mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[Feature] (Remote): expose a package-neutral embedded display server API
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user