render2d: 4x MSAA fuer glatte Linien (wie im Browser)

Beide Pipelines (Fill/Line) rendern mit sample_count=4 in eine lazily verwaltete
Multisample-Textur (ensure_msaa, Muster analog render3d::ensure_depth) und
resolven in die Surface-View. Kantige Haarlinien im nativen 2D-Viewport werden
dadurch knackscharf; Renderer-API unveraendert.
This commit is contained in:
2026-07-02 19:44:14 +02:00
parent 483ad3f278
commit 0a1aaedef8
+54 -4
View File
@@ -22,6 +22,9 @@ use crate::shaders::{FILL_WGSL, LINE_WGSL};
use crate::tessellate::{compile_scene, GpuGeometry};
use crate::types::{Scene, ViewBox};
/// MSAA-Faktor: 4x Multisampling fuer glatte Linien-/Fuellkanten (wie im Browser).
const SAMPLE_COUNT: u32 = 4;
/// Uniform-Block, 1:1 zum WGSL-`Globals`-Struct. std140-kompatibel ausgelegt
/// (16-Byte-Alignment der mat4/vec4, Skalare am Ende gepackt).
#[repr(C)]
@@ -75,6 +78,12 @@ pub struct Renderer {
/// Aktuell allozierter Uniform-Puffer + zugehoerige Bind-Group (wachsen bei Bedarf).
uniform: Option<UniformArena>,
scene: Option<SceneBuffers>,
/// Farbformat der Ziel-Surface (auch Format der MSAA-Textur).
format: wgpu::TextureFormat,
/// Multisample-Farbtextur (4x), lazily an die Ziel-Groesse gebunden.
msaa_view: Option<wgpu::TextureView>,
/// Groesse, fuer die `msaa_view` angelegt wurde.
msaa_size: (u32, u32),
/// Loeschfarbe (Zeichenblatt), Default hell wie im WebGL-Pfad (#f0f0f0).
pub clear_color: wgpu::Color,
/// Papier-Massstab-Nenner (1:N) fuer echte mm-Strichbreiten.
@@ -161,7 +170,11 @@ impl Renderer {
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multisample: wgpu::MultisampleState {
count: SAMPLE_COUNT,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
@@ -193,7 +206,11 @@ impl Renderer {
..Default::default()
},
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multisample: wgpu::MultisampleState {
count: SAMPLE_COUNT,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
cache: None,
});
@@ -209,6 +226,9 @@ impl Renderer {
uniform_stride,
uniform: None,
scene: None,
format: color_format,
msaa_view: None,
msaa_size: (0, 0),
// #f0f0f0 (== --sheet). 0.941 entspricht dem WebGL-clearColor.
clear_color: wgpu::Color {
r: 0.941,
@@ -289,6 +309,31 @@ impl Renderer {
});
}
/// Stellt sicher, dass eine 4x-MSAA-Farbtextur passend zur Ziel-Groesse
/// existiert; realloziert nur bei Groessenaenderung (analog ensure_depth in 3D).
fn ensure_msaa(&mut self, device: &wgpu::Device, w: u32, h: u32) {
let (w, h) = (w.max(1), h.max(1));
if self.msaa_view.is_some() && self.msaa_size == (w, h) {
return;
}
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: Some("msaa.color"),
size: wgpu::Extent3d {
width: w,
height: h,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: SAMPLE_COUNT,
dimension: wgpu::TextureDimension::D2,
format: self.format,
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
self.msaa_view = Some(texture.create_view(&wgpu::TextureViewDescriptor::default()));
self.msaa_size = (w, h);
}
/// Zeichnet einen Frame in `view` (Surface- oder Offscreen-Textur).
/// Pan/Zoom kommt ueber `view_box`/`viewport`; nur die Ortho-Matrix aendert sich.
pub fn render(
@@ -343,6 +388,11 @@ impl Renderer {
}
}
// In die 4x-MSAA-Textur zeichnen und in die Surface aufloesen (Resolve):
// das glaettet alle Fuell-/Linienkanten, die Aufrufer bleiben unveraendert.
self.ensure_msaa(device, viewport.0, viewport.1);
let msaa_view = self.msaa_view.as_ref().unwrap();
let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("2d.encoder"),
});
@@ -350,8 +400,8 @@ impl Renderer {
let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: Some("2d.pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view,
resolve_target: None,
view: msaa_view,
resolve_target: Some(view),
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(self.clear_color),
store: wgpu::StoreOp::Store,