mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-07 19:58:32 +09:00
[Test] (Review): pin the composite's linked capture list, the capture-stage rule, and the matrix uniform forms' link check
This commit is contained in:
@@ -570,3 +570,155 @@ void main() { gl_Position = vec4(0.0); EmitVertex(); EndPrimitive(); }
|
||||
for (Int drained = 0; drained < 16 && GetError() != GL_NO_ERROR; ++drained) {
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// The composite's transform-feedback capture list.
|
||||
//
|
||||
// Two rules, and getting either wrong turns a working pipeline into one where EVERY draw reports
|
||||
// GL_INVALID_OPERATION: an unresolvable capture name fails the composite's own link, and
|
||||
// ValidateProgramForExecution rejects every draw through a pipeline whose composite did not link -
|
||||
// while glValidateProgramPipeline keeps reporting TRUE.
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
|
||||
namespace {
|
||||
const char* kCaptureVs = R"(#version 430 core
|
||||
out gl_PerVertex { vec4 gl_Position; };
|
||||
out float v_captured;
|
||||
out float v_other;
|
||||
void main() { gl_Position = vec4(0.0); v_captured = 1.0; v_other = 2.0; }
|
||||
)";
|
||||
|
||||
// A geometry stage that re-emits nothing the vertex stage named, so a capture list taken from
|
||||
// the VERTEX program cannot resolve against it.
|
||||
const char* kPassthroughGs = R"(#version 430 core
|
||||
layout(points) in;
|
||||
layout(points, max_vertices = 1) out;
|
||||
out gl_PerVertex { vec4 gl_Position; };
|
||||
out float g_only;
|
||||
void main() { gl_Position = vec4(0.0); g_only = 1.0; EmitVertex(); EndPrimitive(); }
|
||||
)";
|
||||
|
||||
Vector<String> CompositeCaptureNames(MG_State::GLState::ProgramObject& composite) {
|
||||
Vector<String> names;
|
||||
for (SizeT i = 0; i < composite.GetTransformFeedbackVaryingCount(); ++i) {
|
||||
if (const auto* varying = composite.GetTransformFeedbackVarying(i)) {
|
||||
names.push_back(varying->name);
|
||||
}
|
||||
}
|
||||
return names;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// glTransformFeedbackVaryings does not take effect until the program's NEXT link (GL 4.6 core
|
||||
// 7.3/11.1.2.1) and deliberately bumps no version, so a request written after the stage program's
|
||||
// last link is invisible to the composite cache's signature - yet the next rebuild would pick it
|
||||
// up. The capture list would then depend on whether some unrelated event happened to invalidate
|
||||
// the cache. Reading the LINKED snapshot removes the whole class, and makes the existing cache key
|
||||
// sufficient: linked state only moves at a link, which is exactly what the key tracks.
|
||||
TEST_F(ProgramPipelineCompositeTest, CompositeCaptureListComesFromTheLinkedSnapshotNotThePendingRequest) {
|
||||
const GLuint vs = CreateProgram();
|
||||
{
|
||||
const GLuint shader = CreateShader(GL_VERTEX_SHADER);
|
||||
ShaderSource(shader, 1, &kCaptureVs, nullptr);
|
||||
CompileShader(shader);
|
||||
ProgramParameteri(vs, GL_PROGRAM_SEPARABLE, GL_TRUE);
|
||||
AttachShader(vs, shader);
|
||||
const char* captured = "v_captured";
|
||||
TransformFeedbackVaryings(vs, 1, &captured, GL_INTERLEAVED_ATTRIBS);
|
||||
LinkProgram(vs);
|
||||
GLint linked = GL_FALSE;
|
||||
GetProgramiv(vs, GL_LINK_STATUS, &linked);
|
||||
ASSERT_EQ(linked, GL_TRUE);
|
||||
}
|
||||
const GLuint fs = MakeSeparableProgram(GL_FRAGMENT_SHADER, kSharedUniformFs);
|
||||
|
||||
GLuint pipeline = 0;
|
||||
GenProgramPipelines(1, &pipeline);
|
||||
BindProgramPipeline(pipeline);
|
||||
UseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, vs);
|
||||
UseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, fs);
|
||||
ASSERT_EQ(GetError(), GL_NO_ERROR);
|
||||
|
||||
{
|
||||
const auto composite = DrawProgram();
|
||||
ASSERT_NE(composite, nullptr);
|
||||
EXPECT_EQ(CompositeCaptureNames(*composite), (Vector<String>{"v_captured"}));
|
||||
}
|
||||
|
||||
// A NEW request with no relink. GL says the program still captures v_captured.
|
||||
const char* other = "v_other";
|
||||
TransformFeedbackVaryings(vs, 1, &other, GL_INTERLEAVED_ATTRIBS);
|
||||
ASSERT_EQ(GetError(), GL_NO_ERROR);
|
||||
|
||||
// Force a composite rebuild through something entirely unrelated to the capture list: a new
|
||||
// fragment stage program moves that slot's lifetime id, so the cache signature changes.
|
||||
const GLuint fs2 = MakeSeparableProgram(GL_FRAGMENT_SHADER, kSharedUniformFs);
|
||||
UseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, fs2);
|
||||
ASSERT_EQ(GetError(), GL_NO_ERROR);
|
||||
|
||||
{
|
||||
const auto composite = DrawProgram();
|
||||
ASSERT_NE(composite, nullptr);
|
||||
EXPECT_TRUE(composite->GetLinkStatus()) << "the composite must still link";
|
||||
EXPECT_EQ(CompositeCaptureNames(*composite), (Vector<String>{"v_captured"}))
|
||||
<< "an unlinked request must not reach the composite";
|
||||
}
|
||||
|
||||
// Relinking the stage program IS what makes the new request take effect - and the composite
|
||||
// follows, because the relink moves the link version the cache keys on.
|
||||
LinkProgram(vs);
|
||||
{
|
||||
const auto composite = DrawProgram();
|
||||
ASSERT_NE(composite, nullptr);
|
||||
EXPECT_EQ(CompositeCaptureNames(*composite), (Vector<String>{"v_other"}));
|
||||
}
|
||||
|
||||
BindProgramPipeline(0);
|
||||
DeleteProgramPipelines(1, &pipeline);
|
||||
}
|
||||
|
||||
// Transform feedback captures the output of the LAST vertex-processing stage (GL 4.6 core
|
||||
// 11.1.2.1) - the last stage that EXISTS, not the last one that happens to carry a capture list.
|
||||
// Falling through a geometry stage with no request and installing the vertex stage's list instead
|
||||
// made the two halves disagree: this loop picks whose list, the link task resolves those names
|
||||
// against the geometry intermediate. Either it captures where GL says it must not, or the
|
||||
// composite fails to link and every draw through the pipeline reports GL_INVALID_OPERATION.
|
||||
TEST_F(ProgramPipelineCompositeTest, CompositeCaptureStageIsTheLastVertexProcessingStageThatExists) {
|
||||
const GLuint vs = CreateProgram();
|
||||
{
|
||||
const GLuint shader = CreateShader(GL_VERTEX_SHADER);
|
||||
ShaderSource(shader, 1, &kCaptureVs, nullptr);
|
||||
CompileShader(shader);
|
||||
ProgramParameteri(vs, GL_PROGRAM_SEPARABLE, GL_TRUE);
|
||||
AttachShader(vs, shader);
|
||||
const char* captured = "v_captured";
|
||||
TransformFeedbackVaryings(vs, 1, &captured, GL_INTERLEAVED_ATTRIBS);
|
||||
LinkProgram(vs);
|
||||
GLint linked = GL_FALSE;
|
||||
GetProgramiv(vs, GL_LINK_STATUS, &linked);
|
||||
ASSERT_EQ(linked, GL_TRUE);
|
||||
}
|
||||
// The geometry program was never given a capture list, and "v_captured" is not one of its
|
||||
// outputs - so a composite seeded from the VERTEX program's list cannot resolve it.
|
||||
const GLuint gs = MakeSeparableProgram(GL_GEOMETRY_SHADER, kPassthroughGs);
|
||||
const GLuint fs = MakeSeparableProgram(GL_FRAGMENT_SHADER, kSharedUniformFs);
|
||||
|
||||
GLuint pipeline = 0;
|
||||
GenProgramPipelines(1, &pipeline);
|
||||
BindProgramPipeline(pipeline);
|
||||
UseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, vs);
|
||||
UseProgramStages(pipeline, GL_GEOMETRY_SHADER_BIT, gs);
|
||||
UseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, fs);
|
||||
ASSERT_EQ(GetError(), GL_NO_ERROR);
|
||||
|
||||
const auto composite = DrawProgram();
|
||||
ASSERT_NE(composite, nullptr);
|
||||
EXPECT_TRUE(composite->GetLinkStatus())
|
||||
<< "the geometry stage is the capture stage and has no capture list, so the composite links "
|
||||
"with none - it must not inherit the vertex stage's and fail resolving it";
|
||||
EXPECT_EQ(composite->GetTransformFeedbackVaryingCount(), 0u)
|
||||
<< "the capture stage is the geometry program, which declared nothing to capture";
|
||||
|
||||
BindProgramPipeline(0);
|
||||
DeleteProgramPipelines(1, &pipeline);
|
||||
}
|
||||
|
||||
@@ -4361,6 +4361,54 @@ TEST_F(ProgramTest, UniformEntryPointsRejectAnUnlinkedProgram) {
|
||||
ProgramUniform1f(program, 0, 1.0f);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION);
|
||||
|
||||
// THE MATRIX FORMS TOO. The reorder originally landed on ProgramUniformv_State alone, so all
|
||||
// thirteen glProgramUniformMatrix* entry points kept the old `if (location == -1) return;`
|
||||
// first statement and stayed silent on exactly the case the rule exists for.
|
||||
const GLfloat m[16] = {};
|
||||
ProgramUniformMatrix2fv(program, -1, 1, GL_FALSE, m);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix2fv";
|
||||
ProgramUniformMatrix3fv(program, -1, 1, GL_FALSE, m);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix3fv";
|
||||
ProgramUniformMatrix4fv(program, -1, 1, GL_FALSE, m);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix4fv";
|
||||
ProgramUniformMatrix2x3fv(program, -1, 1, GL_FALSE, m);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix2x3fv";
|
||||
ProgramUniformMatrix3x2fv(program, -1, 1, GL_FALSE, m);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix3x2fv";
|
||||
ProgramUniformMatrix2x4fv(program, -1, 1, GL_FALSE, m);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix2x4fv";
|
||||
ProgramUniformMatrix4x2fv(program, -1, 1, GL_FALSE, m);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix4x2fv";
|
||||
ProgramUniformMatrix3x4fv(program, -1, 1, GL_FALSE, m);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix3x4fv";
|
||||
ProgramUniformMatrix4x3fv(program, -1, 1, GL_FALSE, m);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix4x3fv";
|
||||
|
||||
const GLdouble md[16] = {};
|
||||
ProgramUniformMatrix2dv(program, -1, 1, GL_FALSE, md);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix2dv";
|
||||
ProgramUniformMatrix3dv(program, -1, 1, GL_FALSE, md);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix3dv";
|
||||
ProgramUniformMatrix4dv(program, -1, 1, GL_FALSE, md);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix4dv";
|
||||
ProgramUniformMatrix2x3dv(program, -1, 1, GL_FALSE, md);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix2x3dv";
|
||||
ProgramUniformMatrix3x2dv(program, -1, 1, GL_FALSE, md);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix3x2dv";
|
||||
ProgramUniformMatrix2x4dv(program, -1, 1, GL_FALSE, md);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix2x4dv";
|
||||
ProgramUniformMatrix4x2dv(program, -1, 1, GL_FALSE, md);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix4x2dv";
|
||||
ProgramUniformMatrix3x4dv(program, -1, 1, GL_FALSE, md);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix3x4dv";
|
||||
ProgramUniformMatrix4x3dv(program, -1, 1, GL_FALSE, md);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_OPERATION) << "glProgramUniformMatrix4x3dv";
|
||||
|
||||
// A name GL never handed out is INVALID_VALUE, and the -1 location must not swallow that
|
||||
// either - this is the second error the early-out was hiding.
|
||||
ProgramUniformMatrix4fv(0xDEADBEEFu, -1, 1, GL_FALSE, m);
|
||||
EXPECT_EQ(GetError(), GL_INVALID_VALUE);
|
||||
|
||||
DrainProgramTestErrors();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user