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); } }