From 46fbd837b35aa540561b5fe5ee14dd15c28b73e0 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Wed, 12 Aug 2026 06:41:54 -0400 Subject: [PATCH] [Fix, Test] (MG_State, MG_Impl, MG_IntegrationTest): a double uniform initializer no longer reads zero --- .../MG_Impl/GLImpl/Program/GL_Program.cpp | 7 +++- .../Scenarios/DoublePrecisionScenario.cpp | 39 +++++++++++++++++++ .../GLState/ProgramState/ProgramObject.cpp | 12 +++++- 3 files changed, 55 insertions(+), 3 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp index eeea664b..91da6f17 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp @@ -850,7 +850,12 @@ namespace MobileGL::MG_Impl::GLImpl { // vector per column - while the value glGetUniform* must return is tightly packed // columns * rows floats. Only mat4 is the same either way; every other shape needs the // padding undone, and the readback has to undo exactly what UniformMatrixfv_Object put - // there. Returns false when `ttype` is not a float matrix (nothing to unpack). + // there. Returns false when there is nothing here to unpack. + // + // A DOUBLE matrix is declined not because it is laid out differently - it is not, the + // demotion makes a dmat4 a mat4 in the shader and a mat4-shaped slot here - but because it + // is ROUTED differently: the caller's component-by-component EbtDouble branch has to widen + // each float back to the queried type, and it undoes the same padding itself. Bool TryGatherFloatMatrixColumns(const glslang::TType* ttype, const char* pBase, void* params) { if (ttype == nullptr || !ttype->isMatrix() || ttype->getBasicType() == glslang::EbtDouble) return false; const Int columns = ttype->getMatrixCols(); diff --git a/MobileGL/MG_IntegrationTest/Scenarios/DoublePrecisionScenario.cpp b/MobileGL/MG_IntegrationTest/Scenarios/DoublePrecisionScenario.cpp index fdac891c..cb782e31 100644 --- a/MobileGL/MG_IntegrationTest/Scenarios/DoublePrecisionScenario.cpp +++ b/MobileGL/MG_IntegrationTest/Scenarios/DoublePrecisionScenario.cpp @@ -314,6 +314,45 @@ void main() { EXPECT_EQ(glGetError(), static_cast(GL_NO_ERROR)); } + TEST_F(DoublePrecisionScenario, ADoubleUniformKeepsItsDeclaredInitializer) { + if (!Ready()) return; + // A declared initializer is seeded straight into the uniform shadow at link, and the + // seeding used to skip 64-bit floats outright ("no 32-bit shadow encoding") - which + // was true before the demotion and silently left every such uniform reading zero. + const char* source = R"(#version 430 core +layout(local_size_x = 1) in; +uniform double uSeeded = 2.5lf; +uniform dvec3 uSeededVector = dvec3(4.0lf, 5.0lf, 6.0lf); +layout(std430, binding = 0) buffer Output { + float g_out[]; +}; +void main() { + g_out[0] = float(uSeeded); + g_out[1] = float(uSeededVector.x); + g_out[2] = float(uSeededVector.y); + g_out[3] = float(uSeededVector.z); +} +)"; + const GLuint program = CompileComputeProgram(source); + ASSERT_NE(program, 0u) << m_buildLog; + + glUseProgram(program); + glDispatchCompute(1, 1, 1); + glMemoryBarrier(GL_BUFFER_UPDATE_BARRIER_BIT); + std::vector values(4, -1.0f); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, m_output); + glGetBufferSubData(GL_SHADER_STORAGE_BUFFER, 0, 4 * sizeof(float), values.data()); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0); + glUseProgram(0); + glDeleteProgram(program); + + EXPECT_FLOAT_EQ(values[0], 2.5f) << "scalar double initializer"; + EXPECT_FLOAT_EQ(values[1], 4.0f) << "dvec3 initializer .x"; + EXPECT_FLOAT_EQ(values[2], 5.0f) << "dvec3 initializer .y"; + EXPECT_FLOAT_EQ(values[3], 6.0f) << "dvec3 initializer .z"; + EXPECT_EQ(glGetError(), static_cast(GL_NO_ERROR)); + } + TEST_F(DoublePrecisionScenario, TheFp64ExtensionIsNotAdvertised) { if (!Ready()) return; // The shader above compiled, linked and ran without the extension string, which is diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 41f81b6f..f2844f08 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -165,10 +165,18 @@ namespace MobileGL::MG_State::GLState { const Int elements = init.arraySize; if (componentsPerElement <= 0 || elements <= 0) continue; - const Bool isFloat = init.basicType == glslang::EbtFloat || init.basicType == glslang::EbtFloat16; + // EbtDouble belongs with the floats now, not with the skipped types: every 64-bit + // float in a shader is narrowed to 32 bits before the module reaches a backend + // (ShaderTranspiler::DemoteFloat64Pass), so a `uniform double d = 1.5;` has exactly + // the 32-bit shadow encoding a `uniform float` does - and glslang already folded its + // value into floatValues, which is a vector either way. Leaving it out meant + // the initializer was silently dropped and the uniform came up zero. + const Bool isFloat = init.basicType == glslang::EbtFloat || + init.basicType == glslang::EbtFloat16 || + init.basicType == glslang::EbtDouble; const Bool isInt = init.basicType == glslang::EbtInt || init.basicType == glslang::EbtUint || init.basicType == glslang::EbtBool; - // Anything else (fp64, 64-bit integers) has no 32-bit shadow encoding here, and a + // Anything else (64-bit integers) has no 32-bit shadow encoding here, and a // half-written uniform is worse than an untouched one. if (!isFloat && !isInt) continue; const SizeT provided = isFloat ? init.floatValues.size() : init.intValues.size();