TLS: Espryt's bound context serial and per-context lookup cache share one thread_local block.

This commit is contained in:
2026-10-10 04:14:13 -04:00
parent 892f4609be
commit fb0502886b
2 changed files with 41 additions and 19 deletions
+13 -8
View File
@@ -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);
+28 -11
View File
@@ -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;
};