using System.Numerics;
using AcDream.App.Rendering.Gpu;
namespace AcDream.App.Tests.Rendering.Gpu;
///
/// Campaign V slice V0 — the test double every later renderer-port slice asserts
/// against. If the double misreports ordering or ring alignment, the migration
/// slices inherit false confidence, so it gets its own tests.
///
public sealed class RecordingGpuDeviceTests
{
[Fact]
public void FramePassAndDrawCallsAreRecordedInSubmissionOrder()
{
using RecordingGpuDevice device = new();
IGpuPipeline pipeline = device.CreatePipeline(new GpuPipelineDescription
{
Name = "mesh-opaque",
Shaders = new GpuShaderSet("mesh_modern"),
VertexLayout = GpuVertexLayout.WorldMesh,
});
IGpuBuffer indirect = device.CreateBuffer(new GpuBufferDescription(
"indirect", 4096, GpuBufferUsage.Indirect, GpuMemoryResidency.HostWritable));
device.Clear();
using (IGpuFrame frame = device.BeginFrame())
{
using (IGpuPassEncoder pass = frame.BeginPass(
GpuPassDescription.BackbufferClear("world", Vector4.Zero, sampleCount: 1)))
{
pass.BindPipeline(pipeline);
pass.SetCullMode(GpuCullMode.None);
pass.MultiDrawIndexedIndirect(indirect, offsetBytes: 0, drawCount: 12, strideBytes: 20);
}
frame.End();
}
Assert.Collection(
device.Calls,
call => Assert.Equal(new GpuRecordedFrameBegin(1, 0), call),
call => Assert.Equal(new GpuRecordedPassBegin("world", 1), call),
call => Assert.Equal(new GpuRecordedPipelineBind("mesh-opaque"), call),
call => Assert.Equal(new GpuRecordedCullMode(GpuCullMode.None), call),
call => Assert.Equal(new GpuRecordedMultiDrawIndirect("indirect", 0, 12, 20), call),
call => Assert.Equal(new GpuRecordedPassEnd("world"), call),
call => Assert.Equal(new GpuRecordedFrameEnd(1), call));
}
[Fact]
public void RingAllocationsAreAlignedForTheirUsageAndReadableAfterWriting()
{
using RecordingGpuDevice device = new();
uint storageAlignment = device.Capabilities.MinStorageBufferOffsetAlignment;
using IGpuFrame frame = device.BeginFrame();
GpuRingAllocation first = frame.AllocateRing(12, GpuRingUsage.Indirect);
Assert.Equal(0u, first.OffsetBytes);
Assert.Equal(12, first.Data.Length);
GpuRingAllocation second = frame.AllocateRing(64, GpuRingUsage.Storage);
Assert.Equal(0u, second.OffsetBytes % storageAlignment);
Assert.True(second.OffsetBytes >= 12);
Span transforms = second.AsSpan();
Assert.Equal(1, transforms.Length);
transforms[0] = Matrix4x4.CreateTranslation(1f, 2f, 3f);
frame.End();
// The bytes a renderer writes are the bytes a driver would read; a test can
// therefore verify upload content without a GPU.
ReadOnlySpan ring = device.RingBytes;
Matrix4x4 written = System.Runtime.InteropServices.MemoryMarshal.Read(
ring.Slice((int)second.OffsetBytes, 64));
Assert.Equal(Matrix4x4.CreateTranslation(1f, 2f, 3f), written);
}
[Fact]
public void RingIsRewoundEachFrameSoPerFrameDataDoesNotAccumulate()
{
using RecordingGpuDevice device = new();
using (IGpuFrame first = device.BeginFrame())
{
first.AllocateRing(256, GpuRingUsage.Storage);
first.End();
}
uint afterFirst = device.RingBytesAllocated;
using (IGpuFrame second = device.BeginFrame())
{
GpuRingAllocation allocation = second.AllocateRing(256, GpuRingUsage.Storage);
Assert.Equal(0u, allocation.OffsetBytes);
second.End();
}
Assert.Equal(afterFirst, device.RingBytesAllocated);
}
[Fact]
public void OverlargeRingRequestThrowsRatherThanTruncating()
{
using RecordingGpuDevice device = new(ringCapacityBytes: 1024);
using IGpuFrame frame = device.BeginFrame();
// Silently shortening an allocation would corrupt the frame invisibly.
Assert.Throws(() =>
{
frame.AllocateRing(4096, GpuRingUsage.Storage);
});
}
[Fact]
public void OverlappingFramesAreRejected()
{
using RecordingGpuDevice device = new();
using IGpuFrame frame = device.BeginFrame();
Assert.Throws(device.BeginFrame);
}
[Fact]
public void FrameSlotsAlternateAcrossTwoFramesInFlight()
{
using RecordingGpuDevice device = new();
int[] slots = new int[4];
for (int i = 0; i < slots.Length; i++)
{
using IGpuFrame frame = device.BeginFrame();
slots[i] = frame.SlotIndex;
frame.End();
}
Assert.Equal([0, 1, 0, 1], slots);
Assert.Equal(0, device.OpenFrameCount);
}
[Fact]
public void ReleasedTextureSlotsAreRecycledRatherThanLeaked()
{
using RecordingGpuDevice device = new();
IGpuSampler sampler = device.CreateSampler(GpuSamplerDescription.WorldRepeat);
IGpuTexture texture = device.CreateTexture(new GpuTextureDescription(
"wall", GpuTextureKind.Texture2DArray, GpuTextureFormat.Bc1Unorm, 64, 64, 4, 1));
int liveBefore = device.LiveTextureSlotCount;
GpuTextureSlot slot = device.RegisterTexture(texture, sampler);
Assert.True(slot.IsAssigned);
Assert.Equal(liveBefore + 1, device.LiveTextureSlotCount);
device.ReleaseTextureSlot(slot);
Assert.Equal(liveBefore, device.LiveTextureSlotCount);
GpuTextureSlot reused = device.RegisterTexture(texture, sampler);
Assert.Equal(slot.Index, reused.Index);
}
[Fact]
public void ReleasingAnUnassignedSlotIsRejected()
{
using RecordingGpuDevice device = new();
Assert.Throws(() => device.ReleaseTextureSlot(GpuTextureSlot.Unassigned));
}
[Fact]
public void DefaultTextureSlotIsRegisteredAndUsable()
{
using RecordingGpuDevice device = new();
// Renderers needing a fallback take this, rather than assuming slot 0
// resolves to something sensible.
Assert.True(device.DefaultTextureSlot.IsAssigned);
}
[Fact]
public void SamplersAreDeduplicatedByValue()
{
using RecordingGpuDevice device = new();
IGpuSampler first = device.CreateSampler(GpuSamplerDescription.WorldRepeat);
IGpuSampler second = device.CreateSampler(GpuSamplerDescription.WorldRepeat);
IGpuSampler other = device.CreateSampler(GpuSamplerDescription.UiNearest);
Assert.Same(first, second);
Assert.NotSame(first, other);
}
[Fact]
public void QueuedDeviceActionsRunOnlyWhenProcessed()
{
using RecordingGpuDevice device = new();
int ran = 0;
device.QueueDeviceAction(() => ran++);
Assert.Equal(0, ran);
device.ProcessDeviceActions();
Assert.Equal(1, ran);
device.ProcessDeviceActions();
Assert.Equal(1, ran);
}
[Fact]
public void DisposingAFrameClosesItExactlyOnce()
{
using RecordingGpuDevice device = new();
IGpuFrame frame = device.BeginFrame();
frame.End();
frame.Dispose();
Assert.Single(device.OfKind());
Assert.Equal(0, device.OpenFrameCount);
}
}