mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-10 21:28:32 +09:00
[Merge] (DirectGLES, GLState, ShaderTranspiler): land GL43 wave5 with its two new passes inside the L2 boundary
This commit is contained in:
@@ -1313,6 +1313,24 @@ namespace MobileGL::MG_State::GLState {
|
||||
// span is left; grow the table instead of leaving the uniform without
|
||||
// a location (which would make it unsettable via glUniform*).
|
||||
const SizeT base = artifacts.uniformIndexInTProgram.size();
|
||||
// The growth stops at the pool GL advertises. GL 4.6 core 7.6.1 bounds every
|
||||
// uniform location by GL_MAX_UNIFORM_LOCATIONS, and the conformance suite reads a
|
||||
// returned location >= the advertised maximum as a failure outright
|
||||
// (KHR-GLES31.explicit_uniform_location.uniform-loc-mix-with-implicit-max). Minting
|
||||
// 4095, 4096, ... is strictly worse than refusing: those are locations no
|
||||
// application may legally name and no later query can make legal, so they would
|
||||
// only turn a link-time exhaustion into a silently unwritable uniform. Unreachable
|
||||
// for any program that fits glslang's per-stage uniform-component limits - it takes
|
||||
// a fragmented pool of thousands of explicitly-located slots to get here.
|
||||
if (base + static_cast<SizeT>(locationSpan) > kMaxUniformLocations) {
|
||||
artifacts.infoLog = std::format(
|
||||
"Uniform locations exhausted: '{}' needs {} location(s) and no free span is left below "
|
||||
"GL_MAX_UNIFORM_LOCATIONS ({}).",
|
||||
uniform.name, locationSpan, kMaxUniformLocations);
|
||||
DeferLog(std::format("ProgramObject {}: Link failed - {}", in.externalIndex, artifacts.infoLog));
|
||||
ProgramObject::ResetLinkArtifacts(artifacts);
|
||||
return false;
|
||||
}
|
||||
artifacts.uniformIndexInTProgram.resize(base + locationSpan,
|
||||
glslang::TQualifier::layoutLocationEnd);
|
||||
artifacts.uniformSamplerOrImageUnitIndex.resize(base + locationSpan, -1);
|
||||
|
||||
@@ -281,6 +281,9 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
GLenum GetActiveUniformType(Uint index) const {
|
||||
// The lowered counter is a plain uint inside a synthesized block; what the GL
|
||||
// client declared - and what glGetActiveUniform must report - is an atomic_uint.
|
||||
if (IsActiveUniformAtomicCounter(index)) return GL_UNSIGNED_INT_ATOMIC_COUNTER;
|
||||
return UniformAt(TProgramUniformIndex(index)).glDefineType;
|
||||
}
|
||||
|
||||
@@ -298,10 +301,57 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
|
||||
Int GetActiveUniformBlockIndex(Uint index) const {
|
||||
// An atomic counter is a DEFAULT-BLOCK uniform to GL, whatever block the
|
||||
// transpiler lowered it onto (GL 4.6 core 7.6, table 7.6): -1.
|
||||
if (IsActiveUniformAtomicCounter(index)) return -1;
|
||||
// Members of the synthesized global UBO are default-block uniforms to GL: -1.
|
||||
return GlBlockIndexFromTProgram(UniformAt(TProgramUniformIndex(index)).index);
|
||||
}
|
||||
|
||||
// The transpiler lowers every atomic_uint onto a synthesized gl_AtomicCounterBlock_N
|
||||
// block, but GL keeps seeing an atomic counter as a default-block uniform of type
|
||||
// GL_UNSIGNED_INT_ATOMIC_COUNTER that points at an atomic-counter BUFFER. These two
|
||||
// answer for that GL-level declaration; without them the query surface reports the
|
||||
// lowering instead (GL_UNSIGNED_INT, block index 0) and
|
||||
// KHR-GL43.shader_atomic_counters.basic-program-query fails on both.
|
||||
//
|
||||
// The returned value is an index into the GL_ACTIVE_ATOMIC_COUNTER_BUFFERS list, i.e.
|
||||
// the RANK of the owning counter block among the counter blocks in glslang's block
|
||||
// order - exactly how ProgramInterface numbers the GL_ATOMIC_COUNTER_BUFFER
|
||||
// resources glGetActiveAtomicCounterBufferiv answers from. -1 when this uniform is
|
||||
// not an atomic counter.
|
||||
// Answered from the OWNED reflection snapshot, never from Artifacts().program. This
|
||||
// arrived reading the live TProgram, which is null for every program served from the
|
||||
// translation cache's L1 - and unlike the other query-surface accessors that made the
|
||||
// same mistake, this one DEREFERENCES it, so the second program built from a given set
|
||||
// of sources would have taken the process down rather than answered wrongly. The
|
||||
// snapshot carries the same three facts in the same TPROGRAM index space:
|
||||
// getUniform(i).index -> UniformAt(i).index, getNumUniformBlocks() ->
|
||||
// blockReflection.size(), getUniformBlock(i).name -> BlockAt(i).name.
|
||||
Int GetActiveUniformAtomicCounterBufferIndex(Uint index) const {
|
||||
const Int tIndex = TProgramUniformIndex(index);
|
||||
if (tIndex < 0) return -1;
|
||||
const Int owner = UniformAt(tIndex).index;
|
||||
if (owner < 0) return -1;
|
||||
const Int blockCount = static_cast<Int>(Artifacts().blockReflection.size());
|
||||
if (owner >= blockCount) return -1;
|
||||
const SizeT prefixLength = StringView(MG_Util::ShaderTranspiler::ATOMIC_COUNTER_BLOCK_PREFIX).size();
|
||||
Int counterBufferIndex = 0;
|
||||
for (Int i = 0; i < blockCount; ++i) {
|
||||
const auto& blockName = BlockAt(i).name;
|
||||
if (blockName.compare(0, prefixLength, MG_Util::ShaderTranspiler::ATOMIC_COUNTER_BLOCK_PREFIX) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (i == owner) return counterBufferIndex;
|
||||
++counterBufferIndex;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
Bool IsActiveUniformAtomicCounter(Uint index) const {
|
||||
return GetActiveUniformAtomicCounterBufferIndex(index) >= 0;
|
||||
}
|
||||
|
||||
// GL_UNIFORM_OFFSET: byte offset within the owning named block; -1 for a default-block
|
||||
// uniform. The relaxed parse gives global-UBO members real byte offsets, but GL must keep
|
||||
// seeing them as default-block uniforms, so gate on the GL-visible block index.
|
||||
|
||||
@@ -195,6 +195,13 @@ namespace {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (const std::optional<String> counterOffsetError =
|
||||
FindAtomicCounterOffsetViolation(result.preprocessedSource)) {
|
||||
result.outcome = ShaderPreprocessOutcome::AtomicCounterOffsetRejected;
|
||||
result.infoLog = *counterOffsetError;
|
||||
return result;
|
||||
}
|
||||
|
||||
// The parse this feeds runs in the link-compatible configuration (Vulkan-client
|
||||
// env with relaxed rules): the TShader it produces is what glLinkProgram links and
|
||||
// what the backends' SPIR-V is generated from - there is no second, GL-client
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace MobileGL::MG_State::GLState {
|
||||
// FindShaderStorageBindingViolation rejected it: a storage block declared a binding at or
|
||||
// past GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS.
|
||||
ResourceBindingRejected,
|
||||
// FindAtomicCounterOffsetViolation rejected it: an atomic counter declared a
|
||||
// layout(offset =) that is misaligned or reaches past GL_MAX_ATOMIC_COUNTER_BUFFER_SIZE.
|
||||
AtomicCounterOffsetRejected,
|
||||
// The source-only half was clean but glslang rejected the preprocessed source.
|
||||
// Memoizing this saves the parse itself on every later object with that source.
|
||||
ParseFailed,
|
||||
|
||||
@@ -843,6 +843,21 @@ namespace MobileGL {
|
||||
stored = box;
|
||||
stateChanged = true;
|
||||
}
|
||||
// "The application has written this rectangle" is a DIFFERENT predicate from "the
|
||||
// value moved", and the backends need the first one: glScissor(0, 0, 0, 0) as the
|
||||
// very first scissor call leaves every stored box byte-identical to its
|
||||
// never-written default, and that call is precisely the one whose meaning a
|
||||
// backend must stop guessing at (see ScissorBoxWrittenMask).
|
||||
//
|
||||
// The transition has to count as a state change for the version too. DirectGLES'
|
||||
// SyncRenderState early-outs on an unchanged render-state version BEFORE it
|
||||
// reaches the span memcmp that would otherwise notice the mask, so a version-less
|
||||
// flag flip would sit in the parameter block and never be pushed. It is a
|
||||
// once-per-index transition, so the steady state still costs nothing.
|
||||
if (m_parameters.ScissorBoxWrittenMask != kAllViewportsMask) {
|
||||
m_parameters.ScissorBoxWrittenMask = kAllViewportsMask;
|
||||
stateChanged = true;
|
||||
}
|
||||
if (stateChanged) ++m_version;
|
||||
}
|
||||
|
||||
@@ -855,9 +870,15 @@ namespace MobileGL {
|
||||
MOBILEGL_ASSERT(false, "Scissor box index out of range: %u", index);
|
||||
return;
|
||||
}
|
||||
if (m_parameters.ScissorBoxes[index] == box) return;
|
||||
// See SetScissorBox: a first write is state even when it does not move the value,
|
||||
// so the unchanged-value early-out may only fire once this index is already
|
||||
// marked written.
|
||||
const Uint32 writtenBit = 1u << index;
|
||||
const Bool alreadyWritten = (m_parameters.ScissorBoxWrittenMask & writtenBit) != 0;
|
||||
if (alreadyWritten && m_parameters.ScissorBoxes[index] == box) return;
|
||||
|
||||
m_parameters.ScissorBoxes[index] = box;
|
||||
m_parameters.ScissorBoxWrittenMask |= writtenBit;
|
||||
++m_version;
|
||||
}
|
||||
|
||||
|
||||
@@ -328,6 +328,18 @@ namespace MobileGL {
|
||||
// turns it into a real glEnable/glDisable.
|
||||
Uint32 ScissorTestEnabledMask = 0;
|
||||
Array<IntVec4, MAX_VIEWPORTS> ScissorBoxes{}; // x, y, width, height
|
||||
// One bit per viewport, set the first time the application writes that index's scissor
|
||||
// rectangle - glScissor broadcasts and sets all 16, glScissorIndexed/glScissorArrayv set
|
||||
// the indices they name. It exists because the RECTANGLE cannot answer "has the
|
||||
// application spoken?": ScissorBoxes starts all-zero (its spec initial value is the size
|
||||
// of a window the frontend does not know yet, see the RenderState constructor), and
|
||||
// glScissor(0, 0, 0, 0) is a legal GL state meaning "the scissor test rejects every
|
||||
// fragment". A backend that reads an empty rectangle as the never-written sentinel
|
||||
// therefore INVERTS that request into "accept every fragment"; DirectGLES did exactly
|
||||
// that and KHR-GL43.viewport_array.scissor_zero_dimension caught it. Deliberately beside
|
||||
// ScissorBoxes so it shares their tail span (after LogicOp) and DirectGLES' span memcmp
|
||||
// picks a transition up like any other state.
|
||||
Uint32 ScissorBoxWrittenMask = 0;
|
||||
// glEnable(GL_CLIP_DISTANCE0 + i) for i in [0, 8), one bit each. A bitmask rather than
|
||||
// eight bools because every consumer wants the set, not an individual flag, and because
|
||||
// the SYNC_CAPABILITY/SET_CAPABILITY macros key off a "<Name>Enabled" field name that
|
||||
|
||||
Reference in New Issue
Block a user