Files
MobileGL/docs/Disaggregated/notes/p12/map-server-surface.md
T

16 KiB
Raw Blame History

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

  • SurfaceOpKind is 0..11 (protocol.fbs:218-237). WindowKind is None=0, AndroidNativeWindow=1, X11=2, Win32Hwnd=3, Surfaceless=4, Pbuffer=5, MetalLayer=6 (:239-250). Both are append-only.
  • SurfaceOp carries seq, kind, display, surface, windowKind, nativeToken, width, height, swapInterval, readSurface, context (:252-268). The comment says "Android transfers the window out of band" (:258).
  • SurfaceReply.defaultFb still exists in the schema (:275), but the encoder writes 0 (SurfaceOpCodec.cpp:192-193) and CONTRACT-P6.md:75 calls the slot burned.
  • SurfaceProgress is at :351-354.

Client emission

  • Every Server* forwarder builds a SurfaceControlFrame (ServerLoop.cpp:1289-1404) and calls RunSurfaceControlFrame.
  • Under spawn or tcp, m_remoteSink routes the frame to ClientSession::RunRemoteSurfaceControlFrame (ServerLoop.cpp:793-795, ClientSession.cpp:1205-1249). An encode error there comes back as MOBILEGL_ERR_UNSUPPORTED (:1217-1226).
  • BackendObject_Remote::CreateEGLWindowSurface always sends ServerSetWindowHandle(handle) first and then ServerCreateEGLWindowSurface (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 plus InitCapabilities=11 on 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).
  • OpNeedsAWindowBackend is true only for CreateWindowSurface and SetWindowHandle (SurfaceOpCodec.cpp:60-62).
  • For a pbuffer, the encoder fills in WindowKind::Pbuffer itself (:133-135).

Dispatch in ApplySurfaceControlFrame

  • A null backend answers NOT_INITIALIZED for every op (:1110-1111).
  • CreateWindowSurface: UnpackWindowHandle (:1050-1058) turns the token back into a void*, then calls backend->CreateEGLWindowSurface and ForgetCurrentTuple (: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: ReleaseEGLResources plus SetContextLive(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-171 and :217-223).
  • Fatal{UnmigratedSurface,"MetalLayer@P12"} (:172-175, :224-228).
  • WindowKindNamesNoBackend for Surfaceless/Pbuffer on a window op, and UnknownWindowKind; both become Fatal{ProtocolCorruption,"SurfaceOp"} (:176-182, :229-235).
  • SurfaceOp.windowBackend for an out-of-range tag (ServerLoop.cpp:1039-1048).
  • SurfaceOp.kind for 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 specifies X11@P12 and Win32Hwnd@P12; they never landed.
  • The client-side mirror of the Android refusal (CONTRACT-P6.md:451-456) is also missing: there are no @P12 hits under MG_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 stores surfaceState.Window = handle (:232-238).
  • Activate calls SetWindowHandle(surfaceState->Window) (:283), then InitWindowSurface() (:284). For a pbuffer it calls InitPbufferSurface(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)

  • InitWindowSurface casts m_windowHandle.Handle → DirectGLES::InitWindowSurface (BackendObject_DirectGLES.cpp:899-907).
  • CreateEGLWindowSurface checks 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 runs DestroyEGLContext + ResetEGLRuntimeState (:969-972).
  • Process globals g_Display/g_Context/g_Surface/g_Config (DirectGLES.cpp:15814-15817).
  • InitDisplayAndContext destroys the old context first (:16159), then eglGetDisplay(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).
  • DestroyEGLContext destroys the surface and context and calls eglTerminate (:16907-16919).
  • Resize is not overridden (BackendObject_DirectGLES.h:39-44), so it only updates bookkeeping.
  • Width/Height gap: PublishDefaultFramebufferDepthStencilFormat posts MGPSurfaceInfo with the format only, W/H = 0 (DirectGLES.cpp:15850-15852, :15943-15950). QueryCurrentSurfaceSize exists (:15819-15837) but is not used for publishing.

DirectVulkan (Magma)

  • CreateEGLWindowSurface only registers (BackendObject_DirectVulkan.cpp:418-431). The real creation happens at the client's MakeCurrent: Activate → InitWindowSurface → VulkanRenderer(nativeWindow) (:337-352).
  • InitPbufferSurface uses a config with SurfaceWidth/Height and 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).
  • OnSurfaceChanged carries 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 sets m_presentSuspended (:14386). VK_VERIFY aborts 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 sized ResolveWidth/Height (apitrace_glws_android.cpp:198-219).
  • EGLImpl.cpp:458-469 passes EGL_WIDTH/EGL_HEIGHT (default 1) → frame width/height → server InitPbufferSurface(w,h).
    • Espryt: eglCreatePbufferSurface.
    • Magma: a headless surface or an AImageReader swapchain at w×h.
  • Present on Espryt is eglSwapBuffers on a pbuffer (a no-op). On Magma it is vkQueuePresent to 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 runs RunSession (:268, [[noreturn]]), sets Transport=Spawn (:356-358) and calls InitServerRoleForSpawn (: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 before RunSurfaceControlFrame (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::Present eglSwapBuffers on the window surface, or Magma vkQueuePresentKHR on 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_setBuffersGeometry or Java SurfaceHolder.setFixedSize, which is the lever the code comment names (VulkanRenderer.cpp:14402-14405).
  • 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

  1. 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:process that is restarted per session. RunSession exits the process when it ends, and ServerLoopInstance is 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.
  2. 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?
  3. 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.
  4. 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.
  5. Losing the window mid-session.
    • Espryt: destroying g_Surface destroys the context as well (DirectGLES.cpp:16907-16919). Parking on a pbuffer would need g_* de-globalisation and a config with both WINDOW and PBUFFER bits (:16085).
    • Magma: it aborts on SURFACE_LOST (:14394), and vkCreateAndroidSurfaceKHR failure is VK_VERIFY rather than a false return.
    • Espryt ignores the eglSwapBuffers error (:16842).
  6. surfaceDestroyed threading. Teardown must be serialized onto the apply thread before the Java callback returns.
  7. Unrelated gap. The X11/Win32/None hole in CONTRACT-P6 §7.2 is still open.

MINIMAL CHANGE LIST

  • protocol.fbs:249: append ServerOwned = 7 and regenerate. SurfaceOpCodec.cpp:
    • Add a static_assert for the new value; widen the IsOutRange bound (:178-179).
    • In the decoder, add a ServerOwned arm before the Android refusal. It requires nativeToken==0 (otherwise Fatal{ProtocolCorruption,"SurfaceOp.nativeToken"}) and sets windowBackend=Android.
    • In ServerApplyWireSurfaceOp, fill the token from the provider. Add a new SurfaceWireError::ServerWindowAbsent that answers ok=false, is logged by name, and does not latch; the client then gets EGL_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.
  • New Server/ServerWindowProvider.{h,cpp}:
    • A process-wide holder, SetServerOwnedWindow(ANativeWindow*) with acquire/release, a generation counter, and ApplyGeometry(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.
  • SurfaceControlFrame.h/.cpp, ServerLoop.cpp:
    • Append an inproc-only ServerWindowLost kind (15), posted by the provider on surfaceDestroyed.
    • Its dispatch releases the surface and latches through a new FatalFamilies.def row (e.g. ServerWindowLost).
    • Recheck the provider at activation (BackendObject.cpp:283-284) or in UnpackWindowHandle (ServerLoop.cpp:1050-1058).
  • DirectGLES.cpp:
    • :15947-15950: fill W/H from QueryCurrentSurfaceSize when the surface is a server-owned window.
    • :16842: check the eglSwapBuffers result, re-query the size after the swap, and call OnSurfaceChanged when it changed.
    • g_* de-globalisation is not needed while there is one backend per session process.
  • VulkanRenderer.cpp:14394, :14456, :16202: turn SURFACE_LOST and a failed surface creation into named latches instead of aborts. The on-screen path itself needs nothing new, because InitWindowSurface already 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:215 currently sends the client pointer unconditionally. With the encoder mapping above it becomes harmless.