[gtk/wip/chergert/glproto: 542/920] stub out prototype bits for different GL renderer design




commit ca5631d34e9a30d130393e3c7e3191f9fa5e8a19
Author: Christian Hergert <chergert redhat com>
Date:   Fri Dec 18 17:36:59 2020 -0800

    stub out prototype bits for different GL renderer design

 gsk/gskrenderer.c                      |    3 +
 gsk/meson.build                        |   17 +-
 gsk/next/gskglattachmentstate.c        |  153 ++++
 gsk/next/gskglattachmentstateprivate.h |   71 ++
 gsk/next/gskglbuffer.c                 |  171 +++++
 gsk/next/gskglbufferprivate.h          |   41 ++
 gsk/next/gskglcommandqueue.c           | 1212 ++++++++++++++++++++++++++++++++
 gsk/next/gskglcommandqueueprivate.h    |  148 ++++
 gsk/next/gskglcompiler.c               |  661 +++++++++++++++++
 gsk/next/gskglcompilerprivate.h        |   69 ++
 gsk/next/gskgldriver.c                 |  219 ++++++
 gsk/next/gskgldriverprivate.h          |   87 +++
 gsk/next/gskglglyphlibrary.c           |   50 ++
 gsk/next/gskglglyphlibraryprivate.h    |   53 ++
 gsk/next/gskgliconlibrary.c            |   50 ++
 gsk/next/gskgliconlibraryprivate.h     |   38 +
 gsk/next/gskglprogram.c                |  361 ++++++++++
 gsk/next/gskglprogramprivate.h         |   93 +++
 gsk/next/gskglprograms.defs            |   85 +++
 gsk/next/gskglrenderer.c               |  310 ++++++++
 gsk/next/gskglrenderer.h               |   47 ++
 gsk/next/gskglrenderjob.c              |  124 ++++
 gsk/next/gskglrenderjobprivate.h       |   36 +
 gsk/next/gskglshadowlibrary.c          |   50 ++
 gsk/next/gskglshadowlibraryprivate.h   |   42 ++
 gsk/next/gskgltextureatlas.c           |   46 ++
 gsk/next/gskgltextureatlasprivate.h    |   36 +
 gsk/next/gskgltexturelibrary.c         |  156 ++++
 gsk/next/gskgltexturelibraryprivate.h  |   61 ++
 gsk/next/gskgltypes.h                  |   53 ++
 gsk/next/gskgluniformstate.c           |  746 ++++++++++++++++++++
 gsk/next/gskgluniformstateprivate.h    |  189 +++++
 32 files changed, 5477 insertions(+), 1 deletion(-)
---
diff --git a/gsk/gskrenderer.c b/gsk/gskrenderer.c
index dbbfd28f35..c86dd98382 100644
--- a/gsk/gskrenderer.c
+++ b/gsk/gskrenderer.c
@@ -39,6 +39,7 @@
 #include "gskcairorenderer.h"
 #include "gskdebugprivate.h"
 #include "gl/gskglrenderer.h"
+#include "next/gskglrenderer.h"
 #include "gskprofilerprivate.h"
 #include "gskrendernodeprivate.h"
 
@@ -496,6 +497,8 @@ get_renderer_for_name (const char *renderer_name)
   else if (g_ascii_strcasecmp (renderer_name, "opengl") == 0
            || g_ascii_strcasecmp (renderer_name, "gl") == 0)
     return GSK_TYPE_GL_RENDERER;
+  else if (g_ascii_strcasecmp (renderer_name, "next") == 0)
+    return GSK_TYPE_NEXT_RENDERER;
 #ifdef GDK_RENDERING_VULKAN
   else if (g_ascii_strcasecmp (renderer_name, "vulkan") == 0)
     return GSK_TYPE_VULKAN_RENDERER;
diff --git a/gsk/meson.build b/gsk/meson.build
index 748f4738a1..8b63ccbb65 100644
--- a/gsk/meson.build
+++ b/gsk/meson.build
@@ -31,6 +31,7 @@ gsk_public_sources = files([
   'gskroundedrect.c',
   'gsktransform.c',
   'gl/gskglrenderer.c',
+  'next/gskglrenderer.c',
 ])
 
 gsk_private_sources = files([
@@ -48,6 +49,19 @@ gsk_private_sources = files([
   'gl/gskgliconcache.c',
   'gl/opbuffer.c',
   'gl/stb_rect_pack.c',
+  'next/gskglattachmentstate.c',
+  'next/gskglbuffer.c',
+  'next/gskglcommandqueue.c',
+  'next/gskglcompiler.c',
+  'next/gskgldriver.c',
+  'next/gskglglyphlibrary.c',
+  'next/gskgliconlibrary.c',
+  'next/gskglprogram.c',
+  'next/gskglrenderjob.c',
+  'next/gskglshadowlibrary.c',
+  'next/gskgltextureatlas.c',
+  'next/gskgltexturelibrary.c',
+  'next/gskgluniformstate.c',
 ])
 
 gsk_public_headers = files([
@@ -64,7 +78,8 @@ gsk_public_headers = files([
 install_headers(gsk_public_headers, 'gsk.h', subdir: 'gtk-4.0/gsk')
 
 gsk_public_gl_headers = files([
-  'gl/gskglrenderer.h'
+  'gl/gskglrenderer.h',
+  'next/gskglrenderer.h',
 ])
 install_headers(gsk_public_gl_headers, subdir: 'gtk-4.0/gsk/gl')
 gsk_public_headers += gsk_public_gl_headers
diff --git a/gsk/next/gskglattachmentstate.c b/gsk/next/gskglattachmentstate.c
new file mode 100644
index 0000000000..404bf3c11c
--- /dev/null
+++ b/gsk/next/gskglattachmentstate.c
@@ -0,0 +1,153 @@
+/* gskglattachmentstate.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 "gskglattachmentstateprivate.h"
+
+GskGLAttachmentState *
+gsk_gl_attachment_state_new (void)
+{
+  GskGLAttachmentState *self;
+
+  self = g_new0 (GskGLAttachmentState, 1);
+
+  self->fbo.changed = FALSE;
+  self->fbo.id = 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;
+}
+
+void
+gsk_gl_attachment_state_free (GskGLAttachmentState *self)
+{
+  g_free (self);
+}
+
+void
+gsk_gl_attachment_state_bind_texture (GskGLAttachmentState *self,
+                                      GLenum                target,
+                                      GLenum                texture,
+                                      guint                 id)
+{
+  GskGLBindTexture *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->changed = TRUE;
+      attach->initial = FALSE;
+      self->has_texture_change = TRUE;
+    }
+}
+
+void
+gsk_gl_attachment_state_bind_framebuffer (GskGLAttachmentState *self,
+                                          guint                 id)
+{
+  g_assert (self != NULL);
+
+  if (self->fbo.id != id)
+    {
+      self->fbo.id = id;
+      self->fbo.changed = TRUE;
+    }
+}
+
+/**
+ * gsk_gl_attachment_state_save:
+ * @self: a #GskGLAttachmentState
+ *
+ * Creates a copy of @self that represents the current attachments
+ * as known to @self.
+ *
+ * This can be used to restore state later, such as after running
+ * various GL commands that are external to the GL renderer.
+ *
+ * This must be freed by calling either gsk_gl_attachment_state_free()
+ * or gsk_gl_attachment_state_restore().
+ *
+ * Returns: (transfer full): a new #GskGLAttachmentState or %NULL
+ */
+GskGLAttachmentState *
+gsk_gl_attachment_state_save (GskGLAttachmentState *self)
+{
+  GskGLAttachmentState *ret;
+
+  if (self == NULL)
+    return NULL;
+
+  ret = g_slice_dup (GskGLAttachmentState, self);
+  ret->fbo.changed = FALSE;
+  for (guint i = 0; i < G_N_ELEMENTS (ret->textures); i++)
+    ret->textures[i].changed = FALSE;
+
+  return g_steal_pointer (&ret);
+}
+
+/**
+ * gsk_gl_attachment_state_restore:
+ * @self: (transfer full): the #GskGLAttachmentState
+ *
+ * Restores the attachment state and frees @self.
+ */
+void
+gsk_gl_attachment_state_restore (GskGLAttachmentState *self)
+{
+  if (self == NULL)
+    return;
+
+  glBindFramebuffer (GL_FRAMEBUFFER, self->fbo.id);
+
+  for (guint i = 0; i < G_N_ELEMENTS (self->textures); i++)
+    {
+      if (!self->textures[i].initial)
+        {
+          glActiveTexture (GL_TEXTURE0 + i);
+          glBindTexture (self->textures[i].target,
+                         self->textures[i].id);
+        }
+    }
+
+  g_slice_free (GskGLAttachmentState, self);
+}
diff --git a/gsk/next/gskglattachmentstateprivate.h b/gsk/next/gskglattachmentstateprivate.h
new file mode 100644
index 0000000000..fe2bf328d4
--- /dev/null
+++ b/gsk/next/gskglattachmentstateprivate.h
@@ -0,0 +1,71 @@
+/* gskglattachmentstateprivate.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_GL_ATTACHMENT_STATE_PRIVATE_H__
+#define __GSK_GL_ATTACHMENT_STATE_PRIVATE_H__
+
+#include "gskgltypes.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GskGLAttachmentState GskGLAttachmentState;
+typedef struct _GskGLBindFramebuffer GskGLBindFramebuffer;
+typedef struct _GskGLBindTexture     GskGLBindTexture;
+
+struct _GskGLBindTexture
+{
+  guint changed : 1;
+  guint initial : 1;
+  GLenum target : 30;
+  GLenum texture;
+  guint id;
+};
+
+G_STATIC_ASSERT (sizeof (GskGLBindTexture) == 12);
+
+struct _GskGLBindFramebuffer
+{
+  guint changed : 1;
+  guint id : 31;
+};
+
+G_STATIC_ASSERT (sizeof (GskGLBindFramebuffer) == 4);
+
+struct _GskGLAttachmentState
+{
+  GskGLBindFramebuffer fbo;
+  GskGLBindTexture textures[8];
+  guint has_texture_change : 1;
+};
+
+GskGLAttachmentState *gsk_gl_attachment_state_new              (void);
+GskGLAttachmentState *gsk_gl_attachment_state_save             (GskGLAttachmentState *self);
+void                  gsk_gl_attachment_state_restore          (GskGLAttachmentState *self);
+void                  gsk_gl_attachment_state_free             (GskGLAttachmentState *self);
+void                  gsk_gl_attachment_state_bind_texture     (GskGLAttachmentState *self,
+                                                                GLenum                target,
+                                                                GLenum                texture,
+                                                                guint                 id);
+void                  gsk_gl_attachment_state_bind_framebuffer (GskGLAttachmentState *self,
+                                                                guint                 id);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_ATTACHMENT_STATE_PRIVATE_H__ */
diff --git a/gsk/next/gskglbuffer.c b/gsk/next/gskglbuffer.c
new file mode 100644
index 0000000000..913f5ebc8e
--- /dev/null
+++ b/gsk/next/gskglbuffer.c
@@ -0,0 +1,171 @@
+/* gskglbufferprivate.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 <epoxy/gl.h>
+
+#include "gskglbufferprivate.h"
+
+#define N_BUFFERS 2
+#define RESERVED_SIZE 1024
+
+typedef struct
+{
+  GLuint   id;
+  guint    size_on_gpu;
+} GskGLBufferShadow;
+
+struct _GskGLBuffer
+{
+  GArray            *buffer;
+  GskGLBufferShadow  shadows[N_BUFFERS];
+  GLenum             target;
+  guint              current;
+};
+
+static void
+gsk_gl_buffer_shadow_init (GskGLBufferShadow *shadow,
+                           GLenum             target,
+                           guint              element_size,
+                           guint              reserved_size)
+{
+  GLuint id;
+
+  glGenBuffers (1, &id);
+  glBindBuffer (target, id);
+  glBufferData (target, element_size * reserved_size, NULL, GL_STATIC_DRAW);
+  glBindBuffer (target, 0);
+
+  shadow->id = id;
+  shadow->size_on_gpu = element_size * reserved_size;
+}
+
+static void
+gsk_gl_buffer_shadow_destroy (GskGLBufferShadow *shadow)
+{
+  shadow->size_on_gpu = 0;
+
+  if (shadow->id > 0)
+    {
+      glDeleteBuffers (1, &shadow->id);
+      shadow->id = 0;
+    }
+}
+
+static void
+gsk_gl_buffer_shadow_submit (GskGLBufferShadow *shadow,
+                             GLenum             target,
+                             GArray            *buffer)
+{
+  guint to_upload = buffer->len * g_array_get_element_size (buffer);
+
+  /* If what we generated is larger than our size on the GPU, then we need
+   * to release our previous buffer and create a new one of the appropriate
+   * size. We add some padding to make it more likely the next frame with this
+   * buffer does not need to do the same thing again. We also try to keep
+   * things aligned to the size of a whole (4096 byte) page.
+   */
+  if G_UNLIKELY (to_upload > shadow->size_on_gpu)
+    {
+      guint size_on_gpu = (to_upload & ~0xFFF) + (4 * 4096L);
+
+      glBindBuffer (target, 0);
+      glDeleteBuffers (1, &shadow->id);
+      glGenBuffers (1, &shadow->id);
+      glBindBuffer (target, shadow->id);
+      glBufferData (target, size_on_gpu, NULL, GL_STATIC_DRAW);
+      glBufferSubData (target, 0, to_upload, buffer->data);
+      shadow->size_on_gpu = size_on_gpu;
+    }
+  else
+    {
+      glBindBuffer (target, shadow->id);
+      glBufferSubData (target, 0, to_upload, buffer->data);
+    }
+}
+
+/**
+ * gsk_gl_buffer_new:
+ * @target: the target buffer such as %GL_ARRAY_BUFFER or %GL_UNIFORM_BUFFER
+ * @element_size: the size of elements within the buffer
+ *
+ * Creates a new #GskGLBuffer which can be used to deliver data to shaders
+ * within a GLSL program. You can use this to store vertices such as with
+ * %GL_ARRAY_BUFFER or uniform data with %GL_UNIFORM_BUFFER.
+ *
+ * Note that only writing to this buffer is allowed (see %GL_WRITE_ONLY for
+ * more details).
+ *
+ * The buffer will be bound to target upon returning from this function.
+ */
+GskGLBuffer *
+gsk_gl_buffer_new (GLenum target,
+                   guint  element_size)
+{
+  GskGLBuffer *buffer;
+  GLuint id = 0;
+
+  glGenBuffers (1, &id);
+
+  buffer = g_new0 (GskGLBuffer, 1);
+  buffer->buffer = g_array_sized_new (FALSE, FALSE, element_size, RESERVED_SIZE);
+  buffer->target = target;
+  buffer->current = 0;
+
+  for (guint i = 0; i < N_BUFFERS; i++)
+    gsk_gl_buffer_shadow_init (&buffer->shadows[i],
+                               target,
+                               element_size,
+                               RESERVED_SIZE);
+
+  return g_steal_pointer (&buffer);
+}
+
+void
+gsk_gl_buffer_submit (GskGLBuffer *buffer)
+{
+  gsk_gl_buffer_shadow_submit (&buffer->shadows[buffer->current],
+                               buffer->target,
+                               buffer->buffer);
+  buffer->current = (buffer->current + 1) % N_BUFFERS;
+  buffer->buffer->len = 0;
+}
+
+void
+gsk_gl_buffer_free (GskGLBuffer *buffer)
+{
+  buffer->target = 0;
+  buffer->current = 0;
+  for (guint i = 0; i < N_BUFFERS; i++)
+    gsk_gl_buffer_shadow_destroy (&buffer->shadows[i]);
+  g_free (buffer);
+}
+
+gpointer
+gsk_gl_buffer_advance (GskGLBuffer *buffer,
+                       guint        count,
+                       guint       *offset)
+{
+
+  *offset = buffer->buffer->len;
+  g_array_set_size (buffer->buffer, buffer->buffer->len + count);
+  return (guint8 *)buffer->buffer->data + (*offset * g_array_get_element_size (buffer->buffer));
+}
diff --git a/gsk/next/gskglbufferprivate.h b/gsk/next/gskglbufferprivate.h
new file mode 100644
index 0000000000..2d88bb4995
--- /dev/null
+++ b/gsk/next/gskglbufferprivate.h
@@ -0,0 +1,41 @@
+/* gskglbufferprivate.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_GL_BUFFER_PRIVATE_H__
+#define __GSK_GL_BUFFER_PRIVATE_H__
+
+#include <glib.h>
+#include <epoxy/gl.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GskGLBuffer GskGLBuffer;
+
+GskGLBuffer *gsk_gl_buffer_new     (GLenum       target,
+                                    guint        element_size);
+void         gsk_gl_buffer_free    (GskGLBuffer *buffer);
+void         gsk_gl_buffer_submit  (GskGLBuffer *buffer);
+gpointer     gsk_gl_buffer_advance (GskGLBuffer *buffer,
+                                    guint        count,
+                                    guint       *offset);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_BUFFER_PRIVATE_H__ */
diff --git a/gsk/next/gskglcommandqueue.c b/gsk/next/gskglcommandqueue.c
new file mode 100644
index 0000000000..fbef0fcee0
--- /dev/null
+++ b/gsk/next/gskglcommandqueue.c
@@ -0,0 +1,1212 @@
+/* gskglcommandqueue.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 <epoxy/gl.h>
+
+#include "gskglattachmentstateprivate.h"
+#include "gskglbufferprivate.h"
+#include "gskglcommandqueueprivate.h"
+#include "gskgluniformstateprivate.h"
+
+/* The MAX_MERGE_DISTANCE is used to reduce how far back we'll look for
+ * programs to merge a batch with. This number is specific to batches using
+ * the same program as a secondary index (See program_link field) is used
+ * for tracking those.
+ */
+#define MAX_MERGE_DISTANCE 5
+
+struct _GskGLCommandQueue
+{
+  GObject parent_instance;
+
+  GdkGLContext *context;
+
+  /* Queue containing a linked list of all the GskGLCommandBatch that have
+   * been allocated so that we can reuse them on the next frame without
+   * allocating additional memory. Using stable pointers instead of offsets
+   * into an array makes this a bit easier to manage from a life-cycle
+   * standpoint as well as reordering using the links instead of memmove()s
+   * in an array.
+   */
+  GQueue unused_batches;
+
+  /* As we build the real command queue, we place the batches into this
+   * queue by pushing onto the tail. Executing commands will result in
+   * walking this queue from head to tail.
+   */
+  GQueue all_batches;
+
+  /* When merging batches we want to skip all items between the merge
+   * candidate and the previous within it's program. To do this we keep an
+   * index of commands by program to avoid iteration overhead. This array
+   * contains a GQueue for each program which will point into the statically
+   * allocated @program_link in GskGLCommandBatch.
+   *
+   * After we find a merge candidate, we check for clipping and other
+   * changes which might make them unacceptable to merge.
+   */
+  GArray *program_batches;
+
+  /* The GskGLAttachmentState contains information about our FBO and texture
+   * attachments as we process incoming operations. We snapshot them into
+   * various batches so that we can compare differences between merge
+   * candidates.
+   */
+  GskGLAttachmentState *attachments;
+
+  /* The uniform state across all programs. We snapshot this into batches so
+   * that we can compare uniform state between batches to give us more
+   * chances at merging draw commands.
+   */
+  GskGLUniformState *uniforms;
+
+  /* Our VBO containing all the vertices to upload to the GPU before calling
+   * glDrawArrays() to draw. Each drawing operation contains 6 vec4 with the
+   * positions necessary to draw with glDrawArrays().
+   */
+  GskGLBuffer *vertices;
+
+  /* Sometimes we want to save attachment state so that operations we do
+   * cannot affect anything that is known to the command queue. We call
+   * gsk_gl_command_queue_save()/restore() which stashes attachment state
+   * into this pointer array.
+   */
+  GPtrArray *saved_state;
+
+  /* Sometimes it is handy to keep a number of textures or framebuffers
+   * around until the frame has finished drawing. That way some objects can
+   * be used immediately even though they won't have any rendering until the
+   * frame has finished.
+   *
+   * When end_frame is called, we remove these resources.
+   */
+  GArray *autorelease_framebuffers;
+  GArray *autorelease_textures;
+
+  int max_texture_size;
+};
+
+typedef struct _GskGLCommandDraw
+{
+  guint vao_offset;
+  guint vao_count;
+} GskGLCommandDraw;
+
+G_STATIC_ASSERT (sizeof (GskGLCommandDraw) == 8);
+
+typedef struct _GskGLCommandUniform
+{
+  guint offset;
+  guint array_count : 16;
+  guint location : 7;
+  guint format : 5;
+  guint flags : 4;
+} GskGLCommandUniform;
+
+G_STATIC_ASSERT (sizeof (GskGLCommandUniform) == 8);
+
+typedef struct _GskGLCommandBatch
+{
+  /* An index into GskGLCommandQueue.all_batches */
+  GList all_link;
+
+  /* An index into GskGLCommandBatch.program_batches */
+  GList program_link;
+
+  union {
+    GskGLBindTexture  bind;
+    GskGLBindTexture *binds;
+  };
+
+  union {
+    GskGLCommandDraw  draw;
+    GskGLCommandDraw *draws;
+  };
+
+  union {
+    GskGLCommandUniform  uniform;
+    GskGLCommandUniform *uniforms;
+  };
+
+  guint program_changed : 1;
+  guint program : 15;
+  guint n_draws : 16;
+  guint n_binds : 16;
+  guint n_uniforms : 16;
+
+  /* The framebuffer to use and if it has changed */
+  GskGLBindFramebuffer framebuffer;
+} GskGLCommandBatch;
+
+G_DEFINE_TYPE (GskGLCommandQueue, gsk_gl_command_queue, G_TYPE_OBJECT)
+
+static inline gboolean
+ispow2 (guint n)
+{
+  return !(n & (n-1));
+}
+
+static GskGLCommandBatch *
+gsk_gl_command_queue_alloc_batch (GskGLCommandQueue *self)
+{
+  GskGLCommandBatch *batch;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  if G_LIKELY (self->unused_batches.length > 0)
+    return g_queue_pop_head_link (&self->unused_batches)->data;
+
+  batch = g_slice_new0 (GskGLCommandBatch);
+  batch->all_link.data = batch;
+  batch->program_link.data = batch;
+
+  return batch;
+}
+
+static void
+gsk_gl_command_queue_release_batch (GskGLCommandQueue *self,
+                                    GskGLCommandBatch *batch)
+{
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (batch != NULL);
+
+  g_queue_unlink (&self->all_batches, &batch->all_link);
+
+  g_assert (batch->program ||
+            (batch->program_link.prev == NULL &&
+             batch->program_link.next == NULL));
+
+  if (batch->program_link.prev || batch->program_link.next)
+    {
+      GQueue *queue = &g_array_index (self->program_batches, GQueue, batch->program);
+      g_queue_unlink (queue, &batch->program_link);
+    }
+
+  if (batch->n_draws > 1)
+    g_free (batch->draws);
+
+  if (batch->n_binds > 1)
+    g_free (batch->binds);
+
+  batch->n_binds = 0;
+  batch->n_draws = 0;
+  batch->binds = NULL;
+  batch->draws = NULL;
+  batch->framebuffer.id = 0;
+  batch->framebuffer.changed = FALSE;
+  batch->program = 0;
+  batch->program_changed = FALSE;
+
+  g_assert (batch->program_link.prev == NULL);
+  g_assert (batch->program_link.next == NULL);
+  g_assert (batch->program_link.data == batch);
+  g_assert (batch->all_link.prev == NULL);
+  g_assert (batch->all_link.next == NULL);
+  g_assert (batch->all_link.data == batch);
+
+  g_queue_push_head_link (&self->unused_batches, &batch->all_link);
+}
+
+static void
+gsk_gl_command_batch_apply_uniform (GskGLCommandBatch         *batch,
+                                    GskGLUniformState         *state,
+                                    const GskGLCommandUniform *uniform)
+{
+  const union {
+    graphene_matrix_t matrix[0];
+    GskRoundedRect rounded_rect[0];
+    float fval[0];
+    int ival[0];
+  } *data;
+
+  g_assert (batch != NULL);
+  g_assert (uniform != NULL);
+
+  data = gsk_gl_uniform_state_get_uniform_data (state, uniform->offset);
+
+  switch (uniform->format)
+    {
+    case GSK_GL_UNIFORM_FORMAT_1F:
+      glUniform1f (uniform->location, data->fval[0]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_2F:
+      glUniform2f (uniform->location, data->fval[0], data->fval[1]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_3F:
+      glUniform3f (uniform->location, data->fval[0], data->fval[1], data->fval[2]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_4F:
+      glUniform4f (uniform->location, data->fval[0], data->fval[1], data->fval[2], data->fval[3]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_1FV:
+      glUniform1fv (uniform->location, uniform->array_count, data->fval);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_2FV:
+      glUniform2fv (uniform->location, uniform->array_count, data->fval);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_3FV:
+      glUniform3fv (uniform->location, uniform->array_count, data->fval);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_4FV:
+      glUniform4fv (uniform->location, uniform->array_count, data->fval);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_1I:
+    case GSK_GL_UNIFORM_FORMAT_TEXTURE:
+      glUniform1i (uniform->location, data->ival[0]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_2I:
+      glUniform2i (uniform->location, data->ival[0], data->ival[1]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_3I:
+      glUniform3i (uniform->location, data->ival[0], data->ival[1], data->ival[2]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_4I:
+      glUniform4i (uniform->location, data->ival[0], data->ival[1], data->ival[2], data->ival[3]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_MATRIX: {
+      float mat[16];
+      graphene_matrix_to_float (&data->matrix[0], mat);
+      glUniformMatrix4fv (uniform->location, 1, GL_FALSE, mat);
+      break;
+    }
+
+    case GSK_GL_UNIFORM_FORMAT_COLOR:
+      glUniform4fv (uniform->location, 1, &data->fval[0]);
+      break;
+
+    case GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT:
+      if (uniform->flags & GSK_GL_UNIFORM_FLAGS_SEND_CORNERS)
+        glUniform4fv (uniform->location, 3, (const float *)&data->rounded_rect[0]);
+      else
+        glUniform4fv (uniform->location, 1, (const float *)&data->rounded_rect[0]);
+      break;
+
+    default:
+      break;
+    }
+}
+
+static void
+gsk_gl_command_batch_draw (GskGLCommandBatch *batch,
+                           guint              vao_offset,
+                           guint              vao_count)
+{
+  GskGLCommandDraw *last;
+
+  g_assert (batch != NULL);
+
+  if (batch->n_draws == 0)
+    {
+      batch->draw.vao_offset = vao_offset;
+      batch->draw.vao_count = vao_count;
+      batch->n_draws = 1;
+      return;
+    }
+
+  last = batch->n_draws == 1 ? &batch->draw : &batch->draws[batch->n_draws-1];
+
+  if (last->vao_offset + last->vao_count == vao_offset)
+    {
+      batch->draw.vao_count += vao_count;
+    }
+  else if (batch->n_draws == 1)
+    {
+      GskGLCommandDraw *draws = g_new (GskGLCommandDraw, 16);
+
+      draws[0].vao_offset = batch->draw.vao_offset;
+      draws[0].vao_count = batch->draw.vao_count;
+      draws[1].vao_offset = vao_offset;
+      draws[1].vao_count = vao_count;
+
+      batch->draws = draws;
+      batch->n_draws = 2;
+    }
+  else
+    {
+      if G_UNLIKELY (batch->n_draws >= 16 && ispow2 (batch->n_draws))
+        batch->draws = g_realloc (batch->draws, sizeof (GskGLCommandDraw) * batch->n_draws * 2);
+
+      batch->draws[batch->n_draws].vao_count = vao_count;
+      batch->draws[batch->n_draws].vao_offset = vao_offset;
+      batch->n_draws++;
+    }
+}
+
+static void
+gsk_gl_command_batch_execute (GskGLCommandBatch *batch,
+                              GskGLUniformState *uniforms)
+{
+  g_assert (batch != NULL);
+  g_assert (batch->n_draws > 0);
+
+  if (batch->framebuffer.changed)
+    glBindFramebuffer (GL_FRAMEBUFFER, batch->framebuffer.id);
+
+  if (batch->program_changed)
+    glUseProgram (batch->program);
+
+  if (batch->n_binds == 1)
+    {
+      g_assert (batch->bind.changed);
+
+      glActiveTexture (batch->bind.texture);
+      glBindTexture (batch->bind.target, batch->bind.id);
+    }
+  else if (batch->n_binds > 1)
+    {
+      for (guint i = 0; i < batch->n_binds; i++)
+        {
+          const GskGLBindTexture *bind = &batch->binds[i];
+
+          g_assert (bind->changed);
+
+          glActiveTexture (bind->texture);
+          glBindTexture (bind->target, bind->id);
+        }
+    }
+
+  if (batch->n_uniforms == 1)
+    {
+      gsk_gl_command_batch_apply_uniform (batch, uniforms, &batch->uniform);
+    }
+  else if (batch->n_uniforms > 0)
+    {
+      for (guint i = 0; i < batch->n_uniforms; i++)
+        {
+          const GskGLCommandUniform *uniform = &batch->uniforms[i];
+          gsk_gl_command_batch_apply_uniform (batch, uniforms, uniform);
+        }
+    }
+
+  if (batch->n_draws == 1)
+    {
+      glDrawArrays (GL_TRIANGLES, batch->draw.vao_offset, batch->draw.vao_count);
+    }
+  else if (batch->n_draws > 1)
+    {
+      for (guint i = 0; i < batch->n_draws; i++)
+        {
+          const GskGLCommandDraw *draw = &batch->draws[i];
+
+          g_assert (draw->vao_count > 0);
+
+          glDrawArrays (GL_TRIANGLES, draw->vao_offset, draw->vao_count);
+        }
+    }
+}
+
+static void
+gsk_gl_command_batch_uniform_cb (const GskGLUniformInfo *info,
+                                 guint                   location,
+                                 gpointer                user_data)
+{
+  GskGLCommandBatch *batch = user_data;
+  GskGLCommandUniform *u;
+
+  g_assert (batch != NULL);
+  g_assert (info != NULL);
+
+  if (batch->n_uniforms == 0)
+    {
+      u = &batch->uniform;
+      batch->n_uniforms = 1;
+    }
+  else if (batch->n_uniforms == 1)
+    {
+      u = g_new (GskGLCommandUniform, 2);
+      u[0] = batch->uniform;
+      batch->uniforms = u;
+      batch->n_uniforms = 2;
+      u = &u[1];
+    }
+  else
+    {
+      u = g_realloc_n (batch->uniforms, batch->n_uniforms+1, sizeof (GskGLCommandUniform));
+      batch->uniforms = u;
+      u = &u[batch->n_uniforms];
+      batch->n_uniforms++;
+    }
+
+  u->format = info->format;
+  u->flags = info->flags;
+  u->array_count = info->array_count;
+  u->location = location;
+  u->offset = info->offset;
+}
+
+static gboolean
+gsk_gl_command_batch_mergeable (GskGLCommandBatch *batch,
+                                GskGLCommandBatch *other)
+{
+  g_assert (batch != NULL);
+  g_assert (other != NULL);
+  g_assert (batch != other);
+
+  if (batch->program != other->program)
+    return FALSE;
+
+  return FALSE;
+}
+
+static void
+gsk_gl_command_queue_try_merge (GskGLCommandQueue *self,
+                                GskGLCommandBatch *batch)
+{
+  guint count = 0;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (batch != NULL);
+  g_assert (batch->program != 0);
+
+  /* We probably only want to look at the past couple by program to
+   * avoid pathological situations. In most cases, they will naturally
+   * come within the last few submissions.
+   */
+
+  for (const GList *iter = batch->program_link.prev;
+       iter != NULL && count < MAX_MERGE_DISTANCE;
+       iter = iter->prev, count++)
+    {
+      GskGLCommandBatch *predecessor = iter->data;
+
+      if (gsk_gl_command_batch_mergeable (predecessor, batch))
+        {
+          /* We need to check all the intermediates for overdrawing. */
+        }
+    }
+}
+
+static inline GskGLCommandBatch *
+gsk_gl_command_queue_get_batch (GskGLCommandQueue *self)
+{
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  return self->all_batches.tail->data;
+}
+
+static GskGLCommandBatch *
+gsk_gl_command_queue_advance (GskGLCommandQueue *self,
+                              guint              new_program)
+{
+  GskGLCommandBatch *last_batch = NULL;
+  GskGLCommandBatch *batch;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  if (self->all_batches.length > 0)
+    {
+      last_batch = self->all_batches.tail->data;
+
+      gsk_gl_uniform_state_snapshot (self->uniforms,
+                                     last_batch->program,
+                                     gsk_gl_command_batch_uniform_cb,
+                                     last_batch);
+    }
+
+  batch = gsk_gl_command_queue_alloc_batch (self);
+
+  if G_LIKELY (last_batch != NULL)
+    {
+      batch->program = new_program ? new_program : last_batch->program;
+      batch->program_changed = batch->program != last_batch->program;
+      batch->framebuffer = last_batch->framebuffer;
+      batch->framebuffer.changed = FALSE;
+    }
+  else
+    {
+      batch->program = new_program;
+      batch->program_changed = TRUE;
+      batch->framebuffer.id = 0;
+      batch->framebuffer.changed = FALSE;
+    }
+
+  g_queue_push_tail_link (&self->all_batches, &batch->all_link);
+
+  if (batch->program)
+    {
+      GQueue *q;
+
+      if (self->program_batches->len <= batch->program)
+        g_array_set_size (self->program_batches, batch->program + 1);
+
+      q = &g_array_index (self->program_batches, GQueue, batch->program);
+      g_queue_push_tail_link (q, &batch->program_link);
+    }
+
+  if (last_batch != NULL)
+    gsk_gl_command_queue_try_merge (self, last_batch);
+
+  return g_steal_pointer (&batch);
+}
+
+static void
+gsk_gl_command_queue_dispose (GObject *object)
+{
+  GskGLCommandQueue *self = (GskGLCommandQueue *)object;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  while (self->all_batches.length > 0)
+    {
+      GskGLCommandBatch *batch = self->all_batches.head->data;
+
+      gsk_gl_command_queue_release_batch (self, batch);
+    }
+
+  while (self->unused_batches.length > 0)
+    {
+      GskGLCommandBatch *batch = self->unused_batches.head->data;
+
+      g_queue_unlink (&self->unused_batches, self->unused_batches.head->data);
+      g_slice_free (GskGLCommandBatch, batch);
+    }
+
+#ifndef G_DISABLE_DEBUG
+  g_assert (self->unused_batches.length == 0);
+  g_assert (self->all_batches.length == 0);
+
+  for (guint i = 0; i < self->program_batches->len; i++)
+    {
+      GQueue *q = &g_array_index (self->program_batches, GQueue, i);
+      g_assert (q->length == 0);
+    }
+#endif
+
+  g_clear_pointer (&self->saved_state, g_ptr_array_unref);
+  g_clear_pointer (&self->attachments, gsk_gl_attachment_state_free);
+  g_clear_object (&self->context);
+  g_clear_pointer (&self->uniforms, gsk_gl_uniform_state_free);
+  g_clear_pointer (&self->vertices, gsk_gl_buffer_free);
+  g_clear_pointer (&self->program_batches, g_array_unref);
+  g_clear_pointer (&self->autorelease_framebuffers, g_array_unref);
+  g_clear_pointer (&self->autorelease_textures, g_array_unref);
+
+  G_OBJECT_CLASS (gsk_gl_command_queue_parent_class)->dispose (object);
+}
+
+static void
+gsk_gl_command_queue_class_init (GskGLCommandQueueClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gsk_gl_command_queue_dispose;
+}
+
+static void
+gsk_gl_command_queue_init (GskGLCommandQueue *self)
+{
+  self->max_texture_size = -1;
+
+  self->attachments = gsk_gl_attachment_state_new ();
+  self->vertices = gsk_gl_buffer_new (GL_ARRAY_BUFFER, sizeof (GskGLDrawVertex));
+  self->uniforms = gsk_gl_uniform_state_new ();
+  self->program_batches = g_array_new (FALSE, TRUE, sizeof (GQueue));
+  self->saved_state = g_ptr_array_new_with_free_func ((GDestroyNotify)gsk_gl_attachment_state_free);
+  self->autorelease_textures = g_array_new (FALSE, FALSE, sizeof (GLuint));
+  self->autorelease_framebuffers = g_array_new (FALSE, FALSE, sizeof (GLuint));
+
+  gsk_gl_command_queue_advance (self, 0);
+}
+
+GskGLCommandQueue *
+gsk_gl_command_queue_new (GdkGLContext *context)
+{
+  GskGLCommandQueue *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);
+
+  if (self->max_texture_size < 0)
+    {
+      gdk_gl_context_make_current (context);
+      glGetIntegerv (GL_MAX_TEXTURE_SIZE, (GLint *)&self->max_texture_size);
+      GSK_NOTE (OPENGL, g_message ("GL max texture size: %d", self->max_texture_size));
+    }
+
+  return g_steal_pointer (&self);
+}
+
+GdkGLContext *
+gsk_gl_command_queue_get_context (GskGLCommandQueue *self)
+{
+  g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self), NULL);
+
+  return self->context;
+}
+
+void
+gsk_gl_command_queue_make_current (GskGLCommandQueue *self)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_return_if_fail (GDK_IS_GL_CONTEXT (self->context));
+
+  gdk_gl_context_make_current (self->context);
+}
+
+void
+gsk_gl_command_queue_use_program (GskGLCommandQueue *self,
+                                  guint              program)
+{
+  GskGLCommandBatch *batch;
+
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  batch = gsk_gl_command_queue_get_batch (self);
+
+  if (batch->program == program || program == 0)
+    return;
+
+  if (batch->n_draws == 0)
+    {
+      batch->program = program;
+      return;
+    }
+
+  batch = gsk_gl_command_queue_advance (self, program);
+  batch->program = program;
+  batch->program_changed = TRUE;
+}
+
+void
+gsk_gl_command_queue_delete_program (GskGLCommandQueue *self,
+                                     guint              program)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  glDeleteProgram (program);
+  gsk_gl_uniform_state_clear_program (self->uniforms, program);
+}
+
+GskGLDrawVertex *
+gsk_gl_command_queue_draw (GskGLCommandQueue    *self,
+                           const GskGLDrawVertex  vertices[6])
+{
+  GskGLCommandBatch *batch;
+  GskGLDrawVertex *dest;
+  guint offset;
+
+  g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self), NULL);
+
+  batch = gsk_gl_command_queue_get_batch (self);
+  dest = gsk_gl_buffer_advance (self->vertices, 6, &offset);
+
+  gsk_gl_command_batch_draw (batch, offset, 6);
+
+  if (vertices != NULL)
+    {
+      memcpy (dest, vertices, sizeof (GskGLDrawVertex) * 6);
+      return NULL;
+    }
+
+  return dest;
+}
+
+void
+gsk_gl_command_queue_set_uniform1i (GskGLCommandQueue *self,
+                                    guint              program,
+                                    guint              location,
+                                    int                value0)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_uniform_state_set1i (self->uniforms, program, location, value0);
+}
+
+void
+gsk_gl_command_queue_set_uniform2i (GskGLCommandQueue *self,
+                                    guint              program,
+                                    guint              location,
+                                    int                value0,
+                                    int                value1)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_uniform_state_set2i (self->uniforms, program, location, value0, value1);
+}
+
+void
+gsk_gl_command_queue_set_uniform3i (GskGLCommandQueue *self,
+                                    guint              program,
+                                    guint              location,
+                                    int                value0,
+                                    int                value1,
+                                    int                value2)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_uniform_state_set3i (self->uniforms, program, location, value0, value1, value2);
+}
+
+void
+gsk_gl_command_queue_set_uniform4i (GskGLCommandQueue *self,
+                                    guint              program,
+                                    guint              location,
+                                    int                value0,
+                                    int                value1,
+                                    int                value2,
+                                    int                value3)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_uniform_state_set4i (self->uniforms, program, location, value0, value1, value2, value3);
+}
+
+void
+gsk_gl_command_queue_set_uniform1f (GskGLCommandQueue *self,
+                                    guint              program,
+                                    guint              location,
+                                    float              value0)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_uniform_state_set1f (self->uniforms, program, location, value0);
+}
+
+void
+gsk_gl_command_queue_set_uniform2f (GskGLCommandQueue *self,
+                                    guint              program,
+                                    guint              location,
+                                    float              value0,
+                                    float              value1)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_uniform_state_set2f (self->uniforms, program, location, value0, value1);
+}
+
+void
+gsk_gl_command_queue_set_uniform3f (GskGLCommandQueue *self,
+                                    guint              program,
+                                    guint              location,
+                                    float              value0,
+                                    float              value1,
+                                    float              value2)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_uniform_state_set3f (self->uniforms, program, location, value0, value1, value2);
+}
+
+void
+gsk_gl_command_queue_set_uniform4f (GskGLCommandQueue *self,
+                                    guint              program,
+                                    guint              location,
+                                    float              value0,
+                                    float              value1,
+                                    float              value2,
+                                    float              value3)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_uniform_state_set4f (self->uniforms, program, location, value0, value1, value2, value3);
+}
+
+void
+gsk_gl_command_queue_set_uniform2fv (GskGLCommandQueue *self,
+                                     guint              program,
+                                     guint              location,
+                                     gsize              count,
+                                     const float       *value)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_uniform_state_set2fv (self->uniforms, program, location, count, value);
+}
+
+void
+gsk_gl_command_queue_set_uniform1fv (GskGLCommandQueue *self,
+                                     guint              program,
+                                     guint              location,
+                                     gsize              count,
+                                     const float       *value)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_uniform_state_set1fv (self->uniforms, program, location, count, value);
+}
+
+void
+gsk_gl_command_queue_set_uniform4fv (GskGLCommandQueue *self,
+                                     guint              program,
+                                     guint              location,
+                                     gsize              count,
+                                     const float       *value)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_uniform_state_set4fv (self->uniforms, program, location, count, value);
+}
+
+void
+gsk_gl_command_queue_set_uniform_matrix (GskGLCommandQueue       *self,
+                                         guint                    program,
+                                         guint                    location,
+                                         const graphene_matrix_t *matrix)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_uniform_state_set_matrix (self->uniforms, program, location, matrix);
+}
+
+void
+gsk_gl_command_queue_set_uniform_color (GskGLCommandQueue *self,
+                                        guint              program,
+                                        guint              location,
+                                        const GdkRGBA     *color)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_uniform_state_set_color (self->uniforms, program, location, color);
+}
+
+/**
+ * gsk_gl_command_queue_set_uniform_texture:
+ * @self: A #GskGLCommandQueue
+ * @program: the program id
+ * @location: the location of the uniform
+ * @texture_target: a texture target such as %GL_TEXTURE_2D
+ * @texture_slot: the texture slot such as %GL_TEXTURE0 or %GL_TEXTURE1
+ * @texture_id: the id of the texture from glGenTextures()
+ *
+ * This sets the value of a uniform to map to @texture_slot (after subtracting
+ * GL_TEXTURE0 from the value) and ensures that @texture_id is available in the
+ * same texturing slot, ensuring @texture_target.
+ */
+void
+gsk_gl_command_queue_set_uniform_texture (GskGLCommandQueue *self,
+                                          guint              program,
+                                          guint              location,
+                                          GLenum             texture_target,
+                                          GLenum             texture_slot,
+                                          guint              texture_id)
+{
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (program > 0);
+  g_assert (texture_target == GL_TEXTURE_1D ||
+            texture_target == GL_TEXTURE_2D ||
+            texture_target == GL_TEXTURE_3D);
+  g_assert (texture_slot >= GL_TEXTURE0);
+  g_assert (texture_slot < GL_TEXTURE16);
+
+  gsk_gl_attachment_state_bind_texture (self->attachments,
+                                        texture_target,
+                                        texture_slot,
+                                        texture_id);
+
+  gsk_gl_uniform_state_set_texture (self->uniforms,
+                                    program,
+                                    location,
+                                    texture_slot);
+}
+
+/**
+ * gsk_gl_command_queue_set_uniform_rounded_rect:
+ * @self: a #GskGLCommandQueue
+ * @program: the program to execute
+ * @location: the location of the uniform
+ * @rounded_rect: the rounded rect to apply
+ *
+ * Sets a uniform that is expecting a rounded rect. This is stored as a
+ * 4fv using glUniform4fv() when uniforms are applied to the progrma.
+ */
+void
+gsk_gl_command_queue_set_uniform_rounded_rect (GskGLCommandQueue    *self,
+                                               guint                 program,
+                                               guint                 location,
+                                               const GskRoundedRect *rounded_rect)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_return_if_fail (program > 0);
+  g_return_if_fail (rounded_rect != NULL);
+
+  gsk_gl_uniform_state_set_rounded_rect (self->uniforms,
+                                         program,
+                                         location,
+                                         rounded_rect);
+}
+
+/**
+ * gsk_gl_command_queue_execute:
+ * @self: a #GskGLCommandQueue
+ *
+ * Executes all of the batches in the command queue.
+ */
+static void
+gsk_gl_command_queue_execute (GskGLCommandQueue *self)
+{
+  GskGLCommandBatch *last_batch;
+  GLuint vao_id;
+
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  if (self->all_batches.length == 0)
+    return;
+
+  /* First advance the queue to ensure that we have stashed all the
+   * state we need and possibly merged the final batch.
+   */
+  last_batch = gsk_gl_command_queue_get_batch (self);
+  if (last_batch->program != 0 && last_batch->n_draws > 0)
+    gsk_gl_command_queue_advance (self, 0);
+
+  gsk_gl_command_queue_make_current (self);
+
+  glGenVertexArrays (1, &vao_id);
+  glBindVertexArray (vao_id);
+
+  gsk_gl_buffer_submit (self->vertices);
+
+  /* 0 = position location */
+  glEnableVertexAttribArray (0);
+  glVertexAttribPointer (0, 2, GL_FLOAT, GL_FALSE,
+                         sizeof (GskGLDrawVertex),
+                         (void *) G_STRUCT_OFFSET (GskGLDrawVertex, position));
+
+  /* 1 = texture coord location */
+  glEnableVertexAttribArray (1);
+  glVertexAttribPointer (1, 2, GL_FLOAT, GL_FALSE,
+                         sizeof (GskGLDrawVertex),
+                         (void *) G_STRUCT_OFFSET (GskGLDrawVertex, uv));
+
+  for (const GList *iter = self->all_batches.head; iter != NULL; iter = iter->next)
+    {
+      GskGLCommandBatch *batch = self->all_batches.head->data;
+
+      if (batch->n_draws > 0)
+        gsk_gl_command_batch_execute (batch, self->uniforms);
+    }
+
+  glDeleteVertexArrays (1, &vao_id);
+}
+
+void
+gsk_gl_command_queue_begin_frame (GskGLCommandQueue *self)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  glBindFramebuffer (GL_FRAMEBUFFER, 0);
+
+  for (guint i = 0; i < 8; i++)
+    {
+      glActiveTexture (GL_TEXTURE0 + i);
+      glBindTexture (GL_TEXTURE_2D, 0);
+    }
+
+  glBindVertexArray (0);
+  glUseProgram (0);
+}
+
+/**
+ * gsk_gl_command_queue_end_frame:
+ * @self: a #GskGLCommandQueue
+ *
+ * This function performs cleanup steps that need to be done after
+ * a frame has finished. This is not performed as part of the command
+ * queue execution to allow for the frame to be submitted as soon
+ * as possible.
+ *
+ * However, it should be executed after the draw contexts end_frame
+ * has been called to swap the OpenGL framebuffers.
+ */
+void
+gsk_gl_command_queue_end_frame (GskGLCommandQueue *self)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_command_queue_execute (self);
+
+  while (self->all_batches.length > 0)
+    {
+      GskGLCommandBatch *batch = self->all_batches.head->data;
+      gsk_gl_command_queue_release_batch (self, batch);
+    }
+
+  gsk_gl_uniform_state_end_frame (self->uniforms);
+
+  /* Release autoreleased framebuffers */
+  if (self->autorelease_framebuffers->len > 0)
+    glDeleteFramebuffers (self->autorelease_framebuffers->len,
+                          (GLuint *)(gpointer)self->autorelease_framebuffers->data);
+
+  /* Release autoreleased textures */
+  if (self->autorelease_textures->len > 0)
+    glDeleteTextures (self->autorelease_textures->len,
+                      (GLuint *)(gpointer)self->autorelease_textures->data);
+}
+
+void
+gsk_gl_command_queue_bind_framebuffer (GskGLCommandQueue *self,
+                                       guint              framebuffer)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  gsk_gl_attachment_state_bind_framebuffer (self->attachments, framebuffer);
+}
+
+static void
+gsk_gl_command_queue_save (GskGLCommandQueue *self)
+{
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+
+  g_ptr_array_add (self->saved_state,
+                   gsk_gl_attachment_state_save (self->attachments));
+}
+
+static void
+gsk_gl_command_queue_restore (GskGLCommandQueue *self)
+{
+  GskGLAttachmentState *saved;
+
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_assert (self->saved_state->len > 0);
+
+  saved = g_ptr_array_steal_index (self->saved_state,
+                                   self->saved_state->len - 1);
+
+  gsk_gl_attachment_state_restore (saved);
+}
+
+int
+gsk_gl_command_queue_create_texture (GskGLCommandQueue *self,
+                                     int                width,
+                                     int                height,
+                                     int                min_filter,
+                                     int                mag_filter)
+{
+  GLuint texture_id;
+
+  g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self), -1);
+
+  if (width > self->max_texture_size || height > self->max_texture_size)
+    return -1;
+
+  gsk_gl_command_queue_save (self);
+  gsk_gl_command_queue_make_current (self);
+
+  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);
+
+  gsk_gl_command_queue_restore (self);
+
+  return (int)texture_id;
+}
+
+guint
+gsk_gl_command_queue_create_framebuffer (GskGLCommandQueue *self)
+{
+  GLuint fbo_id;
+
+  g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self), -1);
+
+  gsk_gl_command_queue_make_current (self);
+  glGenFramebuffers (1, &fbo_id);
+  return fbo_id;
+}
+
+gboolean
+gsk_gl_command_queue_create_render_target (GskGLCommandQueue *self,
+                                           int                width,
+                                           int                height,
+                                           guint             *out_fbo_id,
+                                           guint             *out_texture_id)
+{
+  GLuint fbo_id = 0;
+  GLint texture_id;
+
+  g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self), FALSE);
+  g_return_val_if_fail (width > 0, FALSE);
+  g_return_val_if_fail (height > 0, FALSE);
+  g_return_val_if_fail (out_fbo_id != NULL, FALSE);
+  g_return_val_if_fail (out_texture_id != NULL, FALSE);
+
+  gsk_gl_command_queue_save (self);
+
+  texture_id = gsk_gl_command_queue_create_texture (self, width, height, GL_NEAREST, GL_NEAREST);
+
+  if (texture_id == -1)
+    {
+      *out_fbo_id = 0;
+      *out_texture_id = 0;
+      gsk_gl_command_queue_restore (self);
+      return FALSE;
+    }
+
+  fbo_id = gsk_gl_command_queue_create_framebuffer (self);
+
+  glBindFramebuffer (GL_FRAMEBUFFER, fbo_id);
+  glFramebufferTexture2D (GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, texture_id, 0);
+  g_assert_cmphex (glCheckFramebufferStatus (GL_FRAMEBUFFER), ==, GL_FRAMEBUFFER_COMPLETE);
+
+  gsk_gl_command_queue_restore (self);
+
+  *out_fbo_id = fbo_id;
+  *out_texture_id = texture_id;
+
+  return TRUE;
+}
+
+void
+gsk_gl_command_queue_autorelease_framebuffer (GskGLCommandQueue *self,
+                                              guint              framebuffer_id)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_return_if_fail (framebuffer_id > 0);
+
+  g_array_append_val (self->autorelease_framebuffers, framebuffer_id);
+}
+
+void
+gsk_gl_command_queue_autorelease_texture (GskGLCommandQueue *self,
+                                          guint              texture_id)
+{
+  g_return_if_fail (GSK_IS_GL_COMMAND_QUEUE (self));
+  g_return_if_fail (texture_id > 0);
+
+  g_array_append_val (self->autorelease_textures, texture_id);
+}
diff --git a/gsk/next/gskglcommandqueueprivate.h b/gsk/next/gskglcommandqueueprivate.h
new file mode 100644
index 0000000000..5e1c001bba
--- /dev/null
+++ b/gsk/next/gskglcommandqueueprivate.h
@@ -0,0 +1,148 @@
+/* gskglcommandqueueprivate.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_GL_COMMAND_QUEUE_PRIVATE_H__
+#define __GSK_GL_COMMAND_QUEUE_PRIVATE_H__
+
+#include "gskgltypes.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_COMMAND_QUEUE (gsk_gl_command_queue_get_type())
+
+G_DECLARE_FINAL_TYPE (GskGLCommandQueue, gsk_gl_command_queue, GSK, GL_COMMAND_QUEUE, GObject)
+
+GskGLCommandQueue *gsk_gl_command_queue_new                      (GdkGLContext             *context);
+GdkGLContext      *gsk_gl_command_queue_get_context              (GskGLCommandQueue        *self);
+void               gsk_gl_command_queue_make_current             (GskGLCommandQueue        *self);
+void               gsk_gl_command_queue_begin_frame              (GskGLCommandQueue        *self);
+void               gsk_gl_command_queue_end_frame                (GskGLCommandQueue        *self);
+GdkTexture        *gsk_gl_command_queue_download                 (GskGLCommandQueue        *self,
+                                                                  GError                  **error);
+GdkMemoryTexture  *gsk_gl_command_queue_download_texture         (GskGLCommandQueue        *self,
+                                                                  guint                     texture_id,
+                                                                  GError                  **error);
+guint              gsk_gl_command_queue_upload_texture           (GskGLCommandQueue        *self,
+                                                                  GdkTexture               *texture,
+                                                                  GError                  **error);
+int                gsk_gl_command_queue_create_texture           (GskGLCommandQueue        *self,
+                                                                  int                       width,
+                                                                  int                       height,
+                                                                  int                       min_filter,
+                                                                  int                       mag_filter);
+guint              gsk_gl_command_queue_create_framebuffer       (GskGLCommandQueue        *self);
+gboolean           gsk_gl_command_queue_create_render_target     (GskGLCommandQueue        *self,
+                                                                  int                       width,
+                                                                  int                       height,
+                                                                  guint                    *out_fbo_id,
+                                                                  guint                    *out_texture_id);
+void               gsk_gl_command_queue_delete_program           (GskGLCommandQueue        *self,
+                                                                  guint                     program_id);
+void               gsk_gl_command_queue_use_program              (GskGLCommandQueue        *self,
+                                                                  guint                     program_id);
+void               gsk_gl_command_queue_bind_framebuffer         (GskGLCommandQueue        *self,
+                                                                  guint                     framebuffer);
+void               gsk_gl_command_queue_set_uniform1i            (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  int                       value0);
+void               gsk_gl_command_queue_set_uniform2i            (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  int                       value0,
+                                                                  int                       value1);
+void               gsk_gl_command_queue_set_uniform3i            (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  int                       value0,
+                                                                  int                       value1,
+                                                                  int                       value2);
+void               gsk_gl_command_queue_set_uniform4i            (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  int                       value0,
+                                                                  int                       value1,
+                                                                  int                       value2,
+                                                                  int                       value3);
+void               gsk_gl_command_queue_set_uniform1f            (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  float                     value0);
+void               gsk_gl_command_queue_set_uniform2f            (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  float                     value0,
+                                                                  float                     value1);
+void               gsk_gl_command_queue_set_uniform3f            (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  float                     value0,
+                                                                  float                     value1,
+                                                                  float                     value2);
+void               gsk_gl_command_queue_set_uniform4f            (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  float                     value0,
+                                                                  float                     value1,
+                                                                  float                     value2,
+                                                                  float                     value3);
+void               gsk_gl_command_queue_set_uniform2fv           (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  gsize                     count,
+                                                                  const float              *value);
+void               gsk_gl_command_queue_set_uniform1fv           (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  gsize                     count,
+                                                                  const float              *value);
+void               gsk_gl_command_queue_set_uniform4fv           (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  gsize                     count,
+                                                                  const float              *value);
+void               gsk_gl_command_queue_set_uniform_matrix       (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  const graphene_matrix_t  *matrix);
+void               gsk_gl_command_queue_set_uniform_color        (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  const GdkRGBA            *color);
+void               gsk_gl_command_queue_set_uniform_texture      (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  GLenum                    texture_target,
+                                                                  GLenum                    texture_slot,
+                                                                  guint                     texture_id);
+void               gsk_gl_command_queue_set_uniform_rounded_rect (GskGLCommandQueue        *self,
+                                                                  guint                     program,
+                                                                  guint                     location,
+                                                                  const GskRoundedRect     *rounded_rect);
+void               gsk_gl_command_queue_autorelease_framebuffer  (GskGLCommandQueue        *self,
+                                                                  guint                     framebuffer_id);
+void               gsk_gl_command_queue_autorelease_texture      (GskGLCommandQueue        *self,
+                                                                  guint                     texture_id);
+GskGLDrawVertex   *gsk_gl_command_queue_draw                     (GskGLCommandQueue        *self,
+                                                                  const GskGLDrawVertex     vertices[6]);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_COMMAND_QUEUE_PRIVATE_H__ */
diff --git a/gsk/next/gskglcompiler.c b/gsk/next/gskglcompiler.c
new file mode 100644
index 0000000000..d5df9bc3b4
--- /dev/null
+++ b/gsk/next/gskglcompiler.c
@@ -0,0 +1,661 @@
+/* gskglcompiler.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 <epoxy/gl.h>
+#include <string.h>
+
+#include "gskglcommandqueueprivate.h"
+#include "gskglcompilerprivate.h"
+#include "gskglprogramprivate.h"
+
+#define SHADER_VERSION_GLES       100
+#define SHADER_VERSION_GL2_LEGACY 110
+#define SHADER_VERSION_GL3_LEGACY 130
+#define SHADER_VERSION_GL3        150
+
+struct _GskGLCompiler
+{
+  GObject parent_instance;
+
+  GskGLCommandQueue *command_queue;
+
+  GBytes *all_preamble;
+  GBytes *fragment_preamble;
+  GBytes *vertex_preamble;
+  GBytes *fragment_source;
+  GBytes *fragment_suffix;
+  GBytes *vertex_source;
+  GBytes *vertex_suffix;
+
+  GHashTable *attrib_locations;
+
+  int glsl_version;
+
+  guint gl3 : 1;
+  guint gles : 1;
+  guint legacy : 1;
+  guint debug_shaders : 1;
+};
+
+static GBytes *empty_bytes;
+
+G_DEFINE_TYPE (GskGLCompiler, gsk_gl_compiler, G_TYPE_OBJECT)
+
+static void
+gsk_gl_compiler_finalize (GObject *object)
+{
+  GskGLCompiler *self = (GskGLCompiler *)object;
+
+  g_clear_pointer (&self->all_preamble, g_bytes_unref);
+  g_clear_pointer (&self->fragment_preamble, g_bytes_unref);
+  g_clear_pointer (&self->vertex_preamble, g_bytes_unref);
+  g_clear_pointer (&self->vertex_suffix, g_bytes_unref);
+  g_clear_pointer (&self->fragment_source, g_bytes_unref);
+  g_clear_pointer (&self->fragment_suffix, g_bytes_unref);
+  g_clear_pointer (&self->vertex_source, g_bytes_unref);
+  g_clear_pointer (&self->attrib_locations, g_hash_table_unref);
+  g_clear_object (&self->command_queue);
+
+  G_OBJECT_CLASS (gsk_gl_compiler_parent_class)->finalize (object);
+}
+
+static void
+gsk_gl_compiler_class_init (GskGLCompilerClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gsk_gl_compiler_finalize;
+
+  empty_bytes = g_bytes_new (NULL, 0);
+}
+
+static void
+gsk_gl_compiler_init (GskGLCompiler *self)
+{
+  self->glsl_version = 150;
+  self->attrib_locations = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+  self->all_preamble = g_bytes_ref (empty_bytes);
+  self->vertex_preamble = g_bytes_ref (empty_bytes);
+  self->fragment_preamble = g_bytes_ref (empty_bytes);
+  self->vertex_source = g_bytes_ref (empty_bytes);
+  self->vertex_suffix = g_bytes_ref (empty_bytes);
+  self->fragment_source = g_bytes_ref (empty_bytes);
+  self->fragment_suffix = g_bytes_ref (empty_bytes);
+}
+
+GskGLCompiler *
+gsk_gl_compiler_new (GskGLCommandQueue *command_queue,
+                     gboolean           debug_shaders)
+{
+  GskGLCompiler *self;
+  GdkGLContext *context;
+
+  g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (command_queue), NULL);
+
+  self = g_object_new (GSK_TYPE_GL_COMPILER, NULL);
+  self->command_queue = g_object_ref (command_queue);
+  self->debug_shaders = !!debug_shaders;
+
+  context = gsk_gl_command_queue_get_context (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_gl_command_queue_make_current (command_queue);
+
+  return g_steal_pointer (&self);
+}
+
+void
+gsk_gl_compiler_bind_attribute (GskGLCompiler *self,
+                                const char    *name,
+                                guint          location)
+{
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (name != NULL);
+  g_return_if_fail (location < 32);
+
+  g_hash_table_insert (self->attrib_locations,
+                       g_strdup (name),
+                       GUINT_TO_POINTER (location));
+}
+
+void
+gsk_gl_compiler_clear_attributes (GskGLCompiler *self)
+{
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+
+  g_hash_table_remove_all (self->attrib_locations);
+}
+
+void
+gsk_gl_compiler_set_preamble (GskGLCompiler     *self,
+                              GskGLCompilerKind  kind,
+                              GBytes            *preamble_bytes)
+{
+  GBytes **loc = NULL;
+
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (preamble_bytes != NULL);
+
+  if (kind == GSK_GL_COMPILER_ALL)
+    loc = &self->all_preamble;
+  else if (kind == GSK_GL_COMPILER_FRAGMENT)
+    loc = &self->fragment_preamble;
+  else if (kind == GSK_GL_COMPILER_VERTEX)
+    loc = &self->vertex_preamble;
+  else
+    g_return_if_reached ();
+
+  g_assert (loc != NULL);
+
+  if (*loc != preamble_bytes)
+    {
+      g_clear_pointer (loc, g_bytes_unref);
+      *loc = preamble_bytes ? g_bytes_ref (preamble_bytes) : NULL;
+    }
+}
+
+void
+gsk_gl_compiler_set_preamble_from_resource (GskGLCompiler     *self,
+                                            GskGLCompilerKind  kind,
+                                            const char        *resource_path)
+{
+  GError *error = NULL;
+  GBytes *bytes;
+
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (kind == GSK_GL_COMPILER_ALL ||
+                    kind == GSK_GL_COMPILER_VERTEX ||
+                    kind == GSK_GL_COMPILER_FRAGMENT);
+  g_return_if_fail (resource_path != NULL);
+
+  bytes = g_resources_lookup_data (resource_path,
+                                   G_RESOURCE_LOOKUP_FLAGS_NONE,
+                                   &error);
+
+  if (bytes == NULL)
+    g_warning ("Cannot set shader from resource: %s", error->message);
+  else
+    gsk_gl_compiler_set_preamble (self, kind, bytes);
+
+  g_clear_pointer (&bytes, g_bytes_unref);
+  g_clear_error (&error);
+}
+
+void
+gsk_gl_compiler_set_source (GskGLCompiler     *self,
+                            GskGLCompilerKind  kind,
+                            GBytes            *source_bytes)
+{
+  GBytes **loc = NULL;
+
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (kind == GSK_GL_COMPILER_ALL ||
+                    kind == GSK_GL_COMPILER_VERTEX ||
+                    kind == GSK_GL_COMPILER_FRAGMENT);
+
+  if (source_bytes == NULL)
+    source_bytes = empty_bytes;
+
+  /* If kind is ALL, then we need to split the fragment and
+   * vertex shaders from the bytes and assign them individually.
+   * This safely scans for FRAGMENT_SHADER and VERTEX_SHADER as
+   * specified within the GLSL resources. Some care is taken to
+   * use GBytes which reference the original bytes instead of
+   * copying them.
+   */
+  if (kind == GSK_GL_COMPILER_ALL)
+    {
+      gsize len = 0;
+      const char *source;
+      const char *vertex_shader_start;
+      const char *fragment_shader_start;
+      const char *endpos;
+      GBytes *fragment_bytes;
+      GBytes *vertex_bytes;
+
+      source = g_bytes_get_data (source_bytes, &len);
+      endpos = source + len;
+      vertex_shader_start = g_strstr_len (source, len, "VERTEX_SHADER");
+      fragment_shader_start = g_strstr_len (source, len, "FRAGMENT_SHADER");
+
+      if (vertex_shader_start == NULL)
+        {
+          g_warning ("Failed to locate VERTEX_SHADER in shader source");
+          return;
+        }
+
+      if (fragment_shader_start == NULL)
+        {
+          g_warning ("Failed to locate FRAGMENT_SHADER in shader source");
+          return;
+        }
+
+      if (vertex_shader_start > fragment_shader_start)
+        {
+          g_warning ("VERTEX_SHADER must come before FRAGMENT_SHADER");
+          return;
+        }
+
+      /* Locate next newlines */
+      while (vertex_shader_start < endpos && vertex_shader_start[0] != '\n')
+        vertex_shader_start++;
+      while (fragment_shader_start < endpos && fragment_shader_start[0] != '\n')
+        fragment_shader_start++;
+
+      vertex_bytes = g_bytes_new_from_bytes (source_bytes,
+                                             vertex_shader_start - source,
+                                             fragment_shader_start - vertex_shader_start);
+      fragment_bytes = g_bytes_new_from_bytes (source_bytes,
+                                               fragment_shader_start - source,
+                                               endpos - fragment_shader_start);
+
+      gsk_gl_compiler_set_source (self, GSK_GL_COMPILER_VERTEX, vertex_bytes);
+      gsk_gl_compiler_set_source (self, GSK_GL_COMPILER_FRAGMENT, fragment_bytes);
+
+      g_bytes_unref (fragment_bytes);
+      g_bytes_unref (vertex_bytes);
+
+      return;
+    }
+
+  if (kind == GSK_GL_COMPILER_FRAGMENT)
+    loc = &self->fragment_source;
+  else if (kind == GSK_GL_COMPILER_VERTEX)
+    loc = &self->vertex_source;
+  else
+    g_return_if_reached ();
+
+  if (*loc != source_bytes)
+    {
+      g_clear_pointer (loc, g_bytes_unref);
+      *loc = g_bytes_ref (source_bytes);
+    }
+}
+
+void
+gsk_gl_compiler_set_source_from_resource (GskGLCompiler     *self,
+                                          GskGLCompilerKind  kind,
+                                          const char        *resource_path)
+{
+  GError *error = NULL;
+  GBytes *bytes;
+
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (kind == GSK_GL_COMPILER_ALL ||
+                    kind == GSK_GL_COMPILER_VERTEX ||
+                    kind == GSK_GL_COMPILER_FRAGMENT);
+  g_return_if_fail (resource_path != NULL);
+
+  bytes = g_resources_lookup_data (resource_path,
+                                   G_RESOURCE_LOOKUP_FLAGS_NONE,
+                                   &error);
+
+  if (bytes == NULL)
+    g_warning ("Cannot set shader from resource: %s", error->message);
+  else
+    gsk_gl_compiler_set_source (self, kind, bytes);
+
+  g_clear_pointer (&bytes, g_bytes_unref);
+  g_clear_error (&error);
+}
+
+void
+gsk_gl_compiler_set_suffix (GskGLCompiler     *self,
+                            GskGLCompilerKind  kind,
+                            GBytes            *suffix_bytes)
+{
+  GBytes **loc;
+
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (kind == GSK_GL_COMPILER_VERTEX ||
+                    kind == GSK_GL_COMPILER_FRAGMENT);
+  g_return_if_fail (suffix_bytes != NULL);
+
+  if (suffix_bytes == NULL)
+    suffix_bytes = empty_bytes;
+
+  if (kind == GSK_GL_COMPILER_FRAGMENT)
+    loc = &self->fragment_suffix;
+  else if (kind == GSK_GL_COMPILER_VERTEX)
+    loc = &self->vertex_suffix;
+  else
+    g_return_if_reached ();
+
+  if (*loc != suffix_bytes)
+    {
+      g_clear_pointer (loc, g_bytes_unref);
+      *loc = g_bytes_ref (suffix_bytes);
+    }
+}
+
+void
+gsk_gl_compiler_set_suffix_from_resource (GskGLCompiler     *self,
+                                          GskGLCompilerKind  kind,
+                                          const char        *resource_path)
+{
+  GError *error = NULL;
+  GBytes *bytes;
+
+  g_return_if_fail (GSK_IS_GL_COMPILER (self));
+  g_return_if_fail (kind == GSK_GL_COMPILER_VERTEX ||
+                    kind == GSK_GL_COMPILER_FRAGMENT);
+  g_return_if_fail (resource_path != NULL);
+
+  bytes = g_resources_lookup_data (resource_path,
+                                   G_RESOURCE_LOOKUP_FLAGS_NONE,
+                                   &error);
+
+  if (bytes == NULL)
+    g_warning ("Cannot set suffix from resource: %s", error->message);
+  else
+    gsk_gl_compiler_set_suffix (self, kind, bytes);
+
+  g_clear_pointer (&bytes, g_bytes_unref);
+  g_clear_error (&error);
+}
+
+static void
+prepend_line_numbers (char    *code,
+                      GString *s)
+{
+  char *p;
+  int line;
+
+  p = code;
+  line = 1;
+  while (*p)
+    {
+      char *end = strchr (p, '\n');
+      if (end)
+        end = end + 1; /* Include newline */
+      else
+        end = p + strlen (p);
+
+      g_string_append_printf (s, "%3d| ", line++);
+      g_string_append_len (s, p, end - p);
+
+      p = end;
+    }
+}
+
+static gboolean
+check_shader_error (int     shader_id,
+                    GError **error)
+{
+  GLint status;
+  GLint log_len;
+  GLint code_len;
+  char *buffer;
+  char *code;
+  GString *s;
+
+  glGetShaderiv (shader_id, GL_COMPILE_STATUS, &status);
+
+  if G_LIKELY (status == GL_TRUE)
+    return TRUE;
+
+  glGetShaderiv (shader_id, GL_INFO_LOG_LENGTH, &log_len);
+  buffer = g_malloc0 (log_len + 1);
+  glGetShaderInfoLog (shader_id, log_len, NULL, buffer);
+
+  glGetShaderiv (shader_id, GL_SHADER_SOURCE_LENGTH, &code_len);
+  code = g_malloc0 (code_len + 1);
+  glGetShaderSource (shader_id, code_len, NULL, code);
+
+  s = g_string_new ("");
+  prepend_line_numbers (code, s);
+
+  g_set_error (error,
+               GDK_GL_ERROR,
+               GDK_GL_ERROR_COMPILATION_FAILED,
+               "Compilation failure in shader.\n"
+               "Source Code: %s\n"
+               "\n"
+               "Error Message:\n"
+               "%s\n"
+               "\n",
+               s->str,
+               buffer);
+
+  g_string_free (s, TRUE);
+  g_free (buffer);
+  g_free (code);
+
+  return FALSE;
+}
+
+static void
+print_shader_info (const char *prefix,
+                   int         shader_id,
+                   const char *name)
+{
+  if (GSK_DEBUG_CHECK(SHADERS))
+    {
+      int code_len;
+
+      glGetShaderiv (shader_id, GL_SHADER_SOURCE_LENGTH, &code_len);
+
+      if (code_len > 0)
+        {
+          char *code;
+          GString *s;
+
+          code = g_malloc0 (code_len + 1);
+          glGetShaderSource (shader_id, code_len, NULL, code);
+
+          s = g_string_new (NULL);
+          prepend_line_numbers (code, s);
+
+          g_message ("%s %d, %s:\n%s",
+                     prefix, shader_id,
+                     name ? name : "unnamed",
+                     s->str);
+          g_string_free (s,  TRUE);
+          g_free (code);
+        }
+    }
+}
+
+static const char *
+get_shader_string (GBytes *bytes)
+{
+  /* 0 length bytes will give us NULL back */
+  const char *str = g_bytes_get_data (bytes, NULL);
+  return str ? str : "";
+}
+
+GskGLProgram *
+gsk_gl_compiler_compile (GskGLCompiler  *self,
+                         const char     *name,
+                         GError        **error)
+{
+  char version[32];
+  const char *debug = "";
+  const char *legacy = "";
+  const char *gl3 = "";
+  const char *gles = "";
+  const char *key;
+  gpointer value;
+  GHashTableIter iter;
+  int program_id;
+  int vertex_id;
+  int fragment_id;
+  int status;
+
+  g_return_val_if_fail (GSK_IS_GL_COMPILER (self), NULL);
+  g_return_val_if_fail (self->all_preamble != NULL, NULL);
+  g_return_val_if_fail (self->fragment_preamble != NULL, NULL);
+  g_return_val_if_fail (self->vertex_preamble != NULL, NULL);
+  g_return_val_if_fail (self->fragment_source != NULL, NULL);
+  g_return_val_if_fail (self->vertex_source != NULL, NULL);
+  g_return_val_if_fail (self->command_queue != NULL, NULL);
+
+  gsk_gl_command_queue_make_current (self->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_GLES 1\n";
+
+  if (self->gl3)
+    gl3 = "#define GSK_GL3 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),
+                  0,
+                    //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 0;
+    }
+
+  print_shader_info ("Fragment shader", fragment_id, name);
+
+  program_id = glCreateProgram ();
+  glAttachShader (program_id, vertex_id);
+  glAttachShader (program_id, fragment_id);
+  g_hash_table_iter_init (&iter, self->attrib_locations);
+  while (g_hash_table_iter_next (&iter, (gpointer *)&key, &value))
+    glBindAttribLocation (program_id, GPOINTER_TO_UINT (value), key);
+  glLinkProgram (program_id);
+
+  glDeleteShader (fragment_id);
+  glDeleteShader (vertex_id);
+
+  glGetProgramiv (program_id, GL_LINK_STATUS, &status);
+
+  if (status == GL_FALSE)
+    {
+      char *buffer = NULL;
+      int log_len = 0;
+
+      glGetProgramiv (program_id, GL_INFO_LOG_LENGTH, &log_len);
+
+      if (log_len > 0)
+        {
+          /* log_len includes NULL */
+          buffer = g_malloc0 (log_len);
+          glGetProgramInfoLog (program_id, log_len, NULL, buffer);
+        }
+
+      g_warning ("Linking failure in shader:\n%s",
+                 buffer ? buffer : "");
+
+      g_set_error (error,
+                   GDK_GL_ERROR,
+                   GDK_GL_ERROR_LINK_FAILED,
+                   "Linking failure in shader: %s",
+                   buffer ? buffer : "");
+
+      g_free (buffer);
+
+      glDeleteProgram (program_id);
+
+      return NULL;
+    }
+
+  return gsk_gl_program_new (self->command_queue, name, program_id);
+}
diff --git a/gsk/next/gskglcompilerprivate.h b/gsk/next/gskglcompilerprivate.h
new file mode 100644
index 0000000000..d28c48c0b9
--- /dev/null
+++ b/gsk/next/gskglcompilerprivate.h
@@ -0,0 +1,69 @@
+/* gskglcompilerprivate.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_GL_COMPILER_PRIVATE_H__
+#define __GSK_GL_COMPILER_PRIVATE_H__
+
+#include "gskgltypes.h"
+
+G_BEGIN_DECLS
+
+typedef enum _GskGLCompilerKind
+{
+  GSK_GL_COMPILER_ALL,
+  GSK_GL_COMPILER_FRAGMENT,
+  GSK_GL_COMPILER_VERTEX,
+} GskGLCompilerKind;
+
+#define GSK_TYPE_GL_COMPILER (gsk_gl_compiler_get_type())
+
+G_DECLARE_FINAL_TYPE (GskGLCompiler, gsk_gl_compiler, GSK, GL_COMPILER, GObject)
+
+GskGLCompiler *gsk_gl_compiler_new                        (GskGLCommandQueue  *command_queue,
+                                                           gboolean            debug);
+void           gsk_gl_compiler_set_preamble               (GskGLCompiler      *self,
+                                                           GskGLCompilerKind   kind,
+                                                           GBytes             *preamble_bytes);
+void           gsk_gl_compiler_set_preamble_from_resource (GskGLCompiler      *self,
+                                                           GskGLCompilerKind   kind,
+                                                           const char         *resource_path);
+void           gsk_gl_compiler_set_source                 (GskGLCompiler      *self,
+                                                           GskGLCompilerKind   kind,
+                                                           GBytes             *source_bytes);
+void           gsk_gl_compiler_set_source_from_resource   (GskGLCompiler      *self,
+                                                           GskGLCompilerKind   kind,
+                                                           const char         *resource_path);
+void           gsk_gl_compiler_set_suffix                 (GskGLCompiler      *self,
+                                                           GskGLCompilerKind   kind,
+                                                           GBytes             *suffix_bytes);
+void           gsk_gl_compiler_set_suffix_from_resource   (GskGLCompiler      *self,
+                                                           GskGLCompilerKind   kind,
+                                                           const char         *resource_path);
+void           gsk_gl_compiler_bind_attribute             (GskGLCompiler      *self,
+                                                           const char         *name,
+                                                           guint               location);
+void           gsk_gl_compiler_clear_attributes           (GskGLCompiler      *self);
+GskGLProgram  *gsk_gl_compiler_compile                    (GskGLCompiler      *self,
+                                                           const char         *name,
+                                                           GError            **error);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_COMPILER_PRIVATE_H__ */
diff --git a/gsk/next/gskgldriver.c b/gsk/next/gskgldriver.c
new file mode 100644
index 0000000000..0fde92af53
--- /dev/null
+++ b/gsk/next/gskgldriver.c
@@ -0,0 +1,219 @@
+/* gskgldriver.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 <gsk/gskrendererprivate.h>
+
+#include "gskglcommandqueueprivate.h"
+#include "gskglcompilerprivate.h"
+#include "gskgldriverprivate.h"
+#include "gskglglyphlibraryprivate.h"
+#include "gskgliconlibraryprivate.h"
+#include "gskglprogramprivate.h"
+#include "gskglshadowlibraryprivate.h"
+
+G_DEFINE_TYPE (GskNextDriver, gsk_next_driver, G_TYPE_OBJECT)
+
+static void
+gsk_next_driver_dispose (GObject *object)
+{
+  GskNextDriver *self = (GskNextDriver *)object;
+
+#define GSK_GL_NO_UNIFORMS
+#define GSK_GL_ADD_UNIFORM(pos, KEY, name)
+#define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms) \
+  G_STMT_START {                                        \
+    if (self->name)                                     \
+      gsk_gl_program_delete (self->name);               \
+    g_clear_object (&self->name);                       \
+  } G_STMT_END;
+# include "gskglprograms.defs"
+#undef GSK_GL_NO_UNIFORMS
+#undef GSK_GL_ADD_UNIFORM
+#undef GSK_GL_DEFINE_PROGRAM
+
+  g_clear_object (&self->command_queue);
+
+  G_OBJECT_CLASS (gsk_next_driver_parent_class)->dispose (object);
+}
+
+static void
+gsk_next_driver_class_init (GskNextDriverClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gsk_next_driver_dispose;
+}
+
+static void
+gsk_next_driver_init (GskNextDriver *self)
+{
+}
+
+static gboolean
+gsk_next_driver_load_programs (GskNextDriver  *self,
+                               GError        **error)
+{
+  GskGLCompiler *compiler;
+  gboolean ret = FALSE;
+
+  g_assert (GSK_IS_NEXT_DRIVER (self));
+  g_assert (GSK_IS_GL_COMMAND_QUEUE (self->command_queue));
+
+  compiler = gsk_gl_compiler_new (self->command_queue, self->debug);
+
+  /* Setup preambles that are shared by all shaders */
+  gsk_gl_compiler_set_preamble_from_resource (compiler,
+                                              GSK_GL_COMPILER_ALL,
+                                              "/org/gtk/libgsk/glsl/preamble.glsl");
+  gsk_gl_compiler_set_preamble_from_resource (compiler,
+                                              GSK_GL_COMPILER_VERTEX,
+                                              "/org/gtk/libgsk/glsl/preamble.vs.glsl");
+  gsk_gl_compiler_set_preamble_from_resource (compiler,
+                                              GSK_GL_COMPILER_FRAGMENT,
+                                              "/org/gtk/libgsk/glsl/preamble.fs.glsl");
+
+  /* Setup attributes that are provided via VBO */
+  gsk_gl_compiler_bind_attribute (compiler, "aPosition", 0);
+  gsk_gl_compiler_bind_attribute (compiler, "vUv", 1);
+
+  /* Use XMacros to register all of our programs and their uniforms */
+#define GSK_GL_NO_UNIFORMS
+#define GSK_GL_ADD_UNIFORM(pos, KEY, name)                                                      \
+  gsk_gl_program_add_uniform (program, #name, UNIFORM_##KEY);
+#define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms)                                         \
+  G_STMT_START {                                                                                \
+    GskGLProgram *program;                                                                      \
+    gboolean have_alpha;                                                                        \
+                                                                                                \
+    gsk_gl_compiler_set_source_from_resource (compiler, GSK_GL_COMPILER_ALL, resource);         \
+                                                                                                \
+    if (!(program = gsk_gl_compiler_compile (compiler, #name, error)))                          \
+      goto failure;                                                                             \
+                                                                                                \
+    have_alpha = gsk_gl_program_add_uniform (program, "u_alpha", UNIFORM_SHARED_ALPHA);         \
+    gsk_gl_program_add_uniform (program, "u_source", UNIFORM_SHARED_SOURCE);                    \
+    gsk_gl_program_add_uniform (program, "u_clip_rect", UNIFORM_SHARED_CLIP_RECT);              \
+    gsk_gl_program_add_uniform (program, "u_viewport", UNIFORM_SHARED_VIEWPORT);                \
+    gsk_gl_program_add_uniform (program, "u_projection", UNIFORM_SHARED_PROJECTION);            \
+    gsk_gl_program_add_uniform (program, "u_modelview", UNIFORM_SHARED_MODELVIEW);              \
+                                                                                                \
+    uniforms                                                                                    \
+                                                                                                \
+    if (have_alpha)                                                                             \
+      gsk_gl_program_set_uniform1f (program, UNIFORM_SHARED_ALPHA, 1.0f);                       \
+                                                                                                \
+    *(GskGLProgram **)(((guint8 *)self) + G_STRUCT_OFFSET (GskNextDriver, name)) =              \
+        g_steal_pointer (&program);                                                             \
+  } G_STMT_END;
+# include "gskglprograms.defs"
+#undef GSK_GL_DEFINE_PROGRAM
+#undef GSK_GL_ADD_UNIFORM
+
+  ret = TRUE;
+
+failure:
+  g_clear_object (&compiler);
+
+  return ret;
+}
+
+GskNextDriver *
+gsk_next_driver_new (GskGLCommandQueue  *command_queue,
+                     gboolean            debug,
+                     GError            **error)
+{
+  GskNextDriver *self;
+  GdkGLContext *context;
+
+  g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (command_queue), NULL);
+
+  context = gsk_gl_command_queue_get_context (command_queue);
+
+  gdk_gl_context_make_current (context);
+
+  self = g_object_new (GSK_TYPE_NEXT_DRIVER, NULL);
+  self->command_queue = g_object_ref (command_queue);
+  self->debug = !!debug;
+
+  if (!gsk_next_driver_load_programs (self, error))
+    {
+      g_object_unref (self);
+      return NULL;
+    }
+
+  self->glyphs = gsk_gl_glyph_library_new (context);
+  self->icons = gsk_gl_icon_library_new (context);
+  self->shadows = gsk_gl_shadow_library_new (context);
+
+  return g_steal_pointer (&self);
+}
+
+void
+gsk_next_driver_begin_frame (GskNextDriver *self)
+{
+  g_return_if_fail (GSK_IS_NEXT_DRIVER (self));
+  g_return_if_fail (self->in_frame == FALSE);
+
+  self->in_frame = TRUE;
+
+  gsk_gl_command_queue_begin_frame (self->command_queue);
+}
+
+void
+gsk_next_driver_end_frame (GskNextDriver *self)
+{
+  g_return_if_fail (GSK_IS_NEXT_DRIVER (self));
+  g_return_if_fail (self->in_frame == TRUE);
+
+  gsk_gl_command_queue_end_frame (self->command_queue);
+
+  self->in_frame = FALSE;
+}
+
+GdkGLContext *
+gsk_next_driver_get_context (GskNextDriver *self)
+{
+  g_return_val_if_fail (GSK_IS_NEXT_DRIVER (self), NULL);
+  g_return_val_if_fail (GSK_IS_GL_COMMAND_QUEUE (self->command_queue), NULL);
+
+  return gsk_gl_command_queue_get_context (self->command_queue);
+}
+
+gboolean
+gsk_next_driver_create_render_target (GskNextDriver *self,
+                                      int            width,
+                                      int            height,
+                                      guint         *out_fbo_id,
+                                      guint         *out_texture_id)
+{
+  g_return_val_if_fail (GSK_IS_NEXT_DRIVER (self), FALSE);
+
+  if (self->command_queue == NULL)
+    return FALSE;
+
+  return gsk_gl_command_queue_create_render_target (self->command_queue,
+                                                    width,
+                                                    height,
+                                                    out_fbo_id,
+                                                    out_texture_id);
+}
diff --git a/gsk/next/gskgldriverprivate.h b/gsk/next/gskgldriverprivate.h
new file mode 100644
index 0000000000..b7c5a852b5
--- /dev/null
+++ b/gsk/next/gskgldriverprivate.h
@@ -0,0 +1,87 @@
+/* gskgldriverprivate.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_GL_DRIVER_PRIVATE_H__
+#define __GSK_GL_DRIVER_PRIVATE_H__
+
+#include "gskgltypes.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
+};
+
+#define GSL_GK_NO_UNIFORMS UNIFORM_INVALID_##__COUNTER__
+#define GSK_GL_ADD_UNIFORM(pos, KEY, name) UNIFORM_##KEY = UNIFORM_SHARED_LAST + pos,
+#define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms) enum { uniforms };
+# include "gskglprograms.defs"
+#undef GSK_GL_DEFINE_PROGRAM
+#undef GSK_GL_ADD_UNIFORM
+#undef GSL_GK_NO_UNIFORMS
+
+#define GSK_TYPE_NEXT_DRIVER (gsk_next_driver_get_type())
+
+G_DECLARE_FINAL_TYPE (GskNextDriver, gsk_next_driver, GSK, NEXT_DRIVER, GObject)
+
+struct _GskNextDriver
+{
+  GObject parent_instance;
+
+  GskGLCommandQueue *command_queue;
+
+  GskGLGlyphLibrary  *glyphs;
+  GskGLIconLibrary   *icons;
+  GskGLShadowLibrary *shadows;
+
+#define GSK_GL_NO_UNIFORMS
+#define GSK_GL_ADD_UNIFORM(pos, KEY, name)
+#define GSK_GL_DEFINE_PROGRAM(name, resource, uniforms) GskGLProgram *name;
+# include "gskglprograms.defs"
+#undef GSK_GL_NO_UNIFORMS
+#undef GSK_GL_ADD_UNIFORM
+#undef GSK_GL_DEFINE_PROGRAM
+
+  guint debug : 1;
+  guint in_frame : 1;
+};
+
+GskNextDriver *gsk_next_driver_new                  (GskGLCommandQueue  *command_queue,
+                                                     gboolean            debug,
+                                                     GError            **error);
+GdkGLContext  *gsk_next_driver_get_context          (GskNextDriver      *self);
+gboolean       gsk_next_driver_create_render_target (GskNextDriver      *self,
+                                                     int                 width,
+                                                     int                 height,
+                                                     guint              *out_fbo_id,
+                                                     guint              *out_texture_id);
+void           gsk_next_driver_begin_frame          (GskNextDriver      *driver);
+void           gsk_next_driver_end_frame            (GskNextDriver      *driver);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_DRIVER_PRIVATE_H__ */
diff --git a/gsk/next/gskglglyphlibrary.c b/gsk/next/gskglglyphlibrary.c
new file mode 100644
index 0000000000..ed40f1af7f
--- /dev/null
+++ b/gsk/next/gskglglyphlibrary.c
@@ -0,0 +1,50 @@
+/* gskglglyphlibrary.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 "gskglglyphlibraryprivate.h"
+
+struct _GskGLGlyphLibrary
+{
+  GskGLTextureLibrary parent_instance;
+};
+
+G_DEFINE_TYPE (GskGLGlyphLibrary, gsk_gl_glyph_library, GSK_TYPE_GL_TEXTURE_LIBRARY)
+
+GskGLGlyphLibrary *
+gsk_gl_glyph_library_new (GdkGLContext *context)
+{
+  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
+
+  return g_object_new (GSK_TYPE_GL_GLYPH_LIBRARY,
+                       "context", context,
+                       NULL);
+}
+
+static void
+gsk_gl_glyph_library_class_init (GskGLGlyphLibraryClass *klass)
+{
+}
+
+static void
+gsk_gl_glyph_library_init (GskGLGlyphLibrary *self)
+{
+}
diff --git a/gsk/next/gskglglyphlibraryprivate.h b/gsk/next/gskglglyphlibraryprivate.h
new file mode 100644
index 0000000000..1929bc9ad4
--- /dev/null
+++ b/gsk/next/gskglglyphlibraryprivate.h
@@ -0,0 +1,53 @@
+/* gskglglyphlibraryprivate.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_GL_GLYPH_LIBRARY_PRIVATE_H__
+#define __GSK_GL_GLYPH_LIBRARY_PRIVATE_H__
+
+#include <pango/pango.h>
+
+#include "gskgltexturelibraryprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GskGLGlyphKey
+{
+  PangoFont *font;
+  PangoGlyph glyph;
+  guint xshift : 3;
+  guint yshift : 3;
+  guint scale  : 26; /* times 1024 */
+} GskGLGlyphKey;
+
+#if GLIB_SIZEOF_VOID_P == 8
+G_STATIC_ASSERT (sizeof (GskGLGlyphKey) == 16);
+#elif GLIB_SIZEOF_VOID_P == 4
+G_STATIC_ASSERT (sizeof (GskGLGlyphKey) == 12);
+#endif
+
+#define GSK_TYPE_GL_GLYPH_LIBRARY (gsk_gl_glyph_library_get_type())
+
+G_DECLARE_FINAL_TYPE (GskGLGlyphLibrary, gsk_gl_glyph_library, GSK, GL_GLYPH_LIBRARY, GskGLTextureLibrary)
+
+GskGLGlyphLibrary *gsk_gl_glyph_library_new (GdkGLContext *context);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_GLYPH_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/next/gskgliconlibrary.c b/gsk/next/gskgliconlibrary.c
new file mode 100644
index 0000000000..64e800d115
--- /dev/null
+++ b/gsk/next/gskgliconlibrary.c
@@ -0,0 +1,50 @@
+/* gskgliconlibrary.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 "gskgliconlibraryprivate.h"
+
+struct _GskGLIconLibrary
+{
+  GskGLTextureLibrary parent_instance;
+};
+
+G_DEFINE_TYPE (GskGLIconLibrary, gsk_gl_icon_library, GSK_TYPE_GL_TEXTURE_LIBRARY)
+
+GskGLIconLibrary *
+gsk_gl_icon_library_new (GdkGLContext *context)
+{
+  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
+
+  return g_object_new (GSK_TYPE_GL_ICON_LIBRARY,
+                       "context", context,
+                       NULL);
+}
+
+static void
+gsk_gl_icon_library_class_init (GskGLIconLibraryClass *klass)
+{
+}
+
+static void
+gsk_gl_icon_library_init (GskGLIconLibrary *self)
+{
+}
diff --git a/gsk/next/gskgliconlibraryprivate.h b/gsk/next/gskgliconlibraryprivate.h
new file mode 100644
index 0000000000..c6b4363e33
--- /dev/null
+++ b/gsk/next/gskgliconlibraryprivate.h
@@ -0,0 +1,38 @@
+/* gskgliconlibraryprivate.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_GL_ICON_LIBRARY_PRIVATE_H__
+#define __GSK_GL_ICON_LIBRARY_PRIVATE_H__
+
+#include <pango/pango.h>
+
+#include "gskgltexturelibraryprivate.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_ICON_LIBRARY (gsk_gl_icon_library_get_type())
+
+G_DECLARE_FINAL_TYPE (GskGLIconLibrary, gsk_gl_icon_library, GSK, GL_ICON_LIBRARY, GskGLTextureLibrary)
+
+GskGLIconLibrary *gsk_gl_icon_library_new (GdkGLContext *context);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_ICON_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/next/gskglprogram.c b/gsk/next/gskglprogram.c
new file mode 100644
index 0000000000..2c13bb0b65
--- /dev/null
+++ b/gsk/next/gskglprogram.c
@@ -0,0 +1,361 @@
+/* gskglprogram.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 "gskglcommandqueueprivate.h"
+#include "gskglprogramprivate.h"
+#include "gskgluniformstateprivate.h"
+
+struct _GskGLProgram
+{
+  GObject               parent_instance;
+  int                   id;
+  char                 *name;
+  GArray               *uniform_locations;
+  GskGLCommandQueue   *command_queue;
+};
+
+G_DEFINE_TYPE (GskGLProgram, gsk_gl_program, G_TYPE_OBJECT)
+
+GskGLProgram *
+gsk_gl_program_new (GskGLCommandQueue *command_queue,
+                    const char        *name,
+                    int                program_id)
+{
+  GskGLProgram *self;
+
+  g_return_val_if_fail (program_id >= 0, NULL);
+
+  self = g_object_new (GSK_TYPE_GL_PROGRAM, NULL);
+  self->id = program_id;
+  self->name = g_strdup (name);
+  self->command_queue = g_object_ref (command_queue);
+
+  return self;
+}
+
+static void
+gsk_gl_program_finalize (GObject *object)
+{
+  GskGLProgram *self = (GskGLProgram *)object;
+
+  if (self->id >= 0)
+    g_warning ("Leaking GLSL program %d (%s)",
+               self->id,
+               self->name ? self->name : "");
+
+  g_clear_pointer (&self->name, g_free);
+  g_clear_pointer (&self->uniform_locations, g_array_unref);
+  g_clear_object (&self->command_queue);
+
+  G_OBJECT_CLASS (gsk_gl_program_parent_class)->finalize (object);
+}
+
+static void
+gsk_gl_program_class_init (GskGLProgramClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->finalize = gsk_gl_program_finalize;
+}
+
+static void
+gsk_gl_program_init (GskGLProgram *self)
+{
+  self->id = -1;
+  self->uniform_locations = g_array_new (FALSE, TRUE, sizeof (GLint));
+}
+
+/**
+ * gsk_gl_program_use:
+ * @self: a #GskGLProgram
+ *
+ * Sets @self as the current program.
+ */
+void
+gsk_gl_program_use (GskGLProgram *self)
+{
+  g_return_if_fail (GSK_IS_GL_PROGRAM (self));
+  g_return_if_fail (self->command_queue != NULL);
+
+  gsk_gl_command_queue_use_program (self->command_queue, self->id);
+}
+
+/**
+ * gsk_gl_program_unuse:
+ * @self: a #GskGLProgram
+ *
+ * Changes the program to 0 and cleans up any necessary state.
+ */
+void
+gsk_gl_program_unuse (GskGLProgram *self)
+{
+  g_return_if_fail (GSK_IS_GL_PROGRAM (self));
+
+  gsk_gl_command_queue_use_program (self->command_queue, 0);
+}
+
+/**
+ * gsk_gl_program_add_uniform:
+ * @self: a #GskGLProgram
+ * @name: the name of the uniform such as "u_source"
+ * @key: the identifier to use for the uniform
+ *
+ * This method will create a mapping between @key and the location
+ * of the uniform on the GPU. This simplifies calling code to not
+ * need to know where the uniform location is and only register it
+ * when creating the program.
+ *
+ * You might use this with an enum of all your uniforms for the
+ * program and then register each of them like:
+ *
+ * ```
+ * gsk_gl_program_add_uniform (program, "u_source", UNIFORM_SOURCE);
+ * ```
+ *
+ * That allows you to set values for the program with something
+ * like the following:
+ *
+ * ```
+ * gsk_gl_program_set_uniform1i (program, UNIFORM_SOURCE, 1);
+ * ```
+ *
+ * Returns: %TRUE if the uniform was found; otherwise %FALSE
+ */
+gboolean
+gsk_gl_program_add_uniform (GskGLProgram *self,
+                            const char   *name,
+                            guint         key)
+{
+  const GLint invalid = -1;
+  GLint location;
+
+  g_return_val_if_fail (GSK_IS_GL_PROGRAM (self), FALSE);
+  g_return_val_if_fail (name != NULL, FALSE);
+  g_return_val_if_fail (key < 1024, FALSE);
+
+  if (-1 == (location = glGetUniformLocation (self->id, name)))
+    return FALSE;
+
+  while (key >= self->uniform_locations->len)
+    g_array_append_val (self->uniform_locations, invalid);
+  g_array_index (self->uniform_locations, GLint, key) = location;
+
+  return TRUE;
+}
+
+static inline GLint
+get_uniform_location (GskGLProgram *self,
+                      guint         key)
+{
+  if G_LIKELY (key < self->uniform_locations->len)
+    return g_array_index (self->uniform_locations, GLint, key);
+  else
+    return -1;
+}
+
+/**
+ * gsk_gl_program_delete:
+ * @self: a #GskGLProgram
+ *
+ * Deletes the GLSL program.
+ *
+ * You must call gsk_gl_program_use() before and
+ * gsk_gl_program_unuse() after this function.
+ */
+void
+gsk_gl_program_delete (GskGLProgram *self)
+{
+  g_return_if_fail (GSK_IS_GL_PROGRAM (self));
+  g_return_if_fail (self->command_queue != NULL);
+
+  gsk_gl_command_queue_delete_program (self->command_queue, self->id);
+  self->id = -1;
+}
+
+void
+gsk_gl_program_set_uniform1i (GskGLProgram *self,
+                              guint         key,
+                              int           value0)
+{
+  gsk_gl_command_queue_set_uniform1i (self->command_queue,
+                                      self->id,
+                                      get_uniform_location (self, key),
+                                      value0);
+}
+
+void
+gsk_gl_program_set_uniform2i (GskGLProgram *self,
+                              guint         key,
+                              int           value0,
+                              int           value1)
+{
+  gsk_gl_command_queue_set_uniform2i (self->command_queue,
+                                      self->id,
+                                      get_uniform_location (self, key),
+                                      value0,
+                                      value1);
+}
+
+void
+gsk_gl_program_set_uniform3i (GskGLProgram *self,
+                              guint         key,
+                              int           value0,
+                              int           value1,
+                              int           value2)
+{
+  gsk_gl_command_queue_set_uniform3i (self->command_queue,
+                                      self->id,
+                                      get_uniform_location (self, key),
+                                      value0,
+                                      value1,
+                                      value2);
+}
+
+void
+gsk_gl_program_set_uniform4i (GskGLProgram *self,
+                              guint         key,
+                              int           value0,
+                              int           value1,
+                              int           value2,
+                              int           value3)
+{
+  gsk_gl_command_queue_set_uniform4i (self->command_queue,
+                                      self->id,
+                                      get_uniform_location (self, key),
+                                      value0,
+                                      value1,
+                                      value2,
+                                      value3);
+}
+
+void
+gsk_gl_program_set_uniform1f (GskGLProgram *self,
+                              guint         key,
+                              float         value0)
+{
+  gsk_gl_command_queue_set_uniform1f (self->command_queue,
+                                      self->id,
+                                      get_uniform_location (self, key),
+                                      value0);
+}
+
+void
+gsk_gl_program_set_uniform2f (GskGLProgram *self,
+                              guint         key,
+                              float         value0,
+                              float         value1)
+{
+  gsk_gl_command_queue_set_uniform2f (self->command_queue,
+                                      self->id,
+                                      get_uniform_location (self, key),
+                                      value0,
+                                      value1);
+}
+
+void
+gsk_gl_program_set_uniform3f (GskGLProgram *self,
+                              guint         key,
+                              float         value0,
+                              float         value1,
+                              float         value2)
+{
+  gsk_gl_command_queue_set_uniform3f (self->command_queue,
+                                      self->id,
+                                      get_uniform_location (self, key),
+                                      value0,
+                                      value1,
+                                      value2);
+}
+
+void
+gsk_gl_program_set_uniform4f (GskGLProgram *self,
+                              guint         key,
+                              float         value0,
+                              float         value1,
+                              float         value2,
+                              float         value3)
+{
+  gsk_gl_command_queue_set_uniform4f (self->command_queue,
+                                      self->id,
+                                      get_uniform_location (self, key),
+                                      value0,
+                                      value1,
+                                      value2,
+                                      value3);
+}
+
+void
+gsk_gl_program_set_uniform_color (GskGLProgram  *self,
+                                  guint          key,
+                                  const GdkRGBA *color)
+{
+  g_assert (GSK_IS_GL_PROGRAM (self));
+
+  gsk_gl_command_queue_set_uniform_color (self->command_queue,
+                                          self->id,
+                                          get_uniform_location (self, key),
+                                          color);
+}
+
+void
+gsk_gl_program_set_uniform_texture (GskGLProgram *self,
+                                    guint         key,
+                                    GLenum        texture_target,
+                                    GLenum        texture_slot,
+                                    guint         texture_id)
+{
+  g_assert (GSK_IS_GL_PROGRAM (self));
+  g_assert (texture_target == GL_TEXTURE_1D ||
+            texture_target == GL_TEXTURE_2D ||
+            texture_target == GL_TEXTURE_3D);
+  g_assert (texture_slot >= GL_TEXTURE0);
+  g_assert (texture_slot <= GL_TEXTURE16);
+
+  gsk_gl_command_queue_set_uniform_texture (self->command_queue,
+                                            self->id,
+                                            get_uniform_location (self, key),
+                                            texture_target,
+                                            texture_slot,
+                                            texture_id);
+}
+
+void
+gsk_gl_program_set_uniform_rounded_rect (GskGLProgram         *self,
+                                         guint                 key,
+                                         const GskRoundedRect *rounded_rect)
+{
+  g_assert (GSK_IS_GL_PROGRAM (self));
+
+  gsk_gl_command_queue_set_uniform_rounded_rect (self->command_queue,
+                                                 self->id,
+                                                 get_uniform_location (self, key),
+                                                 rounded_rect);
+}
+
+GskGLDrawVertex *
+gsk_gl_program_draw (GskGLProgram          *self,
+                     const GskGLDrawVertex  vertices[6])
+{
+  g_assert (GSK_IS_GL_PROGRAM (self));
+
+  return gsk_gl_command_queue_draw (self->command_queue, vertices);
+}
diff --git a/gsk/next/gskglprogramprivate.h b/gsk/next/gskglprogramprivate.h
new file mode 100644
index 0000000000..e223a5263a
--- /dev/null
+++ b/gsk/next/gskglprogramprivate.h
@@ -0,0 +1,93 @@
+/* gskglprogramprivate.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_GL_PROGRAM_H__
+#define __GSK_GL_PROGRAM_H__
+
+#include "gskgltypes.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_PROGRAM (gsk_gl_program_get_type())
+
+G_DECLARE_FINAL_TYPE (GskGLProgram, gsk_gl_program, GSK, GL_PROGRAM, GObject)
+
+GskGLProgram    *gsk_gl_program_new                      (GskGLCommandQueue    *command_queue,
+                                                          const char           *name,
+                                                          int                   program_id);
+gboolean         gsk_gl_program_add_uniform              (GskGLProgram         *self,
+                                                          const char           *name,
+                                                          guint                 key);
+void             gsk_gl_program_use                      (GskGLProgram         *self);
+void             gsk_gl_program_unuse                    (GskGLProgram         *self);
+void             gsk_gl_program_delete                   (GskGLProgram         *self);
+void             gsk_gl_program_set_uniform1i            (GskGLProgram         *self,
+                                                          guint                 key,
+                                                          int                   value0);
+void             gsk_gl_program_set_uniform2i            (GskGLProgram         *self,
+                                                          guint                 key,
+                                                          int                   value0,
+                                                          int                   value1);
+void             gsk_gl_program_set_uniform3i            (GskGLProgram         *self,
+                                                          guint                 key,
+                                                          int                   value0,
+                                                          int                   value1,
+                                                          int                   value2);
+void             gsk_gl_program_set_uniform4i            (GskGLProgram         *self,
+                                                          guint                 key,
+                                                          int                   value0,
+                                                          int                   value1,
+                                                          int                   value2,
+                                                          int                   value3);
+void             gsk_gl_program_set_uniform1f            (GskGLProgram         *self,
+                                                          guint                 key,
+                                                          float                 value0);
+void             gsk_gl_program_set_uniform2f            (GskGLProgram         *self,
+                                                          guint                 key,
+                                                          float                 value0,
+                                                          float                 value1);
+void             gsk_gl_program_set_uniform3f            (GskGLProgram         *self,
+                                                          guint                 key,
+                                                          float                 value0,
+                                                          float                 value1,
+                                                          float                 value2);
+void             gsk_gl_program_set_uniform4f            (GskGLProgram         *self,
+                                                          guint                 key,
+                                                          float                 value0,
+                                                          float                 value1,
+                                                          float                 value2,
+                                                          float                 value3);
+void             gsk_gl_program_set_uniform_color        (GskGLProgram         *self,
+                                                          guint                 key,
+                                                          const GdkRGBA        *color);
+void             gsk_gl_program_set_uniform_texture      (GskGLProgram         *self,
+                                                          guint                 key,
+                                                          GLenum                texture_target,
+                                                          GLenum                texture_slot,
+                                                          guint                 texture_id);
+void             gsk_gl_program_set_uniform_rounded_rect (GskGLProgram         *self,
+                                                          guint                 key,
+                                                          const GskRoundedRect *rounded_rect);
+GskGLDrawVertex *gsk_gl_program_draw                     (GskGLProgram          *self,
+                                                          const GskGLDrawVertex  vertices[6]);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_PROGRAM_H__ */
diff --git a/gsk/next/gskglprograms.defs b/gsk/next/gskglprograms.defs
new file mode 100644
index 0000000000..1e075bea81
--- /dev/null
+++ b/gsk/next/gskglprograms.defs
@@ -0,0 +1,85 @@
+GSK_GL_DEFINE_PROGRAM (blend,
+                       "/org/gtk/libgsk/glsl/blend.glsl",
+                       GSK_GL_ADD_UNIFORM (1, BLEND_SOURCE2, u_source2)
+                       GSK_GL_ADD_UNIFORM (2, BLEND_MODE, u_mode))
+
+GSK_GL_DEFINE_PROGRAM (blit,
+                       "/org/gtk/libgsk/glsl/blit.glsl",
+                       GSK_GL_NO_UNIFORMS)
+
+GSK_GL_DEFINE_PROGRAM (blur,
+                       "/org/gtk/libgsk/glsl/blur.glsl",
+                       GSK_GL_ADD_UNIFORM (1, BLUR_RADIUS, u_blur_radius)
+                       GSK_GL_ADD_UNIFORM (2, BLUR_SIZE, u_blur_size)
+                       GSK_GL_ADD_UNIFORM (3, BLUR_DIR, u_blur_dir))
+
+GSK_GL_DEFINE_PROGRAM (border,
+                       "/org/gtk/libgsk/glsl/border.glsl",
+                       GSK_GL_ADD_UNIFORM (1, BORDER_COLOR, u_color)
+                       GSK_GL_ADD_UNIFORM (2, BORDER_WIDTHS, u_widths)
+                       GSK_GL_ADD_UNIFORM (3, BORDER_OUTLINE_RECT, u_outline_rect))
+
+GSK_GL_DEFINE_PROGRAM (color,
+                       "/org/gtk/libgsk/glsl/color.glsl",
+                       GSK_GL_ADD_UNIFORM (1, COLOR_COLOR, u_color))
+
+GSK_GL_DEFINE_PROGRAM (coloring,
+                       "/org/gtk/libgsk/glsl/coloring.glsl",
+                       GSK_GL_ADD_UNIFORM (1, COLORING_COLOR, u_color))
+
+GSK_GL_DEFINE_PROGRAM (color_matrix,
+                       "/org/gtk/libgsk/glsl/color_matrix.glsl",
+                       GSK_GL_ADD_UNIFORM (1, COLOR_MATRIX_COLOR_MATRIX, u_color_matrix)
+                       GSK_GL_ADD_UNIFORM (2, COLOR_MATRIX_COLOR_OFFSET, u_color_offset))
+
+GSK_GL_DEFINE_PROGRAM (conic_gradient,
+                       "/org/gtk/libgsk/glsl/conic_gradient.glsl",
+                       GSK_GL_ADD_UNIFORM (1, CONIC_GRADIENT_COLOR_STOPS, u_color_stops)
+                       GSK_GL_ADD_UNIFORM (2, CONIC_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
+                       GSK_GL_ADD_UNIFORM (3, CONIC_GRADIENT_CENTER, u_center)
+                       GSK_GL_ADD_UNIFORM (4, CONIC_GRADIENT_ROTATION, u_rotation))
+
+GSK_GL_DEFINE_PROGRAM (cross_fade,
+                       "/org/gtk/libgsk/glsl/cross_fade.glsl",
+                       GSK_GL_ADD_UNIFORM (1, CROSS_FADE_PROGRESS, u_progress)
+                       GSK_GL_ADD_UNIFORM (2, CROSS_FADE_SOURCE2, u_source2))
+
+GSK_GL_DEFINE_PROGRAM (inset_shadow,
+                       "/org/gtk/libgsk/glsl/inset_shadow.glsl",
+                       GSK_GL_ADD_UNIFORM (1, INSET_SHADOW_COLOR, u_color)
+                       GSK_GL_ADD_UNIFORM (2, INSET_SHADOW_SPREAD, u_spread)
+                       GSK_GL_ADD_UNIFORM (3, INSET_SHADOW_OFFSET, u_offset)
+                       GSK_GL_ADD_UNIFORM (4, INSET_SHADOW_OUTLINE_RECT, u_outline_rect))
+
+GSK_GL_DEFINE_PROGRAM (linear_gradient,
+                       "/org/gtk/libgsk/glsl/linear_gradient.glsl",
+                       GSK_GL_ADD_UNIFORM (1, LINEAR_GRADIENT_COLOR_STOPS, u_color_stops)
+                       GSK_GL_ADD_UNIFORM (2, LINEAR_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
+                       GSK_GL_ADD_UNIFORM (3, LINEAR_GRADIENT_START_POINT, u_start_point)
+                       GSK_GL_ADD_UNIFORM (4, LINEAR_GRADIENT_END_POINT, u_end_point))
+
+GSK_GL_DEFINE_PROGRAM (outset_shadow,
+                       "/org/gtk/libgsk/glsl/outset_shadow.glsl",
+                       GSK_GL_ADD_UNIFORM (1, OUTSET_SHADOW_COLOR, u_color)
+                       GSK_GL_ADD_UNIFORM (2, OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect))
+
+GSK_GL_DEFINE_PROGRAM (radial_gradient,
+                       "/org/gtk/libgsk/glsl/radial_gradient.glsl",
+                       GSK_GL_ADD_UNIFORM (1, RADIAL_GRADIENT_COLOR_STOPS, u_color_stops)
+                       GSK_GL_ADD_UNIFORM (2, RADIAL_GRADIENT_NUM_COLOR_STOPS, u_num_color_stops)
+                       GSK_GL_ADD_UNIFORM (3, RADIAL_GRADIENT_CENTER, u_center)
+                       GSK_GL_ADD_UNIFORM (4, RADIAL_GRADIENT_START, u_start)
+                       GSK_GL_ADD_UNIFORM (5, RADIAL_GRADIENT_END, u_end)
+                       GSK_GL_ADD_UNIFORM (6, RADIAL_GRADIENT_RADIUS, u_radius))
+
+GSK_GL_DEFINE_PROGRAM (repeat,
+                       "/org/gtk/libgsk/glsl/repeat.glsl",
+                       GSK_GL_ADD_UNIFORM (1, REPEAT_CHILD_BOUNDS, u_child_bounds)
+                       GSK_GL_ADD_UNIFORM (2, REPEAT_EXTURE_RECT, u_texture_rect))
+
+GSK_GL_DEFINE_PROGRAM (unblurred_outset_shadow,
+                       "/org/gtk/libgsk/glsl/unblurred_outset_shadow.glsl",
+                       GSK_GL_ADD_UNIFORM (1, UNBLURRED_OUTSET_SHADOW_COLOR, u_color)
+                       GSK_GL_ADD_UNIFORM (1, UNBLURRED_OUTSET_SHADOW_SPREAD, u_spread)
+                       GSK_GL_ADD_UNIFORM (1, UNBLURRED_OUTSET_SHADOW_OFFSET, u_offset)
+                       GSK_GL_ADD_UNIFORM (2, UNBLURRED_OUTSET_SHADOW_OUTLINE_RECT, u_outline_rect))
diff --git a/gsk/next/gskglrenderer.c b/gsk/next/gskglrenderer.c
new file mode 100644
index 0000000000..19c67e671e
--- /dev/null
+++ b/gsk/next/gskglrenderer.c
@@ -0,0 +1,310 @@
+/* gskglrenderer.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 <gsk/gskdebugprivate.h>
+#include <gsk/gskrendererprivate.h>
+
+#include "gskglcommandqueueprivate.h"
+#include "gskgldriverprivate.h"
+#include "gskglprogramprivate.h"
+#include "gskglrenderjobprivate.h"
+#include "gskglrenderer.h"
+
+struct _GskNextRendererClass
+{
+  GskRendererClass parent_class;
+};
+
+struct _GskNextRenderer
+{
+  GskRenderer parent_instance;
+
+  /* The GskGLCommandQueue manages how we send all drawing operations,
+   * uniform changes, and other texture related operations to the GPU.
+   * It maintains a cache of state to help reduce the number of state
+   * changes we send to the GPU. Furthermore, it can reorder batches so
+   * that we switch programs and uniforms less frequently by verifying
+   * what batches can be reordered based on clipping and stacking.
+   */
+  GskGLCommandQueue *command_queue;
+
+  /* The driver manages our program state and command queues. It gives
+   * us a single place to manage loading all the programs as well which
+   * unfortunately cannot be shared across all renderers for a display.
+   * (Context sharing explicitly does not name program/uniform state as
+   * shareable even though on some implementations it is).
+   */
+  GskNextDriver *driver;
+};
+
+G_DEFINE_TYPE (GskNextRenderer, gsk_next_renderer, GSK_TYPE_RENDERER)
+
+GskRenderer *
+gsk_next_renderer_new (void)
+{
+  return g_object_new (GSK_TYPE_NEXT_RENDERER, NULL);
+}
+
+static gboolean
+gsk_next_renderer_realize (GskRenderer  *renderer,
+                           GdkSurface   *surface,
+                           GError      **error)
+{
+  GskNextRenderer *self = (GskNextRenderer *)renderer;
+  GskGLCommandQueue *command_queue = NULL;
+  GdkGLContext *context = NULL;
+  GskNextDriver *driver = NULL;
+  gboolean ret = FALSE;
+  gboolean debug = FALSE;
+
+  g_assert (GSK_IS_NEXT_RENDERER (self));
+  g_assert (GDK_IS_SURFACE (surface));
+
+  if (!(context = gdk_surface_create_gl_context (surface, error)) ||
+      !gdk_gl_context_realize (context, error))
+    goto failure;
+
+  gdk_gl_context_make_current (context);
+
+  command_queue = gsk_gl_command_queue_new (context);
+
+#ifdef G_ENABLE_DEBUG
+  if (GSK_RENDERER_DEBUG_CHECK (GSK_RENDERER (self), SHADERS))
+    debug = TRUE;
+#endif
+
+  if (!(driver = gsk_next_driver_new (command_queue, debug, error)))
+    goto failure;
+
+  self->command_queue = g_steal_pointer (&command_queue);
+  self->driver = g_steal_pointer (&driver);
+
+  ret = TRUE;
+
+failure:
+  g_clear_object (&driver);
+  g_clear_object (&context);
+  g_clear_object (&command_queue);
+
+  return ret;
+}
+
+static void
+gsk_next_renderer_unrealize (GskRenderer *renderer)
+{
+  GskNextRenderer *self = (GskNextRenderer *)renderer;
+
+  g_assert (GSK_IS_NEXT_RENDERER (renderer));
+
+  g_clear_object (&self->driver);
+  g_clear_object (&self->command_queue);
+}
+
+typedef struct _GskGLTextureState
+{
+  GdkGLContext *context;
+  GLuint        texture_id;
+} GskGLTextureState;
+
+static void
+create_texture_from_texture_destroy (gpointer data)
+{
+  GskGLTextureState *state = data;
+
+  g_assert (state != NULL);
+  g_assert (GDK_IS_GL_CONTEXT (state->context));
+
+  gdk_gl_context_make_current (state->context);
+  glDeleteTextures (1, &state->texture_id);
+  g_clear_object (&state->context);
+  g_slice_free (GskGLTextureState, state);
+}
+
+static GdkTexture *
+create_texture_from_texture (GdkGLContext *context,
+                             GLuint        texture_id,
+                             int           width,
+                             int           height)
+{
+  GskGLTextureState *state;
+
+  g_assert (GDK_IS_GL_CONTEXT (context));
+
+  state = g_slice_new0 (GskGLTextureState);
+  state->texture_id = texture_id;
+  state->context = g_object_ref (context);
+
+  return gdk_gl_texture_new (context,
+                             texture_id,
+                             width,
+                             height,
+                             create_texture_from_texture_destroy,
+                             state);
+}
+
+static cairo_region_t *
+get_render_region (GdkSurface   *surface,
+                   GdkGLContext *context)
+{
+  const cairo_region_t *damage;
+  GdkRectangle whole_surface;
+  GdkRectangle extents;
+  float scale_factor;
+
+  g_assert (GDK_IS_SURFACE (surface));
+  g_assert (GDK_IS_GL_CONTEXT (context));
+
+  scale_factor = gdk_surface_get_scale_factor (surface);
+
+  whole_surface.x = 0;
+  whole_surface.y = 0;
+  whole_surface.width = gdk_surface_get_width (surface) * scale_factor;
+  whole_surface.height = gdk_surface_get_height (surface) * scale_factor;
+
+  damage = gdk_draw_context_get_frame_region (GDK_DRAW_CONTEXT (context));
+
+  /* NULL means everything in this case, and ensures that we
+   * don't setup any complicated clips for full scene redraw.
+   */
+  if (damage == NULL ||
+      cairo_region_contains_rectangle (damage, &whole_surface) == CAIRO_REGION_OVERLAP_IN)
+    return NULL;
+
+  /* If the extents match the full-scene, do the same as above */
+  cairo_region_get_extents (damage, &extents);
+  if (gdk_rectangle_equal (&extents, &whole_surface))
+    return NULL;
+
+  /* Draw clipped to the bounding-box of the region */
+  return cairo_region_create_rectangle (&extents);
+}
+
+static void
+gsk_gl_renderer_render (GskRenderer          *renderer,
+                        GskRenderNode        *root,
+                        const cairo_region_t *update_area)
+{
+  GskNextRenderer *self = (GskNextRenderer *)renderer;
+  cairo_region_t *render_region;
+  graphene_rect_t viewport;
+  GskGLRenderJob *job;
+  GdkGLContext *context;
+  GdkSurface *surface;
+  float scale_factor;
+
+  g_assert (GSK_IS_NEXT_RENDERER (renderer));
+  g_assert (root != NULL);
+
+  context = gsk_gl_command_queue_get_context (self->command_queue);
+  surface = gdk_draw_context_get_surface (GDK_DRAW_CONTEXT (context));
+  scale_factor = gdk_surface_get_scale_factor (surface);
+  render_region = get_render_region (surface, context);
+
+  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;
+
+  job = gsk_gl_render_job_new (self->driver,
+                               root,
+                               &viewport,
+                               scale_factor,
+                               render_region,
+                               0,
+                               FALSE);
+
+  gdk_draw_context_begin_frame (GDK_DRAW_CONTEXT (context), update_area);
+  gsk_gl_render_job_run (job);
+  gdk_draw_context_end_frame (GDK_DRAW_CONTEXT (context));
+
+  gsk_gl_render_job_free (job);
+
+  cairo_region_destroy (render_region);
+}
+
+static GdkTexture *
+gsk_gl_renderer_render_texture (GskRenderer           *renderer,
+                                GskRenderNode         *root,
+                                const graphene_rect_t *viewport)
+{
+  GskNextRenderer *self = (GskNextRenderer *)renderer;
+  GskGLRenderJob *job;
+  GdkGLContext *context;
+  GLuint fbo_id;
+  GLuint texture_id;
+  int width;
+  int height;
+
+  g_assert (GSK_IS_NEXT_RENDERER (renderer));
+  g_assert (root != NULL);
+
+  context = gsk_gl_command_queue_get_context (self->command_queue);
+  width = ceilf (viewport->size.width);
+  height = ceilf (viewport->size.height);
+
+  if (!gsk_next_driver_create_render_target (self->driver,
+                                             width,
+                                             height,
+                                             &fbo_id,
+                                             &texture_id))
+    return NULL;
+
+  gsk_gl_command_queue_autorelease_framebuffer (self->command_queue, fbo_id);
+
+  job = gsk_gl_render_job_new (self->driver, root, viewport, 1, NULL, fbo_id, TRUE);
+  gsk_gl_render_job_run (job);
+  gsk_gl_render_job_free (job);
+
+  return create_texture_from_texture (context, texture_id, width, height);
+}
+
+static void
+gsk_next_renderer_dispose (GObject *object)
+{
+#ifdef G_ENABLE_DEBUG
+  GskNextRenderer *self = (GskNextRenderer *)object;
+
+  g_assert (self->command_queue == NULL);
+  g_assert (self->driver == NULL);
+#endif
+
+  G_OBJECT_CLASS (gsk_next_renderer_parent_class)->dispose (object);
+}
+
+static void
+gsk_next_renderer_class_init (GskNextRendererClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+  GskRendererClass *renderer_class = GSK_RENDERER_CLASS (klass);
+
+  object_class->dispose = gsk_next_renderer_dispose;
+
+  renderer_class->realize = gsk_next_renderer_realize;
+  renderer_class->unrealize = gsk_next_renderer_unrealize;
+  renderer_class->render = gsk_gl_renderer_render;
+  renderer_class->render_texture = gsk_gl_renderer_render_texture;
+}
+
+static void
+gsk_next_renderer_init (GskNextRenderer *self)
+{
+}
diff --git a/gsk/next/gskglrenderer.h b/gsk/next/gskglrenderer.h
new file mode 100644
index 0000000000..c2e2354f06
--- /dev/null
+++ b/gsk/next/gskglrenderer.h
@@ -0,0 +1,47 @@
+/* gskglrendererprivate.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_NEXT_RENDERER_PRIVATE_H__
+#define __GSK_NEXT_RENDERER_PRIVATE_H__
+
+#include <gsk/gskrenderer.h>
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_NEXT_RENDERER (gsk_next_renderer_get_type ())
+
+#define GSK_NEXT_RENDERER(obj)                    (G_TYPE_CHECK_INSTANCE_CAST ((obj), 
GSK_TYPE_NEXT_RENDERER, GskNextRenderer))
+#define GSK_IS_NEXT_RENDERER(obj)                 (G_TYPE_CHECK_INSTANCE_TYPE ((obj), 
GSK_TYPE_NEXT_RENDERER))
+#define GSK_NEXT_RENDERER_CLASS(klass)            (G_TYPE_CHECK_CLASS_CAST ((klass), GSK_TYPE_NEXT_RENDERER, 
GskNextRendererClass))
+#define GSK_IS_NEXT_RENDERER_CLASS(klass)         (G_TYPE_CHECK_CLASS_TYPE ((klass), GSK_TYPE_NEXT_RENDERER))
+#define GSK_NEXT_RENDERER_GET_CLASS(obj)          (G_TYPE_INSTANCE_GET_CLASS ((obj), GSK_TYPE_NEXT_RENDERER, 
GskNextRendererClass))
+
+typedef struct _GskNextRenderer                   GskNextRenderer;
+typedef struct _GskNextRendererClass              GskNextRendererClass;
+
+GDK_AVAILABLE_IN_ALL
+GType                   gsk_next_renderer_get_type                (void) G_GNUC_CONST;
+
+GDK_AVAILABLE_IN_ALL
+GskRenderer *           gsk_next_renderer_new                     (void);
+
+G_END_DECLS
+
+#endif /* __GSK_NEXT_RENDERER_PRIVATE_H__ */
diff --git a/gsk/next/gskglrenderjob.c b/gsk/next/gskglrenderjob.c
new file mode 100644
index 0000000000..e0d8393cfa
--- /dev/null
+++ b/gsk/next/gskglrenderjob.c
@@ -0,0 +1,124 @@
+/* gskglrenderjob.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 "gskglcommandqueueprivate.h"
+#include "gskgldriverprivate.h"
+#include "gskglrenderjobprivate.h"
+
+#define ORTHO_NEAR_PLANE -10000
+#define ORTHO_FAR_PLANE   10000
+
+struct _GskGLRenderJob
+{
+  GskNextDriver     *driver;
+  GskRenderNode     *root;
+  cairo_region_t    *region;
+  guint              framebuffer;
+  float              scale_factor;
+  graphene_rect_t    viewport;
+  graphene_matrix_t  projection;
+  GArray            *modelview;
+  GArray            *clip;
+  guint              flip_y : 1;
+};
+
+typedef struct _GskGLRenderClip
+{
+  GskRoundedRect rect;
+  bool           is_rectilinear;
+} GskGLRenderClip;
+
+typedef struct _GskGLRenderModelview
+{
+  GskTransform *transform;
+  guint         n_repeated;
+} GskGLRenderModelview;
+
+static void
+init_projection_matrix (graphene_matrix_t     *projection,
+                        const graphene_rect_t *viewport,
+                        gboolean               flip_y)
+{
+  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);
+
+  if (!flip_y)
+    graphene_matrix_scale (projection, 1, -1, 1);
+}
+
+GskGLRenderJob *
+gsk_gl_render_job_new (GskNextDriver         *driver,
+                       GskRenderNode         *root,
+                       const graphene_rect_t *viewport,
+                       float                  scale_factor,
+                       const cairo_region_t  *region,
+                       guint                  framebuffer,
+                       gboolean               flip_y)
+{
+  GskGLRenderJob *job;
+
+  g_return_val_if_fail (GSK_IS_NEXT_DRIVER (driver), NULL);
+  g_return_val_if_fail (root != NULL, NULL);
+  g_return_val_if_fail (viewport != NULL, NULL);
+  g_return_val_if_fail (scale_factor > 0, NULL);
+
+  job = g_slice_new0 (GskGLRenderJob);
+  job->driver = g_object_ref (driver);
+  job->root = gsk_render_node_ref (root);
+  job->modelview = g_array_new (FALSE, FALSE, sizeof (GskGLRenderModelview));
+  job->clip = g_array_new (FALSE, FALSE, sizeof (GskGLRenderClip));
+  job->framebuffer = framebuffer;
+  job->scale_factor = scale_factor;
+  job->viewport = *viewport;
+  job->region = region ? cairo_region_copy (region) : NULL;
+  job->flip_y = !!flip_y;
+
+  return job;
+}
+
+void
+gsk_gl_render_job_free (GskGLRenderJob *job)
+{
+  g_clear_object (&job->driver);
+  g_clear_pointer (&job->root, gsk_render_node_unref);
+  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 (GskGLRenderJob, job);
+}
+
+void
+gsk_gl_render_job_run (GskGLRenderJob *job)
+{
+  g_return_if_fail (job != NULL);
+  g_return_if_fail (GSK_IS_NEXT_DRIVER (job->driver));
+
+  gsk_next_driver_begin_frame (job->driver);
+  gsk_next_driver_end_frame (job->driver);
+}
diff --git a/gsk/next/gskglrenderjobprivate.h b/gsk/next/gskglrenderjobprivate.h
new file mode 100644
index 0000000000..3d3440c80c
--- /dev/null
+++ b/gsk/next/gskglrenderjobprivate.h
@@ -0,0 +1,36 @@
+/* gskglrenderjobprivate.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_GL_RENDER_JOB_H__
+#define __GSK_GL_RENDER_JOB_H__
+
+#include "gskgltypes.h"
+
+GskGLRenderJob *gsk_gl_render_job_new  (GskNextDriver         *driver,
+                                        GskRenderNode         *root,
+                                        const graphene_rect_t *viewport,
+                                        float                  scale_factor,
+                                        const cairo_region_t  *region,
+                                        guint                  framebuffer,
+                                        gboolean               flip_y);
+void            gsk_gl_render_job_free (GskGLRenderJob        *job);
+void            gsk_gl_render_job_run  (GskGLRenderJob        *job);
+
+#endif /* __GSK_GL_RENDER_JOB_H__ */
diff --git a/gsk/next/gskglshadowlibrary.c b/gsk/next/gskglshadowlibrary.c
new file mode 100644
index 0000000000..af611adba7
--- /dev/null
+++ b/gsk/next/gskglshadowlibrary.c
@@ -0,0 +1,50 @@
+/* gskglshadowlibrary.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 "gskglshadowlibraryprivate.h"
+
+struct _GskGLShadowLibrary
+{
+  GskGLTextureLibrary parent_instance;
+};
+
+G_DEFINE_TYPE (GskGLShadowLibrary, gsk_gl_shadow_library, GSK_TYPE_GL_TEXTURE_LIBRARY)
+
+GskGLShadowLibrary *
+gsk_gl_shadow_library_new (GdkGLContext *context)
+{
+  g_return_val_if_fail (GDK_IS_GL_CONTEXT (context), NULL);
+
+  return g_object_new (GSK_TYPE_GL_SHADOW_LIBRARY,
+                       "context", context,
+                       NULL);
+}
+
+static void
+gsk_gl_shadow_library_class_init (GskGLShadowLibraryClass *klass)
+{
+}
+
+static void
+gsk_gl_shadow_library_init (GskGLShadowLibrary *self)
+{
+}
diff --git a/gsk/next/gskglshadowlibraryprivate.h b/gsk/next/gskglshadowlibraryprivate.h
new file mode 100644
index 0000000000..3493e6d93d
--- /dev/null
+++ b/gsk/next/gskglshadowlibraryprivate.h
@@ -0,0 +1,42 @@
+/* gskglshadowlibraryprivate.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_GL_SHADOW_LIBRARY_PRIVATE_H__
+#define __GSK_GL_SHADOW_LIBRARY_PRIVATE_H__
+
+#include "gskgltexturelibraryprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GskGLShadowKey
+{
+  GskRoundedRect rounded_rect;
+  float blur_radius;
+} GskGLShadowKey;
+
+#define GSK_TYPE_GL_SHADOW_LIBRARY (gsk_gl_shadow_library_get_type())
+
+G_DECLARE_FINAL_TYPE (GskGLShadowLibrary, gsk_gl_shadow_library, GSK, GL_SHADOW_LIBRARY, GskGLTextureLibrary)
+
+GskGLShadowLibrary *gsk_gl_shadow_library_new (GdkGLContext *context);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_SHADOW_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/next/gskgltextureatlas.c b/gsk/next/gskgltextureatlas.c
new file mode 100644
index 0000000000..d0b98e89bc
--- /dev/null
+++ b/gsk/next/gskgltextureatlas.c
@@ -0,0 +1,46 @@
+/* gskgltextureatlas.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 "gskgltextureatlasprivate.h"
+
+struct _GskGLTextureAtlas
+{
+  GObject parent_instance;
+};
+
+G_DEFINE_TYPE (GskGLTextureAtlas, gsk_gl_texture_atlas, G_TYPE_OBJECT)
+
+GskGLTextureAtlas *
+gsk_gl_texture_atlas_new (void)
+{
+  return g_object_new (GSK_TYPE_GL_TEXTURE_ATLAS, NULL);
+}
+
+static void
+gsk_gl_texture_atlas_class_init (GskGLTextureAtlasClass *klass)
+{
+}
+
+static void
+gsk_gl_texture_atlas_init (GskGLTextureAtlas *self)
+{
+}
diff --git a/gsk/next/gskgltextureatlasprivate.h b/gsk/next/gskgltextureatlasprivate.h
new file mode 100644
index 0000000000..0350d1fe91
--- /dev/null
+++ b/gsk/next/gskgltextureatlasprivate.h
@@ -0,0 +1,36 @@
+/* gskgltextureatlasprivate.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_GL_TEXTURE_ATLAS_PRIVATE_H__
+#define __GSK_GL_TEXTURE_ATLAS_PRIVATE_H__
+
+#include "gskgltypes.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_TEXTURE_ATLAS (gsk_gl_texture_atlas_get_type())
+
+G_DECLARE_FINAL_TYPE (GskGLTextureAtlas, gsk_gl_texture_atlas, GSK, GL_TEXTURE_ATLAS, GObject)
+
+GskGLTextureAtlas *gsk_gl_texture_atlas_new (void);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_TEXTURE_ATLAS_PRIVATE_H__ */
diff --git a/gsk/next/gskgltexturelibrary.c b/gsk/next/gskgltexturelibrary.c
new file mode 100644
index 0000000000..25ff5599ac
--- /dev/null
+++ b/gsk/next/gskgltexturelibrary.c
@@ -0,0 +1,156 @@
+/* gskgltexturelibrary.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 "gskgltextureatlasprivate.h"
+#include "gskgltexturelibraryprivate.h"
+
+typedef struct
+{
+  guint  hash;
+  guint  len;
+  guint8 key[0];
+} Entry;
+
+typedef struct
+{
+  GdkGLContext *context;
+  GHashFunc hash_func;
+  GEqualFunc equal_func;
+} GskGLTextureLibraryPrivate;
+
+G_DEFINE_ABSTRACT_TYPE_WITH_PRIVATE (GskGLTextureLibrary, gsk_gl_texture_library, G_TYPE_OBJECT)
+
+enum {
+  PROP_0,
+  PROP_CONTEXT,
+  N_PROPS
+};
+
+static GParamSpec *properties [N_PROPS];
+
+static void
+gsk_gl_texture_library_dispose (GObject *object)
+{
+  GskGLTextureLibrary *self = (GskGLTextureLibrary *)object;
+  GskGLTextureLibraryPrivate *priv = gsk_gl_texture_library_get_instance_private (self);
+
+  g_clear_object (&priv->context);
+
+  G_OBJECT_CLASS (gsk_gl_texture_library_parent_class)->dispose (object);
+}
+
+static void
+gsk_gl_texture_library_get_property (GObject    *object,
+                                     guint       prop_id,
+                                     GValue     *value,
+                                     GParamSpec *pspec)
+{
+  GskGLTextureLibrary *self = GSK_GL_TEXTURE_LIBRARY (object);
+  GskGLTextureLibraryPrivate *priv = gsk_gl_texture_library_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_CONTEXT:
+      g_value_set_object (value, priv->context);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gsk_gl_texture_library_set_property (GObject      *object,
+                                     guint         prop_id,
+                                     const GValue *value,
+                                     GParamSpec   *pspec)
+{
+  GskGLTextureLibrary *self = GSK_GL_TEXTURE_LIBRARY (object);
+  GskGLTextureLibraryPrivate *priv = gsk_gl_texture_library_get_instance_private (self);
+
+  switch (prop_id)
+    {
+    case PROP_CONTEXT:
+      priv->context = g_value_dup_object (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+    }
+}
+
+static void
+gsk_gl_texture_library_class_init (GskGLTextureLibraryClass *klass)
+{
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+  object_class->dispose = gsk_gl_texture_library_dispose;
+  object_class->get_property = gsk_gl_texture_library_get_property;
+  object_class->set_property = gsk_gl_texture_library_set_property;
+
+  properties [PROP_CONTEXT] =
+    g_param_spec_object ("context",
+                         "Context",
+                         "Context",
+                         GDK_TYPE_GL_CONTEXT,
+                         (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_properties (object_class, N_PROPS, properties);
+}
+
+static void
+gsk_gl_texture_library_init (GskGLTextureLibrary *self)
+{
+  GskGLTextureLibraryPrivate *priv = gsk_gl_texture_library_get_instance_private (self);
+
+  priv->hash_func = g_direct_hash;
+  priv->equal_func = g_direct_equal;
+}
+
+GdkGLContext *
+gsk_gl_texture_library_get_context (GskGLTextureLibrary *self)
+{
+  GskGLTextureLibraryPrivate *priv = gsk_gl_texture_library_get_instance_private (self);
+
+  g_return_val_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self), NULL);
+  g_return_val_if_fail (GDK_IS_GL_CONTEXT (priv->context), NULL);
+
+  return priv->context;
+}
+
+void
+gsk_gl_texture_library_begin_frame (GskGLTextureLibrary *self)
+{
+  g_return_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self));
+
+  if (GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame)
+    GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->begin_frame (self);
+}
+
+void
+gsk_gl_texture_library_end_frame (GskGLTextureLibrary *self)
+{
+  g_return_if_fail (GSK_IS_GL_TEXTURE_LIBRARY (self));
+
+  if (GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->end_frame)
+    GSK_GL_TEXTURE_LIBRARY_GET_CLASS (self)->end_frame (self);
+}
diff --git a/gsk/next/gskgltexturelibraryprivate.h b/gsk/next/gskgltexturelibraryprivate.h
new file mode 100644
index 0000000000..9f8d002e58
--- /dev/null
+++ b/gsk/next/gskgltexturelibraryprivate.h
@@ -0,0 +1,61 @@
+/* gskgltexturelibraryprivate.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_GL_TEXTURE_LIBRARY_PRIVATE_H__
+#define __GSK_GL_TEXTURE_LIBRARY_PRIVATE_H__
+
+#include "gskgltypes.h"
+
+G_BEGIN_DECLS
+
+#define GSK_TYPE_GL_TEXTURE_LIBRARY (gsk_gl_texture_library_get_type())
+
+G_DECLARE_DERIVABLE_TYPE (GskGLTextureLibrary, gsk_gl_texture_library, GSK, GL_TEXTURE_LIBRARY, GObject)
+
+struct _GskGLTextureLibraryClass
+{
+  GObjectClass parent_class;
+
+  void (*begin_frame) (GskGLTextureLibrary *library);
+  void (*end_frame)   (GskGLTextureLibrary *library);
+};
+
+GdkGLContext *gsk_gl_texture_library_get_context (GskGLTextureLibrary  *self);
+void          gsk_gl_texture_library_set_funcs   (GHashFunc             hash_func,
+                                                  GEqualFunc            equal_func);
+void          gsk_gl_texture_library_begin_frame (GskGLTextureLibrary  *self);
+void          gsk_gl_texture_library_end_frame   (GskGLTextureLibrary  *self);
+gboolean      gsk_gl_texture_library_pack        (GskGLTextureLibrary  *self,
+                                                  gconstpointer         key,
+                                                  gsize                 keylen,
+                                                  int                   width,
+                                                  int                   height,
+                                                  GskGLTextureAtlas   **atlas,
+                                                  int                  *atlas_x,
+                                                  int                  *atlas_y);
+gboolean      gsk_gl_texture_library_lookup      (GskGLTextureLibrary  *library,
+                                                  gconstpointer         key,
+                                                  GskGLTextureAtlas   **atlas,
+                                                  int                  *atlas_x,
+                                                  int                  *atlas_y);
+
+G_END_DECLS
+
+#endif /* __GSK_GL_TEXTURE_LIBRARY_PRIVATE_H__ */
diff --git a/gsk/next/gskgltypes.h b/gsk/next/gskgltypes.h
new file mode 100644
index 0000000000..232786438e
--- /dev/null
+++ b/gsk/next/gskgltypes.h
@@ -0,0 +1,53 @@
+/* gskgltypes.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_GL_TYPES_H
+#define GSK_GL_TYPES_H
+
+#include <graphene.h>
+#include <epoxy/gl.h>
+#include <gdk/gdk.h>
+#include <gsk/gsk.h>
+
+G_BEGIN_DECLS
+
+typedef struct _GskGLAttachmentState GskGLAttachmentState;
+typedef struct _GskGLCommandQueue GskGLCommandQueue;
+typedef struct _GskGLCompiler GskGLCompiler;
+typedef struct _GskGLDrawVertex GskGLDrawVertex;
+typedef struct _GskGLGlyphLibrary GskGLGlyphLibrary;
+typedef struct _GskGLIconLibrary GskGLIconLibrary;
+typedef struct _GskGLProgram GskGLProgram;
+typedef struct _GskGLShadowLibrary GskGLShadowLibrary;
+typedef struct _GskGLTextureAtlas GskGLTextureAtlas;
+typedef struct _GskGLTextureLibrary GskGLTextureLibrary;
+typedef struct _GskGLUniformState GskGLUniformState;
+typedef struct _GskNextDriver GskNextDriver;
+typedef struct _GskGLRenderJob GskGLRenderJob;
+
+struct _GskGLDrawVertex
+{
+  float position[2];
+  float uv[2];
+};
+
+G_END_DECLS
+
+#endif /* GSK_GL_TYPES_H */
diff --git a/gsk/next/gskgluniformstate.c b/gsk/next/gskgluniformstate.c
new file mode 100644
index 0000000000..c079b2ac1e
--- /dev/null
+++ b/gsk/next/gskgluniformstate.c
@@ -0,0 +1,746 @@
+/* gskgluniformstate.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 <epoxy/gl.h>
+#include <string.h>
+
+#include "gskgluniformstateprivate.h"
+
+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;
+
+static guint8 uniform_sizes[] = {
+  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 (guint),
+
+  sizeof (graphene_matrix_t),
+  sizeof (GskRoundedRect),
+  sizeof (GdkRGBA),
+
+  0,
+};
+
+#define REPLACE_UNIFORM(info, u, format, count)                                          \
+  G_STMT_START {                                                                         \
+    guint offset;                                                                        \
+    u = alloc_uniform_data(state->uniform_data, uniform_sizes[format] * count, &offset); \
+    (info)->offset = offset;                                                             \
+  } G_STMT_END
+
+typedef struct
+{
+  GArray *uniform_info;
+  guint   n_changed;
+} ProgramInfo;
+
+GskGLUniformState *
+gsk_gl_uniform_state_new (void)
+{
+  GskGLUniformState *state;
+
+  state = g_new0 (GskGLUniformState, 1);
+  state->program_info = g_array_new (FALSE, TRUE, sizeof (ProgramInfo));
+  state->uniform_data = g_byte_array_new ();
+
+  return g_steal_pointer (&state);
+}
+
+void
+gsk_gl_uniform_state_free (GskGLUniformState *state)
+{
+  g_clear_pointer (&state->program_info, g_array_unref);
+  g_clear_pointer (&state->uniform_data, g_byte_array_unref);
+  g_free (state);
+}
+
+static inline void
+program_changed (GskGLUniformState *state,
+                 GskGLUniformInfo  *info,
+                 guint              program)
+{
+  if (!info->changed)
+    {
+      info->changed = TRUE;
+      g_array_index (state->program_info, ProgramInfo, program).n_changed++;
+    }
+}
+
+void
+gsk_gl_uniform_state_clear_program (GskGLUniformState *state,
+                                    guint              program)
+{
+  ProgramInfo *program_info;
+
+  g_assert (state != NULL);
+
+  if (program == 0 || program >= state->program_info->len)
+    return;
+
+  program_info = &g_array_index (state->program_info, ProgramInfo, program);
+  program_info->n_changed = 0;
+  g_clear_pointer (&program_info->uniform_info, g_array_unref);
+}
+
+static gpointer
+alloc_uniform_data (GByteArray *buffer,
+                    guint       size,
+                    guint      *offset)
+{
+  guint align = size > 4 ? GLIB_SIZEOF_VOID_P : 4;
+  guint masked = buffer->len & (align - 1);
+
+  /* Try to give a more natural alignment based on the size
+   * of the uniform. In case it's greater than 4 try to at least
+   * give us an 8-byte alignment to be sure we can dereference
+   * without a memcpy().
+   */
+  if (masked != 0)
+    {
+      guint prefix_align = align - masked;
+      g_byte_array_set_size (buffer, buffer->len + prefix_align);
+    }
+
+  *offset = buffer->len;
+  g_byte_array_set_size (buffer, buffer->len + size);
+
+  g_assert ((*offset & (align - 1)) == 0);
+
+  return (gpointer)&buffer->data[*offset];
+}
+
+static gpointer
+get_uniform (GskGLUniformState  *state,
+             guint               program,
+             GskGLUniformFormat  format,
+             guint               array_count,
+             guint               location,
+             GskGLUniformInfo  **infoptr)
+{
+  ProgramInfo *program_info;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+  g_assert (array_count < 256);
+  g_assert ((int)format >= 0 && format < GSK_GL_UNIFORM_FORMAT_LAST);
+  g_assert (location < GL_MAX_UNIFORM_LOCATIONS);
+
+  /* Fast path for common case (state already initialized) */
+  if G_LIKELY (program < state->program_info->len &&
+               (program_info = &g_array_index (state->program_info, ProgramInfo, program)) &&
+               program_info->uniform_info != NULL &&
+               location < program_info->uniform_info->len)
+    {
+      info = &g_array_index (program_info->uniform_info, GskGLUniformInfo, location);
+
+      if G_LIKELY (format == info->format && array_count <= info->array_count)
+        {
+          *infoptr = info;
+          return state->uniform_data->data + info->offset;
+        }
+      else
+        {
+          g_critical ("Attempt to access uniform with different type of value "
+                      "than it was initialized with. Program %u Location %u.",
+                      program, location);
+          *infoptr = NULL;
+          return NULL;
+        }
+    }
+  else
+    {
+      guint offset;
+
+      if (program >= state->program_info->len ||
+          g_array_index (state->program_info, ProgramInfo, program).uniform_info == NULL)
+        {
+          if (program >= state->program_info->len)
+            g_array_set_size (state->program_info, program + 1);
+
+          program_info = &g_array_index (state->program_info, ProgramInfo, program);
+          program_info->uniform_info = g_array_new (FALSE, TRUE, sizeof (GskGLUniformInfo));
+          program_info->n_changed = 0;
+        }
+
+      g_assert (program_info != NULL);
+      g_assert (program_info->uniform_info != NULL);
+
+      if (location >= program_info->uniform_info->len)
+        g_array_set_size (program_info->uniform_info, location + 1);
+
+      alloc_uniform_data (state->uniform_data, uniform_sizes[format] * array_count, &offset);
+
+      info = &g_array_index (program_info->uniform_info, GskGLUniformInfo, location);
+      info->changed = TRUE;
+      info->format = format;
+      info->offset = offset;
+      info->array_count = 0;
+
+      *infoptr = info;
+
+      return state->uniform_data->data + offset;
+    }
+}
+
+void
+gsk_gl_uniform_state_set1f (GskGLUniformState *state,
+                            guint              program,
+                            guint              location,
+                            float              value0)
+{
+  Uniform1f *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_1F, 1, location, &info)))
+    {
+      if (u->v0 != value0)
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_1F, 1);
+          u->v0 = value0;
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_set2f (GskGLUniformState *state,
+                            guint              program,
+                            guint              location,
+                            float              value0,
+                            float              value1)
+{
+  Uniform2f *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_2F, 1, location, &info)))
+    {
+      if (u->v0 != value0 || u->v1 != value1)
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_2F, 1);
+          u->v0 = value0;
+          u->v1 = value1;
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_set3f (GskGLUniformState *state,
+                            guint              program,
+                            guint              location,
+                            float              value0,
+                            float              value1,
+                            float              value2)
+{
+  Uniform3f *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_3F, 1, location, &info)))
+    {
+      if (u->v0 != value0 || u->v1 != value1 || u->v2 != value2)
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_3F, 1);
+          u->v0 = value0;
+          u->v1 = value1;
+          u->v2 = value2;
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_set4f (GskGLUniformState *state,
+                            guint              program,
+                            guint              location,
+                            float              value0,
+                            float              value1,
+                            float              value2,
+                            float              value3)
+{
+  Uniform4f *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_4F, 1, location, &info)))
+    {
+      if (u->v0 != value0 || u->v1 != value1 || u->v2 != value2 || u->v3 != value3)
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_4F, 1);
+          u->v0 = value0;
+          u->v1 = value1;
+          u->v2 = value2;
+          u->v3 = value3;
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_set1i (GskGLUniformState *state,
+                            guint              program,
+                            guint              location,
+                            int                value0)
+{
+  Uniform1i *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_1I, 1, location, &info)))
+    {
+      if (u->v0 != value0)
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_1I, 1);
+          u->v0 = value0;
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_set2i (GskGLUniformState *state,
+                            guint              program,
+                            guint              location,
+                            int                value0,
+                            int                value1)
+{
+  Uniform2i *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_2I, 1, location, &info)))
+    {
+      if (u->v0 != value0 || u->v1 != value1)
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_2I, 1);
+          u->v0 = value0;
+          u->v1 = value1;
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_set3i (GskGLUniformState *state,
+                            guint              program,
+                            guint              location,
+                            int                value0,
+                            int                value1,
+                            int                value2)
+{
+  Uniform3i *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_3I, 1, location, &info)))
+    {
+      if (u->v0 != value0 || u->v1 != value1 || u->v2 != value2)
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_3I, 1);
+          u->v0 = value0;
+          u->v1 = value1;
+          u->v2 = value2;
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_set4i (GskGLUniformState *state,
+                            guint              program,
+                            guint              location,
+                            int                value0,
+                            int                value1,
+                            int                value2,
+                            int                value3)
+{
+  Uniform4i *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_4I, 1, location, &info)))
+    {
+      if (u->v0 != value0 || u->v1 != value1 || u->v2 != value2 || u->v3 != value3)
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_4I, 1);
+          u->v0 = value0;
+          u->v1 = value1;
+          u->v2 = value2;
+          u->v3 = value3;
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_set_rounded_rect (GskGLUniformState    *state,
+                                       guint                 program,
+                                       guint                 location,
+                                       const GskRoundedRect *rounded_rect)
+{
+  GskRoundedRect *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+  g_assert (rounded_rect != NULL);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT, 1, location, &info)))
+    {
+      if (!gsk_rounded_rect_equal (rounded_rect, u))
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT, 1);
+
+          if (!(info->flags & GSK_GL_UNIFORM_FLAGS_SEND_CORNERS))
+            {
+              if (!graphene_size_equal (&u->corner[0], &rounded_rect->corner[0]) ||
+                  !graphene_size_equal (&u->corner[1], &rounded_rect->corner[1]) ||
+                  !graphene_size_equal (&u->corner[2], &rounded_rect->corner[2]) ||
+                  !graphene_size_equal (&u->corner[3], &rounded_rect->corner[3]))
+                info->flags |= GSK_GL_UNIFORM_FLAGS_SEND_CORNERS;
+            }
+
+          memcpy (u, rounded_rect, sizeof *rounded_rect);
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_set_matrix (GskGLUniformState       *state,
+                                 guint                    program,
+                                 guint                    location,
+                                 const graphene_matrix_t *matrix)
+{
+  graphene_matrix_t *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+  g_assert (matrix != NULL);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_MATRIX, 1, location, &info)))
+    {
+      if (graphene_matrix_equal_fast (u, matrix))
+        return;
+
+      if (!graphene_matrix_equal (u, matrix))
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_MATRIX, 1);
+          memcpy (u, matrix, sizeof *matrix);
+          program_changed (state, info, program);
+        }
+    }
+}
+
+/**
+ * gsk_gl_uniform_state_set_texture:
+ * @state: a #GskGLUniformState
+ * @program: the program id
+ * @location: the location of the texture
+ * @texture_slot: a texturing slot such as GL_TEXTURE0
+ *
+ * Sets the uniform expecting a texture to @texture_slot. This API
+ * expects a texture slot such as GL_TEXTURE0 to reduce chances of
+ * miss-use by the caller.
+ *
+ * The value stored to the uniform is in the form of 0 for GL_TEXTURE0,
+ * 1 for GL_TEXTURE1, and so on.
+ */
+void
+gsk_gl_uniform_state_set_texture (GskGLUniformState *state,
+                                  guint              program,
+                                  guint              location,
+                                  guint              texture_slot)
+{
+  GskGLUniformInfo *info;
+  guint *u;
+
+  g_assert (texture_slot >= GL_TEXTURE0);
+  g_assert (texture_slot < GL_TEXTURE16);
+
+  texture_slot -= GL_TEXTURE0;
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_TEXTURE, 1, location, &info)))
+    {
+      if (*u != texture_slot)
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_TEXTURE, 1);
+          *u = texture_slot;
+          program_changed (state, info, program);
+        }
+    }
+}
+
+/**
+ * gsk_gl_uniform_state_set_color:
+ * @state: a #GskGLUniformState
+ * @program: a program id > 0
+ * @location: the uniform location
+ * @color: a color to set or %NULL for transparent
+ *
+ * Sets a uniform to the color described by @color. This is a convenience
+ * function to allow callers to avoid having to translate colors to floats
+ * in other portions of the renderer.
+ */
+void
+gsk_gl_uniform_state_set_color (GskGLUniformState *state,
+                                guint              program,
+                                guint              location,
+                                const GdkRGBA     *color)
+{
+  static const GdkRGBA transparent = {0};
+  GskGLUniformInfo *info;
+  GdkRGBA *u;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_COLOR, 1, location, &info)))
+    {
+      if (color == NULL)
+        color = &transparent;
+
+      if (!gdk_rgba_equal (u, color))
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_COLOR, 1);
+          memcpy (u, color, sizeof *color);
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_set1fv (GskGLUniformState *state,
+                             guint              program,
+                             guint              location,
+                             guint              count,
+                             const float       *value)
+{
+  Uniform1f *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_1F, count, location, &info)))
+    {
+      gboolean changed = memcmp (u, value, sizeof (Uniform1f) * count) != 0;
+
+      if (changed)
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_1F, count);
+          memcpy (u, value, sizeof (Uniform1f) * count);
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_set2fv (GskGLUniformState *state,
+                             guint              program,
+                             guint              location,
+                             guint              count,
+                             const float       *value)
+{
+  Uniform2f *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_2F, count, location, &info)))
+    {
+      gboolean changed = memcmp (u, value, sizeof (Uniform2f) * count) != 0;
+
+      if (changed)
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_2F, count);
+          memcpy (u, value, sizeof (Uniform2f) * count);
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_set3fv (GskGLUniformState *state,
+                             guint              program,
+                             guint              location,
+                             guint              count,
+                             const float       *value)
+{
+  Uniform3f *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_3F, count, location, &info)))
+    {
+      gboolean changed = memcmp (u, value, sizeof (Uniform3f) * count) != 0;
+
+      if (changed)
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_3F, count);
+          memcpy (u, value, sizeof (Uniform3f) * count);
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_set4fv (GskGLUniformState *state,
+                             guint              program,
+                             guint              location,
+                             guint              count,
+                             const float       *value)
+{
+  Uniform4f *u;
+  GskGLUniformInfo *info;
+
+  g_assert (state != NULL);
+  g_assert (program > 0);
+
+  if ((u = get_uniform (state, program, GSK_GL_UNIFORM_FORMAT_4F, count, location, &info)))
+    {
+      gboolean changed = memcmp (u, value, sizeof (Uniform4f) * count) != 0;
+
+      if (changed)
+        {
+          REPLACE_UNIFORM (info, u, GSK_GL_UNIFORM_FORMAT_4F, count);
+          memcpy (u, value, sizeof (Uniform4f) * count);
+          program_changed (state, info, program);
+        }
+    }
+}
+
+void
+gsk_gl_uniform_state_snapshot (GskGLUniformState         *state,
+                               guint                      program_id,
+                               GskGLUniformStateCallback  callback,
+                               gpointer                   user_data)
+{
+  ProgramInfo *program_info;
+
+  g_assert (state != NULL);
+  g_assert (program_id > 0);
+
+  if (program_id >= state->program_info->len)
+    return;
+
+  program_info = &g_array_index (state->program_info, ProgramInfo, program_id);
+  if (program_info->n_changed == 0 || program_info->uniform_info == NULL)
+    return;
+
+  for (guint i = 0; i < program_info->uniform_info->len; i++)
+    {
+      GskGLUniformInfo *info = &g_array_index (program_info->uniform_info, GskGLUniformInfo, i);
+
+      if (!info->changed)
+        continue;
+
+      callback (info, i, user_data);
+
+      info->changed = FALSE;
+      info->flags = 0;
+    }
+
+  program_info->n_changed = 0;
+}
+
+void
+gsk_gl_uniform_state_end_frame (GskGLUniformState *state)
+{
+  GByteArray *buffer;
+
+  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. We just create a new byte array
+   * that contains the new data with the gaps removed.
+   */
+
+  buffer = g_byte_array_sized_new (4096);
+
+  for (guint i = 0; i < state->program_info->len; i++)
+    {
+      ProgramInfo *program_info = &g_array_index (state->program_info, ProgramInfo, i);
+
+      if (program_info->uniform_info == NULL)
+        continue;
+
+      for (guint j = 0; j < program_info->uniform_info->len; j++)
+        {
+          GskGLUniformInfo *info = &g_array_index (program_info->uniform_info, GskGLUniformInfo, j);
+          guint size = uniform_sizes[info->format] * info->array_count;
+          guint offset;
+
+          alloc_uniform_data (buffer, size, &offset);
+          memcpy (&buffer->data[offset], &state->uniform_data->data[info->offset], size);
+          info->changed = FALSE;
+          info->offset = offset;
+        }
+    }
+
+  g_clear_pointer (&state->uniform_data, g_byte_array_unref);
+  state->uniform_data = buffer;
+}
diff --git a/gsk/next/gskgluniformstateprivate.h b/gsk/next/gskgluniformstateprivate.h
new file mode 100644
index 0000000000..2fea9dbd3e
--- /dev/null
+++ b/gsk/next/gskgluniformstateprivate.h
@@ -0,0 +1,189 @@
+/* gskgluniformstateprivate.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_GL_UNIFORM_STATE_PRIVATE_H
+#define GSK_GL_UNIFORM_STATE_PRIVATE_H
+
+#include "gskgltypes.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GskGLUniformState
+{
+  GArray     *program_info;
+  GByteArray *uniform_data;
+} GskGLUniformState;
+
+typedef struct _GskGLUniformInfo
+{
+  guint changed : 1;
+  guint format : 5;
+  guint array_count : 6;
+  guint flags : 4;
+  guint offset : 16;
+} GskGLUniformInfo;
+
+G_STATIC_ASSERT (sizeof (GskGLUniformInfo) == 4);
+
+/**
+ * GskGLUniformStateCallback:
+ * @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 (*GskGLUniformStateCallback) (const GskGLUniformInfo *info,
+                                           guint                   location,
+                                           gpointer                user_data);
+
+typedef enum _GskGLUniformFlags
+{
+  GSK_GL_UNIFORM_FLAGS_SEND_CORNERS = 1 << 0,
+} GskGLUniformFlags;
+
+typedef enum _GskGLUniformKind
+{
+  GSK_GL_UNIFORM_FORMAT_1F = 1,
+  GSK_GL_UNIFORM_FORMAT_2F,
+  GSK_GL_UNIFORM_FORMAT_3F,
+  GSK_GL_UNIFORM_FORMAT_4F,
+
+  GSK_GL_UNIFORM_FORMAT_1FV,
+  GSK_GL_UNIFORM_FORMAT_2FV,
+  GSK_GL_UNIFORM_FORMAT_3FV,
+  GSK_GL_UNIFORM_FORMAT_4FV,
+
+  GSK_GL_UNIFORM_FORMAT_1I,
+  GSK_GL_UNIFORM_FORMAT_2I,
+  GSK_GL_UNIFORM_FORMAT_3I,
+  GSK_GL_UNIFORM_FORMAT_4I,
+
+  GSK_GL_UNIFORM_FORMAT_TEXTURE,
+
+  GSK_GL_UNIFORM_FORMAT_MATRIX,
+  GSK_GL_UNIFORM_FORMAT_ROUNDED_RECT,
+  GSK_GL_UNIFORM_FORMAT_COLOR,
+
+  GSK_GL_UNIFORM_FORMAT_LAST
+} GskGLUniformFormat;
+
+GskGLUniformState *gsk_gl_uniform_state_new              (void);
+void               gsk_gl_uniform_state_clear_program    (GskGLUniformState         *state,
+                                                          guint                      program);
+void               gsk_gl_uniform_state_end_frame        (GskGLUniformState         *state);
+void               gsk_gl_uniform_state_set1f            (GskGLUniformState         *state,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          float                      value0);
+void               gsk_gl_uniform_state_set2f            (GskGLUniformState         *state,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          float                      value0,
+                                                          float                      value1);
+void               gsk_gl_uniform_state_set3f            (GskGLUniformState         *state,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          float                      value0,
+                                                          float                      value1,
+                                                          float                      value2);
+void               gsk_gl_uniform_state_set4f            (GskGLUniformState         *state,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          float                      value0,
+                                                          float                      value1,
+                                                          float                      value2,
+                                                          float                      value3);
+void               gsk_gl_uniform_state_set1fv           (GskGLUniformState         *state,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          guint                      count,
+                                                          const float               *value);
+void               gsk_gl_uniform_state_set2fv           (GskGLUniformState         *state,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          guint                      count,
+                                                          const float               *value);
+void               gsk_gl_uniform_state_set3fv           (GskGLUniformState         *state,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          guint                      count,
+                                                          const float               *value);
+void               gsk_gl_uniform_state_set4fv           (GskGLUniformState         *state,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          guint                      count,
+                                                          const float               *value);
+void               gsk_gl_uniform_state_set1i            (GskGLUniformState         *state,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          int                        value0);
+void               gsk_gl_uniform_state_set2i            (GskGLUniformState         *state,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          int                        value0,
+                                                          int                        value1);
+void               gsk_gl_uniform_state_set3i            (GskGLUniformState         *state,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          int                        value0,
+                                                          int                        value1,
+                                                          int                        value2);
+void               gsk_gl_uniform_state_set4i            (GskGLUniformState         *state,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          int                        value0,
+                                                          int                        value1,
+                                                          int                        value2,
+                                                          int                        value3);
+void               gsk_gl_uniform_state_set_rounded_rect (GskGLUniformState         *self,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          const GskRoundedRect      *rect);
+void               gsk_gl_uniform_state_set_matrix       (GskGLUniformState         *self,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          const graphene_matrix_t   *value);
+void               gsk_gl_uniform_state_set_texture      (GskGLUniformState         *self,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          guint                      texture_slot);
+void               gsk_gl_uniform_state_set_color        (GskGLUniformState         *self,
+                                                          guint                      program,
+                                                          guint                      location,
+                                                          const GdkRGBA             *color);
+void               gsk_gl_uniform_state_snapshot         (GskGLUniformState         *self,
+                                                          guint                      program_id,
+                                                          GskGLUniformStateCallback  callback,
+                                                          gpointer                   user_data);
+void               gsk_gl_uniform_state_free             (GskGLUniformState         *state);
+
+static inline gconstpointer
+gsk_gl_uniform_state_get_uniform_data (GskGLUniformState *state,
+                                       guint              offset)
+{
+  return (gconstpointer)&state->uniform_data->data[offset];
+}
+
+G_END_DECLS
+
+#endif /* GSK_GL_UNIFORM_STATE_PRIVATE_H */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]