perf(render): bound animation and alpha scratch residency
Complete Slice D3 by replacing the unbounded animation dictionary with a concurrent byte/count LRU and by putting the three retail alpha scratch owners behind one typed aggregate budget. Preserve immediate growth and draw order while reclaiming one-frame density spikes after sustained under-use. Close stale bounds-cache issue evidence without inventing a cache.
This commit is contained in:
parent
3e18fc2730
commit
f2644d42c2
18 changed files with 857 additions and 34 deletions
|
|
@ -1,7 +1,9 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using AcDream.App.Rendering.Residency;
|
||||
using AcDream.Core.Lighting;
|
||||
using AcDream.Core.Meshing;
|
||||
using AcDream.Core.Rendering;
|
||||
|
|
@ -91,6 +93,8 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
private readonly IRetailSelectionLightingSource? _selectionLighting;
|
||||
private readonly RetailAlphaQueue? _alphaQueue;
|
||||
private readonly AlphaDrawSource _alphaSource;
|
||||
private readonly RetainedScratchCapacityPolicy _alphaScratchPolicy;
|
||||
private int _scratchPeakUnits;
|
||||
|
||||
private readonly BindlessSupport _bindless;
|
||||
|
||||
|
|
@ -596,7 +600,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
=> owner.DrawPreparedAlphaBatch(firstPreparedDraw, drawCount);
|
||||
|
||||
public void ResetAlphaSubmissions()
|
||||
=> owner._deferredAlpha.Clear();
|
||||
=> owner.ResetDeferredAlphaSubmissions();
|
||||
}
|
||||
|
||||
// Per-frame scratch — reused across frames to avoid per-frame allocation.
|
||||
|
|
@ -606,6 +610,25 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
private readonly List<DeferredAlphaInstance> _deferredAlpha = new(128);
|
||||
private TranslucencyKind[] _deferredAlphaKinds = new TranslucencyKind[128];
|
||||
private Matrix4x4 _deferredAlphaViewProjection;
|
||||
|
||||
internal long AlphaScratchBudgetBytes => _alphaScratchPolicy.BudgetBytes;
|
||||
internal long RetainedAlphaScratchBytes => checked(
|
||||
(long)_instanceData.Length * sizeof(float)
|
||||
+ (long)_clipSlotData.Length * sizeof(uint)
|
||||
+ (long)_lightSetData.Length * sizeof(int)
|
||||
+ (long)_indoorData.Length * sizeof(uint)
|
||||
+ (long)_alphaData.Length * sizeof(float)
|
||||
+ (long)_selectionLightingData.Length * Unsafe.SizeOf<Vector2>()
|
||||
+ (long)_batchData.Length * Unsafe.SizeOf<BatchData>()
|
||||
+ (long)_indirectCommands.Length
|
||||
* Unsafe.SizeOf<DrawElementsIndirectCommand>()
|
||||
+ (long)_drawCullModes.Length * Unsafe.SizeOf<CullMode>()
|
||||
+ (long)_batchPublicScratch.Length
|
||||
* Unsafe.SizeOf<BatchDataPublic>()
|
||||
+ (long)_deferredAlphaKinds.Length
|
||||
* Unsafe.SizeOf<TranslucencyKind>()
|
||||
+ (long)_deferredAlpha.Capacity
|
||||
* Unsafe.SizeOf<DeferredAlphaInstance>());
|
||||
// A.5 T26 follow-up (Bug B): WalkEntities populates this scratch list
|
||||
// instead of allocating a fresh List<(WorldEntity, int)> per frame. At
|
||||
// ~10K entities × ~3 mesh refs = ~30K tuples × 16 bytes = ~480 KB / frame
|
||||
|
|
@ -729,7 +752,8 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
EntityClassificationCache classificationCache,
|
||||
AcDream.Core.Rendering.TranslucencyFadeManager translucencyFades,
|
||||
IRetailSelectionRenderSink? selectionSink = null,
|
||||
RetailAlphaQueue? alphaQueue = null)
|
||||
RetailAlphaQueue? alphaQueue = null,
|
||||
long? alphaScratchBudgetBytes = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(gl);
|
||||
ArgumentNullException.ThrowIfNull(shader);
|
||||
|
|
@ -750,6 +774,11 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
_selectionLighting = selectionSink as IRetailSelectionLightingSource;
|
||||
_alphaQueue = alphaQueue;
|
||||
_alphaSource = new AlphaDrawSource(this);
|
||||
long scratchBudget = alphaScratchBudgetBytes
|
||||
?? AlphaScratchBudgetProfile.Create(
|
||||
ResidencyBudgetOptions.Default.AlphaScratchBytes).DispatcherBytes;
|
||||
_alphaScratchPolicy =
|
||||
new RetainedScratchCapacityPolicy(scratchBudget);
|
||||
_bindless = bindless ?? throw new ArgumentNullException(nameof(bindless));
|
||||
}
|
||||
|
||||
|
|
@ -766,6 +795,8 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
if (_groupFrame == long.MaxValue)
|
||||
throw new InvalidOperationException("Instance-group frame identity was exhausted.");
|
||||
|
||||
ApplyScratchRetention(_scratchPeakUnits);
|
||||
_scratchPeakUnits = 0;
|
||||
_groupFrame++;
|
||||
PruneInstanceGroupsUnusedBeforeFrame(
|
||||
_groups,
|
||||
|
|
@ -1965,6 +1996,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
// fill via BuildIndirectArrays ──────────────────────────────────
|
||||
int immediateTransparentCount = deferTransparent ? 0 : _translucentDraws.Count;
|
||||
int totalDraws = _opaqueDraws.Count + immediateTransparentCount;
|
||||
TrackScratchDemand(Math.Max(totalInstances, totalDraws));
|
||||
if (_batchData.Length < totalDraws)
|
||||
_batchData = new BatchData[totalDraws + 64];
|
||||
if (_indirectCommands.Length < totalDraws)
|
||||
|
|
@ -2493,6 +2525,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
|
||||
private void EnsureDeferredAlphaCapacity(int count)
|
||||
{
|
||||
TrackScratchDemand(count);
|
||||
int neededMatrixFloats = count * 16;
|
||||
if (_instanceData.Length < neededMatrixFloats)
|
||||
_instanceData = new float[neededMatrixFloats + 256 * 16];
|
||||
|
|
@ -2516,6 +2549,63 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
_deferredAlphaKinds = new TranslucencyKind[count + 64];
|
||||
}
|
||||
|
||||
private void ResetDeferredAlphaSubmissions()
|
||||
{
|
||||
_deferredAlpha.Clear();
|
||||
}
|
||||
|
||||
private void TrackScratchDemand(int units)
|
||||
{
|
||||
if (units > _scratchPeakUnits)
|
||||
_scratchPeakUnits = units;
|
||||
}
|
||||
|
||||
private void ApplyScratchRetention(int observedUnits)
|
||||
{
|
||||
int currentCapacity = Math.Max(
|
||||
_instanceData.Length / 16,
|
||||
Math.Max(
|
||||
_lightSetData.Length / LightManager.MaxLightsPerObject,
|
||||
Math.Max(
|
||||
_deferredAlpha.Capacity,
|
||||
Math.Max(_batchData.Length, _indirectCommands.Length))));
|
||||
int bytesPerUnit = checked(
|
||||
16 * sizeof(float)
|
||||
+ sizeof(uint)
|
||||
+ LightManager.MaxLightsPerObject * sizeof(int)
|
||||
+ sizeof(uint)
|
||||
+ sizeof(float)
|
||||
+ Unsafe.SizeOf<Vector2>()
|
||||
+ Unsafe.SizeOf<BatchData>()
|
||||
+ Unsafe.SizeOf<DrawElementsIndirectCommand>()
|
||||
+ Unsafe.SizeOf<CullMode>()
|
||||
+ Unsafe.SizeOf<BatchDataPublic>()
|
||||
+ Unsafe.SizeOf<TranslucencyKind>()
|
||||
+ Unsafe.SizeOf<DeferredAlphaInstance>());
|
||||
int targetCapacity = _alphaScratchPolicy.ObserveAndSelectCapacity(
|
||||
currentCapacity,
|
||||
observedUnits,
|
||||
bytesPerUnit,
|
||||
minimumCapacity: 256,
|
||||
growthQuantum: 256);
|
||||
if (targetCapacity >= currentCapacity)
|
||||
return;
|
||||
|
||||
_instanceData = new float[checked(targetCapacity * 16)];
|
||||
_clipSlotData = new uint[targetCapacity];
|
||||
_lightSetData = new int[
|
||||
checked(targetCapacity * LightManager.MaxLightsPerObject)];
|
||||
_indoorData = new uint[targetCapacity];
|
||||
_alphaData = new float[targetCapacity];
|
||||
_selectionLightingData = new Vector2[targetCapacity];
|
||||
_batchData = new BatchData[targetCapacity];
|
||||
_indirectCommands = new DrawElementsIndirectCommand[targetCapacity];
|
||||
_drawCullModes = new CullMode[targetCapacity];
|
||||
_batchPublicScratch = new BatchDataPublic[targetCapacity];
|
||||
_deferredAlphaKinds = new TranslucencyKind[targetCapacity];
|
||||
_deferredAlpha.Capacity = targetCapacity;
|
||||
}
|
||||
|
||||
private void UploadDeferredAlphaBuffers(int count)
|
||||
{
|
||||
fixed (float* p = _instanceData)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue