天天看点

最简单的DRM应用程序 (plane-test)

在上一篇 最简单的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()

参数含义:

最简单的DRM应用程序 (plane-test)

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

当 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 <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;
}
           

以上代码参考Google Android工程中external/libdrm/tests/planetest/planetest.c文件,为了演示方便,仅仅实现了一个最简单的

drmModeSetPlane()

调用。需要注意的是,该函数调用之前,必须先通过

drmModeSetCrtc()

初始化整个显示链路,否则Plane设置将无效。

运行结果:(模拟效果)

最简单的DRM应用程序 (plane-test)

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

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

源码下载:modeset-plane-test

参考资料:

Android libdrm: planetest.c

文章汇总: DRM (Direct Rendering Manager) 学习简介

继续阅读