react-native-wgpu 0.1.7 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/android/cpp/cpp-adapter.cpp +13 -35
  2. package/android/src/main/java/com/webgpu/WebGPUModule.java +0 -37
  3. package/android/src/main/java/com/webgpu/WebGPUView.java +0 -1
  4. package/android/src/main/java/com/webgpu/WebGPUViewPackage.java +1 -1
  5. package/cpp/rnwgpu/RNWebGPUManager.h +2 -2
  6. package/cpp/rnwgpu/SurfaceRegistry.h +148 -13
  7. package/cpp/rnwgpu/api/Canvas.h +15 -15
  8. package/cpp/rnwgpu/api/GPUCanvasContext.cpp +81 -23
  9. package/cpp/rnwgpu/api/GPUCanvasContext.h +16 -7
  10. package/cpp/rnwgpu/api/GPUDevice.cpp +6 -3
  11. package/cpp/rnwgpu/api/OffscreenSurface.h +49 -0
  12. package/cpp/rnwgpu/api/RNWebGPU.h +21 -10
  13. package/ios/MetalView.mm +11 -0
  14. package/ios/SurfaceUtils.h +2 -0
  15. package/ios/SurfaceUtils.mm +16 -4
  16. package/ios/WebGPUModule.mm +8 -25
  17. package/ios/WebGPUView.mm +9 -6
  18. package/lib/commonjs/Canvas.js +79 -18
  19. package/lib/commonjs/Canvas.js.map +1 -1
  20. package/lib/commonjs/Offscreen.js +124 -0
  21. package/lib/commonjs/Offscreen.js.map +1 -0
  22. package/lib/commonjs/index.js +12 -0
  23. package/lib/commonjs/index.js.map +1 -1
  24. package/lib/commonjs/utils.js +26 -15
  25. package/lib/commonjs/utils.js.map +1 -1
  26. package/lib/module/Canvas.js +80 -19
  27. package/lib/module/Canvas.js.map +1 -1
  28. package/lib/module/Offscreen.js +117 -0
  29. package/lib/module/Offscreen.js.map +1 -0
  30. package/lib/module/index.js +1 -0
  31. package/lib/module/index.js.map +1 -1
  32. package/lib/module/utils.js +25 -15
  33. package/lib/module/utils.js.map +1 -1
  34. package/lib/typescript/lib/commonjs/Offscreen.d.ts +32 -0
  35. package/lib/typescript/lib/commonjs/Offscreen.d.ts.map +1 -0
  36. package/lib/typescript/lib/commonjs/utils.d.ts +5 -1
  37. package/lib/typescript/lib/commonjs/utils.d.ts.map +1 -1
  38. package/lib/typescript/lib/module/Canvas.d.ts.map +1 -1
  39. package/lib/typescript/lib/module/Offscreen.d.ts +31 -0
  40. package/lib/typescript/lib/module/Offscreen.d.ts.map +1 -0
  41. package/lib/typescript/lib/module/index.d.ts +1 -0
  42. package/lib/typescript/lib/module/utils.d.ts +5 -1
  43. package/lib/typescript/lib/module/utils.d.ts.map +1 -1
  44. package/lib/typescript/src/Canvas.d.ts +6 -4
  45. package/lib/typescript/src/Canvas.d.ts.map +1 -1
  46. package/lib/typescript/src/Offscreen.d.ts +41 -0
  47. package/lib/typescript/src/Offscreen.d.ts.map +1 -0
  48. package/lib/typescript/src/__tests__/setup.d.ts +5 -4
  49. package/lib/typescript/src/__tests__/setup.d.ts.map +1 -1
  50. package/lib/typescript/src/index.d.ts +1 -0
  51. package/lib/typescript/src/index.d.ts.map +1 -1
  52. package/lib/typescript/src/utils.d.ts +6 -3
  53. package/lib/typescript/src/utils.d.ts.map +1 -1
  54. package/libs/android/arm64-v8a/libwebgpu_dawn.so +0 -0
  55. package/libs/android/armeabi-v7a/libwebgpu_dawn.so +0 -0
  56. package/libs/android/x86/libwebgpu_dawn.so +0 -0
  57. package/libs/android/x86_64/libwebgpu_dawn.so +0 -0
  58. package/libs/apple/arm64_iphoneos/libwebgpu_dawn.a +0 -0
  59. package/libs/apple/arm64_iphonesimulator/libwebgpu_dawn.a +0 -0
  60. package/libs/apple/arm64_xros/libwebgpu_dawn.a +0 -0
  61. package/libs/apple/arm64_xrsimulator/libwebgpu_dawn.a +0 -0
  62. package/libs/apple/{libwebgpu_dawn.a → iphonesimulator/libwebgpu_dawn.a} +0 -0
  63. package/libs/apple/libwebgpu_dawn.xcframework/Info.plist +45 -0
  64. package/libs/apple/libwebgpu_dawn.xcframework/ios-arm64/libwebgpu_dawn.a +0 -0
  65. package/libs/apple/libwebgpu_dawn.xcframework/ios-arm64_x86_64-simulator/libwebgpu_dawn.a +0 -0
  66. package/libs/apple/{libwebgpu_dawn_macosx.xcframework → libwebgpu_dawn.xcframework}/macos-arm64_x86_64/libwebgpu_dawn.a +0 -0
  67. package/libs/apple/{libwebgpu_dawn_visionos.xcframework → libwebgpu_dawn.xcframework}/xros-arm64/libwebgpu_dawn.a +0 -0
  68. package/libs/apple/{x86_64_xrsimulator → libwebgpu_dawn.xcframework/xros-arm64-simulator}/libwebgpu_dawn.a +0 -0
  69. package/libs/apple/universal_macosx/libwebgpu_dawn.a +0 -0
  70. package/libs/apple/x86_64_iphonesimulator/libwebgpu_dawn.a +0 -0
  71. package/package.json +1 -1
  72. package/react-native-wgpu.podspec +2 -8
  73. package/src/Canvas.tsx +101 -37
  74. package/src/Offscreen.ts +164 -0
  75. package/src/__tests__/ExternalTexture.spec.ts +4 -4
  76. package/src/__tests__/Texture.spec.ts +8 -2
  77. package/src/__tests__/demos/ABuffer.spec.ts +17 -12
  78. package/src/__tests__/demos/Blur.spec.ts +2 -1
  79. package/src/__tests__/demos/Cube.spec.ts +16 -12
  80. package/src/__tests__/demos/FractalCube.spec.ts +6 -5
  81. package/src/__tests__/demos/OcclusionQuery.spec.ts +3 -3
  82. package/src/__tests__/demos/RenderBundles.spec.ts +4 -3
  83. package/src/__tests__/demos/Triangle.spec.ts +27 -9
  84. package/src/__tests__/setup.ts +25 -10
  85. package/src/index.tsx +1 -0
  86. package/src/utils.ts +28 -18
  87. package/lib/typescript/src/__tests__/components/DrawingContext.d.ts +0 -12
  88. package/lib/typescript/src/__tests__/components/DrawingContext.d.ts.map +0 -1
  89. package/libs/apple/libwebgpu_dawn_macosx.xcframework/Info.plist +0 -28
  90. package/libs/apple/libwebgpu_dawn_visionos.a +0 -0
  91. package/libs/apple/libwebgpu_dawn_visionos.xcframework/Info.plist +0 -44
  92. package/libs/apple/libwebgpu_dawn_visionos.xcframework/xros-arm64_x86_64-simulator/libwebgpu_dawn_visionos.a +0 -0
  93. package/src/__tests__/components/DrawingContext.ts +0 -11
@@ -1,4 +1,5 @@
1
1
  export * from "./Canvas";
2
+ export * from "./Offscreen";
2
3
  export * from "./WebGPUViewNativeComponent";
3
4
  export * from "./utils";
4
5
  export { default as WebGPUModule } from "./NativeWebGPUModule";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAGA,cAAc,UAAU,CAAC;AACzB,cAAc,6BAA6B,CAAC;AAC5C,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/index.tsx"],"names":[],"mappings":"AAGA,cAAc,UAAU,CAAC;AACzB,cAAc,aAAa,CAAC;AAC5B,cAAc,6BAA6B,CAAC;AAC5C,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,OAAO,IAAI,YAAY,EAAE,MAAM,sBAAsB,CAAC"}
@@ -1,7 +1,10 @@
1
- import type { DependencyList } from "react";
2
- import type { CanvasRef } from "./Canvas";
1
+ import type { RNCanvasContext, CanvasRef } from "./Canvas";
3
2
  type Unsubscribe = () => void;
4
3
  export declare const warnIfNotHardwareAccelerated: (adapter: GPUAdapter) => void;
5
- export declare const useCanvasEffect: (effect: () => void | Unsubscribe | Promise<void | Unsubscribe>, deps?: DependencyList) => import("react").RefObject<CanvasRef>;
4
+ export declare const useGPUContext: () => {
5
+ ref: import("react").RefObject<CanvasRef>;
6
+ context: RNCanvasContext | null;
7
+ };
8
+ export declare const useCanvasEffect: (effect: () => void | Unsubscribe | Promise<Unsubscribe | void> | Promise<void>) => import("react").RefObject<CanvasRef>;
6
9
  export {};
7
10
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAG5C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C,KAAK,WAAW,GAAG,MAAM,IAAI,CAAC;AAE9B,eAAO,MAAM,4BAA4B,YAAa,UAAU,SAM/D,CAAC;AAEF,eAAO,MAAM,eAAe,WAClB,MAAM,IAAI,GAAG,WAAW,GAAG,OAAO,CAAC,IAAI,GAAG,WAAW,CAAC,SACxD,cAAc,yCAwBrB,CAAC"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../../src/utils.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE3D,KAAK,WAAW,GAAG,MAAM,IAAI,CAAC;AAE9B,eAAO,MAAM,4BAA4B,YAAa,UAAU,SAM/D,CAAC;AAEF,eAAO,MAAM,aAAa;;;CAOzB,CAAC;AAEF,eAAO,MAAM,eAAe,WAClB,MACJ,IAAI,GACJ,WAAW,GACX,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,GAC3B,OAAO,CAAC,IAAI,CAAC,yCAuBlB,CAAC"}
Binary file
@@ -4,6 +4,36 @@
4
4
  <dict>
5
5
  <key>AvailableLibraries</key>
6
6
  <array>
7
+ <dict>
8
+ <key>BinaryPath</key>
9
+ <string>libwebgpu_dawn.a</string>
10
+ <key>LibraryIdentifier</key>
11
+ <string>xros-arm64</string>
12
+ <key>LibraryPath</key>
13
+ <string>libwebgpu_dawn.a</string>
14
+ <key>SupportedArchitectures</key>
15
+ <array>
16
+ <string>arm64</string>
17
+ </array>
18
+ <key>SupportedPlatform</key>
19
+ <string>xros</string>
20
+ </dict>
21
+ <dict>
22
+ <key>BinaryPath</key>
23
+ <string>libwebgpu_dawn.a</string>
24
+ <key>LibraryIdentifier</key>
25
+ <string>xros-arm64-simulator</string>
26
+ <key>LibraryPath</key>
27
+ <string>libwebgpu_dawn.a</string>
28
+ <key>SupportedArchitectures</key>
29
+ <array>
30
+ <string>arm64</string>
31
+ </array>
32
+ <key>SupportedPlatform</key>
33
+ <string>xros</string>
34
+ <key>SupportedPlatformVariant</key>
35
+ <string>simulator</string>
36
+ </dict>
7
37
  <dict>
8
38
  <key>BinaryPath</key>
9
39
  <string>libwebgpu_dawn.a</string>
@@ -35,6 +65,21 @@
35
65
  <key>SupportedPlatform</key>
36
66
  <string>ios</string>
37
67
  </dict>
68
+ <dict>
69
+ <key>BinaryPath</key>
70
+ <string>libwebgpu_dawn.a</string>
71
+ <key>LibraryIdentifier</key>
72
+ <string>macos-arm64_x86_64</string>
73
+ <key>LibraryPath</key>
74
+ <string>libwebgpu_dawn.a</string>
75
+ <key>SupportedArchitectures</key>
76
+ <array>
77
+ <string>arm64</string>
78
+ <string>x86_64</string>
79
+ </array>
80
+ <key>SupportedPlatform</key>
81
+ <string>macos</string>
82
+ </dict>
38
83
  </array>
39
84
  <key>CFBundlePackageType</key>
40
85
  <string>XFWK</string>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-wgpu",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "React Native WebGPU",
5
5
  "main": "lib/commonjs/index",
6
6
  "module": "lib/module/index",
@@ -15,17 +15,11 @@ Pod::Spec.new do |s|
15
15
  s.source = { :git => "https://github.com/wcandillon/react-native-webgpu.git", :tag => "#{s.version}" }
16
16
 
17
17
  s.source_files = [
18
- "ios/**/*.{h,c,cc,cpp,m,mm,swift}",
18
+ "ios/**/*.{h,c,cc,cpp,m,mm,swift}",
19
19
  "cpp/**/*.{h,cpp}"
20
20
  ]
21
21
 
22
- s.ios.vendored_frameworks = [
23
- 'libs/apple/libwebgpu_dawn.xcframework',
24
- ]
25
-
26
- s.visionos.vendored_frameworks = [
27
- 'libs/apple/libwebgpu_dawn_visionos.xcframework',
28
- ]
22
+ s.vendored_frameworks = 'libs/apple/libwebgpu_dawn.xcframework'
29
23
 
30
24
  s.pod_target_xcconfig = {
31
25
  'HEADER_SEARCH_PATHS' => '$(PODS_TARGET_SRCROOT)/cpp',
package/src/Canvas.tsx CHANGED
@@ -1,8 +1,17 @@
1
- import type { ViewProps } from "react-native";
2
- import { forwardRef, useEffect, useImperativeHandle, useState } from "react";
1
+ import type { ViewProps, LayoutChangeEvent } from "react-native";
2
+ import { View } from "react-native";
3
+ import {
4
+ forwardRef,
5
+ useEffect,
6
+ useImperativeHandle,
7
+ useRef,
8
+ useState,
9
+ useLayoutEffect,
10
+ useCallback,
11
+ } from "react";
12
+ import type { RefObject } from "react";
3
13
 
4
14
  import WebGPUNativeView from "./WebGPUViewNativeComponent";
5
- import WebGPUNativeModule from "./NativeWebGPUModule";
6
15
 
7
16
  let CONTEXT_COUNTER = 1;
8
17
  function generateContextId() {
@@ -10,12 +19,16 @@ function generateContextId() {
10
19
  }
11
20
 
12
21
  declare global {
13
- // eslint-disable-next-line no-var
14
- var __WebGPUContextRegistry: Record<number, NativeCanvas>;
15
22
  // eslint-disable-next-line no-var
16
23
  var RNWebGPU: {
17
24
  gpu: GPU;
18
- MakeWebGPUCanvasContext: (nativeCanvas: NativeCanvas) => CanvasContext;
25
+ fabric: boolean;
26
+ getNativeSurface: (contextId: number) => NativeCanvas;
27
+ MakeWebGPUCanvasContext: (
28
+ contextId: number,
29
+ width: number,
30
+ height: number,
31
+ ) => RNCanvasContext;
19
32
  DecodeToUTF8: (buffer: NodeJS.ArrayBufferView | ArrayBuffer) => string;
20
33
  createImageBitmap: typeof createImageBitmap;
21
34
  };
@@ -31,45 +44,96 @@ export interface NativeCanvas {
31
44
  clientHeight: number;
32
45
  }
33
46
 
34
- global.__WebGPUContextRegistry = {};
35
- const WebGPUContextRegistry = global.__WebGPUContextRegistry;
36
-
37
- type CanvasContext = GPUCanvasContext & {
47
+ export type RNCanvasContext = GPUCanvasContext & {
38
48
  present: () => void;
39
49
  };
40
50
 
41
51
  export interface CanvasRef {
42
- getContext(contextName: "webgpu"): CanvasContext | null;
52
+ getContext(contextName: "webgpu"): RNCanvasContext | null;
43
53
  getNativeSurface: () => NativeCanvas;
54
+ whenReady: (callback: () => void) => void;
44
55
  }
45
56
 
46
- export const Canvas = forwardRef<CanvasRef, ViewProps>((props, ref) => {
47
- const [contextId, _] = useState(() => generateContextId());
57
+ interface Size {
58
+ width: number;
59
+ height: number;
60
+ }
48
61
 
49
- useImperativeHandle(ref, () => ({
50
- getNativeSurface: () => {
51
- WebGPUNativeModule.createSurfaceContext(contextId);
52
- return WebGPUContextRegistry[contextId];
53
- },
54
- getContext(contextName: "webgpu"): CanvasContext | null {
55
- if (contextName !== "webgpu") {
56
- throw new Error(`[WebGPU] Unsupported context: ${contextName}`);
57
- }
58
- WebGPUNativeModule.createSurfaceContext(contextId);
59
- const nativeSurface = WebGPUContextRegistry[contextId];
60
- if (!nativeSurface) {
61
- return null;
62
+ const useSizeFabric = (ref: RefObject<View>) => {
63
+ const [size, setSize] = useState<null | Size>(null);
64
+ useLayoutEffect(() => {
65
+ if (!ref.current) {
66
+ throw new Error("Canvas ref is null");
67
+ }
68
+ ref.current.measureInWindow((_x, _y, width, height) => {
69
+ setSize({ width, height });
70
+ });
71
+ }, [ref]);
72
+ return { size, onLayout: undefined };
73
+ };
74
+
75
+ const useSizePaper = (_ref: RefObject<View>) => {
76
+ const [size, setSize] = useState<null | Size>(null);
77
+ const onLayout = useCallback<(event: LayoutChangeEvent) => void>(
78
+ ({
79
+ nativeEvent: {
80
+ layout: { width, height },
81
+ },
82
+ }) => {
83
+ if (size === null) {
84
+ setSize({ width, height });
62
85
  }
63
- const ctx = RNWebGPU.MakeWebGPUCanvasContext(nativeSurface);
64
- return ctx;
65
86
  },
66
- }));
67
-
68
- useEffect(() => {
69
- return () => {
70
- delete WebGPUContextRegistry[contextId];
71
- };
72
- }, [contextId]);
87
+ [size],
88
+ );
89
+ return { size, onLayout };
90
+ };
73
91
 
74
- return <WebGPUNativeView {...props} contextId={contextId} />;
75
- });
92
+ export const Canvas = forwardRef<CanvasRef, ViewProps>(
93
+ ({ onLayout: _onLayout, ...props }, ref) => {
94
+ const viewRef = useRef(null);
95
+ const FABRIC = RNWebGPU.fabric;
96
+ const useSize = FABRIC ? useSizeFabric : useSizePaper;
97
+ const [contextId, _] = useState(() => generateContextId());
98
+ const cb = useRef<() => void>();
99
+ const { size, onLayout } = useSize(viewRef);
100
+ useEffect(() => {
101
+ if (size && cb.current) {
102
+ cb.current();
103
+ }
104
+ }, [size]);
105
+ useImperativeHandle(ref, () => ({
106
+ getNativeSurface: () => {
107
+ if (size === null) {
108
+ throw new Error("[WebGPU] Canvas size is not available yet");
109
+ }
110
+ return RNWebGPU.getNativeSurface(contextId);
111
+ },
112
+ whenReady(callback: () => void) {
113
+ if (size === null) {
114
+ cb.current = callback;
115
+ } else {
116
+ callback();
117
+ }
118
+ },
119
+ getContext(contextName: "webgpu"): RNCanvasContext | null {
120
+ if (contextName !== "webgpu") {
121
+ throw new Error(`[WebGPU] Unsupported context: ${contextName}`);
122
+ }
123
+ if (size === null) {
124
+ throw new Error("[WebGPU] Canvas size is not available yet");
125
+ }
126
+ return RNWebGPU.MakeWebGPUCanvasContext(
127
+ contextId,
128
+ size.width,
129
+ size.height,
130
+ );
131
+ },
132
+ }));
133
+ return (
134
+ <View ref={viewRef} onLayout={onLayout} {...props}>
135
+ <WebGPUNativeView style={{ flex: 1 }} contextId={contextId} />
136
+ </View>
137
+ );
138
+ },
139
+ );
@@ -0,0 +1,164 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ export class GPUOffscreenCanvas implements OffscreenCanvas {
3
+ width: number;
4
+ height: number;
5
+ oncontextlost: ((this: OffscreenCanvas, ev: Event) => any) | null = null;
6
+ oncontextrestored: ((this: OffscreenCanvas, ev: Event) => any) | null = null;
7
+
8
+ private context: GPUOffscreenCanvasContext;
9
+
10
+ constructor(width: number, height: number) {
11
+ this.width = width;
12
+ this.height = height;
13
+ this.context = new GPUOffscreenCanvasContext(this);
14
+ }
15
+
16
+ convertToBlob(_options?: ImageEncodeOptions): Promise<Blob> {
17
+ // Implementation for converting the canvas content to a Blob
18
+ throw new Error("Method not implemented.");
19
+ }
20
+
21
+ // Overloaded method signatures
22
+ getContext(
23
+ contextId: "2d",
24
+ options?: any,
25
+ ): OffscreenCanvasRenderingContext2D | null;
26
+ getContext(
27
+ contextId: "bitmaprenderer",
28
+ options?: any,
29
+ ): ImageBitmapRenderingContext | null;
30
+ getContext(contextId: "webgl", options?: any): WebGLRenderingContext | null;
31
+ getContext(contextId: "webgl2", options?: any): WebGL2RenderingContext | null;
32
+ getContext(
33
+ contextId: OffscreenRenderingContextId,
34
+ options?: any,
35
+ ): OffscreenRenderingContext | null;
36
+ getContext(contextId: "webgpu"): GPUOffscreenCanvasContext | null;
37
+ getContext(
38
+ contextId: unknown,
39
+ _options?: any,
40
+ ): OffscreenRenderingContext | GPUOffscreenCanvasContext | null {
41
+ if (contextId === "webgpu") {
42
+ return this.context;
43
+ }
44
+ // Implement other context types if necessary
45
+ return null;
46
+ }
47
+
48
+ transferToImageBitmap(): ImageBitmap {
49
+ // Implementation for transferring the canvas content to an ImageBitmap
50
+ throw new Error("Method not implemented.");
51
+ }
52
+
53
+ addEventListener<K extends keyof OffscreenCanvasEventMap>(
54
+ _type: K,
55
+ _listener: (this: OffscreenCanvas, ev: OffscreenCanvasEventMap[K]) => any,
56
+ _options?: boolean | AddEventListenerOptions,
57
+ ): void {
58
+ // Event listener implementation
59
+ throw new Error("Method not implemented.");
60
+ }
61
+
62
+ removeEventListener<K extends keyof OffscreenCanvasEventMap>(
63
+ _type: K,
64
+ _listener: (this: OffscreenCanvas, ev: OffscreenCanvasEventMap[K]) => any,
65
+ _options?: boolean | EventListenerOptions,
66
+ ): void {
67
+ // Remove event listener implementation
68
+ throw new Error("Method not implemented.");
69
+ }
70
+
71
+ dispatchEvent(_event: Event): boolean {
72
+ // Event dispatch implementation
73
+ throw new Error("Method not implemented.");
74
+ }
75
+
76
+ getImageData() {
77
+ const device = this.context.getDevice();
78
+ const texture = this.context.getTexture();
79
+ const commandEncoder = device.createCommandEncoder();
80
+ const bytesPerRow = this.width * 4;
81
+ const buffer = device.createBuffer({
82
+ size: bytesPerRow * this.height,
83
+ usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
84
+ });
85
+ commandEncoder.copyTextureToBuffer(
86
+ { texture: texture },
87
+ { buffer: buffer, bytesPerRow },
88
+ [this.width, this.height],
89
+ );
90
+ device.queue.submit([commandEncoder.finish()]);
91
+
92
+ return buffer.mapAsync(GPUMapMode.READ).then(() => {
93
+ const arrayBuffer = buffer.getMappedRange();
94
+ const uint8Array = new Uint8Array(arrayBuffer);
95
+ const data = Array.from(uint8Array);
96
+ buffer.unmap();
97
+ return {
98
+ data,
99
+ width: this.width,
100
+ height: this.height,
101
+ format: navigator.gpu.getPreferredCanvasFormat(),
102
+ };
103
+ });
104
+ }
105
+ }
106
+
107
+ class GPUOffscreenCanvasContext implements GPUCanvasContext {
108
+ __brand = "GPUCanvasContext" as const;
109
+
110
+ private textureFormat: GPUTextureFormat;
111
+ private texture: GPUTexture | null = null;
112
+ private device: GPUDevice | null = null;
113
+
114
+ constructor(public readonly canvas: OffscreenCanvas) {
115
+ this.textureFormat = navigator.gpu.getPreferredCanvasFormat();
116
+ }
117
+
118
+ present() {
119
+ // Do nothing
120
+ }
121
+
122
+ getDevice() {
123
+ if (!this.device) {
124
+ throw new Error("Device is not configured.");
125
+ }
126
+ return this.device;
127
+ }
128
+
129
+ getTexture() {
130
+ if (!this.texture) {
131
+ throw new Error("Texture is not configured");
132
+ }
133
+ return this.texture;
134
+ }
135
+
136
+ configure(config: GPUCanvasConfiguration) {
137
+ // Configure the canvas context with the device and format
138
+ this.device = config.device;
139
+ this.texture = config.device.createTexture({
140
+ size: [this.canvas.width, this.canvas.height],
141
+ format: this.textureFormat,
142
+ usage:
143
+ GPUTextureUsage.RENDER_ATTACHMENT |
144
+ GPUTextureUsage.COPY_SRC |
145
+ GPUTextureUsage.TEXTURE_BINDING,
146
+ });
147
+ return undefined;
148
+ }
149
+
150
+ unconfigure() {
151
+ // Unconfigure the canvas context
152
+ if (this.texture) {
153
+ this.texture.destroy();
154
+ }
155
+ return undefined;
156
+ }
157
+
158
+ getCurrentTexture(): GPUTexture {
159
+ if (!this.texture) {
160
+ throw new Error("Texture is not configured");
161
+ }
162
+ return this.texture;
163
+ }
164
+ }
@@ -3,7 +3,7 @@ import { checkImage, client, encodeImage } from "./setup";
3
3
  describe("External Textures", () => {
4
4
  it("Simple (1)", async () => {
5
5
  const result = await client.eval(
6
- ({ gpu, device, ctx, urls: { fTexture } }) => {
6
+ ({ gpu, device, ctx, canvas, urls: { fTexture } }) => {
7
7
  const module = device.createShaderModule({
8
8
  label: "our hardcoded textured quad shaders",
9
9
  code: /* wgsl */ `
@@ -131,7 +131,7 @@ describe("External Textures", () => {
131
131
  device.queue.submit([commandBuffer]);
132
132
  }
133
133
  render();
134
- return ctx.getImageData();
134
+ return canvas.getImageData();
135
135
  });
136
136
  });
137
137
  });
@@ -143,7 +143,7 @@ describe("External Textures", () => {
143
143
  });
144
144
  it("Simple (2)", async () => {
145
145
  const result = await client.eval(
146
- ({ gpu, device, ctx, urls: { fTexture } }) => {
146
+ ({ gpu, device, ctx, canvas, urls: { fTexture } }) => {
147
147
  const module = device.createShaderModule({
148
148
  label: "our hardcoded textured quad shaders",
149
149
  code: /* wgsl */ `
@@ -271,7 +271,7 @@ describe("External Textures", () => {
271
271
  device.queue.submit([commandBuffer]);
272
272
  }
273
273
  render();
274
- return ctx.getImageData();
274
+ return canvas.getImageData();
275
275
  });
276
276
  });
277
277
  });
@@ -138,7 +138,13 @@ describe("Texture", () => {
138
138
  });
139
139
  it("Create texture and reads it", async () => {
140
140
  const result = await client.eval(
141
- ({ device, shaders: { triangleVertWGSL, redFragWGSL }, gpu, ctx }) => {
141
+ ({
142
+ device,
143
+ shaders: { triangleVertWGSL, redFragWGSL },
144
+ gpu,
145
+ ctx,
146
+ canvas,
147
+ }) => {
142
148
  const pipeline = device.createRenderPipeline({
143
149
  layout: "auto",
144
150
  vertex: {
@@ -182,7 +188,7 @@ describe("Texture", () => {
182
188
  passEncoder.end();
183
189
 
184
190
  device.queue.submit([commandEncoder.finish()]);
185
- return ctx.getImageData();
191
+ return canvas.getImageData();
186
192
  },
187
193
  );
188
194
  const image = encodeImage(result);
@@ -248,6 +248,7 @@ describe("A Buffer", () => {
248
248
  translucentWGSL,
249
249
  mat4,
250
250
  vec3,
251
+ canvas,
251
252
  }) => {
252
253
  const presentationFormat = gpu.getPreferredCanvasFormat();
253
254
  const settings = {
@@ -579,7 +580,7 @@ describe("A Buffer", () => {
579
580
  }
580
581
 
581
582
  const depthTexture = device.createTexture({
582
- size: [ctx.width, ctx.height],
583
+ size: [ctx.canvas.width, ctx.canvas.height],
583
584
  format: "depth24plus",
584
585
  usage:
585
586
  GPUTextureUsage.RENDER_ATTACHMENT |
@@ -605,12 +606,12 @@ describe("A Buffer", () => {
605
606
  // We want to keep the linked-list buffer size under the maxStorageBufferBindingSize.
606
607
  // Split the frame into enough slices to meet that constraint.
607
608
  const bytesPerline =
608
- ctx.width * averageLayersPerFragment * linkedListElementSize;
609
+ ctx.canvas.width * averageLayersPerFragment * linkedListElementSize;
609
610
  const maxLinesSupported = Math.floor(
610
611
  device.limits.maxStorageBufferBindingSize / bytesPerline,
611
612
  );
612
- const numSlices = Math.ceil(ctx.height / maxLinesSupported);
613
- const sliceHeight = Math.ceil(ctx.height / numSlices);
613
+ const numSlices = Math.ceil(ctx.canvas.height / maxLinesSupported);
614
+ const sliceHeight = Math.ceil(ctx.canvas.height / numSlices);
614
615
  const linkedListBufferSize = sliceHeight * bytesPerline;
615
616
 
616
617
  const linkedListBuffer = device.createBuffer({
@@ -645,13 +646,17 @@ describe("A Buffer", () => {
645
646
  // * numFragments : u32
646
647
  // * data : array<u32>
647
648
  const headsBuffer = device.createBuffer({
648
- size: (1 + ctx.width * sliceHeight) * Uint32Array.BYTES_PER_ELEMENT,
649
+ size:
650
+ (1 + ctx.canvas.width * sliceHeight) *
651
+ Uint32Array.BYTES_PER_ELEMENT,
649
652
  usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
650
653
  label: "headsBuffer",
651
654
  });
652
655
 
653
656
  const headsInitBuffer = device.createBuffer({
654
- size: (1 + ctx.width * sliceHeight) * Uint32Array.BYTES_PER_ELEMENT,
657
+ size:
658
+ (1 + ctx.canvas.width * sliceHeight) *
659
+ Uint32Array.BYTES_PER_ELEMENT,
655
660
  usage: GPUBufferUsage.COPY_SRC,
656
661
  mappedAtCreation: true,
657
662
  label: "headsInitBuffer",
@@ -745,7 +750,7 @@ describe("A Buffer", () => {
745
750
 
746
751
  // Rotates the camera around the origin based on time.
747
752
  function getCameraViewProjMatrix() {
748
- const aspect = ctx.width / ctx.height;
753
+ const aspect = ctx.canvas.width / ctx.canvas.height;
749
754
 
750
755
  const projectionMatrix = mat4.perspective(
751
756
  (2 * Math.PI) / 5,
@@ -776,8 +781,8 @@ describe("A Buffer", () => {
776
781
 
777
782
  new Float32Array(buffer).set(getCameraViewProjMatrix());
778
783
  new Uint32Array(buffer, 16 * Float32Array.BYTES_PER_ELEMENT).set([
779
- averageLayersPerFragment * ctx.width * sliceHeight,
780
- ctx.width,
784
+ averageLayersPerFragment * ctx.canvas.width * sliceHeight,
785
+ ctx.canvas.width,
781
786
  ]);
782
787
 
783
788
  device.queue.writeBuffer(uniformBuffer, 0, buffer);
@@ -810,9 +815,9 @@ describe("A Buffer", () => {
810
815
 
811
816
  const scissorX = 0;
812
817
  const scissorY = slice * sliceHeight;
813
- const scissorWidth = ctx.width;
818
+ const scissorWidth = ctx.canvas.width;
814
819
  const scissorHeight =
815
- Math.min((slice + 1) * sliceHeight, ctx.height) -
820
+ Math.min((slice + 1) * sliceHeight, ctx.canvas.height) -
816
821
  slice * sliceHeight;
817
822
 
818
823
  // Draw the translucent objects
@@ -870,7 +875,7 @@ describe("A Buffer", () => {
870
875
 
871
876
  doDraw();
872
877
 
873
- return ctx.getImageData();
878
+ return canvas.getImageData();
874
879
  },
875
880
  {
876
881
  mesh: teapotMesh,
@@ -133,6 +133,7 @@ describe("Blur", () => {
133
133
  assets: { di3D: imageBitmap },
134
134
  fullscreenTexturedQuadWGSL,
135
135
  blurWGSL,
136
+ canvas,
136
137
  }) => {
137
138
  const tileDim = 128;
138
139
  const batch = [4, 4];
@@ -387,7 +388,7 @@ describe("Blur", () => {
387
388
  device.queue.submit([commandEncoder.finish()]);
388
389
  }
389
390
  frame();
390
- return ctx.getImageData();
391
+ return canvas.getImageData();
391
392
  },
392
393
  { blurWGSL: blur, fullscreenTexturedQuadWGSL: fullscreenTexturedQuad },
393
394
  );