Use paint volumes to do automatic culling

This uses actor paint volumes to perform culling during
clutter_actor_paint.

When performing a clipped redraw (because only a few localized actors
changed) then as we traverse the scenegraph painting the actors we can
now ignore actors that don't intersect the clip region. Early testing
shows this can have a big performance benefit; e.g. 100% fps improvement
for test-state with culling enabled and we hope that there are even much
more compelling examples than that in the real world,

Most Clutter applications are 2Dish interfaces and have quite a lot of
actors that get continuously painted when anything is animated. The
dynamic actors are often localized to an area of user focus though so
with culling we can completely avoid painting any of the static actors
outside the current clip region.

Obviously the cost of culling has to be offset against the cost of
painting to determine if it's a win, but our (limited) testing suggests
it should be a win for most applications.

Note: we hope we will be able to also bring another performance bump
from culling with another iteration - hopefully in the 1.6 cycle - to
avoid doing the culling in screen space and instead do it in the stage's
model space. This will hopefully let us minimize the cost of
transforming the actor volumes for culling.
This commit is contained in:
Robert Bragg 2010-09-07 22:21:28 +01:00
parent ef8be9e25e
commit b499696d83
10 changed files with 86 additions and 11 deletions

View File

@ -2478,6 +2478,38 @@ _clutter_actor_draw_paint_volume (ClutterActor *self)
clutter_paint_volume_free (&fake_pv); clutter_paint_volume_free (&fake_pv);
} }
/* Returns TRUE if the actor can be ignored */
static gboolean
cull_actor (ClutterActor *self)
{
ClutterActorPrivate *priv = self->priv;
ClutterActor *stage;
const ClutterGeometry *stage_clip;
ClutterActorBox *box;
ClutterGeometry paint_geom;
if (G_UNLIKELY (clutter_paint_debug_flags & CLUTTER_DEBUG_DISABLE_CULLING))
return FALSE;
stage = _clutter_actor_get_stage_internal (self);
stage_clip = _clutter_stage_get_clip (CLUTTER_STAGE (stage));
if (G_UNLIKELY (!stage_clip))
return FALSE;
/* XXX: It might be better if _get_paint_box returned a
* ClutterGeometry instead. */
box = &priv->last_paint_box;
paint_geom.x = box->x1;
paint_geom.y = box->y1;
paint_geom.width = box->x2 - box->x1;
paint_geom.height = box->y2 - box->y1;
if (!clutter_geometry_intersects (stage_clip, &paint_geom))
return TRUE;
else
return FALSE;
}
/** /**
* clutter_actor_paint: * clutter_actor_paint:
* @self: A #ClutterActor * @self: A #ClutterActor
@ -2621,9 +2653,27 @@ clutter_actor_paint (ClutterActor *self)
* XXX: We are starting to do a lot of vertex transforms on * XXX: We are starting to do a lot of vertex transforms on
* the CPU in a typical paint, so at some point we should * the CPU in a typical paint, so at some point we should
* audit these and consider caching some things. * audit these and consider caching some things.
*
* XXX: We should consider doing all our culling in the
* stage's model space using PaintVolumes so we don't have
* to project actor paint volumes all the way into window
* coordinates!
* XXX: To do this we also need a way to store an
* "absolute paint volume" in some way. Currently the
* paint volumes are defined relative to a referenced
* actor's coordinates, but we'd need to be able to cache
* the last paint volume used with the actor's *current*
* modelview and either with a specific projection matrix
* or we'd need to be able to invalidate paint-volumes on
* projection changes.
*/ */
if (clutter_actor_get_paint_box (self, &priv->last_paint_box)) if (clutter_actor_get_paint_box (self, &priv->last_paint_box))
priv->last_paint_box_valid = TRUE; {
priv->last_paint_box_valid = TRUE;
if (cull_actor (self))
goto done;
}
else else
priv->last_paint_box_valid = FALSE; priv->last_paint_box_valid = FALSE;
} }
@ -2659,6 +2709,7 @@ clutter_actor_paint (ClutterActor *self)
g_signal_emit (self, actor_signals[PICK], 0, &col); g_signal_emit (self, actor_signals[PICK], 0, &col);
} }
done:
if (clip_set) if (clip_set)
cogl_clip_pop(); cogl_clip_pop();

View File

@ -36,7 +36,8 @@ typedef enum {
CLUTTER_DEBUG_DISABLE_SWAP_EVENTS = 1 << 0, CLUTTER_DEBUG_DISABLE_SWAP_EVENTS = 1 << 0,
CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS = 1 << 1, CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS = 1 << 1,
CLUTTER_DEBUG_REDRAWS = 1 << 2, CLUTTER_DEBUG_REDRAWS = 1 << 2,
CLUTTER_DEBUG_PAINT_VOLUMES = 1 << 3 CLUTTER_DEBUG_PAINT_VOLUMES = 1 << 3,
CLUTTER_DEBUG_DISABLE_CULLING = 1 << 4
} ClutterDrawDebugFlag; } ClutterDrawDebugFlag;
#ifdef CLUTTER_ENABLE_DEBUG #ifdef CLUTTER_ENABLE_DEBUG

View File

@ -181,6 +181,7 @@ static const GDebugKey clutter_paint_debug_keys[] = {
{ "disable-clipped-redraws", CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS }, { "disable-clipped-redraws", CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS },
{ "redraws", CLUTTER_DEBUG_REDRAWS }, { "redraws", CLUTTER_DEBUG_REDRAWS },
{ "paint-volumes", CLUTTER_DEBUG_PAINT_VOLUMES }, { "paint-volumes", CLUTTER_DEBUG_PAINT_VOLUMES },
{ "disable-culling", CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS }
}; };
#ifdef CLUTTER_ENABLE_PROFILE #ifdef CLUTTER_ENABLE_PROFILE
@ -622,7 +623,7 @@ _clutter_do_pick (ClutterStage *stage,
*/ */
CLUTTER_TIMER_START (_clutter_uprof_context, pick_paint); CLUTTER_TIMER_START (_clutter_uprof_context, pick_paint);
context->pick_mode = mode; context->pick_mode = mode;
_clutter_stage_do_paint (stage); _clutter_stage_do_paint (stage, NULL);
context->pick_mode = CLUTTER_PICK_NONE; context->pick_mode = CLUTTER_PICK_NONE;
CLUTTER_TIMER_STOP (_clutter_uprof_context, pick_paint); CLUTTER_TIMER_STOP (_clutter_uprof_context, pick_paint);

View File

@ -329,7 +329,8 @@ void _clutter_stage_manager_set_default_stage (ClutterStageManager *stage_manage
ClutterStage *stage); ClutterStage *stage);
/* stage */ /* stage */
void _clutter_stage_do_paint (ClutterStage *stage); void _clutter_stage_do_paint (ClutterStage *stage,
const ClutterGeometry *clip);
void _clutter_stage_set_window (ClutterStage *stage, void _clutter_stage_set_window (ClutterStage *stage,
ClutterStageWindow *stage_window); ClutterStageWindow *stage_window);
ClutterStageWindow *_clutter_stage_get_window (ClutterStage *stage); ClutterStageWindow *_clutter_stage_get_window (ClutterStage *stage);
@ -377,6 +378,9 @@ guint _clutter_stage_get_picks_per_frame_counter (ClutterStage *stage);
ClutterPaintVolume *_clutter_stage_paint_volume_stack_allocate (ClutterStage *stage); ClutterPaintVolume *_clutter_stage_paint_volume_stack_allocate (ClutterStage *stage);
void _clutter_stage_paint_volume_stack_free_all (ClutterStage *stage); void _clutter_stage_paint_volume_stack_free_all (ClutterStage *stage);
const ClutterGeometry *_clutter_stage_get_clip (ClutterStage *stage);
/* vfuncs implemented by backend */ /* vfuncs implemented by backend */
GType _clutter_backend_impl_get_type (void); GType _clutter_backend_impl_get_type (void);

View File

@ -120,6 +120,8 @@ struct _ClutterStagePrivate
GArray *paint_volume_stack; GArray *paint_volume_stack;
const ClutterGeometry *current_paint_clip;
guint relayout_pending : 1; guint relayout_pending : 1;
guint redraw_pending : 1; guint redraw_pending : 1;
guint is_fullscreen : 1; guint is_fullscreen : 1;
@ -314,12 +316,20 @@ clutter_stage_allocate (ClutterActor *self,
/* This provides a common point of entry for painting the scenegraph /* This provides a common point of entry for painting the scenegraph
* for picking or painting... * for picking or painting...
*
* XXX: Instead of having a toplevel 2D clip region, it might be
* better to have a clip volume within the view frustum. This could
* allow us to avoid projecting actors into window coordinates to
* be able to cull them.
*/ */
void void
_clutter_stage_do_paint (ClutterStage *stage) _clutter_stage_do_paint (ClutterStage *stage, const ClutterGeometry *clip)
{ {
ClutterStagePrivate *priv = stage->priv;
priv->current_paint_clip = clip;
_clutter_stage_paint_volume_stack_free_all (stage); _clutter_stage_paint_volume_stack_free_all (stage);
clutter_actor_paint (CLUTTER_ACTOR (stage)); clutter_actor_paint (CLUTTER_ACTOR (stage));
priv->current_paint_clip = NULL;
} }
static void static void
@ -3100,3 +3110,11 @@ _clutter_stage_paint_volume_stack_free_all (ClutterStage *stage)
g_array_set_size (paint_volume_stack, 0); g_array_set_size (paint_volume_stack, 0);
} }
/* The is an out-of-band paramater available while painting that
* can be used to cull actors. */
const ClutterGeometry *
_clutter_stage_get_clip (ClutterStage *stage)
{
return stage->priv->current_paint_clip;
}

View File

@ -359,7 +359,7 @@ _clutter_stage_egl_redraw (ClutterStageEGL *stage_egl,
egl_surface = backend_egl->egl_surface; egl_surface = backend_egl->egl_surface;
#endif #endif
_clutter_stage_do_paint (CLUTTER_STAGE (wrapper)); _clutter_stage_do_paint (CLUTTER_STAGE (wrapper), NULL);
cogl_flush (); cogl_flush ();
eglSwapBuffers (backend_egl->edpy, egl_surface); eglSwapBuffers (backend_egl->edpy, egl_surface);

View File

@ -71,7 +71,7 @@ clutter_backend_egl_redraw (ClutterBackend *backend,
stage_egl = CLUTTER_STAGE_EGL (impl); stage_egl = CLUTTER_STAGE_EGL (impl);
eglWaitNative (EGL_CORE_NATIVE_ENGINE); eglWaitNative (EGL_CORE_NATIVE_ENGINE);
_clutter_stage_do_paint (stage); _clutter_stage_do_paint (stage, NULL);
cogl_flush (); cogl_flush ();
eglWaitGL(); eglWaitGL();
eglSwapBuffers (backend_egl->edpy, stage_egl->egl_surface); eglSwapBuffers (backend_egl->edpy, stage_egl->egl_surface);

View File

@ -544,11 +544,11 @@ _clutter_stage_glx_redraw (ClutterStageGLX *stage_glx,
stage_glx->bounding_redraw_clip.y, stage_glx->bounding_redraw_clip.y,
stage_glx->bounding_redraw_clip.width, stage_glx->bounding_redraw_clip.width,
stage_glx->bounding_redraw_clip.height); stage_glx->bounding_redraw_clip.height);
_clutter_stage_do_paint (stage); _clutter_stage_do_paint (stage, &stage_glx->bounding_redraw_clip);
cogl_clip_pop (); cogl_clip_pop ();
} }
else else
_clutter_stage_do_paint (stage); _clutter_stage_do_paint (stage, NULL);
if (clutter_paint_debug_flags & CLUTTER_DEBUG_REDRAWS && if (clutter_paint_debug_flags & CLUTTER_DEBUG_REDRAWS &&
may_use_clipped_redraw) may_use_clipped_redraw)

View File

@ -147,7 +147,7 @@ clutter_stage_osx_get_wrapper (ClutterStageWindow *stage_window);
- (void) drawRect: (NSRect) bounds - (void) drawRect: (NSRect) bounds
{ {
_clutter_stage_do_paint (CLUTTER_STAGE (self->stage_osx->wrapper)); _clutter_stage_do_paint (CLUTTER_STAGE (self->stage_osx->wrapper), NULL);
cogl_flush (); cogl_flush ();
[[self openGLContext] flushBuffer]; [[self openGLContext] flushBuffer];
} }

View File

@ -494,7 +494,7 @@ clutter_backend_win32_redraw (ClutterBackend *backend,
stage_win32 = CLUTTER_STAGE_WIN32 (impl); stage_win32 = CLUTTER_STAGE_WIN32 (impl);
/* this will cause the stage implementation to be painted */ /* this will cause the stage implementation to be painted */
_clutter_stage_do_paint (stage); _clutter_stage_do_paint (stage, NULL);
cogl_flush (); cogl_flush ();
if (stage_win32->client_dc) if (stage_win32->client_dc)