using AcDream.App.Rendering; using Xunit; namespace AcDream.App.Tests.Rendering; /// /// Campaign V slice V2 (2026-07-27): pure-CPU proof of /// 's bookkeeping — the handle→slot /// allocator each Campaign V-touched renderer (WbDrawDispatcher, /// EnvCellRenderer, TerrainModernRenderer, ParticleRenderer) owns to back its /// own binding=9 GL texture table. /// public class GlBindlessHandleTableTests { [Fact] public void GetOrAdd_FirstHandle_AssignsSlotZero_AndMarksDirty() { var table = new GlBindlessHandleTable(); uint slot = table.GetOrAdd(0xDEADBEEFu); Assert.Equal(0u, slot); Assert.True(table.Dirty); Assert.Equal(new ulong[] { 0xDEADBEEFu }, table.Handles.ToArray()); } [Fact] public void GetOrAdd_SameHandleTwice_ReturnsSameSlot_AndDoesNotDuplicate() { var table = new GlBindlessHandleTable(); uint first = table.GetOrAdd(111ul); table.MarkFlushed(); uint second = table.GetOrAdd(111ul); Assert.Equal(first, second); Assert.False(table.Dirty); // no NEW handle was registered Assert.Single(table.Handles.ToArray()); } [Fact] public void GetOrAdd_DistinctHandles_AssignStableIncreasingSlots() { var table = new GlBindlessHandleTable(); uint a = table.GetOrAdd(1ul); uint b = table.GetOrAdd(2ul); uint c = table.GetOrAdd(3ul); // Re-querying an already-registered handle must not shift anyone else's slot. uint aAgain = table.GetOrAdd(1ul); Assert.Equal(0u, a); Assert.Equal(1u, b); Assert.Equal(2u, c); Assert.Equal(a, aAgain); Assert.Equal(new ulong[] { 1ul, 2ul, 3ul }, table.Handles.ToArray()); } [Fact] public void ZeroHandle_IsRegisteredLikeAnyOther_NotSpecialCased() { // The pre-V2 behaviour let a batch/pass carry a literal zero bindless // handle through to the shader unchanged (an existing "no texture" // edge case some batches hit). V2 must reproduce that bit-for-bit: a // zero handle gets a real slot whose table entry is uvec2(0,0) — the // same value the shader would have received directly before V2. var table = new GlBindlessHandleTable(); uint slot = table.GetOrAdd(0ul); Assert.Equal(0u, table.Handles[(int)slot]); } [Fact] public void MarkFlushed_ClearsDirty_UntilNextNewHandle() { var table = new GlBindlessHandleTable(); table.GetOrAdd(42ul); Assert.True(table.Dirty); table.MarkFlushed(); Assert.False(table.Dirty); table.GetOrAdd(42ul); // already known — must NOT re-dirty Assert.False(table.Dirty); table.GetOrAdd(43ul); // genuinely new — must re-dirty Assert.True(table.Dirty); } [Fact] public void GetOrAdd_GrowsPastInitialCapacity_WithoutLosingEarlierSlots() { var table = new GlBindlessHandleTable(); const int count = 200; // exceeds the 64-entry initial backing array for (int i = 0; i < count; i++) { uint slot = table.GetOrAdd((ulong)i + 1000ul); Assert.Equal((uint)i, slot); } for (int i = 0; i < count; i++) Assert.Equal((ulong)i + 1000ul, table.Handles[i]); } }