refactor(render): Campaign V slice V6i-2 commit 3 — the mesh pipeline stops naming a backend

Plan §5.5.10 recorded the blocker as a fact about types: "WbMeshAdapter owns an
OpenGLGraphicsDevice, so it is not constructible on Vulkan until slice V4t" —
which is the entire reason NullWbMeshAdapter exists. §5.5.12 item 6 then measured
how wide that dependency really is, and the answer is seven members out of a
760-line class: a GL context, the retirement queue, the shared instance VBO, and
two capability flags.

IMeshPipelineDevice is exactly that surface. OpenGLGraphicsDevice declares it and
every member already existed under a GL-specific name, so the shipping backend
executes not one changed statement — these are aliases, not behaviour.

Two casts moved, and they are what actually blocked construction:

- ObjectMeshManager downcast IGpuDevice to GlGpuDevice in its CONSTRUCTOR, so a
  Vulkan-composed pipeline threw before running a statement. V4t put it there
  because the class registered bindless handles itself; commit 2 moved that into
  the array, leaving the field a pass-through for the raw-GL renderers' handle
  table. The cast now lives on that one property and names the backend it was
  composed against instead of reporting a failed cast.
- The atlas array factory is selected by IWorldTextureArrayFactory.For, which is
  the one place the texture stack branches on a backend.

MeshPipelineDeviceSeamTests proves the decoupling rather than describing it: it
builds ObjectMeshManager against a device whose Gl is null, asserts it constructs,
asserts construction built no GL object, asserts the handle table refuses by name,
and asserts the factory picks the RHI arm. A reflection test pins the seam's
member set so a later slice cannot quietly widen it back out — the whole value
here is that it is narrow.

What this does NOT claim: the mesh pipeline does not RUN on Vulkan. Its upload
bodies are still raw GL — GlobalMeshBuffer, the VAO/IBO construction, the layer
transfers — and they now fail through one RequireGl() accessor that names the
slice that owns porting them, instead of failing at construction. WbMeshAdapter
still creates an OpenGLGraphicsDevice in its GL constructor, because there is no
second implementation to create yet. Those bodies are items 3–5 of §5.5.12's
remainder list, along with RetailPViewPassExecutor and the three world renderers'
submission arms.

§5.5.13 reports the whole of V6i-2 and the slice table gains its V6i row.

Gates: Release build; App tests 4,109 / 3 skips (the 4,086 baseline plus 23 across
the three commits); complete Release suite 9,172 / 5; strict GL offline pixel gate
vs 0ca802cd 1.60e-05 (9 px of 563,200 — the low end of the documented 9–31 px
control band, and fewer than a same-commit control has measured); GL connected
tools/run-repeat-connected-gate.ps1 -Runs 3 at 3/3 RENDERED on the desktop witness
and 3/3 on the client capture; one Vulkan composition-host run with
VK_LAYER_KHRONOS_validation proven inserted by the loader at zero errors, zero
warnings, no [shutdown] diagnostic, and a captured frame.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-07-28 14:09:48 +02:00
parent c8d0f70bbe
commit 5b3d72a90c
7 changed files with 459 additions and 18 deletions

View file

@ -101,6 +101,26 @@ internal interface IWorldTextureArray : IDisposable
/// </summary>
internal interface IWorldTextureArrayFactory
{
/// <summary>
/// Campaign V slice V6i-2: picks the arm from what the composed devices
/// actually are. This is the ONE place the mesh pipeline's texture stack
/// branches on a backend, which is what lets everything above it — capacity
/// policy, slot allocation, ref counting, layer retirement, eviction — be
/// written once.
/// </summary>
internal static IWorldTextureArrayFactory For(
IMeshPipelineDevice graphicsDevice,
IGpuDevice gpuDevice,
ILogger logger)
{
ArgumentNullException.ThrowIfNull(graphicsDevice);
ArgumentNullException.ThrowIfNull(gpuDevice);
ArgumentNullException.ThrowIfNull(logger);
return graphicsDevice is OpenGLGraphicsDevice gl && gpuDevice is GlGpuDevice table
? new GlWorldTextureArrayFactory(gl, table, logger)
: new RhiWorldTextureArrayFactory(gpuDevice);
}
/// <summary>The retirement queue array layers and images are released through.</summary>
IGpuResourceRetirementQueue Retirement { get; }