feat(render): implement Campaign AR and terrain fidelity
This commit is contained in:
parent
99cf26e00c
commit
7a5f96ede5
368 changed files with 50611 additions and 950 deletions
|
|
@ -0,0 +1,391 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Gpu;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using AcDream.App.Tests.Rendering.Gpu;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public sealed class DirectionalShadowTransformBufferSetTests
|
||||
{
|
||||
[Fact]
|
||||
public void FlightSlotsRetainStaticPrefix_AndWarmedReuseAllocatesAndWritesNothing()
|
||||
{
|
||||
using var device = new RecordingGpuDevice();
|
||||
using var buffers = new DirectionalShadowTransformBufferSet(device);
|
||||
Matrix4x4[] transforms =
|
||||
[
|
||||
Matrix4x4.CreateTranslation(1f, 2f, 3f),
|
||||
Matrix4x4.CreateRotationZ(0.25f),
|
||||
];
|
||||
|
||||
RecordingGpuBuffer slotZero;
|
||||
using (IGpuFrame first = device.BeginFrame())
|
||||
{
|
||||
WorldTransformFrameSlice slice = buffers.Publish(
|
||||
first,
|
||||
topologyBuildSequence: 7,
|
||||
transforms,
|
||||
ReadOnlySpan<int>.Empty);
|
||||
slotZero = Assert.IsType<RecordingGpuBuffer>(slice.Buffer);
|
||||
Assert.Equal(0, first.SlotIndex);
|
||||
Assert.Equal(WorldTransformCapacityPolicy.InitialBindingSizeBytes,
|
||||
slice.BindingSizeBytes);
|
||||
}
|
||||
using (IGpuFrame second = device.BeginFrame())
|
||||
{
|
||||
WorldTransformFrameSlice slice = buffers.Publish(
|
||||
second,
|
||||
topologyBuildSequence: 7,
|
||||
transforms,
|
||||
ReadOnlySpan<int>.Empty);
|
||||
Assert.Equal(1, second.SlotIndex);
|
||||
Assert.NotSame(slotZero, slice.Buffer);
|
||||
}
|
||||
|
||||
device.Clear();
|
||||
int buffersBefore = device.CreatedBuffers.Count;
|
||||
int uploadsBefore = slotZero.UploadCount;
|
||||
using IGpuFrame warmed = device.BeginFrame();
|
||||
Assert.Equal(0, warmed.SlotIndex);
|
||||
buffers.Publish(
|
||||
warmed,
|
||||
topologyBuildSequence: 7,
|
||||
transforms,
|
||||
ReadOnlySpan<int>.Empty);
|
||||
long before = GC.GetAllocatedBytesForCurrentThread();
|
||||
for (int iteration = 0; iteration < 256; iteration++)
|
||||
{
|
||||
buffers.Publish(
|
||||
warmed,
|
||||
topologyBuildSequence: 7,
|
||||
transforms,
|
||||
ReadOnlySpan<int>.Empty);
|
||||
}
|
||||
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
|
||||
|
||||
Assert.Equal(0, allocated);
|
||||
Assert.Equal(buffersBefore, device.CreatedBuffers.Count);
|
||||
Assert.Equal(uploadsBefore, slotZero.UploadCount);
|
||||
Assert.Empty(device.OfKind<GpuRecordedHostStorageVisibility>());
|
||||
Assert.False(buffers.LastStats.TopologyUploaded);
|
||||
Assert.Equal(0, buffers.LastStats.BytesWritten);
|
||||
Assert.Equal(2, buffers.BufferCount);
|
||||
Assert.Equal(
|
||||
2L * WorldTransformCapacityPolicy.InitialBindingSizeBytes,
|
||||
buffers.RetainedGpuBytes);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void StableTopology_UpdatesOnlyStrictDynamicRangesWithExactMatrixBits()
|
||||
{
|
||||
using var device = new RecordingGpuDevice();
|
||||
using var buffers = new DirectionalShadowTransformBufferSet(device);
|
||||
Matrix4x4[] transforms =
|
||||
[
|
||||
Matrix4x4.Identity,
|
||||
Matrix4x4.CreateTranslation(1f, 2f, 3f),
|
||||
Matrix4x4.CreateTranslation(4f, 5f, 6f),
|
||||
Matrix4x4.CreateTranslation(7f, 8f, 9f),
|
||||
Matrix4x4.CreateTranslation(10f, 11f, 12f),
|
||||
];
|
||||
int[] dynamicSlots = [1, 2, 4];
|
||||
|
||||
using (IGpuFrame first = device.BeginFrame())
|
||||
buffers.Publish(first, 11, transforms, dynamicSlots);
|
||||
using (IGpuFrame second = device.BeginFrame())
|
||||
buffers.Publish(second, 11, transforms, dynamicSlots);
|
||||
|
||||
float exactX = BitConverter.Int32BitsToSingle(0x41234567);
|
||||
float exactY = BitConverter.Int32BitsToSingle(0x40ABCDEF);
|
||||
transforms[1] = Matrix4x4.CreateRotationX(0.3f)
|
||||
* Matrix4x4.CreateTranslation(exactX, 20f, 30f);
|
||||
transforms[2] = Matrix4x4.CreateRotationY(0.4f)
|
||||
* Matrix4x4.CreateTranslation(40f, exactY, 50f);
|
||||
transforms[4] = Matrix4x4.CreateRotationZ(0.5f)
|
||||
* Matrix4x4.CreateTranslation(60f, 70f, 80f);
|
||||
|
||||
device.Clear();
|
||||
using IGpuFrame third = device.BeginFrame();
|
||||
WorldTransformFrameSlice slice = buffers.Publish(
|
||||
third,
|
||||
11,
|
||||
transforms,
|
||||
dynamicSlots);
|
||||
var retained = Assert.IsType<RecordingGpuBuffer>(slice.Buffer);
|
||||
Matrix4x4[] readback = new Matrix4x4[transforms.Length];
|
||||
retained.Read(0, MemoryMarshal.AsBytes(readback.AsSpan()));
|
||||
|
||||
AssertMatrixBitsEqual(transforms[0], readback[0]);
|
||||
AssertMatrixBitsEqual(transforms[1], readback[1]);
|
||||
AssertMatrixBitsEqual(transforms[2], readback[2]);
|
||||
AssertMatrixBitsEqual(transforms[3], readback[3]);
|
||||
AssertMatrixBitsEqual(transforms[4], readback[4]);
|
||||
Assert.False(buffers.LastStats.TopologyUploaded);
|
||||
Assert.Equal(3, buffers.LastStats.DynamicMatricesUpdated);
|
||||
Assert.Equal(2, buffers.LastStats.DynamicRangesUpdated);
|
||||
Assert.Equal(3 * 64, buffers.LastStats.BytesWritten);
|
||||
Assert.Single(device.OfKind<GpuRecordedHostStorageVisibility>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChangedMatrix_ReplaysExactlyToEveryFlightSlotWithoutRescanningAllDynamics()
|
||||
{
|
||||
using var device = new RecordingGpuDevice();
|
||||
using var buffers = new DirectionalShadowTransformBufferSet(device);
|
||||
Matrix4x4[] transforms =
|
||||
[
|
||||
Matrix4x4.Identity,
|
||||
Matrix4x4.CreateTranslation(1f, 2f, 3f),
|
||||
Matrix4x4.CreateTranslation(4f, 5f, 6f),
|
||||
];
|
||||
int[] allDynamic = [1, 2];
|
||||
using (IGpuFrame first = device.BeginFrame())
|
||||
buffers.Publish(first, 12, transforms, [], allDynamic);
|
||||
using (IGpuFrame second = device.BeginFrame())
|
||||
buffers.Publish(second, 12, transforms, [], allDynamic);
|
||||
|
||||
float exact = BitConverter.Int32BitsToSingle(0x41234567);
|
||||
transforms[2] = Matrix4x4.CreateRotationZ(0.25f)
|
||||
* Matrix4x4.CreateTranslation(exact, 8f, 9f);
|
||||
RecordingGpuBuffer slotZero;
|
||||
using (IGpuFrame changed = device.BeginFrame())
|
||||
{
|
||||
slotZero = Assert.IsType<RecordingGpuBuffer>(buffers.Publish(
|
||||
changed,
|
||||
12,
|
||||
transforms,
|
||||
[2],
|
||||
allDynamic).Buffer);
|
||||
Assert.Equal(1, buffers.LastStats.DynamicMatricesUpdated);
|
||||
}
|
||||
|
||||
device.Clear();
|
||||
RecordingGpuBuffer slotOne;
|
||||
using (IGpuFrame replay = device.BeginFrame())
|
||||
{
|
||||
slotOne = Assert.IsType<RecordingGpuBuffer>(buffers.Publish(
|
||||
replay,
|
||||
12,
|
||||
transforms,
|
||||
[],
|
||||
allDynamic).Buffer);
|
||||
Assert.Equal(1, buffers.LastStats.DynamicMatricesUpdated);
|
||||
Assert.Equal(64, buffers.LastStats.BytesWritten);
|
||||
Assert.Equal(0, buffers.LastStats.CurrentChangedMatrices);
|
||||
Assert.Equal(1, buffers.LastStats.PendingReplayMatrices);
|
||||
}
|
||||
Matrix4x4[] zeroReadback = new Matrix4x4[3];
|
||||
Matrix4x4[] oneReadback = new Matrix4x4[3];
|
||||
slotZero.Read(0, MemoryMarshal.AsBytes(zeroReadback.AsSpan()));
|
||||
slotOne.Read(0, MemoryMarshal.AsBytes(oneReadback.AsSpan()));
|
||||
AssertMatrixBitsEqual(transforms[2], zeroReadback[2]);
|
||||
AssertMatrixBitsEqual(transforms[2], oneReadback[2]);
|
||||
Assert.Single(device.OfKind<GpuRecordedHostStorageVisibility>());
|
||||
Assert.True(buffers.RetainedScratchBytes > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RepeatedChanges_CoalesceToOneLatestValueForWaitingFlightSlot()
|
||||
{
|
||||
using var device = new RecordingGpuDevice();
|
||||
using var buffers = new DirectionalShadowTransformBufferSet(device);
|
||||
Matrix4x4[] transforms =
|
||||
[
|
||||
Matrix4x4.Identity,
|
||||
Matrix4x4.CreateTranslation(1f, 2f, 3f),
|
||||
];
|
||||
int[] allDynamic = [1];
|
||||
using (IGpuFrame first = device.BeginFrame())
|
||||
buffers.Publish(first, 13, transforms, [], allDynamic);
|
||||
using (IGpuFrame second = device.BeginFrame())
|
||||
buffers.Publish(second, 13, transforms, [], allDynamic);
|
||||
|
||||
using (IGpuFrame current = device.BeginFrame())
|
||||
{
|
||||
transforms[1] = Matrix4x4.CreateTranslation(10f, 20f, 30f);
|
||||
buffers.Publish(current, 13, transforms, [1], allDynamic);
|
||||
transforms[1] = Matrix4x4.CreateTranslation(40f, 50f, 60f);
|
||||
buffers.Publish(current, 13, transforms, [1], allDynamic);
|
||||
}
|
||||
using IGpuFrame waiting = device.BeginFrame();
|
||||
WorldTransformFrameSlice slice = buffers.Publish(
|
||||
waiting,
|
||||
13,
|
||||
transforms,
|
||||
[],
|
||||
allDynamic);
|
||||
var retained = Assert.IsType<RecordingGpuBuffer>(slice.Buffer);
|
||||
Matrix4x4[] readback = new Matrix4x4[2];
|
||||
retained.Read(0, MemoryMarshal.AsBytes(readback.AsSpan()));
|
||||
|
||||
AssertMatrixBitsEqual(transforms[1], readback[1]);
|
||||
Assert.Equal(1, buffers.LastStats.DynamicMatricesUpdated);
|
||||
Assert.Equal(1, buffers.LastStats.PendingReplayMatrices);
|
||||
Assert.Equal(64, buffers.LastStats.BytesWritten);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DenseRefresh_UploadsFourExactContiguousRangesAndReplaysDirectlyPerFlight()
|
||||
{
|
||||
using var device = new RecordingGpuDevice();
|
||||
using var buffers = new DirectionalShadowTransformBufferSet(device);
|
||||
var transforms = new Matrix4x4[2_000];
|
||||
for (int index = 0; index < transforms.Length; index++)
|
||||
transforms[index] = Matrix4x4.CreateTranslation(index, index + 1, index + 2);
|
||||
int[] dynamicSlots = Enumerable.Range(0, 494)
|
||||
.Concat(Enumerable.Range(500, 494))
|
||||
.Concat(Enumerable.Range(1_000, 494))
|
||||
.Concat(Enumerable.Range(1_500, 494))
|
||||
.ToArray();
|
||||
using (IGpuFrame first = device.BeginFrame())
|
||||
buffers.Publish(first, 14, transforms, [], dynamicSlots, false);
|
||||
using (IGpuFrame second = device.BeginFrame())
|
||||
buffers.Publish(second, 14, transforms, [], dynamicSlots, false);
|
||||
|
||||
for (int index = 0; index < dynamicSlots.Length; index++)
|
||||
{
|
||||
int slot = dynamicSlots[index];
|
||||
transforms[slot] = Matrix4x4.CreateTranslation(
|
||||
slot + 10_000,
|
||||
slot + 20_000,
|
||||
slot + 30_000);
|
||||
}
|
||||
using (IGpuFrame dense = device.BeginFrame())
|
||||
{
|
||||
buffers.Publish(
|
||||
dense,
|
||||
14,
|
||||
transforms,
|
||||
dynamicSlots,
|
||||
dynamicSlots,
|
||||
denseRefresh: true);
|
||||
Assert.True(buffers.LastStats.DenseDirectUpload);
|
||||
Assert.False(buffers.LastStats.DenseFlightReplay);
|
||||
Assert.Equal(1_976, buffers.LastStats.DynamicMatricesUpdated);
|
||||
Assert.Equal(4, buffers.LastStats.DynamicRangesUpdated);
|
||||
Assert.Equal(1_976 * 64, buffers.LastStats.BytesWritten);
|
||||
Assert.Equal(0, buffers.LastStats.PendingReplayMatrices);
|
||||
}
|
||||
|
||||
using IGpuFrame replay = device.BeginFrame();
|
||||
WorldTransformFrameSlice replaySlice = buffers.Publish(
|
||||
replay,
|
||||
14,
|
||||
transforms,
|
||||
[],
|
||||
dynamicSlots,
|
||||
denseRefresh: false);
|
||||
Assert.False(buffers.LastStats.DenseDirectUpload);
|
||||
Assert.True(buffers.LastStats.DenseFlightReplay);
|
||||
Assert.Equal(1_976, buffers.LastStats.DynamicMatricesUpdated);
|
||||
Assert.Equal(4, buffers.LastStats.DynamicRangesUpdated);
|
||||
Assert.Equal(1_976 * 64, buffers.LastStats.BytesWritten);
|
||||
var retained = Assert.IsType<RecordingGpuBuffer>(replaySlice.Buffer);
|
||||
var readback = new Matrix4x4[2_000];
|
||||
retained.Read(0, MemoryMarshal.AsBytes(readback.AsSpan()));
|
||||
AssertMatrixBitsEqual(transforms[0], readback[0]);
|
||||
AssertMatrixBitsEqual(transforms[1_993], readback[1_993]);
|
||||
AssertMatrixBitsEqual(transforms[1_999], readback[1_999]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TopologyChange_CreateSwapsCurrentSlotAndDisposalReleasesEverySlot()
|
||||
{
|
||||
using var device = new RecordingGpuDevice();
|
||||
var buffers = new DirectionalShadowTransformBufferSet(device);
|
||||
Matrix4x4[] firstPose = [Matrix4x4.Identity];
|
||||
RecordingGpuBuffer firstSlot;
|
||||
RecordingGpuBuffer secondSlot;
|
||||
using (IGpuFrame first = device.BeginFrame())
|
||||
{
|
||||
firstSlot = Assert.IsType<RecordingGpuBuffer>(buffers.Publish(
|
||||
first, 1, firstPose, []).Buffer);
|
||||
}
|
||||
using (IGpuFrame second = device.BeginFrame())
|
||||
{
|
||||
secondSlot = Assert.IsType<RecordingGpuBuffer>(buffers.Publish(
|
||||
second, 1, firstPose, []).Buffer);
|
||||
}
|
||||
|
||||
Matrix4x4[] rebuiltPose =
|
||||
[
|
||||
Matrix4x4.CreateTranslation(9f, 8f, 7f),
|
||||
Matrix4x4.CreateScale(2f),
|
||||
];
|
||||
RecordingGpuBuffer replacement;
|
||||
using (IGpuFrame third = device.BeginFrame())
|
||||
{
|
||||
replacement = Assert.IsType<RecordingGpuBuffer>(buffers.Publish(
|
||||
third, 2, rebuiltPose, []).Buffer);
|
||||
}
|
||||
|
||||
Assert.True(firstSlot.IsDisposed);
|
||||
Assert.False(secondSlot.IsDisposed);
|
||||
Assert.False(replacement.IsDisposed);
|
||||
Assert.True(buffers.LastStats.TopologyUploaded);
|
||||
buffers.Dispose();
|
||||
Assert.True(secondSlot.IsDisposed);
|
||||
Assert.True(replacement.IsDisposed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InvalidDynamicSlotsAndUnsupportedBindingFailBeforePublishing()
|
||||
{
|
||||
using var device = new RecordingGpuDevice();
|
||||
using var buffers = new DirectionalShadowTransformBufferSet(device);
|
||||
using IGpuFrame frame = device.BeginFrame();
|
||||
Matrix4x4[] pose = [Matrix4x4.Identity, Matrix4x4.Identity];
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
buffers.Publish(frame, 1, pose, [1, 1]));
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
buffers.Publish(frame, 1, pose, [2]));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
buffers.Publish(
|
||||
frame,
|
||||
1,
|
||||
pose,
|
||||
[],
|
||||
[],
|
||||
denseRefresh: false,
|
||||
bindingSizeBytes: 64u));
|
||||
|
||||
Assert.Equal(0, buffers.BufferCount);
|
||||
Assert.Empty(device.OfKind<GpuRecordedHostStorageVisibility>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConnectedDenseDemandPublishesBeyondFormer65536CeilingInOneBinding()
|
||||
{
|
||||
using var device = new RecordingGpuDevice();
|
||||
using var buffers = new DirectionalShadowTransformBufferSet(device);
|
||||
var transforms = new Matrix4x4[68_395];
|
||||
transforms[^1] = Matrix4x4.CreateTranslation(68_394f, 2f, 3f);
|
||||
using IGpuFrame frame = device.BeginFrame();
|
||||
|
||||
WorldTransformFrameSlice slice = buffers.Publish(
|
||||
frame,
|
||||
topologyBuildSequence: 68_395,
|
||||
transforms,
|
||||
ReadOnlySpan<int>.Empty);
|
||||
|
||||
Assert.Equal(68_395u, slice.InstanceCount);
|
||||
Assert.True(slice.IsValidFor(frame));
|
||||
Assert.Equal(WorldTransformCapacityPolicy.InitialBindingSizeBytes,
|
||||
slice.BindingSizeBytes);
|
||||
Assert.Same(Assert.Single(device.CreatedBuffers), slice.Buffer);
|
||||
}
|
||||
|
||||
private static void AssertMatrixBitsEqual(
|
||||
Matrix4x4 expected,
|
||||
Matrix4x4 actual)
|
||||
{
|
||||
ReadOnlySpan<byte> expectedBits = MemoryMarshal.AsBytes(
|
||||
MemoryMarshal.CreateReadOnlySpan(ref expected, 1));
|
||||
ReadOnlySpan<byte> actualBits = MemoryMarshal.AsBytes(
|
||||
MemoryMarshal.CreateReadOnlySpan(ref actual, 1));
|
||||
Assert.True(expectedBits.SequenceEqual(actualBits));
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue