在上一篇 最简单的DRM应用程序 (page-flip)中,我们学习了drmModePageFlip()
的用法。而在更早的两篇文章中,我们还学习了drmModeSetCrtc()
的使用方法。但是这两个接口都只能全屏显示framebuffer的内容,如何才能在屏幕上只显示framebuffer的一部分内容呢?本篇我们将一起来学习DRM另一个重要的刷图接口:drmModeSetPlane()
。
在学习该函数之前,我们首先来了解一下,什么是Plane?在开篇 DRM (Direct Rendering Manager) 学习简介 文章中,曾简单描述过Plane的概念,即硬件图层。今天,我们将详细了解下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的纽带,是内存的搬运工。
伪代码:
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,通常具有如下功能:
功能 | 说明 |
---|---|
Crop | 裁剪,如上图 |
Scaling | 缩放,放大或缩小 |
Rotation | 旋转,90° 180° 270° X/Y镜像 |
Z-Order | Z-顺序,调整当前层在总图层中的Z轴顺序 |
Blending | 合成,pixel alpha / global alpha |
Format | 颜色格式,ARGB888 XRGB888 YUV420 等 |
再次强调,以上这些功能都是由硬件直接完成的,而非软件实现。
在DRM框架中,Plane又分为如下3种类型:
类型 | 说明 |
---|---|
Cursor | 光标图层,一般用于PC系统,用于显示鼠标 |
Overlay | 叠加图层,通常用于YUV格式的视频图层 |
Primary | 主要图层,通常用于仅支持RGB格式的简单图层 |
其实随着现代半导体技术的飞速发展,
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。
详细参考代码如下:
modeset-plane-test.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;
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;
}
以上代码参考Google Android工程中external/libdrm/tests/planetest/planetest.c文件,为了演示方便,仅仅实现了一个最简单的drmModeSetPlane()
调用。需要注意的是,该函数调用之前,必须先通过drmModeSetCrtc()
初始化整个显示链路,否则Plane设置将无效。
描述:程序运行后,屏幕显示全屏白色;当输入回车后,屏幕将framebuffer中的(100,150)的矩形,显示到屏幕的(50,50)位置;再次输入回车后,程序退出。
注意:程序运行之前,请确保没有其它应用或服务占用/dev/dri/card0节点,否则将出现 Permission Denied 错误。
源码下载:modeset-plane-test
参考资料:
Android libdrm: planetest.c
文章汇总: DRM (Direct Rendering Manager) 学习简介