From 950a9a72ab0e6bd3c4ead42d68e83b271a135c7f Mon Sep 17 00:00:00 2001 From: Frank Praznik Date: Fri, 7 Nov 2025 13:25:16 -0500 Subject: [PATCH] wayland: Use a pool to allocate icon image buffers Allocate the buffers from a pool to avoid a separate mmap for each image. --- src/video/wayland/SDL_waylandshmbuffer.c | 52 ----------------- src/video/wayland/SDL_waylandshmbuffer.h | 11 ---- src/video/wayland/SDL_waylandwindow.c | 71 ++++++++++++++++++------ src/video/wayland/SDL_waylandwindow.h | 2 +- 4 files changed, 56 insertions(+), 80 deletions(-) diff --git a/src/video/wayland/SDL_waylandshmbuffer.c b/src/video/wayland/SDL_waylandshmbuffer.c index 709491dc54..7b274c700a 100644 --- a/src/video/wayland/SDL_waylandshmbuffer.c +++ b/src/video/wayland/SDL_waylandshmbuffer.c @@ -114,58 +114,6 @@ static struct wl_buffer_listener buffer_listener = { buffer_handle_release }; -bool Wayland_AllocSHMBuffer(int width, int height, Wayland_SHMBuffer *shmBuffer) -{ - SDL_VideoDevice *vd = SDL_GetVideoDevice(); - SDL_VideoData *data = vd->internal; - const Uint32 SHM_FMT = WL_SHM_FORMAT_ARGB8888; - - if (!shmBuffer) { - return SDL_InvalidParamError("shmBuffer"); - } - - const int stride = width * 4; - shmBuffer->shm_data_size = stride * height; - - const int shm_fd = CreateTempFD(shmBuffer->shm_data_size); - if (shm_fd < 0) { - return SDL_SetError("Creating SHM buffer failed."); - } - - shmBuffer->shm_data = mmap(NULL, shmBuffer->shm_data_size, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); - if (shmBuffer->shm_data == MAP_FAILED) { - shmBuffer->shm_data = NULL; - close(shm_fd); - return SDL_SetError("mmap() failed."); - } - - SDL_assert(shmBuffer->shm_data != NULL); - - struct wl_shm_pool *shm_pool = wl_shm_create_pool(data->shm, shm_fd, shmBuffer->shm_data_size); - shmBuffer->wl_buffer = wl_shm_pool_create_buffer(shm_pool, 0, width, height, stride, SHM_FMT); - wl_buffer_add_listener(shmBuffer->wl_buffer, &buffer_listener, shmBuffer); - - wl_shm_pool_destroy(shm_pool); - close(shm_fd); - - return true; -} - -void Wayland_ReleaseSHMBuffer(Wayland_SHMBuffer *shmBuffer) -{ - if (shmBuffer) { - if (shmBuffer->wl_buffer) { - wl_buffer_destroy(shmBuffer->wl_buffer); - shmBuffer->wl_buffer = NULL; - } - if (shmBuffer->shm_data) { - munmap(shmBuffer->shm_data, shmBuffer->shm_data_size); - shmBuffer->shm_data = NULL; - } - shmBuffer->shm_data_size = 0; - } -} - struct Wayland_SHMPool { struct wl_shm_pool *shm_pool; diff --git a/src/video/wayland/SDL_waylandshmbuffer.h b/src/video/wayland/SDL_waylandshmbuffer.h index 5355412f3a..e5a54a25d0 100644 --- a/src/video/wayland/SDL_waylandshmbuffer.h +++ b/src/video/wayland/SDL_waylandshmbuffer.h @@ -24,19 +24,8 @@ #ifndef SDL_waylandshmbuffer_h_ #define SDL_waylandshmbuffer_h_ -typedef struct -{ - struct wl_buffer *wl_buffer; - void *shm_data; - int shm_data_size; -} Wayland_SHMBuffer; - typedef struct Wayland_SHMPool Wayland_SHMPool; -// Allocates an SHM buffer with the format WL_SHM_FORMAT_ARGB8888 -extern bool Wayland_AllocSHMBuffer(int width, int height, Wayland_SHMBuffer *shmBuffer); -extern void Wayland_ReleaseSHMBuffer(Wayland_SHMBuffer *shmBuffer); - extern Wayland_SHMPool *Wayland_AllocSHMPool(int size); extern struct wl_buffer *Wayland_AllocBufferFromPool(Wayland_SHMPool *shmPool, int width, int height, void **data); extern void Wayland_ReleaseSHMPool(Wayland_SHMPool *shmPool); diff --git a/src/video/wayland/SDL_waylandwindow.c b/src/video/wayland/SDL_waylandwindow.c index fb9334e5ac..4cfc8121cb 100644 --- a/src/video/wayland/SDL_waylandwindow.c +++ b/src/video/wayland/SDL_waylandwindow.c @@ -2953,9 +2953,18 @@ void Wayland_SetWindowTitle(SDL_VideoDevice *_this, SDL_Window *window) } } +static int icon_sort_callback(const void *a, const void *b) +{ + SDL_Surface *s1 = (SDL_Surface *)a; + SDL_Surface *s2 = (SDL_Surface *)b; + + return (s1->w * s1->h) <= (s2->w * s2->h) ? -1 : 1; +} + bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surface *icon) { SDL_WindowData *wind = window->internal; + Wayland_SHMPool *shm_pool = NULL; struct xdg_toplevel *toplevel = NULL; if (!_this->internal->xdg_toplevel_icon_manager_v1) { @@ -2979,52 +2988,75 @@ bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surfa } for (int i = 0; i < wind->icon_buffer_count; ++i) { - Wayland_ReleaseSHMBuffer(&wind->icon_buffers[i]); + wl_buffer_destroy(wind->icon_buffers[i]); } - SDL_free(wind->icon_buffers); wind->icon_buffer_count = 0; wind->xdg_toplevel_icon_v1 = xdg_toplevel_icon_manager_v1_create_icon(_this->internal->xdg_toplevel_icon_manager_v1); - wind->icon_buffers = SDL_calloc(image_count, sizeof(Wayland_SHMBuffer)); + wind->icon_buffers = SDL_realloc(wind->icon_buffers, image_count * sizeof(struct wl_buffer *)); if (!wind->icon_buffers) { goto failure_cleanup; } + // Calculate the size of the buffer pool. + size_t pool_size = 0; + for (int i = 0; i < image_count; ++i) { + // Ignore non-square images; if we got here, we know that at least the base image is square. + if (images[i]->w == images[i]->h) { + pool_size += images[i]->w * images[i]->h * 4; + } else { + SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "wayland: icon width and height must be equal, got %ix%i for image level %i; skipping", images[i]->w, images[i]->h, i); + } + } + + // Sort the images in ascending order by size. + SDL_qsort(images, image_count, sizeof(SDL_Surface *), icon_sort_callback); + + shm_pool = Wayland_AllocSHMPool(pool_size); + if (!shm_pool) { + SDL_SetError("wayland: failed to allocate SHM pool for the icon"); + goto failure_cleanup; + } + for (int i = 0; i < image_count; ++i) { if (images[i]->w == images[i]->h) { SDL_Surface *surface = images[i]; - Wayland_SHMBuffer *buffer = &wind->icon_buffers[wind->icon_buffer_count]; - if (!Wayland_AllocSHMBuffer(surface->w, surface->h, buffer)) { + // Choose the largest image for each integer scale, ignoring any below the base size. + const int scale = (int)SDL_floor((double)surface->w / (double)icon->w); + if (!scale) { + continue; + } + + void *buffer_mem; + struct wl_buffer *buffer = Wayland_AllocBufferFromPool(shm_pool, surface->w, surface->h, &buffer_mem); + if (!buffer) { SDL_SetError("wayland: failed to allocate SHM buffer for the icon"); goto failure_cleanup; } + wind->icon_buffers[wind->icon_buffer_count++] = buffer; + if (surface->format != SDL_PIXELFORMAT_ARGB8888) { surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_ARGB8888); if (!surface) { + SDL_SetError("wayland: unable to convert surface to ARGB8888 format"); goto failure_cleanup; } } - SDL_PremultiplyAlpha(surface->w, surface->h, surface->format, surface->pixels, surface->pitch, SDL_PIXELFORMAT_ARGB8888, buffer->shm_data, surface->w * 4, true); + SDL_PremultiplyAlpha(surface->w, surface->h, surface->format, surface->pixels, surface->pitch, + SDL_PIXELFORMAT_ARGB8888, buffer_mem, surface->w * 4, true); - const int scale = (int)SDL_ceil((double)surface->w / (double)icon->w); - xdg_toplevel_icon_v1_add_buffer(wind->xdg_toplevel_icon_v1, buffer->wl_buffer, scale); + xdg_toplevel_icon_v1_add_buffer(wind->xdg_toplevel_icon_v1, buffer, scale); // Clean up the temporary conversion surface. if (surface != images[i]) { SDL_DestroySurface(surface); } - - wind->icon_buffer_count++; - } else { - SDL_LogWarn(SDL_LOG_CATEGORY_VIDEO, "wayland: icon width and height must be equal, got %ix%i for image level %i; skipping", images[i]->w, images[i]->h, i); } } - SDL_free(images); - #ifdef HAVE_LIBDECOR_H if (wind->shell_surface_type == WAYLAND_SHELL_SURFACE_TYPE_LIBDECOR && wind->shell_surface.libdecor.frame) { toplevel = libdecor_frame_get_xdg_toplevel(wind->shell_surface.libdecor.frame); @@ -3038,17 +3070,24 @@ bool Wayland_SetWindowIcon(SDL_VideoDevice *_this, SDL_Window *window, SDL_Surfa xdg_toplevel_icon_manager_v1_set_icon(_this->internal->xdg_toplevel_icon_manager_v1, toplevel, wind->xdg_toplevel_icon_v1); } + Wayland_ReleaseSHMPool(shm_pool); + SDL_free(images); + return true; failure_cleanup: + SDL_free(images); + if (wind->xdg_toplevel_icon_v1) { xdg_toplevel_icon_v1_destroy(wind->xdg_toplevel_icon_v1); wind->xdg_toplevel_icon_v1 = NULL; } + Wayland_ReleaseSHMPool(shm_pool); + for (int i = 0; i < wind->icon_buffer_count; ++i) { - Wayland_ReleaseSHMBuffer(&wind->icon_buffers[i]); + wl_buffer_destroy(wind->icon_buffers[i]); } SDL_free(wind->icon_buffers); wind->icon_buffers = NULL; @@ -3257,7 +3296,7 @@ void Wayland_DestroyWindow(SDL_VideoDevice *_this, SDL_Window *window) } for (int i = 0; i < wind->icon_buffer_count; ++i) { - Wayland_ReleaseSHMBuffer(&wind->icon_buffers[i]); + wl_buffer_destroy(wind->icon_buffers[i]); } SDL_free(wind->icon_buffers); wind->icon_buffer_count = 0; diff --git a/src/video/wayland/SDL_waylandwindow.h b/src/video/wayland/SDL_waylandwindow.h index 50f8c528d0..8c7b4a2cd8 100644 --- a/src/video/wayland/SDL_waylandwindow.h +++ b/src/video/wayland/SDL_waylandwindow.h @@ -127,7 +127,7 @@ struct SDL_WindowData char *app_id; double scale_factor; - Wayland_SHMBuffer *icon_buffers; + struct wl_buffer **icon_buffers; int icon_buffer_count; // Keyboard, pointer, and touch focus refcount.