react-native-effects 0.0.1 → 0.2.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 (110) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +313 -0
  3. package/lib/module/components/Aurora.js +184 -0
  4. package/lib/module/components/Aurora.js.map +1 -0
  5. package/lib/module/components/CalicoSwirl.js +155 -0
  6. package/lib/module/components/CalicoSwirl.js.map +1 -0
  7. package/lib/module/components/Campfire.js +225 -0
  8. package/lib/module/components/Campfire.js.map +1 -0
  9. package/lib/module/components/CircularGradient.js +52 -0
  10. package/lib/module/components/CircularGradient.js.map +1 -0
  11. package/lib/module/components/Iridescence.js +57 -0
  12. package/lib/module/components/Iridescence.js.map +1 -0
  13. package/lib/module/components/LinearGradient.js +48 -0
  14. package/lib/module/components/LinearGradient.js.map +1 -0
  15. package/lib/module/components/LiquidChrome.js +75 -0
  16. package/lib/module/components/LiquidChrome.js.map +1 -0
  17. package/lib/module/components/ShaderView/index.js +252 -0
  18. package/lib/module/components/ShaderView/index.js.map +1 -0
  19. package/lib/module/components/ShaderView/types.js +4 -0
  20. package/lib/module/components/ShaderView/types.js.map +1 -0
  21. package/lib/module/components/ShaderViewWithPanGesture/index.js +196 -0
  22. package/lib/module/components/ShaderViewWithPanGesture/index.js.map +1 -0
  23. package/lib/module/components/Silk.js +83 -0
  24. package/lib/module/components/Silk.js.map +1 -0
  25. package/lib/module/consts.js +154 -0
  26. package/lib/module/consts.js.map +1 -0
  27. package/lib/module/hooks/useClock.js +15 -0
  28. package/lib/module/hooks/useClock.js.map +1 -0
  29. package/lib/module/hooks/useParamsSynchronizable.js +37 -0
  30. package/lib/module/hooks/useParamsSynchronizable.js.map +1 -0
  31. package/lib/module/hooks/useWGPUSetup.js +54 -0
  32. package/lib/module/hooks/useWGPUSetup.js.map +1 -0
  33. package/lib/module/index.js +15 -0
  34. package/lib/module/index.js.map +1 -0
  35. package/lib/module/package.json +1 -0
  36. package/lib/module/shaders/TRIANGLE_VERTEX_SHADER.js +20 -0
  37. package/lib/module/shaders/TRIANGLE_VERTEX_SHADER.js.map +1 -0
  38. package/lib/module/shaders/uniforms.js +21 -0
  39. package/lib/module/shaders/uniforms.js.map +1 -0
  40. package/lib/module/utils/backgroundRuntime.js +12 -0
  41. package/lib/module/utils/backgroundRuntime.js.map +1 -0
  42. package/lib/module/utils/colors.js +94 -0
  43. package/lib/module/utils/colors.js.map +1 -0
  44. package/lib/module/utils/initWebGPU.js +40 -0
  45. package/lib/module/utils/initWebGPU.js.map +1 -0
  46. package/lib/typescript/package.json +1 -0
  47. package/lib/typescript/src/components/Aurora.d.ts +17 -0
  48. package/lib/typescript/src/components/Aurora.d.ts.map +1 -0
  49. package/lib/typescript/src/components/CalicoSwirl.d.ts +13 -0
  50. package/lib/typescript/src/components/CalicoSwirl.d.ts.map +1 -0
  51. package/lib/typescript/src/components/Campfire.d.ts +17 -0
  52. package/lib/typescript/src/components/Campfire.d.ts.map +1 -0
  53. package/lib/typescript/src/components/CircularGradient.d.ts +19 -0
  54. package/lib/typescript/src/components/CircularGradient.d.ts.map +1 -0
  55. package/lib/typescript/src/components/Iridescence.d.ts +11 -0
  56. package/lib/typescript/src/components/Iridescence.d.ts.map +1 -0
  57. package/lib/typescript/src/components/LinearGradient.d.ts +15 -0
  58. package/lib/typescript/src/components/LinearGradient.d.ts.map +1 -0
  59. package/lib/typescript/src/components/LiquidChrome.d.ts +17 -0
  60. package/lib/typescript/src/components/LiquidChrome.d.ts.map +1 -0
  61. package/lib/typescript/src/components/ShaderView/index.d.ts +3 -0
  62. package/lib/typescript/src/components/ShaderView/index.d.ts.map +1 -0
  63. package/lib/typescript/src/components/ShaderView/types.d.ts +35 -0
  64. package/lib/typescript/src/components/ShaderView/types.d.ts.map +1 -0
  65. package/lib/typescript/src/components/ShaderViewWithPanGesture/index.d.ts +35 -0
  66. package/lib/typescript/src/components/ShaderViewWithPanGesture/index.d.ts.map +1 -0
  67. package/lib/typescript/src/components/Silk.d.ts +17 -0
  68. package/lib/typescript/src/components/Silk.d.ts.map +1 -0
  69. package/lib/typescript/src/consts.d.ts +2 -0
  70. package/lib/typescript/src/consts.d.ts.map +1 -0
  71. package/lib/typescript/src/hooks/useClock.d.ts +3 -0
  72. package/lib/typescript/src/hooks/useClock.d.ts.map +1 -0
  73. package/lib/typescript/src/hooks/useParamsSynchronizable.d.ts +22 -0
  74. package/lib/typescript/src/hooks/useParamsSynchronizable.d.ts.map +1 -0
  75. package/lib/typescript/src/hooks/useWGPUSetup.d.ts +15 -0
  76. package/lib/typescript/src/hooks/useWGPUSetup.d.ts.map +1 -0
  77. package/lib/typescript/src/index.d.ts +16 -0
  78. package/lib/typescript/src/index.d.ts.map +1 -0
  79. package/lib/typescript/src/shaders/TRIANGLE_VERTEX_SHADER.d.ts +2 -0
  80. package/lib/typescript/src/shaders/TRIANGLE_VERTEX_SHADER.d.ts.map +1 -0
  81. package/lib/typescript/src/shaders/uniforms.d.ts +6 -0
  82. package/lib/typescript/src/shaders/uniforms.d.ts.map +1 -0
  83. package/lib/typescript/src/utils/backgroundRuntime.d.ts +3 -0
  84. package/lib/typescript/src/utils/backgroundRuntime.d.ts.map +1 -0
  85. package/lib/typescript/src/utils/colors.d.ts +22 -0
  86. package/lib/typescript/src/utils/colors.d.ts.map +1 -0
  87. package/lib/typescript/src/utils/initWebGPU.d.ts +23 -0
  88. package/lib/typescript/src/utils/initWebGPU.d.ts.map +1 -0
  89. package/package.json +175 -7
  90. package/src/components/Aurora.tsx +203 -0
  91. package/src/components/CalicoSwirl.tsx +167 -0
  92. package/src/components/Campfire.tsx +244 -0
  93. package/src/components/CircularGradient.tsx +76 -0
  94. package/src/components/Iridescence.tsx +67 -0
  95. package/src/components/LinearGradient.tsx +62 -0
  96. package/src/components/LiquidChrome.tsx +94 -0
  97. package/src/components/ShaderView/index.tsx +262 -0
  98. package/src/components/ShaderView/types.ts +36 -0
  99. package/src/components/ShaderViewWithPanGesture/index.tsx +225 -0
  100. package/src/components/Silk.tsx +102 -0
  101. package/src/consts.ts +152 -0
  102. package/src/hooks/useClock.ts +20 -0
  103. package/src/hooks/useParamsSynchronizable.ts +52 -0
  104. package/src/hooks/useWGPUSetup.tsx +73 -0
  105. package/src/index.tsx +32 -0
  106. package/src/shaders/TRIANGLE_VERTEX_SHADER.ts +17 -0
  107. package/src/shaders/uniforms.ts +18 -0
  108. package/src/utils/backgroundRuntime.ts +10 -0
  109. package/src/utils/colors.ts +117 -0
  110. package/src/utils/initWebGPU.ts +47 -0
@@ -0,0 +1,67 @@
1
+ import { useMemo } from 'react';
2
+ import type { ViewProps } from 'react-native';
3
+ import type { ColorInput } from '../utils/colors';
4
+ import ShaderView from './ShaderView';
5
+
6
+ type Props = ViewProps & {
7
+ /** The color tint for the iridescence effect. */
8
+ color?: ColorInput;
9
+ /** Animation speed multiplier. Default: 1.0 */
10
+ speed?: number;
11
+ };
12
+
13
+ export default function Iridescence({
14
+ color = '#ffffff',
15
+ speed = 1.0,
16
+ ...viewProps
17
+ }: Props) {
18
+ const colors = useMemo(() => [color], [color]);
19
+
20
+ return (
21
+ <ShaderView
22
+ fragmentShader={IRIDESCENCE_SHADER}
23
+ colors={colors}
24
+ params={[]}
25
+ speed={speed}
26
+ isStatic={false}
27
+ {...viewProps}
28
+ />
29
+ );
30
+ }
31
+
32
+ const IRIDESCENCE_SHADER = /* wgsl */ `
33
+ struct Uniforms {
34
+ resolution: vec4<f32>,
35
+ time: vec4<f32>,
36
+ color0: vec4<f32>,
37
+ color1: vec4<f32>,
38
+ params0: vec4<f32>,
39
+ params1: vec4<f32>,
40
+ };
41
+ @group(0) @binding(0) var<uniform> u: Uniforms;
42
+
43
+ @fragment
44
+ fn main(@location(0) ndc: vec2<f32>) -> @location(0) vec4<f32> {
45
+ let time = u.time.x;
46
+
47
+ let vUv = ndc * 0.5 + vec2<f32>(0.5, 0.5);
48
+ let mr = min(u.resolution.x, u.resolution.y);
49
+ var uv = (vUv * 2.0 - vec2<f32>(1.0, 1.0)) * (u.resolution.xy / mr);
50
+
51
+ var d = -time * 0.5;
52
+ var a = 0.0;
53
+ for (var i: f32 = 0.0; i < 8.0; i = i + 1.0) {
54
+ a = a + cos(i - d - a * uv.x);
55
+ d = d + sin(uv.y * i + a);
56
+ }
57
+ d = d + time * 0.5;
58
+
59
+ let c1 = cos(uv * vec2<f32>(d, a)) * 0.6 + 0.4;
60
+ let c2 = cos(a + d) * 0.5 + 0.5;
61
+ let col = vec3<f32>(c1.x, c1.y, c2);
62
+
63
+ let finalCol = cos(col * cos(vec3<f32>(d, a, 2.5)) * 0.5 + 0.5);
64
+ let coloredCol = finalCol * u.color0.rgb;
65
+ return vec4<f32>(clamp(coloredCol, vec3<f32>(0.0), vec3<f32>(1.0)), u.color0.a);
66
+ }
67
+ `;
@@ -0,0 +1,62 @@
1
+ import { useMemo } from 'react';
2
+ import type { ViewProps } from 'react-native';
3
+ import type { ColorInput } from '../utils/colors';
4
+ import ShaderView from './ShaderView';
5
+
6
+ type Props = ViewProps & {
7
+ /** The color of the start of the gradient. */
8
+ startColor: ColorInput;
9
+ /** The color of the end of the gradient. */
10
+ endColor: ColorInput;
11
+ /** The angle of the gradient in degrees (0-360). */
12
+ angle: number;
13
+ /** Rotation speed in degrees/second. 0 = static. Default: 0 */
14
+ speed?: number;
15
+ };
16
+
17
+ export default function LinearGradient({
18
+ startColor,
19
+ endColor,
20
+ angle,
21
+ speed = 0,
22
+ ...viewProps
23
+ }: Props) {
24
+ const colors = useMemo(() => [startColor, endColor], [startColor, endColor]);
25
+ const params = useMemo(() => [angle, speed], [angle, speed]);
26
+
27
+ return (
28
+ <ShaderView
29
+ fragmentShader={LINEAR_GRADIENT_SHADER}
30
+ colors={colors}
31
+ params={params}
32
+ speed={speed === 0 ? 0 : 1}
33
+ isStatic={speed === 0}
34
+ {...viewProps}
35
+ />
36
+ );
37
+ }
38
+
39
+ const LINEAR_GRADIENT_SHADER = /* wgsl */ `
40
+ struct Uniforms {
41
+ resolution: vec4<f32>,
42
+ time: vec4<f32>,
43
+ color0: vec4<f32>,
44
+ color1: vec4<f32>,
45
+ params0: vec4<f32>,
46
+ params1: vec4<f32>,
47
+ };
48
+ @group(0) @binding(0) var<uniform> u: Uniforms;
49
+
50
+ @fragment
51
+ fn main(@location(0) ndc: vec2<f32>) -> @location(0) vec4<f32> {
52
+ let uv = ndc * 0.5 + vec2<f32>(0.5, 0.5);
53
+ let baseAngle = u.params0.x;
54
+ let rotationSpeed = u.params0.y;
55
+ let angle = (baseAngle + u.time.x * rotationSpeed) * 3.14159265359 / 180.0;
56
+ let dir = vec2<f32>(cos(angle), sin(angle));
57
+ let fromCenter = uv - vec2<f32>(0.5, 0.5);
58
+ let corrected = vec2<f32>(fromCenter.x * u.resolution.z, fromCenter.y);
59
+ let t = clamp(dot(corrected, dir) + 0.5, 0.0, 1.0);
60
+ return mix(u.color0, u.color1, t);
61
+ }
62
+ `;
@@ -0,0 +1,94 @@
1
+ import { useMemo } from 'react';
2
+ import type { ViewProps } from 'react-native';
3
+ import type { ColorInput } from '../utils/colors';
4
+ import ShaderView from './ShaderView';
5
+
6
+ type Props = ViewProps & {
7
+ /** The base color for the liquid chrome effect. */
8
+ color?: ColorInput;
9
+ /** Animation speed multiplier. Default: 0.2 */
10
+ speed?: number;
11
+ /** Amplitude of the distortion. Default: 0.3 */
12
+ amplitude?: number;
13
+ /** Horizontal frequency. Default: 3 */
14
+ frequencyX?: number;
15
+ /** Vertical frequency. Default: 3 */
16
+ frequencyY?: number;
17
+ };
18
+
19
+ export default function LiquidChrome({
20
+ color = '#1a1a1a',
21
+ speed = 0.2,
22
+ amplitude = 0.3,
23
+ frequencyX = 3,
24
+ frequencyY = 3,
25
+ ...viewProps
26
+ }: Props) {
27
+ const colors = useMemo(() => [color], [color]);
28
+ const params = useMemo(
29
+ () => [amplitude, frequencyX, frequencyY],
30
+ [amplitude, frequencyX, frequencyY]
31
+ );
32
+
33
+ return (
34
+ <ShaderView
35
+ fragmentShader={LIQUID_CHROME_SHADER}
36
+ colors={colors}
37
+ params={params}
38
+ speed={speed}
39
+ isStatic={false}
40
+ {...viewProps}
41
+ />
42
+ );
43
+ }
44
+
45
+ const LIQUID_CHROME_SHADER = /* wgsl */ `
46
+ struct Uniforms {
47
+ resolution: vec4<f32>,
48
+ time: vec4<f32>,
49
+ color0: vec4<f32>,
50
+ color1: vec4<f32>,
51
+ params0: vec4<f32>,
52
+ params1: vec4<f32>,
53
+ };
54
+ @group(0) @binding(0) var<uniform> u: Uniforms;
55
+
56
+ fn renderImage(uvCoord: vec2<f32>) -> vec4<f32> {
57
+ let resolution2D = u.resolution.xy;
58
+ let time = u.time.x;
59
+ let amplitude = u.params0.x;
60
+ let frequencyX = u.params0.y;
61
+ let frequencyY = u.params0.z;
62
+
63
+ let fragCoord = uvCoord * resolution2D;
64
+ var uv = (2.0 * fragCoord - resolution2D) / min(u.resolution.x, u.resolution.y);
65
+
66
+ for (var i: f32 = 1.0; i < 10.0; i = i + 1.0) {
67
+ uv.x = uv.x + amplitude / i * cos(i * frequencyX * uv.y + time);
68
+ uv.y = uv.y + amplitude / i * cos(i * frequencyY * uv.x + time);
69
+ }
70
+
71
+ let baseColor = u.color0;
72
+ let color = baseColor.rgb / abs(sin(time - uv.y - uv.x));
73
+ return vec4<f32>(color, baseColor.a);
74
+ }
75
+
76
+ @fragment
77
+ fn main(@location(0) ndc: vec2<f32>) -> @location(0) vec4<f32> {
78
+ let vUv = ndc * 0.5 + vec2<f32>(0.5, 0.5);
79
+ let resolution2D = u.resolution.xy;
80
+
81
+ var col = vec4<f32>(0.0);
82
+ var samples = 0;
83
+
84
+ for (var i: i32 = -1; i <= 1; i = i + 1) {
85
+ for (var j: i32 = -1; j <= 1; j = j + 1) {
86
+ let offset = vec2<f32>(f32(i), f32(j)) * (1.0 / min(resolution2D.x, resolution2D.y));
87
+ col = col + renderImage(vUv + offset);
88
+ samples = samples + 1;
89
+ }
90
+ }
91
+
92
+ return col / f32(samples);
93
+ }
94
+ `;
@@ -0,0 +1,262 @@
1
+ import { PixelRatio, StyleSheet } from 'react-native';
2
+ import { Canvas } from 'react-native-webgpu';
3
+ import { useEffect, useRef } from 'react';
4
+ import { createSynchronizable, scheduleOnRuntime } from 'react-native-worklets';
5
+ import { colorToVec4 } from '../../utils/colors';
6
+ import { useWGPUSetup } from '../../hooks/useWGPUSetup';
7
+ import { TRIANGLE_VERTEX_SHADER } from '../../shaders/TRIANGLE_VERTEX_SHADER';
8
+ import {
9
+ UNIFORM_BUFFER_SIZE,
10
+ UNIFORM_FLOAT_COUNT,
11
+ } from '../../shaders/uniforms';
12
+ import type { ShaderViewProps } from './types';
13
+
14
+ // Synchronizable layout: [c0r,c0g,c0b,c0a, c1r,c1g,c1b,c1a, speed, p0..p7, alive]
15
+ // Total: 4 + 4 + 1 + 8 + 1 = 18 floats
16
+ const SYNC_SIZE = 18;
17
+ const IDX_SPEED = 8;
18
+ const IDX_PARAMS = 9; // 9..16
19
+ const IDX_ALIVE = 17;
20
+
21
+ export default function ShaderView({
22
+ fragmentShader,
23
+ colors = [],
24
+ speed = 1.0,
25
+ params = [],
26
+ isStatic = false,
27
+ transparent = false,
28
+ paramsSynchronizable,
29
+ style,
30
+ ...viewProps
31
+ }: ShaderViewProps) {
32
+ const { canvasRef, runtime, resources } = useWGPUSetup();
33
+
34
+ const propsSync = useRef(
35
+ createSynchronizable<Float64Array>(new Float64Array(SYNC_SIZE))
36
+ ).current;
37
+
38
+ // Convert props to flat floats and push to synchronizable
39
+ useEffect(() => {
40
+ const data = new Float64Array(SYNC_SIZE);
41
+
42
+ // color0 (indices 0-3)
43
+ if (colors[0] !== undefined) {
44
+ const c0 = colorToVec4(colors[0]);
45
+ data[0] = c0.r;
46
+ data[1] = c0.g;
47
+ data[2] = c0.b;
48
+ data[3] = c0.a;
49
+ }
50
+
51
+ // color1 (indices 4-7)
52
+ if (colors[1] !== undefined) {
53
+ const c1 = colorToVec4(colors[1]);
54
+ data[4] = c1.r;
55
+ data[5] = c1.g;
56
+ data[6] = c1.b;
57
+ data[7] = c1.a;
58
+ }
59
+
60
+ // speed
61
+ data[IDX_SPEED] = speed;
62
+
63
+ // params (indices 9-16)
64
+ for (let i = 0; i < 8; i++) {
65
+ data[IDX_PARAMS + i] = params[i] ?? 0;
66
+ }
67
+
68
+ // alive
69
+ data[IDX_ALIVE] = 1;
70
+
71
+ propsSync.setBlocking(() => data);
72
+ }, [colors, speed, params, propsSync]);
73
+
74
+ // Signal cleanup on unmount
75
+ useEffect(() => {
76
+ return () => {
77
+ propsSync.setBlocking((prev) => {
78
+ prev[IDX_ALIVE] = 0;
79
+ return prev;
80
+ });
81
+ };
82
+ }, [propsSync]);
83
+
84
+ // Start render loop when GPU resources are ready
85
+ useEffect(() => {
86
+ if (!resources) {
87
+ return;
88
+ }
89
+
90
+ const { device, context, presentationFormat } = resources;
91
+ const dpr = PixelRatio.get();
92
+
93
+ // Per-run cancellation token. On Fast Refresh / dep change / unmount, React
94
+ // runs this effect's cleanup, which flips the flag and stops *this* loop —
95
+ // otherwise the old worklet RAF loop keeps running forever alongside the new
96
+ // one, stacking a duplicate render loop on every Metro reload.
97
+ const cancelled = createSynchronizable<Float64Array>(new Float64Array(1));
98
+
99
+ scheduleOnRuntime(runtime, () => {
100
+ 'worklet';
101
+
102
+ // Create pipeline once
103
+ const pipeline = device.createRenderPipeline({
104
+ layout: 'auto',
105
+ vertex: {
106
+ module: device.createShaderModule({ code: TRIANGLE_VERTEX_SHADER }),
107
+ entryPoint: 'main',
108
+ },
109
+ fragment: {
110
+ module: device.createShaderModule({ code: fragmentShader }),
111
+ entryPoint: 'main',
112
+ targets: [{ format: presentationFormat }],
113
+ },
114
+ primitive: { topology: 'triangle-list' },
115
+ });
116
+
117
+ // Create uniform buffer + bind group once, reuse via writeBuffer
118
+ const uniformBuffer = device.createBuffer({
119
+ size: UNIFORM_BUFFER_SIZE,
120
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
121
+ });
122
+
123
+ const bindGroup = device.createBindGroup({
124
+ layout: pipeline.getBindGroupLayout(0),
125
+ entries: [{ binding: 0, resource: { buffer: uniformBuffer } }],
126
+ });
127
+
128
+ const uniformData = new Float32Array(UNIFORM_FLOAT_COUNT);
129
+ let accumulatedTime = 0;
130
+ let lastTimestamp = 0;
131
+
132
+ function render(timestamp: number) {
133
+ const props = propsSync.getDirty();
134
+ if (props[IDX_ALIVE] === 0) {
135
+ return;
136
+ }
137
+
138
+ // This loop was superseded (Fast Refresh / unmount) — bail without
139
+ // scheduling another frame so it can be garbage-collected.
140
+ if (cancelled.getDirty()[0] === 1) {
141
+ return;
142
+ }
143
+
144
+ // Compute dt
145
+ const dt = lastTimestamp === 0 ? 0 : (timestamp - lastTimestamp) / 1000;
146
+ lastTimestamp = timestamp;
147
+
148
+ // Accumulate time with speed
149
+ const currentSpeed = props[IDX_SPEED]!;
150
+ accumulatedTime += dt * currentSpeed;
151
+
152
+ // Resolution
153
+ const canvas = context.canvas as typeof context.canvas & {
154
+ width: number;
155
+ height: number;
156
+ };
157
+ const width = canvas.width || 1;
158
+ const height = canvas.height || 1;
159
+ const aspect = width / height;
160
+
161
+ // Fill uniform data (7 × vec4 = 28 floats)
162
+ // resolution: vec4<f32>
163
+ uniformData[0] = width;
164
+ uniformData[1] = height;
165
+ uniformData[2] = aspect;
166
+ uniformData[3] = dpr;
167
+
168
+ // time: vec4<f32>
169
+ uniformData[4] = accumulatedTime;
170
+ uniformData[5] = dt;
171
+ uniformData[6] = 0;
172
+ uniformData[7] = 0;
173
+
174
+ // color0: vec4<f32>
175
+ uniformData[8] = props[0]!;
176
+ uniformData[9] = props[1]!;
177
+ uniformData[10] = props[2]!;
178
+ uniformData[11] = props[3]!;
179
+
180
+ // color1: vec4<f32>
181
+ uniformData[12] = props[4]!;
182
+ uniformData[13] = props[5]!;
183
+ uniformData[14] = props[6]!;
184
+ uniformData[15] = props[7]!;
185
+
186
+ // params0: vec4<f32>
187
+ uniformData[16] = props[IDX_PARAMS]!;
188
+ uniformData[17] = props[IDX_PARAMS + 1]!;
189
+ uniformData[18] = props[IDX_PARAMS + 2]!;
190
+ uniformData[19] = props[IDX_PARAMS + 3]!;
191
+
192
+ // params1: vec4<f32> — static params[4..7]
193
+ uniformData[20] = props[IDX_PARAMS + 4]!;
194
+ uniformData[21] = props[IDX_PARAMS + 5]!;
195
+ uniformData[22] = props[IDX_PARAMS + 6]!;
196
+ uniformData[23] = props[IDX_PARAMS + 7]!;
197
+
198
+ // live: vec4<f32> — off-thread input (touch/scroll/audio) from
199
+ // paramsSynchronizable, written into its own slot so it never collides
200
+ // with the static params. Stays (0,0,0,0) when no channel is attached.
201
+ if (paramsSynchronizable) {
202
+ const live = paramsSynchronizable.getDirty();
203
+ uniformData[24] = live[0]!;
204
+ uniformData[25] = live[1]!;
205
+ uniformData[26] = live[2]!;
206
+ uniformData[27] = live[3]!;
207
+ }
208
+
209
+ device.queue.writeBuffer(uniformBuffer, 0, uniformData);
210
+
211
+ const commandEncoder = device.createCommandEncoder();
212
+ const textureView = context.getCurrentTexture().createView();
213
+ const passEncoder = commandEncoder.beginRenderPass({
214
+ colorAttachments: [
215
+ {
216
+ view: textureView,
217
+ clearValue: transparent ? [0, 0, 0, 0] : [0, 0, 0, 1],
218
+ loadOp: 'clear',
219
+ storeOp: 'store',
220
+ },
221
+ ],
222
+ });
223
+
224
+ passEncoder.setPipeline(pipeline);
225
+ passEncoder.setBindGroup(0, bindGroup);
226
+ passEncoder.draw(3);
227
+ passEncoder.end();
228
+
229
+ device.queue.submit([commandEncoder.finish()]);
230
+ context.present();
231
+
232
+ if (!isStatic) {
233
+ requestAnimationFrame(render);
234
+ }
235
+ }
236
+
237
+ requestAnimationFrame(render);
238
+ });
239
+
240
+ return () => {
241
+ cancelled.setBlocking(() => Float64Array.of(1));
242
+ };
243
+ }, [
244
+ resources,
245
+ runtime,
246
+ propsSync,
247
+ paramsSynchronizable,
248
+ fragmentShader,
249
+ isStatic,
250
+ transparent,
251
+ ]);
252
+
253
+ return (
254
+ <Canvas ref={canvasRef} style={[styles.canvas, style]} {...viewProps} />
255
+ );
256
+ }
257
+
258
+ const styles = StyleSheet.create({
259
+ canvas: {
260
+ flex: 1,
261
+ },
262
+ });
@@ -0,0 +1,36 @@
1
+ import type { ViewProps } from 'react-native';
2
+ import type { Synchronizable } from 'react-native-worklets';
3
+ import type { ColorInput } from '../../utils/colors';
4
+
5
+ /**
6
+ * A 4-float synchronizable whose values are written into the dedicated `u.live`
7
+ * uniform slot every frame. It has its own slot, so it never collides with the
8
+ * 8 static `params` (`u.params0`/`u.params1`).
9
+ *
10
+ * This is the bridge for live, per-frame input (touch position, scroll
11
+ * progress, velocity) coming from the JS thread into the off-thread render
12
+ * loop. Create one with `useParamsSynchronizable` and update it from
13
+ * gesture/scroll handlers. See `ShaderViewWithPanGesture`.
14
+ */
15
+ export type ParamsSynchronizable = Synchronizable<Float64Array>;
16
+
17
+ export type ShaderViewProps = ViewProps & {
18
+ /** WGSL fragment shader source (must declare the Uniforms struct) */
19
+ fragmentShader: string;
20
+ /** Array of colors mapped to u.color0, u.color1 (max 2). Default: [] */
21
+ colors?: ColorInput[];
22
+ /** Time multiplier — controls animation speed. Default: 1.0 */
23
+ speed?: number;
24
+ /** Up to 8 shader-specific floats mapped to u.params0.xyzw and u.params1.xyzw */
25
+ params?: number[];
26
+ /** Render once then stop the RAF loop. Default: false */
27
+ isStatic?: boolean;
28
+ /** Use transparent background (clear to alpha 0). Default: false */
29
+ transparent?: boolean;
30
+ /**
31
+ * Optional live input. Its 4 floats are written into the dedicated `u.live`
32
+ * slot every frame — independent of the static `params`. Use for
33
+ * touch/scroll/audio. Create it with `useParamsSynchronizable`.
34
+ */
35
+ paramsSynchronizable?: ParamsSynchronizable;
36
+ };