From b95fcb7bca55107639ebc82d1b975db5de7057cf Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Tue, 4 Aug 2026 10:58:14 -0400 Subject: [PATCH] [Feat] (MG_State, MG_Impl, DirectGLES): implement glPatchParameteri GL_PATCH_VERTICES decides how many vertices one tessellation patch consumes, and glPatchParameteri was a stub - so the value stayed at the driver's default of 3 no matter what the application asked for. KHR-GL40.texture_gather.gather-tesselation-shader sets it to 1 and then draws a single patch: with the request dropped the draw had too few vertices for one patch, produced nothing at all, and the case read back the clear colour. The value is context state on both sides and ES 3.2 spells the entry point exactly the same way, so it is stored in the render state (where glGetIntegerv(GL_PATCH_VERTICES) now finds it) and forwarded. Validation needs the real bound, so GL_MAX_PATCH_VERTICES and GL_MAX_TESS_GEN_LEVEL are probed off the host driver alongside the other limits and answered from there too; the defaults are the GL 4.0 core minimums. KHR-GL40.texture_gather is now 75/75. --- MobileGL/MG_Backend/BackendObject.h | 5 ++++ .../DirectGLES/BackendObject_DirectGLES.cpp | 3 +++ MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 5 ++++ MobileGL/MG_Backend/DirectGLES/DirectGLES.h | 2 ++ .../MG_Impl/GLImpl/Drawing/GL_Drawing.cpp | 23 +++++++++++++++++++ MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h | 1 + .../MG_Impl/GLImpl/Exporting/Definitions.cpp | 2 +- MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp | 9 ++++++++ MobileGL/MG_State/GLState/Core.cpp | 8 +++++++ MobileGL/MG_State/GLState/Core.h | 2 ++ .../GLState/RenderState/RenderState.cpp | 11 +++++++++ .../GLState/RenderState/RenderState.h | 4 ++++ .../MG_Util/BackendLoaders/OpenGL/Loader.cpp | 6 +++++ .../MG_Util/BackendLoaders/OpenGL/Loader.h | 2 ++ 14 files changed, 82 insertions(+), 1 deletion(-) diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 134e247d..0c135b56 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -234,6 +234,8 @@ namespace MobileGL { // drives capture from its draw recording instead (DirectVulkan). End is // called while the frontend capture state is still active, so the backend // can still see the capture program and buffer bindings. + // GL_PATCH_VERTICES; ES 3.2 spells it the same way. + void (*PatchParameteri)(GLenum pname, GLint value); void (*BeginTransformFeedback)(GLenum primitiveMode); void (*EndTransformFeedback)(); // ARB_transform_feedback2. A backend that leaves these null keeps the single @@ -295,6 +297,9 @@ namespace MobileGL { Int MaxIntegerSamples = 1; Int MaxSamples = 1; Int MaxSampleMaskWords = 1; + // Tessellation limits; defaults are the GL 4.0 core minimums. + Int MaxPatchVertices = 32; + Int MaxTessGenLevel = 64; // GL_MIN/MAX_PROGRAM_TEXTURE_GATHER_OFFSET. Defaults are the GL 4.0 core // minimums, which every ES 3.1 driver also guarantees. Int MinProgramTextureGatherOffset = -8; diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index 6e8ef1a2..20070ff9 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -1034,6 +1034,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // Transform feedback is captured by the real ES driver rather than // reconstructed from the draw recording, so the frontend has to hand the // span boundaries over. + funcsTable.GL.PatchParameteri = DirectGLES::PatchParameteri; funcsTable.GL.BeginTransformFeedback = XfbImpl::BeginTransformFeedback; funcsTable.GL.EndTransformFeedback = XfbImpl::EndTransformFeedback; funcsTable.GL.PauseTransformFeedback = XfbImpl::PauseTransformFeedback; @@ -1080,6 +1081,8 @@ namespace MobileGL::MG_Backend::DirectGLES { m_dynamicParameters.MaxIntegerSamples = m_GLESCapabilities.MaxIntegerSamples; m_dynamicParameters.MaxSamples = m_GLESCapabilities.MaxSamples; m_dynamicParameters.MaxSampleMaskWords = m_GLESCapabilities.MaxSampleMaskWords; + m_dynamicParameters.MaxPatchVertices = m_GLESCapabilities.MaxPatchVertices; + m_dynamicParameters.MaxTessGenLevel = m_GLESCapabilities.MaxTessGenLevel; m_dynamicParameters.MinProgramTextureGatherOffset = m_GLESCapabilities.MinProgramTextureGatherOffset; m_dynamicParameters.MaxProgramTextureGatherOffset = m_GLESCapabilities.MaxProgramTextureGatherOffset; // Clamp the advertised sampler limits the same way the DirectVulkan backend does: per-stage diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index c7ceba56..e9bde988 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -4025,6 +4025,11 @@ namespace MobileGL::MG_Backend::DirectGLES { return true; } + void PatchParameteri(GLenum pname, GLint value) { + if (g_GLESFuncs.glPatchParameteri == nullptr) return; + g_GLESFuncs.glPatchParameteri(pname, value); + } + void GenerateMipmap(GLenum target) { #if MOBILEGL_LOG_ACTIVE_LEVEL <= MOBILEGL_LOG_LEVEL_DEBUG && MOBILEGL_ENABLE_SCOPE_MARKER DebugImpl::OpenGLScopeMarker marker(__func__); diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index fa2eaf14..9a11cfc6 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -169,6 +169,8 @@ namespace MobileGL::MG_Backend::DirectGLES { // begin is deferred to the first draw of the span (ES needs the capturing // program current and the capture buffers bound), and the end also mirrors the // captured bytes back into the frontend buffer shadows. + void PatchParameteri(GLenum pname, GLint value); + namespace XfbImpl { Bool AreTransformFeedbacksSupported(); void BeginTransformFeedback(GLenum primitiveMode); diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index 35abb221..94678efd 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -11,6 +11,7 @@ #include #include #include +#include "../Getter/GL_Getter.h" namespace MobileGL::MG_Impl::GLImpl { static Bool ValidateCurrentProgramForExecution(const char* functionName) { @@ -486,6 +487,28 @@ namespace MobileGL::MG_Impl::GLImpl { dispatchComputeIndirect(indirect); } + void PatchParameteri(GLenum pname, GLint value) { + if (pname != GL_PATCH_VERTICES) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidEnum, + MakeUnique("MG_Impl/GLImpl", __func__, "pname must be GL_PATCH_VERTICES.")); + return; + } + GLint maxPatchVertices = 32; + GetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices); + if (value <= 0 || value > maxPatchVertices) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidValue, + MakeUnique("MG_Impl/GLImpl", __func__, + "value must be in [1, GL_MAX_PATCH_VERTICES].")); + return; + } + MG_State::pGLContext->SetPatchVertices(static_cast(value)); + if (const auto patchParameteri = MG_Backend::gBackendFunctionsTable.GL.PatchParameteri) { + patchParameteri(pname, value); + } + } + void MemoryBarrier(GLbitfield barriers) { auto memoryBarrier = MG_Backend::gBackendFunctionsTable.GL.MemoryBarrier; if (!memoryBarrier) { diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h index 688d27bd..94fe61cb 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.h @@ -25,6 +25,7 @@ namespace MobileGL::MG_Impl::GLImpl { void DrawTransformFeedbackStreamInstanced(GLenum mode, GLuint id, GLuint stream, GLsizei instancecount); void DispatchCompute(GLuint numGroupsX, GLuint numGroupsY, GLuint numGroupsZ); void DispatchComputeIndirect(GLintptr indirect); + void PatchParameteri(GLenum pname, GLint value); void MemoryBarrier(GLbitfield barriers); void MemoryBarrierByRegion(GLbitfield barriers); void MultiDrawElementsIndirect(GLenum mode, GLenum type, const void* indirect, GLsizei drawcount, GLsizei stride); diff --git a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp index 7024976a..2897364b 100644 --- a/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp +++ b/MobileGL/MG_Impl/GLImpl/Exporting/Definitions.cpp @@ -424,7 +424,7 @@ DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformfv, GLuint program, GLint locatio DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformiv, GLuint program, GLint location, GLsizei bufSize, GLint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformiv, program, location, bufSize, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, GetnUniformuiv, GLuint program, GLint location, GLsizei bufSize, GLuint* params) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, GetnUniformuiv, program, location, bufSize, params) DECLARE_GL_FUNCTION_STUB_HEAD(void, MinSampleShading, GLfloat value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, MinSampleShading, value) -DECLARE_GL_FUNCTION_STUB_HEAD(void, PatchParameteri, GLenum pname, GLint value) DECLARE_GL_FUNCTION_STUB_END_NO_RETURN(void, PatchParameteri, pname, value) +DECLARE_GL_FUNCTION_HEAD(void, PatchParameteri, GLenum pname, GLint value) DECLARE_GL_FUNCTION_END_NO_RETURN(void, PatchParameteri, pname, value) DECLARE_GL_FUNCTION_HEAD(void, TexParameterIiv, GLenum target, GLenum pname, const GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexParameterIiv, target, pname, params) DECLARE_GL_FUNCTION_HEAD(void, TexParameterIuiv, GLenum target, GLenum pname, const GLuint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, TexParameterIuiv, target, pname, params) DECLARE_GL_FUNCTION_HEAD(void, GetTexParameterIiv, GLenum target, GLenum pname, GLint* params) DECLARE_GL_FUNCTION_END_NO_RETURN(void, GetTexParameterIiv, target, pname, params) diff --git a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp index c70b3c07..e74dfd29 100644 --- a/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp +++ b/MobileGL/MG_Impl/GLImpl/Getter/GL_Getter.cpp @@ -1917,6 +1917,15 @@ namespace MobileGL::MG_Impl::GLImpl { case GL_MAX_SAMPLE_MASK_WORDS: *params = dynamicParameters.MaxSampleMaskWords; break; + case GL_PATCH_VERTICES: + *params = static_cast(MG_State::pGLContext->GetPatchVertices()); + break; + case GL_MAX_PATCH_VERTICES: + *params = dynamicParameters.MaxPatchVertices; + break; + case GL_MAX_TESS_GEN_LEVEL: + *params = dynamicParameters.MaxTessGenLevel; + break; case GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET: *params = dynamicParameters.MinProgramTextureGatherOffset; break; diff --git a/MobileGL/MG_State/GLState/Core.cpp b/MobileGL/MG_State/GLState/Core.cpp index f4738080..2b62242e 100644 --- a/MobileGL/MG_State/GLState/Core.cpp +++ b/MobileGL/MG_State/GLState/Core.cpp @@ -424,6 +424,14 @@ namespace MobileGL::MG_State { m_renderState.SetPointSize(size); } + void GLContext::SetPatchVertices(Uint vertices) { + m_renderState.SetPatchVertices(vertices); + } + + Uint GLContext::GetPatchVertices() const { + return m_renderState.GetPatchVertices(); + } + Float GLContext::GetPointSize() const { return m_renderState.GetPointSize(); } diff --git a/MobileGL/MG_State/GLState/Core.h b/MobileGL/MG_State/GLState/Core.h index 0961f850..5d9dde65 100644 --- a/MobileGL/MG_State/GLState/Core.h +++ b/MobileGL/MG_State/GLState/Core.h @@ -138,6 +138,8 @@ namespace MobileGL { Float GetLineWidth() const; void SetPointSize(Float size); Float GetPointSize() const; + void SetPatchVertices(Uint vertices); + Uint GetPatchVertices() const; void SetPolygonOffset(Float factor, Float units); Float GetPolygonOffsetFactor() const; Float GetPolygonOffsetUnits() const; diff --git a/MobileGL/MG_State/GLState/RenderState/RenderState.cpp b/MobileGL/MG_State/GLState/RenderState/RenderState.cpp index 81cf77f0..74bb7603 100644 --- a/MobileGL/MG_State/GLState/RenderState/RenderState.cpp +++ b/MobileGL/MG_State/GLState/RenderState/RenderState.cpp @@ -154,6 +154,17 @@ namespace MobileGL { return m_parameters.PointSize; } + void RenderState::SetPatchVertices(Uint vertices) { + if (m_parameters.PatchVertices == vertices) return; + + m_parameters.PatchVertices = vertices; + ++m_version; + } + + Uint RenderState::GetPatchVertices() const { + return m_parameters.PatchVertices; + } + void RenderState::SetPolygonOffset(Float factor, Float units) { if (m_parameters.PolygonOffsetFactor == factor && m_parameters.PolygonOffsetUnits == units) return; diff --git a/MobileGL/MG_State/GLState/RenderState/RenderState.h b/MobileGL/MG_State/GLState/RenderState/RenderState.h index 44b43a2d..65e22393 100644 --- a/MobileGL/MG_State/GLState/RenderState/RenderState.h +++ b/MobileGL/MG_State/GLState/RenderState/RenderState.h @@ -224,6 +224,8 @@ namespace MobileGL { IntVec4 Viewport = IntVec4(0, 0, 0, 0); // x, y, width, height Float LineWidth = 1.0f; Float PointSize = 1.0f; + // GL_PATCH_VERTICES: how many vertices one tessellation patch consumes. + Uint PatchVertices = 3; Float PolygonOffsetFactor = 0.0f; Float PolygonOffsetUnits = 0.0f; @@ -319,6 +321,8 @@ namespace MobileGL { Float GetLineWidth() const; void SetPointSize(Float size); Float GetPointSize() const; + void SetPatchVertices(Uint vertices); + Uint GetPatchVertices() const; void SetPolygonOffset(Float factor, Float units); Float GetPolygonOffsetFactor() const; Float GetPolygonOffsetUnits() const; diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp index 2e367d80..cba00e44 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.cpp @@ -920,6 +920,8 @@ namespace MobileGL::MG_Util::BackendLoader { // below only ever widens them. GLint minProgramTextureGatherOffset = -8; GLint maxProgramTextureGatherOffset = 7; + GLint maxPatchVertices = 32; + GLint maxTessGenLevel = 64; glesFuncs.glGetFloatv(GL_ALIASED_LINE_WIDTH_RANGE, aliasedLineWidthRange); glesFuncs.glGetFloatv(GL_SMOOTH_LINE_WIDTH_RANGE, smoothLineWidthRange); glesFuncs.glGetFloatv(GL_SMOOTH_LINE_WIDTH_GRANULARITY, &smoothLineWidthGranularity); @@ -948,6 +950,8 @@ namespace MobileGL::MG_Util::BackendLoader { // single test case. 1 is a spec-legal value (the minimum required), so cap // to what is actually implemented instead of forwarding the raw driver limit. maxSampleMaskWords = std::min(maxSampleMaskWords, 1); + glesFuncs.glGetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices); + glesFuncs.glGetIntegerv(GL_MAX_TESS_GEN_LEVEL, &maxTessGenLevel); glesFuncs.glGetIntegerv(GL_MIN_PROGRAM_TEXTURE_GATHER_OFFSET, &minProgramTextureGatherOffset); glesFuncs.glGetIntegerv(GL_MAX_PROGRAM_TEXTURE_GATHER_OFFSET, &maxProgramTextureGatherOffset); // A driver that leaves the probe untouched (pre-ES 3.1, or an ignored enum) must not @@ -1037,6 +1041,8 @@ namespace MobileGL::MG_Util::BackendLoader { caps.MaxIntegerSamples = maxIntegerSamples; caps.MaxSamples = maxSamples; caps.MaxSampleMaskWords = maxSampleMaskWords; + caps.MaxPatchVertices = maxPatchVertices; + caps.MaxTessGenLevel = maxTessGenLevel; caps.MinProgramTextureGatherOffset = minProgramTextureGatherOffset; caps.MaxProgramTextureGatherOffset = maxProgramTextureGatherOffset; caps.MaxTextureImageUnits = maxTextureImageUnits; diff --git a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h index 9971140f..3986965d 100644 --- a/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h +++ b/MobileGL/MG_Util/BackendLoaders/OpenGL/Loader.h @@ -1102,6 +1102,8 @@ namespace MobileGL { Int MaxIntegerSamples = 1; Int MaxSamples = 1; Int MaxSampleMaskWords = 1; + Int MaxPatchVertices = 32; + Int MaxTessGenLevel = 64; Int MinProgramTextureGatherOffset = -8; Int MaxProgramTextureGatherOffset = 7; Int MaxTextureImageUnits = 32;