@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.
- package/.biome/plugins/README.md +21 -0
- package/.biome/plugins/no-anchor-element.grit +12 -0
- package/.biome/plugins/no-relative-parent-imports.grit +10 -0
- package/.biome/plugins/no-unnecessary-forwardref.grit +9 -0
- package/.changeset/README.md +17 -0
- package/.changeset/config.json +11 -0
- package/.editorconfig +40 -0
- package/.env.example +81 -0
- package/.gitattributes +19 -0
- package/.github/workflows/canary.yml +80 -0
- package/.github/workflows/ci.yml +37 -0
- package/.github/workflows/release.yml +56 -0
- package/.tldrignore +84 -0
- package/.vscode/extensions.json +20 -0
- package/.vscode/settings.json +105 -0
- package/README.md +119 -0
- package/biome.json +249 -0
- package/bun.lock +1224 -0
- package/next.config.ts +131 -0
- package/package.json +73 -0
- package/packages/shader-lab-react/CHANGELOG.md +9 -0
- package/packages/shader-lab-react/README.md +119 -0
- package/packages/shader-lab-react/assets/patterns/bars/1.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/2.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/3.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/4.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/5.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/bars/6.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/candles/1.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/candles/2.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/candles/3.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/candles/4.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/1.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/2.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/3.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/4.svg +4 -0
- package/packages/shader-lab-react/assets/patterns/shapes/5.svg +3 -0
- package/packages/shader-lab-react/assets/patterns/shapes/6.svg +4 -0
- package/packages/shader-lab-react/assets/textures/blue-noise.png +0 -0
- package/packages/shader-lab-react/package.json +36 -0
- package/packages/shader-lab-react/scripts/fix-esm-specifiers.mjs +57 -0
- package/packages/shader-lab-react/scripts/prepare-dist.mjs +4 -0
- package/packages/shader-lab-react/src/ambient/three-tsl.d.ts +146 -0
- package/packages/shader-lab-react/src/ambient/three-webgpu.d.ts +51 -0
- package/packages/shader-lab-react/src/easings.ts +4 -0
- package/packages/shader-lab-react/src/index.ts +35 -0
- package/packages/shader-lab-react/src/lib/editor/custom-shader/shared.ts +2 -0
- package/packages/shader-lab-react/src/renderer/ascii-atlas.ts +83 -0
- package/packages/shader-lab-react/src/renderer/ascii-pass.ts +416 -0
- package/packages/shader-lab-react/src/renderer/asset-url.ts +3 -0
- package/packages/shader-lab-react/src/renderer/blend-modes.ts +229 -0
- package/packages/shader-lab-react/src/renderer/contracts.ts +54 -0
- package/packages/shader-lab-react/src/renderer/create-webgpu-renderer.ts +48 -0
- package/packages/shader-lab-react/src/renderer/crt-pass.ts +1040 -0
- package/packages/shader-lab-react/src/renderer/custom-shader-pass.ts +108 -0
- package/packages/shader-lab-react/src/renderer/custom-shader-runtime.ts +309 -0
- package/packages/shader-lab-react/src/renderer/dither-textures.ts +99 -0
- package/packages/shader-lab-react/src/renderer/dithering-pass.ts +322 -0
- package/packages/shader-lab-react/src/renderer/gradient-pass.ts +521 -0
- package/packages/shader-lab-react/src/renderer/halftone-pass.ts +932 -0
- package/packages/shader-lab-react/src/renderer/ink-pass.ts +802 -0
- package/packages/shader-lab-react/src/renderer/live-pass.ts +194 -0
- package/packages/shader-lab-react/src/renderer/media-pass.ts +187 -0
- package/packages/shader-lab-react/src/renderer/media-texture.ts +66 -0
- package/packages/shader-lab-react/src/renderer/particle-grid-pass.ts +389 -0
- package/packages/shader-lab-react/src/renderer/pass-node.ts +209 -0
- package/packages/shader-lab-react/src/renderer/pattern-atlas.ts +133 -0
- package/packages/shader-lab-react/src/renderer/pattern-pass.ts +552 -0
- package/packages/shader-lab-react/src/renderer/pipeline-manager.ts +369 -0
- package/packages/shader-lab-react/src/renderer/pixel-sorting-pass.ts +277 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/color/tonemapping.ts +87 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/cosine-palette.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/common.ts +31 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +36 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +36 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/fbm.ts +13 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +96 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/ridge-noise.ts +24 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +79 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +89 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/turbulence.ts +56 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/value-noise-3d.ts +32 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +60 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +15 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/bloom.ts +11 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +24 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +11 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/atan2.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-conj.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-cos.ts +10 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-div.ts +11 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-log.ts +7 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mobius.ts +12 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-mul.ts +9 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-pow.ts +16 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sin.ts +10 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-sqrt.ts +18 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-tan.ts +12 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/complex-to-polar.ts +10 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/hyperbolic.ts +20 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/index.ts +48 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/rotate.ts +15 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +15 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-box-2d.ts +6 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-diamond.ts +6 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-rhombus.ts +27 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/sd-sphere.ts +6 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smax.ts +7 -0
- package/packages/shader-lab-react/src/renderer/shaders/tsl/utils/smin.ts +7 -0
- package/packages/shader-lab-react/src/renderer/text-pass.ts +176 -0
- package/packages/shader-lab-react/src/runtime-clock.ts +42 -0
- package/packages/shader-lab-react/src/runtime-frame.ts +29 -0
- package/packages/shader-lab-react/src/shader-lab-composition.tsx +163 -0
- package/packages/shader-lab-react/src/timeline.ts +283 -0
- package/packages/shader-lab-react/src/types/editor.ts +5 -0
- package/packages/shader-lab-react/src/types.ts +141 -0
- package/packages/shader-lab-react/tsconfig.build.json +8 -0
- package/packages/shader-lab-react/tsconfig.json +21 -0
- package/postcss.config.mjs +5 -0
- package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf-atlas.png +0 -0
- package/public/assets/fonts/msdf/geist-mono/GeistMono-Regular-msdf.json +1412 -0
- package/public/assets/patterns/bars/1.svg +3 -0
- package/public/assets/patterns/bars/2.svg +3 -0
- package/public/assets/patterns/bars/3.svg +3 -0
- package/public/assets/patterns/bars/4.svg +3 -0
- package/public/assets/patterns/bars/5.svg +3 -0
- package/public/assets/patterns/bars/6.svg +3 -0
- package/public/assets/patterns/candles/1.svg +3 -0
- package/public/assets/patterns/candles/2.svg +3 -0
- package/public/assets/patterns/candles/3.svg +3 -0
- package/public/assets/patterns/candles/4.svg +3 -0
- package/public/assets/patterns/shapes/1.svg +3 -0
- package/public/assets/patterns/shapes/2.svg +3 -0
- package/public/assets/patterns/shapes/3.svg +3 -0
- package/public/assets/patterns/shapes/4.svg +4 -0
- package/public/assets/patterns/shapes/5.svg +3 -0
- package/public/assets/patterns/shapes/6.svg +4 -0
- package/public/fonts/geist/Geist-Mono.woff2 +0 -0
- package/public/textures/blue-noise.png +0 -0
- package/public/textures/crt-mask.png +0 -0
- package/src/app/design/page.tsx +398 -0
- package/src/app/favicon.ico +0 -0
- package/src/app/globals.css +280 -0
- package/src/app/layout.tsx +89 -0
- package/src/app/page.tsx +20 -0
- package/src/app/robots.ts +13 -0
- package/src/app/sitemap.ts +13 -0
- package/src/components/editor/editor-canvas-viewport.tsx +116 -0
- package/src/components/editor/editor-export-dialog.tsx +1177 -0
- package/src/components/editor/editor-timeline-overlay.tsx +983 -0
- package/src/components/editor/editor-topbar.tsx +287 -0
- package/src/components/editor/layer-sidebar.tsx +738 -0
- package/src/components/editor/properties-sidebar-content.tsx +574 -0
- package/src/components/editor/properties-sidebar-fields.tsx +389 -0
- package/src/components/editor/properties-sidebar-utils.ts +178 -0
- package/src/components/editor/properties-sidebar.tsx +421 -0
- package/src/components/ui/button/index.tsx +57 -0
- package/src/components/ui/color-picker/index.tsx +358 -0
- package/src/components/ui/glass-panel/index.tsx +45 -0
- package/src/components/ui/icon-button/index.tsx +46 -0
- package/src/components/ui/select/index.tsx +136 -0
- package/src/components/ui/slider/index.tsx +192 -0
- package/src/components/ui/toggle/index.tsx +34 -0
- package/src/components/ui/typography/index.tsx +61 -0
- package/src/components/ui/xy-pad/index.tsx +160 -0
- package/src/features/editor/components/editor-export-dialog.module.css +271 -0
- package/src/hooks/use-editor-renderer.ts +182 -0
- package/src/lib/app.ts +6 -0
- package/src/lib/cn.ts +7 -0
- package/src/lib/easings.ts +240 -0
- package/src/lib/editor/config/layer-registry.ts +2434 -0
- package/src/lib/editor/custom-shader/shared.ts +28 -0
- package/src/lib/editor/export.ts +420 -0
- package/src/lib/editor/history.ts +71 -0
- package/src/lib/editor/layers.ts +76 -0
- package/src/lib/editor/parameter-schema.ts +75 -0
- package/src/lib/editor/project-file.ts +145 -0
- package/src/lib/editor/shader-export-snippet.ts +37 -0
- package/src/lib/editor/shader-export.ts +315 -0
- package/src/lib/editor/timeline/evaluate.ts +252 -0
- package/src/lib/editor/view-transform.ts +58 -0
- package/src/lib/fonts.ts +28 -0
- package/src/renderer/ascii-atlas.ts +83 -0
- package/src/renderer/ascii-pass.ts +416 -0
- package/src/renderer/blend-modes.ts +229 -0
- package/src/renderer/contracts.ts +161 -0
- package/src/renderer/create-webgpu-renderer.ts +48 -0
- package/src/renderer/crt-pass.ts +1040 -0
- package/src/renderer/custom-shader-pass.ts +117 -0
- package/src/renderer/custom-shader-runtime.ts +309 -0
- package/src/renderer/dither-textures.ts +99 -0
- package/src/renderer/dithering-pass.ts +322 -0
- package/src/renderer/gradient-pass.ts +520 -0
- package/src/renderer/halftone-pass.ts +932 -0
- package/src/renderer/ink-pass.ts +683 -0
- package/src/renderer/live-pass.ts +194 -0
- package/src/renderer/media-pass.ts +187 -0
- package/src/renderer/media-texture.ts +66 -0
- package/src/renderer/particle-grid-pass.ts +389 -0
- package/src/renderer/pass-node-factory.ts +33 -0
- package/src/renderer/pass-node.ts +209 -0
- package/src/renderer/pattern-atlas.ts +97 -0
- package/src/renderer/pattern-pass.ts +552 -0
- package/src/renderer/pipeline-manager.ts +343 -0
- package/src/renderer/pixel-sorting-pass.ts +277 -0
- package/src/renderer/project-clock.ts +57 -0
- package/src/renderer/shaders/tsl/color/tonemapping.ts +86 -0
- package/src/renderer/shaders/tsl/cosine-palette.ts +8 -0
- package/src/renderer/shaders/tsl/noise/common.ts +30 -0
- package/src/renderer/shaders/tsl/noise/curl-noise-3d.ts +35 -0
- package/src/renderer/shaders/tsl/noise/curl-noise-4d.ts +35 -0
- package/src/renderer/shaders/tsl/noise/fbm.ts +12 -0
- package/src/renderer/shaders/tsl/noise/perlin-noise-3d.ts +97 -0
- package/src/renderer/shaders/tsl/noise/ridge-noise.ts +23 -0
- package/src/renderer/shaders/tsl/noise/simplex-noise-3d.ts +78 -0
- package/src/renderer/shaders/tsl/noise/simplex-noise-4d.ts +88 -0
- package/src/renderer/shaders/tsl/noise/turbulence.ts +55 -0
- package/src/renderer/shaders/tsl/noise/value-noise-3d.ts +31 -0
- package/src/renderer/shaders/tsl/noise/voronoi-noise-3d.ts +59 -0
- package/src/renderer/shaders/tsl/patterns/bloom-edge-pattern.ts +14 -0
- package/src/renderer/shaders/tsl/patterns/bloom.ts +10 -0
- package/src/renderer/shaders/tsl/patterns/canvas-weave-pattern.ts +23 -0
- package/src/renderer/shaders/tsl/patterns/grain-texture-pattern.ts +8 -0
- package/src/renderer/shaders/tsl/patterns/repeating-pattern.ts +10 -0
- package/src/renderer/shaders/tsl/utils/atan2.ts +8 -0
- package/src/renderer/shaders/tsl/utils/complex-conj.ts +8 -0
- package/src/renderer/shaders/tsl/utils/complex-cos.ts +9 -0
- package/src/renderer/shaders/tsl/utils/complex-div.ts +10 -0
- package/src/renderer/shaders/tsl/utils/complex-log.ts +6 -0
- package/src/renderer/shaders/tsl/utils/complex-mobius.ts +11 -0
- package/src/renderer/shaders/tsl/utils/complex-mul.ts +8 -0
- package/src/renderer/shaders/tsl/utils/complex-pow.ts +15 -0
- package/src/renderer/shaders/tsl/utils/complex-sin.ts +9 -0
- package/src/renderer/shaders/tsl/utils/complex-sqrt.ts +17 -0
- package/src/renderer/shaders/tsl/utils/complex-tan.ts +11 -0
- package/src/renderer/shaders/tsl/utils/complex-to-polar.ts +9 -0
- package/src/renderer/shaders/tsl/utils/hyperbolic.ts +19 -0
- package/src/renderer/shaders/tsl/utils/index.ts +47 -0
- package/src/renderer/shaders/tsl/utils/rotate.ts +14 -0
- package/src/renderer/shaders/tsl/utils/screen-aspect-uv.ts +14 -0
- package/src/renderer/shaders/tsl/utils/sd-box-2d.ts +5 -0
- package/src/renderer/shaders/tsl/utils/sd-diamond.ts +5 -0
- package/src/renderer/shaders/tsl/utils/sd-rhombus.ts +26 -0
- package/src/renderer/shaders/tsl/utils/sd-sphere.ts +5 -0
- package/src/renderer/shaders/tsl/utils/smax.ts +7 -0
- package/src/renderer/shaders/tsl/utils/smin.ts +6 -0
- package/src/renderer/text-pass.ts +176 -0
- package/src/store/asset-store.ts +193 -0
- package/src/store/editor-store.ts +223 -0
- package/src/store/history-store.ts +172 -0
- package/src/store/index.ts +31 -0
- package/src/store/layer-store.ts +675 -0
- package/src/store/timeline-store.ts +572 -0
- package/src/types/assets.d.ts +6 -0
- package/src/types/css.d.ts +21 -0
- package/src/types/editor.ts +357 -0
- package/src/types/react.d.ts +15 -0
- package/src/types/three-tsl.d.ts +146 -0
- package/src/types/three-webgpu.d.ts +51 -0
- package/tsconfig.json +49 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { clamp, float, type TSLNode, uniform, vec3, vec4 } from "three/tsl"
|
|
2
|
+
import { CUSTOM_SHADER_ENTRY_EXPORT } from "../lib/editor/custom-shader/shared"
|
|
3
|
+
import { compileCustomShaderModule } from "./custom-shader-runtime"
|
|
4
|
+
import { PassNode } from "./pass-node"
|
|
5
|
+
import type { LayerParameterValues } from "../types/editor"
|
|
6
|
+
|
|
7
|
+
type Node = TSLNode
|
|
8
|
+
|
|
9
|
+
export class CustomShaderPass extends PassNode {
|
|
10
|
+
private readonly onRuntimeError: ((message: string | null) => void) | undefined
|
|
11
|
+
private compiledSketch: (() => Node) | null = null
|
|
12
|
+
private compileRequestId = 0
|
|
13
|
+
private lastCompileSignature = ""
|
|
14
|
+
private readonly timeUniform: Node
|
|
15
|
+
|
|
16
|
+
constructor(layerId: string, onRuntimeError?: (message: string | null) => void) {
|
|
17
|
+
super(layerId)
|
|
18
|
+
this.onRuntimeError = onRuntimeError
|
|
19
|
+
this.timeUniform = uniform(0)
|
|
20
|
+
this.rebuildEffectNode()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
override needsContinuousRender(): boolean {
|
|
24
|
+
return true
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
override updateParams(params: LayerParameterValues): void {
|
|
28
|
+
const sourceCode =
|
|
29
|
+
typeof params.sourceCode === "string" ? params.sourceCode : ""
|
|
30
|
+
const entryExport =
|
|
31
|
+
typeof params.entryExport === "string" && params.entryExport.trim()
|
|
32
|
+
? params.entryExport.trim()
|
|
33
|
+
: CUSTOM_SHADER_ENTRY_EXPORT
|
|
34
|
+
const sourceFileName =
|
|
35
|
+
typeof params.sourceFileName === "string" ? params.sourceFileName : ""
|
|
36
|
+
const sourceRevision =
|
|
37
|
+
typeof params.sourceRevision === "number" ? params.sourceRevision : 0
|
|
38
|
+
const compileSignature = [
|
|
39
|
+
entryExport,
|
|
40
|
+
sourceCode,
|
|
41
|
+
sourceFileName,
|
|
42
|
+
sourceRevision,
|
|
43
|
+
].join("\n")
|
|
44
|
+
|
|
45
|
+
if (compileSignature === this.lastCompileSignature) {
|
|
46
|
+
return
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.lastCompileSignature = compileSignature
|
|
50
|
+
this.compileRequestId += 1
|
|
51
|
+
const requestId = this.compileRequestId
|
|
52
|
+
|
|
53
|
+
void compileCustomShaderModule({
|
|
54
|
+
entryExport,
|
|
55
|
+
extraScope: {
|
|
56
|
+
time: this.timeUniform,
|
|
57
|
+
},
|
|
58
|
+
fileName: sourceFileName || "custom-shader.ts",
|
|
59
|
+
sourceCode,
|
|
60
|
+
})
|
|
61
|
+
.then((compiled) => {
|
|
62
|
+
if (requestId !== this.compileRequestId) {
|
|
63
|
+
return
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
this.compiledSketch = compiled.buildNode
|
|
67
|
+
this.onRuntimeError?.(null)
|
|
68
|
+
this.rebuildEffectNode()
|
|
69
|
+
})
|
|
70
|
+
.catch((error) => {
|
|
71
|
+
if (requestId !== this.compileRequestId) {
|
|
72
|
+
return
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
this.compiledSketch = null
|
|
76
|
+
this.onRuntimeError?.(
|
|
77
|
+
error instanceof Error ? error.message : "Custom shader compilation failed.",
|
|
78
|
+
)
|
|
79
|
+
this.rebuildEffectNode()
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
protected override beforeRender(time: number): void {
|
|
84
|
+
this.timeUniform.value = time
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
protected override buildEffectNode(): Node {
|
|
88
|
+
if (!this.compiledSketch) {
|
|
89
|
+
return vec4(vec3(float(0), float(0), float(0)), float(1))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
return vec4(
|
|
94
|
+
clamp(
|
|
95
|
+
this.compiledSketch(),
|
|
96
|
+
vec3(float(0), float(0), float(0)),
|
|
97
|
+
vec3(float(1), float(1), float(1))
|
|
98
|
+
),
|
|
99
|
+
float(1)
|
|
100
|
+
)
|
|
101
|
+
} catch (error) {
|
|
102
|
+
this.onRuntimeError?.(
|
|
103
|
+
error instanceof Error ? error.message : "Custom shader execution failed.",
|
|
104
|
+
)
|
|
105
|
+
return vec4(vec3(float(0), float(0), float(0)), float(1))
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import type { TSLNode } from "three/tsl"
|
|
2
|
+
import * as tsl from "three/tsl"
|
|
3
|
+
import * as shaderUtils from "./shaders/tsl/utils/index"
|
|
4
|
+
|
|
5
|
+
type CompiledShaderModule = {
|
|
6
|
+
buildNode: () => TSLNode
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const PRELUDE = {
|
|
10
|
+
...tsl,
|
|
11
|
+
...shaderUtils,
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const TRANSPILED_CACHE = new Map<string, string>()
|
|
15
|
+
let typescriptPromise: Promise<typeof import("typescript")> | null = null
|
|
16
|
+
|
|
17
|
+
function isNodeLike(value: unknown): value is TSLNode {
|
|
18
|
+
return Boolean(
|
|
19
|
+
value &&
|
|
20
|
+
typeof value === "object" &&
|
|
21
|
+
"mul" in value &&
|
|
22
|
+
"add" in value &&
|
|
23
|
+
"sub" in value
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function formatDiagnostics(
|
|
28
|
+
compiler: typeof import("typescript"),
|
|
29
|
+
diagnostics: readonly import("typescript").Diagnostic[] | undefined
|
|
30
|
+
): string | null {
|
|
31
|
+
if (!(diagnostics && diagnostics.length > 0)) {
|
|
32
|
+
return null
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return diagnostics
|
|
36
|
+
.map((diagnostic) =>
|
|
37
|
+
compiler.flattenDiagnosticMessageText(diagnostic.messageText, "\n")
|
|
38
|
+
)
|
|
39
|
+
.join("\n\n")
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createSourceFile(
|
|
43
|
+
compiler: typeof import("typescript"),
|
|
44
|
+
fileName: string,
|
|
45
|
+
sourceCode: string
|
|
46
|
+
) {
|
|
47
|
+
return compiler.createSourceFile(
|
|
48
|
+
fileName,
|
|
49
|
+
sourceCode,
|
|
50
|
+
compiler.ScriptTarget.ES2020,
|
|
51
|
+
true,
|
|
52
|
+
getScriptKind(compiler, fileName)
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function getSourceFileDiagnostics(sourceFile: import("typescript").SourceFile) {
|
|
57
|
+
return (
|
|
58
|
+
sourceFile as import("typescript").SourceFile & {
|
|
59
|
+
parseDiagnostics?: readonly import("typescript").Diagnostic[]
|
|
60
|
+
}
|
|
61
|
+
).parseDiagnostics
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isDirectiveStatement(
|
|
65
|
+
compiler: typeof import("typescript"),
|
|
66
|
+
statement: import("typescript").Statement
|
|
67
|
+
) {
|
|
68
|
+
return (
|
|
69
|
+
compiler.isExpressionStatement(statement) &&
|
|
70
|
+
compiler.isStringLiteral(statement.expression) &&
|
|
71
|
+
(statement.expression.text === "use client" ||
|
|
72
|
+
statement.expression.text === "use server")
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function statementContainsJsx(
|
|
77
|
+
compiler: typeof import("typescript"),
|
|
78
|
+
statement: import("typescript").Statement
|
|
79
|
+
): boolean {
|
|
80
|
+
let containsJsx = false
|
|
81
|
+
|
|
82
|
+
const visit = (node: import("typescript").Node) => {
|
|
83
|
+
if (
|
|
84
|
+
compiler.isJsxElement(node) ||
|
|
85
|
+
compiler.isJsxSelfClosingElement(node) ||
|
|
86
|
+
compiler.isJsxFragment(node)
|
|
87
|
+
) {
|
|
88
|
+
containsJsx = true
|
|
89
|
+
return
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
compiler.forEachChild(node, visit)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
compiler.forEachChild(statement, visit)
|
|
96
|
+
|
|
97
|
+
return containsJsx
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async function sanitizeCustomShaderSource({
|
|
101
|
+
fileName,
|
|
102
|
+
sourceCode,
|
|
103
|
+
}: {
|
|
104
|
+
fileName: string
|
|
105
|
+
sourceCode: string
|
|
106
|
+
}) {
|
|
107
|
+
const compiler = await getTypeScript()
|
|
108
|
+
const sourceFile = createSourceFile(compiler, fileName, sourceCode)
|
|
109
|
+
const diagnosticsMessage = formatDiagnostics(
|
|
110
|
+
compiler,
|
|
111
|
+
getSourceFileDiagnostics(sourceFile)
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
if (diagnosticsMessage) {
|
|
115
|
+
throw new Error(diagnosticsMessage)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const statements = sourceFile.statements.filter((statement) => {
|
|
119
|
+
if (isDirectiveStatement(compiler, statement)) {
|
|
120
|
+
return false
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (
|
|
124
|
+
compiler.isImportDeclaration(statement) ||
|
|
125
|
+
compiler.isImportEqualsDeclaration(statement) ||
|
|
126
|
+
compiler.isExportAssignment(statement)
|
|
127
|
+
) {
|
|
128
|
+
return false
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (
|
|
132
|
+
compiler.isExportDeclaration(statement) &&
|
|
133
|
+
statement.moduleSpecifier !== undefined
|
|
134
|
+
) {
|
|
135
|
+
return false
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (statementContainsJsx(compiler, statement)) {
|
|
139
|
+
return false
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return true
|
|
143
|
+
})
|
|
144
|
+
|
|
145
|
+
const sanitizedFile = compiler.factory.updateSourceFile(
|
|
146
|
+
sourceFile,
|
|
147
|
+
statements
|
|
148
|
+
)
|
|
149
|
+
const printer = compiler.createPrinter({
|
|
150
|
+
newLine: compiler.NewLineKind.LineFeed,
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
return `${printer.printFile(sanitizedFile).trim()}\n`
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function assertNoExplicitImports(sourceCode: string) {
|
|
157
|
+
if (/^\s*import[\s{*]/m.test(sourceCode)) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
"Custom shader imports are resolved through the injected prelude. Remove the imports or paste the whole sketch file and let the custom shader layer strip them."
|
|
160
|
+
)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function getTypeScript() {
|
|
165
|
+
if (!typescriptPromise) {
|
|
166
|
+
typescriptPromise = import("typescript")
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
return typescriptPromise
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function getScriptKind(
|
|
173
|
+
compiler: typeof import("typescript"),
|
|
174
|
+
fileName: string
|
|
175
|
+
): import("typescript").ScriptKind {
|
|
176
|
+
return fileName.endsWith(".tsx")
|
|
177
|
+
? compiler.ScriptKind.TSX
|
|
178
|
+
: compiler.ScriptKind.TS
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export async function formatCustomShaderSource({
|
|
182
|
+
fileName,
|
|
183
|
+
sourceCode,
|
|
184
|
+
}: {
|
|
185
|
+
fileName?: string
|
|
186
|
+
sourceCode: string
|
|
187
|
+
}): Promise<string> {
|
|
188
|
+
const compiler = await getTypeScript()
|
|
189
|
+
const resolvedFileName = fileName ?? "custom-shader.ts"
|
|
190
|
+
const sourceFile = createSourceFile(compiler, resolvedFileName, sourceCode)
|
|
191
|
+
const diagnosticsMessage = formatDiagnostics(
|
|
192
|
+
compiler,
|
|
193
|
+
getSourceFileDiagnostics(sourceFile)
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
if (diagnosticsMessage) {
|
|
197
|
+
throw new Error(diagnosticsMessage)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const printer = compiler.createPrinter({
|
|
201
|
+
newLine: compiler.NewLineKind.LineFeed,
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
return `${printer.printFile(sourceFile).trim()}\n`
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function compileCustomShaderModule({
|
|
208
|
+
entryExport,
|
|
209
|
+
extraScope,
|
|
210
|
+
fileName,
|
|
211
|
+
force = false,
|
|
212
|
+
sourceCode,
|
|
213
|
+
}: {
|
|
214
|
+
entryExport: string
|
|
215
|
+
extraScope?: Record<string, unknown>
|
|
216
|
+
fileName?: string
|
|
217
|
+
force?: boolean
|
|
218
|
+
sourceCode: string
|
|
219
|
+
}): Promise<CompiledShaderModule> {
|
|
220
|
+
const resolvedFileName = fileName ?? "custom-shader.ts"
|
|
221
|
+
const sanitizedSourceCode = await sanitizeCustomShaderSource({
|
|
222
|
+
fileName: resolvedFileName,
|
|
223
|
+
sourceCode,
|
|
224
|
+
})
|
|
225
|
+
assertNoExplicitImports(sanitizedSourceCode)
|
|
226
|
+
|
|
227
|
+
const cacheKey = `${entryExport}\n${resolvedFileName}\n${sanitizedSourceCode}`
|
|
228
|
+
let outputText = !force ? (TRANSPILED_CACHE.get(cacheKey) ?? null) : null
|
|
229
|
+
|
|
230
|
+
const compiler = await getTypeScript()
|
|
231
|
+
if (!outputText) {
|
|
232
|
+
const transpiled = compiler.transpileModule(sanitizedSourceCode, {
|
|
233
|
+
compilerOptions: {
|
|
234
|
+
esModuleInterop: true,
|
|
235
|
+
jsx: compiler.JsxEmit.ReactJSX,
|
|
236
|
+
module: compiler.ModuleKind.CommonJS,
|
|
237
|
+
target: compiler.ScriptTarget.ES2020,
|
|
238
|
+
},
|
|
239
|
+
fileName: resolvedFileName,
|
|
240
|
+
reportDiagnostics: true,
|
|
241
|
+
})
|
|
242
|
+
const diagnosticsMessage = formatDiagnostics(
|
|
243
|
+
compiler,
|
|
244
|
+
transpiled.diagnostics
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
if (diagnosticsMessage) {
|
|
248
|
+
throw new Error(diagnosticsMessage)
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
outputText = transpiled.outputText
|
|
252
|
+
TRANSPILED_CACHE.set(cacheKey, outputText)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const runtimeScope = {
|
|
256
|
+
...PRELUDE,
|
|
257
|
+
...(extraScope ?? {}),
|
|
258
|
+
}
|
|
259
|
+
const scopeNames = Object.keys(runtimeScope)
|
|
260
|
+
const scopeValues = scopeNames.map(
|
|
261
|
+
(key) => runtimeScope[key as keyof typeof runtimeScope]
|
|
262
|
+
)
|
|
263
|
+
const module = { exports: {} as Record<string, unknown> }
|
|
264
|
+
const exportsObject = module.exports
|
|
265
|
+
const evaluator = new Function(
|
|
266
|
+
"exports",
|
|
267
|
+
"module",
|
|
268
|
+
...scopeNames,
|
|
269
|
+
`${outputText}\nreturn module.exports;`
|
|
270
|
+
)
|
|
271
|
+
|
|
272
|
+
let resolvedExports: Record<string, unknown>
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
resolvedExports = evaluator(
|
|
276
|
+
exportsObject,
|
|
277
|
+
module,
|
|
278
|
+
...scopeValues
|
|
279
|
+
) as Record<string, unknown>
|
|
280
|
+
} catch (error) {
|
|
281
|
+
throw new Error(
|
|
282
|
+
error instanceof Error
|
|
283
|
+
? error.message
|
|
284
|
+
: "Custom shader evaluation failed."
|
|
285
|
+
)
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const exported = resolvedExports[entryExport] ?? module.exports[entryExport]
|
|
289
|
+
|
|
290
|
+
if (typeof exported !== "function") {
|
|
291
|
+
throw new Error(
|
|
292
|
+
`Expected a named export \`${entryExport}\` that resolves to a TSL sketch function.`
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return {
|
|
297
|
+
buildNode: () => {
|
|
298
|
+
const result = (exported as () => unknown)()
|
|
299
|
+
|
|
300
|
+
if (!isNodeLike(result)) {
|
|
301
|
+
throw new Error(
|
|
302
|
+
`The export \`${entryExport}\` did not return a valid TSL node.`
|
|
303
|
+
)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return result
|
|
307
|
+
},
|
|
308
|
+
}
|
|
309
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import * as THREE from "three/webgpu"
|
|
2
|
+
|
|
3
|
+
const BAYER_2X2 = [0, 2, 3, 1] as const
|
|
4
|
+
|
|
5
|
+
const BAYER_4X4 = [
|
|
6
|
+
0, 8, 2, 10,
|
|
7
|
+
12, 4, 14, 6,
|
|
8
|
+
3, 11, 1, 9,
|
|
9
|
+
15, 7, 13, 5,
|
|
10
|
+
] as const
|
|
11
|
+
|
|
12
|
+
const BAYER_8X8 = [
|
|
13
|
+
0, 32, 8, 40, 2, 34, 10, 42,
|
|
14
|
+
48, 16, 56, 24, 50, 18, 58, 26,
|
|
15
|
+
12, 44, 4, 36, 14, 46, 6, 38,
|
|
16
|
+
60, 28, 52, 20, 62, 30, 54, 22,
|
|
17
|
+
3, 35, 11, 43, 1, 33, 9, 41,
|
|
18
|
+
51, 19, 59, 27, 49, 17, 57, 25,
|
|
19
|
+
15, 47, 7, 39, 13, 45, 5, 37,
|
|
20
|
+
63, 31, 55, 23, 61, 29, 53, 21,
|
|
21
|
+
] as const
|
|
22
|
+
|
|
23
|
+
function buildBayerTexture(values: readonly number[], size: number): THREE.DataTexture {
|
|
24
|
+
const normalizer = size * size
|
|
25
|
+
const data = new Uint8Array(size * size * 4)
|
|
26
|
+
|
|
27
|
+
for (let index = 0; index < size * size; index += 1) {
|
|
28
|
+
const channel = Math.round(((values[index] ?? 0) / normalizer) * 255)
|
|
29
|
+
const offset = index * 4
|
|
30
|
+
data[offset] = channel
|
|
31
|
+
data[offset + 1] = channel
|
|
32
|
+
data[offset + 2] = channel
|
|
33
|
+
data[offset + 3] = 255
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const texture = new THREE.DataTexture(
|
|
37
|
+
data,
|
|
38
|
+
size,
|
|
39
|
+
size,
|
|
40
|
+
THREE.RGBAFormat,
|
|
41
|
+
THREE.UnsignedByteType,
|
|
42
|
+
)
|
|
43
|
+
texture.magFilter = THREE.NearestFilter
|
|
44
|
+
texture.minFilter = THREE.NearestFilter
|
|
45
|
+
texture.wrapS = THREE.RepeatWrapping
|
|
46
|
+
texture.wrapT = THREE.RepeatWrapping
|
|
47
|
+
texture.needsUpdate = true
|
|
48
|
+
|
|
49
|
+
return texture
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function buildBlueNoiseTexture(size = 64): THREE.DataTexture {
|
|
53
|
+
const data = new Uint8Array(size * size * 4)
|
|
54
|
+
const fract = (value: number) => value - Math.floor(value)
|
|
55
|
+
|
|
56
|
+
for (let y = 0; y < size; y += 1) {
|
|
57
|
+
for (let x = 0; x < size; x += 1) {
|
|
58
|
+
const noise = Math.round(
|
|
59
|
+
fract(52.9829189 * fract(0.06711056 * x + 0.00583715 * y)) * 255,
|
|
60
|
+
)
|
|
61
|
+
const offset = (y * size + x) * 4
|
|
62
|
+
data[offset] = noise
|
|
63
|
+
data[offset + 1] = noise
|
|
64
|
+
data[offset + 2] = noise
|
|
65
|
+
data[offset + 3] = 255
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const texture = new THREE.DataTexture(
|
|
70
|
+
data,
|
|
71
|
+
size,
|
|
72
|
+
size,
|
|
73
|
+
THREE.RGBAFormat,
|
|
74
|
+
THREE.UnsignedByteType,
|
|
75
|
+
)
|
|
76
|
+
texture.magFilter = THREE.NearestFilter
|
|
77
|
+
texture.minFilter = THREE.NearestFilter
|
|
78
|
+
texture.wrapS = THREE.RepeatWrapping
|
|
79
|
+
texture.wrapT = THREE.RepeatWrapping
|
|
80
|
+
texture.needsUpdate = true
|
|
81
|
+
|
|
82
|
+
return texture
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface DitherTextures {
|
|
86
|
+
bayer2: THREE.DataTexture
|
|
87
|
+
bayer4: THREE.DataTexture
|
|
88
|
+
bayer8: THREE.DataTexture
|
|
89
|
+
noise: THREE.DataTexture
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function buildDitherTextures(): DitherTextures {
|
|
93
|
+
return {
|
|
94
|
+
bayer2: buildBayerTexture(BAYER_2X2, 2),
|
|
95
|
+
bayer4: buildBayerTexture(BAYER_4X4, 4),
|
|
96
|
+
bayer8: buildBayerTexture(BAYER_8X8, 8),
|
|
97
|
+
noise: buildBlueNoiseTexture(),
|
|
98
|
+
}
|
|
99
|
+
}
|