docs(render): record V6i-3, and enumerate what the world arm still needs
The slice brief was a Dereth PNG on Vulkan. There is not one, and §5.5.14 says so in its first paragraph rather than at the end. What landed is the two prerequisites — the mesh pipeline running on both arms, and the frame having a world pass to record into — plus the seven measured findings the world arm needs and that are cheaper to read than to re-derive. Three of those correct earlier text rather than extending it, which is the part worth reading: - §5.5.8 offered to promote bindings 6-8 back to dynamic and said there were "four unused dynamic slots to promote into". There are not. Vulkan's guaranteed maxDescriptorSetStorageBuffersDynamic is 4, which is exactly what V6g already spends, so the four bindings the world arm re-points per draw cost a descriptor write each. That is bounded and correct, and it is why commit 2's draw-time descriptor bind matters: without it the cost is ten scopes per draw rather than one. - V4c created every world pipeline with SampleCount = 1 because the GL backend ignores it. Vulkan requires the pipeline to match the pass, and alpha-to-coverage requires MSAA at all. - The world renderers cannot each open their own pass on Vulkan, which is the shape difference from V4c and follows directly from the MSAA resolve. The slice table gains its V6i-3 row with the gate numbers. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
887de4aec2
commit
847f14aeef
1 changed files with 117 additions and 0 deletions
|
|
@ -575,6 +575,7 @@ 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 |
|
| **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.78e-05 |
|
| **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.78e-05 |
|
||||||
| **V6i** ✅ | **The world arm's prerequisites**, in two parts. **V6i-1** (`df6e2a79`, reported in §5.5.12) closed §5.5.8's one-binding-two-buffers hazard with one descriptor-set pair per renderer scope, derived from the descriptor state rather than declared, and measured the ordered remainder list the world arm still needed. **V6i-2** (reported in §5.5.13) took items 1, 2 and 6 of that list in three gated commits: **1** the `TerrainClip` descriptor-set fix plus set 1's missing bindings 2 and 4, proven by `spirv-dis` and now gated by a SPIR-V-reading contract test (`f7344758`); **2** world texture CREATION crosses to `IGpuTexture` — `IWorldTextureArray` over `TextureAtlasManager`/`ManagedGLTextureArray`, `TerrainAtlas`'s second construction path, and `ICompositeTextureArrayBackend`'s RHI arm — with the Vulkan arm exercised at startup (`c8d0f70b`); **3** `IMeshPipelineDevice` decouples `ObjectMeshManager`/`WbMeshAdapter` from `OpenGLGraphicsDevice`. **Items 3–5 — the submission arms, `RetailPViewPassExecutor`, and the pass-structure merge — are the next slice's.** | pixel gate per commit (3.02e-05, 3.20e-05, 1.60e-05 vs `0ca802cd`), App tests 4,109/3 and complete Release suite 9,172/5, 3/3 desktop-witness connected run at commits 2 and 3, one validation-layer Vulkan run per commit |
|
| **V6i** ✅ | **The world arm's prerequisites**, in two parts. **V6i-1** (`df6e2a79`, reported in §5.5.12) closed §5.5.8's one-binding-two-buffers hazard with one descriptor-set pair per renderer scope, derived from the descriptor state rather than declared, and measured the ordered remainder list the world arm still needed. **V6i-2** (reported in §5.5.13) took items 1, 2 and 6 of that list in three gated commits: **1** the `TerrainClip` descriptor-set fix plus set 1's missing bindings 2 and 4, proven by `spirv-dis` and now gated by a SPIR-V-reading contract test (`f7344758`); **2** world texture CREATION crosses to `IGpuTexture` — `IWorldTextureArray` over `TextureAtlasManager`/`ManagedGLTextureArray`, `TerrainAtlas`'s second construction path, and `ICompositeTextureArrayBackend`'s RHI arm — with the Vulkan arm exercised at startup (`c8d0f70b`); **3** `IMeshPipelineDevice` decouples `ObjectMeshManager`/`WbMeshAdapter` from `OpenGLGraphicsDevice`. **Items 3–5 — the submission arms, `RetailPViewPassExecutor`, and the pass-structure merge — are the next slice's.** | pixel gate per commit (3.02e-05, 3.20e-05, 1.60e-05 vs `0ca802cd`), App tests 4,109/3 and complete Release suite 9,172/5, 3/3 desktop-witness connected run at commits 2 and 3, one validation-layer Vulkan run per commit |
|
||||||
|
| **V6i-3** | **The mesh pipeline runs on both arms, and the Vulkan frame gets a world pass**, reported in §5.5.14. Two commits: **1** `GlobalMeshBuffer` takes `GL?` and publishes `VertexStore`/`IndexStore`, `ObjectMeshManager`'s `RequireGl` narrows to the unreachable legacy upload, `VulkanMeshPipelineDevice` is `IMeshPipelineDevice`'s second implementation, and `NullWbMeshAdapter` is deleted (`fe8abacf`); **2** the clear merges into the world pass with `Store = Resolve` and descriptor sets bind at draw time (`887de4ae`). **The world renderers' submission arms and `RetailPViewPassExecutor` did NOT land** — §5.5.14 enumerates what they still need. | pixel gate per commit (4.44e-05, 3.73e-05 vs `579e0b7f`), App tests 4,112/3, one 3/3 desktop-witness connected run at HEAD, one validation-layer Vulkan run per commit, and a bit-identical (0/921,600) Vulkan capture across the pass merge |
|
||||||
| **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 |
|
| **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 |
|
| **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 |
|
| **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 |
|
||||||
|
|
@ -1740,6 +1741,122 @@ arm is one slice: the offline gate's scene has buildings, so
|
||||||
the three world renderers' submission arms, and the mesh pipeline's raw-GL upload
|
the three world renderers' submission arms, and the mesh pipeline's raw-GL upload
|
||||||
bodies this slice deliberately left in place.
|
bodies this slice deliberately left in place.
|
||||||
|
|
||||||
|
#### 5.5.14 V6i-3 (2026-07-28): the mesh pipeline runs, and the frame has a world pass
|
||||||
|
|
||||||
|
§5.5.13's remainder was items 3–5 — the three world renderers' submission arms,
|
||||||
|
`RetailPViewPassExecutor`, and the pass-structure merge — plus the mesh
|
||||||
|
pipeline's raw-GL upload bodies. **This slice delivered the two prerequisites and
|
||||||
|
did NOT deliver the world arm.** Stating that first, because the slice's brief
|
||||||
|
was a Dereth PNG and there is not one.
|
||||||
|
|
||||||
|
**1. The mesh pipeline's upload bodies cross the seam** (`fe8abacf`). V6i-2 cut
|
||||||
|
`IMeshPipelineDevice` and proved the pipeline could be CONSTRUCTED without naming
|
||||||
|
a backend; it said plainly it did not RUN. `GlobalMeshBuffer` now takes `GL?`.
|
||||||
|
Its two backing stores were already `IGpuBuffer` (V4b); what still needed a
|
||||||
|
context was the vertex array and its attribute pointers, which have no RHI verb
|
||||||
|
because Vulkan bakes vertex input into the pipeline. A backend with none builds
|
||||||
|
the stores and nothing else, publishes 0 for `VAO`/`VBO`/`IBO`, and publishes
|
||||||
|
`VertexStore`/`IndexStore` — the same buffers, named the way a pass encoder binds
|
||||||
|
them — plus `HasStores`, the backend-neutral form of the `VAO != 0` readiness
|
||||||
|
test the raw-GL draw paths make. Two bodies fork on the context: `InitBuffers`
|
||||||
|
skips the vertex array, and `CommitMigration` skips the rebind, because on the
|
||||||
|
encoder arm the field swap IS the atomic publication.
|
||||||
|
|
||||||
|
`ObjectMeshManager`'s `RequireGl` narrowed to the LEGACY per-mesh upload — every
|
||||||
|
GL statement it guarded sits inside `if (!_useModernRendering)`, which the N.5
|
||||||
|
ship amendment makes unreachable, so the accessor survives as the guard on dead
|
||||||
|
code rather than as a blocker. `VulkanMeshPipelineDevice` is the second
|
||||||
|
implementation and is four properties and two no-ops; `WbMeshAdapter` selects
|
||||||
|
between them once. **`NullWbMeshAdapter` is deleted** — it existed for exactly
|
||||||
|
this gap — and composition builds the mesh pipeline on both arms, so streaming's
|
||||||
|
publication into GPU state stops being a no-op there.
|
||||||
|
|
||||||
|
**2. The clear merges into the world pass** (`887de4ae`), which is §5.5.12
|
||||||
|
item 5. V6h's clear phase opened a backbuffer pass of its own; under MSAA that
|
||||||
|
pass resolves into the swapchain image and stores `DONT_CARE` into the
|
||||||
|
multisampled scratch, so any world pass that followed would `Load` undefined
|
||||||
|
contents. The clear phase now publishes only the COLOUR and
|
||||||
|
`VulkanWorldScenePhase` opens the one backbuffer pass, clearing as its load op
|
||||||
|
with `Store = Resolve`. **This is why the world renderers cannot each open their
|
||||||
|
own pass on this backend**, and it is the whole reason the arm is shaped
|
||||||
|
differently from V4c's.
|
||||||
|
|
||||||
|
The same commit made descriptor sets bind at DRAW time rather than at bind time.
|
||||||
|
V6i-1's arena derives a scope from the descriptor state, and the encoder resolved
|
||||||
|
after every bind — so a renderer binding ten buffers materialised up to ten
|
||||||
|
scopes per draw, nine of them PARTIAL states no draw uses, each claiming a
|
||||||
|
descriptor-set pair and a round of `vkUpdateDescriptorSets`. Legal to defer
|
||||||
|
because acdream has one pipeline layout by design (§4.4).
|
||||||
|
|
||||||
|
**Gates.** Release build green. App tests **4,112 / 3 skips** (the 4,109 baseline
|
||||||
|
plus three). Strict GL offline pixel gate against `579e0b7f`: **4.44e-05** at
|
||||||
|
commit 1 and **3.73e-05** at commit 2 (25 and 21 differing pixels of 563,200),
|
||||||
|
both inside the documented 9–31 px control band. GL connected
|
||||||
|
`tools/run-repeat-connected-gate.ps1 -Runs 3` at HEAD: **3/3 RENDERED** on the
|
||||||
|
desktop witness and 3/3 on the client capture. One offline Vulkan run per commit
|
||||||
|
with `VK_LAYER_KHRONOS_validation` **proven inserted by the loader**: zero
|
||||||
|
errors, zero warnings, no `[shutdown]` diagnostic. The Vulkan capture at commit 2
|
||||||
|
is compared against commit 1's rather than eyeballed — **0 differing pixels of
|
||||||
|
921,600, maximum channel delta 0**, bit-identical across the merge.
|
||||||
|
|
||||||
|
**What the world arm still needs, measured rather than estimated.** Every item
|
||||||
|
below was designed and, where noted, written and then withdrawn because nothing
|
||||||
|
exercised it — §7.1 rule 3. The design decisions are the expensive part and they
|
||||||
|
are recorded here so the next slice does not re-derive them.
|
||||||
|
|
||||||
|
1. **One pass, shared by every world renderer.** V4c's renderers each opened
|
||||||
|
their own `Load`/`Store` pass. On this backend they must record into the pass
|
||||||
|
`VulkanWorldScenePhase` opens, for the resolve reason above. The seam that
|
||||||
|
fits is a `VulkanWorldPassScope` the phase publishes the encoder on and the
|
||||||
|
renderers borrow, with the phase bracketing `WorldSceneRenderer`.
|
||||||
|
2. **Three sections are frame-global on GL and cannot be on Vulkan**: the
|
||||||
|
SceneLighting UBO (set 1 binding 1), the per-cell clip regions (set 0 binding
|
||||||
|
2) and the terrain clip block (set 1 binding 2). GL binds each globally and
|
||||||
|
every consumer inherits it. `SceneLightingUboBinding` and `ClipFrame` need
|
||||||
|
ring-writing arms that PUBLISH their sections on the scope; each renderer then
|
||||||
|
binds them inside the pass, after its own binds, because its own binds are
|
||||||
|
what select the descriptor scope they have to land in.
|
||||||
|
3. **Retail's interior depth clear has no RHI verb.** `RetailPViewPassExecutor`
|
||||||
|
issues `glClear(GL_DEPTH_BUFFER_BIT)` between the landscape slice and the
|
||||||
|
interior cells. Splitting the world pass to get a depth load-op is exactly
|
||||||
|
what the resolve forbids, so the Vulkan arm must record
|
||||||
|
`vkCmdClearAttachments` — reached through the scope, so the pinned contract
|
||||||
|
stays frozen and the backend-only verb stays inside the backend.
|
||||||
|
4. **Clip distances need no enable on Vulkan, and that is safe rather than a
|
||||||
|
divergence.** GL requires `glEnable(GL_CLIP_DISTANCE0 + i)`; Vulkan activates
|
||||||
|
every element the shader declares. All three vertex shaders already write
|
||||||
|
`1.0` — keep everything — into every plane slot past the active count, so a
|
||||||
|
frame with no clip planes clips nothing on either backend.
|
||||||
|
`EnableClipDistances`/`DisableClipDistances` are no-ops on the Vulkan arm.
|
||||||
|
5. **Only FOUR storage bindings can be dynamic, and the world arm re-points
|
||||||
|
eight per draw.** §5.5.8 offered to promote bindings 6–8 back to dynamic and
|
||||||
|
said there were "four unused dynamic slots"; there are not. Vulkan's
|
||||||
|
guaranteed `maxDescriptorSetStorageBuffersDynamic` is **4**, which is exactly
|
||||||
|
what V6g spends (bindings 0, 1, 3, 5). Bindings 4, 6, 7 and 8 are per-draw
|
||||||
|
ring allocations too, so on Vulkan each moves the descriptor's own offset and
|
||||||
|
therefore costs a descriptor write and a distinct arena entry per draw.
|
||||||
|
Correct, bounded (the arena recycles entries across frames), and the reason
|
||||||
|
the draw-time bind above matters: without it the cost is ten scopes per draw
|
||||||
|
instead of one. A slice that wants it cheaper has to change the shaders'
|
||||||
|
indexing, not the layout.
|
||||||
|
6. **Pipeline `SampleCount` is load-bearing on Vulkan.** V4c created every world
|
||||||
|
pipeline with `SampleCount = 1` because the GL backend ignores it. Vulkan
|
||||||
|
requires the pipeline's `rasterizationSamples` to match the pass, and
|
||||||
|
alpha-to-coverage requires MSAA, so the world pipelines must be created with
|
||||||
|
the device's sample count.
|
||||||
|
7. **The collision-wireframe debug lines would nest a pass.**
|
||||||
|
`WorldSceneDiagnosticsController.DrawAndPublish` flushes `DebugLineRenderer`
|
||||||
|
INSIDE the world phase, and that renderer opens its own pass. On the Vulkan
|
||||||
|
arm the diagnostics controller must be composed with a null debug-line
|
||||||
|
renderer: the toggle is DevTools-only and DevTools is not composed there, so
|
||||||
|
nothing is lost, but composing it would throw on the first wireframe frame
|
||||||
|
rather than silently misdraw.
|
||||||
|
|
||||||
|
Beyond that the remainder is unchanged from §5.5.13: the three renderers'
|
||||||
|
submission arms and `RetailPViewPassExecutor`, which §5.5.12 item 3 established
|
||||||
|
is on the critical path because the offline gate's scene has buildings and
|
||||||
|
therefore takes the PView route.
|
||||||
|
|
||||||
### 5.4 The null-target `BeginPass` divergence (V4c) — must be undone at V6
|
### 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
|
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