diff --git a/src/scene/mod.rs b/src/scene/mod.rs index bf04f246b..18e40c7cd 100644 --- a/src/scene/mod.rs +++ b/src/scene/mod.rs @@ -1249,15 +1249,18 @@ impl Scene { } } - // ── Wipeout solid fills ─────────────────────────────────────────────── - // Wipeouts are rendered as background-colored filled polygons so that - // they mask hatch fills drawn earlier in the same pass. - // Wipeout fills are appended last so they render on top of other hatches. + models + } + + /// Wipeout fill models — rendered in a separate pass AFTER wires so that + /// wipeouts correctly mask everything below them in the draw order. + pub(super) fn wipeout_models(&self) -> Vec { let bg_color: [f32; 4] = if self.current_layout == "Model" { self.bg_color } else { self.paper_bg_color }; + let mut models = Vec::new(); for entity in self.document.entities() { let EntityType::Wipeout(wo) = entity else { continue }; if entity.common().invisible { @@ -1286,7 +1289,6 @@ impl Scene { }); } } - models } diff --git a/src/scene/pipeline/mod.rs b/src/scene/pipeline/mod.rs index 020d02dfa..abdc007e4 100644 --- a/src/scene/pipeline/mod.rs +++ b/src/scene/pipeline/mod.rs @@ -33,6 +33,8 @@ pub struct Pipeline { depth_view: wgpu::TextureView, gpu_wires: Vec, gpu_hatches: Vec, + /// Wipeout fills — rendered after wires in a separate pass. + gpu_wipeouts: Vec, gpu_images: Vec, gpu_meshes: Vec, pub viewcube: ViewCubePipeline, @@ -356,6 +358,7 @@ impl Pipeline { depth_view, gpu_wires: vec![], gpu_hatches: vec![], + gpu_wipeouts: vec![], gpu_images: vec![], gpu_meshes: vec![], viewcube, @@ -382,6 +385,14 @@ impl Pipeline { .collect(); } + pub fn upload_wipeouts(&mut self, device: &wgpu::Device, wipeouts: &[HatchModel]) { + self.gpu_wipeouts = wipeouts + .iter() + .filter(|h| h.boundary.len() >= 3) + .map(|h| HatchGpu::new(device, h, &self.hatch_bgl1)) + .collect(); + } + pub fn upload_images( &mut self, device: &wgpu::Device, @@ -574,6 +585,47 @@ impl Pipeline { } } } + + // ── Pass 6: wipeout fills (drawn after wires to mask them) ──────── + if !self.gpu_wipeouts.is_empty() { + let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("wipeout.render_pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: target, + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: Some(wgpu::RenderPassDepthStencilAttachment { + view: &self.depth_view, + depth_ops: Some(wgpu::Operations { + load: wgpu::LoadOp::Load, + store: wgpu::StoreOp::Store, + }), + stencil_ops: None, + }), + timestamp_writes: None, + occlusion_query_set: None, + }); + pass.set_viewport( + vp.x as f32, + vp.y as f32, + vp.width as f32, + vp.height as f32, + 0.0, + 1.0, + ); + pass.set_pipeline(&self.hatch_pipeline); + pass.set_bind_group(0, &self.uniform_bind_group, &[]); + for wipeout in &self.gpu_wipeouts { + pass.set_bind_group(1, &wipeout.bind_group, &[]); + pass.set_vertex_buffer(0, wipeout.vertex_buffer.slice(..)); + pass.draw(0..6, 0..1); + } + } } pub fn ensure_depth_texture(&mut self, device: &wgpu::Device, size: Size) { diff --git a/src/scene/render.rs b/src/scene/render.rs index 23e3b85f0..453255180 100644 --- a/src/scene/render.rs +++ b/src/scene/render.rs @@ -27,6 +27,8 @@ pub struct CameraState { pub struct Primitive { pub(super) wires: Vec, pub(super) hatches: Vec, + /// Wipeout fills — rendered in a separate pass AFTER wires. + pub(super) wipeout_hatches: Vec, pub(super) images: Vec, pub(super) meshes: Vec, pub(super) uniforms: Uniforms, @@ -60,6 +62,7 @@ impl shader::Program for Scene { Primitive { wires: all_wires, hatches: self.synced_hatch_models(), + wipeout_hatches: self.wipeout_models(), images: self.images.values().cloned().collect(), meshes: self.meshes.values().cloned().collect(), uniforms: Uniforms::new(&cam, bounds), @@ -125,6 +128,7 @@ impl shader::Primitive for Primitive { pipeline.viewcube.ensure_depth_texture(device, size); pipeline.upload_uniforms(queue, &self.uniforms); pipeline.upload_hatches(device, &self.hatches); + pipeline.upload_wipeouts(device, &self.wipeout_hatches); pipeline.upload_images(device, queue, &self.images); pipeline.upload_meshes(device, &self.meshes); pipeline.upload_wires(device, &self.wires);