From 8d1a734c221acfb1ddda4400ff453a257d869267 Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Tue, 4 Aug 2026 09:55:36 -0400 Subject: [PATCH] [Fix] (MG_State, MG_Impl): reject a draw mode the geometry stage cannot accept A geometry shader declares the primitive type it consumes, and a draw may only present a mode that decomposes into it - points for `points`, the three triangle modes for `triangles`, and so on (GL 4.6 core 11.3.1). Anything else is GL_INVALID_OPERATION. Nothing checked it, so KHR-GL40.draw_indirect.negative-gshIncompatible-arrays and -elements drew points through a `layout(triangles) in` shader and got no error. The program object had no notion of the geometry input primitive at all: glslang knows it right after the link, so it is read off the geometry intermediate and kept as the GL enum (this is also what GL_GEOMETRY_INPUT_TYPE would report). Resolved on every link rather than only when transform feedback captures the stage, since every draw consults it, and cleared with the rest of the link artifacts. The check sits on the shared pre-draw gate next to the transform feedback primitive rule, which is the same shape of constraint. GL_PATCHES is deliberately exempt: it is the tessellation pipeline's input and has already become the tessellator's output primitive by the time the geometry stage sees it. draw_indirect is now at 70/70. --- .../MG_Impl/GLImpl/Drawing/GL_Drawing.cpp | 37 +++++++++++++++++++ .../GLState/ProgramState/ProgramObject.cpp | 15 ++++++++ .../GLState/ProgramState/ProgramObject.h | 5 +++ 3 files changed, 57 insertions(+) diff --git a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp index 572b8f6d..2f91ea24 100644 --- a/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp +++ b/MobileGL/MG_Impl/GLImpl/Drawing/GL_Drawing.cpp @@ -162,6 +162,43 @@ namespace MobileGL::MG_Impl::GLImpl { return false; } + // A geometry stage only accepts the primitive types that decompose into its declared + // input primitive (GL 4.6 core 11.3.1); anything else is INVALID_OPERATION. GL_PATCHES + // is the tessellation pipeline's input and reaches the geometry stage already + // converted, so it is not constrained here. + const auto& currentProgram = MG_State::pGLContext->GetCurrentProgram(); + const GLenum gsInput = currentProgram ? currentProgram->GetGeometryInputType() : GL_NONE; + if (gsInput != GL_NONE && mode != GL_PATCHES) { + Bool compatible = false; + switch (gsInput) { + case GL_POINTS: + compatible = mode == GL_POINTS; + break; + case GL_LINES: + compatible = mode == GL_LINES || mode == GL_LINE_STRIP || mode == GL_LINE_LOOP; + break; + case GL_LINES_ADJACENCY: + compatible = mode == GL_LINES_ADJACENCY || mode == GL_LINE_STRIP_ADJACENCY; + break; + case GL_TRIANGLES: + compatible = mode == GL_TRIANGLES || mode == GL_TRIANGLE_STRIP || mode == GL_TRIANGLE_FAN; + break; + case GL_TRIANGLES_ADJACENCY: + compatible = mode == GL_TRIANGLES_ADJACENCY || mode == GL_TRIANGLE_STRIP_ADJACENCY; + break; + default: + break; + } + if (!compatible) { + MG_State::pGLContext->RecordError( + ErrorCode::InvalidOperation, + MakeUnique( + "MG_Impl/GLImpl", functionName, + "Primitive mode is incompatible with the geometry shader's input primitive type.")); + return false; + } + } + // While transform feedback is active the draw's primitive type must match // the feedback primitive mode (GL 3.3 core 13.2.2). With a geometry shader // the constraint moves to the shader's output primitive type instead, so diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp index 222c8f41..11db3fa0 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.cpp @@ -174,6 +174,7 @@ namespace MobileGL::MG_State::GLState { m_xfbStrides.clear(); m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS; m_xfbVaryingNameMaxLength = 0; + m_gsInputPrimitive = GL_NONE; m_linkStatus = false; } @@ -542,6 +543,20 @@ namespace MobileGL::MG_State::GLState { return; } + // GL_GEOMETRY_INPUT_TYPE. A draw's primitive type has to be compatible with it + // (GL 4.6 core 11.3.1), so it is resolved for every link, not only a capturing one. + m_gsInputPrimitive = GL_NONE; + if (const glslang::TIntermediate* gs = m_program->getIntermediate(EShLangGeometry)) { + switch (gs->getInputPrimitive()) { + case glslang::ElgPoints: m_gsInputPrimitive = GL_POINTS; break; + case glslang::ElgLines: m_gsInputPrimitive = GL_LINES; break; + case glslang::ElgLinesAdjacency: m_gsInputPrimitive = GL_LINES_ADJACENCY; break; + case glslang::ElgTriangles: m_gsInputPrimitive = GL_TRIANGLES; break; + case glslang::ElgTrianglesAdjacency: m_gsInputPrimitive = GL_TRIANGLES_ADJACENCY; break; + default: break; + } + } + MGLOG_D("ProgramObject %u: Starting reflection", m_externalIndex); DoReflection(); MGLOG_D("ProgramObject %u: Reflection done (linkStatus=%d)", m_externalIndex, (int)m_linkStatus); diff --git a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h index e9518edc..e8dadf3c 100644 --- a/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h +++ b/MobileGL/MG_State/GLState/ProgramState/ProgramObject.h @@ -497,6 +497,10 @@ namespace MobileGL::MG_State::GLState { Bool HasGsTriangleStripCaptureFixup() const { return m_gsStripCaptureFixup; } // Triangles per strip, in emission order, for ONE geometry invocation. const Vector& GetGsStripTriangles() const { return m_gsStripTriangles; } + // GL_GEOMETRY_INPUT_TYPE of the linked geometry stage (GL_POINTS, GL_LINES, + // GL_LINES_ADJACENCY, GL_TRIANGLES or GL_TRIANGLES_ADJACENCY), or GL_NONE when the + // program has no geometry stage. Draws must present a compatible primitive type. + GLenum GetGeometryInputType() const { return m_gsInputPrimitive; } Uint GetExternalIndex() const { return m_externalIndex; } // Globally-unique, never-reused id for this program object's lifetime. Unlike the GL @@ -604,6 +608,7 @@ namespace MobileGL::MG_State::GLState { Vector m_xfbStrides; Vector m_gsStripTriangles; Bool m_gsStripCaptureFixup = false; + GLenum m_gsInputPrimitive = GL_NONE; GLenum m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS; Int m_xfbVaryingNameMaxLength = 0; };