天天看點

最簡單的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) 學習簡介

繼續閱讀