shm: Add wl_shm_buffer ref and unref functions

Shared memory buffers are currently tied to the lifetime of their
underlying wl_buffer resource. This becomes problematic when the client
destroys the resource after committing new state which references the
wl_buffer because a compositor might have to defer applying the commit.

This commit adds methods to keep the wl_shm_buffer alive longer than the
underlying resource. This implicitly also keeps the buffer pool alive
and because the wl_shm_buffer uses offsets into the pool, it even works
when the underlying storage gets remapped somewhere else, which can
happen when the client resizes the pool.

Signed-off-by: Sebastian Wick <sebastian.wick@redhat.com>
This commit is contained in:
Sebastian Wick 2025-03-21 19:42:52 +01:00
parent af453f876e
commit 9367c4da76
2 changed files with 79 additions and 5 deletions

View file

@ -666,6 +666,12 @@ wl_shm_buffer_get_width(const struct wl_shm_buffer *buffer);
int32_t
wl_shm_buffer_get_height(const struct wl_shm_buffer *buffer);
struct wl_shm_buffer *
wl_shm_buffer_ref(struct wl_shm_buffer *buffer);
void
wl_shm_buffer_unref(struct wl_shm_buffer *buffer);
struct wl_shm_pool *
wl_shm_buffer_ref_pool(struct wl_shm_buffer *buffer);

View file

@ -84,6 +84,8 @@ struct wl_shm_pool {
*/
struct wl_shm_buffer {
struct wl_resource *resource;
int internal_refcount;
int external_refcount;
int32_t width, height;
int32_t stride;
uint32_t format;
@ -165,13 +167,36 @@ shm_pool_unref(struct wl_shm_pool *pool, bool external)
free(pool);
}
static void
shm_buffer_unref(struct wl_shm_buffer *buffer, bool external)
{
if (external) {
buffer->external_refcount--;
if (buffer->external_refcount < 0) {
wl_abort("Requested to unref an external reference to "
"buffer but none found\n");
}
} else {
buffer->internal_refcount--;
if (buffer->internal_refcount < 0) {
wl_abort("Requested to unref an internal reference to "
"buffer but none found\n");
}
}
if (buffer->internal_refcount + buffer->external_refcount > 0)
return;
shm_pool_unref(buffer->pool, false);
free(buffer);
}
static void
destroy_buffer(struct wl_resource *resource)
{
struct wl_shm_buffer *buffer = wl_resource_get_user_data(resource);
shm_pool_unref(buffer->pool, false);
free(buffer);
shm_buffer_unref(buffer, false);
}
static void
@ -237,6 +262,8 @@ shm_pool_create_buffer(struct wl_client *client, struct wl_resource *resource,
return;
}
buffer->internal_refcount = 1;
buffer->external_refcount = 0;
buffer->width = width;
buffer->height = height;
buffer->format = format;
@ -495,6 +522,45 @@ wl_shm_buffer_get_height(const struct wl_shm_buffer *buffer)
return buffer->height;
}
/** Reference a shm_buffer
*
* \param buffer The buffer object
*
* Returns a pointer to the buffer and increases the refcount.
*
* The compositor must remember to call wl_shm_buffer_unref() when
* it no longer needs the reference to ensure proper destruction
* of the buffer.
*
* \memberof wl_shm_buffer
* \sa wl_shm_buffer_unref
*/
WL_EXPORT struct wl_shm_buffer *
wl_shm_buffer_ref(struct wl_shm_buffer *buffer)
{
buffer->external_refcount++;
return buffer;
}
/** Unreference a shm_buffer
*
* \param buffer The buffer object
*
* Drops a reference to a buffer object.
*
* This is only necessary if the compositor has explicitly
* taken a reference with wl_shm_buffer_ref(), otherwise
* the buffer will be automatically destroyed when appropriate.
*
* \memberof wl_shm_buffer
* \sa wl_shm_buffer_ref
*/
WL_EXPORT void
wl_shm_buffer_unref(struct wl_shm_buffer *buffer)
{
shm_buffer_unref(buffer, true);
}
/** Get a reference to a shm_buffer's shm_pool
*
* \param buffer The buffer object
@ -693,9 +759,11 @@ wl_shm_buffer_end_access(struct wl_shm_buffer *buffer)
if (--sigbus_data->access_count == 0) {
if (sigbus_data->fallback_mapping_used) {
wl_resource_post_error(buffer->resource,
WL_SHM_ERROR_INVALID_FD,
"error accessing SHM buffer");
if (buffer->resource) {
wl_resource_post_error(buffer->resource,
WL_SHM_ERROR_INVALID_FD,
"error accessing SHM buffer");
}
sigbus_data->fallback_mapping_used = 0;
}