1
0
Fork 0

Adds initial clipped redraw support to Clutter

A new (internal only currently) API, _clutter_actor_queue_clipped_redraw
can be used to queue a redraw along with a clip rectangle in actor
coordinates. This clip rectangle propagates up to the stage and clutter
backend which may optionally use the information to optimize stage
redraws. The GLX backend in particular may scissor the next redraw to
the clip rectangle and use GLX_MESA_copy_sub_buffer to present the stage
subregion.

The intention is that any actors that can naturally determine the bounds
of updates should queue clipped redraws to reduce the cost of updating
small regions of the screen.

Notes:
» If GLX_MESA_copy_sub_buffer isn't available then the GLX backend
  ignores any clip rectangles.

» queuing multiple clipped redraws will result in the bounding box of
  each clip rectangle being used.

» If a clipped redraw has a height > 300 pixels then it's promoted into
  a full stage redraw, so that the GPU doesn't end up blocking too long
  waiting for the vsync to reach the optimal position to avoid tearing.

  » Note: no empirical data was used to come up with this threshold so
    we may need to tune this.

» Currently only ClutterX11TexturePixmap makes use of this new API. This
  is done via a new "queue-damage-redraw" signal that is emitted when
  the pixmap is updated. The default handler queues a clipped redraw
  with the assumption that the pixmap is being painted as a rectangle
  covering the actors transformed allocation. If you subclass
  ClutterX11TexturePixmap and change how it's painted you now also
  need to override the signal handler and queue your own redraw.

  Technically this is a semantic break, but it's assumed that no one
  is currently doing this.

This still leaves a few unsolved issues with regards to optimizing sub
stage redraws that need to be addressed in further work so this can only
be considered a stepping stone a this point:

» Because we have no reliable way to determine if the painting of any
  given actor is being modified any optimizations implemented using
  _clutter_actor_queue_redraw_with_clip must be overridable by a
  subclass, and technically must be opt-in for existing classes to avoid
  a change in semantics. E.g. consider that a user connects to the paint
  signal for ClutterTexture and paints a circle instead of a rectangle.
  In this case any original logic to queue clipped redraws would be
  incorrect.

» Currently only the implementation of an actor has enough information
  with which to queue clipped redraws. E.g. It is not possible for
  generic code in clutter-actor.c to queue a clipped redraw when hiding
  an actor because actors have no way to report a "paint box". (remember
  actors can draw outside their allocation and actors with depth may
  also be projected outside of their allocation)

  » The current plan is to add a actor_class->get_paint_cuboid()
    virtual so actors can report a bounding cube for everything they
    would draw in their current state and use that to queue clipped
    redraws against the stage by projecting the paint cube into stage
    coordinates.

» Our heuristics for promoting clipped redraws into full redraws to
  avoid blocking the GPU while we wait for the vsync need improving:

  » vsync issues aren't relevant for redirected/composited applications
    so they should use different heuristics. In this case we instead
    need to trade off the cost of blitting when using glXCopySubBuffer
    vs promoting to a full redraw and flipping instead.
This commit is contained in:
Robert Bragg 2009-11-30 17:47:55 +00:00
parent 09d8460a5c
commit c0d5af5de5
14 changed files with 1110 additions and 334 deletions

View file

@ -302,13 +302,13 @@ struct _ClutterActorPrivate
guint needs_height_request : 1;
/* cached allocation is invalid (request has changed, probably) */
guint needs_allocation : 1;
guint queued_redraw : 1;
guint show_on_set_parent : 1;
guint has_clip : 1;
guint clip_to_allocation : 1;
guint enable_model_view_transform : 1;
guint enable_paint_unmapped : 1;
guint has_pointer : 1;
guint propagated_one_redraw : 1;
gfloat clip[4];
@ -353,6 +353,12 @@ struct _ClutterActorPrivate
ClutterTextDirection text_direction;
gint internal_child;
/* XXX: This is a workaround for not being able to break the ABI
* of the QUEUE_REDRAW signal. It's an out-of-band argument.
* See clutter_actor_queue_clipped_redraw() for details.
*/
const ClutterActorBox *oob_queue_redraw_clip;
};
enum
@ -1640,10 +1646,15 @@ static void
clutter_actor_queue_redraw_with_origin (ClutterActor *self,
ClutterActor *origin)
{
/* already queued since last paint() */
if (self->priv->queued_redraw)
/* no point in queuing a redraw on a destroyed actor */
if (CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_DESTRUCTION)
return;
/* NB: We can't bail out early here if the actor is hidden in case
* the actor bas been cloned. In this case the clone will need to
* receive the signal so it can queue its own redraw.
*/
/* calls klass->queue_redraw in default handler */
g_signal_emit (self, actor_signals[QUEUE_REDRAW], 0, origin);
}
@ -1654,18 +1665,13 @@ clutter_actor_real_queue_redraw (ClutterActor *self,
{
ClutterActor *parent;
/* already queued since last paint() */
if (self->priv->queued_redraw)
return;
/* no point in queuing a paint on a destroyed actor */
if (CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_DESTRUCTION)
return;
CLUTTER_NOTE (PAINT, "Redraw queued on '%s'",
clutter_actor_get_name (self) ? clutter_actor_get_name (self)
: G_OBJECT_TYPE_NAME (self));
self->priv->queued_redraw = TRUE;
/* no point in queuing a redraw on a destroyed actor */
if (CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_IN_DESTRUCTION)
return;
/* If the actor isn't visible, we still had to emit the signal
* to allow for a ClutterClone, but the appearance of the parent
@ -1674,6 +1680,22 @@ clutter_actor_real_queue_redraw (ClutterActor *self,
if (!CLUTTER_ACTOR_IS_VISIBLE (self))
return;
/* Although we could determine here that a full stage redraw
* has already been queued and immediately bail out, we actually
* guarantee that we will propagate a queue-redraw signal to our
* parent at least once so that it's possible to implement a
* container that tracks which of its children have queued a
* redraw.
*/
if (self->priv->propagated_one_redraw)
{
ClutterActor *stage = clutter_actor_get_stage (self);
if (stage &&
_clutter_stage_has_full_redraw_queued (CLUTTER_STAGE (stage)))
return;
}
self->priv->propagated_one_redraw = TRUE;
/* notify parents, if they are all visible eventually we'll
* queue redraw on the stage, which queues the redraw idle.
*/
@ -1730,29 +1752,8 @@ full_vertex_to_units (const full_vertex_t *f,
u->z = f->z;
}
/* Transforms a vertex using the passed matrix; vertex is
* an in-out parameter
*/
static void
mtx_transform (const CoglMatrix *matrix,
full_vertex_t *vertex)
{
cogl_matrix_transform_point (matrix,
&vertex->x,
&vertex->y,
&vertex->z,
&vertex->w);
}
/* Help macros to scale from OpenGL <-1,1> coordinates system to our
* X-window based <0,window-size> coordinates
*/
#define MTX_GL_SCALE_X(x,w,v1,v2) ((((((x) / (w)) + 1.0) / 2) * (v1)) + (v2))
#define MTX_GL_SCALE_Y(y,w,v1,v2) ((v1) - (((((y) / (w)) + 1.0) / 2) * (v1)) + (v2))
#define MTX_GL_SCALE_Z(z,w,v1,v2) (MTX_GL_SCALE_X ((z), (w), (v1), (v2)))
/* transforms a 4-tuple of coordinates using @matrix and
* places the result into a fixed @vertex
* places the result into a @vertex
*/
static inline void
full_vertex_transform (const CoglMatrix *matrix,
@ -1762,18 +1763,21 @@ full_vertex_transform (const CoglMatrix *matrix,
gfloat w,
full_vertex_t *vertex)
{
full_vertex_t tmp = { 0, };
cogl_matrix_transform_point (matrix, &x, &y, &z, &w);
tmp.x = x;
tmp.y = y;
tmp.z = z;
tmp.w = w;
mtx_transform (matrix, &tmp);
*vertex = tmp;
vertex->x = x;
vertex->y = y;
vertex->z = z;
vertex->w = w;
}
/* Help macros to scale from OpenGL <-1,1> coordinates system to our
* X-window based <0,window-size> coordinates
*/
#define MTX_GL_SCALE_X(x,w,v1,v2) ((((((x) / (w)) + 1.0) / 2) * (v1)) + (v2))
#define MTX_GL_SCALE_Y(y,w,v1,v2) ((v1) - (((((y) / (w)) + 1.0) / 2) * (v1)) + (v2))
#define MTX_GL_SCALE_Z(z,w,v1,v2) (MTX_GL_SCALE_X ((z), (w), (v1), (v2)))
/* scales a fixed @vertex using @matrix and @viewport, and
* transforms the result into a ClutterVertex, filling @vertex_p
*/
@ -1784,11 +1788,15 @@ full_vertex_scale (const CoglMatrix *matrix,
ClutterVertex *vertex_p)
{
gfloat v_x, v_y, v_width, v_height;
full_vertex_t tmp = { 0, };
full_vertex_t tmp;
tmp = *vertex;
mtx_transform (matrix, &tmp);
cogl_matrix_transform_point (matrix,
&tmp.x,
&tmp.y,
&tmp.z,
&tmp.w);
v_x = viewport[0];
v_y = viewport[1];
@ -1817,7 +1825,7 @@ clutter_actor_transform_point_relative (ClutterActor *actor,
gfloat *z,
gfloat *w)
{
full_vertex_t vertex = { 0, };
full_vertex_t vertex;
CoglMatrix matrix;
vertex.x = (x != NULL) ? *x : 0;
@ -1830,7 +1838,12 @@ clutter_actor_transform_point_relative (ClutterActor *actor,
_clutter_actor_apply_modelview_transform_recursive (actor, ancestor);
cogl_get_modelview_matrix (&matrix);
mtx_transform (&matrix, &vertex);
cogl_matrix_transform_point (&matrix,
&vertex.x,
&vertex.y,
&vertex.z,
&vertex.w);
cogl_pop_matrix();
@ -1858,7 +1871,7 @@ clutter_actor_transform_point (ClutterActor *actor,
gfloat *z,
gfloat *w)
{
full_vertex_t vertex = { 0, };
full_vertex_t vertex;
CoglMatrix matrix;
vertex.x = (x != NULL) ? *x : 0;
@ -1871,7 +1884,12 @@ clutter_actor_transform_point (ClutterActor *actor,
_clutter_actor_apply_modelview_transform_recursive (actor, NULL);
cogl_get_modelview_matrix (&matrix);
mtx_transform (&matrix, &vertex);
cogl_matrix_transform_point (&matrix,
&vertex.x,
&vertex.y,
&vertex.z,
&vertex.w);
cogl_pop_matrix();
@ -1986,7 +2004,12 @@ clutter_actor_apply_transform_to_point (ClutterActor *self,
cogl_get_viewport (v);
/* Now, transform it again with the projection matrix */
mtx_transform (&matrix_p, &tmp);
cogl_matrix_transform_point (&matrix_p,
&tmp.x,
&tmp.y,
&tmp.z,
&tmp.w);
/* Finaly translate from OpenGL coords to window coords */
vertex->x = MTX_GL_SCALE_X (tmp.x, tmp.w, v[2], v[0]);
@ -2024,29 +2047,19 @@ clutter_actor_transform_vertices_relative (ClutterActor *self,
cogl_pop_matrix();
}
/* Recursively tranform supplied box with the tranform for the current
* actor and all its ancestors (like clutter_actor_transform_point()
* but for all the vertices in one go) and project it into screen
* coordinates
/* _clutter_actor_ensure_stage_current
*
* Ensures that the actors corresponding stage is made current so we
* have a valid viewport, projection matrix and modelview matrix stack.
*/
static void
clutter_actor_transform_and_project_box (ClutterActor *self,
const ClutterActorBox *box,
ClutterVertex verts[])
_clutter_actor_ensure_stage_current (ClutterActor *self)
{
ClutterActor *stage;
CoglMatrix mtx;
CoglMatrix mtx_p;
gfloat v[4];
gfloat width, height;
full_vertex_t vertices[4];
width = box->x2 - box->x1;
height = box->y2 - box->y1;
/* We essentially have to dupe some code from clutter_redraw() here
* to make sure GL Matrices etc are initialised if we're called and we
* havn't yet rendered anything.
* haven't yet rendered anything.
*
* Simply duping code for now in wait for Cogl cleanup that can hopefully
* address this in a nicer way.
@ -2061,22 +2074,81 @@ clutter_actor_transform_and_project_box (ClutterActor *self,
clutter_stage_ensure_current (CLUTTER_STAGE (stage));
_clutter_stage_maybe_setup_viewport (CLUTTER_STAGE (stage));
}
cogl_push_matrix();
/* _clutter_actor_get_relative_modelview:
*
* Retrives the modelview transformation relative to some ancestor actor, or
* the stage if NULL is given for the ancestor.
*
* It assumes you currently have an empty matrix stack.
*/
/* FIXME: We should be caching the stage relative modelview along with the
* actor itself */
/* TODO: Replace all other occurrences of this code pattern in clutter-actor.c:
* cogl_push_matrix();
* _clutter_actor_apply_modelview_transform_recursive (self, ancestor)
* cogl_get_modelview_matrix()
* cogl_pop_matrix();
* with a call to this function:
*/
void
_clutter_actor_get_relative_modelview (ClutterActor *self,
ClutterActor *ancestor,
CoglMatrix *matrix)
{
_clutter_actor_ensure_stage_current (self);
_clutter_actor_apply_modelview_transform_recursive (self, NULL);
/* FIXME: init_identity instead of assuming we have an empty stack! */
cogl_get_modelview_matrix (&mtx);
cogl_push_matrix ();
full_vertex_transform (&mtx, 0, 0, 0, 1.0, &vertices[0]);
full_vertex_transform (&mtx, width, 0, 0, 1.0, &vertices[1]);
full_vertex_transform (&mtx, 0, height, 0, 1.0, &vertices[2]);
full_vertex_transform (&mtx, width, height, 0, 1.0, &vertices[3]);
_clutter_actor_apply_modelview_transform_recursive (self, ancestor);
cogl_pop_matrix();
cogl_get_modelview_matrix (matrix);
cogl_get_projection_matrix (&mtx_p);
cogl_get_viewport (v);
cogl_pop_matrix ();
}
/* _clutter_actor_get_projection_and_viewport
*
* Retrieves the projection matrix and viewport for the actors corresponding
* stage.
*/
void
_clutter_actor_get_projection_and_viewport (ClutterActor *self,
CoglMatrix *matrix,
float *viewport)
{
_clutter_actor_ensure_stage_current (self);
cogl_get_projection_matrix (matrix);
cogl_get_viewport (viewport);
}
/* Recursively transform supplied box with the transform for the current
* actor and all its ancestors (like clutter_actor_transform_point()
* but for all the vertices in one go) and project it into screen
* coordinates
*/
void
_clutter_actor_transform_and_project_box (ClutterActor *self,
const ClutterActorBox *box,
ClutterVertex verts[])
{
CoglMatrix mtx;
CoglMatrix mtx_p;
float v[4];
full_vertex_t vertices[4];
_clutter_actor_get_relative_modelview (self, NULL, &mtx);
full_vertex_transform (&mtx, box->x1, box->y1, 0, 1.0, &vertices[0]);
full_vertex_transform (&mtx, box->x2, box->y1, 0, 1.0, &vertices[1]);
full_vertex_transform (&mtx, box->x1, box->y2, 0, 1.0, &vertices[2]);
full_vertex_transform (&mtx, box->x2, box->y2, 0, 1.0, &vertices[3]);
_clutter_actor_get_projection_and_viewport (self, &mtx_p, v);
full_vertex_scale (&mtx_p, &vertices[0], v, &verts[0]);
full_vertex_scale (&mtx_p, &vertices[1], v, &verts[1]);
@ -2201,6 +2273,7 @@ clutter_actor_get_abs_allocation_vertices (ClutterActor *self,
ClutterVertex verts[])
{
ClutterActorPrivate *priv;
ClutterActorBox actor_space_allocation;
g_return_if_fail (CLUTTER_IS_ACTOR (self));
@ -2208,7 +2281,7 @@ clutter_actor_get_abs_allocation_vertices (ClutterActor *self,
/* if the actor needs to be allocated we force a relayout, so that
* the actor allocation box will be valid for
* clutter_actor_transform_and_project_box()
* _clutter_actor_transform_and_project_box()
*/
if (priv->needs_allocation)
{
@ -2223,9 +2296,17 @@ clutter_actor_get_abs_allocation_vertices (ClutterActor *self,
_clutter_stage_maybe_relayout (stage);
}
clutter_actor_transform_and_project_box (self,
&self->priv->allocation,
verts);
/* NB: _clutter_actor_transform_and_project_box expects a box in the actor's
* own coordinate space... */
actor_space_allocation.x1 = 0;
actor_space_allocation.y1 = 0;
actor_space_allocation.x2 =
self->priv->allocation.x2 - self->priv->allocation.x1;
actor_space_allocation.y2 =
self->priv->allocation.y2 - self->priv->allocation.y1;
_clutter_actor_transform_and_project_box (self,
&actor_space_allocation,
verts);
}
static void
@ -2393,7 +2474,7 @@ clutter_actor_paint (ClutterActor *self)
clone's opacity instead */
(priv->opacity_parent ? priv->opacity_parent->priv : priv)->opacity == 0)
{
priv->queued_redraw = FALSE;
priv->propagated_one_redraw = FALSE;
return;
}
@ -2450,7 +2531,7 @@ clutter_actor_paint (ClutterActor *self)
clutter_actor_shader_pre_paint (self, FALSE);
self->priv->queued_redraw = FALSE;
self->priv->propagated_one_redraw = FALSE;
g_signal_emit (self, actor_signals[PAINT], 0);
clutter_actor_shader_post_paint (self);
@ -4546,6 +4627,130 @@ clutter_actor_queue_redraw (ClutterActor *self)
clutter_actor_queue_redraw_with_origin (self, self);
}
static void
_clutter_actor_get_allocation_clip (ClutterActor *self,
ClutterActorBox *clip)
{
ClutterActorBox allocation;
/* XXX: we don't care if we get an out of date allocation here
* because clutter_actor_queue_redraw_with_origin knows to ignore
* the clip if the actor's allocation is invalid.
*
* This is noted because clutter_actor_get_allocation_box does some
* unnecessary work to support buggy code with a comment suggesting
* that it could be changed later which would be good for this use
* case!
*/
clutter_actor_get_allocation_box (self, &allocation);
/* NB: clutter_actor_queue_clipped_redraw expects a box in the
* actor's own coordinate space but the allocation is in parent
* coordinates */
clip->x1 = 0;
clip->y1 = 0;
clip->x2 = allocation.x2 - allocation.x1;
clip->y2 = allocation.y2 - allocation.y1;
}
/*
* clutter_actor_queue_redraw_with_clip:
* @self: A #ClutterActor
* @flags: A mask of #ClutterRedrawFlags controlling the behaviour of
* this queue redraw.
* @clip: A #ClutterActorBox describing the bounds of what needs to be
* redrawn or NULL if you are just using a @flag to state your
* desired clipping.
*
* Queues up a clipped redraw of an actor and any children. The redraw
* occurs once the main loop becomes idle (after the current batch of
* events has been processed, roughly).
*
* If the CLUTTER_REDRAW_CLIPPED_TO_BOX @flag is used, the clip box is
* specified in actor coordinates and tells Clutter that only content
* within this box has been changed so Clutter can optionally optimize
* the redraw.
*
* If you are queuing a clipped redraw it is assumed that the actor is
* flat, and once the clip rectangle is projected into stage
* coordinates it will cover the area of the stage that needs to be
* redrawn. This is not possible to determine for 3D actors since the
* projection of such actors may escape the clip rectangle.
*
* If the CLUTTER_REDRAW_CLIPPED_TO_ALLOCATION @flag is used, @clip
* should be NULL and this tells Clutter to use the actors current
* allocation as a clip box. As above this flag can only be used for
* 2D actors.
*
* Applications rarely need to call this, as redraws are handled
* automatically by modification functions.
*
* This function will not do anything if @self is not visible, or if
* the actor is inside an invisible part of the scenegraph.
*
* Also be aware that painting is a NOP for actors with an opacity of
* 0
*/
void
_clutter_actor_queue_redraw_with_clip (ClutterActor *self,
ClutterRedrawFlags flags,
ClutterActorBox *clip)
{
ClutterActorBox allocation_clip;
/* If the actor doesn't have a valid allocation then we will queue a
* full stage redraw */
if (self->priv->needs_allocation)
{
clutter_actor_queue_redraw (self);
return;
}
/* SYNC_MATRICES is a flag for the stage, which means that we just
* got resized and we need to re-setup the viewport.
* IN_RESIZE is used on X11 where the resize is asynchronous, so we
* don't ask for a viewport change before we have the final size.
*
* If either of these flags are set then we won't be able to
* transform the given clip rectangle into valid stage coordinates,
* so we instead queue a full stage redraw.
*
* (Note: to some extent this is redundant because these flags
* should imply a full stage redraw will be queued, but we at least
* avoid needlessly traversing the actors ancestors to derive an
* incorrect modelview matrix.)
*/
if ((CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_ACTOR_SYNC_MATRICES) &&
!(CLUTTER_PRIVATE_FLAGS (self) & CLUTTER_STAGE_IN_RESIZE))
{
clutter_actor_queue_redraw (self);
return;
}
if (flags & CLUTTER_REDRAW_CLIPPED_TO_ALLOCATION)
{
_clutter_actor_get_allocation_clip (self, &allocation_clip);
clip = &allocation_clip;
}
/* XXX: Ideally the redraw signal would take a clip rectangle
* argument, but that would be an ABI break. Until we can break the
* ABI we pass the argument out-of-band via an actor->priv member...
*/
_clutter_actor_set_queue_redraw_clip (self, clip);
clutter_actor_queue_redraw_with_origin (self, self);
/* Just in case anyone is manually firing redraw signals without
* using the public queue_redraw() API we are careful to ensure that
* our out-of-band clip member is cleared before returning...
*
* Note: A NULL clip denotes a full-stage, un-clipped redraw
*/
_clutter_actor_set_queue_redraw_clip (self, NULL);
}
/**
* clutter_actor_queue_relayout:
* @self: A #ClutterActor
@ -5692,18 +5897,23 @@ clutter_actor_get_transformed_size (ClutterActor *self,
gfloat natural_width, natural_height;
ClutterActorBox box;
/* make a fake allocation to transform */
clutter_actor_get_position (self, &box.x1, &box.y1);
/* Make a fake allocation to transform.
*
* NB: _clutter_actor_transform_and_project_box expects a box in
* the actor's coordinate space... */
box.x1 = 0;
box.y1 = 0;
natural_width = natural_height = 0;
clutter_actor_get_preferred_size (self, NULL, NULL,
&natural_width,
&natural_height);
box.x2 = box.x1 + natural_width;
box.y2 = box.y1 + natural_height;
box.x2 = natural_width;
box.y2 = natural_height;
clutter_actor_transform_and_project_box (self, &box, v);
_clutter_actor_transform_and_project_box (self, &box, v);
}
else
clutter_actor_get_abs_allocation_vertices (self, v);
@ -9840,3 +10050,21 @@ clutter_actor_has_pointer (ClutterActor *self)
return self->priv->has_pointer;
}
/* XXX: This is a workaround for not being able to break the ABI of
* the QUEUE_REDRAW signal. It is an out-of-band argument. See
* clutter_actor_queue_clipped_redraw() for details.
*/
const ClutterActorBox *
_clutter_actor_get_queue_redraw_clip (ClutterActor *self)
{
return self->priv->oob_queue_redraw_clip;
}
void
_clutter_actor_set_queue_redraw_clip (ClutterActor *self,
const ClutterActorBox *clip)
{
self->priv->oob_queue_redraw_clip = clip;
}

View file

@ -134,6 +134,25 @@ typedef enum
CLUTTER_ABSOLUTE_ORIGIN_CHANGED = 1 << 1
} ClutterAllocationFlags;
/**
* ClutterRedrawFlags:
* @CLUTTER_REDRAW_CLIPPED_TO_BOX: Tells clutter the redraw is clipped
* to a given clip box in actor coordinates.
* @CLUTTER_REDRAW_CLIPPED_TO_ALLOCATION: Tells clutter the maximum
* extents of what needs to be redrawn lies within the actors
* current allocation.
*
* Flags passed to the clutter_actor_queue_redraw_with_clip ()
* function
*
* Since: 1.2
*/
typedef enum
{
CLUTTER_REDRAW_CLIPPED_TO_BOX = 0,
CLUTTER_REDRAW_CLIPPED_TO_ALLOCATION = 1 << 1
} ClutterRedrawFlags;
/**
* ClutterActor:
* @flags: #ClutterActorFlags
@ -303,6 +322,7 @@ void clutter_actor_map (ClutterActor
void clutter_actor_unmap (ClutterActor *self);
void clutter_actor_paint (ClutterActor *self);
void clutter_actor_queue_redraw (ClutterActor *self);
void clutter_actor_queue_relayout (ClutterActor *self);
void clutter_actor_destroy (ClutterActor *self);

View file

@ -22,7 +22,7 @@ typedef enum {
CLUTTER_DEBUG_SHADER = 1 << 12,
CLUTTER_DEBUG_MULTISTAGE = 1 << 13,
CLUTTER_DEBUG_ANIMATION = 1 << 14,
CLUTTER_DEBUG_LAYOUT = 1 << 15,
CLUTTER_DEBUG_LAYOUT = 1 << 15
} ClutterDebugFlag;
typedef enum {
@ -31,7 +31,9 @@ typedef enum {
} ClutterPickDebugFlag;
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_REDRAWS = 1 << 2
} ClutterDrawDebugFlag;
#ifdef CLUTTER_ENABLE_DEBUG

View file

@ -173,7 +173,9 @@ static const GDebugKey clutter_pick_debug_keys[] = {
};
static const GDebugKey clutter_paint_debug_keys[] = {
{ "disable-swap-events", CLUTTER_DEBUG_DISABLE_SWAP_EVENTS }
{ "disable-swap-events", CLUTTER_DEBUG_DISABLE_SWAP_EVENTS },
{ "disable-clipped-redraws", CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS },
{ "redraws", CLUTTER_DEBUG_REDRAWS }
};
#ifdef CLUTTER_ENABLE_PROFILE

View file

@ -125,11 +125,11 @@ struct _ClutterStageManager
struct _ClutterMainContext
{
ClutterBackend *backend; /* holds a pointer to the windowing
ClutterBackend *backend; /* holds a pointer to the windowing
system backend */
GQueue *events_queue; /* the main event queue */
guint is_initialized : 1;
guint is_initialized : 1;
guint motion_events_per_actor : 1;/* set for enter/leave events */
guint defer_display_setup : 1;
guint options_parsed : 1;
@ -140,8 +140,8 @@ struct _ClutterMainContext
gint num_reactives; /* Num of reactive actors */
ClutterIDPool *id_pool; /* mapping between reused integer ids
* and actors
ClutterIDPool *id_pool; /* mapping between reused integer ids
* and actors
*/
guint frame_rate; /* Default FPS */
@ -149,8 +149,8 @@ struct _ClutterMainContext
* (or NULL if there is no pointer grab
*/
ClutterActor *keyboard_grab_actor; /* The actor having the pointer grab
* (or NULL if there is no pointer
* grab)
* (or NULL if there is no pointer
* grab)
*/
GSList *shaders; /* stack of overridden shaders */
@ -229,13 +229,17 @@ void _clutter_stage_maybe_relayout (ClutterActor *sta
gboolean _clutter_stage_needs_update (ClutterStage *stage);
gboolean _clutter_stage_do_update (ClutterStage *stage);
void _clutter_stage_queue_event (ClutterStage *stage,
ClutterEvent *event);
gboolean _clutter_stage_has_queued_events (ClutterStage *stage);
void _clutter_stage_process_queued_events (ClutterStage *stage);
void _clutter_stage_update_input_devices (ClutterStage *stage);
int _clutter_stage_get_pending_swaps (ClutterStage *stage);
void _clutter_stage_queue_event (ClutterStage *stage,
ClutterEvent *event);
gboolean _clutter_stage_has_queued_events (ClutterStage *stage);
void _clutter_stage_process_queued_events (ClutterStage *stage);
void _clutter_stage_update_input_devices (ClutterStage *stage);
int _clutter_stage_get_pending_swaps (ClutterStage *stage);
gboolean _clutter_stage_has_full_redraw_queued (ClutterStage *stage);
/* vfuncs implemented by backend */
GType _clutter_backend_impl_get_type (void);
@ -312,6 +316,17 @@ void _clutter_actor_set_enable_paint_unmapped (ClutterActor *self,
void _clutter_actor_set_has_pointer (ClutterActor *self,
gboolean has_pointer);
void _clutter_actor_transform_and_project_box (ClutterActor *self,
const ClutterActorBox *box,
ClutterVertex verts[]);
void _clutter_actor_queue_redraw_with_clip (ClutterActor *self,
ClutterRedrawFlags flags,
ClutterActorBox *clip);
const ClutterActorBox *_clutter_actor_get_queue_redraw_clip (ClutterActor *self);
void _clutter_actor_set_queue_redraw_clip (ClutterActor *self,
const ClutterActorBox *clip);
void _clutter_run_repaint_functions (void);
gint32 _clutter_backend_get_units_serial (ClutterBackend *backend);

View file

@ -121,3 +121,32 @@ _clutter_stage_window_get_pending_swaps (ClutterStageWindow *window)
return CLUTTER_STAGE_WINDOW_GET_IFACE (window)->get_pending_swaps (window);
}
void
_clutter_stage_window_add_redraw_clip (ClutterStageWindow *window,
ClutterGeometry *stage_clip)
{
ClutterStageWindowIface *iface = CLUTTER_STAGE_WINDOW_GET_IFACE (window);
if (iface->add_redraw_clip)
iface->add_redraw_clip (window, stage_clip);
}
gboolean
_clutter_stage_window_has_redraw_clips (ClutterStageWindow *window)
{
ClutterStageWindowIface *iface = CLUTTER_STAGE_WINDOW_GET_IFACE (window);
if (iface->has_redraw_clips)
return iface->has_redraw_clips (window);
else
return FALSE;
}
gboolean
_clutter_stage_window_ignoring_redraw_clips (ClutterStageWindow *window)
{
ClutterStageWindowIface *iface = CLUTTER_STAGE_WINDOW_GET_IFACE (window);
if (iface->ignoring_redraw_clips)
return iface->ignoring_redraw_clips (window);
else
return TRUE;
}

View file

@ -17,31 +17,36 @@ struct _ClutterStageWindowIface
{
GTypeInterface parent_iface;
ClutterActor *(* get_wrapper) (ClutterStageWindow *stage_window);
ClutterActor *(* get_wrapper) (ClutterStageWindow *stage_window);
void (* set_title) (ClutterStageWindow *stage_window,
const gchar *title);
void (* set_fullscreen) (ClutterStageWindow *stage_window,
gboolean is_fullscreen);
void (* set_cursor_visible) (ClutterStageWindow *stage_window,
gboolean cursor_visible);
void (* set_user_resizable) (ClutterStageWindow *stage_window,
gboolean is_resizable);
void (* set_title) (ClutterStageWindow *stage_window,
const gchar *title);
void (* set_fullscreen) (ClutterStageWindow *stage_window,
gboolean is_fullscreen);
void (* set_cursor_visible) (ClutterStageWindow *stage_window,
gboolean cursor_visible);
void (* set_user_resizable) (ClutterStageWindow *stage_window,
gboolean is_resizable);
gboolean (* realize) (ClutterStageWindow *stage_window);
void (* unrealize) (ClutterStageWindow *stage_window);
gboolean (* realize) (ClutterStageWindow *stage_window);
void (* unrealize) (ClutterStageWindow *stage_window);
void (* show) (ClutterStageWindow *stage_window,
gboolean do_raise);
void (* hide) (ClutterStageWindow *stage_window);
void (* show) (ClutterStageWindow *stage_window,
gboolean do_raise);
void (* hide) (ClutterStageWindow *stage_window);
void (* resize) (ClutterStageWindow *stage_window,
gint width,
gint height);
void (* get_geometry) (ClutterStageWindow *stage_window,
ClutterGeometry *geometry);
void (* resize) (ClutterStageWindow *stage_window,
gint width,
gint height);
void (* get_geometry) (ClutterStageWindow *stage_window,
ClutterGeometry *geometry);
int (* get_pending_swaps) (ClutterStageWindow *stage_window);
int (* get_pending_swaps) (ClutterStageWindow *stage_window);
void (* add_redraw_clip) (ClutterStageWindow *stage_window,
ClutterGeometry *stage_rectangle);
gboolean (* has_redraw_clips) (ClutterStageWindow *stage_window);
gboolean (* ignoring_redraw_clips) (ClutterStageWindow *stage_window);
};
GType clutter_stage_window_get_type (void) G_GNUC_CONST;
@ -57,19 +62,24 @@ void _clutter_stage_window_set_cursor_visible (ClutterStageWindow *wind
void _clutter_stage_window_set_user_resizable (ClutterStageWindow *window,
gboolean is_resizable);
gboolean _clutter_stage_window_realize (ClutterStageWindow *window);
void _clutter_stage_window_unrealize (ClutterStageWindow *window);
gboolean _clutter_stage_window_realize (ClutterStageWindow *window);
void _clutter_stage_window_unrealize (ClutterStageWindow *window);
void _clutter_stage_window_show (ClutterStageWindow *window,
gboolean do_raise);
void _clutter_stage_window_hide (ClutterStageWindow *window);
void _clutter_stage_window_show (ClutterStageWindow *window,
gboolean do_raise);
void _clutter_stage_window_hide (ClutterStageWindow *window);
void _clutter_stage_window_resize (ClutterStageWindow *window,
gint width,
gint height);
void _clutter_stage_window_get_geometry (ClutterStageWindow *window,
ClutterGeometry *geometry);
int _clutter_stage_window_get_pending_swaps (ClutterStageWindow *window);
void _clutter_stage_window_resize (ClutterStageWindow *window,
gint width,
gint height);
void _clutter_stage_window_get_geometry (ClutterStageWindow *window,
ClutterGeometry *geometry);
int _clutter_stage_window_get_pending_swaps (ClutterStageWindow *window);
void _clutter_stage_window_add_redraw_clip (ClutterStageWindow *window,
ClutterGeometry *stage_clip);
gboolean _clutter_stage_window_has_redraw_clips (ClutterStageWindow *window);
gboolean _clutter_stage_window_ignoring_redraw_clips (ClutterStageWindow *window);
G_END_DECLS

View file

@ -670,6 +670,12 @@ clutter_stage_real_queue_redraw (ClutterActor *actor,
{
ClutterStage *stage = CLUTTER_STAGE (actor);
ClutterStagePrivate *priv = stage->priv;
ClutterStageWindow *stage_window;
ClutterGeometry stage_clip;
const ClutterActorBox *clip;
ClutterActorBox bounds;
ClutterVertex v[4];
int i;
CLUTTER_NOTE (PAINT, "Redraw request number %lu",
CLUTTER_CONTEXT ()->redraw_count + 1);
@ -685,6 +691,64 @@ clutter_stage_real_queue_redraw (ClutterActor *actor,
}
else
CLUTTER_CONTEXT ()->redraw_count += 1;
/* If the backend can't do anything with redraw clips (e.g. it already knows
* it needs to redraw everything anyway) then don't spend time transforming
* any clip regions into stage coordinates... */
stage_window = _clutter_stage_get_window (stage);
if (_clutter_stage_window_ignoring_redraw_clips (stage_window))
return;
/* Convert the clip rectangle (which is in leaf actor coordinates) into stage
* coordinates and then into an axis aligned stage coordinates bounding
* box...
*/
clip = _clutter_actor_get_queue_redraw_clip (leaf);
if (!clip)
{
_clutter_stage_window_add_redraw_clip (stage_window, NULL);
return;
}
_clutter_actor_transform_and_project_box (leaf, clip, v);
bounds.x1 = v[0].x; bounds.y1 = v[0].y;
bounds.x2 = v[0].x; bounds.y2 = v[0].y;
for (i = 0; i < 4; i++)
{
if (v[i].x < bounds.x1)
bounds.x1 = v[i].x;
else if (v[i].x > bounds.x2)
bounds.x2 = v[i].x;
if (v[i].y < bounds.y1)
bounds.y1 = v[i].y;
else if (v[i].y > bounds.y2)
bounds.y2 = v[i].y;
}
/* when converting to integer coordinates make sure we round the edges of the
* clip rectangle outwards... */
stage_clip.x = bounds.x1;
stage_clip.y = bounds.y1;
stage_clip.width = ceilf (bounds.x2) - stage_clip.x;
stage_clip.height = ceilf (bounds.y2) - stage_clip.y;
_clutter_stage_window_add_redraw_clip (stage_window, &stage_clip);
}
gboolean
_clutter_stage_has_full_redraw_queued (ClutterStage *stage)
{
ClutterStageWindow *stage_window = _clutter_stage_get_window (stage);
if (stage->priv->redraw_pending &&
!_clutter_stage_window_has_redraw_clips (stage_window))
return TRUE;
else
return FALSE;
}
static gboolean

View file

@ -28,14 +28,10 @@
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <fcntl.h>
#include <glib/gi18n-lib.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#include <GL/glx.h>
#include <GL/glxext.h>
#include <GL/gl.h>
@ -62,48 +58,6 @@ static ClutterBackendGLX *backend_singleton = NULL;
static gchar *clutter_vblank_name = NULL;
#ifdef __linux__
#define DRM_VBLANK_RELATIVE 0x1;
struct drm_wait_vblank_request {
int type;
unsigned int sequence;
unsigned long signal;
};
struct drm_wait_vblank_reply {
int type;
unsigned int sequence;
long tval_sec;
long tval_usec;
};
typedef union drm_wait_vblank {
struct drm_wait_vblank_request request;
struct drm_wait_vblank_reply reply;
} drm_wait_vblank_t;
#define DRM_IOCTL_BASE 'd'
#define DRM_IOWR(nr,type) _IOWR(DRM_IOCTL_BASE,nr,type)
#define DRM_IOCTL_WAIT_VBLANK DRM_IOWR(0x3a, drm_wait_vblank_t)
static int drm_wait_vblank(int fd, drm_wait_vblank_t *vbl)
{
int ret, rc;
do
{
ret = ioctl(fd, DRM_IOCTL_WAIT_VBLANK, vbl);
vbl->request.type &= ~DRM_VBLANK_RELATIVE;
rc = errno;
}
while (ret && rc == EINTR);
return rc;
}
#endif
G_CONST_RETURN gchar*
clutter_backend_glx_get_vblank_method (void)
{
@ -294,7 +248,7 @@ clutter_backend_glx_get_features (ClutterBackend *backend)
if (check_vblank_env ("none"))
{
CLUTTER_NOTE (BACKEND, "vblank sync: disabled at user request");
goto done;
goto vblank_setup_done;
}
if (g_getenv ("__GL_SYNC_TO_VBLANK") != NULL)
@ -303,7 +257,7 @@ clutter_backend_glx_get_features (ClutterBackend *backend)
flags |= CLUTTER_FEATURE_SYNC_TO_VBLANK;
CLUTTER_NOTE (BACKEND, "Using __GL_SYNC_TO_VBLANK hint");
goto done;
goto vblank_setup_done;
}
/* We try two GL vblank syncing mechanisms.
@ -346,7 +300,7 @@ clutter_backend_glx_get_features (ClutterBackend *backend)
}
#endif /* GLX_INTEL_swap_event */
goto done;
goto vblank_setup_done;
}
CLUTTER_NOTE (BACKEND, "glXSwapIntervalSGI vblank setup failed");
@ -372,7 +326,7 @@ clutter_backend_glx_get_features (ClutterBackend *backend)
backend_glx->vblank_type = CLUTTER_VBLANK_GLX;
flags |= CLUTTER_FEATURE_SYNC_TO_VBLANK;
goto done;
goto vblank_setup_done;
}
CLUTTER_NOTE (BACKEND, "glXGetVideoSyncSGI vblank setup failed");
@ -394,7 +348,7 @@ clutter_backend_glx_get_features (ClutterBackend *backend)
backend_glx->vblank_type = CLUTTER_VBLANK_DRI;
flags |= CLUTTER_FEATURE_SYNC_TO_VBLANK;
goto done;
goto vblank_setup_done;
}
CLUTTER_NOTE (BACKEND, "DRI vblank setup failed");
@ -403,7 +357,14 @@ clutter_backend_glx_get_features (ClutterBackend *backend)
CLUTTER_NOTE (BACKEND, "no use-able vblank mechanism found");
done:
vblank_setup_done:
if (_cogl_check_extension ("GLX_MESA_copy_sub_buffer", glx_extensions))
{
backend_glx->copy_sub_buffer =
(CopySubBufferProc) cogl_get_proc_address ("glXCopySubBufferMESA");
}
CLUTTER_NOTE (BACKEND, "backend features checked");
return flags;
@ -739,87 +700,16 @@ clutter_backend_glx_ensure_context (ClutterBackend *backend,
}
}
static void
glx_wait_for_vblank (ClutterBackendGLX *backend_glx)
{
/* If we are going to wait for VBLANK manually, we not only need
* to flush out pending drawing to the GPU before we sleep, we
* need to wait for it to finish. Otherwise, we may end up with
* the situation:
*
* - We finish drawing - GPU drawing continues
* - We go to sleep - GPU drawing continues
* VBLANK - We call glXSwapBuffers - GPU drawing continues
* - GPU drawing continues
* - Swap buffers happens
*
* Producing a tear. Calling glFinish() first will cause us to properly
* wait for the next VBLANK before we swap. This obviously does not
* happen when we use GLX_SWAP and let the driver do the right thing
*/
switch (backend_glx->vblank_type)
{
case CLUTTER_VBLANK_GLX_SWAP:
CLUTTER_NOTE (BACKEND, "Waiting for vblank (swap)");
break;
case CLUTTER_VBLANK_GLX:
{
unsigned int retraceCount;
glFinish ();
CLUTTER_NOTE (BACKEND, "Waiting for vblank (wait_video_sync)");
backend_glx->get_video_sync (&retraceCount);
backend_glx->wait_video_sync (2,
(retraceCount + 1) % 2,
&retraceCount);
}
break;
case CLUTTER_VBLANK_DRI:
#ifdef __linux__
{
drm_wait_vblank_t blank;
glFinish ();
CLUTTER_NOTE (BACKEND, "Waiting for vblank (drm)");
blank.request.type = DRM_VBLANK_RELATIVE;
blank.request.sequence = 1;
blank.request.signal = 0;
drm_wait_vblank (backend_glx->dri_fd, &blank);
}
#endif
break;
case CLUTTER_VBLANK_NONE:
default:
break;
}
}
/*
* FIXME: we should remove backend_class->redraw() and just
* have stage_window_iface->redraw()
*/
static void
clutter_backend_glx_redraw (ClutterBackend *backend,
ClutterStage *stage)
{
ClutterBackendX11 *backend_x11;
ClutterStageGLX *stage_glx;
ClutterStageX11 *stage_x11;
ClutterStageWindow *impl;
CLUTTER_STATIC_TIMER (painting_timer,
"Redrawing", /* parent */
"Painting actors",
"The time spent painting actors",
0 /* no application private data */);
CLUTTER_STATIC_TIMER (swapbuffers_timer,
"Redrawing", /* parent */
"glXSwapBuffers",
"The time spent blocked by glXSwapBuffers",
0 /* no application private data */);
ClutterStageWindow *impl = _clutter_stage_get_window (stage);
impl = _clutter_stage_get_window (stage);
if (G_UNLIKELY (impl == NULL))
{
CLUTTER_NOTE (BACKEND, "Stage [%p] has no implementation", stage);
@ -828,39 +718,8 @@ clutter_backend_glx_redraw (ClutterBackend *backend,
g_assert (CLUTTER_IS_STAGE_GLX (impl));
backend_x11 = CLUTTER_BACKEND_X11 (backend);
stage_x11 = CLUTTER_STAGE_X11 (impl);
stage_glx = CLUTTER_STAGE_GLX (impl);
CLUTTER_TIMER_START (_clutter_uprof_context, painting_timer);
/* this will cause the stage implementation to be painted */
clutter_actor_paint (CLUTTER_ACTOR (stage));
cogl_flush ();
CLUTTER_TIMER_STOP (_clutter_uprof_context, painting_timer);
if (stage_x11->xwin != None)
{
GLXDrawable drawable =
stage_glx->glxwin ? stage_glx->glxwin : stage_x11->xwin;
/* wait for the next vblank */
glx_wait_for_vblank (CLUTTER_BACKEND_GLX (backend));
/* push on the screen */
CLUTTER_NOTE (BACKEND, "glXSwapBuffers (display: %p, window: 0x%lx)",
backend_x11->xdpy,
(unsigned long) drawable);
/* If we have GLX swap buffer events then glXSwapBuffers will return
* immediately and we need to track that there is a swap in
* progress... */
if (clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS))
stage_glx->pending_swaps++;
CLUTTER_TIMER_START (_clutter_uprof_context, swapbuffers_timer);
glXSwapBuffers (backend_x11->xdpy, drawable);
CLUTTER_TIMER_STOP (_clutter_uprof_context, swapbuffers_timer);
}
clutter_stage_glx_redraw (CLUTTER_STAGE_GLX (impl),
stage);
}
static ClutterStageWindow *

View file

@ -57,6 +57,9 @@ typedef int (*WaitVideoSyncProc) (int divisor,
int remainder,
unsigned int *count);
typedef int (*SwapIntervalProc) (int interval);
typedef void (*CopySubBufferProc)(Display *dpy,
GLXDrawable drawable,
int x, int y, int width, int height);
struct _ClutterBackendGLX
{
@ -79,6 +82,8 @@ struct _ClutterBackendGLX
gint dri_fd;
ClutterGLXVBlankType vblank_type;
CopySubBufferProc copy_sub_buffer;
/* props */
Atom atom_WM_STATE;
Atom atom_WM_STATE_FULLSCREEN;

View file

@ -64,6 +64,7 @@
#include "../clutter-util.h"
#include "../clutter-debug.h"
#include "../clutter-private.h"
#include "cogl/cogl.h"
@ -84,6 +85,12 @@ typedef enum
CLUTTER_GLX_RECTANGLE_FORCE
} RectangleState;
enum
{
PROP_0,
PROP_AUTO_REDRAW,
};
static BindTexImage _gl_bind_tex_image = NULL;
static ReleaseTexImage _gl_release_tex_image = NULL;
static GenerateMipmap _gl_generate_mipmap = NULL;
@ -767,8 +774,8 @@ clutter_glx_texture_pixmap_update_area (ClutterX11TexturePixmap *texture,
gint height)
{
ClutterGLXTexturePixmap *texture_glx = CLUTTER_GLX_TEXTURE_PIXMAP (texture);
ClutterGLXTexturePixmapPrivate *priv = texture_glx->priv;
Display *dpy;
ClutterGLXTexturePixmapPrivate *priv = texture_glx->priv;
Display *dpy;
CLUTTER_NOTE (TEXTURE, "Updating texture pixmap");
@ -830,7 +837,7 @@ clutter_glx_texture_pixmap_update_area (ClutterX11TexturePixmap *texture,
else
g_warning ("Failed to bind initial tex");
clutter_actor_queue_redraw (CLUTTER_ACTOR(texture));
priv->bind_tex_image_queued = TRUE;
}
static void
@ -852,7 +859,6 @@ clutter_glx_texture_pixmap_class_init (ClutterGLXTexturePixmapClass *klass)
actor_class->unrealize = clutter_glx_texture_pixmap_unrealize;
x11_texture_class->update_area = clutter_glx_texture_pixmap_update_area;
}
/**

View file

@ -26,6 +26,7 @@
#include "clutter-backend-glx.h"
#include "clutter-stage-glx.h"
#include "clutter-glx.h"
#include "clutter-profile.h"
#include "../clutter-main.h"
#include "../clutter-feature.h"
@ -47,6 +48,38 @@
#include <GL/glx.h>
#include <GL/gl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>
#ifdef __linux__
#define DRM_VBLANK_RELATIVE 0x1;
struct drm_wait_vblank_request {
int type;
unsigned int sequence;
unsigned long signal;
};
struct drm_wait_vblank_reply {
int type;
unsigned int sequence;
long tval_sec;
long tval_usec;
};
typedef union drm_wait_vblank {
struct drm_wait_vblank_request request;
struct drm_wait_vblank_reply reply;
} drm_wait_vblank_t;
#define DRM_IOCTL_BASE 'd'
#define DRM_IOWR(nr,type) _IOWR(DRM_IOCTL_BASE,nr,type)
#define DRM_IOCTL_WAIT_VBLANK DRM_IOWR(0x3a, drm_wait_vblank_t)
#endif /* __linux__ */
static void clutter_stage_window_iface_init (ClutterStageWindowIface *iface);
static ClutterStageWindowIface *clutter_stage_glx_parent_iface = NULL;
@ -273,6 +306,154 @@ clutter_stage_glx_init (ClutterStageGLX *stage)
{
}
static gboolean
clutter_stage_glx_has_redraw_clips (ClutterStageWindow *stage_window)
{
ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window);
/* NB: a degenerate clip means a full stage redraw is required */
if (stage_glx->initialized_redraw_clip &&
stage_glx->bounding_redraw_clip.width != 0)
return TRUE;
else
return FALSE;
}
static gboolean
clutter_stage_glx_ignoring_redraw_clips (ClutterStageWindow *stage_window)
{
ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window);
/* NB: a degenerate clip means a full stage redraw is required */
if (stage_glx->initialized_redraw_clip &&
stage_glx->bounding_redraw_clip.width == 0)
return TRUE;
else
return FALSE;
}
/* A redraw clip represents (in stage coordinates) the bounding box of
* something that needs to be redraw. Typically they are added to the
* StageWindow as a result of clutter_actor_queue_clipped_redraw() by
* actors such as ClutterGLXTexturePixmap. All redraw clips are
* discarded after the next paint.
*
* A NULL stage_clip means the whole stage needs to be redrawn.
*
* What we do with this information:
* - we keep track of the bounding box for all redraw clips
* - when we come to redraw; if the bounding box is smaller than the
* stage we scissor the redraw to that box and use
* GLX_MESA_copy_sub_buffer to present the redraw to the front
* buffer. Some heuristics are used to decide when a clipped redraw
* should be promoted into a full stage redraw.
*
* Currently we simply check that the bounding box height is < 300
* pixels.
*
* XXX: we don't have any empirical data telling us what a sensible
* thresholds is!
*
* TODO - we should use different heuristics depending on whether the
* framebuffer is on screen and not redirected by a compositor VS
* offscreen (either due to compositor redirection or because we are
* rendering to a CoglOffscreen framebuffer)
*
* When not redirected glXCopySubBuffer (on intel hardware at least)
* will block the GPU until the vertical trace is at the optimal point
* so the copy can be done without tearing. In this case we don't want
* to copy tall regions because they increase the average time spent
* blocking the GPU.
*
* When rendering offscreen (CoglOffscreen or redirected by
* compositor) then no extra synchronization is needed before the copy
* can start.
*
* In all cases we need to consider that glXCopySubBuffer implies a
* blit which may be avoided by promoting to a full stage redraw if:
* - the framebuffer is redirected offscreen or a CoglOffscreen.
* - the framebuffer is onscreen and fullscreen.
* By promoting to a full stage redraw we trade off the cost involved
* in rasterizing the extra pixels vs avoiding to use a blit to
* present the back buffer.
*
*/
static void
clutter_stage_glx_add_redraw_clip (ClutterStageWindow *stage_window,
ClutterGeometry *stage_clip)
{
ClutterStageGLX *stage_glx = CLUTTER_STAGE_GLX (stage_window);
/* If we are already forced to do a full stage redraw then bail early */
if (clutter_stage_glx_ignoring_redraw_clips (stage_window))
return;
/* A NULL stage clip means a full stage redraw has been queued and
* we keep track of this by setting a degenerate
* stage_glx->bounding_redraw_clip */
if (!stage_clip)
{
stage_glx->bounding_redraw_clip.width = 0;
return;
}
if (!stage_glx->initialized_redraw_clip)
{
stage_glx->bounding_redraw_clip.x = stage_clip->x;
stage_glx->bounding_redraw_clip.y = stage_clip->y;
stage_glx->bounding_redraw_clip.width = stage_clip->width;
stage_glx->bounding_redraw_clip.height = stage_clip->height;
}
else
{
int x2, y2;
stage_glx->bounding_redraw_clip.x =
MIN (stage_clip->x, stage_glx->bounding_redraw_clip.x);
stage_glx->bounding_redraw_clip.y =
MIN (stage_clip->y, stage_glx->bounding_redraw_clip.y);
x2 = MAX (stage_clip->x + stage_clip->width,
stage_glx->bounding_redraw_clip.x +
stage_glx->bounding_redraw_clip.width);
y2 = MAX (stage_clip->y + stage_clip->height,
stage_glx->bounding_redraw_clip.y +
stage_glx->bounding_redraw_clip.height);
stage_glx->bounding_redraw_clip.width =
x2 - stage_glx->bounding_redraw_clip.x;
stage_glx->bounding_redraw_clip.height =
y2 - stage_glx->bounding_redraw_clip.y;
}
/* FIXME: This threshold was plucked out of thin air! */
if (stage_glx->bounding_redraw_clip.height > 300)
{
/* Set a degenerate clip to force a full redraw */
stage_glx->bounding_redraw_clip.width = 0;
}
#if 0
redraw_area = (stage_glx->bounding_redraw_clip.width *
stage_glx->bounding_redraw_clip.height);
stage_area = stage_x11->xwin_width * stage_x11->xwin_height;
/* Redrawing and blitting >70% of the stage is assumed to be more
* expensive than redrawing the additional 30% to avoid the blit.
*
* FIXME: This threshold was plucked out of thin air!
*/
if (redraw_area > (stage_area * 0.7f))
{
g_print ("DEBUG: clipped redraw too big, forcing full redraw\n");
/* Set a degenerate clip to force a full redraw */
stage_glx->bounding_redraw_clip.width = 0;
}
#endif
stage_glx->initialized_redraw_clip = TRUE;
}
static void
clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
{
@ -282,6 +463,251 @@ clutter_stage_window_iface_init (ClutterStageWindowIface *iface)
iface->unrealize = clutter_stage_glx_unrealize;
iface->get_pending_swaps = clutter_stage_glx_get_pending_swaps;
iface->add_redraw_clip = clutter_stage_glx_add_redraw_clip;
iface->has_redraw_clips = clutter_stage_glx_has_redraw_clips;
iface->ignoring_redraw_clips = clutter_stage_glx_ignoring_redraw_clips;
/* the rest is inherited from ClutterStageX11 */
}
#ifdef __linux__
static int
drm_wait_vblank(int fd, drm_wait_vblank_t *vbl)
{
int ret, rc;
do
{
ret = ioctl(fd, DRM_IOCTL_WAIT_VBLANK, vbl);
vbl->request.type &= ~DRM_VBLANK_RELATIVE;
rc = errno;
}
while (ret && rc == EINTR);
return rc;
}
#endif /* __linux__ */
static void
glx_wait_for_vblank (ClutterBackendGLX *backend_glx)
{
/* If we are going to wait for VBLANK manually, we not only need
* to flush out pending drawing to the GPU before we sleep, we
* need to wait for it to finish. Otherwise, we may end up with
* the situation:
*
* - We finish drawing - GPU drawing continues
* - We go to sleep - GPU drawing continues
* VBLANK - We call glXSwapBuffers - GPU drawing continues
* - GPU drawing continues
* - Swap buffers happens
*
* Producing a tear. Calling glFinish() first will cause us to properly
* wait for the next VBLANK before we swap. This obviously does not
* happen when we use GLX_SWAP and let the driver do the right thing
*/
switch (backend_glx->vblank_type)
{
case CLUTTER_VBLANK_GLX_SWAP:
CLUTTER_NOTE (BACKEND, "Waiting for vblank (swap)");
break;
case CLUTTER_VBLANK_GLX:
{
unsigned int retraceCount;
glFinish ();
CLUTTER_NOTE (BACKEND, "Waiting for vblank (wait_video_sync)");
backend_glx->get_video_sync (&retraceCount);
backend_glx->wait_video_sync (2,
(retraceCount + 1) % 2,
&retraceCount);
}
break;
case CLUTTER_VBLANK_DRI:
#ifdef __linux__
{
drm_wait_vblank_t blank;
glFinish ();
CLUTTER_NOTE (BACKEND, "Waiting for vblank (drm)");
blank.request.type = DRM_VBLANK_RELATIVE;
blank.request.sequence = 1;
blank.request.signal = 0;
drm_wait_vblank (backend_glx->dri_fd, &blank);
}
#endif
break;
case CLUTTER_VBLANK_NONE:
default:
break;
}
}
void
clutter_stage_glx_redraw (ClutterStageGLX *stage_glx,
ClutterStage *stage)
{
ClutterBackend *backend;
ClutterBackendX11 *backend_x11;
ClutterBackendGLX *backend_glx;
ClutterStageX11 *stage_x11;
GLXDrawable drawable;
CLUTTER_STATIC_TIMER (painting_timer,
"Redrawing", /* parent */
"Painting actors",
"The time spent painting actors",
0 /* no application private data */);
CLUTTER_STATIC_TIMER (swapbuffers_timer,
"Redrawing", /* parent */
"glXSwapBuffers",
"The time spent blocked by glXSwapBuffers",
0 /* no application private data */);
CLUTTER_STATIC_TIMER (copy_sub_buffer_timer,
"Redrawing", /* parent */
"glXCopySubBufferMESA",
"The time spent blocked by glXCopySubBufferMESA",
0 /* no application private data */);
backend = clutter_get_default_backend ();
backend_x11 = CLUTTER_BACKEND_X11 (backend);
backend_glx = CLUTTER_BACKEND_GLX (backend);
stage_x11 = CLUTTER_STAGE_X11 (stage_glx);
CLUTTER_TIMER_START (_clutter_uprof_context, painting_timer);
if (backend_glx->copy_sub_buffer &&
/* NB: a degenerate redraw clip width == full stage redraw */
(stage_glx->bounding_redraw_clip.width != 0) &&
G_LIKELY (!(clutter_paint_debug_flags &
CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS)))
{
cogl_clip_push_window_rectangle (stage_glx->bounding_redraw_clip.x,
stage_glx->bounding_redraw_clip.y,
stage_glx->bounding_redraw_clip.width,
stage_glx->bounding_redraw_clip.height);
clutter_actor_paint (CLUTTER_ACTOR (stage));
cogl_clip_pop ();
}
else
clutter_actor_paint (CLUTTER_ACTOR (stage));
cogl_flush ();
CLUTTER_TIMER_STOP (_clutter_uprof_context, painting_timer);
if (stage_x11->xwin == None)
return;
drawable = stage_glx->glxwin ? stage_glx->glxwin : stage_x11->xwin;
/* wait for the next vblank */
glx_wait_for_vblank (CLUTTER_BACKEND_GLX (backend));
/* push on the screen */
if (backend_glx->copy_sub_buffer &&
/* NB: a degenerate redraw clip width == full stage redraw */
(stage_glx->bounding_redraw_clip.width != 0) &&
G_LIKELY (!(clutter_paint_debug_flags &
CLUTTER_DEBUG_DISABLE_CLIPPED_REDRAWS)))
{
ClutterGeometry *clip = &stage_glx->bounding_redraw_clip;
ClutterGeometry copy_area;
CLUTTER_NOTE (BACKEND,
"glXCopySubBufferMESA (display: %p, "
"window: 0x%lx, "
"x: %d, y: %d, "
"width: %d, height: %d)",
backend_x11->xdpy,
(unsigned long) drawable,
stage_glx->bounding_redraw_clip.x,
stage_glx->bounding_redraw_clip.y,
stage_glx->bounding_redraw_clip.width,
stage_glx->bounding_redraw_clip.height);
if (clutter_paint_debug_flags & CLUTTER_DEBUG_REDRAWS)
{
static CoglHandle outline = COGL_INVALID_HANDLE;
CoglHandle vbo;
float x_1 = clip->x;
float x_2 = clip->x + clip->width;
float y_1 = clip->y;
float y_2 = clip->y + clip->height;
float quad[8] = {
x_1, y_1,
x_2, y_1,
x_2, y_2,
x_1, y_2
};
if (outline == COGL_INVALID_HANDLE)
{
outline = cogl_material_new ();
cogl_material_set_color4ub (outline, 0xff, 0x00, 0x00, 0xff);
}
vbo = cogl_vertex_buffer_new (4);
cogl_vertex_buffer_add (vbo,
"gl_Vertex",
2, /* n_components */
COGL_ATTRIBUTE_TYPE_FLOAT,
FALSE, /* normalized */
0, /* stride */
quad);
cogl_vertex_buffer_submit (vbo);
cogl_set_source (outline);
cogl_vertex_buffer_draw (vbo, COGL_VERTICES_MODE_LINE_LOOP,
0 , 4);
cogl_flush ();
}
/* XXX: It seems there will be a race here in that the stage
* window may be resized before glXCopySubBufferMESA is handled
* and so we may copy the wrong region. I can't really see how
* we can handle this with the current state of X but at least
* in this case a full redraw should be queued by the resize
* anyway so it should only exhibit temporary artefacts.
*/
copy_area.y = clutter_actor_get_height (CLUTTER_ACTOR (stage))
- clip->y - clip->height;
copy_area.x = clip->x;
copy_area.width = clip->width;
copy_area.height = clip->height;
CLUTTER_TIMER_START (_clutter_uprof_context, copy_sub_buffer_timer);
backend_glx->copy_sub_buffer (backend_x11->xdpy,
drawable,
copy_area.x,
copy_area.y,
copy_area.width,
copy_area.height);
CLUTTER_TIMER_STOP (_clutter_uprof_context, copy_sub_buffer_timer);
}
else
{
CLUTTER_NOTE (BACKEND, "glXSwapBuffers (display: %p, window: 0x%lx)",
backend_x11->xdpy,
(unsigned long) drawable);
/* If we have GLX swap buffer events then glXSwapBuffers will return
* immediately and we need to track that there is a swap in
* progress... */
if (clutter_feature_available (CLUTTER_FEATURE_SWAP_EVENTS))
stage_glx->pending_swaps++;
CLUTTER_TIMER_START (_clutter_uprof_context, swapbuffers_timer);
glXSwapBuffers (backend_x11->xdpy, drawable);
CLUTTER_TIMER_STOP (_clutter_uprof_context, swapbuffers_timer);
}
/* reset the redraw clipping for the next paint... */
stage_glx->initialized_redraw_clip = FALSE;
}

View file

@ -52,6 +52,9 @@ struct _ClutterStageGLX
GLXPixmap glxpixmap;
GLXWindow glxwin;
gboolean initialized_redraw_clip;
ClutterGeometry bounding_redraw_clip;
};
struct _ClutterStageGLXClass
@ -59,7 +62,10 @@ struct _ClutterStageGLXClass
ClutterStageX11Class parent_class;
};
GType clutter_stage_glx_get_type (void) G_GNUC_CONST;
GType clutter_stage_glx_get_type (void) G_GNUC_CONST;
void clutter_stage_glx_redraw (ClutterStageGLX *stage_glx,
ClutterStage *stage);
G_END_DECLS

View file

@ -44,6 +44,7 @@
#include "clutter-x11-texture-pixmap.h"
#include "clutter-x11.h"
#include "clutter-backend-x11.h"
#include "clutter-private.h"
#include "cogl/cogl.h"
@ -74,6 +75,7 @@ enum
enum
{
UPDATE_AREA,
QUEUE_DAMAGE_REDRAW,
/* FIXME: Pixmap lost signal? */
LAST_SIGNAL
};
@ -255,60 +257,68 @@ failed_image_create:
return FALSE;
}
static void
check_for_pixmap_damage (ClutterX11TexturePixmap *texture)
{
ClutterX11TexturePixmapPrivate *priv = texture->priv;
Display *dpy;
XserverRegion parts;
int i, r_count;
XRectangle *r_damage;
XRectangle r_bounds;
clutter_x11_trap_x_errors ();
/*
* Retrieve the damaged region and break it down into individual
* rectangles so we do not have to update the whole shebang.
*/
dpy = clutter_x11_get_default_display();
parts = XFixesCreateRegion (dpy, 0, 0);
XDamageSubtract (dpy, priv->damage, None, parts);
r_damage = XFixesFetchRegionAndBounds (dpy,
parts,
&r_count,
&r_bounds);
clutter_x11_untrap_x_errors ();
if (r_damage)
{
for (i = 0; i < r_count; ++i)
clutter_x11_texture_pixmap_update_area (texture,
r_damage[i].x,
r_damage[i].y,
r_damage[i].width,
r_damage[i].height);
XFree (r_damage);
}
XFixesDestroyRegion (dpy, parts);
}
static ClutterX11FilterReturn
on_x_event_filter (XEvent *xev, ClutterEvent *cev, gpointer data)
{
ClutterX11TexturePixmap *texture;
ClutterX11TexturePixmapPrivate *priv;
Display *dpy;
texture = CLUTTER_X11_TEXTURE_PIXMAP (data);
g_return_val_if_fail (CLUTTER_X11_IS_TEXTURE_PIXMAP (texture), \
CLUTTER_X11_FILTER_CONTINUE);
dpy = clutter_x11_get_default_display();
priv = texture->priv;
if (xev->type == _damage_event_base + XDamageNotify)
{
XserverRegion parts;
gint i, r_count;
XRectangle *r_damage;
XRectangle r_bounds;
XDamageNotifyEvent *dev = (XDamageNotifyEvent*)xev;
if (dev->drawable != priv->damage_drawable)
return CLUTTER_X11_FILTER_CONTINUE;
clutter_x11_trap_x_errors ();
/*
* Retrieve the damaged region and break it down into individual
* rectangles so we do not have to update the whole shebang.
*/
parts = XFixesCreateRegion (dpy, 0, 0);
XDamageSubtract (dpy, priv->damage, None, parts);
r_damage = XFixesFetchRegionAndBounds (dpy,
parts,
&r_count,
&r_bounds);
clutter_x11_untrap_x_errors ();
if (r_damage)
{
for (i = 0; i < r_count; ++i)
clutter_x11_texture_pixmap_update_area (texture,
r_damage[i].x,
r_damage[i].y,
r_damage[i].width,
r_damage[i].height);
XFree (r_damage);
}
XFixesDestroyRegion (dpy, parts);
check_for_pixmap_damage (texture);
}
return CLUTTER_X11_FILTER_CONTINUE;
@ -404,6 +414,54 @@ free_damage_resources (ClutterX11TexturePixmap *texture)
}
}
static void
clutter_x11_texture_pixmap_real_queue_damage_redraw (
ClutterX11TexturePixmap *texture,
gint x,
gint y,
gint width,
gint height)
{
ClutterActor *self = CLUTTER_ACTOR (texture);
ClutterActorBox allocation;
guint pixmap_width = 0;
guint pixmap_height = 0;
float scale_x;
float scale_y;
ClutterActorBox clip;
/* NB: clutter_actor_queue_clipped_redraw expects a box in the actor's
* coordinate space so we need to convert from pixmap coordinates to
* actor coordinates...
*/
/* XXX: we don't care if we get an out of date allocation here because
* clutter_actor_queue_clipped_redraw knows to ignore the clip if the
* actor's allocation is invalid.
*
* This is noted because clutter_actor_get_allocation_box does some
* unnecessary work to support buggy code with a comment suggesting that
* it could be changed later which would be good for this use case!
*/
clutter_actor_get_allocation_box (self, &allocation);
g_object_get (self,
"pixmap-width", &pixmap_width,
"pixmap-height", &pixmap_height,
NULL);
scale_x = (allocation.x2 - allocation.x1) / pixmap_width;
scale_y = (allocation.y2 - allocation.y1) / pixmap_height;
clip.x1 = x * scale_x;
clip.y1 = y * scale_y;
clip.x2 = clip.x1 + width * scale_x;
clip.y2 = clip.y1 + height * scale_y;
_clutter_actor_queue_redraw_with_clip (self,
CLUTTER_REDRAW_CLIPPED_TO_BOX,
&clip);
}
static void
clutter_x11_texture_pixmap_init (ClutterX11TexturePixmap *self)
@ -413,6 +471,11 @@ clutter_x11_texture_pixmap_init (ClutterX11TexturePixmap *self)
CLUTTER_X11_TYPE_TEXTURE_PIXMAP,
ClutterX11TexturePixmapPrivate);
g_signal_override_class_handler (
"queue-damage-redraw",
CLUTTER_X11_TYPE_TEXTURE_PIXMAP,
G_CALLBACK (clutter_x11_texture_pixmap_real_queue_damage_redraw));
if (!check_extensions (self))
{
/* FIMXE: means display lacks needed extensions for at least auto.
@ -729,6 +792,40 @@ clutter_x11_texture_pixmap_class_init (ClutterX11TexturePixmapClass *klass)
G_TYPE_INT,
G_TYPE_INT);
/**
* ClutterX11TexturePixmap::queue-damage-redraw
* @texture: the object which received the signal
* @x: The top left x position of the damage region
* @y: The top left y position of the damage region
* @width: The width of the damage region
* @height: The height of the damage region
*
* ::queue-damage-redraw is emitted to notify that some sub-region of the
* underlying pixmap has changed and you need to queue a
* corresponding redraw for the actor.
*
* The default handler will queue a clipped redraw in response to
* the damage, using the assumption that the pixmap is being painted
* to a rectangle covering the transformed allocation of the actor.
* If you sub-class and change the paint method so this isn't true
* then you must also provide your own damage signal handler to
* queue a redraw that blocks this default behaviour.
*
* Since: 1.2
*/
signals[QUEUE_DAMAGE_REDRAW] =
g_signal_new ("queue-damage-redraw",
G_TYPE_FROM_CLASS (object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL, NULL,
clutter_marshal_VOID__INT_INT_INT_INT,
G_TYPE_NONE, 4,
G_TYPE_INT,
G_TYPE_INT,
G_TYPE_INT,
G_TYPE_INT);
default_backend = clutter_get_default_backend ();
if (!CLUTTER_IS_BACKEND_X11 (default_backend))
@ -1369,6 +1466,12 @@ clutter_x11_texture_pixmap_update_area (ClutterX11TexturePixmap *texture,
g_return_if_fail (CLUTTER_X11_IS_TEXTURE_PIXMAP (texture));
g_signal_emit (texture, signals[UPDATE_AREA], 0, x, y, width, height);
/* The default handler for the "queue-damage-redraw" signal is
* clutter_x11_texture_pixmap_real_queue_damage_redraw which will queue a
* clipped redraw. */
g_signal_emit (texture, signals[QUEUE_DAMAGE_REDRAW],
0, x, y, width, height);
}
/**
@ -1405,3 +1508,4 @@ clutter_x11_texture_pixmap_set_automatic (ClutterX11TexturePixmap *texture,
priv->automatic_updates = setting;
}