From c88c6ff196a8d20adaae28f3a245704fffba1c4e Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Thu, 1 Oct 2026 09:47:18 -0400 Subject: [PATCH] [Fix] (Remote): the present record presents a host-framed surface A split client's swap reaches the server as a present record, and ServerVerbSink::OnPresent only ran the backend's Present - an eglSwapBuffers of the pbuffer the context was made with. BackendObject::SwapEGLBuffers is the only route to PresentHostFrame and a split client never takes it, so over TCP the display host was never told a frame was drawn: the app offered thousands of frames and presented none. BackendObject::PresentHostFrameIfCurrent presents the host frame when the current surface is host-framed and is a no-op otherwise; OnPresent calls it after Present and before the credit goes back, so a client released by the credit is released by a frame that is on its way to the glass. --- MobileGL/MG_Backend/BackendObject.cpp | 6 ++++++ MobileGL/MG_Backend/BackendObject.h | 5 +++++ MobileGL/MG_Remote/Server/PipeApplier.cpp | 9 +++++++++ 3 files changed, 20 insertions(+) diff --git a/MobileGL/MG_Backend/BackendObject.cpp b/MobileGL/MG_Backend/BackendObject.cpp index e1e31c80..835857d6 100644 --- a/MobileGL/MG_Backend/BackendObject.cpp +++ b/MobileGL/MG_Backend/BackendObject.cpp @@ -479,6 +479,12 @@ namespace MobileGL::MG_Backend { return false; } + Bool BackendObject::PresentHostFrameIfCurrent() { + const std::lock_guard lock(m_eglStateMutex); + if (!m_eglSurfaceInitialized || m_eglSurfaceKind != SurfaceKind::HostFrame) return true; + return PresentHostFrame(); + } + 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 86abcb13..21cb5996 100644 --- a/MobileGL/MG_Backend/BackendObject.h +++ b/MobileGL/MG_Backend/BackendObject.h @@ -602,6 +602,11 @@ namespace MobileGL { virtual Bool CreateEGLHostFrameSurface(EGLSurface surface, Uint32 width, Uint32 height); virtual Bool InitHostFrameSurface(EGLint width, EGLint height); virtual Bool PresentHostFrame(); + // The present RECORD's half of a host-framed swap. A split client's swap reaches the + // server as a present record (ServerVerbSink::OnPresent), never as SwapEGLBuffers, so + // without this the host was never told a frame was drawn. True when the current + // surface is not host-framed: there is nothing to present then. + Bool PresentHostFrameIfCurrent(); 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 diff --git a/MobileGL/MG_Remote/Server/PipeApplier.cpp b/MobileGL/MG_Remote/Server/PipeApplier.cpp index a2e2ff5a..ecaeb033 100644 --- a/MobileGL/MG_Remote/Server/PipeApplier.cpp +++ b/MobileGL/MG_Remote/Server/PipeApplier.cpp @@ -404,6 +404,15 @@ namespace MobileGL::MG_Remote::Server { // was wanted, recorded here so a future swap-without-present is looked for rather than // assumed impossible. Presents() is exposed for a lane that wants to make the comparison. table->Present(); + // A HOST-FRAMED SURFACE IS PRESENTED BY TELLING THE HOST, not by the swap above (which + // swaps the pbuffer the context was made with). BackendObject::SwapEGLBuffers is the + // only other route to PresentHostFrame and a split client never reaches it, so this is + // where the display host learns the frame is drawn - before the credit goes back, so a + // client released by it is released by a frame that is on its way to the glass. + if (m_backend != nullptr) { + if (!m_backend->PresentHostFrameIfCurrent()) + MGLOG_E_ONCE("OnPresent: the host frame was not presented (PresentHostFrame failed; see above)"); + } ++m_presents; // FrameSerial 0 means "the server stamps its own" (c1-v1 8.3): P5 has no client-side // present credit, so the client sends 0 and the frame count on this side IS the serial.