[Fix] (MG_Util/ShaderTranspiler, MG_State, MG_Impl/GLImpl, MG_Backend/DirectGLES): GL CTS uniform_block - coerce packed/shared block layouts to std140 at source preprocess (glslang rejects them when targeting SPIR-V; std140 is the only UBO layout the pipeline emits), GL-style block reflection (array "[0]" names, per-element struct-array expansion, unused members and declared-but-unread blocks stay active), vec4-padded GL_UNIFORM_BLOCK_DATA_SIZE, std140 array strides for struct-nested arrays (glslang reflects tight strides there), arrayed-block instances share the first instance member set, glDeleteShader-flagged names stay usable while attached, and backend ESSL emits against highp default precision so relaxed block members match across stages (KHR-GL33.shaders.uniform_block on llvmpipe: 659 Fail -> 828/828 Pass)

This commit is contained in:
2026-07-16 12:09:11 -04:00
parent 1cefb9780b
commit 254cf1dc21
11 changed files with 522 additions and 18 deletions
+229
View File
@@ -2053,3 +2053,232 @@ void main() {
EXPECT_EQ(readback, 9.0f); // untouched by the overlong write
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
// ---------------------------------------------------------------------------
// GL CTS KHR-GL33.shaders.uniform_block regression pack. MobileGL's SPIR-V
// pipeline lays every uniform block out as std140; the frontend implements the
// GL-visible consequences of that choice: packed/shared qualifiers compile (as
// std140), reflection uses GL naming ("arr[0]", per-element struct arrays),
// unused block members stay active, block sizes are vec4-padded, and array
// strides are std140 even for arrays nested inside struct members.
// ---------------------------------------------------------------------------
TEST_F(ProgramTest, UniformBlockPackedAndSharedLayoutsCompileAsStd140) {
const char* fsSource = R"(#version 330
layout(packed) uniform PackedBlock {
vec4 pv;
};
layout(shared, row_major) uniform SharedBlock {
float sf;
mat4 sm;
};
out vec4 o_color;
void main() {
o_color = pv + vec4(sf) + vec4(sm[0][0]);
})";
GLuint program = LinkVsFsProgram(kPassthroughCoordsVs, fsSource);
// The blocks land on the implementation's chosen layout: std140 offsets.
const GLuint pv = UniformIndexByName(program, "pv");
const GLuint sf = UniformIndexByName(program, "sf");
const GLuint sm = UniformIndexByName(program, "sm");
ASSERT_NE(pv, GL_INVALID_INDEX);
ASSERT_NE(sf, GL_INVALID_INDEX);
ASSERT_NE(sm, GL_INVALID_INDEX);
EXPECT_EQ(QueryUniformiv(program, pv, GL_UNIFORM_OFFSET), 0);
EXPECT_EQ(QueryUniformiv(program, sf, GL_UNIFORM_OFFSET), 0);
EXPECT_EQ(QueryUniformiv(program, sm, GL_UNIFORM_OFFSET), 16);
// The remaining qualifiers in the rewritten layout() list survive.
EXPECT_EQ(QueryUniformiv(program, sm, GL_UNIFORM_IS_ROW_MAJOR), 1);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
TEST_F(ProgramTest, UniformBlockReflectsUnusedMembersWithGLNamesAndPaddedSize) {
const char* fsSource = R"(#version 330
layout(std140) uniform Blk {
float used;
vec4 unusedArr[3];
ivec3 tail;
};
out vec4 o_color;
void main() {
o_color = vec4(used);
})";
GLuint program = LinkVsFsProgram(kPassthroughCoordsVs, fsSource);
const GLuint blockIndex = GetUniformBlockIndex(program, "Blk");
ASSERT_NE(blockIndex, GL_INVALID_INDEX);
// All three members are active (unusedArr and tail are never read), the array is
// reported under its GL name "unusedArr[0]", and both spellings resolve.
const GLuint used = UniformIndexByName(program, "used");
const GLuint unusedSuffixed = UniformIndexByName(program, "unusedArr[0]");
const GLuint unusedBare = UniformIndexByName(program, "unusedArr");
const GLuint tail = UniformIndexByName(program, "tail");
ASSERT_NE(used, GL_INVALID_INDEX);
ASSERT_NE(unusedSuffixed, GL_INVALID_INDEX);
ASSERT_NE(tail, GL_INVALID_INDEX);
EXPECT_EQ(unusedSuffixed, unusedBare);
char nameBuf[64] = "";
GLsizei nameLen = 0;
GLint arraySize = 0;
GLenum type = 0;
GetActiveUniform(program, unusedSuffixed, sizeof(nameBuf), &nameLen, &arraySize, &type, nameBuf);
EXPECT_STREQ(nameBuf, "unusedArr[0]");
EXPECT_EQ(arraySize, 3);
EXPECT_EQ(type, static_cast<GLenum>(GL_FLOAT_VEC4));
// std140 layout of the unused members.
EXPECT_EQ(QueryUniformiv(program, unusedSuffixed, GL_UNIFORM_OFFSET), 16);
EXPECT_EQ(QueryUniformiv(program, unusedSuffixed, GL_UNIFORM_ARRAY_STRIDE), 16);
EXPECT_EQ(QueryUniformiv(program, tail, GL_UNIFORM_OFFSET), 64);
// GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS agrees with the INDICES list and counts all members.
GLint activeInBlock = 0;
GetActiveUniformBlockiv(program, blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &activeInBlock);
ASSERT_EQ(activeInBlock, 3);
GLint indices[3] = {-1, -1, -1};
GetActiveUniformBlockiv(program, blockIndex, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, indices);
for (GLint index : indices) {
EXPECT_TRUE(index == static_cast<GLint>(used) || index == static_cast<GLint>(unusedSuffixed) ||
index == static_cast<GLint>(tail));
}
// The block ends with an ivec3 at offset 64 (unpadded end 76); the backend compiles
// the std140 block at its vec4-padded size, and the reported size must cover it or
// buffers sized from this query are too small to draw with.
GLint dataSize = 0;
GetActiveUniformBlockiv(program, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &dataSize);
EXPECT_EQ(dataSize, 80);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
TEST_F(ProgramTest, UniformBlockStructArrayExpandsPerElementWithStd140Strides) {
const char* fsSource = R"(#version 330
struct S {
ivec2 v[2];
float f;
};
layout(std140) uniform Blk2 {
S s[2];
} inst;
out vec4 o_color;
void main() {
o_color = vec4(inst.s[0].f);
})";
GLuint program = LinkVsFsProgram(kPassthroughCoordsVs, fsSource);
// ARB_program_interface_query naming: one entry per struct array element, prefixed
// with the BLOCK name (not the instance name), basic arrays suffixed with "[0]".
const GLuint v0 = UniformIndexByName(program, "Blk2.s[0].v[0]");
const GLuint f0 = UniformIndexByName(program, "Blk2.s[0].f");
const GLuint v1 = UniformIndexByName(program, "Blk2.s[1].v[0]");
const GLuint f1 = UniformIndexByName(program, "Blk2.s[1].f");
ASSERT_NE(v0, GL_INVALID_INDEX);
ASSERT_NE(f0, GL_INVALID_INDEX);
ASSERT_NE(v1, GL_INVALID_INDEX);
ASSERT_NE(f1, GL_INVALID_INDEX);
// std140: ivec2 v[2] rounds each element up to a vec4 (stride 16, NOT the tight 8
// glslang reflects for arrays nested inside a struct member); struct size rounds to
// 48, giving s[1] members a 48-byte bias.
EXPECT_EQ(QueryUniformiv(program, v0, GL_UNIFORM_OFFSET), 0);
EXPECT_EQ(QueryUniformiv(program, v0, GL_UNIFORM_ARRAY_STRIDE), 16);
EXPECT_EQ(QueryUniformiv(program, v0, GL_UNIFORM_SIZE), 2);
EXPECT_EQ(QueryUniformiv(program, f0, GL_UNIFORM_OFFSET), 32);
EXPECT_EQ(QueryUniformiv(program, v1, GL_UNIFORM_OFFSET), 48);
EXPECT_EQ(QueryUniformiv(program, f1, GL_UNIFORM_OFFSET), 80);
GLint dataSize = 0;
const GLuint blockIndex = GetUniformBlockIndex(program, "Blk2");
ASSERT_NE(blockIndex, GL_INVALID_INDEX);
GetActiveUniformBlockiv(program, blockIndex, GL_UNIFORM_BLOCK_DATA_SIZE, &dataSize);
EXPECT_EQ(dataSize, 96);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
TEST_F(ProgramTest, UniformBlockInstanceArrayReportsPerInstanceBlocks) {
const char* fsSource = R"(#version 330
layout(std140) uniform ArrBlk {
vec4 av;
} insts[2];
out vec4 o_color;
void main() {
o_color = insts[0].av + insts[1].av;
})";
GLuint program = LinkVsFsProgram(kPassthroughCoordsVs, fsSource);
const GLuint inst0 = GetUniformBlockIndex(program, "ArrBlk[0]");
const GLuint inst1 = GetUniformBlockIndex(program, "ArrBlk[1]");
ASSERT_NE(inst0, GL_INVALID_INDEX);
ASSERT_NE(inst1, GL_INVALID_INDEX);
EXPECT_NE(inst0, inst1);
// A bare block name resolves to the first instance.
EXPECT_EQ(GetUniformBlockIndex(program, "ArrBlk"), inst0);
// Every instance of the array shares the single reflected member set.
GLint count0 = 0;
GLint count1 = 0;
GetActiveUniformBlockiv(program, inst0, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &count0);
GetActiveUniformBlockiv(program, inst1, GL_UNIFORM_BLOCK_ACTIVE_UNIFORMS, &count1);
EXPECT_EQ(count0, 1);
EXPECT_EQ(count1, 1);
GLint index0 = -1;
GLint index1 = -1;
GetActiveUniformBlockiv(program, inst0, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, &index0);
GetActiveUniformBlockiv(program, inst1, GL_UNIFORM_BLOCK_ACTIVE_UNIFORM_INDICES, &index1);
EXPECT_EQ(index0, index1);
EXPECT_EQ(static_cast<GLuint>(index0), UniformIndexByName(program, "ArrBlk.av"));
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
TEST_F(ProgramTest, DeleteShaderWhileAttachedKeepsNameUsableUntilDetach) {
// GL CTS compiles through exactly this sequence (create, attach, DELETE, source,
// compile): glDeleteShader on an attached shader only flags it, and the name must
// keep working until the last detach.
const char* vsSource = R"(#version 330
void main() { gl_Position = vec4(0.0); }
)";
const char* fsSource = R"(#version 330
out vec4 o_color;
void main() { o_color = vec4(1.0); }
)";
GLuint program = CreateProgram();
GLuint vs = CreateShader(GL_VERTEX_SHADER);
AttachShader(program, vs);
DeleteShader(vs);
EXPECT_EQ(IsShader(vs), GL_TRUE); // still alive: attached
ShaderSource(vs, 1, &vsSource, nullptr);
CompileShader(vs);
GLint status = GL_FALSE;
GetShaderiv(vs, GL_COMPILE_STATUS, &status);
EXPECT_EQ(status, GL_TRUE);
status = GL_FALSE;
GetShaderiv(vs, GL_DELETE_STATUS, &status);
EXPECT_EQ(status, GL_TRUE);
GLuint fs = CreateShader(GL_FRAGMENT_SHADER);
AttachShader(program, fs);
DeleteShader(fs);
ShaderSource(fs, 1, &fsSource, nullptr);
CompileShader(fs);
LinkProgram(program);
GLint linkStatus = GL_FALSE;
char infoLog[1024] = "";
GetProgramiv(program, GL_LINK_STATUS, &linkStatus);
GetProgramInfoLog(program, sizeof(infoLog), nullptr, infoLog);
EXPECT_EQ(linkStatus, GL_TRUE) << infoLog;
// The last GL-visible detach releases the flagged shader's name.
DetachShader(program, vs);
EXPECT_EQ(IsShader(vs), GL_FALSE);
// Deleting the program releases the other flagged shader.
DeleteProgram(program);
EXPECT_EQ(IsShader(fs), GL_FALSE);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}