fix: Glatte Strichkanten (Issue #7)
Die Zeichenfläche wird mit 4× Multisampling gezeichnet und in die Fensterfläche aufgelöst; Strichkanten, Formen und Bildränder sind damit kantengeglättet (ohne Unterstützung des Formats wie bisher). Runde Strichenden und Punkte sind feiner unterteilt (10 statt 4 Schritte je Viertelkreis, 20 statt 8 für Punkte) und wirken auch stark vergrößert rund. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+64
-5
@@ -160,6 +160,9 @@ pub struct CanvasState {
|
|||||||
pub surface: wgpu::Surface<'static>,
|
pub surface: wgpu::Surface<'static>,
|
||||||
/// Verwendeter Grafikadapter (für erneute Abfrage der Surface-Fähigkeiten bei Größenänderung)
|
/// Verwendeter Grafikadapter (für erneute Abfrage der Surface-Fähigkeiten bei Größenänderung)
|
||||||
adapter: wgpu::Adapter,
|
adapter: wgpu::Adapter,
|
||||||
|
/// Multisampling der Zeichenfläche (1 = aus) und das zugehörige Zwischenbild
|
||||||
|
msaa_samples: u32,
|
||||||
|
msaa_view: Option<wgpu::TextureView>,
|
||||||
/// Meldung „keine Darstellungsmodi“ wurde bereits ausgegeben
|
/// Meldung „keine Darstellungsmodi“ wurde bereits ausgegeben
|
||||||
presentation_warned: bool,
|
presentation_warned: bool,
|
||||||
pub device: wgpu::Device,
|
pub device: wgpu::Device,
|
||||||
@@ -326,6 +329,18 @@ impl CanvasState {
|
|||||||
};
|
};
|
||||||
surface.configure(&device, &config);
|
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
|
// 5. Shader & Bind Group Layout
|
||||||
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
|
||||||
label: Some("Ink WGSL Shader"),
|
label: Some("Ink WGSL Shader"),
|
||||||
@@ -406,7 +421,10 @@ impl CanvasState {
|
|||||||
conservative: false,
|
conservative: false,
|
||||||
},
|
},
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
multisample: wgpu::MultisampleState::default(),
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: msaa_samples,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
multiview: None,
|
multiview: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -438,7 +456,10 @@ impl CanvasState {
|
|||||||
conservative: false,
|
conservative: false,
|
||||||
},
|
},
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
multisample: wgpu::MultisampleState::default(),
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: msaa_samples,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
multiview: None,
|
multiview: None,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -487,6 +508,7 @@ impl CanvasState {
|
|||||||
&shader,
|
&shader,
|
||||||
&bind_group_layout,
|
&bind_group_layout,
|
||||||
config.format,
|
config.format,
|
||||||
|
msaa_samples,
|
||||||
);
|
);
|
||||||
|
|
||||||
// 8. egui Renderer für das schwebende UI initialisieren
|
// 8. egui Renderer für das schwebende UI initialisieren
|
||||||
@@ -495,6 +517,8 @@ impl CanvasState {
|
|||||||
Ok(Self {
|
Ok(Self {
|
||||||
surface,
|
surface,
|
||||||
adapter,
|
adapter,
|
||||||
|
msaa_samples,
|
||||||
|
msaa_view,
|
||||||
presentation_warned: false,
|
presentation_warned: false,
|
||||||
device,
|
device,
|
||||||
queue,
|
queue,
|
||||||
@@ -643,6 +667,7 @@ impl CanvasState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.surface.configure(&self.device, &self.config);
|
self.surface.configure(&self.device, &self.config);
|
||||||
|
self.msaa_view = create_msaa_view(&self.device, &self.config, self.msaa_samples);
|
||||||
self.recalculate_viewport();
|
self.recalculate_viewport();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1264,12 +1289,17 @@ impl CanvasState {
|
|||||||
{
|
{
|
||||||
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
|
||||||
label: Some("Ink Canvas Render Pass"),
|
label: Some("Ink Canvas Render Pass"),
|
||||||
|
// Mit Multisampling ins Zwischenbild zeichnen und in die Fensterfläche auflösen
|
||||||
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
|
||||||
view: &view,
|
view: self.msaa_view.as_ref().unwrap_or(&view),
|
||||||
resolve_target: None,
|
resolve_target: self.msaa_view.as_ref().map(|_| &view),
|
||||||
ops: wgpu::Operations {
|
ops: wgpu::Operations {
|
||||||
load: wgpu::LoadOp::Clear(self.paper_theme.desk_color()), // Tischhintergrund
|
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,
|
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<wgpu::TextureView> {
|
||||||
|
(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.
|
/// Zu rasterndes Textfeld.
|
||||||
struct TextSpec {
|
struct TextSpec {
|
||||||
text: crate::text::TextBox,
|
text: crate::text::TextBox,
|
||||||
|
|||||||
+5
-1
@@ -102,6 +102,7 @@ impl ImageLayer {
|
|||||||
shader: &wgpu::ShaderModule,
|
shader: &wgpu::ShaderModule,
|
||||||
camera_layout: &wgpu::BindGroupLayout,
|
camera_layout: &wgpu::BindGroupLayout,
|
||||||
format: wgpu::TextureFormat,
|
format: wgpu::TextureFormat,
|
||||||
|
sample_count: u32,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
let texture_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
|
||||||
label: Some("Image Texture Bind Group Layout"),
|
label: Some("Image Texture Bind Group Layout"),
|
||||||
@@ -151,7 +152,10 @@ impl ImageLayer {
|
|||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
depth_stencil: None,
|
depth_stencil: None,
|
||||||
multisample: wgpu::MultisampleState::default(),
|
multisample: wgpu::MultisampleState {
|
||||||
|
count: sample_count,
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
multiview: None,
|
multiview: None,
|
||||||
});
|
});
|
||||||
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
|
||||||
|
|||||||
+3
-2
@@ -274,7 +274,7 @@ pub fn generate_triangle_strip(
|
|||||||
// Einzelner Punkt (Stift-Tippen / Dot): Erzeuge kreisförmiges Pad
|
// Einzelner Punkt (Stift-Tippen / Dot): Erzeuge kreisförmiges Pad
|
||||||
let pt = control_points[0];
|
let pt = control_points[0];
|
||||||
let radius = calculate_radius(pt.pressure, config.min_radius, config.max_radius);
|
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);
|
let mut vertices = Vec::with_capacity((segments + 1) * 2);
|
||||||
|
|
||||||
for i in 0..=segments {
|
for i in 0..=segments {
|
||||||
@@ -599,7 +599,8 @@ pub fn generate_finalized_triangle_strip(
|
|||||||
return Vec::new();
|
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();
|
let mut vertices = Vec::new();
|
||||||
|
|
||||||
// 3. Runde Start-Kappe vor dem ersten Run
|
// 3. Runde Start-Kappe vor dem ersten Run
|
||||||
|
|||||||
Reference in New Issue
Block a user