#include "gsk/gskrendernodeparserprivate.h"
#include "gsk/gl/gskglrenderer.h"
+#include "gsk/ngl/gsknglrenderer.h"
#ifdef GDK_WINDOWING_BROADWAY
#include "gsk/broadway/gskbroadwayrenderer.h"
#endif
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 (),
#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)
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");
#include "gskcairorenderer.h"
#include "gskdebugprivate.h"
#include "gl/gskglrenderer.h"
+#include "ngl/gsknglrenderer.h"
#include "gskprofilerprivate.h"
#include "gskrendernodeprivate.h"
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;
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
'gskroundedrect.c',
'gsktransform.c',
'gl/gskglrenderer.c',
+ 'ngl/gsknglrenderer.c',
])
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([
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
--- /dev/null
+/* 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;
+ }
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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);
+ }
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+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))
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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--;
+ }
+ }
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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__ */
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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 */
--- /dev/null
+#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__ */
--- /dev/null
+/* 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__ */
#define GTK_COMPILATION
#include <gsk/gl/gskglrenderer.h>
+#include <gsk/ngl/gsknglrenderer.h>
#ifdef GDK_WINDOWING_BROADWAY
#include <gsk/broadway/gskbroadwayrenderer.h>
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
renderers = [
# name exclude term
[ 'opengl', '' ],
+ [ 'next', '' ],
[ 'broadway', '-3d' ],
[ 'cairo', '-3d' ],
]