acdream/tests
Erik f353fb53f8 feat(render): Campaign V slice V4c - move the world draw path onto the RHI
The two renderers that draw everything in the world - WbDrawDispatcher for
entities and EnvCellRenderer for dungeon shells - now record through
IGpuPassEncoder instead of calling GL directly. They share mesh_modern and its
binding layout, which is why they had to move together.

What moved. Every per-frame upload became an IGpuFrame.AllocateRing slice:
instance transforms, batch metadata, clip slots, global lights, per-instance
light sets, indoor flags, opacity, selection lighting, and the indirect command
array. That retires both renderers' DynamicBufferSet pools outright. Those pools
existed so a second Draw within one frame could not overwrite an earlier draw's
still-pending data; the frame ring gives that structurally, because every
allocation within a frame is distinct memory that lives until the frame retires.
DynamicBufferSetCount now reports 0 for both, which is the truth rather than a
silent change - they own no such pool any more.

The imperative Enable/Disable/BlendFunc/DepthMask brackets around the two
multi-draw passes became pipeline variants: five for the dispatcher (opaque,
opaque+alpha-to-coverage, and the three retail blends) and three for the cell
shells. Cull mode and front face stay dynamic per MDI run, exactly where
ApplyCullMode and SetCullMode set them, because core Vulkan 1.3 makes those
dynamic and blend and alpha-to-coverage not. ApplyRetailBlend is gone: its three
cases are now three pipelines, including the inverse-alpha one that
GpuBlendMode.InverseAlpha was added for. uViewProjection, uDrawIDOffset,
uLightingMode, uRenderPass and uLightDebug became fields of the shared
GpuPushConstants block. Issue #52's per-pass batch offset is unchanged - the
draw index still resets per indirect call, and Vulkan's gl_DrawID resets
identically.

Depth compare is baked as GL_LESS, not the contract's LessOrEqual default. The
world frame runs under GL_LESS (RenderFrameGlStateController.RestoreFrameDefaults)
and neither renderer ever called glDepthFunc, so both inherited it; baking
LessOrEqual would have changed which of two coplanar retail surfaces wins.

Two uniform writes were dropped rather than ported, and both are no-ops today:
uFilterByCell and uHighlightColor are declared in neither mesh_modern stage, so
they resolved to location -1. Saying so here rather than letting them vanish.

GPU timing moved to IGpuPassEncoder.BeginTimerScope. The [WB-DIAG] median/p95
window is still fed and still measures opaque + transparent time for the
dispatch, but the sample now comes from IGpuTimerPool.TryResolve - the most
recent retired result - instead of a hand-rolled 3-deep query ring read at N-3.
A sample can therefore repeat when the GPU has not finished a newer query,
where the old code dropped it. The pool also owns the #125 "never read a query
that was never begun" guard now. Diagnostic-only, and flagged rather than left
to be discovered.

Three things deliberately did NOT move, per the campaign doc's section 5.3.
The interim GlBindlessHandleTable stays; both renderers still intern raw
bindless handles and now bind that table through the encoder as an ordinary
IGpuBuffer at binding 9. Retiring it is slice V4t, because the handles are
produced by the texture caches and carried through GroupKey and CachedBatch.
ClipFrame's region buffer (binding 2) and the SceneLighting UBO stay globally
bound by raw GL, because terrain and the viewport/portal renderers read the same
bindings and are raw GL until V4d/V4g. EnvCellRenderer's glMemoryBarrier stays a
raw call: it has no RHI verb, and it guards incoherent shader writes that
acdream does not make, so it was already a no-op against client-side uploads.

RetailAlphaQueue, the GroupKey bucketing, the front-to-back and translucent sort
orders, and every other piece of CPU fidelity logic are untouched. The deferred
alpha payload is still prepared exactly once per sorted alpha scope: a ring
allocation cannot outlive its frame as a ref struct, but its buffer, offset and
size can be stored, so DrawPreparedAlphaBatch binds the same bytes many times
without recopying them.

Two supporting changes outside the two renderers, both flagged.

GlGpuDevice.BeginPass no longer binds framebuffer 0 for a null colour target; it
leaves the binding alone and only binds an explicitly named target. A null target
means "whatever the spine bound", which is what GpuPassDescription's own remarks
describe when they say clears and framebuffer management stay with the spine
until V4h. Forcing 0 would have been fatal here and invisible to this gate:
PrivateEntityViewportRenderer binds its offscreen FBO and then calls
WbDrawDispatcher.Draw, as does PortalTunnelPresentation, so the paperdoll and
creature-appraisal viewports would have rendered to the backbuffer and left their
textures empty - and the offline gate does not cover those viewports. This is the
same class of fix as the ambient-capability save/restore in GlGpuPassEncoder.

GlGpuDevice.CreatePipeline now splices the slice-V2 shared preamble
(Shaders/common.glsl) into every pipeline, reusing Shader.InjectPreamble - widened
from private to internal - so a pipeline-compiled program and a Shader-compiled
one are built from byte-identical sources. mesh_modern requires it: the preamble
declares the binding-9 table and defines ACDREAM_TEXTURE_HANDLE, without which
the world shaders do not compile. Shaders that reference none of it gain an
unused SSBO declaration and two macros; every shader in the tree is #version 430
core, so that is always legal.

Both renderers keep their trailing raw-GL disable block after the pass closes.
The encoder's Dispose restores the capability state that was ambient on ENTRY,
which is not the state these renderers used to leave behind - terrain, sky and
particles are still raw GL and still inherit what the previous renderer left, so
the exit state is reasserted explicitly. It goes at V4h with the last raw-GL
renderer.

A defect caught in review and fixed before the gate: each IGpuPipeline owns its
own vertex array, and vertex attribute pointers plus the index binding are
vertex-array state, so switching blend variants mid-pass silently dropped the
mesh source while the storage bindings survived. Every pipeline switch now goes
through one helper that re-binds the arena.

Gates. Release build green with TreatWarningsAsErrors. App tests 3,844 passed /
3 skipped, stable over four consecutive runs, against a 3,843 baseline plus the
InverseAlpha contract test. Offline pixel gate against 111e7236: 20 differing
pixels of 563,200 compared (fraction 3.55e-05), against a same-commit control
captured immediately afterwards of 17 - indistinguishable from capture noise and
28x under the 0.001 threshold. The gate run's client log has zero exceptions and
an empty stderr.

Coverage gap, stated rather than assumed: the offline gate's scene is a fixed
outdoor view, so it exercises WbDrawDispatcher heavily and EnvCellRenderer not at
all. Dungeon interiors, the paperdoll and appraisal viewports, and portal transit
need a user visual check before this slice is considered proven.

No divergence-register row: this slice changes no retail-facing behaviour.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-27 20:55:25 +02:00
..
AcDream.App.Tests feat(render): Campaign V slice V4c - move the world draw path onto the RHI 2026-07-27 20:55:25 +02:00
AcDream.Bake.Tests feat(content): bake and read flat collision assets 2026-07-25 15:22:08 +02:00
AcDream.Cli.Tests perf(diag): complete trustworthy Slice A capture tooling 2026-07-24 12:46:51 +02:00
AcDream.Content.Tests feat(headless): share immutable gameplay content 2026-07-27 09:00:48 +02:00
AcDream.Core.Net.Tests perf(net): frame recurring sends in place 2026-07-25 06:02:52 +02:00
AcDream.Core.Tests feat(render): Campaign V slice V4b - move the mesh arena onto IGpuBuffer 2026-07-27 20:02:50 +02:00
AcDream.Core.Tests.Fixtures.HelloPlugin feat(core): add PluginLoader with collectible ALC 2026-04-10 09:51:16 +02:00
AcDream.Headless.Tests perf(headless): enforce K4 resource envelope 2026-07-27 11:14:50 +02:00
AcDream.Runtime.Tests feat(linux): add graphical platform services 2026-07-27 11:54:59 +02:00
AcDream.UI.Abstractions.Tests feat(linux): add graphical platform services 2026-07-27 11:54:59 +02:00