feat(render): Campaign V slice V6i-3 commit 1 — the mesh pipeline's upload bodies cross the seam
V6i-2 cut IMeshPipelineDevice at the measured surface and proved the mesh
pipeline could be CONSTRUCTED without naming a backend. It said plainly what it
did 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."
This moves them, and gives the interface its second implementation.
GlobalMeshBuffer takes GL?. The two backing stores were already IGpuBuffer
(V4b); what still needed a context was the vertex array and the attribute
pointers, which have no RHI verb because Vulkan bakes vertex input into the
pipeline. So 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. HasStores is the backend-neutral form
of the VAO != 0 readiness test the raw-GL draw paths make. Two bodies fork on
the context and nothing else does: InitBuffers skips the vertex array, and
CommitMigration skips the rebind — on the encoder arm the field swap IS the
atomic publication, because the next pass reads whatever the field then holds.
The store deletion likewise splits: GL keeps its immediate DeleteRetired,
because the arena's own flight gate has already proven no submitted frame can
reference the store, while the other arm has no second deferral to skip and
Dispose is its retirement-queued release.
ObjectMeshManager's RequireGl narrowed to the LEGACY per-mesh upload. Its three
call sites were one modern-path constructor argument and two bodies whose every
GL statement sits inside `if (!_useModernRendering)`. The constructor now hands
the arena the nullable context; the two bodies resolve one lazily inside the
legacy branch. That branch is unreachable in every shipping configuration —
missing bindless or draw-parameters throws at startup under the N.5 ship
amendment — so the accessor survives as the guard on dead code rather than as a
blocker, and it is deleted with that code.
VulkanMeshPipelineDevice is the second implementation, and it is four
properties and two no-ops. Two things about it are worth stating rather than
leaving to be inferred. HasBindless and HasOpenGL43 answer TRUE: their names are
GL-shaped because the seam was cut from a GL device, but what they gate is the
MODERN path — one shared arena, table texture indexing, multi-draw indirect —
which Vulkan supplies unconditionally and the capability gate rejects a device
for lacking, so answering false would disable the only path that exists.
HasPendingWork answers false because the GL device's queue exists to defer work
onto the thread holding the context, and Vulkan resource work is recorded into
the frame's command buffer or routed through the retirement queue.
WbMeshAdapter selects between them once, in the one place the mesh pipeline
still names a backend. The GL arm is unchanged, including the queue-drain
guarantee its construction rollback asserts.
So composition builds the mesh pipeline on BOTH arms, and NullWbMeshAdapter is
deleted — it existed for exactly the gap this closes, and the landblock spawn
ledger now registers against the real adapter. Streaming's publication into GPU
state stops being a no-op there: the Vulkan run below builds real render data,
including the [up-null] zero-vertex caching path.
Gates. Release build green. App tests 4,112 passed / 3 skipped, against a 4,109
baseline plus the three added here. Strict GL offline pixel gate against
579e0b7f: 4.44e-05 (25 differing pixels of 563,200), inside the documented 9-31
px control band and 22x under the 0.001 threshold. One offline Vulkan run with
VK_LAYER_KHRONOS_validation proven inserted by the loader (VK_LOADER_DEBUG=layer
reports `Insert instance layer "VK_LAYER_KHRONOS_validation"`): zero validation
errors, zero warnings, a captured frame, and no [shutdown] diagnostic on either
stream.
What this does NOT claim: nothing draws the world on Vulkan yet. The three
world renderers' submission arms, the two pass executors, and the pass-structure
merge are the next commit's.
No divergence-register row: no retail-facing behaviour changes.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
579e0b7f60
commit
fe8abacfc6
10 changed files with 410 additions and 186 deletions
|
|
@ -1,4 +1,4 @@
|
|||
using Chorizite.Core.Lib;
|
||||
using Chorizite.Core.Lib;
|
||||
using Chorizite.Core.Render;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using DatReaderWriter.DBObjs;
|
||||
|
|
@ -125,16 +125,25 @@ namespace AcDream.App.Rendering.Wb
|
|||
private readonly IMeshPipelineDevice _graphicsDevice;
|
||||
|
||||
/// <summary>
|
||||
/// The GL context this class's still-raw upload bodies write through.
|
||||
/// Null only on a backend with none, where every one of those bodies is
|
||||
/// a programming error rather than a runtime condition — the slice that
|
||||
/// ports them owns deleting this accessor.
|
||||
/// The GL context the LEGACY (pre-modern-path) upload bodies write
|
||||
/// through.
|
||||
///
|
||||
/// <para>Campaign V slice V6i-3 narrowed what still needs it. The modern
|
||||
/// path's arena upload is <see cref="GlobalMeshBuffer"/>'s, and that is
|
||||
/// now <see cref="AcDream.App.Rendering.Gpu.IGpuBuffer"/> work on both
|
||||
/// arms; what remains raw is the per-mesh VAO/VBO/IBO construction the
|
||||
/// N.5 ship amendment made unreachable — missing bindless or
|
||||
/// draw-parameters throws at startup, so <c>_useModernRendering</c> is
|
||||
/// true in every shipping configuration. The accessor therefore survives
|
||||
/// as the guard on genuinely dead code rather than as a blocker, and it
|
||||
/// is deleted with that code.</para>
|
||||
/// </summary>
|
||||
private GL RequireGl() =>
|
||||
_graphicsDevice.Gl
|
||||
?? throw new InvalidOperationException(
|
||||
"The mesh pipeline's upload bodies are still raw GL and this device has no "
|
||||
+ "context. Campaign V's world-draw slice owns porting them.");
|
||||
"The mesh pipeline's legacy per-mesh vertex-array upload is raw GL and this "
|
||||
+ "device has no context. The modern path is mandatory (N.5 ship amendment), "
|
||||
+ "so reaching this is a composition error rather than a backend gap.");
|
||||
private readonly IPreparedAssetSource _preparedAssets;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
|
|
@ -522,7 +531,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
if (_useModernRendering)
|
||||
{
|
||||
GlobalBuffer = new GlobalMeshBuffer(
|
||||
RequireGl(),
|
||||
_graphicsDevice.Gl,
|
||||
gpuDevice,
|
||||
_graphicsDevice.ResourceRetirement);
|
||||
}
|
||||
|
|
@ -1984,7 +1993,11 @@ namespace AcDream.App.Rendering.Wb
|
|||
{
|
||||
if (meshData.Vertices.Length == 0) return null;
|
||||
|
||||
var gl = RequireGl();
|
||||
// Resolved lazily since Campaign V slice V6i-3: every reader below
|
||||
// is inside a !_useModernRendering branch, and the modern path is
|
||||
// mandatory, so a backend with no GL context uploads meshes here
|
||||
// without ever asking for one.
|
||||
GL? gl = _graphicsDevice.Gl;
|
||||
uint vao = 0, vbo = 0;
|
||||
var modernIndexBatches = meshData.TextureBatches.Values
|
||||
.SelectMany(batches => batches)
|
||||
|
|
@ -2008,40 +2021,41 @@ namespace AcDream.App.Rendering.Wb
|
|||
}
|
||||
else
|
||||
{
|
||||
gl.GenVertexArrays(1, out vao);
|
||||
gl.BindVertexArray(vao);
|
||||
GL legacyGl = RequireGl();
|
||||
legacyGl.GenVertexArrays(1, out vao);
|
||||
legacyGl.BindVertexArray(vao);
|
||||
|
||||
gl.GenBuffers(1, out vbo);
|
||||
gl.BindBuffer(GLEnum.ArrayBuffer, vbo);
|
||||
legacyGl.GenBuffers(1, out vbo);
|
||||
legacyGl.BindBuffer(GLEnum.ArrayBuffer, vbo);
|
||||
fixed (VertexPositionNormalTexture* ptr = meshData.Vertices)
|
||||
{
|
||||
gl.BufferData(GLEnum.ArrayBuffer, (nuint)(meshData.Vertices.Length * VertexPositionNormalTexture.Size), ptr, GLEnum.StaticDraw);
|
||||
legacyGl.BufferData(GLEnum.ArrayBuffer, (nuint)(meshData.Vertices.Length * VertexPositionNormalTexture.Size), ptr, GLEnum.StaticDraw);
|
||||
}
|
||||
GpuMemoryTracker.TrackAllocation(meshData.Vertices.Length * VertexPositionNormalTexture.Size, GpuResourceType.Buffer);
|
||||
|
||||
int stride = VertexPositionNormalTexture.Size;
|
||||
// Position (location 0)
|
||||
gl.EnableVertexAttribArray(0);
|
||||
gl.VertexAttribPointer(0, 3, GLEnum.Float, false, (uint)stride, (void*)0);
|
||||
legacyGl.EnableVertexAttribArray(0);
|
||||
legacyGl.VertexAttribPointer(0, 3, GLEnum.Float, false, (uint)stride, (void*)0);
|
||||
// Normal (location 1)
|
||||
gl.EnableVertexAttribArray(1);
|
||||
gl.VertexAttribPointer(1, 3, GLEnum.Float, false, (uint)stride, (void*)(3 * sizeof(float)));
|
||||
legacyGl.EnableVertexAttribArray(1);
|
||||
legacyGl.VertexAttribPointer(1, 3, GLEnum.Float, false, (uint)stride, (void*)(3 * sizeof(float)));
|
||||
// TexCoord (location 2)
|
||||
gl.EnableVertexAttribArray(2);
|
||||
gl.VertexAttribPointer(2, 2, GLEnum.Float, false, (uint)stride, (void*)(6 * sizeof(float)));
|
||||
legacyGl.EnableVertexAttribArray(2);
|
||||
legacyGl.VertexAttribPointer(2, 2, GLEnum.Float, false, (uint)stride, (void*)(6 * sizeof(float)));
|
||||
|
||||
// Instance data (shared VBO)
|
||||
gl.BindBuffer(GLEnum.ArrayBuffer, _graphicsDevice.InstanceVBO);
|
||||
legacyGl.BindBuffer(GLEnum.ArrayBuffer, _graphicsDevice.InstanceVBO);
|
||||
for (uint i = 0; i < 4; i++)
|
||||
{
|
||||
var loc = 3 + i;
|
||||
gl.EnableVertexAttribArray(loc);
|
||||
gl.VertexAttribPointer(loc, 4, GLEnum.Float, false, (uint)sizeof(InstanceData), (void*)(i * 16));
|
||||
gl.VertexAttribDivisor(loc, 1);
|
||||
legacyGl.EnableVertexAttribArray(loc);
|
||||
legacyGl.VertexAttribPointer(loc, 4, GLEnum.Float, false, (uint)sizeof(InstanceData), (void*)(i * 16));
|
||||
legacyGl.VertexAttribDivisor(loc, 1);
|
||||
}
|
||||
gl.EnableVertexAttribArray(8);
|
||||
gl.VertexAttribIPointer(8, 1, GLEnum.UnsignedInt, (uint)sizeof(InstanceData), (void*)64);
|
||||
gl.VertexAttribDivisor(8, 1);
|
||||
legacyGl.EnableVertexAttribArray(8);
|
||||
legacyGl.VertexAttribIPointer(8, 1, GLEnum.UnsignedInt, (uint)sizeof(InstanceData), (void*)64);
|
||||
legacyGl.VertexAttribDivisor(8, 1);
|
||||
}
|
||||
|
||||
// Allocate the shared vertex/index range before acquiring texture
|
||||
|
|
@ -2120,12 +2134,13 @@ namespace AcDream.App.Rendering.Wb
|
|||
}
|
||||
else
|
||||
{
|
||||
gl.GenBuffers(1, out ibo);
|
||||
gl.BindBuffer(GLEnum.ElementArrayBuffer, ibo);
|
||||
GL legacyGl = RequireGl();
|
||||
legacyGl.GenBuffers(1, out ibo);
|
||||
legacyGl.BindBuffer(GLEnum.ElementArrayBuffer, ibo);
|
||||
var indexArray = batch.Indices.ToArray();
|
||||
fixed (ushort* iptr = indexArray)
|
||||
{
|
||||
gl.BufferData(GLEnum.ElementArrayBuffer, (nuint)(indexArray.Length * sizeof(ushort)), iptr, GLEnum.StaticDraw);
|
||||
legacyGl.BufferData(GLEnum.ElementArrayBuffer, (nuint)(indexArray.Length * sizeof(ushort)), iptr, GLEnum.StaticDraw);
|
||||
}
|
||||
GpuMemoryTracker.TrackAllocation(indexArray.Length * sizeof(ushort), GpuResourceType.Buffer);
|
||||
legacyIndexBuffers.Add((ibo, indexArray.Length * sizeof(ushort)));
|
||||
|
|
@ -2200,7 +2215,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
|
||||
if (!_useModernRendering)
|
||||
{
|
||||
gl.BindVertexArray(0);
|
||||
RequireGl().BindVertexArray(0);
|
||||
}
|
||||
return renderData;
|
||||
}
|
||||
|
|
@ -2234,7 +2249,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
|
||||
private RetryableResourceReleaseLedger CreateUploadRollback(
|
||||
ObjectMeshData meshData,
|
||||
GL gl,
|
||||
GL? gl,
|
||||
uint vao,
|
||||
uint vbo,
|
||||
GlobalMeshAllocation? globalAllocation,
|
||||
|
|
@ -2269,12 +2284,13 @@ namespace AcDream.App.Rendering.Wb
|
|||
|
||||
if (!_useModernRendering)
|
||||
{
|
||||
GL legacyGl = gl ?? RequireGl();
|
||||
for (int i = 0; i < legacyIndexBuffers.Count; i++)
|
||||
{
|
||||
int bufferIndex = i;
|
||||
releases.Add((
|
||||
$"legacy-index-buffer-{bufferIndex}-delete",
|
||||
() => gl.DeleteBuffer(legacyIndexBuffers[bufferIndex].Name)));
|
||||
() => legacyGl.DeleteBuffer(legacyIndexBuffers[bufferIndex].Name)));
|
||||
releases.Add((
|
||||
$"legacy-index-buffer-{bufferIndex}-accounting",
|
||||
() => GpuMemoryTracker.TrackDeallocation(
|
||||
|
|
@ -2284,7 +2300,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
|
||||
if (vbo != 0)
|
||||
{
|
||||
releases.Add(("legacy-vertex-buffer-delete", () => gl.DeleteBuffer(vbo)));
|
||||
releases.Add(("legacy-vertex-buffer-delete", () => legacyGl.DeleteBuffer(vbo)));
|
||||
releases.Add((
|
||||
"legacy-vertex-buffer-accounting",
|
||||
() => GpuMemoryTracker.TrackDeallocation(
|
||||
|
|
@ -2292,7 +2308,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
GpuResourceType.Buffer)));
|
||||
}
|
||||
if (vao != 0)
|
||||
releases.Add(("legacy-vertex-array-delete", () => gl.DeleteVertexArray(vao)));
|
||||
releases.Add(("legacy-vertex-array-delete", () => legacyGl.DeleteVertexArray(vao)));
|
||||
}
|
||||
|
||||
return new RetryableResourceReleaseLedger(releases);
|
||||
|
|
@ -2431,7 +2447,6 @@ namespace AcDream.App.Rendering.Wb
|
|||
return null;
|
||||
|
||||
var releases = new List<(string Name, Action Release)>();
|
||||
GL gl = RequireGl();
|
||||
if (_useModernRendering)
|
||||
{
|
||||
if (data.GlobalAllocation is { } allocation)
|
||||
|
|
@ -2446,6 +2461,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
}
|
||||
else
|
||||
{
|
||||
GL gl = RequireGl();
|
||||
if (data.VAO != 0)
|
||||
releases.Add(("legacy-vertex-array-delete", () => gl.DeleteVertexArray(data.VAO)));
|
||||
if (data.VBO != 0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue