[Fix, Test] (ShaderTranspiler, GLImpl, ProgramState, DirectVulkan): keep fp64 where the backend consumes it natively

This commit is contained in:
2026-08-22 00:57:06 -04:00
parent e4f41e0fd3
commit d4247db6c3
23 changed files with 761 additions and 181 deletions
@@ -491,6 +491,14 @@ TEST_F(TranslationCacheTest, L1KeyMovesWithEveryInputThatMovesTheSpirv) {
v.enableSpirvValidation = true;
variants.emplace_back("enableSpirvValidation", BuildSpirvTranslationKey(v));
}
{ // CompileEnv::ConsumesFloat64Natively(): the fp64 tail of SanitizeAndOptimizeBinary is
// skipped under it, so the SAME GLSL yields modules with real doubles under one answer
// and demoted, storage-block-flattened ones under the other. The one backend capability
// bit in this key, and the only one allowed in without changing what glslang produces.
SpirvTranslationKeyInputs v = base;
v.nativeFloat64 = true;
variants.emplace_back("nativeFloat64", BuildSpirvTranslationKey(v));
}
// ---- inputs the WIDENED payload pulled into the key ----
// They cannot move a word of the generated SPIR-V, but they do shape the reflection the
// payload now carries, so they have to split the key. This is the group that would go
@@ -599,6 +607,45 @@ TEST_F(TranslationCacheTest, TwoBackendsCompilingTheSameGlslShareOneL1Entry) {
EXPECT_TRUE(BuildSpirvTranslationKey(onA) == BuildSpirvTranslationKey(onB));
}
// The ONE capability bit that breaks that sharing, and the two halves of why it is placed where
// it is. It must NOT move the front-end fingerprint - glslang parses, reflects and generates a
// `double` identically under it, and L1c (the parse-verdict memo) keys on that same fingerprint
// and would take a false miss per backend for nothing. It MUST move the L1 key, because L1's
// payload is the module AFTER SanitizeAndOptimizeBinary and the fp64 tail of that chain is
// exactly what this bit gates.
TEST_F(TranslationCacheTest, NativeFloat64IsOutOfTheFrontendFingerprintAndInsideTheL1Key) {
CompileEnv none; // no backend at all
CompileEnv emulated; // a backend without the feature
CompileEnv nativeEnv; // a backend with it
emulated.backend = BackendType::DirectVulkan;
nativeEnv.backend = BackendType::DirectVulkan;
nativeEnv.params.SupportsShaderFloat64 = true;
// No backend answers FALSE: the demoted module is the one that works everywhere, so a
// standalone compile gets it.
EXPECT_FALSE(none.ConsumesFloat64Natively());
EXPECT_FALSE(emulated.ConsumesFloat64Natively());
EXPECT_TRUE(nativeEnv.ConsumesFloat64Natively());
EXPECT_EQ(ComputeFrontendCompileEnvFingerprint(emulated), ComputeFrontendCompileEnvFingerprint(nativeEnv))
<< "the fp64 capability leaked into the front-end fingerprint";
EXPECT_NE(ComputeCompileEnvFingerprint(emulated), ComputeCompileEnvFingerprint(nativeEnv))
<< "the whole-environment fingerprint has to notice it - it is a DynamicBackendParameters "
"field, hashed by object representation";
const Vector<SpirvTranslationKeyInputs::Stage> stages{{GL_VERTEX_SHADER, kVertexSource},
{GL_FRAGMENT_SHADER, kFragmentSource}};
SpirvTranslationKeyInputs demoted = BaselineSpirvInputs(stages);
demoted.frontendFingerprint = ComputeFrontendCompileEnvFingerprint(emulated);
demoted.nativeFloat64 = emulated.ConsumesFloat64Natively();
SpirvTranslationKeyInputs kept = BaselineSpirvInputs(stages);
kept.frontendFingerprint = ComputeFrontendCompileEnvFingerprint(nativeEnv);
kept.nativeFloat64 = nativeEnv.ConsumesFloat64Natively();
EXPECT_FALSE(BuildSpirvTranslationKey(demoted) == BuildSpirvTranslationKey(kept))
<< "one L1 entry would then describe two different module sets";
}
// The other direction, one case per input that was KEPT. Each is a limit the front end
// really consumes - everything BuildTBuiltInResource copies into TBuiltInResource, plus the
// two inputs to the reflection vertex-attrib limit - so each must still split the key.