react-native-effects 0.2.0 → 0.3.0
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.
- package/lib/module/components/ShaderView/index.js +92 -25
- package/lib/module/components/ShaderView/index.js.map +1 -1
- package/lib/module/hooks/useWGPUSetup.js +61 -12
- package/lib/module/hooks/useWGPUSetup.js.map +1 -1
- package/lib/module/utils/gpuDevice.js +38 -0
- package/lib/module/utils/gpuDevice.js.map +1 -0
- package/lib/typescript/src/components/ShaderView/index.d.ts.map +1 -1
- package/lib/typescript/src/hooks/useWGPUSetup.d.ts +4 -1
- package/lib/typescript/src/hooks/useWGPUSetup.d.ts.map +1 -1
- package/lib/typescript/src/utils/gpuDevice.d.ts +13 -0
- package/lib/typescript/src/utils/gpuDevice.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/components/ShaderView/index.tsx +100 -29
- package/src/hooks/useWGPUSetup.tsx +68 -20
- package/src/utils/gpuDevice.ts +38 -0
- package/lib/module/utils/initWebGPU.js +0 -40
- package/lib/module/utils/initWebGPU.js.map +0 -1
- package/lib/typescript/src/utils/initWebGPU.d.ts +0 -23
- package/lib/typescript/src/utils/initWebGPU.d.ts.map +0 -1
- package/src/utils/initWebGPU.ts +0 -47
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
import { PixelRatio, StyleSheet } from 'react-native';
|
|
4
|
-
import { Canvas } from 'react-native-webgpu';
|
|
5
|
-
import { useEffect, useRef } from 'react';
|
|
3
|
+
import { AppState, PixelRatio, StyleSheet } from 'react-native';
|
|
4
|
+
import { Canvas, installWebGPU } from 'react-native-webgpu';
|
|
5
|
+
import { useEffect, useRef, useState } from 'react';
|
|
6
6
|
import { createSynchronizable, scheduleOnRuntime } from 'react-native-worklets';
|
|
7
7
|
import { colorToVec4 } from "../../utils/colors.js";
|
|
8
8
|
import { useWGPUSetup } from "../../hooks/useWGPUSetup.js";
|
|
@@ -29,8 +29,22 @@ export default function ShaderView({
|
|
|
29
29
|
const {
|
|
30
30
|
canvasRef,
|
|
31
31
|
runtime,
|
|
32
|
-
resources
|
|
32
|
+
resources,
|
|
33
|
+
onCanvasLayout
|
|
33
34
|
} = useWGPUSetup();
|
|
35
|
+
|
|
36
|
+
// Pause the render loop while the app is backgrounded. The rAF loop otherwise
|
|
37
|
+
// keeps churning frames against a surface that's offscreen (and often
|
|
38
|
+
// transiently invalid), wasting battery/GPU. We only pause on a true
|
|
39
|
+
// 'background' transition — iOS 'inactive' (app switcher peek, notification
|
|
40
|
+
// pulldown) is left running to avoid flicker on brief, foreground interruptions.
|
|
41
|
+
const [appActive, setAppActive] = useState(() => (AppState.currentState ?? 'active') !== 'background');
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const subscription = AppState.addEventListener('change', state => {
|
|
44
|
+
setAppActive(state !== 'background');
|
|
45
|
+
});
|
|
46
|
+
return () => subscription.remove();
|
|
47
|
+
}, []);
|
|
34
48
|
const propsSync = useRef(createSynchronizable(new Float64Array(SYNC_SIZE))).current;
|
|
35
49
|
|
|
36
50
|
// Convert props to flat floats and push to synchronizable
|
|
@@ -78,9 +92,12 @@ export default function ShaderView({
|
|
|
78
92
|
};
|
|
79
93
|
}, [propsSync]);
|
|
80
94
|
|
|
81
|
-
// Start render loop when GPU resources are ready
|
|
95
|
+
// Start render loop when GPU resources are ready and the app is foregrounded.
|
|
96
|
+
// When the app backgrounds, `appActive` flips false and this effect's cleanup
|
|
97
|
+
// tears the loop down (via the `cancelled` token below); on return to the
|
|
98
|
+
// foreground it re-runs and starts a fresh loop.
|
|
82
99
|
useEffect(() => {
|
|
83
|
-
if (!resources) {
|
|
100
|
+
if (!resources || !appActive) {
|
|
84
101
|
return;
|
|
85
102
|
}
|
|
86
103
|
const {
|
|
@@ -98,6 +115,12 @@ export default function ShaderView({
|
|
|
98
115
|
scheduleOnRuntime(runtime, () => {
|
|
99
116
|
'worklet';
|
|
100
117
|
|
|
118
|
+
// Worklet runtimes start without the WebGPU flag constants
|
|
119
|
+
// (GPUBufferUsage, GPUTextureUsage, ...). installWebGPU() captures them
|
|
120
|
+
// into this runtime so they're available below. Idempotent / safe no-op
|
|
121
|
+
// if already installed.
|
|
122
|
+
installWebGPU();
|
|
123
|
+
|
|
101
124
|
// Create pipeline once
|
|
102
125
|
const pipeline = device.createRenderPipeline({
|
|
103
126
|
layout: 'auto',
|
|
@@ -138,15 +161,37 @@ export default function ShaderView({
|
|
|
138
161
|
const uniformData = new Float32Array(UNIFORM_FLOAT_COUNT);
|
|
139
162
|
let accumulatedTime = 0;
|
|
140
163
|
let lastTimestamp = 0;
|
|
164
|
+
let warned = false;
|
|
165
|
+
let bufferDestroyed = false;
|
|
166
|
+
|
|
167
|
+
// Free this loop's uniform buffer when the loop ends (alive=0 / superseded
|
|
168
|
+
// by Fast Refresh / unmount). On a fragmentShader change the effect
|
|
169
|
+
// re-runs and schedules a fresh loop with a new buffer while the device
|
|
170
|
+
// persists, so without this the old buffer leaks every shader swap.
|
|
171
|
+
function destroyBuffer() {
|
|
172
|
+
if (bufferDestroyed) {
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
bufferDestroyed = true;
|
|
176
|
+
try {
|
|
177
|
+
uniformBuffer.destroy();
|
|
178
|
+
} catch {
|
|
179
|
+
// The device may already have been destroyed on unmount — the buffer
|
|
180
|
+
// is gone either way, so there's nothing to recover.
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
141
184
|
function render(timestamp) {
|
|
142
185
|
const props = propsSync.getDirty();
|
|
143
186
|
if (props[IDX_ALIVE] === 0) {
|
|
187
|
+
destroyBuffer();
|
|
144
188
|
return;
|
|
145
189
|
}
|
|
146
190
|
|
|
147
191
|
// This loop was superseded (Fast Refresh / unmount) — bail without
|
|
148
192
|
// scheduling another frame so it can be garbage-collected.
|
|
149
193
|
if (cancelled.getDirty()[0] === 1) {
|
|
194
|
+
destroyBuffer();
|
|
150
195
|
return;
|
|
151
196
|
}
|
|
152
197
|
|
|
@@ -211,23 +256,40 @@ export default function ShaderView({
|
|
|
211
256
|
uniformData[26] = live[2];
|
|
212
257
|
uniformData[27] = live[3];
|
|
213
258
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
259
|
+
|
|
260
|
+
// GPU work can throw when the surface is transiently invalid — the app
|
|
261
|
+
// backgrounded, the view detached, or the device was lost. Swallow the
|
|
262
|
+
// failed frame so the loop survives and recovers when the surface comes
|
|
263
|
+
// back (rather than the worklet crashing or the loop dying silently).
|
|
264
|
+
try {
|
|
265
|
+
device.queue.writeBuffer(uniformBuffer, 0, uniformData);
|
|
266
|
+
const commandEncoder = device.createCommandEncoder();
|
|
267
|
+
const textureView = context.getCurrentTexture().createView();
|
|
268
|
+
const passEncoder = commandEncoder.beginRenderPass({
|
|
269
|
+
colorAttachments: [{
|
|
270
|
+
view: textureView,
|
|
271
|
+
clearValue: transparent ? [0, 0, 0, 0] : [0, 0, 0, 1],
|
|
272
|
+
loadOp: 'clear',
|
|
273
|
+
storeOp: 'store'
|
|
274
|
+
}]
|
|
275
|
+
});
|
|
276
|
+
passEncoder.setPipeline(pipeline);
|
|
277
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
278
|
+
passEncoder.draw(3);
|
|
279
|
+
passEncoder.end();
|
|
280
|
+
device.queue.submit([commandEncoder.finish()]);
|
|
281
|
+
context.present();
|
|
282
|
+
} catch (e) {
|
|
283
|
+
// Warn once to avoid spamming the console every frame on a persistent
|
|
284
|
+
// failure (e.g. a lost device).
|
|
285
|
+
if (!warned) {
|
|
286
|
+
warned = true;
|
|
287
|
+
console.warn('[react-native-effects] render frame failed:', e);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// Always reschedule animated loops, even after a caught failure, so the
|
|
292
|
+
// effect self-heals once the surface is valid again.
|
|
231
293
|
if (!isStatic) {
|
|
232
294
|
requestAnimationFrame(render);
|
|
233
295
|
}
|
|
@@ -237,11 +299,16 @@ export default function ShaderView({
|
|
|
237
299
|
return () => {
|
|
238
300
|
cancelled.setBlocking(() => Float64Array.of(1));
|
|
239
301
|
};
|
|
240
|
-
}, [resources, runtime, propsSync, paramsSynchronizable, fragmentShader, isStatic, transparent]);
|
|
302
|
+
}, [resources, appActive, runtime, propsSync, paramsSynchronizable, fragmentShader, isStatic, transparent]);
|
|
241
303
|
return /*#__PURE__*/_jsx(Canvas, {
|
|
242
304
|
ref: canvasRef,
|
|
305
|
+
transparent: transparent,
|
|
243
306
|
style: [styles.canvas, style],
|
|
244
|
-
...viewProps
|
|
307
|
+
...viewProps,
|
|
308
|
+
onLayout: event => {
|
|
309
|
+
onCanvasLayout(event);
|
|
310
|
+
viewProps.onLayout?.(event);
|
|
311
|
+
}
|
|
245
312
|
});
|
|
246
313
|
}
|
|
247
314
|
const styles = StyleSheet.create({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["PixelRatio","StyleSheet","Canvas","useEffect","useRef","createSynchronizable","scheduleOnRuntime","colorToVec4","useWGPUSetup","TRIANGLE_VERTEX_SHADER","UNIFORM_BUFFER_SIZE","UNIFORM_FLOAT_COUNT","jsx","_jsx","SYNC_SIZE","IDX_SPEED","IDX_PARAMS","IDX_ALIVE","ShaderView","fragmentShader","colors","speed","params","isStatic","transparent","paramsSynchronizable","style","viewProps","canvasRef","runtime","resources","propsSync","Float64Array","current","data","undefined","c0","r","g","b","a","c1","i","setBlocking","prev","device","context","presentationFormat","dpr","get","cancelled","pipeline","createRenderPipeline","layout","vertex","module","createShaderModule","code","entryPoint","fragment","targets","format","primitive","topology","uniformBuffer","createBuffer","size","usage","GPUBufferUsage","UNIFORM","COPY_DST","bindGroup","createBindGroup","getBindGroupLayout","entries","binding","resource","buffer","uniformData","Float32Array","accumulatedTime","lastTimestamp","render","timestamp","props","getDirty","dt","currentSpeed","canvas","width","height","aspect","live","queue","writeBuffer","commandEncoder","createCommandEncoder","textureView","getCurrentTexture","createView","passEncoder","beginRenderPass","colorAttachments","view","clearValue","loadOp","storeOp","setPipeline","setBindGroup","draw","end","submit","finish","present","requestAnimationFrame","of","ref","styles","create","flex"],"sourceRoot":"../../../../src","sources":["components/ShaderView/index.tsx"],"mappings":";;AAAA,SAASA,UAAU,EAAEC,UAAU,QAAQ,cAAc;
|
|
1
|
+
{"version":3,"names":["AppState","PixelRatio","StyleSheet","Canvas","installWebGPU","useEffect","useRef","useState","createSynchronizable","scheduleOnRuntime","colorToVec4","useWGPUSetup","TRIANGLE_VERTEX_SHADER","UNIFORM_BUFFER_SIZE","UNIFORM_FLOAT_COUNT","jsx","_jsx","SYNC_SIZE","IDX_SPEED","IDX_PARAMS","IDX_ALIVE","ShaderView","fragmentShader","colors","speed","params","isStatic","transparent","paramsSynchronizable","style","viewProps","canvasRef","runtime","resources","onCanvasLayout","appActive","setAppActive","currentState","subscription","addEventListener","state","remove","propsSync","Float64Array","current","data","undefined","c0","r","g","b","a","c1","i","setBlocking","prev","device","context","presentationFormat","dpr","get","cancelled","pipeline","createRenderPipeline","layout","vertex","module","createShaderModule","code","entryPoint","fragment","targets","format","primitive","topology","uniformBuffer","createBuffer","size","usage","GPUBufferUsage","UNIFORM","COPY_DST","bindGroup","createBindGroup","getBindGroupLayout","entries","binding","resource","buffer","uniformData","Float32Array","accumulatedTime","lastTimestamp","warned","bufferDestroyed","destroyBuffer","destroy","render","timestamp","props","getDirty","dt","currentSpeed","canvas","width","height","aspect","live","queue","writeBuffer","commandEncoder","createCommandEncoder","textureView","getCurrentTexture","createView","passEncoder","beginRenderPass","colorAttachments","view","clearValue","loadOp","storeOp","setPipeline","setBindGroup","draw","end","submit","finish","present","e","console","warn","requestAnimationFrame","of","ref","styles","onLayout","event","create","flex"],"sourceRoot":"../../../../src","sources":["components/ShaderView/index.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -2,36 +2,44 @@
|
|
|
2
2
|
|
|
3
3
|
import { PixelRatio } from 'react-native';
|
|
4
4
|
import { useCanvasRef } from 'react-native-webgpu';
|
|
5
|
-
import { useEffect, useState } from 'react';
|
|
6
|
-
import { initWebGPU } from "../utils/initWebGPU.js";
|
|
5
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
7
6
|
import { BackgroundRuntime } from "../utils/backgroundRuntime.js";
|
|
7
|
+
import { getSharedGPUDevice } from "../utils/gpuDevice.js";
|
|
8
8
|
export function useWGPUSetup() {
|
|
9
9
|
const canvasRef = useCanvasRef();
|
|
10
10
|
const [resources, setResources] = useState(null);
|
|
11
11
|
const runtime = BackgroundRuntime;
|
|
12
|
+
|
|
13
|
+
// Physical-pixel size the surface is currently configured for. Used to drive
|
|
14
|
+
// resize (and to skip redundant reconfigures when the layout pass reports the
|
|
15
|
+
// same size).
|
|
16
|
+
const configuredSizeRef = useRef(null);
|
|
12
17
|
useEffect(() => {
|
|
13
18
|
let cancelled = false;
|
|
14
19
|
(async () => {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const device = await adapter.requestDevice();
|
|
20
|
-
if (cancelled) {
|
|
20
|
+
// Shared across every ShaderView — see getSharedGPUDevice. Returns null if
|
|
21
|
+
// no adapter is available.
|
|
22
|
+
const device = await getSharedGPUDevice();
|
|
23
|
+
if (!device || cancelled) {
|
|
21
24
|
return;
|
|
22
25
|
}
|
|
23
26
|
const context = canvasRef.current.getContext('webgpu');
|
|
24
27
|
const canvas = context.canvas;
|
|
25
28
|
const dpr = PixelRatio.get();
|
|
26
|
-
|
|
27
|
-
|
|
29
|
+
const width = Math.max(1, Math.round(canvas.width * dpr));
|
|
30
|
+
const height = Math.max(1, Math.round(canvas.height * dpr));
|
|
31
|
+
canvas.width = width;
|
|
32
|
+
canvas.height = height;
|
|
28
33
|
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
|
|
29
34
|
context.configure({
|
|
30
35
|
device,
|
|
31
36
|
format: presentationFormat,
|
|
32
37
|
alphaMode: 'premultiplied'
|
|
33
38
|
});
|
|
34
|
-
|
|
39
|
+
configuredSizeRef.current = {
|
|
40
|
+
width,
|
|
41
|
+
height
|
|
42
|
+
};
|
|
35
43
|
if (!cancelled) {
|
|
36
44
|
setResources({
|
|
37
45
|
device,
|
|
@@ -42,13 +50,54 @@ export function useWGPUSetup() {
|
|
|
42
50
|
})();
|
|
43
51
|
return () => {
|
|
44
52
|
cancelled = true;
|
|
53
|
+
// The device is shared across all ShaderViews and lives for the JS
|
|
54
|
+
// runtime's lifetime, so it must NOT be destroyed here. This view's own
|
|
55
|
+
// GPU resources (the uniform buffer) are torn down by the render loop when
|
|
56
|
+
// it stops; the pipeline/shader modules are released once the loop's
|
|
57
|
+
// worklet closure is garbage-collected.
|
|
45
58
|
};
|
|
46
59
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
47
60
|
}, []);
|
|
61
|
+
|
|
62
|
+
// Resize the drawing buffer and reconfigure the surface whenever the view's
|
|
63
|
+
// layout changes (most notably device rotation). Without this the buffer keeps
|
|
64
|
+
// its original dimensions and the shader renders stretched / wrong-aspect. The
|
|
65
|
+
// render loop reads `canvas.width/height` each frame, so the resolution uniform
|
|
66
|
+
// picks up the new size on the next frame. No-op until GPU resources are ready
|
|
67
|
+
// (initial sizing is handled by the setup effect above).
|
|
68
|
+
const onCanvasLayout = useCallback(event => {
|
|
69
|
+
if (!resources) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const {
|
|
73
|
+
width,
|
|
74
|
+
height
|
|
75
|
+
} = event.nativeEvent.layout;
|
|
76
|
+
const dpr = PixelRatio.get();
|
|
77
|
+
const w = Math.max(1, Math.round(width * dpr));
|
|
78
|
+
const h = Math.max(1, Math.round(height * dpr));
|
|
79
|
+
const prev = configuredSizeRef.current;
|
|
80
|
+
if (prev && prev.width === w && prev.height === h) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const canvas = resources.context.canvas;
|
|
84
|
+
canvas.width = w;
|
|
85
|
+
canvas.height = h;
|
|
86
|
+
resources.context.configure({
|
|
87
|
+
device: resources.device,
|
|
88
|
+
format: resources.presentationFormat,
|
|
89
|
+
alphaMode: 'premultiplied'
|
|
90
|
+
});
|
|
91
|
+
configuredSizeRef.current = {
|
|
92
|
+
width: w,
|
|
93
|
+
height: h
|
|
94
|
+
};
|
|
95
|
+
}, [resources]);
|
|
48
96
|
return {
|
|
49
97
|
canvasRef,
|
|
50
98
|
runtime,
|
|
51
|
-
resources
|
|
99
|
+
resources,
|
|
100
|
+
onCanvasLayout
|
|
52
101
|
};
|
|
53
102
|
}
|
|
54
103
|
//# sourceMappingURL=useWGPUSetup.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["PixelRatio","useCanvasRef","useEffect","
|
|
1
|
+
{"version":3,"names":["PixelRatio","useCanvasRef","useCallback","useEffect","useRef","useState","BackgroundRuntime","getSharedGPUDevice","useWGPUSetup","canvasRef","resources","setResources","runtime","configuredSizeRef","cancelled","device","context","current","getContext","canvas","dpr","get","width","Math","max","round","height","presentationFormat","navigator","gpu","getPreferredCanvasFormat","configure","format","alphaMode","onCanvasLayout","event","nativeEvent","layout","w","h","prev"],"sourceRoot":"../../../src","sources":["hooks/useWGPUSetup.tsx"],"mappings":"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","ignoreList":[]}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
let devicePromise = null;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lazily requests a single GPUDevice shared by every ShaderView.
|
|
7
|
+
*
|
|
8
|
+
* Requesting an adapter + device per view is expensive (real GPU/memory cost and
|
|
9
|
+
* slower init), and a screen that mounts several effects would otherwise spin up
|
|
10
|
+
* N devices on the one background runtime. The promise is cached for the lifetime
|
|
11
|
+
* of the JS runtime so every view awaits the same device.
|
|
12
|
+
*
|
|
13
|
+
* If the device is ever lost, the cache is cleared so the next ShaderView mount
|
|
14
|
+
* requests a fresh one instead of awaiting a dead device forever.
|
|
15
|
+
*/
|
|
16
|
+
export function getSharedGPUDevice() {
|
|
17
|
+
if (devicePromise) {
|
|
18
|
+
return devicePromise;
|
|
19
|
+
}
|
|
20
|
+
const promise = (async () => {
|
|
21
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
22
|
+
if (!adapter) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
const device = await adapter.requestDevice();
|
|
26
|
+
device.lost?.then(() => {
|
|
27
|
+
// Only clear if we're still the cached promise, so we don't clobber a
|
|
28
|
+
// newer device that a later mount may already have requested.
|
|
29
|
+
if (devicePromise === promise) {
|
|
30
|
+
devicePromise = null;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return device;
|
|
34
|
+
})();
|
|
35
|
+
devicePromise = promise;
|
|
36
|
+
return promise;
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=gpuDevice.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["devicePromise","getSharedGPUDevice","promise","adapter","navigator","gpu","requestAdapter","device","requestDevice","lost","then"],"sourceRoot":"../../../src","sources":["utils/gpuDevice.ts"],"mappings":";;AAAA,IAAIA,aAA+C,GAAG,IAAI;;AAE1D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,kBAAkBA,CAAA,EAA8B;EAC9D,IAAID,aAAa,EAAE;IACjB,OAAOA,aAAa;EACtB;EAEA,MAAME,OAAO,GAAG,CAAC,YAAY;IAC3B,MAAMC,OAAO,GAAG,MAAMC,SAAS,CAACC,GAAG,CAACC,cAAc,CAAC,CAAC;IACpD,IAAI,CAACH,OAAO,EAAE;MACZ,OAAO,IAAI;IACb;IAEA,MAAMI,MAAM,GAAG,MAAMJ,OAAO,CAACK,aAAa,CAAC,CAAC;IAC5CD,MAAM,CAACE,IAAI,EAAEC,IAAI,CAAC,MAAM;MACtB;MACA;MACA,IAAIV,aAAa,KAAKE,OAAO,EAAE;QAC7BF,aAAa,GAAG,IAAI;MACtB;IACF,CAAC,CAAC;IACF,OAAOO,MAAM;EACf,CAAC,EAAE,CAAC;EAEJP,aAAa,GAAGE,OAAO;EACvB,OAAOA,OAAO;AAChB","ignoreList":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/ShaderView/index.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAS/C,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EACjC,cAAc,EACd,MAAW,EACX,KAAW,EACX,MAAW,EACX,QAAgB,EAChB,WAAmB,EACnB,oBAAoB,EACpB,KAAK,EACL,GAAG,SAAS,EACb,EAAE,eAAe,+
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/ShaderView/index.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAS/C,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EACjC,cAAc,EACd,MAAW,EACX,KAAW,EACX,MAAW,EACX,QAAgB,EAChB,WAAmB,EACnB,oBAAoB,EACpB,KAAK,EACL,GAAG,SAAS,EACb,EAAE,eAAe,+BAwSjB"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type LayoutChangeEvent } from 'react-native';
|
|
1
2
|
import { type CanvasRef, type RNCanvasContext } from 'react-native-webgpu';
|
|
2
|
-
import type
|
|
3
|
+
import { type WorkletRuntime } from 'react-native-worklets';
|
|
3
4
|
type GPUResources = {
|
|
4
5
|
device: GPUDevice;
|
|
5
6
|
context: RNCanvasContext;
|
|
@@ -9,6 +10,8 @@ type WGPUSetupResult = {
|
|
|
9
10
|
canvasRef: React.RefObject<CanvasRef>;
|
|
10
11
|
runtime: WorkletRuntime;
|
|
11
12
|
resources: GPUResources | null;
|
|
13
|
+
/** Wire to `<Canvas onLayout>` so the surface resizes on rotation/layout change. */
|
|
14
|
+
onCanvasLayout: (event: LayoutChangeEvent) => void;
|
|
12
15
|
};
|
|
13
16
|
export declare function useWGPUSetup(): WGPUSetupResult;
|
|
14
17
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useWGPUSetup.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useWGPUSetup.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useWGPUSetup.d.ts","sourceRoot":"","sources":["../../../../src/hooks/useWGPUSetup.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAClE,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,eAAe,EACrB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAI5D,KAAK,YAAY,GAAG;IAClB,MAAM,EAAE,SAAS,CAAC;IAClB,OAAO,EAAE,eAAe,CAAC;IACzB,kBAAkB,EAAE,gBAAgB,CAAC;CACtC,CAAC;AAOF,KAAK,eAAe,GAAG;IACrB,SAAS,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,OAAO,EAAE,cAAc,CAAC;IACxB,SAAS,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/B,oFAAoF;IACpF,cAAc,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,CAAC;CACpD,CAAC;AAEF,wBAAgB,YAAY,IAAI,eAAe,CA0F9C"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazily requests a single GPUDevice shared by every ShaderView.
|
|
3
|
+
*
|
|
4
|
+
* Requesting an adapter + device per view is expensive (real GPU/memory cost and
|
|
5
|
+
* slower init), and a screen that mounts several effects would otherwise spin up
|
|
6
|
+
* N devices on the one background runtime. The promise is cached for the lifetime
|
|
7
|
+
* of the JS runtime so every view awaits the same device.
|
|
8
|
+
*
|
|
9
|
+
* If the device is ever lost, the cache is cleared so the next ShaderView mount
|
|
10
|
+
* requests a fresh one instead of awaiting a dead device forever.
|
|
11
|
+
*/
|
|
12
|
+
export declare function getSharedGPUDevice(): Promise<GPUDevice | null>;
|
|
13
|
+
//# sourceMappingURL=gpuDevice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gpuDevice.d.ts","sourceRoot":"","sources":["../../../../src/utils/gpuDevice.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,CAwB9D"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-effects",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "WebGPU-powered visual effects running on a background thread in React Native",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
"react-native-builder-bob": "^0.41.0",
|
|
90
90
|
"react-native-gesture-handler": "2.31.1",
|
|
91
91
|
"react-native-reanimated": "4.3.1",
|
|
92
|
-
"react-native-webgpu": "0.5.
|
|
92
|
+
"react-native-webgpu": "0.5.15",
|
|
93
93
|
"react-native-worklets": "0.8.3",
|
|
94
94
|
"release-it": "^20.2.0",
|
|
95
95
|
"turbo": "^2.9.16",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { PixelRatio, StyleSheet } from 'react-native';
|
|
2
|
-
import { Canvas } from 'react-native-webgpu';
|
|
3
|
-
import { useEffect, useRef } from 'react';
|
|
1
|
+
import { AppState, PixelRatio, StyleSheet } from 'react-native';
|
|
2
|
+
import { Canvas, installWebGPU } from 'react-native-webgpu';
|
|
3
|
+
import { useEffect, useRef, useState } from 'react';
|
|
4
4
|
import { createSynchronizable, scheduleOnRuntime } from 'react-native-worklets';
|
|
5
5
|
import { colorToVec4 } from '../../utils/colors';
|
|
6
6
|
import { useWGPUSetup } from '../../hooks/useWGPUSetup';
|
|
@@ -29,7 +29,22 @@ export default function ShaderView({
|
|
|
29
29
|
style,
|
|
30
30
|
...viewProps
|
|
31
31
|
}: ShaderViewProps) {
|
|
32
|
-
const { canvasRef, runtime, resources } = useWGPUSetup();
|
|
32
|
+
const { canvasRef, runtime, resources, onCanvasLayout } = useWGPUSetup();
|
|
33
|
+
|
|
34
|
+
// Pause the render loop while the app is backgrounded. The rAF loop otherwise
|
|
35
|
+
// keeps churning frames against a surface that's offscreen (and often
|
|
36
|
+
// transiently invalid), wasting battery/GPU. We only pause on a true
|
|
37
|
+
// 'background' transition — iOS 'inactive' (app switcher peek, notification
|
|
38
|
+
// pulldown) is left running to avoid flicker on brief, foreground interruptions.
|
|
39
|
+
const [appActive, setAppActive] = useState(
|
|
40
|
+
() => (AppState.currentState ?? 'active') !== 'background'
|
|
41
|
+
);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const subscription = AppState.addEventListener('change', (state) => {
|
|
44
|
+
setAppActive(state !== 'background');
|
|
45
|
+
});
|
|
46
|
+
return () => subscription.remove();
|
|
47
|
+
}, []);
|
|
33
48
|
|
|
34
49
|
const propsSync = useRef(
|
|
35
50
|
createSynchronizable<Float64Array>(new Float64Array(SYNC_SIZE))
|
|
@@ -81,9 +96,12 @@ export default function ShaderView({
|
|
|
81
96
|
};
|
|
82
97
|
}, [propsSync]);
|
|
83
98
|
|
|
84
|
-
// Start render loop when GPU resources are ready
|
|
99
|
+
// Start render loop when GPU resources are ready and the app is foregrounded.
|
|
100
|
+
// When the app backgrounds, `appActive` flips false and this effect's cleanup
|
|
101
|
+
// tears the loop down (via the `cancelled` token below); on return to the
|
|
102
|
+
// foreground it re-runs and starts a fresh loop.
|
|
85
103
|
useEffect(() => {
|
|
86
|
-
if (!resources) {
|
|
104
|
+
if (!resources || !appActive) {
|
|
87
105
|
return;
|
|
88
106
|
}
|
|
89
107
|
|
|
@@ -99,6 +117,12 @@ export default function ShaderView({
|
|
|
99
117
|
scheduleOnRuntime(runtime, () => {
|
|
100
118
|
'worklet';
|
|
101
119
|
|
|
120
|
+
// Worklet runtimes start without the WebGPU flag constants
|
|
121
|
+
// (GPUBufferUsage, GPUTextureUsage, ...). installWebGPU() captures them
|
|
122
|
+
// into this runtime so they're available below. Idempotent / safe no-op
|
|
123
|
+
// if already installed.
|
|
124
|
+
installWebGPU();
|
|
125
|
+
|
|
102
126
|
// Create pipeline once
|
|
103
127
|
const pipeline = device.createRenderPipeline({
|
|
104
128
|
layout: 'auto',
|
|
@@ -128,16 +152,38 @@ export default function ShaderView({
|
|
|
128
152
|
const uniformData = new Float32Array(UNIFORM_FLOAT_COUNT);
|
|
129
153
|
let accumulatedTime = 0;
|
|
130
154
|
let lastTimestamp = 0;
|
|
155
|
+
let warned = false;
|
|
156
|
+
let bufferDestroyed = false;
|
|
157
|
+
|
|
158
|
+
// Free this loop's uniform buffer when the loop ends (alive=0 / superseded
|
|
159
|
+
// by Fast Refresh / unmount). On a fragmentShader change the effect
|
|
160
|
+
// re-runs and schedules a fresh loop with a new buffer while the device
|
|
161
|
+
// persists, so without this the old buffer leaks every shader swap.
|
|
162
|
+
function destroyBuffer() {
|
|
163
|
+
if (bufferDestroyed) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
bufferDestroyed = true;
|
|
167
|
+
try {
|
|
168
|
+
uniformBuffer.destroy();
|
|
169
|
+
} catch {
|
|
170
|
+
// The device may already have been destroyed on unmount — the buffer
|
|
171
|
+
// is gone either way, so there's nothing to recover.
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
131
175
|
|
|
132
176
|
function render(timestamp: number) {
|
|
133
177
|
const props = propsSync.getDirty();
|
|
134
178
|
if (props[IDX_ALIVE] === 0) {
|
|
179
|
+
destroyBuffer();
|
|
135
180
|
return;
|
|
136
181
|
}
|
|
137
182
|
|
|
138
183
|
// This loop was superseded (Fast Refresh / unmount) — bail without
|
|
139
184
|
// scheduling another frame so it can be garbage-collected.
|
|
140
185
|
if (cancelled.getDirty()[0] === 1) {
|
|
186
|
+
destroyBuffer();
|
|
141
187
|
return;
|
|
142
188
|
}
|
|
143
189
|
|
|
@@ -206,29 +252,44 @@ export default function ShaderView({
|
|
|
206
252
|
uniformData[27] = live[3]!;
|
|
207
253
|
}
|
|
208
254
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
255
|
+
// GPU work can throw when the surface is transiently invalid — the app
|
|
256
|
+
// backgrounded, the view detached, or the device was lost. Swallow the
|
|
257
|
+
// failed frame so the loop survives and recovers when the surface comes
|
|
258
|
+
// back (rather than the worklet crashing or the loop dying silently).
|
|
259
|
+
try {
|
|
260
|
+
device.queue.writeBuffer(uniformBuffer, 0, uniformData);
|
|
261
|
+
|
|
262
|
+
const commandEncoder = device.createCommandEncoder();
|
|
263
|
+
const textureView = context.getCurrentTexture().createView();
|
|
264
|
+
const passEncoder = commandEncoder.beginRenderPass({
|
|
265
|
+
colorAttachments: [
|
|
266
|
+
{
|
|
267
|
+
view: textureView,
|
|
268
|
+
clearValue: transparent ? [0, 0, 0, 0] : [0, 0, 0, 1],
|
|
269
|
+
loadOp: 'clear',
|
|
270
|
+
storeOp: 'store',
|
|
271
|
+
},
|
|
272
|
+
],
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
passEncoder.setPipeline(pipeline);
|
|
276
|
+
passEncoder.setBindGroup(0, bindGroup);
|
|
277
|
+
passEncoder.draw(3);
|
|
278
|
+
passEncoder.end();
|
|
279
|
+
|
|
280
|
+
device.queue.submit([commandEncoder.finish()]);
|
|
281
|
+
context.present();
|
|
282
|
+
} catch (e) {
|
|
283
|
+
// Warn once to avoid spamming the console every frame on a persistent
|
|
284
|
+
// failure (e.g. a lost device).
|
|
285
|
+
if (!warned) {
|
|
286
|
+
warned = true;
|
|
287
|
+
console.warn('[react-native-effects] render frame failed:', e);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
231
290
|
|
|
291
|
+
// Always reschedule animated loops, even after a caught failure, so the
|
|
292
|
+
// effect self-heals once the surface is valid again.
|
|
232
293
|
if (!isStatic) {
|
|
233
294
|
requestAnimationFrame(render);
|
|
234
295
|
}
|
|
@@ -242,6 +303,7 @@ export default function ShaderView({
|
|
|
242
303
|
};
|
|
243
304
|
}, [
|
|
244
305
|
resources,
|
|
306
|
+
appActive,
|
|
245
307
|
runtime,
|
|
246
308
|
propsSync,
|
|
247
309
|
paramsSynchronizable,
|
|
@@ -251,7 +313,16 @@ export default function ShaderView({
|
|
|
251
313
|
]);
|
|
252
314
|
|
|
253
315
|
return (
|
|
254
|
-
<Canvas
|
|
316
|
+
<Canvas
|
|
317
|
+
ref={canvasRef}
|
|
318
|
+
transparent={transparent}
|
|
319
|
+
style={[styles.canvas, style]}
|
|
320
|
+
{...viewProps}
|
|
321
|
+
onLayout={(event) => {
|
|
322
|
+
onCanvasLayout(event);
|
|
323
|
+
viewProps.onLayout?.(event);
|
|
324
|
+
}}
|
|
325
|
+
/>
|
|
255
326
|
);
|
|
256
327
|
}
|
|
257
328
|
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { PixelRatio } from 'react-native';
|
|
1
|
+
import { PixelRatio, type LayoutChangeEvent } from 'react-native';
|
|
2
2
|
import {
|
|
3
3
|
useCanvasRef,
|
|
4
4
|
type CanvasRef,
|
|
5
5
|
type RNCanvasContext,
|
|
6
6
|
} from 'react-native-webgpu';
|
|
7
|
-
import { useEffect, useState } from 'react';
|
|
8
|
-
import type
|
|
9
|
-
import { initWebGPU } from '../utils/initWebGPU';
|
|
7
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
8
|
+
import { type WorkletRuntime } from 'react-native-worklets';
|
|
10
9
|
import { BackgroundRuntime } from '../utils/backgroundRuntime';
|
|
10
|
+
import { getSharedGPUDevice } from '../utils/gpuDevice';
|
|
11
11
|
|
|
12
12
|
type GPUResources = {
|
|
13
13
|
device: GPUDevice;
|
|
@@ -15,10 +15,17 @@ type GPUResources = {
|
|
|
15
15
|
presentationFormat: GPUTextureFormat;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
+
type CanvasWithSize = RNCanvasContext['canvas'] & {
|
|
19
|
+
width: number;
|
|
20
|
+
height: number;
|
|
21
|
+
};
|
|
22
|
+
|
|
18
23
|
type WGPUSetupResult = {
|
|
19
24
|
canvasRef: React.RefObject<CanvasRef>;
|
|
20
25
|
runtime: WorkletRuntime;
|
|
21
26
|
resources: GPUResources | null;
|
|
27
|
+
/** Wire to `<Canvas onLayout>` so the surface resizes on rotation/layout change. */
|
|
28
|
+
onCanvasLayout: (event: LayoutChangeEvent) => void;
|
|
22
29
|
};
|
|
23
30
|
|
|
24
31
|
export function useWGPUSetup(): WGPUSetupResult {
|
|
@@ -26,28 +33,31 @@ export function useWGPUSetup(): WGPUSetupResult {
|
|
|
26
33
|
const [resources, setResources] = useState<GPUResources | null>(null);
|
|
27
34
|
const runtime = BackgroundRuntime;
|
|
28
35
|
|
|
36
|
+
// Physical-pixel size the surface is currently configured for. Used to drive
|
|
37
|
+
// resize (and to skip redundant reconfigures when the layout pass reports the
|
|
38
|
+
// same size).
|
|
39
|
+
const configuredSizeRef = useRef<{ width: number; height: number } | null>(
|
|
40
|
+
null
|
|
41
|
+
);
|
|
42
|
+
|
|
29
43
|
useEffect(() => {
|
|
30
44
|
let cancelled = false;
|
|
31
45
|
|
|
32
46
|
(async () => {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const device = await adapter.requestDevice();
|
|
39
|
-
if (cancelled) {
|
|
47
|
+
// Shared across every ShaderView — see getSharedGPUDevice. Returns null if
|
|
48
|
+
// no adapter is available.
|
|
49
|
+
const device = await getSharedGPUDevice();
|
|
50
|
+
if (!device || cancelled) {
|
|
40
51
|
return;
|
|
41
52
|
}
|
|
42
53
|
|
|
43
54
|
const context = canvasRef.current!.getContext('webgpu')!;
|
|
44
|
-
const canvas = context.canvas as
|
|
45
|
-
width: number;
|
|
46
|
-
height: number;
|
|
47
|
-
};
|
|
55
|
+
const canvas = context.canvas as CanvasWithSize;
|
|
48
56
|
const dpr = PixelRatio.get();
|
|
49
|
-
|
|
50
|
-
|
|
57
|
+
const width = Math.max(1, Math.round(canvas.width * dpr));
|
|
58
|
+
const height = Math.max(1, Math.round(canvas.height * dpr));
|
|
59
|
+
canvas.width = width;
|
|
60
|
+
canvas.height = height;
|
|
51
61
|
|
|
52
62
|
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
|
|
53
63
|
context.configure({
|
|
@@ -55,8 +65,7 @@ export function useWGPUSetup(): WGPUSetupResult {
|
|
|
55
65
|
format: presentationFormat,
|
|
56
66
|
alphaMode: 'premultiplied',
|
|
57
67
|
});
|
|
58
|
-
|
|
59
|
-
initWebGPU(runtime);
|
|
68
|
+
configuredSizeRef.current = { width, height };
|
|
60
69
|
|
|
61
70
|
if (!cancelled) {
|
|
62
71
|
setResources({ device, context, presentationFormat });
|
|
@@ -65,9 +74,48 @@ export function useWGPUSetup(): WGPUSetupResult {
|
|
|
65
74
|
|
|
66
75
|
return () => {
|
|
67
76
|
cancelled = true;
|
|
77
|
+
// The device is shared across all ShaderViews and lives for the JS
|
|
78
|
+
// runtime's lifetime, so it must NOT be destroyed here. This view's own
|
|
79
|
+
// GPU resources (the uniform buffer) are torn down by the render loop when
|
|
80
|
+
// it stops; the pipeline/shader modules are released once the loop's
|
|
81
|
+
// worklet closure is garbage-collected.
|
|
68
82
|
};
|
|
69
83
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
70
84
|
}, []);
|
|
71
85
|
|
|
72
|
-
|
|
86
|
+
// Resize the drawing buffer and reconfigure the surface whenever the view's
|
|
87
|
+
// layout changes (most notably device rotation). Without this the buffer keeps
|
|
88
|
+
// its original dimensions and the shader renders stretched / wrong-aspect. The
|
|
89
|
+
// render loop reads `canvas.width/height` each frame, so the resolution uniform
|
|
90
|
+
// picks up the new size on the next frame. No-op until GPU resources are ready
|
|
91
|
+
// (initial sizing is handled by the setup effect above).
|
|
92
|
+
const onCanvasLayout = useCallback(
|
|
93
|
+
(event: LayoutChangeEvent) => {
|
|
94
|
+
if (!resources) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const { width, height } = event.nativeEvent.layout;
|
|
98
|
+
const dpr = PixelRatio.get();
|
|
99
|
+
const w = Math.max(1, Math.round(width * dpr));
|
|
100
|
+
const h = Math.max(1, Math.round(height * dpr));
|
|
101
|
+
|
|
102
|
+
const prev = configuredSizeRef.current;
|
|
103
|
+
if (prev && prev.width === w && prev.height === h) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const canvas = resources.context.canvas as CanvasWithSize;
|
|
108
|
+
canvas.width = w;
|
|
109
|
+
canvas.height = h;
|
|
110
|
+
resources.context.configure({
|
|
111
|
+
device: resources.device,
|
|
112
|
+
format: resources.presentationFormat,
|
|
113
|
+
alphaMode: 'premultiplied',
|
|
114
|
+
});
|
|
115
|
+
configuredSizeRef.current = { width: w, height: h };
|
|
116
|
+
},
|
|
117
|
+
[resources]
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return { canvasRef, runtime, resources, onCanvasLayout };
|
|
73
121
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
let devicePromise: Promise<GPUDevice | null> | null = null;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Lazily requests a single GPUDevice shared by every ShaderView.
|
|
5
|
+
*
|
|
6
|
+
* Requesting an adapter + device per view is expensive (real GPU/memory cost and
|
|
7
|
+
* slower init), and a screen that mounts several effects would otherwise spin up
|
|
8
|
+
* N devices on the one background runtime. The promise is cached for the lifetime
|
|
9
|
+
* of the JS runtime so every view awaits the same device.
|
|
10
|
+
*
|
|
11
|
+
* If the device is ever lost, the cache is cleared so the next ShaderView mount
|
|
12
|
+
* requests a fresh one instead of awaiting a dead device forever.
|
|
13
|
+
*/
|
|
14
|
+
export function getSharedGPUDevice(): Promise<GPUDevice | null> {
|
|
15
|
+
if (devicePromise) {
|
|
16
|
+
return devicePromise;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const promise = (async () => {
|
|
20
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
21
|
+
if (!adapter) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const device = await adapter.requestDevice();
|
|
26
|
+
device.lost?.then(() => {
|
|
27
|
+
// Only clear if we're still the cached promise, so we don't clobber a
|
|
28
|
+
// newer device that a later mount may already have requested.
|
|
29
|
+
if (devicePromise === promise) {
|
|
30
|
+
devicePromise = null;
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
return device;
|
|
34
|
+
})();
|
|
35
|
+
|
|
36
|
+
devicePromise = promise;
|
|
37
|
+
return promise;
|
|
38
|
+
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
|
|
3
|
-
import { scheduleOnRuntime } from 'react-native-worklets';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Installs WebGPU globals on the given worklet runtime.
|
|
7
|
-
*
|
|
8
|
-
* Worklet runtimes run on a separate JS context that lacks browser-like globals.
|
|
9
|
-
* This function captures WebGPU constants and `navigator.gpu` from the RN thread
|
|
10
|
-
* and injects them into the worklet's `globalThis` so that WebGPU code can run
|
|
11
|
-
* as if it were in a browser environment.
|
|
12
|
-
*
|
|
13
|
-
* @param runtime - The worklet runtime to initialize
|
|
14
|
-
*/
|
|
15
|
-
export function initWebGPU(runtime) {
|
|
16
|
-
const navigator = globalThis.navigator;
|
|
17
|
-
const GPUBufferUsage = globalThis.GPUBufferUsage;
|
|
18
|
-
const GPUColorWrite = globalThis.GPUColorWrite;
|
|
19
|
-
const GPUMapMode = globalThis.GPUMapMode;
|
|
20
|
-
const GPUShaderStage = globalThis.GPUShaderStage;
|
|
21
|
-
const GPUTextureUsage = globalThis.GPUTextureUsage;
|
|
22
|
-
scheduleOnRuntime(runtime, () => {
|
|
23
|
-
'worklet';
|
|
24
|
-
|
|
25
|
-
if (globalThis.self) {
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
globalThis.self = globalThis;
|
|
29
|
-
globalThis.navigator = {
|
|
30
|
-
gpu: navigator.gpu
|
|
31
|
-
};
|
|
32
|
-
globalThis.GPUBufferUsage = GPUBufferUsage;
|
|
33
|
-
globalThis.GPUColorWrite = GPUColorWrite;
|
|
34
|
-
globalThis.GPUMapMode = GPUMapMode;
|
|
35
|
-
globalThis.GPUShaderStage = GPUShaderStage;
|
|
36
|
-
globalThis.GPUTextureUsage = GPUTextureUsage;
|
|
37
|
-
globalThis.setImmediate = globalThis.requestAnimationFrame;
|
|
38
|
-
});
|
|
39
|
-
}
|
|
40
|
-
//# sourceMappingURL=initWebGPU.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"names":["scheduleOnRuntime","initWebGPU","runtime","navigator","globalThis","GPUBufferUsage","GPUColorWrite","GPUMapMode","GPUShaderStage","GPUTextureUsage","self","gpu","setImmediate","requestAnimationFrame"],"sourceRoot":"../../../src","sources":["utils/initWebGPU.ts"],"mappings":";;AAAA,SAASA,iBAAiB,QAA6B,uBAAuB;;AAE9E;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,UAAUA,CAACC,OAAuB,EAAE;EAClD,MAAMC,SAAS,GAAGC,UAAU,CAACD,SAAyB;EACtD,MAAME,cAAc,GAAGD,UAAU,CAACC,cAAc;EAChD,MAAMC,aAAa,GAAGF,UAAU,CAACE,aAAa;EAC9C,MAAMC,UAAU,GAAGH,UAAU,CAACG,UAAU;EACxC,MAAMC,cAAc,GAAGJ,UAAU,CAACI,cAAc;EAChD,MAAMC,eAAe,GAAGL,UAAU,CAACK,eAAe;EAElDT,iBAAiB,CAACE,OAAO,EAAE,MAAM;IAC/B,SAAS;;IAET,IAAIE,UAAU,CAACM,IAAI,EAAE;MACnB;IACF;IACAN,UAAU,CAACM,IAAI,GAAGN,UAAU;IAC5BA,UAAU,CAACD,SAAS,GAAG;MAAEQ,GAAG,EAAER,SAAS,CAACQ;IAAI,CAAyB;IACrEP,UAAU,CAACC,cAAc,GAAGA,cAAc;IAC1CD,UAAU,CAACE,aAAa,GAAGA,aAAa;IACxCF,UAAU,CAACG,UAAU,GAAGA,UAAU;IAClCH,UAAU,CAACI,cAAc,GAAGA,cAAc;IAC1CJ,UAAU,CAACK,eAAe,GAAGA,eAAe;IAC5CL,UAAU,CAACQ,YAAY,GACrBR,UAAU,CAACS,qBAA4C;EAC3D,CAAC,CAAC;AACJ","ignoreList":[]}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { type WorkletRuntime } from 'react-native-worklets';
|
|
2
|
-
/**
|
|
3
|
-
* Installs WebGPU globals on the given worklet runtime.
|
|
4
|
-
*
|
|
5
|
-
* Worklet runtimes run on a separate JS context that lacks browser-like globals.
|
|
6
|
-
* This function captures WebGPU constants and `navigator.gpu` from the RN thread
|
|
7
|
-
* and injects them into the worklet's `globalThis` so that WebGPU code can run
|
|
8
|
-
* as if it were in a browser environment.
|
|
9
|
-
*
|
|
10
|
-
* @param runtime - The worklet runtime to initialize
|
|
11
|
-
*/
|
|
12
|
-
export declare function initWebGPU(runtime: WorkletRuntime): void;
|
|
13
|
-
declare global {
|
|
14
|
-
var self: typeof globalThis;
|
|
15
|
-
var _WORKLET: boolean | undefined;
|
|
16
|
-
var performance: {
|
|
17
|
-
now(): number;
|
|
18
|
-
};
|
|
19
|
-
interface Navigator {
|
|
20
|
-
gpu: GPU;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
//# sourceMappingURL=initWebGPU.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"initWebGPU.d.ts","sourceRoot":"","sources":["../../../../src/utils/initWebGPU.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAE/E;;;;;;;;;GASG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,cAAc,QAwBjD;AAED,OAAO,CAAC,MAAM,CAAC;IACb,IAAI,IAAI,EAAE,OAAO,UAAU,CAAC;IAC5B,IAAI,QAAQ,EAAE,OAAO,GAAG,SAAS,CAAC;IAClC,IAAI,WAAW,EAAE;QAAE,GAAG,IAAI,MAAM,CAAA;KAAE,CAAC;IAEnC,UAAU,SAAS;QACjB,GAAG,EAAE,GAAG,CAAC;KACV;CACF"}
|
package/src/utils/initWebGPU.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { scheduleOnRuntime, type WorkletRuntime } from 'react-native-worklets';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Installs WebGPU globals on the given worklet runtime.
|
|
5
|
-
*
|
|
6
|
-
* Worklet runtimes run on a separate JS context that lacks browser-like globals.
|
|
7
|
-
* This function captures WebGPU constants and `navigator.gpu` from the RN thread
|
|
8
|
-
* and injects them into the worklet's `globalThis` so that WebGPU code can run
|
|
9
|
-
* as if it were in a browser environment.
|
|
10
|
-
*
|
|
11
|
-
* @param runtime - The worklet runtime to initialize
|
|
12
|
-
*/
|
|
13
|
-
export function initWebGPU(runtime: WorkletRuntime) {
|
|
14
|
-
const navigator = globalThis.navigator as NavigatorGPU;
|
|
15
|
-
const GPUBufferUsage = globalThis.GPUBufferUsage;
|
|
16
|
-
const GPUColorWrite = globalThis.GPUColorWrite;
|
|
17
|
-
const GPUMapMode = globalThis.GPUMapMode;
|
|
18
|
-
const GPUShaderStage = globalThis.GPUShaderStage;
|
|
19
|
-
const GPUTextureUsage = globalThis.GPUTextureUsage;
|
|
20
|
-
|
|
21
|
-
scheduleOnRuntime(runtime, () => {
|
|
22
|
-
'worklet';
|
|
23
|
-
|
|
24
|
-
if (globalThis.self) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
globalThis.self = globalThis;
|
|
28
|
-
globalThis.navigator = { gpu: navigator.gpu } as unknown as Navigator;
|
|
29
|
-
globalThis.GPUBufferUsage = GPUBufferUsage;
|
|
30
|
-
globalThis.GPUColorWrite = GPUColorWrite;
|
|
31
|
-
globalThis.GPUMapMode = GPUMapMode;
|
|
32
|
-
globalThis.GPUShaderStage = GPUShaderStage;
|
|
33
|
-
globalThis.GPUTextureUsage = GPUTextureUsage;
|
|
34
|
-
globalThis.setImmediate =
|
|
35
|
-
globalThis.requestAnimationFrame as typeof setImmediate;
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
declare global {
|
|
40
|
-
var self: typeof globalThis;
|
|
41
|
-
var _WORKLET: boolean | undefined;
|
|
42
|
-
var performance: { now(): number };
|
|
43
|
-
|
|
44
|
-
interface Navigator {
|
|
45
|
-
gpu: GPU;
|
|
46
|
-
}
|
|
47
|
-
}
|