shaders-oneshot 0.0.81-shared.1

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 (129) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +5 -0
  3. package/README.md +7 -0
  4. package/dist/empty-pixel.d.ts +1 -0
  5. package/dist/empty-pixel.js +10 -0
  6. package/dist/empty-pixel.js.map +7 -0
  7. package/dist/get-shader-color-from-string.d.ts +3 -0
  8. package/dist/get-shader-color-from-string.js +101 -0
  9. package/dist/get-shader-color-from-string.js.map +7 -0
  10. package/dist/get-shader-color-from-string.test.d.ts +1 -0
  11. package/dist/get-shader-color-from-string.test.js +69 -0
  12. package/dist/get-shader-noise-texture.d.ts +1 -0
  13. package/dist/get-shader-noise-texture.js +18 -0
  14. package/dist/get-shader-noise-texture.js.map +7 -0
  15. package/dist/index.d.ts +68 -0
  16. package/dist/index.js +194 -0
  17. package/dist/index.js.map +7 -0
  18. package/dist/shader-color-spaces.d.ts +6 -0
  19. package/dist/shader-color-spaces.js +96 -0
  20. package/dist/shader-color-spaces.js.map +7 -0
  21. package/dist/shader-mount.d.ts +199 -0
  22. package/dist/shader-mount.js +735 -0
  23. package/dist/shader-mount.js.map +7 -0
  24. package/dist/shader-sizing.d.ts +30 -0
  25. package/dist/shader-sizing.js +38 -0
  26. package/dist/shader-sizing.js.map +7 -0
  27. package/dist/shader-utils.d.ts +10 -0
  28. package/dist/shader-utils.js +131 -0
  29. package/dist/shader-utils.js.map +7 -0
  30. package/dist/shaders/color-panels.d.ts +70 -0
  31. package/dist/shaders/color-panels.js +224 -0
  32. package/dist/shaders/color-panels.js.map +7 -0
  33. package/dist/shaders/dithering.d.ts +62 -0
  34. package/dist/shaders/dithering.js +271 -0
  35. package/dist/shaders/dithering.js.map +7 -0
  36. package/dist/shaders/dot-grid.d.ts +65 -0
  37. package/dist/shaders/dot-grid.js +110 -0
  38. package/dist/shaders/dot-grid.js.map +7 -0
  39. package/dist/shaders/dot-orbit.d.ts +57 -0
  40. package/dist/shaders/dot-orbit.js +123 -0
  41. package/dist/shaders/dot-orbit.js.map +7 -0
  42. package/dist/shaders/fluted-glass.d.ts +112 -0
  43. package/dist/shaders/fluted-glass.js +361 -0
  44. package/dist/shaders/fluted-glass.js.map +7 -0
  45. package/dist/shaders/gem-smoke.d.ts +96 -0
  46. package/dist/shaders/gem-smoke.js +573 -0
  47. package/dist/shaders/gem-smoke.js.map +7 -0
  48. package/dist/shaders/god-rays.d.ts +67 -0
  49. package/dist/shaders/god-rays.js +133 -0
  50. package/dist/shaders/god-rays.js.map +7 -0
  51. package/dist/shaders/grain-gradient.d.ts +80 -0
  52. package/dist/shaders/grain-gradient.js +301 -0
  53. package/dist/shaders/grain-gradient.js.map +7 -0
  54. package/dist/shaders/halftone-cmyk.d.ts +104 -0
  55. package/dist/shaders/halftone-cmyk.js +295 -0
  56. package/dist/shaders/halftone-cmyk.js.map +7 -0
  57. package/dist/shaders/halftone-dots.d.ts +80 -0
  58. package/dist/shaders/halftone-dots.js +321 -0
  59. package/dist/shaders/halftone-dots.js.map +7 -0
  60. package/dist/shaders/heatmap.d.ts +66 -0
  61. package/dist/shaders/heatmap.js +384 -0
  62. package/dist/shaders/heatmap.js.map +7 -0
  63. package/dist/shaders/image-dithering.d.ts +64 -0
  64. package/dist/shaders/image-dithering.js +199 -0
  65. package/dist/shaders/image-dithering.js.map +7 -0
  66. package/dist/shaders/lens-distortion.d.ts +97 -0
  67. package/dist/shaders/lens-distortion.js +276 -0
  68. package/dist/shaders/lens-distortion.js.map +7 -0
  69. package/dist/shaders/liquid-metal.d.ts +89 -0
  70. package/dist/shaders/liquid-metal.js +633 -0
  71. package/dist/shaders/liquid-metal.js.map +7 -0
  72. package/dist/shaders/mesh-gradient.d.ts +52 -0
  73. package/dist/shaders/mesh-gradient.js +131 -0
  74. package/dist/shaders/mesh-gradient.js.map +7 -0
  75. package/dist/shaders/metaballs.d.ts +51 -0
  76. package/dist/shaders/metaballs.js +111 -0
  77. package/dist/shaders/metaballs.js.map +7 -0
  78. package/dist/shaders/neuro-noise.d.ts +47 -0
  79. package/dist/shaders/neuro-noise.js +77 -0
  80. package/dist/shaders/neuro-noise.js.map +7 -0
  81. package/dist/shaders/paper-texture.d.ts +109 -0
  82. package/dist/shaders/paper-texture.js +527 -0
  83. package/dist/shaders/paper-texture.js.map +7 -0
  84. package/dist/shaders/perlin-noise.d.ts +52 -0
  85. package/dist/shaders/perlin-noise.js +167 -0
  86. package/dist/shaders/perlin-noise.js.map +7 -0
  87. package/dist/shaders/pulsing-border.d.ts +98 -0
  88. package/dist/shaders/pulsing-border.js +239 -0
  89. package/dist/shaders/pulsing-border.js.map +7 -0
  90. package/dist/shaders/simplex-noise.d.ts +45 -0
  91. package/dist/shaders/simplex-noise.js +92 -0
  92. package/dist/shaders/simplex-noise.js.map +7 -0
  93. package/dist/shaders/smoke-ring.d.ts +61 -0
  94. package/dist/shaders/smoke-ring.js +133 -0
  95. package/dist/shaders/smoke-ring.js.map +7 -0
  96. package/dist/shaders/spiral.d.ts +61 -0
  97. package/dist/shaders/spiral.js +83 -0
  98. package/dist/shaders/spiral.js.map +7 -0
  99. package/dist/shaders/static-mesh-gradient.d.ts +63 -0
  100. package/dist/shaders/static-mesh-gradient.js +131 -0
  101. package/dist/shaders/static-mesh-gradient.js.map +7 -0
  102. package/dist/shaders/static-radial-gradient.d.ts +72 -0
  103. package/dist/shaders/static-radial-gradient.js +188 -0
  104. package/dist/shaders/static-radial-gradient.js.map +7 -0
  105. package/dist/shaders/swirl.d.ts +63 -0
  106. package/dist/shaders/swirl.js +104 -0
  107. package/dist/shaders/swirl.js.map +7 -0
  108. package/dist/shaders/voronoi.d.ts +65 -0
  109. package/dist/shaders/voronoi.js +139 -0
  110. package/dist/shaders/voronoi.js.map +7 -0
  111. package/dist/shaders/warp.d.ts +70 -0
  112. package/dist/shaders/warp.js +130 -0
  113. package/dist/shaders/warp.js.map +7 -0
  114. package/dist/shaders/water.d.ts +59 -0
  115. package/dist/shaders/water.js +122 -0
  116. package/dist/shaders/water.js.map +7 -0
  117. package/dist/shaders/waves.d.ts +53 -0
  118. package/dist/shaders/waves.js +66 -0
  119. package/dist/shaders/waves.js.map +7 -0
  120. package/dist/shared-webgl-renderer.d.ts +63 -0
  121. package/dist/shared-webgl-renderer.js +262 -0
  122. package/dist/shared-webgl-renderer.js.map +7 -0
  123. package/dist/types.d.ts +3 -0
  124. package/dist/types.js +6 -0
  125. package/dist/types.js.map +7 -0
  126. package/dist/vertex-shader.d.ts +2 -0
  127. package/dist/vertex-shader.js +160 -0
  128. package/dist/vertex-shader.js.map +7 -0
  129. package/package.json +29 -0
@@ -0,0 +1,735 @@
1
+ /* * * * * * * * * * * * * * * * * * * * * * * * * * * *
2
+ * Paper Shaders *
3
+ * https://github.com/paper-design/shaders *
4
+ * * * * * * * * * * * * * * * * * * * * * * * * * * * */
5
+
6
+ import { vertexShaderSource } from "./vertex-shader.js";
7
+ import { SharedWebGLRenderer } from "./shared-webgl-renderer.js";
8
+ const DEFAULT_MAX_PIXEL_COUNT = 1920 * 1080 * 4;
9
+ const FRAG_COORD_OFFSET_UNIFORM = "u_paperFragCoordOffset";
10
+ const MAX_CONTEXT_RECOVERIES = 3;
11
+ const CONTEXT_RECOVERY_WINDOW_MS = 3e4;
12
+ class ShaderMount {
13
+ parentElement;
14
+ canvasElement;
15
+ gl;
16
+ /** Set when this mount draws through the context shared by all shared mounts, null when it owns a context */
17
+ sharedRenderer = null;
18
+ /** The visible canvas's 2D context in shared mode, where each frame is copied to */
19
+ context2d = null;
20
+ webGlContextAttributes;
21
+ program = null;
22
+ vertexArray = null;
23
+ positionBuffer = null;
24
+ uniformLocations = {};
25
+ /** The fragment shader that we are using */
26
+ fragmentShader;
27
+ /** Stores the RAF for the render loop */
28
+ rafId = null;
29
+ /** Time of the last rendered frame */
30
+ lastRenderTime = 0;
31
+ /** Total time that we have played any animation, passed as a uniform to the shader for time-based VFX */
32
+ currentFrame = 0;
33
+ /** The speed that we progress through animation time (multiplies by delta time every update). Allows negatives to play in reverse. If set to 0, rAF will stop entirely so static shaders have no recurring performance costs */
34
+ speed = 0;
35
+ /** Actual speed used that accounts for visibility: we pause the shader if the tab is hidden or the element out of the viewport */
36
+ currentSpeed = 0;
37
+ /** Uniforms that are provided by the user for the specific shader being mounted (not including uniforms that this Mount adds, like time and resolution) */
38
+ providedUniforms;
39
+ /** Names of the uniforms that should have mipmaps generated for them */
40
+ mipmaps = [];
41
+ /** Just a sanity check to make sure frames don't run after we're disposed */
42
+ hasBeenDisposed = false;
43
+ /** If the resolution of the canvas has changed since the last render */
44
+ resolutionChanged = true;
45
+ /** Store textures that are provided by the user */
46
+ textures = /* @__PURE__ */ new Map();
47
+ minPixelRatio;
48
+ maxPixelCount;
49
+ isSafari = isSafari();
50
+ uniformCache = {};
51
+ textureUnitMap = /* @__PURE__ */ new Map();
52
+ ownerDocument;
53
+ /** Shared mode: whether time is advancing from frame to frame, as opposed to one-off redraws */
54
+ sharedLoopActive = false;
55
+ /** Shared mode: the canvas size to apply right before the next draw (resizing clears the canvas) */
56
+ pendingCanvasWidth = null;
57
+ pendingCanvasHeight = null;
58
+ contextRecoveries = [];
59
+ /** Dedicated mode: the context was lost and a new one goes in once the mount is visible */
60
+ awaitingNewContext = false;
61
+ constructor(parentElement, fragmentShader, uniforms, webGlContextAttributes, speed = 0, frame = 0, minPixelRatio = 2, maxPixelCount = DEFAULT_MAX_PIXEL_COUNT, mipmaps = [], webGlContextMode) {
62
+ if (parentElement?.nodeType === 1) {
63
+ this.parentElement = parentElement;
64
+ } else {
65
+ throw new Error("Paper Shaders: parent element must be an HTMLElement");
66
+ }
67
+ this.ownerDocument = parentElement.ownerDocument;
68
+ if (!this.ownerDocument.querySelector("style[data-paper-shader]")) {
69
+ const styleElement = this.ownerDocument.createElement("style");
70
+ styleElement.innerHTML = defaultStyle;
71
+ styleElement.setAttribute("data-paper-shader", "");
72
+ this.ownerDocument.head.prepend(styleElement);
73
+ }
74
+ let canvasElement = this.ownerDocument.createElement("canvas");
75
+ this.parentElement.prepend(canvasElement);
76
+ this.fragmentShader = fragmentShader;
77
+ this.providedUniforms = uniforms;
78
+ this.mipmaps = mipmaps;
79
+ this.webGlContextAttributes = webGlContextAttributes;
80
+ this.currentFrame = frame;
81
+ this.minPixelRatio = minPixelRatio;
82
+ this.maxPixelCount = maxPixelCount;
83
+ const contextMode = webGlContextMode ?? (webGlContextAttributes ? "dedicated" : "shared");
84
+ const sharedRenderer = contextMode === "shared" ? SharedWebGLRenderer.get() : null;
85
+ const context2d = sharedRenderer ? canvasElement.getContext("2d") : null;
86
+ if (sharedRenderer && context2d) {
87
+ this.sharedRenderer = sharedRenderer;
88
+ this.context2d = context2d;
89
+ this.gl = sharedRenderer.gl;
90
+ sharedRenderer.register(this);
91
+ } else {
92
+ if (sharedRenderer) {
93
+ const freshCanvas = this.ownerDocument.createElement("canvas");
94
+ canvasElement.replaceWith(freshCanvas);
95
+ canvasElement = freshCanvas;
96
+ }
97
+ const gl = canvasElement.getContext("webgl2", webGlContextAttributes);
98
+ if (!gl) {
99
+ throw new Error("Paper Shaders: WebGL is not supported in this browser");
100
+ }
101
+ this.gl = gl;
102
+ canvasElement.addEventListener("webglcontextlost", this.handleContextLost);
103
+ }
104
+ this.canvasElement = canvasElement;
105
+ this.initGL();
106
+ this.setupResizeObserver();
107
+ visualViewport?.addEventListener("resize", this.handleVisualViewportChange);
108
+ this.setupIntersectionObserver();
109
+ this.setSpeed(speed);
110
+ this.parentElement.setAttribute("data-paper-shader", "");
111
+ this.parentElement.paperShaderMount = this;
112
+ this.ownerDocument.addEventListener("visibilitychange", this.handleDocumentVisibilityChange);
113
+ }
114
+ /** Create every GL resource this mount owns on the current context. Also rebuilds them after a context is replaced */
115
+ initGL = () => {
116
+ this.program = null;
117
+ this.vertexArray = null;
118
+ this.positionBuffer = null;
119
+ this.uniformLocations = {};
120
+ this.uniformCache = {};
121
+ this.textures.clear();
122
+ this.textureUnitMap.clear();
123
+ this.resolutionChanged = true;
124
+ if (this.gl.isContextLost()) return;
125
+ this.initProgram();
126
+ if (!this.program) return;
127
+ this.setupPositionAttribute();
128
+ this.setupUniforms();
129
+ this.setUniformValues(this.providedUniforms);
130
+ };
131
+ initProgram = () => {
132
+ const program = createProgram(this.gl, vertexShaderSource, this.fragmentShader, this.sharedRenderer !== null);
133
+ if (!program) return;
134
+ this.program = program;
135
+ };
136
+ setupPositionAttribute = () => {
137
+ const positionAttributeLocation = this.gl.getAttribLocation(this.program, "a_position");
138
+ this.vertexArray = this.gl.createVertexArray();
139
+ this.gl.bindVertexArray(this.vertexArray);
140
+ this.positionBuffer = this.gl.createBuffer();
141
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.positionBuffer);
142
+ const positions = [-1, -1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1];
143
+ this.gl.bufferData(this.gl.ARRAY_BUFFER, new Float32Array(positions), this.gl.STATIC_DRAW);
144
+ this.gl.enableVertexAttribArray(positionAttributeLocation);
145
+ this.gl.vertexAttribPointer(positionAttributeLocation, 2, this.gl.FLOAT, false, 0, 0);
146
+ this.gl.bindVertexArray(null);
147
+ };
148
+ setupUniforms = () => {
149
+ const uniformLocations = {
150
+ u_time: this.gl.getUniformLocation(this.program, "u_time"),
151
+ u_pixelRatio: this.gl.getUniformLocation(this.program, "u_pixelRatio"),
152
+ u_resolution: this.gl.getUniformLocation(this.program, "u_resolution"),
153
+ [FRAG_COORD_OFFSET_UNIFORM]: this.gl.getUniformLocation(this.program, FRAG_COORD_OFFSET_UNIFORM)
154
+ };
155
+ Object.entries(this.providedUniforms).forEach(([key, value]) => {
156
+ uniformLocations[key] = this.gl.getUniformLocation(this.program, key);
157
+ if (value instanceof HTMLImageElement) {
158
+ const aspectRatioUniformName = `${key}AspectRatio`;
159
+ uniformLocations[aspectRatioUniformName] = this.gl.getUniformLocation(this.program, aspectRatioUniformName);
160
+ }
161
+ });
162
+ this.uniformLocations = uniformLocations;
163
+ };
164
+ /**
165
+ * The scale that we should render at.
166
+ * - Used to target 2x rendering even on 1x screens for better antialiasing
167
+ * - Prevents the virtual resolution from going beyond the maximum resolution
168
+ * - Accounts for the page zoom level so we render in physical device pixels rather than CSS pixels
169
+ */
170
+ renderScale = 1;
171
+ parentWidth = 0;
172
+ parentHeight = 0;
173
+ parentDevicePixelWidth = 0;
174
+ parentDevicePixelHeight = 0;
175
+ devicePixelsSupported = false;
176
+ intersectionObserver = null;
177
+ isInViewport = true;
178
+ resizeObserver = null;
179
+ setupResizeObserver = () => {
180
+ this.resizeObserver = new ResizeObserver(([entry]) => {
181
+ if (entry?.borderBoxSize[0]) {
182
+ const physicalPixelSize = entry.devicePixelContentBoxSize?.[0];
183
+ if (physicalPixelSize !== void 0) {
184
+ this.devicePixelsSupported = true;
185
+ this.parentDevicePixelWidth = physicalPixelSize.inlineSize;
186
+ this.parentDevicePixelHeight = physicalPixelSize.blockSize;
187
+ }
188
+ this.parentWidth = entry.borderBoxSize[0].inlineSize;
189
+ this.parentHeight = entry.borderBoxSize[0].blockSize;
190
+ }
191
+ this.handleResize();
192
+ });
193
+ this.resizeObserver.observe(this.parentElement);
194
+ };
195
+ setupIntersectionObserver = () => {
196
+ const view = this.ownerDocument.defaultView;
197
+ if (!view?.IntersectionObserver) return;
198
+ this.intersectionObserver = new view.IntersectionObserver(([entry]) => {
199
+ this.isInViewport = entry?.isIntersecting ?? true;
200
+ this.updateCurrentSpeed();
201
+ });
202
+ this.intersectionObserver.observe(this.parentElement);
203
+ };
204
+ // Visual viewport resize handler, mainly used to react to browser zoom changes.
205
+ // Resize observer by itself does not react to pinch zoom, and although it usually
206
+ // reacts to classic browser zoom, it's not guaranteed in edge cases.
207
+ // Since timing between visual viewport changes and resize observer is complex
208
+ // and because we'd like to know the device pixel sizes of elements, we just restart
209
+ // the observer to get a guaranteed fresh callback regardless if it would have triggered or not.
210
+ handleVisualViewportChange = () => {
211
+ this.resizeObserver?.disconnect();
212
+ this.setupResizeObserver();
213
+ };
214
+ /** Resize handler for when the container div changes size or the max pixel count changes and we want to resize our canvas to match */
215
+ handleResize = () => {
216
+ let targetPixelWidth = 0;
217
+ let targetPixelHeight = 0;
218
+ const dpr = Math.max(1, window.devicePixelRatio);
219
+ const pinchZoom = visualViewport?.scale ?? 1;
220
+ if (this.devicePixelsSupported) {
221
+ const scaleToMeetMinPixelRatio = Math.max(1, this.minPixelRatio / dpr);
222
+ targetPixelWidth = this.parentDevicePixelWidth * scaleToMeetMinPixelRatio * pinchZoom;
223
+ targetPixelHeight = this.parentDevicePixelHeight * scaleToMeetMinPixelRatio * pinchZoom;
224
+ } else {
225
+ let targetRenderScale = Math.max(dpr, this.minPixelRatio) * pinchZoom;
226
+ if (this.isSafari) {
227
+ const zoomLevel = bestGuessBrowserZoom(this.ownerDocument);
228
+ targetRenderScale *= Math.max(1, zoomLevel);
229
+ }
230
+ targetPixelWidth = Math.round(this.parentWidth) * targetRenderScale;
231
+ targetPixelHeight = Math.round(this.parentHeight) * targetRenderScale;
232
+ }
233
+ const maxPixelCountHeadroom = Math.sqrt(this.maxPixelCount) / Math.sqrt(targetPixelWidth * targetPixelHeight);
234
+ let scaleToMeetMaxPixelCount = Math.min(1, maxPixelCountHeadroom);
235
+ if (this.sharedRenderer) {
236
+ const maxSide = this.sharedRenderer.maxSide;
237
+ scaleToMeetMaxPixelCount = Math.min(
238
+ scaleToMeetMaxPixelCount,
239
+ maxSide / targetPixelWidth,
240
+ maxSide / targetPixelHeight
241
+ );
242
+ }
243
+ const newWidth = Math.round(targetPixelWidth * scaleToMeetMaxPixelCount);
244
+ const newHeight = Math.round(targetPixelHeight * scaleToMeetMaxPixelCount);
245
+ const newRenderScale = newWidth / Math.round(this.parentWidth);
246
+ const currentWidth = this.pendingCanvasWidth ?? this.canvasElement.width;
247
+ const currentHeight = this.pendingCanvasHeight ?? this.canvasElement.height;
248
+ if (currentWidth !== newWidth || currentHeight !== newHeight || this.renderScale !== newRenderScale) {
249
+ this.renderScale = newRenderScale;
250
+ this.resolutionChanged = true;
251
+ if (this.sharedRenderer) {
252
+ this.pendingCanvasWidth = newWidth;
253
+ this.pendingCanvasHeight = newHeight;
254
+ this.sharedRenderer.schedule(this);
255
+ return;
256
+ }
257
+ this.canvasElement.width = newWidth;
258
+ this.canvasElement.height = newHeight;
259
+ this.gl.viewport(0, 0, this.gl.canvas.width, this.gl.canvas.height);
260
+ this.render(performance.now());
261
+ }
262
+ };
263
+ render = (currentTime) => {
264
+ if (this.hasBeenDisposed) return;
265
+ if (this.program === null) {
266
+ console.warn("Tried to render before program or gl was initialized");
267
+ return;
268
+ }
269
+ const dt = currentTime - this.lastRenderTime;
270
+ this.lastRenderTime = currentTime;
271
+ if (this.currentSpeed !== 0) {
272
+ this.currentFrame += dt * this.currentSpeed;
273
+ }
274
+ this.gl.clear(this.gl.COLOR_BUFFER_BIT);
275
+ this.draw(0, 0);
276
+ if (this.currentSpeed !== 0) {
277
+ this.requestRender();
278
+ } else {
279
+ this.rafId = null;
280
+ }
281
+ };
282
+ /** Issue the draw call with this mount's program, geometry, textures and per-frame uniforms */
283
+ draw = (fragCoordOffsetX, fragCoordOffsetY) => {
284
+ const gl = this.gl;
285
+ gl.useProgram(this.program);
286
+ gl.bindVertexArray(this.vertexArray);
287
+ this.textures.forEach((texture, uniformName) => {
288
+ gl.activeTexture(gl.TEXTURE0 + this.textureUnitMap.get(uniformName));
289
+ gl.bindTexture(gl.TEXTURE_2D, texture);
290
+ });
291
+ gl.uniform1f(this.uniformLocations.u_time, this.currentFrame * 1e-3);
292
+ if (this.resolutionChanged) {
293
+ gl.uniform2f(this.uniformLocations.u_resolution, this.canvasElement.width, this.canvasElement.height);
294
+ gl.uniform1f(this.uniformLocations.u_pixelRatio, this.renderScale);
295
+ this.resolutionChanged = false;
296
+ }
297
+ const fragCoordOffsetLocation = this.uniformLocations[FRAG_COORD_OFFSET_UNIFORM];
298
+ if (fragCoordOffsetLocation) {
299
+ gl.uniform2f(fragCoordOffsetLocation, fragCoordOffsetX, fragCoordOffsetY);
300
+ }
301
+ gl.drawArrays(gl.TRIANGLES, 0, 6);
302
+ };
303
+ requestRender = () => {
304
+ if (this.sharedRenderer) {
305
+ this.sharedRenderer.schedule(this);
306
+ return;
307
+ }
308
+ if (this.rafId !== null) {
309
+ cancelAnimationFrame(this.rafId);
310
+ }
311
+ this.rafId = requestAnimationFrame(this.render);
312
+ };
313
+ /** Draw the current state: right away with a dedicated context, on the next batched frame with the shared one */
314
+ redraw = () => {
315
+ if (this.sharedRenderer) {
316
+ this.sharedRenderer.schedule(this);
317
+ } else {
318
+ this.render(performance.now());
319
+ }
320
+ };
321
+ /** @internal Shared renderer hook, see SharedRenderTarget */
322
+ prepareSharedFrame = (currentTime) => {
323
+ if (this.hasBeenDisposed || this.program === null) return null;
324
+ if (this.pendingCanvasWidth !== null && this.pendingCanvasHeight !== null) {
325
+ this.canvasElement.width = this.pendingCanvasWidth;
326
+ this.canvasElement.height = this.pendingCanvasHeight;
327
+ this.pendingCanvasWidth = null;
328
+ this.pendingCanvasHeight = null;
329
+ }
330
+ const dt = this.sharedLoopActive ? Math.max(0, currentTime - this.lastRenderTime) : 0;
331
+ this.lastRenderTime = currentTime;
332
+ if (this.currentSpeed !== 0) {
333
+ this.currentFrame += dt * this.currentSpeed;
334
+ }
335
+ return { width: this.canvasElement.width, height: this.canvasElement.height };
336
+ };
337
+ /** @internal Shared renderer hook, see SharedRenderTarget */
338
+ drawShared = (x, y) => {
339
+ this.draw(x, y);
340
+ };
341
+ /** @internal Shared renderer hook, see SharedRenderTarget */
342
+ presentShared = (frame, x, y, width, height) => {
343
+ if (!this.context2d) return;
344
+ this.context2d.globalCompositeOperation = "copy";
345
+ this.context2d.drawImage(frame, x, y, width, height, 0, 0, this.canvasElement.width, this.canvasElement.height);
346
+ };
347
+ /** @internal Shared renderer hook, see SharedRenderTarget */
348
+ finishSharedFrame = () => {
349
+ this.sharedLoopActive = !this.hasBeenDisposed && this.program !== null && this.currentSpeed !== 0;
350
+ return this.sharedLoopActive;
351
+ };
352
+ /** @internal Shared renderer hook, see SharedRenderTarget */
353
+ restoreShared = (gl) => {
354
+ if (this.hasBeenDisposed) return;
355
+ this.gl = gl;
356
+ this.initGL();
357
+ this.lastRenderTime = performance.now();
358
+ this.sharedRenderer?.schedule(this);
359
+ };
360
+ /**
361
+ * A dedicated context was lost, usually because the page went over the browser's context limit and ours was the
362
+ * one that rendered least recently.
363
+ */
364
+ handleContextLost = () => {
365
+ if (this.hasBeenDisposed) return;
366
+ if (this.rafId !== null) {
367
+ cancelAnimationFrame(this.rafId);
368
+ this.rafId = null;
369
+ }
370
+ this.awaitingNewContext = true;
371
+ this.maybeReplaceDedicatedContext();
372
+ };
373
+ /**
374
+ * We don't wait for 'webglcontextrestored': browsers restore an evicted context only once another context has been
375
+ * garbage collected, which can take a long time or never happen. A new canvas with a new context goes in instead,
376
+ * but only once the mount is on screen: every new context pushes the least recently used one out when the page is
377
+ * at the limit, so offscreen or hidden mounts replacing theirs would just take turns evicting each other.
378
+ */
379
+ maybeReplaceDedicatedContext = () => {
380
+ if (!this.awaitingNewContext || this.hasBeenDisposed) return;
381
+ if (this.ownerDocument.hidden || !this.isInViewport) return;
382
+ this.awaitingNewContext = false;
383
+ const now = performance.now();
384
+ this.contextRecoveries = this.contextRecoveries.filter((time) => now - time < CONTEXT_RECOVERY_WINDOW_MS);
385
+ if (this.contextRecoveries.length >= MAX_CONTEXT_RECOVERIES) {
386
+ console.warn("Paper Shaders: WebGL context keeps getting lost, giving up on restoring it");
387
+ return;
388
+ }
389
+ this.contextRecoveries.push(now);
390
+ requestAnimationFrame(this.replaceDedicatedContext);
391
+ };
392
+ replaceDedicatedContext = () => {
393
+ if (this.hasBeenDisposed) return;
394
+ const oldCanvas = this.canvasElement;
395
+ const canvas = this.ownerDocument.createElement("canvas");
396
+ canvas.width = oldCanvas.width;
397
+ canvas.height = oldCanvas.height;
398
+ const gl = canvas.getContext("webgl2", this.webGlContextAttributes);
399
+ if (!gl) {
400
+ console.warn("Paper Shaders: could not create a new WebGL context after the previous one was lost");
401
+ return;
402
+ }
403
+ oldCanvas.removeEventListener("webglcontextlost", this.handleContextLost);
404
+ canvas.addEventListener("webglcontextlost", this.handleContextLost);
405
+ oldCanvas.replaceWith(canvas);
406
+ this.canvasElement = canvas;
407
+ this.gl = gl;
408
+ this.initGL();
409
+ gl.viewport(0, 0, canvas.width, canvas.height);
410
+ this.lastRenderTime = performance.now();
411
+ this.render(performance.now());
412
+ };
413
+ /** Creates a texture from an image and sets it into a uniform value */
414
+ setTextureUniform = (uniformName, image) => {
415
+ if (!image.complete || image.naturalWidth === 0) {
416
+ throw new Error(`Paper Shaders: image for uniform ${uniformName} must be fully loaded`);
417
+ }
418
+ const existingTexture = this.textures.get(uniformName);
419
+ if (existingTexture) {
420
+ this.gl.deleteTexture(existingTexture);
421
+ }
422
+ if (!this.textureUnitMap.has(uniformName)) {
423
+ this.textureUnitMap.set(uniformName, this.textureUnitMap.size);
424
+ }
425
+ const textureUnit = this.textureUnitMap.get(uniformName);
426
+ for (let i = 0; i < 8 && this.gl.getError() !== this.gl.NO_ERROR; i++) ;
427
+ this.gl.activeTexture(this.gl.TEXTURE0 + textureUnit);
428
+ const texture = this.gl.createTexture();
429
+ this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
430
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_S, this.gl.CLAMP_TO_EDGE);
431
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_WRAP_T, this.gl.CLAMP_TO_EDGE);
432
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR);
433
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MAG_FILTER, this.gl.LINEAR);
434
+ this.gl.texImage2D(this.gl.TEXTURE_2D, 0, this.gl.RGBA, this.gl.RGBA, this.gl.UNSIGNED_BYTE, image);
435
+ if (this.mipmaps.includes(uniformName)) {
436
+ this.gl.generateMipmap(this.gl.TEXTURE_2D);
437
+ this.gl.texParameteri(this.gl.TEXTURE_2D, this.gl.TEXTURE_MIN_FILTER, this.gl.LINEAR_MIPMAP_LINEAR);
438
+ }
439
+ const error = this.gl.getError();
440
+ if (error !== this.gl.NO_ERROR || texture === null) {
441
+ console.error("Paper Shaders: WebGL error when uploading texture:", error);
442
+ return;
443
+ }
444
+ this.textures.set(uniformName, texture);
445
+ const location = this.uniformLocations[uniformName];
446
+ if (location) {
447
+ this.gl.uniform1i(location, textureUnit);
448
+ const aspectRatioUniformName = `${uniformName}AspectRatio`;
449
+ const aspectRatioLocation = this.uniformLocations[aspectRatioUniformName];
450
+ if (aspectRatioLocation) {
451
+ const aspectRatio = image.naturalWidth / image.naturalHeight;
452
+ this.gl.uniform1f(aspectRatioLocation, aspectRatio);
453
+ }
454
+ }
455
+ };
456
+ /** Utility: recursive equality test for all the uniforms */
457
+ areUniformValuesEqual = (a, b) => {
458
+ if (a === b) return true;
459
+ if (Array.isArray(a) && Array.isArray(b) && a.length === b.length) {
460
+ return a.every((val, i) => this.areUniformValuesEqual(val, b[i]));
461
+ }
462
+ return false;
463
+ };
464
+ /** Sets the provided uniform values into the WebGL program, can be a partial list of uniforms that have changed */
465
+ setUniformValues = (updatedUniforms) => {
466
+ this.gl.useProgram(this.program);
467
+ Object.entries(updatedUniforms).forEach(([key, value]) => {
468
+ let cacheValue = value;
469
+ if (value instanceof HTMLImageElement) {
470
+ cacheValue = `${value.src.slice(0, 200)}|${value.naturalWidth}x${value.naturalHeight}`;
471
+ }
472
+ if (this.areUniformValuesEqual(this.uniformCache[key], cacheValue)) return;
473
+ this.uniformCache[key] = cacheValue;
474
+ const location = this.uniformLocations[key];
475
+ if (!location) {
476
+ console.warn(`Uniform location for ${key} not found`);
477
+ return;
478
+ }
479
+ if (value instanceof HTMLImageElement) {
480
+ this.setTextureUniform(key, value);
481
+ } else if (Array.isArray(value)) {
482
+ let flatArray = null;
483
+ let valueLength = null;
484
+ if (value[0] !== void 0 && Array.isArray(value[0])) {
485
+ const firstChildLength = value[0].length;
486
+ if (value.every((arr) => arr.length === firstChildLength)) {
487
+ flatArray = value.flat();
488
+ valueLength = firstChildLength;
489
+ } else {
490
+ console.warn(`All child arrays must be the same length for ${key}`);
491
+ return;
492
+ }
493
+ } else {
494
+ flatArray = value;
495
+ valueLength = flatArray.length;
496
+ }
497
+ switch (valueLength) {
498
+ case 2:
499
+ this.gl.uniform2fv(location, flatArray);
500
+ break;
501
+ case 3:
502
+ this.gl.uniform3fv(location, flatArray);
503
+ break;
504
+ case 4:
505
+ this.gl.uniform4fv(location, flatArray);
506
+ break;
507
+ case 9:
508
+ this.gl.uniformMatrix3fv(location, false, flatArray);
509
+ break;
510
+ case 16:
511
+ this.gl.uniformMatrix4fv(location, false, flatArray);
512
+ break;
513
+ default:
514
+ console.warn(`Unsupported uniform array length: ${valueLength}`);
515
+ }
516
+ } else if (typeof value === "number") {
517
+ this.gl.uniform1f(location, value);
518
+ } else if (typeof value === "boolean") {
519
+ this.gl.uniform1i(location, value ? 1 : 0);
520
+ } else {
521
+ console.warn(`Unsupported uniform type for ${key}: ${typeof value}`);
522
+ }
523
+ });
524
+ };
525
+ /** Gets the current total animation time from 0ms */
526
+ getCurrentFrame = () => {
527
+ return this.currentFrame;
528
+ };
529
+ /** Set a frame to get a deterministic result, frames are literally just milliseconds from zero since the animation started */
530
+ setFrame = (newFrame) => {
531
+ this.currentFrame = newFrame;
532
+ this.lastRenderTime = performance.now();
533
+ this.redraw();
534
+ };
535
+ /** Set an animation speed (or 0 to stop animation) */
536
+ setSpeed = (newSpeed = 1) => {
537
+ this.speed = newSpeed;
538
+ this.updateCurrentSpeed();
539
+ };
540
+ /** Apply the target speed, pausing (0) while the tab is hidden or the element is out of the viewport */
541
+ updateCurrentSpeed = () => {
542
+ this.setCurrentSpeed(this.ownerDocument.hidden || !this.isInViewport ? 0 : this.speed);
543
+ this.maybeReplaceDedicatedContext();
544
+ };
545
+ setCurrentSpeed = (newSpeed) => {
546
+ this.currentSpeed = newSpeed;
547
+ if (this.sharedRenderer) {
548
+ if (newSpeed !== 0 && !this.sharedLoopActive) {
549
+ this.sharedLoopActive = true;
550
+ this.lastRenderTime = performance.now();
551
+ this.sharedRenderer.schedule(this);
552
+ }
553
+ return;
554
+ }
555
+ if (this.rafId === null && newSpeed !== 0) {
556
+ this.lastRenderTime = performance.now();
557
+ this.rafId = requestAnimationFrame(this.render);
558
+ }
559
+ if (this.rafId !== null && newSpeed === 0) {
560
+ cancelAnimationFrame(this.rafId);
561
+ this.rafId = null;
562
+ }
563
+ };
564
+ /** Set the maximum pixel count for the shader, this will limit the number of pixels that will be rendered */
565
+ setMaxPixelCount = (newMaxPixelCount = DEFAULT_MAX_PIXEL_COUNT) => {
566
+ this.maxPixelCount = newMaxPixelCount;
567
+ this.handleResize();
568
+ };
569
+ /** Set the minimum pixel ratio for the shader */
570
+ setMinPixelRatio = (newMinPixelRatio = 2) => {
571
+ this.minPixelRatio = newMinPixelRatio;
572
+ this.handleResize();
573
+ };
574
+ /** Update the uniforms that are provided by the outside shader, can be a partial set with only the uniforms that have changed */
575
+ setUniforms = (newUniforms) => {
576
+ this.setUniformValues(newUniforms);
577
+ this.providedUniforms = { ...this.providedUniforms, ...newUniforms };
578
+ this.redraw();
579
+ };
580
+ handleDocumentVisibilityChange = () => {
581
+ this.updateCurrentSpeed();
582
+ };
583
+ /** Dispose of the shader mount, cleaning up all of the WebGL resources */
584
+ dispose = () => {
585
+ this.hasBeenDisposed = true;
586
+ if (this.rafId !== null) {
587
+ cancelAnimationFrame(this.rafId);
588
+ this.rafId = null;
589
+ }
590
+ this.sharedRenderer?.unregister(this);
591
+ if (this.gl && this.program) {
592
+ this.textures.forEach((texture) => {
593
+ this.gl.deleteTexture(texture);
594
+ });
595
+ this.textures.clear();
596
+ this.gl.deleteProgram(this.program);
597
+ this.program = null;
598
+ this.gl.deleteVertexArray(this.vertexArray);
599
+ this.vertexArray = null;
600
+ this.gl.deleteBuffer(this.positionBuffer);
601
+ this.positionBuffer = null;
602
+ if (!this.sharedRenderer) {
603
+ this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
604
+ this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null);
605
+ this.gl.bindRenderbuffer(this.gl.RENDERBUFFER, null);
606
+ this.gl.bindFramebuffer(this.gl.FRAMEBUFFER, null);
607
+ this.gl.getError();
608
+ }
609
+ }
610
+ if (!this.sharedRenderer) {
611
+ this.canvasElement.removeEventListener("webglcontextlost", this.handleContextLost);
612
+ this.gl.getExtension("WEBGL_lose_context")?.loseContext();
613
+ }
614
+ if (this.resizeObserver) {
615
+ this.resizeObserver.disconnect();
616
+ this.resizeObserver = null;
617
+ }
618
+ if (this.intersectionObserver) {
619
+ this.intersectionObserver.disconnect();
620
+ this.intersectionObserver = null;
621
+ }
622
+ visualViewport?.removeEventListener("resize", this.handleVisualViewportChange);
623
+ this.ownerDocument.removeEventListener("visibilitychange", this.handleDocumentVisibilityChange);
624
+ this.uniformLocations = {};
625
+ this.canvasElement.remove();
626
+ delete this.parentElement.paperShaderMount;
627
+ };
628
+ }
629
+ function createShader(gl, type, source) {
630
+ const shader = gl.createShader(type);
631
+ if (!shader) return null;
632
+ gl.shaderSource(shader, source);
633
+ gl.compileShader(shader);
634
+ if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
635
+ console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
636
+ gl.deleteShader(shader);
637
+ return null;
638
+ }
639
+ return shader;
640
+ }
641
+ function offsetFragCoord(fragmentShaderSource) {
642
+ if (!fragmentShaderSource.includes("gl_FragCoord")) return fragmentShaderSource;
643
+ const source = fragmentShaderSource.replace(/\bgl_FragCoord\b/g, "paperFragCoord");
644
+ const declarations = `uniform highp vec2 ${FRAG_COORD_OFFSET_UNIFORM};
645
+ #define paperFragCoord (gl_FragCoord - vec4(${FRAG_COORD_OFFSET_UNIFORM}, 0.0, 0.0))
646
+ `;
647
+ const versionMatch = source.match(/^\s*#version[^\n]*\n/);
648
+ if (versionMatch) {
649
+ const versionLength = versionMatch[0].length;
650
+ return source.slice(0, versionLength) + declarations + source.slice(versionLength);
651
+ }
652
+ return declarations + source;
653
+ }
654
+ function createProgram(gl, vertexShaderSource2, fragmentShaderSource, isShared) {
655
+ if (isShared) {
656
+ fragmentShaderSource = offsetFragCoord(fragmentShaderSource);
657
+ }
658
+ const format = gl.getShaderPrecisionFormat(gl.FRAGMENT_SHADER, gl.MEDIUM_FLOAT);
659
+ const precision = format ? format.precision : null;
660
+ if (precision && precision < 23) {
661
+ vertexShaderSource2 = vertexShaderSource2.replace(/precision\s+(lowp|mediump)\s+float;/g, "precision highp float;");
662
+ fragmentShaderSource = fragmentShaderSource.replace(/precision\s+(lowp|mediump)\s+float/g, "precision highp float").replace(/\b(uniform|varying|attribute)\s+(lowp|mediump)\s+(\w+)/g, "$1 highp $3");
663
+ }
664
+ const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource2);
665
+ const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
666
+ if (!vertexShader || !fragmentShader) return null;
667
+ const program = gl.createProgram();
668
+ if (!program) return null;
669
+ gl.attachShader(program, vertexShader);
670
+ gl.attachShader(program, fragmentShader);
671
+ gl.linkProgram(program);
672
+ if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
673
+ console.error("Unable to initialize the shader program: " + gl.getProgramInfoLog(program));
674
+ gl.deleteProgram(program);
675
+ gl.deleteShader(vertexShader);
676
+ gl.deleteShader(fragmentShader);
677
+ return null;
678
+ }
679
+ gl.detachShader(program, vertexShader);
680
+ gl.detachShader(program, fragmentShader);
681
+ gl.deleteShader(vertexShader);
682
+ gl.deleteShader(fragmentShader);
683
+ return program;
684
+ }
685
+ const defaultStyle = `@layer paper-shaders {
686
+ :where([data-paper-shader]) {
687
+ isolation: isolate;
688
+ position: relative;
689
+
690
+ & canvas {
691
+ contain: strict;
692
+ display: block;
693
+ position: absolute;
694
+ inset: 0;
695
+ z-index: -1;
696
+ width: 100%;
697
+ height: 100%;
698
+ border-radius: inherit;
699
+ corner-shape: inherit;
700
+ }
701
+ }
702
+ }`;
703
+ function isPaperShaderElement(element) {
704
+ return "paperShaderMount" in element;
705
+ }
706
+ function isSafari() {
707
+ const ua = navigator.userAgent.toLowerCase();
708
+ return ua.includes("safari") && !ua.includes("chrome") && !ua.includes("android");
709
+ }
710
+ function bestGuessBrowserZoom(doc) {
711
+ const viewportScale = visualViewport?.scale ?? 1;
712
+ const viewportWidth = visualViewport?.width ?? window.innerWidth;
713
+ const scrollbarWidth = window.innerWidth - doc.documentElement.clientWidth;
714
+ const innerWidth = viewportScale * viewportWidth + scrollbarWidth;
715
+ const ratio = outerWidth / innerWidth;
716
+ const zoomPercentageRounded = Math.round(100 * ratio);
717
+ if (zoomPercentageRounded % 5 === 0) {
718
+ return zoomPercentageRounded / 100;
719
+ }
720
+ if (zoomPercentageRounded === 33) {
721
+ return 1 / 3;
722
+ }
723
+ if (zoomPercentageRounded === 67) {
724
+ return 2 / 3;
725
+ }
726
+ if (zoomPercentageRounded === 133) {
727
+ return 4 / 3;
728
+ }
729
+ return ratio;
730
+ }
731
+ export {
732
+ ShaderMount,
733
+ isPaperShaderElement
734
+ };
735
+ //# sourceMappingURL=shader-mount.js.map