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 0000000000..d383d8bbf4 Binary files /dev/null and b/demos/gtk-demo/large-image-thumbnail.png differ diff --git a/demos/gtk-demo/large-image.png b/demos/gtk-demo/large-image.png new file mode 100644 index 0000000000..0ddeaef501 Binary files /dev/null and b/demos/gtk-demo/large-image.png differ diff --git a/demos/gtk-demo/portland-rose-thumbnail.png b/demos/gtk-demo/portland-rose-thumbnail.png new file mode 100644 index 0000000000..d3838ddb17 Binary files /dev/null and b/demos/gtk-demo/portland-rose-thumbnail.png differ diff --git a/gdk/gdkmemoryformat.c b/gdk/gdkmemoryformat.c index 6f513d8737..01b1859224 100644 --- a/gdk/gdkmemoryformat.c +++ b/gdk/gdkmemoryformat.c @@ -22,14 +22,26 @@ #include "gdkmemoryformatprivate.h" #include "gdkdmabuffourccprivate.h" -#include "gdkglcontextprivate.h" #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; @@ -313,6 +325,88 @@ 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 ## _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; \ +\ + 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; @@ -346,6 +440,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_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 @@ -387,6 +483,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = b8g8r8a8_premultiplied_to_float, .from_float = b8g8r8a8_premultiplied_from_float, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_A8R8G8B8_PREMULTIPLIED] = { .name = "ARGB8(p)", @@ -418,6 +516,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8r8g8b8_premultiplied_to_float, .from_float = a8r8g8b8_premultiplied_from_float, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_R8G8B8A8_PREMULTIPLIED] = { .name = "RGBA8(p)", @@ -448,6 +548,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r8g8b8a8_premultiplied_to_float, .from_float = r8g8b8a8_premultiplied_from_float, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_A8B8G8R8_PREMULTIPLIED] = { .name = "ABGR8(p)", @@ -479,6 +581,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8b8g8r8_premultiplied_to_float, .from_float = a8b8g8r8_premultiplied_from_float, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_B8G8R8A8] = { .name = "BGRA8", @@ -510,6 +614,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = b8g8r8a8_to_float, .from_float = b8g8r8a8_from_float, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_A8R8G8B8] = { .name = "ARGB8", @@ -541,6 +647,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8r8g8b8_to_float, .from_float = a8r8g8b8_from_float, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_R8G8B8A8] = { .name = "RGBA8", @@ -571,6 +679,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r8g8b8a8_to_float, .from_float = r8g8b8a8_from_float, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_A8B8G8R8] = { .name = "ABGR8", @@ -602,6 +712,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8b8g8r8_to_float, .from_float = a8b8g8r8_from_float, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_B8G8R8X8] = { .name = "BGRX8", @@ -634,6 +746,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = b8g8r8x8_to_float, .from_float = b8g8r8x8_from_float, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_X8R8G8B8] = { .name = "XRGB8", @@ -666,6 +780,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = x8r8g8b8_to_float, .from_float = x8r8g8b8_from_float, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_R8G8B8X8] = { .name = "RGBX8", @@ -697,6 +813,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r8g8b8x8_to_float, .from_float = r8g8b8x8_from_float, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_X8B8G8R8] = { .name = "XBGR8", @@ -729,6 +847,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = x8b8g8r8_to_float, .from_float = x8b8g8r8_from_float, + .mipmap_nearest = gdk_mipmap_guint8_4_nearest, + .mipmap_linear = gdk_mipmap_guint8_4_linear, }, [GDK_MEMORY_R8G8B8] = { .name = "RGB8", @@ -760,6 +880,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r8g8b8_to_float, .from_float = r8g8b8_from_float, + .mipmap_nearest = gdk_mipmap_guint8_3_nearest, + .mipmap_linear = gdk_mipmap_guint8_3_linear, }, [GDK_MEMORY_B8G8R8] = { .name = "BGR8", @@ -792,6 +914,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = b8g8r8_to_float, .from_float = b8g8r8_from_float, + .mipmap_nearest = gdk_mipmap_guint8_3_nearest, + .mipmap_linear = gdk_mipmap_guint8_3_linear, }, [GDK_MEMORY_R16G16B16] = { .name = "RGB16", @@ -826,6 +950,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16_to_float, .from_float = r16g16b16_from_float, + .mipmap_nearest = gdk_mipmap_guint16_3_nearest, + .mipmap_linear = gdk_mipmap_guint16_3_linear, }, [GDK_MEMORY_R16G16B16A16_PREMULTIPLIED] = { .name = "RGBA16(p)", @@ -859,6 +985,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16a16_to_float, .from_float = r16g16b16a16_from_float, + .mipmap_nearest = gdk_mipmap_guint16_4_nearest, + .mipmap_linear = gdk_mipmap_guint16_4_linear, }, [GDK_MEMORY_R16G16B16A16] = { .name = "RGBA16", @@ -892,6 +1020,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16a16_to_float, .from_float = r16g16b16a16_from_float, + .mipmap_nearest = gdk_mipmap_guint16_4_nearest, + .mipmap_linear = gdk_mipmap_guint16_4_linear, }, [GDK_MEMORY_R16G16B16_FLOAT] = { .name = "RGBA16f", @@ -925,6 +1055,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16_float_to_float, .from_float = r16g16b16_float_from_float, + .mipmap_nearest = gdk_mipmap_half_float_3_nearest, + .mipmap_linear = gdk_mipmap_half_float_3_linear, }, [GDK_MEMORY_R16G16B16A16_FLOAT_PREMULTIPLIED] = { .name = "RGBA16f(p)", @@ -957,6 +1089,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16a16_float_to_float, .from_float = r16g16b16a16_float_from_float, + .mipmap_nearest = gdk_mipmap_half_float_4_nearest, + .mipmap_linear = gdk_mipmap_half_float_4_linear, }, [GDK_MEMORY_R16G16B16A16_FLOAT] = { .name = "RGBA16f", @@ -989,6 +1123,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r16g16b16a16_float_to_float, .from_float = r16g16b16a16_float_from_float, + .mipmap_nearest = gdk_mipmap_half_float_4_nearest, + .mipmap_linear = gdk_mipmap_half_float_4_linear, }, [GDK_MEMORY_R32G32B32_FLOAT] = { .name = "RGB32f", @@ -1022,6 +1158,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r32g32b32_float_to_float, .from_float = r32g32b32_float_from_float, + .mipmap_nearest = gdk_mipmap_float_3_nearest, + .mipmap_linear = gdk_mipmap_float_3_linear, }, [GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED] = { .name = "RGBA32f(p)", @@ -1054,6 +1192,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r32g32b32a32_float_to_float, .from_float = r32g32b32a32_float_from_float, + .mipmap_nearest = gdk_mipmap_float_4_nearest, + .mipmap_linear = gdk_mipmap_float_4_linear, }, [GDK_MEMORY_R32G32B32A32_FLOAT] = { .name = "RGBA32f", @@ -1086,6 +1226,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = r32g32b32a32_float_to_float, .from_float = r32g32b32a32_float_from_float, + .mipmap_nearest = gdk_mipmap_float_4_nearest, + .mipmap_linear = gdk_mipmap_float_4_linear, }, [GDK_MEMORY_G8A8_PREMULTIPLIED] = { .name = "GA8(p)", @@ -1117,6 +1259,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g8a8_premultiplied_to_float, .from_float = g8a8_premultiplied_from_float, + .mipmap_nearest = gdk_mipmap_guint8_2_nearest, + .mipmap_linear = gdk_mipmap_guint8_2_linear, }, [GDK_MEMORY_G8A8] = { .name = "GA8", @@ -1148,6 +1292,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g8a8_to_float, .from_float = g8a8_from_float, + .mipmap_nearest = gdk_mipmap_guint8_2_nearest, + .mipmap_linear = gdk_mipmap_guint8_2_linear, }, [GDK_MEMORY_G8] = { .name = "G8", @@ -1179,6 +1325,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g8_to_float, .from_float = g8_from_float, + .mipmap_nearest = gdk_mipmap_guint8_1_nearest, + .mipmap_linear = gdk_mipmap_guint8_1_linear, }, [GDK_MEMORY_G16A16_PREMULTIPLIED] = { .name = "GA16(p)", @@ -1213,6 +1361,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g16a16_premultiplied_to_float, .from_float = g16a16_premultiplied_from_float, + .mipmap_nearest = gdk_mipmap_guint16_2_nearest, + .mipmap_linear = gdk_mipmap_guint16_2_linear, }, [GDK_MEMORY_G16A16] = { .name = "GA16", @@ -1247,6 +1397,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g16a16_to_float, .from_float = g16a16_from_float, + .mipmap_nearest = gdk_mipmap_guint16_2_nearest, + .mipmap_linear = gdk_mipmap_guint16_2_linear, }, [GDK_MEMORY_G16] = { .name = "G16", @@ -1281,6 +1433,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = g16_to_float, .from_float = g16_from_float, + .mipmap_nearest = gdk_mipmap_guint16_1_nearest, + .mipmap_linear = gdk_mipmap_guint16_1_linear, }, [GDK_MEMORY_A8] = { .name = "A8", @@ -1312,6 +1466,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a8_to_float, .from_float = a8_from_float, + .mipmap_nearest = gdk_mipmap_guint8_1_nearest, + .mipmap_linear = gdk_mipmap_guint8_1_linear, }, [GDK_MEMORY_A16] = { .name = "A16", @@ -1346,6 +1502,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a16_to_float, .from_float = a16_from_float, + .mipmap_nearest = gdk_mipmap_guint16_1_nearest, + .mipmap_linear = gdk_mipmap_guint16_1_linear, }, [GDK_MEMORY_A16_FLOAT] = { .name = "A16f", @@ -1379,6 +1537,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a16_float_to_float, .from_float = a16_float_from_float, + .mipmap_nearest = gdk_mipmap_half_float_1_nearest, + .mipmap_linear = gdk_mipmap_half_float_1_linear, }, [GDK_MEMORY_A32_FLOAT] = { .name = "A32f", @@ -1412,6 +1572,8 @@ static const GdkMemoryFormatDescription memory_formats[] = { #endif .to_float = a32_float_to_float, .from_float = a32_float_from_float, + .mipmap_nearest = gdk_mipmap_float_1_nearest, + .mipmap_linear = gdk_mipmap_float_1_linear, } }; @@ -1850,119 +2012,98 @@ unpremultiply (float (*rgba)[4], } } -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 void (* FastConversionFunc) (guchar *dest, + const guchar *src, + gsize n); + +static FastConversionFunc +get_fast_conversion_func (GdkMemoryFormat dest_format, + GdkMemoryFormat src_format) { - const GdkMemoryFormatDescription *dest_desc = &memory_formats[dest_format]; - const GdkMemoryFormatDescription *src_desc = &memory_formats[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; +} + +typedef struct _MemoryConvert MemoryConvert; + +struct _MemoryConvert +{ + 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; - void (*func) (guchar *, const guchar *, gsize) = NULL; gboolean needs_premultiply, needs_unpremultiply; + gsize y, n; + gint64 before = GDK_PROFILER_CURRENT_TIME; + gsize rows; - 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 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) - { - 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; @@ -1974,31 +2115,134 @@ 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), rows = 0; + y < mc->height; + y = g_atomic_int_add (&mc->rows_done, n), rows++) { - 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); + + ADD_MARK (before, + "Memory convert (thread)", "size %lux%lu, %lu rows", + mc->width, mc->height, rows); } +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); +} + +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, @@ -2103,43 +2347,101 @@ 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; + guint64 before = GDK_PROFILER_CURRENT_TIME; + gsize rows; + + for (y = g_atomic_int_add (&mc->rows_done, 1), rows = 0; + y < mc->height; + y = g_atomic_int_add (&mc->rows_done, 1), rows++) { - 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); } + + ADD_MARK (before, + "Color state convert srgb->srgb-linear (thread)", "size %lux%lu, %lu rows", + mc->width, mc->height, rows); } 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; + guint64 before = GDK_PROFILER_CURRENT_TIME; + gsize rows; + + for (y = g_atomic_int_add (&mc->rows_done, 1), rows = 0; + y < mc->height; + y = g_atomic_int_add (&mc->rows_done, 1), rows++) { - convert_srgb_linear_to_srgb (data, height * width); + convert_srgb_linear_to_srgb (mc->data + y * mc->stride, mc->width); } - else + + ADD_MARK (before, + "Color state convert srgb-linear->srgb (thread)", "size %lux%lu, %lu rows", + mc->width, mc->height, rows); +} + +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; + guint64 before = GDK_PROFILER_CURRENT_TIME; + gsize rows; + + 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), rows = 0; + y < mc->height; + y = g_atomic_int_add (&mc->rows_done, 1), rows++) + { + 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); + + ADD_MARK (before, + "Color state convert (thread)", "size %lux%lu, %lu rows", + mc->width, mc->height, rows); } void @@ -2151,10 +2453,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; @@ -2163,52 +2470,185 @@ 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); + } + else + { + gdk_parallel_task_run (gdk_memory_convert_color_state_generic, &mc); + } +} + +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; + gboolean linear; + + gint rows_done; +}; + +static void +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), rows = 0; + y < mipmap->src_height; + 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; + + desc->mipmap_nearest (dest, mipmap->dest_stride, + src, mipmap->src_stride, + mipmap->src_width, MIN (n, mipmap->src_height - y), + mipmap->lod_level); } - convert_func = gdk_color_state_get_convert_to (src_cs, dest_cs); + ADD_MARK (before, + "Mipmap nearest (thread)", "size %lux%lu, lod %u, %lu rows", + mipmap->src_width, mipmap->src_height, mipmap->lod_level, rows); +} - if (!convert_func) +static void +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), rows = 0; + y < mipmap->src_height; + y = g_atomic_int_add (&mipmap->rows_done, n), rows++) { - convert_func2 = gdk_color_state_get_convert_from (dest_cs, src_cs); + 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); } - if (!convert_func && !convert_func2) + 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 +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; + guint64 before = GDK_PROFILER_CURRENT_TIME; + gsize rows; + + 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), rows = 0; + y < mipmap->src_height; + y = g_atomic_int_add (&mipmap->rows_done, n), rows++) { - 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); - } + guchar *dest = mipmap->dest + (y >> mipmap->lod_level) * mipmap->dest_stride; + const guchar *src = mipmap->src + y * mipmap->src_stride; - 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; + 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 + 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 +gdk_memory_mipmap (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, + gboolean linear) +{ + 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, + .linear = linear, + .rows_done = 0, + }; + + g_assert (lod_level > 0); + + if (dest_format == src_format) + { + 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 + { + gdk_parallel_task_run (gdk_memory_mipmap_generic, &mipmap); + } +} + diff --git a/gdk/gdkmemoryformatprivate.h b/gdk/gdkmemoryformatprivate.h index 57f55bd5fd..323ac927ab 100644 --- a/gdk/gdkmemoryformatprivate.h +++ b/gdk/gdkmemoryformatprivate.h @@ -110,6 +110,16 @@ 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 dest_format, + const guchar *src, + gsize src_stride, + GdkMemoryFormat src_format, + gsize src_width, + gsize src_height, + guint lod_level, + gboolean linear); G_END_DECLS 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/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" diff --git a/gdk/meson.build b/gdk/meson.build index 78609a9870..b75349f291 100644 --- a/gdk/meson.build +++ b/gdk/meson.build @@ -52,23 +52,24 @@ gdk_public_sources = files([ 'gdkmonitor.c', 'gdkpaintable.c', 'gdkpango.c', + 'gdkparalleltask.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', diff --git a/gsk/gpu/gskgpucache.c b/gsk/gpu/gskgpucache.c index 724b9ecf57..4c26210393 100644 --- a/gsk/gpu/gskgpucache.c +++ b/gsk/gpu/gskgpucache.c @@ -537,6 +537,8 @@ struct _GskGpuCachedTile GskGpuCached parent; 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 @@ -630,7 +632,10 @@ 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) ^ + (self->lod_linear << 31); } static gboolean @@ -641,12 +646,16 @@ 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->lod_linear == b->lod_linear && a->tile_id == b->tile_id; } 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) @@ -655,6 +664,8 @@ 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); @@ -673,14 +684,18 @@ gsk_gpu_cached_tile_new (GskGpuCache *cache, } GskGpuImage * -gsk_gpu_cache_lookup_tile (GskGpuCache *self, - GdkTexture *texture, - 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 }; @@ -699,15 +714,23 @@ gsk_gpu_cache_lookup_tile (GskGpuCache *self, } void -gsk_gpu_cache_cache_tile (GskGpuCache *self, - GdkTexture *texture, - guint 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, 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 9164504c76..517cbbe824 100644 --- a/gsk/gpu/gskgpucacheprivate.h +++ b/gsk/gpu/gskgpucacheprivate.h @@ -77,11 +77,15 @@ void gsk_gpu_cache_cache_texture_image (GskGpuC GdkColorState *color_state); 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 tile_id, + 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 6659063797..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, 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 6ab7c120fb..52a4fe90ea 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, scaling_filter, 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, scaling_filter, 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, scaling_filter, 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, 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 ad4b9aa02d..6f788f738e 100644 --- a/gsk/gpu/gskgpuuploadop.c +++ b/gsk/gpu/gskgpuuploadop.c @@ -214,6 +214,8 @@ struct _GskGpuUploadTextureOp GskGpuImage *image; GskGpuBuffer *buffer; GdkTexture *texture; + guint lod_level; + GskScalingFilter lod_filter; }; static void @@ -236,6 +238,10 @@ 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 %s", + 1 << self->lod_level, + self->lod_filter == GSK_SCALING_FILTER_TRILINEAR ? "linear" : "nearest"); gsk_gpu_print_newline (string); } @@ -248,9 +254,31 @@ 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)); - gdk_texture_downloader_download_into (downloader, data, stride); + 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 + { + 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, + self->lod_filter == GSK_SCALING_FILTER_TRILINEAR ? TRUE : FALSE); + g_bytes_unref (bytes); + } gdk_texture_downloader_free (downloader); } @@ -296,9 +324,11 @@ static const GskGpuOpClass GSK_GPU_UPLOAD_TEXTURE_OP_CLASS = { }; GskGpuImage * -gsk_gpu_upload_texture_op_try (GskGpuFrame *frame, - gboolean with_mipmap, - 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; @@ -311,8 +341,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 +373,8 @@ 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->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 f29ca24250..452e8bd896 100644 --- a/gsk/gpu/gskgpuuploadopprivate.h +++ b/gsk/gpu/gskgpuuploadopprivate.h @@ -11,6 +11,8 @@ 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,