// MobileGL - MobileGL/MG_State/GLState/RenderState/RenderState.cpp // Copyright (c) 2025-2026 MobileGL-Dev // Licensed under the GNU Lesser General Public License v3.0: // https://www.gnu.org/licenses/gpl-3.0.txt // https://www.gnu.org/licenses/lgpl-3.0.txt // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header #include "RenderState.h" #include "MG_Util/Types.h" namespace MobileGL { namespace MG_State { namespace GLState { namespace { SizeT GetStencilFaceIndex(StencilFace face) { switch (face) { case StencilFace::Front: return 0; case StencilFace::Back: return 1; default: MOBILEGL_ASSERT(false, "Invalid stencil face enum: %d", static_cast(face)); return 0; } } } // namespace 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; } const RenderStateParameters& RenderState::GetAllParameters() const { return m_parameters; } // -------------------- Rasterization -------------------- void RenderState::SetViewport(IntVec4 viewport) { if (m_parameters.Viewport == viewport) return; m_parameters.Viewport = viewport; ++m_version; } const IntVec4& RenderState::GetViewport() const { return m_parameters.Viewport; } void RenderState::SetLineWidth(Float width) { if (m_parameters.LineWidth == width) return; m_parameters.LineWidth = width; ++m_version; } Float RenderState::GetLineWidth() const { return m_parameters.LineWidth; } void RenderState::SetHint(GLenum target, GLenum mode) { GLenum* slot = nullptr; switch (target) { case GL_LINE_SMOOTH_HINT: slot = &m_parameters.LineSmoothHint; break; case GL_POLYGON_SMOOTH_HINT: slot = &m_parameters.PolygonSmoothHint; break; case GL_TEXTURE_COMPRESSION_HINT: slot = &m_parameters.TextureCompressionHint; break; case GL_FRAGMENT_SHADER_DERIVATIVE_HINT: slot = &m_parameters.FragmentShaderDerivativeHint; break; default: return; } if (*slot == mode) return; *slot = mode; ++m_version; } GLenum RenderState::GetHint(GLenum target) const { switch (target) { case GL_LINE_SMOOTH_HINT: return m_parameters.LineSmoothHint; case GL_POLYGON_SMOOTH_HINT: return m_parameters.PolygonSmoothHint; case GL_TEXTURE_COMPRESSION_HINT: return m_parameters.TextureCompressionHint; case GL_FRAGMENT_SHADER_DERIVATIVE_HINT: return m_parameters.FragmentShaderDerivativeHint; default: return GL_DONT_CARE; } } void RenderState::SetPointFadeThresholdSize(Float size) { if (m_parameters.PointFadeThresholdSize == size) return; m_parameters.PointFadeThresholdSize = size; ++m_version; } Float RenderState::GetPointFadeThresholdSize() const { return m_parameters.PointFadeThresholdSize; } void RenderState::SetPointSpriteCoordOrigin(GLenum origin) { if (m_parameters.PointSpriteCoordOrigin == origin) return; m_parameters.PointSpriteCoordOrigin = origin; ++m_version; } GLenum RenderState::GetPointSpriteCoordOrigin() const { return m_parameters.PointSpriteCoordOrigin; } void RenderState::SetClampReadColor(GLenum clamp) { if (m_parameters.ClampReadColor == clamp) return; m_parameters.ClampReadColor = clamp; ++m_version; } GLenum RenderState::GetClampReadColor() const { return m_parameters.ClampReadColor; } void RenderState::SetPolygonMode(GLenum front, GLenum back) { if (m_parameters.PolygonModeFront == front && m_parameters.PolygonModeBack == back) return; m_parameters.PolygonModeFront = front; m_parameters.PolygonModeBack = back; ++m_version; } GLenum RenderState::GetPolygonModeFront() const { return m_parameters.PolygonModeFront; } GLenum RenderState::GetPolygonModeBack() const { return m_parameters.PolygonModeBack; } void RenderState::SetPointSize(Float size) { if (m_parameters.PointSize == size) return; m_parameters.PointSize = size; ++m_version; } Float RenderState::GetPointSize() const { return m_parameters.PointSize; } void RenderState::SetPolygonOffset(Float factor, Float units) { if (m_parameters.PolygonOffsetFactor == factor && m_parameters.PolygonOffsetUnits == units) return; m_parameters.PolygonOffsetFactor = factor; m_parameters.PolygonOffsetUnits = units; ++m_version; } Float RenderState::GetPolygonOffsetFactor() const { return m_parameters.PolygonOffsetFactor; } Float RenderState::GetPolygonOffsetUnits() const { return m_parameters.PolygonOffsetUnits; } // -------------------- Capabilities -------------------- void RenderState::SetCapability(CapabilityInput cap, Bool enabled) { #define SET_CAPABILITY(capability, flag) \ case CapabilityInput::capability: \ if (m_parameters.capability##Enabled == (flag)) break; \ m_parameters.capability##Enabled = (flag); \ ++m_version; \ break; switch (cap) { SET_CAPABILITY(ColorLogicOp, enabled); SET_CAPABILITY(DebugOutput, enabled); SET_CAPABILITY(DebugOutputSynchronous, enabled); SET_CAPABILITY(DepthTest, enabled); SET_CAPABILITY(CullFace, enabled); SET_CAPABILITY(Dither, enabled); SET_CAPABILITY(LineSmooth, enabled); SET_CAPABILITY(Multisample, enabled); SET_CAPABILITY(PolygonOffsetFill, enabled); SET_CAPABILITY(PolygonOffsetLine, enabled); SET_CAPABILITY(PolygonOffsetPoint, enabled); SET_CAPABILITY(PolygonSmooth, enabled); SET_CAPABILITY(PrimitiveRestart, enabled); SET_CAPABILITY(PrimitiveRestartFixedIndex, enabled); SET_CAPABILITY(RasterizerDiscard, enabled); SET_CAPABILITY(SampleAlphaToCoverage, enabled); SET_CAPABILITY(SampleAlphaToOne, enabled); SET_CAPABILITY(SampleCoverage, enabled); SET_CAPABILITY(SampleMask, enabled); SET_CAPABILITY(ScissorTest, enabled); SET_CAPABILITY(StencilTest, enabled); SET_CAPABILITY(ProgramPointSize, enabled); case CapabilityInput::Blend: { Bool stateChanged = false; for (auto& blendState : m_parameters.BlendStates) { if (blendState.Enabled == enabled) continue; blendState.Enabled = enabled; stateChanged = true; } if (stateChanged) ++m_version; break; } default: // not supported currently break; } #undef SET_CAPABILITY } Bool RenderState::IsCapabilityEnabled(CapabilityInput cap) const { #define RETURN_CAPABILITY(capability) \ case CapabilityInput::capability: \ return m_parameters.capability##Enabled; switch (cap) { RETURN_CAPABILITY(ColorLogicOp); RETURN_CAPABILITY(DebugOutput); RETURN_CAPABILITY(DebugOutputSynchronous); RETURN_CAPABILITY(DepthTest); RETURN_CAPABILITY(CullFace); RETURN_CAPABILITY(Dither); RETURN_CAPABILITY(LineSmooth); RETURN_CAPABILITY(Multisample); RETURN_CAPABILITY(PolygonOffsetFill); RETURN_CAPABILITY(PolygonOffsetLine); RETURN_CAPABILITY(PolygonOffsetPoint); RETURN_CAPABILITY(PolygonSmooth); RETURN_CAPABILITY(PrimitiveRestart); RETURN_CAPABILITY(PrimitiveRestartFixedIndex); RETURN_CAPABILITY(RasterizerDiscard); RETURN_CAPABILITY(SampleAlphaToCoverage); RETURN_CAPABILITY(SampleAlphaToOne); RETURN_CAPABILITY(SampleCoverage); RETURN_CAPABILITY(SampleMask); RETURN_CAPABILITY(ScissorTest); RETURN_CAPABILITY(StencilTest); RETURN_CAPABILITY(ProgramPointSize); case CapabilityInput::Blend: return m_parameters.BlendStates[0].Enabled; default: return false; } } void RenderState::SetCapabilityIndexed(CapabilityInput cap, Uint index, Bool enabled) { // Only for BlendState currently if (cap != CapabilityInput::Blend) { THROW_UNIMPL_EXCEPTION; return; } if (index >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) { MOBILEGL_ASSERT(false, "Blend capability index out of range: %d", index); return; } if (m_parameters.BlendStates[index].Enabled == enabled) return; m_parameters.BlendStates[index].Enabled = enabled; ++m_version; } Bool RenderState::IsCapabilityEnabledIndexed(CapabilityInput cap, Uint index) const { // Only for BlendState currently if (cap != CapabilityInput::Blend) { THROW_UNIMPL_EXCEPTION; return false; } if (index >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) { MOBILEGL_ASSERT(false, "Blend capability index out of range: %d", index); return false; } return m_parameters.BlendStates[index].Enabled; } // -------------------- Blending -------------------- void RenderState::SetBlendFunc(BlendFactor srcRGB, BlendFactor dstRGB, BlendFactor srcAlpha, BlendFactor dstAlpha) { Bool stateChanged = false; for (auto& blendState : m_parameters.BlendStates) { if (blendState.SrcFactorRGB == srcRGB && blendState.DstFactorRGB == dstRGB && blendState.SrcFactorAlpha == srcAlpha && blendState.DstFactorAlpha == dstAlpha) { continue; } blendState.SrcFactorRGB = srcRGB; blendState.DstFactorRGB = dstRGB; blendState.SrcFactorAlpha = srcAlpha; blendState.DstFactorAlpha = dstAlpha; stateChanged = true; } if (!stateChanged) return; ++m_version; } void RenderState::GetBlendFunc(BlendFactor& srcRGB, BlendFactor& dstRGB, BlendFactor& srcAlpha, BlendFactor& dstAlpha) const { srcRGB = m_parameters.BlendStates[0].SrcFactorRGB; dstRGB = m_parameters.BlendStates[0].DstFactorRGB; srcAlpha = m_parameters.BlendStates[0].SrcFactorAlpha; dstAlpha = m_parameters.BlendStates[0].DstFactorAlpha; } void RenderState::SetBlendFuncIndexed(Uint index, BlendFactor srcRGB, BlendFactor dstRGB, BlendFactor srcAlpha, BlendFactor dstAlpha) { if (index >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) { MOBILEGL_ASSERT(false, "Blend function index out of range: %d", index); return; } PerBufferBlendState& blendState = m_parameters.BlendStates[index]; if (blendState.SrcFactorRGB == srcRGB && blendState.DstFactorRGB == dstRGB && blendState.SrcFactorAlpha == srcAlpha && blendState.DstFactorAlpha == dstAlpha) { return; } blendState.SrcFactorRGB = srcRGB; blendState.DstFactorRGB = dstRGB; blendState.SrcFactorAlpha = srcAlpha; blendState.DstFactorAlpha = dstAlpha; ++m_version; } void RenderState::GetBlendFuncIndexed(Uint index, BlendFactor& srcRGB, BlendFactor& dstRGB, BlendFactor& srcAlpha, BlendFactor& dstAlpha) const { if (index >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) { MOBILEGL_ASSERT(false, "Blend function index out of range: %d", index); return; } srcRGB = m_parameters.BlendStates[index].SrcFactorRGB; dstRGB = m_parameters.BlendStates[index].DstFactorRGB; srcAlpha = m_parameters.BlendStates[index].SrcFactorAlpha; dstAlpha = m_parameters.BlendStates[index].DstFactorAlpha; } void RenderState::SetBlendEquation(BlendEquation color, BlendEquation alpha) { Bool stateChanged = false; for (auto& blendState : m_parameters.BlendStates) { if (blendState.ColorEquation == color && blendState.AlphaEquation == alpha) { continue; } blendState.ColorEquation = color; blendState.AlphaEquation = alpha; stateChanged = true; } if (!stateChanged) return; ++m_version; } void RenderState::GetBlendEquation(BlendEquation& color, BlendEquation& alpha) const { color = m_parameters.BlendStates[0].ColorEquation; alpha = m_parameters.BlendStates[0].AlphaEquation; } void RenderState::SetBlendEquationIndexed(Uint index, BlendEquation color, BlendEquation alpha) { if (index >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) { MOBILEGL_ASSERT(false, "Blend equation index out of range: %d", index); return; } PerBufferBlendState& blendState = m_parameters.BlendStates[index]; if (blendState.ColorEquation == color && blendState.AlphaEquation == alpha) { return; } blendState.ColorEquation = color; blendState.AlphaEquation = alpha; ++m_version; } void RenderState::GetBlendEquationIndexed(Uint index, BlendEquation& color, BlendEquation& alpha) const { if (index >= MG_State::GLState::FramebufferObject::MAX_DRAW_BUFFERS) { MOBILEGL_ASSERT(false, "Blend equation index out of range: %d", index); return; } color = m_parameters.BlendStates[index].ColorEquation; alpha = m_parameters.BlendStates[index].AlphaEquation; } void RenderState::SetLogicOp(LogicOperation logicOp) { if (m_parameters.LogicOp == logicOp) return; m_parameters.LogicOp = logicOp; ++m_version; } LogicOperation RenderState::GetLogicOp() const { return m_parameters.LogicOp; } // -------------------- Depth -------------------- void RenderState::SetDepthFunc(DepthTestFunc func) { if (m_parameters.DepthFunc == func) return; m_parameters.DepthFunc = func; ++m_version; } DepthTestFunc RenderState::GetDepthFunc() const { return m_parameters.DepthFunc; } void RenderState::SetDepthMask(Bool flag) { if (m_parameters.DepthMask == flag) return; m_parameters.DepthMask = flag; ++m_version; } Bool RenderState::GetDepthMask() const { return m_parameters.DepthMask; } void RenderState::SetStencilFunc(StencilFace face, DepthTestFunc func, Int ref, Uint32 mask) { StencilFaceState& state = m_parameters.StencilStates[GetStencilFaceIndex(face)]; if (state.Func == func && state.Ref == ref && state.ValueMask == mask) return; state.Func = func; state.Ref = ref; state.ValueMask = mask; ++m_version; } void RenderState::SetStencilMask(StencilFace face, Uint32 mask) { StencilFaceState& state = m_parameters.StencilStates[GetStencilFaceIndex(face)]; if (state.WriteMask == mask) return; state.WriteMask = mask; ++m_version; } void RenderState::SetStencilOp(StencilFace face, StencilOperation fail, StencilOperation depthFail, StencilOperation depthPass) { StencilFaceState& state = m_parameters.StencilStates[GetStencilFaceIndex(face)]; if (state.FailOp == fail && state.PassDepthFailOp == depthFail && state.PassDepthPassOp == depthPass) { return; } state.FailOp = fail; state.PassDepthFailOp = depthFail; state.PassDepthPassOp = depthPass; ++m_version; } const StencilFaceState& RenderState::GetStencilState(StencilFace face) const { return m_parameters.StencilStates[GetStencilFaceIndex(face)]; } // -------------------- Color Mask -------------------- void RenderState::SetColorMask(BoolVec4 mask) { // 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 { // 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 -------------------- void RenderState::SetClearColor(FloatVec4 color) { if (m_parameters.ClearColor == color) return; m_parameters.ClearColor = color; ++m_version; } const FloatVec4& RenderState::GetClearColor() const { return m_parameters.ClearColor; } void RenderState::SetClearDepth(Float depth) { if (m_parameters.ClearDepth == depth) return; m_parameters.ClearDepth = depth; ++m_version; } Float RenderState::GetClearDepth() const { return m_parameters.ClearDepth; } void RenderState::SetClearStencil(Int stencil) { if (m_parameters.ClearStencil == stencil) return; m_parameters.ClearStencil = stencil; ++m_version; } Uint32 RenderState::GetClearStencil() const { return m_parameters.ClearStencil; } void RenderState::SetBlendColor(FloatVec4 color) { if (m_parameters.BlendColor == color) return; m_parameters.BlendColor = color; ++m_version; } const FloatVec4& RenderState::GetBlendColor() const { return m_parameters.BlendColor; } void RenderState::SetDepthRange(FloatVec2 range) { if (m_parameters.DepthRange == range) return; m_parameters.DepthRange = range; ++m_version; } const FloatVec2& RenderState::GetDepthRange() const { return m_parameters.DepthRange; } void RenderState::SetSampleCoverage(Float value, Bool invert) { if (m_parameters.SampleCoverageValue == value && m_parameters.SampleCoverageInvert == invert) return; m_parameters.SampleCoverageValue = value; m_parameters.SampleCoverageInvert = invert; ++m_version; } Float RenderState::GetSampleCoverageValue() const { return m_parameters.SampleCoverageValue; } Bool RenderState::GetSampleCoverageInvert() const { return m_parameters.SampleCoverageInvert; } void RenderState::SetSampleMaskValue(Uint32 mask) { if (m_parameters.SampleMaskValue == mask) return; m_parameters.SampleMaskValue = mask; ++m_version; } Uint32 RenderState::GetSampleMaskValue() const { return m_parameters.SampleMaskValue; } // -------------------- Pixel Store -------------------- void RenderState::SetPixelStoreParam(PixelStoreParam param, Int value) { #define SET_PIXEL_STORE_PARAM(paramNameHead, paramNameTail, val) \ case PixelStoreParam::paramNameHead##paramNameTail: \ if (m_pixelStore##paramNameHead##Parameters.paramNameTail == (val)) break; \ m_pixelStore##paramNameHead##Parameters.paramNameTail = (val); \ break; switch (param) { SET_PIXEL_STORE_PARAM(Pack, Alignment, value); SET_PIXEL_STORE_PARAM(Pack, RowLength, value); SET_PIXEL_STORE_PARAM(Pack, ImageHeight, value); SET_PIXEL_STORE_PARAM(Pack, SkipPixels, value); SET_PIXEL_STORE_PARAM(Pack, SkipRows, value); SET_PIXEL_STORE_PARAM(Pack, SkipImages, value); SET_PIXEL_STORE_PARAM(Pack, SwapBytes, value != 0); SET_PIXEL_STORE_PARAM(Pack, LSBFirst, value != 0); SET_PIXEL_STORE_PARAM(Unpack, Alignment, value); SET_PIXEL_STORE_PARAM(Unpack, RowLength, value); SET_PIXEL_STORE_PARAM(Unpack, ImageHeight, value); SET_PIXEL_STORE_PARAM(Unpack, SkipPixels, value); SET_PIXEL_STORE_PARAM(Unpack, SkipRows, value); SET_PIXEL_STORE_PARAM(Unpack, SkipImages, value); SET_PIXEL_STORE_PARAM(Unpack, SwapBytes, value != 0); SET_PIXEL_STORE_PARAM(Unpack, LSBFirst, value != 0); default: MOBILEGL_ASSERT(false, "Invalid PixelStoreParam enum: %d", static_cast(param)); return; } } Int RenderState::GetPixelStoreParam(PixelStoreParam param) const { #define RETURN_PIXEL_STORE_PARAM(paramNameHead, paramNameTail) \ case PixelStoreParam::paramNameHead##paramNameTail: \ return m_pixelStore##paramNameHead##Parameters.paramNameTail; switch (param) { RETURN_PIXEL_STORE_PARAM(Pack, Alignment); RETURN_PIXEL_STORE_PARAM(Pack, RowLength); RETURN_PIXEL_STORE_PARAM(Pack, ImageHeight); RETURN_PIXEL_STORE_PARAM(Pack, SkipPixels); RETURN_PIXEL_STORE_PARAM(Pack, SkipRows); RETURN_PIXEL_STORE_PARAM(Pack, SkipImages); RETURN_PIXEL_STORE_PARAM(Pack, SwapBytes); RETURN_PIXEL_STORE_PARAM(Pack, LSBFirst); RETURN_PIXEL_STORE_PARAM(Unpack, Alignment); RETURN_PIXEL_STORE_PARAM(Unpack, RowLength); RETURN_PIXEL_STORE_PARAM(Unpack, ImageHeight); RETURN_PIXEL_STORE_PARAM(Unpack, SkipPixels); RETURN_PIXEL_STORE_PARAM(Unpack, SkipRows); RETURN_PIXEL_STORE_PARAM(Unpack, SkipImages); RETURN_PIXEL_STORE_PARAM(Unpack, SwapBytes); RETURN_PIXEL_STORE_PARAM(Unpack, LSBFirst); default: MOBILEGL_ASSERT(false, "Invalid PixelStoreParam enum: %d", static_cast(param)); return 0; } } PixelStoreParameters RenderState::GetPixelStoreParameters(Bool isUnpack) const { return isUnpack ? m_pixelStoreUnpackParameters : m_pixelStorePackParameters; } // -------------------- Cull Face -------------------- void RenderState::SetCullFaceMode(CullFaceMode mode) { if (m_parameters.CullFaceModeSetting == mode) return; m_parameters.CullFaceModeSetting = mode; ++m_version; } CullFaceMode RenderState::GetCullFaceMode() const { return m_parameters.CullFaceModeSetting; } void RenderState::SetFrontFaceMode(FrontFaceMode mode) { if (m_parameters.FrontFaceModeSetting == mode) return; m_parameters.FrontFaceModeSetting = mode; ++m_version; } FrontFaceMode RenderState::GetFrontFaceMode() const { return m_parameters.FrontFaceModeSetting; } void RenderState::SetProvokingVertexMode(ProvokingVertexMode mode) { if (m_parameters.ProvokingVertexModeSetting == mode) return; m_parameters.ProvokingVertexModeSetting = mode; ++m_version; } ProvokingVertexMode RenderState::GetProvokingVertexMode() const { return m_parameters.ProvokingVertexModeSetting; } // --------------------- Scissor --------------------- void RenderState::SetScissorBox(IntVec4 box) { if (m_parameters.ScissorBox == box) return; m_parameters.ScissorBox = box; ++m_version; } const IntVec4& RenderState::GetScissorBox() const { return m_parameters.ScissorBox; } } // namespace GLState } // namespace MG_State } // namespace MobileGL