[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.
This commit is contained in:
2026-09-24 14:03:47 +00:00
parent 277940f39f
commit 29a82617e0
8 changed files with 164 additions and 17 deletions
@@ -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<const void*>(window) == MG_Pipe::MGPipeServerOwnedWindow();
PublishDefaultFramebufferDepthStencilFormat();
g_publishWindowExtent = false;
#else
@@ -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
+7
View File
@@ -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
@@ -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<int>(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<EGLint>(realWidth),
static_cast<EGLint>(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.
+31 -13
View File
@@ -39,6 +39,7 @@
#include <chrono>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <thread>
#include <utility>
#include <vector>
@@ -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<EGLSurface> 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<EGLSurface> 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<EGLint>(width),
static_cast<EGLint>(height));
if (width == 0 || height == 0 || !MG_State::pEGLContext) return;
const std::lock_guard<std::mutex> lock(g_serverOwnedSurfacesMutex);
for (const EGLSurface surface : g_serverOwnedSurfaces) {
(void)MG_State::pEGLContext->SetSurfaceExtent(surface, static_cast<EGLint>(width),
static_cast<EGLint>(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<std::mutex> 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<std::mutex> 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<std::mutex> 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(); }
+5 -2
View File
@@ -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 ==
+67
View File
@@ -17,6 +17,7 @@
#include <MG_Util/Debug/Log.h>
#include <MG_Util/Metrics/PipeStats.h>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstdlib>
@@ -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<EGLSurface>(frame.surface)))
return ApplyServerOwnedWindowResize(backend, frame);
frame.ok = backend->ResizeEGLWindowSurface(HandleFromToken<EGLSurface>(frame.surface),
static_cast<Uint32>(frame.width),
static_cast<Uint32>(frame.height));
@@ -1273,6 +1278,7 @@ namespace MobileGL::MG_Remote::Server {
case SurfaceControlOp::ReleaseSurface: {
const EGLSurface surface = HandleFromToken<EGLSurface>(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<EGLSurface>(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<EGLSurface>(frame.surface)))
m_serverOwnedSurfaces.push_back(HandleFromToken<EGLSurface>(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<Int>(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<Uint8>(SurfaceRefusalCode::None);
const Uint32 wantWidth = frame.width > 0 ? static_cast<Uint32>(frame.width) : 0u;
const Uint32 wantHeight = frame.height > 0 ? static_cast<Uint32>(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<Uint8>(refusal);
return acquired;
}
frame.ok = backend->ResizeEGLWindowSurface(HandleFromToken<EGLSurface>(frame.surface), lease.width, lease.height);
frame.width = static_cast<Int>(lease.width);
frame.height = static_cast<Int>(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<unsigned long long>(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<Int>(width);
frame.height = static_cast<Int>(height);
const MobileGLResult rc = ServerLoopInstance().RunSurfaceControlFrame(frame);
ServerOwnedWindowReply reply;
reply.transport = rc;
reply.ok = rc == MOBILEGL_OK && frame.ok;
reply.refusal = static_cast<SurfaceRefusalCode>(frame.refusal);
reply.width = frame.width > 0 ? static_cast<Uint32>(frame.width) : 0u;
reply.height = frame.height > 0 ? static_cast<Uint32>(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
+11
View File
@@ -81,6 +81,7 @@
#include <mutex>
#include <thread>
#include <type_traits>
#include <vector>
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<EGLSurface> 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 -