Allocating a buffer
Wayland is designed to support buffers of different formats and of different nature. The only kind of buffer you can create using just the core Wayland protocol is a buffer in a shared memory pool, but extensions can add new kinds of buffers: for example, the wl_drm
extension adds the ability to allocate buffers in the GPU memory.
So, let's go with shared memory pool. The idea here is that instead of sending the complete buffer contents over the socket, which would be very expensive and slow, we set up a pool of memory that's shared between the client and the compositor and then only send the location of a buffer inside the pool over the socket.
The first thing to do is to map some memory into the client's address space. I'm going to use Linux's memfd_create
syscall, but if you can't use it, creating a temporary file would work as well:
#include <syscall.h>
#include <unistd.h>
#include <sys/mman.h>
int size = 200 * 200 * 4; // bytes, explained below
// open an anonymous file and write some zero bytes to it
int fd = syscall(SYS_memfd_create, "buffer", 0);
ftruncate(fd, size);
// map it to the memory
unsigned char *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
Next, we want to inform the compositor that it should create a shared memory pool by mapping the same file to its memory. This is done with the wl_shm.create_pool
request:
struct wl_shm *shm = ...;
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
The file descriptor and the size are sent over the socket to the compositor, so that it can make a similar mmap
call and get access to the same memory pool.
The shared memory pool is not itself a buffer; you can allocate multiple buffers inside that memory pool. Allocating a buffer is just telling the compositor that a piece of that shared memory starting at a certain offset into the pool with the given size and stride represents an image in a particular format, using the wl_shm_pool.create_buffer
request:
int width = 200;
int height = 200;
int stride = width * 4;
int size = stride * height; // bytes
struct wl_shm_pool *pool = ...;
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool,
0, width, height, stride, WL_SHM_FORMAT_XRGB8888);
In our simplest case, we allocate one buffer taking up the whole pool: specifically, we pass offset equal to zero, and I earlier set the size of the pool to be that of the buffer. In a real program, you may want to use some advanced memory allocation strategy here to create multiple buffers as needed without mapping and unmapping new files all the time.
Here's our complete code for allocating a buffer:
#include <syscall.h>
#include <unistd.h>
#include <sys/mman.h>
int width = 200;
int height = 200;
int stride = width * 4;
int size = stride * height; // bytes
// open an anonymous file and write some zero bytes to it
int fd = syscall(SYS_memfd_create, "buffer", 0);
ftruncate(fd, size);
// map it to the memory
unsigned char *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
// turn it into a shared memory pool
struct wl_shm *shm = ...;
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
// allocate the buffer in that pool
struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool,
0, width, height, stride, WL_SHM_FORMAT_XRGB8888);
If we were to render something now, we would need to put something into the memory pointed to by our data
pointer, because in our case the buffer starts immediately inside the pool. We don't have to do any rendering to display a black square though, because in the format we chose, XRGB8888, four zero bytes (0, 0, 0, 0)
denote a black pixel (the first byte is ignored, the rest are just the RGB components of the color), so our buffer is already filled with 200x200 black pixels. In fact, we didn't even need to mmap
the memory; just getting a file descriptor and passing it over to the compositor would work as well.