声音越大的人不见得越有真本事,当然混沌社会使得声音大的人更易有存在感,但把时间拉长,对的错不了,错的也对不了。
认识龙哥已有 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」,即可免费获取!!
记得点击分享、赞和在看,给我充点儿电吧