.
diff --git a/sparse_strips/vello_dev_macros/src/test.rs b/sparse_strips/vello_dev_macros/src/test.rs
index 2517367..267cb61 100644
--- a/sparse_strips/vello_dev_macros/src/test.rs
+++ b/sparse_strips/vello_dev_macros/src/test.rs
@@ -31,9 +31,8 @@
     skip_multithreaded: bool,
     /// Whether the test should not be run on the GPU (`vello_hybrid`).
     skip_hybrid: bool,
-    /// Whether `vello_hybrid` should generate the reference image instead of the scalar f32 CPU
-    /// renderer.
-    hybrid_reference: bool,
+    /// Whether only `vello_hybrid` should run and generate the reference image.
+    hybrid_only: bool,
     /// The maximum number of pixels that are allowed to completely deviate from the reference
     /// images. This attribute mainly exists because there are some test cases (like gradients),
     /// where, due to floating point inaccuracies, some pixels might land on a different color
@@ -59,7 +58,7 @@
             skip_cpu: false,
             skip_multithreaded: false,
             skip_hybrid: false,
-            hybrid_reference: false,
+            hybrid_only: false,
             no_ref: false,
             glyph: false,
             diff_pixels: 0,
@@ -155,7 +154,7 @@
         skip_cpu,
         skip_multithreaded,
         mut skip_hybrid,
-        hybrid_reference,
+        hybrid_only,
         ignore_reason,
         no_ref,
         glyph,
@@ -217,8 +216,8 @@
             || input_fn_name_str.contains("mask")
     };
     assert!(
-        !(hybrid_reference && skip_hybrid),
-        "`hybrid_reference` cannot be combined with `skip_hybrid`"
+        !(hybrid_only && skip_hybrid),
+        "`hybrid_only` cannot be combined with `skip_hybrid`"
     );
 
     let empty_snippet = quote! {};
@@ -332,7 +331,7 @@
         f32_fn_name_str_scalar,
         input_fn_name_str.clone(),
         cpu_f32_tolerance_scalar,
-        !hybrid_reference,
+        !hybrid_only,
         0,
         quote! {"fallback"},
         skip_cpu,
@@ -356,7 +355,7 @@
         f32_fn_name_wasm_str,
         input_fn_name_str.clone(),
         cpu_f32_tolerance_scalar,
-        !hybrid_reference,
+        !hybrid_only,
         0,
         wasm_simd_level,
         skip_cpu,
@@ -471,7 +470,7 @@
             cached_cpu_f32_fn_name_str,
             cached_reference_test_name.clone(),
             cpu_f32_tolerance_scalar,
-            !hybrid_reference,
+            !hybrid_only,
             0,
             quote! {"fallback"},
             skip_cpu,
@@ -495,7 +494,7 @@
                 #invoke_cached_test
                 ctx.flush();
                 if !#no_ref {
-                    check_ref(&mut ctx, #cached_reference_test_name, #cached_hybrid_fn_name_str, #hybrid_tolerance, #diff_pixels, #hybrid_reference, #reference_image_name);
+                    check_ref(&mut ctx, #cached_reference_test_name, #cached_hybrid_fn_name_str, #hybrid_tolerance, #diff_pixels, #hybrid_only, #reference_image_name);
                 }
             }
         }
@@ -545,7 +544,7 @@
             #invoke_test
             ctx.flush();
             if !#no_ref {
-                check_ref(&mut ctx, #input_fn_name_str, #hybrid_fn_name_str, #hybrid_tolerance, #diff_pixels, #hybrid_reference, #reference_image_name);
+                check_ref(&mut ctx, #input_fn_name_str, #hybrid_fn_name_str, #hybrid_tolerance, #diff_pixels, #hybrid_only, #reference_image_name);
             }
         }
 
@@ -606,7 +605,10 @@
                     "skip_cpu" => args.skip_cpu = true,
                     "skip_multithreaded" => args.skip_multithreaded = true,
                     "skip_hybrid" => args.skip_hybrid = true,
-                    "hybrid_reference" => args.hybrid_reference = true,
+                    "hybrid_only" => {
+                        args.skip_cpu = true;
+                        args.hybrid_only = true;
+                    }
                     "no_ref" => args.no_ref = true,
                     "glyph" => args.glyph = true,
                     "ignore" => {
diff --git a/sparse_strips/vello_hybrid/src/render/common.rs b/sparse_strips/vello_hybrid/src/render/common.rs
index 92c40b2..372382e 100644
--- a/sparse_strips/vello_hybrid/src/render/common.rs
+++ b/sparse_strips/vello_hybrid/src/render/common.rs
@@ -579,7 +579,8 @@
     ((x as u32) << 16) | (y as u32)
 }
 
-/// Pack image `quality`, extend modes, and `atlas_index` into a single u32.
+/// Pack image `quality`, extend modes, `atlas_index`, and source type into a single u32.
+/// `is_external`: stored in bit 14
 /// `atlas_index`: stored in bits 6-13 (8 bits, supports up to 256 atlases)
 /// `extend_y`: stored in bits 4-5 (2 bits)
 /// `extend_x`: stored in bits 2-3 (2 bits)
@@ -590,12 +591,17 @@
     extend_x: u32,
     extend_y: u32,
     atlas_index: u32,
+    is_external: bool,
 ) -> u32 {
     debug_assert!(extend_x <= 3, "extend_x must be 0-3 (2 bits)");
     debug_assert!(extend_y <= 3, "extend_y must be 0-3 (2 bits)");
     debug_assert!(quality <= 3, "quality must be 0-3 (2 bits)");
     debug_assert!(atlas_index <= 255, "atlas_index must be 0-255 (8 bits)");
-    (atlas_index << 6) | (extend_y << 4) | (extend_x << 2) | quality
+    (u32::from(is_external) << 14)
+        | (atlas_index << 6)
+        | (extend_y << 4)
+        | (extend_x << 2)
+        | quality
 }
 
 /// Pack an optional [`Tint`](vello_common::paint::Tint) into a (`tint_color_u32`, `tint_mode_u32`) pair for the GPU.
diff --git a/sparse_strips/vello_hybrid/src/render/webgl.rs b/sparse_strips/vello_hybrid/src/render/webgl.rs
index c52d56b..fee372c 100644
--- a/sparse_strips/vello_hybrid/src/render/webgl.rs
+++ b/sparse_strips/vello_hybrid/src/render/webgl.rs
@@ -86,8 +86,6 @@
     transform: [0.0; 6],
 });
 
-const EXTERNAL_IMAGE_SOURCE_FLAG: u32 = 1 << 14;
-
 /// Texture unit the strip program samples external textures from.
 const EXTERNAL_TEXTURE_UNIT: u32 = 5;
 
@@ -782,6 +780,7 @@
             image.sampler.x_extend as u32,
             image.sampler.y_extend as u32,
             image_resource.atlas_id.as_u32(),
+            false,
         );
         let (tint, tint_mode) = pack_tint(image.tint);
 
@@ -806,7 +805,8 @@
             texture.sampler.x_extend as u32,
             texture.sampler.y_extend as u32,
             0,
-        ) | EXTERNAL_IMAGE_SOURCE_FLAG;
+            true,
+        );
         let (tint, tint_mode) = pack_tint(texture.tint);
 
         GpuEncodedPaint::Image(GpuEncodedImage {
@@ -2509,9 +2509,7 @@
     /// Draw `count` strip instances starting at `first_instance`, split into one draw per
     /// external texture run.
     ///
-    /// WebGL2 cannot offset the instance index at draw time, so each range is drawn by
-    /// re-specifying the vertex attribute pointers at that range's byte offset. The offsets are
-    /// restored to the start of the buffer before returning.
+    /// Strip attribute offsets are restored to the start of the buffer before returning.
     fn draw_strips(
         &self,
         external_texture_runs: &[ExternalTextureRun],
diff --git a/sparse_strips/vello_hybrid/src/render/wgpu.rs b/sparse_strips/vello_hybrid/src/render/wgpu.rs
index a71825a..ea9ba9a 100644
--- a/sparse_strips/vello_hybrid/src/render/wgpu.rs
+++ b/sparse_strips/vello_hybrid/src/render/wgpu.rs
@@ -83,8 +83,6 @@
     transform: [0.0; 6],
 });
 
-const EXTERNAL_IMAGE_SOURCE_FLAG: u32 = 1 << 14;
-
 /// Options for the renderer
 #[derive(Debug)]
 pub struct RenderTargetConfig {
@@ -745,6 +743,7 @@
             image.sampler.x_extend as u32,
             image.sampler.y_extend as u32,
             image_resource.atlas_id.as_u32(),
+            false,
         );
         let (tint, tint_mode) = pack_tint(image.tint);
 
@@ -769,7 +768,8 @@
             image.sampler.x_extend as u32,
             image.sampler.y_extend as u32,
             0,
-        ) | EXTERNAL_IMAGE_SOURCE_FLAG;
+            true,
+        );
         let (tint, tint_mode) = pack_tint(image.tint);
 
         GpuEncodedPaint::Image(GpuEncodedImage {
diff --git a/sparse_strips/vello_sparse_tests/snapshots/external_texture_opaque_interleaving.png b/sparse_strips/vello_sparse_tests/snapshots/external_texture_opaque_interleaving.png
new file mode 100644
index 0000000..8491b61
--- /dev/null
+++ b/sparse_strips/vello_sparse_tests/snapshots/external_texture_opaque_interleaving.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:5a55e59bab17dd19dd33efdff70b089aea3f5611003b2f7e288a6abb1d3aac54
+size 153
diff --git a/sparse_strips/vello_sparse_tests/snapshots/external_texture_runs_with_opaque_prefix.png b/sparse_strips/vello_sparse_tests/snapshots/external_texture_runs_with_opaque_prefix.png
new file mode 100644
index 0000000..cb20bdc
--- /dev/null
+++ b/sparse_strips/vello_sparse_tests/snapshots/external_texture_runs_with_opaque_prefix.png
@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:cd506155eb000faa21fda117e7f0c65578851d87e966827e7197bcc273227443
+size 164
diff --git a/sparse_strips/vello_sparse_tests/tests/external_texture.rs b/sparse_strips/vello_sparse_tests/tests/external_texture.rs
index 4f925a8..e7f262d 100644
--- a/sparse_strips/vello_sparse_tests/tests/external_texture.rs
+++ b/sparse_strips/vello_sparse_tests/tests/external_texture.rs
@@ -86,7 +86,7 @@
         ctx.fill_path(&circle.to_path(0.1));
     }
 
-    #[vello_test(width = 96, height = 96, skip_cpu, hybrid_reference)]
+    #[vello_test(width = 96, height = 96, hybrid_only)]
     fn external_texture_composite(ctx: &mut impl Renderer) {
         let texture_id = ctx.register_external_texture(load_image!("glyphs_colr_noto"));
         ctx.draw_texture_rects(
@@ -109,7 +109,37 @@
         );
     }
 
-    #[vello_test(skip_cpu, hybrid_reference)]
+    #[vello_test(width = 96, height = 96, hybrid_only)]
+    fn external_texture_opaque_interleaving(ctx: &mut impl Renderer) {
+        let texture_id = ctx.register_external_texture(solid_pixmap(192, 0, 0, 192));
+
+        ctx.set_paint(AlphaColor::from_rgba8(0, 0, 255, 255));
+        ctx.fill_rect(&Rect::new(8., 8., 64., 64.));
+
+        ctx.draw_texture_rects(texture_id, ImageQuality::Low, texture_rect_at(28., 28.));
+
+        ctx.set_paint(AlphaColor::from_rgba8(0, 255, 0, 255));
+        ctx.fill_rect(&Rect::new(48., 16., 88., 56.));
+    }
+
+    #[vello_test(width = 96, height = 96, hybrid_only)]
+    fn external_texture_runs_with_opaque_prefix(ctx: &mut impl Renderer) {
+        let red_texture = ctx.register_external_texture(solid_pixmap(255, 0, 0, 255));
+        let green_texture = ctx.register_external_texture(solid_pixmap(0, 255, 0, 255));
+
+        ctx.set_paint(AlphaColor::from_rgba8(32, 32, 32, 255));
+        ctx.fill_rect(&Rect::new(4., 4., 92., 92.));
+
+        ctx.draw_texture_rects(red_texture, ImageQuality::Low, texture_rect_at(8., 8.));
+
+        ctx.set_paint(AlphaColor::from_rgba8(255, 255, 255, 128));
+        ctx.fill_rect(&Rect::new(24., 24., 72., 72.));
+
+        ctx.draw_texture_rects(green_texture, ImageQuality::Low, texture_rect_at(28., 28.));
+        ctx.draw_texture_rects(red_texture, ImageQuality::Low, texture_rect_at(48., 48.));
+    }
+
+    #[vello_test(hybrid_only)]
     fn external_texture_atlas_interleaving(ctx: &mut impl Renderer) {
         let atlas_red = ctx.get_image_source(solid_pixmap(254, 0, 0, 254));
         let external_green = ctx.register_external_texture(solid_pixmap(0, 254, 0, 254));
@@ -138,7 +168,7 @@
         draw_atlas_rect(ctx, atlas_magenta, Rect::new(66., 66., 90., 90.));
     }
 
-    #[vello_test(skip_cpu, hybrid_reference)]
+    #[vello_test(hybrid_only)]
     fn external_texture_layer_before_external(ctx: &mut impl Renderer) {
         let texture_id = ctx.register_external_texture(solid_pixmap(128, 0, 0, 128));
 
@@ -152,7 +182,7 @@
         ctx.pop_layer();
     }
 
-    #[vello_test(skip_cpu, hybrid_reference)]
+    #[vello_test(hybrid_only)]
     fn external_texture_external_before_layer(ctx: &mut impl Renderer) {
         let texture_id = ctx.register_external_texture(solid_pixmap(0, 255, 0, 255));
 
@@ -166,7 +196,7 @@
         ctx.pop_layer();
     }
 
-    #[vello_test(skip_cpu, hybrid_reference)]
+    #[vello_test(hybrid_only)]
     fn external_texture_layer_circle_orders(ctx: &mut impl Renderer) {
         let blue_texture = ctx.register_external_texture(solid_pixmap(0, 0, 255, 255));
         let red_texture = ctx.register_external_texture(solid_pixmap(255, 0, 0, 255));
@@ -243,7 +273,7 @@
         );
     }
 
-    #[vello_test(width = 96, height = 96, skip_cpu, hybrid_reference)]
+    #[vello_test(width = 96, height = 96, hybrid_only)]
     fn external_texture_skewed(ctx: &mut impl Renderer) {
         let texture_id = ctx.register_external_texture(load_image!("glyphs_colr_noto"));
         ctx.draw_texture_rects(
@@ -256,7 +286,7 @@
         );
     }
 
-    #[vello_test(width = 96, height = 96, skip_cpu, hybrid_reference)]
+    #[vello_test(width = 96, height = 96, hybrid_only)]
     fn external_texture_clipped(ctx: &mut impl Renderer) {
         let texture_id = ctx.register_external_texture(load_image!("glyphs_colr_noto"));
         let clip = Circle::new((48., 48.), 24.).to_path(0.1);
@@ -279,13 +309,7 @@
         ctx.pop_layer();
     }
 
-    #[vello_test(
-        width = 96,
-        height = 96,
-        skip_cpu,
-        hybrid_reference,
-        hybrid_tolerance = 2
-    )]
+    #[vello_test(width = 96, height = 96, hybrid_only, hybrid_tolerance = 2)]
     fn external_texture_blurred(ctx: &mut impl Renderer) {
         let texture_id = ctx.register_external_texture(load_image!("glyphs_colr_noto"));
         let blur = Filter::from_primitive(FilterPrimitive::GaussianBlur {
@@ -305,7 +329,7 @@
         ctx.pop_layer();
     }
 
-    #[vello_test(width = 192, height = 132, skip_cpu, hybrid_reference)]
+    #[vello_test(width = 192, height = 132, hybrid_only)]
     fn external_texture_many_sprites(ctx: &mut impl Renderer) {
         let texture_id = ctx.register_external_texture(load_image!("glyphs_colr_noto"));
         let placements = [
@@ -333,13 +357,7 @@
         );
     }
 
-    #[vello_test(
-        width = 96,
-        height = 96,
-        skip_cpu,
-        hybrid_reference,
-        hybrid_tolerance = 2
-    )]
+    #[vello_test(width = 96, height = 96, hybrid_only, hybrid_tolerance = 2)]
     fn external_texture_with_scene_transform(ctx: &mut impl Renderer) {
         let texture_id = ctx.register_external_texture(load_image!("glyphs_colr_noto"));