Merge branch 'wip/otte/cpu-mipmap' into 'main'

gpu: Allow uploading of mipmap levels when tiling

See merge request GNOME/gtk!7657
This commit is contained in:
Matthias Clasen 2024-09-06 22:14:37 +00:00
commit 0274294a6f
17 changed files with 1011 additions and 223 deletions

View File

@ -454,6 +454,9 @@
<file>icons/16x16/categories/applications-other.png</file>
<file>icons/48x48/status/starred.png</file>
<file alias="icons/scalable/apps/org.gtk.Demo4.svg">data/scalable/apps/org.gtk.Demo4.svg</file>
<file>portland-rose-thumbnail.png</file>
<file>large-image-thumbnail.png</file>
<file compressed="true">large-image.png</file>
</gresource>
<gresource prefix="/org/gtk/Demo4/gtk">
<file preprocess="xml-stripblanks">help-overlay.ui</file>

View File

@ -14,6 +14,103 @@
#include <gtk/gtk.h>
#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));
}
return window;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 622 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

File diff suppressed because it is too large Load Diff

View File

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

86
gdk/gdkparalleltask.c Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#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 ();
}

View File

@ -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 <http://www.gnu.org/licenses/>.
*
* Authors: Benjamin Otte <otte@gnome.org>
*/
#pragma once
#include <glib.h>
G_BEGIN_DECLS
typedef void (* GdkTaskFunc) (gpointer user_data);
void gdk_parallel_task_run (GdkTaskFunc task_func,
gpointer task_data);
G_END_DECLS

View File

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

View File

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

View File

@ -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);
@ -675,12 +686,16 @@ gsk_gpu_cached_tile_new (GskGpuCache *cache,
GskGpuImage *
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
};
@ -701,13 +716,21 @@ gsk_gpu_cache_lookup_tile (GskGpuCache *self,
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)
{
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);
}

View File

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

View File

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

View File

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

View File

@ -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));
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);
}
@ -298,6 +326,8 @@ static const GskGpuOpClass GSK_GPU_UPLOAD_TEXTURE_OP_CLASS = {
GskGpuImage *
gsk_gpu_upload_texture_op_try (GskGpuFrame *frame,
gboolean with_mipmap,
guint lod_level,
GskScalingFilter lod_filter,
GdkTexture *texture)
{
GskGpuUploadTextureOp *self;
@ -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);

View File

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