feat: 4× MSAA anti-aliasing for main drawing pipelines
All main render pipelines (wire, hatch, image, mesh) now use 4× multisampling. Drawing passes render to a dedicated MSAA color buffer; a final empty resolve pass transfers the anti-aliased result to the iced surface target. The ViewCube renders post-resolve at 1× to avoid pipeline incompatibility. Depth buffer also upgraded to 4× MSAA. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
25b91bc09e
commit
c12b30ad3d
3 changed files with 87 additions and 15 deletions
|
|
@ -42,7 +42,7 @@ Durum simgeleri: ✅ Tamamlandı · 🔧 Kısmen yapıldı · ⬜ Yapılmadı
|
|||
| 2.11 | UCS simgesi (XYZ tripod) | ✅ |
|
||||
| 2.12 | Solid3D / 3DSOLID tessellation (truck pipeline) | ✅ |
|
||||
| 2.13 | Region / Body / Wire / Silhouette entity render | ⬜ |
|
||||
| 2.14 | Anti-aliasing / MSAA seçeneği | ⬜ |
|
||||
| 2.14 | Anti-aliasing / MSAA (4×) | ✅ |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,9 @@ use crate::scene::image_model::ImageModel;
|
|||
use crate::scene::mesh_model::MeshModel;
|
||||
use crate::scene::wire_model::WireModel;
|
||||
|
||||
/// MSAA sample count for the main drawing pipelines.
|
||||
const MSAA_SAMPLES: u32 = 4;
|
||||
|
||||
pub struct Pipeline {
|
||||
wire_pipeline: wgpu::RenderPipeline,
|
||||
hatch_pipeline: wgpu::RenderPipeline,
|
||||
|
|
@ -31,6 +34,10 @@ pub struct Pipeline {
|
|||
image_bgl1: wgpu::BindGroupLayout,
|
||||
depth_texture_size: Size<u32>,
|
||||
depth_view: wgpu::TextureView,
|
||||
/// 4× MSAA color buffer for the main drawing passes.
|
||||
msaa_view: wgpu::TextureView,
|
||||
/// Cached texture format (needed to recreate MSAA / depth textures on resize).
|
||||
surface_format: wgpu::TextureFormat,
|
||||
gpu_wires: Vec<WireGpu>,
|
||||
gpu_hatches: Vec<HatchGpu>,
|
||||
/// Wipeout fills — rendered after wires in a separate pass.
|
||||
|
|
@ -113,7 +120,7 @@ impl Pipeline {
|
|||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
count: MSAA_SAMPLES,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
|
|
@ -182,7 +189,7 @@ impl Pipeline {
|
|||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
count: MSAA_SAMPLES,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
|
|
@ -236,7 +243,7 @@ impl Pipeline {
|
|||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
count: MSAA_SAMPLES,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
|
|
@ -325,7 +332,7 @@ impl Pipeline {
|
|||
bias: wgpu::DepthBiasState::default(),
|
||||
}),
|
||||
multisample: wgpu::MultisampleState {
|
||||
count: 1,
|
||||
count: MSAA_SAMPLES,
|
||||
mask: !0,
|
||||
alpha_to_coverage_enabled: false,
|
||||
},
|
||||
|
|
@ -345,6 +352,10 @@ impl Pipeline {
|
|||
|
||||
let viewcube = ViewCubePipeline::new(device, queue, format);
|
||||
|
||||
let init_size = Size::new(1, 1);
|
||||
let msaa_view = create_msaa_texture(device, init_size, format)
|
||||
.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
|
||||
Self {
|
||||
wire_pipeline,
|
||||
hatch_pipeline,
|
||||
|
|
@ -356,6 +367,8 @@ impl Pipeline {
|
|||
image_bgl1,
|
||||
depth_texture_size: Size::new(1, 1),
|
||||
depth_view,
|
||||
msaa_view,
|
||||
surface_format: format,
|
||||
gpu_wires: vec![],
|
||||
gpu_hatches: vec![],
|
||||
gpu_wipeouts: vec![],
|
||||
|
|
@ -414,19 +427,24 @@ impl Pipeline {
|
|||
encoder: &mut wgpu::CommandEncoder,
|
||||
target: &wgpu::TextureView,
|
||||
clip_bounds: Rectangle<u32>,
|
||||
bg_color: [f32; 4],
|
||||
) {
|
||||
let vp = clip_bounds;
|
||||
let msaa = &self.msaa_view;
|
||||
let [r, g, b, a] = bg_color;
|
||||
let clear_color = wgpu::Color { r: r as f64, g: g as f64, b: b as f64, a: a as f64 };
|
||||
|
||||
// ── Pass 1: hatch fills ────────────────────────────────────────────
|
||||
{
|
||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("hatch.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
view: msaa,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
// Clear MSAA to background color on the first pass.
|
||||
load: wgpu::LoadOp::Clear(clear_color),
|
||||
store: wgpu::StoreOp::Store,
|
||||
},
|
||||
})],
|
||||
|
|
@ -465,7 +483,7 @@ impl Pipeline {
|
|||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("image.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
view: msaa,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
|
|
@ -506,7 +524,7 @@ impl Pipeline {
|
|||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("mesh.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
view: msaa,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
|
|
@ -549,7 +567,7 @@ impl Pipeline {
|
|||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("wire.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
view: msaa,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
|
|
@ -591,7 +609,7 @@ impl Pipeline {
|
|||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("wipeout.render_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: target,
|
||||
view: msaa,
|
||||
depth_slice: None,
|
||||
resolve_target: None,
|
||||
ops: wgpu::Operations {
|
||||
|
|
@ -626,12 +644,36 @@ impl Pipeline {
|
|||
pass.draw(0..6, 0..1);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Resolve MSAA → target ─────────────────────────────────────────
|
||||
// An empty render pass with resolve_target transfers the MSAA samples
|
||||
// to the non-multisampled iced target surface.
|
||||
{
|
||||
let _resolve = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||
label: Some("msaa.resolve_pass"),
|
||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||
view: msaa,
|
||||
depth_slice: None,
|
||||
resolve_target: Some(target),
|
||||
ops: wgpu::Operations {
|
||||
load: wgpu::LoadOp::Load,
|
||||
store: wgpu::StoreOp::Discard,
|
||||
},
|
||||
})],
|
||||
depth_stencil_attachment: None,
|
||||
timestamp_writes: None,
|
||||
occlusion_query_set: None,
|
||||
});
|
||||
// No draw calls — the pass itself triggers the MSAA resolve.
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ensure_depth_texture(&mut self, device: &wgpu::Device, size: Size<u32>) {
|
||||
if self.depth_texture_size != size {
|
||||
let tex = create_depth_texture(device, size);
|
||||
self.depth_view = tex.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let depth_tex = create_depth_texture(device, size);
|
||||
self.depth_view = depth_tex.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
let msaa_tex = create_msaa_texture(device, size, self.surface_format);
|
||||
self.msaa_view = msaa_tex.create_view(&wgpu::TextureViewDescriptor::default());
|
||||
self.depth_texture_size = size;
|
||||
}
|
||||
}
|
||||
|
|
@ -646,7 +688,7 @@ fn create_depth_texture(device: &wgpu::Device, size: Size<u32>) -> wgpu::Texture
|
|||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: 1,
|
||||
sample_count: MSAA_SAMPLES,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format: wgpu::TextureFormat::Depth32Float,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
|
|
@ -654,6 +696,27 @@ fn create_depth_texture(device: &wgpu::Device, size: Size<u32>) -> wgpu::Texture
|
|||
})
|
||||
}
|
||||
|
||||
fn create_msaa_texture(
|
||||
device: &wgpu::Device,
|
||||
size: Size<u32>,
|
||||
format: wgpu::TextureFormat,
|
||||
) -> wgpu::Texture {
|
||||
device.create_texture(&wgpu::TextureDescriptor {
|
||||
label: Some("viewer.msaa_texture"),
|
||||
size: wgpu::Extent3d {
|
||||
width: size.width.max(1),
|
||||
height: size.height.max(1),
|
||||
depth_or_array_layers: 1,
|
||||
},
|
||||
mip_level_count: 1,
|
||||
sample_count: MSAA_SAMPLES,
|
||||
dimension: wgpu::TextureDimension::D2,
|
||||
format,
|
||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
||||
view_formats: &[],
|
||||
})
|
||||
}
|
||||
|
||||
impl iced::widget::shader::Pipeline for Pipeline {
|
||||
fn new(device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat) -> Self {
|
||||
Self::new(device, queue, format)
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ pub struct Primitive {
|
|||
/// Used by the ViewCube pipeline — no gimbal lock.
|
||||
pub(super) cam_rotation: Mat4,
|
||||
pub(super) hover_region: Option<usize>,
|
||||
/// Background color used to clear the MSAA buffer at the start of each frame.
|
||||
pub(super) bg_color: [f32; 4],
|
||||
}
|
||||
|
||||
// ── shader::Program impl ──────────────────────────────────────────────────
|
||||
|
|
@ -59,6 +61,12 @@ impl<Msg: std::fmt::Debug + Clone> shader::Program<Msg> for Scene {
|
|||
}
|
||||
all_wires.extend(self.preview_wires.iter().cloned());
|
||||
|
||||
let bg_color = if self.current_layout == "Model" {
|
||||
self.bg_color
|
||||
} else {
|
||||
self.paper_bg_color
|
||||
};
|
||||
|
||||
Primitive {
|
||||
wires: all_wires,
|
||||
hatches: self.synced_hatch_models(),
|
||||
|
|
@ -68,6 +76,7 @@ impl<Msg: std::fmt::Debug + Clone> shader::Program<Msg> for Scene {
|
|||
uniforms: Uniforms::new(&cam, bounds),
|
||||
cam_rotation: cam.view_rotation_mat(),
|
||||
hover_region: state.hover_region,
|
||||
bg_color,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -149,7 +158,7 @@ impl shader::Primitive for Primitive {
|
|||
target: &iced::wgpu::TextureView,
|
||||
clip: &Rectangle<u32>,
|
||||
) {
|
||||
pipeline.render(encoder, target, *clip);
|
||||
pipeline.render(encoder, target, *clip, self.bg_color);
|
||||
pipeline.viewcube.render(encoder, target, *clip);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue