react-native-effects 0.1.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.
Files changed (61) hide show
  1. package/README.md +75 -13
  2. package/lib/module/components/ShaderView/index.js +122 -27
  3. package/lib/module/components/ShaderView/index.js.map +1 -1
  4. package/lib/module/components/ShaderViewWithPanGesture/index.js +196 -0
  5. package/lib/module/components/ShaderViewWithPanGesture/index.js.map +1 -0
  6. package/lib/module/hooks/useParamsSynchronizable.js +37 -0
  7. package/lib/module/hooks/useParamsSynchronizable.js.map +1 -0
  8. package/lib/module/hooks/useWGPUSetup.js +62 -13
  9. package/lib/module/hooks/useWGPUSetup.js.map +1 -1
  10. package/lib/module/index.js +3 -1
  11. package/lib/module/index.js.map +1 -1
  12. package/lib/module/shaders/uniforms.js +4 -3
  13. package/lib/module/shaders/uniforms.js.map +1 -1
  14. package/lib/module/utils/gpuDevice.js +38 -0
  15. package/lib/module/utils/gpuDevice.js.map +1 -0
  16. package/lib/typescript/src/components/Aurora.d.ts +1 -1
  17. package/lib/typescript/src/components/Aurora.d.ts.map +1 -1
  18. package/lib/typescript/src/components/CalicoSwirl.d.ts +1 -1
  19. package/lib/typescript/src/components/CalicoSwirl.d.ts.map +1 -1
  20. package/lib/typescript/src/components/Campfire.d.ts +1 -1
  21. package/lib/typescript/src/components/Campfire.d.ts.map +1 -1
  22. package/lib/typescript/src/components/CircularGradient.d.ts +1 -1
  23. package/lib/typescript/src/components/CircularGradient.d.ts.map +1 -1
  24. package/lib/typescript/src/components/Iridescence.d.ts +1 -1
  25. package/lib/typescript/src/components/Iridescence.d.ts.map +1 -1
  26. package/lib/typescript/src/components/LinearGradient.d.ts +1 -1
  27. package/lib/typescript/src/components/LinearGradient.d.ts.map +1 -1
  28. package/lib/typescript/src/components/LiquidChrome.d.ts +1 -1
  29. package/lib/typescript/src/components/LiquidChrome.d.ts.map +1 -1
  30. package/lib/typescript/src/components/ShaderView/index.d.ts +1 -1
  31. package/lib/typescript/src/components/ShaderView/index.d.ts.map +1 -1
  32. package/lib/typescript/src/components/ShaderView/types.d.ts +20 -0
  33. package/lib/typescript/src/components/ShaderView/types.d.ts.map +1 -1
  34. package/lib/typescript/src/components/ShaderViewWithPanGesture/index.d.ts +35 -0
  35. package/lib/typescript/src/components/ShaderViewWithPanGesture/index.d.ts.map +1 -0
  36. package/lib/typescript/src/components/Silk.d.ts +1 -1
  37. package/lib/typescript/src/components/Silk.d.ts.map +1 -1
  38. package/lib/typescript/src/hooks/useParamsSynchronizable.d.ts +22 -0
  39. package/lib/typescript/src/hooks/useParamsSynchronizable.d.ts.map +1 -0
  40. package/lib/typescript/src/hooks/useWGPUSetup.d.ts +5 -2
  41. package/lib/typescript/src/hooks/useWGPUSetup.d.ts.map +1 -1
  42. package/lib/typescript/src/index.d.ts +6 -2
  43. package/lib/typescript/src/index.d.ts.map +1 -1
  44. package/lib/typescript/src/shaders/uniforms.d.ts +3 -3
  45. package/lib/typescript/src/shaders/uniforms.d.ts.map +1 -1
  46. package/lib/typescript/src/utils/gpuDevice.d.ts +13 -0
  47. package/lib/typescript/src/utils/gpuDevice.d.ts.map +1 -0
  48. package/package.json +31 -30
  49. package/src/components/ShaderView/index.tsx +140 -32
  50. package/src/components/ShaderView/types.ts +21 -0
  51. package/src/components/ShaderViewWithPanGesture/index.tsx +225 -0
  52. package/src/hooks/useParamsSynchronizable.ts +52 -0
  53. package/src/hooks/useWGPUSetup.tsx +69 -21
  54. package/src/index.tsx +10 -1
  55. package/src/shaders/uniforms.ts +4 -3
  56. package/src/utils/gpuDevice.ts +38 -0
  57. package/lib/module/utils/initWebGPU.js +0 -40
  58. package/lib/module/utils/initWebGPU.js.map +0 -1
  59. package/lib/typescript/src/utils/initWebGPU.d.ts +0 -23
  60. package/lib/typescript/src/utils/initWebGPU.d.ts.map +0 -1
  61. package/src/utils/initWebGPU.ts +0 -47
@@ -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
- } from 'react-native-wgpu';
7
- import { useEffect, useState } from 'react';
8
- import type { WorkletRuntime } from 'react-native-worklets';
9
- import { initWebGPU } from '../utils/initWebGPU';
6
+ } from 'react-native-webgpu';
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
- const adapter = await navigator.gpu.requestAdapter();
34
- if (!adapter || cancelled) {
35
- return;
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 typeof context.canvas & {
45
- width: number;
46
- height: number;
47
- };
55
+ const canvas = context.canvas as CanvasWithSize;
48
56
  const dpr = PixelRatio.get();
49
- canvas.width = canvas.width * dpr;
50
- canvas.height = canvas.height * dpr;
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
- return { canvasRef, runtime, resources };
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
  }
package/src/index.tsx CHANGED
@@ -1,23 +1,32 @@
1
1
  import CircularGradient from './components/CircularGradient';
2
2
  import LinearGradient from './components/LinearGradient';
3
3
  import ShaderView from './components/ShaderView';
4
+ import ShaderViewWithPanGesture from './components/ShaderViewWithPanGesture';
4
5
  import Iridescence from './components/Iridescence';
5
6
  import LiquidChrome from './components/LiquidChrome';
6
7
  import Silk from './components/Silk';
7
8
  import Campfire from './components/Campfire';
8
9
  import CalicoSwirl from './components/CalicoSwirl';
9
10
  import Aurora from './components/Aurora';
11
+ import { useParamsSynchronizable } from './hooks/useParamsSynchronizable';
10
12
 
11
- export type { ShaderViewProps } from './components/ShaderView/types';
13
+ export type {
14
+ ShaderViewProps,
15
+ ParamsSynchronizable,
16
+ } from './components/ShaderView/types';
17
+ export type { ShaderViewWithPanGestureProps } from './components/ShaderViewWithPanGesture';
18
+ export type { ColorInput } from './utils/colors';
12
19
 
13
20
  export {
14
21
  CircularGradient,
15
22
  LinearGradient,
16
23
  ShaderView,
24
+ ShaderViewWithPanGesture,
17
25
  Iridescence,
18
26
  LiquidChrome,
19
27
  Silk,
20
28
  Campfire,
21
29
  CalicoSwirl,
22
30
  Aurora,
31
+ useParamsSynchronizable,
23
32
  };
@@ -1,8 +1,8 @@
1
- /** 96 bytes = 6 × vec4<f32> */
2
- export const UNIFORM_BUFFER_SIZE = 96;
1
+ /** 112 bytes = 7 × vec4<f32> */
2
+ export const UNIFORM_BUFFER_SIZE = 112;
3
3
 
4
4
  /** Number of float32 values in the uniform buffer */
5
- export const UNIFORM_FLOAT_COUNT = UNIFORM_BUFFER_SIZE / 4; // 24
5
+ export const UNIFORM_FLOAT_COUNT = UNIFORM_BUFFER_SIZE / 4; // 28
6
6
 
7
7
  export const UNIFORMS_WGSL = /* wgsl */ `
8
8
  struct Uniforms {
@@ -12,6 +12,7 @@ struct Uniforms {
12
12
  color1: vec4<f32>, // colors[1] RGBA
13
13
  params0: vec4<f32>, // params[0..3]
14
14
  params1: vec4<f32>, // params[4..7]
15
+ live: vec4<f32>, // paramsSynchronizable (touch/scroll/audio); (0,0,0,0) when unused
15
16
  };
16
17
  @group(0) @binding(0) var<uniform> u: Uniforms;
17
18
  `;
@@ -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"}
@@ -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
- }