From 29a82617e072dc5b6a61ecec8bda87411ad3864b Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Thu, 24 Sep 2026 14:03:47 +0000 Subject: [PATCH] [MG_Remote, MG_Backend] (P12 review): a server-owned surface resizes the server's window and reports its real size, every server-owned surface keeps its geometry, and Espryt reports a window's extent only for the server's own window - Client resize: eglResize on a headless client's server-owned surface only rewrote the backend's record of the window - the phone's SurfaceView kept its size - and the client's EGL state answered the size it asked for. The server now recognises the surface as its own (ServerLoop::m_serverOwnedSurfaces, per session) and resizes it as a geometry request (the lease re-taken by its holder, the display waited for), resizes the backend surface to the window's real extent and replies with it; BackendObject_Remote adopts that extent (ServerResizeServerOwnedWindowSurface). - Size tracking: the client held ONE server-owned surface, so a second one took the first's geometry updates and releasing it left the first with none. ClientSession now tracks every server-owned surface and applies an extent to all of them (they share the server's one window). - Size reports without the knob: Espryt's new extent publish fired for every window surface of a spawn/tcp session, including windows a client named itself without MOBILEGL_IPC_SURFACE=server, switching their consumer to the reallocating arm. ServerLoop now names its own leased window to the backend (MGPipeServerSetOwnedWindow, split-only) and Espryt publishes the extent only for that window. --- MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp | 7 +- MobileGL/MG_Backend/MGPipe/PipeInputs.cpp | 8 +++ MobileGL/MG_Backend/MGPipe/PipeInputs.h | 7 ++ .../MG_Remote/Client/BackendObject_Remote.cpp | 30 +++++++++ MobileGL/MG_Remote/Client/ClientSession.cpp | 44 ++++++++---- MobileGL/MG_Remote/Client/ClientSession.h | 7 +- MobileGL/MG_Remote/Server/ServerLoop.cpp | 67 +++++++++++++++++++ MobileGL/MG_Remote/Server/ServerLoop.h | 11 +++ 8 files changed, 164 insertions(+), 17 deletions(-) diff --git a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp index 2d5b88fb..8b5133d0 100644 --- a/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp +++ b/MobileGL/MG_Backend/DirectGLES/DirectGLES.cpp @@ -16258,8 +16258,11 @@ namespace MobileGL::MG_Backend::DirectGLES { ApplyRequestedSwapInterval(); #if MOBILEGL_BUILD_DISAGGREGATED - // P12 (D3): this publish may carry the window's extent (see the push arm of the publish). - g_publishWindowExtent = true; + // P12 (D3): this publish may carry the window's extent (see the push arm of the publish) - + // review fix: only when the window is the SERVER's own (a headless client's ServerOwned + // surface). A window the client named keeps the format-only publish without the knob. + g_publishWindowExtent = MG_Pipe::MGPipeServerOwnedWindow() != nullptr && + reinterpret_cast(window) == MG_Pipe::MGPipeServerOwnedWindow(); PublishDefaultFramebufferDepthStencilFormat(); g_publishWindowExtent = false; #else diff --git a/MobileGL/MG_Backend/MGPipe/PipeInputs.cpp b/MobileGL/MG_Backend/MGPipe/PipeInputs.cpp index 37e553b7..e1011631 100644 --- a/MobileGL/MG_Backend/MGPipe/PipeInputs.cpp +++ b/MobileGL/MG_Backend/MGPipe/PipeInputs.cpp @@ -406,6 +406,14 @@ namespace MobileGL::MG_Pipe { Bool MGPipeServerContextIsLive() { return g_serverContextLive; } +#if MOBILEGL_BUILD_DISAGGREGATED + namespace { const void* g_serverOwnedWindow = nullptr; } + + void MGPipeServerSetOwnedWindow(const void* window) { g_serverOwnedWindow = window; } + + const void* MGPipeServerOwnedWindow() { return g_serverOwnedWindow; } +#endif + void MGPipeServerBlockNoteIdentity() { // D10: THE WIDER PREDICATE, and the narrow one was a latent crash rather than a missing // optimisation. Gated on the REHEARSAL, this early-returned for the whole life of a diff --git a/MobileGL/MG_Backend/MGPipe/PipeInputs.h b/MobileGL/MG_Backend/MGPipe/PipeInputs.h index 08058531..914f285a 100644 --- a/MobileGL/MG_Backend/MGPipe/PipeInputs.h +++ b/MobileGL/MG_Backend/MGPipe/PipeInputs.h @@ -938,6 +938,13 @@ namespace MobileGL::MG_Pipe { // GLContext or resurrected by stamping a verb after teardown. void MGPipeServerSetContextLive(Bool live); Bool MGPipeServerContextIsLive(); + // P12 review fix (size reports without the knob). The SERVER's own display window while this + // session holds its lease (ServerLoop's ServerOwned arm sets it, the lease's end clears it), else + // null. A backend whose window surface does not otherwise publish its extent (Espryt) publishes + // it only for this window: a window the client named itself keeps the format-only publish every + // earlier session relied on. Apply thread only. + void MGPipeServerSetOwnedWindow(const void* window); + const void* MGPipeServerOwnedWindow(); #else // The push-without-transport build has one role and one block, so the fill side's spelling // folds onto gPipeInputs and PipeFill.cpp reads identically in both build flavours. An diff --git a/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp b/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp index 943b3b12..4f805fad 100644 --- a/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp +++ b/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp @@ -230,6 +230,36 @@ namespace MobileGL::MG_Remote::Client { Bool BackendObject_Remote::ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) { WaitForApplyBeforeEglForwarder("ResizeEGLWindowSurface"); + // P12 review fix: a surface on the SERVER's window is resized by resizing that window. The + // server asks its display for the size and answers with the extent the window really took, + // and that - not the size asked for, which EGLImpl already wrote into the EGL state - is what + // eglQuerySurface answers from here on. + if (ClientSession::IsServerOwnedWindowSurface(surface)) { + const Server::ServerOwnedWindowReply reply = + Server::ServerResizeServerOwnedWindowSurface(surface, width, height); + if (!reply.ok) { + MGLOG_E("MG_Remote client: the server could not resize the server-owned window surface to %ux%u " + "(channel rc=%d, refusal %s)", + width, height, static_cast(reply.transport), Server::SurfaceRefusalCodeName(reply.refusal)); + return false; + } + const Uint32 realWidth = reply.width != 0 ? reply.width : width; + const Uint32 realHeight = reply.height != 0 ? reply.height : height; + if (MG_State::pEGLContext) { + (void)MG_State::pEGLContext->SetSurfaceExtent(surface, static_cast(realWidth), + static_cast(realHeight)); + } + if (ClientSession* session = ClientSession::Active()) { + session->NoteServerOwnedWindowSurface(surface, realWidth, realHeight); + session->DrainPublishedEvents(); + } + if (realWidth != width || realHeight != height) { + MGLOG_W("MG_Remote client: the server window took %ux%u, not the %ux%u asked for; the surface " + "reports the window's size", + realWidth, realHeight, width, height); + } + return MG_Backend::BackendObject::ResizeEGLWindowSurface(surface, realWidth, realHeight); + } if (!Server::ServerResizeEGLWindowSurface(surface, width, height)) return false; // A resize re-creates the server's swapchain, which re-posts the surface-changed // event - same drain, same reason as CreateEGLWindowSurface. diff --git a/MobileGL/MG_Remote/Client/ClientSession.cpp b/MobileGL/MG_Remote/Client/ClientSession.cpp index 2344db54..36ea4481 100644 --- a/MobileGL/MG_Remote/Client/ClientSession.cpp +++ b/MobileGL/MG_Remote/Client/ClientSession.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -347,22 +348,27 @@ namespace MobileGL::MG_Remote::Client { } } - // P12 (on-screen server window), D1: the session's server-owned window surface - the - // client's EGL handle for it - or EGL_NO_SURFACE. Written on the GL thread by the surface - // RPCs (NoteServerOwnedWindowSurface / ForgetServerOwnedWindowSurface) and read by the - // drain, which runs on the same thread; atomic only because nothing else pins that. - std::atomic g_serverOwnedSurface{EGL_NO_SURFACE}; + // P12 (on-screen server window), D1: the session's server-owned window surfaces - the + // client's EGL handles for them. Written by the surface RPCs (NoteServerOwnedWindowSurface / + // ForgetServerOwnedWindowSurface) and read by the drain; a mutex because an app may create + // and release surfaces from more than one thread. REVIEW FIX: a SET, not one slot - with one + // slot a second surface took the geometry updates from the first, and releasing the second + // left the first with none. Every one of them is on the server's one window, so an extent + // applies to all of them. + std::mutex g_serverOwnedSurfacesMutex; + std::vector g_serverOwnedSurfaces; // An event's extent is the SERVER's surface size. For the server-owned window that is the // size the client renders at, so the EGL state's record of the surface follows it: // eglQuerySurface(EGL_WIDTH/EGL_HEIGHT) is what a headless client sizes its viewport from. // A format-only event (Width/Height 0) says nothing about the size and changes nothing. void ApplyServerOwnedSurfaceExtent(Uint32 width, Uint32 height) { - if (width == 0 || height == 0) return; - const EGLSurface surface = g_serverOwnedSurface.load(std::memory_order_acquire); - if (surface == EGL_NO_SURFACE || !MG_State::pEGLContext) return; - (void)MG_State::pEGLContext->SetSurfaceExtent(surface, static_cast(width), - static_cast(height)); + if (width == 0 || height == 0 || !MG_State::pEGLContext) return; + const std::lock_guard lock(g_serverOwnedSurfacesMutex); + for (const EGLSurface surface : g_serverOwnedSurfaces) { + (void)MG_State::pEGLContext->SetSurfaceExtent(surface, static_cast(width), + static_cast(height)); + } } Uint32 DrainEventRing(Transport::EventRingConsumer& events) { @@ -2468,13 +2474,25 @@ namespace MobileGL::MG_Remote::Client { } void ClientSession::NoteServerOwnedWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) { - g_serverOwnedSurface.store(surface, std::memory_order_release); + { + const std::lock_guard lock(g_serverOwnedSurfacesMutex); + if (std::find(g_serverOwnedSurfaces.begin(), g_serverOwnedSurfaces.end(), surface) == + g_serverOwnedSurfaces.end()) + g_serverOwnedSurfaces.push_back(surface); + } ApplyServerOwnedSurfaceExtent(width, height); } void ClientSession::ForgetServerOwnedWindowSurface(EGLSurface surface) { - EGLSurface expected = surface; - (void)g_serverOwnedSurface.compare_exchange_strong(expected, EGL_NO_SURFACE, std::memory_order_acq_rel); + const std::lock_guard lock(g_serverOwnedSurfacesMutex); + g_serverOwnedSurfaces.erase(std::remove(g_serverOwnedSurfaces.begin(), g_serverOwnedSurfaces.end(), surface), + g_serverOwnedSurfaces.end()); + } + + Bool ClientSession::IsServerOwnedWindowSurface(EGLSurface surface) { + const std::lock_guard lock(g_serverOwnedSurfacesMutex); + return std::find(g_serverOwnedSurfaces.begin(), g_serverOwnedSurfaces.end(), surface) != + g_serverOwnedSurfaces.end(); } Uint64 ClientSession::EventRingCapacityBytes() const { return m_link->Memory().EventRingCapacity(); } diff --git a/MobileGL/MG_Remote/Client/ClientSession.h b/MobileGL/MG_Remote/Client/ClientSession.h index d5a46ab6..c381102a 100644 --- a/MobileGL/MG_Remote/Client/ClientSession.h +++ b/MobileGL/MG_Remote/Client/ClientSession.h @@ -407,10 +407,13 @@ namespace MobileGL::MG_Remote::Client { // goes into the EGL state at once - eglQuerySurface answers it before eglCreateWindowSurface // returns - and every later surface-changed event that carries an extent (the server window // resized or rotated) is applied to the same surface, beside the default-framebuffer - // reallocation the drain already does. One surface at a time: the newest wins, and - // ForgetServerOwnedWindowSurface drops it when the client releases it. + // reallocation the drain already does. Every server-owned surface is tracked (they are all + // on the server's one window, so each extent applies to all of them), and + // ForgetServerOwnedWindowSurface drops one when the client releases it. void NoteServerOwnedWindowSurface(EGLSurface surface, Uint32 width, Uint32 height); void ForgetServerOwnedWindowSurface(EGLSurface surface); + // Whether `surface` is one of them (the resize path asks: it resizes the server's window). + static Bool IsServerOwnedWindowSurface(EGLSurface surface); // SEG_EVENT's ring capacity, exposed so the readback path can slice a writeback // request into records that always fit (RingProducer::MaxRecordBytes == diff --git a/MobileGL/MG_Remote/Server/ServerLoop.cpp b/MobileGL/MG_Remote/Server/ServerLoop.cpp index d64e54bb..c81ee6f8 100644 --- a/MobileGL/MG_Remote/Server/ServerLoop.cpp +++ b/MobileGL/MG_Remote/Server/ServerLoop.cpp @@ -17,6 +17,7 @@ #include #include +#include #include #include #include @@ -231,6 +232,7 @@ namespace MobileGL::MG_Remote::Server { // has no window-lost request pending. The apply thread that reads these is not started yet. m_surfaceMode = SessionSurfaceMode::None; m_holdsWindowLease = false; + m_serverOwnedSurfaces.clear(); m_windowLostRequested.store(false, std::memory_order_release); m_serverWindowsLost.store(0, std::memory_order_release); { @@ -1188,6 +1190,9 @@ namespace MobileGL::MG_Remote::Server { return MOBILEGL_OK; } case SurfaceControlOp::ResizeWindowSurface: + // P12 review fix: a server-owned surface is resized by resizing the SERVER's window. + if (IsServerOwnedSurface(HandleFromToken(frame.surface))) + return ApplyServerOwnedWindowResize(backend, frame); frame.ok = backend->ResizeEGLWindowSurface(HandleFromToken(frame.surface), static_cast(frame.width), static_cast(frame.height)); @@ -1273,6 +1278,7 @@ namespace MobileGL::MG_Remote::Server { case SurfaceControlOp::ReleaseSurface: { const EGLSurface surface = HandleFromToken(frame.surface); backend->ReleaseEGLSurface(surface); + ForgetServerOwnedSurface(surface); // N-3: a released surface the held tuple names may have taken the context with it // (BackendObject::ReleaseEGLSurface -> OnEGLSurfaceReleased -> DestroyEGLContext once // nothing holds it current). Forgetting when the base class only DEFERRED the destroy @@ -1456,6 +1462,9 @@ namespace MobileGL::MG_Remote::Server { window.Handle = lease.window; window.Width = lease.width; window.Height = lease.height; + // Review fix: the backend is told which window is the server's own, so its surface init + // reports the extent for this window and for no window a client named (MGPipeServerOwnedWindow). + MG_Pipe::MGPipeServerSetOwnedWindow(lease.window); frame.ok = backend->CreateEGLWindowSurface(HandleFromToken(frame.surface), window); if (!frame.ok) { MGLOG_E("MG_Remote server: the backend could not create a window surface on the server window %p " @@ -1468,6 +1477,8 @@ namespace MobileGL::MG_Remote::Server { // N-3: as for any window surface - a (re)creation may have destroyed the held context. ForgetCurrentTuple(); m_surfaceMode = SessionSurfaceMode::OnScreen; + if (!IsServerOwnedSurface(HandleFromToken(frame.surface))) + m_serverOwnedSurfaces.push_back(HandleFromToken(frame.surface)); // The reply's geometry (SurfaceReply.width/height): the window's real extent, which is what // the headless client's eglQuerySurface answers from here on. frame.width = static_cast(lease.width); @@ -1480,9 +1491,48 @@ namespace MobileGL::MG_Remote::Server { return MOBILEGL_OK; } + Bool ServerLoop::IsServerOwnedSurface(EGLSurface surface) const { + return std::find(m_serverOwnedSurfaces.begin(), m_serverOwnedSurfaces.end(), surface) != + m_serverOwnedSurfaces.end(); + } + + void ServerLoop::ForgetServerOwnedSurface(EGLSurface surface) { + m_serverOwnedSurfaces.erase(std::remove(m_serverOwnedSurfaces.begin(), m_serverOwnedSurfaces.end(), surface), + m_serverOwnedSurfaces.end()); + } + + // P12 review fix (client resize). eglResize on a headless client's server-owned surface used to + // resize only the backend's record of the window - the phone's SurfaceView kept its size - and + // the client's EGL state answered the size it ASKED for. The window is the server's, so the + // resize is a geometry request like the creation's: the display is asked for the size and waited + // for (the same lease, re-taken by its holder), the backend surface follows the window's REAL + // extent, and that extent is the reply's width/height - which the client adopts. + MobileGLResult ServerLoop::ApplyServerOwnedWindowResize(MG_Backend::BackendObject* backend, + SurfaceControlFrame& frame) { + frame.ok = false; + frame.refusal = static_cast(SurfaceRefusalCode::None); + const Uint32 wantWidth = frame.width > 0 ? static_cast(frame.width) : 0u; + const Uint32 wantHeight = frame.height > 0 ? static_cast(frame.height) : 0u; + ServerWindowLease lease; + SurfaceRefusalCode refusal = SurfaceRefusalCode::None; + const MobileGLResult acquired = AcquireServerWindow(wantWidth, wantHeight, kServerWindowWaitMs, &lease, &refusal); + if (acquired != MOBILEGL_OK) { + frame.refusal = static_cast(refusal); + return acquired; + } + frame.ok = backend->ResizeEGLWindowSurface(HandleFromToken(frame.surface), lease.width, lease.height); + frame.width = static_cast(lease.width); + frame.height = static_cast(lease.height); + MGLOG_I("MG_Remote server: surface=window %ux%u owner=server resized (ServerOwned seq %llu, %ux%u requested%s)", + lease.width, lease.height, static_cast(frame.seq), wantWidth, wantHeight, + lease.sizeAsRequested ? "" : ", NOT reached"); + return MOBILEGL_OK; + } + void ServerLoop::EndServerWindowLease() { if (!m_holdsWindowLease) return; m_holdsWindowLease = false; + MG_Pipe::MGPipeServerSetOwnedWindow(nullptr); ServerDisplayInstance().EndLease(this); } @@ -1574,6 +1624,23 @@ namespace MobileGL::MG_Remote::Server { return reply; } + ServerOwnedWindowReply ServerResizeServerOwnedWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) { + // The ordinary resize frame: the server knows the surface is on its own window. + SurfaceControlFrame frame; + frame.kind = SurfaceControlOp::ResizeWindowSurface; + frame.surface = TokenFromHandle(surface); + frame.width = static_cast(width); + frame.height = static_cast(height); + const MobileGLResult rc = ServerLoopInstance().RunSurfaceControlFrame(frame); + ServerOwnedWindowReply reply; + reply.transport = rc; + reply.ok = rc == MOBILEGL_OK && frame.ok; + reply.refusal = static_cast(frame.refusal); + reply.width = frame.width > 0 ? static_cast(frame.width) : 0u; + reply.height = frame.height > 0 ? static_cast(frame.height) : 0u; + return reply; + } + Bool ServerMakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) { SurfaceControlFrame frame; // The release request (the three NO_* markers, ClassifyEglMakeCurrent's ClientRelease diff --git a/MobileGL/MG_Remote/Server/ServerLoop.h b/MobileGL/MG_Remote/Server/ServerLoop.h index c1ebb5eb..ccd2fa36 100644 --- a/MobileGL/MG_Remote/Server/ServerLoop.h +++ b/MobileGL/MG_Remote/Server/ServerLoop.h @@ -81,6 +81,7 @@ #include #include #include +#include namespace MobileGL::MG_Remote::Server { @@ -463,6 +464,13 @@ namespace MobileGL::MG_Remote::Server { void ReleaseLostServerWindow(); // Ends this loop's display lease if it holds one (after the backend let go of the window). void EndServerWindowLease(); + // Review fix: the ResizeWindowSurface arm for a server-owned surface - a geometry request to + // the display, the backend surface at the window's real extent, that extent in the reply. + MobileGLResult ApplyServerOwnedWindowResize(MG_Backend::BackendObject* backend, SurfaceControlFrame& frame); + Bool IsServerOwnedSurface(EGLSurface surface) const; + void ForgetServerOwnedSurface(EGLSurface surface); + // Apply thread only: this session's surfaces created on the server's window; reset by Start(). + std::vector m_serverOwnedSurfaces; // ServerDisplay's lost hook: called under the display's lock from Detach's thread. Sets the // request and rings the apply thread's bell; never blocks. static void ServerWindowLostThunk(void* self); @@ -642,6 +650,9 @@ namespace MobileGL::MG_Remote::Server { Uint32 height = 0; }; ServerOwnedWindowReply ServerCreateServerOwnedWindowSurface(EGLSurface surface, Uint32 width, Uint32 height); + // P12 review fix: ResizeWindowSurface for a surface created on the server's window. The server + // resizes its WINDOW (a geometry request) and replies with the window's real extent. + ServerOwnedWindowReply ServerResizeServerOwnedWindowSurface(EGLSurface surface, Uint32 width, Uint32 height); // Also RE-PUBLISHES THE CAPS SNAPSHOT on success (R-12). BackendObject::MakeEGLCurrent runs // InitCapabilities() on the first make-current per surface (BackendObject.cpp:341-347), so // this is the moment the server's answers stop being the empty ones Accept() published -