feat(render): Campaign V slice V6h — the Vulkan composition host
ACDREAM_RENDER_BACKEND=vulkan now runs the real GameWindow composition rather
than a second main(). All nine phases execute: DAT load, streaming, camera,
entity table, session, and the real retained UiHost drawing through the RHI.
No world renderers — they are raw GL until V4t and the world arm behind it.
The offline log is the client's own (acdream.pak opened, 6266 spells, Region
0x13000000, "loading world view centered on 0xA9B4FFFF", fourteen retail
LayoutDesc lines, streaming radii), and the captured frame is the retail
retained UI: vitals, combat/spell bar with DAT scarab icons, the nine-slot
toolbar, chat with tabs and Send, radar/compass with dat-font glyphs. Sampled
against the GL capture the widgets agree — chat interior RGBA (25,24,27,158)
vs (22,21,23,158), vitals bar (117,1,0) and toolbar slot (0,11,17) identical.
Three seams, as §5.5.9 specified:
1. Platform acquisition — already generic — publishes GameWindowGraphics
instead of a bare GL. Phases that still speak raw GL read Graphics.Gl and
take their Vulkan arm when it is null; each branch names the slice that
removes it.
2. VulkanHostInputCameraCompositionFactory is a new file and the whole of the
Phase-1 fork: four graphics members differ, input/camera/pointer delegate.
The default factory is chosen inside the phase from the platform result.
HostInputCameraResult gained backend-neutral Retirement and FrameSlots.
3. The frame root forks on one condition. The GL world-scene assembly is
unchanged, wrapped in `if (gl is not null)`; the Vulkan arm's graph is one
backbuffer clear pass computing the same RenderFrameFoundation from the same
clock and weather owners, then private presentation over it.
§5.5.9's three TextureCache couplings are unpicked: the constructor takes GL?
and rejects bindless without one, world entry points route through a Gl
property that throws naming V4t, and the (GlGpuTexture) VRAM-accounting cast
became a backend test. That cast's stated reason — DrawSprite's texture-unit
binding — was already stale, deleted at V6d.
VulkanBringUpHost is reduced to the capability-probe harness it is named for:
the instance/surface/device/swapchain sequence moved into VulkanGraphicsContext,
which the composition host and the harness now share. It is reached only with
ACDREAM_VULKAN_PROBE=1.
One latent Vulkan defect surfaced and is fixed here. The first composition-host
frame died with ErrorDeviceLost; validation named VUID-vkCmdDraw-None-08600 —
descriptor set 2 never bound. VulkanGpuPassEncoder bound sets 0/1/2 only as a
side effect of BindStorageBuffer/BindUniformBuffer, so a pass sampling the
texture table while binding no buffer — every retained-UI and debug-line pass —
drew with the table unbound. It survived V6c-V6g because the bring-up host
always drew VulkanRhiScene first and the UI pass inherited its binds; the
composition host has no 3-D scene. The fix is one line in the encoder's
constructor beside the viewport and scissor defaults, which exist for exactly
the same reason: a pass opens with complete binding state rather than depending
on what preceded it.
Gates: strict GL offline pixel gate against 46d893f7 measures 1.24e-05 (7 of
563,200 pixels), inside the documented 15-23 px / 4.1e-05 band, so GL behaviour
did not move. App tests 4,075/3 skips; complete Release suite 9,138/5 skips.
One full Vulkan run with VK_LAYER_KHRONOS_validation: zero errors, zero
warnings. Both Vulkan runs converged the ownership ledger — no [shutdown]
diagnostic on either stream. The reduced probe harness presented 34,811
validation-clean frames.
No divergence-register row: GL is the shipping backend and the pixel gate proves
it unmoved; the Vulkan arm is not a retail deviation but a backend under
construction.
Next is V4t, the texture stack, which the world arm cannot be written without.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
46d893f7c3
commit
b16f820643
29 changed files with 2292 additions and 997 deletions
|
|
@ -573,6 +573,7 @@ dialect slice. **V6f closed all three** (the third was the frag's GL-only
|
|||
tenth pair with no consumer at all; see the V6e report.
|
||||
|
||||
| **V6g** | The four Vulkan validation defects §5.5.7 and its log left open: the dynamic-descriptor split (an architect decision, §5.5.8 item 1), per-pass depth-format pipeline variants, first-use backbuffer attachment layout transitions, and a backbuffer capture that no longer reads a presented swapchain image. Confined to `Gpu/Vk/`; the GL backend executes not one changed statement. | validation-clean bring-up run (0 errors / 0 warnings over 39,855 frames, against 7 VUIDs + 1 UNASSIGNED at the parent), App tests, GL offline pixel gate 4.08e-05 — its own same-commit control value |
|
||||
| **V6h** ✅ | **The Vulkan composition host**, specified by §5.5.9 and reported in §5.5.10. `ACDREAM_RENDER_BACKEND=vulkan` runs the real `GameWindow` composition — DAT load, streaming, camera, entity table, session, and the real retained `UiHost` through the RHI — with no world renderers. Three seams: the already-generic platform acquisition now publishes a `GameWindowGraphics`; `VulkanHostInputCameraCompositionFactory` is the host-phase fork; the frame root gains a Vulkan arm. `VulkanBringUpHost` is reduced to the capability-probe harness over the extracted `VulkanGraphicsContext`. | offline Vulkan launch reaching the real composition with the client's own UI captured, one validation-layer run at 0 errors / 0 warnings, converging ownership ledger, App tests 4,075/3, complete Release suite 9,138/5, GL offline pixel gate 1.24e-05 |
|
||||
| **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 |
|
||||
| **V8** | Perf gate on the RX 9070 XT, uncapped, both backends, same route. | §2 acceptance table; parity is the floor |
|
||||
| **V9** | Linux + CI: X11/Wayland surfaces; a `linux-vulkan` job on lavapipe (probe accepts on a real 1.3 software device, a short real render under xvfb, forced-unsupported → exit 4, `.spv` freshness). Physical Linux GPU row deferred post-cutover, as for Slice L. | CI green including the new job |
|
||||
|
|
@ -1285,6 +1286,103 @@ Step 2's own acceptance criterion — "the real UI renders" — is what makes it
|
|||
doing before V4t rather than after: it is the first frame acdream draws on Vulkan
|
||||
that is the *client's* frame rather than a scene written to prove the backend.
|
||||
|
||||
#### 5.5.10 V6h (2026-07-28): the Vulkan composition host is built
|
||||
|
||||
§5.5.9's step 2 is done. `ACDREAM_RENDER_BACKEND=vulkan` now runs the real
|
||||
composition: `GameWindow.Run` opens a `WindowOptions.DefaultVulkan` window,
|
||||
platform acquisition publishes a Vulkan graphics handle, and every one of the
|
||||
nine composition phases executes. The offline log is the client's own —
|
||||
`prepared assets: opened acdream.pak`, `spells: loaded 6266 entries`,
|
||||
`sky: loaded Region 0x13000000`, `loading world view centered on 0xA9B4FFFF`,
|
||||
the fourteen retail LayoutDesc lines, `streaming: nearRadius=4 farRadius=12` —
|
||||
and the captured frame is the retail retained UI: vitals window, combat/spell bar
|
||||
with DAT scarab icons, the nine-slot toolbar with backpack and dove chrome, the
|
||||
chat window with its tabs and Send button, and the radar/compass with dat-font
|
||||
N/E/S/W glyphs. Sampled against the GL capture the widgets agree —
|
||||
chat interior RGBA (25,24,27,158) versus (22,21,23,158), vitals bar (117,1,0) and
|
||||
toolbar slot (0,11,17) identical. The background is the atmosphere fog colour
|
||||
because there is no world behind it, which is the slice's declared scope.
|
||||
|
||||
**The estimate held.** §5.5.9 predicted ~1,200–2,000 lines across ~20 files
|
||||
including `GameWindow.cs`; the commit is 22 modified and 5 new files.
|
||||
|
||||
**What the three seams became.**
|
||||
|
||||
1. **Platform acquisition** publishes `GameWindowGraphics` — an abstract handle
|
||||
with an `OpenGl` and a `Vulkan` subclass — instead of a bare `GL`. Every phase
|
||||
that still speaks raw GL asks `Graphics.Gl` and takes its Vulkan arm when the
|
||||
answer is null; each such branch names the slice that will remove it. The
|
||||
dependency records' `ReferenceEquals` consistency checks are unchanged in
|
||||
kind, only in type.
|
||||
2. **`VulkanHostInputCameraCompositionFactory`** is a new file and the whole of
|
||||
the Phase-1 fork. Four members differ — viewport target, frame flights, GPU
|
||||
device, GL state tripwire; input, camera and pointer construction delegate to
|
||||
the retail factory because they are platform concerns, not graphics ones. The
|
||||
default factory is now chosen inside the phase from the platform result rather
|
||||
than at the call site. `HostInputCameraResult` gained backend-neutral
|
||||
`Retirement` and `FrameSlots` views: on GL both are the fence ring, on Vulkan
|
||||
the device's timeline-backed queue and flight controller.
|
||||
3. **The frame root** forks on one condition. The GL world-scene assembly is
|
||||
unchanged and merely wrapped in `if (gl is not null)`; the Vulkan arm's render
|
||||
graph is `VulkanRenderFrameClearPhase` (one backbuffer clear pass computing
|
||||
the same `RenderFrameFoundation` from the same clock and weather owners) plus
|
||||
the private-presentation phase that composites the retained UI.
|
||||
|
||||
**§5.5.9's three `TextureCache` couplings are unpicked.** The constructor takes
|
||||
`GL?` and rejects a bindless argument without one; world entry points route
|
||||
through a `Gl` property that throws naming slice V4t; and the
|
||||
`(GlGpuTexture)texture` VRAM-accounting cast became a backend test, with a
|
||||
descending synthetic counter supplying the dictionary key off GL. Worth
|
||||
recording: that cast's stated reason — `TextRenderer.DrawSprite`'s texture-unit
|
||||
binding — was already stale, deleted at V6d. Nothing draws with the value.
|
||||
|
||||
**One latent Vulkan defect was exposed and fixed, and it was ours.** The first
|
||||
composition-host frame died with `ErrorDeviceLost` at `vkQueueSubmit2`, and
|
||||
validation named it: `VUID-vkCmdDraw-None-08600`, "the VkPipeline statically uses
|
||||
descriptor set 2, but because a descriptor was never bound, the VkPipelineLayouts
|
||||
are not compatible." `VulkanGpuPassEncoder` bound sets 0/1/2 only as a *side
|
||||
effect* of `BindStorageBuffer`/`BindUniformBuffer`, so a pass whose pipeline
|
||||
samples the texture table but binds no buffer — every retained-UI and debug-line
|
||||
pass, whose per-draw data travels in push constants and a vertex buffer — drew
|
||||
with the table unbound. It survived V6c–V6g because the bring-up host always drew
|
||||
`VulkanRhiScene` first and its storage binds left all three sets bound in the same
|
||||
command buffer; the UI pass inherited them. The composition host has no 3-D
|
||||
scene, so its UI pass is first and inherits nothing. **The fix is one line in the
|
||||
encoder's constructor, beside the viewport and scissor defaults that exist for
|
||||
exactly the same reason**: a pass must open with complete binding state rather
|
||||
than depend on what preceded it in the frame. This is the third instance of the
|
||||
project's "latent bug masked by a wider path" class, and the first on Vulkan.
|
||||
|
||||
**What the Vulkan arm deliberately does not have**, each with the slice that
|
||||
brings it: the world renderers and the terrain blending tables (V4t, then the
|
||||
world arm — streaming still runs and builds real heightfields and collision, but
|
||||
publishes into no GPU state and every surface resolves to `SurfaceInfo.None`);
|
||||
DevTools, because ImGui is not ported and V11 deletes it; the GPU-timer bracket,
|
||||
because `FrameProfiler`'s query ring is GL-only and `IGpuDevice.Timers` replaces
|
||||
it at V4h; and the portal tunnel, whose renderer is raw GL — the teleport owner
|
||||
drives a presentation reporting "no tunnel showing" while reveal generation,
|
||||
destination latch, placement and session run unchanged.
|
||||
|
||||
**Gate results.** The strict GL offline pixel gate against `46d893f7` measured
|
||||
**1.24e-05** (7 differing pixels of 563,200) — inside the documented 15–23 px /
|
||||
≤4.1e-05 band, so GL behaviour did not move. App tests 4,075 / 3 skips; complete
|
||||
Release suite 9,138 / 5 skips. One full Vulkan run with
|
||||
`VK_LAYER_KHRONOS_validation`: **zero errors, zero warnings**. Both Vulkan runs
|
||||
shut down with the ownership ledger converged — no `[shutdown]` diagnostic on
|
||||
either stream, which is `GameWindowLifetimeStatus.Complete`. The reduced probe
|
||||
harness (`ACDREAM_VULKAN_PROBE=1`) presented 34,811 validation-clean frames and
|
||||
resolved its GPU timer scopes.
|
||||
|
||||
**Next is V4t**, the texture stack, which the world arm cannot be written
|
||||
without. §5.5.9 sized it: 69 references across 9 source and 4 test files for the
|
||||
`ulong` bindless handle alone, on top of `TextureCache`,
|
||||
`CompositeTextureArrayCache`, `ManagedGLTextureArray`, `TerrainAtlas` and
|
||||
`ObjectMeshManager`'s material path. Two things this slice learned should go into
|
||||
it: the composition host is now a real consumer, so V4t's Vulkan side can be
|
||||
exercised the moment it exists; and §5.5.8's recorded one-binding-two-buffers
|
||||
hazard is still unfired, because nothing on the Vulkan arm yet binds the same
|
||||
storage binding to two buffers in one frame. The world arm will.
|
||||
|
||||
### 5.4 The null-target `BeginPass` divergence (V4c) — must be undone at V6
|
||||
|
||||
V4c had to stop GL's `BeginPass` from binding framebuffer 0 when a pass declares
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue