From 035f1b07ae7a4a1d05c5e9c9ed5fab726d4e5686 Mon Sep 17 00:00:00 2001 From: gold Date: Thu, 1 Oct 2026 01:11:21 +0800 Subject: [PATCH] [Feature] (disaggregated): MOBILEGL_IPC_SURFACE=host - the display host owns the screen and the frames The third on-screen shape, and the one this whole line was built toward: Anland keeps Wayland, input and the display, and the render server draws into the frames the display host allocates. client (container) asks for a window surface with no window of its own server (Android) takes a frame from the host over the host frame bridge and binds it as the draw target of that surface host (the app that owns the screen) offers each frame, presents it when the server says it is drawn The pieces, in the order a frame travels: - Config: MOBILEGL_IPC_SURFACE=host beside =server, and HostFrameWindowSurfaces() as the one predicate every client-side consumer asks (EGLImpl accepts a NULL window for either on-screen shape, and BackendObject_Remote routes the creation to the server instead of sending a handle). - Wire: the frame-local tag kHostFrameWindowBackend, the mirror of kServerOwnedWindowBackend - it names no WindowBackend of the pull build's enum, and the server consumes it before the unpack that would refuse it. - Server: ApplyHostFrameWindowSurface replaces nothing, unlike the server-owned arm: there is no window of either side's to substitute, because the backend takes the frames from the bridge. - Backend: SurfaceKind::HostFrame, InitHostFrameSurface (the EGL context comes from the pbuffer path; what changes is what it draws INTO) and PresentHostFrame (glFinish, answer the host, take the next frame) - and SwapEGLBuffers routes to it, because a host-framed surface has no swapchain to swap: the host is what puts the frame on the glass. HostFrameTarget and HostFrameBridge, committed just before this, are what the backend uses. Compiles and links in this tree (libMobileGL.so and libMobileGLServer.so, exit 0). What is NOT here yet, and is the next thing: the run that puts Plasma on the phone's screen through it. --- MobileGL/Config.h | 10 +++ MobileGL/ConfigLoader.cpp | 18 +++-- MobileGL/MG_Backend/BackendObject.cpp | 48 +++++++++++++ MobileGL/MG_Backend/BackendObject.h | 13 +++- MobileGL/MG_Backend/HostFrameBridge.cpp | 16 +++-- MobileGL/MG_Backend/HostFrameBridge.h | 6 +- MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp | 59 +++++++++++----- .../MG_Remote/Client/BackendObject_Remote.cpp | 48 +++++++++++++ .../MG_Remote/Client/BackendObject_Remote.h | 3 + MobileGL/MG_Remote/Server/ServerLoop.cpp | 69 +++++++++++++++++++ MobileGL/MG_Remote/Server/ServerLoop.h | 11 +++ .../MG_Remote/Server/SurfaceControlFrame.h | 8 +++ MobileGL/MG_State/EGLState/Core.cpp | 12 ++++ MobileGL/MG_State/EGLState/Core.h | 7 ++ 14 files changed, 298 insertions(+), 30 deletions(-) diff --git a/MobileGL/Config.h b/MobileGL/Config.h index 2d61289b..2b170d9c 100644 --- a/MobileGL/Config.h +++ b/MobileGL/Config.h @@ -499,6 +499,7 @@ namespace MobileGL::MG_Config { enum class IpcSurface : Uint8 { Offscreen = 0, // a window surface is the client's own window, as before (the default) Server = 1, // a window surface is the SERVER's window: WindowKind::ServerOwned + Host = 2, // a window surface draws into frames the DISPLAY HOST owns; the host presents }; // IpcTable::AdoptTier when MOBILEGL_IPC_ADOPT_TIER is unset (or unparseable): T0 by default, // with QUIET fallbacks. Not a tier a user can type - the knob admits 0..2. @@ -729,6 +730,15 @@ namespace MobileGL::MG_Config { inline Bool ServerOwnedWindowSurfaces() { return Transport == TransportMode::Spawn && Ipc.Surface == IpcSurface::Server; } + + // P12: the other on-screen shape, and the mirror image of the one above. The display host + // (the Android side that owns the screen) allocates the frames, offers each one over the + // host frame bridge, and puts it on the glass when this side says it is drawn - so a + // client that asks for this has no window of its own either, and the server takes the + // frames rather than making a window. + inline Bool HostFrameWindowSurfaces() { + return Transport == TransportMode::Spawn && Ipc.Surface == IpcSurface::Host; + } #else // The whole point: in a build without MG_Remote this folds at compile time, so // `if (MG_Config::Transport != MG_Config::TransportMode::Monolith)` in Init.cpp is a diff --git a/MobileGL/ConfigLoader.cpp b/MobileGL/ConfigLoader.cpp index d02abdfd..6b170840 100644 --- a/MobileGL/ConfigLoader.cpp +++ b/MobileGL/ConfigLoader.cpp @@ -494,8 +494,8 @@ namespace MobileGL::MG_ConfigLoader { } } - // P12 (on-screen server window): MOBILEGL_IPC_SURFACE = offscreen | server (Config.h has - // the semantics). An unknown value is named and read as the default, like every knob here. + // P12 (on-screen): MOBILEGL_IPC_SURFACE = offscreen | server | host (Config.h has the + // semantics). An unknown value is named and read as the default, like every knob here. { String surface; QueryEnvVariable("MOBILEGL_IPC_SURFACE", surface, "offscreen"); @@ -505,9 +505,11 @@ namespace MobileGL::MG_ConfigLoader { ipc.Surface = MG_Config::IpcSurface::Offscreen; } else if (surface == "server") { ipc.Surface = MG_Config::IpcSurface::Server; + } else if (surface == "host") { + ipc.Surface = MG_Config::IpcSurface::Host; } else { MGLOG_W("Config: Ignoring invalid env variable MOBILEGL_IPC_SURFACE='%s'; expected " - "offscreen|server, using offscreen", + "offscreen|server|host, using offscreen", surface.c_str()); ipc.Surface = MG_Config::IpcSurface::Offscreen; } @@ -515,12 +517,14 @@ namespace MobileGL::MG_ConfigLoader { // parses this before RunSession sets its own transport, and has no client half anyway.) const char* role = std::getenv("MOBILEGL_IPC_ROLE"); const Bool serverProcess = role != nullptr && std::strcmp(role, "server") == 0; - if (ipc.Surface == MG_Config::IpcSurface::Server && !serverProcess && + if (ipc.Surface != MG_Config::IpcSurface::Offscreen && !serverProcess && MG_Config::Transport != MG_Config::TransportMode::Spawn) { // Said, not silently honoured: there is no remote server here to own a window. - MGLOG_W("Config: MOBILEGL_IPC_SURFACE=server is IGNORED - it asks a remote server " - "(MOBILEGL_TRANSPORT=spawn, a fork or tcp:// control) to own the window surface, " - "and this run has none. Window surfaces stay the client's own"); + MGLOG_W("Config: MOBILEGL_IPC_SURFACE=%s is IGNORED - it asks for an on-screen " + "surface (the server's own window, or the frames the display host owns) and " + "this run has no remote server to provide one: MOBILEGL_TRANSPORT is not " + "spawn. Window surfaces stay the client's own", + ipc.Surface == MG_Config::IpcSurface::Server ? "server" : "host"); } } diff --git a/MobileGL/MG_Backend/BackendObject.cpp b/MobileGL/MG_Backend/BackendObject.cpp index f238dd3a..83f05a58 100644 --- a/MobileGL/MG_Backend/BackendObject.cpp +++ b/MobileGL/MG_Backend/BackendObject.cpp @@ -209,6 +209,28 @@ namespace MobileGL::MG_Backend { return true; } + // The frame itself is the backend's business (InitHostFrameSurface); what happens here is + // the registration every surface kind shares, so the swap path and the state mirrors see + // a host-framed surface exactly like any other. + Bool BackendObject::CreateEGLHostFrameSurface(EGLSurface surface, Uint32 width, Uint32 height) { + const std::lock_guard lock(m_eglStateMutex); + if (!m_eglDisplayInitialized) { + MGLOG_E("CreateEGLHostFrameSurface failed: EGL display is not initialized"); + return false; + } + if (surface == EGL_NO_SURFACE) { + MGLOG_E("CreateEGLHostFrameSurface failed: invalid EGLSurface"); + return false; + } + auto& state = m_eglSurfaces[surface]; + state = EGLSurfaceState{ + .Kind = SurfaceKind::HostFrame, + .Width = static_cast(std::max(width, 1)), + .Height = static_cast(std::max(height, 1)), + }; + return ActivateEGLSurface(surface); + } + Bool BackendObject::CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) { const std::lock_guard lock(m_eglStateMutex); return RegisterEGLPbufferSurface(surface, width, height) && ActivateEGLSurface(surface); @@ -290,6 +312,11 @@ namespace MobileGL::MG_Backend { MGLOG_E("ActivateEGLSurface failed: backend InitPbufferSurface failed"); return false; } + } else if (surfaceState->Kind == SurfaceKind::HostFrame) { + if (!InitHostFrameSurface(surfaceState->Width, surfaceState->Height)) { + MGLOG_E("ActivateEGLSurface failed: backend InitHostFrameSurface failed"); + return false; + } } else { MGLOG_E("ActivateEGLSurface failed: unsupported surface kind"); return false; @@ -387,6 +414,12 @@ namespace MobileGL::MG_Backend { return false; } + // A host-framed surface has no swapchain: presenting it means telling the host the frame + // is drawn, because the host is what puts it on the glass. + if (m_eglSurfaceKind == SurfaceKind::HostFrame) { + return PresentHostFrame(); + } + const auto& backendFunctions = GetBackendFunctions(); if (!backendFunctions.Present) { MGLOG_E("SwapEGLBuffers failed: backend Present function is null"); @@ -404,6 +437,21 @@ namespace MobileGL::MG_Backend { } } + // A backend that has no host-framed surfaces says so by name: the server may ask for one + // (MOBILEGL_IPC_SURFACE=host) against a build that cannot serve it, and that has to be a + // refusal with a reason rather than a surface that draws nowhere. + Bool BackendObject::InitHostFrameSurface(EGLint width, EGLint height) { + (void)width; + (void)height; + MGLOG_E("InitHostFrameSurface: this backend has no host-framed surfaces"); + return false; + } + + Bool BackendObject::PresentHostFrame() { + MGLOG_E("PresentHostFrame: this backend has no host-framed surfaces"); + return false; + } + Bool BackendObject::IsEGLSurfaceCurrent(EGLSurface surface) const { if (surface == EGL_NO_SURFACE) { return false; diff --git a/MobileGL/MG_Backend/BackendObject.h b/MobileGL/MG_Backend/BackendObject.h index 94639b31..86abcb13 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -595,6 +595,13 @@ namespace MobileGL { virtual Bool CreateEGLWindowSurface(EGLSurface surface, const WindowHandle& handle); virtual Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height); virtual Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height); + // The host owns the screen: this side never gets a window of the client's to draw + // into, so the surface it draws into is a frame the host allocated and offered over + // the host frame bridge. Each swap tells the host the frame is done and takes the + // next; presentation is the host's, not a swapchain's. + virtual Bool CreateEGLHostFrameSurface(EGLSurface surface, Uint32 width, Uint32 height); + virtual Bool InitHostFrameSurface(EGLint width, EGLint height); + virtual Bool PresentHostFrame(); virtual Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx); virtual Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw); // Forwards the app-requested eglSwapInterval to the backend's native @@ -616,7 +623,11 @@ namespace MobileGL { enum class SurfaceKind { None, Window, - Pbuffer + Pbuffer, + // A surface whose drawing target is a frame the display host owns. There is no + // swapchain behind it: the host puts the frame on the glass when this side says + // it is drawn, which is why presenting one is its own call and not a Present. + HostFrame }; struct EGLCurrentState { diff --git a/MobileGL/MG_Backend/HostFrameBridge.cpp b/MobileGL/MG_Backend/HostFrameBridge.cpp index 37091418..1a65d7d9 100644 --- a/MobileGL/MG_Backend/HostFrameBridge.cpp +++ b/MobileGL/MG_Backend/HostFrameBridge.cpp @@ -63,13 +63,21 @@ namespace MobileGL::MG_Backend { sockaddr_un address{}; address.sun_family = AF_UNIX; if (std::strlen(name) >= sizeof(address.sun_path)) { - why = std::format("the socket path is longer than the kernel takes: {}", name); + why = std::format("the socket name is longer than the kernel takes: {}", name); Close(); return false; } - std::strncpy(address.sun_path, name, sizeof(address.sun_path) - 1); - // A leftover socket file from a server that died is not a reason to refuse the host. - ::unlink(name); + // "@name" is the abstract namespace: sun_path[0] is NUL and the name follows it. + // Nothing is created on disk, so nothing is left behind and no file permission stands + // between two apps that may not write each other's directories. + if (name[0] == '@') { + address.sun_path[0] = 0; + std::strncpy(address.sun_path + 1, name + 1, sizeof(address.sun_path) - 2); + } else { + std::strncpy(address.sun_path, name, sizeof(address.sun_path) - 1); + // A leftover socket file from a server that died is not a reason to refuse the host. + ::unlink(name); + } if (::bind(m_listen, reinterpret_cast(&address), sizeof(address)) != 0) { why = std::format("bind({}): {}", name, std::strerror(errno)); Close(); diff --git a/MobileGL/MG_Backend/HostFrameBridge.h b/MobileGL/MG_Backend/HostFrameBridge.h index cca3945e..8ff0a844 100644 --- a/MobileGL/MG_Backend/HostFrameBridge.h +++ b/MobileGL/MG_Backend/HostFrameBridge.h @@ -29,7 +29,11 @@ namespace MobileGL::MG_Backend { // makes a mismatched pair fail loudly instead of being read as plausible numbers. inline constexpr Uint32 kHostFrameMagic = 0x4D474C41u; // the host spells it "ALGM" in memory inline constexpr Uint32 kHostFrameVersion = 1u; - inline constexpr const char* kHostFrameSocket = "/data/local/tmp/mobilegl_bridge.sock"; + // AN ABSTRACT NAME, not a path: the two ends are separate apps, and neither may create a + // socket file in /data/local/tmp (its directory grants others traverse only). An abstract + // name lives in the kernel namespace instead of the filesystem, so there is no file + // permission to get wrong - the same reason this project names its own endpoints @mgl-*. + inline constexpr const char* kHostFrameSocket = "@mobilegl-host-frame"; struct HostFrameOffer { Uint32 Magic; diff --git a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp index 16a62203..87130e00 100644 --- a/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp +++ b/MobileGL/MG_Impl/EGLImpl/EGLImpl.cpp @@ -137,8 +137,11 @@ namespace MobileGL::MG_Impl::EGLImpl { // the window is the SERVER's: a headless client passes none, so NULL is not // EGL_BAD_NATIVE_WINDOW here. Whatever the client passed never reaches the wire - the // remote backend sends WindowKind::ServerOwned with token 0 (BackendObject_Remote). - const Bool serverOwnedWindow = MG_Config::ServerOwnedWindowSurfaces(); - if (IsNullNativeHandle(window) && !serverOwnedWindow) { + // Either on-screen shape leaves this side without a window of its own: =server asks the + // server for its own window, =host draws into the frames the display host offers. + const Bool windowlessOnScreen = + MG_Config::ServerOwnedWindowSurfaces() || MG_Config::HostFrameWindowSurfaces(); + if (IsNullNativeHandle(window) && !windowlessOnScreen) { #else if (IsNullNativeHandle(window)) { #endif @@ -157,15 +160,15 @@ namespace MobileGL::MG_Impl::EGLImpl { // alike; the same game in a landscape window was right). The native window it handed us // knows the size the game believes it has: ask for THAT, and the server fixes its window // buffer to it and aspect-fits the view (MobileGLDisplayActivity), letterboxed. - if (serverOwnedWindow && requestedWidth == 0 && requestedHeight == 0 && !IsNullNativeHandle(window)) { + if (windowlessOnScreen && requestedWidth == 0 && requestedHeight == 0 && !IsNullNativeHandle(window)) { auto* native = reinterpret_cast(ToVoidHandle(window)); const int32_t nativeWidth = ANativeWindow_getWidth(native); const int32_t nativeHeight = ANativeWindow_getHeight(native); if (nativeWidth > 0 && nativeHeight > 0) { requestedWidth = static_cast(nativeWidth); requestedHeight = static_cast(nativeHeight); - MGLOG_I("eglCreateWindowSurface: no EGL_WIDTH/EGL_HEIGHT; the server-owned window is asked for the " - "native window's %dx%d", nativeWidth, nativeHeight); + MGLOG_I("eglCreateWindowSurface: no EGL_WIDTH/EGL_HEIGHT; the on-screen surface is asked for " + "the native window's %dx%d", nativeWidth, nativeHeight); } } #endif @@ -177,11 +180,20 @@ namespace MobileGL::MG_Impl::EGLImpl { }; #if MOBILEGL_BUILD_DISAGGREGATED - EGLSurface surface = serverOwnedWindow - ? state->CreateServerOwnedWindowSurface( - dpy, config, windowHandle.Handle, static_cast(windowHandle.Width), - static_cast(windowHandle.Height), /*platformWindow=*/false) - : state->CreateWindowSurface(dpy, config, window, attrib_list); + EGLSurface surface = EGL_NO_SURFACE; + if (MG_Config::ServerOwnedWindowSurfaces()) { + // The server's own window: it makes one and draws into it. + surface = state->CreateServerOwnedWindowSurface( + dpy, config, windowHandle.Handle, static_cast(windowHandle.Width), + static_cast(windowHandle.Height), /*platformWindow=*/false); + } else if (MG_Config::HostFrameWindowSurfaces()) { + // The display host's frames, as above: no window of the client's either way. + surface = state->CreateHostFrameWindowSurface( + dpy, config, windowHandle.Handle, static_cast(windowHandle.Width), + static_cast(windowHandle.Height), /*platformWindow=*/false); + } else { + surface = state->CreateWindowSurface(dpy, config, window, attrib_list); + } #else EGLSurface surface = state->CreateWindowSurface(dpy, config, window, attrib_list); #endif @@ -724,8 +736,11 @@ namespace MobileGL::MG_Impl::EGLImpl { #if MOBILEGL_BUILD_DISAGGREGATED // P12 (on-screen server window), D1: as CreateWindowSurface - with MOBILEGL_IPC_SURFACE=server // the window is the server's, and a headless client's NULL is accepted. - const Bool serverOwnedWindow = MG_Config::ServerOwnedWindowSurfaces(); - if (native_window == nullptr && !serverOwnedWindow) { + // Either on-screen shape leaves this side without a window of its own: =server asks the + // server for its own window, =host draws into the frames the display host offers. + const Bool windowlessOnScreen = + MG_Config::ServerOwnedWindowSurfaces() || MG_Config::HostFrameWindowSurfaces(); + if (native_window == nullptr && !windowlessOnScreen) { #else if (native_window == nullptr) { #endif @@ -749,11 +764,21 @@ namespace MobileGL::MG_Impl::EGLImpl { }; #if MOBILEGL_BUILD_DISAGGREGATED - EGLSurface surface = serverOwnedWindow - ? state->CreateServerOwnedWindowSurface( - dpy, config, native_window, static_cast(windowHandle.Width), - static_cast(windowHandle.Height), /*platformWindow=*/true) - : state->CreatePlatformWindowSurface(dpy, config, native_window, attrib_list); + EGLSurface surface = EGL_NO_SURFACE; + if (MG_Config::ServerOwnedWindowSurfaces()) { + // The server's own window: it makes one and draws into it. + surface = state->CreateServerOwnedWindowSurface( + dpy, config, native_window, static_cast(windowHandle.Width), + static_cast(windowHandle.Height), /*platformWindow=*/true); + } else if (MG_Config::HostFrameWindowSurfaces()) { + // The display host's frames: it allocates them, the server draws into them, + // and the host puts each one on the glass when the server says it is drawn. + surface = state->CreateHostFrameWindowSurface( + dpy, config, native_window, static_cast(windowHandle.Width), + static_cast(windowHandle.Height), /*platformWindow=*/true); + } else { + surface = state->CreatePlatformWindowSurface(dpy, config, native_window, attrib_list); + } #else EGLSurface surface = state->CreatePlatformWindowSurface(dpy, config, native_window, attrib_list); #endif diff --git a/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp b/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp index c09e8578..e329a4e4 100644 --- a/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp +++ b/MobileGL/MG_Remote/Client/BackendObject_Remote.cpp @@ -216,6 +216,10 @@ namespace MobileGL::MG_Remote::Client { WaitForApplyBeforeEglForwarder("CreateEGLWindowSurface"); // P12 (D1): unless the window is the SERVER's - then there is no handle of ours to tell. if (MG_Config::ServerOwnedWindowSurfaces()) return CreateServerOwnedWindowSurface(surface, handle); + // The other on-screen shape: the frames belong to the display host, and the server takes + // them from the bridge. As for the server-owned shape, no window of the client's crosses + // the wire - only the size it asked for. + if (MG_Config::HostFrameWindowSurfaces()) return CreateHostFrameWindowSurface(surface, handle); Server::ServerSetWindowHandle(handle); if (!Server::ServerCreateEGLWindowSurface(surface, handle)) return false; // The server's surface init published the default framebuffer's shape as a @@ -295,6 +299,10 @@ namespace MobileGL::MG_Remote::Client { // dereferenced - the client's InitWindowSurface is a no-op - and never crosses the wire // (the ServerOwned frame carries token 0). char g_serverOwnedWindowPlaceholder = 0; + // The host-framed shape names no window of the client's either; the base class still wants + // a handle to register the surface with, and this is the same stand-in the server-owned + // shape uses. + char g_hostFrameWindowPlaceholder = 0; } // namespace Bool BackendObject_Remote::CreateServerOwnedWindowSurface(EGLSurface surface, @@ -360,6 +368,46 @@ namespace MobileGL::MG_Remote::Client { return MG_Backend::BackendObject::CreateEGLWindowSurface(surface, local); } + Bool BackendObject_Remote::CreateHostFrameWindowSurface(EGLSurface surface, + const MG_Backend::WindowHandle& handle) { + // The size crosses the wire and nothing else of the client's does: the server takes frames + // from the display host at that size and answers with the extent the surface has. + const Server::ServerOwnedWindowReply reply = + Server::ServerCreateHostFrameWindowSurface(surface, handle.Width, handle.Height); + if (!reply.ok) { + // FAILS BY NAME ON THIS SIDE TOO, as the server-owned shape does: the server logged its + // reason, and the refusal code is what lets this line say the same thing. + switch (reply.refusal) { + case Server::SurfaceRefusalCode::SurfaceModeMismatch: + MGLOG_E("MG_Remote client: Refuse HostFrame (SurfaceModeMismatch) - MOBILEGL_IPC_SURFACE=host " + "asked for this %ux%u window surface in a session that already chose a pbuffer: only " + "one rendering path is active per session. eglCreateWindowSurface fails with " + "EGL_BAD_NATIVE_WINDOW", + handle.Width, handle.Height); + break; + case Server::SurfaceRefusalCode::NoServerWindow: + MGLOG_E("MG_Remote client: Refuse HostFrame (NoServerWindow) - the server could not take a frame " + "from the display host for this %ux%u surface; its log names what the host or the " + "bridge said (its own answer was %ux%u)", + handle.Width, handle.Height, reply.width, reply.height); + break; + default: + MGLOG_E("MG_Remote client: Refuse HostFrame (code %u) for this %ux%u window surface", + static_cast(reply.refusal), handle.Width, handle.Height); + break; + } + return false; + } + // From here the frames are the server's and the host's business; this side only registers the + // surface, exactly as it does for the server-owned shape. + MG_Backend::WindowHandle local = handle; + if (local.Backend == MG_Backend::WindowBackend::Unknown) local.Backend = MG_Backend::WindowBackend::Android; + if (local.Handle == nullptr) local.Handle = &g_hostFrameWindowPlaceholder; + local.Width = reply.width; + local.Height = reply.height; + return MG_Backend::BackendObject::CreateEGLWindowSurface(surface, local); + } + Bool BackendObject_Remote::MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) { // FORWARD FIRST, THEN RUN THE BASE. The server has to own the context before the base diff --git a/MobileGL/MG_Remote/Client/BackendObject_Remote.h b/MobileGL/MG_Remote/Client/BackendObject_Remote.h index 1af3d405..fb07f50e 100644 --- a/MobileGL/MG_Remote/Client/BackendObject_Remote.h +++ b/MobileGL/MG_Remote/Client/BackendObject_Remote.h @@ -91,6 +91,9 @@ namespace MobileGL::MG_Remote::Client { // CreateEGLWindowSurface - ONE ServerOwned frame, no SetWindowHandle, the server's real // geometry adopted before it returns, and every refusal named in this process's log. Bool CreateServerOwnedWindowSurface(EGLSurface surface, const MG_Backend::WindowHandle& handle); + // MOBILEGL_IPC_SURFACE=host: the frames are the display host's, and the server takes + // them from the host frame bridge. Nothing of the client's crosses the wire here either. + Bool CreateHostFrameWindowSurface(EGLSurface surface, const MG_Backend::WindowHandle& handle); // The generation of the snapshot m_formatCapabilities was filled from. Exposed only // through the log line on a refresh: a cache that silently stopped tracking the mirror diff --git a/MobileGL/MG_Remote/Server/ServerLoop.cpp b/MobileGL/MG_Remote/Server/ServerLoop.cpp index fab60fb4..8e2dadce 100644 --- a/MobileGL/MG_Remote/Server/ServerLoop.cpp +++ b/MobileGL/MG_Remote/Server/ServerLoop.cpp @@ -287,6 +287,7 @@ namespace MobileGL::MG_Remote::Server { m_surfaceMode = SessionSurfaceMode::None; m_holdsWindowLease = false; m_serverOwnedSurfaces.clear(); + m_hostFrameSurfaces.clear(); m_windowLostRequested.store(false, std::memory_order_release); m_serverWindowsLost.store(0, std::memory_order_release); m_testLogFloodLines = ApplyLogFloodLinesForTest(); @@ -1338,6 +1339,8 @@ namespace MobileGL::MG_Remote::Server { // P12 (D3): the SERVER's window, asked for by a headless client. Before the unpack, // which would refuse the frame-local tag as a WindowBackend out of range. if (frame.windowBackend == kServerOwnedWindowBackend) return ApplyServerOwnedWindowSurface(backend, frame); + // The mirror image: the frames are the display host's, and it presents them. + if (frame.windowBackend == kHostFrameWindowBackend) return ApplyHostFrameWindowSurface(backend, frame); MG_Backend::WindowHandle window; if (!UnpackWindowHandle(frame, &window)) { frame.ok = false; @@ -1445,6 +1448,7 @@ namespace MobileGL::MG_Remote::Server { const EGLSurface surface = HandleFromToken(frame.surface); backend->ReleaseEGLSurface(surface); ForgetServerOwnedSurface(surface); + ForgetHostFrameSurface(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 @@ -1658,6 +1662,52 @@ namespace MobileGL::MG_Remote::Server { return MOBILEGL_OK; } + MobileGLResult ServerLoop::ApplyHostFrameWindowSurface(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; + // Both on-screen shapes are the same session mode: one rendering path per session, and + // this is the on-screen one whichever side owns the window. + if (!SessionSurfaceModeAdmits(m_surfaceMode, /*serverOwnedWindow=*/true)) { + frame.refusal = static_cast(SurfaceRefusalCode::SurfaceModeMismatch); + MGLOG_E("MG_Remote server: SurfaceModeMismatch - a HostFrame CreateWindowSurface (seq %llu, " + "%ux%u) in a session whose surface mode is %s. Refused, the session is not latched", + static_cast(frame.seq), wantWidth, wantHeight, + SessionSurfaceModeName(m_surfaceMode)); + return MOBILEGL_ERR_INVALID_ARGUMENT; + } + const EGLSurface surface = HandleFromToken(frame.surface); + // THE SUBSTITUTION, the other way round from ServerOwned: nothing of the client's and + // nothing of the server's goes into the backend call - the backend takes the frames from + // the display host, and every swap hands the next one back to it. + frame.ok = backend->CreateEGLHostFrameSurface(surface, wantWidth, wantHeight); + if (!frame.ok) { + MGLOG_E("MG_Remote server: the backend could not take the display host frames (seq %llu, %ux%u)", + static_cast(frame.seq), wantWidth, wantHeight); + return MOBILEGL_OK; + } + ForgetCurrentTuple(); + m_surfaceMode = SessionSurfaceMode::OnScreen; + if (!IsHostFrameSurface(surface)) m_hostFrameSurfaces.push_back(surface); + frame.width = static_cast(wantWidth); + frame.height = static_cast(wantHeight); + MGLOG_I("MG_Remote server: surface=window %ux%u owner=host (HostFrame seq %llu)", wantWidth, + wantHeight, static_cast(frame.seq)); + return MOBILEGL_OK; + } + + Bool ServerLoop::IsHostFrameSurface(EGLSurface surface) const { + return std::find(m_hostFrameSurfaces.begin(), m_hostFrameSurfaces.end(), surface) != + m_hostFrameSurfaces.end(); + } + + void ServerLoop::ForgetHostFrameSurface(EGLSurface surface) { + m_hostFrameSurfaces.erase(std::remove(m_hostFrameSurfaces.begin(), m_hostFrameSurfaces.end(), surface), + m_hostFrameSurfaces.end()); + } + Bool ServerLoop::IsServerOwnedSurface(EGLSurface surface) const { return std::find(m_serverOwnedSurfaces.begin(), m_serverOwnedSurfaces.end(), surface) != m_serverOwnedSurfaces.end(); @@ -1770,6 +1820,25 @@ namespace MobileGL::MG_Remote::Server { return rc == MOBILEGL_OK && frame.ok; } + ServerOwnedWindowReply ServerCreateHostFrameWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) { + // One frame, and no SetWindowHandle in front of it: the client names no window, and this + // side does not make one - the frames are the display host's, taken from the bridge. + SurfaceControlFrame frame; + frame.kind = SurfaceControlOp::CreateWindowSurface; + frame.surface = TokenFromHandle(surface); + frame.windowBackend = kHostFrameWindowBackend; + frame.nativeToken = 0; + 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; + } ServerOwnedWindowReply ServerCreateServerOwnedWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) { // P12 (D1/D2): ONE frame, and no SetWindowHandle in front of it - the window is the // server's, so there is nothing of the client's to name. The frame-local tag is what the diff --git a/MobileGL/MG_Remote/Server/ServerLoop.h b/MobileGL/MG_Remote/Server/ServerLoop.h index c0ff6e4f..c9c8ec05 100644 --- a/MobileGL/MG_Remote/Server/ServerLoop.h +++ b/MobileGL/MG_Remote/Server/ServerLoop.h @@ -459,6 +459,9 @@ namespace MobileGL::MG_Remote::Server { // P12. The ServerOwned arm of the CreateWindowSurface dispatch (D3, D4). MobileGLResult ApplyServerOwnedWindowSurface(MG_Backend::BackendObject* backend, SurfaceControlFrame& frame); + MobileGLResult ApplyHostFrameWindowSurface(MG_Backend::BackendObject* backend, SurfaceControlFrame& frame); + Bool IsHostFrameSurface(EGLSurface surface) const; + void ForgetHostFrameSurface(EGLSurface surface); // P12 (D6), apply thread: the lost window's backend surface goes, the lease ends, and the // session latches ServerWindowLost. Runs from PumpControlRequest when Detach asked. void ReleaseLostServerWindow(); @@ -471,6 +474,10 @@ namespace MobileGL::MG_Remote::Server { void ForgetServerOwnedSurface(EGLSurface surface); // Apply thread only: this session's surfaces created on the server's window; reset by Start(). std::vector m_serverOwnedSurfaces; + // The surfaces whose drawing target is a frame the display host owns. Kept apart from + // the server-owned list because the two are different paths with different owners, + // and a resize or a release has to reach the right one. + std::vector m_hostFrameSurfaces; // 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); @@ -659,6 +666,10 @@ namespace MobileGL::MG_Remote::Server { Uint32 height = 0; }; ServerOwnedWindowReply ServerCreateServerOwnedWindowSurface(EGLSurface surface, Uint32 width, Uint32 height); + // The host-framed on-screen shape (MOBILEGL_IPC_SURFACE=host): the client names no window + // either, but the frames come from the display host over the host frame bridge, and the + // host presents them. The reply carries the size the surface was made at. + ServerOwnedWindowReply ServerCreateHostFrameWindowSurface(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); diff --git a/MobileGL/MG_Remote/Server/SurfaceControlFrame.h b/MobileGL/MG_Remote/Server/SurfaceControlFrame.h index c7811b4a..7d03552b 100644 --- a/MobileGL/MG_Remote/Server/SurfaceControlFrame.h +++ b/MobileGL/MG_Remote/Server/SurfaceControlFrame.h @@ -87,6 +87,14 @@ namespace MobileGL::MG_Remote::Server { // substitutes its own window there; no pointer crosses the wire (Rule G/H). inline constexpr Int kServerOwnedWindowBackend = 0x10000; + // The OTHER on-screen shape, and its mirror image. With MOBILEGL_IPC_SURFACE=server the + // client has no window and the SERVER's own goes into the backend call; with =host the + // client still has no window, but the frames are the display host's - it allocates them, + // offers each one over the host frame bridge, and puts them on the glass itself. Same + // kind of frame-local tag as the one above, and for the same reason: it names no + // WindowBackend of MG_Backend's enum, which is in the pull build (gate G1). + inline constexpr Int kHostFrameWindowBackend = 0x10001; + // P12. Why the server declined a surface op, carried back in the reply half (`refusal`). The // values ARE the wire's SurfaceRefusal (protocol.fbs); SurfaceOpCodec pins the agreement with // static_asserts, the way it pins SurfaceControlOp against SurfaceOpKind. None of them latches diff --git a/MobileGL/MG_State/EGLState/Core.cpp b/MobileGL/MG_State/EGLState/Core.cpp index 133d21b8..69e3ac0a 100644 --- a/MobileGL/MG_State/EGLState/Core.cpp +++ b/MobileGL/MG_State/EGLState/Core.cpp @@ -1091,6 +1091,18 @@ namespace MobileGL { return surface; } + // MOBILEGL_IPC_SURFACE=host: the registration is the server-owned one verbatim, because + // what makes a window surface host-framed is not its EGL state but where its pixels go - + // and that is decided one layer down, by the backend the creation calls next. + EGLContext::EGLSurfaceHandle EGLContext::CreateHostFrameWindowSurface(EGLDisplayHandle display, + EGLConfigHandle config, + const void* nativeWindow, + EGLint width, EGLint height, + Bool platformWindow) { + return CreateServerOwnedWindowSurface(display, config, nativeWindow, width, height, + platformWindow); + } + Bool EGLContext::SetSurfaceExtent(EGLSurfaceHandle surface, EGLint width, EGLint height) { const std::lock_guard lock(m_mutex); auto surfaceIt = m_surfaces.find(surface); diff --git a/MobileGL/MG_State/EGLState/Core.h b/MobileGL/MG_State/EGLState/Core.h index 3c4b24ba..8bba629e 100644 --- a/MobileGL/MG_State/EGLState/Core.h +++ b/MobileGL/MG_State/EGLState/Core.h @@ -97,6 +97,13 @@ namespace MobileGL { EGLSurfaceHandle CreateServerOwnedWindowSurface(EGLDisplayHandle display, EGLConfigHandle config, const void* nativeWindow, EGLint width, EGLint height, Bool platformWindow); + // MOBILEGL_IPC_SURFACE=host: the same registration under the other on-screen name. + // The EGL state does not care which side owns the frames - that is the backend's + // business - but a trace has to be able to tell the two runs apart, and the two + // paths part company in the backend call that follows. + EGLSurfaceHandle CreateHostFrameWindowSurface(EGLDisplayHandle display, EGLConfigHandle config, + const void* nativeWindow, EGLint width, + EGLint height, Bool platformWindow); // P12: the server's REAL extent of a surface - a server-owned window's geometry, from // the CreateWindowSurface reply and from every surface-changed event after it - so // eglQuerySurface(EGL_WIDTH/EGL_HEIGHT) answers what the server renders at. Unlike