前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >《DRM 专栏》| 从应用程序谈起

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

作者头像
刘盼
发布2022-06-10 16:44:19
3.3K1
发布2022-06-10 16:44:19
举报
文章被收录于专栏:人人都是极客人人都是极客

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

认识龙哥已有 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」,即可免费获取!!

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

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-06-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 人人都是极客 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • DRM
    • libdrm
      • KMS
        • GEM
        • 基本元素
        • single-buffer DRM 应用程序
        • double-buffer DRM 应用程序
        • page-flip DRM 应用程序
        • plane-test DRM 应用程序
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档