mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
- Layout only: token streams match up to split literals, continuations and using order - The size totals fall, so the structure baseline is lowered
459 lines
20 KiB
C++
459 lines
20 KiB
C++
// MobileGL - MobileGL/MG_Remote/Server/ServerDisplay.cpp
|
|
// Copyright (c) 2025-2026 MobileGL-Dev
|
|
// Licensed under the GNU Lesser General Public License v3.0:
|
|
// https://www.gnu.org/licenses/gpl-3.0.txt
|
|
// https://www.gnu.org/licenses/lgpl-3.0.txt
|
|
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// End of Source File Header
|
|
|
|
// P12 (on-screen server window), D3/D6. See ServerDisplay.h.
|
|
|
|
#include "ServerDisplay.h"
|
|
|
|
#include <MG_Util/Debug/Log.h>
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <utility>
|
|
|
|
#if defined(__ANDROID__)
|
|
#include <android/native_window.h>
|
|
#endif
|
|
|
|
namespace MobileGL::MG_Remote::Server {
|
|
|
|
namespace {
|
|
using Clock = std::chrono::steady_clock;
|
|
|
|
// How often a waiting AcquireFor re-asks its cancel predicate. The predicate is the apply
|
|
// thread's own stop / latch state, which nobody signals through this object's condition.
|
|
constexpr auto kCancelPollInterval = std::chrono::milliseconds(50);
|
|
} // namespace
|
|
|
|
const char* ServerWindowAcquireName(ServerWindowAcquire result) {
|
|
switch (result) {
|
|
case ServerWindowAcquire::Acquired:
|
|
return "Acquired";
|
|
case ServerWindowAcquire::NoDisplay:
|
|
return "NoDisplay";
|
|
case ServerWindowAcquire::NoWindow:
|
|
return "NoWindow";
|
|
case ServerWindowAcquire::Cancelled:
|
|
return "Cancelled";
|
|
case ServerWindowAcquire::Interrupted:
|
|
return "Interrupted";
|
|
case ServerWindowAcquire::LeasedElsewhere:
|
|
return "LeasedElsewhere";
|
|
}
|
|
return "<unknown ServerWindowAcquire>";
|
|
}
|
|
|
|
const char* ServerWindowDetachName(ServerWindowDetach result) {
|
|
switch (result) {
|
|
case ServerWindowDetach::NoWindow:
|
|
return "NoWindow";
|
|
case ServerWindowDetach::Released:
|
|
return "Released";
|
|
case ServerWindowDetach::ReleasedBySession:
|
|
return "ReleasedBySession";
|
|
case ServerWindowDetach::TimedOut:
|
|
return "TimedOut";
|
|
}
|
|
return "<unknown ServerWindowDetach>";
|
|
}
|
|
|
|
void ServerDisplay::ReleaseOutsideLock(const ServerDisplayHooks& hooks, void* window) const {
|
|
if (window != nullptr && hooks.release != nullptr) hooks.release(hooks.user, window);
|
|
}
|
|
|
|
void ServerDisplay::NoteExtentReportLocked(Uint32 width, Uint32 height) {
|
|
++m_extentReports;
|
|
// Only a report made while the layout owns the size says what the layout's size is.
|
|
if (m_requestedWidth == 0 && m_requestedHeight == 0) {
|
|
m_layoutWidth = width;
|
|
m_layoutHeight = height;
|
|
}
|
|
}
|
|
|
|
void ServerDisplay::Install(const ServerDisplayHooks& hooks) {
|
|
{
|
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
|
m_hooks = hooks;
|
|
m_installed = true;
|
|
}
|
|
m_cv.notify_all();
|
|
MGLOG_I("MG_Remote server: this process owns a display (ServerDisplay installed); a ServerOwned "
|
|
"window surface is served on its window");
|
|
}
|
|
|
|
void ServerDisplay::Uninstall(Uint32 detachTimeoutMs) {
|
|
(void)Detach(detachTimeoutMs);
|
|
{
|
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
|
m_installed = false;
|
|
// The hooks stay until every reference they took is released: a timed-out Detach may
|
|
// still owe EndLease a release through them.
|
|
if (m_releaseAfterLease.empty()) m_hooks = ServerDisplayHooks{};
|
|
}
|
|
m_cv.notify_all();
|
|
MGLOG_I("MG_Remote server: ServerDisplay uninstalled; this process owns no display");
|
|
}
|
|
|
|
Bool ServerDisplay::HasDisplay() const {
|
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
|
return m_installed;
|
|
}
|
|
|
|
void ServerDisplay::Attach(void* window, Uint32 width, Uint32 height) {
|
|
if (window == nullptr) return;
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
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%s", window, width, height,
|
|
told ? "; its session is told" : "");
|
|
return;
|
|
}
|
|
if (m_window != nullptr) {
|
|
// A new window while one is still attached - the platform skipped the destroy. The old
|
|
// one is detached exactly as surfaceDestroyed would have done it, bounded.
|
|
lock.unlock();
|
|
const ServerWindowDetach detached = Detach(kDefaultDetachTimeoutMs);
|
|
MGLOG_W("MG_Remote server: server window %p attached while another was still attached; the "
|
|
"old one was detached first (%s)",
|
|
window, ServerWindowDetachName(detached));
|
|
lock.lock();
|
|
}
|
|
if (m_hooks.acquire != nullptr) m_hooks.acquire(m_hooks.user, window);
|
|
m_window = window;
|
|
m_width = width;
|
|
m_height = height;
|
|
NoteExtentReportLocked(width, height);
|
|
++m_generation;
|
|
const Uint64 generation = m_generation;
|
|
// A suspended session (screen off, lock) rebuilds its surface on this window.
|
|
const Uint32 suspended = static_cast<Uint32>(m_suspended.size());
|
|
NotifySuspendedLocked();
|
|
lock.unlock();
|
|
m_cv.notify_all();
|
|
MGLOG_I("MG_Remote server: server window %p attached at %ux%u (generation %llu)%s", window, width, height,
|
|
static_cast<unsigned long long>(generation),
|
|
suspended != 0 ? "; a suspended session is told to resume on it" : "");
|
|
}
|
|
|
|
void ServerDisplay::NotifySuspendedLocked() const {
|
|
for (const SuspendedHolder& entry : m_suspended) {
|
|
if (entry.onAttached != nullptr) entry.onAttached(entry.holder);
|
|
}
|
|
}
|
|
|
|
ServerWindowDetach ServerDisplay::Detach(Uint32 timeoutMs) {
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
if (m_window == nullptr) return ServerWindowDetach::NoWindow;
|
|
void* const window = m_window;
|
|
m_window = nullptr;
|
|
m_width = 0;
|
|
m_height = 0;
|
|
++m_generation;
|
|
ServerWindowDetach result = ServerWindowDetach::Released;
|
|
if (m_leaseHolder != nullptr) {
|
|
// A live on-screen session renders into this window. Ask it to let go - the hook runs
|
|
// under this lock, so the holder cannot end its lease (and go away) between being read
|
|
// here and being called - then wait for EndLease, bounded.
|
|
if (!m_lostRequested) {
|
|
m_lostRequested = true;
|
|
if (m_onLost != nullptr) m_onLost(m_leaseHolder);
|
|
}
|
|
const Bool released =
|
|
m_cv.wait_for(lock, std::chrono::milliseconds(timeoutMs), [this] { return m_leaseHolder == nullptr; });
|
|
if (!released) {
|
|
// NOT RELEASED UNDER IT. The holder may still reference the window (Magma keeps the
|
|
// raw pointer until the swapchain is built), so the reference stays until it ends
|
|
// its lease. The UI thread is not held past its bound either way.
|
|
m_releaseAfterLease.push_back(window);
|
|
lock.unlock();
|
|
m_cv.notify_all();
|
|
MGLOG_E("MG_Remote server: surfaceDestroyed - the on-screen session did not release server "
|
|
"window %p within %u ms; its reference is kept until it does (Detach TimedOut)",
|
|
window, timeoutMs);
|
|
return ServerWindowDetach::TimedOut;
|
|
}
|
|
result = ServerWindowDetach::ReleasedBySession;
|
|
}
|
|
const ServerDisplayHooks hooks = m_hooks;
|
|
lock.unlock();
|
|
m_cv.notify_all();
|
|
ReleaseOutsideLock(hooks, window);
|
|
MGLOG_I("MG_Remote server: server window %p detached (%s)", window, ServerWindowDetachName(result));
|
|
return result;
|
|
}
|
|
|
|
ServerWindowAcquire ServerDisplay::AcquireFor(Uint32 width, Uint32 height, Uint32 timeoutMs, void* holder,
|
|
ServerWindowLostHook onLost, ServerWindowWaitCancel cancel,
|
|
void* cancelUser, ServerWindowLease* out,
|
|
ServerWindowResizedHook onResized) {
|
|
ServerDisplayHooks hooks{};
|
|
Uint64 interrupts = 0;
|
|
const Bool sizeRequested = width != 0 && height != 0;
|
|
// P12 review fix (stale size): a 0/0 request after a fixed one hands the size BACK to the
|
|
// layout, and until the platform has done so the window still reports the fixed size.
|
|
Bool backToLayout = false;
|
|
Uint32 previousWidth = 0;
|
|
Uint32 previousHeight = 0;
|
|
Uint64 reportsAtRequest = 0;
|
|
{
|
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
|
if (!m_installed) return ServerWindowAcquire::NoDisplay;
|
|
if (m_leaseHolder != nullptr && m_leaseHolder != holder && !m_leaseEnding)
|
|
return ServerWindowAcquire::LeasedElsewhere;
|
|
hooks = m_hooks;
|
|
interrupts = m_interrupts;
|
|
previousWidth = m_requestedWidth;
|
|
previousHeight = m_requestedHeight;
|
|
backToLayout = !sizeRequested && (previousWidth != 0 || previousHeight != 0);
|
|
m_requestedWidth = sizeRequested ? width : 0u;
|
|
m_requestedHeight = sizeRequested ? height : 0u;
|
|
reportsAtRequest = m_extentReports;
|
|
}
|
|
// The geometry request is an up-call into the platform (Java), made without the lock: its
|
|
// answer comes back as an Attach() of the same window, which needs the lock.
|
|
if (hooks.requestGeometry != nullptr) hooks.requestGeometry(hooks.user, width, height);
|
|
// What "the size asked for" means on each path. A fixed size: the window reports it. The
|
|
// layout's, after a fixed one: the window reports the layout's extent it reported before, or
|
|
// - the layout moved meanwhile (a rotation), or was never seen - reports anything but the old
|
|
// fixed size after this request. The layout's, with no fixed size in effect: whatever it is.
|
|
const auto sizeReached = [&] {
|
|
if (sizeRequested) return m_width == width && m_height == height;
|
|
if (!backToLayout) return true;
|
|
if (m_layoutWidth != 0 && m_width == m_layoutWidth && m_height == m_layoutHeight) return true;
|
|
return m_extentReports != reportsAtRequest && (m_width != previousWidth || m_height != previousHeight);
|
|
};
|
|
|
|
std::unique_lock<std::mutex> lock(m_mutex);
|
|
const auto deadline = Clock::now() + std::chrono::milliseconds(timeoutMs);
|
|
Bool attachedSeen = false;
|
|
Bool waitedForEndingHolder = false;
|
|
auto geometryDeadline = deadline;
|
|
for (;;) {
|
|
if (!m_installed) return ServerWindowAcquire::NoDisplay;
|
|
if (m_interrupts != interrupts) return ServerWindowAcquire::Interrupted;
|
|
if (cancel != nullptr && cancel(cancelUser)) return ServerWindowAcquire::Cancelled;
|
|
// ANOTHER HOLDER: refused at once while its session goes on, waited for while it ends
|
|
// (its EndLease wakes this). The window is judged only once the lease is free.
|
|
const Bool heldElsewhere = m_leaseHolder != nullptr && m_leaseHolder != holder;
|
|
if (heldElsewhere && !m_leaseEnding) return ServerWindowAcquire::LeasedElsewhere;
|
|
if (heldElsewhere && !waitedForEndingHolder) {
|
|
waitedForEndingHolder = true;
|
|
MGLOG_I("MG_Remote server: the server window is still leased by a session that is ending; this "
|
|
"session waits for that lease to end (at most %u ms)",
|
|
timeoutMs);
|
|
}
|
|
const auto now = Clock::now();
|
|
if (heldElsewhere) {
|
|
attachedSeen = false;
|
|
geometryDeadline = deadline;
|
|
} else if (m_window != nullptr) {
|
|
if (!attachedSeen) {
|
|
attachedSeen = true;
|
|
geometryDeadline = std::min(deadline, now + std::chrono::milliseconds(kGeometryGraceMs));
|
|
}
|
|
const Bool sizeOk = sizeReached();
|
|
if (sizeOk || now >= geometryDeadline) {
|
|
m_leaseHolder = holder;
|
|
m_onLost = onLost;
|
|
m_onResized = onResized;
|
|
m_lostRequested = false;
|
|
m_leaseEnding = 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(),
|
|
[holder](const SuspendedHolder& entry) { return entry.holder == holder; }),
|
|
m_suspended.end());
|
|
if (out != nullptr) {
|
|
out->window = m_window;
|
|
out->width = m_width;
|
|
out->height = m_height;
|
|
out->generation = m_generation;
|
|
out->sizeAsRequested = sizeOk;
|
|
}
|
|
if (!sizeOk && sizeRequested) {
|
|
MGLOG_W("MG_Remote server: the server window is %ux%u, not the %ux%u requested, after "
|
|
"%u ms of geometry grace; the surface is created at the window's size and the "
|
|
"client is told so",
|
|
m_width, m_height, width, height, kGeometryGraceMs);
|
|
} else if (!sizeOk) {
|
|
MGLOG_W("MG_Remote server: the server window still reports %ux%u - the previous fixed "
|
|
"size - %u ms after its size was handed back to the layout; the surface is created "
|
|
"at that size and the client is told so",
|
|
m_width, m_height, kGeometryGraceMs);
|
|
}
|
|
if (waitedForEndingHolder) {
|
|
MGLOG_I("MG_Remote server: the ending session's lease ended; the server window is leased to "
|
|
"this session");
|
|
}
|
|
return ServerWindowAcquire::Acquired;
|
|
}
|
|
} else {
|
|
attachedSeen = false;
|
|
geometryDeadline = deadline;
|
|
}
|
|
if (now >= deadline)
|
|
return heldElsewhere ? ServerWindowAcquire::LeasedElsewhere : ServerWindowAcquire::NoWindow;
|
|
const auto wake = std::min({deadline, geometryDeadline, now + kCancelPollInterval});
|
|
m_cv.wait_until(lock, wake);
|
|
}
|
|
}
|
|
|
|
Bool ServerDisplay::EndLeaseLocked(void* holder, std::vector<void*>* releases, ServerDisplayHooks* hooks) {
|
|
if (holder == nullptr || m_leaseHolder != holder) return false;
|
|
m_leaseHolder = nullptr;
|
|
m_onLost = nullptr;
|
|
m_onResized = nullptr;
|
|
m_lostRequested = false;
|
|
m_leaseEnding = false;
|
|
releases->swap(m_releaseAfterLease);
|
|
*hooks = m_hooks;
|
|
// An Uninstall that ran while this lease still owed releases kept the hooks for them.
|
|
if (!m_installed) m_hooks = ServerDisplayHooks{};
|
|
return true;
|
|
}
|
|
|
|
void ServerDisplay::ReleaseAfterLease(const ServerDisplayHooks& hooks, const std::vector<void*>& releases) const {
|
|
for (void* window : releases) {
|
|
ReleaseOutsideLock(hooks, window);
|
|
MGLOG_I("MG_Remote server: server window %p released after its session let go (a late Detach)", window);
|
|
}
|
|
}
|
|
|
|
void ServerDisplay::EndLease(void* holder) {
|
|
std::vector<void*> releases;
|
|
ServerDisplayHooks hooks{};
|
|
{
|
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
|
if (!EndLeaseLocked(holder, &releases, &hooks)) return;
|
|
}
|
|
m_cv.notify_all();
|
|
ReleaseAfterLease(hooks, releases);
|
|
}
|
|
|
|
void ServerDisplay::MarkLeaseEnding(void* holder) {
|
|
{
|
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
|
if (holder == nullptr || m_leaseHolder != holder || m_leaseEnding) return;
|
|
m_leaseEnding = true;
|
|
}
|
|
// A request already refused is not waiting; one made from here on waits for the EndLease.
|
|
m_cv.notify_all();
|
|
}
|
|
|
|
void ServerDisplay::SuspendLease(void* holder, ServerWindowAttachedHook onAttached) {
|
|
if (holder == nullptr) return;
|
|
std::vector<void*> releases;
|
|
ServerDisplayHooks hooks{};
|
|
{
|
|
// ONE LOCK HOLD for the lease's end and the registration, so no Attach can fall between
|
|
// them and go unheard.
|
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
|
(void)EndLeaseLocked(holder, &releases, &hooks);
|
|
Bool registered = false;
|
|
for (SuspendedHolder& entry : m_suspended) {
|
|
if (entry.holder != holder) continue;
|
|
entry.onAttached = onAttached;
|
|
registered = true;
|
|
}
|
|
if (!registered) m_suspended.push_back(SuspendedHolder{holder, onAttached});
|
|
// A window that is already up (a Detach that raced an Attach) is the one to resume on.
|
|
if (m_window != nullptr && onAttached != nullptr) onAttached(holder);
|
|
}
|
|
m_cv.notify_all();
|
|
ReleaseAfterLease(hooks, releases);
|
|
}
|
|
|
|
void ServerDisplay::CancelResume(void* holder) {
|
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
|
m_suspended.erase(std::remove_if(m_suspended.begin(), m_suspended.end(),
|
|
[holder](const SuspendedHolder& entry) { return entry.holder == holder; }),
|
|
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());
|
|
}
|
|
|
|
void ServerDisplay::Interrupt() {
|
|
{
|
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
|
++m_interrupts;
|
|
}
|
|
m_cv.notify_all();
|
|
}
|
|
|
|
Bool ServerDisplay::Attached() const {
|
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
|
return m_window != nullptr;
|
|
}
|
|
|
|
Bool ServerDisplay::Leased() const {
|
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
|
return m_leaseHolder != nullptr;
|
|
}
|
|
|
|
Bool ServerDisplay::LeaseEnding() const {
|
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
|
return m_leaseHolder != nullptr && m_leaseEnding;
|
|
}
|
|
|
|
Uint64 ServerDisplay::Generation() const {
|
|
const std::lock_guard<std::mutex> lock(m_mutex);
|
|
return m_generation;
|
|
}
|
|
|
|
ServerDisplay& ServerDisplayInstance() {
|
|
// ID-8: leak at exit, like every MG_Remote singleton.
|
|
static ServerDisplay& instance = *new ServerDisplay{};
|
|
return instance;
|
|
}
|
|
|
|
#if defined(__ANDROID__)
|
|
namespace {
|
|
void AcquireAndroidWindow(void*, void* window) {
|
|
ANativeWindow_acquire(static_cast<ANativeWindow*>(window));
|
|
}
|
|
void ReleaseAndroidWindow(void*, void* window) {
|
|
ANativeWindow_release(static_cast<ANativeWindow*>(window));
|
|
}
|
|
} // namespace
|
|
|
|
ServerDisplayHooks AndroidNativeWindowHooks(void (*requestGeometry)(void* user, Uint32 width, Uint32 height),
|
|
void* user) {
|
|
ServerDisplayHooks hooks;
|
|
hooks.acquire = &AcquireAndroidWindow;
|
|
hooks.release = &ReleaseAndroidWindow;
|
|
hooks.requestGeometry = requestGeometry;
|
|
hooks.user = user;
|
|
return hooks;
|
|
}
|
|
#endif
|
|
|
|
} // namespace MobileGL::MG_Remote::Server
|