mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-09 20:58:31 +09:00
[Feat] (MG_Impl/GLImpl, MG_State, MG_Backend): implement glColorMaski
Promote the color writemask to per-draw-buffer state and implement the indexed glColorMaski entry point (previously a stub), plus its read-back through glGetBooleani_v. - RenderState: replace the single BoolVec4 ColorMask with an array of MAX_DRAW_BUFFERS masks, all initialized to true. SetColorMask now broadcasts to every draw buffer (glColorMask semantics); GetColorMask returns draw buffer 0. Add indexed set/get accessors + GLContext wrappers. - glColorMaski sets only the addressed draw buffer; out-of-range index raises GL_INVALID_VALUE (buf is a GLuint, so no GL_INVALID_ENUM path), mirroring the indexed blend entry points' MAX_DRAW_BUFFERS bound. - glGetBooleani_v(GL_COLOR_WRITEMASK, i) reports draw buffer i's four booleans; the non-indexed glGetBooleanv still reports draw buffer 0. - Fix GLboolean coercion in the color-mask path: any nonzero value enables the component (was == GL_TRUE, which wrongly rejected e.g. 2). - DirectGLES sync reads ColorMasks[0] (GLES core has only non-indexed glColorMask). Tests: ColorMaskIndexedStoresAndReadsBack covers the per-buffer vs broadcast semantics, buffer-0 read-back, out-of-range INVALID_VALUE, and the GLboolean coercion (mutation-verified: == GL_TRUE fails it). Full SanityTest sweep green (30/30).
This commit is contained in:
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<GenericErrorInfo>("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<GenericErrorInfo>(
|
||||
"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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 --------------------
|
||||
|
||||
@@ -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<BoolVec4, MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS> 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);
|
||||
|
||||
@@ -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<MG_State::GLState::GLContext>();
|
||||
|
||||
// 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<GLboolean>(2), static_cast<GLboolean>(0),
|
||||
static_cast<GLboolean>(2), static_cast<GLboolean>(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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user