diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/MagmaProgramSource.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/MagmaProgramSource.h new file mode 100644 index 00000000..f4b56873 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/MagmaProgramSource.h @@ -0,0 +1,228 @@ +// MobileGL - server-owned program input for Magma (P5f fm). +#pragma once + +#include + +#if MOBILEGL_BUILD_DISAGGREGATED +#include +#include +#include "../DirectVulkanResourceState.h" +#include +#endif + +namespace MobileGL::MG_Backend::DirectVulkan { +#if MOBILEGL_BUILD_DISAGGREGATED + // A borrowed view, valid only while its frontend object or server record lives. + // It never constructs a ProgramObject on the server. Post-link mutable values + // come from the record tails, not from the archive's link-time defaults. + class MagmaProgramSource { + public: + using Program = MG_State::GLState::ProgramObject; + MagmaProgramSource(const Program& program) : m_frontend(&program) {} + MagmaProgramSource(MG_Pipe::MGPipeHandle handle, const MG_Pipe::MGPipeShaderCsoRecord& record) + : m_handle(handle), m_record(&record) { + MOBILEGL_ASSERT(record.Archive != nullptr, "Magma program record has no archive"); + } + + Bool IsWire() const { return m_record != nullptr; } + MG_Pipe::MGPipeHandle Handle() const { return m_handle; } + const Program* Frontend() const { return m_frontend; } + Uint GetExternalIndex() const { return IsWire() ? m_handle.Slot : m_frontend->GetExternalIndex(); } + Uint64 GetLifetimeId() const { + return IsWire() ? (Uint64(m_handle.Gen) << 32) | m_handle.Slot : m_frontend->GetLifetimeId(); + } + Bool GetLinkStatus() const { return IsWire() ? m_record->Desc.LinkStatus != 0 : m_frontend->GetLinkStatus(); } + Bool GetSpirvStatus() const { return IsWire() ? m_record->Desc.SpirvStatus != 0 : m_frontend->GetSpirvStatus(); } + Bool PointSizeDemoted() const { return IsWire() ? m_record->Desc.PointSizeDemoted != 0 : m_frontend->PointSizeDemoted(); } + Bool GetSpirvValidationEnabled() const { + return IsWire() ? m_record->Desc.EnableSpirvValidation != 0 : m_frontend->GetSpirvValidationEnabled(); + } + const Vector>& GetGeneratedSpirv() const { + return IsWire() ? m_record->Archive->Spirv.generatedSpirv : m_frontend->GetGeneratedSpirv(); + } + Vector GetLinkedShaderStages() const { + if (!IsWire()) return m_frontend->GetLinkedShaderStages(); + Vector result; + for (const auto stage : m_record->Archive->LinkedStages) result.push_back(static_cast(stage)); + return result; + } + Bool GetBackendHashMemo(Uint flags, Uint64& hash) const { + // A view has no lifetime in which a memo can safely persist. The server + // factory's content cache still owns compiled programs; hash from current + // archive + binding tails rather than borrowing the client's mutable memo. + return !IsWire() && m_frontend->GetBackendHashMemo(flags, hash); + } + void SetBackendHashMemo(Uint flags, Uint64 hash) const { + if (!IsWire()) m_frontend->SetBackendHashMemo(flags, hash); + } + Uint64 GetBackendStateVersion() const { return IsWire() ? m_record->Serial : m_frontend->GetBackendStateVersion(); } + Uint64 GetBlockBindingVersion() const { return IsWire() ? m_record->BindingsSerial : m_frontend->GetBlockBindingVersion(); } + Uint64 GetImageUnitVersion() const { return IsWire() ? m_record->BindingsSerial : m_frontend->GetImageUnitVersion(); } + Uint64 GetLinkVersion() const { return IsWire() ? m_record->Serial : m_frontend->GetLinkVersion(); } + + const void* GetUBOData() const { return IsWire() ? m_record->GlobalConstants.data() : m_frontend->GetUBOData(); } + Uint GetUBOSize() const { return IsWire() ? static_cast(m_record->GlobalConstants.size()) : m_frontend->GetUBOSize(); } + Uint32 GetUBOContentVersion() const { return IsWire() ? m_record->GlobalConstantsVersion : m_frontend->GetUBOContentVersion(); } + Int GetActiveUniformBlocksCount() const { + return IsWire() ? static_cast(Link().glBlockIndexToTProgram.size()) : m_frontend->GetActiveUniformBlocksCount(); + } + Uint GetUniformBlockIndex(const char* name) const { + if (!IsWire()) return m_frontend->GetUniformBlockIndex(name); + auto found = Link().uniformBlockIndexByName.find(name); + if (found == Link().uniformBlockIndexByName.end()) found = Link().uniformBlockIndexByName.find(String(name) + "[0]"); + return found == Link().uniformBlockIndexByName.end() ? GL_INVALID_INDEX : found->second; + } + Uint GetUniformBlockBinding(Uint index) const { + if (!IsWire()) return m_frontend->GetUniformBlockBinding(index); + return index < m_record->BlockBindings.size() ? static_cast(m_record->BlockBindings[index]) : 0; + } + const String& GetUniformBlockName(Uint index) const { + if (!IsWire()) return m_frontend->GetUniformBlockName(index); + const auto* block = Block(index); + static const String empty; + return block ? block->name : empty; + } + Uint GetUBOSizeAt(Uint index) const { + if (!IsWire()) return m_frontend->GetUBOSizeAt(index); + const auto* block = Block(index); + return block ? (static_cast(block->size) + 15u) & ~15u : 0; + } + Bool IsValidUniformLocation(Int location) const { + return IsWire() ? Program::IsValidUniformLocation(Link(), location) : m_frontend->IsValidUniformLocation(location); + } + Bool UniformLocationsAliasSameUniform(Int a, Int b) const { + if (!IsWire()) return m_frontend->UniformLocationsAliasSameUniform(a, b); + return IsValidUniformLocation(a) && IsValidUniformLocation(b) && + Link().uniformIndexInTProgram[a] == Link().uniformIndexInTProgram[b]; + } + GLenum GetUniformType(Uint location) const { + if (!IsWire()) return m_frontend->GetUniformType(location); + return IsValidUniformLocation(static_cast(location)) + ? Program::UniformAtIn(Link(), Link().uniformIndexInTProgram[location]).glDefineType : 0; + } + Int GetUniformSamplerOrImageUnitIndex(Uint location) const { + if (!IsWire()) return m_frontend->GetUniformSamplerOrImageUnitIndex(location); + const auto& units = m_record->SamplerUnits; + const auto found = std::lower_bound(units.begin(), units.end(), location, + [](const MG_Pipe::MGPProgramSamplerUnit& unit, Uint value) { return unit.Location < value; }); + return found != units.end() && found->Location == location ? found->Unit : -1; + } + Int GetUniformLocation(const String& name) const { + if (!IsWire()) return m_frontend->GetUniformLocation(name); + const auto& locations = Link().uniformLocations; + auto found = locations.find(name); + if (found != locations.end()) return static_cast(found->second); + if (name.empty()) return -1; + found = locations.find(name + "[0]"); + if (found != locations.end()) return static_cast(found->second); + if (name.back() != ']' || name.size() < 4) return -1; + const SizeT bracket = name.rfind('['); + if (bracket == String::npos || bracket + 1 >= name.size() - 1) return -1; + Uint element = 0; + for (SizeT i = bracket + 1; i < name.size() - 1; ++i) { + if (name[i] < '0' || name[i] > '9') return -1; + element = element * 10 + static_cast(name[i] - '0'); + if (element > 0x0fffffffu) return -1; + } + found = locations.find(name.substr(0, bracket) + "[0]"); + if (found == locations.end()) found = locations.find(name.substr(0, bracket)); + if (found == locations.end()) return -1; + const Int base = static_cast(found->second); + if (!IsValidUniformLocation(base)) return -1; + const Int tIndex = Link().uniformIndexInTProgram[base]; + if (!Program::UniformAtIn(Link(), tIndex).type.isArray || + static_cast(element) >= Program::GetUniformArraySizeByTIndex(Link(), tIndex)) return -1; + const Int location = base + static_cast(element); + return UniformLocationsAliasSameUniform(base, location) ? location : -1; + } + + SizeT GetTransformFeedbackVaryingCount() const { return IsWire() ? Link().xfbVaryings.size() : m_frontend->GetTransformFeedbackVaryingCount(); } + const Vector& GetTransformFeedbackVaryings() const { + return IsWire() ? Link().xfbVaryings : m_frontend->GetTransformFeedbackVaryings(); + } + SizeT GetTransformFeedbackBufferCount() const { return IsWire() ? Link().xfbStrides.size() : m_frontend->GetTransformFeedbackBufferCount(); } + Uint32 GetTransformFeedbackStride(Uint index) const { + if (!IsWire()) return m_frontend->GetTransformFeedbackStride(index); + return index < Link().xfbStrides.size() ? Link().xfbStrides[index] : 0; + } + Uint GetShaderStorageBlockIndex(const String& name) const { + if (!IsWire()) return DirectVulkan::GetShaderStorageBlockIndex(*m_frontend, name); + EnsureStorageBlocks(); + String key = name; + auto find = [&] { + return std::find_if(m_storageBlocks.begin(), m_storageBlocks.end(), + [&](const StorageBlock& block) { return block.Name == key; }); + }; + auto found = find(); + if (found == m_storageBlocks.end() && !name.empty() && name.back() == ']') { + const auto bracket = name.rfind('['); + if (bracket != String::npos) { key = name.substr(0, bracket); found = find(); } + } + return found == m_storageBlocks.end() ? GL_INVALID_INDEX : static_cast(found - m_storageBlocks.begin()); + } + Uint GetShaderStorageBlockBinding(Uint index) const { + if (!IsWire()) return DirectVulkan::GetShaderStorageBlockBinding(*m_frontend, index); + EnsureStorageBlocks(); + if (index >= m_storageBlocks.size()) return 0; + const auto& block = m_storageBlocks[index]; + for (const auto& override : m_record->StorageOverrides) { + if (override.Name == block.Name) return override.Binding; + } + return block.Binding; + } + + private: + const MG_State::GLState::LinkArtifacts& Link() const { return m_record->Archive->Link; } + const MG_State::GLState::BlockReflection* Block(Uint index) const { + if (index >= Link().glBlockIndexToTProgram.size()) return nullptr; + const Int tIndex = Link().glBlockIndexToTProgram[index]; + return tIndex >= 0 && static_cast(tIndex) < Link().blockReflection.size() + ? &Link().blockReflection[tIndex] : nullptr; + } + struct StorageBlock { String Name; Uint Binding; }; + void EnsureStorageBlocks() const { + if (m_storageBlocksReady) return; + m_storageBlocksReady = true; + // Match the legacy resource-query index order: stage order, then + // normalized descriptor name/binding, deduplicated across stages. + for (const auto& spirv : GetGeneratedSpirv()) { + if (spirv.empty()) continue; + SpvReflectShaderModule module{}; + if (spvReflectCreateShaderModule(spirv.size() * sizeof(Uint), spirv.data(), &module) != SPV_REFLECT_RESULT_SUCCESS) continue; + Uint32 count = 0; + if (spvReflectEnumerateDescriptorBindings(&module, &count, nullptr) == SPV_REFLECT_RESULT_SUCCESS) { + Vector bindings(count); + if (spvReflectEnumerateDescriptorBindings(&module, &count, bindings.data()) == SPV_REFLECT_RESULT_SUCCESS) { + auto name = [](const SpvReflectDescriptorBinding* binding) { + const char* raw = binding->name; + if (binding->type_description && binding->type_description->type_name) raw = binding->type_description->type_name; + String result = raw ? raw : ""; + const auto suffix = result.find("[0]"); + return suffix == String::npos ? result : result.substr(0, suffix); + }; + std::sort(bindings.begin(), bindings.end(), [&](const auto* a, const auto* b) { + const String an = a ? name(a) : String(), bn = b ? name(b) : String(); + return an != bn ? an < bn : (a && b && a->binding < b->binding); + }); + for (const auto* binding : bindings) { + if (!binding || binding->descriptor_type != SPV_REFLECT_DESCRIPTOR_TYPE_STORAGE_BUFFER) continue; + const String key = name(binding); + if (key.empty() || std::any_of(m_storageBlocks.begin(), m_storageBlocks.end(), + [&](const StorageBlock& block) { return block.Name == key; })) continue; + m_storageBlocks.push_back({key, binding->binding}); + } + } + } + spvReflectDestroyShaderModule(&module); + } + } + const Program* m_frontend = nullptr; + MG_Pipe::MGPipeHandle m_handle = MG_Pipe::kMGPipeNullHandle; + const MG_Pipe::MGPipeShaderCsoRecord* m_record = nullptr; + mutable Bool m_storageBlocksReady = false; + mutable Vector m_storageBlocks; + }; +#else + using MagmaProgramSource = MG_State::GLState::ProgramObject; +#endif +} diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp index a460d456..cde67a8f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.cpp @@ -1891,7 +1891,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } Bool TransformSpirvForXfbCapture(const Vector& input, Vector& output, - const MG_State::GLState::ProgramObject& program) { + const MagmaProgramSource& program) { if (input.empty()) { output.clear(); return true; @@ -2466,7 +2466,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } - ProgramFactory::HashType ProgramFactory::ComputeHash(const MG_State::GLState::ProgramObject& program, + ProgramFactory::HashType ProgramFactory::ComputeHash(const MagmaProgramSource& program, CompileOptionFlags flags) const { XXHASH_VERIFY(XXH64_reset(m_hashState, m_config.CacheVersion)); // We expect shader stages in program object are sorted @@ -2832,7 +2832,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // key and every element of an array of arrays seeds texture unit 0. Resolving those elements // would then paint silently-wrong pixels with no diagnostic at all - strictly worse than // declining. The decline goes away together with the seeding fix, not before it. - static Uint32 DescriptorCountForOpaqueUniformArray(const MG_State::GLState::ProgramObject& program, + static Uint32 DescriptorCountForOpaqueUniformArray(const MagmaProgramSource& program, const String& uniformName, Uint32 binding, Int baseLocation, Uint32 reflectedCount, Uint32 maxBindings, const char* kindLabel, Bool& outDeclined) { @@ -2868,7 +2868,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { return count; } - void ProgramFactory::ReflectLayout(const MG_State::GLState::ProgramObject& program, + void ProgramFactory::ReflectLayout(const MagmaProgramSource& program, const Vector>& spirv, VkProgramObject& entry) const { // Initialize layout vectors entry.bindingKinds.assign(m_maxBindings, DescriptorBindingKind::None); @@ -3068,7 +3068,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { entry.bindingKinds[binding] = descriptorKind; if (descriptorKind == DescriptorBindingKind::StorageBuffer) { - const GLuint blockIndex = GetShaderStorageBlockIndex(program, uniformName); + const GLuint blockIndex = +#if MOBILEGL_BUILD_DISAGGREGATED + program.GetShaderStorageBlockIndex(uniformName); +#else + GetShaderStorageBlockIndex(program, uniformName); +#endif if (blockIndex == GL_INVALID_INDEX) { MGLOG_D("ProgramFactory::ReflectLayout: skipping inactive SSBO '%s' at binding %u", uniformName.c_str(), binding); @@ -3426,7 +3431,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const ProgramFactory::VkProgramObject& ProgramFactory::GetOrCreateProgram( - const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) { + const MagmaProgramSource& program, CompileOptionFlags flags) { // Hashing the full SPIR-V of every stage is far too expensive to repeat per draw; // reuse the program's memoized hash while its backend state version is unchanged. // The memo keys on the flags word, which ComputeHash is no longer a pure function of: diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h index 9ac24faa..eb4d54ef 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h @@ -10,7 +10,7 @@ #include "../VkIncludes.h" #include "PipelineFactory.h" -#include "MG_State/GLState/ProgramState/ProgramObject.h" +#include "MagmaProgramSource.h" #include "MG_State/GLState/ProgramState/ShaderObject.h" #include "MG_State/GLState/TextureState/TextureEnum.h" @@ -471,9 +471,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { ~ProgramFactory(); ProgramFactory(const ProgramFactory&) = delete; - HashType ComputeHash(const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags) const; + HashType ComputeHash(const MagmaProgramSource& program, CompileOptionFlags flags) const; const VkProgramObject& GetOrCreateProgram( - const MG_State::GLState::ProgramObject& program, CompileOptionFlags flags); + const MagmaProgramSource& program, CompileOptionFlags flags); // The default framebuffer's current image height, baked as a literal into every // FragCoordYFlip variant (there is no push-constant or specialization channel here, and @@ -600,7 +600,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void ReflectFragmentOutputs(const Vector& stages, const Vector>& spirv, VkProgramObject& entry) const; - void ReflectLayout(const MG_State::GLState::ProgramObject& program, const Vector>& spirv, + void ReflectLayout(const MagmaProgramSource& program, const Vector>& spirv, VkProgramObject& entry) const; // Fills needsPassthroughTessControl / passthroughTessControlEmulatable off the linked // modules. Const and reflection-only: it decides nothing about the pipeline, it only