在上一篇 最简单的DRM应用程序 (single-buffer)中,我们学习了如何去编写一个最基本的DRM应用程序。而本篇文章,将在 modeset-single-buffer 的基础上,对其进行扩展,使用双buffer机制的案例,来加深大家对drmModeSetCrtc()
函数的印象。
使用上一节中的modeset-single-buffer程序,如果用户想要修改画面内容,就只能对mmap()
后的buffer进行修改,这就会导致用户能很明显的在屏幕上看到软件修改buffer的过程,用户体验大大降低。而双buffer机制则能很好的避免这种问题,双buffer的概念无需过多赘述,大家听名字就知道什么意思了,即前后台buffer切换机制。
伪代码:
int main(void)
{
...
while(1) {
drmModeSetCrtc(fb0);
...
drmModeSetCrtc(fb1);
...
}
...
}
详细参考代码如下:
modeset-double-buffer.c
#define _GNU_SOURCE
#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 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);
}
int main(int argc, char **argv)
{
int fd;
drmModeConnector *conn;
drmModeRes *res;
uint32_t conn_id;
uint32_t crtc_id;
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]);
getchar();
drmModeSetCrtc(fd, crtc_id, buf[1].fb_id,
0, 0, &conn_id, 1, &conn->modes[0]);
getchar();
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-double-buffered.c 文件修改的。和modeset-single-buffer案例一样,为了方便大家关注重点,以上代码删除了许多异常错误处理,并对程序功能做了简化。
从上面的代码我们可以看出,drmModeSetCrtc()
的功能除了可以初始化整条显示pipeline,建立crtc到connector之间的连接关系外,它还可以更新屏幕显示内容,即通过修改fb_id,来完成显示buffer的切换。
有的同学可能会担心,重复调用
drmModeSetCrtc()
会导致硬件链路被重复初始化。其实不必担心,因为DRM驱动框架会对传入的参数进行检查,只要display mode 和 pipeline 链路连接关系没有发生变化,就不会重新初始化硬件。
运行结果:(模拟效果)
描述:程序运行后,屏幕显示红色;输入回车后,屏幕显示蓝色;再次输入回车后,程序退出。
注意:程序运行之前,请确保没有其它应用或服务占用/dev/dri/card0节点,否则将出现 Permission Denied 错误。
源码下载:modeset-double-buffer
参考资料:
David Herrmann’s Blog: Advanced DRM Mode-Setting API
David Herrmann’s Github: drm-howto/modeset-double-buffered.c
文章汇总:DRM (Direct Rendering Manager) 学习简介