diff --git a/src/canvas.rs b/src/canvas.rs index 291b0c0..a0f5060 100644 --- a/src/canvas.rs +++ b/src/canvas.rs @@ -160,6 +160,9 @@ pub struct CanvasState { pub surface: wgpu::Surface<'static>, /// Verwendeter Grafikadapter (für erneute Abfrage der Surface-Fähigkeiten bei Größenänderung) adapter: wgpu::Adapter, + /// Multisampling der Zeichenfläche (1 = aus) und das zugehörige Zwischenbild + msaa_samples: u32, + msaa_view: Option, /// Meldung „keine Darstellungsmodi“ wurde bereits ausgegeben presentation_warned: bool, pub device: wgpu::Device, @@ -326,6 +329,18 @@ impl CanvasState { }; surface.configure(&device, &config); + // Kantenglättung der Zeichenfläche (4× Multisampling), falls das Format es erlaubt + let msaa_samples = if adapter + .get_texture_format_features(config.format) + .flags + .sample_count_supported(MSAA_SAMPLES) + { + MSAA_SAMPLES + } else { + 1 + }; + let msaa_view = create_msaa_view(&device, &config, msaa_samples); + // 5. Shader & Bind Group Layout let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Ink WGSL Shader"), @@ -406,7 +421,10 @@ impl CanvasState { conservative: false, }, depth_stencil: None, - multisample: wgpu::MultisampleState::default(), + multisample: wgpu::MultisampleState { + count: msaa_samples, + ..Default::default() + }, multiview: None, }); @@ -438,7 +456,10 @@ impl CanvasState { conservative: false, }, depth_stencil: None, - multisample: wgpu::MultisampleState::default(), + multisample: wgpu::MultisampleState { + count: msaa_samples, + ..Default::default() + }, multiview: None, }); @@ -487,6 +508,7 @@ impl CanvasState { &shader, &bind_group_layout, config.format, + msaa_samples, ); // 8. egui Renderer für das schwebende UI initialisieren @@ -495,6 +517,8 @@ impl CanvasState { Ok(Self { surface, adapter, + msaa_samples, + msaa_view, presentation_warned: false, device, queue, @@ -643,6 +667,7 @@ impl CanvasState { } } self.surface.configure(&self.device, &self.config); + self.msaa_view = create_msaa_view(&self.device, &self.config, self.msaa_samples); self.recalculate_viewport(); } } @@ -1264,12 +1289,17 @@ impl CanvasState { { let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Ink Canvas Render Pass"), + // Mit Multisampling ins Zwischenbild zeichnen und in die Fensterfläche auflösen color_attachments: &[Some(wgpu::RenderPassColorAttachment { - view: &view, - resolve_target: None, + view: self.msaa_view.as_ref().unwrap_or(&view), + resolve_target: self.msaa_view.as_ref().map(|_| &view), ops: wgpu::Operations { load: wgpu::LoadOp::Clear(self.paper_theme.desk_color()), // Tischhintergrund - store: wgpu::StoreOp::Store, + store: if self.msaa_view.is_some() { + wgpu::StoreOp::Discard + } else { + wgpu::StoreOp::Store + }, }, })], depth_stencil_attachment: None, @@ -1376,6 +1406,35 @@ impl CanvasState { } } +/// Multisampling der Zeichenfläche gegen gezackte Strichkanten. +const MSAA_SAMPLES: u32 = 4; + +/// Zwischenbild für das Multisampling in Fenstergröße; `None` ohne Multisampling. +fn create_msaa_view( + device: &wgpu::Device, + config: &wgpu::SurfaceConfiguration, + samples: u32, +) -> Option { + (samples > 1).then(|| { + device + .create_texture(&wgpu::TextureDescriptor { + label: Some("MSAA Color Target"), + size: wgpu::Extent3d { + width: config.width.max(1), + height: config.height.max(1), + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: samples, + dimension: wgpu::TextureDimension::D2, + format: config.format, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + view_formats: &[], + }) + .create_view(&wgpu::TextureViewDescriptor::default()) + }) +} + /// Zu rasterndes Textfeld. struct TextSpec { text: crate::text::TextBox, diff --git a/src/image_layer.rs b/src/image_layer.rs index f7532fc..ac47334 100644 --- a/src/image_layer.rs +++ b/src/image_layer.rs @@ -102,6 +102,7 @@ impl ImageLayer { shader: &wgpu::ShaderModule, camera_layout: &wgpu::BindGroupLayout, format: wgpu::TextureFormat, + sample_count: u32, ) -> Self { let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { label: Some("Image Texture Bind Group Layout"), @@ -151,7 +152,10 @@ impl ImageLayer { ..Default::default() }, depth_stencil: None, - multisample: wgpu::MultisampleState::default(), + multisample: wgpu::MultisampleState { + count: sample_count, + ..Default::default() + }, multiview: None, }); let sampler = device.create_sampler(&wgpu::SamplerDescriptor { diff --git a/src/spline.rs b/src/spline.rs index fba5b56..1c4153d 100644 --- a/src/spline.rs +++ b/src/spline.rs @@ -274,7 +274,7 @@ pub fn generate_triangle_strip( // Einzelner Punkt (Stift-Tippen / Dot): Erzeuge kreisförmiges Pad let pt = control_points[0]; let radius = calculate_radius(pt.pressure, config.min_radius, config.max_radius); - let segments = 8; + let segments = 20; let mut vertices = Vec::with_capacity((segments + 1) * 2); for i in 0..=segments { @@ -599,7 +599,8 @@ pub fn generate_finalized_triangle_strip( return Vec::new(); } - const CAP_STEPS: usize = 4; + // Runde Kappen fein genug unterteilt, dass sie auch stark vergrößert rund wirken + const CAP_STEPS: usize = 10; let mut vertices = Vec::new(); // 3. Runde Start-Kappe vor dem ersten Run