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 int32_t
wl_shm_buffer_get_height(const struct wl_shm_buffer *buffer); 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 * struct wl_shm_pool *
wl_shm_buffer_ref_pool(struct wl_shm_buffer *buffer); 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_shm_buffer {
struct wl_resource *resource; struct wl_resource *resource;
int internal_refcount;
int external_refcount;
int32_t width, height; int32_t width, height;
int32_t stride; int32_t stride;
uint32_t format; uint32_t format;
@ -165,13 +167,36 @@ shm_pool_unref(struct wl_shm_pool *pool, bool external)
free(pool); 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 static void
destroy_buffer(struct wl_resource *resource) destroy_buffer(struct wl_resource *resource)
{ {
struct wl_shm_buffer *buffer = wl_resource_get_user_data(resource); struct wl_shm_buffer *buffer = wl_resource_get_user_data(resource);
shm_pool_unref(buffer->pool, false); shm_buffer_unref(buffer, false);
free(buffer);
} }
static void static void
@ -237,6 +262,8 @@ shm_pool_create_buffer(struct wl_client *client, struct wl_resource *resource,
return; return;
} }
buffer->internal_refcount = 1;
buffer->external_refcount = 0;
buffer->width = width; buffer->width = width;
buffer->height = height; buffer->height = height;
buffer->format = format; buffer->format = format;
@ -495,6 +522,45 @@ wl_shm_buffer_get_height(const struct wl_shm_buffer *buffer)
return buffer->height; 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 /** Get a reference to a shm_buffer's shm_pool
* *
* \param buffer The buffer object * \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->access_count == 0) {
if (sigbus_data->fallback_mapping_used) { if (sigbus_data->fallback_mapping_used) {
wl_resource_post_error(buffer->resource, if (buffer->resource) {
WL_SHM_ERROR_INVALID_FD, wl_resource_post_error(buffer->resource,
"error accessing SHM buffer"); WL_SHM_ERROR_INVALID_FD,
"error accessing SHM buffer");
}
sigbus_data->fallback_mapping_used = 0; sigbus_data->fallback_mapping_used = 0;
} }