mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Fix, Test] (GLImpl, GLState): give a storage block with no binding qualifier GL's default binding of zero
This commit is contained in:
@@ -1548,6 +1548,89 @@ namespace MobileGL {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::set<String> ExtractStorageBlocksWithoutExplicitBinding(const String& source) {
|
||||
std::set<String> names;
|
||||
// Fast path: no storage block, nothing to record. `buffer` as a whole token is
|
||||
// what declares one; samplerBuffer/imageBuffer/textureBuffer tokenize as single
|
||||
// identifiers and so cannot match below, but this substring test is only a
|
||||
// cheap pre-filter and is allowed to be generous.
|
||||
if (source.find("buffer") == String::npos) return names;
|
||||
|
||||
const Vector<CodeToken> tokens = TokenizeCode(source);
|
||||
const SizeT count = tokens.size();
|
||||
Int braceDepth = 0;
|
||||
// Block names seen WITH a binding. Subtracted at the end so a name that is
|
||||
// declared unqualified in one place and qualified in another is never reported:
|
||||
// this scans preprocessor-visible text, so both arms of a #if can be present,
|
||||
// and defaulting a block the active arm binds explicitly would be a regression.
|
||||
// A name is dropped whenever there is any doubt, never kept.
|
||||
std::set<String> qualified;
|
||||
// The binding the qualifier run currently being scanned declared, -1 for none.
|
||||
// Several layout(...) lists may precede one declaration and the later one wins -
|
||||
// the same accumulate-then-consume shape FindShaderStorageBindingViolation uses.
|
||||
long long binding = -1;
|
||||
long long literal = 0;
|
||||
for (SizeT pos = 0; pos < count; ++pos) {
|
||||
const String& text = tokens[pos].text;
|
||||
if (text == "{") {
|
||||
++braceDepth;
|
||||
binding = -1;
|
||||
continue;
|
||||
}
|
||||
if (text == "}") {
|
||||
if (braceDepth > 0) --braceDepth;
|
||||
continue;
|
||||
}
|
||||
// Only depth-0 declarations are block declarations; `buffer` inside a block
|
||||
// body or a function is a member qualifier or an identifier.
|
||||
if (braceDepth != 0) continue;
|
||||
|
||||
if (text == "layout" && pos + 1 < count && tokens[pos + 1].text == "(") {
|
||||
SizeT j = pos + 2;
|
||||
Int parenDepth = 1;
|
||||
while (j < count && parenDepth > 0) {
|
||||
const String& layoutToken = tokens[j].text;
|
||||
if (layoutToken == "(") {
|
||||
++parenDepth;
|
||||
} else if (layoutToken == ")") {
|
||||
--parenDepth;
|
||||
} else if (parenDepth == 1 && layoutToken == "binding" && j + 2 < count &&
|
||||
tokens[j + 1].text == "=" &&
|
||||
ParseGlslIntegerLiteral(tokens[j + 2].text, literal)) {
|
||||
binding = std::min(literal, static_cast<long long>(INT_MAX / 2));
|
||||
j += 2;
|
||||
}
|
||||
++j;
|
||||
}
|
||||
pos = j - 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (text == "buffer") {
|
||||
// Recorded ONLY for the fully recognised shape: a block type name
|
||||
// followed by the body's '{'. The "layout(...) buffer;"
|
||||
// default-qualifier form declares no block and has no name to key on,
|
||||
// and anything else here is grammar this scanner does not judge - both
|
||||
// fall through and keep today's behaviour.
|
||||
if (pos + 2 < count && IsIdentifierToken(tokens[pos + 1]) &&
|
||||
tokens[pos + 2].text == "{") {
|
||||
(binding < 0 ? names : qualified).insert(tokens[pos + 1].text);
|
||||
}
|
||||
binding = -1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Qualifiers may sit between the layout list and the `buffer` keyword;
|
||||
// anything else ends the run, so a binding never leaks onto an unrelated
|
||||
// declaration - and, just as importantly, the ABSENCE of one never does.
|
||||
if (!IsNonLayoutQualifierKeyword(text)) binding = -1;
|
||||
}
|
||||
for (const String& name : qualified) {
|
||||
names.erase(name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
std::optional<String> FindShaderStorageBindingViolation(const String& source, Int maxBindings) {
|
||||
// A backend that advertises nothing has no ceiling to enforce.
|
||||
if (maxBindings <= 0) return std::nullopt;
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
// End of Source File Header
|
||||
|
||||
#pragma once
|
||||
#include <set>
|
||||
|
||||
#include <Includes.h>
|
||||
#include <MG_State/GLState/ProgramState/ShaderObject.h>
|
||||
#include <MG_Util/ShaderTranspiler/CompileEnv.h>
|
||||
@@ -65,6 +67,24 @@ namespace MobileGL {
|
||||
// grammar discipline as ExtractExplicitUniformLocations).
|
||||
UnorderedMap<String, Uint> ExtractExplicitOpaqueBindings(const String& source);
|
||||
|
||||
// The BLOCK TYPE NAMES of the shader storage blocks this source declares WITHOUT a
|
||||
// layout(binding = N) qualifier. GL 4.3 core 7.8 gives such a block a buffer binding
|
||||
// of ZERO, which the application may then move with glShaderStorageBlockBinding.
|
||||
//
|
||||
// Exists because nothing downstream can still tell. Every shader is parsed as a
|
||||
// Vulkan client, so glslang's IO mapper allocates a binding for the block out of one
|
||||
// flat space shared with every sampler, image and uniform block in the program
|
||||
// (iomapper.cpp resolveBinding: `set = openGl ? resource : ent.newSet`, and openGl is
|
||||
// 0 here) - and then WRITES IT BACK into the type's qualifier, so the reflection
|
||||
// reports an auto-assigned number as if the shader had declared it. An unqualified
|
||||
// block therefore lands on 0 only when nothing else claimed 0 first.
|
||||
//
|
||||
// Reported POSITIVELY - only blocks the scanner recognised in full, and recognised as
|
||||
// carrying no binding - so anything outside its narrow grammar is left to the
|
||||
// existing behaviour rather than defaulted on a guess. Same discipline as
|
||||
// ExtractExplicitOpaqueBindings.
|
||||
std::set<String> ExtractStorageBlocksWithoutExplicitBinding(const String& source);
|
||||
|
||||
// A shader storage block whose layout(binding = N) reaches or passes
|
||||
// GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS is a compile-time error in GL 4.3 core 4.4.5,
|
||||
// and an arrayed block instance takes CONSECUTIVE points, so the last element is what
|
||||
|
||||
Reference in New Issue
Block a user