From 15dbe78ce668169d7b6b48d06200e771d13b00fe Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 2 Oct 2026 05:52:38 -0400 Subject: [PATCH] [Fix] (DirectVulkan): give each served session its own renderer and Magma state --- CMakeLists.txt | 1 + MobileGL/MG_Backend/BackendObject.h | 6 ++ .../BackendObject_DirectVulkan.cpp | 16 +++ .../DirectVulkan/BackendObject_DirectVulkan.h | 5 + .../MG_Backend/DirectVulkan/DirectVulkan.cpp | 32 ++++-- .../MG_Backend/DirectVulkan/DirectVulkan.h | 24 ++++- .../MG_Backend/DirectVulkan/MagmaSession.cpp | 87 +++++++++++++++ .../MG_Backend/DirectVulkan/MagmaSession.h | 100 ++++++++++++++++++ .../DirectVulkan/Renderer/PipelineFactory.h | 2 +- .../DirectVulkan/Renderer/ProgramFactory.h | 15 +-- .../Renderer/VertexInputStateFactory.h | 5 +- .../DirectVulkan/Renderer/VkBufferManager.cpp | 36 +++---- .../Renderer/VkRenderPassManager.cpp | 76 ++++++------- .../Renderer/VkRenderPassManager.h | 25 +++-- .../DirectVulkan/Renderer/VkSamplerManager.h | 2 +- .../Renderer/VkTextureManager.cpp | 25 +++-- .../DirectVulkan/Renderer/VkTextureManager.h | 21 ++-- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 55 +++++----- .../DirectVulkan/Renderer/VulkanRenderer.h | 14 +-- MobileGL/MG_Remote/Server/ServerLoop.cpp | 5 + 20 files changed, 407 insertions(+), 145 deletions(-) create mode 100644 MobileGL/MG_Backend/DirectVulkan/MagmaSession.cpp create mode 100644 MobileGL/MG_Backend/DirectVulkan/MagmaSession.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 6a97186e..358d7046 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -446,6 +446,7 @@ set(SOURCE_FILES MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp + MobileGL/MG_Backend/DirectVulkan/MagmaSession.cpp MobileGL/MG_Backend/DirectVulkan/VmaImpl.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index c8735004..5333ea17 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -591,6 +591,12 @@ namespace MobileGL { virtual Bool InitCapabilities() = 0; virtual Bool InitWindowSurface() = 0; + // A served session's apply thread - the one thread that runs this backend from here on - + // calls this first, in a process that may serve several sessions at once. A backend whose + // native state is process-global by construction gives this object its own copy and binds + // it to the calling thread. The default has nothing per process to separate. + virtual void BindSessionStateToThisThread() {} + virtual Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor); virtual Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle); virtual Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height); diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp index 8500fe74..45859757 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.cpp @@ -332,6 +332,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { #if MOBILEGL_BUILD_DISAGGREGATED BackendObject_DirectVulkan::~BackendObject_DirectVulkan() { + // Everything below reaches this session's renderer slot, whichever thread destroys it. + MagmaSession* const ownSession = m_magmaSession.get(); + MagmaSession* const previousSession = BoundMagmaSession(); + if (ownSession != nullptr) BindMagmaSessionToThisThread(ownSession); // P12 review fix: A SERVER SESSION THAT ENDS WITHOUT A CLEAN TEARDOWN LEAVES NO RENDERER ON // THE WINDOW. The renderer - swapchain and VkSurfaceKHR on the window - is a process global // that only ReleaseEGLResources / a released surface drops, and a client that went away @@ -349,6 +353,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (MG_Config::Transport != MG_Config::TransportMode::Monolith) { ForgetRequestedSwapInterval(); } + // The session's state goes with it, and no thread may keep answering with it. + m_magmaSession.reset(); + if (ownSession != nullptr) { + BindMagmaSessionToThisThread(previousSession == ownSession ? nullptr : previousSession); + } } #else BackendObject_DirectVulkan::~BackendObject_DirectVulkan() = default; @@ -388,6 +397,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { return true; } + void BackendObject_DirectVulkan::BindSessionStateToThisThread() { + if (m_magmaSession == nullptr) m_magmaSession = MakeUnique(); + BindMagmaSessionToThisThread(m_magmaSession.get()); + MGLOG_I("DirectVulkan: session state %llu bound to this apply thread", + static_cast(m_magmaSession->Id())); + } + void BackendObject_DirectVulkan::Initialize() { #if MOBILEGL_BUILD_DISAGGREGATED VkBufferManager::RegisterWireResourceOps(); diff --git a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h index 30a14d59..eb739b2e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/BackendObject_DirectVulkan.h @@ -9,6 +9,7 @@ #pragma once #include #include "../BackendObject.h" +#include "MagmaSession.h" #include namespace MobileGL::MG_Backend::DirectVulkan { @@ -26,6 +27,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { ~BackendObject_DirectVulkan() override; void Initialize() override; + void BindSessionStateToThisThread() override; Bool InitWindowSurface() override; Bool InitCapabilities() override; Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override; @@ -55,6 +57,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { DynamicBackendParameters m_dynamicParameters; MG_External::VulkanCapabilities m_vulkanCaps; RendererInfo m_rendererInfo; + // A served session's own copy of Magma's per-process state (MagmaSession.h), bound to its + // apply thread. Null in the monolith and every other single-session shape. + UniquePtr m_magmaSession; }; // Single-source-of-truth helpers shared with the driver POST diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp index 61c1724c..ef62d30f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp @@ -35,8 +35,9 @@ #include namespace MobileGL::MG_Backend::DirectVulkan { - // Leak-at-exit storage; see GlobalObjects.cpp. - UniquePtr& pVulkanRenderer = *new UniquePtr(); + // Per Magma session (MagmaSession.h); the process-wide session is leak-at-exit storage, see + // GlobalObjects.cpp. + RendererSlot pVulkanRenderer; namespace { #if MOBILEGL_BUILD_DISAGGREGATED @@ -59,7 +60,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { // current one (a new renderer restarts its frame-serial counter and // reuses pool indices). Atomic because handles may be polled from a // thread other than the EGL thread that recreates the renderer. - std::atomic g_rendererGeneration{1}; + // + // One per Magma session, drawn from one process-wide source: a handle stamped by one + // session's renderer must read as stale against every other session's, never equal. + std::atomic g_rendererGenerationSource{1}; + struct RendererGeneration { + std::atomic value{g_rendererGenerationSource.fetch_add(1, std::memory_order_acq_rel)}; + }; + SessionLocal g_rendererGeneration; } // namespace #if MOBILEGL_BUILD_DISAGGREGATED && MOBILEGL_PIPE_PUSH @@ -123,11 +131,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { #endif // MOBILEGL_BUILD_DISAGGREGATED && MOBILEGL_PIPE_PUSH Uint64 GetRendererGeneration() { - return g_rendererGeneration.load(std::memory_order_acquire); + return g_rendererGeneration->value.load(std::memory_order_acquire); } void BumpRendererGeneration() { - g_rendererGeneration.fetch_add(1, std::memory_order_acq_rel); + g_rendererGeneration->value.store(g_rendererGenerationSource.fetch_add(1, std::memory_order_acq_rel), + std::memory_order_release); } namespace { @@ -1274,7 +1283,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { payload.mode = mode; // TODO: allocate draw cmd buf elsewhere - static Vector params; + static thread_local Vector params; params.clear(); params.resize(drawcount); @@ -1347,7 +1356,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Uint32 indexSizeShift = static_cast(std::countr_zero(indexSize)); // TODO: allocate draw cmd buf elsewhere - static Vector params; + static thread_local Vector params; params.clear(); params.resize(drawcount); @@ -1763,23 +1772,24 @@ namespace MobileGL::MG_Backend::DirectVulkan { } namespace { - Optional g_requestedSwapInterval; + // Per session: each client asks for its own interval. + SessionLocal> g_requestedSwapInterval; } // namespace void SetSwapInterval(Int interval) { - g_requestedSwapInterval = interval; + *g_requestedSwapInterval = interval; if (pVulkanRenderer) { pVulkanRenderer->SetSwapInterval(interval); } } Optional GetRequestedSwapInterval() { - return g_requestedSwapInterval; + return *g_requestedSwapInterval; } #if MOBILEGL_BUILD_DISAGGREGATED void ForgetRequestedSwapInterval() { - g_requestedSwapInterval.reset(); + g_requestedSwapInterval->reset(); } #endif diff --git a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h index 93c669e4..b091a291 100644 --- a/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h +++ b/MobileGL/MG_Backend/DirectVulkan/DirectVulkan.h @@ -9,10 +9,32 @@ #pragma once #include #include +#include "MagmaSession.h" #include "Renderer/VulkanRenderer.h" namespace MobileGL::MG_Backend::DirectVulkan { - extern UniquePtr& pVulkanRenderer; + // The renderer of the calling thread's Magma session (MagmaSession.h): a served session's own on + // its apply thread, the process-wide one everywhere else. Spelled like the UniquePtr it used to + // be so the call sites read the same. + class RendererSlot { + public: + UniquePtr& Slot() const { return m_slot.Get(); } + VulkanRenderer* get() const { return Slot().get(); } + VulkanRenderer* operator->() const { return get(); } + VulkanRenderer& operator*() const { return *get(); } + explicit operator bool() const { return get() != nullptr; } + RendererSlot& operator=(UniquePtr&& renderer) { + Slot() = std::move(renderer); + return *this; + } + void reset() { Slot().reset(); } + friend Bool operator==(const RendererSlot& slot, std::nullptr_t) { return slot.get() == nullptr; } + friend Bool operator!=(const RendererSlot& slot, std::nullptr_t) { return slot.get() != nullptr; } + + private: + SessionLocal> m_slot; + }; + extern RendererSlot pVulkanRenderer; // Generation of the live VulkanRenderer instance, mirroring DirectGLES's // g_syncContextGeneration. BackendObject_DirectVulkan bumps it wherever diff --git a/MobileGL/MG_Backend/DirectVulkan/MagmaSession.cpp b/MobileGL/MG_Backend/DirectVulkan/MagmaSession.cpp new file mode 100644 index 00000000..7f22897f --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/MagmaSession.cpp @@ -0,0 +1,87 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/MagmaSession.cpp +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#include "MagmaSession.h" + +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + namespace { + struct SessionLocalEntry { + Detail::SessionLocalConstruct construct; + Detail::SessionLocalDestroy destroy; + }; + + // Construct-on-first-use: SessionLocals are namespace-scope objects in several translation + // units and register during static initialization, in no particular order. + Vector& Registry() { + static auto* registry = new Vector(); + return *registry; + } + + std::atomic g_nextSessionId{1}; + + thread_local MagmaSession* t_boundSession = nullptr; + + // Leak-at-exit, like the renderer it holds (GlobalObjects.cpp): nothing may tear the + // monolith's renderer down from a static destructor after the driver is gone. + MagmaSession& ProcessSession() { + static auto* session = new MagmaSession(); + return *session; + } + } // namespace + + Uint32 Detail::RegisterSessionLocal(SessionLocalConstruct construct, SessionLocalDestroy destroy) { + auto& registry = Registry(); + registry.push_back({construct, destroy}); + return static_cast(registry.size() - 1); + } + + MagmaSession& Detail::CurrentMagmaSession() { + MagmaSession* bound = t_boundSession; + return bound != nullptr ? *bound : ProcessSession(); + } + + void* Detail::SessionLocalSlot(MagmaSession& session, Uint32 index) { + if (index >= session.m_slots.size()) session.m_slots.resize(Registry().size(), nullptr); + void*& slot = session.m_slots[index]; + if (slot == nullptr) slot = Registry()[index].construct(); + return slot; + } + + MagmaSession::MagmaSession() : m_id(g_nextSessionId.fetch_add(1, std::memory_order_relaxed)) { + // Every slot is built up front: the process-wide session is reached from whichever thread + // the app renders on, and a lazily built slot would be a first-touch race between two. + const auto& registry = Registry(); + m_slots.resize(registry.size(), nullptr); + for (SizeT i = 0; i < registry.size(); ++i) m_slots[i] = registry[i].construct(); + } + + MagmaSession::~MagmaSession() { + MagmaSession* const previous = t_boundSession; + t_boundSession = this; + const auto& registry = Registry(); + for (SizeT i = m_slots.size(); i-- > 0;) { + if (m_slots[i] != nullptr) registry[i].destroy(m_slots[i]); + m_slots[i] = nullptr; + } + // Never back to this session: it is gone. + t_boundSession = previous == this ? nullptr : previous; + } + + MagmaSessionScope::MagmaSessionScope(MagmaSession* session) : m_previous(t_boundSession) { + t_boundSession = session; + } + + MagmaSessionScope::~MagmaSessionScope() { t_boundSession = m_previous; } + + void BindMagmaSessionToThisThread(MagmaSession* session) { t_boundSession = session; } + + MagmaSession* BoundMagmaSession() { return t_boundSession; } + +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/MagmaSession.h b/MobileGL/MG_Backend/DirectVulkan/MagmaSession.h new file mode 100644 index 00000000..bea6597f --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/MagmaSession.h @@ -0,0 +1,100 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/MagmaSession.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +// MAGMA'S PER-SESSION STATE. Magma was written for one renderer per process: the renderer slot, +// the active buffer manager, the dynamic-state shadow, the render-pass manager's statics and the +// renderer generation are all file-scope. A multi-session server (several clients served from one +// process, each on its own apply thread with its own BackendObject_DirectVulkan) needs one copy of +// each per session, or one session's work lands in another's tables. +// +// SessionLocal is that copy. It resolves through the CALLING THREAD's bound MagmaSession, and to +// the process-wide default session on every thread nothing bound - which is the monolith, the client +// role and every unit case, i.e. exactly the old behaviour, on whatever thread the app renders from. +// A served session's backend owns its MagmaSession and binds it on its apply thread (the only thread +// that runs that session's backend), so the two never mix. + +#pragma once +#include + +#include +#include +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + + class MagmaSession; + + namespace Detail { + using SessionLocalConstruct = void* (*)(); + using SessionLocalDestroy = void (*)(void*); + // Registers one SessionLocal and returns its slot. Called from static initialization. + Uint32 RegisterSessionLocal(SessionLocalConstruct construct, SessionLocalDestroy destroy); + // The calling thread's session, or the process-wide default. + MagmaSession& CurrentMagmaSession(); + void* SessionLocalSlot(MagmaSession& session, Uint32 index); + } // namespace Detail + + // One served session's copy of every SessionLocal. Slots are built lazily on first use and + // destroyed in reverse registration order with the session still bound, so a destructor that + // reaches another SessionLocal finds this session's copy and not the binding thread's. + class MagmaSession { + public: + MagmaSession(); + ~MagmaSession(); + MagmaSession(const MagmaSession&) = delete; + MagmaSession& operator=(const MagmaSession&) = delete; + + // A monotonic id, unique for the process lifetime. Never an address: a later session can + // be allocated where an ended one lived. + Uint64 Id() const { return m_id; } + + private: + friend void* Detail::SessionLocalSlot(MagmaSession& session, Uint32 index); + Vector m_slots; + Uint64 m_id = 0; + }; + + // Makes `session` the calling thread's Magma session for the scope's lifetime, restoring the + // previous binding (usually none) afterwards. + class MagmaSessionScope { + public: + explicit MagmaSessionScope(MagmaSession* session); + ~MagmaSessionScope(); + MagmaSessionScope(const MagmaSessionScope&) = delete; + MagmaSessionScope& operator=(const MagmaSessionScope&) = delete; + + private: + MagmaSession* m_previous = nullptr; + }; + + // Binds `session` to the calling thread until the next call (nullptr unbinds). For the apply + // thread, which binds its backend once and runs it for the rest of its life. + void BindMagmaSessionToThisThread(MagmaSession* session); + // The calling thread's bound session, or nullptr (the process-wide default answers). + MagmaSession* BoundMagmaSession(); + + template + class SessionLocal { + public: + SessionLocal() : m_index(Detail::RegisterSessionLocal(&Construct, &Destroy)) {} + SessionLocal(const SessionLocal&) = delete; + SessionLocal& operator=(const SessionLocal&) = delete; + + T& Get() const { + return *static_cast(Detail::SessionLocalSlot(Detail::CurrentMagmaSession(), m_index)); + } + T& operator*() const { return Get(); } + T* operator->() const { return &Get(); } + + private: + static void* Construct() { return new T(); } + static void Destroy(void* object) { delete static_cast(object); } + const Uint32 m_index; + }; + +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h index 79317b45..253e4e79 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/PipelineFactory.h @@ -199,7 +199,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { UnorderedMap m_cache; // Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging. Uint64 m_frameCounter = 0; - static inline XXH64_state_t* m_hashState = XXH64_createState(); + static inline thread_local XXH64_state_t* m_hashState = XXH64_createState(); static inline Bool s_suppressBlendedDepthWrite = false; }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h index eb4d54ef..f5717b79 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/ProgramFactory.h @@ -9,6 +9,7 @@ #pragma once #include "../VkIncludes.h" +#include "../MagmaSession.h" #include "PipelineFactory.h" #include "MagmaProgramSource.h" #include "MG_State/GLState/ProgramState/ShaderObject.h" @@ -251,7 +252,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // entry pointer re-stamps use through a const reference (StampProgramUse). mutable Uint64 lastUsedFrame = 0; - static inline VkDevice s_device = VK_NULL_HANDLE; + static inline SessionLocal s_device; // per Magma session (MagmaSession.h) VkProgramObject() = default; VkProgramObject(const VkProgramObject&) = delete; @@ -395,18 +396,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { private: void Destroy() { - if (s_device != VK_NULL_HANDLE) { + if (s_device.Get() != VK_NULL_HANDLE) { if (pipelineLayout != VK_NULL_HANDLE) { - vkDestroyPipelineLayout(s_device, pipelineLayout, nullptr); + vkDestroyPipelineLayout(s_device.Get(), pipelineLayout, nullptr); pipelineLayout = VK_NULL_HANDLE; } if (descriptorSetLayout != VK_NULL_HANDLE) { - vkDestroyDescriptorSetLayout(s_device, descriptorSetLayout, nullptr); + vkDestroyDescriptorSetLayout(s_device.Get(), descriptorSetLayout, nullptr); descriptorSetLayout = VK_NULL_HANDLE; } for (auto module : modules) { if (module != VK_NULL_HANDLE) { - vkDestroyShaderModule(s_device, module, nullptr); + vkDestroyShaderModule(s_device.Get(), module, nullptr); } } } @@ -463,7 +464,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_enableSpirvValidation(enableSpirvValidation), m_updateAfterBindLimits(updateAfterBindLimits), m_subgroupPolicy(subgroupPolicy) { - VkProgramObject::s_device = device; + VkProgramObject::s_device.Get() = device; } // Destroys the pass-through tessellation control modules. Runs while the device is // still alive for the same reason ~VkProgramObject's does: this factory outlives @@ -655,6 +656,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { // and adds only a handful of values, so it does not move the cap in practice. static constexpr SizeT kMaxPassthroughTessControlStages = 64; UnorderedMap m_passthroughTessControlStages; - static inline XXH64_state_t* m_hashState = XXH64_createState(); + static inline thread_local XXH64_state_t* m_hashState = XXH64_createState(); }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h index bb602cc4..42b8679d 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VertexInputStateFactory.h @@ -19,6 +19,7 @@ #include "VertexInputStateBuilder.h" #include "MG_State/GLState/VertexArrayState/VertexArrayObject.h" #include +#include #include "../VkIncludes.h" namespace MobileGL::MG_Backend::DirectVulkan { @@ -217,11 +218,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { // memo table is owned by this factory and dies with it, so a per-instance counter is // enough there and the epoch shrinks back to what it looks like it should be. #if MOBILEGL_PIPE_LEGACY_MEMOS - static inline Uint64 s_evictionEpochSource = 0; + static inline std::atomic s_evictionEpochSource{0}; Uint64 m_evictionEpoch = ++s_evictionEpochSource; #else Uint64 m_evictionEpoch = 1; #endif - static inline XXH64_state_t* m_hashState = XXH64_createState(); + static inline thread_local XXH64_state_t* m_hashState = XXH64_createState(); }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index daedf1c5..cd371dbe 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -113,30 +113,30 @@ namespace MobileGL::MG_Backend::DirectVulkan { using MG_State::GLState::BufferObject; // The manager owned by the active VulkanRenderer; immediate ops route here. - VkBufferManager* g_activeBufferManager = nullptr; + SessionLocal g_activeBufferManager; // per Magma session (MagmaSession.h) void Ops_Respecify(BufferObject& bufferObject) { - if (g_activeBufferManager) { - g_activeBufferManager->OnRespecify(bufferObject); + if (g_activeBufferManager.Get()) { + g_activeBufferManager.Get()->OnRespecify(bufferObject); } } void Ops_SubData(BufferObject& bufferObject, SizeT offset, SizeT size) { - if (g_activeBufferManager) { - g_activeBufferManager->OnSubData(bufferObject, offset, size); + if (g_activeBufferManager.Get()) { + g_activeBufferManager.Get()->OnSubData(bufferObject, offset, size); } } void Ops_ResidentSubData(BufferObject& bufferObject, SizeT offset, DataPtr data) { - if (g_activeBufferManager) { - g_activeBufferManager->OnResidentSubData(bufferObject, offset, data); + if (g_activeBufferManager.Get()) { + g_activeBufferManager.Get()->OnResidentSubData(bufferObject, offset, data); } } void Ops_FlushMappedRange(BufferObject& bufferObject, Range1D range, Flags appAccess) { - if (g_activeBufferManager) { - g_activeBufferManager->OnFlushMappedRange(bufferObject, range, appAccess); + if (g_activeBufferManager.Get()) { + g_activeBufferManager.Get()->OnFlushMappedRange(bufferObject, range, appAccess); } } @@ -154,15 +154,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void* Ops_AcquirePersistentMap(BufferObject& bufferObject) { - if (g_activeBufferManager) { - return g_activeBufferManager->AcquirePersistentMap(bufferObject); + if (g_activeBufferManager.Get()) { + return g_activeBufferManager.Get()->AcquirePersistentMap(bufferObject); } return nullptr; } void Ops_OnDestroy(SharedPtr&& resource) { - if (g_activeBufferManager) { - g_activeBufferManager->OnResourceDestroyed(std::move(resource)); + if (g_activeBufferManager.Get()) { + g_activeBufferManager.Get()->OnResourceDestroyed(std::move(resource)); } // No active manager: the device/allocator is gone or going away and // Shutdown() already destroyed the storage; dropping the handle here @@ -182,11 +182,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { #if MOBILEGL_BUILD_DISAGGREGATED VkBufferManager& WireManager() { - if (g_activeBufferManager == nullptr) { + if (g_activeBufferManager.Get() == nullptr) { MGLOG_F("Magma: Fatal{ResourceUnavailable, \"wire-buffer-manager\"}"); std::abort(); } - return *g_activeBufferManager; + return *g_activeBufferManager.Get(); } const MG_Pipe::MGPipeResourceOps g_vulkanWireResourceOps = { @@ -1123,7 +1123,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (!InitializeTransientArenas()) { return false; } - g_activeBufferManager = this; + g_activeBufferManager.Get() = this; MG_State::GLState::SetBufferBackendOps(&g_vulkanBufferBackendOps); #if MOBILEGL_BUILD_DISAGGREGATED RegisterWireResourceOps(); @@ -1132,8 +1132,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VkBufferManager::Shutdown() { - if (g_activeBufferManager == this) { - g_activeBufferManager = nullptr; + if (g_activeBufferManager.Get() == this) { + g_activeBufferManager.Get() = nullptr; #if MOBILEGL_BUILD_DISAGGREGATED if (MG_Pipe::MGPipeGetResourceOps() == &g_vulkanWireResourceOps) { MG_Pipe::MGPipeSetResourceOps(nullptr); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 3adaf82c..88e2eee7 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -235,11 +235,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkClearManager& clearManager, VkTextureManager& textureManager, SwapchainObject& swapchainObject): m_device(device), m_physicalDevice(physicalDevice), m_allocator(allocator), m_config(config), m_clearManager(clearManager), m_textureManager(textureManager), m_swapchainObject(swapchainObject) { - RenderPassEntry::s_device = m_device; - s_clearManager = &m_clearManager; - s_textureManager = &m_textureManager; - s_swapchainObject = &m_swapchainObject; - s_renderPassManager = this; + RenderPassEntry::s_device.Get() = m_device; + s_clearManager.Get() = &m_clearManager; + s_textureManager.Get() = &m_textureManager; + s_swapchainObject.Get() = &m_swapchainObject; + s_renderPassManager.Get() = this; } VkRenderPassManager::~VkRenderPassManager() {} @@ -257,8 +257,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { CollectDeferredRenderbufferReleases(/*destroyAll=*/true); // caller guarantees device idle m_pendingRenderbufferClears.clear(); RenderPassEntry::s_textureResourcesScratch.clear(); - s_activeRenderPass = {}; - s_hasActiveRenderPass = false; + s_activeRenderPass.Get() = {}; + s_hasActiveRenderPass.Get() = false; m_rpFastValid = false; } @@ -1554,9 +1554,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { // immediately - and a single batched notification costs one pipeline-cache // scan instead of one per evicted pass. Vector destroyedRenderPasses; - const Uint64 activeHash = s_hasActiveRenderPass ? s_activeRenderPass.hash : 0; + const Uint64 activeHash = s_hasActiveRenderPass.Get() ? s_activeRenderPass.Get().hash : 0; for (auto it = m_renderPasses.begin(); it != m_renderPasses.end();) { - const Bool isActive = s_hasActiveRenderPass && it->first == activeHash; + const Bool isActive = s_hasActiveRenderPass.Get() && it->first == activeHash; if (!isActive && m_frameCounter - it->second.lastUsedFrame > kRetireAgeFrames) { if (m_rpFastValid && m_rpFastRenderPassHash == it->first) { m_rpFastValid = false; @@ -1601,8 +1601,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { // texture path below is immune because it re-reads the live payload). // Same defense as ClearAttachmentsOnActiveRenderPass: prefer the live // pending clear, fall back to the snapshot only when none is queued. - if (s_renderPassManager != nullptr && - s_renderPassManager->GetPendingRenderbufferClear(pending.renderbuffer, clearPayload)) { + if (s_renderPassManager.Get() != nullptr && + s_renderPassManager.Get()->GetPendingRenderbufferClear(pending.renderbuffer, clearPayload)) { if ((clearPayload.mask & GL_COLOR_BUFFER_BIT) != 0 && pending.renderbuffer != nullptr && MG_Util::GetBaseInternalFormatComponentCount(pending.renderbuffer->GetInternalFormat()) == 3) { @@ -1614,7 +1614,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } else { if (pending.key.texture == nullptr || - !s_clearManager->GetPendingClear(pending.key, clearPayload, liveTexture)) { + !s_clearManager.Get()->GetPendingClear(pending.key, clearPayload, liveTexture)) { continue; } } @@ -1636,29 +1636,29 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); // Pre-pass stream bookkeeping: this pass's attachment images are now // referenced by the open frame recording. - if (s_textureManager != nullptr) { + if (s_textureManager.Get() != nullptr) { for (const auto& tracked : renderPassEntry.trackedAttachmentLayouts) { if (tracked.target == TrackedAttachmentTarget::Texture) { if (const auto texture = tracked.texture.lock()) { - s_textureManager->StampTextureRecordingUse(texture.get()); + s_textureManager.Get()->StampTextureRecordingUse(texture.get()); } } } } for (const auto& pending: renderPassEntry.pendingClearAttachments) { if (pending.hasInlinePayload) { - if (s_renderPassManager != nullptr) { - s_renderPassManager->PopPendingRenderbufferClear(pending.renderbuffer); + if (s_renderPassManager.Get() != nullptr) { + s_renderPassManager.Get()->PopPendingRenderbufferClear(pending.renderbuffer); } } else { - s_clearManager->PopPendingClear(pending.key); + s_clearManager.Get()->PopPendingClear(pending.key); } } - s_activeRenderPass.hash = renderPassEntry.hash; - s_activeRenderPass.compatibilityHash = renderPassEntry.compatibilityHash; - s_activeRenderPass.trackedAttachmentLayouts = renderPassEntry.trackedAttachmentLayouts; - s_activeRenderPass.extent = renderPassEntry.extent; - s_hasActiveRenderPass = true; + s_activeRenderPass.Get().hash = renderPassEntry.hash; + s_activeRenderPass.Get().compatibilityHash = renderPassEntry.compatibilityHash; + s_activeRenderPass.Get().trackedAttachmentLayouts = renderPassEntry.trackedAttachmentLayouts; + s_activeRenderPass.Get().extent = renderPassEntry.extent; + s_hasActiveRenderPass.Get() = true; return true; } @@ -1668,16 +1668,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkCmdEndRenderPass(commandBuffer); // The fast-path memo reuses the ACTIVE render pass; once the pass ends it must not carry // over (the next span may be a different FBO resolved before its render pass is begun). - if (s_renderPassManager != nullptr) { - s_renderPassManager->m_rpFastValid = false; + if (s_renderPassManager.Get() != nullptr) { + s_renderPassManager.Get()->m_rpFastValid = false; } if (activeRenderPass != nullptr && !activeRenderPass->trackedAttachmentLayouts.empty()) { for (const auto& trackedAttachment : activeRenderPass->trackedAttachmentLayouts) { switch (trackedAttachment.target) { case TrackedAttachmentTarget::Texture: - MOBILEGL_ASSERT(s_textureManager != nullptr, "EndRenderPass: texture manager is null"); + MOBILEGL_ASSERT(s_textureManager.Get() != nullptr, "EndRenderPass: texture manager is null"); if (const auto texture = trackedAttachment.texture.lock()) { - s_textureManager->UpdateTrackedImageLayoutAfterAttachmentWrite( + s_textureManager.Get()->UpdateTrackedImageLayoutAfterAttachmentWrite( commandBuffer, texture.get(), trackedAttachment.textureMipLevel, @@ -1685,27 +1685,27 @@ namespace MobileGL::MG_Backend::DirectVulkan { } break; case TrackedAttachmentTarget::Renderbuffer: - MOBILEGL_ASSERT(s_renderPassManager != nullptr, "EndRenderPass: render pass manager is null"); + MOBILEGL_ASSERT(s_renderPassManager.Get() != nullptr, "EndRenderPass: render pass manager is null"); if (const auto renderbuffer = trackedAttachment.renderbuffer.lock()) { auto resourceIt = - s_renderPassManager->m_renderbufferResources.find(renderbuffer.get()); - if (resourceIt != s_renderPassManager->m_renderbufferResources.end()) { + s_renderPassManager.Get()->m_renderbufferResources.find(renderbuffer.get()); + if (resourceIt != s_renderPassManager.Get()->m_renderbufferResources.end()) { resourceIt->second.layout = trackedAttachment.finalLayout; } } break; case TrackedAttachmentTarget::SwapchainColor: - MOBILEGL_ASSERT(s_swapchainObject != nullptr, "EndRenderPass: swapchain object is null"); - s_swapchainObject->SetImageLayout(trackedAttachment.swapchainImageIndex, trackedAttachment.finalLayout); + MOBILEGL_ASSERT(s_swapchainObject.Get() != nullptr, "EndRenderPass: swapchain object is null"); + s_swapchainObject.Get()->SetImageLayout(trackedAttachment.swapchainImageIndex, trackedAttachment.finalLayout); // The pass stored into the attachment: its content is defined // until the image is next presented. - s_swapchainObject->SetImageContentDefined(trackedAttachment.swapchainImageIndex, true); + s_swapchainObject.Get()->SetImageContentDefined(trackedAttachment.swapchainImageIndex, true); break; case TrackedAttachmentTarget::SwapchainDepthStencil: - MOBILEGL_ASSERT(s_swapchainObject != nullptr, "EndRenderPass: swapchain object is null"); - s_swapchainObject->SetDepthStencilImageLayout(trackedAttachment.swapchainImageIndex, + MOBILEGL_ASSERT(s_swapchainObject.Get() != nullptr, "EndRenderPass: swapchain object is null"); + s_swapchainObject.Get()->SetDepthStencilImageLayout(trackedAttachment.swapchainImageIndex, trackedAttachment.finalLayout); - s_swapchainObject->SetDepthStencilContentDefined(trackedAttachment.swapchainImageIndex, true); + s_swapchainObject.Get()->SetDepthStencilContentDefined(trackedAttachment.swapchainImageIndex, true); break; default: MOBILEGL_ASSERT(false, "EndRenderPass: unsupported tracked attachment target=%d", @@ -1714,12 +1714,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } } - s_activeRenderPass = {}; - s_hasActiveRenderPass = false; + s_activeRenderPass.Get() = {}; + s_hasActiveRenderPass.Get() = false; return true; } ActiveRenderPassInfo* VkRenderPassManager::GetActiveRenderPass() { - return s_hasActiveRenderPass ? &s_activeRenderPass : nullptr; + return s_hasActiveRenderPass.Get() ? &s_activeRenderPass.Get() : nullptr; } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index 17080b55..b18ab881 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -66,8 +66,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkExtent2D swapchainExtent); struct RenderPassEntry { - static inline VkDevice s_device; - static inline Vector s_textureResourcesScratch; + // Per Magma session (MagmaSession.h): each session's renderer is its own device. + static inline SessionLocal s_device; + static inline thread_local Vector s_textureResourcesScratch; Uint64 hash = 0; VkRenderPass renderPass = VK_NULL_HANDLE; VkFramebuffer framebuffer = VK_NULL_HANDLE; @@ -165,10 +166,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { ~RenderPassEntry() { if (renderPass != VK_NULL_HANDLE) { - vkDestroyRenderPass(s_device, renderPass, nullptr); + vkDestroyRenderPass(s_device.Get(), renderPass, nullptr); } if (framebuffer != VK_NULL_HANDLE) { - vkDestroyFramebuffer(s_device, framebuffer, nullptr); + vkDestroyFramebuffer(s_device.Get(), framebuffer, nullptr); } } @@ -411,12 +412,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { void DeferRenderbufferBackingRelease(RenderbufferResource& resource); void CollectDeferredRenderbufferReleases(Bool destroyAll); - static inline XXH64_state_t* m_hashState = XXH64_createState(); - static inline ActiveRenderPassInfo s_activeRenderPass{}; - static inline Bool s_hasActiveRenderPass = false; - static inline VkClearManager* s_clearManager = nullptr; - static inline VkTextureManager* s_textureManager = nullptr; - static inline SwapchainObject* s_swapchainObject = nullptr; - static inline VkRenderPassManager* s_renderPassManager = nullptr; + static inline thread_local XXH64_state_t* m_hashState = XXH64_createState(); + // Per Magma session (MagmaSession.h): one session's open render pass and managers are not + // another's. + static inline SessionLocal s_activeRenderPass; + static inline SessionLocal s_hasActiveRenderPass; + static inline SessionLocal s_clearManager; + static inline SessionLocal s_textureManager; + static inline SessionLocal s_swapchainObject; + static inline SessionLocal s_renderPassManager; }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h index 199f326d..a1ff5391 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkSamplerManager.h @@ -138,6 +138,6 @@ private: UnorderedMap m_samplers; // Monotonic frame-boundary counter (bumped in OnFrameBoundary) for cache aging. Uint64 m_frameBoundaryCounter = 0; - static inline XXH64_state_t* m_hashState = XXH64_createState(); + static inline thread_local XXH64_state_t* m_hashState = XXH64_createState(); }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index e866f608..631d2fbe 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -42,9 +42,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { // their feature is off (VUID-vkCmdPipelineBarrier-srcStageMask-04090/-04091), and ALL_GRAPHICS // would also serialize against non-shader stages. The default only matters before a device // exists, when nothing records barriers. - static VkPipelineStageFlags s_sampledReadStages = - VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + // Per Magma session: each session's renderer is its own device. + struct SampledReadStages { + VkPipelineStageFlags value = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT; + }; + static SessionLocal s_sampledReadStagesSlot; static Uint32 ComputeFullMipLevelCount(const IntVec3& baseTexelSize) { Int maxDimension = std::max(baseTexelSize.x(), @@ -203,7 +206,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL: case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: - outSrcStageMask = s_sampledReadStages; + outSrcStageMask = s_sampledReadStagesSlot->value; outSrcAccessMask = VK_ACCESS_SHADER_READ_BIT; return; case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: @@ -267,7 +270,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { case VK_IMAGE_LAYOUT_DEPTH_READ_ONLY_STENCIL_ATTACHMENT_OPTIMAL: case VK_IMAGE_LAYOUT_DEPTH_ATTACHMENT_STENCIL_READ_ONLY_OPTIMAL: case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: - outDstStageMask = s_sampledReadStages; + outDstStageMask = s_sampledReadStagesSlot->value; outDstAccessMask = VK_ACCESS_SHADER_READ_BIT; return; case VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL: @@ -646,7 +649,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_commandPool = initInfo.commandPool; m_graphicsQueue = initInfo.graphicsQueue; m_imageFormatListSupported = initInfo.imageFormatListSupported; - s_sampledReadStages = initInfo.sampledReadStageMask; + s_sampledReadStagesSlot->value = initInfo.sampledReadStageMask; m_currentFrameIndex = 0; m_deferredReleases.clear(); m_deferredReleases.resize(initInfo.frameCount); @@ -659,8 +662,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(initInfo.frameCount > 0, "VkTextureManager::Initialize failed: frameCount must be > 0"); - TextureResource::s_device = m_device; - TextureResource::s_allocator = m_allocator; + TextureResource::s_device.Get() = m_device; + TextureResource::s_allocator.Get() = m_allocator; // Own pool for the recycled upload-batch command buffers. Parking a // dozen reset-but-alive command buffers in the renderer's shared pool @@ -1477,7 +1480,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const Bool ok = TransitionImageLayout(commandBuffer, resource->image, resource->layout, targetLayout, srcStageMask, - s_sampledReadStages, srcAccessMask, + s_sampledReadStagesSlot->value, srcAccessMask, VK_ACCESS_SHADER_READ_BIT, resource->aspect, 0, resource->mipLevels); MOBILEGL_ASSERT(ok, "TransitionTextureForSampling: transition failed for textureId=%d", texture.GetExternalIndex()); // Pre-pass stream bookkeeping: a command referencing the image was recorded. @@ -2693,7 +2696,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const VkImageLayout finalLayout = ResolveSampledReadOnlyLayout(aspectMask); VkImageLayout uploadLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; ok = TransitionImageLayout(commandBuffer, resource.image, uploadLayout, finalLayout, - VK_PIPELINE_STAGE_TRANSFER_BIT, s_sampledReadStages, + VK_PIPELINE_STAGE_TRANSFER_BIT, s_sampledReadStagesSlot->value, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, aspectMask, 0, resource.mipLevels); MOBILEGL_ASSERT(ok, "UploadPendingWireLevels: transition to the sampled layout failed"); @@ -4573,7 +4576,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { uploadLayout, finalLayout, VK_PIPELINE_STAGE_TRANSFER_BIT, - s_sampledReadStages, + s_sampledReadStagesSlot->value, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, aspectMask, 0, outResource.mipLevels); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h index d3a28994..c450041c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.h @@ -9,6 +9,7 @@ #pragma once #include "../VkIncludes.h" +#include "../MagmaSession.h" #include #include #include @@ -397,38 +398,38 @@ public: void Reset() { if (fullView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device, fullView, nullptr); + vkDestroyImageView(s_device.Get(), fullView, nullptr); } if (sampledView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device, sampledView, nullptr); + vkDestroyImageView(s_device.Get(), sampledView, nullptr); } for (const auto attachmentView : perMipViews) { if (attachmentView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device, attachmentView, nullptr); + vkDestroyImageView(s_device.Get(), attachmentView, nullptr); } } for (const auto sampledView : perMipSampledViews) { if (sampledView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device, sampledView, nullptr); + vkDestroyImageView(s_device.Get(), sampledView, nullptr); } } for (const auto& [_, attachmentView] : attachmentViews) { if (attachmentView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device, attachmentView, nullptr); + vkDestroyImageView(s_device.Get(), attachmentView, nullptr); } } for (const auto& [_, sampledView] : alternateSampledViews) { if (sampledView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device, sampledView, nullptr); + vkDestroyImageView(s_device.Get(), sampledView, nullptr); } } for (const auto& [_, storageImageView] : storageImageViews) { if (storageImageView != VK_NULL_HANDLE) { - vkDestroyImageView(s_device, storageImageView, nullptr); + vkDestroyImageView(s_device.Get(), storageImageView, nullptr); } } if (image != VK_NULL_HANDLE && allocation != nullptr) { - vmaDestroyImage(s_allocator, image, allocation); + vmaDestroyImage(s_allocator.Get(), image, allocation); } fullView = VK_NULL_HANDLE; sampledView = VK_NULL_HANDLE; @@ -466,8 +467,8 @@ public: Reset(); } - static inline VkDevice s_device = VK_NULL_HANDLE; - static inline VmaAllocator s_allocator = VK_NULL_HANDLE; + static inline SessionLocal s_device; // per Magma session (MagmaSession.h) + static inline SessionLocal s_allocator; }; struct SampledTextureSnapshot { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index c92856ff..20a40706 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -418,7 +418,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { }; DynamicTailKey dynamicTailKey{}; }; - static DynamicStateShadow g_dynamicStateShadow; + // Per Magma session (MagmaSession.h): it shadows the session's own command buffer. + static SessionLocal g_dynamicStateShadow; #if MOBILEGL_PIPE_PUSH // ---- D12.3: DynamicTailKey's inputs against the P2 chunk table ---- @@ -516,14 +517,14 @@ namespace MobileGL::MG_Backend::DirectVulkan { #endif // MOBILEGL_PIPE_PUSH static void ResetDynamicStateShadow() { - g_dynamicStateShadow = {}; + *g_dynamicStateShadow = {}; } // vkCmdBindVertexBuffers, skipped when this command buffer already holds these // buffers and offsets at binding 0. static void ShadowedBindVertexBuffers(VkCommandBuffer commandBuffer, const VkBuffer* buffers, const VkDeviceSize* offsets, Uint32 count) { - auto& shadow = g_dynamicStateShadow; + auto& shadow = *g_dynamicStateShadow; Bool identical = shadow.vertexBindValid && shadow.vertexBindingCount == count && count <= DynamicStateShadow::kMaxShadowedVertexBindings; if (identical) { @@ -549,7 +550,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } static void ShadowedSetScissor(VkCommandBuffer commandBuffer, const VkRect2D& scissor) { - auto& shadow = g_dynamicStateShadow; + auto& shadow = *g_dynamicStateShadow; if (shadow.scissorValid && shadow.scissor.offset.x == scissor.offset.x && shadow.scissor.offset.y == scissor.offset.y && shadow.scissor.extent.width == scissor.extent.width && @@ -627,7 +628,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkSurfaceTransformFlagBitsKHR preTransform, Bool isDefaultFramebuffer) { const VkViewport viewport = ComputeGLViewport(0, framebufferExtent, preTransform, isDefaultFramebuffer); - auto& shadow = g_dynamicStateShadow; + auto& shadow = *g_dynamicStateShadow; if (shadow.viewportValid && shadow.viewport.x == viewport.x && shadow.viewport.y == viewport.y && shadow.viewport.width == viewport.width && shadow.viewport.height == viewport.height && shadow.viewport.minDepth == viewport.minDepth && shadow.viewport.maxDepth == viewport.maxDepth) { @@ -646,7 +647,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { blendColor.z(), blendColor.w(), }; - auto& shadow = g_dynamicStateShadow; + auto& shadow = *g_dynamicStateShadow; if (shadow.blendConstantsValid && shadow.blendConstants[0] == blendConstants[0] && shadow.blendConstants[1] == blendConstants[1] && shadow.blendConstants[2] == blendConstants[2] && shadow.blendConstants[3] == blendConstants[3]) { @@ -674,7 +675,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { static void ApplyPolygonOffsetState(VkCommandBuffer commandBuffer) { const Float constantFactor = MGB_CTX->GetPolygonOffsetUnits(); const Float slopeFactor = MGB_CTX->GetPolygonOffsetFactor(); - auto& shadow = g_dynamicStateShadow; + auto& shadow = *g_dynamicStateShadow; if (shadow.depthBiasValid && shadow.depthBiasConstantFactor == constantFactor && shadow.depthBiasSlopeFactor == slopeFactor) { return; @@ -714,7 +715,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { lineWidth = maxLineWidth; } } - auto& shadow = g_dynamicStateShadow; + auto& shadow = *g_dynamicStateShadow; if (shadow.lineWidthValid && shadow.lineWidth == lineWidth) { return; } @@ -787,7 +788,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const Uint32 frontReference = static_cast(std::max(frontStencil.Ref, 0)); const Uint32 backReference = static_cast(std::max(backStencil.Ref, 0)); - auto& shadow = g_dynamicStateShadow; + auto& shadow = *g_dynamicStateShadow; if (shadow.stencilValid && shadow.stencilFrontCompareMask == frontStencil.ValueMask && shadow.stencilBackCompareMask == backStencil.ValueMask && shadow.stencilFrontWriteMask == frontStencil.WriteMask && @@ -4450,7 +4451,7 @@ void main() { MGLOG_E_ONCE("DrawElements skipped: failed to upload client index data"); return false; } - auto& shadow = g_dynamicStateShadow; + auto& shadow = *g_dynamicStateShadow; if (!shadow.indexBindValid || shadow.indexBuffer != slice.buffer || shadow.indexOffset != slice.offset || shadow.indexType != vkIndexType) { vkCmdBindIndexBuffer(frame.commandBuffer, slice.buffer, slice.offset, vkIndexType); @@ -4490,7 +4491,7 @@ void main() { if (sliceStillValid) { const VkDeviceSize memoBindOffset = indexMemo->indexSliceOffset + static_cast(pIndexBufferView->indexByteOffset); - auto& shadow = g_dynamicStateShadow; + auto& shadow = *g_dynamicStateShadow; if (!shadow.indexBindValid || shadow.indexBuffer != indexMemo->indexVkBuffer || shadow.indexOffset != memoBindOffset || shadow.indexType != vkIndexType) { vkCmdBindIndexBuffer(frame.commandBuffer, indexMemo->indexVkBuffer, memoBindOffset, @@ -4550,7 +4551,7 @@ void main() { } const VkDeviceSize indexBindOffset = slice.offset + static_cast(pIndexBufferView->indexByteOffset); - auto& shadow = g_dynamicStateShadow; + auto& shadow = *g_dynamicStateShadow; if (!shadow.indexBindValid || shadow.indexBuffer != slice.buffer || shadow.indexOffset != indexBindOffset || shadow.indexType != vkIndexType) { vkCmdBindIndexBuffer(frame.commandBuffer, slice.buffer, indexBindOffset, vkIndexType); @@ -6170,7 +6171,7 @@ void main() { // VK_FORMAT_FEATURE_COLOR_ATTACHMENT_BLEND_BIT is invalid pipeline state // (blend support is optional for e.g. 32-bit float formats on some GPUs); // force-disable it instead of baking undefined behavior into the pipeline. - static UnorderedMap formatBlendSupport; + static thread_local UnorderedMap formatBlendSupport; auto blendSupportIt = formatBlendSupport.find(static_cast(colorAttachmentFormat)); if (blendSupportIt == formatBlendSupport.end()) { VkFormatProperties formatProperties{}; @@ -6494,7 +6495,7 @@ void main() { vkCmdSetViewport(commandBuffer, 0, count, viewports.data()); vkCmdSetScissor(commandBuffer, 0, count, scissors.data()); - auto& shadow = g_dynamicStateShadow; + auto& shadow = *g_dynamicStateShadow; shadow.viewportValid = false; shadow.scissorValid = false; shadow.dynamicTailValid = false; @@ -6502,7 +6503,7 @@ void main() { void VulkanRenderer::ApplyDynamicDrawStateTail(FrameContext::FrameData& frame, const IntVec2& extent, Bool isDefaultFbo, Uint32 viewportCount) { - auto& shadow = g_dynamicStateShadow; + auto& shadow = *g_dynamicStateShadow; if (viewportCount > 1) { // The other five Apply* still run: blend constants, depth bias, line width and the // stencil masks are not per-viewport and a multi-viewport draw needs them just as @@ -7051,11 +7052,11 @@ void main() { snap.vaoConfigVersion = vao.GetConfigVersion(); snap.vaoLayoutHash = vaoLayoutHash; snap.pipeline = pipeline; - if (!g_dynamicStateShadow.graphicsPipelineValid || - g_dynamicStateShadow.graphicsPipeline != pipeline) { + if (!g_dynamicStateShadow->graphicsPipelineValid || + g_dynamicStateShadow->graphicsPipeline != pipeline) { vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - g_dynamicStateShadow.graphicsPipelineValid = true; - g_dynamicStateShadow.graphicsPipeline = pipeline; + g_dynamicStateShadow->graphicsPipelineValid = true; + g_dynamicStateShadow->graphicsPipeline = pipeline; } if (!m_uniformManager->BindProgramUniformBuffers(frame.commandBuffer, program, programObj, m_frameContext.GetCurrentFrameIndex(), @@ -7608,10 +7609,10 @@ void main() { MOBILEGL_ASSERT(ok, "%s: BeginRenderPass failed", __func__); } - if (!g_dynamicStateShadow.graphicsPipelineValid || g_dynamicStateShadow.graphicsPipeline != pipeline) { + if (!g_dynamicStateShadow->graphicsPipelineValid || g_dynamicStateShadow->graphicsPipeline != pipeline) { vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); - g_dynamicStateShadow.graphicsPipelineValid = true; - g_dynamicStateShadow.graphicsPipeline = pipeline; + g_dynamicStateShadow->graphicsPipelineValid = true; + g_dynamicStateShadow->graphicsPipeline = pipeline; } const Bool boundUniforms = m_uniformManager->BindProgramUniformBuffers( @@ -13111,7 +13112,7 @@ void main() { } } if (uniformInstances) { - static Vector infos; + static thread_local Vector infos; infos.resize(drawCount); for (Uint32 idraw = 0; idraw < drawCount; ++idraw) { infos[idraw].firstVertex = pParams[idraw].firstVertex; @@ -13193,7 +13194,7 @@ void main() { } } if (uniformInstances) { - static Vector infos; + static thread_local Vector infos; infos.resize(drawCount); for (Uint32 idraw = 0; idraw < drawCount; ++idraw) { infos[idraw].firstIndex = pParams[idraw].firstIndex; @@ -13303,7 +13304,7 @@ void main() { } const DrawIndexedCmdParam* pParams = payload.pParams; Uint32 drawCount = payload.drawCount; - static Vector mergedParams; + static thread_local Vector mergedParams; if (mergeGranularity != 0) { mergedParams.clear(); mergedParams.reserve(drawCount); @@ -14492,7 +14493,7 @@ void main() { InvalidatePipelineMemo(); // an aged-out pipeline may still be memoized // A recreated pipeline could reuse a freed handle value and alias // the bind-dedup shadow; force the next draw to re-bind. - g_dynamicStateShadow.graphicsPipelineValid = false; + g_dynamicStateShadow->graphicsPipelineValid = false; InvalidateSetupDrawSnapshots(); } m_vertexInputStateFactory->OnFrameBoundary(); @@ -16704,7 +16705,7 @@ void main() { m_pipelineFactory->DestroyAll(); } InvalidatePipelineMemo(); // pipelines freed -> the memoized handle would dangle - g_dynamicStateShadow.graphicsPipelineValid = false; + g_dynamicStateShadow->graphicsPipelineValid = false; InvalidateSetupDrawSnapshots(); DestroyComputePipelines(); if (m_frameContext.GetFrameCount() > 0) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index a6d09d54..c6b2304f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -935,17 +935,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkDeviceSize offset, VkBuffer countBuffer, VkDeviceSize countBufferOffset, Uint32 maxDrawCount, Uint32 stride); - static inline PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr; + PFNDrawIndexedIndirectCountFunc s_vkCmdDrawIndexedIndirectCount = nullptr; #if MOBILEGL_BUILD_DISAGGREGATED // P8-D: vkCmdDrawIndirectCount (same signature, same extension), for the wire arm's // glMultiDrawArraysIndirectCount. The monolith arm never loads it: it reads that count on // the CPU (DirectVulkan.cpp's MultiDrawArraysIndirectCount). - static inline PFNDrawIndexedIndirectCountFunc s_vkCmdWireDrawIndirectCount = nullptr; + PFNDrawIndexedIndirectCountFunc s_vkCmdWireDrawIndirectCount = nullptr; #endif // VK_EXT_multi_draw entry points, loaded at device creation when the extension // (and its multiDraw feature) is enabled; null otherwise. - static inline PFN_vkCmdDrawMultiEXT s_vkCmdDrawMultiEXT = nullptr; - static inline PFN_vkCmdDrawMultiIndexedEXT s_vkCmdDrawMultiIndexedEXT = nullptr; + PFN_vkCmdDrawMultiEXT s_vkCmdDrawMultiEXT = nullptr; + PFN_vkCmdDrawMultiIndexedEXT s_vkCmdDrawMultiIndexedEXT = nullptr; // VK_EXT_transform_feedback (GL transform feedback capture) Bool m_transformFeedbackFeatureEnabled = false; @@ -976,9 +976,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { // VK_EXT_vertex_attribute_divisor: without it every non-zero glVertexAttribDivisor // behaves as 1, because that is all Vulkan's instance input rate can express. Bool m_vertexAttributeDivisorEnabled = false; - static inline PFN_vkCmdBindTransformFeedbackBuffersEXT s_vkCmdBindTransformFeedbackBuffersEXT = nullptr; - static inline PFN_vkCmdBeginTransformFeedbackEXT s_vkCmdBeginTransformFeedbackEXT = nullptr; - static inline PFN_vkCmdEndTransformFeedbackEXT s_vkCmdEndTransformFeedbackEXT = nullptr; + PFN_vkCmdBindTransformFeedbackBuffersEXT s_vkCmdBindTransformFeedbackBuffersEXT = nullptr; + PFN_vkCmdBeginTransformFeedbackEXT s_vkCmdBeginTransformFeedbackEXT = nullptr; + PFN_vkCmdEndTransformFeedbackEXT s_vkCmdEndTransformFeedbackEXT = nullptr; // Counter buffers (one 4-byte slot per capture binding) let consecutive // draws within one glBeginTransformFeedback append GL-style. Transform feedback // objects can each hold an open, paused span at the same time, so the counters are diff --git a/MobileGL/MG_Remote/Server/ServerLoop.cpp b/MobileGL/MG_Remote/Server/ServerLoop.cpp index 9e245648..c4d8f425 100644 --- a/MobileGL/MG_Remote/Server/ServerLoop.cpp +++ b/MobileGL/MG_Remote/Server/ServerLoop.cpp @@ -534,6 +534,11 @@ namespace MobileGL::MG_Remote::Server { static_cast(m_affinityMask), policy.spinUs, ApplyPeerName(session.Peer()), session.PeerSharedSegments() ? "shared segments" : "stream", policy.rule); + // A served session shares its process with other sessions: its backend's own native state + // is bound to this thread before anything can apply a record (BackendObject.h). The inproc + // one-process shape has no runtime and keeps the process-wide state. + if (m_runtime != nullptr && m_backend != nullptr) m_backend->BindSessionStateToThisThread(); + // The decoder is built HERE, on this thread, because PipeWireDecoder is "not thread // safe: one decoder on the apply thread, by construction" and its constructor installs // the process-wide apply hook.