using AcDream.App.Rendering; namespace AcDream.App.Tests.Rendering; public sealed class TerrainTextureTilingTableTests { [Fact] public void Build_MapsDatRepeatCountsToAtlasLayers() { var table = TerrainTextureTilingTable.Build( [ (Layer: 0u, RepeatCount: 4u), (Layer: 7u, RepeatCount: 2u), (Layer: 32u, RepeatCount: 8u), ]); Assert.Equal(TerrainTextureTilingTable.LayerCapacity, table.Length); Assert.Equal(4f, table[0]); Assert.Equal(2f, table[7]); Assert.Equal(8f, table[32]); } [Fact] public void Build_DefaultsOnlyUnusedLayersToOne() { var table = TerrainTextureTilingTable.Build( [ (Layer: 3u, RepeatCount: 0u), ]); Assert.Equal(1f, table[2]); Assert.Equal(0f, table[3]); Assert.Equal(1f, table[4]); } [Fact] public void Build_RejectsLayersTheShaderCannotAddress() { var ex = Assert.Throws(() => TerrainTextureTilingTable.Build( [ (Layer: (uint)TerrainTextureTilingTable.LayerCapacity, RepeatCount: 1u), ])); Assert.Contains("exceeds the shader capacity", ex.Message); } [Fact] public void ModernShader_AppliesTheOwningLayersRepeatCountToEveryTerrainSample() { string shaderPath = Path.Combine( AppContext.BaseDirectory, "Rendering", "Shaders", "terrain_modern.frag"); string shader = File.ReadAllText(shaderPath); // Campaign V slice V6f-2 moved the table out of a loose // `uniform float uTexTiling[36]` and into a std140 block at // GpuBindingModel.UniformTerrainTiling. The element type and count are // what every sample site below depends on, so both are still pinned — // and the binding number is now pinned too, because the shader and // GpuBindingModel have to agree. Assert.Contains("binding = 3) uniform TerrainTiling {", shader); Assert.Contains("float uTexTiling[36];", shader); Assert.Contains("baseUV * terrainTiling(pOverlay0.z)", shader); Assert.Contains("baseUV * terrainTiling(pOverlay1.z)", shader); Assert.Contains("baseUV * terrainTiling(pOverlay2.z)", shader); Assert.Contains("baseUV * terrainTiling(pRoad0.z)", shader); Assert.Contains("vBaseUV * terrainTiling(vBaseTexIdx)", shader); Assert.DoesNotContain("const float TILE", shader); } /// /// Campaign V slice V6f-2: the CPU writer and the std140 block must agree on /// where each value lands. /// /// This is the specific hazard the move to a uniform buffer /// introduced. std140 pads every element of a scalar array to 16 bytes, so a /// writer that packs 36 floats tightly produces a buffer in which the shader /// reads layer 0's value for layers 0-3, layer 4's for 4-7, and so on. That /// renders — it just tiles the wrong textures, in a scene where nine of ten /// layers use a tiling factor of 1 and the difference is invisible until the /// tenth appears. Nothing else in the suite would catch it, so the two /// constants are pinned to the layout rule directly. /// [Fact] public void UniformBufferMatchesTheStd140LayoutTheShaderDeclares() { Assert.Equal(16, TerrainTextureTilingTable.UniformElementStrideBytes); Assert.Equal(576, TerrainTextureTilingTable.UniformBufferBytes); Assert.Equal( TerrainTextureTilingTable.LayerCapacity * TerrainTextureTilingTable.UniformElementStrideBytes, TerrainTextureTilingTable.UniformBufferBytes); // A std140 float array element is padded, never packed. If this ever // equals sizeof(float) the writer above has been "simplified" into the // bug this test exists for. Assert.NotEqual(sizeof(float), TerrainTextureTilingTable.UniformElementStrideBytes); } }