diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 1588ca28..ebb8a998 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -702,9 +702,9 @@ namespace MobileGL::MG_Backend::DirectGLES { } } - { // Color mask - if (parameters.ColorMask != g_syncedRenderStateParameters.ColorMask) { - const BoolVec4& colorMask = parameters.ColorMask; + { // Color mask. GLES core has only the non-indexed glColorMask, so sync draw buffer 0. + if (parameters.ColorMasks[0] != g_syncedRenderStateParameters.ColorMasks[0]) { + const BoolVec4& colorMask = parameters.ColorMasks[0]; g_GLESFuncs.glColorMask(ToGLBoolean(colorMask.x()), ToGLBoolean(colorMask.y()), ToGLBoolean(colorMask.z()), ToGLBoolean(colorMask.w())); } diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index d44d683d..be415c3a 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -411,7 +411,7 @@ DECLARE_GL_FUNCTION_HEAD(void, BlendFunci, GLuint buf, GLenum src, GLenum dst) D DECLARE_GL_FUNCTION_HEAD(void, BlendFunciARB, GLuint buf, GLenum src, GLenum dst) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlendFunci, buf, src, dst) DECLARE_GL_FUNCTION_HEAD(void, BlendFuncSeparatei, GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlendFuncSeparatei, buf, srcRGB, dstRGB, srcAlpha, dstAlpha) DECLARE_GL_FUNCTION_HEAD(void, BlendFuncSeparateiARB, GLuint buf, GLenum srcRGB, GLenum dstRGB, GLenum srcAlpha, GLenum dstAlpha) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BlendFuncSeparatei, buf, srcRGB, dstRGB, srcAlpha, dstAlpha) -DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorMaski, GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ColorMaski, index, r, g, b, a) +DECLARE_GL_FUNCTION_HEAD(void, ColorMaski, GLuint index, GLboolean r, GLboolean g, GLboolean b, GLboolean a) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ColorMaski, index, r, g, b, a) DECLARE_GL_FUNCTION_HEAD(GLboolean, IsEnabledi, GLenum target, GLuint index) DECLARE_GL_FUNCTION_END(GLboolean, IsEnabledi, target, index) DECLARE_GL_FUNCTION_HEAD(void, DrawElementsBaseVertex, GLenum mode, GLsizei count, GLenum type, const void* indices, GLint basevertex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawElementsBaseVertex, mode, count, type, indices, basevertex) DECLARE_GL_FUNCTION_HEAD(void, DrawRangeElementsBaseVertex, GLenum mode, GLuint start, GLuint end, GLsizei count, GLenum type, const void* indices, GLint basevertex) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawRangeElementsBaseVertex, mode, start, end, count, type, indices, basevertex) diff --git a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp index 333298e7..7c3d2523 100644 --- a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp +++ b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp @@ -361,6 +361,25 @@ namespace MobileGL::MG_Impl::GLImpl { return; } + if (target == GL_COLOR_WRITEMASK) { + // Indexed per-draw-buffer color writemask writes 4 booleans (R,G,B,A) for draw buffer + // `index`. The non-indexed glGetBooleanv(GL_COLOR_WRITEMASK) reports draw buffer 0. + if (index >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", "GetBooleani_v_State", + "Color writemask draw buffer index " + std::to_string(index) + + " is out of range.")); + return; + } + const BoolVec4 mask = MG_State::pGLContext->GetColorMaskIndexed(index); + data[0] = mask.x() ? GL_TRUE : GL_FALSE; + data[1] = mask.y() ? GL_TRUE : GL_FALSE; + data[2] = mask.z() ? GL_TRUE : GL_FALSE; + data[3] = mask.w() ? GL_TRUE : GL_FALSE; + return; + } + *data = IsEnabledi_State(target, index); } @@ -519,8 +538,27 @@ namespace MobileGL::MG_Impl::GLImpl { } void ColorMask_State(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) { + // glColorMask broadcasts to every draw buffer. GLboolean coercion: any nonzero value enables + // the component; only exactly GL_FALSE disables it. MG_State::pGLContext->SetColorMask( - BoolVec4(red == GL_TRUE, green == GL_TRUE, blue == GL_TRUE, alpha == GL_TRUE)); + BoolVec4(red != GL_FALSE, green != GL_FALSE, blue != GL_FALSE, alpha != GL_FALSE)); + } + + void ColorMaski_State(GLuint buf, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) { + // Indexed color writemask for the single draw buffer `buf`. buf is a GLuint index, never an + // enum, so the only error is GL_INVALID_VALUE when it is out of range (mirrors the indexed + // blend entry points, which bound against MAX_DRAW_BUFFERS). + if (buf >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique( + "MG_Impl/GLImpl", __func__, + "glColorMaski buffer index " + std::to_string(buf) + " is out of range. Max supported is " + + std::to_string(MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS - 1) + ".")); + return; + } + MG_State::pGLContext->SetColorMaskIndexed( + buf, BoolVec4(red != GL_FALSE, green != GL_FALSE, blue != GL_FALSE, alpha != GL_FALSE)); } void ClampColor_State(GLenum target, GLenum clamp) { @@ -878,6 +916,10 @@ namespace MobileGL::MG_Impl::GLImpl { ColorMask_State(red, green, blue, alpha); } + void ColorMaski(GLuint index, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha) { + ColorMaski_State(index, red, green, blue, alpha); + } + void ClampColor(GLenum target, GLenum clamp) { ClampColor_State(target, clamp); } diff --git a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h index e6b00b2f..c569dc83 100644 --- a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h +++ b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h @@ -51,6 +51,7 @@ namespace MobileGL::MG_Impl::GLImpl { void DepthFunc(GLenum func); void CullFace(GLenum mode); void ColorMask(GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); + void ColorMaski(GLuint index, GLboolean red, GLboolean green, GLboolean blue, GLboolean alpha); void ClampColor(GLenum target, GLenum clamp); void BlendFuncSeparate(GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha); void BlendEquation(GLenum mode); diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index b335b58c..3141127e 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -493,6 +493,14 @@ namespace MobileGL::MG_State { return m_renderState.GetColorMask(); } + void GLContext::SetColorMaskIndexed(Uint index, BoolVec4 mask) { + m_renderState.SetColorMaskIndexed(index, mask); + } + + BoolVec4 GLContext::GetColorMaskIndexed(Uint index) const { + return m_renderState.GetColorMaskIndexed(index); + } + void GLContext::SetClearColor(FloatVec4 color) { m_renderState.SetClearColor(color); } diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 5c6429a3..3dddc442 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -164,6 +164,8 @@ namespace MobileGL { const StencilFaceState& GetStencilState(StencilFace face) const; void SetColorMask(BoolVec4 mask); BoolVec4 GetColorMask() const; + void SetColorMaskIndexed(Uint index, BoolVec4 mask); + BoolVec4 GetColorMaskIndexed(Uint index) const; void SetClearColor(FloatVec4 color); const FloatVec4& GetClearColor() const; void SetClearDepth(Float depth); diff --git a/MobileGL/MG_State/GLState/RenderState/RenderState.cpp b/MobileGL/MG_State/GLState/RenderState/RenderState.cpp index b9a6632e..1b8b1411 100644 --- a/MobileGL/MG_State/GLState/RenderState/RenderState.cpp +++ b/MobileGL/MG_State/GLState/RenderState/RenderState.cpp @@ -26,7 +26,12 @@ namespace MobileGL { } } // namespace - RenderState::RenderState() {} + RenderState::RenderState() { + // The color writemask defaults to all-true for every draw buffer. + for (auto& mask : m_parameters.ColorMasks) { + mask = BoolVec4(true, true, true, true); + } + } Uint RenderState::GetVersion() const { return m_version; @@ -437,14 +442,30 @@ namespace MobileGL { // -------------------- Color Mask -------------------- void RenderState::SetColorMask(BoolVec4 mask) { - if (m_parameters.ColorMask == mask) return; - - m_parameters.ColorMask = mask; - ++m_version; + // glColorMask broadcasts the same mask to every draw buffer. + Bool changed = false; + for (auto& slot : m_parameters.ColorMasks) { + if (!(slot == mask)) { + slot = mask; + changed = true; + } + } + if (changed) ++m_version; } BoolVec4 RenderState::GetColorMask() const { - return m_parameters.ColorMask; + // Non-indexed query reports draw buffer 0. + return m_parameters.ColorMasks[0]; + } + + void RenderState::SetColorMaskIndexed(Uint index, BoolVec4 mask) { + if (m_parameters.ColorMasks[index] == mask) return; + m_parameters.ColorMasks[index] = mask; + ++m_version; + } + + BoolVec4 RenderState::GetColorMaskIndexed(Uint index) const { + return m_parameters.ColorMasks[index]; } // -------------------- Clear State -------------------- diff --git a/MobileGL/MG_State/GLState/RenderState/RenderState.h b/MobileGL/MG_State/GLState/RenderState/RenderState.h index 3e8b476c..2a070dea 100644 --- a/MobileGL/MG_State/GLState/RenderState/RenderState.h +++ b/MobileGL/MG_State/GLState/RenderState/RenderState.h @@ -230,8 +230,9 @@ namespace MobileGL { DepthTestFunc DepthFunc = DepthTestFunc::Less; Bool DepthMask = true; - // Color Mask - BoolVec4 ColorMask = BoolVec4(true, true, true, true); + // Color Mask. Per-draw-buffer state (glColorMaski); glColorMask broadcasts to all buffers. + // Every entry is initialized to all-true in RenderState's constructor. + Array ColorMasks; // Clear State FloatVec4 ClearColor = FloatVec4(0.0f, 0.0f, 0.0f, 1.0f); @@ -358,9 +359,13 @@ namespace MobileGL { StencilOperation depthPass); const StencilFaceState& GetStencilState(StencilFace face) const; - // Color Mask + // Color Mask. SetColorMask broadcasts to every draw buffer and GetColorMask returns + // draw buffer 0; the indexed forms address a single draw buffer (glColorMaski). The + // caller is responsible for validating index against MAX_DRAW_BUFFERS. void SetColorMask(BoolVec4 mask); BoolVec4 GetColorMask() const; + void SetColorMaskIndexed(Uint index, BoolVec4 mask); + BoolVec4 GetColorMaskIndexed(Uint index) const; // Clear State void SetClearColor(FloatVec4 color); diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index 4f403d19..f9577142 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -851,3 +851,55 @@ TEST(RenderStateSanity, PolygonModeStoresAndReadsBack) { MG_State::pGLContext.reset(); } + +TEST(RenderStateSanity, ColorMaskIndexedStoresAndReadsBack) { + using namespace MobileGL; + using namespace MobileGL::MG_Impl::GLImpl; + constexpr GLuint kMaxDrawBuffers = MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS; + MG_State::pGLContext = MakeUnique(); + + // Default: every draw buffer's writemask is all-true. + GLboolean b0[4] = {GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE}; + GetBooleanv(GL_COLOR_WRITEMASK, b0); + EXPECT_EQ(b0[0], GL_TRUE); EXPECT_EQ(b0[1], GL_TRUE); + EXPECT_EQ(b0[2], GL_TRUE); EXPECT_EQ(b0[3], GL_TRUE); + GLboolean bi[4] = {GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE}; + GetBooleani_v(GL_COLOR_WRITEMASK, 3, bi); + EXPECT_EQ(bi[0], GL_TRUE); EXPECT_EQ(bi[3], GL_TRUE); + EXPECT_EQ(GetError(), GL_NO_ERROR); + + // glColorMaski sets ONLY the addressed draw buffer; buffer 0 stays untouched, and the non-indexed + // glGetBooleanv still reports buffer 0. + ColorMaski(2, GL_FALSE, GL_TRUE, GL_FALSE, GL_TRUE); + EXPECT_EQ(GetError(), GL_NO_ERROR); + GetBooleani_v(GL_COLOR_WRITEMASK, 2, bi); + EXPECT_EQ(bi[0], GL_FALSE); EXPECT_EQ(bi[1], GL_TRUE); + EXPECT_EQ(bi[2], GL_FALSE); EXPECT_EQ(bi[3], GL_TRUE); + GetBooleanv(GL_COLOR_WRITEMASK, b0); + EXPECT_EQ(b0[0], GL_TRUE); EXPECT_EQ(b0[1], GL_TRUE); // buffer 0 unchanged by ColorMaski(2, ...) + + // GLboolean coercion: any nonzero byte enables the component (NOT == GL_TRUE). + ColorMaski(1, static_cast(2), static_cast(0), + static_cast(2), static_cast(0)); + GetBooleani_v(GL_COLOR_WRITEMASK, 1, bi); + EXPECT_EQ(bi[0], GL_TRUE); // 2 -> TRUE + EXPECT_EQ(bi[1], GL_FALSE); // 0 -> FALSE + EXPECT_EQ(bi[2], GL_TRUE); + EXPECT_EQ(bi[3], GL_FALSE); + + // glColorMask (non-indexed) broadcasts to EVERY draw buffer, overwriting the per-buffer masks. + ColorMask(GL_FALSE, GL_FALSE, GL_TRUE, GL_TRUE); + GetBooleani_v(GL_COLOR_WRITEMASK, 2, bi); + EXPECT_EQ(bi[0], GL_FALSE); EXPECT_EQ(bi[2], GL_TRUE); // buffer 2 was overwritten by the broadcast + GetBooleani_v(GL_COLOR_WRITEMASK, 1, bi); + EXPECT_EQ(bi[0], GL_FALSE); EXPECT_EQ(bi[3], GL_TRUE); + + // Out-of-range index -> GL_INVALID_VALUE, no state change. + ColorMaski(kMaxDrawBuffers, GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + EXPECT_EQ(GetError(), GL_INVALID_VALUE); + GetBooleani_v(GL_COLOR_WRITEMASK, 2, bi); + EXPECT_EQ(bi[0], GL_FALSE); // unchanged (still the broadcast value) + EXPECT_EQ(bi[2], GL_TRUE); + + MG_State::pGLContext.reset(); +}