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
This commit is contained in:
Daniel van Vugt 2020-02-19 14:05:57 +08:00
parent c5d2fc856a
commit 282b09c17e
4 changed files with 118 additions and 4 deletions

View File

@ -3743,7 +3743,13 @@ needs_flatten_effect (ClutterActor *self)
CLUTTER_DEBUG_DISABLE_OFFSCREEN_REDIRECT)) CLUTTER_DEBUG_DISABLE_OFFSCREEN_REDIRECT))
return FALSE; 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; return TRUE;
else if (priv->offscreen_redirect & CLUTTER_OFFSCREEN_REDIRECT_AUTOMATIC_FOR_OPACITY) 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; 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); _clutter_effect_paint (priv->current_effect, paint_context, run_flags);
priv->current_effect = old_current_effect; priv->current_effect = old_current_effect;

View File

@ -538,6 +538,10 @@ typedef enum /*< prefix=CLUTTER_ACTOR >*/
* virtual returns %TRUE. * virtual returns %TRUE.
* @CLUTTER_OFFSCREEN_REDIRECT_ALWAYS: Always redirect the actor to an * @CLUTTER_OFFSCREEN_REDIRECT_ALWAYS: Always redirect the actor to an
* offscreen buffer even if it is fully opaque. * 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(). * Possible flags to pass to clutter_actor_set_offscreen_redirect().
* *
@ -546,7 +550,8 @@ typedef enum /*< prefix=CLUTTER_ACTOR >*/
typedef enum /*< prefix=CLUTTER_OFFSCREEN_REDIRECT >*/ typedef enum /*< prefix=CLUTTER_OFFSCREEN_REDIRECT >*/
{ {
CLUTTER_OFFSCREEN_REDIRECT_AUTOMATIC_FOR_OPACITY = 1 << 0, CLUTTER_OFFSCREEN_REDIRECT_AUTOMATIC_FOR_OPACITY = 1 << 0,
CLUTTER_OFFSCREEN_REDIRECT_ALWAYS = 1<<1 CLUTTER_OFFSCREEN_REDIRECT_ALWAYS = 1 << 1,
CLUTTER_OFFSCREEN_REDIRECT_ON_IDLE = 1 << 2
} ClutterOffscreenRedirect; } ClutterOffscreenRedirect;
/** /**
@ -673,12 +678,15 @@ typedef enum /*< prefix=CLUTTER_BIND >*/
* has queued a redraw before this paint. This implies that the effect * has queued a redraw before this paint. This implies that the effect
* should call clutter_actor_continue_paint() to chain to the next * should call clutter_actor_continue_paint() to chain to the next
* effect and can not cache any results from a previous paint. * 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. * Flags passed to the paint or pick method of #ClutterEffect.
*/ */
typedef enum /*< prefix=CLUTTER_EFFECT_PAINT >*/ 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; } ClutterEffectPaintFlags;
/** /**

View File

@ -469,6 +469,13 @@ clutter_offscreen_effect_paint (ClutterEffect *effect,
ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect); ClutterOffscreenEffect *self = CLUTTER_OFFSCREEN_EFFECT (effect);
ClutterOffscreenEffectPrivate *priv = self->priv; 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 /* 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. * then we can just use the cached image in the FBO.
*/ */

View File

@ -193,6 +193,9 @@ verify_redraws (gpointer user_data)
{ {
Data *data = 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 */ /* Queueing a redraw on the actor should cause a redraw */
clutter_actor_queue_redraw (data->container); clutter_actor_queue_redraw (data->container);
verify_redraw (data, 1); verify_redraw (data, 1);
@ -220,6 +223,7 @@ static gboolean
run_verify (gpointer user_data) run_verify (gpointer user_data)
{ {
Data *data = user_data; Data *data = user_data;
int i;
group_has_overlaps = FALSE; group_has_overlaps = FALSE;
@ -313,6 +317,90 @@ run_verify (gpointer user_data)
0, 0,
255); 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 */ /* Check redraws */
g_idle_add (verify_redraws, data); g_idle_add (verify_redraws, data);