diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx index cc85c03b5..08e0b2838 100644 --- a/apps/example/src/App.tsx +++ b/apps/example/src/App.tsx @@ -37,6 +37,7 @@ import { AsyncStarvation } from "./Diagnostics/AsyncStarvation"; import { DeviceLostHang } from "./Diagnostics/DeviceLostHang"; import { DiagnosticsList } from "./Diagnostics/DiagnosticsList"; import { WorkletRequestAdapter } from "./Diagnostics/WorkletRequestAdapter"; +import { ReloadLifecycle } from "./Diagnostics/ReloadLifecycle"; import { ContextEdgeCases } from "./Diagnostics/ContextEdgeCases"; import { ViewFormatsUseAfterFree } from "./Diagnostics/ViewFormatsUseAfterFree"; import { RenderAfterUnmount } from "./Diagnostics/RenderAfterUnmount"; @@ -113,6 +114,7 @@ function App() { name="WorkletRequestAdapter" component={WorkletRequestAdapter} /> + + device.createComputePipelineAsync({ + label: "reload-lifecycle-pending-pipeline", + layout: "auto", + compute: { + module: device.createShaderModule({ + code: "@compute @workgroup_size(1) fn main() {}", + }), + entryPoint: "main", + }, + }); + +export const ReloadLifecycle = () => { + const deviceRef = useRef(null); + const [status, setStatus] = useState("Requesting adapter…"); + const [error, setError] = useState(null); + + useEffect(() => { + let cancelled = false; + + const prepare = async () => { + try { + const adapter = await navigator.gpu.requestAdapter(); + if (!adapter) { + throw new Error("Failed to acquire a GPU adapter."); + } + + const device = await adapter.requestDevice({ + label: "reload-lifecycle-probe", + }); + if (cancelled) { + return; + } + + deviceRef.current = device; + setStatus("WebGPU is ready. Reload can now be triggered."); + + // Keep a native callback registered so runtime teardown has to cancel it. + void device.lost.catch(() => undefined); + } catch (cause) { + if (!cancelled) { + setError(cause instanceof Error ? cause.message : String(cause)); + } + } + }; + + void prepare(); + return () => { + cancelled = true; + deviceRef.current = null; + }; + }, []); + + const reload = () => { + const device = deviceRef.current; + if (!device) { + return; + } + + // Do not await these operations: reload must invalidate their callbacks while + // they are still associated with the current JavaScript runtime. + void createPendingPipeline(device).catch(() => undefined); + void device.queue.onSubmittedWorkDone().catch(() => undefined); + DevSettings.reload("react-native-webgpu lifecycle regression"); + }; + + const ready = deviceRef.current !== null; + + return ( + + + Runtime Reload Lifecycle + {status} + {error ? {error} : null} + + Start async work and reload + + + After reload, open this screen again. A healthy installation acquires + a fresh adapter and device and reaches the ready state. + + + + ); +}; diff --git a/apps/example/src/Route.ts b/apps/example/src/Route.ts index a3c4cea20..329465bf4 100644 --- a/apps/example/src/Route.ts +++ b/apps/example/src/Route.ts @@ -30,6 +30,7 @@ export type Routes = { AsyncStarvation: undefined; DeviceLostHang: undefined; WorkletRequestAdapter: undefined; + ReloadLifecycle: undefined; ContextEdgeCases: undefined; ViewFormatsUseAfterFree: undefined; RenderAfterUnmount: undefined;