render2d: Headless-Rendering — PNG ohne Fenster + Golden-Image-Test

HeadlessRenderer (Feature headless) baut Instance/Adapter/Device ohne
Surface (Vulkan, kein Display-Server); render_to_image rendert in eine
Rgba8Unorm-Texture (non-sRGB wie ColorMode::Web) und liest mit 256-Byte-
Row-Padding zurueck. Draw-Code unveraendert geteilt mit dem Fenster-Pfad.
CLI-Bin render_png (Demo-Szene, 990x630); Demo additiv um 45-Grad-
Schraffur, Tuerblatt und Schwenkbogen erweitert, damit das Golden alle
Pfade abdeckt. Golden-Test mit eingechecktem Referenzbild: 0/623700 Pixel
Abweichung, bit-exakt reproduzierbar; bei Abweichung Diff-Dump nach
target/. Doku in docs/design/engine-headless.md.
This commit is contained in:
2026-07-03 08:31:38 +02:00
parent ce6bd26ff7
commit 4b02e40238
8 changed files with 625 additions and 9 deletions
+50
View File
@@ -0,0 +1,50 @@
// CLI-Beweis fuer den Headless-Renderpfad (Feature "headless"): rendert die
// Demo-Szene (dieselbe wie der Fenster-Spike, `demo::demo_scene`) OHNE Fenster in
// eine PNG-Datei. Belegt, dass `HeadlessRenderer` unabhaengig von einer Display-
// Session funktioniert.
//
// Start: cargo run --features headless --bin render_png -- [--out PFAD] [--width N] [--height N]
#![cfg(feature = "headless")]
use render2d::headless::HeadlessRenderer;
use render2d::{demo_scene, initial_view_box};
fn main() {
// Defaults passend zum Seitenverhaeltnis von `initial_view_box` (990x630 —
// Bildschirm-Raum-Einheiten der Demo-Szene, siehe demo.rs).
let mut width = 990u32;
let mut height = 630u32;
let mut out = String::from("target/headless-demo.png");
let mut args = std::env::args().skip(1);
while let Some(arg) = args.next() {
match arg.as_str() {
"--width" => {
width = args
.next()
.and_then(|v| v.parse().ok())
.unwrap_or(width);
}
"--height" => {
height = args
.next()
.and_then(|v| v.parse().ok())
.unwrap_or(height);
}
"--out" => out = args.next().unwrap_or(out),
other => eprintln!("render_png: unbekanntes Argument ignoriert: {other}"),
}
}
let mut renderer = HeadlessRenderer::new().expect("Headless-Renderer initialisieren");
let image = renderer.render_to_image(&demo_scene(), width, height, initial_view_box(), 100.0);
let png = image.encode_png();
if let Some(parent) = std::path::Path::new(&out).parent() {
if !parent.as_os_str().is_empty() {
std::fs::create_dir_all(parent).expect("Ausgabeverzeichnis anlegen");
}
}
std::fs::write(&out, &png).expect("PNG schreiben");
println!("render_png: geschrieben nach {out} ({width}x{height})");
}