using System; using AcDream.App.Rendering.Gpu.Vk; namespace AcDream.App.Tests.Rendering.Gpu.Vk; /// /// Campaign V slice V5 — the parts of the active Vulkan probe that can be /// checked without a device. /// /// The probe's submission path needs a driver and is covered by the manual /// "Vulkan boots to a clear colour" gate. Its verdict — did the /// readback really contain the colour we cleared to? — is pure, and it is the /// step most worth pinning: a probe that accepts zeroed memory would report a /// pass on a device that rendered nothing at all. /// public sealed class VulkanActiveDeviceProbeTests { private static byte[] Filled(byte r, byte g, byte b, byte a) { int pixels = (int)(VulkanActiveDeviceProbe.ProbeExtent * VulkanActiveDeviceProbe.ProbeExtent); var buffer = new byte[pixels * 4]; for (int i = 0; i < buffer.Length; i += 4) { buffer[i] = r; buffer[i + 1] = g; buffer[i + 2] = b; buffer[i + 3] = a; } return buffer; } private static byte[] Expected() => Filled( VulkanActiveDeviceProbe.ExpectedClearRgba[0], VulkanActiveDeviceProbe.ExpectedClearRgba[1], VulkanActiveDeviceProbe.ExpectedClearRgba[2], VulkanActiveDeviceProbe.ExpectedClearRgba[3]); /// /// Every channel of the probe colour differs from the others and none is 0 /// or 255, so zeroed or saturated memory cannot accidentally match. /// [Fact] public void TheProbeColourCannotBeMatchedByZeroedOrSaturatedMemory() { ReadOnlySpan colour = VulkanActiveDeviceProbe.ExpectedClearRgba; Assert.Equal(4, colour.Length); foreach (byte channel in colour) { Assert.NotEqual(0, channel); Assert.NotEqual(255, channel); } Assert.Throws( () => VulkanActiveDeviceProbe.VerifyClearColour(Filled(0, 0, 0, 0))); Assert.Throws( () => VulkanActiveDeviceProbe.VerifyClearColour(Filled(255, 255, 255, 255))); } [Fact] public void TheExpectedClearColourIsAccepted() { VulkanActiveDeviceProbe.VerifyClearColour(Expected()); } /// /// The clear colour is specified as a float and quantised by the /// implementation, so one least-significant bit of slack is allowed — and /// exactly one. /// [Fact] public void OneBitOfQuantisationSlackIsAllowedButTwoIsNot() { byte[] colour = VulkanActiveDeviceProbe.ExpectedClearRgba.ToArray(); VulkanActiveDeviceProbe.VerifyClearColour( Filled((byte)(colour[0] + 1), colour[1], colour[2], colour[3])); VulkanActiveDeviceProbe.VerifyClearColour( Filled((byte)(colour[0] - 1), colour[1], colour[2], colour[3])); Assert.Throws( () => VulkanActiveDeviceProbe.VerifyClearColour( Filled((byte)(colour[0] + 2), colour[1], colour[2], colour[3]))); } /// /// A driver that clears only the first tile, or that returns a row-padded /// copy, must fail here rather than at the V7 differential — which is why /// every pixel is compared and not a sample. /// [Fact] public void ASinglyWrongPixelAnywhereIsRejected() { byte[] pixels = Expected(); int lastPixel = pixels.Length - 4; pixels[lastPixel + 1] ^= 0xFF; Assert.Throws( () => VulkanActiveDeviceProbe.VerifyClearColour(pixels)); } [Fact] public void AShortReadbackIsRejectedRatherThanPartiallyChecked() { Assert.Throws( () => VulkanActiveDeviceProbe.VerifyClearColour(new byte[64])); } }