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