summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristian Hergert <chergert@redhat.com>2020-12-18 17:36:59 -0800
committerChristian Hergert <chergert@redhat.com>2021-02-23 14:41:52 -0800
commit2a38cecd3351d60495f82334177ebea3787a3a7c (patch)
treedd53e6d2868825f45ad41eed4c49b99ecfa5cc6f
parent9698d4aa2a7ae42ef1ae2b36e1a647fc304a01c6 (diff)
downloadgtk+-2a38cecd3351d60495f82334177ebea3787a3a7c.tar.gz
gsk: add OpenGL based GskNglRenderer
The primary goal here was to cleanup the current GL renderer to make maintenance easier going forward. Furthermore, it tracks state to allow us to implement more advanced renderer features going forward. Reordering This renderer will reorder batches by render target to reduce the number of times render targets are changed. In the future, we could also reorder by program within the render target if we can determine that vertices do not overlap. Uniform Snapshots To allow for reordering of batches all uniforms need to be tracked for the programs. This allows us to create the full uniform state when the batch has been moved into a new position. Some care was taken as it can be performance sensitive. Attachment Snapshots Similar to uniform snapshots, we need to know all of the texture attachments so that we can rebind them when necessary. Render Jobs To help isolate the process of creating GL commands from the renderer abstraction a render job abstraction was added. This could be extended in the future if we decided to do tiling. Command Queue Render jobs create batches using the command queue. The command queue will snapshot uniform and attachment state so that it can reorder batches right before executing them. Currently, the only reordering done is to ensure that we only visit each render target once. We could extend this by tracking vertices, attachments, and others. This code currently uses an inline array helper to reduce overhead from GArray which was showing up on profiles. It could be changed to use GdkArray without too much work, but had roughly double the instructions. Cycle counts have not yet been determined. GLSL Programs This was simplified to use XMACROS so that we can just extend one file (gskglprograms.defs) instead of multiple places. The programs are added as fields in the driver for easy access. Driver The driver manages textures, render targets, access to atlases, programs, and more. There is one driver per display, by using the shared GL context. Some work could be done here to batch uploads so that we make fewer calls to upload when sending icon theme data to the GPU. We'd need to keep a copy of the atlas data for such purposes.
-rw-r--r--demos/node-editor/node-editor-window.c4
-rw-r--r--gsk/gskglshader.c7
-rw-r--r--gsk/gskrenderer.c4
-rw-r--r--gsk/meson.build17
-rw-r--r--gsk/ngl/gsknglattachmentstate.c106
-rw-r--r--gsk/ngl/gsknglattachmentstateprivate.h71
-rw-r--r--gsk/ngl/gsknglbuffer.c69
-rw-r--r--gsk/ngl/gsknglbufferprivate.h81
-rw-r--r--gsk/ngl/gsknglcommandqueue.c1507
-rw-r--r--gsk/ngl/gsknglcommandqueueprivate.h362
-rw-r--r--gsk/ngl/gsknglcompiler.c678
-rw-r--r--gsk/ngl/gsknglcompilerprivate.h69
-rw-r--r--gsk/ngl/gskngldriver.c1296
-rw-r--r--gsk/ngl/gskngldriverprivate.h234
-rw-r--r--gsk/ngl/gsknglglyphlibrary.c325
-rw-r--r--gsk/ngl/gsknglglyphlibraryprivate.h119
-rw-r--r--gsk/ngl/gskngliconlibrary.c213
-rw-r--r--gsk/ngl/gskngliconlibraryprivate.h60
-rw-r--r--gsk/ngl/gsknglprogram.c176
-rw-r--r--gsk/ngl/gsknglprogramprivate.h273
-rw-r--r--gsk/ngl/gsknglprograms.defs83
-rw-r--r--gsk/ngl/gsknglrenderer.c312
-rw-r--r--gsk/ngl/gsknglrenderer.h46
-rw-r--r--gsk/ngl/gsknglrendererprivate.h34
-rw-r--r--gsk/ngl/gsknglrenderjob.c3760
-rw-r--r--gsk/ngl/gsknglrenderjobprivate.h39
-rw-r--r--gsk/ngl/gsknglshadowlibrary.c228
-rw-r--r--gsk/ngl/gsknglshadowlibraryprivate.h44
-rw-r--r--gsk/ngl/gskngltexturelibrary.c315
-rw-r--r--gsk/ngl/gskngltexturelibraryprivate.h202
-rw-r--r--gsk/ngl/gskngltexturepool.c188
-rw-r--r--gsk/ngl/gskngltexturepoolprivate.h102
-rw-r--r--gsk/ngl/gskngltypesprivate.h62
-rw-r--r--gsk/ngl/gskngluniformstate.c270
-rw-r--r--gsk/ngl/gskngluniformstateprivate.h685
-rw-r--r--gsk/ngl/inlinearray.h77
-rw-r--r--gsk/ngl/ninesliceprivate.h309
-rw-r--r--gtk/gtktestutils.c1
-rw-r--r--gtk/inspector/general.c2
-rw-r--r--testsuite/gsk/meson.build1
40 files changed, 12428 insertions, 3 deletions
diff --git a/demos/node-editor/node-editor-window.c b/demos/node-editor/node-editor-window.c
index 0335a28c46..03da8c67bd 100644
--- a/demos/node-editor/node-editor-window.c
+++ b/demos/node-editor/node-editor-window.c
@@ -25,6 +25,7 @@
#include "gsk/gskrendernodeparserprivate.h"
#include "gsk/gl/gskglrenderer.h"
+#include "gsk/ngl/gsknglrenderer.h"
#ifdef GDK_WINDOWING_BROADWAY
#include "gsk/broadway/gskbroadwayrenderer.h"
#endif
@@ -762,6 +763,9 @@ node_editor_window_realize (GtkWidget *widget)
node_editor_window_add_renderer (self,
gsk_gl_renderer_new (),
"OpenGL");
+ node_editor_window_add_renderer (self,
+ gsk_ngl_renderer_new (),
+ "NGL");
#ifdef GDK_RENDERING_VULKAN
node_editor_window_add_renderer (self,
gsk_vulkan_renderer_new (),
diff --git a/gsk/gskglshader.c b/gsk/gskglshader.c
index 181ae32a2d..081e8ac4a7 100644
--- a/gsk/gskglshader.c
+++ b/gsk/gskglshader.c
@@ -139,7 +139,9 @@
#include "gskglshader.h"
#include "gskglshaderprivate.h"
#include "gskdebugprivate.h"
+
#include "gl/gskglrendererprivate.h"
+#include "ngl/gsknglrendererprivate.h"
static GskGLUniformType
uniform_type_from_glsl (const char *str)
@@ -542,8 +544,9 @@ gsk_gl_shader_compile (GskGLShader *shader,
g_return_val_if_fail (GSK_IS_GL_SHADER (shader), FALSE);
if (GSK_IS_GL_RENDERER (renderer))
- return gsk_gl_renderer_try_compile_gl_shader (GSK_GL_RENDERER (renderer),
- shader, error);
+ return gsk_gl_renderer_try_compile_gl_shader (GSK_GL_RENDERER (renderer), shader, error);
+ else if (GSK_IS_NGL_RENDERER (renderer))
+ return gsk_ngl_renderer_try_compile_gl_shader (GSK_NGL_RENDERER (renderer), shader, error);
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
"The renderer does not support gl shaders");
diff --git a/gsk/gskrenderer.c b/gsk/gskrenderer.c
index dbbfd28f35..9d5dbfcb86 100644
--- a/gsk/gskrenderer.c
+++ b/gsk/gskrenderer.c
@@ -39,6 +39,7 @@
#include "gskcairorenderer.h"
#include "gskdebugprivate.h"
#include "gl/gskglrenderer.h"
+#include "ngl/gsknglrenderer.h"
#include "gskprofilerprivate.h"
#include "gskrendernodeprivate.h"
@@ -496,6 +497,8 @@ get_renderer_for_name (const char *renderer_name)
else if (g_ascii_strcasecmp (renderer_name, "opengl") == 0
|| g_ascii_strcasecmp (renderer_name, "gl") == 0)
return GSK_TYPE_GL_RENDERER;
+ else if (g_ascii_strcasecmp (renderer_name, "ngl") == 0)
+ return GSK_TYPE_NGL_RENDERER;
#ifdef GDK_RENDERING_VULKAN
else if (g_ascii_strcasecmp (renderer_name, "vulkan") == 0)
return GSK_TYPE_VULKAN_RENDERER;
@@ -511,6 +514,7 @@ get_renderer_for_name (const char *renderer_name)
g_print (" cairo - Use the Cairo fallback renderer\n");
g_print (" opengl - Use the default OpenGL renderer\n");
g_print (" gl - Same as opengl\n");
+ g_print (" next - Another OpenGL renderer\n");
#ifdef GDK_RENDERING_VULKAN
g_print (" vulkan - Use the Vulkan renderer\n");
#else
diff --git a/gsk/meson.build b/gsk/meson.build
index 748f4738a1..dd1ac34ff9 100644
--- a/gsk/meson.build
+++ b/gsk/meson.build
@@ -31,6 +31,7 @@ gsk_public_sources = files([
'gskroundedrect.c',
'gsktransform.c',
'gl/gskglrenderer.c',
+ 'ngl/gsknglrenderer.c',
])
gsk_private_sources = files([
@@ -48,6 +49,19 @@ gsk_private_sources = files([
'gl/gskgliconcache.c',
'gl/opbuffer.c',
'gl/stb_rect_pack.c',
+ 'ngl/gsknglattachmentstate.c',
+ 'ngl/gsknglbuffer.c',
+ 'ngl/gsknglcommandqueue.c',
+ 'ngl/gsknglcompiler.c',
+ 'ngl/gskngldriver.c',
+ 'ngl/gsknglglyphlibrary.c',
+ 'ngl/gskngliconlibrary.c',
+ 'ngl/gsknglprogram.c',
+ 'ngl/gsknglrenderjob.c',
+ 'ngl/gsknglshadowlibrary.c',
+ 'ngl/gskngltexturelibrary.c',
+ 'ngl/gskngluniformstate.c',
+ 'ngl/gskngltexturepool.c',
])
gsk_public_headers = files([
@@ -64,7 +78,8 @@ gsk_public_headers = files([
install_headers(gsk_public_headers, 'gsk.h', subdir: 'gtk-4.0/gsk')
gsk_public_gl_headers = files([
- 'gl/gskglrenderer.h'
+ 'gl/gskglrenderer.h',
+ 'ngl/gsknglrenderer.h',
])
install_headers(gsk_public_gl_headers, subdir: 'gtk-4.0/gsk/gl')
gsk_public_headers += gsk_public_gl_headers
diff --git a/gsk/ngl/gsknglattachmentstate.c b/gsk/ngl/gsknglattachmentstate.c
new file mode 100644
index 0000000000..bf37087a69
--- /dev/null
+++ b/gsk/ngl/gsknglattachmentstate.c
@@ -0,0 +1,106 @@
+/* gsknglattachmentstate.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gsknglattachmentstateprivate.h"
+
+GskNglAttachmentState *
+gsk_ngl_attachment_state_new (void)
+{
+ GskNglAttachmentState *self;
+
+ self = g_atomic_rc_box_new0 (GskNglAttachmentState);
+
+ 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;
+ }
+
+ return self;
+}
+
+GskNglAttachmentState *
+gsk_ngl_attachment_state_ref (GskNglAttachmentState *self)
+{
+ return g_atomic_rc_box_acquire (self);
+}
+
+void
+gsk_ngl_attachment_state_unref (GskNglAttachmentState *self)
+{
+ g_atomic_rc_box_release (self);
+}
+
+void
+gsk_ngl_attachment_state_bind_texture (GskNglAttachmentState *self,
+ GLenum target,
+ GLenum texture,
+ guint id)
+{
+ GskNglBindTexture *attach;
+
+ g_assert (self != NULL);
+ g_assert (target == GL_TEXTURE_1D ||
+ target == GL_TEXTURE_2D ||
+ target == GL_TEXTURE_3D);
+ g_assert (texture >= GL_TEXTURE0 && texture <= GL_TEXTURE16);
+
+ attach = &self->textures[texture - GL_TEXTURE0];
+
+ if (attach->target != target || attach->texture != texture || attach->id != id)
+ {
+ attach->target = target;
+ attach->texture = texture;
+ attach->id = id;
+ attach->initial = FALSE;
+
+ if (attach->changed == FALSE)
+ {
+ attach->changed = TRUE;
+ self->n_changed++;
+ }
+ }
+}
+
+void
+gsk_ngl_attachment_state_bind_framebuffer (GskNglAttachmentState *self,
+ guint id)
+{
+ g_assert (self != NULL);
+
+ if (self->fbo.id != id)
+ {
+ self->fbo.id = id;
+ self->fbo.changed = TRUE;
+ }
+}
diff --git a/gsk/ngl/gsknglattachmentstateprivate.h b/gsk/ngl/gsknglattachmentstateprivate.h
new file mode 100644
index 0000000000..b43e91e09c
--- /dev/null
+++ b/gsk/ngl/gsknglattachmentstateprivate.h
@@ -0,0 +1,71 @@
+/* gsknglattachmentstateprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_ATTACHMENT_STATE_PRIVATE_H__
+#define __GSK_NGL_ATTACHMENT_STATE_PRIVATE_H__
+
+#include "gskngltypesprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GskNglAttachmentState GskNglAttachmentState;
+typedef struct _GskNglBindFramebuffer GskNglBindFramebuffer;
+typedef struct _GskNglBindTexture GskNglBindTexture;
+
+struct _GskNglBindTexture
+{
+ guint changed : 1;
+ guint initial : 1;
+ GLenum target : 30;
+ GLenum texture;
+ guint id;
+};
+
+G_STATIC_ASSERT (sizeof (GskNglBindTexture) == 12);
+
+struct _GskNglBindFramebuffer
+{
+ guint changed : 1;
+ guint id : 31;
+};
+
+G_STATIC_ASSERT (sizeof (GskNglBindFramebuffer) == 4);
+
+struct _GskNglAttachmentState
+{
+ GskNglBindFramebuffer fbo;
+ /* Increase if shaders add more textures */
+ GskNglBindTexture textures[4];
+ guint n_changed;
+};
+
+GskNglAttachmentState *gsk_ngl_attachment_state_new (void);
+GskNglAttachmentState *gsk_ngl_attachment_state_ref (GskNglAttachmentState *self);
+void gsk_ngl_attachment_state_unref (GskNglAttachmentState *self);
+void gsk_ngl_attachment_state_bind_texture (GskNglAttachmentState *self,
+ GLenum target,
+ GLenum texture,
+ guint id);
+void gsk_ngl_attachment_state_bind_framebuffer (GskNglAttachmentState *self,
+ guint id);
+
+G_END_DECLS
+
+#endif /* __GSK_NGL_ATTACHMENT_STATE_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglbuffer.c b/gsk/ngl/gsknglbuffer.c
new file mode 100644
index 0000000000..f65923d003
--- /dev/null
+++ b/gsk/ngl/gsknglbuffer.c
@@ -0,0 +1,69 @@
+/* gsknglbufferprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gsknglbufferprivate.h"
+
+/**
+ * gsk_ngl_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 #GskNglBuffer 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_ngl_buffer_init (GskNglBuffer *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_ngl_buffer_submit (GskNglBuffer *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_ngl_buffer_destroy (GskNglBuffer *buffer)
+{
+ g_clear_pointer (&buffer->buffer, g_free);
+}
diff --git a/gsk/ngl/gsknglbufferprivate.h b/gsk/ngl/gsknglbufferprivate.h
new file mode 100644
index 0000000000..fc67bc9e0b
--- /dev/null
+++ b/gsk/ngl/gsknglbufferprivate.h
@@ -0,0 +1,81 @@
+/* gsknglbufferprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_BUFFER_PRIVATE_H__
+#define __GSK_NGL_BUFFER_PRIVATE_H__
+
+#include "gskngltypesprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GskNglBuffer
+{
+ guint8 *buffer;
+ gsize buffer_pos;
+ gsize buffer_len;
+ guint count;
+ GLenum target;
+ guint element_size;
+} GskNglBuffer;
+
+void gsk_ngl_buffer_init (GskNglBuffer *self,
+ GLenum target,
+ guint element_size);
+void gsk_ngl_buffer_destroy (GskNglBuffer *buffer);
+GLuint gsk_ngl_buffer_submit (GskNglBuffer *buffer);
+
+static inline gpointer
+gsk_ngl_buffer_advance (GskNglBuffer *buffer,
+ guint count)
+{
+ gpointer ret;
+ gsize to_alloc = count * buffer->element_size;
+
+ if G_UNLIKELY (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_ngl_buffer_retract (GskNglBuffer *buffer,
+ guint count)
+{
+ buffer->buffer_pos -= count * buffer->element_size;
+ buffer->count -= count;
+}
+
+static inline guint
+gsk_ngl_buffer_get_offset (GskNglBuffer *buffer)
+{
+ return buffer->count;
+}
+
+G_END_DECLS
+
+#endif /* __GSK_NGL_BUFFER_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglcommandqueue.c b/gsk/ngl/gsknglcommandqueue.c
new file mode 100644
index 0000000000..9426c199b1
--- /dev/null
+++ b/gsk/ngl/gsknglcommandqueue.c
@@ -0,0 +1,1507 @@
+/* gsknglcommandqueue.c
+ *
+ * Copyright 2017 Timm Bäder <mail@baedert.org>
+ * Copyright 2018 Matthias Clasen <mclasen@redhat.com>
+ * Copyright 2018 Alexander Larsson <alexl@redhat.com>
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include <gdk/gdkglcontextprivate.h>
+#include <gdk/gdkmemorytextureprivate.h>
+#include <gdk/gdkprofilerprivate.h>
+#include <gsk/gskdebugprivate.h>
+#include <gsk/gskroundedrectprivate.h>
+
+#include "gsknglattachmentstateprivate.h"
+#include "gsknglbufferprivate.h"
+#include "gsknglcommandqueueprivate.h"
+#include "gskngluniformstateprivate.h"
+
+#include "inlinearray.h"
+
+G_DEFINE_TYPE (GskNglCommandQueue, gsk_ngl_command_queue, G_TYPE_OBJECT)
+
+G_GNUC_UNUSED static inline void
+print_uniform (GskNglUniformFormat 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_NGL_UNIFORM_FORMAT_1F:
+ g_printerr ("1f<%f>", data->fval[0]);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_2F:
+ g_printerr ("2f<%f,%f>", data->fval[0], data->fval[1]);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_3F:
+ g_printerr ("3f<%f,%f,%f>", data->fval[0], data->fval[1], data->fval[2]);
+ break;
+
+ case GSK_NGL_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_NGL_UNIFORM_FORMAT_1I:
+ case GSK_NGL_UNIFORM_FORMAT_TEXTURE:
+ g_printerr ("1i<%d>", data->ival[0]);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_1UI:
+ g_printerr ("1ui<%u>", data->uval[0]);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_COLOR: {
+ char *str = gdk_rgba_to_string (valueptr);
+ g_printerr ("%s", str);
+ g_free (str);
+ break;
+ }
+
+ case GSK_NGL_UNIFORM_FORMAT_ROUNDED_RECT: {
+ char *str = gsk_rounded_rect_to_string (valueptr);
+ g_printerr ("%s", str);
+ g_free (str);
+ break;
+ }
+
+ case GSK_NGL_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_NGL_UNIFORM_FORMAT_1FV:
+ case GSK_NGL_UNIFORM_FORMAT_2FV:
+ case GSK_NGL_UNIFORM_FORMAT_3FV:
+ case GSK_NGL_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_ngl_uniform_format_size (format));
+ }
+ g_printerr ("]");
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_2I:
+ g_printerr ("2i<%d,%d>", data->ival[0], data->ival[1]);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_3I:
+ g_printerr ("3i<%d,%d,%d>", data->ival[0], data->ival[1], data->ival[2]);
+ break;
+
+ case GSK_NGL_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_NGL_UNIFORM_FORMAT_LAST:
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+G_GNUC_UNUSED static inline void
+gsk_ngl_command_queue_print_batch (GskNglCommandQueue *self,
+ const GskNglCommandBatch *batch)
+{
+ static const char *command_kinds[] = { "Clear", NULL, NULL, "Draw", };
+ guint framebuffer_id;
+
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
+ g_assert (batch != NULL);
+
+ if (batch->any.kind == GSK_NGL_COMMAND_KIND_CLEAR)
+ framebuffer_id = batch->clear.framebuffer;
+ else if (batch->any.kind == GSK_NGL_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_NGL_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 GskNglCommandBind *bind = &self->batch_binds.items[batch->draw.bind_offset + i];
+ g_print (" Bind[%d]: %u\n", bind->texture, bind->id);
+ }
+
+ for (guint i = 0; i < batch->draw.uniform_count; i++)
+ {
+ const GskNglCommandUniform *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_ngl_uniform_state_get_uniform_data (self->uniforms, uniform->info.offset));
+ g_printerr ("\n");
+ }
+ }
+ else if (batch->any.kind == GSK_NGL_COMMAND_KIND_CLEAR)
+ {
+ g_printerr (" Bits: 0x%x\n", batch->clear.bits);
+ }
+
+ g_printerr ("}\n");
+}
+
+G_GNUC_UNUSED static inline void
+gsk_ngl_command_queue_capture_png (GskNglCommandQueue *self,
+ const char *filename,
+ guint width,
+ guint height,
+ gboolean flip_y)
+{
+ cairo_surface_t *surface;
+ guint8 *data;
+ guint stride;
+
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
+ g_assert (filename != NULL);
+
+ stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width);
+ data = g_malloc_n (height, stride);
+
+ glReadPixels (0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, 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;
+ }
+
+ surface = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, width, height, stride);
+ cairo_surface_write_to_png (surface, filename);
+
+ cairo_surface_destroy (surface);
+ g_free (data);
+}
+
+static inline gboolean
+will_ignore_batch (GskNglCommandQueue *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 guint
+snapshot_attachments (const GskNglAttachmentState *state,
+ GskNglCommandBinds *array)
+{
+ GskNglCommandBind *bind = gsk_ngl_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;
+ count++;
+ }
+ }
+
+ if (count != G_N_ELEMENTS (state->textures))
+ array->len -= G_N_ELEMENTS (state->textures) - count;
+
+ return count;
+}
+
+static inline guint
+snapshot_uniforms (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ GskNglCommandUniforms *array)
+{
+ GskNglCommandUniform *uniform = gsk_ngl_command_uniforms_append_n (array, program->n_sparse);
+ guint count = 0;
+
+ for (guint i = 0; i < program->n_sparse; i++)
+ {
+ guint location = program->sparse[i];
+ const GskNglUniformInfo *info = &program->uniforms[location].info;
+
+ if (!info->initial)
+ {
+ uniform[count].location = location;
+ uniform[count].info = *info;
+ count++;
+ }
+ }
+
+ if (count != program->n_sparse)
+ array->len -= program->n_sparse - count;
+
+ return count;
+}
+
+static inline gboolean
+snapshots_equal (GskNglCommandQueue *self,
+ GskNglCommandBatch *first,
+ GskNglCommandBatch *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 GskNglCommandBind *fb = &self->batch_binds.items[first->draw.bind_offset+i];
+ const GskNglCommandBind *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 GskNglCommandUniform *fu = &self->batch_uniforms.items[first->draw.uniform_offset+i];
+ const GskNglCommandUniform *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_ngl_uniform_state_get_uniform_data (self->uniforms, fu->info.offset);
+ sdata = gsk_ngl_uniform_state_get_uniform_data (self->uniforms, su->info.offset);
+
+ switch (fu->info.format)
+ {
+ case GSK_NGL_UNIFORM_FORMAT_1F:
+ case GSK_NGL_UNIFORM_FORMAT_1FV:
+ case GSK_NGL_UNIFORM_FORMAT_1I:
+ case GSK_NGL_UNIFORM_FORMAT_TEXTURE:
+ case GSK_NGL_UNIFORM_FORMAT_1UI:
+ len = 4;
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_2F:
+ case GSK_NGL_UNIFORM_FORMAT_2FV:
+ case GSK_NGL_UNIFORM_FORMAT_2I:
+ len = 8;
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_3F:
+ case GSK_NGL_UNIFORM_FORMAT_3FV:
+ case GSK_NGL_UNIFORM_FORMAT_3I:
+ len = 12;
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_4F:
+ case GSK_NGL_UNIFORM_FORMAT_4FV:
+ case GSK_NGL_UNIFORM_FORMAT_4I:
+ len = 16;
+ break;
+
+
+ case GSK_NGL_UNIFORM_FORMAT_MATRIX:
+ len = sizeof (float) * 16;
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_ROUNDED_RECT:
+ len = sizeof (float) * 12;
+ break;
+
+ case GSK_NGL_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_ngl_command_queue_dispose (GObject *object)
+{
+ GskNglCommandQueue *self = (GskNglCommandQueue *)object;
+
+ g_assert (GSK_IS_NGL_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_ngl_attachment_state_unref);
+ g_clear_pointer (&self->uniforms, gsk_ngl_uniform_state_unref);
+
+ gsk_ngl_command_batches_clear (&self->batches);
+ gsk_ngl_command_binds_clear (&self->batch_binds);
+ gsk_ngl_command_uniforms_clear (&self->batch_uniforms);
+
+ gsk_ngl_buffer_destroy (&self->vertices);
+
+ G_OBJECT_CLASS (gsk_ngl_command_queue_parent_class)->dispose (object);
+}
+
+static void
+gsk_ngl_command_queue_class_init (GskNglCommandQueueClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gsk_ngl_command_queue_dispose;
+}
+
+static void
+gsk_ngl_command_queue_init (GskNglCommandQueue *self)
+{
+ self->max_texture_size = -1;
+
+ gsk_ngl_command_batches_init (&self->batches, 128);
+ gsk_ngl_command_binds_init (&self->batch_binds, 1024);
+ gsk_ngl_command_uniforms_init (&self->batch_uniforms, 2048);
+
+ self->debug_groups = g_string_chunk_new (4096);
+
+ gsk_ngl_buffer_init (&self->vertices, GL_ARRAY_BUFFER, sizeof (GskNglDrawVertex));
+}
+
+GskNglCommandQueue *
+gsk_ngl_command_queue_new (GdkGLContext *context,
+ GskNglUniformState *uniforms)
+{
+ GskNglCommandQueue *self;
+
+ 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_ngl_attachment_state_new ();
+
+ /* Use shared uniform state if we're provided one */
+ if (uniforms != NULL)
+ self->uniforms = gsk_ngl_uniform_state_ref (uniforms);
+ else
+ self->uniforms = gsk_ngl_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);
+
+ return g_steal_pointer (&self);
+}
+
+static inline GskNglCommandBatch *
+begin_next_batch (GskNglCommandQueue *self)
+{
+ GskNglCommandBatch *batch;
+
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
+
+ /* GskNglCommandBatch 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_ngl_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 (GskNglCommandQueue *self)
+{
+ guint index;
+
+ g_assert (GSK_IS_NGL_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)
+ {
+ GskNglCommandBatch *prev = &self->batches.items[self->tail_batch_index];
+
+ prev->any.next_batch_index = index;
+ }
+
+ self->tail_batch_index = index;
+}
+
+static void
+discard_batch (GskNglCommandQueue *self)
+{
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
+ g_assert (self->batches.len > 0);
+
+ self->batches.len--;
+}
+
+void
+gsk_ngl_command_queue_begin_draw (GskNglCommandQueue *self,
+ GskNglUniformProgram *program,
+ guint width,
+ guint height)
+{
+ GskNglCommandBatch *batch;
+
+ g_assert (GSK_IS_NGL_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;
+
+ self->program_info = program;
+
+ batch = begin_next_batch (self);
+ batch->any.kind = GSK_NGL_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.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_ngl_buffer_get_offset (&self->vertices);
+
+ self->fbo_max = MAX (self->fbo_max, batch->draw.framebuffer);
+
+ self->in_draw = TRUE;
+}
+
+void
+gsk_ngl_command_queue_end_draw (GskNglCommandQueue *self)
+{
+ GskNglCommandBatch *last_batch;
+ GskNglCommandBatch *batch;
+
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
+ g_assert (self->batches.len > 0);
+
+ if (will_ignore_batch (self))
+ return;
+
+ batch = gsk_ngl_command_batches_tail (&self->batches);
+
+ g_assert (self->in_draw == TRUE);
+ g_assert (batch->any.kind == GSK_NGL_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_NGL_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.framebuffer == batch->draw.framebuffer &&
+ last_batch->draw.vbo_offset + last_batch->draw.vbo_count == batch->draw.vbo_offset &&
+ 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_ngl_command_queue_split_draw:
+ * @self a #GskNglCommandQueue
+ *
+ * This function is like calling gsk_ngl_command_queue_end_draw() followed by
+ * a gsk_ngl_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_ngl_program_split_draw()
+ * as that is where the begin/end flow happens from the render job.
+ */
+void
+gsk_ngl_command_queue_split_draw (GskNglCommandQueue *self)
+{
+ GskNglCommandBatch *batch;
+ GskNglUniformProgram *program;
+ guint width;
+ guint height;
+
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
+ g_assert (self->batches.len > 0);
+ g_assert (self->in_draw == TRUE);
+
+ program = self->program_info;
+
+ batch = gsk_ngl_command_batches_tail (&self->batches);
+
+ g_assert (batch->any.kind == GSK_NGL_COMMAND_KIND_DRAW);
+
+ width = batch->any.viewport.width;
+ height = batch->any.viewport.height;
+
+ gsk_ngl_command_queue_end_draw (self);
+ gsk_ngl_command_queue_begin_draw (self, program, width, height);
+}
+
+void
+gsk_ngl_command_queue_clear (GskNglCommandQueue *self,
+ guint clear_bits,
+ const graphene_rect_t *viewport)
+{
+ GskNglCommandBatch *batch;
+
+ g_assert (GSK_IS_NGL_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_NGL_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_ngl_command_queue_get_context (GskNglCommandQueue *self)
+{
+ g_return_val_if_fail (GSK_IS_NGL_COMMAND_QUEUE (self), NULL);
+
+ return self->context;
+}
+
+void
+gsk_ngl_command_queue_make_current (GskNglCommandQueue *self)
+{
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
+ g_assert (GDK_IS_GL_CONTEXT (self->context));
+
+ gdk_gl_context_make_current (self->context);
+}
+
+void
+gsk_ngl_command_queue_delete_program (GskNglCommandQueue *self,
+ guint program)
+{
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
+
+ glDeleteProgram (program);
+}
+
+static inline void
+apply_uniform (gconstpointer dataptr,
+ GskNglUniformInfo info,
+ guint location)
+{
+ g_assert (dataptr != NULL);
+ g_assert (info.format > 0);
+ g_assert (location < GL_MAX_UNIFORM_LOCATIONS);
+
+ switch (info.format)
+ {
+ case GSK_NGL_UNIFORM_FORMAT_1F:
+ glUniform1fv (location, 1, dataptr);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_2F:
+ glUniform2fv (location, 1, dataptr);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_3F:
+ glUniform3fv (location, 1, dataptr);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_4F:
+ glUniform4fv (location, 1, dataptr);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_1FV:
+ glUniform1fv (location, info.array_count, dataptr);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_2FV:
+ glUniform2fv (location, info.array_count, dataptr);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_3FV:
+ glUniform3fv (location, info.array_count, dataptr);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_4FV:
+ glUniform4fv (location, info.array_count, dataptr);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_1I:
+ case GSK_NGL_UNIFORM_FORMAT_TEXTURE:
+ glUniform1iv (location, 1, dataptr);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_2I:
+ glUniform2iv (location, 1, dataptr);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_3I:
+ glUniform3iv (location, 1, dataptr);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_4I:
+ glUniform4iv (location, 1, dataptr);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_1UI:
+ glUniform1uiv (location, 1, dataptr);
+ break;
+
+ case GSK_NGL_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_NGL_UNIFORM_FORMAT_COLOR:
+ glUniform4fv (location, 1, dataptr);
+ break;
+
+ case GSK_NGL_UNIFORM_FORMAT_ROUNDED_RECT:
+ glUniform4fv (location, 3, dataptr);
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+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)
+{
+ g_assert (framebuffer != (guint)-1);
+
+ if (framebuffer != 0 || !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_ngl_command_queue_unlink (GskNglCommandQueue *self,
+ GskNglCommandBatch *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_ngl_command_queue_insert_before (GskNglCommandQueue *self,
+ GskNglCommandBatch *batch,
+ GskNglCommandBatch *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_ngl_command_batches_index_of (&self->batches, batch);
+ sibling_index = gsk_ngl_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_ngl_command_queue_sort_batches (GskNglCommandQueue *self)
+{
+ int *seen;
+ int *seen_free = NULL;
+ int index;
+
+ g_assert (GSK_IS_NGL_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)
+ {
+ GskNglCommandBatch *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_NGL_COMMAND_KIND_DRAW:
+ fbo = batch->draw.framebuffer;
+ break;
+
+ case GSK_NGL_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];
+ GskNglCommandBatch *mru = &self->batches.items[mru_index];
+
+ g_assert (mru_index > -1);
+
+ gsk_ngl_command_queue_unlink (self, batch);
+
+ g_assert (batch->any.prev_batch_index == -1);
+ g_assert (batch->any.next_batch_index == -1);
+
+ gsk_ngl_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_ngl_command_queue_execute:
+ * @self: a #GskNglCommandQueue
+ * @surface_height: the height of the backing surface
+ * @scale_factor: the scale factor of the backing surface
+ * #scissor: (nullable): the scissor clip if any
+ *
+ * Executes all of the batches in the command queue.
+ */
+void
+gsk_ngl_command_queue_execute (GskNglCommandQueue *self,
+ guint surface_height,
+ guint scale_factor,
+ const cairo_region_t *scissor)
+{
+ G_GNUC_UNUSED guint count = 0;
+ graphene_rect_t scissor_test;
+ gboolean has_scissor = scissor != NULL;
+ gboolean scissor_state = -1;
+ guint program = 0;
+ guint width = 0;
+ guint height = 0;
+ guint n_binds = 0;
+ guint n_fbos = 0;
+ guint n_uniforms = 0;
+ guint vao_id;
+ guint vbo_id;
+ int textures[4];
+ int framebuffer = -1;
+ int next_batch_index;
+ int active = -1;
+
+ g_assert (GSK_IS_NGL_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;
+
+ gsk_ngl_command_queue_sort_batches (self);
+
+ gsk_ngl_command_queue_make_current (self);
+
+#ifdef G_ENABLE_DEBUG
+ gsk_gl_profiler_begin_gpu_region (self->gl_profiler);
+ gsk_profiler_timer_begin (self->profiler, self->metrics.cpu_time);
+#endif
+
+ glEnable (GL_DEPTH_TEST);
+ glDepthFunc (GL_LEQUAL);
+
+ /* Pre-multiplied alpha */
+ glEnable (GL_BLEND);
+ glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
+ glBlendEquation (GL_FUNC_ADD);
+
+ glGenVertexArrays (1, &vao_id);
+ glBindVertexArray (vao_id);
+
+ vbo_id = gsk_ngl_buffer_submit (&self->vertices);
+
+ /* 0 = position location */
+ glEnableVertexAttribArray (0);
+ glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE,
+ sizeof (GskNglDrawVertex),
+ (void *) G_STRUCT_OFFSET (GskNglDrawVertex, position));
+
+ /* 1 = texture coord location */
+ glEnableVertexAttribArray (1);
+ glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE,
+ sizeof (GskNglDrawVertex),
+ (void *) G_STRUCT_OFFSET (GskNglDrawVertex, uv));
+
+ /* Setup initial scissor clip */
+ if (scissor != NULL)
+ {
+ cairo_rectangle_int_t r;
+
+ g_assert (cairo_region_num_rectangles (scissor) == 1);
+ cairo_region_get_rectangle (scissor, 0, &r);
+
+ scissor_test.origin.x = r.x * scale_factor;
+ scissor_test.origin.y = surface_height - (r.height * scale_factor) - (r.y * scale_factor);
+ scissor_test.size.width = r.width * scale_factor;
+ scissor_test.size.height = r.height * scale_factor;
+ }
+
+ next_batch_index = self->head_batch_index;
+
+ while (next_batch_index >= 0)
+ {
+ const GskNglCommandBatch *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_NGL_COMMAND_KIND_CLEAR:
+ if (apply_framebuffer (&framebuffer, batch->clear.framebuffer))
+ {
+ apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor);
+ 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_NGL_COMMAND_KIND_DRAW:
+ if (batch->any.program != program)
+ {
+ program = batch->any.program;
+ glUseProgram (program);
+ }
+
+ if (apply_framebuffer (&framebuffer, batch->draw.framebuffer))
+ {
+ apply_scissor (&scissor_state, framebuffer, &scissor_test, has_scissor);
+ n_fbos++;
+ }
+
+ apply_viewport (&width,
+ &height,
+ batch->any.viewport.width,
+ batch->any.viewport.height);
+
+ if G_UNLIKELY (batch->draw.bind_count > 0)
+ {
+ const GskNglCommandBind *bind = &self->batch_binds.items[batch->draw.bind_offset];
+
+ for (guint i = 0; i < batch->draw.bind_count; i++)
+ {
+ if (textures[bind->texture] != bind->id)
+ {
+ if (active != bind->texture)
+ {
+ active = bind->texture;
+ glActiveTexture (GL_TEXTURE0 + bind->texture);
+ }
+
+ glBindTexture (GL_TEXTURE_2D, bind->id);
+ textures[bind->texture] = bind->id;
+ }
+
+ bind++;
+ }
+
+ n_binds += batch->draw.bind_count;
+ }
+
+ if (batch->draw.uniform_count > 0)
+ {
+ const GskNglCommandUniform *u = &self->batch_uniforms.items[batch->draw.uniform_offset];
+
+ for (guint i = 0; i < batch->draw.uniform_count; i++, u++)
+ apply_uniform (gsk_ngl_uniform_state_get_uniform_data (self->uniforms, u->info.offset),
+ u->info, u->location);
+
+ n_uniforms += batch->draw.uniform_count;
+ }
+
+ glDrawArrays (GL_TRIANGLES, batch->draw.vbo_offset, batch->draw.vbo_count);
+
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+
+#if 0
+ if (batch->any.kind == GSK_NGL_COMMAND_KIND_DRAW ||
+ batch->any.kind == GSK_NGL_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_NGL_COMMAND_KIND_DRAW ? batch->draw.uniform_count : 0,
+ batch->any.kind == GSK_NGL_COMMAND_KIND_DRAW ? batch->draw.bind_count : 0,
+ framebuffer,
+ gdk_gl_context_get_current ());
+ gsk_ngl_command_queue_capture_png (self, filename, width, height, TRUE);
+ gsk_ngl_command_queue_print_batch (self, batch);
+ }
+#endif
+
+ next_batch_index = batch->any.next_batch_index;
+ }
+
+ glDeleteBuffers (1, &vbo_id);
+ 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_uploads, self->n_uploads);
+ gdk_profiler_set_int_counter (self->metrics.queue_depth, self->batches.len);
+
+#ifdef G_ENABLE_DEBUG
+ {
+ 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);
+ }
+#endif
+}
+
+void
+gsk_ngl_command_queue_begin_frame (GskNglCommandQueue *self)
+{
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
+ g_assert (self->batches.len == 0);
+
+ gsk_ngl_command_queue_make_current (self);
+
+ self->fbo_max = 0;
+ self->tail_batch_index = -1;
+ self->head_batch_index = -1;
+ self->in_frame = TRUE;
+}
+
+/**
+ * gsk_ngl_command_queue_end_frame:
+ * @self: a #GskNglCommandQueue
+ *
+ * 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_ngl_command_queue_end_frame (GskNglCommandQueue *self)
+{
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
+
+ gsk_ngl_command_queue_make_current (self);
+ gsk_ngl_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 (GL_TEXTURE_2D, 0);
+
+ self->attachments->textures[i].id = 0;
+ self->attachments->textures[i].changed = FALSE;
+ self->attachments->textures[i].initial = TRUE;
+ }
+ }
+
+ g_string_chunk_clear (self->debug_groups);
+
+ self->batches.len = 0;
+ self->batch_binds.len = 0;
+ self->batch_uniforms.len = 0;
+ self->n_uploads = 0;
+ self->tail_batch_index = -1;
+ self->in_frame = FALSE;
+}
+
+gboolean
+gsk_ngl_command_queue_create_render_target (GskNglCommandQueue *self,
+ int width,
+ int height,
+ int min_filter,
+ int mag_filter,
+ guint *out_fbo_id,
+ guint *out_texture_id)
+{
+ GLuint fbo_id = 0;
+ GLint texture_id;
+
+ g_assert (GSK_IS_NGL_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_ngl_command_queue_create_texture (self,
+ width, height,
+ min_filter, mag_filter);
+
+ if (texture_id == -1)
+ {
+ *out_fbo_id = 0;
+ *out_texture_id = 0;
+ return FALSE;
+ }
+
+ fbo_id = gsk_ngl_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_ngl_command_queue_create_texture (GskNglCommandQueue *self,
+ int width,
+ int height,
+ int min_filter,
+ int mag_filter)
+{
+ GLuint texture_id = 0;
+
+ g_assert (GSK_IS_NGL_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, min_filter);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ if (gdk_gl_context_get_use_es (self->context))
+ glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ else
+ glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+
+ /* Restore the previous texture if it was set */
+ if (self->attachments->textures[0].id != 0)
+ glBindTexture (GL_TEXTURE_2D, self->attachments->textures[0].id);
+
+ return (int)texture_id;
+}
+
+guint
+gsk_ngl_command_queue_create_framebuffer (GskNglCommandQueue *self)
+{
+ GLuint fbo_id;
+
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
+
+ glGenFramebuffers (1, &fbo_id);
+
+ return fbo_id;
+}
+
+int
+gsk_ngl_command_queue_upload_texture (GskNglCommandQueue *self,
+ GdkTexture *texture,
+ guint x_offset,
+ guint y_offset,
+ guint width,
+ guint height,
+ int min_filter,
+ int mag_filter)
+{
+ G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
+ cairo_surface_t *surface = NULL;
+ GdkMemoryFormat data_format;
+ const guchar *data;
+ gsize data_stride;
+ gsize bpp;
+ int texture_id;
+
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (self));
+ g_assert (!GDK_IS_GL_TEXTURE (texture));
+ g_assert (x_offset + width <= gdk_texture_get_width (texture));
+ g_assert (y_offset + height <= gdk_texture_get_height (texture));
+ g_assert (min_filter == GL_LINEAR || min_filter == GL_NEAREST);
+ g_assert (mag_filter == GL_LINEAR || min_filter == GL_NEAREST);
+
+ 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 = MAX (width, self->max_texture_size);
+ height = MAX (height, self->max_texture_size);
+ }
+
+ texture_id = gsk_ngl_command_queue_create_texture (self, width, height, min_filter, mag_filter);
+ if (texture_id == -1)
+ return texture_id;
+
+ if (GDK_IS_MEMORY_TEXTURE (texture))
+ {
+ GdkMemoryTexture *memory_texture = GDK_MEMORY_TEXTURE (texture);
+ data = gdk_memory_texture_get_data (memory_texture);
+ data_format = gdk_memory_texture_get_format (memory_texture);
+ data_stride = gdk_memory_texture_get_stride (memory_texture);
+ }
+ else
+ {
+ /* Fall back to downloading to a surface */
+ surface = gdk_texture_download_surface (texture);
+ cairo_surface_flush (surface);
+ data = cairo_image_surface_get_data (surface);
+ data_format = GDK_MEMORY_DEFAULT;
+ data_stride = cairo_image_surface_get_stride (surface);
+ }
+
+ self->n_uploads++;
+
+ bpp = gdk_memory_format_bytes_per_pixel (data_format);
+
+ /* Swtich to texture0 as 2D. We'll restore it later. */
+ glActiveTexture (GL_TEXTURE0);
+ glBindTexture (GL_TEXTURE_2D, texture_id);
+
+ gdk_gl_context_upload_texture (gdk_gl_context_get_current (),
+ data + x_offset * bpp + y_offset * data_stride,
+ width, height, data_stride,
+ data_format, GL_TEXTURE_2D);
+
+ /* Restore previous texture state if any */
+ if (self->attachments->textures[0].id > 0)
+ glBindTexture (self->attachments->textures[0].target,
+ self->attachments->textures[0].id);
+
+ g_clear_pointer (&surface, cairo_surface_destroy);
+
+ 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;
+}
+
+void
+gsk_ngl_command_queue_set_profiler (GskNglCommandQueue *self,
+ GskProfiler *profiler)
+{
+#ifdef G_ENABLE_DEBUG
+ g_assert (GSK_IS_NGL_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.queue_depth = gdk_profiler_define_int_counter ("gl-queue-depth", "Depth of GL command batches");
+ }
+#endif
+}
diff --git a/gsk/ngl/gsknglcommandqueueprivate.h b/gsk/ngl/gsknglcommandqueueprivate.h
new file mode 100644
index 0000000000..9a35326cee
--- /dev/null
+++ b/gsk/ngl/gsknglcommandqueueprivate.h
@@ -0,0 +1,362 @@
+/* gsknglcommandqueueprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_COMMAND_QUEUE_PRIVATE_H__
+#define __GSK_NGL_COMMAND_QUEUE_PRIVATE_H__
+
+#include <gsk/gskprofilerprivate.h>
+
+#include "gskngltypesprivate.h"
+#include "gsknglbufferprivate.h"
+#include "gsknglattachmentstateprivate.h"
+#include "gskngluniformstateprivate.h"
+
+#include "inlinearray.h"
+
+#include "../gl/gskglprofilerprivate.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_COMMAND_QUEUE (gsk_ngl_command_queue_get_type())
+
+G_DECLARE_FINAL_TYPE (GskNglCommandQueue, gsk_ngl_command_queue, GSK, NGL_COMMAND_QUEUE, GObject)
+
+typedef enum _GskNglCommandKind
+{
+ /* The batch will perform a glClear() */
+ GSK_NGL_COMMAND_KIND_CLEAR,
+
+ /* The batch will perform a glDrawArrays() */
+ GSK_NGL_COMMAND_KIND_DRAW,
+} GskNglCommandKind;
+
+typedef struct _GskNglCommandBind
+{
+ /* @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 : 5;
+
+ /* The identifier for the texture created with glGenTextures(). */
+ guint id : 27;
+} GskNglCommandBind;
+
+G_STATIC_ASSERT (sizeof (GskNglCommandBind) == 4);
+
+typedef struct _GskNglCommandBatchAny
+{
+ /* A GskNglCommandKind 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 GskNglCommandDraw 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;
+} GskNglCommandBatchAny;
+
+G_STATIC_ASSERT (sizeof (GskNglCommandBatchAny) == 12);
+
+typedef struct _GskNglCommandDraw
+{
+ GskNglCommandBatchAny head;
+
+ /* 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;
+
+ /* 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 #GskNglCommandUniform elements to apply.
+ */
+ guint uniform_offset;
+
+ /* The offset within the array of bind changes to be made containing
+ * @bind_count #GskNglCommandBind elements to apply.
+ */
+ guint bind_offset;
+} GskNglCommandDraw;
+
+G_STATIC_ASSERT (sizeof (GskNglCommandDraw) == 32);
+
+typedef struct _GskNglCommandClear
+{
+ GskNglCommandBatchAny any;
+ guint bits;
+ guint framebuffer;
+} GskNglCommandClear;
+
+G_STATIC_ASSERT (sizeof (GskNglCommandClear) == 20);
+
+typedef struct _GskNglCommandUniform
+{
+ GskNglUniformInfo info;
+ guint location;
+} GskNglCommandUniform;
+
+G_STATIC_ASSERT (sizeof (GskNglCommandUniform) == 8);
+
+typedef union _GskNglCommandBatch
+{
+ GskNglCommandBatchAny any;
+ GskNglCommandDraw draw;
+ GskNglCommandClear clear;
+} GskNglCommandBatch;
+
+G_STATIC_ASSERT (sizeof (GskNglCommandBatch) == 32);
+
+DEFINE_INLINE_ARRAY (GskNglCommandBatches, gsk_ngl_command_batches, GskNglCommandBatch)
+DEFINE_INLINE_ARRAY (GskNglCommandBinds, gsk_ngl_command_binds, GskNglCommandBind)
+DEFINE_INLINE_ARRAY (GskNglCommandUniforms, gsk_ngl_command_uniforms, GskNglCommandUniform)
+
+struct _GskNglCommandQueue
+{
+ GObject parent_instance;
+
+ /* The GdkGLContext we make current before executing GL commands. */
+ GdkGLContext *context;
+
+ /* Array of GskNglCommandBatch 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.
+ */
+ GskNglCommandBatches 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.
+ */
+ GskNglBuffer vertices;
+
+ /* The GskNglAttachmentState 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.
+ */
+ GskNglAttachmentState *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.
+ */
+ GskNglUniformState *uniforms;
+
+ /* Current program if we are in a draw so that we can send commands
+ * to the uniform state as needed.
+ */
+ GskNglUniformProgram *program_info;
+
+ /* The profiler instance to deliver timing/etc data */
+ GskProfiler *profiler;
+ GskGLProfiler *gl_profiler;
+
+ /* Array of GskNglCommandBind which denote what textures need to be attached
+ * to which slot. GskNglCommandDraw.bind_offset and bind_count reference this
+ * array to determine what to attach.
+ */
+ GskNglCommandBinds batch_binds;
+
+ /* Array of GskNglCommandUniform denoting which uniforms must be updated
+ * before the glDrawArrays() may be called. These are referenced from the
+ * GskNglCommandDraw.uniform_offset and uniform_count fields.
+ */
+ GskNglCommandUniforms batch_uniforms;
+
+ /* String storage for debug groups */
+ GStringChunk *debug_groups;
+
+ /* 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 queue_depth;
+ } metrics;
+
+ /* Counter for uploads on the frame */
+ guint n_uploads;
+
+ /* 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;
+};
+
+GskNglCommandQueue *gsk_ngl_command_queue_new (GdkGLContext *context,
+ GskNglUniformState *uniforms);
+void gsk_ngl_command_queue_set_profiler (GskNglCommandQueue *self,
+ GskProfiler *profiler);
+GdkGLContext *gsk_ngl_command_queue_get_context (GskNglCommandQueue *self);
+void gsk_ngl_command_queue_make_current (GskNglCommandQueue *self);
+void gsk_ngl_command_queue_begin_frame (GskNglCommandQueue *self);
+void gsk_ngl_command_queue_end_frame (GskNglCommandQueue *self);
+void gsk_ngl_command_queue_execute (GskNglCommandQueue *self,
+ guint surface_height,
+ guint scale_factor,
+ const cairo_region_t *scissor);
+int gsk_ngl_command_queue_upload_texture (GskNglCommandQueue *self,
+ GdkTexture *texture,
+ guint x_offset,
+ guint y_offset,
+ guint width,
+ guint height,
+ int min_filter,
+ int mag_filter);
+int gsk_ngl_command_queue_create_texture (GskNglCommandQueue *self,
+ int width,
+ int height,
+ int min_filter,
+ int mag_filter);
+guint gsk_ngl_command_queue_create_framebuffer (GskNglCommandQueue *self);
+gboolean gsk_ngl_command_queue_create_render_target (GskNglCommandQueue *self,
+ int width,
+ int height,
+ int min_filter,
+ int mag_filter,
+ guint *out_fbo_id,
+ guint *out_texture_id);
+void gsk_ngl_command_queue_delete_program (GskNglCommandQueue *self,
+ guint program_id);
+void gsk_ngl_command_queue_clear (GskNglCommandQueue *self,
+ guint clear_bits,
+ const graphene_rect_t *viewport);
+void gsk_ngl_command_queue_begin_draw (GskNglCommandQueue *self,
+ GskNglUniformProgram *program_info,
+ guint width,
+ guint height);
+void gsk_ngl_command_queue_end_draw (GskNglCommandQueue *self);
+void gsk_ngl_command_queue_split_draw (GskNglCommandQueue *self);
+
+static inline GskNglCommandBatch *
+gsk_ngl_command_queue_get_batch (GskNglCommandQueue *self)
+{
+ return gsk_ngl_command_batches_tail (&self->batches);
+}
+
+static inline GskNglDrawVertex *
+gsk_ngl_command_queue_add_vertices (GskNglCommandQueue *self)
+{
+ gsk_ngl_command_queue_get_batch (self)->draw.vbo_count += GSK_NGL_N_VERTICES;
+ return gsk_ngl_buffer_advance (&self->vertices, GSK_NGL_N_VERTICES);
+}
+
+static inline GskNglDrawVertex *
+gsk_ngl_command_queue_add_n_vertices (GskNglCommandQueue *self,
+ guint count)
+{
+ /* This is a batch form of gsk_ngl_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_ngl_buffer_advance (&self->vertices, GSK_NGL_N_VERTICES * count);
+}
+
+static inline void
+gsk_ngl_command_queue_retract_n_vertices (GskNglCommandQueue *self,
+ guint count)
+{
+ /* Like gsk_ngl_command_queue_add_n_vertices(), this does not tweak
+ * the draw vbo_count.
+ */
+ gsk_ngl_buffer_retract (&self->vertices, GSK_NGL_N_VERTICES * count);
+}
+
+static inline guint
+gsk_ngl_command_queue_bind_framebuffer (GskNglCommandQueue *self,
+ guint framebuffer)
+{
+ guint ret = self->attachments->fbo.id;
+ gsk_ngl_attachment_state_bind_framebuffer (self->attachments, framebuffer);
+ return ret;
+}
+
+G_END_DECLS
+
+#endif /* __GSK_NGL_COMMAND_QUEUE_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglcompiler.c b/gsk/ngl/gsknglcompiler.c
new file mode 100644
index 0000000000..c033775c48
--- /dev/null
+++ b/gsk/ngl/gsknglcompiler.c
@@ -0,0 +1,678 @@
+/* gsknglcompiler.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gsk/gskdebugprivate.h>
+#include <gio/gio.h>
+#include <string.h>
+
+#include "gsknglcommandqueueprivate.h"
+#include "gsknglcompilerprivate.h"
+#include "gsknglprogramprivate.h"
+
+#define SHADER_VERSION_GLES 100
+#define SHADER_VERSION_GL2_LEGACY 110
+#define SHADER_VERSION_GL3_LEGACY 130
+#define SHADER_VERSION_GL3 150
+
+struct _GskNglCompiler
+{
+ GObject parent_instance;
+
+ GskNglDriver *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;
+
+ int glsl_version;
+
+ guint gl3 : 1;
+ guint gles : 1;
+ guint legacy : 1;
+ guint debug_shaders : 1;
+};
+
+typedef struct _GskNglProgramAttrib
+{
+ const char *name;
+ guint location;
+} GskNglProgramAttrib;
+
+static GBytes *empty_bytes;
+
+G_DEFINE_TYPE (GskNglCompiler, gsk_ngl_compiler, G_TYPE_OBJECT)
+
+static void
+gsk_ngl_compiler_finalize (GObject *object)
+{
+ GskNglCompiler *self = (GskNglCompiler *)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_ngl_compiler_parent_class)->finalize (object);
+}
+
+static void
+gsk_ngl_compiler_class_init (GskNglCompilerClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsk_ngl_compiler_finalize;
+
+ empty_bytes = g_bytes_new (NULL, 0);
+}
+
+static void
+gsk_ngl_compiler_init (GskNglCompiler *self)
+{
+ self->glsl_version = 150;
+ self->attrib_locations = g_array_new (FALSE, FALSE, sizeof (GskNglProgramAttrib));
+ 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);
+}
+
+GskNglCompiler *
+gsk_ngl_compiler_new (GskNglDriver *driver,
+ gboolean debug_shaders)
+{
+ GskNglCompiler *self;
+ GdkGLContext *context;
+
+ g_return_val_if_fail (GSK_IS_NGL_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_ngl_command_queue_get_context (self->driver->shared_command_queue);
+
+ if (gdk_gl_context_get_use_es (context))
+ {
+ self->glsl_version = SHADER_VERSION_GLES;
+ self->gles = TRUE;
+ }
+ else if (gdk_gl_context_is_legacy (context))
+ {
+ int maj, min;
+
+ gdk_gl_context_get_version (context, &maj, &min);
+
+ if (maj == 3)
+ self->glsl_version = SHADER_VERSION_GL3_LEGACY;
+ else
+ self->glsl_version = SHADER_VERSION_GL2_LEGACY;
+
+ self->legacy = TRUE;
+ }
+ else
+ {
+ self->glsl_version = SHADER_VERSION_GL3;
+ self->gl3 = TRUE;
+ }
+
+ gsk_ngl_command_queue_make_current (self->driver->shared_command_queue);
+
+ return g_steal_pointer (&self);
+}
+
+void
+gsk_ngl_compiler_bind_attribute (GskNglCompiler *self,
+ const char *name,
+ guint location)
+{
+ GskNglProgramAttrib attrib;
+
+ g_return_if_fail (GSK_IS_NGL_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_ngl_compiler_clear_attributes (GskNglCompiler *self)
+{
+ g_return_if_fail (GSK_IS_NGL_COMPILER (self));
+
+ g_array_set_size (self->attrib_locations, 0);
+}
+
+void
+gsk_ngl_compiler_set_preamble (GskNglCompiler *self,
+ GskNglCompilerKind kind,
+ GBytes *preamble_bytes)
+{
+ GBytes **loc = NULL;
+
+ g_return_if_fail (GSK_IS_NGL_COMPILER (self));
+ g_return_if_fail (preamble_bytes != NULL);
+
+ if (kind == GSK_NGL_COMPILER_ALL)
+ loc = &self->all_preamble;
+ else if (kind == GSK_NGL_COMPILER_FRAGMENT)
+ loc = &self->fragment_preamble;
+ else if (kind == GSK_NGL_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_ngl_compiler_set_preamble_from_resource (GskNglCompiler *self,
+ GskNglCompilerKind kind,
+ const char *resource_path)
+{
+ GError *error = NULL;
+ GBytes *bytes;
+
+ g_return_if_fail (GSK_IS_NGL_COMPILER (self));
+ g_return_if_fail (kind == GSK_NGL_COMPILER_ALL ||
+ kind == GSK_NGL_COMPILER_VERTEX ||
+ kind == GSK_NGL_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_ngl_compiler_set_preamble (self, kind, bytes);
+
+ g_clear_pointer (&bytes, g_bytes_unref);
+ g_clear_error (&error);
+}
+
+void
+gsk_ngl_compiler_set_source (GskNglCompiler *self,
+ GskNglCompilerKind kind,
+ GBytes *source_bytes)
+{
+ GBytes **loc = NULL;
+
+ g_return_if_fail (GSK_IS_NGL_COMPILER (self));
+ g_return_if_fail (kind == GSK_NGL_COMPILER_ALL ||
+ kind == GSK_NGL_COMPILER_VERTEX ||
+ kind == GSK_NGL_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_NGL_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_ngl_compiler_set_source (self, GSK_NGL_COMPILER_VERTEX, vertex_bytes);
+ gsk_ngl_compiler_set_source (self, GSK_NGL_COMPILER_FRAGMENT, fragment_bytes);
+
+ g_bytes_unref (fragment_bytes);
+ g_bytes_unref (vertex_bytes);
+
+ return;
+ }
+
+ if (kind == GSK_NGL_COMPILER_FRAGMENT)
+ loc = &self->fragment_source;
+ else if (kind == GSK_NGL_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_ngl_compiler_set_source_from_resource (GskNglCompiler *self,
+ GskNglCompilerKind kind,
+ const char *resource_path)
+{
+ GError *error = NULL;
+ GBytes *bytes;
+
+ g_return_if_fail (GSK_IS_NGL_COMPILER (self));
+ g_return_if_fail (kind == GSK_NGL_COMPILER_ALL ||
+ kind == GSK_NGL_COMPILER_VERTEX ||
+ kind == GSK_NGL_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_ngl_compiler_set_source (self, kind, bytes);
+
+ g_clear_pointer (&bytes, g_bytes_unref);
+ g_clear_error (&error);
+}
+
+void
+gsk_ngl_compiler_set_suffix (GskNglCompiler *self,
+ GskNglCompilerKind kind,
+ GBytes *suffix_bytes)
+{
+ GBytes **loc;
+
+ g_return_if_fail (GSK_IS_NGL_COMPILER (self));
+ g_return_if_fail (kind == GSK_NGL_COMPILER_VERTEX ||
+ kind == GSK_NGL_COMPILER_FRAGMENT);
+ g_return_if_fail (suffix_bytes != NULL);
+
+ if (suffix_bytes == NULL)
+ suffix_bytes = empty_bytes;
+
+ if (kind == GSK_NGL_COMPILER_FRAGMENT)
+ loc = &self->fragment_suffix;
+ else if (kind == GSK_NGL_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_ngl_compiler_set_suffix_from_resource (GskNglCompiler *self,
+ GskNglCompilerKind kind,
+ const char *resource_path)
+{
+ GError *error = NULL;
+ GBytes *bytes;
+
+ g_return_if_fail (GSK_IS_NGL_COMPILER (self));
+ g_return_if_fail (kind == GSK_NGL_COMPILER_VERTEX ||
+ kind == GSK_NGL_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_ngl_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 : "";
+}
+
+GskNglProgram *
+gsk_ngl_compiler_compile (GskNglCompiler *self,
+ const char *name,
+ GError **error)
+{
+ char version[32];
+ const char *debug = "";
+ const char *legacy = "";
+ const char *gl3 = "";
+ const char *gles = "";
+ int program_id;
+ int vertex_id;
+ int fragment_id;
+ int status;
+
+ g_return_val_if_fail (GSK_IS_NGL_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_ngl_command_queue_make_current (self->driver->command_queue);
+
+ g_snprintf (version, sizeof version, "#version %d\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_NGLES 1\n";
+
+ if (self->gl3)
+ gl3 = "#define GSK_NGL3 1\n";
+
+ vertex_id = glCreateShader (GL_VERTEX_SHADER);
+ glShaderSource (vertex_id,
+ 9,
+ (const char *[]) {
+ version, debug, legacy, gl3, gles,
+ 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),
+ 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,
+ 9,
+ (const char *[]) {
+ version, debug, legacy, gl3, gles,
+ 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),
+ 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 GskNglProgramAttrib *attrib;
+
+ attrib = &g_array_index (self->attrib_locations, GskNglProgramAttrib, 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_ngl_program_new (self->driver, name, program_id);
+}
diff --git a/gsk/ngl/gsknglcompilerprivate.h b/gsk/ngl/gsknglcompilerprivate.h
new file mode 100644
index 0000000000..d61dce2697
--- /dev/null
+++ b/gsk/ngl/gsknglcompilerprivate.h
@@ -0,0 +1,69 @@
+/* gsknglcompilerprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_COMPILER_PRIVATE_H__
+#define __GSK_NGL_COMPILER_PRIVATE_H__
+
+#include "gskngltypesprivate.h"
+
+G_BEGIN_DECLS
+
+typedef enum _GskNglCompilerKind
+{
+ GSK_NGL_COMPILER_ALL,
+ GSK_NGL_COMPILER_FRAGMENT,
+ GSK_NGL_COMPILER_VERTEX,
+} GskNglCompilerKind;
+
+#define GSK_TYPE_GL_COMPILER (gsk_ngl_compiler_get_type())
+
+G_DECLARE_FINAL_TYPE (GskNglCompiler, gsk_ngl_compiler, GSK, NGL_COMPILER, GObject)
+
+GskNglCompiler *gsk_ngl_compiler_new (GskNglDriver *driver,
+ gboolean debug);
+void gsk_ngl_compiler_set_preamble (GskNglCompiler *self,
+ GskNglCompilerKind kind,
+ GBytes *preamble_bytes);
+void gsk_ngl_compiler_set_preamble_from_resource (GskNglCompiler *self,
+ GskNglCompilerKind kind,
+ const char *resource_path);
+void gsk_ngl_compiler_set_source (GskNglCompiler *self,
+ GskNglCompilerKind kind,
+ GBytes *source_bytes);
+void gsk_ngl_compiler_set_source_from_resource (GskNglCompiler *self,
+ GskNglCompilerKind kind,
+ const char *resource_path);
+void gsk_ngl_compiler_set_suffix (GskNglCompiler *self,
+ GskNglCompilerKind kind,
+ GBytes *suffix_bytes);
+void gsk_ngl_compiler_set_suffix_from_resource (GskNglCompiler *self,
+ GskNglCompilerKind kind,
+ const char *resource_path);
+void gsk_ngl_compiler_bind_attribute (GskNglCompiler *self,
+ const char *name,
+ guint location);
+void gsk_ngl_compiler_clear_attributes (GskNglCompiler *self);
+GskNglProgram *gsk_ngl_compiler_compile (GskNglCompiler *self,
+ const char *name,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __GSK_NGL_COMPILER_PRIVATE_H__ */
diff --git a/gsk/ngl/gskngldriver.c b/gsk/ngl/gskngldriver.c
new file mode 100644
index 0000000000..a6720754be
--- /dev/null
+++ b/gsk/ngl/gskngldriver.c
@@ -0,0 +1,1296 @@
+/* gskngldriver.c
+ *
+ * Copyright 2017 Timm Bäder <mail@baedert.org>
+ * Copyright 2018 Matthias Clasen <mclasen@redhat.com>
+ * Copyright 2018 Alexander Larsson <alexl@redhat.com>
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdkglcontextprivate.h>
+#include <gdk/gdktextureprivate.h>
+#include <gsk/gskdebugprivate.h>
+#include <gsk/gskglshaderprivate.h>
+#include <gsk/gskrendererprivate.h>
+
+#include "gsknglcommandqueueprivate.h"
+#include "gsknglcompilerprivate.h"
+#include "gskngldriverprivate.h"
+#include "gsknglglyphlibraryprivate.h"
+#include "gskngliconlibraryprivate.h"
+#include "gsknglprogramprivate.h"
+#include "gsknglshadowlibraryprivate.h"
+#include "gskngltexturepoolprivate.h"
+
+#define ATLAS_SIZE 512
+
+typedef struct _GskNglTextureState
+{
+ GdkGLContext *context;
+ GLuint texture_id;
+} GskNglTextureState;
+
+G_DEFINE_TYPE (GskNglDriver, gsk_ngl_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 << 6) |
+ (k->filter << 1) |
+ 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->filter == k2->filter &&
+ 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 (GskNglDriver *self,
+ guint texture_id)
+{
+ GskTextureKey *key;
+
+ g_assert (GSK_IS_NGL_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_ngl_texture_destroyed (gpointer data)
+{
+ ((GskNglTexture *)data)->user = NULL;
+}
+
+static guint
+gsk_ngl_driver_collect_unused_textures (GskNglDriver *self,
+ gint64 watermark)
+{
+ GHashTableIter iter;
+ gpointer k, v;
+ guint old_size;
+ guint collected;
+
+ g_assert (GSK_IS_NGL_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))
+ {
+ GskNglTexture *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);
+
+ /* Steal this texture and put it back into the pool */
+ remove_texture_key_for_id (self, t->texture_id);
+ gsk_ngl_texture_pool_put (&self->texture_pool, t);
+ }
+ }
+
+ collected = old_size - g_hash_table_size (self->textures);
+
+ return collected;
+}
+
+static void
+gsk_ngl_texture_atlas_free (GskNglTextureAtlas *atlas)
+{
+ if (atlas->texture_id != 0)
+ {
+ glDeleteTextures (1, &atlas->texture_id);
+ atlas->texture_id = 0;
+ }
+
+ g_clear_pointer (&atlas->nodes, g_free);
+ g_slice_free (GskNglTextureAtlas, atlas);
+}
+
+GskNglTextureAtlas *
+gsk_ngl_driver_create_atlas (GskNglDriver *self)
+{
+ GskNglTextureAtlas *atlas;
+
+ g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), NULL);
+
+ atlas = g_slice_new0 (GskNglTextureAtlas);
+ atlas->width = ATLAS_SIZE;
+ atlas->height = ATLAS_SIZE;
+ /* 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_ngl_command_queue_create_texture (self->command_queue,
+ atlas->width,
+ atlas->height,
+ GL_LINEAR,
+ GL_LINEAR);
+
+ 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);
+
+ return atlas;
+}
+
+static void
+remove_program (gpointer data)
+{
+ GskNglProgram *program = data;
+
+ g_assert (!program || GSK_IS_NGL_PROGRAM (program));
+
+ if (program != NULL)
+ {
+ gsk_ngl_program_delete (program);
+ g_object_unref (program);
+ }
+}
+
+static void
+gsk_ngl_driver_shader_weak_cb (gpointer data,
+ GObject *where_object_was)
+{
+ GskNglDriver *self = data;
+
+ g_assert (GSK_IS_NGL_DRIVER (self));
+
+ if (self->shader_cache != NULL)
+ g_hash_table_remove (self->shader_cache, where_object_was);
+}
+
+static void
+gsk_ngl_driver_dispose (GObject *object)
+{
+ GskNglDriver *self = (GskNglDriver *)object;
+
+ g_assert (GSK_IS_NGL_DRIVER (self));
+ g_assert (self->in_frame == FALSE);
+
+#define GSK_NGL_NO_UNIFORMS
+#define GSK_NGL_ADD_UNIFORM(pos, KEY, name)
+#define GSK_NGL_DEFINE_PROGRAM(name, resource, uniforms) \
+ G_STMT_START { \
+ if (self->name) \
+ gsk_ngl_program_delete (self->name); \
+ g_clear_object (&self->name); \
+ } G_STMT_END;
+# include "gsknglprograms.defs"
+#undef GSK_NGL_NO_UNIFORMS
+#undef GSK_NGL_ADD_UNIFORM
+#undef GSK_NGL_DEFINE_PROGRAM
+
+ 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_ngl_driver_shader_weak_cb,
+ self);
+ g_hash_table_iter_remove (&iter);
+ }
+
+ g_clear_pointer (&self->shader_cache, g_hash_table_unref);
+ }
+
+ if (self->command_queue != NULL)
+ {
+ gsk_ngl_command_queue_make_current (self->command_queue);
+ gsk_ngl_driver_collect_unused_textures (self, 0);
+ g_clear_object (&self->command_queue);
+ }
+
+ if (self->autorelease_framebuffers->len > 0)
+ {
+ glDeleteFramebuffers (self->autorelease_framebuffers->len,
+ (GLuint *)(gpointer)self->autorelease_framebuffers->data);
+ self->autorelease_framebuffers->len = 0;
+ }
+
+ gsk_ngl_texture_pool_clear (&self->texture_pool);
+
+ g_assert (!self->textures || g_hash_table_size (self->textures) == 0);
+ g_assert (!self->texture_id_to_key || g_hash_table_size (self->texture_id_to_key) == 0);
+ g_assert (!self->key_to_texture_id|| g_hash_table_size (self->key_to_texture_id) == 0);
+
+ g_clear_object (&self->glyphs);
+ g_clear_object (&self->icons);
+ g_clear_object (&self->shadows);
+
+ g_clear_pointer (&self->atlases, g_ptr_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_ngl_driver_parent_class)->dispose (object);
+}
+
+static void
+gsk_ngl_driver_class_init (GskNglDriverClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gsk_ngl_driver_dispose;
+}
+
+static void
+gsk_ngl_driver_init (GskNglDriver *self)
+{
+ self->autorelease_framebuffers = g_array_new (FALSE, FALSE, sizeof (guint));
+ self->textures = g_hash_table_new_full (NULL, NULL, NULL,
+ (GDestroyNotify)gsk_ngl_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);
+ gsk_ngl_texture_pool_init (&self->texture_pool);
+ self->render_targets = g_ptr_array_new ();
+ self->atlases = g_ptr_array_new_with_free_func ((GDestroyNotify)gsk_ngl_texture_atlas_free);
+}
+
+static gboolean
+gsk_ngl_driver_load_programs (GskNglDriver *self,
+ GError **error)
+{
+ GskNglCompiler *compiler;
+ gboolean ret = FALSE;
+
+ g_assert (GSK_IS_NGL_DRIVER (self));
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (self->command_queue));
+
+ compiler = gsk_ngl_compiler_new (self, self->debug);
+
+ /* Setup preambles that are shared by all shaders */
+ gsk_ngl_compiler_set_preamble_from_resource (compiler,
+ GSK_NGL_COMPILER_ALL,
+ "/org/gtk/libgsk/glsl/preamble.glsl");
+ gsk_ngl_compiler_set_preamble_from_resource (compiler,
+ GSK_NGL_COMPILER_VERTEX,
+ "/org/gtk/libgsk/glsl/preamble.vs.glsl");
+ gsk_ngl_compiler_set_preamble_from_resource (compiler,
+ GSK_NGL_COMPILER_FRAGMENT,
+ "/org/gtk/libgsk/glsl/preamble.fs.glsl");
+
+ /* Setup attributes that are provided via VBO */
+ gsk_ngl_compiler_bind_attribute (compiler, "aPosition", 0);
+ gsk_ngl_compiler_bind_attribute (compiler, "aUv", 1);
+
+ /* Use XMacros to register all of our programs and their uniforms */
+#define GSK_NGL_NO_UNIFORMS
+#define GSK_NGL_ADD_UNIFORM(pos, KEY, name) \
+ gsk_ngl_program_add_uniform (program, #name, UNIFORM_##KEY);
+#define GSK_NGL_DEFINE_PROGRAM(name, resource, uniforms) \
+ G_STMT_START { \
+ GskNglProgram *program; \
+ gboolean have_alpha; \
+ gboolean have_source; \
+ \
+ gsk_ngl_compiler_set_source_from_resource (compiler, GSK_NGL_COMPILER_ALL, resource); \
+ \
+ if (!(program = gsk_ngl_compiler_compile (compiler, #name, error))) \
+ goto failure; \
+ \
+ have_alpha = gsk_ngl_program_add_uniform (program, "u_alpha", UNIFORM_SHARED_ALPHA); \
+ have_source = gsk_ngl_program_add_uniform (program, "u_source", UNIFORM_SHARED_SOURCE); \
+ gsk_ngl_program_add_uniform (program, "u_clip_rect", UNIFORM_SHARED_CLIP_RECT); \
+ gsk_ngl_program_add_uniform (program, "u_viewport", UNIFORM_SHARED_VIEWPORT); \
+ gsk_ngl_program_add_uniform (program, "u_projection", UNIFORM_SHARED_PROJECTION); \
+ gsk_ngl_program_add_uniform (program, "u_modelview", UNIFORM_SHARED_MODELVIEW); \
+ \
+ uniforms \
+ \
+ gsk_ngl_program_uniforms_added (program, have_source); \
+ \
+ if (have_alpha) \
+ gsk_ngl_program_set_uniform1f (program, UNIFORM_SHARED_ALPHA, 0, 1.0f); \
+ \
+ *(GskNglProgram **)(((guint8 *)self) + G_STRUCT_OFFSET (GskNglDriver, name)) = \
+ g_steal_pointer (&program); \
+ } G_STMT_END;
+# include "gsknglprograms.defs"
+#undef GSK_NGL_DEFINE_PROGRAM
+#undef GSK_NGL_ADD_UNIFORM
+
+ ret = TRUE;
+
+failure:
+ g_clear_object (&compiler);
+
+ return ret;
+}
+
+/**
+ * gsk_ngl_driver_autorelease_framebuffer:
+ * @self: a #GskNglDriver
+ * @framebuffer_id: the id of the OpenGL framebuffer
+ *
+ * Marks @framebuffer_id to be deleted when the current frame has cmopleted.
+ */
+static void
+gsk_ngl_driver_autorelease_framebuffer (GskNglDriver *self,
+ guint framebuffer_id)
+{
+ g_assert (GSK_IS_NGL_DRIVER (self));
+
+ g_array_append_val (self->autorelease_framebuffers, framebuffer_id);
+}
+
+static GskNglDriver *
+gsk_ngl_driver_new (GskNglCommandQueue *command_queue,
+ gboolean debug_shaders,
+ GError **error)
+{
+ GskNglDriver *self;
+ GdkGLContext *context;
+
+ g_return_val_if_fail (GSK_IS_NGL_COMMAND_QUEUE (command_queue), NULL);
+
+ context = gsk_ngl_command_queue_get_context (command_queue);
+
+ gdk_gl_context_make_current (context);
+
+ self = g_object_new (GSK_TYPE_NGL_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_ngl_driver_load_programs (self, error))
+ {
+ g_object_unref (self);
+ return NULL;
+ }
+
+ self->glyphs = gsk_ngl_glyph_library_new (self);
+ self->icons = gsk_ngl_icon_library_new (self);
+ self->shadows = gsk_ngl_shadow_library_new (self);
+
+ return g_steal_pointer (&self);
+}
+
+/**
+ * gsk_ngl_driver_from_shared_context:
+ * @context: a shared #GdkGLContext retrieved with gdk_gl_context_get_shared_context()
+ * @debug_shaders: if debug information for shaders should be displayed
+ * @error: location for error information
+ *
+ * Retrieves a driver for a shared context. Generally this is shared across all GL
+ * contexts for a display so that fewer programs are necessary for driving output.
+ *
+ * Returns: (transfer full): a #GskNglDriver if successful; otherwise %NULL and
+ * @error is set.
+ */
+GskNglDriver *
+gsk_ngl_driver_from_shared_context (GdkGLContext *context,
+ gboolean debug_shaders,
+ GError **error)
+{
+ GskNglCommandQueue *command_queue = NULL;
+ GskNglDriver *driver;
+
+ g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
+
+ if ((driver = g_object_get_data (G_OBJECT (context), "GSK_NGL_DRIVER")))
+ return g_object_ref (driver);
+
+ 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_ngl_command_queue_new (context, NULL);
+
+ if (!(driver = gsk_ngl_driver_new (command_queue, debug_shaders, error)))
+ goto failure;
+
+ g_object_set_data_full (G_OBJECT (context),
+ "GSK_NGL_DRIVER",
+ g_object_ref (driver),
+ g_object_unref);
+
+failure:
+ g_clear_object (&command_queue);
+
+ return g_steal_pointer (&driver);
+}
+
+/**
+ * gsk_ngl_driver_begin_frame:
+ * @self: a #GskNglDriver
+ * @command_queue: A #GskNglCommandQueue 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_ngl_driver_begin_frame (GskNglDriver *self,
+ GskNglCommandQueue *command_queue)
+{
+ gint64 last_frame_id;
+
+ g_return_if_fail (GSK_IS_NGL_DRIVER (self));
+ g_return_if_fail (GSK_IS_NGL_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_ngl_command_queue_begin_frame (self->command_queue);
+
+ gsk_ngl_texture_library_begin_frame (GSK_NGL_TEXTURE_LIBRARY (self->icons));
+ gsk_ngl_texture_library_begin_frame (GSK_NGL_TEXTURE_LIBRARY (self->glyphs));
+ gsk_ngl_shadow_library_begin_frame (self->shadows);
+
+ /* 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_ngl_driver_collect_unused_textures (self, last_frame_id - 1);
+}
+
+/**
+ * gsk_ngl_driver_end_frame:
+ * @self: a #GskNglDriver
+ *
+ * Clean up resources from drawing the current frame.
+ *
+ * Temporary resources used while drawing will be released.
+ */
+void
+gsk_ngl_driver_end_frame (GskNglDriver *self)
+{
+ g_return_if_fail (GSK_IS_NGL_DRIVER (self));
+ g_return_if_fail (self->in_frame == TRUE);
+
+ gsk_ngl_command_queue_make_current (self->command_queue);
+ gsk_ngl_command_queue_end_frame (self->command_queue);
+
+ gsk_ngl_texture_library_end_frame (GSK_NGL_TEXTURE_LIBRARY (self->icons));
+ gsk_ngl_texture_library_end_frame (GSK_NGL_TEXTURE_LIBRARY (self->glyphs));
+
+ self->in_frame = FALSE;
+}
+
+/**
+ * gsk_ngl_driver_after_frame:
+ * @self: a #GskNglDriver
+ *
+ * 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_ngl_driver_after_frame (GskNglDriver *self)
+{
+ g_return_if_fail (GSK_IS_NGL_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)
+ {
+ GskNglRenderTarget *render_target = g_ptr_array_index (self->render_targets, self->render_targets->len - 1);
+
+ gsk_ngl_driver_autorelease_framebuffer (self, render_target->framebuffer_id);
+ glDeleteTextures (1, &render_target->texture_id);
+ g_slice_free (GskNglRenderTarget, 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 */
+ gsk_ngl_texture_pool_clear (&self->texture_pool);
+
+ /* Reset command queue to our shared queue incase 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_ngl_driver_get_context (GskNglDriver *self)
+{
+ g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), NULL);
+ g_return_val_if_fail (GSK_IS_NGL_COMMAND_QUEUE (self->command_queue), NULL);
+
+ return gsk_ngl_command_queue_get_context (self->command_queue);
+}
+
+/**
+ * gsk_ngl_driver_cache_texture:
+ * @self: a #GskNglDriver
+ * @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_ngl_driver_lookup_texture().
+ *
+ * Textures that have not been used within a number of frames will be
+ * purged from the texture cache automatically.
+ */
+void
+gsk_ngl_driver_cache_texture (GskNglDriver *self,
+ const GskTextureKey *key,
+ guint texture_id)
+{
+ GskTextureKey *k;
+
+ g_assert (GSK_IS_NGL_DRIVER (self));
+ g_assert (key != NULL);
+ g_assert (texture_id > 0);
+ g_assert (g_hash_table_contains (self->textures, GUINT_TO_POINTER (texture_id)));
+
+ k = g_memdup (key, sizeof *key);
+
+ 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);
+}
+
+/**
+ * gsk_ngl_driver_load_texture:
+ * @self: a #GdkTexture
+ * @texture: a #GdkTexture
+ * @min_filter: GL_NEAREST or GL_LINEAR
+ * @mag_filter: GL_NEAREST or GL_LINEAR
+ *
+ * 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_ngl_driver_load_texture (GskNglDriver *self,
+ GdkTexture *texture,
+ int min_filter,
+ int mag_filter)
+{
+ GdkGLContext *context;
+ GdkTexture *downloaded_texture = NULL;
+ GdkTexture *source_texture;
+ GskNglTexture *t;
+ guint texture_id;
+ int height;
+ int width;
+
+ g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), 0);
+ g_return_val_if_fail (GDK_IS_TEXTURE (texture), 0);
+ g_return_val_if_fail (GSK_IS_NGL_COMMAND_QUEUE (self->command_queue), 0);
+
+ context = self->command_queue->context;
+
+ if (GDK_IS_GL_TEXTURE (texture))
+ {
+ GdkGLContext *texture_context = gdk_gl_texture_get_context ((GdkGLTexture *)texture);
+ GdkGLContext *shared_context = gdk_gl_context_get_shared_context (context);
+
+ if (texture_context == context ||
+ (shared_context != NULL &&
+ shared_context == gdk_gl_context_get_shared_context (texture_context)))
+
+ {
+ /* A GL texture from the same GL context is a simple task... */
+ return gdk_gl_texture_get_id ((GdkGLTexture *)texture);
+ }
+ else
+ {
+ cairo_surface_t *surface;
+
+ /* In this case, we have to temporarily make the texture's
+ * context the current one, download its data into our context
+ * and then create a texture from it. */
+ if (texture_context != NULL)
+ gdk_gl_context_make_current (texture_context);
+
+ surface = gdk_texture_download_surface (texture);
+ downloaded_texture = gdk_texture_new_for_surface (surface);
+ cairo_surface_destroy (surface);
+
+ gdk_gl_context_make_current (context);
+
+ source_texture = downloaded_texture;
+ }
+ }
+ else
+ {
+ if ((t = gdk_texture_get_render_data (texture, self)))
+ {
+ if (t->min_filter == min_filter && t->mag_filter == mag_filter)
+ return t->texture_id;
+ }
+
+ source_texture = texture;
+ }
+
+ width = gdk_texture_get_width (texture);
+ height = gdk_texture_get_height (texture);
+ texture_id = gsk_ngl_command_queue_upload_texture (self->command_queue,
+ source_texture,
+ 0,
+ 0,
+ width,
+ height,
+ min_filter,
+ mag_filter);
+
+ t = gsk_ngl_texture_new (texture_id,
+ width, height, min_filter, mag_filter,
+ self->current_frame_id);
+
+ g_hash_table_insert (self->textures, GUINT_TO_POINTER (texture_id), t);
+
+ if (gdk_texture_set_render_data (texture, self, t, gsk_ngl_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 texture_id;
+}
+
+/**
+ * gsk_ngl_driver_create_texture:
+ * @self: a #GskNglDriver
+ * @width: the width of the texture
+ * @height: the height of the texture
+ * @min_filter: GL_NEAREST or GL_LINEAR
+ * @mag_filter: GL_NEAREST or GL_FILTER
+ *
+ * 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.
+ *
+ * Use gsk_ngl_driver_release_texture() to release this texture back into
+ * the pool so it may be reused later in the pipeline.
+ *
+ * Returns: a #GskNglTexture which can be returned to the pool with
+ * gsk_ngl_driver_release_texture().
+ */
+GskNglTexture *
+gsk_ngl_driver_create_texture (GskNglDriver *self,
+ float width,
+ float height,
+ int min_filter,
+ int mag_filter)
+{
+ GskNglTexture *texture;
+
+ g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), NULL);
+
+ texture = gsk_ngl_texture_pool_get (&self->texture_pool,
+ width, height,
+ min_filter, mag_filter);
+ g_hash_table_insert (self->textures,
+ GUINT_TO_POINTER (texture->texture_id),
+ texture);
+ texture->last_used_in_frame = self->current_frame_id;
+ return texture;
+}
+
+/**
+ * gsk_ngl_driver_release_texture:
+ * @self: a #GskNglDriver
+ * @texture: a #GskNglTexture
+ *
+ * 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_ngl_driver_release_texture (GskNglDriver *self,
+ GskNglTexture *texture)
+{
+ guint texture_id;
+
+ g_assert (GSK_IS_NGL_DRIVER (self));
+ g_assert (texture != NULL);
+
+ texture_id = texture->texture_id;
+
+ if (texture_id > 0)
+ remove_texture_key_for_id (self, texture_id);
+
+ g_hash_table_steal (self->textures, GUINT_TO_POINTER (texture_id));
+ gsk_ngl_texture_pool_put (&self->texture_pool, texture);
+}
+
+/**
+ * gsk_ngl_driver_create_render_target:
+ * @self: a #GskNglDriver
+ * @width: the width for the render target
+ * @height: the height for the render target
+ * @min_filter: the min filter to use for the texture
+ * @mag_filter: the mag filter to use for the texture
+ * @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.
+ *
+ * Use gsk_ngl_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_ngl_driver_create_render_target (GskNglDriver *self,
+ int width,
+ int height,
+ int min_filter,
+ int mag_filter,
+ GskNglRenderTarget **out_render_target)
+{
+ guint framebuffer_id;
+ guint texture_id;
+
+ g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), FALSE);
+ g_return_val_if_fail (GSK_IS_NGL_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--)
+ {
+ GskNglRenderTarget *render_target = g_ptr_array_index (self->render_targets, i-1);
+
+ if (render_target->width == width &&
+ render_target->height == height &&
+ render_target->min_filter == min_filter &&
+ render_target->mag_filter == mag_filter)
+ {
+ *out_render_target = g_ptr_array_steal_index_fast (self->render_targets, i-1);
+ return TRUE;
+ }
+ }
+ }
+#endif
+
+ if (gsk_ngl_command_queue_create_render_target (self->command_queue,
+ width, height,
+ min_filter, mag_filter,
+ &framebuffer_id, &texture_id))
+ {
+ GskNglRenderTarget *render_target;
+
+ render_target = g_slice_new0 (GskNglRenderTarget);
+ render_target->min_filter = min_filter;
+ render_target->mag_filter = mag_filter;
+ 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;
+}
+
+/**
+ * gsk_ngl_driver_release_render_target:
+ * @self: a #GskNglDriver
+ * @render_target: a #GskNglRenderTarget created with
+ * gsk_ngl_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_ngl_driver_release_render_target (GskNglDriver *self,
+ GskNglRenderTarget *render_target,
+ gboolean release_texture)
+{
+ guint texture_id;
+
+ g_return_val_if_fail (GSK_IS_NGL_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
+ {
+ GskNglTexture *texture;
+
+ texture_id = render_target->texture_id;
+
+ texture = gsk_ngl_texture_new (render_target->texture_id,
+ render_target->width,
+ render_target->height,
+ render_target->min_filter,
+ render_target->mag_filter,
+ self->current_frame_id);
+ g_hash_table_insert (self->textures,
+ GUINT_TO_POINTER (texture_id),
+ g_steal_pointer (&texture));
+
+ gsk_ngl_driver_autorelease_framebuffer (self, render_target->framebuffer_id);
+ g_slice_free (GskNglRenderTarget, render_target);
+
+ }
+
+ return texture_id;
+}
+
+/**
+ * gsk_ngl_driver_lookup_shader:
+ * @self: a #GskNglDriver
+ * @shader: the shader to lookup or load
+ * @error: a location for a #GError, or %NULL
+ *
+ * Attepts to load @shader from the shader cache.
+ *
+ * If it has not been loaded, then it will compile the shader on demand.
+ *
+ * Returns: (transfer none): a #GskGLShader if successful; otherwise
+ * %NULL and @error is set.
+ */
+GskNglProgram *
+gsk_ngl_driver_lookup_shader (GskNglDriver *self,
+ GskGLShader *shader,
+ GError **error)
+{
+ GskNglProgram *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;
+ GskNglCompiler *compiler;
+ GBytes *suffix;
+ int n_required_textures;
+ int n_uniforms;
+
+ uniforms = gsk_gl_shader_get_uniforms (shader, &n_uniforms);
+ if (n_uniforms > G_N_ELEMENTS (program->args_locations))
+ {
+ g_set_error (error,
+ GDK_GL_ERROR,
+ GDK_GL_ERROR_UNSUPPORTED_FORMAT,
+ "Tried to use %d uniforms, while only %d is supported",
+ n_uniforms,
+ (int)G_N_ELEMENTS (program->args_locations));
+ return NULL;
+ }
+
+ n_required_textures = gsk_gl_shader_get_n_textures (shader);
+ if (n_required_textures > G_N_ELEMENTS (program->texture_locations))
+ {
+ 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,
+ (int)(G_N_ELEMENTS (program->texture_locations)));
+ return NULL;
+ }
+
+ compiler = gsk_ngl_compiler_new (self, FALSE);
+ suffix = gsk_gl_shader_get_source (shader);
+
+ gsk_ngl_compiler_set_preamble_from_resource (compiler,
+ GSK_NGL_COMPILER_ALL,
+ "/org/gtk/libgsk/glsl/preamble.glsl");
+ gsk_ngl_compiler_set_preamble_from_resource (compiler,
+ GSK_NGL_COMPILER_VERTEX,
+ "/org/gtk/libgsk/glsl/preamble.vs.glsl");
+ gsk_ngl_compiler_set_preamble_from_resource (compiler,
+ GSK_NGL_COMPILER_FRAGMENT,
+ "/org/gtk/libgsk/glsl/preamble.fs.glsl");
+ gsk_ngl_compiler_set_source_from_resource (compiler,
+ GSK_NGL_COMPILER_ALL,
+ "/org/gtk/libgsk/glsl/custom.glsl");
+ gsk_ngl_compiler_set_suffix (compiler, GSK_NGL_COMPILER_FRAGMENT, suffix);
+
+ /* Setup attributes that are provided via VBO */
+ gsk_ngl_compiler_bind_attribute (compiler, "aPosition", 0);
+ gsk_ngl_compiler_bind_attribute (compiler, "aUv", 1);
+
+ if ((program = gsk_ngl_compiler_compile (compiler, NULL, error)))
+ {
+ gboolean have_alpha;
+
+ gsk_ngl_program_add_uniform (program, "u_source", UNIFORM_SHARED_SOURCE);
+ gsk_ngl_program_add_uniform (program, "u_clip_rect", UNIFORM_SHARED_CLIP_RECT);
+ gsk_ngl_program_add_uniform (program, "u_viewport", UNIFORM_SHARED_VIEWPORT);
+ gsk_ngl_program_add_uniform (program, "u_projection", UNIFORM_SHARED_PROJECTION);
+ gsk_ngl_program_add_uniform (program, "u_modelview", UNIFORM_SHARED_MODELVIEW);
+ have_alpha = gsk_ngl_program_add_uniform (program, "u_alpha", UNIFORM_SHARED_ALPHA);
+
+ gsk_ngl_program_add_uniform (program, "u_size", UNIFORM_CUSTOM_SIZE);
+ gsk_ngl_program_add_uniform (program, "u_texture1", UNIFORM_CUSTOM_TEXTURE1);
+ gsk_ngl_program_add_uniform (program, "u_texture2", UNIFORM_CUSTOM_TEXTURE2);
+ gsk_ngl_program_add_uniform (program, "u_texture3", UNIFORM_CUSTOM_TEXTURE3);
+ gsk_ngl_program_add_uniform (program, "u_texture4", UNIFORM_CUSTOM_TEXTURE4);
+ for (guint i = 0; i < n_uniforms; i++)
+ gsk_ngl_program_add_uniform (program, uniforms[i].name, UNIFORM_CUSTOM_LAST+i);
+
+ program->size_location = gsk_ngl_program_get_uniform_location (program, UNIFORM_CUSTOM_SIZE);
+ program->texture_locations[0] = gsk_ngl_program_get_uniform_location (program, UNIFORM_CUSTOM_TEXTURE1);
+ program->texture_locations[1] = gsk_ngl_program_get_uniform_location (program, UNIFORM_CUSTOM_TEXTURE2);
+ program->texture_locations[2] = gsk_ngl_program_get_uniform_location (program, UNIFORM_CUSTOM_TEXTURE3);
+ program->texture_locations[3] = gsk_ngl_program_get_uniform_location (program, UNIFORM_CUSTOM_TEXTURE4);
+ for (guint i = 0; i < n_uniforms; i++)
+ program->args_locations[i] = gsk_ngl_program_get_uniform_location (program, UNIFORM_CUSTOM_LAST+i);
+ for (guint i = n_uniforms; i < G_N_ELEMENTS (program->args_locations); i++)
+ program->args_locations[i] = -1;
+
+ gsk_ngl_program_uniforms_added (program, TRUE);
+
+ if (have_alpha)
+ gsk_ngl_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_ngl_driver_shader_weak_cb,
+ self);
+ }
+
+ g_object_unref (compiler);
+ }
+
+ return program;
+}
+
+#ifdef G_ENABLE_DEBUG
+static void
+write_atlas_to_png (GskNglTextureAtlas *atlas,
+ const char *filename)
+{
+ int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, atlas->width);
+ guchar *data = g_malloc (atlas->height * stride);
+ cairo_surface_t *s;
+
+ glBindTexture (GL_TEXTURE_2D, atlas->texture_id);
+ glGetTexImage (GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, data);
+ s = cairo_image_surface_create_for_data (data, CAIRO_FORMAT_ARGB32, atlas->width, atlas->height, stride);
+ cairo_surface_write_to_png (s, filename);
+
+ cairo_surface_destroy (s);
+ g_free (data);
+}
+
+void
+gsk_ngl_driver_save_atlases_to_png (GskNglDriver *self,
+ const char *directory)
+{
+ g_return_if_fail (GSK_IS_NGL_DRIVER (self));
+
+ if (directory == NULL)
+ directory = ".";
+
+ for (guint i = 0; i < self->atlases->len; i++)
+ {
+ GskNglTextureAtlas *atlas = g_ptr_array_index (self->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);
+ write_atlas_to_png (atlas, filename);
+ g_free (filename);
+ }
+}
+#endif
+
+GskNglCommandQueue *
+gsk_ngl_driver_create_command_queue (GskNglDriver *self,
+ GdkGLContext *context)
+{
+ g_return_val_if_fail (GSK_IS_NGL_DRIVER (self), NULL);
+ g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
+
+ return gsk_ngl_command_queue_new (context, self->shared_command_queue->uniforms);
+}
+
+void
+gsk_ngl_driver_add_texture_slices (GskNglDriver *self,
+ GdkTexture *texture,
+ GskNglTextureSlice **out_slices,
+ guint *out_n_slices)
+{
+ int max_texture_size;
+ GskNglTextureSlice *slices;
+ GskNglTexture *t;
+ guint n_slices;
+ guint cols;
+ guint rows;
+ int tex_width;
+ int tex_height;
+ int x = 0, y = 0;
+
+ g_assert (GSK_IS_NGL_DRIVER (self));
+ g_assert (GDK_IS_TEXTURE (texture));
+ g_assert (out_slices != NULL);
+ g_assert (out_n_slices != NULL);
+
+ /* XXX: Too much? */
+ max_texture_size = self->command_queue->max_texture_size / 4;
+
+ tex_width = texture->width;
+ tex_height = texture->height;
+ cols = (texture->width / max_texture_size) + 1;
+ rows = (texture->height / max_texture_size) + 1;
+
+ if ((t = gdk_texture_get_render_data (texture, self)))
+ {
+ *out_slices = t->slices;
+ *out_n_slices = t->n_slices;
+ return;
+ }
+
+ n_slices = cols * rows;
+ slices = g_new0 (GskNglTextureSlice, n_slices);
+
+ for (guint col = 0; col < cols; col ++)
+ {
+ int slice_width = MIN (max_texture_size, texture->width - x);
+
+ for (guint row = 0; row < rows; row ++)
+ {
+ int slice_height = MIN (max_texture_size, texture->height - y);
+ int slice_index = (col * rows) + row;
+ guint texture_id;
+
+ texture_id = gsk_ngl_command_queue_upload_texture (self->command_queue,
+ texture,
+ x, y,
+ slice_width, slice_height,
+ GL_NEAREST, GL_NEAREST);
+
+ 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;
+
+ y += slice_height;
+ }
+
+ y = 0;
+ x += slice_width;
+ }
+
+ /* Allocate one Texture for the entire thing. */
+ t = gsk_ngl_texture_new (0,
+ tex_width, tex_height,
+ GL_NEAREST, GL_NEAREST,
+ self->current_frame_id);
+
+ /* Use gsk_ngl_texture_free() as destroy notify here since we are
+ * not inserting this GskNglTexture into self->textures!
+ */
+ gdk_texture_set_render_data (texture, self, t,
+ (GDestroyNotify)gsk_ngl_texture_free);
+
+ t->slices = *out_slices = slices;
+ t->n_slices = *out_n_slices = n_slices;
+}
+
+GskNglTexture *
+gsk_ngl_driver_mark_texture_permanent (GskNglDriver *self,
+ guint texture_id)
+{
+ GskNglTexture *t;
+
+ g_return_val_if_fail (GSK_IS_NGL_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_ngl_driver_release_texture_by_id (GskNglDriver *self,
+ guint texture_id)
+{
+ GskNglTexture *texture;
+
+ g_return_if_fail (GSK_IS_NGL_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_ngl_driver_release_texture (self, texture);
+}
+
+
+static void
+create_texture_from_texture_destroy (gpointer data)
+{
+ GskNglTextureState *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);
+ g_clear_object (&state->context);
+ g_slice_free (GskNglTextureState, state);
+}
+
+GdkTexture *
+gsk_ngl_driver_create_gdk_texture (GskNglDriver *self,
+ guint texture_id)
+{
+ GskNglTextureState *state;
+ GskNglTexture *texture;
+
+ g_return_val_if_fail (GSK_IS_NGL_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_slice_new0 (GskNglTextureState);
+ state->texture_id = texture_id;
+ state->context = g_object_ref (self->command_queue->context);
+
+ g_hash_table_steal (self->textures, GUINT_TO_POINTER (texture_id));
+
+ return gdk_gl_texture_new (self->command_queue->context,
+ texture_id,
+ texture->width,
+ texture->height,
+ create_texture_from_texture_destroy,
+ state);
+}
diff --git a/gsk/ngl/gskngldriverprivate.h b/gsk/ngl/gskngldriverprivate.h
new file mode 100644
index 0000000000..e5cda21adc
--- /dev/null
+++ b/gsk/ngl/gskngldriverprivate.h
@@ -0,0 +1,234 @@
+/* gskngldriverprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_DRIVER_PRIVATE_H__
+#define __GSK_NGL_DRIVER_PRIVATE_H__
+
+#include <gdk/gdkgltextureprivate.h>
+
+#include "gskngltypesprivate.h"
+#include "gskngltexturepoolprivate.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_LAST
+};
+
+typedef struct {
+ gconstpointer pointer;
+ float scale_x;
+ float scale_y;
+ int filter;
+ int pointer_is_child;
+ graphene_rect_t parent_rect; /* Valid when pointer_is_child */
+} GskTextureKey;
+
+#define GSL_GK_NO_UNIFORMS UNIFORM_INVALID_##__COUNTER__
+#define GSK_NGL_ADD_UNIFORM(pos, KEY, name) UNIFORM_##KEY = UNIFORM_SHARED_LAST + pos,
+#define GSK_NGL_DEFINE_PROGRAM(name, resource, uniforms) enum { uniforms };
+# include "gsknglprograms.defs"
+#undef GSK_NGL_DEFINE_PROGRAM
+#undef GSK_NGL_ADD_UNIFORM
+#undef GSL_GK_NO_UNIFORMS
+
+#define GSK_TYPE_NGL_DRIVER (gsk_ngl_driver_get_type())
+
+G_DECLARE_FINAL_TYPE (GskNglDriver, gsk_ngl_driver, GSK, NGL_DRIVER, GObject)
+
+struct _GskNglRenderTarget
+{
+ guint framebuffer_id;
+ guint texture_id;
+ int min_filter;
+ int mag_filter;
+ int width;
+ int height;
+};
+
+struct _GskNglDriver
+{
+ GObject parent_instance;
+
+ GskNglCommandQueue *shared_command_queue;
+ GskNglCommandQueue *command_queue;
+
+ GskNglTexturePool texture_pool;
+
+ GskNglGlyphLibrary *glyphs;
+ GskNglIconLibrary *icons;
+ GskNglShadowLibrary *shadows;
+
+ GHashTable *textures;
+ GHashTable *key_to_texture_id;
+ GHashTable *texture_id_to_key;
+
+ GPtrArray *atlases;
+
+ GHashTable *shader_cache;
+
+ GArray *autorelease_framebuffers;
+ GPtrArray *render_targets;
+
+#define GSK_NGL_NO_UNIFORMS
+#define GSK_NGL_ADD_UNIFORM(pos, KEY, name)
+#define GSK_NGL_DEFINE_PROGRAM(name, resource, uniforms) GskNglProgram *name;
+# include "gsknglprograms.defs"
+#undef GSK_NGL_NO_UNIFORMS
+#undef GSK_NGL_ADD_UNIFORM
+#undef GSK_NGL_DEFINE_PROGRAM
+
+ gint64 current_frame_id;
+
+ /* Used to reduce number of comparisons */
+ guint stamps[UNIFORM_SHARED_LAST];
+
+ guint debug : 1;
+ guint in_frame : 1;
+};
+
+GskNglDriver *gsk_ngl_driver_from_shared_context (GdkGLContext *context,
+ gboolean debug_shaders,
+ GError **error);
+GskNglCommandQueue *gsk_ngl_driver_create_command_queue (GskNglDriver *self,
+ GdkGLContext *context);
+GdkGLContext *gsk_ngl_driver_get_context (GskNglDriver *self);
+gboolean gsk_ngl_driver_create_render_target (GskNglDriver *self,
+ int width,
+ int height,
+ int min_filter,
+ int mag_filter,
+ GskNglRenderTarget **render_target);
+guint gsk_ngl_driver_release_render_target (GskNglDriver *self,
+ GskNglRenderTarget *render_target,
+ gboolean release_texture);
+void gsk_ngl_driver_begin_frame (GskNglDriver *self,
+ GskNglCommandQueue *command_queue);
+void gsk_ngl_driver_end_frame (GskNglDriver *self);
+void gsk_ngl_driver_after_frame (GskNglDriver *self);
+GdkTexture *gsk_ngl_driver_create_gdk_texture (GskNglDriver *self,
+ guint texture_id);
+void gsk_ngl_driver_cache_texture (GskNglDriver *self,
+ const GskTextureKey *key,
+ guint texture_id);
+guint gsk_ngl_driver_load_texture (GskNglDriver *self,
+ GdkTexture *texture,
+ int min_filter,
+ int mag_filter);
+GskNglTexture *gsk_ngl_driver_create_texture (GskNglDriver *self,
+ float width,
+ float height,
+ int min_filter,
+ int mag_filter);
+void gsk_ngl_driver_release_texture (GskNglDriver *self,
+ GskNglTexture *texture);
+void gsk_ngl_driver_release_texture_by_id (GskNglDriver *self,
+ guint texture_id);
+GskNglTexture *gsk_ngl_driver_mark_texture_permanent (GskNglDriver *self,
+ guint texture_id);
+void gsk_ngl_driver_add_texture_slices (GskNglDriver *self,
+ GdkTexture *texture,
+ GskNglTextureSlice **out_slices,
+ guint *out_n_slices);
+GskNglProgram *gsk_ngl_driver_lookup_shader (GskNglDriver *self,
+ GskGLShader *shader,
+ GError **error);
+GskNglTextureAtlas *gsk_ngl_driver_create_atlas (GskNglDriver *self);
+
+#ifdef G_ENABLE_DEBUG
+void gsk_ngl_driver_save_atlases_to_png (GskNglDriver *self,
+ const char *directory);
+#endif
+
+static inline GskNglTexture *
+gsk_ngl_driver_get_texture_by_id (GskNglDriver *self,
+ guint texture_id)
+{
+ return g_hash_table_lookup (self->textures, GUINT_TO_POINTER (texture_id));
+}
+
+/**
+ * gsk_ngl_driver_lookup_texture:
+ * @self: a #GskNglDriver
+ * @key: the key for the texture
+ *
+ * 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_ngl_driver_lookup_texture (GskNglDriver *self,
+ const GskTextureKey *key)
+{
+ gpointer id;
+
+ if (g_hash_table_lookup_extended (self->key_to_texture_id, key, NULL, &id))
+ {
+ GskNglTexture *texture = g_hash_table_lookup (self->textures, id);
+
+ if (texture != NULL)
+ texture->last_used_in_frame = self->current_frame_id;
+
+ return GPOINTER_TO_UINT (id);
+ }
+
+ return 0;
+}
+
+static inline void
+gsk_ngl_driver_slice_texture (GskNglDriver *self,
+ GdkTexture *texture,
+ GskNglTextureSlice **out_slices,
+ guint *out_n_slices)
+{
+ GskNglTexture *t;
+
+ if ((t = gdk_texture_get_render_data (texture, self)))
+ {
+ *out_slices = t->slices;
+ *out_n_slices = t->n_slices;
+ return;
+ }
+
+ gsk_ngl_driver_add_texture_slices (self, texture, out_slices, out_n_slices);
+}
+
+G_END_DECLS
+
+#endif /* __GSK_NGL_DRIVER_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglglyphlibrary.c b/gsk/ngl/gsknglglyphlibrary.c
new file mode 100644
index 0000000000..5cb2a3fe3b
--- /dev/null
+++ b/gsk/ngl/gsknglglyphlibrary.c
@@ -0,0 +1,325 @@
+/* gsknglglyphlibrary.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdkglcontextprivate.h>
+#include <gdk/gdkmemorytextureprivate.h>
+#include <gdk/gdkprofilerprivate.h>
+
+#include "gsknglcommandqueueprivate.h"
+#include "gskngldriverprivate.h"
+#include "gsknglglyphlibraryprivate.h"
+
+#define MAX_GLYPH_SIZE 128
+
+G_DEFINE_TYPE (GskNglGlyphLibrary, gsk_ngl_glyph_library, GSK_TYPE_GL_TEXTURE_LIBRARY)
+
+GskNglGlyphLibrary *
+gsk_ngl_glyph_library_new (GskNglDriver *driver)
+{
+ g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
+
+ return g_object_new (GSK_TYPE_GL_GLYPH_LIBRARY,
+ "driver", driver,
+ NULL);
+}
+
+static guint
+gsk_ngl_glyph_key_hash (gconstpointer data)
+{
+ const GskNglGlyphKey *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 GskNglGlyphKey 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_ngl_glyph_key_equal (gconstpointer v1,
+ gconstpointer v2)
+{
+ return memcmp (v1, v2, sizeof (GskNglGlyphKey)) == 0;
+}
+
+static void
+gsk_ngl_glyph_key_free (gpointer data)
+{
+ GskNglGlyphKey *key = data;
+
+ g_clear_object (&key->font);
+ g_slice_free (GskNglGlyphKey, key);
+}
+
+static void
+gsk_ngl_glyph_value_free (gpointer data)
+{
+ g_slice_free (GskNglGlyphValue, data);
+}
+
+static void
+gsk_ngl_glyph_library_finalize (GObject *object)
+{
+ GskNglGlyphLibrary *self = (GskNglGlyphLibrary *)object;
+
+ g_clear_pointer (&self->hash_table, g_hash_table_unref);
+ g_clear_pointer (&self->surface_data, g_free);
+
+ G_OBJECT_CLASS (gsk_ngl_glyph_library_parent_class)->finalize (object);
+}
+
+static void
+gsk_ngl_glyph_library_class_init (GskNglGlyphLibraryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsk_ngl_glyph_library_finalize;
+}
+
+static void
+gsk_ngl_glyph_library_init (GskNglGlyphLibrary *self)
+{
+ GSK_NGL_TEXTURE_LIBRARY (self)->max_entry_size = MAX_GLYPH_SIZE;
+ gsk_ngl_texture_library_set_funcs (GSK_NGL_TEXTURE_LIBRARY (self),
+ gsk_ngl_glyph_key_hash,
+ gsk_ngl_glyph_key_equal,
+ gsk_ngl_glyph_key_free,
+ gsk_ngl_glyph_value_free);
+}
+
+static cairo_surface_t *
+gsk_ngl_glyph_library_create_surface (GskNglGlyphLibrary *self,
+ int stride,
+ int width,
+ int height,
+ double device_scale)
+{
+ cairo_surface_t *surface;
+ gsize n_bytes;
+
+ g_assert (GSK_IS_NGL_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, device_scale, device_scale);
+
+ return surface;
+}
+
+static void
+render_glyph (cairo_surface_t *surface,
+ const cairo_scaled_font_t *scaled_font,
+ const GskNglGlyphKey *key,
+ const GskNglGlyphValue *value)
+{
+ cairo_t *cr;
+ PangoGlyphString glyph_string;
+ PangoGlyphInfo glyph_info;
+
+ g_assert (surface != NULL);
+ g_assert (scaled_font != NULL);
+
+ cr = cairo_create (surface);
+ cairo_set_scaled_font (cr, scaled_font);
+ cairo_set_source_rgba (cr, 1, 1, 1, 1);
+
+ glyph_info.glyph = key->glyph;
+ glyph_info.geometry.width = value->ink_rect.width * 1024;
+ if (glyph_info.glyph & PANGO_GLYPH_UNKNOWN_FLAG)
+ glyph_info.geometry.x_offset = 250 * key->xshift;
+ else
+ glyph_info.geometry.x_offset = 250 * key->xshift - value->ink_rect.x * 1024;
+ glyph_info.geometry.y_offset = 250 * 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_ngl_glyph_library_upload_glyph (GskNglGlyphLibrary *self,
+ const GskNglGlyphKey *key,
+ const GskNglGlyphValue *value,
+ int width,
+ int height,
+ double device_scale)
+{
+ G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
+ cairo_scaled_font_t *scaled_font;
+ GskNglTextureAtlas *atlas;
+ cairo_surface_t *surface;
+ guchar *pixel_data;
+ guchar *free_data = NULL;
+ guint gl_format;
+ guint gl_type;
+ guint texture_id;
+ gsize stride;
+ int x, y;
+
+ g_assert (GSK_IS_NGL_GLYPH_LIBRARY (self));
+ g_assert (key != NULL);
+ g_assert (value != NULL);
+
+ scaled_font = pango_cairo_font_get_scaled_font ((PangoCairoFont *)key->font);
+ if G_UNLIKELY (scaled_font == NULL ||
+ cairo_scaled_font_status (scaled_font) != CAIRO_STATUS_SUCCESS)
+ return;
+
+ stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, width);
+ atlas = value->entry.is_atlased ? value->entry.atlas : NULL;
+
+ gdk_gl_context_push_debug_group_printf (gdk_gl_context_get_current (),
+ "Uploading glyph %d",
+ key->glyph);
+
+ surface = gsk_ngl_glyph_library_create_surface (self, stride, width, height, device_scale);
+ render_glyph (surface, scaled_font, key, value);
+
+ texture_id = GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (value);
+
+ g_assert (texture_id > 0);
+
+ glPixelStorei (GL_UNPACK_ROW_LENGTH, stride / 4);
+ glBindTexture (GL_TEXTURE_2D, texture_id);
+
+ if G_UNLIKELY (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,
+ width * 4,
+ GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
+ cairo_image_surface_get_data (surface),
+ width * 4,
+ GDK_MEMORY_DEFAULT,
+ width, height);
+ 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;
+ }
+
+ if G_LIKELY (atlas != NULL)
+ {
+ x = atlas->width * value->entry.area.x;
+ y = atlas->width * value->entry.area.y;
+ }
+ else
+ {
+ x = 0;
+ y = 0;
+ }
+
+ glTexSubImage2D (GL_TEXTURE_2D, 0, x, y, width, height,
+ gl_format, gl_type, pixel_data);
+ glPixelStorei (GL_UNPACK_ROW_LENGTH, 0);
+
+ cairo_surface_destroy (surface);
+ g_free (free_data);
+
+ gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
+
+ GSK_NGL_TEXTURE_LIBRARY (self)->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_ngl_glyph_library_add (GskNglGlyphLibrary *self,
+ GskNglGlyphKey *key,
+ const GskNglGlyphValue **out_value)
+{
+ PangoRectangle ink_rect;
+ GskNglGlyphValue *value;
+ int width;
+ int height;
+ guint packed_x;
+ guint packed_y;
+
+ g_assert (GSK_IS_NGL_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);
+
+ if (key->xshift != 0)
+ ink_rect.width++;
+ if (key->yshift != 0)
+ ink_rect.height++;
+
+ width = ink_rect.width * key->scale / 1024;
+ height = ink_rect.height * key->scale / 1024;
+
+ value = gsk_ngl_texture_library_pack (GSK_NGL_TEXTURE_LIBRARY (self),
+ key,
+ sizeof *value,
+ width,
+ height,
+ 0,
+ &packed_x, &packed_y);
+
+ memcpy (&value->ink_rect, &ink_rect, sizeof ink_rect);
+
+ if (key->scale > 0 && width > 0 && height > 0)
+ gsk_ngl_glyph_library_upload_glyph (self,
+ key,
+ value,
+ width,
+ height,
+ key->scale / 1024.0);
+
+ *out_value = value;
+
+ return GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (value) != 0;
+}
diff --git a/gsk/ngl/gsknglglyphlibraryprivate.h b/gsk/ngl/gsknglglyphlibraryprivate.h
new file mode 100644
index 0000000000..9df9526f99
--- /dev/null
+++ b/gsk/ngl/gsknglglyphlibraryprivate.h
@@ -0,0 +1,119 @@
+/* gsknglglyphlibraryprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_GLYPH_LIBRARY_PRIVATE_H__
+#define __GSK_NGL_GLYPH_LIBRARY_PRIVATE_H__
+
+#include <pango/pango.h>
+
+#include "gskngltexturelibraryprivate.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_GLYPH_LIBRARY (gsk_ngl_glyph_library_get_type())
+
+typedef struct _GskNglGlyphKey
+{
+ PangoFont *font;
+ PangoGlyph glyph;
+ guint xshift : 3;
+ guint yshift : 3;
+ guint scale : 26; /* times 1024 */
+} GskNglGlyphKey;
+
+typedef struct _GskNglGlyphValue
+{
+ GskNglTextureAtlasEntry entry;
+ PangoRectangle ink_rect;
+} GskNglGlyphValue;
+
+#if GLIB_SIZEOF_VOID_P == 8
+G_STATIC_ASSERT (sizeof (GskNglGlyphKey) == 16);
+#elif GLIB_SIZEOF_VOID_P == 4
+G_STATIC_ASSERT (sizeof (GskNglGlyphKey) == 12);
+#endif
+
+G_DECLARE_FINAL_TYPE (GskNglGlyphLibrary, gsk_ngl_glyph_library, GSK, NGL_GLYPH_LIBRARY, GskNglTextureLibrary)
+
+struct _GskNglGlyphLibrary
+{
+ GskNglTextureLibrary parent_instance;
+ GHashTable *hash_table;
+ guint8 *surface_data;
+ gsize surface_data_len;
+ struct {
+ GskNglGlyphKey key;
+ const GskNglGlyphValue *value;
+ } front[256];
+};
+
+GskNglGlyphLibrary *gsk_ngl_glyph_library_new (GskNglDriver *driver);
+gboolean gsk_ngl_glyph_library_add (GskNglGlyphLibrary *self,
+ GskNglGlyphKey *key,
+ const GskNglGlyphValue **out_value);
+
+static inline int
+gsk_ngl_glyph_key_phase (float value)
+{
+ return floor (4 * (value + 0.125)) - 4 * floor (value + 0.125);
+}
+
+static inline void
+gsk_ngl_glyph_key_set_glyph_and_shift (GskNglGlyphKey *key,
+ PangoGlyph glyph,
+ float x,
+ float y)
+{
+ key->glyph = glyph;
+ key->xshift = gsk_ngl_glyph_key_phase (x);
+ key->yshift = gsk_ngl_glyph_key_phase (y);
+}
+
+static inline gboolean
+gsk_ngl_glyph_library_lookup_or_add (GskNglGlyphLibrary *self,
+ const GskNglGlyphKey *key,
+ const GskNglGlyphValue **out_value)
+{
+ GskNglTextureAtlasEntry *entry;
+ guint front_index = key->glyph & 0xFF;
+
+ if (memcmp (key, &self->front[front_index], sizeof *key) == 0)
+ {
+ *out_value = self->front[front_index].value;
+ }
+ else if (gsk_ngl_texture_library_lookup ((GskNglTextureLibrary *)self, key, &entry))
+ {
+ *out_value = (GskNglGlyphValue *)entry;
+ self->front[front_index].key = *key;
+ self->front[front_index].value = *out_value;
+ }
+ else
+ {
+ GskNglGlyphKey *k = g_slice_copy (sizeof *key, key);
+ g_object_ref (k->font);
+ gsk_ngl_glyph_library_add (self, k, out_value);
+ }
+
+ return GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (*out_value) != 0;
+}
+
+G_END_DECLS
+
+#endif /* __GSK_NGL_GLYPH_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/ngl/gskngliconlibrary.c b/gsk/ngl/gskngliconlibrary.c
new file mode 100644
index 0000000000..4d84cb2354
--- /dev/null
+++ b/gsk/ngl/gskngliconlibrary.c
@@ -0,0 +1,213 @@
+/* gskngliconlibrary.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdkglcontextprivate.h>
+#include <gdk/gdkmemorytextureprivate.h>
+#include <gdk/gdkprofilerprivate.h>
+#include <gdk/gdktextureprivate.h>
+
+#include "gsknglcommandqueueprivate.h"
+#include "gskngldriverprivate.h"
+#include "gskngliconlibraryprivate.h"
+
+struct _GskNglIconLibrary
+{
+ GskNglTextureLibrary parent_instance;
+};
+
+G_DEFINE_TYPE (GskNglIconLibrary, gsk_ngl_icon_library, GSK_TYPE_GL_TEXTURE_LIBRARY)
+
+GskNglIconLibrary *
+gsk_ngl_icon_library_new (GskNglDriver *driver)
+{
+ g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
+
+ return g_object_new (GSK_TYPE_GL_ICON_LIBRARY,
+ "driver", driver,
+ NULL);
+}
+
+static void
+gsk_ngl_icon_data_free (gpointer data)
+{
+ GskNglIconData *icon_data = data;
+
+ g_clear_object (&icon_data->source_texture);
+ g_slice_free (GskNglIconData, icon_data);
+}
+
+static void
+gsk_ngl_icon_library_class_init (GskNglIconLibraryClass *klass)
+{
+}
+
+static void
+gsk_ngl_icon_library_init (GskNglIconLibrary *self)
+{
+ GSK_NGL_TEXTURE_LIBRARY (self)->max_entry_size = 128;
+ gsk_ngl_texture_library_set_funcs (GSK_NGL_TEXTURE_LIBRARY (self),
+ NULL, NULL, NULL,
+ gsk_ngl_icon_data_free);
+}
+
+void
+gsk_ngl_icon_library_add (GskNglIconLibrary *self,
+ GdkTexture *key,
+ const GskNglIconData **out_value)
+{
+ G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
+ cairo_surface_t *surface;
+ GskNglIconData *icon_data;
+ guint8 *pixel_data;
+ guint8 *surface_data;
+ guint8 *free_data = NULL;
+ guint gl_format;
+ guint gl_type;
+ guint packed_x;
+ guint packed_y;
+ int width;
+ int height;
+ guint texture_id;
+
+ g_assert (GSK_IS_NGL_ICON_LIBRARY (self));
+ g_assert (GDK_IS_TEXTURE (key));
+ g_assert (out_value != NULL);
+
+ width = key->width;
+ height = key->height;
+
+ icon_data = gsk_ngl_texture_library_pack (GSK_NGL_TEXTURE_LIBRARY (self),
+ key,
+ sizeof (GskNglIconData),
+ width, height, 1,
+ &packed_x, &packed_y);
+ icon_data->source_texture = g_object_ref (key);
+
+ /* actually upload the texture */
+ surface = gdk_texture_download_surface (key);
+ surface_data = cairo_image_surface_get_data (surface);
+ 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 ()))
+ {
+ pixel_data = free_data = g_malloc (width * height * 4);
+ gdk_memory_convert (pixel_data, width * 4,
+ GDK_MEMORY_R8G8B8A8_PREMULTIPLIED,
+ surface_data, cairo_image_surface_get_stride (surface),
+ GDK_MEMORY_DEFAULT, width, height);
+ gl_format = GL_RGBA;
+ gl_type = GL_UNSIGNED_BYTE;
+ }
+ else
+ {
+ pixel_data = surface_data;
+ gl_format = GL_BGRA;
+ gl_type = GL_UNSIGNED_INT_8_8_8_8_REV;
+ }
+
+ texture_id = GSK_NGL_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 */
+ 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);
+
+ gdk_gl_context_pop_debug_group (gdk_gl_context_get_current ());
+
+ *out_value = icon_data;
+
+ cairo_surface_destroy (surface);
+ g_free (free_data);
+
+ GSK_NGL_TEXTURE_LIBRARY (self)->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 a/gsk/ngl/gskngliconlibraryprivate.h b/gsk/ngl/gskngliconlibraryprivate.h
new file mode 100644
index 0000000000..5fd1cb273d
--- /dev/null
+++ b/gsk/ngl/gskngliconlibraryprivate.h
@@ -0,0 +1,60 @@
+/* gskngliconlibraryprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_ICON_LIBRARY_PRIVATE_H__
+#define __GSK_NGL_ICON_LIBRARY_PRIVATE_H__
+
+#include <pango/pango.h>
+
+#include "gskngltexturelibraryprivate.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_ICON_LIBRARY (gsk_ngl_icon_library_get_type())
+
+typedef struct _GskNglIconData
+{
+ GskNglTextureAtlasEntry entry;
+ GdkTexture *source_texture;
+} GskNglIconData;
+
+G_DECLARE_FINAL_TYPE (GskNglIconLibrary, gsk_ngl_icon_library, GSK, NGL_ICON_LIBRARY, GskNglTextureLibrary)
+
+GskNglIconLibrary *gsk_ngl_icon_library_new (GskNglDriver *driver);
+void gsk_ngl_icon_library_add (GskNglIconLibrary *self,
+ GdkTexture *key,
+ const GskNglIconData **out_value);
+
+static inline void
+gsk_ngl_icon_library_lookup_or_add (GskNglIconLibrary *self,
+ GdkTexture *key,
+ const GskNglIconData **out_value)
+{
+ GskNglTextureAtlasEntry *entry;
+
+ if G_LIKELY (gsk_ngl_texture_library_lookup ((GskNglTextureLibrary *)self, key, &entry))
+ *out_value = (GskNglIconData *)entry;
+ else
+ gsk_ngl_icon_library_add (self, key, out_value);
+}
+
+G_END_DECLS
+
+#endif /* __GSK_NGL_ICON_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglprogram.c b/gsk/ngl/gsknglprogram.c
new file mode 100644
index 0000000000..1343eb35a4
--- /dev/null
+++ b/gsk/ngl/gsknglprogram.c
@@ -0,0 +1,176 @@
+/* gsknglprogram.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gsknglcommandqueueprivate.h"
+#include "gsknglprogramprivate.h"
+#include "gskngluniformstateprivate.h"
+
+G_DEFINE_TYPE (GskNglProgram, gsk_ngl_program, G_TYPE_OBJECT)
+
+GskNglProgram *
+gsk_ngl_program_new (GskNglDriver *driver,
+ const char *name,
+ int program_id)
+{
+ GskNglProgram *self;
+
+ g_return_val_if_fail (GSK_IS_NGL_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_uniforms = 0;
+
+ return self;
+}
+
+static void
+gsk_ngl_program_finalize (GObject *object)
+{
+ GskNglProgram *self = (GskNglProgram *)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_ngl_program_parent_class)->finalize (object);
+}
+
+static void
+gsk_ngl_program_class_init (GskNglProgramClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->finalize = gsk_ngl_program_finalize;
+}
+
+static void
+gsk_ngl_program_init (GskNglProgram *self)
+{
+ self->id = -1;
+
+ for (guint i = 0; i < G_N_ELEMENTS (self->uniform_locations); i++)
+ self->uniform_locations[i] = -1;
+}
+
+/**
+ * gsk_ngl_program_add_uniform:
+ * @self: a #GskNglProgram
+ * @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_ngl_program_add_uniform (program, "u_source", UNIFORM_SOURCE);
+ * ```
+ *
+ * That allows you to set values for the program with something
+ * like the following:
+ *
+ * ```
+ * gsk_ngl_program_set_uniform1i (program, UNIFORM_SOURCE, 1);
+ * ```
+ *
+ * Returns: %TRUE if the uniform was found; otherwise %FALSE
+ */
+gboolean
+gsk_ngl_program_add_uniform (GskNglProgram *self,
+ const char *name,
+ guint key)
+{
+ GLint location;
+
+ g_return_val_if_fail (GSK_IS_NGL_PROGRAM (self), FALSE);
+ g_return_val_if_fail (name != NULL, FALSE);
+ g_return_val_if_fail (key < 1024, FALSE);
+
+ if (-1 == (location = glGetUniformLocation (self->id, name)))
+ return FALSE;
+
+ self->uniform_locations[key] = location;
+
+ if (location >= self->n_uniforms)
+ self->n_uniforms = location + 1;
+
+#if 0
+ g_print ("program [%d] %s uniform %s at location %d.\n",
+ self->id, self->name, name, location);
+#endif
+
+ return TRUE;
+}
+
+/**
+ * gsk_ngl_program_delete:
+ * @self: a #GskNglProgram
+ *
+ * Deletes the GLSL program.
+ *
+ * You must call gsk_ngl_program_use() before and
+ * gsk_ngl_program_unuse() after this function.
+ */
+void
+gsk_ngl_program_delete (GskNglProgram *self)
+{
+ g_return_if_fail (GSK_IS_NGL_PROGRAM (self));
+ g_return_if_fail (self->driver->command_queue != NULL);
+
+ gsk_ngl_command_queue_delete_program (self->driver->command_queue, self->id);
+ self->id = -1;
+}
+
+/**
+ * gsk_ngl_program_uniforms_added:
+ * @self: a #GskNglProgram
+ * @has_attachments: if any uniform is for a bind/texture attachment
+ *
+ * This function should be called after all of the uniforms ahve
+ * been added with gsk_ngl_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_ngl_program_uniforms_added (GskNglProgram *self,
+ gboolean has_attachments)
+{
+ g_return_if_fail (GSK_IS_NGL_PROGRAM (self));
+ g_return_if_fail (self->uniforms == NULL);
+
+ self->uniforms = self->driver->command_queue->uniforms;
+ self->program_info = gsk_ngl_uniform_state_get_program (self->uniforms, self->id, self->n_uniforms);
+ self->program_info->has_attachments = has_attachments;
+}
diff --git a/gsk/ngl/gsknglprogramprivate.h b/gsk/ngl/gsknglprogramprivate.h
new file mode 100644
index 0000000000..47bb0e314f
--- /dev/null
+++ b/gsk/ngl/gsknglprogramprivate.h
@@ -0,0 +1,273 @@
+/* gsknglprogramprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_PROGRAM_PRIVATE_H__
+#define __GSK_NGL_PROGRAM_PRIVATE_H__
+
+#include "gskngltypesprivate.h"
+
+#include "gsknglcommandqueueprivate.h"
+#include "gskngldriverprivate.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_PROGRAM (gsk_ngl_program_get_type())
+
+G_DECLARE_FINAL_TYPE (GskNglProgram, gsk_ngl_program, GSK, NGL_PROGRAM, GObject)
+
+struct _GskNglProgram
+{
+ GObject parent_instance;
+
+ int id;
+ char *name;
+ GskNglDriver *driver;
+
+ /* In reality, this is the largest uniform position
+ * as returned after linking so that we can use direct
+ * indexes based on location.
+ */
+ guint n_uniforms;
+
+ /* Cached pointer to avoid lots of pointer chasing/lookups */
+ GskNglUniformState *uniforms;
+ GskNglUniformProgram *program_info;
+
+ /* For custom programs */
+ int texture_locations[4];
+ int args_locations[8];
+ int size_location;
+
+ /* Static array for key->location transforms */
+ int uniform_locations[32];
+};
+
+GskNglProgram *gsk_ngl_program_new (GskNglDriver *driver,
+ const char *name,
+ int program_id);
+gboolean gsk_ngl_program_add_uniform (GskNglProgram *self,
+ const char *name,
+ guint key);
+void gsk_ngl_program_uniforms_added (GskNglProgram *self,
+ gboolean has_attachments);
+void gsk_ngl_program_delete (GskNglProgram *self);
+
+#define gsk_ngl_program_get_uniform_location(s,k) ((s)->uniform_locations[(k)])
+
+static inline void
+gsk_ngl_program_set_uniform1fv (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ guint count,
+ const float *values)
+{
+ gsk_ngl_uniform_state_set1fv (self->uniforms, self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, count, values);
+}
+
+static inline void
+gsk_ngl_program_set_uniform2fv (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ guint count,
+ const float *values)
+{
+ gsk_ngl_uniform_state_set2fv (self->uniforms, self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, count, values);
+}
+
+static inline void
+gsk_ngl_program_set_uniform4fv (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ guint count,
+ const float *values)
+{
+ gsk_ngl_uniform_state_set4fv (self->uniforms, self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, count, values);
+}
+
+static inline void
+gsk_ngl_program_set_uniform_rounded_rect (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ const GskRoundedRect *rounded_rect)
+{
+ gsk_ngl_uniform_state_set_rounded_rect (self->uniforms, self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, rounded_rect);
+}
+
+static inline void
+gsk_ngl_program_set_uniform1i (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ int value0)
+{
+ gsk_ngl_uniform_state_set1i (self->uniforms,
+ self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, value0);
+}
+
+static inline void
+gsk_ngl_program_set_uniform2i (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ int value0,
+ int value1)
+{
+ gsk_ngl_uniform_state_set2i (self->uniforms,
+ self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, value0, value1);
+}
+
+static inline void
+gsk_ngl_program_set_uniform3i (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ int value0,
+ int value1,
+ int value2)
+{
+ gsk_ngl_uniform_state_set3i (self->uniforms,
+ self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, value0, value1, value2);
+}
+
+static inline void
+gsk_ngl_program_set_uniform4i (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ int value0,
+ int value1,
+ int value2,
+ int value3)
+{
+ gsk_ngl_uniform_state_set4i (self->uniforms,
+ self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, value0, value1, value2, value3);
+}
+
+static inline void
+gsk_ngl_program_set_uniform1f (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ float value0)
+{
+ gsk_ngl_uniform_state_set1f (self->uniforms,
+ self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, value0);
+}
+
+static inline void
+gsk_ngl_program_set_uniform2f (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ float value0,
+ float value1)
+{
+ gsk_ngl_uniform_state_set2f (self->uniforms,
+ self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, value0, value1);
+}
+
+static inline void
+gsk_ngl_program_set_uniform3f (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ float value0,
+ float value1,
+ float value2)
+{
+ gsk_ngl_uniform_state_set3f (self->uniforms,
+ self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, value0, value1, value2);
+}
+
+static inline void
+gsk_ngl_program_set_uniform4f (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ float value0,
+ float value1,
+ float value2,
+ float value3)
+{
+ gsk_ngl_uniform_state_set4f (self->uniforms,
+ self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, value0, value1, value2, value3);
+}
+
+static inline void
+gsk_ngl_program_set_uniform_color (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ const GdkRGBA *color)
+{
+ gsk_ngl_uniform_state_set_color (self->uniforms,
+ self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, color);
+}
+
+static inline void
+gsk_ngl_program_set_uniform_texture (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ GLenum texture_target,
+ GLenum texture_slot,
+ guint texture_id)
+{
+ gsk_ngl_attachment_state_bind_texture (self->driver->command_queue->attachments,
+ texture_target,
+ texture_slot,
+ texture_id);
+ gsk_ngl_uniform_state_set_texture (self->uniforms,
+ self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, texture_slot);
+}
+
+static inline void
+gsk_ngl_program_set_uniform_matrix (GskNglProgram *self,
+ guint key,
+ guint stamp,
+ const graphene_matrix_t *matrix)
+{
+ gsk_ngl_uniform_state_set_matrix (self->uniforms,
+ self->program_info,
+ gsk_ngl_program_get_uniform_location (self, key),
+ stamp, matrix);
+}
+
+G_END_DECLS
+
+#endif /* __GSK_NGL_PROGRAM_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglprograms.defs b/gsk/ngl/gsknglprograms.defs
new file mode 100644
index 0000000000..b0b797f0e9
--- /dev/null
+++ b/gsk/ngl/gsknglprograms.defs
@@ -0,0 +1,83 @@
+GSK_NGL_DEFINE_PROGRAM (blend,
+ "/org/gtk/libgsk/glsl/blend.glsl",
+ GSK_NGL_ADD_UNIFORM (1, BLEND_SOURCE2, u_source2)
+ GSK_NGL_ADD_UNIFORM (2, BLEND_MODE, u_mode))
+
+GSK_NGL_DEFINE_PROGRAM (blit,
+ "/org/gtk/libgsk/glsl/blit.glsl",
+ GSK_NGL_NO_UNIFORMS)
+
+GSK_NGL_DEFINE_PROGRAM (blur,
+ "/org/gtk/libgsk/glsl/blur.glsl",
+ GSK_NGL_ADD_UNIFORM (1, BLUR_RADIUS, u_blur_radius)
+ GSK_NGL_ADD_UNIFORM (2, BLUR_SIZE, u_blur_size)
+ GSK_NGL_ADD_UNIFORM (3, BLUR_DIR, u_blur_dir))
+
+GSK_NGL_DEFINE_PROGRAM (border,
+ "/org/gtk/libgsk/glsl/border.glsl",
+ GSK_NGL_ADD_UNIFORM (1, BORDER_COLOR, u_color)
+ GSK_NGL_ADD_UNIFORM (2, BORDER_WIDTHS, u_widths)
+ GSK_NGL_ADD_UNIFORM (3, BORDER_OUTLINE_RECT, u_outline_rect))
+
+GSK_NGL_DEFINE_PROGRAM (color,
+ "/org/gtk/libgsk/glsl/color.glsl",
+ GSK_NGL_ADD_UNIFORM (1, COLOR_COLOR, u_color))
+
+GSK_NGL_DEFINE_PROGRAM (coloring,
+ "/org/gtk/libgsk/glsl/coloring.glsl",
+ GSK_NGL_ADD_UNIFORM (1, COLORING_COLOR, u_color))
+
+GSK_NGL_DEFINE_PROGRAM (color_matrix,
+ "/org/gtk/libgsk/glsl/color_matrix.glsl",
+ GSK_NGL_ADD_UNIFORM (1, COLOR_MATRIX_COLOR_MATRIX, u_color_matrix)
+ GSK_NGL_ADD_UNIFORM (2, COLOR_MATRIX_COLOR_OFFSET, u_color_offset))
+
+GSK_NGL_DEFINE_PROGRAM (conic_gradient,
+ "/org/gtk/libgsk/glsl/conic_gradient.glsl",
+ GSK_NGL_ADD_UNIFORM (1, CONIC_GRADIENT_COLOR_STOPS, u_color_stops)
+ GSK_NGL_ADD_UNIFORM (2, CONIC_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
+ GSK_NGL_ADD_UNIFORM (3, CONIC_GRADIENT_GEOMETRY, u_geometry))
+
+GSK_NGL_DEFINE_PROGRAM (cross_fade,
+ "/org/gtk/libgsk/glsl/cross_fade.glsl",
+ GSK_NGL_ADD_UNIFORM (1, CROSS_FADE_PROGRESS, u_progress)
+ GSK_NGL_ADD_UNIFORM (2, CROSS_FADE_SOURCE2, u_source2))
+
+GSK_NGL_DEFINE_PROGRAM (inset_shadow,
+ "/org/gtk/libgsk/glsl/inset_shadow.glsl",
+ GSK_NGL_ADD_UNIFORM (1, INSET_SHADOW_COLOR, u_color)
+ GSK_NGL_ADD_UNIFORM (2, INSET_SHADOW_SPREAD, u_spread)
+ GSK_NGL_ADD_UNIFORM (3, INSET_SHADOW_OFFSET, u_offset)
+ GSK_NGL_ADD_UNIFORM (4, INSET_SHADOW_OUTLINE_RECT, u_outline_rect))
+
+GSK_NGL_DEFINE_PROGRAM (linear_gradient,
+ "/org/gtk/libgsk/glsl/linear_gradient.glsl",
+ GSK_NGL_ADD_UNIFORM (1, LINEAR_GRADIENT_COLOR_STOPS, u_color_stops)
+ GSK_NGL_ADD_UNIFORM (2, LINEAR_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
+ GSK_NGL_ADD_UNIFORM (3, LINEAR_GRADIENT_POINTS, u_points)
+ GSK_NGL_ADD_UNIFORM (4, LINEAR_GRADIENT_REPEAT, u_repeat))
+
+GSK_NGL_DEFINE_PROGRAM (outset_shadow,
+ "/org/gtk/libgsk/glsl/outset_shadow.glsl",
+ GSK_NGL_ADD_UNIFORM (1, OUTSET_SHADOW_COLOR, u_color)
+ GSK_NGL_ADD_UNIFORM (2, OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect))
+
+GSK_NGL_DEFINE_PROGRAM (radial_gradient,
+ "/org/gtk/libgsk/glsl/radial_gradient.glsl",
+ GSK_NGL_ADD_UNIFORM (1, RADIAL_GRADIENT_COLOR_STOPS, u_color_stops)
+ GSK_NGL_ADD_UNIFORM (2, RADIAL_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
+ GSK_NGL_ADD_UNIFORM (3, RADIAL_GRADIENT_REPEAT, u_repeat)
+ GSK_NGL_ADD_UNIFORM (4, RADIAL_GRADIENT_RANGE, u_range)
+ GSK_NGL_ADD_UNIFORM (5, RADIAL_GRADIENT_GEOMETRY, u_geometry))
+
+GSK_NGL_DEFINE_PROGRAM (repeat,
+ "/org/gtk/libgsk/glsl/repeat.glsl",
+ GSK_NGL_ADD_UNIFORM (1, REPEAT_CHILD_BOUNDS, u_child_bounds)
+ GSK_NGL_ADD_UNIFORM (2, REPEAT_TEXTURE_RECT, u_texture_rect))
+
+GSK_NGL_DEFINE_PROGRAM (unblurred_outset_shadow,
+ "/org/gtk/libgsk/glsl/unblurred_outset_shadow.glsl",
+ GSK_NGL_ADD_UNIFORM (1, UNBLURRED_OUTSET_SHADOW_COLOR, u_color)
+ GSK_NGL_ADD_UNIFORM (2, UNBLURRED_OUTSET_SHADOW_SPREAD, u_spread)
+ GSK_NGL_ADD_UNIFORM (3, UNBLURRED_OUTSET_SHADOW_OFFSET, u_offset)
+ GSK_NGL_ADD_UNIFORM (4, UNBLURRED_OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect))
diff --git a/gsk/ngl/gsknglrenderer.c b/gsk/ngl/gsknglrenderer.c
new file mode 100644
index 0000000000..c20660f09e
--- /dev/null
+++ b/gsk/ngl/gsknglrenderer.c
@@ -0,0 +1,312 @@
+/* gsknglrenderer.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdkprofilerprivate.h>
+#include <gdk/gdksurfaceprivate.h>
+#include <gsk/gskdebugprivate.h>
+#include <gsk/gskrendererprivate.h>
+
+#include "gsknglcommandqueueprivate.h"
+#include "gskngldriverprivate.h"
+#include "gsknglprogramprivate.h"
+#include "gsknglrenderjobprivate.h"
+#include "gsknglrendererprivate.h"
+
+struct _GskNglRendererClass
+{
+ GskRendererClass parent_class;
+};
+
+struct _GskNglRenderer
+{
+ 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.
+ */
+ GskNglCommandQueue *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.
+ */
+ GskNglDriver *driver;
+};
+
+G_DEFINE_TYPE (GskNglRenderer, gsk_ngl_renderer, GSK_TYPE_RENDERER)
+
+GskRenderer *
+gsk_ngl_renderer_new (void)
+{
+ return g_object_new (GSK_TYPE_NGL_RENDERER, NULL);
+}
+
+static gboolean
+gsk_ngl_renderer_realize (GskRenderer *renderer,
+ GdkSurface *surface,
+ GError **error)
+{
+ G_GNUC_UNUSED gint64 start_time = GDK_PROFILER_CURRENT_TIME;
+ GskNglRenderer *self = (GskNglRenderer *)renderer;
+ GdkGLContext *context = NULL;
+ GdkGLContext *shared_context;
+ GskNglDriver *driver = NULL;
+ gboolean ret = FALSE;
+ gboolean debug_shaders = FALSE;
+
+ g_assert (GSK_IS_NGL_RENDERER (self));
+ g_assert (GDK_IS_SURFACE (surface));
+
+ if (self->context != NULL)
+ return TRUE;
+
+ g_assert (self->driver == NULL);
+ g_assert (self->context == NULL);
+ g_assert (self->command_queue == NULL);
+
+ if (!(context = gdk_surface_create_gl_context (surface, error)) ||
+ !gdk_gl_context_realize (context, error))
+ goto failure;
+
+ if (!(shared_context = gdk_surface_get_shared_data_gl_context (surface)))
+ {
+ g_set_error (error,
+ GDK_GL_ERROR,
+ GDK_GL_ERROR_NOT_AVAILABLE,
+ "Failed to locate shared GL context for driver");
+ goto failure;
+ }
+
+#ifdef G_ENABLE_DEBUG
+ if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), SHADERS))
+ debug_shaders = TRUE;
+#endif
+
+ if (!(driver = gsk_ngl_driver_from_shared_context (shared_context, debug_shaders, error)))
+ goto failure;
+
+ self->command_queue = gsk_ngl_driver_create_command_queue (driver, context);
+ self->context = g_steal_pointer (&context);
+ self->driver = g_steal_pointer (&driver);
+
+ gsk_ngl_command_queue_set_profiler (self->command_queue,
+ gsk_renderer_get_profiler (renderer));
+
+ ret = TRUE;
+
+failure:
+ g_clear_object (&driver);
+ g_clear_object (&context);
+
+ gdk_profiler_end_mark (start_time, "GskNglRenderer realize", NULL);
+
+ return ret;
+}
+
+static void
+gsk_ngl_renderer_unrealize (GskRenderer *renderer)
+{
+ GskNglRenderer *self = (GskNglRenderer *)renderer;
+
+ g_assert (GSK_IS_NGL_RENDERER (renderer));
+
+ g_clear_object (&self->driver);
+ g_clear_object (&self->context);
+ g_clear_object (&self->command_queue);
+}
+
+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_frame_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_ngl_renderer_render (GskRenderer *renderer,
+ GskRenderNode *root,
+ const cairo_region_t *update_area)
+{
+ GskNglRenderer *self = (GskNglRenderer *)renderer;
+ cairo_region_t *render_region;
+ graphene_rect_t viewport;
+ GskNglRenderJob *job;
+ GdkSurface *surface;
+ float scale_factor;
+
+ g_assert (GSK_IS_NGL_RENDERER (renderer));
+ g_assert (root != NULL);
+
+ surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (self->context));
+ scale_factor = gdk_surface_get_scale_factor (surface);
+
+ viewport.origin.x = 0;
+ viewport.origin.y = 0;
+ viewport.size.width = gdk_surface_get_width (surface) * scale_factor;
+ viewport.size.height = gdk_surface_get_height (surface) * scale_factor;
+
+ gdk_gl_context_make_current (self->context);
+ gdk_draw_context_begin_frame (GDK_DRAW_CONTEXT (self->context), update_area);
+
+ /* Must be called *AFTER* gdk_draw_context_begin_frame() */
+ render_region = get_render_region (surface, self->context);
+
+ gsk_ngl_driver_begin_frame (self->driver, self->command_queue);
+ job = gsk_ngl_render_job_new (self->driver, &viewport, scale_factor, render_region, 0);
+#ifdef G_ENABLE_DEBUG
+ if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), FALLBACK))
+ gsk_ngl_render_job_set_debug_fallback (job, TRUE);
+#endif
+ gsk_ngl_render_job_render (job, root);
+ gsk_ngl_driver_end_frame (self->driver);
+ gsk_ngl_render_job_free (job);
+
+ gdk_gl_context_make_current (self->context);
+ gdk_draw_context_end_frame (GDK_DRAW_CONTEXT (self->context));
+
+ gsk_ngl_driver_after_frame (self->driver);
+
+ cairo_region_destroy (render_region);
+}
+
+static GdkTexture *
+gsk_ngl_renderer_render_texture (GskRenderer *renderer,
+ GskRenderNode *root,
+ const graphene_rect_t *viewport)
+{
+ GskNglRenderer *self = (GskNglRenderer *)renderer;
+ GskNglRenderTarget *render_target;
+ GskNglRenderJob *job;
+ GdkTexture *texture = NULL;
+ guint texture_id;
+ int width;
+ int height;
+
+ g_assert (GSK_IS_NGL_RENDERER (renderer));
+ g_assert (root != NULL);
+
+ width = ceilf (viewport->size.width);
+ height = ceilf (viewport->size.height);
+
+ if (gsk_ngl_driver_create_render_target (self->driver,
+ width, height,
+ GL_NEAREST, GL_NEAREST,
+ &render_target))
+ {
+ gsk_ngl_driver_begin_frame (self->driver, self->command_queue);
+ job = gsk_ngl_render_job_new (self->driver, viewport, 1, NULL, render_target->framebuffer_id);
+#ifdef G_ENABLE_DEBUG
+ if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), FALLBACK))
+ gsk_ngl_render_job_set_debug_fallback (job, TRUE);
+#endif
+ gsk_ngl_render_job_render_flipped (job, root);
+ texture_id = gsk_ngl_driver_release_render_target (self->driver, render_target, FALSE);
+ texture = gsk_ngl_driver_create_gdk_texture (self->driver, texture_id);
+ gsk_ngl_driver_end_frame (self->driver);
+ gsk_ngl_render_job_free (job);
+
+ gsk_ngl_driver_after_frame (self->driver);
+ }
+
+ return g_steal_pointer (&texture);
+}
+
+static void
+gsk_ngl_renderer_dispose (GObject *object)
+{
+#ifdef G_ENABLE_DEBUG
+ GskNglRenderer *self = (GskNglRenderer *)object;
+
+ g_assert (self->driver == NULL);
+#endif
+
+ G_OBJECT_CLASS (gsk_ngl_renderer_parent_class)->dispose (object);
+}
+
+static void
+gsk_ngl_renderer_class_init (GskNglRendererClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+ GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
+
+ object_class->dispose = gsk_ngl_renderer_dispose;
+
+ renderer_class->realize = gsk_ngl_renderer_realize;
+ renderer_class->unrealize = gsk_ngl_renderer_unrealize;
+ renderer_class->render = gsk_ngl_renderer_render;
+ renderer_class->render_texture = gsk_ngl_renderer_render_texture;
+}
+
+static void
+gsk_ngl_renderer_init (GskNglRenderer *self)
+{
+}
+
+gboolean
+gsk_ngl_renderer_try_compile_gl_shader (GskNglRenderer *renderer,
+ GskGLShader *shader,
+ GError **error)
+{
+ GskNglProgram *program;
+
+ g_return_val_if_fail (GSK_IS_NGL_RENDERER (renderer), FALSE);
+ g_return_val_if_fail (shader != NULL, FALSE);
+
+ program = gsk_ngl_driver_lookup_shader (renderer->driver, shader, error);
+
+ return program != NULL;
+}
diff --git a/gsk/ngl/gsknglrenderer.h b/gsk/ngl/gsknglrenderer.h
new file mode 100644
index 0000000000..014c7755c8
--- /dev/null
+++ b/gsk/ngl/gsknglrenderer.h
@@ -0,0 +1,46 @@
+/* gsknglrenderer.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_RENDERER_H__
+#define __GSK_NGL_RENDERER_H__
+
+#include <gsk/gskrenderer.h>
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_NGL_RENDERER (gsk_ngl_renderer_get_type())
+
+#define GSK_NGL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_NGL_RENDERER, GskNglRenderer))
+#define GSK_IS_NGL_RENDERER(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_NGL_RENDERER))
+#define GSK_NGL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_NGL_RENDERER, GskNglRendererClass))
+#define GSK_IS_NGL_RENDERER_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_NGL_RENDERER))
+#define GSK_NGL_RENDERER_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_NGL_RENDERER, GskNglRendererClass))
+
+typedef struct _GskNglRenderer GskNglRenderer;
+typedef struct _GskNglRendererClass GskNglRendererClass;
+
+GDK_AVAILABLE_IN_ALL
+GType gsk_ngl_renderer_get_type (void) G_GNUC_CONST;
+GDK_AVAILABLE_IN_ALL
+GskRenderer *gsk_ngl_renderer_new (void);
+
+G_END_DECLS
+
+#endif /* __GSK_NGL_RENDERER__ */
diff --git a/gsk/ngl/gsknglrendererprivate.h b/gsk/ngl/gsknglrendererprivate.h
new file mode 100644
index 0000000000..a0a861f0aa
--- /dev/null
+++ b/gsk/ngl/gsknglrendererprivate.h
@@ -0,0 +1,34 @@
+/* gsknglrendererprivate.h
+ *
+ * Copyright 2021 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_RENDERER_PRIVATE_H__
+#define __GSK_NGL_RENDERER_PRIVATE_H__
+
+#include "gsknglrenderer.h"
+
+G_BEGIN_DECLS
+
+gboolean gsk_ngl_renderer_try_compile_gl_shader (GskNglRenderer *renderer,
+ GskGLShader *shader,
+ GError **error);
+
+G_END_DECLS
+
+#endif /* __GSK_NGL_RENDERER_PRIVATE_H__ */
diff --git a/gsk/ngl/gsknglrenderjob.c b/gsk/ngl/gsknglrenderjob.c
new file mode 100644
index 0000000000..b8f983f862
--- /dev/null
+++ b/gsk/ngl/gsknglrenderjob.c
@@ -0,0 +1,3760 @@
+/* gsknglrenderjob.c
+ *
+ * Copyright 2017 Timm Bäder <mail@baedert.org>
+ * Copyright 2018 Matthias Clasen <mclasen@redhat.com>
+ * Copyright 2018 Alexander Larsson <alexl@redhat.com>
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdkglcontextprivate.h>
+#include <gdk/gdkprofilerprivate.h>
+#include <gdk/gdkrgbaprivate.h>
+#include <gsk/gskrendernodeprivate.h>
+#include <gsk/gskglshaderprivate.h>
+#include <gdk/gdktextureprivate.h>
+#include <gsk/gsktransformprivate.h>
+#include <gsk/gskroundedrectprivate.h>
+#include <math.h>
+#include <string.h>
+
+#include "gsknglcommandqueueprivate.h"
+#include "gskngldriverprivate.h"
+#include "gsknglglyphlibraryprivate.h"
+#include "gskngliconlibraryprivate.h"
+#include "gsknglprogramprivate.h"
+#include "gsknglrenderjobprivate.h"
+#include "gsknglshadowlibraryprivate.h"
+
+#include "ninesliceprivate.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_NGL_UNIFORM_ARRAY_BITS));
+
+#define rounded_rect_top_left(r) \
+ (GRAPHENE_RECT_INIT(r->bounds.origin.x, \
+ r->bounds.origin.y, \
+ r->corner[0].width, r->corner[0].height))
+#define rounded_rect_top_right(r) \
+ (GRAPHENE_RECT_INIT(r->bounds.origin.x + r->bounds.size.width - r->corner[1].width, \
+ r->bounds.origin.y, \
+ r->corner[1].width, r->corner[1].height))
+#define rounded_rect_bottom_right(r) \
+ (GRAPHENE_RECT_INIT(r->bounds.origin.x + r->bounds.size.width - r->corner[2].width, \
+ r->bounds.origin.y + r->bounds.size.height - r->corner[2].height, \
+ r->corner[2].width, r->corner[2].height))
+#define rounded_rect_bottom_left(r) \
+ (GRAPHENE_RECT_INIT(r->bounds.origin.x, \
+ r->bounds.origin.y + r->bounds.size.height - r->corner[2].height, \
+ r->corner[3].width, r->corner[3].height))
+#define rounded_rect_corner0(r) rounded_rect_top_left(r)
+#define rounded_rect_corner1(r) rounded_rect_top_right(r)
+#define rounded_rect_corner2(r) rounded_rect_bottom_right(r)
+#define rounded_rect_corner3(r) rounded_rect_bottom_left(r)
+#define rounded_rect_corner(r, i) (rounded_rect_corner##i(r))
+#define ALPHA_IS_CLEAR(alpha) ((alpha) < ((float) 0x00ff / (float) 0xffff))
+#define RGBA_IS_CLEAR(rgba) ALPHA_IS_CLEAR((rgba)->alpha)
+
+typedef struct _GskNglRenderClip
+{
+ GskRoundedRect rect;
+ guint is_rectilinear : 1;
+} GskNglRenderClip;
+
+typedef struct _GskNglRenderModelview
+{
+ GskTransform *transform;
+ float scale_x;
+ float scale_y;
+ float offset_x_before;
+ float offset_y_before;
+ graphene_matrix_t matrix;
+} GskNglRenderModelview;
+
+struct _GskNglRenderJob
+{
+ /* 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_ngl_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.
+ */
+ GskNglDriver *driver;
+
+ /* The command queue (which is just a faster pointer to the driver's
+ * command queue.
+ */
+ GskNglCommandQueue *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;
+
+ /* 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 GskNglRenderModelview updated as nodes are processed. The
+ * current modelview is the last element.
+ */
+ GArray *modelview;
+
+ /* An array of GskNglRenderClip updated as nodes are processed. The
+ * current clip is the last element.
+ */
+ GArray *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 GskNglRenderClip *current_clip;
+ const GskNglRenderModelview *current_modelview;
+
+ /* If we should be rendering red zones over fallback nodes */
+ guint debug_fallback : 1;
+};
+
+typedef struct _GskNglRenderOffscreen
+{
+ const graphene_rect_t *bounds;
+ struct {
+ float x;
+ float y;
+ float x2;
+ float y2;
+ } area;
+ guint texture_id;
+ guint force_offscreen : 1;
+ guint reset_clip : 1;
+ guint do_not_cache : 1;
+ guint linear_filter : 1;
+ guint was_offscreen : 1;
+} GskNglRenderOffscreen;
+
+static void gsk_ngl_render_job_visit_node (GskNglRenderJob *job,
+ const GskRenderNode *node);
+static gboolean gsk_ngl_render_job_visit_node_with_offscreen (GskNglRenderJob *job,
+ const GskRenderNode *node,
+ GskNglRenderOffscreen *offscreen);
+
+static inline void
+init_full_texture_region (GskNglRenderOffscreen *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 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_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 ((int)gsk_render_node_get_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_LINEAR_GRADIENT_NODE:
+ case GSK_DEBUG_NODE:
+ case GSK_TEXT_NODE:
+ return TRUE;
+
+ case GSK_TRANSFORM_NODE:
+ return node_supports_transform (gsk_transform_node_get_child (node));
+
+ default:
+ return FALSE;
+ }
+}
+
+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 gboolean G_GNUC_PURE
+rect_contains_rect (const graphene_rect_t *r1,
+ const graphene_rect_t *r2)
+{
+ return r2->origin.x >= r1->origin.x &&
+ (r2->origin.x + r2->size.width) <= (r1->origin.x + r1->size.width) &&
+ r2->origin.y >= r1->origin.y &&
+ (r2->origin.y + r2->size.height) <= (r1->origin.y + r1->size.height);
+}
+
+static inline gboolean
+rounded_inner_rect_contains_rect (const GskRoundedRect *rounded,
+ const graphene_rect_t *rect)
+{
+ const graphene_rect_t *rounded_bounds = &rounded->bounds;
+ graphene_rect_t inner;
+ float offset_x;
+ float offset_y;
+
+ /* TODO: This is pretty conservative and we could go further,
+ * more fine-grained checks to avoid offscreen drawing.
+ */
+
+ offset_x = MAX (rounded->corner[GSK_CORNER_TOP_LEFT].width,
+ rounded->corner[GSK_CORNER_BOTTOM_LEFT].width);
+ offset_y = MAX (rounded->corner[GSK_CORNER_TOP_LEFT].height,
+ rounded->corner[GSK_CORNER_TOP_RIGHT].height);
+
+ inner.origin.x = rounded_bounds->origin.x + offset_x;
+ inner.origin.y = rounded_bounds->origin.y + offset_y;
+ inner.size.width = rounded_bounds->size.width - offset_x -
+ MAX (rounded->corner[GSK_CORNER_TOP_RIGHT].width,
+ rounded->corner[GSK_CORNER_BOTTOM_RIGHT].width);
+ inner.size.height = rounded_bounds->size.height - offset_y -
+ MAX (rounded->corner[GSK_CORNER_BOTTOM_LEFT].height,
+ rounded->corner[GSK_CORNER_BOTTOM_RIGHT].height);
+
+ return rect_contains_rect (&inner, rect);
+}
+
+static inline gboolean G_GNUC_PURE
+rect_intersects (const graphene_rect_t *r1,
+ const graphene_rect_t *r2)
+{
+ /* Assume both rects are already normalized, as they usually are */
+ if (r1->origin.x > (r2->origin.x + r2->size.width) ||
+ (r1->origin.x + r1->size.width) < r2->origin.x)
+ return FALSE;
+ else if (r1->origin.y > (r2->origin.y + r2->size.height) ||
+ (r1->origin.y + r1->size.height) < r2->origin.y)
+ return FALSE;
+ else
+ return TRUE;
+}
+
+static inline gboolean
+rounded_rect_has_corner (const GskRoundedRect *r,
+ guint i)
+{
+ return r->corner[i].width > 0 && r->corner[i].height > 0;
+}
+
+/* Current clip is NOT rounded but new one is definitely! */
+static inline gboolean
+intersect_rounded_rectilinear (const graphene_rect_t *non_rounded,
+ const GskRoundedRect *rounded,
+ GskRoundedRect *result)
+{
+ gboolean corners[4];
+
+ /* Intersects with top left corner? */
+ corners[0] = rounded_rect_has_corner (rounded, 0) &&
+ rect_intersects (non_rounded,
+ &rounded_rect_corner (rounded, 0));
+ /* top right? */
+ corners[1] = rounded_rect_has_corner (rounded, 1) &&
+ rect_intersects (non_rounded,
+ &rounded_rect_corner (rounded, 1));
+ /* bottom right? */
+ corners[2] = rounded_rect_has_corner (rounded, 2) &&
+ rect_intersects (non_rounded,
+ &rounded_rect_corner (rounded, 2));
+ /* bottom left */
+ corners[3] = rounded_rect_has_corner (rounded, 3) &&
+ rect_intersects (non_rounded,
+ &rounded_rect_corner (rounded, 3));
+
+ if (corners[0] && !rect_contains_rect (non_rounded, &rounded_rect_corner (rounded, 0)))
+ return FALSE;
+ if (corners[1] && !rect_contains_rect (non_rounded, &rounded_rect_corner (rounded, 1)))
+ return FALSE;
+ if (corners[2] && !rect_contains_rect (non_rounded, &rounded_rect_corner (rounded, 2)))
+ return FALSE;
+ if (corners[3] && !rect_contains_rect (non_rounded, &rounded_rect_corner (rounded, 3)))
+ return FALSE;
+
+ /* We do intersect with at least one of the corners, but in such a way that the
+ * intersection between the two clips can still be represented by a single rounded
+ * rect in a trivial way. do that. */
+ graphene_rect_intersection (non_rounded, &rounded->bounds, &result->bounds);
+
+ for (guint i = 0; i < 4; i++)
+ {
+ if (corners[i])
+ result->corner[i] = rounded->corner[i];
+ else
+ result->corner[i].width = result->corner[i].height = 0;
+ }
+
+ return TRUE;
+}
+
+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_ngl_render_job_set_alpha (GskNglRenderJob *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 (GskNglRenderModelview *modelview)
+{
+ float dummy;
+ graphene_matrix_t m;
+
+ gsk_transform_to_matrix (modelview->transform, &modelview->matrix);
+
+ switch (gsk_transform_get_category (modelview->transform))
+ {
+ case GSK_TRANSFORM_CATEGORY_IDENTITY:
+ case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
+ modelview->scale_x = 1;
+ modelview->scale_y = 1;
+ break;
+
+ case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
+ gsk_transform_to_affine (modelview->transform,
+ &modelview->scale_x, &modelview->scale_y,
+ &dummy, &dummy);
+ break;
+
+ case GSK_TRANSFORM_CATEGORY_UNKNOWN:
+ case GSK_TRANSFORM_CATEGORY_ANY:
+ case GSK_TRANSFORM_CATEGORY_3D:
+ case GSK_TRANSFORM_CATEGORY_2D:
+ {
+ graphene_vec3_t col1;
+ graphene_vec3_t col2;
+
+ /* TODO: 90% sure this is incorrect. But we should never hit this code
+ * path anyway. */
+ graphene_vec3_init (&col1,
+ graphene_matrix_get_value (&m, 0, 0),
+ graphene_matrix_get_value (&m, 1, 0),
+ graphene_matrix_get_value (&m, 2, 0));
+
+ graphene_vec3_init (&col2,
+ graphene_matrix_get_value (&m, 0, 1),
+ graphene_matrix_get_value (&m, 1, 1),
+ graphene_matrix_get_value (&m, 2, 1));
+
+ modelview->scale_x = graphene_vec3_length (&col1);
+ modelview->scale_y = graphene_vec3_length (&col2);
+ }
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+gsk_ngl_render_job_set_modelview (GskNglRenderJob *job,
+ GskTransform *transform)
+{
+ GskNglRenderModelview *modelview;
+
+ g_assert (job != NULL);
+ g_assert (job->modelview != NULL);
+
+ job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++;
+
+ g_array_set_size (job->modelview, job->modelview->len + 1);
+
+ modelview = &g_array_index (job->modelview,
+ GskNglRenderModelview,
+ job->modelview->len - 1);
+
+ 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;
+}
+
+static void
+gsk_ngl_render_job_push_modelview (GskNglRenderJob *job,
+ GskTransform *transform)
+{
+ GskNglRenderModelview *modelview;
+
+ g_assert (job != NULL);
+ g_assert (job->modelview != NULL);
+ g_assert (transform != NULL);
+
+ job->driver->stamps[UNIFORM_SHARED_MODELVIEW]++;
+
+ g_array_set_size (job->modelview, job->modelview->len + 1);
+
+ modelview = &g_array_index (job->modelview,
+ GskNglRenderModelview,
+ job->modelview->len - 1);
+
+ if G_LIKELY (job->modelview->len > 1)
+ {
+ GskNglRenderModelview *last;
+ GskTransform *t = NULL;
+
+ last = &g_array_index (job->modelview,
+ GskNglRenderModelview,
+ job->modelview->len - 2);
+
+ /* Multiply given matrix with our previews 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_ngl_render_job_pop_modelview (GskNglRenderJob *job)
+{
+ const GskNglRenderModelview *head;
+
+ g_assert (job != NULL);
+ g_assert (job->modelview);
+ g_assert (job->modelview->len > 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->len--;
+
+ if (job->modelview->len >= 1)
+ {
+ head = &g_array_index (job->modelview, GskNglRenderModelview, job->modelview->len - 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_ngl_render_job_push_clip (GskNglRenderJob *job,
+ const GskRoundedRect *rect)
+{
+ GskNglRenderClip *clip;
+
+ g_assert (job != NULL);
+ g_assert (job->clip != NULL);
+ g_assert (rect != NULL);
+
+ job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++;
+
+ g_array_set_size (job->clip, job->clip->len + 1);
+
+ clip = &g_array_index (job->clip, GskNglRenderClip, job->clip->len - 1);
+ memcpy (&clip->rect, rect, sizeof *rect);
+ clip->is_rectilinear = gsk_rounded_rect_is_rectilinear (rect);
+
+ job->current_clip = clip;
+}
+
+static void
+gsk_ngl_render_job_pop_clip (GskNglRenderJob *job)
+{
+ g_assert (job != NULL);
+ g_assert (job->clip != NULL);
+ g_assert (job->clip->len > 0);
+
+ job->driver->stamps[UNIFORM_SHARED_CLIP_RECT]++;
+ job->current_clip--;
+ job->clip->len--;
+}
+
+static inline void
+gsk_ngl_render_job_offset (GskNglRenderJob *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_ngl_render_job_set_projection (GskNglRenderJob *job,
+ const graphene_matrix_t *projection)
+{
+ memcpy (&job->projection, projection, sizeof job->projection);
+ job->driver->stamps[UNIFORM_SHARED_PROJECTION]++;
+}
+
+static inline void
+gsk_ngl_render_job_set_projection_from_rect (GskNglRenderJob *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_ngl_render_job_set_projection_for_size (GskNglRenderJob *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_ngl_render_job_set_viewport (GskNglRenderJob *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_ngl_render_job_set_viewport_for_size (GskNglRenderJob *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_ngl_render_job_transform_bounds (GskNglRenderJob *job,
+ const graphene_rect_t *rect,
+ graphene_rect_t *out_rect)
+{
+ GskTransform *transform;
+ GskTransformCategory category;
+
+ g_assert (job != NULL);
+ g_assert (job->modelview->len > 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 dx, dy, scale_x, scale_y;
+
+ gsk_transform_to_affine (transform, &scale_x, &scale_y, &dx, &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;
+
+ /* Normaize 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_ngl_render_job_transform_rounded_rect (GskNglRenderJob *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 gboolean
+gsk_ngl_render_job_node_overlaps_clip (GskNglRenderJob *job,
+ const GskRenderNode *node)
+{
+ graphene_rect_t transformed_bounds;
+ gsk_ngl_render_job_transform_bounds (job, &node->bounds, &transformed_bounds);
+ return rect_intersects (&job->current_clip->rect.bounds, &transformed_bounds);
+}
+
+/* load_vertex_data_with_region */
+static inline void
+gsk_ngl_render_job_load_vertices_from_offscreen (GskNglRenderJob *job,
+ const graphene_rect_t *bounds,
+ const GskNglRenderOffscreen *offscreen)
+{
+ GskNglDrawVertex *vertices = gsk_ngl_command_queue_add_vertices (job->command_queue);
+ 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;
+
+ vertices[0].position[0] = min_x;
+ vertices[0].position[1] = min_y;
+ vertices[0].uv[0] = offscreen->area.x;
+ vertices[0].uv[1] = y1;
+
+ vertices[1].position[0] = min_x;
+ vertices[1].position[1] = max_y;
+ vertices[1].uv[0] = offscreen->area.x;
+ vertices[1].uv[1] = y2;
+
+ vertices[2].position[0] = max_x;
+ vertices[2].position[1] = min_y;
+ vertices[2].uv[0] = offscreen->area.x2;
+ vertices[2].uv[1] = y1;
+
+ vertices[3].position[0] = max_x;
+ vertices[3].position[1] = max_y;
+ vertices[3].uv[0] = offscreen->area.x2;
+ vertices[3].uv[1] = y2;
+
+ vertices[4].position[0] = min_x;
+ vertices[4].position[1] = max_y;
+ vertices[4].uv[0] = offscreen->area.x;
+ vertices[4].uv[1] = y2;
+
+ vertices[5].position[0] = max_x;
+ vertices[5].position[1] = min_y;
+ vertices[5].uv[0] = offscreen->area.x2;
+ vertices[5].uv[1] = y1;
+}
+
+/* load_float_vertex_data */
+static inline void
+gsk_ngl_render_job_draw (GskNglRenderJob *job,
+ float x,
+ float y,
+ float width,
+ float height)
+{
+ GskNglDrawVertex *vertices = gsk_ngl_command_queue_add_vertices (job->command_queue);
+ 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;
+
+ vertices[0].position[0] = min_x;
+ vertices[0].position[1] = min_y;
+ vertices[0].uv[0] = 0;
+ vertices[0].uv[1] = 0;
+
+ vertices[1].position[0] = min_x;
+ vertices[1].position[1] = max_y;
+ vertices[1].uv[0] = 0;
+ vertices[1].uv[1] = 1;
+
+ vertices[2].position[0] = max_x;
+ vertices[2].position[1] = min_y;
+ vertices[2].uv[0] = 1;
+ vertices[2].uv[1] = 0;
+
+ vertices[3].position[0] = max_x;
+ vertices[3].position[1] = max_y;
+ vertices[3].uv[0] = 1;
+ vertices[3].uv[1] = 1;
+
+ vertices[4].position[0] = min_x;
+ vertices[4].position[1] = max_y;
+ vertices[4].uv[0] = 0;
+ vertices[4].uv[1] = 1;
+
+ vertices[5].position[0] = max_x;
+ vertices[5].position[1] = min_y;
+ vertices[5].uv[0] = 1;
+ vertices[5].uv[1] = 0;
+}
+
+/* load_vertex_data */
+static inline void
+gsk_ngl_render_job_draw_rect (GskNglRenderJob *job,
+ const graphene_rect_t *bounds)
+{
+ gsk_ngl_render_job_draw (job,
+ bounds->origin.x,
+ bounds->origin.y,
+ bounds->size.width,
+ bounds->size.height);
+}
+
+/* fill_vertex_data */
+static void
+gsk_ngl_render_job_draw_coords (GskNglRenderJob *job,
+ float min_x,
+ float min_y,
+ float max_x,
+ float max_y)
+{
+ GskNglDrawVertex *vertices = gsk_ngl_command_queue_add_vertices (job->command_queue);
+
+ vertices[0].position[0] = min_x;
+ vertices[0].position[1] = min_y;
+ vertices[0].uv[0] = 0;
+ vertices[0].uv[1] = 1;
+
+ vertices[1].position[0] = min_x;
+ vertices[1].position[1] = max_y;
+ vertices[1].uv[0] = 0;
+ vertices[1].uv[1] = 0;
+
+ vertices[2].position[0] = max_x;
+ vertices[2].position[1] = min_y;
+ vertices[2].uv[0] = 1;
+ vertices[2].uv[1] = 1;
+
+ vertices[3].position[0] = max_x;
+ vertices[3].position[1] = max_y;
+ vertices[3].uv[0] = 1;
+ vertices[3].uv[1] = 0;
+
+ vertices[4].position[0] = min_x;
+ vertices[4].position[1] = max_y;
+ vertices[4].uv[0] = 0;
+ vertices[4].uv[1] = 0;
+
+ vertices[5].position[0] = max_x;
+ vertices[5].position[1] = min_y;
+ vertices[5].uv[0] = 1;
+ vertices[5].uv[1] = 1;
+}
+
+/* load_offscreen_vertex_data */
+static inline void
+gsk_ngl_render_job_draw_offscreen_rect (GskNglRenderJob *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;
+
+ gsk_ngl_render_job_draw_coords (job, min_x, min_y, max_x, max_y);
+}
+
+static inline void
+gsk_ngl_render_job_begin_draw (GskNglRenderJob *job,
+ GskNglProgram *program)
+{
+ gsk_ngl_command_queue_begin_draw (job->command_queue,
+ program->program_info,
+ job->viewport.size.width,
+ job->viewport.size.height);
+
+ if (program->uniform_locations[UNIFORM_SHARED_VIEWPORT] > -1)
+ gsk_ngl_uniform_state_set4fv (program->uniforms,
+ program->program_info,
+ program->uniform_locations[UNIFORM_SHARED_VIEWPORT],
+ job->driver->stamps[UNIFORM_SHARED_VIEWPORT],
+ 1,
+ (const float *)&job->viewport);
+
+ if (program->uniform_locations[UNIFORM_SHARED_MODELVIEW] > -1)
+ gsk_ngl_uniform_state_set_matrix (program->uniforms,
+ program->program_info,
+ program->uniform_locations[UNIFORM_SHARED_MODELVIEW],
+ job->driver->stamps[UNIFORM_SHARED_MODELVIEW],
+ &job->current_modelview->matrix);
+
+ if (program->uniform_locations[UNIFORM_SHARED_PROJECTION] > -1)
+ gsk_ngl_uniform_state_set_matrix (program->uniforms,
+ program->program_info,
+ program->uniform_locations[UNIFORM_SHARED_PROJECTION],
+ job->driver->stamps[UNIFORM_SHARED_PROJECTION],
+ &job->projection);
+
+ if (program->uniform_locations[UNIFORM_SHARED_CLIP_RECT] > -1)
+ gsk_ngl_uniform_state_set_rounded_rect (program->uniforms,
+ program->program_info,
+ program->uniform_locations[UNIFORM_SHARED_CLIP_RECT],
+ job->driver->stamps[UNIFORM_SHARED_CLIP_RECT],
+ &job->current_clip->rect);
+
+ if (program->uniform_locations[UNIFORM_SHARED_ALPHA] > -1)
+ gsk_ngl_uniform_state_set1f (program->uniforms,
+ program->program_info,
+ program->uniform_locations[UNIFORM_SHARED_ALPHA],
+ job->driver->stamps[UNIFORM_SHARED_ALPHA],
+ job->alpha);
+}
+
+static inline void
+gsk_ngl_render_job_split_draw (GskNglRenderJob *job)
+{
+ gsk_ngl_command_queue_split_draw (job->command_queue);
+}
+
+static inline void
+gsk_ngl_render_job_end_draw (GskNglRenderJob *job)
+{
+ gsk_ngl_command_queue_end_draw (job->command_queue);
+}
+
+static inline void
+gsk_ngl_render_job_visit_as_fallback (GskNglRenderJob *job,
+ const GskRenderNode *node)
+{
+ float scale_x = job->scale_x;
+ float scale_y = job->scale_y;
+ int surface_width = ceilf (node->bounds.size.width * scale_x);
+ int surface_height = ceilf (node->bounds.size.height * scale_y);
+ GdkTexture *texture;
+ cairo_surface_t *surface;
+ cairo_surface_t *rendered_surface;
+ cairo_t *cr;
+ int cached_id;
+ 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;
+ key.filter = GL_NEAREST;
+
+ cached_id = gsk_ngl_driver_lookup_texture (job->driver, &key);
+
+ if (cached_id != 0)
+ {
+ gsk_ngl_render_job_begin_draw (job, job->driver->blit);
+ gsk_ngl_program_set_uniform_texture (job->driver->blit,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D, GL_TEXTURE0, cached_id);
+ gsk_ngl_render_job_draw_offscreen_rect (job, &node->bounds);
+ gsk_ngl_render_job_end_draw (job);
+ return;
+ }
+
+ /* 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, scale_x, scale_y);
+ cr = cairo_create (rendered_surface);
+
+ cairo_save (cr);
+ cairo_translate (cr, - floorf (node->bounds.origin.x), - floorf (node->bounds.origin.y));
+ /* Render nodes don't modify state, so casting away the const is fine here */
+ gsk_render_node_draw ((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, scale_x, scale_y);
+ cr = cairo_create (surface);
+
+ /* We draw upside down here, so it matches what GL does. */
+ cairo_save (cr);
+ cairo_scale (cr, 1, -1);
+ cairo_translate (cr, 0, - surface_height / scale_y);
+ cairo_set_source_surface (cr, rendered_surface, 0, 0);
+ cairo_rectangle (cr, 0, 0, surface_width / scale_x, surface_height / scale_y);
+ cairo_fill (cr);
+ cairo_restore (cr);
+
+#ifdef G_ENABLE_DEBUG
+ if (job->debug_fallback)
+ {
+ cairo_move_to (cr, 0, 0);
+ cairo_rectangle (cr, 0, 0, node->bounds.size.width, node->bounds.size.height);
+ if (gsk_render_node_get_node_type (node) == GSK_CAIRO_NODE)
+ cairo_set_source_rgba (cr, 0.3, 0, 1, 0.25);
+ else
+ cairo_set_source_rgba (cr, 1, 0, 0, 0.25);
+ cairo_fill_preserve (cr);
+ if (gsk_render_node_get_node_type (node) == GSK_CAIRO_NODE)
+ cairo_set_source_rgba (cr, 0.3, 0, 1, 1);
+ else
+ cairo_set_source_rgba (cr, 1, 0, 0, 1);
+ cairo_stroke (cr);
+ }
+#endif
+ cairo_destroy (cr);
+
+ /* Create texture to upload */
+ texture = gdk_texture_new_for_surface (surface);
+ texture_id = gsk_ngl_driver_load_texture (job->driver, texture,
+ GL_NEAREST, GL_NEAREST);
+
+ if (gdk_gl_context_has_debug (job->command_queue->context))
+ 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_ngl_driver_cache_texture (job->driver, &key, texture_id);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->blit);
+ gsk_ngl_program_set_uniform_texture (job->driver->blit,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ texture_id);
+ gsk_ngl_render_job_draw_offscreen_rect (job, &node->bounds);
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static guint
+blur_offscreen (GskNglRenderJob *job,
+ GskNglRenderOffscreen *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);
+ GskNglRenderTarget *pass1;
+ GskNglRenderTarget *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_ngl_driver_create_render_target (job->driver,
+ MAX (texture_to_blur_width, 1),
+ MAX (texture_to_blur_height, 1),
+ GL_NEAREST, GL_NEAREST,
+ &pass1))
+ return 0;
+
+ if (texture_to_blur_width <= 0 || texture_to_blur_height <= 0)
+ return gsk_ngl_driver_release_render_target (job->driver, pass1, FALSE);
+
+ if (!gsk_ngl_driver_create_render_target (job->driver,
+ texture_to_blur_width,
+ texture_to_blur_height,
+ GL_NEAREST, GL_NEAREST,
+ &pass2))
+ return gsk_ngl_driver_release_render_target (job->driver, pass1, FALSE);
+
+ gsk_ngl_render_job_set_viewport (job, &new_clip.bounds, &prev_viewport);
+ gsk_ngl_render_job_set_projection_from_rect (job, &new_clip.bounds, &prev_projection);
+ gsk_ngl_render_job_set_modelview (job, NULL);
+ gsk_ngl_render_job_push_clip (job, &new_clip);
+
+ /* Bind new framebuffer and clear it */
+ prev_fbo = gsk_ngl_command_queue_bind_framebuffer (job->command_queue, pass1->framebuffer_id);
+ gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
+
+ /* Begin drawing the first horizontal pass, using offscreen as the
+ * source texture for the program.
+ */
+ gsk_ngl_render_job_begin_draw (job, job->driver->blur);
+ gsk_ngl_program_set_uniform_texture (job->driver->blur,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ offscreen->texture_id);
+ gsk_ngl_program_set_uniform1f (job->driver->blur,
+ UNIFORM_BLUR_RADIUS, 0,
+ blur_radius_x);
+ gsk_ngl_program_set_uniform2f (job->driver->blur,
+ UNIFORM_BLUR_SIZE, 0,
+ texture_to_blur_width,
+ texture_to_blur_height);
+ gsk_ngl_program_set_uniform2f (job->driver->blur,
+ UNIFORM_BLUR_DIR, 0,
+ 1, 0);
+ gsk_ngl_render_job_draw_coords (job, 0, 0, texture_to_blur_width, texture_to_blur_height);
+ gsk_ngl_render_job_end_draw (job);
+
+ /* Bind second pass framebuffer and clear it */
+ gsk_ngl_command_queue_bind_framebuffer (job->command_queue, pass2->framebuffer_id);
+ gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
+
+ /* Draw using blur program with first pass as source texture */
+ gsk_ngl_render_job_begin_draw (job, job->driver->blur);
+ gsk_ngl_program_set_uniform_texture (job->driver->blur,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ pass1->texture_id);
+ gsk_ngl_program_set_uniform1f (job->driver->blur,
+ UNIFORM_BLUR_RADIUS, 0,
+ blur_radius_y);
+ gsk_ngl_program_set_uniform2f (job->driver->blur,
+ UNIFORM_BLUR_SIZE, 0,
+ texture_to_blur_width,
+ texture_to_blur_height);
+ gsk_ngl_program_set_uniform2f (job->driver->blur,
+ UNIFORM_BLUR_DIR, 0,
+ 0, 1);
+ gsk_ngl_render_job_draw_coords (job, 0, 0, texture_to_blur_width, texture_to_blur_height);
+ gsk_ngl_render_job_end_draw (job);
+
+ gsk_ngl_render_job_pop_modelview (job);
+ gsk_ngl_render_job_pop_clip (job);
+ gsk_ngl_render_job_set_viewport (job, &prev_viewport, NULL);
+ gsk_ngl_render_job_set_projection (job, &prev_projection);
+ gsk_ngl_command_queue_bind_framebuffer (job->command_queue, prev_fbo);
+
+ gsk_ngl_driver_release_render_target (job->driver, pass1, TRUE);
+
+ return gsk_ngl_driver_release_render_target (job->driver, pass2, FALSE);
+}
+
+static void
+blur_node (GskNglRenderJob *job,
+ GskNglRenderOffscreen *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_ngl_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 * scale_x,
+ texture_height * scale_y,
+ blur_radius * scale_x,
+ blur_radius * 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;
+}
+
+static inline void
+gsk_ngl_render_job_visit_color_node (GskNglRenderJob *job,
+ const GskRenderNode *node)
+{
+ gsk_ngl_render_job_begin_draw (job, job->driver->color);
+ gsk_ngl_program_set_uniform_color (job->driver->color,
+ UNIFORM_COLOR_COLOR, 0,
+ gsk_color_node_get_color (node));
+ gsk_ngl_render_job_draw_rect (job, &node->bounds);
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_ngl_render_job_visit_linear_gradient_node (GskNglRenderJob *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_get_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);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->linear_gradient);
+ gsk_ngl_program_set_uniform1i (job->driver->linear_gradient,
+ UNIFORM_LINEAR_GRADIENT_NUM_COLOR_STOPS, 0,
+ n_color_stops);
+ gsk_ngl_program_set_uniform1fv (job->driver->linear_gradient,
+ UNIFORM_LINEAR_GRADIENT_COLOR_STOPS, 0,
+ n_color_stops * 5,
+ (const float *)stops);
+ gsk_ngl_program_set_uniform4f (job->driver->linear_gradient,
+ UNIFORM_LINEAR_GRADIENT_POINTS, 0,
+ x1, y1, x2 - x1, y2 - y1);
+ gsk_ngl_program_set_uniform1i (job->driver->linear_gradient,
+ UNIFORM_LINEAR_GRADIENT_REPEAT, 0,
+ repeat);
+ gsk_ngl_render_job_draw_rect (job, &node->bounds);
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_ngl_render_job_visit_conic_gradient_node (GskNglRenderJob *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);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->conic_gradient);
+ gsk_ngl_program_set_uniform1i (job->driver->conic_gradient,
+ UNIFORM_CONIC_GRADIENT_NUM_COLOR_STOPS, 0,
+ n_color_stops);
+ gsk_ngl_program_set_uniform1fv (job->driver->conic_gradient,
+ UNIFORM_CONIC_GRADIENT_COLOR_STOPS, 0,
+ n_color_stops * 5,
+ (const float *)stops);
+ gsk_ngl_program_set_uniform4f (job->driver->conic_gradient,
+ UNIFORM_CONIC_GRADIENT_GEOMETRY, 0,
+ job->offset_x + center->x,
+ job->offset_y + center->y,
+ scale,
+ bias);
+ gsk_ngl_render_job_draw_rect (job, &node->bounds);
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_ngl_render_job_visit_radial_gradient_node (GskNglRenderJob *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_get_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);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->radial_gradient);
+ gsk_ngl_program_set_uniform1i (job->driver->radial_gradient,
+ UNIFORM_RADIAL_GRADIENT_NUM_COLOR_STOPS, 0,
+ n_color_stops);
+ gsk_ngl_program_set_uniform1fv (job->driver->radial_gradient,
+ UNIFORM_RADIAL_GRADIENT_COLOR_STOPS, 0,
+ n_color_stops * 5,
+ (const float *)stops);
+ gsk_ngl_program_set_uniform1i (job->driver->radial_gradient,
+ UNIFORM_RADIAL_GRADIENT_REPEAT, 0,
+ repeat);
+ gsk_ngl_program_set_uniform2f (job->driver->radial_gradient,
+ UNIFORM_RADIAL_GRADIENT_RANGE, 0,
+ scale, bias);
+ gsk_ngl_program_set_uniform4f (job->driver->radial_gradient,
+ 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_ngl_render_job_draw_rect (job, &node->bounds);
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_ngl_render_job_visit_clipped_child (GskNglRenderJob *job,
+ const GskRenderNode *child,
+ const graphene_rect_t *clip)
+{
+ graphene_rect_t transformed_clip;
+ GskRoundedRect intersection;
+
+ gsk_ngl_render_job_transform_bounds (job, clip, &transformed_clip);
+
+ if (job->current_clip->is_rectilinear)
+ {
+ memset (&intersection.corner, 0, sizeof intersection.corner);
+ graphene_rect_intersection (&transformed_clip,
+ &job->current_clip->rect.bounds,
+ &intersection.bounds);
+
+ gsk_ngl_render_job_push_clip (job, &intersection);
+ gsk_ngl_render_job_visit_node (job, child);
+ gsk_ngl_render_job_pop_clip (job);
+ }
+ else if (intersect_rounded_rectilinear (&transformed_clip,
+ &job->current_clip->rect,
+ &intersection))
+ {
+ gsk_ngl_render_job_push_clip (job, &intersection);
+ gsk_ngl_render_job_visit_node (job, child);
+ gsk_ngl_render_job_pop_clip (job);
+ }
+ else
+ {
+ GskRoundedRect scaled_clip;
+ GskNglRenderOffscreen offscreen = {0};
+
+ offscreen.bounds = &child->bounds;
+ offscreen.force_offscreen = TRUE;
+
+ scaled_clip = GSK_ROUNDED_RECT_INIT ((job->offset_x + clip->origin.x) * job->scale_x,
+ (job->offset_y + clip->origin.y) * job->scale_y,
+ clip->size.width * job->scale_x,
+ clip->size.height * job->scale_y);
+
+ gsk_ngl_render_job_push_clip (job, &scaled_clip);
+ gsk_ngl_render_job_visit_node_with_offscreen (job, child, &offscreen);
+ gsk_ngl_render_job_pop_clip (job);
+
+ g_assert (offscreen.texture_id);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->blit);
+ gsk_ngl_program_set_uniform_texture (job->driver->blit,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ offscreen.texture_id);
+ gsk_ngl_render_job_draw_offscreen_rect (job, &child->bounds);
+ gsk_ngl_render_job_end_draw (job);
+ }
+}
+
+static inline void
+gsk_ngl_render_job_visit_clip_node (GskNglRenderJob *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_ngl_render_job_visit_clipped_child (job, child, clip);
+}
+
+static inline void
+gsk_ngl_render_job_visit_rounded_clip_node (GskNglRenderJob *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;
+ float scale_x = job->scale_x;
+ float scale_y = job->scale_y;
+ gboolean need_offscreen;
+
+ if (node_is_invisible (child))
+ return;
+
+ gsk_ngl_render_job_transform_bounds (job, &clip->bounds, &transformed_clip.bounds);
+
+ for (guint i = 0; i < G_N_ELEMENTS (transformed_clip.corner); i++)
+ {
+ transformed_clip.corner[i].width = clip->corner[i].width * scale_x;
+ transformed_clip.corner[i].height = clip->corner[i].height * scale_y;
+ }
+
+ if (job->current_clip->is_rectilinear)
+ {
+ GskRoundedRect intersected_clip;
+
+ if (intersect_rounded_rectilinear (&job->current_clip->rect.bounds,
+ &transformed_clip,
+ &intersected_clip))
+ {
+ gsk_ngl_render_job_push_clip (job, &intersected_clip);
+ gsk_ngl_render_job_visit_node (job, child);
+ gsk_ngl_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 (job->clip->len <= 1)
+ need_offscreen = FALSE;
+ else if (rounded_inner_rect_contains_rect (&job->current_clip->rect, &transformed_clip.bounds))
+ need_offscreen = FALSE;
+ else
+ need_offscreen = TRUE;
+
+ if (!need_offscreen)
+ {
+ /* If the new clip entirely contains the current clip, the intersection is simply
+ * the current clip, so we can ignore the new one.
+ */
+ if (rounded_inner_rect_contains_rect (&transformed_clip, &job->current_clip->rect.bounds))
+ {
+ gsk_ngl_render_job_visit_node (job, child);
+ return;
+ }
+
+ gsk_ngl_render_job_push_clip (job, &transformed_clip);
+ gsk_ngl_render_job_visit_node (job, child);
+ gsk_ngl_render_job_pop_clip (job);
+ }
+ else
+ {
+ GskNglRenderOffscreen offscreen = {0};
+
+ offscreen.bounds = &node->bounds;
+ offscreen.force_offscreen = TRUE;
+
+ gsk_ngl_render_job_push_clip (job, &transformed_clip);
+ if (!gsk_ngl_render_job_visit_node_with_offscreen (job, child, &offscreen))
+ g_assert_not_reached ();
+ gsk_ngl_render_job_pop_clip (job);
+
+ g_assert (offscreen.texture_id);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->blit);
+ gsk_ngl_program_set_uniform_texture (job->driver->blit,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ offscreen.texture_id);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job, &node->bounds, &offscreen);
+ gsk_ngl_render_job_end_draw (job);
+ }
+}
+
+static inline void
+sort_border_sides (const GdkRGBA *colors,
+ int *indices)
+{
+ gboolean done[4] = {0, 0, 0, 0};
+ guint cur = 0;
+
+ for (guint i = 0; i < 3; i++)
+ {
+ if (done[i])
+ continue;
+
+ indices[cur] = i;
+ done[i] = TRUE;
+ cur++;
+
+ for (guint k = i + 1; k < 4; k ++)
+ {
+ if (memcmp (&colors[k], &colors[i], sizeof (GdkRGBA)) == 0)
+ {
+ indices[cur] = k;
+ done[k] = TRUE;
+ cur++;
+ }
+ }
+
+ if (cur >= 4)
+ break;
+ }
+}
+
+static inline void
+gsk_ngl_render_job_visit_uniform_border_node (GskNglRenderJob *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);
+ GskRoundedRect outline;
+
+ gsk_ngl_render_job_transform_rounded_rect (job, rounded_outline, &outline);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->inset_shadow);
+ gsk_ngl_program_set_uniform_rounded_rect (job->driver->inset_shadow,
+ UNIFORM_INSET_SHADOW_OUTLINE_RECT, 0,
+ &outline);
+ gsk_ngl_program_set_uniform_color (job->driver->inset_shadow,
+ UNIFORM_INSET_SHADOW_COLOR, 0,
+ &colors[0]);
+ gsk_ngl_program_set_uniform1f (job->driver->inset_shadow,
+ UNIFORM_INSET_SHADOW_SPREAD, 0,
+ widths[0]);
+ gsk_ngl_program_set_uniform2f (job->driver->inset_shadow,
+ UNIFORM_INSET_SHADOW_OFFSET, 0,
+ 0, 0);
+ gsk_ngl_render_job_draw_rect (job, &node->bounds);
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_ngl_render_job_visit_border_node (GskNglRenderJob *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];
+
+ /* Top left */
+ if (widths[3] > 0)
+ sizes[0].w = MAX (widths[3], rounded_outline->corner[0].width);
+ else
+ sizes[0].w = 0;
+
+ if (widths[0] > 0)
+ sizes[0].h = MAX (widths[0], rounded_outline->corner[0].height);
+ else
+ sizes[0].h = 0;
+
+ /* Top right */
+ if (widths[1] > 0)
+ sizes[1].w = MAX (widths[1], rounded_outline->corner[1].width);
+ else
+ sizes[1].w = 0;
+
+ if (widths[0] > 0)
+ sizes[1].h = MAX (widths[0], rounded_outline->corner[1].height);
+ else
+ sizes[1].h = 0;
+
+ /* Bottom right */
+ if (widths[1] > 0)
+ sizes[2].w = MAX (widths[1], rounded_outline->corner[2].width);
+ else
+ sizes[2].w = 0;
+
+ if (widths[2] > 0)
+ sizes[2].h = MAX (widths[2], rounded_outline->corner[2].height);
+ else
+ sizes[2].h = 0;
+
+
+ /* Bottom left */
+ if (widths[3] > 0)
+ sizes[3].w = MAX (widths[3], rounded_outline->corner[3].width);
+ else
+ sizes[3].w = 0;
+
+ if (widths[2] > 0)
+ sizes[3].h = MAX (widths[2], rounded_outline->corner[3].height);
+ else
+ sizes[3].h = 0;
+
+ {
+ 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;
+ const GskNglDrawVertex side_data[4][6] = {
+ /* Top */
+ {
+ { { min_x, min_y }, { 0, 1 }, }, /* Upper left */
+ { { min_x + sizes[0].w, min_y + sizes[0].h }, { 0, 0 }, }, /* Lower left */
+ { { max_x, min_y }, { 1, 1 }, }, /* Upper right */
+
+ { { max_x - sizes[1].w, min_y + sizes[1].h }, { 1, 0 }, }, /* Lower right */
+ { { min_x + sizes[0].w, min_y + sizes[0].h }, { 0, 0 }, }, /* Lower left */
+ { { max_x, min_y }, { 1, 1 }, }, /* Upper right */
+ },
+ /* Right */
+ {
+ { { max_x - sizes[1].w, min_y + sizes[1].h }, { 0, 1 }, }, /* Upper left */
+ { { max_x - sizes[2].w, max_y - sizes[2].h }, { 0, 0 }, }, /* Lower left */
+ { { max_x, min_y }, { 1, 1 }, }, /* Upper right */
+
+ { { max_x, max_y }, { 1, 0 }, }, /* Lower right */
+ { { max_x - sizes[2].w, max_y - sizes[2].h }, { 0, 0 }, }, /* Lower left */
+ { { max_x, min_y }, { 1, 1 }, }, /* Upper right */
+ },
+ /* Bottom */
+ {
+ { { min_x + sizes[3].w, max_y - sizes[3].h }, { 0, 1 }, }, /* Upper left */
+ { { min_x, max_y }, { 0, 0 }, }, /* Lower left */
+ { { max_x - sizes[2].w, max_y - sizes[2].h }, { 1, 1 }, }, /* Upper right */
+
+ { { max_x, max_y }, { 1, 0 }, }, /* Lower right */
+ { { min_x , max_y }, { 0, 0 }, }, /* Lower left */
+ { { max_x - sizes[2].w, max_y - sizes[2].h }, { 1, 1 }, }, /* Upper right */
+ },
+ /* Left */
+ {
+ { { min_x, min_y }, { 0, 1 }, }, /* Upper left */
+ { { min_x, max_y }, { 0, 0 }, }, /* Lower left */
+ { { min_x + sizes[0].w, min_y + sizes[0].h }, { 1, 1 }, }, /* Upper right */
+
+ { { min_x + sizes[3].w, max_y - sizes[3].h }, { 1, 0 }, }, /* Lower right */
+ { { min_x, max_y }, { 0, 0 }, }, /* Lower left */
+ { { min_x + sizes[0].w, min_y + sizes[0].h }, { 1, 1 }, }, /* Upper right */
+ }
+ };
+ int indices[4] = { 0, 1, 2, 3 };
+ GskRoundedRect outline;
+
+ /* We sort them by color */
+ sort_border_sides (colors, indices);
+
+ /* Prepare outline */
+ gsk_ngl_render_job_transform_rounded_rect (job, rounded_outline, &outline);
+
+ gsk_ngl_program_set_uniform4fv (job->driver->border,
+ UNIFORM_BORDER_WIDTHS, 0,
+ 1,
+ widths);
+ gsk_ngl_program_set_uniform_rounded_rect (job->driver->border,
+ UNIFORM_BORDER_OUTLINE_RECT, 0,
+ &outline);
+
+ for (guint i = 0; i < 4; i++)
+ {
+ GskNglDrawVertex *vertices;
+
+ if (widths[indices[i]] <= 0)
+ continue;
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->border);
+ gsk_ngl_program_set_uniform4fv (job->driver->border,
+ UNIFORM_BORDER_COLOR, 0,
+ 1,
+ (const float *)&colors[indices[i]]);
+ vertices = gsk_ngl_command_queue_add_vertices (job->command_queue);
+ memcpy (vertices, side_data[indices[i]], sizeof (GskNglDrawVertex) * GSK_NGL_N_VERTICES);
+ gsk_ngl_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 (fabs (p->x - b1.x) > FLT_EPSILON && fabs (p->x - b2.x) > FLT_EPSILON)
+ return FALSE;
+ if (fabs (p->y - b1.y) > FLT_EPSILON && fabs (p->y - b2.y) > FLT_EPSILON)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static inline void
+gsk_ngl_render_job_visit_transform_node (GskNglRenderJob *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_ngl_render_job_visit_node (job, child);
+ break;
+
+ case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
+ {
+ float dx, dy;
+
+ gsk_transform_to_translate (transform, &dx, &dy);
+ gsk_ngl_render_job_offset (job, dx, dy);
+ gsk_ngl_render_job_visit_node (job, child);
+ gsk_ngl_render_job_offset (job, -dx, -dy);
+ }
+ break;
+
+ case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
+ {
+ gsk_ngl_render_job_push_modelview (job, transform);
+ gsk_ngl_render_job_visit_node (job, child);
+ gsk_ngl_render_job_pop_modelview (job);
+ }
+ break;
+
+ case GSK_TRANSFORM_CATEGORY_2D:
+ case GSK_TRANSFORM_CATEGORY_3D:
+ case GSK_TRANSFORM_CATEGORY_ANY:
+ case GSK_TRANSFORM_CATEGORY_UNKNOWN:
+ if (node_supports_transform (child))
+ {
+ gsk_ngl_render_job_push_modelview (job, transform);
+ gsk_ngl_render_job_visit_node (job, child);
+ gsk_ngl_render_job_pop_modelview (job);
+ }
+ else
+ {
+ GskNglRenderOffscreen offscreen = {0};
+
+ offscreen.bounds = &child->bounds;
+ offscreen.reset_clip = TRUE;
+
+ if (!result_is_axis_aligned (transform, &child->bounds))
+ offscreen.linear_filter = TRUE;
+
+ if (gsk_ngl_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. */
+ /* TODO: We should compute a modelview containing only the "non-trivial"
+ * part (e.g. the rotation) and use that. We want to keep the scale
+ * for the texture.
+ */
+ gsk_ngl_render_job_push_modelview (job, transform);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->blit);
+ gsk_ngl_program_set_uniform_texture (job->driver->blit,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ offscreen.texture_id);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job, &child->bounds, &offscreen);
+ gsk_ngl_render_job_end_draw (job);
+
+ gsk_ngl_render_job_pop_modelview (job);
+ }
+ }
+ break;
+
+ default:
+ g_assert_not_reached ();
+ }
+}
+
+static inline void
+gsk_ngl_render_job_visit_unblurred_inset_shadow_node (GskNglRenderJob *job,
+ const GskRenderNode *node)
+{
+ const GskRoundedRect *outline = gsk_inset_shadow_node_get_outline (node);
+ GskRoundedRect transformed_outline;
+
+ gsk_ngl_render_job_transform_rounded_rect (job, outline, &transformed_outline);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->inset_shadow);
+ gsk_ngl_program_set_uniform_rounded_rect (job->driver->inset_shadow,
+ UNIFORM_INSET_SHADOW_OUTLINE_RECT, 0,
+ &transformed_outline);
+ gsk_ngl_program_set_uniform_color (job->driver->inset_shadow,
+ UNIFORM_INSET_SHADOW_COLOR, 0,
+ gsk_inset_shadow_node_get_color (node));
+ gsk_ngl_program_set_uniform1f (job->driver->inset_shadow,
+ UNIFORM_INSET_SHADOW_SPREAD, 0,
+ gsk_inset_shadow_node_get_spread (node));
+ gsk_ngl_program_set_uniform2f (job->driver->inset_shadow,
+ UNIFORM_INSET_SHADOW_OFFSET, 0,
+ gsk_inset_shadow_node_get_dx (node),
+ gsk_inset_shadow_node_get_dy (node));
+ gsk_ngl_render_job_draw_rect (job, &node->bounds);
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_ngl_render_job_visit_blurred_inset_shadow_node (GskNglRenderJob *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;
+ GskNglRenderOffscreen offscreen = {0};
+
+ 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;
+ key.filter = GL_NEAREST;
+
+ blurred_texture_id = gsk_ngl_driver_lookup_texture (job->driver, &key);
+
+ if (blurred_texture_id == 0)
+ {
+ float spread = gsk_inset_shadow_node_get_spread (node) + half_blur_extra;
+ GskRoundedRect transformed_outline;
+ GskRoundedRect outline_to_blur;
+ GskNglRenderTarget *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_ngl_driver_create_render_target (job->driver,
+ texture_width, texture_height,
+ GL_NEAREST, GL_NEAREST,
+ &render_target))
+ g_assert_not_reached ();
+
+ gsk_ngl_render_job_set_viewport_for_size (job, texture_width, texture_height, &prev_viewport);
+ gsk_ngl_render_job_set_projection_for_size (job, texture_width, texture_height, &prev_projection);
+ gsk_ngl_render_job_set_modelview (job, NULL);
+ gsk_ngl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT (0, 0, texture_width, texture_height));
+
+ prev_fbo = gsk_ngl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id);
+ gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
+
+ gsk_ngl_render_job_transform_rounded_rect (job, &outline_to_blur, &transformed_outline);
+
+ /* Actual inset shadow outline drawing */
+ gsk_ngl_render_job_begin_draw (job, job->driver->inset_shadow);
+ gsk_ngl_program_set_uniform_rounded_rect (job->driver->inset_shadow,
+ UNIFORM_INSET_SHADOW_OUTLINE_RECT, 0,
+ &transformed_outline);
+ gsk_ngl_program_set_uniform_color (job->driver->inset_shadow,
+ UNIFORM_INSET_SHADOW_COLOR, 0,
+ gsk_inset_shadow_node_get_color (node));
+ gsk_ngl_program_set_uniform1f (job->driver->inset_shadow,
+ UNIFORM_INSET_SHADOW_SPREAD, 0,
+ spread * MAX (scale_x, scale_y));
+ gsk_ngl_program_set_uniform2f (job->driver->inset_shadow,
+ UNIFORM_INSET_SHADOW_OFFSET, 0,
+ offset_x * scale_x,
+ offset_y * scale_y);
+ gsk_ngl_render_job_draw (job, 0, 0, texture_width, texture_height);
+ gsk_ngl_render_job_end_draw (job);
+
+ gsk_ngl_render_job_pop_modelview (job);
+ gsk_ngl_render_job_pop_clip (job);
+ gsk_ngl_render_job_set_projection (job, &prev_projection);
+ gsk_ngl_render_job_set_viewport (job, &prev_viewport, NULL);
+ gsk_ngl_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 * scale_x,
+ blur_radius * scale_y);
+
+ gsk_ngl_driver_release_render_target (job->driver, render_target, TRUE);
+
+ gsk_ngl_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_ngl_render_job_transform_bounds (job, &node_outline->bounds, &node_clip.bounds);
+
+ for (guint i = 0; i < 4; i ++)
+ {
+ node_clip.corner[i].width = node_outline->corner[i].width * scale_x;
+ node_clip.corner[i].height = node_outline->corner[i].height * scale_y;
+ }
+
+ gsk_ngl_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;
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->blit);
+ gsk_ngl_program_set_uniform_texture (job->driver->blit,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ blurred_texture_id);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job, &node->bounds, &offscreen);
+ gsk_ngl_render_job_end_draw (job);
+
+ if (needs_clip)
+ gsk_ngl_render_job_pop_clip (job);
+ }
+}
+
+static inline void
+gsk_ngl_render_job_visit_unblurred_outset_shadow_node (GskNglRenderJob *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);
+ 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 },
+ };
+
+ gsk_ngl_render_job_transform_rounded_rect (job, outline, &transformed_outline);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->unblurred_outset_shadow);
+ gsk_ngl_program_set_uniform_rounded_rect (job->driver->unblurred_outset_shadow,
+ UNIFORM_UNBLURRED_OUTSET_SHADOW_OUTLINE_RECT, 0,
+ &transformed_outline);
+ gsk_ngl_program_set_uniform_color (job->driver->unblurred_outset_shadow,
+ UNIFORM_UNBLURRED_OUTSET_SHADOW_COLOR, 0,
+ gsk_outset_shadow_node_get_color (node));
+ gsk_ngl_program_set_uniform1f (job->driver->unblurred_outset_shadow,
+ UNIFORM_UNBLURRED_OUTSET_SHADOW_SPREAD, 0,
+ spread);
+ gsk_ngl_program_set_uniform2f (job->driver->unblurred_outset_shadow,
+ UNIFORM_UNBLURRED_OUTSET_SHADOW_OFFSET, 0,
+ dx, dy);
+
+ /* Corners... */
+ if (corner_sizes[0][0] > 0 && corner_sizes[0][1] > 0) /* Top left */
+ gsk_ngl_render_job_draw (job,
+ x, y,
+ corner_sizes[0][0], corner_sizes[0][1]);
+ if (corner_sizes[1][0] > 0 && corner_sizes[1][1] > 0) /* Top right */
+ gsk_ngl_render_job_draw (job,
+ x + w - corner_sizes[1][0], y,
+ corner_sizes[1][0], corner_sizes[1][1]);
+ if (corner_sizes[2][0] > 0 && corner_sizes[2][1] > 0) /* Bottom right */
+ gsk_ngl_render_job_draw (job,
+ x + w - corner_sizes[2][0], y + h - corner_sizes[2][1],
+ corner_sizes[2][0], corner_sizes[2][1]);
+ if (corner_sizes[3][0] > 0 && corner_sizes[3][1] > 0) /* Bottom left */
+ gsk_ngl_render_job_draw (job,
+ x, y + h - corner_sizes[3][1],
+ corner_sizes[3][0], corner_sizes[3][1]);
+ /* Edges... */;
+ if (edge_sizes[0] > 0) /* Top */
+ gsk_ngl_render_job_draw (job,
+ x + corner_sizes[0][0], y,
+ w - corner_sizes[0][0] - corner_sizes[1][0], edge_sizes[0]);
+ if (edge_sizes[1] > 0) /* Right */
+ gsk_ngl_render_job_draw (job,
+ x + w - edge_sizes[1], y + corner_sizes[1][1],
+ edge_sizes[1], h - corner_sizes[1][1] - corner_sizes[2][1]);
+ if (edge_sizes[2] > 0) /* Bottom */
+ gsk_ngl_render_job_draw (job,
+ x + corner_sizes[3][0], y + h - edge_sizes[2],
+ w - corner_sizes[3][0] - corner_sizes[2][0], edge_sizes[2]);
+ if (edge_sizes[3] > 0) /* Left */
+ gsk_ngl_render_job_draw (job,
+ x, y + corner_sizes[0][1],
+ edge_sizes[3], h - corner_sizes[0][1] - corner_sizes[3][1]);
+
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_ngl_render_job_visit_blurred_outset_shadow_node (GskNglRenderJob *job,
+ const GskRenderNode *node)
+{
+ static const GdkRGBA white = { 1, 1, 1, 1 };
+
+ const GskRoundedRect *outline = gsk_outset_shadow_node_get_outline (node);
+ const GdkRGBA *color = gsk_outset_shadow_node_get_color (node);
+ float scale_x = job->scale_x;
+ float scale_y = 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 = ceilf (half_blur_extra * scale_x);
+ 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;
+ GskNglRenderOffscreen offscreen = {0};
+ int texture_width, texture_height;
+ int blurred_texture_id;
+ int cached_tid;
+ gboolean do_slicing;
+
+ /* 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)
+ {
+ 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)ceil ((scaled_outline.bounds.size.width + blur_extra) * scale_x);
+ texture_height = (int)ceil ((scaled_outline.bounds.size.height + blur_extra) * scale_y);
+
+ scaled_outline.bounds.origin.x = extra_blur_pixels;
+ scaled_outline.bounds.origin.y = extra_blur_pixels;
+ scaled_outline.bounds.size.width = texture_width - (extra_blur_pixels * 2);
+ scaled_outline.bounds.size.height = texture_height - (extra_blur_pixels * 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_ngl_shadow_library_lookup (job->driver->shadows, &scaled_outline, blur_radius);
+
+ if (cached_tid == 0)
+ {
+ GdkGLContext *context = job->command_queue->context;
+ GskNglRenderTarget *render_target;
+ graphene_matrix_t prev_projection;
+ graphene_rect_t prev_viewport;
+ guint prev_fbo;
+
+ gsk_ngl_driver_create_render_target (job->driver,
+ texture_width, texture_height,
+ GL_NEAREST, GL_NEAREST,
+ &render_target);
+
+ if (gdk_gl_context_has_debug (context))
+ {
+ 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_ngl_render_job_set_projection_for_size (job, texture_width, texture_height, &prev_projection);
+ gsk_ngl_render_job_set_viewport_for_size (job, texture_width, texture_height, &prev_viewport);
+ gsk_ngl_render_job_set_modelview (job, NULL);
+ gsk_ngl_render_job_push_clip (job, &scaled_outline);
+
+ /* Bind render target and clear it */
+ prev_fbo = gsk_ngl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id);
+ gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
+
+ /* Draw the outline using color program */
+ gsk_ngl_render_job_begin_draw (job, job->driver->color);
+ gsk_ngl_program_set_uniform_color (job->driver->color,
+ UNIFORM_COLOR_COLOR, 0,
+ &white);
+ gsk_ngl_render_job_draw (job, 0, 0, texture_width, texture_height);
+ gsk_ngl_render_job_end_draw (job);
+
+ /* Reset state from offscreen */
+ gsk_ngl_render_job_pop_clip (job);
+ gsk_ngl_render_job_pop_modelview (job);
+ gsk_ngl_render_job_set_viewport (job, &prev_viewport, NULL);
+ gsk_ngl_render_job_set_projection (job, &prev_projection);
+
+ /* Now blur the outline */
+ init_full_texture_region (&offscreen);
+ offscreen.texture_id = gsk_ngl_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_ngl_shadow_library_insert (job->driver->shadows,
+ &scaled_outline,
+ blur_radius,
+ blurred_texture_id);
+
+ gsk_ngl_command_queue_bind_framebuffer (job->command_queue, prev_fbo);
+ }
+ else
+ {
+ blurred_texture_id = cached_tid;
+ }
+
+ gsk_ngl_render_job_transform_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 = FALSE;
+ offscreen.texture_id = blurred_texture_id;
+ init_full_texture_region (&offscreen);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->outset_shadow);
+ gsk_ngl_program_set_uniform_color (job->driver->outset_shadow,
+ UNIFORM_OUTSET_SHADOW_COLOR, 0,
+ color);
+ gsk_ngl_program_set_uniform_texture (job->driver->outset_shadow,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ blurred_texture_id);
+ gsk_ngl_program_set_uniform_rounded_rect (job->driver->outset_shadow,
+ UNIFORM_OUTSET_SHADOW_OUTLINE_RECT, 0,
+ &transformed_outline);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job,
+ &GRAPHENE_RECT_INIT (min_x,
+ min_y,
+ texture_width / scale_x,
+ texture_height / scale_y),
+ &offscreen);
+ gsk_ngl_render_job_end_draw (job);
+
+ return;
+ }
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->outset_shadow);
+ gsk_ngl_program_set_uniform_color (job->driver->outset_shadow,
+ UNIFORM_OUTSET_SHADOW_COLOR, 0,
+ color);
+ gsk_ngl_program_set_uniform_texture (job->driver->outset_shadow,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ blurred_texture_id);
+ gsk_ngl_program_set_uniform_rounded_rect (job->driver->outset_shadow,
+ 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 GskNglTextureNineSlice *slices;
+ GskNglTexture *texture;
+
+ texture = gsk_ngl_driver_get_texture_by_id (job->driver, blurred_texture_id);
+ slices = gsk_ngl_texture_get_nine_slice (texture, &scaled_outline, extra_blur_pixels);
+
+ offscreen.was_offscreen = TRUE;
+
+ /* Our texture coordinates MUST be scaled, while the actual vertex coords
+ * MUST NOT be scaled. */
+
+ /* 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_ngl_render_job_load_vertices_from_offscreen (job,
+ &GRAPHENE_RECT_INIT (min_x, min_y,
+ slices[NINE_SLICE_TOP_LEFT].rect.width / scale_x,
+ slices[NINE_SLICE_TOP_LEFT].rect.height / scale_y),
+ &offscreen);
+ }
+
+ /* Top center */
+ if (nine_slice_is_visible (&slices[NINE_SLICE_TOP_CENTER]))
+ {
+ memcpy (&offscreen.area, &slices[NINE_SLICE_TOP_CENTER].area, sizeof offscreen.area);
+ float width = (max_x - min_x) - (slices[NINE_SLICE_TOP_LEFT].rect.width / scale_x +
+ slices[NINE_SLICE_TOP_RIGHT].rect.width / scale_x);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job,
+ &GRAPHENE_RECT_INIT (min_x + (slices[NINE_SLICE_TOP_LEFT].rect.width / scale_x),
+ min_y,
+ width,
+ slices[NINE_SLICE_TOP_CENTER].rect.height / scale_y),
+ &offscreen);
+ }
+
+ /* 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_ngl_render_job_load_vertices_from_offscreen (job,
+ &GRAPHENE_RECT_INIT (max_x - (slices[NINE_SLICE_TOP_RIGHT].rect.width / scale_x),
+ min_y,
+ slices[NINE_SLICE_TOP_RIGHT].rect.width / scale_x,
+ slices[NINE_SLICE_TOP_RIGHT].rect.height / scale_y),
+ &offscreen);
+ }
+
+ /* 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_ngl_render_job_load_vertices_from_offscreen (job,
+ &GRAPHENE_RECT_INIT (max_x - (slices[NINE_SLICE_BOTTOM_RIGHT].rect.width / scale_x),
+ max_y - (slices[NINE_SLICE_BOTTOM_RIGHT].rect.height / scale_y),
+ slices[NINE_SLICE_BOTTOM_RIGHT].rect.width / scale_x,
+ slices[NINE_SLICE_BOTTOM_RIGHT].rect.height / scale_y),
+ &offscreen);
+ }
+
+ /* 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_ngl_render_job_load_vertices_from_offscreen (job,
+ &GRAPHENE_RECT_INIT (min_x,
+ max_y - (slices[NINE_SLICE_BOTTOM_LEFT].rect.height / scale_y),
+ slices[NINE_SLICE_BOTTOM_LEFT].rect.width / scale_x,
+ slices[NINE_SLICE_BOTTOM_LEFT].rect.height / scale_y),
+ &offscreen);
+ }
+
+ /* Left side */
+ if (nine_slice_is_visible (&slices[NINE_SLICE_LEFT_CENTER]))
+ {
+ memcpy (&offscreen.area, &slices[NINE_SLICE_LEFT_CENTER].area, sizeof offscreen.area);
+ float height = (max_y - min_y) - (slices[NINE_SLICE_TOP_LEFT].rect.height / scale_y +
+ slices[NINE_SLICE_BOTTOM_LEFT].rect.height / scale_y);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job,
+ &GRAPHENE_RECT_INIT (min_x,
+ min_y + (slices[NINE_SLICE_TOP_LEFT].rect.height / scale_y),
+ slices[NINE_SLICE_LEFT_CENTER].rect.width / scale_x,
+ height),
+ &offscreen);
+ }
+
+ /* Right side */
+ if (nine_slice_is_visible (&slices[NINE_SLICE_RIGHT_CENTER]))
+ {
+ memcpy (&offscreen.area, &slices[NINE_SLICE_RIGHT_CENTER].area, sizeof offscreen.area);
+ float height = (max_y - min_y) - (slices[NINE_SLICE_TOP_RIGHT].rect.height / scale_y +
+ slices[NINE_SLICE_BOTTOM_RIGHT].rect.height / scale_y);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job,
+ &GRAPHENE_RECT_INIT (max_x - (slices[NINE_SLICE_RIGHT_CENTER].rect.width / scale_x),
+ min_y + (slices[NINE_SLICE_TOP_LEFT].rect.height / scale_y),
+ slices[NINE_SLICE_RIGHT_CENTER].rect.width / scale_x,
+ height),
+ &offscreen);
+ }
+
+ /* Bottom side */
+ if (nine_slice_is_visible (&slices[NINE_SLICE_BOTTOM_CENTER]))
+ {
+ memcpy (&offscreen.area, &slices[NINE_SLICE_BOTTOM_CENTER].area, sizeof offscreen.area);
+ float width = (max_x - min_x) - (slices[NINE_SLICE_BOTTOM_LEFT].rect.width / scale_x +
+ slices[NINE_SLICE_BOTTOM_RIGHT].rect.width / scale_x);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job,
+ &GRAPHENE_RECT_INIT (min_x + (slices[NINE_SLICE_BOTTOM_LEFT].rect.width / scale_x),
+ max_y - (slices[NINE_SLICE_BOTTOM_CENTER].rect.height / scale_y),
+ width,
+ slices[NINE_SLICE_BOTTOM_CENTER].rect.height / scale_y),
+ &offscreen);
+ }
+
+ /* Middle */
+ if (nine_slice_is_visible (&slices[NINE_SLICE_CENTER]))
+ {
+ memcpy (&offscreen.area, &slices[NINE_SLICE_CENTER].area, sizeof offscreen.area);
+ float width = (max_x - min_x) - (slices[NINE_SLICE_LEFT_CENTER].rect.width / scale_x +
+ slices[NINE_SLICE_RIGHT_CENTER].rect.width / scale_x);
+ float height = (max_y - min_y) - (slices[NINE_SLICE_TOP_CENTER].rect.height / scale_y +
+ slices[NINE_SLICE_BOTTOM_CENTER].rect.height / scale_y);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job,
+ &GRAPHENE_RECT_INIT (min_x + (slices[NINE_SLICE_LEFT_CENTER].rect.width / scale_x),
+ min_y + (slices[NINE_SLICE_TOP_CENTER].rect.height / scale_y),
+ width, height),
+ &offscreen);
+ }
+ }
+
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline gboolean G_GNUC_PURE
+equal_texture_nodes (const GskRenderNode *node1,
+ const GskRenderNode *node2)
+{
+ if (gsk_render_node_get_node_type (node1) != GSK_TEXTURE_NODE ||
+ gsk_render_node_get_node_type (node2) != GSK_TEXTURE_NODE)
+ return FALSE;
+
+ if (gsk_texture_node_get_texture (node1) !=
+ gsk_texture_node_get_texture (node2))
+ return FALSE;
+
+ return graphene_rect_equal (&node1->bounds, &node2->bounds);
+}
+
+static inline void
+gsk_ngl_render_job_visit_cross_fade_node (GskNglRenderJob *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);
+ GskNglRenderOffscreen offscreen_start = {0};
+ GskNglRenderOffscreen 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;
+
+ if (!gsk_ngl_render_job_visit_node_with_offscreen (job, start_node, &offscreen_start))
+ {
+ gsk_ngl_render_job_visit_node (job, end_node);
+ return;
+ }
+
+ g_assert (offscreen_start.texture_id);
+
+ if (!gsk_ngl_render_job_visit_node_with_offscreen (job, end_node, &offscreen_end))
+ {
+ float prev_alpha = gsk_ngl_render_job_set_alpha (job, job->alpha * progress);
+ gsk_ngl_render_job_visit_node (job, start_node);
+ gsk_ngl_render_job_set_alpha (job, prev_alpha);
+ return;
+ }
+
+ g_assert (offscreen_end.texture_id);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->cross_fade);
+ gsk_ngl_program_set_uniform_texture (job->driver->cross_fade,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ offscreen_start.texture_id);
+ gsk_ngl_program_set_uniform_texture (job->driver->cross_fade,
+ UNIFORM_CROSS_FADE_SOURCE2, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE1,
+ offscreen_end.texture_id);
+ gsk_ngl_program_set_uniform1f (job->driver->cross_fade,
+ UNIFORM_CROSS_FADE_PROGRESS, 0,
+ progress);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job, &node->bounds, &offscreen_end);
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_ngl_render_job_visit_opacity_node (GskNglRenderJob *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_ngl_render_job_set_alpha (job, new_alpha);
+
+ if (gsk_render_node_get_node_type (child) == GSK_CONTAINER_NODE)
+ {
+ GskNglRenderOffscreen offscreen = {0};
+
+ offscreen.bounds = &child->bounds;
+ offscreen.force_offscreen = TRUE;
+ offscreen.reset_clip = TRUE;
+
+ /* The semantics of an opacity node mandate that when, e.g., two
+ * color nodes overlap, there may not be any blending between them.
+ */
+ if (!gsk_ngl_render_job_visit_node_with_offscreen (job, child, &offscreen))
+ return;
+
+ g_assert (offscreen.texture_id);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->blit);
+ gsk_ngl_program_set_uniform_texture (job->driver->blit,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ offscreen.texture_id);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job, &node->bounds, &offscreen);
+ gsk_ngl_render_job_end_draw (job);
+ }
+ else
+ {
+ gsk_ngl_render_job_visit_node (job, child);
+ }
+
+ gsk_ngl_render_job_set_alpha (job, prev_alpha);
+ }
+}
+
+static inline void
+gsk_ngl_render_job_visit_text_node (GskNglRenderJob *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 (job->scale_x, 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;
+ GskNglGlyphLibrary *library = job->driver->glyphs;
+ GskNglCommandBatch *batch;
+ GskNglProgram *program;
+ int x_position = 0;
+ GskNglGlyphKey lookup;
+ guint last_texture = 0;
+ GskNglDrawVertex *vertices;
+ guint used = 0;
+
+ if (num_glyphs == 0)
+ return;
+
+ /* If the font has color glyphs, we don't need to recolor anything */
+ if (!force_color && gsk_text_node_has_color_glyphs (node))
+ {
+ program = job->driver->blit;
+ }
+ else
+ {
+ program = job->driver->coloring;
+ gsk_ngl_program_set_uniform_color (program, UNIFORM_COLORING_COLOR, 0, color);
+ }
+
+ lookup.font = (PangoFont *)font;
+ lookup.scale = (guint) (text_scale * 1024);
+
+ gsk_ngl_render_job_begin_draw (job, program);
+ batch = gsk_ngl_command_queue_get_batch (job->command_queue);
+ vertices = gsk_ngl_command_queue_add_n_vertices (job->command_queue, num_glyphs);
+
+ /* We use one quad per character */
+ for (guint i = 0; i < num_glyphs; i++)
+ {
+ const PangoGlyphInfo *gi = &glyphs[i];
+ const GskNglGlyphValue *glyph;
+ guint base = used * GSK_NGL_N_VERTICES;
+ float glyph_x, glyph_y, glyph_x2, glyph_y2;
+ float tx, ty, tx2, ty2;
+ float cx;
+ float cy;
+ guint texture_id;
+
+ if (gi->glyph == PANGO_GLYPH_EMPTY)
+ continue;
+
+ cx = (float)(x_position + gi->geometry.x_offset) / PANGO_SCALE;
+ cy = (float)(gi->geometry.y_offset) / PANGO_SCALE;
+
+ gsk_ngl_glyph_key_set_glyph_and_shift (&lookup, gi->glyph, x + cx, y + cy);
+
+ if (!gsk_ngl_glyph_library_lookup_or_add (library, &lookup, &glyph))
+ goto next;
+
+ texture_id = GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (glyph);
+
+ if G_UNLIKELY (last_texture != texture_id)
+ {
+ g_assert (texture_id > 0);
+
+ 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_ngl_render_job_split_draw (job);
+ batch = gsk_ngl_command_queue_get_batch (job->command_queue);
+ batch->draw.vbo_offset = vbo_offset;
+ }
+
+ gsk_ngl_program_set_uniform_texture (program,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ texture_id);
+ 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 = floorf (x + cx + 0.125) + glyph->ink_rect.x;
+ glyph_y = floorf (y + cy + 0.125) + glyph->ink_rect.y;
+ glyph_x2 = glyph_x + glyph->ink_rect.width;
+ glyph_y2 = glyph_y + glyph->ink_rect.height;
+
+ vertices[base+0].position[0] = glyph_x;
+ vertices[base+0].position[1] = glyph_y;
+ vertices[base+0].uv[0] = tx;
+ vertices[base+0].uv[1] = ty;
+
+ vertices[base+1].position[0] = glyph_x;
+ vertices[base+1].position[1] = glyph_y2;
+ vertices[base+1].uv[0] = tx;
+ vertices[base+1].uv[1] = ty2;
+
+ vertices[base+2].position[0] = glyph_x2;
+ vertices[base+2].position[1] = glyph_y;
+ vertices[base+2].uv[0] = tx2;
+ vertices[base+2].uv[1] = ty;
+
+ vertices[base+3].position[0] = glyph_x2;
+ vertices[base+3].position[1] = glyph_y2;
+ vertices[base+3].uv[0] = tx2;
+ vertices[base+3].uv[1] = ty2;
+
+ vertices[base+4].position[0] = glyph_x;
+ vertices[base+4].position[1] = glyph_y2;
+ vertices[base+4].uv[0] = tx;
+ vertices[base+4].uv[1] = ty2;
+
+ vertices[base+5].position[0] = glyph_x2;
+ vertices[base+5].position[1] = glyph_y;
+ vertices[base+5].uv[0] = tx2;
+ vertices[base+5].uv[1] = ty;
+
+ batch->draw.vbo_count += GSK_NGL_N_VERTICES;
+ used++;
+
+next:
+ x_position += gi->geometry.width;
+ }
+
+ if (used != num_glyphs)
+ gsk_ngl_command_queue_retract_n_vertices (job->command_queue, num_glyphs - used);
+
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_ngl_render_job_visit_shadow_node (GskNglRenderJob *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_get_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;
+ GskNglRenderOffscreen offscreen = {0};
+ graphene_rect_t bounds;
+
+ if (shadow->radius == 0 &&
+ gsk_render_node_get_node_type (shadow_child) == GSK_TEXT_NODE)
+ {
+ gsk_ngl_render_job_offset (job, dx, dy);
+ gsk_ngl_render_job_visit_text_node (job, shadow_child, &shadow->color, TRUE);
+ gsk_ngl_render_job_offset (job, -dx, -dy);
+ continue;
+ }
+
+ if (RGBA_IS_CLEAR (&shadow->color))
+ continue;
+
+ if (node_is_invisible (shadow_child))
+ 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 if (dx == 0 && dy == 0)
+ {
+ continue; /* Invisible anyway */
+ }
+ else
+ {
+ offscreen.bounds = &shadow_child->bounds;
+ offscreen.reset_clip = TRUE;
+ offscreen.do_not_cache = TRUE;
+
+ if (!gsk_ngl_render_job_visit_node_with_offscreen (job, shadow_child, &offscreen))
+ g_assert_not_reached ();
+
+ bounds = shadow_child->bounds;
+ }
+
+ gsk_ngl_render_job_offset (job, dx, dy);
+ gsk_ngl_render_job_begin_draw (job, job->driver->coloring);
+ gsk_ngl_program_set_uniform_texture (job->driver->coloring,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ offscreen.texture_id);
+ gsk_ngl_program_set_uniform_color (job->driver->coloring,
+ UNIFORM_COLORING_COLOR, 0,
+ &shadow->color);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job, &bounds, &offscreen);
+ gsk_ngl_render_job_end_draw (job);
+ gsk_ngl_render_job_offset (job, -dx, -dy);
+ }
+
+ /* Now draw the child normally */
+ gsk_ngl_render_job_visit_node (job, original_child);
+}
+
+static inline void
+gsk_ngl_render_job_visit_blur_node (GskNglRenderJob *job,
+ const GskRenderNode *node)
+{
+ const GskRenderNode *child = gsk_blur_node_get_child (node);
+ float blur_radius = gsk_blur_node_get_radius (node);
+ GskNglRenderOffscreen 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;
+ key.filter = GL_NEAREST;
+
+ offscreen.texture_id = gsk_ngl_driver_lookup_texture (job->driver, &key);
+ 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_ngl_driver_cache_texture (job->driver, &key, offscreen.texture_id);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->blit);
+ gsk_ngl_program_set_uniform_texture (job->driver->blit,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ offscreen.texture_id);
+ gsk_ngl_render_job_draw_coords (job, min_x, min_y, max_x, max_y);
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_ngl_render_job_visit_blend_node (GskNglRenderJob *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);
+ GskNglRenderOffscreen top_offscreen = {0};
+ GskNglRenderOffscreen 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;
+
+ /* 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_ngl_render_job_visit_node_with_offscreen (job, bottom_child, &bottom_offscreen))
+ {
+ gsk_ngl_render_job_visit_node (job, top_child);
+ return;
+ }
+
+ g_assert (bottom_offscreen.was_offscreen);
+
+ if (!gsk_ngl_render_job_visit_node_with_offscreen (job, top_child, &top_offscreen))
+ {
+ gsk_ngl_render_job_begin_draw (job, job->driver->blit);
+ gsk_ngl_program_set_uniform_texture (job->driver->blit,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ bottom_offscreen.texture_id);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job, &node->bounds, &bottom_offscreen);
+ gsk_ngl_render_job_end_draw (job);
+ return;
+ }
+
+ g_assert (top_offscreen.was_offscreen);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->blend);
+ gsk_ngl_program_set_uniform_texture (job->driver->blend,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ bottom_offscreen.texture_id);
+ gsk_ngl_program_set_uniform_texture (job->driver->blend,
+ UNIFORM_BLEND_SOURCE2, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE1,
+ top_offscreen.texture_id);
+ gsk_ngl_program_set_uniform1i (job->driver->blend,
+ UNIFORM_BLEND_MODE, 0,
+ gsk_blend_node_get_blend_mode (node));
+ gsk_ngl_render_job_draw_offscreen_rect (job, &node->bounds);
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_ngl_render_job_visit_color_matrix_node (GskNglRenderJob *job,
+ const GskRenderNode *node)
+{
+ const GskRenderNode *child = gsk_color_matrix_node_get_child (node);
+ GskNglRenderOffscreen offscreen = {0};
+ float offset[4];
+
+ if (node_is_invisible (child))
+ return;
+
+ offscreen.bounds = &node->bounds;
+ offscreen.reset_clip = TRUE;
+
+ if (!gsk_ngl_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);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->color_matrix);
+ gsk_ngl_program_set_uniform_texture (job->driver->color_matrix,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ offscreen.texture_id);
+ gsk_ngl_program_set_uniform_matrix (job->driver->color_matrix,
+ UNIFORM_COLOR_MATRIX_COLOR_MATRIX, 0,
+ gsk_color_matrix_node_get_color_matrix (node));
+ gsk_ngl_program_set_uniform4fv (job->driver->color_matrix,
+ UNIFORM_COLOR_MATRIX_COLOR_OFFSET, 0,
+ 1,
+ offset);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job, &node->bounds, &offscreen);
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_ngl_render_job_visit_gl_shader_node_fallback (GskNglRenderJob *job,
+ const GskRenderNode *node)
+{
+ static const GdkRGBA pink = { 255 / 255., 105 / 255., 180 / 255., 1.0 };
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->color);
+ gsk_ngl_program_set_uniform_color (job->driver->color,
+ UNIFORM_COLOR_COLOR, 0,
+ &pink);
+ gsk_ngl_render_job_draw_rect (job, &node->bounds);
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static inline void
+gsk_ngl_render_job_visit_gl_shader_node (GskNglRenderJob *job,
+ const GskRenderNode *node)
+{
+ GError *error = NULL;
+ GskGLShader *shader;
+ GskNglProgram *program;
+ int n_children;
+
+ shader = gsk_gl_shader_node_get_shader (node);
+ program = gsk_ngl_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_ngl_render_job_visit_gl_shader_node_fallback (job, node);
+ g_clear_error (&error);
+ }
+ else
+ {
+ GskNglRenderOffscreen offscreens[4] = {{0}};
+ const GskGLUniform *uniforms;
+ const guint8 *base;
+ GBytes *args;
+ int n_uniforms;
+
+ g_assert (n_children < G_N_ELEMENTS (offscreens));
+
+ for (guint i = 0; i < n_children; i++)
+ {
+ const GskRenderNode *child = gsk_gl_shader_node_get_child (node, i);
+
+ offscreens[i].bounds = &node->bounds;
+ offscreens[i].force_offscreen = TRUE;
+ offscreens[i].reset_clip = TRUE;
+
+ if (!gsk_ngl_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);
+
+ gsk_ngl_render_job_begin_draw (job, program);
+ for (guint i = 0; i < n_children; i++)
+ gsk_ngl_program_set_uniform_texture (program,
+ UNIFORM_CUSTOM_TEXTURE1 + i, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0 + i,
+ offscreens[i].texture_id);
+ gsk_ngl_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;
+
+ /* Ignore unused uniforms */
+ if (program->args_locations[i] == -1)
+ continue;
+
+ switch (u->type)
+ {
+ default:
+ case GSK_GL_UNIFORM_TYPE_NONE:
+ break;
+ case GSK_GL_UNIFORM_TYPE_FLOAT:
+ gsk_ngl_uniform_state_set1fv (job->command_queue->uniforms,
+ program->program_info,
+ program->args_locations[i],
+ 0, 1, (const float *)data);
+ break;
+ case GSK_GL_UNIFORM_TYPE_INT:
+ gsk_ngl_uniform_state_set1i (job->command_queue->uniforms,
+ program->program_info,
+ program->args_locations[i],
+ 0, *(const gint32 *)data);
+ break;
+ case GSK_GL_UNIFORM_TYPE_UINT:
+ case GSK_GL_UNIFORM_TYPE_BOOL:
+ gsk_ngl_uniform_state_set1ui (job->command_queue->uniforms,
+ program->program_info,
+ program->args_locations[i],
+ 0, *(const guint32 *)data);
+ break;
+ case GSK_GL_UNIFORM_TYPE_VEC2:
+ gsk_ngl_uniform_state_set2fv (job->command_queue->uniforms,
+ program->program_info,
+ program->args_locations[i],
+ 0, 1, (const float *)data);
+ break;
+ case GSK_GL_UNIFORM_TYPE_VEC3:
+ gsk_ngl_uniform_state_set3fv (job->command_queue->uniforms,
+ program->program_info,
+ program->args_locations[i],
+ 0, 1, (const float *)data);
+ break;
+ case GSK_GL_UNIFORM_TYPE_VEC4:
+ gsk_ngl_uniform_state_set4fv (job->command_queue->uniforms,
+ program->program_info,
+ program->args_locations[i],
+ 0, 1, (const float *)data);
+ break;
+ }
+ }
+ gsk_ngl_render_job_draw_offscreen_rect (job, &node->bounds);
+ gsk_ngl_render_job_end_draw (job);
+ }
+}
+
+static void
+gsk_ngl_render_job_upload_texture (GskNglRenderJob *job,
+ GdkTexture *texture,
+ GskNglRenderOffscreen *offscreen)
+{
+ if (gsk_ngl_texture_library_can_cache (GSK_NGL_TEXTURE_LIBRARY (job->driver->icons),
+ texture->width,
+ texture->height) &&
+ !GDK_IS_GL_TEXTURE (texture))
+ {
+ const GskNglIconData *icon_data;
+
+ gsk_ngl_icon_library_lookup_or_add (job->driver->icons, texture, &icon_data);
+ offscreen->texture_id = GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (icon_data);
+ memcpy (&offscreen->area, &icon_data->entry.area, sizeof offscreen->area);
+ }
+ else
+ {
+ offscreen->texture_id = gsk_ngl_driver_load_texture (job->driver, texture, GL_LINEAR, GL_LINEAR);
+ init_full_texture_region (offscreen);
+ }
+}
+
+static inline void
+gsk_ngl_render_job_visit_texture_node (GskNglRenderJob *job,
+ const GskRenderNode *node)
+{
+ GdkTexture *texture = gsk_texture_node_get_texture (node);
+ int max_texture_size = job->command_queue->max_texture_size;
+
+ if G_LIKELY (texture->width <= max_texture_size &&
+ texture->height <= max_texture_size)
+ {
+ GskNglRenderOffscreen offscreen = {0};
+
+ gsk_ngl_render_job_upload_texture (job, texture, &offscreen);
+
+ g_assert (offscreen.texture_id);
+ g_assert (offscreen.was_offscreen == FALSE);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->blit);
+ gsk_ngl_program_set_uniform_texture (job->driver->blit,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ offscreen.texture_id);
+ gsk_ngl_render_job_load_vertices_from_offscreen (job, &node->bounds, &offscreen);
+ gsk_ngl_render_job_end_draw (job);
+ }
+ else
+ {
+ 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;
+ float scale_x = (max_x - min_x) / texture->width;
+ float scale_y = (max_y - min_y) / texture->height;
+ GskNglTextureSlice *slices = NULL;
+ guint n_slices = 0;
+
+ gsk_ngl_driver_slice_texture (job->driver, texture, &slices, &n_slices);
+
+ g_assert (slices != NULL);
+ g_assert (n_slices > 0);
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->blit);
+
+ for (guint i = 0; i < n_slices; i ++)
+ {
+ GskNglDrawVertex *vertices;
+ const GskNglTextureSlice *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_ngl_render_job_split_draw (job);
+ gsk_ngl_program_set_uniform_texture (job->driver->blit,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ slice->texture_id);
+ vertices = gsk_ngl_command_queue_add_vertices (job->command_queue);
+
+ vertices[0].position[0] = x1;
+ vertices[0].position[1] = y1;
+ vertices[0].uv[0] = 0;
+ vertices[0].uv[1] = 0;
+
+ vertices[1].position[0] = x1;
+ vertices[1].position[1] = y2;
+ vertices[1].uv[0] = 0;
+ vertices[1].uv[1] = 1;
+
+ vertices[2].position[0] = x2;
+ vertices[2].position[1] = y1;
+ vertices[2].uv[0] = 1;
+ vertices[2].uv[1] = 0;
+
+ vertices[3].position[0] = x2;
+ vertices[3].position[1] = y2;
+ vertices[3].uv[0] = 1;
+ vertices[3].uv[1] = 1;
+
+ vertices[4].position[0] = x1;
+ vertices[4].position[1] = y2;
+ vertices[4].uv[0] = 0;
+ vertices[4].uv[1] = 1;
+
+ vertices[5].position[0] = x2;
+ vertices[5].position[1] = y1;
+ vertices[5].uv[0] = 1;
+ vertices[5].uv[1] = 0;
+ }
+
+ gsk_ngl_render_job_end_draw (job);
+ }
+}
+
+static inline void
+gsk_ngl_render_job_visit_repeat_node (GskNglRenderJob *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);
+ GskNglRenderOffscreen offscreen = {0};
+
+ if (node_is_invisible (child))
+ return;
+
+ if (!graphene_rect_equal (child_bounds, &child->bounds))
+ {
+ /* TODO: implement these repeat nodes. */
+ gsk_ngl_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 (rect_contains_rect (child_bounds, &node->bounds))
+ {
+ gsk_ngl_render_job_visit_clipped_child (job, child, &node->bounds);
+ return;
+ }
+
+ offscreen.bounds = &child->bounds;
+ offscreen.reset_clip = TRUE;
+
+ if (!gsk_ngl_render_job_visit_node_with_offscreen (job, child, &offscreen))
+ g_assert_not_reached ();
+
+ gsk_ngl_render_job_begin_draw (job, job->driver->repeat);
+ gsk_ngl_program_set_uniform_texture (job->driver->repeat,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ offscreen.texture_id);
+ gsk_ngl_program_set_uniform4f (job->driver->repeat,
+ 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_ngl_program_set_uniform4f (job->driver->repeat,
+ 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_ngl_render_job_load_vertices_from_offscreen (job, &node->bounds, &offscreen);
+ gsk_ngl_render_job_end_draw (job);
+}
+
+static void
+gsk_ngl_render_job_visit_node (GskNglRenderJob *job,
+ const GskRenderNode *node)
+{
+ g_assert (job != NULL);
+ g_assert (node != NULL);
+ g_assert (GSK_IS_NGL_DRIVER (job->driver));
+ g_assert (GSK_IS_NGL_COMMAND_QUEUE (job->command_queue));
+
+ if (node_is_invisible (node) ||
+ !gsk_ngl_render_job_node_overlaps_clip (job, node))
+ return;
+
+ switch (gsk_render_node_get_node_type (node))
+ {
+ case GSK_BLEND_NODE:
+ gsk_ngl_render_job_visit_blend_node (job, node);
+ break;
+
+ case GSK_BLUR_NODE:
+ if (gsk_blur_node_get_radius (node) > 0)
+ gsk_ngl_render_job_visit_blur_node (job, node);
+ else
+ gsk_ngl_render_job_visit_node (job, gsk_blur_node_get_child (node));
+ break;
+
+ case GSK_BORDER_NODE:
+ if (gsk_border_node_get_uniform (node))
+ gsk_ngl_render_job_visit_uniform_border_node (job, node);
+ else
+ gsk_ngl_render_job_visit_border_node (job, node);
+ break;
+
+ case GSK_CLIP_NODE:
+ gsk_ngl_render_job_visit_clip_node (job, node);
+ break;
+
+ case GSK_COLOR_NODE:
+ gsk_ngl_render_job_visit_color_node (job, node);
+ break;
+
+ case GSK_COLOR_MATRIX_NODE:
+ gsk_ngl_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_ngl_render_job_visit_conic_gradient_node (job, node);
+ else
+ gsk_ngl_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);
+ gsk_ngl_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_ngl_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_ngl_render_job_visit_node (job, gsk_cross_fade_node_get_end_child (node));
+ else
+ gsk_ngl_render_job_visit_cross_fade_node (job, node);
+ }
+ break;
+
+ case GSK_DEBUG_NODE:
+ /* Debug nodes are ignored because draws get reordered anyway */
+ gsk_ngl_render_job_visit_node (job, gsk_debug_node_get_child (node));
+ break;
+
+ case GSK_GL_SHADER_NODE:
+ gsk_ngl_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_ngl_render_job_visit_blurred_inset_shadow_node (job, node);
+ else
+ gsk_ngl_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_ngl_render_job_visit_linear_gradient_node (job, node);
+ else
+ gsk_ngl_render_job_visit_as_fallback (job, node);
+ break;
+
+ case GSK_OPACITY_NODE:
+ gsk_ngl_render_job_visit_opacity_node (job, node);
+ break;
+
+ case GSK_OUTSET_SHADOW_NODE:
+ if (gsk_outset_shadow_node_get_blur_radius (node) > 0)
+ gsk_ngl_render_job_visit_blurred_outset_shadow_node (job, node);
+ else
+ gsk_ngl_render_job_visit_unblurred_outset_shadow_node (job, node);
+ break;
+
+ case GSK_RADIAL_GRADIENT_NODE:
+ case GSK_REPEATING_RADIAL_GRADIENT_NODE:
+ gsk_ngl_render_job_visit_radial_gradient_node (job, node);
+ break;
+
+ case GSK_REPEAT_NODE:
+ gsk_ngl_render_job_visit_repeat_node (job, node);
+ break;
+
+ case GSK_ROUNDED_CLIP_NODE:
+ gsk_ngl_render_job_visit_rounded_clip_node (job, node);
+ break;
+
+ case GSK_SHADOW_NODE:
+ gsk_ngl_render_job_visit_shadow_node (job, node);
+ break;
+
+ case GSK_TEXT_NODE:
+ gsk_ngl_render_job_visit_text_node (job,
+ node,
+ gsk_text_node_get_color (node),
+ FALSE);
+ break;
+
+ case GSK_TEXTURE_NODE:
+ gsk_ngl_render_job_visit_texture_node (job, node);
+ break;
+
+ case GSK_TRANSFORM_NODE:
+ gsk_ngl_render_job_visit_transform_node (job, node);
+ break;
+
+ case GSK_CAIRO_NODE:
+ gsk_ngl_render_job_visit_as_fallback (job, node);
+ break;
+
+ case GSK_NOT_A_RENDER_NODE:
+ default:
+ g_assert_not_reached ();
+ break;
+ }
+}
+
+static gboolean
+gsk_ngl_render_job_visit_node_with_offscreen (GskNglRenderJob *job,
+ const GskRenderNode *node,
+ GskNglRenderOffscreen *offscreen)
+{
+ GskTextureKey key;
+ guint cached_id;
+ int filter;
+
+ 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_get_node_type (node) == GSK_TEXTURE_NODE &&
+ offscreen->force_offscreen == FALSE)
+ {
+ GdkTexture *texture = gsk_texture_node_get_texture (node);
+ gsk_ngl_render_job_upload_texture (job, texture, offscreen);
+ g_assert (offscreen->was_offscreen == FALSE);
+ return TRUE;
+ }
+
+ filter = offscreen->linear_filter ? GL_LINEAR : GL_NEAREST;
+
+ /* Check if we've already cached the drawn texture. */
+ 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;
+ key.filter = filter;
+
+ cached_id = gsk_ngl_driver_lookup_texture (job->driver, &key);
+
+ if (cached_id != 0)
+ {
+ offscreen->texture_id = cached_id;
+ init_full_texture_region (offscreen);
+ /* We didn't render it offscreen, but hand out an offscreen texture id */
+ offscreen->was_offscreen = TRUE;
+ return TRUE;
+ }
+
+ float scaled_width;
+ float scaled_height;
+ float scale_x = job->scale_x;
+ float scale_y = job->scale_y;
+
+ g_assert (job->command_queue->max_texture_size > 0);
+
+ /* 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.
+ */
+ {
+ int max_texture_size = job->command_queue->max_texture_size;
+
+ scaled_width = ceilf (offscreen->bounds->size.width * scale_x);
+ if (scaled_width > max_texture_size)
+ {
+ scale_x *= (float)max_texture_size / scaled_width;
+ scaled_width = max_texture_size;
+ }
+
+ scaled_height = ceilf (offscreen->bounds->size.height * scale_y);
+ if (scaled_height > max_texture_size)
+ {
+ scale_y *= (float)max_texture_size / scaled_height;
+ scaled_height = max_texture_size;
+ }
+ }
+
+ GskNglRenderTarget *render_target;
+ graphene_matrix_t prev_projection;
+ graphene_rect_t prev_viewport;
+ graphene_rect_t viewport;
+ float offset_x = job->offset_x;
+ float offset_y = job->offset_y;
+ float prev_alpha;
+ guint prev_fbo;
+
+ if (!gsk_ngl_driver_create_render_target (job->driver,
+ scaled_width, scaled_height,
+ filter, filter,
+ &render_target))
+ g_assert_not_reached ();
+
+ if (gdk_gl_context_has_debug (job->command_queue->context))
+ {
+ 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_ngl_render_job_transform_bounds (job, offscreen->bounds, &viewport);
+ /* Code above will scale the size with the scale we use in the render ops,
+ * but for the viewport size, we need our own size limited by the texture size */
+ viewport.size.width = scaled_width;
+ viewport.size.height = scaled_height;
+
+ gsk_ngl_render_job_set_viewport (job, &viewport, &prev_viewport);
+ gsk_ngl_render_job_set_projection_from_rect (job, &job->viewport, &prev_projection);
+ gsk_ngl_render_job_set_modelview (job, gsk_transform_scale (NULL, scale_x, scale_y));
+ prev_alpha = gsk_ngl_render_job_set_alpha (job, 1.0f);
+ job->offset_x = offset_x;
+ job->offset_y = offset_y;
+
+ prev_fbo = gsk_ngl_command_queue_bind_framebuffer (job->command_queue, render_target->framebuffer_id);
+ gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
+
+ if (offscreen->reset_clip)
+ gsk_ngl_render_job_push_clip (job, &GSK_ROUNDED_RECT_INIT_FROM_RECT (job->viewport));
+
+ gsk_ngl_render_job_visit_node (job, node);
+
+ if (offscreen->reset_clip)
+ gsk_ngl_render_job_pop_clip (job);
+
+ gsk_ngl_render_job_pop_modelview (job);
+ gsk_ngl_render_job_set_viewport (job, &prev_viewport, NULL);
+ gsk_ngl_render_job_set_projection (job, &prev_projection);
+ gsk_ngl_render_job_set_alpha (job, prev_alpha);
+ gsk_ngl_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_ngl_driver_release_render_target (job->driver,
+ render_target,
+ FALSE);
+
+ init_full_texture_region (offscreen);
+
+ if (!offscreen->do_not_cache)
+ gsk_ngl_driver_cache_texture (job->driver, &key, offscreen->texture_id);
+
+ return TRUE;
+}
+
+void
+gsk_ngl_render_job_render_flipped (GskNglRenderJob *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_NGL_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_ngl_command_queue_create_render_target (job->command_queue,
+ MAX (1, job->viewport.size.width),
+ MAX (1, job->viewport.size.height),
+ GL_NEAREST, GL_NEAREST,
+ &framebuffer_id, &texture_id))
+ return;
+
+ /* Setup drawing to our offscreen texture/framebuffer which is flipped */
+ gsk_ngl_command_queue_bind_framebuffer (job->command_queue, framebuffer_id);
+ gsk_ngl_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_ngl_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_ngl_render_job_set_alpha (job, 1.0f);
+ gsk_ngl_command_queue_bind_framebuffer (job->command_queue, job->framebuffer);
+ gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
+ gsk_ngl_render_job_begin_draw (job, job->driver->blit);
+ gsk_ngl_program_set_uniform_texture (job->driver->blit,
+ UNIFORM_SHARED_SOURCE, 0,
+ GL_TEXTURE_2D,
+ GL_TEXTURE0,
+ texture_id);
+ gsk_ngl_render_job_draw_rect (job, &job->viewport);
+ gsk_ngl_render_job_end_draw (job);
+
+ gdk_gl_context_push_debug_group (job->command_queue->context, "Executing command queue");
+ gsk_ngl_command_queue_execute (job->command_queue, surface_height, 1, NULL);
+ gdk_gl_context_pop_debug_group (job->command_queue->context);
+
+ glDeleteFramebuffers (1, &framebuffer_id);
+ glDeleteTextures (1, &texture_id);
+}
+
+void
+gsk_ngl_render_job_render (GskNglRenderJob *job,
+ GskRenderNode *root)
+{
+ G_GNUC_UNUSED gint64 start_time;
+ guint scale_factor;
+ guint surface_height;
+
+ g_return_if_fail (job != NULL);
+ g_return_if_fail (root != NULL);
+ g_return_if_fail (GSK_IS_NGL_DRIVER (job->driver));
+
+ scale_factor = MAX (job->scale_x, job->scale_y);
+ surface_height = job->viewport.size.height;
+
+ gsk_ngl_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_ngl_command_queue_bind_framebuffer (job->command_queue, job->framebuffer);
+ gsk_ngl_command_queue_clear (job->command_queue, 0, &job->viewport);
+ gsk_ngl_render_job_visit_node (job, root);
+ gdk_gl_context_pop_debug_group (job->command_queue->context);
+ gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-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_ngl_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_ngl_command_queue_make_current (job->command_queue);
+ gdk_gl_context_push_debug_group (job->command_queue->context, "Executing command queue");
+ gsk_ngl_command_queue_execute (job->command_queue, surface_height, scale_factor, job->region);
+ gdk_gl_context_pop_debug_group (job->command_queue->context);
+ gdk_profiler_add_mark (start_time, GDK_PROFILER_CURRENT_TIME-start_time, "Execute GL command queue", "");
+}
+
+void
+gsk_ngl_render_job_set_debug_fallback (GskNglRenderJob *job,
+ gboolean debug_fallback)
+{
+ g_return_if_fail (job != NULL);
+
+ job->debug_fallback = !!debug_fallback;
+}
+
+GskNglRenderJob *
+gsk_ngl_render_job_new (GskNglDriver *driver,
+ const graphene_rect_t *viewport,
+ float scale_factor,
+ const cairo_region_t *region,
+ guint framebuffer)
+{
+ const graphene_rect_t *clip_rect = viewport;
+ graphene_rect_t transformed_extents;
+ GskNglRenderJob *job;
+
+ g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
+ g_return_val_if_fail (viewport != NULL, NULL);
+ g_return_val_if_fail (scale_factor > 0, NULL);
+
+ job = g_slice_new0 (GskNglRenderJob);
+ job->driver = g_object_ref (driver);
+ job->command_queue = job->driver->command_queue;
+ job->clip = g_array_sized_new (FALSE, FALSE, sizeof (GskNglRenderClip), 16);
+ job->modelview = g_array_sized_new (FALSE, FALSE, sizeof (GskNglRenderModelview), 16);
+ job->framebuffer = framebuffer;
+ job->offset_x = 0;
+ job->offset_y = 0;
+ job->scale_x = scale_factor;
+ job->scale_y = scale_factor;
+ job->viewport = *viewport;
+
+ gsk_ngl_render_job_set_alpha (job, 1.0);
+ gsk_ngl_render_job_set_projection_from_rect (job, viewport, NULL);
+ gsk_ngl_render_job_set_modelview (job, gsk_transform_scale (NULL, scale_factor, scale_factor));
+
+ /* 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_ngl_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_ngl_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_ngl_render_job_free (GskNglRenderJob *job)
+{
+ job->current_modelview = NULL;
+ job->current_clip = NULL;
+
+ while (job->modelview->len > 0)
+ {
+ GskNglRenderModelview *modelview = &g_array_index (job->modelview, GskNglRenderModelview, job->modelview->len-1);
+ g_clear_pointer (&modelview->transform, gsk_transform_unref);
+ job->modelview->len--;
+ }
+
+ g_clear_object (&job->driver);
+ g_clear_pointer (&job->region, cairo_region_destroy);
+ g_clear_pointer (&job->modelview, g_array_unref);
+ g_clear_pointer (&job->clip, g_array_unref);
+ g_slice_free (GskNglRenderJob, job);
+}
diff --git a/gsk/ngl/gsknglrenderjobprivate.h b/gsk/ngl/gsknglrenderjobprivate.h
new file mode 100644
index 0000000000..ba3f3e49b7
--- /dev/null
+++ b/gsk/ngl/gsknglrenderjobprivate.h
@@ -0,0 +1,39 @@
+/* gsknglrenderjobprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_RENDER_JOB_H__
+#define __GSK_NGL_RENDER_JOB_H__
+
+#include "gskngltypesprivate.h"
+
+GskNglRenderJob *gsk_ngl_render_job_new (GskNglDriver *driver,
+ const graphene_rect_t *viewport,
+ float scale_factor,
+ const cairo_region_t *region,
+ guint framebuffer);
+void gsk_ngl_render_job_free (GskNglRenderJob *job);
+void gsk_ngl_render_job_render (GskNglRenderJob *job,
+ GskRenderNode *root);
+void gsk_ngl_render_job_render_flipped (GskNglRenderJob *job,
+ GskRenderNode *root);
+void gsk_ngl_render_job_set_debug_fallback (GskNglRenderJob *job,
+ gboolean debug_fallback);
+
+#endif /* __GSK_NGL_RENDER_JOB_H__ */
diff --git a/gsk/ngl/gsknglshadowlibrary.c b/gsk/ngl/gsknglshadowlibrary.c
new file mode 100644
index 0000000000..bcf524c8b7
--- /dev/null
+++ b/gsk/ngl/gsknglshadowlibrary.c
@@ -0,0 +1,228 @@
+/* gsknglshadowlibrary.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <string.h>
+
+#include "gskngldriverprivate.h"
+#include "gsknglshadowlibraryprivate.h"
+
+#define MAX_UNUSED_FRAMES (16 * 5)
+
+struct _GskNglShadowLibrary
+{
+ GObject parent_instance;
+ GskNglDriver *driver;
+ GArray *shadows;
+};
+
+typedef struct _Shadow
+{
+ GskRoundedRect outline;
+ float blur_radius;
+ guint texture_id;
+ gint64 last_used_in_frame;
+} Shadow;
+
+G_DEFINE_TYPE (GskNglShadowLibrary, gsk_ngl_shadow_library, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_DRIVER,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+GskNglShadowLibrary *
+gsk_ngl_shadow_library_new (GskNglDriver *driver)
+{
+ g_return_val_if_fail (GSK_IS_NGL_DRIVER (driver), NULL);
+
+ return g_object_new (GSK_TYPE_GL_SHADOW_LIBRARY,
+ "driver", driver,
+ NULL);
+}
+
+static void
+gsk_ngl_shadow_library_dispose (GObject *object)
+{
+ GskNglShadowLibrary *self = (GskNglShadowLibrary *)object;
+
+ for (guint i = 0; i < self->shadows->len; i++)
+ {
+ const Shadow *shadow = &g_array_index (self->shadows, Shadow, i);
+ gsk_ngl_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_ngl_shadow_library_parent_class)->dispose (object);
+}
+
+static void
+gsk_ngl_shadow_library_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GskNglShadowLibrary *self = GSK_NGL_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_ngl_shadow_library_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GskNglShadowLibrary *self = GSK_NGL_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_ngl_shadow_library_class_init (GskNglShadowLibraryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->dispose = gsk_ngl_shadow_library_dispose;
+ object_class->get_property = gsk_ngl_shadow_library_get_property;
+ object_class->set_property = gsk_ngl_shadow_library_set_property;
+
+ properties [PROP_DRIVER] =
+ g_param_spec_object ("driver",
+ "Driver",
+ "Driver",
+ GSK_TYPE_NGL_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_ngl_shadow_library_init (GskNglShadowLibrary *self)
+{
+ self->shadows = g_array_new (FALSE, FALSE, sizeof (Shadow));
+}
+
+void
+gsk_ngl_shadow_library_insert (GskNglShadowLibrary *self,
+ const GskRoundedRect *outline,
+ float blur_radius,
+ guint texture_id)
+{
+ Shadow *shadow;
+
+ g_assert (GSK_IS_NGL_SHADOW_LIBRARY (self));
+ g_assert (outline != NULL);
+ g_assert (texture_id != 0);
+
+ gsk_ngl_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_ngl_shadow_library_lookup (GskNglShadowLibrary *self,
+ const GskRoundedRect *outline,
+ float blur_radius)
+{
+ Shadow *ret = NULL;
+
+ g_assert (GSK_IS_NGL_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;
+}
+
+void
+gsk_ngl_shadow_library_begin_frame (GskNglShadowLibrary *self)
+{
+ gint64 watermark;
+ int i;
+ int p;
+
+ g_return_if_fail (GSK_IS_NGL_SHADOW_LIBRARY (self));
+
+ 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_ngl_driver_release_texture_by_id (self->driver, shadow->texture_id);
+ g_array_remove_index_fast (self->shadows, i);
+ p--;
+ i--;
+ }
+ }
+}
diff --git a/gsk/ngl/gsknglshadowlibraryprivate.h b/gsk/ngl/gsknglshadowlibraryprivate.h
new file mode 100644
index 0000000000..3c534663dc
--- /dev/null
+++ b/gsk/ngl/gsknglshadowlibraryprivate.h
@@ -0,0 +1,44 @@
+/* gsknglshadowlibraryprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_SHADOW_LIBRARY_PRIVATE_H__
+#define __GSK_NGL_SHADOW_LIBRARY_PRIVATE_H__
+
+#include "gskngltexturelibraryprivate.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_SHADOW_LIBRARY (gsk_ngl_shadow_library_get_type())
+
+G_DECLARE_FINAL_TYPE (GskNglShadowLibrary, gsk_ngl_shadow_library, GSK, NGL_SHADOW_LIBRARY, GObject)
+
+GskNglShadowLibrary *gsk_ngl_shadow_library_new (GskNglDriver *driver);
+void gsk_ngl_shadow_library_begin_frame (GskNglShadowLibrary *self);
+guint gsk_ngl_shadow_library_lookup (GskNglShadowLibrary *self,
+ const GskRoundedRect *outline,
+ float blur_radius);
+void gsk_ngl_shadow_library_insert (GskNglShadowLibrary *self,
+ const GskRoundedRect *outline,
+ float blur_radius,
+ guint texture_id);
+
+G_END_DECLS
+
+#endif /* __GSK_NGL_SHADOW_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/ngl/gskngltexturelibrary.c b/gsk/ngl/gskngltexturelibrary.c
new file mode 100644
index 0000000000..dc9303f373
--- /dev/null
+++ b/gsk/ngl/gskngltexturelibrary.c
@@ -0,0 +1,315 @@
+/* gskngltexturelibrary.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include "gsknglcommandqueueprivate.h"
+#include "gskngldriverprivate.h"
+#include "gskngltexturelibraryprivate.h"
+
+G_DEFINE_ABSTRACT_TYPE (GskNglTextureLibrary, gsk_ngl_texture_library, G_TYPE_OBJECT)
+
+enum {
+ PROP_0,
+ PROP_DRIVER,
+ N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gsk_ngl_texture_library_constructed (GObject *object)
+{
+ G_OBJECT_CLASS (gsk_ngl_texture_library_parent_class)->constructed (object);
+
+ g_assert (GSK_NGL_TEXTURE_LIBRARY (object)->hash_table != NULL);
+}
+
+static void
+gsk_ngl_texture_library_dispose (GObject *object)
+{
+ GskNglTextureLibrary *self = (GskNglTextureLibrary *)object;
+
+ g_clear_object (&self->driver);
+
+ G_OBJECT_CLASS (gsk_ngl_texture_library_parent_class)->dispose (object);
+}
+
+static void
+gsk_ngl_texture_library_get_property (GObject *object,
+ guint prop_id,
+ GValue *value,
+ GParamSpec *pspec)
+{
+ GskNglTextureLibrary *self = GSK_NGL_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_ngl_texture_library_set_property (GObject *object,
+ guint prop_id,
+ const GValue *value,
+ GParamSpec *pspec)
+{
+ GskNglTextureLibrary *self = GSK_NGL_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_ngl_texture_library_class_init (GskNglTextureLibraryClass *klass)
+{
+ GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+ object_class->constructed = gsk_ngl_texture_library_constructed;
+ object_class->dispose = gsk_ngl_texture_library_dispose;
+ object_class->get_property = gsk_ngl_texture_library_get_property;
+ object_class->set_property = gsk_ngl_texture_library_set_property;
+
+ properties [PROP_DRIVER] =
+ g_param_spec_object ("driver",
+ "Driver",
+ "Driver",
+ GSK_TYPE_NGL_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_ngl_texture_library_init (GskNglTextureLibrary *self)
+{
+}
+
+void
+gsk_ngl_texture_library_set_funcs (GskNglTextureLibrary *self,
+ GHashFunc hash_func,
+ GEqualFunc equal_func,
+ GDestroyNotify key_destroy,
+ GDestroyNotify value_destroy)
+{
+ g_return_if_fail (GSK_IS_NGL_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_ngl_texture_library_begin_frame (GskNglTextureLibrary *self)
+{
+ g_return_if_fail (GSK_IS_NGL_TEXTURE_LIBRARY (self));
+
+ if (GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame)
+ GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame (self);
+}
+
+void
+gsk_ngl_texture_library_end_frame (GskNglTextureLibrary *self)
+{
+ g_return_if_fail (GSK_IS_NGL_TEXTURE_LIBRARY (self));
+
+ if (GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->end_frame)
+ GSK_NGL_TEXTURE_LIBRARY_GET_CLASS (self)->end_frame (self);
+}
+
+static GskNglTexture *
+gsk_ngl_texture_library_pack_one (GskNglTextureLibrary *self,
+ guint width,
+ guint height)
+{
+ GskNglTexture *texture;
+
+ g_assert (GSK_IS_NGL_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_ngl_driver_create_texture (self->driver, width, height, GL_LINEAR, GL_LINEAR);
+ texture->permanent = TRUE;
+
+ return texture;
+}
+
+static inline gboolean
+gsk_ngl_texture_atlas_pack (GskNglTextureAtlas *self,
+ int width,
+ int height,
+ int *out_x,
+ int *out_y)
+{
+ stbrp_rect rect;
+
+ rect.w = width;
+ rect.h = height;
+
+ stbrp_pack_rects (&self->context, &rect, 1);
+
+ if (rect.was_packed)
+ {
+ *out_x = rect.x;
+ *out_y = rect.y;
+ }
+
+ return rect.was_packed;
+}
+
+static void
+gsk_ngl_texture_atlases_pack (GskNglDriver *driver,
+ int width,
+ int height,
+ GskNglTextureAtlas **out_atlas,
+ int *out_x,
+ int *out_y)
+{
+ GskNglTextureAtlas *atlas = NULL;
+ int x, y;
+
+ for (guint i = 0; i < driver->atlases->len; i++)
+ {
+ atlas = g_ptr_array_index (driver->atlases, i);
+
+ if (gsk_ngl_texture_atlas_pack (atlas, width, height, &x, &y))
+ break;
+
+ atlas = NULL;
+ }
+
+ if (atlas == NULL)
+ {
+ /* No atlas has enough space, so create a new one... */
+ atlas = gsk_ngl_driver_create_atlas (driver);
+
+ /* Pack it onto that one, which surely has enough space... */
+ if (!gsk_ngl_texture_atlas_pack (atlas, width, height, &x, &y))
+ g_assert_not_reached ();
+ }
+
+ *out_atlas = atlas;
+ *out_x = x;
+ *out_y = y;
+}
+
+gpointer
+gsk_ngl_texture_library_pack (GskNglTextureLibrary *self,
+ gpointer key,
+ gsize valuelen,
+ guint width,
+ guint height,
+ int padding,
+ guint *out_packed_x,
+ guint *out_packed_y)
+{
+ GskNglTextureAtlasEntry *entry;
+ GskNglTextureAtlas *atlas = NULL;
+
+ g_assert (GSK_IS_NGL_TEXTURE_LIBRARY (self));
+ g_assert (key != NULL);
+ g_assert (valuelen > sizeof (GskNglTextureAtlasEntry));
+ g_assert (out_packed_x != NULL);
+ g_assert (out_packed_y != NULL);
+
+ entry = g_slice_alloc0 (valuelen);
+ entry->n_pixels = width * height;
+ entry->accessed = 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 (width <= self->max_entry_size && height <= self->max_entry_size)
+ {
+ int packed_x;
+ int packed_y;
+
+ gsk_ngl_texture_atlases_pack (self->driver,
+ padding + width + padding,
+ padding + height + padding,
+ &atlas,
+ &packed_x,
+ &packed_y);
+
+ entry->atlas = atlas;
+ entry->is_atlased = TRUE;
+ entry->area.x = (float)(packed_x + padding) / atlas->width;
+ entry->area.y = (float)(packed_y + padding) / atlas->height;
+ entry->area.x2 = entry->area.x + (float)width / atlas->width;
+ entry->area.y2 = entry->area.y + (float)height / atlas->height;
+
+ *out_packed_x = packed_x;
+ *out_packed_y = packed_y;
+ }
+ else
+ {
+ GskNglTexture *texture = gsk_ngl_texture_library_pack_one (self,
+ padding + width + padding,
+ padding + height + padding);
+
+ entry->texture = texture;
+ entry->is_atlased = FALSE;
+ entry->accessed = TRUE;
+ entry->area.x = 0.0f;
+ entry->area.y = 0.0f;
+ entry->area.x2 = 1.0f;
+ entry->area.y2 = 1.0f;
+
+ *out_packed_x = padding;
+ *out_packed_y = padding;
+ }
+
+ g_hash_table_insert (self->hash_table, key, entry);
+
+ return entry;
+}
diff --git a/gsk/ngl/gskngltexturelibraryprivate.h b/gsk/ngl/gskngltexturelibraryprivate.h
new file mode 100644
index 0000000000..56c3d604cc
--- /dev/null
+++ b/gsk/ngl/gskngltexturelibraryprivate.h
@@ -0,0 +1,202 @@
+/* gskngltexturelibraryprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_TEXTURE_LIBRARY_PRIVATE_H__
+#define __GSK_NGL_TEXTURE_LIBRARY_PRIVATE_H__
+
+#include "gskngltypesprivate.h"
+#include "gskngltexturepoolprivate.h"
+
+#include "../gl/stb_rect_pack.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_TEXTURE_LIBRARY (gsk_ngl_texture_library_get_type ())
+#define GSK_NGL_TEXTURE_LIBRARY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY, GskNglTextureLibrary))
+#define GSK_IS_NGL_TEXTURE_LIBRARY(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY))
+#define GSK_NGL_TEXTURE_LIBRARY_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_GL_TEXTURE_LIBRARY, GskNglTextureLibraryClass))
+#define GSK_IS_NGL_TEXTURE_LIBRARY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_GL_TEXTURE_LIBRARY))
+#define GSK_NGL_TEXTURE_LIBRARY_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_GL_TEXTURE_LIBRARY, GskNglTextureLibraryClass))
+
+typedef struct _GskNglTextureAtlas
+{
+ 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;
+
+ void *user_data;
+} GskNglTextureAtlas;
+
+typedef struct _GskNglTextureAtlasEntry
+{
+ /* 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 {
+ GskNglTextureAtlas *atlas;
+ GskNglTexture *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;
+
+ /* Suffix data that is per-library specific. gpointer used to
+ * guarantee the alignment for the entries using this.
+ */
+ gpointer data[0];
+} GskNglTextureAtlasEntry;
+
+typedef struct _GskNglTextureLibrary
+{
+ GObject parent_instance;
+ GskNglDriver *driver;
+ GHashTable *hash_table;
+ guint max_entry_size;
+} GskNglTextureLibrary;
+
+typedef struct _GskNglTextureLibraryClass
+{
+ GObjectClass parent_class;
+
+ void (*begin_frame) (GskNglTextureLibrary *library);
+ void (*end_frame) (GskNglTextureLibrary *library);
+} GskNglTextureLibraryClass;
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (GskNglTextureLibrary, g_object_unref)
+
+GType gsk_ngl_texture_library_get_type (void) G_GNUC_CONST;
+void gsk_ngl_texture_library_set_funcs (GskNglTextureLibrary *self,
+ GHashFunc hash_func,
+ GEqualFunc equal_func,
+ GDestroyNotify key_destroy,
+ GDestroyNotify value_destroy);
+void gsk_ngl_texture_library_begin_frame (GskNglTextureLibrary *self);
+void gsk_ngl_texture_library_end_frame (GskNglTextureLibrary *self);
+gpointer gsk_ngl_texture_library_pack (GskNglTextureLibrary *self,
+ gpointer key,
+ gsize valuelen,
+ guint width,
+ guint height,
+ int padding,
+ guint *out_packed_x,
+ guint *out_packed_y);
+
+static inline void
+gsk_ngl_texture_atlas_mark_unused (GskNglTextureAtlas *self,
+ int n_pixels)
+{
+ self->unused_pixels += n_pixels;
+}
+
+static inline void
+gsk_ngl_texture_atlas_mark_used (GskNglTextureAtlas *self,
+ int n_pixels)
+{
+ self->unused_pixels -= n_pixels;
+}
+
+static inline gboolean
+gsk_ngl_texture_library_lookup (GskNglTextureLibrary *self,
+ gconstpointer key,
+ GskNglTextureAtlasEntry **out_entry)
+{
+ GskNglTextureAtlasEntry *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)
+ {
+ if (!entry->used && entry->is_atlased)
+ {
+ g_assert (entry->atlas != NULL);
+ gsk_ngl_texture_atlas_mark_used (entry->atlas, entry->n_pixels);
+ entry->used = TRUE;
+ }
+
+ entry->accessed = TRUE;
+ *out_entry = entry;
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static inline guint
+GSK_NGL_TEXTURE_ATLAS_ENTRY_TEXTURE (gconstpointer d)
+{
+ const GskNglTextureAtlasEntry *e = d;
+
+ return e->is_atlased ? e->atlas->texture_id
+ : e->texture ? e->texture->texture_id : 0;
+}
+
+static inline double
+gsk_ngl_texture_atlas_get_unused_ratio (const GskNglTextureAtlas *self)
+{
+ if (self->unused_pixels > 0)
+ return (double)(self->unused_pixels) / (double)(self->width * self->height);
+ return 0.0;
+}
+
+static inline gboolean
+gsk_ngl_texture_library_can_cache (GskNglTextureLibrary *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
+
+#endif /* __GSK_NGL_TEXTURE_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/ngl/gskngltexturepool.c b/gsk/ngl/gskngltexturepool.c
new file mode 100644
index 0000000000..707ae37455
--- /dev/null
+++ b/gsk/ngl/gskngltexturepool.c
@@ -0,0 +1,188 @@
+/* gskngltexturepool.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gdk/gdktextureprivate.h>
+
+#include "gskngltexturepoolprivate.h"
+#include "ninesliceprivate.h"
+
+void
+gsk_ngl_texture_free (GskNglTexture *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_slice_free (GskNglTexture, texture);
+ }
+}
+
+void
+gsk_ngl_texture_pool_init (GskNglTexturePool *self)
+{
+ g_queue_init (&self->queue);
+}
+
+void
+gsk_ngl_texture_pool_clear (GskNglTexturePool *self)
+{
+ guint *free_me = NULL;
+ guint *texture_ids;
+ guint i = 0;
+
+ if G_LIKELY (self->queue.length <= 1024)
+ texture_ids = g_newa (guint, self->queue.length);
+ else
+ texture_ids = free_me = g_new (guint, self->queue.length);
+
+ while (self->queue.length > 0)
+ {
+ GskNglTexture *head = g_queue_peek_head (&self->queue);
+
+ g_queue_unlink (&self->queue, &head->link);
+
+ texture_ids[i++] = head->texture_id;
+ head->texture_id = 0;
+
+ gsk_ngl_texture_free (head);
+ }
+
+ g_assert (self->queue.length == 0);
+
+ if (i > 0)
+ glDeleteTextures (i, texture_ids);
+
+ g_free (free_me);
+}
+
+void
+gsk_ngl_texture_pool_put (GskNglTexturePool *self,
+ GskNglTexture *texture)
+{
+ g_assert (self != NULL);
+ g_assert (texture != NULL);
+ g_assert (texture->user == NULL);
+ g_assert (texture->link.prev == NULL);
+ g_assert (texture->link.next == NULL);
+ g_assert (texture->link.data == texture);
+
+ if (texture->permanent)
+ gsk_ngl_texture_free (texture);
+ else
+ g_queue_push_tail_link (&self->queue, &texture->link);
+}
+
+GskNglTexture *
+gsk_ngl_texture_pool_get (GskNglTexturePool *self,
+ int width,
+ int height,
+ int min_filter,
+ int mag_filter)
+{
+ GskNglTexture *texture;
+
+ g_assert (self != NULL);
+
+ texture = g_slice_new0 (GskNglTexture);
+ texture->link.data = texture;
+ texture->min_filter = min_filter;
+ texture->mag_filter = mag_filter;
+
+ glGenTextures (1, &texture->texture_id);
+
+ glActiveTexture (GL_TEXTURE0);
+ glBindTexture (GL_TEXTURE_2D, texture->texture_id);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, min_filter);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, mag_filter);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+ glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+ if (gdk_gl_context_get_use_es (gdk_gl_context_get_current ()))
+ glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
+ else
+ glTexImage2D (GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
+
+ glBindTexture (GL_TEXTURE_2D, 0);
+
+ return texture;
+}
+
+GskNglTexture *
+gsk_ngl_texture_new (guint texture_id,
+ int width,
+ int height,
+ int min_filter,
+ int mag_filter,
+ gint64 frame_id)
+{
+ GskNglTexture *texture;
+
+ texture = g_slice_new0 (GskNglTexture);
+ texture->texture_id = texture_id;
+ texture->link.data = texture;
+ texture->min_filter = min_filter;
+ texture->mag_filter = mag_filter;
+ texture->width = width;
+ texture->height = height;
+ texture->last_used_in_frame = frame_id;
+
+ return texture;
+}
+
+const GskNglTextureNineSlice *
+gsk_ngl_texture_get_nine_slice (GskNglTexture *texture,
+ const GskRoundedRect *outline,
+ float extra_pixels)
+{
+ g_assert (texture != NULL);
+ g_assert (outline != NULL);
+
+ if G_UNLIKELY (texture->nine_slice == NULL)
+ {
+ texture->nine_slice = g_new0 (GskNglTextureNineSlice, 9);
+
+ nine_slice_rounded_rect (texture->nine_slice, outline);
+ nine_slice_grow (texture->nine_slice, extra_pixels);
+ nine_slice_to_texture_coords (texture->nine_slice, texture->width, texture->height);
+ }
+
+ return texture->nine_slice;
+}
diff --git a/gsk/ngl/gskngltexturepoolprivate.h b/gsk/ngl/gskngltexturepoolprivate.h
new file mode 100644
index 0000000000..8b39ec5440
--- /dev/null
+++ b/gsk/ngl/gskngltexturepoolprivate.h
@@ -0,0 +1,102 @@
+/* gskngltexturepoolprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef _GSK_NGL_TEXTURE_POOL_PRIVATE_H__
+#define _GSK_NGL_TEXTURE_POOL_PRIVATE_H__
+
+#include "gskngltypesprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GskNglTexturePool
+{
+ GQueue queue;
+} GskNglTexturePool;
+
+struct _GskNglTextureSlice
+{
+ cairo_rectangle_int_t rect;
+ guint texture_id;
+};
+
+struct _GskNglTextureNineSlice
+{
+ cairo_rectangle_int_t rect;
+ struct {
+ float x;
+ float y;
+ float x2;
+ float y2;
+ } area;
+};
+
+struct _GskNglTexture
+{
+ /* 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 sliced textures */
+ GskNglTextureSlice *slices;
+ guint n_slices;
+
+ /* Only used by nine-slice textures */
+ GskNglTextureNineSlice *nine_slice;
+
+ /* The actual GL texture identifier in some shared context */
+ guint texture_id;
+
+ int width;
+ int height;
+ int min_filter;
+ int mag_filter;
+
+ /* Set when used by an atlas so we don't drop the texture */
+ guint permanent : 1;
+};
+
+void gsk_ngl_texture_pool_init (GskNglTexturePool *self);
+void gsk_ngl_texture_pool_clear (GskNglTexturePool *self);
+GskNglTexture *gsk_ngl_texture_pool_get (GskNglTexturePool *self,
+ int width,
+ int height,
+ int min_filter,
+ int mag_filter);
+void gsk_ngl_texture_pool_put (GskNglTexturePool *self,
+ GskNglTexture *texture);
+GskNglTexture *gsk_ngl_texture_new (guint texture_id,
+ int width,
+ int height,
+ int min_filter,
+ int mag_filter,
+ gint64 frame_id);
+const GskNglTextureNineSlice *gsk_ngl_texture_get_nine_slice (GskNglTexture *texture,
+ const GskRoundedRect *outline,
+ float extra_pixels);
+void gsk_ngl_texture_free (GskNglTexture *texture);
+
+G_END_DECLS
+
+#endif /* _GSK_NGL_TEXTURE_POOL_PRIVATE_H__ */
diff --git a/gsk/ngl/gskngltypesprivate.h b/gsk/ngl/gskngltypesprivate.h
new file mode 100644
index 0000000000..aba6f2f4c9
--- /dev/null
+++ b/gsk/ngl/gskngltypesprivate.h
@@ -0,0 +1,62 @@
+/* gskngltypesprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __GSK_NGL_TYPES_PRIVATE_H__
+#define __GSK_NGL_TYPES_PRIVATE_H__
+
+#include <epoxy/gl.h>
+#include <graphene.h>
+#include <gdk/gdk.h>
+#include <gsk/gsk.h>
+
+G_BEGIN_DECLS
+
+#define GSK_NGL_N_VERTICES 6
+
+typedef struct _GskNglAttachmentState GskNglAttachmentState;
+typedef struct _GskNglBuffer GskNglBuffer;
+typedef struct _GskNglCommandQueue GskNglCommandQueue;
+typedef struct _GskNglCompiler GskNglCompiler;
+typedef struct _GskNglDrawVertex GskNglDrawVertex;
+typedef struct _GskNglRenderTarget GskNglRenderTarget;
+typedef struct _GskNglGlyphLibrary GskNglGlyphLibrary;
+typedef struct _GskNglIconLibrary GskNglIconLibrary;
+typedef struct _GskNglProgram GskNglProgram;
+typedef struct _GskNglRenderJob GskNglRenderJob;
+typedef struct _GskNglShadowLibrary GskNglShadowLibrary;
+typedef struct _GskNglTexture GskNglTexture;
+typedef struct _GskNglTextureSlice GskNglTextureSlice;
+typedef struct _GskNglTextureAtlas GskNglTextureAtlas;
+typedef struct _GskNglTextureLibrary GskNglTextureLibrary;
+typedef struct _GskNglTextureNineSlice GskNglTextureNineSlice;
+typedef struct _GskNglUniformInfo GskNglUniformInfo;
+typedef struct _GskNglUniformProgram GskNglUniformProgram;
+typedef struct _GskNglUniformState GskNglUniformState;
+typedef struct _GskNglDriver GskNglDriver;
+
+struct _GskNglDrawVertex
+{
+ float position[2];
+ float uv[2];
+};
+
+G_END_DECLS
+
+#endif /* __GSK_NGL_TYPES_PRIVATE_H__ */
diff --git a/gsk/ngl/gskngluniformstate.c b/gsk/ngl/gskngluniformstate.c
new file mode 100644
index 0000000000..9b896e7d1a
--- /dev/null
+++ b/gsk/ngl/gskngluniformstate.c
@@ -0,0 +1,270 @@
+/* gskngluniformstate.c
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#include "config.h"
+
+#include <gsk/gskroundedrectprivate.h>
+#include <string.h>
+
+#include "gskngluniformstateprivate.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,
+};
+
+GskNglUniformState *
+gsk_ngl_uniform_state_new (void)
+{
+ GskNglUniformState *state;
+
+ state = g_atomic_rc_box_new0 (GskNglUniformState);
+ 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);
+
+ return g_steal_pointer (&state);
+}
+
+GskNglUniformState *
+gsk_ngl_uniform_state_ref (GskNglUniformState *state)
+{
+ return g_atomic_rc_box_acquire (state);
+}
+
+static void
+gsk_ngl_uniform_state_finalize (gpointer data)
+{
+ GskNglUniformState *state = data;
+
+ g_clear_pointer (&state->programs, g_hash_table_unref);
+ g_clear_pointer (&state->values_buf, g_free);
+}
+
+void
+gsk_ngl_uniform_state_unref (GskNglUniformState *state)
+{
+ g_atomic_rc_box_release_full (state, gsk_ngl_uniform_state_finalize);
+}
+
+gpointer
+gsk_ngl_uniform_state_init_value (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ GskNglUniformFormat format,
+ guint array_count,
+ guint location,
+ GskNglUniformInfoElement **infoptr)
+{
+ GskNglUniformInfoElement *info;
+ guint offset;
+
+ g_assert (state != NULL);
+ g_assert (array_count < 32);
+ g_assert ((int)format >= 0 && format < GSK_NGL_UNIFORM_FORMAT_LAST);
+ g_assert (format > 0);
+ g_assert (program != NULL);
+ g_assert (program->sparse != NULL);
+ g_assert (program->n_sparse <= program->n_uniforms);
+ g_assert (location < GL_MAX_UNIFORM_LOCATIONS || location == (guint)-1);
+ g_assert (location < program->n_uniforms);
+
+ /* Handle unused uniforms gracefully */
+ if G_UNLIKELY (location == (guint)-1)
+ return NULL;
+
+ info = &program->uniforms[location];
+
+ if G_LIKELY (format == info->info.format)
+ {
+ if G_LIKELY (array_count <= info->info.array_count)
+ {
+ *infoptr = info;
+ return GSK_NGL_UNIFORM_VALUE (state->values_buf, info->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 (info->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, location, info->info.format, format,
+ info->info.array_count, array_count);
+ *infoptr = NULL;
+ return NULL;
+ }
+
+setup_info:
+
+ gsk_ngl_uniform_state_realloc (state,
+ uniform_sizes[format] * MAX (1, array_count),
+ &offset);
+
+ /* we have 21 bits for offset */
+ g_assert (offset < (1 << GSK_NGL_UNIFORM_OFFSET_BITS));
+
+ /* We could once again be setting up this info if the array size grew.
+ * So make sure that we have space in our space array for the value.
+ */
+ g_assert (info->info.format != 0 || program->n_sparse < program->n_uniforms);
+ if (info->info.format == 0)
+ program->sparse[program->n_sparse++] = location;
+
+ info->info.format = format;
+ info->info.offset = offset;
+ info->info.array_count = array_count;
+ info->info.initial = TRUE;
+ info->stamp = 0;
+
+ *infoptr = info;
+
+ return GSK_NGL_UNIFORM_VALUE (state->values_buf, info->info.offset);
+}
+
+void
+gsk_ngl_uniform_state_end_frame (GskNglUniformState *state)
+{
+ GHashTableIter iter;
+ GskNglUniformProgram *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_sparse; j++)
+ {
+ guint location = program->sparse[j];
+ GskNglUniformInfoElement *info = &program->uniforms[location];
+ guint size;
+
+ g_assert (info->info.format > 0);
+
+ /* Calculate how much size is needed for the uniform, including arrays */
+ size = uniform_sizes[info->info.format] * MAX (1, info->info.array_count);
+
+ /* Adjust alignment for value */
+ allocator += gsk_ngl_uniform_state_align (allocator, size);
+
+ /* Offset is in slots of 4 bytes */
+ info->info.offset = allocator / 4;
+ info->info.initial = TRUE;
+ info->stamp = 0;
+
+ /* Now advance for this items data */
+ allocator += size;
+ }
+ }
+
+ state->values_pos = allocator;
+
+ g_assert (allocator <= state->values_len);
+}
+
+gsize
+gsk_ngl_uniform_format_size (GskNglUniformFormat format)
+{
+ g_assert (format > 0);
+ g_assert (format < GSK_NGL_UNIFORM_FORMAT_LAST);
+
+ return uniform_sizes[format];
+}
+
+GskNglUniformProgram *
+gsk_ngl_uniform_state_get_program (GskNglUniformState *state,
+ guint program,
+ guint n_uniforms)
+{
+ GskNglUniformProgram *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);
+
+ ret = g_hash_table_lookup (state->programs, GUINT_TO_POINTER (program));
+
+ if (ret == NULL)
+ {
+ gsize uniform_size = n_uniforms * sizeof (GskNglUniformInfoElement);
+ gsize sparse_size = n_uniforms * sizeof (guint);
+ gsize size = sizeof (GskNglUniformProgram) + uniform_size + sparse_size;
+
+ /* Must be multiple of 4 for space pointer to align */
+ G_STATIC_ASSERT (sizeof (GskNglUniformInfoElement) == 8);
+
+ ret = g_malloc0 (size);
+ ret->program_id = program;
+ ret->n_uniforms = n_uniforms;
+ ret->n_sparse = 0;
+ ret->sparse = (guint *)&ret->uniforms[n_uniforms];
+
+ for (guint i = 0; i < n_uniforms; i++)
+ ret->uniforms[i].info.initial = TRUE;
+
+ g_hash_table_insert (state->programs, GUINT_TO_POINTER (program), ret);
+ }
+
+ return ret;
+}
diff --git a/gsk/ngl/gskngluniformstateprivate.h b/gsk/ngl/gskngluniformstateprivate.h
new file mode 100644
index 0000000000..1385f93dac
--- /dev/null
+++ b/gsk/ngl/gskngluniformstateprivate.h
@@ -0,0 +1,685 @@
+/* gskngluniformstateprivate.h
+ *
+ * Copyright 2020 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef GSK_NGL_UNIFORM_STATE_PRIVATE_H
+#define GSK_NGL_UNIFORM_STATE_PRIVATE_H
+
+#include "gskngltypesprivate.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_NGL_UNIFORM_ARRAY_BITS 5
+#define GSK_NGL_UNIFORM_FORMAT_BITS 5
+#define GSK_NGL_UNIFORM_OFFSET_BITS 21
+
+typedef struct _GskNglUniformInfo
+{
+ guint initial : 1;
+ guint format : GSK_NGL_UNIFORM_FORMAT_BITS;
+ guint array_count : GSK_NGL_UNIFORM_ARRAY_BITS;
+ guint offset : GSK_NGL_UNIFORM_OFFSET_BITS;
+} GskNglUniformInfo;
+
+G_STATIC_ASSERT (sizeof (GskNglUniformInfo) == 4);
+
+typedef struct _GskNglUniformInfoElement
+{
+ GskNglUniformInfo info;
+ guint stamp;
+} GskNglUniformInfoElement;
+
+G_STATIC_ASSERT (sizeof (GskNglUniformInfoElement) == 8);
+
+typedef struct _GskNglUniformProgram
+{
+ guint program_id;
+ guint n_uniforms : 12;
+ guint has_attachments : 1;
+
+ /* To avoid walking our 1:1 array of location->uniform slots, we have
+ * a sparse index that allows us to skip the empty zones.
+ */
+ guint *sparse;
+ guint n_sparse;
+
+ /* Uniforms are provided inline at the end of structure to avoid
+ * an extra dereference.
+ */
+ GskNglUniformInfoElement uniforms[0];
+} GskNglUniformProgram;
+
+typedef struct _GskNglUniformState
+{
+ GHashTable *programs;
+ guint8 *values_buf;
+ guint values_pos;
+ guint values_len;
+} GskNglUniformState;
+
+/**
+ * GskNglUniformStateCallback:
+ * @info: a pointer to the information about the uniform
+ * @location: the location of the uniform within the GPU program.
+ * @user_data: closure data for the callback
+ *
+ * This callback can be used to snapshot state of a program which
+ * is useful when batching commands so that the state may be compared
+ * with future evocations of the program.
+ */
+typedef void (*GskNglUniformStateCallback) (const GskNglUniformInfo *info,
+ guint location,
+ gpointer user_data);
+
+typedef enum _GskNglUniformKind
+{
+ GSK_NGL_UNIFORM_FORMAT_1F = 1,
+ GSK_NGL_UNIFORM_FORMAT_2F,
+ GSK_NGL_UNIFORM_FORMAT_3F,
+ GSK_NGL_UNIFORM_FORMAT_4F,
+
+ GSK_NGL_UNIFORM_FORMAT_1FV,
+ GSK_NGL_UNIFORM_FORMAT_2FV,
+ GSK_NGL_UNIFORM_FORMAT_3FV,
+ GSK_NGL_UNIFORM_FORMAT_4FV,
+
+ GSK_NGL_UNIFORM_FORMAT_1I,
+ GSK_NGL_UNIFORM_FORMAT_2I,
+ GSK_NGL_UNIFORM_FORMAT_3I,
+ GSK_NGL_UNIFORM_FORMAT_4I,
+
+ GSK_NGL_UNIFORM_FORMAT_1UI,
+
+ GSK_NGL_UNIFORM_FORMAT_TEXTURE,
+
+ GSK_NGL_UNIFORM_FORMAT_MATRIX,
+ GSK_NGL_UNIFORM_FORMAT_ROUNDED_RECT,
+ GSK_NGL_UNIFORM_FORMAT_COLOR,
+
+ GSK_NGL_UNIFORM_FORMAT_LAST
+} GskNglUniformFormat;
+
+G_STATIC_ASSERT (GSK_NGL_UNIFORM_FORMAT_LAST < (1 << GSK_NGL_UNIFORM_FORMAT_BITS));
+
+GskNglUniformState *gsk_ngl_uniform_state_new (void);
+GskNglUniformState *gsk_ngl_uniform_state_ref (GskNglUniformState *state);
+void gsk_ngl_uniform_state_unref (GskNglUniformState *state);
+GskNglUniformProgram *gsk_ngl_uniform_state_get_program (GskNglUniformState *state,
+ guint program,
+ guint n_uniforms);
+void gsk_ngl_uniform_state_end_frame (GskNglUniformState *state);
+gsize gsk_ngl_uniform_format_size (GskNglUniformFormat format);
+gpointer gsk_ngl_uniform_state_init_value (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ GskNglUniformFormat format,
+ guint array_count,
+ guint location,
+ GskNglUniformInfoElement **infoptr);
+
+#define GSK_NGL_UNIFORM_VALUE(base, offset) ((gpointer)((base) + ((offset) * 4)))
+#define gsk_ngl_uniform_state_get_uniform_data(state,offset) GSK_NGL_UNIFORM_VALUE((state)->values_buf, offset)
+#define gsk_ngl_uniform_state_snapshot(state, program_info, callback, user_data) \
+ G_STMT_START { \
+ for (guint z = 0; z < program_info->n_sparse; z++) \
+ { \
+ guint location = program_info->sparse[z]; \
+ GskNglUniformInfoElement *info = &program_info->uniforms[location]; \
+ \
+ g_assert (location < GL_MAX_UNIFORM_LOCATIONS); \
+ g_assert (location < program_info->n_uniforms); \
+ \
+ if (info->info.format > 0) \
+ callback (&info->info, location, user_data); \
+ } \
+ } G_STMT_END
+
+static inline gpointer
+gsk_ngl_uniform_state_get_value (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ GskNglUniformFormat format,
+ guint array_count,
+ guint location,
+ guint stamp,
+ GskNglUniformInfoElement **infoptr)
+{
+ GskNglUniformInfoElement *info;
+
+ if (location == (guint)-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.
+ */
+ info = &program->uniforms[location];
+ if (stamp != 0 && stamp == info->stamp)
+ return NULL;
+
+ if G_LIKELY (format == info->info.format && array_count <= info->info.array_count)
+ {
+ *infoptr = info;
+ return GSK_NGL_UNIFORM_VALUE (state->values_buf, info->info.offset);
+ }
+
+ return gsk_ngl_uniform_state_init_value (state, program, format, array_count, location, infoptr);
+}
+
+static inline guint
+gsk_ngl_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_ngl_uniform_state_realloc (GskNglUniformState *state,
+ guint size,
+ guint *offset)
+{
+ guint padding = gsk_ngl_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_NGL_UNIFORM_VALUE (state->values_buf, *offset);
+}
+
+#define GSK_NGL_UNIFORM_STATE_REPLACE(info, u, type, count) \
+ G_STMT_START { \
+ if ((info)->info.initial && count == (info)->info.array_count) \
+ { \
+ u = GSK_NGL_UNIFORM_VALUE (state->values_buf, (info)->info.offset); \
+ } \
+ else \
+ { \
+ guint offset; \
+ u = gsk_ngl_uniform_state_realloc (state, sizeof(type) * MAX (1, count), &offset); \
+ g_assert (offset < (1 << GSK_NGL_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_ngl_uniform_info_changed (GskNglUniformInfoElement *info,
+ guint location,
+ guint stamp)
+{
+ info->stamp = stamp;
+ info->info.initial = FALSE;
+}
+
+static inline void
+gsk_ngl_uniform_state_set1f (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ float value0)
+{
+ Uniform1f *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != 0);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_1F, 1, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform1f , 1);
+ u->v0 = value0;
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set2f (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ float value0,
+ float value1)
+{
+ Uniform2f *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_2F, 1, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform2f, 1);
+ u->v0 = value0;
+ u->v1 = value1;
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set3f (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ float value0,
+ float value1,
+ float value2)
+{
+ Uniform3f *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_3F, 1, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform3f, 1);
+ u->v0 = value0;
+ u->v1 = value1;
+ u->v2 = value2;
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set4f (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ float value0,
+ float value1,
+ float value2,
+ float value3)
+{
+ Uniform4f *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_4F, 1, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform4f, 1);
+ u->v0 = value0;
+ u->v1 = value1;
+ u->v2 = value2;
+ u->v3 = value3;
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set1ui (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ guint value0)
+{
+ Uniform1ui *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_1UI, 1, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform1ui, 1);
+ u->v0 = value0;
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set1i (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ int value0)
+{
+ Uniform1i *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_1I, 1, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform1i, 1);
+ u->v0 = value0;
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set2i (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ int value0,
+ int value1)
+{
+ Uniform2i *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_2I, 1, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform2i, 1);
+ u->v0 = value0;
+ u->v1 = value1;
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set3i (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ int value0,
+ int value1,
+ int value2)
+{
+ Uniform3i *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_3I, 1, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform3i, 1);
+ u->v0 = value0;
+ u->v1 = value1;
+ u->v2 = value2;
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set4i (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ int value0,
+ int value1,
+ int value2,
+ int value3)
+{
+ Uniform4i *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_4I, 1, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform4i, 1);
+ u->v0 = value0;
+ u->v1 = value1;
+ u->v2 = value2;
+ u->v3 = value3;
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set_rounded_rect (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ const GskRoundedRect *rounded_rect)
+{
+ GskRoundedRect *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+ g_assert (rounded_rect != NULL);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_ROUNDED_RECT, 1, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, GskRoundedRect, 1);
+ memcpy (u, rounded_rect, sizeof *rounded_rect);
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set_matrix (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ const graphene_matrix_t *matrix)
+{
+ graphene_matrix_t *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+ g_assert (matrix != NULL);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_MATRIX, 1, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, graphene_matrix_t, 1);
+ memcpy (u, matrix, sizeof *matrix);
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+/**
+ * gsk_ngl_uniform_state_set_texture:
+ * @state: a #GskNglUniformState
+ * @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_ngl_uniform_state_set_texture (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ guint texture_slot)
+{
+ GskNglUniformInfoElement *info;
+ guint *u;
+
+ g_assert (texture_slot >= GL_TEXTURE0);
+ g_assert (texture_slot < GL_TEXTURE16);
+
+ texture_slot -= GL_TEXTURE0;
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_TEXTURE, 1, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, guint, 1);
+ *u = texture_slot;
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+/**
+ * gsk_ngl_uniform_state_set_color:
+ * @state: a #GskNglUniformState
+ * @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_ngl_uniform_state_set_color (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ const GdkRGBA *color)
+{
+ static const GdkRGBA transparent = {0};
+ GskNglUniformInfoElement *info;
+ GdkRGBA *u;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_COLOR, 1, location, stamp, &info)))
+ {
+ if (color == NULL)
+ color = &transparent;
+
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, GdkRGBA, 1);
+ memcpy (u, color, sizeof *color);
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set1fv (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ guint count,
+ const float *value)
+{
+ Uniform1f *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+ g_assert (count > 0);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_1FV, count, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform1f, count);
+ memcpy (u, value, sizeof (Uniform1f) * count);
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set2fv (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ guint count,
+ const float *value)
+{
+ Uniform2f *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+ g_assert (count > 0);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_2FV, count, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform2f, count);
+ memcpy (u, value, sizeof (Uniform2f) * count);
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set3fv (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ guint count,
+ const float *value)
+{
+ Uniform3f *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+ g_assert (count > 0);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_3FV, count, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform3f, count);
+ memcpy (u, value, sizeof (Uniform3f) * count);
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+static inline void
+gsk_ngl_uniform_state_set4fv (GskNglUniformState *state,
+ GskNglUniformProgram *program,
+ guint location,
+ guint stamp,
+ guint count,
+ const float *value)
+{
+ Uniform4f *u;
+ GskNglUniformInfoElement *info;
+
+ g_assert (state != NULL);
+ g_assert (program != NULL);
+ g_assert (count > 0);
+
+ if ((u = gsk_ngl_uniform_state_get_value (state, program, GSK_NGL_UNIFORM_FORMAT_4FV, count, location, stamp, &info)))
+ {
+ GSK_NGL_UNIFORM_STATE_REPLACE (info, u, Uniform4f, count);
+ memcpy (u, value, sizeof (Uniform4f) * count);
+ gsk_ngl_uniform_info_changed (info, location, stamp);
+ }
+}
+
+G_END_DECLS
+
+#endif /* GSK_NGL_UNIFORM_STATE_PRIVATE_H */
diff --git a/gsk/ngl/inlinearray.h b/gsk/ngl/inlinearray.h
new file mode 100644
index 0000000000..98a9255c68
--- /dev/null
+++ b/gsk/ngl/inlinearray.h
@@ -0,0 +1,77 @@
+#ifndef __INLINE_ARRAY_H__
+#define __INLINE_ARRAY_H__
+
+#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]; \
+ }
+
+#endif /* __INLINE_ARRAY_H__ */
diff --git a/gsk/ngl/ninesliceprivate.h b/gsk/ngl/ninesliceprivate.h
new file mode 100644
index 0000000000..fe787acc1d
--- /dev/null
+++ b/gsk/ngl/ninesliceprivate.h
@@ -0,0 +1,309 @@
+/* ninesliceprivate.h
+ *
+ * Copyright 2017 Timm Bäder <mail@baedert.org>
+ * Copyright 2021 Christian Hergert <chergert@redhat.com>
+ *
+ * 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 <http://www.gnu.org/licenses/>.
+ *
+ * SPDX-License-Identifier: LGPL-2.1-or-later
+ */
+
+#ifndef __NINE_SLICE_PRIVATE_H__
+#define __NINE_SLICE_PRIVATE_H__
+
+#include "gskngltexturepoolprivate.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 GskNglTextureNineSlice *slice)
+{
+ return slice->rect.width > 0 && slice->rect.height > 0;
+}
+
+static inline void
+nine_slice_rounded_rect (GskNglTextureNineSlice *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 (GskNglTextureNineSlice *slices,
+ int texture_width,
+ int texture_height)
+{
+ float fw = texture_width;
+ float fh = texture_height;
+
+ for (guint i = 0; i < 9; i++)
+ {
+ GskNglTextureNineSlice *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 (GskNglTextureNineSlice *slices,
+ int amount)
+{
+ if (amount == 0)
+ return;
+
+ /* top left */
+ slices[0].rect.x -= amount;
+ slices[0].rect.y -= amount;
+ if (amount > slices[0].rect.width)
+ slices[0].rect.width += amount * 2;
+ else
+ slices[0].rect.width += amount;
+
+ if (amount > slices[0].rect.height)
+ slices[0].rect.height += amount * 2;
+ else
+ slices[0].rect.height += amount;
+
+
+ /* Top center */
+ slices[1].rect.y -= amount;
+ if (amount > slices[1].rect.height)
+ slices[1].rect.height += amount * 2;
+ else
+ slices[1].rect.height += amount;
+
+ /* top right */
+ slices[2].rect.y -= amount;
+ if (amount > slices[2].rect.width)
+ {
+ slices[2].rect.x -= amount;
+ slices[2].rect.width += amount * 2;
+ }
+ else
+ {
+ slices[2].rect.width += amount;
+ }
+
+ if (amount > slices[2].rect.height)
+ slices[2].rect.height += amount * 2;
+ else
+ slices[2].rect.height += amount;
+
+
+
+ slices[3].rect.x -= amount;
+ if (amount > slices[3].rect.width)
+ slices[3].rect.width += amount * 2;
+ else
+ slices[3].rect.width += amount;
+
+ /* Leave center alone */
+
+ if (amount > slices[5].rect.width)
+ {
+ slices[5].rect.x -= amount;
+ slices[5].rect.width += amount * 2;
+ }
+ else
+ {
+ slices[5].rect.width += amount;
+ }
+
+
+ /* Bottom left */
+ slices[6].rect.x -= amount;
+ if (amount > slices[6].rect.width)
+ {
+ slices[6].rect.width += amount * 2;
+ }
+ else
+ {
+ slices[6].rect.width += amount;
+ }
+
+ if (amount > slices[6].rect.height)
+ {
+ slices[6].rect.y -= amount;
+ slices[6].rect.height += amount * 2;
+ }
+ else
+ {
+ slices[6].rect.height += amount;
+ }
+
+
+ /* Bottom center */
+ if (amount > slices[7].rect.height)
+ {
+ slices[7].rect.y -= amount;
+ slices[7].rect.height += amount * 2;
+ }
+ else
+ {
+ slices[7].rect.height += amount;
+ }
+
+ if (amount > slices[8].rect.width)
+ {
+ slices[8].rect.x -= amount;
+ slices[8].rect.width += amount * 2;
+ }
+ else
+ {
+ slices[8].rect.width += amount;
+ }
+
+ if (amount > slices[8].rect.height)
+ {
+ slices[8].rect.y -= amount;
+ slices[8].rect.height += amount * 2;
+ }
+ else
+ {
+ slices[8].rect.height += amount;
+ }
+
+#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
+
+#endif /* __NINE_SLICE_PRIVATE_H__ */
diff --git a/gtk/gtktestutils.c b/gtk/gtktestutils.c
index 690931a153..35dcff797c 100644
--- a/gtk/gtktestutils.c
+++ b/gtk/gtktestutils.c
@@ -41,6 +41,7 @@
#define GTK_COMPILATION
#include <gsk/gl/gskglrenderer.h>
+#include <gsk/ngl/gsknglrenderer.h>
#ifdef GDK_WINDOWING_BROADWAY
#include <gsk/broadway/gskbroadwayrenderer.h>
diff --git a/gtk/inspector/general.c b/gtk/inspector/general.c
index 4cbee80b8e..451300bb5a 100644
--- a/gtk/inspector/general.c
+++ b/gtk/inspector/general.c
@@ -147,6 +147,8 @@ init_version (GtkInspectorGeneral *gen)
renderer = "Vulkan";
else if (strcmp (G_OBJECT_TYPE_NAME (gsk_renderer), "GskGLRenderer") == 0)
renderer = "GL";
+ else if (strcmp (G_OBJECT_TYPE_NAME (gsk_renderer), "GskNgltRenderer") == 0)
+ renderer = "NGL";
else if (strcmp (G_OBJECT_TYPE_NAME (gsk_renderer), "GskCairoRenderer") == 0)
renderer = "Cairo";
else
diff --git a/testsuite/gsk/meson.build b/testsuite/gsk/meson.build
index 844e76c475..f78a71fc39 100644
--- a/testsuite/gsk/meson.build
+++ b/testsuite/gsk/meson.build
@@ -90,6 +90,7 @@ informative_render_tests = [
renderers = [
# name exclude term
[ 'opengl', '' ],
+ [ 'next', '' ],
[ 'broadway', '-3d' ],
[ 'cairo', '-3d' ],
]