mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-10 21:28:32 +09:00
[Fix] (Review): bound copies by the requested level, reach every cube face, keep array layer counts, and give glSpecializeShader its spec error surface
This commit is contained in:
@@ -650,6 +650,12 @@ namespace MobileGL::MG_State {
|
||||
// a graphics program carrying a compute module, which Adreno 830 does not reject
|
||||
// from vkCreateGraphicsPipelines - it SIGSEGVs inside it.
|
||||
Bool anyStage = false;
|
||||
// Which stages the composite ACTUALLY got a shader for. Not the same question as
|
||||
// "which stages have a stage program bound": one program bound with
|
||||
// GL_ALL_SHADER_BITS occupies every slot while contributing a shader to only the
|
||||
// stages it was linked with. The transform-feedback capture stage is chosen off this,
|
||||
// because it has to be the stage that will exist in the composite's own link.
|
||||
Bool compositeHasStage[ProgramPipelineObject::kGraphicsStageCount] = {};
|
||||
for (SizeT stage = 0; stage < ProgramPipelineObject::kGraphicsStageCount; ++stage) {
|
||||
const auto& stageProgram = pipeline->GetStageProgram(static_cast<ShaderStage>(stage));
|
||||
if (!stageProgram) continue;
|
||||
@@ -665,6 +671,7 @@ namespace MobileGL::MG_State {
|
||||
if (!ref.shader || static_cast<SizeT>(ref.shader->GetShaderStage()) != stage) continue;
|
||||
composite->AttachShaderWithPinnedLinkInput(ref);
|
||||
anyStage = true;
|
||||
compositeHasStage[stage] = true;
|
||||
}
|
||||
}
|
||||
if (!anyStage) return nullProgram;
|
||||
@@ -672,20 +679,47 @@ namespace MobileGL::MG_State {
|
||||
// (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.
|
||||
// GL-thread-owned state, so without this it 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 passed.
|
||||
//
|
||||
// TWO RULES, both easy to get subtly wrong and both load-bearing:
|
||||
//
|
||||
// (1) THE LINKED LIST, NOT THE PENDING REQUEST. glTransformFeedbackVaryings does not
|
||||
// take effect until the program's next link (GL 4.6 core 7.3/11.1.2.1), and it
|
||||
// deliberately bumps no version - so a request written after the stage program's
|
||||
// last link is invisible to the composite cache's signature yet would be picked up
|
||||
// by the next rebuild, making the capture list depend on whether some unrelated
|
||||
// event happened to invalidate the cache. Worse, a name that is not an output of
|
||||
// the capture stage fails the composite's OWN link, and a failed composite makes
|
||||
// every draw through the pipeline report INVALID_OPERATION. Reading the LINKED
|
||||
// snapshot removes the whole class: linked state only moves at a link, and a link
|
||||
// is exactly what ComputeDrawProgramSignature's per-stage link version tracks, so
|
||||
// the existing cache key is sufficient by construction.
|
||||
// GetTransformFeedbackInterfaceNames() is the right accessor rather than the
|
||||
// resolved xfbVaryings: it is the request as that link consumed it, pseudo-varyings
|
||||
// (gl_NextBuffer / gl_SkipComponentsN) included, which is what re-issuing it needs.
|
||||
//
|
||||
// (2) THE FIRST STAGE THAT EXISTS, not the first with something to capture. This is
|
||||
// the rule ProgramLinkTask::ResolveTransformFeedbackVaryings applies (it breaks on
|
||||
// getIntermediate(stage) != nullptr), and the two MUST agree: this loop picks
|
||||
// WHOSE list, the link task picks WHICH stage's outputs the names resolve against.
|
||||
// Skipping a geometry stage that has no capture list and installing the vertex
|
||||
// stage's instead made them disagree, and the composite then resolved a vertex
|
||||
// program's names against the geometry intermediate - capturing where GL says it
|
||||
// must not, or failing the link and killing every draw. A capture stage with an
|
||||
// empty list is not a reason to look further down: it is the answer, and
|
||||
// glBeginTransformFeedback's INVALID_OPERATION is the correct consequence.
|
||||
for (const ShaderStage captureStage:
|
||||
{ShaderStage::Geometry, ShaderStage::TessEval, ShaderStage::Vertex}) {
|
||||
if (!compositeHasStage[static_cast<SizeT>(captureStage)]) continue;
|
||||
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());
|
||||
const auto& linkedNames = captureProgram->GetTransformFeedbackInterfaceNames();
|
||||
if (!linkedNames.empty()) {
|
||||
composite->SetTransformFeedbackVaryings(Vector<String>(linkedNames),
|
||||
captureProgram->GetTransformFeedbackBufferMode());
|
||||
}
|
||||
break;
|
||||
}
|
||||
// A pipeline with no fragment stage still rasterises, so the default fragment
|
||||
|
||||
@@ -540,6 +540,33 @@ namespace MobileGL::MG_State::GLState {
|
||||
task->in.explicitFragDataIndex = m_explicitFragDataIndex;
|
||||
task->in.requestedXfbVaryings = m_requestedXfbVaryings;
|
||||
task->in.requestedXfbBufferMode = m_requestedXfbBufferMode;
|
||||
// ARB_gl_spirv: a program built from SPIR-V declares its transform feedback through
|
||||
// XfbBuffer/XfbStride/Offset DECORATIONS, and glTransformFeedbackVaryings has no effect on
|
||||
// it at all. glSpecializeShader translated those decorations into the equivalent name
|
||||
// request (ShaderCompiler::SpecializeAndDecompileSpirvModule), and this is where it enters
|
||||
// the link - so everything downstream, the frontend packer and both backends, sees one
|
||||
// declaration form instead of two.
|
||||
//
|
||||
// The capture stage is the LAST vertex-processing stage the program has, which is the same
|
||||
// rule ProgramLinkTask::ResolveTransformFeedbackVaryings resolves the names against. The
|
||||
// application's own request wins if it made one: that can only happen on a mixed program,
|
||||
// which is not a shape ARB_gl_spirv defines, and honouring what the application explicitly
|
||||
// asked for is the safer of the two readings.
|
||||
if (task->in.requestedXfbVaryings.empty()) {
|
||||
for (const ShaderStage captureStage:
|
||||
{ShaderStage::Geometry, ShaderStage::TessEval, ShaderStage::Vertex}) {
|
||||
Bool stagePresent = false;
|
||||
for (const auto& shader : m_shaders) {
|
||||
if (!shader || shader->GetShaderStage() != captureStage) continue;
|
||||
stagePresent = true;
|
||||
if (shader->GetSpirvXfbVaryings().empty()) continue;
|
||||
task->in.requestedXfbVaryings = shader->GetSpirvXfbVaryings();
|
||||
task->in.requestedXfbBufferMode = shader->GetSpirvXfbBufferMode();
|
||||
break;
|
||||
}
|
||||
if (stagePresent) break;
|
||||
}
|
||||
}
|
||||
task->in.maxFragmentOutputColorNumber = m_maxFragmentOutputColorNumber;
|
||||
|
||||
Vector<SharedPtr<ShaderCompileTask>> deps;
|
||||
|
||||
@@ -1536,14 +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; }
|
||||
// NO ACCESSOR FOR THE PENDING REQUEST, deliberately. A program pipeline's draw composite
|
||||
// needs the capture list of the stage program it flattens, and the obvious source - what
|
||||
// glTransformFeedbackVaryings last recorded - is the wrong one: that request does not take
|
||||
// effect until the stage program's next link, and it bumps no version, so reading it makes
|
||||
// the composite's capture list depend on when the composite cache happened to be
|
||||
// invalidated. GetTransformFeedbackInterfaceNames() below is the source that is correct
|
||||
// AND cache-safe, because linked state only moves at a link and the composite signature
|
||||
// already keys on the link version. See GLContext::GetProgramForDraw.
|
||||
GLenum GetTransformFeedbackBufferMode() const { return Artifacts().xfbBufferMode; }
|
||||
SizeT GetTransformFeedbackVaryingCount() const { return Artifacts().xfbVaryings.size(); }
|
||||
const XfbVarying* GetTransformFeedbackVarying(SizeT index) const {
|
||||
|
||||
@@ -21,16 +21,31 @@ namespace MobileGL::MG_State::GLState {
|
||||
ReleaseCompileNode();
|
||||
m_spirvBinary = Move(binary);
|
||||
m_hasSpirvBinary = true;
|
||||
m_specialized = false;
|
||||
m_specializationFailed = false;
|
||||
m_specializationInfoLog.clear();
|
||||
m_spirvXfbVaryings.clear();
|
||||
m_spirvXfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
m_source = MakeShared<const String>(String{});
|
||||
InvalidateCompiledState();
|
||||
}
|
||||
|
||||
void ShaderObject::SpecializeFromSpirv(String&& glsl) {
|
||||
const String& ShaderObject::GetApplicationShaderSource() const {
|
||||
static const String kNoSource;
|
||||
// Both the unspecialized and the specialized windows answer empty: in the first m_source
|
||||
// already is empty, in the second it holds generated GLSL that the application never wrote.
|
||||
return m_hasSpirvBinary ? kNoSource : *m_source;
|
||||
}
|
||||
|
||||
void ShaderObject::SpecializeFromSpirv(String&& glsl, Vector<String>&& xfbVaryings, GLenum xfbBufferMode) {
|
||||
ReleaseCompileNode();
|
||||
// The latch goes up HERE and nowhere else - this is the one path that actually specialized
|
||||
// the shader.
|
||||
m_specialized = true;
|
||||
m_specializationFailed = false;
|
||||
m_specializationInfoLog.clear();
|
||||
m_spirvXfbVaryings = Move(xfbVaryings);
|
||||
m_spirvXfbBufferMode = xfbBufferMode;
|
||||
// The GLSL the module specializes to enters the ORDINARY pipeline from here: preprocess,
|
||||
// glslang parse, reflection, transpile, both backends. Nothing downstream needs to know
|
||||
// the source was not written by the application - which is the whole reason this hop
|
||||
@@ -61,8 +76,11 @@ namespace MobileGL::MG_State::GLState {
|
||||
m_hasSpirvBinary = false;
|
||||
m_spirvBinary.clear();
|
||||
m_spirvBinary.shrink_to_fit();
|
||||
m_specialized = false;
|
||||
m_specializationFailed = false;
|
||||
m_specializationInfoLog.clear();
|
||||
m_spirvXfbVaryings.clear();
|
||||
m_spirvXfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
ReleaseCompileNode();
|
||||
m_source = MakeShared<const String>(source);
|
||||
InvalidateCompiledState();
|
||||
|
||||
@@ -78,10 +78,33 @@ namespace MobileGL {
|
||||
// checks explicitly.
|
||||
void SetSpirvBinary(Vector<Uint32>&& binary);
|
||||
Bool HasSpirvBinary() const { return m_hasSpirvBinary; }
|
||||
// ARB_gl_spirv: "Once specialized, a shader may not be re-specialized without first
|
||||
// re-associating the original SPIR-V module with it, through ShaderBinary." A second
|
||||
// glSpecializeShader is GL_INVALID_OPERATION, and this latch is what answers that.
|
||||
//
|
||||
// Set ONLY on the success path. A specialization that FAILED did not specialize the
|
||||
// shader, and the conformance suite relies on that distinction: it deliberately fails
|
||||
// specialization (a bad entry point, then an unknown constant id) on one shader object
|
||||
// and then requires the next, well-formed call on that same object to be accepted.
|
||||
Bool HasBeenSpecialized() const { return m_specialized; }
|
||||
const Vector<Uint32>& GetSpirvBinary() const { return m_spirvBinary; }
|
||||
// glSpecializeShader's half: hand the object the GLSL its module specializes to and
|
||||
// let the ordinary pipeline compile it.
|
||||
void SpecializeFromSpirv(String&& glsl);
|
||||
void SpecializeFromSpirv(String&& glsl, Vector<String>&& xfbVaryings, GLenum xfbBufferMode);
|
||||
// The capture the object's SPIR-V module DECLARED, as the equivalent
|
||||
// glTransformFeedbackVaryings request. Empty for a GLSL shader and for a SPIR-V module
|
||||
// that declares no transform feedback. ProgramObject::Link picks this up from the
|
||||
// program's last vertex-processing stage, because ARB_gl_spirv makes decorations the
|
||||
// only declaration form for a SPIR-V program and glTransformFeedbackVaryings has no
|
||||
// effect on one.
|
||||
const Vector<String>& GetSpirvXfbVaryings() const { return m_spirvXfbVaryings; }
|
||||
GLenum GetSpirvXfbBufferMode() const { return m_spirvXfbBufferMode; }
|
||||
// What glGetShaderSource / GL_SHADER_SOURCE_LENGTH must answer. A shader created from
|
||||
// glShaderBinary never had glShaderSource called on it, so GL 4.6 core 7.1 makes its
|
||||
// source the empty string - even after glSpecializeShader, when m_source holds the
|
||||
// SPIRV-Cross GLSL the module was translated into. That text is MobileGL's, not the
|
||||
// application's, and handing it back invites an application to cache and re-submit it.
|
||||
const String& GetApplicationShaderSource() const;
|
||||
// The other half: specialization itself failed (a bad entry point, a constant id the
|
||||
// module does not declare, a module spirv-val rejects). There is nothing to compile,
|
||||
// so the verdict is recorded directly - COMPILE_STATUS false with this log - and both
|
||||
@@ -278,6 +301,13 @@ namespace MobileGL {
|
||||
// the ORIGINAL words rather than the ones the first call folded.
|
||||
Vector<Uint32> m_spirvBinary;
|
||||
Bool m_hasSpirvBinary = false;
|
||||
// "This shader has been specialized"; see HasBeenSpecialized. Cleared by anything that
|
||||
// re-associates a module (SetSpirvBinary) or turns the object back into a GLSL shader
|
||||
// (either SetShaderSource overload) - which is exactly the re-association ARB_gl_spirv
|
||||
// names as the way to make a second specialization legal again.
|
||||
Bool m_specialized = false;
|
||||
Vector<String> m_spirvXfbVaryings;
|
||||
GLenum m_spirvXfbBufferMode = GL_INTERLEAVED_ATTRIBS;
|
||||
// A specialization that failed before any compile could start. Kept beside the
|
||||
// compile artifacts rather than inside them because there is no compile job to hang
|
||||
// it on - see RecordSpecializationFailure. Cleared by anything that gives the object
|
||||
|
||||
@@ -248,11 +248,12 @@ namespace MobileGL {
|
||||
}
|
||||
|
||||
void RenderState::SetPolygonOffset(Float factor, Float units) {
|
||||
if (m_parameters.PolygonOffsetFactor == factor && m_parameters.PolygonOffsetUnits == units) return;
|
||||
|
||||
m_parameters.PolygonOffsetFactor = factor;
|
||||
m_parameters.PolygonOffsetUnits = units;
|
||||
++m_version;
|
||||
// GL 4.6 core 14.6.5 defines PolygonOffset(factor, units) as EQUIVALENT to
|
||||
// PolygonOffsetClamp(factor, units, 0) - the equivalence is total, so the clamp is
|
||||
// written too, not merely left alone. Leaving it meant a glPolygonOffsetClamp(1, 1,
|
||||
// 0.5) followed by a plain glPolygonOffset(3, 4) still reported a clamp of 0.5, and
|
||||
// the early-out below could even skip the version bump while doing it.
|
||||
SetPolygonOffsetClamped(factor, units, 0.0f);
|
||||
}
|
||||
|
||||
Float RenderState::GetPolygonOffsetFactor() const {
|
||||
|
||||
Reference in New Issue
Block a user