在上一篇 最簡單的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()
參數含義:
(上圖實現了裁剪、平移和放大的效果)
當 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 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, 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設置將無效。
運行結果:(模擬效果)
描述:程序運行後,屏幕顯示全屏白色;當輸入回車後,屏幕將framebuffer中的(100,150)的矩形,顯示到屏幕的(50,50)位置;再次輸入回車後,程序退出。
源碼下載:modeset-plane-test
參考資料:
Android libdrm: planetest.c