diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index 01ed8dc43f..082a32f290 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -68,6 +68,33 @@ enum { static GParamSpec *properties[N_PROPS]; +static GdkTextureChain * +gdk_texture_chain_new (void) +{ + GdkTextureChain *chain = g_new0 (GdkTextureChain, 1); + + g_atomic_ref_count_init (&chain->ref_count); + g_mutex_init (&chain->lock); + + return chain; +} + +static void +gdk_texture_chain_ref (GdkTextureChain *chain) +{ + g_atomic_ref_count_inc (&chain->ref_count); +} + +static void +gdk_texture_chain_unref (GdkTextureChain *chain) +{ + if (g_atomic_ref_count_dec (&chain->ref_count)) + { + g_mutex_clear (&chain->lock); + g_free (chain); + } +} + static void gdk_texture_paintable_snapshot (GdkPaintable *paintable, GdkSnapshot *snapshot, @@ -283,7 +310,30 @@ gdk_texture_dispose (GObject *object) { GdkTexture *self = GDK_TEXTURE (object); - g_clear_pointer (&self->diff_to_previous, cairo_region_destroy); + if (self->chain) + { + g_mutex_lock (&self->chain->lock); + + if (self->next_texture && self->previous_texture) + { + cairo_region_union (self->next_texture->diff_to_previous, + self->diff_to_previous); + self->next_texture->previous_texture = self->previous_texture; + self->previous_texture->next_texture = self->next_texture; + } + else if (self->next_texture) + self->next_texture->previous_texture = NULL; + else if (self->previous_texture) + self->previous_texture->next_texture = NULL; + + self->next_texture = NULL; + self->previous_texture = NULL; + g_clear_pointer (&self->diff_to_previous, cairo_region_destroy); + + g_mutex_unlock (&self->chain->lock); + + g_clear_pointer (&self->chain, gdk_texture_chain_unref); + } gdk_texture_clear_render_data (self); @@ -679,34 +729,64 @@ gdk_texture_do_download (GdkTexture *texture, GDK_TEXTURE_GET_CLASS (texture)->download (texture, format, data, stride); } +static gboolean +gdk_texture_has_ancestor (GdkTexture *self, + GdkTexture *other) +{ + GdkTexture *texture; + + for (texture = self->previous_texture; + texture != NULL; + texture = texture->previous_texture) + { + if (texture == other) + return TRUE; + } + return FALSE; +} + +static void +gdk_texture_diff_from_known_ancestor (GdkTexture *self, + GdkTexture *ancestor, + cairo_region_t *region) +{ + GdkTexture *texture; + + for (texture = self; texture != ancestor; texture = texture->previous_texture) + cairo_region_union (region, texture->diff_to_previous); +} + void gdk_texture_diff (GdkTexture *self, GdkTexture *other, cairo_region_t *region) { - if (self == other) + cairo_rectangle_int_t fill = { + .x = 0, + .y = 0, + .width = MAX (self->width, other->width), + .height = MAX (self->height, other->height), + }; + GdkTextureChain *chain; + + if (other == self) return; - if (self->previous_texture == other && - g_atomic_pointer_get (&other->next_texture) == self) + chain = g_atomic_pointer_get (&self->chain); + if (chain == NULL || chain != g_atomic_pointer_get (&other->chain)) { - cairo_region_union (region, self->diff_to_previous); - } - else if (other->previous_texture == self && - g_atomic_pointer_get (&self->next_texture) == other) - { - cairo_region_union (region, other->diff_to_previous); + cairo_region_union_rectangle (region, &fill); + return; } + + g_mutex_lock (&chain->lock); + if (gdk_texture_has_ancestor (self, other)) + gdk_texture_diff_from_known_ancestor (self, other, region); + else if (gdk_texture_has_ancestor (other, self)) + gdk_texture_diff_from_known_ancestor (other, self, region); else - { - cairo_region_union_rectangle (region, - &(cairo_rectangle_int_t) { - 0, - 0, - MAX (self->width, other->width), - MAX (self->height, other->height) - }); - } + cairo_region_union_rectangle (region, &fill); + g_mutex_unlock (&chain->lock); } void @@ -715,10 +795,30 @@ gdk_texture_set_diff (GdkTexture *self, cairo_region_t *diff) { g_assert (self->diff_to_previous == NULL); + g_assert (self->chain == NULL); + self->chain = g_atomic_pointer_get (&previous->chain); + if (self->chain == NULL) + { + self->chain = gdk_texture_chain_new (); + if (!g_atomic_pointer_compare_and_exchange (&previous->chain, + NULL, + self->chain)) + gdk_texture_chain_unref (self->chain); + self->chain = previous->chain; + } + gdk_texture_chain_ref (self->chain); + + g_mutex_lock (&self->chain->lock); + if (previous->next_texture) + { + previous->next_texture->previous_texture = NULL; + g_clear_pointer (&previous->next_texture->diff_to_previous, cairo_region_destroy); + } + previous->next_texture = self; self->previous_texture = previous; self->diff_to_previous = diff; - g_atomic_pointer_set (&previous->next_texture, self); + g_mutex_unlock (&self->chain->lock); } cairo_surface_t * diff --git a/gdk/gdktextureprivate.h b/gdk/gdktextureprivate.h index b0fc2a8785..2a78d7415c 100644 --- a/gdk/gdktextureprivate.h +++ b/gdk/gdktextureprivate.h @@ -6,6 +6,14 @@ G_BEGIN_DECLS +typedef struct _GdkTextureChain GdkTextureChain; + +struct _GdkTextureChain +{ + gatomicrefcount ref_count; + GMutex lock; +}; + #define GDK_TEXTURE_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GDK_TYPE_TEXTURE, GdkTextureClass)) #define GDK_IS_TEXTURE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GDK_TYPE_TEXTURE)) #define GDK_TEXTURE_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GDK_TYPE_TEXTURE, GdkTextureClass)) @@ -23,10 +31,13 @@ struct _GdkTexture GDestroyNotify render_notify; /* for diffing swapchain-like textures. - * Links are only valid if both textures agree on them */ - gpointer next_texture; /* atomic, no reference, may be invalid pointer */ - gpointer previous_texture; /* no reference, may be invalid pointer */ - cairo_region_t *diff_to_previous; + * Textures in the same chain are connected in a double linked list which is + * protected using the chain's shared mutex. + */ + GdkTextureChain *chain; /* lazy, atomic, shared by all chain links */ + GdkTexture *next_texture; /* no reference, guarded by chain lock */ + GdkTexture *previous_texture; /* no reference, guarded by chain lock */ + cairo_region_t *diff_to_previous; /* guarded by chain lock */ }; struct _GdkTextureClass { diff --git a/testsuite/gdk/texture.c b/testsuite/gdk/texture.c index ddfa119aef..eeb879294e 100644 --- a/testsuite/gdk/texture.c +++ b/testsuite/gdk/texture.c @@ -3,6 +3,21 @@ #include "gdk/gdkmemorytextureprivate.h" #include "gdk/gdktextureprivate.h" + +#define assert_texture_diff_equal(a, b, expected) G_STMT_START { \ + cairo_region_t *_r; \ +\ + _r = cairo_region_create (); \ + gdk_texture_diff (a, b, _r); \ + g_assert_true (cairo_region_equal (_r, expected)); \ + cairo_region_destroy(_r); \ +\ + _r = cairo_region_create (); \ + gdk_texture_diff (b, a, _r); \ + g_assert_true (cairo_region_equal (_r, expected)); \ + cairo_region_destroy(_r); \ +}G_STMT_END + static void compare_pixels (int width, int height, @@ -312,47 +327,49 @@ test_texture_icon_serialize (void) static void test_texture_diff (void) { + GdkTexture *texture0; GdkTexture *texture; GdkTexture *texture2; + cairo_region_t *empty; cairo_region_t *full; cairo_region_t *center; - cairo_region_t *r; + cairo_region_t *left; + cairo_region_t *left_center; + texture0 = gdk_texture_new_from_resource ("/org/gtk/libgtk/icons/16x16/places/user-trash.png"); texture = gdk_texture_new_from_resource ("/org/gtk/libgtk/icons/16x16/places/user-trash.png"); texture2 = gdk_texture_new_from_resource ("/org/gtk/libgtk/icons/16x16/places/user-trash.png"); + empty = cairo_region_create(); full = cairo_region_create_rectangle (&(cairo_rectangle_int_t) { 0, 0, 16, 16 }); center = cairo_region_create_rectangle (&(cairo_rectangle_int_t) { 4, 4, 8 ,8 }); + left = cairo_region_create_rectangle (&(cairo_rectangle_int_t) { 0, 4, 4, 4 }); + left_center = cairo_region_copy (left); + cairo_region_union (left_center, center); - r = cairo_region_create (); - gdk_texture_diff (texture, texture, r); - - g_assert_true (cairo_region_is_empty (r)); - - gdk_texture_diff (texture, texture2, r); + assert_texture_diff_equal (texture, texture, empty); /* No diff set, so we get the full area */ - g_assert_true (cairo_region_equal (r, full)); - cairo_region_destroy (r); + assert_texture_diff_equal (texture, texture2, full); gdk_texture_set_diff (texture, texture2, cairo_region_copy (center)); - r = cairo_region_create (); - gdk_texture_diff (texture, texture2, r); + assert_texture_diff_equal (texture, texture2, center); - g_assert_true (cairo_region_equal (r, center)); - cairo_region_destroy (r); + gdk_texture_set_diff (texture0, texture, cairo_region_copy (left)); - r = cairo_region_create (); - gdk_texture_diff (texture2, texture, r); + assert_texture_diff_equal (texture0, texture2, left_center); - g_assert_true (cairo_region_equal (r, center)); - cairo_region_destroy (r); + g_object_unref (texture); + + assert_texture_diff_equal (texture0, texture2, left_center); cairo_region_destroy (full); cairo_region_destroy (center); - g_object_unref (texture); + cairo_region_destroy (left); + cairo_region_destroy (left_center); g_object_unref (texture2); + g_object_unref (texture0); } static void