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 9371b65b3b
commit 8a79f6cbaa
17 changed files with 1794 additions and 1124 deletions
+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 }
# --- GPU (nur mit Feature "render") ------------------------------------------
# Standard-Features von wgpu (wgsl, webgpu, dx12, metal). Die Linux-Backends
# Vulkan/GLES aktiviert wgpu selbst ueber seine target-spezifischen Defaults —
# es gibt in wgpu 22 keine expliziten `vulkan`/`gles`-Feature-Namen.
wgpu = { version = "22", optional = true }
# Standard-Features von wgpu (wgsl, webgpu, dx12, metal + target-spezifisch
# Vulkan/GLES auf Linux). wgpu 30 ist bereits erschienen, aber glyphon 0.11
# pinnt `wgpu ^29` — 29 ist damit die neueste Version, bei der beide zusammenpassen.
wgpu = { version = "29", optional = true }
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
# an wgpu gekoppelt: die 0.6er-Reihe ist die zu wgpu 22 passende.
glyphon = { version = "0.6", optional = true }
# an wgpu gekoppelt: die 0.11er-Reihe ist die zu wgpu 29 passende.
glyphon = { version = "0.11", optional = true }
# --- Headless-PNG-Export (nur mit Feature "headless") ------------------------
# 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]
serde_json = "1"
# naga validiert die WGSL-Quellen headless (Parser + Validator) im Test, ohne
# GPU/Display. Version an wgpu 22 gekoppelt (transitiv ohnehin vorhanden).
naga = { version = "22", features = ["wgsl-in"] }
# GPU/Display. Version an wgpu gekoppelt (transitiv ohnehin vorhanden).
naga = { version = "29", features = ["wgsl-in"] }
# Der Fenster-Spike ist nur mit Feature "window" verfuegbar; ohne das Feature
# wird das Binary aus dem Build ausgeschlossen (required-features).
+15 -13
View File
@@ -43,15 +43,14 @@ impl GpuState {
compatible_surface: Some(&surface),
}))
.expect("kein passender GPU-Adapter");
let (device, queue) = pollster::block_on(adapter.request_device(
&wgpu::DeviceDescriptor {
label: Some("2d.device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: wgpu::MemoryHints::Performance,
},
None,
))
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
label: Some("2d.device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
memory_hints: wgpu::MemoryHints::Performance,
trace: wgpu::Trace::Off,
}))
.expect("Device anfordern");
let caps = surface.get_capabilities(&adapter);
@@ -96,14 +95,17 @@ impl GpuState {
}
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() {
Ok(f) => f,
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
wgpu::CurrentSurfaceTexture::Success(f)
| wgpu::CurrentSurfaceTexture::Suboptimal(f) => f,
wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
self.surface.configure(&self.device, &self.config);
return;
}
Err(e) => {
eprintln!("Surface-Fehler: {e:?}");
other => {
eprintln!("Surface-Fehler: {other:?}");
return;
}
};
+25 -14
View File
@@ -222,8 +222,8 @@ impl Renderer {
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("2d.layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
bind_group_layouts: &[Some(&bind_group_layout)],
immediate_size: 0,
});
// Alpha-Blending (SRC_ALPHA, ONE_MINUS_SRC_ALPHA) wie im WebGL-Pfad.
@@ -245,13 +245,13 @@ impl Renderer {
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &fill_module,
entry_point: "vs_main",
entry_point: Some("vs_main"),
buffers: &[fill_vertex_layout],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &fill_module,
entry_point: "fs_main",
entry_point: Some("fs_main"),
targets: &[Some(color_target.clone())],
compilation_options: Default::default(),
}),
@@ -267,7 +267,7 @@ impl Renderer {
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
multiview_mask: None,
cache: None,
});
@@ -282,13 +282,13 @@ impl Renderer {
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &line_module,
entry_point: "vs_main",
entry_point: Some("vs_main"),
buffers: &[line_vertex_layout],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &line_module,
entry_point: "fs_main",
entry_point: Some("fs_main"),
targets: &[Some(color_target)],
compilation_options: Default::default(),
}),
@@ -303,7 +303,7 @@ impl Renderer {
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
multiview_mask: None,
cache: None,
});
@@ -332,8 +332,8 @@ impl Renderer {
let arc_pipeline_layout =
device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("arc.pipeline.layout"),
bind_group_layouts: &[&arc_bind_group_layout],
push_constant_ranges: &[],
bind_group_layouts: &[Some(&arc_bind_group_layout)],
immediate_size: 0,
});
// 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).
@@ -349,13 +349,13 @@ impl Renderer {
layout: Some(&arc_pipeline_layout),
vertex: wgpu::VertexState {
module: &arc_module,
entry_point: "vs_main",
entry_point: Some("vs_main"),
buffers: &[arc_instance_layout],
compilation_options: Default::default(),
},
fragment: Some(wgpu::FragmentState {
module: &arc_module,
entry_point: "fs_main",
entry_point: Some("fs_main"),
targets: &[Some(wgpu::ColorTargetState {
format: color_format,
blend,
@@ -374,7 +374,7 @@ impl Renderer {
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
multiview_mask: None,
cache: None,
});
let arc_uniform = device.create_buffer(&wgpu::BufferDescriptor {
@@ -724,7 +724,15 @@ impl Renderer {
);
// Unbegrenzt: eine Zeile, kein Umbruch (Zeilen sind vorgeflacht).
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.
let (line_w, baseline) = buffer
@@ -817,6 +825,8 @@ impl Renderer {
label: Some("2d.pass"),
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: msaa_view,
// Ziel ist eine 2D-Textur (kein 3D-Volumen) -> kein Depth-Slice.
depth_slice: None,
resolve_target: Some(view),
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(self.clear_color),
@@ -826,6 +836,7 @@ impl Renderer {
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
multiview_mask: None,
});
if let (Some(scene), Some(u)) = (&self.scene, &self.uniform) {
+18 -16
View File
@@ -87,7 +87,7 @@ impl HeadlessRenderer {
pub fn new() -> Result<Self, String> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::VULKAN,
..Default::default()
..wgpu::InstanceDescriptor::new_without_display_handle()
});
let adapter = pollster::block_on(instance.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
@@ -95,16 +95,15 @@ impl HeadlessRenderer {
// Kein Fenster -> keine kompatible Surface noetig.
compatible_surface: None,
}))
.ok_or_else(|| "kein Vulkan-Adapter fuer Headless-Rendering gefunden".to_string())?;
let (device, queue) = pollster::block_on(adapter.request_device(
&wgpu::DeviceDescriptor {
label: Some("headless.device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: wgpu::MemoryHints::Performance,
},
None,
))
.map_err(|e| format!("kein Vulkan-Adapter fuer Headless-Rendering gefunden: {e}"))?;
let (device, queue) = pollster::block_on(adapter.request_device(&wgpu::DeviceDescriptor {
label: Some("headless.device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
memory_hints: wgpu::MemoryHints::Performance,
trace: wgpu::Trace::Off,
}))
.map_err(|e| format!("Headless-Device anfordern fehlgeschlagen: {e}"))?;
let renderer = Renderer::new(&device, COLOR_FORMAT);
@@ -178,15 +177,15 @@ impl HeadlessRenderer {
label: Some("headless.copy"),
});
encoder.copy_texture_to_buffer(
wgpu::ImageCopyTexture {
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::ImageCopyBuffer {
wgpu::TexelCopyBufferInfo {
buffer: &output_buffer,
layout: wgpu::ImageDataLayout {
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(padded_bytes_per_row),
rows_per_image: Some(height),
@@ -206,8 +205,11 @@ impl HeadlessRenderer {
let _ = tx.send(res);
});
// Vulkan hat keine Event-Loop wie ein Fenster -> Poll blockierend, bis
// die Map-Callback feuert (kein busy-loop noetig, `Maintain::Wait` wartet).
self.device.poll(wgpu::Maintain::Wait);
// die Map-Callback feuert (kein busy-loop noetig, `PollType::Wait` wartet
// ohne Timeout auf die juengste Submission).
self.device
.poll(wgpu::PollType::wait_indefinitely())
.expect("Device-Poll fehlgeschlagen");
rx.recv()
.expect("Map-Callback nie aufgerufen")
.expect("Buffer-Mapping fehlgeschlagen");
+20 -16
View File
@@ -54,7 +54,7 @@ impl WebPlanRenderer {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::BROWSER_WEBGPU,
..Default::default()
..wgpu::InstanceDescriptor::new_without_display_handle()
});
let surface = instance
.create_surface(wgpu::SurfaceTarget::Canvas(canvas))
@@ -67,19 +67,20 @@ impl WebPlanRenderer {
compatible_surface: Some(&surface),
})
.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
.request_device(
&wgpu::DeviceDescriptor {
label: Some("render2d.web.device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits()),
memory_hints: wgpu::MemoryHints::Performance,
},
None,
)
.request_device(&wgpu::DeviceDescriptor {
label: Some("render2d.web.device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::downlevel_webgl2_defaults()
.using_resolution(adapter.limits()),
experimental_features: wgpu::ExperimentalFeatures::disabled(),
memory_hints: wgpu::MemoryHints::Performance,
trace: wgpu::Trace::Off,
})
.await
.map_err(|e| JsValue::from_str(&format!("request_device: {e:?}")))?;
@@ -169,15 +170,18 @@ impl WebPlanRenderer {
/// Zeichnet EINEN Frame in die Surface-Textur.
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() {
Ok(f) => f,
Err(wgpu::SurfaceError::Lost | wgpu::SurfaceError::Outdated) => {
wgpu::CurrentSurfaceTexture::Success(f)
| wgpu::CurrentSurfaceTexture::Suboptimal(f) => f,
wgpu::CurrentSurfaceTexture::Lost | wgpu::CurrentSurfaceTexture::Outdated => {
self.surface.configure(&self.device, &self.config);
return;
}
Err(e) => {
other => {
web_sys::console::warn_1(&JsValue::from_str(&format!(
"render2d Surface-Fehler: {e:?}"
"render2d Surface-Fehler: {other:?}"
)));
return;
}