feat: Allocate GPU-sampleable YUV buffers on Android for zero-copy GPU Frame import#4023
feat: Allocate GPU-sampleable YUV buffers on Android for zero-copy GPU Frame import#4023wcandillon wants to merge 2 commits into
Conversation
…U Frame import On Android, the `'yuv'` and `'rgb'` Frame Output formats stream through CameraX's default ImageReaders, which allocate CPU-only buffers. Their HardwareBuffers lack `USAGE_GPU_SAMPLED_IMAGE`, so GPU APIs cannot import them: Vulkan (and therefore WebGPU/Dawn and Skia) derives texture usages from the AHardwareBuffer's usage bits and refuses to sample CPU-only buffers. In practice this meant `Frame.getNativeBuffer()` was only GPU-importable with `pixelFormat: 'native'`. This adds a `YuvImageReaderProxy` (mirroring the existing `PrivateImageReaderProxy`) that allocates YUV_420_888 buffers with `USAGE_CPU_READ_OFTEN | USAGE_GPU_SAMPLED_IMAGE` on API 29+, when `HardwareBuffer.isSupported(...)` reports the combination is available. Frames stay fully CPU-readable (planes are exposed as before) and can now additionally be imported zero-copy as GPU textures. On devices or API levels without support, behavior falls back to CPU-only buffers, same as before. Also fixes the stale `enableGpuBuffers` reference in the physical buffer rotation error message (the option is `pixelFormat: 'native'` nowadays). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@wcandillon is attempting to deploy a commit to the Margelo Team on Vercel. A member of the Team first needs to authorize it. |
|
Hey - thanks for the PR! |
|
native is the better choice here (at least for RN WebGPU/Skia) but in guess the user chooses 'yuv' it allows for GPU only import. But I agree that this not important for WebGPU/Skia. |
|
Hey! I reported this feature request to the CameraX team here: https://issuetracker.google.com/u/1/issues/492934501 (in the latest comment down in this issue) I feel like this makes most sense. The next CameraX release will have GPU buffer support in PRIVATE ImageFormat, while the current one has a slower CPU based route. I believe the HardwareBuffers that are streamed in YUV right now are still accessible on the GPU via Vulkan/OpenGL, it's just a slower path for now. Not sure what we want to do, if I want to merge this custom YUV ImageReader in the meantime or not... 🤔 |
|
The build is failing in CI: |
|
The error message fix can be a separate PR I can merge right now, do you want to do that or should I? :) |
What
On Android,
pixelFormat: 'yuv'Frame Outputs now allocate theirYUV_420_888buffers withUSAGE_CPU_READ_OFTEN | USAGE_GPU_SAMPLED_IMAGE(API 29+, whenHardwareBuffer.isSupported(...)says the device can), via a newYuvImageReaderProxymirroring the existingPrivateImageReaderProxy. Frames stay fully CPU-readable (planes are exposed unchanged), and can now additionally be imported zero-copy as GPU textures throughFrame.getNativeBuffer().Also fixes a stale error message: the physical-buffer-rotation error in
HybridFrameOutput.ktreferencedenableGpuBuffers, an option that no longer exists; it now points atpixelFormat: 'native'/enablePhysicalBufferRotation.Why
CameraX's default ImageReaders allocate CPU-only gralloc buffers. Vulkan derives importable texture usages from the
AHardwareBufferusage bits, so withoutUSAGE_GPU_SAMPLED_IMAGEthe buffer cannot be sampled by the GPU. Concretely, Dawn (WebGPU) maps AHB usage to WebGPU usages inAHBFunctions.cppand only grantsTextureBindingwhenUSAGE_GPU_SAMPLED_IMAGEis present. The same applies to Skia's Vulkan backend and anySamplerYcbcrConversion-based import.Today this means the docs' promise of zero-copy GPU import of Frames (via
NativeBuffer) only holds forpixelFormat: 'native'on Android. ML/vision pipelines that want CPU access and a GPU preview/effects pass (e.g. react-native-webgpu, Skia) have no working format. With this change,'yuv'serves both.Behavior / compatibility
HardwareBuffer.isSupportedreturns false for the GPU-sampled combination: falls back to a plainImageReader, identical behavior to today.getPlanes()works as before.'rgb'is unchanged (CameraX's RGBA conversion writes via CPU into its own reader; that pipeline isn't compatible with a custom GPU-usage reader).PrivateImageReaderProxy, this uses CameraX's restrictedImageReaderProxyProviderAPI and a placeholderImageInfo(CameraX wraps the proxy in aSettableImageProxywith the correct rotation before delivering it).Testing
Untested on a physical device so far; review with that in mind. The structure deliberately mirrors the proven
PrivateImageReaderProxy/PrivateImageProxypair.🤖 Generated with Claude Code