From fcb195cbd0c9169b50fc4ca029933839b744744d Mon Sep 17 00:00:00 2001 From: Daniel Stone Date: Thu, 9 Oct 2025 19:43:14 +0100 Subject: [PATCH] drm-backend: Reuse prior state where possible If we're in a steady state, doing nothing but flipping buffers, we can try to avoid going through our full routine of brute-forcing an acceptable plane state, by instead just reusing the old state and changing only the FB it refers to. Signed-off-by: Daniel Stone Signed-off-by: Derek Foreman --- libweston/backend-drm/drm-internal.h | 1 + libweston/backend-drm/state-helpers.c | 5 + libweston/backend-drm/state-propose.c | 189 ++++++++++++++++++++++++-- 3 files changed, 187 insertions(+), 8 deletions(-) diff --git a/libweston/backend-drm/drm-internal.h b/libweston/backend-drm/drm-internal.h index 677a37afb..0da8a2f2d 100644 --- a/libweston/backend-drm/drm-internal.h +++ b/libweston/backend-drm/drm-internal.h @@ -357,6 +357,7 @@ enum drm_output_propose_state_mode { DRM_OUTPUT_PROPOSE_STATE_RENDERER_AND_CURSOR, /**< only assign to renderer & cursor plane */ DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY, /**< only assign to renderer */ DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY, /**< no renderer use, only planes */ + DRM_OUTPUT_PROPOSE_STATE_REUSE = 128, /**< bit indicates reuse prior state with new buffers */ }; /* diff --git a/libweston/backend-drm/state-helpers.c b/libweston/backend-drm/state-helpers.c index 18a00fca5..de3df3310 100644 --- a/libweston/backend-drm/state-helpers.c +++ b/libweston/backend-drm/state-helpers.c @@ -33,6 +33,7 @@ #include #include "drm-internal.h" +#include "shared/weston-assert.h" #include "shared/weston-drm-fourcc.h" #include "shared/xalloc.h" @@ -401,9 +402,13 @@ drm_output_state_duplicate(struct drm_output_state *src, struct drm_pending_state *pending_state, enum drm_output_state_duplicate_mode plane_mode) { + struct weston_compositor *compositor = src->output->base.compositor; struct drm_output_state *dst = xmalloc(sizeof(*dst)); struct drm_plane_state *ps; + /* The reuse bit isn't stored in the state */ + weston_assert_false(compositor, src->mode & DRM_OUTPUT_PROPOSE_STATE_REUSE); + /* Copy the whole structure, then individually modify the * pending_state, as well as the list link into our pending * state. */ diff --git a/libweston/backend-drm/state-propose.c b/libweston/backend-drm/state-propose.c index 9e4ccbc79..350d5af37 100644 --- a/libweston/backend-drm/state-propose.c +++ b/libweston/backend-drm/state-propose.c @@ -54,13 +54,27 @@ static const char *const drm_output_propose_state_mode_as_string[] = { [DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY] = "plane-only state" }; +static const char *const drm_output_propose_reused_state_mode_as_string[] = { + [DRM_OUTPUT_PROPOSE_STATE_INVALID] = "reused invalid(uninitialized) state", + [DRM_OUTPUT_PROPOSE_STATE_MIXED] = "reused mixed state", + [DRM_OUTPUT_PROPOSE_STATE_RENDERER_AND_CURSOR] = "reused renderer-and-cursor state", + [DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY] = "reused renderer-only state", + [DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY] = "reused plane-only state", +}; + static const char * drm_propose_state_mode_to_string(enum drm_output_propose_state_mode mode) { + bool reuse = mode & DRM_OUTPUT_PROPOSE_STATE_REUSE; + + mode &= ~DRM_OUTPUT_PROPOSE_STATE_REUSE; if (mode < 0 || mode >= ARRAY_LENGTH(drm_output_propose_state_mode_as_string)) return " unknown compositing mode"; - return drm_output_propose_state_mode_as_string[mode]; + if (reuse) + return drm_output_propose_reused_state_mode_as_string[mode]; + else + return drm_output_propose_state_mode_as_string[mode]; } static bool @@ -1007,6 +1021,151 @@ debug_propose_fail(struct drm_output *output, reason); } +static struct drm_output_state * +drm_output_propose_state_try_reuse(struct weston_output *output_base, + struct drm_pending_state *pending_state, + enum drm_output_propose_state_mode mode) +{ + struct drm_output *output = to_drm_output(output_base); + struct drm_device *device = output->device; + struct drm_backend *b = device->backend; + struct weston_compositor *compositor = b->compositor; + struct weston_paint_node *pnode; + struct drm_output_state *state; + + /* These states are where we end up when we can't use planes + * at all, sometimes simply because we don't have a renderer + * fb yet. In that case we'd immediately get into a better + * state on the next repaint, even with no scene graph + * changes. But if we allow reuse we're stuck in a bad state + * waiting for a scene graph change to move us out of it. + * + * Just disable this optimization for these suboptimal states. + */ + if (output->state_cur->mode == DRM_OUTPUT_PROPOSE_STATE_RENDERER_ONLY || + output->state_cur->mode == DRM_OUTPUT_PROPOSE_STATE_RENDERER_AND_CURSOR) { + debug_propose_fail(output, mode, "probable fallback state"); + return NULL; + } + + if (output->state_cur->mode == DRM_OUTPUT_PROPOSE_STATE_INVALID) { + debug_propose_fail(output, mode, "no previous state"); + return NULL; + } + + if (output_base->paint_node_changes & ~WESTON_PAINT_NODE_BUFFER_DIRTY) { + debug_propose_fail(output, mode, "state is outdated"); + return NULL; + } + + if (output->state_cur->planes_enabled != !output_base->disable_planes) { + debug_propose_fail(output, mode, "planes_enabled changed"); + return NULL; + } + + state = drm_output_state_duplicate(output->state_cur, + pending_state, + DRM_OUTPUT_STATE_PRESERVE_PLANES); + if (!state) { + debug_propose_fail(output, mode, "could not clone prior state"); + return NULL; + } + + wl_list_for_each(pnode, &output_base->paint_node_z_order_list, + z_order_link) { + enum try_view_on_plane_failure_reasons reasons; + struct drm_plane_state *pstate; + struct drm_plane *plane; + struct drm_fb *fb; + + /* we don't care about renderer views */ + if (pnode->plane == &output_base->primary_plane) { + drm_debug(b, "\t\t[reuse] ignoring view %s on renderer plane\n", pnode->view->internal_name); + continue; + } + plane = (struct drm_plane *) pnode->plane; + pstate = drm_output_state_get_existing_plane(state, plane); + weston_assert_ptr_not_null(compositor, pstate); + pstate->ev = pnode->view; + + /* cursor is handled out of band */ + if (plane->type == WDRM_PLANE_TYPE_CURSOR) { + drm_debug(b, "\t\t[reuse] ignoring cursor plane for view %s\n", pnode->view->internal_name); + continue; + } + + drm_debug(b, "\t\t[reuse] view %s has plane %d\n", + pnode->view->internal_name, plane->plane_id); + + /* FIXME: If we get here, there should be a valid weston_buffer, and that's + * all we should ever need at this point. If the buffer is deleted while + * attached to a surface right now weston_view_has_valid_buffer() sees the + * buffer as invalid. We don't want that to be the case, but we also don't + * want paint node DIRTY bits to track that. This will be cleaned up in the + * future by chasing down remaining bugs in buffer lifecycle management, at + * which point we replace this with a weston_assert() instead. + */ + if (!weston_view_has_valid_buffer(pnode->view)) { + drm_debug(b, "\t\t[reuse] view %s no longer has a valid buffer\n", + pnode->view->internal_name); + drm_output_state_free(state); + return NULL; + } + + fb = drm_fb_get_from_paint_node(state, pnode, &reasons); + if (!fb) { + char *fr_str = bits_to_str(pnode->try_view_on_plane_failure_reasons, + weston_plane_failure_reasons_to_str); + char *msg; + + str_printf(&msg, "couldn't get new FB: %s", fr_str); + debug_propose_fail(output, mode, msg); + free(msg); + free(fr_str); + drm_output_state_free(state); + return NULL; + } + + drm_fb_unref(pstate->fb); + pstate->fb = fb; + + pstate->in_fence_fd = pnode->surface->acquire_fence_fd; + drm_debug(b, "\t\t[reuse] successfully stole away pnode %s to reused plane\n", pnode->internal_name); + /* XXX: When we set non-default color states in DRM, make sure they match */ + weston_buffer_reference(&pstate->fb_ref.buffer, + pnode->surface->buffer_ref.buffer, + BUFFER_MAY_BE_ACCESSED); + weston_buffer_release_reference(&pstate->fb_ref.release, + pnode->surface->buffer_release_ref.buffer_release); + } + + if (drm_pending_state_test(pending_state) != 0) { + debug_propose_fail(output, mode, "atomic test not OK"); + drm_output_state_free(state); + return NULL; + } + + /* In any state with the renderer, we need to unreference and remove + * the previous renderer fb. + */ + if (state->mode != DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY) { + struct drm_plane *scanout_plane = output->scanout_handle->plane; + struct drm_plane_state *pstate = + drm_output_state_get_existing_plane(state, scanout_plane); + + /* drm_output_repaint expects to see this */ + drm_debug(b, "\t\t[reuse] dropped reference on renderer fb %p\n", pstate->fb); + weston_assert_true(compositor, + pstate->fb->type == BUFFER_GBM_SURFACE || + pstate->fb->type == BUFFER_PIXMAN_DUMB || + pstate->fb->type == BUFFER_DMABUF_BACKEND); + drm_fb_unref(pstate->fb); + pstate->fb = NULL; + } + + return state; +} + static struct drm_output_state * drm_output_propose_state(struct weston_output *output_base, struct drm_pending_state *pending_state, @@ -1027,13 +1186,18 @@ drm_output_propose_state(struct weston_output *output_base, pixman_region32_t background_region; pixman_region32_t obscured_region; - bool renderer_ok = (mode != DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); int ret; /* Record the current lowest zpos of the overlay planes */ uint64_t current_lowest_zpos_overlay = DRM_PLANE_ZPOS_INVALID_PLANE; /* Record the current lowest zpos of the underlay plane */ uint64_t current_lowest_zpos_underlay = DRM_PLANE_ZPOS_INVALID_PLANE; + if (mode & DRM_OUTPUT_PROPOSE_STATE_REUSE) { + return drm_output_propose_state_try_reuse(output_base, + pending_state, + mode); + } + assert(!output->state_last); state = drm_output_state_duplicate(output->state_cur, pending_state, @@ -1182,6 +1346,7 @@ drm_output_propose_state(struct weston_output *output_base, struct drm_plane_state *ps = NULL; bool need_underlay = false; pixman_region32_t tmp; + bool renderer_ok = (mode != DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY); drm_debug(b, "\t\t\t[view] evaluating view %s for plane " "assignment on output %s (%lu)\n", @@ -1368,22 +1533,30 @@ drm_assign_planes(struct weston_output *output_base) struct drm_device *device = output->device; struct drm_backend *b = device->backend; struct drm_pending_state *pending_state = device->repaint_data; - struct drm_output_state *state = NULL; + struct drm_output_state *state; struct drm_plane_state *plane_state; struct drm_writeback_state *wb_state = output->wb_state; struct weston_paint_node *pnode; struct weston_plane *primary = &output_base->primary_plane; - enum drm_output_propose_state_mode mode = DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY; + enum drm_output_propose_state_mode mode; assert(output); drm_debug(b, "\t[repaint] preparing state for output %s (%lu)\n", output_base->name, (unsigned long) output_base->id); - if (!device->disable_client_buffer_scanout && - !output_base->disable_planes && - !output->is_virtual && b->gbm) { + drm_debug(b, "\t[repaint] trying to reuse prior %s\n", + drm_propose_state_mode_to_string(output->state_cur->mode)); + + mode = DRM_OUTPUT_PROPOSE_STATE_REUSE | output->state_cur->mode; + state = drm_output_propose_state(output_base, pending_state, mode); + if (!state) + drm_debug(b, "\t[repaint] could not reuse prior state\n"); + + if (!state && !device->disable_client_buffer_scanout && + !output_base->disable_planes && !output->is_virtual && b->gbm) { drm_debug(b, "\t[repaint] trying planes-only build state\n"); + mode = DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY; state = drm_output_propose_state(output_base, pending_state, mode); if (!state) { drm_debug(b, "\t[repaint] could not build planes-only " @@ -1393,7 +1566,7 @@ drm_assign_planes(struct weston_output *output_base) pending_state, mode); } - } else { + } else if (!state) { drm_debug(b, "\t[state] no hardware plane support\n"); }