drm: handle client buffer destroyed while writeback scheduled

Currently when a client buffer gets destroyed, the output capture task
gets destroyed with weston_capture_task_buffer_destroy_handler().

The problem is that we may have a writeback task scheduled, so
wb_state->ct would be pointing to a ct that has already been retired
and destroyed.

In this commit we start handling this case.

Signed-off-by: Leandro Ribeiro <leandro.ribeiro@collabora.com>
(cherry picked from commit 1152c53e58)
Signed-off-by: Marius Vlad <marius.vlad@collabora.com>
This commit is contained in:
Leandro Ribeiro 2026-03-10 20:37:41 -03:00 committed by Marius Vlad
parent 65c7cc45b3
commit 641ac3ef82
2 changed files with 48 additions and 2 deletions

View file

@ -472,6 +472,7 @@ struct drm_writeback_state {
enum writeback_screenshot_state state;
struct weston_capture_task *ct;
struct wl_listener buffer_destroy_listener;
struct drm_fb *fb;
int32_t out_fence_fd;

View file

@ -570,9 +570,28 @@ drm_writeback_state_free(struct drm_writeback_state *state)
drm_fb_unref(*fb);
wl_array_release(&state->referenced_fbs);
wl_list_remove(&state->buffer_destroy_listener.link);
free(state);
}
static void
drm_writeback_state_buffer_destroy_handler(struct wl_listener *listener, void *data)
{
struct drm_writeback_state *state =
container_of(listener, struct drm_writeback_state,
buffer_destroy_listener);
/**
* Client buffer destroyed while the wb job was scheduled.
* weston_capture_task_buffer_destroy_handler() retires the capture task
* when the buffer is gone, so drop it from the state. The state is
* destroyed once the wb job completes.
*/
state->ct = NULL;
wl_list_remove(&state->buffer_destroy_listener.link);
}
static void
drm_output_pick_writeback_capture_task(struct drm_output *output)
{
@ -626,6 +645,11 @@ drm_output_pick_writeback_capture_task(struct drm_output *output)
output->wb_state->state = DRM_OUTPUT_WB_SCREENSHOT_PREPARE_COMMIT;
output->wb_state->ct = ct;
output->wb_state->buffer_destroy_listener.notify =
drm_writeback_state_buffer_destroy_handler;
wl_resource_add_destroy_listener(buffer->resource,
&output->wb_state->buffer_destroy_listener);
return;
err_fb:
@ -2899,12 +2923,21 @@ static void
drm_writeback_success_screenshot(struct drm_writeback_state *state)
{
struct drm_output *output = state->output;
struct weston_buffer *buffer =
weston_capture_task_get_buffer(state->ct);
struct weston_buffer *buffer;
int width, height;
int dst_stride, src_stride;
uint32_t *src, *dst;
/**
* Capture task already retired, see
* drm_writeback_state_buffer_destroy_handler(). Here we destroy the wb
* state.
*/
if (!state->ct)
goto destroy_state;
buffer = weston_capture_task_get_buffer(state->ct);
src = state->fb->map;
src_stride = state->fb->strides[0];
@ -2921,6 +2954,8 @@ drm_writeback_success_screenshot(struct drm_writeback_state *state)
wl_shm_buffer_end_access(buffer->shm_buffer);
weston_capture_task_retire_complete(state->ct);
destroy_state:
drm_writeback_state_free(state);
output->wb_state = NULL;
}
@ -2931,7 +2966,17 @@ drm_writeback_fail_screenshot(struct drm_writeback_state *state,
{
struct drm_output *output = state->output;
/**
* Capture task already retired, see
* drm_writeback_state_buffer_destroy_handler(). Here we destroy the wb
* state.
*/
if (!state->ct)
goto destroy_state;
weston_capture_task_retire_failed(state->ct, err_msg);
destroy_state:
drm_writeback_state_free(state);
output->wb_state = NULL;
}