docs(render): record V4t at the committed tree, and correct one stale comment
Marks V4t done in the slice table with its two commits and per-commit gate numbers, adds §5.5.11, and ticks off step 3 of §5.5.9's corrected sequence. §5.5.11 records four things the world arm needs and one it should not re-derive: why §5.2's "the device's table is unreachable" argument expired rather than being worked around (its premise was that V4c would move the world renderers onto the encoder; §5.5.6 closed that, so the block became indefinite); that V4t landed narrower than §5.3 sized it, keeping texture creation and residency with the caches and moving only the table entry, with `IGpuTexture` creation deferred to the world arm that actually cannot use a GL handle; why `GroupKey`'s ordering survives a retype of one of its fields; and the pixel-gate control measurement, because one capture read 5.50e-05 against a documented 15–23 px band and the honest response to that is a control, not a paragraph. The decisive number is the 9-px difference between captures at the two V4t commits — two different commits, fewer differing pixels than the same-commit control. `GlBindlessHandleTable`'s own doc comment claimed four renderers own instances. Three of them no longer do. It now names its one remaining owner, `SkyRenderer`, says why that one is different (its textures come from the raw GL-name path V4t did not retype), and names V4f as the slice that deletes both the table and the class. `common.glsl`'s binding-9 comment carries the same stale list. It is left alone deliberately: the file is a shader source with compiled `.spv` artifacts and a V9 freshness gate, so a comment-only edit there is not free, and what the comment says about what binding 9 IS remains correct. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
565c351f93
commit
b9ab5890af
2 changed files with 117 additions and 11 deletions
|
|
@ -549,7 +549,7 @@ because sample positions are not specified across implementations.
|
|||
| **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) |
|
||||
| **V4b** | `GlobalMeshBuffer` + `ObjectMeshManager` onto `IGpuBuffer`; arena, LRU and ledger logic untouched. | pixel gate |
|
||||
| **V4c** ⏸ **PARKED — §5.5.5** | **The large one.** `WbDrawDispatcher` + `EnvCellRenderer`: per-frame uploads → rings, MDI brackets → pipelines + `MultiDrawIndexedIndirect`, loose uniforms → push constants, timer scopes. `RetailAlphaQueue` and all bucketing untouched. **Narrowed after the V4c scouting report — see §5.3.** | pixel gate at several checkpoints + connected lifecycle |
|
||||
| **V4t** ↻ **re-sequenced — §5.5.5** | **World texture stack** (added 2026-07-27, see §5.3): `TextureCache`, `CompositeTextureArrayCache`, `ManagedGLTextureArray`, `TerrainAtlas` and `ObjectMeshManager`'s material path onto `IGpuTexture`/`IGpuSampler`; retype `GroupKey`, `CachedBatch` and `ObjectRenderBatch` from `ulong` bindless handle to `GpuTextureSlot`; retire the interim per-renderer handle tables for V4c, V4d and V4e at once. | pixel gate |
|
||||
| **V4t** ✅ | **World texture stack** (added 2026-07-27, see §5.3; reported in §5.5.11). Two commits: **1** the GL device's world-handle seam plus `TerrainAtlas`/`TerrainModernRenderer` (`b8bcaa3e`); **2** `CompositeTextureArrayCache`, the particle arrays, `ObjectMeshManager`'s material path, and the retype of `GroupKey`, `CachedBatch` and `ObjectRenderBatch` from `ulong` bindless handle to `GpuTextureSlot`, retiring the interim tables in `WbDrawDispatcher`, `EnvCellRenderer`, `TerrainModernRenderer` and `ParticleRenderer` (`565c351f`). **Narrowed from the original scope in one way:** the caches still create and own their GL textures and residency — the device owns only the table entry — so `ManagedGLTextureArray` and the raw `Texture2D` upload path are untouched and `IGpuTexture` creation moves with the Vulkan world arm. `SkyRenderer` keeps its own table; see §5.5.11. | pixel gate per commit (3.02e-05; 5.50e-05 and 3.91e-05 on two captures against a 19 px same-commit control), App tests, 3/3 desktop-witness connected run per commit, one validation-layer Vulkan run per commit |
|
||||
| **V4d** ⏸ **PARKED — §5.5.5** | `TerrainModernRenderer` only — **`TerrainAtlas` belongs to V4t** with the rest of the texture stack. Two sub-commits: first the `uView`/`uProjection` → `uViewProjection` shader convergence on its own pixel gate (it moves a matrix product from per-vertex GPU to a CPU multiply, so its rounding effect must be attributable alone), then the plumbing. Terrain has no GPU timer to port — its diagnostics use a CPU `Stopwatch`. | pixel gate per sub-commit |
|
||||
| **V4e** ↻ **re-sequenced — §5.5.5** | `ParticleRenderer` (after V4c — shared alpha-queue contract). | pixel gate (particle-heavy checkpoint) |
|
||||
| **V4f** ↻ **re-sequenced — §5.5.5** | `SkyRenderer` + weather. | pixel gate (dawn/dusk, day group pinned) |
|
||||
|
|
@ -1278,7 +1278,7 @@ than deleting it outright.
|
|||
with no world renderers. Its gate is an offline Vulkan launch reaching the real
|
||||
composition with a captured UI frame, a converging ownership ledger at shutdown,
|
||||
and the strict GL offline gate unmoved.
|
||||
3. **V4t**, the texture stack, which the world arm cannot be written without.
|
||||
3. ~~**V4t**, the texture stack~~ — done at `b8bcaa3e`/`565c351f` (§5.5.11).
|
||||
4. **The world arm** — V4c/V4d's content behind the construction-time backend
|
||||
selection at the frame-root seam.
|
||||
|
||||
|
|
@ -1392,6 +1392,108 @@ 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.5.11 V4t (2026-07-28): the world's data model is backend-neutral
|
||||
|
||||
§5.5.9's step 3 is done, in two commits — `b8bcaa3e` (terrain) and `565c351f`
|
||||
(everything else). Every world batch now carries a `GpuTextureSlot` rather than
|
||||
a raw 64-bit `ARB_bindless_texture` handle, and the four interim
|
||||
`GlBindlessHandleTable` instances in `WbDrawDispatcher`, `EnvCellRenderer`,
|
||||
`TerrainModernRenderer` and `ParticleRenderer` are gone. The Vulkan world arm's
|
||||
prerequisite is met: its batch structs are already the shape it needs.
|
||||
|
||||
**§5.2's reason not to reach the device's table expired rather than being
|
||||
worked around.** That paragraph's argument was the flush — `GlGpuDevice` drains
|
||||
its dirty table runs inside `FlushBeforeDraw`, which only an encoder-recorded
|
||||
draw reaches, so a raw-GL renderer would sample a stale table. It assumed V4c
|
||||
would move those renderers onto the encoder. §5.5.6 closed the GL re-land, so
|
||||
they stay raw GL through to V10 and "wait for the encoder" became an indefinite
|
||||
block on the one slice the Vulkan world arm cannot be written without. The
|
||||
resolution is two internal members — `FlushTextureTable` (the drain, factored
|
||||
out of `FlushBeforeDraw`) and the already-existing `TextureTableGlName` — which
|
||||
each raw-GL renderer calls immediately before its own draw, in the exact shape
|
||||
its private table had. Both are deleted with the raw-GL world path.
|
||||
|
||||
**The slice was narrower than §5.3 sized it, in a way worth recording.**
|
||||
§5.3 wrote "porting the whole texture stack", and the V4t row said "onto
|
||||
`IGpuTexture`/`IGpuSampler`". What landed keeps texture CREATION and residency
|
||||
with the caches — `ManagedGLTextureArray`, `CompositeTextureArrayCache`'s GL
|
||||
backend, `TerrainAtlas` and `TextureCache`'s array upload all still speak raw
|
||||
GL — and moves only the TABLE ENTRY to the device, keyed 1:1 by the caller's
|
||||
already-resident handle. That is the whole of what the data model needed, and
|
||||
it is what let one slice retype nine source and five test files instead of
|
||||
rewriting three texture caches. Creating world textures through `IGpuTexture`
|
||||
is real remaining work and it belongs with the Vulkan world arm, which is the
|
||||
first thing that cannot use a GL handle at all.
|
||||
|
||||
**Slot release is stricter than the tables it replaces.** The interim tables
|
||||
never released — entries accumulated for the renderer's lifetime, by design and
|
||||
by comment. The device's table is capped at
|
||||
`GpuBindingModel.TextureTableCapacity` (16,384), so an unreleased entry is now
|
||||
a leak with an end, and every producer retires its own: the composite backend
|
||||
and the particle backend at `MakeNonResident`, and `ObjectMeshManager` when a
|
||||
retiring atlas's physical retirement completes. Teardown deliberately does not
|
||||
release — the device dies with its callers, and deferring through a
|
||||
possibly-disposed retirement queue would turn a clean shutdown into a throw.
|
||||
|
||||
**The default value became load-bearing.** `BindlessTextureLocation` could
|
||||
signal "not resolved" with handle 0 because no texture has handle 0. A slot
|
||||
index has no spare value — `default(GpuTextureSlot)` is real slot 0 — so the
|
||||
type is now a struct storing its slot one-based, making `default` exactly
|
||||
`Unresolved`, with a test pinning that a location naming slot 0 is
|
||||
distinguishable from it. Everywhere else the sentinel was already exact:
|
||||
`GpuTextureSlot.Unassigned` is `0xFFFFFFFF`, which is `common.glsl`'s
|
||||
`ACDREAM_TEXTURE_NONE`, so the classify path's readiness test and the particle
|
||||
billboard's untextured branch kept their meaning unchanged.
|
||||
|
||||
**`GroupKey` ordering is preserved because the key never ordered anything.**
|
||||
Handle→slot is a bijection, so the same (entity, batch) pairs bucket together.
|
||||
The key reaches equality, hashing and the scene-digest fingerprints, never a
|
||||
comparator: opaque and translucent groups sort by cull mode then camera
|
||||
distance, delayed alpha by viewer distance then submission ordinal, and group
|
||||
enumeration follows the dictionary's insertion order, which a changed hash does
|
||||
not disturb. Both sides of the render-shadow comparison hash the slot index the
|
||||
same way, so the digest value moving is invisible to it.
|
||||
|
||||
**`SkyRenderer` keeps its `GlBindlessHandleTable`,** which is why that class
|
||||
still exists. Its textures are minted by the sky renderer itself from
|
||||
`TextureCache.GetOrUpload`'s raw GL texture names — the one world path this
|
||||
slice did not retype — so it would be the sole consumer interning handles it
|
||||
produced, a different shape from the rest of the stack. The offline gate also
|
||||
masks the sky band, so the only automated instrument available here could not
|
||||
see a regression in it. V4f owns that renderer and should retire the table and
|
||||
the class together.
|
||||
|
||||
**Gate results.** GL offline pixel gate against `cb2a70b8`: 3.02e-05 at
|
||||
`b8bcaa3e`, and 5.50e-05 / 3.91e-05 on two captures at `565c351f`. The first of
|
||||
those is above the documented 15–23 px band, so a control was measured rather
|
||||
than assumed: two same-commit captures at `565c351f` differ by 19 px, and a
|
||||
capture at `b8bcaa3e` versus one at `565c351f` differs by **9 px** — fewer than
|
||||
the same-commit control, across two different commits. Maximum channel delta is
|
||||
41–52 in every pair including the controls, so the differing pixels come from
|
||||
one flickering population rather than from moved geometry. Both commits passed
|
||||
`tools/run-repeat-connected-gate.ps1 -Runs 3` at 3/3 RENDERED on the desktop
|
||||
witness and the client capture, and one Vulkan composition-host run each with
|
||||
`VK_LAYER_KHRONOS_validation` **proven inserted by the loader** — zero errors,
|
||||
zero warnings, converged ownership ledger. App tests 4,077 / 3 skips and the
|
||||
complete Release suite 9,140 / 5, both baselines plus the two tests added.
|
||||
|
||||
**One connected run died and is filed, not attributed.** The first 3-run
|
||||
attempt at `b8bcaa3e` lost one run to an unhandled
|
||||
`OpenGL returned unexpected fence wait status NoError (0x0)` in the render
|
||||
loop. It did not reproduce in the following three runs at that tree nor in
|
||||
three interleaved runs at `cb2a70b8`, and V4t creates, deletes and waits on no
|
||||
fence. `#251` records it with the evidence; it is the same below-the-API shape
|
||||
§5.5.1–§5.5.3 documented four instances of on this driver, but that is a
|
||||
hypothesis and the issue says so.
|
||||
|
||||
**Next is the world arm** — V4c/V4d's content behind the construction-time
|
||||
backend selection at the frame-root seam. Two things this slice hands it:
|
||||
§5.5.8's one-binding-two-buffers hazard is still unfired and the world arm is
|
||||
what will fire it, since `WbDrawDispatcher` and `EnvCellRenderer` each own
|
||||
instance and batch buffers and both bind bindings 0, 1, 3, 4 and 5 in one
|
||||
frame; and the world path's own texture creation still has to reach
|
||||
`IGpuTexture`, because a Vulkan draw cannot sample a GL handle.
|
||||
|
||||
### 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
|
||||
|
|
|
|||
|
|
@ -18,16 +18,20 @@ namespace AcDream.App.Rendering;
|
|||
/// accumulate for the renderer's lifetime, exactly like the world atlas and
|
||||
/// composite-texture caches it draws handles from.
|
||||
///
|
||||
/// This class, plus the per-renderer SSBO it backs, is deleted at
|
||||
/// V4c/V4d/V4e when each renderer moves onto <c>IGpuDevice</c>'s own
|
||||
/// retirement-gated table — see the campaign doc's §5.2 for why V2 cannot
|
||||
/// reach that table yet.
|
||||
/// <b>Campaign V slice V4t retired every instance but one.</b> The world
|
||||
/// texture stack now produces <c>GpuTextureSlot</c> directly, so
|
||||
/// WbDrawDispatcher, EnvCellRenderer, TerrainModernRenderer and
|
||||
/// ParticleRenderer share <c>GlGpuDevice</c>'s single retirement-gated table
|
||||
/// instead of interning handles themselves — see that class's
|
||||
/// <c>RegisterWorldTextureHandle</c> and the campaign doc's §5.5.11.
|
||||
///
|
||||
/// Each renderer that needs the indirection (WbDrawDispatcher,
|
||||
/// EnvCellRenderer, TerrainModernRenderer, ParticleRenderer) owns its own
|
||||
/// instance. Nothing requires index agreement between renderers: each rebinds
|
||||
/// its own buffer to binding=9 immediately before its own draw call, so two
|
||||
/// renderers may legitimately assign different slots to the same handle.
|
||||
/// <b>The remaining owner is <c>SkyRenderer</c></b>, whose textures are minted
|
||||
/// from <c>TextureCache.GetOrUpload</c>'s raw GL texture names — the one world
|
||||
/// path V4t did not retype — so it is the sole consumer interning handles it
|
||||
/// produced itself. Slice V4f retires that table, and this class with it.
|
||||
/// Nothing requires index agreement between the sky's table and the device's:
|
||||
/// each rebinds its own buffer to binding=9 immediately before its own draw
|
||||
/// call, so the same handle may legitimately hold different slots in the two.
|
||||
/// </summary>
|
||||
internal sealed class GlBindlessHandleTable
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue