mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-08 20:28:32 +09:00
[Fix] (ProgramLink): fail the link when a tessellation control stage declares more output vertices than GL_MAX_PATCH_VERTICES
This commit is contained in:
@@ -686,6 +686,38 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
}
|
||||
|
||||
// GL_TESS_CONTROL_OUTPUT_VERTICES, i.e. the `layout(vertices = N) out` the control stage
|
||||
// declared, and the limit that goes with it.
|
||||
//
|
||||
// GL 4.6 core 11.2.1.1: the LINK fails when N is greater than MAX_PATCH_VERTICES. Nothing
|
||||
// enforced it - glslang's layout handling only rejects N <= 0 (ParseHelper.cpp "must be
|
||||
// greater than 0") and carries maxPatchVertices in TBuiltInResource purely so
|
||||
// gl_MaxPatchVertices can expand from it, exactly the gap ValidateImageUniformLimits
|
||||
// documents for image uniforms. Checked at LINK rather than at compile on purpose: the CTS
|
||||
// requires the offending shader to COMPILE ("Compilation passed as allowed") and only the
|
||||
// link to fail, and turning it into a parse error would newly break an application that
|
||||
// compiles such a shader and never links it.
|
||||
//
|
||||
// The limit is the one glGetIntegerv answers (GL_Getter.cpp reads the same
|
||||
// DynamicBackendParameters field), so the advertised number and the enforced number cannot
|
||||
// drift apart.
|
||||
artifacts.tcsOutputVertices = 0;
|
||||
if (const glslang::TIntermediate* tcs = artifacts.program->getIntermediate(EShLangTessControl)) {
|
||||
artifacts.tcsOutputVertices = static_cast<Int>(tcs->getVertices());
|
||||
if (artifacts.tcsOutputVertices > env.params.MaxPatchVertices) {
|
||||
artifacts.linkStatus = false;
|
||||
// Same invariant as the compute local-size gate above: a rejected link leaves no
|
||||
// TProgram behind for a query surface to find.
|
||||
artifacts.program.reset();
|
||||
artifacts.infoLog = std::format(
|
||||
"Tessellation control shader declares an output patch of {} vertices, more than the {} "
|
||||
"GL_MAX_PATCH_VERTICES allows.",
|
||||
artifacts.tcsOutputVertices, env.params.MaxPatchVertices);
|
||||
DeferLog(std::format("ProgramObject {}: Link failed - {}", in.externalIndex, artifacts.infoLog));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- everything below this line up to GenerateSpirv() is the GL query surface ----
|
||||
//
|
||||
// ORDERING NOTE (rewritten 2026-08-10; the constraint it records was RETESTED, not
|
||||
|
||||
@@ -1317,6 +1317,10 @@ namespace MobileGL::MG_State::GLState {
|
||||
Vector<Uint32> gsStripTriangles;
|
||||
Bool gsStripCaptureFixup = false;
|
||||
GLenum gsInputPrimitive = GL_NONE;
|
||||
// GL_TESS_CONTROL_OUTPUT_VERTICES: the `layout(vertices = N) out` of the linked
|
||||
// tessellation control stage, or 0 when the program has none. Checked against
|
||||
// GL_MAX_PATCH_VERTICES at link (GL 4.6 core 11.2.1.1).
|
||||
Int tcsOutputVertices = 0;
|
||||
GLenum xfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
Int xfbVaryingNameMaxLength = 0;
|
||||
Bool xfbNeedsScatteredCapture = false;
|
||||
@@ -1501,6 +1505,10 @@ namespace MobileGL::MG_State::GLState {
|
||||
// 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 Artifacts().gsInputPrimitive; }
|
||||
// GL_TESS_CONTROL_OUTPUT_VERTICES of the linked tessellation control stage, or 0 when
|
||||
// the program has no such stage. Never greater than GL_MAX_PATCH_VERTICES: a program
|
||||
// that declared more does not link at all (GL 4.6 core 11.2.1.1).
|
||||
Int GetTessControlOutputVertices() const { return Artifacts().tcsOutputVertices; }
|
||||
|
||||
Uint GetExternalIndex() const { return m_externalIndex; }
|
||||
// Globally-unique, never-reused id for this program object's lifetime. Unlike the GL
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <gtest/gtest.h>
|
||||
#include <spirv_reflect.h>
|
||||
#include <cstring>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "Includes.h"
|
||||
@@ -31,6 +32,13 @@ protected:
|
||||
void SetUp() override { MobileGL::Initialize(); }
|
||||
|
||||
void TearDown() override {}
|
||||
|
||||
// GL error flags are sticky per code and the context outlives an individual test in this
|
||||
// binary, so a pending error would be handed to whoever runs next.
|
||||
static void DrainProgramTestErrors() {
|
||||
for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(ProgramTest, Sanity) {
|
||||
@@ -3809,3 +3817,77 @@ TEST_F(ProgramTest, ImplicitLocationStaysInRangeWhenABufferBlockSharesTheProgram
|
||||
}
|
||||
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
// --- `layout(vertices = N) out` against GL_MAX_PATCH_VERTICES -----------------------------------
|
||||
//
|
||||
// GL 4.6 core 11.2.1.1 makes N > MAX_PATCH_VERTICES a LINK failure. Nothing enforced it: glslang
|
||||
// only rejects N <= 0, and carries maxPatchVertices in TBuiltInResource purely so
|
||||
// gl_MaxPatchVertices can expand from it. The check deliberately lives at link and not at compile,
|
||||
// because KHR-GL4x.tessellation_shader.compilation_and_linking_errors.
|
||||
// tc_invalid_output_patch_vertex_count requires the shader to COMPILE ("Compilation passed as
|
||||
// allowed") and only the program to fail.
|
||||
TEST_F(ProgramTest, TessControlOutputPatchSizePastTheLimitFailsToLinkButStillCompiles) {
|
||||
GLint maxPatchVertices = 0;
|
||||
GetIntegerv(GL_MAX_PATCH_VERTICES, &maxPatchVertices);
|
||||
ASSERT_GT(maxPatchVertices, 0);
|
||||
ASSERT_EQ(GetError(), GL_NO_ERROR);
|
||||
|
||||
const char* kVs = R"(#version 460 core
|
||||
void main() { gl_Position = vec4(0.0); }
|
||||
)";
|
||||
const char* kTes = R"(#version 460 core
|
||||
layout(triangles, equal_spacing, cw) in;
|
||||
void main() { gl_Position = gl_in[0].gl_Position; }
|
||||
)";
|
||||
|
||||
const char* kTcsPrologue = R"(#version 460 core
|
||||
layout(vertices = )";
|
||||
const char* kTcsEpilogue = R"() out;
|
||||
void main() {
|
||||
gl_out[gl_InvocationID].gl_Position = gl_in[gl_InvocationID].gl_Position;
|
||||
gl_TessLevelOuter[0] = 1.0;
|
||||
}
|
||||
)";
|
||||
|
||||
const auto buildWith = [&](const GLint vertices) {
|
||||
const String tcs = String(kTcsPrologue) + std::to_string(vertices) + kTcsEpilogue;
|
||||
const char* tcsSource = tcs.c_str();
|
||||
|
||||
const GLuint vs = CreateShader(GL_VERTEX_SHADER);
|
||||
ShaderSource(vs, 1, &kVs, nullptr);
|
||||
CompileShader(vs);
|
||||
const GLuint tc = CreateShader(GL_TESS_CONTROL_SHADER);
|
||||
ShaderSource(tc, 1, &tcsSource, nullptr);
|
||||
CompileShader(tc);
|
||||
const GLuint te = CreateShader(GL_TESS_EVALUATION_SHADER);
|
||||
ShaderSource(te, 1, &kTes, nullptr);
|
||||
CompileShader(te);
|
||||
|
||||
// The offending stage COMPILES; only the link is allowed to notice.
|
||||
GLint tcCompiled = GL_FALSE;
|
||||
GetShaderiv(tc, GL_COMPILE_STATUS, &tcCompiled);
|
||||
EXPECT_EQ(tcCompiled, GL_TRUE) << "vertices=" << vertices << " must still compile";
|
||||
|
||||
const GLuint program = CreateProgram();
|
||||
AttachShader(program, vs);
|
||||
AttachShader(program, tc);
|
||||
AttachShader(program, te);
|
||||
LinkProgram(program);
|
||||
GLint linkStatus = GL_FALSE;
|
||||
GetProgramiv(program, GL_LINK_STATUS, &linkStatus);
|
||||
char infoLog[1024] = "";
|
||||
GetProgramInfoLog(program, sizeof(infoLog), nullptr, infoLog);
|
||||
return std::pair<GLint, String>{linkStatus, String(infoLog)};
|
||||
};
|
||||
|
||||
const auto atTheLimit = buildWith(maxPatchVertices);
|
||||
EXPECT_EQ(atTheLimit.first, GL_TRUE)
|
||||
<< "exactly GL_MAX_PATCH_VERTICES is legal: " << atTheLimit.second;
|
||||
|
||||
const auto pastTheLimit = buildWith(maxPatchVertices + 1);
|
||||
EXPECT_EQ(pastTheLimit.first, GL_FALSE) << "one past GL_MAX_PATCH_VERTICES must not link";
|
||||
EXPECT_NE(pastTheLimit.second.find("GL_MAX_PATCH_VERTICES"), String::npos)
|
||||
<< "the info log must name the limit it broke: " << pastTheLimit.second;
|
||||
|
||||
DrainProgramTestErrors();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user