From 9195c39756f2723ffb43d4787ab71762c3ac8c9f Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 2 Sep 2024 01:16:25 +0200 Subject: [PATCH 01/14] build: Alphabetic order is hard --- gdk/meson.build | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/gdk/meson.build b/gdk/meson.build index 78609a9870..131d0f3018 100644 --- a/gdk/meson.build +++ b/gdk/meson.build @@ -53,22 +53,22 @@ gdk_public_sources = files([ 'gdkpaintable.c', 'gdkpango.c', 'gdkpipeiostream.c', + 'gdkpopup.c', + 'gdkpopuplayout.c', + 'gdkprofiler.c', 'gdkrectangle.c', 'gdkrgba.c', 'gdkseat.c', 'gdkseatdefault.c', 'gdksnapshot.c', - 'gdktexture.c', - 'gdktexturedownloader.c', - 'gdkvulkancontext.c', 'gdksubsurface.c', 'gdksurface.c', - 'gdkpopuplayout.c', - 'gdkprofiler.c', - 'gdkpopup.c', + 'gdktexture.c', + 'gdktexturedownloader.c', 'gdktoplevellayout.c', 'gdktoplevelsize.c', 'gdktoplevel.c', + 'gdkvulkancontext.c', 'loaders/gdkpng.c', 'loaders/gdktiff.c', 'loaders/gdkjpeg.c', From a95c9ebc5177db89204e21f97b90fd87b2f8c5f3 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 27 Aug 2024 06:09:26 +0200 Subject: [PATCH 02/14] memoryformat: Split out a function Allow querying the fast conversion functions outside of gdk_memory_convert(). --- gdk/gdkmemoryformat.c | 103 ++++++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 44 deletions(-) diff --git a/gdk/gdkmemoryformat.c b/gdk/gdkmemoryformat.c index 6f513d8737..17b28872f4 100644 --- a/gdk/gdkmemoryformat.c +++ b/gdk/gdkmemoryformat.c @@ -1850,6 +1850,52 @@ unpremultiply (float (*rgba)[4], } } +typedef void (* FastConversionFunc) (guchar *, const guchar *, gsize); + +static FastConversionFunc +get_fast_conversion_func (GdkMemoryFormat dest_format, + GdkMemoryFormat src_format) +{ + if (src_format == GDK_MEMORY_R8G8B8A8 && dest_format == GDK_MEMORY_R8G8B8A8_PREMULTIPLIED) + return r8g8b8a8_to_r8g8b8a8_premultiplied; + else if (src_format == GDK_MEMORY_B8G8R8A8 && dest_format == GDK_MEMORY_R8G8B8A8_PREMULTIPLIED) + return r8g8b8a8_to_b8g8r8a8_premultiplied; + else if (src_format == GDK_MEMORY_R8G8B8A8 && dest_format == GDK_MEMORY_B8G8R8A8_PREMULTIPLIED) + return r8g8b8a8_to_b8g8r8a8_premultiplied; + else if (src_format == GDK_MEMORY_B8G8R8A8 && dest_format == GDK_MEMORY_B8G8R8A8_PREMULTIPLIED) + return r8g8b8a8_to_r8g8b8a8_premultiplied; + else if (src_format == GDK_MEMORY_R8G8B8A8 && dest_format == GDK_MEMORY_A8R8G8B8_PREMULTIPLIED) + return r8g8b8a8_to_a8r8g8b8_premultiplied; + else if (src_format == GDK_MEMORY_B8G8R8A8 && dest_format == GDK_MEMORY_A8R8G8B8_PREMULTIPLIED) + return r8g8b8a8_to_a8b8g8r8_premultiplied; + else if (src_format == GDK_MEMORY_R8G8B8 && dest_format == GDK_MEMORY_R8G8B8A8_PREMULTIPLIED) + return r8g8b8_to_r8g8b8a8; + else if (src_format == GDK_MEMORY_B8G8R8 && dest_format == GDK_MEMORY_R8G8B8A8_PREMULTIPLIED) + return r8g8b8_to_b8g8r8a8; + else if (src_format == GDK_MEMORY_R8G8B8 && dest_format == GDK_MEMORY_B8G8R8A8_PREMULTIPLIED) + return r8g8b8_to_b8g8r8a8; + else if (src_format == GDK_MEMORY_B8G8R8 && dest_format == GDK_MEMORY_B8G8R8A8_PREMULTIPLIED) + return r8g8b8_to_r8g8b8a8; + else if (src_format == GDK_MEMORY_R8G8B8 && dest_format == GDK_MEMORY_A8R8G8B8_PREMULTIPLIED) + return r8g8b8_to_a8r8g8b8; + else if (src_format == GDK_MEMORY_B8G8R8 && dest_format == GDK_MEMORY_A8R8G8B8_PREMULTIPLIED) + return r8g8b8_to_a8b8g8r8; + else if (src_format == GDK_MEMORY_R8G8B8 && dest_format == GDK_MEMORY_R8G8B8A8) + return r8g8b8_to_r8g8b8a8; + else if (src_format == GDK_MEMORY_B8G8R8 && dest_format == GDK_MEMORY_R8G8B8A8) + return r8g8b8_to_b8g8r8a8; + else if (src_format == GDK_MEMORY_R8G8B8 && dest_format == GDK_MEMORY_B8G8R8A8) + return r8g8b8_to_b8g8r8a8; + else if (src_format == GDK_MEMORY_B8G8R8 && dest_format == GDK_MEMORY_B8G8R8A8) + return r8g8b8_to_r8g8b8a8; + else if (src_format == GDK_MEMORY_R8G8B8 && dest_format == GDK_MEMORY_A8R8G8B8) + return r8g8b8_to_a8r8g8b8; + else if (src_format == GDK_MEMORY_B8G8R8 && dest_format == GDK_MEMORY_A8R8G8B8) + return r8g8b8_to_a8b8g8r8; + + return NULL; +} + void gdk_memory_convert (guchar *dest_data, gsize dest_stride, @@ -1868,7 +1914,6 @@ gdk_memory_convert (guchar *dest_data, gsize y; GdkFloatColorConvert convert_func = NULL; GdkFloatColorConvert convert_func2 = NULL; - void (*func) (guchar *, const guchar *, gsize) = NULL; gboolean needs_premultiply, needs_unpremultiply; g_assert (dest_format < GDK_MEMORY_N_FORMATS); @@ -1913,52 +1958,22 @@ gdk_memory_convert (guchar *dest_data, convert_func2 = gdk_color_state_get_convert_from (dest_cs, connection); } } - else if (src_format == GDK_MEMORY_R8G8B8A8 && dest_format == GDK_MEMORY_R8G8B8A8_PREMULTIPLIED) - func = r8g8b8a8_to_r8g8b8a8_premultiplied; - else if (src_format == GDK_MEMORY_B8G8R8A8 && dest_format == GDK_MEMORY_R8G8B8A8_PREMULTIPLIED) - func = r8g8b8a8_to_b8g8r8a8_premultiplied; - else if (src_format == GDK_MEMORY_R8G8B8A8 && dest_format == GDK_MEMORY_B8G8R8A8_PREMULTIPLIED) - func = r8g8b8a8_to_b8g8r8a8_premultiplied; - else if (src_format == GDK_MEMORY_B8G8R8A8 && dest_format == GDK_MEMORY_B8G8R8A8_PREMULTIPLIED) - func = r8g8b8a8_to_r8g8b8a8_premultiplied; - else if (src_format == GDK_MEMORY_R8G8B8A8 && dest_format == GDK_MEMORY_A8R8G8B8_PREMULTIPLIED) - func = r8g8b8a8_to_a8r8g8b8_premultiplied; - else if (src_format == GDK_MEMORY_B8G8R8A8 && dest_format == GDK_MEMORY_A8R8G8B8_PREMULTIPLIED) - func = r8g8b8a8_to_a8b8g8r8_premultiplied; - else if (src_format == GDK_MEMORY_R8G8B8 && dest_format == GDK_MEMORY_R8G8B8A8_PREMULTIPLIED) - func = r8g8b8_to_r8g8b8a8; - else if (src_format == GDK_MEMORY_B8G8R8 && dest_format == GDK_MEMORY_R8G8B8A8_PREMULTIPLIED) - func = r8g8b8_to_b8g8r8a8; - else if (src_format == GDK_MEMORY_R8G8B8 && dest_format == GDK_MEMORY_B8G8R8A8_PREMULTIPLIED) - func = r8g8b8_to_b8g8r8a8; - else if (src_format == GDK_MEMORY_B8G8R8 && dest_format == GDK_MEMORY_B8G8R8A8_PREMULTIPLIED) - func = r8g8b8_to_r8g8b8a8; - else if (src_format == GDK_MEMORY_R8G8B8 && dest_format == GDK_MEMORY_A8R8G8B8_PREMULTIPLIED) - func = r8g8b8_to_a8r8g8b8; - else if (src_format == GDK_MEMORY_B8G8R8 && dest_format == GDK_MEMORY_A8R8G8B8_PREMULTIPLIED) - func = r8g8b8_to_a8b8g8r8; - else if (src_format == GDK_MEMORY_R8G8B8 && dest_format == GDK_MEMORY_R8G8B8A8) - func = r8g8b8_to_r8g8b8a8; - else if (src_format == GDK_MEMORY_B8G8R8 && dest_format == GDK_MEMORY_R8G8B8A8) - func = r8g8b8_to_b8g8r8a8; - else if (src_format == GDK_MEMORY_R8G8B8 && dest_format == GDK_MEMORY_B8G8R8A8) - func = r8g8b8_to_b8g8r8a8; - else if (src_format == GDK_MEMORY_B8G8R8 && dest_format == GDK_MEMORY_B8G8R8A8) - func = r8g8b8_to_r8g8b8a8; - else if (src_format == GDK_MEMORY_R8G8B8 && dest_format == GDK_MEMORY_A8R8G8B8) - func = r8g8b8_to_a8r8g8b8; - else if (src_format == GDK_MEMORY_B8G8R8 && dest_format == GDK_MEMORY_A8R8G8B8) - func = r8g8b8_to_a8b8g8r8; - - if (func != NULL) + else { - for (y = 0; y < height; y++) + FastConversionFunc func; + + func = get_fast_conversion_func (dest_format, src_format); + + if (func != NULL) { - func (dest_data, src_data, width); - src_data += src_stride; - dest_data += dest_stride; + for (y = 0; y < height; y++) + { + func (dest_data, src_data, width); + src_data += src_stride; + dest_data += dest_stride; + } + return; } - return; } tmp = g_malloc (sizeof (*tmp) * width); From d4ba57fcc34aa5b4caee370733d2cf56c6a445bb Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 2 Sep 2024 02:35:37 +0200 Subject: [PATCH 03/14] gdk: Add gdk_parallel_task_run() This is just the API. Users will come later. I considered putting it into gdkmemoryformat.c because it's likely gonna be the only user and this one function is so little code, but it didn't fit at all. So now it's a new file. --- gdk/gdkparalleltask.c | 86 ++++++++++++++++++++++++++++++++++++ gdk/gdkparalleltaskprivate.h | 32 ++++++++++++++ gdk/meson.build | 1 + 3 files changed, 119 insertions(+) create mode 100644 gdk/gdkparalleltask.c create mode 100644 gdk/gdkparalleltaskprivate.h diff --git a/gdk/gdkparalleltask.c b/gdk/gdkparalleltask.c new file mode 100644 index 0000000000..4b9c49e609 --- /dev/null +++ b/gdk/gdkparalleltask.c @@ -0,0 +1,86 @@ +/* + * Copyright © 2024 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#include "config.h" + +#include "gdkparalleltaskprivate.h" + +typedef struct _TaskData TaskData; + +struct _TaskData +{ + GdkTaskFunc task_func; + gpointer task_data; + int n_running_tasks; +}; + +static void +gdk_parallel_task_thread_func (gpointer data, + gpointer unused) +{ + TaskData *task = data; + + task->task_func (task->task_data); + + g_atomic_int_add (&task->n_running_tasks, -1); +} + +/** + * gdk_parallel_task_run: + * @task_func: the function to spawn + * @task_data: data to pass to the function + * + * Spawns the given function in many threads. + * Once all functions have exited, this function returns. + **/ +void +gdk_parallel_task_run (GdkTaskFunc task_func, + gpointer task_data) +{ + static GThreadPool *pool; + TaskData task = { + .task_func = task_func, + .task_data = task_data, + }; + int i, n_tasks; + + if (g_once_init_enter (&pool)) + { + GThreadPool *the_pool = g_thread_pool_new (gdk_parallel_task_thread_func, + NULL, + MAX (2, g_get_num_processors ()) - 1, + FALSE, + NULL); + g_once_init_leave (&pool, the_pool); + } + + n_tasks = g_get_num_processors (); + task.n_running_tasks = n_tasks; + /* Start with 1 because we run 1 task ourselves */ + for (i = 1; i < n_tasks; i++) + { + g_thread_pool_push (pool, &task, NULL); + } + + gdk_parallel_task_thread_func (&task, NULL); + + while (g_atomic_int_get (&task.n_running_tasks) > 0) + g_thread_yield (); +} + diff --git a/gdk/gdkparalleltaskprivate.h b/gdk/gdkparalleltaskprivate.h new file mode 100644 index 0000000000..a20fb72f95 --- /dev/null +++ b/gdk/gdkparalleltaskprivate.h @@ -0,0 +1,32 @@ +/* + * Copyright © 2024 Benjamin Otte + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library. If not, see . + * + * Authors: Benjamin Otte + */ + +#pragma once + +#include + +G_BEGIN_DECLS + +typedef void (* GdkTaskFunc) (gpointer user_data); + +void gdk_parallel_task_run (GdkTaskFunc task_func, + gpointer task_data); + +G_END_DECLS + diff --git a/gdk/meson.build b/gdk/meson.build index 131d0f3018..b75349f291 100644 --- a/gdk/meson.build +++ b/gdk/meson.build @@ -52,6 +52,7 @@ gdk_public_sources = files([ 'gdkmonitor.c', 'gdkpaintable.c', 'gdkpango.c', + 'gdkparalleltask.c', 'gdkpipeiostream.c', 'gdkpopup.c', 'gdkpopuplayout.c', From ffe56fe6b30c9092dc8e758f8937d47f763bbb3d Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sun, 1 Sep 2024 21:39:29 +0200 Subject: [PATCH 04/14] memoryformat: Parallelize Refactor code so that it uses g_parallel_task_run(). --- gdk/gdkmemoryformat.c | 1 + 1 file changed, 1 insertion(+) diff --git a/gdk/gdkmemoryformat.c b/gdk/gdkmemoryformat.c index 17b28872f4..f81b7081fd 100644 --- a/gdk/gdkmemoryformat.c +++ b/gdk/gdkmemoryformat.c @@ -24,6 +24,7 @@ #include "gdkdmabuffourccprivate.h" #include "gdkglcontextprivate.h" #include "gdkcolorstateprivate.h" +#include "gdkparalleltaskprivate.h" #include "gtk/gtkcolorutilsprivate.h" #include "gsk/gl/fp16private.h" From eb7a42bc13acd441b9fac6404e4588e77fb2177e Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 2 Sep 2024 03:17:53 +0200 Subject: [PATCH 05/14] memoryformat: Parallelize generic path of gdk_memory_convert() --- gdk/gdkmemoryformat.c | 208 +++++++++++++++++++++++++----------------- 1 file changed, 126 insertions(+), 82 deletions(-) diff --git a/gdk/gdkmemoryformat.c b/gdk/gdkmemoryformat.c index f81b7081fd..af44825487 100644 --- a/gdk/gdkmemoryformat.c +++ b/gdk/gdkmemoryformat.c @@ -1897,88 +1897,48 @@ get_fast_conversion_func (GdkMemoryFormat dest_format, return NULL; } -void -gdk_memory_convert (guchar *dest_data, - gsize dest_stride, - GdkMemoryFormat dest_format, - GdkColorState *dest_cs, - const guchar *src_data, - gsize src_stride, - GdkMemoryFormat src_format, - GdkColorState *src_cs, - gsize width, - gsize height) +typedef struct _MemoryConvert MemoryConvert; + +struct _MemoryConvert { - const GdkMemoryFormatDescription *dest_desc = &memory_formats[dest_format]; - const GdkMemoryFormatDescription *src_desc = &memory_formats[src_format]; + guchar *dest_data; + gsize dest_stride; + GdkMemoryFormat dest_format; + GdkColorState *dest_cs; + const guchar *src_data; + gsize src_stride; + GdkMemoryFormat src_format; + GdkColorState *src_cs; + gsize width; + gsize height; + + /* atomic */ int rows_done; +}; + +static void +gdk_memory_convert_generic (gpointer data) +{ + MemoryConvert *mc = data; + const GdkMemoryFormatDescription *dest_desc = &memory_formats[mc->dest_format]; + const GdkMemoryFormatDescription *src_desc = &memory_formats[mc->src_format]; float (*tmp)[4]; - gsize y; GdkFloatColorConvert convert_func = NULL; GdkFloatColorConvert convert_func2 = NULL; gboolean needs_premultiply, needs_unpremultiply; + gsize y, n; - g_assert (dest_format < GDK_MEMORY_N_FORMATS); - g_assert (src_format < GDK_MEMORY_N_FORMATS); - /* We don't allow overlap here. If you want to do in-place color state conversions, - * use gdk_memory_convert_color_state. - */ - g_assert (dest_data + gdk_memory_format_min_buffer_size (dest_format, dest_stride, width, height) <= src_data || - src_data + gdk_memory_format_min_buffer_size (src_format, src_stride, width, height) <= dest_data); + convert_func = gdk_color_state_get_convert_to (mc->src_cs, mc->dest_cs); - if (src_format == dest_format && gdk_color_state_equal (dest_cs, src_cs)) + if (!convert_func) + convert_func2 = gdk_color_state_get_convert_from (mc->dest_cs, mc->src_cs); + + if (!convert_func && !convert_func2) { - gsize bytes_per_row = src_desc->bytes_per_pixel * width; - - if (bytes_per_row == src_stride && bytes_per_row == dest_stride) - { - memcpy (dest_data, src_data, bytes_per_row * height); - } - else - { - for (y = 0; y < height; y++) - { - memcpy (dest_data, src_data, bytes_per_row); - src_data += src_stride; - dest_data += dest_stride; - } - } - return; + GdkColorState *connection = GDK_COLOR_STATE_REC2100_LINEAR; + convert_func = gdk_color_state_get_convert_to (mc->src_cs, connection); + convert_func2 = gdk_color_state_get_convert_from (mc->dest_cs, connection); } - if (!gdk_color_state_equal (dest_cs, src_cs)) - { - convert_func = gdk_color_state_get_convert_to (src_cs, dest_cs); - - if (!convert_func) - convert_func2 = gdk_color_state_get_convert_from (dest_cs, src_cs); - - if (!convert_func && !convert_func2) - { - GdkColorState *connection = GDK_COLOR_STATE_REC2100_LINEAR; - convert_func = gdk_color_state_get_convert_to (src_cs, connection); - convert_func2 = gdk_color_state_get_convert_from (dest_cs, connection); - } - } - else - { - FastConversionFunc func; - - func = get_fast_conversion_func (dest_format, src_format); - - if (func != NULL) - { - for (y = 0; y < height; y++) - { - func (dest_data, src_data, width); - src_data += src_stride; - dest_data += dest_stride; - } - return; - } - } - - tmp = g_malloc (sizeof (*tmp) * width); - if (convert_func) { needs_unpremultiply = src_desc->alpha == GDK_MEMORY_ALPHA_PREMULTIPLIED; @@ -1990,31 +1950,115 @@ gdk_memory_convert (guchar *dest_data, needs_premultiply = src_desc->alpha == GDK_MEMORY_ALPHA_STRAIGHT && dest_desc->alpha != GDK_MEMORY_ALPHA_STRAIGHT; } - for (y = 0; y < height; y++) + tmp = g_malloc (sizeof (*tmp) * mc->width); + n = 1; + + for (y = g_atomic_int_add (&mc->rows_done, n); + y < mc->height; + y = g_atomic_int_add (&mc->rows_done, n)) { - src_desc->to_float (tmp, src_data, width); + const guchar *src_data = mc->src_data + y * mc->src_stride; + guchar *dest_data = mc->dest_data + y * mc->dest_stride; + + src_desc->to_float (tmp, src_data, mc->width); if (needs_unpremultiply) - unpremultiply (tmp, width); + unpremultiply (tmp, mc->width); if (convert_func) - convert_func (src_cs, tmp, width); + convert_func (mc->src_cs, tmp, mc->width); if (convert_func2) - convert_func2 (dest_cs, tmp, width); + convert_func2 (mc->dest_cs, tmp, mc->width); if (needs_premultiply) - premultiply (tmp, width); + premultiply (tmp, mc->width); - dest_desc->from_float (dest_data, tmp, width); - - src_data += src_stride; - dest_data += dest_stride; + dest_desc->from_float (dest_data, tmp, mc->width); } g_free (tmp); } +void +gdk_memory_convert (guchar *dest_data, + gsize dest_stride, + GdkMemoryFormat dest_format, + GdkColorState *dest_cs, + const guchar *src_data, + gsize src_stride, + GdkMemoryFormat src_format, + GdkColorState *src_cs, + gsize width, + gsize height) +{ + MemoryConvert mc = { + .dest_data = dest_data, + .dest_stride = dest_stride, + .dest_format = dest_format, + .dest_cs = dest_cs, + .src_data = src_data, + .src_stride = src_stride, + .src_format = src_format, + .src_cs = src_cs, + .width = width, + .height = height, + }; + + g_assert (dest_format < GDK_MEMORY_N_FORMATS); + g_assert (src_format < GDK_MEMORY_N_FORMATS); + /* We don't allow overlap here. If you want to do in-place color state conversions, + * use gdk_memory_convert_color_state. + */ + g_assert (dest_data + gdk_memory_format_min_buffer_size (dest_format, dest_stride, width, height) <= src_data || + src_data + gdk_memory_format_min_buffer_size (src_format, src_stride, width, height) <= dest_data); + + if (src_format == dest_format && gdk_color_state_equal (dest_cs, src_cs)) + { + const GdkMemoryFormatDescription *src_desc = &memory_formats[src_format]; + gsize bytes_per_row = src_desc->bytes_per_pixel * width; + + if (bytes_per_row == src_stride && bytes_per_row == dest_stride) + { + memcpy (dest_data, src_data, bytes_per_row * height); + } + else + { + gsize y; + + for (y = 0; y < height; y++) + { + memcpy (dest_data, src_data, bytes_per_row); + src_data += src_stride; + dest_data += dest_stride; + } + } + return; + } + + if (gdk_color_state_equal (dest_cs, src_cs)) + { + FastConversionFunc func; + + func = get_fast_conversion_func (dest_format, src_format); + + if (func != NULL) + { + gsize y; + + for (y = 0; y < height; y++) + { + func (dest_data, src_data, width); + src_data += src_stride; + dest_data += dest_stride; + } + return; + } + } + + gdk_parallel_task_run (gdk_memory_convert_generic, &mc); +} + static const guchar srgb_lookup[] = { 0, 12, 21, 28, 33, 38, 42, 46, 49, 52, 55, 58, 61, 63, 66, 68, 70, 73, 75, 77, 79, 81, 82, 84, 86, 88, 89, 91, 93, 94, 96, 97, From 46559039f3342e307aca4bebe844773b0cb0369a Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 2 Sep 2024 03:31:43 +0200 Subject: [PATCH 06/14] memoryformat: Parallelize gdk_memory_convert_color_state() --- gdk/gdkmemoryformat.c | 164 ++++++++++++++++++++++++------------------ 1 file changed, 94 insertions(+), 70 deletions(-) diff --git a/gdk/gdkmemoryformat.c b/gdk/gdkmemoryformat.c index af44825487..44780859e0 100644 --- a/gdk/gdkmemoryformat.c +++ b/gdk/gdkmemoryformat.c @@ -2059,6 +2059,21 @@ gdk_memory_convert (guchar *dest_data, gdk_parallel_task_run (gdk_memory_convert_generic, &mc); } +typedef struct _MemoryConvertColorState MemoryConvertColorState; + +struct _MemoryConvertColorState +{ + guchar *data; + gsize stride; + GdkMemoryFormat format; + GdkColorState *src_cs; + GdkColorState *dest_cs; + gsize width; + gsize height; + + /* atomic */ int rows_done; +}; + static const guchar srgb_lookup[] = { 0, 12, 21, 28, 33, 38, 42, 46, 49, 52, 55, 58, 61, 63, 66, 68, 70, 73, 75, 77, 79, 81, 82, 84, 86, 88, 89, 91, 93, 94, 96, 97, @@ -2163,43 +2178,83 @@ convert_srgb_linear_to_srgb (guchar *data, } static void -convert_srgb_to_srgb_linear_in_place (guchar *data, - gsize stride, - gsize width, - gsize height) +gdk_memory_convert_color_state_srgb_to_srgb_linear (gpointer data) { - if (stride == width * 4) + MemoryConvertColorState *mc = data; + int y; + + for (y = g_atomic_int_add (&mc->rows_done, 1); + y < mc->height; + y = g_atomic_int_add (&mc->rows_done, 1)) { - convert_srgb_to_srgb_linear (data, height * width); - } - else - { - for (gsize y = 0; y < height; y++) - { - convert_srgb_to_srgb_linear (data, width); - data += stride; - } + convert_srgb_to_srgb_linear (mc->data + y * mc->stride, mc->width); } } static void -convert_srgb_linear_to_srgb_in_place (guchar *data, - gsize stride, - gsize width, - gsize height) +gdk_memory_convert_color_state_srgb_linear_to_srgb (gpointer data) { - if (stride == width * 4) + MemoryConvertColorState *mc = data; + int y; + + for (y = g_atomic_int_add (&mc->rows_done, 1); + y < mc->height; + y = g_atomic_int_add (&mc->rows_done, 1)) { - convert_srgb_linear_to_srgb (data, height * width); + convert_srgb_linear_to_srgb (mc->data + y * mc->stride, mc->width); } - else +} + +static void +gdk_memory_convert_color_state_generic (gpointer user_data) +{ + MemoryConvertColorState *mc = user_data; + const GdkMemoryFormatDescription *desc = &memory_formats[mc->format]; + GdkFloatColorConvert convert_func = NULL; + GdkFloatColorConvert convert_func2 = NULL; + float (*tmp)[4]; + int y; + + convert_func = gdk_color_state_get_convert_to (mc->src_cs, mc->dest_cs); + + if (!convert_func) { - for (gsize y = 0; y < height; y++) - { - convert_srgb_linear_to_srgb (data, width); - data += stride; - } + convert_func2 = gdk_color_state_get_convert_from (mc->dest_cs, mc->src_cs); } + + if (!convert_func && !convert_func2) + { + GdkColorState *connection = GDK_COLOR_STATE_REC2100_LINEAR; + convert_func = gdk_color_state_get_convert_to (mc->src_cs, connection); + convert_func2 = gdk_color_state_get_convert_from (mc->dest_cs, connection); + } + + tmp = g_malloc (sizeof (*tmp) * mc->width); + + for (y = g_atomic_int_add (&mc->rows_done, 1); + y < mc->height; + y = g_atomic_int_add (&mc->rows_done, 1)) + { + guchar *data = mc->data + y * mc->stride; + + desc->to_float (tmp, data, mc->width); + + if (desc->alpha == GDK_MEMORY_ALPHA_PREMULTIPLIED) + unpremultiply (tmp, mc->width); + + if (convert_func) + convert_func (mc->src_cs, tmp, mc->width); + + if (convert_func2) + convert_func2 (mc->dest_cs, tmp, mc->width); + + if (desc->alpha == GDK_MEMORY_ALPHA_PREMULTIPLIED) + premultiply (tmp, mc->width); + + desc->from_float (data, tmp, mc->width); + } + + g_free (tmp); } void @@ -2211,10 +2266,15 @@ gdk_memory_convert_color_state (guchar *data, gsize width, gsize height) { - const GdkMemoryFormatDescription *desc = &memory_formats[format]; - GdkFloatColorConvert convert_func = NULL; - GdkFloatColorConvert convert_func2 = NULL; - float (*tmp)[4]; + MemoryConvertColorState mc = { + .data = data, + .stride = stride, + .format = format, + .src_cs = src_cs, + .dest_cs = dest_cs, + .width = width, + .height = height, + }; if (gdk_color_state_equal (src_cs, dest_cs)) return; @@ -2223,52 +2283,16 @@ gdk_memory_convert_color_state (guchar *data, src_cs == GDK_COLOR_STATE_SRGB && dest_cs == GDK_COLOR_STATE_SRGB_LINEAR) { - convert_srgb_to_srgb_linear_in_place (data, stride, width, height); - return; + gdk_parallel_task_run (gdk_memory_convert_color_state_srgb_to_srgb_linear, &mc); } else if (format == GDK_MEMORY_B8G8R8A8_PREMULTIPLIED && src_cs == GDK_COLOR_STATE_SRGB_LINEAR && dest_cs == GDK_COLOR_STATE_SRGB) { - convert_srgb_linear_to_srgb_in_place (data, stride, width, height); - return; + gdk_parallel_task_run (gdk_memory_convert_color_state_srgb_linear_to_srgb, &mc); } - - convert_func = gdk_color_state_get_convert_to (src_cs, dest_cs); - - if (!convert_func) + else { - convert_func2 = gdk_color_state_get_convert_from (dest_cs, src_cs); + gdk_parallel_task_run (gdk_memory_convert_color_state_generic, &mc); } - - if (!convert_func && !convert_func2) - { - GdkColorState *connection = GDK_COLOR_STATE_REC2100_LINEAR; - convert_func = gdk_color_state_get_convert_to (src_cs, connection); - convert_func2 = gdk_color_state_get_convert_from (dest_cs, connection); - } - - tmp = g_malloc (sizeof (*tmp) * width); - - for (gsize y = 0; y < height; y++) - { - desc->to_float (tmp, data, width); - - if (desc->alpha == GDK_MEMORY_ALPHA_PREMULTIPLIED) - unpremultiply (tmp, width); - - if (convert_func) - convert_func (src_cs, tmp, width); - - if (convert_func2) - convert_func2 (dest_cs, tmp, width); - - if (desc->alpha == GDK_MEMORY_ALPHA_PREMULTIPLIED) - premultiply (tmp, width); - - desc->from_float (data, tmp, width); - data += stride; - } - - g_free (tmp); } From 848c6815d329814ae7d2f1453f91fdbc4e0be969 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 27 Aug 2024 04:48:43 +0200 Subject: [PATCH 07/14] gpu: Allow uploading of mipmap levels when tiling This allows uploading less memory but requires computing lod levels on the CPU which is slow because it reads through all of the memory and so far entirely not optimized. However, it uses significantly less VRAM. This is done by adding a gdk_memory_mipmap() function that does this task. The texture upload op now accepts a lod level and if that is >0 it uses gdk_memory_mipmap() on the source texture. --- gdk/gdkmemoryformat.c | 108 +++++++++++++++++++++++++++++++- gdk/gdkmemoryformatprivate.h | 8 +++ gsk/gpu/gskgpucache.c | 13 +++- gsk/gpu/gskgpucacheprivate.h | 4 +- gsk/gpu/gskgpuframe.c | 2 +- gsk/gpu/gskgpunodeprocessor.c | 20 ++++-- gsk/gpu/gskgpuuploadop.c | 30 ++++++++- gsk/gpu/gskgpuuploadopprivate.h | 1 + 8 files changed, 171 insertions(+), 15 deletions(-) diff --git a/gdk/gdkmemoryformat.c b/gdk/gdkmemoryformat.c index 44780859e0..4365857bca 100644 --- a/gdk/gdkmemoryformat.c +++ b/gdk/gdkmemoryformat.c @@ -22,7 +22,6 @@ #include "gdkmemoryformatprivate.h" #include "gdkdmabuffourccprivate.h" -#include "gdkglcontextprivate.h" #include "gdkcolorstateprivate.h" #include "gdkparalleltaskprivate.h" #include "gtk/gtkcolorutilsprivate.h" @@ -314,6 +313,61 @@ ADD_ALPHA_FUNC(r8g8b8_to_b8g8r8a8, 0, 1, 2, 2, 1, 0, 3) ADD_ALPHA_FUNC(r8g8b8_to_a8r8g8b8, 0, 1, 2, 1, 2, 3, 0) ADD_ALPHA_FUNC(r8g8b8_to_a8b8g8r8, 0, 1, 2, 3, 2, 1, 0) +#define MIPMAP_FUNC(SumType, DataType, n_units) \ +static void \ +gdk_mipmap_ ## DataType ## _ ## n_units (guchar *dest, \ + gsize dest_stride, \ + const guchar *src, \ + gsize src_stride, \ + gsize src_width, \ + gsize src_height, \ + guint lod_level) \ +{ \ + gsize y_dest, y, x_dest, x, i; \ + gsize n = 1 << lod_level; \ +\ + for (y_dest = 0; y_dest < src_height; y_dest += n) \ + { \ + DataType *dest_data = (DataType *) dest; \ + for (x_dest = 0; x_dest < src_width; x_dest += n) \ + { \ + SumType tmp[n_units] = { 0, }; \ +\ + for (y = 0; y < MIN (n, src_height - y_dest); y++) \ + { \ + const DataType *src_data = (const DataType *) (src + y * src_stride); \ + for (x = 0; x < MIN (n, src_width - x_dest); x++) \ + { \ + for (i = 0; i < n_units; i++) \ + tmp[i] += src_data[n_units * (x_dest + x) + i]; \ + } \ + } \ +\ + for (i = 0; i < n_units; i++) \ + *dest_data++ = tmp[i] / (x * y); \ + } \ + dest += dest_stride; \ + src += src_stride * n; \ + } \ +} + +MIPMAP_FUNC(guint32, guint8, 1) +MIPMAP_FUNC(guint32, guint8, 2) +MIPMAP_FUNC(guint32, guint8, 3) +MIPMAP_FUNC(guint32, guint8, 4) +MIPMAP_FUNC(guint32, guint16, 1) +MIPMAP_FUNC(guint32, guint16, 2) +MIPMAP_FUNC(guint32, guint16, 3) +MIPMAP_FUNC(guint32, guint16, 4) +MIPMAP_FUNC(float, float, 1) +MIPMAP_FUNC(float, float, 3) +MIPMAP_FUNC(float, float, 4) +#define half_float guint16 +MIPMAP_FUNC(float, half_float, 1) +MIPMAP_FUNC(float, half_float, 3) +MIPMAP_FUNC(float, half_float, 4) +#undef half_float + struct _GdkMemoryFormatDescription { const char *name; @@ -347,6 +401,7 @@ struct _GdkMemoryFormatDescription /* no premultiplication going on here */ void (* to_float) (float (*)[4], const guchar*, gsize); void (* from_float) (guchar *, const float (*)[4], gsize); + void (* mipmap) (guchar *, gsize, const guchar *, gsize, gsize, gsize, guint); }; #if G_BYTE_ORDER == G_LITTLE_ENDIAN @@ -388,6 +443,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = b8g8r8a8_premultiplied_to_float, .from_float = b8g8r8a8_premultiplied_from_float, + .mipmap = gdk_mipmap_guint8_4, }, [GDK_MEMORY_A8R8G8B8_PREMULTIPLIED] = { .name = "ARGB8(p)", @@ -419,6 +475,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8r8g8b8_premultiplied_to_float, .from_float = a8r8g8b8_premultiplied_from_float, + .mipmap = gdk_mipmap_guint8_4, }, [GDK_MEMORY_R8G8B8A8_PREMULTIPLIED] = { .name = "RGBA8(p)", @@ -449,6 +506,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r8g8b8a8_premultiplied_to_float, .from_float = r8g8b8a8_premultiplied_from_float, + .mipmap = gdk_mipmap_guint8_4, }, [GDK_MEMORY_A8B8G8R8_PREMULTIPLIED] = { .name = "ABGR8(p)", @@ -480,6 +538,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8b8g8r8_premultiplied_to_float, .from_float = a8b8g8r8_premultiplied_from_float, + .mipmap = gdk_mipmap_guint8_4, }, [GDK_MEMORY_B8G8R8A8] = { .name = "BGRA8", @@ -511,6 +570,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = b8g8r8a8_to_float, .from_float = b8g8r8a8_from_float, + .mipmap = gdk_mipmap_guint8_4, }, [GDK_MEMORY_A8R8G8B8] = { .name = "ARGB8", @@ -542,6 +602,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8r8g8b8_to_float, .from_float = a8r8g8b8_from_float, + .mipmap = gdk_mipmap_guint8_4, }, [GDK_MEMORY_R8G8B8A8] = { .name = "RGBA8", @@ -572,6 +633,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r8g8b8a8_to_float, .from_float = r8g8b8a8_from_float, + .mipmap = gdk_mipmap_guint8_4, }, [GDK_MEMORY_A8B8G8R8] = { .name = "ABGR8", @@ -603,6 +665,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8b8g8r8_to_float, .from_float = a8b8g8r8_from_float, + .mipmap = gdk_mipmap_guint8_4, }, [GDK_MEMORY_B8G8R8X8] = { .name = "BGRX8", @@ -635,6 +698,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = b8g8r8x8_to_float, .from_float = b8g8r8x8_from_float, + .mipmap = gdk_mipmap_guint8_4, }, [GDK_MEMORY_X8R8G8B8] = { .name = "XRGB8", @@ -667,6 +731,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = x8r8g8b8_to_float, .from_float = x8r8g8b8_from_float, + .mipmap = gdk_mipmap_guint8_4, }, [GDK_MEMORY_R8G8B8X8] = { .name = "RGBX8", @@ -698,6 +763,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r8g8b8x8_to_float, .from_float = r8g8b8x8_from_float, + .mipmap = gdk_mipmap_guint8_4, }, [GDK_MEMORY_X8B8G8R8] = { .name = "XBGR8", @@ -730,6 +796,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = x8b8g8r8_to_float, .from_float = x8b8g8r8_from_float, + .mipmap = gdk_mipmap_guint8_4, }, [GDK_MEMORY_R8G8B8] = { .name = "RGB8", @@ -761,6 +828,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r8g8b8_to_float, .from_float = r8g8b8_from_float, + .mipmap = gdk_mipmap_guint8_3, }, [GDK_MEMORY_B8G8R8] = { .name = "BGR8", @@ -793,6 +861,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = b8g8r8_to_float, .from_float = b8g8r8_from_float, + .mipmap = gdk_mipmap_guint8_3, }, [GDK_MEMORY_R16G16B16] = { .name = "RGB16", @@ -827,6 +896,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16_to_float, .from_float = r16g16b16_from_float, + .mipmap = gdk_mipmap_guint16_3, }, [GDK_MEMORY_R16G16B16A16_PREMULTIPLIED] = { .name = "RGBA16(p)", @@ -860,6 +930,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16a16_to_float, .from_float = r16g16b16a16_from_float, + .mipmap = gdk_mipmap_guint16_4, }, [GDK_MEMORY_R16G16B16A16] = { .name = "RGBA16", @@ -893,6 +964,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16a16_to_float, .from_float = r16g16b16a16_from_float, + .mipmap = gdk_mipmap_guint16_4, }, [GDK_MEMORY_R16G16B16_FLOAT] = { .name = "RGBA16f", @@ -926,6 +998,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16_float_to_float, .from_float = r16g16b16_float_from_float, + .mipmap = gdk_mipmap_half_float_3, }, [GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED] = { .name = "RGBA16f(p)", @@ -958,6 +1031,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16a16_float_to_float, .from_float = r16g16b16a16_float_from_float, + .mipmap = gdk_mipmap_half_float_4, }, [GDK_MEMORY_R16G16B16A16_FLOAT] = { .name = "RGBA16f", @@ -990,6 +1064,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16a16_float_to_float, .from_float = r16g16b16a16_float_from_float, + .mipmap = gdk_mipmap_half_float_4, }, [GDK_MEMORY_R32G32B32_FLOAT] = { .name = "RGB32f", @@ -1023,6 +1098,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r32g32b32_float_to_float, .from_float = r32g32b32_float_from_float, + .mipmap = gdk_mipmap_float_3, }, [GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED] = { .name = "RGBA32f(p)", @@ -1055,6 +1131,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r32g32b32a32_float_to_float, .from_float = r32g32b32a32_float_from_float, + .mipmap = gdk_mipmap_float_4, }, [GDK_MEMORY_R32G32B32A32_FLOAT] = { .name = "RGBA32f", @@ -1087,6 +1164,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r32g32b32a32_float_to_float, .from_float = r32g32b32a32_float_from_float, + .mipmap = gdk_mipmap_float_4, }, [GDK_MEMORY_G8A8_PREMULTIPLIED] = { .name = "GA8(p)", @@ -1118,6 +1196,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g8a8_premultiplied_to_float, .from_float = g8a8_premultiplied_from_float, + .mipmap = gdk_mipmap_guint8_2, }, [GDK_MEMORY_G8A8] = { .name = "GA8", @@ -1149,6 +1228,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g8a8_to_float, .from_float = g8a8_from_float, + .mipmap = gdk_mipmap_guint8_2, }, [GDK_MEMORY_G8] = { .name = "G8", @@ -1180,6 +1260,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g8_to_float, .from_float = g8_from_float, + .mipmap = gdk_mipmap_guint8_1, }, [GDK_MEMORY_G16A16_PREMULTIPLIED] = { .name = "GA16(p)", @@ -1214,6 +1295,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g16a16_premultiplied_to_float, .from_float = g16a16_premultiplied_from_float, + .mipmap = gdk_mipmap_guint16_2, }, [GDK_MEMORY_G16A16] = { .name = "GA16", @@ -1248,6 +1330,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g16a16_to_float, .from_float = g16a16_from_float, + .mipmap = gdk_mipmap_guint16_2, }, [GDK_MEMORY_G16] = { .name = "G16", @@ -1282,6 +1365,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g16_to_float, .from_float = g16_from_float, + .mipmap = gdk_mipmap_guint16_1, }, [GDK_MEMORY_A8] = { .name = "A8", @@ -1313,6 +1397,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8_to_float, .from_float = a8_from_float, + .mipmap = gdk_mipmap_guint8_1, }, [GDK_MEMORY_A16] = { .name = "A16", @@ -1347,6 +1432,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a16_to_float, .from_float = a16_from_float, + .mipmap = gdk_mipmap_guint16_1, }, [GDK_MEMORY_A16_FLOAT] = { .name = "A16f", @@ -1380,6 +1466,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a16_float_to_float, .from_float = a16_float_from_float, + .mipmap = gdk_mipmap_half_float_1, }, [GDK_MEMORY_A32_FLOAT] = { .name = "A32f", @@ -1413,6 +1500,7 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a32_float_to_float, .from_float = a32_float_from_float, + .mipmap = gdk_mipmap_float_1, } }; @@ -2296,3 +2384,21 @@ gdk_memory_convert_color_state (guchar *data, gdk_parallel_task_run (gdk_memory_convert_color_state_generic, &mc); } } + +void +gdk_memory_mipmap (guchar *dest, + gsize dest_stride, + GdkMemoryFormat format, + const guchar *src, + gsize src_stride, + gsize src_width, + gsize src_height, + guint lod_level) +{ + const GdkMemoryFormatDescription *desc = &memory_formats[format]; + + g_assert (lod_level > 0); + + desc->mipmap (dest, dest_stride, src, src_stride, src_width, src_height, lod_level); +} + diff --git a/gdk/gdkmemoryformatprivate.h b/gdk/gdkmemoryformatprivate.h index 57f55bd5fd..8fda93d5e5 100644 --- a/gdk/gdkmemoryformatprivate.h +++ b/gdk/gdkmemoryformatprivate.h @@ -110,6 +110,14 @@ void gdk_memory_convert_color_state (guchar GdkColorState *dest_color_state, gsize width, gsize height); +void gdk_memory_mipmap (guchar *dest, + gsize dest_stride, + GdkMemoryFormat format, + const guchar *src, + gsize src_stride, + gsize src_width, + gsize src_height, + guint lod_level); G_END_DECLS diff --git a/gsk/gpu/gskgpucache.c b/gsk/gpu/gskgpucache.c index 724b9ecf57..84c52fb408 100644 --- a/gsk/gpu/gskgpucache.c +++ b/gsk/gpu/gskgpucache.c @@ -537,6 +537,7 @@ struct _GskGpuCachedTile GskGpuCached parent; GdkTexture *texture; + guint lod_level; gsize tile_id; /* atomic */ int use_count; /* We count the use by the cache (via the linked @@ -630,7 +631,7 @@ gsk_gpu_cached_tile_hash (gconstpointer data) { const GskGpuCachedTile *self = data; - return g_direct_hash (self->texture) ^ self->tile_id; + return g_direct_hash (self->texture) ^ self->tile_id ^ (self->lod_level << 24); } static gboolean @@ -641,12 +642,14 @@ gsk_gpu_cached_tile_equal (gconstpointer data_a, const GskGpuCachedTile *b = data_b; return a->texture == b->texture && + a->lod_level == b->lod_level && a->tile_id == b->tile_id; } static GskGpuCachedTile * gsk_gpu_cached_tile_new (GskGpuCache *cache, GdkTexture *texture, + guint lod_level, guint tile_id, GskGpuImage *image, GdkColorState *color_state) @@ -655,6 +658,7 @@ gsk_gpu_cached_tile_new (GskGpuCache *cache, self = gsk_gpu_cached_new (cache, &GSK_GPU_CACHED_TILE_CLASS); self->texture = texture; + self->lod_level = lod_level; self->tile_id = tile_id; self->image = g_object_ref (image); self->color_state = gdk_color_state_ref (color_state); @@ -675,12 +679,14 @@ gsk_gpu_cached_tile_new (GskGpuCache *cache, GskGpuImage * gsk_gpu_cache_lookup_tile (GskGpuCache *self, GdkTexture *texture, + guint lod_level, gsize tile_id, GdkColorState **out_color_state) { GskGpuCachedTile *tile; GskGpuCachedTile lookup = { .texture = texture, + .lod_level = lod_level, .tile_id = tile_id }; @@ -701,13 +707,14 @@ gsk_gpu_cache_lookup_tile (GskGpuCache *self, void gsk_gpu_cache_cache_tile (GskGpuCache *self, GdkTexture *texture, - guint tile_id, + guint lod_level, + gsize tile_id, GskGpuImage *image, GdkColorState *color_state) { GskGpuCachedTile *tile; - tile = gsk_gpu_cached_tile_new (self, texture, tile_id, image, color_state); + tile = gsk_gpu_cached_tile_new (self, texture, lod_level, tile_id, image, color_state); gsk_gpu_cached_use (self, (GskGpuCached *) tile); } diff --git a/gsk/gpu/gskgpucacheprivate.h b/gsk/gpu/gskgpucacheprivate.h index 9164504c76..afe7e58786 100644 --- a/gsk/gpu/gskgpucacheprivate.h +++ b/gsk/gpu/gskgpucacheprivate.h @@ -77,11 +77,13 @@ void gsk_gpu_cache_cache_texture_image (GskGpuC GdkColorState *color_state); GskGpuImage * gsk_gpu_cache_lookup_tile (GskGpuCache *self, GdkTexture *texture, + guint lod_level, gsize tile_id, GdkColorState **out_color_state); void gsk_gpu_cache_cache_tile (GskGpuCache *self, GdkTexture *texture, - guint tile_id, + guint lod_level, + gsize tile_id, GskGpuImage *image, GdkColorState *color_state); diff --git a/gsk/gpu/gskgpuframe.c b/gsk/gpu/gskgpuframe.c index 6659063797..fb2789608c 100644 --- a/gsk/gpu/gskgpuframe.c +++ b/gsk/gpu/gskgpuframe.c @@ -107,7 +107,7 @@ gsk_gpu_frame_default_upload_texture (GskGpuFrame *self, { GskGpuImage *image; - image = gsk_gpu_upload_texture_op_try (self, with_mipmap, texture); + image = gsk_gpu_upload_texture_op_try (self, with_mipmap, 0, texture); return image; } diff --git a/gsk/gpu/gskgpunodeprocessor.c b/gsk/gpu/gskgpunodeprocessor.c index 6ab7c120fb..ca5849a1d3 100644 --- a/gsk/gpu/gskgpunodeprocessor.c +++ b/gsk/gpu/gskgpunodeprocessor.c @@ -1964,18 +1964,26 @@ gsk_gpu_node_processor_draw_texture_tiles (GskGpuNodeProcessor *self, gboolean need_mipmap; GdkMemoryTexture *memtex; GdkTexture *subtex; - float scaled_tile_width, scaled_tile_height; + float scale_factor, scaled_tile_width, scaled_tile_height; gsize tile_size, width, height, n_width, n_height, x, y; graphene_rect_t clip_bounds; + guint lod_level; device = gsk_gpu_frame_get_device (self->frame); cache = gsk_gpu_device_get_cache (device); 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); height = gdk_texture_get_height (texture); + tile_size = gsk_gpu_device_get_tile_size (device); + scale_factor = MIN (width / MAX (tile_size, texture_bounds->size.width), + height / MAX (tile_size, texture_bounds->size.height)); + if (scale_factor <= 1.0) + lod_level = 0; + else + lod_level = floor (log2f (scale_factor)); + tile_size <<= lod_level; 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; @@ -1994,7 +2002,7 @@ 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, &tile_cs); + tile = gsk_gpu_cache_lookup_tile (cache, texture, lod_level, y * n_width + x, &tile_cs); if (tile == NULL) { @@ -2005,7 +2013,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, need_mipmap, subtex); + tile = gsk_gpu_upload_texture_op_try (self->frame, need_mipmap, lod_level, subtex); g_object_unref (subtex); if (tile == NULL) { @@ -2021,7 +2029,7 @@ gsk_gpu_node_processor_draw_texture_tiles (GskGpuNodeProcessor *self, g_assert (tile_cs); } - gsk_gpu_cache_cache_tile (cache, texture, y * n_width + x, tile, tile_cs); + gsk_gpu_cache_cache_tile (cache, texture, lod_level, y * n_width + x, tile, tile_cs); } if (need_mipmap && @@ -2029,7 +2037,7 @@ gsk_gpu_node_processor_draw_texture_tiles (GskGpuNodeProcessor *self, { tile = gsk_gpu_copy_image (self->frame, self->ccs, tile, tile_cs, TRUE); tile_cs = self->ccs; - gsk_gpu_cache_cache_tile (cache, texture, y * n_width + x, tile, tile_cs); + gsk_gpu_cache_cache_tile (cache, texture, lod_level, 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); diff --git a/gsk/gpu/gskgpuuploadop.c b/gsk/gpu/gskgpuuploadop.c index ad4b9aa02d..f9514fe875 100644 --- a/gsk/gpu/gskgpuuploadop.c +++ b/gsk/gpu/gskgpuuploadop.c @@ -214,6 +214,7 @@ struct _GskGpuUploadTextureOp GskGpuImage *image; GskGpuBuffer *buffer; GdkTexture *texture; + guint lod_level; }; static void @@ -236,6 +237,8 @@ gsk_gpu_upload_texture_op_print (GskGpuOp *op, gsk_gpu_print_op (string, indent, "upload-texture"); gsk_gpu_print_image (string, self->image); + if (self->lod_level > 0) + g_string_append_printf (string, " @%ux ", 1 << self->lod_level); gsk_gpu_print_newline (string); } @@ -250,7 +253,26 @@ gsk_gpu_upload_texture_op_draw (GskGpuOp *op, downloader = gdk_texture_downloader_new (self->texture); gdk_texture_downloader_set_format (downloader, gsk_gpu_image_get_format (self->image)); gdk_texture_downloader_set_color_state (downloader, gdk_texture_get_color_state (self->texture)); - gdk_texture_downloader_download_into (downloader, data, stride); + if (self->lod_level == 0) + { + gdk_texture_downloader_download_into (downloader, data, stride); + } + else + { + GBytes *bytes; + gsize src_stride; + + bytes = gdk_texture_downloader_download_bytes (downloader, &src_stride); + gdk_memory_mipmap (data, + stride, + gsk_gpu_image_get_format (self->image), + g_bytes_get_data (bytes, NULL), + src_stride, + gdk_texture_get_width (self->texture), + gdk_texture_get_height (self->texture), + self->lod_level); + g_bytes_unref (bytes); + } gdk_texture_downloader_free (downloader); } @@ -298,6 +320,7 @@ static const GskGpuOpClass GSK_GPU_UPLOAD_TEXTURE_OP_CLASS = { GskGpuImage * gsk_gpu_upload_texture_op_try (GskGpuFrame *frame, gboolean with_mipmap, + guint lod_level, GdkTexture *texture) { GskGpuUploadTextureOp *self; @@ -311,8 +334,8 @@ gsk_gpu_upload_texture_op_try (GskGpuFrame *frame, format, gdk_memory_format_alpha (format) != GDK_MEMORY_ALPHA_PREMULTIPLIED && gdk_color_state_get_no_srgb_tf (gdk_texture_get_color_state (texture)) != NULL, - gdk_texture_get_width (texture), - gdk_texture_get_height (texture)); + (gdk_texture_get_width (texture) + (1 << lod_level) - 1) >> lod_level, + (gdk_texture_get_height (texture) + (1 << lod_level) - 1) >> lod_level); if (image == NULL) return NULL; @@ -343,6 +366,7 @@ gsk_gpu_upload_texture_op_try (GskGpuFrame *frame, self = (GskGpuUploadTextureOp *) gsk_gpu_op_alloc (frame, &GSK_GPU_UPLOAD_TEXTURE_OP_CLASS); self->texture = g_object_ref (texture); + self->lod_level = lod_level; self->image = image; return g_object_ref (self->image); diff --git a/gsk/gpu/gskgpuuploadopprivate.h b/gsk/gpu/gskgpuuploadopprivate.h index f29ca24250..cbd077cb4e 100644 --- a/gsk/gpu/gskgpuuploadopprivate.h +++ b/gsk/gpu/gskgpuuploadopprivate.h @@ -11,6 +11,7 @@ typedef void (* GskGpuCairoFunc) (gpointe GskGpuImage * gsk_gpu_upload_texture_op_try (GskGpuFrame *frame, gboolean with_mipmap, + guint lod_level, GdkTexture *texture); GskGpuImage * gsk_gpu_upload_cairo_op (GskGpuFrame *frame, From cea961f4f4294b026357b1931220d92426e76623 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 27 Aug 2024 05:59:54 +0200 Subject: [PATCH 08/14] memoryformat: Take src_format and dest_format Why do we need this? Because RGB images are provided in RGB format but GPUs can't handle RGB, only RGBA, so we need to convert. And we need to do that without allocating too much memory, because allocating memory is slow. Which means in aprticular we need to do the conversion after mipmapping, not before (like we were doing). --- gdk/gdkmemoryformat.c | 34 +++++++++++++++++++++++++++++++--- gdk/gdkmemoryformatprivate.h | 3 ++- gsk/gpu/gskgpuuploadop.c | 4 +++- 3 files changed, 36 insertions(+), 5 deletions(-) diff --git a/gdk/gdkmemoryformat.c b/gdk/gdkmemoryformat.c index 4365857bca..dacdb46cae 100644 --- a/gdk/gdkmemoryformat.c +++ b/gdk/gdkmemoryformat.c @@ -2388,17 +2388,45 @@ gdk_memory_convert_color_state (guchar *data, void gdk_memory_mipmap (guchar *dest, gsize dest_stride, - GdkMemoryFormat format, + GdkMemoryFormat dest_format, const guchar *src, gsize src_stride, + GdkMemoryFormat src_format, gsize src_width, gsize src_height, guint lod_level) { - const GdkMemoryFormatDescription *desc = &memory_formats[format]; + const GdkMemoryFormatDescription *desc = &memory_formats[src_format]; g_assert (lod_level > 0); - desc->mipmap (dest, dest_stride, src, src_stride, src_width, src_height, lod_level); + if (dest_format == src_format) + { + desc->mipmap (dest, dest_stride, src, src_stride, src_width, src_height, lod_level); + } + else + { + gsize dest_width; + gsize size; + guchar *tmp; + gsize y, n; + + n = 1 << lod_level; + dest_width = (src_width + n - 1) >> lod_level; + size = gdk_memory_format_bytes_per_pixel (src_format) * dest_width; + tmp = g_malloc (size); + + for (y = 0; y < src_height; y += n) + { + desc->mipmap (tmp, (size + 7) & 7, src, src_stride, src_width, MIN (n, src_height - y), lod_level); + gdk_memory_convert (dest, dest_stride, dest_format, GDK_COLOR_STATE_SRGB, + tmp, (size + 7) & 7, src_format, GDK_COLOR_STATE_SRGB, + dest_width, 1); + dest += dest_stride; + src += n * src_stride; + } + + g_free (tmp); + } } diff --git a/gdk/gdkmemoryformatprivate.h b/gdk/gdkmemoryformatprivate.h index 8fda93d5e5..96c4189285 100644 --- a/gdk/gdkmemoryformatprivate.h +++ b/gdk/gdkmemoryformatprivate.h @@ -112,9 +112,10 @@ void gdk_memory_convert_color_state (guchar gsize height); void gdk_memory_mipmap (guchar *dest, gsize dest_stride, - GdkMemoryFormat format, + GdkMemoryFormat dest_format, const guchar *src, gsize src_stride, + GdkMemoryFormat src_format, gsize src_width, gsize src_height, guint lod_level); diff --git a/gsk/gpu/gskgpuuploadop.c b/gsk/gpu/gskgpuuploadop.c index f9514fe875..4ce95a4fd6 100644 --- a/gsk/gpu/gskgpuuploadop.c +++ b/gsk/gpu/gskgpuuploadop.c @@ -251,10 +251,10 @@ gsk_gpu_upload_texture_op_draw (GskGpuOp *op, GdkTextureDownloader *downloader; downloader = gdk_texture_downloader_new (self->texture); - gdk_texture_downloader_set_format (downloader, gsk_gpu_image_get_format (self->image)); gdk_texture_downloader_set_color_state (downloader, gdk_texture_get_color_state (self->texture)); if (self->lod_level == 0) { + gdk_texture_downloader_set_format (downloader, gsk_gpu_image_get_format (self->image)); gdk_texture_downloader_download_into (downloader, data, stride); } else @@ -262,12 +262,14 @@ gsk_gpu_upload_texture_op_draw (GskGpuOp *op, GBytes *bytes; gsize src_stride; + gdk_texture_downloader_set_format (downloader, gdk_texture_get_format (self->texture)); bytes = gdk_texture_downloader_download_bytes (downloader, &src_stride); gdk_memory_mipmap (data, stride, gsk_gpu_image_get_format (self->image), g_bytes_get_data (bytes, NULL), src_stride, + gdk_texture_get_format (self->texture), gdk_texture_get_width (self->texture), gdk_texture_get_height (self->texture), self->lod_level); From 534a9b6ba09610c40ce356da7617603edb89900d Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 27 Aug 2024 06:13:06 +0200 Subject: [PATCH 09/14] memoryformat: Add fast path for mipmap We have fast conversion functions, use those directly instead of calling into gdk_memory_convert(). This is useful because as mentioned before, the main optimization here is RGB8 => RGBA8 and we have a fastpath for that. --- gdk/gdkmemoryformat.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/gdk/gdkmemoryformat.c b/gdk/gdkmemoryformat.c index dacdb46cae..649416a0b5 100644 --- a/gdk/gdkmemoryformat.c +++ b/gdk/gdkmemoryformat.c @@ -1939,7 +1939,9 @@ unpremultiply (float (*rgba)[4], } } -typedef void (* FastConversionFunc) (guchar *, const guchar *, gsize); +typedef void (* FastConversionFunc) (guchar *dest, + const guchar *src, + gsize n); static FastConversionFunc get_fast_conversion_func (GdkMemoryFormat dest_format, @@ -2406,6 +2408,7 @@ gdk_memory_mipmap (guchar *dest, } else { + FastConversionFunc func; gsize dest_width; gsize size; guchar *tmp; @@ -2415,13 +2418,17 @@ gdk_memory_mipmap (guchar *dest, dest_width = (src_width + n - 1) >> lod_level; size = gdk_memory_format_bytes_per_pixel (src_format) * dest_width; tmp = g_malloc (size); + func = get_fast_conversion_func (dest_format, src_format); for (y = 0; y < src_height; y += n) { desc->mipmap (tmp, (size + 7) & 7, src, src_stride, src_width, MIN (n, src_height - y), lod_level); - gdk_memory_convert (dest, dest_stride, dest_format, GDK_COLOR_STATE_SRGB, - tmp, (size + 7) & 7, src_format, GDK_COLOR_STATE_SRGB, - dest_width, 1); + if (func) + func (dest, tmp, dest_width); + else + gdk_memory_convert (dest, dest_stride, dest_format, GDK_COLOR_STATE_SRGB, + tmp, (size + 7) & 7, src_format, GDK_COLOR_STATE_SRGB, + dest_width, 1); dest += dest_stride; src += n * src_stride; } From 5498b077fd3dcf2943714edc8304fcb30c1b296c Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Mon, 2 Sep 2024 03:35:50 +0200 Subject: [PATCH 10/14] memoryformat: Parallelize gdk_memory_mipmap() --- gdk/gdkmemoryformat.c | 122 ++++++++++++++++++++++++++++++++---------- 1 file changed, 94 insertions(+), 28 deletions(-) diff --git a/gdk/gdkmemoryformat.c b/gdk/gdkmemoryformat.c index 649416a0b5..26b1c99075 100644 --- a/gdk/gdkmemoryformat.c +++ b/gdk/gdkmemoryformat.c @@ -2387,6 +2387,86 @@ gdk_memory_convert_color_state (guchar *data, } } +typedef struct _MipmapData MipmapData; + +struct _MipmapData +{ + guchar *dest; + gsize dest_stride; + GdkMemoryFormat dest_format; + const guchar *src; + gsize src_stride; + GdkMemoryFormat src_format; + gsize src_width; + gsize src_height; + guint lod_level; + + gint rows_done; +}; + +static void +gdk_memory_mipmap_same_format (gpointer data) +{ + MipmapData *mipmap = data; + const GdkMemoryFormatDescription *desc = &memory_formats[mipmap->src_format]; + gsize n, y; + + n = 1 << mipmap->lod_level; + + for (y = g_atomic_int_add (&mipmap->rows_done, n); + y < mipmap->src_height; + y = g_atomic_int_add (&mipmap->rows_done, n)) + { + guchar *dest = mipmap->dest + (y >> mipmap->lod_level) * mipmap->dest_stride; + const guchar *src = mipmap->src + y * mipmap->src_stride; + + desc->mipmap (dest, mipmap->dest_stride, + src, mipmap->src_stride, + mipmap->src_width, MIN (n, mipmap->src_height - y), + mipmap->lod_level); + } +} + +static void +gdk_memory_mipmap_generic (gpointer data) +{ + MipmapData *mipmap = data; + const GdkMemoryFormatDescription *desc = &memory_formats[mipmap->src_format]; + FastConversionFunc func; + gsize dest_width; + gsize size; + guchar *tmp; + gsize n, y; + + n = 1 << mipmap->lod_level; + dest_width = (mipmap->src_width + n - 1) >> mipmap->lod_level; + size = gdk_memory_format_bytes_per_pixel (mipmap->src_format) * dest_width; + tmp = g_malloc (size); + func = get_fast_conversion_func (mipmap->dest_format, mipmap->src_format); + + for (y = g_atomic_int_add (&mipmap->rows_done, n); + y < mipmap->src_height; + y = g_atomic_int_add (&mipmap->rows_done, n)) + { + guchar *dest = mipmap->dest + (y >> mipmap->lod_level) * mipmap->dest_stride; + const guchar *src = mipmap->src + y * mipmap->src_stride; + + desc->mipmap (tmp, (size + 7) & 7, + src, mipmap->src_stride, + mipmap->src_width, MIN (n, mipmap->src_height - y), + mipmap->lod_level); + if (func) + func (dest, tmp, dest_width); + else + gdk_memory_convert (dest, mipmap->dest_stride, mipmap->dest_format, GDK_COLOR_STATE_SRGB, + tmp, (size + 7) & 7, mipmap->src_format, GDK_COLOR_STATE_SRGB, + dest_width, 1); + + } + + g_free (tmp); +} + void gdk_memory_mipmap (guchar *dest, gsize dest_stride, @@ -2398,42 +2478,28 @@ gdk_memory_mipmap (guchar *dest, gsize src_height, guint lod_level) { - const GdkMemoryFormatDescription *desc = &memory_formats[src_format]; + MipmapData mipmap = { + .dest = dest, + .dest_stride = dest_stride, + .dest_format = dest_format, + .src = src, + .src_stride = src_stride, + .src_format = src_format, + .src_width = src_width, + .src_height = src_height, + .lod_level = lod_level, + .rows_done = 0, + }; g_assert (lod_level > 0); if (dest_format == src_format) { - desc->mipmap (dest, dest_stride, src, src_stride, src_width, src_height, lod_level); + gdk_parallel_task_run (gdk_memory_mipmap_same_format, &mipmap); } else { - FastConversionFunc func; - gsize dest_width; - gsize size; - guchar *tmp; - gsize y, n; - - n = 1 << lod_level; - dest_width = (src_width + n - 1) >> lod_level; - size = gdk_memory_format_bytes_per_pixel (src_format) * dest_width; - tmp = g_malloc (size); - func = get_fast_conversion_func (dest_format, src_format); - - for (y = 0; y < src_height; y += n) - { - desc->mipmap (tmp, (size + 7) & 7, src, src_stride, src_width, MIN (n, src_height - y), lod_level); - if (func) - func (dest, tmp, dest_width); - else - gdk_memory_convert (dest, dest_stride, dest_format, GDK_COLOR_STATE_SRGB, - tmp, (size + 7) & 7, src_format, GDK_COLOR_STATE_SRGB, - dest_width, 1); - dest += dest_stride; - src += n * src_stride; - } - - g_free (tmp); + gdk_parallel_task_run (gdk_memory_mipmap_generic, &mipmap); } } From 896ea5b75331df8a80271d8fd1a4626c1313c436 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Tue, 3 Sep 2024 06:12:03 +0200 Subject: [PATCH 11/14] memoryformat: Add linear/nearest choice for mipmaping linear will average all the pixels for the lod, nearest will just pick one (using the same method as OpenGL/Vulkan, picking bottom right center). This doesn't really make linear/nearest filtering work as it should (because it's still a form of mipmaps), but it has 2 advantages: 1. it gets closer to the desired effect 2. it is a lot faster Because only 1 pixel is chosen from the original image, instead of averaging all pixels, a lot less memory needs to be accessed, and because memory access is the bottleneck for large images, the speedup is almost linear with the number of pixels not accessed. And that means that even for lot level 3, aka 1/8th scale, only 1/64 of the pixels need to be accessed, and everything is 50x faster. Switching gtk4-demo --run=image_scaling to linear/nearest makes all the lag go away for me, even with a 64k x 64k image. --- gdk/gdkmemoryformat.c | 200 +++++++++++++++++++++++--------- gdk/gdkmemoryformatprivate.h | 3 +- gsk/gpu/gskgpucache.c | 42 ++++--- gsk/gpu/gskgpucacheprivate.h | 2 + gsk/gpu/gskgpuframe.c | 2 +- gsk/gpu/gskgpunodeprocessor.c | 8 +- gsk/gpu/gskgpuuploadop.c | 18 ++- gsk/gpu/gskgpuuploadopprivate.h | 1 + 8 files changed, 199 insertions(+), 77 deletions(-) diff --git a/gdk/gdkmemoryformat.c b/gdk/gdkmemoryformat.c index 26b1c99075..712abcf787 100644 --- a/gdk/gdkmemoryformat.c +++ b/gdk/gdkmemoryformat.c @@ -315,13 +315,40 @@ ADD_ALPHA_FUNC(r8g8b8_to_a8b8g8r8, 0, 1, 2, 3, 2, 1, 0) #define MIPMAP_FUNC(SumType, DataType, n_units) \ static void \ -gdk_mipmap_ ## DataType ## _ ## n_units (guchar *dest, \ - gsize dest_stride, \ - const guchar *src, \ - gsize src_stride, \ - gsize src_width, \ - gsize src_height, \ - guint lod_level) \ +gdk_mipmap_ ## DataType ## _ ## n_units ## _nearest (guchar *dest, \ + gsize dest_stride, \ + const guchar *src, \ + gsize src_stride, \ + gsize src_width, \ + gsize src_height, \ + guint lod_level) \ +{ \ + gsize y, x, i; \ + gsize n = 1 << lod_level; \ +\ + for (y = 0; y < src_height; y += n) \ + { \ + DataType *dest_data = (DataType *) dest; \ + for (x = 0; x < src_width; x += n) \ + { \ + const DataType *src_data = (const DataType *) (src + (y + MIN (n / 2, src_height - y)) * src_stride); \ +\ + for (i = 0; i < n_units; i++) \ + *dest_data++ = src_data[n_units * (x + MIN (n / 2, src_width - n_units)) + i]; \ + } \ + dest += dest_stride; \ + src += src_stride * n; \ + } \ +} \ +\ +static void \ +gdk_mipmap_ ## DataType ## _ ## n_units ## _linear (guchar *dest, \ + gsize dest_stride, \ + const guchar *src, \ + gsize src_stride, \ + gsize src_width, \ + gsize src_height, \ + guint lod_level) \ { \ gsize y_dest, y, x_dest, x, i; \ gsize n = 1 << lod_level; \ @@ -401,7 +428,8 @@ struct _GdkMemoryFormatDescription /* no premultiplication going on here */ void (* to_float) (float (*)[4], const guchar*, gsize); void (* from_float) (guchar *, const float (*)[4], gsize); - void (* mipmap) (guchar *, gsize, const guchar *, gsize, gsize, gsize, guint); + void (* mipmap_nearest) (guchar *, gsize, const guchar *, gsize, gsize, gsize, guint); + void (* mipmap_linear) (guchar *, gsize, const guchar *, gsize, gsize, gsize, guint); }; #if G_BYTE_ORDER == G_LITTLE_ENDIAN @@ -443,7 +471,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = b8g8r8a8_premultiplied_to_float, .from_float = b8g8r8a8_premultiplied_from_float, - .mipmap = gdk_mipmap_guint8_4, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_A8R8G8B8_PREMULTIPLIED] = { .name = "ARGB8(p)", @@ -475,7 +504,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8r8g8b8_premultiplied_to_float, .from_float = a8r8g8b8_premultiplied_from_float, - .mipmap = gdk_mipmap_guint8_4, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_R8G8B8A8_PREMULTIPLIED] = { .name = "RGBA8(p)", @@ -506,7 +536,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r8g8b8a8_premultiplied_to_float, .from_float = r8g8b8a8_premultiplied_from_float, - .mipmap = gdk_mipmap_guint8_4, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_A8B8G8R8_PREMULTIPLIED] = { .name = "ABGR8(p)", @@ -538,7 +569,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8b8g8r8_premultiplied_to_float, .from_float = a8b8g8r8_premultiplied_from_float, - .mipmap = gdk_mipmap_guint8_4, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_B8G8R8A8] = { .name = "BGRA8", @@ -570,7 +602,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = b8g8r8a8_to_float, .from_float = b8g8r8a8_from_float, - .mipmap = gdk_mipmap_guint8_4, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_A8R8G8B8] = { .name = "ARGB8", @@ -602,7 +635,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8r8g8b8_to_float, .from_float = a8r8g8b8_from_float, - .mipmap = gdk_mipmap_guint8_4, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_R8G8B8A8] = { .name = "RGBA8", @@ -633,7 +667,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r8g8b8a8_to_float, .from_float = r8g8b8a8_from_float, - .mipmap = gdk_mipmap_guint8_4, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_A8B8G8R8] = { .name = "ABGR8", @@ -665,7 +700,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8b8g8r8_to_float, .from_float = a8b8g8r8_from_float, - .mipmap = gdk_mipmap_guint8_4, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_B8G8R8X8] = { .name = "BGRX8", @@ -698,7 +734,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = b8g8r8x8_to_float, .from_float = b8g8r8x8_from_float, - .mipmap = gdk_mipmap_guint8_4, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_X8R8G8B8] = { .name = "XRGB8", @@ -731,7 +768,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = x8r8g8b8_to_float, .from_float = x8r8g8b8_from_float, - .mipmap = gdk_mipmap_guint8_4, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_R8G8B8X8] = { .name = "RGBX8", @@ -763,7 +801,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r8g8b8x8_to_float, .from_float = r8g8b8x8_from_float, - .mipmap = gdk_mipmap_guint8_4, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_X8B8G8R8] = { .name = "XBGR8", @@ -796,7 +835,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = x8b8g8r8_to_float, .from_float = x8b8g8r8_from_float, - .mipmap = gdk_mipmap_guint8_4, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_R8G8B8] = { .name = "RGB8", @@ -828,7 +868,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r8g8b8_to_float, .from_float = r8g8b8_from_float, - .mipmap = gdk_mipmap_guint8_3, + .mipmap_nearest = gdk_mipmap_guint8_3_nearest, + .mipmap_linear = gdk_mipmap_guint8_3_linear, }, [GDK_MEMORY_B8G8R8] = { .name = "BGR8", @@ -861,7 +902,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = b8g8r8_to_float, .from_float = b8g8r8_from_float, - .mipmap = gdk_mipmap_guint8_3, + .mipmap_nearest = gdk_mipmap_guint8_3_nearest, + .mipmap_linear = gdk_mipmap_guint8_3_linear, }, [GDK_MEMORY_R16G16B16] = { .name = "RGB16", @@ -896,7 +938,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16_to_float, .from_float = r16g16b16_from_float, - .mipmap = gdk_mipmap_guint16_3, + .mipmap_nearest = gdk_mipmap_guint16_3_nearest, + .mipmap_linear = gdk_mipmap_guint16_3_linear, }, [GDK_MEMORY_R16G16B16A16_PREMULTIPLIED] = { .name = "RGBA16(p)", @@ -930,7 +973,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16a16_to_float, .from_float = r16g16b16a16_from_float, - .mipmap = gdk_mipmap_guint16_4, + .mipmap_nearest = gdk_mipmap_guint16_4_nearest, + .mipmap_linear = gdk_mipmap_guint16_4_linear, }, [GDK_MEMORY_R16G16B16A16] = { .name = "RGBA16", @@ -964,7 +1008,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16a16_to_float, .from_float = r16g16b16a16_from_float, - .mipmap = gdk_mipmap_guint16_4, + .mipmap_nearest = gdk_mipmap_guint16_4_nearest, + .mipmap_linear = gdk_mipmap_guint16_4_linear, }, [GDK_MEMORY_R16G16B16_FLOAT] = { .name = "RGBA16f", @@ -998,7 +1043,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16_float_to_float, .from_float = r16g16b16_float_from_float, - .mipmap = gdk_mipmap_half_float_3, + .mipmap_nearest = gdk_mipmap_half_float_3_nearest, + .mipmap_linear = gdk_mipmap_half_float_3_linear, }, [GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED] = { .name = "RGBA16f(p)", @@ -1031,7 +1077,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16a16_float_to_float, .from_float = r16g16b16a16_float_from_float, - .mipmap = gdk_mipmap_half_float_4, + .mipmap_nearest = gdk_mipmap_half_float_4_nearest, + .mipmap_linear = gdk_mipmap_half_float_4_linear, }, [GDK_MEMORY_R16G16B16A16_FLOAT] = { .name = "RGBA16f", @@ -1064,7 +1111,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16a16_float_to_float, .from_float = r16g16b16a16_float_from_float, - .mipmap = gdk_mipmap_half_float_4, + .mipmap_nearest = gdk_mipmap_half_float_4_nearest, + .mipmap_linear = gdk_mipmap_half_float_4_linear, }, [GDK_MEMORY_R32G32B32_FLOAT] = { .name = "RGB32f", @@ -1098,7 +1146,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r32g32b32_float_to_float, .from_float = r32g32b32_float_from_float, - .mipmap = gdk_mipmap_float_3, + .mipmap_nearest = gdk_mipmap_float_3_nearest, + .mipmap_linear = gdk_mipmap_float_3_linear, }, [GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED] = { .name = "RGBA32f(p)", @@ -1131,7 +1180,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r32g32b32a32_float_to_float, .from_float = r32g32b32a32_float_from_float, - .mipmap = gdk_mipmap_float_4, + .mipmap_nearest = gdk_mipmap_float_4_nearest, + .mipmap_linear = gdk_mipmap_float_4_linear, }, [GDK_MEMORY_R32G32B32A32_FLOAT] = { .name = "RGBA32f", @@ -1164,7 +1214,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r32g32b32a32_float_to_float, .from_float = r32g32b32a32_float_from_float, - .mipmap = gdk_mipmap_float_4, + .mipmap_nearest = gdk_mipmap_float_4_nearest, + .mipmap_linear = gdk_mipmap_float_4_linear, }, [GDK_MEMORY_G8A8_PREMULTIPLIED] = { .name = "GA8(p)", @@ -1196,7 +1247,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g8a8_premultiplied_to_float, .from_float = g8a8_premultiplied_from_float, - .mipmap = gdk_mipmap_guint8_2, + .mipmap_nearest = gdk_mipmap_guint8_2_nearest, + .mipmap_linear = gdk_mipmap_guint8_2_linear, }, [GDK_MEMORY_G8A8] = { .name = "GA8", @@ -1228,7 +1280,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g8a8_to_float, .from_float = g8a8_from_float, - .mipmap = gdk_mipmap_guint8_2, + .mipmap_nearest = gdk_mipmap_guint8_2_nearest, + .mipmap_linear = gdk_mipmap_guint8_2_linear, }, [GDK_MEMORY_G8] = { .name = "G8", @@ -1260,7 +1313,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g8_to_float, .from_float = g8_from_float, - .mipmap = gdk_mipmap_guint8_1, + .mipmap_nearest = gdk_mipmap_guint8_1_nearest, + .mipmap_linear = gdk_mipmap_guint8_1_linear, }, [GDK_MEMORY_G16A16_PREMULTIPLIED] = { .name = "GA16(p)", @@ -1295,7 +1349,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g16a16_premultiplied_to_float, .from_float = g16a16_premultiplied_from_float, - .mipmap = gdk_mipmap_guint16_2, + .mipmap_nearest = gdk_mipmap_guint16_2_nearest, + .mipmap_linear = gdk_mipmap_guint16_2_linear, }, [GDK_MEMORY_G16A16] = { .name = "GA16", @@ -1330,7 +1385,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g16a16_to_float, .from_float = g16a16_from_float, - .mipmap = gdk_mipmap_guint16_2, + .mipmap_nearest = gdk_mipmap_guint16_2_nearest, + .mipmap_linear = gdk_mipmap_guint16_2_linear, }, [GDK_MEMORY_G16] = { .name = "G16", @@ -1365,7 +1421,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g16_to_float, .from_float = g16_from_float, - .mipmap = gdk_mipmap_guint16_1, + .mipmap_nearest = gdk_mipmap_guint16_1_nearest, + .mipmap_linear = gdk_mipmap_guint16_1_linear, }, [GDK_MEMORY_A8] = { .name = "A8", @@ -1397,7 +1454,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8_to_float, .from_float = a8_from_float, - .mipmap = gdk_mipmap_guint8_1, + .mipmap_nearest = gdk_mipmap_guint8_1_nearest, + .mipmap_linear = gdk_mipmap_guint8_1_linear, }, [GDK_MEMORY_A16] = { .name = "A16", @@ -1432,7 +1490,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a16_to_float, .from_float = a16_from_float, - .mipmap = gdk_mipmap_guint16_1, + .mipmap_nearest = gdk_mipmap_guint16_1_nearest, + .mipmap_linear = gdk_mipmap_guint16_1_linear, }, [GDK_MEMORY_A16_FLOAT] = { .name = "A16f", @@ -1466,7 +1525,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a16_float_to_float, .from_float = a16_float_from_float, - .mipmap = gdk_mipmap_half_float_1, + .mipmap_nearest = gdk_mipmap_half_float_1_nearest, + .mipmap_linear = gdk_mipmap_half_float_1_linear, }, [GDK_MEMORY_A32_FLOAT] = { .name = "A32f", @@ -1500,7 +1560,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a32_float_to_float, .from_float = a32_float_from_float, - .mipmap = gdk_mipmap_float_1, + .mipmap_nearest = gdk_mipmap_float_1_nearest, + .mipmap_linear = gdk_mipmap_float_1_linear, } }; @@ -2400,12 +2461,13 @@ struct _MipmapData gsize src_width; gsize src_height; guint lod_level; + gboolean linear; gint rows_done; }; static void -gdk_memory_mipmap_same_format (gpointer data) +gdk_memory_mipmap_same_format_nearest (gpointer data) { MipmapData *mipmap = data; const GdkMemoryFormatDescription *desc = &memory_formats[mipmap->src_format]; @@ -2420,10 +2482,33 @@ gdk_memory_mipmap_same_format (gpointer data) guchar *dest = mipmap->dest + (y >> mipmap->lod_level) * mipmap->dest_stride; const guchar *src = mipmap->src + y * mipmap->src_stride; - desc->mipmap (dest, mipmap->dest_stride, - src, mipmap->src_stride, - mipmap->src_width, MIN (n, mipmap->src_height - y), - mipmap->lod_level); + desc->mipmap_nearest (dest, mipmap->dest_stride, + src, mipmap->src_stride, + mipmap->src_width, MIN (n, mipmap->src_height - y), + mipmap->lod_level); + } +} + +static void +gdk_memory_mipmap_same_format_linear (gpointer data) +{ + MipmapData *mipmap = data; + const GdkMemoryFormatDescription *desc = &memory_formats[mipmap->src_format]; + gsize n, y; + + n = 1 << mipmap->lod_level; + + for (y = g_atomic_int_add (&mipmap->rows_done, n); + y < mipmap->src_height; + y = g_atomic_int_add (&mipmap->rows_done, n)) + { + guchar *dest = mipmap->dest + (y >> mipmap->lod_level) * mipmap->dest_stride; + const guchar *src = mipmap->src + y * mipmap->src_stride; + + desc->mipmap_linear (dest, mipmap->dest_stride, + src, mipmap->src_stride, + mipmap->src_width, MIN (n, mipmap->src_height - y), + mipmap->lod_level); } } @@ -2451,10 +2536,16 @@ gdk_memory_mipmap_generic (gpointer data) guchar *dest = mipmap->dest + (y >> mipmap->lod_level) * mipmap->dest_stride; const guchar *src = mipmap->src + y * mipmap->src_stride; - desc->mipmap (tmp, (size + 7) & 7, - src, mipmap->src_stride, - mipmap->src_width, MIN (n, mipmap->src_height - y), - mipmap->lod_level); + if (mipmap->linear) + desc->mipmap_linear (tmp, (size + 7) & 7, + src, mipmap->src_stride, + mipmap->src_width, MIN (n, mipmap->src_height - y), + mipmap->lod_level); + else + desc->mipmap_nearest (tmp, (size + 7) & 7, + src, mipmap->src_stride, + mipmap->src_width, MIN (n, mipmap->src_height - y), + mipmap->lod_level); if (func) func (dest, tmp, dest_width); else @@ -2476,7 +2567,8 @@ gdk_memory_mipmap (guchar *dest, GdkMemoryFormat src_format, gsize src_width, gsize src_height, - guint lod_level) + guint lod_level, + gboolean linear) { MipmapData mipmap = { .dest = dest, @@ -2488,6 +2580,7 @@ gdk_memory_mipmap (guchar *dest, .src_width = src_width, .src_height = src_height, .lod_level = lod_level, + .linear = linear, .rows_done = 0, }; @@ -2495,7 +2588,10 @@ gdk_memory_mipmap (guchar *dest, if (dest_format == src_format) { - gdk_parallel_task_run (gdk_memory_mipmap_same_format, &mipmap); + if (linear) + gdk_parallel_task_run (gdk_memory_mipmap_same_format_linear, &mipmap); + else + gdk_parallel_task_run (gdk_memory_mipmap_same_format_nearest, &mipmap); } else { diff --git a/gdk/gdkmemoryformatprivate.h b/gdk/gdkmemoryformatprivate.h index 96c4189285..323ac927ab 100644 --- a/gdk/gdkmemoryformatprivate.h +++ b/gdk/gdkmemoryformatprivate.h @@ -118,7 +118,8 @@ void gdk_memory_mipmap (guchar GdkMemoryFormat src_format, gsize src_width, gsize src_height, - guint lod_level); + guint lod_level, + gboolean linear); G_END_DECLS diff --git a/gsk/gpu/gskgpucache.c b/gsk/gpu/gskgpucache.c index 84c52fb408..4c26210393 100644 --- a/gsk/gpu/gskgpucache.c +++ b/gsk/gpu/gskgpucache.c @@ -538,6 +538,7 @@ struct _GskGpuCachedTile GdkTexture *texture; guint lod_level; + gboolean lod_linear; gsize tile_id; /* atomic */ int use_count; /* We count the use by the cache (via the linked @@ -631,7 +632,10 @@ gsk_gpu_cached_tile_hash (gconstpointer data) { const GskGpuCachedTile *self = data; - return g_direct_hash (self->texture) ^ self->tile_id ^ (self->lod_level << 24); + return g_direct_hash (self->texture) ^ + self->tile_id ^ + (self->lod_level << 24) ^ + (self->lod_linear << 31); } static gboolean @@ -643,6 +647,7 @@ gsk_gpu_cached_tile_equal (gconstpointer data_a, return a->texture == b->texture && a->lod_level == b->lod_level && + a->lod_linear == b->lod_linear && a->tile_id == b->tile_id; } @@ -650,6 +655,7 @@ static GskGpuCachedTile * gsk_gpu_cached_tile_new (GskGpuCache *cache, GdkTexture *texture, guint lod_level, + gboolean lod_linear, guint tile_id, GskGpuImage *image, GdkColorState *color_state) @@ -659,6 +665,7 @@ gsk_gpu_cached_tile_new (GskGpuCache *cache, self = gsk_gpu_cached_new (cache, &GSK_GPU_CACHED_TILE_CLASS); self->texture = texture; self->lod_level = lod_level; + self->lod_linear = lod_linear; self->tile_id = tile_id; self->image = g_object_ref (image); self->color_state = gdk_color_state_ref (color_state); @@ -677,16 +684,18 @@ gsk_gpu_cached_tile_new (GskGpuCache *cache, } GskGpuImage * -gsk_gpu_cache_lookup_tile (GskGpuCache *self, - GdkTexture *texture, - guint lod_level, - gsize tile_id, - GdkColorState **out_color_state) +gsk_gpu_cache_lookup_tile (GskGpuCache *self, + GdkTexture *texture, + guint lod_level, + GskScalingFilter lod_filter, + gsize tile_id, + GdkColorState **out_color_state) { GskGpuCachedTile *tile; GskGpuCachedTile lookup = { .texture = texture, .lod_level = lod_level, + .lod_linear = lod_filter == GSK_SCALING_FILTER_TRILINEAR, .tile_id = tile_id }; @@ -705,16 +714,23 @@ gsk_gpu_cache_lookup_tile (GskGpuCache *self, } void -gsk_gpu_cache_cache_tile (GskGpuCache *self, - GdkTexture *texture, - guint lod_level, - gsize tile_id, - GskGpuImage *image, - GdkColorState *color_state) +gsk_gpu_cache_cache_tile (GskGpuCache *self, + GdkTexture *texture, + guint lod_level, + GskScalingFilter lod_filter, + gsize tile_id, + GskGpuImage *image, + GdkColorState *color_state) { GskGpuCachedTile *tile; - tile = gsk_gpu_cached_tile_new (self, texture, lod_level, tile_id, image, color_state); + tile = gsk_gpu_cached_tile_new (self, + texture, + lod_level, + lod_filter == GSK_SCALING_FILTER_TRILINEAR, + tile_id, + image, + color_state); gsk_gpu_cached_use (self, (GskGpuCached *) tile); } diff --git a/gsk/gpu/gskgpucacheprivate.h b/gsk/gpu/gskgpucacheprivate.h index afe7e58786..517cbbe824 100644 --- a/gsk/gpu/gskgpucacheprivate.h +++ b/gsk/gpu/gskgpucacheprivate.h @@ -78,11 +78,13 @@ void gsk_gpu_cache_cache_texture_image (GskGpuC GskGpuImage * gsk_gpu_cache_lookup_tile (GskGpuCache *self, GdkTexture *texture, guint lod_level, + GskScalingFilter lod_filter, gsize tile_id, GdkColorState **out_color_state); void gsk_gpu_cache_cache_tile (GskGpuCache *self, GdkTexture *texture, guint lod_level, + GskScalingFilter lod_filter, gsize tile_id, GskGpuImage *image, GdkColorState *color_state); diff --git a/gsk/gpu/gskgpuframe.c b/gsk/gpu/gskgpuframe.c index fb2789608c..6403ad982d 100644 --- a/gsk/gpu/gskgpuframe.c +++ b/gsk/gpu/gskgpuframe.c @@ -107,7 +107,7 @@ gsk_gpu_frame_default_upload_texture (GskGpuFrame *self, { GskGpuImage *image; - image = gsk_gpu_upload_texture_op_try (self, with_mipmap, 0, texture); + image = gsk_gpu_upload_texture_op_try (self, with_mipmap, 0, GSK_SCALING_FILTER_NEAREST, texture); return image; } diff --git a/gsk/gpu/gskgpunodeprocessor.c b/gsk/gpu/gskgpunodeprocessor.c index ca5849a1d3..52a4fe90ea 100644 --- a/gsk/gpu/gskgpunodeprocessor.c +++ b/gsk/gpu/gskgpunodeprocessor.c @@ -2002,7 +2002,7 @@ 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, lod_level, y * n_width + x, &tile_cs); + tile = gsk_gpu_cache_lookup_tile (cache, texture, lod_level, scaling_filter, y * n_width + x, &tile_cs); if (tile == NULL) { @@ -2013,7 +2013,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, need_mipmap, lod_level, subtex); + tile = gsk_gpu_upload_texture_op_try (self->frame, need_mipmap, lod_level, scaling_filter, subtex); g_object_unref (subtex); if (tile == NULL) { @@ -2029,7 +2029,7 @@ gsk_gpu_node_processor_draw_texture_tiles (GskGpuNodeProcessor *self, g_assert (tile_cs); } - gsk_gpu_cache_cache_tile (cache, texture, lod_level, y * n_width + x, tile, tile_cs); + gsk_gpu_cache_cache_tile (cache, texture, lod_level, scaling_filter, y * n_width + x, tile, tile_cs); } if (need_mipmap && @@ -2037,7 +2037,7 @@ gsk_gpu_node_processor_draw_texture_tiles (GskGpuNodeProcessor *self, { tile = gsk_gpu_copy_image (self->frame, self->ccs, tile, tile_cs, TRUE); tile_cs = self->ccs; - gsk_gpu_cache_cache_tile (cache, texture, lod_level, y * n_width + x, tile, tile_cs); + gsk_gpu_cache_cache_tile (cache, texture, lod_level, scaling_filter, 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); diff --git a/gsk/gpu/gskgpuuploadop.c b/gsk/gpu/gskgpuuploadop.c index 4ce95a4fd6..6f788f738e 100644 --- a/gsk/gpu/gskgpuuploadop.c +++ b/gsk/gpu/gskgpuuploadop.c @@ -215,6 +215,7 @@ struct _GskGpuUploadTextureOp GskGpuBuffer *buffer; GdkTexture *texture; guint lod_level; + GskScalingFilter lod_filter; }; static void @@ -238,7 +239,9 @@ gsk_gpu_upload_texture_op_print (GskGpuOp *op, gsk_gpu_print_op (string, indent, "upload-texture"); gsk_gpu_print_image (string, self->image); if (self->lod_level > 0) - g_string_append_printf (string, " @%ux ", 1 << self->lod_level); + g_string_append_printf (string, " @%ux %s", + 1 << self->lod_level, + self->lod_filter == GSK_SCALING_FILTER_TRILINEAR ? "linear" : "nearest"); gsk_gpu_print_newline (string); } @@ -272,7 +275,8 @@ gsk_gpu_upload_texture_op_draw (GskGpuOp *op, gdk_texture_get_format (self->texture), gdk_texture_get_width (self->texture), gdk_texture_get_height (self->texture), - self->lod_level); + self->lod_level, + self->lod_filter == GSK_SCALING_FILTER_TRILINEAR ? TRUE : FALSE); g_bytes_unref (bytes); } gdk_texture_downloader_free (downloader); @@ -320,10 +324,11 @@ static const GskGpuOpClass GSK_GPU_UPLOAD_TEXTURE_OP_CLASS = { }; GskGpuImage * -gsk_gpu_upload_texture_op_try (GskGpuFrame *frame, - gboolean with_mipmap, - guint lod_level, - GdkTexture *texture) +gsk_gpu_upload_texture_op_try (GskGpuFrame *frame, + gboolean with_mipmap, + guint lod_level, + GskScalingFilter lod_filter, + GdkTexture *texture) { GskGpuUploadTextureOp *self; GskGpuImage *image; @@ -369,6 +374,7 @@ gsk_gpu_upload_texture_op_try (GskGpuFrame *frame, self->texture = g_object_ref (texture); self->lod_level = lod_level; + self->lod_filter = lod_filter; self->image = image; return g_object_ref (self->image); diff --git a/gsk/gpu/gskgpuuploadopprivate.h b/gsk/gpu/gskgpuuploadopprivate.h index cbd077cb4e..452e8bd896 100644 --- a/gsk/gpu/gskgpuuploadopprivate.h +++ b/gsk/gpu/gskgpuuploadopprivate.h @@ -12,6 +12,7 @@ typedef void (* GskGpuCairoFunc) (gpointe GskGpuImage * gsk_gpu_upload_texture_op_try (GskGpuFrame *frame, gboolean with_mipmap, guint lod_level, + GskScalingFilter lod_filter, GdkTexture *texture); GskGpuImage * gsk_gpu_upload_cairo_op (GskGpuFrame *frame, From d3db28b3f4e394cee5ebd077a24fd234d3b7d072 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 6 Sep 2024 12:31:27 -0400 Subject: [PATCH 12/14] memory format: Add profiler marks Add profiler marks to our long-running threaded operations. To avoid spamming profiles too much, only report runs that take at least 0.5 ms. --- gdk/gdkmemoryformat.c | 83 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 15 deletions(-) diff --git a/gdk/gdkmemoryformat.c b/gdk/gdkmemoryformat.c index 712abcf787..01b1859224 100644 --- a/gdk/gdkmemoryformat.c +++ b/gdk/gdkmemoryformat.c @@ -25,11 +25,23 @@ #include "gdkcolorstateprivate.h" #include "gdkparalleltaskprivate.h" #include "gtk/gtkcolorutilsprivate.h" +#include "gdkprofilerprivate.h" #include "gsk/gl/fp16private.h" #include +/* Don't report quick (< 0.5 msec) runs */ +#define MIN_MARK_DURATION 500000 + +#define ADD_MARK(before,name,fmt,...) \ + if (GDK_PROFILER_IS_RUNNING) \ + { \ + gint64 duration = GDK_PROFILER_CURRENT_TIME - before; \ + if (duration > MIN_MARK_DURATION) \ + gdk_profiler_add_markf (before, duration, name, fmt, __VA_ARGS__); \ + } + G_STATIC_ASSERT ((1 << GDK_MEMORY_DEPTH_BITS) > GDK_N_DEPTHS); typedef struct _GdkMemoryFormatDescription GdkMemoryFormatDescription; @@ -2077,6 +2089,8 @@ gdk_memory_convert_generic (gpointer data) GdkFloatColorConvert convert_func2 = NULL; gboolean needs_premultiply, needs_unpremultiply; gsize y, n; + gint64 before = GDK_PROFILER_CURRENT_TIME; + gsize rows; convert_func = gdk_color_state_get_convert_to (mc->src_cs, mc->dest_cs); @@ -2104,9 +2118,9 @@ gdk_memory_convert_generic (gpointer data) tmp = g_malloc (sizeof (*tmp) * mc->width); n = 1; - for (y = g_atomic_int_add (&mc->rows_done, n); + for (y = g_atomic_int_add (&mc->rows_done, n), rows = 0; y < mc->height; - y = g_atomic_int_add (&mc->rows_done, n)) + y = g_atomic_int_add (&mc->rows_done, n), rows++) { const guchar *src_data = mc->src_data + y * mc->src_stride; guchar *dest_data = mc->dest_data + y * mc->dest_stride; @@ -2129,6 +2143,10 @@ gdk_memory_convert_generic (gpointer data) } g_free (tmp); + + ADD_MARK (before, + "Memory convert (thread)", "size %lux%lu, %lu rows", + mc->width, mc->height, rows); } void @@ -2333,13 +2351,19 @@ gdk_memory_convert_color_state_srgb_to_srgb_linear (gpointer data) { MemoryConvertColorState *mc = data; int y; + guint64 before = GDK_PROFILER_CURRENT_TIME; + gsize rows; - for (y = g_atomic_int_add (&mc->rows_done, 1); + for (y = g_atomic_int_add (&mc->rows_done, 1), rows = 0; y < mc->height; - y = g_atomic_int_add (&mc->rows_done, 1)) + y = g_atomic_int_add (&mc->rows_done, 1), rows++) { convert_srgb_to_srgb_linear (mc->data + y * mc->stride, mc->width); } + + ADD_MARK (before, + "Color state convert srgb->srgb-linear (thread)", "size %lux%lu, %lu rows", + mc->width, mc->height, rows); } static void @@ -2347,13 +2371,19 @@ gdk_memory_convert_color_state_srgb_linear_to_srgb (gpointer data) { MemoryConvertColorState *mc = data; int y; + guint64 before = GDK_PROFILER_CURRENT_TIME; + gsize rows; - for (y = g_atomic_int_add (&mc->rows_done, 1); + for (y = g_atomic_int_add (&mc->rows_done, 1), rows = 0; y < mc->height; - y = g_atomic_int_add (&mc->rows_done, 1)) + y = g_atomic_int_add (&mc->rows_done, 1), rows++) { convert_srgb_linear_to_srgb (mc->data + y * mc->stride, mc->width); } + + ADD_MARK (before, + "Color state convert srgb-linear->srgb (thread)", "size %lux%lu, %lu rows", + mc->width, mc->height, rows); } static void @@ -2365,6 +2395,8 @@ gdk_memory_convert_color_state_generic (gpointer user_data) GdkFloatColorConvert convert_func2 = NULL; float (*tmp)[4]; int y; + guint64 before = GDK_PROFILER_CURRENT_TIME; + gsize rows; convert_func = gdk_color_state_get_convert_to (mc->src_cs, mc->dest_cs); @@ -2382,9 +2414,9 @@ gdk_memory_convert_color_state_generic (gpointer user_data) tmp = g_malloc (sizeof (*tmp) * mc->width); - for (y = g_atomic_int_add (&mc->rows_done, 1); + for (y = g_atomic_int_add (&mc->rows_done, 1), rows = 0; y < mc->height; - y = g_atomic_int_add (&mc->rows_done, 1)) + y = g_atomic_int_add (&mc->rows_done, 1), rows++) { guchar *data = mc->data + y * mc->stride; @@ -2406,6 +2438,10 @@ gdk_memory_convert_color_state_generic (gpointer user_data) } g_free (tmp); + + ADD_MARK (before, + "Color state convert (thread)", "size %lux%lu, %lu rows", + mc->width, mc->height, rows); } void @@ -2472,12 +2508,14 @@ gdk_memory_mipmap_same_format_nearest (gpointer data) MipmapData *mipmap = data; const GdkMemoryFormatDescription *desc = &memory_formats[mipmap->src_format]; gsize n, y; + guint64 before = GDK_PROFILER_CURRENT_TIME; + gsize rows; n = 1 << mipmap->lod_level; - for (y = g_atomic_int_add (&mipmap->rows_done, n); + for (y = g_atomic_int_add (&mipmap->rows_done, n), rows = 0; y < mipmap->src_height; - y = g_atomic_int_add (&mipmap->rows_done, n)) + y = g_atomic_int_add (&mipmap->rows_done, n), rows++) { guchar *dest = mipmap->dest + (y >> mipmap->lod_level) * mipmap->dest_stride; const guchar *src = mipmap->src + y * mipmap->src_stride; @@ -2487,6 +2525,10 @@ gdk_memory_mipmap_same_format_nearest (gpointer data) mipmap->src_width, MIN (n, mipmap->src_height - y), mipmap->lod_level); } + + ADD_MARK (before, + "Mipmap nearest (thread)", "size %lux%lu, lod %u, %lu rows", + mipmap->src_width, mipmap->src_height, mipmap->lod_level, rows); } static void @@ -2495,12 +2537,14 @@ gdk_memory_mipmap_same_format_linear (gpointer data) MipmapData *mipmap = data; const GdkMemoryFormatDescription *desc = &memory_formats[mipmap->src_format]; gsize n, y; + guint64 before = GDK_PROFILER_CURRENT_TIME; + gsize rows; n = 1 << mipmap->lod_level; - for (y = g_atomic_int_add (&mipmap->rows_done, n); + for (y = g_atomic_int_add (&mipmap->rows_done, n), rows = 0; y < mipmap->src_height; - y = g_atomic_int_add (&mipmap->rows_done, n)) + y = g_atomic_int_add (&mipmap->rows_done, n), rows++) { guchar *dest = mipmap->dest + (y >> mipmap->lod_level) * mipmap->dest_stride; const guchar *src = mipmap->src + y * mipmap->src_stride; @@ -2510,6 +2554,10 @@ gdk_memory_mipmap_same_format_linear (gpointer data) mipmap->src_width, MIN (n, mipmap->src_height - y), mipmap->lod_level); } + + ADD_MARK (before, + "Mipmap linear (thread)", "size %lux%lu, lod %u, %lu rows", + mipmap->src_width, mipmap->src_height, mipmap->lod_level, rows); } static void @@ -2522,6 +2570,8 @@ gdk_memory_mipmap_generic (gpointer data) gsize size; guchar *tmp; gsize n, y; + guint64 before = GDK_PROFILER_CURRENT_TIME; + gsize rows; n = 1 << mipmap->lod_level; dest_width = (mipmap->src_width + n - 1) >> mipmap->lod_level; @@ -2529,9 +2579,9 @@ gdk_memory_mipmap_generic (gpointer data) tmp = g_malloc (size); func = get_fast_conversion_func (mipmap->dest_format, mipmap->src_format); - for (y = g_atomic_int_add (&mipmap->rows_done, n); + for (y = g_atomic_int_add (&mipmap->rows_done, n), rows = 0; y < mipmap->src_height; - y = g_atomic_int_add (&mipmap->rows_done, n)) + y = g_atomic_int_add (&mipmap->rows_done, n), rows++) { guchar *dest = mipmap->dest + (y >> mipmap->lod_level) * mipmap->dest_stride; const guchar *src = mipmap->src + y * mipmap->src_stride; @@ -2552,10 +2602,13 @@ gdk_memory_mipmap_generic (gpointer data) gdk_memory_convert (dest, mipmap->dest_stride, mipmap->dest_format, GDK_COLOR_STATE_SRGB, tmp, (size + 7) & 7, mipmap->src_format, GDK_COLOR_STATE_SRGB, dest_width, 1); - } g_free (tmp); + + ADD_MARK (before, + "Mipmap generic (thread)", "size %lux%lu, lod %u, %lu rows", + mipmap->src_width, mipmap->src_height, mipmap->lod_level, rows); } void From 171612671f6abe19708fa8ef52eb4c12edcf2e13 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 6 Sep 2024 13:46:46 -0400 Subject: [PATCH 13/14] demo: Beef up the image scaling demo Add buttons for loading the Portland Rose, and a nameless large png. Make them load the texture in a thread, to demonstrate better handling of large images. --- demos/gtk-demo/demo.gresource.xml | 3 + demos/gtk-demo/image_scaling.c | 164 +++++++++++++++++++-- demos/gtk-demo/large-image-thumbnail.png | Bin 0 -> 16973 bytes demos/gtk-demo/large-image.png | Bin 0 -> 636767 bytes demos/gtk-demo/portland-rose-thumbnail.png | Bin 0 -> 3618 bytes 5 files changed, 153 insertions(+), 14 deletions(-) create mode 100644 demos/gtk-demo/large-image-thumbnail.png create mode 100644 demos/gtk-demo/large-image.png create mode 100644 demos/gtk-demo/portland-rose-thumbnail.png diff --git a/demos/gtk-demo/demo.gresource.xml b/demos/gtk-demo/demo.gresource.xml index 2998560b02..295c056f0b 100644 --- a/demos/gtk-demo/demo.gresource.xml +++ b/demos/gtk-demo/demo.gresource.xml @@ -454,6 +454,9 @@ icons/16x16/categories/applications-other.png icons/48x48/status/starred.png data/scalable/apps/org.gtk.Demo4.svg + portland-rose-thumbnail.png + large-image-thumbnail.png + large-image.png help-overlay.ui diff --git a/demos/gtk-demo/image_scaling.c b/demos/gtk-demo/image_scaling.c index 65c344f8bf..7a541292b9 100644 --- a/demos/gtk-demo/image_scaling.c +++ b/demos/gtk-demo/image_scaling.c @@ -14,6 +14,103 @@ #include #include "demo3widget.h" +static GtkWidget *window = NULL; +static GCancellable *cancellable = NULL; + +static void +load_texture (GTask *task, + gpointer source_object, + gpointer task_data, + GCancellable *cable) +{ + GFile *file = task_data; + GdkTexture *texture; + GError *error = NULL; + + texture = gdk_texture_new_from_file (file, &error); + + if (texture) + g_task_return_pointer (task, texture, g_object_unref); + else + g_task_return_error (task, error); +} + +static void +set_wait_cursor (GtkWidget *widget) +{ + gtk_widget_set_cursor_from_name (GTK_WIDGET (gtk_widget_get_root (widget)), "wait"); +} + +static void +unset_wait_cursor (GtkWidget *widget) +{ + gtk_widget_set_cursor (GTK_WIDGET (gtk_widget_get_root (widget)), NULL); +} + +static void +texture_loaded (GObject *source, + GAsyncResult *result, + gpointer data) +{ + GdkTexture *texture; + GError *error = NULL; + + texture = g_task_propagate_pointer (G_TASK (result), &error); + + if (!texture) + { + g_print ("%s\n", error->message); + g_error_free (error); + return; + } + + if (!window) + { + g_object_unref (texture); + return; + } + + unset_wait_cursor (GTK_WIDGET (data)); + + g_object_set (G_OBJECT (data), "texture", texture, NULL); +} + +static void +open_file_async (GFile *file, + GtkWidget *demo) +{ + GTask *task; + + set_wait_cursor (demo); + + task = g_task_new (demo, cancellable, texture_loaded, demo); + g_task_set_task_data (task, g_object_ref (file), g_object_unref); + g_task_run_in_thread (task, load_texture); + g_object_unref (task); +} + +static void +open_portland_rose (GtkWidget *button, + GtkWidget *demo) +{ + GFile *file; + + file = g_file_new_for_uri ("resource:///transparent/portland-rose.jpg"); + open_file_async (file, demo); + g_object_unref (file); +} + +static void +open_large_image (GtkWidget *button, + GtkWidget *demo) +{ + GFile *file; + + file = g_file_new_for_uri ("resource:///org/gtk/Demo4/large-image.png"); + open_file_async (file, demo); + g_object_unref (file); +} + static void file_opened (GObject *source, GAsyncResult *result, @@ -21,7 +118,6 @@ file_opened (GObject *source, { GFile *file; GError *error = NULL; - GdkTexture *texture; file = gtk_file_dialog_open_finish (GTK_FILE_DIALOG (source), result, &error); @@ -32,17 +128,9 @@ file_opened (GObject *source, return; } - texture = gdk_texture_new_from_file (file, &error); - g_object_unref (file); - if (!texture) - { - g_print ("%s\n", error->message); - g_error_free (error); - return; - } + open_file_async (file, data); - g_object_set (G_OBJECT (data), "texture", texture, NULL); - g_object_unref (texture); + g_object_unref (file); } static void @@ -116,11 +204,26 @@ transform_from (GBinding *binding, return TRUE; } +static void +free_cancellable (gpointer data) +{ + g_cancellable_cancel (cancellable); + g_clear_object (&cancellable); +} + +static gboolean +cancel_load (GtkWidget *widget, + GVariant *args, + gpointer data) +{ + unset_wait_cursor (widget); + g_cancellable_cancel (G_CANCELLABLE (data)); + return TRUE; +} + GtkWidget * do_image_scaling (GtkWidget *do_widget) { - static GtkWidget *window = NULL; - if (!window) { GtkWidget *box; @@ -130,6 +233,7 @@ do_image_scaling (GtkWidget *do_widget) GtkWidget *scale; GtkWidget *dropdown; GtkWidget *button; + GtkEventController *controller; window = gtk_window_new (); gtk_window_set_title (GTK_WINDOW (window), "Image Scaling"); @@ -138,6 +242,20 @@ do_image_scaling (GtkWidget *do_widget) gtk_widget_get_display (do_widget)); g_object_add_weak_pointer (G_OBJECT (window), (gpointer *)&window); + cancellable = g_cancellable_new (); + g_object_set_data_full (G_OBJECT (window), "cancellable", + cancellable, free_cancellable); + + controller = gtk_shortcut_controller_new (); + gtk_shortcut_controller_add_shortcut (GTK_SHORTCUT_CONTROLLER (controller), + gtk_shortcut_new ( + gtk_keyval_trigger_new (GDK_KEY_Escape, 0), + gtk_callback_action_new (cancel_load, cancellable, NULL) + )); + gtk_shortcut_controller_set_scope (GTK_SHORTCUT_CONTROLLER (controller), + GTK_SHORTCUT_SCOPE_GLOBAL); + gtk_widget_add_controller (window, controller); + box = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0); gtk_window_set_child (GTK_WINDOW (window), box); @@ -156,6 +274,22 @@ do_image_scaling (GtkWidget *do_widget) g_signal_connect (button, "clicked", G_CALLBACK (open_file), widget); gtk_box_append (GTK_BOX (box2), button); + button = gtk_button_new (); + gtk_button_set_child (GTK_BUTTON (button), + gtk_image_new_from_resource ("/org/gtk/Demo4/portland-rose-thumbnail.png")); + gtk_widget_add_css_class (button, "image-button"); + gtk_widget_set_tooltip_text (button, "Portland Rose"); + g_signal_connect (button, "clicked", G_CALLBACK (open_portland_rose), widget); + gtk_box_append (GTK_BOX (box2), button); + + button = gtk_button_new (); + gtk_button_set_child (GTK_BUTTON (button), + gtk_image_new_from_resource ("/org/gtk/Demo4/large-image-thumbnail.png")); + gtk_widget_add_css_class (button, "image-button"); + gtk_widget_set_tooltip_text (button, "Large image"); + g_signal_connect (button, "clicked", G_CALLBACK (open_large_image), widget); + gtk_box_append (GTK_BOX (box2), button); + button = gtk_button_new_from_icon_name ("object-rotate-right-symbolic"); gtk_widget_set_tooltip_text (button, "Rotate"); g_signal_connect (button, "clicked", G_CALLBACK (rotate), widget); @@ -191,7 +325,9 @@ do_image_scaling (GtkWidget *do_widget) if (!gtk_widget_get_visible (window)) gtk_widget_set_visible (window, TRUE); else - gtk_window_destroy (GTK_WINDOW (window)); + { + gtk_window_destroy (GTK_WINDOW (window)); + } return window; } diff --git a/demos/gtk-demo/large-image-thumbnail.png b/demos/gtk-demo/large-image-thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..d383d8bbf4e3b412cf41e930d185ca58c1a99b7d GIT binary patch literal 16973 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 MN;ecs~4gdfE literal 0 HcmV?d00001 diff --git a/demos/gtk-demo/large-image.png b/demos/gtk-demo/large-image.png new file mode 100644 index 0000000000000000000000000000000000000000..0ddeaef501cde186604b3734bf9dd1d7c84954d3 GIT binary patch literal 636767 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 NKl{Enzn=8W{|6|Y)p-B_ literal 0 HcmV?d00001 diff --git a/demos/gtk-demo/portland-rose-thumbnail.png b/demos/gtk-demo/portland-rose-thumbnail.png new file mode 100644 index 0000000000000000000000000000000000000000..d3838ddb174b65af37be4166b6d8cc5803c81ea8 GIT binary patch literal 3618 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 Suy6r%14er0y4Bj3qW=dZR}bv~ literal 0 HcmV?d00001 From 03b19d886171fdd22538743e4ac2d06753e66357 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Fri, 6 Sep 2024 15:56:06 -0400 Subject: [PATCH 14/14] docs: Recommend glycin for image loading Best to send people elsewhere to avoid misunderstandings of our api. --- gdk/gdktexture.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/gdk/gdktexture.c b/gdk/gdktexture.c index e4e7baa001..318265efd7 100644 --- a/gdk/gdktexture.c +++ b/gdk/gdktexture.c @@ -33,7 +33,18 @@ * * `GdkTexture` is an immutable object: That means you cannot change * anything about it other than increasing the reference count via - * [method@GObject.Object.ref], and consequently, it is a thread-safe object. + * [method@GObject.Object.ref], and consequently, it is a threadsafe object. + * + * GDK provides a number of threadsafe texture loading functions: + * [ctor@Gdk.Texture.new_from_resource], + * [ctor@Gdk.Texture.new_from_bytes], + * [ctor@Gdk.Texture.new_from_file], + * [ctor@Gdk.Texture.new_from_filename], + * [ctor@Gdk.Texture.new_for_pixbuf]. Note that these are meant for loading + * icons and resources that are shipped with the toolkit or application. It + * is recommended that you use a dedicated image loading framework such as + * [glycin](https://lib.rs/crates/glycin), if you need to load untrusted image + * data. */ #include "config.h"