mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (GL): fail the link of a non-separable program that has a tessellation or geometry stage but no vertex stage
This commit is contained in:
@@ -823,6 +823,9 @@ namespace MobileGL::MG_State {
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Separable, as every program it is assembled from is: a pipeline may leave the vertex
|
||||
// stage to nobody, and that is the draw-time pipeline rule's to refuse, not the link's.
|
||||
composite->SetSeparable(true);
|
||||
// A pipeline with no fragment stage still rasterises, so the default fragment
|
||||
// shader is wanted here even though the separable stage programs never get one.
|
||||
composite->Link(true);
|
||||
|
||||
@@ -1223,6 +1223,28 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
}
|
||||
|
||||
// GL 4.6 core 7.3: a program that is NOT separable and has a tessellation control,
|
||||
// tessellation evaluation or geometry shader must also have a vertex shader - only a
|
||||
// separable program may supply a subset of the vertex-processing stages and leave the
|
||||
// rest to a program pipeline. Pipeline composites link as separable for the same reason;
|
||||
// the draw-time pipeline rule is what refuses such a composite.
|
||||
if (!in.separable) {
|
||||
Bool hasVertex = false;
|
||||
Bool hasLaterVertexProcessing = false;
|
||||
for (const LinkShaderInput& input : in.shaders) {
|
||||
hasVertex = hasVertex || input.stage == ShaderStage::Vertex;
|
||||
hasLaterVertexProcessing = hasLaterVertexProcessing || input.stage == ShaderStage::TessControl ||
|
||||
input.stage == ShaderStage::TessEval ||
|
||||
input.stage == ShaderStage::Geometry;
|
||||
}
|
||||
if (hasLaterVertexProcessing && !hasVertex) {
|
||||
artifacts.infoLog = "A program that is not separable and has a tessellation or geometry shader "
|
||||
"must also have a vertex shader.";
|
||||
DeferLog(std::format("ProgramObject {}: Link failed - {}", in.externalIndex, artifacts.infoLog));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (SizeT i = 0; i < in.shaders.size(); i++) {
|
||||
const LinkShaderInput& input = in.shaders[i];
|
||||
const GLenum shaderType = MG_Util::ConvertShaderStageToGLEnum(input.stage);
|
||||
|
||||
@@ -63,6 +63,8 @@ namespace MobileGL::MG_State::GLState {
|
||||
SharedPtr<const MG_Util::ShaderTranspiler::CompileEnv> env;
|
||||
// Startup configuration copied with the task, never read from worker code.
|
||||
Bool enableSpirvValidation = false;
|
||||
// GL_PROGRAM_SEPARABLE as this link latches it (GL 4.6 core 7.3).
|
||||
Bool separable = false;
|
||||
// The four "takes effect at the next link" request maps. Snapshotted rather than
|
||||
// referenced, which is precisely what makes glBindAttribLocation and friends
|
||||
// legal to call over a pending link without cancelling it: the pending link keeps
|
||||
|
||||
@@ -617,6 +617,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
task->in.externalIndex = m_externalIndex;
|
||||
task->in.env = MG_Util::ShaderTranspiler::GetCurrentCompileEnv();
|
||||
task->in.enableSpirvValidation = MG_Config::Features.EnableSpirvValidation;
|
||||
task->in.separable = m_linkedSeparable;
|
||||
task->in.explicitAttribLocations = m_explicitAttribLocations;
|
||||
task->in.explicitFragDataLocation = m_explicitFragDataLocation;
|
||||
task->in.explicitFragDataIndex = m_explicitFragDataIndex;
|
||||
|
||||
@@ -571,6 +571,40 @@ void main() { gl_Position = vec4(0.0); EmitVertex(); EndPrimitive(); }
|
||||
}
|
||||
}
|
||||
|
||||
// GL 4.6 core 7.3: only a SEPARABLE program may have a geometry (or tessellation) stage without a
|
||||
// vertex stage - KHR-GL4x.geometry_shader.linking.incomplete_program_objects. The pipeline
|
||||
// composite assembled from such a separable program has to keep linking, which the case above
|
||||
// already pins.
|
||||
TEST_F(ProgramPipelineCompositeTest, OnlyASeparableProgramMayLinkAGeometryStageWithoutAVertexStage) {
|
||||
const char* kGs = R"(#version 430 core
|
||||
layout(points) in;
|
||||
layout(points, max_vertices = 1) out;
|
||||
void main() { gl_Position = vec4(0.0); EmitVertex(); EndPrimitive(); }
|
||||
)";
|
||||
const auto link = [&](Bool separable, Bool withFragment) {
|
||||
const GLuint program = CreateProgram();
|
||||
if (separable) ProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE);
|
||||
const auto attach = [program](GLenum stage, const char* source) {
|
||||
const GLuint shader = CreateShader(stage);
|
||||
ShaderSource(shader, 1, &source, nullptr);
|
||||
CompileShader(shader);
|
||||
AttachShader(program, shader);
|
||||
};
|
||||
attach(GL_GEOMETRY_SHADER, kGs);
|
||||
if (withFragment) attach(GL_FRAGMENT_SHADER, kSharedUniformFs);
|
||||
LinkProgram(program);
|
||||
GLint linked = -1;
|
||||
GetProgramiv(program, GL_LINK_STATUS, &linked);
|
||||
DeleteProgram(program);
|
||||
return linked == GL_TRUE;
|
||||
};
|
||||
EXPECT_FALSE(link(false, false));
|
||||
EXPECT_FALSE(link(false, true));
|
||||
EXPECT_TRUE(link(true, false));
|
||||
EXPECT_TRUE(link(true, true));
|
||||
EXPECT_EQ(GetError(), GL_NO_ERROR);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------------------------
|
||||
// The composite's transform-feedback capture list.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user