vulkan: Invent a new abstraction
authorBenjamin Otte <otte@redhat.com>
Sat, 24 Jun 2023 02:00:02 +0000 (04:00 +0200)
committerBenjamin Otte <otte@redhat.com>
Sun, 16 Jul 2023 10:12:36 +0000 (12:12 +0200)
GskVulkanOp is meant to be a proper abstraction of operations
the Vulkan renderer will be doing.

For now it's an atrocious clunky piece of junk wedged into the
renderpass codebase.

It's so temporary that I didn't even adjust indentation of the code.

gsk/meson.build
gsk/vulkan/gskvulkanop.c [new file with mode: 0644]
gsk/vulkan/gskvulkanopprivate.h [new file with mode: 0644]
gsk/vulkan/gskvulkanrenderpass.c

index 90908881d89af6e101aa97f7429a5c5938789471..bfec80f6c0833e1bbd2c77c75472a6af1f249c08 100644 (file)
@@ -115,21 +115,22 @@ if have_vulkan
     'vulkan/gskvulkanclip.c',
     'vulkan/gskvulkancolorpipeline.c',
     'vulkan/gskvulkancolortextpipeline.c',
-    'vulkan/gskvulkancrossfadepipeline.c',
     'vulkan/gskvulkancommandpool.c',
+    'vulkan/gskvulkancrossfadepipeline.c',
     'vulkan/gskvulkaneffectpipeline.c',
     'vulkan/gskvulkanglyphcache.c',
-    'vulkan/gskvulkanlineargradientpipeline.c',
     'vulkan/gskvulkanimage.c',
-    'vulkan/gskvulkantextpipeline.c',
-    'vulkan/gskvulkantexturepipeline.c',
+    'vulkan/gskvulkanlineargradientpipeline.c',
     'vulkan/gskvulkanmemory.c',
+    'vulkan/gskvulkanop.c',
     'vulkan/gskvulkanpipeline.c',
     'vulkan/gskvulkanpushconstants.c',
     'vulkan/gskvulkanrender.c',
     'vulkan/gskvulkanrenderer.c',
     'vulkan/gskvulkanrenderpass.c',
     'vulkan/gskvulkanshader.c',
+    'vulkan/gskvulkantextpipeline.c',
+    'vulkan/gskvulkantexturepipeline.c',
   ])
 
   subdir('vulkan/resources')
diff --git a/gsk/vulkan/gskvulkanop.c b/gsk/vulkan/gskvulkanop.c
new file mode 100644 (file)
index 0000000..6c8aaaa
--- /dev/null
@@ -0,0 +1,58 @@
+#include "config.h"
+
+#include "gskvulkanopprivate.h"
+
+void
+gsk_vulkan_op_finish (GskVulkanOp *op)
+{
+  op->op_class->finish (op);
+}
+
+void
+gsk_vulkan_op_upload (GskVulkanOp           *op,
+                      GskVulkanRenderPass   *pass,
+                      GskVulkanRender       *render,
+                      GskVulkanUploader     *uploader,
+                      const graphene_rect_t *clip,
+                      const graphene_vec2_t *scale)
+{
+  op->op_class->upload (op, pass, render, uploader, clip, scale);
+}
+
+gsize
+gsk_vulkan_op_count_vertex_data (GskVulkanOp *op,
+                                 gsize        n_bytes)
+{
+  return op->op_class->count_vertex_data (op, n_bytes);
+}
+
+void
+gsk_vulkan_op_collect_vertex_data (GskVulkanOp         *op,
+                                   GskVulkanRenderPass *pass,
+                                   GskVulkanRender     *render,
+                                   guchar              *data)
+{
+  op->op_class->collect_vertex_data (op, pass, render, data);
+}
+
+void
+gsk_vulkan_op_reserve_descriptor_sets (GskVulkanOp     *op,
+                                       GskVulkanRender *render)
+{
+  op->op_class->reserve_descriptor_sets (op, render);
+}
+
+GskVulkanPipeline *
+gsk_vulkan_op_get_pipeline (GskVulkanOp *op)
+{
+  return op->op_class->get_pipeline (op);
+}
+
+void
+gsk_vulkan_op_command (GskVulkanOp      *op,
+                       VkPipelineLayout  pipeline_layout,
+                       VkCommandBuffer   command_buffer)
+{
+  op->op_class->command (op, pipeline_layout, command_buffer);
+}
+
diff --git a/gsk/vulkan/gskvulkanopprivate.h b/gsk/vulkan/gskvulkanopprivate.h
new file mode 100644 (file)
index 0000000..e808bf4
--- /dev/null
@@ -0,0 +1,65 @@
+#pragma once
+
+#include <gdk/gdk.h>
+
+#include "gskvulkanrenderpassprivate.h"
+
+G_BEGIN_DECLS
+
+typedef struct _GskVulkanOp GskVulkanOp;
+typedef struct _GskVulkanOpClass GskVulkanOpClass;
+
+struct _GskVulkanOp
+{
+  const GskVulkanOpClass *op_class;
+};
+
+struct _GskVulkanOpClass
+{
+  gsize                 size;
+
+  void                  (* finish)                                      (GskVulkanOp            *op);
+
+  void                  (* upload)                                      (GskVulkanOp            *op,
+                                                                         GskVulkanRenderPass    *pass,
+                                                                         GskVulkanRender        *render,
+                                                                         GskVulkanUploader      *uploader,
+                                                                         const graphene_rect_t  *clip,
+                                                                         const graphene_vec2_t  *scale);
+  gsize                 (* count_vertex_data)                           (GskVulkanOp            *op,
+                                                                         gsize                   n_bytes);
+  void                  (* collect_vertex_data)                         (GskVulkanOp            *op,
+                                                                         GskVulkanRenderPass    *pass,
+                                                                         GskVulkanRender        *render,
+                                                                         guchar                 *data);
+  void                  (* reserve_descriptor_sets)                     (GskVulkanOp            *op,
+                                                                         GskVulkanRender        *render);
+  GskVulkanPipeline *   (* get_pipeline)                                (GskVulkanOp            *op);
+  void                  (* command)                                     (GskVulkanOp            *op,
+                                                                         VkPipelineLayout        pipeline_layout,
+                                                                         VkCommandBuffer         command_buffer);
+};
+
+void                    gsk_vulkan_op_finish                            (GskVulkanOp            *op);
+
+void                    gsk_vulkan_op_upload                            (GskVulkanOp            *op,
+                                                                         GskVulkanRenderPass    *pass,
+                                                                         GskVulkanRender        *render,
+                                                                         GskVulkanUploader      *uploader,
+                                                                         const graphene_rect_t  *clip,
+                                                                         const graphene_vec2_t  *scale);
+gsize                   gsk_vulkan_op_count_vertex_data                 (GskVulkanOp            *op,
+                                                                         gsize                   n_bytes);
+void                    gsk_vulkan_op_collect_vertex_data               (GskVulkanOp            *op,
+                                                                         GskVulkanRenderPass    *pass,
+                                                                         GskVulkanRender        *render,
+                                                                         guchar                 *data);
+void                    gsk_vulkan_op_reserve_descriptor_sets           (GskVulkanOp            *op,
+                                                                         GskVulkanRender        *render);
+GskVulkanPipeline *     gsk_vulkan_op_get_pipeline                      (GskVulkanOp            *op);
+void                    gsk_vulkan_op_command                           (GskVulkanOp            *op,
+                                                                         VkPipelineLayout        pipeline_layout,
+                                                                         VkCommandBuffer         command_buffer);
+
+G_END_DECLS
+
index 2b6ab9fc936b83a20678fbc374d71d629ff54a47..f0ffc0dea0624f13abafe3b1f28358924ddc2d70 100644 (file)
@@ -19,6 +19,7 @@
 #include "gskvulkancrossfadepipelineprivate.h"
 #include "gskvulkaneffectpipelineprivate.h"
 #include "gskvulkanlineargradientpipelineprivate.h"
+#include "gskvulkanopprivate.h"
 #include "gskvulkantextpipelineprivate.h"
 #include "gskvulkantexturepipelineprivate.h"
 #include "gskvulkanimageprivate.h"
@@ -39,7 +40,8 @@
 
 typedef struct _GskVulkanParseState GskVulkanParseState;
 
-typedef union _GskVulkanOp GskVulkanOp;
+typedef struct _GskVulkanOpAny GskVulkanOpAny;
+typedef union  _GskVulkanOpAll GskVulkanOpAll;
 typedef struct _GskVulkanOpRender GskVulkanOpRender;
 typedef struct _GskVulkanOpText GskVulkanOpText;
 typedef struct _GskVulkanOpPushConstants GskVulkanOpPushConstants;
@@ -75,6 +77,7 @@ typedef enum {
 /* render ops with 0, 1 or 2 sources */
 struct _GskVulkanOpRender
 {
+  GskVulkanOp          base;
   GskVulkanOpType      type;
   GskRenderNode       *node; /* node that's the source of this op */
   graphene_point_t     offset; /* offset of the node */
@@ -92,6 +95,7 @@ struct _GskVulkanOpRender
 
 struct _GskVulkanOpText
 {
+  GskVulkanOp          base;
   GskVulkanOpType      type;
   GskRenderNode       *node; /* node that's the source of this op */
   graphene_point_t     offset; /* offset of the node */
@@ -108,6 +112,7 @@ struct _GskVulkanOpText
 
 struct _GskVulkanOpPushConstants
 {
+  GskVulkanOp             base;
   GskVulkanOpType         type;
   GskRenderNode          *node; /* node that's the source of this op */
   graphene_vec2_t         scale;
@@ -117,14 +122,22 @@ struct _GskVulkanOpPushConstants
 
 struct _GskVulkanOpScissor
 {
+  GskVulkanOp             base;
   GskVulkanOpType         type;
   GskRenderNode          *node; /* node that's the source of this op */
   cairo_rectangle_int_t   rect;
 };
 
-union _GskVulkanOp
+struct _GskVulkanOpAny
 {
-  GskVulkanOpType          type;
+  GskVulkanOp             base;
+  GskVulkanOpType         type;
+  GskRenderNode          *node; /* node that's the source of this op */
+};
+
+union _GskVulkanOpAll
+{
+  GskVulkanOpAny          any;
   GskVulkanOpRender        render;
   GskVulkanOpText          text;
   GskVulkanOpPushConstants constants;
@@ -282,14 +295,7 @@ gsk_vulkan_render_pass_free (GskVulkanRenderPass *self)
 
 static void
 gsk_vulkan_render_pass_add_op (GskVulkanRenderPass *self,
-                               GskVulkanOp         *op)
-{
-  gsk_vulkan_render_ops_splice (&self->render_ops,
-                                gsk_vulkan_render_ops_get_size (&self->render_ops),
-                                0, FALSE,
-                                (guchar *) op,
-                                sizeof (GskVulkanOp));
-}
+                               GskVulkanOp         *op);
 
 static void
 gsk_vulkan_render_pass_append_scissor (GskVulkanRenderPass       *self,
@@ -1567,11 +1573,32 @@ gsk_vulkan_render_pass_upload (GskVulkanRenderPass  *self,
   const graphene_rect_t *clip = NULL;
   const graphene_vec2_t *scale = NULL;
 
-  for (i = 0; i < gsk_vulkan_render_ops_get_size (&self->render_ops); i += sizeof (GskVulkanOp))
+  for (i = 0; i < gsk_vulkan_render_ops_get_size (&self->render_ops); i += op->op_class->size)
     {
       op = (GskVulkanOp *) gsk_vulkan_render_ops_index (&self->render_ops, i);
 
-      switch (op->type)
+      gsk_vulkan_op_upload (op, self, render, uploader, clip, scale);
+
+      if (((GskVulkanOpAny *) op)->type == GSK_VULKAN_OP_PUSH_VERTEX_CONSTANTS)
+        {
+          GskVulkanOpPushConstants *constants = (GskVulkanOpPushConstants *) op;
+          clip = &constants->clip.bounds;
+          scale = &constants->scale;
+        }
+    }
+}
+
+static void
+gsk_vulkan_render_op_upload (GskVulkanOp           *op_,
+                             GskVulkanRenderPass   *self,
+                             GskVulkanRender       *render,
+                             GskVulkanUploader     *uploader,
+                             const graphene_rect_t *clip,
+                             const graphene_vec2_t *scale)
+{
+  GskVulkanOpAll *op = (GskVulkanOpAll *) op_;
+
+      switch (op->any.type)
         {
         case GSK_VULKAN_OP_FALLBACK:
         case GSK_VULKAN_OP_FALLBACK_CLIP:
@@ -1784,13 +1811,9 @@ gsk_vulkan_render_pass_upload (GskVulkanRenderPass  *self,
           }
           break;
 
-        case GSK_VULKAN_OP_PUSH_VERTEX_CONSTANTS:
-          clip = &op->constants.clip.bounds;
-          scale = &op->constants.scale;
-          break;
-
         default:
           g_assert_not_reached ();
+        case GSK_VULKAN_OP_PUSH_VERTEX_CONSTANTS:
         case GSK_VULKAN_OP_COLOR:
         case GSK_VULKAN_OP_LINEAR_GRADIENT:
         case GSK_VULKAN_OP_BORDER:
@@ -1799,7 +1822,6 @@ gsk_vulkan_render_pass_upload (GskVulkanRenderPass  *self,
         case GSK_VULKAN_OP_SCISSOR:
           break;
         }
-    }
 }
 
 static inline gsize
@@ -1812,15 +1834,28 @@ static gsize
 gsk_vulkan_render_pass_count_vertex_data (GskVulkanRenderPass *self)
 {
   GskVulkanOp *op;
-  gsize n_bytes, vertex_stride;
+  gsize n_bytes;
   guint i;
 
   n_bytes = 0;
-  for (i = 0; i < gsk_vulkan_render_ops_get_size (&self->render_ops); i += sizeof (GskVulkanOp))
+  for (i = 0; i < gsk_vulkan_render_ops_get_size (&self->render_ops); i += op->op_class->size)
     {
       op = (GskVulkanOp *) gsk_vulkan_render_ops_index (&self->render_ops, i);
 
-      switch (op->type)
+      n_bytes = gsk_vulkan_op_count_vertex_data (op, n_bytes);
+    }
+
+  return n_bytes;
+}
+
+static gsize
+gsk_vulkan_render_op_count_vertex_data (GskVulkanOp *op_,
+                                        gsize        n_bytes)
+{
+  GskVulkanOpAll *op = (GskVulkanOpAll *) op_;
+  gsize vertex_stride;
+
+      switch (op->any.type)
         {
         case GSK_VULKAN_OP_FALLBACK:
         case GSK_VULKAN_OP_FALLBACK_CLIP:
@@ -1857,9 +1892,8 @@ gsk_vulkan_render_pass_count_vertex_data (GskVulkanRenderPass *self)
 
         case GSK_VULKAN_OP_PUSH_VERTEX_CONSTANTS:
         case GSK_VULKAN_OP_SCISSOR:
-          continue;
+          break;
         }
-    }
 
   return n_bytes;
 }
@@ -1872,11 +1906,23 @@ gsk_vulkan_render_pass_collect_vertex_data (GskVulkanRenderPass *self,
   GskVulkanOp *op;
   guint i;
 
-  for (i = 0; i < gsk_vulkan_render_ops_get_size (&self->render_ops); i += sizeof (GskVulkanOp))
+  for (i = 0; i < gsk_vulkan_render_ops_get_size (&self->render_ops); i += op->op_class->size)
     {
       op = (GskVulkanOp *) gsk_vulkan_render_ops_index (&self->render_ops, i);
 
-      switch (op->type)
+      gsk_vulkan_op_collect_vertex_data (op, self, render, data);
+    }
+}
+
+static void
+gsk_vulkan_render_op_collect_vertex_data (GskVulkanOp         *op_,
+                                          GskVulkanRenderPass *pass,
+                                          GskVulkanRender     *render,
+                                          guchar              *data)
+{
+  GskVulkanOpAll *op = (GskVulkanOpAll *) op_;
+
+      switch (op->any.type)
         {
         case GSK_VULKAN_OP_FALLBACK:
         case GSK_VULKAN_OP_FALLBACK_CLIP:
@@ -2068,9 +2114,8 @@ gsk_vulkan_render_pass_collect_vertex_data (GskVulkanRenderPass *self,
           g_assert_not_reached ();
         case GSK_VULKAN_OP_PUSH_VERTEX_CONSTANTS:
         case GSK_VULKAN_OP_SCISSOR:
-          continue;
+          break;
         }
-    }
 }
 
 static GskVulkanBuffer *
@@ -2118,11 +2163,21 @@ gsk_vulkan_render_pass_reserve_descriptor_sets (GskVulkanRenderPass *self,
   GskVulkanOp *op;
   guint i;
 
-  for (i = 0; i < gsk_vulkan_render_ops_get_size (&self->render_ops); i += sizeof (GskVulkanOp))
+  for (i = 0; i < gsk_vulkan_render_ops_get_size (&self->render_ops); i += op->op_class->size)
     {
       op = (GskVulkanOp *) gsk_vulkan_render_ops_index (&self->render_ops, i);
 
-      switch (op->type)
+      gsk_vulkan_op_reserve_descriptor_sets (op, render);
+    }
+}
+
+static void
+gsk_vulkan_render_op_reserve_descriptor_sets (GskVulkanOp     *op_,
+                                              GskVulkanRender *render)
+{
+  GskVulkanOpAll *op = (GskVulkanOpAll *) op_;
+
+      switch (op->any.type)
         {
         case GSK_VULKAN_OP_FALLBACK:
         case GSK_VULKAN_OP_FALLBACK_CLIP:
@@ -2208,7 +2263,6 @@ gsk_vulkan_render_pass_reserve_descriptor_sets (GskVulkanRenderPass *self,
         case GSK_VULKAN_OP_SCISSOR:
           break;
         }
-    }
 }
 
 static void
@@ -2218,6 +2272,7 @@ gsk_vulkan_render_pass_draw_rect (GskVulkanRenderPass     *self,
                                   VkCommandBuffer          command_buffer)
 {
   GskVulkanPipeline *current_pipeline = NULL;
+  GskVulkanPipeline *op_pipeline;
   GskVulkanOp *op;
   guint i;
   GskVulkanBuffer *vertex_buffer;
@@ -2233,11 +2288,70 @@ gsk_vulkan_render_pass_draw_rect (GskVulkanRenderPass     *self,
                             },
                             (VkDeviceSize[1]) { 0 });
 
-  for (i = 0; i < gsk_vulkan_render_ops_get_size (&self->render_ops); i += sizeof (GskVulkanOp))
+  for (i = 0; i < gsk_vulkan_render_ops_get_size (&self->render_ops); i += op->op_class->size)
     {
       op = (GskVulkanOp *) gsk_vulkan_render_ops_index (&self->render_ops, i);
 
-      switch (op->type)
+      op_pipeline = gsk_vulkan_op_get_pipeline (op);
+      if (op_pipeline && op_pipeline != current_pipeline)
+        {
+          current_pipeline = op_pipeline;
+          vkCmdBindPipeline (command_buffer,
+                             VK_PIPELINE_BIND_POINT_GRAPHICS,
+                             gsk_vulkan_pipeline_get_pipeline (current_pipeline));
+        }
+
+      gsk_vulkan_op_command (op, pipeline_layout, command_buffer);
+    }
+}
+
+static GskVulkanPipeline *
+gsk_vulkan_render_op_get_pipeline (GskVulkanOp *op_)
+{
+  GskVulkanOpAll *op = (GskVulkanOpAll *) op_;
+
+  switch (op->any.type)
+    {
+    case GSK_VULKAN_OP_FALLBACK:
+    case GSK_VULKAN_OP_FALLBACK_CLIP:
+    case GSK_VULKAN_OP_FALLBACK_ROUNDED_CLIP:
+    case GSK_VULKAN_OP_TEXTURE:
+    case GSK_VULKAN_OP_TEXTURE_SCALE:
+    case GSK_VULKAN_OP_REPEAT:
+    case GSK_VULKAN_OP_COLOR:
+    case GSK_VULKAN_OP_LINEAR_GRADIENT:
+    case GSK_VULKAN_OP_OPACITY:
+    case GSK_VULKAN_OP_COLOR_MATRIX:
+    case GSK_VULKAN_OP_BLUR:
+    case GSK_VULKAN_OP_BORDER:
+    case GSK_VULKAN_OP_INSET_SHADOW:
+    case GSK_VULKAN_OP_OUTSET_SHADOW:
+    case GSK_VULKAN_OP_CROSS_FADE:
+    case GSK_VULKAN_OP_BLEND_MODE:
+      return op->render.pipeline;
+
+    case GSK_VULKAN_OP_TEXT:
+    case GSK_VULKAN_OP_COLOR_TEXT:
+      return op->text.pipeline;
+
+    case GSK_VULKAN_OP_PUSH_VERTEX_CONSTANTS:
+    case GSK_VULKAN_OP_SCISSOR:
+      return NULL;
+
+    default:
+      g_assert_not_reached ();
+      return NULL;
+    }
+}
+
+static void
+gsk_vulkan_render_op_command (GskVulkanOp      *op_,
+                              VkPipelineLayout  pipeline_layout,
+                              VkCommandBuffer   command_buffer)
+{
+  GskVulkanOpAll *op = (GskVulkanOpAll *) op_;
+
+      switch (op->any.type)
         {
         case GSK_VULKAN_OP_FALLBACK:
         case GSK_VULKAN_OP_FALLBACK_CLIP:
@@ -2246,141 +2360,72 @@ gsk_vulkan_render_pass_draw_rect (GskVulkanRenderPass     *self,
         case GSK_VULKAN_OP_TEXTURE_SCALE:
         case GSK_VULKAN_OP_REPEAT:
           if (!op->render.source)
-            continue;
-          if (current_pipeline != op->render.pipeline)
-            {
-              current_pipeline = op->render.pipeline;
-              vkCmdBindPipeline (command_buffer,
-                                 VK_PIPELINE_BIND_POINT_GRAPHICS,
-                                 gsk_vulkan_pipeline_get_pipeline (current_pipeline));
-            }
-
-          gsk_vulkan_texture_pipeline_draw (GSK_VULKAN_TEXTURE_PIPELINE (current_pipeline),
+            break;
+          gsk_vulkan_texture_pipeline_draw (GSK_VULKAN_TEXTURE_PIPELINE (op->render.pipeline),
                                             command_buffer,
-                                            op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (current_pipeline),
+                                            op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (op->render.pipeline),
                                             1);
           break;
 
         case GSK_VULKAN_OP_TEXT:
-          if (current_pipeline != op->text.pipeline)
-            {
-              current_pipeline = op->text.pipeline;
-              vkCmdBindPipeline (command_buffer,
-                                 VK_PIPELINE_BIND_POINT_GRAPHICS,
-                                 gsk_vulkan_pipeline_get_pipeline (current_pipeline));
-            }
-
-          gsk_vulkan_text_pipeline_draw (GSK_VULKAN_TEXT_PIPELINE (current_pipeline),
+          gsk_vulkan_text_pipeline_draw (GSK_VULKAN_TEXT_PIPELINE (op->text.pipeline),
                                          command_buffer,
-                                         op->text.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (current_pipeline),
+                                         op->text.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (op->text.pipeline),
                                          op->text.num_glyphs);
           break;
 
         case GSK_VULKAN_OP_COLOR_TEXT:
-          if (current_pipeline != op->text.pipeline)
-            {
-              current_pipeline = op->text.pipeline;
-              vkCmdBindPipeline (command_buffer,
-                                 VK_PIPELINE_BIND_POINT_GRAPHICS,
-                                 gsk_vulkan_pipeline_get_pipeline (current_pipeline));
-            }
-
-          gsk_vulkan_color_text_pipeline_draw (GSK_VULKAN_COLOR_TEXT_PIPELINE (current_pipeline),
+          gsk_vulkan_color_text_pipeline_draw (GSK_VULKAN_COLOR_TEXT_PIPELINE (op->text.pipeline),
                                                command_buffer,
-                                               op->text.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (current_pipeline),
+                                               op->text.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (op->text.pipeline),
                                                op->text.num_glyphs);
           break;
 
         case GSK_VULKAN_OP_OPACITY:
         case GSK_VULKAN_OP_COLOR_MATRIX:
           if (!op->render.source)
-            continue;
-          if (current_pipeline != op->render.pipeline)
-            {
-              current_pipeline = op->render.pipeline;
-              vkCmdBindPipeline (command_buffer,
-                                 VK_PIPELINE_BIND_POINT_GRAPHICS,
-                                 gsk_vulkan_pipeline_get_pipeline (current_pipeline));
-            }
-
-          gsk_vulkan_effect_pipeline_draw (GSK_VULKAN_EFFECT_PIPELINE (current_pipeline),
+            break;
+          gsk_vulkan_effect_pipeline_draw (GSK_VULKAN_EFFECT_PIPELINE (op->render.pipeline),
                                            command_buffer,
-                                           op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (current_pipeline),
+                                           op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (op->render.pipeline),
                                            1);
           break;
 
         case GSK_VULKAN_OP_BLUR:
           if (!op->render.source)
-            continue;
-          if (current_pipeline != op->render.pipeline)
-            {
-              current_pipeline = op->render.pipeline;
-              vkCmdBindPipeline (command_buffer,
-                                 VK_PIPELINE_BIND_POINT_GRAPHICS,
-                                 gsk_vulkan_pipeline_get_pipeline (current_pipeline));
-            }
-
-          gsk_vulkan_blur_pipeline_draw (GSK_VULKAN_BLUR_PIPELINE (current_pipeline),
+            break;
+          gsk_vulkan_blur_pipeline_draw (GSK_VULKAN_BLUR_PIPELINE (op->render.pipeline),
                                          command_buffer,
-                                         op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (current_pipeline),
+                                         op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (op->render.pipeline),
                                          1);
           break;
 
         case GSK_VULKAN_OP_COLOR:
-          if (current_pipeline != op->render.pipeline)
-            {
-              current_pipeline = op->render.pipeline;
-              vkCmdBindPipeline (command_buffer,
-                                 VK_PIPELINE_BIND_POINT_GRAPHICS,
-                                 gsk_vulkan_pipeline_get_pipeline (current_pipeline));
-            }
-
-          gsk_vulkan_color_pipeline_draw (GSK_VULKAN_COLOR_PIPELINE (current_pipeline),
+          gsk_vulkan_color_pipeline_draw (GSK_VULKAN_COLOR_PIPELINE (op->render.pipeline),
                                           command_buffer,
-                                          op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (current_pipeline),
+                                          op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (op->render.pipeline),
                                           1);
           break;
 
         case GSK_VULKAN_OP_LINEAR_GRADIENT:
-          if (current_pipeline != op->render.pipeline)
-            {
-              current_pipeline = op->render.pipeline;
-              vkCmdBindPipeline (command_buffer,
-                                 VK_PIPELINE_BIND_POINT_GRAPHICS,
-                                 gsk_vulkan_pipeline_get_pipeline (current_pipeline));
-            }
-          gsk_vulkan_linear_gradient_pipeline_draw (GSK_VULKAN_LINEAR_GRADIENT_PIPELINE (current_pipeline),
+          gsk_vulkan_linear_gradient_pipeline_draw (GSK_VULKAN_LINEAR_GRADIENT_PIPELINE (op->render.pipeline),
                                                     command_buffer,
-                                                    op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (current_pipeline),
+                                                    op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (op->render.pipeline),
                                                     1);
           break;
 
         case GSK_VULKAN_OP_BORDER:
-          if (current_pipeline != op->render.pipeline)
-            {
-              current_pipeline = op->render.pipeline;
-              vkCmdBindPipeline (command_buffer,
-                                 VK_PIPELINE_BIND_POINT_GRAPHICS,
-                                 gsk_vulkan_pipeline_get_pipeline (current_pipeline));
-            }
-          gsk_vulkan_border_pipeline_draw (GSK_VULKAN_BORDER_PIPELINE (current_pipeline),
+          gsk_vulkan_border_pipeline_draw (GSK_VULKAN_BORDER_PIPELINE (op->render.pipeline),
                                            command_buffer,
-                                           op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (current_pipeline),
+                                           op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (op->render.pipeline),
                                            1);
           break;
 
         case GSK_VULKAN_OP_INSET_SHADOW:
         case GSK_VULKAN_OP_OUTSET_SHADOW:
-          if (current_pipeline != op->render.pipeline)
-            {
-              current_pipeline = op->render.pipeline;
-              vkCmdBindPipeline (command_buffer,
-                                 VK_PIPELINE_BIND_POINT_GRAPHICS,
-                                 gsk_vulkan_pipeline_get_pipeline (current_pipeline));
-            }
-          gsk_vulkan_box_shadow_pipeline_draw (GSK_VULKAN_BOX_SHADOW_PIPELINE (current_pipeline),
+          gsk_vulkan_box_shadow_pipeline_draw (GSK_VULKAN_BOX_SHADOW_PIPELINE (op->render.pipeline),
                                                command_buffer,
-                                               op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (current_pipeline),
+                                               op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (op->render.pipeline),
                                                1);
           break;
 
@@ -2404,35 +2449,19 @@ gsk_vulkan_render_pass_draw_rect (GskVulkanRenderPass     *self,
 
         case GSK_VULKAN_OP_CROSS_FADE:
           if (!op->render.source || !op->render.source2)
-            continue;
-          if (current_pipeline != op->render.pipeline)
-            {
-              current_pipeline = op->render.pipeline;
-              vkCmdBindPipeline (command_buffer,
-                                 VK_PIPELINE_BIND_POINT_GRAPHICS,
-                                 gsk_vulkan_pipeline_get_pipeline (current_pipeline));
-            }
-
-          gsk_vulkan_cross_fade_pipeline_draw (GSK_VULKAN_CROSS_FADE_PIPELINE (current_pipeline),
+            break;
+          gsk_vulkan_cross_fade_pipeline_draw (GSK_VULKAN_CROSS_FADE_PIPELINE (op->render.pipeline),
                                                command_buffer,
-                                               op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (current_pipeline),
+                                               op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (op->render.pipeline),
                                                1);
           break;
 
         case GSK_VULKAN_OP_BLEND_MODE:
           if (!op->render.source || !op->render.source2)
-            continue;
-          if (current_pipeline != op->render.pipeline)
-            {
-              current_pipeline = op->render.pipeline;
-              vkCmdBindPipeline (command_buffer,
-                                 VK_PIPELINE_BIND_POINT_GRAPHICS,
-                                 gsk_vulkan_pipeline_get_pipeline (current_pipeline));
-            }
-
-          gsk_vulkan_blend_mode_pipeline_draw (GSK_VULKAN_BLEND_MODE_PIPELINE (current_pipeline),
+            break;
+          gsk_vulkan_blend_mode_pipeline_draw (GSK_VULKAN_BLEND_MODE_PIPELINE (op->render.pipeline),
                                                command_buffer,
-                                               op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (current_pipeline),
+                                               op->render.vertex_offset / gsk_vulkan_pipeline_get_vertex_stride (op->render.pipeline),
                                                1);
           break;
 
@@ -2440,7 +2469,6 @@ gsk_vulkan_render_pass_draw_rect (GskVulkanRenderPass     *self,
           g_assert_not_reached ();
           break;
         }
-    }
 }
 
 void
@@ -2487,3 +2515,33 @@ gsk_vulkan_render_pass_draw (GskVulkanRenderPass *self,
 
   vkCmdEndRenderPass (command_buffer);
 }
+
+static void
+gsk_vulkan_render_op_finish (GskVulkanOp *op)
+{
+}
+
+static const GskVulkanOpClass GSK_VULKAN_OP_ALL_CLASS = {
+  sizeof (GskVulkanOpAll),
+  gsk_vulkan_render_op_finish,
+  gsk_vulkan_render_op_upload,
+  gsk_vulkan_render_op_count_vertex_data,
+  gsk_vulkan_render_op_collect_vertex_data,
+  gsk_vulkan_render_op_reserve_descriptor_sets,
+  gsk_vulkan_render_op_get_pipeline,
+  gsk_vulkan_render_op_command
+};
+
+static void
+gsk_vulkan_render_pass_add_op (GskVulkanRenderPass *self,
+                               GskVulkanOp         *op)
+{
+  op->op_class = &GSK_VULKAN_OP_ALL_CLASS;
+
+  gsk_vulkan_render_ops_splice (&self->render_ops,
+                                gsk_vulkan_render_ops_get_size (&self->render_ops),
+                                0, FALSE,
+                                (guchar *) op,
+                                sizeof (GskVulkanOpAll));
+}
+