[Feat] (MG_Impl/GLImpl, MG_State): implement glBindFragDataLocationIndexed

Bind a fragment output to both a color number and a color index (0 or 1
for dual-source blending), and report the bound index back through
glGetFragDataIndex.

- ProgramObject now tracks a per-output color index alongside the
  location: SetExplicitFragmentOutIndex stores it, it is snapshotted into
  the linked map at link time (like the location map), and
  GetFragmentDataIndex returns it (0 by default) for an active output.
- glBindFragDataLocation becomes glBindFragDataLocationIndexed with index
  0, matching the GL definition, so it also resets a previously-bound
  index to 0.
- Validation: index must be 0 or 1 (GL_INVALID_VALUE); colorNumber is
  bounded by GL_MAX_DRAW_BUFFERS for index 0 and GL_MAX_DUAL_SOURCE_DRAW_BUFFERS
  (reported as 1) for index 1 (GL_INVALID_VALUE); a gl_ name is
  GL_INVALID_OPERATION.
- glGetFragDataIndex now returns the real bound index instead of a
  hardcoded 0.

The index is tracked for reflection but is not yet plumbed into dual-source
blend rendering, and shader-side layout(index=) qualifiers are not
reflected -- both documented at the call sites.

Tests: index round-trip through a re-link (bind 1 -> GetFragDataIndex == 1;
glBindFragDataLocation resets to 0), plus the validation error table;
mutation-verified end to end. ProgramTest 24/24.
This commit is contained in:
2026-07-11 00:39:23 -04:00
parent bebe534bad
commit 22ac8a8c10
6 changed files with 79 additions and 10 deletions
@@ -866,7 +866,7 @@ DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElementsBaseVertex, GLenum mode, const G
DECLARE_GL_FUNCTION_HEAD(void, ProvokingVertex, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProvokingVertex, mode)
DECLARE_GL_FUNCTION_HEAD(void, TexImage2DMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexImage2DMultisample, target, samples, internalformat, width, height, fixedsamplelocations)
DECLARE_GL_FUNCTION_HEAD(void, TexImage3DMultisample, GLenum target, GLsizei samples, GLenum internalformat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexImage3DMultisample, target, samples, internalformat, width, height, depth, fixedsamplelocations)
DECLARE_GL_FUNCTION_STUB_HEAD(void, BindFragDataLocationIndexed, GLuint program, GLuint colorNumber, GLuint index, const GLchar* name) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, BindFragDataLocationIndexed, program, colorNumber, index, name)
DECLARE_GL_FUNCTION_HEAD(void, BindFragDataLocationIndexed, GLuint program, GLuint colorNumber, GLuint index, const GLchar* name) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindFragDataLocationIndexed, program, colorNumber, index, name)
DECLARE_GL_FUNCTION_HEAD(GLint, GetFragDataIndex, GLuint program, const GLchar* name) DECLARE_GL_FUNCTION_END(GLint, GetFragDataIndex, program, name)
DECLARE_GL_FUNCTION_HEAD(void, QueryCounter, GLuint id, GLenum target) DECLARE_GL_FUNCTION_END_NO_RETURN(void, QueryCounter, id, target)
DECLARE_GL_FUNCTION_HEAD(void, GetQueryObjecti64v, GLuint id, GLenum pname, GLint64* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetQueryObjecti64v, id, pname, params)
+29 -9
View File
@@ -1483,7 +1483,7 @@ namespace MobileGL::MG_Impl::GLImpl {
length ? *length : 0);
}
void BindFragDataLocation_State(GLuint program, GLuint colorNumber, const char* name) {
void BindFragDataLocationIndexed_State(GLuint program, GLuint colorNumber, GLuint index, const char* name) {
auto& programObject = TryToGetProgramObject(program);
// TryToGetProgramObject already recorded the error for a bad handle (GL_INVALID_VALUE for an
// unknown name, GL_INVALID_OPERATION for a non-program object); do not record a second one.
@@ -1494,12 +1494,23 @@ namespace MobileGL::MG_Impl::GLImpl {
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "name cannot be null."));
return;
}
// index selects the single (0) or dual-source (1) color; it must be 0 or 1.
if (index > 1) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "index must be 0 or 1."));
return;
}
const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters();
if (colorNumber >= static_cast<GLuint>(dynamicParameters.MaxDrawBuffers)) {
// colorNumber is bounded by GL_MAX_DRAW_BUFFERS for index 0, and by
// GL_MAX_DUAL_SOURCE_DRAW_BUFFERS (which MobileGL reports as 1) for index 1.
const GLuint colorNumberLimit =
(index == 0) ? static_cast<GLuint>(dynamicParameters.MaxDrawBuffers) : 1u;
if (colorNumber >= colorNumberLimit) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("MG_Impl/GLImpl", __func__,
"colorNumber is greater than or equal to GL_MAX_DRAW_BUFFERS."));
"colorNumber exceeds the applicable draw-buffer limit."));
return;
}
if (strncmp(name, "gl_", 3) == 0) {
@@ -1510,8 +1521,14 @@ namespace MobileGL::MG_Impl::GLImpl {
return;
}
MGLOG_D("%s: loc %02d = \"%s\"", __func__, colorNumber, name);
MGLOG_D("%s: loc %02d index %u = \"%s\"", __func__, colorNumber, index, name);
programObject->SetExplicitFragmentOutLocation(colorNumber, name);
programObject->SetExplicitFragmentOutIndex(index, name);
}
// glBindFragDataLocation is glBindFragDataLocationIndexed with color index 0.
void BindFragDataLocation_State(GLuint program, GLuint colorNumber, const char* name) {
BindFragDataLocationIndexed_State(program, colorNumber, 0, name);
}
GLint GetFragDataLocation_State(GLuint program, const char* name) {
@@ -1549,11 +1566,10 @@ namespace MobileGL::MG_Impl::GLImpl {
std::to_string(program) + " has not been linked successfully."));
return -1;
}
// A name that is not an active user-defined fragment output (including gl_ built-ins) has no
// index. Every bound output uses color index 0: MobileGL does not yet track dual-source
// (index 1) bindings -- glBindFragDataLocationIndexed and the layout(index = 1) qualifier are
// not supported -- so this is exact for every program that does not use dual-source blending.
return programObject->GetFragmentDataLocation(name) < 0 ? -1 : 0;
// Returns the color index bound by glBindFragDataLocationIndexed (0 by default), or -1 if name
// is not an active user-defined output. Note: the index is tracked for reflection but is not
// yet plumbed into dual-source blend rendering, and shader-side layout(index=) is not reflected.
return programObject->GetFragmentDataIndex(name);
}
void ValidateProgram_State(GLuint program) {
@@ -1990,6 +2006,10 @@ namespace MobileGL::MG_Impl::GLImpl {
BindFragDataLocation_State(program, colorNumber, name);
}
void BindFragDataLocationIndexed(GLuint program, GLuint colorNumber, GLuint index, const char* name) {
BindFragDataLocationIndexed_State(program, colorNumber, index, name);
}
GLint GetFragDataLocation(GLuint program, const char* name) {
return GetFragDataLocation_State(program, name);
}
@@ -125,6 +125,7 @@ namespace MobileGL::MG_Impl::GLImpl {
void GetActiveUniformBlockName(GLuint program, GLuint uniformBlockIndex, GLsizei bufSize, GLsizei* length,
GLchar* uniformBlockName);
void BindFragDataLocation(GLuint program, GLuint colorNumber, const char* name);
void BindFragDataLocationIndexed(GLuint program, GLuint colorNumber, GLuint index, const char* name);
GLint GetFragDataLocation(GLuint program, const char* name);
GLint GetFragDataIndex(GLuint program, const char* name);
void GetProgramInterfaceiv(GLuint program, GLenum programInterface, GLenum pname, GLint* params);
@@ -291,6 +291,7 @@ namespace MobileGL::MG_State::GLState {
m_linkStatus = true;
m_program = result.value();
m_linkedFragDataLocation = m_explicitFragDataLocation;
m_linkedFragDataIndex = m_explicitFragDataIndex;
MGLOG_D("ProgramObject %u: LinkProgram succeeded, TProgram ptr %p", m_externalIndex, m_program.get());
} else {
m_infoLog = result.error().log;
@@ -692,6 +693,12 @@ namespace MobileGL::MG_State::GLState {
m_externalIndex, name, index);
}
void ProgramObject::SetExplicitFragmentOutIndex(Uint colorIndex, const char* name) {
m_explicitFragDataIndex[name] = colorIndex;
MGLOG_D("ProgramObject %u: SetExplicitFragmentOutIndex - stored color index for '%s' -> %u", m_externalIndex,
name, colorIndex);
}
Bool ProgramObject::ValidateFragmentOutputLocations() {
if (!m_program) return false;
@@ -746,4 +753,13 @@ namespace MobileGL::MG_State::GLState {
}
return -1;
}
Int ProgramObject::GetFragmentDataIndex(const char* name) {
// Only an active user-defined fragment output has an index; reuse the location lookup to test
// that. The color index defaults to 0 unless glBindFragDataLocationIndexed bound it to 1.
// (Shader-side layout(index = ...) qualifiers are not reflected here, only API bindings.)
if (GetFragmentDataLocation(name) < 0) return -1;
const auto it = m_linkedFragDataIndex.find(name);
return it != m_linkedFragDataIndex.end() ? static_cast<Int>(it->second) : 0;
}
} // namespace MobileGL::MG_State::GLState
@@ -26,10 +26,14 @@ namespace MobileGL::MG_State::GLState {
void SetExplicitVertexInLocation(Uint index, const char* name);
void SetExplicitFragmentOutLocation(Uint index, const char* name);
// Dual-source blend color index (glBindFragDataLocationIndexed). Takes effect on next link.
void SetExplicitFragmentOutIndex(Uint colorIndex, const char* name);
void SetMaxFragmentOutputColorNumber(Int maxDrawBuffers) {
m_maxFragmentOutputColorNumber = maxDrawBuffers;
}
Int GetFragmentDataLocation(const char* name);
// Bound color index for an active fragment output (0 by default), or -1 if name is not one.
Int GetFragmentDataIndex(const char* name);
Vector<SharedPtr<ShaderObject>>& GetAttachedShaders();
const Vector<SharedPtr<ShaderObject>>& GetAttachedShaders() const;
@@ -361,6 +365,10 @@ namespace MobileGL::MG_State::GLState {
// FragData (Frag out)
UnorderedMap<String, Uint> m_explicitFragDataLocation;
UnorderedMap<String, Uint> m_linkedFragDataLocation;
// Dual-source blend color index per output name (glBindFragDataLocationIndexed); snapshotted
// into the linked map at link time, like the location maps above.
UnorderedMap<String, Uint> m_explicitFragDataIndex;
UnorderedMap<String, Uint> m_linkedFragDataIndex;
Int m_maxFragmentOutputColorNumber = 8;
// Uniforms
+24
View File
@@ -1389,6 +1389,30 @@ TEST_F(ProgramTest, CompileAndLinkWithExplicitFragmentOut) {
// }
ASSERT_TRUE(pSrcfragOut != nullptr) << "Not found expected string in generated shader.\n(Searching for \"" << needle
<< "\")";
// glBindFragDataLocationIndexed round-trips the color index through a re-link. index 1 requires
// colorNumber 0 (GL_MAX_DUAL_SOURCE_DRAW_BUFFERS is 1).
BindFragDataLocationIndexed(program, 0, 1, "fragColor");
LinkProgram(program);
GetProgramiv(program, GL_LINK_STATUS, &linkStatus);
ASSERT_EQ(linkStatus, GL_TRUE);
EXPECT_EQ(GetFragDataIndex(program, "fragColor"), 1);
EXPECT_EQ(GetFragDataIndex(program, "notAnActiveOutput"), -1);
// glBindFragDataLocation is equivalent to index 0 and resets it.
BindFragDataLocation(program, 0, "fragColor");
LinkProgram(program);
EXPECT_EQ(GetFragDataIndex(program, "fragColor"), 0);
// Validation: index > 1 and a too-large colorNumber for index 1 are GL_INVALID_VALUE; a gl_ name is
// GL_INVALID_OPERATION.
BindFragDataLocationIndexed(program, 0, 2, "fragColor");
EXPECT_EQ(GetError(), GL_INVALID_VALUE);
BindFragDataLocationIndexed(program, 1, 1, "fragColor"); // colorNumber 1 invalid for index 1
EXPECT_EQ(GetError(), GL_INVALID_VALUE);
BindFragDataLocationIndexed(program, 0, 0, "gl_FragColor");
EXPECT_EQ(GetError(), GL_INVALID_OPERATION);
EXPECT_EQ(GetError(), GL_NO_ERROR);
}
const char* vs_sampler_as_varname = R"(#version 330