diff --git a/clutter/clutter-actor.c b/clutter/clutter-actor.c index 0a37789dd..faf9daf9e 100644 --- a/clutter/clutter-actor.c +++ b/clutter/clutter-actor.c @@ -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; +} + diff --git a/clutter/clutter-actor.h b/clutter/clutter-actor.h index 074ca67f6..4bb46cd9e 100644 --- a/clutter/clutter-actor.h +++ b/clutter/clutter-actor.h @@ -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); diff --git a/clutter/clutter-debug.h b/clutter/clutter-debug.h index c2c49585b..554eb8e49 100644 --- a/clutter/clutter-debug.h +++ b/clutter/clutter-debug.h @@ -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 diff --git a/clutter/clutter-main.c b/clutter/clutter-main.c index 97bc661e3..053b6fc56 100644 --- a/clutter/clutter-main.c +++ b/clutter/clutter-main.c @@ -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 diff --git a/clutter/clutter-private.h b/clutter/clutter-private.h index 9a1237efc..b26771a20 100644 --- a/clutter/clutter-private.h +++ b/clutter/clutter-private.h @@ -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); diff --git a/clutter/clutter-stage-window.c b/clutter/clutter-stage-window.c index 7268643d7..67c2adca2 100644 --- a/clutter/clutter-stage-window.c +++ b/clutter/clutter-stage-window.c @@ -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; +} + diff --git a/clutter/clutter-stage-window.h b/clutter/clutter-stage-window.h index 096a4f353..3c4c21e65 100644 --- a/clutter/clutter-stage-window.h +++ b/clutter/clutter-stage-window.h @@ -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 diff --git a/clutter/clutter-stage.c b/clutter/clutter-stage.c index b71064e82..03358f2c3 100644 --- a/clutter/clutter-stage.c +++ b/clutter/clutter-stage.c @@ -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 diff --git a/clutter/glx/clutter-backend-glx.c b/clutter/glx/clutter-backend-glx.c index 772efcf46..d77eda22d 100644 --- a/clutter/glx/clutter-backend-glx.c +++ b/clutter/glx/clutter-backend-glx.c @@ -28,14 +28,10 @@ #ifdef HAVE_UNISTD_H #include #endif +#include #include -#include -#include -#include -#include - #include #include #include @@ -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 * diff --git a/clutter/glx/clutter-backend-glx.h b/clutter/glx/clutter-backend-glx.h index a7e493983..feb8d4f3e 100644 --- a/clutter/glx/clutter-backend-glx.h +++ b/clutter/glx/clutter-backend-glx.h @@ -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; diff --git a/clutter/glx/clutter-glx-texture-pixmap.c b/clutter/glx/clutter-glx-texture-pixmap.c index a70223f87..59a80e8ba 100644 --- a/clutter/glx/clutter-glx-texture-pixmap.c +++ b/clutter/glx/clutter-glx-texture-pixmap.c @@ -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; - } /** diff --git a/clutter/glx/clutter-stage-glx.c b/clutter/glx/clutter-stage-glx.c index 69cc754ba..e23c77038 100644 --- a/clutter/glx/clutter-stage-glx.c +++ b/clutter/glx/clutter-stage-glx.c @@ -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 #include +#include +#include +#include +#include + +#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; +} + diff --git a/clutter/glx/clutter-stage-glx.h b/clutter/glx/clutter-stage-glx.h index 80b1867f3..baad929a6 100644 --- a/clutter/glx/clutter-stage-glx.h +++ b/clutter/glx/clutter-stage-glx.h @@ -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 diff --git a/clutter/x11/clutter-x11-texture-pixmap.c b/clutter/x11/clutter-x11-texture-pixmap.c index eee0a26f3..0c66a81cb 100644 --- a/clutter/x11/clutter-x11-texture-pixmap.c +++ b/clutter/x11/clutter-x11-texture-pixmap.c @@ -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; } +