最簡單的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, src_y, src_w << 16, src_h << 16);
	...
}

先來了解一下drmModeSetPlane()參數含義:

plane-draft.png-42.4kB
(上圖實現了裁剪、平移和放大的效果)

當 SRC_X/Y 和 CRTC_X/Y 不相等時,就實現了平移的效果;
當 SRC_W/H 和 CRTC_W/H 不相等時,就實現了縮放的效果;
當 SRC_W/H 和 FB_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 PlanePrimary 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, 150, 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設置將無效。

運行結果:(模擬效果)
plane-test.gif-61.5kB
描述:程序運行後,屏幕顯示全屏白色;當輸入回車後,屏幕將framebuffer中的(100,150)的矩形,顯示到屏幕的(50,50)位置;再次輸入回車後,程序退出。

源碼下載:modeset-plane-test

參考資料:
Android libdrm: planetest.c

文章彙總: DRM (Direct Rendering Manager) 學習簡介

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章