gdkwindow: Remove implicit paints

We now only do one expose event per native window, so there will
only be one begin/end_paint() call. This means all the work with
implicit paints to combine the paints on a single double buffer
surface is unnecessary, so we can just delete it.
This commit is contained in:
Alexander Larsson 2013-04-17 23:06:07 +02:00
parent d22fd7223c
commit fe0982818d
2 changed files with 32 additions and 399 deletions

View File

@ -224,6 +224,7 @@ struct _GdkWindow
guint native_visibility : 2; /* the native visibility of a impl windows */
guint viewable : 1; /* mapped and all parents mapped */
guint applied_shape : 1;
guint in_update : 1;
GdkFullscreenMode fullscreen_mode;
/* The GdkWindow that has the impl, ref:ed if another window.
@ -250,8 +251,6 @@ struct _GdkWindow
GdkCursor *cursor;
GHashTable *device_cursor;
GSList *implicit_paint;
cairo_region_t *shape;
cairo_region_t *input_shape;

View File

@ -133,14 +133,6 @@
* For native windows we apply the calculated clip region as a window shape
* so that eg. client side siblings that overlap the native child properly
* draws over the native child window.
*
* We use something called a "implicit paint" during repaint handling.
* An implicit paint is similar to a regular paint for the paint stack, but it is
* not put on the stack. Instead, it is set on the impl window, and later when
* regular gdk_window_begin_paint_region() happen on a window of this impl window
* we reuse the surface from the implicit paint. During repaint we create and at the
* end flush an implicit paint, which means we can collect all the paints on
* multiple client side windows in the same backing store.
*/
/* This adds a local value to the GdkVisibilityState enum */
@ -169,9 +161,6 @@ struct _GdkWindowPaint
{
cairo_region_t *region;
cairo_surface_t *surface;
cairo_region_t *flushed;
guint8 alpha;
guint uses_implicit : 1;
};
/* Global info */
@ -196,7 +185,6 @@ static void gdk_window_clear_backing_region (GdkWindow *window,
static void recompute_visible_regions (GdkWindow *private,
gboolean recalculate_children);
static void gdk_window_flush_recursive (GdkWindow *window);
static void gdk_window_invalidate_in_parent (GdkWindow *private);
static void move_native_children (GdkWindow *private);
static void update_cursor (GdkDisplay *display,
@ -2655,217 +2643,12 @@ gdk_window_get_content (GdkWindow *window)
return content;
}
/* This creates an empty "implicit" paint region for the impl window.
* By itself this does nothing, but real paints to this window
* or children of it can use this surface as backing to avoid allocating
* multiple surfaces for subwindow rendering. When doing so they
* add to the region of the implicit paint region, which will be
* pushed to the window when the implicit paint region is ended.
* Such paints should not copy anything to the window on paint end, but
* should rely on the implicit paint end.
* The implicit paint will be automatically ended if someone draws
* directly to the window or a child window.
*/
static gboolean
gdk_window_begin_implicit_paint (GdkWindow *window, GdkRectangle *rect,
gboolean with_alpha, guint8 alpha)
{
GdkWindowPaint *paint;
g_assert (gdk_window_has_impl (window));
if (GDK_IS_PAINTABLE (window->impl))
return FALSE; /* Implementation does double buffering */
if (window->paint_stack != NULL)
return FALSE; /* Don't stack implicit paints */
/* Never do implicit paints for foreign windows, they don't need
* double buffer combination since they have no client side children,
* and creating surfaces for them is risky since they could disappear
* at any time
*/
if (window->window_type == GDK_WINDOW_FOREIGN)
return FALSE;
paint = g_new (GdkWindowPaint, 1);
paint->region = cairo_region_create (); /* Empty */
paint->uses_implicit = FALSE;
paint->flushed = NULL;
paint->alpha = alpha;
paint->surface = gdk_window_create_similar_surface (window,
with_alpha ? CAIRO_CONTENT_COLOR_ALPHA : gdk_window_get_content (window),
MAX (rect->width, 1),
MAX (rect->height, 1));
cairo_surface_set_device_offset (paint->surface, -rect->x, -rect->y);
window->implicit_paint = g_slist_prepend (window->implicit_paint, paint);
return TRUE;
}
static cairo_surface_t *
gdk_window_ref_impl_surface (GdkWindow *window)
{
return GDK_WINDOW_IMPL_GET_CLASS (window->impl)->ref_cairo_surface (gdk_window_get_impl_window (window));
}
static cairo_t *
gdk_cairo_create_for_impl (GdkWindow *window)
{
cairo_surface_t *surface;
cairo_t *cr;
surface = gdk_window_ref_impl_surface (window);
cr = cairo_create (surface);
cairo_surface_destroy (surface);
return cr;
}
/* This is called whenever something is drawing directly to the
* window, bypassing the double buffering. When this happens we
* need to mark any the currently drawn data in the double buffer
* as invalid to avoid later drawing it back over the directly
* rendered pixels. We also need to mark this region as "flushed"
* so that if we later try to paint on it double-buffered we need
* to read back the on-window pixels rather than relying on what
* is in the current double-buffer pixmap.
*
* Note that this doesn't correctly handle the case where the
* non-double buffered drawing uses transparency and relies on
* what the windows below it draws. A fix for that would require
* drawing the existing double-buffered background to the window,
* but that causes ugly flashes. Non-double buffered drawing is
* typically only used in old code or when the drawed widget
* already has a double-buffering layer, and in these cases the
* pixels are opaque anyway. If you need transparency, don't
* disable double buffering.
*/
static void
gdk_window_flush_implicit_paint (GdkWindow *window)
{
GdkWindow *impl_window;
GdkWindowPaint *paint;
cairo_region_t *region;
GSList *l;
impl_window = gdk_window_get_impl_window (window);
if (impl_window->implicit_paint == NULL)
return;
/* There is no proper way to flush any non-toplevel implicit
paint, because direct rendering is not compatible with
opacity rendering, as it requires an opacity group.
We warn and flush just the outermost paint in this case.
*/
l = g_slist_last (impl_window->implicit_paint);
if (l != impl_window->implicit_paint)
g_warning ("Non double buffered drawing not supported inside transparent windows");
paint = l->data;
region = cairo_region_copy (window->clip_region);
cairo_region_translate (region, window->abs_x, window->abs_y);
/* Anything in the whole flushed window that was drawn is now
considered unpainted, so that we don't push it back at the
end of the implicit paint overwriting the directly rendered
pixels. */
cairo_region_subtract (paint->region, region);
/* Save flushed area so we can read it back if we draw over it later */
if (paint->flushed == NULL)
paint->flushed = region;
else
{
cairo_region_union (paint->flushed, region);
cairo_region_destroy (region);
}
}
/* Ends an implicit paint, paired with gdk_window_begin_implicit_paint returning TRUE */
static void
gdk_window_end_implicit_paint (GdkWindow *window)
{
GdkWindowPaint *paint;
g_assert (gdk_window_has_impl (window));
g_assert (window->implicit_paint != NULL);
paint = window->implicit_paint->data;
window->implicit_paint = g_slist_delete_link (window->implicit_paint,
window->implicit_paint);
if (!GDK_WINDOW_DESTROYED (window) && !cairo_region_is_empty (paint->region) && paint->alpha > 0)
{
GdkWindowPaint *parent_paint = NULL;
cairo_t *cr;
/* Some regions are valid, push these to window now */
if (window->implicit_paint)
{
parent_paint = window->implicit_paint->data;
/* If the toplevel implicit paint was flushed, restore it now
before we blend over it. */
if (parent_paint->flushed != NULL &&
!cairo_region_is_empty (parent_paint->flushed))
{
cairo_surface_t *source_surface = gdk_window_ref_impl_surface (window);
cairo_region_t *flushed = cairo_region_copy (parent_paint->flushed);
cr = cairo_create (parent_paint->surface);
cairo_set_source_surface (cr, source_surface, 0, 0);
cairo_surface_destroy (source_surface);
cairo_region_intersect (flushed, paint->region);
gdk_cairo_region (cr, flushed);
cairo_clip (cr);
cairo_region_subtract (parent_paint->flushed, flushed);
cairo_region_destroy (flushed);
cairo_paint (cr);
cairo_destroy (cr);
}
cr = cairo_create (parent_paint->surface);
}
else
cr = gdk_cairo_create_for_impl (window);
gdk_cairo_region (cr, paint->region);
cairo_clip (cr);
cairo_set_source_surface (cr, paint->surface, 0, 0);
if (paint->alpha == 255)
{
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_paint (cr);
}
else
{
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
cairo_paint_with_alpha (cr, paint->alpha / 255.0);
}
cairo_destroy (cr);
if (parent_paint)
cairo_region_union (parent_paint->region, paint->region);
}
cairo_region_destroy (paint->region);
if (paint->flushed)
cairo_region_destroy (paint->flushed);
cairo_surface_destroy (paint->surface);
g_free (paint);
}
/**
* gdk_window_begin_paint_rect:
* @window: a #GdkWindow
@ -2939,8 +2722,7 @@ gdk_window_begin_paint_region (GdkWindow *window,
const cairo_region_t *region)
{
GdkRectangle clip_box;
GdkWindowPaint *paint, *implicit_paint;
GdkWindow *impl_window;
GdkWindowPaint *paint;
GSList *list;
g_return_if_fail (GDK_IS_WINDOW (window));
@ -2958,78 +2740,16 @@ gdk_window_begin_paint_region (GdkWindow *window,
return;
}
impl_window = gdk_window_get_impl_window (window);
implicit_paint = impl_window->implicit_paint != NULL ? impl_window->implicit_paint->data : NULL;
paint = g_new (GdkWindowPaint, 1);
paint->region = cairo_region_copy (region);
cairo_region_intersect (paint->region, window->clip_region);
cairo_region_get_extents (paint->region, &clip_box);
cairo_region_translate (paint->region, window->abs_x, window->abs_y);
/* Mark the region as valid on the implicit paint */
if (implicit_paint)
cairo_region_union (implicit_paint->region, paint->region);
/* Convert back to normal coords */
cairo_region_translate (paint->region, -window->abs_x, -window->abs_y);
if (implicit_paint)
{
paint->uses_implicit = TRUE;
paint->surface = cairo_surface_create_for_rectangle (implicit_paint->surface,
window->abs_x + clip_box.x,
window->abs_y + clip_box.y,
MAX (clip_box.width, 1),
MAX (clip_box.height, 1));
}
else
{
paint->uses_implicit = FALSE;
paint->surface = gdk_window_create_similar_surface (window,
gdk_window_get_content (window),
MAX (clip_box.width, 1),
MAX (clip_box.height, 1));
}
/* Normally alpha backgrounded client side windows are composited on the implicit paint
by being drawn in back to front order. However, if implicit paints are not used, for
instance if it was flushed due to a non-double-buffered paint in the middle of the
expose we need to copy in the existing data here. */
if (!implicit_paint ||
(implicit_paint && implicit_paint->flushed != NULL && !cairo_region_is_empty (implicit_paint->flushed)))
{
cairo_t *cr = cairo_create (paint->surface);
/* We can't use gdk_cairo_set_source_window here, as that might
flush the implicit paint at an unfortunate time, since this
would be detected as a draw during non-expose time */
cairo_surface_t *source_surface = gdk_window_ref_impl_surface (impl_window);
cairo_set_source_surface (cr, source_surface,
- (window->abs_x + clip_box.x),
- (window->abs_y + clip_box.y));
cairo_surface_destroy (source_surface);
/* Only read back the flushed area if any */
if (implicit_paint)
{
cairo_region_t *flushed = cairo_region_copy (implicit_paint->flushed);
/* Convert from impl coords */
cairo_region_translate (flushed, -window->abs_x, -window->abs_y);
cairo_region_intersect (flushed, paint->region);
gdk_cairo_region (cr, flushed);
cairo_clip (cr);
/* Convert to impl coords */
cairo_region_translate (flushed, window->abs_x, window->abs_y);
cairo_region_subtract (implicit_paint->flushed, flushed);
cairo_region_destroy (flushed);
}
cairo_paint (cr);
cairo_destroy (cr);
}
paint->surface = gdk_window_create_similar_surface (window,
gdk_window_get_content (window),
MAX (clip_box.width, 1),
MAX (clip_box.height, 1));
cairo_surface_set_device_offset (paint->surface, -clip_box.x, -clip_box.y);
@ -3069,6 +2789,7 @@ gdk_window_end_paint (GdkWindow *window)
GdkWindowPaint *paint;
GdkRectangle clip_box;
cairo_region_t *full_clip;
cairo_t *cr;
g_return_if_fail (GDK_IS_WINDOW (window));
@ -3096,23 +2817,27 @@ gdk_window_end_paint (GdkWindow *window)
window->paint_stack);
cairo_region_get_extents (paint->region, &clip_box);
full_clip = cairo_region_copy (window->clip_region);
cairo_region_intersect (full_clip, paint->region);
if (!paint->uses_implicit)
cr = gdk_cairo_create (window);
cairo_set_source_surface (cr, paint->surface, 0, 0);
gdk_cairo_region (cr, full_clip);
cairo_clip (cr);
if (gdk_window_has_impl (window) ||
window->alpha == 255)
{
cairo_t *cr;
full_clip = cairo_region_copy (window->clip_region);
cairo_region_intersect (full_clip, paint->region);
cr = gdk_cairo_create (window);
cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE);
cairo_set_source_surface (cr, paint->surface, 0, 0);
gdk_cairo_region (cr, full_clip);
cairo_fill (cr);
cairo_destroy (cr);
cairo_region_destroy (full_clip);
cairo_paint (cr);
}
else
{
cairo_set_operator (cr, CAIRO_OPERATOR_OVER);
cairo_paint_with_alpha (cr, window->alpha / 255.0);
}
cairo_destroy (cr);
cairo_region_destroy (full_clip);
cairo_surface_destroy (paint->surface);
cairo_region_destroy (paint->region);
@ -3190,33 +2915,6 @@ gdk_window_free_paint_stack (GdkWindow *window)
void
gdk_window_flush (GdkWindow *window)
{
gdk_window_flush_implicit_paint (window);
}
static void
gdk_window_flush_recursive_helper (GdkWindow *window,
GdkWindowImpl *impl)
{
GdkWindow *child;
GList *l;
for (l = window->children; l != NULL; l = l->next)
{
child = l->data;
if (child->impl == impl)
/* Same impl, ignore */
gdk_window_flush_recursive_helper (child, impl);
else
gdk_window_flush_recursive (child);
}
}
static void
gdk_window_flush_recursive (GdkWindow *window)
{
gdk_window_flush (window);
gdk_window_flush_recursive_helper (window, window->impl);
}
/**
@ -3395,10 +3093,6 @@ _gdk_window_ref_cairo_surface (GdkWindow *window)
}
else
{
/* This will be drawing directly to the window, so flush implicit paint */
gdk_window_flush (window);
if (!window->cairo_surface)
{
window->cairo_surface = gdk_window_create_cairo_surface (window,
@ -3452,18 +3146,6 @@ gdk_cairo_create (GdkWindow *window)
gdk_cairo_region (cr, window->clip_region);
cairo_clip (cr);
}
else
{
GdkWindowPaint *paint = window->paint_stack->data;
/* Only needs to clip to region if piggybacking
on an implicit paint */
if (paint->uses_implicit)
{
gdk_cairo_region (cr, paint->region);
cairo_clip (cr);
}
}
cairo_surface_destroy (surface);
@ -3608,7 +3290,6 @@ _gdk_window_process_updates_recurse (GdkWindow *window,
cairo_region_t *clipped_expose_region;
GdkRectangle clip_box;
GList *l, *children;
gboolean end_implicit;
if (cairo_region_is_empty (expose_region))
return;
@ -3617,7 +3298,6 @@ _gdk_window_process_updates_recurse (GdkWindow *window,
gdk_window_has_impl (window))
_gdk_window_add_damage ((GdkWindow *) window->impl_window, expose_region);
end_implicit = FALSE;
if (window->alpha != 255 && !gdk_window_has_impl (window))
{
if (window->alpha == 0)
@ -3626,7 +3306,7 @@ _gdk_window_process_updates_recurse (GdkWindow *window,
cairo_region_get_extents (expose_region, &clip_box);
clip_box.x += window->abs_x;
clip_box.y += window->abs_y;
end_implicit = gdk_window_begin_implicit_paint (window->impl_window, &clip_box, TRUE, window->alpha);
/* TODO: How do we now do subwindow alphas */
}
/* Paint the window before the children, clipped to the window region */
@ -3681,9 +3361,6 @@ _gdk_window_process_updates_recurse (GdkWindow *window,
}
g_list_free_full (children, g_object_unref);
if (end_implicit)
gdk_window_end_implicit_paint (window->impl_window);
}
/* Process and remove any invalid area on the native window by creating
@ -3699,6 +3376,8 @@ gdk_window_process_updates_internal (GdkWindow *window)
/* Ensure the window lives while updating it */
g_object_ref (window);
window->in_update = TRUE;
/* If an update got queued during update processing, we can get a
* window in the update queue that has an empty update_area.
* just ignore it.
@ -3711,9 +3390,8 @@ gdk_window_process_updates_internal (GdkWindow *window)
if (gdk_window_is_viewable (window))
{
cairo_region_t *expose_region;
gboolean end_implicit;
/* Clip to part visible in toplevel */
/* Clip to part visible in impl window */
cairo_region_intersect (update_area, window->clip_region);
if (debug_updates)
@ -3723,63 +3401,19 @@ gdk_window_process_updates_internal (GdkWindow *window)
g_usleep (70000);
}
/* By now we an update region that should be repainted. However in order to
* get nicer drawing we do some tricks:
*
* First of all, each subwindow expose may be double buffered by
* itself (depending on widget setting) via
* gdk_window_begin/end_paint(). But we also do an "implicit" paint,
* creating a single surface the size of the invalid area on the
* native window which all the individual normal paints will draw
* into. This way in the normal case there will be only one surface
* allocated and only once surface draw done for all the windows
* in this native window.
* There are a couple of reasons this may fail, for instance, some
* backends (like quartz) do its own double buffering, so we disable
* gdk double buffering there. Secondly, some subwindow could be
* non-double buffered and draw directly to the window outside a
* begin/end_paint pair. That will be lead to a gdk_window_flush
* which immediately paints+removes the implicit paint (further
* paints will allocate their own surfaces).
*
* Secondly, we queue an "antiexpose" on the area that will be drawn by
* the expose, which means that any invalid region on the native window side
* before the first expose drawing operation will be discarded, as it
* has by then been overdrawn with valid data. This means we can
* avoid doing the unnecessary repaint any outstanding expose events.
*/
cairo_region_get_extents (update_area, &clip_box);
end_implicit = gdk_window_begin_implicit_paint (window, &clip_box, FALSE, 255);
expose_region = cairo_region_copy (update_area);
impl_class = GDK_WINDOW_IMPL_GET_CLASS (window->impl);
if (!end_implicit)
{
save_region = impl_class->queue_antiexpose (window, update_area);
}
/* Render the invalid areas to the implicit paint, by sending exposes.
* May flush if non-double buffered widget draw. */
save_region = impl_class->queue_antiexpose (window, update_area);
impl_class->process_updates_recurse (window, expose_region);
if (end_implicit)
{
/* By this time we know that any outstanding expose for this
* area is invalid and we can avoid it, so queue an antiexpose.
* If we flushed we have already started drawing to the window, so it would
* be to late to anti-expose now. Since this is merely an
* optimization we just avoid doing it at all in that case.
*/
if (window->implicit_paint != NULL && !((GdkWindowPaint *)window->implicit_paint->data)->flushed)
save_region = impl_class->queue_antiexpose (window, update_area);
gdk_window_end_implicit_paint (window);
}
cairo_region_destroy (expose_region);
}
if (!save_region)
cairo_region_destroy (update_area);
}
window->in_update = FALSE;
g_object_unref (window);
}
@ -3926,7 +3560,7 @@ gdk_window_process_updates_with_mode (GdkWindow *window,
/* Don't recurse into process_updates_internal, we'll
* do the update later when idle instead. */
impl_window->implicit_paint == NULL)
!impl_window->in_update)
{
gdk_window_process_updates_internal ((GdkWindow *)impl_window);
gdk_window_remove_update_window ((GdkWindow *)impl_window);