[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);