[Fix] (DirectVulkan): give each served session its own renderer and Magma state

This commit is contained in:
2026-10-02 05:52:38 -04:00
parent 168808d1a7
commit 15dbe78ce6
20 changed files with 407 additions and 145 deletions
+1
View File
@@ -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
+6
View File
@@ -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);
@@ -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<MagmaSession>();
BindMagmaSessionToThisThread(m_magmaSession.get());
MGLOG_I("DirectVulkan: session state %llu bound to this apply thread",
static_cast<unsigned long long>(m_magmaSession->Id()));
}
void BackendObject_DirectVulkan::Initialize() {
#if MOBILEGL_BUILD_DISAGGREGATED
VkBufferManager::RegisterWireResourceOps();
@@ -9,6 +9,7 @@
#pragma once
#include <Includes.h>
#include "../BackendObject.h"
#include "MagmaSession.h"
#include <MG_Util/BackendLoaders/Vulkan/Loader.h>
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<MagmaSession> m_magmaSession;
};
// Single-source-of-truth helpers shared with the driver POST
@@ -35,8 +35,9 @@
#include <spirv_reflect.h>
namespace MobileGL::MG_Backend::DirectVulkan {
// Leak-at-exit storage; see GlobalObjects.cpp.
UniquePtr<VulkanRenderer>& pVulkanRenderer = *new UniquePtr<VulkanRenderer>();
// 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<Uint64> 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<Uint64> g_rendererGenerationSource{1};
struct RendererGeneration {
std::atomic<Uint64> value{g_rendererGenerationSource.fetch_add(1, std::memory_order_acq_rel)};
};
SessionLocal<RendererGeneration> 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<DrawCmdParam> params;
static thread_local Vector<DrawCmdParam> params;
params.clear();
params.resize(drawcount);
@@ -1347,7 +1356,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
const Uint32 indexSizeShift = static_cast<Uint32>(std::countr_zero(indexSize));
// TODO: allocate draw cmd buf elsewhere
static Vector<DrawIndexedCmdParam> params;
static thread_local Vector<DrawIndexedCmdParam> params;
params.clear();
params.resize(drawcount);
@@ -1763,23 +1772,24 @@ namespace MobileGL::MG_Backend::DirectVulkan {
}
namespace {
Optional<Int> g_requestedSwapInterval;
// Per session: each client asks for its own interval.
SessionLocal<Optional<Int>> g_requestedSwapInterval;
} // namespace
void SetSwapInterval(Int interval) {
g_requestedSwapInterval = interval;
*g_requestedSwapInterval = interval;
if (pVulkanRenderer) {
pVulkanRenderer->SetSwapInterval(interval);
}
}
Optional<Int> GetRequestedSwapInterval() {
return g_requestedSwapInterval;
return *g_requestedSwapInterval;
}
#if MOBILEGL_BUILD_DISAGGREGATED
void ForgetRequestedSwapInterval() {
g_requestedSwapInterval.reset();
g_requestedSwapInterval->reset();
}
#endif
@@ -9,10 +9,32 @@
#pragma once
#include <Includes.h>
#include <MG_Backend/BackendObject.h>
#include "MagmaSession.h"
#include "Renderer/VulkanRenderer.h"
namespace MobileGL::MG_Backend::DirectVulkan {
extern UniquePtr<VulkanRenderer>& 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<VulkanRenderer>& 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<VulkanRenderer>&& 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<UniquePtr<VulkanRenderer>> m_slot;
};
extern RendererSlot pVulkanRenderer;
// Generation of the live VulkanRenderer instance, mirroring DirectGLES's
// g_syncContextGeneration. BackendObject_DirectVulkan bumps it wherever
@@ -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 <atomic>
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<SessionLocalEntry>& Registry() {
static auto* registry = new Vector<SessionLocalEntry>();
return *registry;
}
std::atomic<Uint64> 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<Uint32>(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
@@ -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<T> 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 <Includes.h>
#include <cstddef>
#include <new>
#include <utility>
namespace MobileGL::MG_Backend::DirectVulkan {
class MagmaSession;
namespace Detail {
using SessionLocalConstruct = void* (*)();
using SessionLocalDestroy = void (*)(void*);
// Registers one SessionLocal<T> 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<void*> 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 T>
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<T*>(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<T*>(object); }
const Uint32 m_index;
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -199,7 +199,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
UnorderedMap<HashType, PipelineCacheEntry> 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
@@ -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<VkDevice> 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<Uint64, VkPipelineShaderStageCreateInfo> 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
@@ -19,6 +19,7 @@
#include "VertexInputStateBuilder.h"
#include "MG_State/GLState/VertexArrayState/VertexArrayObject.h"
#include <Includes.h>
#include <atomic>
#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<Uint64> 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
@@ -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<VkBufferManager*> 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<BufferMappingAccessBit> 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<BackendBufferResource>&& 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);
@@ -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<VkRenderPass> 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
@@ -66,8 +66,9 @@ namespace MobileGL::MG_Backend::DirectVulkan {
VkExtent2D swapchainExtent);
struct RenderPassEntry {
static inline VkDevice s_device;
static inline Vector<VkTextureManager::TextureResource*> s_textureResourcesScratch;
// Per Magma session (MagmaSession.h): each session's renderer is its own device.
static inline SessionLocal<VkDevice> s_device;
static inline thread_local Vector<VkTextureManager::TextureResource*> 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<ActiveRenderPassInfo> s_activeRenderPass;
static inline SessionLocal<Bool> s_hasActiveRenderPass;
static inline SessionLocal<VkClearManager*> s_clearManager;
static inline SessionLocal<VkTextureManager*> s_textureManager;
static inline SessionLocal<SwapchainObject*> s_swapchainObject;
static inline SessionLocal<VkRenderPassManager*> s_renderPassManager;
};
} // namespace MobileGL::MG_Backend::DirectVulkan
@@ -138,6 +138,6 @@ private:
UnorderedMap<Uint64, SamplerCacheEntry> 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
@@ -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<SampledReadStages> s_sampledReadStagesSlot;
static Uint32 ComputeFullMipLevelCount(const IntVec3& baseTexelSize) {
Int maxDimension = std::max<Int>(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);
@@ -9,6 +9,7 @@
#pragma once
#include "../VkIncludes.h"
#include "../MagmaSession.h"
#include <Includes.h>
#include <MG_State/GLState/FramebufferState/FramebufferObject.h>
#include <MG_State/GLState/TextureState/TextureObject.h>
@@ -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<VkDevice> s_device; // per Magma session (MagmaSession.h)
static inline SessionLocal<VmaAllocator> s_allocator;
};
struct SampledTextureSnapshot {
@@ -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<DynamicStateShadow> 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<Uint32>(std::max(frontStencil.Ref, 0));
const Uint32 backReference = static_cast<Uint32>(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<VkDeviceSize>(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<VkDeviceSize>(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<Int, Bool> formatBlendSupport;
static thread_local UnorderedMap<Int, Bool> formatBlendSupport;
auto blendSupportIt = formatBlendSupport.find(static_cast<Int>(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<VkMultiDrawInfoEXT> infos;
static thread_local Vector<VkMultiDrawInfoEXT> 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<VkMultiDrawIndexedInfoEXT> infos;
static thread_local Vector<VkMultiDrawIndexedInfoEXT> 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<DrawIndexedCmdParam> mergedParams;
static thread_local Vector<DrawIndexedCmdParam> 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) {
@@ -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
+5
View File
@@ -534,6 +534,11 @@ namespace MobileGL::MG_Remote::Server {
static_cast<unsigned long long>(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.