From e460536119b8588ee8b680e4f04ecf7578b7a470 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 10 Jul 2026 20:35:08 -0400 Subject: [PATCH] [Feat] (MG_Impl/GLImpl, MG_State): implement glHint, glPointParameter*, glPixelStoref, glGetDoublev Six pure-state entry points that were stubs or empty // TODO bodies, all backed by new context state and read back through glGet*. * glHint: Hint_State was an empty TODO. Store the 4 GL 3.3 core hint targets (LINE_SMOOTH, POLYGON_SMOOTH, TEXTURE_COMPRESSION, FRAGMENT_SHADER_DERIVATIVE), default GL_DONT_CARE. Validate target and mode (FASTEST/NICEST/DONT_CARE) -> GL_INVALID_ENUM otherwise. The compatibility-only targets (GL_PERSPECTIVE_CORRECTION_HINT, GL_POINT_SMOOTH_HINT, GL_FOG_HINT, GL_GENERATE_MIPMAP_HINT) are rejected. The glGetIntegerv hint cases, previously hardcoded to GL_DONT_CARE, now read the stored value; glGetBooleanv on a hint is always GL_TRUE. * glPointParameter{f,i,fv,iv}: the scalar _State bodies were empty TODOs and the *v forms were stubs. Only the 2 core pnames are accepted: GL_POINT_FADE_THRESHOLD_SIZE (float, default 1.0, GL_INVALID_VALUE if negative) and GL_POINT_SPRITE_COORD_ORIGIN (GL_LOWER_LEFT/GL_UPPER_LEFT, default GL_UPPER_LEFT, GL_INVALID_ENUM on a bad value -- note the different error code from the fade case). The compat pnames (POINT_SIZE_MIN/MAX, POINT_DISTANCE_ATTENUATION) are rejected. All four forms funnel through one (pname, float) handler. glGetIntegerv(GL_POINT_FADE_THRESHOLD_SIZE) was hardcoded to 1; it now rounds the stored float, glGetFloatv reads the float directly (keeping the fractional part), and GL_POINT_SPRITE_COORD_ORIGIN gained a getter case (it had none). * glPixelStoref: funnels into the existing glPixelStorei state, but converts per type -- boolean pnames (PACK/UNPACK_SWAP_BYTES/LSB_FIRST) by a zero-test so 0.4 -> TRUE, integer pnames by round-to-nearest. A blanket round would wrongly turn a fractional true flag into false. * glGetDoublev: funnels through glGetFloatv and widens, writing exactly the pname's component count (1/2/4) so a single-component query cannot overrun the caller's buffer. MobileGL stores no native double state (depth range/clear are float), so widening from float matches its real resolution. State added to RenderStateParameters + RenderState Set/Get + GLContext wrappers, following the existing LineWidth/DepthRange pattern. Covered by 4 SanityTest cases (set-then-get round trips, the core-vs-compat enum rejections, the two different error codes, and the glPixelStoref boolean zero-test, which was verified to fail against a blanket-round implementation). --- .../MG_Impl/GLImpl/Exporting/Definitions.cpp | 8 +- MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp | 58 ++++++- MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.h | 1 + .../GLImpl/RenderState/GL_RenderState.cpp | 110 +++++++++++- .../GLImpl/RenderState/GL_RenderState.h | 3 + MobileGL/MG_State/GLState/Core.cpp | 24 +++ MobileGL/MG_State/GLState/Core.h | 6 + .../GLState/RenderState/RenderState.cpp | 44 +++++ .../GLState/RenderState/RenderState.h | 17 ++ MobileGL/MG_Test/SanityTest.cpp | 157 ++++++++++++++++++ 10 files changed, 415 insertions(+), 13 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index ad77b9ce..d44d683d 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -453,7 +453,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetClipPlane, GLenum plane, GLdouble* equati DECLARE_GL_FUNCTION_HEAD(void, DrawBuffer, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, DrawBuffer, mode) DECLARE_GL_FUNCTION_STUB_HEAD(void, EnableClientState, GLenum cap) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, EnableClientState, cap) DECLARE_GL_FUNCTION_STUB_HEAD(void, DisableClientState, GLenum cap) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, DisableClientState, cap) -DECLARE_GL_FUNCTION_STUB_HEAD(void, GetDoublev, GLenum pname, GLdouble* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetDoublev, pname, params) +DECLARE_GL_FUNCTION_HEAD(void, GetDoublev, GLenum pname, GLdouble* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetDoublev, pname, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, PushAttrib, GLbitfield mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PushAttrib, mask) DECLARE_GL_FUNCTION_STUB_HEAD(void, PopAttrib) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PopAttrib) DECLARE_GL_FUNCTION_STUB_HEAD(void, PushClientAttrib, GLbitfield mask) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PushClientAttrib, mask) @@ -655,7 +655,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMaterialfv, GLenum face, GLenum pname, GL DECLARE_GL_FUNCTION_STUB_HEAD(void, GetMaterialiv, GLenum face, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetMaterialiv, face, pname, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, ColorMaterial, GLenum face, GLenum mode) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ColorMaterial, face, mode) DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelZoom, GLfloat xfactor, GLfloat yfactor) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelZoom, xfactor, yfactor) -DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelStoref, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelStoref, pname, param) +DECLARE_GL_FUNCTION_HEAD(void, PixelStoref, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PixelStoref, pname, param) DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelTransferf, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelTransferf, pname, param) DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelTransferi, GLenum pname, GLint param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelTransferi, pname, param) DECLARE_GL_FUNCTION_STUB_HEAD(void, PixelMapfv, GLenum map, GLsizei mapsize, const GLfloat* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PixelMapfv, map, mapsize, values) @@ -788,9 +788,9 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, MultTransposeMatrixd, const GLdouble* m) DEC DECLARE_GL_FUNCTION_STUB_HEAD(void, MultiDrawArrays, GLenum mode, const GLint* first, const GLsizei* count, GLsizei drawcount) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MultiDrawArrays, mode, first, count, drawcount) DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElements, GLenum mode, const GLsizei* count, GLenum type, const void* const* indices, GLsizei drawcount) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawElements, mode, count, type, indices, drawcount) DECLARE_GL_FUNCTION_HEAD(void, PointParameterf, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameterf, pname, param) -DECLARE_GL_FUNCTION_STUB_HEAD(void, PointParameterfv, GLenum pname, const GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PointParameterfv, pname, params) +DECLARE_GL_FUNCTION_HEAD(void, PointParameterfv, GLenum pname, const GLfloat* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameterfv, pname, params) DECLARE_GL_FUNCTION_HEAD(void, PointParameteri, GLenum pname, GLint param) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameteri, pname, param) -DECLARE_GL_FUNCTION_STUB_HEAD(void, PointParameteriv, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PointParameteriv, pname, params) +DECLARE_GL_FUNCTION_HEAD(void, PointParameteriv, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PointParameteriv, pname, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, FogCoordf, GLfloat coord) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FogCoordf, coord) DECLARE_GL_FUNCTION_STUB_HEAD(void, FogCoordfv, const GLfloat* coord) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FogCoordfv, coord) DECLARE_GL_FUNCTION_STUB_HEAD(void, FogCoordd, GLdouble coord) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, FogCoordd, coord) diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index 8057347a..379d9916 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -7,6 +7,7 @@ // End of Source File Header #include "GL_Getter.h" +#include #include #include #include @@ -562,6 +563,10 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_SAMPLE_COVERAGE_VALUE: params[0] = MG_State::pGLContext->GetSampleCoverageValue(); return; + case GL_POINT_FADE_THRESHOLD_SIZE: + // Float state: read it directly so the fractional part is not lost to the integer path. + params[0] = MG_State::pGLContext->GetPointFadeThresholdSize(); + return; default: break; } @@ -837,6 +842,46 @@ namespace MobileGL::MG_Impl::GLImpl { } } + // glGetDoublev shares GetFloatv's accepted-pname set (and its INVALID_ENUM handling) and widens the + // result. MobileGL stores no native-double state (depth range/clear are float), so widening from + // float matches the resolution MobileGL actually holds. Only the pname's own component count is + // written, never a fixed 4, so a 1-component query cannot overrun the caller's buffer. + void GetDoublev(GLenum pname, GLdouble* params) { + if (!params) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "params pointer cannot be null")); + return; + } + GLfloat floats[4] = {}; + GetFloatv(pname, floats); + GLsizei count = 1; + switch (pname) { + case GL_DEPTH_RANGE: + case GL_VIEWPORT_BOUNDS_RANGE: + case GL_ALIASED_LINE_WIDTH_RANGE: + case GL_ALIASED_POINT_SIZE_RANGE: + case GL_POINT_SIZE_RANGE: + case GL_SMOOTH_LINE_WIDTH_RANGE: + case GL_MAX_VIEWPORT_DIMS: + count = 2; + break; + case GL_BLEND_COLOR: + case GL_COLOR_CLEAR_VALUE: + case GL_VIEWPORT: + case GL_SCISSOR_BOX: + case GL_COLOR_WRITEMASK: + count = 4; + break; + default: + count = 1; + break; + } + for (GLsizei i = 0; i < count; ++i) { + params[i] = static_cast(floats[i]); + } + } + void GetIntegerv(GLenum pname, GLint* params) { MGLOG_D("glGetIntegerv, pname: %s", MG_Util::ConvertGLEnumToString(pname).c_str()); if (!params) { @@ -1063,7 +1108,7 @@ namespace MobileGL::MG_Impl::GLImpl { return; } case GL_FRAGMENT_SHADER_DERIVATIVE_HINT: - *params = GL_DONT_CARE; + *params = static_cast(MG_State::pGLContext->GetHint(pname)); return; case GL_IMPLEMENTATION_COLOR_READ_FORMAT: { GLint format = 0; @@ -1081,7 +1126,7 @@ namespace MobileGL::MG_Impl::GLImpl { *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::LineSmooth) ? GL_TRUE : GL_FALSE; return; case GL_LINE_SMOOTH_HINT: - *params = GL_DONT_CARE; + *params = static_cast(MG_State::pGLContext->GetHint(pname)); return; case GL_LINE_WIDTH: *params = static_cast(MG_State::pGLContext->GetLineWidth()); @@ -1290,7 +1335,10 @@ namespace MobileGL::MG_Impl::GLImpl { return; } case GL_POINT_FADE_THRESHOLD_SIZE: - *params = 1; + *params = static_cast(std::lround(MG_State::pGLContext->GetPointFadeThresholdSize())); + return; + case GL_POINT_SPRITE_COORD_ORIGIN: + *params = static_cast(MG_State::pGLContext->GetPointSpriteCoordOrigin()); return; case GL_PRIMITIVE_RESTART: *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::PrimitiveRestart) ? GL_TRUE @@ -1341,7 +1389,7 @@ namespace MobileGL::MG_Impl::GLImpl { *params = MG_State::pGLContext->IsCapabilityEnabled(CapabilityInput::PolygonSmooth) ? GL_TRUE : GL_FALSE; return; case GL_POLYGON_SMOOTH_HINT: - *params = GL_DONT_CARE; + *params = static_cast(MG_State::pGLContext->GetHint(pname)); return; case GL_READ_BUFFER: if (const auto& fbo = MG_State::pGLContext->GetFramebufferBindingSlot(FramebufferTarget::Read) @@ -1571,7 +1619,7 @@ namespace MobileGL::MG_Impl::GLImpl { return; } case GL_TEXTURE_COMPRESSION_HINT: - *params = GL_DONT_CARE; + *params = static_cast(MG_State::pGLContext->GetHint(pname)); return; case GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT: *params = 0; // texture-buffer range entrypoints are stubbed diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.h b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.h index 3700e2c2..4289e0c7 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.h +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.h @@ -15,6 +15,7 @@ namespace MobileGL::MG_Impl::GLImpl { const GLubyte* GetStringi(GLenum name, GLuint index); void GetBooleanv(GLenum pname, GLboolean* params); void GetFloatv(GLenum pname, GLfloat* params); + void GetDoublev(GLenum pname, GLdouble* params); void GetIntegerv(GLenum pname, GLint* params); void GetInteger64v(GLenum pname, GLint64* params); void GetIntegeri_v(GLenum target, GLuint index, GLint* data); diff --git a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp index 69cc06aa..db2e1692 100644 --- a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp +++ b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp @@ -7,6 +7,7 @@ // End of Source File Header #include "GL_RenderState.h" +#include #include #include #include @@ -207,12 +208,67 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->SetPointSize(static_cast(size)); } - void PointParameterf_State(GLenum pname, GLfloat param) { - // TODO: implement + // Single funnel for all four glPointParameter forms. The two GL 3.3 core pnames both carry one + // component, so the *v forms pass params[0], and the integer forms widen to float. The + // GL_POINT_SPRITE_COORD_ORIGIN value is an enum passed as a float (36001.0/36002.0 are exact). + void PointParameter_State(GLenum pname, GLfloat value) { + switch (pname) { + case GL_POINT_FADE_THRESHOLD_SIZE: + if (value < 0.0f) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, + "GL_POINT_FADE_THRESHOLD_SIZE must be non-negative.")); + return; + } + MG_State::pGLContext->SetPointFadeThresholdSize(value); + return; + case GL_POINT_SPRITE_COORD_ORIGIN: { + const GLenum origin = static_cast(std::lround(value)); + if (origin != GL_LOWER_LEFT && origin != GL_UPPER_LEFT) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", __func__, + "GL_POINT_SPRITE_COORD_ORIGIN must be GL_LOWER_LEFT or " + "GL_UPPER_LEFT.")); + return; + } + MG_State::pGLContext->SetPointSpriteCoordOrigin(origin); + return; + } + default: + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", __func__, + "Unsupported point parameter pname: " + std::to_string(pname))); + return; + } } + void PointParameterf_State(GLenum pname, GLfloat param) { PointParameter_State(pname, param); } + void PointParameteri_State(GLenum pname, GLint param) { - // TODO: implement + PointParameter_State(pname, static_cast(param)); + } + + void PointParameterfv_State(GLenum pname, const GLfloat* params) { + if (!params) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "params pointer cannot be null.")); + return; + } + PointParameter_State(pname, params[0]); + } + + void PointParameteriv_State(GLenum pname, const GLint* params) { + if (!params) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, "params pointer cannot be null.")); + return; + } + PointParameter_State(pname, static_cast(params[0])); } void PixelStorei_State(GLenum pname, GLint param) { @@ -302,7 +358,27 @@ namespace MobileGL::MG_Impl::GLImpl { } void Hint_State(GLenum target, GLenum mode) { - // TODO: implement + switch (target) { + case GL_LINE_SMOOTH_HINT: + case GL_POLYGON_SMOOTH_HINT: + case GL_TEXTURE_COMPRESSION_HINT: + case GL_FRAGMENT_SHADER_DERIVATIVE_HINT: + break; + default: + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", __func__, + "Unsupported hint target: " + std::to_string(target))); + return; + } + if (mode != GL_FASTEST && mode != GL_NICEST && mode != GL_DONT_CARE) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", __func__, + "Hint mode must be GL_FASTEST, GL_NICEST or GL_DONT_CARE.")); + return; + } + MG_State::pGLContext->SetHint(target, mode); } void FrontFace_State(GLenum mode) { @@ -672,10 +748,36 @@ namespace MobileGL::MG_Impl::GLImpl { PointParameteri_State(pname, param); } + void PointParameterfv(GLenum pname, const GLfloat* params) { + PointParameterfv_State(pname, params); + } + + void PointParameteriv(GLenum pname, const GLint* params) { + PointParameteriv_State(pname, params); + } + void PixelStorei(GLenum pname, GLint param) { PixelStorei_State(pname, param); } + void PixelStoref(GLenum pname, GLfloat param) { + // Boolean pixel-store pnames convert by a zero-test (0.4 -> TRUE); integer pnames round to + // nearest. Branch before converting so a fractional value cannot round a true flag to false. + GLint intParam; + switch (pname) { + case GL_PACK_SWAP_BYTES: + case GL_UNPACK_SWAP_BYTES: + case GL_PACK_LSB_FIRST: + case GL_UNPACK_LSB_FIRST: + intParam = (param != 0.0f) ? 1 : 0; + break; + default: + intParam = static_cast(std::lround(param)); + break; + } + PixelStorei_State(pname, intParam); + } + void LogicOp(GLenum opcode) { LogicOp_State(opcode); } diff --git a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h index 24c55f26..e6b00b2f 100644 --- a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h +++ b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h @@ -33,7 +33,10 @@ namespace MobileGL::MG_Impl::GLImpl { void PointSize(GLfloat size); void PointParameterf(GLenum pname, GLfloat param); void PointParameteri(GLenum pname, GLint param); + void PointParameterfv(GLenum pname, const GLfloat* params); + void PointParameteriv(GLenum pname, const GLint* params); void PixelStorei(GLenum pname, GLint param); + void PixelStoref(GLenum pname, GLfloat param); void LogicOp(GLenum opcode); void LineWidth(GLfloat width); GLboolean IsEnabledi(GLenum target, GLuint index); diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index 39cecbd5..1b7ffee2 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -328,6 +328,30 @@ namespace MobileGL::MG_State { return m_renderState.GetLineWidth(); } + void GLContext::SetHint(GLenum target, GLenum mode) { + m_renderState.SetHint(target, mode); + } + + GLenum GLContext::GetHint(GLenum target) const { + return m_renderState.GetHint(target); + } + + void GLContext::SetPointFadeThresholdSize(Float size) { + m_renderState.SetPointFadeThresholdSize(size); + } + + Float GLContext::GetPointFadeThresholdSize() const { + return m_renderState.GetPointFadeThresholdSize(); + } + + void GLContext::SetPointSpriteCoordOrigin(GLenum origin) { + m_renderState.SetPointSpriteCoordOrigin(origin); + } + + GLenum GLContext::GetPointSpriteCoordOrigin() const { + return m_renderState.GetPointSpriteCoordOrigin(); + } + void GLContext::SetPointSize(Float size) { m_renderState.SetPointSize(size); } diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 291f2a44..c6559214 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -125,6 +125,12 @@ namespace MobileGL { void SetPolygonOffset(Float factor, Float units); Float GetPolygonOffsetFactor() const; Float GetPolygonOffsetUnits() const; + void SetHint(GLenum target, GLenum mode); + GLenum GetHint(GLenum target) const; + void SetPointFadeThresholdSize(Float size); + Float GetPointFadeThresholdSize() const; + void SetPointSpriteCoordOrigin(GLenum origin); + GLenum GetPointSpriteCoordOrigin() const; void SetCapability(CapabilityInput cap, Bool enabled); Bool IsCapabilityEnabled(CapabilityInput cap) const; void SetCapabilityIndexed(CapabilityInput cap, Uint index, Bool enabled); diff --git a/MobileGL/MG_State/GLState/RenderState/RenderState.cpp b/MobileGL/MG_State/GLState/RenderState/RenderState.cpp index f7bcd382..6488b50f 100644 --- a/MobileGL/MG_State/GLState/RenderState/RenderState.cpp +++ b/MobileGL/MG_State/GLState/RenderState/RenderState.cpp @@ -59,6 +59,50 @@ namespace MobileGL { 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::SetPointSize(Float size) { if (m_parameters.PointSize == size) return; diff --git a/MobileGL/MG_State/GLState/RenderState/RenderState.h b/MobileGL/MG_State/GLState/RenderState/RenderState.h index eca56acc..0bfc922b 100644 --- a/MobileGL/MG_State/GLState/RenderState/RenderState.h +++ b/MobileGL/MG_State/GLState/RenderState/RenderState.h @@ -250,6 +250,16 @@ namespace MobileGL { FrontFaceMode FrontFaceModeSetting = FrontFaceMode::CounterClockwise; ProvokingVertexMode ProvokingVertexModeSetting = ProvokingVertexMode::LastVertex; + // Hints (glHint). All GL 3.3 core hint targets default to GL_DONT_CARE. + GLenum LineSmoothHint = GL_DONT_CARE; + GLenum PolygonSmoothHint = GL_DONT_CARE; + GLenum TextureCompressionHint = GL_DONT_CARE; + GLenum FragmentShaderDerivativeHint = GL_DONT_CARE; + + // Point parameters (glPointParameter). Only the two GL 3.3 core pnames. + Float PointFadeThresholdSize = 1.0f; + GLenum PointSpriteCoordOrigin = GL_UPPER_LEFT; + // Scissor Bool ColorLogicOpEnabled = false; Bool DebugOutputEnabled = false; @@ -293,6 +303,13 @@ namespace MobileGL { void SetPolygonOffset(Float factor, Float units); Float GetPolygonOffsetFactor() const; Float GetPolygonOffsetUnits() const; + // Hints. target must be one of the 4 GL 3.3 core hint targets (validated by the caller). + void SetHint(GLenum target, GLenum mode); + GLenum GetHint(GLenum target) const; + void SetPointFadeThresholdSize(Float size); + Float GetPointFadeThresholdSize() const; + void SetPointSpriteCoordOrigin(GLenum origin); + GLenum GetPointSpriteCoordOrigin() const; // Capabilities void SetCapability(CapabilityInput cap, Bool enabled); diff --git a/MobileGL/MG_Test/SanityTest.cpp b/MobileGL/MG_Test/SanityTest.cpp index 01fc4e45..30549280 100644 --- a/MobileGL/MG_Test/SanityTest.cpp +++ b/MobileGL/MG_Test/SanityTest.cpp @@ -604,3 +604,160 @@ TEST(LogSanity, UsesEnvOverrideForFilePath) { fs::remove(logPath); } + +// ---- Pure-state entry points: glHint / glPointParameter* / glPixelStoref / glGetDoublev ----------- + +TEST(RenderStateSanity, HintStoresAndReadsBack) { + using namespace MobileGL; + using namespace MobileGL::MG_Impl::GLImpl; + MG_State::pGLContext = MakeUnique(); + + // Default is GL_DONT_CARE. + GLint value = -1; + GetIntegerv(GL_LINE_SMOOTH_HINT, &value); + EXPECT_EQ(value, GL_DONT_CARE); + + // Each of the 4 core targets round-trips. + Hint(GL_LINE_SMOOTH_HINT, GL_NICEST); + Hint(GL_POLYGON_SMOOTH_HINT, GL_FASTEST); + Hint(GL_TEXTURE_COMPRESSION_HINT, GL_NICEST); + Hint(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, GL_FASTEST); + EXPECT_EQ(GetError(), GL_NO_ERROR); + + GetIntegerv(GL_LINE_SMOOTH_HINT, &value); + EXPECT_EQ(value, GL_NICEST); + GetIntegerv(GL_POLYGON_SMOOTH_HINT, &value); + EXPECT_EQ(value, GL_FASTEST); + GetIntegerv(GL_TEXTURE_COMPRESSION_HINT, &value); + EXPECT_EQ(value, GL_NICEST); + GetIntegerv(GL_FRAGMENT_SHADER_DERIVATIVE_HINT, &value); + EXPECT_EQ(value, GL_FASTEST); + + // glGetBooleanv on a hint is always GL_TRUE (all hint enums are non-zero). + GLboolean b = GL_FALSE; + GetBooleanv(GL_LINE_SMOOTH_HINT, &b); + EXPECT_EQ(b, GL_TRUE); + + // A compatibility-only target and a bad mode both raise GL_INVALID_ENUM and change nothing. + Hint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST); + EXPECT_EQ(GetError(), GL_INVALID_ENUM); + Hint(GL_LINE_SMOOTH_HINT, GL_LINEAR); + EXPECT_EQ(GetError(), GL_INVALID_ENUM); + GetIntegerv(GL_LINE_SMOOTH_HINT, &value); + EXPECT_EQ(value, GL_NICEST); // unchanged by the failed calls + + MG_State::pGLContext.reset(); +} + +TEST(RenderStateSanity, PointParameterStoresAndReadsBack) { + using namespace MobileGL; + using namespace MobileGL::MG_Impl::GLImpl; + MG_State::pGLContext = MakeUnique(); + + // Defaults: fade threshold 1.0, coord origin GL_UPPER_LEFT. + GLfloat f = -1.0f; + GetFloatv(GL_POINT_FADE_THRESHOLD_SIZE, &f); + EXPECT_FLOAT_EQ(f, 1.0f); + GLint origin = -1; + GetIntegerv(GL_POINT_SPRITE_COORD_ORIGIN, &origin); + EXPECT_EQ(origin, GL_UPPER_LEFT); + + // Scalar float sets the fade threshold; GetFloatv keeps the fractional part, GetIntegerv rounds. + PointParameterf(GL_POINT_FADE_THRESHOLD_SIZE, 2.5f); + GetFloatv(GL_POINT_FADE_THRESHOLD_SIZE, &f); + EXPECT_FLOAT_EQ(f, 2.5f); + GLint fi = 0; + GetIntegerv(GL_POINT_FADE_THRESHOLD_SIZE, &fi); + EXPECT_EQ(fi, 3); // 2.5 rounds to nearest (to even or up; lround gives 3) + + // The v-form reads params[0]; the integer form sets the coord-origin enum. + const GLint lowerLeft = GL_LOWER_LEFT; + PointParameteriv(GL_POINT_SPRITE_COORD_ORIGIN, &lowerLeft); + GetIntegerv(GL_POINT_SPRITE_COORD_ORIGIN, &origin); + EXPECT_EQ(origin, GL_LOWER_LEFT); + EXPECT_EQ(GetError(), GL_NO_ERROR); + + // Errors: negative fade -> GL_INVALID_VALUE; bad coord-origin -> GL_INVALID_ENUM; compat pname -> + // GL_INVALID_ENUM. None change state. + PointParameterf(GL_POINT_FADE_THRESHOLD_SIZE, -1.0f); + EXPECT_EQ(GetError(), GL_INVALID_VALUE); + PointParameteri(GL_POINT_SPRITE_COORD_ORIGIN, GL_FASTEST); + EXPECT_EQ(GetError(), GL_INVALID_ENUM); + PointParameterf(GL_POINT_SIZE_MIN, 0.0f); + EXPECT_EQ(GetError(), GL_INVALID_ENUM); + + GetFloatv(GL_POINT_FADE_THRESHOLD_SIZE, &f); + EXPECT_FLOAT_EQ(f, 2.5f); // unchanged + GetIntegerv(GL_POINT_SPRITE_COORD_ORIGIN, &origin); + EXPECT_EQ(origin, GL_LOWER_LEFT); // unchanged + + MG_State::pGLContext.reset(); +} + +TEST(RenderStateSanity, PixelStorefRoundsAndZeroTestsBooleans) { + using namespace MobileGL; + using namespace MobileGL::MG_Impl::GLImpl; + MG_State::pGLContext = MakeUnique(); + + // Integer pname: round to nearest. + PixelStoref(GL_UNPACK_ROW_LENGTH, 7.4f); + GLint iv = 0; + GetIntegerv(GL_UNPACK_ROW_LENGTH, &iv); + EXPECT_EQ(iv, 7); + PixelStoref(GL_UNPACK_ROW_LENGTH, 7.6f); + GetIntegerv(GL_UNPACK_ROW_LENGTH, &iv); + EXPECT_EQ(iv, 8); + + // Boolean pname: a fractional value must map to TRUE via a zero-test, NOT round-to-zero. + PixelStoref(GL_PACK_SWAP_BYTES, 0.4f); + GLboolean bv = GL_FALSE; + GetBooleanv(GL_PACK_SWAP_BYTES, &bv); + EXPECT_EQ(bv, GL_TRUE); + PixelStoref(GL_PACK_SWAP_BYTES, 0.0f); + GetBooleanv(GL_PACK_SWAP_BYTES, &bv); + EXPECT_EQ(bv, GL_FALSE); + + // glPixelStoref matches glPixelStorei for an integer pname. + PixelStorei(GL_PACK_ALIGNMENT, 8); + GLint viaI = 0; + GetIntegerv(GL_PACK_ALIGNMENT, &viaI); + PixelStoref(GL_PACK_ALIGNMENT, 8.0f); + GLint viaF = 0; + GetIntegerv(GL_PACK_ALIGNMENT, &viaF); + EXPECT_EQ(viaI, viaF); + EXPECT_EQ(GetError(), GL_NO_ERROR); + + MG_State::pGLContext.reset(); +} + +TEST(RenderStateSanity, GetDoublevMatchesGetFloatvWidened) { + using namespace MobileGL; + using namespace MobileGL::MG_Impl::GLImpl; + MG_State::pGLContext = MakeUnique(); + + // Single-component pname. + PointParameterf(GL_POINT_FADE_THRESHOLD_SIZE, 2.5f); + GLdouble d1[4] = {-1, -1, -1, -1}; + GetDoublev(GL_POINT_FADE_THRESHOLD_SIZE, d1); + EXPECT_DOUBLE_EQ(d1[0], 2.5); + EXPECT_DOUBLE_EQ(d1[1], -1.0); // second component untouched (1-component pname) + + // 2-component pname: GL_DEPTH_RANGE (default 0..1). + GLdouble d2[2] = {-1, -1}; + GetDoublev(GL_DEPTH_RANGE, d2); + EXPECT_DOUBLE_EQ(d2[0], 0.0); + EXPECT_DOUBLE_EQ(d2[1], 1.0); + + // 4-component pname: GL_COLOR_CLEAR_VALUE (default 0,0,0,1) matches GetFloatv widened. + GLfloat cf[4] = {}; + GetFloatv(GL_COLOR_CLEAR_VALUE, cf); + GLdouble cd[4] = {}; + GetDoublev(GL_COLOR_CLEAR_VALUE, cd); + for (int i = 0; i < 4; ++i) EXPECT_DOUBLE_EQ(cd[i], static_cast(cf[i])); + + // Null params -> GL_INVALID_VALUE. + GetDoublev(GL_DEPTH_RANGE, nullptr); + EXPECT_EQ(GetError(), GL_INVALID_VALUE); + + MG_State::pGLContext.reset(); +}