[Feature] (Remote): expose a package-neutral embedded display server API

This commit is contained in:
2026-10-01 12:31:57 -04:00
parent 32c79f4135
commit a5b463a5e8
4 changed files with 243 additions and 0 deletions
+2
View File
@@ -677,6 +677,8 @@ if (MOBILEGL_BUILD_DISAGGREGATED)
# (the display Activity's SurfaceView on Android; nobody installs one on the host, so a
# ServerOwned request is refused by name there).
MobileGL/MG_Remote/Server/ServerDisplay.cpp
# Package-neutral C ABI for an APK that owns the server and its ANativeWindow.
MobileGL/MG_Remote/Server/EmbeddedServer.cpp
# P7 gate 5 (g5-msprobe): the Magma wire arm's two multisample depth/stencil resolve
# arms and the probe that measures the first against the second, shared by the
# renderer's bring-up and the Vulkan POST. Wire-only - the monolith records neither arm -
@@ -0,0 +1,90 @@
// SPDX-License-Identifier: LGPL-3.0-only
#include "EmbeddedServer.h"
#include "ServerDisplay.h"
#if defined(__ANDROID__)
#include <android/native_window.h>
#endif
namespace {
using namespace MobileGL::MG_Remote::Server;
int DetachResult(ServerWindowDetach result) {
switch (result) {
case ServerWindowDetach::NoWindow: return MOBILEGL_SERVER_WINDOW_NO_WINDOW;
case ServerWindowDetach::Released: return MOBILEGL_SERVER_WINDOW_RELEASED;
case ServerWindowDetach::ReleasedBySession: return MOBILEGL_SERVER_WINDOW_RELEASED_BY_SESSION;
case ServerWindowDetach::TimedOut: return MOBILEGL_SERVER_WINDOW_TIMED_OUT;
}
return MOBILEGL_SERVER_WINDOW_TIMED_OUT;
}
}
extern "C" int mobilegl_server_display_install(const struct mobilegl_server_display_hooks* hooks) {
if (hooks == nullptr || hooks->acquire == nullptr || hooks->release == nullptr)
return MOBILEGL_SERVER_DISPLAY_INVALID_ARGUMENT;
auto& display = ServerDisplayInstance();
if (display.HasDisplay() || display.Attached() || display.Leased())
return MOBILEGL_SERVER_DISPLAY_ALREADY_INSTALLED;
ServerDisplayHooks nativeHooks;
nativeHooks.acquire = hooks->acquire;
nativeHooks.release = hooks->release;
nativeHooks.requestGeometry = hooks->request_geometry;
nativeHooks.user = hooks->user;
display.Install(nativeHooks);
return MOBILEGL_SERVER_DISPLAY_OK;
}
extern "C" int mobilegl_server_display_install_android(mobilegl_server_geometry_callback request_geometry, void* user) {
#if defined(__ANDROID__)
const auto androidHooks = AndroidNativeWindowHooks(request_geometry, user);
const mobilegl_server_display_hooks hooks{androidHooks.acquire, androidHooks.release,
androidHooks.requestGeometry, androidHooks.user};
return mobilegl_server_display_install(&hooks);
#else
(void)request_geometry;
(void)user;
return MOBILEGL_SERVER_DISPLAY_UNSUPPORTED_PLATFORM;
#endif
}
extern "C" int mobilegl_server_display_attach(void* window, uint32_t width, uint32_t height) {
if (window == nullptr) return MOBILEGL_SERVER_DISPLAY_INVALID_ARGUMENT;
auto& display = ServerDisplayInstance();
if (!display.HasDisplay()) return MOBILEGL_SERVER_DISPLAY_NOT_INSTALLED;
display.Attach(window, width, height);
return MOBILEGL_SERVER_DISPLAY_OK;
}
extern "C" int mobilegl_server_display_attach_android(struct ANativeWindow* window, uint32_t width, uint32_t height) {
#if defined(__ANDROID__)
if (window == nullptr) return MOBILEGL_SERVER_DISPLAY_INVALID_ARGUMENT;
if (width == 0) {
const int32_t actual = ANativeWindow_getWidth(window);
if (actual > 0) width = static_cast<uint32_t>(actual);
}
if (height == 0) {
const int32_t actual = ANativeWindow_getHeight(window);
if (actual > 0) height = static_cast<uint32_t>(actual);
}
return mobilegl_server_display_attach(window, width, height);
#else
(void)window;
(void)width;
(void)height;
return MOBILEGL_SERVER_DISPLAY_UNSUPPORTED_PLATFORM;
#endif
}
extern "C" int mobilegl_server_display_detach(uint32_t timeout_ms) {
return DetachResult(ServerDisplayInstance().Detach(timeout_ms));
}
extern "C" int mobilegl_server_display_uninstall(uint32_t timeout_ms) {
auto& display = ServerDisplayInstance();
const int result = DetachResult(display.Detach(timeout_ms));
// Detach has already removed the attached window even if its lease is still live. Uninstall
// keeps the original hooks for EndLease's deferred release, without another timeout budget.
display.Uninstall(0);
return result;
}
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: LGPL-3.0-only
// Public C interface for an application that owns a MobileGL split server.
#pragma once
#include <stdint.h>
#if defined(_WIN32)
#define MOBILEGL_SERVER_EXPORT
#else
#define MOBILEGL_SERVER_EXPORT __attribute__((visibility("default")))
#endif
#ifdef __cplusplus
extern "C" {
#endif
struct ANativeWindow;
typedef void (*mobilegl_server_geometry_callback)(void* user, uint32_t width, uint32_t height);
// Opaque-window hooks also let non-Android hosts embed the display without depending on
// MobileGL's C++ headers. Both reference callbacks are required. Geometry may be null.
struct mobilegl_server_display_hooks {
void (*acquire)(void* user, void* window);
void (*release)(void* user, void* window);
mobilegl_server_geometry_callback request_geometry;
void* user;
};
enum mobilegl_server_display_result {
MOBILEGL_SERVER_DISPLAY_OK = 0,
MOBILEGL_SERVER_DISPLAY_INVALID_ARGUMENT = -1,
MOBILEGL_SERVER_DISPLAY_ALREADY_INSTALLED = -2,
MOBILEGL_SERVER_DISPLAY_NOT_INSTALLED = -3,
MOBILEGL_SERVER_DISPLAY_UNSUPPORTED_PLATFORM = -4
};
enum mobilegl_server_window_detach {
MOBILEGL_SERVER_WINDOW_NO_WINDOW = 0,
MOBILEGL_SERVER_WINDOW_RELEASED = 1,
MOBILEGL_SERVER_WINDOW_RELEASED_BY_SESSION = 2,
MOBILEGL_SERVER_WINDOW_TIMED_OUT = 3
};
// Install once before serving; owner lifecycle calls must be serialized. A live display or
// an old lease awaiting release refuses a replacement, so its window always uses its original
// reference hooks. The geometry callback runs on the apply thread and must return promptly.
// A positive size requests fixed buffer dimensions; 0/0 requests the host's layout dimensions.
// Report the resulting dimensions with attach, for example from SurfaceHolder.surfaceChanged.
// Callback user data must live until uninstall has returned and any timed-out lease has ended.
MOBILEGL_SERVER_EXPORT int mobilegl_server_display_install(const struct mobilegl_server_display_hooks* hooks);
MOBILEGL_SERVER_EXPORT int mobilegl_server_display_install_android(mobilegl_server_geometry_callback request_geometry,
void* user);
// Attach retains its own reference. Attaching the same window updates its dimensions without
// another retain. A different window detaches the previous one first (up to 3 seconds).
// Android attach uses ANativeWindow_getWidth/Height for zero dimensions; the caller may release
// its ANativeWindow_fromSurface reference immediately after this call.
MOBILEGL_SERVER_EXPORT int mobilegl_server_display_attach(void* window, uint32_t width, uint32_t height);
MOBILEGL_SERVER_EXPORT int mobilegl_server_display_attach_android(struct ANativeWindow* window, uint32_t width,
uint32_t height);
// Returns mobilegl_server_window_detach. A timeout never drops a reference while the backend
// is rendering: release happens when the apply thread ends its lease. Surface loss currently
// ends the live split session; a fresh client session can use a newly attached window.
MOBILEGL_SERVER_EXPORT int mobilegl_server_display_detach(uint32_t timeout_ms);
MOBILEGL_SERVER_EXPORT int mobilegl_server_display_uninstall(uint32_t timeout_ms);
// Blocking serve on the caller's thread; stop is non-blocking and safe on the UI thread.
// Endpoint: filesystem path, @abstract AF_UNIX name (SCM_RIGHTS + shared-memory rings), or
// tcp://host:port. One client session per process; independent workers need distinct endpoints.
// Before loading libMobileGL set MOBILEGL_IPC_ROLE=server and MOBILEGL_IPC_DIAL=no, and unset
// MOBILEGL_TRANSPORT, MOBILEGL_IPC_SERVER_PATH, MOBILEGL_IPC_RING_MB, MOBILEGL_IPC_STAGE_MB.
MOBILEGL_SERVER_EXPORT int mobilegl_server_serve_inprocess(const char* endpoint);
MOBILEGL_SERVER_EXPORT void mobilegl_server_stop_inprocess(void);
#ifdef __cplusplus
}
#endif
@@ -19,6 +19,7 @@
// wait by name.
#include <MG_Remote/Server/ServerDisplay.h>
#include <MG_Remote/Server/EmbeddedServer.h>
#include <gtest/gtest.h>
@@ -118,6 +119,77 @@ namespace {
} // namespace
// The public embedding ABI must preserve the same ownership rules for an APK that supplies its
// own Surface, including a late apply-thread release after its UI thread has stopped waiting.
class EmbeddedServerTest : public ::testing::Test {
protected:
FakePlatform platform;
mobilegl_server_display_hooks Hooks() {
return {&FakeAcquire, &FakeRelease, &FakeGeometry, &platform};
}
void TearDown() override {
ServerDisplayInstance().EndLease(this);
(void)mobilegl_server_display_uninstall(0);
}
};
TEST_F(EmbeddedServerTest, AHostsReferenceIsRetainedAndGeometryUpdatesDoNotRetainAgain) {
const auto hooks = Hooks();
ASSERT_EQ(mobilegl_server_display_install(&hooks), MOBILEGL_SERVER_DISPLAY_OK);
ASSERT_EQ(mobilegl_server_display_attach(FakeWindow(0), 32, 24), MOBILEGL_SERVER_DISPLAY_OK);
ASSERT_EQ(mobilegl_server_display_attach(FakeWindow(0), 64, 48), MOBILEGL_SERVER_DISPLAY_OK);
EXPECT_EQ(platform.acquires.load(), 1);
ServerWindowLease lease;
ASSERT_EQ(ServerDisplayInstance().AcquireFor(64, 48, 100, this, nullptr, nullptr, nullptr, &lease),
ServerWindowAcquire::Acquired);
EXPECT_EQ(platform.lastWidth, 64u);
EXPECT_EQ(platform.lastHeight, 48u);
EXPECT_EQ(lease.width, 64u);
EXPECT_EQ(lease.height, 48u);
ServerDisplayInstance().EndLease(this);
EXPECT_EQ(mobilegl_server_display_detach(100), MOBILEGL_SERVER_WINDOW_RELEASED);
EXPECT_EQ(platform.releases.load(), 1);
EXPECT_EQ(mobilegl_server_display_uninstall(100), MOBILEGL_SERVER_WINDOW_NO_WINDOW);
}
TEST_F(EmbeddedServerTest, ATimeoutKeepsTheOriginalHooksUntilTheBackendLetsGo) {
const auto hooks = Hooks();
ASSERT_EQ(mobilegl_server_display_install(&hooks), MOBILEGL_SERVER_DISPLAY_OK);
ASSERT_EQ(mobilegl_server_display_attach(FakeWindow(0), 64, 48), MOBILEGL_SERVER_DISPLAY_OK);
ServerWindowLease lease;
// The holder deliberately has no lost callback: it is a wedged apply thread. Uninstall must
// remain bounded and retain the window until this holder explicitly releases its lease.
ASSERT_EQ(ServerDisplayInstance().AcquireFor(0, 0, 100, this, nullptr, nullptr, nullptr, &lease),
ServerWindowAcquire::Acquired);
EXPECT_EQ(mobilegl_server_display_uninstall(1), MOBILEGL_SERVER_WINDOW_TIMED_OUT);
EXPECT_FALSE(ServerDisplayInstance().HasDisplay());
EXPECT_EQ(platform.releases.load(), 0);
EXPECT_EQ(mobilegl_server_display_install(&hooks), MOBILEGL_SERVER_DISPLAY_ALREADY_INSTALLED);
ServerDisplayInstance().EndLease(this);
EXPECT_EQ(platform.releases.load(), 1);
EXPECT_EQ(mobilegl_server_display_install(&hooks), MOBILEGL_SERVER_DISPLAY_OK);
}
TEST_F(EmbeddedServerTest, AnOwnerCannotReplaceLiveReferenceHooksOrAttachBeforeInstall) {
const auto hooks = Hooks();
EXPECT_EQ(mobilegl_server_display_attach(FakeWindow(0), 64, 48), MOBILEGL_SERVER_DISPLAY_NOT_INSTALLED);
EXPECT_EQ(mobilegl_server_display_install(nullptr), MOBILEGL_SERVER_DISPLAY_INVALID_ARGUMENT);
mobilegl_server_display_hooks incomplete{};
EXPECT_EQ(mobilegl_server_display_install(&incomplete), MOBILEGL_SERVER_DISPLAY_INVALID_ARGUMENT);
ASSERT_EQ(mobilegl_server_display_install(&hooks), MOBILEGL_SERVER_DISPLAY_OK);
EXPECT_EQ(mobilegl_server_display_install(&hooks), MOBILEGL_SERVER_DISPLAY_ALREADY_INSTALLED);
EXPECT_EQ(mobilegl_server_display_attach(nullptr, 64, 48), MOBILEGL_SERVER_DISPLAY_INVALID_ARGUMENT);
}
#if !defined(__ANDROID__)
TEST_F(EmbeddedServerTest, AndroidWindowsAreRefusedByNameOnOtherPlatforms) {
EXPECT_EQ(mobilegl_server_display_install_android(nullptr, nullptr), MOBILEGL_SERVER_DISPLAY_UNSUPPORTED_PLATFORM);
EXPECT_EQ(mobilegl_server_display_attach_android(nullptr, 64, 48), MOBILEGL_SERVER_DISPLAY_UNSUPPORTED_PLATFORM);
EXPECT_FALSE(ServerDisplayInstance().HasDisplay());
}
#endif
// Nobody installed a display: HasDisplay is false and a wait for a window answers NoDisplay at once
// - the host server's and the exec'd supervisor's shape, and what makes a ServerOwned request a named
// refusal there.