mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[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:
@@ -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;
|
||||
|
||||
@@ -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; }
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user