From 90f7c6f2f4874b5fe1dd688ff20228d90b1a9ab1 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 27 Jul 2026 14:28:06 +0200 Subject: [PATCH] docs(render): drop the ambient-encoder relaxation from Campaign V The transitional path for slices V4a-V4g needs no special API after all. On GL, BeginPass binds the target framebuffer and applies load ops but deliberately leaves viewport and scissor to the encoder, so a renderer being ported mid-campaign opens a Load/Store pass against the backbuffer and gets exactly today's behavior while the frame spine still owns clears. One less contract concept, and one less thing for V4h to unwind. Co-Authored-By: Claude Fable 5 --- docs/plans/2026-07-27-vulkan-campaign.md | 4 ++-- src/AcDream.App/Rendering/Gpu/GpuPassDescription.cs | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/docs/plans/2026-07-27-vulkan-campaign.md b/docs/plans/2026-07-27-vulkan-campaign.md index 2d955a83..76897682 100644 --- a/docs/plans/2026-07-27-vulkan-campaign.md +++ b/docs/plans/2026-07-27-vulkan-campaign.md @@ -398,7 +398,7 @@ commit's build, capture again at slice HEAD, compare with the | Slice | Scope | Gate | |---|---|---| | **V0** ✅ | Pinned RHI contract, `RecordingGpuDevice`, contract tests, this document, roadmap entry. | build + tests + contract tests | -| **V1** | GL backend: `GlGpuDevice` (no Chorizite inheritance), buffers (`BufferSubData`, behaviour-preserving), ring over the existing fence-bounded pattern, textures + the binding-9 handle table, samplers, pipelines, the ambient-encoder relaxation, timers, `IGpuFenceApi` re-home, backbuffer capture. Constructed in composition; no consumers yet. | build + tests + GL unit tests + pixel gate (trivially identical — a tripwire) | +| **V1** | GL backend: `GlGpuDevice` (no Chorizite inheritance), buffers (`BufferSubData`, behaviour-preserving), ring over the existing fence-bounded pattern, textures + the binding-9 handle table, samplers, pipelines, timers, backbuffer capture. Constructed in composition; no consumers yet. | build + tests + GL unit tests + pixel gate (trivially identical — a tripwire) | | **V2** | Shader dialect + texture-index migration **on GL**: `uvec2 textureHandle` → `uint textureIndex`, binding-9 table, `common.glsl` preamble, CPU batch-struct change, caches registering into the device table. Sub-commits: V2a mesh, V2b terrain, V2c particles. | pixel gate per sub-commit | | **V3** | Clip-space and sRGB audit: verify every projection producer is [0,1] convention, confirm clip-plane derivation, record the sRGB swapchain decision and the depth-precision divergence class here. | pixel gate + connected lifecycle | | **V4a** | `TextRenderer` (three fence-buffered VBO sets → ring allocations), `BitmapFont`, `DebugLineRenderer`, the UI RenderSurface upload path, `UiViewport`'s texture handoff. | pixel gate (UI-heavy checkpoints) | @@ -408,7 +408,7 @@ commit's build, capture again at slice HEAD, compare with the | **V4e** | `ParticleRenderer` (after V4c — shared alpha-queue contract). | pixel gate (particle-heavy checkpoint) | | **V4f** | `SkyRenderer` + weather. | pixel gate (dawn/dusk, day group pinned) | | **V4g** | `PrivateEntityViewportRenderer` → `IGpuRenderTarget`; `PortalDepthMaskRenderer` + `PortalTunnelPresentation` → stencil/depth-mask pipelines. | pixel gate incl. paperdoll and portal transit | -| **V4h** | Frame-spine formalization: pass executors emit `BeginPass`/`EndPass`, the ambient relaxation is removed, flight/screenshot/resize/profiler move onto the RHI, `OpenGLGraphicsDevice`'s live role retires, Chorizite consumers are audited, and the architecture test lands. **Milestone: seam complete.** | pixel + connected lifecycle + R6 soak + complete Release suite + interim perf (RHI-on-GL CPU p50 ≤ 1.95 ms) | +| **V4h** | Frame-spine formalization: pass executors emit real declared `BeginPass`/`EndPass` (clears and framebuffer management move out of the spine and into pass load/store ops), flight/screenshot/resize/profiler move onto the RHI, `OpenGLGraphicsDevice`'s live role retires, Chorizite consumers are audited, and the architecture test lands. **Milestone: seam complete.** | pixel + connected lifecycle + R6 soak + complete Release suite + interim perf (RHI-on-GL CPU p50 ≤ 1.95 ms) | | **V5** | Vulkan bring-up, dark: `ACDREAM_RENDER_BACKEND`, surface/instance/device/queues/swapchain, the capability record/probe/guard with the exit-4 contract, a clear-colour loop with screenshot and clean shutdown. | VK boots to clear on the RX 9070 XT; forced-unsupported knob → exit 4 | | **V6** | Vulkan RHI backend, dark, three sequential commits: **a** allocator/buffers/staging/rings/timeline; **b** textures/BC mips/samplers/descriptor table/render targets/MSAA resolve; **c** `.spv` toolchain, pipelines, pipeline cache, negative viewport, push constants, timestamps, readback, debug names. **Milestone: full game frame on Vulkan.** | per-commit build + tests; VK renders world, UI, paperdoll, portals | | **V7** | GL-versus-Vulkan differential: `tools/run-backend-differential-gate.ps1`, strict paired-PNG compare, divergences fixed in the Vulkan backend only, then lifecycle + R6 soak natively on Vulkan, one validation-layer-clean run, one RenderDoc capture. **Milestone: parity.** | every differential checkpoint passes; both connected routes green on VK | diff --git a/src/AcDream.App/Rendering/Gpu/GpuPassDescription.cs b/src/AcDream.App/Rendering/Gpu/GpuPassDescription.cs index f9fd91e2..96b2740a 100644 --- a/src/AcDream.App/Rendering/Gpu/GpuPassDescription.cs +++ b/src/AcDream.App/Rendering/Gpu/GpuPassDescription.cs @@ -42,10 +42,15 @@ internal readonly record struct GpuDepthAttachment( /// /// GL has no such object — its "pass" is implicit in whatever framebuffer happens /// to be bound — so making passes explicit is the single largest structural change -/// the RHI imposes on the existing renderers. The GL backend therefore accepts an -/// ambient encoder during Campaign V slices V1..V4g (draws outside any declared -/// pass, preserving today's behaviour) and slice V4h removes that relaxation once -/// every renderer declares its passes. +/// the RHI imposes on the existing renderers. +/// +/// The transitional path needs no special API. On GL, BeginPass binds the +/// target framebuffer and applies load ops, and deliberately does NOT touch +/// viewport or scissor — encoders set those explicitly. A renderer being ported +/// during Campaign V slices V4a..V4g therefore opens a pass with +/// / against the +/// backbuffer and gets exactly today's behaviour, while the frame spine still +/// owns clears and framebuffer management. Slice V4h converts the spine itself. /// internal sealed record GpuPassDescription {