using System.Numerics; using AcDream.App.Rendering; using Xunit; namespace AcDream.App.Tests.Rendering; /// /// Phase U.3: CPU-side proof that packs the shared clip /// data in the EXACT std430 (mesh SSBO) / std140 (terrain UBO) byte layout the /// shaders read. A silent layout drift here would mis-clip at U.4 with no build /// error — these tests are the gate that catches it. /// /// Layout under test (mesh CellClip, std430): /// offset 0 : uint count /// offset 4 : uint _p0 (pad) /// offset 8 : uint _p1 (pad) /// offset 12 : uint _p2 (pad) /// offset 16 : vec4 planes[0] (16-byte vec4 stride) /// ... /// offset 16 + i*16 : vec4 planes[i] /// stride 144 bytes per slot. /// Terrain UBO (std140): int count at 0 (padded to 16), vec4 planes[8] at 16. /// public class ClipFrameLayoutTests { private static float ReadFloat(System.ReadOnlySpan b, int offset) => System.BitConverter.ToSingle(b.Slice(offset, 4)); private static uint ReadUInt(System.ReadOnlySpan b, int offset) => System.BitConverter.ToUInt32(b.Slice(offset, 4)); private static int ReadInt(System.ReadOnlySpan b, int offset) => System.BitConverter.ToInt32(b.Slice(offset, 4)); [Fact] public void LayoutConstants_MatchShaderStruct() { // CellClip: 16 (count + 3 pad uints) + 8*16 (vec4 planes) = 144. Assert.Equal(144, ClipFrame.CellClipStrideBytes); Assert.Equal(16, ClipFrame.CellClipPlanesOffset); Assert.Equal(8, ClipFrame.MaxPlanes); Assert.Equal(144, ClipFrame.TerrainUboBytes); // Binding contract: mesh clip regions on SSBO binding=2, terrain on UBO binding=2. Assert.Equal(2u, ClipFrame.MeshClipSsboBinding); Assert.Equal(2u, ClipFrame.TerrainClipUboBinding); } [Fact] public void NoClip_HasExactlyOneSlot_AllZeros_Count0() { var frame = ClipFrame.NoClip(); Assert.Equal(1, frame.SlotCount); var bytes = frame.RegionBytesForTest; Assert.Equal(ClipFrame.CellClipStrideBytes, bytes.Length); // 144 — exactly one slot // count == 0 ⇒ shader passes every plane (no-clip). Assert.Equal(0u, ReadUInt(bytes, 0)); // Every byte of the reserved no-clip slot is zero. foreach (var b in bytes) Assert.Equal(0, b); } [Fact] public void NoClip_TerrainBytes_Count0_AllZeros() { var frame = ClipFrame.NoClip(); var t = frame.TerrainBytesForTest; Assert.Equal(ClipFrame.TerrainUboBytes, t.Length); Assert.Equal(0, ReadInt(t, 0)); // count 0 ⇒ terrain ungated foreach (var b in t) Assert.Equal(0, b); } [Fact] public void AppendSlot_WritesCountAndPlanes_AtStd430Offsets() { var frame = ClipFrame.NoClip(); // Three distinct planes so each lands at a verifiable offset. var p0 = new Vector4(1f, 0f, 0f, 0.5f); var p1 = new Vector4(0f, 1f, 0f, 0.25f); var p2 = new Vector4(-1f, 0f, 0f, -0.75f); int slot = frame.AppendSlot(new[] { p0, p1, p2 }); Assert.Equal(1, slot); // slot 0 is the reserved no-clip; this is slot 1 Assert.Equal(2, frame.SlotCount); var bytes = frame.RegionBytesForTest; Assert.Equal(2 * ClipFrame.CellClipStrideBytes, bytes.Length); // two slots now int baseOff = slot * ClipFrame.CellClipStrideBytes; // 144 // count == 3 at offset 0 of the slot; the 3 pad uints stay zero. Assert.Equal(3u, ReadUInt(bytes, baseOff + 0)); Assert.Equal(0u, ReadUInt(bytes, baseOff + 4)); Assert.Equal(0u, ReadUInt(bytes, baseOff + 8)); Assert.Equal(0u, ReadUInt(bytes, baseOff + 12)); // planes[0..2] at offset 16, 32, 48 (vec4 stride 16). AssertPlaneAt(bytes, baseOff + 16, p0); AssertPlaneAt(bytes, baseOff + 32, p1); AssertPlaneAt(bytes, baseOff + 48, p2); // Slot 0 (the reserved no-clip) is untouched: still count 0. Assert.Equal(0u, ReadUInt(bytes, 0)); } [Fact] public void AppendSlot_EmptyPlaneList_PacksNoClipSlot_Count0() { var frame = ClipFrame.NoClip(); int slot = frame.AppendSlot(System.ReadOnlySpan.Empty); Assert.Equal(1, slot); var bytes = frame.RegionBytesForTest; Assert.Equal(0u, ReadUInt(bytes, slot * ClipFrame.CellClipStrideBytes)); // count 0 } [Fact] public void AppendSlot_ClampsToEightPlanes() { var frame = ClipFrame.NoClip(); var planes = new Vector4[12]; for (int i = 0; i < planes.Length; i++) planes[i] = new Vector4(i, 0f, 0f, 0f); int slot = frame.AppendSlot(planes); var bytes = frame.RegionBytesForTest; // Only MaxPlanes (8) are recorded in the count. Assert.Equal((uint)ClipFrame.MaxPlanes, ReadUInt(bytes, slot * ClipFrame.CellClipStrideBytes)); } [Fact] public void AppendSlot_FromClipPlaneSet_AxisAlignedSquare_PacksFourPlanes() { // A unit square in NDC → ClipPlaneSet with 4 convex planes. var cv = new CellView(); cv.Add(new ViewPolygon(new[] { new Vector2(-0.5f, -0.5f), new Vector2(0.5f, -0.5f), new Vector2(0.5f, 0.5f), new Vector2(-0.5f, 0.5f), })); var cps = ClipPlaneSet.From(cv); Assert.Equal(4, cps.Count); var frame = ClipFrame.NoClip(); int slot = frame.AppendSlot(cps); var bytes = frame.RegionBytesForTest; int baseOff = slot * ClipFrame.CellClipStrideBytes; Assert.Equal(4u, ReadUInt(bytes, baseOff + 0)); // Each packed plane must match the ClipPlaneSet's plane bit-for-bit. for (int i = 0; i < 4; i++) AssertPlaneAt(bytes, baseOff + ClipFrame.CellClipPlanesOffset + i * 16, cps.Planes[i]); } [Fact] public void SetTerrainClip_WritesCountAndPlanes_AtStd140Offsets() { var frame = ClipFrame.NoClip(); var p0 = new Vector4(0.3f, -0.4f, 0f, 0.1f); var p1 = new Vector4(-0.6f, 0.8f, 0f, -0.2f); frame.SetTerrainClip(new[] { p0, p1 }); var t = frame.TerrainBytesForTest; Assert.Equal(2, ReadInt(t, 0)); // int count at offset 0 AssertPlaneAt(t, ClipFrame.CellClipPlanesOffset + 0, p0); // planes start at 16 under std140 too AssertPlaneAt(t, ClipFrame.CellClipPlanesOffset + 16, p1); } private static void AssertPlaneAt(System.ReadOnlySpan bytes, int offset, Vector4 expected) { Assert.Equal(expected.X, ReadFloat(bytes, offset + 0), 6); Assert.Equal(expected.Y, ReadFloat(bytes, offset + 4), 6); Assert.Equal(expected.Z, ReadFloat(bytes, offset + 8), 6); Assert.Equal(expected.W, ReadFloat(bytes, offset + 12), 6); } }