mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 10:03:23 +09:00
13 KiB
13 KiB
P12 subset "on-screen server window" — implementation brief (2026-09-23)
Base: feat/disaggregated@9f669e52 (P7 closed). Package tree ~/w7/p12-onscreen, branch p12/onscreen,
build dirs build-split (disaggregated, ITEST tcp port 40813) and build-linux (pull build, for G1), both
already built at base. G1 base: .text 0xa52203, symbols ~/w7/logs/p12/pull-syms-base.txt.
Gate: bash ~/w7/notes/p12/gate.sh ~/w7/p12-onscreen <tag>.
User rulings (binding)
- The Android server APK creates an ANativeWindow (its own SurfaceView) and renders the IPC render stream ON SCREEN on it. Both rendering paths stay available: the existing offscreen (pbuffer) path and the new on-screen path.
- Only one of them is active at a time, decided at server initialization / context creation.
- Topology: in-process server — the display Activity's own app process runs the TCP server on a thread and renders straight into its SurfaceView; sessions run sequentially in that process. (Not fork + AHB.)
- Client side: the client opts in via a new
WindowKind::ServerOwned(control protocol revision 3, client env knob). In this mode the client can be fully headless: it asks the server to create the real (window) context/surface and gets the surface geometry etc. from the server — as if the client were rendering directly on the server's GPU and screen.
Design
D1 Client knob and headless window surfaces
- New IPC knob
MOBILEGL_IPC_SURFACE=offscreen|server(defaultoffscreen), added toMG_Config::IpcTable(Config.h, "a later phase's knob is added HERE") +ConfigLoader.cpp, printed in the IPC config log line. Meaningful only when the client talks to a remote server (spawn/tcp); ignored (logged) otherwise. - With
server: the client EGL layer'seglCreateWindowSurface/eglCreatePlatformWindowSurfaceaccept ANY native window, including NULL (today NULL isEGL_BAD_NATIVE_WINDOW,EGLImpl.cpp:130-133) — a headless client has no window.BackendObject_Remote::CreateEGLWindowSurfacethen sends oneCreateWindowSurfacewithWindowKind::ServerOwned,nativeToken = 0, width/height = the size the client asked for (the existingWindowHandle.Width/HeightfromEGL_WIDTH/EGL_HEIGHTattribs; 0/0 = "the server's own size"), and does not sendSetWindowHandle. The client's native window value never reaches the wire (Rule G/H). eglCreatePbufferSurfacestays a pbuffer in both modes (offscreen). See D4 for the one-mode-per-session rule.- With
offscreen(default) nothing changes, byte for byte. - Geometry flows back: when a
kEventSurfaceChangedwith non-zero W/H arrives for the session's server-owned surface, the client updates the EGL state's surface size (soeglQuerySurface(EGL_WIDTH/HEIGHT)answers the server's real size;state->ResizeSurfaceas used atEGLImpl.cpp:724) in addition to the default-FB reallocationApplySurfaceChangedToClientalready does (ClientSession.cpp:306-347). Make sure the client has the size beforeeglCreateWindowSurfacereturns (the create path already drains events,BackendObject_Remote.cpp:224).
D2 Wire
- Append
ServerOwned = 7toWindowKind(protocol.fbs:239-250; append-only), regenerate + commit the generated header, bumpMOBILEGL_PROTOCOL_CONTROL_REVISION2 -> 3 (mg_protocol_base.h, with its comment row),protocol_revision_pin.py --write. wireFingerprint moves: APK and host redeploy together. - Codec (
SurfaceOpCodec.{h,cpp}): encoder maps the client's server-owned request toServerOwned; decoder acceptsServerOwnedonly onCreateWindowSurface(onSetWindowHandleit is a named refusal), only withnativeToken == 0(non-zero =ProtocolCorruption, latched like the other peer-corruption rows). Update the static_assert / out-of-range bound. KeepAndroidNativeWindow@P12/MetalLayer@P12refusals. - Do NOT add a
WindowBackendenumerator inMG_Backend/BackendObject.h(pull build, G1). Carry the server-owned request in the MG_Remote frame with a MG_Remote-local marker; on the server it becomesWindowBackend::Android+ the server's ownANativeWindow*, substituted ON THE SERVER at apply time. No pointer ever crosses the wire. - D8 (X11/Win32/None whitelist, CONTRACT-P6 §7.2) is OUT of scope (separate follow-up).
D3 Server display (the on-screen server's window)
- New
MG_Remote/Server/ServerDisplay.{h,cpp}: process-wide holder of the server-owned window, set only by the in-process display server (Android). API sketch:Attach(ANativeWindow*)(acquire),Detach()(blocking, see D6),HasDisplay(), a geometry request hook (native -> Java up-call:SurfaceHolder.setFixedSize(w,h)for w,h > 0,setSizeFromLayout()for 0/0), andAcquireFor(w,h, deadline)that waits (bounded, ~10 s, wakes on detach/shutdown) until a window is attached and reports the requested size (or any size for 0/0). Linux/host and the exec'd supervisor have no display:HasDisplay()is false. ServerLoopCreateWindowSurfacearm, for a ServerOwned frame: no display -> reply failure with a distinct result and a named logRefuse ServerOwned: this server owns no display(NOT a latch — it is a configuration error, not peer corruption); otherwise substitute the window and call the backend'sCreateEGLWindowSurfaceexactly as monolith Android does (Register -> Activate -> SetWindowHandle -> InitWindowSurface). Log an arm proof once per surface:MG_Remote server: surface=window WxH owner=server(andsurface=pbuffer WxHon the pbuffer arm). The apply thread may wait for the window; the existingSurfaceProgressheartbeat must keep the client's reply budget alive while it does.- Reporting size: Magma already publishes swapchain extent in
OnSurfaceChanged(SwapchainObject.cpp:323-339). Espryt publishes format only (DirectGLES.cpp:15939-15950): publish W/H (viaQueryCurrentSurfaceSize) for a window surface in the disaggregated build only —DirectGLES.cppis in the pull build, so guard it (#if MOBILEGL_BUILD_DISAGGREGATEDor equivalent) and keep G1 byte-identical. Consider whether it must be limited to server-owned windows (publishing W/H switches the client to the reallocating arm). - Espryt:
sameHandlededup (BackendObject_DirectGLES.cpp:962-967) keys on surface + window; with the server's pointer it dedups correctly. A new surface token re-creates the context (existing behaviour). - Magma: a new renderer on the SAME ANativeWindow must not be created while the old renderer's VkSurface/
swapchain still holds it (
VK_ERROR_NATIVE_WINDOW_IN_USE_KHR); checkBackendObject_DirectVulkan.cpp:337-352ordering and fix if needed (guard for G1 if that file is in the pull build).
D4 One mode per session ("only one active", decided at context creation)
- A session's surface mode latches at its FIRST surface creation: ServerOwned -> on-screen; pbuffer -> offscreen.
A later surface of the other kind in the same session is refused by name (
SurfaceModeMismatch, reply failure, logged on both sides, no latch). A server with no display serves only offscreen sessions. - Server-level: the offscreen supervisor (exec'd,
MobileGLServerService) and the on-screen display server (in-process,MobileGLDisplayActivity) are mutually exclusive on the device (D8).
D5 In-process server
RunSession(ServerMain.cpp:268-638,[[noreturn]],_exiton every path) becomes returning (an exit code); every fork site becomes_exit(RunSession(...))so the forked shapes are unchanged.- A new exported entry for the in-process server (e.g.
mobilegl_server_serve_inprocess(endpoint, ...)), same env preconditions asmobilegl_server_main(:1035-1049), TCP only,--servesemantics with THREADS instead of fork: pre-auth before hand-off unchanged, one session at a time, an authenticated second Hello while a session is live ->Refuse{Busy}.TcpSupervisor's fork body closes fds per fd-table (:990-995, 1009); the thread hand-off must not. Reuse TcpSupervisor with a pluggable hand-off if tractable. FatalFunnel: addResetSessionLatch(); the in-process server arms the latch for every session and resets it between sessions. UnarmedSessionFailstill aborts (documented: in-process mode loses crash isolation).- Backend pinned for the process lifetime: an explicit extra/env, else the first session's backend becomes the
pin (Espryt and Magma must never share a process,
BenchService.java:12-17); mismatch -> the existing pinned-backend refusal (:371-375). - Everything else per
map-apk-process§4 (config init per session, server role, PipeStats restartable). - Must be testable on the Linux host: run the in-process entry on a thread in a test (pbuffer sessions) and prove two sequential sessions, latch reset (second session serves after the first latched), and Busy.
D6 Window lost
- Java
surfaceDestroyed-> JNIDetach()blocks (bounded ~3 s) until the apply thread has released the backend surface (post onto the apply thread;ReleaseEGLResources-style), then the live on-screen session is latched by name (newFatalFamilies.defrow, e.g.ServerWindowLost) so the client gets a clean device-lost. The display server keeps listening; the next on-screen session waits for a window (D3) or is refused by name.surfaceCreatedre-attaches. A session that never had a window is unaffected.
D7 Android (trace flavor)
- New
MobileGLDisplayActivity(trace flavor),android:process=":mglwin", exported like the service, fullscreen SurfaceView,FLAG_KEEP_SCREEN_ON,configChangeslikeTraceReplayActivity,android:hardwareAccelerated="false"(keeps HWUI off EGL: Espryt teardowneglTerminates the default display,main/AndroidManifest.xml:25-28). Extras:listen,token,env(sameK=V;Kgrammar asMobileGLServerService.applyEnvironment— share the helper), optionalbackend. Applies env withOs.setenv/unsetenv(ROLE=server, DIAL=no, LOG_FORWARD=1, scrub list, TOKEN, LOG_FILE_PATH) BEFORESystem.loadLibrary("MobileGL"). Aspect-fit (letterbox) the SurfaceView when a fixed buffer size is set. - JNI in libMobileGL (Android + disaggregated only, next to
DriverPostJni.cppinCMakeLists.txt): start/stop the in-process server thread, attach/detach the window (ANativeWindow_fromSurface, acquire/ release), the geometry up-call. - Readiness line in logcat (
listening on tcp://...) like the supervisor's.
D8 Only one server active on the device
- The Activity on start stops
MobileGLServerService; the service on start kills:mglwin(ActivityManager.getRunningAppProcesses+Process.killProcess, same uid) and waits for it to go. - Forked session children get
prctl(PR_SET_PDEATHSIG, SIGKILL)(+ the getppid race check) so they die with their supervisor (fixes the documented child leak,notes/p65/code-review-findings.md:9). - The in-process listen retries
EADDRINUSEfor up to ~5 s (the previous server may still be dying).
D9 Tools
tools/trace_replay/tcp_device_server.py --surface window|pbuffer(default pbuffer = today): window starts the Activity (am start -n <pkg>/top.mobilegl.plugin.MobileGLDisplayActivity+ the same extras).- Linux glws
tools/trace_replay/apitrace_glws_egl.cpp:MOBILEGL_TRACE_SURFACE=windowon Linux createseglCreateWindowSurface(dpy, cfg, (EGLNativeWindowType)0, {EGL_WIDTH,w,EGL_HEIGHT,h})(only valid withMOBILEGL_IPC_SURFACE=server; otherwise the create fails by name);resize()destroys + recreates at the new size. Sotrace_replay --window-surface+MOBILEGL_IPC_SURFACE=serverreplays a trace on the phone screen.
Constraints (all must hold)
- G1: pull build
.text0xa52203 and 0 symbols added/removed. Every edit to a file compiled into the pull build (MG_Backend/*,MG_Impl/*,MG_State/*,DirectGLES.cpp, ...) must be guarded so the pull build is byte-identical. Measure with the gate. - fatal_census (79, down-ratchet; every new
Fatal{Wordneeds aFatalFamilies.defrow), link ratchet 171 monotone, spawn_lane_parity 0 errors (device-only or new arms must be named there), protocol revision pin, wire_declines_audit, hygiene (stdio grep in MG_Backend/MG_State, gen_pipe clean, doc citations strict). - fbs append-only; no branching on link kind above
Transport/; handshake refusals areRefuse, never aborts. - Every existing lane stays green (base numbers at
9f669e52: unit 2568, integration-split 322, spawn 239, tcp 243, magma-split 125, magma-spawn 104, magma-tcp 89, magma-full-split 558, integration-gpu 2188 x 2). - Red-once per behaviour (a test that is red without the fix and green with it), noted in the report.
- Commits: single-line
[Scope, Scope] (P12): <what>(e.g.[MG_Remote, MG_Test] (P12): ...), NO trailers of any kind (no Co-Authored-By). Several focused commits are fine.
Working rules
- Edit files through
\\wsl.localhost\Arch\home\swung\w7\p12-onscreen\...with Read/Edit/Write (UNC is writable). - Run commands as
MSYS_NO_PATHCONV=1 wsl -d arch --cd /home/swung -- bash -lc '<cmd>'. The Windows command line mangles heredocs and nested quotes: put anything non-trivial in a script file (Write via UNC, then run it; strip CR withsed -i 's/\r$//'if needed). Long jobs:setsid nohup ... &and poll the log. - git ONLY inside
/home/swung/w7/p12-onscreenviawsl. NEVER touch~/w7/pipeor the Windows checkouts. Nevergit lfs pull. Never push. - The phone (Redmi 2f7cbe2e) is shared: any adb work runs under
flock -w 3600 /home/swung/w7/locks/2f7cbe2e.lock <cmd>.