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
|
|
@ -2,7 +2,9 @@ using System;
|
|||
using System.Collections.Generic;
|
||||
using System.Numerics;
|
||||
using System.Runtime.ExceptionServices;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using AcDream.App.Rendering.Residency;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
|
|
@ -68,15 +70,42 @@ internal interface IWorldSceneAlphaFrame
|
|||
|
||||
internal sealed class RetailAlphaQueue : IWorldSceneAlphaFrame
|
||||
{
|
||||
private const int MinimumSubmissionCapacity = 256;
|
||||
private const int SubmissionGrowthQuantum = 256;
|
||||
private const int MinimumSourceCapacity = 4;
|
||||
private readonly List<RetailAlphaSubmission> _submissions = new(256);
|
||||
private readonly List<IRetailAlphaDrawSource> _sources = new(4);
|
||||
private int[] _tokenScratch = new int[256];
|
||||
private int[] _sourceDrawOffsets = new int[4];
|
||||
private RetailAlphaSubmission[] _sortScratch = new RetailAlphaSubmission[256];
|
||||
private readonly int[] _radixOffsets = new int[256];
|
||||
private readonly RetainedScratchCapacityPolicy _scratchPolicy;
|
||||
private readonly long _scratchBudgetBytes;
|
||||
|
||||
internal RetailAlphaQueue(long? scratchBudgetBytes = null)
|
||||
{
|
||||
long budget = scratchBudgetBytes
|
||||
?? AlphaScratchBudgetProfile.Create(
|
||||
ResidencyBudgetOptions.Default.AlphaScratchBytes).QueueBytes;
|
||||
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(budget);
|
||||
_scratchBudgetBytes = budget;
|
||||
_scratchPolicy = new RetainedScratchCapacityPolicy(Math.Max(
|
||||
1,
|
||||
budget - (long)_radixOffsets.Length * sizeof(int)));
|
||||
}
|
||||
|
||||
public bool IsCollecting { get; private set; }
|
||||
internal int PendingCount => _submissions.Count;
|
||||
internal long ScratchBudgetBytes => _scratchBudgetBytes;
|
||||
internal long RetainedScratchBytes => checked(
|
||||
(long)_submissions.Capacity
|
||||
* Unsafe.SizeOf<RetailAlphaSubmission>()
|
||||
+ (long)_sortScratch.Length
|
||||
* Unsafe.SizeOf<RetailAlphaSubmission>()
|
||||
+ (long)_tokenScratch.Length * sizeof(int)
|
||||
+ (long)_sources.Capacity * IntPtr.Size
|
||||
+ (long)_sourceDrawOffsets.Length * sizeof(int)
|
||||
+ (long)_radixOffsets.Length * sizeof(int));
|
||||
|
||||
public void BeginFrame()
|
||||
{
|
||||
|
|
@ -171,6 +200,8 @@ internal sealed class RetailAlphaQueue : IWorldSceneAlphaFrame
|
|||
}
|
||||
finally
|
||||
{
|
||||
int submissionCount = _submissions.Count;
|
||||
int sourceCount = _sources.Count;
|
||||
for (int i = 0; i < _sources.Count; i++)
|
||||
{
|
||||
try
|
||||
|
|
@ -184,6 +215,7 @@ internal sealed class RetailAlphaQueue : IWorldSceneAlphaFrame
|
|||
}
|
||||
_sources.Clear();
|
||||
_submissions.Clear();
|
||||
ApplyScratchRetention(submissionCount, sourceCount);
|
||||
}
|
||||
|
||||
if (drawFailure is not null)
|
||||
|
|
@ -246,9 +278,12 @@ internal sealed class RetailAlphaQueue : IWorldSceneAlphaFrame
|
|||
}
|
||||
finally
|
||||
{
|
||||
int submissionCount = _submissions.Count;
|
||||
int sourceCount = _sources.Count;
|
||||
_sources.Clear();
|
||||
_submissions.Clear();
|
||||
IsCollecting = false;
|
||||
ApplyScratchRetention(submissionCount, sourceCount);
|
||||
}
|
||||
|
||||
if (failures is { Count: > 0 })
|
||||
|
|
@ -339,4 +374,40 @@ internal sealed class RetailAlphaQueue : IWorldSceneAlphaFrame
|
|||
return i;
|
||||
throw new InvalidOperationException("Retail alpha source was not registered for this frame.");
|
||||
}
|
||||
|
||||
private void ApplyScratchRetention(
|
||||
int observedSubmissionCount,
|
||||
int observedSourceCount)
|
||||
{
|
||||
int currentCapacity = Math.Max(
|
||||
_submissions.Capacity,
|
||||
Math.Max(_tokenScratch.Length, _sortScratch.Length));
|
||||
int bytesPerSubmission =
|
||||
checked(
|
||||
2 * Unsafe.SizeOf<RetailAlphaSubmission>()
|
||||
+ 2 * sizeof(int)
|
||||
+ IntPtr.Size);
|
||||
int targetCapacity = _scratchPolicy.ObserveAndSelectCapacity(
|
||||
currentCapacity,
|
||||
observedSubmissionCount,
|
||||
bytesPerSubmission,
|
||||
MinimumSubmissionCapacity,
|
||||
SubmissionGrowthQuantum);
|
||||
if (targetCapacity < currentCapacity)
|
||||
{
|
||||
_submissions.Capacity = targetCapacity;
|
||||
Array.Resize(ref _tokenScratch, targetCapacity);
|
||||
Array.Resize(ref _sortScratch, targetCapacity);
|
||||
|
||||
int sourceTarget = Math.Max(
|
||||
MinimumSourceCapacity,
|
||||
observedSourceCount == 0
|
||||
? MinimumSourceCapacity
|
||||
: checked(observedSourceCount * 2));
|
||||
sourceTarget = Math.Min(sourceTarget, _sources.Capacity);
|
||||
_sources.Capacity = sourceTarget;
|
||||
if (_sourceDrawOffsets.Length > sourceTarget)
|
||||
Array.Resize(ref _sourceDrawOffsets, sourceTarget);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue