当前位置: 代码迷 >> 综合 >> 最简单的DRM应用程序 (page-flip)
  详细解决方案

最简单的DRM应用程序 (page-flip)

热度:42   发布时间:2023-12-16 11:57:54.0

!!!声明!!!
本文章转自:何小龙
链接:https://blog.csdn.net/hexiaolong2009/article/details/84674127
转载只是为了学习备份。

在上一篇 最简单的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 <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include <signal.h>
#include <xf86drm.h>
#include <xf86drmMode.h>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) 学习简介

  相关解决方案