1d3e62c67f
The wl_buffers for the background surface only need to be updated when the output dimensions change. Using the fixed pool of two buffers to cache these buffers does not help, since if a new buffer is needed, it will have a different size than whatever buffers were cached. Furthermore, because the pool has fixed size, it is possible to run out of buffers if configure events arrive faster than pool buffers are marked not busy, which can lead to protocol errors when the background surface is committed after acknowledging a new size, but without attaching a buffer that matches that size.
128 lines
2.8 KiB
C
128 lines
2.8 KiB
C
#define _POSIX_C_SOURCE 200809L
|
|
#include <assert.h>
|
|
#include <cairo/cairo.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/mman.h>
|
|
#include <time.h>
|
|
#include <unistd.h>
|
|
#include <wayland-client.h>
|
|
#include "pool-buffer.h"
|
|
|
|
static int anonymous_shm_open(void) {
|
|
int retries = 100;
|
|
|
|
do {
|
|
// try a probably-unique name
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
pid_t pid = getpid();
|
|
char name[50];
|
|
snprintf(name, sizeof(name), "/swaylock-%x-%x",
|
|
(unsigned int)pid, (unsigned int)ts.tv_nsec);
|
|
|
|
// shm_open guarantees that O_CLOEXEC is set
|
|
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
|
|
if (fd >= 0) {
|
|
shm_unlink(name);
|
|
return fd;
|
|
}
|
|
|
|
--retries;
|
|
} while (retries > 0 && errno == EEXIST);
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void buffer_release(void *data, struct wl_buffer *wl_buffer) {
|
|
struct pool_buffer *buffer = data;
|
|
buffer->busy = false;
|
|
}
|
|
|
|
static const struct wl_buffer_listener buffer_listener = {
|
|
.release = buffer_release
|
|
};
|
|
|
|
struct pool_buffer *create_buffer(struct wl_shm *shm,
|
|
struct pool_buffer *buf, int32_t width, int32_t height,
|
|
uint32_t format) {
|
|
uint32_t stride = width * 4;
|
|
size_t size = stride * height;
|
|
|
|
void *data = NULL;
|
|
if (size > 0) {
|
|
int fd = anonymous_shm_open();
|
|
if (fd == -1) {
|
|
return NULL;
|
|
}
|
|
if (ftruncate(fd, size) < 0) {
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
struct wl_shm_pool *pool = wl_shm_create_pool(shm, fd, size);
|
|
buf->buffer = wl_shm_pool_create_buffer(pool, 0,
|
|
width, height, stride, format);
|
|
wl_buffer_add_listener(buf->buffer, &buffer_listener, buf);
|
|
wl_shm_pool_destroy(pool);
|
|
close(fd);
|
|
}
|
|
|
|
buf->size = size;
|
|
buf->width = width;
|
|
buf->height = height;
|
|
buf->data = data;
|
|
buf->surface = cairo_image_surface_create_for_data(data,
|
|
CAIRO_FORMAT_ARGB32, width, height, stride);
|
|
buf->cairo = cairo_create(buf->surface);
|
|
return buf;
|
|
}
|
|
|
|
void destroy_buffer(struct pool_buffer *buffer) {
|
|
if (buffer->buffer) {
|
|
wl_buffer_destroy(buffer->buffer);
|
|
}
|
|
if (buffer->cairo) {
|
|
cairo_destroy(buffer->cairo);
|
|
}
|
|
if (buffer->surface) {
|
|
cairo_surface_destroy(buffer->surface);
|
|
}
|
|
if (buffer->data) {
|
|
munmap(buffer->data, buffer->size);
|
|
}
|
|
memset(buffer, 0, sizeof(struct pool_buffer));
|
|
}
|
|
|
|
struct pool_buffer *get_next_buffer(struct wl_shm *shm,
|
|
struct pool_buffer pool[static 2], uint32_t width, uint32_t height) {
|
|
struct pool_buffer *buffer = NULL;
|
|
|
|
for (size_t i = 0; i < 2; ++i) {
|
|
if (pool[i].busy) {
|
|
continue;
|
|
}
|
|
buffer = &pool[i];
|
|
}
|
|
|
|
if (!buffer) {
|
|
return NULL;
|
|
}
|
|
|
|
if (buffer->width != width || buffer->height != height) {
|
|
destroy_buffer(buffer);
|
|
}
|
|
|
|
if (!buffer->buffer) {
|
|
if (!create_buffer(shm, buffer, width, height,
|
|
WL_SHM_FORMAT_ARGB8888)) {
|
|
return NULL;
|
|
}
|
|
}
|
|
buffer->busy = true;
|
|
return buffer;
|
|
}
|