mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 18:13:23 +09:00
[MG_Remote, MG_Test] (Disaggregated): P5f fc - the EGL control plane crosses as value frames, not function pointers
The twelve Server* forwarders' one-slot mailbox carried a raw function
pointer plus a void* to a stack-local Args struct (P5c audit row G4);
neither means anything across a process boundary. The slot now carries
one SurfaceControlFrame by value: kind is the op, scalars are the
arguments, and ok/eglMajor/eglMinor come back in the same value. The
blocking handshake, the shadow, the doorbell order and the C2 exit
block are unchanged.
- protocol.fbs: append-only - SurfaceOpKind gains SetSwapInterval=8,
ReleaseResources=9, SetWindowHandle=10; WindowKind gains MetalLayer=6;
SurfaceOp gains readSurface and context (the MakeCurrent four-tuple).
protocol_generated.h regenerated with the pinned flatc.
- Server/SurfaceControlFrame.{h,cpp}: the frame (trivially copyable by
static_assert) and the op enum - ten wire kinds plus four inproc-only
kinds (InitCapabilities is answered by CapsSnapshot on the wire,
SwapBuffers/InitWindowSurface have no production caller - f0-egl F8,
kept for the inproc test lane).
- Protocol/SurfaceOpCodec.{h,cpp}: the frame <-> SurfaceOp/SurfaceReply
codec, the explicit WindowBackend <-> WindowKind table, and the wire
entry ServerApplyWireSurfaceOp whose refusals are named:
Fatal{UnmigratedSurface, "AndroidNativeWindow@P12"} and
Fatal{ProtocolCorruption, "SurfaceOp"}.
- ServerLoop: RunOnApplyThread(fn, void*) -> RunSurfaceControlFrame(frame)
plus the dispatch ApplySurfaceControlFrame; the old per-forwarder Args
bodies moved into the dispatch unchanged (C7/ID-54, N-3, R-12 included).
RunProbeOnApplyThreadForTesting is the test seam through the same
channel (the hook lives in a member, never in the slot).
- Tests: RemoteClientTest/ServerLoopTest migrated to the probe seam; new
SurfaceControlFrameTest suite (mapping tables, wire round-trips, the
two named refusals) plus ServerLoopTest's AVoidForwarderCrossesAsOne-
DispatchedFrame (the frame channel's red-once handle) and
ServerLoopEglTest's TheInitializeDisplayReplyArrivesThroughTheFrame.
This commit is contained in:
@@ -0,0 +1,215 @@
|
||||
// MobileGL - MobileGL/MG_Remote/Protocol/SurfaceOpCodec.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
|
||||
|
||||
#include "SurfaceOpCodec.h"
|
||||
|
||||
#include <MG_Backend/BackendObject.h>
|
||||
#include <MG_Remote/Protocol/generated/protocol_generated.h>
|
||||
#include <MG_Remote/Server/ServerLoop.h>
|
||||
#include <MG_Util/Debug/Log.h>
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
namespace MobileGL::MG_Remote {
|
||||
|
||||
namespace {
|
||||
|
||||
using Server::SurfaceControlFrame;
|
||||
using Server::SurfaceControlOp;
|
||||
|
||||
// THE ALIGNMENT IS PINNED, NOT ASSUMED. The frame's first eleven values were chosen to
|
||||
// agree with the wire enum so the tables below read as identity - but a schema edit that
|
||||
// renumbers either side fails HERE, at compile time, instead of silently mistranslating
|
||||
// op identities between two processes that both still announce the same ABI major.
|
||||
static_assert(static_cast<Uint8>(SurfaceControlOp::InitializeDisplay) ==
|
||||
static_cast<Uint8>(::MobileGL::Wire::SurfaceOpKind::InitializeDisplay));
|
||||
static_assert(static_cast<Uint8>(SurfaceControlOp::CreateWindowSurface) ==
|
||||
static_cast<Uint8>(::MobileGL::Wire::SurfaceOpKind::CreateWindowSurface));
|
||||
static_assert(static_cast<Uint8>(SurfaceControlOp::CreatePbufferSurface) ==
|
||||
static_cast<Uint8>(::MobileGL::Wire::SurfaceOpKind::CreatePbufferSurface));
|
||||
static_assert(static_cast<Uint8>(SurfaceControlOp::ResizeWindowSurface) ==
|
||||
static_cast<Uint8>(::MobileGL::Wire::SurfaceOpKind::ResizeWindowSurface));
|
||||
static_assert(static_cast<Uint8>(SurfaceControlOp::ReleaseSurface) ==
|
||||
static_cast<Uint8>(::MobileGL::Wire::SurfaceOpKind::ReleaseSurface));
|
||||
static_assert(static_cast<Uint8>(SurfaceControlOp::MakeCurrent) ==
|
||||
static_cast<Uint8>(::MobileGL::Wire::SurfaceOpKind::MakeCurrent));
|
||||
static_assert(static_cast<Uint8>(SurfaceControlOp::ReleaseCurrent) ==
|
||||
static_cast<Uint8>(::MobileGL::Wire::SurfaceOpKind::ReleaseCurrent));
|
||||
// fc's three schema additions, append-only (protocol.fbs's own rule).
|
||||
static_assert(static_cast<Uint8>(SurfaceControlOp::SetSwapInterval) ==
|
||||
static_cast<Uint8>(::MobileGL::Wire::SurfaceOpKind::SetSwapInterval));
|
||||
static_assert(static_cast<Uint8>(SurfaceControlOp::ReleaseResources) ==
|
||||
static_cast<Uint8>(::MobileGL::Wire::SurfaceOpKind::ReleaseResources));
|
||||
static_assert(static_cast<Uint8>(SurfaceControlOp::SetWindowHandle) ==
|
||||
static_cast<Uint8>(::MobileGL::Wire::SurfaceOpKind::SetWindowHandle));
|
||||
static_assert(static_cast<Uint8>(::MobileGL::Wire::SurfaceOpKind::SetSwapInterval) == 8 &&
|
||||
static_cast<Uint8>(::MobileGL::Wire::SurfaceOpKind::ReleaseResources) == 9 &&
|
||||
static_cast<Uint8>(::MobileGL::Wire::SurfaceOpKind::SetWindowHandle) == 10);
|
||||
static_assert(static_cast<Uint8>(::MobileGL::Wire::WindowKind::MetalLayer) == 6);
|
||||
|
||||
bool OpNeedsAWindowBackend(SurfaceControlOp op) {
|
||||
return op == SurfaceControlOp::CreateWindowSurface || op == SurfaceControlOp::SetWindowHandle;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* SurfaceWireErrorName(SurfaceWireError error) {
|
||||
switch (error) {
|
||||
case SurfaceWireError::None: return "None";
|
||||
case SurfaceWireError::UnknownOpKind: return "UnknownOpKind";
|
||||
case SurfaceWireError::InprocOnlyOpOnTheWire: return "InprocOnlyOpOnTheWire";
|
||||
case SurfaceWireError::WindowKindNamesNoBackend: return "WindowKindNamesNoBackend";
|
||||
case SurfaceWireError::UnknownWindowKind: return "UnknownWindowKind";
|
||||
case SurfaceWireError::AndroidNativeWindowArrived: return "AndroidNativeWindowArrived";
|
||||
}
|
||||
return "<unknown SurfaceWireError>";
|
||||
}
|
||||
|
||||
bool WireKindForSurfaceControlOp(SurfaceControlOp op, ::MobileGL::Wire::SurfaceOpKind* out) {
|
||||
if (!Server::SurfaceControlOpHasWireKind(op)) return false;
|
||||
// The static_asserts above make this cast a table lookup spelled as a conversion: every
|
||||
// kind that reaches this line has a pinned, equal wire value.
|
||||
*out = static_cast<::MobileGL::Wire::SurfaceOpKind>(static_cast<Uint8>(op));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SurfaceControlOpForWireKind(::MobileGL::Wire::SurfaceOpKind kind, SurfaceControlOp* out) {
|
||||
if (::flatbuffers::IsOutRange(kind, ::MobileGL::Wire::SurfaceOpKind::InitializeDisplay,
|
||||
::MobileGL::Wire::SurfaceOpKind::SetWindowHandle)) {
|
||||
return false;
|
||||
}
|
||||
const SurfaceControlOp op = static_cast<SurfaceControlOp>(static_cast<Uint8>(kind));
|
||||
if (!Server::SurfaceControlOpHasWireKind(op)) return false;
|
||||
*out = op;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WireWindowKindForWindowBackend(MG_Backend::WindowBackend backend, ::MobileGL::Wire::WindowKind* out) {
|
||||
switch (backend) {
|
||||
case MG_Backend::WindowBackend::Android: *out = ::MobileGL::Wire::WindowKind::AndroidNativeWindow; return true;
|
||||
case MG_Backend::WindowBackend::X11: *out = ::MobileGL::Wire::WindowKind::X11; return true;
|
||||
case MG_Backend::WindowBackend::MetalLayer: *out = ::MobileGL::Wire::WindowKind::MetalLayer; return true;
|
||||
case MG_Backend::WindowBackend::Win32: *out = ::MobileGL::Wire::WindowKind::Win32Hwnd; return true;
|
||||
case MG_Backend::WindowBackend::Unknown: *out = ::MobileGL::Wire::WindowKind::None; return true;
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowBackendForWireWindowKind(::MobileGL::Wire::WindowKind kind, MG_Backend::WindowBackend* out) {
|
||||
switch (kind) {
|
||||
case ::MobileGL::Wire::WindowKind::AndroidNativeWindow: *out = MG_Backend::WindowBackend::Android; return true;
|
||||
case ::MobileGL::Wire::WindowKind::X11: *out = MG_Backend::WindowBackend::X11; return true;
|
||||
case ::MobileGL::Wire::WindowKind::MetalLayer: *out = MG_Backend::WindowBackend::MetalLayer; return true;
|
||||
case ::MobileGL::Wire::WindowKind::Win32Hwnd: *out = MG_Backend::WindowBackend::Win32; return true;
|
||||
case ::MobileGL::Wire::WindowKind::None: *out = MG_Backend::WindowBackend::Unknown; return true;
|
||||
// Surfaceless and Pbuffer are surface SHAPES, not window backends: no answer.
|
||||
default: return false;
|
||||
}
|
||||
}
|
||||
|
||||
SurfaceWireError EncodeSurfaceOpFrame(const SurfaceControlFrame& frame,
|
||||
flatbuffers::FlatBufferBuilder* builder) {
|
||||
::MobileGL::Wire::SurfaceOpKind kind;
|
||||
if (!WireKindForSurfaceControlOp(frame.kind, &kind)) {
|
||||
return frame.kind == SurfaceControlOp::None ? SurfaceWireError::UnknownOpKind
|
||||
: SurfaceWireError::InprocOnlyOpOnTheWire;
|
||||
}
|
||||
::MobileGL::Wire::WindowKind windowKind = ::MobileGL::Wire::WindowKind::None;
|
||||
if (frame.kind == SurfaceControlOp::CreatePbufferSurface) {
|
||||
// The op implies the shape; the frame carries no windowBackend for it.
|
||||
windowKind = ::MobileGL::Wire::WindowKind::Pbuffer;
|
||||
} else if (OpNeedsAWindowBackend(frame.kind)) {
|
||||
if (!WireWindowKindForWindowBackend(
|
||||
static_cast<MG_Backend::WindowBackend>(frame.windowBackend), &windowKind)) {
|
||||
return SurfaceWireError::UnknownWindowKind;
|
||||
}
|
||||
}
|
||||
const auto op = ::MobileGL::Wire::CreateSurfaceOp(
|
||||
*builder, frame.seq, kind, frame.display, frame.surface, windowKind, frame.nativeToken,
|
||||
frame.width, frame.height, frame.swapInterval, frame.readSurface, frame.context);
|
||||
const auto envelope = ::MobileGL::Wire::CreateCtrlEnvelope(*builder, ::MobileGL::Wire::CtrlMsg::SurfaceOp, op.Union());
|
||||
::MobileGL::Wire::FinishCtrlEnvelopeBuffer(*builder, envelope);
|
||||
return SurfaceWireError::None;
|
||||
}
|
||||
|
||||
SurfaceWireError DecodeWireSurfaceOp(const ::MobileGL::Wire::SurfaceOp& op, SurfaceControlFrame* frame) {
|
||||
SurfaceControlOp kind;
|
||||
if (!SurfaceControlOpForWireKind(op.kind(), &kind)) {
|
||||
return SurfaceWireError::UnknownOpKind;
|
||||
}
|
||||
SurfaceControlFrame decoded;
|
||||
decoded.kind = kind;
|
||||
decoded.seq = op.seq();
|
||||
decoded.display = op.display();
|
||||
decoded.surface = op.surface();
|
||||
decoded.readSurface = op.readSurface();
|
||||
decoded.context = op.context();
|
||||
decoded.width = op.width();
|
||||
decoded.height = op.height();
|
||||
decoded.swapInterval = op.swapInterval();
|
||||
if (OpNeedsAWindowBackend(kind)) {
|
||||
if (op.windowKind() == ::MobileGL::Wire::WindowKind::AndroidNativeWindow) {
|
||||
// The one refusal that is not corruption: the window kind is LEGAL and the token
|
||||
// is an ANativeWindow*, which names memory in the CLIENT's process. Real window
|
||||
// arrival is P12; until then this is refused by name at ServerApplyWireSurfaceOp.
|
||||
return SurfaceWireError::AndroidNativeWindowArrived;
|
||||
}
|
||||
MG_Backend::WindowBackend backend;
|
||||
if (!WindowBackendForWireWindowKind(op.windowKind(), &backend)) {
|
||||
return ::flatbuffers::IsOutRange(op.windowKind(), ::MobileGL::Wire::WindowKind::None,
|
||||
::MobileGL::Wire::WindowKind::MetalLayer)
|
||||
? SurfaceWireError::UnknownWindowKind
|
||||
: SurfaceWireError::WindowKindNamesNoBackend;
|
||||
}
|
||||
decoded.windowBackend = static_cast<Int>(backend);
|
||||
decoded.nativeToken = op.nativeToken();
|
||||
}
|
||||
*frame = decoded;
|
||||
return SurfaceWireError::None;
|
||||
}
|
||||
|
||||
void EncodeSurfaceReplyFrame(const SurfaceControlFrame& frame,
|
||||
flatbuffers::FlatBufferBuilder* builder) {
|
||||
const auto reply = ::MobileGL::Wire::CreateSurfaceReply(*builder, frame.seq, frame.ok, frame.eglMajor,
|
||||
frame.eglMinor, /*defaultFb=*/0);
|
||||
const auto envelope =
|
||||
::MobileGL::Wire::CreateCtrlEnvelope(*builder, ::MobileGL::Wire::CtrlMsg::SurfaceReply, reply.Union());
|
||||
::MobileGL::Wire::FinishCtrlEnvelopeBuffer(*builder, envelope);
|
||||
}
|
||||
|
||||
void DecodeWireSurfaceReply(const ::MobileGL::Wire::SurfaceReply& reply, SurfaceControlFrame* frame) {
|
||||
frame->seq = reply.seq();
|
||||
frame->ok = reply.ok();
|
||||
frame->eglMajor = reply.eglMajor();
|
||||
frame->eglMinor = reply.eglMinor();
|
||||
}
|
||||
|
||||
MobileGLResult ServerApplyWireSurfaceOp(const ::MobileGL::Wire::SurfaceOp& op, SurfaceControlFrame* replyOut) {
|
||||
SurfaceControlFrame frame;
|
||||
const SurfaceWireError error = DecodeWireSurfaceOp(op, &frame);
|
||||
if (error != SurfaceWireError::None) {
|
||||
if (error == SurfaceWireError::AndroidNativeWindowArrived) {
|
||||
MGLOG_F("MGPipe: Fatal{UnmigratedSurface, \"AndroidNativeWindow@P12\"} - a wire "
|
||||
"SurfaceOp (%s) named an ANativeWindow*, which is a pointer into the "
|
||||
"CLIENT's process and means nothing here. Real window arrival is P12; "
|
||||
"until then the spawn surface path is pbuffer/surfaceless only",
|
||||
::MobileGL::Wire::EnumNameSurfaceOpKind(op.kind()));
|
||||
} else {
|
||||
MGLOG_F("MGPipe: Fatal{ProtocolCorruption, \"SurfaceOp\"} - a wire surface op "
|
||||
"failed validation: %s (wire kind %u, window kind %u)",
|
||||
SurfaceWireErrorName(error), static_cast<unsigned>(op.kind()),
|
||||
static_cast<unsigned>(op.windowKind()));
|
||||
}
|
||||
std::abort();
|
||||
}
|
||||
const MobileGLResult rc = Server::ServerLoopInstance().RunSurfaceControlFrame(frame);
|
||||
if (replyOut != nullptr) *replyOut = frame;
|
||||
return rc;
|
||||
}
|
||||
|
||||
} // namespace MobileGL::MG_Remote
|
||||
Reference in New Issue
Block a user