Only destover for first draw to screen
diff --git a/sparse_strips/vello_hybrid/src/render/webgl.rs b/sparse_strips/vello_hybrid/src/render/webgl.rs index f70a358..2ef18c0 100644 --- a/sparse_strips/vello_hybrid/src/render/webgl.rs +++ b/sparse_strips/vello_hybrid/src/render/webgl.rs
@@ -313,7 +313,7 @@ image_cache: &self.image_cache, filter_context: &self.filter_context, filter_pass_state: &mut self.filter_pass_state, - depth_cleared: false, + first_surface_pass: true, }; self.scheduler.do_scene( &mut self.scheduler_state, @@ -2004,8 +2004,9 @@ image_cache: &'a ImageCache, filter_context: &'a FilterContext, filter_pass_state: &'a mut FilterPassState, - /// Whether the surface depth buffer has been cleared this frame. - depth_cleared: bool, + /// `true` until the first surface draw completes. Only the first surface + /// draw uses dest-over + depth; subsequent ones use src-over. + first_surface_pass: bool, } impl WebGlRendererContext<'_> { @@ -2019,7 +2020,20 @@ if strips.is_empty() { return; } - self.programs.upload_strips(self.gl, strips); + + let is_surface = matches!( + target, + StripPassRenderTarget::Output(OutputTarget::FinalView) + ); + let will_use_dest_over = is_surface && self.first_surface_pass; + + if will_use_dest_over { + let mut reversed = strips.to_vec(); + reversed.reverse(); + self.programs.upload_strips(self.gl, &reversed); + } else { + self.programs.upload_strips(self.gl, strips); + } match &target { StripPassRenderTarget::Output(OutputTarget::IntermediateTexture(layer_id)) => { @@ -2132,13 +2146,14 @@ } } - let is_surface = matches!( - target, - StripPassRenderTarget::Output(OutputTarget::FinalView) - ); - - // Surface pass: dest-over blend + depth testing (front-to-back). + let use_dest_over = will_use_dest_over; if is_surface { + self.first_surface_pass = false; + } + + // First surface pass: dest-over blend + depth (front-to-back). + // Subsequent surface passes: src-over (content goes on top). + if use_dest_over { self.gl.enable(WebGl2RenderingContext::DEPTH_TEST); self.gl.depth_func(WebGl2RenderingContext::LEQUAL); self.gl.depth_mask(true); @@ -2153,9 +2168,7 @@ self.gl.clear(WebGl2RenderingContext::COLOR_BUFFER_BIT); } - // Clear depth on first surface pass of the frame. - if is_surface && !self.depth_cleared { - self.depth_cleared = true; + if use_dest_over { self.gl.clear_depth(1.0); self.gl .clear(WebGl2RenderingContext::DEPTH_BUFFER_BIT); @@ -2230,8 +2243,8 @@ strips.len() as i32, ); - // Restore src-over + no depth for subsequent slot/intermediate passes. - if is_surface { + // Restore src-over + no depth after dest-over surface pass. + if use_dest_over { self.gl.disable(WebGl2RenderingContext::DEPTH_TEST); self.gl.blend_func( WebGl2RenderingContext::ONE,
diff --git a/sparse_strips/vello_hybrid/src/render/wgpu.rs b/sparse_strips/vello_hybrid/src/render/wgpu.rs index 8d18983..d3282d6 100644 --- a/sparse_strips/vello_hybrid/src/render/wgpu.rs +++ b/sparse_strips/vello_hybrid/src/render/wgpu.rs
@@ -371,7 +371,7 @@ image_cache: &self.image_cache, filter_context: &self.filter_context, filter_pass_state: &mut self.filter_pass_state, - depth_cleared: false, + first_surface_pass: true, }; self.scheduler.do_scene( &mut self.scheduler_state, @@ -2336,10 +2336,11 @@ image_cache: &'a ImageCache, filter_context: &'a FilterContext, filter_pass_state: &'a mut FilterPassState, - /// Whether the depth buffer has been cleared this frame. Reset to `false` - /// at the start of each `render_scene`; set to `true` after the first - /// surface pass clears depth to 1.0. - depth_cleared: bool, + /// `true` until the first surface draw completes. The first surface draw + /// uses dest-over + depth (front-to-back, reversed strips). Subsequent + /// surface draws use src-over so that later-round content composites on + /// top of earlier rounds. + first_surface_pass: bool, } impl RendererContext<'_> { @@ -2353,9 +2354,32 @@ if strips.is_empty() { return; } + let use_dest_over = matches!(target, StripPassRenderTarget::Output(OutputTarget::FinalView)) + && self.first_surface_pass; + + // For the first surface pass, reverse strips for front-to-back + // dest-over rendering. Subsequent surface passes and all slot passes + // keep original back-to-front order with src-over. + let reversed; + let upload_strips = if use_dest_over { + reversed = { + let mut v = strips.to_vec(); + v.reverse(); + v + }; + &reversed[..] + } else { + strips + }; + + if matches!(target, StripPassRenderTarget::Output(OutputTarget::FinalView)) { + self.first_surface_pass = false; + } + // TODO: We currently allocate a new strips buffer for each render pass. A more efficient // approach would be to re-use buffers or slices of a larger buffer. - self.programs.upload_strips(self.device, self.queue, strips); + self.programs + .upload_strips(self.device, self.queue, upload_strips); enum MaybeOwned<'a, T> { Borrowed(&'a T), @@ -2469,13 +2493,11 @@ let (depth_view, depth_load) = match target { StripPassRenderTarget::Output(OutputTarget::FinalView) => { - let load = if self.depth_cleared { - wgpu::LoadOp::Load - } else { - self.depth_cleared = true; - wgpu::LoadOp::Clear(1.0) - }; - (&self.programs.resources.depth_texture_view, load) + // Always clear depth for each surface pass. Round boundaries + // are determined by clip/blend dependency resolution, not by + // scene depth, so preserving depth across rounds would + // incorrectly cull content from later rounds. + (&self.programs.resources.depth_texture_view, wgpu::LoadOp::Clear(1.0)) } StripPassRenderTarget::SlotTexture(_) => ( &self.programs.resources.slot_depth_texture_view, @@ -2501,14 +2523,15 @@ stencil_ops: None, }); - let pipeline = match target { - StripPassRenderTarget::Output(OutputTarget::FinalView) => { - &self.programs.strip_pipeline_dest_over + let pipeline = if use_dest_over { + &self.programs.strip_pipeline_dest_over + } else { + match target { + StripPassRenderTarget::Output(OutputTarget::IntermediateTexture(_)) => { + &self.programs.strip_pipelines_src_over[1] + } + _ => &self.programs.strip_pipelines_src_over[0], } - StripPassRenderTarget::Output(OutputTarget::IntermediateTexture(_)) => { - &self.programs.strip_pipelines_src_over[1] - } - StripPassRenderTarget::SlotTexture(_) => &self.programs.strip_pipelines_src_over[0], }; let mut render_pass = self.encoder.begin_render_pass(&RenderPassDescriptor {
diff --git a/sparse_strips/vello_hybrid/src/schedule.rs b/sparse_strips/vello_hybrid/src/schedule.rs index 6b12444..6875b96 100644 --- a/sparse_strips/vello_hybrid/src/schedule.rs +++ b/sparse_strips/vello_hybrid/src/schedule.rs
@@ -858,8 +858,8 @@ /// /// The rounds queue must not be empty. fn flush<R: RendererBackend>(&mut self, renderer: &mut R) { - let mut round = self.rounds_queue.pop_front().unwrap(); - for (i, draw) in round.draws.iter_mut().enumerate() { + let round = self.rounds_queue.pop_front().unwrap(); + for (i, draw) in round.draws.iter().enumerate() { #[cfg(debug_assertions)] { // This is an expensive O(n²) debug only check that enforces that there are no @@ -905,16 +905,6 @@ continue; } - // Reverse the surface draw (i == 2) for front-to-back ordering. - // The scheduler appends strips in scene traversal order - // (back-to-front). Reversing gives front-to-back, which is - // required for the dest-over blending + depth buffer strategy on - // the surface pass. Slot texture draws (i == 0, 1) keep their - // original order with src-over blending. - if i == 2 { - draw.0.reverse(); - } - renderer.render_strips(&draw.0, target, load); } for i in 0..2 {