forked from google/angle
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement new "Global Mutex" functionality.
This CL improves performance compared to the existing code as well as allows using non-std mutex implementations. Also acts as a base for future changes. CL adds new build option: angle_enable_global_mutex_recursion = is_android && angle_enable_vulkan "mutex_recursion" work same way as `std::recursive_mutex` before. It will help in situations when Vulkan API may return back to the ANGLE. For example: RenderDoc layer EGL deadlock. Automatic loading of "libVkLayer_GLES_RenderDoc.so" layer causes deadlock in EGL. Recursion stack: #1 pc 000000000029ea80 /vendor/lib64/egl/libGLESv2_angle.so (egl::GlobalMutexHelper::lock(int)+596) #2 pc 000000000029c59c /vendor/lib64/egl/libGLESv2_angle.so (EGL_GetError+32) #4 pc 0000000000062368 /system/lib64/libEGL.so (eglQueryString+20) #5 pc 0000000000508fec /data/local/debug/vulkan/libVkLayer_GLES_RenderDoc.so google#20 pc 0000000000016690 /system/lib64/libvulkan.so (vulkan::api::EnumerateInstanceLayerProperties(unsigned int*, VkLayerProperties*)+40) google#21 pc 00000000005aa030 /vendor/lib64/egl/libGLESv2_angle.so (rx::RendererVk::initialize(rx::DisplayVk*, egl::Display*, char const*, char const*)+292) google#26 pc 000000000029c7e8 /vendor/lib64/egl/libGLESv2_angle.so (EGL_Initialize+192) Additionally, recursive mutex will partially solve Android SurfaceTexture deadlock (angleproject:4354). Some performance numbers for 1000'000 `eglGetError()` calls. Mutex Time (ms.) Android S906B egl::GetGlobalMutex()(std::recursive_mutex) 41.4 (Default) GlobalMutex (std::recursive_mutex) 39.1 (Recursive) GlobalMutex (std::mutex) 34.9 (Debug) GlobalMutex (std::mutex) 34.7 (Default) GlobalMutex (std::mutex) 34.4 Windows egl::GetGlobalMutex()(std::recursive_mutex) 20.5 (Default) GlobalMutex (std::recursive_mutex) 20.0 (Recursive) GlobalMutex (std::mutex) 21.9 (Debug) GlobalMutex (std::mutex) 20.5 (Default) GlobalMutex (std::mutex) 19.9 Note: Recursive GlobalMutex enabled only for Android Vulkan by default. Original fix: https://chromium-review.googlesource.com/c/angle/angle/+/2029218 Bug: angleproject:8101 Bug: angleproject:4354 Test: angle_unittests --gtest_filter=GlobalMutexTest.* Change-Id: I9e9d9b5c598ad1177ffa147ea690bd955946a712 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/4401940 Commit-Queue: Igor Nazarov <[email protected]> Reviewed-by: Charlie Lao <[email protected]> Reviewed-by: Shahbaz Youssefi <[email protected]>
- Loading branch information
1 parent
22b5159
commit b22124b
Showing
8 changed files
with
361 additions
and
64 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
// | ||
// 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 "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 priv | ||
|
||
namespace | ||
{ | ||
ANGLE_REQUIRE_CONSTANT_INIT std::atomic<priv::GlobalMutex *> g_Mutex(nullptr); | ||
static_assert(std::is_trivially_destructible<decltype(g_Mutex)>::value, | ||
"global mutex is not trivially destructible"); | ||
|
||
priv::GlobalMutex *AllocateGlobalMutexImpl() | ||
{ | ||
priv::GlobalMutex *currentMutex = nullptr; | ||
std::unique_ptr<priv::GlobalMutex> newMutex(new priv::GlobalMutex()); | ||
if (g_Mutex.compare_exchange_strong(currentMutex, newMutex.get(), std::memory_order_release, | ||
std::memory_order_acquire)) | ||
{ | ||
return newMutex.release(); | ||
} | ||
return currentMutex; | ||
} | ||
|
||
priv::GlobalMutex *GetGlobalMutex() | ||
{ | ||
priv::GlobalMutex *mutex = g_Mutex.load(std::memory_order_acquire); | ||
return mutex != nullptr ? mutex : AllocateGlobalMutexImpl(); | ||
} | ||
} // anonymous namespace | ||
|
||
// ScopedGlobalMutexLock implementation. | ||
ScopedGlobalMutexLock::ScopedGlobalMutexLock() : mMutex(*GetGlobalMutex()) | ||
{ | ||
mMutex.lock(); | ||
} | ||
|
||
ScopedGlobalMutexLock::~ScopedGlobalMutexLock() | ||
{ | ||
mMutex.unlock(); | ||
} | ||
|
||
// ScopedOptionalGlobalMutexLock implementation. | ||
ScopedOptionalGlobalMutexLock::ScopedOptionalGlobalMutexLock(bool enabled) | ||
{ | ||
if (enabled) | ||
{ | ||
mMutex = GetGlobalMutex(); | ||
mMutex->lock(); | ||
} | ||
else | ||
{ | ||
mMutex = nullptr; | ||
} | ||
} | ||
|
||
ScopedOptionalGlobalMutexLock::~ScopedOptionalGlobalMutexLock() | ||
{ | ||
if (mMutex != nullptr) | ||
{ | ||
mMutex->unlock(); | ||
} | ||
} | ||
|
||
// Global functions. | ||
#if defined(ANGLE_PLATFORM_WINDOWS) && !defined(ANGLE_STATIC) | ||
void AllocateGlobalMutex() | ||
{ | ||
(void)AllocateGlobalMutexImpl(); | ||
} | ||
|
||
void DeallocateGlobalMutex() | ||
{ | ||
priv::GlobalMutex *mutex = g_Mutex.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; | ||
} | ||
} | ||
#endif | ||
|
||
} // namespace egl |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// | ||
// 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.h: Defines Global Mutex and utilities. | ||
|
||
#ifndef LIBANGLE_GLOBAL_MUTEX_H_ | ||
#define LIBANGLE_GLOBAL_MUTEX_H_ | ||
|
||
#include "common/angleutils.h" | ||
|
||
namespace egl | ||
{ | ||
namespace priv | ||
{ | ||
class GlobalMutex; | ||
} // namespace priv | ||
|
||
class [[nodiscard]] ScopedGlobalMutexLock final : angle::NonCopyable | ||
{ | ||
public: | ||
ScopedGlobalMutexLock(); | ||
~ScopedGlobalMutexLock(); | ||
|
||
private: | ||
priv::GlobalMutex &mMutex; | ||
}; | ||
|
||
// For Context protection where lock is optional. Works slower than ScopedGlobalMutexLock. | ||
class [[nodiscard]] ScopedOptionalGlobalMutexLock final : angle::NonCopyable | ||
{ | ||
public: | ||
explicit ScopedOptionalGlobalMutexLock(bool enabled); | ||
~ScopedOptionalGlobalMutexLock(); | ||
|
||
private: | ||
priv::GlobalMutex *mMutex; | ||
}; | ||
|
||
#if defined(ANGLE_PLATFORM_WINDOWS) && !defined(ANGLE_STATIC) | ||
void AllocateGlobalMutex(); | ||
void DeallocateGlobalMutex(); | ||
#endif | ||
|
||
} // namespace egl | ||
|
||
#endif // LIBANGLE_GLOBAL_MUTEX_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
// | ||
// 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_unittest: | ||
// Tests of the Scoped<*>GlobalMutexLock classes | ||
// | ||
|
||
#include <gtest/gtest.h> | ||
|
||
#include "libANGLE/GlobalMutex.h" | ||
|
||
namespace | ||
{ | ||
template <class ScopedGlobalLockT, class... Args> | ||
void runBasicGlobalMutexTest(bool expectToPass, Args &&...args) | ||
{ | ||
constexpr size_t kThreadCount = 16; | ||
constexpr size_t kIterationCount = 50'000; | ||
|
||
std::array<std::thread, kThreadCount> threads; | ||
|
||
std::mutex mutex; | ||
std::condition_variable condVar; | ||
size_t readyCount = 0; | ||
|
||
std::atomic<size_t> testVar; | ||
|
||
for (size_t i = 0; i < kThreadCount; ++i) | ||
{ | ||
threads[i] = std::thread([&]() { | ||
{ | ||
std::unique_lock<std::mutex> lock(mutex); | ||
++readyCount; | ||
if (readyCount < kThreadCount) | ||
{ | ||
condVar.wait(lock, [&]() { return readyCount == kThreadCount; }); | ||
} | ||
else | ||
{ | ||
condVar.notify_all(); | ||
} | ||
} | ||
for (size_t j = 0; j < kIterationCount; ++j) | ||
{ | ||
ScopedGlobalLockT lock(std::forward<Args>(args)...); | ||
const int local = testVar.load(std::memory_order_relaxed); | ||
const int newValue = local + 1; | ||
testVar.store(newValue, std::memory_order_relaxed); | ||
} | ||
}); | ||
} | ||
|
||
for (size_t i = 0; i < kThreadCount; ++i) | ||
{ | ||
threads[i].join(); | ||
} | ||
|
||
if (expectToPass) | ||
{ | ||
EXPECT_EQ(testVar.load(), kThreadCount * kIterationCount); | ||
} | ||
else | ||
{ | ||
EXPECT_NE(testVar.load(), kThreadCount * kIterationCount); | ||
} | ||
} | ||
|
||
// Tests basic usage of ScopedGlobalMutexLock. | ||
TEST(GlobalMutexTest, ScopedGlobalMutexLock) | ||
{ | ||
runBasicGlobalMutexTest<egl::ScopedGlobalMutexLock>(true); | ||
} | ||
|
||
// Tests basic usage of ScopedOptionalGlobalMutexLock (Enabled). | ||
TEST(GlobalMutexTest, ScopedOptionalGlobalMutexLockEnabled) | ||
{ | ||
runBasicGlobalMutexTest<egl::ScopedOptionalGlobalMutexLock>(true, true); | ||
} | ||
|
||
// Tests basic usage of ScopedOptionalGlobalMutexLock (Disabled). | ||
TEST(GlobalMutexTest, ScopedOptionalGlobalMutexLockDisabled) | ||
{ | ||
runBasicGlobalMutexTest<egl::ScopedOptionalGlobalMutexLock>(false, false); | ||
} | ||
|
||
#if defined(ANGLE_ENABLE_GLOBAL_MUTEX_RECURSION) | ||
// Tests that ScopedGlobalMutexLock can be recursively locked. | ||
TEST(GlobalMutexTest, RecursiveScopedGlobalMutexLock) | ||
{ | ||
egl::ScopedGlobalMutexLock lock; | ||
egl::ScopedGlobalMutexLock lock2; | ||
} | ||
|
||
// Tests that ScopedOptionalGlobalMutexLock can be recursively locked. | ||
TEST(GlobalMutexTest, RecursiveScopedOptionalGlobalMutexLock) | ||
{ | ||
egl::ScopedOptionalGlobalMutexLock lock(true); | ||
egl::ScopedOptionalGlobalMutexLock lock2(true); | ||
} | ||
#endif | ||
|
||
} // anonymous namespace |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.