From 0b314ddae7305cdca429dda0f720a57e5c085a4b Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Fri, 2 Oct 2026 01:38:20 -0400 Subject: [PATCH] [Fix] (P14): serialize sessions on one backend turn and give each client surface its own native surface --- MobileGL/MG_Backend/BackendObject.cpp | 42 ++- MobileGL/MG_Backend/BackendObject.h | 14 + .../DirectGLES/BackendObject_DirectGLES.cpp | 100 ++++++- .../DirectGLES/BackendObject_DirectGLES.h | 12 + MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 268 ++++++++++++++++-- MobileGL/MG_Backend/DirectGLES/DirectGLES.h | 25 ++ MobileGL/MG_Backend/DirectGLES/Managers.cpp | 24 +- MobileGL/MG_Backend/DirectGLES/Managers.h | 3 + MobileGL/MG_Remote/Server/ServerLoop.cpp | 77 +++++ scripts/ci/fatal_census_baseline.json | 4 +- 10 files changed, 530 insertions(+), 39 deletions(-) diff --git a/MobileGL/MG_Backend/BackendObject.cpp b/MobileGL/MG_Backend/BackendObject.cpp index a7034b88..e7250c18 100644 --- a/MobileGL/MG_Backend/BackendObject.cpp +++ b/MobileGL/MG_Backend/BackendObject.cpp @@ -200,12 +200,15 @@ namespace MobileGL::MG_Backend { Bool BackendObject::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) { const std::lock_guard lock(m_eglStateMutex); auto surfaceIt = m_eglSurfaces.find(surface); - if (surfaceIt == m_eglSurfaces.end() || surfaceIt->second.Kind != SurfaceKind::Window) { + if (surfaceIt == m_eglSurfaces.end() || surfaceIt->second.Kind == SurfaceKind::None) { MGLOG_E("ResizeEGLWindowSurface failed: no window surface is initialized"); return false; } + // A pbuffer-backed window (a Wayland window presented through wl_shm) resizes too. surfaceIt->second.Window.Width = width; surfaceIt->second.Window.Height = height; + surfaceIt->second.Width = static_cast(std::max(width, 1)); + surfaceIt->second.Height = static_cast(std::max(height, 1)); if (m_eglSurface == surface) { m_windowHandle.Width = width; m_windowHandle.Height = height; @@ -283,6 +286,7 @@ namespace MobileGL::MG_Backend { return true; } + m_activatingSurface = surface; if (surfaceState->Kind == SurfaceKind::Window) { SetWindowHandle(surfaceState->Window); if (!InitWindowSurface()) { @@ -302,8 +306,14 @@ namespace MobileGL::MG_Backend { m_eglSurface = surface; m_eglSurfaceInitialized = true; m_eglSurfaceKind = surfaceState->Kind; - m_eglCurrentThreads.clear(); - m_backendCapabilitiesInitialized = false; + // NOT m_eglCurrentThreads.clear(): another thread's current context and surface are its + // own (a multi-window client renders each window on its own thread), and wiping them made + // that thread's next eglSwapBuffers fail with "no current context attached". + auto activatedIt = m_eglSurfaces.find(surface); + if (activatedIt != m_eglSurfaces.end() && !activatedIt->second.Activated) { + activatedIt->second.Activated = true; + m_backendCapabilitiesInitialized = false; + } return true; } @@ -370,6 +380,14 @@ namespace MobileGL::MG_Backend { m_eglCurrentThreads.clear(); } + void BackendObject::ForgetActiveEGLSurface() { + const std::lock_guard lock(m_eglStateMutex); + m_eglSurfaceInitialized = false; + m_eglSurfaceKind = SurfaceKind::None; + m_eglSurface = EGL_NO_SURFACE; + m_windowHandle = {}; + } + Bool BackendObject::SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) { const std::lock_guard lock(m_eglStateMutex); if (!m_eglDisplayInitialized || m_eglDisplay != dpy) { @@ -386,10 +404,16 @@ namespace MobileGL::MG_Backend { MGLOG_E("SwapEGLBuffers failed: draw surface is not current on this thread"); return false; } - if (!m_eglSurfaceInitialized || draw == EGL_NO_SURFACE || draw != m_eglSurface) { + if (draw == EGL_NO_SURFACE || !GetRegisteredEGLSurface(draw)) { MGLOG_E("SwapEGLBuffers failed: invalid draw surface"); return false; } + // The calling thread's surface, which another thread's make-current may have moved the + // backend off: bring it back before presenting it. + if ((!m_eglSurfaceInitialized || draw != m_eglSurface) && !ActivateEGLSurface(draw)) { + MGLOG_E("SwapEGLBuffers failed: the draw surface could not be re-activated"); + return false; + } const auto& backendFunctions = GetBackendFunctions(); if (!backendFunctions.Present) { @@ -430,8 +454,9 @@ namespace MobileGL::MG_Backend { m_eglSurfaces.erase(surfaceIt); if (m_eglSurface == surface) { OnEGLSurfaceReleased(surface); - ResetEGLRuntimeState(); + ForgetActiveEGLSurface(); } + OnEGLSurfaceForgotten(surface); } void BackendObject::ReleaseEGLCurrentThread(const std::thread::id& threadKey) { @@ -462,8 +487,9 @@ namespace MobileGL::MG_Backend { m_eglSurfaces.erase(surfaceIt); if (m_eglSurface == surface) { OnEGLSurfaceReleased(surface); - ResetEGLRuntimeState(); + ForgetActiveEGLSurface(); } + OnEGLSurfaceForgotten(surface); } void BackendObject::ReleaseEGLResources() { @@ -496,4 +522,8 @@ namespace MobileGL::MG_Backend { void BackendObject::OnEGLSurfaceReleased(EGLSurface surface) { (void)surface; } + + void BackendObject::OnEGLSurfaceForgotten(EGLSurface surface) { + (void)surface; + } } // namespace MobileGL::MG_Backend diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 94639b31..c8735004 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -629,18 +629,28 @@ namespace MobileGL { struct EGLSurfaceState { SurfaceKind Kind = SurfaceKind::None; Bool DestroyPending = false; + // Activated at least once: a re-activation (a multi-window client switching back) + // is a re-bind and does not re-run the backend's capability probe. + Bool Activated = false; WindowHandle Window; EGLint Width = 1; EGLint Height = 1; }; void ResetEGLRuntimeState(); + // ResetEGLRuntimeState for a released ACTIVE surface: the backend has no active surface + // any more, but every OTHER thread's current context and surface stay as they were (a + // multi-window client closing one popup must not unbind its other windows' threads). + void ForgetActiveEGLSurface(); Bool RegisterEGLWindowSurface(EGLSurface surface, const WindowHandle& handle); Bool RegisterEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height); const EGLSurfaceState* GetRegisteredEGLSurface(EGLSurface surface) const; Bool ActivateEGLSurface(EGLSurface surface); virtual Bool InitPbufferSurface(EGLint width, EGLint height); virtual void OnEGLSurfaceReleased(EGLSurface surface); + // Every registered surface the base class drops, current or not, after it is gone from + // m_eglSurfaces. A backend that keeps a native surface per client surface frees it here. + virtual void OnEGLSurfaceForgotten(EGLSurface surface); FormatCapabilityCache& MutableFormatCapabilities(); mutable std::recursive_mutex m_eglStateMutex; @@ -648,6 +658,10 @@ namespace MobileGL { WindowHandle m_windowHandle; EGLDisplay m_eglDisplay = EGL_NO_DISPLAY; EGLSurface m_eglSurface = EGL_NO_SURFACE; + // The client surface ActivateEGLSurface is bringing up while it calls InitWindowSurface / + // InitPbufferSurface, which take no surface argument: a backend that maps client surfaces + // to native ones reads it to re-bind an existing native surface instead of making another. + EGLSurface m_activatingSurface = EGL_NO_SURFACE; Bool m_eglDisplayInitialized = false; Bool m_eglSurfaceInitialized = false; Bool m_backendCapabilitiesInitialized = false; diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp index ddd7e665..50a904b7 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.cpp @@ -906,13 +906,45 @@ namespace MobileGL::MG_Backend::DirectGLES { #endif } + Bool BackendObject_DirectGLES::RebindNativeSurfaceFor(EGLSurface surface) { + const auto it = m_nativeSurfaces.find(surface); + if (it == m_nativeSurfaces.end()) return false; + if (DirectGLES::BindSessionSurface(it->second)) return true; + // The session no longer owns it (destroyed with a window loss): forget and recreate. + m_nativeSurfaces.erase(it); + return false; + } + + void BackendObject_DirectGLES::RestoreSurfaceAfterCreation(EGLSurface previousNativeDraw) { + // The ACTIVE CONTEXT's own surface, exactly: the creation's bind attached the new surface to + // whichever context the session had bound (often another window's thread), and putting back + // "the surface activated last" instead re-attached THAT context to a third window's surface - + // a desktop drawing into and reading back from the panel's pbuffer. + if (previousNativeDraw == EGL_NO_SURFACE) return; + if (!DirectGLES::BindSessionSurface(previousNativeDraw)) return; + for (const auto& [client, native] : m_nativeSurfaces) { + if (native != previousNativeDraw) continue; + const auto state = m_eglSurfaces.find(client); + if (state == m_eglSurfaces.end()) break; + m_eglSurface = client; + m_eglSurfaceInitialized = true; + m_eglSurfaceKind = state->second.Kind; + if (state->second.Kind == SurfaceKind::Window) m_windowHandle = state->second.Window; + break; + } + } + Bool BackendObject_DirectGLES::InitWindowSurface() { + if (RebindNativeSurfaceFor(m_activatingSurface)) return true; // Only use EGL for now auto nativeWindow = reinterpret_cast(m_windowHandle.Handle); if (!DirectGLES::InitWindowSurface(nativeWindow)) { MGLOG_E("Failed to initialize window surface for DirectGLES backend"); return false; } + if (m_activatingSurface != EGL_NO_SURFACE) { + m_nativeSurfaces[m_activatingSurface] = DirectGLES::CurrentSessionDrawSurface(); + } return true; } @@ -986,6 +1018,8 @@ namespace MobileGL::MG_Backend::DirectGLES { // and its objects. ResetEGLRuntimeState still runs because the base class's per-surface // bookkeeping (which surface is active, which threads made it current) does describe the // surface that is being replaced. + const EGLSurface previous = m_eglSurfaceInitialized ? m_eglSurface : EGL_NO_SURFACE; + const EGLSurface previousNativeDraw = DirectGLES::CurrentContextDrawSurface(); if (m_eglSurfaceInitialized) { ResetEGLRuntimeState(); } @@ -995,7 +1029,12 @@ namespace MobileGL::MG_Backend::DirectGLES { // the first virtual tuple adopts that bind. On failure nothing is known to be bound. The // pull arm below is the original statement, byte for byte (G1). const Bool created = BackendObject::CreateEGLWindowSurface(surface, handle); - if (created) { + if (created && previous != EGL_NO_SURFACE && previous != surface) { + // Another surface was current: the creation must not leave the context that held it + // drawing to the new one (P14 multi-window clients). + RestoreSurfaceAfterCreation(previousNativeDraw); + NoteNativeContextGone(); + } else if (created) { NoteNativeContextFreshFromSurfaceCreation(); } else { NoteNativeContextGone(); @@ -1019,6 +1058,8 @@ namespace MobileGL::MG_Backend::DirectGLES { // P14 S4: as for the window surface above - the native context is the (session, context // token) tuple's and survives a surface switch. This used to be DestroyEGLContext(). + const EGLSurface previous = m_eglSurfaceInitialized ? m_eglSurface : EGL_NO_SURFACE; + const EGLSurface previousNativeDraw = DirectGLES::CurrentContextDrawSurface(); if (m_eglSurfaceInitialized) { ResetEGLRuntimeState(); } @@ -1027,7 +1068,10 @@ namespace MobileGL::MG_Backend::DirectGLES { // ID-54 / ID-67: as for the window surface - InitPbufferSurface bound natively. The pull // arm below is the original statement, byte for byte (G1). const Bool created = BackendObject::CreateEGLPbufferSurface(surface, width, height); - if (created) { + if (created && previous != EGL_NO_SURFACE && previous != surface) { + RestoreSurfaceAfterCreation(previousNativeDraw); + NoteNativeContextGone(); + } else if (created) { NoteNativeContextFreshFromSurfaceCreation(); } else { NoteNativeContextGone(); @@ -1038,8 +1082,34 @@ namespace MobileGL::MG_Backend::DirectGLES { #endif } + Bool BackendObject_DirectGLES::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) { + const std::lock_guard lock(m_eglStateMutex); + const auto state = m_eglSurfaces.find(surface); + const auto native = m_nativeSurfaces.find(surface); + if (state == m_eglSurfaces.end() || state->second.Kind != SurfaceKind::Pbuffer || native == m_nativeSurfaces.end()) { + return BackendObject::ResizeEGLWindowSurface(surface, width, height); + } + // A pbuffer has no resize: the client surface gets a new native pbuffer at the new size, + // and every context that drew to the old one draws to the new one. + const EGLint w = static_cast(std::max(width, 1)); + const EGLint h = static_cast(std::max(height, 1)); + const EGLSurface replacement = DirectGLES::CreateSessionPbuffer(w, h); + if (replacement == EGL_NO_SURFACE) { + MGLOG_E("ResizeEGLWindowSurface: no %dx%d pbuffer could replace the old drawable", w, h); + return false; + } + DirectGLES::ReplaceSessionSurface(native->second, replacement); + native->second = replacement; + return BackendObject::ResizeEGLWindowSurface(surface, width, height); + } + Bool BackendObject_DirectGLES::InitPbufferSurface(EGLint width, EGLint height) { - return DirectGLES::InitPbufferSurface(width, height); + if (RebindNativeSurfaceFor(m_activatingSurface)) return true; + if (!DirectGLES::InitPbufferSurface(width, height)) return false; + if (m_activatingSurface != EGL_NO_SURFACE) { + m_nativeSurfaces[m_activatingSurface] = DirectGLES::CurrentSessionDrawSurface(); + } + return true; } Bool BackendObject_DirectGLES::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) { @@ -1099,7 +1169,16 @@ namespace MobileGL::MG_Backend::DirectGLES { m_eglSurfaceInitialized && m_eglSurface == draw && DirectGLES::IsBackendContextCurrentOnThisThread() && NativeBindCanBeSkippedFor(dpy, draw, read, ctx); - if (!nativelyCurrentAlready && !DirectGLES::MakeCurrent()) { + // The draw surface's own native surface (P14 multi-window): a known one is re-bound, so the + // bind goes to it and not to whichever surface the session drew to last. + // The real bind still runs whenever the skip above says so (ID-67: a different tuple onto + // the same surface needs MakeCurrent's invalidations even though the driver triple is equal). + const auto nativeDraw = m_nativeSurfaces.find(draw); + if (!nativelyCurrentAlready && nativeDraw != m_nativeSurfaces.end()) { + (void)DirectGLES::SelectSessionSurface(nativeDraw->second); + } + const Bool bound = nativelyCurrentAlready || DirectGLES::MakeCurrent(); + if (!bound) { NoteNativeContextGone(); return false; } @@ -1139,13 +1218,24 @@ namespace MobileGL::MG_Backend::DirectGLES { } void BackendObject_DirectGLES::OnEGLSurfaceReleased(EGLSurface surface) { + // P14: releasing a surface releases THAT SURFACE (OnEGLSurfaceForgotten), never the + // contexts. This used to be DestroyEGLContext() - every native context of the session, with + // every object the client had built - which a multi-window client hit on its first closed + // popup. (void)surface; - DestroyEGLContext(); #if MOBILEGL_BUILD_DISAGGREGATED NoteNativeContextGone(); #endif } + void BackendObject_DirectGLES::OnEGLSurfaceForgotten(EGLSurface surface) { + const auto it = m_nativeSurfaces.find(surface); + if (it == m_nativeSurfaces.end()) return; + const EGLSurface native = it->second; + m_nativeSurfaces.erase(it); + DirectGLES::DestroySessionSurface(native); + } + const RendererInfo& BackendObject_DirectGLES::GetRendererInfo() const { return MutableRendererInfo(); } diff --git a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h index 8f3fd0c6..717b244a 100644 --- a/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/BackendObject_DirectGLES.h @@ -38,6 +38,7 @@ namespace MobileGL::MG_Backend::DirectGLES { Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override; Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle) override; Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) override; + Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) override; Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override; Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override; void ReleaseEGLSurface(EGLSurface surface) override; @@ -57,8 +58,19 @@ namespace MobileGL::MG_Backend::DirectGLES { void UpdateDynamicBackendParameters(); Bool InitPbufferSurface(EGLint width, EGLint height) override; void OnEGLSurfaceReleased(EGLSurface surface) override; + void OnEGLSurfaceForgotten(EGLSurface surface) override; + // An existing native surface for the client surface being activated, re-bound; false when + // there is none yet (the caller creates it). + Bool RebindNativeSurfaceFor(EGLSurface surface); + // EGL: creating a surface does not change what is current. The creation bound the new + // surface natively; put the previous one back for the context that held it. + void RestoreSurfaceAfterCreation(EGLSurface previousNativeDraw); Bool m_initialized = false; + // P14: ONE NATIVE SURFACE PER CLIENT SURFACE, created on its first activation and re-bound + // on every later one. A multi-window client switches surfaces every frame; making a new + // native surface each time leaked them and lost what the window had drawn. + UnorderedMap m_nativeSurfaces; MG_External::EGLFunctionsTable m_EGLFunctions; MG_External::GLESFunctionsTable m_GLESFunctions; MG_External::GLESCapabilities m_GLESCapabilities; diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index d0f48c47..8584fb0d 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -3408,13 +3408,23 @@ namespace MobileGL::MG_Backend::DirectGLES { // the same statement one level down, for the backend that would have papered over // it. Under monolith the fall-back is correct and stays. if (MG_Config::Transport != MG_Config::TransportMode::Monolith && maxTouchedUnit >= 0) { - MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"SetSamplerViews.Count\"} - a draw " + // The windows are the PEER's records, so in a served session this ends THAT + // session (the in-process display server serves every client from one + // process; an abort here took the compositor down with the offender). The + // answer while the latch drains is "no unit is bound": the walk the decline + // would run reads client memory, and the session stops at its next record. + // Unarmed, SessionLatch is the old abort. + if (!MG_Remote::SessionLatch( + MG_Remote::MGFatalFamily::ProtocolCorruption, + "MGPipe: Fatal{ProtocolCorruption, \"SetSamplerViews.Count\"} - a draw " "touches units 0..%d and neither a sampler-view nor a sampler-state " "window has ever been applied while the sampler subsystem bit is set; " "the pre-handle unit-bindings snapshot walk reads client memory and is " "refused under an active transport (CONTRACT-P5E.md ยง5.3)", - static_cast(maxTouchedUnit)); - std::abort(); + static_cast(maxTouchedUnit))) { + out = 0; + return true; + } } #endif if (maxTouchedUnit >= 0) { @@ -16317,6 +16327,9 @@ namespace MobileGL::MG_Backend::DirectGLES { // what makes MakeNativeContextCurrentForBoundToken a no-op when the bind did not actually // change the native context (the single-context world's every bind). thread_local EGLContext t_boundNativeContext = EGL_NO_CONTEXT; + // And the surface it was made current with: a bind to the same context on a different + // surface (one client context drawing to several windows) is a real switch. + thread_local EGLSurface t_boundNativeDraw = EGL_NO_SURFACE; } // namespace static NativeSessionState& AcquireNativeSession(Uint64 sessionKey) { @@ -16844,10 +16857,17 @@ namespace MobileGL::MG_Backend::DirectGLES { } static Bool EnsureNativeContext(NativeContextTuple& tuple, EGLint surfaceBit, NativeWindowType window) { - if (!EnsureDisplayInitialized()) return false; + if (!EnsureDisplayInitialized()) { + MGLOG_E("EnsureNativeContext: the EGL display did not initialize"); + return false; + } if (tuple.Context != EGL_NO_CONTEXT) return true; - if (tuple.Config == nullptr && !ChooseConfigForSurface(surfaceBit, tuple.Config, window)) return false; + if (tuple.Config == nullptr && !ChooseConfigForSurface(surfaceBit, tuple.Config, window)) { + MGLOG_E("EnsureNativeContext: no EGL config for surface bit 0x%x (window=%p)", + static_cast(surfaceBit), reinterpret_cast(window)); + return false; + } // A share group whose leader has a live native context hands it to eglCreateContext: this is // the whole of "shared contexts see the same objects" on the server. The group is recorded @@ -16861,6 +16881,23 @@ namespace MobileGL::MG_Backend::DirectGLES { break; } } +#if MOBILEGL_BUILD_DISAGGREGATED + // P14: AND OTHERWISE ANY LIVE NATIVE CONTEXT IN THE PROCESS. The backend keeps buffers, + // textures and syncs of its own in process-wide state (the UBO/unpack/upload rings, the + // buffer pool, scratch textures, frame fences) and every session's apply thread uses + // them; they are only names in another session's context if all the contexts are one + // share group. Client isolation does not come from the driver's namespaces - the twin + // tables are bucketed per {session, share group} and never hand one bucket's names to + // another - so a process-wide group costs nothing in separation. + if (share == EGL_NO_CONTEXT) { + for (auto* other : NativeContexts()) { + if (other != &tuple && other->Context != EGL_NO_CONTEXT) { + share = other->Context; + break; + } + } + } +#endif // Negotiate the highest ES 3.x context. Version-strict EGL implementations // (ANGLE) return exactly the requested minor, and a bare CLIENT_VERSION 3 @@ -16941,15 +16978,126 @@ namespace MobileGL::MG_Backend::DirectGLES { // P14 S4. THE NATIVE HALF OF A CONTEXT SWITCH. See DirectGLES.h; in one line, `bind_context` // moves the session's context attribution and this makes the calling thread's native tuple // follow it, binding the session's current surface to the newly named context's EGLContext. + static Bool SessionOwnsSurface(const NativeSessionState& session, EGLSurface surface) { + if (surface == EGL_NO_SURFACE) return false; + for (const auto& entry : session.Surfaces) { + if (entry.Surface == surface) return true; + } + return false; + } + Bool MakeNativeContextCurrentForBoundToken() { NativeContextTuple& tuple = ActiveNativeContext(); // Nothing to switch to: a session that never sent CreateContext has no native context for // this token, and building one here would bind a context the client does not know it has. if (tuple.Context == EGL_NO_CONTEXT) return true; - if (t_boundNativeContext == tuple.Context) return true; + // A MULTI-WINDOW CLIENT: each context draws to the surface it was last made current with, + // not to whichever surface the session bound most recently - Qt renders each window on its + // own thread with its own context, and binding a context back onto another window's + // surface drew one window's frame into the other. + NativeSessionState& session = ActiveNativeSession(); + if (tuple.Draw != session.Draw && SessionOwnsSurface(session, tuple.Draw)) { + session.Draw = tuple.Draw; + session.Read = SessionOwnsSurface(session, tuple.Read) ? tuple.Read : tuple.Draw; + } + if (t_boundNativeContext == tuple.Context && t_boundNativeDraw == session.Draw) return true; return MakeCurrent(); } + EGLSurface CurrentSessionDrawSurface() { return ActiveNativeSession().Draw; } + + EGLSurface CurrentContextDrawSurface() { return ActiveNativeContext().Draw; } + + Bool SelectSessionSurface(EGLSurface surface) { + NativeSessionState& session = ActiveNativeSession(); + if (!SessionOwnsSurface(session, surface)) return false; + session.Draw = surface; + session.Read = surface; + return true; + } + + Bool BindSessionSurface(EGLSurface surface) { + NativeSessionState& session = ActiveNativeSession(); + if (!SessionOwnsSurface(session, surface)) return false; + session.Draw = surface; + session.Read = surface; + if (ActiveNativeContext().Context == EGL_NO_CONTEXT) return true; + if (t_boundNativeContext == ActiveNativeContext().Context && t_boundNativeDraw == surface) return true; + return MakeCurrent(); + } + + EGLSurface CreateSessionPbuffer(EGLint width, EGLint height) { + NativeContextTuple& tuple = ActiveNativeContext(); + if (!EnsureNativeContext(tuple, EGL_PBUFFER_BIT, static_cast(0))) return EGL_NO_SURFACE; + const EGLint attribs[] = {EGL_WIDTH, width, EGL_HEIGHT, height, EGL_NONE}; + const EGLSurface surface = g_EGLFuncs.eglCreatePbufferSurface(g_Display, tuple.Config, attribs); + if (surface == EGL_NO_SURFACE) return EGL_NO_SURFACE; + NativeSessionState& session = ActiveNativeSession(); + const std::lock_guard lock(g_nativeRegistryMutex); + session.Surfaces.push_back({static_cast(0), surface}); + g_nativeRegistryEpoch.fetch_add(1, std::memory_order_acq_rel); + return surface; + } + + void ReplaceSessionSurface(EGLSurface previous, EGLSurface replacement) { + if (previous == EGL_NO_SURFACE || replacement == EGL_NO_SURFACE || previous == replacement) return; + NativeSessionState& session = ActiveNativeSession(); + { + const std::lock_guard lock(g_nativeRegistryMutex); + for (auto* tuple : NativeContexts()) { + if (tuple->SessionKey != session.SessionKey) continue; + if (tuple->Draw == previous) tuple->Draw = replacement; + if (tuple->Read == previous) tuple->Read = replacement; + } + auto& entries = session.Surfaces; + entries.erase(std::remove_if(entries.begin(), entries.end(), + [&](const NativeSessionState::SurfaceEntry& entry) { + return entry.Surface == previous; + }), + entries.end()); + g_nativeRegistryEpoch.fetch_add(1, std::memory_order_acq_rel); + } + if (session.Draw == previous) session.Draw = replacement; + if (session.Read == previous) session.Read = replacement; + // The calling thread drew to the old one: draw to the new one from here (the old is then + // no longer current, and its destruction below is immediate). + if (t_boundNativeDraw == previous && ActiveNativeContext().Context != EGL_NO_CONTEXT) (void)MakeCurrent(); + if (g_EGLFuncs.eglDestroySurface && g_Display != EGL_NO_DISPLAY) g_EGLFuncs.eglDestroySurface(g_Display, previous); + } + + void DestroySessionSurface(EGLSurface surface) { + if (surface == EGL_NO_SURFACE) return; + NativeSessionState& session = ActiveNativeSession(); + { + const std::lock_guard lock(g_nativeRegistryMutex); + auto& entries = session.Surfaces; + const auto it = std::find_if(entries.begin(), entries.end(), + [&](const NativeSessionState::SurfaceEntry& entry) { return entry.Surface == surface; }); + if (it == entries.end()) return; + entries.erase(it); + for (auto* tuple : NativeContexts()) { + if (tuple->SessionKey != session.SessionKey) continue; + if (tuple->Draw == surface) tuple->Draw = EGL_NO_SURFACE; + if (tuple->Read == surface) tuple->Read = EGL_NO_SURFACE; + } + g_nativeRegistryEpoch.fetch_add(1, std::memory_order_acq_rel); + } + if (session.Draw == surface || session.Read == surface) { + // Fall back onto a surface the session still has, so the next bind has somewhere to go. + const EGLSurface fallback = session.Surfaces.empty() ? EGL_NO_SURFACE : session.Surfaces.front().Surface; + session.Draw = fallback; + session.Read = fallback; + if (t_boundNativeDraw == surface && fallback != EGL_NO_SURFACE && + ActiveNativeContext().Context != EGL_NO_CONTEXT) { + (void)MakeCurrent(); + } + } + // A surface still current on this thread is destroyed by EGL once it is not (EGL 1.5 + // 3.5.6), so this needs no unbind first. + if (g_EGLFuncs.eglDestroySurface && g_Display != EGL_NO_DISPLAY) g_EGLFuncs.eglDestroySurface(g_Display, surface); + if (t_boundNativeDraw == surface && session.Draw == EGL_NO_SURFACE) t_boundNativeDraw = EGL_NO_SURFACE; + } + namespace { // Last swap interval the app requested through eglSwapInterval; -1 = never // requested (keep the EGL default of 1). Re-applied when the window surface @@ -17007,7 +17155,12 @@ namespace MobileGL::MG_Backend::DirectGLES { } const EGLSurface surface = g_EGLFuncs.eglCreateWindowSurface(g_Display, tuple.Config, window, nullptr); - if (surface == EGL_NO_SURFACE) return false; + if (surface == EGL_NO_SURFACE) { + const EGLint error = g_EGLFuncs.eglGetError ? g_EGLFuncs.eglGetError() : EGL_SUCCESS; + MGLOG_E("InitWindowSurface: eglCreateWindowSurface(window=%p) failed with EGL error 0x%x", + reinterpret_cast(window), static_cast(error)); + return false; + } NativeSessionState& session = ActiveNativeSession(); session.Surfaces.push_back({window, surface}); session.Draw = surface; @@ -17125,6 +17278,39 @@ namespace MobileGL::MG_Backend::DirectGLES { // EGL verification stamp BEFORE publishing the new owner. void InvalidateEglVerifiedStamp(); + // EVERY SHADOW OF THE CURRENT NATIVE CONTEXT'S BINDINGS, dropped whenever the context the + // calling thread renders into may have changed (MakeCurrent, a turn handoff between two + // sessions' apply threads). Re-issuing a bind is cheap; trusting a shadow that describes + // another context skips one the driver needed (a texture sampled from the wrong unit, a + // draw against another context's VAO). Requires a current context: it re-establishes + // GL_TEXTURE0 so the active-unit shadow is true rather than guessed. + static void InvalidateNativeBindingShadows() { + // Conservatively drop the redundant-glUseProgram guard: re-issuing one bind + // after a MakeCurrent is cheaper than trusting a possibly-reset context. + PrgramImpl::g_lastUsedBackendProgramId = 0; + // The GLContext becoming current may be a fresh one whose slot versions + // restarted at zero; the broadcast memo's key is only monotonic within one. + PrgramImpl::InvalidateBroadcastMemo(); + BufferImpl::InvalidateArrayBufferBindingCache(); + BufferImpl::InvalidateIndexedBufferBindingCache(); + BufferImpl::InvalidatePixelBufferBindingCaches(); + BufferImpl::InvalidateTransformFeedbackBindingShadows(); + FramebufferImpl::InvalidateFramebufferBindingCache(); +#if MOBILEGL_PIPE_PUSH + FramebufferImpl::InvalidateFramebufferHandleArmMemos(); +#endif + VertexArrayImpl::InvalidateVAOBindingCache(); + PixelStoreImpl::InvalidatePackStateCache(); + // The render-state shadow belongs in this list for the same reason as the ones above: + // it describes the real ES context, which outlives the MobileGL context that is + // becoming current. See InvalidateSyncedRenderState. + RenderStateImpl::InvalidateSyncedRenderState(); + TextureImpl::g_boundTexturesCache = {}; + SamplerImpl::g_boundSamplersCache = {}; + if (g_GLESFuncs.glActiveTexture) g_GLESFuncs.glActiveTexture(GL_TEXTURE0); + TextureImpl::g_activeTextureUnit = 0; + } + Bool MakeCurrent() { // P14 S4: the context comes from the calling thread's tuple (the session's bound token), // the surface from the session. Both halves used to be file-level singletons. @@ -17144,33 +17330,34 @@ namespace MobileGL::MG_Backend::DirectGLES { } InvalidateEglVerifiedStamp(); t_boundNativeContext = tuple.Context; + t_boundNativeDraw = session.Draw; 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. BufferImpl::RegisterBufferBackendOps(); - // Conservatively drop the redundant-glUseProgram guard: re-issuing one bind - // after a MakeCurrent is cheaper than trusting a possibly-reset context. - PrgramImpl::g_lastUsedBackendProgramId = 0; - // The GLContext becoming current may be a fresh one whose slot versions - // restarted at zero; the broadcast memo's key is only monotonic within one. - PrgramImpl::InvalidateBroadcastMemo(); - BufferImpl::InvalidateIndexedBufferBindingCache(); - BufferImpl::InvalidatePixelBufferBindingCaches(); - FramebufferImpl::InvalidateFramebufferBindingCache(); -#if MOBILEGL_PIPE_PUSH - FramebufferImpl::InvalidateFramebufferHandleArmMemos(); -#endif - PixelStoreImpl::InvalidatePackStateCache(); - // The render-state shadow belongs in this list for the same reason as the ones above: - // it describes the real ES context, which outlives the MobileGL context that is - // becoming current. See InvalidateSyncedRenderState. - RenderStateImpl::InvalidateSyncedRenderState(); + InvalidateNativeBindingShadows(); // eglSwapInterval requires a current context; a request made while none was // current (and dropped by the driver) is retried here. ApplyRequestedSwapInterval(); return true; } +#if MOBILEGL_BUILD_DISAGGREGATED + // P14: THE BACKEND'S TURN MOVED TO ANOTHER APPLY THREAD (ServerLoop's BackendTurn). Each + // session's apply thread keeps its own native context current, but the backend's owner + // thread and every binding shadow are process-wide and still describe the context of the + // thread that held the turn before. Take ownership for this thread and forget them. + void OnBackendTurnHandoff() { + InvalidateEglVerifiedStamp(); + if (t_boundNativeContext == EGL_NO_CONTEXT) { + g_backendContextOwnerThread.store(std::thread::id{}, std::memory_order_release); + return; + } + g_backendContextOwnerThread.store(std::this_thread::get_id(), std::memory_order_release); + InvalidateNativeBindingShadows(); + } +#endif + Bool ReleaseCurrent() { if (!g_EGLFuncs.eglMakeCurrent || g_Display == EGL_NO_DISPLAY) { InvalidateEglVerifiedStamp(); @@ -17187,6 +17374,7 @@ namespace MobileGL::MG_Backend::DirectGLES { // "not current" only defers buffer ops, which is always safe. InvalidateEglVerifiedStamp(); t_boundNativeContext = EGL_NO_CONTEXT; + t_boundNativeDraw = EGL_NO_SURFACE; g_backendContextOwnerThread.store(std::thread::id{}, std::memory_order_release); return true; } @@ -17712,11 +17900,35 @@ namespace MobileGL::MG_Backend::DirectGLES { // // ONE CONTEXT's teardown is DestroyNativeContextFor (the DestroyContext control frame); this is // the session's. + static void DestroyNativeStateOfSession(Uint64 sessionKey); + void DestroyEGLContext() { #if MOBILEGL_BUILD_DISAGGREGATED if (MG_Config::Transport != MG_Config::TransportMode::Monolith) MG_Pipe::MGPipeServerSetContextLive(false); #endif + // P14: ANOTHER SESSION'S CONTEXT STILL LIVES. Every native context is in one share group, so + // the buffers, textures and syncs this process's backend holds outlive this session's + // contexts: bumping the process-wide generations here told every live session that its + // GL objects were dead, and they were rebuilt EMPTY (a client exiting or closing its last + // window turned the compositor's textures to noise). Only this session's own state goes; + // the process-wide reset below is for the last context in the process. + const Uint64 endingSession = CurrentNativeSessionKey(); + Bool othersLive = false; + { + const std::lock_guard lock(g_nativeRegistryMutex); + for (auto* tuple : NativeContexts()) { + if (tuple->SessionKey != endingSession && tuple->Context != EGL_NO_CONTEXT) { + othersLive = true; + break; + } + } + } + if (othersLive) { + BufferImpl::OnServerSessionEndedWithOthersLive(); + DestroyNativeStateOfSession(endingSession); + return; + } BufferImpl::OnBackendContextDestroyed(); XfbImpl::OnBackendContextDestroyed(); MultiDrawImpl::OnBackendContextDestroyed(); @@ -17744,13 +17956,19 @@ namespace MobileGL::MG_Backend::DirectGLES { g_completedFrameSerial.store(g_currentFrameSerial.load(std::memory_order_relaxed), std::memory_order_relaxed); - const Uint64 sessionKey = CurrentNativeSessionKey(); + DestroyNativeStateOfSession(CurrentNativeSessionKey()); + } + + // This session's native contexts and surfaces, and the display when they were the last. + static void DestroyNativeStateOfSession(Uint64 sessionKey) { if (g_Display == EGL_NO_DISPLAY) return; { const std::lock_guard lock(g_nativeRegistryMutex); if (g_EGLFuncs.eglMakeCurrent) { g_EGLFuncs.eglMakeCurrent(g_Display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); } + t_boundNativeContext = EGL_NO_CONTEXT; + t_boundNativeDraw = EGL_NO_SURFACE; for (auto* tuple : NativeContexts()) { if (tuple->SessionKey != sessionKey || tuple->Context == EGL_NO_CONTEXT) continue; if (g_EGLFuncs.eglDestroyContext) g_EGLFuncs.eglDestroyContext(g_Display, tuple->Context); diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h index a259f416..94b2441f 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.h +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.h @@ -111,6 +111,25 @@ namespace MobileGL::MG_Backend::DirectGLES { void ShaderStorageBlockBinding(GLuint program, const GLchar* storageBlockName, GLuint storageBlockBinding); Bool InitWindowSurface(NativeWindowType window); Bool InitPbufferSurface(EGLint width, EGLint height); + // The calling thread's session's current native draw surface (the one the last Init*Surface + // created, or the last BindSessionSurface named). + EGLSurface CurrentSessionDrawSurface(); + // The surface the calling thread's ACTIVE context last drew to (EGL_NO_SURFACE for none). + EGLSurface CurrentContextDrawSurface(); + // Makes an EXISTING native surface of the calling thread's session the one it draws to, and + // binds it to the active context. False for a surface the session does not own. + Bool BindSessionSurface(EGLSurface surface); + // The same selection without the bind (the caller binds - MakeCurrent - unconditionally). + Bool SelectSessionSurface(EGLSurface surface); + // Destroys one native surface of the calling thread's session - and only it; the session's + // contexts and other surfaces are untouched. + void DestroySessionSurface(EGLSurface surface); + // A new pbuffer of the calling thread's session, created with the active context's config and + // bound to nothing (EGL: creating a surface changes no binding). + EGLSurface CreateSessionPbuffer(EGLint width, EGLint height); + // Every context of the session that drew to `previous` draws to `replacement` instead, and + // `previous` is destroyed (a pbuffer-backed window's resize). + void ReplaceSessionSurface(EGLSurface previous, EGLSurface replacement); Bool MakeCurrent(); Bool ReleaseCurrent(); // True when the backend ES context is current on the calling thread, i.e. @@ -281,6 +300,12 @@ namespace MobileGL::MG_Backend::DirectGLES { // native context already current on this thread IS that one - so the single-context world pays // nothing for it. Called from the bind_context path on the apply thread. Bool MakeNativeContextCurrentForBoundToken(); +#if MOBILEGL_BUILD_DISAGGREGATED + // P14: the server's backend turn moved to the calling apply thread (ServerLoop.cpp's + // BackendTurnLock). Takes the backend's GL ownership for this thread and drops every + // binding shadow, which still describes the previous holder's native context. + void OnBackendTurnHandoff(); +#endif // How many native contexts this process holds, and how many of them belong to one session. The // test observables for "a second context did not tear the first one down". Uint64 NativeContextCount(); diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.cpp b/MobileGL/MG_Backend/DirectGLES/Managers.cpp index e1ad0370..b77853d4 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.cpp +++ b/MobileGL/MG_Backend/DirectGLES/Managers.cpp @@ -3872,6 +3872,18 @@ namespace MobileGL::MG_Backend::DirectGLES { g_hasDeferredBufferReleases.store(false, std::memory_order_release); } + void OnServerSessionEndedWithOthersLive() { +#if MOBILEGL_PIPE_PUSH + // Only what belongs to the ending session: its staged bytes (both stores are + // session-scoped). The rings, the ops table and the buffer generation are the + // process's and every other session's GL buffers are still alive in the shared group. + MGL_SERVER_STAGED_DROP_ALL(); +#if MOBILEGL_BUILD_DISAGGREGATED + MG_Remote::Server::ServerStagedTexture().DropAll(); +#endif +#endif + } + void OnBackendContextDestroyed() { UnregisterBufferBackendOps(); // also bumps the buffer-mutation epoch ++g_bufferContextGeneration; @@ -9697,8 +9709,18 @@ namespace MobileGL::MG_Backend::DirectGLES { // entry's rects are then read out of the SERVER'S WHOLE LEVEL IMAGE - // which is what the derived pointer names - and those piece-relative // offsets are precisely the ones that must not be used here. + // + // GATED ON regionsAddressable, NOT subRectEligible: the per-region arm + // for a level the GPU may have written (m_driverMayHoldGpuWrites below) + // reads MGB_RECT_SRC_PTR too, and it runs exactly when the union box is + // the whole level - which is what two or more slab pieces always add up + // to. Checked only under subRectEligible, a damage rect split across + // pieces kept the later pieces' run-relative offsets and read their + // rows from the top of the level image (KWin's wl_shm window textures: + // the panel repeated down the desktop). bpp is valid whenever the + // regions are addressable. Bool pendingOffsetsAgree = true; - if (pendingUpload != nullptr && subRectEligible && carriedLayoutApplies) { + if (pendingUpload != nullptr && regionsAddressable && carriedLayoutApplies) { for (const auto& region : pendingUpload->Regions) { const SizeT derivedOffset = static_cast(region.Z) * levelSliceBytes + static_cast(region.Y) * levelRowBytes + diff --git a/MobileGL/MG_Backend/DirectGLES/Managers.h b/MobileGL/MG_Backend/DirectGLES/Managers.h index 4d092db9..32591853 100644 --- a/MobileGL/MG_Backend/DirectGLES/Managers.h +++ b/MobileGL/MG_Backend/DirectGLES/Managers.h @@ -1366,6 +1366,9 @@ namespace MobileGL::MG_Backend::DirectGLES { void UnregisterBufferBackendOps(); // The ES context died: unregister ops, invalidate all outstanding GL ids // (they belonged to the dead context) and drop deferred deletes. + // P14: a server session ended while another session's native context is still alive (one + // process-wide share group): drops only the ending session's own staged bytes. + void OnServerSessionEndedWithOthersLive(); void OnBackendContextDestroyed(); // Get-or-create the backend resource and bring its storage up to date diff --git a/MobileGL/MG_Remote/Server/ServerLoop.cpp b/MobileGL/MG_Remote/Server/ServerLoop.cpp index bd3cad08..9e245648 100644 --- a/MobileGL/MG_Remote/Server/ServerLoop.cpp +++ b/MobileGL/MG_Remote/Server/ServerLoop.cpp @@ -24,9 +24,12 @@ #include #include #include +#include #include #include #include +#include +#include #include #if defined(__linux__) || defined(__ANDROID__) @@ -38,6 +41,67 @@ namespace MobileGL::MG_Remote::Server { namespace { + // P14: THE BACKEND'S TURN. The in-process display server runs one apply thread per + // session, and the backend those threads drive was written for one GL thread: one owner + // thread, one set of binding shadows, process-wide rings and pools. Two sessions applying + // at once raced all of it - a session's buffer ops were deferred while the other thread + // owned the backend, and a texture bind was skipped because the shadow described the + // other session's context. So an apply thread holds the turn while it does backend work + // (control requests, a drain batch, fence reports, teardown) and gives it up before it + // parks. FIFO, so a session with a busy ring cannot starve its neighbours; reentrant for + // the thread that holds it. When the turn moves to a different thread the backend takes + // ownership for it and drops its binding shadows (DirectGLES::OnBackendTurnHandoff). + class BackendTurnLock { + public: + void Lock() { + const std::thread::id self = std::this_thread::get_id(); + std::unique_lock lock(m_mutex); + if (m_owner == self) { + ++m_depth; + return; + } + const Uint64 ticket = m_nextTicket++; + m_turn.wait(lock, [&] { return m_serving == ticket; }); + m_owner = self; + m_depth = 1; + if (m_lastOwner != self) { + const Bool handoff = m_lastOwner != std::thread::id{}; + m_lastOwner = self; + lock.unlock(); + if (handoff) MG_Backend::DirectGLES::OnBackendTurnHandoff(); + } + } + void Unlock() { + const std::lock_guard lock(m_mutex); + if (--m_depth > 0) return; + m_owner = std::thread::id{}; + ++m_serving; + m_turn.notify_all(); + } + + private: + std::mutex m_mutex; + std::condition_variable m_turn; + Uint64 m_nextTicket = 0; + Uint64 m_serving = 0; + std::thread::id m_owner{}; + std::thread::id m_lastOwner{}; + Uint32 m_depth = 0; + }; + + BackendTurnLock& BackendTurn() { + static BackendTurnLock& turn = *new BackendTurnLock(); // leaked at exit (ID-8) + return turn; + } + + class ScopedBackendTurn { + public: + ScopedBackendTurn() { BackendTurn().Lock(); } + ~ScopedBackendTurn() { BackendTurn().Unlock(); } + ScopedBackendTurn(const ScopedBackendTurn&) = delete; + ScopedBackendTurn& operator=(const ScopedBackendTurn&) = delete; + }; + // The bounded join. InProcessTransportTest.cpp:344 uses five seconds for the same // reason: a lost wakeup must be a RED TEST and not a hung CI job. constexpr Uint32 kJoinTimeoutMs = 5000; @@ -533,6 +597,7 @@ namespace MobileGL::MG_Remote::Server { }; for (;;) { + std::optional turn(std::in_place); PumpControlRequest(); if (stopOrForfeit()) break; if (eventRingHasRoom()) DrainRing(); @@ -557,6 +622,8 @@ namespace MobileGL::MG_Remote::Server { if (ready()) continue; } + // Never park holding the turn: a neighbour session may be waiting for it. + turn.reset(); const Uint64 waits = m_parks.fetch_add(1, std::memory_order_acq_rel) + 1; // THE WAIT LEDGER'S SERVER HALF (P5d round 3, package T item 4). The client may not // name this counter - CONTRACT-P5C rule E, layer 2: a GL-thread read of @@ -622,6 +689,7 @@ namespace MobileGL::MG_Remote::Server { // AND AFTER AbandonQueuedRecords (P12 review fix), for the forfeit's reason: the server is // stopping under a client that may still be streaming, and draining what it keeps queuing // is the unbounded hold Stop()'s bounded join turns into an abort. + std::optional exitTurn(std::in_place); const Bool forfeited = session.ReverseChannelForfeited(); const Bool abandoned = m_abandonQueue.load(std::memory_order_acquire); PumpControlRequest(); @@ -650,6 +718,7 @@ namespace MobileGL::MG_Remote::Server { "which is the context owner"); m_backend.reset(); } + exitTurn.reset(); // P12 (D6): the backend - and with it every surface on the server's window - is gone, so the // session's lease on that window ends here, AFTER the reset. A Detach waiting on it returns // now; one that arrives later finds no lease and releases the window at once. @@ -1418,6 +1487,14 @@ namespace MobileGL::MG_Remote::Server { // already current on this thread is skipped one layer down // (BackendObject_DirectGLES's ID-54 arm), which is what makes "the owner slot is // written once" TRUE and measured (ServerLoopTest's C7 control) rather than claimed. + // + // P14 MULTI-WINDOW: the frame names the client's CONTEXT TOKEN (BackendObject_Remote's + // MakeEGLCurrent), and the native bind below resolves the context through the session's + // current token - which still names whatever another thread bound last, since this + // thread's own bind_context follows the frame. Bound first, so the surface is made + // current with ITS context and not attached to a neighbour's. A frame from a client + // that sends a handle names no token and leaves the binding alone. + if (m_session != nullptr && frame.context != 0) (void)m_session->BindContext(frame.context); const MakeCurrentOutcome outcome = ApplyMakeCurrent(backend, HandleFromToken(frame.display), HandleFromToken(frame.surface), diff --git a/scripts/ci/fatal_census_baseline.json b/scripts/ci/fatal_census_baseline.json index 0f9888e1..a703435c 100644 --- a/scripts/ci/fatal_census_baseline.json +++ b/scripts/ci/fatal_census_baseline.json @@ -1,7 +1,7 @@ { - "abort_sites": 78, + "abort_sites": 77, "abort_sites_by_file": { - "MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp": 14, + "MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp": 13, "MobileGL/MG_Backend/DirectGLES/Managers.cpp": 12, "MobileGL/MG_Backend/DirectGLES/MultiDraw.cpp": 2, "MobileGL/MG_Backend/DirectGLES/Utils.cpp": 1,