Post-Effekt-Kette als Gerüst, Dither als erstes Glied

Bildweite Effekte gehören nicht in den Szenen-Shader. Cel-Ramp und
Sobel-Outline sollen dazukommen, ohne einander (oder scene.wgsl) zu
kennen — also eine Kette gleichrangiger Fullscreen-Pässe zwischen
Szene und Blit, Ping-Pong über zwei interne Targets.

Der Vertrag ist für alle gleich: Farbtextur rein (group 0/binding 0),
Farbe raus. Die Vertex-Stufe teilen sich alle über fullscreen.wgsl,
damit eine Effekt-Datei nur aus ihrem fs_main besteht. Ein neuer Effekt
ist eine .wgsl plus eine Zeile in EFFECTS. Bei 640×480 kostet ein
eigener Pass je Effekt praktisch nichts — deshalb kein Über-Shader.

Erster Bewohner ist die RGB555-Quantisierung samt Bayer-Dither, aus
scene.wgsl herausgelöst. Das UI zeichnet jetzt in einem eigenen Pass
hinter der Kette, sonst würde der Dither auch den Text quantisieren,
den er bisher nicht anfasst; damit braucht SpritePass kein
Tiefen-Attachment mehr.

Geschaltet wird über den exec-Trichter (`post`, `post <e> on|off`).
Die Namen kennt nur das Frontend: es meldet seine Kette beim Start in
engine::post an, wie render::audio seine Emitter aus engine::audio
zieht. Der Kern bekommt dadurch keine GPU zu sehen.

Das Bild soll unverändert bleiben — gleicher Bayer-Kernel, gleiche
Koordinaten, gleiche Reihenfolge relativ zum UI. Verifiziert sind
Tests, clippy und ein Start ohne Validation-Fehler; ein Pixelvergleich
steht aus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-28 23:31:49 +02:00
co-authored by Claude Opus 5
parent 8bee8322c3
commit 7f87dbca35
11 changed files with 461 additions and 95 deletions
+6 -11
View File
@@ -1,5 +1,8 @@
//! Sprite-Pass: das 2D-Overlay (UI) als getintete Textur-Quads.
//!
//! Zeichnet in einem eigenen Pass *hinter* der Post-Kette: Bild-Effekte
//! (Farbtiefe, Dither, später Outline) treffen so nur die Welt, nie den Text.
//!
//! Immediate-Mode: der Vertex-Buffer wird jeden Frame neu befüllt (kleine
//! Datenmenge). Geometrie kommt von [`crate::render::ui`] als fertige
//! [`SpriteVertex`]-Liste + [`SpriteBatch`]es (ein Batch je Textur). Die
@@ -60,7 +63,6 @@ impl SpritePass {
device: &wgpu::Device,
queue: &wgpu::Queue,
color_format: wgpu::TextureFormat,
depth_format: wgpu::TextureFormat,
internal: [f32; 2],
textures: &[Image],
) -> Self {
@@ -89,16 +91,9 @@ impl SpritePass {
})],
}),
primitive: wgpu::PrimitiveState::default(),
// Der interne Pass hat ein Tiefen-Attachment → die Pipeline muss
// ein kompatibles Format deklarieren. UI testet/schreibt aber
// keine Tiefe (Always, kein Write) → liegt immer obenauf.
depth_stencil: Some(wgpu::DepthStencilState {
format: depth_format,
depth_write_enabled: Some(false),
depth_compare: Some(wgpu::CompareFunction::Always),
stencil: wgpu::StencilState::default(),
bias: wgpu::DepthBiasState::default(),
}),
// Eigener Pass hinter der Post-Kette, ohne Tiefen-Attachment:
// das Overlay liegt per Reihenfolge obenauf.
depth_stencil: None,
multisample: wgpu::MultisampleState::default(),
multiview_mask: None,
cache: None,