Extract fractal GPU reservoir budget planner
This commit is contained in:
parent
b1e70162b8
commit
bc65a4655f
4 changed files with 166 additions and 1 deletions
|
|
@ -0,0 +1,109 @@
|
|||
namespace Aquarium.Engine.Fractal.Lod;
|
||||
|
||||
public enum FractalGpuReservoirPassKind
|
||||
{
|
||||
SdfEnvelope,
|
||||
PbrMaterial,
|
||||
Radiosity,
|
||||
}
|
||||
|
||||
public readonly record struct FractalGpuReservoirPassPlan(
|
||||
FractalGpuReservoirPassKind Kind,
|
||||
int ResidentEntries,
|
||||
int UpdatesThisFrame,
|
||||
int CandidatesPerUpdate,
|
||||
double ExpectedFullCoverageFrames,
|
||||
ulong ResidentBytes,
|
||||
ulong CandidateEvaluationsThisFrame);
|
||||
|
||||
public sealed class FractalGpuReservoirFramePlan
|
||||
{
|
||||
public FractalGpuReservoirFramePlan(
|
||||
int splatCount,
|
||||
int splatStrideBytes,
|
||||
ulong splatBytes,
|
||||
IReadOnlyList<FractalGpuReservoirPassPlan> passes)
|
||||
{
|
||||
SplatCount = splatCount;
|
||||
SplatStrideBytes = splatStrideBytes;
|
||||
SplatBytes = splatBytes;
|
||||
Passes = passes;
|
||||
}
|
||||
|
||||
public int SplatCount { get; }
|
||||
|
||||
public int SplatStrideBytes { get; }
|
||||
|
||||
public ulong SplatBytes { get; }
|
||||
|
||||
public IReadOnlyList<FractalGpuReservoirPassPlan> Passes { get; }
|
||||
|
||||
public ulong ReservoirBytes => Passes.Aggregate(0UL, (sum, pass) => sum + pass.ResidentBytes);
|
||||
|
||||
public ulong TotalResidentBytes => SplatBytes + ReservoirBytes;
|
||||
|
||||
public ulong CandidateEvaluationsThisFrame => Passes.Aggregate(0UL, (sum, pass) => sum + pass.CandidateEvaluationsThisFrame);
|
||||
}
|
||||
|
||||
public static class FractalGpuReservoirBudgetPlanner
|
||||
{
|
||||
public static FractalGpuReservoirFramePlan Plan(
|
||||
int splatCount,
|
||||
int reservoirUpdatesPerPass,
|
||||
int candidatesPerUpdate,
|
||||
int splatStrideBytes = 80,
|
||||
int reservoirStrideBytes = 64)
|
||||
{
|
||||
if (splatCount <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(splatCount), splatCount, "Splat count must be positive.");
|
||||
}
|
||||
|
||||
if (reservoirUpdatesPerPass <= 0 || reservoirUpdatesPerPass > splatCount)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(reservoirUpdatesPerPass), reservoirUpdatesPerPass, "Reservoir updates must be positive and no larger than the resident splat count.");
|
||||
}
|
||||
|
||||
if (candidatesPerUpdate <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(candidatesPerUpdate), candidatesPerUpdate, "Candidates per update must be positive.");
|
||||
}
|
||||
|
||||
if (splatStrideBytes <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(splatStrideBytes), splatStrideBytes, "Splat stride must be positive.");
|
||||
}
|
||||
|
||||
if (reservoirStrideBytes <= 0)
|
||||
{
|
||||
throw new ArgumentOutOfRangeException(nameof(reservoirStrideBytes), reservoirStrideBytes, "Reservoir stride must be positive.");
|
||||
}
|
||||
|
||||
var splatBytes = checked((ulong)splatCount * (ulong)splatStrideBytes);
|
||||
var expectedCoverageFrames = splatCount / (double)reservoirUpdatesPerPass;
|
||||
var passes = new[]
|
||||
{
|
||||
Pass(FractalGpuReservoirPassKind.SdfEnvelope),
|
||||
Pass(FractalGpuReservoirPassKind.PbrMaterial),
|
||||
Pass(FractalGpuReservoirPassKind.Radiosity),
|
||||
};
|
||||
|
||||
return new FractalGpuReservoirFramePlan(
|
||||
splatCount,
|
||||
splatStrideBytes,
|
||||
splatBytes,
|
||||
passes);
|
||||
|
||||
FractalGpuReservoirPassPlan Pass(FractalGpuReservoirPassKind kind)
|
||||
{
|
||||
return new FractalGpuReservoirPassPlan(
|
||||
kind,
|
||||
splatCount,
|
||||
reservoirUpdatesPerPass,
|
||||
candidatesPerUpdate,
|
||||
expectedCoverageFrames,
|
||||
checked((ulong)splatCount * (ulong)reservoirStrideBytes),
|
||||
checked((ulong)reservoirUpdatesPerPass * (ulong)candidatesPerUpdate));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
using Aquarium.Engine.Fractal.Lod;
|
||||
|
||||
namespace Aquarium.Engine.Fractal.Tests;
|
||||
|
||||
public sealed class FractalGpuReservoirBudgetPlannerTests
|
||||
{
|
||||
[Fact]
|
||||
public void PlanKeepsReservoirPassesDistinctWhileSharingBudgetMath()
|
||||
{
|
||||
var plan = FractalGpuReservoirBudgetPlanner.Plan(
|
||||
splatCount: 2_000_000,
|
||||
reservoirUpdatesPerPass: 50_000,
|
||||
candidatesPerUpdate: 2);
|
||||
|
||||
Assert.Equal(2_000_000, plan.SplatCount);
|
||||
Assert.Equal(160_000_000UL, plan.SplatBytes);
|
||||
Assert.Equal(384_000_000UL, plan.ReservoirBytes);
|
||||
Assert.Equal(544_000_000UL, plan.TotalResidentBytes);
|
||||
Assert.Equal(300_000UL, plan.CandidateEvaluationsThisFrame);
|
||||
Assert.Equal(
|
||||
new[]
|
||||
{
|
||||
FractalGpuReservoirPassKind.SdfEnvelope,
|
||||
FractalGpuReservoirPassKind.PbrMaterial,
|
||||
FractalGpuReservoirPassKind.Radiosity,
|
||||
},
|
||||
plan.Passes.Select(pass => pass.Kind).ToArray());
|
||||
|
||||
foreach (var pass in plan.Passes)
|
||||
{
|
||||
Assert.Equal(2_000_000, pass.ResidentEntries);
|
||||
Assert.Equal(50_000, pass.UpdatesThisFrame);
|
||||
Assert.Equal(2, pass.CandidatesPerUpdate);
|
||||
Assert.Equal(40.0, pass.ExpectedFullCoverageFrames);
|
||||
Assert.Equal(128_000_000UL, pass.ResidentBytes);
|
||||
Assert.Equal(100_000UL, pass.CandidateEvaluationsThisFrame);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlanRejectsBudgetsThatCannotConvergeOverResidentReservoirs()
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => FractalGpuReservoirBudgetPlanner.Plan(0, 1, 1));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => FractalGpuReservoirBudgetPlanner.Plan(16, 0, 1));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => FractalGpuReservoirBudgetPlanner.Plan(16, 17, 1));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => FractalGpuReservoirBudgetPlanner.Plan(16, 1, 0));
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\src\Aquarium.Engine.Contracts\Aquarium.Engine.Contracts.csproj" />
|
||||
<ProjectReference Include="..\..\src\Aquarium.Engine.Fractal\Aquarium.Engine.Fractal.csproj" />
|
||||
<PackageReference Include="Vortice.Direct3D12" Version="3.8.3" />
|
||||
<PackageReference Include="Vortice.D3DCompiler" Version="3.8.3" />
|
||||
<PackageReference Include="Vortice.DXGI" Version="3.8.3" />
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ using System.Diagnostics;
|
|||
using System.Runtime.InteropServices;
|
||||
using System.Text.Json;
|
||||
using Aquarium.Engine.Fractal;
|
||||
using Aquarium.Engine.Fractal.Lod;
|
||||
using Vortice.D3DCompiler;
|
||||
using Vortice.Direct3D;
|
||||
using Vortice.Direct3D12;
|
||||
|
|
@ -74,6 +75,12 @@ internal sealed class GpuFractalSplatReceiptRunner : IDisposable
|
|||
{
|
||||
var splatStride = Marshal.SizeOf<AquariumPackedFractalSdfSplat3D>();
|
||||
var reservoirStride = Marshal.SizeOf<AquariumPackedSdfEnvelopeReservoir>();
|
||||
var framePlan = FractalGpuReservoirBudgetPlanner.Plan(
|
||||
options.SplatCount,
|
||||
options.ReservoirUpdatesPerPass,
|
||||
options.CandidatesPerPass,
|
||||
splatStride,
|
||||
reservoirStride);
|
||||
var splatBytes = checked((ulong)splatStride * (ulong)options.SplatCount);
|
||||
var reservoirBytes = checked((ulong)reservoirStride * (ulong)options.SplatCount);
|
||||
using var splats = CreateUavBuffer(splatBytes, "Aquarium Fractal Receipt GPU Splat Buffer");
|
||||
|
|
@ -150,7 +157,7 @@ internal sealed class GpuFractalSplatReceiptRunner : IDisposable
|
|||
options.SplatCount,
|
||||
options.CandidatesPerPass,
|
||||
options.ReservoirUpdatesPerPass,
|
||||
options.SplatCount / (double)options.ReservoirUpdatesPerPass,
|
||||
framePlan.Passes[0].ExpectedFullCoverageFrames,
|
||||
measuredFrames,
|
||||
splatStride,
|
||||
reservoirStride,
|
||||
|
|
|
|||
Loading…
Reference in a new issue