[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.
This commit is contained in:
BZLZHH
2026-08-04 09:55:36 -04:00
parent 7d215028fb
commit 8d1a734c22
3 changed files with 57 additions and 0 deletions
@@ -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<GenericErrorInfo>(
"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
@@ -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);
@@ -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<Uint32>& 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<Uint32> m_xfbStrides;
Vector<Uint32> m_gsStripTriangles;
Bool m_gsStripCaptureFixup = false;
GLenum m_gsInputPrimitive = GL_NONE;
GLenum m_xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
Int m_xfbVaryingNameMaxLength = 0;
};