mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-12 06:08:30 +09:00
[Refactor, Test] (ShaderTranspiler, GLState): take what the relaxed parse destroys from glslang instead of scanning the source
This commit is contained in:
@@ -579,8 +579,9 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
if (!ValidateAttachedShaders()) return;
|
||||
|
||||
// The two merges below read the COMPILE snapshots only - no parsed shader - so they
|
||||
// run before the L1 probe, which needs the merged opaque bindings in its key.
|
||||
// Reads the COMPILE snapshots only - no parsed shader - so it runs before the L1
|
||||
// probe: a conflicting explicit uniform location must fail the link whether or not
|
||||
// the memo has an answer for this program's sources.
|
||||
MergeShaderSideChannels();
|
||||
if (!artifacts.infoLog.empty()) return; // a conflicting explicit uniform location
|
||||
|
||||
@@ -617,11 +618,16 @@ namespace MobileGL::MG_State::GLState {
|
||||
}
|
||||
}
|
||||
|
||||
// The last two are OUT parameters that mapIO fills, not requests it honours: the IO
|
||||
// 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.
|
||||
ProgramAttrib attrib{.shaders = Move(shaders),
|
||||
.explicitVertexInLocations = in.explicitAttribLocations,
|
||||
.explicitFragmentOutLocations = in.explicitFragDataLocation,
|
||||
.explicitFragmentOutIndices = in.explicitFragDataIndex,
|
||||
.explicitOpaqueUniformBindings = &artifacts.explicitOpaqueUniformBindings};
|
||||
.explicitOpaqueUniformBindings = &artifacts.explicitOpaqueUniformBindings,
|
||||
.storageBlocksWithoutBinding = &artifacts.storageBlocksWithoutBinding};
|
||||
|
||||
MGLOG_D("ProgramObject %u: Calling ShaderCompiler::LinkProgram", in.externalIndex);
|
||||
auto result = ShaderCompiler::LinkProgram(attrib);
|
||||
@@ -639,6 +645,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// A compute program must have a fixed local group size, and GL states that as a
|
||||
// property of the PROGRAM: "at least one" of its compute shaders declares it (GL 4.6
|
||||
// core 7.13 / GLSL 4.30 4.4.1.4). MobileGL used to answer that question per SHADER,
|
||||
@@ -814,7 +821,6 @@ namespace MobileGL::MG_State::GLState {
|
||||
keyInputs.explicitVertexInLocations = &in.explicitAttribLocations;
|
||||
keyInputs.explicitFragmentOutLocations = &in.explicitFragDataLocation;
|
||||
keyInputs.explicitFragmentOutIndices = &in.explicitFragDataIndex;
|
||||
keyInputs.explicitOpaqueUniformBindings = &artifacts.explicitOpaqueUniformBindings;
|
||||
// In the key ONLY because the payload now carries the reflection: transform feedback
|
||||
// is resolved by reading the linked intermediates and never perturbs the generated
|
||||
// SPIR-V, but it does shape xfbVaryings / xfbStrides / xfbBufferMode /
|
||||
@@ -826,17 +832,18 @@ namespace MobileGL::MG_State::GLState {
|
||||
return BuildSpirvTranslationKey(keyInputs);
|
||||
}
|
||||
|
||||
// The link rejections that need nothing but the compile snapshots. They run before the
|
||||
// The one link rejection that needs nothing but the compile snapshots. It runs before the
|
||||
// L1 memo is consulted, so a hit can never paper over a program that must fail to link.
|
||||
// The two lexical side channels the relaxed parse cannot provide, merged across stages:
|
||||
// explicit default-block uniform locations (which must agree, or the link fails) and
|
||||
// sampler/image layout(binding = N) initial units. Reads the COMPILE snapshots only, so
|
||||
// it is legal - and necessary - before any shader is parsed: the merged bindings are part
|
||||
// of the L1 memo key.
|
||||
//
|
||||
// Only the explicit default-block uniform locations are merged here, and only because they
|
||||
// are the one piece of relaxed-parse wreckage that has to be recovered at COMPILE time:
|
||||
// the snapshot is taken inside the parse, so it is per-shader by construction, and the
|
||||
// same uniform declared in several stages must agree or the program cannot link. The
|
||||
// opaque bindings and the unqualified storage blocks used to be merged alongside them;
|
||||
// both now arrive from mapIO during LinkProgram below, straight into `artifacts`, which is
|
||||
// both later and strictly better informed - the IO mapper sees macro-expanded declarations
|
||||
// and a per-shader lexer never could.
|
||||
void ProgramLinkTask::MergeShaderSideChannels() {
|
||||
// Merge the shaders' lexically extracted explicit uniform locations. The same
|
||||
// uniform declared in several stages must agree on its location (config-A glslang
|
||||
// enforced this at mapIO; the relaxed parse no longer sees the qualifiers).
|
||||
for (const auto& shader : in.shaders) {
|
||||
const ShaderCompileArtifacts& compiled = CompiledArtifacts(shader.compiled);
|
||||
for (const auto& [name, location] : compiled.explicitUniformLocations) {
|
||||
@@ -850,21 +857,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Sampler/image layout(binding = N) initial units, likewise invisible to the
|
||||
// relaxed parse. Stage order matches the old per-stage mapIO capture, so a
|
||||
// name declared in several stages keeps the last stage's binding as before.
|
||||
for (const auto& [name, binding] : compiled.explicitOpaqueBindings) {
|
||||
artifacts.explicitOpaqueUniformBindings[name] = binding;
|
||||
}
|
||||
// Storage blocks declared with NO layout(binding = N), which GL puts on binding 0.
|
||||
// A UNION across stages, unlike the maps above: a block that any stage declared
|
||||
// unqualified is unqualified, because GLSL requires every stage declaring the same
|
||||
// block to declare it identically - so the stages cannot disagree, and a stage whose
|
||||
// grammar the scanner did not recognise simply contributes nothing.
|
||||
artifacts.storageBlocksWithoutBinding.insert(compiled.storageBlocksWithoutBinding.begin(),
|
||||
compiled.storageBlocksWithoutBinding.end());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// An L1 hit: the entire front end, published without constructing a TShader or a
|
||||
@@ -1117,7 +1110,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
artifacts.activeUniformCount, tProgramUniformCount);
|
||||
|
||||
// Effective explicit location per TProgram uniform, from two sources:
|
||||
// - the lexical side-channel for default-block uniforms - the relaxed parse
|
||||
// - the parse-time snapshot for default-block uniforms - the relaxed parse
|
||||
// dropped their layout(location = N) qualifiers when collecting them into
|
||||
// MGL_GLOBAL_UBO, so reflection cannot provide them ("source-explicit");
|
||||
// - glslang's layoutLocation() for opaque uniforms, where the qualifier
|
||||
@@ -1681,9 +1674,13 @@ namespace MobileGL::MG_State::GLState {
|
||||
// Seeded INSIDE `artifacts`, so an L1 translation-cache hit that republishes the artifacts
|
||||
// wholesale carries it too; a seed applied outside them would silently vanish on a hit.
|
||||
//
|
||||
// Only blocks the lexical scanner recognised in full AND recognised as unqualified are
|
||||
// seeded. A block whose grammar it did not understand keeps today's behaviour rather than
|
||||
// being defaulted on a guess - the shape of the input decides, never an assumption about it.
|
||||
// The blocks are named by TMglGlslIoResolver at mapIO's collect callback, which runs over
|
||||
// every declared block of every stage BEFORE the write-back above happens - so "declared no
|
||||
// binding" is a fact read off the AST, not a guess made about the text. The lexical scanner
|
||||
// this replaced could only report positively, dropping any declaration whose grammar it did
|
||||
// not fully recognise, and could not read `binding = SOME_MACRO` at all (it ran on
|
||||
// macro-unexpanded source, and reading "no literal" as "no binding" once aliased eight
|
||||
// Flywheel storage blocks onto 0).
|
||||
//
|
||||
// THE COLLISION IS DELIBERATE, and it is GL's. Several unqualified blocks all default to 0
|
||||
// and alias there until the application rebinds them; a real GL driver does the same, which
|
||||
|
||||
@@ -1205,9 +1205,10 @@ namespace MobileGL::MG_State::GLState {
|
||||
// BuildGlobalUboRouting read as "member of the synthesized global UBO".
|
||||
Vector<Int> glUniformBlockIndexToBlock; // GL uniform-block index -> block index
|
||||
Vector<Int> blockIndexToGlUniformBlock; // block index -> GL uniform-block index (-1)
|
||||
// Per-link merged snapshot of the attached shaders' lexically extracted
|
||||
// layout(location = N) default-block uniform qualifiers (the relaxed parse drops
|
||||
// them from reflection; the DoReflection assigner restores them from here).
|
||||
// Per-link merged snapshot of the layout(location = N) qualifiers the attached
|
||||
// shaders' default-block uniforms declared, as glslang recorded them at the point
|
||||
// its relaxed remap dropped them (the relaxed parse drops them from reflection; the
|
||||
// DoReflection assigner restores them from here).
|
||||
UnorderedMap<String, Int> linkedExplicitUniformLocations;
|
||||
// Per-link snapshot of the default-block uniform INITIALIZERS the attached shaders
|
||||
// declared ("uniform int i = 1;"). Desktop GLSL says that value is what the uniform
|
||||
@@ -1232,6 +1233,10 @@ namespace MobileGL::MG_State::GLState {
|
||||
Vector<Int> uniformIndexInTProgram;
|
||||
// ditto. Will be set at glUniform1i
|
||||
Vector<Int> uniformSamplerOrImageUnitIndex;
|
||||
// Sampler/image layout(binding = N) initial texture/image units, captured by
|
||||
// TMglGlslIoResolver at mapIO's collect callback - the last point at which the
|
||||
// qualifier still says what the shader declared. An OUTPUT of the link, not an
|
||||
// input to it: nothing supplies this map, the resolver fills it.
|
||||
UnorderedMap<String, Uint> explicitOpaqueUniformBindings;
|
||||
|
||||
// Ordered by uniform block index
|
||||
@@ -1256,9 +1261,10 @@ namespace MobileGL::MG_State::GLState {
|
||||
// overwrites it - default and rebind travel one path.
|
||||
UnorderedMap<String, Int> shaderStorageBlockBinding;
|
||||
// Block type names of the storage blocks the program's shaders declared with NO
|
||||
// layout(binding = N), merged across stages by MergeShaderSideChannels. Input to the
|
||||
// seeding above; see ExtractStorageBlocksWithoutExplicitBinding for why it has to be
|
||||
// captured lexically.
|
||||
// layout(binding = N). Input to the seeding above; filled during mapIO by
|
||||
// TMglGlslIoResolver, which is the last observer that can still tell a declared
|
||||
// binding from an invented one - and, unlike the per-shader lexer this replaced,
|
||||
// sees the declaration with its macros expanded.
|
||||
std::set<String> storageBlocksWithoutBinding;
|
||||
|
||||
Uint activeUniformCount = 0;
|
||||
|
||||
@@ -154,10 +154,16 @@ namespace {
|
||||
}
|
||||
|
||||
// The half of a compile that depends on nothing but the source text, the stage and the
|
||||
// environment snapshot: preprocessing, the three lexical rejections, and the two lexical
|
||||
// side-channel extractions. Split out so P0b layer 2 can memoize exactly this and
|
||||
// nothing else - the glslang parse stays per-object because its TShader is consume-once.
|
||||
// Deliberately free of any per-object state so the memo is sound.
|
||||
// environment snapshot: preprocessing and the three lexical rejections. Split out so P0b
|
||||
// layer 2 can memoize exactly this and nothing else - the glslang parse stays per-object
|
||||
// because its TShader is consume-once. Deliberately free of any per-object state so the
|
||||
// memo is sound.
|
||||
//
|
||||
// The side-channel EXTRACTIONS that used to live here are gone: what the relaxed parse
|
||||
// destroys is now recovered from glslang itself, at the two points where it is destroyed
|
||||
// (see ShaderCompileArtifacts::explicitUniformLocations and
|
||||
// TMglGlslIoResolver::reserverResourceSlot). They could not stay here anyway - none of
|
||||
// them is a function of the unexpanded source text, which is all this half can see.
|
||||
//
|
||||
// The compute local-size verdict reads `env` rather than the live backend, and
|
||||
// env.fingerprint is part of the P0b cache key, so a memo can never be returned against
|
||||
@@ -195,28 +201,21 @@ namespace {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (const std::optional<String> counterOffsetError =
|
||||
FindAtomicCounterOffsetViolation(result.preprocessedSource)) {
|
||||
result.outcome = ShaderPreprocessOutcome::AtomicCounterOffsetRejected;
|
||||
result.infoLog = *counterOffsetError;
|
||||
return result;
|
||||
}
|
||||
// NO ATOMIC-COUNTER OFFSET SCAN HERE ANY MORE: glslang raises both rules itself now, at
|
||||
// the site where its relaxed remap folds the counter into a synthesized block
|
||||
// (ParseHelper.cpp atomicCounterOffsetCheck, called from vkRelaxedRemapUniformVariable).
|
||||
// A violation is an ordinary parse failure, so it reaches GL through the same path every
|
||||
// other compile error does - and, unlike a scan of unexpanded text, it sees an offset
|
||||
// spelled as a macro or a const expression.
|
||||
|
||||
// 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
|
||||
// parse. The GL frontend semantics the relaxed parse cannot provide are restored
|
||||
// on top: explicit default-block uniform locations through the lexical
|
||||
// side-channels below, dead-uniform/global-UBO filtering in
|
||||
// on top, all of them out of glslang: explicit default-block uniform locations from
|
||||
// the snapshot the parse takes, opaque bindings and unqualified storage blocks from
|
||||
// the IO mapper's collect callback, dead-uniform/global-UBO filtering in
|
||||
// ProgramObject::DoReflection.
|
||||
result.explicitUniformLocations = ExtractExplicitUniformLocations(result.preprocessedSource);
|
||||
result.explicitOpaqueBindings = ExtractExplicitOpaqueBindings(result.preprocessedSource);
|
||||
// The third side-channel, and the one that restores a GL DEFAULT rather than an
|
||||
// application-declared value: a storage block with no layout(binding = N) has buffer
|
||||
// binding 0 in GL, and by the time the reflection is built glslang's IO mapper has
|
||||
// already invented one and written it into the qualifier.
|
||||
result.storageBlocksWithoutBinding =
|
||||
ExtractStorageBlocksWithoutExplicitBinding(result.preprocessedSource);
|
||||
result.outcome = ShaderPreprocessOutcome::Preprocessed;
|
||||
return result;
|
||||
}
|
||||
@@ -319,10 +318,14 @@ namespace MobileGL::MG_State::GLState {
|
||||
Bool parsedOk = false;
|
||||
String parseLog;
|
||||
SharedPtr<glslang::TShader> parsedShader;
|
||||
UnorderedMap<String, Int> explicitUniformLocations;
|
||||
|
||||
if (verdict) {
|
||||
parsedOk = verdict->parsed;
|
||||
parseLog = verdict->infoLog;
|
||||
// From the verdict, not from a parse - see ShaderParseVerdict for why they had to
|
||||
// move into the payload when their origin moved into glslang.
|
||||
explicitUniformLocations = verdict->explicitUniformLocations;
|
||||
MGLOG_D("ShaderCompileTask: shader %u (stage %d) L1c hit - the glslang parse was skipped; "
|
||||
"compileStatus = %d",
|
||||
externalIndex, static_cast<Int>(stage), static_cast<Int>(parsedOk));
|
||||
@@ -335,6 +338,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
parsedOk = result.has_value();
|
||||
if (parsedOk) {
|
||||
parsedShader = result.value();
|
||||
explicitUniformLocations = CollectExplicitUniformLocations(*parsedShader);
|
||||
} else {
|
||||
parseLog = result.error().log;
|
||||
}
|
||||
@@ -344,6 +348,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
// Empty on success by construction, matching what the publish below does with
|
||||
// the artifacts' own log; the diagnostic the application reads on failure.
|
||||
freshVerdict->infoLog = parseLog;
|
||||
freshVerdict->explicitUniformLocations = explicitUniformLocations;
|
||||
const SizeT verdictBytes = ShaderParseVerdictBytes(*freshVerdict);
|
||||
GetShaderParseVerdictCache().Insert(parseKey, ShaderParseVerdictPtr(Move(freshVerdict)),
|
||||
verdictBytes);
|
||||
@@ -359,9 +364,7 @@ namespace MobileGL::MG_State::GLState {
|
||||
// `fresh` is about to be handed to the cache. Populated on the hit path too - it
|
||||
// is what ClaimParsedShader's deferred parse consumes.
|
||||
artifacts.preprocessedSource = shared.preprocessedSource;
|
||||
artifacts.explicitUniformLocations = shared.explicitUniformLocations;
|
||||
artifacts.explicitOpaqueBindings = shared.explicitOpaqueBindings;
|
||||
artifacts.storageBlocksWithoutBinding = shared.storageBlocksWithoutBinding;
|
||||
artifacts.explicitUniformLocations = Move(explicitUniformLocations);
|
||||
artifacts.infoLog.clear();
|
||||
if (shouldPopulateCache) {
|
||||
cache->Insert(stage, sourceHash, *source, compileEnv.fingerprint, Move(fresh));
|
||||
@@ -386,9 +389,6 @@ namespace MobileGL::MG_State::GLState {
|
||||
if (shouldPopulateCache) {
|
||||
fresh->outcome = ShaderPreprocessOutcome::ParseFailed;
|
||||
fresh->infoLog = artifacts.infoLog;
|
||||
fresh->explicitUniformLocations.clear();
|
||||
fresh->explicitOpaqueBindings.clear();
|
||||
fresh->storageBlocksWithoutBinding.clear();
|
||||
cache->Insert(stage, sourceHash, *source, compileEnv.fingerprint, Move(fresh));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,19 +60,23 @@ namespace MobileGL::MG_State::GLState {
|
||||
// ClaimParsedShader's re-parse so a later link never depends on the preprocessor
|
||||
// being deterministic across backend-state changes.
|
||||
String preprocessedSource;
|
||||
// The explicit layout(location = N) qualifiers this stage's default-block uniforms
|
||||
// declared, as glslang recorded them at the point its Vulkan-relaxed remap dropped
|
||||
// them (CollectExplicitUniformLocations).
|
||||
//
|
||||
// Populated on the L1c HIT path too, out of the cached verdict rather than out of a
|
||||
// parse - which is why the verdict carries them. Everything else the relaxed parse
|
||||
// destroys is recovered at LINK instead, from the IO mapper's collect callback, and so
|
||||
// has no field here at all.
|
||||
UnorderedMap<String, Int> explicitUniformLocations;
|
||||
UnorderedMap<String, Uint> explicitOpaqueBindings;
|
||||
// Block type names of this stage's shader storage blocks that declared no
|
||||
// layout(binding = N), i.e. the ones GL puts on binding 0.
|
||||
std::set<String> storageBlocksWithoutBinding;
|
||||
String infoLog;
|
||||
Bool compileStatus = false;
|
||||
};
|
||||
|
||||
// The unit of asynchronous shader compilation: one glCompileShader's worth of pure CPU
|
||||
// work - preprocess, the two lexical rejections, the two lexical extractions, and (unless
|
||||
// the translation memo's compile half already knows the answer) the glslang parse - with
|
||||
// every input it needs owned by the node itself.
|
||||
// work - preprocess, the lexical rejections, and (unless the translation memo's compile
|
||||
// half already knows the answer) the glslang parse plus the explicit-uniform-location
|
||||
// snapshot it yields - with every input it needs owned by the node itself.
|
||||
//
|
||||
// That ownership is the whole point. The node reads no GL-thread state (the source is a
|
||||
// SharedPtr<const String> snapshot, the device limits come from the CompileEnv snapshot,
|
||||
|
||||
@@ -101,24 +101,11 @@ namespace MobileGL {
|
||||
const SharedPtr<glslang::TShader>& GetCompiledShader() const { return Compiled().shader; }
|
||||
const String& GetInfoLog() const { return Compiled().infoLog; }
|
||||
// Explicit layout(location = N) qualifiers on this shader's default-block
|
||||
// uniforms, captured lexically at Compile() because the relaxed parse drops
|
||||
// them from reflection (see ExtractExplicitUniformLocations).
|
||||
// uniforms, as glslang recorded them at the point its Vulkan-relaxed remap
|
||||
// discarded them (see CollectExplicitUniformLocations).
|
||||
const UnorderedMap<String, Int>& GetExplicitUniformLocations() const {
|
||||
return Compiled().explicitUniformLocations;
|
||||
}
|
||||
// Explicit layout(binding = N) on sampler/image uniforms - their initial
|
||||
// texture/image units - captured lexically for the same reason (see
|
||||
// ExtractExplicitOpaqueBindings).
|
||||
const UnorderedMap<String, Uint>& GetExplicitOpaqueBindings() const {
|
||||
return Compiled().explicitOpaqueBindings;
|
||||
}
|
||||
// Block type names of this shader's storage blocks that declared NO
|
||||
// layout(binding = N), captured lexically because glslang's IO mapper invents one
|
||||
// and overwrites the qualifier before anything can ask (see
|
||||
// ExtractStorageBlocksWithoutExplicitBinding).
|
||||
const std::set<String>& GetStorageBlocksWithoutBinding() const {
|
||||
return Compiled().storageBlocksWithoutBinding;
|
||||
}
|
||||
Bool GetCompileStatus() const { return Compiled().compileStatus; }
|
||||
Bool GetDeleteStatus() const { return m_deleteStatus; }
|
||||
|
||||
|
||||
@@ -29,9 +29,6 @@ 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,
|
||||
@@ -39,18 +36,23 @@ namespace MobileGL::MG_State::GLState {
|
||||
|
||||
// Everything ShaderObject::Compile() derives from the source text alone, i.e.
|
||||
// everything that is identical for two shader objects holding byte-identical source.
|
||||
//
|
||||
// "The source text alone" is now literally true: the preprocessed text, an accept/reject
|
||||
// verdict, and the log that explains a rejection. Anything that needs to know what the
|
||||
// shader MEANS is derived from the parse instead - see the note on the missing fields.
|
||||
struct ShaderPreprocessResult {
|
||||
ShaderPreprocessOutcome outcome = ShaderPreprocessOutcome::Preprocessed;
|
||||
// Valid unless the preprocessor itself never ran; kept even for the rejection
|
||||
// outcomes because that is the text the diagnostics refer to.
|
||||
String preprocessedSource;
|
||||
UnorderedMap<String, Int> explicitUniformLocations;
|
||||
UnorderedMap<String, Uint> explicitOpaqueBindings;
|
||||
// Block type names of the shader storage blocks declared here with NO
|
||||
// layout(binding = N). GL gives such a block binding 0; nothing downstream can still
|
||||
// tell, because glslang's IO mapper auto-assigns one and writes it into the qualifier.
|
||||
// See ExtractStorageBlocksWithoutExplicitBinding.
|
||||
std::set<String> storageBlocksWithoutBinding;
|
||||
// NO EXTRACTED SIDE CHANNELS ANY MORE, and their absence is the point. Explicit
|
||||
// uniform locations, explicit opaque bindings and unqualified storage blocks used to be
|
||||
// lexed out of the text here, which meant reading MACRO-UNEXPANDED source: MobileGL's
|
||||
// preprocessor rewrites the text, it does not run the C preprocessor, so
|
||||
// `binding = SOME_MACRO` reached the scanners verbatim. All three now come from
|
||||
// glslang - the first from a snapshot taken inside the parse, the other two from the
|
||||
// IO mapper's collect callback - and none of them is a function of the source text
|
||||
// ALONE any more, which is the only thing this struct is allowed to hold.
|
||||
// The compile info log to publish; empty when outcome == Preprocessed.
|
||||
String infoLog;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user