@@ -2,6 +2,7 @@ import React, { useEffect } from "react";
22import {
33 Linking ,
44 PixelRatio ,
5+ Platform ,
56 StyleSheet ,
67 Text ,
78 TouchableOpacity ,
@@ -10,7 +11,6 @@ import {
1011import {
1112 Canvas ,
1213 useCanvasRef ,
13- useDevice ,
1414 type NativeCanvas ,
1515 type RNCanvasContext ,
1616} from "react-native-wgpu" ;
@@ -84,6 +84,15 @@ const REQUIRED_FEATURES: GPUFeatureName[] = [
8484 "dawn-multi-planar-formats" as GPUFeatureName ,
8585] ;
8686
87+ // Android-only feature, gates Dawn's "wrap a YCbCr AHB as a GPUExternalTexture
88+ // with implicit SamplerYcbcrConversion" path. Without it our native
89+ // `importExternalTexture` flow on Android can't produce a usable external
90+ // texture from a camera frame. We probe the adapter for it and surface a
91+ // clear error if the device's Vulkan driver doesn't advertise it (e.g. some
92+ // Android-Desktop / Chromebook configurations).
93+ const OPAQUE_YCBCR_EXT =
94+ "opaque-ycbcr-android-for-external-texture" as GPUFeatureName ;
95+
8796const ABERRATION_STRENGTH = 0.006 ;
8897
8998export const VisionCamera = ( ) => {
@@ -114,9 +123,69 @@ export const VisionCamera = () => {
114123
115124const CameraView = ( ) => {
116125 const ref = useCanvasRef ( ) ;
117- const { device, adapter } = useDevice ( undefined , {
118- requiredFeatures : REQUIRED_FEATURES ,
119- } ) ;
126+ const [ gpu , setGpu ] = React . useState < {
127+ adapter : GPUAdapter ;
128+ device : GPUDevice ;
129+ } | null > ( null ) ;
130+ const [ deviceError , setDeviceError ] = React . useState < string | null > ( null ) ;
131+ React . useEffect ( ( ) => {
132+ let cancelled = false ;
133+ ( async ( ) => {
134+ try {
135+ const adapter = await navigator . gpu . requestAdapter ( ) ;
136+ if ( ! adapter ) {
137+ throw new Error ( "requestAdapter returned null" ) ;
138+ }
139+ const adapterFeatures = [ ...adapter . features ] . sort ( ) ;
140+ console . log (
141+ "[VisionCamera] adapter features (" +
142+ adapterFeatures . length +
143+ "): " +
144+ adapterFeatures . join ( ", " ) ,
145+ ) ;
146+ const hasOpaqueYCbCrExt =
147+ Platform . OS !== "android" || adapter . features . has ( OPAQUE_YCBCR_EXT ) ;
148+ if ( Platform . OS === "android" && ! hasOpaqueYCbCrExt ) {
149+ throw new Error (
150+ "This Android device's Vulkan driver doesn't advertise " +
151+ "opaque-ycbcr-android-for-external-texture. Camera-frame import " +
152+ "as a GPUExternalTexture isn't supported here. (This is a " +
153+ "device/driver limitation, not a code issue.)" ,
154+ ) ;
155+ }
156+ const featuresToRequest : GPUFeatureName [ ] = [
157+ ...REQUIRED_FEATURES ,
158+ ...( Platform . OS === "android" ? [ OPAQUE_YCBCR_EXT ] : [ ] ) ,
159+ ] ;
160+ console . log (
161+ "[VisionCamera] requesting device with features: " +
162+ featuresToRequest . join ( ", " ) ,
163+ ) ;
164+ const device = await adapter . requestDevice ( {
165+ requiredFeatures : featuresToRequest ,
166+ } ) ;
167+ if ( cancelled ) {
168+ return ;
169+ }
170+ console . log (
171+ "[VisionCamera] device created, features: " +
172+ [ ...device . features ] . sort ( ) . join ( ", " ) ,
173+ ) ;
174+ setGpu ( { adapter, device } ) ;
175+ } catch ( e ) {
176+ if ( cancelled ) {
177+ return ;
178+ }
179+ console . warn ( "[VisionCamera] device creation failed: " + String ( e ) ) ;
180+ setDeviceError ( String ( e ) ) ;
181+ }
182+ } ) ( ) ;
183+ return ( ) => {
184+ cancelled = true ;
185+ } ;
186+ } , [ ] ) ;
187+ const device = gpu ?. device ?? null ;
188+ const adapter = gpu ?. adapter ?? null ;
120189 const devices = useCameraDevices ( ) ;
121190 // Pick back camera if available, otherwise front, otherwise anything. The
122191 // iOS simulator returns an empty list since there are no cameras, in which
@@ -274,10 +343,18 @@ const CameraView = () => {
274343 new Float32Array ( [ sx , sy , ABERRATION_STRENGTH , 0 ] ) ,
275344 ) ;
276345
277- const externalTex = device . importExternalTexture ( {
278- source : videoFrame ,
279- label : "camera-frame" ,
280- } ) ;
346+ let externalTex ;
347+ try {
348+ externalTex = device . importExternalTexture ( {
349+ source : videoFrame ,
350+ label : "camera-frame" ,
351+ } ) ;
352+ } catch ( e ) {
353+ console . warn (
354+ "[VisionCamera] importExternalTexture threw: " + String ( e ) ,
355+ ) ;
356+ throw e ;
357+ }
281358 const bindGroup = device . createBindGroup ( {
282359 layout : pipeline . getBindGroupLayout ( 0 ) ,
283360 entries : [
@@ -323,13 +400,29 @@ const CameraView = () => {
323400 outputs : [ frameOutput ] ,
324401 } ) ;
325402
403+ if ( deviceError ) {
404+ return (
405+ < View style = { styles . errorContainer } >
406+ < Text style = { styles . errorText } >
407+ Device creation failed: { deviceError }
408+ </ Text >
409+ </ View >
410+ ) ;
411+ }
326412 if ( error ) {
327413 return (
328414 < View style = { styles . errorContainer } >
329415 < Text style = { styles . errorText } > { error } </ Text >
330416 </ View >
331417 ) ;
332418 }
419+ if ( ! device ) {
420+ return (
421+ < View style = { styles . errorContainer } >
422+ < Text style = { styles . errorText } > Waiting for GPU device...</ Text >
423+ </ View >
424+ ) ;
425+ }
333426 if ( cameraDevice == null ) {
334427 return (
335428 < View style = { styles . errorContainer } >
0 commit comments