mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[Fix] (DirectVulkan): give each served session its own renderer and Magma state
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user