Commit 19b86229 authored by Robert Bragg's avatar Robert Bragg

Optimize culling by doing culling in eye-coordinates

This implements a variation of frustum culling whereby we convert screen
space clip rectangles into eye space mini-frustums so that we don't have
to repeatedly transform actor paint-volumes all the way into screen
coordinates to perform culling, we just have to apply the modelview
transform and then determine each points distance from the planes that
make up the clip frustum.

By avoiding the projective transform, perspective divide and viewport
scale for each point culled this makes culling much cheaper.
parent eef9078f
......@@ -147,9 +147,9 @@ void _clutter_actor_set_has_pointer (ClutterActor *self,
void _clutter_actor_queue_redraw_with_clip (ClutterActor *self,
ClutterRedrawFlags flags,
ClutterPaintVolume *clip_volume);
const ClutterPaintVolume *_clutter_actor_get_queue_redraw_clip (ClutterActor *self);
ClutterPaintVolume *_clutter_actor_get_queue_redraw_clip (ClutterActor *self);
void _clutter_actor_set_queue_redraw_clip (ClutterActor *self,
const ClutterPaintVolume *clip_volume);
ClutterPaintVolume *clip_volume);
void _clutter_actor_finish_queue_redraw (ClutterActor *self,
ClutterPaintVolume *clip);
......
This diff is collapsed.
......@@ -25,7 +25,8 @@ typedef enum {
CLUTTER_DEBUG_ANIMATION = 1 << 14,
CLUTTER_DEBUG_LAYOUT = 1 << 15,
CLUTTER_DEBUG_PICK = 1 << 16,
CLUTTER_DEBUG_EVENTLOOP = 1 << 17
CLUTTER_DEBUG_EVENTLOOP = 1 << 17,
CLUTTER_DEBUG_CLIPPING = 1 << 18
} ClutterDebugFlag;
typedef enum {
......
......@@ -169,7 +169,8 @@ static const GDebugKey clutter_debug_keys[] = {
{ "shader", CLUTTER_DEBUG_SHADER },
{ "multistage", CLUTTER_DEBUG_MULTISTAGE },
{ "animation", CLUTTER_DEBUG_ANIMATION },
{ "layout", CLUTTER_DEBUG_LAYOUT }
{ "layout", CLUTTER_DEBUG_LAYOUT },
{ "clipping", CLUTTER_DEBUG_CLIPPING }
};
#endif /* CLUTTER_ENABLE_DEBUG */
......
......@@ -23,11 +23,14 @@
#define __CLUTTER_PAINT_VOLUME_PRIVATE_H__
#include <clutter/clutter-types.h>
#include <clutter/clutter-private.h>
G_BEGIN_DECLS
struct _ClutterPaintVolume
{
/* A paint volume represents a volume in a given actors private
* coordinate system. */
ClutterActor *actor;
/* cuboid for the volume:
......@@ -99,8 +102,8 @@ struct _ClutterPaintVolume
*/
};
void _clutter_paint_volume_init_static (ClutterActor *actor,
ClutterPaintVolume *pv);
void _clutter_paint_volume_init_static (ClutterPaintVolume *pv,
ClutterActor *actor);
ClutterPaintVolume *_clutter_paint_volume_new (ClutterActor *actor);
void _clutter_paint_volume_copy_static (const ClutterPaintVolume *src_pv,
ClutterPaintVolume *dst_pv);
......@@ -120,6 +123,16 @@ void _clutter_paint_volume_axis_align (ClutterPaintVolu
void _clutter_paint_volume_set_reference_actor (ClutterPaintVolume *pv,
ClutterActor *actor);
ClutterCullResult _clutter_paint_volume_cull (ClutterPaintVolume *pv,
const ClutterPlane *planes);
void _clutter_paint_volume_get_stage_paint_box (ClutterPaintVolume *pv,
ClutterStage *stage,
ClutterActorBox *box);
void _clutter_paint_volume_transform_relative (ClutterPaintVolume *pv,
ClutterActor *relative_to_ancestor);
G_END_DECLS
#endif /* __CLUTTER_PAINT_VOLUME_PRIVATE_H__ */
......@@ -35,6 +35,7 @@
#include "clutter-actor-private.h"
#include "clutter-paint-volume-private.h"
#include "clutter-private.h"
#include "clutter-stage-private.h"
G_DEFINE_BOXED_TYPE (ClutterPaintVolume, clutter_paint_volume,
clutter_paint_volume_copy,
......@@ -90,11 +91,9 @@ _clutter_paint_volume_new (ClutterActor *actor)
* free it during _paint_volume_free().
*/
void
_clutter_paint_volume_init_static (ClutterActor *actor,
ClutterPaintVolume *pv)
_clutter_paint_volume_init_static (ClutterPaintVolume *pv,
ClutterActor *actor)
{
g_return_if_fail (actor != NULL);
pv->actor = actor;
memset (pv->vertices, 0, 8 * sizeof (ClutterVertex));
......@@ -433,12 +432,12 @@ void
clutter_paint_volume_union (ClutterPaintVolume *pv,
const ClutterPaintVolume *another_pv)
{
ClutterPaintVolume aligned_pv;
static const int key_vertices[4] = { 0, 1, 3, 4 };
g_return_if_fail (pv != NULL);
g_return_if_fail (pv->is_axis_aligned);
g_return_if_fail (another_pv != NULL);
g_return_if_fail (another_pv->is_axis_aligned);
/* NB: we only have to update vertices 0, 1, 3 and 4
* (See the ClutterPaintVolume typedef for more details) */
......@@ -459,6 +458,13 @@ clutter_paint_volume_union (ClutterPaintVolume *pv,
goto done;
}
if (!another_pv->is_axis_aligned)
{
_clutter_paint_volume_copy_static (another_pv, &aligned_pv);
_clutter_paint_volume_axis_align (&aligned_pv);
another_pv = &aligned_pv;
}
/* grow left*/
/* left vertices 0, 3, 4, 7 */
if (another_pv->vertices[0].x < pv->vertices[0].x)
......@@ -905,3 +911,154 @@ _clutter_paint_volume_set_reference_actor (ClutterPaintVolume *pv,
pv->actor = actor;
}
ClutterCullResult
_clutter_paint_volume_cull (ClutterPaintVolume *pv,
const ClutterPlane *planes)
{
int vertex_count;
ClutterVertex *vertices = pv->vertices;
gboolean in = TRUE;
gboolean out = TRUE;
int i;
int j;
/* We expect the volume to already be transformed into eye coordinates
*/
g_return_val_if_fail (pv->is_complete == TRUE, CLUTTER_CULL_RESULT_IN);
g_return_val_if_fail (pv->actor == NULL, CLUTTER_CULL_RESULT_IN);
if (pv->is_empty)
return CLUTTER_CULL_RESULT_OUT;
/* Most actors are 2D so we only have to transform the front 4
* vertices of the paint volume... */
if (G_LIKELY (pv->is_2d))
vertex_count = 4;
else
vertex_count = 8;
for (i = 0; i < vertex_count; i++)
{
gboolean point_in = TRUE;
for (j = 0; j < 4; j++)
{
ClutterVertex p;
float distance;
/* XXX: for perspective projections this can be optimized
* out because all the planes should pass through the origin
* so (0,0,0) is a valid v0. */
p.x = vertices[i].x - planes[j].v0.x;
p.y = vertices[i].y - planes[j].v0.y;
p.z = vertices[i].z - planes[j].v0.z;
distance =
planes[j].n.x * p.x + planes[j].n.y * p.y + planes[j].n.z * p.z;
if (distance < 0)
{
point_in = FALSE;
break;
}
}
if (!point_in)
in = FALSE;
else
out = FALSE;
}
if (in)
return CLUTTER_CULL_RESULT_IN;
else if (out)
return CLUTTER_CULL_RESULT_OUT;
else
return CLUTTER_CULL_RESULT_PARTIAL;
}
void
_clutter_paint_volume_get_stage_paint_box (ClutterPaintVolume *pv,
ClutterStage *stage,
ClutterActorBox *box)
{
ClutterPaintVolume projected_pv;
CoglMatrix modelview;
CoglMatrix projection;
float viewport[4];
_clutter_paint_volume_copy_static (pv, &projected_pv);
/* NB: _clutter_actor_apply_modelview_transform_recursive will never
* include the transformation between stage coordinates and OpenGL
* eye coordinates, we have to explicitly use the
* stage->apply_transform to get that... */
cogl_matrix_init_identity (&modelview);
/* If the paint volume isn't already in eye coordinates... */
if (pv->actor)
{
ClutterActor *stage_actor = CLUTTER_ACTOR (stage);
_clutter_actor_apply_modelview_transform (stage_actor, &modelview);
_clutter_actor_apply_modelview_transform_recursive (pv->actor,
stage_actor,
&modelview);
}
_clutter_stage_get_projection_matrix (stage, &projection);
_clutter_stage_get_viewport (stage,
&viewport[0],
&viewport[1],
&viewport[2],
&viewport[3]);
_clutter_paint_volume_project (&projected_pv,
&modelview,
&projection,
viewport);
_clutter_paint_volume_get_bounding_box (&projected_pv, box);
clutter_actor_box_clamp_to_pixel (box);
clutter_paint_volume_free (&projected_pv);
}
void
_clutter_paint_volume_transform_relative (ClutterPaintVolume *pv,
ClutterActor *relative_to_ancestor)
{
CoglMatrix matrix;
ClutterActor *actor;
actor = pv->actor;
g_return_if_fail (actor != NULL);
_clutter_paint_volume_set_reference_actor (pv, relative_to_ancestor);
cogl_matrix_init_identity (&matrix);
if (relative_to_ancestor == NULL)
{
/* NB: _clutter_actor_apply_modelview_transform_recursive will never
* include the transformation between stage coordinates and OpenGL
* eye coordinates, we have to explicitly use the
* stage->apply_transform to get that... */
ClutterActor *stage = _clutter_actor_get_stage_internal (actor);
/* We really can't do anything meaningful in this case so don't try
* to do any transform */
if (G_UNLIKELY (stage == NULL))
return;
_clutter_actor_apply_modelview_transform (stage, &matrix);
relative_to_ancestor = stage;
}
_clutter_actor_apply_modelview_transform_recursive (actor,
relative_to_ancestor,
&matrix);
_clutter_paint_volume_transform (pv, &matrix);
}
......@@ -232,6 +232,14 @@ typedef struct _ClutterPlane
CoglVector3 n;
} ClutterPlane;
typedef enum _ClutterCullResult
{
CLUTTER_CULL_RESULT_UNKNOWN,
CLUTTER_CULL_RESULT_IN,
CLUTTER_CULL_RESULT_OUT,
CLUTTER_CULL_RESULT_PARTIAL
} ClutterCullResult;
G_END_DECLS
#endif /* __CLUTTER_PRIVATE_H__ */
......@@ -25,6 +25,7 @@
#include <clutter/clutter-stage-window.h>
#include <clutter/clutter-stage.h>
#include <clutter/clutter-input-device.h>
#include <clutter/clutter-private.h>
G_BEGIN_DECLS
......@@ -75,7 +76,7 @@ guint _clutter_stage_get_picks_per_frame_counter (ClutterStage *stage);
ClutterPaintVolume *_clutter_stage_paint_volume_stack_allocate (ClutterStage *stage);
void _clutter_stage_paint_volume_stack_free_all (ClutterStage *stage);
const ClutterGeometry *_clutter_stage_get_clip (ClutterStage *stage);
const ClutterPlane *_clutter_stage_get_clip (ClutterStage *stage);
ClutterStageQueueRedrawEntry *_clutter_stage_queue_actor_redraw (ClutterStage *stage,
ClutterStageQueueRedrawEntry *entry,
......
......@@ -133,7 +133,7 @@ struct _ClutterStagePrivate
GArray *paint_volume_stack;
const ClutterGeometry *current_paint_clip;
ClutterPlane current_clip_planes[4];
GList *pending_queue_redraws;
......@@ -380,6 +380,110 @@ clutter_stage_allocate (ClutterActor *self,
}
}
typedef struct _Vector4
{
float x, y, z, w;
} Vector4;
static void
_cogl_util_get_eye_planes_for_screen_poly (float *polygon,
int n_vertices,
float *viewport,
const CoglMatrix *projection,
const CoglMatrix *inverse_project,
ClutterPlane *planes)
{
float Wc;
Vector4 *tmp_poly;
ClutterPlane *plane;
int i;
CoglVector3 b;
CoglVector3 c;
int count;
tmp_poly = g_alloca (sizeof (Vector4) * n_vertices * 2);
#define DEPTH -50
/* Determine W in clip-space (Wc) for a point (0, 0, DEPTH, 1)
*
* Note: the depth could be anything except 0.
*
* We will transform the polygon into clip coordinates using this
* depth and then into eye coordinates. Our clip planes will be
* defined by triangles that extend between points of the polygon at
* DEPTH and corresponding points of the same polygon at DEPTH * 2.
*
* NB: Wc defines the position of the clip planes in clip
* coordinates. Given a screen aligned cross section through the
* frustum; coordinates range from [-Wc,Wc] left to right on the
* x-axis and [Wc,-Wc] top to bottom on the y-axis.
*/
Wc = DEPTH * projection->wz + projection->ww;
#define CLIP_X(X) ((((float)X - viewport[0]) * (2.0 / viewport[2])) - 1) * Wc
#define CLIP_Y(Y) ((((float)Y - viewport[1]) * (2.0 / viewport[3])) - 1) * -Wc
for (i = 0; i < n_vertices; i++)
{
tmp_poly[i].x = CLIP_X (polygon[i * 2]);
tmp_poly[i].y = CLIP_Y (polygon[i * 2 + 1]);
tmp_poly[i].z = DEPTH;
tmp_poly[i].w = Wc;
}
Wc = DEPTH * 2 * projection->wz + projection->ww;
/* FIXME: technically we don't need to project all of the points
* twice, it would be enough project every other point since
* we can share points in this set to define the plane vectors. */
for (i = 0; i < n_vertices; i++)
{
tmp_poly[n_vertices + i].x = CLIP_X (polygon[i * 2]);
tmp_poly[n_vertices + i].y = CLIP_Y (polygon[i * 2 + 1]);
tmp_poly[n_vertices + i].z = DEPTH * 2;
tmp_poly[n_vertices + i].w = Wc;
}
#undef CLIP_X
#undef CLIP_Y
cogl_matrix_project_points (inverse_project,
4,
sizeof (Vector4),
tmp_poly,
sizeof (Vector4),
tmp_poly,
n_vertices * 2);
/* XXX: It's quite ugly that we end up with these casts between
* Vector4 types and CoglVector3s, it might be better if the
* cogl_vector APIs just took pointers to floats.
*/
count = n_vertices - 1;
for (i = 0; i < count; i++)
{
plane = &planes[i];
plane->v0 = *(CoglVector3 *)&tmp_poly[i];
b = *(CoglVector3 *)&tmp_poly[n_vertices + i];
c = *(CoglVector3 *)&tmp_poly[n_vertices + i + 1];
cogl_vector3_subtract (&b, &b, &plane->v0);
cogl_vector3_subtract (&c, &c, &plane->v0);
cogl_vector3_cross_product (&plane->n, &b, &c);
cogl_vector3_normalize (&plane->n);
}
plane = &planes[n_vertices - 1];
plane->v0 = *(CoglVector3 *)&tmp_poly[0];
b = *(CoglVector3 *)&tmp_poly[2 * n_vertices - 1];
c = *(CoglVector3 *)&tmp_poly[n_vertices];
cogl_vector3_subtract (&b, &b, &plane->v0);
cogl_vector3_subtract (&c, &c, &plane->v0);
cogl_vector3_cross_product (&plane->n, &b, &c);
cogl_vector3_normalize (&plane->n);
}
/* This provides a common point of entry for painting the scenegraph
* for picking or painting...
*
......@@ -392,10 +496,44 @@ void
_clutter_stage_do_paint (ClutterStage *stage, const ClutterGeometry *clip)
{
ClutterStagePrivate *priv = stage->priv;
priv->current_paint_clip = clip;
float clip_poly[8];
if (clip)
{
clip_poly[0] = clip->x;
clip_poly[1] = clip->y;
clip_poly[2] = clip->x + clip->width;
clip_poly[3] = clip->y;
clip_poly[4] = clip->x + clip->width;
clip_poly[5] = clip->y + clip->height;
clip_poly[6] = clip->x;
clip_poly[7] = clip->y + clip->height;
}
else
{
ClutterGeometry geom;
_clutter_stage_window_get_geometry (priv->impl, &geom);
clip_poly[0] = 0;
clip_poly[1] = 0;
clip_poly[2] = geom.width;
clip_poly[3] = 0;
clip_poly[4] = geom.width;
clip_poly[5] = geom.height;
clip_poly[6] = 0;
clip_poly[7] = geom.height;
}
_cogl_util_get_eye_planes_for_screen_poly (clip_poly,
4,
priv->viewport,
&priv->projection,
&priv->inverse_projection,
priv->current_clip_planes);
_clutter_stage_paint_volume_stack_free_all (stage);
clutter_actor_paint (CLUTTER_ACTOR (stage));
priv->current_paint_clip = NULL;
}
static void
......@@ -924,12 +1062,9 @@ clutter_stage_real_queue_redraw (ClutterActor *actor,
ClutterActor *leaf)
{
ClutterStage *stage = CLUTTER_STAGE (actor);
ClutterStagePrivate *priv = stage->priv;
ClutterStageWindow *stage_window;
ClutterGeometry stage_clip;
const ClutterPaintVolume *redraw_clip;
ClutterPaintVolume projected_clip;
CoglMatrix modelview;
ClutterPaintVolume *redraw_clip;
ClutterActorBox bounding_box;
if (CLUTTER_ACTOR_IN_DESTRUCTION (actor))
......@@ -948,9 +1083,8 @@ clutter_stage_real_queue_redraw (ClutterActor *actor,
return;
}
/* Convert the clip volume (which is in leaf actor coordinates) into stage
* coordinates and then into an axis aligned stage coordinates bounding
* box...
/* Convert the clip volume into stage coordinates and then into an
* axis aligned stage coordinates bounding box...
*/
if (!_clutter_actor_get_queue_redraw_clip (leaf))
......@@ -961,25 +1095,9 @@ clutter_stage_real_queue_redraw (ClutterActor *actor,
redraw_clip = _clutter_actor_get_queue_redraw_clip (leaf);
_clutter_paint_volume_copy_static (redraw_clip, &projected_clip);
/* NB: _clutter_actor_apply_modelview_transform_recursive will never
* include the transformation between stage coordinates and OpenGL
* window coordinates, we have to explicitly use the
* stage->apply_transform to get that... */
cogl_matrix_init_identity (&modelview);
_clutter_actor_apply_modelview_transform (CLUTTER_ACTOR (stage), &modelview);
_clutter_actor_apply_modelview_transform_recursive (leaf, NULL, &modelview);
_clutter_paint_volume_project (&projected_clip,
&modelview,
&priv->projection,
priv->viewport);
_clutter_paint_volume_get_bounding_box (&projected_clip, &bounding_box);
clutter_paint_volume_free (&projected_clip);
clutter_actor_box_clamp_to_pixel (&bounding_box);
_clutter_paint_volume_get_stage_paint_box (redraw_clip,
stage,
&bounding_box);
/* when converting to integer coordinates make sure we round the edges of the
* clip rectangle outwards... */
......@@ -3195,10 +3313,10 @@ _clutter_stage_paint_volume_stack_free_all (ClutterStage *stage)
/* The is an out-of-band paramater available while painting that
* can be used to cull actors. */
const ClutterGeometry *
const ClutterPlane *
_clutter_stage_get_clip (ClutterStage *stage)
{
return stage->priv->current_paint_clip;
return stage->priv->current_clip_planes;
}
/* When an actor queues a redraw we add it to a list on the stage that
......@@ -3220,6 +3338,9 @@ _clutter_stage_queue_actor_redraw (ClutterStage *stage,
{
ClutterStagePrivate *priv = stage->priv;
CLUTTER_NOTE (CLIPPING, "stage_queue_actor_redraw (actor=%s, clip=%p): ",
G_OBJECT_TYPE_NAME (actor), clip);
if (!priv->redraw_pending)
{
ClutterMasterClock *master_clock;
......@@ -3256,7 +3377,12 @@ _clutter_stage_queue_actor_redraw (ClutterStage *stage,
/* Ignore all requests to queue a redraw for an actor if a full
* (non-clipped) redraw of the actor has already been queued. */
if (!entry->has_clip)
return entry;
{
CLUTTER_NOTE (CLIPPING, "Bail from stage_queue_actor_redraw (%s): "
"Unclipped redraw of actor already queued",
G_OBJECT_CLASS_NAME (actor));
return entry;
}
/* If queuing a clipped redraw and a clipped redraw has
* previously been queued for this actor then combine the latest
......@@ -3278,7 +3404,7 @@ _clutter_stage_queue_actor_redraw (ClutterStage *stage,
if (clip)
{
entry->has_clip = TRUE;
_clutter_paint_volume_init_static (actor, &entry->clip);
_clutter_paint_volume_init_static (&entry->clip, actor);
_clutter_paint_volume_set_from_volume (&entry->clip, clip);
}
else
......
......@@ -425,6 +425,12 @@ clutter_stage_glx_redraw (ClutterStageWindow *stage_window)
if (use_clipped_redraw)
{
CLUTTER_NOTE (CLIPPING,
"Stage clip pushed: x=%d, y=%d, width=%d, height=%d\n",
stage_glx->bounding_redraw_clip.x,
stage_glx->bounding_redraw_clip.y,
stage_glx->bounding_redraw_clip.width,
stage_glx->bounding_redraw_clip.height);
cogl_clip_push_window_rectangle (stage_glx->bounding_redraw_clip.x,
stage_glx->bounding_redraw_clip.y,
stage_glx->bounding_redraw_clip.width,
......@@ -434,7 +440,10 @@ clutter_stage_glx_redraw (ClutterStageWindow *stage_window)
cogl_clip_pop ();
}
else
_clutter_stage_do_paint (stage_x11->wrapper, NULL);
{
CLUTTER_NOTE (CLIPPING, "Unclipped stage paint\n");
_clutter_stage_do_paint (stage_x11->wrapper, NULL);
}
if (may_use_clipped_redraw &&
G_UNLIKELY ((clutter_paint_debug_flags & CLUTTER_DEBUG_REDRAWS)))
......
......@@ -1046,7 +1046,7 @@ clutter_stage_x11_translate_event (ClutterEventTranslator *translator,
origin.y = expose->y;
origin.z = 0;
_clutter_paint_volume_init_static (CLUTTER_ACTOR (stage), &clip);
_clutter_paint_volume_init_static (&clip, CLUTTER_ACTOR (stage));
clutter_paint_volume_set_origin (&clip, &origin);
clutter_paint_volume_set_width (&clip, expose->width);
......
......@@ -372,7 +372,7 @@ clutter_x11_texture_pixmap_real_queue_damage_redraw (
scale_x = (allocation.x2 - allocation.x1) / priv->pixmap_width;
scale_y = (allocation.y2 - allocation.y1) / priv->pixmap_height;