6. Buffer和Surface

[TOC]
顯然,這個(gè)系統(tǒng)的全部目的是向用戶顯示信息,并接收他們的反饋以進(jìn)行額外的處理。在本章中,我們將探討這些任務(wù)中的第一個(gè):在屏幕上顯示像素。

為此,我們使用兩個(gè)原始對象,即緩沖區(qū)和表面,它們分別由wl_buffer和wl_surface接口管理。緩沖區(qū)充當(dāng)不透明容器,用于存儲(chǔ)一些底層像素,并且客戶端通過一系列方法提供這些緩沖區(qū) - 最常見的是共享內(nèi)存緩沖區(qū)和GPU句柄。

6.1 使用wl_compositor

他們說命名是計(jì)算機(jī)科學(xué)中最困難的問題之一,我們在這里,手里有證據(jù)。wl_compositor全局是Wayland合成器的合成器。通過這個(gè)接口,你可以將你的窗口發(fā)送到服務(wù)器進(jìn)行演示,與其他顯示的窗口一起進(jìn)行合成。合成器有兩項(xiàng)工作:創(chuàng)建表面和區(qū)域。

引用規(guī)范,Wayland表面有一個(gè)矩形區(qū)域,可以顯示在零個(gè)或多個(gè)輸出、當(dāng)前緩沖區(qū)、接收用戶輸入,并定義一個(gè)本地坐標(biāo)系。我們稍后將詳細(xì)介紹所有這些,但讓我們從基礎(chǔ)知識(shí)開始:獲取表面并將緩沖區(qū)附加到它上面。要獲取表面,我們首先綁定到wl_compositor全局。通過擴(kuò)展第5.1章的示例,我們可以得到以下內(nèi)容:

struct our_state {
    // ...
    struct wl_compositor *compositor;
    // ...
};

static void
registry_handle_global(void *data, struct wl_registry *wl_registry,
        uint32_t name, const char *interface, uint32_t version)
{
    struct our_state *state = data;
    if (strcmp(interface, wl_compositor_interface.name) == 0) {
        state->compositor = wl_registry_bind(
            wl_registry, name, &wl_compositor_interface, 4);
    }
}

int
main(int argc, char *argv[])
{
    struct our_state state = { 0 };
    // ...
    wl_registry_add_listener(registry, &registry_listener, &state);
    // ...
}

請注意,我們在調(diào)用wl_registry_bind時(shí)指定了版本4,這是在編寫時(shí)最新的版本。有了這個(gè)參考,我們可以創(chuàng)建一個(gè)wl_surface:

struct wl_surface *surface = wl_compositor_create_surface(state.compositor);

在我們展示之前,我們必須首先將其附加到一個(gè)像素源上:一個(gè)wl_buffer。

6.2 共享內(nèi)存Buffer

從客戶端向合成器獲取像素的最簡單方法,并且在wayland.xml中唯一規(guī)定的方法是wl_shm-共享內(nèi)存。簡單來說,它允許您將一個(gè)文件描述符傳遞給合成器,以便使用MAP_SHARED進(jìn)行mmap,然后從這個(gè)池中共享像素緩沖區(qū)。添加一些簡單的同步原語,以防止每個(gè)人都在爭奪每個(gè)緩沖區(qū),您就會(huì)得到一個(gè)可行且可移植的解決方案。

綁定到wl_shm

第5.1章中介紹的注冊表全局偵聽器將在可用時(shí)宣傳wl_shm全局。綁定到它相當(dāng)直接。擴(kuò)展第5.1章中給出的示例,我們得到以下內(nèi)容:

struct our_state {
    // ...
    struct wl_shm *shm;
    // ...
};

static void
registry_handle_global(void *data, struct wl_registry *registry,
        uint32_t name, const char *interface, uint32_t version)
{
    struct our_state *state = data;
    if (strcmp(interface, wl_shm_interface.name) == 0) {
        state->shm = wl_registry_bind(
            wl_registry, name, &wl_shm_interface, 1);
    }
}

int
main(int argc, char *argv[])
{
    struct our_state state = { 0 };
    // ...
    wl_registry_add_listener(registry, &registry_listener, &state);
    // ...
}

綁定后,我們可以通過wl_shm_add_listener添加一個(gè)偵聽器。合成器將通過此偵聽器宣傳其支持的像素格式。可能的像素格式的完整列表在wayland.xml中給出。需要支持兩種格式:ARGB8888和XRGB8888,它們分別是24位顏色,具有和不具有alpha通道。

分配共享內(nèi)存池

POSIX shm_open和隨機(jī)文件名的組合可用于創(chuàng)建適合此目的的文件,ftruncate可用于將其調(diào)整到適當(dāng)?shù)拇笮 R韵履0蹇梢栽诠差I(lǐng)域或CC0下自由使用

#define _POSIX_C_SOURCE 200112L
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>

static void
randname(char *buf)
{
    struct timespec ts;
    clock_gettime(CLOCK_REALTIME, &ts);
    long r = ts.tv_nsec;
    for (int i = 0; i < 6; ++i) {
        buf[i] = 'A'+(r&15)+(r&16)*2;
        r >>= 5;
    }
}

static int
create_shm_file(void)
{
    int retries = 100;
    do {
        char name[] = "/wl_shm-XXXXXX";
        randname(name + sizeof(name) - 7);
        --retries;
        int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
        if (fd >= 0) {
            shm_unlink(name);
            return fd;
        }
    } while (retries > 0 && errno == EEXIST);
    return -1;
}

int
allocate_shm_file(size_t size)
{
    int fd = create_shm_file();
    if (fd < 0)
        return -1;
    int ret;
    do {
        ret = ftruncate(fd, size);
    } while (ret < 0 && errno == EINTR);
    if (ret < 0) {
        close(fd);
        return -1;
    }
    return fd;
}

希望代碼相當(dāng)容易理解(最后一句名言)。有了這個(gè),客戶端可以相當(dāng)容易地創(chuàng)建一個(gè)共享內(nèi)存池。比如說,假設(shè)我們要顯示一個(gè)1920x1080的窗口。我們需要兩個(gè)緩沖區(qū)來進(jìn)行雙緩沖,所以需要4147200個(gè)像素。假設(shè)像素格式是WL_SHM_FORMAT_XRGB8888,每個(gè)像素需要4個(gè)字節(jié),因此總池大小為16588800字節(jié)。按第5.1章中所述,綁定到注冊表中的wl_shm全局,然后像這樣使用它來創(chuàng)建一個(gè)可以容納這些緩沖區(qū)的shm池:

const int width = 1920, height = 1080;
const int stride = width * 4;
const int shm_pool_size = height * stride * 2;

int fd = allocate_shm_file(shm_pool_size);
uint8_t *pool_data = mmap(NULL, shm_pool_size,
    PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

struct wl_shm *shm = ...; // Bound from registry
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, shm_pool_size);

從池中創(chuàng)建緩沖區(qū)

一旦此消息傳到合成器,它將mmap這個(gè)文件描述符。不過,Wayland是異步的,所以我們可以立即從這個(gè)池中開始分配緩沖區(qū)。由于我們?yōu)閮蓚€(gè)緩沖區(qū)分配了空間,我們可以為每個(gè)緩沖區(qū)分配一個(gè)索引,并將該索引轉(zhuǎn)換為池中的字節(jié)偏移量。有了這些信息,我們就可以創(chuàng)建一個(gè)wl_buffer:

int index = 0;
int offset = height * stride * index;
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, offset,
    width, height, stride, WL_SHM_FORMAT_XRGB8888);

我們現(xiàn)在可以將圖像寫入此緩沖區(qū)。例如,將其設(shè)置為純白色:

uint32_t *pixels = (uint32_t *)&pool_data[offset];
memset(pixels, 0, width * height * 4);

或者,為了更有趣的東西,這是一個(gè)棋盤圖案:

uint32_t *pixels = (uint32_t *)&pool_data[offset];
for (int y = 0; y < height; ++y) {
  for (int x = 0; x < width; ++x) {
    if ((x + y / 8 * 8) % 16 < 8) {
      pixels[y * width + x] = 0xFF666666;
    } else {
      pixels[y * width + x] = 0xFFEEEEEE;
    }
  }
}

設(shè)置好舞臺(tái)后,我們將緩沖區(qū)附加到我們的表面上,將整個(gè)表面標(biāo)記為Damaged 1,并提交:

wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0, UINT32_MAX, UINT32_MAX);
wl_surface_commit(surface);

如果你將所有這些新獲得的知識(shí)應(yīng)用到自己編寫Wayland客戶端上,那么在緩沖區(qū)沒有顯示在屏幕上時(shí),你可能會(huì)感到困惑。我們?nèi)鄙僖粋€(gè)關(guān)鍵的最后一步——為你的表面分配一個(gè)角色。

1 “Damaged”意思是“這個(gè)區(qū)域需要重新繪制”

服務(wù)器端的wl_shm

然而,在到達(dá)那里之前,我們需要注意一下服務(wù)器端的部分。libwayland提供了一些幫助程序,使得使用wl_shm更加容易。為了為您的顯示器配置它,它只需要以下內(nèi)容:

int
wl_display_init_shm(struct wl_display *display);

uint32_t *
wl_display_add_shm_format(struct wl_display *display, uint32_t format);

前者創(chuàng)建全局并配置內(nèi)部實(shí)現(xiàn),后者添加受支持的像素格式(記得至少添加ARGB8888和XRGB8888)。一旦客戶端將其緩沖區(qū)附加到其表面之一,您可以將緩沖區(qū)資源傳遞給wl_shm_buffer_get以獲取wl_shm_buffer引用,并像這樣使用它:

void
wl_shm_buffer_begin_access(struct wl_shm_buffer *buffer);

void
wl_shm_buffer_end_access(struct wl_shm_buffer *buffer);

void *
wl_shm_buffer_get_data(struct wl_shm_buffer *buffer);

int32_t
wl_shm_buffer_get_stride(struct wl_shm_buffer *buffer);

uint32_t
wl_shm_buffer_get_format(struct wl_shm_buffer *buffer);

int32_t
wl_shm_buffer_get_width(struct wl_shm_buffer *buffer);

int32_t
wl_shm_buffer_get_height(struct wl_shm_buffer *buffer);

如果您使用begin_access和end_access來保護(hù)對緩沖區(qū)數(shù)據(jù)的訪問,libwayland將負(fù)責(zé)鎖定。

6.3 Linux dmabuf

大多數(shù)Wayland合成器在GPU上進(jìn)行渲染,許多Wayland客戶端也在GPU上進(jìn)行渲染。使用共享內(nèi)存方法,從客戶端向合成器發(fā)送緩沖區(qū)在這種情況下效率非常低,因?yàn)榭蛻舳吮仨殢腉PU讀取數(shù)據(jù)到CPU,然后合成器必須從CPU讀取數(shù)據(jù)回到GPU進(jìn)行渲染。

Linux DRM(直接渲染管理器)接口(也在一些BSD上實(shí)現(xiàn))為我們提供了導(dǎo)出GPU資源句柄的方法。Mesa是用戶空間Linux圖形驅(qū)動(dòng)程序的主要實(shí)現(xiàn),它實(shí)現(xiàn)了一個(gè)協(xié)議,允許EGL用戶將句柄從客戶端傳輸?shù)胶铣善鞯腉PU緩沖區(qū)進(jìn)行渲染,而無需將數(shù)據(jù)復(fù)制到CPU。

該協(xié)議的內(nèi)部工作方式超出了本書的范圍,更適合于專注于Mesa或Linux DRM的資源。然而,我們可以簡要總結(jié)它的用法。

  • 使用eglGetPlatformDisplayEXT與EGL_PLATFORM_WAYLAND_KHR一起創(chuàng)建一個(gè)EGL顯示。
  • 正常配置顯示,選擇適合您情況的配置,并將EGL_SURFACE_TYPE設(shè)置為EGL_WINDOW_BIT。
  • 使用wl_egl_window_create為給定的wl_surface創(chuàng)建一個(gè)wl_egl_window。
  • 使用eglCreatePlatformWindowSurfaceEXT創(chuàng)建一個(gè)EGLSurface的wl_egl_window。
  • 繼續(xù)使用EGL正常操作,例如eglMakeCurrent將EGL上下文設(shè)置為當(dāng)前上下文,并使用eglSwapBuffers將更新的緩沖區(qū)發(fā)送給合成器并提交表面。

如果您稍后需要更改wl_egl_window的大小,請使用wl_egl_window_resize。

內(nèi)部實(shí)現(xiàn)

一些不使用libwayland的Wayland程序員抱怨說,這種方法將Mesa和libwayland緊密地聯(lián)系在一起,這是事實(shí)。然而,解開它們并不是不可能的-它只是需要您自己實(shí)現(xiàn)linux-dmabuf的大量工作。查閱Wayland擴(kuò)展XML以獲取有關(guān)協(xié)議的詳細(xì)信息,以及Mesa在src / egl / drivers / dri2 / platform_wayland.c中的實(shí)現(xiàn)(在編寫時(shí))。祝你好運(yùn),祝順利。

對于服務(wù)器

不幸的是,合成器的細(xì)節(jié)既復(fù)雜又超出了本書的范圍。然而,我可以為你指明正確的方向:wlroots實(shí)現(xiàn)(在types / wlr_linux_dmabuf_v1.c中編寫)是直截了當(dāng)?shù)模瑧?yīng)該讓你走上正確的道路。

6.4 Surface角色Role

我們已經(jīng)創(chuàng)建了一個(gè)像素緩沖區(qū),將其發(fā)送到服務(wù)器,并通過一個(gè)表面附加它,據(jù)稱可以向用戶顯示它。然而,要讓表面具有意義,缺少一個(gè)關(guān)鍵的環(huán)節(jié),那就是它的角色(Role)。

有很多不同的情況可能會(huì)向用戶顯示像素緩沖區(qū),每種情況都需要不同的語義。一些例子包括應(yīng)用程序窗口,當(dāng)然,但其他例子包括光標(biāo)圖像或桌面壁紙。為了對比應(yīng)用程序窗口與光標(biāo)的語義,請考慮光標(biāo)無法最小化,應(yīng)用程序窗口不應(yīng)粘在鼠標(biāo)上。因此,角色提供了另一層抽象,允許您為表面分配適當(dāng)?shù)恼Z義。

您可能迫不及待地想給它分配的角色,在第6章中介紹了一種機(jī)制來實(shí)現(xiàn)這一點(diǎn):XDG shell。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評(píng)論 6 533
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,556評(píng)論 3 418
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評(píng)論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評(píng)論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,778評(píng)論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,218評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評(píng)論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,436評(píng)論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,969評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,795評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,993評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評(píng)論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,229評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評(píng)論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,687評(píng)論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,990評(píng)論 2 374

推薦閱讀更多精彩內(nèi)容