diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 07a429cc..d39fcdaa 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -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) diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp index c4b40dde..d9a87961 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.cpp @@ -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("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("MG_Impl/GLImpl", __func__, "index must be 0 or 1.")); + return; + } const auto& dynamicParameters = MG_Backend::pActiveBackendObject->GetDynamicParameters(); - if (colorNumber >= static_cast(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(dynamicParameters.MaxDrawBuffers) : 1u; + if (colorNumber >= colorNumberLimit) { MG_State::pGLContext->RecordError( ErrorCode::InvalidValue, MakeUnique("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); } diff --git a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h index 9e4f9ebe..07e56ff6 100644 --- a/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h +++ b/MobileGL/MG_Impl/GLImpl/Program/GL_Program.h @@ -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); diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 63644c3a..c6740699 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -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(it->second) : 0; + } } // namespace MobileGL::MG_State::GLState diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index 9126af96..a90c3e7e 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -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>& GetAttachedShaders(); const Vector>& GetAttachedShaders() const; @@ -361,6 +365,10 @@ namespace MobileGL::MG_State::GLState { // FragData (Frag out) UnorderedMap m_explicitFragDataLocation; UnorderedMap 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 m_explicitFragDataIndex; + UnorderedMap m_linkedFragDataIndex; Int m_maxFragmentOutputColorNumber = 8; // Uniforms diff --git a/MobileGL/MG_Test/Program/ProgramTest.cpp b/MobileGL/MG_Test/Program/ProgramTest.cpp index 15200bac..3447e83f 100644 --- a/MobileGL/MG_Test/Program/ProgramTest.cpp +++ b/MobileGL/MG_Test/Program/ProgramTest.cpp @@ -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