From 282b09c17ef366aa49ae83950f087c1592b081d5 Mon Sep 17 00:00:00 2001 From: Daniel van Vugt Date: Wed, 19 Feb 2020 14:05:57 +0800 Subject: [PATCH] clutter/actor: Add CLUTTER_OFFSCREEN_REDIRECT_ON_IDLE Which offscreens actor rendering only in cases where it hasn't changed for 2 frames or more. This avoids the performance penalty of offscreening an actor whose content is trying to animate at full frame rate. It will switch automatically. https://gitlab.gnome.org/GNOME/mutter/merge_requests/1069 --- clutter/clutter/clutter-actor.c | 13 ++- clutter/clutter/clutter-enums.h | 14 ++- clutter/clutter/clutter-offscreen-effect.c | 7 ++ .../conform/actor-offscreen-redirect.c | 88 +++++++++++++++++++ 4 files changed, 118 insertions(+), 4 deletions(-) diff --git a/clutter/clutter/clutter-actor.c b/clutter/clutter/clutter-actor.c index 163d30d43..728fd5cca 100644 --- a/clutter/clutter/clutter-actor.c +++ b/clutter/clutter/clutter-actor.c @@ -3743,7 +3743,13 @@ needs_flatten_effect (ClutterActor *self) CLUTTER_DEBUG_DISABLE_OFFSCREEN_REDIRECT)) return FALSE; - if (priv->offscreen_redirect & CLUTTER_OFFSCREEN_REDIRECT_ALWAYS) + /* We need to enable the effect immediately even in ON_IDLE because that can + * only be implemented efficiently within the effect itself. + * If it was implemented here using just priv->is_dirty then we would lose + * the ability to animate opacity without repaints. + */ + if ((priv->offscreen_redirect & CLUTTER_OFFSCREEN_REDIRECT_ALWAYS) || + (priv->offscreen_redirect & CLUTTER_OFFSCREEN_REDIRECT_ON_IDLE)) return TRUE; else if (priv->offscreen_redirect & CLUTTER_OFFSCREEN_REDIRECT_AUTOMATIC_FOR_OPACITY) { @@ -4201,6 +4207,11 @@ clutter_actor_continue_paint (ClutterActor *self, run_flags |= CLUTTER_EFFECT_PAINT_ACTOR_DIRTY; } + if (priv->current_effect == priv->flatten_effect && + priv->offscreen_redirect & CLUTTER_OFFSCREEN_REDIRECT_ON_IDLE && + run_flags & CLUTTER_EFFECT_PAINT_ACTOR_DIRTY) + run_flags |= CLUTTER_EFFECT_PAINT_BYPASS_EFFECT; + _clutter_effect_paint (priv->current_effect, paint_context, run_flags); priv->current_effect = old_current_effect; diff --git a/clutter/clutter/clutter-enums.h b/clutter/clutter/clutter-enums.h index d53b7ab25..ed178762b 100644 --- a/clutter/clutter/clutter-enums.h +++ b/clutter/clutter/clutter-enums.h @@ -538,6 +538,10 @@ typedef enum /*< prefix=CLUTTER_ACTOR >*/ * virtual returns %TRUE. * @CLUTTER_OFFSCREEN_REDIRECT_ALWAYS: Always redirect the actor to an * offscreen buffer even if it is fully opaque. + * @CLUTTER_OFFSCREEN_REDIRECT_ON_IDLE: Only redirect the actor if it is the + * most efficient thing to do based on its recent repaint behaviour. That + * means when its contents are changing less frequently than it's being used + * on stage. * * Possible flags to pass to clutter_actor_set_offscreen_redirect(). * @@ -545,8 +549,9 @@ typedef enum /*< prefix=CLUTTER_ACTOR >*/ */ typedef enum /*< prefix=CLUTTER_OFFSCREEN_REDIRECT >*/ { - CLUTTER_OFFSCREEN_REDIRECT_AUTOMATIC_FOR_OPACITY = 1<<0, - CLUTTER_OFFSCREEN_REDIRECT_ALWAYS = 1<<1 + CLUTTER_OFFSCREEN_REDIRECT_AUTOMATIC_FOR_OPACITY = 1 << 0, + CLUTTER_OFFSCREEN_REDIRECT_ALWAYS = 1 << 1, + CLUTTER_OFFSCREEN_REDIRECT_ON_IDLE = 1 << 2 } ClutterOffscreenRedirect; /** @@ -673,12 +678,15 @@ typedef enum /*< prefix=CLUTTER_BIND >*/ * has queued a redraw before this paint. This implies that the effect * should call clutter_actor_continue_paint() to chain to the next * effect and can not cache any results from a previous paint. + * @CLUTTER_EFFECT_PAINT_BYPASS_EFFECT: The effect should not be used + * on this frame, but it will be asked to paint the actor still. * * Flags passed to the ‘paint’ or ‘pick’ method of #ClutterEffect. */ typedef enum /*< prefix=CLUTTER_EFFECT_PAINT >*/ { - CLUTTER_EFFECT_PAINT_ACTOR_DIRTY = (1 << 0) + CLUTTER_EFFECT_PAINT_ACTOR_DIRTY = (1 << 0), + CLUTTER_EFFECT_PAINT_BYPASS_EFFECT = (1 << 1) } ClutterEffectPaintFlags; /** diff --git a/clutter/clutter/clutter-offscreen-effect.c b/clutter/clutter/clutter-offscreen-effect.c index 89467e541..ac4ea98e2 100644 --- a/clutter/clutter/clutter-offscreen-effect.c +++ b/clutter/clutter/clutter-offscreen-effect.c @@ -469,6 +469,13 @@ clutter_offscreen_effect_paint (ClutterEffect *effect, ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect); ClutterOffscreenEffectPrivate *priv = self->priv; + if (flags & CLUTTER_EFFECT_PAINT_BYPASS_EFFECT) + { + clutter_actor_continue_paint (priv->actor, paint_context); + cogl_clear_object (&priv->offscreen); + return; + } + /* If we've already got a cached image and the actor hasn't been redrawn * then we can just use the cached image in the FBO. */ diff --git a/src/tests/clutter/conform/actor-offscreen-redirect.c b/src/tests/clutter/conform/actor-offscreen-redirect.c index 820ccb4b4..a3faaabfc 100644 --- a/src/tests/clutter/conform/actor-offscreen-redirect.c +++ b/src/tests/clutter/conform/actor-offscreen-redirect.c @@ -193,6 +193,9 @@ verify_redraws (gpointer user_data) { Data *data = user_data; + clutter_actor_set_offscreen_redirect (data->container, + CLUTTER_OFFSCREEN_REDIRECT_ALWAYS); + /* Queueing a redraw on the actor should cause a redraw */ clutter_actor_queue_redraw (data->container); verify_redraw (data, 1); @@ -220,6 +223,7 @@ static gboolean run_verify (gpointer user_data) { Data *data = user_data; + int i; group_has_overlaps = FALSE; @@ -313,6 +317,90 @@ run_verify (gpointer user_data) 0, 255); + /* ON_IDLE: Defer redirection through the FBO until it is deemed to be the + * best performing option, which means when the actor's contents have + * stopped changing. + */ + clutter_actor_set_offscreen_redirect (data->container, + CLUTTER_OFFSCREEN_REDIRECT_ON_IDLE); + + /* Changing modes should not incur a redraw */ + verify_results (data, + 255, 0, 0, + 0, + 255); + + /* These will incur a redraw because the actor is dirty: */ + for (i = 0; i < 10; i++) + { + clutter_actor_queue_redraw (data->container); + verify_results (data, + 255, 0, 0, + 1, + 255); + } + + /* The actor is not dirty, but also not yet cached so a redraw is expected */ + verify_results (data, + 255, 0, 0, + 1, + 255); + + /* These will NOT incur a redraw because the actor is unchanged: */ + for (i = 0; i < 10; i++) + { + verify_results (data, + 255, 0, 0, + 0, + 255); + } + + /* The first opacity change should require no redaw */ + clutter_actor_set_opacity (data->container, 64); + verify_results (data, + 255, 191, 191, + 0, + 255); + + /* The second opacity change should require no redaw */ + clutter_actor_set_opacity (data->container, 127); + verify_results (data, + 255, 127, 127, + 0, + 255); + + /* The third opacity change should require no redaw */ + clutter_actor_set_opacity (data->container, 255); + verify_results (data, + 255, 0, 0, + 0, + 255); + + /* Now several frames without the actor changing AND the FBO is populated. + * Expect no internal repaints. + */ + for (i = 0; i < 10; i++) + { + verify_results (data, + 255, 0, 0, + 0, + 255); + } + + /* Another opacity change, no redraw expected */ + clutter_actor_set_opacity (data->container, 127); + verify_results (data, + 255, 127, 127, + 0, + 255); + + /* Finally the actor's content changes so a redraw is expected */ + clutter_actor_queue_redraw (data->container); + verify_results (data, + 255, 127, 127, + 1, + 127); + /* Check redraws */ g_idle_add (verify_redraws, data);