diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 90c15faf..07fc5f11 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -26,6 +26,48 @@ void main() {} namespace MobileGL::MG_State::GLState { static std::atomic 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(std::distance(attribs.begin(), it)); + Int span = 0; + while (baseLocation + span < static_cast(attribs.size()) && + attribs[static_cast(baseLocation + span)] == input.name) { + ++span; + } + const Int locationsPerElement = std::max(1, span / std::max(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); } diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index 5fe20972..29c69837 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -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(Artifacts().attribs.size(), 32); diff --git a/MobileGL/MG_Test/Program/ProgramInterfaceTest.cpp b/MobileGL/MG_Test/Program/ProgramInterfaceTest.cpp index bff01d63..6fac8773 100644 --- a/MobileGL/MG_Test/Program/ProgramInterfaceTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramInterfaceTest.cpp @@ -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