From 5248b8b74605396065eb625ead9731ed70a77d1a Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 10 Aug 2026 10:06:26 -0400 Subject: [PATCH] [Test] (MG_Test): replay a real Iris shader pair through the whole async frontend, both phases and both quirk states --- .../MG_Test/Program/AsyncSpirvPhaseTest.cpp | 609 ++++++++++++++++++ 1 file changed, 609 insertions(+) diff --git a/MobileGL/MG_Test/Program/AsyncSpirvPhaseTest.cpp b/MobileGL/MG_Test/Program/AsyncSpirvPhaseTest.cpp index 8ba7e184..bcd91279 100644 --- a/MobileGL/MG_Test/Program/AsyncSpirvPhaseTest.cpp +++ b/MobileGL/MG_Test/Program/AsyncSpirvPhaseTest.cpp @@ -29,9 +29,13 @@ #include #include +#include +#include #include #include +#include + #include "Config.h" #include "Includes.h" #include "Init.h" @@ -946,3 +950,608 @@ TEST_F(AsyncSpirvPhaseTest, AsyncOffAndAsyncOnProduceIdenticalSpirvAndShadow) { EXPECT_EQ(asyncOffsets, syncOffsets); EXPECT_EQ(GetError(), GL_NO_ERROR); } + +// --------------------------------------------------------------------------------------- +// The program that crashed the device, replayed end to end through the async frontend +// --------------------------------------------------------------------------------------- +// +// This is the shape that killed DirectVulkan on an Adreno 830: an Iris-transformed shader +// pair whose vertex stage declares inputs that Iris does not bind through +// glBindAttribLocation and that the shader itself never reads. Before the io-resolver fix +// such an input reached SPIR-V with no Location decoration, which is invalid +// (VUID-StandaloneSpirv-Location-04916); Adreno rejected the whole pipeline with +// VK_ERROR_UNKNOWN at the first rainy-world draw while lavapipe accepted it, so no desktop +// gate could see it. +// +// The sources are the real thing, lifted verbatim from the extracted BSL corpus: a vertex +// shader carrying the victim's condition - it DECLARES mc_Entity and mc_midTexCoord without +// reading either, and Iris binds neither - paired with the iris_FragData0 fragment shader +// that consumes its varyings. It is not the device's exact pack revision (that build is not +// in the corpus), but it is a real Iris-transformed program with the same partial-binding +// shape, driven through the same call sequence. +// +// FRONTEND ONLY, deliberately: nothing here touches a backend or a driver. The replay stops +// at the SPIR-V the frontend hands over, and validates it with the same validator whose VUID +// the driver enforces. +namespace { + const char* kIrisWeatherVs = R"GLSL(#version 330 core +// Generated by glsl-transformer +uniform mat4 iris_ProjMat; +in vec3 iris_Position; +uniform mat4 iris_ModelViewMatInverse; +uniform mat4 iris_ProjMatInverse; +uniform mat3 iris_NormalMat; +uniform mat4 iris_LightmapTextureMatrix; +uniform mat4 iris_TextureMat; +uniform mat4 iris_ModelViewMat; +in vec4 iris_Color; +uniform vec4 iris_ColorModulator; +in ivec2 iris_UV2; +in vec2 iris_UV0; +uniform float iris_FogDensity; +uniform float iris_FogStart; +uniform float iris_FogEnd; +uniform vec4 iris_FogColor; +struct iris_FogParameters { +vec4 color; +float density; +float start; +float end; +float scale; +}; +iris_FogParameters iris_Fog = iris_FogParameters(iris_FogColor, iris_FogDensity, iris_FogStart, iris_FogEnd, 1.0f / (iris_FogEnd - iris_FogStart)); +vec4 iris_FrontColor; +out float iris_FogFragCoord; +const int shadowMapResolution = 2048; +const float shadowDistance = 256.0f; +const float shadowMapBias = 1.0f - 25.6f / shadowDistance; +const float sunPathRotation = -40.0f; +const float ambientOcclusionLevel = 1.0f; +out vec2 texCoord, lmCoord; +out vec3 normal; +out vec3 sunVec, upVec, eastVec; +out vec4 color; +uniform int worldTime; +uniform float frameTimeCounter; +uniform float timeAngle; +uniform vec3 cameraPosition; +uniform mat4 gbufferModelView, gbufferModelViewInverse; +uniform int frameCounter; +uniform float viewWidth, viewHeight; +in vec4 mc_Entity; +in vec4 mc_midTexCoord; +float time = frameTimeCounter * 1.0f; +uniform float framemod8; +uniform float framemod2; +vec2 jitterOffsets8[8] = vec2[8](vec2(0.125f, -0.375f), vec2(-0.125f, 0.375f), vec2(0.625f, 0.125f), vec2(0.375f, -0.625f), vec2(-0.625f, 0.625f), vec2(-0.875f, -0.125f), vec2(0.375f, -0.875f), vec2(0.875f, 0.875f)); +vec2 jitterOffsets2[2] = vec2[2](vec2(1.0f, 0.0f), vec2(0.0f, 1.0f)); +uniform vec3 iris_ChunkOffset; +mat4 _iris_internal_translate(vec3 offset) { +return mat4(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, offset.x, offset.y, offset.z, 1.0f); +} +vec4 ftransform() { +return (iris_ProjMat * (iris_ModelViewMat * _iris_internal_translate(iris_ChunkOffset))) * vec4(iris_Position, 1.0f); +} +vec2 TAAJitter(vec2 coord, float w) { +vec2 offset = jitterOffsets8[int(framemod8)] * (w / vec2(viewWidth, viewHeight)); +return coord + offset; +} +void main() { +iris_FogFragCoord = 0.0f; +texCoord = (iris_TextureMat * vec4(iris_UV0, 0.0f, 1.0f)).xy; +lmCoord = (iris_LightmapTextureMatrix * vec4(iris_UV2, 0.0f, 1.0f)).xy; +lmCoord = clamp((lmCoord - 0.03125f) * 1.06667f, vec2(0.0f), vec2(0.9333f, 1.0f)); +normal = normalize(iris_NormalMat * vec3(0.0f, 0.0f, 1.0f)); +color = (iris_Color * iris_ColorModulator); +const vec2 sunRotationData = vec2(cos(sunPathRotation * 0.01745329251994f), -sin(sunPathRotation * 0.01745329251994f)); +float ang = fract(timeAngle - 0.25f); +ang = (ang + (cos(ang * 3.14159265358979f) * -0.5f + 0.5f - ang) / 3.0f) * 6.28318530717959f; +sunVec = normalize((gbufferModelView * vec4(vec3(-sin(ang), cos(ang) * sunRotationData) * 2000.0f, 1.0f)).xyz); +upVec = normalize(gbufferModelView[1].xyz); +eastVec = normalize(gbufferModelView[0].xyz); +gl_Position = ftransform(); +gl_Position.xy = TAAJitter(gl_Position.xy, gl_Position.w); +} +)GLSL"; + + const char* kIrisWeatherFs = R"GLSL(#version 330 core +// Generated by glsl-transformer +uniform mat4 iris_ProjMat; +uniform mat4 iris_ModelViewMatInverse; +uniform mat4 iris_ProjMatInverse; +uniform mat3 iris_NormalMat; +uniform mat4 iris_LightmapTextureMatrix; +uniform mat4 iris_TextureMat; +uniform mat4 iris_ModelViewMat; +uniform vec4 iris_ColorModulator; +uniform float iris_FogDensity; +uniform float iris_FogStart; +uniform float iris_FogEnd; +uniform vec4 iris_FogColor; +struct iris_FogParameters { +vec4 color; +float density; +float start; +float end; +float scale; +}; +iris_FogParameters iris_Fog = iris_FogParameters(iris_FogColor, iris_FogDensity, iris_FogStart, iris_FogEnd, 1.0f / (iris_FogEnd - iris_FogStart)); +uniform float iris_currentAlphaTest; +layout(location = 0) out vec4 iris_FragData0; +in float iris_FogFragCoord; +const int shadowMapResolution = 2048; +const float shadowDistance = 256.0f; +const float shadowMapBias = 1.0f - 25.6f / shadowDistance; +const float sunPathRotation = -40.0f; +const float ambientOcclusionLevel = 1.0f; +in vec2 texCoord, lmCoord; +in vec3 normal; +in vec3 sunVec, upVec, eastVec; +in vec4 color; +uniform int bedrockLevel; +uniform int frameCounter; +uniform int isEyeInWater; +uniform int moonPhase; +uniform int worldTime; +uniform float blindFactor, darknessFactor, nightVision; +uniform float cloudHeight; +uniform float endFlashIntensity; +uniform float far, near; +uniform float frameTimeCounter; +uniform float rainStrength; +uniform float screenBrightness; +uniform float shadowFade; +uniform float timeAngle, timeBrightness; +uniform float viewWidth, viewHeight; +uniform ivec2 eyeBrightnessSmooth; +uniform vec3 cameraPosition; +uniform vec3 relativeEyePosition; +uniform mat4 gbufferProjectionInverse; +uniform mat4 gbufferModelViewInverse; +uniform mat4 shadowProjection; +uniform mat4 shadowModelView; +uniform sampler2D gtexture; +uniform sampler2D noisetex; +uniform int heldBlockLightValue, heldBlockLightValue2; +float eBS = eyeBrightnessSmooth.y / 240.0f; +float sunVisibility = clamp(dot(sunVec, upVec) * 10.0f + 0.5f, 0.0f, 1.0f); +float moonVisibility = clamp(dot(-sunVec, upVec) * 10.0f + 0.5f, 0.0f, 1.0f); +float time = frameTimeCounter * 1.0f; +vec3 lightVec = sunVec * ((timeAngle < 0.5325f || timeAngle > 0.9675f) ? 1.0f : -1.0f); +float GetLuminance(vec3 color) { +return dot(color, vec3(0.299f, 0.587f, 0.114f)); +} +vec3 blocklightColSqrt = vec3(255, 212, 160) * 0.85f / 255.0f; +vec3 blocklightCol = blocklightColSqrt * blocklightColSqrt; +vec3 lightMorning = vec3(255, 160, 80) * 1.2f / 255.0f; +vec3 lightDay = vec3(196, 220, 255) * 1.4f / 255.0f; +vec3 lightEvening = vec3(255, 160, 80) * 1.2f / 255.0f; +vec3 ambientMorning = vec3(255, 204, 144) * 0.35f / 255.0f; +vec3 ambientDay = vec3(120, 172, 255) * 0.6f / 255.0f; +vec3 ambientEvening = vec3(255, 204, 144) * 0.35f / 255.0f; +float moonPhaseMultiplier[8] = float[8](1.0f, 0.875f, 0.75f, 0.625f, 0.5f, 0.625f, 0.75f, 0.875f); +float nightMult = 0.3f * moonPhaseMultiplier[moonPhase]; +vec3 lightNight = vec3(96, 192, 255) * 1.0f * nightMult / 255.0f; +vec3 ambientNight = vec3(96, 192, 255) * 0.6f * nightMult / 255.0f; +uniform float isDesert, isMesa, isCold, isSwamp, isMushroom, isSavanna, isJungle; +vec4 weatherRain = vec4(vec3(176, 224, 255) / 255.0f, 1.0f) * 1.2f; +vec4 weatherCold = vec4(vec3(216, 240, 255) / 255.0f, 1.0f) * 1.2f; +vec4 weatherDesert = vec4(vec3(255, 232, 180) / 255.0f, 1.0f) * 1.2f; +vec4 weatherBadlands = vec4(vec3(255, 216, 176) / 255.0f, 1.0f) * 1.2f; +vec4 weatherSwamp = vec4(vec3(200, 224, 160) / 255.0f, 1.0f) * 1.2f; +vec4 weatherMushroom = vec4(vec3(216, 216, 255) / 255.0f, 1.0f) * 1.2f; +vec4 weatherSavanna = vec4(vec3(224, 224, 224) / 255.0f, 1.0f) * 1.2f; +vec4 weatherJungle = vec4(vec3(176, 232, 232) / 255.0f, 1.0f) * 1.2f; +float weatherWeight = clamp(isCold + isDesert + isMesa + isSwamp + isMushroom + isSavanna + isJungle, 0.0f, 1.0f); +vec4 weatherCol = mix(weatherRain, (weatherCold * isCold + weatherDesert * isDesert + weatherBadlands * isMesa + weatherSwamp * isSwamp + weatherMushroom * isMushroom + weatherSavanna * isSavanna + weatherJungle * isJungle) / max(weatherWeight, 1.0E-4f), weatherWeight); +float mefade = 1.0f - clamp(abs(timeAngle - 0.5f) * 8.0f - 1.5f, 0.0f, 1.0f); +float dfade = 1.0f - pow(1.0f - timeBrightness, 1.5f); +vec3 lightSun = mix(mix(lightMorning, lightEvening, mefade), lightDay, dfade); +vec3 ambientSun = mix(mix(ambientMorning, ambientEvening, mefade), ambientDay, dfade); +vec3 lightColRaw = mix(lightNight, lightSun, sunVisibility); +vec3 lightColSqrt = mix(lightColRaw, dot(lightColRaw, vec3(0.299f, 0.587f, 0.114f)) * weatherCol.rgb, rainStrength); +vec3 lightCol = lightColSqrt * lightColSqrt; +vec3 ambientColRaw = mix(ambientNight, ambientSun, sunVisibility); +vec3 ambientColSqrt = mix(ambientColRaw, dot(ambientColRaw, vec3(0.299f, 0.587f, 0.114f)) * weatherCol.rgb, rainStrength); +vec3 ambientCol = ambientColSqrt * ambientColSqrt; +vec3 minLightColSqrt = vec3(128, 128, 128) * 0.5f / 255.0f; +vec3 minLightCol = minLightColSqrt * minLightColSqrt * 0.04f; +float sunSkyVisibility = clamp(dot(sunVec, upVec) * 2.0f + 0.5f, 0.0f, 1.0f); +vec3 lightSkyColRaw = mix(lightNight, lightSun, sunSkyVisibility); +vec3 lightSkyColSqrt = mix(lightSkyColRaw, dot(lightSkyColRaw, vec3(0.299f, 0.587f, 0.114f)) * weatherCol.rgb, rainStrength); +vec3 lightSkyCol = lightSkyColSqrt * lightSkyColSqrt; +vec3 skyColSqrt = vec3(96, 160, 255) * 1.0f / 255.0f; +vec3 fogColSqrt = vec3(96, 160, 255) * 1.0f / 255.0f; +vec3 skyCol = skyColSqrt * skyColSqrt; +vec3 fogCol = fogColSqrt * fogColSqrt; +vec3 ToNDC(vec3 pos) { +vec4 iProjDiag = vec4(gbufferProjectionInverse[0].x, gbufferProjectionInverse[1].y, gbufferProjectionInverse[2].zw); +vec3 p3 = pos * 2.0f - 1.0f; +vec4 viewPos = iProjDiag * p3.xyzz + gbufferProjectionInverse[3]; +return viewPos.xyz / viewPos.w; +} +vec3 ToWorld(vec3 pos) { +return mat3(gbufferModelViewInverse) * pos + gbufferModelViewInverse[3].xyz; +} +vec3 ToShadow(vec3 pos) { +vec3 shadowpos = mat3(shadowModelView) * pos + shadowModelView[3].xyz; +return (vec3((shadowProjection)[0].x, (shadowProjection)[1].y, shadowProjection[2].z) * (shadowpos) + (shadowProjection)[3].xyz); +} +float fogDensity = 1.0f * mix(1.0f, (1.0f * isCold + 1.0f * (isDesert + isMesa + isSavanna) + 1.0f * (isSwamp + isMushroom + isJungle)) / max(weatherWeight, 1.0E-4f), weatherWeight); +vec3 GetFogColor(vec3 viewPos) { +vec3 nViewPos = normalize(viewPos); +float lViewPos = length(viewPos) / 64.0f; +lViewPos = 1.0f - exp(-lViewPos * lViewPos); +float VoU = clamp(dot(nViewPos, upVec), -1.0f, 1.0f); +float VoL = clamp(dot(nViewPos, sunVec), -1.0f, 1.0f); +float density = 0.4f; +float nightDensity = 1.0f; +float weatherDensity = 1.5f; +float groundDensity = 0.08f * (4.0f - 3.0f * sunSkyVisibility) * (10.0f * rainStrength * rainStrength + 1.0f); +float exposure = exp2(timeBrightness * 0.75f - 0.75f); +float nightExposure = exp2(-3.5f); +float baseGradient = exp(-(VoU * 0.5f + 0.5f) * 0.5f / density); +float groundVoU = clamp(-VoU * 0.5f + 0.5f, 0.0f, 1.0f); +float ground = 1.0f - exp(-groundDensity / groundVoU); +vec3 fog = skyCol; +fog *= baseGradient / (1.0f * 1.0f); +fog = fog / sqrt(fog * fog + 1.0f) * exposure * sunSkyVisibility * (1.0f * 1.0f); +float sunMix = pow((VoL * 0.5f + 0.5f) * clamp(1.0f - VoU, 0.0f, 1.0f), 2.0f - sunSkyVisibility) * pow(1.0f - timeBrightness * 0.6f, 3.0f); +float horizonMix = pow(1.0f - abs(VoU), 2.5f) * 0.125f; +float lightMix = (1.0f - (1.0f - sunMix) * (1.0f - horizonMix)) * lViewPos; +vec3 lightFog = pow(lightSun, vec3(4.0f - sunSkyVisibility)) * baseGradient; +lightFog = lightFog / (1.0f + lightFog * rainStrength); +fog = mix(sqrt(fog * (1.0f - lightMix)), sqrt(lightFog), lightMix); +fog *= fog; +float nightGradient = exp(-(VoU * 0.5f + 0.5f) * 0.35f / nightDensity); +vec3 nightFog = lightNight * lightNight * nightGradient * nightExposure; +fog = mix(nightFog, fog, sunSkyVisibility * sunSkyVisibility); +float rainGradient = exp(-(VoU * 0.5f + 0.5f) * 0.125f / weatherDensity); +vec3 weatherFog = weatherCol.rgb * weatherCol.rgb; +weatherFog *= GetLuminance(ambientCol / (weatherFog)) * (0.2f * sunSkyVisibility + 0.2f); +fog = mix(fog, weatherFog * rainGradient, rainStrength); +float exteriorFactor = eBS; +fog = mix(minLightCol * 0.5f, fog * exteriorFactor, exteriorFactor); +fog *= clamp((cameraPosition.y - bedrockLevel + 6.0f) / 8.0f, 0.0f, 1.0f); +return fog; +} +void NormalFog(inout vec3 color, vec3 viewPos) { +float viewLength = length(viewPos); +vec4 worldPos = gbufferModelViewInverse * vec4(viewPos, 1.0f); +worldPos.xyz /= worldPos.w; +float fogFactor = viewLength; +float fog = viewLength * fogDensity / 1024.0f; +float clearDay = sunSkyVisibility * (1.0f - rainStrength); +float exteriorFactor = eBS; +float fogDensityMult = mix(1.0f, 1.5f, rainStrength) / mix(1.0f / 4.0f, 1.0f, clearDay); +fogDensityMult = mix(1.0f, fogDensityMult * exteriorFactor, exteriorFactor); +fog *= fogDensityMult; +float fogDampen = 0.3f * rainStrength + 0.5f; +fog = min(fog, (fog - fogDampen) * 0.25f + fogDampen); +fog *= exp2(-max(worldPos.y + cameraPosition.y - 62, 0.0f) / exp2(7.0f)); +fog = 1.0f - exp(-2.0f * pow(fog, 0.35f * clearDay * exteriorFactor + 1.25f)); +vec3 fogColor = GetFogColor(viewPos); +color = mix(color, fogColor, fog); +} +void BlindFog(inout vec3 color, vec3 viewPos) { +float fog = length(viewPos) * max(blindFactor * 0.2f, darknessFactor * 0.075f); +fog = (1.0f - exp(-6.0f * fog * fog * fog)) * max(blindFactor, darknessFactor); +color = mix(color, vec3(0.0f), fog); +} +vec3 denseFogColor[2] = vec3[2](vec3(1.0f, 0.3f, 0.01f), vec3(0.1f, 0.16f, 0.2f)); +void DenseFog(inout vec3 color, vec3 viewPos) { +float fog = length(viewPos) * 0.5f; +fog = (1.0f - exp(-4.0f * fog * fog * fog)); +color = mix(color, denseFogColor[isEyeInWater - 2], fog); +} +vec2 ApplyDynamicHandlight(vec2 lightmap, vec3 worldPos) { +float heldLightValue = max(float(heldBlockLightValue), float(heldBlockLightValue2)); +if (heldLightValue == 0.0f) return lightmap; +vec3 heldLightPos = worldPos + relativeEyePosition + vec3(0.0f, 0.5f, 0.0f); +float handlight = min((heldLightValue - 2.0f * length(heldLightPos)) / 15.0f, 0.9333f); +lightmap.x = log2(exp2(lightmap.x * 32.0f) + exp2(handlight * 32.0f)) / 32.0f; +return lightmap; +} +uniform sampler2DShadow shadowtex0; +uniform sampler2DShadow shadowtex1; +uniform sampler2D shadowcolor0; +vec2 shadowOffsets[9] = vec2[9](vec2(0.0f, 0.0f), vec2(0.0f, 1.0f), vec2(0.7f, 0.7f), vec2(1.0f, 0.0f), vec2(0.7f, -0.7f), vec2(0.0f, -1.0f), vec2(-0.7f, -0.7f), vec2(-1.0f, 0.0f), vec2(-0.7f, 0.7f)); +float texture2DShadow(sampler2DShadow shadowtex, vec3 shadowPos) { +return vec4(texture(shadowtex, shadowPos)).x; +} +vec3 DistortShadow(vec3 shadowPos, float distortFactor) { +shadowPos.xy /= distortFactor; +shadowPos.z *= 0.2f; +shadowPos = shadowPos * 0.5f + 0.5f; +return shadowPos; +} +float InterleavedGradientNoise() { +float n = 52.9829189f * fract(0.06711056f * gl_FragCoord.x + 0.00583715f * gl_FragCoord.y); +return fract(n + frameCounter * 1.618f); +} +vec3 SampleFilteredShadow(vec3 shadowPos, float offset, float subsurface) { +float shadow0 = 0.0f; +for (int i = 0; i < 9; i++) { +vec2 shadowOffset = shadowOffsets[i] * offset; +shadow0 += texture2DShadow(shadowtex0, vec3(shadowPos.st + shadowOffset, shadowPos.z)); +} +shadow0 /= 9.0f; +vec3 shadowCol = vec3(0.0f); +if (shadow0 < 0.999f) { +for (int i = 0; i < 9; i++) { +vec2 shadowOffset = shadowOffsets[i] * offset; +vec3 shadowColSample = texture(shadowcolor0, shadowPos.st + shadowOffset).rgb * texture2DShadow(shadowtex1, vec3(shadowPos.st + shadowOffset, shadowPos.z)); +shadowCol += shadowColSample; +} +shadowCol /= 9.0f; +} +shadow0 *= mix(shadow0, 1.0f, subsurface); +shadowCol *= shadowCol; +return clamp(shadowCol * (1.0f - shadow0) + shadow0, vec3(0.0f), vec3(16.0f)); +} +vec3 GetShadow(vec3 worldPos, vec3 normal, float NoL, float subsurface, float skylight) { +vec3 rawShadowPos = ToShadow(worldPos); +float distb = sqrt(dot(rawShadowPos.xy, rawShadowPos.xy)); +float distortFactor = distb * shadowMapBias + (1.0f - shadowMapBias); +vec3 shadowPos = DistortShadow(rawShadowPos, distortFactor); +float shadowFade = clamp(100.0f - 100.0f * max(abs(rawShadowPos.x), abs(rawShadowPos.y)), 0.0f, 1.0f); +shadowFade *= clamp(skylight * 1000.0f - 1.0f, 0.0f, 1.0f); +if (shadowFade < 1.0E-5f) return vec3(1.0f); +float bias = 0.0f; +float offset = 1.0f / shadowMapResolution; +float biasFactor = sqrt(1.0f - NoL * NoL) / NoL; +float distortBias = distortFactor * shadowDistance / 256.0f; +distortBias *= 8.0f * distortBias; +float distanceBias = sqrt(dot(worldPos.xyz, worldPos.xyz)) * 0.005f; +bias = (distortBias * biasFactor + distanceBias + 0.05f) / shadowMapResolution; +if (subsurface > 0.0f) { +float blurFadeIn = clamp(distb * 20.0f, 0.0f, 1.0f); +float blurFadeOut = 1.0f - clamp(distb * 10.0f - 2.0f, 0.0f, 1.0f); +float blurMult = blurFadeIn * blurFadeOut * (1.0f - NoL); +blurMult = blurMult * 1.5f + 1.0f; +offset = 7.0E-4f * blurMult; +bias = 2.0E-4f; +} +shadowPos.z -= bias; +vec3 shadow = SampleFilteredShadow(shadowPos, offset, subsurface); +shadow = mix(vec3(1.0f), shadow, shadowFade); +return shadow; +} +void GetLighting( +inout vec3 albedo, +out vec3 shadow, +vec3 viewPos, +vec3 worldPos, +vec3 normal, +vec2 lightmap, +float smoothLighting, +float NoL, +float vanillaDiffuse, +float parallaxShadow, +float emission, +float subsurface, +float basicSubsurface +) { +float skylightSqr = lightmap.y * lightmap.y; +if (NoL > 0.0f || basicSubsurface > 0.0f) { +shadow = GetShadow(worldPos, normal, NoL, basicSubsurface, lightmap.y); +} +shadow *= parallaxShadow; +shadow = max(shadow, vec3(0.0f)); +NoL = clamp(NoL * 1.01f - 0.01f, 0.0f, 1.0f); +float scattering = 0.0f; +if (basicSubsurface > 0.0f) { +float VoL = clamp(dot(normalize(viewPos.xyz), lightVec) * 0.5f + 0.5f, 0.0f, 1.0f); +scattering = pow(VoL, 16.0f) * (1.0f - rainStrength) * basicSubsurface * shadowFade; +NoL = mix(NoL, 1.0f, sqrt(basicSubsurface) * 0.7f); +NoL = mix(NoL, 1.0f, scattering); +} +vec3 fullShadow = max(shadow * NoL, vec3(0.0f)); +float shadowMult = (1.0f - 0.95f * rainStrength) * shadowFade; +vec3 sceneLighting = mix(ambientCol * lightmap.y, lightCol, fullShadow * shadowMult); +sceneLighting *= skylightSqr * (1.0f + scattering * shadow); +float newLightmap = pow(lightmap.x, 10.0f) * 1.6f + lightmap.x * 0.6f; +vec3 blockLighting = blocklightCol * newLightmap * newLightmap; +vec3 minLighting = minLightCol * (1.0f - skylightSqr); +vec3 albedoNormalized = normalize(albedo.rgb + 1.0E-5f); +emission = pow(emission, max(1.0f, 1.0f)); +vec3 emissiveLighting = mix(albedoNormalized, vec3(1.0f), emission * 0.5f); +emissiveLighting *= emission * 4.0f; +float lightFlatten = clamp(1.0f - pow(1.0f - emission, 128.0f), 0.0f, 1.0f); +vanillaDiffuse = mix(vanillaDiffuse, 1.0f, lightFlatten); +smoothLighting = mix(smoothLighting, 1.0f, lightFlatten); +float nightVisionLighting = nightVision * 0.25f; +float albedoBrightness = max(max(albedo.r, albedo.g), albedo.b); +albedo.rgb /= 1.0f + albedoBrightness * 0.25f * (1.0f - lightFlatten); +albedo *= max(sceneLighting + blockLighting + emissiveLighting + nightVisionLighting + minLighting, vec3(0.0f)); +albedo *= vanillaDiffuse * smoothLighting * smoothLighting; +float desatAmount = 1.0f - sqrt(max(sqrt(length(fullShadow / 3.0f)) * lightmap.y, lightmap.y)) * sunVisibility * (1.0f - rainStrength * 0.7f); +desatAmount *= smoothstep(0.25f, 1.0f, (1.0f - lightmap.x) * (1.0f - lightmap.x)) * (1.0f - lightFlatten); +desatAmount = 1.0f - desatAmount; +vec3 desatNight = normalize(lightNight * lightNight + 1.0E-6f); +vec3 desatWeather = normalize(weatherCol.rgb * weatherCol.rgb + 1.0E-6f); +float desatNWMix = (1.0f - sunVisibility) * (1.0f - rainStrength); +vec3 desatColor = mix(desatWeather, desatNight, desatNWMix); +desatColor = mix(vec3(0.4f), desatColor, sqrt(lightmap.y)) * 1.7f; +vec3 desatAlbedo = mix(albedo, GetLuminance(albedo) * desatColor, 1.0f - 1.5f * 0.4f); +albedo = mix(desatAlbedo, albedo, desatAmount); +} +uniform float framemod8; +uniform float framemod2; +vec2 jitterOffsets8[8] = vec2[8](vec2(0.125f, -0.375f), vec2(-0.125f, 0.375f), vec2(0.625f, 0.125f), vec2(0.375f, -0.625f), vec2(-0.625f, 0.625f), vec2(-0.875f, -0.125f), vec2(0.375f, -0.875f), vec2(0.875f, 0.875f)); +vec2 jitterOffsets2[2] = vec2[2](vec2(1.0f, 0.0f), vec2(0.0f, 1.0f)); +vec2 TAAJitter(vec2 coord, float w) { +vec2 offset = jitterOffsets8[int(framemod8)] * (w / vec2(viewWidth, viewHeight)); +return coord + offset; +} +void main() { +vec4 albedo = texture(gtexture, texCoord) * color; +{ +vec2 lightmap = clamp(lmCoord, vec2(0.0f), vec2(1.0f)); +vec3 screenPos = vec3(gl_FragCoord.xy / vec2(viewWidth, viewHeight), gl_FragCoord.z); +vec3 viewPos = ToNDC(vec3(TAAJitter(screenPos.xy, -0.5f), screenPos.z)); +vec3 worldPos = ToWorld(viewPos); +lightmap = ApplyDynamicHandlight(lightmap, worldPos); +albedo.rgb = pow(albedo.rgb, vec3(2.2f)); +float NoL = 1.0f; +float NoU = clamp(dot(normal, upVec), -1.0f, 1.0f); +float NoE = clamp(dot(normal, eastVec), -1.0f, 1.0f); +float vanillaDiffuse = (0.25f * NoU + 0.75f) + (0.667f - abs(NoE)) * (1.0f - abs(NoU)) * 0.15f; +vanillaDiffuse *= vanillaDiffuse; +vec3 shadow = vec3(0.0f); +GetLighting(albedo.rgb, shadow, viewPos, worldPos, normal, lightmap, 1.0f, NoL, 1.0f, 1.0f, 0.0f, 0.0f, 0.0f); +albedo.rgb = sqrt(max(albedo.rgb, vec3(0.0f))); +} +iris_FragData0 = albedo; +if (!(iris_FragData0.a > iris_currentAlphaTest)) { +discard; +} +} +)GLSL"; + + // Iris's partial pattern: it binds the vanilla attributes and leaves the pack's extras + // (mc_Entity, mc_midTexCoord) unbound - which, with neither of them read by the shader, is + // exactly the inactive-and-unbound case that reached SPIR-V undecorated. + struct BoundAttribute { + const char* name; + GLint location; + }; + const BoundAttribute kIrisWeatherBindings[] = { + {"iris_Position", 0}, {"iris_Color", 1}, {"iris_UV0", 2}, {"iris_UV2", 3}, + }; + + // Drives Iris's own call sequence and returns the linked program name. + GLuint ReplayIrisWeatherProgram() { + const GLuint vs = CreateShader(GL_VERTEX_SHADER); + ShaderSource(vs, 1, &kIrisWeatherVs, nullptr); + CompileShader(vs); + const GLuint fs = CreateShader(GL_FRAGMENT_SHADER); + ShaderSource(fs, 1, &kIrisWeatherFs, nullptr); + CompileShader(fs); + + // Iris reads the log and the status for every shader, in that order. + for (const GLuint shader : {vs, fs}) { + GLint logLength = 0; + GetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength); + if (logLength > 0) { + std::vector log(static_cast(logLength)); + GLsizei written = 0; + GetShaderInfoLog(shader, logLength, &written, log.data()); + } + GLint compileStatus = GL_FALSE; + GetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus); + EXPECT_EQ(compileStatus, GL_TRUE) << "shader " << shader << " failed to compile"; + } + + const GLuint program = CreateProgram(); + AttachShader(program, vs); + AttachShader(program, fs); + for (const BoundAttribute& binding : kIrisWeatherBindings) { + BindAttribLocation(program, static_cast(binding.location), binding.name); + } + LinkProgram(program); + return program; + } +} // namespace + +TEST_F(AsyncSpirvPhaseTest, IrisWeatherProgramReplaysCleanlyThroughBothPhases) { + for (const Bool optimisticQuirk : {false, true}) { + const AsyncModeScope async(true); + const MG_Config::QuirkOverride savedQuirk = MG_Config::Features.AsyncOptimisticShaderStatus; + MG_Config::Features.AsyncOptimisticShaderStatus = + optimisticQuirk ? MG_Config::QuirkOverride::ForceOn : MG_Config::QuirkOverride::ForceOff; + + const GLuint program = ReplayIrisWeatherProgram(); + + // ---- phase A: LINK_STATUS is the join, and it must be truthful ---- + GLint linkStatus = GL_FALSE; + GetProgramiv(program, GL_LINK_STATUS, &linkStatus); + ASSERT_EQ(linkStatus, GL_TRUE) << QueryProgramInfoLog(program) + << " (optimistic quirk " << (optimisticQuirk ? "on" : "off") << ")"; + + // Iris queries uniforms straight after the status read; these are phase-A answers. + EXPECT_GE(GetUniformLocation(program, "iris_ModelViewMatrix"), -1); + EXPECT_GE(GetUniformLocation(program, "iris_ProjectionMatrix"), -1); + EXPECT_GE(GetUniformLocation(program, "texture"), -1); + + // ---- the API bindings survived exactly ---- + for (const BoundAttribute& binding : kIrisWeatherBindings) { + EXPECT_EQ(GetAttribLocation(program, binding.name), binding.location) << binding.name; + } + + // ---- settle phase B through a gated getter and check what it published ---- + const auto& object = Object(program); + ASSERT_NE(object, nullptr); + const auto& modules = object->GetGeneratedSpirv(); // joins phase B + EXPECT_TRUE(object->IsSpirvComplete()); + EXPECT_TRUE(object->GetSpirvStatus()); + ASSERT_FALSE(modules.empty()); + EXPECT_GT(object->GetUBOSize(), 0u) << "the uniform shadow should have been allocated"; + EXPECT_NE(object->GetUniformOffset(0), MG_State::GLState::ProgramObject::kInvalidUniformOffset); + + // ---- every module the frontend hands over must be valid SPIR-V ---- + // NOTE ON WHAT THIS DOES AND DOES NOT CATCH. It validates the modules AFTER spirv-opt, + // which is what a backend actually receives - but AggressiveDCE deletes an input that + // nothing reads, so for a merely-unused attribute this assertion cannot fail even with + // the io-resolver bug reinstated (verified by mutation). The discriminating gate for + // VUID-StandaloneSpirv-Location-04916 is + // ProgramUtilTest.PartiallyBoundVertexInputsAllReceiveALocation, which validates the + // RAW GlslangToSpv output before the optimizer can hide the defect. This assertion is + // still worth having: it is the end-to-end guarantee that whatever the frontend ships + // to a driver is valid, and it would catch a regression whose variable SURVIVES DCE - + // which is precisely what the device victim did. + spvtools::SpirvTools tools(SPV_ENV_VULKAN_1_1); + String validatorMessages; + tools.SetMessageConsumer([&validatorMessages](spv_message_level_t, const char*, const spv_position_t&, + const char* message) { + if (message != nullptr) validatorMessages += String(message) + "\n"; + }); + for (SizeT i = 0; i < modules.size(); ++i) { + validatorMessages.clear(); + EXPECT_TRUE(tools.Validate(modules[i])) + << "module " << i << " is not valid SPIR-V - Adreno rejects the pipeline for this while " + << "lavapipe tolerates it:\n" << validatorMessages; + } + + // ---- every vertex input carries a unique Location ---- + const Int vsIndex = object->GetShaderIndexByStage(ShaderStage::Vertex); + ASSERT_GE(vsIndex, 0); + const auto& vsModule = modules[static_cast(vsIndex)]; + constexpr unsigned kOpDecorate = 71, kOpVariable = 59; + constexpr unsigned kDecorationBuiltIn = 11, kDecorationLocation = 30; + constexpr unsigned kStorageClassInput = 1; + std::map locationById; + std::set builtInIds; + std::vector inputIds; + for (SizeT i = 5; i < vsModule.size();) { + const unsigned wordCount = vsModule[i] >> 16; + const unsigned opcode = vsModule[i] & 0xFFFFu; + ASSERT_GT(wordCount, 0u); + if (i + wordCount > vsModule.size()) break; + if (opcode == kOpDecorate && wordCount >= 4 && vsModule[i + 2] == kDecorationLocation) { + locationById[vsModule[i + 1]] = vsModule[i + 3]; + } else if (opcode == kOpDecorate && wordCount >= 3 && vsModule[i + 2] == kDecorationBuiltIn) { + builtInIds.insert(vsModule[i + 1]); + } else if (opcode == kOpVariable && wordCount >= 4 && vsModule[i + 3] == kStorageClassInput) { + inputIds.push_back(vsModule[i + 2]); + } + i += wordCount; + } + std::set usedLocations; + SizeT checkedInputs = 0; + for (const unsigned id : inputIds) { + if (builtInIds.count(id) != 0) continue; + const auto it = locationById.find(id); + ASSERT_NE(it, locationById.end()) + << "a vertex input reached SPIR-V with no Location decoration (optimistic quirk " + << (optimisticQuirk ? "on" : "off") << ")"; + EXPECT_TRUE(usedLocations.insert(it->second).second) + << "two vertex inputs share location " << it->second; + ++checkedInputs; + } + EXPECT_GT(checkedInputs, 0u) << "no vertex inputs found; the scan proved nothing"; + + EXPECT_EQ(GetError(), GL_NO_ERROR); + MG_Config::Features.AsyncOptimisticShaderStatus = savedQuirk; + } +}