mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 13:48:30 +09:00
[Fix] (ShaderTranspiler): enforce the layout(binding) range rule for samplers, images and uniform/atomic-counter blocks, not only for SSBOs
This commit is contained in:
@@ -668,6 +668,26 @@ namespace MobileGL::MG_State {
|
||||
}
|
||||
}
|
||||
if (!anyStage) return nullProgram;
|
||||
// Transform feedback captures the output of the LAST vertex-processing stage
|
||||
// (GL 4.6 core 11.1.2.1), and glTransformFeedbackVaryings is per-PROGRAM state that
|
||||
// only the stage program carrying that stage can have been given. The composite is
|
||||
// assembled out of the stage programs' shaders and inherits none of their
|
||||
// GL-thread-owned request state, so without this the composite links with an empty
|
||||
// capture list and glBeginTransformFeedback rejects the draw with INVALID_OPERATION
|
||||
// ("the program has no transform feedback varyings") even though
|
||||
// glValidateProgramPipeline had just passed. Same resolution order as
|
||||
// ProgramLinkTask::ResolveTransformFeedbackVaryings: geometry, else tessellation
|
||||
// evaluation, else vertex.
|
||||
for (const ShaderStage captureStage:
|
||||
{ShaderStage::Geometry, ShaderStage::TessEval, ShaderStage::Vertex}) {
|
||||
const auto& captureProgram = pipeline->GetStageProgram(captureStage);
|
||||
if (!captureProgram) continue;
|
||||
const auto& requested = captureProgram->GetRequestedTransformFeedbackVaryings();
|
||||
if (requested.empty()) continue;
|
||||
composite->SetTransformFeedbackVaryings(Vector<String>(requested),
|
||||
captureProgram->GetRequestedTransformFeedbackBufferMode());
|
||||
break;
|
||||
}
|
||||
// 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);
|
||||
|
||||
@@ -621,13 +621,21 @@ namespace MobileGL::MG_State::GLState {
|
||||
// mapper's collect callback is the last point at which a resource's qualifier still
|
||||
// says what the SHADER declared rather than what glslang assigned, so both captures
|
||||
// have to be taken from inside the link. See TMglGlslIoResolver::reserverResourceSlot.
|
||||
// The binding-range rule (GLSL 4.30 4.4.5): its ceilings in, and the first violation the
|
||||
// resolver finds out. Enforced at the link because mapIO's collect callback is the last
|
||||
// point at which a resource's qualifier still says what the SHADER declared - see
|
||||
// TMglGlslIoResolver::CheckDeclaredBindingRange.
|
||||
String resourceBindingViolation;
|
||||
ProgramAttrib attrib{.shaders = Move(shaders),
|
||||
.explicitVertexInLocations = in.explicitAttribLocations,
|
||||
.explicitFragmentOutLocations = in.explicitFragDataLocation,
|
||||
.explicitFragmentOutIndices = in.explicitFragDataIndex,
|
||||
.explicitOpaqueUniformBindings = &artifacts.explicitOpaqueUniformBindings,
|
||||
.storageBlocksWithoutBinding = &artifacts.storageBlocksWithoutBinding,
|
||||
.uniformBlocksWithoutBinding = &artifacts.uniformBlocksWithoutBinding};
|
||||
.uniformBlocksWithoutBinding = &artifacts.uniformBlocksWithoutBinding,
|
||||
.resourceBindingLimits = in.env ? ResolveResourceBindingLimits(*in.env)
|
||||
: MG_Util::ShaderTranspiler::ResourceBindingLimits{},
|
||||
.resourceBindingViolation = &resourceBindingViolation};
|
||||
|
||||
MGLOG_D("ProgramObject %u: Calling ShaderCompiler::LinkProgram", in.externalIndex);
|
||||
auto result = ShaderCompiler::LinkProgram(attrib);
|
||||
|
||||
@@ -492,6 +492,10 @@ namespace MobileGL::MG_State::GLState {
|
||||
// time, for anything cached during the pending window itself.)
|
||||
++m_backendStateVersion;
|
||||
BumpLinkObservableVersions();
|
||||
// The separable flag takes effect HERE, at the link, and nowhere else (GL 4.6 core 7.3).
|
||||
// Latched before the early-outs below so a link that fails still counts as a link -
|
||||
// what must not update it is a link that never happened at all.
|
||||
m_linkedSeparable = m_separable;
|
||||
// A whole-struct reset, unlike ResetLinkArtifacts(): during the pending window this
|
||||
// is what every gated reader sees, so it has to be the complete "not linked" state -
|
||||
// including the fields ResetLinkArtifacts deliberately preserves for its own callers.
|
||||
|
||||
@@ -915,6 +915,14 @@ namespace MobileGL::MG_State::GLState {
|
||||
// subset of the stages of a program pipeline. Only takes effect on the next link,
|
||||
// which is why it is plain state here rather than something Link() consults.
|
||||
Bool GetSeparable() const { return m_separable; }
|
||||
// What GL_PROGRAM_SEPARABLE actually reports, and what glUseProgramStages actually
|
||||
// requires: the value the flag held at the program's LAST LINK, not the live flag.
|
||||
// GL 4.6 core 7.3 - "the flag takes effect the next time the program is linked" - so a
|
||||
// program that was told to be separable and then never linked is still NOT separable,
|
||||
// which is precisely what es31cSeparateShaderObjsTests's PipelineApi and CreateShadProgApi
|
||||
// assert. The live flag stays available as GetSeparable() for glGetProgramiv's sibling
|
||||
// state and for the next link to latch.
|
||||
Bool GetLinkedSeparable() const { return m_linkedSeparable; }
|
||||
void SetSeparable(Bool separable) {
|
||||
m_separable = separable;
|
||||
// ---- arming the uniform-write tracking latch ----
|
||||
@@ -1528,6 +1536,14 @@ namespace MobileGL::MG_State::GLState {
|
||||
m_requestedXfbVaryings = Move(names);
|
||||
m_requestedXfbBufferMode = bufferMode;
|
||||
}
|
||||
// The REQUEST, not the linked result: what glTransformFeedbackVaryings last recorded,
|
||||
// which the next link will try to resolve. A program pipeline's draw composite reads it
|
||||
// off the capturing stage program and re-issues it on itself, because the composite is
|
||||
// built from the stage programs' SHADERS and would otherwise inherit no capture list at
|
||||
// all - which made glBeginTransformFeedback reject every separable-program capture
|
||||
// (glcSeparableProgramsTransformFeedbackTests).
|
||||
const Vector<String>& GetRequestedTransformFeedbackVaryings() const { return m_requestedXfbVaryings; }
|
||||
GLenum GetRequestedTransformFeedbackBufferMode() const { return m_requestedXfbBufferMode; }
|
||||
GLenum GetTransformFeedbackBufferMode() const { return Artifacts().xfbBufferMode; }
|
||||
SizeT GetTransformFeedbackVaryingCount() const { return Artifacts().xfbVaryings.size(); }
|
||||
const XfbVarying* GetTransformFeedbackVarying(SizeT index) const {
|
||||
@@ -1703,6 +1719,11 @@ namespace MobileGL::MG_State::GLState {
|
||||
Bool m_deleteStatus = false;
|
||||
Bool m_binaryRetrievableHint = false;
|
||||
Bool m_separable = false;
|
||||
// m_separable as of the last link; see GetLinkedSeparable. Latched by Link() rather than
|
||||
// carried in LinkArtifacts because it is a GL-thread-owned decision made at enqueue time,
|
||||
// not a result the worker computes - and because a FAILED link still latches it, exactly
|
||||
// as a successful one does.
|
||||
Bool m_linkedSeparable = false;
|
||||
// Monotone "this program may ever be a pipeline stage" latch; see SetSeparable for why
|
||||
// it is a latch and not just m_separable. Outside LinkArtifacts on purpose: a relink
|
||||
// clears the write SET, but a program that was separable is still separable after it.
|
||||
|
||||
@@ -140,17 +140,21 @@ namespace {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// What glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS) answers, recomputed rather than
|
||||
// queried: the compile runs on a worker with no context, and the pname is not a plain backend
|
||||
// parameter - the getter caps the backend's count by the state layer's fixed binding-point
|
||||
// array (GL_Getter's GetIndexedBufferQueryPointCount). A shader must be judged against the
|
||||
// number the application was told, not against either half of it.
|
||||
// What glGetIntegerv(GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS) answers. Derived by the shared
|
||||
// ResolveResourceBindingLimits so the compile-time scan below and the link-time general check
|
||||
// (TMglGlslIoResolver::CheckDeclaredBindingRange) can never disagree about the number.
|
||||
//
|
||||
// Why BOTH still exist. GLSL makes an over-range binding a COMPILE-time error, and this scan
|
||||
// is the only place MobileGL can raise one - glslang's own ceilings are switched off by the
|
||||
// relaxed Vulkan parse and cannot be turned back on without changing the parse everything
|
||||
// else depends on. The link-time check covers the four kinds a lexical scan of unexpanded
|
||||
// source cannot see at all (samplers, images, uniform blocks, atomic counters, whose binding
|
||||
// only survives inside a synthesized block NAME) and re-covers storage blocks as a backstop.
|
||||
// The conformance predicate is compile AND link, so either site satisfies it; the split is
|
||||
// about WHICH error GL reports, not about whether the shader is rejected.
|
||||
static MobileGL::Int MaxShaderStorageBufferBindings(
|
||||
const MobileGL::MG_Util::ShaderTranspiler::CompileEnv& env) {
|
||||
const MobileGL::Int frontendPoints =
|
||||
static_cast<MobileGL::Int>(MobileGL::MG_State::GLState::BufferBindingPointCount);
|
||||
if (!env.HasBackend()) return frontendPoints;
|
||||
return std::min<MobileGL::Int>(frontendPoints, std::max<MobileGL::Int>(env.params.MaxShaderStorageBufferBindings, 0));
|
||||
return MobileGL::MG_State::GLState::ResolveResourceBindingLimits(env).MaxShaderStorageBufferBindings;
|
||||
}
|
||||
|
||||
// The half of a compile that depends on nothing but the source text, the stage and the
|
||||
|
||||
@@ -10,9 +10,48 @@
|
||||
#include <Includes.h>
|
||||
#include <MG_Util/Async/JobNode.h>
|
||||
#include <MG_Util/ShaderTranspiler/CompileEnv.h>
|
||||
#include <MG_Util/ShaderTranspiler/Types.h>
|
||||
#include <MG_State/GLState/BufferState/BufferState.h>
|
||||
#include <MG_State/GLState/ProgramState/ShaderPreprocessCache.h>
|
||||
|
||||
namespace MobileGL::MG_State::GLState {
|
||||
// THE one derivation of the binding ceilings a shader-declared layout(binding = N) is judged
|
||||
// against. Two readers have to agree on them - the compile-time storage-block scan below and
|
||||
// the link-time general check in TMglGlslIoResolver - and the numbers are recomputed here
|
||||
// rather than queried because both readers run on a worker with no context.
|
||||
//
|
||||
// Each is exactly what glGetIntegerv answers for the matching pname, and none of them is a
|
||||
// plain backend parameter: the buffer families are additionally capped by the state layer's
|
||||
// indexed-binding array (GL_Getter's GetIndexedBufferQueryPointCount does the same), because
|
||||
// a shader must be judged against the number the APPLICATION was told, not against either
|
||||
// half of it. Lives in MG_State rather than in MG_Util/ShaderTranspiler/Types.h purely
|
||||
// because BufferBindingPointCount is state-layer knowledge that the transpiler layer must
|
||||
// not reach up for.
|
||||
inline MG_Util::ShaderTranspiler::ResourceBindingLimits ResolveResourceBindingLimits(
|
||||
const MG_Util::ShaderTranspiler::CompileEnv& env) {
|
||||
namespace ST = MG_Util::ShaderTranspiler;
|
||||
ST::ResourceBindingLimits limits;
|
||||
const Int bindingPoints = static_cast<Int>(BufferBindingPointCount);
|
||||
// The atomic-counter ceiling is a frontend constant, so it holds even with no backend -
|
||||
// and it is the number BuildTBuiltInResource compiles a layout(binding = N) atomic_uint
|
||||
// against, which is what makes it enforceable at all.
|
||||
limits.MaxAtomicCounterBufferBindings = std::min<Int>(bindingPoints, ST::MAX_ATOMIC_COUNTER_BUFFER_BINDINGS);
|
||||
// So is the uniform-buffer one: GL_MAX_UNIFORM_BUFFER_BINDINGS is clamped to the indexed
|
||||
// binding array in the getter and its floor (the GL 4.5 core minimum of 84) is that same
|
||||
// array's width, so the backend's own number never moves it.
|
||||
limits.MaxUniformBufferBindings = bindingPoints;
|
||||
if (!env.HasBackend()) {
|
||||
// No backend: the two backend-derived ceilings have nothing to be measured against,
|
||||
// and zero means "do not enforce this kind" rather than "reject everything".
|
||||
return limits;
|
||||
}
|
||||
limits.MaxSamplerBindings = std::max<Int>(env.params.MaxCombinedTextureImageUnits, 0);
|
||||
limits.MaxImageBindings = std::max<Int>(env.params.MaxImageUnits, 0);
|
||||
limits.MaxShaderStorageBufferBindings =
|
||||
std::min<Int>(bindingPoints, std::max<Int>(env.params.MaxShaderStorageBufferBindings, 0));
|
||||
return limits;
|
||||
}
|
||||
|
||||
// glslang has no "detach this thread" API in the vendored revision, but TShader::parse
|
||||
// leaves the calling thread's TLS pool allocator pointing at the shader's own pool and
|
||||
// never restores it. Left there, the next allocation this thread makes - in an unrelated
|
||||
|
||||
Reference in New Issue
Block a user