display:The Wayland Book 节选

source code:https://git.sr.ht/~sircmpwn/wayland-book

本文内容均节选自 《The Wayland Book》

High-level design

Your computer has input and output devices, which respectively are responsible for receiving information from you and displaying information to you. These input devices take the form of, for example: •  Keyboards •  Mice •  Touchpads •  Touch screens •  Drawing tablets Your output devices generally take the form of displays, on your desk or your laptop or mobile device. These resources are shared between all of your applications, and the role of the Wayland compositor is to dispatch input events to the appropriate Wayland client and to display their windows in their appropriate place on your outputs. The process of bringing together all of your application windows for display on an output is called compositing - and thus we call the software which does this the compositor[weston...]
你的计算机有输入和输出设备,分别负责接收信息和显示信息。这些输入设备的形式,例如:键盘、鼠标、触控板、触摸屏、绘图板。你的输出设备通常采取显示器的形式,在你的桌面电脑,你的笔记本电脑或移动设备上。这些资源在所有应用程序之间共享,Wayland compositor的角色是将输入事件分派到适当的Wayland客户端,并在输出设备的适当位置显示它们的窗口。把你所有的应用窗口放在一起以显示在一个输出上的过程叫做合成——因此我们称做这个的软件为合成器

There are many distinct software components in desktop ecosystem. There are tools like mesa for rendering (and each of its drivers), the Linux KMS/DRM subsystem, buffer allocation with GBM, the userspace libdrm library, libinput and evdev, and much more still. 
桌面生态系统中有许多不同的软件组件。有像mesa这样用于渲染的工具(以及它的每个驱动程序)、Linux KMS/DRM子系统、GBM的缓冲区分配、用户空间libdrm库、libinput和evdev等等。

The kernel

This responsibility falls onto the kernel. The kernel is a complex beast, so we'll focus on only the parts which are relevant to Wayland. Linux's job is to provide an abstraction over your hardware, so that they can be safely accessed by userspace - where our Wayland compositors run. For graphics, this is called DRM , or direct rendering manager , for efficiently tasking the GPU with work from userspace. An important subsystem of DRM is KMS , or kernel mode setting , for enumerating your displays and setting properties such as their selected resolution (also known as their "mode"). Input devices are abstracted through an interface called evdev . Most kernel interfaces are made available to userspace by way of special files in /dev . In the case of DRM, these files are in /dev/dri/ , usually in the form of a primary node (e.g. card0 ) for privileged operations like modesetting, and a render node (e.g. renderD128 ), for unprivileged operations like rendering or video decoding. For evdev, the "device nodes" are /dev/input/event*。
这个责任落到了内核身上。内核是一个复杂的怪兽,所以我们将只关注与Wayland相关的部分。Linux的工作是为你的硬件提供一个抽象,这样它们就可以被用户空间——我们的Wayland组合程序运行的地方——安全地访问。对于图形,这被称为DRM,或直接渲染管理器,有效地向GPU分配用户空间的工作。DRM的一个重要子系统是KMS,即内核模式设置,用于枚举显示和设置属性,比如它们所选择的分辨率(也称为它们的“模式”)。输入设备通过一个名为evdev的接口进行抽象。大多数内核接口通过/dev中的特殊文件对用户空间可用。在DRM的情况下,这些文件在/dev/dri/中,通常以主节点(例如card0)的形式,用于modesetting等特权操作,以及渲染节点(例如renderD128),用于渲染或视频解码等非特权操作。对于evdev,“设备节点”是/dev/input/event*。

Userspace

Now, we enter userspace. Here, applications are isolated from the hardware and must work via the device nodes provided by the kernel.

  • libdrm
    • Most Linux interfaces have a userspace counterpart which provides a pleasant(ish) C API for working with these device nodes. One such library is libdrm, which is the userspace portion of the DRM subsystem. libdrm is used by Wayland compositors to do modesetting and other DRM operations, but is generally not used by Wayland clients directly.
  • Mesa
    • Mesa is one of the most important parts of the Linux graphics stack. It provides, among other things, vendor-optimized implementations of OpenGL (and Vulkan) for Linux and the GBM (Generic Buffer Management) library - an abstraction on top of libdrm for allocating buffers on the GPU. Most Wayland compositors will use both GBM and OpenGL via Mesa, and most Wayland clients will use at least its OpenGL or Vulkan implementations.
  • libinput
    • Like libdrm abstracts the DRM subsystem, libinput provides the userspace end of evdev. It's responsible for receiving input events from the kernel from your various input devices, decoding them into a usable form, and passing them on to the Wayland compositor. The Wayland compositor requires special permissions to use the evdev files, forcing Wayland clients to go through the compositor to receive input events - which, for example, prevents keylogging.
  • (e)udev
    • Dealing with the appearance of new devices from the kernel, configuring permissions for the resulting device nodes in /dev , and sending word of these changes to applications running on your system, is a responsibility that falls onto userspace. Most systems use udev (or eudev, a fork) for this purpose. Your Wayland compositor uses udev to enumerate input devices and GPUs, and to receive notifications when new ones appear or old ones are unplugged.[听起来就像android的eventhub/getevent]
  • xkbcommon
    • XKB, short for X keyboard, is the original keyboard handling subsystem of the Xorg server. Several years ago, it was extracted from the Xorg tree and made into an independent library for keyboard handling, and it no longer has any practical relationship with X. Libinput (along with the Wayland compositor) delivers keyboard events in the form of scancodes, whose precise meaning varies from keyboard to keyboard. It's the responsibility of xkbcommon to translate these scan codes into meaningful and generic key "symbols" - for example, converting 65 to XKB_KEY_Space . It also contains a state machine which knows that pressing "1" while shift is held emits "!".[听起来像是android里面的input mapper]
  • pixman
    • A simple library used by clients and compositors alike for efficiently manipulating pixel buffers, doing math with intersecting rectangles, and performing other similar pix el man ipulation tasks. 
  • libwayland
    • libwayland the most commonly used implementation of the Wayland protocol, is written in C, and handles much of the low-level wire protocol. It also provides a tool which generates high-level code from Wayland protocol definitions (which are XML files).
  • ...

 

Atomicity

The most important of the Wayland protocol design patterns is atomicity . A stated goal of Wayland is "every frame is perfect". To this end, most interfaces allow you to update them transactionally, using several requests to build up a new representation of its state, then committing them all at once. For example, there are several properties that may be configured on a wl_surface :

  •  An attached pixel buffer
  •  A damaged area that needs to be redrawn
  •  A region defined as opaque, for optimization purposes
  •  A region where input events are acceptable
  •  A transformation, like rotating 90 degrees
  •  A buffer scale, used for HiDPI

The interface includes separate requests for configuring each of these, but these are applied to a pending state. Only when the commit request is sent does the pending state get merged into the current state, allowing you to atomically update all of these properties within a single frame. Combined with a few other key design decisions, this allows Wayland compositors to render everything perfectly in every frame - no tearing or partially updated windows, just every pixel in its place and every place in its pixel.

Resource lifetimes

另一个重要的设计模式是避免服务器或客户端发送属于无效对象的事件或请求。
Another important design pattern is avoiding a situation where the server or client is sending events or requests that pertain to an invalid object. For this reason, interfaces which define resources that have finite lifetimes will often include requests and events through which the client or server can state their intention to no longer send requests or events for that object. Only once both sides have agreed to this - asynchronously - do they destroy the resources they allocated for that object. Wayland is a fully asynchronous protocol. Messages are guaranteed to arrive in the order they were sent, but only with respect to one sender. For example, the server may have several input events queued up when the client decides to destroy its keyboard device. The client must correctly deal with events for an object it no longer needs until the server catches up. Likewise, had the client queued up some requests for an object before destroying it, it would have had to send these requests in the correct order so that the object is no longer used after the client agreed it had been destroyed.

Proxies & resources 与 Interfaces & listeners,所有这些都是围绕object的。object在客户端以wl_proxy抽象,在服务端以wl_resource展现;客户端发送request,监听event;服务端接受request,发送event。在整个交互过程中,wl_display起到至关作用。

 

Create a display

For Wayland clients

Connecting to a Wayland server and creating a wl_display to manage the connection's state is quite easy:

 #include <stdio.h>
 #include <wayland-client.h>
 int main(int argc, char *argv[])
 {     
     struct wl_display *display = wl_display_connect(NULL);     
     if (!display) {
             fprintf(stderr, "Failed to connect to Wayland display.\n");
             return 1;
     }
     fprintf(stderr, "Connection established!\n");
     wl_display_disconnect(display);
     return 0;
 } 

Let's compile and run this program. Assuming you're using a Wayland compositor as you read this, the result should look like this:

$ cc -o client -lwayland-client client.c
$ ./client

Connection established!
wl_display_connect is the most common way for clients to establish a Wayland connection. The signature is:

struct wl_display *wl_display_connect(const char *name); 

The "name" argument is the name of the Wayland display, which is typically "wayland-0" . You can swap the NULL for this in our test client and try for yourself - it's likely to work. This corresponds to the name of a Unix socket in $XDG_RUNTIME_DIR . NULL is preferred, however, in which case libwayland will:

  1. 1  If $WAYLAND_DISPLAY is set, attempt to connect to $XDG_RUNTIME_DIR/$WAYLAND_DISPLAY
  2. 2  Attempt to connect to $XDG_RUNTIME_DIR/wayland-0
  3. 3  Fail :(

This allows users to specify the Wayland display they want to run their clients on by setting the $WAYLAND_DISPLAY to the desired display. If you have more complex requirements, you can also establish the connection yourself and create a Wayland display from a file descriptor:

struct wl_display *wl_display_connect_to_fd(int fd);

You can also obtain the file descriptor that the wl_display is using via wl_display_get_fd , regardless of how you created the display.

 int wl_display_get_fd(struct wl_display *display);

这里面需要注意,这个wl_display_connect可以接不同的name,因为weston不支持多card。那么你可以启动多个weston对应不同的card,并且不同的weston对应不同的name,那么就可以实现多weston以及多client的显示功能。 

---------------- 

For Wayland servers

The process is fairly simple for servers as well. The creation of the display and binding to a socket are separate, to give you time to configure the display before any clients are able to connect to it. Here's another minimal example program:

 #include <stdio.h>
 #include <wayland-server.h>
 int main(int argc, char *argv[])
 {
     struct wl_display *display = wl_display_create();
     if (!display) {
         fprintf(stderr, "Unable to create Wayland display.\n");
         return 1;
     }
     const char *socket = wl_display_add_socket_auto(display);
     if (!socket) {
         fprintf(stderr, "Unable to add socket to Wayland display.\n");
         return 1;
     }
     fprintf(stderr, "Running Wayland display on %s\n", socket);
     wl_display_run(display);
     wl_display_destroy(display);
     return 0;
 } 

Let's compile and run this, too:

 $ cc -o server -lwayland-server server.c
 $ ./server &

Running Wayland display on wayland-1

$ WAYLAND_DISPLAY=wayland-1 ./client

Connection established! Using wl_display_add_socket_auto will allow libwayland to decide the name for the display automatically, which defaults to wayland-0 , or wayland-$n , depending on if any other Wayland compositors have sockets in $XDG_RUNTIME_DIR . However, as with the client, you have some other options for configuring the display:

int wl_display_add_socket(struct wl_display *display, const char *name);
int wl_display_add_socket_fd(struct wl_display *display, int sock_fd);

这个地方说了,你可以自己指定client与socket交互用到的如wayland-0的socket名字 

After adding the socket, calling wl_display_run will run libwayland's internal event loop and block until wl_display_terminate is called. What's this event loop?

Wayland server event loop

Each wl_display created by libwayland-server has a corresponding wl_event_loop , which you may obtain a reference to with wl_display_get_event_loop . If you're writing a new Wayland compositor, you will likely want to use this as your only event loop. You can add file descriptors to it with wl_event_loop_add_fd , and timers with wl_event_loop_add_timer . It also handles signals via wl_event_loop_add_signal , which can be pretty convenient. With the event loop configured to your liking to monitor all of the events your compositor has to respond to, you can process events and dispatch Wayland clients all at once by calling wl_display_run , which will process the event loop and block until the display terminates (via wl_display_terminate ). Most Wayland compositors which were built from the ground-up with Wayland in mind (as opposed to being ported from X11) use this approach. However, it's also possible to take the wheel and incorporate the Wayland display into your own event loop. wl_display uses the event loop internally for processing clients, and you can choose to either monitor the Wayland event loop on your own, dispatching it as necessary, or you can disregard it entirely and manually process client updates. If you wish to allow the Wayland event loop to look after itself and treat it as subservient to your own event loop, you can use wl_event_loop_get_fd to obtain a poll -able file descriptor, then call wl_event_loop_dispatch to process events when activity occurs on that file descriptor. You will also need to call wl_display_flush_clients when you have data which needs writing to clients.
libwayland-server创建的每个wl_display都有一个对应的wl_event_loop,您可以通过wl_display_get_event_loop获得对它的引用。如果你正在写一个新的Wayland compositor,你可能想要使用它作为你唯一的事件循环。您可以使用wl_event_loop_add_fd向它添加文件描述符,使用wl_event_loop_add_timer向它添加计时器。它还通过wl_event_loop_add_signal处理信号,这非常方便。根据您的喜好配置事件循环来监视compositor必须响应的所有事件,你可以一次性处理event和dispatch wayland-client通过调用wl_display_run,wl_display_run将处理事件循环并且阻塞,直到显示终止(通过wl_display_terminate)。大多数基于Wayland构建的复合程序(与从X11移植相反)都使用这种方法。但是,也可以使用这个轮子并将Wayland显示合并到您自己的事件循环中。wl_display在内部使用事件循环来处理客户端,您可以选择自己监视Wayland事件循环,根据需要分派它,或者完全忽略它,手动处理客户端更新。如果你想让wayland event loop照顾自己,把它当作屈从于自己的事件循环,您可以使用wl_event_loop_get_fd获得能够调查文件描述符,然后调用wl_event_loop_dispatch活动发生时处理事件的文件描述符。当您有需要写入客户机的数据时,还需要调用wl_display_flush_clients。

Wayland client event loop

libwayland-client, on the other hand, does not have its own event loop. However, since there is only generally one file descriptor, it's easier to manage without. If Wayland events are the only sort which your program expects, then this simple loop will suffice:

 while (wl_display_dispatch(display) != -1) {
     /* This space deliberately left blank */
 }

However, if you have a more sophisticated application, you can build your own event loop in any manner you please, and obtain the Wayland display's file descriptor with wl_display_get_fd . Upon POLLIN events, call wl_display_dispatch to process incoming events. To flush outgoing requests, call wl_display_flush。

 

Armed with this information, we can write our first useful Wayland client: one which simply prints all of the globals available on the server.打印server端可用的全部接口服务。

 #include <stdint.h>
 #include <stdio.h>
 #include <wayland-client.h>
 static void registry_handle_global(void *data, struct wl_registry *registry,
         uint32_t name, const char *interface, uint32_t version)
 {
     printf("interface: '%s', version: %d, name: %d\n",
             interface, version, name);
 }
 static void registry_handle_global_remove(void *data, struct wl_registry *registry,
         uint32_t name)
 {
     // This space deliberately left blank
 }
 static const struct wl_registry_listener registry_listener = {
     .global = registry_handle_global,
     .global_remove = registry_handle_global_remove,
 };
 int main(int argc, char *argv[])
 {
     struct wl_display *display = wl_display_connect(NULL);
     struct wl_registry *registry = wl_display_get_registry(display);
     wl_registry_add_listener(registry, &registry_listener, NULL);
     wl_display_roundtrip(display);
     return 0;
 }

请参考前面的章节来解释这个程序。我们连接到显示器(4.1章),获得注册表(本章),向它添加一个监听器(3.4章),然后round-trip,通过打印这个组合器上可用的全局变量来处理全局事件。注意:这一章是我们最后一次用十六进制表示有线协议转储,这可能也是你最后一次看到它们。跟踪Wayland客户机或服务器的更好方法是在运行程序之前将环境中的WAYLAND_DEBUG变量设置为1。现在尝试一下“globals”程序! 

Registering globals with libwayland-server is done somewhat differently. When you generate "server-code" with wayland-scanner, it creates interfaces (which are analogous to listeners) and glue code for sending events. The first task is to register the global, with a function to rig up a resource 1 when the global is bound. In terms of code, the result looks something like this:
在libwayland-server上注册全局的方式有些不同。当您使用wayland-scanner生成“服务器代码”时,它会创建接口(类似于侦听器)和用于发送事件的粘合代码。第一个任务是注册全局变量,使用一个函数在绑定全局变量时构建一个资源1。在代码方面,结果是这样的:

static void wl_output_handle_bind(struct wl_client *client, void *data,
     uint32_t version, uint32_t id)
 {
     struct my_state *state = data;
     // TODO
 }
 int main(int argc, char *argv[])
 {
     struct wl_display *display = wl_display_create();
     struct my_state state = { ... };
     // ...
     wl_global_create(wl_display, &wl_output_interface,
         1, &my_state, wl_output_handle_bind);
     // ...
 }

If you take this code and, for example, patch it into the server example chapter 4.1, you'll make a wl_output global visible to the "globals" program we wrote last time 2 . However, any attempts to bind to this global will run into our TODO. To fill this in, we need to provide an implementation of the wl_output interface as well. 
如果您使用这段代码,并将其修补到4.1章的服务器示例中,那么您将使一个wl_output全局变量对我们上次编写的“globals”程序可见。然而,任何试图与这一全球性联系在一起的尝试都会遇到我们的麻烦。为了填充此内容,我们还需要提供wl_output接口的实现。

static void wl_output_handle_resource_destroy(struct wl_resource *resource)
 {
     struct my_output *client_output = wl_resource_get_user_data(resource);
     // TODO: Clean up resource
     remove_to_list(client_output->state->client_outputs, client_output);
 }
 static void wl_output_handle_release(struct wl_client *client, struct wl_resource *resource)
 {
     wl_resource_destroy(resource);
 }
 static const struct wl_output_interface wl_output_implementation = {
     .release = wl_output_handle_release,
 };
 static void wl_output_handle_bind(struct wl_client *client, void *data,
    uint32_t version, uint32_t id)
 {
     struct my_state *state = data;
     struct my_output *client_output = calloc(1, sizeof(struct client_output));
     struct wl_resource *resource = wl_resource_create(
         client, &wl_output_implementation, wl_output_interface.version, id);
     wl_resource_set_implementation(resource, wl_output_implementation,
         client_output, wl_output_handle_resource_destroy);
     client_output->resource = resource;
     client_output->state = state;
     // TODO: Send geometry event, et al
     add_to_list(state->client_outputs, client_output);
 }

This is a lot to take in, so let's explain it one piece at a time. At the bottom, we've extended our "bind" handler to create a wl_resource to track the server-side state for this object (using the ID that the client allocated). As we do this, we provide wl_resource_create with a pointer to our implementation of the interface - wl_output_implementation , a constant static struct in this file. The type ( struct wl_output_interface ) is generated by wayland-scanner and contains one function pointer for each request supported by this interface. We also take the opportunity to allocate a small container for storing any additional state we need that libwayland doesn't handle for us, the specific nature of which varies from protocol to protocol. Our wl_output_handle_release function is called when the client sends the release request, indicating that they no longer need this resource - so we destroy it. This in turn triggers the wl_output_handle_resource_destroy function, which later we'll extend to free any of the state we allocated for it earlier. This function is also passed into wl_resource_create as the destructor, and will be called if the client terminates without explicitly sending the release request. 

Buffers & surfaces

Apparently, the whole point of this system is to display information to users and receive their feedback for additional processing. In this chapter, we'll explore the first of these tasks: showing pixels on the screen. There are two primitives which are used for this purpose: buffers and surfaces, governed respectively by the wl_buffer and wl_surface interfaces. Buffers act as an opaque container for some underlying pixel storage, and are supplied by clients with a number of methods - shared memory buffers and GPU handles being the most common.[第三方厂商会在buffer这里添加自己独特的支持]

Using wl_compositor

The wl_compositor global is the Wayland compositor's, er, compositor. Through this interface, you may send the server your windows for presentation, to be composited with the other windows being shown alongside it. The compositor has two jobs: the creation of surfaces and regions. To quote the spec, a Wayland surface has a rectangular area which may be displayed on zero or more outputs, present buffers, receive user input, and define a local coordinate system. Let's start with the basics: obtaining a surface and attaching a buffer to it. To obtain a surface, we first bind to the wl_compositor global. By extending the example from chapter 5.1 we get the following:

wl_compositor全局变量是Wayland compositor的compositor。通过这个接口,您可以将您的窗口发送给服务器,以便与其他窗口组合。compositor有两项工作:创建surfaces和区域。一个Wayland surfaces 有一个矩形区域,可以显示在0个或多个outputs上,显示buffers,接收用户input,并定义一个局部坐标系。让我们从基础开始:获得一个surfaces 并在其上附加一个buffer 。为了获得surfaces ,我们首先绑定到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);
     // ...
 }

Note that we've specified version 4 when calling wl_registry_bind , which is the latest version at the time of writing. With this reference secured, we can create a wl_surface :

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

Before we can present it, we must first attach a source of pixels to it: a wl_buffer

 ARGB8888, and XRGB8888, which are 24-bit color, with and without an alpha channel respectively.

Shared memory buffers

The simplest means of getting pixels from client to compositor, and the only one enshrined in wayland.xml , is wl_shm - shared memory. Simply put, it allows you to transfer a file descriptor for the compositor to mmap with MAP_SHARED , then share pixel buffers out of this pool. Add some simple synchronization primitives to keep everyone from fighting over each buffer, and you have a workable - and portable - solution.[shmem保证同一个buffer可以被多个client共享?]

Binding to wl_shm

The registry global listener explained in chapter 5.1 will advertise the wl_shm global when it's available. Binding to it is fairly straightforward. Extending the example given in chapter 5.1, we get the following:

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);
     // ...
 }

 Once bound, we can optionally add a listener via wl_shm_add_listener . The compositor will advertise its supported pixel formats via this listener. The full list of possible pixel formats is given in wayland.xml . Two formats are required to be supported: ARGB8888 , and XRGB8888 , which are 24-bit color, with and without an alpha channel respectively.

Allocating a shared memory pool

A combination of POSIX shm_open and random file names can be utilized to create a file suitable for this purpose, and ftruncate can be utilized to bring it up to the appropriate size. The following boilerplate may be freely used under public domain or 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;
 }

Armed with this, the client can create a shared memory pool fairly easily. Let's say, for example, that we want to show a 1920x1080 window. We'll need two buffers for double-buffering,so that'll be 4,147,200 pixels. Assuming the pixel format is WL_SHM_FORMAT_XRGB8888 , that'll be 4 bytes to the pixel, for a total pool size of 16,588,800 bytes. Bind to the wl_shm global from the registry as explained in chapter 5.1, then use it like so to create an shm pool which can hold these buffers:

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, 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);

 Creating buffers from a pool

Once word of this gets to the compositor, it will mmap this file descriptor as well. Wayland is asynchronous, though, so we can start allocating buffers from this pool right away. Since we allocated space for two buffers, we can assign each an index and convert that index into a byte offset in the pool. Equipped with this information, we can create a 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);

We can write an image to this buffer now as well. For example, to set it to solid white:

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

Or, for something more interesting, here's a checkerboard pattern:

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;
     }
   }
 }

With the stage set, we'll attach our buffer to our surface, mark the whole surface as damaged ["Damaged" meaning "this area needs to be redrawn"] , and commit it:

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

If you were to apply all of this newfound knowledge to writing a Wayland client yourself, you may arrive at this point confused when your buffer is not shown on-screen. We're missing a critical final step - assigning your surface a role.

wl_shm on the server

Before we get there, however, the server-side part of this deserves note. libwayland provides some helpers to make using wl_shm easier. To configure it for your display, it only requires the following:

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

The former creates the global and rigs up the internal implementation, and the latter adds a supported pixel format (remember to at least add ARGB8888 and XRGB8888). Once a client attaches a buffer to one of its surfaces, you can pass the buffer resource into wl_shm_buffer_get to obtain a wl_shm_buffer reference, and utilize it like so:

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);

If you guard your accesses to the buffer data with begin_access and end_access , libwayland will take care of locking for you.

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值