feat(render): unify physical residency accounting
This commit is contained in:
parent
1866ea0c6d
commit
3e18fc2730
25 changed files with 642 additions and 48 deletions
|
|
@ -3,6 +3,7 @@ using System.Runtime.CompilerServices;
|
|||
using AcDream.App.Composition;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Rendering.Residency;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Content;
|
||||
using AcDream.Core.Terrain;
|
||||
|
|
@ -19,7 +20,13 @@ public sealed class WorldRenderCompositionTests
|
|||
[Fact]
|
||||
public void SuccessPublishesExactModernFoundationInFrozenOrder()
|
||||
{
|
||||
var fixture = new Fixture();
|
||||
ResidencyBudgetOptions budgets =
|
||||
ResidencyBudgetOptions.Default with
|
||||
{
|
||||
ObjectMeshGpuBytes = 321,
|
||||
CompositePhysicalBytes = 654,
|
||||
};
|
||||
var fixture = new Fixture(budgets: budgets);
|
||||
|
||||
WorldRenderResult result = fixture.Compose();
|
||||
|
||||
|
|
@ -30,6 +37,10 @@ public sealed class WorldRenderCompositionTests
|
|||
Assert.Same(fixture.Lifetime.Atlas, result.Foundation.TerrainAtlas);
|
||||
Assert.Equal(QualitySettings.From(QualityPreset.High).AnisotropicLevel,
|
||||
fixture.Factory.AnisotropicLevel);
|
||||
Assert.Same(budgets, result.Foundation.Residency.Budgets);
|
||||
Assert.Same(budgets, fixture.Factory.MeshBudgets);
|
||||
Assert.Same(budgets, fixture.Factory.TextureBudgets);
|
||||
Assert.Same(result.Foundation.Residency, fixture.Factory.RegisteredResidency);
|
||||
Assert.Equal(1, fixture.Lifetime.AcquireCalls);
|
||||
Assert.Empty(fixture.Factory.Releases);
|
||||
}
|
||||
|
|
@ -134,13 +145,16 @@ public sealed class WorldRenderCompositionTests
|
|||
private sealed class Fixture
|
||||
{
|
||||
private readonly WorldRenderCompositionPoint? _failurePoint;
|
||||
private readonly ResidencyBudgetOptions _budgets;
|
||||
|
||||
public Fixture(
|
||||
bool hasFont = true,
|
||||
WorldRenderCompositionPoint? failurePoint = null,
|
||||
string? publicationFailure = null)
|
||||
string? publicationFailure = null,
|
||||
ResidencyBudgetOptions? budgets = null)
|
||||
{
|
||||
_failurePoint = failurePoint;
|
||||
_budgets = budgets ?? ResidencyBudgetOptions.Default;
|
||||
Factory = new Factory(hasFont);
|
||||
Publication = new Publication(publicationFailure);
|
||||
Lifetime = new RenderLifetime(Factory.Atlas);
|
||||
|
|
@ -161,6 +175,7 @@ public sealed class WorldRenderCompositionTests
|
|||
new WorldEnvironmentController(),
|
||||
Lifetime,
|
||||
ImmediateGpuResourceRetirementQueue.Instance,
|
||||
_budgets,
|
||||
0xA9B4FFFFu,
|
||||
_ => { }),
|
||||
Publication,
|
||||
|
|
@ -199,6 +214,9 @@ public sealed class WorldRenderCompositionTests
|
|||
public TerrainAtlas Atlas { get; } = Stub<TerrainAtlas>();
|
||||
public int AnisotropicLevel { get; private set; }
|
||||
public List<string> Releases { get; } = [];
|
||||
public ResidencyBudgetOptions? MeshBudgets { get; private set; }
|
||||
public ResidencyBudgetOptions? TextureBudgets { get; private set; }
|
||||
public ResidencyManager? RegisteredResidency { get; private set; }
|
||||
|
||||
public void InitializeGlState(GL gl) { }
|
||||
|
||||
|
|
@ -266,15 +284,32 @@ public sealed class WorldRenderCompositionTests
|
|||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
IPreparedAssetSource preparedAssets,
|
||||
IGpuResourceRetirementQueue retirement) =>
|
||||
Resource<WbMeshAdapter>("WB mesh adapter");
|
||||
IGpuResourceRetirementQueue retirement,
|
||||
ResidencyBudgetOptions budgets)
|
||||
{
|
||||
MeshBudgets = budgets;
|
||||
return Resource<WbMeshAdapter>("WB mesh adapter");
|
||||
}
|
||||
|
||||
public TextureCache CreateTextureCache(
|
||||
GL gl,
|
||||
IDatReaderWriter dats,
|
||||
BindlessSupport bindless,
|
||||
IGpuResourceRetirementQueue retirement) =>
|
||||
Resource<TextureCache>("texture cache");
|
||||
IGpuResourceRetirementQueue retirement,
|
||||
ResidencyBudgetOptions budgets)
|
||||
{
|
||||
TextureBudgets = budgets;
|
||||
return Resource<TextureCache>("texture cache");
|
||||
}
|
||||
|
||||
public void RegisterResidencySources(
|
||||
ResidencyManager manager,
|
||||
WbMeshAdapter meshes,
|
||||
TextureCache textures,
|
||||
IPreparedAssetSource preparedAssets)
|
||||
{
|
||||
RegisteredResidency = manager;
|
||||
}
|
||||
|
||||
public SamplerCache CreateSamplerCache(GL gl) =>
|
||||
Resource<SamplerCache>("sampler cache");
|
||||
|
|
|
|||
|
|
@ -278,9 +278,9 @@ public sealed class WorldLifecycleAutomationControllerTests
|
|||
|
||||
private static WorldLifecycleResourceSnapshot EmptyResources() => new(
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0L, 0, 0L, 0L, 0, 0, 0, 0, 0, 0, 0, 0L, 0L,
|
||||
0, 0, 0L, 0, 0L, 0L, 0L, 0L, 0, 0, 0, 0, 0, 0, 0, 0L, 0L,
|
||||
0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L,
|
||||
0d, 0d, null);
|
||||
default, 0d, 0d, null);
|
||||
|
||||
private static string NewDirectory()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -6,6 +6,40 @@ namespace AcDream.App.Tests.Rendering;
|
|||
|
||||
public sealed class CompositeTextureArrayCachePolicyTests
|
||||
{
|
||||
[Fact]
|
||||
public void ResidencySnapshotSeparatesResidentRequestedAndRetiringStorage()
|
||||
{
|
||||
var backend = new FakeBackend(maximumLayers: 1);
|
||||
var retirements = new DeferredRetirementQueue();
|
||||
using var cache = CreateCache(
|
||||
backend,
|
||||
retirements,
|
||||
unownedBudgetBytes: 0,
|
||||
physicalBudgetBytes: 0);
|
||||
cache.BeginFrame();
|
||||
Assert.True(cache.TryAddAndAcquire(
|
||||
1,
|
||||
Key(1),
|
||||
Texture(4, 4),
|
||||
out _));
|
||||
|
||||
var resident = cache.CaptureResidency();
|
||||
Assert.Equal(64, resident.Charges.GpuResidentBytes);
|
||||
Assert.Equal(0, resident.Charges.RetiringBytes);
|
||||
Assert.Equal(64, resident.CapacityBytes);
|
||||
Assert.Equal(64, resident.UsedBytes);
|
||||
|
||||
cache.ReleaseOwner(1);
|
||||
cache.Tick();
|
||||
retirements.DrainAll();
|
||||
cache.Tick();
|
||||
|
||||
var retired = cache.CaptureResidency();
|
||||
Assert.Equal(0, retired.Charges.GpuResidentBytes);
|
||||
Assert.Equal(0, retired.Charges.RetiringBytes);
|
||||
Assert.Equal(0, retired.CapacityBytes);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(16, 16, 64)]
|
||||
[InlineData(128, 128, 64)]
|
||||
|
|
|
|||
|
|
@ -343,6 +343,60 @@ public sealed class ResidencyManagerTests
|
|||
Assert.Equal(1, journal.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DomainSourcesAggregateWithoutCountingMappedAddressSpaceAsRam()
|
||||
{
|
||||
var manager = new ResidencyManager();
|
||||
manager.RegisterDomainSource(new DelegateResidencyDomainSource(
|
||||
ResidencyDomain.PreparedPackage,
|
||||
() => new ResidencyDomainSnapshot(
|
||||
ResidencyDomain.PreparedPackage,
|
||||
EntryCount: 1,
|
||||
OwnerCount: 1,
|
||||
Charges: new ResidencyCharges(
|
||||
LogicalBytes: 64,
|
||||
PinnedBytes: 32,
|
||||
MappedVirtualBytes: 30_000))));
|
||||
manager.RegisterDomainSource(new DelegateResidencyDomainSource(
|
||||
ResidencyDomain.StandaloneTextures,
|
||||
() => new ResidencyDomainSnapshot(
|
||||
ResidencyDomain.StandaloneTextures,
|
||||
EntryCount: 2,
|
||||
OwnerCount: 1,
|
||||
Charges: new ResidencyCharges(
|
||||
GpuResidentBytes: 128),
|
||||
BudgetBytes: 256,
|
||||
CapacityBytes: 128,
|
||||
UsedBytes: 96,
|
||||
LargestFreeBytes: 16)));
|
||||
|
||||
ResidencySnapshot snapshot = manager.CaptureSnapshot();
|
||||
|
||||
Assert.Equal(64, snapshot.TotalCharges.CommittedCpuBytes);
|
||||
Assert.Equal(30_000, snapshot.TotalCharges.MappedVirtualBytes);
|
||||
Assert.Equal(128, snapshot.TotalCharges.PhysicalGpuBytes);
|
||||
Assert.Equal(
|
||||
16,
|
||||
snapshot.Get(ResidencyDomain.StandaloneTextures)
|
||||
.FragmentedFreeBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void OnlyOneCanonicalSourceMayOwnADomain()
|
||||
{
|
||||
var manager = new ResidencyManager();
|
||||
var first = new DelegateResidencyDomainSource(
|
||||
ResidencyDomain.ObjectMeshes,
|
||||
() => default);
|
||||
manager.RegisterDomainSource(first);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
manager.RegisterDomainSource(
|
||||
new DelegateResidencyDomainSource(
|
||||
ResidencyDomain.ObjectMeshes,
|
||||
() => default)));
|
||||
}
|
||||
|
||||
private static AssetHandle<MeshAsset> Resident(
|
||||
ResidencyManager manager,
|
||||
ulong id,
|
||||
|
|
|
|||
|
|
@ -52,6 +52,8 @@ public sealed class StandaloneBindlessTextureCacheTests
|
|||
cache.Tick();
|
||||
|
||||
Assert.Equal(2, cache.EntryCount);
|
||||
Assert.Equal(12, cache.AllocatedBytes);
|
||||
Assert.Equal(4, cache.RetiringBytes);
|
||||
Assert.Equal(2, cache.UnownedEntryCount);
|
||||
Assert.Single(retirements.Actions);
|
||||
Assert.Empty(backend.Events);
|
||||
|
|
@ -60,6 +62,8 @@ public sealed class StandaloneBindlessTextureCacheTests
|
|||
|
||||
retirements.Drain();
|
||||
Assert.Equal(["nonresident:1", "delete:1"], backend.Events);
|
||||
Assert.Equal(8, cache.AllocatedBytes);
|
||||
Assert.Equal(0, cache.RetiringBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -196,6 +200,32 @@ public sealed class StandaloneBindlessTextureCacheTests
|
|||
backend.Events);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisposePreservesAcceptedFenceRetirementAccountingUntilCompletion()
|
||||
{
|
||||
var backend = new FakeBackend();
|
||||
var retirements = new DeferredRetirementQueue();
|
||||
var cache = CreateCache(
|
||||
backend,
|
||||
retirements,
|
||||
unownedBudgetBytes: 1_000,
|
||||
maximumUnownedCount: 1);
|
||||
AddAndRelease(cache, ownerId: 10, Resource(1, bytes: 4));
|
||||
AddAndRelease(cache, ownerId: 20, Resource(2, bytes: 4));
|
||||
cache.Tick();
|
||||
|
||||
Assert.Equal(8, cache.AllocatedBytes);
|
||||
Assert.Equal(4, cache.RetiringBytes);
|
||||
|
||||
cache.Dispose();
|
||||
|
||||
Assert.Equal(4, cache.AllocatedBytes);
|
||||
Assert.Equal(4, cache.RetiringBytes);
|
||||
retirements.Drain();
|
||||
Assert.Equal(0, cache.AllocatedBytes);
|
||||
Assert.Equal(0, cache.RetiringBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FailedResidencyReleaseNeverDeletesThatBackingTexture()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue