From 3d526786354903e511a3eca575b1edc8feb38744 Mon Sep 17 00:00:00 2001 From: Matthias Clasen Date: Sun, 8 May 2022 14:42:32 -0400 Subject: [PATCH] gsk: Do texture conversions in shaders Do linearization and premultiplication of textures in a shader after uploading the unconverted data. This moves move work from the cpu to the gpu. Conversions between other colorspaces are still done with lcms on the cpu. --- gsk/gl/gskglcommandqueue.c | 69 ++++++++++--- gsk/gl/gskglcommandqueueprivate.h | 9 +- gsk/gl/gskgldriver.c | 109 +++++++++++++++----- gsk/gl/gskglprograms.defs | 8 ++ gsk/gl/resources/linearize_premultiply.glsl | 21 ++++ gsk/gl/resources/preamble.glsl | 5 +- gsk/gl/resources/premultiply.glsl | 20 ++++ gsk/meson.build | 2 + 8 files changed, 201 insertions(+), 42 deletions(-) create mode 100644 gsk/gl/resources/linearize_premultiply.glsl create mode 100644 gsk/gl/resources/premultiply.glsl diff --git a/gsk/gl/gskglcommandqueue.c b/gsk/gl/gskglcommandqueue.c index a63e6d700e..1454dcd488 100644 --- a/gsk/gl/gskglcommandqueue.c +++ b/gsk/gl/gskglcommandqueue.c @@ -530,7 +530,7 @@ gsk_gl_command_queue_begin_draw (GskGLCommandQueue *self, GskGLCommandBatch *batch; g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); - g_assert (self->in_draw == FALSE); + g_assert (!self->in_draw); g_assert (width <= G_MAXUINT16); g_assert (height <= G_MAXUINT16); @@ -568,6 +568,7 @@ gsk_gl_command_queue_end_draw (GskGLCommandQueue *self) GskGLCommandBatch *batch; g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (self->in_draw); g_assert (self->batches.len > 0); if (will_ignore_batch (self)) @@ -1352,23 +1353,35 @@ gsk_gl_command_queue_create_framebuffer (GskGLCommandQueue *self) static void gsk_gl_command_queue_do_upload_texture (GskGLCommandQueue *self, - GdkTexture *texture) + GdkTexture *texture, + GskConversion *conversion) { GdkGLContext *context; const guchar *data; gsize stride; GdkMemoryTexture *memtex; GdkMemoryFormat data_format; + GdkColorProfile *data_profile; int width, height; GLenum gl_internalformat; GLenum gl_format; GLenum gl_type; gsize bpp; gboolean use_es; + gboolean convert_locally = FALSE; context = gdk_gl_context_get_current (); use_es = gdk_gl_context_get_use_es (context); data_format = gdk_texture_get_format (texture); + data_profile = gdk_texture_get_color_profile (texture); + + if (data_profile == gdk_color_profile_get_srgb ()) + *conversion = GSK_CONVERSION_LINEARIZE; + else if (data_profile == gdk_color_profile_get_srgb_linear ()) + *conversion = 0; + else + convert_locally = TRUE; + width = gdk_texture_get_width (texture); height = gdk_texture_get_height (texture); @@ -1378,21 +1391,38 @@ gsk_gl_command_queue_do_upload_texture (GskGLCommandQueue *self, &gl_format, &gl_type)) { - if (gdk_memory_format_prefers_high_depth (data_format)) - data_format = GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED; - else - data_format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; - if (!gdk_memory_format_gl_format (data_format, - use_es, - &gl_internalformat, - &gl_format, - &gl_type)) + if (gdk_gl_context_get_api (context) == GDK_GL_API_GL) { - g_assert_not_reached (); + *conversion |= GSK_CONVERSION_PREMULTIPLY; + } + else + { + convert_locally = TRUE; + if (gdk_memory_format_prefers_high_depth (data_format)) + data_format = GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED; + else + data_format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; + if (!gdk_memory_format_gl_format (data_format, + use_es, + &gl_internalformat, + &gl_format, + &gl_type)) + { + g_assert_not_reached (); + } } } - memtex = gdk_memory_texture_from_texture (texture, data_format, gdk_color_profile_get_srgb_linear ()); + if (convert_locally) + { + memtex = gdk_memory_texture_from_texture (texture, data_format, gdk_color_profile_get_srgb_linear ()); + *conversion = 0; + } + else + { + memtex = gdk_memory_texture_from_texture (texture, gdk_texture_get_format (texture), gdk_texture_get_color_profile (texture)); + } + data = gdk_memory_texture_get_data (memtex); stride = gdk_memory_texture_get_stride (memtex); bpp = gdk_memory_format_bytes_per_pixel (data_format); @@ -1431,11 +1461,13 @@ int gsk_gl_command_queue_upload_texture (GskGLCommandQueue *self, GdkTexture *texture, int min_filter, - int mag_filter) + int mag_filter, + GskConversion *conversion) { G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME; cairo_surface_t *surface = NULL; int width, height; + int format; int texture_id; g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); @@ -1445,6 +1477,9 @@ gsk_gl_command_queue_upload_texture (GskGLCommandQueue *self, width = gdk_texture_get_width (texture); height = gdk_texture_get_height (texture); + + *conversion = 0; + if (width > self->max_texture_size || height > self->max_texture_size) { g_warning ("Attempt to create texture of size %ux%u but max size is %d. " @@ -1453,7 +1488,9 @@ gsk_gl_command_queue_upload_texture (GskGLCommandQueue *self, width = MAX (width, self->max_texture_size); height = MAX (height, self->max_texture_size); } - texture_id = gsk_gl_command_queue_create_texture (self, width, height, GL_RGBA8, min_filter, mag_filter); + + format = gdk_memory_format_prefers_high_depth (gdk_texture_get_format (texture)) ? GL_RGBA16F : GL_RGBA8; + texture_id = gsk_gl_command_queue_create_texture (self, width, height, format, min_filter, mag_filter); if (texture_id == -1) return texture_id; @@ -1463,7 +1500,7 @@ gsk_gl_command_queue_upload_texture (GskGLCommandQueue *self, glActiveTexture (GL_TEXTURE0); glBindTexture (GL_TEXTURE_2D, texture_id); - gsk_gl_command_queue_do_upload_texture (self, texture); + gsk_gl_command_queue_do_upload_texture (self, texture, conversion); /* Restore previous texture state if any */ if (self->attachments->textures[0].id > 0) diff --git a/gsk/gl/gskglcommandqueueprivate.h b/gsk/gl/gskglcommandqueueprivate.h index 48911305a8..a6ffc9f96d 100644 --- a/gsk/gl/gskglcommandqueueprivate.h +++ b/gsk/gl/gskglcommandqueueprivate.h @@ -280,10 +280,17 @@ void gsk_gl_command_queue_execute (GskGLCommandQueue guint scale_factor, const cairo_region_t *scissor, guint default_framebuffer); + +typedef enum { + GSK_CONVERSION_LINEARIZE = 1 << 0, + GSK_CONVERSION_PREMULTIPLY = 1 << 1, +} GskConversion; + int gsk_gl_command_queue_upload_texture (GskGLCommandQueue *self, GdkTexture *texture, int min_filter, - int mag_filter); + int mag_filter, + GskConversion *remaining); int gsk_gl_command_queue_create_texture (GskGLCommandQueue *self, int width, int height, diff --git a/gsk/gl/gskgldriver.c b/gsk/gl/gskgldriver.c index a3011fe3c1..4257166aec 100644 --- a/gsk/gl/gskgldriver.c +++ b/gsk/gl/gskgldriver.c @@ -713,9 +713,10 @@ draw_offscreen (GskGLCommandQueue *command_queue, } static void -set_viewport_for_size (GskGLProgram *program, - float width, - float height) +set_viewport_for_size (GskGLDriver *self, + GskGLProgram *program, + float width, + float height) { float viewport[4] = { 0, 0, width, height }; @@ -724,15 +725,17 @@ set_viewport_for_size (GskGLProgram *program, UNIFORM_SHARED_VIEWPORT, 0, 1, (const float *)&viewport); + self->stamps[UNIFORM_SHARED_VIEWPORT]++; } #define ORTHO_NEAR_PLANE -10000 #define ORTHO_FAR_PLANE 10000 static void -set_projection_for_size (GskGLProgram *program, - float width, - float height) +set_projection_for_size (GskGLDriver *self, + GskGLProgram *program, + float width, + float height) { graphene_matrix_t projection; @@ -743,10 +746,12 @@ set_projection_for_size (GskGLProgram *program, program->program_info, UNIFORM_SHARED_PROJECTION, 0, &projection); + self->stamps[UNIFORM_SHARED_PROJECTION]++; } static void -reset_modelview (GskGLProgram *program) +reset_modelview (GskGLDriver *self, + GskGLProgram *program) { graphene_matrix_t modelview; @@ -756,25 +761,38 @@ reset_modelview (GskGLProgram *program) program->program_info, UNIFORM_SHARED_MODELVIEW, 0, &modelview); + self->stamps[UNIFORM_SHARED_MODELVIEW]++; } static GskGLTexture * -gsk_gl_driver_convert_texture (GskGLDriver *self, - int texture_id, - int width, - int height, - int format) +gsk_gl_driver_convert_texture (GskGLDriver *self, + int texture_id, + int width, + int height, + int format, + int min_filter, + int max_filter, + GskConversion conversion) { GskGLRenderTarget *target; int prev_fbo; - GskGLProgram *program = self->linearize_no_clip; + GskGLProgram *program; + + if (conversion == (GSK_CONVERSION_LINEARIZE | GSK_CONVERSION_PREMULTIPLY)) + program = self->linearize_premultiply_no_clip; + else if (conversion & GSK_CONVERSION_LINEARIZE) + program = self->linearize_no_clip; + else if (conversion & GSK_CONVERSION_PREMULTIPLY) + program = self->premultiply_no_clip; + else + g_assert_not_reached (); gdk_gl_context_make_current (self->command_queue->context); gsk_gl_driver_create_render_target (self, width, height, format, - GL_LINEAR, GL_LINEAR, + min_filter, max_filter, &target); prev_fbo = gsk_gl_command_queue_bind_framebuffer (self->command_queue, target->framebuffer_id); @@ -784,9 +802,9 @@ gsk_gl_driver_convert_texture (GskGLDriver *self, program->program_info, width, height); - set_projection_for_size (program, width, height); - set_viewport_for_size (program, width, height); - reset_modelview (program); + set_projection_for_size (self, program, width, height); + set_viewport_for_size (self, program, width, height); + reset_modelview (self, program); gsk_gl_program_set_uniform_texture (program, UNIFORM_SHARED_SOURCE, 0, @@ -839,6 +857,7 @@ gsk_gl_driver_load_texture (GskGLDriver *self, int height; int width; int format; + GskConversion conversion; g_return_val_if_fail (GSK_IS_GL_DRIVER (self), 0); g_return_val_if_fail (GDK_IS_TEXTURE (texture), 0); @@ -879,9 +898,13 @@ gsk_gl_driver_load_texture (GskGLDriver *self, * is nonlinear sRGB. Eventually, we should figure out how * to convert from other color spaces to linear sRGB */ - - t = gsk_gl_driver_convert_texture (self, gl_texture_id, width, height, format); - if (gdk_texture_set_render_data (texture, self, t, (GDestroyNotify)gsk_gl_texture_destroyed)) + t = gsk_gl_driver_convert_texture (self, + gl_texture_id, + width, height, + format, + min_filter, mag_filter, + GSK_CONVERSION_LINEARIZE); + if (gdk_texture_set_render_data (texture, self, t, gsk_gl_texture_destroyed)) t->user = texture; return t->texture_id; @@ -900,8 +923,8 @@ gsk_gl_driver_load_texture (GskGLDriver *self, texture_id = gsk_gl_command_queue_upload_texture (self->command_queue, GDK_TEXTURE (downloaded_texture), - min_filter, - mag_filter); + min_filter, mag_filter, + &conversion); t = gsk_gl_texture_new (texture_id, width, height, format, min_filter, mag_filter, @@ -909,6 +932,15 @@ gsk_gl_driver_load_texture (GskGLDriver *self, g_hash_table_insert (self->textures, GUINT_TO_POINTER (texture_id), t); + g_clear_object (&downloaded_texture); + + if (conversion) + t = gsk_gl_driver_convert_texture (self, + texture_id, + width, height, format, + min_filter, mag_filter, + conversion); + if (gdk_texture_set_render_data (texture, self, t, gsk_gl_texture_destroyed)) t->user = texture; @@ -967,6 +999,8 @@ gsk_gl_driver_create_texture (GskGLDriver *self, GUINT_TO_POINTER (texture->texture_id), texture); + texture->last_used_in_frame = self->current_frame_id; + return texture; } @@ -1334,6 +1368,7 @@ gsk_gl_driver_add_texture_slices (GskGLDriver *self, GskGLTextureSlice *slices; GskGLTexture *t; guint n_slices; + int format; guint cols; guint rows; int tex_width; @@ -1351,6 +1386,9 @@ gsk_gl_driver_add_texture_slices (GskGLDriver *self, tex_width = texture->width; tex_height = texture->height; + + format = gdk_memory_format_prefers_high_depth (gdk_texture_get_format (texture)) ? GL_RGBA16F : GL_RGBA8; + cols = (texture->width / max_texture_size) + 1; rows = (texture->height / max_texture_size) + 1; @@ -1377,13 +1415,38 @@ gsk_gl_driver_add_texture_slices (GskGLDriver *self, int slice_index = (col * rows) + row; GdkTexture *subtex; guint texture_id; + GskConversion conversion; subtex = gdk_memory_texture_new_subtexture (memtex, x, y, slice_width, slice_height); texture_id = gsk_gl_command_queue_upload_texture (self->command_queue, subtex, - GL_NEAREST, GL_NEAREST); + GL_NEAREST, GL_NEAREST, + &conversion); + + if (conversion) + { + t = gsk_gl_texture_new (texture_id, + slice_width, slice_height, + format, + GL_NEAREST, GL_NEAREST, + 0); + g_hash_table_insert (self->textures, GUINT_TO_POINTER (texture_id), t); + + t = gsk_gl_driver_convert_texture (self, + texture_id, + slice_width, slice_height, + format, + GL_NEAREST, GL_NEAREST, + conversion); + + texture_id = t->texture_id; + t->texture_id = 0; + g_hash_table_steal (self->textures, GUINT_TO_POINTER (texture_id)); + gsk_gl_texture_free (t); + } + g_object_unref (subtex); slices[slice_index].rect.x = x; diff --git a/gsk/gl/gskglprograms.defs b/gsk/gl/gskglprograms.defs index 9e820dc4ef..6791c90037 100644 --- a/gsk/gl/gskglprograms.defs +++ b/gsk/gl/gskglprograms.defs @@ -90,3 +90,11 @@ GSK_GL_DEFINE_PROGRAM (postprocessing, GSK_GL_DEFINE_PROGRAM (linearize, GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("linearize.glsl")), GSK_GL_NO_UNIFORMS) + +GSK_GL_DEFINE_PROGRAM (premultiply, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("premultiply.glsl")), + GSK_GL_NO_UNIFORMS) + +GSK_GL_DEFINE_PROGRAM (linearize_premultiply, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("linearize_premultiply.glsl")), + GSK_GL_NO_UNIFORMS) diff --git a/gsk/gl/resources/linearize_premultiply.glsl b/gsk/gl/resources/linearize_premultiply.glsl new file mode 100644 index 0000000000..85a37c4ca3 --- /dev/null +++ b/gsk/gl/resources/linearize_premultiply.glsl @@ -0,0 +1,21 @@ + +// VERTEX_SHADER: +// linearize_premultiply.glsl + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + vUv = vec2(aUv.x, aUv.y); +} + +// FRAGMENT_SHADER: +// linearize_premultiply.glsl + +void main() { + vec4 diffuse = GskTexture(u_source, vUv); + + diffuse = gsk_srgb_to_linear(diffuse); + diffuse = gsk_premultiply(diffuse); + + gskSetOutputColor(diffuse); +} diff --git a/gsk/gl/resources/preamble.glsl b/gsk/gl/resources/preamble.glsl index 68a89552e9..c32379150c 100644 --- a/gsk/gl/resources/preamble.glsl +++ b/gsk/gl/resources/preamble.glsl @@ -42,7 +42,8 @@ gsk_get_bounds(vec4[3] data) return vec4(data[0].xy, data[0].xy + data[0].zw); } -vec4 gsk_premultiply(vec4 c) { +vec4 gsk_premultiply(vec4 c) +{ return vec4(c.rgb * c.a, c.a); } @@ -62,7 +63,7 @@ vec4 gsk_srgb_to_linear(vec4 srgb) vec4 gsk_linear_to_srgb(vec4 linear_rgba) { - vec3 srgb = pow(linear_rgba.rgb , vec3(1/2.2)); + vec3 srgb = pow(linear_rgba.rgb, vec3(1/2.2)); return vec4(srgb, linear_rgba.a); } diff --git a/gsk/gl/resources/premultiply.glsl b/gsk/gl/resources/premultiply.glsl new file mode 100644 index 0000000000..b83ce9e90e --- /dev/null +++ b/gsk/gl/resources/premultiply.glsl @@ -0,0 +1,20 @@ + +// VERTEX_SHADER: +// premultiply.glsl + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + vUv = vec2(aUv.x, aUv.y); +} + +// FRAGMENT_SHADER: +// premultiply.glsl + +void main() { + vec4 diffuse = GskTexture(u_source, vUv); + + diffuse = gsk_premultiply(diffuse); + + gskSetOutputColor(diffuse); +} diff --git a/gsk/meson.build b/gsk/meson.build index ca203eef85..dcfdf26849 100644 --- a/gsk/meson.build +++ b/gsk/meson.build @@ -21,6 +21,8 @@ gsk_private_gl_shaders = [ 'gl/resources/filled_border.glsl', 'gl/resources/postprocessing.glsl', 'gl/resources/linearize.glsl', + 'gl/resources/premultiply.glsl', + 'gl/resources/linearize_premultiply.glsl', ] gsk_public_sources = files([