diff --git a/apps/example/getWebMetroConfig.js b/apps/example/getWebMetroConfig.js new file mode 100644 index 000000000..d16cdb946 --- /dev/null +++ b/apps/example/getWebMetroConfig.js @@ -0,0 +1,32 @@ +const path = require("path"); +const root = path.resolve(__dirname, "../.."); +const rnwPath = path.resolve(root, "node_modules/react-native-web"); +const assetRegistryPath = path.resolve( + root, + "node_modules/react-native-web/dist/modules/AssetRegistry/index", +); + +module.exports = function (metroConfig) { + metroConfig.resolver.platforms = ["ios", "android", "web"]; + const origResolveRequest = metroConfig.resolver.resolveRequest; + metroConfig.resolver.resolveRequest = (contextRaw, moduleName, platform) => { + const context = { + ...contextRaw, + preferNativePlatform: false, + }; + + if (moduleName === "react-native") { + return { + filePath: path.resolve(rnwPath, "dist/index.js"), + type: "sourceFile", + }; + } + + // Let default config handle other modules + return origResolveRequest(context, moduleName, platform); + }; + + metroConfig.transformer.assetRegistryPath = assetRegistryPath; + + return metroConfig; +}; diff --git a/apps/example/index.html b/apps/example/index.html new file mode 100644 index 000000000..456a055c3 --- /dev/null +++ b/apps/example/index.html @@ -0,0 +1,37 @@ + + + + + + + + + + Example + + + + + +
+ + + diff --git a/apps/example/index.web.js b/apps/example/index.web.js new file mode 100644 index 000000000..405131a75 --- /dev/null +++ b/apps/example/index.web.js @@ -0,0 +1,22 @@ +import { AppRegistry } from "react-native"; + +import App from "./src/App"; +import { name as appName } from "./app.json"; + +AppRegistry.registerComponent(appName, () => App); + +const rootTag = document.getElementById("root"); +if (process.env.NODE_ENV !== "production") { + if (!rootTag) { + throw new Error( + 'Required HTML element with id "root" was not found in the document HTML.', + ); + } +} + +CanvasKitInit({ + locateFile: (file) => `https://unpkg.com/canvaskit-wasm/bin/full/${file}`, +}).then((CanvasKit) => { + window.CanvasKit = global.CanvasKit = CanvasKit; + AppRegistry.runApplication(appName, { rootTag }); +}); diff --git a/apps/example/ios/Podfile.lock b/apps/example/ios/Podfile.lock index fa69eaf26..5b2e58d55 100644 --- a/apps/example/ios/Podfile.lock +++ b/apps/example/ios/Podfile.lock @@ -1865,7 +1865,7 @@ PODS: - ReactCommon/turbomodule/core - SocketRocket - Yoga - - react-native-wgpu (0.3.2): + - react-native-wgpu (0.4.0): - boost - DoubleConversion - fast_float @@ -2903,7 +2903,7 @@ SPEC CHECKSUMS: React-microtasksnativemodule: 75b6604b667d297292345302cc5bfb6b6aeccc1b react-native-safe-area-context: c6e2edd1c1da07bdce287fa9d9e60c5f7b514616 react-native-skia: 5bf2b2107cd7f2d806fd364f5e16b1c7554ed3cd - react-native-wgpu: 15ebc049194b0d06082ab00fb2e5d21ec871c2f4 + react-native-wgpu: 5abf760f89d1517255e48de1b5208d2a307109a0 React-NativeModulesApple: 879fbdc5dcff7136abceb7880fe8a2022a1bd7c3 React-oscompat: 93b5535ea7f7dff46aaee4f78309a70979bdde9d React-perflogger: 5536d2df3d18fe0920263466f7b46a56351c0510 diff --git a/apps/example/metro.config.js b/apps/example/metro.config.js index cc4d67857..28b1ad125 100644 --- a/apps/example/metro.config.js +++ b/apps/example/metro.config.js @@ -1,9 +1,11 @@ const { getDefaultConfig, mergeConfig } = require("@react-native/metro-config"); const path = require('path'); +const getWebMetroConfig = require('./getWebMetroConfig'); const root = path.resolve(__dirname, '../..'); const threePackagePath = path.resolve(root, 'node_modules/three'); +const r3fPath = path.resolve(root, "node_modules/@react-three/fiber"); const defaultConfig = getDefaultConfig(__dirname); const customConfig = { @@ -32,6 +34,14 @@ const customConfig = { type: 'sourceFile', }; } + + if (moduleName === "@react-three/fiber") { + //Just use the vanilla web build of react three fiber, not the stale "native" code path which has not been kept up to date. + return { + filePath: path.resolve(r3fPath, "dist/react-three-fiber.esm.js"), + type: "sourceFile", + }; + } // Let Metro handle other modules return context.resolveRequest(context, moduleName, platform); }, @@ -51,5 +61,4 @@ const customConfig = { const metroConfig = mergeConfig(defaultConfig, customConfig); - -module.exports = metroConfig; +module.exports = !!process.env.IS_WEB_BUILD ? getWebMetroConfig(metroConfig) : metroConfig; diff --git a/apps/example/package.json b/apps/example/package.json index 1b8223271..95e9bc9a1 100644 --- a/apps/example/package.json +++ b/apps/example/package.json @@ -7,6 +7,7 @@ "tsc": "tsc --noEmit", "android": "react-native run-android", "ios": "react-native run-ios", + "web": "IS_WEB_BUILD=true react-native start", "start": "react-native start", "pod:install:ios": "pod install --project-directory=ios", "pod:install:macos": "pod install --project-directory=macos", @@ -28,11 +29,13 @@ "async-mutex": "^0.5.0", "fast-text-encoding": "^1.0.6", "react": "19.1.0", + "react-dom": "19.1.0", "react-native": "0.81.4", "react-native-gesture-handler": "^2.28.0", "react-native-macos": "^0.79.0", "react-native-reanimated": "3.19.1", "react-native-safe-area-context": "^5.4.0", + "react-native-web": "^0.21.2", "react-native-wgpu": "*", "teapot": "^1.0.0", "three": "0.172.0", @@ -50,6 +53,7 @@ "@rnx-kit/metro-config": "^2.0.0", "@types/node": "^20.14.7", "@types/react": "^18.2.6", + "@types/react-dom": "^19.2.2", "@types/react-test-renderer": "^18.0.0", "@types/three": "0.172.0", "@webgpu/types": "0.1.65", diff --git a/apps/example/src/App.tsx b/apps/example/src/App.tsx index 21637b42c..c1f2037d7 100644 --- a/apps/example/src/App.tsx +++ b/apps/example/src/App.tsx @@ -1,3 +1,5 @@ +import "./resolveAssetSourcePolyfill"; + import { NavigationContainer } from "@react-navigation/native"; import { createStackNavigator } from "@react-navigation/stack"; import { GestureHandlerRootView } from "react-native-gesture-handler"; @@ -48,7 +50,10 @@ function App() { return ( - + ; } +// Use a CORS proxy for web to bypass CORS restrictions +const getCorsProxyUrl = (url: string) => { + return Platform.OS === "web" + ? `https://corsproxy.io/?${encodeURIComponent(url)}` + : url; +}; + export const useComputeToy = (toyId: number) => { const [props, setProps] = useState(null); useEffect(() => { (async () => { - const shaderURL = `https://compute.toys/view/${toyId}/wgsl`; - const uniformsURL = `https://compute.toys/view/${toyId}/json`; + const shaderURL = getCorsProxyUrl( + `https://compute.toys/view/${toyId}/wgsl`, + ); + const uniformsURL = getCorsProxyUrl( + `https://compute.toys/view/${toyId}/json`, + ); // Execute both fetch requests in parallel const [shaderResponse, uniformsResponse] = await Promise.all([ diff --git a/apps/example/src/Home.tsx b/apps/example/src/Home.tsx index 0894a25f9..711978821 100644 --- a/apps/example/src/Home.tsx +++ b/apps/example/src/Home.tsx @@ -126,9 +126,11 @@ export const examples = [ ]; const styles = StyleSheet.create({ - container: {}, + container: { + flex: 1, + }, content: { - paddingBottom: 32, + marginBottom: 32, }, thumbnail: { backgroundColor: "white", diff --git a/apps/example/src/MNISTInference/MNISTInference.web.tsx b/apps/example/src/MNISTInference/MNISTInference.web.tsx new file mode 100644 index 000000000..128b1b096 --- /dev/null +++ b/apps/example/src/MNISTInference/MNISTInference.web.tsx @@ -0,0 +1,3 @@ +export const MNISTInference = () => { + return null; +}; diff --git a/apps/example/src/assets/helvetica.ttf b/apps/example/src/assets/helvetica.ttf new file mode 100644 index 000000000..ecc926416 Binary files /dev/null and b/apps/example/src/assets/helvetica.ttf differ diff --git a/apps/example/src/resolveAssetSourcePolyfill.js b/apps/example/src/resolveAssetSourcePolyfill.js new file mode 100644 index 000000000..315749983 --- /dev/null +++ b/apps/example/src/resolveAssetSourcePolyfill.js @@ -0,0 +1,57 @@ +import { Image, PixelRatio, Platform } from "react-native"; +import { getAssetByID } from "react-native-web/dist/modules/AssetRegistry"; + +// react-native-web does not support resolveAssetSource out of the box +// https://github.com/necolas/react-native-web/issues/1666 +if (Platform.OS == "web") { + function resolveAssetUri(source) { + let uri = null; + if (typeof source === "number") { + // get the URI from the packager + const asset = getAssetByID(source); + if (asset == null) { + throw new Error( + `Image: asset with ID "${source}" could not be found. Please check the image source or packager.`, + ); + } + // eslint-disable-next-line prefer-destructuring + let scale = asset.scales[0]; + if (asset.scales.length > 1) { + const preferredScale = PixelRatio.get(); + // Get the scale which is closest to the preferred scale + scale = asset.scales.reduce((prev, curr) => + Math.abs(curr - preferredScale) < Math.abs(prev - preferredScale) + ? curr + : prev, + ); + } + const scaleSuffix = scale !== 1 ? `@${scale}x` : ""; + uri = asset + ? `${asset.httpServerLocation}/${asset.name}${scaleSuffix}.${asset.type}` + : ""; + } else if (typeof source === "string") { + uri = source; + } else if (source && typeof source.uri === "string") { + // eslint-disable-next-line prefer-destructuring + uri = source.uri; + } + + if (uri) { + const svgDataUriPattern = /^(data:image\/svg\+xml;utf8,)(.*)/; + const match = uri.match(svgDataUriPattern); + // inline SVG markup may contain characters (e.g., #, ") that need to be escaped + if (match) { + const [, prefix, svg] = match; + const encodedSvg = encodeURIComponent(svg); + return `${prefix}${encodedSvg}`; + } + } + + return uri; + } + + Image.resolveAssetSource = (source) => { + const uri = resolveAssetUri(source) || ""; + return { uri }; + }; +} diff --git a/package.json b/package.json index c837e2c27..d66820d35 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "pod:install": "turbo run pod:install" }, "devDependencies": { + "@types/react-native-web": "^0.19.2", "turbo": "^2.1.0" } } diff --git a/packages/webgpu/README.md b/packages/webgpu/README.md index 5ed3e459a..ea2eb0ccc 100644 --- a/packages/webgpu/README.md +++ b/packages/webgpu/README.md @@ -1,7 +1,7 @@ # React Native WebGPU -React Native implementation of WebGPU using [Dawn](https://dawn.googlesource.com/dawn). -This is currently a technical preview for early adopters. +React Native implementation of WebGPU using [Dawn](https://dawn.googlesource.com/dawn). +This is currently a technical preview for early adopters. React Native WebGPU requires React Native 0.81 or newer and doesn't run on legacy architecture. @@ -13,6 +13,8 @@ Please note that the package name is `react-native-wgpu`. npm install react-native-wgpu ``` +Note that if you use pnpm, you MUST use a `node-linker = hoisted` so that the external reference to the Dawn webgpu library can successfully link it. + Below are some examples from the [example app](/apps/example/). https://github.com/user-attachments/assets/116a41b2-2cf8-49f1-9f16-a5c83637c198 @@ -156,8 +158,8 @@ From there you will be able to run the example app properly. ## Similarities and Differences with the Web -The API has been designed to be completely symmetric with the Web. -For instance, you can access the WebGPU context synchronously, as well as the canvas size. +The API has been designed to be completely symmetric with the Web. +For instance, you can access the WebGPU context synchronously, as well as the canvas size. Pixel density and canvas resizing are handled exactly like on the Web as well. ```tsx @@ -171,7 +173,7 @@ ctx.canvas.height = ctx.canvas.clientHeight * PixelRatio.get(); ### Frame Scheduling -In React Native, we want to keep frame presentation as a manual operation as we plan to provide more advanced rendering options that are React Native specific. +In React Native, we want to keep frame presentation as a manual operation as we plan to provide more advanced rendering options that are React Native specific. This means that when you are ready to present a frame, you need to call `present` on the context. ```tsx diff --git a/packages/webgpu/package.json b/packages/webgpu/package.json index 3a688afe2..801e206a4 100644 --- a/packages/webgpu/package.json +++ b/packages/webgpu/package.json @@ -1,6 +1,6 @@ { "name": "react-native-wgpu", - "version": "0.3.2", + "version": "0.4.0", "description": "React Native WebGPU", "main": "lib/commonjs/index", "module": "lib/module/index", @@ -81,6 +81,7 @@ "react": "19.1.0", "react-native": "0.81.4", "react-native-builder-bob": "^0.23.2", + "react-native-web": "^0.21.2", "rimraf": "^5.0.7", "seedrandom": "^3.0.5", "teapot": "^1.0.0", diff --git a/packages/webgpu/src/Canvas.tsx b/packages/webgpu/src/Canvas.tsx index e311f0b48..5ea1bf1c9 100644 --- a/packages/webgpu/src/Canvas.tsx +++ b/packages/webgpu/src/Canvas.tsx @@ -1,5 +1,5 @@ import type { ViewProps } from "react-native"; -import { View } from "react-native"; +import { Platform, View } from "react-native"; import { forwardRef, useImperativeHandle, useRef, useState } from "react"; import WebGPUNativeView from "./WebGPUViewNativeComponent"; @@ -62,9 +62,16 @@ export const Canvas = forwardRef< if (!viewRef.current) { throw new Error("[WebGPU] Cannot get context before mount"); } - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - const size = viewRef.current.unstable_getBoundingClientRect(); + let size; + if (Platform.OS === "web") { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + size = viewRef.current.getBoundingClientRect(); + } else { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-expect-error + size = viewRef.current.unstable_getBoundingClientRect(); + } return RNWebGPU.MakeWebGPUCanvasContext( contextId, size.width, @@ -72,6 +79,7 @@ export const Canvas = forwardRef< ); }, })); + return ( void>( + func: T, + wait: number, + immediate = false, +) { + let timeout: ReturnType | undefined; + return function debounced( + this: ThisParameterType, + ...args: Parameters + ) { + const context = this; + const callNow = immediate && !timeout; + if (timeout) { + clearTimeout(timeout); + } + timeout = setTimeout(() => { + timeout = undefined; + if (!immediate) { + func.apply(context, args); + } + }, wait); + if (callNow) { + func.apply(context, args); + } + }; +} + +function resizeCanvas(canvas?: HTMLCanvasElement) { + if (!canvas) { + return; + } + + const dpr = window.devicePixelRatio || 1; + + const { height, width } = canvas.getBoundingClientRect(); + canvas.setAttribute("height", (height * dpr).toString()); + canvas.setAttribute("width", (width * dpr).toString()); +} + +// eslint-disable-next-line import/no-default-export +export default function WebGPUViewNativeComponent(props: NativeProps) { + const { contextId, style, transparent, ...rest } = props; + + const canvasElm = useRef(); + + useEffect(() => { + const onResize = debounce(() => resizeCanvas(canvasElm.current), 100); + window.addEventListener("resize", onResize); + return () => { + window.removeEventListener("resize", onResize); + }; + }, []); + + return unstableCreateElement("canvas", { + ...rest, + style: [ + styles.view, + styles.flex1, + transparent === false && { backgroundColor: "white" }, // Canvas elements are transparent by default on the web + style, + ], + id: contextIdToId(contextId), + ref: (ref: HTMLCanvasElement) => { + canvasElm.current = ref; + if (ref) { + resizeCanvas(ref); + } + }, + }); +} + +const styles = StyleSheet.create({ + flex1: { + flex: 1, + }, + view: { + alignItems: "stretch", + backgroundColor: "transparent", + // @ts-expect-error - not a valid RN style, but it's valid for web + border: "0 solid black", + boxSizing: "border-box", + display: "flex", + flexBasis: "auto", + flexDirection: "column", + flexShrink: 0, + listStyle: "none", + margin: 0, + minHeight: 0, + minWidth: 0, + padding: 0, + position: "relative", + zIndex: 0, + }, +}); diff --git a/packages/webgpu/src/WebPolyfillGPUModule.ts b/packages/webgpu/src/WebPolyfillGPUModule.ts new file mode 100644 index 000000000..9dcc1f1c5 --- /dev/null +++ b/packages/webgpu/src/WebPolyfillGPUModule.ts @@ -0,0 +1,53 @@ +import { contextIdToId } from "./utils"; + +const fabric = true; + +function getNativeSurface(contextId: number) { + const canvas = document.getElementById( + contextIdToId(contextId), + ) as HTMLCanvasElement; + + const { height, width } = canvas.getBoundingClientRect()!; + + return { + surface: BigInt(contextId), + height, + width, + clientHeight: height, + clientWidth: width, + }; +} + +function makeWebGPUCanvasContext( + contextId: number, + width: number, + height: number, +) { + const canvas = document.getElementById( + contextIdToId(contextId), + ) as HTMLCanvasElement; + + const dpr = window.devicePixelRatio || 1; + const pixelWidth = (width * dpr).toString(); + const pixelHeight = (height * dpr).toString(); + + if ( + canvas.getAttribute("width") !== pixelWidth || + canvas.getAttribute("height") !== pixelHeight + ) { + canvas.setAttribute("width", pixelWidth); + canvas.setAttribute("height", pixelHeight); + } + + const context = canvas.getContext("webgpu")!; + return Object.assign(context, { + present: () => {}, + }); +} + +// @ts-expect-error - polyfill for RNWebGPU native module +window.RNWebGPU = { + getNativeSurface, + MakeWebGPUCanvasContext: makeWebGPUCanvasContext, + fabric, +}; diff --git a/packages/webgpu/src/index.tsx b/packages/webgpu/src/index.tsx index 88d6e991e..46875c256 100644 --- a/packages/webgpu/src/index.tsx +++ b/packages/webgpu/src/index.tsx @@ -1,197 +1,26 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import WebGPUNativeModule from "./NativeWebGPUModule"; - -export * from "./Canvas"; -export * from "./Offscreen"; -export * from "./WebGPUViewNativeComponent"; -export * from "./hooks"; -export { default as WebGPUModule } from "./NativeWebGPUModule"; - -const GPU: any = {}; -GPU[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPU"; -}; - -const GPUAdapter: any = {}; -GPUAdapter[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUAdapter"; -}; - -const GPUAdapterInfo: any = {}; -GPUAdapterInfo[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUAdapterInfo"; -}; - -const GPUBindGroup: any = {}; -GPUBindGroup[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUBindGroup"; -}; - -const GPUBindGroupLayout: any = {}; -GPUBindGroupLayout[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUBindGroupLayout"; -}; - -const GPUBuffer: any = {}; -GPUBuffer[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUBuffer"; -}; - -const GPUCanvasContext: any = {}; -GPUCanvasContext[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUCanvasContext"; -}; - -const GPUCommandBuffer: any = {}; -GPUCommandBuffer[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUCommandBuffer"; -}; - -const GPUCommandEncoder: any = {}; -GPUCommandEncoder[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUCommandEncoder"; -}; - -const GPUCompilationInfo: any = {}; -GPUCompilationInfo[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUCompilationInfo"; -}; - -const GPUCompilationMessage: any = {}; -GPUCompilationMessage[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUCompilationMessage"; -}; - -const GPUComputePassEncoder: any = {}; -GPUComputePassEncoder[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUComputePassEncoder"; -}; - -const GPUComputePipeline: any = {}; -GPUComputePipeline[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUComputePipeline"; -}; - -const GPUDevice: any = {}; -GPUDevice[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUDevice"; -}; - -const GPUDeviceLostInfo: any = {}; -GPUDeviceLostInfo[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUDeviceLostInfo"; -}; - -const GPUError: any = {}; -GPUError[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUError"; -}; - -const GPUExternalTexture: any = {}; -GPUExternalTexture[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUExternalTexture"; -}; - -const GPUPipelineLayout: any = {}; -GPUPipelineLayout[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUPipelineLayout"; -}; - -const GPUQuerySet: any = {}; -GPUQuerySet[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUQuerySet"; -}; - -const GPUQueue: any = {}; -GPUQueue[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUQueue"; -}; - -const GPURenderBundle: any = {}; -GPURenderBundle[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPURenderBundle"; -}; - -const GPURenderBundleEncoder: any = {}; -GPURenderBundleEncoder[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPURenderBundleEncoder"; -}; - -const GPURenderPassEncoder: any = {}; -GPURenderPassEncoder[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPURenderPassEncoder"; -}; - -const GPURenderPipeline: any = {}; -GPURenderPipeline[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPURenderPipeline"; -}; - -const GPUSampler: any = {}; -GPUSampler[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUSampler"; -}; - -const GPUShaderModule: any = {}; -GPUShaderModule[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUShaderModule"; -}; - -const GPUTexture: any = {}; -GPUTexture[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUTexture"; -}; - -const GPUTextureView: any = {}; -GPUTextureView[Symbol.hasInstance] = function (instance: object) { - return "__brand" in instance && instance.__brand === "GPUTextureView"; -}; - -global.GPU = GPU; -global.GPUAdapter = GPUAdapter; -global.GPUAdapterInfo = GPUAdapterInfo; -global.GPUBindGroup = GPUBindGroup; -global.GPUBindGroupLayout = GPUBindGroupLayout; -global.GPUBuffer = GPUBuffer; -global.GPUCanvasContext = GPUCanvasContext; -global.GPUCommandBuffer = GPUCommandBuffer; -global.GPUCommandEncoder = GPUCommandEncoder; -global.GPUCompilationInfo = GPUCompilationInfo; -global.GPUCompilationMessage = GPUCompilationMessage; -global.GPUComputePassEncoder = GPUComputePassEncoder; -global.GPUComputePipeline = GPUComputePipeline; -global.GPUDevice = GPUDevice; -global.GPUDeviceLostInfo = GPUDeviceLostInfo; -global.GPUError = GPUError; -global.GPUExternalTexture = GPUExternalTexture; -global.GPUPipelineLayout = GPUPipelineLayout; -global.GPUQuerySet = GPUQuerySet; -global.GPUQueue = GPUQueue; -global.GPURenderBundle = GPURenderBundle; -global.GPURenderBundleEncoder = GPURenderBundleEncoder; -global.GPURenderPassEncoder = GPURenderPassEncoder; -global.GPURenderPipeline = GPURenderPipeline; -global.GPUSampler = GPUSampler; -global.GPUShaderModule = GPUShaderModule; -global.GPUTexture = GPUTexture; -global.GPUTextureView = GPUTextureView; - -WebGPUNativeModule.install(); - -if (!navigator) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-expect-error - navigator = {}; +/// + +import type { NativeCanvas, RNCanvasContext } from "./types"; + +export * from "./main"; + +declare global { + interface Navigator { + gpu: GPU; + } + + var navigator: Navigator; + + var RNWebGPU: { + gpu: GPU; + fabric: boolean; + getNativeSurface: (contextId: number) => NativeCanvas; + MakeWebGPUCanvasContext: ( + contextId: number, + width: number, + height: number, + ) => RNCanvasContext; + DecodeToUTF8: (buffer: NodeJS.ArrayBufferView | ArrayBuffer) => string; + createImageBitmap: typeof createImageBitmap; + }; } -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-expect-error -navigator.gpu = RNWebGPU.gpu; - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore -navigator.userAgent = "react-native"; - -global.createImageBitmap = - global.createImageBitmap ?? - ((...params: Parameters) => - new Promise((resolve) => resolve(RNWebGPU.createImageBitmap(...params)))); diff --git a/packages/webgpu/src/main/index.tsx b/packages/webgpu/src/main/index.tsx new file mode 100644 index 000000000..3bcbe6c22 --- /dev/null +++ b/packages/webgpu/src/main/index.tsx @@ -0,0 +1,204 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import WebGPUModule from "../NativeWebGPUModule"; + +export * from "../Canvas"; +export * from "../Offscreen"; +export * from "../WebGPUViewNativeComponent"; +export * from "../hooks"; + +export { default as WebGPUModule } from "../NativeWebGPUModule"; + +const GPU: any = {}; +GPU[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPU"; +}; + +const GPUAdapter: any = {}; +GPUAdapter[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUAdapter"; +}; + +const GPUAdapterInfo: any = {}; +GPUAdapterInfo[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUAdapterInfo"; +}; + +const GPUBindGroup: any = {}; +GPUBindGroup[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUBindGroup"; +}; + +const GPUBindGroupLayout: any = {}; +GPUBindGroupLayout[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUBindGroupLayout"; +}; + +const GPUBuffer: any = {}; +GPUBuffer[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUBuffer"; +}; + +const GPUCanvasContext: any = {}; +GPUCanvasContext[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUCanvasContext"; +}; + +const GPUCommandBuffer: any = {}; +GPUCommandBuffer[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUCommandBuffer"; +}; + +const GPUCommandEncoder: any = {}; +GPUCommandEncoder[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUCommandEncoder"; +}; + +const GPUCompilationInfo: any = {}; +GPUCompilationInfo[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUCompilationInfo"; +}; + +const GPUCompilationMessage: any = {}; +GPUCompilationMessage[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUCompilationMessage"; +}; + +const GPUComputePassEncoder: any = {}; +GPUComputePassEncoder[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUComputePassEncoder"; +}; + +const GPUComputePipeline: any = {}; +GPUComputePipeline[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUComputePipeline"; +}; + +const GPUDevice: any = {}; +GPUDevice[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUDevice"; +}; + +const GPUDeviceLostInfo: any = {}; +GPUDeviceLostInfo[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUDeviceLostInfo"; +}; + +const GPUError: any = {}; +GPUError[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUError"; +}; + +const GPUExternalTexture: any = {}; +GPUExternalTexture[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUExternalTexture"; +}; + +const GPUPipelineLayout: any = {}; +GPUPipelineLayout[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUPipelineLayout"; +}; + +const GPUQuerySet: any = {}; +GPUQuerySet[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUQuerySet"; +}; + +const GPUQueue: any = {}; +GPUQueue[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUQueue"; +}; + +const GPURenderBundle: any = {}; +GPURenderBundle[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPURenderBundle"; +}; + +const GPURenderBundleEncoder: any = {}; +GPURenderBundleEncoder[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPURenderBundleEncoder"; +}; + +const GPURenderPassEncoder: any = {}; +GPURenderPassEncoder[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPURenderPassEncoder"; +}; + +const GPURenderPipeline: any = {}; +GPURenderPipeline[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPURenderPipeline"; +}; + +const GPUSampler: any = {}; +GPUSampler[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUSampler"; +}; + +const GPUShaderModule: any = {}; +GPUShaderModule[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUShaderModule"; +}; + +const GPUTexture: any = {}; +GPUTexture[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUTexture"; +}; + +const GPUTextureView: any = {}; +GPUTextureView[Symbol.hasInstance] = function (instance: object) { + return "__brand" in instance && instance.__brand === "GPUTextureView"; +}; + +global.GPU = GPU; +global.GPUAdapter = GPUAdapter; +global.GPUAdapterInfo = GPUAdapterInfo; +global.GPUBindGroup = GPUBindGroup; +global.GPUBindGroupLayout = GPUBindGroupLayout; +global.GPUBuffer = GPUBuffer; +global.GPUCanvasContext = GPUCanvasContext; +global.GPUCommandBuffer = GPUCommandBuffer; +global.GPUCommandEncoder = GPUCommandEncoder; +global.GPUCompilationInfo = GPUCompilationInfo; +global.GPUCompilationMessage = GPUCompilationMessage; +global.GPUComputePassEncoder = GPUComputePassEncoder; +global.GPUComputePipeline = GPUComputePipeline; +global.GPUDevice = GPUDevice; +global.GPUDeviceLostInfo = GPUDeviceLostInfo; +global.GPUError = GPUError; +global.GPUExternalTexture = GPUExternalTexture; +global.GPUPipelineLayout = GPUPipelineLayout; +global.GPUQuerySet = GPUQuerySet; +global.GPUQueue = GPUQueue; +global.GPURenderBundle = GPURenderBundle; +global.GPURenderBundleEncoder = GPURenderBundleEncoder; +global.GPURenderPassEncoder = GPURenderPassEncoder; +global.GPURenderPipeline = GPURenderPipeline; +global.GPUSampler = GPUSampler; +global.GPUShaderModule = GPUShaderModule; +global.GPUTexture = GPUTexture; +global.GPUTextureView = GPUTextureView; + +WebGPUModule.install(); + +if (!navigator) { + // @ts-expect-error Navigation object is more complex than this, setting it to an empty object to add gpu property + navigator = { + gpu: RNWebGPU.gpu, + userAgent: "react-native", + }; +} else { + navigator.gpu = RNWebGPU.gpu; + if (typeof navigator.userAgent !== "string") { + try { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore - Hermes navigator may not include a userAgent, align with the polyfill if needed + navigator.userAgent = "react-native"; + } catch { + // navigator.userAgent can be read-only; ignore if assignment fails + } + } +} + +global.createImageBitmap = + global.createImageBitmap ?? + ((...params: Parameters) => + new Promise((resolve) => resolve(RNWebGPU.createImageBitmap(...params)))); diff --git a/packages/webgpu/src/main/index.web.tsx b/packages/webgpu/src/main/index.web.tsx new file mode 100644 index 000000000..b848bbc69 --- /dev/null +++ b/packages/webgpu/src/main/index.web.tsx @@ -0,0 +1,8 @@ +import "../WebPolyfillGPUModule"; + +export * from "../Canvas"; +export * from "../Offscreen"; +export * from "../WebGPUViewNativeComponent"; +export * from "../hooks"; + +// We don't need to set all global properties on web, webgpu is already available globally diff --git a/packages/webgpu/src/types.ts b/packages/webgpu/src/types.ts new file mode 100644 index 000000000..af4684cfa --- /dev/null +++ b/packages/webgpu/src/types.ts @@ -0,0 +1,20 @@ +type SurfacePointer = bigint; + +export interface NativeCanvas { + surface: SurfacePointer; + width: number; + height: number; + clientWidth: number; + clientHeight: number; +} + +export type RNCanvasContext = GPUCanvasContext & { + present: () => void; +}; + +export interface CanvasRef { + getContextId: () => number; + getContext(contextName: "webgpu"): RNCanvasContext | null; + getNativeSurface: () => NativeCanvas; + whenReady: (callback: () => void) => void; +} diff --git a/packages/webgpu/src/utils.ts b/packages/webgpu/src/utils.ts new file mode 100644 index 000000000..39fa1b8df --- /dev/null +++ b/packages/webgpu/src/utils.ts @@ -0,0 +1,4 @@ +// Only used on the web +export function contextIdToId(contextId: number) { + return "rnwgpu-canvas-" + contextId; +} diff --git a/yarn.lock b/yarn.lock index 09d499738..766610bc0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1585,7 +1585,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.25.0": +"@babel/runtime@npm:^7.17.8, @babel/runtime@npm:^7.18.6, @babel/runtime@npm:^7.25.0": version: 7.28.4 resolution: "@babel/runtime@npm:7.28.4" checksum: 934b0a0460f7d06637d93fcd1a44ac49adc33518d17253b5a0b55ff4cb90a45d8fe78bf034b448911dbec7aff2a90b918697559f78d21c99ff8dbadae9565b55 @@ -2799,6 +2799,13 @@ __metadata: languageName: node linkType: hard +"@react-native/assets-registry@npm:0.82.1": + version: 0.82.1 + resolution: "@react-native/assets-registry@npm:0.82.1" + checksum: f511a248f455c7fe2bae330ed15929294d5d25e5d8aa27e148588a554bde31a1ddf54d08ebc21128cabe9978be4604b2e8ad4343566c9840fc49e2b0d119cbc6 + languageName: node + linkType: hard + "@react-native/babel-plugin-codegen@npm:0.74.84": version: 0.74.84 resolution: "@react-native/babel-plugin-codegen@npm:0.74.84" @@ -2992,6 +2999,23 @@ __metadata: languageName: node linkType: hard +"@react-native/codegen@npm:0.82.1": + version: 0.82.1 + resolution: "@react-native/codegen@npm:0.82.1" + dependencies: + "@babel/core": ^7.25.2 + "@babel/parser": ^7.25.3 + glob: ^7.1.1 + hermes-parser: 0.32.0 + invariant: ^2.2.4 + nullthrows: ^1.1.1 + yargs: ^17.6.2 + peerDependencies: + "@babel/core": "*" + checksum: d965d9f387a62e27a75d80546a6c825b21a42b56738eedf294b59221c117345148daa0362af5b793a2d5ce3e089e3682dd4bdd9548235ab040543f0aa21d46f0 + languageName: node + linkType: hard + "@react-native/community-cli-plugin@npm:0.74.84": version: 0.74.84 resolution: "@react-native/community-cli-plugin@npm:0.74.84" @@ -3056,6 +3080,29 @@ __metadata: languageName: node linkType: hard +"@react-native/community-cli-plugin@npm:0.82.1": + version: 0.82.1 + resolution: "@react-native/community-cli-plugin@npm:0.82.1" + dependencies: + "@react-native/dev-middleware": 0.82.1 + debug: ^4.4.0 + invariant: ^2.2.4 + metro: ^0.83.1 + metro-config: ^0.83.1 + metro-core: ^0.83.1 + semver: ^7.1.3 + peerDependencies: + "@react-native-community/cli": "*" + "@react-native/metro-config": "*" + peerDependenciesMeta: + "@react-native-community/cli": + optional: true + "@react-native/metro-config": + optional: true + checksum: 680aef3270c56a73467ba40f7de416d32fef8b7e484421fccd437f940692eee76382e82eddd0a4749675b855d642fd6c83b147136bdcf03ee84f36ed38f7e0de + languageName: node + linkType: hard + "@react-native/debugger-frontend@npm:0.74.84": version: 0.74.84 resolution: "@react-native/debugger-frontend@npm:0.74.84" @@ -3077,6 +3124,23 @@ __metadata: languageName: node linkType: hard +"@react-native/debugger-frontend@npm:0.82.1": + version: 0.82.1 + resolution: "@react-native/debugger-frontend@npm:0.82.1" + checksum: b767c7586c782a130d3579a1d8c137a8c55361d579028e44a31b220c566ab793a83b256b39eb114a759e07031574cd142cae1bdc1ec80dc02e7a6a191409548e + languageName: node + linkType: hard + +"@react-native/debugger-shell@npm:0.82.1": + version: 0.82.1 + resolution: "@react-native/debugger-shell@npm:0.82.1" + dependencies: + cross-spawn: ^7.0.6 + fb-dotslash: 0.5.8 + checksum: 9b4ec7f413d5e776a7361f1a5e8ecc7dbc8b56c7fc119fec1895f4821071a7b95b2a3db0cef016614a1eb50288df0282c7f7cf5944e3934bc8694529556f44e8 + languageName: node + linkType: hard + "@react-native/dev-middleware@npm:0.74.84": version: 0.74.84 resolution: "@react-native/dev-middleware@npm:0.74.84" @@ -3136,6 +3200,26 @@ __metadata: languageName: node linkType: hard +"@react-native/dev-middleware@npm:0.82.1": + version: 0.82.1 + resolution: "@react-native/dev-middleware@npm:0.82.1" + dependencies: + "@isaacs/ttlcache": ^1.4.1 + "@react-native/debugger-frontend": 0.82.1 + "@react-native/debugger-shell": 0.82.1 + chrome-launcher: ^0.15.2 + chromium-edge-launcher: ^0.2.0 + connect: ^3.6.5 + debug: ^4.4.0 + invariant: ^2.2.4 + nullthrows: ^1.1.1 + open: ^7.0.3 + serve-static: ^1.16.2 + ws: ^6.2.3 + checksum: 0fed27cb7d7bd9e2e3b9cd20776000ec730ea6672779ccd971e831c67a4b25adcda9d82e0042d3f37e1311736add0d4bb51519c463ea81a11565a2bac1cee68c + languageName: node + linkType: hard + "@react-native/eslint-config@npm:0.81.0": version: 0.81.0 resolution: "@react-native/eslint-config@npm:0.81.0" @@ -3187,6 +3271,13 @@ __metadata: languageName: node linkType: hard +"@react-native/gradle-plugin@npm:0.82.1": + version: 0.82.1 + resolution: "@react-native/gradle-plugin@npm:0.82.1" + checksum: 7e7e2d768a8ff599dba5ef7b0a417e1d14a032a3344cc1e57852d4ebee1587dc877f83ae9dd4beae3b27fe2389d235227df12bd8aaa9be8b6ef1c7784419e0de + languageName: node + linkType: hard + "@react-native/js-polyfills@npm:0.74.84": version: 0.74.84 resolution: "@react-native/js-polyfills@npm:0.74.84" @@ -3215,6 +3306,13 @@ __metadata: languageName: node linkType: hard +"@react-native/js-polyfills@npm:0.82.1": + version: 0.82.1 + resolution: "@react-native/js-polyfills@npm:0.82.1" + checksum: 271d5bcff95d3867237ae4ec4745247c7048ea950912b3c31c8bbffd801c714509294b04d176c8121389788a192680f482b21e99ab24c9b2dcbce37acfdeaa5f + languageName: node + linkType: hard + "@react-native/metro-babel-transformer@npm:0.74.84": version: 0.74.84 resolution: "@react-native/metro-babel-transformer@npm:0.74.84" @@ -3276,6 +3374,20 @@ __metadata: languageName: node linkType: hard +"@react-native/normalize-colors@npm:0.82.1": + version: 0.82.1 + resolution: "@react-native/normalize-colors@npm:0.82.1" + checksum: d180cc6591989a3d490ad4454d63a19ec9be796314632adb29051515eb31e98fbdd12903c00750d4ce023306e159a2498867bf6f25bcf11a8ed48e5486482947 + languageName: node + linkType: hard + +"@react-native/normalize-colors@npm:^0.74.1": + version: 0.74.89 + resolution: "@react-native/normalize-colors@npm:0.74.89" + checksum: df62772f029dd132d3061a8ee7f90b6aaf5c525bb7e33c22908249daffa42995cddd91adc790ec9ce701636c14eeafc1809a9d0d879e3f8c71c9f340145abfce + languageName: node + linkType: hard + "@react-native/typescript-config@npm:0.81.0": version: 0.81.0 resolution: "@react-native/typescript-config@npm:0.81.0" @@ -3334,6 +3446,23 @@ __metadata: languageName: node linkType: hard +"@react-native/virtualized-lists@npm:0.82.1": + version: 0.82.1 + resolution: "@react-native/virtualized-lists@npm:0.82.1" + dependencies: + invariant: ^2.2.4 + nullthrows: ^1.1.1 + peerDependencies: + "@types/react": ^19.1.1 + react: "*" + react-native: "*" + peerDependenciesMeta: + "@types/react": + optional: true + checksum: 4961af57d477f16c1b7e0b584e9f54cef876bbb738b3e628b5dfc8ddc157f83bbe1b3e9d8a0c426f41018cdec5d1073f2ffe23b830e288c7d8b730753700608a + languageName: node + linkType: hard + "@react-navigation/core@npm:^6.4.17": version: 6.4.17 resolution: "@react-navigation/core@npm:6.4.17" @@ -4038,6 +4167,25 @@ __metadata: languageName: node linkType: hard +"@types/react-dom@npm:^19.2.2": + version: 19.2.2 + resolution: "@types/react-dom@npm:19.2.2" + peerDependencies: + "@types/react": ^19.2.0 + checksum: a9e16d59f89b2794a3b062766de2eedf98cf66e59de7560de5beb95fb8742161b2dc4751530380c38d51320bc99b8a1d66fa113cee9b5d0f138ef6fb49fb4ce9 + languageName: node + linkType: hard + +"@types/react-native-web@npm:^0.19.2": + version: 0.19.2 + resolution: "@types/react-native-web@npm:0.19.2" + dependencies: + "@types/react": "*" + react-native: "*" + checksum: d6c6bbaa4610dbc1588205b2a936d88938f3d24e6b6a79d238192ff1cf1dc1865864c9c5e00d500c108f0324b6464c3903e5b5aba9e69fc1ab5638ec81b0f163 + languageName: node + linkType: hard + "@types/react-reconciler@npm:^0.28.9": version: 0.28.9 resolution: "@types/react-reconciler@npm:0.28.9" @@ -4714,6 +4862,7 @@ __metadata: "@tensorflow/tfjs-vis": ^1.5.1 "@types/node": ^20.14.7 "@types/react": ^18.2.6 + "@types/react-dom": ^19.2.2 "@types/react-test-renderer": ^18.0.0 "@types/three": 0.172.0 "@webgpu/types": 0.1.65 @@ -4726,12 +4875,14 @@ __metadata: jest: ^29.6.3 prettier: 2.8.8 react: 19.1.0 + react-dom: 19.1.0 react-native: 0.81.4 react-native-gesture-handler: ^2.28.0 react-native-macos: ^0.79.0 react-native-reanimated: 3.19.1 react-native-safe-area-context: ^5.4.0 react-native-test-app: 4.4.10 + react-native-web: ^0.21.2 react-native-wgpu: "*" react-test-renderer: 18.2.0 teapot: ^1.0.0 @@ -5280,6 +5431,15 @@ __metadata: languageName: node linkType: hard +"babel-plugin-syntax-hermes-parser@npm:0.32.0": + version: 0.32.0 + resolution: "babel-plugin-syntax-hermes-parser@npm:0.32.0" + dependencies: + hermes-parser: 0.32.0 + checksum: ec76abeefabf940e2d571db3b47d022a9be7602286133291e8e047d4855af6a8afc079e4631bc9a56209d751fad54b5199932a55753b1e2b56a719d20e2d5065 + languageName: node + linkType: hard + "babel-plugin-transform-flow-enums@npm:^0.0.2": version: 0.0.2 resolution: "babel-plugin-transform-flow-enums@npm:0.0.2" @@ -6125,6 +6285,15 @@ __metadata: languageName: node linkType: hard +"cross-fetch@npm:^3.1.5": + version: 3.2.0 + resolution: "cross-fetch@npm:3.2.0" + dependencies: + node-fetch: ^2.7.0 + checksum: 8ded5ea35f705e81e569e7db244a3f96e05e95996ff51877c89b0c1ec1163c76bb5dad77d0f8fba6bb35a0abacb36403d7271dc586d8b1f636110ee7a8d959fd + languageName: node + linkType: hard + "cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" @@ -6146,6 +6315,15 @@ __metadata: languageName: node linkType: hard +"css-in-js-utils@npm:^3.1.0": + version: 3.1.0 + resolution: "css-in-js-utils@npm:3.1.0" + dependencies: + hyphenate-style-name: ^1.0.3 + checksum: 066318e918c04a5e5bce46b38fe81052ea6ac051bcc6d3c369a1d59ceb1546cb2b6086901ab5d22be084122ee3732169996a3dfb04d3406eaee205af77aec61b + languageName: node + linkType: hard + "csstype@npm:^3.0.2": version: 3.1.3 resolution: "csstype@npm:3.1.3" @@ -7843,6 +8021,15 @@ __metadata: languageName: node linkType: hard +"fb-dotslash@npm:0.5.8": + version: 0.5.8 + resolution: "fb-dotslash@npm:0.5.8" + bin: + dotslash: bin/dotslash + checksum: 5678efe96898294e41c983cb8ea28952539566df5f8bfd2913e8e146425d7d9999d2c458bb4f3e0b07b36b5bcd23cada0868d94509c8b2d4b17de8bf0641775a + languageName: node + linkType: hard + "fb-watchman@npm:^2.0.0": version: 2.0.2 resolution: "fb-watchman@npm:2.0.2" @@ -7852,6 +8039,13 @@ __metadata: languageName: node linkType: hard +"fbjs-css-vars@npm:^1.0.0": + version: 1.0.2 + resolution: "fbjs-css-vars@npm:1.0.2" + checksum: 72baf6d22c45b75109118b4daecb6c8016d4c83c8c0f23f683f22e9d7c21f32fff6201d288df46eb561e3c7d4bb4489b8ad140b7f56444c453ba407e8bd28511 + languageName: node + linkType: hard + "fbjs@npm:^0.8.12": version: 0.8.18 resolution: "fbjs@npm:0.8.18" @@ -7867,6 +8061,21 @@ __metadata: languageName: node linkType: hard +"fbjs@npm:^3.0.4": + version: 3.0.5 + resolution: "fbjs@npm:3.0.5" + dependencies: + cross-fetch: ^3.1.5 + fbjs-css-vars: ^1.0.0 + loose-envify: ^1.0.0 + object-assign: ^4.1.0 + promise: ^7.1.1 + setimmediate: ^1.0.5 + ua-parser-js: ^1.0.35 + checksum: e609b5b64686bc96495a5c67728ed9b2710b9b3d695c5759c5f5e47c9483d1c323543ac777a86459e3694efc5712c6ce7212e944feb19752867d699568bb0e54 + languageName: node + linkType: hard + "fd-slicer@npm:~1.1.0": version: 1.1.0 resolution: "fd-slicer@npm:1.1.0" @@ -8439,6 +8648,13 @@ __metadata: languageName: node linkType: hard +"hermes-compiler@npm:0.0.0": + version: 0.0.0 + resolution: "hermes-compiler@npm:0.0.0" + checksum: 8b6fc8a64c2fa18c9aa6ddb8831c92253b6a2f10adf7d5d8f361b574f07e91b64f0c44b1370665075c33c17dd71c02fd19422124a3d2aa1717c37006ab12a1f0 + languageName: node + linkType: hard + "hermes-estree@npm:0.19.1": version: 0.19.1 resolution: "hermes-estree@npm:0.19.1" @@ -8607,7 +8823,7 @@ __metadata: languageName: node linkType: hard -"hyphenate-style-name@npm:^1.0.2": +"hyphenate-style-name@npm:^1.0.2, hyphenate-style-name@npm:^1.0.3": version: 1.1.0 resolution: "hyphenate-style-name@npm:1.1.0" checksum: b9ed74e29181d96bd58a2d0e62fc4a19879db591dba268275829ff0ae595fcdf11faafaeaa63330a45c3004664d7db1f0fc7cdb372af8ee4615ed8260302c207 @@ -8744,6 +8960,15 @@ __metadata: languageName: node linkType: hard +"inline-style-prefixer@npm:^7.0.1": + version: 7.0.1 + resolution: "inline-style-prefixer@npm:7.0.1" + dependencies: + css-in-js-utils: ^3.1.0 + checksum: 07a72573dfdac5e08fa18f5ce71d922861716955e230175ac415db227d9ed49443c764356cb407a92f4c85b30ebf39604165260b4dfbf3196b7736d7332c5c06 + languageName: node + linkType: hard + "internal-slot@npm:^1.1.0": version: 1.1.0 resolution: "internal-slot@npm:1.1.0" @@ -10293,6 +10518,13 @@ __metadata: languageName: node linkType: hard +"memoize-one@npm:^6.0.0": + version: 6.0.0 + resolution: "memoize-one@npm:6.0.0" + checksum: f185ea69f7cceae5d1cb596266dcffccf545e8e7b4106ec6aa93b71ab9d16460dd118ac8b12982c55f6d6322fcc1485de139df07eacffaae94888b9b3ad7675f + languageName: node + linkType: hard + "meow@npm:^10.1.3": version: 10.1.5 resolution: "meow@npm:10.1.5" @@ -11345,7 +11577,7 @@ __metadata: languageName: node linkType: hard -"node-fetch@npm:^2.2.0, node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7": +"node-fetch@npm:^2.2.0, node-fetch@npm:^2.6.0, node-fetch@npm:^2.6.1, node-fetch@npm:^2.6.7, node-fetch@npm:^2.7.0": version: 2.7.0 resolution: "node-fetch@npm:2.7.0" dependencies: @@ -11979,6 +12211,13 @@ __metadata: languageName: node linkType: hard +"postcss-value-parser@npm:^4.2.0": + version: 4.2.0 + resolution: "postcss-value-parser@npm:4.2.0" + checksum: 819ffab0c9d51cf0acbabf8996dffbfafbafa57afc0e4c98db88b67f2094cb44488758f06e5da95d7036f19556a4a732525e84289a425f4f6fd8e412a9d7442f + languageName: node + linkType: hard + "preact@npm:~8.2.9": version: 8.2.9 resolution: "preact@npm:8.2.9" @@ -12260,6 +12499,17 @@ __metadata: languageName: node linkType: hard +"react-dom@npm:19.1.0": + version: 19.1.0 + resolution: "react-dom@npm:19.1.0" + dependencies: + scheduler: ^0.26.0 + peerDependencies: + react: ^19.1.0 + checksum: 1d154b6543467095ac269e61ca59db546f34ef76bcdeb90f2dad41d682cd210aae492e70c85010ed5d0a2caea225e9a55139ebc1a615ee85bf197d7f99678cdf + languageName: node + linkType: hard + "react-is@npm:^16.12.0 || ^17.0.0 || ^18.0.0, react-is@npm:^18.0.0, react-is@npm:^18.2.0": version: 18.3.1 resolution: "react-is@npm:18.3.1" @@ -12460,10 +12710,30 @@ __metadata: languageName: node linkType: hard +"react-native-web@npm:^0.21.2": + version: 0.21.2 + resolution: "react-native-web@npm:0.21.2" + dependencies: + "@babel/runtime": ^7.18.6 + "@react-native/normalize-colors": ^0.74.1 + fbjs: ^3.0.4 + inline-style-prefixer: ^7.0.1 + memoize-one: ^6.0.0 + nullthrows: ^1.1.1 + postcss-value-parser: ^4.2.0 + styleq: ^0.1.3 + peerDependencies: + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + checksum: 3d8be3ee2bae2790949683d8002973882538a49d5182bdda2a38739d44f0a5918bf082427ad062c98b71d3585ab9664c406685ceafe2432bb99877188dc9782d + languageName: node + linkType: hard + "react-native-webgpu@workspace:.": version: 0.0.0-use.local resolution: "react-native-webgpu@workspace:." dependencies: + "@types/react-native-web": ^0.19.2 turbo: ^2.1.0 languageName: unknown linkType: soft @@ -12497,6 +12767,7 @@ __metadata: react: 19.1.0 react-native: 0.81.4 react-native-builder-bob: ^0.23.2 + react-native-web: ^0.21.2 rimraf: ^5.0.7 seedrandom: ^3.0.5 teapot: ^1.0.0 @@ -12512,6 +12783,57 @@ __metadata: languageName: unknown linkType: soft +"react-native@npm:*": + version: 0.82.1 + resolution: "react-native@npm:0.82.1" + dependencies: + "@jest/create-cache-key-function": ^29.7.0 + "@react-native/assets-registry": 0.82.1 + "@react-native/codegen": 0.82.1 + "@react-native/community-cli-plugin": 0.82.1 + "@react-native/gradle-plugin": 0.82.1 + "@react-native/js-polyfills": 0.82.1 + "@react-native/normalize-colors": 0.82.1 + "@react-native/virtualized-lists": 0.82.1 + abort-controller: ^3.0.0 + anser: ^1.4.9 + ansi-regex: ^5.0.0 + babel-jest: ^29.7.0 + babel-plugin-syntax-hermes-parser: 0.32.0 + base64-js: ^1.5.1 + commander: ^12.0.0 + flow-enums-runtime: ^0.0.6 + glob: ^7.1.1 + hermes-compiler: 0.0.0 + invariant: ^2.2.4 + jest-environment-node: ^29.7.0 + memoize-one: ^5.0.0 + metro-runtime: ^0.83.1 + metro-source-map: ^0.83.1 + nullthrows: ^1.1.1 + pretty-format: ^29.7.0 + promise: ^8.3.0 + react-devtools-core: ^6.1.5 + react-refresh: ^0.14.0 + regenerator-runtime: ^0.13.2 + scheduler: 0.26.0 + semver: ^7.1.3 + stacktrace-parser: ^0.1.10 + whatwg-fetch: ^3.0.0 + ws: ^6.2.3 + yargs: ^17.6.2 + peerDependencies: + "@types/react": ^19.1.1 + react: ^19.1.1 + peerDependenciesMeta: + "@types/react": + optional: true + bin: + react-native: cli.js + checksum: 16c5945aae14bd8d7113d2751ad558d91a0bc8cfbffd8ad588722635aac404b512be5ef0f5953ce62b9808ae7ddcbefac909575bcf7da013d06146ab0d48f29a + languageName: node + linkType: hard + "react-native@npm:0.81.4": version: 0.81.4 resolution: "react-native@npm:0.81.4" @@ -13059,7 +13381,7 @@ __metadata: languageName: node linkType: hard -"scheduler@npm:0.26.0": +"scheduler@npm:0.26.0, scheduler@npm:^0.26.0": version: 0.26.0 resolution: "scheduler@npm:0.26.0" checksum: c63a9f1c0e5089b537231cff6c11f75455b5c8625ae09535c1d7cd0a1b0c77ceecdd9f1074e5e063da5d8dc11e73e8033dcac3361791088be08a6e60c0283ed9 @@ -13747,6 +14069,13 @@ __metadata: languageName: node linkType: hard +"styleq@npm:^0.1.3": + version: 0.1.3 + resolution: "styleq@npm:0.1.3" + checksum: 14a8d23abd914166a9b4bd04ed753bd91363f0e029ee4a94ec2c7dc37d3213fe01fceee22dc655288da3ae89f5dc01cec42d5e2b58478b0dea33bf5bdf509be1 + languageName: node + linkType: hard + "sudo-prompt@npm:^9.0.0": version: 9.2.1 resolution: "sudo-prompt@npm:9.2.1" @@ -14315,6 +14644,15 @@ __metadata: languageName: node linkType: hard +"ua-parser-js@npm:^1.0.35": + version: 1.0.41 + resolution: "ua-parser-js@npm:1.0.41" + bin: + ua-parser-js: script/cli.js + checksum: a57c258ea3a242ade7601460ddf9a7e990d8d8bffc15df2ca87057a81993ca19f5045432c744d07bf2d9f280665d84aebb08630c5af5bea3922fdbe8f6fe6cb0 + languageName: node + linkType: hard + "unbox-primitive@npm:^1.1.0": version: 1.1.0 resolution: "unbox-primitive@npm:1.1.0"