《DRM 专栏》| 从应用程序谈起

2022-06-10 16:44:19 浏览数 (2)

声音越大的人不见得越有真本事,当然混沌社会使得声音大的人更易有存在感,但把时间拉长,对的错不了,错的也对不了。

认识龙哥已有 2 年之久,他是很 nice 的一位技术大佬,很荣幸获其授权,在【人人都是极客】发表 DRM 的系列好文。虽然是 4 年前的文章,但放在现在仍然吊打各种技术文。

DRM

DRM是Linux目前主流的图形显示框架,相比FB架构,DRM更能适应当前日益更新的显示硬件。比如FB原生不支持多层合成,不支持VSYNC,不支持DMA-BUF,不支持异步更新,不支持fence机制等等,而这些功能DRM原生都支持。同时DRM可以统一管理GPU和Display驱动,使得软件架构更为统一,方便管理和维护。

DRM从模块上划分,可以简单分为3部分:libdrm、KMS、GEM

libdrm

对底层接口进行封装,向上层提供通用的API接口,主要是对各种IOCTL接口进行封装。

KMS

Kernel Mode Setting,所谓Mode setting,其实说白了就两件事:更新画面和设置显示参数。

  • 更新画面:显示buffer的切换,多图层的合成方式,以及每个图层的显示位置。
  • 设置显示参数:包括分辨率、刷新率、电源状态(休眠唤醒)等。

GEM

Graphic Execution Manager,主要负责显示buffer的分配和释放,也是GPU唯一用到DRM的地方。

基本元素

DRM框架涉及到的元素很多,大致如下:

  • KMS:CRTC,ENCODER,CONNECTOR,PLANE,FB,VBLANK,property
  • GEM:DUMB、PRIME、fence

学习 DRM 驱动其实就是学习上面各个元素的实现及用法,如果你能掌握这些知识点,那么在编写 DRM 驱动的时候就能游刃有余。

为了更好理解 DRM 当中的概念,从应用层开始是个不错的方向。

single-buffer DRM 应用程序

在学习DRM驱动之前,应该首先了解如何使用DRM驱动。以下使用伪代码的方式,简单介绍如何编写一个最简单的DRM应用程序。

伪代码:

代码语言:javascript复制
int main(int argc, char **argv)
{
 /* open the drm device */
 open("/dev/dri/card0");

 /* get crtc/encoder/connector id */
 drmModeGetResources(...);

 /* get connector for display mode */
 drmModeGetConnector(...);

 /* create a dumb-buffer */
 drmIoctl(DRM_IOCTL_MODE_CREATE_DUMB);

 /* bind the dumb-buffer to an FB object */
 drmModeAddFB(...);

 /* map the dumb buffer for userspace drawing */
 drmIoctl(DRM_IOCTL_MODE_MAP_DUMB);
 mmap(...);

 /* start display */
 drmModeSetCrtc(crtc_id, fb_id, connector_id, mode);
}

当执行完 mmap 之后,我们就可以直接在应用层对 framebuffer 进行绘图操作了。

详细参考代码如下:

代码语言:javascript复制
#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 <xf86drm.h>
#include <xf86drmMode.h>

struct buffer_object {
 uint32_t width;
 uint32_t height;
 uint32_t pitch;
 uint32_t handle;
 uint32_t size;
 uint8_t *vaddr;
 uint32_t fb_id;
};

struct buffer_object buf;

static int modeset_create_fb(int fd, struct buffer_object *bo)
{
 struct drm_mode_create_dumb create = {};
  struct drm_mode_map_dumb map = {};

 /* create a dumb-buffer, the pixel format is XRGB888 */
 create.width = bo->width;
 create.height = bo->height;
 create.bpp = 32;
 drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);

 /* bind the dumb-buffer to an FB object */
 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 the dumb-buffer to userspace */
 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);

 /* initialize the dumb-buffer with white-color */
 memset(bo->vaddr, 0xff, bo->size);

 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.width = conn->modes[0].hdisplay;
 buf.height = conn->modes[0].vdisplay;

 modeset_create_fb(fd, &buf);

 drmModeSetCrtc(fd, crtc_id, buf.fb_id,
   0, 0, &conn_id, 1, &conn->modes[0]);

 getchar();

 modeset_destroy_fb(fd, &buf);

 drmModeFreeConnector(conn);
 drmModeFreeResources(res);

 close(fd);

 return 0;
}

上面代码有一个关键的函数,它就是 drmModeSetCrtc(),该函数需要crtc_id、connector_id、fb_id、drm_mode 这几个参数。可以看到,几乎所有的代码都是为了该函数能够顺利传参而编写的:

为了获取 crtc_id 和 connector_id,需要调用 drmModeGetResources() 为了获取 fb_id,需要调用 drmModeAddFB() 为了获取 drm_mode,需要调用 drmModeGetConnector

通过调用 drmModeSetCrtc(),整个底层显示pipeline硬件就都初始化好了,并且还在屏幕上显示出了FB的内容,非常简单。

以上代码其实是基于 kernel DRM maintainer David Herrmann 所写的 drm-howto/modeset.c 文件修改的,需要注意的是,以上参考代码删除了许多异常错误处理,且只有在以下条件都满足时,才能正常运行:

DRM驱动支持MODESET; DRM驱动支持dumb-buffer(即连续物理内存); DRM驱动至少支持1个CRTC,1个Encoder,1个Connector; DRM驱动的Connector至少包含1个有效的drm_display_mode。

运行结果:(模拟效果)

描述:程序运行后,显示全屏白色,等待用户输入按键;当用户按下任意按键后,程序退出,显示黑屏。

注意:程序运行之前,请确保没有其它应用或服务占用 /dev/dri/card0 节点,否则将出现 Permission Denied 错误。

double-buffer DRM 应用程序

现在在上面的基础上,对其进行扩展,使用双 buffer 机制的案例,来加深大家对 drmModeSetCrtc()函数的印象。

如果用户想要修改画面内容,就只能对mmap()后的buffer进行修改,这就会导致用户能很明显的在屏幕上看到软件修改buffer的过程,用户体验大大降低。而双buffer机制则能很好的避免这种问题,双buffer的概念无需过多赘述,大家听名字就知道什么意思了,即前后台buffer切换机制。

伪代码:

代码语言:javascript复制
int main(void)
{
    ...
    while(1) {
        drmModeSetCrtc(fb0);
        ...
        drmModeSetCrtc(fb1);
        ...
    }
    ...
}

详细参考代码如下:

代码语言:javascript复制
#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 <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 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;
}

从上面的代码我们可以看出,drmModeSetCrtc() 的功能除了可以初始化整条显示pipeline,建立crtc到connector之间的连接关系外,它还可以更新屏幕显示内容,即通过修改fb_id,来完成显示buffer的切换。

有的同学可能会担心,重复调用 drmModeSetCrtc()会导致硬件链路被重复初始化。其实不必担心,因为DRM驱动框架会对传入的参数进行检查,只要display mode 和 pipeline 链路连接关系没有发生变化,就不会重新初始化硬件。

运行结果:(模拟效果)

描述:程序运行后,屏幕显示红色;输入回车后,屏幕显示蓝色;再次输入回车后,程序退出。

注意:程序运行之前,请确保没有其它应用或服务占用/dev/dri/card0节点,否则将出现 Permission Denied 错误。

page-flip DRM 应用程序

我们了解了DRM更新图像的一个重要接口 drmModeSetCrtc()。现在,我们将一起来学习DRM另一个重要的刷图接口:drmModePageFlip()。

drmModePageFlip() 的功能也是用于更新显示内容的,但是它和 drmModeSetCrtc()最大的区别在于:drmModePageFlip() 只会等到 VSYNC 到来后才会真正执行 framebuffer 切换动作,而 drmModeSetCrtc() 则会立即执行 framebuffer 切换动作。很明显,drmModeSetCrtc() 对于某些硬件来说,很容易造成撕裂(tear effect)问题,而 drmModePageFlip() 则不会造成这种问题。

由于 drmModePageFlip()本身是基于VSYNC事件机制的,因此底层DRM驱动必须支持VBLANK事件。

伪代码:

代码语言:javascript复制
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);
 }
}

详细参考代码如下:

代码语言:javascript复制
#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;
}

从上面的代码可以看出,要使用 drmModePageFlip(),就必须依赖 drmHandleEvent()函数,该函数内部以阻塞的形式等待底层驱动返回相应的 vblank 事件,以确保和 VSYNC 同步。需要注意的是,drmModePageFlip() 不允许在 1 个 VSYNC 周期内被调用多次,否则只有第一次调用有效,后面几次调用都会返回 -EBUSY 错误(-16)。

运行结果:(模拟效果)

描述:程序运行后,屏幕在红色和蓝色之间来回切换;当输入CTRL C后,程序退出。

注意:程序运行之前,请确保没有其它应用或服务占用/dev/dri/card0节点,否则将出现 Permission Denied 错误。

plane-test DRM 应用程序

上面我们学习了 drmModeSetCrtc() 和 drmModePageFlip() 的用法,但是这两个接口都只能全屏显示 framebuffer 的内容,如何才能在屏幕上只显示 framebuffer 的一部分内容呢?本篇我们将一起来学习 DRM 另一个重要的刷图接口:drmModeSetPlane()。

在学习该函数之前,我们首先来了解一下,什么是 Plane?

DRM 中的 Plane 和我们常说的 YUV/YCbCr 图形格式中的 plane 完全是两个不同的概念。YUV 图形格式中的 plane 指的是图像数据在内存中的排列形式,一般 Y 通道占一段连续的内存块,UV 通道占另一段连续的内存块,我们称之为 YUV-2plane (也叫 YUV 2平面),属于软件层面。而 DRM 中的 Plane 指的是 Display Controller 中用于多层合成的单个硬件图层模块,属于硬件层面。二者概念上不要混淆。

Plane 的历史: 随着软件技术的不断更新,对硬件的性能要求越来越高,在满足功能正常使用的前提下,对功耗的要求也越来越苛刻。本来GPU可以处理所有图形任务,但是由于它运行时的功耗实在太高,设计者们决定将一部分简单的任务交给Display Controller去处理(比如合成),而让GPU专注于绘图(即渲染)这一主要任务,减轻GPU的负担,从而达到降低功耗提升性能的目的。于是,Plane(硬件图层单元)就诞生了。

Plane 是连接 FB 与 CRTC 的纽带,是内存的搬运工。

伪代码:

代码语言:javascript复制
int main(void)
{
 ...
 drmSetClientCap(DRM_CLIENT_CAP_UNIVERSAL_PLANES);
 drmModeGetPlaneResources();

 drmModeSetPlane(plane_id, crtc_id, fb_id, 0,
   crtc_x, crtc_y, crtc_w, crtc_h,
   src_x << 16, src_y << 16, src_w << 16, src_h << 16);
 ...
}

先来了解一下 drmModeSetPlane() 参数含义:

(上图实现了裁剪、平移和放大的效果)

当 SRC 与 CRTC 的 X/Y 不相等时,则实现了平移的效果; 当 SRC 与 CRTC 的 W/H 不相等时,则实现了缩放的效果; 当 SRC 与 FrameBuffer 的 W/H 不相等时,则实现了裁剪的效果;

一个高级的 Plane,通常具有如下功能:

再次强调,以上这些功能都是由硬件直接完成的,而非软件实现。

在 DRM 框架中,Plane 又分为如下3种类型:

其实随着现代半导体技术的飞速发展,Overlay Plane 和 Primary Plane 之间已经没有明显的界限了,许多芯片的图层处理能力已经非常强大,不仅仅可以处理简单的 RGB 格式,也可以处理 YUV 视频格式,甚至 FBC 压缩格式。针对这类硬件图层,它既可以是 Overlay Plane,也可以是 Primary Plane,至于驱动如何定义,就要看工程师的喜好了。 而对于一些早期处理能力比较弱的硬件,为了节约成本,每个图层支持的格式并不一样,比如将平常使用格式最多的 RGB 图层作为 Primary Plane,而将平时用不多的 YUV 视频图层作为 Overlay Plane,那么这个时候上层应用程序在使用这两种 plane 的时候就需要区别对待了。

需要注意的是,并不是所有的 Display Controller 都支持 Plane,从前面 single-buffer 案例中的 drmModeSetCrtc() 函数也能看出,即使没有 plane_id,屏幕也能正常显示。比如 s3c2440 这种骨灰级 ARM9 SoC,它的 LCDC 就没有 Plane 的概念。但是 DRM 框架规定,任何一个 CRTC,必须要有 1 个 Primary Plane。 即使像 S3C2440 这种不带真实 Plane 硬件的 Display Controller,我们也认为它的 Primary Plane 就是 LCDC 本身,因为它实现了从 Framebuffer 到 CRTC 的数据搬运工作,而这正是一个 Plane 最基本的功能。

为什么要设置DRM_CLIENT_CAP_UNIVERSAL_PLANES ?

因为如果不设置,drmModeGetPlaneResources() 就只会返回 Overlay Plane,其他 Plane 都不会返回。而如果设置了,DRM 驱动则会返回所有支持的 Plane 资源,包括 cursor、overlay 和 primary。

详细参考代码如下:

代码语言:javascript复制
#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 <xf86drm.h>
#include <xf86drmMode.h>

struct buffer_object {
 uint32_t width;
 uint32_t height;
 uint32_t pitch;
 uint32_t handle;
 uint32_t size;
 uint8_t *vaddr;
 uint32_t fb_id;
};

struct buffer_object buf;

static int modeset_create_fb(int fd, struct buffer_object *bo)
{
 struct drm_mode_create_dumb create = {};
  struct drm_mode_map_dumb map = {};

 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);

 memset(bo->vaddr, 0xff, bo->size);

 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;
 drmModePlaneRes *plane_res;
 uint32_t conn_id;
 uint32_t crtc_id;
 uint32_t plane_id;

 fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);

 res = drmModeGetResources(fd);
 crtc_id = res->crtcs[0];
 conn_id = res->connectors[0];

 drmSetClientCap(fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);
 plane_res = drmModeGetPlaneResources(fd);
 plane_id = plane_res->planes[0];

 conn = drmModeGetConnector(fd, conn_id);
 buf.width = conn->modes[0].hdisplay;
 buf.height = conn->modes[0].vdisplay;

 modeset_create_fb(fd, &buf);

 drmModeSetCrtc(fd, crtc_id, buf.fb_id,
   0, 0, &conn_id, 1, &conn->modes[0]);

 getchar();

 /* crop the rect from framebuffer(100, 150) to crtc(50, 50) */
 drmModeSetPlane(fd, plane_id, crtc_id, buf.fb_id, 0,
   50, 50, 320, 320,
   100 << 16, 150 << 16, 320 << 16, 320 << 16);

 getchar();

 modeset_destroy_fb(fd, &buf);

 drmModeFreeConnector(conn);
 drmModeFreePlaneResources(plane_res);
 drmModeFreeResources(res);

 close(fd);

 return 0;
}

为了演示方便,仅仅实现了一个最简单的 drmModeSetPlane() 调用。需要注意的是,该函数调用之前,必须先通过 drmModeSetCrtc() 初始化整个显示链路,否则 Plane 设置将无效。

运行结果:(模拟效果)

描述:程序运行后,屏幕显示全屏白色;当输入回车后,屏幕将framebuffer中的(100,150)的矩形,显示到屏幕的(50,50)位置;再次输入回车后,程序退出。

注意:程序运行之前,请确保没有其它应用或服务占用/dev/dri/card0节点,否则将出现 Permission Denied 错误。

5T技术资源大放送!包括但不限于:C/C ,Arm, Linux,Android,人工智能,单片机,树莓派,等等。在上面的【人人都是极客】公众号内回复「peter」,即可免费获取!!

记得点击分享、赞和在看,给我充点儿电吧

0 人点赞