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