@basementstudio/shader-lab 0.1.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 (261) hide show
  1. package/.biome/plugins/README.md +21 -0
  2. package/.biome/plugins/no-anchor-element.grit +12 -0
  3. package/.biome/plugins/no-relative-parent-imports.grit +10 -0
  4. package/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
  5. package/.changeset/README.md +17 -0
  6. package/.changeset/config.json +11 -0
  7. package/.editorconfig +40 -0
  8. package/.env.example +81 -0
  9. package/.gitattributes +19 -0
  10. package/.github/workflows/canary.yml +80 -0
  11. package/.github/workflows/ci.yml +37 -0
  12. package/.github/workflows/release.yml +56 -0
  13. package/.tldrignore +84 -0
  14. package/.vscode/extensions.json +20 -0
  15. package/.vscode/settings.json +105 -0
  16. package/README.md +119 -0
  17. package/biome.json +249 -0
  18. package/bun.lock +1224 -0
  19. package/next.config.ts +131 -0
  20. package/package.json +73 -0
  21. package/packages/shader-lab-react/CHANGELOG.md +9 -0
  22. package/packages/shader-lab-react/README.md +119 -0
  23. package/packages/shader-lab-react/assets/patterns/bars/1.svg +3 -0
  24. package/packages/shader-lab-react/assets/patterns/bars/2.svg +3 -0
  25. package/packages/shader-lab-react/assets/patterns/bars/3.svg +3 -0
  26. package/packages/shader-lab-react/assets/patterns/bars/4.svg +3 -0
  27. package/packages/shader-lab-react/assets/patterns/bars/5.svg +3 -0
  28. package/packages/shader-lab-react/assets/patterns/bars/6.svg +3 -0
  29. package/packages/shader-lab-react/assets/patterns/candles/1.svg +3 -0
  30. package/packages/shader-lab-react/assets/patterns/candles/2.svg +3 -0
  31. package/packages/shader-lab-react/assets/patterns/candles/3.svg +3 -0
  32. package/packages/shader-lab-react/assets/patterns/candles/4.svg +3 -0
  33. package/packages/shader-lab-react/assets/patterns/shapes/1.svg +3 -0
  34. package/packages/shader-lab-react/assets/patterns/shapes/2.svg +3 -0
  35. package/packages/shader-lab-react/assets/patterns/shapes/3.svg +3 -0
  36. package/packages/shader-lab-react/assets/patterns/shapes/4.svg +4 -0
  37. package/packages/shader-lab-react/assets/patterns/shapes/5.svg +3 -0
  38. package/packages/shader-lab-react/assets/patterns/shapes/6.svg +4 -0
  39. package/packages/shader-lab-react/assets/textures/blue-noise.png +0 -0
  40. package/packages/shader-lab-react/package.json +36 -0
  41. package/packages/shader-lab-react/scripts/fix-esm-specifiers.mjs +57 -0
  42. package/packages/shader-lab-react/scripts/prepare-dist.mjs +4 -0
  43. package/packages/shader-lab-react/src/ambient/three-tsl.d.ts +146 -0
  44. package/packages/shader-lab-react/src/ambient/three-webgpu.d.ts +51 -0
  45. package/packages/shader-lab-react/src/easings.ts +4 -0
  46. package/packages/shader-lab-react/src/index.ts +35 -0
  47. package/packages/shader-lab-react/src/lib/editor/custom-shader/shared.ts +2 -0
  48. package/packages/shader-lab-react/src/renderer/ascii-atlas.ts +83 -0
  49. package/packages/shader-lab-react/src/renderer/ascii-pass.ts +416 -0
  50. package/packages/shader-lab-react/src/renderer/asset-url.ts +3 -0
  51. package/packages/shader-lab-react/src/renderer/blend-modes.ts +229 -0
  52. package/packages/shader-lab-react/src/renderer/contracts.ts +54 -0
  53. package/packages/shader-lab-react/src/renderer/create-webgpu-renderer.ts +48 -0
  54. package/packages/shader-lab-react/src/renderer/crt-pass.ts +1040 -0
  55. package/packages/shader-lab-react/src/renderer/custom-shader-pass.ts +108 -0
  56. package/packages/shader-lab-react/src/renderer/custom-shader-runtime.ts +309 -0
  57. package/packages/shader-lab-react/src/renderer/dither-textures.ts +99 -0
  58. package/packages/shader-lab-react/src/renderer/dithering-pass.ts +322 -0
  59. package/packages/shader-lab-react/src/renderer/gradient-pass.ts +521 -0
  60. package/packages/shader-lab-react/src/renderer/halftone-pass.ts +932 -0
  61. package/packages/shader-lab-react/src/renderer/ink-pass.ts +802 -0
  62. package/packages/shader-lab-react/src/renderer/live-pass.ts +194 -0
  63. package/packages/shader-lab-react/src/renderer/media-pass.ts +187 -0
  64. package/packages/shader-lab-react/src/renderer/media-texture.ts +66 -0
  65. package/packages/shader-lab-react/src/renderer/particle-grid-pass.ts +389 -0
  66. package/packages/shader-lab-react/src/renderer/pass-node.ts +209 -0
  67. package/packages/shader-lab-react/src/renderer/pattern-atlas.ts +133 -0
  68. package/packages/shader-lab-react/src/renderer/pattern-pass.ts +552 -0
  69. package/packages/shader-lab-react/src/renderer/pipeline-manager.ts +369 -0
  70. package/packages/shader-lab-react/src/renderer/pixel-sorting-pass.ts +277 -0
  71. package/packages/shader-lab-react/src/renderer/shaders/tsl/color/tonemapping.ts +87 -0
  72. package/packages/shader-lab-react/src/renderer/shaders/tsl/cosine-palette.ts +9 -0
  73. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/common.ts +31 -0
  74. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +36 -0
  75. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +36 -0
  76. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/fbm.ts +13 -0
  77. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +96 -0
  78. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/ridge-noise.ts +24 -0
  79. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +79 -0
  80. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +89 -0
  81. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/turbulence.ts +56 -0
  82. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/value-noise-3d.ts +32 -0
  83. package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +60 -0
  84. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +15 -0
  85. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom.ts +11 -0
  86. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +24 -0
  87. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +9 -0
  88. package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +11 -0
  89. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/atan2.ts +9 -0
  90. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-conj.ts +9 -0
  91. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-cos.ts +10 -0
  92. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-div.ts +11 -0
  93. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-log.ts +7 -0
  94. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mobius.ts +12 -0
  95. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mul.ts +9 -0
  96. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-pow.ts +16 -0
  97. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sin.ts +10 -0
  98. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sqrt.ts +18 -0
  99. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-tan.ts +12 -0
  100. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-to-polar.ts +10 -0
  101. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/hyperbolic.ts +20 -0
  102. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/index.ts +48 -0
  103. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/rotate.ts +15 -0
  104. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +15 -0
  105. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-box-2d.ts +6 -0
  106. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-diamond.ts +6 -0
  107. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-rhombus.ts +27 -0
  108. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-sphere.ts +6 -0
  109. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smax.ts +7 -0
  110. package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smin.ts +7 -0
  111. package/packages/shader-lab-react/src/renderer/text-pass.ts +176 -0
  112. package/packages/shader-lab-react/src/runtime-clock.ts +42 -0
  113. package/packages/shader-lab-react/src/runtime-frame.ts +29 -0
  114. package/packages/shader-lab-react/src/shader-lab-composition.tsx +163 -0
  115. package/packages/shader-lab-react/src/timeline.ts +283 -0
  116. package/packages/shader-lab-react/src/types/editor.ts +5 -0
  117. package/packages/shader-lab-react/src/types.ts +141 -0
  118. package/packages/shader-lab-react/tsconfig.build.json +8 -0
  119. package/packages/shader-lab-react/tsconfig.json +21 -0
  120. package/postcss.config.mjs +5 -0
  121. package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf-atlas.png +0 -0
  122. package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf.json +1412 -0
  123. package/public/assets/patterns/bars/1.svg +3 -0
  124. package/public/assets/patterns/bars/2.svg +3 -0
  125. package/public/assets/patterns/bars/3.svg +3 -0
  126. package/public/assets/patterns/bars/4.svg +3 -0
  127. package/public/assets/patterns/bars/5.svg +3 -0
  128. package/public/assets/patterns/bars/6.svg +3 -0
  129. package/public/assets/patterns/candles/1.svg +3 -0
  130. package/public/assets/patterns/candles/2.svg +3 -0
  131. package/public/assets/patterns/candles/3.svg +3 -0
  132. package/public/assets/patterns/candles/4.svg +3 -0
  133. package/public/assets/patterns/shapes/1.svg +3 -0
  134. package/public/assets/patterns/shapes/2.svg +3 -0
  135. package/public/assets/patterns/shapes/3.svg +3 -0
  136. package/public/assets/patterns/shapes/4.svg +4 -0
  137. package/public/assets/patterns/shapes/5.svg +3 -0
  138. package/public/assets/patterns/shapes/6.svg +4 -0
  139. package/public/fonts/geist/Geist-Mono.woff2 +0 -0
  140. package/public/textures/blue-noise.png +0 -0
  141. package/public/textures/crt-mask.png +0 -0
  142. package/src/app/design/page.tsx +398 -0
  143. package/src/app/favicon.ico +0 -0
  144. package/src/app/globals.css +280 -0
  145. package/src/app/layout.tsx +89 -0
  146. package/src/app/page.tsx +20 -0
  147. package/src/app/robots.ts +13 -0
  148. package/src/app/sitemap.ts +13 -0
  149. package/src/components/editor/editor-canvas-viewport.tsx +116 -0
  150. package/src/components/editor/editor-export-dialog.tsx +1177 -0
  151. package/src/components/editor/editor-timeline-overlay.tsx +983 -0
  152. package/src/components/editor/editor-topbar.tsx +287 -0
  153. package/src/components/editor/layer-sidebar.tsx +738 -0
  154. package/src/components/editor/properties-sidebar-content.tsx +574 -0
  155. package/src/components/editor/properties-sidebar-fields.tsx +389 -0
  156. package/src/components/editor/properties-sidebar-utils.ts +178 -0
  157. package/src/components/editor/properties-sidebar.tsx +421 -0
  158. package/src/components/ui/button/index.tsx +57 -0
  159. package/src/components/ui/color-picker/index.tsx +358 -0
  160. package/src/components/ui/glass-panel/index.tsx +45 -0
  161. package/src/components/ui/icon-button/index.tsx +46 -0
  162. package/src/components/ui/select/index.tsx +136 -0
  163. package/src/components/ui/slider/index.tsx +192 -0
  164. package/src/components/ui/toggle/index.tsx +34 -0
  165. package/src/components/ui/typography/index.tsx +61 -0
  166. package/src/components/ui/xy-pad/index.tsx +160 -0
  167. package/src/features/editor/components/editor-export-dialog.module.css +271 -0
  168. package/src/hooks/use-editor-renderer.ts +182 -0
  169. package/src/lib/app.ts +6 -0
  170. package/src/lib/cn.ts +7 -0
  171. package/src/lib/easings.ts +240 -0
  172. package/src/lib/editor/config/layer-registry.ts +2434 -0
  173. package/src/lib/editor/custom-shader/shared.ts +28 -0
  174. package/src/lib/editor/export.ts +420 -0
  175. package/src/lib/editor/history.ts +71 -0
  176. package/src/lib/editor/layers.ts +76 -0
  177. package/src/lib/editor/parameter-schema.ts +75 -0
  178. package/src/lib/editor/project-file.ts +145 -0
  179. package/src/lib/editor/shader-export-snippet.ts +37 -0
  180. package/src/lib/editor/shader-export.ts +315 -0
  181. package/src/lib/editor/timeline/evaluate.ts +252 -0
  182. package/src/lib/editor/view-transform.ts +58 -0
  183. package/src/lib/fonts.ts +28 -0
  184. package/src/renderer/ascii-atlas.ts +83 -0
  185. package/src/renderer/ascii-pass.ts +416 -0
  186. package/src/renderer/blend-modes.ts +229 -0
  187. package/src/renderer/contracts.ts +161 -0
  188. package/src/renderer/create-webgpu-renderer.ts +48 -0
  189. package/src/renderer/crt-pass.ts +1040 -0
  190. package/src/renderer/custom-shader-pass.ts +117 -0
  191. package/src/renderer/custom-shader-runtime.ts +309 -0
  192. package/src/renderer/dither-textures.ts +99 -0
  193. package/src/renderer/dithering-pass.ts +322 -0
  194. package/src/renderer/gradient-pass.ts +520 -0
  195. package/src/renderer/halftone-pass.ts +932 -0
  196. package/src/renderer/ink-pass.ts +683 -0
  197. package/src/renderer/live-pass.ts +194 -0
  198. package/src/renderer/media-pass.ts +187 -0
  199. package/src/renderer/media-texture.ts +66 -0
  200. package/src/renderer/particle-grid-pass.ts +389 -0
  201. package/src/renderer/pass-node-factory.ts +33 -0
  202. package/src/renderer/pass-node.ts +209 -0
  203. package/src/renderer/pattern-atlas.ts +97 -0
  204. package/src/renderer/pattern-pass.ts +552 -0
  205. package/src/renderer/pipeline-manager.ts +343 -0
  206. package/src/renderer/pixel-sorting-pass.ts +277 -0
  207. package/src/renderer/project-clock.ts +57 -0
  208. package/src/renderer/shaders/tsl/color/tonemapping.ts +86 -0
  209. package/src/renderer/shaders/tsl/cosine-palette.ts +8 -0
  210. package/src/renderer/shaders/tsl/noise/common.ts +30 -0
  211. package/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +35 -0
  212. package/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +35 -0
  213. package/src/renderer/shaders/tsl/noise/fbm.ts +12 -0
  214. package/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +97 -0
  215. package/src/renderer/shaders/tsl/noise/ridge-noise.ts +23 -0
  216. package/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +78 -0
  217. package/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +88 -0
  218. package/src/renderer/shaders/tsl/noise/turbulence.ts +55 -0
  219. package/src/renderer/shaders/tsl/noise/value-noise-3d.ts +31 -0
  220. package/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +59 -0
  221. package/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +14 -0
  222. package/src/renderer/shaders/tsl/patterns/bloom.ts +10 -0
  223. package/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +23 -0
  224. package/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +8 -0
  225. package/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +10 -0
  226. package/src/renderer/shaders/tsl/utils/atan2.ts +8 -0
  227. package/src/renderer/shaders/tsl/utils/complex-conj.ts +8 -0
  228. package/src/renderer/shaders/tsl/utils/complex-cos.ts +9 -0
  229. package/src/renderer/shaders/tsl/utils/complex-div.ts +10 -0
  230. package/src/renderer/shaders/tsl/utils/complex-log.ts +6 -0
  231. package/src/renderer/shaders/tsl/utils/complex-mobius.ts +11 -0
  232. package/src/renderer/shaders/tsl/utils/complex-mul.ts +8 -0
  233. package/src/renderer/shaders/tsl/utils/complex-pow.ts +15 -0
  234. package/src/renderer/shaders/tsl/utils/complex-sin.ts +9 -0
  235. package/src/renderer/shaders/tsl/utils/complex-sqrt.ts +17 -0
  236. package/src/renderer/shaders/tsl/utils/complex-tan.ts +11 -0
  237. package/src/renderer/shaders/tsl/utils/complex-to-polar.ts +9 -0
  238. package/src/renderer/shaders/tsl/utils/hyperbolic.ts +19 -0
  239. package/src/renderer/shaders/tsl/utils/index.ts +47 -0
  240. package/src/renderer/shaders/tsl/utils/rotate.ts +14 -0
  241. package/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +14 -0
  242. package/src/renderer/shaders/tsl/utils/sd-box-2d.ts +5 -0
  243. package/src/renderer/shaders/tsl/utils/sd-diamond.ts +5 -0
  244. package/src/renderer/shaders/tsl/utils/sd-rhombus.ts +26 -0
  245. package/src/renderer/shaders/tsl/utils/sd-sphere.ts +5 -0
  246. package/src/renderer/shaders/tsl/utils/smax.ts +7 -0
  247. package/src/renderer/shaders/tsl/utils/smin.ts +6 -0
  248. package/src/renderer/text-pass.ts +176 -0
  249. package/src/store/asset-store.ts +193 -0
  250. package/src/store/editor-store.ts +223 -0
  251. package/src/store/history-store.ts +172 -0
  252. package/src/store/index.ts +31 -0
  253. package/src/store/layer-store.ts +675 -0
  254. package/src/store/timeline-store.ts +572 -0
  255. package/src/types/assets.d.ts +6 -0
  256. package/src/types/css.d.ts +21 -0
  257. package/src/types/editor.ts +357 -0
  258. package/src/types/react.d.ts +15 -0
  259. package/src/types/three-tsl.d.ts +146 -0
  260. package/src/types/three-webgpu.d.ts +51 -0
  261. package/tsconfig.json +49 -0
@@ -0,0 +1,369 @@
1
+ import { float, type TSLNode, texture as tslTexture, uv, vec2 } from "three/tsl"
2
+ import * as THREE from "three/webgpu"
3
+ import { AsciiPass } from "./ascii-pass"
4
+ import { CrtPass } from "./crt-pass"
5
+ import { CustomShaderPass } from "./custom-shader-pass"
6
+ import { DitheringPass } from "./dithering-pass"
7
+ import { GradientPass } from "./gradient-pass"
8
+ import { HalftonePass } from "./halftone-pass"
9
+ import { InkPass } from "./ink-pass"
10
+ import { LivePass } from "./live-pass"
11
+ import { MediaPass } from "./media-pass"
12
+ import { ParticleGridPass } from "./particle-grid-pass"
13
+ import type { PassNode } from "./pass-node"
14
+ import { PatternPass } from "./pattern-pass"
15
+ import { PixelSortingPass } from "./pixel-sorting-pass"
16
+ import { TextPass } from "./text-pass"
17
+ import type { ShaderLabCompositeMode, ShaderLabLayerConfig } from "../types"
18
+
19
+ type LayerPassNode =
20
+ | AsciiPass
21
+ | CrtPass
22
+ | CustomShaderPass
23
+ | DitheringPass
24
+ | GradientPass
25
+ | HalftonePass
26
+ | InkPass
27
+ | LivePass
28
+ | MediaPass
29
+ | ParticleGridPass
30
+ | PassNode
31
+ | PatternPass
32
+ | PixelSortingPass
33
+ | TextPass
34
+
35
+ const RENDER_TARGET_OPTIONS = {
36
+ depthBuffer: false,
37
+ format: THREE.RGBAFormat,
38
+ generateMipmaps: false,
39
+ magFilter: THREE.NearestFilter,
40
+ minFilter: THREE.NearestFilter,
41
+ stencilBuffer: false,
42
+ type: THREE.HalfFloatType,
43
+ } as const
44
+
45
+ function clampUnit(value: number): number {
46
+ return Math.max(0, Math.min(1, value))
47
+ }
48
+
49
+ function parameterValuesSignature(params: ShaderLabLayerConfig["params"]): string {
50
+ return JSON.stringify(
51
+ Object.entries(params)
52
+ .sort(([left], [right]) => left.localeCompare(right))
53
+ .map(([key, value]) => [key, value]),
54
+ )
55
+ }
56
+
57
+ function createLayerSignature(layer: ShaderLabLayerConfig): string {
58
+ if (layer.type === "custom-shader") {
59
+ return [
60
+ layer.id,
61
+ layer.kind,
62
+ layer.type,
63
+ layer.visible ? "1" : "0",
64
+ layer.opacity.toFixed(4),
65
+ layer.hue.toFixed(4),
66
+ layer.saturation.toFixed(4),
67
+ layer.blendMode,
68
+ layer.compositeMode,
69
+ typeof layer.params.sourceRevision === "number"
70
+ ? String(layer.params.sourceRevision)
71
+ : "0",
72
+ typeof layer.params.sourceMode === "string" ? layer.params.sourceMode : "paste",
73
+ typeof layer.params.entryExport === "string" ? layer.params.entryExport : "sketch",
74
+ typeof layer.params.sourceFileName === "string" ? layer.params.sourceFileName : "",
75
+ ].join("|")
76
+ }
77
+
78
+ return [
79
+ layer.id,
80
+ layer.kind,
81
+ layer.type,
82
+ layer.asset?.kind ?? "no-asset",
83
+ layer.asset?.src ?? "no-src",
84
+ layer.visible ? "1" : "0",
85
+ layer.opacity.toFixed(4),
86
+ layer.hue.toFixed(4),
87
+ layer.saturation.toFixed(4),
88
+ layer.blendMode,
89
+ layer.compositeMode,
90
+ parameterValuesSignature(layer.params),
91
+ ].join("|")
92
+ }
93
+
94
+ export class PipelineManager {
95
+ private readonly renderer: THREE.WebGPURenderer
96
+ private readonly baseScene: THREE.Scene
97
+ private readonly baseCamera: THREE.OrthographicCamera
98
+ private readonly blitScene: THREE.Scene
99
+ private readonly blitCamera: THREE.OrthographicCamera
100
+ private readonly blitInputNode: TSLNode
101
+ private readonly blitMaterial: THREE.MeshBasicNodeMaterial
102
+ private readonly onRuntimeError: ((message: string | null) => void) | undefined
103
+
104
+ private passMap = new Map<string, LayerPassNode>()
105
+ private passes: LayerPassNode[] = []
106
+ private layerSignatures = new Map<string, string>()
107
+ private dirty = true
108
+ private width: number
109
+ private height: number
110
+ private logicalWidth: number
111
+ private logicalHeight: number
112
+ private rtA: THREE.WebGLRenderTarget
113
+ private rtB: THREE.WebGLRenderTarget
114
+
115
+ constructor(
116
+ renderer: THREE.WebGPURenderer,
117
+ size: { height: number; width: number },
118
+ onRuntimeError?: (message: string | null) => void,
119
+ ) {
120
+ this.renderer = renderer
121
+ this.onRuntimeError = onRuntimeError
122
+ this.width = Math.max(1, size.width)
123
+ this.height = Math.max(1, size.height)
124
+ this.logicalWidth = this.width
125
+ this.logicalHeight = this.height
126
+
127
+ this.baseScene = new THREE.Scene()
128
+ this.baseCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
129
+ const baseMaterial = new THREE.MeshBasicMaterial({ color: "#080808" })
130
+ const baseMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), baseMaterial)
131
+ baseMesh.frustumCulled = false
132
+ this.baseScene.add(baseMesh)
133
+
134
+ this.rtA = new THREE.WebGLRenderTarget(this.width, this.height, RENDER_TARGET_OPTIONS)
135
+ this.rtB = new THREE.WebGLRenderTarget(this.width, this.height, RENDER_TARGET_OPTIONS)
136
+
137
+ this.blitScene = new THREE.Scene()
138
+ this.blitCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
139
+ const blitUv = vec2(uv().x, float(1).sub(uv().y))
140
+ this.blitInputNode = tslTexture(new THREE.Texture(), blitUv)
141
+ this.blitMaterial = new THREE.MeshBasicNodeMaterial()
142
+ this.blitMaterial.colorNode = this.blitInputNode
143
+ const blitMesh = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), this.blitMaterial)
144
+ blitMesh.frustumCulled = false
145
+ this.blitScene.add(blitMesh)
146
+ }
147
+
148
+ syncLayers(layers: ShaderLabLayerConfig[]): void {
149
+ const incomingIds = new Set(layers.map((layer) => layer.id))
150
+
151
+ for (const [layerId, pass] of this.passMap) {
152
+ if (incomingIds.has(layerId)) {
153
+ continue
154
+ }
155
+
156
+ pass.dispose()
157
+ this.passMap.delete(layerId)
158
+ this.layerSignatures.delete(layerId)
159
+ this.dirty = true
160
+ }
161
+
162
+ const orderedPasses: LayerPassNode[] = []
163
+
164
+ for (const layer of layers) {
165
+ const signature = createLayerSignature(layer)
166
+ let pass = this.passMap.get(layer.id)
167
+
168
+ if (!pass) {
169
+ pass = this.createPass(layer)
170
+ pass.resize(this.width, this.height)
171
+ pass.updateLogicalSize(this.logicalWidth, this.logicalHeight)
172
+ this.passMap.set(layer.id, pass)
173
+ this.dirty = true
174
+ }
175
+
176
+ if (this.layerSignatures.get(layer.id) !== signature) {
177
+ this.layerSignatures.set(layer.id, signature)
178
+ this.applyLayerState(pass, layer)
179
+ this.dirty = true
180
+ }
181
+
182
+ orderedPasses.push(pass)
183
+ }
184
+
185
+ if (
186
+ orderedPasses.length !== this.passes.length ||
187
+ orderedPasses.some((pass, index) => this.passes[index] !== pass)
188
+ ) {
189
+ this.passes = orderedPasses
190
+ this.dirty = true
191
+ }
192
+ }
193
+
194
+ render(time: number, delta: number): boolean {
195
+ const activePasses = this.passes.filter((pass) => pass.enabled)
196
+ const needsContinuousRender = activePasses.some((pass) => pass.needsContinuousRender())
197
+
198
+ if (!(this.dirty || needsContinuousRender)) {
199
+ return false
200
+ }
201
+
202
+ if (activePasses.length === 0) {
203
+ this.renderer.setRenderTarget(null)
204
+ this.renderer.render(this.baseScene, this.baseCamera)
205
+ this.dirty = false
206
+ return true
207
+ }
208
+
209
+ this.renderer.setRenderTarget(this.rtA)
210
+ this.renderer.render(this.baseScene, this.baseCamera)
211
+
212
+ let readTarget = this.rtA
213
+ let writeTarget = this.rtB
214
+
215
+ for (const pass of activePasses) {
216
+ pass.render(this.renderer, readTarget.texture, writeTarget, time, delta)
217
+ const previousRead = readTarget
218
+ readTarget = writeTarget
219
+ writeTarget = previousRead
220
+ }
221
+
222
+ this.blitInputNode.value = readTarget.texture
223
+ this.renderer.setRenderTarget(null)
224
+ this.renderer.render(this.blitScene, this.blitCamera)
225
+ this.dirty = false
226
+ return true
227
+ }
228
+
229
+ resize(size: { height: number; width: number }): void {
230
+ this.width = Math.max(1, size.width)
231
+ this.height = Math.max(1, size.height)
232
+ this.rtA.setSize(this.width, this.height)
233
+ this.rtB.setSize(this.width, this.height)
234
+
235
+ for (const pass of this.passMap.values()) {
236
+ pass.resize(this.width, this.height)
237
+ }
238
+
239
+ this.dirty = true
240
+ }
241
+
242
+ updateLogicalSize(size: { height: number; width: number }): void {
243
+ const nextWidth = Math.max(1, size.width)
244
+ const nextHeight = Math.max(1, size.height)
245
+
246
+ if (nextWidth === this.logicalWidth && nextHeight === this.logicalHeight) {
247
+ return
248
+ }
249
+
250
+ this.logicalWidth = nextWidth
251
+ this.logicalHeight = nextHeight
252
+
253
+ for (const pass of this.passMap.values()) {
254
+ pass.updateLogicalSize(this.logicalWidth, this.logicalHeight)
255
+ }
256
+
257
+ this.dirty = true
258
+ }
259
+
260
+ dispose(): void {
261
+ this.rtA.dispose()
262
+ this.rtB.dispose()
263
+ this.blitMaterial.dispose()
264
+
265
+ for (const pass of this.passMap.values()) {
266
+ pass.dispose()
267
+ }
268
+
269
+ this.passMap.clear()
270
+ this.passes = []
271
+ this.layerSignatures.clear()
272
+ }
273
+
274
+ private applyLayerState(pass: LayerPassNode, layer: ShaderLabLayerConfig): void {
275
+ pass.enabled = layer.visible
276
+ pass.updateOpacity(clampUnit(layer.opacity))
277
+ pass.updateBlendMode(layer.blendMode)
278
+ const compositeMode: ShaderLabCompositeMode =
279
+ layer.compositeMode === "mask" ? "mask" : "filter"
280
+ pass.updateCompositeMode(compositeMode)
281
+ pass.updateLayerColorAdjustments(layer.hue, layer.saturation)
282
+ pass.updateParams(layer.params)
283
+
284
+ if (pass instanceof MediaPass) {
285
+ const asset = layer.asset
286
+ if (asset?.kind === "image" || asset?.kind === "video") {
287
+ void pass
288
+ .setMedia(asset.src, asset.kind)
289
+ .then(() => {
290
+ this.dirty = true
291
+ })
292
+ .catch((error) => {
293
+ this.onRuntimeError?.(
294
+ error instanceof Error ? error.message : "Failed to load media asset.",
295
+ )
296
+ this.dirty = true
297
+ })
298
+ } else {
299
+ pass.clearMedia()
300
+ }
301
+ }
302
+
303
+ if (pass instanceof LivePass) {
304
+ const facingMode =
305
+ typeof layer.params.facingMode === "string" ? layer.params.facingMode : "user"
306
+
307
+ if (facingMode !== pass.getFacingMode() || !pass.needsContinuousRender()) {
308
+ void pass
309
+ .startCamera(facingMode)
310
+ .then(() => {
311
+ this.dirty = true
312
+ })
313
+ .catch((error) => {
314
+ this.onRuntimeError?.(
315
+ error instanceof Error ? error.message : "Failed to start live camera input.",
316
+ )
317
+ this.dirty = true
318
+ })
319
+ }
320
+ }
321
+ }
322
+
323
+ private createPass(layer: ShaderLabLayerConfig): LayerPassNode {
324
+ if (layer.kind === "effect") {
325
+ switch (layer.type) {
326
+ case "ascii":
327
+ return new AsciiPass(layer.id)
328
+ case "crt":
329
+ return new CrtPass(layer.id)
330
+ case "dithering":
331
+ return new DitheringPass(layer.id)
332
+ case "halftone":
333
+ return new HalftonePass(layer.id)
334
+ case "ink":
335
+ return new InkPass(layer.id)
336
+ case "particle-grid":
337
+ return new ParticleGridPass(layer.id)
338
+ case "pattern":
339
+ return new PatternPass(layer.id)
340
+ case "pixel-sorting":
341
+ return new PixelSortingPass(layer.id)
342
+ }
343
+ }
344
+
345
+ if (layer.kind === "source" && (layer.type === "image" || layer.type === "video")) {
346
+ return new MediaPass(layer.id)
347
+ }
348
+
349
+ if (layer.kind === "source" && layer.type === "gradient") {
350
+ return new GradientPass(layer.id)
351
+ }
352
+
353
+ if (layer.kind === "source" && layer.type === "text") {
354
+ return new TextPass(layer.id)
355
+ }
356
+
357
+ if (layer.kind === "source" && layer.type === "custom-shader") {
358
+ return new CustomShaderPass(layer.id, this.onRuntimeError)
359
+ }
360
+
361
+ if (layer.kind === "source" && layer.type === "live") {
362
+ return new LivePass(layer.id)
363
+ }
364
+
365
+ throw new Error(
366
+ `Layer "${layer.name}" of type "${layer.type}" is not supported by the package runtime yet.`,
367
+ )
368
+ }
369
+ }
@@ -0,0 +1,277 @@
1
+ import * as THREE from "three/webgpu"
2
+ import {
3
+ dot,
4
+ float,
5
+ floor,
6
+ max,
7
+ min,
8
+ mod,
9
+ select,
10
+ texture as tslTexture,
11
+ type TSLNode,
12
+ uniform,
13
+ uv,
14
+ vec2,
15
+ vec3,
16
+ vec4,
17
+ } from "three/tsl"
18
+ import { PassNode } from "./pass-node"
19
+ import type { LayerParameterValues } from "../types/editor"
20
+
21
+ type Node = TSLNode
22
+
23
+ export class PixelSortingPass extends PassNode {
24
+ private readonly sortScene: THREE.Scene
25
+ private readonly sortCamera: THREE.OrthographicCamera
26
+ private readonly sortMaterial: THREE.MeshBasicNodeMaterial
27
+ private sortRtA: THREE.WebGLRenderTarget
28
+ private sortRtB: THREE.WebGLRenderTarget
29
+
30
+ private readonly blitInputNode: Node
31
+ private readonly sortTexNodeA: Node
32
+ private readonly sortTexNodeB: Node
33
+
34
+ private readonly passOffsetUniform: Node
35
+ private readonly widthUniform: Node
36
+ private readonly heightUniform: Node
37
+ private readonly thresholdUniform: Node
38
+ private readonly upperThresholdUniform: Node
39
+ private readonly directionUniform: Node
40
+ private readonly modeUniform: Node
41
+ private readonly reverseUniform: Node
42
+
43
+ private passCount = 150
44
+ private width = 1
45
+ private height = 1
46
+
47
+ constructor(layerId: string) {
48
+ super(layerId)
49
+
50
+ this.sortScene = new THREE.Scene()
51
+ this.sortCamera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, 1)
52
+
53
+ this.passOffsetUniform = uniform(0)
54
+ this.widthUniform = uniform(1)
55
+ this.heightUniform = uniform(1)
56
+ this.thresholdUniform = uniform(0.25)
57
+ this.upperThresholdUniform = uniform(1)
58
+ this.directionUniform = uniform(0)
59
+ this.modeUniform = uniform(0)
60
+ this.reverseUniform = uniform(0)
61
+
62
+ const placeholder = new THREE.Texture()
63
+
64
+ // Compute pixel coordinates from flipped UVs
65
+ const texUv = vec2(uv().x, float(1).sub(uv().y))
66
+ const dims = vec2(this.widthUniform, this.heightUniform)
67
+ const pixelCoord = floor(texUv.mul(dims))
68
+
69
+ // Sort axis: horizontal (x) or vertical (y)
70
+ const isHorizontal = this.directionUniform.lessThan(float(0.5))
71
+ const sortIdx = select(isHorizontal, pixelCoord.x, pixelCoord.y)
72
+ const maxIdx = select(isHorizontal, this.widthUniform, this.heightUniform)
73
+
74
+ // Odd-even transposition: alternate pair groupings each pass
75
+ const pairMod = mod(sortIdx.add(this.passOffsetUniform), float(2))
76
+ const isLeft = pairMod.lessThan(float(1))
77
+
78
+ // Neighbor pixel coordinate
79
+ const neighborDir = select(isLeft, float(1), float(-1))
80
+ const neighborCoord = vec2(
81
+ select(isHorizontal, pixelCoord.x.add(neighborDir), pixelCoord.x),
82
+ select(isHorizontal, pixelCoord.y, pixelCoord.y.add(neighborDir)),
83
+ )
84
+
85
+ // Bounds check
86
+ const neighborIdx = select(isHorizontal, neighborCoord.x, neighborCoord.y)
87
+ const inBounds = neighborIdx
88
+ .greaterThanEqual(float(0))
89
+ .and(neighborIdx.lessThan(maxIdx))
90
+
91
+ // Sample both pixels (snapped to texel centers)
92
+ const mySnapUv = pixelCoord.add(0.5).div(dims)
93
+ const neighborSnapUv = neighborCoord.add(0.5).div(dims)
94
+
95
+ this.sortTexNodeA = tslTexture(placeholder, mySnapUv)
96
+ this.sortTexNodeB = tslTexture(placeholder, neighborSnapUv)
97
+
98
+ const myColor = this.sortTexNodeA
99
+ const neighborColor = this.sortTexNodeB
100
+
101
+ // Sort value: luminance
102
+ const lumaW = vec3(0.2126, 0.7152, 0.0722)
103
+ const myLuma = dot(vec3(myColor.r, myColor.g, myColor.b), lumaW)
104
+ const neighborLuma = dot(
105
+ vec3(neighborColor.r, neighborColor.g, neighborColor.b),
106
+ lumaW,
107
+ )
108
+
109
+ // Sort value: warmth (R-B, approximates hue/temperature)
110
+ const myWarmth = myColor.r.sub(myColor.b)
111
+ const neighborWarmth = neighborColor.r.sub(neighborColor.b)
112
+
113
+ // Sort value: saturation (HSV)
114
+ const myMax = max(max(myColor.r, myColor.g), myColor.b)
115
+ const myMin = min(min(myColor.r, myColor.g), myColor.b)
116
+ const mySat = select(
117
+ myMax.greaterThan(float(0.001)),
118
+ myMax.sub(myMin).div(myMax),
119
+ float(0),
120
+ )
121
+ const nMax = max(max(neighborColor.r, neighborColor.g), neighborColor.b)
122
+ const nMin = min(min(neighborColor.r, neighborColor.g), neighborColor.b)
123
+ const nSat = select(
124
+ nMax.greaterThan(float(0.001)),
125
+ nMax.sub(nMin).div(nMax),
126
+ float(0),
127
+ )
128
+
129
+ // Select sort value based on mode (0=luma, 1=hue/warmth, 2=saturation)
130
+ const isSatMode = this.modeUniform.greaterThan(float(1.5))
131
+ const isHueMode = this.modeUniform.greaterThan(float(0.5))
132
+ const myValue = select(isSatMode, mySat, select(isHueMode, myWarmth, myLuma))
133
+ const neighborValue = select(
134
+ isSatMode,
135
+ nSat,
136
+ select(isHueMode, neighborWarmth, neighborLuma),
137
+ )
138
+
139
+ // Validation: both pixels must be within threshold band (luma-based)
140
+ const myInBand = myLuma
141
+ .greaterThan(this.thresholdUniform)
142
+ .and(myLuma.lessThan(this.upperThresholdUniform))
143
+ const neighborInBand = neighborLuma
144
+ .greaterThan(this.thresholdUniform)
145
+ .and(neighborLuma.lessThan(this.upperThresholdUniform))
146
+ const valid = myInBand.or(neighborInBand)
147
+
148
+ // Sort: swap if left > right (ascending) or left < right (descending/reverse)
149
+ const leftValue = select(isLeft, myValue, neighborValue)
150
+ const rightValue = select(isLeft, neighborValue, myValue)
151
+ const isReverse = this.reverseUniform.greaterThan(float(0.5))
152
+ const shouldSwap = select(
153
+ isReverse,
154
+ rightValue.greaterThan(leftValue),
155
+ leftValue.greaterThan(rightValue),
156
+ )
157
+
158
+ const doSwap = inBounds.and(valid).and(shouldSwap)
159
+ const result = vec4(
160
+ select(doSwap, neighborColor.r, myColor.r),
161
+ select(doSwap, neighborColor.g, myColor.g),
162
+ select(doSwap, neighborColor.b, myColor.b),
163
+ float(1),
164
+ )
165
+
166
+ this.sortMaterial = new THREE.MeshBasicNodeMaterial()
167
+ this.sortMaterial.colorNode = result as Node
168
+
169
+ const sortMesh = new THREE.Mesh(
170
+ new THREE.PlaneGeometry(2, 2),
171
+ this.sortMaterial,
172
+ )
173
+ sortMesh.frustumCulled = false
174
+ this.sortScene.add(sortMesh)
175
+
176
+ // Internal ping-pong render targets
177
+ const rtOptions = {
178
+ depthBuffer: false,
179
+ format: THREE.RGBAFormat,
180
+ generateMipmaps: false,
181
+ magFilter: THREE.NearestFilter,
182
+ minFilter: THREE.NearestFilter,
183
+ stencilBuffer: false,
184
+ type: THREE.HalfFloatType,
185
+ }
186
+ this.sortRtA = new THREE.WebGLRenderTarget(1, 1, rtOptions)
187
+ this.sortRtB = new THREE.WebGLRenderTarget(1, 1, rtOptions)
188
+
189
+ // Blit node for PassNode pipeline
190
+ const blitUv = vec2(uv().x, float(1).sub(uv().y))
191
+ this.blitInputNode = tslTexture(new THREE.Texture(), blitUv)
192
+
193
+ this.rebuildEffectNode()
194
+ }
195
+
196
+ override render(
197
+ renderer: THREE.WebGPURenderer,
198
+ inputTexture: THREE.Texture,
199
+ outputTarget: THREE.WebGLRenderTarget,
200
+ time: number,
201
+ delta: number,
202
+ ): void {
203
+ let readTexture: THREE.Texture = inputTexture
204
+ let writeTarget = this.sortRtA
205
+
206
+ for (let i = 0; i < this.passCount; i++) {
207
+ this.passOffsetUniform.value = i % 2
208
+ this.sortTexNodeA.value = readTexture
209
+ this.sortTexNodeB.value = readTexture
210
+ renderer.setRenderTarget(writeTarget)
211
+ renderer.render(this.sortScene, this.sortCamera)
212
+
213
+ if (writeTarget === this.sortRtA) {
214
+ readTexture = this.sortRtA.texture
215
+ writeTarget = this.sortRtB
216
+ } else {
217
+ readTexture = this.sortRtB.texture
218
+ writeTarget = this.sortRtA
219
+ }
220
+ }
221
+
222
+ this.blitInputNode.value = readTexture
223
+ super.render(renderer, inputTexture, outputTarget, time, delta)
224
+ }
225
+
226
+ override updateParams(params: LayerParameterValues): void {
227
+ this.thresholdUniform.value =
228
+ typeof params.threshold === "number" ? params.threshold : 0.25
229
+
230
+ this.upperThresholdUniform.value =
231
+ typeof params.upperThreshold === "number" ? params.upperThreshold : 1
232
+
233
+ this.directionUniform.value = params.direction === "vertical" ? 1 : 0
234
+
235
+ this.reverseUniform.value = params.reverse === true ? 1 : 0
236
+
237
+ if (params.mode === "hue") {
238
+ this.modeUniform.value = 1
239
+ } else if (params.mode === "saturation") {
240
+ this.modeUniform.value = 2
241
+ } else {
242
+ this.modeUniform.value = 0
243
+ }
244
+
245
+ const range = typeof params.range === "number" ? params.range : 0.3
246
+ this.passCount = Math.max(1, Math.round(range * 300))
247
+ }
248
+
249
+ override resize(width: number, height: number): void {
250
+ this.width = Math.max(1, width)
251
+ this.height = Math.max(1, height)
252
+ this.widthUniform.value = this.width
253
+ this.heightUniform.value = this.height
254
+ this.sortRtA.setSize(this.width, this.height)
255
+ this.sortRtB.setSize(this.width, this.height)
256
+ }
257
+
258
+ override dispose(): void {
259
+ this.sortRtA.dispose()
260
+ this.sortRtB.dispose()
261
+ this.sortMaterial.dispose()
262
+ super.dispose()
263
+ }
264
+
265
+ protected override buildEffectNode(): Node {
266
+ if (!this.blitInputNode) {
267
+ return this.inputNode
268
+ }
269
+
270
+ return vec4(
271
+ this.blitInputNode.r,
272
+ this.blitInputNode.g,
273
+ this.blitInputNode.b,
274
+ float(1),
275
+ )
276
+ }
277
+ }