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
|
|
@ -4,6 +4,7 @@ using AcDream.App.Audio;
|
|||
using AcDream.App.Composition;
|
||||
using AcDream.App.Physics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Residency;
|
||||
using AcDream.App.Rendering.Vfx;
|
||||
using AcDream.App.Spells;
|
||||
using AcDream.Content;
|
||||
|
|
@ -253,6 +254,7 @@ public sealed class ContentEffectsAudioCompositionTests
|
|||
Dependencies = new ContentEffectsAudioDependencies(
|
||||
"test-dat",
|
||||
"test.pak",
|
||||
ResidencyBudgetOptions.Default,
|
||||
new PhysicsDataCache(),
|
||||
false,
|
||||
new Spellbook(),
|
||||
|
|
@ -386,7 +388,19 @@ public sealed class ContentEffectsAudioCompositionTests
|
|||
public MagicCatalog LoadMagicCatalog(IDatReaderWriter dats) => _magic;
|
||||
public void InstallSpellMetadata(Spellbook spellBook, MagicCatalog catalog) { }
|
||||
public int GetSpellCount(MagicCatalog catalog) => 0;
|
||||
public IAnimationLoader CreateAnimationLoader(IDatReaderWriter dats) => _animations;
|
||||
public IAnimationLoader CreateAnimationLoader(
|
||||
IDatReaderWriter dats,
|
||||
long maximumEstimatedBytes,
|
||||
int maximumEntries)
|
||||
{
|
||||
Assert.Equal(
|
||||
ResidencyBudgetOptions.Default.AnimationBytes,
|
||||
maximumEstimatedBytes);
|
||||
Assert.Equal(
|
||||
ResidencyBudgetOptions.Default.AnimationEntries,
|
||||
maximumEntries);
|
||||
return _animations;
|
||||
}
|
||||
|
||||
public LiveEntityCollisionBuilder CreateCollisionBuilder(
|
||||
PhysicsDataCache physicsData,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using AcDream.App.Rendering.Wb;
|
|||
using AcDream.App.Rendering.Residency;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Content;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.Terrain;
|
||||
using AcDream.UI.Abstractions.Settings;
|
||||
using DatReaderWriter.DBObjs;
|
||||
|
|
@ -306,7 +307,8 @@ public sealed class WorldRenderCompositionTests
|
|||
ResidencyManager manager,
|
||||
WbMeshAdapter meshes,
|
||||
TextureCache textures,
|
||||
IPreparedAssetSource preparedAssets)
|
||||
IPreparedAssetSource preparedAssets,
|
||||
IAnimationLoader animations)
|
||||
{
|
||||
RegisteredResidency = manager;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,20 @@ public sealed class ResidencyManagerTests
|
|||
private sealed class MeshAsset;
|
||||
private sealed class TextureAsset;
|
||||
|
||||
[Fact]
|
||||
public void ScratchBytesAreCommittedCpuResidence()
|
||||
{
|
||||
var charges = new ResidencyCharges(
|
||||
LogicalBytes: 1,
|
||||
CpuPreparedBytes: 2,
|
||||
DecodedBytes: 3,
|
||||
ScratchBytes: 4,
|
||||
StagingBytes: 5);
|
||||
|
||||
Assert.Equal(15, charges.CommittedCpuBytes);
|
||||
charges.Validate();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandlesAndOwnersReserveZeroAsInvalid()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -0,0 +1,86 @@
|
|||
using AcDream.App.Rendering.Residency;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering.Residency;
|
||||
|
||||
public sealed class RetainedScratchCapacityPolicyTests
|
||||
{
|
||||
[Fact]
|
||||
public void AlphaBudgetProfilePartitionsTheAggregateExactly()
|
||||
{
|
||||
AlphaScratchBudgetProfile profile =
|
||||
AlphaScratchBudgetProfile.Create(16 * 1024 * 1024);
|
||||
|
||||
Assert.Equal(4 * 1024 * 1024, profile.QueueBytes);
|
||||
Assert.Equal(8 * 1024 * 1024, profile.DispatcherBytes);
|
||||
Assert.Equal(4 * 1024 * 1024, profile.ParticleBytes);
|
||||
Assert.Equal(16 * 1024 * 1024, profile.TotalBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SpikeRetainsWarmCapacityUntilDemandFallsThenConverges()
|
||||
{
|
||||
var policy = new RetainedScratchCapacityPolicy(
|
||||
budgetBytes: 1024,
|
||||
lowDemandSamplesBeforeShrink: 3);
|
||||
|
||||
Assert.Equal(
|
||||
4096,
|
||||
policy.ObserveAndSelectCapacity(
|
||||
currentCapacity: 4096,
|
||||
requiredCapacity: 4096,
|
||||
bytesPerUnit: 4,
|
||||
minimumCapacity: 64,
|
||||
growthQuantum: 64));
|
||||
Assert.Equal(4096, ObserveLowDemand(policy));
|
||||
Assert.Equal(4096, ObserveLowDemand(policy));
|
||||
|
||||
int converged = ObserveLowDemand(policy);
|
||||
|
||||
Assert.InRange(converged, 64, 256);
|
||||
Assert.True((long)converged * 4 <= 1024);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SustainedLegitimateDemandIsNeverTrimmedBelowRequirement()
|
||||
{
|
||||
var policy = new RetainedScratchCapacityPolicy(
|
||||
budgetBytes: 1024,
|
||||
lowDemandSamplesBeforeShrink: 2);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
Assert.Equal(
|
||||
4096,
|
||||
policy.ObserveAndSelectCapacity(
|
||||
currentCapacity: 4096,
|
||||
requiredCapacity: 4096,
|
||||
bytesPerUnit: 4,
|
||||
minimumCapacity: 64,
|
||||
growthQuantum: 64));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RequiredGrowthIsImmediateAndQuantumRounded()
|
||||
{
|
||||
var policy = new RetainedScratchCapacityPolicy(1024);
|
||||
|
||||
Assert.Equal(
|
||||
320,
|
||||
policy.ObserveAndSelectCapacity(
|
||||
currentCapacity: 64,
|
||||
requiredCapacity: 257,
|
||||
bytesPerUnit: 4,
|
||||
minimumCapacity: 64,
|
||||
growthQuantum: 64));
|
||||
}
|
||||
|
||||
private static int ObserveLowDemand(
|
||||
RetainedScratchCapacityPolicy policy) =>
|
||||
policy.ObserveAndSelectCapacity(
|
||||
currentCapacity: 4096,
|
||||
requiredCapacity: 32,
|
||||
bytesPerUnit: 4,
|
||||
minimumCapacity: 64,
|
||||
growthQuantum: 64);
|
||||
}
|
||||
|
|
@ -157,6 +157,31 @@ public sealed class RetailAlphaQueueTests
|
|||
Assert.Equal(expected, source.PreparedTokens);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RetainedScratchConvergesAfterAOneScopeSpike()
|
||||
{
|
||||
const int budgetBytes = 128 * 1024;
|
||||
var log = new List<string>();
|
||||
var source = new RecordingSource("alpha", log);
|
||||
var queue = new RetailAlphaQueue(budgetBytes);
|
||||
|
||||
queue.BeginFrame();
|
||||
for (int i = 0; i < 8_192; i++)
|
||||
queue.Submit(source, i, i);
|
||||
queue.EndFrame();
|
||||
Assert.True(queue.RetainedScratchBytes > budgetBytes);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
queue.BeginFrame();
|
||||
queue.EndFrame();
|
||||
}
|
||||
|
||||
Assert.True(queue.RetainedScratchBytes <= budgetBytes);
|
||||
Assert.False(queue.IsCollecting);
|
||||
Assert.Equal(0, queue.PendingCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Flush_EqualDistanceOrderStartsFreshInEachFrameScope()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue