using AcDream.App.Rendering; using Xunit; namespace AcDream.App.Tests.Rendering; /// /// Campaign V slice V6l: the portal depth mask is the one renderer whose two arms /// do NOT share a shader source, and this is the tripwire that stops them /// drifting. /// /// Every other RHI arm in the campaign compiles the same GLSL file the GL /// arm does. This one cannot: its clip planes would have to travel in the /// TerrainClip uniform block at binding 2, and on GL that binding is held /// globally by ClipFrame for terrain — a portal draw that rebound it would /// leave every later terrain draw in the frame reading the wrong region. So the /// GL arm keeps its inline program, the RHI arm compiles /// Rendering/Shaders/portal_depth.vert, and the numbers that decide where /// depth lands are asserted to appear in both. /// /// Deleted with the GL arm at V11, when the inline string goes. /// public class PortalDepthShaderParityTests { private static string PortalDepthVertexSource() => File.ReadAllText(Path.Combine( AppContext.BaseDirectory, "Rendering", "Shaders", "portal_depth.vert")); [Fact] public void BothArmsPunchWithRetailsFarZConstant() { // 0.99999988 is retail's own value at the tail of // D3DPolyRender::DrawPortalPolyInternal (0x0059bc90). It decides where // the punched depth lands, so the two arms agreeing on it is the whole // point of this file. const string FarZ = "0.99999988"; Assert.Contains(FarZ, PortalDepthMaskRenderer.VertSrc, StringComparison.Ordinal); Assert.Contains(FarZ, PortalDepthVertexSource(), StringComparison.Ordinal); } [Fact] public void BothArmsApplyTheSameEyeCappedMarkBias() { // #129's capped bias: min(bias, cap / max(w*w, 1e-6)), then z -= bias*w. // Spelled with different identifier names on the two arms — the RHI arm // reads the shared push block's uParamA/uParamB — so the assertion is on // the SHAPE that decides the result rather than on the text. string rhi = PortalDepthVertexSource(); Assert.Contains("max(clipPos.w * clipPos.w, 1e-6)", PortalDepthMaskRenderer.VertSrc, StringComparison.Ordinal); Assert.Contains("max(clipPos.w * clipPos.w, 1e-6)", rhi, StringComparison.Ordinal); Assert.Contains("clipPos.z -= biasNdc * clipPos.w", PortalDepthMaskRenderer.VertSrc, StringComparison.Ordinal); Assert.Contains("clipPos.z -= biasNdc * clipPos.w", rhi, StringComparison.Ordinal); } [Fact] public void BothArmsClipAgainstEightHalfPlanes() { // The plane budget is ClipFrame.MaxPlanes and GL's guaranteed // GL_MAX_CLIP_DISTANCES; a shader that looped to a different number // would clip a differently-shaped region than the one the CPU published. Assert.Equal(8, ClipFrame.MaxPlanes); Assert.Contains("for (int i = 0; i < 8; i++)", PortalDepthMaskRenderer.VertSrc, StringComparison.Ordinal); Assert.Contains("for (int i = 0; i < 8; i++)", PortalDepthVertexSource(), StringComparison.Ordinal); } [Fact] public void TheRhiArmReadsTheClipPlanesFromTheSharedTerrainClipBlock() { string rhi = PortalDepthVertexSource(); Assert.Contains("uniform TerrainClip", rhi, StringComparison.Ordinal); Assert.Contains("binding = 2", rhi, StringComparison.Ordinal); // And the block is the one ClipFrame already packs for terrain and sky: // an int count padded to 16 bytes, then eight vec4 planes. Assert.Equal(144, ClipFrame.TerrainUboBytes); Assert.Equal(2u, ClipFrame.TerrainClipUboBinding); } }