在上一篇 最简单的DRM应用程序 (double-buffer)中,我们了解了DRM更新图像的一个重要接口drmModeSetCrtc()
。在本篇文章中,我们将一起来学习DRM另一个重要的刷图接口:drmModePageFlip()
。
drmModePageFlip()
的功能也是用于更新显示内容的,但是它和drmModeSetCrtc()
最大的区别在于,drmModePageFlip()
只会等到VSYNC到来后才会真正执行framebuffer切换动作,而drmModeSetCrtc()
则会立即执行framebuffer切换动作。很明显,drmModeSetCrtc()
对于某些硬件来说,很容易造成 撕裂(tear effect)问题,而drmModePageFlip()
则不会造成这种问题。
由于drmModePageFlip()
本身是基于VSYNC事件机制的,因此底层DRM驱动必须支持VBLANK事件。
伪代码:
void my_page_flip_handler(...)
{
drmModePageFlip(DRM_MODE_PAGE_FLIP_EVENT);
...
}
int main(void)
{
drmEventContext ev = {};
ev.version = DRM_EVENT_CONTEXT_VERSION;
ev.page_flip_handler = my_page_flip_handler;
...
drmModePageFlip(DRM_MODE_PAGE_FLIP_EVENT);
while (1) {
drmHandleEvent(&ev);
}
}
详细参考代码如下:
modeset-page-flip.c
#define _GNU_SOURCE
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct buffer_object {
uint32_t width;
uint32_t height;
uint32_t pitch;
uint32_t handle;
uint32_t size;
uint32_t *vaddr;
uint32_t fb_id;
};
struct buffer_object buf[2];
static int terminate;
static int modeset_create_fb(int fd, struct buffer_object *bo, uint32_t color)
{
struct drm_mode_create_dumb create = {};
struct drm_mode_map_dumb map = {};
uint32_t i;
create.width = bo->width;
create.height = bo->height;
create.bpp = 32;
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
bo->pitch = create.pitch;
bo->size = create.size;
bo->handle = create.handle;
drmModeAddFB(fd, bo->width, bo->height, 24, 32, bo->pitch,
bo->handle, &bo->fb_id);
map.handle = create.handle;
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
bo->vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, map.offset);
for (i = 0; i < (bo->size / 4); i++)
bo->vaddr[i] = color;
return 0;
}
static void modeset_destroy_fb(int fd, struct buffer_object *bo)
{
struct drm_mode_destroy_dumb destroy = {};
drmModeRmFB(fd, bo->fb_id);
munmap(bo->vaddr, bo->size);
destroy.handle = bo->handle;
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
}
static void modeset_page_flip_handler(int fd, uint32_t frame,
uint32_t sec, uint32_t usec,
void *data)
{
static int i = 0;
uint32_t crtc_id = *(uint32_t *)data;
i ^= 1;
drmModePageFlip(fd, crtc_id, buf[i].fb_id,
DRM_MODE_PAGE_FLIP_EVENT, data);
usleep(500000);
}
static void sigint_handler(int arg)
{
terminate = 1;
}
int main(int argc, char **argv)
{
int fd;
drmEventContext ev = {};
drmModeConnector *conn;
drmModeRes *res;
uint32_t conn_id;
uint32_t crtc_id;
/* register CTRL+C terminate interrupt */
signal(SIGINT, sigint_handler);
ev.version = DRM_EVENT_CONTEXT_VERSION;
ev.page_flip_handler = modeset_page_flip_handler;
fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
res = drmModeGetResources(fd);
crtc_id = res->crtcs[0];
conn_id = res->connectors[0];
conn = drmModeGetConnector(fd, conn_id);
buf[0].width = conn->modes[0].hdisplay;
buf[0].height = conn->modes[0].vdisplay;
buf[1].width = conn->modes[0].hdisplay;
buf[1].height = conn->modes[0].vdisplay;
modeset_create_fb(fd, &buf[0], 0xff0000);
modeset_create_fb(fd, &buf[1], 0x0000ff);
drmModeSetCrtc(fd, crtc_id, buf[0].fb_id,
0, 0, &conn_id, 1, &conn->modes[0]);
drmModePageFlip(fd, crtc_id, buf[0].fb_id,
DRM_MODE_PAGE_FLIP_EVENT, &crtc_id);
while (!terminate) {
drmHandleEvent(fd, &ev);
}
modeset_destroy_fb(fd, &buf[1]);
modeset_destroy_fb(fd, &buf[0]);
drmModeFreeConnector(conn);
drmModeFreeResources(res);
close(fd);
return 0;
}
以上代码是基于David Herrmann 所写的 drm-howto/modeset-vsync.c 文件修改的。和前两篇的案例一样,为了方便大家关注重点,以上代码删除了许多异常错误处理,并对程序功能做了简化。
从上面的代码可以看出,要使用drmModePageFlip()
,就必须依赖drmHandleEvent()
函数,该函数内部以阻塞的形式等待底层驱动返回相应的vblank事件,以确保和VSYNC同步。需要注意的是,drmModePageFlip()
不允许在1个VSYNC周期内被调用多次,否则只有第一次调用有效,后面几次调用都会返回-EBUSY
错误(-16)。
运行结果:(模拟效果)
描述:程序运行后,屏幕在红色和蓝色之间来回切换;当输入CTRL+C
后,程序退出。
注意:程序运行之前,请确保没有其它应用或服务占用/dev/dri/card0节点,否则将出现 Permission Denied 错误。
源码下载:modeset-page-flip
参考资料:
David Herrmann’s Blog: Advanced DRM Mode-Setting API
David Herrmann’s Github: drm-howto/modeset-vsync.c
文章汇总: DRM (Direct Rendering Manager) 学习简介