wgpu 22 → 29, glyphon 0.6 → 0.11: requestDevice-Shim entfällt

glyphon 0.11 ist die neueste zu wgpu 29 passende Version (wgpu 30 existiert
bereits, glyphon pinnt aber ^29). Das 22er-Limit maxInterStageShaderComponents
wird von 29 nicht mehr in requiredLimits gesendet — src/engine/requestDeviceShim.ts
komplett entfernt, beide Hook-Importstellen (useWasmPlanRenderer, useWasm3dRenderer)
angepasst. pollster 0.3→0.4, naga 22→29 mitgezogen. Alle Draw-/Text-Pfade
(draw_sequence, glyphon ColorMode::Web, widthScreen-Polylinien, Headless/Golden)
unverändert funktionsfähig.

Verifiziert: cargo check nativ (render2d/render3d/src-tauri) grün, cargo test
render2d 17/17 + Golden bit-exakt, render3d 29/29, wasm32 --features web für
beide grün, npx tsc -b + npm run build grün.
This commit is contained in:
2026-07-03 18:17:41 +02:00
parent 382771b2e7
commit b4c4a2cc6b
17 changed files with 1794 additions and 1124 deletions
+386 -368
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -47,5 +47,5 @@ render2d = { path = "render2d", features = ["window"], optional = true }
render3d = { path = "render3d", features = ["window"], optional = true } render3d = { path = "render3d", features = ["window"], optional = true }
# Versionen an render2d gekoppelt (identische raw-window-handle 0.6-Kette). # Versionen an render2d gekoppelt (identische raw-window-handle 0.6-Kette).
winit = { version = "0.30", optional = true } winit = { version = "0.30", optional = true }
wgpu = { version = "22", optional = true } wgpu = { version = "29", optional = true }
pollster = { version = "0.3", optional = true } pollster = { version = "0.4", optional = true }
+795 -288
View File
File diff suppressed because it is too large Load Diff
+9 -9
View File
@@ -66,15 +66,15 @@ serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", optional = true } serde_json = { version = "1", optional = true }
# --- GPU (nur mit Feature "render") ------------------------------------------ # --- GPU (nur mit Feature "render") ------------------------------------------
# Standard-Features von wgpu (wgsl, webgpu, dx12, metal). Die Linux-Backends # Standard-Features von wgpu (wgsl, webgpu, dx12, metal + target-spezifisch
# Vulkan/GLES aktiviert wgpu selbst ueber seine target-spezifischen Defaults — # Vulkan/GLES auf Linux). wgpu 30 ist bereits erschienen, aber glyphon 0.11
# es gibt in wgpu 22 keine expliziten `vulkan`/`gles`-Feature-Namen. # pinnt `wgpu ^29` — 29 ist damit die neueste Version, bei der beide zusammenpassen.
wgpu = { version = "22", optional = true } wgpu = { version = "29", optional = true }
bytemuck = { version = "1", optional = true, features = ["derive"] } bytemuck = { version = "1", optional = true, features = ["derive"] }
pollster = { version = "0.3", optional = true } pollster = { version = "0.4", optional = true }
# Echte TrueType-Glyphen ueber einen GPU-Atlas (cosmic-text + etagere). Version # Echte TrueType-Glyphen ueber einen GPU-Atlas (cosmic-text + etagere). Version
# an wgpu gekoppelt: die 0.6er-Reihe ist die zu wgpu 22 passende. # an wgpu gekoppelt: die 0.11er-Reihe ist die zu wgpu 29 passende.
glyphon = { version = "0.6", optional = true } glyphon = { version = "0.11", optional = true }
# --- Headless-PNG-Export (nur mit Feature "headless") ------------------------ # --- Headless-PNG-Export (nur mit Feature "headless") ------------------------
# Nur der PNG-Codec noetig (kein volles Format-Universum) -> default-features aus. # Nur der PNG-Codec noetig (kein volles Format-Universum) -> default-features aus.
@@ -103,8 +103,8 @@ web-sys = { version = "0.3", optional = true, features = [
[dev-dependencies] [dev-dependencies]
serde_json = "1" serde_json = "1"
# naga validiert die WGSL-Quellen headless (Parser + Validator) im Test, ohne # naga validiert die WGSL-Quellen headless (Parser + Validator) im Test, ohne
# GPU/Display. Version an wgpu 22 gekoppelt (transitiv ohnehin vorhanden). # GPU/Display. Version an wgpu gekoppelt (transitiv ohnehin vorhanden).
naga = { version = "22", features = ["wgsl-in"] } naga = { version = "29", features = ["wgsl-in"] }
# Der Fenster-Spike ist nur mit Feature "window" verfuegbar; ohne das Feature # Der Fenster-Spike ist nur mit Feature "window" verfuegbar; ohne das Feature
# wird das Binary aus dem Build ausgeschlossen (required-features). # wird das Binary aus dem Build ausgeschlossen (required-features).
+11 -9
View File
@@ -43,15 +43,14 @@ impl GpuState {
compatible_surface: Some(&surface), compatible_surface: Some(&surface),
})) }))
.expect("kein passender GPU-Adapter"); .expect("kein passender GPU-Adapter");
let (device, queue) = pollster::block_on(adapter.request_device( let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
&wgpu::DeviceDescriptor {
label: Some("2d.device"), label: Some("2d.device"),
required_features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(), required_limits: wgpu::Limits::default(),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
memory_hints: wgpu::MemoryHints::Performance, memory_hints: wgpu::MemoryHints::Performance,
}, trace: wgpu::Trace::Off,
None, }))
))
.expect("Device anfordern"); .expect("Device anfordern");
let caps = surface.get_capabilities(&adapter); let caps = surface.get_capabilities(&adapter);
@@ -96,14 +95,17 @@ impl GpuState {
} }
fn render(&mut self, view_box: ViewBox) { fn render(&mut self, view_box: ViewBox) {
// wgpu 29: `get_current_texture` liefert ein Enum statt eines Results —
// Suboptimal ist weiterhin praesentierbar, Lost/Outdated -> rekonfigurieren.
let frame = match self.surface.get_current_texture() { let frame = match self.surface.get_current_texture() {
Ok(f) => f, wgpu::CurrentSurfaceTexture::Success(f)
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { | wgpu::CurrentSurfaceTexture::Suboptimal(f) => f,
wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
return; return;
} }
Err(e) => { other => {
eprintln!("Surface-Fehler: {e:?}"); eprintln!("Surface-Fehler: {other:?}");
return; return;
} }
}; };
+25 -14
View File
@@ -222,8 +222,8 @@ impl Renderer {
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("2d.layout"), label: Some("2d.layout"),
bind_group_layouts: &[&bind_group_layout], bind_group_layouts: &[Some(&bind_group_layout)],
push_constant_ranges: &[], immediate_size: 0,
}); });
// Alpha-Blending (SRC_ALPHA, ONE_MINUS_SRC_ALPHA) wie im WebGL-Pfad. // Alpha-Blending (SRC_ALPHA, ONE_MINUS_SRC_ALPHA) wie im WebGL-Pfad.
@@ -245,13 +245,13 @@ impl Renderer {
layout: Some(&pipeline_layout), layout: Some(&pipeline_layout),
vertex: wgpu::VertexState { vertex: wgpu::VertexState {
module: &fill_module, module: &fill_module,
entry_point: "vs_main", entry_point: Some("vs_main"),
buffers: &[fill_vertex_layout], buffers: &[fill_vertex_layout],
compilation_options: Default::default(), compilation_options: Default::default(),
}, },
fragment: Some(wgpu::FragmentState { fragment: Some(wgpu::FragmentState {
module: &fill_module, module: &fill_module,
entry_point: "fs_main", entry_point: Some("fs_main"),
targets: &[Some(color_target.clone())], targets: &[Some(color_target.clone())],
compilation_options: Default::default(), compilation_options: Default::default(),
}), }),
@@ -267,7 +267,7 @@ impl Renderer {
mask: !0, mask: !0,
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
multiview: None, multiview_mask: None,
cache: None, cache: None,
}); });
@@ -282,13 +282,13 @@ impl Renderer {
layout: Some(&pipeline_layout), layout: Some(&pipeline_layout),
vertex: wgpu::VertexState { vertex: wgpu::VertexState {
module: &line_module, module: &line_module,
entry_point: "vs_main", entry_point: Some("vs_main"),
buffers: &[line_vertex_layout], buffers: &[line_vertex_layout],
compilation_options: Default::default(), compilation_options: Default::default(),
}, },
fragment: Some(wgpu::FragmentState { fragment: Some(wgpu::FragmentState {
module: &line_module, module: &line_module,
entry_point: "fs_main", entry_point: Some("fs_main"),
targets: &[Some(color_target)], targets: &[Some(color_target)],
compilation_options: Default::default(), compilation_options: Default::default(),
}), }),
@@ -303,7 +303,7 @@ impl Renderer {
mask: !0, mask: !0,
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
multiview: None, multiview_mask: None,
cache: None, cache: None,
}); });
@@ -332,8 +332,8 @@ impl Renderer {
let arc_pipeline_layout = let arc_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("arc.pipeline.layout"), label: Some("arc.pipeline.layout"),
bind_group_layouts: &[&arc_bind_group_layout], bind_group_layouts: &[Some(&arc_bind_group_layout)],
push_constant_ranges: &[], immediate_size: 0,
}); });
// Instanz-Layout: [center vec2, geom vec4, color vec4, wdash vec4, dash vec4], // Instanz-Layout: [center vec2, geom vec4, color vec4, wdash vec4, dash vec4],
// stride 18*4, step_mode Instance. Das Quad kommt aus @builtin(vertex_index). // stride 18*4, step_mode Instance. Das Quad kommt aus @builtin(vertex_index).
@@ -349,13 +349,13 @@ impl Renderer {
layout: Some(&arc_pipeline_layout), layout: Some(&arc_pipeline_layout),
vertex: wgpu::VertexState { vertex: wgpu::VertexState {
module: &arc_module, module: &arc_module,
entry_point: "vs_main", entry_point: Some("vs_main"),
buffers: &[arc_instance_layout], buffers: &[arc_instance_layout],
compilation_options: Default::default(), compilation_options: Default::default(),
}, },
fragment: Some(wgpu::FragmentState { fragment: Some(wgpu::FragmentState {
module: &arc_module, module: &arc_module,
entry_point: "fs_main", entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState { targets: &[Some(wgpu::ColorTargetState {
format: color_format, format: color_format,
blend, blend,
@@ -374,7 +374,7 @@ impl Renderer {
mask: !0, mask: !0,
alpha_to_coverage_enabled: false, alpha_to_coverage_enabled: false,
}, },
multiview: None, multiview_mask: None,
cache: None, cache: None,
}); });
let arc_uniform = device.create_buffer(&wgpu::BufferDescriptor { let arc_uniform = device.create_buffer(&wgpu::BufferDescriptor {
@@ -724,7 +724,15 @@ impl Renderer {
); );
// Unbegrenzt: eine Zeile, kein Umbruch (Zeilen sind vorgeflacht). // Unbegrenzt: eine Zeile, kein Umbruch (Zeilen sind vorgeflacht).
buffer.set_size(&mut ts.font_system, None, None); buffer.set_size(&mut ts.font_system, None, None);
buffer.set_text(&mut ts.font_system, &t.content, attrs, glyphon::Shaping::Advanced); // Kein Align-Override (None): die Ausrichtung rechnet der
// Platzierungscode unten selbst ueber die gemessene Zeilenbreite.
buffer.set_text(
&mut ts.font_system,
&t.content,
&attrs,
glyphon::Shaping::Advanced,
None,
);
// Gemessene Breite + Baseline fuer Ausrichtung/Anker. // Gemessene Breite + Baseline fuer Ausrichtung/Anker.
let (line_w, baseline) = buffer let (line_w, baseline) = buffer
@@ -817,6 +825,8 @@ impl Renderer {
label: Some("2d.pass"), label: Some("2d.pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment { color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: msaa_view, view: msaa_view,
// Ziel ist eine 2D-Textur (kein 3D-Volumen) -> kein Depth-Slice.
depth_slice: None,
resolve_target: Some(view), resolve_target: Some(view),
ops: wgpu::Operations { ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(self.clear_color), load: wgpu::LoadOp::Clear(self.clear_color),
@@ -826,6 +836,7 @@ impl Renderer {
depth_stencil_attachment: None, depth_stencil_attachment: None,
timestamp_writes: None, timestamp_writes: None,
occlusion_query_set: None, occlusion_query_set: None,
multiview_mask: None,
}); });
if let (Some(scene), Some(u)) = (&self.scene, &self.uniform) { if let (Some(scene), Some(u)) = (&self.scene, &self.uniform) {
+14 -12
View File
@@ -87,7 +87,7 @@ impl HeadlessRenderer {
pub fn new() -> Result<Self, String> { pub fn new() -> Result<Self, String> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::VULKAN, backends: wgpu::Backends::VULKAN,
..Default::default() ..wgpu::InstanceDescriptor::new_without_display_handle()
}); });
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions { let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance, power_preference: wgpu::PowerPreference::HighPerformance,
@@ -95,16 +95,15 @@ impl HeadlessRenderer {
// Kein Fenster -> keine kompatible Surface noetig. // Kein Fenster -> keine kompatible Surface noetig.
compatible_surface: None, compatible_surface: None,
})) }))
.ok_or_else(|| "kein Vulkan-Adapter fuer Headless-Rendering gefunden".to_string())?; .map_err(|e| format!("kein Vulkan-Adapter fuer Headless-Rendering gefunden: {e}"))?;
let (device, queue) = pollster::block_on(adapter.request_device( let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
&wgpu::DeviceDescriptor {
label: Some("headless.device"), label: Some("headless.device"),
required_features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(), required_limits: wgpu::Limits::default(),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
memory_hints: wgpu::MemoryHints::Performance, memory_hints: wgpu::MemoryHints::Performance,
}, trace: wgpu::Trace::Off,
None, }))
))
.map_err(|e| format!("Headless-Device anfordern fehlgeschlagen: {e}"))?; .map_err(|e| format!("Headless-Device anfordern fehlgeschlagen: {e}"))?;
let renderer = Renderer::new(&device, COLOR_FORMAT); let renderer = Renderer::new(&device, COLOR_FORMAT);
@@ -178,15 +177,15 @@ impl HeadlessRenderer {
label: Some("headless.copy"), label: Some("headless.copy"),
}); });
encoder.copy_texture_to_buffer( encoder.copy_texture_to_buffer(
wgpu::ImageCopyTexture { wgpu::TexelCopyTextureInfo {
texture, texture,
mip_level: 0, mip_level: 0,
origin: wgpu::Origin3d::ZERO, origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All, aspect: wgpu::TextureAspect::All,
}, },
wgpu::ImageCopyBuffer { wgpu::TexelCopyBufferInfo {
buffer: &output_buffer, buffer: &output_buffer,
layout: wgpu::ImageDataLayout { layout: wgpu::TexelCopyBufferLayout {
offset: 0, offset: 0,
bytes_per_row: Some(padded_bytes_per_row), bytes_per_row: Some(padded_bytes_per_row),
rows_per_image: Some(height), rows_per_image: Some(height),
@@ -206,8 +205,11 @@ impl HeadlessRenderer {
let _ = tx.send(res); let _ = tx.send(res);
}); });
// Vulkan hat keine Event-Loop wie ein Fenster -> Poll blockierend, bis // Vulkan hat keine Event-Loop wie ein Fenster -> Poll blockierend, bis
// die Map-Callback feuert (kein busy-loop noetig, `Maintain::Wait` wartet). // die Map-Callback feuert (kein busy-loop noetig, `PollType::Wait` wartet
self.device.poll(wgpu::Maintain::Wait); // ohne Timeout auf die juengste Submission).
self.device
.poll(wgpu::PollType::wait_indefinitely())
.expect("Device-Poll fehlgeschlagen");
rx.recv() rx.recv()
.expect("Map-Callback nie aufgerufen") .expect("Map-Callback nie aufgerufen")
.expect("Buffer-Mapping fehlgeschlagen"); .expect("Buffer-Mapping fehlgeschlagen");
+15 -11
View File
@@ -54,7 +54,7 @@ impl WebPlanRenderer {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::BROWSER_WEBGPU, backends: wgpu::Backends::BROWSER_WEBGPU,
..Default::default() ..wgpu::InstanceDescriptor::new_without_display_handle()
}); });
let surface = instance let surface = instance
.create_surface(wgpu::SurfaceTarget::Canvas(canvas)) .create_surface(wgpu::SurfaceTarget::Canvas(canvas))
@@ -67,19 +67,20 @@ impl WebPlanRenderer {
compatible_surface: Some(&surface), compatible_surface: Some(&surface),
}) })
.await .await
.ok_or_else(|| JsValue::from_str("kein WebGPU-Adapter (navigator.gpu?)"))?; .map_err(|e| {
JsValue::from_str(&format!("kein WebGPU-Adapter (navigator.gpu?): {e}"))
})?;
let (device, queue) = adapter let (device, queue) = adapter
.request_device( .request_device(&wgpu::DeviceDescriptor {
&wgpu::DeviceDescriptor {
label: Some("render2d.web.device"), label: Some("render2d.web.device"),
required_features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::downlevel_webgl2_defaults() required_limits: wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits()), .using_resolution(adapter.limits()),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
memory_hints: wgpu::MemoryHints::Performance, memory_hints: wgpu::MemoryHints::Performance,
}, trace: wgpu::Trace::Off,
None, })
)
.await .await
.map_err(|e| JsValue::from_str(&format!("request_device: {e:?}")))?; .map_err(|e| JsValue::from_str(&format!("request_device: {e:?}")))?;
@@ -169,15 +170,18 @@ impl WebPlanRenderer {
/// Zeichnet EINEN Frame in die Surface-Textur. /// Zeichnet EINEN Frame in die Surface-Textur.
pub fn render(&mut self) { pub fn render(&mut self) {
// wgpu 29: `get_current_texture` liefert ein Enum statt eines Results —
// Suboptimal ist weiterhin praesentierbar, Lost/Outdated -> rekonfigurieren.
let frame = match self.surface.get_current_texture() { let frame = match self.surface.get_current_texture() {
Ok(f) => f, wgpu::CurrentSurfaceTexture::Success(f)
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { | wgpu::CurrentSurfaceTexture::Suboptimal(f) => f,
wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
return; return;
} }
Err(e) => { other => {
web_sys::console::warn_1(&JsValue::from_str(&format!( web_sys::console::warn_1(&JsValue::from_str(&format!(
"render2d Surface-Fehler: {e:?}" "render2d Surface-Fehler: {other:?}"
))); )));
return; return;
} }
+449 -302
View File
File diff suppressed because it is too large Load Diff
+7 -7
View File
@@ -59,12 +59,12 @@ serde = { version = "1", features = ["derive"] }
serde_json = { version = "1", optional = true } serde_json = { version = "1", optional = true }
# --- GPU (nur mit Feature "render") ------------------------------------------ # --- GPU (nur mit Feature "render") ------------------------------------------
# Standard-Features von wgpu (wgsl, webgpu, dx12, metal). Die Linux-Backends # Standard-Features von wgpu (wgsl, webgpu, dx12, metal + target-spezifisch
# Vulkan/GLES aktiviert wgpu selbst ueber seine target-spezifischen Defaults — # Vulkan/GLES auf Linux). Version im Gleichschritt mit render2d: wgpu 29, weil
# es gibt in wgpu 22 keine expliziten `vulkan`/`gles`-Feature-Namen. # glyphon (dort) noch keine 30 unterstuetzt — beide Crates teilen den Stand.
wgpu = { version = "22", optional = true } wgpu = { version = "29", optional = true }
bytemuck = { version = "1", optional = true, features = ["derive"] } bytemuck = { version = "1", optional = true, features = ["derive"] }
pollster = { version = "0.3", optional = true } pollster = { version = "0.4", optional = true }
# --- Fenster (nur mit Feature "window") -------------------------------------- # --- Fenster (nur mit Feature "window") --------------------------------------
winit = { version = "0.30", optional = true } winit = { version = "0.30", optional = true }
@@ -87,8 +87,8 @@ web-sys = { version = "0.3", optional = true, features = [
[dev-dependencies] [dev-dependencies]
serde_json = "1" serde_json = "1"
# naga validiert die WGSL-Quellen headless (Parser + Validator) im Test, ohne # naga validiert die WGSL-Quellen headless (Parser + Validator) im Test, ohne
# GPU/Display. Version an wgpu 22 gekoppelt (transitiv ohnehin vorhanden). # GPU/Display. Version an wgpu gekoppelt (transitiv ohnehin vorhanden).
naga = { version = "22", features = ["wgsl-in"] } naga = { version = "29", features = ["wgsl-in"] }
# Der Fenster-Spike ist nur mit Feature "window" verfuegbar; ohne das Feature # Der Fenster-Spike ist nur mit Feature "window" verfuegbar; ohne das Feature
# wird das Binary aus dem Build ausgeschlossen (required-features). # wird das Binary aus dem Build ausgeschlossen (required-features).
+11 -9
View File
@@ -76,15 +76,14 @@ impl GpuState {
compatible_surface: Some(&surface), compatible_surface: Some(&surface),
})) }))
.expect("kein passender GPU-Adapter"); .expect("kein passender GPU-Adapter");
let (device, queue) = pollster::block_on(adapter.request_device( let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
&wgpu::DeviceDescriptor {
label: Some("3d.device"), label: Some("3d.device"),
required_features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(), required_limits: wgpu::Limits::default(),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
memory_hints: wgpu::MemoryHints::Performance, memory_hints: wgpu::MemoryHints::Performance,
}, trace: wgpu::Trace::Off,
None, }))
))
.expect("Device anfordern"); .expect("Device anfordern");
let caps = surface.get_capabilities(&adapter); let caps = surface.get_capabilities(&adapter);
@@ -129,14 +128,17 @@ impl GpuState {
} }
fn render(&mut self, camera: &Camera) { fn render(&mut self, camera: &Camera) {
// wgpu 29: `get_current_texture` liefert ein Enum statt eines Results —
// Suboptimal ist weiterhin praesentierbar, Lost/Outdated -> rekonfigurieren.
let frame = match self.surface.get_current_texture() { let frame = match self.surface.get_current_texture() {
Ok(f) => f, wgpu::CurrentSurfaceTexture::Success(f)
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { | wgpu::CurrentSurfaceTexture::Suboptimal(f) => f,
wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
return; return;
} }
Err(e) => { other => {
eprintln!("Surface-Fehler: {e:?}"); eprintln!("Surface-Fehler: {other:?}");
return; return;
} }
}; };
+10 -7
View File
@@ -108,8 +108,8 @@ impl Renderer {
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("3d.layout"), label: Some("3d.layout"),
bind_group_layouts: &[&bind_group_layout], bind_group_layouts: &[Some(&bind_group_layout)],
push_constant_ranges: &[], immediate_size: 0,
}); });
// Vertex-Layout: [pos vec3, normal vec3, color vec3], stride 9*4. // Vertex-Layout: [pos vec3, normal vec3, color vec3], stride 9*4.
@@ -124,13 +124,13 @@ impl Renderer {
layout: Some(&pipeline_layout), layout: Some(&pipeline_layout),
vertex: wgpu::VertexState { vertex: wgpu::VertexState {
module: &module, module: &module,
entry_point: "vs_main", entry_point: Some("vs_main"),
buffers: &[vertex_layout], buffers: &[vertex_layout],
compilation_options: Default::default(), compilation_options: Default::default(),
}, },
fragment: Some(wgpu::FragmentState { fragment: Some(wgpu::FragmentState {
module: &module, module: &module,
entry_point: "fs_main", entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState { targets: &[Some(wgpu::ColorTargetState {
format: color_format, format: color_format,
blend: Some(wgpu::BlendState::REPLACE), blend: Some(wgpu::BlendState::REPLACE),
@@ -147,13 +147,13 @@ impl Renderer {
}, },
depth_stencil: Some(wgpu::DepthStencilState { depth_stencil: Some(wgpu::DepthStencilState {
format: DEPTH_FORMAT, format: DEPTH_FORMAT,
depth_write_enabled: true, depth_write_enabled: Some(true),
depth_compare: wgpu::CompareFunction::Less, depth_compare: Some(wgpu::CompareFunction::Less),
stencil: wgpu::StencilState::default(), stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(), bias: wgpu::DepthBiasState::default(),
}), }),
multisample: wgpu::MultisampleState::default(), multisample: wgpu::MultisampleState::default(),
multiview: None, multiview_mask: None,
cache: None, cache: None,
}); });
@@ -292,6 +292,8 @@ impl Renderer {
label: Some("3d.pass"), label: Some("3d.pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment { color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view, view,
// Ziel ist eine 2D-Textur (kein 3D-Volumen) -> kein Depth-Slice.
depth_slice: None,
resolve_target: None, resolve_target: None,
ops: wgpu::Operations { ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(self.clear_color), load: wgpu::LoadOp::Clear(self.clear_color),
@@ -308,6 +310,7 @@ impl Renderer {
}), }),
timestamp_writes: None, timestamp_writes: None,
occlusion_query_set: None, occlusion_query_set: None,
multiview_mask: None,
}); });
if let Some(m) = &self.mesh { if let Some(m) = &self.mesh {
+15 -11
View File
@@ -63,7 +63,7 @@ impl WebModelRenderer {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor { let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::BROWSER_WEBGPU, backends: wgpu::Backends::BROWSER_WEBGPU,
..Default::default() ..wgpu::InstanceDescriptor::new_without_display_handle()
}); });
let surface = instance let surface = instance
.create_surface(wgpu::SurfaceTarget::Canvas(canvas)) .create_surface(wgpu::SurfaceTarget::Canvas(canvas))
@@ -76,19 +76,20 @@ impl WebModelRenderer {
compatible_surface: Some(&surface), compatible_surface: Some(&surface),
}) })
.await .await
.ok_or_else(|| JsValue::from_str("kein WebGPU-Adapter (navigator.gpu?)"))?; .map_err(|e| {
JsValue::from_str(&format!("kein WebGPU-Adapter (navigator.gpu?): {e}"))
})?;
let (device, queue) = adapter let (device, queue) = adapter
.request_device( .request_device(&wgpu::DeviceDescriptor {
&wgpu::DeviceDescriptor {
label: Some("render3d.web.device"), label: Some("render3d.web.device"),
required_features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::downlevel_webgl2_defaults() required_limits: wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits()), .using_resolution(adapter.limits()),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
memory_hints: wgpu::MemoryHints::Performance, memory_hints: wgpu::MemoryHints::Performance,
}, trace: wgpu::Trace::Off,
None, })
)
.await .await
.map_err(|e| JsValue::from_str(&format!("request_device: {e:?}")))?; .map_err(|e| JsValue::from_str(&format!("request_device: {e:?}")))?;
@@ -192,15 +193,18 @@ impl WebModelRenderer {
/// Zeichnet EINEN Frame in die Surface-Textur. /// Zeichnet EINEN Frame in die Surface-Textur.
pub fn render(&mut self) { pub fn render(&mut self) {
// wgpu 29: `get_current_texture` liefert ein Enum statt eines Results —
// Suboptimal ist weiterhin praesentierbar, Lost/Outdated -> rekonfigurieren.
let frame = match self.surface.get_current_texture() { let frame = match self.surface.get_current_texture() {
Ok(f) => f, wgpu::CurrentSurfaceTexture::Success(f)
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { | wgpu::CurrentSurfaceTexture::Suboptimal(f) => f,
wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
return; return;
} }
Err(e) => { other => {
web_sys::console::warn_1(&JsValue::from_str(&format!( web_sys::console::warn_1(&JsValue::from_str(&format!(
"render3d Surface-Fehler: {e:?}" "render3d Surface-Fehler: {other:?}"
))); )));
return; return;
} }
+19 -13
View File
@@ -139,14 +139,17 @@ impl GpuState2d {
} }
fn render(&mut self, view_box: ViewBox) { fn render(&mut self, view_box: ViewBox) {
// wgpu 29: `get_current_texture` liefert ein Enum statt eines Results —
// Suboptimal ist weiterhin praesentierbar, Lost/Outdated -> rekonfigurieren.
let frame = match self.surface.get_current_texture() { let frame = match self.surface.get_current_texture() {
Ok(f) => f, wgpu::CurrentSurfaceTexture::Success(f)
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { | wgpu::CurrentSurfaceTexture::Suboptimal(f) => f,
wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
return; return;
} }
Err(e) => { other => {
eprintln!("native2d Surface-Fehler: {e:?}"); eprintln!("native2d Surface-Fehler: {other:?}");
return; return;
} }
}; };
@@ -274,14 +277,17 @@ impl GpuState3d {
} }
fn render(&mut self, camera: &Camera) { fn render(&mut self, camera: &Camera) {
// wgpu 29: `get_current_texture` liefert ein Enum statt eines Results —
// Suboptimal ist weiterhin praesentierbar, Lost/Outdated -> rekonfigurieren.
let frame = match self.surface.get_current_texture() { let frame = match self.surface.get_current_texture() {
Ok(f) => f, wgpu::CurrentSurfaceTexture::Success(f)
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => { | wgpu::CurrentSurfaceTexture::Suboptimal(f) => f,
wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
self.surface.configure(&self.device, &self.config); self.surface.configure(&self.device, &self.config);
return; return;
} }
Err(e) => { other => {
eprintln!("native3d Surface-Fehler: {e:?}"); eprintln!("native3d Surface-Fehler: {other:?}");
return; return;
} }
}; };
@@ -332,6 +338,7 @@ fn demo_walls() -> Vec<WallInput> {
height: ht, height: ht,
base_elevation: 0.0, base_elevation: 0.0,
color: grey, color: grey,
openings: vec![],
}; };
vec![ vec![
mk([0.0, 0.0], [6.0, 0.0]), mk([0.0, 0.0], [6.0, 0.0]),
@@ -443,15 +450,14 @@ fn configure_surface(
compatible_surface: Some(&surface), compatible_surface: Some(&surface),
})) }))
.expect("kein passender GPU-Adapter"); .expect("kein passender GPU-Adapter");
let (device, queue) = pollster::block_on(adapter.request_device( let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
&wgpu::DeviceDescriptor {
label: Some(device_label), label: Some(device_label),
required_features: wgpu::Features::empty(), required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(), required_limits: wgpu::Limits::default(),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
memory_hints: wgpu::MemoryHints::Performance, memory_hints: wgpu::MemoryHints::Performance,
}, trace: wgpu::Trace::Off,
None, }))
))
.expect("Device anfordern"); .expect("Device anfordern");
let caps = surface.get_capabilities(&adapter); let caps = surface.get_capabilities(&adapter);
-30
View File
@@ -1,30 +0,0 @@
/**
* Kompatibilitäts-Shim für die WASM-Engines (render2d/render3d, beide wgpu 22):
* wgpu 22 sendet in `requestDevice` noch das Limit `maxInterStageShaderComponents`,
* das AUS DER WebGPU-Spec ENTFERNT wurde. Neuere Chromium-Builds lehnen einen
* nicht-`undefined`-Wert dafür mit `OperationError` ab (→ Device-Erzeugung
* schlägt fehl). Wir entfernen den Schlüssel EINMAL global aus dem Descriptor,
* bevor wgpu ihn stellt. Entfällt, sobald die Engines auf ein wgpu (+ glyphon)
* hochgezogen werden, das dieses Limit nicht mehr mitschickt.
*/
let requestDevicePatched = false;
export function patchRequestDeviceLimits(): void {
if (requestDevicePatched) return;
const AdapterCtor = (globalThis as any).GPUAdapter;
if (!AdapterCtor?.prototype?.requestDevice) return;
const orig = AdapterCtor.prototype.requestDevice;
AdapterCtor.prototype.requestDevice = function (
this: unknown,
desc?: any,
) {
if (desc?.requiredLimits && "maxInterStageShaderComponents" in desc.requiredLimits) {
const requiredLimits = { ...desc.requiredLimits };
delete requiredLimits.maxInterStageShaderComponents;
desc = { ...desc, requiredLimits };
}
return orig.call(this, desc);
};
requestDevicePatched = true;
}
-3
View File
@@ -15,8 +15,6 @@
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import type { Plan } from "./generatePlan"; import type { Plan } from "./generatePlan";
import { planToRenderScene } from "./toRenderScene"; import { planToRenderScene } from "./toRenderScene";
// wgpu-22-Limit-Shim (maxInterStageShaderComponents), geteilt mit useWasm3dRenderer.
import { patchRequestDeviceLimits } from "../engine/requestDeviceShim";
interface ViewBox { interface ViewBox {
x: number; x: number;
@@ -64,7 +62,6 @@ function getRenderer(canvas: HTMLCanvasElement): Promise<WebPlanRendererLike> {
let p = rendererCache.get(canvas); let p = rendererCache.get(canvas);
if (!p) { if (!p) {
p = (async () => { p = (async () => {
patchRequestDeviceLimits(); // wgpu-22-Limit-Shim (src/engine/requestDeviceShim)
const mod = await loadEngine(); const mod = await loadEngine();
// Canvas VOR dem Surface-Erzeugen auf die Zielpixel (DPR) bringen. // Canvas VOR dem Surface-Erzeugen auf die Zielpixel (DPR) bringen.
const rect = canvas.getBoundingClientRect(); const rect = canvas.getBoundingClientRect();
-3
View File
@@ -19,8 +19,6 @@
import { useCallback, useEffect, useRef, useState } from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import type { Project } from "../model/types"; import type { Project } from "../model/types";
import { projectToModel3d } from "../plan/toWalls3d"; import { projectToModel3d } from "../plan/toWalls3d";
// wgpu-22-Limit-Shim (maxInterStageShaderComponents), geteilt mit useWasmPlanRenderer.
import { patchRequestDeviceLimits } from "../engine/requestDeviceShim";
/** Fertige Kamera für einen Frame (world-Meter, Y-up — wie render3d::types::Camera). */ /** Fertige Kamera für einen Frame (world-Meter, Y-up — wie render3d::types::Camera). */
export interface Camera3d { export interface Camera3d {
@@ -84,7 +82,6 @@ function getRenderer(canvas: HTMLCanvasElement): Promise<WebModelRendererLike> {
let p = rendererCache.get(canvas); let p = rendererCache.get(canvas);
if (!p) { if (!p) {
p = (async () => { p = (async () => {
patchRequestDeviceLimits(); // wgpu-22-Limit-Shim (src/engine/requestDeviceShim)
const mod = await loadEngine(); const mod = await loadEngine();
// Canvas VOR dem Surface-Erzeugen auf die Zielpixel (DPR) bringen. // Canvas VOR dem Surface-Erzeugen auf die Zielpixel (DPR) bringen.
const rect = canvas.getBoundingClientRect(); const rect = canvas.getBoundingClientRect();