mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix, Test] (ShaderTranspiler): route every sub-array of an array-of-arrays uniform to its own UBO offset
This commit is contained in:
@@ -2162,6 +2162,69 @@ void main() {
|
|||||||
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Repro for KHR-GLES31.explicit_uniform_location.uniform-loc-arrays-of-arrays: an
|
||||||
|
// array-of-arrays uniform reaches the GL surface as one entry PER SUB-ARRAY ("u0[0]",
|
||||||
|
// "u0[1]" - glslang stops expanding at reflection granularity), while SPIRV-Reflect keeps
|
||||||
|
// it as a single leaf carrying every dimension. Routing the single leaf only ever covered
|
||||||
|
// the first sub-array, so every element from u0[1][0] on found no UBO offset and fell
|
||||||
|
// through to the fallback scratch at the tail of the shadow - storage the GPU never reads,
|
||||||
|
// which made those glUniform writes silently vanish.
|
||||||
|
TEST_F(ProgramTest, ArrayOfArraysUniformElementOffsets) {
|
||||||
|
// Arrays of arrays need GLSL 4.30; both stages take the same version.
|
||||||
|
const char* vsSource = R"(#version 430 core
|
||||||
|
in vec4 a_position;
|
||||||
|
void main() {
|
||||||
|
gl_Position = a_position;
|
||||||
|
})";
|
||||||
|
const char* fsSource = R"(#version 430 core
|
||||||
|
uniform float u0[2][3];
|
||||||
|
uniform vec3 u1[2][2];
|
||||||
|
out vec4 o_color;
|
||||||
|
void main() {
|
||||||
|
float s = 0.0;
|
||||||
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
for (int j = 0; j < 3; ++j) s += u0[i][j];
|
||||||
|
}
|
||||||
|
vec3 v = vec3(0.0);
|
||||||
|
for (int i = 0; i < 2; ++i) {
|
||||||
|
for (int j = 0; j < 2; ++j) v += u1[i][j];
|
||||||
|
}
|
||||||
|
o_color = vec4(v, s);
|
||||||
|
})";
|
||||||
|
GLuint program = LinkVsFsProgram(vsSource, fsSource);
|
||||||
|
UseProgram(program);
|
||||||
|
auto programObject = MG_State::pGLContext->GetProgramObject(program);
|
||||||
|
ASSERT_NE(programObject, nullptr);
|
||||||
|
|
||||||
|
// std140 gives a float array element and a vec3 array element the same 16-byte slot,
|
||||||
|
// and a flattened array-of-arrays is one contiguous run of those slots.
|
||||||
|
constexpr Uint kStd140ElementStride = 16u;
|
||||||
|
|
||||||
|
const auto checkFlattenedRun = [&](const char* base, int outer, int inner) {
|
||||||
|
Uint firstOffset = MG_State::GLState::ProgramObject::kInvalidUniformOffset;
|
||||||
|
for (int i = 0; i < outer; ++i) {
|
||||||
|
for (int j = 0; j < inner; ++j) {
|
||||||
|
const std::string name =
|
||||||
|
std::string(base) + "[" + std::to_string(i) + "][" + std::to_string(j) + "]";
|
||||||
|
const GLint location = GetUniformLocation(program, name.c_str());
|
||||||
|
ASSERT_GE(location, 0) << name;
|
||||||
|
const Uint offset = programObject->GetUniformOffset(static_cast<Uint>(location));
|
||||||
|
ASSERT_NE(offset, MG_State::GLState::ProgramObject::kInvalidUniformOffset) << name;
|
||||||
|
const Uint element = static_cast<Uint>(i * inner + j);
|
||||||
|
if (element == 0) {
|
||||||
|
firstOffset = offset;
|
||||||
|
} else {
|
||||||
|
EXPECT_EQ(offset, firstOffset + element * kStd140ElementStride) << name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkFlattenedRun("u0", 2, 3);
|
||||||
|
checkFlattenedRun("u1", 2, 2);
|
||||||
|
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// GL CTS KHR-GL33.shaders.uniform_block regression pack. MobileGL's SPIR-V
|
// 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
|
// pipeline lays every uniform block out as std140; the frontend implements the
|
||||||
|
|||||||
@@ -48,13 +48,16 @@ namespace MobileGL {
|
|||||||
return SPVC_BASETYPE_UNKNOWN;
|
return SPVC_BASETYPE_UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Record one flattened leaf uniform of the global UBO into the metadata maps.
|
// Write one metadata entry. `name` is already the glslang-reflection spelling the
|
||||||
static void RecordGlobalUboLeaf(const SpvReflectBlockVariable& member, const String& name,
|
// GL uniform locations are keyed on, and `arrayStride`/`sizeInBytes` describe the
|
||||||
Uint32 offsetInUBO, SpvcMetadata& metadata) {
|
// entry rather than the whole declaration (they differ for a sub-array of an
|
||||||
|
// array-of-arrays - see RecordGlobalUboLeaf).
|
||||||
|
static void RecordGlobalUboLeafEntry(const SpvReflectBlockVariable& member, const String& name,
|
||||||
|
Uint32 offsetInUBO, Uint32 arrayStride, SizeT sizeInBytes,
|
||||||
|
SpvcMetadata& metadata) {
|
||||||
metadata.plainUniformOffsetsInUBO[name] = offsetInUBO;
|
metadata.plainUniformOffsetsInUBO[name] = offsetInUBO;
|
||||||
metadata.plainUniformMemberSizesInBytes[name] = member.size;
|
metadata.plainUniformMemberSizesInBytes[name] = sizeInBytes;
|
||||||
metadata.plainUniformArrayStridesInUBO[name] =
|
metadata.plainUniformArrayStridesInUBO[name] = arrayStride;
|
||||||
member.array.dims_count > 0 ? member.array.stride : 0;
|
|
||||||
|
|
||||||
Uint32 vectorSize = member.numeric.vector.component_count;
|
Uint32 vectorSize = member.numeric.vector.component_count;
|
||||||
if (vectorSize == 0) vectorSize = 1;
|
if (vectorSize == 0) vectorSize = 1;
|
||||||
@@ -67,6 +70,70 @@ namespace MobileGL {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Record one flattened leaf uniform of the global UBO into the metadata maps.
|
||||||
|
//
|
||||||
|
// SPIRV-Reflect keeps `float u[2][3]` as ONE leaf carrying every dimension in
|
||||||
|
// array.dims[] and the INNERMOST element stride in array.stride (the outer
|
||||||
|
// ArrayStride decoration is overwritten as ParseType recurses into the element
|
||||||
|
// type, which is also why array.size == product(dims) * stride). glslang's
|
||||||
|
// reflection - which owns the names GL uniform locations are keyed on - stops at
|
||||||
|
// "reflection granularity" instead (reflection.cpp: !type.isArrayOfArrays()), so
|
||||||
|
// the same declaration arrives on the GL side as "u[0]" and "u[1]", each a
|
||||||
|
// `float[3]` holding its own three locations.
|
||||||
|
//
|
||||||
|
// Emitting a single "u" leaf here therefore only ever routes the FIRST sub-array:
|
||||||
|
// the routing loop stops as soon as a location belongs to a different uniform, and
|
||||||
|
// every element from "u[1][0]" on finds no offset and falls through to the fallback
|
||||||
|
// scratch storage at the tail of the shadow - bytes the GPU never reads, so those
|
||||||
|
// glUniform writes are silently lost
|
||||||
|
// (KHR-GLES31.explicit_uniform_location.uniform-loc-arrays-of-arrays). Expand every
|
||||||
|
// dimension but the last, exactly as glslang does, and give each sub-array its own
|
||||||
|
// byte offset.
|
||||||
|
static void RecordGlobalUboLeaf(const SpvReflectBlockVariable& member, const String& name,
|
||||||
|
Uint32 offsetInUBO, SpvcMetadata& metadata) {
|
||||||
|
const Uint32 arrayStride = member.array.dims_count > 0 ? member.array.stride : 0;
|
||||||
|
const Bool isArrayOfArrays = member.array.dims_count > 1 && arrayStride > 0;
|
||||||
|
if (!isArrayOfArrays) {
|
||||||
|
RecordGlobalUboLeafEntry(member, name, offsetInUBO, arrayStride, member.size, metadata);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extent 0 is SPIRV-Reflect's OpTypeRuntimeArray marker, which a plain uniform
|
||||||
|
// cannot be - but it must not be expanded (or divided by) if it ever appears.
|
||||||
|
Uint32 subArrayCount = 1;
|
||||||
|
for (Uint32 dim = 0; dim + 1 < member.array.dims_count; ++dim) {
|
||||||
|
const Uint32 extent = member.array.dims[dim];
|
||||||
|
if (extent == 0) {
|
||||||
|
MGLOG_W_ONCE("RecordGlobalUboLeaf: multi-dimensional uniform '%s' has a non-constant "
|
||||||
|
"dimension, recording the base entry only",
|
||||||
|
name.c_str());
|
||||||
|
RecordGlobalUboLeafEntry(member, name, offsetInUBO, arrayStride, member.size, metadata);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
subArrayCount *= extent;
|
||||||
|
}
|
||||||
|
const Uint32 innerExtent = member.array.dims[member.array.dims_count - 1] > 0
|
||||||
|
? member.array.dims[member.array.dims_count - 1]
|
||||||
|
: 1;
|
||||||
|
const Uint32 subArrayStride = innerExtent * arrayStride;
|
||||||
|
|
||||||
|
// Row-major odometer over dims[0 .. dims_count-2]: the last dimension varies
|
||||||
|
// fastest, so `subArray` counts sub-arrays in exactly memory order.
|
||||||
|
Vector<Uint32> indices(member.array.dims_count - 1, 0);
|
||||||
|
for (Uint32 subArray = 0; subArray < subArrayCount; ++subArray) {
|
||||||
|
String elementName = name;
|
||||||
|
for (const Uint32 index : indices) {
|
||||||
|
elementName += "[" + std::to_string(index) + "]";
|
||||||
|
}
|
||||||
|
RecordGlobalUboLeafEntry(member, elementName, offsetInUBO + subArray * subArrayStride,
|
||||||
|
arrayStride, subArrayStride, metadata);
|
||||||
|
for (SizeT dim = indices.size(); dim-- > 0;) {
|
||||||
|
if (++indices[dim] < member.array.dims[dim]) break;
|
||||||
|
indices[dim] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Flatten a (possibly nested struct / struct array) member of the global UBO
|
// Flatten a (possibly nested struct / struct array) member of the global UBO
|
||||||
// into leaf entries named the way glslang reflection names plain uniforms:
|
// into leaf entries named the way glslang reflection names plain uniforms:
|
||||||
// "s[0].b[1].b" for `uniform S s[2]` with `struct T { vec2 b[2]; }` members.
|
// "s[0].b[1].b" for `uniform S s[2]` with `struct T { vec2 b[2]; }` members.
|
||||||
|
|||||||
Reference in New Issue
Block a user