6. Buffer和Surface

[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, &registry_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, &registry_listener, &state);
    // ...
}

绑定后,我们可以通过wl_shm_add_listener添加一个侦听器。合成器将通过此侦听器宣传其支持的像素格式。可能的像素格式的完整列表在wayland.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。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容