Repository navigation
feat(🤖): render to a native window with MakeWebGPUCanvasContextFromNativeWindow - #588
Conversation
…tiveWindow A canvas context can now present to an ANativeWindow that is not a WebGPU view, e.g. the input Surface of a MediaCodec encoder: an app can render frames straight into a video encoder, with no EGL context and no copy. The window is passed as a BigInt and the context takes its own reference to it. Its registry entry gets an id from allocateContextId and is retired by the context when it is destroyed, since no view does. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GsWPRiqsBPtiH9G7qvnLrr
wcandillon
left a comment
There was a problem hiding this comment.
I like it a lot. This doesn't need to be Android only. The use-case on iOS for it is much more narrower but it could still be handy in the future.
You can add it on the PlatformContext API (it actually might already be there I haven't checked).
I see that you use the SurfaceRegistry. Does it make sense? We could argue here it's your responsibility to handle thee native surface and thread-safety associated with it?
…ept a CAMetalLayer As suggested in review: the registry bridges the lifecycle of the views, which a window handed in by the caller doesn't have. The context now owns a SurfaceInfo of its own (makeUnregisteredSurfaceInfo): no context id, no registry entry to retire. The SurfaceInfo is still tracked for releaseSurfacesForDevice, so destroying the device first doesn't hit the Android Dawn crash. The caller keeps the window alive and drives the context from one thread at a time; the context still holds a reference to the window. On Apple platforms, the window is a CAMetalLayer, retained like MetalView's (released on the main thread). WebGPURuntime::makeSurface already makes surfaces for both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GsWPRiqsBPtiH9G7qvnLrr
The native window context now owns its surface outside of the SurfaceRegistry, as reworked in wcandillon/react-native-webgpu#588 after review (and accepts a CAMetalLayer on Apple platforms). No change on this side: the JS API is the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GsWPRiqsBPtiH9G7qvnLrr
|
Thanks, done: not Android-only anymore (a |
On Android, a window keeps the buffers it handed to the swapchain until it is disconnected, which the Vulkan loader only does when the VkSurfaceKHR is destroyed: unconfigure() destroyed the swapchain but not the surface, so a MediaCodec input surface kept its buffers until the context was garbage collected (about 150 MB per 1080x1920 export in our tests). A context made from a native window now releases its surface and its reference to the window on unconfigure(); it no longer presents afterwards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GsWPRiqsBPtiH9G7qvnLrr
…xport The export's canvas context now releases its surface on unconfigure() (React Native WebGPU patch, as in wcandillon/react-native-webgpu#588): on Android, the encoder's input window keeps the buffers it handed to the swapchain until the Vulkan surface is destroyed, which otherwise only happened when the export runtime collected the context. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GsWPRiqsBPtiH9G7qvnLrr
wcandillon
left a comment
There was a problem hiding this comment.
look good, added a small change request
MakeWebGPUCanvasContextFromNativeWindow branched on __ANDROID__ in the shared RNWebGPU.h to retain the window and build its releaser. Move that into PlatformContext::retainNativeWindow, next to wrapNativeBuffer, so the platform specifics live in the Android and Apple platform contexts.
unconfigure() on a context made from a native window detached its surface for good: a later configure() succeeded but rendered offscreen, with no error, which breaks the web's unconfigure/configure pattern. Handing the window its buffers back only takes destroying the Dawn surface, not dropping the window. SurfaceInfo now has a releaseSurfaceOnUnconfigure mode in which unconfigure() destroys the surface and configure() makes a new one for the same window (recreateSurfaceLocked, now on both platforms through a static WebGPURuntime::makeSurface). The window is retained until the SurfaceInfo is destroyed, like a view's, and GPUCanvasContext loses its _ownsSurface flag. MakeWebGPUCanvasContextFromNativeWindow rejects a non-finite or non-positive size and a window Dawn cannot make a surface for (there is no view to fall back to), and retains the window last, so nothing can throw between the retain and the SurfaceInfo adopting the reference. The registry's two SurfaceInfo creation paths share makeSurfaceInfoLocked. Docs: the example uses getPreferredCanvasFormat() (a CAMetalLayer wants bgra8unorm) and says what the context keeps alive (the window) and what the caller does (whatever it belongs to).
|
🎉 This PR is included in version 0.19.0 🎉 The release is available on:
Your semantic-release bot 📦🚀 |
Closes #583.
Adds
RNWebGPU.MakeWebGPUCanvasContextFromNativeWindow(window, width, height)(Android): a canvas context that presents to anANativeWindowthat is not a WebGPU view, e.g. the inputSurfaceof aMediaCodecencoder.BigInt; the context takes its own reference to it (released with the registry entry).allocateContextId()and is attached like a view's surface, then adopted byconfigure(). No view retires it, so the context removes it when it is destroyed (GPUCanvasContext::ownRegistryEntry).CAMetalLayercould come later the same way.present(); no Dawn change needed, see Android: render into an arbitrary Surface (e.g. MediaCodec's input) with a per-frame timestamp #583).Tested in
@azzapp/react-native-skia-video, whose Android export now renders straight into MediaCodec's input surface through this context, with no EGL (Qualcomm device, Android 15): correct frame timing and colors, and the export is about 35% faster when the encoder is the bottleneck. There is no e2e test here: the harness can't create anANativeWindowfrom JS.