[Fix] (GL): resolve attribute array elements in glGetAttribLocation

This commit is contained in:
2026-10-12 08:10:04 -04:00
parent 4c9ce56124
commit b22c37f763
3 changed files with 89 additions and 1 deletions
@@ -26,6 +26,48 @@ void main() {}
namespace MobileGL::MG_State::GLState {
static std::atomic<Uint64> s_nextProgramLifetimeId = 1;
Int ProgramObject::GetAttributeArrayElementLocation(const String& name) {
if (name.empty()) return -1;
if (name.back() != ']') return GetAttributeArrayElementLocation(name + "[0]");
// A strict decimal subscript: no white space, no sign, no leading zeros.
const SizeT bracket = name.rfind('[');
if (bracket == String::npos || bracket == 0) return -1;
const SizeT first = bracket + 1;
const SizeT last = name.size() - 1;
if (first >= last || (name[first] == '0' && last - first > 1)) return -1;
Int element = 0;
for (SizeT i = first; i < last; ++i) {
if (name[i] < '0' || name[i] > '9') return -1;
element = element * 10 + (name[i] - '0');
if (element > 0xFFFF) return -1;
}
const String base = name.substr(0, bracket);
const auto& artifacts = Artifacts();
const auto& attribs = artifacts.attribs;
for (const auto& input : artifacts.pipeInputReflection) {
// Reflection may spell an array with or without its "[0]".
if (input.name != base && input.name != base + "[0]") continue;
if (!input.type.isArray || element >= input.arraySize) return -1;
// The link expanded the array over consecutive locations, every one carrying the
// reflected name (ProgramLinkTask's attribute table), so its span is the run of them.
// An exact match for the reflected name was already tried by GetAttributeLocation, so
// this is reached for element 0 only through the plain-name spelling.
const auto it = std::find(attribs.begin(), attribs.end(), input.name);
if (it == attribs.end()) return -1;
const Int baseLocation = static_cast<Int>(std::distance(attribs.begin(), it));
Int span = 0;
while (baseLocation + span < static_cast<Int>(attribs.size()) &&
attribs[static_cast<SizeT>(baseLocation + span)] == input.name) {
++span;
}
const Int locationsPerElement = std::max<Int>(1, span / std::max<Int>(input.arraySize, 1));
const Int location = baseLocation + element * locationsPerElement;
return location < baseLocation + span ? location : -1;
}
return -1;
}
Uint64 ProgramObject::AllocateLifetimeId() {
return s_nextProgramLifetimeId.fetch_add(1, std::memory_order_relaxed);
}
@@ -663,8 +663,15 @@ namespace MobileGL::MG_State::GLState {
Int GetAttributeLocation(const String& name) {
const auto it = std::find(Artifacts().attribs.begin(), Artifacts().attribs.end(), name);
return (it == Artifacts().attribs.end()) ? -1 : (Int)std::distance(Artifacts().attribs.begin(), it);
return (it == Artifacts().attribs.end()) ? GetAttributeArrayElementLocation(name)
: (Int)std::distance(Artifacts().attribs.begin(), it);
}
// An attribute ARRAY by its plain name or as "name[N]" (GL 4.6 core 7.3.1.1 / 11.1.1).
// Reflection records the array as "name[0]", so the plain name answers element 0, and
// "name[N]" answers the array's base location plus N times the locations one element
// occupies (a matrix takes one per column). -1 past the end, for a subscript on a
// non-array, and for a subscript the resource-name grammar rejects ("a[01]", "a[ 1]").
Int GetAttributeArrayElementLocation(const String& name);
Uint32 GetActiveAttributeLocationMask() const {
Uint32 mask = 0;
const SizeT count = std::min<SizeT>(Artifacts().attribs.size(), 32);
@@ -951,6 +951,45 @@ void main() {
}
}
// glGetAttribLocation("name[N]") on an attribute array answers element N's location
// (KHR-GL4x.cull_distance.functional_* feed `in float culldistance_data[N]` element by element).
TEST_F(ProgramInterfaceTest, AttribLocationResolvesArrayElements) {
const char* vs = R"(#version 430
in vec4 position;
in float data[4];
in mat2 m[2];
out float v;
void main(void) {
v = data[0] + data[1] + data[2] + data[3] + m[0][0][0] + m[1][1][1];
gl_Position = position;
}
)";
const GLuint p = MakeProgram(vs, kSimpleFs);
LinkProgram(p);
ExpectLinked(p);
ClearErrors();
const GLint data = GetAttribLocation(p, "data");
ASSERT_GE(data, 0);
EXPECT_EQ(GetAttribLocation(p, "data[0]"), data);
EXPECT_EQ(GetAttribLocation(p, "data[1]"), data + 1);
EXPECT_EQ(GetAttribLocation(p, "data[3]"), data + 3);
EXPECT_EQ(GetAttribLocation(p, "data[4]"), -1);
EXPECT_EQ(GetAttribLocation(p, "data[01]"), -1);
EXPECT_EQ(GetAttribLocation(p, "data[ 1]"), -1);
// A matrix element occupies one location per column.
const GLint m = GetAttribLocation(p, "m");
ASSERT_GE(m, 0);
EXPECT_EQ(GetAttribLocation(p, "m[1]"), m + 2);
EXPECT_EQ(GetAttribLocation(p, "m[2]"), -1);
// A subscript on a non-array names nothing.
ASSERT_GE(GetAttribLocation(p, "position"), 0);
EXPECT_EQ(GetAttribLocation(p, "position[0]"), -1);
EXPECT_EQ(TakeError(), GL_NO_ERROR);
}
// The same rule for the tessellation stages, whose limits come from the backend like the
// others (they used to be hard-coded zeros). Sized off the advertised limit for the same
// reason as above: one image over it must fail and name the limit, and exactly at it must