mirror of
https://github.com/MobileGL-Dev/MobileGL
synced 2026-11-07 01:53:24 +09:00
16 KiB
16 KiB
Server-side surface path and both backends' window surfaces (commit 9f669e52)
Everything below was read in code unless it is marked [INFER]. Nothing was edited or built.
1. How the control/surface ops travel today [VERIFIED]
Wire schema
SurfaceOpKindis 0..11 (protocol.fbs:218-237).WindowKindisNone=0, AndroidNativeWindow=1, X11=2, Win32Hwnd=3, Surfaceless=4, Pbuffer=5, MetalLayer=6(:239-250). Both are append-only.SurfaceOpcarriesseq, kind, display, surface, windowKind, nativeToken, width, height, swapInterval, readSurface, context(:252-268). The comment says "Android transfers the window out of band" (:258).SurfaceReply.defaultFbstill exists in the schema (:275), but the encoder writes 0 (SurfaceOpCodec.cpp:192-193) and CONTRACT-P6.md:75 calls the slot burned.SurfaceProgressis at:351-354.
Client emission
- Every
Server*forwarder builds aSurfaceControlFrame(ServerLoop.cpp:1289-1404) and callsRunSurfaceControlFrame. - Under spawn or tcp,
m_remoteSinkroutes the frame toClientSession::RunRemoteSurfaceControlFrame(ServerLoop.cpp:793-795,ClientSession.cpp:1205-1249). An encode error there comes back asMOBILEGL_ERR_UNSUPPORTED(:1217-1226). BackendObject_Remote::CreateEGLWindowSurfacealways sendsServerSetWindowHandle(handle)first and thenServerCreateEGLWindowSurface(BackendObject_Remote.cpp:215-216). There is no split check.- SwapBuffers is never forwarded (
:279-288). Present travels as a class-B record. - The frame is values only, and a compile-time binding enforces it (
SurfaceControlFrame.h:75-100,:113-114). The four inproc-only kinds are 12..14 plusInitCapabilities=11on the wire (:46-73,SurfaceControlFrame.cpp:34-54).
Server intake
- Path:
ServerMain.cpp:594-612→ServerApplyWireSurfaceOp(SurfaceOpCodec.cpp:207-246) →DecodeWireSurfaceOp(:150-188) →ServerLoop::RunSurfaceControlFrame(:785-807), which posts to the apply thread or runs inline if already on it (:803) →ApplySurfaceControlFrame(:1097-1283). OpNeedsAWindowBackendis true only for CreateWindowSurface and SetWindowHandle (SurfaceOpCodec.cpp:60-62).- For a pbuffer, the encoder fills in
WindowKind::Pbufferitself (:133-135).
Dispatch in ApplySurfaceControlFrame
- A null backend answers
NOT_INITIALIZEDfor every op (:1110-1111). - CreateWindowSurface:
UnpackWindowHandle(:1050-1058) turns the token back into avoid*, then callsbackend->CreateEGLWindowSurfaceandForgetCurrentTuple(:1126-1138). - ResizeWindowSurface (
:1139-1143) and CreatePbufferSurface (:1144-1151) call the matching backend method. - MakeCurrent:
ApplyMakeCurrent, then a CapsSnapshot republish on a native bind (:1152-1188). - ReleaseCurrent: recorded, not forwarded (
:1189-1200). - SetSwapInterval:
:1201-1204. - ReleaseSurface:
:1205-1216. - ReleaseResources:
ReleaseEGLResourcesplusSetContextLive(false)(:1217-1231). - SetWindowHandle:
backend->SetWindowHandle(:1232-1241). - InitCapabilities:
:1242-1254. - Present:
ServerVerbSink::OnPresent→table->Present()(PipeApplier.cpp:313-331), then the present credit is returned (:357-359).
Refusals, by name
Fatal{UnmigratedSurface,"AndroidNativeWindow@P12"}for AndroidNativeWindow on a window op (SurfaceOpCodec.cpp:166-171and:217-223).Fatal{UnmigratedSurface,"MetalLayer@P12"}(:172-175,:224-228).WindowKindNamesNoBackendfor Surfaceless/Pbuffer on a window op, andUnknownWindowKind; both becomeFatal{ProtocolCorruption,"SurfaceOp"}(:176-182,:229-235).SurfaceOp.windowBackendfor an out-of-range tag (ServerLoop.cpp:1039-1048).SurfaceOp.kindfor an unknown kind (:1279-1282).ApplyThreadNotRunning(:815-831).- In an armed session child all of these latch the session instead of aborting (
SurfaceOpCodec.cpp:211,:215-216). - The X11 refusal is not in the code. X11, Win32Hwnd and None are accepted, and their token is copied through (
:183-184). CONTRACT-P6.md:438-446 specifiesX11@P12andWin32Hwnd@P12; they never landed. - The client-side mirror of the Android refusal (CONTRACT-P6.md:451-456) is also missing: there are no
@P12hits underMG_Remote/Client.
2. Backend window surfaces [VERIFIED]
Base class, BackendObject
WindowBackend {Android, X11, MetalLayer, Win32, Count, Unknown=-1}(BackendObject.h:569-577);WindowHandle(:579-584);SurfaceKind {None, Window, Pbuffer}(:616-620).CreateEGLWindowSurface=RegisterEGLWindowSurface+ActivateEGLSurface(BackendObject.cpp:191-194).- Register refuses Unknown/null (
:227-230) and storessurfaceState.Window = handle(:232-238). - Activate calls
SetWindowHandle(surfaceState->Window)(:283), thenInitWindowSurface()(:284). For a pbuffer it callsInitPbufferSurface(w,h)(:288-292). - MakeCurrent activates lazily (
:318-323,:332). - Stores to
m_windowHandle::475(SetWindowHandle),:365(ResetEGLRuntimeState), and:206-207(Resize, width/height only; nothing reads those). SwapEGLBuffers→Present()(:396).
DirectGLES (Espryt)
InitWindowSurfacecastsm_windowHandle.Handle→DirectGLES::InitWindowSurface(BackendObject_DirectGLES.cpp:899-907).CreateEGLWindowSurfacechecks the window backend (:955-960). The sameHandle dedup key (:962-967) requires the same surface, kind Window, and the same Backend and Handle. Otherwise it runsDestroyEGLContext+ResetEGLRuntimeState(:969-972).- Process globals
g_Display/g_Context/g_Surface/g_Config(DirectGLES.cpp:15814-15817). InitDisplayAndContextdestroys the old context first (:16159), theneglGetDisplay(EGL_DEFAULT_DISPLAY)(:16177). The config has a single surface bit (:16085).InitWindowSurface→eglCreateWindowSurface(:16230) → MakeCurrent → publish (:16225-16241). The pbuffer twin is:16243-16258.Present→eglSwapBuffers(g_Display, g_Surface), return value ignored (:16842).DestroyEGLContextdestroys the surface and context and callseglTerminate(:16907-16919).- Resize is not overridden (
BackendObject_DirectGLES.h:39-44), so it only updates bookkeeping. - Width/Height gap:
PublishDefaultFramebufferDepthStencilFormatpostsMGPSurfaceInfowith the format only, W/H = 0 (DirectGLES.cpp:15850-15852,:15943-15950).QueryCurrentSurfaceSizeexists (:15819-15837) but is not used for publishing.
DirectVulkan (Magma)
CreateEGLWindowSurfaceonly registers (BackendObject_DirectVulkan.cpp:418-431). The real creation happens at the client's MakeCurrent:Activate→InitWindowSurface→VulkanRenderer(nativeWindow)(:337-352).InitPbufferSurfaceuses a config withSurfaceWidth/Heightand a null window (:354-365).- Resize →
RequestSwapchainResize(:433-446,VulkanRenderer.cpp:16533-16542). - Window:
vkCreateAndroidSurfaceKHR(VulkanRenderer.cpp:16196-16202). - Offscreen on Android: headless surface (
:16112-16121), otherwise an AImageReader ANativeWindow (:16129-16158). - Swapchain extent =
currentExtent, or the clamped desired extent when currentExtent is UINT32_MAX (SwapchainObject.cpp:194-202). Quarter-turn swap (:204-207).preTransform = currentTransform(:234). OnSurfaceChangedcarries Width/Height plus format (:323-339).- On present: OUT_OF_DATE, or an extent/transform change (
VulkanRenderer.cpp:16501-16531), →RecreateSwapchain(:14381-14418) → re-publish. Zero area setsm_presentSuspended(:14386).VK_VERIFYaborts on any other present or acquire result (:14394,:14456).
How the size and format reach the client
ServerOnSurfaceChanged→kEventSurfaceChanged(ServerSession.cpp:483-499, installed at:827-828).ApplySurfaceChangedToClient(ClientSession.cpp:306-347): if W/H ≠ 0 it reallocates all three attachments (:316-338); otherwise it changes the format only (:339-346).- The client drains after create, resize and make-current (
BackendObject_Remote.cpp:224,:233,:272).
3. What the device split path renders into today [VERIFIED]
- The trace app forces a pbuffer for spawn (
trace_replay_jni.cpp:173-188). apitrace creates a pbuffer sizedResolveWidth/Height(apitrace_glws_android.cpp:198-219). EGLImpl.cpp:458-469passesEGL_WIDTH/EGL_HEIGHT(default 1) → frame width/height → serverInitPbufferSurface(w,h).- Espryt:
eglCreatePbufferSurface. - Magma: a headless surface or an AImageReader swapchain at w×h.
- Espryt:
- Present on Espryt is
eglSwapBufferson a pbuffer (a no-op). On Magma it isvkQueuePresentto a surface nobody sees. - The service exec's
libMobileGLServer.so --serve(MobileGLServerService.java:60-61). The supervisor forks one child per session (ServerMain.cpp:985,:1123). The child runsRunSession(:268,[[noreturn]]), setsTransport=Spawn(:356-358) and callsInitServerRoleForSpawn(:377). - This whole pbuffer chain is what must stay.
4. What WindowKind::ServerOwned needs on the server
- Injection point [VERIFIED as the natural seam]. Substitute the server's window token in
ServerApplyWireSurfaceOp, after decode and beforeRunSurfaceControlFrame(SurfaceOpCodec.cpp:243). Downstream nothing changes:UnpackWindowHandle→ Register (:232) → Activate → SetWindowHandle (:283), which is exactly the inproc path today. - sameHandle (
BackendObject_DirectGLES.cpp:962-964) then compares the server's pointer with the server's pointer and dedups correctly. The concern a6 §6(d) raised disappears. - Present: unchanged. The Present record →
DirectGLES::PresenteglSwapBufferson the window surface, or MagmavkQueuePresentKHRon the real swapchain. - Size and rotation
- Magma already reports window resize and rotation (
SwapchainObject.cpp:323-339); rotation is compensated inside the server via preTransform. - Espryt reports nothing (§2 gap).
- Resize requests from the client do not move the extent on either backend. Espryt only updates bookkeeping; Magma uses currentExtent. A client-requested size therefore has to be applied to the window itself:
ANativeWindow_setBuffersGeometryor JavaSurfaceHolder.setFixedSize, which is the lever the code comment names (VulkanRenderer.cpp:14402-14405).
- Magma already reports window resize and rotation (
- Window absent: an absent window must be checked both when CreateWindowSurface is decoded and when the surface is activated, because on Magma activation is deferred to MakeCurrent (
BackendObject.cpp:319,:332). - Existing precedent: the codebase refuses by name and never falls back silently (
ServerLoop.cpp:180-186,:824-831).
OPEN QUESTIONS / RISKS
- Process topology is the main blocker [INFER]. An ANativeWindow from a SurfaceView is a Binder proxy living in the APK's JVM process. The server is an exec'd binary that forks one child per session, and neither exec nor fork carries a usable window (Binder does not survive fork). The options:
- (a) Run the session in-process via JNI, in a dedicated
android:processthat is restarted per session.RunSessionexits the process when it ends, andServerLoopInstanceis a leaked singleton (ServerLoop.cpp:980-984). - (b) Pass a Parcel to the exec'd child. This needs API 34 and a binder route to a native process; impractical.
- (c) Keep the server offscreen and send
AHardwareBuffers over the existing AF_UNIX socket (API 26) to an APK-side presenter.
- (a) Run the session in-process via JNI, in a dedicated
- Selection knob. Should the mode be client-driven (the client sends ServerOwned) or a server environment policy? Should a client pbuffer be allowed to go on-screen?
- Espryt Width/Height publishing. Publishing W/H through the transport arm switches the client to the reallocating arm (
ClientSession.cpp:316-338), which is a divergence from monolith. It should be gated to ServerOwned only. - Initial viewport and client default-FB extent. Does the frontend's initial viewport or the default-FB extent depend on the 512×512 placeholder? That is another slice.
- Losing the window mid-session.
- Espryt: destroying
g_Surfacedestroys the context as well (DirectGLES.cpp:16907-16919). Parking on a pbuffer would needg_*de-globalisation and a config with both WINDOW and PBUFFER bits (:16085). - Magma: it aborts on
SURFACE_LOST(:14394), andvkCreateAndroidSurfaceKHRfailure isVK_VERIFYrather than afalsereturn. - Espryt ignores the
eglSwapBufferserror (:16842).
- Espryt: destroying
surfaceDestroyedthreading. Teardown must be serialized onto the apply thread before the Java callback returns.- Unrelated gap. The X11/Win32/None hole in CONTRACT-P6 §7.2 is still open.
MINIMAL CHANGE LIST
protocol.fbs:249: appendServerOwned = 7and regenerate.SurfaceOpCodec.cpp:- Add a static_assert for the new value; widen the
IsOutRangebound (:178-179). - In the decoder, add a ServerOwned arm before the Android refusal. It requires
nativeToken==0(otherwiseFatal{ProtocolCorruption,"SurfaceOp.nativeToken"}) and setswindowBackend=Android. - In
ServerApplyWireSurfaceOp, fill the token from the provider. Add a newSurfaceWireError::ServerWindowAbsentthat answersok=false, is logged by name, and does not latch; the client then getsEGL_BAD_NATIVE_WINDOW(EGLImpl.cpp:153-156). - Encoder (client side): under spawn/tcp, map
WindowBackend::Android→ ServerOwned with token 0, and send the client's desired w/h.
- Add a static_assert for the new value; widen the
- New
Server/ServerWindowProvider.{h,cpp}:- A process-wide holder,
SetServerOwnedWindow(ANativeWindow*)with acquire/release, a generation counter, andApplyGeometry(w,h). - It is set by the APK's JNI before a session starts; no window means the ServerOwned path is refused.
- This is the "both paths" switch: pbuffer ops are untouched.
- A process-wide holder,
SurfaceControlFrame.h/.cpp,ServerLoop.cpp:- Append an inproc-only
ServerWindowLostkind (15), posted by the provider onsurfaceDestroyed. - Its dispatch releases the surface and latches through a new
FatalFamilies.defrow (e.g.ServerWindowLost). - Recheck the provider at activation (
BackendObject.cpp:283-284) or inUnpackWindowHandle(ServerLoop.cpp:1050-1058).
- Append an inproc-only
DirectGLES.cpp::15947-15950: fill W/H fromQueryCurrentSurfaceSizewhen the surface is a server-owned window.:16842: check theeglSwapBuffersresult, re-query the size after the swap, and callOnSurfaceChangedwhen it changed.g_*de-globalisation is not needed while there is one backend per session process.
VulkanRenderer.cpp:14394,:14456,:16202: turnSURFACE_LOSTand a failed surface creation into named latches instead of aborts. The on-screen path itself needs nothing new, becauseInitWindowSurfacealready takes a real window.BackendObject_DirectGLES.cpp:962-967: no change once the token is the server's.ServerMain.cpp,MobileGLServerService.java, JNI: decide the topology in risk 1. Option (a) needs an in-process entry point without fork; the service is gated by environment or intent extras.- Tests:
SurfaceControlFrameTest.cpp:125-163,:236-249,:323,:376(enum bounds).PeerLatchTest.cpp:1250-1257: add ServerOwned rows (absent, nonzero token).ServerLoopTest: a lost-window control.
- Client (another slice):
BackendObject_Remote.cpp:215currently sends the client pointer unconditionally. With the encoder mapping above it becomes harmless.