mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 10:03:23 +09:00
TLS: Espryt's bound context serial and per-context lookup cache share one thread_local block.
This commit is contained in:
@@ -12039,7 +12039,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Uint64 activeToken = 0;
|
||||
EGLContext boundContext = EGL_NO_CONTEXT;
|
||||
EGLSurface boundDraw = EGL_NO_SURFACE;
|
||||
Uint64 boundSerial = 0;
|
||||
// QueryCurrentSurfaceSize's memo (P15): the last answer, good while the session, the
|
||||
// registry epoch and the surface-size epoch all hold.
|
||||
Bool sizeMemoValid = false;
|
||||
@@ -12105,7 +12104,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// surface (one client context drawing to several windows) is a real switch.
|
||||
// (t_native.boundDraw)
|
||||
// ...and that context's lifetime serial, the key of its container objects.
|
||||
// (t_native.boundSerial)
|
||||
// (t_nativeContextThread.BoundSerial, DirectGLES.h)
|
||||
std::atomic<Uint64> g_nativeContextSerials{0};
|
||||
|
||||
std::mutex& PerNativeContextRegistryMutex() {
|
||||
@@ -12118,7 +12117,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
} // namespace
|
||||
|
||||
Uint64 CurrentNativeContextSerial() { return t_native.boundSerial; }
|
||||
|
||||
namespace {
|
||||
std::atomic<Uint64> g_perNativeContextEpoch{1};
|
||||
@@ -12126,12 +12124,19 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
Uint64 PerNativeContextEpoch() { return g_perNativeContextEpoch.load(std::memory_order_acquire); }
|
||||
void BumpPerNativeContextEpoch() { g_perNativeContextEpoch.fetch_add(1, std::memory_order_acq_rel); }
|
||||
|
||||
PerNativeContextBase::PerNativeContextBase() {
|
||||
namespace {
|
||||
std::atomic<Uint32> g_perNativeContextCacheSlots{0};
|
||||
}
|
||||
|
||||
PerNativeContextBase::PerNativeContextBase()
|
||||
: m_cacheSlot(g_perNativeContextCacheSlots.fetch_add(1, std::memory_order_relaxed) %
|
||||
NativeContextThreadState::kCacheEntries) {
|
||||
const std::lock_guard<std::mutex> lock(PerNativeContextRegistryMutex());
|
||||
PerNativeContextRegistry().push_back(this);
|
||||
}
|
||||
|
||||
PerNativeContextBase::~PerNativeContextBase() {
|
||||
BumpPerNativeContextEpoch();
|
||||
const std::lock_guard<std::mutex> lock(PerNativeContextRegistryMutex());
|
||||
auto& registry = PerNativeContextRegistry();
|
||||
registry.erase(std::remove(registry.begin(), registry.end(), this), registry.end());
|
||||
@@ -12872,7 +12877,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
if (t_native.boundContext == tuple->Context) {
|
||||
t_native.boundContext = EGL_NO_CONTEXT;
|
||||
t_native.boundDraw = EGL_NO_SURFACE;
|
||||
t_native.boundSerial = 0;
|
||||
t_nativeContextThread.BoundSerial = 0;
|
||||
}
|
||||
ForgetNativeContextObjects(tuple->Serial);
|
||||
tuple->Serial = 0;
|
||||
@@ -13476,7 +13481,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
InvalidateEglVerifiedStamp();
|
||||
t_native.boundContext = tuple.Context;
|
||||
t_native.boundDraw = session.Draw;
|
||||
t_native.boundSerial = tuple.Serial;
|
||||
t_nativeContextThread.BoundSerial = tuple.Serial;
|
||||
g_backendContextOwnerThread.store(std::this_thread::get_id(), std::memory_order_release);
|
||||
// The ops table may have been unregistered when a previous ES context was
|
||||
// destroyed (e.g. a probe context); re-register now that GL is usable.
|
||||
@@ -13522,7 +13527,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
InvalidateEglVerifiedStamp();
|
||||
t_native.boundContext = EGL_NO_CONTEXT;
|
||||
t_native.boundDraw = EGL_NO_SURFACE;
|
||||
t_native.boundSerial = 0;
|
||||
t_nativeContextThread.BoundSerial = 0;
|
||||
g_backendContextOwnerThread.store(std::thread::id{}, std::memory_order_release);
|
||||
return true;
|
||||
}
|
||||
@@ -14248,7 +14253,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
t_native.boundContext = EGL_NO_CONTEXT;
|
||||
t_native.boundDraw = EGL_NO_SURFACE;
|
||||
t_native.boundSerial = 0;
|
||||
t_nativeContextThread.BoundSerial = 0;
|
||||
for (auto* tuple : NativeContexts()) {
|
||||
if (tuple->SessionKey != sessionKey || tuple->Context == EGL_NO_CONTEXT) continue;
|
||||
ForgetNativeContextObjects(tuple->Serial);
|
||||
|
||||
@@ -327,7 +327,24 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
// The calling thread's current native context as a lifetime serial: assigned when the context
|
||||
// is created and never reused (a recycled EGLContext address is a new serial). 0 when none was
|
||||
// made current through MakeCurrent.
|
||||
Uint64 CurrentNativeContextSerial();
|
||||
//
|
||||
// P15: it lives in one constant-initialized thread_local with the PerNativeContext lookup
|
||||
// cache below, so the draw path's "this context's entry" question is one TLS address
|
||||
// computation, not two calls each paying their own.
|
||||
struct NativeContextThreadState {
|
||||
struct CacheEntry {
|
||||
const void* Owner = nullptr;
|
||||
Uint64 Serial = 0;
|
||||
Uint64 Epoch = 0;
|
||||
void* Entry = nullptr;
|
||||
};
|
||||
static constexpr Uint32 kCacheEntries = 8;
|
||||
Uint64 BoundSerial = 0;
|
||||
// One entry per PerNativeContext instance slot (instances sharing a slot evict each other).
|
||||
CacheEntry Cache[kCacheEntries]{};
|
||||
};
|
||||
inline constinit thread_local NativeContextThreadState t_nativeContextThread;
|
||||
inline Uint64 CurrentNativeContextSerial() { return t_nativeContextThread.BoundSerial; }
|
||||
|
||||
class PerNativeContextBase {
|
||||
public:
|
||||
@@ -340,6 +357,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
|
||||
protected:
|
||||
~PerNativeContextBase();
|
||||
// This instance's entry in NativeContextThreadState::Cache.
|
||||
const Uint32 m_cacheSlot;
|
||||
};
|
||||
|
||||
// Called where a native context is destroyed.
|
||||
@@ -354,12 +373,16 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
class PerNativeContext final : public PerNativeContextBase {
|
||||
public:
|
||||
T& Current() {
|
||||
const Uint64 serial = CurrentNativeContextSerial();
|
||||
NativeContextThreadState& thread = t_nativeContextThread;
|
||||
const Uint64 serial = thread.BoundSerial;
|
||||
// The draw path asks for the same entry over and over from one thread: a one-entry
|
||||
// thread-local cache, invalidated by any drop anywhere (rare).
|
||||
thread_local CurrentCache cache;
|
||||
// thread-local cache per instance, invalidated by any drop anywhere (rare) and by an
|
||||
// instance's destruction (so a later instance at the same address cannot hit).
|
||||
NativeContextThreadState::CacheEntry& cache = thread.Cache[m_cacheSlot];
|
||||
const Uint64 epoch = PerNativeContextEpoch();
|
||||
if (cache.Owner == this && cache.Serial == serial && cache.Epoch == epoch) return *cache.Entry;
|
||||
if (cache.Owner == this && cache.Serial == serial && cache.Epoch == epoch) {
|
||||
return *static_cast<T*>(cache.Entry);
|
||||
}
|
||||
const std::lock_guard<std::mutex> lock(m_mutex);
|
||||
T& entry = m_bySerial[serial]; // node-based: the reference survives other contexts' inserts
|
||||
cache = {this, serial, epoch, &entry};
|
||||
@@ -382,12 +405,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
|
||||
}
|
||||
|
||||
private:
|
||||
struct CurrentCache {
|
||||
const PerNativeContext* Owner = nullptr;
|
||||
Uint64 Serial = 0;
|
||||
Uint64 Epoch = 0;
|
||||
T* Entry = nullptr;
|
||||
};
|
||||
std::mutex m_mutex;
|
||||
std::unordered_map<Uint64, T> m_bySerial;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user