[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).
This commit is contained in:
2026-07-10 20:35:08 -04:00
parent 561d8992bc
commit e460536119
10 changed files with 415 additions and 13 deletions
@@ -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)
+53 -5
View File
@@ -7,6 +7,7 @@
// End of Source File Header
#include "GL_Getter.h"
#include <cmath>
#include <Config.h>
#include <MGGitHash.h>
#include <MG_Impl/GLImpl/VertexArray/Validators.h>
@@ -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<GenericErrorInfo>("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<GLdouble>(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<GLint>(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<GLint>(MG_State::pGLContext->GetHint(pname));
return;
case GL_LINE_WIDTH:
*params = static_cast<GLint>(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<GLint>(std::lround(MG_State::pGLContext->GetPointFadeThresholdSize()));
return;
case GL_POINT_SPRITE_COORD_ORIGIN:
*params = static_cast<GLint>(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<GLint>(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<GLint>(MG_State::pGLContext->GetHint(pname));
return;
case GL_TEXTURE_BUFFER_OFFSET_ALIGNMENT:
*params = 0; // texture-buffer range entrypoints are stubbed
@@ -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);
@@ -7,6 +7,7 @@
// End of Source File Header
#include "GL_RenderState.h"
#include <cmath>
#include <MG_State/GLState/Core.h>
#include <MG_Util/Converters/GLToStr/GLEnumConverter.h>
#include <MG_Util/Converters/GLToMG/RenderStateEnumConverter.h>
@@ -207,12 +208,67 @@ namespace MobileGL::MG_Impl::GLImpl {
MG_State::pGLContext->SetPointSize(static_cast<Float>(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<GenericErrorInfo>("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<GLenum>(std::lround(value));
if (origin != GL_LOWER_LEFT && origin != GL_UPPER_LEFT) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidEnum,
MakeUnique<GenericErrorInfo>("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<GenericErrorInfo>("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<GLfloat>(param));
}
void PointParameterfv_State(GLenum pname, const GLfloat* params) {
if (!params) {
MG_State::pGLContext->RecordError(
ErrorCode::InvalidValue,
MakeUnique<GenericErrorInfo>("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<GenericErrorInfo>("MG_Impl/GLImpl", __func__, "params pointer cannot be null."));
return;
}
PointParameter_State(pname, static_cast<GLfloat>(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<GenericErrorInfo>("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<GenericErrorInfo>("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<GLint>(std::lround(param));
break;
}
PixelStorei_State(pname, intParam);
}
void LogicOp(GLenum opcode) {
LogicOp_State(opcode);
}
@@ -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);
+24
View File
@@ -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);
}
+6
View File
@@ -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);
@@ -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;
@@ -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);
+157
View File
@@ -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<MG_State::GLState::GLContext>();
// 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<MG_State::GLState::GLContext>();
// 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<MG_State::GLState::GLContext>();
// 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<MG_State::GLState::GLContext>();
// 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<GLdouble>(cf[i]));
// Null params -> GL_INVALID_VALUE.
GetDoublev(GL_DEPTH_RANGE, nullptr);
EXPECT_EQ(GetError(), GL_INVALID_VALUE);
MG_State::pGLContext.reset();
}