在上一篇 最簡單的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()
參數含義:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbw5iN1ITNxADM0ETMxAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
(上圖實作了裁剪、平移和放大的效果)
當 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
之間已經沒有明顯的界限了,許多晶片的圖層處理能力已經非常強大,不僅僅可以處理簡單的RGB格式,也可以處理YUV視訊格式,甚至FBC壓縮格式。針對這類硬體圖層,它既可以是
Primary Plane
,也可以是
Overlay Plane
Primary Plane
,至于驅動如何定義,就要看工程師的喜好了。
而對于一些早期處理能力比較弱的硬體,為了節約成本,每個圖層支援的格式并不一樣,比如将平常使用格式最多的RGB圖層作為
,而将平時用不多的YUV視訊圖層作為
Primary Plane
,那麼這個時候上層應用程式在使用這兩種plane的時候就需要差別對待了。
Overlay 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設定将無效。
運作結果:(模拟效果)
描述:程式運作後,螢幕顯示全屏白色;當輸入回車後,螢幕将framebuffer中的(100,150)的矩形,顯示到螢幕的(50,50)位置;再次輸入回車後,程式退出。
注意:程式運作之前,請確定沒有其它應用或服務占用/dev/dri/card0節點,否則将出現 Permission Denied 錯誤。
源碼下載下傳:modeset-plane-test
參考資料:
Android libdrm: planetest.c
文章彙總: DRM (Direct Rendering Manager) 學習簡介