[TOC]
顯然,這個系統的全部目的是向用戶顯示信息,并接收他們的反饋以進行額外的處理。在本章中,我們將探討這些任務中的第一個:在屏幕上顯示像素。
為此,我們使用兩個原始對象,即緩沖區和表面,它們分別由wl_buffer和wl_surface接口管理。緩沖區充當不透明容器,用于存儲一些底層像素,并且客戶端通過一系列方法提供這些緩沖區 - 最常見的是共享內存緩沖區和GPU句柄。
6.1 使用wl_compositor
他們說命名是計算機科學中最困難的問題之一,我們在這里,手里有證據。wl_compositor全局是Wayland合成器的合成器。通過這個接口,你可以將你的窗口發送到服務器進行演示,與其他顯示的窗口一起進行合成。合成器有兩項工作:創建表面和區域。
引用規范,Wayland表面有一個矩形區域,可以顯示在零個或多個輸出、當前緩沖區、接收用戶輸入,并定義一個本地坐標系。我們稍后將詳細介紹所有這些,但讓我們從基礎知識開始:獲取表面并將緩沖區附加到它上面。要獲取表面,我們首先綁定到wl_compositor全局。通過擴展第5.1章的示例,我們可以得到以下內容:
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, ®istry_listener, &state);
// ...
}
請注意,我們在調用wl_registry_bind時指定了版本4,這是在編寫時最新的版本。有了這個參考,我們可以創建一個wl_surface:
struct wl_surface *surface = wl_compositor_create_surface(state.compositor);
在我們展示之前,我們必須首先將其附加到一個像素源上:一個wl_buffer。
6.2 共享內存Buffer
從客戶端向合成器獲取像素的最簡單方法,并且在wayland.xml中唯一規定的方法是wl_shm-共享內存。簡單來說,它允許您將一個文件描述符傳遞給合成器,以便使用MAP_SHARED進行mmap,然后從這個池中共享像素緩沖區。添加一些簡單的同步原語,以防止每個人都在爭奪每個緩沖區,您就會得到一個可行且可移植的解決方案。
綁定到wl_shm
第5.1章中介紹的注冊表全局偵聽器將在可用時宣傳wl_shm全局。綁定到它相當直接。擴展第5.1章中給出的示例,我們得到以下內容:
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, ®istry_listener, &state);
// ...
}
綁定后,我們可以通過wl_shm_add_listener添加一個偵聽器。合成器將通過此偵聽器宣傳其支持的像素格式??赡艿南袼馗袷降耐暾斜碓趙ayland.xml中給出。需要支持兩種格式:ARGB8888和XRGB8888,它們分別是24位顏色,具有和不具有alpha通道。
分配共享內存池
POSIX shm_open和隨機文件名的組合可用于創建適合此目的的文件,ftruncate可用于將其調整到適當的大小。以下模板可以在公共領域或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;
}
希望代碼相當容易理解(最后一句名言)。有了這個,客戶端可以相當容易地創建一個共享內存池。比如說,假設我們要顯示一個1920x1080的窗口。我們需要兩個緩沖區來進行雙緩沖,所以需要4147200個像素。假設像素格式是WL_SHM_FORMAT_XRGB8888,每個像素需要4個字節,因此總池大小為16588800字節。按第5.1章中所述,綁定到注冊表中的wl_shm全局,然后像這樣使用它來創建一個可以容納這些緩沖區的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);
從池中創建緩沖區
一旦此消息傳到合成器,它將mmap這個文件描述符。不過,Wayland是異步的,所以我們可以立即從這個池中開始分配緩沖區。由于我們為兩個緩沖區分配了空間,我們可以為每個緩沖區分配一個索引,并將該索引轉換為池中的字節偏移量。有了這些信息,我們就可以創建一個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);
我們現在可以將圖像寫入此緩沖區。例如,將其設置為純白色:
uint32_t *pixels = (uint32_t *)&pool_data[offset];
memset(pixels, 0, width * height * 4);
或者,為了更有趣的東西,這是一個棋盤圖案:
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;
}
}
}
設置好舞臺后,我們將緩沖區附加到我們的表面上,將整個表面標記為Damaged 1,并提交:
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0, UINT32_MAX, UINT32_MAX);
wl_surface_commit(surface);
如果你將所有這些新獲得的知識應用到自己編寫Wayland客戶端上,那么在緩沖區沒有顯示在屏幕上時,你可能會感到困惑。我們缺少一個關鍵的最后一步——為你的表面分配一個角色。
1 “Damaged”意思是“這個區域需要重新繪制”
服務器端的wl_shm
然而,在到達那里之前,我們需要注意一下服務器端的部分。libwayland提供了一些幫助程序,使得使用wl_shm更加容易。為了為您的顯示器配置它,它只需要以下內容:
int
wl_display_init_shm(struct wl_display *display);
uint32_t *
wl_display_add_shm_format(struct wl_display *display, uint32_t format);
前者創建全局并配置內部實現,后者添加受支持的像素格式(記得至少添加ARGB8888和XRGB8888)。一旦客戶端將其緩沖區附加到其表面之一,您可以將緩沖區資源傳遞給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來保護對緩沖區數據的訪問,libwayland將負責鎖定。
6.3 Linux dmabuf
大多數Wayland合成器在GPU上進行渲染,許多Wayland客戶端也在GPU上進行渲染。使用共享內存方法,從客戶端向合成器發送緩沖區在這種情況下效率非常低,因為客戶端必須從GPU讀取數據到CPU,然后合成器必須從CPU讀取數據回到GPU進行渲染。
Linux DRM(直接渲染管理器)接口(也在一些BSD上實現)為我們提供了導出GPU資源句柄的方法。Mesa是用戶空間Linux圖形驅動程序的主要實現,它實現了一個協議,允許EGL用戶將句柄從客戶端傳輸到合成器的GPU緩沖區進行渲染,而無需將數據復制到CPU。
該協議的內部工作方式超出了本書的范圍,更適合于專注于Mesa或Linux DRM的資源。然而,我們可以簡要總結它的用法。
- 使用eglGetPlatformDisplayEXT與EGL_PLATFORM_WAYLAND_KHR一起創建一個EGL顯示。
- 正常配置顯示,選擇適合您情況的配置,并將EGL_SURFACE_TYPE設置為EGL_WINDOW_BIT。
- 使用wl_egl_window_create為給定的wl_surface創建一個wl_egl_window。
- 使用eglCreatePlatformWindowSurfaceEXT創建一個EGLSurface的wl_egl_window。
- 繼續使用EGL正常操作,例如eglMakeCurrent將EGL上下文設置為當前上下文,并使用eglSwapBuffers將更新的緩沖區發送給合成器并提交表面。
如果您稍后需要更改wl_egl_window的大小,請使用wl_egl_window_resize。
內部實現
一些不使用libwayland的Wayland程序員抱怨說,這種方法將Mesa和libwayland緊密地聯系在一起,這是事實。然而,解開它們并不是不可能的-它只是需要您自己實現linux-dmabuf的大量工作。查閱Wayland擴展XML以獲取有關協議的詳細信息,以及Mesa在src / egl / drivers / dri2 / platform_wayland.c中的實現(在編寫時)。祝你好運,祝順利。
對于服務器
不幸的是,合成器的細節既復雜又超出了本書的范圍。然而,我可以為你指明正確的方向:wlroots實現(在types / wlr_linux_dmabuf_v1.c中編寫)是直截了當的,應該讓你走上正確的道路。
6.4 Surface角色Role
我們已經創建了一個像素緩沖區,將其發送到服務器,并通過一個表面附加它,據稱可以向用戶顯示它。然而,要讓表面具有意義,缺少一個關鍵的環節,那就是它的角色(Role)。
有很多不同的情況可能會向用戶顯示像素緩沖區,每種情況都需要不同的語義。一些例子包括應用程序窗口,當然,但其他例子包括光標圖像或桌面壁紙。為了對比應用程序窗口與光標的語義,請考慮光標無法最小化,應用程序窗口不應粘在鼠標上。因此,角色提供了另一層抽象,允許您為表面分配適當的語義。
您可能迫不及待地想給它分配的角色,在第6章中介紹了一種機制來實現這一點:XDG shell。