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.11 | UCS simgesi (XYZ tripod) | ✅ |
|
||||||
| 2.12 | Solid3D / 3DSOLID tessellation (truck pipeline) | ✅ |
|
| 2.12 | Solid3D / 3DSOLID tessellation (truck pipeline) | ✅ |
|
||||||
| 2.13 | Region / Body / Wire / Silhouette entity render | ⬜ |
|
| 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::mesh_model::MeshModel;
|
||||||
use crate::scene::wire_model::WireModel;
|
use crate::scene::wire_model::WireModel;
|
||||||
|
|
||||||
|
/// MSAA sample count for the main drawing pipelines.
|
||||||
|
const MSAA_SAMPLES: u32 = 4;
|
||||||
|
|
||||||
pub struct Pipeline {
|
pub struct Pipeline {
|
||||||
wire_pipeline: wgpu::RenderPipeline,
|
wire_pipeline: wgpu::RenderPipeline,
|
||||||
hatch_pipeline: wgpu::RenderPipeline,
|
hatch_pipeline: wgpu::RenderPipeline,
|
||||||
|
|
@ -31,6 +34,10 @@ pub struct Pipeline {
|
||||||
image_bgl1: wgpu::BindGroupLayout,
|
image_bgl1: wgpu::BindGroupLayout,
|
||||||
depth_texture_size: Size<u32>,
|
depth_texture_size: Size<u32>,
|
||||||
depth_view: wgpu::TextureView,
|
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_wires: Vec<WireGpu>,
|
||||||
gpu_hatches: Vec<HatchGpu>,
|
gpu_hatches: Vec<HatchGpu>,
|
||||||
/// Wipeout fills — rendered after wires in a separate pass.
|
/// Wipeout fills — rendered after wires in a separate pass.
|
||||||
|
|
@ -113,7 +120,7 @@ impl Pipeline {
|
||||||
bias: wgpu::DepthBiasState::default(),
|
bias: wgpu::DepthBiasState::default(),
|
||||||
}),
|
}),
|
||||||
multisample: wgpu::MultisampleState {
|
multisample: wgpu::MultisampleState {
|
||||||
count: 1,
|
count: MSAA_SAMPLES,
|
||||||
mask: !0,
|
mask: !0,
|
||||||
alpha_to_coverage_enabled: false,
|
alpha_to_coverage_enabled: false,
|
||||||
},
|
},
|
||||||
|
|
@ -182,7 +189,7 @@ impl Pipeline {
|
||||||
bias: wgpu::DepthBiasState::default(),
|
bias: wgpu::DepthBiasState::default(),
|
||||||
}),
|
}),
|
||||||
multisample: wgpu::MultisampleState {
|
multisample: wgpu::MultisampleState {
|
||||||
count: 1,
|
count: MSAA_SAMPLES,
|
||||||
mask: !0,
|
mask: !0,
|
||||||
alpha_to_coverage_enabled: false,
|
alpha_to_coverage_enabled: false,
|
||||||
},
|
},
|
||||||
|
|
@ -236,7 +243,7 @@ impl Pipeline {
|
||||||
bias: wgpu::DepthBiasState::default(),
|
bias: wgpu::DepthBiasState::default(),
|
||||||
}),
|
}),
|
||||||
multisample: wgpu::MultisampleState {
|
multisample: wgpu::MultisampleState {
|
||||||
count: 1,
|
count: MSAA_SAMPLES,
|
||||||
mask: !0,
|
mask: !0,
|
||||||
alpha_to_coverage_enabled: false,
|
alpha_to_coverage_enabled: false,
|
||||||
},
|
},
|
||||||
|
|
@ -325,7 +332,7 @@ impl Pipeline {
|
||||||
bias: wgpu::DepthBiasState::default(),
|
bias: wgpu::DepthBiasState::default(),
|
||||||
}),
|
}),
|
||||||
multisample: wgpu::MultisampleState {
|
multisample: wgpu::MultisampleState {
|
||||||
count: 1,
|
count: MSAA_SAMPLES,
|
||||||
mask: !0,
|
mask: !0,
|
||||||
alpha_to_coverage_enabled: false,
|
alpha_to_coverage_enabled: false,
|
||||||
},
|
},
|
||||||
|
|
@ -345,6 +352,10 @@ impl Pipeline {
|
||||||
|
|
||||||
let viewcube = ViewCubePipeline::new(device, queue, format);
|
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 {
|
Self {
|
||||||
wire_pipeline,
|
wire_pipeline,
|
||||||
hatch_pipeline,
|
hatch_pipeline,
|
||||||
|
|
@ -356,6 +367,8 @@ impl Pipeline {
|
||||||
image_bgl1,
|
image_bgl1,
|
||||||
depth_texture_size: Size::new(1, 1),
|
depth_texture_size: Size::new(1, 1),
|
||||||
depth_view,
|
depth_view,
|
||||||
|
msaa_view,
|
||||||
|
surface_format: format,
|
||||||
gpu_wires: vec![],
|
gpu_wires: vec![],
|
||||||
gpu_hatches: vec![],
|
gpu_hatches: vec![],
|
||||||
gpu_wipeouts: vec![],
|
gpu_wipeouts: vec![],
|
||||||
|
|
@ -414,19 +427,24 @@ impl Pipeline {
|
||||||
encoder: &mut wgpu::CommandEncoder,
|
encoder: &mut wgpu::CommandEncoder,
|
||||||
target: &wgpu::TextureView,
|
target: &wgpu::TextureView,
|
||||||
clip_bounds: Rectangle<u32>,
|
clip_bounds: Rectangle<u32>,
|
||||||
|
bg_color: [f32; 4],
|
||||||
) {
|
) {
|
||||||
let vp = clip_bounds;
|
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 ────────────────────────────────────────────
|
// ── Pass 1: hatch fills ────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("hatch.render_pass"),
|
label: Some("hatch.render_pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: target,
|
view: msaa,
|
||||||
depth_slice: None,
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
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,
|
store: wgpu::StoreOp::Store,
|
||||||
},
|
},
|
||||||
})],
|
})],
|
||||||
|
|
@ -465,7 +483,7 @@ impl Pipeline {
|
||||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("image.render_pass"),
|
label: Some("image.render_pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: target,
|
view: msaa,
|
||||||
depth_slice: None,
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
|
|
@ -506,7 +524,7 @@ impl Pipeline {
|
||||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("mesh.render_pass"),
|
label: Some("mesh.render_pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: target,
|
view: msaa,
|
||||||
depth_slice: None,
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
|
|
@ -549,7 +567,7 @@ impl Pipeline {
|
||||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("wire.render_pass"),
|
label: Some("wire.render_pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: target,
|
view: msaa,
|
||||||
depth_slice: None,
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
|
|
@ -591,7 +609,7 @@ impl Pipeline {
|
||||||
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("wipeout.render_pass"),
|
label: Some("wipeout.render_pass"),
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: target,
|
view: msaa,
|
||||||
depth_slice: None,
|
depth_slice: None,
|
||||||
resolve_target: None,
|
resolve_target: None,
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
|
|
@ -626,12 +644,36 @@ impl Pipeline {
|
||||||
pass.draw(0..6, 0..1);
|
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>) {
|
pub fn ensure_depth_texture(&mut self, device: &wgpu::Device, size: Size<u32>) {
|
||||||
if self.depth_texture_size != size {
|
if self.depth_texture_size != size {
|
||||||
let tex = create_depth_texture(device, size);
|
let depth_tex = create_depth_texture(device, size);
|
||||||
self.depth_view = tex.create_view(&wgpu::TextureViewDescriptor::default());
|
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;
|
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,
|
depth_or_array_layers: 1,
|
||||||
},
|
},
|
||||||
mip_level_count: 1,
|
mip_level_count: 1,
|
||||||
sample_count: 1,
|
sample_count: MSAA_SAMPLES,
|
||||||
dimension: wgpu::TextureDimension::D2,
|
dimension: wgpu::TextureDimension::D2,
|
||||||
format: wgpu::TextureFormat::Depth32Float,
|
format: wgpu::TextureFormat::Depth32Float,
|
||||||
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
|
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 {
|
impl iced::widget::shader::Pipeline for Pipeline {
|
||||||
fn new(device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat) -> Self {
|
fn new(device: &wgpu::Device, queue: &wgpu::Queue, format: wgpu::TextureFormat) -> Self {
|
||||||
Self::new(device, queue, format)
|
Self::new(device, queue, format)
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ pub struct Primitive {
|
||||||
/// Used by the ViewCube pipeline — no gimbal lock.
|
/// Used by the ViewCube pipeline — no gimbal lock.
|
||||||
pub(super) cam_rotation: Mat4,
|
pub(super) cam_rotation: Mat4,
|
||||||
pub(super) hover_region: Option<usize>,
|
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 ──────────────────────────────────────────────────
|
// ── 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());
|
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 {
|
Primitive {
|
||||||
wires: all_wires,
|
wires: all_wires,
|
||||||
hatches: self.synced_hatch_models(),
|
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),
|
uniforms: Uniforms::new(&cam, bounds),
|
||||||
cam_rotation: cam.view_rotation_mat(),
|
cam_rotation: cam.view_rotation_mat(),
|
||||||
hover_region: state.hover_region,
|
hover_region: state.hover_region,
|
||||||
|
bg_color,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,7 +158,7 @@ impl shader::Primitive for Primitive {
|
||||||
target: &iced::wgpu::TextureView,
|
target: &iced::wgpu::TextureView,
|
||||||
clip: &Rectangle<u32>,
|
clip: &Rectangle<u32>,
|
||||||
) {
|
) {
|
||||||
pipeline.render(encoder, target, *clip);
|
pipeline.render(encoder, target, *clip, self.bg_color);
|
||||||
pipeline.viewcube.render(encoder, target, *clip);
|
pipeline.viewcube.render(encoder, target, *clip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue