| // | |
| // Copyright 2023 The ANGLE Project Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // | |
| // GlobalMutex.cpp: Defines Global Mutex and utilities. | |
| #include "libANGLE/GlobalMutex.h" | |
| #include <atomic> | |
| #include <memory> | |
| #include <type_traits> | |
| #include "common/debug.h" | |
| #include "common/system_utils.h" | |
| namespace egl | |
| { | |
| namespace priv | |
| { | |
| using GlobalMutexType = std::mutex; | |
| #if !defined(ANGLE_ENABLE_ASSERTS) && !defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION) | |
| // Default version. | |
| class GlobalMutex final : angle::NonCopyable | |
| { | |
| public: | |
| ANGLE_INLINE void lock() { mMutex.lock(); } | |
| ANGLE_INLINE void unlock() { mMutex.unlock(); } | |
| protected: | |
| GlobalMutexType mMutex; | |
| }; | |
| #endif | |
| #if defined(ANGLE_ENABLE_ASSERTS) && !defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION) | |
| // Debug version. | |
| class GlobalMutex final : angle::NonCopyable | |
| { | |
| public: | |
| ANGLE_INLINE void lock() | |
| { | |
| const angle::ThreadId threadId = angle::GetCurrentThreadId(); | |
| ASSERT(getOwnerThreadId() != threadId); | |
| mMutex.lock(); | |
| ASSERT(getOwnerThreadId() == angle::InvalidThreadId()); | |
| mOwnerThreadId.store(threadId, std::memory_order_relaxed); | |
| } | |
| ANGLE_INLINE void unlock() | |
| { | |
| ASSERT(getOwnerThreadId() == angle::GetCurrentThreadId()); | |
| mOwnerThreadId.store(angle::InvalidThreadId(), std::memory_order_relaxed); | |
| mMutex.unlock(); | |
| } | |
| private: | |
| ANGLE_INLINE angle::ThreadId getOwnerThreadId() const | |
| { | |
| return mOwnerThreadId.load(std::memory_order_relaxed); | |
| } | |
| GlobalMutexType mMutex; | |
| std::atomic<angle::ThreadId> mOwnerThreadId{angle::InvalidThreadId()}; | |
| }; | |
| #endif // defined(ANGLE_ENABLE_ASSERTS) && !defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION) | |
| #if defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION) | |
| // Recursive version. | |
| class GlobalMutex final : angle::NonCopyable | |
| { | |
| public: | |
| ANGLE_INLINE void lock() | |
| { | |
| const angle::ThreadId threadId = angle::GetCurrentThreadId(); | |
| if (ANGLE_UNLIKELY(!mMutex.try_lock())) | |
| { | |
| if (ANGLE_UNLIKELY(getOwnerThreadId() == threadId)) | |
| { | |
| ASSERT(mLockLevel > 0); | |
| ++mLockLevel; | |
| return; | |
| } | |
| mMutex.lock(); | |
| } | |
| ASSERT(getOwnerThreadId() == angle::InvalidThreadId()); | |
| ASSERT(mLockLevel == 0); | |
| mOwnerThreadId.store(threadId, std::memory_order_relaxed); | |
| mLockLevel = 1; | |
| } | |
| ANGLE_INLINE void unlock() | |
| { | |
| ASSERT(getOwnerThreadId() == angle::GetCurrentThreadId()); | |
| ASSERT(mLockLevel > 0); | |
| if (ANGLE_LIKELY(--mLockLevel == 0)) | |
| { | |
| mOwnerThreadId.store(angle::InvalidThreadId(), std::memory_order_relaxed); | |
| mMutex.unlock(); | |
| } | |
| } | |
| private: | |
| ANGLE_INLINE angle::ThreadId getOwnerThreadId() const | |
| { | |
| return mOwnerThreadId.load(std::memory_order_relaxed); | |
| } | |
| GlobalMutexType mMutex; | |
| std::atomic<angle::ThreadId> mOwnerThreadId{angle::InvalidThreadId()}; | |
| uint32_t mLockLevel = 0; | |
| }; | |
| #endif // defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION) | |
| namespace | |
| { | |
| #if defined(ANGLE_ENABLE_GLOBAL_MUTEX_LOAD_TIME_ALLOCATE) | |
| # if !ANGLE_HAS_ATTRIBUTE_CONSTRUCTOR || !ANGLE_HAS_ATTRIBUTE_DESTRUCTOR | |
| # error \ | |
| "'angle_enable_global_mutex_load_time_allocate' " \ | |
| "requires constructor/destructor compiler atributes." | |
| # endif | |
| GlobalMutex *g_MutexPtr = nullptr; | |
| GlobalMutex *g_EGLSyncMutexPtr = nullptr; | |
| void ANGLE_CONSTRUCTOR AllocateGlobalMutex() | |
| { | |
| ASSERT(g_MutexPtr == nullptr); | |
| g_MutexPtr = new GlobalMutex(); | |
| ASSERT(g_EGLSyncMutexPtr == nullptr); | |
| g_EGLSyncMutexPtr = new GlobalMutex(); | |
| } | |
| void ANGLE_DESTRUCTOR DeallocateGlobalMutex() | |
| { | |
| SafeDelete(g_MutexPtr); | |
| SafeDelete(g_EGLSyncMutexPtr); | |
| } | |
| #else | |
| ANGLE_REQUIRE_CONSTANT_INIT std::atomic<GlobalMutex *> g_Mutex(nullptr); | |
| ANGLE_REQUIRE_CONSTANT_INIT std::atomic<GlobalMutex *> g_EGLSyncMutex(nullptr); | |
| static_assert(std::is_trivially_destructible<decltype(g_Mutex)>::value, | |
| "global mutex is not trivially destructible"); | |
| static_assert(std::is_trivially_destructible<decltype(g_EGLSyncMutex)>::value, | |
| "global EGL Sync mutex is not trivially destructible"); | |
| GlobalMutex *AllocateGlobalMutexImpl(std::atomic<GlobalMutex *> *globalMutex) | |
| { | |
| GlobalMutex *currentMutex = nullptr; | |
| std::unique_ptr<GlobalMutex> newMutex(new GlobalMutex()); | |
| do | |
| { | |
| if (globalMutex->compare_exchange_weak(currentMutex, newMutex.get())) | |
| { | |
| return newMutex.release(); | |
| } | |
| } while (currentMutex == nullptr); | |
| return currentMutex; | |
| } | |
| GlobalMutex *GetGlobalMutex() | |
| { | |
| GlobalMutex *mutex = g_Mutex.load(); | |
| return mutex != nullptr ? mutex : AllocateGlobalMutexImpl(&g_Mutex); | |
| } | |
| GlobalMutex *GetGlobalEGLSyncObjectMutex() | |
| { | |
| GlobalMutex *mutex = g_EGLSyncMutex.load(); | |
| return mutex != nullptr ? mutex : AllocateGlobalMutexImpl(&g_EGLSyncMutex); | |
| } | |
| #endif | |
| } // anonymous namespace | |
| // ScopedGlobalMutexLock implementation. | |
| #if defined(ANGLE_ENABLE_GLOBAL_MUTEX_LOAD_TIME_ALLOCATE) | |
| template <GlobalMutexChoice mutexChoice> | |
| ScopedGlobalMutexLock<mutexChoice>::ScopedGlobalMutexLock() | |
| { | |
| switch (mutexChoice) | |
| { | |
| case GlobalMutexChoice::EGL: | |
| g_MutexPtr->lock(); | |
| break; | |
| case GlobalMutexChoice::Sync: | |
| g_EGLSyncMutexPtr->lock(); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| } | |
| template <GlobalMutexChoice mutexChoice> | |
| ScopedGlobalMutexLock<mutexChoice>::~ScopedGlobalMutexLock() | |
| { | |
| switch (mutexChoice) | |
| { | |
| case GlobalMutexChoice::EGL: | |
| g_MutexPtr->unlock(); | |
| break; | |
| case GlobalMutexChoice::Sync: | |
| g_EGLSyncMutexPtr->unlock(); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| } | |
| #else | |
| template <GlobalMutexChoice mutexChoice> | |
| ScopedGlobalMutexLock<mutexChoice>::ScopedGlobalMutexLock() | |
| { | |
| switch (mutexChoice) | |
| { | |
| case GlobalMutexChoice::EGL: | |
| mMutex = GetGlobalMutex(); | |
| break; | |
| case GlobalMutexChoice::Sync: | |
| mMutex = GetGlobalEGLSyncObjectMutex(); | |
| break; | |
| default: | |
| UNREACHABLE(); | |
| break; | |
| } | |
| mMutex->lock(); | |
| } | |
| template <GlobalMutexChoice mutexChoice> | |
| ScopedGlobalMutexLock<mutexChoice>::~ScopedGlobalMutexLock() | |
| { | |
| mMutex->unlock(); | |
| } | |
| #endif | |
| template class ScopedGlobalMutexLock<GlobalMutexChoice::EGL>; | |
| template class ScopedGlobalMutexLock<GlobalMutexChoice::Sync>; | |
| } // namespace priv | |
| // ScopedOptionalGlobalMutexLock implementation. | |
| ScopedOptionalGlobalMutexLock::ScopedOptionalGlobalMutexLock(bool enabled) | |
| { | |
| if (enabled) | |
| { | |
| #if defined(ANGLE_ENABLE_GLOBAL_MUTEX_LOAD_TIME_ALLOCATE) | |
| mMutex = priv::g_MutexPtr; | |
| #else | |
| mMutex = priv::GetGlobalMutex(); | |
| #endif | |
| mMutex->lock(); | |
| } | |
| else | |
| { | |
| mMutex = nullptr; | |
| } | |
| } | |
| ScopedOptionalGlobalMutexLock::~ScopedOptionalGlobalMutexLock() | |
| { | |
| if (mMutex != nullptr) | |
| { | |
| mMutex->unlock(); | |
| } | |
| } | |
| // Global functions. | |
| #if defined(ANGLE_PLATFORM_WINDOWS) && !defined(ANGLE_STATIC) | |
| # if defined(ANGLE_ENABLE_GLOBAL_MUTEX_LOAD_TIME_ALLOCATE) | |
| # error "'angle_enable_global_mutex_load_time_allocate' is not supported in Windows DLL." | |
| # endif | |
| void AllocateGlobalMutex() | |
| { | |
| (void)priv::AllocateGlobalMutexImpl(&priv::g_Mutex); | |
| (void)priv::AllocateGlobalMutexImpl(&priv::g_EGLSyncMutex); | |
| } | |
| void DeallocateGlobalMutexImpl(std::atomic<priv::GlobalMutex *> *globalMutex) | |
| { | |
| priv::GlobalMutex *mutex = globalMutex->exchange(nullptr); | |
| if (mutex != nullptr) | |
| { | |
| { | |
| // Wait for the mutex to become released by other threads before deleting. | |
| std::lock_guard<priv::GlobalMutex> lock(*mutex); | |
| } | |
| delete mutex; | |
| } | |
| } | |
| void DeallocateGlobalMutex() | |
| { | |
| DeallocateGlobalMutexImpl(&priv::g_Mutex); | |
| DeallocateGlobalMutexImpl(&priv::g_EGLSyncMutex); | |
| } | |
| #endif | |
| } // namespace egl |