mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-09-11 21:58:31 +09:00
[Fix] (MG_State, MG_Util): a default-block uniform starts at its declared initializer instead of zero
This commit is contained in:
@@ -301,6 +301,29 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
Vector<SharedPtr<glslang::TShader>> shaders;
|
Vector<SharedPtr<glslang::TShader>> shaders;
|
||||||
if (!ConsumeShaders(shaders)) return;
|
if (!ConsumeShaders(shaders)) return;
|
||||||
|
|
||||||
|
// Harvest the declared default-block uniform initializers before the TShaders are
|
||||||
|
// handed to the linker. They come from the parse itself (glslang folds the constant
|
||||||
|
// and hands it over instead of dropping it), not from a lexical scan, so an
|
||||||
|
// expression like vec3(10, 20, 30) or int[](1, 2, 3) is already evaluated.
|
||||||
|
//
|
||||||
|
// Stage order decides a tie. GLSL requires a uniform declared in several stages to be
|
||||||
|
// declared identically, initializer included, so a conflict is a malformed program;
|
||||||
|
// taking the first stage's value keeps a link that other implementations accept from
|
||||||
|
// failing here, and both stages agree in every well-formed one.
|
||||||
|
for (const auto& shader : shaders) {
|
||||||
|
const glslang::TIntermediate* intermediate = shader ? shader->getIntermediate() : nullptr;
|
||||||
|
if (intermediate == nullptr) continue;
|
||||||
|
for (const auto& initializer : intermediate->getUniformInitializers()) {
|
||||||
|
const auto known = std::find_if(artifacts.uniformInitialValues.begin(),
|
||||||
|
artifacts.uniformInitialValues.end(),
|
||||||
|
[&initializer](const auto& existing) {
|
||||||
|
return existing.name == initializer.name;
|
||||||
|
});
|
||||||
|
if (known != artifacts.uniformInitialValues.end()) continue;
|
||||||
|
artifacts.uniformInitialValues.push_back(initializer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Merge the shaders' lexically extracted explicit uniform locations. The same
|
// Merge the shaders' lexically extracted explicit uniform locations. The same
|
||||||
// uniform declared in several stages must agree on its location (config-A glslang
|
// 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).
|
// enforced this at mapIO; the relaxed parse no longer sees the qualifiers).
|
||||||
|
|||||||
@@ -90,8 +90,11 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// A node that settled as Cancelled published nothing, so m_spirv stays empty with
|
// A node that settled as Cancelled published nothing, so m_spirv stays empty with
|
||||||
// spirvStatus false: linked, queryable, not drawable. Nothing to repair.
|
// spirvStatus false: linked, queryable, not drawable. Nothing to repair.
|
||||||
|
|
||||||
// Before the version bump, and before any caller can read the shadow: the writes the
|
// Order matters, and it is the GL order. The shadow arrives zero-filled; the shaders'
|
||||||
// application made while the layout did not exist yet.
|
// declared uniform initializers are what it should actually start from, and only then
|
||||||
|
// do the application's own writes - the ones it made while the layout did not exist
|
||||||
|
// yet - land on top. Seeding after the replay would clobber them.
|
||||||
|
ApplyUniformInitialValues();
|
||||||
ReplayBufferedUniformWrites();
|
ReplayBufferedUniformWrites();
|
||||||
|
|
||||||
// The THIRD version bump of this link (enqueue, phase-A publish, phase-B publish), and
|
// The THIRD version bump of this link (enqueue, phase-A publish, phase-B publish), and
|
||||||
@@ -126,6 +129,89 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// "uniform vec3 v = vec3(10, 20, 30);" - legal desktop GLSL since 1.20, and the value is
|
||||||
|
// what the uniform reads until glUniform* replaces it (and again after every relink).
|
||||||
|
// MobileGL parses with Vulkan-relaxed rules, which sweep default-block uniforms into
|
||||||
|
// MGL_GLOBAL_UBO; a block member cannot carry an initializer in SPIR-V, so glslang hands
|
||||||
|
// the folded constants over as a side-channel (TIntermediate::getUniformInitializers) and
|
||||||
|
// this is where they are honoured. Without it every such uniform silently read zero -
|
||||||
|
// which is what half of KHR-GL43.shader_storage_buffer_object was actually failing on.
|
||||||
|
//
|
||||||
|
// Writes go straight into the shadow rather than through glUniform*: this runs INSIDE the
|
||||||
|
// phase-B publish, so re-entering the join gate is not available, and the location space
|
||||||
|
// reflection assigns (one location per array element) is all that is needed.
|
||||||
|
void ProgramObject::ApplyUniformInitialValues() const {
|
||||||
|
const auto& initializers = m_artifacts.uniformInitialValues;
|
||||||
|
if (initializers.empty()) return;
|
||||||
|
if (m_spirv.globalUboScratch.empty() || m_spirv.uniformOffsets.empty()) {
|
||||||
|
// Phase B published no shadow (cancelled, or superseded by a relink). The program
|
||||||
|
// is not drawable; there is nowhere for these to land.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Uint8* const scratch = m_spirv.globalUboScratch.data();
|
||||||
|
const SizeT uboSize = m_spirv.globalUboScratch.size();
|
||||||
|
|
||||||
|
for (const auto& init : initializers) {
|
||||||
|
// Scalars per array ELEMENT. A matrix element carries cols * rows of them, laid
|
||||||
|
// out column by column - which is also the order glslang folded them in.
|
||||||
|
const Int columns = init.matrixCols;
|
||||||
|
const Int rows = init.matrixRows;
|
||||||
|
const Int componentsPerElement = columns > 0 ? columns * rows : init.vectorSize;
|
||||||
|
const Int elements = init.arraySize;
|
||||||
|
if (componentsPerElement <= 0 || elements <= 0) continue;
|
||||||
|
|
||||||
|
const Bool isFloat = init.basicType == glslang::EbtFloat || init.basicType == glslang::EbtFloat16;
|
||||||
|
const Bool isInt = init.basicType == glslang::EbtInt || init.basicType == glslang::EbtUint ||
|
||||||
|
init.basicType == glslang::EbtBool;
|
||||||
|
// Anything else (fp64, 64-bit integers) has no 32-bit shadow encoding here, and a
|
||||||
|
// half-written uniform is worse than an untouched one.
|
||||||
|
if (!isFloat && !isInt) continue;
|
||||||
|
const SizeT provided = isFloat ? init.floatValues.size() : init.intValues.size();
|
||||||
|
if (provided < static_cast<SizeT>(componentsPerElement) * static_cast<SizeT>(elements)) continue;
|
||||||
|
|
||||||
|
const Int baseLocation = GetUniformLocation(init.name);
|
||||||
|
if (baseLocation < 0) continue; // optimized away, or not a default-block uniform
|
||||||
|
|
||||||
|
for (Int element = 0; element < elements; ++element) {
|
||||||
|
const Int location = baseLocation + element;
|
||||||
|
if (element > 0 && !UniformLocationsAliasSameUniform(baseLocation, location)) break;
|
||||||
|
if (!IsValidUniformLocation(location)) break;
|
||||||
|
const Uint offset = GetUniformOffset(static_cast<Uint>(location));
|
||||||
|
if (offset == kInvalidUniformOffset) continue;
|
||||||
|
|
||||||
|
// std140 pads every column of a float matrix out to a vec4, so the columns of
|
||||||
|
// a mat3 are 16 bytes apart even though each carries 12. The slot's own span
|
||||||
|
// states the stride the rest of the pipeline agreed on rather than guessing it.
|
||||||
|
const SizeT slotSpan = GetUniformStorageSpanInBytes(static_cast<Uint>(location));
|
||||||
|
const SizeT columnStride =
|
||||||
|
columns > 0 ? slotSpan / static_cast<SizeT>(columns) : slotSpan;
|
||||||
|
const Int componentsPerColumn = columns > 0 ? rows : componentsPerElement;
|
||||||
|
const Int columnCount = columns > 0 ? columns : 1;
|
||||||
|
|
||||||
|
for (Int column = 0; column < columnCount; ++column) {
|
||||||
|
const SizeT byteOffset = static_cast<SizeT>(offset) + static_cast<SizeT>(column) * columnStride;
|
||||||
|
const SizeT writeSize = static_cast<SizeT>(componentsPerColumn) * sizeof(Uint32);
|
||||||
|
if (byteOffset + writeSize > uboSize) break;
|
||||||
|
const SizeT firstComponent = static_cast<SizeT>(element) * componentsPerElement +
|
||||||
|
static_cast<SizeT>(column) * componentsPerColumn;
|
||||||
|
for (Int component = 0; component < componentsPerColumn; ++component) {
|
||||||
|
const SizeT source = firstComponent + static_cast<SizeT>(component);
|
||||||
|
Uint8* const destination = scratch + byteOffset + component * sizeof(Uint32);
|
||||||
|
if (isFloat) {
|
||||||
|
const Float value = static_cast<Float>(init.floatValues[source]);
|
||||||
|
std::memcpy(destination, &value, sizeof(value));
|
||||||
|
} else {
|
||||||
|
const Int32 value = static_cast<Int32>(init.intValues[source]);
|
||||||
|
std::memcpy(destination, &value, sizeof(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MarkUBOContentDirty();
|
||||||
|
}
|
||||||
|
|
||||||
void ProgramObject::ReplayBufferedUniformWrites() const {
|
void ProgramObject::ReplayBufferedUniformWrites() const {
|
||||||
if (m_pendingUniformWrites.empty()) {
|
if (m_pendingUniformWrites.empty()) {
|
||||||
m_pendingUniformBytes.clear();
|
m_pendingUniformBytes.clear();
|
||||||
@@ -234,6 +320,7 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
artifacts.glBlockIndexToTProgram.clear();
|
artifacts.glBlockIndexToTProgram.clear();
|
||||||
artifacts.tProgramBlockIndexToGl.clear();
|
artifacts.tProgramBlockIndexToGl.clear();
|
||||||
artifacts.linkedExplicitUniformLocations.clear();
|
artifacts.linkedExplicitUniformLocations.clear();
|
||||||
|
artifacts.uniformInitialValues.clear();
|
||||||
artifacts.uniformIndexInTProgram.clear();
|
artifacts.uniformIndexInTProgram.clear();
|
||||||
artifacts.uniformSamplerOrImageUnitIndex.clear();
|
artifacts.uniformSamplerOrImageUnitIndex.clear();
|
||||||
artifacts.explicitOpaqueUniformBindings.clear();
|
artifacts.explicitOpaqueUniformBindings.clear();
|
||||||
|
|||||||
@@ -756,6 +756,13 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// layout(location = N) default-block uniform qualifiers (the relaxed parse drops
|
// layout(location = N) default-block uniform qualifiers (the relaxed parse drops
|
||||||
// them from reflection; the DoReflection assigner restores them from here).
|
// them from reflection; the DoReflection assigner restores them from here).
|
||||||
UnorderedMap<String, Int> linkedExplicitUniformLocations;
|
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
|
||||||
|
// reads until the application overwrites it, and relinking restores it - but the
|
||||||
|
// relaxed parse turns those uniforms into members of MGL_GLOBAL_UBO, where SPIR-V
|
||||||
|
// cannot carry an initializer, so the value only survives as this side-channel.
|
||||||
|
// Applied into the uniform shadow at the phase-B publish (ApplyUniformInitialValues).
|
||||||
|
Vector<glslang::TIntermediate::TUniformInitializer> uniformInitialValues;
|
||||||
UnorderedMap<String, Uint> uniformLocations;
|
UnorderedMap<String, Uint> uniformLocations;
|
||||||
// Ordered by location,
|
// Ordered by location,
|
||||||
// aka. uniformIndexInTProgram[loc] == "uniform index of TProgram at location `loc`"
|
// aka. uniformIndexInTProgram[loc] == "uniform index of TProgram at location `loc`"
|
||||||
@@ -1010,6 +1017,10 @@ namespace MobileGL::MG_State::GLState {
|
|||||||
// detour exactly - and a record that really does change bytes moves the version, which
|
// detour exactly - and a record that really does change bytes moves the version, which
|
||||||
// is what makes a backend re-upload the UBO it cached during the window.
|
// is what makes a backend re-upload the UBO it cached during the window.
|
||||||
void ReplayBufferedUniformWrites() const;
|
void ReplayBufferedUniformWrites() const;
|
||||||
|
// Seeds the freshly published uniform shadow with the declared initializers. Runs at
|
||||||
|
// the phase-B publish, BEFORE ReplayBufferedUniformWrites, so an application write
|
||||||
|
// made during the A->B window still wins - which is the GL ordering.
|
||||||
|
void ApplyUniformInitialValues() const;
|
||||||
// Past this, BufferUniformWrite declines and the write joins instead. Sized so an
|
// Past this, BufferUniformWrite declines and the write joins instead. Sized so an
|
||||||
// ordinary pack load never reaches it (a pending window is one program's worth of
|
// ordinary pack load never reaches it (a pending window is one program's worth of
|
||||||
// uniforms) while a pathological writer cannot grow the heap without bound.
|
// uniforms) while a pathological writer cannot grow the heap without bound.
|
||||||
|
|||||||
@@ -367,6 +367,7 @@ public:
|
|||||||
|
|
||||||
TIntermTyped* vkRelaxedRemapFunctionCall(const TSourceLoc&, TFunction*, TIntermNode*);
|
TIntermTyped* vkRelaxedRemapFunctionCall(const TSourceLoc&, TFunction*, TIntermNode*);
|
||||||
// returns true if the variable was remapped to something else
|
// returns true if the variable was remapped to something else
|
||||||
|
void recordUniformInitializer(const TString&, const TType&, const TConstUnionArray&);
|
||||||
bool vkRelaxedRemapUniformVariable(const TSourceLoc&, TString&, const TPublicType&, TArraySizes*, TIntermTyped*, TType&);
|
bool vkRelaxedRemapUniformVariable(const TSourceLoc&, TString&, const TPublicType&, TArraySizes*, TIntermTyped*, TType&);
|
||||||
void vkRelaxedRemapUniformMembers(const TSourceLoc&, const TPublicType&, const TType&, const TString&);
|
void vkRelaxedRemapUniformMembers(const TSourceLoc&, const TPublicType&, const TType&, const TString&);
|
||||||
void vkRelaxedRemapFunctionParameter(TFunction*, TParameter&, std::vector<int>* newParams = nullptr);
|
void vkRelaxedRemapFunctionParameter(TFunction*, TParameter&, std::vector<int>* newParams = nullptr);
|
||||||
|
|||||||
@@ -611,6 +611,32 @@ public:
|
|||||||
void setGlobalUniformBinding(unsigned int binding) { globalUniformBlockBinding = binding; }
|
void setGlobalUniformBinding(unsigned int binding) { globalUniformBlockBinding = binding; }
|
||||||
unsigned int getGlobalUniformBinding() const { return globalUniformBlockBinding; }
|
unsigned int getGlobalUniformBinding() const { return globalUniformBlockBinding; }
|
||||||
|
|
||||||
|
// A default-block uniform's initializer, folded to constants at parse time.
|
||||||
|
//
|
||||||
|
// Desktop GLSL 1.20+ lets a default-block uniform carry an initializer, and that value is
|
||||||
|
// what the uniform reads until the application overwrites it with glUniform*. Vulkan-relaxed
|
||||||
|
// parsing sweeps such uniforms into a uniform BLOCK, and a block member cannot carry an
|
||||||
|
// initializer in SPIR-V - so the value has nowhere to live in the generated module and used
|
||||||
|
// to be dropped outright, leaving the uniform silently zero. The CLIENT is the only party
|
||||||
|
// that can still honor it, by writing the value into the block's backing storage once the
|
||||||
|
// program links, so the folded constants are handed out here instead of discarded.
|
||||||
|
//
|
||||||
|
// Scalars appear in the same flattened order glslang folds them in: array element by array
|
||||||
|
// element, and within a matrix, column by column. Exactly one of the two value vectors is
|
||||||
|
// populated, chosen by basicType.
|
||||||
|
struct TUniformInitializer {
|
||||||
|
std::string name;
|
||||||
|
TBasicType basicType = EbtVoid;
|
||||||
|
int vectorSize = 1; // components per vector; 1 for a scalar
|
||||||
|
int matrixCols = 0; // 0 when the type is not a matrix
|
||||||
|
int matrixRows = 0;
|
||||||
|
int arraySize = 1; // outer array element count; 1 when not an array
|
||||||
|
std::vector<long long> intValues;
|
||||||
|
std::vector<double> floatValues;
|
||||||
|
};
|
||||||
|
void addUniformInitializer(TUniformInitializer&& init) { uniformInitializers.push_back(std::move(init)); }
|
||||||
|
const std::vector<TUniformInitializer>& getUniformInitializers() const { return uniformInitializers; }
|
||||||
|
|
||||||
void setAtomicCounterBlockName(const char* name) { atomicCounterBlockName = std::string(name); }
|
void setAtomicCounterBlockName(const char* name) { atomicCounterBlockName = std::string(name); }
|
||||||
const char* getAtomicCounterBlockName() const { return atomicCounterBlockName.c_str(); }
|
const char* getAtomicCounterBlockName() const { return atomicCounterBlockName.c_str(); }
|
||||||
void setAtomicCounterBlockSet(unsigned int set) { atomicCounterBlockSet = set; }
|
void setAtomicCounterBlockSet(unsigned int set) { atomicCounterBlockSet = set; }
|
||||||
@@ -1223,6 +1249,7 @@ protected:
|
|||||||
|
|
||||||
std::string globalUniformBlockName;
|
std::string globalUniformBlockName;
|
||||||
std::string atomicCounterBlockName;
|
std::string atomicCounterBlockName;
|
||||||
|
std::vector<TUniformInitializer> uniformInitializers;
|
||||||
unsigned int globalUniformBlockSet;
|
unsigned int globalUniformBlockSet;
|
||||||
unsigned int globalUniformBlockBinding;
|
unsigned int globalUniformBlockBinding;
|
||||||
unsigned int atomicCounterBlockSet;
|
unsigned int atomicCounterBlockSet;
|
||||||
|
|||||||
Reference in New Issue
Block a user