Merge branch 'texture-diff-against-distant-ancestors' into 'main'

texture: Enable texture diff against arbitrarily distant ancestors

See merge request GNOME/gtk!6644
This commit is contained in:
Benjamin Otte
2023-12-12 01:46:05 +00:00
3 changed files with 170 additions and 42 deletions

View File

@@ -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 *

View File

@@ -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 {

View File

@@ -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