[Fix] (P12): publish the server window's extent from the display on every resize, at once, and let eglQuerySurface take it in

This commit is contained in:
2026-10-02 12:03:52 -04:00
parent 16b07cf4b5
commit 258b946086
21 changed files with 392 additions and 89 deletions
+11
View File
@@ -710,6 +710,17 @@ namespace MobileGL {
(void)window, (void)width, (void)height;
return false;
}
// `window` changed size to `width`x`height` (the display's report - the one size to publish).
// Its surfaces publish that extent to the client now, without waiting for a present; a
// swapchain that has to follow is rebuilt when no frame is half-recorded, else at the
// present. False: nothing was published (no surface on it, or not supported).
virtual Bool ServerWindowResized(void* window, Uint32 width, Uint32 height) {
(void)window, (void)width, (void)height;
return false;
}
// CLIENT side, from eglQuerySurface(EGL_WIDTH/EGL_HEIGHT): a backend whose surface extents
// change under the application (a remote server's window) applies what it has received.
virtual void RefreshSurfaceExtent(EGLSurface surface) { (void)surface; }
void SetWindowHandle(const WindowHandle& handle);
@@ -1301,6 +1301,31 @@ namespace MobileGL::MG_Backend::DirectGLES {
return true;
}
Bool BackendObject_DirectGLES::ServerWindowResized(void* window, Uint32 width, Uint32 height) {
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
if (window == nullptr) return false;
Bool published = false;
for (auto& [client, state] : m_eglSurfaces) {
if (state.Kind != SurfaceKind::Window || state.Window.Handle != window) continue;
if (std::find(m_suspendedWindowSurfaces.begin(), m_suspendedWindowSurfaces.end(), client) !=
m_suspendedWindowSurfaces.end())
continue;
state.Window.Width = width;
state.Window.Height = height;
state.Width = static_cast<EGLint>(std::max<Uint32>(width, 1));
state.Height = static_cast<EGLint>(std::max<Uint32>(height, 1));
if (m_eglSurface == client) m_windowHandle = state.Window;
// The window surface's buffers follow the window by themselves (from the next one the
// driver dequeues); only the client has to be told.
const auto native = m_nativeSurfaces.find(client);
if (native == m_nativeSurfaces.end()) continue;
published = DirectGLES::RepublishWindowSurfaceShape(native->second,
reinterpret_cast<NativeWindowType>(window)) ||
published;
}
return published;
}
void BackendObject_DirectGLES::OnEGLSurfaceReleased(EGLSurface surface) {
// P14: releasing a surface releases THAT SURFACE (OnEGLSurfaceForgotten), never the
// contexts. This used to be DestroyEGLContext() - every native context of the session, with
@@ -51,6 +51,7 @@ namespace MobileGL::MG_Backend::DirectGLES {
// window surface for a placeholder pbuffer and back (DirectGLES::Suspend/ResumeSessionWindowSurface).
Bool SuspendServerWindow(void* window) override;
Bool ResumeServerWindow(void* window, Uint32 width, Uint32 height) override;
Bool ServerWindowResized(void* window, Uint32 width, Uint32 height) override;
const RendererInfo& GetRendererInfo() const override;
String GetBackendAPIVersionString() const override;
+17 -79
View File
@@ -36,7 +36,6 @@
#include <MG_Remote/Server/SharedImageRegistry.h>
#if defined(__ANDROID__)
#include <android/hardware_buffer.h>
#include <android/native_window.h>
#include <unistd.h>
#endif
#endif
@@ -16276,10 +16275,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// re-makes current, and what Present/eglSwapBuffers names.
EGLSurface Draw = EGL_NO_SURFACE;
EGLSurface Read = EGL_NO_SURFACE;
// The server window's extent this session last published to its client (0x0: none yet).
// Present compares the window against it (RepublishServerWindowExtentIfMoved).
Int PublishedWidth = 0;
Int PublishedHeight = 0;
};
static std::mutex g_nativeRegistryMutex;
@@ -16521,10 +16516,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
// framebuffer's shape, so that publish - and no pbuffer's - may carry the surface's extent.
// Split builds only: the pull build's publish is unchanged (G1).
static Bool g_publishWindowExtent = false;
// The extent that publish carries when the caller already knows it (a republish from the window's
// own size, which the surface's buffers follow from the next frame on); 0x0 = ask the surface.
static Int g_publishExtentWidth = 0;
static Int g_publishExtentHeight = 0;
#endif
static Bool QueryCurrentSurfaceSize(Int& outWidth, Int& outHeight) {
@@ -16689,15 +16680,14 @@ namespace MobileGL::MG_Backend::DirectGLES {
// (ClientSession.cpp ApplySurfaceChangedToClient), so it is deliberately NOT published
// for a pbuffer, nor under inproc / monolith: those keep the format-only shape above.
if (g_publishWindowExtent && MG_Config::Transport == MG_Config::TransportMode::Spawn) {
Int extentWidth = g_publishExtentWidth;
Int extentHeight = g_publishExtentHeight;
if ((extentWidth > 0 && extentHeight > 0) || QueryCurrentSurfaceSize(extentWidth, extentHeight)) {
info.Width = static_cast<Uint32>(extentWidth);
info.Height = static_cast<Uint32>(extentHeight);
// What Present compares the window against from here on.
NativeSessionState& session = ActiveNativeSession();
session.PublishedWidth = extentWidth;
session.PublishedHeight = extentHeight;
// THE DISPLAY'S EXTENT, NEVER THE SURFACE'S: eglQuerySurface answers the buffer last
// dequeued, which after a resize is still the old size - publishing it told the client a
// size the window no longer had.
Uint32 extentWidth = 0;
Uint32 extentHeight = 0;
if (MG_Pipe::MGPipeServerOwnedWindowExtent(&extentWidth, &extentHeight)) {
info.Width = extentWidth;
info.Height = extentHeight;
}
}
#endif
@@ -17262,64 +17252,17 @@ namespace MobileGL::MG_Backend::DirectGLES {
#endif
}
#if MOBILEGL_BUILD_DISAGGREGATED
// The window's own extent, which the surface's buffers take from the next frame on - not the
// surface's, which on some drivers still reports the buffer just presented.
static Bool CurrentWindowExtent(NativeWindowType window, EGLSurface surface, Int& outWidth, Int& outHeight) {
#if defined(__ANDROID__)
(void)surface;
auto* nativeWindow = reinterpret_cast<ANativeWindow*>(window);
outWidth = static_cast<Int>(ANativeWindow_getWidth(nativeWindow));
outHeight = static_cast<Int>(ANativeWindow_getHeight(nativeWindow));
#else
(void)window;
EGLint width = 0;
EGLint height = 0;
if (!g_EGLFuncs.eglQuerySurface || !g_EGLFuncs.eglQuerySurface(g_Display, surface, EGL_WIDTH, &width) ||
!g_EGLFuncs.eglQuerySurface(g_Display, surface, EGL_HEIGHT, &height))
return false;
outWidth = static_cast<Int>(width);
outHeight = static_cast<Int>(height);
#endif
return outWidth > 0 && outHeight > 0;
}
// THE SERVER WINDOW CHANGED SIZE UNDER ITS SURFACE (the app's layout moved the view: a bar shown
// or hidden, a rotation). Its extent used to be published once, at the surface's creation, so the
// client kept sizing its viewport and default framebuffer for the old one while the window's
// buffers took the new one. Asked at every present of the session's server-owned window surface -
// one size query - and republished, as the creation publishes it, when it moved.
static void RepublishServerWindowExtentIfMoved(EGLSurface drawSurface) {
const void* const serverWindow = MG_Pipe::MGPipeServerOwnedWindow();
if (serverWindow == nullptr || drawSurface == EGL_NO_SURFACE ||
MG_Config::Transport != MG_Config::TransportMode::Spawn)
return;
NativeSessionState& session = ActiveNativeSession();
NativeWindowType window = static_cast<NativeWindowType>(0);
for (const auto& entry : session.Surfaces) {
if (entry.Surface != drawSurface) continue;
window = entry.Window;
break;
}
if (reinterpret_cast<const void*>(window) != serverWindow) return;
Int width = 0;
Int height = 0;
if (!CurrentWindowExtent(window, drawSurface, width, height)) return;
if (width == session.PublishedWidth && height == session.PublishedHeight) return;
MGLOG_I("DirectGLES: the server window is now %dx%d (last published %dx%d); its extent is republished to "
"the client",
width, height, session.PublishedWidth, session.PublishedHeight);
g_publishExtentWidth = width;
g_publishExtentHeight = height;
// THE SERVER WINDOW CHANGED SIZE (the app's layout moved the view: a bar shown or hidden, a
// rotation). The new extent - the display's, set by the server loop before this runs - is published
// as the creation publishes it, at once: the client sizes its viewport and default framebuffer from
// it, and a compositor with nothing to redraw makes no present to hang this on.
Bool RepublishWindowSurfaceShape(EGLSurface surface, NativeWindowType window) {
if (surface == EGL_NO_SURFACE || !window) return false;
// Bound, because the publish reads the default framebuffer's format.
if (!BindSessionSurface(surface) || t_boundNativeDraw != surface) return false;
PublishWindowSurfaceShape(window);
g_publishExtentWidth = 0;
g_publishExtentHeight = 0;
// Asked once per size, whatever the publish managed: a publish that could not run is not
// retried - and logged - every frame.
session.PublishedWidth = width;
session.PublishedHeight = height;
return true;
}
#endif
EGLSurface SuspendSessionWindowSurface(EGLSurface windowSurface) {
if (windowSurface == EGL_NO_SURFACE || g_Display == EGL_NO_DISPLAY) return EGL_NO_SURFACE;
@@ -18135,11 +18078,6 @@ namespace MobileGL::MG_Backend::DirectGLES {
if (canFence) PollFrameFences();
#if MOBILEGL_BUILD_DISAGGREGATED
// The frame is out; the next one is drawn into the window's current buffers.
RepublishServerWindowExtentIfMoved(drawSurface);
#endif
// After the watermark advanced: retire grown-away ring stores and record the
// frame's ring high-water marks for slot reclamation.
BufferImpl::UboRingOnPresent();
@@ -140,6 +140,8 @@ namespace MobileGL::MG_Backend::DirectGLES {
// replace) for every context that drew to it, is bound, and its extent is published as a
// creation publishes it. Returns the new surface, or EGL_NO_SURFACE.
EGLSurface ResumeSessionWindowSurface(EGLSurface placeholder, NativeWindowType window);
// The window under `surface` changed size: its extent (the display's) is published again, at once.
Bool RepublishWindowSurfaceShape(EGLSurface surface, NativeWindowType window);
Bool MakeCurrent();
Bool ReleaseCurrent();
// True when the backend ES context is current on the calling thread, i.e.
@@ -695,6 +695,27 @@ namespace MobileGL::MG_Backend::DirectVulkan {
return true;
}
Bool BackendObject_DirectVulkan::ServerWindowResized(void* window, Uint32 width, Uint32 height) {
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
if (window == nullptr || !UsesSurfaceTargets()) return false;
Bool followed = false;
for (auto& [client, state] : m_eglSurfaces) {
if (state.Kind != SurfaceKind::Window || state.Window.Handle != window) continue;
if (m_suspendedWindowSurfaces.find(client) != m_suspendedWindowSurfaces.end()) continue;
state.Window.Width = width;
state.Window.Height = height;
state.Width = static_cast<EGLint>(std::max<Uint32>(width, 1));
state.Height = static_cast<EGLint>(std::max<Uint32>(height, 1));
if (m_eglSurface == client) m_windowHandle = state.Window;
// Magma's extent is its swapchain's, published when it is built: the active target follows
// the window now (or at the half-recorded frame's present); a parked one at its next present.
if (pVulkanRenderer && pVulkanRenderer->IsActiveSurfaceTarget(SurfaceTargetKey(client))) {
followed = pVulkanRenderer->FollowActiveWindowResize() || followed;
}
}
return followed;
}
Bool BackendObject_DirectVulkan::UsesSurfaceTargets() const {
return MG_Config::Transport != MG_Config::TransportMode::Monolith;
}
@@ -45,6 +45,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// swapchain) is destroyed and a pbuffer target of the same extent stands in for it, and back.
Bool SuspendServerWindow(void* window) override;
Bool ResumeServerWindow(void* window, Uint32 width, Uint32 height) override;
Bool ServerWindowResized(void* window, Uint32 width, Uint32 height) override;
const RendererInfo& GetRendererInfo() const override;
String GetBackendAPIVersionString() const override;
@@ -17094,6 +17094,30 @@ void main() {
m_parkedTargets.erase(parked);
}
Bool VulkanRenderer::FollowActiveWindowResize() {
if (m_surface == VK_NULL_HANDLE || !m_presentsToAppWindow) return false;
const auto& frame = m_frameContext.GetCurrent();
if (frame.isCommandRecording || frame.hasCommandBufferRecorded || frame.isPreCommandRecording ||
frame.hasPreCommandBufferRecorded) {
m_swapchainResizeRequested = true;
MGLOG_I("DirectVulkan: the window resized mid-frame; its swapchain is rebuilt at this frame's present");
return true;
}
// The surface already reports the swapchain's extent: nothing to rebuild.
if (m_swapchainObject.GetHandle() != VK_NULL_HANDLE && !SwapchainIsOutOfDate()) return true;
// The target's own rebuild - the path a surface's re-activation takes: its pending acquire waited
// out, a new VkSurfaceKHR and swapchain at the surface's current extent and pre-transform, a new
// target serial for the render passes, and the extent published.
const Uint64 key = m_activeTargetKey;
const NativeWindowType window = m_window;
VulkanRendererConfig config;
config.SurfaceWidth = m_config.SurfaceWidth;
config.SurfaceHeight = m_config.SurfaceHeight;
config.SwapInterval = m_config.SwapInterval;
DestroySurfaceTarget(key);
return ActivateSurfaceTarget(key, window, config);
}
VkExtent2D VulkanRenderer::SurfaceTargetExtent(Uint64 key) const {
if (key == m_activeTargetKey && m_surface != VK_NULL_HANDLE) {
return m_swapchainObject.GetHandle() != VK_NULL_HANDLE ? m_swapchainObject.GetSurfaceExtent()
@@ -461,6 +461,12 @@ namespace MobileGL::MG_Backend::DirectVulkan {
// The default framebuffer's extent (surface space, what PublishDefaultFramebufferInfo
// publishes) of `key`'s target, active or parked; 0x0 when it has none or no swapchain.
VkExtent2D SurfaceTargetExtent(Uint64 key) const;
// The active WINDOW target's window changed size. Between frames (nothing recorded since the last
// present) its swapchain is rebuilt now - which publishes the new extent - so a client that has
// nothing to redraw learns it without presenting; with a frame half-recorded the rebuild waits
// for that frame's present (it would otherwise land half in each swapchain). False: no active
// window target.
Bool FollowActiveWindowResize();
// Whether `key` names the active target.
Bool IsActiveSurfaceTarget(Uint64 key) const { return key == m_activeTargetKey && m_surface != VK_NULL_HANDLE; }
+17
View File
@@ -413,6 +413,23 @@ namespace MobileGL::MG_Pipe {
void MGPipeServerSetOwnedWindow(const void* window) { g_serverOwnedWindow = window; }
const void* MGPipeServerOwnedWindow() { return g_serverOwnedWindow; }
namespace {
Uint32 g_serverOwnedWindowWidth = 0;
Uint32 g_serverOwnedWindowHeight = 0;
} // namespace
void MGPipeServerSetOwnedWindowExtent(Uint32 width, Uint32 height) {
g_serverOwnedWindowWidth = width;
g_serverOwnedWindowHeight = height;
}
Bool MGPipeServerOwnedWindowExtent(Uint32* width, Uint32* height) {
if (g_serverOwnedWindowWidth == 0 || g_serverOwnedWindowHeight == 0) return false;
if (width != nullptr) *width = g_serverOwnedWindowWidth;
if (height != nullptr) *height = g_serverOwnedWindowHeight;
return true;
}
#endif
void MGPipeServerBlockNoteIdentity() {
+5
View File
@@ -996,6 +996,11 @@ namespace MobileGL::MG_Pipe {
// earlier session relied on. Apply thread only.
void MGPipeServerSetOwnedWindow(const void* window);
const void* MGPipeServerOwnedWindow();
// That window's extent as the display last reported it (ServerLoop sets it from ServerDisplay with the
// lease and on every resize; 0x0 with no lease). The ONE size a backend publishes for it: a surface's own
// query can still answer the buffer before the resize. False while unknown. Apply thread only.
void MGPipeServerSetOwnedWindowExtent(Uint32 width, Uint32 height);
Bool MGPipeServerOwnedWindowExtent(Uint32* width, Uint32* height);
#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
+6
View File
@@ -728,6 +728,12 @@ namespace MobileGL::MG_Impl::EGLImpl {
if (!state) {
return EGL_FALSE;
}
// A size that changes under the application (a server-owned window resized) arrives from the
// backend: let it bring the surface's record up to date first, so a caller that polls the size
// while it draws nothing still sees the change.
if (attribute == EGL_WIDTH || attribute == EGL_HEIGHT) {
if (auto* backendObject = MG_Backend::pActiveBackendObject.get()) backendObject->RefreshSurfaceExtent(surface);
}
return state->QuerySurface(display, surface, attribute, value) ? EGL_TRUE : EGL_FALSE;
}
@@ -269,6 +269,16 @@ namespace MobileGL::MG_Remote::Client {
return MG_Backend::BackendObject::ResizeEGLWindowSurface(surface, width, height);
}
void BackendObject_Remote::RefreshSurfaceExtent(EGLSurface surface) {
if (!ClientSession::IsServerOwnedWindowSurface(surface)) return;
ClientSession* session = ClientSession::Active();
if (session == nullptr) return;
// The known-idle instant every EGL forwarder drains at: the server has applied what this client
// published, so the events it posted for it - a resized window's extent - are all there.
WaitForApplyBeforeEglForwarder("QuerySurface");
session->DrainPublishedEvents();
}
Bool BackendObject_Remote::CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) {
WaitForApplyBeforeEglForwarder("CreateEGLPbufferSurface");
Server::SurfaceRefusalCode refusal = Server::SurfaceRefusalCode::None;
@@ -72,6 +72,9 @@ namespace MobileGL::MG_Remote::Client {
Bool InitializeEGLDisplay(EGLDisplay dpy, EGLint* major, EGLint* minor) override;
Bool CreateEGLWindowSurface(EGLSurface surface, const MG_Backend::WindowHandle& handle) override;
Bool ResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height) override;
// A server-owned window surface: the published surface-changed events are applied first (after the
// server caught up), so a client polling eglQuerySurface sees a resize it drew nothing for.
void RefreshSurfaceExtent(EGLSurface surface) override;
Bool CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) override;
Bool MakeEGLCurrent(EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext ctx) override;
Bool SwapEGLBuffers(EGLDisplay dpy, EGLSurface draw) override;
+20 -2
View File
@@ -100,12 +100,18 @@ namespace MobileGL::MG_Remote::Server {
if (window == m_window) {
// surfaceChanged: the same window at a new extent. This is also how a geometry request
// is answered, so a waiting AcquireFor is woken.
const Bool resized = width != m_width || height != m_height;
m_width = width;
m_height = height;
NoteExtentReportLocked(width, height);
// The session rendering into it publishes the new extent to its client NOW - not at its
// next present, which a compositor with nothing to redraw never makes.
const Bool told = resized && m_leaseHolder != nullptr && m_onResized != nullptr;
if (told) m_onResized(m_leaseHolder);
lock.unlock();
m_cv.notify_all();
MGLOG_I("MG_Remote server: server window %p is now %ux%u", window, width, height);
MGLOG_I("MG_Remote server: server window %p is now %ux%u%s", window, width, height,
told ? "; its session is told" : "");
return;
}
if (m_window != nullptr) {
@@ -184,7 +190,8 @@ namespace MobileGL::MG_Remote::Server {
ServerWindowAcquire ServerDisplay::AcquireFor(Uint32 width, Uint32 height, Uint32 timeoutMs, void* holder,
ServerWindowLostHook onLost, ServerWindowWaitCancel cancel,
void* cancelUser, ServerWindowLease* out) {
void* cancelUser, ServerWindowLease* out,
ServerWindowResizedHook onResized) {
ServerDisplayHooks hooks{};
Uint64 interrupts = 0;
const Bool sizeRequested = width != 0 && height != 0;
@@ -240,6 +247,7 @@ namespace MobileGL::MG_Remote::Server {
if (sizeOk || now >= geometryDeadline) {
m_leaseHolder = holder;
m_onLost = onLost;
m_onResized = onResized;
m_lostRequested = false;
// A suspended holder that leased a window again waits for no other.
m_suspended.erase(std::remove_if(m_suspended.begin(), m_suspended.end(),
@@ -281,6 +289,7 @@ namespace MobileGL::MG_Remote::Server {
if (holder == nullptr || m_leaseHolder != holder) return false;
m_leaseHolder = nullptr;
m_onLost = nullptr;
m_onResized = nullptr;
m_lostRequested = false;
releases->swap(m_releaseAfterLease);
*hooks = m_hooks;
@@ -338,6 +347,15 @@ namespace MobileGL::MG_Remote::Server {
m_suspended.end());
}
Bool ServerDisplay::WindowExtent(void* holder, void** window, Uint32* width, Uint32* height) const {
const std::lock_guard<std::mutex> lock(m_mutex);
if (holder == nullptr || m_leaseHolder != holder || m_window == nullptr) return false;
if (window != nullptr) *window = m_window;
if (width != nullptr) *width = m_width;
if (height != nullptr) *height = m_height;
return true;
}
Uint32 ServerDisplay::SuspendedHolders() const {
const std::lock_guard<std::mutex> lock(m_mutex);
return static_cast<Uint32>(m_suspended.size());
+12 -2
View File
@@ -72,6 +72,9 @@ namespace MobileGL::MG_Remote::Server {
// A new window was attached while `holder` was suspended (SuspendLease). Same rules as the lost
// hook: called with the display's lock held.
using ServerWindowAttachedHook = void (*)(void* holder);
// The leased window changed size (surfaceChanged of the SAME window: the app's layout moved it).
// Same rules: called with the display's lock held; the holder reads the new extent with WindowExtent.
using ServerWindowResizedHook = void (*)(void* holder);
// Asked about every 50 ms while AcquireFor waits; true ends the wait (Cancelled).
using ServerWindowWaitCancel = Bool (*)(void* user);
@@ -139,10 +142,16 @@ namespace MobileGL::MG_Remote::Server {
// The apply thread's side. Requests the geometry (w, h; 0/0 = the window's own), then waits
// until a window is attached - and, for a requested size, has reached it or the geometry
// grace ran out - and leases it to `holder`, whose `onLost` Detach will call. The same holder
// may lease again while it holds the lease (a surface re-created at a new size).
// may lease again while it holds the lease (a surface re-created at a new size). `onResized`, when
// given, is called whenever the leased window changes size while the lease lasts.
ServerWindowAcquire AcquireFor(Uint32 width, Uint32 height, Uint32 timeoutMs, void* holder,
ServerWindowLostHook onLost, ServerWindowWaitCancel cancel,
void* cancelUser, ServerWindowLease* out);
void* cancelUser, ServerWindowLease* out,
ServerWindowResizedHook onResized = nullptr);
// THE WINDOW'S EXTENT AS THE PLATFORM LAST REPORTED IT (surfaceCreated / surfaceChanged): the one
// source of truth for the size a session publishes to its client. False unless `holder` holds
// the lease on an attached window.
Bool WindowExtent(void* holder, void** window, Uint32* width, Uint32* height) const;
// The holder has released everything that referenced the window (its backend surface, or the
// whole backend). Wakes a waiting Detach; releases a reference Detach left behind on timeout.
// A holder that holds no lease is a no-op.
@@ -196,6 +205,7 @@ namespace MobileGL::MG_Remote::Server {
Uint64 m_extentReports = 0;
void* m_leaseHolder = nullptr;
ServerWindowLostHook m_onLost = nullptr;
ServerWindowResizedHook m_onResized = nullptr;
Bool m_lostRequested = false;
// References a timed-out Detach could not release yet; EndLease releases them.
std::vector<void*> m_releaseAfterLease;
+49 -6
View File
@@ -371,6 +371,9 @@ namespace MobileGL::MG_Remote::Server {
m_serverWindowsResumed.store(0, std::memory_order_release);
m_serverWindowWantWidth = 0;
m_serverWindowWantHeight = 0;
m_publishedWindowWidth = 0;
m_publishedWindowHeight = 0;
m_serverWindowResizes.store(0, std::memory_order_release);
m_testLogFloodLines = ApplyLogFloodLinesForTest();
{
const std::lock_guard<std::mutex> lock(m_exitMutex);
@@ -1748,6 +1751,15 @@ namespace MobileGL::MG_Remote::Server {
}
}
void ServerLoop::ServerWindowResizedThunk(void* self) {
auto* loop = static_cast<ServerLoop*>(self);
// As the lost hook: called under the display's lock while this loop holds the lease.
loop->m_windowRequests.fetch_or(kWindowResized, std::memory_order_acq_rel);
if (loop->m_session != nullptr && loop->m_session->DataLink() != nullptr) {
loop->m_session->DataLink()->ConsumerBell().Notify();
}
}
Bool ServerLoop::ServerWindowWaitCancelled(void* self) {
const auto* loop = static_cast<const ServerLoop*>(self);
// A window-lost request also ends the wait: this thread holds a lease on a window that is
@@ -1762,7 +1774,8 @@ namespace MobileGL::MG_Remote::Server {
ServerWindowLease local;
ServerWindowLease* const lease = out != nullptr ? out : &local;
const ServerWindowAcquire acquired = display.AcquireFor(width, height, timeoutMs, this, &ServerWindowLostThunk,
&ServerWindowWaitCancelled, this, lease);
&ServerWindowWaitCancelled, this, lease,
&ServerWindowResizedThunk);
switch (acquired) {
case ServerWindowAcquire::Acquired:
m_holdsWindowLease = true;
@@ -1841,7 +1854,7 @@ namespace MobileGL::MG_Remote::Server {
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);
NoteServerWindowExtent(lease.window, lease.width, lease.height);
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 "
@@ -1912,6 +1925,7 @@ namespace MobileGL::MG_Remote::Server {
}
m_serverWindowWantWidth = wantWidth;
m_serverWindowWantHeight = wantHeight;
NoteServerWindowExtent(lease.window, lease.width, lease.height);
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);
@@ -1925,7 +1939,7 @@ namespace MobileGL::MG_Remote::Server {
if (!m_holdsWindowLease) return;
m_holdsWindowLease = false;
m_leasedWindow = nullptr;
MG_Pipe::MGPipeServerSetOwnedWindow(nullptr);
NoteServerWindowExtent(nullptr, 0, 0);
ServerDisplayInstance().EndLease(this);
}
@@ -1942,6 +1956,35 @@ namespace MobileGL::MG_Remote::Server {
const Uint32 requests = m_windowRequests.exchange(0, std::memory_order_acq_rel);
if ((requests & kWindowLost) != 0) ReleaseLostServerWindow();
if ((requests & kWindowAttached) != 0 && !SessionLatched()) ResumeSuspendedServerWindow();
if ((requests & kWindowResized) != 0 && !SessionLatched()) PublishServerWindowResize();
}
void ServerLoop::NoteServerWindowExtent(void* window, Uint32 width, Uint32 height) {
MG_Pipe::MGPipeServerSetOwnedWindow(window);
MG_Pipe::MGPipeServerSetOwnedWindowExtent(width, height);
m_publishedWindowWidth = width;
m_publishedWindowHeight = height;
}
void ServerLoop::PublishServerWindowResize() {
if (!m_holdsWindowLease || m_backend == nullptr) return;
// THE DISPLAY'S EXTENT, READ NOW: the latest report wins over any a burst of them left queued.
void* window = nullptr;
Uint32 width = 0;
Uint32 height = 0;
if (!ServerDisplayInstance().WindowExtent(this, &window, &width, &height) || window != m_leasedWindow ||
width == 0 || height == 0)
return;
if (width == m_publishedWindowWidth && height == m_publishedWindowHeight) return;
const Uint32 previousWidth = m_publishedWindowWidth;
const Uint32 previousHeight = m_publishedWindowHeight;
NoteServerWindowExtent(window, width, height);
const Bool published = m_backend->ServerWindowResized(window, width, height);
if (published) m_serverWindowResizes.fetch_add(1, std::memory_order_acq_rel);
MGLOG_I("MG_Remote server: server window %p resized %ux%u -> %ux%u; %s", window, previousWidth, previousHeight,
width, height,
published ? "the new extent is published to the client"
: "the backend published nothing (no surface on the window, or it follows at its present)");
}
void ServerLoop::LatchServerWindowLost(const char* why) {
@@ -1974,7 +2017,7 @@ namespace MobileGL::MG_Remote::Server {
const Uint64 suspends = m_serverWindowsSuspended.fetch_add(1, std::memory_order_acq_rel) + 1;
m_holdsWindowLease = false;
m_leasedWindow = nullptr;
MG_Pipe::MGPipeServerSetOwnedWindow(nullptr);
NoteServerWindowExtent(nullptr, 0, 0);
MGLOG_I("MG_Remote server: server window %p lost - the on-screen session is SUSPENDED, not ended: what "
"was bound to the window was released on mgl-srv-apply, its surfaces draw into an offscreen "
"placeholder, and it resumes on the next window (suspend #%llu)",
@@ -1997,7 +2040,7 @@ namespace MobileGL::MG_Remote::Server {
const ServerWindowAcquire acquired =
ServerDisplayInstance().AcquireFor(m_serverWindowWantWidth, m_serverWindowWantHeight,
ServerDisplay::kGeometryGraceMs, this, &ServerWindowLostThunk,
&ServerWindowWaitCancelled, this, &lease);
&ServerWindowWaitCancelled, this, &lease, &ServerWindowResizedThunk);
if (acquired != ServerWindowAcquire::Acquired) {
// Still registered: the next new window wakes this again.
MGLOG_I("MG_Remote server: a suspended on-screen session did not get the new server window (%s); it "
@@ -2012,7 +2055,7 @@ namespace MobileGL::MG_Remote::Server {
Bool ServerLoop::ResumeServerWindowOnLease(const ServerWindowLease& lease) {
// Before the backend: its republished extent is the server window's (MGPipeServerOwnedWindow).
MG_Pipe::MGPipeServerSetOwnedWindow(lease.window);
NoteServerWindowExtent(lease.window, lease.width, lease.height);
if (m_backend == nullptr || !m_backend->ResumeServerWindow(lease.window, lease.width, lease.height)) {
LatchServerWindowLost("the suspended on-screen session's surfaces could not be rebuilt on the new server "
"window");
+16
View File
@@ -440,6 +440,9 @@ namespace MobileGL::MG_Remote::Server {
// a placeholder until the display attaches a window again.
Uint64 ServerWindowsSuspended() const { return m_serverWindowsSuspended.load(std::memory_order_acquire); }
Uint64 ServerWindowsResumed() const { return m_serverWindowsResumed.load(std::memory_order_acquire); }
// How many times a resize of the leased window was published to the client (the display's
// surfaceChanged, answered on the apply thread without waiting for a present).
Uint64 ServerWindowResizesPublished() const { return m_serverWindowResizes.load(std::memory_order_acquire); }
Bool ServerWindowSuspended() const { return m_serverWindowSuspended.load(std::memory_order_acquire); }
// TEST SEAM: the backend this loop owns, instead of CreateBackend's (a fake that can or cannot
@@ -484,6 +487,14 @@ namespace MobileGL::MG_Remote::Server {
static void ServerWindowLostThunk(void* self);
// ServerDisplay's attached hook for a suspended session: the same discipline as the lost one.
static void ServerWindowAttachedThunk(void* self);
// ServerDisplay's resized hook for the lease holder: the same discipline again.
static void ServerWindowResizedThunk(void* self);
// Apply thread: the leased window's new extent (the display's) is handed to the backend, which
// publishes it to the client now.
void PublishServerWindowResize();
// Apply thread: the extent a backend publishes for the server window from here on (MGPipe's), and
// what this session last handed it.
void NoteServerWindowExtent(void* window, Uint32 width, Uint32 height);
// AcquireFor's cancel predicate: the loop is stopping, or the session latched.
static Bool ServerWindowWaitCancelled(void* self);
// D4: latched at the session's first successful surface creation; reset by Start(). Per
@@ -500,6 +511,7 @@ namespace MobileGL::MG_Remote::Server {
// apply thread wakes for it.
static constexpr Uint32 kWindowLost = 1u;
static constexpr Uint32 kWindowAttached = 2u;
static constexpr Uint32 kWindowResized = 4u;
std::atomic<Uint32> m_windowRequests{0};
std::atomic<Uint64> m_serverWindowsLost{0};
// Written by the apply thread, read by tests.
@@ -510,6 +522,10 @@ namespace MobileGL::MG_Remote::Server {
// layout's), which a resume asks for again.
Uint32 m_serverWindowWantWidth = 0;
Uint32 m_serverWindowWantHeight = 0;
// Apply thread only: the leased window's extent this session last published.
Uint32 m_publishedWindowWidth = 0;
Uint32 m_publishedWindowHeight = 0;
std::atomic<Uint64> m_serverWindowResizes{0};
void ApplyThreadMain();
// Runs a posted control frame, if there is one. Returns true if it ran one.
@@ -3156,6 +3156,21 @@ TEST(RemoteRunAhead, ARepublishedServerWindowExtentResizesTheServerOwnedSurfaceA
if (ClientSessionInstance().DrainPublishedEvents() < 1u) ::_exit(95);
if (query(EGL_WIDTH) != 2560 || query(EGL_HEIGHT) != 1600) ::_exit(96);
// AN APPLICATION THAT POLLS THE SIZE WHILE IT DRAWS NOTHING (the compositor's timer): the next
// resize is published and nothing drains it - no verb, no make-current - but eglQuerySurface
// itself takes it in. Red with BackendObject_Remote::RefreshSurfaceExtent a no-op (exit 97).
MG_Backend::pActiveBackendObject = MakeUnique<BackendObject_Remote>();
s_info.Width = 2560;
s_info.Height = 1412;
republish();
EGLint polledWidth = -1;
EGLint polledHeight = -1;
if (MG_Impl::EGLImpl::QuerySurface(dpy, surface, EGL_WIDTH, &polledWidth) != EGL_TRUE ||
MG_Impl::EGLImpl::QuerySurface(dpy, surface, EGL_HEIGHT, &polledHeight) != EGL_TRUE ||
polledWidth != 2560 || polledHeight != 1412)
::_exit(97);
MG_Backend::pActiveBackendObject.reset();
defaultFramebuffer.reset();
ClientSessionInstance().ForgetServerOwnedWindowSurface(surface);
MG_State::pEGLContext.reset();
@@ -485,3 +485,45 @@ TEST(ServerDisplayTest, ASuspendedHolderIsToldAboutTheNextNewWindowUntilItLeases
EXPECT_EQ(attached.load(), 2);
display.Uninstall(100);
}
// A RESIZE OF THE LEASED WINDOW REACHES ITS HOLDER AT ONCE (surfaceChanged of the same window), and
// WindowExtent answers the platform's latest report - the one size a session publishes. Not for a
// report of the same size, not for anyone but the holder, not after the lease ended. Red with Attach's
// same-window arm not calling the hook (resized stays 0).
TEST(ServerDisplayTest, AResizeOfTheLeasedWindowIsToldToItsHolderAndWindowExtentAnswersIt) {
FakePlatform platform;
ServerDisplay display;
display.Install(HooksFor(platform));
display.Attach(FakeWindow(0), 64, 48);
static std::atomic<int> resized{0};
resized.store(0);
const auto onResized = [](void*) { resized.fetch_add(1); };
int holder = 0;
int stranger = 0;
ServerWindowLease lease;
ASSERT_EQ(display.AcquireFor(64, 48, 100, &holder, nullptr, nullptr, nullptr, &lease, onResized),
ServerWindowAcquire::Acquired);
void* window = nullptr;
Uint32 width = 0;
Uint32 height = 0;
ASSERT_TRUE(display.WindowExtent(&holder, &window, &width, &height));
EXPECT_EQ(window, FakeWindow(0));
EXPECT_EQ(width, 64u);
EXPECT_EQ(height, 48u);
EXPECT_FALSE(display.WindowExtent(&stranger, &window, &width, &height)) << "only the holder reads it";
display.Attach(FakeWindow(0), 64, 60); // the app's bar hidden
EXPECT_EQ(resized.load(), 1);
ASSERT_TRUE(display.WindowExtent(&holder, &window, &width, &height));
EXPECT_EQ(height, 60u);
display.Attach(FakeWindow(0), 64, 60); // the same report again
EXPECT_EQ(resized.load(), 1) << "an unchanged extent is not a resize";
display.Attach(FakeWindow(0), 64, 48); // and back
EXPECT_EQ(resized.load(), 2);
display.EndLease(&holder);
display.Attach(FakeWindow(0), 64, 60);
EXPECT_EQ(resized.load(), 2) << "a holder whose lease ended is not told";
EXPECT_FALSE(display.WindowExtent(&holder, &window, &width, &height));
display.Uninstall(100);
}
+89
View File
@@ -3460,6 +3460,7 @@ namespace {
Bool InitWindowSurface() override { return true; }
Bool CreateEGLWindowSurface(EGLSurface, const MG_Backend::WindowHandle& handle) override {
window.store(handle.Handle, std::memory_order_release);
NoteExtentAt(createExtent);
creates.fetch_add(1, std::memory_order_acq_rel);
return true;
}
@@ -3475,10 +3476,19 @@ namespace {
resumeOnApplyThread.store(Server::ServerLoop::OnApplyThread(), std::memory_order_release);
resumeWidth.store(width, std::memory_order_release);
resumeHeight.store(height, std::memory_order_release);
NoteExtentAt(resumeExtent);
window.store(fresh, std::memory_order_release);
resumes.fetch_add(1, std::memory_order_acq_rel);
return true;
}
Bool ServerWindowResized(void* resizedWindow, Uint32 width, Uint32 height) override {
if (resizedWindow != window.load(std::memory_order_acquire)) return false;
resizedOnApplyThread.store(Server::ServerLoop::OnApplyThread(), std::memory_order_release);
resizedTo.store(Pack(width, height), std::memory_order_release);
NoteExtentAt(resizeExtent);
resizes.fetch_add(1, std::memory_order_acq_rel);
return true;
}
void ReleaseEGLResources() override {
releases.fetch_add(1, std::memory_order_acq_rel);
BackendObject::ReleaseEGLResources();
@@ -3506,6 +3516,21 @@ namespace {
static inline std::atomic<Bool> resumeOnApplyThread{false};
static inline std::atomic<Uint32> resumeWidth{0};
static inline std::atomic<Uint32> resumeHeight{0};
// The extent the server window had in MGPipe - the one a real backend publishes - when each
// call ran (width << 32 | height; 0 = none).
static inline std::atomic<Uint64> createExtent{0};
static inline std::atomic<Uint64> resumeExtent{0};
static inline std::atomic<Uint64> resizeExtent{0};
static inline std::atomic<Uint64> resizedTo{0};
static inline std::atomic<int> resizes{0};
static inline std::atomic<Bool> resizedOnApplyThread{false};
static constexpr Uint64 Pack(Uint32 width, Uint32 height) { return (Uint64{width} << 32) | height; }
static void NoteExtentAt(std::atomic<Uint64>& slot) {
Uint32 width = 0;
Uint32 height = 0;
slot.store(MG_Pipe::MGPipeServerOwnedWindowExtent(&width, &height) ? Pack(width, height) : 0,
std::memory_order_release);
}
private:
Bool m_canSuspend = false;
@@ -3734,6 +3759,70 @@ TEST(ServerLoopSuspendTest, ABackendThatCannotSuspendStillLatchesServerWindowLos
// Mid-stream: answered within the stream (DrainRing's per-pop check) and the stream goes on. Red with
// the suspend arm deleted: the stream is cut at the latch (bits 4|8|16).
namespace {
enum : int {
kResizeNotPublished = 1, // a resize with no present was not handed to the backend at the new size, on the apply thread
kResizeExtent = 2, // the extent a backend publishes (MGPipe's) did not follow the display
kResizeRepeated = 4, // an unchanged report was published again
kResizePresented = 8, // a present ran (the case is about publishing without one)
kResizeAfterResume = 16, // the resume did not take the new window's extent, or a later resize was lost
};
[[noreturn]] void ResizeWithoutAPresentAndExit() {
Remote::ArmSessionLatch();
ServerFixture fixture;
if (!BringUpAnOnScreenSession(fixture, /*canSuspend=*/true)) ::_exit(250);
Server::ServerLoop& loop = Server::ServerLoopInstance();
Server::ServerDisplay& display = Server::ServerDisplayInstance();
using Double = WindowBackendDouble;
int failed = 0;
if (Double::createExtent.load() != Double::Pack(64, 48)) failed |= kResizeExtent;
// The app's bar is hidden: the SAME window, taller - and the client draws nothing at all.
display.Attach(&g_fakeServerWindow, 64, 60);
if (!PollUntil([&] { return loop.ServerWindowResizesPublished() == 1; }, 3000) || Double::resizes.load() != 1 ||
Double::resizedTo.load() != Double::Pack(64, 60) || !Double::resizedOnApplyThread.load())
failed |= kResizeNotPublished;
if (Double::resizeExtent.load() != Double::Pack(64, 60)) failed |= kResizeExtent;
display.Attach(&g_fakeServerWindow, 64, 60);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
if (Double::resizes.load() != 1) failed |= kResizeRepeated;
if (Double::presents.load() != 0) failed |= kResizePresented;
// Screen off; the new window comes back at the session's own size, then the bar moves again.
if (!fixture.WaitUntilTrulyParked()) ::_exit(251);
if (display.Detach(3000) != Server::ServerWindowDetach::ReleasedBySession) failed |= kResizeAfterResume;
if (MG_Pipe::MGPipeServerOwnedWindowExtent(nullptr, nullptr)) failed |= kResizeExtent; // no window, no extent
display.Attach(&g_secondFakeServerWindow, 64, 48);
if (!PollUntil([&] { return loop.ServerWindowsResumed() == 1; }, 3000) ||
Double::resumeExtent.load() != Double::Pack(64, 48))
failed |= kResizeAfterResume;
display.Attach(&g_secondFakeServerWindow, 64, 60);
if (!PollUntil([&] { return Double::resizes.load() == 2; }, 3000) ||
Double::resizedTo.load() != Double::Pack(64, 60) || Double::resizeExtent.load() != Double::Pack(64, 60))
failed |= kResizeAfterResume;
std::fprintf(stderr, "[resize] published=%llu resizes=%d presents=%d\n",
static_cast<unsigned long long>(loop.ServerWindowResizesPublished()), Double::resizes.load(),
Double::presents.load());
fixture.Stop();
::_exit(failed);
}
} // namespace
// THE RESIZE NOBODY PRESENTED FOR (device: the bar toggled while the compositor was idle, or just
// before the lock, never reached it). The display's surfaceChanged of the leased window reaches the
// session's apply thread at once, and the backend publishes the DISPLAY's extent - MGPipe's, which
// every publish of the server window reads - with no present in between; after a suspend and resume
// the resume publishes the new window's extent and a later resize is published again. Red with
// ServerDisplay::Attach not telling the holder (exit 23: nothing published), and with the resize path
// handing the backend the size without setting MGPipe's extent (exit 18: bits 2|16).
TEST(ServerLoopSuspendTest, AResizeOfTheServerWindowIsPublishedWithoutWaitingForAPresent) {
EXPECT_EXIT(ResizeWithoutAPresentAndExit(), ::testing::ExitedWithCode(0), ".*")
<< "bits: 1 = the resize was not published without a present; 2 = the published extent is not the "
"display's; 4 = an unchanged report was published again; 8 = a present ran; 16 = the resume or a "
"resize after it went wrong (250+ = setup)";
}
// MAGMA'S PLACEHOLDER ON A RENDERER CREATED FOR A WINDOW (the device crash: SIGSEGV at pc 0 in
// VulkanRenderer::CreateSurface on screen-off). The on-screen session's renderer was built for the
// server's window, so its instance enabled the Android surface extension and nothing headless; the