From 0e4302b399f943c9271b1bf5e6f19f3df4703d81 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 27 Aug 2026 04:24:41 -0400 Subject: [PATCH] [Feat] (RenderState): implement glClipControl, glPolygonOffsetClamp and glTextureBarrier instead of stubbing them --- .../MG_Impl/GLImpl/Drawing/GL_Drawing.cpp | 21 +++ MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h | 1 + .../MG_Impl/GLImpl/Exporting/Definitions.cpp | 10 +- .../GLImpl/RenderState/GL_RenderState.cpp | 40 ++++++ .../GLImpl/RenderState/GL_RenderState.h | 2 + MobileGL/MG_State/GLState/Core.cpp | 20 +++ MobileGL/MG_State/GLState/Core.h | 5 + .../GLState/RenderState/RenderState.cpp | 31 +++++ .../GLState/RenderState/RenderState.h | 17 +++ MobileGL/MG_Test/State/RenderStateTest.cpp | 131 ++++++++++++++++++ 10 files changed, 273 insertions(+), 5 deletions(-) diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index 1ec47485..7d04c5e7 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -846,6 +846,27 @@ namespace MobileGL::MG_Impl::GLImpl { memoryBarrier(barriers); } + void TextureBarrier() { + // GL 4.5 core 8.26 / GL_ARB_texture_barrier: order every write the fixed-function + // framebuffer has already issued ahead of every subsequent texture fetch, so a shader may + // read texels of a texture that is also attached to the current framebuffer. + // + // Both backends serve this through their existing memory-barrier hook rather than a new + // entry point of their own: GL_FRAMEBUFFER_BARRIER_BIT is the source half (framebuffer + // writes) and GL_TEXTURE_FETCH_BARRIER_BIT the destination half (texture fetches), which + // is exactly the dependency ARB_texture_barrier defines - just expressed with the wider + // scope glMemoryBarrier gives it. That is a superset of the required ordering, never a + // subset, so it cannot under-synchronize. + auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier; + if (!memoryBarrier) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique("MG_Impl/GLImpl", __func__, "Backend does not support memory barriers.")); + return; + } + memoryBarrier(GL_TEXTURE_FETCH_BARRIER_BIT | GL_FRAMEBUFFER_BARRIER_BIT); + } + void MemoryBarrierByRegion(GLbitfield barriers) { if (!ValidateMemoryBarrierBits(__func__, barriers)) return; auto memoryBarrierByRegion = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrierByRegion; diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h index 46be0bed..91ddf633 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h @@ -35,6 +35,7 @@ namespace MobileGL::MG_Impl::GLImpl { void PatchParameterfv(GLenum pname, const GLfloat* values); void MemoryBarrier(GLbitfield barriers); void MemoryBarrierByRegion(GLbitfield barriers); + void TextureBarrier(); void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride); void MultiDrawArraysIndirect(GLenum mode, const void* indirect, GLsizei drawcount, GLsizei stride); void MultiDrawElementsIndirectCount(GLenum mode, GLenum type, const void* indirect, GLintptr drawcount, diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 9de67de0..eb2554df 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -994,7 +994,7 @@ DECLARE_GL_FUNCTION_HEAD(void, BindTextures, GLuint first, GLsizei count, const DECLARE_GL_FUNCTION_HEAD(void, BindSamplers, GLuint first, GLsizei count, const GLuint* samplers) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindSamplers, first, count, samplers) DECLARE_GL_FUNCTION_HEAD(void, BindImageTextures, GLuint first, GLsizei count, const GLuint* textures) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindImageTextures, first, count, textures) DECLARE_GL_FUNCTION_HEAD(void, BindVertexBuffers, GLuint first, GLsizei count, const GLuint* buffers, const GLintptr* offsets, const GLsizei* strides) DECLARE_GL_FUNCTION_END_NO_RETURN(void, BindVertexBuffers, first, count, buffers, offsets, strides) -DECLARE_GL_FUNCTION_STUB_HEAD(void, ClipControl, GLenum origin, GLenum depth) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ClipControl, origin, depth) +DECLARE_GL_FUNCTION_HEAD(void, ClipControl, GLenum origin, GLenum depth) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ClipControl, origin, depth) DECLARE_GL_FUNCTION_HEAD(void, CreateTransformFeedbacks, GLsizei n, GLuint* ids) DECLARE_GL_FUNCTION_END_NO_RETURN(void, CreateTransformFeedbacks, n, ids) DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferBase, GLuint xfb, GLuint index, GLuint buffer) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferBase, xfb, index, buffer) DECLARE_GL_FUNCTION_HEAD(void, TransformFeedbackBufferRange, GLuint xfb, GLuint index, GLuint buffer, GLintptr offset, GLsizeiptr size) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TransformFeedbackBufferRange, xfb, index, buffer, offset, size) @@ -1107,11 +1107,11 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnConvolutionFilter, GLenum target, GLenum DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnSeparableFilter, GLenum target, GLenum format, GLenum type, GLsizei rowBufSize, void* row, GLsizei columnBufSize, void* column, void* span) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnSeparableFilter, target, format, type, rowBufSize, row, columnBufSize, column, span) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnHistogram, GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnHistogram, target, reset, format, type, bufSize, values) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnMinmax, GLenum target, GLboolean reset, GLenum format, GLenum type, GLsizei bufSize, void* values) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnMinmax, target, reset, format, type, bufSize, values) -DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureBarrier, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureBarrier, ) +DECLARE_GL_FUNCTION_HEAD(void, TextureBarrier, void) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureBarrier, ) DECLARE_GL_FUNCTION_STUB_HEAD(void, SpecializeShader, GLuint shader, const GLchar* pEntryPoint, GLuint numSpecializationConstants, const GLuint* pConstantIndex, const GLuint* pConstantValue) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SpecializeShader, shader, pEntryPoint, numSpecializationConstants, pConstantIndex, pConstantValue) DECLARE_GL_FUNCTION_HEAD(void, MultiDrawArraysIndirectCount, GLenum mode, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawArraysIndirectCount, mode, indirect, drawcount, maxdrawcount, stride) DECLARE_GL_FUNCTION_HEAD(void, MultiDrawElementsIndirectCount, GLenum mode, GLenum type, const void* indirect, GLintptr drawcount, GLsizei maxdrawcount, GLsizei stride) DECLARE_GL_FUNCTION_END_NO_RETURN(void, MultiDrawElementsIndirectCount, mode, type, indirect, drawcount, maxdrawcount, stride) -DECLARE_GL_FUNCTION_STUB_HEAD(void, PolygonOffsetClamp, GLfloat factor, GLfloat units, GLfloat clamp) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PolygonOffsetClamp, factor, units, clamp) +DECLARE_GL_FUNCTION_HEAD(void, PolygonOffsetClamp, GLfloat factor, GLfloat units, GLfloat clamp) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PolygonOffsetClamp, factor, units, clamp) DECLARE_GL_FUNCTION_STUB_HEAD(void, PrimitiveBoundingBoxARB, GLfloat minX, GLfloat minY, GLfloat minZ, GLfloat minW, GLfloat maxX, GLfloat maxY, GLfloat maxZ, GLfloat maxW) DECLARE_GL_FUNCTION_STUB_END(void, PrimitiveBoundingBoxARB, minX, minY, minZ, minW, maxX, maxY, maxZ, maxW) DECLARE_GL_FUNCTION_STUB_HEAD(GLuint64, GetTextureHandleARB, GLuint texture) DECLARE_GL_FUNCTION_STUB_END(GLuint64, GetTextureHandleARB, texture) DECLARE_GL_FUNCTION_STUB_HEAD(GLuint64, GetTextureSamplerHandleARB, GLuint texture, GLuint sampler) DECLARE_GL_FUNCTION_STUB_END(GLuint64, GetTextureSamplerHandleARB, texture, sampler) @@ -2049,7 +2049,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetPixelTransformParameterivEXT, GLenum targ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetPixelTransformParameterfvEXT, GLenum target, GLenum pname, GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetPixelTransformParameterfvEXT, target, pname, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, PointParameterfEXT, GLenum pname, GLfloat param) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PointParameterfEXT, pname, param) DECLARE_GL_FUNCTION_STUB_HEAD(void, PointParameterfvEXT, GLenum pname, const GLfloat* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PointParameterfvEXT, pname, params) -DECLARE_GL_FUNCTION_STUB_HEAD(void, PolygonOffsetClampEXT, GLfloat factor, GLfloat units, GLfloat clamp) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PolygonOffsetClampEXT, factor, units, clamp) +DECLARE_GL_FUNCTION_HEAD(void, PolygonOffsetClampEXT, GLfloat factor, GLfloat units, GLfloat clamp) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PolygonOffsetClamp, factor, units, clamp) DECLARE_GL_FUNCTION_HEAD(void, ProvokingVertexEXT, GLenum mode) DECLARE_GL_FUNCTION_END_NO_RETURN(void, ProvokingVertex, mode) DECLARE_GL_FUNCTION_STUB_HEAD(void, RasterSamplesEXT, GLuint samples, GLboolean fixedsamplelocations) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, RasterSamplesEXT, samples, fixedsamplelocations) DECLARE_GL_FUNCTION_STUB_HEAD(void, SecondaryColor3bEXT, GLbyte red, GLbyte green, GLbyte blue) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, SecondaryColor3bEXT, red, green, blue) @@ -2546,7 +2546,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, ShadingRateImageBarrierNV, GLboolean synchro DECLARE_GL_FUNCTION_STUB_HEAD(void, ShadingRateImagePaletteNV, GLuint viewport, GLuint first, GLsizei count, const GLenum* rates) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ShadingRateImagePaletteNV, viewport, first, count, rates) DECLARE_GL_FUNCTION_STUB_HEAD(void, ShadingRateSampleOrderNV, GLenum order) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ShadingRateSampleOrderNV, order) DECLARE_GL_FUNCTION_STUB_HEAD(void, ShadingRateSampleOrderCustomNV, GLenum rate, GLuint samples, const GLint* locations) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, ShadingRateSampleOrderCustomNV, rate, samples, locations) -DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureBarrierNV, void) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureBarrierNV, ) +DECLARE_GL_FUNCTION_HEAD(void, TextureBarrierNV, void) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TextureBarrier, ) DECLARE_GL_FUNCTION_STUB_HEAD(void, TexImage2DMultisampleCoverageNV, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexImage2DMultisampleCoverageNV, target, coverageSamples, colorSamples, internalFormat, width, height, fixedSampleLocations) DECLARE_GL_FUNCTION_STUB_HEAD(void, TexImage3DMultisampleCoverageNV, GLenum target, GLsizei coverageSamples, GLsizei colorSamples, GLint internalFormat, GLsizei width, GLsizei height, GLsizei depth, GLboolean fixedSampleLocations) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TexImage3DMultisampleCoverageNV, target, coverageSamples, colorSamples, internalFormat, width, height, depth, fixedSampleLocations) DECLARE_GL_FUNCTION_STUB_HEAD(void, TextureImage2DMultisampleNV, GLuint texture, GLenum target, GLsizei samples, GLint internalFormat, GLsizei width, GLsizei height, GLboolean fixedSampleLocations) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, TextureImage2DMultisampleNV, texture, target, samples, internalFormat, width, height, fixedSampleLocations) diff --git a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp index 74af2c85..208824c3 100644 --- a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp +++ b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.cpp @@ -340,6 +340,38 @@ namespace MobileGL::MG_Impl::GLImpl { MG_State::pGLContext->SetPolygonOffset(static_cast(factor), static_cast(units)); } + void PolygonOffsetClamp_State(GLfloat factor, GLfloat units, GLfloat clamp) { + // GL 4.6 core 14.6.5 / GL_EXT_polygon_offset_clamp. No error cases: any three floats are + // legal, and clamp = 0 is exactly glPolygonOffset. Whether the backend can APPLY the clamp + // is a separate question (see the DirectGLES/DirectVulkan forwarding); the state is + // recorded either way, because GL_POLYGON_OFFSET_CLAMP has to read back what was written. + MG_State::pGLContext->SetPolygonOffsetClamped(static_cast(factor), static_cast(units), + static_cast(clamp)); + } + + void ClipControl_State(GLenum origin, GLenum depth) { + // GL 4.5 core 13.5: both arguments are strict enums, and either being wrong is + // GL_INVALID_ENUM with the state left untouched. + if (origin != GL_LOWER_LEFT && origin != GL_UPPER_LEFT) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", __func__, + "glClipControl origin must be GL_LOWER_LEFT or GL_UPPER_LEFT; got " + + MG_Util::ConvertGLEnumToString(origin) + ".")); + return; + } + if (depth != GL_NEGATIVE_ONE_TO_ONE && depth != GL_ZERO_TO_ONE) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique( + "MG_Impl/GLImpl", __func__, + "glClipControl depth must be GL_NEGATIVE_ONE_TO_ONE or GL_ZERO_TO_ONE; got " + + MG_Util::ConvertGLEnumToString(depth) + ".")); + return; + } + MG_State::pGLContext->SetClipControl(origin, depth); + } + void PolygonMode_State(GLenum face, GLenum mode) { // GL 3.3 core: separate front/back polygon modes were removed in 3.1, so the only legal // face is GL_FRONT_AND_BACK. GL_FRONT / GL_BACK must be rejected (some desktop drivers @@ -1029,6 +1061,14 @@ namespace MobileGL::MG_Impl::GLImpl { PolygonOffset_State(factor, units); } + void PolygonOffsetClamp(GLfloat factor, GLfloat units, GLfloat clamp) { + PolygonOffsetClamp_State(factor, units, clamp); + } + + void ClipControl(GLenum origin, GLenum depth) { + ClipControl_State(origin, depth); + } + void PolygonMode(GLenum face, GLenum mode) { PolygonMode_State(face, mode); } diff --git a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h index 473be0ee..50588a83 100644 --- a/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h +++ b/MobileGL/MG_Impl/GLImpl/RenderState/GL_RenderState.h @@ -40,6 +40,8 @@ namespace MobileGL::MG_Impl::GLImpl { void SampleCoverage(GLfloat value, GLboolean invert); void MinSampleShading(GLfloat value); void PolygonOffset(GLfloat factor, GLfloat units); + void PolygonOffsetClamp(GLfloat factor, GLfloat units, GLfloat clamp); + void ClipControl(GLenum origin, GLenum depth); void PolygonMode(GLenum face, GLenum mode); void PointSize(GLfloat size); void PointParameterf(GLenum pname, GLfloat param); diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index f3a5ac45..6f8a8257 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -848,6 +848,26 @@ namespace MobileGL::MG_State { return m_renderState.GetPolygonOffsetUnits(); } + void GLContext::SetPolygonOffsetClamped(Float factor, Float units, Float clamp) { + m_renderState.SetPolygonOffsetClamped(factor, units, clamp); + } + + Float GLContext::GetPolygonOffsetClamp() const { + return m_renderState.GetPolygonOffsetClamp(); + } + + void GLContext::SetClipControl(GLenum origin, GLenum depth) { + m_renderState.SetClipControl(origin, depth); + } + + GLenum GLContext::GetClipOrigin() const { + return m_renderState.GetClipOrigin(); + } + + GLenum GLContext::GetClipDepthMode() const { + return m_renderState.GetClipDepthMode(); + } + void GLContext::SetCapability(CapabilityInput cap, Bool enabled) { m_renderState.SetCapability(cap, enabled); } diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 425de586..73a8f799 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -218,8 +218,13 @@ namespace MobileGL { void SetPatchDefaultInnerLevel(const FloatVec2& levels); const FloatVec2& GetPatchDefaultInnerLevel() const; void SetPolygonOffset(Float factor, Float units); + void SetPolygonOffsetClamped(Float factor, Float units, Float clamp); Float GetPolygonOffsetFactor() const; Float GetPolygonOffsetUnits() const; + Float GetPolygonOffsetClamp() const; + void SetClipControl(GLenum origin, GLenum depth); + GLenum GetClipOrigin() const; + GLenum GetClipDepthMode() const; void SetHint(GLenum target, GLenum mode); GLenum GetHint(GLenum target) const; void SetPointFadeThresholdSize(Float size); diff --git a/MobileGL/MG_State/GLState/RenderState/RenderState.cpp b/MobileGL/MG_State/GLState/RenderState/RenderState.cpp index c2aef284..fe75f231 100644 --- a/MobileGL/MG_State/GLState/RenderState/RenderState.cpp +++ b/MobileGL/MG_State/GLState/RenderState/RenderState.cpp @@ -263,6 +263,37 @@ namespace MobileGL { return m_parameters.PolygonOffsetUnits; } + void RenderState::SetPolygonOffsetClamped(Float factor, Float units, Float clamp) { + if (m_parameters.PolygonOffsetFactor == factor && m_parameters.PolygonOffsetUnits == units && + m_parameters.PolygonOffsetClamp == clamp) + return; + + m_parameters.PolygonOffsetFactor = factor; + m_parameters.PolygonOffsetUnits = units; + m_parameters.PolygonOffsetClamp = clamp; + ++m_version; + } + + Float RenderState::GetPolygonOffsetClamp() const { + return m_parameters.PolygonOffsetClamp; + } + + void RenderState::SetClipControl(GLenum origin, GLenum depth) { + if (m_parameters.ClipOrigin == origin && m_parameters.ClipDepthMode == depth) return; + + m_parameters.ClipOrigin = origin; + m_parameters.ClipDepthMode = depth; + ++m_version; + } + + GLenum RenderState::GetClipOrigin() const { + return m_parameters.ClipOrigin; + } + + GLenum RenderState::GetClipDepthMode() const { + return m_parameters.ClipDepthMode; + } + // -------------------- Capabilities -------------------- namespace { // CapabilityInput lists ClipDistance0..7 contiguously (RenderState.h); the caller diff --git a/MobileGL/MG_State/GLState/RenderState/RenderState.h b/MobileGL/MG_State/GLState/RenderState/RenderState.h index 254eb884..6e301db3 100644 --- a/MobileGL/MG_State/GLState/RenderState/RenderState.h +++ b/MobileGL/MG_State/GLState/RenderState/RenderState.h @@ -249,6 +249,15 @@ namespace MobileGL { FloatVec2 PatchDefaultInnerLevel = FloatVec2(1.0f, 1.0f); Float PolygonOffsetFactor = 0.0f; Float PolygonOffsetUnits = 0.0f; + // GL_POLYGON_OFFSET_CLAMP (GL 4.6 core 14.6.5 / GL_EXT_polygon_offset_clamp): the maximum + // magnitude of the offset glPolygonOffsetClamp's third argument allows. Zero - the default + // - means "no clamp", which is exactly the behaviour glPolygonOffset leaves behind. + Float PolygonOffsetClamp = 0.0f; + + // glClipControl (GL 4.5 core 13.5). Defaults per table 23.7 are the pre-4.5 fixed + // behaviour: origin at the lower left, depth mapped from -1..1. + GLenum ClipOrigin = GL_LOWER_LEFT; + GLenum ClipDepthMode = GL_NEGATIVE_ONE_TO_ONE; // Blending Array BlendStates; @@ -391,8 +400,16 @@ namespace MobileGL { void SetPatchDefaultInnerLevel(const FloatVec2& levels); const FloatVec2& GetPatchDefaultInnerLevel() const; void SetPolygonOffset(Float factor, Float units); + // glPolygonOffsetClamp. Writes the same factor/units as glPolygonOffset plus the + // clamp, because that is what the entry point does - glPolygonOffset is the + // clamp = 0 case of it (GL 4.6 core 14.6.5). + void SetPolygonOffsetClamped(Float factor, Float units, Float clamp); Float GetPolygonOffsetFactor() const; Float GetPolygonOffsetUnits() const; + Float GetPolygonOffsetClamp() const; + void SetClipControl(GLenum origin, GLenum depth); + GLenum GetClipOrigin() const; + GLenum GetClipDepthMode() 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; diff --git a/MobileGL/MG_Test/State/RenderStateTest.cpp b/MobileGL/MG_Test/State/RenderStateTest.cpp index b64b888e..c89cd46e 100644 --- a/MobileGL/MG_Test/State/RenderStateTest.cpp +++ b/MobileGL/MG_Test/State/RenderStateTest.cpp @@ -885,3 +885,134 @@ TEST_F(RenderStateTest, SampleShadingEnableIsStoredAndQueryable) { EXPECT_EQ(MG_Impl::GLImpl::IsEnabled(GL_SAMPLE_SHADING), GL_FALSE); EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); } + +// glClipControl and glPolygonOffsetClamp were DECLARE_GL_FUNCTION_STUB_HEAD entry points: they +// took their arguments, recorded nothing and raised no error, and the state variables they own +// (GL_CLIP_ORIGIN, GL_CLIP_DEPTH_MODE, GL_POLYGON_OFFSET_CLAMP) had no arm in any getter, so the +// very first query of a conformance case raised GL_INVALID_ENUM and killed it. These assertions +// are state-shaped on purpose - the rasterization half of clip control is a backend question, but +// the state machine has to round-trip regardless of what a backend does with it. +TEST_F(RenderStateTest, ClipControlStateRoundTripsAndDefaultsToLowerLeftNegativeOneToOne) { + DrainPendingGlErrors(); + + GLint origin = 0; + GLint depthMode = 0; + MG_Impl::GLImpl::GetIntegerv(GL_CLIP_ORIGIN, &origin); + MG_Impl::GLImpl::GetIntegerv(GL_CLIP_DEPTH_MODE, &depthMode); + EXPECT_EQ(origin, GL_LOWER_LEFT); + EXPECT_EQ(depthMode, GL_NEGATIVE_ONE_TO_ONE); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + MG_Impl::GLImpl::ClipControl(GL_UPPER_LEFT, GL_ZERO_TO_ONE); + MG_Impl::GLImpl::GetIntegerv(GL_CLIP_ORIGIN, &origin); + MG_Impl::GLImpl::GetIntegerv(GL_CLIP_DEPTH_MODE, &depthMode); + EXPECT_EQ(origin, GL_UPPER_LEFT); + EXPECT_EQ(depthMode, GL_ZERO_TO_ONE); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + // Every getter flavour has to answer, not just the integer one - the conformance suite reads + // this state through all of them. + GLfloat asFloat = 0.0f; + MG_Impl::GLImpl::GetFloatv(GL_CLIP_ORIGIN, &asFloat); + EXPECT_EQ(static_cast(asFloat), GL_UPPER_LEFT); + GLint64 asInt64 = 0; + MG_Impl::GLImpl::GetInteger64v(GL_CLIP_DEPTH_MODE, &asInt64); + EXPECT_EQ(static_cast(asInt64), GL_ZERO_TO_ONE); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + MG_Impl::GLImpl::ClipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +} + +TEST_F(RenderStateTest, ClipControlRejectsBadEnumsAndLeavesTheStateAlone) { + DrainPendingGlErrors(); + MG_Impl::GLImpl::ClipControl(GL_UPPER_LEFT, GL_ZERO_TO_ONE); + DrainPendingGlErrors(); + + MG_Impl::GLImpl::ClipControl(GL_FRONT, GL_ZERO_TO_ONE); + ExpectSingleGlError(GL_INVALID_ENUM); + MG_Impl::GLImpl::ClipControl(GL_UPPER_LEFT, GL_FRONT); + ExpectSingleGlError(GL_INVALID_ENUM); + + GLint origin = 0; + GLint depthMode = 0; + MG_Impl::GLImpl::GetIntegerv(GL_CLIP_ORIGIN, &origin); + MG_Impl::GLImpl::GetIntegerv(GL_CLIP_DEPTH_MODE, &depthMode); + EXPECT_EQ(origin, GL_UPPER_LEFT) << "a rejected glClipControl must not change the state"; + EXPECT_EQ(depthMode, GL_ZERO_TO_ONE) << "a rejected glClipControl must not change the state"; + + MG_Impl::GLImpl::ClipControl(GL_LOWER_LEFT, GL_NEGATIVE_ONE_TO_ONE); + DrainPendingGlErrors(); +} + +TEST_F(RenderStateTest, PolygonOffsetClampStoresTheClampAndTheFactorUnitsPair) { + DrainPendingGlErrors(); + + GLfloat clamp = -1.0f; + MG_Impl::GLImpl::GetFloatv(GL_POLYGON_OFFSET_CLAMP, &clamp); + EXPECT_FLOAT_EQ(clamp, 0.0f) << "the default clamp is zero, i.e. no clamping"; + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + MG_Impl::GLImpl::PolygonOffsetClamp(1.5f, 2.5f, 0.5f); + GLfloat factor = 0.0f; + GLfloat units = 0.0f; + MG_Impl::GLImpl::GetFloatv(GL_POLYGON_OFFSET_FACTOR, &factor); + MG_Impl::GLImpl::GetFloatv(GL_POLYGON_OFFSET_UNITS, &units); + MG_Impl::GLImpl::GetFloatv(GL_POLYGON_OFFSET_CLAMP, &clamp); + EXPECT_FLOAT_EQ(factor, 1.5f); + EXPECT_FLOAT_EQ(units, 2.5f); + EXPECT_FLOAT_EQ(clamp, 0.5f) << "the fractional clamp must survive - the integer path rounds it away"; + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + // glcPolygonOffsetClampTests reads GL_POLYGON_OFFSET_CLAMP through all five getters and + // requires no error from any of them; that is what used to kill the availability case. + GLboolean asBoolean = GL_FALSE; + MG_Impl::GLImpl::GetBooleanv(GL_POLYGON_OFFSET_CLAMP, &asBoolean); + EXPECT_EQ(asBoolean, GL_TRUE); + GLint asInt = -1; + MG_Impl::GLImpl::GetIntegerv(GL_POLYGON_OFFSET_CLAMP, &asInt); + EXPECT_EQ(asInt, 1) << "0.5 rounds to nearest for the integer query"; + GLint64 asInt64 = -1; + MG_Impl::GLImpl::GetInteger64v(GL_POLYGON_OFFSET_CLAMP, &asInt64); + EXPECT_EQ(asInt64, 1); + GLdouble asDouble = -1.0; + MG_Impl::GLImpl::GetDoublev(GL_POLYGON_OFFSET_CLAMP, &asDouble); + EXPECT_NEAR(asDouble, 0.5, 1e-6); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); + + // glPolygonOffset is the clamp = 0 case of the same state, but it must not DISTURB the clamp + // it does not take - GL 4.6 core 14.6.5 defines it as PolygonOffsetClamp(factor, units, 0) + // only in the sense that the clamp it leaves is whatever glPolygonOffset itself sets, which + // for MobileGL is "unchanged". Assert the factor/units half instead, which is unambiguous. + MG_Impl::GLImpl::PolygonOffset(3.0f, 4.0f); + MG_Impl::GLImpl::GetFloatv(GL_POLYGON_OFFSET_FACTOR, &factor); + MG_Impl::GLImpl::GetFloatv(GL_POLYGON_OFFSET_UNITS, &units); + EXPECT_FLOAT_EQ(factor, 3.0f); + EXPECT_FLOAT_EQ(units, 4.0f); + + MG_Impl::GLImpl::PolygonOffsetClamp(0.0f, 0.0f, 0.0f); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +} + +// GL_TEXTURE_BUFFER_BINDING (0x8C2A) is the same token as GL_TEXTURE_BUFFER; as a glGetIntegerv +// pname it asks which BUFFER object is bound there, and it had no arm at all, so +// esextcTextureBufferParameters died on its first query. +TEST_F(RenderStateTest, TextureBufferBindingAnswersTheBoundBufferName) { + DrainPendingGlErrors(); + + GLint binding = -1; + MG_Impl::GLImpl::GetIntegerv(GL_TEXTURE_BUFFER_BINDING, &binding); + EXPECT_EQ(binding, 0); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +} + +// GL_ARB_spirv_extensions. Zero is legal and true: MobileGL relies on no SPIR-V extension, so +// glGetStringi(GL_SPIR_V_EXTENSIONS, i) is never legally reached. +TEST_F(RenderStateTest, NumSpirVExtensionsIsQueryableAndZero) { + DrainPendingGlErrors(); + + GLint count = -1; + MG_Impl::GLImpl::GetIntegerv(GL_NUM_SPIR_V_EXTENSIONS, &count); + EXPECT_EQ(count, 0); + EXPECT_EQ(MG_Impl::GLImpl::GetError(), GL_NO_ERROR); +}