react-shadertoy 0.1.0 → 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.
package/dist/index.d.mts CHANGED
@@ -1,9 +1,20 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { CSSProperties, RefObject } from 'react';
3
3
 
4
+ /** Texture source: URL string for image loading */
5
+ type TextureSource = string;
6
+ /** Texture inputs mapped to Shadertoy channels */
7
+ type TextureInputs = {
8
+ iChannel0?: TextureSource;
9
+ iChannel1?: TextureSource;
10
+ iChannel2?: TextureSource;
11
+ iChannel3?: TextureSource;
12
+ };
4
13
  interface ShadertoyProps {
5
14
  /** Shadertoy-compatible GLSL fragment shader (must contain mainImage) */
6
15
  fragmentShader: string;
16
+ /** Texture inputs for iChannel0-3 */
17
+ textures?: TextureInputs;
7
18
  /** Container style */
8
19
  style?: CSSProperties;
9
20
  /** Container className */
@@ -23,6 +34,7 @@ interface ShadertoyProps {
23
34
  }
24
35
  interface UseShadertoyOptions {
25
36
  fragmentShader: string;
37
+ textures?: TextureInputs;
26
38
  paused?: boolean;
27
39
  speed?: number;
28
40
  pixelRatio?: number;
@@ -38,8 +50,8 @@ interface UseShadertoyReturn {
38
50
  resume: () => void;
39
51
  }
40
52
 
41
- declare function Shadertoy({ fragmentShader, style, className, paused, speed, pixelRatio, mouse, onError, onLoad, }: ShadertoyProps): react_jsx_runtime.JSX.Element;
53
+ declare function Shadertoy({ fragmentShader, textures, style, className, paused, speed, pixelRatio, mouse, onError, onLoad, }: ShadertoyProps): react_jsx_runtime.JSX.Element;
42
54
 
43
- declare function useShadertoy({ fragmentShader, paused, speed, pixelRatio, mouse: mouseEnabled, onError, onLoad, }: UseShadertoyOptions): UseShadertoyReturn;
55
+ declare function useShadertoy({ fragmentShader, textures: texturesProp, paused, speed, pixelRatio, mouse: mouseEnabled, onError, onLoad, }: UseShadertoyOptions): UseShadertoyReturn;
44
56
 
45
- export { Shadertoy, type ShadertoyProps, type UseShadertoyOptions, type UseShadertoyReturn, useShadertoy };
57
+ export { Shadertoy, type ShadertoyProps, type TextureInputs, type UseShadertoyOptions, type UseShadertoyReturn, useShadertoy };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,20 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import { CSSProperties, RefObject } from 'react';
3
3
 
4
+ /** Texture source: URL string for image loading */
5
+ type TextureSource = string;
6
+ /** Texture inputs mapped to Shadertoy channels */
7
+ type TextureInputs = {
8
+ iChannel0?: TextureSource;
9
+ iChannel1?: TextureSource;
10
+ iChannel2?: TextureSource;
11
+ iChannel3?: TextureSource;
12
+ };
4
13
  interface ShadertoyProps {
5
14
  /** Shadertoy-compatible GLSL fragment shader (must contain mainImage) */
6
15
  fragmentShader: string;
16
+ /** Texture inputs for iChannel0-3 */
17
+ textures?: TextureInputs;
7
18
  /** Container style */
8
19
  style?: CSSProperties;
9
20
  /** Container className */
@@ -23,6 +34,7 @@ interface ShadertoyProps {
23
34
  }
24
35
  interface UseShadertoyOptions {
25
36
  fragmentShader: string;
37
+ textures?: TextureInputs;
26
38
  paused?: boolean;
27
39
  speed?: number;
28
40
  pixelRatio?: number;
@@ -38,8 +50,8 @@ interface UseShadertoyReturn {
38
50
  resume: () => void;
39
51
  }
40
52
 
41
- declare function Shadertoy({ fragmentShader, style, className, paused, speed, pixelRatio, mouse, onError, onLoad, }: ShadertoyProps): react_jsx_runtime.JSX.Element;
53
+ declare function Shadertoy({ fragmentShader, textures, style, className, paused, speed, pixelRatio, mouse, onError, onLoad, }: ShadertoyProps): react_jsx_runtime.JSX.Element;
42
54
 
43
- declare function useShadertoy({ fragmentShader, paused, speed, pixelRatio, mouse: mouseEnabled, onError, onLoad, }: UseShadertoyOptions): UseShadertoyReturn;
55
+ declare function useShadertoy({ fragmentShader, textures: texturesProp, paused, speed, pixelRatio, mouse: mouseEnabled, onError, onLoad, }: UseShadertoyOptions): UseShadertoyReturn;
44
56
 
45
- export { Shadertoy, type ShadertoyProps, type UseShadertoyOptions, type UseShadertoyReturn, useShadertoy };
57
+ export { Shadertoy, type ShadertoyProps, type TextureInputs, type UseShadertoyOptions, type UseShadertoyReturn, useShadertoy };
package/dist/index.js CHANGED
@@ -1,9 +1,32 @@
1
- 'use strict';
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
2
19
 
3
- var react = require('react');
4
- var jsxRuntime = require('react/jsx-runtime');
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ Shadertoy: () => Shadertoy,
24
+ useShadertoy: () => useShadertoy
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
5
27
 
6
28
  // src/useShadertoy.ts
29
+ var import_react = require("react");
7
30
 
8
31
  // src/renderer.ts
9
32
  var QUAD_VERTICES = new Float32Array([
@@ -35,6 +58,14 @@ uniform float iTimeDelta;
35
58
  uniform int iFrame;
36
59
  uniform vec4 iMouse;
37
60
  uniform vec4 iDate;
61
+ uniform sampler2D iChannel0;
62
+ uniform sampler2D iChannel1;
63
+ uniform sampler2D iChannel2;
64
+ uniform sampler2D iChannel3;
65
+ uniform vec3 iChannelResolution[4];
66
+
67
+ // Shadertoy compatibility: texture() is GLSL 300 es, WebGL1 uses texture2D()
68
+ #define texture texture2D
38
69
 
39
70
  ${shader}
40
71
 
@@ -90,13 +121,21 @@ function createRenderer(canvas, fragmentShader) {
90
121
  iTimeDelta: gl.getUniformLocation(program, "iTimeDelta"),
91
122
  iFrame: gl.getUniformLocation(program, "iFrame"),
92
123
  iMouse: gl.getUniformLocation(program, "iMouse"),
93
- iDate: gl.getUniformLocation(program, "iDate")
124
+ iDate: gl.getUniformLocation(program, "iDate"),
125
+ iChannel: [
126
+ gl.getUniformLocation(program, "iChannel0"),
127
+ gl.getUniformLocation(program, "iChannel1"),
128
+ gl.getUniformLocation(program, "iChannel2"),
129
+ gl.getUniformLocation(program, "iChannel3")
130
+ ],
131
+ iChannelResolution: gl.getUniformLocation(program, "iChannelResolution")
94
132
  };
95
133
  gl.useProgram(program);
96
134
  return {
97
135
  gl,
98
136
  program,
99
137
  locations,
138
+ textures: [null, null, null, null],
100
139
  time: 0,
101
140
  frame: 0,
102
141
  lastTime: 0
@@ -113,6 +152,72 @@ function dispose(state) {
113
152
  gl.getExtension("WEBGL_lose_context")?.loseContext();
114
153
  }
115
154
 
155
+ // src/textures.ts
156
+ function loadImageTexture(gl, url, unit) {
157
+ const texture = gl.createTexture();
158
+ gl.activeTexture(gl.TEXTURE0 + unit);
159
+ gl.bindTexture(gl.TEXTURE_2D, texture);
160
+ gl.texImage2D(
161
+ gl.TEXTURE_2D,
162
+ 0,
163
+ gl.RGBA,
164
+ 1,
165
+ 1,
166
+ 0,
167
+ gl.RGBA,
168
+ gl.UNSIGNED_BYTE,
169
+ new Uint8Array([255, 0, 255, 255])
170
+ );
171
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
172
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
173
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
174
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
175
+ const state = { texture, width: 1, height: 1, unit, loaded: false };
176
+ const promise = new Promise((resolve, reject) => {
177
+ const img = new Image();
178
+ img.crossOrigin = "anonymous";
179
+ img.onload = () => {
180
+ if (gl.isContextLost()) {
181
+ resolve();
182
+ return;
183
+ }
184
+ gl.activeTexture(gl.TEXTURE0 + unit);
185
+ gl.bindTexture(gl.TEXTURE_2D, texture);
186
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
187
+ if (isPOT(img.width) && isPOT(img.height)) {
188
+ gl.generateMipmap(gl.TEXTURE_2D);
189
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
190
+ }
191
+ state.width = img.width;
192
+ state.height = img.height;
193
+ state.loaded = true;
194
+ resolve();
195
+ };
196
+ img.onerror = () => reject(new Error(`Failed to load texture: ${url}`));
197
+ img.src = url;
198
+ });
199
+ return { state, promise };
200
+ }
201
+ function bindTextures(gl, locations, textures) {
202
+ for (let i = 0; i < 4; i++) {
203
+ const tex = textures[i];
204
+ if (!tex) continue;
205
+ gl.activeTexture(gl.TEXTURE0 + tex.unit);
206
+ gl.bindTexture(gl.TEXTURE_2D, tex.texture);
207
+ if (locations[i]) {
208
+ gl.uniform1i(locations[i], tex.unit);
209
+ }
210
+ }
211
+ }
212
+ function disposeTextures(gl, textures) {
213
+ for (const tex of textures) {
214
+ if (tex) gl.deleteTexture(tex.texture);
215
+ }
216
+ }
217
+ function isPOT(v) {
218
+ return (v & v - 1) === 0 && v > 0;
219
+ }
220
+
116
221
  // src/uniforms.ts
117
222
  function updateUniforms(state, delta, speed, mouse) {
118
223
  const { gl, locations } = state;
@@ -140,6 +245,18 @@ function updateUniforms(state, delta, speed, mouse) {
140
245
  const mw = mouse.pressed ? mouse.clickY : -Math.abs(mouse.clickY);
141
246
  gl.uniform4f(locations.iMouse, mouse.x, mouse.y, mz, mw);
142
247
  }
248
+ if (locations.iChannelResolution) {
249
+ const res = new Float32Array(12);
250
+ for (let i = 0; i < 4; i++) {
251
+ const tex = state.textures[i];
252
+ if (tex) {
253
+ res[i * 3] = tex.width;
254
+ res[i * 3 + 1] = tex.height;
255
+ res[i * 3 + 2] = 1;
256
+ }
257
+ }
258
+ gl.uniform3fv(locations.iChannelResolution, res);
259
+ }
143
260
  if (locations.iDate) {
144
261
  const now = /* @__PURE__ */ new Date();
145
262
  const seconds = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() + now.getMilliseconds() / 1e3;
@@ -155,8 +272,10 @@ function updateUniforms(state, delta, speed, mouse) {
155
272
  }
156
273
 
157
274
  // src/useShadertoy.ts
275
+ var CHANNEL_KEYS = ["iChannel0", "iChannel1", "iChannel2", "iChannel3"];
158
276
  function useShadertoy({
159
277
  fragmentShader,
278
+ textures: texturesProp,
160
279
  paused = false,
161
280
  speed = 1,
162
281
  pixelRatio,
@@ -164,14 +283,14 @@ function useShadertoy({
164
283
  onError,
165
284
  onLoad
166
285
  }) {
167
- const canvasRef = react.useRef(null);
168
- const rendererRef = react.useRef(null);
169
- const rafRef = react.useRef(0);
170
- const pausedRef = react.useRef(paused);
171
- const speedRef = react.useRef(speed);
172
- const [isReady, setIsReady] = react.useState(false);
173
- const [error, setError] = react.useState(null);
174
- const mouseState = react.useRef({
286
+ const canvasRef = (0, import_react.useRef)(null);
287
+ const rendererRef = (0, import_react.useRef)(null);
288
+ const rafRef = (0, import_react.useRef)(0);
289
+ const pausedRef = (0, import_react.useRef)(paused);
290
+ const speedRef = (0, import_react.useRef)(speed);
291
+ const [isReady, setIsReady] = (0, import_react.useState)(false);
292
+ const [error, setError] = (0, import_react.useState)(null);
293
+ const mouseState = (0, import_react.useRef)({
175
294
  x: 0,
176
295
  y: 0,
177
296
  clickX: 0,
@@ -180,7 +299,7 @@ function useShadertoy({
180
299
  });
181
300
  pausedRef.current = paused;
182
301
  speedRef.current = speed;
183
- react.useEffect(() => {
302
+ (0, import_react.useEffect)(() => {
184
303
  const canvas = canvasRef.current;
185
304
  if (!canvas) return;
186
305
  const result = createRenderer(canvas, fragmentShader);
@@ -190,16 +309,42 @@ function useShadertoy({
190
309
  return;
191
310
  }
192
311
  rendererRef.current = result;
193
- setIsReady(true);
194
- setError(null);
195
- onLoad?.();
312
+ const texturePromises = [];
313
+ if (texturesProp) {
314
+ for (let i = 0; i < 4; i++) {
315
+ const src = texturesProp[CHANNEL_KEYS[i]];
316
+ if (typeof src === "string") {
317
+ const { state, promise } = loadImageTexture(result.gl, src, i);
318
+ result.textures[i] = state;
319
+ texturePromises.push(promise);
320
+ }
321
+ }
322
+ }
323
+ const markReady = () => {
324
+ setIsReady(true);
325
+ setError(null);
326
+ onLoad?.();
327
+ };
328
+ if (texturePromises.length > 0) {
329
+ Promise.all(texturePromises).then(() => {
330
+ if (rendererRef.current) markReady();
331
+ }).catch((err) => {
332
+ const msg = err instanceof Error ? err.message : "Texture load failed";
333
+ setError(msg);
334
+ onError?.(msg);
335
+ });
336
+ } else {
337
+ markReady();
338
+ }
196
339
  let lastTimestamp = 0;
197
340
  const loop = (timestamp) => {
198
341
  const delta = lastTimestamp ? (timestamp - lastTimestamp) / 1e3 : 0;
199
342
  lastTimestamp = timestamp;
200
343
  if (!pausedRef.current && rendererRef.current) {
201
- updateUniforms(rendererRef.current, delta, speedRef.current, mouseState.current);
202
- render(rendererRef.current);
344
+ const r = rendererRef.current;
345
+ bindTextures(r.gl, r.locations.iChannel, r.textures);
346
+ updateUniforms(r, delta, speedRef.current, mouseState.current);
347
+ render(r);
203
348
  }
204
349
  rafRef.current = requestAnimationFrame(loop);
205
350
  };
@@ -207,13 +352,14 @@ function useShadertoy({
207
352
  return () => {
208
353
  cancelAnimationFrame(rafRef.current);
209
354
  if (rendererRef.current) {
355
+ disposeTextures(rendererRef.current.gl, rendererRef.current.textures);
210
356
  dispose(rendererRef.current);
211
357
  rendererRef.current = null;
212
358
  }
213
359
  setIsReady(false);
214
360
  };
215
- }, [fragmentShader, onError, onLoad]);
216
- react.useEffect(() => {
361
+ }, [fragmentShader, texturesProp, onError, onLoad]);
362
+ (0, import_react.useEffect)(() => {
217
363
  const canvas = canvasRef.current;
218
364
  if (!canvas) return;
219
365
  const dpr = pixelRatio ?? (typeof window !== "undefined" ? window.devicePixelRatio : 1);
@@ -227,7 +373,7 @@ function useShadertoy({
227
373
  observer.observe(canvas);
228
374
  return () => observer.disconnect();
229
375
  }, [pixelRatio]);
230
- react.useEffect(() => {
376
+ (0, import_react.useEffect)(() => {
231
377
  if (!mouseEnabled) return;
232
378
  const canvas = canvasRef.current;
233
379
  if (!canvas) return;
@@ -282,16 +428,20 @@ function useShadertoy({
282
428
  window.removeEventListener("touchend", te);
283
429
  };
284
430
  }, [mouseEnabled, pixelRatio]);
285
- const pause = react.useCallback(() => {
431
+ const pause = (0, import_react.useCallback)(() => {
286
432
  pausedRef.current = true;
287
433
  }, []);
288
- const resume = react.useCallback(() => {
434
+ const resume = (0, import_react.useCallback)(() => {
289
435
  pausedRef.current = false;
290
436
  }, []);
291
437
  return { canvasRef, isReady, error, pause, resume };
292
438
  }
439
+
440
+ // src/Shadertoy.tsx
441
+ var import_jsx_runtime = require("react/jsx-runtime");
293
442
  function Shadertoy({
294
443
  fragmentShader,
444
+ textures,
295
445
  style,
296
446
  className,
297
447
  paused,
@@ -303,6 +453,7 @@ function Shadertoy({
303
453
  }) {
304
454
  const { canvasRef } = useShadertoy({
305
455
  fragmentShader,
456
+ textures,
306
457
  paused,
307
458
  speed,
308
459
  pixelRatio,
@@ -310,7 +461,7 @@ function Shadertoy({
310
461
  onError,
311
462
  onLoad
312
463
  });
313
- return /* @__PURE__ */ jsxRuntime.jsx(
464
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
314
465
  "canvas",
315
466
  {
316
467
  ref: canvasRef,
@@ -319,8 +470,8 @@ function Shadertoy({
319
470
  }
320
471
  );
321
472
  }
322
-
323
- exports.Shadertoy = Shadertoy;
324
- exports.useShadertoy = useShadertoy;
325
- //# sourceMappingURL=index.js.map
326
- //# sourceMappingURL=index.js.map
473
+ // Annotate the CommonJS export names for ESM import in node:
474
+ 0 && (module.exports = {
475
+ Shadertoy,
476
+ useShadertoy
477
+ });
package/dist/index.mjs CHANGED
@@ -1,7 +1,5 @@
1
- import { useRef, useState, useEffect, useCallback } from 'react';
2
- import { jsx } from 'react/jsx-runtime';
3
-
4
1
  // src/useShadertoy.ts
2
+ import { useCallback, useEffect, useRef, useState } from "react";
5
3
 
6
4
  // src/renderer.ts
7
5
  var QUAD_VERTICES = new Float32Array([
@@ -33,6 +31,14 @@ uniform float iTimeDelta;
33
31
  uniform int iFrame;
34
32
  uniform vec4 iMouse;
35
33
  uniform vec4 iDate;
34
+ uniform sampler2D iChannel0;
35
+ uniform sampler2D iChannel1;
36
+ uniform sampler2D iChannel2;
37
+ uniform sampler2D iChannel3;
38
+ uniform vec3 iChannelResolution[4];
39
+
40
+ // Shadertoy compatibility: texture() is GLSL 300 es, WebGL1 uses texture2D()
41
+ #define texture texture2D
36
42
 
37
43
  ${shader}
38
44
 
@@ -88,13 +94,21 @@ function createRenderer(canvas, fragmentShader) {
88
94
  iTimeDelta: gl.getUniformLocation(program, "iTimeDelta"),
89
95
  iFrame: gl.getUniformLocation(program, "iFrame"),
90
96
  iMouse: gl.getUniformLocation(program, "iMouse"),
91
- iDate: gl.getUniformLocation(program, "iDate")
97
+ iDate: gl.getUniformLocation(program, "iDate"),
98
+ iChannel: [
99
+ gl.getUniformLocation(program, "iChannel0"),
100
+ gl.getUniformLocation(program, "iChannel1"),
101
+ gl.getUniformLocation(program, "iChannel2"),
102
+ gl.getUniformLocation(program, "iChannel3")
103
+ ],
104
+ iChannelResolution: gl.getUniformLocation(program, "iChannelResolution")
92
105
  };
93
106
  gl.useProgram(program);
94
107
  return {
95
108
  gl,
96
109
  program,
97
110
  locations,
111
+ textures: [null, null, null, null],
98
112
  time: 0,
99
113
  frame: 0,
100
114
  lastTime: 0
@@ -111,6 +125,72 @@ function dispose(state) {
111
125
  gl.getExtension("WEBGL_lose_context")?.loseContext();
112
126
  }
113
127
 
128
+ // src/textures.ts
129
+ function loadImageTexture(gl, url, unit) {
130
+ const texture = gl.createTexture();
131
+ gl.activeTexture(gl.TEXTURE0 + unit);
132
+ gl.bindTexture(gl.TEXTURE_2D, texture);
133
+ gl.texImage2D(
134
+ gl.TEXTURE_2D,
135
+ 0,
136
+ gl.RGBA,
137
+ 1,
138
+ 1,
139
+ 0,
140
+ gl.RGBA,
141
+ gl.UNSIGNED_BYTE,
142
+ new Uint8Array([255, 0, 255, 255])
143
+ );
144
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
145
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
146
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
147
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
148
+ const state = { texture, width: 1, height: 1, unit, loaded: false };
149
+ const promise = new Promise((resolve, reject) => {
150
+ const img = new Image();
151
+ img.crossOrigin = "anonymous";
152
+ img.onload = () => {
153
+ if (gl.isContextLost()) {
154
+ resolve();
155
+ return;
156
+ }
157
+ gl.activeTexture(gl.TEXTURE0 + unit);
158
+ gl.bindTexture(gl.TEXTURE_2D, texture);
159
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
160
+ if (isPOT(img.width) && isPOT(img.height)) {
161
+ gl.generateMipmap(gl.TEXTURE_2D);
162
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
163
+ }
164
+ state.width = img.width;
165
+ state.height = img.height;
166
+ state.loaded = true;
167
+ resolve();
168
+ };
169
+ img.onerror = () => reject(new Error(`Failed to load texture: ${url}`));
170
+ img.src = url;
171
+ });
172
+ return { state, promise };
173
+ }
174
+ function bindTextures(gl, locations, textures) {
175
+ for (let i = 0; i < 4; i++) {
176
+ const tex = textures[i];
177
+ if (!tex) continue;
178
+ gl.activeTexture(gl.TEXTURE0 + tex.unit);
179
+ gl.bindTexture(gl.TEXTURE_2D, tex.texture);
180
+ if (locations[i]) {
181
+ gl.uniform1i(locations[i], tex.unit);
182
+ }
183
+ }
184
+ }
185
+ function disposeTextures(gl, textures) {
186
+ for (const tex of textures) {
187
+ if (tex) gl.deleteTexture(tex.texture);
188
+ }
189
+ }
190
+ function isPOT(v) {
191
+ return (v & v - 1) === 0 && v > 0;
192
+ }
193
+
114
194
  // src/uniforms.ts
115
195
  function updateUniforms(state, delta, speed, mouse) {
116
196
  const { gl, locations } = state;
@@ -138,6 +218,18 @@ function updateUniforms(state, delta, speed, mouse) {
138
218
  const mw = mouse.pressed ? mouse.clickY : -Math.abs(mouse.clickY);
139
219
  gl.uniform4f(locations.iMouse, mouse.x, mouse.y, mz, mw);
140
220
  }
221
+ if (locations.iChannelResolution) {
222
+ const res = new Float32Array(12);
223
+ for (let i = 0; i < 4; i++) {
224
+ const tex = state.textures[i];
225
+ if (tex) {
226
+ res[i * 3] = tex.width;
227
+ res[i * 3 + 1] = tex.height;
228
+ res[i * 3 + 2] = 1;
229
+ }
230
+ }
231
+ gl.uniform3fv(locations.iChannelResolution, res);
232
+ }
141
233
  if (locations.iDate) {
142
234
  const now = /* @__PURE__ */ new Date();
143
235
  const seconds = now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() + now.getMilliseconds() / 1e3;
@@ -153,8 +245,10 @@ function updateUniforms(state, delta, speed, mouse) {
153
245
  }
154
246
 
155
247
  // src/useShadertoy.ts
248
+ var CHANNEL_KEYS = ["iChannel0", "iChannel1", "iChannel2", "iChannel3"];
156
249
  function useShadertoy({
157
250
  fragmentShader,
251
+ textures: texturesProp,
158
252
  paused = false,
159
253
  speed = 1,
160
254
  pixelRatio,
@@ -188,16 +282,42 @@ function useShadertoy({
188
282
  return;
189
283
  }
190
284
  rendererRef.current = result;
191
- setIsReady(true);
192
- setError(null);
193
- onLoad?.();
285
+ const texturePromises = [];
286
+ if (texturesProp) {
287
+ for (let i = 0; i < 4; i++) {
288
+ const src = texturesProp[CHANNEL_KEYS[i]];
289
+ if (typeof src === "string") {
290
+ const { state, promise } = loadImageTexture(result.gl, src, i);
291
+ result.textures[i] = state;
292
+ texturePromises.push(promise);
293
+ }
294
+ }
295
+ }
296
+ const markReady = () => {
297
+ setIsReady(true);
298
+ setError(null);
299
+ onLoad?.();
300
+ };
301
+ if (texturePromises.length > 0) {
302
+ Promise.all(texturePromises).then(() => {
303
+ if (rendererRef.current) markReady();
304
+ }).catch((err) => {
305
+ const msg = err instanceof Error ? err.message : "Texture load failed";
306
+ setError(msg);
307
+ onError?.(msg);
308
+ });
309
+ } else {
310
+ markReady();
311
+ }
194
312
  let lastTimestamp = 0;
195
313
  const loop = (timestamp) => {
196
314
  const delta = lastTimestamp ? (timestamp - lastTimestamp) / 1e3 : 0;
197
315
  lastTimestamp = timestamp;
198
316
  if (!pausedRef.current && rendererRef.current) {
199
- updateUniforms(rendererRef.current, delta, speedRef.current, mouseState.current);
200
- render(rendererRef.current);
317
+ const r = rendererRef.current;
318
+ bindTextures(r.gl, r.locations.iChannel, r.textures);
319
+ updateUniforms(r, delta, speedRef.current, mouseState.current);
320
+ render(r);
201
321
  }
202
322
  rafRef.current = requestAnimationFrame(loop);
203
323
  };
@@ -205,12 +325,13 @@ function useShadertoy({
205
325
  return () => {
206
326
  cancelAnimationFrame(rafRef.current);
207
327
  if (rendererRef.current) {
328
+ disposeTextures(rendererRef.current.gl, rendererRef.current.textures);
208
329
  dispose(rendererRef.current);
209
330
  rendererRef.current = null;
210
331
  }
211
332
  setIsReady(false);
212
333
  };
213
- }, [fragmentShader, onError, onLoad]);
334
+ }, [fragmentShader, texturesProp, onError, onLoad]);
214
335
  useEffect(() => {
215
336
  const canvas = canvasRef.current;
216
337
  if (!canvas) return;
@@ -288,8 +409,12 @@ function useShadertoy({
288
409
  }, []);
289
410
  return { canvasRef, isReady, error, pause, resume };
290
411
  }
412
+
413
+ // src/Shadertoy.tsx
414
+ import { jsx } from "react/jsx-runtime";
291
415
  function Shadertoy({
292
416
  fragmentShader,
417
+ textures,
293
418
  style,
294
419
  className,
295
420
  paused,
@@ -301,6 +426,7 @@ function Shadertoy({
301
426
  }) {
302
427
  const { canvasRef } = useShadertoy({
303
428
  fragmentShader,
429
+ textures,
304
430
  paused,
305
431
  speed,
306
432
  pixelRatio,
@@ -317,7 +443,7 @@ function Shadertoy({
317
443
  }
318
444
  );
319
445
  }
320
-
321
- export { Shadertoy, useShadertoy };
322
- //# sourceMappingURL=index.mjs.map
323
- //# sourceMappingURL=index.mjs.map
446
+ export {
447
+ Shadertoy,
448
+ useShadertoy
449
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-shadertoy",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Run Shadertoy GLSL shaders in React. Copy-paste and it works.",
5
5
  "author": "Wrennly (https://github.com/wrennly)",
6
6
  "license": "MIT",
package/dist/index.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/renderer.ts","../src/uniforms.ts","../src/useShadertoy.ts","../src/Shadertoy.tsx"],"names":["useRef","useState","useEffect","useCallback","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import type { RendererState, UniformLocations } from './types'\n\n// Full-screen quad: two triangles covering clip space\nconst QUAD_VERTICES = new Float32Array([\n -1, -1,\n 1, -1,\n -1, 1,\n -1, 1,\n 1, -1,\n 1, 1,\n])\n\nconst VERTEX_SHADER = `\nattribute vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`\n\n/**\n * Wrap Shadertoy GLSL: prepend uniform declarations + main() bridge.\n */\nfunction wrapFragmentShader(shader: string): string {\n return `precision highp float;\n\nuniform vec3 iResolution;\nuniform float iTime;\nuniform float iTimeDelta;\nuniform int iFrame;\nuniform vec4 iMouse;\nuniform vec4 iDate;\n\n${shader}\n\nvoid main() {\n mainImage(gl_FragColor, gl_FragCoord.xy);\n}\n`\n}\n\nfunction compileShader(\n gl: WebGLRenderingContext,\n type: number,\n source: string,\n): WebGLShader | string {\n const shader = gl.createShader(type)\n if (!shader) return 'Failed to create shader'\n\n gl.shaderSource(shader, source)\n gl.compileShader(shader)\n\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader) || 'Unknown compile error'\n gl.deleteShader(shader)\n return log\n }\n\n return shader\n}\n\n/**\n * Initialize WebGL: compile shaders, link program, setup quad.\n * Returns RendererState on success or error string on failure.\n */\nexport function createRenderer(\n canvas: HTMLCanvasElement,\n fragmentShader: string,\n): RendererState | string {\n const gl = canvas.getContext('webgl', {\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n })\n if (!gl) return 'WebGL not supported'\n\n // Compile vertex shader\n const vert = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER)\n if (typeof vert === 'string') return vert\n\n // Compile fragment shader (wrapped)\n const frag = compileShader(gl, gl.FRAGMENT_SHADER, wrapFragmentShader(fragmentShader))\n if (typeof frag === 'string') return frag\n\n // Link program\n const program = gl.createProgram()\n if (!program) return 'Failed to create program'\n\n gl.attachShader(program, vert)\n gl.attachShader(program, frag)\n gl.linkProgram(program)\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(program) || 'Unknown link error'\n gl.deleteProgram(program)\n return log\n }\n\n // Clean up individual shaders (attached to program)\n gl.deleteShader(vert)\n gl.deleteShader(frag)\n\n // Setup quad geometry\n const buffer = gl.createBuffer()\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer)\n gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTICES, gl.STATIC_DRAW)\n\n const positionLoc = gl.getAttribLocation(program, 'position')\n gl.enableVertexAttribArray(positionLoc)\n gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0)\n\n // Get uniform locations\n const locations: UniformLocations = {\n iResolution: gl.getUniformLocation(program, 'iResolution'),\n iTime: gl.getUniformLocation(program, 'iTime'),\n iTimeDelta: gl.getUniformLocation(program, 'iTimeDelta'),\n iFrame: gl.getUniformLocation(program, 'iFrame'),\n iMouse: gl.getUniformLocation(program, 'iMouse'),\n iDate: gl.getUniformLocation(program, 'iDate'),\n }\n\n gl.useProgram(program)\n\n return {\n gl,\n program,\n locations,\n time: 0,\n frame: 0,\n lastTime: 0,\n }\n}\n\n/**\n * Render one frame.\n */\nexport function render(state: RendererState): void {\n const { gl } = state\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)\n gl.drawArrays(gl.TRIANGLES, 0, 6)\n}\n\n/**\n * Clean up WebGL resources.\n */\nexport function dispose(state: RendererState): void {\n const { gl, program } = state\n gl.deleteProgram(program)\n gl.getExtension('WEBGL_lose_context')?.loseContext()\n}\n","import type { MouseState, RendererState } from './types'\n\n/**\n * Update all Shadertoy standard uniforms for one frame.\n */\nexport function updateUniforms(\n state: RendererState,\n delta: number,\n speed: number,\n mouse: MouseState,\n): void {\n const { gl, locations } = state\n\n // iTime\n state.time += delta * speed\n if (locations.iTime) {\n gl.uniform1f(locations.iTime, state.time)\n }\n\n // iTimeDelta\n if (locations.iTimeDelta) {\n gl.uniform1f(locations.iTimeDelta, delta)\n }\n\n // iFrame\n state.frame++\n if (locations.iFrame) {\n gl.uniform1i(locations.iFrame, state.frame)\n }\n\n // iResolution\n if (locations.iResolution) {\n gl.uniform3f(\n locations.iResolution,\n gl.drawingBufferWidth,\n gl.drawingBufferHeight,\n 1.0,\n )\n }\n\n // iMouse — Shadertoy convention:\n // xy: current position (if pressed)\n // zw: click position (positive when pressed, negative when released)\n if (locations.iMouse) {\n const mz = mouse.pressed ? mouse.clickX : -Math.abs(mouse.clickX)\n const mw = mouse.pressed ? mouse.clickY : -Math.abs(mouse.clickY)\n gl.uniform4f(locations.iMouse, mouse.x, mouse.y, mz, mw)\n }\n\n // iDate — vec4(year, month, day, seconds_since_midnight)\n if (locations.iDate) {\n const now = new Date()\n const seconds =\n now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() + now.getMilliseconds() / 1000\n gl.uniform4f(\n locations.iDate,\n now.getFullYear(),\n now.getMonth(), // 0-based, matches Shadertoy\n now.getDate(),\n seconds,\n )\n }\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { createRenderer, dispose, render } from './renderer'\nimport type { MouseState, RendererState, UseShadertoyOptions, UseShadertoyReturn } from './types'\nimport { updateUniforms } from './uniforms'\n\nexport function useShadertoy({\n fragmentShader,\n paused = false,\n speed = 1.0,\n pixelRatio,\n mouse: mouseEnabled = true,\n onError,\n onLoad,\n}: UseShadertoyOptions): UseShadertoyReturn {\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const rendererRef = useRef<RendererState | null>(null)\n const rafRef = useRef<number>(0)\n const pausedRef = useRef(paused)\n const speedRef = useRef(speed)\n\n const [isReady, setIsReady] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const mouseState = useRef<MouseState>({\n x: 0, y: 0,\n clickX: 0, clickY: 0,\n pressed: false,\n })\n\n // Keep refs in sync\n pausedRef.current = paused\n speedRef.current = speed\n\n // Initialize WebGL\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const result = createRenderer(canvas, fragmentShader)\n if (typeof result === 'string') {\n setError(result)\n onError?.(result)\n return\n }\n\n rendererRef.current = result\n setIsReady(true)\n setError(null)\n onLoad?.()\n\n // Render loop\n let lastTimestamp = 0\n\n const loop = (timestamp: number) => {\n const delta = lastTimestamp ? (timestamp - lastTimestamp) / 1000 : 0\n lastTimestamp = timestamp\n\n if (!pausedRef.current && rendererRef.current) {\n updateUniforms(rendererRef.current, delta, speedRef.current, mouseState.current)\n render(rendererRef.current)\n }\n\n rafRef.current = requestAnimationFrame(loop)\n }\n\n rafRef.current = requestAnimationFrame(loop)\n\n return () => {\n cancelAnimationFrame(rafRef.current)\n if (rendererRef.current) {\n dispose(rendererRef.current)\n rendererRef.current = null\n }\n setIsReady(false)\n }\n }, [fragmentShader, onError, onLoad])\n\n // Canvas resize\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const dpr = pixelRatio ?? (typeof window !== 'undefined' ? window.devicePixelRatio : 1)\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width, height } = entry.contentRect\n canvas.width = Math.round(width * dpr)\n canvas.height = Math.round(height * dpr)\n }\n })\n\n observer.observe(canvas)\n return () => observer.disconnect()\n }, [pixelRatio])\n\n // Mouse / touch events\n useEffect(() => {\n if (!mouseEnabled) return\n const canvas = canvasRef.current\n if (!canvas) return\n\n const toPixel = (cx: number, cy: number) => {\n const r = canvas.getBoundingClientRect()\n const dpr = pixelRatio ?? window.devicePixelRatio\n return {\n x: (cx - r.left) * dpr,\n y: (r.height - (cy - r.top)) * dpr, // flip Y\n }\n }\n\n const onMove = (cx: number, cy: number) => {\n if (!mouseState.current.pressed) return\n const { x, y } = toPixel(cx, cy)\n mouseState.current.x = x\n mouseState.current.y = y\n }\n\n const onDown = (cx: number, cy: number) => {\n const { x, y } = toPixel(cx, cy)\n mouseState.current.x = x\n mouseState.current.y = y\n mouseState.current.clickX = x\n mouseState.current.clickY = y\n mouseState.current.pressed = true\n }\n\n const onUp = () => {\n mouseState.current.pressed = false\n }\n\n const mm = (e: MouseEvent) => onMove(e.clientX, e.clientY)\n const md = (e: MouseEvent) => onDown(e.clientX, e.clientY)\n const mu = () => onUp()\n const tm = (e: TouchEvent) => {\n if (e.touches[0]) onMove(e.touches[0].clientX, e.touches[0].clientY)\n }\n const ts = (e: TouchEvent) => {\n if (e.touches[0]) onDown(e.touches[0].clientX, e.touches[0].clientY)\n }\n const te = () => onUp()\n\n window.addEventListener('mousemove', mm)\n canvas.addEventListener('mousedown', md)\n window.addEventListener('mouseup', mu)\n window.addEventListener('touchmove', tm, { passive: true })\n canvas.addEventListener('touchstart', ts, { passive: true })\n window.addEventListener('touchend', te)\n\n return () => {\n window.removeEventListener('mousemove', mm)\n canvas.removeEventListener('mousedown', md)\n window.removeEventListener('mouseup', mu)\n window.removeEventListener('touchmove', tm)\n canvas.removeEventListener('touchstart', ts)\n window.removeEventListener('touchend', te)\n }\n }, [mouseEnabled, pixelRatio])\n\n const pause = useCallback(() => { pausedRef.current = true }, [])\n const resume = useCallback(() => { pausedRef.current = false }, [])\n\n return { canvasRef, isReady, error, pause, resume }\n}\n","import type { ShadertoyProps } from './types'\nimport { useShadertoy } from './useShadertoy'\n\nexport function Shadertoy({\n fragmentShader,\n style,\n className,\n paused,\n speed,\n pixelRatio,\n mouse,\n onError,\n onLoad,\n}: ShadertoyProps) {\n const { canvasRef } = useShadertoy({\n fragmentShader,\n paused,\n speed,\n pixelRatio,\n mouse,\n onError,\n onLoad,\n })\n\n return (\n <canvas\n ref={canvasRef}\n className={className}\n style={{ width: '100%', height: '100%', display: 'block', ...style }}\n />\n )\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/renderer.ts","../src/uniforms.ts","../src/useShadertoy.ts","../src/Shadertoy.tsx"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["import type { RendererState, UniformLocations } from './types'\n\n// Full-screen quad: two triangles covering clip space\nconst QUAD_VERTICES = new Float32Array([\n -1, -1,\n 1, -1,\n -1, 1,\n -1, 1,\n 1, -1,\n 1, 1,\n])\n\nconst VERTEX_SHADER = `\nattribute vec2 position;\nvoid main() {\n gl_Position = vec4(position, 0.0, 1.0);\n}\n`\n\n/**\n * Wrap Shadertoy GLSL: prepend uniform declarations + main() bridge.\n */\nfunction wrapFragmentShader(shader: string): string {\n return `precision highp float;\n\nuniform vec3 iResolution;\nuniform float iTime;\nuniform float iTimeDelta;\nuniform int iFrame;\nuniform vec4 iMouse;\nuniform vec4 iDate;\n\n${shader}\n\nvoid main() {\n mainImage(gl_FragColor, gl_FragCoord.xy);\n}\n`\n}\n\nfunction compileShader(\n gl: WebGLRenderingContext,\n type: number,\n source: string,\n): WebGLShader | string {\n const shader = gl.createShader(type)\n if (!shader) return 'Failed to create shader'\n\n gl.shaderSource(shader, source)\n gl.compileShader(shader)\n\n if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {\n const log = gl.getShaderInfoLog(shader) || 'Unknown compile error'\n gl.deleteShader(shader)\n return log\n }\n\n return shader\n}\n\n/**\n * Initialize WebGL: compile shaders, link program, setup quad.\n * Returns RendererState on success or error string on failure.\n */\nexport function createRenderer(\n canvas: HTMLCanvasElement,\n fragmentShader: string,\n): RendererState | string {\n const gl = canvas.getContext('webgl', {\n antialias: false,\n alpha: true,\n premultipliedAlpha: false,\n })\n if (!gl) return 'WebGL not supported'\n\n // Compile vertex shader\n const vert = compileShader(gl, gl.VERTEX_SHADER, VERTEX_SHADER)\n if (typeof vert === 'string') return vert\n\n // Compile fragment shader (wrapped)\n const frag = compileShader(gl, gl.FRAGMENT_SHADER, wrapFragmentShader(fragmentShader))\n if (typeof frag === 'string') return frag\n\n // Link program\n const program = gl.createProgram()\n if (!program) return 'Failed to create program'\n\n gl.attachShader(program, vert)\n gl.attachShader(program, frag)\n gl.linkProgram(program)\n\n if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {\n const log = gl.getProgramInfoLog(program) || 'Unknown link error'\n gl.deleteProgram(program)\n return log\n }\n\n // Clean up individual shaders (attached to program)\n gl.deleteShader(vert)\n gl.deleteShader(frag)\n\n // Setup quad geometry\n const buffer = gl.createBuffer()\n gl.bindBuffer(gl.ARRAY_BUFFER, buffer)\n gl.bufferData(gl.ARRAY_BUFFER, QUAD_VERTICES, gl.STATIC_DRAW)\n\n const positionLoc = gl.getAttribLocation(program, 'position')\n gl.enableVertexAttribArray(positionLoc)\n gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0)\n\n // Get uniform locations\n const locations: UniformLocations = {\n iResolution: gl.getUniformLocation(program, 'iResolution'),\n iTime: gl.getUniformLocation(program, 'iTime'),\n iTimeDelta: gl.getUniformLocation(program, 'iTimeDelta'),\n iFrame: gl.getUniformLocation(program, 'iFrame'),\n iMouse: gl.getUniformLocation(program, 'iMouse'),\n iDate: gl.getUniformLocation(program, 'iDate'),\n }\n\n gl.useProgram(program)\n\n return {\n gl,\n program,\n locations,\n time: 0,\n frame: 0,\n lastTime: 0,\n }\n}\n\n/**\n * Render one frame.\n */\nexport function render(state: RendererState): void {\n const { gl } = state\n gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)\n gl.drawArrays(gl.TRIANGLES, 0, 6)\n}\n\n/**\n * Clean up WebGL resources.\n */\nexport function dispose(state: RendererState): void {\n const { gl, program } = state\n gl.deleteProgram(program)\n gl.getExtension('WEBGL_lose_context')?.loseContext()\n}\n","import type { MouseState, RendererState } from './types'\n\n/**\n * Update all Shadertoy standard uniforms for one frame.\n */\nexport function updateUniforms(\n state: RendererState,\n delta: number,\n speed: number,\n mouse: MouseState,\n): void {\n const { gl, locations } = state\n\n // iTime\n state.time += delta * speed\n if (locations.iTime) {\n gl.uniform1f(locations.iTime, state.time)\n }\n\n // iTimeDelta\n if (locations.iTimeDelta) {\n gl.uniform1f(locations.iTimeDelta, delta)\n }\n\n // iFrame\n state.frame++\n if (locations.iFrame) {\n gl.uniform1i(locations.iFrame, state.frame)\n }\n\n // iResolution\n if (locations.iResolution) {\n gl.uniform3f(\n locations.iResolution,\n gl.drawingBufferWidth,\n gl.drawingBufferHeight,\n 1.0,\n )\n }\n\n // iMouse — Shadertoy convention:\n // xy: current position (if pressed)\n // zw: click position (positive when pressed, negative when released)\n if (locations.iMouse) {\n const mz = mouse.pressed ? mouse.clickX : -Math.abs(mouse.clickX)\n const mw = mouse.pressed ? mouse.clickY : -Math.abs(mouse.clickY)\n gl.uniform4f(locations.iMouse, mouse.x, mouse.y, mz, mw)\n }\n\n // iDate — vec4(year, month, day, seconds_since_midnight)\n if (locations.iDate) {\n const now = new Date()\n const seconds =\n now.getHours() * 3600 + now.getMinutes() * 60 + now.getSeconds() + now.getMilliseconds() / 1000\n gl.uniform4f(\n locations.iDate,\n now.getFullYear(),\n now.getMonth(), // 0-based, matches Shadertoy\n now.getDate(),\n seconds,\n )\n }\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\nimport { createRenderer, dispose, render } from './renderer'\nimport type { MouseState, RendererState, UseShadertoyOptions, UseShadertoyReturn } from './types'\nimport { updateUniforms } from './uniforms'\n\nexport function useShadertoy({\n fragmentShader,\n paused = false,\n speed = 1.0,\n pixelRatio,\n mouse: mouseEnabled = true,\n onError,\n onLoad,\n}: UseShadertoyOptions): UseShadertoyReturn {\n const canvasRef = useRef<HTMLCanvasElement | null>(null)\n const rendererRef = useRef<RendererState | null>(null)\n const rafRef = useRef<number>(0)\n const pausedRef = useRef(paused)\n const speedRef = useRef(speed)\n\n const [isReady, setIsReady] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n const mouseState = useRef<MouseState>({\n x: 0, y: 0,\n clickX: 0, clickY: 0,\n pressed: false,\n })\n\n // Keep refs in sync\n pausedRef.current = paused\n speedRef.current = speed\n\n // Initialize WebGL\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const result = createRenderer(canvas, fragmentShader)\n if (typeof result === 'string') {\n setError(result)\n onError?.(result)\n return\n }\n\n rendererRef.current = result\n setIsReady(true)\n setError(null)\n onLoad?.()\n\n // Render loop\n let lastTimestamp = 0\n\n const loop = (timestamp: number) => {\n const delta = lastTimestamp ? (timestamp - lastTimestamp) / 1000 : 0\n lastTimestamp = timestamp\n\n if (!pausedRef.current && rendererRef.current) {\n updateUniforms(rendererRef.current, delta, speedRef.current, mouseState.current)\n render(rendererRef.current)\n }\n\n rafRef.current = requestAnimationFrame(loop)\n }\n\n rafRef.current = requestAnimationFrame(loop)\n\n return () => {\n cancelAnimationFrame(rafRef.current)\n if (rendererRef.current) {\n dispose(rendererRef.current)\n rendererRef.current = null\n }\n setIsReady(false)\n }\n }, [fragmentShader, onError, onLoad])\n\n // Canvas resize\n useEffect(() => {\n const canvas = canvasRef.current\n if (!canvas) return\n\n const dpr = pixelRatio ?? (typeof window !== 'undefined' ? window.devicePixelRatio : 1)\n\n const observer = new ResizeObserver((entries) => {\n for (const entry of entries) {\n const { width, height } = entry.contentRect\n canvas.width = Math.round(width * dpr)\n canvas.height = Math.round(height * dpr)\n }\n })\n\n observer.observe(canvas)\n return () => observer.disconnect()\n }, [pixelRatio])\n\n // Mouse / touch events\n useEffect(() => {\n if (!mouseEnabled) return\n const canvas = canvasRef.current\n if (!canvas) return\n\n const toPixel = (cx: number, cy: number) => {\n const r = canvas.getBoundingClientRect()\n const dpr = pixelRatio ?? window.devicePixelRatio\n return {\n x: (cx - r.left) * dpr,\n y: (r.height - (cy - r.top)) * dpr, // flip Y\n }\n }\n\n const onMove = (cx: number, cy: number) => {\n if (!mouseState.current.pressed) return\n const { x, y } = toPixel(cx, cy)\n mouseState.current.x = x\n mouseState.current.y = y\n }\n\n const onDown = (cx: number, cy: number) => {\n const { x, y } = toPixel(cx, cy)\n mouseState.current.x = x\n mouseState.current.y = y\n mouseState.current.clickX = x\n mouseState.current.clickY = y\n mouseState.current.pressed = true\n }\n\n const onUp = () => {\n mouseState.current.pressed = false\n }\n\n const mm = (e: MouseEvent) => onMove(e.clientX, e.clientY)\n const md = (e: MouseEvent) => onDown(e.clientX, e.clientY)\n const mu = () => onUp()\n const tm = (e: TouchEvent) => {\n if (e.touches[0]) onMove(e.touches[0].clientX, e.touches[0].clientY)\n }\n const ts = (e: TouchEvent) => {\n if (e.touches[0]) onDown(e.touches[0].clientX, e.touches[0].clientY)\n }\n const te = () => onUp()\n\n window.addEventListener('mousemove', mm)\n canvas.addEventListener('mousedown', md)\n window.addEventListener('mouseup', mu)\n window.addEventListener('touchmove', tm, { passive: true })\n canvas.addEventListener('touchstart', ts, { passive: true })\n window.addEventListener('touchend', te)\n\n return () => {\n window.removeEventListener('mousemove', mm)\n canvas.removeEventListener('mousedown', md)\n window.removeEventListener('mouseup', mu)\n window.removeEventListener('touchmove', tm)\n canvas.removeEventListener('touchstart', ts)\n window.removeEventListener('touchend', te)\n }\n }, [mouseEnabled, pixelRatio])\n\n const pause = useCallback(() => { pausedRef.current = true }, [])\n const resume = useCallback(() => { pausedRef.current = false }, [])\n\n return { canvasRef, isReady, error, pause, resume }\n}\n","import type { ShadertoyProps } from './types'\nimport { useShadertoy } from './useShadertoy'\n\nexport function Shadertoy({\n fragmentShader,\n style,\n className,\n paused,\n speed,\n pixelRatio,\n mouse,\n onError,\n onLoad,\n}: ShadertoyProps) {\n const { canvasRef } = useShadertoy({\n fragmentShader,\n paused,\n speed,\n pixelRatio,\n mouse,\n onError,\n onLoad,\n })\n\n return (\n <canvas\n ref={canvasRef}\n className={className}\n style={{ width: '100%', height: '100%', display: 'block', ...style }}\n />\n )\n}\n"]}