mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (P14): serialize sessions on one backend turn and give each client surface its own native surface
This commit is contained in:
@@ -200,12 +200,15 @@ namespace MobileGL::MG_Backend {
|
||||
Bool BackendObject::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) {
|
||||
const std::lock_guard<std::recursive_mutex> 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<EGLint>(std::max<Uint32>(width, 1));
|
||||
surfaceIt->second.Height = static_cast<EGLint>(std::max<Uint32>(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<std::recursive_mutex> 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<std::recursive_mutex> 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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<NativeWindowType>(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<std::recursive_mutex> 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<EGLint>(std::max<Uint32>(width, 1));
|
||||
const EGLint h = static_cast<EGLint>(std::max<Uint32>(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();
|
||||
}
|
||||
|
||||
@@ -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<EGLSurface, EGLSurface> m_nativeSurfaces;
|
||||
MG_External::EGLFunctionsTable m_EGLFunctions;
|
||||
MG_External::GLESFunctionsTable m_GLESFunctions;
|
||||
MG_External::GLESCapabilities m_GLESCapabilities;
|
||||
|
||||
@@ -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<int>(maxTouchedUnit));
|
||||
std::abort();
|
||||
static_cast<int>(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<unsigned>(surfaceBit), reinterpret_cast<void*>(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<NativeWindowType>(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<std::mutex> lock(g_nativeRegistryMutex);
|
||||
session.Surfaces.push_back({static_cast<NativeWindowType>(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<std::mutex> 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<std::mutex> 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<void*>(window), static_cast<unsigned>(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<std::mutex> 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<std::mutex> 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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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<SizeT>(region.Z) * levelSliceBytes +
|
||||
static_cast<SizeT>(region.Y) * levelRowBytes +
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -24,9 +24,12 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <thread>
|
||||
|
||||
#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<std::mutex> 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<std::mutex> 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<ScopedBackendTurn> 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<ScopedBackendTurn> 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<EGLDisplay>(frame.display),
|
||||
HandleFromToken<EGLSurface>(frame.surface),
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user