feat(render): Vulkan campaign V11 step 2 — delete the OpenGL backend
Vulkan is the sole, user-signed-off backend (V10 landed) and step 1 already removed ImGui/Studio/DevTools. This step deletes the GL rendering backend itself: every Gpu/Gl/** implementation, the Wb ManagedGL*/GLHelpers/GLSLShader/GLStateScope/RenderStateCache/ BindlessSupport family, Shader/ShaderProgramConstruction/SamplerCache, RenderBootstrap, and RenderFrameGlStateController. GameWindow.cs's Run()/CreateGraphics()/CreateBackbufferReader()/ OnLoad() collapse to their Vulkan-only arm; GameWindowGraphics loses its OpenGlGameWindowGraphics subclass. RuntimeOptions.RenderBackend and RenderBackendKind (incl. the Gl member of GpuBackendKind) are gone — there is nothing left to select between. The five world-draw dual-arm renderers (WbDrawDispatcher, EnvCellRenderer, TerrainModernRenderer, ParticleRenderer, SkyRenderer) and the composition roots (WorldRenderComposition, HostInputCameraComposition, LivePresentationComposition, FrameRootComposition) collapse to their RHI-only arm. GL-only diagnostic properties with a live external reader (DynamicBufferCount and friends) simplify to a documented `=> 0`/no-op rather than disappearing, since the reader is out of this commit's scope. A few GL-flavored mechanisms turned out to be backend-neutral once isolated: GlConstructionCleanupLedger is renamed ResourceConstructionCleanupLedger (exception-chain walking has nothing to do with GL), and GlfwNativePlatformProbe moved out of the otherwise GL-only GraphicalCapabilityRecord.cs into GraphicalWindowBackendSelection.cs before the rest of that file was deleted. Test files with no surviving subject are deleted outright (GraphicalCapabilityRequirementsTests, ShaderProgramConstructionTests, PortalDepthShaderParityTests, TextureCacheBindlessTests, TextRendererFailureSafetyTests, ClipFrameUploadTests, every Gpu/Gl/*Tests, GlTextureOwnershipTests, RenderFrameGlStateControllerTests); others get their dead GL-only members trimmed while their live assertions stay (ClipFrameLayoutTests' MeshClipSsboBinding check now reads GpuBindingModel.StorageClipRegions, the same binding index under its new backend-neutral name; GpuResourceRetirementTransactionTests drops its OpenGLGraphicsDevice-subclassing test double and the two GL queue tests it existed for). EnvCellRendererTests' construction helper now builds a real ObjectMeshManager via VulkanMeshPipelineDevice instead of passing null through a null-forgiving operator, since the RHI constructor never tolerated a null mesh manager and the old GL constructor (which did) is gone. Deferred to the next two steps, deliberately not touched here: the Silk.NET.OpenGL/.Extensions.ARB package references, IMeshPipelineDevice.Gl (WbMeshAdapter's GL? threading stays in place), Chorizite.Core's stale csproj comment (the package itself is still load-bearing — TextureFormat and friends are used well beyond the deleted ManagedGLUniformBuffer), and the CI/gate scripts. Build: `dotnet build AcDream.slnx -c Release` — 0 warnings, 0 errors. Tests: full-solution `dotnet test` green across every project (App.Tests 3937/3940 + 3 skips, Core.Tests 3296/3298 + 2 skips, all others 100%); the 2 App.Tests names that flake under full-suite parallel execution (#250-family, documented pre-existing) pass in isolation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b70b9832ff
commit
8a7a0837e1
121 changed files with 1243 additions and 19840 deletions
|
|
@ -1,23 +1,23 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Gpu.Gl;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.Core.Terrain;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Phase N.5b modern terrain dispatcher. Single global VBO/EBO with a slot
|
||||
/// allocator (one slot per landblock, 384 verts × 40 bytes = 15,360 bytes
|
||||
/// per slot). Per-frame: build a DrawElementsIndirectCommand array from
|
||||
/// visible slots, upload, dispatch via glMultiDrawElementsIndirect. Atlas
|
||||
/// textures bound via bindless handles set per-frame as sampler uniforms.
|
||||
/// Phase N.5b modern terrain dispatcher. Single global vertex/index arena with
|
||||
/// a slot allocator (one slot per landblock, 384 verts × 40 bytes = 15,360
|
||||
/// bytes per slot). Per-frame: build a DrawElementsIndirectCommand array from
|
||||
/// visible slots and dispatch via one multi-draw-indirect call. Atlas
|
||||
/// textures bound via the device's global texture table.
|
||||
///
|
||||
/// Total ~6-8 GL calls per frame for terrain regardless of visible
|
||||
/// landblock count.
|
||||
/// <para>Campaign V slice V11 deleted the raw-GL submission arm
|
||||
/// (<c>TerrainModernRenderer.cs</c>'s former GL fields/constructor/Draw/Dispose
|
||||
/// bodies); the RHI arm this file now exclusively hosts the shared allocator
|
||||
/// and visibility logic for is defined in <c>TerrainModernRenderer.Rhi.cs</c>.</para>
|
||||
/// </summary>
|
||||
public sealed unsafe partial class TerrainModernRenderer : IDisposable
|
||||
public sealed partial class TerrainModernRenderer : IDisposable
|
||||
{
|
||||
// VertsPerLandblock MUST stay divisible by 6 — terrain_modern.vert uses
|
||||
// `gl_VertexID % 6` to pick the cell-corner index (BL/BR/TR/TL), and
|
||||
|
|
@ -32,12 +32,6 @@ public sealed unsafe partial class TerrainModernRenderer : IDisposable
|
|||
private const int IndexSize = sizeof(uint);
|
||||
private const float LandblockSize = LandblockMesh.LandblockSize; // 192
|
||||
|
||||
// Campaign V slice V6j: null on the RHI arm, where every statement below that
|
||||
// reaches one of these is forked into TerrainModernRenderer.Rhi.cs. The GL arm
|
||||
// executes exactly what it did before.
|
||||
private readonly GL? _gl;
|
||||
private readonly BindlessSupport? _bindless;
|
||||
private readonly Shader? _shader;
|
||||
private readonly TerrainAtlas _atlas;
|
||||
|
||||
/// <summary>A.5 T22.5: exposes the terrain atlas so callers can update
|
||||
|
|
@ -46,7 +40,6 @@ public sealed unsafe partial class TerrainModernRenderer : IDisposable
|
|||
|
||||
private readonly GpuRetiredTerrainSlotAllocator _alloc;
|
||||
private readonly GpuRetirementLedger _retirementLedger;
|
||||
private RetryableResourceReleaseLedger? _disposeResources;
|
||||
private bool _disposed;
|
||||
|
||||
// Per-slot live data (index by slot integer; null entries are unused slots).
|
||||
|
|
@ -55,58 +48,22 @@ public sealed unsafe partial class TerrainModernRenderer : IDisposable
|
|||
// Reverse map: landblockId -> slot, for RemoveLandblock and replacement.
|
||||
private readonly Dictionary<uint, int> _idToSlot = new();
|
||||
|
||||
// GPU buffers.
|
||||
private uint _globalVao;
|
||||
private uint _globalVbo;
|
||||
private uint _globalEbo;
|
||||
// Backing-store capacity bookkeeping, shared with the RHI arm's arena.
|
||||
private long _globalVboCapacityBytes;
|
||||
private long _globalEboCapacityBytes;
|
||||
private uint _indirectBuffer;
|
||||
private int _indirectCapacity;
|
||||
|
||||
private sealed class DynamicIndirectBuffer
|
||||
{
|
||||
public uint Buffer;
|
||||
public int Capacity;
|
||||
}
|
||||
|
||||
private readonly List<DynamicIndirectBuffer>[] _indirectBuffersByFrame =
|
||||
[[], [], []];
|
||||
// Per-GPU-fenced-frame-slot draw bookkeeping, shared with the RHI arm.
|
||||
private int _dynamicFrameSlot;
|
||||
private int _dynamicBufferCursor;
|
||||
private bool _dynamicFrameStarted;
|
||||
|
||||
internal int DynamicIndirectBufferCount =>
|
||||
_indirectBuffersByFrame.Sum(frameBuffers => frameBuffers.Count);
|
||||
|
||||
// Phase U.3: terrain clip UBO (binding=2, terrain_modern.vert TerrainClip).
|
||||
// The shared one is created + uploaded by the GameWindow-level ClipFrame and
|
||||
// handed in via SetClipUbo. When 0, we bind a lazily-created no-clip fallback
|
||||
// (count 0 = ungated) so the shader never reads an unbound UBO at binding=2.
|
||||
private TerrainClipBufferBinding _sharedClipBinding;
|
||||
private uint _fallbackClipUbo;
|
||||
|
||||
// Campaign V slice V2b (2026-07-27): uTerrainHandle/uAlphaHandle (uvec2)
|
||||
// became uTextureIndexA/uTextureIndexB (uint table slots) — cached
|
||||
// uniform locations (matrix uniforms are set by name via Shader.SetMatrix4).
|
||||
private int _uTextureIndexALoc;
|
||||
private int _uTextureIndexBLoc;
|
||||
private bool _textureTilingUploaded;
|
||||
|
||||
// Campaign V slice V6f-2: the 36 per-layer tiling factors used to be a loose
|
||||
// `uniform float uTexTiling[36]`, which Vulkan GLSL cannot declare at all.
|
||||
// They now live in the uniform buffer GpuBindingModel reserved
|
||||
// UniformTerrainTiling (binding 3) for — see terrain_modern.frag for the
|
||||
// std140 packing and why it is vec4[9] rather than float[36].
|
||||
private uint _tilingUbo;
|
||||
|
||||
// Campaign V slice V4t: the interim per-renderer GlBindlessHandleTable is
|
||||
// retired. TerrainAtlas hands out GpuTextureSlots from the device's one
|
||||
// table and this renderer flushes and binds that table at
|
||||
// GpuBindingModel.StorageTextureTable itself, because it still submits
|
||||
// through raw GL and so never reaches GlGpuDevice.FlushBeforeDraw. Null on
|
||||
// a backend with no GL device, where this renderer is never constructed.
|
||||
private readonly GlGpuDevice? _gpuDevice;
|
||||
/// <summary>
|
||||
/// The dynamic per-frame-slot indirect-command buffer pool this used to
|
||||
/// report was raw-GL-only bookkeeping, deleted with that arm at Campaign V
|
||||
/// slice V11. The RHI arm allocates its indirect-command storage from the
|
||||
/// GPU frame's own upload ring instead, so there is no separate pool to
|
||||
/// count.
|
||||
/// </summary>
|
||||
internal int DynamicIndirectBufferCount => 0;
|
||||
|
||||
// Reusable per-frame buffers.
|
||||
private readonly List<int> _visibleSlots = new();
|
||||
|
|
@ -126,183 +83,18 @@ public sealed unsafe partial class TerrainModernRenderer : IDisposable
|
|||
|
||||
public void BeginVisibilityFrame() => _visibleCellIds.Clear();
|
||||
|
||||
internal TerrainModernRenderer(
|
||||
GL gl,
|
||||
BindlessSupport bindless,
|
||||
Shader shader,
|
||||
TerrainAtlas atlas,
|
||||
GlGpuDevice? gpuDevice,
|
||||
int initialSlotCapacity = 64)
|
||||
: this(
|
||||
gl,
|
||||
bindless,
|
||||
shader,
|
||||
atlas,
|
||||
gpuDevice,
|
||||
ImmediateGpuResourceRetirementQueue.Instance,
|
||||
initialSlotCapacity)
|
||||
{
|
||||
}
|
||||
|
||||
internal TerrainModernRenderer(
|
||||
GL gl,
|
||||
BindlessSupport bindless,
|
||||
Shader shader,
|
||||
TerrainAtlas atlas,
|
||||
GlGpuDevice? gpuDevice,
|
||||
IGpuResourceRetirementQueue resourceRetirement,
|
||||
int initialSlotCapacity = 64)
|
||||
{
|
||||
_gl = gl;
|
||||
_bindless = bindless;
|
||||
_shader = shader;
|
||||
_atlas = atlas;
|
||||
_gpuDevice = gpuDevice;
|
||||
ArgumentNullException.ThrowIfNull(resourceRetirement);
|
||||
_retirementLedger = new GpuRetirementLedger(resourceRetirement);
|
||||
_alloc = new GpuRetiredTerrainSlotAllocator(initialSlotCapacity, resourceRetirement);
|
||||
_slots = new SlotData?[initialSlotCapacity];
|
||||
|
||||
_uTextureIndexALoc = _gl.GetUniformLocation(_shader.Program, "uTextureIndexA");
|
||||
_uTextureIndexBLoc = _gl.GetUniformLocation(_shader.Program, "uTextureIndexB");
|
||||
|
||||
var constructionResources = new ResourceCleanupGroup();
|
||||
try
|
||||
{
|
||||
// Campaign V slice V6f-2: the tiling UBO. Fixed size — the table is
|
||||
// 36 immutable floats packed four to a vec4 — so it is allocated
|
||||
// once here and written once on the first bound draw, which is the
|
||||
// same cadence the glUniform1fv it replaces already had.
|
||||
_tilingUbo = TrackedGlResource.CreateBuffer(
|
||||
_gl,
|
||||
"creating terrain tiling UBO");
|
||||
RetryableGpuResourceRelease tilingUboRelease =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
_tilingUbo,
|
||||
TerrainTextureTilingTable.UniformBufferBytes,
|
||||
"rolling back terrain tiling UBO");
|
||||
constructionResources.Add("terrain tiling UBO", tilingUboRelease.Run);
|
||||
TrackedGlResource.AllocateBufferStorage(
|
||||
_gl,
|
||||
BufferTargetARB.UniformBuffer,
|
||||
_tilingUbo,
|
||||
0,
|
||||
TerrainTextureTilingTable.UniformBufferBytes,
|
||||
BufferUsageARB.StaticDraw,
|
||||
"allocating terrain tiling UBO");
|
||||
|
||||
_globalVao = TrackedGlResource.CreateVertexArray(
|
||||
_gl,
|
||||
"creating terrain global VAO");
|
||||
RetryableGpuResourceRelease globalVaoRelease =
|
||||
TrackedGlResource.CreateRetryableVertexArrayDeletion(
|
||||
_gl,
|
||||
_globalVao,
|
||||
"rolling back terrain global VAO");
|
||||
constructionResources.Add(
|
||||
"terrain global VAO",
|
||||
globalVaoRelease.Run);
|
||||
_globalVbo = TrackedGlResource.CreateBuffer(
|
||||
_gl,
|
||||
"creating terrain global vertex buffer");
|
||||
RetryableGpuResourceRelease globalVboRelease =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
_globalVbo,
|
||||
() => _globalVboCapacityBytes,
|
||||
"rolling back terrain global vertex buffer");
|
||||
constructionResources.Add(
|
||||
"terrain global vertex buffer",
|
||||
globalVboRelease.Run);
|
||||
_globalEbo = TrackedGlResource.CreateBuffer(
|
||||
_gl,
|
||||
"creating terrain global index buffer");
|
||||
RetryableGpuResourceRelease globalEboRelease =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
_globalEbo,
|
||||
() => _globalEboCapacityBytes,
|
||||
"rolling back terrain global index buffer");
|
||||
constructionResources.Add(
|
||||
"terrain global index buffer",
|
||||
globalEboRelease.Run);
|
||||
AllocateGpuBuffers(initialSlotCapacity);
|
||||
GlResourceCommand.Execute(
|
||||
_gl,
|
||||
"configure terrain global vertex array",
|
||||
() => ConfigureVao(_globalVao, _globalVbo, _globalEbo));
|
||||
constructionResources.TransferAll();
|
||||
}
|
||||
catch (Exception constructionFailure)
|
||||
{
|
||||
constructionResources.RollbackConstructionAndThrow(
|
||||
"TerrainModernRenderer construction failed and its GL prefix did not cleanly roll back.",
|
||||
constructionFailure);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Resets the indirect-command submission cursor for a GPU-fenced frame
|
||||
/// slot. A retail outside view may draw terrain more than once in a frame;
|
||||
/// each draw receives storage that cannot overwrite an earlier command.
|
||||
/// Resets the per-GPU-fenced-frame-slot draw state. A retail outside view
|
||||
/// may draw terrain more than once in a frame.
|
||||
/// </summary>
|
||||
public void BeginFrame(int frameSlot)
|
||||
{
|
||||
if ((uint)frameSlot >= (uint)_indirectBuffersByFrame.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(frameSlot));
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(frameSlot);
|
||||
_retirementLedger.RetryPendingPublications();
|
||||
_dynamicFrameSlot = frameSlot;
|
||||
_dynamicBufferCursor = 0;
|
||||
_dynamicFrameStarted = true;
|
||||
}
|
||||
|
||||
private void ActivateNextIndirectBuffer()
|
||||
{
|
||||
if (!_dynamicFrameStarted)
|
||||
throw new InvalidOperationException("BeginFrame must be called before drawing terrain.");
|
||||
|
||||
List<DynamicIndirectBuffer> frameBuffers = _indirectBuffersByFrame[_dynamicFrameSlot];
|
||||
if (_dynamicBufferCursor == frameBuffers.Count)
|
||||
{
|
||||
uint buffer = TrackedGlResource.CreateBuffer(
|
||||
_gl!,
|
||||
$"creating terrain indirect buffer for frame slot {_dynamicFrameSlot}");
|
||||
try
|
||||
{
|
||||
frameBuffers.Add(new DynamicIndirectBuffer { Buffer = buffer });
|
||||
}
|
||||
catch
|
||||
{
|
||||
TrackedGlResource.DeleteBuffer(
|
||||
_gl!,
|
||||
buffer,
|
||||
0,
|
||||
"rolling back terrain indirect buffer");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
DynamicIndirectBuffer active = frameBuffers[_dynamicBufferCursor++];
|
||||
_indirectBuffer = active.Buffer;
|
||||
_indirectCapacity = active.Capacity;
|
||||
}
|
||||
|
||||
private void PersistIndirectCapacity()
|
||||
{
|
||||
_indirectBuffersByFrame[_dynamicFrameSlot][_dynamicBufferCursor - 1].Capacity =
|
||||
_indirectCapacity;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Hand the renderer the current aligned terrain-clip range (binding=2).
|
||||
/// Each outside-view slice occupies a distinct range in the current
|
||||
/// GPU-fenced frame's UBO arena.
|
||||
/// </summary>
|
||||
public void SetClipUbo(TerrainClipBufferBinding sharedClipBinding) =>
|
||||
_sharedClipBinding = sharedClipBinding;
|
||||
|
||||
/// <summary>
|
||||
/// Two-tier streaming entry point. Accepts a prebuilt mesh from
|
||||
/// <see cref="LandblockStreamResult.Loaded.MeshData"/> built on the worker
|
||||
|
|
@ -343,59 +135,26 @@ public sealed unsafe partial class TerrainModernRenderer : IDisposable
|
|||
EnsureCapacity(newCap);
|
||||
}
|
||||
|
||||
// Bake worldOrigin into vertex positions; capture min/max Z for AABB.
|
||||
var bakedVerts = new TerrainVertex[VertsPerLandblock];
|
||||
float zMin = float.MaxValue, zMax = float.MinValue;
|
||||
for (int i = 0; i < VertsPerLandblock; i++)
|
||||
{
|
||||
var v = meshData.Vertices[i];
|
||||
var worldPos = v.Position + worldOrigin;
|
||||
bakedVerts[i] = new TerrainVertex(worldPos, v.Normal, v.Data0, v.Data1, v.Data2, v.Data3);
|
||||
if (worldPos.Z < zMin) zMin = worldPos.Z;
|
||||
if (worldPos.Z > zMax) zMax = worldPos.Z;
|
||||
}
|
||||
if (zMin == float.MaxValue) { zMin = 0f; zMax = 0f; }
|
||||
|
||||
// Bake baseVertex into indices on the CPU side (driver-portable pattern).
|
||||
uint baseVertex = (uint)(slot * VertsPerLandblock);
|
||||
var bakedIndices = new uint[IndicesPerLandblock];
|
||||
for (int i = 0; i < IndicesPerLandblock; i++)
|
||||
bakedIndices[i] = meshData.Indices[i] + baseVertex;
|
||||
|
||||
// glBufferSubData into the slot's VBO + EBO regions.
|
||||
nint vboByteOffset = (nint)(slot * VertsPerLandblock * VertexSize);
|
||||
nint eboByteOffset = (nint)(slot * IndicesPerLandblock * IndexSize);
|
||||
|
||||
if (_gl is null)
|
||||
// Bake worldOrigin into vertex positions; capture min/max Z for AABB.
|
||||
var bakedVerts = new TerrainVertex[VertsPerLandblock];
|
||||
float zMin = float.MaxValue, zMax = float.MinValue;
|
||||
for (int i = 0; i < VertsPerLandblock; i++)
|
||||
{
|
||||
UploadRhiLandblock(slot, bakedVerts, bakedIndices);
|
||||
}
|
||||
else
|
||||
{
|
||||
fixed (TerrainVertex* p = bakedVerts)
|
||||
{
|
||||
TrackedGlResource.UpdateBufferSubData(
|
||||
_gl,
|
||||
BufferTargetARB.ArrayBuffer,
|
||||
_globalVbo,
|
||||
vboByteOffset,
|
||||
VertsPerLandblock * VertexSize,
|
||||
p,
|
||||
$"uploading terrain vertices for 0x{landblockId:X8}");
|
||||
var v = meshData.Vertices[i];
|
||||
var worldPos = v.Position + worldOrigin;
|
||||
bakedVerts[i] = new TerrainVertex(worldPos, v.Normal, v.Data0, v.Data1, v.Data2, v.Data3);
|
||||
if (worldPos.Z < zMin) zMin = worldPos.Z;
|
||||
if (worldPos.Z > zMax) zMax = worldPos.Z;
|
||||
}
|
||||
if (zMin == float.MaxValue) { zMin = 0f; zMax = 0f; }
|
||||
|
||||
fixed (uint* p = bakedIndices)
|
||||
{
|
||||
TrackedGlResource.UpdateBufferSubData(
|
||||
_gl,
|
||||
BufferTargetARB.ElementArrayBuffer,
|
||||
_globalEbo,
|
||||
eboByteOffset,
|
||||
IndicesPerLandblock * IndexSize,
|
||||
p,
|
||||
$"uploading terrain indices for 0x{landblockId:X8}");
|
||||
}
|
||||
}
|
||||
// Bake baseVertex into indices on the CPU side (driver-portable pattern).
|
||||
uint baseVertex = (uint)(slot * VertsPerLandblock);
|
||||
var bakedIndices = new uint[IndicesPerLandblock];
|
||||
for (int i = 0; i < IndicesPerLandblock; i++)
|
||||
bakedIndices[i] = meshData.Indices[i] + baseVertex;
|
||||
|
||||
UploadRhiLandblock(slot, bakedVerts, bakedIndices);
|
||||
|
||||
_slots[slot] = new SlotData
|
||||
{
|
||||
|
|
@ -472,128 +231,15 @@ public sealed unsafe partial class TerrainModernRenderer : IDisposable
|
|||
}
|
||||
if (_visibleSlots.Count == 0) return;
|
||||
|
||||
// Campaign V slice V6j: the command array is built the same way on both
|
||||
// arms; only where it lands differs. The RHI arm writes it into a frame
|
||||
// ring slice, which retires the per-frame-slot indirect buffer pool
|
||||
// structurally — every allocation within a frame is already distinct
|
||||
// memory that outlives the draw recorded against it.
|
||||
BuildIndirectCommands();
|
||||
if (_gl is null)
|
||||
{
|
||||
if (!_dynamicFrameStarted)
|
||||
throw new InvalidOperationException("BeginFrame must be called before drawing terrain.");
|
||||
DrawRhi(viewProjection, _visibleSlots.Count);
|
||||
return;
|
||||
}
|
||||
|
||||
ActivateNextIndirectBuffer();
|
||||
|
||||
// Grow indirect buffer if needed.
|
||||
if (_visibleSlots.Count > _indirectCapacity)
|
||||
{
|
||||
int grownCapacity = Math.Max(64, _visibleSlots.Count * 2);
|
||||
TrackedGlResource.AllocateBufferStorage(
|
||||
_gl,
|
||||
GLEnum.DrawIndirectBuffer,
|
||||
_indirectBuffer,
|
||||
checked((long)_indirectCapacity * sizeof(DrawElementsIndirectCommand)),
|
||||
checked((long)grownCapacity * sizeof(DrawElementsIndirectCommand)),
|
||||
GLEnum.DynamicDraw,
|
||||
"growing terrain indirect command buffer");
|
||||
_indirectCapacity = grownCapacity;
|
||||
}
|
||||
|
||||
// Upload DEIC array.
|
||||
fixed (DrawElementsIndirectCommand* p = _deicScratch)
|
||||
{
|
||||
TrackedGlResource.UpdateBufferSubData(
|
||||
_gl,
|
||||
GLEnum.DrawIndirectBuffer,
|
||||
_indirectBuffer,
|
||||
0,
|
||||
checked((long)_visibleSlots.Count * sizeof(DrawElementsIndirectCommand)),
|
||||
p,
|
||||
"uploading terrain indirect commands");
|
||||
}
|
||||
PersistIndirectCapacity();
|
||||
|
||||
// Bind shader + uniforms + atlas handles.
|
||||
// Verified Phase W Stage 4 (T4.2): terrain projects from the camera view-proj;
|
||||
// no separate landscape viewpoint to sync. uViewProjection derives from
|
||||
// the ICamera passed into this method — the same camera used for all other
|
||||
// renderers in the unified pipeline. Retail's LScape::update_viewpoint
|
||||
// pre-positions terrain to the outdoor landcell, but acdream uses the
|
||||
// unified camera matrix everywhere, so no separate viewpoint divergence can occur.
|
||||
_shader!.Use();
|
||||
UploadTextureTilingOnce();
|
||||
// Campaign V slice V6f-2: bind the tiling UBO every draw, not once. GL's
|
||||
// uniform-buffer binding points are global and shared with the sky's
|
||||
// params block and the SceneLighting block, so a renderer that runs
|
||||
// between two terrain draws can take binding 3 out from under us.
|
||||
// Self-contained state, per feedback_render_self_contained_gl_state.
|
||||
_gl.BindBufferBase(
|
||||
BufferTargetARB.UniformBuffer,
|
||||
Gpu.GpuBindingModel.UniformTerrainTiling,
|
||||
_tilingUbo);
|
||||
// Campaign V slice V6f-1: one uViewProjection, matching the field
|
||||
// GpuPushConstants already carries, instead of the separate uView and
|
||||
// uProjection the shader used to combine per vertex. viewProjection is
|
||||
// the same product the visibility pass above already computed.
|
||||
_shader.SetMatrix4("uViewProjection", viewProjection);
|
||||
|
||||
// Campaign V slice V2b: pass each texture's binding=9 table slot
|
||||
// instead of the raw uvec2 handle. GLSL reconstructs
|
||||
// sampler2DArray(ACDREAM_TEXTURE_HANDLE(uTextureIndexA)) at the use
|
||||
// site — see terrain_modern.frag. Slice V4t: the slots come from the
|
||||
// device's one table rather than a table private to this renderer.
|
||||
(GpuTextureSlot terrainSlot, GpuTextureSlot alphaSlot) =
|
||||
_atlas.GetTextureSlots(GpuDevice);
|
||||
FlushAndBindTextureTable();
|
||||
_gl.ProgramUniform1(_shader.Program, _uTextureIndexALoc, terrainSlot.Index);
|
||||
_gl.ProgramUniform1(_shader.Program, _uTextureIndexBLoc, alphaSlot.Index);
|
||||
|
||||
// Phase U.3: bind the terrain clip UBO (binding=2). Shared ClipFrame UBO
|
||||
// when wired, else the no-clip fallback (count 0 = ungated terrain).
|
||||
BindClipUboBinding2();
|
||||
|
||||
// #108-residual: retail terrain is SINGLE-SIDED — ACRender::landPolysDraw
|
||||
// (0x006b7040) draws each land triangle ONLY when the camera is on the
|
||||
// POSITIVE (upper) side of its plane (Plane::which_side2 vs
|
||||
// Render::FrameCurrent, zFightTerrainAdjust bias). GL backface culling
|
||||
// evaluates the same per-triangle eye-side predicate at rasterization.
|
||||
// LandblockMesh emits every triangle CCW in world XY seen from above
|
||||
// (LandblockMeshTests winding pin), which the unified camera chain
|
||||
// (CreateLookAt up=+Z + Numerics perspective) maps to CCW window
|
||||
// winding from above / CW from below (TerrainCullOrientationTests) —
|
||||
// so FrontFace(Ccw)+Cull(Back) keeps the top side and culls the
|
||||
// underside. WB drew the whole world with culling DISABLED
|
||||
// frame-globally (WB GameScene.cs:841 — an editor camera goes
|
||||
// underground); inheriting that drew terrain DOUBLE-SIDED, and a
|
||||
// below-grade eye (cellar ascent) saw the UNDERSIDE of the grade
|
||||
// sheet through the exit-door aperture — the #108 grass window.
|
||||
// Self-contained state per feedback_render_self_contained_gl_state;
|
||||
// the frame-global CW + cull-off baseline is restored after the draw.
|
||||
_gl.Enable(EnableCap.CullFace);
|
||||
_gl.CullFace(TriangleFace.Back);
|
||||
_gl.FrontFace(FrontFaceDirection.Ccw);
|
||||
|
||||
_gl.BindVertexArray(_globalVao);
|
||||
_gl.MemoryBarrier(MemoryBarrierMask.CommandBarrierBit);
|
||||
_gl.MultiDrawElementsIndirect(
|
||||
PrimitiveType.Triangles, DrawElementsType.UnsignedInt,
|
||||
(void*)0,
|
||||
(uint)_visibleSlots.Count,
|
||||
(uint)sizeof(DrawElementsIndirectCommand));
|
||||
_gl.BindVertexArray(0);
|
||||
_gl.BindBuffer(GLEnum.DrawIndirectBuffer, 0);
|
||||
|
||||
_gl.FrontFace(FrontFaceDirection.CW);
|
||||
_gl.Disable(EnableCap.CullFace);
|
||||
if (!_dynamicFrameStarted)
|
||||
throw new InvalidOperationException("BeginFrame must be called before drawing terrain.");
|
||||
DrawRhi(viewProjection, _visibleSlots.Count);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds this frame's <c>DrawElementsIndirectCommand</c> array from the
|
||||
/// visible slot list. Pure CPU, identical on both arms.
|
||||
/// visible slot list. Pure CPU.
|
||||
/// </summary>
|
||||
private void BuildIndirectCommands()
|
||||
{
|
||||
|
|
@ -618,298 +264,13 @@ public sealed unsafe partial class TerrainModernRenderer : IDisposable
|
|||
if (_disposed)
|
||||
return;
|
||||
_retirementLedger.RetryPendingPublications();
|
||||
if (_gl is null)
|
||||
{
|
||||
DisposeRhi();
|
||||
return;
|
||||
}
|
||||
|
||||
if (_disposeResources is null)
|
||||
{
|
||||
var releases = new List<(string Name, Action Release)>();
|
||||
if (_globalVao != 0)
|
||||
{
|
||||
RetryableGpuResourceRelease release =
|
||||
TrackedGlResource.CreateRetryableVertexArrayDeletion(
|
||||
_gl,
|
||||
_globalVao,
|
||||
"deleting terrain global VAO");
|
||||
releases.Add(("global-vao", release.Run));
|
||||
}
|
||||
if (_globalVbo != 0)
|
||||
{
|
||||
RetryableGpuResourceRelease release =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
_globalVbo,
|
||||
_globalVboCapacityBytes,
|
||||
"deleting terrain global vertex buffer");
|
||||
releases.Add(("global-vbo", release.Run));
|
||||
}
|
||||
if (_globalEbo != 0)
|
||||
{
|
||||
RetryableGpuResourceRelease release =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
_globalEbo,
|
||||
_globalEboCapacityBytes,
|
||||
"deleting terrain global index buffer");
|
||||
releases.Add(("global-ebo", release.Run));
|
||||
}
|
||||
for (int frame = 0; frame < _indirectBuffersByFrame.Length; frame++)
|
||||
{
|
||||
List<DynamicIndirectBuffer> frameBuffers = _indirectBuffersByFrame[frame];
|
||||
for (int index = 0; index < frameBuffers.Count; index++)
|
||||
{
|
||||
DynamicIndirectBuffer buffer = frameBuffers[index];
|
||||
RetryableGpuResourceRelease release =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
buffer.Buffer,
|
||||
checked((long)buffer.Capacity * sizeof(DrawElementsIndirectCommand)),
|
||||
"deleting terrain indirect command buffer");
|
||||
releases.Add(($"indirect-{frame}-{index}", release.Run));
|
||||
}
|
||||
}
|
||||
if (_fallbackClipUbo != 0)
|
||||
{
|
||||
RetryableGpuResourceRelease release =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
_fallbackClipUbo,
|
||||
ClipFrame.TerrainUboBytes,
|
||||
"deleting terrain fallback clip UBO");
|
||||
releases.Add(("fallback-clip-ubo", release.Run));
|
||||
}
|
||||
if (_tilingUbo != 0)
|
||||
{
|
||||
RetryableGpuResourceRelease release =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
_tilingUbo,
|
||||
TerrainTextureTilingTable.UniformBufferBytes,
|
||||
"deleting terrain tiling UBO");
|
||||
releases.Add(("tiling-ubo", release.Run));
|
||||
}
|
||||
_disposeResources = new RetryableResourceReleaseLedger(releases);
|
||||
}
|
||||
|
||||
ResourceReleaseAttempt attempt = _disposeResources.Advance();
|
||||
if (!_disposeResources.IsComplete)
|
||||
{
|
||||
throw attempt.ToException(
|
||||
"One or more terrain GPU resources could not be released.");
|
||||
}
|
||||
|
||||
_globalVao = 0;
|
||||
_globalVbo = 0;
|
||||
_globalEbo = 0;
|
||||
_globalVboCapacityBytes = 0;
|
||||
_globalEboCapacityBytes = 0;
|
||||
foreach (List<DynamicIndirectBuffer> frameBuffers in _indirectBuffersByFrame)
|
||||
frameBuffers.Clear();
|
||||
_indirectBuffer = 0;
|
||||
_indirectCapacity = 0;
|
||||
_dynamicFrameStarted = false;
|
||||
_fallbackClipUbo = 0;
|
||||
_disposeResources = null;
|
||||
_disposed = true;
|
||||
|
||||
if (attempt.HasFailures)
|
||||
{
|
||||
throw attempt.ToException(
|
||||
"Terrain GPU resources released with exceptional committed outcomes.");
|
||||
}
|
||||
DisposeRhi();
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Private helpers
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
/// <summary>
|
||||
/// Upload the texture-array adapter for retail's per-surface repeat count.
|
||||
/// Retail passes <c>TerrainTex::tex_tiling</c> directly to
|
||||
/// <c>ImgTex::TileCSI</c> / <c>ImgTex::MergeTexture</c>
|
||||
/// (`TexMerge::CopyAndTile` 0x00503580, `TexMerge::Merge` 0x005038C0).
|
||||
/// Uniform values persist for the lifetime of this linked shader program,
|
||||
/// so the immutable atlas table is uploaded on its first bound draw.
|
||||
/// </summary>
|
||||
private void UploadTextureTilingOnce()
|
||||
{
|
||||
if (_textureTilingUploaded)
|
||||
return;
|
||||
|
||||
if (_atlas.TilingByLayer.Count != TerrainTextureTilingTable.LayerCapacity)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Terrain tiling table has {_atlas.TilingByLayer.Count} entries; " +
|
||||
$"expected {TerrainTextureTilingTable.LayerCapacity}.");
|
||||
}
|
||||
|
||||
// Campaign V slice V6f-2: one whole-buffer write into the binding=3
|
||||
// uniform buffer, replacing the glUniform1fv into the loose array. The
|
||||
// block is std140, so each value sits at a 16-byte stride with three
|
||||
// dead words after it; the span is cleared first so those words are
|
||||
// zero rather than whatever the stack held.
|
||||
Span<byte> block = stackalloc byte[TerrainTextureTilingTable.UniformBufferBytes];
|
||||
block.Clear();
|
||||
for (int i = 0; i < TerrainTextureTilingTable.LayerCapacity; i++)
|
||||
{
|
||||
BitConverter.TryWriteBytes(
|
||||
block[(i * TerrainTextureTilingTable.UniformElementStrideBytes)..],
|
||||
_atlas.TilingByLayer[i]);
|
||||
}
|
||||
|
||||
fixed (byte* p = block)
|
||||
{
|
||||
TrackedGlResource.UpdateBufferSubData(
|
||||
_gl!,
|
||||
BufferTargetARB.UniformBuffer,
|
||||
_tilingUbo,
|
||||
0,
|
||||
TerrainTextureTilingTable.UniformBufferBytes,
|
||||
p,
|
||||
"uploading terrain tiling UBO");
|
||||
}
|
||||
|
||||
_textureTilingUploaded = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The GL device whose texture table this renderer samples through.
|
||||
/// Campaign V slice V4t: a terrain renderer without one could not resolve a
|
||||
/// single texture, so the failure names the composition that built it rather
|
||||
/// than dereferencing null mid-draw.
|
||||
/// </summary>
|
||||
private GlGpuDevice GpuDevice => _gpuDevice ?? throw new InvalidOperationException(
|
||||
"TerrainModernRenderer was constructed without a GL GPU device: its texture " +
|
||||
"slots come from that device's table (Campaign V slice V4t).");
|
||||
|
||||
/// <summary>
|
||||
/// Campaign V slice V4t: drains the device texture table's dirty runs and
|
||||
/// (re)binds it at <see cref="GpuBindingModel.StorageTextureTable"/>.
|
||||
/// Terrain registers at most two slots (the terrain and alpha atlases), so
|
||||
/// the table is dirty only on the atlas's first draw — but the bind is
|
||||
/// unconditional, because GL storage-buffer binding points are global and
|
||||
/// another renderer's binding 9 sits there between two terrain draws.
|
||||
/// Deleted with the raw-GL world path when the Vulkan world arm lands and
|
||||
/// this renderer's draws go through the encoder, which binds the same table
|
||||
/// on every pipeline bind.
|
||||
/// </summary>
|
||||
private void FlushAndBindTextureTable()
|
||||
{
|
||||
GlGpuDevice device = GpuDevice;
|
||||
device.FlushTextureTable();
|
||||
_gl!.BindBufferBase(
|
||||
GLEnum.ShaderStorageBuffer,
|
||||
GpuBindingModel.StorageTextureTable,
|
||||
device.TextureTableGlName);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Phase U.3: bind the terrain clip UBO to binding=2. Prefers the shared
|
||||
/// <see cref="ClipFrame"/> UBO range (<see cref="SetClipUbo"/>); otherwise lazily
|
||||
/// creates + binds a no-clip fallback (count 0 = ungated) so the shader never
|
||||
/// reads an unbound UBO. The fallback is std140-sized to
|
||||
/// <see cref="ClipFrame.TerrainUboBytes"/> and zero-filled (count 0).
|
||||
/// </summary>
|
||||
private void BindClipUboBinding2()
|
||||
{
|
||||
if (_sharedClipBinding.IsValid)
|
||||
{
|
||||
_sharedClipBinding.Bind(_gl!);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_fallbackClipUbo == 0)
|
||||
{
|
||||
var zero = stackalloc byte[ClipFrame.TerrainUboBytes];
|
||||
for (int i = 0; i < ClipFrame.TerrainUboBytes; i++) zero[i] = 0;
|
||||
uint fallback = TrackedGlResource.CreateBuffer(
|
||||
_gl!,
|
||||
"creating terrain fallback clip UBO");
|
||||
try
|
||||
{
|
||||
TrackedGlResource.AllocateBufferStorage(
|
||||
_gl!,
|
||||
BufferTargetARB.UniformBuffer,
|
||||
fallback,
|
||||
0,
|
||||
ClipFrame.TerrainUboBytes,
|
||||
BufferUsageARB.DynamicDraw,
|
||||
zero,
|
||||
"allocating terrain fallback clip UBO");
|
||||
_fallbackClipUbo = fallback;
|
||||
}
|
||||
catch
|
||||
{
|
||||
TrackedGlResource.DeleteBuffer(
|
||||
_gl!,
|
||||
fallback,
|
||||
0,
|
||||
"rolling back terrain fallback clip UBO");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
_gl!.BindBufferBase(BufferTargetARB.UniformBuffer,
|
||||
ClipFrame.TerrainClipUboBinding, _fallbackClipUbo);
|
||||
}
|
||||
|
||||
private void AllocateGpuBuffers(int capacitySlots)
|
||||
{
|
||||
long vboBytes = checked((long)capacitySlots * VertsPerLandblock * VertexSize);
|
||||
long eboBytes = checked((long)capacitySlots * IndicesPerLandblock * IndexSize);
|
||||
|
||||
TrackedGlResource.AllocateBufferStorage(
|
||||
_gl!,
|
||||
BufferTargetARB.ArrayBuffer,
|
||||
_globalVbo,
|
||||
_globalVboCapacityBytes,
|
||||
vboBytes,
|
||||
BufferUsageARB.DynamicDraw,
|
||||
"allocating terrain global vertex storage");
|
||||
_globalVboCapacityBytes = vboBytes;
|
||||
|
||||
TrackedGlResource.AllocateBufferStorage(
|
||||
_gl!,
|
||||
BufferTargetARB.ElementArrayBuffer,
|
||||
_globalEbo,
|
||||
_globalEboCapacityBytes,
|
||||
eboBytes,
|
||||
BufferUsageARB.DynamicDraw,
|
||||
"allocating terrain global index storage");
|
||||
_globalEboCapacityBytes = eboBytes;
|
||||
}
|
||||
|
||||
private void ConfigureVao(uint vao, uint vbo, uint ebo)
|
||||
{
|
||||
_gl!.BindVertexArray(vao);
|
||||
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo);
|
||||
_gl.BindBuffer(BufferTargetARB.ElementArrayBuffer, ebo);
|
||||
|
||||
uint stride = (uint)VertexSize;
|
||||
|
||||
// location 0: Position
|
||||
_gl.EnableVertexAttribArray(0);
|
||||
_gl.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, stride, (void*)0);
|
||||
// location 1: Normal
|
||||
_gl.EnableVertexAttribArray(1);
|
||||
_gl.VertexAttribPointer(1, 3, VertexAttribPointerType.Float, false, stride, (void*)(3 * sizeof(float)));
|
||||
// locations 2-5: Data0..Data3 (uvec4 byte attributes)
|
||||
nint dataOffset = 6 * sizeof(float);
|
||||
_gl.EnableVertexAttribArray(2);
|
||||
_gl.VertexAttribIPointer(2, 4, VertexAttribIType.UnsignedByte, stride, (void*)dataOffset);
|
||||
_gl.EnableVertexAttribArray(3);
|
||||
_gl.VertexAttribIPointer(3, 4, VertexAttribIType.UnsignedByte, stride, (void*)(dataOffset + 4));
|
||||
_gl.EnableVertexAttribArray(4);
|
||||
_gl.VertexAttribIPointer(4, 4, VertexAttribIType.UnsignedByte, stride, (void*)(dataOffset + 8));
|
||||
_gl.EnableVertexAttribArray(5);
|
||||
_gl.VertexAttribIPointer(5, 4, VertexAttribIType.UnsignedByte, stride, (void*)(dataOffset + 12));
|
||||
|
||||
_gl.BindVertexArray(0);
|
||||
GLHelpers.ThrowOnResourceError(_gl, "configuring terrain VAO");
|
||||
}
|
||||
|
||||
internal static void CollectVisibleCells(
|
||||
HashSet<uint> destination,
|
||||
uint landblockId,
|
||||
|
|
@ -1038,132 +399,7 @@ public sealed unsafe partial class TerrainModernRenderer : IDisposable
|
|||
{
|
||||
if (newCapacity <= _alloc.Capacity)
|
||||
return;
|
||||
if (_gl is null)
|
||||
{
|
||||
EnsureRhiCapacity(newCapacity);
|
||||
return;
|
||||
}
|
||||
|
||||
var grownSlots = new SlotData?[newCapacity];
|
||||
Array.Copy(_slots, grownSlots, _slots.Length);
|
||||
|
||||
long newVboBytes = checked((long)newCapacity * VertsPerLandblock * VertexSize);
|
||||
long newEboBytes = checked((long)newCapacity * IndicesPerLandblock * IndexSize);
|
||||
uint newVbo = 0;
|
||||
uint newEbo = 0;
|
||||
uint newVao = 0;
|
||||
long allocatedNewVboBytes = 0;
|
||||
long allocatedNewEboBytes = 0;
|
||||
bool published = false;
|
||||
try
|
||||
{
|
||||
newVbo = TrackedGlResource.CreateBuffer(
|
||||
_gl,
|
||||
"creating grown terrain vertex buffer");
|
||||
TrackedGlResource.AllocateBufferStorage(
|
||||
_gl,
|
||||
BufferTargetARB.ArrayBuffer,
|
||||
newVbo,
|
||||
0,
|
||||
newVboBytes,
|
||||
BufferUsageARB.DynamicDraw,
|
||||
"allocating grown terrain vertex buffer");
|
||||
allocatedNewVboBytes = newVboBytes;
|
||||
|
||||
newEbo = TrackedGlResource.CreateBuffer(
|
||||
_gl,
|
||||
"creating grown terrain index buffer");
|
||||
TrackedGlResource.AllocateBufferStorage(
|
||||
_gl,
|
||||
BufferTargetARB.ElementArrayBuffer,
|
||||
newEbo,
|
||||
0,
|
||||
newEboBytes,
|
||||
BufferUsageARB.DynamicDraw,
|
||||
"allocating grown terrain index buffer");
|
||||
allocatedNewEboBytes = newEboBytes;
|
||||
|
||||
GLHelpers.ThrowOnResourceError(_gl, "copying terrain buffers (precondition)");
|
||||
_gl.BindBuffer(BufferTargetARB.CopyReadBuffer, _globalVbo);
|
||||
_gl.BindBuffer(BufferTargetARB.CopyWriteBuffer, newVbo);
|
||||
_gl.CopyBufferSubData(
|
||||
CopyBufferSubDataTarget.CopyReadBuffer,
|
||||
CopyBufferSubDataTarget.CopyWriteBuffer,
|
||||
0,
|
||||
0,
|
||||
checked((nuint)_globalVboCapacityBytes));
|
||||
_gl.BindBuffer(BufferTargetARB.CopyReadBuffer, _globalEbo);
|
||||
_gl.BindBuffer(BufferTargetARB.CopyWriteBuffer, newEbo);
|
||||
_gl.CopyBufferSubData(
|
||||
CopyBufferSubDataTarget.CopyReadBuffer,
|
||||
CopyBufferSubDataTarget.CopyWriteBuffer,
|
||||
0,
|
||||
0,
|
||||
checked((nuint)_globalEboCapacityBytes));
|
||||
GLHelpers.ThrowOnResourceError(_gl, "copying terrain buffers");
|
||||
|
||||
newVao = TrackedGlResource.CreateVertexArray(
|
||||
_gl,
|
||||
"creating grown terrain VAO");
|
||||
ConfigureVao(newVao, newVbo, newEbo);
|
||||
|
||||
uint oldVao = _globalVao;
|
||||
uint oldVbo = _globalVbo;
|
||||
uint oldEbo = _globalEbo;
|
||||
long oldVboBytes = _globalVboCapacityBytes;
|
||||
long oldEboBytes = _globalEboCapacityBytes;
|
||||
|
||||
_globalVao = newVao;
|
||||
_globalVbo = newVbo;
|
||||
_globalEbo = newEbo;
|
||||
_globalVboCapacityBytes = newVboBytes;
|
||||
_globalEboCapacityBytes = newEboBytes;
|
||||
_slots = grownSlots;
|
||||
_alloc.GrowTo(newCapacity);
|
||||
published = true;
|
||||
|
||||
// Older submitted draws captured the former VAO/buffer bindings.
|
||||
// Retire the complete old set only after the replacement is valid.
|
||||
RetryableGpuResourceRelease oldVaoRelease =
|
||||
TrackedGlResource.CreateRetryableVertexArrayDeletion(
|
||||
_gl,
|
||||
oldVao,
|
||||
"retiring terrain VAO after growth");
|
||||
RetryableGpuResourceRelease oldVboRelease =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
oldVbo,
|
||||
oldVboBytes,
|
||||
"retiring terrain vertex buffer after growth");
|
||||
RetryableGpuResourceRelease oldEboRelease =
|
||||
TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
oldEbo,
|
||||
oldEboBytes,
|
||||
"retiring terrain index buffer after growth");
|
||||
_retirementLedger.RetireMany(
|
||||
[oldVaoRelease, oldVboRelease, oldEboRelease]);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!published)
|
||||
{
|
||||
TrackedGlResource.DeleteVertexArray(
|
||||
_gl,
|
||||
newVao,
|
||||
"rolling back grown terrain VAO");
|
||||
TrackedGlResource.DeleteBuffer(
|
||||
_gl,
|
||||
newVbo,
|
||||
allocatedNewVboBytes,
|
||||
"rolling back grown terrain vertex buffer");
|
||||
TrackedGlResource.DeleteBuffer(
|
||||
_gl,
|
||||
newEbo,
|
||||
allocatedNewEboBytes,
|
||||
"rolling back grown terrain index buffer");
|
||||
}
|
||||
}
|
||||
EnsureRhiCapacity(newCapacity);
|
||||
}
|
||||
|
||||
private sealed class SlotData
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue