mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Fix, Test] (GLState): fail the link when a stage exceeds GL_MAX_*_IMAGE_UNIFORMS
This commit is contained in:
@@ -134,6 +134,81 @@ namespace {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GL 4.6 core 7.6: LinkProgram FAILS when a stage's count of active image uniforms exceeds
|
||||||
|
// GL_MAX_{VERTEX,TESS_CONTROL,TESS_EVALUATION,GEOMETRY,FRAGMENT,COMPUTE}_IMAGE_UNIFORMS, or
|
||||||
|
// when their sum exceeds GL_MAX_COMBINED_IMAGE_UNIFORMS. Nothing enforced it: glslang carries
|
||||||
|
// those numbers in TBuiltInResource only so gl_Max*ImageUniforms can expand from them, and
|
||||||
|
// its linker never counts uniforms against them - so a program declaring one image uniform
|
||||||
|
// more than the limit linked cleanly and then rendered nothing.
|
||||||
|
//
|
||||||
|
// The limits are the ones glGetIntegerv answers (MG_Impl/GLImpl/Getter/GL_Getter.cpp), the
|
||||||
|
// hardcoded tessellation zeros included: a program may not exceed a limit the implementation
|
||||||
|
// advertises, whatever the driver underneath would have taken.
|
||||||
|
//
|
||||||
|
// Counts the APPLICATION's image uniforms. The DirectGLES read/write split emits a second
|
||||||
|
// declaration for an image a stage both reads and writes (MG_Backend/DirectGLES/Utils.h), but
|
||||||
|
// that happens in the backend after this link, and counting the expanded set here would
|
||||||
|
// reject programs that are legal by the numbers GL advertises. Returns the info-log line for
|
||||||
|
// a program over a limit, empty for one within them.
|
||||||
|
static MobileGL::String ValidateImageUniformLimits(
|
||||||
|
glslang::TProgram& reflection, const MobileGL::MG_Util::ShaderTranspiler::CompileEnv& env) {
|
||||||
|
using MobileGL::Int;
|
||||||
|
using MobileGL::SizeT;
|
||||||
|
|
||||||
|
static constexpr EShLanguage kStages[] = {EShLangVertex, EShLangTessControl, EShLangTessEvaluation,
|
||||||
|
EShLangGeometry, EShLangFragment, EShLangCompute};
|
||||||
|
static constexpr const char* kLimitNames[] = {
|
||||||
|
"GL_MAX_VERTEX_IMAGE_UNIFORMS", "GL_MAX_TESS_CONTROL_IMAGE_UNIFORMS",
|
||||||
|
"GL_MAX_TESS_EVALUATION_IMAGE_UNIFORMS", "GL_MAX_GEOMETRY_IMAGE_UNIFORMS",
|
||||||
|
"GL_MAX_FRAGMENT_IMAGE_UNIFORMS", "GL_MAX_COMPUTE_IMAGE_UNIFORMS"};
|
||||||
|
constexpr SizeT kStageCount = sizeof(kStages) / sizeof(kStages[0]);
|
||||||
|
const Int limits[kStageCount] = {env.params.MaxVertexImageUniforms,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
env.params.MaxGeometryImageUniforms,
|
||||||
|
env.params.MaxFragmentImageUniforms,
|
||||||
|
env.params.MaxComputeImageUniforms};
|
||||||
|
|
||||||
|
Int counts[kStageCount] = {};
|
||||||
|
const Int uniformCount = reflection.getNumUniformVariables();
|
||||||
|
for (Int i = 0; i < uniformCount; ++i) {
|
||||||
|
const auto& uniform = reflection.getUniform(i);
|
||||||
|
const glslang::TType* type = uniform.getType();
|
||||||
|
if (type == nullptr || !type->isImage()) continue;
|
||||||
|
// An array occupies one image unit per element; an unsized one (never indexed, so
|
||||||
|
// never more than the single element glslang kept) counts as one.
|
||||||
|
Int elements = uniform.size > 1 ? uniform.size : 1;
|
||||||
|
if (type->isArray()) {
|
||||||
|
elements = type->isSizedArray() ? type->getCumulativeArraySize() : 1;
|
||||||
|
}
|
||||||
|
// `stages` is the set of stages that REFERENCE the uniform, which is exactly what GL
|
||||||
|
// counts: an image declared in two stages costs a unit in each, and one no stage
|
||||||
|
// reads is not active at all and costs nothing.
|
||||||
|
for (SizeT stage = 0; stage < kStageCount; ++stage) {
|
||||||
|
if ((static_cast<unsigned>(uniform.stages) & (1u << static_cast<unsigned>(kStages[stage]))) == 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
counts[stage] += elements;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Int combined = 0;
|
||||||
|
for (SizeT stage = 0; stage < kStageCount; ++stage) {
|
||||||
|
combined += counts[stage];
|
||||||
|
if (counts[stage] > limits[stage]) {
|
||||||
|
return std::format("This program uses {} active image uniforms in one stage, more than the {} "
|
||||||
|
"{} allows.",
|
||||||
|
counts[stage], limits[stage], kLimitNames[stage]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (combined > env.params.MaxCombinedImageUniforms) {
|
||||||
|
return std::format("This program uses {} active image uniforms across its stages, more than the {} "
|
||||||
|
"GL_MAX_COMBINED_IMAGE_UNIFORMS allows.",
|
||||||
|
combined, env.params.MaxCombinedImageUniforms);
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
static bool IsBuiltInPipelineOutput(const glslang::TObjectReflection& output) {
|
static bool IsBuiltInPipelineOutput(const glslang::TObjectReflection& output) {
|
||||||
const auto* type = output.getType();
|
const auto* type = output.getType();
|
||||||
return type && type->getQualifier().builtIn != glslang::EbvNone;
|
return type && type->getQualifier().builtIn != glslang::EbvNone;
|
||||||
@@ -685,6 +760,14 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (String imageUniformError = ValidateImageUniformLimits(*artifacts.program, env);
|
||||||
|
!imageUniformError.empty()) {
|
||||||
|
artifacts.infoLog = Move(imageUniformError);
|
||||||
|
DeferLog(std::format("ProgramObject {}: Link failed - {}", in.externalIndex, artifacts.infoLog));
|
||||||
|
ProgramObject::ResetLinkArtifacts(artifacts);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- GL-facing index spaces (relaxed-parse cleanup) ----------
|
// ---------- GL-facing index spaces (relaxed-parse cleanup) ----------
|
||||||
// Blocks first: global-UBO membership drives the uniform filter below. The
|
// Blocks first: global-UBO membership drives the uniform filter below. The
|
||||||
// synthesized MGL_GLOBAL_UBO is a transpiler artifact - its members are GL
|
// synthesized MGL_GLOBAL_UBO is a transpiler artifact - its members are GL
|
||||||
|
|||||||
@@ -754,6 +754,77 @@ void main() {
|
|||||||
ClearErrors();
|
ClearErrors();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GL 4.6 core 7.6 fails the link when a stage's active image uniforms exceed
|
||||||
|
// GL_MAX_*_IMAGE_UNIFORMS, or when their sum exceeds GL_MAX_COMBINED_IMAGE_UNIFORMS. Nothing
|
||||||
|
// counted them - glslang keeps those numbers only so gl_Max*ImageUniforms can expand from
|
||||||
|
// them - so every deliberately-oversized program in
|
||||||
|
// KHR-GL4x.shader_image_load_store.uniform-limits linked cleanly and then rendered nothing.
|
||||||
|
//
|
||||||
|
// Sized off the ADVERTISED limits rather than a constant, because the numbers come from the
|
||||||
|
// active backend and the whole point of the check is that the two agree.
|
||||||
|
TEST_F(ProgramInterfaceTest, ImageUniformsOverAStageLimitFailToLink) {
|
||||||
|
GLint maxFragmentImages = 0;
|
||||||
|
GLint maxCombinedImages = 0;
|
||||||
|
GetIntegerv(GL_MAX_FRAGMENT_IMAGE_UNIFORMS, &maxFragmentImages);
|
||||||
|
GetIntegerv(GL_MAX_COMBINED_IMAGE_UNIFORMS, &maxCombinedImages);
|
||||||
|
ClearErrors();
|
||||||
|
ASSERT_GT(maxFragmentImages, 0);
|
||||||
|
|
||||||
|
// The fragment stage is compiled explicitly so a COMPILE failure can never be mistaken
|
||||||
|
// for the link failure under test.
|
||||||
|
const auto linkWithFragmentImages = [](GLint count) {
|
||||||
|
const std::string n = std::to_string(count);
|
||||||
|
const std::string source = std::string(R"(#version 430
|
||||||
|
out vec4 color;
|
||||||
|
layout(r32i) uniform iimage2D u_image[)") + n + R"(];
|
||||||
|
void main() {
|
||||||
|
int value = 1;
|
||||||
|
for (int i = 0; i < )" + n + R"(; ++i) {
|
||||||
|
value = imageAtomicAdd(u_image[i], ivec2(0), value);
|
||||||
|
}
|
||||||
|
color = vec4(float(value));
|
||||||
|
}
|
||||||
|
)";
|
||||||
|
const char* sourcePtr = source.c_str();
|
||||||
|
const GLuint fs = CreateShader(GL_FRAGMENT_SHADER);
|
||||||
|
ShaderSource(fs, 1, &sourcePtr, nullptr);
|
||||||
|
CompileShader(fs);
|
||||||
|
GLint compiled = 0;
|
||||||
|
GetShaderiv(fs, GL_COMPILE_STATUS, &compiled);
|
||||||
|
EXPECT_EQ(compiled, GL_TRUE) << "the fragment stage with " << count << " image uniforms must compile";
|
||||||
|
|
||||||
|
const GLuint vs = CreateShader(GL_VERTEX_SHADER);
|
||||||
|
ShaderSource(vs, 1, &kSimpleVs, nullptr);
|
||||||
|
CompileShader(vs);
|
||||||
|
|
||||||
|
const GLuint program = CreateProgram();
|
||||||
|
AttachShader(program, vs);
|
||||||
|
AttachShader(program, fs);
|
||||||
|
LinkProgram(program);
|
||||||
|
return program;
|
||||||
|
};
|
||||||
|
|
||||||
|
const GLuint over = linkWithFragmentImages(maxFragmentImages + 1);
|
||||||
|
GLint status = -1;
|
||||||
|
GetProgramiv(over, GL_LINK_STATUS, &status);
|
||||||
|
EXPECT_EQ(status, GL_FALSE);
|
||||||
|
char log[4096] = "";
|
||||||
|
GetProgramInfoLog(over, sizeof(log), nullptr, log);
|
||||||
|
EXPECT_NE(std::string(log).find("GL_MAX_FRAGMENT_IMAGE_UNIFORMS"), std::string::npos)
|
||||||
|
<< "info log was: " << log;
|
||||||
|
ClearErrors();
|
||||||
|
|
||||||
|
// Exactly AT the limit is legal and must still link: the comparison is strictly
|
||||||
|
// greater-than, and the conformance suite's combined-stage subcase builds a program that
|
||||||
|
// fills every stage to its own limit and expects it to link whenever the combined limit
|
||||||
|
// can hold them.
|
||||||
|
if (maxFragmentImages <= maxCombinedImages) {
|
||||||
|
const GLuint atLimit = linkWithFragmentImages(maxFragmentImages);
|
||||||
|
ExpectLinked(atLimit);
|
||||||
|
ClearErrors();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// glGetProgramiv(GL_ACTIVE_ATOMIC_COUNTER_BUFFERS) and glGetActiveAtomicCounterBufferiv are
|
// glGetProgramiv(GL_ACTIVE_ATOMIC_COUNTER_BUFFERS) and glGetActiveAtomicCounterBufferiv are
|
||||||
// the pre-4.3 spelling of the interface above, and the spec requires the two to agree.
|
// the pre-4.3 spelling of the interface above, and the spec requires the two to agree.
|
||||||
// Neither did: the first counted glslang's atomic counter UNIFORMS - zero, because the
|
// Neither did: the first counted glslang's atomic counter UNIFORMS - zero, because the
|
||||||
|
|||||||
Reference in New Issue
Block a user