diff --git a/include/types/wlr_buffer.h b/include/types/wlr_buffer.h index 56b50f3d..8abb68d6 100644 --- a/include/types/wlr_buffer.h +++ b/include/types/wlr_buffer.h @@ -3,6 +3,28 @@ #include +struct wlr_shm_client_buffer { + struct wlr_buffer base; + + uint32_t format; + size_t stride; + + // The following fields are NULL if the client has destroyed the wl_buffer + struct wl_resource *resource; + struct wl_shm_buffer *shm_buffer; + + // This is used to keep the backing storage alive after the client has + // destroyed the wl_buffer + struct wl_shm_pool *saved_shm_pool; + void *saved_data; + + struct wl_listener resource_destroy; + struct wl_listener release; +}; + +struct wlr_shm_client_buffer *shm_client_buffer_create( + struct wl_resource *resource); + /** * Buffer capabilities. * diff --git a/types/wlr_buffer.c b/types/wlr_buffer.c index c35c8f2f..40d5ae34 100644 --- a/types/wlr_buffer.c +++ b/types/wlr_buffer.c @@ -346,3 +346,106 @@ struct wlr_client_buffer *wlr_client_buffer_apply_damage( buffer->resource_released = true; return buffer; } + +static const struct wlr_buffer_impl shm_client_buffer_impl; + +static struct wlr_shm_client_buffer *shm_client_buffer_from_buffer( + struct wlr_buffer *buffer) { + assert(buffer->impl == &shm_client_buffer_impl); + return (struct wlr_shm_client_buffer *)buffer; +} + +static void shm_client_buffer_destroy(struct wlr_buffer *wlr_buffer) { + struct wlr_shm_client_buffer *buffer = + shm_client_buffer_from_buffer(wlr_buffer); + wl_list_remove(&buffer->resource_destroy.link); + wl_list_remove(&buffer->release.link); + if (buffer->saved_shm_pool != NULL) { + wl_shm_pool_unref(buffer->saved_shm_pool); + } + free(buffer); +} + +static bool shm_client_buffer_begin_data_ptr_access(struct wlr_buffer *wlr_buffer, + void **data, uint32_t *format, size_t *stride) { + struct wlr_shm_client_buffer *buffer = + shm_client_buffer_from_buffer(wlr_buffer); + *format = buffer->format; + *stride = buffer->stride; + if (buffer->shm_buffer != NULL) { + *data = wl_shm_buffer_get_data(buffer->shm_buffer); + wl_shm_buffer_begin_access(buffer->shm_buffer); + } else { + *data = buffer->saved_data; + } + return true; +} + +static void shm_client_buffer_end_data_ptr_access(struct wlr_buffer *wlr_buffer) { + struct wlr_shm_client_buffer *buffer = + shm_client_buffer_from_buffer(wlr_buffer); + if (buffer->shm_buffer != NULL) { + wl_shm_buffer_end_access(buffer->shm_buffer); + } +} + +static const struct wlr_buffer_impl shm_client_buffer_impl = { + .destroy = shm_client_buffer_destroy, + .begin_data_ptr_access = shm_client_buffer_begin_data_ptr_access, + .end_data_ptr_access = shm_client_buffer_end_data_ptr_access, +}; + +static void shm_client_buffer_resource_handle_destroy( + struct wl_listener *listener, void *data) { + struct wlr_shm_client_buffer *buffer = + wl_container_of(listener, buffer, resource_destroy); + + // In order to still be able to access the shared memory region, we need to + // keep a reference to the wl_shm_pool + buffer->saved_shm_pool = wl_shm_buffer_ref_pool(buffer->shm_buffer); + buffer->saved_data = wl_shm_buffer_get_data(buffer->shm_buffer); + + // The wl_shm_buffer destroys itself with the wl_resource + buffer->resource = NULL; + buffer->shm_buffer = NULL; + wl_list_remove(&buffer->resource_destroy.link); + wl_list_init(&buffer->resource_destroy.link); +} + +static void shm_client_buffer_handle_release(struct wl_listener *listener, + void *data) { + struct wlr_shm_client_buffer *buffer = + wl_container_of(listener, buffer, release); + if (buffer->resource != NULL) { + wl_buffer_send_release(buffer->resource); + } +} + +struct wlr_shm_client_buffer *shm_client_buffer_create( + struct wl_resource *resource) { + struct wl_shm_buffer *shm_buffer = wl_shm_buffer_get(resource); + assert(shm_buffer != NULL); + + int32_t width = wl_shm_buffer_get_width(shm_buffer); + int32_t height = wl_shm_buffer_get_height(shm_buffer); + + struct wlr_shm_client_buffer *buffer = calloc(1, sizeof(*buffer)); + if (buffer == NULL) { + return NULL; + } + wlr_buffer_init(&buffer->base, &shm_client_buffer_impl, width, height); + buffer->resource = resource; + buffer->shm_buffer = shm_buffer; + + enum wl_shm_format wl_shm_format = wl_shm_buffer_get_format(shm_buffer); + buffer->format = convert_wl_shm_format_to_drm(wl_shm_format); + buffer->stride = wl_shm_buffer_get_stride(shm_buffer); + + buffer->resource_destroy.notify = shm_client_buffer_resource_handle_destroy; + wl_resource_add_destroy_listener(resource, &buffer->resource_destroy); + + buffer->release.notify = shm_client_buffer_handle_release; + wl_signal_add(&buffer->base.events.release, &buffer->release); + + return buffer; +}