mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
[Fix] (Remote): a surfaceless make-current's hidden pbuffer no longer latches the session offscreen.
This commit is contained in:
@@ -235,6 +235,11 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
return *standIns;
|
||||
}
|
||||
|
||||
// eglCreatePbufferSurface for the stand-in, except that a split client creates it on the
|
||||
// server as a stand-in, which takes no surface mode there (a server-owned window may follow).
|
||||
EGLSurface CreateSurfacelessStandInPbuffer(EGLStateContext* state, EGLDisplay dpy, EGLConfig config,
|
||||
const EGLint* attribs);
|
||||
|
||||
// The display's stand-in, made now if it has none (or the one it had did not survive).
|
||||
// EGL_NO_SURFACE when the backend could not make a pbuffer; the EGL error is then set.
|
||||
EGLSurface SurfacelessStandIn(EGLStateContext* state, EGLDisplay dpy) {
|
||||
@@ -251,7 +256,7 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
const EGLint attribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
|
||||
const EGLSurface standIn = CreatePbufferSurface(dpy, config, attribs);
|
||||
const EGLSurface standIn = CreateSurfacelessStandInPbuffer(state, dpy, config, attribs);
|
||||
if (standIn != EGL_NO_SURFACE) standIns[dpy] = standIn;
|
||||
return standIn;
|
||||
}
|
||||
@@ -428,6 +433,26 @@ namespace MobileGL::MG_Impl::EGLImpl {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
EGLSurface CreateSurfacelessStandInPbuffer(EGLStateContext* state, EGLDisplay dpy, EGLConfig config,
|
||||
const EGLint* attribs) {
|
||||
#if MOBILEGL_BUILD_DISAGGREGATED
|
||||
auto* remote = dynamic_cast<MG_Remote::Client::BackendObject_Remote*>(GetBackendObject(state));
|
||||
if (remote == nullptr) return CreatePbufferSurface(dpy, config, attribs);
|
||||
if (!RecoverBeforeCreatingSurface(state)) return EGL_NO_SURFACE;
|
||||
const EGLSurface surface = state->CreatePbufferSurface(dpy, config, attribs);
|
||||
if (surface == EGL_NO_SURFACE) return EGL_NO_SURFACE;
|
||||
if (!remote->CreateEGLSurfacelessStandIn(surface, GetAttribValue(attribs, EGL_WIDTH, 1),
|
||||
GetAttribValue(attribs, EGL_HEIGHT, 1))) {
|
||||
state->DestroySurface(dpy, surface);
|
||||
state->SetError(EGL_BAD_ALLOC);
|
||||
return EGL_NO_SURFACE;
|
||||
}
|
||||
return surface;
|
||||
#else
|
||||
return CreatePbufferSurface(dpy, config, attribs);
|
||||
#endif
|
||||
}
|
||||
} // namespace
|
||||
|
||||
EGLSurface CreateWindowSurface(EGLDisplay dpy, EGLConfig config, NativeWindowType window,
|
||||
|
||||
@@ -289,9 +289,21 @@ namespace MobileGL::MG_Remote::Client {
|
||||
}
|
||||
|
||||
Bool BackendObject_Remote::CreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height) {
|
||||
return CreatePbufferOnServer(surface, width, height, /*standIn=*/false);
|
||||
}
|
||||
|
||||
Bool BackendObject_Remote::CreateEGLSurfacelessStandIn(EGLSurface surface, EGLint width, EGLint height) {
|
||||
if (!CreatePbufferOnServer(surface, width, height, /*standIn=*/true)) return false;
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
m_standInSurfaces.insert(surface);
|
||||
return true;
|
||||
}
|
||||
|
||||
Bool BackendObject_Remote::CreatePbufferOnServer(EGLSurface surface, EGLint width, EGLint height, Bool standIn) {
|
||||
WaitForApplyBeforeEglForwarder("CreateEGLPbufferSurface");
|
||||
Server::SurfaceRefusalCode refusal = Server::SurfaceRefusalCode::None;
|
||||
if (!Server::ServerCreateEGLPbufferSurface(surface, width, height, &refusal)) {
|
||||
if (!Server::ServerCreateEGLPbufferSurface(surface, width, height, &refusal,
|
||||
standIn ? Server::kPbufferFlagSurfacelessStandIn : 0u)) {
|
||||
if (refusal == Server::SurfaceRefusalCode::SurfaceModeMismatch) {
|
||||
// P12 (D4), named on THIS side too: the server logged its half.
|
||||
MGLOG_E("MG_Remote client: SurfaceModeMismatch - eglCreatePbufferSurface (%dx%d) in a session "
|
||||
@@ -468,6 +480,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
{
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
homed = m_homedSurfaces.erase(surface) != 0;
|
||||
m_standInSurfaces.erase(surface);
|
||||
}
|
||||
if (homed && !ClientSession::SessionLatchedLost()) Server::ServerReleaseEGLSurface(surface);
|
||||
MG_Backend::BackendObject::ReleaseEGLSurface(surface);
|
||||
@@ -481,6 +494,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
{
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
m_homedSurfaces.clear();
|
||||
m_standInSurfaces.clear();
|
||||
}
|
||||
MG_Backend::BackendObject::ReleaseEGLResources();
|
||||
}
|
||||
@@ -516,7 +530,8 @@ namespace MobileGL::MG_Remote::Client {
|
||||
Bool recreated = false;
|
||||
const char* kind = "pbuffer";
|
||||
if (state.Kind == SurfaceKind::Pbuffer) {
|
||||
recreated = Server::ServerCreateEGLPbufferSurface(surface, state.Width, state.Height);
|
||||
const Uint32 flags = m_standInSurfaces.count(surface) != 0 ? Server::kPbufferFlagSurfacelessStandIn : 0u;
|
||||
recreated = Server::ServerCreateEGLPbufferSurface(surface, state.Width, state.Height, nullptr, flags);
|
||||
} else if (ClientSession::IsServerOwnedWindowSurface(surface)) {
|
||||
kind = "server-owned window";
|
||||
const Server::ServerOwnedWindowReply reply =
|
||||
|
||||
@@ -108,6 +108,11 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// session are forgotten without a release (their server is gone).
|
||||
void OnSessionReplaced();
|
||||
|
||||
// EGL_KHR_surfaceless_context's hidden pbuffer (EGLImpl's SurfacelessStandIn): an ordinary
|
||||
// pbuffer on this side, created on the server with kPbufferFlagSurfacelessStandIn so it
|
||||
// takes no surface mode there, and re-created the same way on a fresh session.
|
||||
Bool CreateEGLSurfacelessStandIn(EGLSurface surface, EGLint width, EGLint height);
|
||||
|
||||
protected:
|
||||
Bool InitPbufferSurface(EGLint width, EGLint height) override;
|
||||
|
||||
@@ -125,6 +130,7 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// The server side of `surface` on the CURRENT session: re-created from its client
|
||||
// registration when the session that had it was replaced. True when it exists now.
|
||||
Bool EnsureSurfaceHomed(EGLSurface surface);
|
||||
Bool CreatePbufferOnServer(EGLSurface surface, EGLint width, EGLint height, Bool standIn);
|
||||
void NoteHomed(EGLSurface surface) {
|
||||
const std::lock_guard<std::recursive_mutex> lock(m_eglStateMutex);
|
||||
m_homedSurfaces.insert(surface);
|
||||
@@ -140,6 +146,8 @@ namespace MobileGL::MG_Remote::Client {
|
||||
// nor presented to nor attached on the new server, which never issued it. Guarded by the
|
||||
// base's m_eglStateMutex.
|
||||
std::unordered_set<EGLSurface> m_homedSurfaces;
|
||||
// The surfaceless stand-ins among this object's pbuffers. Guarded by m_eglStateMutex.
|
||||
std::unordered_set<EGLSurface> m_standInSurfaces;
|
||||
std::unordered_map<Uint64, Uint32> m_sessionSharedImages; // id -> references taken
|
||||
// The ids a replaced session held: releasing one is a success with nothing sent.
|
||||
std::unordered_set<Uint64> m_lostSessionSharedImages;
|
||||
|
||||
@@ -1513,12 +1513,16 @@ namespace MobileGL::MG_Remote::Server {
|
||||
static_cast<Uint32>(frame.width),
|
||||
static_cast<Uint32>(frame.height));
|
||||
return MOBILEGL_OK;
|
||||
case SurfaceControlOp::CreatePbufferSurface:
|
||||
case SurfaceControlOp::CreatePbufferSurface: {
|
||||
// A surfaceless stand-in (kPbufferFlagSurfacelessStandIn) is no rendering path: it is
|
||||
// admitted whatever the mode and latches none, so a client that binds surfacelessly
|
||||
// before it creates its server-owned window still gets that window.
|
||||
const Bool standIn = (frame.contextFlags & kPbufferFlagSurfacelessStandIn) != 0;
|
||||
// P12 (D4): a pbuffer in a session that went on-screen is the other mode, refused by
|
||||
// name and not latched - the session and its window surface carry on. P14 S2: the mode
|
||||
// is the session's own - latched by its first surface, on its own loop - and never
|
||||
// another session's.
|
||||
if (!SessionSurfaceModeAdmits(m_surfaceMode, /*serverOwnedWindow=*/false)) {
|
||||
if (!standIn && !SessionSurfaceModeAdmits(m_surfaceMode, /*serverOwnedWindow=*/false)) {
|
||||
frame.ok = false;
|
||||
frame.refusal = static_cast<Uint8>(SurfaceRefusalCode::SurfaceModeMismatch);
|
||||
MGLOG_E("MG_Remote server: SurfaceModeMismatch - CreatePbufferSurface (seq %llu, %dx%d) in a "
|
||||
@@ -1535,11 +1539,18 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// make-current that follows is forwarded and deduped one layer down (ID-54).
|
||||
if (frame.ok) {
|
||||
ForgetCurrentTuple();
|
||||
if (standIn) {
|
||||
MGLOG_I("MG_Remote server: surface=pbuffer %dx%d (surfaceless stand-in; the session's surface "
|
||||
"mode stays %s)",
|
||||
frame.width, frame.height, SessionSurfaceModeName(m_surfaceMode));
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
if (m_surfaceMode == SessionSurfaceMode::None) m_surfaceMode = SessionSurfaceMode::Offscreen;
|
||||
// P12: THE ARM PROOF (ID-124), once per surface: which of the two paths ran.
|
||||
MGLOG_I("MG_Remote server: surface=pbuffer %dx%d", frame.width, frame.height);
|
||||
}
|
||||
return MOBILEGL_OK;
|
||||
}
|
||||
case SurfaceControlOp::MakeCurrent: {
|
||||
// C7 / ID-54: the apply thread binds the native context ONCE per context lifetime
|
||||
// and holds it for life. ApplyMakeCurrent forwards a bind only for a tuple it does
|
||||
@@ -2133,12 +2144,14 @@ namespace MobileGL::MG_Remote::Server {
|
||||
return rc == MOBILEGL_OK && frame.ok;
|
||||
}
|
||||
|
||||
Bool ServerCreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height, SurfaceRefusalCode* refusal) {
|
||||
Bool ServerCreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height, SurfaceRefusalCode* refusal,
|
||||
Uint32 flags) {
|
||||
SurfaceControlFrame frame;
|
||||
frame.kind = SurfaceControlOp::CreatePbufferSurface;
|
||||
frame.surface = TokenFromHandle(surface);
|
||||
frame.width = width;
|
||||
frame.height = height;
|
||||
frame.contextFlags = flags;
|
||||
const MobileGLResult rc = ServerLoopInstance().RunSurfaceControlFrame(frame);
|
||||
if (refusal != nullptr) *refusal = static_cast<SurfaceRefusalCode>(frame.refusal);
|
||||
return rc == MOBILEGL_OK && frame.ok;
|
||||
|
||||
@@ -702,9 +702,10 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Bool ServerCreateEGLWindowSurface(EGLSurface surface, const MG_Backend::WindowHandle& handle);
|
||||
Bool ServerResizeEGLWindowSurface(EGLSurface surface, Uint32 width, Uint32 height);
|
||||
// P12: `refusal`, when given, receives the server's named refusal (SurfaceModeMismatch for a
|
||||
// pbuffer in an on-screen session), None otherwise.
|
||||
// pbuffer in an on-screen session), None otherwise. `flags`: kPbufferFlagSurfacelessStandIn
|
||||
// for the hidden pbuffer of a surfaceless make-current, which takes no surface mode.
|
||||
Bool ServerCreateEGLPbufferSurface(EGLSurface surface, EGLint width, EGLint height,
|
||||
SurfaceRefusalCode* refusal = nullptr);
|
||||
SurfaceRefusalCode* refusal = nullptr, Uint32 flags = 0);
|
||||
// P12 (on-screen server window), D1/D2. CreateWindowSurface on the SERVER's window: one frame,
|
||||
// WindowKind::ServerOwned with nativeToken 0 on the wire, no SetWindowHandle. `width`/`height`
|
||||
// is the size the client asked for (0/0 = the server window's own size); the reply carries the
|
||||
|
||||
@@ -99,6 +99,15 @@ namespace MobileGL::MG_Remote::Server {
|
||||
// substitutes its own window there; no pointer crosses the wire (Rule G/H).
|
||||
inline constexpr Int kServerOwnedWindowBackend = 0x10000;
|
||||
|
||||
// CreatePbufferSurface's flags, in the frame's `contextFlags` word (the wire's SurfaceOp
|
||||
// contextFlags, 0 on every op but CreateContext before this, so an older server reads none).
|
||||
// SURFACELESS STAND-IN: the hidden 1x1 pbuffer a client binds the backend to for a surfaceless
|
||||
// make-current (EGL_KHR_surfaceless_context). It is never presented or read back, so it is no
|
||||
// rendering path: it neither latches the session's surface mode nor is refused by it (D4).
|
||||
// Without it, a client that made a context current with no surface before it created its
|
||||
// server-owned window latched its session offscreen, and the window was refused.
|
||||
inline constexpr Uint32 kPbufferFlagSurfacelessStandIn = 1u;
|
||||
|
||||
// P12. Why the server declined a surface op, carried back in the reply half (`refusal`). The
|
||||
// values ARE the wire's SurfaceRefusal (protocol.fbs); SurfaceOpCodec pins the agreement with
|
||||
// static_asserts, the way it pins SurfaceControlOp against SurfaceOpKind. None of them latches
|
||||
@@ -133,7 +142,8 @@ namespace MobileGL::MG_Remote::Server {
|
||||
Int swapInterval = 0;
|
||||
// P14 S1. CreateContext's two values beyond `context` (which is the client context token
|
||||
// itself): the share group the context was derived into, and the reserved flags word the
|
||||
// schema carries so a later attribute has a slot. 0 on every other op.
|
||||
// schema carries so a later attribute has a slot. CreatePbufferSurface carries its own
|
||||
// flags in the same word (kPbufferFlagSurfacelessStandIn). 0 on every other op.
|
||||
Uint64 shareGroupToken = 0;
|
||||
Uint32 contextFlags = 0;
|
||||
// The reply half: written by the dispatch on the apply thread, read by the poster after
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
// * without the knob a NULL window is the client's own EGL_BAD_NATIVE_WINDOW and the server never
|
||||
// hears of it;
|
||||
// * D4: a session whose first surface was a pbuffer (offscreen) is refused a ServerOwned surface as
|
||||
// SurfaceModeMismatch - named by the server and by the client - and carries on.
|
||||
// SurfaceModeMismatch - named by the server and by the client - and carries on;
|
||||
// * a surfaceless make-current (EGL_KHR_surfaceless_context) commits the session to neither mode:
|
||||
// the hidden pbuffer the backend is bound to is not the session's first surface.
|
||||
|
||||
#include "P12ServerRig.h"
|
||||
|
||||
@@ -35,6 +37,7 @@ namespace {
|
||||
ServerOwnedFirst, // the ServerOwned create, then a pbuffer
|
||||
NullWindowWithoutKnob, // the knob unset: a NULL window, then a pbuffer
|
||||
PbufferThenServerOwned, // a pbuffer first (offscreen), then the ServerOwned create
|
||||
SurfacelessThenServerOwned, // a surfaceless make-current first, then the ServerOwned create
|
||||
};
|
||||
|
||||
struct EglPeerReport {
|
||||
@@ -95,7 +98,13 @@ namespace {
|
||||
r.pbufferCreated = pbuffer != EGL_NO_SURFACE ? 1 : 0;
|
||||
if (r.pbufferCreated) r.madeCurrent = EGL::MakeCurrent(dpy, pbuffer, pbuffer, context) == EGL_TRUE;
|
||||
};
|
||||
if (scenario == Scenario::PbufferThenServerOwned) {
|
||||
if (scenario == Scenario::SurfacelessThenServerOwned) {
|
||||
r.madeCurrent = EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, context) == EGL_TRUE;
|
||||
(void)EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
|
||||
tryWindow();
|
||||
r.madeCurrentAfter =
|
||||
EGL::MakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, context) == EGL_TRUE;
|
||||
} else if (scenario == Scenario::PbufferThenServerOwned) {
|
||||
tryPbuffer();
|
||||
tryWindow();
|
||||
if (r.pbufferCreated) {
|
||||
@@ -243,3 +252,33 @@ TEST(ServerOwnedSurface, AnOffscreenSessionRefusesAServerOwnedSurfaceAsSurfaceMo
|
||||
EXPECT_NE(client.Own().find("SurfaceModeMismatch - eglCreateWindowSurface"), std::string::npos) << client.Own();
|
||||
EXPECT_EQ(r.madeCurrentAfter, r.madeCurrent) << "the session did not keep its pbuffer after the refusal";
|
||||
}
|
||||
|
||||
// A SURFACELESS BIND IS NO SURFACE MODE. KWin and Qt make a context current with no surface when
|
||||
// EGL_KHR_surfaceless_context is advertised, and the backend is then bound to a hidden 1x1 pbuffer.
|
||||
// That pbuffer must not latch the session offscreen: a client with MOBILEGL_IPC_SURFACE=server
|
||||
// creates its window after such a bind, and a refusal there is a black desktop. The window here is
|
||||
// refused for the reason a displayless server has (NoServerDisplay), never SurfaceModeMismatch, and
|
||||
// the surfaceless bind still works after it. Red with the stand-in created as an ordinary pbuffer:
|
||||
// the server answers SurfaceModeMismatch.
|
||||
TEST(ServerOwnedSurface, ASurfacelessBindDoesNotLatchTheSessionOffscreen) {
|
||||
ServerProcess server;
|
||||
ASSERT_TRUE(LaunchSupervisor("surfaceless", &server)) << server.Log();
|
||||
ClientLog client("surfaceless", server);
|
||||
const EglPeerReport r = RunEglPeer(server, Scenario::SurfacelessThenServerOwned, /*surfaceKnob=*/true, client.base);
|
||||
ASSERT_EQ(r.initialized, 1) << r.note;
|
||||
if (r.madeCurrent == 0) GTEST_SKIP() << "the surfaceless bind needs a pbuffer, and this host has no headless EGL";
|
||||
EXPECT_EQ(r.windowCreated, 0) << "a server with no display created a server-owned window surface";
|
||||
EXPECT_EQ(r.windowError, EGL_BAD_NATIVE_WINDOW);
|
||||
std::string reap;
|
||||
ASSERT_TRUE(WaitFor([&] { return !(reap = ReapLine(server, r.serverPid)).empty(); }, 15000)) << server.Log();
|
||||
const std::string serverLog = server.Log();
|
||||
EXPECT_NE(serverLog.find("surface=pbuffer 1x1 (surfaceless stand-in"), std::string::npos)
|
||||
<< "no stand-in arm proof:\n" << serverLog;
|
||||
EXPECT_EQ(serverLog.find("SurfaceModeMismatch"), std::string::npos)
|
||||
<< "the surfaceless bind latched the session offscreen:\n" << serverLog;
|
||||
EXPECT_NE(serverLog.find("Refuse ServerOwned: this server owns no display"), std::string::npos) << serverLog;
|
||||
EXPECT_EQ(serverLog.find("Fatal{"), std::string::npos) << serverLog;
|
||||
EXPECT_NE(reap.find("exit=0 "), std::string::npos) << reap;
|
||||
EXPECT_NE(client.Own().find("Refuse ServerOwned (NoServerDisplay)"), std::string::npos) << client.Own();
|
||||
EXPECT_EQ(r.madeCurrentAfter, 1) << "the surfaceless bind failed after the refused window";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user