.
diff --git a/sparse_strips/vello_hybrid/src/render/webgl/mod.rs b/sparse_strips/vello_hybrid/src/render/webgl/mod.rs
index a4e3850..484cd21 100644
--- a/sparse_strips/vello_hybrid/src/render/webgl/mod.rs
+++ b/sparse_strips/vello_hybrid/src/render/webgl/mod.rs
@@ -594,14 +594,14 @@
             root_output_target,
         );
 
-        // See: https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#use_invalidateframebuffer
-        // We want to indicate to the GPU driver that we won't read the depth buffer again
-        // until the next clear. This enables the GPU to avoid storing depth tiles back to VRAM.
         self.gl.bind_framebuffer(
             WebGl2RenderingContext::FRAMEBUFFER,
             self.programs.resources.view_framebuffer_override.as_deref(),
         );
         if self.programs.resources.depth_cleared_this_frame {
+            // See: https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices#use_invalidateframebuffer
+            // We want to indicate to the GPU driver that we won't read the depth buffer again
+            // until the next clear. This enables the GPU to avoid storing depth tiles back to VRAM.
             self.gl
                 .invalidate_framebuffer(
                     WebGl2RenderingContext::FRAMEBUFFER,
diff --git a/sparse_strips/vello_hybrid/src/render/wgpu/mod.rs b/sparse_strips/vello_hybrid/src/render/wgpu/mod.rs
index 0845bb8..36e4189 100644
--- a/sparse_strips/vello_hybrid/src/render/wgpu/mod.rs
+++ b/sparse_strips/vello_hybrid/src/render/wgpu/mod.rs
@@ -923,9 +923,7 @@
     alpha_strip_pipeline: RenderPipeline,
     /// Root opaque-strip pipeline.
     opaque_strip_pipeline: RenderPipeline,
-    /// Depth texture for early-z rejection on the Output target.
-    depth_texture: Option<Texture>,
-    /// View for the depth texture.
+    /// View of the depth texture used for early-z rejection on the Output target.
     depth_texture_view: Option<TextureView>,
     /// Whether the depth buffer has been cleared this frame.
     depth_cleared_this_frame: bool,
@@ -1731,22 +1729,19 @@
             view_config_buffer,
         };
 
-        let depth_texture = use_depth_buffer.then(|| {
+        let depth_texture_view = use_depth_buffer.then(|| {
             Self::create_depth_texture(
                 device,
                 render_target_config.width,
                 render_target_config.height,
             )
+            .create_view(&TextureViewDescriptor::default())
         });
-        let depth_texture_view = depth_texture
-            .as_ref()
-            .map(|texture| texture.create_view(&TextureViewDescriptor::default()));
 
         Self {
             intermediate_strip_pipeline,
             alpha_strip_pipeline,
             opaque_strip_pipeline,
-            depth_texture,
             depth_texture_view,
             depth_cleared_this_frame: false,
             strip_bind_group_layout,
@@ -2417,15 +2412,15 @@
                 .expect("Buffer only ever holds `Config`");
             buffer.copy_from_slice(bytemuck::bytes_of(&config));
 
-            if self.depth_texture.is_some() {
-                let depth_texture = Self::create_depth_texture(
-                    device,
-                    new_render_size.width,
-                    new_render_size.height,
+            if self.depth_texture_view.is_some() {
+                self.depth_texture_view = Some(
+                    Self::create_depth_texture(
+                        device,
+                        new_render_size.width,
+                        new_render_size.height,
+                    )
+                    .create_view(&TextureViewDescriptor::default()),
                 );
-                self.depth_texture_view =
-                    Some(depth_texture.create_view(&TextureViewDescriptor::default()));
-                self.depth_texture = Some(depth_texture);
             }
 
             self.render_size = new_render_size.clone();
diff --git a/sparse_strips/vello_hybrid/src/schedule/execute.rs b/sparse_strips/vello_hybrid/src/schedule/execute.rs
index 3f7cbec..969431a 100644
--- a/sparse_strips/vello_hybrid/src/schedule/execute.rs
+++ b/sparse_strips/vello_hybrid/src/schedule/execute.rs
@@ -259,7 +259,8 @@
         let mut case = SceneCase::new(16, 8);
         case.draw_at(0.0, 1.0);
         chain(&mut case, depth);
-        case.schedule(root_target, SizeU16::new(64), 2).unwrap()
+        case.schedule(root_target, SizeU16::new(64), 2, true)
+            .unwrap()
     }
 
     fn round_order_case() -> ScheduledCase {
@@ -294,7 +295,7 @@
             });
         });
 
-        case.schedule(RootTarget::UserSurface, SizeU16::new(128), 6)
+        case.schedule(RootTarget::UserSurface, SizeU16::new(128), 6, true)
             .unwrap()
     }
 
@@ -371,7 +372,9 @@
     #[test]
     fn empty_passes_are_skipped() {
         let mut recorder = Recorder::default();
-        SceneCase::new(16, 8).schedule_root().execute(&mut recorder);
+        SceneCase::new(16, 8)
+            .schedule_root(true)
+            .execute(&mut recorder);
 
         assert!(recorder.calls.is_empty());
     }
diff --git a/sparse_strips/vello_hybrid/src/schedule/schedule_tests.rs b/sparse_strips/vello_hybrid/src/schedule/schedule_tests.rs
index 2859e06..824334e 100644
--- a/sparse_strips/vello_hybrid/src/schedule/schedule_tests.rs
+++ b/sparse_strips/vello_hybrid/src/schedule/schedule_tests.rs
@@ -75,7 +75,7 @@
             |case| case.draw(Rect::new(8.0, 0.0, 16.0, 8.0), 0.5),
         );
     });
-    case.schedule_root()
+    case.schedule_root(true)
 }
 
 fn root_blend_case() -> SceneCase {
@@ -130,7 +130,7 @@
     let mut case = SceneCase::new(8, 8);
 
     add_chain(&mut case, depth);
-    case.schedule_root()
+    case.schedule_root(true)
 }
 
 fn binding_case() -> ScheduledCase {
@@ -145,7 +145,7 @@
         })),
         |case| case.draw(Rect::new(0.0, 0.0, 60.0, 60.0), 0.5),
     );
-    case.schedule(RootTarget::UserSurface, SizeU16::new(80), 8)
+    case.schedule(RootTarget::UserSurface, SizeU16::new(80), 8, true)
         .unwrap()
 }
 
@@ -167,7 +167,7 @@
 
 #[test]
 fn empty_scene() {
-    let scheduled = SceneCase::new(32, 32).schedule_root();
+    let scheduled = SceneCase::new(32, 32).schedule_root(true);
 
     assert!(scheduled.views().is_empty());
     assert_eq!(scheduled.page_counts(), [0, 0]);
@@ -181,7 +181,7 @@
         case.draw_at(x, 0.5);
     }
 
-    let scheduled = case.schedule_root();
+    let scheduled = case.schedule_root(true);
     let rounds_view = scheduled.views();
 
     assert_eq!(rounds_view.len(), 1);
@@ -197,7 +197,7 @@
     case.layer(|case| case.draw_at(8.0, 0.5));
     case.draw_at(16.0, 0.5);
 
-    let scheduled = case.schedule_root();
+    let scheduled = case.schedule_root(true);
     let rounds_view = scheduled.views();
 
     assert_eq!(rounds_view.len(), 1);
@@ -213,19 +213,17 @@
     }
     case.draw_at(24.0, 0.5);
 
-    let user = case.schedule_root();
+    let user = case.schedule_root(true);
     assert_eq!(user.opaque_x(), [16, 8, 0]);
     assert_eq!(user.views()[0].root.x, [24]);
 
     let atlas = case
-        .schedule(RootTarget::AtlasLayer, SizeU16::new(64), 8)
+        .schedule(RootTarget::AtlasLayer, SizeU16::new(64), 8, true)
         .unwrap();
     assert!(atlas.opaque_x().is_empty());
     assert_eq!(atlas.views()[0].root.x, [0, 8, 16, 24]);
 
-    let without_depth = case
-        .schedule_with_depth(RootTarget::UserSurface, SizeU16::new(64), 8, false)
-        .unwrap();
+    let without_depth = case.schedule_root(false);
     assert!(without_depth.opaque_x().is_empty());
     assert_eq!(without_depth.views()[0].root.x, [0, 8, 16, 24]);
 }
@@ -237,13 +235,11 @@
         case.draw_at(x, opacity);
     }
 
-    let with_depth = case.schedule_root();
+    let with_depth = case.schedule_root(true);
     assert_eq!(with_depth.opaque_x(), [16, 0]);
     assert_eq!(with_depth.views()[0].root.x, [8, 24]);
 
-    let without_depth = case
-        .schedule_with_depth(RootTarget::UserSurface, SizeU16::new(64), 8, false)
-        .unwrap();
+    let without_depth = case.schedule_root(false);
     assert!(without_depth.opaque_x().is_empty());
     assert_eq!(without_depth.views()[0].root.x, [0, 8, 16, 24]);
 }
@@ -253,7 +249,7 @@
     let mut case = SceneCase::new(32, 8);
     case.layer(|case| case.draw_at(8.0, 0.5));
 
-    let scheduled = case.schedule_root();
+    let scheduled = case.schedule_root(true);
     let rounds_view = scheduled.views();
     let round = &rounds_view[0];
 
@@ -270,7 +266,7 @@
     let mut case = SceneCase::new(16, 8);
     case.layer(|case| case.draw_at(4.0, 0.5));
 
-    let scheduled = case.schedule_root();
+    let scheduled = case.schedule_root(true);
 
     assert!(scheduled.views()[0].root.has_child_layer);
     assert!(scheduled.storage.buffers.blend_ops.is_empty());
@@ -301,7 +297,7 @@
         );
     });
 
-    let scheduled = case.schedule_root();
+    let scheduled = case.schedule_root(true);
 
     assert_eq!(scheduled.storage.buffers.filter_ops.len(), 1);
     assert!(scheduled.storage.buffers.blend_ops.is_empty());
@@ -327,7 +323,7 @@
     // There is room for exactly one padded filter region per page. Allocating the parent therefore
     // advances the cursor before the clipped-away child is released.
     let scheduled = case
-        .schedule(RootTarget::UserSurface, filter_page_size(), 2)
+        .schedule(RootTarget::UserSurface, filter_page_size(), 2, true)
         .unwrap();
 
     assert_eq!(scheduled.storage.buffers.filter_ops.len(), 1);
@@ -362,7 +358,7 @@
 #[test]
 fn root_blend_resources() {
     let scheduled = root_blend_case()
-        .schedule(RootTarget::UserSurface, SizeU16::new(16), 3)
+        .schedule(RootTarget::UserSurface, SizeU16::new(16), 3, true)
         .unwrap();
 
     // Root lands in the first odd layer, its child in the even one.
@@ -373,7 +369,7 @@
 #[test]
 fn root_release() {
     let scheduled = root_blend_case()
-        .schedule(RootTarget::UserSurface, SizeU16::new(16), 3)
+        .schedule(RootTarget::UserSurface, SizeU16::new(16), 3, true)
         .unwrap();
     let rounds_view = scheduled.views();
     let root_round = rounds_view
@@ -389,11 +385,11 @@
     let case = root_blend_case();
 
     assert!(
-        case.schedule(RootTarget::UserSurface, SizeU16::new(16), 3,)
+        case.schedule(RootTarget::UserSurface, SizeU16::new(16), 3, true)
             .is_ok()
     );
     assert!(matches!(
-        case.schedule(RootTarget::UserSurface, SizeU16::new(16), 2,),
+        case.schedule(RootTarget::UserSurface, SizeU16::new(16), 2, true),
         Err(RenderError::IntermediateTexture(
             IntermediateTextureError::LimitReached {
                 required: 3,
@@ -410,7 +406,7 @@
         case.layer(|case| case.draw_at(4.0, 0.5));
     });
 
-    let scheduled = case.schedule_root();
+    let scheduled = case.schedule_root(true);
     let rounds_view = scheduled.views();
     let round = &rounds_view[0];
 
@@ -460,7 +456,7 @@
         case.draw_at(24.0, 0.5);
     });
 
-    let scheduled = case.schedule_root();
+    let scheduled = case.schedule_root(true);
     let rounds_view = scheduled.views();
 
     assert_eq!(rounds_view[0].odd.x.len(), 1);
@@ -479,7 +475,7 @@
     }
 
     let scheduled = case
-        .schedule(RootTarget::UserSurface, SizeU16::from_wh(16, 8), 1)
+        .schedule(RootTarget::UserSurface, SizeU16::from_wh(16, 8), 1, true)
         .unwrap();
     let rounds_view = scheduled.views();
     let round = &rounds_view[0];
@@ -542,7 +538,7 @@
     add_chain(&mut case, DEPTH);
 
     let scheduled = case
-        .schedule(RootTarget::UserSurface, SizeU16::new(8), 2)
+        .schedule(RootTarget::UserSurface, SizeU16::new(8), 2, true)
         .unwrap();
     let rounds_view = scheduled.views();
     let layer_draws = rounds_view
@@ -568,7 +564,7 @@
         add_chain(&mut case, depth);
 
         let scheduled = case
-            .schedule(RootTarget::UserSurface, SizeU16::new(8), 2)
+            .schedule(RootTarget::UserSurface, SizeU16::new(8), 2, true)
             .unwrap();
         let textures = scheduled.page_counts().into_iter().sum::<usize>();
 
@@ -583,7 +579,7 @@
         add_blend_chain(&mut case, depth);
 
         let scheduled = case
-            .schedule(RootTarget::UserSurface, SizeU16::new(8), 3)
+            .schedule(RootTarget::UserSurface, SizeU16::new(8), 3, true)
             .unwrap();
 
         assert_eq!(
@@ -598,7 +594,7 @@
 fn wide_layers() {
     for count in 1..=32 {
         let scheduled = sibling_case(count)
-            .schedule(RootTarget::UserSurface, SizeU16::from_wh(64, 8), 1)
+            .schedule(RootTarget::UserSurface, SizeU16::from_wh(64, 8), 1, true)
             .unwrap();
         let rounds_view = scheduled.views();
 
@@ -624,7 +620,7 @@
         let mut case = SceneCase::new(8, 8);
         add_tree(&mut case, depth, CHILDREN);
         let scheduled = case
-            .schedule(RootTarget::UserSurface, SizeU16::new(256), 2)
+            .schedule(RootTarget::UserSurface, SizeU16::new(256), 2, true)
             .unwrap();
         let layers =
             (CHILDREN.pow(depth.try_into().expect("test depth fits in u32")) - 1) / (CHILDREN - 1);
@@ -650,7 +646,7 @@
         let mut case = SceneCase::new(8, 8);
         add_tree(&mut case, depth, CHILDREN);
         let scheduled = case
-            .schedule(RootTarget::UserSurface, SizeU16::new(8), 16)
+            .schedule(RootTarget::UserSurface, SizeU16::new(8), 16, true)
             .unwrap();
         let layers =
             (CHILDREN.pow(depth.try_into().expect("test depth fits in u32")) - 1) / (CHILDREN - 1);
@@ -674,7 +670,7 @@
     let mut case = SceneCase::new(16, 8);
     case.layer(|_| {});
 
-    let scheduled = case.schedule_root();
+    let scheduled = case.schedule_root(true);
 
     assert!(scheduled.views().is_empty());
     assert_eq!(scheduled.page_counts(), [0, 0]);
@@ -693,7 +689,7 @@
         );
     });
 
-    let scheduled = case.schedule_root();
+    let scheduled = case.schedule_root(true);
 
     assert_eq!(scheduled.page_counts(), [1, 1]);
     assert!(scheduled.scratch_texture());
@@ -707,7 +703,7 @@
     clipped.layer_with(Some(clip), None, None, |case| {
         case.draw(Rect::new(0.0, 0.0, 24.0, 8.0), 0.5);
     });
-    let scheduled = clipped.schedule_root();
+    let scheduled = clipped.schedule_root(true);
     let rounds_view = scheduled.views();
     assert_eq!(scheduled.page_counts(), [0, 1]);
     assert_eq!(rounds_view.len(), 1);
@@ -722,7 +718,7 @@
     disjoint.layer_with(Some(Rect::new(24.0, 0.0, 32.0, 8.0)), None, None, |case| {
         case.draw(Rect::new(0.0, 0.0, 8.0, 8.0), 0.5);
     });
-    let scheduled = disjoint.schedule_root();
+    let scheduled = disjoint.schedule_root(true);
     assert!(scheduled.views().is_empty());
     assert_eq!(scheduled.page_counts(), [0, 0]);
 }
@@ -737,7 +733,7 @@
     });
 
     let scheduled = case
-        .schedule(RootTarget::UserSurface, SizeU16::new(128), 2)
+        .schedule(RootTarget::UserSurface, SizeU16::new(128), 2, true)
         .unwrap();
     let rounds_view = scheduled.views();
 
@@ -764,7 +760,7 @@
     });
 
     let scheduled = case
-        .schedule(RootTarget::UserSurface, SizeU16::new(64), 2)
+        .schedule(RootTarget::UserSurface, SizeU16::new(64), 2, true)
         .unwrap();
 
     assert_eq!(scheduled.page_counts(), [1, 1]);
@@ -780,7 +776,7 @@
     });
 
     let scheduled = case
-        .schedule(RootTarget::UserSurface, filter_page_size(), 2)
+        .schedule(RootTarget::UserSurface, filter_page_size(), 2, true)
         .unwrap();
     let rounds_view = scheduled.views();
     let filter = scheduled.storage.buffers.filter_ops[0];
@@ -824,7 +820,7 @@
     }
 
     let scheduled = case
-        .schedule(RootTarget::UserSurface, filter_page_size(), 2)
+        .schedule(RootTarget::UserSurface, filter_page_size(), 2, true)
         .unwrap();
 
     // Sibling filter layers should also reuse pages.
@@ -859,7 +855,13 @@
 
     let mut storage = ScheduleStorage::default();
     let first_schedule = first
-        .schedule_into(&mut storage, RootTarget::UserSurface, SizeU16::new(128), 4)
+        .schedule_into(
+            &mut storage,
+            RootTarget::UserSurface,
+            SizeU16::new(128),
+            4,
+            true,
+        )
         .unwrap();
 
     assert!(!storage.buffers.draw_buffers.strips.is_empty());
@@ -872,7 +874,13 @@
     let mut second = SceneCase::new(64, 16);
     second.draw_at(48.0, 0.5);
     let second_schedule = second
-        .schedule_into(&mut storage, RootTarget::UserSurface, SizeU16::new(128), 4)
+        .schedule_into(
+            &mut storage,
+            RootTarget::UserSurface,
+            SizeU16::new(128),
+            4,
+            true,
+        )
         .unwrap();
 
     assert_eq!(storage.buffers.draw_buffers.strips.len(), 1);
@@ -902,7 +910,7 @@
             |case| case.draw(Rect::new(4.0, 0.0, 16.0, 8.0), 0.5),
         );
     });
-    let scheduled = case.schedule_root();
+    let scheduled = case.schedule_root(true);
     let blend = scheduled.storage.buffers.blend_ops.first().unwrap();
     assert_eq!(blend.blend_bbox, blend.parent_region.layer_bbox);
     // This must not panic.
diff --git a/sparse_strips/vello_hybrid/src/schedule/test_support.rs b/sparse_strips/vello_hybrid/src/schedule/test_support.rs
index 013c222..80cc180 100644
--- a/sparse_strips/vello_hybrid/src/schedule/test_support.rs
+++ b/sparse_strips/vello_hybrid/src/schedule/test_support.rs
@@ -59,15 +59,6 @@
         root_target: RootTarget,
         texture_size: SizeU16,
         max_textures: usize,
-    ) -> Result<ScheduledCase, RenderError> {
-        self.schedule_with_depth(root_target, texture_size, max_textures, true)
-    }
-
-    pub(super) fn schedule_with_depth(
-        &self,
-        root_target: RootTarget,
-        texture_size: SizeU16,
-        max_textures: usize,
         use_depth_buffer: bool,
     ) -> Result<ScheduledCase, RenderError> {
         let mut storage = ScheduleStorage::default();
@@ -97,6 +88,7 @@
         root_target: RootTarget,
         texture_size: SizeU16,
         max_textures: usize,
+        use_depth_buffer: bool,
     ) -> Result<Schedule, RenderError> {
         let encoded = &self.scene.encoded_paints;
         let offsets = vec![0; encoded.len()];
@@ -104,7 +96,7 @@
             storage,
             &self.scene,
             root_target,
-            true,
+            use_depth_buffer,
             PaintResolver::new(encoded, &offsets),
             texture_size,
             IntermediateTextureAllocations::default(),
@@ -112,9 +104,14 @@
         )
     }
 
-    pub(super) fn schedule_root(&self) -> ScheduledCase {
-        self.schedule(RootTarget::UserSurface, SizeU16::new(64), 8)
-            .unwrap()
+    pub(super) fn schedule_root(&self, use_depth_buffer: bool) -> ScheduledCase {
+        self.schedule(
+            RootTarget::UserSurface,
+            SizeU16::new(64),
+            8,
+            use_depth_buffer,
+        )
+        .unwrap()
     }
 }
 
diff --git a/sparse_strips/vello_sparse_tests/tests/depth.rs b/sparse_strips/vello_sparse_tests/tests/hybrid_depth.rs
similarity index 100%
rename from sparse_strips/vello_sparse_tests/tests/depth.rs
rename to sparse_strips/vello_sparse_tests/tests/hybrid_depth.rs
diff --git a/sparse_strips/vello_sparse_tests/tests/mod.rs b/sparse_strips/vello_sparse_tests/tests/mod.rs
index 99a58dc..864ff5d 100644
--- a/sparse_strips/vello_sparse_tests/tests/mod.rs
+++ b/sparse_strips/vello_sparse_tests/tests/mod.rs
@@ -34,11 +34,11 @@
 mod blurred_rounded_rect;
 mod clip;
 mod compose;
-mod depth;
 mod external_texture;
 mod filter;
 mod glyph;
 mod gradient;
+mod hybrid_depth;
 mod hybrid_schedule;
 mod image;
 #[cfg(all(target_arch = "wasm32", feature = "webgl"))]