From 1cae48ab93835eb6140b11544aedcb0d1cd8bb7f Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 12 Jul 2024 10:56:27 +0200 Subject: [PATCH 1/7] gpu: Add a tile cache Nobody is using it yet, but it's the API. It's very simple and just allows adding tiles by an index. What that index means is up to the caller. --- gsk/gpu/gskgpucache.c | 174 +++++++++++++++++++++++++++++++++++ gsk/gpu/gskgpucacheprivate.h | 9 ++ 2 files changed, 183 insertions(+) diff --git a/gsk/gpu/gskgpucache.c b/gsk/gpu/gskgpucache.c index 4b1995ae0b..0cebeafe57 100644 --- a/gsk/gpu/gskgpucache.c +++ b/gsk/gpu/gskgpucache.c @@ -32,6 +32,7 @@ typedef struct _GskGpuCachedClass GskGpuCachedClass; typedef struct _GskGpuCachedAtlas GskGpuCachedAtlas; typedef struct _GskGpuCachedGlyph GskGpuCachedGlyph; typedef struct _GskGpuCachedTexture GskGpuCachedTexture; +typedef struct _GskGpuCachedTile GskGpuCachedTile; struct _GskGpuCache { @@ -44,6 +45,7 @@ struct _GskGpuCache GHashTable *texture_cache; GHashTable *ccs_texture_caches[GDK_COLOR_STATE_N_IDS]; + GHashTable *tile_cache; GHashTable *glyph_cache; GskGpuCachedAtlas *current_atlas; @@ -398,6 +400,177 @@ gsk_gpu_cached_texture_new (GskGpuCache *cache, return self; } +/* }}} */ +/* {{{ CachedTile */ + +struct _GskGpuCachedTile +{ + GskGpuCached parent; + + GdkTexture *texture; + gsize tile_id; + + /* atomic */ int use_count; /* We count the use by the cache (via the linked + * list) and by the texture (via weak ref) + */ + + gsize *dead_pixels_counter; + + GskGpuImage *image; +}; + +static void +gsk_gpu_cached_tile_free (GskGpuCache *cache, + GskGpuCached *cached) +{ + GskGpuCachedTile *self = (GskGpuCachedTile *) cached; + gpointer key, value; + + g_clear_object (&self->image); + + if (g_hash_table_steal_extended (cache->tile_cache, self, &key, &value)) + { + /* If the texture has been reused already, we put the entry back */ + if ((GskGpuCached *) value != cached) + g_hash_table_insert (cache->tile_cache, key, value); + } + + /* If the cached item itself is still in use by the texture, we leave + * it to the weak ref or render data to free it. + */ + if (g_atomic_int_dec_and_test (&self->use_count)) + { + g_free (self); + return; + } +} + +static inline gboolean +gsk_gpu_cached_tile_is_invalid (GskGpuCachedTile *self) +{ + /* If the use count is less than 2, the orignal texture has died, + * and the memory may have been reused for a new texture, so we + * can't hand out the image that is for the original texture. + */ + return g_atomic_int_get (&self->use_count) < 2; +} + +static gboolean +gsk_gpu_cached_tile_should_collect (GskGpuCache *cache, + GskGpuCached *cached, + gint64 cache_timeout, + gint64 timestamp) +{ + GskGpuCachedTile *self = (GskGpuCachedTile *) cached; + + return gsk_gpu_cached_is_old (cache, cached, cache_timeout, timestamp) || + gsk_gpu_cached_tile_is_invalid (self); +} + +static const GskGpuCachedClass GSK_GPU_CACHED_TILE_CLASS = +{ + sizeof (GskGpuCachedTile), + gsk_gpu_cached_tile_free, + gsk_gpu_cached_tile_should_collect +}; + +/* Note: this function can run in an arbitrary thread, so it can + * only access things atomically + */ +static void +gsk_gpu_cached_tile_destroy_cb (gpointer data) +{ + GskGpuCachedTile *self = data; + + if (!gsk_gpu_cached_tile_is_invalid (self)) + g_atomic_pointer_add (self->dead_pixels_counter, ((GskGpuCached *) self)->pixels); + + if (g_atomic_int_dec_and_test (&self->use_count)) + g_free (self); +} + +static guint +gsk_gpu_cached_tile_hash (gconstpointer data) +{ + const GskGpuCachedTile *self = data; + + return g_direct_hash (self->texture) ^ self->tile_id; +} + +static gboolean +gsk_gpu_cached_tile_equal (gconstpointer data_a, + gconstpointer data_b) +{ + const GskGpuCachedTile *a = data_a; + const GskGpuCachedTile *b = data_b; + + return a->texture == b->texture && + a->tile_id == b->tile_id; +} + +static GskGpuCachedTile * +gsk_gpu_cached_tile_new (GskGpuCache *cache, + GdkTexture *texture, + guint tile_id, + GskGpuImage *image) +{ + GskGpuCachedTile *self; + + self = gsk_gpu_cached_new (cache, &GSK_GPU_CACHED_TILE_CLASS, NULL); + self->texture = texture; + self->tile_id = tile_id; + self->image = g_object_ref (image); + ((GskGpuCached *)self)->pixels = gsk_gpu_image_get_width (image) * gsk_gpu_image_get_height (image); + self->dead_pixels_counter = &cache->dead_texture_pixels; + self->use_count = 2; + + g_object_weak_ref (G_OBJECT (texture), (GWeakNotify) gsk_gpu_cached_tile_destroy_cb, self); + if (cache->tile_cache == NULL) + cache->tile_cache = g_hash_table_new (gsk_gpu_cached_tile_hash, + gsk_gpu_cached_tile_equal); + g_hash_table_add (cache->tile_cache, self); + + return self; +} + +GskGpuImage * +gsk_gpu_cache_lookup_tile (GskGpuCache *self, + GdkTexture *texture, + gsize tile_id, + gint64 timestamp) +{ + GskGpuCachedTile *tile; + GskGpuCachedTile lookup = { + .texture = texture, + .tile_id = tile_id + }; + + if (self->tile_cache == NULL) + return NULL; + + tile = g_hash_table_lookup (self->tile_cache, &lookup); + if (tile == NULL) + return NULL; + + gsk_gpu_cached_use (self, (GskGpuCached *) tile, timestamp); + + return g_object_ref (tile->image); +} + +void +gsk_gpu_cache_cache_tile (GskGpuCache *self, + gint64 timestamp, + GdkTexture *texture, + guint tile_id, + GskGpuImage *image) +{ + GskGpuCachedTile *tile; + + tile = gsk_gpu_cached_tile_new (self, texture, tile_id, image); + + gsk_gpu_cached_use (self, (GskGpuCached *) tile, timestamp); +} + /* }}} */ /* {{{ CachedGlyph */ @@ -600,6 +773,7 @@ gsk_gpu_cache_dispose (GObject *object) gsk_gpu_cache_clear_cache (self); g_hash_table_unref (self->glyph_cache); + g_clear_pointer (&self->tile_cache, g_hash_table_unref); g_hash_table_unref (self->texture_cache); G_OBJECT_CLASS (gsk_gpu_cache_parent_class)->dispose (object); diff --git a/gsk/gpu/gskgpucacheprivate.h b/gsk/gpu/gskgpucacheprivate.h index 7d44dd46ea..b5dcc85805 100644 --- a/gsk/gpu/gskgpucacheprivate.h +++ b/gsk/gpu/gskgpucacheprivate.h @@ -39,6 +39,15 @@ void gsk_gpu_cache_cache_texture_image (GskGpuC gint64 timestamp, GskGpuImage *image, GdkColorState *color_state); +GskGpuImage * gsk_gpu_cache_lookup_tile (GskGpuCache *self, + GdkTexture *texture, + gsize tile_id, + gint64 timestamp); +void gsk_gpu_cache_cache_tile (GskGpuCache *self, + gint64 timestamp, + GdkTexture *texture, + guint tile_id, + GskGpuImage *image); typedef enum { From 392f6855ca8b54f890e04a0e2d4d8c8003f7a786 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 12 Jul 2024 11:30:06 +0200 Subject: [PATCH 2/7] gpu: Add gsk_gpu_device_get_tile_size() This allows managing tiling of images. And I'd like this value to live somewhere prominent instead of as a hardcoded number in the nodeprocessor. --- gsk/gpu/gskgldevice.c | 5 ++++- gsk/gpu/gskgpudevice.c | 37 ++++++++++++++++++++++++++++++++++- gsk/gpu/gskgpudeviceprivate.h | 6 +++++- gsk/gpu/gskvulkandevice.c | 3 ++- 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/gsk/gpu/gskgldevice.c b/gsk/gpu/gskgldevice.c index 42813bb56c..e67b30618a 100644 --- a/gsk/gpu/gskgldevice.c +++ b/gsk/gpu/gskgldevice.c @@ -260,7 +260,10 @@ gsk_gl_device_get_for_display (GdkDisplay *display, gdk_gl_context_make_current (context); glGetIntegerv (GL_MAX_TEXTURE_SIZE, &max_texture_size); - gsk_gpu_device_setup (GSK_GPU_DEVICE (self), display, max_texture_size); + gsk_gpu_device_setup (GSK_GPU_DEVICE (self), + display, + max_texture_size, + GSK_GPU_DEVICE_DEFAULT_TILE_SIZE); self->version_string = gdk_gl_context_get_glsl_version_string (context); self->api = gdk_gl_context_get_api (context); diff --git a/gsk/gpu/gskgpudevice.c b/gsk/gpu/gskgpudevice.c index 61c1c653cf..faaecc47f1 100644 --- a/gsk/gpu/gskgpudevice.c +++ b/gsk/gpu/gskgpudevice.c @@ -22,6 +22,7 @@ struct _GskGpuDevicePrivate { GdkDisplay *display; gsize max_image_size; + gsize tile_size; GskGpuCache *cache; /* we don't own a ref, but manage the cache */ guint cache_gc_source; @@ -142,13 +143,15 @@ gsk_gpu_device_init (GskGpuDevice *self) void gsk_gpu_device_setup (GskGpuDevice *self, GdkDisplay *display, - gsize max_image_size) + gsize max_image_size, + gsize tile_size) { GskGpuDevicePrivate *priv = gsk_gpu_device_get_instance_private (self); const char *str; priv->display = g_object_ref (display); priv->max_image_size = max_image_size; + priv->tile_size = tile_size; priv->cache_timeout = CACHE_TIMEOUT; str = g_getenv ("GSK_CACHE_TIMEOUT"); @@ -200,6 +203,19 @@ gsk_gpu_device_get_cache (GskGpuDevice *self) return priv->cache; } +/* + * gsk_gpu_device_get_max_image_size: + * @self: a device + * + * Returns the max image size supported by this device. + * + * This maps to GL_MAX_TEXTURE_SIZE on GL, but Vulkan is more flexible with + * per-format size limits, so this is an estimate and code should still handle + * failures of image creation at smaller sizes. (Besides handling them anyway + * in case of OOM.) + * + * Returns: The maximum size in pixels for creating a GskGpuImage + **/ gsize gsk_gpu_device_get_max_image_size (GskGpuDevice *self) { @@ -208,6 +224,25 @@ gsk_gpu_device_get_max_image_size (GskGpuDevice *self) return priv->max_image_size; } +/* + * gsk_gpu_device_get_tile_size: + * @self: a device + * + * The suggested size for tiling images. This value will be small enough so that + * image creation never fails due to size constraints. It should also not be too + * large to allow efficient caching of tiles and evictions of unused tiles + * (think of an image editor showing only a section of a large image). + * + * Returns: The suggested size of tiles when tiling images. + **/ +gsize +gsk_gpu_device_get_tile_size (GskGpuDevice *self) +{ + GskGpuDevicePrivate *priv = gsk_gpu_device_get_instance_private (self); + + return priv->tile_size; +} + GskGpuImage * gsk_gpu_device_create_offscreen_image (GskGpuDevice *self, gboolean with_mipmap, diff --git a/gsk/gpu/gskgpudeviceprivate.h b/gsk/gpu/gskgpudeviceprivate.h index be54efcd72..90b590ec05 100644 --- a/gsk/gpu/gskgpudeviceprivate.h +++ b/gsk/gpu/gskgpudeviceprivate.h @@ -6,6 +6,8 @@ G_BEGIN_DECLS +#define GSK_GPU_DEVICE_DEFAULT_TILE_SIZE 1024 + #define GSK_TYPE_GPU_DEVICE (gsk_gpu_device_get_type ()) #define GSK_GPU_DEVICE(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), GSK_TYPE_GPU_DEVICE, GskGpuDevice)) #define GSK_GPU_DEVICE_CLASS(k) (G_TYPE_CHECK_CLASS_CAST ((k), GSK_TYPE_GPU_DEVICE, GskGpuDeviceClass)) @@ -50,12 +52,14 @@ GType gsk_gpu_device_get_type (void) G void gsk_gpu_device_setup (GskGpuDevice *self, GdkDisplay *display, - gsize max_image_size); + gsize max_image_size, + gsize tile_size); void gsk_gpu_device_maybe_gc (GskGpuDevice *self); void gsk_gpu_device_queue_gc (GskGpuDevice *self); GdkDisplay * gsk_gpu_device_get_display (GskGpuDevice *self); GskGpuCache * gsk_gpu_device_get_cache (GskGpuDevice *self); gsize gsk_gpu_device_get_max_image_size (GskGpuDevice *self); +gsize gsk_gpu_device_get_tile_size (GskGpuDevice *self); GskGpuImage * gsk_gpu_device_create_offscreen_image (GskGpuDevice *self, gboolean with_mipmap, diff --git a/gsk/gpu/gskvulkandevice.c b/gsk/gpu/gskvulkandevice.c index da207cde50..80f53a94f2 100644 --- a/gsk/gpu/gskvulkandevice.c +++ b/gsk/gpu/gskvulkandevice.c @@ -575,7 +575,8 @@ gsk_vulkan_device_setup (GskVulkanDevice *self, self->max_immutable_samplers = MIN (self->max_samplers / 3, 32); gsk_gpu_device_setup (GSK_GPU_DEVICE (self), display, - vk_props.properties.limits.maxImageDimension2D); + vk_props.properties.limits.maxImageDimension2D, + GSK_GPU_DEVICE_DEFAULT_TILE_SIZE); } GskGpuDevice * From 39f5c5bf499bf8ea361ae71859ef85242bb1786f Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 12 Jul 2024 11:32:58 +0200 Subject: [PATCH 3/7] gpu: Implement tiling for texture nodes Use the new cache feature to split oversized textures into tiles the size given by the new device API. Then number those tiles from left to right and top to bottom and use that number as the tile id. --- gsk/gpu/gskgpunodeprocessor.c | 142 ++++++++++++++++++++++++++++++++-- 1 file changed, 135 insertions(+), 7 deletions(-) diff --git a/gsk/gpu/gskgpunodeprocessor.c b/gsk/gpu/gskgpunodeprocessor.c index 2d420a1a25..ef6482999b 100644 --- a/gsk/gpu/gskgpunodeprocessor.c +++ b/gsk/gpu/gskgpunodeprocessor.c @@ -43,8 +43,10 @@ #include "gskprivate.h" #include "gdk/gdkcolorstateprivate.h" +#include "gdk/gdkmemorytextureprivate.h" #include "gdk/gdkrgbaprivate.h" #include "gdk/gdksubsurfaceprivate.h" +#include "gdk/gdktextureprivate.h" /* the epsilon we allow pixels to be off due to rounding errors. * Chosen rather randomly. @@ -1725,6 +1727,110 @@ gsk_gpu_lookup_texture (GskGpuFrame *frame, return image; } +static GskGpuImage * +gsk_gpu_get_texture_tiles_as_image (GskGpuFrame *frame, + GdkColorState *ccs, + const graphene_rect_t *clip_bounds, + const graphene_vec2_t *scale, + const graphene_rect_t *texture_bounds, + GdkTexture *texture) +{ + GskGpuNodeProcessor self; + GskGpuCache *cache; + GskGpuDevice *device; + gint64 timestamp; + GskGpuImage *image, *tile; + GdkColorState *tile_cs; + GdkMemoryTexture *memtex; + GdkTexture *subtex; + float scaled_tile_width, scaled_tile_height; + gsize tile_size, width, height, n_width, n_height, x, y; + + device = gsk_gpu_frame_get_device (frame); + cache = gsk_gpu_device_get_cache (device); + timestamp = gsk_gpu_frame_get_timestamp (frame); + tile_size = gsk_gpu_device_get_tile_size (device); + width = gdk_texture_get_width (texture); + height = gdk_texture_get_height (texture); + n_width = (width + tile_size - 1) / tile_size; + n_height = (height + tile_size - 1) / tile_size; + scaled_tile_width = texture_bounds->size.width * tile_size / width; + scaled_tile_height = texture_bounds->size.height * tile_size / height; + + image = gsk_gpu_node_processor_init_draw (&self, + frame, + ccs, + gdk_texture_get_depth (texture), + scale, + clip_bounds); + if (image == NULL) + return NULL; + + self.blend = GSK_GPU_BLEND_ADD; + self.pending_globals |= GSK_GPU_GLOBAL_BLEND; + gsk_gpu_node_processor_sync_globals (&self, 0); + + memtex = NULL; + for (y = 0; y < n_height; y++) + { + for (x = 0; x < n_width; x++) + { + graphene_rect_t tile_rect = GRAPHENE_RECT_INIT (texture_bounds->origin.x + scaled_tile_width * x, + texture_bounds->origin.y + scaled_tile_height * y, + scaled_tile_width, + scaled_tile_height); + if (!gsk_rect_intersection (&tile_rect, texture_bounds, &tile_rect) || + !gsk_rect_intersects (clip_bounds, &tile_rect)) + continue; + + tile = gsk_gpu_cache_lookup_tile (cache, texture, y * n_width + x, timestamp); + if (tile == NULL) + { + if (memtex == NULL) + memtex = gdk_memory_texture_from_texture (texture); + subtex = gdk_memory_texture_new_subtexture (memtex, + x * tile_size, + y * tile_size, + MIN (tile_size, width - x * tile_size), + MIN (tile_size, height - y * tile_size)); + tile = gsk_gpu_upload_texture_op_try (self.frame, FALSE, subtex); + g_object_unref (subtex); + if (tile == NULL) + { + g_warning ("failed to create %zux%zu tile for %zux%zu texture. Out of memory?", + tile_size, tile_size, width, height); + goto out; + } + + gsk_gpu_cache_cache_tile (cache, timestamp, texture, y * n_width + x, tile); + } + + tile_cs = gdk_texture_get_color_state (texture); + if (gsk_gpu_image_get_flags (tile) & GSK_GPU_IMAGE_SRGB) + { + tile_cs = gdk_color_state_get_no_srgb_tf (tile_cs); + g_assert (tile_cs); + } + + gsk_gpu_node_processor_image_op (&self, + tile, + tile_cs, + GSK_GPU_SAMPLER_DEFAULT, + &tile_rect, + &tile_rect); + + g_object_unref (tile); + } + } + +out: + gsk_gpu_node_processor_finish_draw (&self, image); + + g_clear_object (&memtex); + + return image; +} + static void gsk_gpu_node_processor_add_texture_node (GskGpuNodeProcessor *self, GskRenderNode *node) @@ -1741,11 +1847,25 @@ gsk_gpu_node_processor_add_texture_node (GskGpuNodeProcessor *self, if (image == NULL) { - GSK_DEBUG (FALLBACK, "Unsupported texture format %u for size %dx%d", - gdk_texture_get_format (texture), - gdk_texture_get_width (texture), - gdk_texture_get_height (texture)); - gsk_gpu_node_processor_add_cairo_node (self, node); + graphene_rect_t clip, rounded_clip; + + if (!gsk_gpu_node_processor_clip_node_bounds (self, node, &clip)) + return; + rect_round_to_pixels (&clip, &self->scale, &self->offset, &rounded_clip); + + image = gsk_gpu_get_texture_tiles_as_image (self->frame, + self->ccs, + &rounded_clip, + &self->scale, + &node->bounds, + texture); + gsk_gpu_node_processor_image_op (self, + image, + self->ccs, + GSK_GPU_SAMPLER_DEFAULT, + &clip, + &rounded_clip); + g_object_unref (image); return; } @@ -1803,9 +1923,17 @@ gsk_gpu_get_texture_node_as_image (GskGpuFrame *frame, image = gsk_gpu_lookup_texture (frame, ccs, texture, FALSE, &image_cs); - /* Happens ie for oversized textures */ if (image == NULL) - return gsk_gpu_get_node_as_image_via_offscreen (frame, ccs, clip_bounds, scale, node, out_bounds); + { + image = gsk_gpu_get_texture_tiles_as_image (frame, + ccs, + clip_bounds, + scale, + &node->bounds, + gsk_texture_node_get_texture (node)); + *out_bounds = *clip_bounds; + return image; + } if (!gdk_color_state_equal (ccs, image_cs) || gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_STRAIGHT_ALPHA) From 340c98c6cd4e7124b0a81933a593d2ca6f810820 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 12 Jul 2024 14:20:12 +0200 Subject: [PATCH 4/7] gpu: Split a function Split drawing the tiles from setting up the offscreen for drawing the tiles. --- gsk/gpu/gskgpunodeprocessor.c | 73 +++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/gsk/gpu/gskgpunodeprocessor.c b/gsk/gpu/gskgpunodeprocessor.c index ef6482999b..01c53ef4b9 100644 --- a/gsk/gpu/gskgpunodeprocessor.c +++ b/gsk/gpu/gskgpunodeprocessor.c @@ -1727,28 +1727,27 @@ gsk_gpu_lookup_texture (GskGpuFrame *frame, return image; } -static GskGpuImage * -gsk_gpu_get_texture_tiles_as_image (GskGpuFrame *frame, - GdkColorState *ccs, - const graphene_rect_t *clip_bounds, - const graphene_vec2_t *scale, - const graphene_rect_t *texture_bounds, - GdkTexture *texture) +/* must be set up with BLEND_ADD to avoid seams */ +static void +gsk_gpu_node_processor_draw_texture_tiles (GskGpuNodeProcessor *self, + const graphene_rect_t *texture_bounds, + GdkTexture *texture) { - GskGpuNodeProcessor self; GskGpuCache *cache; GskGpuDevice *device; gint64 timestamp; - GskGpuImage *image, *tile; + GskGpuImage *tile; GdkColorState *tile_cs; GdkMemoryTexture *memtex; GdkTexture *subtex; float scaled_tile_width, scaled_tile_height; gsize tile_size, width, height, n_width, n_height, x, y; + graphene_rect_t clip_bounds; - device = gsk_gpu_frame_get_device (frame); + device = gsk_gpu_frame_get_device (self->frame); cache = gsk_gpu_device_get_cache (device); - timestamp = gsk_gpu_frame_get_timestamp (frame); + timestamp = gsk_gpu_frame_get_timestamp (self->frame); + gsk_gpu_node_processor_get_clip_bounds (self, &clip_bounds); tile_size = gsk_gpu_device_get_tile_size (device); width = gdk_texture_get_width (texture); height = gdk_texture_get_height (texture); @@ -1757,19 +1756,6 @@ gsk_gpu_get_texture_tiles_as_image (GskGpuFrame *frame, scaled_tile_width = texture_bounds->size.width * tile_size / width; scaled_tile_height = texture_bounds->size.height * tile_size / height; - image = gsk_gpu_node_processor_init_draw (&self, - frame, - ccs, - gdk_texture_get_depth (texture), - scale, - clip_bounds); - if (image == NULL) - return NULL; - - self.blend = GSK_GPU_BLEND_ADD; - self.pending_globals |= GSK_GPU_GLOBAL_BLEND; - gsk_gpu_node_processor_sync_globals (&self, 0); - memtex = NULL; for (y = 0; y < n_height; y++) { @@ -1780,7 +1766,7 @@ gsk_gpu_get_texture_tiles_as_image (GskGpuFrame *frame, scaled_tile_width, scaled_tile_height); if (!gsk_rect_intersection (&tile_rect, texture_bounds, &tile_rect) || - !gsk_rect_intersects (clip_bounds, &tile_rect)) + !gsk_rect_intersects (&clip_bounds, &tile_rect)) continue; tile = gsk_gpu_cache_lookup_tile (cache, texture, y * n_width + x, timestamp); @@ -1793,7 +1779,7 @@ gsk_gpu_get_texture_tiles_as_image (GskGpuFrame *frame, y * tile_size, MIN (tile_size, width - x * tile_size), MIN (tile_size, height - y * tile_size)); - tile = gsk_gpu_upload_texture_op_try (self.frame, FALSE, subtex); + tile = gsk_gpu_upload_texture_op_try (self->frame, FALSE, subtex); g_object_unref (subtex); if (tile == NULL) { @@ -1812,7 +1798,7 @@ gsk_gpu_get_texture_tiles_as_image (GskGpuFrame *frame, g_assert (tile_cs); } - gsk_gpu_node_processor_image_op (&self, + gsk_gpu_node_processor_image_op (self, tile, tile_cs, GSK_GPU_SAMPLER_DEFAULT, @@ -1824,9 +1810,38 @@ gsk_gpu_get_texture_tiles_as_image (GskGpuFrame *frame, } out: - gsk_gpu_node_processor_finish_draw (&self, image); - g_clear_object (&memtex); +} + +static GskGpuImage * +gsk_gpu_get_texture_tiles_as_image (GskGpuFrame *frame, + GdkColorState *ccs, + const graphene_rect_t *clip_bounds, + const graphene_vec2_t *scale, + const graphene_rect_t *texture_bounds, + GdkTexture *texture) +{ + GskGpuNodeProcessor self; + GskGpuImage *image; + + image = gsk_gpu_node_processor_init_draw (&self, + frame, + ccs, + gdk_texture_get_depth (texture), + scale, + clip_bounds); + if (image == NULL) + return NULL; + + self.blend = GSK_GPU_BLEND_ADD; + self.pending_globals |= GSK_GPU_GLOBAL_BLEND; + gsk_gpu_node_processor_sync_globals (&self, 0); + + gsk_gpu_node_processor_draw_texture_tiles (&self, + texture_bounds, + texture); + + gsk_gpu_node_processor_finish_draw (&self, image); return image; } From c581f722bdd30e54b0c44861b06bc3c75b0dfce2 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 12 Jul 2024 16:35:01 +0200 Subject: [PATCH 5/7] gpu: Split out a function We'll need mapping scaling filters to samplers elsewhere soon. --- gsk/gpu/gskgpunodeprocessor.c | 39 +++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/gsk/gpu/gskgpunodeprocessor.c b/gsk/gpu/gskgpunodeprocessor.c index 01c53ef4b9..2f0dfa284d 100644 --- a/gsk/gpu/gskgpunodeprocessor.c +++ b/gsk/gpu/gskgpunodeprocessor.c @@ -1727,6 +1727,26 @@ gsk_gpu_lookup_texture (GskGpuFrame *frame, return image; } +static GskGpuSampler +gsk_gpu_sampler_for_scaling_filter (GskScalingFilter scaling_filter) +{ + switch (scaling_filter) + { + case GSK_SCALING_FILTER_LINEAR: + return GSK_GPU_SAMPLER_DEFAULT; + + case GSK_SCALING_FILTER_NEAREST: + return GSK_GPU_SAMPLER_NEAREST; + + case GSK_SCALING_FILTER_TRILINEAR: + return GSK_GPU_SAMPLER_MIPMAP_DEFAULT; + + default: + g_assert_not_reached (); + return GSK_GPU_SAMPLER_DEFAULT; + } +} + /* must be set up with BLEND_ADD to avoid seams */ static void gsk_gpu_node_processor_draw_texture_tiles (GskGpuNodeProcessor *self, @@ -2045,24 +2065,7 @@ gsk_gpu_node_processor_add_texture_scale_node (GskGpuNodeProcessor *self, if (need_mipmap && !(gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_MIPMAP)) gsk_gpu_mipmap_op (self->frame, image); - switch (scaling_filter) - { - case GSK_SCALING_FILTER_LINEAR: - descriptor = gsk_gpu_node_processor_add_image (self, image, GSK_GPU_SAMPLER_DEFAULT); - break; - - case GSK_SCALING_FILTER_NEAREST: - descriptor = gsk_gpu_node_processor_add_image (self, image, GSK_GPU_SAMPLER_NEAREST); - break; - - case GSK_SCALING_FILTER_TRILINEAR: - descriptor = gsk_gpu_node_processor_add_image (self, image, GSK_GPU_SAMPLER_MIPMAP_DEFAULT); - break; - - default: - g_assert_not_reached (); - return; - } + descriptor = gsk_gpu_node_processor_add_image (self, image, gsk_gpu_sampler_for_scaling_filter (scaling_filter)); gsk_gpu_texture_op (self->frame, gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds), From cdb2308ddd375031bf3f5d90d0a52ea111d31afc Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 12 Jul 2024 16:38:56 +0200 Subject: [PATCH 6/7] gpu: Add filter support to tiled images This allows mipmapping if downscaled a lot, like we do for non-tiled images. A side effect is that due to the simpler caching for tiles, we can only cache the mipmapped images in one colorstate. But we need to pick a potentially non-default one, because we want to mipmap in a linear colorstate. So this is somewhat suboptimal. Patches with improvements accepted. --- gsk/gpu/gskgpucache.c | 30 ++++++++++++------- gsk/gpu/gskgpucacheprivate.h | 6 ++-- gsk/gpu/gskgpunodeprocessor.c | 54 ++++++++++++++++++++++++----------- 3 files changed, 61 insertions(+), 29 deletions(-) diff --git a/gsk/gpu/gskgpucache.c b/gsk/gpu/gskgpucache.c index 0cebeafe57..3e65f44fa3 100644 --- a/gsk/gpu/gskgpucache.c +++ b/gsk/gpu/gskgpucache.c @@ -417,6 +417,7 @@ struct _GskGpuCachedTile gsize *dead_pixels_counter; GskGpuImage *image; + GdkColorState *color_state; }; static void @@ -427,6 +428,7 @@ gsk_gpu_cached_tile_free (GskGpuCache *cache, gpointer key, value; g_clear_object (&self->image); + g_clear_pointer (&self->color_state, gdk_color_state_unref); if (g_hash_table_steal_extended (cache->tile_cache, self, &key, &value)) { @@ -512,7 +514,8 @@ static GskGpuCachedTile * gsk_gpu_cached_tile_new (GskGpuCache *cache, GdkTexture *texture, guint tile_id, - GskGpuImage *image) + GskGpuImage *image, + GdkColorState *color_state) { GskGpuCachedTile *self; @@ -520,6 +523,7 @@ gsk_gpu_cached_tile_new (GskGpuCache *cache, self->texture = texture; self->tile_id = tile_id; self->image = g_object_ref (image); + self->color_state = gdk_color_state_ref (color_state); ((GskGpuCached *)self)->pixels = gsk_gpu_image_get_width (image) * gsk_gpu_image_get_height (image); self->dead_pixels_counter = &cache->dead_texture_pixels; self->use_count = 2; @@ -534,10 +538,11 @@ gsk_gpu_cached_tile_new (GskGpuCache *cache, } GskGpuImage * -gsk_gpu_cache_lookup_tile (GskGpuCache *self, - GdkTexture *texture, - gsize tile_id, - gint64 timestamp) +gsk_gpu_cache_lookup_tile (GskGpuCache *self, + GdkTexture *texture, + gsize tile_id, + gint64 timestamp, + GdkColorState **out_color_state) { GskGpuCachedTile *tile; GskGpuCachedTile lookup = { @@ -554,19 +559,22 @@ gsk_gpu_cache_lookup_tile (GskGpuCache *self, gsk_gpu_cached_use (self, (GskGpuCached *) tile, timestamp); + *out_color_state = tile->color_state; + return g_object_ref (tile->image); } void -gsk_gpu_cache_cache_tile (GskGpuCache *self, - gint64 timestamp, - GdkTexture *texture, - guint tile_id, - GskGpuImage *image) +gsk_gpu_cache_cache_tile (GskGpuCache *self, + gint64 timestamp, + GdkTexture *texture, + guint tile_id, + GskGpuImage *image, + GdkColorState *color_state) { GskGpuCachedTile *tile; - tile = gsk_gpu_cached_tile_new (self, texture, tile_id, image); + tile = gsk_gpu_cached_tile_new (self, texture, tile_id, image, color_state); gsk_gpu_cached_use (self, (GskGpuCached *) tile, timestamp); } diff --git a/gsk/gpu/gskgpucacheprivate.h b/gsk/gpu/gskgpucacheprivate.h index b5dcc85805..6f448040dc 100644 --- a/gsk/gpu/gskgpucacheprivate.h +++ b/gsk/gpu/gskgpucacheprivate.h @@ -42,12 +42,14 @@ void gsk_gpu_cache_cache_texture_image (GskGpuC GskGpuImage * gsk_gpu_cache_lookup_tile (GskGpuCache *self, GdkTexture *texture, gsize tile_id, - gint64 timestamp); + gint64 timestamp, + GdkColorState **out_color_state); void gsk_gpu_cache_cache_tile (GskGpuCache *self, gint64 timestamp, GdkTexture *texture, guint tile_id, - GskGpuImage *image); + GskGpuImage *image, + GdkColorState *color_state); typedef enum { diff --git a/gsk/gpu/gskgpunodeprocessor.c b/gsk/gpu/gskgpunodeprocessor.c index 2f0dfa284d..2907236b79 100644 --- a/gsk/gpu/gskgpunodeprocessor.c +++ b/gsk/gpu/gskgpunodeprocessor.c @@ -1751,13 +1751,16 @@ gsk_gpu_sampler_for_scaling_filter (GskScalingFilter scaling_filter) static void gsk_gpu_node_processor_draw_texture_tiles (GskGpuNodeProcessor *self, const graphene_rect_t *texture_bounds, - GdkTexture *texture) + GdkTexture *texture, + GskScalingFilter scaling_filter) { GskGpuCache *cache; GskGpuDevice *device; gint64 timestamp; GskGpuImage *tile; GdkColorState *tile_cs; + GskGpuSampler sampler; + gboolean need_mipmap; GdkMemoryTexture *memtex; GdkTexture *subtex; float scaled_tile_width, scaled_tile_height; @@ -1767,6 +1770,8 @@ gsk_gpu_node_processor_draw_texture_tiles (GskGpuNodeProcessor *self, device = gsk_gpu_frame_get_device (self->frame); cache = gsk_gpu_device_get_cache (device); timestamp = gsk_gpu_frame_get_timestamp (self->frame); + sampler = gsk_gpu_sampler_for_scaling_filter (scaling_filter); + need_mipmap = scaling_filter == GSK_SCALING_FILTER_TRILINEAR; gsk_gpu_node_processor_get_clip_bounds (self, &clip_bounds); tile_size = gsk_gpu_device_get_tile_size (device); width = gdk_texture_get_width (texture); @@ -1789,7 +1794,8 @@ gsk_gpu_node_processor_draw_texture_tiles (GskGpuNodeProcessor *self, !gsk_rect_intersects (&clip_bounds, &tile_rect)) continue; - tile = gsk_gpu_cache_lookup_tile (cache, texture, y * n_width + x, timestamp); + tile = gsk_gpu_cache_lookup_tile (cache, texture, y * n_width + x, timestamp, &tile_cs); + if (tile == NULL) { if (memtex == NULL) @@ -1799,7 +1805,7 @@ gsk_gpu_node_processor_draw_texture_tiles (GskGpuNodeProcessor *self, y * tile_size, MIN (tile_size, width - x * tile_size), MIN (tile_size, height - y * tile_size)); - tile = gsk_gpu_upload_texture_op_try (self->frame, FALSE, subtex); + tile = gsk_gpu_upload_texture_op_try (self->frame, need_mipmap, subtex); g_object_unref (subtex); if (tile == NULL) { @@ -1808,20 +1814,30 @@ gsk_gpu_node_processor_draw_texture_tiles (GskGpuNodeProcessor *self, goto out; } - gsk_gpu_cache_cache_tile (cache, timestamp, texture, y * n_width + x, tile); + tile_cs = gdk_texture_get_color_state (texture); + if (gsk_gpu_image_get_flags (tile) & GSK_GPU_IMAGE_SRGB) + { + tile_cs = gdk_color_state_get_no_srgb_tf (tile_cs); + g_assert (tile_cs); + } + + gsk_gpu_cache_cache_tile (cache, timestamp, texture, y * n_width + x, tile, tile_cs); } - tile_cs = gdk_texture_get_color_state (texture); - if (gsk_gpu_image_get_flags (tile) & GSK_GPU_IMAGE_SRGB) + if (need_mipmap && + (gsk_gpu_image_get_flags (tile) & (GSK_GPU_IMAGE_STRAIGHT_ALPHA | GSK_GPU_IMAGE_CAN_MIPMAP)) != GSK_GPU_IMAGE_CAN_MIPMAP) { - tile_cs = gdk_color_state_get_no_srgb_tf (tile_cs); - g_assert (tile_cs); + tile = gsk_gpu_copy_image (self->frame, self->ccs, tile, tile_cs, TRUE); + tile_cs = self->ccs; + gsk_gpu_cache_cache_tile (cache, timestamp, texture, y * n_width + x, tile, tile_cs); } + if (need_mipmap && !(gsk_gpu_image_get_flags (tile) & GSK_GPU_IMAGE_MIPMAP)) + gsk_gpu_mipmap_op (self->frame, tile); gsk_gpu_node_processor_image_op (self, tile, tile_cs, - GSK_GPU_SAMPLER_DEFAULT, + sampler, &tile_rect, &tile_rect); @@ -1839,7 +1855,8 @@ gsk_gpu_get_texture_tiles_as_image (GskGpuFrame *frame, const graphene_rect_t *clip_bounds, const graphene_vec2_t *scale, const graphene_rect_t *texture_bounds, - GdkTexture *texture) + GdkTexture *texture, + GskScalingFilter scaling_filter) { GskGpuNodeProcessor self; GskGpuImage *image; @@ -1859,7 +1876,8 @@ gsk_gpu_get_texture_tiles_as_image (GskGpuFrame *frame, gsk_gpu_node_processor_draw_texture_tiles (&self, texture_bounds, - texture); + texture, + scaling_filter); gsk_gpu_node_processor_finish_draw (&self, image); @@ -1893,7 +1911,8 @@ gsk_gpu_node_processor_add_texture_node (GskGpuNodeProcessor *self, &rounded_clip, &self->scale, &node->bounds, - texture); + texture, + should_mipmap ? GSK_SCALING_FILTER_TRILINEAR : GSK_SCALING_FILTER_LINEAR); gsk_gpu_node_processor_image_op (self, image, self->ccs, @@ -1952,10 +1971,9 @@ gsk_gpu_get_texture_node_as_image (GskGpuFrame *frame, GdkTexture *texture = gsk_texture_node_get_texture (node); GdkColorState *image_cs; GskGpuImage *image; + gboolean should_mipmap; - if (texture_node_should_mipmap (node, frame, scale)) - return gsk_gpu_get_node_as_image_via_offscreen (frame, ccs, clip_bounds, scale, node, out_bounds); - + should_mipmap = texture_node_should_mipmap (node, frame, scale); image = gsk_gpu_lookup_texture (frame, ccs, texture, FALSE, &image_cs); if (image == NULL) @@ -1965,11 +1983,15 @@ gsk_gpu_get_texture_node_as_image (GskGpuFrame *frame, clip_bounds, scale, &node->bounds, - gsk_texture_node_get_texture (node)); + gsk_texture_node_get_texture (node), + should_mipmap ? GSK_SCALING_FILTER_TRILINEAR : GSK_SCALING_FILTER_LINEAR); *out_bounds = *clip_bounds; return image; } + if (should_mipmap) + return gsk_gpu_get_node_as_image_via_offscreen (frame, ccs, clip_bounds, scale, node, out_bounds); + if (!gdk_color_state_equal (ccs, image_cs) || gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_STRAIGHT_ALPHA) { From 1c1b78aa1ce33e5fd7db5a328d9c5be4c3925f37 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 12 Jul 2024 16:39:34 +0200 Subject: [PATCH 7/7] gpu: Implement tiling for texture-scale nodes This is actually the node Loupe is using, so having tiling work with it is important. Because of the previous commit, different filters are supported fine. Fixes: #6324 --- gsk/gpu/gskgpunodeprocessor.c | 46 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/gsk/gpu/gskgpunodeprocessor.c b/gsk/gpu/gskgpunodeprocessor.c index 2907236b79..1d5a9be21d 100644 --- a/gsk/gpu/gskgpunodeprocessor.c +++ b/gsk/gpu/gskgpunodeprocessor.c @@ -2018,8 +2018,15 @@ gsk_gpu_node_processor_add_texture_scale_node (GskGpuNodeProcessor *self, guint32 descriptor; gboolean need_mipmap, need_offscreen; - need_offscreen = self->modelview != NULL || - !graphene_vec2_equal (&self->scale, graphene_vec2_one ()); + texture = gsk_texture_scale_node_get_texture (node); + scaling_filter = gsk_texture_scale_node_get_filter (node); + need_mipmap = scaling_filter == GSK_SCALING_FILTER_TRILINEAR; + image = gsk_gpu_lookup_texture (self->frame, self->ccs, texture, need_mipmap, &image_cs); + + need_offscreen = image == NULL || + self->modelview != NULL || + !graphene_vec2_equal (&self->scale, graphene_vec2_one ()); + if (need_offscreen) { GskGpuImage *offscreen; @@ -2038,11 +2045,20 @@ gsk_gpu_node_processor_add_texture_scale_node (GskGpuNodeProcessor *self, return; clip_bounds.size.width = ceilf (clip_bounds.size.width); clip_bounds.size.height = ceilf (clip_bounds.size.height); - offscreen = gsk_gpu_node_processor_create_offscreen (self->frame, - self->ccs, - graphene_vec2_one (), - &clip_bounds, - node); + if (image == NULL) + offscreen = gsk_gpu_get_texture_tiles_as_image (self->frame, + self->ccs, + &clip_bounds, + graphene_vec2_one (), + &node->bounds, + texture, + scaling_filter); + else + offscreen = gsk_gpu_node_processor_create_offscreen (self->frame, + self->ccs, + graphene_vec2_one (), + &clip_bounds, + node); descriptor = gsk_gpu_node_processor_add_image (self, offscreen, GSK_GPU_SAMPLER_DEFAULT); gsk_gpu_texture_op (self->frame, gsk_gpu_clip_get_shader_clip (&self->clip, &self->offset, &node->bounds), @@ -2055,22 +2071,6 @@ gsk_gpu_node_processor_add_texture_scale_node (GskGpuNodeProcessor *self, return; } - texture = gsk_texture_scale_node_get_texture (node); - scaling_filter = gsk_texture_scale_node_get_filter (node); - need_mipmap = scaling_filter == GSK_SCALING_FILTER_TRILINEAR; - - image = gsk_gpu_lookup_texture (self->frame, self->ccs, texture, need_mipmap, &image_cs); - - if (image == NULL) - { - GSK_DEBUG (FALLBACK, "Unsupported texture format %u for size %dx%d", - gdk_texture_get_format (texture), - gdk_texture_get_width (texture), - gdk_texture_get_height (texture)); - gsk_gpu_node_processor_add_cairo_node (self, node); - return; - } - if ((gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_STRAIGHT_ALPHA) || (need_mipmap && !(gsk_gpu_image_get_flags (image) & GSK_GPU_IMAGE_CAN_MIPMAP)) || !gdk_color_state_equal (image_cs, self->ccs))