From 8c50836f7a7ee4b2c2887f6d86286628b27be15c Mon Sep 17 00:00:00 2001 From: Junzhuo Zhou Date: Tue, 16 Jun 2026 01:38:57 +0800 Subject: [PATCH] Fix canvas ghosting in the wgpu renderer --- wgpu/src/lib.rs | 9 ++-- wgpu/src/shader/triangle.wgsl | 14 +++++++ wgpu/src/shader/triangle/gradient.wgsl | 2 + wgpu/src/shader/triangle/solid.wgsl | 2 + wgpu/src/triangle.rs | 58 ++++++++++++++++++++------ wgpu/src/triangle/msaa.rs | 19 +++++++++ wgpu/src/window/compositor.rs | 11 +++-- 7 files changed, 93 insertions(+), 22 deletions(-) diff --git a/wgpu/src/lib.rs b/wgpu/src/lib.rs index 6c88aa8e76..41cb229c14 100644 --- a/wgpu/src/lib.rs +++ b/wgpu/src/lib.rs @@ -293,13 +293,13 @@ impl Renderer { for layer in self.layers.iter() { let clip_bounds = layer.bounds * scale_factor; - if physical_bounds + let Some(layer_bounds) = physical_bounds .intersection(&clip_bounds) .and_then(Rectangle::snap) - .is_none() - { + .map(Rectangle::::from) + else { continue; - } + }; if !layer.quads.is_empty() { let prepare_span = debug::prepare(debug::Primitive::Quad); @@ -326,6 +326,7 @@ impl Renderer { &mut self.staging_belt, encoder, &layer.triangles, + layer_bounds, Transformation::scale(scale_factor), viewport.physical_size(), ); diff --git a/wgpu/src/shader/triangle.wgsl b/wgpu/src/shader/triangle.wgsl index e4c193444d..dc46a91dae 100644 --- a/wgpu/src/shader/triangle.wgsl +++ b/wgpu/src/shader/triangle.wgsl @@ -1,5 +1,19 @@ struct Globals { transform: mat4x4, + clip_bounds: vec4, } @group(0) @binding(0) var globals: Globals; + +fn discard_if_clipped(position: vec4) { + let pixel = position.xy; + + if ( + pixel.x < globals.clip_bounds.x || + pixel.y < globals.clip_bounds.y || + pixel.x >= globals.clip_bounds.z || + pixel.y >= globals.clip_bounds.w + ) { + discard; + } +} diff --git a/wgpu/src/shader/triangle/gradient.wgsl b/wgpu/src/shader/triangle/gradient.wgsl index 31a4802692..be2e769f6e 100644 --- a/wgpu/src/shader/triangle/gradient.wgsl +++ b/wgpu/src/shader/triangle/gradient.wgsl @@ -86,6 +86,8 @@ fn gradient( @fragment fn gradient_fs_main(input: GradientVertexOutput) -> @location(0) vec4 { + discard_if_clipped(input.position); + let colors = array, 8>( unpack_color(input.colors_1.xy), unpack_color(input.colors_1.zw), diff --git a/wgpu/src/shader/triangle/solid.wgsl b/wgpu/src/shader/triangle/solid.wgsl index b0d8057bfc..4867f414a3 100644 --- a/wgpu/src/shader/triangle/solid.wgsl +++ b/wgpu/src/shader/triangle/solid.wgsl @@ -20,5 +20,7 @@ fn solid_vs_main(input: SolidVertexInput) -> SolidVertexOutput { @fragment fn solid_fs_main(input: SolidVertexOutput) -> @location(0) vec4 { + discard_if_clipped(input.position); + return input.color; } diff --git a/wgpu/src/triangle.rs b/wgpu/src/triangle.rs index d8ffd00a0b..bf71de6bf3 100644 --- a/wgpu/src/triangle.rs +++ b/wgpu/src/triangle.rs @@ -31,6 +31,7 @@ pub enum Item { struct Upload { layer: Layer, transformation: Transformation, + bounds: Rectangle, version: usize, batch: Arc<[Mesh]>, } @@ -62,12 +63,16 @@ impl Storage { gradient: &gradient::Pipeline, cache: &mesh::Cache, new_transformation: Transformation, + new_bounds: Rectangle, + screen_transformation: Transformation, ) { match self.uploads.entry(cache.id()) { hash_map::Entry::Occupied(entry) => { let upload = entry.into_mut(); - if upload.version != cache.version() || upload.transformation != new_transformation + if upload.version != cache.version() + || upload.transformation != new_transformation + || upload.bounds != new_bounds { if !cache.is_empty() { upload.layer.prepare( @@ -78,12 +83,15 @@ impl Storage { gradient, cache.batch(), new_transformation, + new_bounds, + screen_transformation, ); } upload.batch = cache.batch().clone(); upload.version = cache.version(); upload.transformation = new_transformation; + upload.bounds = new_bounds; } } hash_map::Entry::Vacant(entry) => { @@ -97,12 +105,15 @@ impl Storage { gradient, cache.batch(), new_transformation, + new_bounds, + screen_transformation, ); let _ = entry.insert(Upload { layer, transformation: new_transformation, - version: 0, + bounds: new_bounds, + version: cache.version(), batch: cache.batch().clone(), }); @@ -155,14 +166,15 @@ impl State { belt: &mut wgpu::util::StagingBelt, encoder: &mut wgpu::CommandEncoder, items: &[Item], + bounds: Rectangle, scale: Transformation, target_size: Size, ) { let projection = if let Some((state, pipeline)) = self.msaa.as_mut().zip(pipeline.msaa.as_ref()) { - state.prepare(device, encoder, belt, pipeline, target_size) * scale + state.prepare(device, encoder, belt, pipeline, target_size) } else { - Transformation::orthographic(target_size.width, target_size.height) * scale + Transformation::orthographic(target_size.width, target_size.height) }; for item in items { @@ -171,6 +183,8 @@ impl State { transformation, meshes, } => { + let screen_transformation = scale * *transformation; + if self.layers.len() <= self.prepare_layer { self.layers .push(Layer::new(device, &pipeline.solid, &pipeline.gradient)); @@ -184,7 +198,9 @@ impl State { &pipeline.solid, &pipeline.gradient, meshes, - projection * *transformation, + projection * screen_transformation, + bounds, + screen_transformation, ); self.prepare_layer += 1; @@ -193,6 +209,8 @@ impl State { transformation, cache, } => { + let screen_transformation = scale * *transformation; + self.storage.prepare( device, encoder, @@ -200,7 +218,9 @@ impl State { &pipeline.solid, &pipeline.gradient, cache, - projection * *transformation, + projection * screen_transformation, + bounds, + screen_transformation, ); } } @@ -359,6 +379,8 @@ impl Layer { gradient: &gradient::Pipeline, meshes: &[Mesh], transformation: Transformation, + bounds: Rectangle, + screen_transformation: Transformation, ) { // Count the total amount of vertices & indices we need to handle let count = mesh::attribute_count_of(meshes); @@ -394,19 +416,24 @@ impl Layer { let mut index_offset = 0; for mesh in meshes { - let clip_bounds = mesh.clip_bounds() * transformation; - let snap_distance = clip_bounds + let transformed_clip_bounds = mesh.clip_bounds() * transformation; + let snap_distance = transformed_clip_bounds .snap() .map(|snapped_bounds| { Point::new(snapped_bounds.x as f32, snapped_bounds.y as f32) - - clip_bounds.position() + - transformed_clip_bounds.position() }) .unwrap_or(Vector::ZERO); + let clip_bounds = bounds + .intersection(&(mesh.clip_bounds() * screen_transformation)) + .unwrap_or_default(); + let uniforms = Uniforms::new( transformation * mesh.transformation() * Transformation::translate(snap_distance.x, snap_distance.y), + clip_bounds, ); let indices = mesh.indices(); @@ -579,16 +606,23 @@ fn multisample_state(antialiasing: Option) -> wgpu::MultisampleSta #[repr(C)] pub struct Uniforms { transform: [f32; 16], + clip_bounds: [f32; 4], /// Uniform values must be 256-aligned; /// see: [`wgpu::Limits`] `min_uniform_buffer_offset_alignment`. - _padding: [f32; 48], + _padding: [f32; 44], } impl Uniforms { - pub fn new(transform: Transformation) -> Self { + pub fn new(transform: Transformation, clip_bounds: Rectangle) -> Self { Self { transform: transform.into(), - _padding: [0.0; 48], + clip_bounds: [ + clip_bounds.x, + clip_bounds.y, + clip_bounds.x + clip_bounds.width, + clip_bounds.y + clip_bounds.height, + ], + _padding: [0.0; 44], } } diff --git a/wgpu/src/triangle/msaa.rs b/wgpu/src/triangle/msaa.rs index 7cefdc88f4..2f4030e5f7 100644 --- a/wgpu/src/triangle/msaa.rs +++ b/wgpu/src/triangle/msaa.rs @@ -142,6 +142,25 @@ impl Pipeline { let targets = self.targets.read().expect("Read MSAA targets"); let targets = targets.as_ref().unwrap(); + { + let _clear_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: Some("iced_wgpu.triangle.resolve.clear_pass"), + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &targets.resolve, + depth_slice: None, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + multiview_mask: None, + }); + } + encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("iced_wgpu.triangle.render_pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { diff --git a/wgpu/src/window/compositor.rs b/wgpu/src/window/compositor.rs index 33476aab4d..e532f9128e 100644 --- a/wgpu/src/window/compositor.rs +++ b/wgpu/src/window/compositor.rs @@ -131,12 +131,11 @@ impl Compositor { log::info!("Available alpha modes: {alpha_modes:#?}"); - let preferred_alpha = - if alpha_modes.contains(&wgpu::CompositeAlphaMode::PreMultiplied) { - wgpu::CompositeAlphaMode::PreMultiplied - } else { - wgpu::CompositeAlphaMode::Auto - }; + let preferred_alpha = if alpha_modes.contains(&wgpu::CompositeAlphaMode::Opaque) { + wgpu::CompositeAlphaMode::Opaque + } else { + wgpu::CompositeAlphaMode::Auto + }; format.zip(Some(preferred_alpha)) })