From 2c30dda4d8c106e377833019ee42fdfef101d7af Mon Sep 17 00:00:00 2001 From: Sergey Fedorov Date: Tue, 5 May 2026 08:29:05 +0800 Subject: [PATCH] Fix gl 2.0 --- gdk/gdk.c | 1 + gdk/gdkdebugprivate.h | 1 + gdk/gdkglcontext.c | 104 +- gdk/gdkglcontextprivate.h | 5 +- gdk/gdkglversionprivate.h | 10 +- gdk/x11/gdkglcontext-glx.c | 3 +- gsk/gen-gsk-gresources-xml.py | 11 +- gsk/gl/gskglattachmentstate.c | 116 + gsk/gl/gskglattachmentstateprivate.h | 102 + gsk/gl/gskglbuffer.c | 69 + gsk/gl/gskglbufferprivate.h | 80 + gsk/gl/gskglcommandqueue.c | 1904 +++++++ gsk/gl/gskglcommandqueueprivate.h | 474 ++ gsk/gl/gskglcompiler.c | 713 +++ gsk/gl/gskglcompilerprivate.h | 68 + gsk/gl/gskgldriver.c | 1868 +++++++ gsk/gl/gskgldriverprivate.h | 261 + gsk/gl/gskglglyphlibrary.c | 437 ++ gsk/gl/gskglglyphlibraryprivate.h | 103 + gsk/gl/gskgliconlibrary.c | 212 + gsk/gl/gskgliconlibraryprivate.h | 58 + gsk/gl/gskglprofiler.c | 179 + gsk/gl/gskglprofilerprivate.h | 16 + gsk/gl/gskglprogram.c | 173 + gsk/gl/gskglprogramprivate.h | 318 ++ gsk/gl/gskglprograms.defs | 104 + gsk/gl/gskglrenderer.c | 497 ++ gsk/gl/gskglrenderer.h | 41 +- gsk/gl/gskglrendererprivate.h | 38 + gsk/gl/gskglrenderjob.c | 4749 +++++++++++++++++ gsk/gl/gskglrenderjobprivate.h | 36 + gsk/gl/gskglshadowlibrary.c | 256 + gsk/gl/gskglshadowlibraryprivate.h | 42 + gsk/gl/gskgltexture.c | 95 + gsk/gl/gskgltexturelibrary.c | 583 ++ gsk/gl/gskgltexturelibraryprivate.h | 232 + gsk/gl/gskgltextureprivate.h | 93 + gsk/gl/gskgltypesprivate.h | 84 + gsk/gl/gskgluniformstate.c | 269 + gsk/gl/gskgluniformstateprivate.h | 834 +++ gsk/gl/inlinearray.h | 75 + gsk/gl/ninesliceprivate.h | 308 ++ gsk/gl/resources/blend.glsl | 314 ++ gsk/gl/resources/blit.glsl | 17 + gsk/gl/resources/blur.glsl | 59 + gsk/gl/resources/border.glsl | 53 + gsk/gl/resources/color.glsl | 20 + gsk/gl/resources/color_matrix.glsl | 27 + gsk/gl/resources/coloring.glsl | 33 + gsk/gl/resources/conic_gradient.glsl | 85 + gsk/gl/resources/cross_fade.glsl | 24 + gsk/gl/resources/custom.glsl | 25 + gsk/gl/resources/external.glsl | 34 + gsk/gl/resources/filled_border.glsl | 58 + gsk/gl/resources/inset_shadow.glsl | 51 + gsk/gl/resources/linear_gradient.glsl | 107 + gsk/gl/resources/mask.glsl | 39 + gsk/gl/resources/outset_shadow.glsl | 39 + gsk/gl/resources/preamble.fs.glsl | 173 + gsk/gl/resources/preamble.glsl | 133 + gsk/gl/resources/preamble.vs.glsl | 75 + gsk/gl/resources/premultiply.glsl | 19 + gsk/gl/resources/radial_gradient.glsl | 87 + gsk/gl/resources/repeat.glsl | 44 + gsk/gl/resources/unblurred_outset_shadow.glsl | 47 + gsk/gl/stb_rect_pack.c | 427 ++ gsk/gl/stb_rect_pack.h | 197 + gsk/gpu/gskglframe.c | 3 +- gsk/gpu/gskgpudownloadop.c | 5 +- gsk/gsk.h | 1 + gsk/gskcairorenderer.c | 31 +- gsk/gskprofiler.c | 525 ++ gsk/gskprofilerprivate.h | 53 + gsk/gskrenderer.c | 46 +- gsk/gskrendererprivate.h | 3 + gsk/meson.build | 44 + gtk/gtkglarea.c | 7 +- gtk/inspector/eventrecording.h | 1 + gtk/inspector/recorder.c | 1 + gtk/inspector/renderrecording.c | 21 + gtk/inspector/renderrecording.h | 6 + tools/gtk-rendernode-tool-benchmark.c | 2 +- 82 files changed, 18532 insertions(+), 26 deletions(-) create mode 100644 gsk/gl/gskglattachmentstate.c create mode 100644 gsk/gl/gskglattachmentstateprivate.h create mode 100644 gsk/gl/gskglbuffer.c create mode 100644 gsk/gl/gskglbufferprivate.h create mode 100644 gsk/gl/gskglcommandqueue.c create mode 100644 gsk/gl/gskglcommandqueueprivate.h create mode 100644 gsk/gl/gskglcompiler.c create mode 100644 gsk/gl/gskglcompilerprivate.h create mode 100644 gsk/gl/gskgldriver.c create mode 100644 gsk/gl/gskgldriverprivate.h create mode 100644 gsk/gl/gskglglyphlibrary.c create mode 100644 gsk/gl/gskglglyphlibraryprivate.h create mode 100644 gsk/gl/gskgliconlibrary.c create mode 100644 gsk/gl/gskgliconlibraryprivate.h create mode 100644 gsk/gl/gskglprofiler.c create mode 100644 gsk/gl/gskglprofilerprivate.h create mode 100644 gsk/gl/gskglprogram.c create mode 100644 gsk/gl/gskglprogramprivate.h create mode 100644 gsk/gl/gskglprograms.defs create mode 100644 gsk/gl/gskglrenderer.c create mode 100644 gsk/gl/gskglrendererprivate.h create mode 100644 gsk/gl/gskglrenderjob.c create mode 100644 gsk/gl/gskglrenderjobprivate.h create mode 100644 gsk/gl/gskglshadowlibrary.c create mode 100644 gsk/gl/gskglshadowlibraryprivate.h create mode 100644 gsk/gl/gskgltexture.c create mode 100644 gsk/gl/gskgltexturelibrary.c create mode 100644 gsk/gl/gskgltexturelibraryprivate.h create mode 100644 gsk/gl/gskgltextureprivate.h create mode 100644 gsk/gl/gskgltypesprivate.h create mode 100644 gsk/gl/gskgluniformstate.c create mode 100644 gsk/gl/gskgluniformstateprivate.h create mode 100644 gsk/gl/inlinearray.h create mode 100644 gsk/gl/ninesliceprivate.h create mode 100644 gsk/gl/resources/blend.glsl create mode 100644 gsk/gl/resources/blit.glsl create mode 100644 gsk/gl/resources/blur.glsl create mode 100644 gsk/gl/resources/border.glsl create mode 100644 gsk/gl/resources/color.glsl create mode 100644 gsk/gl/resources/color_matrix.glsl create mode 100644 gsk/gl/resources/coloring.glsl create mode 100644 gsk/gl/resources/conic_gradient.glsl create mode 100644 gsk/gl/resources/cross_fade.glsl create mode 100644 gsk/gl/resources/custom.glsl create mode 100644 gsk/gl/resources/external.glsl create mode 100644 gsk/gl/resources/filled_border.glsl create mode 100644 gsk/gl/resources/inset_shadow.glsl create mode 100644 gsk/gl/resources/linear_gradient.glsl create mode 100644 gsk/gl/resources/mask.glsl create mode 100644 gsk/gl/resources/outset_shadow.glsl create mode 100644 gsk/gl/resources/preamble.fs.glsl create mode 100644 gsk/gl/resources/preamble.glsl create mode 100644 gsk/gl/resources/preamble.vs.glsl create mode 100644 gsk/gl/resources/premultiply.glsl create mode 100644 gsk/gl/resources/radial_gradient.glsl create mode 100644 gsk/gl/resources/repeat.glsl create mode 100644 gsk/gl/resources/unblurred_outset_shadow.glsl create mode 100644 gsk/gl/stb_rect_pack.c create mode 100644 gsk/gl/stb_rect_pack.h create mode 100644 gsk/gskprofiler.c create mode 100644 gsk/gskprofilerprivate.h diff --git gdk/gdk.c gdk/gdk.c index c84edac346..2c1b68893a 100644 --- gdk/gdk.c +++ gdk/gdk.c @@ -148,6 +148,7 @@ static const GdkDebugKey gdk_debug_keys[] = { { "no-vsync", GDK_DEBUG_NO_VSYNC, "Repaint instantly (uses 100% CPU with animations)" }, { "color-mgmt", GDK_DEBUG_COLOR_MANAGEMENT, "Enable color management" }, { "dcomp", GDK_DEBUG_DCOMP, "Enable Direct Composition (Windows)" }, + { "gl-no-fractional",GDK_DEBUG_GL_NO_FRACTIONAL, "Disable fractional scaling for OpenGL" }, }; static const GdkDebugKey gdk_feature_keys[] = { diff --git gdk/gdkdebugprivate.h gdk/gdkdebugprivate.h index eb211d5aec..4bc36459c3 100644 --- gdk/gdkdebugprivate.h +++ gdk/gdkdebugprivate.h @@ -51,6 +51,7 @@ typedef enum { GDK_DEBUG_NO_VSYNC = 1 << 23, GDK_DEBUG_COLOR_MANAGEMENT = 1 << 24, GDK_DEBUG_DCOMP = 1 << 25, + GDK_DEBUG_GL_NO_FRACTIONAL = 1 << 26, } GdkDebugFlags; typedef enum { diff --git gdk/gdkglcontext.c gdk/gdkglcontext.c index ae06a06978..f55f52e44a 100644 --- gdk/gdkglcontext.c +++ gdk/gdkglcontext.c @@ -111,6 +111,7 @@ const GdkDebugKey gdk_gl_feature_keys[] = { { "external-objects", GDK_GL_FEATURE_EXTERNAL_OBJECTS, "GL_EXT_memory_object and GL_EXT_semaphore"}, { "external-objects-win32", GDK_GL_FEATURE_EXTERNAL_OBJECTS_WIN32, "GL_EXT_memory_object_win32 and GL_EXT_semaphore_win32" }, { "blend-func-extended", GDK_GL_FEATURE_BLEND_FUNC_EXTENDED, "GL_EXT_blend_func_extended" }, + { "sync", GDK_GL_FEATURE_SYNC, "GL_ARB_sync" }, }; typedef struct _GdkGLContextPrivate GdkGLContextPrivate; @@ -595,6 +596,23 @@ gdk_gl_context_real_make_current (GdkGLContext *context, #endif } +double +gdk_gl_context_get_scale (GdkGLContext *self) +{ + GdkDisplay *display; + GdkSurface *surface; + double scale; + + surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (self)); + scale = gdk_surface_get_scale (surface); + + display = gdk_gl_context_get_display (self); + if (gdk_display_get_debug_flags (display) & GDK_DEBUG_GL_NO_FRACTIONAL) + scale = ceil (scale); + + return scale; +} + #ifdef HAVE_EGL void gdk_gl_context_set_egl_native_window (GdkGLContext *self, @@ -1681,10 +1699,78 @@ gdk_gl_context_init_memory_flags (GdkGLContext *self) if (!gdk_gl_context_get_use_es (self)) { + gboolean has_rg, has_float, has_half_float, has_swizzle; + for (i = 0; i < G_N_ELEMENTS (priv->memory_flags); i++) { priv->memory_flags[i] = GDK_GL_FORMAT_USABLE | GDK_GL_FORMAT_RENDERABLE | GDK_GL_FORMAT_FILTERABLE; } + + /* Everything the formats below need is core in GL 3.3, which used to be + * the minimum version GTK would accept. Only the older contexts we now + * allow have to have their formats restricted. + */ + has_rg = gdk_gl_version_greater_equal (&priv->gl_version, &GDK_GL_VERSION_INIT (3, 0)) || + epoxy_has_gl_extension ("GL_ARB_texture_rg"); + has_float = gdk_gl_version_greater_equal (&priv->gl_version, &GDK_GL_VERSION_INIT (3, 0)) || + epoxy_has_gl_extension ("GL_ARB_texture_float"); + has_half_float = has_float && + (gdk_gl_version_greater_equal (&priv->gl_version, &GDK_GL_VERSION_INIT (3, 0)) || + epoxy_has_gl_extension ("GL_ARB_half_float_pixel")); + has_swizzle = gdk_gl_version_greater_equal (&priv->gl_version, &GDK_GL_VERSION_INIT (3, 3)) || + epoxy_has_gl_extension ("GL_ARB_texture_swizzle"); + + if (has_rg && has_float && has_half_float && has_swizzle) + return; + + for (i = 0; i < G_N_ELEMENTS (priv->memory_flags); i++) + { + GLint internal_format, internal_srgb_format; + GLenum format, type; + GdkSwizzle swizzle; + gboolean supported; + + if (!gdk_memory_format_gl_format ((GdkMemoryFormat) i, 0, FALSE, + &internal_format, &internal_srgb_format, + &format, &type, &swizzle)) + continue; + + switch (internal_format) + { + case GL_R8: + case GL_RG8: + case GL_R16: + case GL_RG16: + supported = has_rg; + break; + + case GL_R16F: + case GL_RGB16F: + case GL_RGBA16F: + supported = has_half_float; + break; + + case GL_R32F: + case GL_RGB32F: + case GL_RGBA32F: + supported = has_float; + break; + + default: + supported = TRUE; + break; + } + + /* Without GL_ARB_texture_swizzle we cannot make the channels end up + * where the shaders expect them, so those formats are unusable. + */ + if (!gdk_swizzle_is_identity (swizzle) && !has_swizzle) + supported = FALSE; + + if (!supported) + priv->memory_flags[i] = 0; + } + return; } @@ -1700,6 +1786,9 @@ gdk_gl_context_init_memory_flags (GdkGLContext *self) priv->memory_flags[GDK_MEMORY_G8A8] = GDK_GL_FORMAT_USABLE | GDK_GL_FORMAT_FILTERABLE; #endif + if (!gdk_gl_version_greater_equal (&priv->gl_version, &GDK_GL_VERSION_INIT (3, 0))) + return; + /* GLES 3.0.6 spec, table 3.13 */ priv->memory_flags[GDK_MEMORY_G8] |= GDK_GL_FORMAT_USABLE | GDK_GL_FORMAT_RENDERABLE | GDK_GL_FORMAT_FILTERABLE; priv->memory_flags[GDK_MEMORY_A8] |= GDK_GL_FORMAT_USABLE | GDK_GL_FORMAT_RENDERABLE | GDK_GL_FORMAT_FILTERABLE; @@ -1832,6 +1921,11 @@ gdk_gl_context_check_features (GdkGLContext *context) epoxy_has_gl_extension ("GL_EXT_blend_func_extended")) features |= GDK_GL_FEATURE_BLEND_FUNC_EXTENDED; + if (gdk_gl_context_check_version (context, "3.2", "3.0") || + epoxy_has_gl_extension ("GL_ARB_sync") || + epoxy_has_gl_extension ("GL_APPLE_sync")) + features |= GDK_GL_FEATURE_SYNC; + return features; } @@ -2082,8 +2176,12 @@ gdk_gl_context_get_glsl_version_string (GdkGLContext *self) return "#version 150"; else if (gdk_gl_version_greater_equal (&priv->gl_version, &GDK_GL_VERSION_INIT (3, 1))) return "#version 140"; - else + else if (gdk_gl_version_greater_equal (&priv->gl_version, &GDK_GL_VERSION_INIT (3, 0))) return "#version 130"; + else if (gdk_gl_version_greater_equal (&priv->gl_version, &GDK_GL_VERSION_INIT (2, 1))) + return "#version 120"; + else + return "#version 110"; } else if (priv->api == GDK_GL_API_GLES) { @@ -2208,7 +2306,9 @@ gdk_gl_context_has_vertex_arrays (GdkGLContext *self) switch (priv->api) { case GDK_GL_API_GL: - return TRUE; + return gdk_gl_version_greater_equal (&priv->gl_version, &GDK_GL_VERSION_INIT (3, 0)) || + epoxy_has_gl_extension ("GL_ARB_vertex_array_object") || + epoxy_has_gl_extension ("GL_APPLE_vertex_array_object"); case GDK_GL_API_GLES: return gdk_gl_version_get_major (&priv->gl_version) >= 3; diff --git gdk/gdkglcontextprivate.h gdk/gdkglcontextprivate.h index 5411579006..7d42f5377d 100644 --- gdk/gdkglcontextprivate.h +++ gdk/gdkglcontextprivate.h @@ -35,9 +35,10 @@ typedef enum { GDK_GL_FEATURE_EXTERNAL_OBJECTS = 1 << 3, GDK_GL_FEATURE_EXTERNAL_OBJECTS_WIN32 = 1 << 4, GDK_GL_FEATURE_BLEND_FUNC_EXTENDED = 1 << 5, + GDK_GL_FEATURE_SYNC = 1 << 6, } GdkGLFeatures; -#define GDK_GL_N_FEATURES 3 +#define GDK_GL_N_FEATURES 7 extern const GdkDebugKey gdk_gl_feature_keys[]; @@ -183,6 +184,8 @@ gboolean gdk_gl_context_use_es_bgra (GdkGLContext gboolean gdk_gl_context_has_vertex_arrays (GdkGLContext *self) G_GNUC_PURE; +double gdk_gl_context_get_scale (GdkGLContext *self); + void gdk_gl_context_download (GdkGLContext *self, GLuint tex_id, GdkMemoryFormat tex_format, diff --git gdk/gdkglversionprivate.h gdk/gdkglversionprivate.h index d9475082ae..c0c39400a3 100644 --- gdk/gdkglversionprivate.h +++ gdk/gdkglversionprivate.h @@ -34,8 +34,8 @@ G_BEGIN_DECLS /* Minimum OpenGL versions supported by GTK. * Backends should make sure to never create a context of a previous version. */ -#define GDK_GL_MIN_GL_VERSION GDK_GL_VERSION_INIT (3, 3) -#define GDK_GL_MIN_GLES_VERSION GDK_GL_VERSION_INIT (3, 0) +#define GDK_GL_MIN_GL_VERSION GDK_GL_VERSION_INIT (2, 0) +#define GDK_GL_MIN_GLES_VERSION GDK_GL_VERSION_INIT (2, 0) typedef struct _GdkGLVersion GdkGLVersion; @@ -56,6 +56,11 @@ static const GdkGLVersion supported_gl_versions[] = { GDK_GL_VERSION_INIT (4, 1), GDK_GL_VERSION_INIT (4, 0), GDK_GL_VERSION_INIT (3, 3), + GDK_GL_VERSION_INIT (3, 2), + GDK_GL_VERSION_INIT (3, 1), + GDK_GL_VERSION_INIT (3, 0), + GDK_GL_VERSION_INIT (2, 1), + GDK_GL_VERSION_INIT (2, 0), GDK_GL_VERSION_INIT (0, 0) }; @@ -64,6 +69,7 @@ static const GdkGLVersion supported_gles_versions[] = { GDK_GL_VERSION_INIT (3, 2), GDK_GL_VERSION_INIT (3, 1), GDK_GL_VERSION_INIT (3, 0), + GDK_GL_VERSION_INIT (2, 0), GDK_GL_VERSION_INIT (0, 0) }; diff --git gdk/x11/gdkglcontext-glx.c gdk/x11/gdkglcontext-glx.c index f7a028bf50..e6cb06dead 100644 --- gdk/x11/gdkglcontext-glx.c +++ gdk/x11/gdkglcontext-glx.c @@ -187,7 +187,8 @@ gdk_x11_gl_context_glx_end_frame (GdkDrawContext *draw_context, gdk_x11_surface_pre_damage (surface); #ifdef HAVE_XDAMAGE - if (self->xdamage != 0 && _gdk_x11_surface_syncs_frames (surface)) + if (self->xdamage != 0 && _gdk_x11_surface_syncs_frames (surface) && + gdk_gl_context_has_feature (context, GDK_GL_FEATURE_SYNC)) { g_assert (self->frame_fence == 0); diff --git gsk/gen-gsk-gresources-xml.py gsk/gen-gsk-gresources-xml.py index 8a18126283..7c17ac249b 100644 --- gsk/gen-gsk-gresources-xml.py +++ gsk/gen-gsk-gresources-xml.py @@ -21,11 +21,15 @@ def replace_if_changed(new, old): os.remove(new) gl_source_shaders = [] +gl2_source_shaders = [] vulkan_compiled_shaders = [] for f in sys.argv[2:]: if f.endswith('.glsl'): - gl_source_shaders.append(f) + if f.find('gsk/gpu') > -1: + gl_source_shaders.append(f) + else: + gl2_source_shaders.append(f) elif f.endswith('.spv'): vulkan_compiled_shaders.append(f) else: @@ -37,6 +41,11 @@ xml = ''' ''' +for f in gl2_source_shaders: + xml += ' gl/resources/{0}\n'.format(os.path.basename(f)) + +xml += '\n' + for f in gl_source_shaders: xml += ' gpu/shaders/{0}\n'.format(os.path.basename(f)) diff --git gsk/gl/gskglattachmentstate.c gsk/gl/gskglattachmentstate.c new file mode 100644 index 0000000000..3765e95f79 --- /dev/null +++ gsk/gl/gskglattachmentstate.c @@ -0,0 +1,116 @@ +/* gskglattachmentstate.c + * + * Copyright 2020 Christian Hergert + * + * This file 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 file 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 General Public License along + * with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include "gskglattachmentstateprivate.h" + +GskGLAttachmentState * +gsk_gl_attachment_state_new (void) +{ + GskGLAttachmentState *self; + + self = g_atomic_rc_box_new0 (GskGLAttachmentState); + + self->fbo.changed = FALSE; + self->fbo.id = 0; + self->n_changed = 0; + + /* Initialize textures, assume we are 2D by default since it + * doesn't really matter until we bind something other than + * GL_TEXTURE0 to it anyway. + */ + for (guint i = 0; i < G_N_ELEMENTS (self->textures); i++) + { + self->textures[i].target = GL_TEXTURE_2D; + self->textures[i].texture = GL_TEXTURE0; + self->textures[i].id = 0; + self->textures[i].changed = FALSE; + self->textures[i].initial = TRUE; + self->textures[i].sampler = sampler_index (GL_LINEAR, GL_LINEAR); + } + + return self; +} + +GskGLAttachmentState * +gsk_gl_attachment_state_ref (GskGLAttachmentState *self) +{ + return g_atomic_rc_box_acquire (self); +} + +void +gsk_gl_attachment_state_unref (GskGLAttachmentState *self) +{ + g_atomic_rc_box_release (self); +} + +void +gsk_gl_attachment_state_bind_texture (GskGLAttachmentState *self, + GLenum target, + GLenum texture, + guint id, + GLint min_filter, + GLint mag_filter) +{ + GskGLBindTexture *attach; + unsigned int sampler; + + g_assert (self != NULL); + g_assert (target == GL_TEXTURE_1D || + target == GL_TEXTURE_2D || + target == GL_TEXTURE_3D || + target == GL_TEXTURE_EXTERNAL_OES); + g_assert (texture >= GL_TEXTURE0 && texture <= GL_TEXTURE16); + g_assert (texture - GL_TEXTURE0 < G_N_ELEMENTS (self->textures)); + + attach = &self->textures[texture - GL_TEXTURE0]; + + sampler = sampler_index (min_filter, mag_filter); + + if (attach->target != target || attach->texture != texture || attach->id != id || + attach->sampler != sampler) + { + attach->target = target; + attach->texture = texture; + attach->id = id; + attach->initial = FALSE; + attach->sampler = sampler; + + if (attach->changed == FALSE) + { + attach->changed = TRUE; + self->n_changed++; + } + } +} + +void +gsk_gl_attachment_state_bind_framebuffer (GskGLAttachmentState *self, + guint id) +{ + g_assert (self != NULL); + + if (self->fbo.id != id) + { + self->fbo.id = id; + self->fbo.changed = TRUE; + } +} diff --git gsk/gl/gskglattachmentstateprivate.h gsk/gl/gskglattachmentstateprivate.h new file mode 100644 index 0000000000..e2ecf1a539 --- /dev/null +++ gsk/gl/gskglattachmentstateprivate.h @@ -0,0 +1,102 @@ +/* gskglattachmentstateprivate.h + * + * Copyright 2020 Christian Hergert + * + * This file 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 file 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 General Public License along + * with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include "gskgltypesprivate.h" + +G_BEGIN_DECLS + +typedef struct _GskGLAttachmentState GskGLAttachmentState; +typedef struct _GskGLBindFramebuffer GskGLBindFramebuffer; +typedef struct _GskGLBindTexture GskGLBindTexture; + +#define GSK_GL_N_FILTERS 3 +#define SAMPLER_EXTERNAL 9 + +G_STATIC_ASSERT (SAMPLER_EXTERNAL >= GSK_GL_N_FILTERS * GSK_GL_N_FILTERS); + +static inline guint +filter_index (GLint filter) +{ + switch (filter) + { + case GL_LINEAR: + return 0; + case GL_NEAREST: + return 1; + case GL_LINEAR_MIPMAP_LINEAR: + return 2; + default: + g_assert_not_reached (); + } +} + +static inline guint +sampler_index (GLint min_filter, + GLint mag_filter) +{ + return filter_index (min_filter) * GSK_GL_N_FILTERS + filter_index (mag_filter); +} + +struct _GskGLBindTexture +{ + guint changed : 1; + guint initial : 1; + GLenum target : 26; + guint sampler : 4; + GLenum texture; + guint id; +}; + +G_STATIC_ASSERT (sizeof (GskGLBindTexture) == 12); + +struct _GskGLBindFramebuffer +{ + guint changed : 1; + guint id : 31; +}; + +G_STATIC_ASSERT (sizeof (GskGLBindFramebuffer) == 4); + +/* Increase if shaders add more textures */ +#define GSK_GL_MAX_TEXTURES_PER_PROGRAM 4 + +struct _GskGLAttachmentState +{ + GskGLBindFramebuffer fbo; + GskGLBindTexture textures[GSK_GL_MAX_TEXTURES_PER_PROGRAM]; + guint n_changed; +}; + +GskGLAttachmentState *gsk_gl_attachment_state_new (void); +GskGLAttachmentState *gsk_gl_attachment_state_ref (GskGLAttachmentState *self); +void gsk_gl_attachment_state_unref (GskGLAttachmentState *self); +void gsk_gl_attachment_state_bind_texture (GskGLAttachmentState *self, + GLenum target, + GLenum texture, + guint id, + GLint min_filter, + GLint mag_filter); +void gsk_gl_attachment_state_bind_framebuffer (GskGLAttachmentState *self, + guint id); + +G_END_DECLS + diff --git gsk/gl/gskglbuffer.c gsk/gl/gskglbuffer.c new file mode 100644 index 0000000000..63aabd1dc3 --- /dev/null +++ gsk/gl/gskglbuffer.c @@ -0,0 +1,69 @@ +/* gskglbufferprivate.h + * + * Copyright 2020 Christian Hergert + * + * This file 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 file 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 General Public License along + * with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include + +#include "gskglbufferprivate.h" + +/** + * gsk_gl_buffer_init: + * @target: the target buffer such as %GL_ARRAY_BUFFER or %GL_UNIFORM_BUFFER + * @element_size: the size of elements within the buffer + * + * Creates a new `GskGLBuffer` which can be used to deliver data to shaders + * within a GLSL program. You can use this to store vertices such as with + * %GL_ARRAY_BUFFER or uniform data with %GL_UNIFORM_BUFFER. + */ +void +gsk_gl_buffer_init (GskGLBuffer *self, + GLenum target, + guint element_size) +{ + memset (self, 0, sizeof *self); + + /* Default to 2 pages, power-of-two growth from there */ + self->buffer_len = 4096 * 2; + self->buffer = g_malloc (self->buffer_len); + self->target = target; + self->element_size = element_size; +} + +GLuint +gsk_gl_buffer_submit (GskGLBuffer *buffer) +{ + GLuint id; + + glGenBuffers (1, &id); + glBindBuffer (buffer->target, id); + glBufferData (buffer->target, buffer->buffer_pos, buffer->buffer, GL_STATIC_DRAW); + + buffer->buffer_pos = 0; + buffer->count = 0; + + return id; +} + +void +gsk_gl_buffer_destroy (GskGLBuffer *buffer) +{ + g_clear_pointer (&buffer->buffer, g_free); +} diff --git gsk/gl/gskglbufferprivate.h gsk/gl/gskglbufferprivate.h new file mode 100644 index 0000000000..fa4e65cc37 --- /dev/null +++ gsk/gl/gskglbufferprivate.h @@ -0,0 +1,80 @@ +/* gskglbufferprivate.h + * + * Copyright 2020 Christian Hergert + * + * This file 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 file 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 General Public License along + * with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include "gskgltypesprivate.h" + +G_BEGIN_DECLS + +typedef struct _GskGLBuffer +{ + guint8 *buffer; + gsize buffer_pos; + gsize buffer_len; + guint count; + GLenum target; + gsize element_size; +} GskGLBuffer; + +void gsk_gl_buffer_init (GskGLBuffer *self, + GLenum target, + guint element_size); +void gsk_gl_buffer_destroy (GskGLBuffer *buffer); +GLuint gsk_gl_buffer_submit (GskGLBuffer *buffer); + +static inline gpointer +gsk_gl_buffer_advance (GskGLBuffer *buffer, + guint count) +{ + gpointer ret; + gsize to_alloc = count * buffer->element_size; + + if G_UNLIKELY (buffer->buffer_pos + to_alloc > buffer->buffer_len) + { + while (buffer->buffer_pos + to_alloc > buffer->buffer_len) + buffer->buffer_len *= 2; + buffer->buffer = g_realloc (buffer->buffer, buffer->buffer_len); + } + + ret = buffer->buffer + buffer->buffer_pos; + + buffer->buffer_pos += to_alloc; + buffer->count += count; + + return ret; +} + +static inline void +gsk_gl_buffer_retract (GskGLBuffer *buffer, + guint count) +{ + buffer->buffer_pos -= count * buffer->element_size; + buffer->count -= count; +} + +static inline guint +gsk_gl_buffer_get_offset (GskGLBuffer *buffer) +{ + return buffer->count; +} + +G_END_DECLS + diff --git gsk/gl/gskglcommandqueue.c gsk/gl/gskglcommandqueue.c new file mode 100644 index 0000000000..f96fe6108d --- /dev/null +++ gsk/gl/gskglcommandqueue.c @@ -0,0 +1,1904 @@ +/* gskglcommandqueue.c + * + * Copyright 2017 Timm Bäder + * Copyright 2018 Matthias Clasen + * Copyright 2018 Alexander Larsson + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include + +#include +#include +#include +#include +#include +#include + +#include "fp16private.h" +#include "gskglattachmentstateprivate.h" +#include "gskglbufferprivate.h" +#include "gskglcommandqueueprivate.h" +#include "gskgluniformstateprivate.h" + +#include "inlinearray.h" + +G_DEFINE_TYPE (GskGLCommandQueue, gsk_gl_command_queue, G_TYPE_OBJECT) + +#if 0 +G_GNUC_UNUSED static inline void +print_uniform (GskGLUniformFormat format, + guint array_count, + gconstpointer valueptr) +{ + const union { + graphene_matrix_t matrix[0]; + GskRoundedRect rounded_rect[0]; + float fval[0]; + int ival[0]; + guint uval[0]; + } *data = valueptr; + + switch (format) + { + case GSK_GL_UNIFORM_FORMAT_1F: + g_printerr ("1f<%f>", data->fval[0]); + break; + + case GSK_GL_UNIFORM_FORMAT_2F: + g_printerr ("2f<%f,%f>", data->fval[0], data->fval[1]); + break; + + case GSK_GL_UNIFORM_FORMAT_3F: + g_printerr ("3f<%f,%f,%f>", data->fval[0], data->fval[1], data->fval[2]); + break; + + case GSK_GL_UNIFORM_FORMAT_4F: + g_printerr ("4f<%f,%f,%f,%f>", data->fval[0], data->fval[1], data->fval[2], data->fval[3]); + break; + + case GSK_GL_UNIFORM_FORMAT_1I: + case GSK_GL_UNIFORM_FORMAT_TEXTURE: + g_printerr ("1i<%d>", data->ival[0]); + break; + + case GSK_GL_UNIFORM_FORMAT_1UI: + g_printerr ("1ui<%u>", data->uval[0]); + break; + + case GSK_GL_UNIFORM_FORMAT_COLOR: { + char *str = gdk_rgba_to_string (valueptr); + g_printerr ("%s", str); + g_free (str); + break; + } + + case GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT: { + char *str = gsk_rounded_rect_to_string (valueptr); + g_printerr ("%s", str); + g_free (str); + break; + } + + case GSK_GL_UNIFORM_FORMAT_MATRIX: { + float mat[16]; + graphene_matrix_to_float (&data->matrix[0], mat); + g_printerr ("matrix<"); + for (guint i = 0; i < G_N_ELEMENTS (mat)-1; i++) + g_printerr ("%f,", mat[i]); + g_printerr ("%f>", mat[G_N_ELEMENTS (mat)-1]); + break; + } + + case GSK_GL_UNIFORM_FORMAT_1FV: + case GSK_GL_UNIFORM_FORMAT_2FV: + case GSK_GL_UNIFORM_FORMAT_3FV: + case GSK_GL_UNIFORM_FORMAT_4FV: + /* non-V variants are -4 from V variants */ + format -= 4; + g_printerr ("["); + for (guint i = 0; i < array_count; i++) + { + print_uniform (format, 0, valueptr); + if (i + 1 != array_count) + g_printerr (","); + valueptr = ((guint8*)valueptr + gsk_gl_uniform_format_size (format)); + } + g_printerr ("]"); + break; + + case GSK_GL_UNIFORM_FORMAT_2I: + g_printerr ("2i<%d,%d>", data->ival[0], data->ival[1]); + break; + + case GSK_GL_UNIFORM_FORMAT_3I: + g_printerr ("3i<%d,%d,%d>", data->ival[0], data->ival[1], data->ival[2]); + break; + + case GSK_GL_UNIFORM_FORMAT_4I: + g_printerr ("3i<%d,%d,%d,%d>", data->ival[0], data->ival[1], data->ival[2], data->ival[3]); + break; + + case GSK_GL_UNIFORM_FORMAT_LAST: + default: + g_assert_not_reached (); + } +} + +G_GNUC_UNUSED static inline void +gsk_gl_command_queue_print_batch (GskGLCommandQueue *self, + const GskGLCommandBatch *batch) +{ + static const char *command_kinds[] = { "Clear", "Draw", }; + guint framebuffer_id; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (batch != NULL); + + if (batch->any.kind == GSK_GL_COMMAND_KIND_CLEAR) + framebuffer_id = batch->clear.framebuffer; + else if (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW) + framebuffer_id = batch->draw.framebuffer; + else + return; + + g_printerr ("Batch {\n"); + g_printerr (" Kind: %s\n", command_kinds[batch->any.kind]); + g_printerr (" Viewport: %dx%d\n", batch->any.viewport.width, batch->any.viewport.height); + g_printerr (" Framebuffer: %d\n", framebuffer_id); + + if (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW) + { + g_printerr (" Program: %d\n", batch->any.program); + g_printerr (" Vertices: %d\n", batch->draw.vbo_count); + + for (guint i = 0; i < batch->draw.bind_count; i++) + { + const GskGLCommandBind *bind = &self->batch_binds.items[batch->draw.bind_offset + i]; + g_printerr (" Bind[%d]: %u\n", bind->texture, bind->id); + } + + for (guint i = 0; i < batch->draw.uniform_count; i++) + { + const GskGLCommandUniform *uniform = &self->batch_uniforms.items[batch->draw.uniform_offset + i]; + g_printerr (" Uniform[%02d]: ", uniform->location); + print_uniform (uniform->info.format, + uniform->info.array_count, + gsk_gl_uniform_state_get_uniform_data (self->uniforms, uniform->info.offset)); + g_printerr ("\n"); + } + } + else if (batch->any.kind == GSK_GL_COMMAND_KIND_CLEAR) + { + g_printerr (" Bits: 0x%x\n", batch->clear.bits); + } + + g_printerr ("}\n"); +} + +G_GNUC_UNUSED static inline void +gsk_gl_command_queue_capture_png (GskGLCommandQueue *self, + const char *filename, + guint width, + guint height, + gboolean flip_y) +{ + guint stride; + guint8 *data; + GBytes *bytes; + GdkTexture *texture; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (filename != NULL); + + stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width); + data = g_malloc_n (height, stride); + + /* GL_UNSIGNED_INT_8_8_8_8_REV, not GL_UNSIGNED_BYTE: cairo's ARGB32 is + * native endian, so a byte-order-dependent read gets it wrong on big endian. + */ + glReadPixels (0, 0, width, height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data); + + if (flip_y) + { + guint8 *flipped = g_malloc_n (height, stride); + + for (guint i = 0; i < height; i++) + memcpy (flipped + (height * stride) - ((i + 1) * stride), + data + (stride * i), + stride); + + g_free (data); + data = flipped; + } + + bytes = g_bytes_new_take (data, height * stride); + texture = gdk_memory_texture_new (width, height, GDK_MEMORY_DEFAULT, bytes, stride); + g_bytes_unref (bytes); + + gdk_texture_save_to_png (texture, filename); + g_object_unref (texture); +} +#endif + +static inline gboolean +will_ignore_batch (GskGLCommandQueue *self) +{ + if G_LIKELY (self->batches.len < G_MAXINT16) + return FALSE; + + if (!self->have_truncated) + { + self->have_truncated = TRUE; + g_critical ("GL command queue too large, truncating further batches."); + } + + return TRUE; +} + +static inline GLint +min_filter_from_index (guint index) +{ + GLint filters[3] = { GL_LINEAR, GL_NEAREST, GL_LINEAR_MIPMAP_LINEAR }; + + g_assert (index < GSK_GL_N_FILTERS); + + return filters[index]; +} + +static inline GLint +mag_filter_from_index (guint index) +{ + GLint filters[3] = { GL_LINEAR, GL_NEAREST, GL_LINEAR }; + + g_assert (index < GSK_GL_N_FILTERS); + + return filters[index]; +} + +static inline guint +snapshot_attachments (const GskGLAttachmentState *state, + GskGLCommandBinds *array) +{ + GskGLCommandBind *bind = gsk_gl_command_binds_append_n (array, G_N_ELEMENTS (state->textures)); + guint count = 0; + + for (guint i = 0; i < G_N_ELEMENTS (state->textures); i++) + { + if (state->textures[i].id) + { + bind[count].id = state->textures[i].id; + bind[count].texture = state->textures[i].texture; + if (state->textures[i].target == GL_TEXTURE_EXTERNAL_OES) + bind[count].sampler = SAMPLER_EXTERNAL; + else + bind[count].sampler = state->textures[i].sampler; + count++; + } + } + + if (count != G_N_ELEMENTS (state->textures)) + array->len -= G_N_ELEMENTS (state->textures) - count; + + return count; +} + +static inline guint +snapshot_uniforms (GskGLUniformState *state, + GskGLUniformProgram *program, + GskGLCommandUniforms *array) +{ + GskGLCommandUniform *uniform = gsk_gl_command_uniforms_append_n (array, program->n_mappings); + guint count = 0; + + for (guint i = 0; i < program->n_mappings; i++) + { + const GskGLUniformMapping *mapping = &program->mappings[i]; + + if (!mapping->info.initial && mapping->info.format && mapping->location > -1) + { + uniform[count].location = mapping->location; + uniform[count].info = mapping->info; + count++; + } + } + + if (count != program->n_mappings) + array->len -= program->n_mappings - count; + + return count; +} + +static inline gboolean +snapshots_equal (GskGLCommandQueue *self, + GskGLCommandBatch *first, + GskGLCommandBatch *second) +{ + if (first->draw.bind_count != second->draw.bind_count || + first->draw.uniform_count != second->draw.uniform_count) + return FALSE; + + for (guint i = 0; i < first->draw.bind_count; i++) + { + const GskGLCommandBind *fb = &self->batch_binds.items[first->draw.bind_offset+i]; + const GskGLCommandBind *sb = &self->batch_binds.items[second->draw.bind_offset+i]; + + if (fb->id != sb->id || fb->texture != sb->texture) + return FALSE; + } + + for (guint i = 0; i < first->draw.uniform_count; i++) + { + const GskGLCommandUniform *fu = &self->batch_uniforms.items[first->draw.uniform_offset+i]; + const GskGLCommandUniform *su = &self->batch_uniforms.items[second->draw.uniform_offset+i]; + gconstpointer fdata; + gconstpointer sdata; + gsize len; + + /* Short circuit if we'd end up with the same memory */ + if (fu->info.offset == su->info.offset) + continue; + + if (fu->info.format != su->info.format || + fu->info.array_count != su->info.array_count) + return FALSE; + + fdata = gsk_gl_uniform_state_get_uniform_data (self->uniforms, fu->info.offset); + sdata = gsk_gl_uniform_state_get_uniform_data (self->uniforms, su->info.offset); + + switch (fu->info.format) + { + case GSK_GL_UNIFORM_FORMAT_1F: + case GSK_GL_UNIFORM_FORMAT_1FV: + case GSK_GL_UNIFORM_FORMAT_1I: + case GSK_GL_UNIFORM_FORMAT_TEXTURE: + case GSK_GL_UNIFORM_FORMAT_1UI: + len = 4; + break; + + case GSK_GL_UNIFORM_FORMAT_2F: + case GSK_GL_UNIFORM_FORMAT_2FV: + case GSK_GL_UNIFORM_FORMAT_2I: + len = 8; + break; + + case GSK_GL_UNIFORM_FORMAT_3F: + case GSK_GL_UNIFORM_FORMAT_3FV: + case GSK_GL_UNIFORM_FORMAT_3I: + len = 12; + break; + + case GSK_GL_UNIFORM_FORMAT_4F: + case GSK_GL_UNIFORM_FORMAT_4FV: + case GSK_GL_UNIFORM_FORMAT_4I: + len = 16; + break; + + + case GSK_GL_UNIFORM_FORMAT_MATRIX: + len = sizeof (float) * 16; + break; + + case GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT: + len = sizeof (float) * 12; + break; + + case GSK_GL_UNIFORM_FORMAT_COLOR: + len = sizeof (float) * 4; + break; + + default: + g_assert_not_reached (); + } + + len *= fu->info.array_count; + + if (memcmp (fdata, sdata, len) != 0) + return FALSE; + } + + return TRUE; +} + +static void +gsk_gl_command_queue_dispose (GObject *object) +{ + GskGLCommandQueue *self = (GskGLCommandQueue *)object; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + + g_clear_object (&self->profiler); + g_clear_object (&self->gl_profiler); + g_clear_object (&self->context); + g_clear_pointer (&self->attachments, gsk_gl_attachment_state_unref); + g_clear_pointer (&self->uniforms, gsk_gl_uniform_state_unref); + + if (self->has_samplers) + glDeleteSamplers (G_N_ELEMENTS (self->samplers), self->samplers); + + gsk_gl_command_batches_clear (&self->batches); + gsk_gl_command_binds_clear (&self->batch_binds); + gsk_gl_command_uniforms_clear (&self->batch_uniforms); + gsk_gl_syncs_clear (&self->syncs); + + gsk_gl_buffer_destroy (&self->vertices); + + g_clear_pointer (&self->float_vertices, g_free); + self->float_vertices_len = 0; + + G_OBJECT_CLASS (gsk_gl_command_queue_parent_class)->dispose (object); +} + +static void +gsk_gl_command_queue_class_init (GskGLCommandQueueClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gsk_gl_command_queue_dispose; +} + +static void +gsk_gl_command_queue_init (GskGLCommandQueue *self) +{ + self->max_texture_size = -1; + + gsk_gl_command_batches_init (&self->batches, 128); + gsk_gl_command_binds_init (&self->batch_binds, 1024); + gsk_gl_command_uniforms_init (&self->batch_uniforms, 2048); + gsk_gl_syncs_init (&self->syncs, 10); + + gsk_gl_buffer_init (&self->vertices, GL_ARRAY_BUFFER, sizeof (GskGLDrawVertex)); +} + +GskGLCommandQueue * +gsk_gl_command_queue_new (GdkGLContext *context, + GskGLUniformState *uniforms) +{ + GskGLCommandQueue *self; + guint i; + + g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL); + + self = g_object_new (GSK_TYPE_GL_COMMAND_QUEUE, NULL); + self->context = g_object_ref (context); + self->attachments = gsk_gl_attachment_state_new (); + + /* Use shared uniform state if we're provided one */ + if (uniforms != NULL) + self->uniforms = gsk_gl_uniform_state_ref (uniforms); + else + self->uniforms = gsk_gl_uniform_state_new (); + + /* Determine max texture size immediately and restore context */ + gdk_gl_context_make_current (context); + + glGetIntegerv (GL_MAX_TEXTURE_SIZE, &self->max_texture_size); + if (g_getenv ("GSK_MAX_TEXTURE_SIZE")) + { + int max_texture_size = atoi (g_getenv ("GSK_MAX_TEXTURE_SIZE")); + if (max_texture_size == 0) + { + g_warning ("Failed to parse %s", "GSK_MAX_TEXTURE_SIZE"); + } + else + { + max_texture_size = MAX (max_texture_size, 512); + GSK_DEBUG (RENDERER, + "Limiting texture size in the GL renderer to %d (via %s)", + max_texture_size, "GSK_MAX_TEXTURE_SIZE"); + self->max_texture_size = MIN (self->max_texture_size, max_texture_size); + } + } + + /* Sampler objects are core in GLES 3.0. On desktop they are core in GL 3.3, + * but only trust that if the driver also does GLSL 1.30, since genuine GL + * 3.3 requires both: drivers that claim 3.3 (or the extension) but top out + * at GLSL 1.20 don't actually export glGenSamplers. + */ + if (gdk_gl_context_get_use_es (context)) + self->has_samplers = gdk_gl_context_check_version (context, NULL, "3.0"); + else + self->has_samplers = gsk_gl_has_glsl_version (1, 30) && + (gdk_gl_context_check_version (context, "3.3", NULL) || + epoxy_has_gl_extension ("GL_ARB_sampler_objects")); + + /* Texture swizzling is GL 3.3 and GLES 3.0, not GL 3.0 */ + self->can_swizzle = gdk_gl_context_check_version (context, "3.3", "3.0") || + epoxy_has_gl_extension ("GL_ARB_texture_swizzle"); + + /* Half float vertex attributes are GL 3.0 and GLES 3.0. GL_HALF_FLOAT and + * GL_HALF_FLOAT_NV share a token value, so the NV extension works as-is; + * GL_HALF_FLOAT_OES does not, so GLES 2 always takes the fallback. + */ + self->has_half_float_vertex = gdk_gl_context_check_version (context, "3.0", "3.0") || + epoxy_has_gl_extension ("GL_ARB_half_float_vertex") || + epoxy_has_gl_extension ("GL_NV_half_float"); + + self->has_sync = gdk_gl_context_has_feature (context, GDK_GL_FEATURE_SYNC); + + if (!self->has_half_float_vertex) + GSK_DEBUG (RENDERER, + "No half float vertex support, expanding vertices on the CPU"); + + /* create the samplers */ + if (self->has_samplers) + { + glGenSamplers (G_N_ELEMENTS (self->samplers), self->samplers); + for (i = 0; i < G_N_ELEMENTS (self->samplers); i++) + { + glSamplerParameteri (self->samplers[i], GL_TEXTURE_MIN_FILTER, min_filter_from_index (i / GSK_GL_N_FILTERS)); + glSamplerParameteri (self->samplers[i], GL_TEXTURE_MAG_FILTER, mag_filter_from_index (i % GSK_GL_N_FILTERS)); + glSamplerParameteri (self->samplers[i], GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glSamplerParameteri (self->samplers[i], GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + } + } + + return g_steal_pointer (&self); +} + +/* Uploads the vertex buffer and points the vertex attributes at it. + * + * Drivers without half float vertex support get the vertices expanded into + * GskGLDrawVertexFloat first; that costs a pass over the vertices and twice + * the buffer memory, but keeps the fast path untouched. + */ +static GLuint +gsk_gl_command_queue_submit_vertices (GskGLCommandQueue *self) +{ + GskGLBuffer *buffer = &self->vertices; + const GskGLDrawVertex *src; + gsize n_vertices; + GLuint id; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + + if G_LIKELY (self->has_half_float_vertex) + { + id = gsk_gl_buffer_submit (buffer); + + /* 0 = position location */ + glEnableVertexAttribArray (0); + glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE, + sizeof (GskGLDrawVertex), + GSIZE_TO_POINTER ((gsize) G_STRUCT_OFFSET (GskGLDrawVertex, position))); + + /* 1 = texture coord location */ + glEnableVertexAttribArray (1); + glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE, + sizeof (GskGLDrawVertex), + GSIZE_TO_POINTER ((gsize) G_STRUCT_OFFSET (GskGLDrawVertex, uv))); + + /* 2 = color location */ + glEnableVertexAttribArray (2); + glVertexAttribPointer (2, 4, GL_HALF_FLOAT, GL_FALSE, + sizeof (GskGLDrawVertex), + GSIZE_TO_POINTER ((gsize) G_STRUCT_OFFSET (GskGLDrawVertex, color))); + + /* 3 = color2 location */ + glEnableVertexAttribArray (3); + glVertexAttribPointer (3, 4, GL_HALF_FLOAT, GL_FALSE, + sizeof (GskGLDrawVertex), + GSIZE_TO_POINTER ((gsize) G_STRUCT_OFFSET (GskGLDrawVertex, color2))); + + return id; + } + + n_vertices = buffer->buffer_pos / sizeof (GskGLDrawVertex); + + if (self->float_vertices_len < n_vertices) + { + self->float_vertices_len = n_vertices; + self->float_vertices = g_realloc_n (self->float_vertices, + self->float_vertices_len, + sizeof (GskGLDrawVertexFloat)); + } + + src = (const GskGLDrawVertex *) buffer->buffer; + + for (gsize i = 0; i < n_vertices; i++) + { + GskGLDrawVertexFloat *dst = &self->float_vertices[i]; + + dst->position[0] = src[i].position[0]; + dst->position[1] = src[i].position[1]; + dst->uv[0] = src[i].uv[0]; + dst->uv[1] = src[i].uv[1]; + half_to_float4 (src[i].color, dst->color); + half_to_float4 (src[i].color2, dst->color2); + } + + glGenBuffers (1, &id); + glBindBuffer (GL_ARRAY_BUFFER, id); + glBufferData (GL_ARRAY_BUFFER, + n_vertices * sizeof (GskGLDrawVertexFloat), + self->float_vertices, + GL_STATIC_DRAW); + + buffer->buffer_pos = 0; + buffer->count = 0; + + glEnableVertexAttribArray (0); + glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE, + sizeof (GskGLDrawVertexFloat), + GSIZE_TO_POINTER ((gsize) G_STRUCT_OFFSET (GskGLDrawVertexFloat, position))); + + glEnableVertexAttribArray (1); + glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE, + sizeof (GskGLDrawVertexFloat), + GSIZE_TO_POINTER ((gsize) G_STRUCT_OFFSET (GskGLDrawVertexFloat, uv))); + + glEnableVertexAttribArray (2); + glVertexAttribPointer (2, 4, GL_FLOAT, GL_FALSE, + sizeof (GskGLDrawVertexFloat), + GSIZE_TO_POINTER ((gsize) G_STRUCT_OFFSET (GskGLDrawVertexFloat, color))); + + glEnableVertexAttribArray (3); + glVertexAttribPointer (3, 4, GL_FLOAT, GL_FALSE, + sizeof (GskGLDrawVertexFloat), + GSIZE_TO_POINTER ((gsize) G_STRUCT_OFFSET (GskGLDrawVertexFloat, color2))); + + return id; +} + +static inline GskGLCommandBatch * +begin_next_batch (GskGLCommandQueue *self) +{ + GskGLCommandBatch *batch; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + + /* GskGLCommandBatch contains an embedded linked list using integers into the + * self->batches array. We can't use pointer because the batches could be + * realloc()'d at runtime. + * + * Before we execute the command queue, we sort the batches by framebuffer but + * leave the batches in place as we can just tweak the links via prev/next. + * + * Generally we only traverse forwards, so we could ignore the previous field. + * But to optimize the reordering of batches by framebuffer we walk backwards + * so we sort by most-recently-seen framebuffer to ensure draws happen in the + * proper order. + */ + + batch = gsk_gl_command_batches_append (&self->batches); + batch->any.next_batch_index = -1; + batch->any.prev_batch_index = self->tail_batch_index; + + return batch; +} + +static void +enqueue_batch (GskGLCommandQueue *self) +{ + guint index; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (self->batches.len > 0); + + /* Batches are linked lists but using indexes into the batches array instead + * of pointers. This is for two main reasons. First, 16-bit indexes allow us + * to store the information in 4 bytes, where as two pointers would take 16 + * bytes. Furthermore, we have an array here so pointers would get + * invalidated if we realloc()'d (and that can happen from time to time). + */ + + index = self->batches.len - 1; + + if (self->head_batch_index == -1) + self->head_batch_index = index; + + if (self->tail_batch_index != -1) + { + GskGLCommandBatch *prev = &self->batches.items[self->tail_batch_index]; + + prev->any.next_batch_index = index; + } + + self->tail_batch_index = index; +} + +static void +discard_batch (GskGLCommandQueue *self) +{ + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (self->batches.len > 0); + + self->batches.len--; +} + +gboolean +gsk_gl_command_queue_begin_draw (GskGLCommandQueue *self, + GskGLUniformProgram *program, + guint width, + guint height) +{ + GskGLCommandBatch *batch; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (self->in_draw == FALSE); + g_assert (width <= G_MAXUINT16); + g_assert (height <= G_MAXUINT16); + + /* Our internal links use 16-bits, so that is our max number + * of batches we can have in one frame. + */ + if (will_ignore_batch (self)) + return FALSE; + + self->program_info = program; + + batch = begin_next_batch (self); + batch->any.kind = GSK_GL_COMMAND_KIND_DRAW; + batch->any.program = program->program_id; + batch->any.next_batch_index = -1; + batch->any.viewport.width = width; + batch->any.viewport.height = height; + batch->draw.blend = 1; + batch->draw.framebuffer = 0; + batch->draw.uniform_count = 0; + batch->draw.uniform_offset = self->batch_uniforms.len; + batch->draw.bind_count = 0; + batch->draw.bind_offset = self->batch_binds.len; + batch->draw.vbo_count = 0; + batch->draw.vbo_offset = gsk_gl_buffer_get_offset (&self->vertices); + + self->fbo_max = MAX (self->fbo_max, batch->draw.framebuffer); + + self->in_draw = TRUE; + + return TRUE; +} + +void +gsk_gl_command_queue_end_draw (GskGLCommandQueue *self) +{ + GskGLCommandBatch *last_batch; + GskGLCommandBatch *batch; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (self->batches.len > 0); + + if (will_ignore_batch (self)) + { + self->in_draw = FALSE; + return; + } + + batch = gsk_gl_command_batches_tail (&self->batches); + + g_assert (self->in_draw == TRUE); + g_assert (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW); + + if G_UNLIKELY (batch->draw.vbo_count == 0) + { + discard_batch (self); + self->in_draw = FALSE; + return; + } + + /* Track the destination framebuffer in case it changed */ + batch->draw.framebuffer = self->attachments->fbo.id; + self->attachments->fbo.changed = FALSE; + self->fbo_max = MAX (self->fbo_max, self->attachments->fbo.id); + + /* Save our full uniform state for this draw so we can possibly + * reorder the draw later. + */ + batch->draw.uniform_offset = self->batch_uniforms.len; + batch->draw.uniform_count = snapshot_uniforms (self->uniforms, self->program_info, &self->batch_uniforms); + + /* Track the bind attachments that changed */ + if (self->program_info->has_attachments) + { + batch->draw.bind_offset = self->batch_binds.len; + batch->draw.bind_count = snapshot_attachments (self->attachments, &self->batch_binds); + } + else + { + batch->draw.bind_offset = 0; + batch->draw.bind_count = 0; + } + + if (self->batches.len > 1) + last_batch = &self->batches.items[self->batches.len - 2]; + else + last_batch = NULL; + + /* Do simple chaining of draw to last batch. */ + if (last_batch != NULL && + last_batch->any.kind == GSK_GL_COMMAND_KIND_DRAW && + last_batch->any.program == batch->any.program && + last_batch->any.viewport.width == batch->any.viewport.width && + last_batch->any.viewport.height == batch->any.viewport.height && + last_batch->draw.blend == batch->draw.blend && + last_batch->draw.framebuffer == batch->draw.framebuffer && + last_batch->draw.vbo_offset + last_batch->draw.vbo_count == batch->draw.vbo_offset && + last_batch->draw.vbo_count + batch->draw.vbo_count <= 0xffff && + snapshots_equal (self, last_batch, batch)) + { + last_batch->draw.vbo_count += batch->draw.vbo_count; + discard_batch (self); + } + else + { + enqueue_batch (self); + } + + self->in_draw = FALSE; + self->program_info = NULL; +} + +/** + * gsk_gl_command_queue_split_draw: + * @self a `GskGLCommandQueue` + * + * This function is like calling gsk_gl_command_queue_end_draw() followed by + * a gsk_gl_command_queue_begin_draw() with the same parameters as a + * previous begin draw (if shared uniforms where not changed further). + * + * This is useful to avoid comparisons inside of loops where we know shared + * uniforms are not changing. + * + * This generally should just be called from gsk_gl_program_split_draw() + * as that is where the begin/end flow happens from the render job. + */ +void +gsk_gl_command_queue_split_draw (GskGLCommandQueue *self) +{ + GskGLCommandBatch *batch; + GskGLUniformProgram *program; + guint width; + guint height; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (self->batches.len > 0); + + if (will_ignore_batch (self)) + return; + + g_assert (self->in_draw == TRUE); + + program = self->program_info; + + batch = gsk_gl_command_batches_tail (&self->batches); + + g_assert (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW); + + width = batch->any.viewport.width; + height = batch->any.viewport.height; + + gsk_gl_command_queue_end_draw (self); + gsk_gl_command_queue_begin_draw (self, program, width, height); +} + +void +gsk_gl_command_queue_clear (GskGLCommandQueue *self, + guint clear_bits, + const graphene_rect_t *viewport) +{ + GskGLCommandBatch *batch; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (self->in_draw == FALSE); + + if (will_ignore_batch (self)) + return; + + if (clear_bits == 0) + clear_bits = GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT; + + batch = begin_next_batch (self); + batch->any.kind = GSK_GL_COMMAND_KIND_CLEAR; + batch->any.viewport.width = viewport->size.width; + batch->any.viewport.height = viewport->size.height; + batch->clear.bits = clear_bits; + batch->clear.framebuffer = self->attachments->fbo.id; + batch->any.next_batch_index = -1; + batch->any.program = 0; + + self->fbo_max = MAX (self->fbo_max, batch->clear.framebuffer); + + enqueue_batch (self); + + self->attachments->fbo.changed = FALSE; +} + +GdkGLContext * +gsk_gl_command_queue_get_context (GskGLCommandQueue *self) +{ + g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self), NULL); + + return self->context; +} + +void +gsk_gl_command_queue_make_current (GskGLCommandQueue *self) +{ + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (GDK_IS_GL_CONTEXT (self->context)); + + gdk_gl_context_make_current (self->context); +} + +void +gsk_gl_command_queue_delete_program (GskGLCommandQueue *self, + guint program) +{ + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + + glDeleteProgram (program); +} + +static inline void +apply_viewport (guint *current_width, + guint *current_height, + guint width, + guint height) +{ + if G_UNLIKELY (*current_width != width || *current_height != height) + { + *current_width = width; + *current_height = height; + glViewport (0, 0, width, height); + } +} + +static inline void +apply_scissor (gboolean *state, + guint framebuffer, + const graphene_rect_t *scissor, + gboolean has_scissor, + guint default_framebuffer) +{ + g_assert (framebuffer != (guint)-1); + + if (framebuffer != default_framebuffer || !has_scissor) + { + if (*state != FALSE) + { + glDisable (GL_SCISSOR_TEST); + *state = FALSE; + } + } + else + { + if (*state != TRUE) + { + glEnable (GL_SCISSOR_TEST); + glScissor (scissor->origin.x, + scissor->origin.y, + scissor->size.width, + scissor->size.height); + *state = TRUE; + } + } +} + +static inline gboolean +apply_framebuffer (int *framebuffer, + guint new_framebuffer) +{ + if G_UNLIKELY (new_framebuffer != *framebuffer) + { + *framebuffer = new_framebuffer; + glBindFramebuffer (GL_FRAMEBUFFER, new_framebuffer); + return TRUE; + } + + return FALSE; +} + +static inline void +gsk_gl_command_queue_unlink (GskGLCommandQueue *self, + GskGLCommandBatch *batch) +{ + if (batch->any.prev_batch_index == -1) + self->head_batch_index = batch->any.next_batch_index; + else + self->batches.items[batch->any.prev_batch_index].any.next_batch_index = batch->any.next_batch_index; + + if (batch->any.next_batch_index == -1) + self->tail_batch_index = batch->any.prev_batch_index; + else + self->batches.items[batch->any.next_batch_index].any.prev_batch_index = batch->any.prev_batch_index; + + batch->any.prev_batch_index = -1; + batch->any.next_batch_index = -1; +} + +static inline void +gsk_gl_command_queue_insert_before (GskGLCommandQueue *self, + GskGLCommandBatch *batch, + GskGLCommandBatch *sibling) +{ + int sibling_index; + int index; + + g_assert (batch >= self->batches.items); + g_assert (batch < &self->batches.items[self->batches.len]); + g_assert (sibling >= self->batches.items); + g_assert (sibling < &self->batches.items[self->batches.len]); + + index = gsk_gl_command_batches_index_of (&self->batches, batch); + sibling_index = gsk_gl_command_batches_index_of (&self->batches, sibling); + + batch->any.next_batch_index = sibling_index; + batch->any.prev_batch_index = sibling->any.prev_batch_index; + + if (batch->any.prev_batch_index > -1) + self->batches.items[batch->any.prev_batch_index].any.next_batch_index = index; + + sibling->any.prev_batch_index = index; + + if (batch->any.prev_batch_index == -1) + self->head_batch_index = index; +} + +static void +gsk_gl_command_queue_sort_batches (GskGLCommandQueue *self) +{ + int *seen; + int *seen_free = NULL; + int index; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (self->tail_batch_index >= 0); + g_assert (self->fbo_max >= 0); + + /* Create our seen list with most recent index set to -1, + * meaning we haven't yet seen that framebuffer. + */ + if (self->fbo_max < 1024) + seen = g_alloca (sizeof (int) * (self->fbo_max + 1)); + else + seen = seen_free = g_new0 (int, (self->fbo_max + 1)); + for (int i = 0; i <= self->fbo_max; i++) + seen[i] = -1; + + /* Walk in reverse, and if we've seen that framebuffer before, we want to + * delay this operation until right before the last batch we saw for that + * framebuffer. + * + * We can do this because we don't use a framebuffer's texture until it has + * been completely drawn. + */ + index = self->tail_batch_index; + + while (index >= 0) + { + GskGLCommandBatch *batch = &self->batches.items[index]; + int cur_index = index; + int fbo = -1; + + g_assert (index > -1); + g_assert (index < self->batches.len); + + switch (batch->any.kind) + { + case GSK_GL_COMMAND_KIND_DRAW: + fbo = batch->draw.framebuffer; + break; + + case GSK_GL_COMMAND_KIND_CLEAR: + fbo = batch->clear.framebuffer; + break; + + default: + g_assert_not_reached (); + } + + index = batch->any.prev_batch_index; + + g_assert (index >= -1); + g_assert (index < (int)self->batches.len); + g_assert (fbo >= -1); + + if (fbo == -1) + continue; + + g_assert (fbo <= self->fbo_max); + g_assert (seen[fbo] >= -1); + g_assert (seen[fbo] < (int)self->batches.len); + + if (seen[fbo] != -1 && seen[fbo] != batch->any.next_batch_index) + { + int mru_index = seen[fbo]; + GskGLCommandBatch *mru = &self->batches.items[mru_index]; + + g_assert (mru_index > -1); + + gsk_gl_command_queue_unlink (self, batch); + + g_assert (batch->any.prev_batch_index == -1); + g_assert (batch->any.next_batch_index == -1); + + gsk_gl_command_queue_insert_before (self, batch, mru); + + g_assert (batch->any.prev_batch_index > -1 || + self->head_batch_index == cur_index); + g_assert (batch->any.next_batch_index == seen[fbo]); + } + + g_assert (cur_index > -1); + g_assert (seen[fbo] >= -1); + + seen[fbo] = cur_index; + } + + g_free (seen_free); +} + +/** + * gsk_gl_command_queue_execute: + * @self: a `GskGLCommandQueue` + * @surface_height: the height of the backing surface + * @scale: the scale of the backing surface + * @scissor: (nullable): the scissor clip if any + * @default_framebuffer: the default framebuffer id if not zero + * + * Executes all of the batches in the command queue. + * + * Typically, the scissor rect is only applied when rendering to the default + * framebuffer (zero in most cases). However, if @default_framebuffer is not + * zero, it will be checked to see if the rendering target matches so that + * the scissor rect is applied. This should be used in cases where rendering + * to the backbuffer for display is not the default GL framebuffer of zero. + * Currently, this happens when rendering on macOS using IOSurface. + */ +void +gsk_gl_command_queue_execute (GskGLCommandQueue *self, + guint surface_height, + float scale, + const cairo_region_t *scissor, + guint default_framebuffer) +{ + G_GNUC_UNUSED guint count = 0; + graphene_rect_t scissor_test = GRAPHENE_RECT_INIT (0, 0, 0, 0); + gboolean has_scissor = scissor != NULL; + gboolean scissor_state = -1; + guint program = 0; + guint width = 0; + guint height = 0; + G_GNUC_UNUSED unsigned int n_binds = 0; + G_GNUC_UNUSED unsigned int n_fbos = 0; + G_GNUC_UNUSED unsigned int n_uniforms = 0; + G_GNUC_UNUSED unsigned int n_programs = 0; + guint vao_id; + guint vbo_id; + int textures[GSK_GL_MAX_TEXTURES_PER_PROGRAM]; + int samplers[GSK_GL_MAX_TEXTURES_PER_PROGRAM]; + int framebuffer = -1; + int next_batch_index; + int active = -1; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (self->in_draw == FALSE); + + if (self->batches.len == 0) + return; + + for (guint i = 0; i < G_N_ELEMENTS (textures); i++) + textures[i] = -1; + for (guint i = 0; i < G_N_ELEMENTS (samplers); i++) + samplers[i] = -1; + + gsk_gl_command_queue_sort_batches (self); + + gsk_gl_command_queue_make_current (self); + + gsk_gl_profiler_begin_gpu_region (self->gl_profiler); + gsk_profiler_timer_begin (self->profiler, self->metrics.cpu_time); + + glDisable (GL_DEPTH_TEST); + + /* Pre-multiplied alpha */ + glEnable (GL_BLEND); + glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + glBlendEquation (GL_FUNC_ADD); + + if (gdk_gl_context_has_vertex_arrays (self->context)) + { + glGenVertexArrays (1, &vao_id); + glBindVertexArray (vao_id); + } + + vbo_id = gsk_gl_command_queue_submit_vertices (self); + + /* Setup initial scissor clip */ + if (scissor != NULL && cairo_region_num_rectangles (scissor) > 0) + { + cairo_rectangle_int_t r; + + g_assert (cairo_region_num_rectangles (scissor) == 1); + cairo_region_get_rectangle (scissor, 0, &r); + + scissor_test.origin.x = (int) floor (r.x * scale); + scissor_test.origin.y = (int) floor (surface_height - (r.height * scale) - (r.y * scale)); + scissor_test.size.width = (int) ceil (r.width * scale); + scissor_test.size.height = (int) ceil (r.height * scale); + } + + next_batch_index = self->head_batch_index; + + while (next_batch_index >= 0) + { + const GskGLCommandBatch *batch = &self->batches.items[next_batch_index]; + + g_assert (next_batch_index >= 0); + g_assert (next_batch_index < self->batches.len); + g_assert (batch->any.next_batch_index != next_batch_index); + + count++; + + switch (batch->any.kind) + { + case GSK_GL_COMMAND_KIND_CLEAR: + if (apply_framebuffer (&framebuffer, batch->clear.framebuffer)) + { + apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor, default_framebuffer); + n_fbos++; + } + + apply_viewport (&width, + &height, + batch->any.viewport.width, + batch->any.viewport.height); + + glClearColor (0, 0, 0, 0); + glClear (batch->clear.bits); + break; + + case GSK_GL_COMMAND_KIND_DRAW: + if (batch->any.program != program) + { + program = batch->any.program; + glUseProgram (program); + + n_programs++; + } + + if (apply_framebuffer (&framebuffer, batch->draw.framebuffer)) + { + apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor, default_framebuffer); + n_fbos++; + } + + apply_viewport (&width, + &height, + batch->any.viewport.width, + batch->any.viewport.height); + + if G_UNLIKELY (batch->draw.bind_count > 0) + { + const GskGLCommandBind *bind = &self->batch_binds.items[batch->draw.bind_offset]; + + g_assert (bind->texture < G_N_ELEMENTS (textures)); + for (guint i = 0; i < batch->draw.bind_count; i++) + { + if (textures[bind->texture] != bind->id) + { + GskGLSync *s; + + if (active != bind->texture) + { + active = bind->texture; + glActiveTexture (GL_TEXTURE0 + bind->texture); + } + + s = gsk_gl_syncs_get_sync (&self->syncs, bind->id); + if (s && s->sync) + { + if (self->has_sync) + glWaitSync ((GLsync) s->sync, 0, GL_TIMEOUT_IGNORED); + s->sync = NULL; + } + + if (bind->sampler == SAMPLER_EXTERNAL) + glBindTexture (GL_TEXTURE_EXTERNAL_OES, bind->id); + else + glBindTexture (GL_TEXTURE_2D, bind->id); + textures[bind->texture] = bind->id; + if (!self->has_samplers) + { + if (bind->sampler == SAMPLER_EXTERNAL) + { + glTexParameteri (GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri (GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + else + { + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter_from_index (bind->sampler / GSK_GL_N_FILTERS)); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter_from_index (bind->sampler % GSK_GL_N_FILTERS)); + } + } + } + + if (samplers[bind->texture] != bind->sampler) + { + if (self->has_samplers) + glBindSampler (bind->texture, self->samplers[bind->sampler]); + else + { + if (bind->sampler == SAMPLER_EXTERNAL) + { + glTexParameteri (GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri (GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + } + else + { + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter_from_index (bind->sampler / GSK_GL_N_FILTERS)); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter_from_index (bind->sampler % GSK_GL_N_FILTERS)); + } + } + samplers[bind->texture] = bind->sampler; + } + + bind++; + } + + n_binds += batch->draw.bind_count; + } + + if (batch->draw.uniform_count > 0) + { + const GskGLCommandUniform *u = &self->batch_uniforms.items[batch->draw.uniform_offset]; + + for (guint i = 0; i < batch->draw.uniform_count; i++, u++) + gsk_gl_uniform_state_apply (self->uniforms, program, u->location, u->info); + + n_uniforms += batch->draw.uniform_count; + } + + if (batch->draw.blend == 0) + glDisable (GL_BLEND); + + glDrawArrays (GL_TRIANGLES, batch->draw.vbo_offset, batch->draw.vbo_count); + + if (batch->draw.blend == 0) + glEnable (GL_BLEND); + break; + + default: + g_assert_not_reached (); + } + +#if 0 + if (batch->any.kind == GSK_GL_COMMAND_KIND_DRAW || + batch->any.kind == GSK_GL_COMMAND_KIND_CLEAR) + { + char filename[128]; + g_snprintf (filename, sizeof filename, + "capture%03u_batch%03d_kind%u_program%u_u%u_b%u_fb%u_ctx%p.png", + count, next_batch_index, + batch->any.kind, batch->any.program, + batch->any.kind == GSK_GL_COMMAND_KIND_DRAW ? batch->draw.uniform_count : 0, + batch->any.kind == GSK_GL_COMMAND_KIND_DRAW ? batch->draw.bind_count : 0, + framebuffer, + gdk_gl_context_get_current ()); + gsk_gl_command_queue_capture_png (self, filename, width, height, TRUE); + gsk_gl_command_queue_print_batch (self, batch); + } +#endif + + next_batch_index = batch->any.next_batch_index; + } + + glDeleteBuffers (1, &vbo_id); + if (gdk_gl_context_has_vertex_arrays (self->context)) + glDeleteVertexArrays (1, &vao_id); + + gdk_profiler_set_int_counter (self->metrics.n_binds, n_binds); + gdk_profiler_set_int_counter (self->metrics.n_uniforms, n_uniforms); + gdk_profiler_set_int_counter (self->metrics.n_fbos, n_fbos); + gdk_profiler_set_int_counter (self->metrics.n_programs, n_programs); + gdk_profiler_set_int_counter (self->metrics.n_uploads, self->n_uploads); + gdk_profiler_set_int_counter (self->metrics.queue_depth, self->batches.len); + + { + gint64 start_time G_GNUC_UNUSED = gsk_profiler_timer_get_start (self->profiler, self->metrics.cpu_time); + gint64 cpu_time = gsk_profiler_timer_end (self->profiler, self->metrics.cpu_time); + gint64 gpu_time = gsk_gl_profiler_end_gpu_region (self->gl_profiler); + + gsk_profiler_timer_set (self->profiler, self->metrics.gpu_time, gpu_time); + gsk_profiler_timer_set (self->profiler, self->metrics.cpu_time, cpu_time); + gsk_profiler_counter_inc (self->profiler, self->metrics.n_frames); + + gsk_profiler_push_samples (self->profiler); + } +} + +void +gsk_gl_command_queue_begin_frame (GskGLCommandQueue *self) +{ + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (self->batches.len == 0); + + gsk_gl_command_queue_make_current (self); + + self->fbo_max = 0; + self->tail_batch_index = -1; + self->head_batch_index = -1; + self->in_frame = TRUE; +} + +/** + * gsk_gl_command_queue_end_frame: + * @self: a `GskGLCommandQueue` + * + * This function performs cleanup steps that need to be done after + * a frame has finished. This is not performed as part of the command + * queue execution to allow for the frame to be submitted as soon + * as possible. + * + * However, it should be executed after the draw contexts end_frame + * has been called to swap the OpenGL framebuffers. + */ +void +gsk_gl_command_queue_end_frame (GskGLCommandQueue *self) +{ + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + + gsk_gl_command_queue_make_current (self); + gsk_gl_uniform_state_end_frame (self->uniforms); + + /* Reset attachments so we don't hold on to any textures + * that might be released after the frame. + */ + for (guint i = 0; i < G_N_ELEMENTS (self->attachments->textures); i++) + { + if (self->attachments->textures[i].id != 0) + { + glActiveTexture (GL_TEXTURE0 + i); + glBindTexture (self->attachments->textures[i].target, 0); + + self->attachments->textures[i].id = 0; + self->attachments->textures[i].changed = FALSE; + self->attachments->textures[i].initial = TRUE; + } + } + + self->batches.len = 0; + self->batch_binds.len = 0; + self->batch_uniforms.len = 0; + self->syncs.len = 0; + self->n_uploads = 0; + self->tail_batch_index = -1; + self->in_frame = FALSE; +} + +gboolean +gsk_gl_command_queue_create_render_target (GskGLCommandQueue *self, + int width, + int height, + int format, + guint *out_fbo_id, + guint *out_texture_id) +{ + GLuint fbo_id = 0; + GLint texture_id; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (width > 0); + g_assert (height > 0); + g_assert (out_fbo_id != NULL); + g_assert (out_texture_id != NULL); + + texture_id = gsk_gl_command_queue_create_texture (self, + width, height, + format); + + if (texture_id == -1) + { + *out_fbo_id = 0; + *out_texture_id = 0; + return FALSE; + } + + fbo_id = gsk_gl_command_queue_create_framebuffer (self); + + glBindFramebuffer (GL_FRAMEBUFFER, fbo_id); + glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0); + g_assert_cmphex (glCheckFramebufferStatus (GL_FRAMEBUFFER), ==, GL_FRAMEBUFFER_COMPLETE); + + *out_fbo_id = fbo_id; + *out_texture_id = texture_id; + + return TRUE; +} + +int +gsk_gl_command_queue_create_texture (GskGLCommandQueue *self, + int width, + int height, + int format) +{ + GLuint texture_id = 0; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + + if G_UNLIKELY (self->max_texture_size == -1) + glGetIntegerv (GL_MAX_TEXTURE_SIZE, &self->max_texture_size); + + if (width > self->max_texture_size || height > self->max_texture_size) + return -1; + + glGenTextures (1, &texture_id); + + glActiveTexture (GL_TEXTURE0); + glBindTexture (GL_TEXTURE_2D, texture_id); + + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + switch (format) + { + case 0: + break; + case GL_RGBA8: + glTexImage2D (GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL); + break; + case GL_RGBA16F: + glTexImage2D (GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_HALF_FLOAT, NULL); + break; + case GL_RGBA32F: + glTexImage2D (GL_TEXTURE_2D, 0, format, width, height, 0, GL_RGBA, GL_FLOAT, NULL); + break; + default: + /* If you add new formats, make sure to set the correct format and type here + * so that GLES doesn't barf invalid operations at you. + * Because it is very important that these 3 values match when data is set to + * NULL, do you hear me? + */ + g_assert_not_reached (); + break; + } + + /* Restore the previous texture if it was set */ + if (self->attachments->textures[0].id != 0 && + self->attachments->textures[0].target == GL_TEXTURE_2D) + glBindTexture (self->attachments->textures[0].target, self->attachments->textures[0].id); + + return (int)texture_id; +} + +guint +gsk_gl_command_queue_create_framebuffer (GskGLCommandQueue *self) +{ + GLuint fbo_id; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + + glGenFramebuffers (1, &fbo_id); + + return fbo_id; +} + +static GdkMemoryFormat +memory_format_gl_format (GskGLCommandQueue *self, + GdkMemoryFormat data_format, + gboolean ensure_mipmap, + gboolean *out_can_mipmap, + GLint *gl_internalformat, + GLint *gl_internalsrgbformat, + GLenum *gl_format, + GLenum *gl_type, + GLint gl_swizzle[4]) +{ + GdkGLMemoryFlags flags, required_flags; + GdkMemoryFormat alt_format; + const GdkMemoryFormat *fallbacks; + GdkSwizzle gdk_sw; + gsize i; + + /* No support for straight formats yet */ + if (gdk_memory_format_alpha (data_format) == GDK_MEMORY_ALPHA_STRAIGHT) + data_format = gdk_memory_format_get_premultiplied (data_format); + + required_flags = GDK_GL_FORMAT_USABLE | GDK_GL_FORMAT_FILTERABLE; + if (ensure_mipmap) + required_flags |= GDK_GL_FORMAT_RENDERABLE; + + /* First, try the format itself */ + flags = gdk_gl_context_get_format_flags (self->context, data_format); + if ((flags & required_flags) == required_flags) + { + gdk_memory_format_gl_format (data_format, + 0, /* plane */ + gdk_gl_context_get_use_es (self->context), + gl_internalformat, + gl_internalsrgbformat, + gl_format, + gl_type, + &gdk_sw); + gdk_swizzle_to_gl (gdk_sw, gl_swizzle); + *out_can_mipmap = (flags & GDK_GL_FORMAT_RENDERABLE) ? TRUE : FALSE; + return data_format; + } + + /* Second, try the potential RGBA format */ + { + GdkSwizzle alt_swizzle; + if (gdk_memory_format_get_rgba_format (data_format, &alt_format, &alt_swizzle)) + { + flags = gdk_gl_context_get_format_flags (self->context, alt_format); + if ((flags & required_flags) == required_flags) + { + *out_can_mipmap = (flags & GDK_GL_FORMAT_RENDERABLE) ? TRUE : FALSE; + + gdk_memory_format_gl_format (alt_format, + 0, /* plane */ + gdk_gl_context_get_use_es (self->context), + gl_internalformat, + gl_internalsrgbformat, + gl_format, + gl_type, + &gdk_sw); + + if (self->can_swizzle) + { + /* Upload as alt_format, but swizzle to display as data_format */ + gdk_swizzle_to_gl (alt_swizzle, gl_swizzle); + return data_format; + } + + gdk_swizzle_to_gl (gdk_sw, gl_swizzle); + return alt_format; + } + } + } + + /* Next, try the fallbacks */ + fallbacks = gdk_memory_format_get_fallbacks (data_format); + for (i = 0; fallbacks[i] != -1; i++) + { + flags = gdk_gl_context_get_format_flags (self->context, fallbacks[i]); + if (((flags & required_flags) == required_flags)) + { + gdk_memory_format_gl_format (fallbacks[i], + 0, /* plane */ + gdk_gl_context_get_use_es (self->context), + gl_internalformat, + gl_internalsrgbformat, + gl_format, + gl_type, + &gdk_sw); + gdk_swizzle_to_gl (gdk_sw, gl_swizzle); + + *out_can_mipmap = (flags & GDK_GL_FORMAT_RENDERABLE) ? TRUE : FALSE; + return fallbacks[i]; + } + } + + g_assert_not_reached (); + return GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; +} + +static void +gsk_gl_command_queue_do_upload_texture_chunk (GskGLCommandQueue *self, + GdkTexture *texture, + int x, + int y, + GdkMemoryFormat data_format, + GLenum gl_format, + GLenum gl_type, + GLint gl_swizzle[4]) + +{ + G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME; + const guchar *data; + gsize stride; + GBytes *bytes; + GdkTextureDownloader downloader; + int width, height; + gsize bpp; + + width = gdk_texture_get_width (texture); + height = gdk_texture_get_height (texture); + + if (GSK_DEBUG_CHECK (FALLBACK) && + data_format != gdk_texture_get_format (texture)) + { + GEnumClass *enum_class = g_type_class_ref (GDK_TYPE_MEMORY_FORMAT); + + gdk_debug_message ("Unsupported format %s, converting on CPU to %s", + g_enum_get_value (enum_class, gdk_texture_get_format (texture))->value_nick, + g_enum_get_value (enum_class, data_format)->value_nick); + + g_type_class_unref (enum_class); + } + + gdk_texture_downloader_init (&downloader, texture); + gdk_texture_downloader_set_format (&downloader, data_format); + bytes = gdk_texture_downloader_download_bytes (&downloader, &stride); + gdk_texture_downloader_finish (&downloader); + data = g_bytes_get_data (bytes, NULL); + bpp = gdk_memory_format_get_plane_block_bytes (data_format, 0); + + if (gdk_profiler_is_running ()) + { + gdk_profiler_end_markf (start_time, + "Download texture chunk", + "Tile %dx%d Size %dx%d", x, y, width, height); + start_time = GDK_PROFILER_CURRENT_TIME; + } + + glPixelStorei (GL_UNPACK_ALIGNMENT, gdk_memory_format_alignment (data_format)); + + /* GL_UNPACK_ROW_LENGTH is available on desktop GL, OpenGL ES >= 3.0, or if + * the GL_EXT_unpack_subimage extension for OpenGL ES 2.0 is available + */ + if (stride == width * bpp) + { + glTexSubImage2D (GL_TEXTURE_2D, 0, x, y, width, height, gl_format, gl_type, data); + } + else if (stride % bpp == 0) + { + glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / bpp); + + glTexSubImage2D (GL_TEXTURE_2D, 0, x, y, width, height, gl_format, gl_type, data); + + glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); + } + else + { + for (int i = 0; i < height; i++) + glTexSubImage2D (GL_TEXTURE_2D, 0, x, y + i, width, 1, gl_format, gl_type, data + (i * stride)); + } + + glPixelStorei (GL_UNPACK_ALIGNMENT, 4); + + /* Only apply swizzle if really needed, might not even be + * supported if default values are set + */ + if (self->can_swizzle && + (gl_swizzle[0] != GL_RED || gl_swizzle[1] != GL_GREEN || gl_swizzle[2] != GL_BLUE || gl_swizzle[3] != GL_ALPHA)) + { + /* Set each channel independently since GLES 3.0 doesn't support the iv method */ + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_R, gl_swizzle[0]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_G, gl_swizzle[1]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_B, gl_swizzle[2]); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_A, gl_swizzle[3]); + } + + g_bytes_unref (bytes); + + if (gdk_profiler_is_running ()) + gdk_profiler_end_markf (start_time, + "Upload texture chunk", + "Tile %dx%d Size %dx%d", x, y, width, height); +} + +int +gsk_gl_command_queue_upload_texture_chunks (GskGLCommandQueue *self, + gboolean ensure_mipmap, + unsigned int n_chunks, + GskGLTextureChunk *chunks, + gboolean *out_can_mipmap) +{ + G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME; + int width, height; + GdkMemoryFormat data_format; + GLint gl_internalformat, gl_internalsrgbformat; + GLenum gl_format; + GLenum gl_type; + GLint gl_swizzle[4]; + int texture_id; + + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + + width = height = 0; + for (unsigned int i = 0; i < n_chunks; i++) + { + GskGLTextureChunk *c = &chunks[i]; + + width = MAX (width, c->x + gdk_texture_get_width (c->texture)); + height = MAX (height, c->y + gdk_texture_get_height (c->texture)); + } + + 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. " + "Clipping will occur.", + width, height, self->max_texture_size); + width = MIN (width, self->max_texture_size); + height = MIN (height, self->max_texture_size); + } + + texture_id = gsk_gl_command_queue_create_texture (self, width, height, 0); + if (texture_id == -1) + return texture_id; + + self->n_uploads++; + + /* Switch to texture0 as 2D. We'll restore it later. */ + glActiveTexture (GL_TEXTURE0); + glBindTexture (GL_TEXTURE_2D, texture_id); + + /* Initialize the texture */ + data_format = gdk_texture_get_format (chunks[0].texture); + data_format = memory_format_gl_format (self, + data_format, + ensure_mipmap, + out_can_mipmap, + &gl_internalformat, + &gl_internalsrgbformat, + &gl_format, + &gl_type, + gl_swizzle); + + glTexImage2D (GL_TEXTURE_2D, 0, gl_internalformat, width, height, 0, gl_format, gl_type, NULL); + + for (unsigned int i = 0; i < n_chunks; i++) + { + GskGLTextureChunk *c = &chunks[i]; + gsk_gl_command_queue_do_upload_texture_chunk (self, c->texture, c->x, c->y, data_format, gl_format, gl_type, gl_swizzle); + } + + /* Restore previous texture state if any */ + if (self->attachments->textures[0].id > 0) + glBindTexture (self->attachments->textures[0].target, + self->attachments->textures[0].id); + + if (gdk_profiler_is_running ()) + gdk_profiler_add_markf (start_time, GDK_PROFILER_CURRENT_TIME-start_time, + "Upload Texture", + "Size %dx%d", width, height); + + return texture_id; +} + +int +gsk_gl_command_queue_upload_texture (GskGLCommandQueue *self, + GdkTexture *texture, + gboolean ensure_mipmap, + gboolean *out_can_mipmap) +{ + return gsk_gl_command_queue_upload_texture_chunks (self, + ensure_mipmap, + 1, + &(GskGLTextureChunk){ texture, 0, 0}, + out_can_mipmap); +} + +void +gsk_gl_command_queue_set_profiler (GskGLCommandQueue *self, + GskProfiler *profiler) +{ + g_assert (GSK_IS_GL_COMMAND_QUEUE (self)); + g_assert (GSK_IS_PROFILER (profiler)); + + if (g_set_object (&self->profiler, profiler)) + { + self->gl_profiler = gsk_gl_profiler_new (self->context); + + self->metrics.n_frames = gsk_profiler_add_counter (profiler, "frames", "Frames", FALSE); + self->metrics.cpu_time = gsk_profiler_add_timer (profiler, "cpu-time", "CPU Time", FALSE, TRUE); + self->metrics.gpu_time = gsk_profiler_add_timer (profiler, "gpu-time", "GPU Time", FALSE, TRUE); + + self->metrics.n_binds = gdk_profiler_define_int_counter ("attachments", "Number of texture attachments"); + self->metrics.n_fbos = gdk_profiler_define_int_counter ("fbos", "Number of framebuffers attached"); + self->metrics.n_uniforms = gdk_profiler_define_int_counter ("uniforms", "Number of uniforms changed"); + self->metrics.n_uploads = gdk_profiler_define_int_counter ("uploads", "Number of texture uploads"); + self->metrics.n_programs = gdk_profiler_define_int_counter ("programs", "Number of program changes"); + self->metrics.queue_depth = gdk_profiler_define_int_counter ("gl-queue-depth", "Depth of GL command batches"); + } +} diff --git gsk/gl/gskglcommandqueueprivate.h gsk/gl/gskglcommandqueueprivate.h new file mode 100644 index 0000000000..aa848e689a --- /dev/null +++ gsk/gl/gskglcommandqueueprivate.h @@ -0,0 +1,474 @@ +/* gskglcommandqueueprivate.h + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include + +#include "gskgltypesprivate.h" +#include "gskglbufferprivate.h" +#include "gskglattachmentstateprivate.h" +#include "gskgluniformstateprivate.h" + +#include "inlinearray.h" + +#include "gskglprofilerprivate.h" + +#include + +G_BEGIN_DECLS + +/* The GL version a driver reports and the GLSL version it can actually + * compile do not always agree, so ask the driver directly. Requires the + * context to be current. + */ +static inline gboolean +gsk_gl_has_glsl_version (int major, + int minor) +{ + const char *str = (const char *) glGetString (GL_SHADING_LANGUAGE_VERSION); + int maj = 1; + int min = 0; + + /* "OpenGL ES GLSL ES 3.20" and friends have a prefix, skip to the digits */ + if (str != NULL) + { + while (*str != '\0' && !g_ascii_isdigit (*str)) + str++; + + if (sscanf (str, "%d.%d", &maj, &min) != 2) + { + maj = 1; + min = 0; + } + } + + /* GLSL writes 1.10 as "1.10", ie the minor version is two digits */ + return maj > major || (maj == major && min >= minor); +} + +#define GSK_TYPE_GL_COMMAND_QUEUE (gsk_gl_command_queue_get_type()) + +G_DECLARE_FINAL_TYPE (GskGLCommandQueue, gsk_gl_command_queue, GSK, GL_COMMAND_QUEUE, GObject) + +typedef enum _GskGLCommandKind +{ + /* The batch will perform a glClear() */ + GSK_GL_COMMAND_KIND_CLEAR, + + /* The batch will perform a glDrawArrays() */ + GSK_GL_COMMAND_KIND_DRAW, +} GskGLCommandKind; + +typedef struct _GskGLCommandBind +{ + /* @texture is the value passed to glActiveTexture(), the "slot" the + * texture will be placed into. We always use GL_TEXTURE_2D so we don't + * waste any bits here to indicate that. + */ + guint texture : 4; + + /* the sampler to use. We set sampler to 15 to indicate external textures */ + guint sampler : 4; + + /* The identifier for the texture created with glGenTextures(). */ + guint id: 24; +} GskGLCommandBind; + +G_STATIC_ASSERT (sizeof (GskGLCommandBind) == 4); + +typedef struct _GskGLCommandBatchAny +{ + /* A GskGLCommandKind indicating what the batch will do */ + guint kind : 8; + + /* The program's identifier to use for determining if we can merge two + * batches together into a single set of draw operations. We put this + * here instead of the GskGLCommandDraw so that we can use the extra + * bits here without making the structure larger. + */ + guint program : 24; + + /* The index of the next batch following this one. This is used + * as a sort of integer-based linked list to simplify out-of-order + * batching without moving memory around. -1 indicates last batch. + */ + gint16 next_batch_index; + + /* Same but for reverse direction as we sort in reverse to get the + * batches ordered by framebuffer. + */ + gint16 prev_batch_index; + + /* The viewport size of the batch. We check this as we process + * batches to determine if we need to resize the viewport. + */ + struct { + guint16 width; + guint16 height; + } viewport; +} GskGLCommandBatchAny; + +G_STATIC_ASSERT (sizeof (GskGLCommandBatchAny) == 12); + +typedef struct _GskGLCommandDraw +{ + GskGLCommandBatchAny head; + + guint blend : 1; + + /* There doesn't seem to be a limit on the framebuffer identifier that + * can be returned, so we have to use a whole unsigned for the framebuffer + * we are drawing to. When processing batches, we check to see if this + * changes and adjust the render target accordingly. Some sorting is + * performed to reduce the amount we change framebuffers. + */ + guint framebuffer : 31; + + /* The number of uniforms to change. This must be less than or equal to + * GL_MAX_UNIFORM_LOCATIONS but only guaranteed up to 1024 by any OpenGL + * implementation to be conformant. + */ + guint uniform_count : 11; + + /* The number of textures to bind, which is only guaranteed up to 16 + * by the OpenGL specification to be conformant. + */ + guint bind_count : 5; + + /* GL_MAX_ELEMENTS_VERTICES specifies 33000 for this which requires 16-bit + * to address all possible counts <= GL_MAX_ELEMENTS_VERTICES. + */ + guint vbo_count : 16; + + /* The offset within the VBO containing @vbo_count vertices to send with + * glDrawArrays(). + */ + guint vbo_offset; + + /* The offset within the array of uniform changes to be made containing + * @uniform_count `GskGLCommandUniform` elements to apply. + */ + guint uniform_offset; + + /* The offset within the array of bind changes to be made containing + * @bind_count `GskGLCommandBind` elements to apply. + */ + guint bind_offset; +} GskGLCommandDraw; + +G_STATIC_ASSERT (sizeof (GskGLCommandDraw) == 32); + +typedef struct _GskGLCommandClear +{ + GskGLCommandBatchAny any; + guint bits; + guint framebuffer; +} GskGLCommandClear; + +G_STATIC_ASSERT (sizeof (GskGLCommandClear) == 20); + +typedef struct _GskGLCommandUniform +{ + GskGLUniformInfo info; + guint location; +} GskGLCommandUniform; + +G_STATIC_ASSERT (sizeof (GskGLCommandUniform) == 8); + +typedef union _GskGLCommandBatch +{ + GskGLCommandBatchAny any; + GskGLCommandDraw draw; + GskGLCommandClear clear; +} GskGLCommandBatch; + +G_STATIC_ASSERT (sizeof (GskGLCommandBatch) == 32); + +typedef struct _GskGLSync { + guint id; + gpointer sync; +} GskGLSync; + +DEFINE_INLINE_ARRAY (GskGLCommandBatches, gsk_gl_command_batches, GskGLCommandBatch) +DEFINE_INLINE_ARRAY (GskGLCommandBinds, gsk_gl_command_binds, GskGLCommandBind) +DEFINE_INLINE_ARRAY (GskGLCommandUniforms, gsk_gl_command_uniforms, GskGLCommandUniform) +DEFINE_INLINE_ARRAY (GskGLSyncs, gsk_gl_syncs, GskGLSync) + +struct _GskGLCommandQueue +{ + GObject parent_instance; + + /* The GdkGLContext we make current before executing GL commands. */ + GdkGLContext *context; + + /* Array of GskGLCommandBatch which is a fixed size structure that will + * point into offsets of other arrays so that all similar data is stored + * together. The idea here is that we reduce the need for pointers so that + * using g_realloc()'d arrays is fine. + */ + GskGLCommandBatches batches; + + /* Contains array of vertices and some wrapper code to help upload them + * to the GL driver. We can also tweak this to use double buffered arrays + * if we find that to be faster on some hardware and/or drivers. + */ + GskGLBuffer vertices; + + /* The GskGLAttachmentState contains information about our FBO and texture + * attachments as we process incoming operations. We snapshot them into + * various batches so that we can compare differences between merge + * candidates. + */ + GskGLAttachmentState *attachments; + + /* The uniform state across all programs. We snapshot this into batches so + * that we can compare uniform state between batches to give us more + * chances at merging draw commands. + */ + GskGLUniformState *uniforms; + + /* Current program if we are in a draw so that we can send commands + * to the uniform state as needed. + */ + GskGLUniformProgram *program_info; + + /* The profiler instance to deliver timing/etc data */ + GskProfiler *profiler; + GskGLProfiler *gl_profiler; + + /* Array of GskGLCommandBind which denote what textures need to be attached + * to which slot. GskGLCommandDraw.bind_offset and bind_count reference this + * array to determine what to attach. + */ + GskGLCommandBinds batch_binds; + + /* Array of GskGLCommandUniform denoting which uniforms must be updated + * before the glDrawArrays() may be called. These are referenced from the + * GskGLCommandDraw.uniform_offset and uniform_count fields. + */ + GskGLCommandUniforms batch_uniforms; + + /* Array of samplers that we use for mag/min filter handling. It is indexed + * by the sampler_index() function. + * + * Note that when samplers are not supported (hello GLES), we fall back to + * setting the texture filter, but that needs to be done for every texture. + * + * Also note that we don't use all of these samplers since some combinations + * are invalid. An index of SAMPLER_EXTERNAL is used to indicate an external + * texture, which needs special sampler treatment. + */ + GLuint samplers[GSK_GL_N_FILTERS * GSK_GL_N_FILTERS]; + + /* Array of sync objects to wait on. + */ + GskGLSyncs syncs; + + /* Discovered max texture size when loading the command queue so that we + * can either scale down or slice textures to fit within this size. Assumed + * to be both height and width. + */ + int max_texture_size; + + /* The index of the last batch in @batches, which may not be the element + * at the end of the array, as batches can be reordered. This is used to + * update the "next" index when adding a new batch. + */ + gint16 tail_batch_index; + gint16 head_batch_index; + + /* Max framebuffer we used, so we can sort items faster */ + guint fbo_max; + + /* Various GSK and GDK metric counter ids */ + struct { + GQuark n_frames; + GQuark cpu_time; + GQuark gpu_time; + guint n_binds; + guint n_fbos; + guint n_uniforms; + guint n_uploads; + guint n_programs; + guint queue_depth; + } metrics; + + /* Counter for uploads on the frame */ + guint n_uploads; + + /* Scratch space to expand vertices into when the driver cannot source + * vertex attributes from half floats. Only used if !has_half_float_vertex. + */ + GskGLDrawVertexFloat *float_vertices; + gsize float_vertices_len; + + /* If the GL context is new enough for sampler support */ + guint has_samplers : 1; + + /* If the GL context is new enough to support swizzling (ie is not GLES2) */ + guint can_swizzle : 1; + + /* If the driver can source vertex attributes from half floats */ + guint has_half_float_vertex : 1; + + /* If the driver supports GL_ARB_sync style fences */ + guint has_sync : 1; + + /* If we're inside a begin/end_frame pair */ + guint in_frame : 1; + + /* If we're inside of a begin_draw()/end_draw() pair. */ + guint in_draw : 1; + + /* If we've warned about truncating batches */ + guint have_truncated : 1; +}; + +GskGLCommandQueue *gsk_gl_command_queue_new (GdkGLContext *context, + GskGLUniformState *uniforms); +void gsk_gl_command_queue_set_profiler (GskGLCommandQueue *self, + GskProfiler *profiler); +GdkGLContext *gsk_gl_command_queue_get_context (GskGLCommandQueue *self); +void gsk_gl_command_queue_make_current (GskGLCommandQueue *self); +void gsk_gl_command_queue_begin_frame (GskGLCommandQueue *self); +void gsk_gl_command_queue_end_frame (GskGLCommandQueue *self); +void gsk_gl_command_queue_execute (GskGLCommandQueue *self, + guint surface_height, + float scale, + const cairo_region_t *scissor, + guint default_framebuffer); +int gsk_gl_command_queue_upload_texture (GskGLCommandQueue *self, + GdkTexture *texture, + gboolean ensure_mipmap, + gboolean *out_can_mipmap); +int gsk_gl_command_queue_create_texture (GskGLCommandQueue *self, + int width, + int height, + int format); + + +typedef struct { + GdkTexture *texture; + int x; + int y; +} GskGLTextureChunk; + +int gsk_gl_command_queue_upload_texture_chunks(GskGLCommandQueue *self, + gboolean ensure_mipmap, + unsigned int n_chunks, + GskGLTextureChunk *chunks, + gboolean *out_can_mipmap); + +guint gsk_gl_command_queue_create_framebuffer (GskGLCommandQueue *self); +gboolean gsk_gl_command_queue_create_render_target (GskGLCommandQueue *self, + int width, + int height, + int format, + guint *out_fbo_id, + guint *out_texture_id); +void gsk_gl_command_queue_delete_program (GskGLCommandQueue *self, + guint program_id); +void gsk_gl_command_queue_clear (GskGLCommandQueue *self, + guint clear_bits, + const graphene_rect_t *viewport); +gboolean gsk_gl_command_queue_begin_draw (GskGLCommandQueue *self, + GskGLUniformProgram *program_info, + guint width, + guint height); +void gsk_gl_command_queue_end_draw (GskGLCommandQueue *self); +void gsk_gl_command_queue_split_draw (GskGLCommandQueue *self); + +static inline GskGLCommandBatch * +gsk_gl_command_queue_get_batch (GskGLCommandQueue *self) +{ + return gsk_gl_command_batches_tail (&self->batches); +} + +static inline GskGLDrawVertex * +gsk_gl_command_queue_add_vertices (GskGLCommandQueue *self) +{ + gsk_gl_command_queue_get_batch (self)->draw.vbo_count += GSK_GL_N_VERTICES; + return gsk_gl_buffer_advance (&self->vertices, GSK_GL_N_VERTICES); +} + +static inline GskGLDrawVertex * +gsk_gl_command_queue_add_n_vertices (GskGLCommandQueue *self, + guint count) +{ + /* This is a batch form of gsk_gl_command_queue_add_vertices(). Note that + * it does *not* add the count to .draw.vbo_count as the caller is responsible + * for that. + */ + return gsk_gl_buffer_advance (&self->vertices, GSK_GL_N_VERTICES * count); +} + +static inline void +gsk_gl_command_queue_retract_n_vertices (GskGLCommandQueue *self, + guint count) +{ + /* Like gsk_gl_command_queue_add_n_vertices(), this does not tweak + * the draw vbo_count. + */ + gsk_gl_buffer_retract (&self->vertices, GSK_GL_N_VERTICES * count); +} + +static inline guint +gsk_gl_command_queue_bind_framebuffer (GskGLCommandQueue *self, + guint framebuffer) +{ + guint ret = self->attachments->fbo.id; + gsk_gl_attachment_state_bind_framebuffer (self->attachments, framebuffer); + return ret; +} + +static inline GskGLSync * +gsk_gl_syncs_get_sync (GskGLSyncs *syncs, + guint id) +{ + for (unsigned int i = 0; i < syncs->len; i++) + { + GskGLSync *sync = &syncs->items[i]; + if (sync->id == id) + return sync; + } + return NULL; +} + +static inline void +gsk_gl_syncs_add_sync (GskGLSyncs *syncs, + guint id, + gpointer sync) +{ + GskGLSync *s; + + s = gsk_gl_syncs_get_sync (syncs, id); + if (s) + g_assert (s->sync == sync); + else + { + s = gsk_gl_syncs_append (syncs); + s->id = id; + s->sync = sync; + } +} + +G_END_DECLS + diff --git gsk/gl/gskglcompiler.c gsk/gl/gskglcompiler.c new file mode 100644 index 0000000000..0f6e734412 --- /dev/null +++ gsk/gl/gskglcompiler.c @@ -0,0 +1,713 @@ +/* gskglcompiler.c + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include +#include +#include + +#include "gskglcommandqueueprivate.h" +#include "gskglcompilerprivate.h" +#include "gskglprogramprivate.h" + +#define SHADER_VERSION_GLES "100" +#define SHADER_VERSION_GLES3 "300 es" +#define SHADER_VERSION_GL2_LEGACY "110" +#define SHADER_VERSION_GL21_LEGACY "120" +#define SHADER_VERSION_GL3_LEGACY "130" +#define SHADER_VERSION_GL3 "150" + +struct _GskGLCompiler +{ + GObject parent_instance; + + GskGLDriver *driver; + + GBytes *all_preamble; + GBytes *fragment_preamble; + GBytes *vertex_preamble; + GBytes *fragment_source; + GBytes *fragment_suffix; + GBytes *vertex_source; + GBytes *vertex_suffix; + + GArray *attrib_locations; + + const char *glsl_version; + + guint gl3 : 1; + guint gles : 1; + guint gles3 : 1; + guint legacy : 1; + guint debug_shaders : 1; +}; + +typedef struct _GskGLProgramAttrib +{ + const char *name; + guint location; +} GskGLProgramAttrib; + +static GBytes *empty_bytes; + +G_DEFINE_TYPE (GskGLCompiler, gsk_gl_compiler, G_TYPE_OBJECT) + +static void +gsk_gl_compiler_finalize (GObject *object) +{ + GskGLCompiler *self = (GskGLCompiler *)object; + + g_clear_pointer (&self->all_preamble, g_bytes_unref); + g_clear_pointer (&self->fragment_preamble, g_bytes_unref); + g_clear_pointer (&self->vertex_preamble, g_bytes_unref); + g_clear_pointer (&self->vertex_suffix, g_bytes_unref); + g_clear_pointer (&self->fragment_source, g_bytes_unref); + g_clear_pointer (&self->fragment_suffix, g_bytes_unref); + g_clear_pointer (&self->vertex_source, g_bytes_unref); + g_clear_pointer (&self->attrib_locations, g_array_unref); + g_clear_object (&self->driver); + + G_OBJECT_CLASS (gsk_gl_compiler_parent_class)->finalize (object); +} + +static void +gsk_gl_compiler_class_init (GskGLCompilerClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gsk_gl_compiler_finalize; + + empty_bytes = g_bytes_new (NULL, 0); +} + +static void +gsk_gl_compiler_init (GskGLCompiler *self) +{ + self->glsl_version = "150"; + self->attrib_locations = g_array_new (FALSE, FALSE, sizeof (GskGLProgramAttrib)); + self->all_preamble = g_bytes_ref (empty_bytes); + self->vertex_preamble = g_bytes_ref (empty_bytes); + self->fragment_preamble = g_bytes_ref (empty_bytes); + self->vertex_source = g_bytes_ref (empty_bytes); + self->vertex_suffix = g_bytes_ref (empty_bytes); + self->fragment_source = g_bytes_ref (empty_bytes); + self->fragment_suffix = g_bytes_ref (empty_bytes); +} + +GskGLCompiler * +gsk_gl_compiler_new (GskGLDriver *driver, + gboolean debug_shaders) +{ + GskGLCompiler *self; + GdkGLContext *context; + + g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL); + g_return_val_if_fail (driver->shared_command_queue != NULL, NULL); + + self = g_object_new (GSK_TYPE_GL_COMPILER, NULL); + self->driver = g_object_ref (driver); + self->debug_shaders = !!debug_shaders; + + context = gsk_gl_driver_get_context (self->driver); + + if (gdk_gl_context_get_use_es (context)) + { + int maj, min; + + /* for OpenGL/ES 3.0+, use "300 es" as our shader version */ + gdk_gl_context_get_version (context, &maj, &min); + + if (maj >= 3) + { + self->glsl_version = SHADER_VERSION_GLES3; + self->gles3 = TRUE; + } + else + { + self->glsl_version = SHADER_VERSION_GLES; + self->gles = TRUE; + } + } + else if (gdk_gl_context_is_legacy (context)) + { + /* Take the GLSL version from the driver rather than deriving it from + * the GL version: the two do not have to match. Drivers reporting GL + * 3.x whose GLSL tops out at 1.20 exist (older macOS, some X11 + * drivers), and so do GL 2.0 drivers that already do GLSL 1.20. + * + * We never need more than 1.30 here, the legacy shaders use + * attribute/varying throughout. + * + * Make the context current first so glGetString() is valid. + */ + gsk_gl_command_queue_make_current (driver->shared_command_queue); + + if (gsk_gl_has_glsl_version (1, 30)) + self->glsl_version = SHADER_VERSION_GL3_LEGACY; + else if (gsk_gl_has_glsl_version (1, 20)) + self->glsl_version = SHADER_VERSION_GL21_LEGACY; + else + self->glsl_version = SHADER_VERSION_GL2_LEGACY; + + self->legacy = TRUE; + } + else + { + self->glsl_version = SHADER_VERSION_GL3; + self->gl3 = TRUE; + } + + gsk_gl_command_queue_make_current (self->driver->shared_command_queue); + + return g_steal_pointer (&self); +} + +void +gsk_gl_compiler_bind_attribute (GskGLCompiler *self, + const char *name, + guint location) +{ + GskGLProgramAttrib attrib; + + g_return_if_fail (GSK_IS_GL_COMPILER (self)); + g_return_if_fail (name != NULL); + g_return_if_fail (location < 32); + + attrib.name = g_intern_string (name); + attrib.location = location; + + g_array_append_val (self->attrib_locations, attrib); +} + +void +gsk_gl_compiler_clear_attributes (GskGLCompiler *self) +{ + g_return_if_fail (GSK_IS_GL_COMPILER (self)); + + g_array_set_size (self->attrib_locations, 0); +} + +void +gsk_gl_compiler_set_preamble (GskGLCompiler *self, + GskGLCompilerKind kind, + GBytes *preamble_bytes) +{ + GBytes **loc = NULL; + + g_return_if_fail (GSK_IS_GL_COMPILER (self)); + g_return_if_fail (preamble_bytes != NULL); + + if (kind == GSK_GL_COMPILER_ALL) + loc = &self->all_preamble; + else if (kind == GSK_GL_COMPILER_FRAGMENT) + loc = &self->fragment_preamble; + else if (kind == GSK_GL_COMPILER_VERTEX) + loc = &self->vertex_preamble; + else + g_return_if_reached (); + + g_assert (loc != NULL); + + if (*loc != preamble_bytes) + { + g_clear_pointer (loc, g_bytes_unref); + *loc = preamble_bytes ? g_bytes_ref (preamble_bytes) : NULL; + } +} + +void +gsk_gl_compiler_set_preamble_from_resource (GskGLCompiler *self, + GskGLCompilerKind kind, + const char *resource_path) +{ + GError *error = NULL; + GBytes *bytes; + + g_return_if_fail (GSK_IS_GL_COMPILER (self)); + g_return_if_fail (kind == GSK_GL_COMPILER_ALL || + kind == GSK_GL_COMPILER_VERTEX || + kind == GSK_GL_COMPILER_FRAGMENT); + g_return_if_fail (resource_path != NULL); + + bytes = g_resources_lookup_data (resource_path, + G_RESOURCE_LOOKUP_FLAGS_NONE, + &error); + + if (bytes == NULL) + g_warning ("Cannot set shader from resource: %s", error->message); + else + gsk_gl_compiler_set_preamble (self, kind, bytes); + + g_clear_pointer (&bytes, g_bytes_unref); + g_clear_error (&error); +} + +void +gsk_gl_compiler_set_source (GskGLCompiler *self, + GskGLCompilerKind kind, + GBytes *source_bytes) +{ + GBytes **loc = NULL; + + g_return_if_fail (GSK_IS_GL_COMPILER (self)); + g_return_if_fail (kind == GSK_GL_COMPILER_ALL || + kind == GSK_GL_COMPILER_VERTEX || + kind == GSK_GL_COMPILER_FRAGMENT); + + if (source_bytes == NULL) + source_bytes = empty_bytes; + + /* If kind is ALL, then we need to split the fragment and + * vertex shaders from the bytes and assign them individually. + * This safely scans for FRAGMENT_SHADER and VERTEX_SHADER as + * specified within the GLSL resources. Some care is taken to + * use GBytes which reference the original bytes instead of + * copying them. + */ + if (kind == GSK_GL_COMPILER_ALL) + { + gsize len = 0; + const char *source; + const char *vertex_shader_start; + const char *fragment_shader_start; + const char *endpos; + GBytes *fragment_bytes; + GBytes *vertex_bytes; + + g_clear_pointer (&self->fragment_source, g_bytes_unref); + g_clear_pointer (&self->vertex_source, g_bytes_unref); + + source = g_bytes_get_data (source_bytes, &len); + endpos = source + len; + vertex_shader_start = g_strstr_len (source, len, "VERTEX_SHADER"); + fragment_shader_start = g_strstr_len (source, len, "FRAGMENT_SHADER"); + + if (vertex_shader_start == NULL) + { + g_warning ("Failed to locate VERTEX_SHADER in shader source"); + return; + } + + if (fragment_shader_start == NULL) + { + g_warning ("Failed to locate FRAGMENT_SHADER in shader source"); + return; + } + + if (vertex_shader_start > fragment_shader_start) + { + g_warning ("VERTEX_SHADER must come before FRAGMENT_SHADER"); + return; + } + + /* Locate next newlines */ + while (vertex_shader_start < endpos && vertex_shader_start[0] != '\n') + vertex_shader_start++; + while (fragment_shader_start < endpos && fragment_shader_start[0] != '\n') + fragment_shader_start++; + + vertex_bytes = g_bytes_new_from_bytes (source_bytes, + vertex_shader_start - source, + fragment_shader_start - vertex_shader_start); + fragment_bytes = g_bytes_new_from_bytes (source_bytes, + fragment_shader_start - source, + endpos - fragment_shader_start); + + gsk_gl_compiler_set_source (self, GSK_GL_COMPILER_VERTEX, vertex_bytes); + gsk_gl_compiler_set_source (self, GSK_GL_COMPILER_FRAGMENT, fragment_bytes); + + g_bytes_unref (fragment_bytes); + g_bytes_unref (vertex_bytes); + + return; + } + + if (kind == GSK_GL_COMPILER_FRAGMENT) + loc = &self->fragment_source; + else if (kind == GSK_GL_COMPILER_VERTEX) + loc = &self->vertex_source; + else + g_return_if_reached (); + + if (*loc != source_bytes) + { + g_clear_pointer (loc, g_bytes_unref); + *loc = g_bytes_ref (source_bytes); + } +} + +void +gsk_gl_compiler_set_source_from_resource (GskGLCompiler *self, + GskGLCompilerKind kind, + const char *resource_path) +{ + GError *error = NULL; + GBytes *bytes; + + g_return_if_fail (GSK_IS_GL_COMPILER (self)); + g_return_if_fail (kind == GSK_GL_COMPILER_ALL || + kind == GSK_GL_COMPILER_VERTEX || + kind == GSK_GL_COMPILER_FRAGMENT); + g_return_if_fail (resource_path != NULL); + + bytes = g_resources_lookup_data (resource_path, + G_RESOURCE_LOOKUP_FLAGS_NONE, + &error); + + if (bytes == NULL) + g_warning ("Cannot set shader from resource: %s", error->message); + else + gsk_gl_compiler_set_source (self, kind, bytes); + + g_clear_pointer (&bytes, g_bytes_unref); + g_clear_error (&error); +} + +void +gsk_gl_compiler_set_suffix (GskGLCompiler *self, + GskGLCompilerKind kind, + GBytes *suffix_bytes) +{ + GBytes **loc; + + g_return_if_fail (GSK_IS_GL_COMPILER (self)); + g_return_if_fail (kind == GSK_GL_COMPILER_VERTEX || + kind == GSK_GL_COMPILER_FRAGMENT); + g_return_if_fail (suffix_bytes != NULL); + + if (suffix_bytes == NULL) + suffix_bytes = empty_bytes; + + if (kind == GSK_GL_COMPILER_FRAGMENT) + loc = &self->fragment_suffix; + else if (kind == GSK_GL_COMPILER_VERTEX) + loc = &self->vertex_suffix; + else + g_return_if_reached (); + + if (*loc != suffix_bytes) + { + g_clear_pointer (loc, g_bytes_unref); + *loc = g_bytes_ref (suffix_bytes); + } +} + +void +gsk_gl_compiler_set_suffix_from_resource (GskGLCompiler *self, + GskGLCompilerKind kind, + const char *resource_path) +{ + GError *error = NULL; + GBytes *bytes; + + g_return_if_fail (GSK_IS_GL_COMPILER (self)); + g_return_if_fail (kind == GSK_GL_COMPILER_VERTEX || + kind == GSK_GL_COMPILER_FRAGMENT); + g_return_if_fail (resource_path != NULL); + + bytes = g_resources_lookup_data (resource_path, + G_RESOURCE_LOOKUP_FLAGS_NONE, + &error); + + if (bytes == NULL) + g_warning ("Cannot set suffix from resource: %s", error->message); + else + gsk_gl_compiler_set_suffix (self, kind, bytes); + + g_clear_pointer (&bytes, g_bytes_unref); + g_clear_error (&error); +} + +static void +prepend_line_numbers (char *code, + GString *s) +{ + char *p; + int line; + + p = code; + line = 1; + while (*p) + { + char *end = strchr (p, '\n'); + if (end) + end = end + 1; /* Include newline */ + else + end = p + strlen (p); + + g_string_append_printf (s, "%3d| ", line++); + g_string_append_len (s, p, end - p); + + p = end; + } +} + +static gboolean +check_shader_error (int shader_id, + GError **error) +{ + GLint status; + GLint log_len; + GLint code_len; + char *buffer; + char *code; + GString *s; + + glGetShaderiv (shader_id, GL_COMPILE_STATUS, &status); + + if G_LIKELY (status == GL_TRUE) + return TRUE; + + glGetShaderiv (shader_id, GL_INFO_LOG_LENGTH, &log_len); + buffer = g_malloc0 (log_len + 1); + glGetShaderInfoLog (shader_id, log_len, NULL, buffer); + + glGetShaderiv (shader_id, GL_SHADER_SOURCE_LENGTH, &code_len); + code = g_malloc0 (code_len + 1); + glGetShaderSource (shader_id, code_len, NULL, code); + + s = g_string_new (""); + prepend_line_numbers (code, s); + + g_set_error (error, + GDK_GL_ERROR, + GDK_GL_ERROR_COMPILATION_FAILED, + "Compilation failure in shader.\n" + "Source Code: %s\n" + "\n" + "Error Message:\n" + "%s\n" + "\n", + s->str, + buffer); + + g_string_free (s, TRUE); + g_free (buffer); + g_free (code); + + return FALSE; +} + +static void +print_shader_info (const char *prefix, + int shader_id, + const char *name) +{ + if (GSK_DEBUG_CHECK(SHADERS)) + { + int code_len; + + glGetShaderiv (shader_id, GL_SHADER_SOURCE_LENGTH, &code_len); + + if (code_len > 0) + { + char *code; + GString *s; + + code = g_malloc0 (code_len + 1); + glGetShaderSource (shader_id, code_len, NULL, code); + + s = g_string_new (NULL); + prepend_line_numbers (code, s); + + g_message ("%s %d, %s:\n%s", + prefix, shader_id, + name ? name : "unnamed", + s->str); + g_string_free (s, TRUE); + g_free (code); + } + } +} + +static const char * +get_shader_string (GBytes *bytes) +{ + /* 0 length bytes will give us NULL back */ + const char *str = g_bytes_get_data (bytes, NULL); + return str ? str : ""; +} + +GskGLProgram * +gsk_gl_compiler_compile (GskGLCompiler *self, + const char *name, + const char *clip, + GError **error) +{ + char version[32]; + const char *debug = ""; + const char *legacy = ""; + const char *gl3 = ""; + const char *gles = ""; + const char *gles3 = ""; + int program_id; + int vertex_id; + int fragment_id; + int status; + + g_return_val_if_fail (GSK_IS_GL_COMPILER (self), NULL); + g_return_val_if_fail (self->all_preamble != NULL, NULL); + g_return_val_if_fail (self->fragment_preamble != NULL, NULL); + g_return_val_if_fail (self->vertex_preamble != NULL, NULL); + g_return_val_if_fail (self->fragment_source != NULL, NULL); + g_return_val_if_fail (self->vertex_source != NULL, NULL); + g_return_val_if_fail (self->driver != NULL, NULL); + + gsk_gl_command_queue_make_current (self->driver->command_queue); + + g_snprintf (version, sizeof version, "#version %s\n", self->glsl_version); + + if (self->debug_shaders) + debug = "#define GSK_DEBUG 1\n"; + + if (self->legacy) + legacy = "#define GSK_LEGACY 1\n"; + + if (self->gles) + gles = "#define GSK_GLES 1\n"; + + if (self->gles3) + gles3 = "#define GSK_GLES3 1\n"; + + if (self->gl3) + gl3 = "#define GSK_GL3 1\n"; + + vertex_id = glCreateShader (GL_VERTEX_SHADER); + glShaderSource (vertex_id, + 11, + (const char *[]) { + version, debug, legacy, gl3, gles, gles3, clip, + get_shader_string (self->all_preamble), + get_shader_string (self->vertex_preamble), + get_shader_string (self->vertex_source), + get_shader_string (self->vertex_suffix), + }, + (int[]) { + strlen (version), + strlen (debug), + strlen (legacy), + strlen (gl3), + strlen (gles), + strlen (gles3), + strlen (clip), + g_bytes_get_size (self->all_preamble), + g_bytes_get_size (self->vertex_preamble), + g_bytes_get_size (self->vertex_source), + g_bytes_get_size (self->vertex_suffix), + }); + glCompileShader (vertex_id); + + if (!check_shader_error (vertex_id, error)) + { + glDeleteShader (vertex_id); + return NULL; + } + + print_shader_info ("Vertex shader", vertex_id, name); + + fragment_id = glCreateShader (GL_FRAGMENT_SHADER); + glShaderSource (fragment_id, + 11, + (const char *[]) { + version, debug, legacy, gl3, gles, gles3, clip, + get_shader_string (self->all_preamble), + get_shader_string (self->fragment_preamble), + get_shader_string (self->fragment_source), + get_shader_string (self->fragment_suffix), + }, + (int[]) { + strlen (version), + strlen (debug), + strlen (legacy), + strlen (gl3), + strlen (gles), + strlen (gles3), + strlen (clip), + g_bytes_get_size (self->all_preamble), + g_bytes_get_size (self->fragment_preamble), + g_bytes_get_size (self->fragment_source), + g_bytes_get_size (self->fragment_suffix), + }); + glCompileShader (fragment_id); + + if (!check_shader_error (fragment_id, error)) + { + glDeleteShader (vertex_id); + glDeleteShader (fragment_id); + return NULL; + } + + print_shader_info ("Fragment shader", fragment_id, name); + + program_id = glCreateProgram (); + glAttachShader (program_id, vertex_id); + glAttachShader (program_id, fragment_id); + + for (guint i = 0; i < self->attrib_locations->len; i++) + { + const GskGLProgramAttrib *attrib; + + attrib = &g_array_index (self->attrib_locations, GskGLProgramAttrib, i); + glBindAttribLocation (program_id, attrib->location, attrib->name); + } + + glLinkProgram (program_id); + + glGetProgramiv (program_id, GL_LINK_STATUS, &status); + + glDetachShader (program_id, vertex_id); + glDeleteShader (vertex_id); + + glDetachShader (program_id, fragment_id); + glDeleteShader (fragment_id); + + if (status == GL_FALSE) + { + char *buffer = NULL; + int log_len = 0; + + glGetProgramiv (program_id, GL_INFO_LOG_LENGTH, &log_len); + + if (log_len > 0) + { + /* log_len includes NULL */ + buffer = g_malloc0 (log_len); + glGetProgramInfoLog (program_id, log_len, NULL, buffer); + } + + g_warning ("Linking failure in shader:\n%s", + buffer ? buffer : ""); + + g_set_error (error, + GDK_GL_ERROR, + GDK_GL_ERROR_LINK_FAILED, + "Linking failure in shader: %s", + buffer ? buffer : ""); + + g_free (buffer); + + glDeleteProgram (program_id); + + return NULL; + } + + return gsk_gl_program_new (self->driver, name, program_id); +} diff --git gsk/gl/gskglcompilerprivate.h gsk/gl/gskglcompilerprivate.h new file mode 100644 index 0000000000..82a2dda81a --- /dev/null +++ gsk/gl/gskglcompilerprivate.h @@ -0,0 +1,68 @@ +/* gskglcompilerprivate.h + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include "gskgltypesprivate.h" + +G_BEGIN_DECLS + +typedef enum _GskGLCompilerKind +{ + GSK_GL_COMPILER_ALL, + GSK_GL_COMPILER_FRAGMENT, + GSK_GL_COMPILER_VERTEX, +} GskGLCompilerKind; + +#define GSK_TYPE_GL_COMPILER (gsk_gl_compiler_get_type()) + +G_DECLARE_FINAL_TYPE (GskGLCompiler, gsk_gl_compiler, GSK, GL_COMPILER, GObject) + +GskGLCompiler * gsk_gl_compiler_new (GskGLDriver *driver, + gboolean debug); +void gsk_gl_compiler_set_preamble (GskGLCompiler *self, + GskGLCompilerKind kind, + GBytes *preamble_bytes); +void gsk_gl_compiler_set_preamble_from_resource (GskGLCompiler *self, + GskGLCompilerKind kind, + const char *resource_path); +void gsk_gl_compiler_set_source (GskGLCompiler *self, + GskGLCompilerKind kind, + GBytes *source_bytes); +void gsk_gl_compiler_set_source_from_resource (GskGLCompiler *self, + GskGLCompilerKind kind, + const char *resource_path); +void gsk_gl_compiler_set_suffix (GskGLCompiler *self, + GskGLCompilerKind kind, + GBytes *suffix_bytes); +void gsk_gl_compiler_set_suffix_from_resource (GskGLCompiler *self, + GskGLCompilerKind kind, + const char *resource_path); +void gsk_gl_compiler_bind_attribute (GskGLCompiler *self, + const char *name, + guint location); +void gsk_gl_compiler_clear_attributes (GskGLCompiler *self); +GskGLProgram * gsk_gl_compiler_compile (GskGLCompiler *self, + const char *name, + const char *clip, + GError **error); + +G_END_DECLS + diff --git gsk/gl/gskgldriver.c gsk/gl/gskgldriver.c new file mode 100644 index 0000000000..7e98349de9 --- /dev/null +++ gsk/gl/gskgldriver.c @@ -0,0 +1,1868 @@ +/* gskgldriver.c + * + * Copyright 2017 Timm Bäder + * Copyright 2018 Matthias Clasen + * Copyright 2018 Alexander Larsson + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include "gskgldriverprivate.h" + +#include +#include +#include + +#include "gskglcommandqueueprivate.h" +#include "gskglcompilerprivate.h" +#include "gskglglyphlibraryprivate.h" +#include "gskgliconlibraryprivate.h" +#include "gskglprogramprivate.h" +#include "gskglshadowlibraryprivate.h" +#include "fp16private.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + + +G_DEFINE_TYPE (GskGLDriver, gsk_gl_driver, G_TYPE_OBJECT) + +static guint +texture_key_hash (gconstpointer v) +{ + const GskTextureKey *k = (const GskTextureKey *)v; + + /* Optimize for 0..3 where 0 is the scaled out case. Usually + * we'll be squarely on 1 or 2 for standard vs HiDPI. When rendering + * to a texture scaled out like in node-editor, we might be < 1. + */ + guint scale_x = floorf (k->scale_x); + guint scale_y = floorf (k->scale_y); + + return GPOINTER_TO_SIZE (k->pointer) ^ + ((scale_x << 8) | + (scale_y << 4) | + k->pointer_is_child); +} + +static gboolean +texture_key_equal (gconstpointer v1, + gconstpointer v2) +{ + const GskTextureKey *k1 = (const GskTextureKey *)v1; + const GskTextureKey *k2 = (const GskTextureKey *)v2; + + return k1->pointer == k2->pointer && + k1->scale_x == k2->scale_x && + k1->scale_y == k2->scale_y && + k1->pointer_is_child == k2->pointer_is_child && + (!k1->pointer_is_child || memcmp (&k1->parent_rect, &k2->parent_rect, sizeof k1->parent_rect) == 0); +} + +static void +remove_texture_key_for_id (GskGLDriver *self, + guint texture_id) +{ + GskTextureKey *key; + + g_assert (GSK_IS_GL_DRIVER (self)); + g_assert (texture_id > 0); + + /* g_hash_table_remove() will cause @key to be freed */ + if (g_hash_table_steal_extended (self->texture_id_to_key, + GUINT_TO_POINTER (texture_id), + NULL, + (gpointer *)&key)) + g_hash_table_remove (self->key_to_texture_id, key); +} + +static void +gsk_gl_texture_destroyed (gpointer data) +{ + ((GskGLTexture *)data)->user = NULL; +} + +static void +gsk_gl_driver_autorelease_texture (GskGLDriver *self, + guint texture_id) +{ + g_assert (GSK_IS_GL_DRIVER (self)); + + g_array_append_val (self->texture_pool, texture_id); +} + +static guint +gsk_gl_driver_collect_unused_textures (GskGLDriver *self, + gint64 watermark) +{ + GHashTableIter iter; + gpointer k, v; + guint old_size; + guint collected; + + g_assert (GSK_IS_GL_DRIVER (self)); + + old_size = g_hash_table_size (self->textures); + + g_hash_table_iter_init (&iter, self->textures); + while (g_hash_table_iter_next (&iter, &k, &v)) + { + GskGLTexture *t = v; + + if (t->user || t->permanent) + continue; + + if (t->last_used_in_frame <= watermark) + { + g_hash_table_iter_steal (&iter); + + g_assert (t->link.prev == NULL); + g_assert (t->link.next == NULL); + g_assert (t->link.data == t); + + remove_texture_key_for_id (self, t->texture_id); + gsk_gl_driver_autorelease_texture (self, t->texture_id); + t->texture_id = 0; + gsk_gl_texture_free (t); + } + } + + collected = old_size - g_hash_table_size (self->textures); + + return collected; +} + +static void +remove_program (gpointer data) +{ + GskGLProgram *program = data; + + g_assert (!program || GSK_IS_GL_PROGRAM (program)); + + if (program != NULL) + { + gsk_gl_program_delete (program); + g_object_unref (program); + } +} + +static void +gsk_gl_driver_shader_weak_cb (gpointer data, + GObject *where_object_was) +{ + GskGLDriver *self = data; + + g_assert (GSK_IS_GL_DRIVER (self)); + + if (self->shader_cache != NULL) + { + if (self->command_queue != NULL) + gsk_gl_command_queue_make_current (self->command_queue); + + g_hash_table_remove (self->shader_cache, where_object_was); + } +} + +G_GNUC_UNUSED G_GNUC_NULL_TERMINATED static inline GBytes * +join_sources (GBytes *first_bytes, + ...) +{ + GByteArray *byte_array = g_byte_array_new (); + GBytes *bytes = first_bytes; + va_list args; + + va_start (args, first_bytes); + while (bytes != NULL) + { + gsize len; + const guint8 *data = g_bytes_get_data (bytes, &len); + if (len > 0) + g_byte_array_append (byte_array, data, len); + g_bytes_unref (bytes); + bytes = va_arg (args, GBytes *); + } + va_end (args); + + return g_byte_array_free_to_bytes (byte_array); +} + +static void +gsk_gl_driver_dispose (GObject *object) +{ + GskGLDriver *self = (GskGLDriver *)object; + + g_assert (GSK_IS_GL_DRIVER (self)); + g_assert (self->in_frame == FALSE); + + if (self->shared_command_queue) + gsk_gl_command_queue_make_current (self->shared_command_queue); + +#define GSK_GL_NO_UNIFORMS +#define GSK_GL_SHADER_RESOURCE(name) +#define GSK_GL_SHADER_STRING(str) +#define GSK_GL_SHADER_SINGLE(name) +#define GSK_GL_SHADER_JOINED(kind, ...) +#define GSK_GL_ADD_UNIFORM(pos, KEY, name) +#define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms) \ + GSK_GL_DELETE_PROGRAM(name); \ + GSK_GL_DELETE_PROGRAM(name ## _no_clip); \ + GSK_GL_DELETE_PROGRAM(name ## _rect_clip); +#define GSK_GL_DEFINE_PROGRAM_NO_CLIP(name, resource, uniforms) \ + GSK_GL_DELETE_PROGRAM(name); +#define GSK_GL_DELETE_PROGRAM(name) \ + G_STMT_START { \ + if (self->name) \ + gsk_gl_program_delete (self->name); \ + g_clear_object (&self->name); \ + } G_STMT_END; +# include "gskglprograms.defs" +#undef GSK_GL_NO_UNIFORMS +#undef GSK_GL_SHADER_RESOURCE +#undef GSK_GL_SHADER_STRING +#undef GSK_GL_SHADER_SINGLE +#undef GSK_GL_SHADER_JOINED +#undef GSK_GL_ADD_UNIFORM +#undef GSK_GL_DEFINE_PROGRAM +#undef GSK_GL_DEFINE_PROGRAM_NO_CLIP + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + if (self->shader_cache != NULL) + { + GHashTableIter iter; + gpointer k, v; + + g_hash_table_iter_init (&iter, self->shader_cache); + while (g_hash_table_iter_next (&iter, &k, &v)) + { + GskGLShader *shader = k; + g_object_weak_unref (G_OBJECT (shader), + gsk_gl_driver_shader_weak_cb, + self); + g_hash_table_iter_remove (&iter); + } + + g_clear_pointer (&self->shader_cache, g_hash_table_unref); + } +G_GNUC_END_IGNORE_DEPRECATIONS + + if (self->command_queue != NULL) + { + gsk_gl_command_queue_make_current (self->command_queue); + gsk_gl_driver_collect_unused_textures (self, 0); + g_clear_object (&self->command_queue); + } + + if (self->autorelease_framebuffers != NULL && + self->autorelease_framebuffers->len > 0) + { + glDeleteFramebuffers (self->autorelease_framebuffers->len, + (GLuint *)(gpointer)self->autorelease_framebuffers->data); + self->autorelease_framebuffers->len = 0; + } + + g_clear_object (&self->glyphs_library); + g_clear_object (&self->icons_library); + g_clear_object (&self->shadows_library); + + g_clear_pointer (&self->texture_pool, g_array_unref); + + g_clear_pointer (&self->autorelease_framebuffers, g_array_unref); + g_clear_pointer (&self->key_to_texture_id, g_hash_table_unref); + g_clear_pointer (&self->textures, g_hash_table_unref); + g_clear_pointer (&self->key_to_texture_id, g_hash_table_unref); + g_clear_pointer (&self->texture_id_to_key, g_hash_table_unref); + g_clear_pointer (&self->render_targets, g_ptr_array_unref); + g_clear_pointer (&self->shader_cache, g_hash_table_unref); + + g_clear_object (&self->command_queue); + g_clear_object (&self->shared_command_queue); + + G_OBJECT_CLASS (gsk_gl_driver_parent_class)->dispose (object); +} + +static void +gsk_gl_driver_class_init (GskGLDriverClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gsk_gl_driver_dispose; +} + +static void +gsk_gl_driver_init (GskGLDriver *self) +{ + self->autorelease_framebuffers = g_array_new (FALSE, FALSE, sizeof (guint)); + self->textures = g_hash_table_new_full (NULL, NULL, NULL, + (GDestroyNotify)gsk_gl_texture_free); + self->texture_id_to_key = g_hash_table_new (NULL, NULL); + self->key_to_texture_id = g_hash_table_new_full (texture_key_hash, + texture_key_equal, + g_free, + NULL); + self->shader_cache = g_hash_table_new_full (NULL, NULL, NULL, remove_program); + self->texture_pool = g_array_new (FALSE, FALSE, sizeof (guint)); + self->render_targets = g_ptr_array_new (); +} + +static gboolean +gsk_gl_driver_load_programs (GskGLDriver *self, + GError **error) +{ + GskGLCompiler *compiler; + gboolean ret = FALSE; + G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME; + + g_assert (GSK_IS_GL_DRIVER (self)); + g_assert (GSK_IS_GL_COMMAND_QUEUE (self->command_queue)); + + compiler = gsk_gl_compiler_new (self, self->debug); + + /* Setup preambles that are shared by all shaders */ + gsk_gl_compiler_set_preamble_from_resource (compiler, + GSK_GL_COMPILER_ALL, + "/org/gtk/libgsk/gl/preamble.glsl"); + gsk_gl_compiler_set_preamble_from_resource (compiler, + GSK_GL_COMPILER_VERTEX, + "/org/gtk/libgsk/gl/preamble.vs.glsl"); + gsk_gl_compiler_set_preamble_from_resource (compiler, + GSK_GL_COMPILER_FRAGMENT, + "/org/gtk/libgsk/gl/preamble.fs.glsl"); + + /* Setup attributes that are provided via VBO */ + gsk_gl_compiler_bind_attribute (compiler, "aPosition", 0); + gsk_gl_compiler_bind_attribute (compiler, "aUv", 1); + gsk_gl_compiler_bind_attribute (compiler, "aColor", 2); + gsk_gl_compiler_bind_attribute (compiler, "aColor2", 3); + + /* Use XMacros to register all of our programs and their uniforms */ +#define GSK_GL_NO_UNIFORMS +#define GSK_GL_SHADER_RESOURCE(name) \ + g_resources_lookup_data("/org/gtk/libgsk/gl/" name, 0, NULL) +#define GSK_GL_SHADER_STRING(str) \ + g_bytes_new_static(str, strlen(str)) +#define GSK_GL_SHADER_SINGLE(bytes) \ + G_STMT_START { \ + GBytes *b = bytes; \ + gsk_gl_compiler_set_source (compiler, GSK_GL_COMPILER_ALL, b); \ + g_bytes_unref (b); \ + } G_STMT_END; +#define GSK_GL_SHADER_JOINED(kind, ...) \ + G_STMT_START { \ + GBytes *bytes = join_sources(__VA_ARGS__); \ + gsk_gl_compiler_set_source (compiler, GSK_GL_COMPILER_##kind, bytes); \ + g_bytes_unref (bytes); \ + } G_STMT_END; +#define GSK_GL_ADD_UNIFORM(pos, KEY, name) \ + gsk_gl_program_add_uniform (program, #name, UNIFORM_##KEY); +#define GSK_GL_DEFINE_PROGRAM(name, sources, uniforms) \ + gsk_gl_compiler_set_source (compiler, GSK_GL_COMPILER_VERTEX, NULL); \ + gsk_gl_compiler_set_source (compiler, GSK_GL_COMPILER_FRAGMENT, NULL); \ + sources \ + GSK_GL_COMPILE_PROGRAM(name ## _no_clip, uniforms, "#define NO_CLIP 1\n"); \ + GSK_GL_COMPILE_PROGRAM(name ## _rect_clip, uniforms, "#define RECT_CLIP 1\n"); \ + GSK_GL_COMPILE_PROGRAM(name, uniforms, ""); +#define GSK_GL_DEFINE_PROGRAM_NO_CLIP(name, sources, uniforms) \ + gsk_gl_compiler_set_source (compiler, GSK_GL_COMPILER_VERTEX, NULL); \ + gsk_gl_compiler_set_source (compiler, GSK_GL_COMPILER_FRAGMENT, NULL); \ + sources \ + GSK_GL_COMPILE_PROGRAM(name, uniforms, "#define NO_CLIP 1\n"); +#define GSK_GL_COMPILE_PROGRAM(name, uniforms, clip) \ + G_STMT_START { \ + GskGLProgram *program; \ + gboolean have_alpha; \ + gboolean have_source; \ + \ + if (!(program = gsk_gl_compiler_compile (compiler, #name, clip, error))) \ + goto failure; \ + \ + have_alpha = gsk_gl_program_add_uniform (program, "u_alpha", UNIFORM_SHARED_ALPHA); \ + have_source = gsk_gl_program_add_uniform (program, "u_source", UNIFORM_SHARED_SOURCE); \ + gsk_gl_program_add_uniform (program, "u_clip_rect", UNIFORM_SHARED_CLIP_RECT); \ + gsk_gl_program_add_uniform (program, "u_viewport", UNIFORM_SHARED_VIEWPORT); \ + gsk_gl_program_add_uniform (program, "u_projection", UNIFORM_SHARED_PROJECTION); \ + gsk_gl_program_add_uniform (program, "u_modelview", UNIFORM_SHARED_MODELVIEW); \ + \ + uniforms \ + \ + gsk_gl_program_uniforms_added (program, have_source); \ + if (have_alpha) \ + gsk_gl_program_set_uniform1f (program, UNIFORM_SHARED_ALPHA, 0, 1.0f); \ + \ + *(GskGLProgram **)(((guint8 *)self) + G_STRUCT_OFFSET (GskGLDriver, name)) = \ + g_steal_pointer (&program); \ + } G_STMT_END; +# include "gskglprograms.defs" +#undef GSK_GL_DEFINE_PROGRAM +#undef GSK_GL_DEFINE_PROGRAM_NO_CLIP +#undef GSK_GL_ADD_UNIFORM +#undef GSK_GL_SHADER_SINGLE +#undef GSK_GL_SHADER_JOINED +#undef GSK_GL_SHADER_RESOURCE +#undef GSK_GL_SHADER_STRING +#undef GSK_GL_NO_UNIFORMS + + ret = TRUE; + +failure: + g_clear_object (&compiler); + + gdk_profiler_end_mark (start_time, "Load GL programs", NULL); + + return ret; +} + +/** + * gsk_gl_driver_autorelease_framebuffer: + * @self: a `GskGLDriver` + * @framebuffer_id: the id of the OpenGL framebuffer + * + * Marks @framebuffer_id to be deleted when the current frame has cmopleted. + */ +static void +gsk_gl_driver_autorelease_framebuffer (GskGLDriver *self, + guint framebuffer_id) +{ + g_assert (GSK_IS_GL_DRIVER (self)); + + g_array_append_val (self->autorelease_framebuffers, framebuffer_id); +} + +static GskGLDriver * +gsk_gl_driver_new (GskGLCommandQueue *command_queue, + gboolean debug_shaders, + GError **error) +{ + GskGLDriver *self; + GdkGLContext *context; + gint64 before G_GNUC_UNUSED; + + g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (command_queue), NULL); + + before = GDK_PROFILER_CURRENT_TIME; + + context = gsk_gl_command_queue_get_context (command_queue); + + gdk_gl_context_make_current (context); + + self = g_object_new (GSK_TYPE_GL_DRIVER, NULL); + self->command_queue = g_object_ref (command_queue); + self->shared_command_queue = g_object_ref (command_queue); + self->debug = !!debug_shaders; + + if (!gsk_gl_driver_load_programs (self, error)) + { + g_object_unref (self); + return NULL; + } + + self->glyphs_library = gsk_gl_glyph_library_new (self); + self->icons_library = gsk_gl_icon_library_new (self); + self->shadows_library = gsk_gl_shadow_library_new (self); + + gdk_profiler_end_mark (before, "Create GL driver", NULL); + + return g_steal_pointer (&self); +} + +static void +free_driver (GskGLDriver *driver) +{ + g_object_run_dispose (G_OBJECT (driver)); + g_object_unref (driver); +} + +static void +display_closed (GdkDisplay *display) +{ + g_object_set_data (G_OBJECT (display), "GSK_GL_DRIVER", NULL); +} + +/** + * gsk_gl_driver_for_display: + * @display: A #GdkDisplay that is known to support GL + * @debug_shaders: if debug information for shaders should be displayed + * @error: location for error information + * + * Retrieves a driver for a shared display. Generally this is shared across all GL + * contexts for a display so that fewer programs are necessary for driving output. + * + * Returns: (transfer full): a `GskGLDriver` if successful; otherwise %NULL and + * @error is set. + */ +GskGLDriver * +gsk_gl_driver_for_display (GdkDisplay *display, + gboolean debug_shaders, + GError **error) +{ + GdkGLContext *context; + GskGLCommandQueue *command_queue = NULL; + GskGLDriver *driver; + + g_return_val_if_fail (GDK_IS_DISPLAY (display), NULL); + + if ((driver = g_object_get_data (G_OBJECT (display), "GSK_GL_DRIVER"))) + return g_object_ref (driver); + + context = gdk_display_get_gl_context (display); + g_assert (context); + + gdk_gl_context_make_current (context); + + /* Initially we create a command queue using the shared context. However, + * as frames are processed this will be replaced with the command queue + * for a given renderer. But since the programs are compiled into the + * shared context, all other contexts sharing with it will have access + * to those programs. + */ + command_queue = gsk_gl_command_queue_new (context, NULL); + + if (!(driver = gsk_gl_driver_new (command_queue, debug_shaders, error))) + goto failure; + + g_object_set_data_full (G_OBJECT (display), + "GSK_GL_DRIVER", + g_object_ref (driver), + (GDestroyNotify) free_driver); + g_signal_connect (display, "closed", G_CALLBACK (display_closed), NULL); + +failure: + g_clear_object (&command_queue); + + return g_steal_pointer (&driver); +} + +/** + * gsk_gl_driver_begin_frame: + * @self: a `GskGLDriver` + * @command_queue: A `GskGLCommandQueue` from the renderer + * + * Begin a new frame. + * + * Texture atlases, pools, and other resources will be prepared to draw the + * next frame. The command queue should be one that was created for the + * target context to be drawn into (the context of the renderer's surface). + */ +void +gsk_gl_driver_begin_frame (GskGLDriver *self, + GskGLCommandQueue *command_queue) +{ + gint64 last_frame_id; + + g_return_if_fail (GSK_IS_GL_DRIVER (self)); + g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (command_queue)); + g_return_if_fail (self->in_frame == FALSE); + + last_frame_id = self->current_frame_id; + + self->in_frame = TRUE; + self->current_frame_id++; + + g_set_object (&self->command_queue, command_queue); + + gsk_gl_command_queue_begin_frame (self->command_queue); + + /* Mark unused pixel regions of the atlases */ + gsk_gl_texture_library_begin_frame (GSK_GL_TEXTURE_LIBRARY (self->icons_library), + self->current_frame_id); + gsk_gl_texture_library_begin_frame (GSK_GL_TEXTURE_LIBRARY (self->glyphs_library), + self->current_frame_id); + + /* Cleanup old shadows */ + gsk_gl_shadow_library_begin_frame (self->shadows_library); + + /* Remove all textures that are from a previous frame or are no + * longer used by linked GdkTexture. We do this at the beginning + * of the following frame instead of the end so that we reduce chances + * we block on any resources while delivering our frames. + */ + gsk_gl_driver_collect_unused_textures (self, last_frame_id - 1); +} + +/** + * gsk_gl_driver_end_frame: + * @self: a `GskGLDriver` + * + * Clean up resources from drawing the current frame. + * + * Temporary resources used while drawing will be released. + */ +void +gsk_gl_driver_end_frame (GskGLDriver *self) +{ + g_return_if_fail (GSK_IS_GL_DRIVER (self)); + g_return_if_fail (self->in_frame == TRUE); + + gsk_gl_command_queue_make_current (self->command_queue); + gsk_gl_command_queue_end_frame (self->command_queue); + + self->in_frame = FALSE; +} + +/** + * gsk_gl_driver_after_frame: + * @self: a `GskGLDriver` + * + * This function does post-frame cleanup operations. + * + * To reduce the chances of blocking on the driver it is performed + * after the frame has swapped buffers. + */ +void +gsk_gl_driver_after_frame (GskGLDriver *self) +{ + g_return_if_fail (GSK_IS_GL_DRIVER (self)); + g_return_if_fail (self->in_frame == FALSE); + + /* Release any render targets (possibly adding them to + * self->autorelease_framebuffers) so we can release the FBOs immediately + * afterwards. + */ + while (self->render_targets->len > 0) + { + GskGLRenderTarget *render_target = g_ptr_array_index (self->render_targets, self->render_targets->len - 1); + + gsk_gl_driver_autorelease_framebuffer (self, render_target->framebuffer_id); + gsk_gl_driver_autorelease_texture (self, render_target->texture_id); + g_free (render_target); + + self->render_targets->len--; + } + + /* Now that we have collected render targets, release all the FBOs */ + if (self->autorelease_framebuffers->len > 0) + { + glDeleteFramebuffers (self->autorelease_framebuffers->len, + (GLuint *)(gpointer)self->autorelease_framebuffers->data); + self->autorelease_framebuffers->len = 0; + } + + /* Release any cached textures we used during the frame */ + if (self->texture_pool->len > 0) + { + glDeleteTextures (self->texture_pool->len, + (GLuint *)(gpointer)self->texture_pool->data); + self->texture_pool->len = 0; + } + + /* Reset command queue to our shared queue in case we have operations + * that need to be processed outside of a frame (such as callbacks + * from external systems such as GDK). + */ + g_set_object (&self->command_queue, self->shared_command_queue); +} + +GdkGLContext * +gsk_gl_driver_get_context (GskGLDriver *self) +{ + g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL); + g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->command_queue), NULL); + + return gsk_gl_command_queue_get_context (self->command_queue); +} + +/** + * gsk_gl_driver_cache_texture: + * @self: a `GskGLDriver` + * @key: the key for the texture + * @texture_id: the id of the texture to be cached + * + * Inserts @texture_id into the texture cache using @key. + * + * Textures can be looked up by @key after calling this function using + * gsk_gl_driver_lookup_texture(). + * + * Textures that have not been used within a number of frames will be + * purged from the texture cache automatically. + */ +void +gsk_gl_driver_cache_texture (GskGLDriver *self, + const GskTextureKey *key, + guint texture_id) +{ + g_assert (GSK_IS_GL_DRIVER (self)); + g_assert (key != NULL); + g_assert (texture_id > 0); + g_assert (g_hash_table_contains (self->textures, GUINT_TO_POINTER (texture_id))); + + if (!g_hash_table_contains (self->key_to_texture_id, key)) + { + GskTextureKey *k; + + k = g_memdup2 (key, sizeof *key); + + g_assert (!g_hash_table_contains (self->texture_id_to_key, GUINT_TO_POINTER (texture_id))); + g_hash_table_insert (self->key_to_texture_id, k, GUINT_TO_POINTER (texture_id)); + g_hash_table_insert (self->texture_id_to_key, GUINT_TO_POINTER (texture_id), k); + } +} + +#if defined(HAVE_DMABUF) && defined (HAVE_EGL) +static void +set_viewport_for_size (GskGLDriver *self, + GskGLProgram *program, + float width, + float height) +{ + float viewport[4] = { 0, 0, width, height }; + + gsk_gl_uniform_state_set4fv (program->uniforms, + program->program_info, + 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 (GskGLDriver *self, + GskGLProgram *program, + float width, + float height) +{ + graphene_matrix_t projection; + + graphene_matrix_init_ortho (&projection, 0, width, 0, height, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE); + graphene_matrix_scale (&projection, 1, -1, 1); + + gsk_gl_uniform_state_set_matrix (program->uniforms, + program->program_info, + UNIFORM_SHARED_PROJECTION, 0, + &projection); + self->stamps[UNIFORM_SHARED_PROJECTION]++; +} + +static void +reset_modelview (GskGLDriver *self, + GskGLProgram *program) +{ + graphene_matrix_t modelview; + + graphene_matrix_init_identity (&modelview); + + gsk_gl_uniform_state_set_matrix (program->uniforms, + program->program_info, + UNIFORM_SHARED_MODELVIEW, 0, + &modelview); + self->stamps[UNIFORM_SHARED_MODELVIEW]++; +} + +static void +draw_rect (GskGLCommandQueue *command_queue, + float min_x, + float min_y, + float max_x, + float max_y) +{ + GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (command_queue); + float min_u = 0; + float max_u = 1; + float min_v = 1; + float max_v = 0; + guint16 c = FP16_ZERO; + + vertices[0] = (GskGLDrawVertex) { .position = { min_x, min_y }, .uv = { min_u, min_v }, .color = { c, c, c, c } }; + vertices[1] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { min_u, max_v }, .color = { c, c, c, c } }; + vertices[2] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { max_u, min_v }, .color = { c, c, c, c } }; + vertices[3] = (GskGLDrawVertex) { .position = { max_x, max_y }, .uv = { max_u, max_v }, .color = { c, c, c, c } }; + vertices[4] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { min_u, max_v }, .color = { c, c, c, c } }; + vertices[5] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { max_u, min_v }, .color = { c, c, c, c } }; +} + +static unsigned int release_render_target (GskGLDriver *self, + GskGLRenderTarget *render_target, + gboolean release_texture, + gboolean cache_texture); + +static guint +gsk_gl_driver_import_dmabuf_texture (GskGLDriver *self, + GdkDmabufTexture *texture) +{ + GdkGLContext *context = self->command_queue->context; + int max_texture_size = self->command_queue->max_texture_size; + const GdkDmabuf *dmabuf; + guint texture_id; + int width, height; + GskGLProgram *program; + GskGLRenderTarget *render_target; + guint prev_fbo; + gboolean external; + GdkMemoryFormat format; + gboolean premultiply; + + gdk_gl_context_make_current (context); + + width = gdk_texture_get_width (GDK_TEXTURE (texture)); + height = gdk_texture_get_height (GDK_TEXTURE (texture)); + + if (width > max_texture_size || height > max_texture_size) + { + GDK_DISPLAY_DEBUG (gdk_gl_context_get_display (context), DMABUF, + "Can't import dmabuf bigger than MAX_TEXTURE_SIZE (%d)", + max_texture_size); + return 0; + } + + dmabuf = gdk_dmabuf_texture_get_dmabuf (texture); + format = gdk_texture_get_format (GDK_TEXTURE (texture)); + premultiply = gdk_memory_format_alpha (format) == GDK_MEMORY_ALPHA_STRAIGHT; + + texture_id = gdk_gl_context_import_dmabuf (context, + width, height, + dmabuf, + &external); + if (texture_id == 0) + return 0; + + if (!external && !premultiply) + return texture_id; + + gsk_gl_driver_autorelease_texture (self, texture_id); + + if (external) + program = self->external; + else + program = self->premultiply; + + if (!gsk_gl_driver_create_render_target (self, width, height, GL_RGBA8, &render_target)) + return texture_id; + + prev_fbo = gsk_gl_command_queue_bind_framebuffer (self->command_queue, render_target->framebuffer_id); + gsk_gl_command_queue_clear (self->command_queue, 0, &GRAPHENE_RECT_INIT (0, 0, width, height)); + + if (gsk_gl_command_queue_begin_draw (self->command_queue, program->program_info, width, height)) + { + set_projection_for_size (self, program, width, height); + set_viewport_for_size (self, program, width, height); + reset_modelview (self, program); + + if (external) + { + gsk_gl_program_set_uniform_texture (program, + UNIFORM_EXTERNAL_SOURCE, 0, + GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE0, texture_id); + + gsk_gl_program_set_uniform1i (program, UNIFORM_PREMULTIPLY, 0, premultiply); + } + else + { + gsk_gl_program_set_uniform_texture (program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, GL_TEXTURE0, texture_id); + } + + draw_rect (self->command_queue, 0, 0, width, height); + + gsk_gl_command_queue_end_draw (self->command_queue); + } + + gsk_gl_command_queue_bind_framebuffer (self->command_queue, prev_fbo); + + return release_render_target (self, render_target, FALSE, FALSE); +} + +#else + +static guint +gsk_gl_driver_import_dmabuf_texture (GskGLDriver *self, + GdkDmabufTexture *texture) +{ + return 0; +} + +#endif /* HAVE_DMABUF && HAVE_EGL */ + +/** + * gsk_gl_driver_load_texture: + * @self: a `GdkTexture` + * @texture: a `GdkTexture` + * @ensure_mipmap: Mipmaps for this texture must exist for downscaling + * + * Loads a `GdkTexture` by uploading the contents to the GPU when + * necessary. If @texture is a `GdkGLTexture`, it can be used without + * uploading contents to the GPU. + * + * If the texture has already been uploaded and not yet released + * from cache, this function returns that texture id without further + * work. + * + * If the texture has not been used for a number of frames, it will + * be removed from cache. + * + * There is no need to release the resulting texture identifier after + * using it. It will be released automatically. + * + * Returns: a texture identifier + */ +guint +gsk_gl_driver_load_texture (GskGLDriver *self, + GdkTexture *texture, + gboolean ensure_mipmap) +{ + GdkGLContext *context; + GdkMemoryTexture *downloaded_texture; + GskGLTexture *t; + guint texture_id; + int height; + int width; + gboolean can_mipmap = FALSE; + + g_return_val_if_fail (GSK_IS_GL_DRIVER (self), 0); + g_return_val_if_fail (GDK_IS_TEXTURE (texture), 0); + g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->command_queue), 0); + + context = self->command_queue->context; + + texture_id = 0; + downloaded_texture = NULL; + + t = gdk_texture_get_render_data (texture, self); + if (t && t->texture_id) + { + if (ensure_mipmap && t->can_mipmap && !t->has_mipmap) + { + glBindTexture (GL_TEXTURE_2D, t->texture_id); + glGenerateMipmap (GL_TEXTURE_2D); + t->has_mipmap = TRUE; + } + + if (!ensure_mipmap || t->has_mipmap) + return t->texture_id; + + gdk_texture_clear_render_data (texture); + } + + if (GDK_IS_DMABUF_TEXTURE (texture) && !ensure_mipmap) + { + texture_id = gsk_gl_driver_import_dmabuf_texture (self, GDK_DMABUF_TEXTURE (texture)); + } + else if (GDK_IS_GL_TEXTURE (texture)) + { + GdkGLTexture *gl_texture = (GdkGLTexture *) texture; + GdkGLContext *texture_context = gdk_gl_texture_get_context (gl_texture); + + if (gdk_gl_context_is_shared (context, texture_context) && + (!ensure_mipmap || gdk_gl_texture_has_mipmap (gl_texture)) && + gdk_memory_format_alpha (gdk_texture_get_format (texture)) != GDK_MEMORY_ALPHA_STRAIGHT) + { + /* A GL texture from the same GL context is a simple task... */ + return gdk_gl_texture_get_id (gl_texture); + } + } + + if (texture_id == 0) + { + downloaded_texture = gdk_memory_texture_from_texture (texture); + + /* The download_texture() call may have switched the GL context. Make sure + * the right context is at work again. + */ + gdk_gl_context_make_current (context); + + texture_id = gsk_gl_command_queue_upload_texture (self->command_queue, GDK_TEXTURE (downloaded_texture), ensure_mipmap, &can_mipmap); + } + + width = gdk_texture_get_width (texture); + height = gdk_texture_get_height (texture); + + t = gsk_gl_texture_new (texture_id, + width, height, + self->current_frame_id); + t->can_mipmap = can_mipmap; + if (ensure_mipmap) + { + g_assert (can_mipmap); + glBindTexture (GL_TEXTURE_2D, t->texture_id); + glGenerateMipmap (GL_TEXTURE_2D); + t->has_mipmap = TRUE; + } + + g_hash_table_insert (self->textures, GUINT_TO_POINTER (texture_id), t); + + if (gdk_texture_set_render_data (texture, self, t, gsk_gl_texture_destroyed)) + t->user = texture; + + gdk_gl_context_label_object_printf (context, GL_TEXTURE, t->texture_id, + "GdkTexture<%p> %d", texture, t->texture_id); + + g_clear_object (&downloaded_texture); + + return t->texture_id; +} + +/** + * gsk_gl_driver_create_texture: + * @self: a `GskGLDriver` + * @width: the width of the texture + * @height: the height of the texture + * @format: format for the texture + * + * Creates a new texture immediately that can be used by the caller + * to upload data, map to a framebuffer, or other uses which may + * modify the texture immediately. + * + * Typical examples for @format are GL_RGBA8, GL_RGBA16F or GL_RGBA32F. + * + * Use gsk_gl_driver_release_texture() to release this texture back into + * the pool so it may be reused later in the pipeline. + * + * Returns: a `GskGLTexture` which can be returned to the pool with + * gsk_gl_driver_release_texture(). + */ +GskGLTexture * +gsk_gl_driver_create_texture (GskGLDriver *self, + float width, + float height, + int format) +{ + GskGLTexture *texture; + guint texture_id; + + g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL); + + texture_id = gsk_gl_command_queue_create_texture (self->command_queue, + width, height, + format); + texture = gsk_gl_texture_new (texture_id, + width, height, + self->current_frame_id); + g_hash_table_insert (self->textures, + GUINT_TO_POINTER (texture->texture_id), + texture); + + return texture; +} + +/** + * gsk_gl_driver_release_texture: + * @self: a `GskGLDriver` + * @texture: a `GskGLTexture` + * + * Releases @texture back into the pool so that it can be used later + * in the command stream by future batches. This helps reduce VRAM + * usage on the GPU. + * + * When the frame has completed, pooled textures will be released + * to free additional VRAM back to the system. + */ +void +gsk_gl_driver_release_texture (GskGLDriver *self, + GskGLTexture *texture) +{ + guint texture_id; + + g_assert (GSK_IS_GL_DRIVER (self)); + g_assert (texture != NULL); + + texture_id = texture->texture_id; + texture->texture_id = 0; + gsk_gl_texture_free (texture); + + if (texture_id > 0) + remove_texture_key_for_id (self, texture_id); + + g_hash_table_steal (self->textures, GUINT_TO_POINTER (texture_id)); + gsk_gl_driver_autorelease_texture (self, texture_id); +} + +/** + * gsk_gl_driver_create_render_target: + * @self: a `GskGLDriver` + * @width: the width for the render target + * @height: the height for the render target + * @format: the format to use + * @out_render_target: (out): a location for the render target + * + * Creates a new render target which contains a framebuffer and a texture + * bound to that framebuffer of the size @width x @height and using the + * appropriate filters. + * + * Typical examples for @format are GK_RGBA8, GL_RGBA16F or GL_RGBA32F. + * + * Use gsk_gl_driver_release_render_target() when you are finished with + * the render target to release it. You may steal the texture from the + * render target when releasing it. + * + * Returns: %TRUE if successful; otherwise %FALSE and @out_fbo_id and + * @out_texture_id are undefined. + */ +gboolean +gsk_gl_driver_create_render_target (GskGLDriver *self, + int width, + int height, + int format, + GskGLRenderTarget **out_render_target) +{ + guint framebuffer_id; + guint texture_id; + + g_return_val_if_fail (GSK_IS_GL_DRIVER (self), FALSE); + g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->command_queue), FALSE); + g_return_val_if_fail (out_render_target != NULL, FALSE); + +#if 0 + if (self->render_targets->len > 0) + { + for (guint i = self->render_targets->len; i > 0; i--) + { + GskGLRenderTarget *render_target = g_ptr_array_index (self->render_targets, i-1); + + if (render_target->width == width && + render_target->height == height) + { + *out_render_target = g_ptr_array_steal_index_fast (self->render_targets, i-1); + return TRUE; + } + } + } +#endif + + if (gsk_gl_command_queue_create_render_target (self->command_queue, + width, height, + format, + &framebuffer_id, &texture_id)) + { + GskGLRenderTarget *render_target; + + render_target = g_new0 (GskGLRenderTarget, 1); + render_target->format = format; + render_target->width = width; + render_target->height = height; + render_target->framebuffer_id = framebuffer_id; + render_target->texture_id = texture_id; + + *out_render_target = render_target; + + return TRUE; + } + + *out_render_target = NULL; + + return FALSE; +} + +static unsigned int +release_render_target (GskGLDriver *self, + GskGLRenderTarget *render_target, + gboolean release_texture, + gboolean cache_texture) +{ + guint texture_id; + + g_return_val_if_fail (GSK_IS_GL_DRIVER (self), 0); + g_return_val_if_fail (render_target != NULL, 0); + + if (release_texture) + { + texture_id = 0; + g_ptr_array_add (self->render_targets, render_target); + } + else + { + texture_id = render_target->texture_id; + + if (cache_texture) + { + GskGLTexture *texture; + + texture = gsk_gl_texture_new (render_target->texture_id, + render_target->width, + render_target->height, + self->current_frame_id); + g_hash_table_insert (self->textures, + GUINT_TO_POINTER (texture_id), + g_steal_pointer (&texture)); + } + + gsk_gl_driver_autorelease_framebuffer (self, render_target->framebuffer_id); + g_free (render_target); + + } + + return texture_id; +} + +/** + * gsk_gl_driver_release_render_target: + * @self: a `GskGLDriver` + * @render_target: a `GskGLRenderTarget` created with + * gsk_gl_driver_create_render_target(). + * @release_texture: if the texture should also be released + * + * Releases a render target that was previously created. An attempt may + * be made to cache the render target so that future creations of render + * targets are performed faster. + * + * If @release_texture is %FALSE, the backing texture id is returned and + * the framebuffer is released. Otherwise, both the texture and framebuffer + * are released or cached until the end of the frame. + * + * This may be called when building the render job as the texture or + * framebuffer will not be removed immediately. + * + * Returns: a texture id if @release_texture is %FALSE, otherwise zero. + */ +guint +gsk_gl_driver_release_render_target (GskGLDriver *self, + GskGLRenderTarget *render_target, + gboolean release_texture) +{ + return release_render_target (self, render_target, release_texture, TRUE); +} + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + +/** + * gsk_gl_driver_lookup_shader: + * @self: a `GskGLDriver` + * @shader: the shader to lookup or load + * @error: a location for a `GError` + * + * Attempts to load @shader from the shader cache. + * + * If it has not been loaded, then it will compile the shader on demand. + * + * Returns: (nullable) (transfer none): a `GskGLShader` + */ +GskGLProgram * +gsk_gl_driver_lookup_shader (GskGLDriver *self, + GskGLShader *shader, + GError **error) +{ + GskGLProgram *program; + + g_return_val_if_fail (self != NULL, NULL); + g_return_val_if_fail (shader != NULL, NULL); + + program = g_hash_table_lookup (self->shader_cache, shader); + + if (program == NULL) + { + const GskGLUniform *uniforms; + GskGLCompiler *compiler; + GBytes *suffix; + int n_required_textures; + int n_uniforms; + + uniforms = gsk_gl_shader_get_uniforms (shader, &n_uniforms); + if (n_uniforms > GSK_GL_PROGRAM_MAX_CUSTOM_ARGS) + { + g_set_error (error, + GDK_GL_ERROR, + GDK_GL_ERROR_UNSUPPORTED_FORMAT, + "Tried to use %d uniforms, while only %d is supported", + n_uniforms, + GSK_GL_PROGRAM_MAX_CUSTOM_ARGS); + return NULL; + } + + n_required_textures = gsk_gl_shader_get_n_textures (shader); + if (n_required_textures > GSK_GL_PROGRAM_MAX_CUSTOM_TEXTURES) + { + g_set_error (error, + GDK_GL_ERROR, + GDK_GL_ERROR_UNSUPPORTED_FORMAT, + "Tried to use %d textures, while only %d is supported", + n_required_textures, + GSK_GL_PROGRAM_MAX_CUSTOM_TEXTURES); + return NULL; + } + + compiler = gsk_gl_compiler_new (self, FALSE); + suffix = gsk_gl_shader_get_source (shader); + + gsk_gl_compiler_set_preamble_from_resource (compiler, + GSK_GL_COMPILER_ALL, + "/org/gtk/libgsk/gl/preamble.glsl"); + gsk_gl_compiler_set_preamble_from_resource (compiler, + GSK_GL_COMPILER_VERTEX, + "/org/gtk/libgsk/gl/preamble.vs.glsl"); + gsk_gl_compiler_set_preamble_from_resource (compiler, + GSK_GL_COMPILER_FRAGMENT, + "/org/gtk/libgsk/gl/preamble.fs.glsl"); + gsk_gl_compiler_set_source_from_resource (compiler, + GSK_GL_COMPILER_ALL, + "/org/gtk/libgsk/gl/custom.glsl"); + gsk_gl_compiler_set_suffix (compiler, GSK_GL_COMPILER_FRAGMENT, suffix); + + /* Setup attributes that are provided via VBO */ + gsk_gl_compiler_bind_attribute (compiler, "aPosition", 0); + gsk_gl_compiler_bind_attribute (compiler, "aUv", 1); + gsk_gl_compiler_bind_attribute (compiler, "aColor", 2); + gsk_gl_compiler_bind_attribute (compiler, "aColor2", 3); + + if ((program = gsk_gl_compiler_compile (compiler, NULL, "", error))) + { + gboolean have_alpha; + + gsk_gl_program_add_uniform (program, "u_source", UNIFORM_SHARED_SOURCE); + gsk_gl_program_add_uniform (program, "u_clip_rect", UNIFORM_SHARED_CLIP_RECT); + gsk_gl_program_add_uniform (program, "u_viewport", UNIFORM_SHARED_VIEWPORT); + gsk_gl_program_add_uniform (program, "u_projection", UNIFORM_SHARED_PROJECTION); + gsk_gl_program_add_uniform (program, "u_modelview", UNIFORM_SHARED_MODELVIEW); + have_alpha = gsk_gl_program_add_uniform (program, "u_alpha", UNIFORM_SHARED_ALPHA); + + gsk_gl_program_add_uniform (program, "u_size", UNIFORM_CUSTOM_SIZE); + gsk_gl_program_add_uniform (program, "u_texture1", UNIFORM_CUSTOM_TEXTURE1); + gsk_gl_program_add_uniform (program, "u_texture2", UNIFORM_CUSTOM_TEXTURE2); + gsk_gl_program_add_uniform (program, "u_texture3", UNIFORM_CUSTOM_TEXTURE3); + gsk_gl_program_add_uniform (program, "u_texture4", UNIFORM_CUSTOM_TEXTURE4); + + /* Custom arguments (max is 8) */ + for (guint i = 0; i < n_uniforms; i++) + gsk_gl_program_add_uniform (program, uniforms[i].name, UNIFORM_CUSTOM_ARG0+i); + + gsk_gl_program_uniforms_added (program, TRUE); + + if (have_alpha) + gsk_gl_program_set_uniform1f (program, UNIFORM_SHARED_ALPHA, 0, 1.0f); + + g_hash_table_insert (self->shader_cache, shader, program); + g_object_weak_ref (G_OBJECT (shader), + gsk_gl_driver_shader_weak_cb, + self); + } + + g_object_unref (compiler); + } + + return program; +} + +G_GNUC_END_IGNORE_DEPRECATIONS + +#if 0 +void +gsk_gl_driver_save_texture_to_png (GskGLDriver *driver, + int texture_id, + int width, + int height, + const char *filename) +{ + GdkGLTextureBuilder *builder; + GdkTexture *texture; + + builder = gdk_gl_texture_builder_new (); + gdk_gl_texture_builder_set_context (builder, gsk_gl_driver_get_context (driver)); + gdk_gl_texture_builder_set_id (builder, texture_id); + gdk_gl_texture_builder_set_width (builder, width); + gdk_gl_texture_builder_set_height (builder, height); + + texture = gdk_gl_texture_builder_build (builder, NULL, NULL); + gdk_texture_save_to_png (texture, filename); + + g_object_unref (texture); + g_object_unref (builder); +} + +void +gsk_gl_driver_save_atlases_to_png (GskGLDriver *self, + const char *directory) +{ + GPtrArray *atlases; + + g_return_if_fail (GSK_IS_GL_DRIVER (self)); + + if (directory == NULL) + directory = "."; + +#define copy_atlases(dst, library) \ + g_ptr_array_extend(dst, GSK_GL_TEXTURE_LIBRARY(library)->atlases, NULL, NULL) + atlases = g_ptr_array_new (); + copy_atlases (atlases, self->glyphs_library); + copy_atlases (atlases, self->icons_library); +#undef copy_atlases + + for (guint i = 0; i < atlases->len; i++) + { + GskGLTextureAtlas *atlas = g_ptr_array_index (atlases, i); + char *filename = g_strdup_printf ("%s%sframe-%d-atlas-%d.png", + directory, + G_DIR_SEPARATOR_S, + (int)self->current_frame_id, + atlas->texture_id); + gsk_gl_driver_save_texture_to_png (self, atlas->texture_id, atlas->width, atlas->height, filename); + g_free (filename); + } + + g_ptr_array_unref (atlases); +} +#endif + +GskGLCommandQueue * +gsk_gl_driver_create_command_queue (GskGLDriver *self, + GdkGLContext *context) +{ + g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL); + g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL); + + return gsk_gl_command_queue_new (context, self->shared_command_queue->uniforms); +} + +void +gsk_gl_driver_add_texture_slices (GskGLDriver *self, + GdkTexture *texture, + gboolean ensure_mipmap, + GskGLTextureSlice **out_slices, + guint *out_n_slices) +{ + int max_slice_size; + GskGLTextureSlice *slices; + GskGLTexture *t; + guint n_slices; + guint cols; + guint rows; + int tex_width; + int tex_height; + int x = 0, y = 0; + GdkMemoryTexture *memtex; + GdkMemoryFormat format; + gsize bpp; + int extra_pixels; + GdkMemoryTexture *memtex1 = NULL; + GdkMemoryTexture *memtex2 = NULL; + GdkMemoryTexture *memtex3 = NULL; + GdkMemoryTexture *memtex4 = NULL; + gboolean can_mipmap = TRUE, slice_can_mipmap; + + g_assert (GSK_IS_GL_DRIVER (self)); + g_assert (GDK_IS_TEXTURE (texture)); + g_assert (out_slices != NULL); + g_assert (out_n_slices != NULL); + + max_slice_size = self->command_queue->max_texture_size / 2; + tex_width = texture->width; + tex_height = texture->height; + + cols = (texture->width / max_slice_size) + 1; + rows = (texture->height / max_slice_size) + 1; + + n_slices = cols * rows; + + t = gdk_texture_get_render_data (texture, self); + + if (t) + { + if (t->n_slices == n_slices && + (t->has_mipmap || !ensure_mipmap)) + { + *out_slices = t->slices; + *out_n_slices = t->n_slices; + return; + } + + gdk_texture_clear_render_data (texture); + } + + slices = g_new0 (GskGLTextureSlice, n_slices); + memtex = gdk_memory_texture_from_texture (texture); + + if (ensure_mipmap) + { + guchar *data1, *data2, *data3, *data4; + guchar *top_row, *bot_row, *left_row, *right_row; + GdkTexture *tmp; + int w; + GBytes *bytes; + + /* We need some extra pixels around our tiles, in order for + * GL to properly determine the right level of detail to use. + * This number should probably depend on the scale, but for + * now we just hardcode it. + * + * We create some auxiliary textures to hold the extra pixels: + * + * +---------------------+ + * | memtex1 | + * *---+-------------+---+ + * | | | | + * memtex2| memtex |memtex3 + * | | | | + * *---+-------------+---+ + * | memtex4 | | + * +---------------------+ + */ + + extra_pixels = 15; + + top_row = g_malloc (4 * tex_width); + tmp = gdk_memory_texture_new_subtexture (memtex, 0, 0, tex_width, 1); + gdk_texture_download (tmp, top_row, 4 * tex_width); + g_object_unref (tmp); + + bot_row = g_malloc (4 * tex_width); + tmp = gdk_memory_texture_new_subtexture (memtex, 0, tex_height - 1, tex_width, 1); + gdk_texture_download (tmp, bot_row, 4 * tex_width); + g_object_unref (tmp); + + left_row = g_malloc (4 * tex_height); + tmp = gdk_memory_texture_new_subtexture (memtex, 0, 0, 1, tex_height); + gdk_texture_download (tmp, left_row, 4); + g_object_unref (tmp); + + right_row = g_malloc (4 * tex_height); + tmp = gdk_memory_texture_new_subtexture (memtex, tex_width - 1, 0, 1, tex_height); + gdk_texture_download (tmp, right_row, 4); + g_object_unref (tmp); + + w = tex_width + 2 * extra_pixels; + + data1 = g_malloc (4 * w * extra_pixels); + data2 = g_malloc (4 * extra_pixels * tex_height); + data3 = g_malloc (4 * extra_pixels * tex_height); + data4 = g_malloc (4 * w * extra_pixels); + + format = gdk_texture_get_format (GDK_TEXTURE (memtex)); + bpp = gdk_memory_format_get_plane_block_bytes (format, 0); + + for (int i = 0; i < w; i++) + { + int ii = CLAMP (i, extra_pixels, (tex_width - 1) + extra_pixels) - extra_pixels; + + for (int j = 0; j < extra_pixels; j++) + { + for (int k = 0; k < bpp; k++) + { + data1[(j * w + i) * 4 + k] = top_row[ii * 4 + k]; + data4[(j * w + i) * 4 + k] = bot_row[ii * 4 + k]; + } + } + } + + for (int i = 0; i < extra_pixels; i++) + { + for (int j = 0; j < tex_height; j++) + { + for (int k = 0; k < bpp; k++) + { + data2[(j * extra_pixels + i) * 4 + k] = left_row[j * 4 + k]; + data3[(j * extra_pixels + i) * 4 + k] = right_row[j * 4 + k]; + } + } + } + + g_free (top_row); + g_free (bot_row); + g_free (left_row); + g_free (right_row); + + bytes = g_bytes_new_take (data1, 4 * w * extra_pixels); + memtex1 = GDK_MEMORY_TEXTURE (gdk_memory_texture_new (w, extra_pixels, format, bytes, 4 * w)); + g_bytes_unref (bytes); + + bytes = g_bytes_new_take (data2, 4 * extra_pixels * tex_height); + memtex2 = GDK_MEMORY_TEXTURE (gdk_memory_texture_new (extra_pixels, tex_height, format, bytes, 4 * extra_pixels)); + g_bytes_unref (bytes); + + bytes = g_bytes_new_take (data3, 4 * extra_pixels * tex_height); + memtex3 = GDK_MEMORY_TEXTURE (gdk_memory_texture_new (extra_pixels, tex_height, format, bytes, 4 * extra_pixels)); + g_bytes_unref (bytes); + + bytes = g_bytes_new_take (data4, 4 * w * extra_pixels); + memtex4 = GDK_MEMORY_TEXTURE (gdk_memory_texture_new (w, extra_pixels, format, bytes, 4 * w)); + g_bytes_unref (bytes); + } + else + extra_pixels = 0; + + x = 0; + for (guint col = 0; col < cols; col++) + { + int slice_width = col + 1 < cols ? tex_width / cols : tex_width - x; + + y = 0; + for (guint row = 0; row < rows; row++) + { + int slice_height = row + 1 < rows ? tex_height / rows : tex_height - y; + int slice_index = (col * rows) + row; + guint texture_id; + + if (ensure_mipmap) + { + GskGLTextureChunk chunks[5]; + unsigned int n_chunks = 0; + + if (row == 0) + { + chunks[n_chunks].texture = gdk_memory_texture_new_subtexture (memtex1, + x, 0, + slice_width + 2 * extra_pixels, extra_pixels); + chunks[n_chunks].x = 0; + chunks[n_chunks].y = 0; + n_chunks++; + } + + if (row == rows - 1) + { + chunks[n_chunks].texture = gdk_memory_texture_new_subtexture (memtex4, + x, 0, + slice_width + 2 * extra_pixels, extra_pixels); + chunks[n_chunks].x = 0; + chunks[n_chunks].y = slice_height + extra_pixels; + n_chunks++; + } + + if (col == 0) + { + int yy = y - extra_pixels; + int hh = slice_height + 2 * extra_pixels; + int y0 = 0; + + if (row == 0) + { + yy = 0; + y0 = extra_pixels; + hh -= extra_pixels; + } + if (row == rows - 1) + { + hh -= extra_pixels; + } + + chunks[n_chunks].texture = gdk_memory_texture_new_subtexture (memtex2, + 0, yy, extra_pixels, hh); + chunks[n_chunks].x = 0; + chunks[n_chunks].y = y0; + n_chunks++; + } + + if (col == cols - 1) + { + int yy = y - extra_pixels; + int hh = slice_height + 2 * extra_pixels; + int y0 = 0; + + if (row == 0) + { + yy = 0; + y0 = extra_pixels; + hh -= extra_pixels; + } + if (row == rows - 1) + { + hh -= extra_pixels; + } + + chunks[n_chunks].texture = gdk_memory_texture_new_subtexture (memtex3, + 0, yy, extra_pixels, hh); + chunks[n_chunks].x = slice_width + extra_pixels; + chunks[n_chunks].y = y0; + n_chunks++; + } + + { + int xx = x - extra_pixels; + int yy = y - extra_pixels; + int ww = slice_width + 2 * extra_pixels; + int hh = slice_height + 2 * extra_pixels; + int x0 = 0; + int y0 = 0; + if (col == 0) + { + xx = 0; + ww -= extra_pixels; + x0 = extra_pixels; + } + if (col == cols - 1) + { + ww -= extra_pixels; + } + if (row == 0) + { + yy = 0; + hh -= extra_pixels; + y0 = extra_pixels; + } + if (row == rows - 1) + { + hh -= extra_pixels; + } + + chunks[n_chunks].texture = gdk_memory_texture_new_subtexture (memtex, xx, yy, ww, hh); + chunks[n_chunks].x = x0; + chunks[n_chunks].y = y0; + n_chunks++; + } + + texture_id = gsk_gl_command_queue_upload_texture_chunks (self->command_queue, TRUE, n_chunks, chunks, &slice_can_mipmap); + + glBindTexture (GL_TEXTURE_2D, texture_id); + glGenerateMipmap (GL_TEXTURE_2D); + + for (unsigned int i = 0; i < n_chunks; i++) + g_object_unref (chunks[i].texture); + } + else + { + GdkTexture *subtex; + + 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, FALSE, &slice_can_mipmap); + g_object_unref (subtex); + } + + can_mipmap &= slice_can_mipmap; + + slices[slice_index].rect.x = x; + slices[slice_index].rect.y = y; + slices[slice_index].rect.width = slice_width; + slices[slice_index].rect.height = slice_height; + slices[slice_index].texture_id = texture_id; + slices[slice_index].area.x = extra_pixels / (float) (slice_width + 2 * extra_pixels); + slices[slice_index].area.y = extra_pixels / (float) (slice_height + 2 * extra_pixels); + slices[slice_index].area.x2 = (extra_pixels + slice_width) / (float) (slice_width + 2 * extra_pixels); + slices[slice_index].area.y2 = (extra_pixels + slice_height) / (float) (slice_height + 2 * extra_pixels); + + y += slice_height; + } + + x += slice_width; + } + + g_object_unref (memtex); + g_clear_object (&memtex1); + g_clear_object (&memtex2); + g_clear_object (&memtex3); + g_clear_object (&memtex4); + + /* Allocate one Texture for the entire thing. */ + t = gsk_gl_texture_new (0, + tex_width, tex_height, + self->current_frame_id); + t->can_mipmap = can_mipmap; + t->has_mipmap = ensure_mipmap; + + /* Use gsk_gl_texture_free() as destroy notify here since we are + * not inserting this GskGLTexture into self->textures! + */ + gdk_texture_set_render_data (texture, self, t, + (GDestroyNotify)gsk_gl_texture_free); + + t->slices = *out_slices = slices; + t->n_slices = *out_n_slices = n_slices; +} + +GskGLTexture * +gsk_gl_driver_mark_texture_permanent (GskGLDriver *self, + guint texture_id) +{ + GskGLTexture *t; + + g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL); + g_return_val_if_fail (texture_id > 0, NULL); + + if ((t = g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id)))) + t->permanent = TRUE; + + return t; +} + +void +gsk_gl_driver_release_texture_by_id (GskGLDriver *self, + guint texture_id) +{ + GskGLTexture *texture; + + g_return_if_fail (GSK_IS_GL_DRIVER (self)); + g_return_if_fail (texture_id > 0); + + remove_texture_key_for_id (self, texture_id); + + if ((texture = g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id)))) + gsk_gl_driver_release_texture (self, texture); +} + +typedef struct _GskGLTextureState +{ + GdkGLContext *context; + GLuint texture_id; + GLsync sync; +} GskGLTextureState; + +static void +create_texture_from_texture_destroy (gpointer data) +{ + GskGLTextureState *state = data; + + g_assert (state != NULL); + g_assert (GDK_IS_GL_CONTEXT (state->context)); + + gdk_gl_context_make_current (state->context); + glDeleteTextures (1, &state->texture_id); + if (state->sync) + glDeleteSync (state->sync); + g_clear_object (&state->context); + g_free (state); +} + +GdkTexture * +gsk_gl_driver_create_gdk_texture (GskGLDriver *self, + guint texture_id, + GdkMemoryFormat format) +{ + GskGLTextureState *state; + GdkGLTextureBuilder *builder; + GskGLTexture *texture; + GdkTexture *result; + + g_return_val_if_fail (GSK_IS_GL_DRIVER (self), NULL); + g_return_val_if_fail (self->command_queue != NULL, NULL); + g_return_val_if_fail (GDK_IS_GL_CONTEXT (self->command_queue->context), NULL); + g_return_val_if_fail (texture_id > 0, NULL); + g_return_val_if_fail (!g_hash_table_contains (self->texture_id_to_key, GUINT_TO_POINTER (texture_id)), NULL); + + /* We must be tracking this texture_id already to use it */ + if (!(texture = g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id)))) + g_return_val_if_reached (NULL); + + state = g_new0 (GskGLTextureState, 1); + state->texture_id = texture_id; + state->context = g_object_ref (self->shared_command_queue->context); + if (gdk_gl_context_has_feature (state->context, GDK_GL_FEATURE_SYNC)) + state->sync = glFenceSync (GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + + g_hash_table_steal (self->textures, GUINT_TO_POINTER (texture_id)); + + builder = gdk_gl_texture_builder_new (); + gdk_gl_texture_builder_set_context (builder, self->shared_command_queue->context); + gdk_gl_texture_builder_set_id (builder, texture_id); + gdk_gl_texture_builder_set_format (builder, format); + gdk_gl_texture_builder_set_width (builder, texture->width); + gdk_gl_texture_builder_set_height (builder, texture->height); + gdk_gl_texture_builder_set_sync (builder, state->sync); + + result = gdk_gl_texture_builder_build (builder, + create_texture_from_texture_destroy, + state); + + texture->texture_id = 0; + gsk_gl_texture_free (texture); + g_object_unref (builder); + + return result; +} diff --git gsk/gl/gskgldriverprivate.h gsk/gl/gskgldriverprivate.h new file mode 100644 index 0000000000..9eb84b17a2 --- /dev/null +++ gsk/gl/gskgldriverprivate.h @@ -0,0 +1,261 @@ +/* gskgldriverprivate.h + * + * Copyright 2020 Christian Hergert + * + * This file 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 file 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 General Public License along + * with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include + +#include "gskgltypesprivate.h" +#include "gskgltextureprivate.h" + +G_BEGIN_DECLS + +enum { + UNIFORM_SHARED_ALPHA, + UNIFORM_SHARED_SOURCE, + UNIFORM_SHARED_CLIP_RECT, + UNIFORM_SHARED_VIEWPORT, + UNIFORM_SHARED_PROJECTION, + UNIFORM_SHARED_MODELVIEW, + + UNIFORM_SHARED_LAST +}; + +enum { + UNIFORM_CUSTOM_SIZE = UNIFORM_SHARED_LAST, + UNIFORM_CUSTOM_TEXTURE1, + UNIFORM_CUSTOM_TEXTURE2, + UNIFORM_CUSTOM_TEXTURE3, + UNIFORM_CUSTOM_TEXTURE4, + UNIFORM_CUSTOM_ARG0, + UNIFORM_CUSTOM_ARG1, + UNIFORM_CUSTOM_ARG2, + UNIFORM_CUSTOM_ARG3, + UNIFORM_CUSTOM_ARG4, + UNIFORM_CUSTOM_ARG5, + UNIFORM_CUSTOM_ARG6, + UNIFORM_CUSTOM_ARG7, + + UNIFORM_CUSTOM_LAST +}; + +typedef struct { + gconstpointer pointer; + float scale_x; + float scale_y; + int pointer_is_child; + graphene_rect_t parent_rect; /* Valid when pointer_is_child */ +} GskTextureKey; + +#define GSK_GL_NO_UNIFORMS CONCAT_EXPANDED(UNIFORM_INVALID_,__COUNTER__) +#define CONCAT_EXPANDED(a,b) CONCAT_EXPANDED2(a,b) +#define CONCAT_EXPANDED2(a,b) a##b +#define GSK_GL_ADD_UNIFORM(pos, KEY, name) UNIFORM_##KEY = UNIFORM_SHARED_LAST + pos, +#define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms) enum { uniforms }; +#define GSK_GL_DEFINE_PROGRAM_NO_CLIP(name, resource, uniforms) enum { uniforms }; +# include "gskglprograms.defs" +#undef GSK_GL_DEFINE_PROGRAM_NO_CLIP +#undef GSK_GL_DEFINE_PROGRAM +#undef GSK_GL_ADD_UNIFORM +#undef GSK_GL_NO_UNIFORMS +#undef CONCAT_EXPANDED +#undef CONCAT_EXPANDED2 + +#define GSK_TYPE_GL_DRIVER (gsk_gl_driver_get_type()) + +G_DECLARE_FINAL_TYPE (GskGLDriver, gsk_gl_driver, GSK, GL_DRIVER, GObject) + +struct _GskGLRenderTarget +{ + guint framebuffer_id; + guint texture_id; + int format; + int width; + int height; +}; + +struct _GskGLDriver +{ + GObject parent_instance; + + GskGLCommandQueue *shared_command_queue; + GskGLCommandQueue *command_queue; + + GskGLGlyphLibrary *glyphs_library; + GskGLIconLibrary *icons_library; + GskGLShadowLibrary *shadows_library; + + GArray *texture_pool; + GHashTable *textures; + GHashTable *key_to_texture_id; + GHashTable *texture_id_to_key; + + GHashTable *shader_cache; + + GArray *autorelease_framebuffers; + GPtrArray *render_targets; + +#define GSK_GL_NO_UNIFORMS +#define GSK_GL_ADD_UNIFORM(pos, KEY, name) +#define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms) \ + GskGLProgram *name ## _no_clip; \ + GskGLProgram *name ## _rect_clip; \ + GskGLProgram *name; +#define GSK_GL_DEFINE_PROGRAM_NO_CLIP(name, resource, uniforms) \ + GskGLProgram *name; +# include "gskglprograms.defs" +#undef GSK_GL_NO_UNIFORMS +#undef GSK_GL_ADD_UNIFORM +#undef GSK_GL_DEFINE_PROGRAM +#undef GSK_GL_DEFINE_PROGRAM_NO_CLIP + + gint64 current_frame_id; + + /* Used to reduce number of comparisons */ + guint stamps[UNIFORM_SHARED_LAST]; + + guint debug : 1; + guint in_frame : 1; +}; + +GskGLDriver * gsk_gl_driver_for_display (GdkDisplay *display, + gboolean debug_shaders, + GError **error); +GskGLCommandQueue * gsk_gl_driver_create_command_queue (GskGLDriver *self, + GdkGLContext *context); +GdkGLContext * gsk_gl_driver_get_context (GskGLDriver *self); +gboolean gsk_gl_driver_create_render_target (GskGLDriver *self, + int width, + int height, + int format, + GskGLRenderTarget **render_target); +guint gsk_gl_driver_release_render_target (GskGLDriver *self, + GskGLRenderTarget *render_target, + gboolean release_texture); +void gsk_gl_driver_begin_frame (GskGLDriver *self, + GskGLCommandQueue *command_queue); +void gsk_gl_driver_end_frame (GskGLDriver *self); +void gsk_gl_driver_after_frame (GskGLDriver *self); +GdkTexture * gsk_gl_driver_create_gdk_texture (GskGLDriver *self, + guint texture_id, + GdkMemoryFormat format); +void gsk_gl_driver_cache_texture (GskGLDriver *self, + const GskTextureKey *key, + guint texture_id); +guint gsk_gl_driver_load_texture (GskGLDriver *self, + GdkTexture *texture, + gboolean ensure_mipmap); +GskGLTexture * gsk_gl_driver_create_texture (GskGLDriver *self, + float width, + float height, + int format); +void gsk_gl_driver_release_texture (GskGLDriver *self, + GskGLTexture *texture); +void gsk_gl_driver_release_texture_by_id (GskGLDriver *self, + guint texture_id); +GskGLTexture * gsk_gl_driver_mark_texture_permanent (GskGLDriver *self, + guint texture_id); +void gsk_gl_driver_add_texture_slices (GskGLDriver *self, + GdkTexture *texture, + gboolean ensure_mipmap, + GskGLTextureSlice **out_slices, + guint *out_n_slices); +G_GNUC_BEGIN_IGNORE_DEPRECATIONS +GskGLProgram * gsk_gl_driver_lookup_shader (GskGLDriver *self, + GskGLShader *shader, + GError **error); +G_GNUC_END_IGNORE_DEPRECATIONS + +#if 0 +void gsk_gl_driver_save_texture_to_png (GskGLDriver *self, + int texture_id, + int width, + int height, + const char *filename); +void gsk_gl_driver_save_atlases_to_png (GskGLDriver *self, + const char *filename); +#endif + +static inline GskGLTexture * +gsk_gl_driver_get_texture_by_id (GskGLDriver *self, + guint texture_id) +{ + return g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id)); +} + +/** + * gsk_gl_driver_lookup_texture: + * @self: a `GskGLDriver` + * @key: the key for the texture + * @has_mipmap: (out): Return location for whether the texture has a mipmap + * + * Looks up a texture in the texture cache by @key. + * + * If the texture could not be found, then zero is returned. + * + * Returns: a positive integer if the texture was found; otherwise 0. + */ +static inline guint +gsk_gl_driver_lookup_texture (GskGLDriver *self, + const GskTextureKey *key, + gboolean *has_mipmap) +{ + gpointer id; + + if (g_hash_table_lookup_extended (self->key_to_texture_id, key, NULL, &id)) + { + GskGLTexture *texture = g_hash_table_lookup (self->textures, id); + + if (texture != NULL) + texture->last_used_in_frame = self->current_frame_id; + + if (has_mipmap) + *has_mipmap = texture ? texture->has_mipmap : FALSE; + + return GPOINTER_TO_UINT (id); + } + + return 0; +} + +static inline void +gsk_gl_driver_slice_texture (GskGLDriver *self, + GdkTexture *texture, + gboolean ensure_mipmap, + GskGLTextureSlice **out_slices, + guint *out_n_slices) +{ + GskGLTexture *t; + + t = gdk_texture_get_render_data (texture, self); + + if (t && t->slices && + (t->has_mipmap || !ensure_mipmap)) + { + *out_slices = t->slices; + *out_n_slices = t->n_slices; + return; + } + + gsk_gl_driver_add_texture_slices (self, texture, ensure_mipmap, out_slices, out_n_slices); +} + +G_END_DECLS + diff --git gsk/gl/gskglglyphlibrary.c gsk/gl/gskglglyphlibrary.c new file mode 100644 index 0000000000..90963c9274 --- /dev/null +++ gsk/gl/gskglglyphlibrary.c @@ -0,0 +1,437 @@ +/* gskglglyphlibrary.c + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include +#include +#include + +#include "gskglcommandqueueprivate.h" +#include "gskgldriverprivate.h" +#include "gskglglyphlibraryprivate.h" + +#include "gskdebugprivate.h" + +#define MAX_GLYPH_SIZE 128 + +G_DEFINE_TYPE (GskGLGlyphLibrary, gsk_gl_glyph_library, GSK_TYPE_GL_TEXTURE_LIBRARY) + +GskGLGlyphLibrary * +gsk_gl_glyph_library_new (GskGLDriver *driver) +{ + g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL); + + return g_object_new (GSK_TYPE_GL_GLYPH_LIBRARY, + "driver", driver, + NULL); +} + +static guint +gsk_gl_glyph_key_hash (gconstpointer data) +{ + const GskGLGlyphKey *key = data; + + /* We do not store the hash within the key because GHashTable will already + * store the hash value for us and so this is called only a single time per + * cached item. This saves an extra 4 bytes per GskGLGlyphKey which means on + * 64-bit, we fit nicely within 2 pointers (the smallest allocation size + * for GSlice). + */ + + return GPOINTER_TO_UINT (key->font) ^ + key->glyph ^ + (key->xshift << 24) ^ + (key->yshift << 26) ^ + key->scale; +} + +static gboolean +gsk_gl_glyph_key_equal (gconstpointer v1, + gconstpointer v2) +{ + return memcmp (v1, v2, sizeof (GskGLGlyphKey)) == 0; +} + +static void +gsk_gl_glyph_key_free (gpointer data) +{ + GskGLGlyphKey *key = data; + + g_clear_object (&key->font); + g_free (key); +} + +static void +gsk_gl_glyph_value_free (gpointer data) +{ + g_free (data); +} + +static void +gsk_gl_glyph_library_clear_cache (GskGLTextureLibrary *library) +{ + GskGLGlyphLibrary *self = (GskGLGlyphLibrary *)library; + + g_assert (GSK_IS_GL_GLYPH_LIBRARY (self)); + + memset (self->front, 0, sizeof self->front); +} + +static void +gsk_gl_glyph_library_init_atlas (GskGLTextureLibrary *self, + GskGLTextureAtlas *atlas) +{ + gboolean packed G_GNUC_UNUSED; + int x, y; + guint gl_format; + guint gl_type; + guint8 pixel_data[4 * 3 * 3]; + + g_assert (GSK_IS_GL_GLYPH_LIBRARY (self)); + g_assert (atlas != NULL); + + /* Insert a single pixel at 0,0 for use in coloring */ + + gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (), + "Initializing Atlas"); + + packed = gsk_gl_texture_library_allocate (self, atlas, 3, 3, &x, &y); + g_assert (packed); + g_assert (x == 0 && y == 0); + + memset (pixel_data, 255, sizeof pixel_data); + + if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ())) + { + gl_format = GL_RGBA; + gl_type = GL_UNSIGNED_BYTE; + } + else + { + gl_format = GL_BGRA; + gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; + } + glBindTexture (GL_TEXTURE_2D, atlas->texture_id); + + glTexSubImage2D (GL_TEXTURE_2D, 0, + 0, 0, + 3, 3, + gl_format, gl_type, + pixel_data); + + gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ()); + + self->driver->command_queue->n_uploads++; +} + + +static void +gsk_gl_glyph_library_finalize (GObject *object) +{ + GskGLGlyphLibrary *self = (GskGLGlyphLibrary *)object; + + g_clear_pointer (&self->surface_data, g_free); + + G_OBJECT_CLASS (gsk_gl_glyph_library_parent_class)->finalize (object); +} + +static void +gsk_gl_glyph_library_class_init (GskGLGlyphLibraryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GskGLTextureLibraryClass *library_class = GSK_GL_TEXTURE_LIBRARY_CLASS (klass); + + object_class->finalize = gsk_gl_glyph_library_finalize; + + library_class->clear_cache = gsk_gl_glyph_library_clear_cache; + library_class->init_atlas = gsk_gl_glyph_library_init_atlas; +} + +static void +gsk_gl_glyph_library_init (GskGLGlyphLibrary *self) +{ + GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self; + + tl->max_entry_size = MAX_GLYPH_SIZE; + gsk_gl_texture_library_set_funcs (tl, + gsk_gl_glyph_key_hash, + gsk_gl_glyph_key_equal, + gsk_gl_glyph_key_free, + gsk_gl_glyph_value_free); +} + +static cairo_surface_t * +gsk_gl_glyph_library_create_surface (GskGLGlyphLibrary *self, + int stride, + int width, + int height, + int uwidth, + int uheight) +{ + cairo_surface_t *surface; + gsize n_bytes; + + g_assert (GSK_IS_GL_GLYPH_LIBRARY (self)); + g_assert (width > 0); + g_assert (height > 0); + + n_bytes = stride * height; + + if G_LIKELY (n_bytes > self->surface_data_len) + { + self->surface_data = g_realloc (self->surface_data, n_bytes); + self->surface_data_len = n_bytes; + } + + memset (self->surface_data, 0, n_bytes); + surface = cairo_image_surface_create_for_data (self->surface_data, + CAIRO_FORMAT_ARGB32, + width, height, stride); + cairo_surface_set_device_scale (surface, width / (double)uwidth, height / (double)uheight); + + return surface; +} + +static void +render_glyph (cairo_surface_t *surface, + const GskGLGlyphKey *key, + const GskGLGlyphValue *value) +{ + cairo_t *cr; + PangoGlyphString glyph_string; + PangoGlyphInfo glyph_info; + + g_assert (surface != NULL); + + cr = cairo_create (surface); + cairo_set_source_rgba (cr, 1, 1, 1, 1); + + glyph_info.glyph = key->glyph; + glyph_info.geometry.width = value->ink_rect.width * 1024; + glyph_info.geometry.x_offset = (0.25 * key->xshift - value->ink_rect.x) * 1024; + glyph_info.geometry.y_offset = (0.25 * key->yshift - value->ink_rect.y) * 1024; + + glyph_string.num_glyphs = 1; + glyph_string.glyphs = &glyph_info; + + pango_cairo_show_glyph_string (cr, key->font, &glyph_string); + cairo_destroy (cr); + + cairo_surface_flush (surface); +} + +static void +gsk_gl_glyph_library_upload_glyph (GskGLGlyphLibrary *self, + const GskGLGlyphKey *key, + const GskGLGlyphValue *value, + int packed_x, + int packed_y, + int width, + int height, + int uwidth, + int uheight) +{ + GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self; + G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME; + cairo_surface_t *surface; + guchar *pixel_data; + guchar *free_data = NULL; + guint gl_format; + guint gl_type; + guint texture_id; + gsize stride; + + g_assert (GSK_IS_GL_GLYPH_LIBRARY (self)); + g_assert (key != NULL); + g_assert (value != NULL); + + stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width); + + gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (), + "Uploading glyph %d", + key->glyph); + + surface = gsk_gl_glyph_library_create_surface (self, stride, width, height, uwidth, uheight); + render_glyph (surface, key, value); + + texture_id = GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (value); + + g_assert (texture_id > 0); + + if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ())) + { + pixel_data = free_data = g_malloc (width * height * 4); + gdk_memory_convert (pixel_data, + &GDK_MEMORY_LAYOUT_SIMPLE (GDK_MEMORY_R8G8B8A8_PREMULTIPLIED, width, height, width * 4), + GDK_COLOR_STATE_SRGB, + cairo_image_surface_get_data (surface), + &GDK_MEMORY_LAYOUT_SIMPLE (GDK_MEMORY_DEFAULT, width, height, stride), + GDK_COLOR_STATE_SRGB); + stride = width * 4; + gl_format = GL_RGBA; + gl_type = GL_UNSIGNED_BYTE; + } + else + { + pixel_data = cairo_image_surface_get_data (surface); + gl_format = GL_BGRA; + gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; + } + + glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / 4); + glBindTexture (GL_TEXTURE_2D, texture_id); + + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x + 1, packed_y + 1, + width, height, + gl_format, gl_type, + pixel_data); + + /* Padding top */ + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x + 1, packed_y, + width, 1, + gl_format, gl_type, + pixel_data); + /* Padding left */ + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x, packed_y + 1, + 1, height, + gl_format, gl_type, + pixel_data); + /* Padding top left */ + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x, packed_y, + 1, 1, + gl_format, gl_type, + pixel_data); + /* Padding right */ + glPixelStorei (GL_UNPACK_ROW_LENGTH, width); + glPixelStorei (GL_UNPACK_SKIP_PIXELS, width - 1); + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x + width + 1, packed_y + 1, + 1, height, + gl_format, gl_type, + pixel_data); + /* Padding top right */ + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x + width + 1, packed_y, + 1, 1, + gl_format, gl_type, + pixel_data); + /* Padding bottom */ + glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei (GL_UNPACK_SKIP_ROWS, height - 1); + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x + 1, packed_y + 1 + height, + width, 1, + gl_format, gl_type, + pixel_data); + /* Padding bottom left */ + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x, packed_y + 1 + height, + 1, 1, + gl_format, gl_type, + pixel_data); + /* Padding bottom right */ + glPixelStorei (GL_UNPACK_ROW_LENGTH, width); + glPixelStorei (GL_UNPACK_SKIP_PIXELS, width - 1); + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x + 1 + width, packed_y + 1 + height, + 1, 1, + gl_format, gl_type, + pixel_data); + /* Reset this */ + glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei (GL_UNPACK_SKIP_ROWS, 0); + + cairo_surface_destroy (surface); + g_free (free_data); + + gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ()); + + tl->driver->command_queue->n_uploads++; + + if (gdk_profiler_is_running ()) + { + char message[64]; + g_snprintf (message, sizeof message, "Size %dx%d", width, height); + gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Upload glyph", message); + } +} + +gboolean +gsk_gl_glyph_library_add (GskGLGlyphLibrary *self, + GskGLGlyphKey *key, + const GskGLGlyphValue **out_value) +{ + GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self; + PangoRectangle ink_rect; + GskGLGlyphValue *value; + int width; + int height; + guint packed_x; + guint packed_y; + + g_assert (GSK_IS_GL_GLYPH_LIBRARY (self)); + g_assert (key != NULL); + g_assert (out_value != NULL); + + pango_font_get_glyph_extents (key->font, key->glyph, &ink_rect, NULL); + pango_extents_to_pixels (&ink_rect, NULL); + + ink_rect.x -= 1; + ink_rect.width += 2; + ink_rect.y -= 1; + ink_rect.height += 2; + + width = (int) ceil (ink_rect.width * key->scale / 1024.0); + height = (int) ceil (ink_rect.height * key->scale / 1024.0); + + GSK_DEBUG (CACHE, "font %p glyph %u: %u x %u pixels", key->font, key->glyph, width, height); + + value = gsk_gl_texture_library_pack (tl, + key, + sizeof *value, + width, + height, + 1, + &packed_x, &packed_y); + + memcpy (&value->ink_rect, &ink_rect, sizeof ink_rect); + + if (key->scale > 0 && width > 0 && height > 0) + gsk_gl_glyph_library_upload_glyph (self, + key, + value, + packed_x, + packed_y, + width, + height, + ink_rect.width, + ink_rect.height); + + *out_value = value; + + return GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (value) != 0; +} diff --git gsk/gl/gskglglyphlibraryprivate.h gsk/gl/gskglglyphlibraryprivate.h new file mode 100644 index 0000000000..b3022b4630 --- /dev/null +++ gsk/gl/gskglglyphlibraryprivate.h @@ -0,0 +1,103 @@ +/* gskglglyphlibraryprivate.h + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include + +#include "gskgltexturelibraryprivate.h" + +G_BEGIN_DECLS + +#define GSK_TYPE_GL_GLYPH_LIBRARY (gsk_gl_glyph_library_get_type()) + +typedef struct _GskGLGlyphKey +{ + PangoFont *font; + PangoGlyph glyph; + guint xshift : 2; + guint yshift : 2; + guint scale : 28; /* times 1024 */ +} GskGLGlyphKey; + +typedef struct _GskGLGlyphValue +{ + GskGLTextureAtlasEntry entry; + PangoRectangle ink_rect; +} GskGLGlyphValue; + +#if GLIB_SIZEOF_VOID_P == 8 +G_STATIC_ASSERT (sizeof (GskGLGlyphKey) == 16); +#elif GLIB_SIZEOF_VOID_P == 4 +G_STATIC_ASSERT (sizeof (GskGLGlyphKey) == 12); +#endif + +G_DECLARE_FINAL_TYPE (GskGLGlyphLibrary, gsk_gl_glyph_library, GSK, GL_GLYPH_LIBRARY, GskGLTextureLibrary) + +struct _GskGLGlyphLibrary +{ + GskGLTextureLibrary parent_instance; + guint8 *surface_data; + gsize surface_data_len; + struct { + GskGLGlyphKey key; + const GskGLGlyphValue *value; + } front[256]; +}; + +GskGLGlyphLibrary *gsk_gl_glyph_library_new (GskGLDriver *driver); +gboolean gsk_gl_glyph_library_add (GskGLGlyphLibrary *self, + GskGLGlyphKey *key, + const GskGLGlyphValue **out_value); + +static inline guint +gsk_gl_glyph_library_lookup_or_add (GskGLGlyphLibrary *self, + const GskGLGlyphKey *key, + const GskGLGlyphValue **out_value) +{ + GskGLTextureAtlasEntry *entry; + guint front_index = ((key->glyph << 2) | key->xshift) & 0xFF; + + if (memcmp (key, &self->front[front_index], sizeof *key) == 0) + { + *out_value = self->front[front_index].value; + } + else if (gsk_gl_texture_library_lookup ((GskGLTextureLibrary *)self, key, &entry)) + { + *out_value = (GskGLGlyphValue *)entry; + self->front[front_index].key = *key; + self->front[front_index].value = *out_value; + } + else + { + GskGLGlyphKey *k; + k = g_new (GskGLGlyphKey, 1); + memcpy (k, key, sizeof (GskGLGlyphKey)); + g_object_ref (k->font); + gsk_gl_glyph_library_add (self, k, out_value); + self->front[front_index].key = *key; + self->front[front_index].value = *out_value; + } + + return GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (*out_value); +} + +G_END_DECLS + diff --git gsk/gl/gskgliconlibrary.c gsk/gl/gskgliconlibrary.c new file mode 100644 index 0000000000..63b25dea4b --- /dev/null +++ gsk/gl/gskgliconlibrary.c @@ -0,0 +1,212 @@ +/* gskgliconlibrary.c + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "gskglcommandqueueprivate.h" +#include "gskgldriverprivate.h" +#include "gskgliconlibraryprivate.h" + +struct _GskGLIconLibrary +{ + GskGLTextureLibrary parent_instance; +}; + +G_DEFINE_TYPE (GskGLIconLibrary, gsk_gl_icon_library, GSK_TYPE_GL_TEXTURE_LIBRARY) + +GskGLIconLibrary * +gsk_gl_icon_library_new (GskGLDriver *driver) +{ + g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL); + + return g_object_new (GSK_TYPE_GL_ICON_LIBRARY, + "driver", driver, + NULL); +} + +static void +gsk_gl_icon_data_free (gpointer data) +{ + GskGLIconData *icon_data = data; + + g_clear_object (&icon_data->source_texture); + g_free (icon_data); +} + +static void +gsk_gl_icon_library_class_init (GskGLIconLibraryClass *klass) +{ +} + +static void +gsk_gl_icon_library_init (GskGLIconLibrary *self) +{ + GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self; + + tl->max_entry_size = 128; + gsk_gl_texture_library_set_funcs (tl, + NULL, NULL, NULL, + gsk_gl_icon_data_free); +} + +void +gsk_gl_icon_library_add (GskGLIconLibrary *self, + GdkTexture *key, + const GskGLIconData **out_value) +{ + GskGLTextureLibrary *tl = (GskGLTextureLibrary *)self; + G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME; + GskGLIconData *icon_data; + GdkTextureDownloader downloader; + guint8 *pixel_data; + guint gl_format; + guint gl_type; + guint packed_x; + guint packed_y; + int width; + int height; + guint texture_id; + + g_assert (GSK_IS_GL_ICON_LIBRARY (self)); + g_assert (GDK_IS_TEXTURE (key)); + g_assert (out_value != NULL); + + width = key->width; + height = key->height; + + icon_data = gsk_gl_texture_library_pack (tl, + key, + sizeof (GskGLIconData), + width, height, 1, + &packed_x, &packed_y); + icon_data->source_texture = g_object_ref (key); + + /* actually upload the texture */ + gdk_texture_downloader_init (&downloader, key); + gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (), + "Uploading texture"); + + if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ())) + { + gdk_texture_downloader_set_format (&downloader, GDK_MEMORY_R8G8B8A8_PREMULTIPLIED); + gl_format = GL_RGBA; + gl_type = GL_UNSIGNED_BYTE; + } + else + { + gdk_texture_downloader_set_format (&downloader, GDK_MEMORY_DEFAULT); + gl_format = GL_BGRA; + gl_type = GL_UNSIGNED_INT_8_8_8_8_REV; + } + pixel_data = g_malloc (width * height * 4); + gdk_texture_downloader_download_into (&downloader, pixel_data, width * 4); + gdk_texture_downloader_finish (&downloader); + + texture_id = GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (icon_data); + + glBindTexture (GL_TEXTURE_2D, texture_id); + + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x + 1, packed_y + 1, + width, height, + gl_format, gl_type, + pixel_data); + /* Padding top */ + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x + 1, packed_y, + width, 1, + gl_format, gl_type, + pixel_data); + /* Padding left */ + glPixelStorei (GL_UNPACK_ROW_LENGTH, width); + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x, packed_y + 1, + 1, height, + gl_format, gl_type, + pixel_data); + /* Padding top left */ + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x, packed_y, + 1, 1, + gl_format, gl_type, + pixel_data); + + /* Padding right */ + glPixelStorei (GL_UNPACK_SKIP_PIXELS, width - 1); + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x + width + 1, packed_y + 1, + 1, height, + gl_format, gl_type, + pixel_data); + /* Padding top right */ + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x + width + 1, packed_y, + 1, 1, + gl_format, gl_type, + pixel_data); + /* Padding bottom */ + glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei (GL_UNPACK_SKIP_ROWS, height - 1); + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x + 1, packed_y + 1 + height, + width, 1, + gl_format, gl_type, + pixel_data); + /* Padding bottom left */ + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x, packed_y + 1 + height, + 1, 1, + gl_format, gl_type, + pixel_data); + /* Padding bottom right */ + glPixelStorei (GL_UNPACK_ROW_LENGTH, width); + glPixelStorei (GL_UNPACK_SKIP_PIXELS, width - 1); + glTexSubImage2D (GL_TEXTURE_2D, 0, + packed_x + 1 + width, packed_y + 1 + height, + 1, 1, + gl_format, gl_type, + pixel_data); + /* Reset this */ + glPixelStorei (GL_UNPACK_SKIP_PIXELS, 0); + glPixelStorei (GL_UNPACK_ROW_LENGTH, 0); + glPixelStorei (GL_UNPACK_SKIP_ROWS, 0); + + gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ()); + + *out_value = icon_data; + + g_free (pixel_data); + + tl->driver->command_queue->n_uploads++; + + if (gdk_profiler_is_running ()) + { + char message[64]; + g_snprintf (message, sizeof message, "Size %dx%d", width, height); + gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Upload icon", message); + } +} diff --git gsk/gl/gskgliconlibraryprivate.h gsk/gl/gskgliconlibraryprivate.h new file mode 100644 index 0000000000..dc99b546ce --- /dev/null +++ gsk/gl/gskgliconlibraryprivate.h @@ -0,0 +1,58 @@ +/* gskgliconlibraryprivate.h + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include + +#include "gskgltexturelibraryprivate.h" + +G_BEGIN_DECLS + +#define GSK_TYPE_GL_ICON_LIBRARY (gsk_gl_icon_library_get_type()) + +typedef struct _GskGLIconData +{ + GskGLTextureAtlasEntry entry; + GdkTexture *source_texture; +} GskGLIconData; + +G_DECLARE_FINAL_TYPE (GskGLIconLibrary, gsk_gl_icon_library, GSK, GL_ICON_LIBRARY, GskGLTextureLibrary) + +GskGLIconLibrary *gsk_gl_icon_library_new (GskGLDriver *driver); +void gsk_gl_icon_library_add (GskGLIconLibrary *self, + GdkTexture *key, + const GskGLIconData **out_value); + +static inline void +gsk_gl_icon_library_lookup_or_add (GskGLIconLibrary *self, + GdkTexture *key, + const GskGLIconData **out_value) +{ + GskGLTextureAtlasEntry *entry; + + if G_LIKELY (gsk_gl_texture_library_lookup ((GskGLTextureLibrary *)self, key, &entry)) + *out_value = (GskGLIconData *)entry; + else + gsk_gl_icon_library_add (self, key, out_value); +} + +G_END_DECLS + diff --git gsk/gl/gskglprofiler.c gsk/gl/gskglprofiler.c new file mode 100644 index 0000000000..6aff5ab0a5 --- /dev/null +++ gsk/gl/gskglprofiler.c @@ -0,0 +1,179 @@ +#include "config.h" + +#include "gskglprofilerprivate.h" + +#include + +#define N_QUERIES 4 + +struct _GskGLProfiler +{ + GObject parent_instance; + + GdkGLContext *gl_context; + + /* Creating GL queries is kind of expensive, so we pay the + * price upfront and create a circular buffer of queries + */ + GLuint gl_queries[N_QUERIES]; + GLuint active_query; + + unsigned int has_queries : 1; + unsigned int has_timer : 1; + unsigned int first_frame : 1; +}; + +enum { + PROP_GL_CONTEXT = 1, + + N_PROPERTIES +}; + +static GParamSpec *gsk_gl_profiler_properties[N_PROPERTIES]; + +G_DEFINE_TYPE (GskGLProfiler, gsk_gl_profiler, G_TYPE_OBJECT) + +static void +gsk_gl_profiler_finalize (GObject *gobject) +{ + GskGLProfiler *self = GSK_GL_PROFILER (gobject); + + if (self->has_queries) + glDeleteQueries (N_QUERIES, self->gl_queries); + + g_clear_object (&self->gl_context); + + G_OBJECT_CLASS (gsk_gl_profiler_parent_class)->finalize (gobject); +} + +static void +gsk_gl_profiler_set_property (GObject *gobject, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GskGLProfiler *self = GSK_GL_PROFILER (gobject); + + switch (prop_id) + { + case PROP_GL_CONTEXT: + self->gl_context = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +gsk_gl_profiler_get_property (GObject *gobject, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GskGLProfiler *self = GSK_GL_PROFILER (gobject); + + switch (prop_id) + { + case PROP_GL_CONTEXT: + g_value_set_object (value, self->gl_context); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (gobject, prop_id, pspec); + } +} + +static void +gsk_gl_profiler_class_init (GskGLProfilerClass *klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + + gobject_class->set_property = gsk_gl_profiler_set_property; + gobject_class->get_property = gsk_gl_profiler_get_property; + gobject_class->finalize = gsk_gl_profiler_finalize; + + gsk_gl_profiler_properties[PROP_GL_CONTEXT] = + g_param_spec_object ("gl-context", NULL, NULL, + GDK_TYPE_GL_CONTEXT, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (gobject_class, N_PROPERTIES, gsk_gl_profiler_properties); +} + +static void +gsk_gl_profiler_init (GskGLProfiler *self) +{ + self->has_queries = epoxy_is_desktop_gl(); + self->has_timer = epoxy_is_desktop_gl() && (epoxy_gl_version () >= 33 || epoxy_has_gl_extension ("GL_ARB_timer_query")); + + if (!self->has_queries) + return; + + glGenQueries (N_QUERIES, self->gl_queries); + self->first_frame = TRUE; +} + +GskGLProfiler * +gsk_gl_profiler_new (GdkGLContext *context) +{ + g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL); + + return g_object_new (GSK_TYPE_GL_PROFILER, "gl-context", context, NULL); +} + +void +gsk_gl_profiler_begin_gpu_region (GskGLProfiler *profiler) +{ + GLuint query_id; + + g_return_if_fail (GSK_IS_GL_PROFILER (profiler)); + + if (!profiler->has_timer || !profiler->has_queries) + return; + + query_id = profiler->gl_queries[profiler->active_query]; + glBeginQuery (GL_TIME_ELAPSED, query_id); +} + +guint64 +gsk_gl_profiler_end_gpu_region (GskGLProfiler *profiler) +{ + GLuint last_query_id; + GLint res; + GLuint64 elapsed; + + g_return_val_if_fail (GSK_IS_GL_PROFILER (profiler), 0); + + if (!profiler->has_timer || !profiler->has_queries) + return 0; + + glEndQuery (GL_TIME_ELAPSED); + + if (profiler->active_query == 0) + last_query_id = N_QUERIES - 1; + else + last_query_id = profiler->active_query - 1; + + /* Advance iterator */ + profiler->active_query += 1; + if (profiler->active_query == N_QUERIES) + profiler->active_query = 0; + + /* If this is the first frame we already have a result */ + if (profiler->first_frame) + { + profiler->first_frame = FALSE; + return 0; + } + + glGetQueryObjectiv (profiler->gl_queries[last_query_id], GL_QUERY_RESULT_AVAILABLE, &res); + if (res == 1) + glGetQueryObjectui64v (profiler->gl_queries[last_query_id], GL_QUERY_RESULT, &elapsed); + else + elapsed = 0; + + return elapsed / 1000; /* Convert to usec to match other profiler APIs */ +} diff --git gsk/gl/gskglprofilerprivate.h gsk/gl/gskglprofilerprivate.h new file mode 100644 index 0000000000..3aa0650c11 --- /dev/null +++ gsk/gl/gskglprofilerprivate.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +G_BEGIN_DECLS + +#define GSK_TYPE_GL_PROFILER (gsk_gl_profiler_get_type ()) +G_DECLARE_FINAL_TYPE (GskGLProfiler, gsk_gl_profiler, GSK, GL_PROFILER, GObject) + +GskGLProfiler * gsk_gl_profiler_new (GdkGLContext *context); + +void gsk_gl_profiler_begin_gpu_region (GskGLProfiler *profiler); +guint64 gsk_gl_profiler_end_gpu_region (GskGLProfiler *profiler); + +G_END_DECLS + diff --git gsk/gl/gskglprogram.c gsk/gl/gskglprogram.c new file mode 100644 index 0000000000..7288284593 --- /dev/null +++ gsk/gl/gskglprogram.c @@ -0,0 +1,173 @@ +/* gskglprogram.c + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include "gskglcommandqueueprivate.h" +#include "gskglprogramprivate.h" +#include "gskgluniformstateprivate.h" + +G_DEFINE_TYPE (GskGLProgram, gsk_gl_program, G_TYPE_OBJECT) + +GskGLProgram * +gsk_gl_program_new (GskGLDriver *driver, + const char *name, + int program_id) +{ + GskGLProgram *self; + + g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL); + g_return_val_if_fail (program_id >= -1, NULL); + + self = g_object_new (GSK_TYPE_GL_PROGRAM, NULL); + self->id = program_id; + self->name = g_strdup (name); + self->driver = g_object_ref (driver); + self->n_mappings = 0; + + return self; +} + +static void +gsk_gl_program_finalize (GObject *object) +{ + GskGLProgram *self = (GskGLProgram *)object; + + if (self->id >= 0) + g_warning ("Leaking GLSL program %d (%s)", + self->id, + self->name ? self->name : ""); + + g_clear_pointer (&self->name, g_free); + g_clear_object (&self->driver); + + G_OBJECT_CLASS (gsk_gl_program_parent_class)->finalize (object); +} + +static void +gsk_gl_program_class_init (GskGLProgramClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->finalize = gsk_gl_program_finalize; +} + +static void +gsk_gl_program_init (GskGLProgram *self) +{ + self->id = -1; + + for (guint i = 0; i < G_N_ELEMENTS (self->mappings); i++) + self->mappings[i].location = -1; +} + +/** + * gsk_gl_program_add_uniform: + * @self: a `GskGLProgram` + * @name: the name of the uniform such as "u_source" + * @key: the identifier to use for the uniform + * + * This method will create a mapping between @key and the location + * of the uniform on the GPU. This simplifies calling code to not + * need to know where the uniform location is and only register it + * when creating the program. + * + * You might use this with an enum of all your uniforms for the + * program and then register each of them like: + * + * ``` + * gsk_gl_program_add_uniform (program, "u_source", UNIFORM_SOURCE); + * ``` + * + * That allows you to set values for the program with something + * like the following: + * + * ``` + * gsk_gl_program_set_uniform1i (program, UNIFORM_SOURCE, 1); + * ``` + * + * Returns: %TRUE if the uniform was found; otherwise %FALSE + */ +gboolean +gsk_gl_program_add_uniform (GskGLProgram *self, + const char *name, + guint key) +{ + GLint location; + + g_return_val_if_fail (GSK_IS_GL_PROGRAM (self), FALSE); + g_return_val_if_fail (name != NULL, FALSE); + g_return_val_if_fail (key < G_N_ELEMENTS (self->mappings), FALSE); + + location = glGetUniformLocation (self->id, name); + + /* Register the information even if unused */ + self->mappings[key].name = g_intern_string (name); + self->mappings[key].location = location; + if (key >= self->n_mappings) + self->n_mappings = key + 1; + +#if 0 + g_print ("program [%d] %s uniform %s [%u of %u] at location %d.\n", + self->id, self->name, name, key, self->n_mappings, location); +#endif + + return location > -1; +} + +/** + * gsk_gl_program_delete: + * @self: a `GskGLProgram` + * + * Deletes the GLSL program. + */ +void +gsk_gl_program_delete (GskGLProgram *self) +{ + g_return_if_fail (GSK_IS_GL_PROGRAM (self)); + g_return_if_fail (self->driver->command_queue != NULL); + + gsk_gl_command_queue_delete_program (self->driver->command_queue, self->id); + self->id = -1; +} + +/** + * gsk_gl_program_uniforms_added: + * @self: a `GskGLProgram` + * @has_attachments: if any uniform is for a bind/texture attachment + * + * This function should be called after all of the uniforms have + * been added with gsk_gl_program_add_uniform(). + * + * This function will setup the uniform state so that the program + * has fast access to the data buffers without as many lookups at + * runtime for comparison data. + */ +void +gsk_gl_program_uniforms_added (GskGLProgram *self, + gboolean has_attachments) +{ + g_return_if_fail (GSK_IS_GL_PROGRAM (self)); + g_return_if_fail (self->uniforms == NULL); + + self->uniforms = self->driver->command_queue->uniforms; + self->program_info = gsk_gl_uniform_state_get_program (self->uniforms, self->id, self->mappings, self->n_mappings); + self->program_info->has_attachments = has_attachments; +} diff --git gsk/gl/gskglprogramprivate.h gsk/gl/gskglprogramprivate.h new file mode 100644 index 0000000000..6bfbe48f02 --- /dev/null +++ gsk/gl/gskglprogramprivate.h @@ -0,0 +1,318 @@ +/* gskglprogramprivate.h + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include "gskgltypesprivate.h" + +#include "gskglattachmentstateprivate.h" +#include "gskglcommandqueueprivate.h" +#include "gskgldriverprivate.h" + +G_BEGIN_DECLS + +#define GSK_TYPE_GL_PROGRAM (gsk_gl_program_get_type()) +#define GSK_GL_PROGRAM_MAX_CUSTOM_TEXTURES 4 +#define GSK_GL_PROGRAM_MAX_CUSTOM_ARGS 8 + +G_DECLARE_FINAL_TYPE (GskGLProgram, gsk_gl_program, GSK, GL_PROGRAM, GObject) + +struct _GskGLProgram +{ + GObject parent_instance; + + int id; + char *name; + GskGLDriver *driver; + + /* Cached pointer to avoid lots of pointer chasing/lookups */ + GskGLUniformState *uniforms; + GskGLUniformProgram *program_info; + + /* Static array for key->location transforms */ + GskGLUniformMapping mappings[32]; + guint n_mappings; +}; + +GskGLProgram * gsk_gl_program_new (GskGLDriver *driver, + const char *name, + int program_id); +gboolean gsk_gl_program_add_uniform (GskGLProgram *self, + const char *name, + guint key); +void gsk_gl_program_uniforms_added (GskGLProgram *self, + gboolean has_attachments); +void gsk_gl_program_delete (GskGLProgram *self); + +static inline void +gsk_gl_program_set_uniform1fv (GskGLProgram *self, + guint key, + guint stamp, + guint count, + const float *values) +{ + gsk_gl_uniform_state_set1fv (self->uniforms, self->program_info, + key, + stamp, + count, + values); +} + +static inline void +gsk_gl_program_set_uniform2fv (GskGLProgram *self, + guint key, + guint stamp, + guint count, + const float *values) +{ + gsk_gl_uniform_state_set2fv (self->uniforms, self->program_info, + key, + stamp, + count, + values); +} + +static inline void +gsk_gl_program_set_uniform4fv (GskGLProgram *self, + guint key, + guint stamp, + guint count, + const float *values) +{ + gsk_gl_uniform_state_set4fv (self->uniforms, self->program_info, + key, + stamp, + count, + values); +} + +static inline void +gsk_gl_program_set_uniform_rounded_rect (GskGLProgram *self, + guint key, + guint stamp, + const GskRoundedRect *rounded_rect) +{ + gsk_gl_uniform_state_set_rounded_rect (self->uniforms, self->program_info, + key, + stamp, + rounded_rect); +} + +static inline void +gsk_gl_program_set_uniform1i (GskGLProgram *self, + guint key, + guint stamp, + int value0) +{ + gsk_gl_uniform_state_set1i (self->uniforms, + self->program_info, + key, + stamp, + value0); +} + +static inline void +gsk_gl_program_set_uniform2i (GskGLProgram *self, + guint key, + guint stamp, + int value0, + int value1) +{ + gsk_gl_uniform_state_set2i (self->uniforms, + self->program_info, + key, + stamp, + value0, value1); +} + +static inline void +gsk_gl_program_set_uniform3i (GskGLProgram *self, + guint key, + guint stamp, + int value0, + int value1, + int value2) +{ + gsk_gl_uniform_state_set3i (self->uniforms, + self->program_info, + key, + stamp, + value0, value1, value2); +} + +static inline void +gsk_gl_program_set_uniform4i (GskGLProgram *self, + guint key, + guint stamp, + int value0, + int value1, + int value2, + int value3) +{ + gsk_gl_uniform_state_set4i (self->uniforms, + self->program_info, + key, + stamp, + value0, value1, value2, value3); +} + +static inline void +gsk_gl_program_set_uniform1f (GskGLProgram *self, + guint key, + guint stamp, + float value0) +{ + gsk_gl_uniform_state_set1f (self->uniforms, + self->program_info, + key, + stamp, + value0); +} + +static inline void +gsk_gl_program_set_uniform2f (GskGLProgram *self, + guint key, + guint stamp, + float value0, + float value1) +{ + gsk_gl_uniform_state_set2f (self->uniforms, + self->program_info, + key, + stamp, + value0, value1); +} + +static inline void +gsk_gl_program_set_uniform3f (GskGLProgram *self, + guint key, + guint stamp, + float value0, + float value1, + float value2) +{ + gsk_gl_uniform_state_set3f (self->uniforms, + self->program_info, + key, + stamp, + value0, value1, value2); +} + +static inline void +gsk_gl_program_set_uniform4f (GskGLProgram *self, + guint key, + guint stamp, + float value0, + float value1, + float value2, + float value3) +{ + gsk_gl_uniform_state_set4f (self->uniforms, + self->program_info, + key, + stamp, + value0, value1, value2, value3); +} + +static inline void +gsk_gl_program_set_uniform_color (GskGLProgram *self, + guint key, + guint stamp, + const GdkRGBA *color) +{ + gsk_gl_uniform_state_set_color (self->uniforms, + self->program_info, + key, + stamp, + color); +} + +static inline void +gsk_gl_program_set_uniform_texture_with_filter (GskGLProgram *self, + guint key, + guint stamp, + GLenum texture_target, + GLenum texture_slot, + guint texture_id, + GLint min_filter, + GLint mag_filter) +{ + gsk_gl_attachment_state_bind_texture (self->driver->command_queue->attachments, + texture_target, + texture_slot, + texture_id, + min_filter, + mag_filter); + gsk_gl_uniform_state_set_texture (self->uniforms, + self->program_info, + key, + stamp, + texture_slot); +} + +static inline void +gsk_gl_program_set_uniform_texture (GskGLProgram *self, + guint key, + guint stamp, + GLenum texture_target, + GLenum texture_slot, + guint texture_id) +{ + gsk_gl_program_set_uniform_texture_with_filter (self, + key, + stamp, + texture_target, + texture_slot, + texture_id, + GL_LINEAR, + GL_LINEAR); +} + +static inline void +gsk_gl_program_set_uniform_texture_with_sync (GskGLProgram *self, + guint key, + guint stamp, + GLenum texture_target, + GLenum texture_slot, + guint texture_id, + GLint min_filter, + GLint max_filter, + gpointer sync) +{ + gsk_gl_program_set_uniform_texture_with_filter (self, key, stamp, texture_target, texture_slot, texture_id, + min_filter, max_filter); + gsk_gl_syncs_add_sync (&self->driver->command_queue->syncs, texture_id, sync); +} + +static inline void +gsk_gl_program_set_uniform_matrix (GskGLProgram *self, + guint key, + guint stamp, + const graphene_matrix_t *matrix) +{ + gsk_gl_uniform_state_set_matrix (self->uniforms, + self->program_info, + key, + stamp, + matrix); +} + +G_END_DECLS + diff --git gsk/gl/gskglprograms.defs gsk/gl/gskglprograms.defs new file mode 100644 index 0000000000..0eabd8a9bd --- /dev/null +++ gsk/gl/gskglprograms.defs @@ -0,0 +1,104 @@ +GSK_GL_DEFINE_PROGRAM (blend, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("blend.glsl")), + GSK_GL_ADD_UNIFORM (1, BLEND_SOURCE2, u_source2) + GSK_GL_ADD_UNIFORM (2, BLEND_MODE, u_mode)) + +GSK_GL_DEFINE_PROGRAM (blit, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("blit.glsl")), + GSK_GL_NO_UNIFORMS) + +GSK_GL_DEFINE_PROGRAM (blur, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("blur.glsl")), + GSK_GL_ADD_UNIFORM (1, BLUR_RADIUS, u_blur_radius) + GSK_GL_ADD_UNIFORM (2, BLUR_SIZE, u_blur_size) + GSK_GL_ADD_UNIFORM (3, BLUR_DIR, u_blur_dir)) + +GSK_GL_DEFINE_PROGRAM (border, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("border.glsl")), + GSK_GL_ADD_UNIFORM (1, BORDER_WIDTHS, u_widths) + GSK_GL_ADD_UNIFORM (2, BORDER_OUTLINE_RECT, u_outline_rect)) + +GSK_GL_DEFINE_PROGRAM (color, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("color.glsl")), + GSK_GL_NO_UNIFORMS) + +GSK_GL_DEFINE_PROGRAM (coloring, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("coloring.glsl")), + GSK_GL_NO_UNIFORMS) + +GSK_GL_DEFINE_PROGRAM (color_matrix, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("color_matrix.glsl")), + GSK_GL_ADD_UNIFORM (1, COLOR_MATRIX_COLOR_MATRIX, u_color_matrix) + GSK_GL_ADD_UNIFORM (2, COLOR_MATRIX_COLOR_OFFSET, u_color_offset)) + +GSK_GL_DEFINE_PROGRAM (conic_gradient, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("conic_gradient.glsl")), + GSK_GL_ADD_UNIFORM (1, CONIC_GRADIENT_COLOR_STOPS, u_color_stops) + GSK_GL_ADD_UNIFORM (2, CONIC_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops) + GSK_GL_ADD_UNIFORM (3, CONIC_GRADIENT_GEOMETRY, u_geometry)) + +GSK_GL_DEFINE_PROGRAM (cross_fade, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("cross_fade.glsl")), + GSK_GL_ADD_UNIFORM (1, CROSS_FADE_PROGRESS, u_progress) + GSK_GL_ADD_UNIFORM (2, CROSS_FADE_SOURCE2, u_source2)) + +GSK_GL_DEFINE_PROGRAM (filled_border, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("filled_border.glsl")), + GSK_GL_ADD_UNIFORM (1, FILLED_BORDER_WIDTHS, u_widths) + GSK_GL_ADD_UNIFORM (2, FILLED_BORDER_OUTLINE_RECT, u_outline_rect)) + +GSK_GL_DEFINE_PROGRAM (inset_shadow, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("inset_shadow.glsl")), + GSK_GL_ADD_UNIFORM (1, INSET_SHADOW_SPREAD, u_spread) + GSK_GL_ADD_UNIFORM (2, INSET_SHADOW_OFFSET, u_offset) + GSK_GL_ADD_UNIFORM (3, INSET_SHADOW_OUTLINE_RECT, u_outline_rect)) + +GSK_GL_DEFINE_PROGRAM (linear_gradient, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("linear_gradient.glsl")), + GSK_GL_ADD_UNIFORM (1, LINEAR_GRADIENT_COLOR_STOPS, u_color_stops) + GSK_GL_ADD_UNIFORM (2, LINEAR_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops) + GSK_GL_ADD_UNIFORM (3, LINEAR_GRADIENT_POINTS, u_points) + GSK_GL_ADD_UNIFORM (4, LINEAR_GRADIENT_REPEAT, u_repeat)) + +GSK_GL_DEFINE_PROGRAM (mask, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("mask.glsl")), + GSK_GL_ADD_UNIFORM (1, MASK_SOURCE, u_mask) + GSK_GL_ADD_UNIFORM (2, MASK_MODE, u_mode)) + +GSK_GL_DEFINE_PROGRAM (outset_shadow, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("outset_shadow.glsl")), + GSK_GL_ADD_UNIFORM (1, OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect)) + +GSK_GL_DEFINE_PROGRAM (radial_gradient, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("radial_gradient.glsl")), + GSK_GL_ADD_UNIFORM (1, RADIAL_GRADIENT_COLOR_STOPS, u_color_stops) + GSK_GL_ADD_UNIFORM (2, RADIAL_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops) + GSK_GL_ADD_UNIFORM (3, RADIAL_GRADIENT_REPEAT, u_repeat) + GSK_GL_ADD_UNIFORM (4, RADIAL_GRADIENT_RANGE, u_range) + GSK_GL_ADD_UNIFORM (5, RADIAL_GRADIENT_GEOMETRY, u_geometry)) + +GSK_GL_DEFINE_PROGRAM (repeat, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("repeat.glsl")), + GSK_GL_ADD_UNIFORM (1, REPEAT_CHILD_BOUNDS, u_child_bounds) + GSK_GL_ADD_UNIFORM (2, REPEAT_TEXTURE_RECT, u_texture_rect)) + +GSK_GL_DEFINE_PROGRAM (unblurred_outset_shadow, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("unblurred_outset_shadow.glsl")), + GSK_GL_ADD_UNIFORM (1, UNBLURRED_OUTSET_SHADOW_SPREAD, u_spread) + GSK_GL_ADD_UNIFORM (2, UNBLURRED_OUTSET_SHADOW_OFFSET, u_offset) + GSK_GL_ADD_UNIFORM (3, UNBLURRED_OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect)) + +/* Texture conversion shaders. + * + * Note: If you add new formats here, they need to be added + * to the list of supported formats in gdk/gdkdmabuftexture.c. + */ + +GSK_GL_DEFINE_PROGRAM_NO_CLIP (external, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("external.glsl")), + GSK_GL_ADD_UNIFORM (1, EXTERNAL_SOURCE, u_external_source) + GSK_GL_ADD_UNIFORM (2, PREMULTIPLY, u_premultiply)) + +GSK_GL_DEFINE_PROGRAM_NO_CLIP (premultiply, + GSK_GL_SHADER_SINGLE (GSK_GL_SHADER_RESOURCE ("premultiply.glsl")), + GSK_GL_NO_UNIFORMS) diff --git gsk/gl/gskglrenderer.c gsk/gl/gskglrenderer.c new file mode 100644 index 0000000000..928cb83c68 --- /dev/null +++ gsk/gl/gskglrenderer.c @@ -0,0 +1,497 @@ +/* gskglrenderer.c + * + * Copyright 2020 Christian Hergert + * + * This file 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 file 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 General Public License along + * with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include "gskglrendererprivate.h" + +#include "gskglcommandqueueprivate.h" +#include "gskgldriverprivate.h" +#include "gskglprogramprivate.h" +#include "gskglrenderjobprivate.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * GskGLLegacyRenderer: + * + * A GL based renderer. + * + * See [class@Gsk.Renderer]. + * + * Since: 4.2 + */ +struct _GskGLLegacyRendererClass +{ + GskRendererClass parent_class; +}; + +struct _GskGLLegacyRenderer +{ + GskRenderer parent_instance; + + /* This context is used to swap buffers when we are rendering directly + * to a GDK surface. It is also used to locate the shared driver for + * the display that we use to drive the command queue. + */ + GdkGLContext *context; + + /* Our command queue is private to this renderer and talks to the GL + * context for our target surface. This ensure that framebuffer 0 matches + * the surface we care about. Since the context is shared with other + * contexts from other renderers on the display, texture atlases, + * programs, and other objects are available to them all. + */ + GskGLCommandQueue *command_queue; + + /* The driver manages our program state and command queues. It also + * deals with caching textures, shaders, shadows, glyph, and icon + * caches through various helpers. + */ + GskGLDriver *driver; +}; + +static gboolean +gsk_gl_legacy_renderer_dmabuf_downloader_supports (GdkDmabufDownloader *downloader, + GdkDmabufTexture *texture, + GError **error) +{ + GdkDisplay *display = gdk_dmabuf_texture_get_display (texture); + const GdkDmabuf *dmabuf = gdk_dmabuf_texture_get_dmabuf (texture); + + if (!gdk_dmabuf_formats_contains (display->egl_dmabuf_formats, dmabuf->fourcc, dmabuf->modifier)) + { + g_set_error (error, + GDK_DMABUF_ERROR, GDK_DMABUF_ERROR_UNSUPPORTED_FORMAT, + "Unsupported dmabuf format: %.4s:%#" G_GINT64_MODIFIER "x", + (char *) &dmabuf->fourcc, dmabuf->modifier); + return FALSE; + } + + return TRUE; +} + +static gboolean +gsk_gl_legacy_renderer_dmabuf_downloader_download (GdkDmabufDownloader *downloader_, + GdkDmabufTexture *texture, + guchar *data, + const GdkMemoryLayout *layout, + GdkColorState *color_state) +{ + GskRenderer *renderer = GSK_RENDERER (downloader_); + GdkGLContext *previous; + GdkTexture *native; + GdkTextureDownloader *downloader; + int width, height; + GskRenderNode *node; + gsize stride; + + previous = gdk_gl_context_get_current (); + + width = gdk_texture_get_width (GDK_TEXTURE (texture)); + height = gdk_texture_get_height (GDK_TEXTURE (texture)); + + node = gsk_texture_node_new (GDK_TEXTURE (texture), &GRAPHENE_RECT_INIT (0, 0, width, height)); + native = gsk_renderer_render_texture (renderer, node, &GRAPHENE_RECT_INIT (0, 0, width, height)); + gsk_render_node_unref (node); + + downloader = gdk_texture_downloader_new (native); + gdk_texture_downloader_set_format (downloader, layout->format); + gdk_texture_downloader_set_color_state (downloader, color_state); + gdk_texture_downloader_download_into (downloader, data, layout->planes[0].stride); + gdk_texture_downloader_free (downloader); + + g_object_unref (native); + + if (previous) + gdk_gl_context_make_current (previous); + else + gdk_gl_context_clear_current (); +} + +static void +gsk_gl_legacy_renderer_dmabuf_downloader_close (GdkDmabufDownloader *downloader) +{ + gsk_renderer_unrealize (GSK_RENDERER (downloader)); +} + +static void +gsk_gl_legacy_renderer_dmabuf_downloader_init (GdkDmabufDownloaderInterface *iface) +{ + iface->close = gsk_gl_legacy_renderer_dmabuf_downloader_close; + iface->download = gsk_gl_legacy_renderer_dmabuf_downloader_download; +} + +G_DEFINE_TYPE_EXTENDED (GskGLLegacyRenderer, gsk_gl_legacy_renderer, GSK_TYPE_RENDERER, 0, + G_IMPLEMENT_INTERFACE (GDK_TYPE_DMABUF_DOWNLOADER, + gsk_gl_legacy_renderer_dmabuf_downloader_init)) + +/** + * gsk_gl_legacy_renderer_new: + * + * Creates a new `GskRenderer` using the new OpenGL renderer. + * + * Returns: a new GL renderer + * + * Since: 4.2 + */ +GskRenderer * +gsk_gl_legacy_renderer_new (void) +{ + return g_object_new (GSK_TYPE_GL_LEGACY_RENDERER, NULL); +} + +static gboolean +gsk_gl_legacy_renderer_realize (GskRenderer *renderer, + GdkDisplay *display, + GdkSurface *surface, + gboolean attach, + GError **error) +{ + G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME; + GskGLLegacyRenderer *self = (GskGLLegacyRenderer *)renderer; + GdkGLContext *context = NULL; + GskGLDriver *driver = NULL; + gboolean ret = FALSE; + gboolean debug_shaders = FALSE; + + if (self->context != NULL) + return TRUE; + + g_assert (self->driver == NULL); + g_assert (self->context == NULL); + g_assert (self->command_queue == NULL); + + if (!gdk_display_prepare_gl (display, error)) + goto failure; + + context = gdk_gl_context_new (display, surface, surface != NULL); + + if (!gdk_gl_context_realize (context, error)) + goto failure; + + gdk_gl_context_make_current (context); + + if (!gdk_gl_context_check_version (context, "2.0", "2.0")) + { + g_set_error_literal (error, GDK_GL_ERROR, + GDK_GL_ERROR_NOT_AVAILABLE, + _("OpenGL 2.0 required")); + goto failure; + } + + if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), SHADERS)) + debug_shaders = TRUE; + + if (!(driver = gsk_gl_driver_for_display (display, debug_shaders, error))) + goto failure; + + self->command_queue = gsk_gl_driver_create_command_queue (driver, context); + self->context = g_steal_pointer (&context); + self->driver = g_steal_pointer (&driver); + + gsk_gl_command_queue_set_profiler (self->command_queue, + gsk_renderer_get_profiler (renderer)); + + if (attach && !gdk_draw_context_attach (GDK_DRAW_CONTEXT (self->context), error)) + goto failure; + + ret = TRUE; + +failure: + g_clear_object (&driver); + g_clear_object (&context); + + gdk_profiler_end_mark (start_time, "realize GskGLLegacyRenderer", NULL); + + /* Assert either all or no state was set */ + g_assert ((ret && self->driver != NULL && self->context != NULL && self->command_queue != NULL) || + (!ret && self->driver == NULL && self->context == NULL && self->command_queue == NULL)); + + return ret; +} + +static void +gsk_gl_legacy_renderer_unrealize (GskRenderer *renderer) +{ + GskGLLegacyRenderer *self = (GskGLLegacyRenderer *)renderer; + + g_assert (GSK_IS_GL_LEGACY_RENDERER (renderer)); + + gdk_gl_context_make_current (self->context); + + g_clear_object (&self->driver); + g_clear_object (&self->command_queue); + + gdk_draw_context_detach (GDK_DRAW_CONTEXT (self->context)); + + g_clear_object (&self->context); + + gdk_gl_context_clear_current (); +} + +static cairo_region_t * +get_render_region (GdkSurface *surface, + GdkGLContext *context) +{ + const cairo_region_t *damage; + GdkRectangle whole_surface; + GdkRectangle extents; + + g_assert (GDK_IS_SURFACE (surface)); + g_assert (GDK_IS_GL_CONTEXT (context)); + + whole_surface.x = 0; + whole_surface.y = 0; + whole_surface.width = gdk_surface_get_width (surface); + whole_surface.height = gdk_surface_get_height (surface); + + /* Damage does not have scale factor applied so we can compare it to + * @whole_surface which also doesn't have the scale factor applied. + */ + damage = gdk_draw_context_get_render_region (GDK_DRAW_CONTEXT (context)); + + if (cairo_region_contains_rectangle (damage, &whole_surface) == CAIRO_REGION_OVERLAP_IN) + return NULL; + + /* If the extents match the full-scene, do the same as above */ + cairo_region_get_extents (damage, &extents); + if (gdk_rectangle_equal (&extents, &whole_surface)) + return NULL; + + /* Draw clipped to the bounding-box of the region. */ + return cairo_region_create_rectangle (&extents); +} + +static void +gsk_gl_legacy_renderer_render (GskRenderer *renderer, + GskRenderNode *root, + const cairo_region_t *update_area) +{ + GskGLLegacyRenderer *self = (GskGLLegacyRenderer *)renderer; + cairo_region_t *render_region; + graphene_rect_t viewport; + GskGLRenderJob *job; + GdkSurface *surface; + graphene_rect_t opaque_tmp; + const graphene_rect_t *opaque; + float scale; + + g_assert (GSK_IS_GL_LEGACY_RENDERER (renderer)); + g_assert (root != NULL); + + surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (self->context)); + scale = gdk_gl_context_get_scale (self->context); + + if (cairo_region_is_empty (update_area)) + { + gdk_draw_context_empty_frame (GDK_DRAW_CONTEXT (self->context)); + return; + } + + viewport.origin.x = 0; + viewport.origin.y = 0; + viewport.size.width = gdk_surface_get_width (surface) * scale; + viewport.size.height = gdk_surface_get_height (surface) * scale; + + if (gsk_render_node_get_opaque_rect (root, &opaque_tmp)) + opaque = &opaque_tmp; + else + opaque = NULL; + gdk_draw_context_begin_frame_full (GDK_DRAW_CONTEXT (self->context), + NULL, + gsk_render_node_get_preferred_depth (root), + update_area, + opaque); + + gdk_gl_context_make_current (self->context); + + /* Must be called *AFTER* gdk_draw_context_begin_frame() */ + render_region = get_render_region (surface, self->context); + + gsk_gl_driver_begin_frame (self->driver, self->command_queue); + job = gsk_gl_render_job_new (self->driver, &viewport, scale, render_region, 0, TRUE); + gsk_gl_render_job_render (job, root); + gsk_gl_driver_end_frame (self->driver); + gsk_gl_render_job_free (job); + + gdk_draw_context_end_frame_full (GDK_DRAW_CONTEXT (self->context), NULL); + + gsk_gl_driver_after_frame (self->driver); + + cairo_region_destroy (render_region); +} + +static GdkTexture * +gsk_gl_legacy_renderer_render_texture (GskRenderer *renderer, + GskRenderNode *root, + const graphene_rect_t *viewport) +{ + GskGLLegacyRenderer *self = (GskGLLegacyRenderer *)renderer; + GskGLRenderTarget *render_target; + GskGLRenderJob *job; + GdkTexture *texture; + guint texture_id; + GdkMemoryFormat gdk_format; + int width, height, max_size; + int format; + + g_assert (GSK_IS_GL_LEGACY_RENDERER (renderer)); + g_assert (root != NULL); + + width = ceilf (viewport->size.width); + height = ceilf (viewport->size.height); + max_size = self->command_queue->max_texture_size; + if (width > max_size || height > max_size) + { + gsize x, y, size, stride; + GBytes *bytes; + guchar *data; + + stride = width * 4; + size = stride * height; + data = g_malloc_n (stride, height); + + for (y = 0; y < height; y += max_size) + { + for (x = 0; x < width; x += max_size) + { + texture = gsk_gl_legacy_renderer_render_texture (renderer, root, + &GRAPHENE_RECT_INIT (viewport->origin.x + x, + viewport->origin.y + y, + MIN (max_size, viewport->size.width - x), + MIN (max_size, viewport->size.height - y))); + gdk_texture_download (texture, + data + stride * y + x * 4, + stride); + g_object_unref (texture); + } + } + + bytes = g_bytes_new_take (data, size); + texture = gdk_memory_texture_new (width, height, GDK_MEMORY_DEFAULT, bytes, stride); + g_bytes_unref (bytes); + return texture; + } + + /* Don't use float textures for SRGB or node-editor turns on high + * depth unconditionally. */ + if (gsk_render_node_get_preferred_depth (root) != GDK_MEMORY_NONE && + gsk_render_node_get_preferred_depth (root) != GDK_MEMORY_U8 && + gdk_gl_context_check_version (self->context, "3.0", "3.0")) + { + gdk_format = GDK_MEMORY_R32G32B32A32_FLOAT_PREMULTIPLIED; + format = GL_RGBA32F; + } + else + { + format = GL_RGBA8; + gdk_format = GDK_MEMORY_R8G8B8A8_PREMULTIPLIED; + } + + gdk_gl_context_make_current (self->context); + + if (gsk_gl_driver_create_render_target (self->driver, + width, height, + format, + &render_target)) + { + gsk_gl_driver_begin_frame (self->driver, self->command_queue); + job = gsk_gl_render_job_new (self->driver, viewport, 1, NULL, render_target->framebuffer_id, TRUE); + gsk_gl_render_job_render_flipped (job, root); + texture_id = gsk_gl_driver_release_render_target (self->driver, render_target, FALSE); + texture = gsk_gl_driver_create_gdk_texture (self->driver, texture_id, gdk_format); + gsk_gl_driver_end_frame (self->driver); + gsk_gl_render_job_free (job); + + gsk_gl_driver_after_frame (self->driver); + } + else + { + g_assert_not_reached (); + } + + return g_steal_pointer (&texture); +} + +static void +gsk_gl_legacy_renderer_dispose (GObject *object) +{ + GskGLLegacyRenderer *self = (GskGLLegacyRenderer *)object; + + if (self->driver != NULL) + g_critical ("Attempt to dispose %s without calling gsk_renderer_unrealize()", + G_OBJECT_TYPE_NAME (self)); + + G_OBJECT_CLASS (gsk_gl_legacy_renderer_parent_class)->dispose (object); +} + +static void +gsk_gl_legacy_renderer_class_init (GskGLLegacyRendererClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass); + + object_class->dispose = gsk_gl_legacy_renderer_dispose; + + renderer_class->supports_offload = TRUE; + + renderer_class->realize = gsk_gl_legacy_renderer_realize; + renderer_class->unrealize = gsk_gl_legacy_renderer_unrealize; + renderer_class->render = gsk_gl_legacy_renderer_render; + renderer_class->render_texture = gsk_gl_legacy_renderer_render_texture; +} + +static void +gsk_gl_legacy_renderer_init (GskGLLegacyRenderer *self) +{ +} + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + +gboolean +gsk_gl_legacy_renderer_try_compile_gl_shader (GskGLLegacyRenderer *renderer, + GskGLShader *shader, + GError **error) +{ + GskGLProgram *program; + + g_return_val_if_fail (GSK_IS_GL_LEGACY_RENDERER (renderer), FALSE); + g_return_val_if_fail (shader != NULL, FALSE); + + program = gsk_gl_driver_lookup_shader (renderer->driver, shader, error); + + return program != NULL; +} + +G_GNUC_END_IGNORE_DEPRECATIONS diff --git gsk/gl/gskglrenderer.h gsk/gl/gskglrenderer.h index 89c37a3b93..84def71701 100644 --- gsk/gl/gskglrenderer.h +++ gsk/gl/gskglrenderer.h @@ -20,14 +20,37 @@ #pragma once -#ifndef __GI_SCANNER__ -# define WARNING_MSG "#include instead of to avoid this warning" -# ifdef _MSC_VER -# pragma message("WARNING: " WARNING_MSG) -# else -# warning WARNING_MSG -# endif -# undef WARNING_MSG +/* Allow direct inclusion: if we are not inside and not building + * GTK itself, pull in the umbrella header first so that all prerequisites + * (gdk version macros, GskRenderer forward-decl, …) are available. + * The #pragma once above makes the subsequent #include + * a no-op if gsktypes.h was already included by gsk.h. + */ +#if !defined (__GSK_H_INSIDE__) && !defined (GTK_COMPILATION) +#include #endif -#include +#include + +G_BEGIN_DECLS + +#define GSK_TYPE_GL_LEGACY_RENDERER (gsk_gl_legacy_renderer_get_type ()) + +#define GSK_GL_LEGACY_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_GL_LEGACY_RENDERER, GskGLLegacyRenderer)) +#define GSK_IS_GL_LEGACY_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_GL_LEGACY_RENDERER)) +#define GSK_GL_LEGACY_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_GL_LEGACY_RENDERER, GskGLLegacyRendererClass)) +#define GSK_IS_GL_LEGACY_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_GL_LEGACY_RENDERER)) +#define GSK_GL_LEGACY_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_GL_LEGACY_RENDERER, GskGLLegacyRendererClass)) + +typedef struct _GskGLLegacyRenderer GskGLLegacyRenderer; +typedef struct _GskGLLegacyRendererClass GskGLLegacyRendererClass; + +GDK_AVAILABLE_IN_4_2 +GType gsk_gl_legacy_renderer_get_type (void) G_GNUC_CONST; + +GDK_AVAILABLE_IN_4_2 +GskRenderer *gsk_gl_legacy_renderer_new (void); + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GskGLLegacyRenderer, g_object_unref) + +G_END_DECLS diff --git gsk/gl/gskglrendererprivate.h gsk/gl/gskglrendererprivate.h new file mode 100644 index 0000000000..8a9e9b06c8 --- /dev/null +++ gsk/gl/gskglrendererprivate.h @@ -0,0 +1,38 @@ +/* gskglrendererprivate.h + * + * Copyright 2021 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include "gskglrenderer.h" + +#include "gskglshader.h" + +G_BEGIN_DECLS + +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + +gboolean gsk_gl_legacy_renderer_try_compile_gl_shader (GskGLLegacyRenderer *renderer, + GskGLShader *shader, + GError **error); + +G_GNUC_END_IGNORE_DEPRECATIONS + +G_END_DECLS + diff --git gsk/gl/gskglrenderjob.c gsk/gl/gskglrenderjob.c new file mode 100644 index 0000000000..5c078742d9 --- /dev/null +++ gsk/gl/gskglrenderjob.c @@ -0,0 +1,4749 @@ +/* gskglrenderjob.c + * + * Copyright 2017 Timm Bäder + * Copyright 2018 Matthias Clasen + * Copyright 2018 Alexander Larsson + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gskbordernodeprivate.h" +#include "gskcolornodeprivate.h" +#include "gskinsetshadownodeprivate.h" +#include "gskoutsetshadownodeprivate.h" +#include "gsktextnodeprivate.h" +#include "gskglcommandqueueprivate.h" +#include "gskgldriverprivate.h" +#include "gskglglyphlibraryprivate.h" +#include "gskgliconlibraryprivate.h" +#include "gskbordernodeprivate.h" +#include "gskglprogramprivate.h" +#include "gskglrenderjobprivate.h" +#include "gskglshadowlibraryprivate.h" +#include "gskdebugprivate.h" + +#include "ninesliceprivate.h" +#include "fp16private.h" + +#define ORTHO_NEAR_PLANE -10000 +#define ORTHO_FAR_PLANE 10000 +#define MAX_GRADIENT_STOPS 6 +#define SHADOW_EXTRA_SIZE 4 + +/* Make sure gradient stops fits in packed array_count */ +G_STATIC_ASSERT ((MAX_GRADIENT_STOPS * 5) < (1 << GSK_GL_UNIFORM_ARRAY_BITS)); + +#define ALPHA_IS_CLEAR(alpha) ((alpha) < ((float) 0x00ff / (float) 0xffff)) +#define RGBA_IS_CLEAR(rgba) ALPHA_IS_CLEAR((rgba)->alpha) + +typedef struct _GskGLRenderClip +{ + GskRoundedRect rect; + guint is_rectilinear : 1; + guint is_fully_contained : 1; +} GskGLRenderClip; + +#define GDK_ARRAY_NAME clips +#define GDK_ARRAY_TYPE_NAME Clips +#define GDK_ARRAY_ELEMENT_TYPE GskGLRenderClip +#define GDK_ARRAY_BY_VALUE 1 +#define GDK_ARRAY_PREALLOC 16 +#define GDK_ARRAY_NO_MEMSET +#include "gdk/gdkarrayimpl.c" + +typedef struct _GskGLRenderModelview +{ + GskTransform *transform; + float scale_x; + float scale_y; + float dx; + float dy; + float offset_x_before; + float offset_y_before; + graphene_matrix_t matrix; +} GskGLRenderModelview; + +#define GDK_ARRAY_NAME modelviews +#define GDK_ARRAY_TYPE_NAME Modelviews +#define GDK_ARRAY_ELEMENT_TYPE GskGLRenderModelview +#define GDK_ARRAY_BY_VALUE 1 +#define GDK_ARRAY_PREALLOC 16 +#define GDK_ARRAY_NO_MEMSET +#include "gdk/gdkarrayimpl.c" + +struct _GskGLRenderJob +{ + /* The context containing the framebuffer we are drawing to. Generally this + * is the context of the surface but may be a shared context if rendering to + * an offscreen texture such as gsk_gl_renderer_render_texture(). + */ + GdkGLContext *context; + + /* The driver to be used. This is shared among all the renderers on a given + * GdkDisplay and uses the shared GL context to send commands. + */ + GskGLDriver *driver; + + /* The command queue (which is just a faster pointer to the driver's + * command queue. + */ + GskGLCommandQueue *command_queue; + + /* The region that we are clipping. Normalized to a single rectangle region. */ + cairo_region_t *region; + + /* The framebuffer to draw to in the @context GL context. So 0 would be the + * default framebuffer of @context. This is important to note as many other + * operations could be done using objects shared from the command queues + * GL context. + */ + guint framebuffer; + guint default_framebuffer; + + /* The viewport we are using. This state is updated as we process render + * nodes in the specific visitor callbacks. + */ + graphene_rect_t viewport; + + /* The current projection, updated as we process nodes */ + graphene_matrix_t projection; + + /* An array of GskGLRenderModelview updated as nodes are processed. The + * current modelview is the last element. + */ + Modelviews modelview; + + /* An array of GskGLRenderClip updated as nodes are processed. The + * current clip is the last element. + */ + Clips clip; + + /* Our current alpha state as we process nodes */ + float alpha; + + /* Offset (delta x,y) as we process nodes. Occasionally this is merged into + * a transform that is referenced from child transform nodes. + */ + float offset_x; + float offset_y; + + /* The scale we are processing, possibly updated by transforms */ + float scale_x; + float scale_y; + + /* Cached pointers */ + const GskGLRenderClip *current_clip; + const GskGLRenderModelview *current_modelview; + GskGLProgram *current_program; + + guint source_is_glyph_atlas : 1; + + /* In some cases we might want to avoid clearing the framebuffer + * because we're going to render over the existing contents. + */ + guint clear_framebuffer : 1; + + /* Format we want to use for intermediate textures, determined by + * looking at the format of the framebuffer we are rendering on. + */ + int target_format; + + guint offscreen_count; /* if > 0, we're rendering to an offscreen */ +}; + +typedef struct _GskGLRenderOffscreen +{ + /* The bounds to render */ + const graphene_rect_t *bounds; + + /* Return location for texture coordinates */ + struct { + float x; + float y; + float x2; + float y2; + } area; + + /* Return location for texture ID */ + guint texture_id; + gpointer sync; + + /* Whether to force creating a new texture, even if the + * input already is a texture + */ + guint force_offscreen : 1; + guint reset_clip : 1; + guint do_not_cache : 1; + + /* Return location for whether we created a texture */ + guint was_offscreen : 1; + guint has_mipmap : 1; +} GskGLRenderOffscreen; + +static void gsk_gl_render_job_visit_node (GskGLRenderJob *job, + const GskRenderNode *node); +static gboolean gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job, + const GskRenderNode *node, + GskGLRenderOffscreen *offscreen); +static void gsk_gl_render_job_upload_texture (GskGLRenderJob *job, + GdkTexture *texture, + gboolean ensure_mipmap, + GskGLRenderOffscreen *offscreen); + +static inline GskGLRenderClip * +clips_grow_one (Clips *clips) +{ + guint len = clips_get_size (clips); + clips_set_size (clips, len + 1); + return clips_get (clips, len); +} + +static inline GskGLRenderModelview * +modelviews_grow_one (Modelviews *modelviews) +{ + guint len = modelviews_get_size (modelviews); + modelviews_set_size (modelviews, len + 1); + return modelviews_get (modelviews, len); +} + +static inline int +get_target_format (GskGLRenderJob *job, + const GskRenderNode *node) +{ + if (gsk_render_node_get_preferred_depth (node) != GDK_MEMORY_U8) + return job->target_format; + + return GL_RGBA8; +} + +static inline void +init_full_texture_region (GskGLRenderOffscreen *offscreen) +{ + offscreen->area.x = 0; + offscreen->area.y = 0; + offscreen->area.x2 = 1; + offscreen->area.y2 = 1; +} + +static inline gboolean G_GNUC_PURE +node_is_invisible (const GskRenderNode *node) +{ + return node->bounds.size.width == 0.0f || + node->bounds.size.height == 0.0f; +} + +static inline gboolean G_GNUC_PURE +rounded_rect_equal (const GskRoundedRect *r1, + const GskRoundedRect *r2) +{ + return memcmp (r1, r2, sizeof (GskRoundedRect)) == 0; +} + +static inline void +gsk_rounded_rect_shrink_to_minimum (GskRoundedRect *self) +{ + self->bounds.size.width = MAX (self->corner[0].width + self->corner[1].width, + self->corner[3].width + self->corner[2].width); + self->bounds.size.height = MAX (self->corner[0].height + self->corner[3].height, + self->corner[1].height + self->corner[2].height); +} + +static inline gboolean G_GNUC_PURE +node_supports_2d_transform (const GskRenderNode *node) +{ + switch (GSK_RENDER_NODE_TYPE (node)) + { + case GSK_COLOR_NODE: + case GSK_OPACITY_NODE: + case GSK_COLOR_MATRIX_NODE: + case GSK_TEXTURE_NODE: + case GSK_TEXTURE_SCALE_NODE: + case GSK_CROSS_FADE_NODE: + case GSK_LINEAR_GRADIENT_NODE: + case GSK_REPEATING_LINEAR_GRADIENT_NODE: + case GSK_CONIC_GRADIENT_NODE: + case GSK_RADIAL_GRADIENT_NODE: + case GSK_REPEATING_RADIAL_GRADIENT_NODE: + case GSK_DEBUG_NODE: + case GSK_TEXT_NODE: + case GSK_CAIRO_NODE: + case GSK_BLEND_NODE: + case GSK_BLUR_NODE: + case GSK_MASK_NODE: + case GSK_FILL_NODE: + case GSK_STROKE_NODE: + case GSK_SUBSURFACE_NODE: + return TRUE; + + case GSK_SHADOW_NODE: + return node_supports_2d_transform (gsk_shadow_node_get_child (node)); + + case GSK_TRANSFORM_NODE: + return node_supports_2d_transform (gsk_transform_node_get_child (node)); + + case GSK_CONTAINER_NODE: + for (guint i = 0, p = gsk_container_node_get_n_children (node); i < p; i++) + { + if (!node_supports_2d_transform (gsk_container_node_get_child (node, i))) + return FALSE; + } + return TRUE; + + case GSK_BORDER_NODE: + case GSK_INSET_SHADOW_NODE: + case GSK_OUTSET_SHADOW_NODE: + case GSK_REPEAT_NODE: + case GSK_CLIP_NODE: + case GSK_ROUNDED_CLIP_NODE: + case GSK_GL_SHADER_NODE: + case GSK_COMPONENT_TRANSFER_NODE: + case GSK_COPY_NODE: + case GSK_PASTE_NODE: + case GSK_COMPOSITE_NODE: + case GSK_ISOLATION_NODE: + case GSK_DISPLACEMENT_NODE: + case GSK_ARITHMETIC_NODE: + return FALSE; + + case GSK_NOT_A_RENDER_NODE: + default: + g_assert_not_reached (); + } +} + +static inline gboolean G_GNUC_PURE +node_supports_transform (const GskRenderNode *node) +{ + /* Some nodes can't handle non-trivial transforms without being + * rendered to a texture (e.g. rotated clips, etc.). Some however work + * just fine, mostly because they already draw their child to a + * texture and just render the texture manipulated in some way, think + * opacity or color matrix. + */ + + switch (GSK_RENDER_NODE_TYPE (node)) + { + case GSK_COLOR_NODE: + case GSK_OPACITY_NODE: + case GSK_COLOR_MATRIX_NODE: + case GSK_TEXTURE_NODE: + case GSK_CROSS_FADE_NODE: + case GSK_DEBUG_NODE: + case GSK_TEXT_NODE: + case GSK_CAIRO_NODE: + case GSK_BLEND_NODE: + case GSK_BLUR_NODE: + case GSK_MASK_NODE: + case GSK_FILL_NODE: + case GSK_STROKE_NODE: + case GSK_SUBSURFACE_NODE: + return TRUE; + + case GSK_SHADOW_NODE: + return node_supports_transform (gsk_shadow_node_get_child (node)); + + case GSK_TRANSFORM_NODE: + return node_supports_transform (gsk_transform_node_get_child (node)); + + case GSK_CONTAINER_NODE: + case GSK_LINEAR_GRADIENT_NODE: + case GSK_REPEATING_LINEAR_GRADIENT_NODE: + case GSK_RADIAL_GRADIENT_NODE: + case GSK_REPEATING_RADIAL_GRADIENT_NODE: + case GSK_CONIC_GRADIENT_NODE: + case GSK_BORDER_NODE: + case GSK_INSET_SHADOW_NODE: + case GSK_OUTSET_SHADOW_NODE: + case GSK_REPEAT_NODE: + case GSK_CLIP_NODE: + case GSK_ROUNDED_CLIP_NODE: + case GSK_GL_SHADER_NODE: + case GSK_TEXTURE_SCALE_NODE: + case GSK_COMPONENT_TRANSFER_NODE: + case GSK_COPY_NODE: + case GSK_PASTE_NODE: + case GSK_COMPOSITE_NODE: + case GSK_ISOLATION_NODE: + case GSK_DISPLACEMENT_NODE: + case GSK_ARITHMETIC_NODE: + return FALSE; + + case GSK_NOT_A_RENDER_NODE: + + default: + g_assert_not_reached (); + } +} + +static inline gboolean G_GNUC_PURE +color_matrix_modifies_alpha (const GskRenderNode *node) +{ + const graphene_matrix_t *matrix = gsk_color_matrix_node_get_color_matrix (node); + const graphene_vec4_t *offset = gsk_color_matrix_node_get_color_offset (node); + graphene_vec4_t row3; + + if (graphene_vec4_get_w (offset) != 0.0f) + return TRUE; + + graphene_matrix_get_row (matrix, 3, &row3); + + return !graphene_vec4_equal (graphene_vec4_w_axis (), &row3); +} + +static inline void +init_projection_matrix (graphene_matrix_t *projection, + const graphene_rect_t *viewport) +{ + graphene_matrix_init_ortho (projection, + viewport->origin.x, + viewport->origin.x + viewport->size.width, + viewport->origin.y, + viewport->origin.y + viewport->size.height, + ORTHO_NEAR_PLANE, + ORTHO_FAR_PLANE); + graphene_matrix_scale (projection, 1, -1, 1); +} + +static inline float +gsk_gl_render_job_set_alpha (GskGLRenderJob *job, + float alpha) +{ + if (job->alpha != alpha) + { + float ret = job->alpha; + job->alpha = alpha; + job->driver->stamps[UNIFORM_SHARED_ALPHA]++; + return ret; + } + + return alpha; +} + +static void +extract_matrix_metadata (GskGLRenderModelview *modelview) +{ + gsk_transform_to_matrix (modelview->transform, &modelview->matrix); + + switch (gsk_transform_get_category (modelview->transform)) + { + case GSK_TRANSFORM_CATEGORY_IDENTITY: + modelview->scale_x = 1; + modelview->scale_y = 1; + modelview->dx = 0; + modelview->dy = 0; + break; + + case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: + modelview->scale_x = 1; + modelview->scale_y = 1; + gsk_transform_to_translate (modelview->transform, + &modelview->dx, &modelview->dy); + break; + + case GSK_TRANSFORM_CATEGORY_2D_AFFINE: + gsk_transform_to_affine (modelview->transform, + &modelview->scale_x, &modelview->scale_y, + &modelview->dx, &modelview->dy); + break; + + case GSK_TRANSFORM_CATEGORY_2D: + { + float skew_x, skew_y, angle, dx, dy; + + gsk_transform_to_2d_components (modelview->transform, + &skew_x, &skew_y, + &modelview->scale_x, &modelview->scale_y, + &angle, &dx, &dy); + modelview->dx = 0; + modelview->dy = 0; + } + break; + + case GSK_TRANSFORM_CATEGORY_UNKNOWN: + case GSK_TRANSFORM_CATEGORY_ANY: + case GSK_TRANSFORM_CATEGORY_3D: + { + graphene_quaternion_t rotation; + graphene_vec4_t perspective; + graphene_vec3_t translation; + graphene_vec3_t scale; + graphene_vec3_t shear; + + graphene_matrix_decompose (&modelview->matrix, + &translation, + &scale, + &rotation, + &shear, + &perspective); + + modelview->scale_x = graphene_vec3_get_x (&scale); + modelview->scale_y = graphene_vec3_get_y (&scale); + modelview->dx = 0; + modelview->dy = 0; + } + break; + + default: + break; + } +} + +/* takes ownership of transform */ +static void +gsk_gl_render_job_set_modelview (GskGLRenderJob *job, + GskTransform *transform) +{ + GskGLRenderModelview *modelview; + + g_assert (job != NULL); + + job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++; + + modelview = modelviews_grow_one (&job->modelview); + + modelview->transform = transform; + + modelview->offset_x_before = job->offset_x; + modelview->offset_y_before = job->offset_y; + + extract_matrix_metadata (modelview); + + job->offset_x = 0; + job->offset_y = 0; + job->scale_x = modelview->scale_x; + job->scale_y = modelview->scale_y; + + job->current_modelview = modelview; +} + +/* doesn't take ownership of transform */ +static void +gsk_gl_render_job_push_modelview (GskGLRenderJob *job, + GskTransform *transform) +{ + GskGLRenderModelview *modelview; + + g_assert (job != NULL); + g_assert (transform != NULL); + + job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++; + + modelview = modelviews_grow_one (&job->modelview); + + if G_LIKELY (modelviews_get_size (&job->modelview) > 1) + { + GskGLRenderModelview *last = job->modelview.end - 2; + GskTransform *t = NULL; + + /* Multiply given matrix with our previous modelview */ + t = gsk_transform_translate (gsk_transform_ref (last->transform), + &(graphene_point_t) { + job->offset_x, + job->offset_y + }); + t = gsk_transform_transform (t, transform); + modelview->transform = t; + } + else + { + modelview->transform = gsk_transform_ref (transform); + } + + modelview->offset_x_before = job->offset_x; + modelview->offset_y_before = job->offset_y; + + extract_matrix_metadata (modelview); + + job->offset_x = 0; + job->offset_y = 0; + job->scale_x = modelview->scale_x; + job->scale_y = modelview->scale_y; + + job->current_modelview = modelview; +} + +static void +gsk_gl_render_job_pop_modelview (GskGLRenderJob *job) +{ + const GskGLRenderModelview *head; + + g_assert (job != NULL); + g_assert (modelviews_get_size (&job->modelview) > 0); + + job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++; + + head = job->current_modelview; + + job->offset_x = head->offset_x_before; + job->offset_y = head->offset_y_before; + + gsk_transform_unref (head->transform); + + job->modelview.end--; + + if (modelviews_get_size (&job->modelview) >= 1) + { + head = job->modelview.end - 1; + + job->scale_x = head->scale_x; + job->scale_y = head->scale_y; + + job->current_modelview = head; + } + else + { + job->current_modelview = NULL; + } +} + +static void +gsk_gl_render_job_push_clip (GskGLRenderJob *job, + const GskRoundedRect *rect) +{ + GskGLRenderClip *clip; + + g_assert (job != NULL); + g_assert (rect != NULL); + + job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++; + + clip = clips_grow_one (&job->clip); + + memcpy (&clip->rect, rect, sizeof *rect); + clip->is_rectilinear = gsk_rounded_rect_is_rectilinear (rect); + clip->is_fully_contained = FALSE; + + job->current_clip = clip; +} + +static void +gsk_gl_render_job_push_contained_clip (GskGLRenderJob *job) +{ + GskGLRenderClip *clip; + GskGLRenderClip *old_clip; + + g_assert (job != NULL); + g_assert (clips_get_size (&job->clip) > 0); + + job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++; + + clip = clips_grow_one (&job->clip); + old_clip = clips_get (&job->clip, clips_get_size (&job->clip) - 2); + + memcpy (&clip->rect.bounds, &old_clip->rect.bounds, sizeof (graphene_rect_t)); + memset (clip->rect.corner, 0, sizeof clip->rect.corner); + clip->is_rectilinear = TRUE; + clip->is_fully_contained = TRUE; + + job->current_clip = clip; +} + +static void +gsk_gl_render_job_pop_clip (GskGLRenderJob *job) +{ + g_assert (job != NULL); + g_assert (clips_get_size (&job->clip) > 0); + + job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++; + job->current_clip--; + job->clip.end--; +} + +static inline void +gsk_gl_render_job_offset (GskGLRenderJob *job, + float offset_x, + float offset_y) +{ + if (offset_x || offset_y) + { + job->offset_x += offset_x; + job->offset_y += offset_y; + } +} + +static inline void +gsk_gl_render_job_set_projection (GskGLRenderJob *job, + const graphene_matrix_t *projection) +{ + memcpy (&job->projection, projection, sizeof job->projection); + job->driver->stamps[UNIFORM_SHARED_PROJECTION]++; +} + +static inline void +gsk_gl_render_job_set_projection_from_rect (GskGLRenderJob *job, + const graphene_rect_t *rect, + graphene_matrix_t *prev_projection) +{ + if (prev_projection) + memcpy (prev_projection, &job->projection, sizeof *prev_projection); + init_projection_matrix (&job->projection, rect); + job->driver->stamps[UNIFORM_SHARED_PROJECTION]++; +} + +static inline void +gsk_gl_render_job_set_projection_for_size (GskGLRenderJob *job, + float width, + float height, + graphene_matrix_t *prev_projection) +{ + if (prev_projection) + memcpy (prev_projection, &job->projection, sizeof *prev_projection); + graphene_matrix_init_ortho (&job->projection, 0, width, 0, height, ORTHO_NEAR_PLANE, ORTHO_FAR_PLANE); + graphene_matrix_scale (&job->projection, 1, -1, 1); + job->driver->stamps[UNIFORM_SHARED_PROJECTION]++; +} + +static inline void +gsk_gl_render_job_set_viewport (GskGLRenderJob *job, + const graphene_rect_t *viewport, + graphene_rect_t *prev_viewport) +{ + if (prev_viewport) + memcpy (prev_viewport, &job->viewport, sizeof *prev_viewport); + memcpy (&job->viewport, viewport, sizeof job->viewport); + job->driver->stamps[UNIFORM_SHARED_VIEWPORT]++; +} + +static inline void +gsk_gl_render_job_set_viewport_for_size (GskGLRenderJob *job, + float width, + float height, + graphene_rect_t *prev_viewport) +{ + if (prev_viewport) + memcpy (prev_viewport, &job->viewport, sizeof *prev_viewport); + job->viewport.origin.x = 0; + job->viewport.origin.y = 0; + job->viewport.size.width = width; + job->viewport.size.height = height; + job->driver->stamps[UNIFORM_SHARED_VIEWPORT]++; +} + +static inline void +gsk_gl_render_job_transform_bounds (GskGLRenderJob *job, + const graphene_rect_t *rect, + graphene_rect_t *out_rect) +{ + GskTransform *transform; + GskTransformCategory category; + + g_assert (job != NULL); + g_assert (modelviews_get_size (&job->modelview) > 0); + g_assert (rect != NULL); + g_assert (out_rect != NULL); + + transform = job->current_modelview->transform; + category = gsk_transform_get_category (transform); + + /* Our most common transform is 2d-affine, so inline it. + * Both identity and 2d-translate are virtually unseen here. + */ + if G_LIKELY (category >= GSK_TRANSFORM_CATEGORY_2D_AFFINE) + { + float scale_x = job->current_modelview->scale_x; + float scale_y = job->current_modelview->scale_y; + float dx = job->current_modelview->dx; + float dy = job->current_modelview->dy; + + /* Init directly into out rect */ + out_rect->origin.x = ((rect->origin.x + job->offset_x) * scale_x) + dx; + out_rect->origin.y = ((rect->origin.y + job->offset_y) * scale_y) + dy; + out_rect->size.width = rect->size.width * scale_x; + out_rect->size.height = rect->size.height * scale_y; + + /* Normalize in place */ + if (out_rect->size.width < 0.f) + { + float size = fabsf (out_rect->size.width); + + out_rect->origin.x -= size; + out_rect->size.width = size; + } + + if (out_rect->size.height < 0.f) + { + float size = fabsf (out_rect->size.height); + + out_rect->origin.y -= size; + out_rect->size.height = size; + } + } + else + { + graphene_rect_t r; + + r.origin.x = rect->origin.x + job->offset_x; + r.origin.y = rect->origin.y + job->offset_y; + r.size.width = rect->size.width; + r.size.height = rect->size.height; + + gsk_transform_transform_bounds (transform, &r, out_rect); + } +} + +static inline void +gsk_gl_render_job_untransform_bounds (GskGLRenderJob *job, + const graphene_rect_t *rect, + graphene_rect_t *out_rect) +{ + GskTransform *transform; + + transform = gsk_transform_invert (gsk_transform_ref (job->current_modelview->transform)); + + gsk_transform_transform_bounds (transform, rect, out_rect); + + out_rect->origin.x -= job->offset_x; + out_rect->origin.y -= job->offset_y; + + gsk_transform_unref (transform); +} + +static inline void +gsk_gl_render_job_translate_rounded_rect (GskGLRenderJob *job, + const GskRoundedRect *rect, + GskRoundedRect *out_rect) + { + out_rect->bounds.origin.x = job->offset_x + rect->bounds.origin.x; + out_rect->bounds.origin.y = job->offset_y + rect->bounds.origin.y; + out_rect->bounds.size.width = rect->bounds.size.width; + out_rect->bounds.size.height = rect->bounds.size.height; + memcpy (out_rect->corner, rect->corner, sizeof rect->corner); +} + +static inline void +rounded_rect_scale_corners (const GskRoundedRect *rect, + GskRoundedRect *out_rect, + float scale_x, + float scale_y) +{ + for (guint i = 0; i < G_N_ELEMENTS (out_rect->corner); i++) + { + out_rect->corner[i].width = rect->corner[i].width * fabsf (scale_x); + out_rect->corner[i].height = rect->corner[i].height * fabsf (scale_y); + } + + if (scale_x < 0) + { + graphene_size_t p; + + p = out_rect->corner[GSK_CORNER_TOP_LEFT]; + out_rect->corner[GSK_CORNER_TOP_LEFT] = out_rect->corner[GSK_CORNER_TOP_RIGHT]; + out_rect->corner[GSK_CORNER_TOP_RIGHT] = p; + p = out_rect->corner[GSK_CORNER_BOTTOM_LEFT]; + out_rect->corner[GSK_CORNER_BOTTOM_LEFT] = out_rect->corner[GSK_CORNER_BOTTOM_RIGHT]; + out_rect->corner[GSK_CORNER_BOTTOM_RIGHT] = p; + } + + if (scale_y < 0) + { + graphene_size_t p; + + p = out_rect->corner[GSK_CORNER_TOP_LEFT]; + out_rect->corner[GSK_CORNER_TOP_LEFT] = out_rect->corner[GSK_CORNER_BOTTOM_LEFT]; + out_rect->corner[GSK_CORNER_BOTTOM_LEFT] = p; + p = out_rect->corner[GSK_CORNER_TOP_RIGHT]; + out_rect->corner[GSK_CORNER_TOP_RIGHT] = out_rect->corner[GSK_CORNER_BOTTOM_RIGHT]; + out_rect->corner[GSK_CORNER_BOTTOM_RIGHT] = p; + } +} + +static inline void +gsk_gl_render_job_transform_rounded_rect (GskGLRenderJob *job, + const GskRoundedRect *rect, + GskRoundedRect *out_rect) +{ + gsk_gl_render_job_transform_bounds (job, &rect->bounds, &out_rect->bounds); + rounded_rect_scale_corners (rect, out_rect, job->scale_x, job->scale_y); +} + +static inline void +rounded_rect_get_inner (const GskRoundedRect *rect, + graphene_rect_t *inner) +{ + float left = MAX (rect->corner[GSK_CORNER_TOP_LEFT].width, rect->corner[GSK_CORNER_BOTTOM_LEFT].width); + float right = MAX (rect->corner[GSK_CORNER_TOP_RIGHT].width, rect->corner[GSK_CORNER_BOTTOM_RIGHT].width); + float top = MAX (rect->corner[GSK_CORNER_TOP_LEFT].height, rect->corner[GSK_CORNER_TOP_RIGHT].height); + float bottom = MAX (rect->corner[GSK_CORNER_BOTTOM_LEFT].height, rect->corner[GSK_CORNER_BOTTOM_RIGHT].height); + + inner->origin.x = rect->bounds.origin.x + left; + inner->size.width = rect->bounds.size.width - (left + right); + + inner->origin.y = rect->bounds.origin.y + top; + inner->size.height = rect->bounds.size.height - (top + bottom); +} + +static inline gboolean +interval_contains (float p1, float w1, + float p2, float w2) +{ + if (p2 < p1) + return FALSE; + + if (p2 + w2 > p1 + w1) + return FALSE; + + return TRUE; +} + +static inline gboolean +gsk_gl_render_job_update_clip (GskGLRenderJob *job, + const graphene_rect_t *bounds, + gboolean *pushed_clip) +{ + graphene_rect_t transformed_bounds; + gboolean no_clip = FALSE; + gboolean rect_clip = FALSE; + + *pushed_clip = FALSE; + + if (job->current_clip->is_fully_contained) + { + /* Already fully contained - no further checks needed */ + return TRUE; + } + + gsk_gl_render_job_transform_bounds (job, bounds, &transformed_bounds); + + if (!gsk_rect_intersects (&job->current_clip->rect.bounds, &transformed_bounds)) + { + /* Completely clipped away */ + return FALSE; + } + + if (job->current_clip->is_rectilinear) + { + if (gsk_rect_contains_rect (&job->current_clip->rect.bounds, &transformed_bounds)) + no_clip = TRUE; + else + rect_clip = TRUE; + } + else if (gsk_rounded_rect_contains_rect (&job->current_clip->rect, &transformed_bounds)) + { + no_clip = TRUE; + } + else + { + graphene_rect_t inner; + + rounded_rect_get_inner (&job->current_clip->rect, &inner); + + if (interval_contains (inner.origin.x, inner.size.width, + transformed_bounds.origin.x, transformed_bounds.size.width) || + interval_contains (inner.origin.y, inner.size.height, + transformed_bounds.origin.y, transformed_bounds.size.height)) + rect_clip = TRUE; + } + + if (no_clip) + { + /* This node is completely contained inside the clip. + * Record this fact on the clip stack, so we don't do more work + * for child nodes. + */ + + gsk_gl_render_job_push_contained_clip (job); + + *pushed_clip = TRUE; + } + else if (rect_clip && !job->current_clip->is_rectilinear) + { + graphene_rect_t rect; + + /* The clip gets simpler for this node */ + + gsk_rect_intersection (&job->current_clip->rect.bounds, &transformed_bounds, &rect); + gsk_gl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT_FROM_RECT (rect)); + + *pushed_clip = TRUE; + } + + return TRUE; +} + +static inline void +rgba_to_half (const GdkRGBA *rgba, + guint16 h[4]) +{ + float_to_half4 ((const float *)rgba, h); +} + +/* fill_vertex_data */ +static void +gsk_gl_render_job_draw_coords (GskGLRenderJob *job, + float min_x, + float min_y, + float max_x, + float max_y, + float min_u, + float min_v, + float max_u, + float max_v, + guint16 c[4]) +{ + GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (job->command_queue); + + vertices[0] = (GskGLDrawVertex) { .position = { min_x, min_y }, .uv = { min_u, min_v }, .color = { c[0], c[1], c[2], c[3] } }; + vertices[1] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { min_u, max_v }, .color = { c[0], c[1], c[2], c[3] } }; + vertices[2] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { max_u, min_v }, .color = { c[0], c[1], c[2], c[3] } }; + vertices[3] = (GskGLDrawVertex) { .position = { max_x, max_y }, .uv = { max_u, max_v }, .color = { c[0], c[1], c[2], c[3] } }; + vertices[4] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { min_u, max_v }, .color = { c[0], c[1], c[2], c[3] } }; + vertices[5] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { max_u, min_v }, .color = { c[0], c[1], c[2], c[3] } }; +} + +/* load_vertex_data_with_region */ +static inline void +gsk_gl_render_job_draw_offscreen_with_color (GskGLRenderJob *job, + const graphene_rect_t *bounds, + const GskGLRenderOffscreen *offscreen, + guint16 color[4]) +{ + float min_x = job->offset_x + bounds->origin.x; + float min_y = job->offset_y + bounds->origin.y; + float max_x = min_x + bounds->size.width; + float max_y = min_y + bounds->size.height; + float y1 = offscreen->was_offscreen ? offscreen->area.y2 : offscreen->area.y; + float y2 = offscreen->was_offscreen ? offscreen->area.y : offscreen->area.y2; + + gsk_gl_render_job_draw_coords (job, + min_x, min_y, max_x, max_y, + offscreen->area.x, y1, offscreen->area.x2, y2, + color); +} + +static inline void +gsk_gl_render_job_draw_offscreen (GskGLRenderJob *job, + const graphene_rect_t *bounds, + const GskGLRenderOffscreen *offscreen) +{ + gsk_gl_render_job_draw_offscreen_with_color (job, bounds, offscreen, + (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO }); +} + +/* load_float_vertex_data */ +static inline void +gsk_gl_render_job_draw_with_color (GskGLRenderJob *job, + float x, + float y, + float width, + float height, + guint16 color[4]) +{ + float min_x = job->offset_x + x; + float min_y = job->offset_y + y; + float max_x = min_x + width; + float max_y = min_y + height; + + gsk_gl_render_job_draw_coords (job, min_x, min_y, max_x, max_y, 0, 0, 1, 1, color); +} + +static inline void +gsk_gl_render_job_draw (GskGLRenderJob *job, + float x, + float y, + float width, + float height) +{ + gsk_gl_render_job_draw_with_color (job, x, y, width, height, + (guint16[]) { FP_ZERO, FP_ZERO, FP_ZERO, FP_ZERO }); +} + +/* load_vertex_data */ +static inline void +gsk_gl_render_job_draw_rect_with_color (GskGLRenderJob *job, + const graphene_rect_t *bounds, + guint16 color[4]) +{ + gsk_gl_render_job_draw_with_color (job, + bounds->origin.x, + bounds->origin.y, + bounds->size.width, + bounds->size.height, + color); +} +static inline void +gsk_gl_render_job_draw_rect (GskGLRenderJob *job, + const graphene_rect_t *bounds) +{ + gsk_gl_render_job_draw (job, + bounds->origin.x, + bounds->origin.y, + bounds->size.width, + bounds->size.height); +} + +/* load_offscreen_vertex_data */ +static inline void +gsk_gl_render_job_draw_offscreen_rect (GskGLRenderJob *job, + const graphene_rect_t *bounds) +{ + float min_x = job->offset_x + bounds->origin.x; + float min_y = job->offset_y + bounds->origin.y; + float max_x = min_x + bounds->size.width; + float max_y = min_y + bounds->size.height; + guint16 color[4] = { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO }; + + gsk_gl_render_job_draw_coords (job, + min_x, min_y, max_x, max_y, + 0, 1, 1, 0, + color); +} + +static inline gboolean +gsk_gl_render_job_begin_draw (GskGLRenderJob *job, + GskGLProgram *program) +{ + job->current_program = program; + + if (!gsk_gl_command_queue_begin_draw (job->command_queue, + program->program_info, + job->viewport.size.width, + job->viewport.size.height)) + return FALSE; + + gsk_gl_uniform_state_set4fv (program->uniforms, + program->program_info, + UNIFORM_SHARED_VIEWPORT, + job->driver->stamps[UNIFORM_SHARED_VIEWPORT], + 1, + (const float *)&job->viewport); + + gsk_gl_uniform_state_set_matrix (program->uniforms, + program->program_info, + UNIFORM_SHARED_MODELVIEW, + job->driver->stamps[UNIFORM_SHARED_MODELVIEW], + &job->current_modelview->matrix); + + gsk_gl_uniform_state_set_matrix (program->uniforms, + program->program_info, + UNIFORM_SHARED_PROJECTION, + job->driver->stamps[UNIFORM_SHARED_PROJECTION], + &job->projection); + + gsk_gl_uniform_state_set_rounded_rect (program->uniforms, + program->program_info, + UNIFORM_SHARED_CLIP_RECT, + job->driver->stamps[UNIFORM_SHARED_CLIP_RECT], + &job->current_clip->rect); + + gsk_gl_uniform_state_set1f (program->uniforms, + program->program_info, + UNIFORM_SHARED_ALPHA, + job->driver->stamps[UNIFORM_SHARED_ALPHA], + job->alpha); + + return TRUE; +} + +#define CHOOSE_PROGRAM(job,name) \ + (job->current_clip->is_fully_contained \ + ? job->driver->name ## _no_clip \ + : (job->current_clip->is_rectilinear \ + ? job->driver->name ## _rect_clip \ + : job->driver->name)) + +static inline void +gsk_gl_render_job_split_draw (GskGLRenderJob *job) +{ + gsk_gl_command_queue_split_draw (job->command_queue); +} + +static inline void +gsk_gl_render_job_end_draw (GskGLRenderJob *job) +{ + gsk_gl_command_queue_end_draw (job->command_queue); + + job->current_program = NULL; +} + +static inline void +gsk_gl_render_job_visit_as_fallback (GskGLRenderJob *job, + const GskRenderNode *node) +{ + float scale_x = job->scale_x; + float scale_y = job->scale_y; + int surface_width = ceilf (node->bounds.size.width * fabsf (scale_x)); + int surface_height = ceilf (node->bounds.size.height * fabsf (scale_y)); + GdkTexture *texture; + cairo_surface_t *surface; + cairo_surface_t *rendered_surface; + cairo_t *cr; + int texture_id; + GskTextureKey key; + + if (surface_width <= 0 || surface_height <= 0) + return; + + key.pointer = node; + key.pointer_is_child = FALSE; + key.scale_x = scale_x; + key.scale_y = scale_y; + + texture_id = gsk_gl_driver_lookup_texture (job->driver, &key, NULL); + + if (texture_id != 0) + goto done; + + /* We first draw the recording surface on an image surface, + * just because the scaleY(-1) later otherwise screws up the + * rendering... */ + { + rendered_surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + surface_width, + surface_height); + + cairo_surface_set_device_scale (rendered_surface, fabsf (scale_x), fabsf (scale_y)); + cr = cairo_create (rendered_surface); + + cairo_save (cr); + cairo_translate (cr, - node->bounds.origin.x, - node->bounds.origin.y); + /* Render nodes don't modify state, so casting away the const is fine here */ + gsk_render_node_draw_fallback ((GskRenderNode *)node, cr); + cairo_restore (cr); + cairo_destroy (cr); + } + + surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, + surface_width, + surface_height); + cairo_surface_set_device_scale (surface, fabsf (scale_x), fabsf (scale_y)); + cr = cairo_create (surface); + + /* We draw upside down here, so it matches what GL does. */ + cairo_save (cr); + cairo_scale (cr, scale_x < 0 ? -1 : 1, scale_y < 0 ? 1 : -1); + cairo_translate (cr, scale_x < 0 ? - surface_width / fabsf (scale_x) : 0, + scale_y < 0 ? 0 : - surface_height / fabsf (scale_y)); + cairo_set_source_surface (cr, rendered_surface, 0, 0); + cairo_rectangle (cr, 0, 0, surface_width / fabsf (scale_x), surface_height / fabsf (scale_y)); + cairo_fill (cr); + cairo_restore (cr); + cairo_destroy (cr); + + /* Create texture to upload */ + texture = gdk_texture_new_for_surface (surface); + texture_id = gsk_gl_driver_load_texture (job->driver, texture, FALSE); + + if (gdk_gl_context_has_feature (job->command_queue->context, GDK_GL_FEATURE_DEBUG)) + gdk_gl_context_label_object_printf (job->command_queue->context, GL_TEXTURE, texture_id, + "Fallback %s %d", + g_type_name_from_instance ((GTypeInstance *) node), + texture_id); + + g_object_unref (texture); + cairo_surface_destroy (surface); + cairo_surface_destroy (rendered_surface); + + gsk_gl_driver_cache_texture (job->driver, &key, texture_id); + +done: + if (scale_x < 0 || scale_y < 0) + { + GskTransform *transform = gsk_transform_translate (gsk_transform_scale (NULL, scale_x < 0 ? -1 : 1, scale_y < 0 ? -1 : 1), + &GRAPHENE_POINT_INIT (scale_x < 0 ? - (node->bounds.size.width + 2 * node->bounds.origin.x) : 0, + scale_y < 0 ? - (node->bounds.size.height + 2 * node->bounds.origin.y) : 0)); + gsk_gl_render_job_push_modelview (job, transform); + gsk_transform_unref (transform); + } + + if (!gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + goto out; + + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_offscreen_rect (job, &node->bounds); + gsk_gl_render_job_end_draw (job); + +out: + if (scale_x < 0 || scale_y < 0) + gsk_gl_render_job_pop_modelview (job); +} + +static guint +blur_offscreen (GskGLRenderJob *job, + GskGLRenderOffscreen *offscreen, + int texture_to_blur_width, + int texture_to_blur_height, + float blur_radius_x, + float blur_radius_y) +{ + const GskRoundedRect new_clip = GSK_ROUNDED_RECT_INIT (0, 0, texture_to_blur_width, texture_to_blur_height); + GskGLRenderTarget *pass1; + GskGLRenderTarget *pass2; + graphene_matrix_t prev_projection; + graphene_rect_t prev_viewport; + guint prev_fbo; + + g_assert (blur_radius_x > 0); + g_assert (blur_radius_y > 0); + g_assert (offscreen->texture_id > 0); + g_assert (offscreen->area.x2 > offscreen->area.x); + g_assert (offscreen->area.y2 > offscreen->area.y); + + if (!gsk_gl_driver_create_render_target (job->driver, + MAX (texture_to_blur_width, 1), + MAX (texture_to_blur_height, 1), + job->target_format, + &pass1)) + return 0; + + if (texture_to_blur_width <= 0 || texture_to_blur_height <= 0) + return gsk_gl_driver_release_render_target (job->driver, pass1, FALSE); + + if (!gsk_gl_driver_create_render_target (job->driver, + texture_to_blur_width, + texture_to_blur_height, + job->target_format, + &pass2)) + return gsk_gl_driver_release_render_target (job->driver, pass1, FALSE); + + gsk_gl_render_job_set_viewport (job, &new_clip.bounds, &prev_viewport); + gsk_gl_render_job_set_projection_from_rect (job, &new_clip.bounds, &prev_projection); + gsk_gl_render_job_set_modelview (job, NULL); + gsk_gl_render_job_push_clip (job, &new_clip); + + /* Bind new framebuffer and clear it */ + prev_fbo = gsk_gl_command_queue_bind_framebuffer (job->command_queue, pass1->framebuffer_id); + gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport); + + /* Begin drawing the first horizontal pass, using offscreen as the + * source texture for the program. + */ + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blur))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen->texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_program_set_uniform1f (job->current_program, + UNIFORM_BLUR_RADIUS, 0, + blur_radius_x); + gsk_gl_program_set_uniform2f (job->current_program, + UNIFORM_BLUR_SIZE, 0, + texture_to_blur_width, + texture_to_blur_height); + gsk_gl_program_set_uniform2f (job->current_program, + UNIFORM_BLUR_DIR, 0, + 1, 0); + gsk_gl_render_job_draw_coords (job, + 0, 0, texture_to_blur_width, texture_to_blur_height, + 0, 1, 1, 0, + (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO }); + gsk_gl_render_job_end_draw (job); + } + + /* Bind second pass framebuffer and clear it */ + gsk_gl_command_queue_bind_framebuffer (job->command_queue, pass2->framebuffer_id); + gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport); + + /* Draw using blur program with first pass as source texture */ + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blur))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + pass1->texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_program_set_uniform1f (job->current_program, + UNIFORM_BLUR_RADIUS, 0, + blur_radius_y); + gsk_gl_program_set_uniform2f (job->current_program, + UNIFORM_BLUR_SIZE, 0, + texture_to_blur_width, + texture_to_blur_height); + gsk_gl_program_set_uniform2f (job->current_program, + UNIFORM_BLUR_DIR, 0, + 0, 1); + gsk_gl_render_job_draw_coords (job, + 0, 0, texture_to_blur_width, texture_to_blur_height, + 0, 1, 1, 0, + (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO }); + gsk_gl_render_job_end_draw (job); + } + + gsk_gl_render_job_pop_modelview (job); + gsk_gl_render_job_pop_clip (job); + gsk_gl_render_job_set_viewport (job, &prev_viewport, NULL); + gsk_gl_render_job_set_projection (job, &prev_projection); + gsk_gl_command_queue_bind_framebuffer (job->command_queue, prev_fbo); + + gsk_gl_driver_release_render_target (job->driver, pass1, TRUE); + + return gsk_gl_driver_release_render_target (job->driver, pass2, FALSE); +} + +static void +blur_node (GskGLRenderJob *job, + GskGLRenderOffscreen *offscreen, + const GskRenderNode *node, + float blur_radius, + float *min_x, + float *max_x, + float *min_y, + float *max_y) +{ + const float blur_extra = blur_radius * 2.0; /* 2.0 = shader radius_multiplier */ + const float half_blur_extra = (blur_extra / 2.0); + float scale_x = job->scale_x; + float scale_y = job->scale_y; + float texture_width; + float texture_height; + + g_assert (blur_radius > 0); + + /* Increase texture size for the given blur radius and scale it */ + texture_width = ceilf ((node->bounds.size.width + blur_extra)); + texture_height = ceilf ((node->bounds.size.height + blur_extra)); + + /* Only blur this if the out region has no texture id yet */ + if (offscreen->texture_id == 0) + { + const graphene_rect_t bounds = GRAPHENE_RECT_INIT (node->bounds.origin.x - half_blur_extra, + node->bounds.origin.y - half_blur_extra, + texture_width, texture_height); + + offscreen->bounds = &bounds; + offscreen->reset_clip = TRUE; + offscreen->force_offscreen = TRUE; + + if (!gsk_gl_render_job_visit_node_with_offscreen (job, node, offscreen)) + g_assert_not_reached (); + + /* Ensure that we actually got a real texture_id */ + g_assert (offscreen->texture_id != 0); + + offscreen->texture_id = blur_offscreen (job, + offscreen, + texture_width * fabsf (scale_x), + texture_height * fabsf (scale_y), + blur_radius * fabsf (scale_x), + blur_radius * fabsf (scale_y)); + init_full_texture_region (offscreen); + } + + *min_x = job->offset_x + node->bounds.origin.x - half_blur_extra; + *max_x = job->offset_x + node->bounds.origin.x + node->bounds.size.width + half_blur_extra; + *min_y = job->offset_y + node->bounds.origin.y - half_blur_extra; + *max_y = job->offset_y + node->bounds.origin.y + node->bounds.size.height + half_blur_extra; +} + +#define ATLAS_SIZE 512 + +static void +get_color_node_color_as_srgb (const GskRenderNode *node, + GdkRGBA *rgba) +{ + const GdkColor *color = gsk_color_node_get_gdk_color (node); + gdk_color_to_float (color, GDK_COLOR_STATE_SRGB, (float *) rgba); +} + +static inline void +gsk_gl_render_job_visit_color_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + GdkRGBA rgba; + guint16 color[4]; + GskGLProgram *program; + GskGLCommandBatch *batch; + + get_color_node_color_as_srgb (node, &rgba); + if (RGBA_IS_CLEAR (&rgba)) + return; + + rgba_to_half (&rgba, color); + + /* Avoid switching away from the coloring program for + * rendering a solid color. + */ + program = CHOOSE_PROGRAM (job, coloring); + batch = gsk_gl_command_queue_get_batch (job->command_queue); + + /* Limit the size, or we end up with a coordinate overflow somewhere. */ + if (job->source_is_glyph_atlas && + node->bounds.size.width < 300 && + node->bounds.size.height < 300 && + batch->any.kind == GSK_GL_COMMAND_KIND_DRAW && + batch->any.program == program->id) + { + GskGLRenderOffscreen offscreen = {0}; + + if (gsk_gl_render_job_begin_draw (job, program)) + { + /* The top left few pixels in our atlases are always + * solid white, so we can use it here, without + * having to choose any particular atlas texture. + */ + offscreen.was_offscreen = FALSE; + offscreen.area.x = 1.f / ATLAS_SIZE; + offscreen.area.y = 1.f / ATLAS_SIZE; + offscreen.area.x2 = 2.f / ATLAS_SIZE; + offscreen.area.y2 = 2.f / ATLAS_SIZE; + + gsk_gl_render_job_draw_offscreen_with_color (job, + &node->bounds, + &offscreen, + color); + + gsk_gl_render_job_end_draw (job); + } + } + else + { + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color))) + { + gsk_gl_render_job_draw_rect_with_color (job, &node->bounds, color); + gsk_gl_render_job_end_draw (job); + } + } +} + +static inline void +gsk_gl_render_job_visit_linear_gradient_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskColorStop *stops = gsk_linear_gradient_node_get_color_stops (node, NULL); + const graphene_point_t *start = gsk_linear_gradient_node_get_start (node); + const graphene_point_t *end = gsk_linear_gradient_node_get_end (node); + int n_color_stops = gsk_linear_gradient_node_get_n_color_stops (node); + gboolean repeat = GSK_RENDER_NODE_TYPE (node) == GSK_REPEATING_LINEAR_GRADIENT_NODE; + float x1 = job->offset_x + start->x; + float x2 = job->offset_x + end->x; + float y1 = job->offset_y + start->y; + float y2 = job->offset_y + end->y; + + g_assert (n_color_stops < MAX_GRADIENT_STOPS); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, linear_gradient))) + { + gsk_gl_program_set_uniform1i (job->current_program, + UNIFORM_LINEAR_GRADIENT_NUM_COLOR_STOPS, 0, + n_color_stops); + gsk_gl_program_set_uniform1fv (job->current_program, + UNIFORM_LINEAR_GRADIENT_COLOR_STOPS, 0, + n_color_stops * 5, + (const float *)stops); + gsk_gl_program_set_uniform4f (job->current_program, + UNIFORM_LINEAR_GRADIENT_POINTS, 0, + x1, y1, x2 - x1, y2 - y1); + gsk_gl_program_set_uniform1i (job->current_program, + UNIFORM_LINEAR_GRADIENT_REPEAT, 0, + repeat); + gsk_gl_render_job_draw_rect (job, &node->bounds); + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_conic_gradient_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + static const float scale = 0.5f * M_1_PI; + + const GskColorStop *stops = gsk_conic_gradient_node_get_color_stops (node, NULL); + const graphene_point_t *center = gsk_conic_gradient_node_get_center (node); + int n_color_stops = gsk_conic_gradient_node_get_n_color_stops (node); + float angle = gsk_conic_gradient_node_get_angle (node); + float bias = angle * scale + 2.0f; + + g_assert (n_color_stops < MAX_GRADIENT_STOPS); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, conic_gradient))) + { + gsk_gl_program_set_uniform1i (job->current_program, + UNIFORM_CONIC_GRADIENT_NUM_COLOR_STOPS, 0, + n_color_stops); + gsk_gl_program_set_uniform1fv (job->current_program, + UNIFORM_CONIC_GRADIENT_COLOR_STOPS, 0, + n_color_stops * 5, + (const float *)stops); + gsk_gl_program_set_uniform4f (job->current_program, + UNIFORM_CONIC_GRADIENT_GEOMETRY, 0, + job->offset_x + center->x, + job->offset_y + center->y, + scale, + bias); + gsk_gl_render_job_draw_rect (job, &node->bounds); + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_radial_gradient_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + int n_color_stops = gsk_radial_gradient_node_get_n_color_stops (node); + const GskColorStop *stops = gsk_radial_gradient_node_get_color_stops (node, NULL); + const graphene_point_t *center = gsk_radial_gradient_node_get_center (node); + float start = gsk_radial_gradient_node_get_start (node); + float end = gsk_radial_gradient_node_get_end (node); + float hradius = gsk_radial_gradient_node_get_hradius (node); + float vradius = gsk_radial_gradient_node_get_vradius (node); + gboolean repeat = GSK_RENDER_NODE_TYPE (node) == GSK_REPEATING_RADIAL_GRADIENT_NODE; + float scale = 1.0f / (end - start); + float bias = -start * scale; + + g_assert (n_color_stops < MAX_GRADIENT_STOPS); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, radial_gradient))) + { + gsk_gl_program_set_uniform1i (job->current_program, + UNIFORM_RADIAL_GRADIENT_NUM_COLOR_STOPS, 0, + n_color_stops); + gsk_gl_program_set_uniform1fv (job->current_program, + UNIFORM_RADIAL_GRADIENT_COLOR_STOPS, 0, + n_color_stops * 5, + (const float *)stops); + gsk_gl_program_set_uniform1i (job->current_program, + UNIFORM_RADIAL_GRADIENT_REPEAT, 0, + repeat); + gsk_gl_program_set_uniform2f (job->current_program, + UNIFORM_RADIAL_GRADIENT_RANGE, 0, + scale, bias); + gsk_gl_program_set_uniform4f (job->current_program, + UNIFORM_RADIAL_GRADIENT_GEOMETRY, 0, + job->offset_x + center->x, + job->offset_y + center->y, + 1.0f / (hradius * job->scale_x), + 1.0f / (vradius * job->scale_y)); + gsk_gl_render_job_draw_rect (job, &node->bounds); + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_clipped_child (GskGLRenderJob *job, + const GskRenderNode *child, + const graphene_rect_t *clip) +{ + graphene_rect_t transformed_clip; + GskRoundedRect intersection; + GskRoundedRectIntersection result; + + gsk_gl_render_job_transform_bounds (job, clip, &transformed_clip); + + if (job->current_clip->is_rectilinear) + { + memset (&intersection.corner, 0, sizeof intersection.corner); + gsk_rect_intersection (&transformed_clip, + &job->current_clip->rect.bounds, + &intersection.bounds); + + gsk_gl_render_job_push_clip (job, &intersection); + gsk_gl_render_job_visit_node (job, child); + gsk_gl_render_job_pop_clip (job); + return; + } + + result = gsk_rounded_rect_intersect_with_rect (&job->current_clip->rect, + &transformed_clip, + &intersection); + + if (result == GSK_INTERSECTION_EMPTY) + return; + + if (result == GSK_INTERSECTION_NONEMPTY) + { + gsk_gl_render_job_push_clip (job, &intersection); + gsk_gl_render_job_visit_node (job, child); + gsk_gl_render_job_pop_clip (job); + } + else + { + GskGLRenderOffscreen offscreen = {0}; + + offscreen.bounds = clip; + offscreen.force_offscreen = TRUE; + offscreen.reset_clip = TRUE; + offscreen.do_not_cache = TRUE; + + gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreen); + + g_assert (offscreen.texture_id); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen.texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_offscreen_rect (job, clip); + gsk_gl_render_job_end_draw (job); + } + } +} + +static inline void +gsk_gl_render_job_visit_clip_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const graphene_rect_t *clip = gsk_clip_node_get_clip (node); + const GskRenderNode *child = gsk_clip_node_get_child (node); + + gsk_gl_render_job_visit_clipped_child (job, child, clip); +} + +static inline void +gsk_gl_render_job_visit_rounded_clip_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskRenderNode *child = gsk_rounded_clip_node_get_child (node); + const GskRoundedRect *clip = gsk_rounded_clip_node_get_clip (node); + GskRoundedRect transformed_clip; + gboolean need_offscreen; + + if (node_is_invisible (child)) + return; + + gsk_gl_render_job_transform_rounded_rect (job, clip, &transformed_clip); + + if (job->current_clip->is_rectilinear) + { + GskRoundedRect intersected_clip; + GskRoundedRectIntersection result; + + result = gsk_rounded_rect_intersect_with_rect (&transformed_clip, + &job->current_clip->rect.bounds, + &intersected_clip); + + if (result == GSK_INTERSECTION_EMPTY) + return; + + if (result == GSK_INTERSECTION_NONEMPTY) + { + gsk_gl_render_job_push_clip (job, &intersected_clip); + gsk_gl_render_job_visit_node (job, child); + gsk_gl_render_job_pop_clip (job); + return; + } + } + + /* After this point we are really working with a new and a current clip + * which both have rounded corners. + */ + + if (clips_get_size (&job->clip) <= 1) + need_offscreen = FALSE; + else if (gsk_rounded_rect_contains_rect (&job->current_clip->rect, &transformed_clip.bounds)) + need_offscreen = FALSE; + else + need_offscreen = TRUE; + + if (!need_offscreen) + { + gsk_gl_render_job_push_clip (job, &transformed_clip); + gsk_gl_render_job_visit_node (job, child); + gsk_gl_render_job_pop_clip (job); + } + else + { + GskGLRenderOffscreen offscreen = {0}; + + offscreen.bounds = &node->bounds; + offscreen.force_offscreen = TRUE; + offscreen.reset_clip = FALSE; + + gsk_gl_render_job_push_clip (job, &transformed_clip); + if (!gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreen)) + g_assert_not_reached (); + gsk_gl_render_job_pop_clip (job); + + g_assert (offscreen.texture_id); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen.texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen); + gsk_gl_render_job_end_draw (job); + } + } +} + +static inline void +gsk_gl_render_job_visit_rect_border_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GdkRGBA *colors = gsk_border_node_get_colors (node); + const float *widths = gsk_border_node_get_widths (node); + const graphene_point_t *origin = &node->bounds.origin; + const graphene_size_t *size = &node->bounds.size; + guint16 color[4]; + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color))) + { + if (widths[0] > 0) + { + rgba_to_half (&colors[0], color); + gsk_gl_render_job_draw_rect_with_color (job, + &GRAPHENE_RECT_INIT (origin->x, origin->y, size->width - widths[1], widths[0]), + color); + } + + if (widths[1] > 0) + { + rgba_to_half (&colors[1], color); + gsk_gl_render_job_draw_rect_with_color (job, + &GRAPHENE_RECT_INIT (origin->x + size->width - widths[1], origin->y, widths[1], size->height - widths[2]), + color); + } + + if (widths[2] > 0) + { + rgba_to_half (&colors[2], color); + gsk_gl_render_job_draw_rect_with_color (job, + &GRAPHENE_RECT_INIT (origin->x + widths[3], origin->y + size->height - widths[2], size->width - widths[3], widths[2]), + color); + } + + if (widths[3] > 0) + { + rgba_to_half (&colors[3], color); + gsk_gl_render_job_draw_rect_with_color (job, + &GRAPHENE_RECT_INIT (origin->x, origin->y + widths[0], widths[3], size->height - widths[0]), + color); + } + + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_border_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskRoundedRect *rounded_outline = gsk_border_node_get_outline (node); + const GdkRGBA *colors = gsk_border_node_get_colors (node); + const float *widths = gsk_border_node_get_widths (node); + struct { + float w; + float h; + } sizes[4]; + float min_x = job->offset_x + node->bounds.origin.x; + float min_y = job->offset_y + node->bounds.origin.y; + float max_x = min_x + node->bounds.size.width; + float max_y = min_y + node->bounds.size.height; + GskRoundedRect outline; + guint16 color[4]; + + memset (sizes, 0, sizeof sizes); + + if (widths[0] > 0) + { + sizes[0].h = MAX (widths[0], rounded_outline->corner[0].height); + sizes[1].h = MAX (widths[0], rounded_outline->corner[1].height); + } + + if (widths[1] > 0) + { + sizes[1].w = MAX (widths[1], rounded_outline->corner[1].width); + sizes[2].w = MAX (widths[1], rounded_outline->corner[2].width); + } + + if (widths[2] > 0) + { + sizes[2].h = MAX (widths[2], rounded_outline->corner[2].height); + sizes[3].h = MAX (widths[2], rounded_outline->corner[3].height); + } + + if (widths[3] > 0) + { + sizes[0].w = MAX (widths[3], rounded_outline->corner[0].width); + sizes[3].w = MAX (widths[3], rounded_outline->corner[3].width); + } + + gsk_gl_render_job_translate_rounded_rect (job, rounded_outline, &outline); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, border))) + { + gsk_gl_program_set_uniform4fv (job->current_program, + UNIFORM_BORDER_WIDTHS, 0, + 1, + widths); + gsk_gl_program_set_uniform_rounded_rect (job->current_program, + UNIFORM_BORDER_OUTLINE_RECT, 0, + &outline); + + if (widths[0] > 0) + { + GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (job->command_queue); + + rgba_to_half (&colors[0], color); + + vertices[0] = (GskGLDrawVertex) { .position = { min_x, min_y }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[1] = (GskGLDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[2] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } }; + + vertices[3] = (GskGLDrawVertex) { .position = { max_x - sizes[1].w, min_y + sizes[1].h }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[4] = (GskGLDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[5] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } }; + } + + if (widths[1] > 0) + { + GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (job->command_queue); + + rgba_to_half (&colors[1], color); + + vertices[0] = (GskGLDrawVertex) { .position = { max_x - sizes[1].w, min_y + sizes[1].h }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[1] = (GskGLDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[2] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } }; + + vertices[3] = (GskGLDrawVertex) { .position = { max_x, max_y }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[4] = (GskGLDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[5] = (GskGLDrawVertex) { .position = { max_x, min_y }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } }; + } + + if (widths[2] > 0) + { + GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (job->command_queue); + + rgba_to_half (&colors[2], color); + + vertices[0] = (GskGLDrawVertex) { .position = { min_x + sizes[3].w, max_y - sizes[3].h }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[1] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[2] = (GskGLDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } }; + + vertices[3] = (GskGLDrawVertex) { .position = { max_x, max_y }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[4] = (GskGLDrawVertex) { .position = { min_x , max_y }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[5] = (GskGLDrawVertex) { .position = { max_x - sizes[2].w, max_y - sizes[2].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } }; + } + + if (widths[3] > 0) + { + GskGLDrawVertex *vertices = gsk_gl_command_queue_add_vertices (job->command_queue); + + rgba_to_half (&colors[3], color); + + vertices[0] = (GskGLDrawVertex) { .position = { min_x, min_y }, .uv = { 0, 1 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[1] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[2] = (GskGLDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } }; + + vertices[3] = (GskGLDrawVertex) { .position = { min_x + sizes[3].w, max_y - sizes[3].h }, .uv = { 1, 0 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[4] = (GskGLDrawVertex) { .position = { min_x, max_y }, .uv = { 0, 0 }, .color = { color[0], color[1], color[2], color[3] } }; + vertices[5] = (GskGLDrawVertex) { .position = { min_x + sizes[0].w, min_y + sizes[0].h }, .uv = { 1, 1 }, .color = { color[0], color[1], color[2], color[3] } }; + } + + gsk_gl_render_job_end_draw (job); + } +} + +/* A special case for a pattern that occurs frequently with CSS + * backgrounds: two sibling nodes, the first of which is a rounded + * clip node with a color node as child, and the second one is a + * border node, with the same outline as the clip node. We render + * this using the filled_border shader. + */ +static void +gsk_gl_render_job_visit_css_background (GskGLRenderJob *job, + const GskRenderNode *node, + const GskRenderNode *node2) +{ + const GskRenderNode *child = gsk_rounded_clip_node_get_child (node); + const GskRoundedRect *rounded_outline = gsk_border_node_get_outline (node2); + const float *widths = gsk_border_node_get_widths (node2); + float min_x = job->offset_x + node2->bounds.origin.x; + float min_y = job->offset_y + node2->bounds.origin.y; + float max_x = min_x + node2->bounds.size.width; + float max_y = min_y + node2->bounds.size.height; + GskRoundedRect outline; + GskGLDrawVertex *vertices; + guint16 color[4]; + guint16 color2[4]; + GdkRGBA rgba; + + if (node_is_invisible (node2)) + return; + + get_color_node_color_as_srgb (child, &rgba); + rgba_to_half (&gsk_border_node_get_colors (node2)[0], color); + rgba_to_half (&rgba, color2); + + gsk_gl_render_job_translate_rounded_rect (job, rounded_outline, &outline); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, filled_border))) + { + gsk_gl_program_set_uniform4fv (job->current_program, + UNIFORM_FILLED_BORDER_WIDTHS, 0, + 1, + widths); + gsk_gl_program_set_uniform_rounded_rect (job->current_program, + UNIFORM_FILLED_BORDER_OUTLINE_RECT, 0, + &outline); + + vertices = gsk_gl_command_queue_add_vertices (job->command_queue); + + vertices[0] = (GskGLDrawVertex) { .position = { min_x, min_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } }; + vertices[1] = (GskGLDrawVertex) { .position = { min_x, max_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } }; + vertices[2] = (GskGLDrawVertex) { .position = { max_x, min_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } }; + vertices[3] = (GskGLDrawVertex) { .position = { max_x, max_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } }; + vertices[4] = (GskGLDrawVertex) { .position = { min_x, max_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } }; + vertices[5] = (GskGLDrawVertex) { .position = { max_x, min_y }, .color = { color[0], color[1], color[2], color[3] }, .color2 = { color2[0], color2[1], color2[2], color2[3] } }; + + gsk_gl_render_job_end_draw (job); + } +} + +/* Returns TRUE if applying @transform to @bounds + * yields an axis-aligned rectangle + */ +static gboolean +result_is_axis_aligned (GskTransform *transform, + const graphene_rect_t *bounds) +{ + graphene_matrix_t m; + graphene_quad_t q; + graphene_rect_t b; + graphene_point_t b1, b2; + const graphene_point_t *p; + + gsk_transform_to_matrix (transform, &m); + gsk_matrix_transform_rect (&m, bounds, &q); + graphene_quad_bounds (&q, &b); + graphene_rect_get_top_left (&b, &b1); + graphene_rect_get_bottom_right (&b, &b2); + + for (guint i = 0; i < 4; i++) + { + p = graphene_quad_get_point (&q, i); + if (fabsf (p->x - b1.x) > FLT_EPSILON && fabsf (p->x - b2.x) > FLT_EPSILON) + return FALSE; + if (fabsf (p->y - b1.y) > FLT_EPSILON && fabsf (p->y - b2.y) > FLT_EPSILON) + return FALSE; + } + + return TRUE; +} + +static inline void +gsk_gl_render_job_visit_transform_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + GskTransform *transform = gsk_transform_node_get_transform (node); + const GskTransformCategory category = gsk_transform_get_category (transform); + const GskRenderNode *child = gsk_transform_node_get_child (node); + + switch (category) + { + case GSK_TRANSFORM_CATEGORY_IDENTITY: + gsk_gl_render_job_visit_node (job, child); + break; + + case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE: + { + float dx, dy; + + gsk_transform_to_translate (gsk_transform_node_get_transform (node), &dx, &dy); + gsk_gl_render_job_offset (job, dx, dy); + gsk_gl_render_job_visit_node (job, child); + gsk_gl_render_job_offset (job, -dx, -dy); + } + break; + + case GSK_TRANSFORM_CATEGORY_2D_AFFINE: + { + gsk_gl_render_job_push_modelview (job, transform); + gsk_gl_render_job_visit_node (job, child); + gsk_gl_render_job_pop_modelview (job); + } + break; + + case GSK_TRANSFORM_CATEGORY_2D: + if (node_supports_2d_transform (child)) + { + gsk_gl_render_job_push_modelview (job, transform); + gsk_gl_render_job_visit_node (job, child); + gsk_gl_render_job_pop_modelview (job); + return; + } + G_GNUC_FALLTHROUGH; + case GSK_TRANSFORM_CATEGORY_3D: + case GSK_TRANSFORM_CATEGORY_ANY: + case GSK_TRANSFORM_CATEGORY_UNKNOWN: + if (node_supports_transform (child)) + { + gsk_gl_render_job_push_modelview (job, transform); + gsk_gl_render_job_visit_node (job, child); + gsk_gl_render_job_pop_modelview (job); + } + else + { + GskGLRenderOffscreen offscreen = {0}; + float sx = 1, sy = 1; + gboolean linear_filter = FALSE; + + offscreen.bounds = &child->bounds; + offscreen.force_offscreen = FALSE; + offscreen.reset_clip = TRUE; + + if (!result_is_axis_aligned (transform, &child->bounds)) + linear_filter = TRUE; + + if (category == GSK_TRANSFORM_CATEGORY_2D) + { + graphene_matrix_t m; + double a, b, c, d, tx, ty; + + g_assert (transform != NULL); + gsk_transform_to_matrix (transform, &m); + if (graphene_matrix_to_2d (&m, &a, &b, &c, &d, &tx, &ty)) + { + sx = sqrt (a * a + b * b); + sy = sqrt (c * c + d * d); + } + else + sx = sy = 1; + + if (sx != 1 || sy != 1) + { + GskTransform *scale; + + scale = gsk_transform_translate (gsk_transform_scale (NULL, sx, sy), &GRAPHENE_POINT_INIT (tx, ty)); + gsk_gl_render_job_push_modelview (job, scale); + transform = gsk_transform_transform (gsk_transform_invert (scale), transform); + } + } + + if (gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreen)) + { + /* For non-trivial transforms, we draw everything on a texture and then + * draw the texture transformed. + */ + if (transform) + gsk_gl_render_job_push_modelview (job, transform); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + { + gsk_gl_program_set_uniform_texture_with_filter (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen.texture_id, + linear_filter ? GL_LINEAR : GL_NEAREST, + linear_filter ? GL_LINEAR : GL_NEAREST); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_offscreen (job, &child->bounds, &offscreen); + gsk_gl_render_job_end_draw (job); + } + + if (transform) + gsk_gl_render_job_pop_modelview (job); + } + + if (category == GSK_TRANSFORM_CATEGORY_2D) + { + if (sx != 1 || sy != 1) + { + gsk_gl_render_job_pop_modelview (job); + gsk_transform_unref (transform); + } + } + } + break; + + default: + g_assert_not_reached (); + } +} + +static inline void +gsk_gl_render_job_visit_unblurred_inset_shadow_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskRoundedRect *outline = gsk_inset_shadow_node_get_outline (node); + GskRoundedRect transformed_outline; + guint16 color[4]; + + gsk_gl_render_job_translate_rounded_rect (job, outline, &transformed_outline); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, inset_shadow))) + { + const GdkRGBA rgba; + + gdk_color_to_float (gsk_inset_shadow_node_get_gdk_color (node), GDK_COLOR_STATE_SRGB, (float *) &rgba); + + gsk_gl_program_set_uniform_rounded_rect (job->current_program, + UNIFORM_INSET_SHADOW_OUTLINE_RECT, 0, + &transformed_outline); + gsk_gl_program_set_uniform1f (job->current_program, + UNIFORM_INSET_SHADOW_SPREAD, 0, + gsk_inset_shadow_node_get_spread (node)); + gsk_gl_program_set_uniform2f (job->current_program, + UNIFORM_INSET_SHADOW_OFFSET, 0, + gsk_inset_shadow_node_get_dx (node), + gsk_inset_shadow_node_get_dy (node)); + rgba_to_half (&rgba, color); + gsk_gl_render_job_draw_rect_with_color (job, &node->bounds, color); + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_blurred_inset_shadow_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskRoundedRect *node_outline = gsk_inset_shadow_node_get_outline (node); + float blur_radius = gsk_inset_shadow_node_get_blur_radius (node); + float offset_x = gsk_inset_shadow_node_get_dx (node); + float offset_y = gsk_inset_shadow_node_get_dy (node); + float scale_x = job->scale_x; + float scale_y = job->scale_y; + float blur_extra = blur_radius * 2.0; /* 2.0 = shader radius_multiplier */ + float half_blur_extra = blur_radius; + float texture_width; + float texture_height; + int blurred_texture_id; + GskTextureKey key; + GskGLRenderOffscreen offscreen = {0}; + guint16 color[4]; + + g_assert (blur_radius > 0); + + texture_width = ceilf ((node_outline->bounds.size.width + blur_extra) * scale_x); + texture_height = ceilf ((node_outline->bounds.size.height + blur_extra) * scale_y); + + key.pointer = node; + key.pointer_is_child = FALSE; + key.scale_x = scale_x; + key.scale_y = scale_y; + + blurred_texture_id = gsk_gl_driver_lookup_texture (job->driver, &key, NULL); + + if (blurred_texture_id == 0) + { + float spread = gsk_inset_shadow_node_get_spread (node) + half_blur_extra; + GskRoundedRect transformed_outline; + GskRoundedRect outline_to_blur; + GskGLRenderTarget *render_target; + graphene_matrix_t prev_projection; + graphene_rect_t prev_viewport; + guint prev_fbo; + + /* TODO: In the following code, we have to be careful about where we apply the scale. + * We're manually scaling stuff (e.g. the outline) so we can later use texture_width + * and texture_height (which are already scaled) as the geometry and keep the modelview + * at a scale of 1. That's kinda complicated though... */ + + /* Outline of what we actually want to blur later. + * Spread grows inside, so we don't need to account for that. But the blur will need + * to read outside of the inset shadow, so we need to draw some color in there. */ + outline_to_blur = *node_outline; + gsk_rounded_rect_shrink (&outline_to_blur, + -half_blur_extra, + -half_blur_extra, + -half_blur_extra, + -half_blur_extra); + + /* Fit to our texture */ + outline_to_blur.bounds.origin.x = 0; + outline_to_blur.bounds.origin.y = 0; + outline_to_blur.bounds.size.width *= scale_x; + outline_to_blur.bounds.size.height *= scale_y; + + for (guint i = 0; i < 4; i ++) + { + outline_to_blur.corner[i].width *= scale_x; + outline_to_blur.corner[i].height *= scale_y; + } + + if (!gsk_gl_driver_create_render_target (job->driver, + texture_width, texture_height, + get_target_format (job, node), + &render_target)) + g_assert_not_reached (); + + gsk_gl_render_job_set_viewport_for_size (job, texture_width, texture_height, &prev_viewport); + gsk_gl_render_job_set_projection_for_size (job, texture_width, texture_height, &prev_projection); + gsk_gl_render_job_set_modelview (job, NULL); + gsk_gl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT (0, 0, texture_width, texture_height)); + + prev_fbo = gsk_gl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id); + gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport); + + gsk_gl_render_job_translate_rounded_rect (job, &outline_to_blur, &transformed_outline); + + /* Actual inset shadow outline drawing */ + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, inset_shadow))) + { + const GdkRGBA rgba; + + gdk_color_to_float (gsk_inset_shadow_node_get_gdk_color (node), GDK_COLOR_STATE_SRGB, (float *) &rgba); + + gsk_gl_program_set_uniform_rounded_rect (job->current_program, + UNIFORM_INSET_SHADOW_OUTLINE_RECT, 0, + &transformed_outline); + gsk_gl_program_set_uniform1f (job->current_program, + UNIFORM_INSET_SHADOW_SPREAD, 0, + spread * MAX (scale_x, scale_y)); + gsk_gl_program_set_uniform2f (job->current_program, + UNIFORM_INSET_SHADOW_OFFSET, 0, + offset_x * scale_x, + offset_y * scale_y); + rgba_to_half (&rgba, color); + gsk_gl_render_job_draw_with_color (job, + 0, 0, texture_width, texture_height, + color); + gsk_gl_render_job_end_draw (job); + } + + gsk_gl_render_job_pop_modelview (job); + gsk_gl_render_job_pop_clip (job); + gsk_gl_render_job_set_projection (job, &prev_projection); + gsk_gl_render_job_set_viewport (job, &prev_viewport, NULL); + gsk_gl_command_queue_bind_framebuffer (job->command_queue, prev_fbo); + + offscreen.texture_id = render_target->texture_id; + init_full_texture_region (&offscreen); + + blurred_texture_id = blur_offscreen (job, + &offscreen, + texture_width, + texture_height, + blur_radius * fabsf (scale_x), + blur_radius * fabsf (scale_y)); + + gsk_gl_driver_release_render_target (job->driver, render_target, TRUE); + + gsk_gl_driver_cache_texture (job->driver, &key, blurred_texture_id); + } + + g_assert (blurred_texture_id != 0); + + /* Blur the rendered unblurred inset shadow */ + /* Use a clip to cut away the unwanted parts outside of the original outline */ + { + const gboolean needs_clip = !gsk_rounded_rect_is_rectilinear (node_outline); + const float tx1 = half_blur_extra * scale_x / texture_width; + const float tx2 = 1.0 - tx1; + const float ty1 = half_blur_extra * scale_y / texture_height; + const float ty2 = 1.0 - ty1; + + if (needs_clip) + { + GskRoundedRect node_clip; + + gsk_gl_render_job_translate_rounded_rect (job, node_outline, &node_clip); + gsk_gl_render_job_push_clip (job, &node_clip); + } + + offscreen.was_offscreen = TRUE; + offscreen.area.x = tx1; + offscreen.area.y = ty1; + offscreen.area.x2 = tx2; + offscreen.area.y2 = ty2; + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + blurred_texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen); + gsk_gl_render_job_end_draw (job); + } + + if (needs_clip) + gsk_gl_render_job_pop_clip (job); + } +} + +static inline void +gsk_gl_render_job_visit_unblurred_outset_shadow_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskRoundedRect *outline = gsk_outset_shadow_node_get_outline (node); + GskRoundedRect transformed_outline; + float x = node->bounds.origin.x; + float y = node->bounds.origin.y; + float w = node->bounds.size.width; + float h = node->bounds.size.height; + float spread = gsk_outset_shadow_node_get_spread (node); + float dx = gsk_outset_shadow_node_get_dx (node); + float dy = gsk_outset_shadow_node_get_dy (node); + GdkRGBA rgba; + guint16 color[4]; + const float edge_sizes[] = { // Top, right, bottom, left + spread - dy, spread + dx, spread + dy, spread - dx + }; + const float corner_sizes[][2] = { // top left, top right, bottom right, bottom left + { outline->corner[0].width + spread - dx, outline->corner[0].height + spread - dy }, + { outline->corner[1].width + spread + dx, outline->corner[1].height + spread - dy }, + { outline->corner[2].width + spread + dx, outline->corner[2].height + spread + dy }, + { outline->corner[3].width + spread - dx, outline->corner[3].height + spread + dy }, + }; + + gdk_color_to_float (gsk_outset_shadow_node_get_gdk_color (node), GDK_COLOR_STATE_SRGB, (float *) &rgba); + rgba_to_half (&rgba, color); + + gsk_gl_render_job_translate_rounded_rect (job, outline, &transformed_outline); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, unblurred_outset_shadow))) + { + gsk_gl_program_set_uniform_rounded_rect (job->current_program, + UNIFORM_UNBLURRED_OUTSET_SHADOW_OUTLINE_RECT, 0, + &transformed_outline); + gsk_gl_program_set_uniform1f (job->current_program, + UNIFORM_UNBLURRED_OUTSET_SHADOW_SPREAD, 0, + spread); + gsk_gl_program_set_uniform2f (job->current_program, + UNIFORM_UNBLURRED_OUTSET_SHADOW_OFFSET, 0, + dx, dy); + + /* Corners... */ + if (corner_sizes[0][0] > 0 && corner_sizes[0][1] > 0) /* Top left */ + gsk_gl_render_job_draw_with_color (job, + x, y, corner_sizes[0][0], corner_sizes[0][1], + color); + if (corner_sizes[1][0] > 0 && corner_sizes[1][1] > 0) /* Top right */ + gsk_gl_render_job_draw_with_color (job, + x + w - corner_sizes[1][0], y, + corner_sizes[1][0], corner_sizes[1][1], + color); + if (corner_sizes[2][0] > 0 && corner_sizes[2][1] > 0) /* Bottom right */ + gsk_gl_render_job_draw_with_color (job, + x + w - corner_sizes[2][0], y + h - corner_sizes[2][1], + corner_sizes[2][0], corner_sizes[2][1], + color); + if (corner_sizes[3][0] > 0 && corner_sizes[3][1] > 0) /* Bottom left */ + gsk_gl_render_job_draw_with_color (job, + x, y + h - corner_sizes[3][1], + corner_sizes[3][0], corner_sizes[3][1], + color); + /* Edges... */; + if (edge_sizes[0] > 0) /* Top */ + gsk_gl_render_job_draw_with_color (job, + x + corner_sizes[0][0], y, + w - corner_sizes[0][0] - corner_sizes[1][0], edge_sizes[0], + color); + if (edge_sizes[1] > 0) /* Right */ + gsk_gl_render_job_draw_with_color (job, + x + w - edge_sizes[1], y + corner_sizes[1][1], + edge_sizes[1], h - corner_sizes[1][1] - corner_sizes[2][1], + color); + if (edge_sizes[2] > 0) /* Bottom */ + gsk_gl_render_job_draw_with_color (job, + x + corner_sizes[3][0], y + h - edge_sizes[2], + w - corner_sizes[3][0] - corner_sizes[2][0], edge_sizes[2], + color); + if (edge_sizes[3] > 0) /* Left */ + gsk_gl_render_job_draw_with_color (job, + x, y + corner_sizes[0][1], + edge_sizes[3], h - corner_sizes[0][1] - corner_sizes[3][1], + color); + + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_blurred_outset_shadow_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskRoundedRect *outline = gsk_outset_shadow_node_get_outline (node); + float scale_x = fabsf (job->scale_x); + float scale_y = fabsf (job->scale_y); + float blur_radius = gsk_outset_shadow_node_get_blur_radius (node); + float blur_extra = blur_radius * 2.0f; /* 2.0 = shader radius_multiplier */ + float half_blur_extra = blur_extra / 2.0f; + int extra_blur_pixels_x = ceilf (half_blur_extra * scale_x); + int extra_blur_pixels_y = ceilf (half_blur_extra * scale_y); + float spread = gsk_outset_shadow_node_get_spread (node); + float dx = gsk_outset_shadow_node_get_dx (node); + float dy = gsk_outset_shadow_node_get_dy (node); + GskRoundedRect scaled_outline; + GskRoundedRect transformed_outline; + GskGLRenderOffscreen offscreen = {0}; + int texture_width, texture_height; + int blurred_texture_id; + int cached_tid; + gboolean do_slicing; + GdkRGBA rgba; + guint16 color[4]; + float half_width = outline->bounds.size.width / 2; + float half_height = outline->bounds.size.height / 2; + + gdk_color_to_float (gsk_outset_shadow_node_get_gdk_color (node), GDK_COLOR_STATE_SRGB, (float *) &rgba); + rgba_to_half (&rgba, color); + + /* scaled_outline is the minimal outline we need to draw the given drop shadow, + * enlarged by the spread and offset by the blur radius. */ + scaled_outline = *outline; + + if (outline->bounds.size.width < blur_extra || + outline->bounds.size.height < blur_extra || + outline->corner[0].width >= half_width || + outline->corner[1].width >= half_width || + outline->corner[2].width >= half_width || + outline->corner[3].width >= half_width || + outline->corner[0].height >= half_height || + outline->corner[1].height >= half_height || + outline->corner[2].height >= half_height || + outline->corner[3].height >= half_height) + { + do_slicing = FALSE; + gsk_rounded_rect_shrink (&scaled_outline, -spread, -spread, -spread, -spread); + } + else + { + /* Shrink our outline to the minimum size that can still hold all the border radii */ + gsk_rounded_rect_shrink_to_minimum (&scaled_outline); + /* Increase by the spread */ + gsk_rounded_rect_shrink (&scaled_outline, -spread, -spread, -spread, -spread); + /* Grow bounds but don't grow corners */ + graphene_rect_inset (&scaled_outline.bounds, - blur_extra / 2.0, - blur_extra / 2.0); + /* For the center part, we add a few pixels */ + scaled_outline.bounds.size.width += SHADOW_EXTRA_SIZE; + scaled_outline.bounds.size.height += SHADOW_EXTRA_SIZE; + + do_slicing = TRUE; + } + + texture_width = (int)ceilf ((scaled_outline.bounds.size.width + blur_extra) * scale_x); + texture_height = (int)ceilf ((scaled_outline.bounds.size.height + blur_extra) * scale_y); + + scaled_outline.bounds.origin.x = extra_blur_pixels_x; + scaled_outline.bounds.origin.y = extra_blur_pixels_y; + scaled_outline.bounds.size.width = texture_width - (extra_blur_pixels_x * 2); + scaled_outline.bounds.size.height = texture_height - (extra_blur_pixels_y * 2); + + for (guint i = 0; i < G_N_ELEMENTS (scaled_outline.corner); i++) + { + scaled_outline.corner[i].width *= scale_x; + scaled_outline.corner[i].height *= scale_y; + } + + cached_tid = gsk_gl_shadow_library_lookup (job->driver->shadows_library, + &scaled_outline, + blur_radius); + + if (cached_tid == 0) + { + GdkGLContext *context = job->command_queue->context; + GskGLRenderTarget *render_target; + graphene_matrix_t prev_projection; + graphene_rect_t prev_viewport; + guint prev_fbo; + + gsk_gl_driver_create_render_target (job->driver, + texture_width, texture_height, + get_target_format (job, node), + &render_target); + + if (gdk_gl_context_has_feature (context, GDK_GL_FEATURE_DEBUG)) + { + gdk_gl_context_label_object_printf (context, + GL_TEXTURE, + render_target->texture_id, + "Outset Shadow Temp %d", + render_target->texture_id); + gdk_gl_context_label_object_printf (context, + GL_FRAMEBUFFER, + render_target->framebuffer_id, + "Outset Shadow FB Temp %d", + render_target->framebuffer_id); + } + + /* Change state for offscreen */ + gsk_gl_render_job_set_projection_for_size (job, texture_width, texture_height, &prev_projection); + gsk_gl_render_job_set_viewport_for_size (job, texture_width, texture_height, &prev_viewport); + gsk_gl_render_job_set_modelview (job, NULL); + gsk_gl_render_job_push_clip (job, &scaled_outline); + + /* Bind render target and clear it */ + prev_fbo = gsk_gl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id); + gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport); + + /* Draw the outline using color program */ + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color))) + { + gsk_gl_render_job_draw_with_color (job, 0, 0, texture_width, texture_height, + (guint16[]){ FP16_ONE, FP16_ONE, FP16_ONE, FP16_ONE }); + gsk_gl_render_job_end_draw (job); + } + + /* Reset state from offscreen */ + gsk_gl_render_job_pop_clip (job); + gsk_gl_render_job_pop_modelview (job); + gsk_gl_render_job_set_viewport (job, &prev_viewport, NULL); + gsk_gl_render_job_set_projection (job, &prev_projection); + + /* Now blur the outline */ + init_full_texture_region (&offscreen); + offscreen.texture_id = gsk_gl_driver_release_render_target (job->driver, render_target, FALSE); + blurred_texture_id = blur_offscreen (job, + &offscreen, + texture_width, + texture_height, + blur_radius * scale_x, + blur_radius * scale_y); + + gsk_gl_shadow_library_insert (job->driver->shadows_library, + &scaled_outline, + blur_radius, + blurred_texture_id); + + gsk_gl_command_queue_bind_framebuffer (job->command_queue, prev_fbo); + } + else + { + blurred_texture_id = cached_tid; + } + + gsk_gl_render_job_translate_rounded_rect (job, outline, &transformed_outline); + + if (!do_slicing) + { + float min_x = floorf (outline->bounds.origin.x - spread - half_blur_extra + dx); + float min_y = floorf (outline->bounds.origin.y - spread - half_blur_extra + dy); + + offscreen.was_offscreen = TRUE; + offscreen.texture_id = blurred_texture_id; + init_full_texture_region (&offscreen); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, outset_shadow))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + blurred_texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_program_set_uniform_rounded_rect (job->current_program, + UNIFORM_OUTSET_SHADOW_OUTLINE_RECT, 0, + &transformed_outline); + gsk_gl_render_job_draw_offscreen_with_color (job, + &GRAPHENE_RECT_INIT (min_x, + min_y, + texture_width / scale_x, + texture_height / scale_y), + &offscreen, + color); + gsk_gl_render_job_end_draw (job); + } + + return; + } + + /* slicing */ + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, outset_shadow))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + blurred_texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_program_set_uniform_rounded_rect (job->current_program, + UNIFORM_OUTSET_SHADOW_OUTLINE_RECT, 0, + &transformed_outline); + + { + float min_x = floorf (outline->bounds.origin.x - spread - half_blur_extra + dx); + float min_y = floorf (outline->bounds.origin.y - spread - half_blur_extra + dy); + float max_x = ceilf (outline->bounds.origin.x + outline->bounds.size.width + + half_blur_extra + dx + spread); + float max_y = ceilf (outline->bounds.origin.y + outline->bounds.size.height + + half_blur_extra + dy + spread); + const GskGLTextureNineSlice *slices; + float left_width, center_width, right_width; + float top_height, center_height, bottom_height; + GskGLTexture *texture; + + texture = gsk_gl_driver_get_texture_by_id (job->driver, blurred_texture_id); + slices = gsk_gl_texture_get_nine_slice (texture, &scaled_outline, extra_blur_pixels_x, extra_blur_pixels_y); + + offscreen.was_offscreen = TRUE; + + /* Our texture coordinates MUST be scaled, while the actual vertex coords + * MUST NOT be scaled. + */ + + left_width = slices[NINE_SLICE_TOP_LEFT].rect.width / scale_x; + right_width = slices[NINE_SLICE_TOP_RIGHT].rect.width / scale_x; + center_width = (max_x - min_x) - (left_width + right_width); + + top_height = slices[NINE_SLICE_TOP_LEFT].rect.height / scale_y; + bottom_height = slices[NINE_SLICE_BOTTOM_LEFT].rect.height / scale_y; + center_height = (max_y - min_y) - (top_height + bottom_height); + + /* Top left */ + if (nine_slice_is_visible (&slices[NINE_SLICE_TOP_LEFT])) + { + memcpy (&offscreen.area, &slices[NINE_SLICE_TOP_LEFT].area, sizeof offscreen.area); + gsk_gl_render_job_draw_offscreen_with_color (job, + &GRAPHENE_RECT_INIT (min_x, + min_y, + left_width, + top_height), + &offscreen, + color); + } + + /* Top center */ + if (nine_slice_is_visible (&slices[NINE_SLICE_TOP_CENTER])) + { + memcpy (&offscreen.area, &slices[NINE_SLICE_TOP_CENTER].area, sizeof offscreen.area); + gsk_gl_render_job_draw_offscreen_with_color (job, + &GRAPHENE_RECT_INIT (min_x + left_width, + min_y, + center_width, + top_height), + &offscreen, + color); + } + + /* Top right */ + if (nine_slice_is_visible (&slices[NINE_SLICE_TOP_RIGHT])) + { + memcpy (&offscreen.area, &slices[NINE_SLICE_TOP_RIGHT].area, sizeof offscreen.area); + gsk_gl_render_job_draw_offscreen_with_color (job, + &GRAPHENE_RECT_INIT (max_x - right_width, + min_y, + right_width, + top_height), + &offscreen, + color); + } + + /* Bottom right */ + if (nine_slice_is_visible (&slices[NINE_SLICE_BOTTOM_RIGHT])) + { + memcpy (&offscreen.area, &slices[NINE_SLICE_BOTTOM_RIGHT].area, sizeof offscreen.area); + gsk_gl_render_job_draw_offscreen_with_color (job, + &GRAPHENE_RECT_INIT (max_x - right_width, + max_y - bottom_height, + right_width, + bottom_height), + &offscreen, + color); + } + + /* Bottom left */ + if (nine_slice_is_visible (&slices[NINE_SLICE_BOTTOM_LEFT])) + { + memcpy (&offscreen.area, &slices[NINE_SLICE_BOTTOM_LEFT].area, sizeof offscreen.area); + gsk_gl_render_job_draw_offscreen_with_color (job, + &GRAPHENE_RECT_INIT (min_x, + max_y - bottom_height, + left_width, + bottom_height), + &offscreen, + color); + } + + /* Left side */ + if (nine_slice_is_visible (&slices[NINE_SLICE_LEFT_CENTER])) + { + memcpy (&offscreen.area, &slices[NINE_SLICE_LEFT_CENTER].area, sizeof offscreen.area); + gsk_gl_render_job_draw_offscreen_with_color (job, + &GRAPHENE_RECT_INIT (min_x, + min_y + top_height, + left_width, + center_height), + &offscreen, + color); + } + + /* Right side */ + if (nine_slice_is_visible (&slices[NINE_SLICE_RIGHT_CENTER])) + { + memcpy (&offscreen.area, &slices[NINE_SLICE_RIGHT_CENTER].area, sizeof offscreen.area); + gsk_gl_render_job_draw_offscreen_with_color (job, + &GRAPHENE_RECT_INIT (max_x - right_width, + min_y + top_height, + right_width, + center_height), + &offscreen, + color); + } + + /* Bottom side */ + if (nine_slice_is_visible (&slices[NINE_SLICE_BOTTOM_CENTER])) + { + memcpy (&offscreen.area, &slices[NINE_SLICE_BOTTOM_CENTER].area, sizeof offscreen.area); + gsk_gl_render_job_draw_offscreen_with_color (job, + &GRAPHENE_RECT_INIT (min_x + left_width, + max_y - bottom_height, + center_width, + bottom_height), + &offscreen, + color); + } + + /* Middle */ + if (nine_slice_is_visible (&slices[NINE_SLICE_CENTER])) + { + if (!gsk_rounded_rect_contains_rect (outline, &GRAPHENE_RECT_INIT (min_x + left_width, + min_y + top_height, + center_width, + center_height))) + { + memcpy (&offscreen.area, &slices[NINE_SLICE_CENTER].area, sizeof offscreen.area); + gsk_gl_render_job_draw_offscreen_with_color (job, + &GRAPHENE_RECT_INIT (min_x + left_width, + min_y + top_height, + center_width, + center_height), + &offscreen, + color); + } + } + } + + gsk_gl_render_job_end_draw (job); + } +} + +static inline gboolean G_GNUC_PURE +equal_texture_nodes (const GskRenderNode *node1, + const GskRenderNode *node2) +{ + if (GSK_RENDER_NODE_TYPE (node1) != GSK_TEXTURE_NODE || + GSK_RENDER_NODE_TYPE (node2) != GSK_TEXTURE_NODE) + return FALSE; + + if (gsk_texture_node_get_texture (node1) != + gsk_texture_node_get_texture (node2)) + return FALSE; + + return gsk_rect_equal (&node1->bounds, &node2->bounds); +} + +static inline void +gsk_gl_render_job_visit_cross_fade_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskRenderNode *start_node = gsk_cross_fade_node_get_start_child (node); + const GskRenderNode *end_node = gsk_cross_fade_node_get_end_child (node); + float progress = gsk_cross_fade_node_get_progress (node); + GskGLRenderOffscreen offscreen_start = {0}; + GskGLRenderOffscreen offscreen_end = {0}; + + g_assert (progress > 0.0); + g_assert (progress < 1.0); + + offscreen_start.force_offscreen = TRUE; + offscreen_start.reset_clip = TRUE; + offscreen_start.bounds = &node->bounds; + + offscreen_end.force_offscreen = TRUE; + offscreen_end.reset_clip = TRUE; + offscreen_end.bounds = &node->bounds; + + gsk_gl_render_job_set_modelview (job, gsk_transform_scale (NULL, fabsf (job->scale_x), fabsf (job->scale_y))); + + if (!gsk_gl_render_job_visit_node_with_offscreen (job, start_node, &offscreen_start)) + { + gsk_gl_render_job_pop_modelview (job); + + gsk_gl_render_job_visit_node (job, end_node); + return; + } + + g_assert (offscreen_start.texture_id); + + if (!gsk_gl_render_job_visit_node_with_offscreen (job, end_node, &offscreen_end)) + { + float prev_alpha; + + gsk_gl_render_job_pop_modelview (job); + + prev_alpha = gsk_gl_render_job_set_alpha (job, job->alpha * progress); + gsk_gl_render_job_visit_node (job, start_node); + gsk_gl_render_job_set_alpha (job, prev_alpha); + return; + } + + gsk_gl_render_job_pop_modelview (job); + + g_assert (offscreen_end.texture_id); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, cross_fade))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen_start.texture_id); + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_CROSS_FADE_SOURCE2, 0, + GL_TEXTURE_2D, + GL_TEXTURE1, + offscreen_end.texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_program_set_uniform1f (job->current_program, + UNIFORM_CROSS_FADE_PROGRESS, 0, + progress); + gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen_end); + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_opacity_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskRenderNode *child = gsk_opacity_node_get_child (node); + float opacity = gsk_opacity_node_get_opacity (node); + float new_alpha = job->alpha * opacity; + + if (!ALPHA_IS_CLEAR (new_alpha)) + { + float prev_alpha = gsk_gl_render_job_set_alpha (job, new_alpha); + + if (!gsk_render_node_needs_blending (child)) + { + gsk_gl_render_job_visit_node (job, child); + gsk_gl_render_job_set_alpha (job, prev_alpha); + } + else + { + GskGLRenderOffscreen offscreen = {0}; + + offscreen.bounds = &child->bounds; + offscreen.force_offscreen = TRUE; + offscreen.reset_clip = TRUE; + + /* Note: offscreen rendering resets alpha to 1.0 */ + if (!gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreen)) + return; + + g_assert (offscreen.texture_id); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen.texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen); + gsk_gl_render_job_end_draw (job); + } + } + + gsk_gl_render_job_set_alpha (job, prev_alpha); + } +} + +static inline int +compute_phase_and_pos (float value, float *pos) +{ + float v; + + *pos = floorf (value); + + v = value - *pos; + + if (v < 0.125) + return 0; + else if (v < 0.375) + return 1; + else if (v < 0.625) + return 2; + else if (v < 0.875) + return 3; + else + { + *pos += 1; + return 0; + } +} + +static inline void +gsk_gl_render_job_visit_text_node (GskGLRenderJob *job, + const GskRenderNode *node, + const GdkRGBA *color, + gboolean force_color) +{ + const PangoFont *font = gsk_text_node_get_font (node); + const PangoGlyphInfo *glyphs = gsk_text_node_get_glyphs (node, NULL); + const graphene_point_t *offset = gsk_text_node_get_offset (node); + float text_scale = MAX (fabsf (job->scale_x), fabsf (job->scale_y)); /* TODO: Fix for uneven scales? */ + guint num_glyphs = gsk_text_node_get_num_glyphs (node); + float x = offset->x + job->offset_x; + float y = offset->y + job->offset_y; + GskGLGlyphLibrary *library = job->driver->glyphs_library; + GskGLCommandBatch *batch; + int x_position = 0; + GskGLGlyphKey lookup; + guint last_texture = 0; + GskGLDrawVertex *vertices; + guint used = 0; + guint16 nc[4] = { FP16_MINUS_ONE, FP16_MINUS_ONE, FP16_MINUS_ONE, FP16_MINUS_ONE }; + guint16 cc[4]; + const guint16 *c; + const PangoGlyphInfo *gi; + guint i; + int yshift; + float ypos; + + if (num_glyphs == 0) + return; + + if ((force_color || !gsk_text_node_has_color_glyphs (node)) && + RGBA_IS_CLEAR (color)) + return; + + rgba_to_half (color, cc); + + lookup.font = (PangoFont *)font; + lookup.scale = (guint) (text_scale * 1024); + + yshift = compute_phase_and_pos (y, &ypos); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, coloring))) + { + batch = gsk_gl_command_queue_get_batch (job->command_queue); + vertices = gsk_gl_command_queue_add_n_vertices (job->command_queue, num_glyphs); + + /* We use one quad per character */ + for (i = 0, gi = glyphs; i < num_glyphs; i++, gi++) + { + const GskGLGlyphValue *glyph; + float glyph_x, glyph_y, glyph_x2, glyph_y2; + float tx, ty, tx2, ty2; + float cx; + float cy; + guint texture_id; + + lookup.glyph = gi->glyph; + + /* If the glyph has color, we don't need to recolor anything. + * We tell the shader by setting the color to vec4(-1). + */ + if (!force_color && gi->attr.is_color) + c = nc; + else + c = cc; + + cx = (float)(x_position + gi->geometry.x_offset) / PANGO_SCALE; + lookup.xshift = compute_phase_and_pos (x + cx, &cx); + + if G_UNLIKELY (gi->geometry.y_offset != 0) + { + cy = (float)(gi->geometry.y_offset) / PANGO_SCALE; + lookup.yshift = compute_phase_and_pos (y + cy, &cy); + } + else + { + lookup.yshift = yshift; + cy = ypos; + } + + x_position += gi->geometry.width; + + texture_id = gsk_gl_glyph_library_lookup_or_add (library, &lookup, &glyph); + if G_UNLIKELY (texture_id == 0) + continue; + + if G_UNLIKELY (last_texture != texture_id || batch->draw.vbo_count + GSK_GL_N_VERTICES > 0xffff) + { + if G_LIKELY (last_texture != 0) + { + guint vbo_offset = batch->draw.vbo_offset + batch->draw.vbo_count; + + /* Since we have batched added our VBO vertices to avoid repeated + * calls to the buffer, we need to manually tweak the vbo offset + * of the new batch as otherwise it will point at the end of our + * vbo array. + */ + gsk_gl_render_job_split_draw (job); + batch = gsk_gl_command_queue_get_batch (job->command_queue); + batch->draw.vbo_offset = vbo_offset; + } + + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + texture_id); + job->source_is_glyph_atlas = TRUE; + last_texture = texture_id; + } + + tx = glyph->entry.area.x; + ty = glyph->entry.area.y; + tx2 = glyph->entry.area.x2; + ty2 = glyph->entry.area.y2; + + glyph_x = cx + glyph->ink_rect.x; + glyph_y = cy + glyph->ink_rect.y; + glyph_x2 = glyph_x + glyph->ink_rect.width; + glyph_y2 = glyph_y + glyph->ink_rect.height; + + *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x, glyph_y }, .uv = { tx, ty }, .color = { c[0], c[1], c[2], c[3] } }; + *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x, glyph_y2 }, .uv = { tx, ty2 }, .color = { c[0], c[1], c[2], c[3] } }; + *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x2, glyph_y }, .uv = { tx2, ty }, .color = { c[0], c[1], c[2], c[3] } }; + + *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x2, glyph_y2 }, .uv = { tx2, ty2 }, .color = { c[0], c[1], c[2], c[3] } }; + *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x, glyph_y2 }, .uv = { tx, ty2 }, .color = { c[0], c[1], c[2], c[3] } }; + *(vertices++) = (GskGLDrawVertex) { .position = { glyph_x2, glyph_y }, .uv = { tx2, ty }, .color = { c[0], c[1], c[2], c[3] } }; + + batch->draw.vbo_count += GSK_GL_N_VERTICES; + used++; + } + + if (used != num_glyphs) + gsk_gl_command_queue_retract_n_vertices (job->command_queue, num_glyphs - used); + + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_shadow_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const gsize n_shadows = gsk_shadow_node_get_n_shadows (node); + const GskRenderNode *original_child = gsk_shadow_node_get_child (node); + const GskRenderNode *shadow_child = original_child; + + /* Shadow nodes recolor every pixel of the source texture, but leave the alpha in tact. + * If the child is a color matrix node that doesn't touch the alpha, we can throw that away. */ + if (GSK_RENDER_NODE_TYPE (shadow_child) == GSK_COLOR_MATRIX_NODE && + !color_matrix_modifies_alpha (shadow_child)) + shadow_child = gsk_color_matrix_node_get_child (shadow_child); + + for (guint i = 0; i < n_shadows; i++) + { + const GskShadow *shadow = gsk_shadow_node_get_shadow (node, i); + const float dx = shadow->dx; + const float dy = shadow->dy; + GskGLRenderOffscreen offscreen = {0}; + graphene_rect_t bounds; + guint16 color[4]; + + if (RGBA_IS_CLEAR (&shadow->color)) + continue; + + if (node_is_invisible (shadow_child)) + continue; + + if (shadow->radius == 0 && + GSK_RENDER_NODE_TYPE (shadow_child) == GSK_TEXT_NODE) + { + if (dx != 0 || dy != 0) + { + gsk_gl_render_job_offset (job, dx, dy); + gsk_gl_render_job_visit_text_node (job, shadow_child, &shadow->color, TRUE); + gsk_gl_render_job_offset (job, -dx, -dy); + } + continue; + } + + if (shadow->radius > 0) + { + float min_x; + float min_y; + float max_x; + float max_y; + + offscreen.do_not_cache = TRUE; + + blur_node (job, + &offscreen, + shadow_child, + shadow->radius, + &min_x, &max_x, + &min_y, &max_y); + + bounds.origin.x = min_x - job->offset_x; + bounds.origin.y = min_y - job->offset_y; + bounds.size.width = max_x - min_x; + bounds.size.height = max_y - min_y; + + offscreen.was_offscreen = TRUE; + } + else + { + offscreen.bounds = &shadow_child->bounds; + offscreen.reset_clip = TRUE; + offscreen.do_not_cache = TRUE; + + if (!gsk_gl_render_job_visit_node_with_offscreen (job, shadow_child, &offscreen)) + g_assert_not_reached (); + + bounds = shadow_child->bounds; + } + + gsk_gl_render_job_offset (job, dx, dy); + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, coloring))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen.texture_id); + job->source_is_glyph_atlas = FALSE; + rgba_to_half (&shadow->color, color); + gsk_gl_render_job_draw_offscreen_with_color (job, &bounds, &offscreen, color); + gsk_gl_render_job_end_draw (job); + } + gsk_gl_render_job_offset (job, -dx, -dy); + } + + /* Now draw the child normally */ + gsk_gl_render_job_visit_node (job, original_child); +} + +static inline void +gsk_gl_render_job_visit_blur_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskRenderNode *child = gsk_blur_node_get_child (node); + float blur_radius = gsk_blur_node_get_radius (node); + GskGLRenderOffscreen offscreen = {0}; + GskTextureKey key; + gboolean cache_texture; + float min_x; + float max_x; + float min_y; + float max_y; + + g_assert (blur_radius > 0); + + if (node_is_invisible (child)) + return; + + key.pointer = node; + key.pointer_is_child = FALSE; + key.scale_x = job->scale_x; + key.scale_y = job->scale_y; + + offscreen.texture_id = gsk_gl_driver_lookup_texture (job->driver, &key, NULL); + cache_texture = offscreen.texture_id == 0; + + blur_node (job, + &offscreen, + child, + blur_radius, + &min_x, &max_x, &min_y, &max_y); + + g_assert (offscreen.texture_id != 0); + + if (cache_texture) + gsk_gl_driver_cache_texture (job->driver, &key, offscreen.texture_id); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen.texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_coords (job, + min_x, min_y, max_x, max_y, + 0, 1, 1, 0, + (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO } ); + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_blend_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskRenderNode *top_child = gsk_blend_node_get_top_child (node); + const GskRenderNode *bottom_child = gsk_blend_node_get_bottom_child (node); + GskGLRenderOffscreen top_offscreen = {0}; + GskGLRenderOffscreen bottom_offscreen = {0}; + + top_offscreen.bounds = &node->bounds; + top_offscreen.force_offscreen = TRUE; + top_offscreen.reset_clip = TRUE; + + bottom_offscreen.bounds = &node->bounds; + bottom_offscreen.force_offscreen = TRUE; + bottom_offscreen.reset_clip = TRUE; + + gsk_gl_render_job_set_modelview (job, gsk_transform_scale (NULL, fabsf (job->scale_x), fabsf (job->scale_y))); + + /* TODO: We create 2 textures here as big as the blend node, but both the + * start and the end node might be a lot smaller than that. */ + if (!gsk_gl_render_job_visit_node_with_offscreen (job, bottom_child, &bottom_offscreen)) + { + gsk_gl_render_job_pop_modelview (job); + + gsk_gl_render_job_visit_node (job, top_child); + return; + } + + g_assert (bottom_offscreen.was_offscreen); + + if (!gsk_gl_render_job_visit_node_with_offscreen (job, top_child, &top_offscreen)) + { + gsk_gl_render_job_pop_modelview (job); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + bottom_offscreen.texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_offscreen (job, &node->bounds, &bottom_offscreen); + gsk_gl_render_job_end_draw (job); + } + return; + } + + g_assert (top_offscreen.was_offscreen); + + gsk_gl_render_job_pop_modelview (job); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blend))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + bottom_offscreen.texture_id); + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_BLEND_SOURCE2, 0, + GL_TEXTURE_2D, + GL_TEXTURE1, + top_offscreen.texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_program_set_uniform1i (job->current_program, + UNIFORM_BLEND_MODE, 0, + gsk_blend_node_get_blend_mode (node)); + gsk_gl_render_job_draw_offscreen_rect (job, &node->bounds); + gsk_gl_render_job_end_draw (job); + } +} + +static gboolean +gsk_gl_render_job_texture_mask_for_color (GskGLRenderJob *job, + const GskRenderNode *mask, + const GskRenderNode *color, + const graphene_rect_t *bounds) +{ + int max_texture_size = job->command_queue->max_texture_size; + GdkTexture *texture = gsk_texture_node_get_texture (mask); + GdkRGBA rgba; + + get_color_node_color_as_srgb (color, &rgba); + if (RGBA_IS_CLEAR (&rgba)) + return TRUE; + + if G_LIKELY (texture->width <= max_texture_size && + texture->height <= max_texture_size && + gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, coloring))) + { + GskGLRenderOffscreen offscreen = {0}; + float scale_x = mask->bounds.size.width / texture->width; + float scale_y = mask->bounds.size.height / texture->height; + gboolean use_mipmap; + guint16 cc[4]; + + use_mipmap = (scale_x * fabsf (job->scale_x)) < 0.5 || + (scale_y * fabsf (job->scale_y)) < 0.5; + + rgba_to_half (&rgba, cc); + gsk_gl_render_job_upload_texture (job, texture, use_mipmap, &offscreen); + gsk_gl_program_set_uniform_texture_with_sync (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen.texture_id, + offscreen.has_mipmap ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR, + GL_LINEAR, + offscreen.sync); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_offscreen_with_color (job, bounds, &offscreen, cc); + gsk_gl_render_job_end_draw (job); + + return TRUE; + } + + return FALSE; +} + +static inline void +gsk_gl_render_job_visit_mask_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskRenderNode *source = gsk_mask_node_get_source (node); + const GskRenderNode *mask = gsk_mask_node_get_mask (node); + GskGLRenderOffscreen source_offscreen = {0}; + GskGLRenderOffscreen mask_offscreen = {0}; + + /* If the mask is a texture and the source is a color node + * then we can take a shortcut and avoid offscreens. + */ + if (GSK_RENDER_NODE_TYPE (mask) == GSK_TEXTURE_NODE && + GSK_RENDER_NODE_TYPE (source) == GSK_COLOR_NODE && + gsk_mask_node_get_mask_mode (node) == GSK_MASK_MODE_ALPHA) + { + if (gsk_gl_render_job_texture_mask_for_color (job, mask, source, &node->bounds)) + return; + } + + source_offscreen.bounds = &node->bounds; + source_offscreen.force_offscreen = TRUE; + source_offscreen.reset_clip = TRUE; + + mask_offscreen.bounds = &node->bounds; + mask_offscreen.force_offscreen = TRUE; + mask_offscreen.reset_clip = TRUE; + mask_offscreen.do_not_cache = TRUE; + + gsk_gl_render_job_set_modelview (job, gsk_transform_scale (NULL, fabsf (job->scale_x), fabsf (job->scale_y))); + + /* TODO: We create 2 textures here as big as the mask node, but both + * nodes might be a lot smaller than that. + */ + if (!gsk_gl_render_job_visit_node_with_offscreen (job, source, &source_offscreen)) + { + gsk_gl_render_job_pop_modelview (job); + gsk_gl_render_job_visit_node (job, source); + return; + } + + g_assert (source_offscreen.was_offscreen); + + if (!gsk_gl_render_job_visit_node_with_offscreen (job, mask, &mask_offscreen)) + { + gsk_gl_render_job_pop_modelview (job); + if (gsk_mask_node_get_mask_mode (node) == GSK_MASK_MODE_INVERTED_ALPHA) + gsk_gl_render_job_visit_node (job, source); + return; + } + + g_assert (mask_offscreen.was_offscreen); + + gsk_gl_render_job_pop_modelview (job); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, mask))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + source_offscreen.texture_id); + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_MASK_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE1, + mask_offscreen.texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_program_set_uniform1i (job->current_program, + UNIFORM_MASK_MODE, 0, + gsk_mask_node_get_mask_mode (node)); + gsk_gl_render_job_draw_offscreen_rect (job, &node->bounds); + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_color_matrix_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskRenderNode *child = gsk_color_matrix_node_get_child (node); + GskGLRenderOffscreen offscreen = {0}; + float offset[4]; + + if (node_is_invisible (child)) + return; + + offscreen.bounds = &node->bounds; + offscreen.reset_clip = TRUE; + + if (!gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreen)) + g_assert_not_reached (); + + g_assert (offscreen.texture_id > 0); + + graphene_vec4_to_float (gsk_color_matrix_node_get_color_offset (node), offset); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color_matrix))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen.texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_program_set_uniform_matrix (job->current_program, + UNIFORM_COLOR_MATRIX_COLOR_MATRIX, 0, + gsk_color_matrix_node_get_color_matrix (node)); + gsk_gl_program_set_uniform4fv (job->current_program, + UNIFORM_COLOR_MATRIX_COLOR_OFFSET, 0, + 1, + offset); + gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen); + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_gl_shader_node_fallback (GskGLRenderJob *job, + const GskRenderNode *node) +{ + guint16 pink[4] = { 15360, 13975, 14758, 15360 }; /* 255 105 180 */ + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color))) + { + gsk_gl_render_job_draw_rect_with_color (job, &node->bounds, pink); + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_gl_shader_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ +G_GNUC_BEGIN_IGNORE_DEPRECATIONS + GError *error = NULL; + GskGLShader *shader; + GskGLProgram *program; + int n_children; + + shader = gsk_gl_shader_node_get_shader (node); + program = gsk_gl_driver_lookup_shader (job->driver, shader, &error); + n_children = gsk_gl_shader_node_get_n_children (node); + + if G_UNLIKELY (program == NULL) + { + if (g_object_get_data (G_OBJECT (shader), "gsk-did-warn") == NULL) + { + g_object_set_data (G_OBJECT (shader), "gsk-did-warn", GUINT_TO_POINTER (1)); + g_warning ("Failed to compile gl shader: %s", error->message); + } + gsk_gl_render_job_visit_gl_shader_node_fallback (job, node); + g_clear_error (&error); + } + else + { + GskGLRenderOffscreen offscreens[4] = {{0}}; + const GskGLUniform *uniforms; + const guint8 *base; + GBytes *args; + int n_uniforms; + guint n_children; + + g_assert (n_children < G_N_ELEMENTS (offscreens)); + + n_children = gsk_container_node_get_n_children (node); + for (guint i = 0; i < n_children; i++) + { + const GskRenderNode *child = gsk_container_node_get_child (node, i); + + offscreens[i].bounds = &node->bounds; + offscreens[i].force_offscreen = TRUE; + offscreens[i].reset_clip = TRUE; + + if (!gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreens[i])) + return; + } + + args = gsk_gl_shader_node_get_args (node); + base = g_bytes_get_data (args, NULL); + uniforms = gsk_gl_shader_get_uniforms (shader, &n_uniforms); + + if (gsk_gl_render_job_begin_draw (job, program)) + { + for (guint i = 0; i < n_children; i++) + gsk_gl_program_set_uniform_texture (program, + UNIFORM_CUSTOM_TEXTURE1 + i, 0, + GL_TEXTURE_2D, + GL_TEXTURE0 + i, + offscreens[i].texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_program_set_uniform2f (program, + UNIFORM_CUSTOM_SIZE, 0, + node->bounds.size.width, + node->bounds.size.height); + for (guint i = 0; i < n_uniforms; i++) + { + const GskGLUniform *u = &uniforms[i]; + const guint8 *data = base + u->offset; + + switch (u->type) + { + default: + case GSK_GL_UNIFORM_TYPE_NONE: + break; + case GSK_GL_UNIFORM_TYPE_FLOAT: + gsk_gl_uniform_state_set1fv (job->command_queue->uniforms, + program->program_info, + UNIFORM_CUSTOM_ARG0 + i, + 0, 1, (const float *)data); + break; + case GSK_GL_UNIFORM_TYPE_INT: + gsk_gl_uniform_state_set1i (job->command_queue->uniforms, + program->program_info, + UNIFORM_CUSTOM_ARG0 + i, + 0, *(const gint32 *)data); + break; + case GSK_GL_UNIFORM_TYPE_UINT: + case GSK_GL_UNIFORM_TYPE_BOOL: + gsk_gl_uniform_state_set1ui (job->command_queue->uniforms, + program->program_info, + UNIFORM_CUSTOM_ARG0 + i, + 0, *(const guint32 *)data); + break; + case GSK_GL_UNIFORM_TYPE_VEC2: + gsk_gl_uniform_state_set2fv (job->command_queue->uniforms, + program->program_info, + UNIFORM_CUSTOM_ARG0 + i, + 0, 1, (const float *)data); + break; + case GSK_GL_UNIFORM_TYPE_VEC3: + gsk_gl_uniform_state_set3fv (job->command_queue->uniforms, + program->program_info, + UNIFORM_CUSTOM_ARG0 + i, + 0, 1, (const float *)data); + break; + case GSK_GL_UNIFORM_TYPE_VEC4: + gsk_gl_uniform_state_set4fv (job->command_queue->uniforms, + program->program_info, + UNIFORM_CUSTOM_ARG0 + i, + 0, 1, (const float *)data); + break; + } + } + gsk_gl_render_job_draw_offscreen_rect (job, &node->bounds); + gsk_gl_render_job_end_draw (job); + } + } +G_GNUC_END_IGNORE_DEPRECATIONS +} + +static void +gsk_gl_render_job_upload_texture (GskGLRenderJob *job, + GdkTexture *texture, + gboolean ensure_mipmap, + GskGLRenderOffscreen *offscreen) +{ + /* Don't put GL or dmabuf textures into icon caches, they are already on the GPU side */ + if (!ensure_mipmap && + gsk_gl_texture_library_can_cache ((GskGLTextureLibrary *)job->driver->icons_library, + texture->width, + texture->height) && + !(GDK_IS_GL_TEXTURE (texture) || GDK_IS_DMABUF_TEXTURE (texture))) + { + const GskGLIconData *icon_data; + + gsk_gl_icon_library_lookup_or_add (job->driver->icons_library, texture, &icon_data); + offscreen->texture_id = GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (icon_data); + memcpy (&offscreen->area, &icon_data->entry.area, sizeof offscreen->area); + offscreen->has_mipmap = FALSE; + } + else + { + /* Only generate a mipmap if it does not make use reupload + * a GL texture which we could otherwise use directly. + */ + if (GDK_IS_GL_TEXTURE (texture) && + gdk_gl_context_is_shared (gdk_gl_texture_get_context (GDK_GL_TEXTURE (texture)), + job->command_queue->context)) + ensure_mipmap = gdk_gl_texture_has_mipmap (GDK_GL_TEXTURE (texture)); + else if (GDK_IS_DMABUF_TEXTURE (texture)) + ensure_mipmap = FALSE; + + offscreen->texture_id = gsk_gl_driver_load_texture (job->driver, texture, ensure_mipmap); + init_full_texture_region (offscreen); + offscreen->has_mipmap = ensure_mipmap; + + if (GDK_IS_GL_TEXTURE (texture) && + offscreen->texture_id == gdk_gl_texture_get_id (GDK_GL_TEXTURE (texture))) + offscreen->sync = gdk_gl_texture_get_sync (GDK_GL_TEXTURE (texture)); + } +} + +static inline void +gsk_gl_render_job_visit_texture (GskGLRenderJob *job, + GdkTexture *texture, + const graphene_rect_t *bounds) +{ + int max_texture_size = job->command_queue->max_texture_size; + float scale_x = bounds->size.width / texture->width; + float scale_y = bounds->size.height / texture->height; + gboolean use_mipmap; + + use_mipmap = (scale_x * fabsf (job->scale_x)) < 0.5 || + (scale_y * fabsf (job->scale_y)) < 0.5; + + if G_LIKELY (texture->width <= max_texture_size && + texture->height <= max_texture_size) + { + GskGLRenderOffscreen offscreen = {0}; + + gsk_gl_render_job_upload_texture (job, texture, use_mipmap, &offscreen); + + g_assert (offscreen.texture_id); + g_assert (offscreen.was_offscreen == FALSE); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + { + gsk_gl_program_set_uniform_texture_with_sync (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen.texture_id, + offscreen.has_mipmap ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR, + GL_LINEAR, + offscreen.sync); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_offscreen (job, bounds, &offscreen); + gsk_gl_render_job_end_draw (job); + } + } + else + { + float min_x = job->offset_x + bounds->origin.x; + float min_y = job->offset_y + bounds->origin.y; + GskGLTextureSlice *slices = NULL; + guint n_slices = 0; + + gsk_gl_driver_slice_texture (job->driver, texture, use_mipmap, &slices, &n_slices); + + g_assert (slices != NULL); + g_assert (n_slices > 0); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + { + for (unsigned int i = 0; i < n_slices; i++) + { + const GskGLTextureSlice *slice = &slices[i]; + float x1, x2, y1, y2; + + x1 = min_x + (scale_x * slice->rect.x); + x2 = x1 + (slice->rect.width * scale_x); + y1 = min_y + (scale_y * slice->rect.y); + y2 = y1 + (slice->rect.height * scale_y); + + if (i > 0) + gsk_gl_render_job_split_draw (job); + gsk_gl_program_set_uniform_texture_with_filter (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + slice->texture_id, + use_mipmap ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR, + GL_LINEAR); + job->source_is_glyph_atlas = FALSE; + + gsk_gl_render_job_draw_coords (job, + x1, y1, x2, y2, + slice->area.x, slice->area.y, + slice->area.x2, slice->area.y2, + (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO }); + } + + gsk_gl_render_job_end_draw (job); + } + } +} + +static inline void +gsk_gl_render_job_visit_texture_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + GdkTexture *texture = gsk_texture_node_get_texture (node); + const graphene_rect_t *bounds = &node->bounds; + + gsk_gl_render_job_visit_texture (job, texture, bounds); +} + +static inline void +gsk_gl_render_job_visit_texture_scale_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + GdkTexture *texture = gsk_texture_scale_node_get_texture (node); + const graphene_rect_t *bounds = &node->bounds; + GskScalingFilter filter = gsk_texture_scale_node_get_filter (node); + int min_filters[] = { GL_LINEAR, GL_NEAREST, GL_LINEAR_MIPMAP_LINEAR }; + int mag_filters[] = { GL_LINEAR, GL_NEAREST, GL_LINEAR }; + int min_filter = min_filters[filter]; + int mag_filter = mag_filters[filter]; + int max_texture_size = job->command_queue->max_texture_size; + graphene_rect_t clip_rect; + GskGLRenderTarget *render_target; + graphene_rect_t viewport; + graphene_rect_t prev_viewport; + graphene_matrix_t prev_projection; + float prev_alpha; + guint prev_fbo; + float u0, u1, v0, v1; + GskTextureKey key; + guint texture_id; + gboolean need_mipmap; + gboolean has_mipmap; + + gsk_gl_render_job_untransform_bounds (job, &job->current_clip->rect.bounds, &clip_rect); + + if (!gsk_rect_intersection (bounds, &clip_rect, &clip_rect)) + return; + + key.pointer = node; + key.pointer_is_child = TRUE; + key.parent_rect = clip_rect; + key.scale_x = 1.; + key.scale_y = 1.; + + need_mipmap = (filter == GSK_SCALING_FILTER_TRILINEAR); + + texture_id = gsk_gl_driver_lookup_texture (job->driver, &key, &has_mipmap); + + if (texture_id != 0 && (!need_mipmap || has_mipmap)) + goto render_texture; + + viewport = GRAPHENE_RECT_INIT (0, 0, + clip_rect.size.width, + clip_rect.size.height); + + if (!gsk_gl_driver_create_render_target (job->driver, + (int) ceilf (clip_rect.size.width), + (int) ceilf (clip_rect.size.height), + get_target_format (job, node), + &render_target)) + { + gsk_gl_render_job_visit_texture (job, texture, bounds); + return; + } + + gsk_gl_render_job_set_viewport (job, &viewport, &prev_viewport); + gsk_gl_render_job_set_projection_from_rect (job, &viewport, &prev_projection); + gsk_gl_render_job_set_modelview (job, NULL); + prev_alpha = gsk_gl_render_job_set_alpha (job, 1.0f); + gsk_gl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT_FROM_RECT (viewport)); + + prev_fbo = gsk_gl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id); + gsk_gl_command_queue_clear (job->command_queue, 0, &viewport); + + if G_LIKELY (texture->width <= max_texture_size && + texture->height <= max_texture_size) + { + gpointer sync; + + texture_id = gsk_gl_driver_load_texture (job->driver, texture, need_mipmap); + + if (GDK_IS_GL_TEXTURE (texture) && texture_id == gdk_gl_texture_get_id (GDK_GL_TEXTURE (texture))) + sync = gdk_gl_texture_get_sync (GDK_GL_TEXTURE (texture)); + else + sync = NULL; + + u0 = (clip_rect.origin.x - bounds->origin.x) / bounds->size.width; + v0 = (clip_rect.origin.y - bounds->origin.y) / bounds->size.height; + u1 = (clip_rect.origin.x + clip_rect.size.width - bounds->origin.x) / bounds->size.width; + v1 = (clip_rect.origin.y + clip_rect.size.height - bounds->origin.y) / bounds->size.height; + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + { + gsk_gl_program_set_uniform_texture_with_sync (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + texture_id, + min_filter, + mag_filter, + sync); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_coords (job, + 0, 0, clip_rect.size.width, clip_rect.size.height, + u0, v0, u1, v1, + (guint16[]) { FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO }); + gsk_gl_render_job_end_draw (job); + } + } + else + { + float scale_x = bounds->size.width / texture->width; + float scale_y = bounds->size.height / texture->height; + GskGLTextureSlice *slices = NULL; + guint n_slices = 0; + + gsk_gl_driver_slice_texture (job->driver, texture, need_mipmap, &slices, &n_slices); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + { + for (guint i = 0; i < n_slices; i++) + { + const GskGLTextureSlice *slice = &slices[i]; + graphene_rect_t slice_bounds; + + slice_bounds.origin.x = bounds->origin.x - clip_rect.origin.x + slice->rect.x * scale_x; + slice_bounds.origin.y = bounds->origin.y - clip_rect.origin.y + slice->rect.y * scale_y; + slice_bounds.size.width = slice->rect.width * scale_x; + slice_bounds.size.height = slice->rect.height * scale_y; + + if (!gsk_rect_intersects (&slice_bounds, &viewport)) + continue; + + if (i > 0) + gsk_gl_render_job_split_draw (job); + + gsk_gl_program_set_uniform_texture_with_filter (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + slice->texture_id, + min_filter, + mag_filter); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_coords (job, + slice_bounds.origin.x, + slice_bounds.origin.y, + slice_bounds.origin.x + slice_bounds.size.width, + slice_bounds.origin.y + slice_bounds.size.height, + slice->area.x, slice->area.y, + slice->area.x2, slice->area.y2, + (guint16[]){ FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO } ); + } + + gsk_gl_render_job_end_draw (job); + } + } + + gsk_gl_render_job_pop_clip (job); + gsk_gl_render_job_pop_modelview (job); + gsk_gl_render_job_set_viewport (job, &prev_viewport, NULL); + gsk_gl_render_job_set_projection (job, &prev_projection); + gsk_gl_render_job_set_alpha (job, prev_alpha); + gsk_gl_command_queue_bind_framebuffer (job->command_queue, prev_fbo); + + texture_id = gsk_gl_driver_release_render_target (job->driver, render_target, FALSE); + gsk_gl_driver_cache_texture (job->driver, &key, texture_id); + +render_texture: + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_coords (job, + job->offset_x + clip_rect.origin.x, + job->offset_y + clip_rect.origin.y, + job->offset_x + clip_rect.origin.x + clip_rect.size.width, + job->offset_y + clip_rect.origin.y + clip_rect.size.height, + 0, clip_rect.size.width / ceilf (clip_rect.size.width), + clip_rect.size.height / ceilf (clip_rect.size.height), 0, + (guint16[]){ FP16_ZERO, FP16_ZERO, FP16_ZERO, FP16_ZERO } ); + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_repeat_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + const GskRenderNode *child = gsk_repeat_node_get_child (node); + const graphene_rect_t *child_bounds = gsk_repeat_node_get_child_bounds (node); + GskGLRenderOffscreen offscreen = {0}; + + if (node_is_invisible (child)) + return; + + if (!gsk_rect_equal (child_bounds, &child->bounds)) + { + /* TODO: implement these repeat nodes. */ + gsk_gl_render_job_visit_as_fallback (job, node); + return; + } + + /* If the size of the repeat node is smaller than the size of the + * child node, we don't repeat at all and can just draw that part + * of the child texture... */ + if (gsk_rect_contains_rect (child_bounds, &node->bounds)) + { + gsk_gl_render_job_visit_clipped_child (job, child, &node->bounds); + return; + } + + offscreen.bounds = &child->bounds; + offscreen.reset_clip = TRUE; + + if (!gsk_gl_render_job_visit_node_with_offscreen (job, child, &offscreen)) + g_assert_not_reached (); + + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, repeat))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + offscreen.texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_program_set_uniform4f (job->current_program, + UNIFORM_REPEAT_CHILD_BOUNDS, 0, + (node->bounds.origin.x - child_bounds->origin.x) / child_bounds->size.width, + (node->bounds.origin.y - child_bounds->origin.y) / child_bounds->size.height, + node->bounds.size.width / child_bounds->size.width, + node->bounds.size.height / child_bounds->size.height); + gsk_gl_program_set_uniform4f (job->current_program, + UNIFORM_REPEAT_TEXTURE_RECT, 0, + offscreen.area.x, + offscreen.was_offscreen ? offscreen.area.y2 : offscreen.area.y, + offscreen.area.x2, + offscreen.was_offscreen ? offscreen.area.y : offscreen.area.y2); + gsk_gl_render_job_draw_offscreen (job, &node->bounds, &offscreen); + gsk_gl_render_job_end_draw (job); + } +} + +static inline void +gsk_gl_render_job_visit_subsurface_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + GdkSubsurface *subsurface; + + subsurface = (GdkSubsurface *) gsk_subsurface_node_get_subsurface (node); + + if (subsurface && + gdk_subsurface_get_texture (subsurface) && + gdk_subsurface_get_parent (subsurface) == gdk_gl_context_get_surface (job->command_queue->context)) + { + if (!gdk_subsurface_is_above_parent (subsurface)) + { + if (job->offscreen_count > 0) + { + GDK_DISPLAY_DEBUG (gdk_gl_context_get_display (job->command_queue->context), OFFLOAD, "Hiding subsurface %p in offscreen context", subsurface); + gdk_subsurface_detach (subsurface); + gsk_gl_render_job_visit_node (job, gsk_subsurface_node_get_child (node)); + } + else if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, color))) + { + /* Clear the area so we can see through */ + GskGLCommandBatch *batch; + guint16 color[4]; + rgba_to_half (&GDK_RGBA_TRANSPARENT, color); + + batch = gsk_gl_command_queue_get_batch (job->command_queue); + batch->draw.blend = 0; + gsk_gl_render_job_draw_rect_with_color (job, &node->bounds, color); + gsk_gl_render_job_end_draw (job); + } + } + } + else + { + gsk_gl_render_job_visit_node (job, gsk_subsurface_node_get_child (node)); + } +} + +static void +gsk_gl_render_job_visit_node (GskGLRenderJob *job, + const GskRenderNode *node) +{ + gboolean has_clip; + + g_assert (job != NULL); + g_assert (node != NULL); + g_assert (GSK_IS_GL_DRIVER (job->driver)); + g_assert (GSK_IS_GL_COMMAND_QUEUE (job->command_queue)); + + if (node_is_invisible (node)) + return; + + if (!gsk_gl_render_job_update_clip (job, &node->bounds, &has_clip)) + return; + + switch (GSK_RENDER_NODE_TYPE (node)) + { + case GSK_BLEND_NODE: + gsk_gl_render_job_visit_blend_node (job, node); + break; + + case GSK_BLUR_NODE: + if (gsk_blur_node_get_radius (node) > 0) + gsk_gl_render_job_visit_blur_node (job, node); + else + gsk_gl_render_job_visit_node (job, gsk_blur_node_get_child (node)); + break; + + case GSK_BORDER_NODE: + if (gsk_border_node_get_uniform_color (node) && + gsk_rounded_rect_is_rectilinear (gsk_border_node_get_outline (node))) + gsk_gl_render_job_visit_rect_border_node (job, node); + else + gsk_gl_render_job_visit_border_node (job, node); + break; + + case GSK_CLIP_NODE: + gsk_gl_render_job_visit_clip_node (job, node); + break; + + case GSK_COLOR_NODE: + gsk_gl_render_job_visit_color_node (job, node); + break; + + case GSK_COLOR_MATRIX_NODE: + gsk_gl_render_job_visit_color_matrix_node (job, node); + break; + + case GSK_CONIC_GRADIENT_NODE: + if (gsk_conic_gradient_node_get_n_color_stops (node) < MAX_GRADIENT_STOPS) + gsk_gl_render_job_visit_conic_gradient_node (job, node); + else + gsk_gl_render_job_visit_as_fallback (job, node); + break; + + case GSK_CONTAINER_NODE: + { + guint n_children = gsk_container_node_get_n_children (node); + + for (guint i = 0; i < n_children; i++) + { + const GskRenderNode *child = gsk_container_node_get_child (node, i); + + if (i + 1 < n_children && + job->current_clip->is_fully_contained && + GSK_RENDER_NODE_TYPE (child) == GSK_ROUNDED_CLIP_NODE) + { + const GskRenderNode *grandchild = gsk_rounded_clip_node_get_child (child); + const GskRenderNode *child2 = gsk_container_node_get_child (node, i + 1); + if (GSK_RENDER_NODE_TYPE (grandchild) == GSK_COLOR_NODE && + GSK_RENDER_NODE_TYPE (child2) == GSK_BORDER_NODE && + gsk_border_node_get_uniform_color (child2) && + rounded_rect_equal (gsk_rounded_clip_node_get_clip (child), + gsk_border_node_get_outline (child2))) + { + gsk_gl_render_job_visit_css_background (job, child, child2); + i++; + continue; + } + } + + gsk_gl_render_job_visit_node (job, child); + } + } + break; + + case GSK_CROSS_FADE_NODE: + { + const GskRenderNode *start_node = gsk_cross_fade_node_get_start_child (node); + const GskRenderNode *end_node = gsk_cross_fade_node_get_end_child (node); + float progress = gsk_cross_fade_node_get_progress (node); + + if (progress <= 0.0f) + gsk_gl_render_job_visit_node (job, gsk_cross_fade_node_get_start_child (node)); + else if (progress >= 1.0f || equal_texture_nodes (start_node, end_node)) + gsk_gl_render_job_visit_node (job, gsk_cross_fade_node_get_end_child (node)); + else + gsk_gl_render_job_visit_cross_fade_node (job, node); + } + break; + + case GSK_DEBUG_NODE: + /* Debug nodes are ignored because draws get reordered anyway */ + gsk_gl_render_job_visit_node (job, gsk_debug_node_get_child (node)); + break; + + case GSK_GL_SHADER_NODE: + gsk_gl_render_job_visit_gl_shader_node (job, node); + break; + + case GSK_INSET_SHADOW_NODE: + if (gsk_inset_shadow_node_get_blur_radius (node) > 0) + gsk_gl_render_job_visit_blurred_inset_shadow_node (job, node); + else + gsk_gl_render_job_visit_unblurred_inset_shadow_node (job, node); + break; + + case GSK_LINEAR_GRADIENT_NODE: + case GSK_REPEATING_LINEAR_GRADIENT_NODE: + if (gsk_linear_gradient_node_get_n_color_stops (node) < MAX_GRADIENT_STOPS) + gsk_gl_render_job_visit_linear_gradient_node (job, node); + else + gsk_gl_render_job_visit_as_fallback (job, node); + break; + + case GSK_MASK_NODE: + gsk_gl_render_job_visit_mask_node (job, node); + break; + + case GSK_OPACITY_NODE: + gsk_gl_render_job_visit_opacity_node (job, node); + break; + + case GSK_OUTSET_SHADOW_NODE: + if (gsk_outset_shadow_node_get_blur_radius (node) > 0) + gsk_gl_render_job_visit_blurred_outset_shadow_node (job, node); + else + gsk_gl_render_job_visit_unblurred_outset_shadow_node (job, node); + break; + + case GSK_RADIAL_GRADIENT_NODE: + case GSK_REPEATING_RADIAL_GRADIENT_NODE: + if (gsk_radial_gradient_node_get_n_color_stops (node) < MAX_GRADIENT_STOPS) + gsk_gl_render_job_visit_radial_gradient_node (job, node); + else + gsk_gl_render_job_visit_as_fallback (job, node); + break; + + case GSK_REPEAT_NODE: + gsk_gl_render_job_visit_repeat_node (job, node); + break; + + case GSK_ROUNDED_CLIP_NODE: + gsk_gl_render_job_visit_rounded_clip_node (job, node); + break; + + case GSK_SHADOW_NODE: + gsk_gl_render_job_visit_shadow_node (job, node); + break; + + case GSK_TEXT_NODE: + { + GdkRGBA rgba; + + gdk_color_to_float (gsk_text_node_get_gdk_color (node), GDK_COLOR_STATE_SRGB, (float *) &rgba); + gsk_gl_render_job_visit_text_node (job, node, &rgba, FALSE); + } + break; + + case GSK_TEXTURE_NODE: + gsk_gl_render_job_visit_texture_node (job, node); + break; + + case GSK_TEXTURE_SCALE_NODE: + gsk_gl_render_job_visit_texture_scale_node (job, node); + break; + + case GSK_TRANSFORM_NODE: + gsk_gl_render_job_visit_transform_node (job, node); + break; + + case GSK_CAIRO_NODE: + gsk_gl_render_job_visit_as_fallback (job, node); + break; + + case GSK_FILL_NODE: + gsk_gl_render_job_visit_as_fallback (job, node); + break; + + case GSK_STROKE_NODE: + gsk_gl_render_job_visit_as_fallback (job, node); + break; + + case GSK_SUBSURFACE_NODE: + gsk_gl_render_job_visit_subsurface_node (job, node); + break; + + case GSK_NOT_A_RENDER_NODE: + default: + g_assert_not_reached (); + break; + } + + if (has_clip) + gsk_gl_render_job_pop_clip (job); +} + +static gboolean +gsk_gl_render_job_visit_node_with_offscreen (GskGLRenderJob *job, + const GskRenderNode *node, + GskGLRenderOffscreen *offscreen) +{ + GskTextureKey key; + guint cached_id; + + g_assert (job != NULL); + g_assert (node != NULL); + g_assert (offscreen != NULL); + g_assert (offscreen->texture_id == 0); + g_assert (offscreen->bounds != NULL); + + if (node_is_invisible (node)) + { + /* Just to be safe. */ + offscreen->texture_id = 0; + init_full_texture_region (offscreen); + offscreen->was_offscreen = FALSE; + return FALSE; + } + + if (GSK_RENDER_NODE_TYPE (node) == GSK_TEXTURE_NODE && + !offscreen->force_offscreen) + { + GdkTexture *texture = gsk_texture_node_get_texture (node); + gsk_gl_render_job_upload_texture (job, texture, FALSE, offscreen); + return TRUE; + } + + key.pointer = node; + key.pointer_is_child = TRUE; /* Don't conflict with the child using the cache too */ + key.parent_rect = *offscreen->bounds; + key.scale_x = job->scale_x; + key.scale_y = job->scale_y; + + float offset_x = job->offset_x; + float offset_y = job->offset_y; + gboolean flipped_x = job->scale_x < 0; + gboolean flipped_y = job->scale_y < 0; + graphene_rect_t viewport; + gboolean reset_clip = FALSE; + + if (flipped_x || flipped_y) + { + GskTransform *transform = gsk_transform_scale (NULL, + flipped_x ? -1 : 1, + flipped_y ? -1 : 1); + gsk_gl_render_job_push_modelview (job, transform); + gsk_transform_unref (transform); + } + + gsk_gl_render_job_transform_bounds (job, offscreen->bounds, &viewport); + + float aligned_x = floorf (viewport.origin.x); + float padding_left = viewport.origin.x - aligned_x; + float aligned_width = ceilf (viewport.size.width + padding_left); + float padding_right = aligned_width - viewport.size.width - padding_left; + + float aligned_y = floorf (viewport.origin.y); + float padding_top = viewport.origin.y - aligned_y; + float aligned_height = ceilf (viewport.size.height + padding_top); + float padding_bottom = aligned_height - viewport.size.height - padding_top; + + /* Tweak the scale factor so that the required texture doesn't + * exceed the max texture limit. This will render with a lower + * resolution, but this is better than clipping. + */ + + g_assert (job->command_queue->max_texture_size > 0); + + float downscale_x = 1; + float downscale_y = 1; + int texture_width; + int texture_height; + int max_texture_size = job->command_queue->max_texture_size; + + if (aligned_width > max_texture_size) + downscale_x = (float)max_texture_size / viewport.size.width; + + if (aligned_height > max_texture_size) + downscale_y = (float)max_texture_size / viewport.size.height; + + if (downscale_x != 1 || downscale_y != 1) + { + GskTransform *transform = gsk_transform_scale (NULL, downscale_x, downscale_y); + gsk_gl_render_job_push_modelview (job, transform); + gsk_transform_unref (transform); + gsk_gl_render_job_transform_bounds (job, offscreen->bounds, &viewport); + gsk_rect_scale (&viewport, downscale_x, downscale_y, &viewport); + } + + if (downscale_x == 1) + { + viewport.origin.x = aligned_x; + viewport.size.width = aligned_width; + offscreen->area.x = padding_left / aligned_width; + offscreen->area.x2 = 1.0f - (padding_right / aligned_width); + texture_width = aligned_width; + } + else + { + offscreen->area.x = 0; + offscreen->area.x2 = 1; + texture_width = max_texture_size; + } + + if (downscale_y == 1) + { + viewport.origin.y = aligned_y; + viewport.size.height = aligned_height; + offscreen->area.y = padding_bottom / aligned_height; + offscreen->area.y2 = 1.0f - padding_top / aligned_height; + texture_height = aligned_height; + } + else + { + offscreen->area.y = 0; + offscreen->area.y2 = 1; + texture_height = max_texture_size; + } + + /* Check if we've already cached the drawn texture. */ + cached_id = gsk_gl_driver_lookup_texture (job->driver, &key, NULL); + + if (cached_id != 0) + { + if (downscale_x != 1 || downscale_y != 1) + gsk_gl_render_job_pop_modelview (job); + if (flipped_x || flipped_y) + gsk_gl_render_job_pop_modelview (job); + offscreen->texture_id = cached_id; + /* We didn't render it offscreen, but hand out an offscreen texture id */ + offscreen->was_offscreen = TRUE; + return TRUE; + } + + GskGLRenderTarget *render_target; + graphene_matrix_t prev_projection; + graphene_rect_t prev_viewport; + float prev_alpha; + guint prev_fbo; + + if (!gsk_gl_driver_create_render_target (job->driver, + texture_width, texture_height, + get_target_format (job, node), + &render_target)) + g_assert_not_reached (); + + if (gdk_gl_context_has_feature (job->command_queue->context, GDK_GL_FEATURE_DEBUG)) + { + gdk_gl_context_label_object_printf (job->command_queue->context, + GL_TEXTURE, + render_target->texture_id, + "Offscreen<%s> %d", + g_type_name_from_instance ((GTypeInstance *) node), + render_target->texture_id); + gdk_gl_context_label_object_printf (job->command_queue->context, + GL_FRAMEBUFFER, + render_target->framebuffer_id, + "Offscreen<%s> FB %d", + g_type_name_from_instance ((GTypeInstance *) node), + render_target->framebuffer_id); + } + + gsk_gl_render_job_set_viewport (job, &viewport, &prev_viewport); + gsk_gl_render_job_set_projection_from_rect (job, &job->viewport, &prev_projection); + prev_alpha = gsk_gl_render_job_set_alpha (job, 1.0f); + + prev_fbo = gsk_gl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id); + gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport); + + if (offscreen->reset_clip) + { + gsk_gl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT_FROM_RECT (job->viewport)); + reset_clip = TRUE; + } + else if (flipped_x || flipped_y || downscale_x != 1 || downscale_y != 1) + { + GskRoundedRect new_clip; + float scale_x = flipped_x ? - downscale_x : downscale_x; + float scale_y = flipped_y ? - downscale_y : downscale_y; + + gsk_rect_scale (&job->current_clip->rect.bounds, scale_x, scale_y, &new_clip.bounds); + rounded_rect_scale_corners (&job->current_clip->rect, &new_clip, scale_x, scale_y); + gsk_gl_render_job_push_clip (job, &new_clip); + reset_clip = TRUE; + } + + job->offscreen_count++; + + gsk_gl_render_job_visit_node (job, node); + + job->offscreen_count--; + + if (reset_clip) + gsk_gl_render_job_pop_clip (job); + + if (downscale_x != 1 || downscale_y != 1) + gsk_gl_render_job_pop_modelview (job); + + if (flipped_x || flipped_y) + gsk_gl_render_job_pop_modelview (job); + + gsk_gl_render_job_set_viewport (job, &prev_viewport, NULL); + gsk_gl_render_job_set_projection (job, &prev_projection); + gsk_gl_render_job_set_alpha (job, prev_alpha); + gsk_gl_command_queue_bind_framebuffer (job->command_queue, prev_fbo); + + job->offset_x = offset_x; + job->offset_y = offset_y; + + offscreen->was_offscreen = TRUE; + offscreen->texture_id = gsk_gl_driver_release_render_target (job->driver, + render_target, + FALSE); + + if (!offscreen->do_not_cache) + gsk_gl_driver_cache_texture (job->driver, &key, offscreen->texture_id); + + return TRUE; +} + +void +gsk_gl_render_job_render_flipped (GskGLRenderJob *job, + GskRenderNode *root) +{ + graphene_matrix_t proj; + guint framebuffer_id; + guint texture_id; + guint surface_height; + + g_return_if_fail (job != NULL); + g_return_if_fail (root != NULL); + g_return_if_fail (GSK_IS_GL_DRIVER (job->driver)); + + surface_height = job->viewport.size.height; + + graphene_matrix_init_ortho (&proj, + job->viewport.origin.x, + job->viewport.origin.x + job->viewport.size.width, + job->viewport.origin.y, + job->viewport.origin.y + job->viewport.size.height, + ORTHO_NEAR_PLANE, + ORTHO_FAR_PLANE); + graphene_matrix_scale (&proj, 1, -1, 1); + + if (!gsk_gl_command_queue_create_render_target (job->command_queue, + MAX (1, job->viewport.size.width), + MAX (1, job->viewport.size.height), + job->target_format, + &framebuffer_id, &texture_id)) + return; + + /* Setup drawing to our offscreen texture/framebuffer which is flipped */ + gsk_gl_command_queue_bind_framebuffer (job->command_queue, framebuffer_id); + gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport); + + /* Visit all nodes creating batches */ + gdk_gl_context_push_debug_group (job->command_queue->context, "Building command queue"); + gsk_gl_render_job_visit_node (job, root); + gdk_gl_context_pop_debug_group (job->command_queue->context); + + /* Now draw to our real destination, but flipped */ + gsk_gl_render_job_set_alpha (job, 1.0f); + gsk_gl_command_queue_bind_framebuffer (job->command_queue, job->framebuffer); + gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport); + if (gsk_gl_render_job_begin_draw (job, CHOOSE_PROGRAM (job, blit))) + { + gsk_gl_program_set_uniform_texture (job->current_program, + UNIFORM_SHARED_SOURCE, 0, + GL_TEXTURE_2D, + GL_TEXTURE0, + texture_id); + job->source_is_glyph_atlas = FALSE; + gsk_gl_render_job_draw_rect (job, &job->viewport); + gsk_gl_render_job_end_draw (job); + } + + gdk_gl_context_push_debug_group (job->command_queue->context, "Executing command queue"); + gsk_gl_command_queue_execute (job->command_queue, surface_height, 1, NULL, job->default_framebuffer); + gdk_gl_context_pop_debug_group (job->command_queue->context); + + glDeleteFramebuffers (1, &framebuffer_id); + glDeleteTextures (1, &texture_id); +} + +void +gsk_gl_render_job_render (GskGLRenderJob *job, + GskRenderNode *root) +{ + G_GNUC_UNUSED gint64 start_time; + float scale; + guint surface_height; + + g_return_if_fail (job != NULL); + g_return_if_fail (root != NULL); + g_return_if_fail (GSK_IS_GL_DRIVER (job->driver)); + + scale = MAX (job->scale_x, job->scale_y); + surface_height = job->viewport.size.height; + + gsk_gl_command_queue_make_current (job->command_queue); + + /* Build the command queue using the shared GL context for all renderers + * on the same display. + */ + start_time = GDK_PROFILER_CURRENT_TIME; + gdk_gl_context_push_debug_group (job->command_queue->context, "Building command queue"); + gsk_gl_command_queue_bind_framebuffer (job->command_queue, job->framebuffer); + if (job->clear_framebuffer) + gsk_gl_command_queue_clear (job->command_queue, 0, &job->viewport); + gsk_gl_render_job_visit_node (job, root); + gdk_gl_context_pop_debug_group (job->command_queue->context); + + gdk_profiler_end_mark (start_time, "Build GL command queue", ""); + +#if 0 + /* At this point the atlases have uploaded content while we processed + * nodes but have not necessarily been used by the commands in the queue. + */ + gsk_gl_driver_save_atlases_to_png (job->driver, NULL); +#endif + + /* But now for executing the command queue, we want to use the context + * that was provided to us when creating the render job as framebuffer 0 + * is bound to that context. + */ + start_time = GDK_PROFILER_CURRENT_TIME; + gsk_gl_command_queue_make_current (job->command_queue); + gdk_gl_context_push_debug_group (job->command_queue->context, "Executing command queue"); + gsk_gl_command_queue_execute (job->command_queue, surface_height, scale, job->region, job->default_framebuffer); + gdk_gl_context_pop_debug_group (job->command_queue->context); + gdk_profiler_end_mark (start_time, "Execute GL command queue", ""); +} + +static int +get_framebuffer_format (GdkGLContext *context, + guint framebuffer) +{ + int size; + + /* Both the query below and the float formats it can select need GL 3.0; + * before that this used to be NULL, ie "any desktop GL", which was fine + * while GTK required GL 3.3. + */ + if (!gdk_gl_context_check_version (context, "3.0", "3.0")) + return GL_RGBA8; + + glBindFramebuffer (GL_FRAMEBUFFER, framebuffer); + glGetFramebufferAttachmentParameteriv (GL_FRAMEBUFFER, + framebuffer ? GL_COLOR_ATTACHMENT0 + : gdk_gl_context_get_use_es (context) ? GL_BACK + : GL_BACK_LEFT, + GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE, &size); + + if (size > 16) + return GL_RGBA32F; + else if (size > 8) + return GL_RGBA16F; + else + return GL_RGBA8; +} + +GskGLRenderJob * +gsk_gl_render_job_new (GskGLDriver *driver, + const graphene_rect_t *viewport, + float scale, + const cairo_region_t *region, + guint framebuffer, + gboolean clear_framebuffer) +{ + const graphene_rect_t *clip_rect = viewport; + graphene_rect_t transformed_extents; + GskGLRenderJob *job; + GdkGLContext *context; + GLint default_framebuffer = 0; + + g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL); + g_return_val_if_fail (viewport != NULL, NULL); + g_return_val_if_fail (scale > 0, NULL); + + /* Check for non-standard framebuffer binding as we might not be using + * the default framebuffer on systems like macOS where we've bound an + * IOSurface to a GL_TEXTURE_RECTANGLE. Otherwise, no scissor clip will + * be applied in the command queue causing overdrawing. + */ + context = driver->command_queue->context; + default_framebuffer = GDK_GL_CONTEXT_GET_CLASS (context)->get_default_framebuffer (context); + if (framebuffer == 0 && default_framebuffer != 0) + framebuffer = default_framebuffer; + + job = g_new0 (GskGLRenderJob, 1); + job->driver = g_object_ref (driver); + job->command_queue = job->driver->command_queue; + clips_init (&job->clip); + modelviews_init (&job->modelview); + job->framebuffer = framebuffer; + job->clear_framebuffer = !!clear_framebuffer; + job->default_framebuffer = default_framebuffer; + job->offset_x = 0; + job->offset_y = 0; + job->scale_x = scale; + job->scale_y = scale; + job->viewport = *viewport; + job->target_format = get_framebuffer_format (job->command_queue->context, framebuffer); + + gsk_gl_render_job_set_alpha (job, 1.0f); + gsk_gl_render_job_set_projection_from_rect (job, viewport, NULL); + gsk_gl_render_job_set_modelview (job, gsk_transform_scale (NULL, scale, scale)); + + /* Setup our initial clip. If region is NULL then we are drawing the + * whole viewport. Otherwise, we need to convert the region to a + * bounding box and clip based on that. + */ + + if (region != NULL) + { + cairo_rectangle_int_t extents; + + cairo_region_get_extents (region, &extents); + gsk_gl_render_job_transform_bounds (job, + &GRAPHENE_RECT_INIT (extents.x, + extents.y, + extents.width, + extents.height), + &transformed_extents); + clip_rect = &transformed_extents; + job->region = cairo_region_create_rectangle (&extents); + } + + gsk_gl_render_job_push_clip (job, + &GSK_ROUNDED_RECT_INIT (clip_rect->origin.x, + clip_rect->origin.y, + clip_rect->size.width, + clip_rect->size.height)); + + return job; +} + +void +gsk_gl_render_job_free (GskGLRenderJob *job) +{ + job->current_modelview = NULL; + job->current_clip = NULL; + + while (job->modelview.end > job->modelview.start) + { + GskGLRenderModelview *modelview = job->modelview.end-1; + g_clear_pointer (&modelview->transform, gsk_transform_unref); + job->modelview.end--; + } + + g_clear_object (&job->driver); + g_clear_pointer (&job->region, cairo_region_destroy); + modelviews_clear (&job->modelview); + clips_clear (&job->clip); + g_free (job); +} diff --git gsk/gl/gskglrenderjobprivate.h gsk/gl/gskglrenderjobprivate.h new file mode 100644 index 0000000000..2e38b07a2e --- /dev/null +++ gsk/gl/gskglrenderjobprivate.h @@ -0,0 +1,36 @@ +/* gskglrenderjobprivate.h + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include "gskgltypesprivate.h" + +GskGLRenderJob *gsk_gl_render_job_new (GskGLDriver *driver, + const graphene_rect_t *viewport, + float scale, + const cairo_region_t *region, + guint framebuffer, + gboolean clear_framebuffer); +void gsk_gl_render_job_free (GskGLRenderJob *job); +void gsk_gl_render_job_render (GskGLRenderJob *job, + GskRenderNode *root); +void gsk_gl_render_job_render_flipped (GskGLRenderJob *job, + GskRenderNode *root); + diff --git gsk/gl/gskglshadowlibrary.c gsk/gl/gskglshadowlibrary.c new file mode 100644 index 0000000000..d7c22c2588 --- /dev/null +++ gsk/gl/gskglshadowlibrary.c @@ -0,0 +1,256 @@ +/* gskglshadowlibrary.c + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include + +#include "gskgldriverprivate.h" +#include "gskglshadowlibraryprivate.h" + +#define MAX_UNUSED_FRAMES (16 * 5) + +struct _GskGLShadowLibrary +{ + GObject parent_instance; + GskGLDriver *driver; + GArray *shadows; +}; + +typedef struct _Shadow +{ + GskRoundedRect outline; + float blur_radius; + guint texture_id; + gint64 last_used_in_frame; +} Shadow; + +G_DEFINE_TYPE (GskGLShadowLibrary, gsk_gl_shadow_library, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_DRIVER, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +GskGLShadowLibrary * +gsk_gl_shadow_library_new (GskGLDriver *driver) +{ + g_return_val_if_fail (GSK_IS_GL_DRIVER (driver), NULL); + + return g_object_new (GSK_TYPE_GL_SHADOW_LIBRARY, + "driver", driver, + NULL); +} + +static void +gsk_gl_shadow_library_dispose (GObject *object) +{ + GskGLShadowLibrary *self = (GskGLShadowLibrary *)object; + + for (guint i = 0; i < self->shadows->len; i++) + { + const Shadow *shadow = &g_array_index (self->shadows, Shadow, i); + gsk_gl_driver_release_texture_by_id (self->driver, shadow->texture_id); + } + + g_clear_pointer (&self->shadows, g_array_unref); + g_clear_object (&self->driver); + + G_OBJECT_CLASS (gsk_gl_shadow_library_parent_class)->dispose (object); +} + +static void +gsk_gl_shadow_library_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GskGLShadowLibrary *self = GSK_GL_SHADOW_LIBRARY (object); + + switch (prop_id) + { + case PROP_DRIVER: + g_value_set_object (value, self->driver); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gsk_gl_shadow_library_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GskGLShadowLibrary *self = GSK_GL_SHADOW_LIBRARY (object); + + switch (prop_id) + { + case PROP_DRIVER: + self->driver = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gsk_gl_shadow_library_class_init (GskGLShadowLibraryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->dispose = gsk_gl_shadow_library_dispose; + object_class->get_property = gsk_gl_shadow_library_get_property; + object_class->set_property = gsk_gl_shadow_library_set_property; + + properties [PROP_DRIVER] = + g_param_spec_object ("driver", NULL, NULL, + GSK_TYPE_GL_DRIVER, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +gsk_gl_shadow_library_init (GskGLShadowLibrary *self) +{ + self->shadows = g_array_new (FALSE, FALSE, sizeof (Shadow)); +} + +void +gsk_gl_shadow_library_insert (GskGLShadowLibrary *self, + const GskRoundedRect *outline, + float blur_radius, + guint texture_id) +{ + Shadow *shadow; + + g_assert (GSK_IS_GL_SHADOW_LIBRARY (self)); + g_assert (outline != NULL); + g_assert (texture_id != 0); + + gsk_gl_driver_mark_texture_permanent (self->driver, texture_id); + + g_array_set_size (self->shadows, self->shadows->len + 1); + + shadow = &g_array_index (self->shadows, Shadow, self->shadows->len - 1); + shadow->outline = *outline; + shadow->blur_radius = blur_radius; + shadow->texture_id = texture_id; + shadow->last_used_in_frame = self->driver->current_frame_id; +} + +guint +gsk_gl_shadow_library_lookup (GskGLShadowLibrary *self, + const GskRoundedRect *outline, + float blur_radius) +{ + Shadow *ret = NULL; + + g_assert (GSK_IS_GL_SHADOW_LIBRARY (self)); + g_assert (outline != NULL); + + /* Ensure GskRoundedRect is 12 packed floats without padding + * so that we can use memcmp instead of float comparisons. + */ + G_STATIC_ASSERT (sizeof *outline == (sizeof (float) * 12)); + + for (guint i = 0; i < self->shadows->len; i++) + { + Shadow *shadow = &g_array_index (self->shadows, Shadow, i); + + if (blur_radius == shadow->blur_radius && + memcmp (outline, &shadow->outline, sizeof *outline) == 0) + { + ret = shadow; + break; + } + } + + if (ret == NULL) + return 0; + + g_assert (ret->texture_id != 0); + + ret->last_used_in_frame = self->driver->current_frame_id; + + return ret->texture_id; +} + +#if 0 +static void +write_shadow_to_png (GskGLDriver *driver, + const Shadow *shadow) +{ + int width = shadow->outline.bounds.size.width + (shadow->outline.bounds.origin.x * 2); + int height = shadow->outline.bounds.size.height + (shadow->outline.bounds.origin.y * 2); + char *filename = g_strdup_printf ("shadow_cache_%d_%d_%d.png", + width, height, shadow->texture_id); + GdkTexture *texture; + + texture = gdk_gl_texture_new (gsk_gl_driver_get_context (driver), + shadow->texture_id, + width, height, + NULL, NULL); + gdk_texture_save_to_png (texture, filename); + + g_object_unref (texture); + g_free (filename); +} +#endif + +void +gsk_gl_shadow_library_begin_frame (GskGLShadowLibrary *self) +{ + gint64 watermark; + int i; + int p; + + g_return_if_fail (GSK_IS_GL_SHADOW_LIBRARY (self)); + +#if 0 + for (i = 0, p = self->shadows->len; i < p; i++) + { + const Shadow *shadow = &g_array_index (self->shadows, Shadow, i); + write_shadow_to_png (self->driver, shadow); + } +#endif + + watermark = self->driver->current_frame_id - MAX_UNUSED_FRAMES; + + for (i = 0, p = self->shadows->len; i < p; i++) + { + const Shadow *shadow = &g_array_index (self->shadows, Shadow, i); + + if (shadow->last_used_in_frame < watermark) + { + gsk_gl_driver_release_texture_by_id (self->driver, shadow->texture_id); + g_array_remove_index_fast (self->shadows, i); + p--; + i--; + } + } +} diff --git gsk/gl/gskglshadowlibraryprivate.h gsk/gl/gskglshadowlibraryprivate.h new file mode 100644 index 0000000000..3c3c77c9e3 --- /dev/null +++ gsk/gl/gskglshadowlibraryprivate.h @@ -0,0 +1,42 @@ +/* gskglshadowlibraryprivate.h + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include "gskgltexturelibraryprivate.h" + +G_BEGIN_DECLS + +#define GSK_TYPE_GL_SHADOW_LIBRARY (gsk_gl_shadow_library_get_type()) + +G_DECLARE_FINAL_TYPE (GskGLShadowLibrary, gsk_gl_shadow_library, GSK, GL_SHADOW_LIBRARY, GObject) + +GskGLShadowLibrary * gsk_gl_shadow_library_new (GskGLDriver *driver); +void gsk_gl_shadow_library_begin_frame (GskGLShadowLibrary *self); +guint gsk_gl_shadow_library_lookup (GskGLShadowLibrary *self, + const GskRoundedRect *outline, + float blur_radius); +void gsk_gl_shadow_library_insert (GskGLShadowLibrary *self, + const GskRoundedRect *outline, + float blur_radius, + guint texture_id); + +G_END_DECLS + diff --git gsk/gl/gskgltexture.c gsk/gl/gskgltexture.c new file mode 100644 index 0000000000..a578e80358 --- /dev/null +++ gsk/gl/gskgltexture.c @@ -0,0 +1,95 @@ +/* gskgltexture.c + * + * Copyright 2020 Christian Hergert + * + * This file 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 file 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 General Public License along + * with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include + +#include "gskgltextureprivate.h" +#include "ninesliceprivate.h" + +void +gsk_gl_texture_free (GskGLTexture *texture) +{ + if (texture != NULL) + { + g_assert (texture->link.prev == NULL); + g_assert (texture->link.next == NULL); + + if (texture->user) + g_clear_pointer (&texture->user, gdk_texture_clear_render_data); + + if (texture->texture_id != 0) + { + glDeleteTextures (1, &texture->texture_id); + texture->texture_id = 0; + } + + for (guint i = 0; i < texture->n_slices; i++) + { + glDeleteTextures (1, &texture->slices[i].texture_id); + texture->slices[i].texture_id = 0; + } + + g_clear_pointer (&texture->slices, g_free); + g_clear_pointer (&texture->nine_slice, g_free); + + g_free (texture); + } +} + +GskGLTexture * +gsk_gl_texture_new (guint texture_id, + int width, + int height, + gint64 frame_id) +{ + GskGLTexture *texture; + + texture = g_new0 (GskGLTexture, 1); + texture->texture_id = texture_id; + texture->link.data = texture; + texture->width = width; + texture->height = height; + texture->last_used_in_frame = frame_id; + + return texture; +} + +const GskGLTextureNineSlice * +gsk_gl_texture_get_nine_slice (GskGLTexture *texture, + const GskRoundedRect *outline, + float extra_pixels_x, + float extra_pixels_y) +{ + g_assert (texture != NULL); + g_assert (outline != NULL); + + if G_UNLIKELY (texture->nine_slice == NULL) + { + texture->nine_slice = g_new0 (GskGLTextureNineSlice, 9); + + nine_slice_rounded_rect (texture->nine_slice, outline); + nine_slice_grow (texture->nine_slice, extra_pixels_x, extra_pixels_y); + nine_slice_to_texture_coords (texture->nine_slice, texture->width, texture->height); + } + + return texture->nine_slice; +} diff --git gsk/gl/gskgltexturelibrary.c gsk/gl/gskgltexturelibrary.c new file mode 100644 index 0000000000..772b382477 --- /dev/null +++ gsk/gl/gskgltexturelibrary.c @@ -0,0 +1,583 @@ +/* gskgltexturelibrary.c + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include +#include + +#include "gskglcommandqueueprivate.h" +#include "gskgldriverprivate.h" +#include "gskgltexturelibraryprivate.h" + +#define DEFAULT_MAX_FRAME_AGE 60 +#define DEFAULT_ATLAS_WIDTH 512 +#define DEFAULT_ATLAS_HEIGHT 512 +#define MAX_OLD_RATIO 0.5 + +G_DEFINE_ABSTRACT_TYPE (GskGLTextureLibrary, gsk_gl_texture_library, G_TYPE_OBJECT) + +enum { + PROP_0, + PROP_DRIVER, + N_PROPS +}; + +static GParamSpec *properties [N_PROPS]; + +static void +gsk_gl_texture_atlas_free (GskGLTextureAtlas *atlas) +{ + if (atlas->texture_id != 0) + { + glDeleteTextures (1, &atlas->texture_id); + atlas->texture_id = 0; + } + + g_clear_pointer (&atlas->nodes, g_free); + g_free (atlas); +} + +static gboolean +gsk_gl_texture_library_real_compact (GskGLTextureLibrary *self, + gint64 frame_id) +{ + GPtrArray *removed = NULL; + gboolean ret = FALSE; + gboolean periodic_scan; + + g_assert (GSK_IS_GL_TEXTURE_LIBRARY (self)); + + periodic_scan = (self->max_frame_age > 0 && + (frame_id % self->max_frame_age) == 0); + + for (guint i = self->atlases->len; i > 0; i--) + { + GskGLTextureAtlas *atlas = g_ptr_array_index (self->atlases, i - 1); + + if (gsk_gl_texture_atlas_get_unused_ratio (atlas) > MAX_OLD_RATIO) + { + GSK_DEBUG (CACHE, + "Dropping atlas %d (%g.2%% old)", i, + 100.0 * gsk_gl_texture_atlas_get_unused_ratio (atlas)); + if (removed == NULL) + removed = g_ptr_array_new_with_free_func ((GDestroyNotify)gsk_gl_texture_atlas_free); + g_ptr_array_add (removed, g_ptr_array_steal_index (self->atlases, i - 1)); + } + } + + if (periodic_scan || removed != NULL) + { + GskGLTextureAtlasEntry *entry; + GHashTableIter iter; + guint dropped = 0; + G_GNUC_UNUSED guint atlased = 0; + + g_hash_table_iter_init (&iter, self->hash_table); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&entry)) + { + if (entry->is_atlased) + { + if (removed && g_ptr_array_find (removed, entry->atlas, NULL)) + { + g_hash_table_iter_remove (&iter); + dropped++; + } + else if (periodic_scan) + { + gsk_gl_texture_atlas_entry_mark_unused (entry); + entry->accessed = FALSE; + if (entry->is_atlased) + atlased++; + } + } + else + { + if (!entry->accessed) + { + if (entry->texture) + gsk_gl_driver_release_texture (self->driver, entry->texture); + g_hash_table_iter_remove (&iter); + dropped++; + } + else if (periodic_scan) + entry->accessed = FALSE; + } + } + + GSK_DEBUG (CACHE, "%s: Dropped %d individual items", + G_OBJECT_TYPE_NAME (self), + dropped); + GSK_DEBUG (CACHE, "%s: %d items cached (%d atlased, %d individually)", + G_OBJECT_TYPE_NAME (self), + g_hash_table_size (self->hash_table), + atlased, + g_hash_table_size (self->hash_table) - atlased); + + if (dropped > 0) + gsk_gl_texture_library_clear_cache (self); + + ret = TRUE; + + g_clear_pointer (&removed, g_ptr_array_unref); + } + + if (GSK_DEBUG_CHECK (CACHE)) + { + static gint64 last_message; + gint64 now = g_get_monotonic_time (); + if (now - last_message > G_USEC_PER_SEC) + { + last_message = now; + gdk_debug_message ("%s contains %d atlases", + G_OBJECT_TYPE_NAME (self), + self->atlases->len); + } + } + + return ret; +} + +static gboolean +gsk_gl_texture_library_real_allocate (GskGLTextureLibrary *self, + GskGLTextureAtlas *atlas, + int width, + int height, + int *out_x, + int *out_y) +{ + stbrp_rect rect; + + g_assert (GSK_IS_GL_TEXTURE_LIBRARY (self)); + g_assert (atlas != NULL); + g_assert (width > 0); + g_assert (height > 0); + g_assert (out_x != NULL); + g_assert (out_y != NULL); + + rect.w = width; + rect.h = height; + + stbrp_pack_rects (&atlas->context, &rect, 1); + + if (rect.was_packed) + { + *out_x = rect.x; + *out_y = rect.y; + } + + return rect.was_packed; +} + +static void +gsk_gl_texture_library_constructed (GObject *object) +{ + G_OBJECT_CLASS (gsk_gl_texture_library_parent_class)->constructed (object); + + g_assert (GSK_GL_TEXTURE_LIBRARY (object)->hash_table != NULL); +} + +static void +gsk_gl_texture_library_dispose (GObject *object) +{ + GskGLTextureLibrary *self = (GskGLTextureLibrary *)object; + + g_clear_pointer (&self->atlases, g_ptr_array_unref); + g_clear_object (&self->driver); + g_clear_pointer (&self->hash_table, g_hash_table_unref); + + G_OBJECT_CLASS (gsk_gl_texture_library_parent_class)->dispose (object); +} + +static void +gsk_gl_texture_library_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GskGLTextureLibrary *self = GSK_GL_TEXTURE_LIBRARY (object); + + switch (prop_id) + { + case PROP_DRIVER: + g_value_set_object (value, self->driver); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gsk_gl_texture_library_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GskGLTextureLibrary *self = GSK_GL_TEXTURE_LIBRARY (object); + + switch (prop_id) + { + case PROP_DRIVER: + self->driver = g_value_dup_object (value); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + } +} + +static void +gsk_gl_texture_library_class_init (GskGLTextureLibraryClass *klass) +{ + GObjectClass *object_class = G_OBJECT_CLASS (klass); + + object_class->constructed = gsk_gl_texture_library_constructed; + object_class->dispose = gsk_gl_texture_library_dispose; + object_class->get_property = gsk_gl_texture_library_get_property; + object_class->set_property = gsk_gl_texture_library_set_property; + + klass->compact = gsk_gl_texture_library_real_compact; + klass->allocate = gsk_gl_texture_library_real_allocate; + + properties [PROP_DRIVER] = + g_param_spec_object ("driver", NULL, NULL, + GSK_TYPE_GL_DRIVER, + (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_properties (object_class, N_PROPS, properties); +} + +static void +gsk_gl_texture_library_init (GskGLTextureLibrary *self) +{ + self->max_frame_age = DEFAULT_MAX_FRAME_AGE; + self->atlas_width = DEFAULT_ATLAS_WIDTH; + self->atlas_height = DEFAULT_ATLAS_HEIGHT; + self->atlases = g_ptr_array_new_with_free_func ((GDestroyNotify)gsk_gl_texture_atlas_free); +} + +void +gsk_gl_texture_library_set_funcs (GskGLTextureLibrary *self, + GHashFunc hash_func, + GEqualFunc equal_func, + GDestroyNotify key_destroy, + GDestroyNotify value_destroy) +{ + g_return_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self)); + g_return_if_fail (self->hash_table == NULL); + + self->hash_table = g_hash_table_new_full (hash_func, equal_func, + key_destroy, value_destroy); +} + +void +gsk_gl_texture_library_begin_frame (GskGLTextureLibrary *self, + gint64 frame_id) +{ + g_return_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self)); + + gsk_gl_texture_library_compact (self, frame_id); + + if (GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame) + GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame (self, frame_id); +} + +static GskGLTexture * +gsk_gl_texture_library_pack_one (GskGLTextureLibrary *self, + guint width, + guint height) +{ + GskGLTexture *texture; + + g_assert (GSK_IS_GL_TEXTURE_LIBRARY (self)); + + if (width > self->driver->command_queue->max_texture_size || + height > self->driver->command_queue->max_texture_size) + { + g_warning ("Clipping requested texture of size %ux%u to maximum allowable size %u.", + width, height, self->driver->command_queue->max_texture_size); + width = MIN (width, self->driver->command_queue->max_texture_size); + height = MIN (height, self->driver->command_queue->max_texture_size); + } + + texture = gsk_gl_driver_create_texture (self->driver, width, height, GL_RGBA8); + texture->permanent = TRUE; + + return texture; +} + +static void +gsk_gl_texture_library_pack_any_atlas (GskGLTextureLibrary *self, + int width, + int height, + GskGLTextureAtlas **out_atlas, + int *out_x, + int *out_y) +{ + GskGLTextureAtlas *atlas = NULL; + int x, y; + + g_assert (GSK_IS_GL_TEXTURE_LIBRARY (self)); + g_assert (width > 0); + g_assert (height > 0); + g_assert (out_atlas != NULL); + g_assert (out_x != NULL); + g_assert (out_y != NULL); + + for (guint i = 0; i < self->atlases->len; i++) + { + atlas = g_ptr_array_index (self->atlases, i); + + if (gsk_gl_texture_library_allocate (self, atlas, width, height, &x, &y)) + break; + + atlas = NULL; + } + + if (atlas == NULL) + { + /* No atlas has enough space, so create a new one... */ + atlas = gsk_gl_texture_library_acquire_atlas (self); + + /* Pack it onto that one, which surely has enough space... */ + if (!gsk_gl_texture_library_allocate (self, atlas, width, height, &x, &y)) + g_assert_not_reached (); + } + + *out_atlas = atlas; + *out_x = x; + *out_y = y; +} + +gpointer +gsk_gl_texture_library_pack (GskGLTextureLibrary *self, + gpointer key, + gsize valuelen, + guint width, + guint height, + int padding, + guint *out_packed_x, + guint *out_packed_y) +{ + GskGLTextureAtlasEntry *entry; + GskGLTextureAtlas *atlas = NULL; + + g_assert (GSK_IS_GL_TEXTURE_LIBRARY (self)); + g_assert (key != NULL); + g_assert (valuelen > sizeof (GskGLTextureAtlasEntry)); + g_assert (out_packed_x != NULL); + g_assert (out_packed_y != NULL); + + entry = g_malloc0 (valuelen); + entry->n_pixels = width * height; + entry->accessed = TRUE; + entry->used = TRUE; + + /* If our size is invisible then we just want an entry in the + * cache for faster lookups, but do not actually spend any texture + * allocations on this entry. + */ + if (width <= 0 && height <= 0) + { + entry->is_atlased = FALSE; + entry->texture = NULL; + entry->area.x = 0.0f; + entry->area.y = 0.0f; + entry->area.x2 = 0.0f; + entry->area.y2 = 0.0f; + + *out_packed_x = 0; + *out_packed_y = 0; + } + else if (self->max_entry_size == 0 || + (width <= self->max_entry_size && + height <= self->max_entry_size)) + { + int packed_x; + int packed_y; + + gsk_gl_texture_library_pack_any_atlas (self, + padding + width + padding, + padding + height + padding, + &atlas, + &packed_x, + &packed_y); + + entry->atlas = atlas; + entry->is_atlased = TRUE; + entry->area.x = (packed_x + padding) / (float)atlas->width; + entry->area.y = (packed_y + padding) / (float)atlas->height; + entry->area.x2 = (packed_x + padding + width) / (float)atlas->width; + entry->area.y2 = (packed_y + padding + height) / (float)atlas->height; + + *out_packed_x = packed_x; + *out_packed_y = packed_y; + } + else + { + GskGLTexture *texture = gsk_gl_texture_library_pack_one (self, + padding + width + padding, + padding + height + padding); + + entry->texture = texture; + entry->is_atlased = FALSE; + entry->area.x = padding / (float) (padding + width + padding); + entry->area.y = padding / (float) (padding + height + padding); + entry->area.x2 = (padding + width) / (float) (padding + width + padding); + entry->area.y2 = (padding + height) / (float) (padding + height + padding); + + *out_packed_x = 0; + *out_packed_y = 0; + } + + g_hash_table_insert (self->hash_table, key, entry); + + return entry; +} + +/* + * gsk_gl_texture_library_clear_cache: + * + * Clear the front cache if the texture library is using one. For + * example the glyph cache would drop it's front cache to force + * next lookups to fall through to the GHashTable key lookup. + */ +void +gsk_gl_texture_library_clear_cache (GskGLTextureLibrary *self) +{ + g_return_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self)); + + if (GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->clear_cache) + GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->clear_cache (self); +} + +/* + * gsk_gl_texture_library_compact: + * + * Requests that the texture library compact it's altases. That + * generally means to traverse them to look for unused pixels over + * a certain threshold and release them if necessary. + * + * Returns: %TRUE if any compaction occurred. + */ +gboolean +gsk_gl_texture_library_compact (GskGLTextureLibrary *self, + gint64 frame_id) +{ + g_return_val_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self), FALSE); + + if (GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->compact) + return GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->compact (self, frame_id); + + return FALSE; +} + +void +gsk_gl_texture_library_reset (GskGLTextureLibrary *self) +{ + g_return_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self)); + + gsk_gl_texture_library_clear_cache (self); + + g_hash_table_remove_all (self->hash_table); + + if (self->atlases->len) + g_ptr_array_remove_range (self->atlases, 0, self->atlases->len); +} + +void +gsk_gl_texture_library_set_atlas_size (GskGLTextureLibrary *self, + int width, + int height) +{ + g_return_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self)); + + if (width <= 0) + width = DEFAULT_ATLAS_WIDTH; + + if (height <= 0) + height = DEFAULT_ATLAS_HEIGHT; + + self->atlas_height = height; + self->atlas_width = width; + + gsk_gl_texture_library_reset (self); +} + +/* + * gsk_gl_texture_library_acquire_atlas: + * + * Allocates a new texture atlas based on the current size + * and format requirements. + */ +GskGLTextureAtlas * +gsk_gl_texture_library_acquire_atlas (GskGLTextureLibrary *self) +{ + GskGLTextureAtlas *atlas; + + g_return_val_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self), NULL); + g_return_val_if_fail (GSK_IS_GL_DRIVER (self->driver), NULL); + g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->driver->command_queue), NULL); + g_return_val_if_fail (self->atlas_width > 0, NULL); + g_return_val_if_fail (self->atlas_height > 0, NULL); + + atlas = g_new0 (GskGLTextureAtlas, 1); + atlas->width = self->atlas_width; + atlas->height = self->atlas_height; + /* TODO: We might want to change the strategy about the amount of + * nodes here? stb_rect_pack.h says width is optimal. */ + atlas->nodes = g_malloc0_n (atlas->width, sizeof (struct stbrp_node)); + stbrp_init_target (&atlas->context, atlas->width, atlas->height, atlas->nodes, atlas->width); + atlas->texture_id = gsk_gl_command_queue_create_texture (self->driver->command_queue, + atlas->width, + atlas->height, + GL_RGBA8); + + gdk_gl_context_label_object_printf (gdk_gl_context_get_current (), + GL_TEXTURE, atlas->texture_id, + "Texture atlas %d", + atlas->texture_id); + + g_ptr_array_add (self->atlases, atlas); + + if (GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->init_atlas) + GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->init_atlas (self, atlas); + + return atlas; +} + +gboolean +gsk_gl_texture_library_allocate (GskGLTextureLibrary *self, + GskGLTextureAtlas *atlas, + int width, + int height, + int *out_x, + int *out_y) +{ + g_assert (GSK_IS_GL_TEXTURE_LIBRARY (self)); + g_assert (atlas != NULL); + g_assert (width > 0); + g_assert (height > 0); + g_assert (out_x != NULL); + g_assert (out_y != NULL); + + return GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->allocate (self, atlas, width, height, out_x, out_y); +} diff --git gsk/gl/gskgltexturelibraryprivate.h gsk/gl/gskgltexturelibraryprivate.h new file mode 100644 index 0000000000..e2c3589fab --- /dev/null +++ gsk/gl/gskgltexturelibraryprivate.h @@ -0,0 +1,232 @@ +/* gskgltexturelibraryprivate.h + * + * Copyright 2020 Christian Hergert + * + * This file 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 file 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 General Public License along + * with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include "gskgltypesprivate.h" +#include "gskgltextureprivate.h" + +#include "stb_rect_pack.h" + +G_BEGIN_DECLS + +#define GSK_TYPE_GL_TEXTURE_LIBRARY (gsk_gl_texture_library_get_type ()) +#define GSK_GL_TEXTURE_LIBRARY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY, GskGLTextureLibrary)) +#define GSK_IS_GL_TEXTURE_LIBRARY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY)) +#define GSK_GL_TEXTURE_LIBRARY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_GL_TEXTURE_LIBRARY, GskGLTextureLibraryClass)) +#define GSK_IS_GL_TEXTURE_LIBRARY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_GL_TEXTURE_LIBRARY)) +#define GSK_GL_TEXTURE_LIBRARY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY, GskGLTextureLibraryClass)) + +typedef struct _GskGLTextureAtlas +{ + struct stbrp_context context; + struct stbrp_node *nodes; + + int width; + int height; + + guint texture_id; + + /* Pixels of rects that have been used at some point, + * But are now unused. + */ + int unused_pixels; + +} GskGLTextureAtlas; + +typedef struct _GskGLTextureAtlasEntry +{ + /* A backreference to either the atlas or texture containing + * the contents of the atlas entry. For larger items, no atlas + * is used and instead a direct texture. + */ + union { + GskGLTextureAtlas *atlas; + GskGLTexture *texture; + }; + + /* The area within the atlas translated to 0..1 bounds */ + struct { + float x; + float y; + float x2; + float y2; + } area; + + /* Number of pixels in the entry, used to calculate usage + * of an atlas while processing. + */ + guint n_pixels : 29; + + /* If entry has marked pixels as used in the atlas this frame */ + guint used : 1; + + /* If entry was accessed this frame */ + guint accessed : 1; + + /* When true, backref is an atlas, otherwise texture */ + guint is_atlased : 1; +} GskGLTextureAtlasEntry; + +typedef struct _GskGLTextureLibrary +{ + GObject parent_instance; + GskGLDriver *driver; + GPtrArray *atlases; + GHashTable *hash_table; + guint max_entry_size; + guint max_frame_age; + guint atlas_width; + guint atlas_height; +} GskGLTextureLibrary; + +typedef struct _GskGLTextureLibraryClass +{ + GObjectClass parent_class; + + void (*begin_frame) (GskGLTextureLibrary *library, + gint64 frame_id); + gboolean (*compact) (GskGLTextureLibrary *library, + gint64 frame_id); + void (*clear_cache) (GskGLTextureLibrary *library); + void (*init_atlas) (GskGLTextureLibrary *library, + GskGLTextureAtlas *atlas); + gboolean (*allocate) (GskGLTextureLibrary *library, + GskGLTextureAtlas *atlas, + int width, + int height, + int *out_x, + int *out_y); +} GskGLTextureLibraryClass; + +G_DEFINE_AUTOPTR_CLEANUP_FUNC (GskGLTextureLibrary, g_object_unref) + +GType gsk_gl_texture_library_get_type (void) G_GNUC_CONST; +gboolean gsk_gl_texture_library_compact (GskGLTextureLibrary *self, + gint64 frame_id); +void gsk_gl_texture_library_clear_cache (GskGLTextureLibrary *self); +void gsk_gl_texture_library_reset (GskGLTextureLibrary *self); +void gsk_gl_texture_library_set_atlas_size (GskGLTextureLibrary *self, + int width, + int height); +GskGLTextureAtlas *gsk_gl_texture_library_acquire_atlas (GskGLTextureLibrary *self); +void gsk_gl_texture_library_set_funcs (GskGLTextureLibrary *self, + GHashFunc hash_func, + GEqualFunc equal_func, + GDestroyNotify key_destroy, + GDestroyNotify value_destroy); +void gsk_gl_texture_library_begin_frame (GskGLTextureLibrary *self, + gint64 frame_id); +gboolean gsk_gl_texture_library_allocate (GskGLTextureLibrary *self, + GskGLTextureAtlas *atlas, + int width, + int height, + int *out_x, + int *out_y); +gpointer gsk_gl_texture_library_pack (GskGLTextureLibrary *self, + gpointer key, + gsize valuelen, + guint width, + guint height, + int padding, + guint *out_packed_x, + guint *out_packed_y); + +static inline void +gsk_gl_texture_atlas_mark_unused (GskGLTextureAtlas *self, + int n_pixels) +{ + g_assert (n_pixels >= 0); + + self->unused_pixels += n_pixels; +} + +static inline void +gsk_gl_texture_atlas_entry_mark_used (GskGLTextureAtlasEntry *entry) +{ + if (entry->used == TRUE || entry->is_atlased == FALSE) + return; + + entry->atlas->unused_pixels -= entry->n_pixels; + entry->used = TRUE; +} + +static inline void +gsk_gl_texture_atlas_entry_mark_unused (GskGLTextureAtlasEntry *entry) +{ + if (entry->used == FALSE || entry->is_atlased == FALSE) + return; + + entry->atlas->unused_pixels += entry->n_pixels; + entry->used = FALSE; +} + +static inline gboolean +gsk_gl_texture_library_lookup (GskGLTextureLibrary *self, + gconstpointer key, + GskGLTextureAtlasEntry **out_entry) +{ + GskGLTextureAtlasEntry *entry = g_hash_table_lookup (self->hash_table, key); + + if G_LIKELY (entry != NULL && entry->accessed && entry->used) + { + *out_entry = entry; + return TRUE; + } + + if (entry != NULL) + { + gsk_gl_texture_atlas_entry_mark_used (entry); + entry->accessed = TRUE; + *out_entry = entry; + return TRUE; + } + + return FALSE; +} + +static inline guint +GSK_GL_TEXTURE_ATLAS_ENTRY_TEXTURE (gconstpointer d) +{ + const GskGLTextureAtlasEntry *e = d; + + return e->is_atlased ? e->atlas->texture_id + : e->texture ? e->texture->texture_id : 0; +} + +static inline double +gsk_gl_texture_atlas_get_unused_ratio (const GskGLTextureAtlas *self) +{ + if (self->unused_pixels > 0) + return (double)(self->unused_pixels) / (double)(self->width * self->height); + return 0.0; +} + +static inline gboolean +gsk_gl_texture_library_can_cache (GskGLTextureLibrary *self, + int width, + int height) +{ + g_assert (self->max_entry_size > 0); + return width <= self->max_entry_size && height <= self->max_entry_size; +} + +G_END_DECLS + diff --git gsk/gl/gskgltextureprivate.h gsk/gl/gskgltextureprivate.h new file mode 100644 index 0000000000..620a9ed076 --- /dev/null +++ gsk/gl/gskgltextureprivate.h @@ -0,0 +1,93 @@ +/* gskgltextureprivate.h + * + * Copyright 2020 Christian Hergert + * + * This file 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 file 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 General Public License along + * with this program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include "gskgltypesprivate.h" + +G_BEGIN_DECLS + +struct _GskGLTextureSlice +{ + cairo_rectangle_int_t rect; + struct { + float x; + float y; + float x2; + float y2; + } area; + guint texture_id; +}; + +struct _GskGLTextureNineSlice +{ + cairo_rectangle_int_t rect; + struct { + float x; + float y; + float x2; + float y2; + } area; +}; + +struct _GskGLTexture +{ + /* Used to insert into queue */ + GList link; + + /* Identifier of the frame that created it */ + gint64 last_used_in_frame; + + /* Backpointer to texture (can be cleared asynchronously) */ + GdkTexture *user; + + /* Only used by nine-slice textures */ + GskGLTextureNineSlice *nine_slice; + + /* Only used by sliced textures */ + GskGLTextureSlice *slices; + guint n_slices; + + /* The actual GL texture identifier in some shared context */ + guint texture_id; + + int width; + int height; + + /* Set when used by an atlas so we don't drop the texture */ + guint permanent : 1; + /* we are allowed to call glGenerateMipmap() for this texture */ + guint can_mipmap : 1; + /* we called glGenerateMipmap() for this texture */ + guint has_mipmap : 1; +}; + +GskGLTexture * gsk_gl_texture_new (guint texture_id, + int width, + int height, + gint64 frame_id); +const GskGLTextureNineSlice * gsk_gl_texture_get_nine_slice (GskGLTexture *texture, + const GskRoundedRect *outline, + float extra_pixels_x, + float extra_pixels_y); +void gsk_gl_texture_free (GskGLTexture *texture); + +G_END_DECLS + diff --git gsk/gl/gskgltypesprivate.h gsk/gl/gskgltypesprivate.h new file mode 100644 index 0000000000..8f7e8fdbfd --- /dev/null +++ gsk/gl/gskgltypesprivate.h @@ -0,0 +1,84 @@ +/* gskgltypesprivate.h + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include +#include +#ifdef GDK_WINDOWING_WIN32 +/* epoxy needs this, see https://github.com/anholt/libepoxy/issues/299 */ +#include +#endif +#include +#include + +G_BEGIN_DECLS + +#define GSK_GL_N_VERTICES 6 + +typedef struct _GskGLAttachmentState GskGLAttachmentState; +typedef struct _GskGLBuffer GskGLBuffer; +typedef struct _GskGLCommandQueue GskGLCommandQueue; +typedef struct _GskGLCompiler GskGLCompiler; +typedef struct _GskGLDrawVertex GskGLDrawVertex; +typedef struct _GskGLRenderTarget GskGLRenderTarget; +typedef struct _GskGLGlyphLibrary GskGLGlyphLibrary; +typedef struct _GskGLIconLibrary GskGLIconLibrary; +typedef struct _GskGLProgram GskGLProgram; +typedef struct _GskGLRenderJob GskGLRenderJob; +typedef struct _GskGLShadowLibrary GskGLShadowLibrary; +typedef struct _GskGLTexture GskGLTexture; +typedef struct _GskGLTextureSlice GskGLTextureSlice; +typedef struct _GskGLTextureAtlas GskGLTextureAtlas; +typedef struct _GskGLTextureLibrary GskGLTextureLibrary; +typedef struct _GskGLTextureNineSlice GskGLTextureNineSlice; +typedef struct _GskGLUniformInfo GskGLUniformInfo; +typedef struct _GskGLUniformProgram GskGLUniformProgram; +typedef struct _GskGLUniformState GskGLUniformState; +typedef struct _GskGLDriver GskGLDriver; + +struct _GskGLDrawVertex +{ + float position[2]; + union { + float uv[2]; + guint16 color2[4]; + }; + guint16 color[4]; +}; + +/* Same vertex, but with the colors expanded to full floats. Used on drivers + * that cannot source vertex attributes from half floats (pre-GL 3.0 without + * GL_ARB_half_float_vertex). Note that uv and color2 alias each other in + * GskGLDrawVertex, so both members are filled in from the same bytes and the + * shader picks whichever one it declared. + */ +typedef struct _GskGLDrawVertexFloat GskGLDrawVertexFloat; + +struct _GskGLDrawVertexFloat +{ + float position[2]; + float uv[2]; + float color[4]; + float color2[4]; +}; + +G_END_DECLS + diff --git gsk/gl/gskgluniformstate.c gsk/gl/gskgluniformstate.c new file mode 100644 index 0000000000..77cd45a859 --- /dev/null +++ gsk/gl/gskgluniformstate.c @@ -0,0 +1,269 @@ +/* gskgluniformstate.c + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#include "config.h" + +#include +#include + +#include "gskgluniformstateprivate.h" + +static const guint8 uniform_sizes[] = { + 0, + + sizeof (Uniform1f), + sizeof (Uniform2f), + sizeof (Uniform3f), + sizeof (Uniform4f), + + sizeof (Uniform1f), + sizeof (Uniform2f), + sizeof (Uniform3f), + sizeof (Uniform4f), + + sizeof (Uniform1i), + sizeof (Uniform2i), + sizeof (Uniform3i), + sizeof (Uniform4i), + + sizeof (Uniform1ui), + + sizeof (guint), + + sizeof (graphene_matrix_t), + sizeof (GskRoundedRect), + sizeof (GdkRGBA), + + 0, +}; + +GskGLUniformState * +gsk_gl_uniform_state_new (void) +{ + GskGLUniformState *state; + + state = g_atomic_rc_box_new0 (GskGLUniformState); + state->programs = g_hash_table_new_full (NULL, NULL, NULL, g_free); + state->values_len = 4096; + state->values_pos = 0; + state->values_buf = g_malloc (4096); + + memset (state->apply_hash, 0, sizeof state->apply_hash); + + return g_steal_pointer (&state); +} + +GskGLUniformState * +gsk_gl_uniform_state_ref (GskGLUniformState *state) +{ + return g_atomic_rc_box_acquire (state); +} + +static void +gsk_gl_uniform_state_finalize (gpointer data) +{ + GskGLUniformState *state = data; + + g_clear_pointer (&state->programs, g_hash_table_unref); + g_clear_pointer (&state->values_buf, g_free); +} + +void +gsk_gl_uniform_state_unref (GskGLUniformState *state) +{ + g_atomic_rc_box_release_full (state, gsk_gl_uniform_state_finalize); +} + +gpointer +gsk_gl_uniform_state_init_value (GskGLUniformState *state, + GskGLUniformProgram *program, + GskGLUniformFormat format, + guint array_count, + guint key, + GskGLUniformMapping **infoptr) +{ + GskGLUniformMapping *mapping; + guint offset; + + g_assert (state != NULL); + g_assert (array_count < 32); + g_assert ((int)format >= 0 && format < GSK_GL_UNIFORM_FORMAT_LAST); + g_assert (format > 0); + g_assert (program != NULL); + g_assert (key < program->n_mappings); + + mapping = &program->mappings[key]; + + if (mapping->location == -1) + { + *infoptr = NULL; + return NULL; + } + + if G_LIKELY (format == mapping->info.format) + { + if G_LIKELY (array_count <= mapping->info.array_count) + { + *infoptr = mapping; + return GSK_GL_UNIFORM_VALUE (state->values_buf, mapping->info.offset); + } + + /* We found the uniform, but there is not enough space for the + * amount that was requested. Instead, allocate new space and + * set the value to "initial" so that the caller just writes + * over the previous value. + * + * This can happen when using dynamic array lengths like the + * "n_color_stops" in gradient shaders. + */ + goto setup_info; + } + else if (mapping->info.format == 0) + { + goto setup_info; + } + else + { + g_critical ("Attempt to access uniform with different type of value " + "than it was initialized with. Program %u Location %u. " + "Was %d now %d (array length %d now %d).", + program->program_id, key, mapping->info.format, format, + mapping->info.array_count, array_count); + *infoptr = NULL; + return NULL; + } + +setup_info: + + gsk_gl_uniform_state_realloc (state, + uniform_sizes[format] * MAX (1, array_count), + &offset); + + /* we have 21 bits for offset */ + g_assert (offset < (1 << GSK_GL_UNIFORM_OFFSET_BITS)); + + mapping->info.format = format; + mapping->info.offset = offset; + mapping->info.array_count = array_count; + mapping->info.initial = TRUE; + mapping->stamp = 0; + + *infoptr = mapping; + + return GSK_GL_UNIFORM_VALUE (state->values_buf, mapping->info.offset); +} + +void +gsk_gl_uniform_state_end_frame (GskGLUniformState *state) +{ + GHashTableIter iter; + GskGLUniformProgram *program; + guint allocator = 0; + + g_return_if_fail (state != NULL); + + /* After a frame finishes, we want to remove all our copies of uniform + * data that isn't needed any longer. Since we treat it as uninitialized + * after this frame (to reset it on first use next frame) we can just + * discard it but keep an allocation around to reuse. + */ + + g_hash_table_iter_init (&iter, state->programs); + while (g_hash_table_iter_next (&iter, NULL, (gpointer *)&program)) + { + for (guint j = 0; j < program->n_mappings; j++) + { + GskGLUniformMapping *mapping = &program->mappings[j]; + guint size; + + /* Skip unused uniform mappings */ + if (mapping->info.format == 0 || mapping->location == -1) + continue; + + /* Calculate how much size is needed for the uniform, including arrays */ + size = uniform_sizes[mapping->info.format] * MAX (1, mapping->info.array_count); + + /* Adjust alignment for value */ + allocator += gsk_gl_uniform_state_align (allocator, size); + + /* Offset is in slots of 4 bytes */ + mapping->info.offset = allocator / 4; + mapping->info.initial = TRUE; + mapping->stamp = 0; + + /* Now advance for this items data */ + allocator += size; + } + } + + state->values_pos = allocator; + + /* It can happen that our space requirements grow due to + * difference in order increasing padding. As a pragmatic + * solution to this, just increase the allocation to cover + * the predefined mappins. + */ + if (allocator > state->values_len) + { + while (allocator > state->values_len) + state->values_len *= 2; + state->values_buf = g_realloc (state->values_buf, state->values_len); + } + + memset (state->apply_hash, 0, sizeof state->apply_hash); +} + +gsize +gsk_gl_uniform_format_size (GskGLUniformFormat format) +{ + g_assert (format > 0); + g_assert (format < GSK_GL_UNIFORM_FORMAT_LAST); + + return uniform_sizes[format]; +} + +GskGLUniformProgram * +gsk_gl_uniform_state_get_program (GskGLUniformState *state, + guint program, + const GskGLUniformMapping *mappings, + guint n_mappings) +{ + GskGLUniformProgram *ret; + + g_return_val_if_fail (state != NULL, NULL); + g_return_val_if_fail (program > 0, NULL); + g_return_val_if_fail (program < G_MAXUINT, NULL); + g_return_val_if_fail (n_mappings <= G_N_ELEMENTS (ret->mappings), NULL); + + ret = g_hash_table_lookup (state->programs, GUINT_TO_POINTER (program)); + + if (ret == NULL) + { + ret = g_new0 (GskGLUniformProgram, 1); + ret->program_id = program; + ret->n_mappings = n_mappings; + + memcpy (ret->mappings, mappings, n_mappings * sizeof *mappings); + + g_hash_table_insert (state->programs, GUINT_TO_POINTER (program), ret); + } + + return ret; +} diff --git gsk/gl/gskgluniformstateprivate.h gsk/gl/gskgluniformstateprivate.h new file mode 100644 index 0000000000..3704be957c --- /dev/null +++ gsk/gl/gskgluniformstateprivate.h @@ -0,0 +1,834 @@ +/* gskgluniformstateprivate.h + * + * Copyright 2020 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include "gskgltypesprivate.h" + +G_BEGIN_DECLS + +typedef struct { float v0; } Uniform1f; +typedef struct { float v0; float v1; } Uniform2f; +typedef struct { float v0; float v1; float v2; } Uniform3f; +typedef struct { float v0; float v1; float v2; float v3; } Uniform4f; + +typedef struct { int v0; } Uniform1i; +typedef struct { int v0; int v1; } Uniform2i; +typedef struct { int v0; int v1; int v2; } Uniform3i; +typedef struct { int v0; int v1; int v2; int v3; } Uniform4i; + +typedef struct { guint v0; } Uniform1ui; + +#define GSK_GL_UNIFORM_ARRAY_BITS 5 +#define GSK_GL_UNIFORM_FORMAT_BITS 5 +#define GSK_GL_UNIFORM_OFFSET_BITS 21 + +typedef struct _GskGLUniformInfo +{ + guint initial : 1; + guint format : GSK_GL_UNIFORM_FORMAT_BITS; + guint array_count : GSK_GL_UNIFORM_ARRAY_BITS; + guint offset : GSK_GL_UNIFORM_OFFSET_BITS; +} GskGLUniformInfo; + +G_STATIC_ASSERT (sizeof (GskGLUniformInfo) == 4); + +typedef struct _GskGLUniformMapping +{ + const char *name; + GskGLUniformInfo info; + guint stamp; + int location; +} GskGLUniformMapping; + +typedef struct _GskGLUniformProgram +{ + guint program_id; + guint n_uniforms : 12; + guint has_attachments : 1; + guint n_mappings; + GskGLUniformMapping mappings[32]; +} GskGLUniformProgram; + +typedef struct _GskGLUniformState +{ + GHashTable *programs; + guint8 *values_buf; + guint values_pos; + guint values_len; + GskGLUniformInfo apply_hash[512]; +} GskGLUniformState; + +typedef enum _GskGLUniformKind +{ + GSK_GL_UNIFORM_FORMAT_1F = 1, + GSK_GL_UNIFORM_FORMAT_2F, + GSK_GL_UNIFORM_FORMAT_3F, + GSK_GL_UNIFORM_FORMAT_4F, + + GSK_GL_UNIFORM_FORMAT_1FV, + GSK_GL_UNIFORM_FORMAT_2FV, + GSK_GL_UNIFORM_FORMAT_3FV, + GSK_GL_UNIFORM_FORMAT_4FV, + + GSK_GL_UNIFORM_FORMAT_1I, + GSK_GL_UNIFORM_FORMAT_2I, + GSK_GL_UNIFORM_FORMAT_3I, + GSK_GL_UNIFORM_FORMAT_4I, + + GSK_GL_UNIFORM_FORMAT_1UI, + + GSK_GL_UNIFORM_FORMAT_TEXTURE, + + GSK_GL_UNIFORM_FORMAT_MATRIX, + GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT, + GSK_GL_UNIFORM_FORMAT_COLOR, + + GSK_GL_UNIFORM_FORMAT_LAST +} GskGLUniformFormat; + +G_STATIC_ASSERT (GSK_GL_UNIFORM_FORMAT_LAST < (1 << GSK_GL_UNIFORM_FORMAT_BITS)); + +GskGLUniformState *gsk_gl_uniform_state_new (void); +GskGLUniformState *gsk_gl_uniform_state_ref (GskGLUniformState *state); +void gsk_gl_uniform_state_unref (GskGLUniformState *state); +GskGLUniformProgram *gsk_gl_uniform_state_get_program (GskGLUniformState *state, + guint program, + const GskGLUniformMapping *mappings, + guint n_mappings); +void gsk_gl_uniform_state_end_frame (GskGLUniformState *state); +gsize gsk_gl_uniform_format_size (GskGLUniformFormat format); +gpointer gsk_gl_uniform_state_init_value (GskGLUniformState *state, + GskGLUniformProgram *program, + GskGLUniformFormat format, + guint array_count, + guint key, + GskGLUniformMapping **out_mapping); + +#define GSK_GL_UNIFORM_VALUE(base, offset) ((gpointer)((base) + ((offset) * 4))) +#define gsk_gl_uniform_state_get_uniform_data(state,offset) GSK_GL_UNIFORM_VALUE((state)->values_buf, offset) + +static inline gpointer +gsk_gl_uniform_state_get_value (GskGLUniformState *state, + GskGLUniformProgram *program, + GskGLUniformFormat format, + guint array_count, + guint key, + guint stamp, + GskGLUniformMapping **out_mapping) +{ + GskGLUniformMapping *mapping; + + g_assert (key < G_N_ELEMENTS (program->mappings)); + g_assert (key < program->n_mappings); + + mapping = &program->mappings[key]; + + /* Short-circuit if the program optimized the uniform out */ + if (mapping->location == -1) + return NULL; + + /* If the stamp is the same, then we can ignore the request + * and short-circuit as early as possible. This requires the + * caller to increment their private stamp when they change + * internal state. + * + * This is generally used for the shared uniforms like projection, + * modelview, clip, etc to avoid so many comparisons which cost + * considerable CPU. + */ + if (stamp != 0 && stamp == mapping->stamp) + return NULL; + + if G_LIKELY (format == mapping->info.format && array_count <= mapping->info.array_count) + { + *out_mapping = mapping; + return GSK_GL_UNIFORM_VALUE (state->values_buf, mapping->info.offset); + } + + return gsk_gl_uniform_state_init_value (state, program, format, array_count, key, out_mapping); +} + +G_GNUC_PURE static inline guint +gsk_gl_uniform_state_align (guint current_pos, + guint size) +{ + guint align = size > 8 ? 16 : (size > 4 ? 8 : 4); + guint masked = current_pos & (align - 1); + + g_assert (size > 0); + g_assert (align == 4 || align == 8 || align == 16); + g_assert (masked < align); + + return align - masked; +} + +static inline gpointer +gsk_gl_uniform_state_realloc (GskGLUniformState *state, + guint size, + guint *offset) +{ + guint padding = gsk_gl_uniform_state_align (state->values_pos, size); + + if G_UNLIKELY (state->values_len - padding - size < state->values_pos) + { + state->values_len *= 2; + state->values_buf = g_realloc (state->values_buf, state->values_len); + } + + /* offsets are in slots of 4 to use fewer bits */ + g_assert ((state->values_pos + padding) % 4 == 0); + *offset = (state->values_pos + padding) / 4; + state->values_pos += padding + size; + + return GSK_GL_UNIFORM_VALUE (state->values_buf, *offset); +} + +#define GSK_GL_UNIFORM_STATE_REPLACE(info, u, type, count) \ + G_STMT_START { \ + if ((info)->info.initial && count == (info)->info.array_count) \ + { \ + u = GSK_GL_UNIFORM_VALUE (state->values_buf, (info)->info.offset); \ + } \ + else \ + { \ + guint offset; \ + u = gsk_gl_uniform_state_realloc (state, sizeof(type) * MAX (1, count), &offset); \ + g_assert (offset < (1 << GSK_GL_UNIFORM_OFFSET_BITS)); \ + (info)->info.offset = offset; \ + /* We might have increased array length */ \ + (info)->info.array_count = count; \ + } \ + } G_STMT_END + +static inline void +gsk_gl_uniform_info_changed (GskGLUniformMapping *info, + guint stamp) +{ + info->stamp = stamp; + info->info.initial = FALSE; +} + +static inline void +gsk_gl_uniform_state_set1f (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + float value0) +{ + Uniform1f *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != 0); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_1F, 1, key, stamp, &info))) + { + if (info->info.initial || u->v0 != value0) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform1f , 1); + u->v0 = value0; + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set2f (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + float value0, + float value1) +{ + Uniform2f *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_2F, 1, key, stamp, &info))) + { + if (info->info.initial || u->v0 != value0 || u->v1 != value1) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform2f, 1); + u->v0 = value0; + u->v1 = value1; + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set3f (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + float value0, + float value1, + float value2) +{ + Uniform3f *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_3F, 1, key, stamp, &info))) + { + if (info->info.initial || u->v0 != value0 || u->v1 != value1 || u->v2 != value2) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform3f, 1); + u->v0 = value0; + u->v1 = value1; + u->v2 = value2; + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set4f (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + float value0, + float value1, + float value2, + float value3) +{ + Uniform4f *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_4F, 1, key, stamp, &info))) + { + if (info->info.initial || u->v0 != value0 || u->v1 != value1 || u->v2 != value2 || u->v3 != value3) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform4f, 1); + u->v0 = value0; + u->v1 = value1; + u->v2 = value2; + u->v3 = value3; + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set1ui (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + guint value0) +{ + Uniform1ui *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_1UI, 1, key, stamp, &info))) + { + if (info->info.initial || u->v0 != value0) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform1ui, 1); + u->v0 = value0; + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set1i (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + int value0) +{ + Uniform1i *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_1I, 1, key, stamp, &info))) + { + if (info->info.initial || u->v0 != value0) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform1i, 1); + u->v0 = value0; + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set2i (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + int value0, + int value1) +{ + Uniform2i *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_2I, 1, key, stamp, &info))) + { + if (info->info.initial || u->v0 != value0 || u->v1 != value1) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform2i, 1); + u->v0 = value0; + u->v1 = value1; + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set3i (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + int value0, + int value1, + int value2) +{ + Uniform3i *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_3I, 1, key, stamp, &info))) + { + if (info->info.initial || u->v0 != value0 || u->v1 != value1 || u->v2 != value2) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform3i, 1); + u->v0 = value0; + u->v1 = value1; + u->v2 = value2; + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set4i (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + int value0, + int value1, + int value2, + int value3) +{ + Uniform4i *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_4I, 1, key, stamp, &info))) + { + if (info->info.initial || u->v0 != value0 || u->v1 != value1 || u->v2 != value2 || u->v3 != value3) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform4i, 1); + u->v0 = value0; + u->v1 = value1; + u->v2 = value2; + u->v3 = value3; + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set_rounded_rect (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + const GskRoundedRect *rounded_rect) +{ + GskRoundedRect *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + g_assert (rounded_rect != NULL); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT, 1, key, stamp, &info))) + { + if (info->info.initial || memcmp (u, rounded_rect, sizeof *u) != 0) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, GskRoundedRect, 1); + memcpy (u, rounded_rect, sizeof *rounded_rect); + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set_matrix (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + const graphene_matrix_t *matrix) +{ + graphene_matrix_t *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + g_assert (matrix != NULL); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_MATRIX, 1, key, stamp, &info))) + { + if (info->info.initial || memcmp (u, matrix, sizeof *u) != 0) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, graphene_matrix_t, 1); + memcpy (u, matrix, sizeof *matrix); + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +/** + * gsk_gl_uniform_state_set_texture: + * @state: a `GskGLUniformState` + * @program: the program id + * @location: the location of the texture + * @texture_slot: a texturing slot such as GL_TEXTURE0 + * + * Sets the uniform expecting a texture to @texture_slot. This API + * expects a texture slot such as GL_TEXTURE0 to reduce chances of + * miss-use by the caller. + * + * The value stored to the uniform is in the form of 0 for GL_TEXTURE0, + * 1 for GL_TEXTURE1, and so on. + */ +static inline void +gsk_gl_uniform_state_set_texture (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + guint texture_slot) +{ + GskGLUniformMapping *info; + guint *u; + + g_assert (texture_slot >= GL_TEXTURE0); + g_assert (texture_slot < GL_TEXTURE16); + + texture_slot -= GL_TEXTURE0; + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_TEXTURE, 1, key, stamp, &info))) + { + if (info->info.initial || *u != texture_slot) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, guint, 1); + *u = texture_slot; + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +/** + * gsk_gl_uniform_state_set_color: + * @state: a `GskGLUniformState` + * @program: a program id > 0 + * @location: the uniform location + * @color: a color to set or %NULL for transparent + * + * Sets a uniform to the color described by @color. This is a convenience + * function to allow callers to avoid having to translate colors to floats + * in other portions of the renderer. + */ +static inline void +gsk_gl_uniform_state_set_color (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + const GdkRGBA *color) +{ + static const GdkRGBA transparent = {0}; + GskGLUniformMapping *info; + GdkRGBA *u; + + g_assert (state != NULL); + g_assert (program != NULL); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_COLOR, 1, key, stamp, &info))) + { + if (color == NULL) + color = &transparent; + + if (info->info.initial || memcmp (color, u, sizeof *u) != 0) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, GdkRGBA, 1); + memcpy (u, color, sizeof *color); + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set1fv (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + guint count, + const float *value) +{ + Uniform1f *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + g_assert (count > 0); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_1FV, count, key, stamp, &info))) + { + if (info->info.initial || count != info->info.array_count || memcmp (u, value, sizeof *u * count) != 0) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform1f, count); + memcpy (u, value, sizeof (Uniform1f) * count); + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set2fv (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + guint count, + const float *value) +{ + Uniform2f *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + g_assert (count > 0); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_2FV, count, key, stamp, &info))) + { + if (info->info.initial || count != info->info.array_count || memcmp (u, value, sizeof *u * count) != 0) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform2f, count); + memcpy (u, value, sizeof (Uniform2f) * count); + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set3fv (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + guint count, + const float *value) +{ + Uniform3f *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + g_assert (count > 0); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_3FV, count, key, stamp, &info))) + { + if (info->info.initial || count != info->info.array_count || memcmp (u, value, sizeof *u * count) != 0) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform3f, count); + memcpy (u, value, sizeof (Uniform3f) * count); + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline void +gsk_gl_uniform_state_set4fv (GskGLUniformState *state, + GskGLUniformProgram *program, + guint key, + guint stamp, + guint count, + const float *value) +{ + Uniform4f *u; + GskGLUniformMapping *info; + + g_assert (state != NULL); + g_assert (program != NULL); + g_assert (count > 0); + + if ((u = gsk_gl_uniform_state_get_value (state, program, GSK_GL_UNIFORM_FORMAT_4FV, count, key, stamp, &info))) + { + if (info->info.initial || count != info->info.array_count || memcmp (u, value, sizeof *u * count) != 0) + { + GSK_GL_UNIFORM_STATE_REPLACE (info, u, Uniform4f, count); + memcpy (u, value, sizeof (Uniform4f) * count); + gsk_gl_uniform_info_changed (info, stamp); + } + } +} + +static inline guint +gsk_gl_uniform_state_fmix (guint program, + guint location) +{ + guint h = (program << 16) | location; + + h ^= h >> 16; + h *= 0x85ebca6b; + h ^= h >> 13; + h *= 0xc2b2ae35; + h ^= h >> 16; + + return h; +} + +/* + * gsk_gl_uniform_state_apply: + * @state: the uniform state + * @program: the program id + * @location: the location of the uniform + * @offset: the offset of the data within the buffer + * @info: the uniform info + * + * This function can be used to apply state that was previously recorded + * by the `GskGLUniformState`. + * + * It is specifically useful from the `GskGLCommandQueue` to execute uniform + * changes but only when they have changed from the current value. + */ +static inline void +gsk_gl_uniform_state_apply (GskGLUniformState *state, + guint program, + guint location, + GskGLUniformInfo info) +{ + guint index = gsk_gl_uniform_state_fmix (program, location) % G_N_ELEMENTS (state->apply_hash); + gconstpointer dataptr = GSK_GL_UNIFORM_VALUE (state->values_buf, info.offset); + + /* aligned, can treat as unsigned */ + if (*(guint *)&info == *(guint *)&state->apply_hash[index]) + return; + + state->apply_hash[index] = info; + + /* TODO: We could do additional comparisons here to make sure we are + * changing state. + */ + + switch (info.format) + { + case GSK_GL_UNIFORM_FORMAT_1F: + glUniform1fv (location, 1, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_2F: + glUniform2fv (location, 1, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_3F: + glUniform3fv (location, 1, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_4F: + glUniform4fv (location, 1, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_1FV: + glUniform1fv (location, info.array_count, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_2FV: + glUniform2fv (location, info.array_count, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_3FV: + glUniform3fv (location, info.array_count, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_4FV: + glUniform4fv (location, info.array_count, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_1I: + case GSK_GL_UNIFORM_FORMAT_TEXTURE: + glUniform1iv (location, 1, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_2I: + glUniform2iv (location, 1, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_3I: + glUniform3iv (location, 1, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_4I: + glUniform4iv (location, 1, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_1UI: + glUniform1uiv (location, 1, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_MATRIX: { + float mat[16]; + graphene_matrix_to_float (dataptr, mat); + glUniformMatrix4fv (location, 1, GL_FALSE, mat); +#if 0 + /* TODO: If Graphene can give us a peek here on platforms + * where the format is float[16] (most/all x86_64?) then + * We can avoid the SIMD operation to convert the format. + */ + G_STATIC_ASSERT (sizeof (graphene_matrix_t) == 16*4); + glUniformMatrix4fv (location, 1, GL_FALSE, dataptr); +#endif + } + break; + + case GSK_GL_UNIFORM_FORMAT_COLOR: + glUniform4fv (location, 1, dataptr); + break; + + case GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT: + glUniform4fv (location, 3, dataptr); + break; + + default: + g_assert_not_reached (); + } +} + +G_END_DECLS + diff --git gsk/gl/inlinearray.h gsk/gl/inlinearray.h new file mode 100644 index 0000000000..204513d23c --- /dev/null +++ gsk/gl/inlinearray.h @@ -0,0 +1,75 @@ +#pragma once + +#define DEFINE_INLINE_ARRAY(Type, prefix, ElementType) \ + typedef struct _##Type { \ + gsize len; \ + gsize allocated; \ + ElementType *items; \ + } Type; \ + \ + static inline void \ + prefix##_init (Type *ar, \ + gsize initial_size) \ + { \ + ar->len = 0; \ + ar->allocated = initial_size ? initial_size : 16; \ + ar->items = g_new0 (ElementType, ar->allocated); \ + } \ + \ + static inline void \ + prefix##_clear (Type *ar) \ + { \ + ar->len = 0; \ + ar->allocated = 0; \ + g_clear_pointer (&ar->items, g_free); \ + } \ + \ + static inline ElementType * \ + prefix##_head (Type *ar) \ + { \ + return &ar->items[0]; \ + } \ + \ + static inline ElementType * \ + prefix##_tail (Type *ar) \ + { \ + return &ar->items[ar->len-1]; \ + } \ + \ + static inline ElementType * \ + prefix##_append (Type *ar) \ + { \ + if G_UNLIKELY (ar->len == ar->allocated) \ + { \ + ar->allocated *= 2; \ + ar->items = g_renew (ElementType, ar->items, ar->allocated);\ + } \ + \ + ar->len++; \ + \ + return prefix##_tail (ar); \ + } \ + \ + static inline ElementType * \ + prefix##_append_n (Type *ar, \ + gsize n) \ + { \ + if G_UNLIKELY ((ar->len + n) > ar->allocated) \ + { \ + while ((ar->len + n) > ar->allocated) \ + ar->allocated *= 2; \ + ar->items = g_renew (ElementType, ar->items, ar->allocated);\ + } \ + \ + ar->len += n; \ + \ + return &ar->items[ar->len-n]; \ + } \ + \ + static inline gsize \ + prefix##_index_of (Type *ar, \ + const ElementType *element) \ + { \ + return element - &ar->items[0]; \ + } + diff --git gsk/gl/ninesliceprivate.h gsk/gl/ninesliceprivate.h new file mode 100644 index 0000000000..7191ec5f13 --- /dev/null +++ gsk/gl/ninesliceprivate.h @@ -0,0 +1,308 @@ +/* ninesliceprivate.h + * + * Copyright 2017 Timm Bäder + * Copyright 2021 Christian Hergert + * + * 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 program. If not, see . + * + * SPDX-License-Identifier: LGPL-2.1-or-later + */ + +#pragma once + +#include "gskgltextureprivate.h" + +#if 0 +# define DEBUG_NINE_SLICE +#endif + +G_BEGIN_DECLS + +enum { + NINE_SLICE_TOP_LEFT = 0, + NINE_SLICE_TOP_CENTER = 1, + NINE_SLICE_TOP_RIGHT = 2, + NINE_SLICE_LEFT_CENTER = 3, + NINE_SLICE_CENTER = 4, + NINE_SLICE_RIGHT_CENTER = 5, + NINE_SLICE_BOTTOM_LEFT = 6, + NINE_SLICE_BOTTOM_CENTER = 7, + NINE_SLICE_BOTTOM_RIGHT = 8, +}; + +static inline bool G_GNUC_PURE +nine_slice_is_visible (const GskGLTextureNineSlice *slice) +{ + return slice->rect.width > 0 && slice->rect.height > 0; +} + +static inline void +nine_slice_rounded_rect (GskGLTextureNineSlice *slices, + const GskRoundedRect *rect) +{ + const graphene_point_t *origin = &rect->bounds.origin; + const graphene_size_t *size = &rect->bounds.size; + int top_height = ceilf (MAX (rect->corner[GSK_CORNER_TOP_LEFT].height, + rect->corner[GSK_CORNER_TOP_RIGHT].height)); + int bottom_height = ceilf (MAX (rect->corner[GSK_CORNER_BOTTOM_LEFT].height, + rect->corner[GSK_CORNER_BOTTOM_RIGHT].height)); + int right_width = ceilf (MAX (rect->corner[GSK_CORNER_TOP_RIGHT].width, + rect->corner[GSK_CORNER_BOTTOM_RIGHT].width)); + int left_width = ceilf (MAX (rect->corner[GSK_CORNER_TOP_LEFT].width, + rect->corner[GSK_CORNER_BOTTOM_LEFT].width)); + + /* Top left */ + slices[0].rect.x = origin->x; + slices[0].rect.y = origin->y; + slices[0].rect.width = left_width; + slices[0].rect.height = top_height; + + /* Top center */ + slices[1].rect.x = origin->x + size->width / 2.0 - 0.5; + slices[1].rect.y = origin->y; + slices[1].rect.width = 1; + slices[1].rect.height = top_height; + + /* Top right */ + slices[2].rect.x = origin->x + size->width - right_width; + slices[2].rect.y = origin->y; + slices[2].rect.width = right_width; + slices[2].rect.height = top_height; + + /* Left center */ + slices[3].rect.x = origin->x; + slices[3].rect.y = origin->y + size->height / 2; + slices[3].rect.width = left_width; + slices[3].rect.height = 1; + + /* center */ + slices[4].rect.x = origin->x + size->width / 2.0 - 0.5; + slices[4].rect.y = origin->y + size->height / 2.0 - 0.5; + slices[4].rect.width = 1; + slices[4].rect.height = 1; + + /* Right center */ + slices[5].rect.x = origin->x + size->width - right_width; + slices[5].rect.y = origin->y + (size->height / 2.0) - 0.5; + slices[5].rect.width = right_width; + slices[5].rect.height = 1; + + /* Bottom Left */ + slices[6].rect.x = origin->x; + slices[6].rect.y = origin->y + size->height - bottom_height; + slices[6].rect.width = left_width; + slices[6].rect.height = bottom_height; + + /* Bottom center */ + slices[7].rect.x = origin->x + (size->width / 2.0) - 0.5; + slices[7].rect.y = origin->y + size->height - bottom_height; + slices[7].rect.width = 1; + slices[7].rect.height = bottom_height; + + /* Bottom right */ + slices[8].rect.x = origin->x + size->width - right_width; + slices[8].rect.y = origin->y + size->height - bottom_height; + slices[8].rect.width = right_width; + slices[8].rect.height = bottom_height; + +#ifdef DEBUG_NINE_SLICE + /* These only hold true when the values from ceilf() above + * are greater than one. Otherwise they fail, like will happen + * with the node editor viewing the textures zoomed out. + */ + if (size->width > 1) + g_assert_cmpfloat (size->width, >=, left_width + right_width); + if (size->height > 1) + g_assert_cmpfloat (size->height, >=, top_height + bottom_height); +#endif +} + +static inline void +nine_slice_to_texture_coords (GskGLTextureNineSlice *slices, + int texture_width, + int texture_height) +{ + float fw = texture_width; + float fh = texture_height; + + for (guint i = 0; i < 9; i++) + { + GskGLTextureNineSlice *slice = &slices[i]; + + slice->area.x = slice->rect.x / fw; + slice->area.y = 1.0 - ((slice->rect.y + slice->rect.height) / fh); + slice->area.x2 = ((slice->rect.x + slice->rect.width) / fw); + slice->area.y2 = (1.0 - (slice->rect.y / fh)); + +#ifdef DEBUG_NINE_SLICE + g_assert_cmpfloat (slice->area.x, >=, 0); + g_assert_cmpfloat (slice->area.x, <=, 1); + g_assert_cmpfloat (slice->area.y, >=, 0); + g_assert_cmpfloat (slice->area.y, <=, 1); + g_assert_cmpfloat (slice->area.x2, >, slice->area.x); + g_assert_cmpfloat (slice->area.y2, >, slice->area.y); +#endif + } +} + +static inline void +nine_slice_grow (GskGLTextureNineSlice *slices, + int amount_x, + int amount_y) +{ + if (amount_x == 0 && amount_y == 0) + return; + + /* top left */ + slices[0].rect.x -= amount_x; + slices[0].rect.y -= amount_y; + if (amount_x > slices[0].rect.width) + slices[0].rect.width += amount_x * 2; + else + slices[0].rect.width += amount_x; + + if (amount_y > slices[0].rect.height) + slices[0].rect.height += amount_y * 2; + else + slices[0].rect.height += amount_y; + + + /* Top center */ + slices[1].rect.y -= amount_y; + if (amount_y > slices[1].rect.height) + slices[1].rect.height += amount_y * 2; + else + slices[1].rect.height += amount_y; + + /* top right */ + slices[2].rect.y -= amount_y; + if (amount_x > slices[2].rect.width) + { + slices[2].rect.x -= amount_x; + slices[2].rect.width += amount_x * 2; + } + else + { + slices[2].rect.width += amount_x; + } + + if (amount_y > slices[2].rect.height) + slices[2].rect.height += amount_y * 2; + else + slices[2].rect.height += amount_y; + + + + slices[3].rect.x -= amount_x; + if (amount_x > slices[3].rect.width) + slices[3].rect.width += amount_x * 2; + else + slices[3].rect.width += amount_x; + + /* Leave center alone */ + + if (amount_x > slices[5].rect.width) + { + slices[5].rect.x -= amount_x; + slices[5].rect.width += amount_x * 2; + } + else + { + slices[5].rect.width += amount_x; + } + + + /* Bottom left */ + slices[6].rect.x -= amount_x; + if (amount_x > slices[6].rect.width) + { + slices[6].rect.width += amount_x * 2; + } + else + { + slices[6].rect.width += amount_x; + } + + if (amount_y > slices[6].rect.height) + { + slices[6].rect.y -= amount_y; + slices[6].rect.height += amount_y * 2; + } + else + { + slices[6].rect.height += amount_y; + } + + + /* Bottom center */ + if (amount_y > slices[7].rect.height) + { + slices[7].rect.y -= amount_y; + slices[7].rect.height += amount_y * 2; + } + else + { + slices[7].rect.height += amount_y; + } + + if (amount_x > slices[8].rect.width) + { + slices[8].rect.x -= amount_x; + slices[8].rect.width += amount_x * 2; + } + else + { + slices[8].rect.width += amount_x; + } + + if (amount_y > slices[8].rect.height) + { + slices[8].rect.y -= amount_y; + slices[8].rect.height += amount_y * 2; + } + else + { + slices[8].rect.height += amount_y; + } + +#ifdef DEBUG_NINE_SLICE + /* These cannot be relied on in all cases right now, specifically + * when viewing data zoomed out. + */ + for (guint i = 0; i < 9; i ++) + { + g_assert_cmpint (slices[i].rect.x, >=, 0); + g_assert_cmpint (slices[i].rect.y, >=, 0); + g_assert_cmpint (slices[i].rect.width, >=, 0); + g_assert_cmpint (slices[i].rect.height, >=, 0); + } + + /* Rows don't overlap */ + for (guint i = 0; i < 3; i++) + { + int lhs = slices[i * 3 + 0].rect.x + slices[i * 3 + 0].rect.width; + int rhs = slices[i * 3 + 1].rect.x; + + /* Ignore the case where we are scaled out and the + * positioning is degenerate, such as from node-editor. + */ + if (rhs > 1) + g_assert_cmpint (lhs, <, rhs); + } +#endif + +} + +G_END_DECLS + diff --git gsk/gl/resources/blend.glsl gsk/gl/resources/blend.glsl new file mode 100644 index 0000000000..41301c4a8b --- /dev/null +++ gsk/gl/resources/blend.glsl @@ -0,0 +1,314 @@ +// VERTEX_SHADER: +// blend.glsl + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + vUv = vec2(aUv.x, aUv.y); +} + +// FRAGMENT_SHADER: +// blend.glsl + +uniform int u_mode; +uniform sampler2D u_source2; + +float +combine (float source, float backdrop) +{ + return source + backdrop * (1.0 - source); +} + +vec4 +composite (vec4 Cs, vec4 Cb, vec3 B) +{ + float ao = Cs.a + Cb.a * (1.0 - Cs.a); + vec3 Co = (Cs.a*(1.0 - Cb.a)*Cs.rgb + Cs.a*Cb.a*B + (1.0 - Cs.a)*Cb.a*Cb.rgb) / ao; + return vec4(Co, ao); +} + +vec4 +normal (vec4 Cs, vec4 Cb) +{ + return composite (Cs, Cb, Cs.rgb); +} + +vec4 +multiply (vec4 Cs, vec4 Cb) +{ + return composite (Cs, Cb, Cs.rgb * Cb.rgb); +} + +vec4 +difference (vec4 Cs, vec4 Cb) +{ + return composite (Cs, Cb, abs(Cs.rgb - Cb.rgb)); +} + +vec4 +screen (vec4 Cs, vec4 Cb) +{ + return composite (Cs, Cb, Cs.rgb + Cb.rgb - Cs.rgb * Cb.rgb); +} + +float +hard_light (float source, float backdrop) +{ + if (source <= 0.5) + return 2.0 * backdrop * source; + else + return 2.0 * (backdrop + source - backdrop * source) - 1.0; +} + +vec4 +hard_light (vec4 Cs, vec4 Cb) +{ + vec3 B = vec3 (hard_light (Cs.r, Cb.r), + hard_light (Cs.g, Cb.g), + hard_light (Cs.b, Cb.b)); + return composite (Cs, Cb, B); +} + +float +soft_light (float source, float backdrop) +{ + float db; + + if (backdrop <= 0.25) + db = ((16.0 * backdrop - 12.0) * backdrop + 4.0) * backdrop; + else + db = sqrt (backdrop); + + if (source <= 0.5) + return backdrop - (1.0 - 2.0 * source) * backdrop * (1.0 - backdrop); + else + return backdrop + (2.0 * source - 1.0) * (db - backdrop); +} + +vec4 +soft_light (vec4 Cs, vec4 Cb) +{ + vec3 B = vec3 (soft_light (Cs.r, Cb.r), + soft_light (Cs.g, Cb.g), + soft_light (Cs.b, Cb.b)); + return composite (Cs, Cb, B); +} + +vec4 +overlay (vec4 Cs, vec4 Cb) +{ + vec3 B = vec3 (hard_light (Cb.r, Cs.r), + hard_light (Cb.g, Cs.g), + hard_light (Cb.b, Cs.b)); + return composite (Cs, Cb, B); +} + +vec4 +darken (vec4 Cs, vec4 Cb) +{ + vec3 B = min (Cs.rgb, Cb.rgb); + return composite (Cs, Cb, B); +} + +vec4 +lighten (vec4 Cs, vec4 Cb) +{ + vec3 B = max (Cs.rgb, Cb.rgb); + return composite (Cs, Cb, B); +} + +float +color_dodge (float source, float backdrop) +{ + return (source == 1.0) ? source : min (backdrop / (1.0 - source), 1.0); +} + +vec4 +color_dodge (vec4 Cs, vec4 Cb) +{ + vec3 B = vec3 (color_dodge (Cs.r, Cb.r), + color_dodge (Cs.g, Cb.g), + color_dodge (Cs.b, Cb.b)); + return composite (Cs, Cb, B); +} + + +float +color_burn (float source, float backdrop) +{ + return (source == 0.0) ? source : max ((1.0 - ((1.0 - backdrop) / source)), 0.0); +} + +vec4 +color_burn (vec4 Cs, vec4 Cb) +{ + vec3 B = vec3 (color_burn (Cs.r, Cb.r), + color_burn (Cs.g, Cb.g), + color_burn (Cs.b, Cb.b)); + return composite (Cs, Cb, B); +} + +vec4 +exclusion (vec4 Cs, vec4 Cb) +{ + vec3 B = Cb.rgb + Cs.rgb - 2.0 * Cb.rgb * Cs.rgb; + return composite (Cs, Cb, B); +} + +float +lum (vec3 c) +{ + return 0.3 * c.r + 0.59 * c.g + 0.11 * c.b; +} + +vec3 +clip_color (vec3 c) +{ + float l = clamp (lum (c), 0.0, 1.0); + float n = min (c.r, min (c.g, c.b)); + float x = max (c.r, max (c.g, c.b)); + if (n < 0.0) c = l + (((c - l) * l) / (l - n)); + if (x > 1.0) c = l + (((c - l) * (1.0 - l)) / (x - l)); + return c; +} + +vec3 +set_lum (vec3 c, float l) +{ + float d = l - lum (c); + return clip_color (vec3 (c.r + d, c.g + d, c.b + d)); +} + +float +sat (vec3 c) +{ + return max (c.r, max (c.g, c.b)) - min (c.r, min (c.g, c.b)); +} + +vec3 +set_sat (vec3 c, float s) +{ + float cmin = min (c.r, min (c.g, c.b)); + float cmax = max (c.r, max (c.g, c.b)); + vec3 res; + + if (cmax == cmin) + res = vec3 (0, 0, 0); + else + { + if (c.r == cmax) + { + if (c.g == cmin) + { + res.b = ((c.b - cmin) * s) / (cmax - cmin); + res.g = 0.0; + } + else + { + res.g = ((c.g - cmin) * s) / (cmax - cmin); + res.b = 0.0; + } + res.r = s; + } + else if (c.g == cmax) + { + if (c.r == cmin) + { + res.b = ((c.b - cmin) * s) / (cmax - cmin); + res.r = 0.0; + } + else + { + res.r = ((c.r - cmin) * s) / (cmax - cmin); + res.b = 0.0; + } + res.g = s; + } + else + { + if (c.r == cmin) + { + res.g = ((c.g - cmin) * s) / (cmax - cmin); + res.r = 0.0; + } + else + { + res.r = ((c.r - cmin) * s) / (cmax - cmin); + res.g = 0.0; + } + res.b = s; + } + } + return res; +} + +vec4 +color (vec4 Cs, vec4 Cb) +{ + vec3 B = set_lum (Cs.rgb, lum (Cb.rgb)); + return composite (Cs, Cb, B); +} + +vec4 +hue (vec4 Cs, vec4 Cb) +{ + vec3 B = set_lum (set_sat (Cs.rgb, sat (Cb.rgb)), lum (Cb.rgb)); + return composite (Cs, Cb, B); +} + +vec4 +saturation (vec4 Cs, vec4 Cb) +{ + vec3 B = set_lum (set_sat (Cb.rgb, sat (Cs.rgb)), lum (Cb.rgb)); + return composite (Cs, Cb, B); +} + +vec4 +luminosity (vec4 Cs, vec4 Cb) +{ + vec3 B = set_lum (Cb.rgb, lum (Cs.rgb)); + return composite (Cs, Cb, B); +} + +void main() { + vec4 bottom_color = GskTexture(u_source, vUv); + vec4 top_color = GskTexture(u_source2, vUv); + + vec4 result; + if (u_mode == 0) + result = normal(top_color, bottom_color); + else if (u_mode == 1) + result = multiply(top_color, bottom_color); + else if (u_mode == 2) + result = screen(top_color, bottom_color); + else if (u_mode == 3) + result = overlay(top_color, bottom_color); + else if (u_mode == 4) + result = darken(top_color, bottom_color); + else if (u_mode == 5) + result = lighten(top_color, bottom_color); + else if (u_mode == 6) + result = color_dodge(top_color, bottom_color); + else if (u_mode == 7) + result = color_burn(top_color, bottom_color); + else if (u_mode == 8) + result = hard_light(top_color, bottom_color); + else if (u_mode == 9) + result = soft_light(top_color, bottom_color); + else if (u_mode == 10) + result = difference(top_color, bottom_color); + else if (u_mode == 11) + result = exclusion(top_color, bottom_color); + else if (u_mode == 12) + result = color(top_color, bottom_color); + else if (u_mode == 13) + result = hue(top_color, bottom_color); + else if (u_mode == 14) + result = saturation(top_color, bottom_color); + else if (u_mode == 15) + result = luminosity(top_color, bottom_color); + else + discard; + + gskSetScaledOutputColor(result, u_alpha); +} diff --git gsk/gl/resources/blit.glsl gsk/gl/resources/blit.glsl new file mode 100644 index 0000000000..ced047b9a9 --- /dev/null +++ gsk/gl/resources/blit.glsl @@ -0,0 +1,17 @@ +// VERTEX_SHADER: +// blit.glsl + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + vUv = vec2(aUv.x, aUv.y); +} + +// FRAGMENT_SHADER: +// blit.glsl + +void main() { + vec4 diffuse = GskTexture(u_source, vUv); + + gskSetScaledOutputColor(diffuse, u_alpha); +} diff --git gsk/gl/resources/blur.glsl gsk/gl/resources/blur.glsl new file mode 100644 index 0000000000..7e7c1c2518 --- /dev/null +++ gsk/gl/resources/blur.glsl @@ -0,0 +1,59 @@ +// VERTEX_SHADER: +// blur.glsl + +uniform float u_blur_radius; +uniform vec2 u_blur_size; +uniform vec2 u_blur_dir; + +_OUT_ vec2 pixel_step; +_OUT_ float pixels_per_side; +_OUT_ vec3 initial_gaussian; + +const float PI = 3.14159265; +const float RADIUS_MULTIPLIER = 2.0; + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + vUv = vec2(aUv.x, aUv.y); + + pixel_step = (vec2(1.0) / u_blur_size) * u_blur_dir; + pixels_per_side = floor(u_blur_radius * RADIUS_MULTIPLIER / 2.0); + + float sigma = u_blur_radius / 2.0; // *shrug* + initial_gaussian.x = 1.0 / (sqrt(2.0 * PI) * sigma); + initial_gaussian.y = exp(-0.5 / (sigma * sigma)); + initial_gaussian.z = initial_gaussian.y * initial_gaussian.y; +} + +// FRAGMENT_SHADER: +// blur.glsl + +_IN_ vec2 pixel_step; +_IN_ float pixels_per_side; +_IN_ vec3 initial_gaussian; + +// blur_radius 0 is NOT supported and MUST be caught before. + +// Partially from http://callumhay.blogspot.com/2010/09/gaussian-blur-shader-glsl.html +void main() { + vec3 incrementalGaussian = initial_gaussian; + + float coefficientSum = 0.0; + vec4 sum = GskTexture(u_source, vUv) * incrementalGaussian.x; + coefficientSum += incrementalGaussian.x; + incrementalGaussian.xy *= incrementalGaussian.yz; + + vec2 p = pixel_step; + for (int i = 1; i <= int(pixels_per_side); i++) { + sum += GskTexture(u_source, vUv - p) * incrementalGaussian.x; + sum += GskTexture(u_source, vUv + p) * incrementalGaussian.x; + + coefficientSum += 2.0 * incrementalGaussian.x; + incrementalGaussian.xy *= incrementalGaussian.yz; + + p += pixel_step; + } + + gskSetOutputColor(sum / coefficientSum); +} diff --git gsk/gl/resources/border.glsl gsk/gl/resources/border.glsl new file mode 100644 index 0000000000..b2436affd1 --- /dev/null +++ gsk/gl/resources/border.glsl @@ -0,0 +1,53 @@ +// VERTEX_SHADER: +// border.glsl + +uniform vec4 u_widths; +#if defined(GSK_LEGACY) || defined(GSK_GLES) +uniform vec4 u_outline_rect[3]; +#else +uniform vec4[3] u_outline_rect; +#endif + +_OUT_ vec4 final_color; +_GSK_ROUNDED_RECT_OUT_(transformed_outside_outline); +_GSK_ROUNDED_RECT_OUT_(transformed_inside_outline); + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + final_color = gsk_scaled_premultiply(aColor, u_alpha); + + GskRoundedRect outside = gsk_create_rect(u_outline_rect); + GskRoundedRect inside = gsk_rounded_rect_shrink (outside, u_widths); + + gsk_rounded_rect_transform(outside, u_modelview); + gsk_rounded_rect_transform(inside, u_modelview); + + gsk_rounded_rect_normalize(outside); + gsk_rounded_rect_normalize(inside); + + gsk_rounded_rect_encode(outside, transformed_outside_outline); + gsk_rounded_rect_encode(inside, transformed_inside_outline); +} + +// FRAGMENT_SHADER: +// border.glsl + +#if defined(GSK_LEGACY) || defined(GSK_GLES) +uniform vec4 u_outline_rect[3]; +#else +uniform vec4[3] u_outline_rect; +#endif + +_IN_ vec4 final_color; +_GSK_ROUNDED_RECT_IN_(transformed_outside_outline); +_GSK_ROUNDED_RECT_IN_(transformed_inside_outline); + +void main() { + vec2 frag = gsk_get_frag_coord(); + float outer_coverage = gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag); + float inner_coverage = gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag); + float alpha = clamp(outer_coverage - inner_coverage, 0.0, 1.0); + + gskSetScaledOutputColor(final_color, alpha); +} diff --git gsk/gl/resources/color.glsl gsk/gl/resources/color.glsl new file mode 100644 index 0000000000..ba98b1b170 --- /dev/null +++ gsk/gl/resources/color.glsl @@ -0,0 +1,20 @@ +// VERTEX_SHADER: +// color.glsl + +_OUT_ vec4 final_color; + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + final_color = gsk_scaled_premultiply(aColor, u_alpha); +} + +// FRAGMENT_SHADER: +// color.glsl + +_IN_ vec4 final_color; + +void main() { + gskSetOutputColor(final_color); +} + diff --git gsk/gl/resources/color_matrix.glsl gsk/gl/resources/color_matrix.glsl new file mode 100644 index 0000000000..51835f056f --- /dev/null +++ gsk/gl/resources/color_matrix.glsl @@ -0,0 +1,27 @@ +// VERTEX_SHADER: +// color_matrix.glsl + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + vUv = vec2(aUv.x, aUv.y); +} + +// FRAGMENT_SHADER: +// color_matrix.glsl + +uniform mat4 u_color_matrix; +uniform vec4 u_color_offset; + +void main() { + vec4 color = GskTexture(u_source, vUv); + + // Un-premultilpy + if (color.a != 0.0) + color.rgb /= color.a; + + color = u_color_matrix * color + u_color_offset; + color = clamp(color, 0.0, 1.0); + + gskSetOutputColor(gsk_scaled_premultiply(color, u_alpha)); +} diff --git gsk/gl/resources/coloring.glsl gsk/gl/resources/coloring.glsl new file mode 100644 index 0000000000..0abe06ff05 --- /dev/null +++ gsk/gl/resources/coloring.glsl @@ -0,0 +1,33 @@ +// VERTEX_SHADER: +// coloring.glsl + +_OUT_ vec4 final_color; +_OUT_ float use_color; + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + vUv = vec2(aUv.x, aUv.y); + + // We use this shader for both plain glyphs (used as mask) + // and color glyphs (used as source). The renderer sets + // aColor to vec4(-1) for color glyphs. + if (distance(aColor,vec4(-1)) < 0.1) + use_color = 0.0; + else + use_color = 1.0; + + final_color = gsk_scaled_premultiply(aColor, u_alpha); +} + +// FRAGMENT_SHADER: +// coloring.glsl + +_IN_ vec4 final_color; +_IN_ float use_color; + +void main() { + vec4 diffuse = GskTexture(u_source, vUv); + + gskSetOutputColor(mix(diffuse * u_alpha, final_color * diffuse.a, use_color)); +} diff --git gsk/gl/resources/conic_gradient.glsl gsk/gl/resources/conic_gradient.glsl new file mode 100644 index 0000000000..9f56978439 --- /dev/null +++ gsk/gl/resources/conic_gradient.glsl @@ -0,0 +1,85 @@ +// VERTEX_SHADER +// conic_gradient.glsl + +uniform vec4 u_geometry; + +_OUT_ vec2 coord; + +void main() { + gl_Position = u_projection * (u_modelview * vec4(aPosition, 0.0, 1.0)); + + vec2 mv0 = u_modelview[0].xy; + vec2 mv1 = u_modelview[1].xy; + vec2 offset = aPosition - u_geometry.xy; + + coord = vec2(dot(mv0, offset), dot(mv1, offset)); +} + +// FRAGMENT_SHADER: +// conic_gradient.glsl + +#define MAX_COLOR_STOPS 6 + +#ifdef GSK_LEGACY +uniform int u_num_color_stops; +#else +uniform highp int u_num_color_stops; // Why? Because it works like this. +#endif + +uniform vec4 u_geometry; +uniform float u_color_stops[MAX_COLOR_STOPS * 5]; + +_IN_ vec2 coord; + +float get_offset(int index) { + // u_color_stops[5 * index] makes Intel Windows driver crash. + // See https://gitlab.gnome.org/GNOME/gtk/-/issues/3783 + int base = 5 * index; + return u_color_stops[base]; +} + +vec4 get_color(int index) { + int base = 5 * index + 1; + + return vec4(u_color_stops[base], + u_color_stops[base + 1], + u_color_stops[base + 2], + u_color_stops[base + 3]); +} + +void main() { + // direction of point in range [-PI, PI] + vec2 pos = floor(coord); + float angle = atan(pos.y, pos.x); + + // fract() does the modulo here, so now we have progress + // into the current conic + float offset = fract(angle * u_geometry.z + u_geometry.w); + float curr_offset; + float next_offset; + + next_offset = get_offset(0); + if (offset < next_offset) { + gskSetOutputColor(gsk_scaled_premultiply(get_color(0), u_alpha)); + return; + } + + if (offset >= get_offset(u_num_color_stops - 1)) { + gskSetOutputColor(gsk_scaled_premultiply(get_color(u_num_color_stops - 1), u_alpha)); + return; + } + + for (int i = 0; i < MAX_COLOR_STOPS; i++) { + curr_offset = next_offset; + next_offset = get_offset(i + 1); + + if (offset < next_offset) { + float f = (offset - curr_offset) / (next_offset - curr_offset); + vec4 curr_color = gsk_scaled_premultiply (get_color(i), u_alpha); + vec4 next_color = gsk_scaled_premultiply (get_color(i + 1), u_alpha); + vec4 color = mix(curr_color, next_color, f); + gskSetOutputColor(color); + return; + } + } +} diff --git gsk/gl/resources/cross_fade.glsl gsk/gl/resources/cross_fade.glsl new file mode 100644 index 0000000000..e61af597e9 --- /dev/null +++ gsk/gl/resources/cross_fade.glsl @@ -0,0 +1,24 @@ +// VERTEX_SHADER: +// cross_fade.glsl + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + vUv = vec2(aUv.x, aUv.y); +} + +// FRAGMENT_SHADER: +// cross_fade.glsl + +uniform float u_progress; +uniform sampler2D u_source2; + +void main() { + vec4 source1 = GskTexture(u_source, vUv); // start child + vec4 source2 = GskTexture(u_source2, vUv); // end child + + float p_start = (1.0 - u_progress) * u_alpha; + float p_end = u_progress * u_alpha; + vec4 color = (p_start * source1) + (p_end * source2); + gskSetOutputColor(color); +} diff --git gsk/gl/resources/custom.glsl gsk/gl/resources/custom.glsl new file mode 100644 index 0000000000..6f91df6cf4 --- /dev/null +++ gsk/gl/resources/custom.glsl @@ -0,0 +1,25 @@ +// VERTEX_SHADER: +// custom.glsl + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + vUv = vec2(aUv.x, aUv.y); +} + +// FRAGMENT_SHADER: +// custom.glsl + +// The shader supplies: +void mainImage(out vec4 fragColor, in vec2 fragCoord, in vec2 resolution, in vec2 uv); + +uniform vec2 u_size; +uniform sampler2D u_source2; +uniform sampler2D u_source3; +uniform sampler2D u_source4; + +void main() { + vec4 fragColor; + vec2 fragCoord = vec2(vUv.x * u_size.x, (1.0-vUv.y) * u_size.y); + mainImage(fragColor, fragCoord, u_size, vUv); + gskSetOutputColor(fragColor); +} diff --git gsk/gl/resources/external.glsl gsk/gl/resources/external.glsl new file mode 100644 index 0000000000..d2aaadd003 --- /dev/null +++ gsk/gl/resources/external.glsl @@ -0,0 +1,34 @@ +// VERTEX_SHADER: +// external.glsl + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + vUv = vec2(aUv.x, aUv.y); +} + +// FRAGMENT_SHADER: +// external.glsl + +#if defined(GSK_GLES) || defined(GSK_GLES3) +uniform samplerExternalOES u_external_source; +#else +/* Just to make this compile, we won't use it without GLES */ +uniform sampler2D u_external_source; +#endif + +uniform int u_premultiply; + +void main() { +/* Open-code this here, since GskTexture() expects a sampler2D */ +#if defined(GSK_GLES) || defined(GSK_LEGACY) + vec4 color = texture2D(u_external_source, vUv); +#else + vec4 color = texture(u_external_source, vUv); +#endif + + if (u_premultiply == 1) + color.rgb *= color.a; + + gskSetOutputColor(color); +} diff --git gsk/gl/resources/filled_border.glsl gsk/gl/resources/filled_border.glsl new file mode 100644 index 0000000000..5c2c221655 --- /dev/null +++ gsk/gl/resources/filled_border.glsl @@ -0,0 +1,58 @@ +// VERTEX_SHADER: +// filled_border.glsl + +uniform vec4 u_widths; +#if defined(GSK_LEGACY) || defined(GSK_GLES) +uniform vec4 u_outline_rect[3]; +#else +uniform vec4[3] u_outline_rect; +#endif + +_OUT_ vec4 outer_color; +_OUT_ vec4 inner_color; +_GSK_ROUNDED_RECT_OUT_(transformed_outside_outline); +_GSK_ROUNDED_RECT_OUT_(transformed_inside_outline); + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + outer_color = gsk_scaled_premultiply(aColor, u_alpha); + inner_color = gsk_scaled_premultiply(aColor2, u_alpha); + + GskRoundedRect outside = gsk_create_rect(u_outline_rect); + GskRoundedRect inside = gsk_rounded_rect_shrink (outside, u_widths); + + gsk_rounded_rect_transform(outside, u_modelview); + gsk_rounded_rect_transform(inside, u_modelview); + + gsk_rounded_rect_normalize(outside); + gsk_rounded_rect_normalize(inside); + + gsk_rounded_rect_encode(outside, transformed_outside_outline); + gsk_rounded_rect_encode(inside, transformed_inside_outline); +} + +// FRAGMENT_SHADER: +// filled_border.glsl + +#if defined(GSK_LEGACY) || defined(GSK_GLES) +uniform vec4 u_outline_rect[3]; +#else +uniform vec4[3] u_outline_rect; +#endif + +_IN_ vec4 outer_color; +_IN_ vec4 inner_color; +_GSK_ROUNDED_RECT_IN_(transformed_outside_outline); +_GSK_ROUNDED_RECT_IN_(transformed_inside_outline); + +void main() { + vec2 frag = gsk_get_frag_coord(); + float outer_coverage = gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag); + float inner_coverage = gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag); + + float alpha = clamp(outer_coverage - inner_coverage, 0.0, 1.0); + float alpha2 = clamp(inner_coverage, 0.0, 1.0); + + gskSetOutputColor((outer_color * alpha) + (inner_color * alpha2)); +} diff --git gsk/gl/resources/inset_shadow.glsl gsk/gl/resources/inset_shadow.glsl new file mode 100644 index 0000000000..dd636c2e7f --- /dev/null +++ gsk/gl/resources/inset_shadow.glsl @@ -0,0 +1,51 @@ +// VERTEX_SHADER: +// inset_shadow.glsl + +uniform float u_spread; +uniform vec2 u_offset; +#if defined(GSK_LEGACY) || defined(GSK_GLES) +uniform vec4 u_outline_rect[3]; +#else +uniform vec4[3] u_outline_rect; +#endif + +_OUT_ vec4 final_color; +_GSK_ROUNDED_RECT_OUT_(transformed_outside_outline); +_GSK_ROUNDED_RECT_OUT_(transformed_inside_outline); + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + final_color = gsk_scaled_premultiply(aColor, u_alpha); + + GskRoundedRect outside = gsk_create_rect(u_outline_rect); + GskRoundedRect inside = gsk_rounded_rect_shrink(outside, vec4(u_spread)); + + gsk_rounded_rect_offset(inside, u_offset); + + gsk_rounded_rect_transform(outside, u_modelview); + gsk_rounded_rect_transform(inside, u_modelview); + + gsk_rounded_rect_normalize(outside); + gsk_rounded_rect_normalize(inside); + + gsk_rounded_rect_encode(outside, transformed_outside_outline); + gsk_rounded_rect_encode(inside, transformed_inside_outline); +} + +// FRAGMENT_SHADER: +// inset_shadow.glsl + +_IN_ vec4 final_color; +_GSK_ROUNDED_RECT_IN_(transformed_outside_outline); +_GSK_ROUNDED_RECT_IN_(transformed_inside_outline); + +void main() { + vec2 frag = gsk_get_frag_coord(); + + float alpha = clamp(gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag) - + gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag), + 0.0, 1.0); + + gskSetScaledOutputColor(final_color, alpha); +} diff --git gsk/gl/resources/linear_gradient.glsl gsk/gl/resources/linear_gradient.glsl new file mode 100644 index 0000000000..d266cdbc79 --- /dev/null +++ gsk/gl/resources/linear_gradient.glsl @@ -0,0 +1,107 @@ +// VERTEX_SHADER +// linear_gradient.glsl +uniform vec4 u_points; + +_OUT_ vec4 info; + +void main() { + gl_Position = u_projection * (u_modelview * vec4(aPosition, 0.0, 1.0)); + + vec2 mv0 = u_modelview[0].xy; + vec2 mv1 = u_modelview[1].xy; + vec2 offset = aPosition - u_points.xy; + vec2 coord = vec2(dot(mv0, offset), + dot(mv1, offset)); + + // Original equation: + // VS | maxDist = length(end - start); + // VS | gradient = end - start; + // VS | gradientLength = length(gradient); + // FS | pos = frag_coord - start + // FS | proj = (dot(gradient, pos) / (gradientLength * gradientLength)) * gradient + // FS | offset = length(proj) / maxDist + + // Simplified formula derivation: + // 1. Notice that maxDist = gradientLength: + // offset = length(proj) / gradientLength + // 2. Let gnorm = gradient / gradientLength, then: + // proj = (dot(gnorm * gradientLength, pos) / (gradientLength * gradientLength)) * (gnorm * gradientLength) = + // = dot(gnorm, pos) * gnorm + // 3. Since gnorm is unit length then: + // length(proj) = length(dot(gnorm, pos) * gnorm) = dot(gnorm, pos) + // 4. We can avoid the FS division by passing a scaled pos from the VS: + // offset = dot(gnorm, pos) / gradientLength = dot(gnorm, pos / gradientLength) + // 5. 1.0 / length(gradient) is inversesqrt(dot(gradient, gradient)) in GLSL + vec2 gradient = vec2(dot(mv0, u_points.zw), + dot(mv1, u_points.zw)); + float rcp_gradient_length = inversesqrt(dot(gradient, gradient)); + + info = rcp_gradient_length * vec4(coord, gradient); +} + +// FRAGMENT_SHADER: +// linear_gradient.glsl + +#define MAX_COLOR_STOPS 6 + +#ifdef GSK_LEGACY +uniform int u_num_color_stops; +#else +uniform highp int u_num_color_stops; // Why? Because it works like this. +#endif + +uniform float u_color_stops[MAX_COLOR_STOPS * 5]; +uniform bool u_repeat; + +_IN_ vec4 info; + +float get_offset(int index) { + // u_color_stops[5 * index] makes Intel Windows driver crash. + // See https://gitlab.gnome.org/GNOME/gtk/-/issues/3783 + int base = 5 * index; + return u_color_stops[base]; +} + +vec4 get_color(int index) { + int base = 5 * index + 1; + + return vec4(u_color_stops[base], + u_color_stops[base + 1], + u_color_stops[base + 2], + u_color_stops[base + 3]); +} + +void main() { + float offset = dot(info.xy, info.zw); + float curr_offset; + float next_offset; + + if (u_repeat) { + offset = fract(offset); + } + + next_offset = get_offset(0); + if (offset < next_offset) { + gskSetOutputColor(gsk_scaled_premultiply(get_color(0), u_alpha)); + return; + } + + if (offset >= get_offset(u_num_color_stops - 1)) { + gskSetOutputColor(gsk_scaled_premultiply(get_color(u_num_color_stops - 1), u_alpha)); + return; + } + + for (int i = 0; i < MAX_COLOR_STOPS; i++) { + curr_offset = next_offset; + next_offset = get_offset(i + 1); + + if (offset < next_offset) { + float f = (offset - curr_offset) / (next_offset - curr_offset); + vec4 curr_color = gsk_scaled_premultiply (get_color(i), u_alpha); + vec4 next_color = gsk_scaled_premultiply (get_color(i + 1), u_alpha); + vec4 color = mix(curr_color, next_color, f); + gskSetOutputColor(color); + return; + } + } +} diff --git gsk/gl/resources/mask.glsl gsk/gl/resources/mask.glsl new file mode 100644 index 0000000000..0c2814be9f --- /dev/null +++ gsk/gl/resources/mask.glsl @@ -0,0 +1,39 @@ +// VERTEX_SHADER: +// mask.glsl + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + vUv = vec2(aUv.x, aUv.y); +} + +// FRAGMENT_SHADER: +// mask.glsl + +uniform int u_mode; +uniform sampler2D u_mask; + +float +luminance (vec3 color) +{ + return dot (vec3 (0.2126, 0.7152, 0.0722), color); +} + +void main() { + vec4 source = GskTexture(u_source, vUv); + vec4 mask = GskTexture(u_mask, vUv); + float mask_value; + + if (u_mode == 0) + mask_value = mask.a; + else if (u_mode == 1) + mask_value = 1.0 - mask.a; + else if (u_mode == 2) + mask_value = luminance (mask.rgb); + else if (u_mode == 3) + mask_value = mask.a - luminance (mask.rgb); + else + mask_value = 0.0; + + gskSetOutputColor(vec4 (source * mask_value)); +} diff --git gsk/gl/resources/outset_shadow.glsl gsk/gl/resources/outset_shadow.glsl new file mode 100644 index 0000000000..03b0a80b2d --- /dev/null +++ gsk/gl/resources/outset_shadow.glsl @@ -0,0 +1,39 @@ +// VERTEX_SHADER: +// outset_shadow.glsl + +#if defined(GSK_LEGACY) || defined(GSK_GLES) +uniform vec4 u_outline_rect[3]; +#else +uniform vec4[3] u_outline_rect; +#endif + +_OUT_ vec4 final_color; +_GSK_ROUNDED_RECT_OUT_(transformed_outline); + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + vUv = vec2(aUv.x, aUv.y); + + final_color = gsk_scaled_premultiply(aColor, u_alpha); + + GskRoundedRect outline = gsk_create_rect(u_outline_rect); + gsk_rounded_rect_transform(outline, u_modelview); + gsk_rounded_rect_normalize(outline); + gsk_rounded_rect_encode(outline, transformed_outline); +} + +// FRAGMENT_SHADER: +// outset_shadow.glsl + +_IN_ vec4 final_color; +_GSK_ROUNDED_RECT_IN_(transformed_outline); + +void main() { + vec2 frag = gsk_get_frag_coord(); + float alpha = GskTexture(u_source, vUv).a; + + alpha *= (1.0 - clamp(gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outline), frag), 0.0, 1.0)); + + gskSetScaledOutputColor(final_color, alpha); +} diff --git gsk/gl/resources/preamble.fs.glsl gsk/gl/resources/preamble.fs.glsl new file mode 100644 index 0000000000..6f8841e955 --- /dev/null +++ gsk/gl/resources/preamble.fs.glsl @@ -0,0 +1,173 @@ +uniform sampler2D u_source; +uniform mat4 u_projection; +uniform mat4 u_modelview; +uniform float u_alpha; +uniform vec4 u_viewport; +#if defined(GSK_LEGACY) || defined(GSK_GLES) +uniform vec4 u_clip_rect[3]; +#else +uniform vec4[3] u_clip_rect; +#endif + +#if defined(GSK_LEGACY) +_OUT_ vec4 outputColor; +#elif !defined(GSK_GLES) +_OUT_ vec4 outputColor; +#endif + +_IN_ vec2 vUv; + +#if defined(GSK_LEGACY) || defined(GSK_GLES) +GskRoundedRect gsk_decode_rect(vec4 r[3]) +#else +GskRoundedRect gsk_decode_rect(_GSK_ROUNDED_RECT_UNIFORM_ r) +#endif +{ + GskRoundedRect rect; +#if defined(GSK_GLES) || defined(GSK_LEGACY) + rect = GskRoundedRect(r[0], r[1], r[2]); +#else + rect = r; +#endif + gsk_rounded_rect_normalize (rect); + + return rect; +} + +float +gsk_ellipsis_dist (vec2 p, vec2 radius) +{ + if (radius == vec2(0, 0)) + return 0.0; + + vec2 p0 = p / radius; + vec2 p1 = 2.0 * p0 / radius; + + return (dot(p0, p0) - 1.0) / length (p1); +} + +float +gsk_ellipsis_coverage (vec2 point, vec2 center, vec2 radius) +{ + float d = gsk_ellipsis_dist (point - center, radius); + return clamp (0.5 - d, 0.0, 1.0); +} + +float +gsk_rounded_rect_coverage (GskRoundedRect r, vec2 p) +{ + if (p.x < r.bounds.x || p.y < r.bounds.y || + p.x >= r.bounds.z || p.y >= r.bounds.w) + return 0.0; + + vec2 ref_tl = r.corner_points1.xy; + vec2 ref_tr = r.corner_points1.zw; + vec2 ref_br = r.corner_points2.xy; + vec2 ref_bl = r.corner_points2.zw; + + if (p.x >= ref_tl.x && p.x >= ref_bl.x && + p.x <= ref_tr.x && p.x <= ref_br.x) + return 1.0; + + if (p.y >= ref_tl.y && p.y >= ref_tr.y && + p.y <= ref_bl.y && p.y <= ref_br.y) + return 1.0; + + vec2 rad_tl = r.corner_points1.xy - r.bounds.xy; + vec2 rad_tr = r.corner_points1.zw - r.bounds.zy; + vec2 rad_br = r.corner_points2.xy - r.bounds.zw; + vec2 rad_bl = r.corner_points2.zw - r.bounds.xw; + + float d_tl = gsk_ellipsis_coverage(p, ref_tl, rad_tl); + float d_tr = gsk_ellipsis_coverage(p, ref_tr, rad_tr); + float d_br = gsk_ellipsis_coverage(p, ref_br, rad_br); + float d_bl = gsk_ellipsis_coverage(p, ref_bl, rad_bl); + + vec4 corner_coverages = 1.0 - vec4(d_tl, d_tr, d_br, d_bl); + + bvec4 is_out = bvec4(p.x < ref_tl.x && p.y < ref_tl.y, + p.x > ref_tr.x && p.y < ref_tr.y, + p.x > ref_br.x && p.y > ref_br.y, + p.x < ref_bl.x && p.y > ref_bl.y); + + return 1.0 - dot(vec4(is_out), corner_coverages); +} + +float +gsk_rect_coverage (vec4 r, vec2 p) +{ + if (p.x < r.x || p.y < r.y || + p.x >= r.z || p.y >= r.w) + return 0.0; + + return 1.0; +} + +vec4 GskTexture(sampler2D sampler, vec2 texCoords) { +#if defined(GSK_GLES) || defined(GSK_LEGACY) + return texture2D(sampler, texCoords); +#else + return texture(sampler, texCoords); +#endif +} + +#ifdef GSK_GL3 +layout(origin_upper_left) in vec4 gl_FragCoord; +#endif + +vec2 gsk_get_frag_coord() { + vec2 fc = gl_FragCoord.xy; + +#ifdef GSK_GL3 + fc += u_viewport.xy; +#else + fc.x += u_viewport.x; + fc.y = (u_viewport.y + u_viewport.w) - fc.y; +#endif + + return fc; +} + +void gskSetOutputColor(vec4 color) { + vec4 result; + +#if defined(NO_CLIP) + result = color; +#elif defined(RECT_CLIP) + float coverage = gsk_rect_coverage(gsk_get_bounds(u_clip_rect), + gsk_get_frag_coord()); + result = color * coverage; +#else + float coverage = gsk_rounded_rect_coverage(gsk_create_rect(u_clip_rect), + gsk_get_frag_coord()); + result = color * coverage; +#endif + +#if defined(GSK_GLES) || defined(GSK_LEGACY) + gl_FragColor = result; +#else + outputColor = result; +#endif +} + +void gskSetScaledOutputColor(vec4 color, float alpha) { + vec4 result; + +#if defined(NO_CLIP) + result = color * alpha; +#elif defined(RECT_CLIP) + float coverage = gsk_rect_coverage(gsk_get_bounds(u_clip_rect), + gsk_get_frag_coord()); + result = color * (alpha * coverage); +#else + float coverage = gsk_rounded_rect_coverage(gsk_create_rect(u_clip_rect), + gsk_get_frag_coord()); + result = color * (alpha * coverage); +#endif + +#if defined(GSK_GLES) || defined(GSK_LEGACY) + gl_FragColor = result; +#else + outputColor = result; +#endif +} diff --git gsk/gl/resources/preamble.glsl gsk/gl/resources/preamble.glsl new file mode 100644 index 0000000000..3c4fa5af2d --- /dev/null +++ gsk/gl/resources/preamble.glsl @@ -0,0 +1,133 @@ +#if defined(GSK_GLES3) +#extension GL_OES_EGL_image_external_essl3 : require +#elif defined (GSK_GLES) +#extension GL_OES_EGL_image_external : require +#endif + +#ifndef GSK_LEGACY +precision highp float; +#endif + +#if defined(GSK_GLES) || defined(GSK_LEGACY) +#define _OUT_ varying +#define _IN_ varying +#define _GSK_ROUNDED_RECT_UNIFORM_ vec4[3] +/* The array size has to follow the name: the "vec4[3] name" spelling is + * GLSL 1.20 and later only, and we want to compile as GLSL 1.10 too. + */ +#define _GSK_ROUNDED_RECT_OUT_(name) varying vec4 name[3] +#define _GSK_ROUNDED_RECT_IN_(name) varying vec4 name[3] +#else +#define _OUT_ out +#define _IN_ in +#define _GSK_ROUNDED_RECT_UNIFORM_ GskRoundedRect +#define _GSK_ROUNDED_RECT_OUT_(name) out GskRoundedRect name +#define _GSK_ROUNDED_RECT_IN_(name) in GskRoundedRect name +#endif + + +struct GskRoundedRect +{ + vec4 bounds; // Top left and bottom right + // Look, arrays can't be in structs if you want to return the struct + // from a function in gles or whatever. Just kill me. + vec4 corner_points1; // xy = top left, zw = top right + vec4 corner_points2; // xy = bottom right, zw = bottom left +}; + +void gsk_rounded_rect_normalize(inout GskRoundedRect r) +{ + if (r.bounds.x > r.bounds.z) + { + float t = r.bounds.x; + r.bounds.x = r.bounds.z; + r.bounds.z = t; + + vec2 c = r.corner_points1.xy; + r.corner_points1.xy = r.corner_points1.zw; + r.corner_points1.zw = c; + + c = r.corner_points2.xy; + r.corner_points2.xy = r.corner_points2.zw; + r.corner_points2.zw = c; + } + + if (r.bounds.y > r.bounds.w) + { + float t = r.bounds.y; + r.bounds.y = r.bounds.w; + r.bounds.w = t; + + vec2 c = r.corner_points1.xy; + r.corner_points1.xy = r.corner_points2.xy; + r.corner_points2.xy = c; + + c = r.corner_points1.zw; + r.corner_points1.zw = r.corner_points2.zw; + r.corner_points2.zw = c; + } +} + +void gsk_bounds_normalize (inout vec4 bounds) +{ + if (bounds.x > bounds.z) + { + float t = bounds.x; + bounds.x = bounds.z; + bounds.z = t; + } + if (bounds.y > bounds.w) + { + float t = bounds.y; + bounds.y = bounds.w; + bounds.w = t; + } +} + +// Transform from a C GskRoundedRect to what we need. +GskRoundedRect +#if defined(GSK_LEGACY) || defined(GSK_GLES) +gsk_create_rect(vec4 data[3]) +#else +gsk_create_rect(vec4[3] data) +#endif +{ + vec4 bounds = vec4(data[0].xy, data[0].xy + data[0].zw); + + vec4 corner_points1 = vec4(bounds.xy + data[1].xy, + bounds.zy + vec2(data[1].zw * vec2(-1, 1))); + vec4 corner_points2 = vec4(bounds.zw + (data[2].xy * vec2(-1, -1)), + bounds.xw + vec2(data[2].zw * vec2(1, -1))); + + GskRoundedRect rect = GskRoundedRect(bounds, corner_points1, corner_points2); + + gsk_rounded_rect_normalize (rect); + + return rect; +} + +vec4 +#if defined(GSK_LEGACY) || defined(GSK_GLES) +gsk_get_bounds(vec4 data[3]) +#else +gsk_get_bounds(vec4[3] data) +#endif +{ + vec4 bounds = vec4(data[0].xy, data[0].xy + data[0].zw); + + gsk_bounds_normalize (bounds); + + return bounds; +} + +vec4 gsk_premultiply(vec4 c) { + return vec4(c.rgb * c.a, c.a); +} + +vec4 gsk_scaled_premultiply(vec4 c, float s) { + // Fast version of gsk_premultiply(c) * s + // 4 muls instead of 7 + float a = s * c.a; + + return vec4(c.rgb * a, a); +} diff --git gsk/gl/resources/preamble.vs.glsl gsk/gl/resources/preamble.vs.glsl new file mode 100644 index 0000000000..ff3d5bb5a7 --- /dev/null +++ gsk/gl/resources/preamble.vs.glsl @@ -0,0 +1,75 @@ +uniform mat4 u_projection; +uniform mat4 u_modelview; +uniform float u_alpha; + +#if defined(GSK_GLES) || defined(GSK_LEGACY) +attribute vec2 aPosition; +attribute vec2 aUv; +attribute vec4 aColor; +attribute vec4 aColor2; +_OUT_ vec2 vUv; +#else +_IN_ vec2 aPosition; +_IN_ vec2 aUv; +_IN_ vec4 aColor; +_IN_ vec4 aColor2; +_OUT_ vec2 vUv; +#endif + +// amount is: top, right, bottom, left +GskRoundedRect +gsk_rounded_rect_shrink (GskRoundedRect r, vec4 amount) +{ + vec4 new_bounds = r.bounds + vec4(1.0,1.0,-1.0,-1.0) * amount.wxyz; + vec4 new_corner_points1 = r.corner_points1; + vec4 new_corner_points2 = r.corner_points2; + + if (r.corner_points1.xy == r.bounds.xy) new_corner_points1.xy = new_bounds.xy; + if (r.corner_points1.zw == r.bounds.zy) new_corner_points1.zw = new_bounds.zy; + if (r.corner_points2.xy == r.bounds.zw) new_corner_points2.xy = new_bounds.zw; + if (r.corner_points2.zw == r.bounds.xw) new_corner_points2.zw = new_bounds.xw; + new_corner_points1 = clamp (new_corner_points1, new_bounds.xyxy, new_bounds.zwzw); + new_corner_points2 = clamp (new_corner_points2, new_bounds.xyxy, new_bounds.zwzw); + + return GskRoundedRect (new_bounds, new_corner_points1, new_corner_points2); +} + +void +gsk_rounded_rect_offset(inout GskRoundedRect r, vec2 offset) +{ + r.bounds.xy += offset; + r.bounds.zw += offset; + r.corner_points1.xy += offset; + r.corner_points1.zw += offset; + r.corner_points2.xy += offset; + r.corner_points2.zw += offset; +} + +void gsk_rounded_rect_transform(inout GskRoundedRect r, mat4 mat) +{ + r.bounds.xy = (mat * vec4(r.bounds.xy, 0.0, 1.0)).xy; + r.bounds.zw = (mat * vec4(r.bounds.zw, 0.0, 1.0)).xy; + + r.corner_points1.xy = (mat * vec4(r.corner_points1.xy, 0.0, 1.0)).xy; + r.corner_points1.zw = (mat * vec4(r.corner_points1.zw, 0.0, 1.0)).xy; + + r.corner_points2.xy = (mat * vec4(r.corner_points2.xy, 0.0, 1.0)).xy; + r.corner_points2.zw = (mat * vec4(r.corner_points2.zw, 0.0, 1.0)).xy; +} + +#if defined(GSK_LEGACY) +// Can't have out or inout array parameters... +#define gsk_rounded_rect_encode(r, uni) uni[0] = r.bounds; uni[1] = r.corner_points1; uni[2] = r.corner_points2; +#else +void gsk_rounded_rect_encode(GskRoundedRect r, out _GSK_ROUNDED_RECT_UNIFORM_ out_r) +{ +#if defined(GSK_GLES) + out_r[0] = r.bounds; + out_r[1] = r.corner_points1; + out_r[2] = r.corner_points2; +#else + out_r = r; +#endif +} + +#endif diff --git gsk/gl/resources/premultiply.glsl gsk/gl/resources/premultiply.glsl new file mode 100644 index 0000000000..892c2c4453 --- /dev/null +++ gsk/gl/resources/premultiply.glsl @@ -0,0 +1,19 @@ +// 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 color = GskTexture(u_source, vUv); + + color.rgb *= color.a; + + gskSetOutputColor(color); +} diff --git gsk/gl/resources/radial_gradient.glsl gsk/gl/resources/radial_gradient.glsl new file mode 100644 index 0000000000..df95516237 --- /dev/null +++ gsk/gl/resources/radial_gradient.glsl @@ -0,0 +1,87 @@ +// VERTEX_SHADER +// radial_gradient.glsl + +uniform vec4 u_geometry; + +_OUT_ vec2 coord; + +void main() { + gl_Position = u_projection * (u_modelview * vec4(aPosition, 0.0, 1.0)); + + vec2 mv0 = u_modelview[0].xy; + vec2 mv1 = u_modelview[1].xy; + vec2 offset = aPosition - u_geometry.xy; + vec2 dir = vec2(dot(mv0, offset), + dot(mv1, offset)); + + coord = dir * u_geometry.zw; +} + +// FRAGMENT_SHADER: +// radial_gradient.glsl + +#define MAX_COLOR_STOPS 6 + +#ifdef GSK_LEGACY +uniform int u_num_color_stops; +#else +uniform highp int u_num_color_stops; +#endif + +uniform bool u_repeat; +uniform vec2 u_range; +uniform float u_color_stops[MAX_COLOR_STOPS * 5]; + +_IN_ vec2 coord; + +float get_offset(int index) { + // u_color_stops[5 * index] makes Intel Windows driver crash. + // See https://gitlab.gnome.org/GNOME/gtk/-/issues/3783 + int base = 5 * index; + return u_color_stops[base]; +} + +vec4 get_color(int index) { + int base = 5 * index + 1; + + return vec4(u_color_stops[base], + u_color_stops[base + 1], + u_color_stops[base + 2], + u_color_stops[base + 3]); +} + +void main() { + // Reverse scale + float offset = length(coord) * u_range.x + u_range.y; + float curr_offset; + float next_offset; + + if (u_repeat) { + offset = fract(offset); + } + + next_offset = get_offset(0); + if (offset < next_offset) { + gskSetOutputColor(gsk_scaled_premultiply(get_color(0), u_alpha)); + return; + } + + if (offset >= get_offset(u_num_color_stops - 1)) { + gskSetOutputColor(gsk_scaled_premultiply(get_color(u_num_color_stops - 1), u_alpha)); + return; + } + + for (int i = 0; i < MAX_COLOR_STOPS; i++) { + curr_offset = next_offset; + next_offset = get_offset(i + 1); + + if (offset < next_offset) { + float f = (offset - curr_offset) / (next_offset - curr_offset); + vec4 curr_color = gsk_scaled_premultiply (get_color(i), u_alpha); + vec4 next_color = gsk_scaled_premultiply (get_color(i + 1), u_alpha); + vec4 color = mix(curr_color, next_color, f); + gskSetOutputColor(color); + return; + } + } +} diff --git gsk/gl/resources/repeat.glsl gsk/gl/resources/repeat.glsl new file mode 100644 index 0000000000..1b811df2da --- /dev/null +++ gsk/gl/resources/repeat.glsl @@ -0,0 +1,44 @@ +// VERTEX_SHADER: +// repeat.glsl + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + vUv = vec2(aUv.x, aUv.y); +} + +// FRAGMENT_SHADER: +// repeat.glsl + +uniform vec4 u_child_bounds; +uniform vec4 u_texture_rect; + +float wrap(float f, float wrap_for) { + return mod(f, wrap_for); +} + +/* We get the texture coordinates via vUv, + * but that might be on a texture atlas, so we need to do the + * wrapping ourselves. + */ +void main() { + + /* We map the texture coordinate to [1;0], then wrap it and scale the result again */ + + float tw = u_texture_rect.z - u_texture_rect.x; + float th = u_texture_rect.w - u_texture_rect.y; + + float mapped_x = (vUv.x - u_texture_rect.x) / tw; + float mapped_y = (vUv.y - u_texture_rect.y) / th; + + float wrapped_x = wrap(u_child_bounds.x + mapped_x * u_child_bounds.z, 1.0); + float wrapped_y = wrap(u_child_bounds.y + mapped_y * u_child_bounds.w, 1.0); + + vec2 tp; + tp.x = u_texture_rect.x + (wrapped_x * tw); + tp.y = u_texture_rect.y + (wrapped_y * th); + + vec4 diffuse = GskTexture(u_source, tp); + + gskSetScaledOutputColor(diffuse, u_alpha); +} diff --git gsk/gl/resources/unblurred_outset_shadow.glsl gsk/gl/resources/unblurred_outset_shadow.glsl new file mode 100644 index 0000000000..6f17370a1d --- /dev/null +++ gsk/gl/resources/unblurred_outset_shadow.glsl @@ -0,0 +1,47 @@ +// VERTEX_SHADER: +// unblurred_outset_shadow.glsl + +uniform float u_spread; +uniform vec2 u_offset; +#if defined(GSK_LEGACY) || defined(GSK_GLES) +uniform vec4 u_outline_rect[3]; +#else +uniform vec4[3] u_outline_rect; +#endif + +_OUT_ vec4 final_color; +_GSK_ROUNDED_RECT_OUT_(transformed_outside_outline); +_GSK_ROUNDED_RECT_OUT_(transformed_inside_outline); + +void main() { + gl_Position = u_projection * u_modelview * vec4(aPosition, 0.0, 1.0); + + final_color = gsk_premultiply(aColor) * u_alpha; + + GskRoundedRect inside = gsk_create_rect(u_outline_rect); + GskRoundedRect outside = gsk_rounded_rect_shrink(inside, vec4(- u_spread)); + + gsk_rounded_rect_offset(outside, u_offset); + + gsk_rounded_rect_transform(outside, u_modelview); + gsk_rounded_rect_transform(inside, u_modelview); + + gsk_rounded_rect_encode(outside, transformed_outside_outline); + gsk_rounded_rect_encode(inside, transformed_inside_outline); +} + +// FRAGMENT_SHADER: +// unblurred_outset_shadow.glsl +_IN_ vec4 final_color; +_GSK_ROUNDED_RECT_IN_(transformed_outside_outline); +_GSK_ROUNDED_RECT_IN_(transformed_inside_outline); + +void main() { + vec2 frag = gsk_get_frag_coord(); + + float alpha = clamp(gsk_rounded_rect_coverage(gsk_decode_rect(transformed_outside_outline), frag) - + gsk_rounded_rect_coverage(gsk_decode_rect(transformed_inside_outline), frag), + 0.0, 1.0); + + gskSetScaledOutputColor(final_color, alpha); +} diff --git gsk/gl/stb_rect_pack.c gsk/gl/stb_rect_pack.c new file mode 100644 index 0000000000..2ef31512b4 --- /dev/null +++ gsk/gl/stb_rect_pack.c @@ -0,0 +1,427 @@ + +#include "stb_rect_pack.h" +#define STB_RECT_PACK_IMPLEMENTATION +////////////////////////////////////////////////////////////////////////////// +// +// IMPLEMENTATION SECTION +// + +#ifdef STB_RECT_PACK_IMPLEMENTATION +#ifndef STBRP_SORT +#include +#define STBRP_SORT qsort +#endif + +#ifndef STBRP_ASSERT +#include +#define STBRP_ASSERT assert +#endif + +#ifdef _MSC_VER +#define STBRP__NOTUSED(v) (void)(v) +#define STBRP__CDECL __cdecl +#else +#define STBRP__NOTUSED(v) (void)sizeof(v) +#define STBRP__CDECL +#endif + +enum +{ + STBRP__INIT_skyline = 1 +}; + +STBRP_DEF void stbrp_setup_heuristic(stbrp_context *context, int heuristic) +{ + switch (context->init_mode) { + case STBRP__INIT_skyline: + STBRP_ASSERT(heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight || heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight); + context->heuristic = heuristic; + break; + default: + STBRP_ASSERT(0); + } +} + +STBRP_DEF void stbrp_setup_allow_out_of_mem(stbrp_context *context, int allow_out_of_mem) +{ + if (allow_out_of_mem) + // if it's ok to run out of memory, then don't bother aligning them; + // this gives better packing, but may fail due to OOM (even though + // the rectangles easily fit). @TODO a smarter approach would be to only + // quantize once we've hit OOM, then we could get rid of this parameter. + context->align = 1; + else { + // if it's not ok to run out of memory, then quantize the widths + // so that num_nodes is always enough nodes. + // + // I.e. num_nodes * align >= width + // align >= width / num_nodes + // align = ceil(width/num_nodes) + + context->align = (context->width + context->num_nodes-1) / context->num_nodes; + } +} + +STBRP_DEF void stbrp_init_target(stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes) +{ + int i; + + for (i=0; i < num_nodes-1; ++i) + nodes[i].next = &nodes[i+1]; + nodes[i].next = NULL; + context->init_mode = STBRP__INIT_skyline; + context->heuristic = STBRP_HEURISTIC_Skyline_default; + context->free_head = &nodes[0]; + context->active_head = &context->extra[0]; + context->width = width; + context->height = height; + context->num_nodes = num_nodes; + stbrp_setup_allow_out_of_mem(context, 0); + + // node 0 is the full width, node 1 is the sentinel (lets us not store width explicitly) + context->extra[0].x = 0; + context->extra[0].y = 0; + context->extra[0].next = &context->extra[1]; + context->extra[1].x = (stbrp_coord) width; + context->extra[1].y = (1<<30); + context->extra[1].next = NULL; +} + +// find minimum y position if it starts at x1 +static int stbrp__skyline_find_min_y(stbrp_context *c, stbrp_node *first, int x0, int width, int *pwaste) +{ + stbrp_node *node = first; + int x1 = x0 + width; + int min_y, visited_width, waste_area; + + STBRP__NOTUSED(c); + + STBRP_ASSERT(first->x <= x0); + + #if 0 + // skip in case we're past the node + while (node->next->x <= x0) + ++node; + #else + STBRP_ASSERT(node->next->x > x0); // we ended up handling this in the caller for efficiency + #endif + + STBRP_ASSERT(node->x <= x0); + + min_y = 0; + waste_area = 0; + visited_width = 0; + while (node->x < x1) { + if (node->y > min_y) { + // raise min_y higher. + // we've accounted for all waste up to min_y, + // but we'll now add more waste for everything we've visited + waste_area += visited_width * (node->y - min_y); + min_y = node->y; + // the first time through, visited_width might be reduced + if (node->x < x0) + visited_width += node->next->x - x0; + else + visited_width += node->next->x - node->x; + } else { + // add waste area + int under_width = node->next->x - node->x; + if (under_width + visited_width > width) + under_width = width - visited_width; + waste_area += under_width * (min_y - node->y); + visited_width += under_width; + } + node = node->next; + } + + *pwaste = waste_area; + return min_y; +} + +typedef struct +{ + int x,y; + stbrp_node **prev_link; +} stbrp__findresult; + +static stbrp__findresult stbrp__skyline_find_best_pos(stbrp_context *c, int width, int height) +{ + int best_waste = (1<<30), best_x, best_y = (1 << 30); + stbrp__findresult fr; + stbrp_node **prev, *node, *tail, **best = NULL; + + // align to multiple of c->align + width = (width + c->align - 1); + width -= width % c->align; + STBRP_ASSERT(width % c->align == 0); + + // if it can't possibly fit, bail immediately + if (width > c->width || height > c->height) { + fr.prev_link = NULL; + fr.x = fr.y = 0; + return fr; + } + + node = c->active_head; + prev = &c->active_head; + while (node->x + width <= c->width) { + int y,waste; + y = stbrp__skyline_find_min_y(c, node, node->x, width, &waste); + if (c->heuristic == STBRP_HEURISTIC_Skyline_BL_sortHeight) { // actually just want to test BL + // bottom left + if (y < best_y) { + best_y = y; + best = prev; + } + } else { + // best-fit + if (y + height <= c->height) { + // can only use it if it first vertically + if (y < best_y || (y == best_y && waste < best_waste)) { + best_y = y; + best_waste = waste; + best = prev; + } + } + } + prev = &node->next; + node = node->next; + } + + best_x = (best == NULL) ? 0 : (*best)->x; + + // if doing best-fit (BF), we also have to try aligning right edge to each node position + // + // e.g, if fitting + // + // ____________________ + // |____________________| + // + // into + // + // | | + // | ____________| + // |____________| + // + // then right-aligned reduces waste, but bottom-left BL is always chooses left-aligned + // + // This makes BF take about 2x the time + + if (c->heuristic == STBRP_HEURISTIC_Skyline_BF_sortHeight) { + tail = c->active_head; + node = c->active_head; + prev = &c->active_head; + // find first node that's admissible + while (tail->x < width) + tail = tail->next; + while (tail) { + int xpos = tail->x - width; + int y,waste; + STBRP_ASSERT(xpos >= 0); + // find the left position that matches this + while (node->next->x <= xpos) { + prev = &node->next; + node = node->next; + } + STBRP_ASSERT(node->next->x > xpos && node->x <= xpos); + y = stbrp__skyline_find_min_y(c, node, xpos, width, &waste); + if (y + height <= c->height) { + if (y <= best_y) { + if (y < best_y || waste < best_waste || (waste==best_waste && xpos < best_x)) { + best_x = xpos; + STBRP_ASSERT(y <= best_y); + best_y = y; + best_waste = waste; + best = prev; + } + } + } + tail = tail->next; + } + } + + fr.prev_link = best; + fr.x = best_x; + fr.y = best_y; + return fr; +} + +static stbrp__findresult stbrp__skyline_pack_rectangle(stbrp_context *context, int width, int height) +{ + // find best position according to heuristic + stbrp__findresult res = stbrp__skyline_find_best_pos(context, width, height); + stbrp_node *node, *cur; + + // bail if: + // 1. it failed + // 2. the best node doesn't fit (we don't always check this) + // 3. we're out of memory + if (res.prev_link == NULL || res.y + height > context->height || context->free_head == NULL) { + res.prev_link = NULL; + return res; + } + + // on success, create new node + node = context->free_head; + node->x = (stbrp_coord) res.x; + node->y = (stbrp_coord) (res.y + height); + + context->free_head = node->next; + + // insert the new node into the right starting point, and + // let 'cur' point to the remaining nodes needing to be + // stiched back in + + cur = *res.prev_link; + if (cur->x < res.x) { + // preserve the existing one, so start testing with the next one + stbrp_node *next = cur->next; + cur->next = node; + cur = next; + } else { + *res.prev_link = node; + } + + // from here, traverse cur and free the nodes, until we get to one + // that shouldn't be freed + while (cur->next && cur->next->x <= res.x + width) { + stbrp_node *next = cur->next; + // move the current node to the free list + cur->next = context->free_head; + context->free_head = cur; + cur = next; + } + + // stitch the list back in + node->next = cur; + + if (cur->x < res.x + width) + cur->x = (stbrp_coord) (res.x + width); + +#ifdef _DEBUG + cur = context->active_head; + while (cur->x < context->width) { + STBRP_ASSERT(cur->x < cur->next->x); + cur = cur->next; + } + STBRP_ASSERT(cur->next == NULL); + + { + int count=0; + cur = context->active_head; + while (cur) { + cur = cur->next; + ++count; + } + cur = context->free_head; + while (cur) { + cur = cur->next; + ++count; + } + STBRP_ASSERT(count == context->num_nodes+2); + } +#endif + + return res; +} + +static int STBRP__CDECL rect_height_compare(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + if (p->h > q->h) + return -1; + if (p->h < q->h) + return 1; + return (p->w > q->w) ? -1 : (p->w < q->w); +} + +static int STBRP__CDECL rect_original_order(const void *a, const void *b) +{ + const stbrp_rect *p = (const stbrp_rect *) a; + const stbrp_rect *q = (const stbrp_rect *) b; + return (p->was_packed < q->was_packed) ? -1 : (p->was_packed > q->was_packed); +} + +STBRP_DEF int stbrp_pack_rects(stbrp_context *context, stbrp_rect *rects, int num_rects) +{ + int i, all_rects_packed = 1; + + // we use the 'was_packed' field internally to allow sorting/unsorting + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = i; + } + + // sort according to heuristic + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_height_compare); + + for (i=0; i < num_rects; ++i) { + if (rects[i].w == 0 || rects[i].h == 0) { + rects[i].x = rects[i].y = 0; // empty rect needs no space + } else { + stbrp__findresult fr = stbrp__skyline_pack_rectangle(context, rects[i].w, rects[i].h); + if (fr.prev_link) { + rects[i].x = (stbrp_coord) fr.x; + rects[i].y = (stbrp_coord) fr.y; + } else { + rects[i].x = rects[i].y = STBRP__MAXVAL; + } + } + } + + // unsort + STBRP_SORT(rects, num_rects, sizeof(rects[0]), rect_original_order); + + // set was_packed flags and all_rects_packed status + for (i=0; i < num_rects; ++i) { + rects[i].was_packed = !(rects[i].x == STBRP__MAXVAL && rects[i].y == STBRP__MAXVAL); + if (!rects[i].was_packed) + all_rects_packed = 0; + } + + // return the all_rects_packed status + return all_rects_packed; +} +#endif + +/* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN +ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +------------------------------------------------------------------------------ +*/ diff --git gsk/gl/stb_rect_pack.h gsk/gl/stb_rect_pack.h new file mode 100644 index 0000000000..2a3833ec80 --- /dev/null +++ gsk/gl/stb_rect_pack.h @@ -0,0 +1,197 @@ +// stb_rect_pack.h - v1.01 - public domain - rectangle packing +// Sean Barrett 2014 +// +// Useful for e.g. packing rectangular textures into an atlas. +// Does not do rotation. +// +// Before #including, +// +// #define STB_RECT_PACK_IMPLEMENTATION +// +// in the file that you want to have the implementation. +// +// Not necessarily the awesomest packing method, but better than +// the totally naive one in stb_truetype (which is primarily what +// this is meant to replace). +// +// Has only had a few tests run, may have issues. +// +// More docs to come. +// +// No memory allocations; uses qsort() and assert() from stdlib. +// Can override those by defining STBRP_SORT and STBRP_ASSERT. +// +// This library currently uses the Skyline Bottom-Left algorithm. +// +// Please note: better rectangle packers are welcome! Please +// implement them to the same API, but with a different init +// function. +// +// Credits +// +// Library +// Sean Barrett +// Minor features +// Martins Mozeiko +// github:IntellectualKitty +// +// Bugfixes / warning fixes +// Jeremy Jaussaud +// Fabian Giesen +// +// Version history: +// +// 1.01 (2021-07-11) always use large rect mode, expose STBRP__MAXVAL in public section +// 1.00 (2019-02-25) avoid small space waste; gracefully fail too-wide rectangles +// 0.99 (2019-02-07) warning fixes +// 0.11 (2017-03-03) return packing success/fail result +// 0.10 (2016-10-25) remove cast-away-const to avoid warnings +// 0.09 (2016-08-27) fix compiler warnings +// 0.08 (2015-09-13) really fix bug with empty rects (w=0 or h=0) +// 0.07 (2015-09-13) fix bug with empty rects (w=0 or h=0) +// 0.06 (2015-04-15) added STBRP_SORT to allow replacing qsort +// 0.05: added STBRP_ASSERT to allow replacing assert +// 0.04: fixed minor bug in STBRP_LARGE_RECTS support +// 0.01: initial release +// +// LICENSE +// +// See end of file for license information. + +////////////////////////////////////////////////////////////////////////////// +// +// INCLUDE SECTION +// + +#pragma once + +#define STB_RECT_PACK_VERSION 1 + +#ifdef STBRP_STATIC +#define STBRP_DEF static +#else +#define STBRP_DEF extern +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct stbrp_context stbrp_context; +typedef struct stbrp_node stbrp_node; +typedef struct stbrp_rect stbrp_rect; + +typedef int stbrp_coord; + +#define STBRP__MAXVAL 0x7fffffff +// Mostly for internal use, but this is the maximum supported coordinate value. + +STBRP_DEF int stbrp_pack_rects (stbrp_context *context, stbrp_rect *rects, int num_rects); +// Assign packed locations to rectangles. The rectangles are of type +// 'stbrp_rect' defined below, stored in the array 'rects', and there +// are 'num_rects' many of them. +// +// Rectangles which are successfully packed have the 'was_packed' flag +// set to a non-zero value and 'x' and 'y' store the minimum location +// on each axis (i.e. bottom-left in cartesian coordinates, top-left +// if you imagine y increasing downwards). Rectangles which do not fit +// have the 'was_packed' flag set to 0. +// +// You should not try to access the 'rects' array from another thread +// while this function is running, as the function temporarily reorders +// the array while it executes. +// +// To pack into another rectangle, you need to call stbrp_init_target +// again. To continue packing into the same rectangle, you can call +// this function again. Calling this multiple times with multiple rect +// arrays will probably produce worse packing results than calling it +// a single time with the full rectangle array, but the option is +// available. +// +// The function returns 1 if all of the rectangles were successfully +// packed and 0 otherwise. + +struct stbrp_rect +{ + // reserved for your use: + int id; + + // input: + stbrp_coord w, h; + + // output: + stbrp_coord x, y; + int was_packed; // non-zero if valid packing + +}; // 16 bytes, nominally + + +STBRP_DEF void stbrp_init_target (stbrp_context *context, int width, int height, stbrp_node *nodes, int num_nodes); +// Initialize a rectangle packer to: +// pack a rectangle that is 'width' by 'height' in dimensions +// using temporary storage provided by the array 'nodes', which is 'num_nodes' long +// +// You must call this function every time you start packing into a new target. +// +// There is no "shutdown" function. The 'nodes' memory must stay valid for +// the following stbrp_pack_rects() call (or calls), but can be freed after +// the call (or calls) finish. +// +// Note: to guarantee best results, either: +// 1. make sure 'num_nodes' >= 'width' +// or 2. call stbrp_allow_out_of_mem() defined below with 'allow_out_of_mem = 1' +// +// If you don't do either of the above things, widths will be quantized to multiples +// of small integers to guarantee the algorithm doesn't run out of temporary storage. +// +// If you do #2, then the non-quantized algorithm will be used, but the algorithm +// may run out of temporary storage and be unable to pack some rectangles. + +STBRP_DEF void stbrp_setup_allow_out_of_mem (stbrp_context *context, int allow_out_of_mem); +// Optionally call this function after init but before doing any packing to +// change the handling of the out-of-temp-memory scenario, described above. +// If you call init again, this will be reset to the default (false). + + +STBRP_DEF void stbrp_setup_heuristic (stbrp_context *context, int heuristic); +// Optionally select which packing heuristic the library should use. Different +// heuristics will produce better/worse results for different data sets. +// If you call init again, this will be reset to the default. + +enum +{ + STBRP_HEURISTIC_Skyline_default=0, + STBRP_HEURISTIC_Skyline_BL_sortHeight = STBRP_HEURISTIC_Skyline_default, + STBRP_HEURISTIC_Skyline_BF_sortHeight +}; + + +////////////////////////////////////////////////////////////////////////////// +// +// the details of the following structures don't matter to you, but they must +// be visible so you can handle the memory allocations for them + +struct stbrp_node +{ + stbrp_coord x,y; + stbrp_node *next; +}; + +struct stbrp_context +{ + int width; + int height; + int align; + int init_mode; + int heuristic; + int num_nodes; + stbrp_node *active_head; + stbrp_node *free_head; + stbrp_node extra[2]; // we allocate two extra nodes so optimal user-node-count is 'width' not 'width+2' +}; + +#ifdef __cplusplus +} +#endif + + diff --git gsk/gpu/gskglframe.c gsk/gpu/gskglframe.c index 7ecad85d9c..50a1ea36ea 100644 --- gsk/gpu/gskglframe.c +++ gsk/gpu/gskglframe.c @@ -322,7 +322,8 @@ gsk_gl_frame_submit (GskGpuFrame *frame, op = gsk_gpu_op_gl_command (op, frame, &state); } - self->sync = glFenceSync (GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + if (gdk_gl_context_has_feature (GDK_GL_CONTEXT(gsk_gpu_frame_get_context (frame)), GDK_GL_FEATURE_SYNC)) + self->sync = glFenceSync (GL_SYNC_GPU_COMMANDS_COMPLETE, 0); } static void diff --git gsk/gpu/gskgpudownloadop.c gsk/gpu/gskgpudownloadop.c index 836f63402d..f60773ec97 100644 --- gsk/gpu/gskgpudownloadop.c +++ gsk/gpu/gskgpudownloadop.c @@ -358,7 +358,10 @@ gsk_gpu_download_op_gl_command (GskGpuOp *op, * the texture might survive the frame and its surface */ data->context = g_object_ref (gdk_display_get_gl_context (gsk_gpu_device_get_display (gsk_gpu_frame_get_device (frame)))); data->texture_id = texture_id; - data->sync = glFenceSync (GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + if (gdk_gl_context_has_feature (data->context, GDK_GL_FEATURE_SYNC)) + data->sync = glFenceSync (GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + else + data->sync = NULL; builder = gdk_gl_texture_builder_new (); gdk_gl_texture_builder_set_context (builder, data->context); diff --git gsk/gsk.h gsk/gsk.h index da1b3311f3..426639465a 100644 --- gsk/gsk.h +++ gsk/gsk.h @@ -72,6 +72,7 @@ #include #include #include +#include #include #include diff --git gsk/gskcairorenderer.c gsk/gskcairorenderer.c index e0f27abf68..aab13a132d 100644 --- gsk/gskcairorenderer.c +++ gsk/gskcairorenderer.c @@ -85,6 +85,27 @@ gsk_cairo_renderer_unrealize (GskRenderer *renderer) } } +static void +gsk_cairo_renderer_do_render (GskRenderer *renderer, + cairo_t *cr, + GdkColorState *ccs, + GskRenderNode *root) +{ + GskCairoRenderer *self = GSK_CAIRO_RENDERER (renderer); + GskProfiler *profiler; + gint64 cpu_time; + + profiler = gsk_renderer_get_profiler (renderer); + gsk_profiler_timer_begin (profiler, self->profile_timers.cpu_time); + + gsk_render_node_draw_with_color_state (root, cr, ccs); + + cpu_time = gsk_profiler_timer_end (profiler, self->profile_timers.cpu_time); + gsk_profiler_timer_set (profiler, self->profile_timers.cpu_time, cpu_time); + + gsk_profiler_push_samples (profiler); +} + static GdkTexture * gsk_cairo_renderer_render_texture (GskRenderer *renderer, GskRenderNode *root, @@ -135,7 +156,7 @@ gsk_cairo_renderer_render_texture (GskRenderer *renderer, cairo_translate (cr, - viewport->origin.x, - viewport->origin.y); - gsk_render_node_draw_with_color_state (root, cr, GDK_COLOR_STATE_SRGB); + gsk_cairo_renderer_do_render (renderer, cr, GDK_COLOR_STATE_SRGB, root); cairo_destroy (cr); @@ -177,7 +198,10 @@ G_GNUC_END_IGNORE_DEPRECATIONS cairo_restore (cr); } - gsk_render_node_draw_with_color_state (root, cr, gdk_draw_context_get_color_state (GDK_DRAW_CONTEXT (self->cairo_context))); + gsk_cairo_renderer_do_render (renderer, + cr, + gdk_draw_context_get_color_state (GDK_DRAW_CONTEXT (self->cairo_context)), + root); cairo_destroy (cr); @@ -198,6 +222,9 @@ gsk_cairo_renderer_class_init (GskCairoRendererClass *klass) static void gsk_cairo_renderer_init (GskCairoRenderer *self) { + GskProfiler *profiler = gsk_renderer_get_profiler (GSK_RENDERER (self)); + + self->profile_timers.cpu_time = gsk_profiler_add_timer (profiler, "cpu-time", "CPU time", FALSE, TRUE); } /** diff --git gsk/gskprofiler.c gsk/gskprofiler.c new file mode 100644 index 0000000000..5344b01ffb --- /dev/null +++ gsk/gskprofiler.c @@ -0,0 +1,525 @@ +#include "config.h" + +#include "gskprofilerprivate.h" + +#define MAX_SAMPLES 32 + +typedef struct { + GQuark id; + char *description; + gint64 value; + gint64 n_samples; + unsigned int can_reset : 1; +} NamedCounter; + +typedef struct { + GQuark id; + char *description; + gint64 value; + gint64 start_time; + gint64 min_value; + gint64 max_value; + gint64 avg_value; + gint64 n_samples; + unsigned int in_flight : 1; + unsigned int can_reset : 1; + unsigned int invert : 1; +} NamedTimer; + +typedef struct { + GQuark id; + gint64 value; +} Sample; + +struct _GskProfiler +{ + GObject parent_instance; + + GHashTable *counters; + GHashTable *timers; + + Sample timer_samples[MAX_SAMPLES]; + guint last_sample; +}; + +G_DEFINE_TYPE (GskProfiler, gsk_profiler, G_TYPE_OBJECT) + +static void +named_counter_free (gpointer data) +{ + NamedCounter *counter = data; + + if (data == NULL) + return; + + g_free (counter->description); + g_free (counter); +} + +static void +named_timer_free (gpointer data) +{ + NamedTimer *timer = data; + + if (data == NULL) + return; + + g_free (timer->description); + g_free (timer); +} + +static void +gsk_profiler_finalize (GObject *gobject) +{ + GskProfiler *self = GSK_PROFILER (gobject); + + g_clear_pointer (&self->counters, g_hash_table_unref); + g_clear_pointer (&self->timers, g_hash_table_unref); + + G_OBJECT_CLASS (gsk_profiler_parent_class)->finalize (gobject); +} + +static void +gsk_profiler_class_init (GskProfilerClass *klass) +{ + G_OBJECT_CLASS (klass)->finalize = gsk_profiler_finalize; +} + +static void +gsk_profiler_init (GskProfiler *self) +{ + self->counters = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, + named_counter_free); + self->timers = g_hash_table_new_full (g_direct_hash, g_direct_equal, + NULL, + named_timer_free); +} + +GskProfiler * +gsk_profiler_new (void) +{ + return g_object_new (GSK_TYPE_PROFILER, NULL); +} + +static NamedCounter * +named_counter_new (GQuark id, + const char *description, + gboolean can_reset) +{ + NamedCounter *res = g_new0 (NamedCounter, 1); + + res->id = id; + res->description = g_strdup (description); + res->can_reset = can_reset; + + return res; +} + +static NamedCounter * +gsk_profiler_get_counter (GskProfiler *profiler, + GQuark id) +{ + return g_hash_table_lookup (profiler->counters, GINT_TO_POINTER (id)); +} + +GQuark +gsk_profiler_add_counter (GskProfiler *profiler, + const char *counter_name, + const char *description, + gboolean can_reset) +{ + NamedCounter *counter; + GQuark id; + + g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); + + id = g_quark_from_string (counter_name); + counter = gsk_profiler_get_counter (profiler, id); + if (counter != NULL) + { + g_critical ("Cannot add a counter '%s' as one already exists.", counter_name); + return counter->id; + } + + counter = named_counter_new (id, description, can_reset); + g_hash_table_insert (profiler->counters, GINT_TO_POINTER (id), counter); + + return counter->id; +} + +static NamedTimer * +named_timer_new (GQuark id, + const char *description, + gboolean invert, + gboolean can_reset) +{ + NamedTimer *res = g_new0 (NamedTimer, 1); + + res->id = id; + res->description = g_strdup (description); + res->invert = invert; + res->can_reset = can_reset; + + return res; +} + +static NamedTimer * +gsk_profiler_get_timer (GskProfiler *profiler, + GQuark id) +{ + return g_hash_table_lookup (profiler->timers, GINT_TO_POINTER (id)); +} + +GQuark +gsk_profiler_add_timer (GskProfiler *profiler, + const char *timer_name, + const char *description, + gboolean invert, + gboolean can_reset) +{ + NamedTimer *timer; + GQuark id; + + g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); + + id = g_quark_from_string (timer_name); + timer = gsk_profiler_get_timer (profiler, id); + if (timer != NULL) + { + g_critical ("Cannot add a timer '%s' as one already exists.", timer_name); + return timer->id; + } + + timer = named_timer_new (id, description, invert, can_reset); + g_hash_table_insert (profiler->timers, GINT_TO_POINTER (id), timer); + + return timer->id; +} + +void +gsk_profiler_counter_inc (GskProfiler *profiler, + GQuark counter_id) +{ + gsk_profiler_counter_add (profiler, counter_id, 1); +} + +void +gsk_profiler_counter_set (GskProfiler *profiler, + GQuark counter_id, + gint64 value) +{ + NamedCounter *counter; + + g_return_if_fail (GSK_IS_PROFILER (profiler)); + + counter = gsk_profiler_get_counter (profiler, counter_id); + if (counter == NULL) + { + g_critical ("No counter '%s' (id:%d) found; did you forget to call gsk_profiler_add_counter()?", + g_quark_to_string (counter_id), counter_id); + return; + } + + counter->value = value; +} + +void +gsk_profiler_counter_add (GskProfiler *profiler, + GQuark counter_id, + gint64 increment) +{ + NamedCounter *counter; + + g_return_if_fail (GSK_IS_PROFILER (profiler)); + + counter = gsk_profiler_get_counter (profiler, counter_id); + if (counter == NULL) + { + g_critical ("No counter '%s' (id:%d) found; did you forget to call gsk_profiler_add_counter()?", + g_quark_to_string (counter_id), counter_id); + return; + } + + counter->value += increment; +} + +void +gsk_profiler_timer_begin (GskProfiler *profiler, + GQuark timer_id) +{ + NamedTimer *timer; + + g_return_if_fail (GSK_IS_PROFILER (profiler)); + + timer = gsk_profiler_get_timer (profiler, timer_id); + if (timer == NULL) + return; + + if (timer->in_flight) + return; + + timer->in_flight = TRUE; + timer->start_time = g_get_monotonic_time (); +} + +gint64 +gsk_profiler_timer_end (GskProfiler *profiler, + GQuark timer_id) +{ + NamedTimer *timer; + gint64 diff; + + g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); + + timer = gsk_profiler_get_timer (profiler, timer_id); + if (timer == NULL) + { + g_critical ("No timer '%s' (id:%d) found; did you forget to call gsk_profiler_add_timer()?", + g_quark_to_string (timer_id), timer_id); + return 0; + } + + if (!timer->in_flight) + { + g_critical ("Timer '%s' (id:%d) is not running; did you forget to call gsk_profiler_timer_begin()?", + g_quark_to_string (timer->id), timer->id); + return 0; + } + + diff = g_get_monotonic_time () - timer->start_time; + + timer->in_flight = FALSE; + timer->value += diff; + + return diff; +} + +void +gsk_profiler_timer_set (GskProfiler *profiler, + GQuark timer_id, + gint64 value) +{ + NamedTimer *timer; + + g_return_if_fail (GSK_IS_PROFILER (profiler)); + + timer = gsk_profiler_get_timer (profiler, timer_id); + if (timer == NULL) + { + g_critical ("No timer '%s' (id:%d) found; did you forget to call gsk_profiler_add_timer()?", + g_quark_to_string (timer_id), timer_id); + return; + } + + if (timer->in_flight) + { + g_critical ("Timer '%s' (id:%d) is running; are you sure you don't want to call " + "gsk_profiler_timer_end() instead of gsk_profiler_timer_set()?", + g_quark_to_string (timer_id), timer_id); + } + + timer->value = value; +} + +gint64 +gsk_profiler_counter_get (GskProfiler *profiler, + GQuark counter_id) +{ + NamedCounter *counter; + + g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); + + counter = gsk_profiler_get_counter (profiler, counter_id); + if (counter == NULL) + { + g_critical ("No counter '%s' (id:%d) found; did you forget to call gsk_profiler_add_counter()?", + g_quark_to_string (counter_id), counter_id); + return 0; + } + + return counter->value; +} + +gint64 +gsk_profiler_timer_get (GskProfiler *profiler, + GQuark timer_id) +{ + NamedTimer *timer; + + g_return_val_if_fail (GSK_IS_PROFILER (profiler), 0); + + timer = gsk_profiler_get_timer (profiler, timer_id); + if (timer == NULL) + { + g_critical ("No timer '%s' (id:%d) found; did you forget to call gsk_profiler_add_timer()?", + g_quark_to_string (timer_id), timer_id); + return 0; + } + + if (timer->invert) + return (gint64) (1000000.0 / (double) timer->value); + + return timer->value; +} + +gint64 +gsk_profiler_timer_get_start (GskProfiler *profiler, + GQuark timer_id) +{ + NamedTimer *timer; + + timer = gsk_profiler_get_timer (profiler, timer_id); + if (timer == NULL) + return 0; + + return timer->start_time; +} + +void +gsk_profiler_reset (GskProfiler *profiler) +{ + GHashTableIter iter; + gpointer value_p = NULL; + + g_return_if_fail (GSK_IS_PROFILER (profiler)); + + g_hash_table_iter_init (&iter, profiler->counters); + while (g_hash_table_iter_next (&iter, NULL, &value_p)) + { + NamedCounter *counter = value_p; + + if (counter->can_reset) + counter->value = 0; + } + + g_hash_table_iter_init (&iter, profiler->timers); + while (g_hash_table_iter_next (&iter, NULL, &value_p)) + { + NamedTimer *timer = value_p; + + if (timer->can_reset) + { + timer->value = 0; + timer->min_value = 0; + timer->max_value = 0; + timer->avg_value = 0; + timer->n_samples = 0; + } + } + + profiler->last_sample = 0; +} + +void +gsk_profiler_push_samples (GskProfiler *profiler) +{ + GHashTableIter iter; + gpointer value_p = NULL; + guint last_sample; + + g_return_if_fail (GSK_IS_PROFILER (profiler)); + + g_hash_table_iter_init (&iter, profiler->timers); + while (g_hash_table_iter_next (&iter, NULL, &value_p)) + { + NamedTimer *timer = value_p; + Sample *s; + + last_sample = profiler->last_sample; + profiler->last_sample += 1; + if (profiler->last_sample == MAX_SAMPLES) + profiler->last_sample = 0; + + s = &(profiler->timer_samples[last_sample]); + s->id = timer->id; + + if (timer->invert) + s->value = (gint64) (1000000.0 / (double) timer->value); + else + s->value = timer->value; + } +} + +void +gsk_profiler_append_counters (GskProfiler *profiler, + GString *buffer) +{ + GHashTableIter iter; + gpointer value_p = NULL; + + g_return_if_fail (GSK_IS_PROFILER (profiler)); + g_return_if_fail (buffer != NULL); + + g_hash_table_iter_init (&iter, profiler->counters); + while (g_hash_table_iter_next (&iter, NULL, &value_p)) + { + NamedCounter *counter = value_p; + + g_string_append_printf (buffer, "%s: %" G_GINT64_FORMAT "\n", + counter->description, + counter->value); + } +} + +void +gsk_profiler_append_timers (GskProfiler *profiler, + GString *buffer) +{ + GHashTableIter iter; + gpointer value_p = NULL; + int i; + + g_return_if_fail (GSK_IS_PROFILER (profiler)); + g_return_if_fail (buffer != NULL); + + g_hash_table_iter_init (&iter, profiler->timers); + while (g_hash_table_iter_next (&iter, NULL, &value_p)) + { + NamedTimer *timer = value_p; + + timer->min_value = G_MAXINT64; + timer->max_value = G_MININT64; + timer->avg_value = 0; + timer->n_samples = 0; + } + + for (i = 0; i < profiler->last_sample; i++) + { + Sample *s = &(profiler->timer_samples[i]); + NamedTimer *timer; + + if (s->id == 0) + continue; + + timer = gsk_profiler_get_timer (profiler, s->id); + timer->min_value = MIN (timer->min_value, s->value); + timer->max_value = MAX (timer->max_value, s->value); + timer->avg_value += s->value; + timer->n_samples += 1; + } + + g_hash_table_iter_init (&iter, profiler->timers); + while (g_hash_table_iter_next (&iter, NULL, &value_p)) + { + NamedTimer *timer = value_p; + const char *unit = timer->invert ? "" : "usec"; + + g_string_append_printf (buffer, "%s (%s): %.2f", + timer->description, + unit, + (double) timer->value); + + if (timer->n_samples > 1) + { + timer->avg_value = timer->avg_value / timer->n_samples; + g_string_append_printf (buffer, " Min: %.2f Avg: %.2f Max: %.2f (%" G_GINT64_FORMAT " samples)", + (double) timer->min_value, + (double) timer->avg_value, + (double) timer->max_value, + timer->n_samples); + } + + g_string_append (buffer, "\n"); + } +} diff --git gsk/gskprofilerprivate.h gsk/gskprofilerprivate.h new file mode 100644 index 0000000000..7444ce0c68 --- /dev/null +++ gsk/gskprofilerprivate.h @@ -0,0 +1,53 @@ +#pragma once + +#include + +G_BEGIN_DECLS + +#define GSK_TYPE_PROFILER (gsk_profiler_get_type ()) +G_DECLARE_FINAL_TYPE (GskProfiler, gsk_profiler, GSK, PROFILER, GObject) + +GskProfiler * gsk_profiler_new (void); + +GQuark gsk_profiler_add_counter (GskProfiler *profiler, + const char *counter_name, + const char *description, + gboolean can_reset); +GQuark gsk_profiler_add_timer (GskProfiler *profiler, + const char *timer_name, + const char *description, + gboolean invert, + gboolean can_reset); + +void gsk_profiler_counter_inc (GskProfiler *profiler, + GQuark counter_id); +void gsk_profiler_counter_add (GskProfiler *profiler, + GQuark counter_id, + gint64 increment); +void gsk_profiler_counter_set (GskProfiler *profiler, + GQuark counter_id, + gint64 value); +void gsk_profiler_timer_begin (GskProfiler *profiler, + GQuark timer_id); +gint64 gsk_profiler_timer_end (GskProfiler *profiler, + GQuark timer_id); +void gsk_profiler_timer_set (GskProfiler *profiler, + GQuark timer_id, + gint64 value); + +gint64 gsk_profiler_counter_get (GskProfiler *profiler, + GQuark counter_id); +gint64 gsk_profiler_timer_get (GskProfiler *profiler, + GQuark timer_id); +gint64 gsk_profiler_timer_get_start (GskProfiler *profiler, + GQuark timer_id); + +void gsk_profiler_reset (GskProfiler *profiler); + +void gsk_profiler_push_samples (GskProfiler *profiler); +void gsk_profiler_append_counters (GskProfiler *profiler, + GString *buffer); +void gsk_profiler_append_timers (GskProfiler *profiler, + GString *buffer); + +G_END_DECLS diff --git gsk/gskrenderer.c gsk/gskrenderer.c index 458792d6cc..34d36b0ce2 100644 --- gsk/gskrenderer.c +++ gsk/gskrenderer.c @@ -41,6 +41,7 @@ #include "gskenumtypes.h" +#include "gl/gskglrenderer.h" #include "gpu/gskglrenderer.h" #include "gpu/gskvulkanrenderer.h" #include "gdk/gdkvulkancontextprivate.h" @@ -62,6 +63,8 @@ typedef struct GdkSurface *surface; GskRenderNode *prev_node; + GskProfiler *profiler; + GskDebugFlags debug_flags; unsigned int is_realized : 1; @@ -120,12 +123,14 @@ static void gsk_renderer_dispose (GObject *gobject) { GskRenderer *self = GSK_RENDERER (gobject); - G_GNUC_UNUSED GskRendererPrivate *priv = gsk_renderer_get_instance_private (self); + GskRendererPrivate *priv = gsk_renderer_get_instance_private (self); /* We can't just unrealize here because superclasses have already run dispose. * So we insist that unrealize must be called before unreffing. */ g_assert (!priv->is_realized); + g_clear_object (&priv->profiler); + G_OBJECT_CLASS (gsk_renderer_parent_class)->dispose (gobject); } @@ -195,6 +200,7 @@ gsk_renderer_init (GskRenderer *self) { GskRendererPrivate *priv = gsk_renderer_get_instance_private (self); + priv->profiler = gsk_profiler_new (); priv->debug_flags = gsk_get_debug_flags (); } @@ -481,6 +487,24 @@ gsk_renderer_render (GskRenderer *renderer, priv->prev_node = gsk_render_node_ref (root); } +/*< private > + * gsk_renderer_get_profiler: + * @renderer: a renderer + * + * Retrieves a pointer to the `GskProfiler` instance of the renderer. + * + * Returns: (transfer none): the profiler + */ +GskProfiler * +gsk_renderer_get_profiler (GskRenderer *renderer) +{ + GskRendererPrivate *priv = gsk_renderer_get_instance_private (renderer); + + g_return_val_if_fail (GSK_IS_RENDERER (renderer), NULL); + + return priv->profiler; +} + static GType get_renderer_for_name (const char *renderer_name) { @@ -492,6 +516,8 @@ get_renderer_for_name (const char *renderer_name) #endif else if (g_ascii_strcasecmp (renderer_name, "cairo") == 0) return GSK_TYPE_CAIRO_RENDERER; + else if (g_ascii_strcasecmp (renderer_name, "legacy") == 0) + return GSK_TYPE_GL_LEGACY_RENDERER; else if (g_ascii_strcasecmp (renderer_name, "gl") == 0 || g_ascii_strcasecmp (renderer_name, "opengl") == 0) return GSK_TYPE_GL_RENDERER; @@ -513,6 +539,7 @@ get_renderer_for_name (const char *renderer_name) " broadway - Disabled during GTK build\n" #endif " cairo - Use the Cairo fallback renderer\n" + " legacy - Use the legacy GL renderer (supports OpenGL 2.0+)\n" " opengl - Use the OpenGL renderer\n" " gl - Use the OpenGL renderer\n" #ifdef GDK_RENDERING_VULKAN @@ -521,8 +548,8 @@ get_renderer_for_name (const char *renderer_name) " vulkan - Disabled during GTK build\n" #endif " help - Print this help\n\n" - "The old OpenGL renderer has been removed in GTK 4.18, so using\n" - "GSK_RENDERER=gl will cause a warning and use the new OpenGL renderer.\n\n" + "The GPU renderer (gl/opengl) requires OpenGL 3.3+ or GLES 3.0+.\n" + "For older OpenGL versions (2.0-3.2), use GSK_RENDERER=legacy.\n\n" "Other arguments will cause a warning and be ignored."); } else @@ -690,6 +717,15 @@ get_renderer_for_vulkan_fallback (GdkSurface *surface) } #endif +static GType +get_renderer_for_gl_legacy (GdkSurface *surface) +{ + if (!gl_supported_platform (surface, TRUE)) + return G_TYPE_INVALID; + + return GSK_TYPE_GL_LEGACY_RENDERER; +} + static GType get_renderer_fallback (GdkSurface *surface) { @@ -711,6 +747,10 @@ static struct { { FALSE, get_renderer_for_vulkan_fallback }, #endif { FALSE, get_renderer_for_gl_fallback }, + /* Last chance before falling back to software: the legacy GL renderer + * still works on the OpenGL 2.x hardware the GPU renderer rejects. + */ + { FALSE, get_renderer_for_gl_legacy }, { FALSE, get_renderer_fallback }, }; diff --git gsk/gskrendererprivate.h gsk/gskrendererprivate.h index dec6dcb318..b32cef44e6 100644 --- gsk/gskrendererprivate.h +++ gsk/gskrendererprivate.h @@ -19,6 +19,7 @@ #pragma once #include "gskrenderer.h" +#include "gskprofilerprivate.h" #include "gskdebugprivate.h" #include "gskoffloadprivate.h" @@ -57,6 +58,8 @@ struct _GskRendererClass GskRenderer * gsk_renderer_new_for_surface_full (GdkSurface *surface, gboolean attach); +GskProfiler * gsk_renderer_get_profiler (GskRenderer *renderer); + GskDebugFlags gsk_renderer_get_debug_flags (GskRenderer *renderer); void gsk_renderer_set_debug_flags (GskRenderer *renderer, GskDebugFlags flags); diff --git gsk/meson.build gsk/meson.build index e1cb918394..e9e9d77905 100644 --- gsk/meson.build +++ gsk/meson.build @@ -1,3 +1,29 @@ +gsk_private_gl_shaders = [ + 'gl/resources/preamble.glsl', + 'gl/resources/preamble.fs.glsl', + 'gl/resources/preamble.vs.glsl', + 'gl/resources/border.glsl', + 'gl/resources/blit.glsl', + 'gl/resources/coloring.glsl', + 'gl/resources/color.glsl', + 'gl/resources/linear_gradient.glsl', + 'gl/resources/radial_gradient.glsl', + 'gl/resources/conic_gradient.glsl', + 'gl/resources/color_matrix.glsl', + 'gl/resources/blur.glsl', + 'gl/resources/inset_shadow.glsl', + 'gl/resources/outset_shadow.glsl', + 'gl/resources/unblurred_outset_shadow.glsl', + 'gl/resources/cross_fade.glsl', + 'gl/resources/blend.glsl', + 'gl/resources/repeat.glsl', + 'gl/resources/custom.glsl', + 'gl/resources/filled_border.glsl', + 'gl/resources/mask.glsl', + 'gl/resources/external.glsl', + 'gl/resources/premultiply.glsl', +] + gsk_public_sources = files([ 'gskarithmeticnode.c', 'gskblendnode.c', @@ -59,6 +85,7 @@ gsk_public_sources = files([ 'gsktransform.c', 'gsktransformnode.c', 'gskturbulencenode.c', + 'gl/gskglrenderer.c', 'gpu/gskglrenderer.c', 'gpu/gskvulkanrenderer.c', ]) @@ -72,7 +99,23 @@ gsk_private_sources = files([ 'gskcurveintersect.c', 'gskdebug.c', 'gskprivate.c', + 'gskprofiler.c', 'gl/fp16.c', + 'gl/gskglattachmentstate.c', + 'gl/gskglbuffer.c', + 'gl/gskglcommandqueue.c', + 'gl/gskglcompiler.c', + 'gl/gskgldriver.c', + 'gl/gskglglyphlibrary.c', + 'gl/gskgliconlibrary.c', + 'gl/gskglprogram.c', + 'gl/gskglrenderjob.c', + 'gl/gskglshadowlibrary.c', + 'gl/gskgltexturelibrary.c', + 'gl/gskgluniformstate.c', + 'gl/gskgltexture.c', + 'gl/gskglprofiler.c', + 'gl/stb_rect_pack.c', 'gpu/gskatlasallocator.c', 'gpu/gskglbuffer.c', 'gpu/gskgldevice.c', @@ -217,6 +260,7 @@ gsk_resources_xml = custom_target(output: 'gsk.resources.xml', command: [ find_program('gen-gsk-gresources-xml.py'), '@OUTPUT@', + gsk_private_gl_shaders, gsk_private_gpu_gl_shaders, gsk_private_gpu_vulkan_shaders, ], diff --git gtk/gtkglarea.c gtk/gtkglarea.c index 8962f0a9e5..90229a18e8 100644 --- gtk/gtkglarea.c +++ gtk/gtkglarea.c @@ -815,8 +815,11 @@ gtk_gl_area_snapshot (GtkWidget *widget, priv->texture = NULL; priv->textures = g_list_prepend (priv->textures, texture); - sync = glFenceSync (GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - gdk_gl_texture_builder_set_sync (texture->builder, sync); + if (gdk_gl_context_has_feature (priv->context, GDK_GL_FEATURE_SYNC)) + { + sync = glFenceSync (GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + gdk_gl_texture_builder_set_sync (texture->builder, sync); + } texture->gl_texture = gdk_gl_texture_builder_build (texture->builder, release_gl_texture, diff --git gtk/inspector/eventrecording.h gtk/inspector/eventrecording.h index f67bceabcf..04fc0eca0d 100644 --- gtk/inspector/eventrecording.h +++ gtk/inspector/eventrecording.h @@ -19,6 +19,7 @@ #include #include +#include "gsk/gskprofilerprivate.h" #include "gtk/gtkenums.h" #include "gtk/gtkwidget.h" #include "gtk/gtkeventcontroller.h" diff --git gtk/inspector/recorder.c gtk/inspector/recorder.c index cd044ac881..4c5e294487 100644 --- gtk/inspector/recorder.c +++ gtk/inspector/recorder.c @@ -2826,6 +2826,7 @@ gtk_inspector_recorder_record_render (GtkInspectorRecorder *recorder, } recording = gtk_inspector_render_recording_new (frame_time, + gsk_renderer_get_profiler (renderer), &(GdkRectangle) { 0, 0, gdk_surface_get_width (surface), gdk_surface_get_height (surface) }, diff --git gtk/inspector/renderrecording.c gtk/inspector/renderrecording.c index bda2afe4da..56fc29c1aa 100644 --- gtk/inspector/renderrecording.c +++ gtk/inspector/renderrecording.c @@ -30,6 +30,7 @@ gtk_inspector_render_recording_finalize (GObject *object) g_clear_pointer (&recording->clip_region, cairo_region_destroy); g_clear_pointer (&recording->node, gsk_render_node_unref); g_clear_pointer (&recording->profile_node, gsk_render_node_unref); + g_clear_pointer (&recording->profiler_info, g_free); G_OBJECT_CLASS (gtk_inspector_render_recording_parent_class)->finalize (object); } @@ -47,8 +48,21 @@ gtk_inspector_render_recording_init (GtkInspectorRenderRecording *vis) { } +static void +collect_profiler_info (GtkInspectorRenderRecording *recording, + GskProfiler *profiler) +{ + GString *string; + + string = g_string_new (NULL); + gsk_profiler_append_timers (profiler, string); + gsk_profiler_append_counters (profiler, string); + recording->profiler_info = g_string_free (string, FALSE); +} + GtkInspectorRecording * gtk_inspector_render_recording_new (gint64 timestamp, + GskProfiler *profiler, const GdkRectangle *area, const cairo_region_t *clip_region, GskRenderNode *node, @@ -60,6 +74,7 @@ gtk_inspector_render_recording_new (gint64 timestamp, "timestamp", timestamp, NULL); + collect_profiler_info (recording, profiler); recording->area = *area; recording->clip_region = cairo_region_copy (clip_region); recording->node = gsk_render_node_ref (node); @@ -107,4 +122,10 @@ gtk_inspector_render_recording_get_surface (GtkInspectorRenderRecording *recordi return recording->surface; } +const char * +gtk_inspector_render_recording_get_profiler_info (GtkInspectorRenderRecording *recording) +{ + return recording->profiler_info; +} + // vim: set et sw=2 ts=2: diff --git gtk/inspector/renderrecording.h gtk/inspector/renderrecording.h index 9448508697..63e0bec5b8 100644 --- gtk/inspector/renderrecording.h +++ gtk/inspector/renderrecording.h @@ -19,6 +19,7 @@ #include #include +#include "gsk/gskprofilerprivate.h" #include "inspector/recording.h" @@ -43,6 +44,7 @@ typedef struct _GtkInspectorRenderRecording GskRenderNode *node; GskRenderNode *profile_node; gpointer surface; + char *profiler_info; } GtkInspectorRenderRecording; typedef struct _GtkInspectorRenderRecordingClass @@ -54,6 +56,7 @@ GType gtk_inspector_render_recording_get_type (void); GtkInspectorRecording * gtk_inspector_render_recording_new (gint64 timestamp, + GskProfiler *profiler, const GdkRectangle *area, const cairo_region_t *clip_region, GskRenderNode *node, @@ -71,6 +74,9 @@ const cairo_rectangle_int_t * gpointer gtk_inspector_render_recording_get_surface (GtkInspectorRenderRecording *recording); +const char * gtk_inspector_render_recording_get_profiler_info + (GtkInspectorRenderRecording *recording); + G_END_DECLS diff --git tools/gtk-rendernode-tool-benchmark.c tools/gtk-rendernode-tool-benchmark.c index 50e1cf7872..5e085170e7 100644 --- tools/gtk-rendernode-tool-benchmark.c +++ tools/gtk-rendernode-tool-benchmark.c @@ -138,7 +138,7 @@ do_benchmark (int *argc, } if (renderers == NULL || renderers[0] == NULL) - renderers = g_strdupv ((char **) (const char *[]) { "gl", "vulkan", "cairo", NULL }); + renderers = g_strdupv ((char **) (const char *[]) { "legacy", "gl", "vulkan", "cairo", NULL }); node = load_node_file (filenames[0]); -- 2.43.0