using System.Runtime.InteropServices; using AcDream.App.Rendering; namespace AcDream.App.Tests.Rendering; public sealed class ParticleBindlessInstanceTests { [Fact] public void BillboardGpuInstance_MatchesVertexAttributeAbi() { // Campaign V slice V2c (2026-07-27): TextureHandleLow/High (the split // halves of a raw 64-bit ARB_bindless_texture handle, 8 bytes) became // one TextureIndex (a binding=9 handle-table slot, 4 bytes), so the // struct shrank by 4 bytes; TextureIndex keeps TextureHandleLow's // former offset (64 — right after the four vec4 fields). Assert.Equal(68, Marshal.SizeOf()); Assert.Equal( new IntPtr(64), Marshal.OffsetOf( nameof(ParticleRenderer.BillboardGpuInstance.TextureIndex))); } [Fact] public void BillboardShaders_ConsumeOneBindlessTextureHandlePerInstance() { string shadersDirectory = Path.Combine( AppContext.BaseDirectory, "Rendering", "Shaders"); string vertex = File.ReadAllText(Path.Combine(shadersDirectory, "particle.vert")); string fragment = File.ReadAllText(Path.Combine(shadersDirectory, "particle.frag")); // Campaign V slice V2c: the per-instance attribute now carries a // binding=9 table slot, not the raw handle; particle.vert // reconstructs the SAME uvec2 handle via ACDREAM_TEXTURE_HANDLE // before handing it to particle.frag, so the fragment shader's half // (extension, reconstruction, varying type/name) is untouched. Assert.Contains("layout(location = 6) in uint aTextureIndex;", vertex); Assert.Contains("flat out uvec2 vTextureHandle;", vertex); Assert.Contains("#extension GL_ARB_bindless_texture : require", fragment); Assert.Contains("sampler2DArray(vTextureHandle)", fragment); Assert.DoesNotContain("uniform sampler2D uParticleTexture", fragment); } }