@motion-core/motion-gpu 0.4.1 → 0.5.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/README.md +99 -0
- package/dist/advanced.d.ts +1 -0
- package/dist/advanced.d.ts.map +1 -0
- package/dist/advanced.js +14 -6
- package/dist/core/advanced.d.ts +1 -0
- package/dist/core/advanced.d.ts.map +1 -0
- package/dist/core/advanced.js +14 -5
- package/dist/core/compute-shader.d.ts +87 -0
- package/dist/core/compute-shader.d.ts.map +1 -0
- package/dist/core/compute-shader.js +205 -0
- package/dist/core/compute-shader.js.map +1 -0
- package/dist/core/current-value.d.ts +1 -0
- package/dist/core/current-value.d.ts.map +1 -0
- package/dist/core/current-value.js +35 -34
- package/dist/core/current-value.js.map +1 -0
- package/dist/core/error-diagnostics.d.ts +1 -0
- package/dist/core/error-diagnostics.d.ts.map +1 -0
- package/dist/core/error-diagnostics.js +70 -137
- package/dist/core/error-diagnostics.js.map +1 -0
- package/dist/core/error-report.d.ts +2 -1
- package/dist/core/error-report.d.ts.map +1 -0
- package/dist/core/error-report.js +247 -233
- package/dist/core/error-report.js.map +1 -0
- package/dist/core/frame-registry.d.ts +1 -0
- package/dist/core/frame-registry.d.ts.map +1 -0
- package/dist/core/frame-registry.js +546 -662
- package/dist/core/frame-registry.js.map +1 -0
- package/dist/core/index.d.ts +6 -2
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +13 -12
- package/dist/core/material-preprocess.d.ts +1 -0
- package/dist/core/material-preprocess.d.ts.map +1 -0
- package/dist/core/material-preprocess.js +131 -152
- package/dist/core/material-preprocess.js.map +1 -0
- package/dist/core/material.d.ts +23 -6
- package/dist/core/material.d.ts.map +1 -0
- package/dist/core/material.js +290 -317
- package/dist/core/material.js.map +1 -0
- package/dist/core/recompile-policy.d.ts +1 -0
- package/dist/core/recompile-policy.d.ts.map +1 -0
- package/dist/core/recompile-policy.js +18 -13
- package/dist/core/recompile-policy.js.map +1 -0
- package/dist/core/render-graph.d.ts +8 -3
- package/dist/core/render-graph.d.ts.map +1 -0
- package/dist/core/render-graph.js +77 -68
- package/dist/core/render-graph.js.map +1 -0
- package/dist/core/render-targets.d.ts +1 -0
- package/dist/core/render-targets.d.ts.map +1 -0
- package/dist/core/render-targets.js +52 -53
- package/dist/core/render-targets.js.map +1 -0
- package/dist/core/renderer.d.ts +1 -0
- package/dist/core/renderer.d.ts.map +1 -0
- package/dist/core/renderer.js +1337 -1081
- package/dist/core/renderer.js.map +1 -0
- package/dist/core/runtime-loop.d.ts +3 -2
- package/dist/core/runtime-loop.d.ts.map +1 -0
- package/dist/core/runtime-loop.js +353 -362
- package/dist/core/runtime-loop.js.map +1 -0
- package/dist/core/scheduler-helpers.d.ts +1 -0
- package/dist/core/scheduler-helpers.d.ts.map +1 -0
- package/dist/core/scheduler-helpers.js +52 -51
- package/dist/core/scheduler-helpers.js.map +1 -0
- package/dist/core/shader.d.ts +10 -1
- package/dist/core/shader.d.ts.map +1 -0
- package/dist/core/shader.js +109 -115
- package/dist/core/shader.js.map +1 -0
- package/dist/core/storage-buffers.d.ts +37 -0
- package/dist/core/storage-buffers.d.ts.map +1 -0
- package/dist/core/storage-buffers.js +95 -0
- package/dist/core/storage-buffers.js.map +1 -0
- package/dist/core/texture-loader.d.ts +1 -0
- package/dist/core/texture-loader.d.ts.map +1 -0
- package/dist/core/texture-loader.js +209 -273
- package/dist/core/texture-loader.js.map +1 -0
- package/dist/core/textures.d.ts +13 -0
- package/dist/core/textures.d.ts.map +1 -0
- package/dist/core/textures.js +111 -116
- package/dist/core/textures.js.map +1 -0
- package/dist/core/types.d.ts +147 -4
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +0 -4
- package/dist/core/uniforms.d.ts +1 -0
- package/dist/core/uniforms.d.ts.map +1 -0
- package/dist/core/uniforms.js +170 -191
- package/dist/core/uniforms.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -6
- package/dist/passes/BlitPass.d.ts +1 -0
- package/dist/passes/BlitPass.d.ts.map +1 -0
- package/dist/passes/BlitPass.js +23 -18
- package/dist/passes/BlitPass.js.map +1 -0
- package/dist/passes/ComputePass.d.ts +83 -0
- package/dist/passes/ComputePass.d.ts.map +1 -0
- package/dist/passes/ComputePass.js +92 -0
- package/dist/passes/ComputePass.js.map +1 -0
- package/dist/passes/CopyPass.d.ts +1 -0
- package/dist/passes/CopyPass.d.ts.map +1 -0
- package/dist/passes/CopyPass.js +58 -52
- package/dist/passes/CopyPass.js.map +1 -0
- package/dist/passes/FullscreenPass.d.ts +1 -0
- package/dist/passes/FullscreenPass.d.ts.map +1 -0
- package/dist/passes/FullscreenPass.js +127 -130
- package/dist/passes/FullscreenPass.js.map +1 -0
- package/dist/passes/PingPongComputePass.d.ts +104 -0
- package/dist/passes/PingPongComputePass.d.ts.map +1 -0
- package/dist/passes/PingPongComputePass.js +132 -0
- package/dist/passes/PingPongComputePass.js.map +1 -0
- package/dist/passes/ShaderPass.d.ts +1 -0
- package/dist/passes/ShaderPass.d.ts.map +1 -0
- package/dist/passes/ShaderPass.js +41 -37
- package/dist/passes/ShaderPass.js.map +1 -0
- package/dist/passes/index.d.ts +3 -0
- package/dist/passes/index.d.ts.map +1 -0
- package/dist/passes/index.js +6 -3
- package/dist/react/FragCanvas.d.ts +3 -2
- package/dist/react/FragCanvas.d.ts.map +1 -0
- package/dist/react/FragCanvas.js +234 -211
- package/dist/react/FragCanvas.js.map +1 -0
- package/dist/react/MotionGPUErrorOverlay.d.ts +1 -0
- package/dist/react/MotionGPUErrorOverlay.d.ts.map +1 -0
- package/dist/react/MotionGPUErrorOverlay.js +200 -14
- package/dist/react/MotionGPUErrorOverlay.js.map +1 -0
- package/dist/react/Portal.d.ts +1 -0
- package/dist/react/Portal.d.ts.map +1 -0
- package/dist/react/Portal.js +18 -21
- package/dist/react/Portal.js.map +1 -0
- package/dist/react/advanced.d.ts +1 -0
- package/dist/react/advanced.d.ts.map +1 -0
- package/dist/react/advanced.js +14 -6
- package/dist/react/frame-context.d.ts +1 -0
- package/dist/react/frame-context.d.ts.map +1 -0
- package/dist/react/frame-context.js +88 -94
- package/dist/react/frame-context.js.map +1 -0
- package/dist/react/index.d.ts +6 -2
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +12 -9
- package/dist/react/motiongpu-context.d.ts +1 -0
- package/dist/react/motiongpu-context.d.ts.map +1 -0
- package/dist/react/motiongpu-context.js +18 -15
- package/dist/react/motiongpu-context.js.map +1 -0
- package/dist/react/use-motiongpu-user-context.d.ts +1 -0
- package/dist/react/use-motiongpu-user-context.d.ts.map +1 -0
- package/dist/react/use-motiongpu-user-context.js +83 -82
- package/dist/react/use-motiongpu-user-context.js.map +1 -0
- package/dist/react/use-texture.d.ts +1 -0
- package/dist/react/use-texture.d.ts.map +1 -0
- package/dist/react/use-texture.js +132 -152
- package/dist/react/use-texture.js.map +1 -0
- package/dist/svelte/FragCanvas.svelte +2 -2
- package/dist/svelte/FragCanvas.svelte.d.ts +3 -2
- package/dist/svelte/FragCanvas.svelte.d.ts.map +1 -0
- package/dist/svelte/MotionGPUErrorOverlay.svelte +137 -7
- package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts +1 -0
- package/dist/svelte/MotionGPUErrorOverlay.svelte.d.ts.map +1 -0
- package/dist/svelte/Portal.svelte.d.ts +1 -0
- package/dist/svelte/Portal.svelte.d.ts.map +1 -0
- package/dist/svelte/advanced.d.ts +1 -0
- package/dist/svelte/advanced.d.ts.map +1 -0
- package/dist/svelte/advanced.js +13 -6
- package/dist/svelte/frame-context.d.ts +1 -0
- package/dist/svelte/frame-context.d.ts.map +1 -0
- package/dist/svelte/frame-context.js +27 -27
- package/dist/svelte/frame-context.js.map +1 -0
- package/dist/svelte/index.d.ts +6 -2
- package/dist/svelte/index.d.ts.map +1 -0
- package/dist/svelte/index.js +12 -9
- package/dist/svelte/motiongpu-context.d.ts +1 -0
- package/dist/svelte/motiongpu-context.d.ts.map +1 -0
- package/dist/svelte/motiongpu-context.js +24 -21
- package/dist/svelte/motiongpu-context.js.map +1 -0
- package/dist/svelte/use-motiongpu-user-context.d.ts +1 -0
- package/dist/svelte/use-motiongpu-user-context.d.ts.map +1 -0
- package/dist/svelte/use-motiongpu-user-context.js +69 -70
- package/dist/svelte/use-motiongpu-user-context.js.map +1 -0
- package/dist/svelte/use-texture.d.ts +1 -0
- package/dist/svelte/use-texture.d.ts.map +1 -0
- package/dist/svelte/use-texture.js +125 -147
- package/dist/svelte/use-texture.js.map +1 -0
- package/package.json +12 -7
- package/src/lib/advanced.ts +6 -0
- package/src/lib/core/advanced.ts +12 -0
- package/src/lib/core/compute-shader.ts +326 -0
- package/src/lib/core/current-value.ts +64 -0
- package/src/lib/core/error-diagnostics.ts +236 -0
- package/src/lib/core/error-report.ts +535 -0
- package/src/lib/core/frame-registry.ts +1190 -0
- package/src/lib/core/index.ts +94 -0
- package/src/lib/core/material-preprocess.ts +295 -0
- package/src/lib/core/material.ts +748 -0
- package/src/lib/core/recompile-policy.ts +31 -0
- package/src/lib/core/render-graph.ts +173 -0
- package/src/lib/core/render-targets.ts +107 -0
- package/src/lib/core/renderer.ts +2161 -0
- package/src/lib/core/runtime-loop.ts +537 -0
- package/src/lib/core/scheduler-helpers.ts +136 -0
- package/src/lib/core/shader.ts +301 -0
- package/src/lib/core/storage-buffers.ts +142 -0
- package/src/lib/core/texture-loader.ts +482 -0
- package/src/lib/core/textures.ts +257 -0
- package/src/lib/core/types.ts +743 -0
- package/src/lib/core/uniforms.ts +282 -0
- package/src/lib/index.ts +6 -0
- package/src/lib/passes/BlitPass.ts +54 -0
- package/src/lib/passes/ComputePass.ts +136 -0
- package/src/lib/passes/CopyPass.ts +80 -0
- package/src/lib/passes/FullscreenPass.ts +173 -0
- package/src/lib/passes/PingPongComputePass.ts +180 -0
- package/src/lib/passes/ShaderPass.ts +89 -0
- package/src/lib/passes/index.ts +9 -0
- package/src/lib/react/FragCanvas.tsx +345 -0
- package/src/lib/react/MotionGPUErrorOverlay.tsx +524 -0
- package/src/lib/react/Portal.tsx +34 -0
- package/src/lib/react/advanced.ts +36 -0
- package/src/lib/react/frame-context.ts +169 -0
- package/src/lib/react/index.ts +68 -0
- package/src/lib/react/motiongpu-context.ts +88 -0
- package/src/lib/react/use-motiongpu-user-context.ts +186 -0
- package/src/lib/react/use-texture.ts +233 -0
- package/src/lib/svelte/FragCanvas.svelte +249 -0
- package/src/lib/svelte/MotionGPUErrorOverlay.svelte +512 -0
- package/src/lib/svelte/Portal.svelte +31 -0
- package/src/lib/svelte/advanced.ts +32 -0
- package/src/lib/svelte/frame-context.ts +87 -0
- package/src/lib/svelte/index.ts +68 -0
- package/src/lib/svelte/motiongpu-context.ts +97 -0
- package/src/lib/svelte/use-motiongpu-user-context.ts +145 -0
- package/src/lib/svelte/use-texture.ts +232 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
UniformLayout,
|
|
3
|
+
UniformLayoutEntry,
|
|
4
|
+
UniformMap,
|
|
5
|
+
UniformMat4Value,
|
|
6
|
+
UniformType,
|
|
7
|
+
UniformValue
|
|
8
|
+
} from './types.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Internal representation of explicitly typed uniform input.
|
|
12
|
+
*/
|
|
13
|
+
type UniformTypedInput = Extract<UniformValue, { type: UniformType; value: unknown }>;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Valid WGSL identifier pattern used for uniform and texture keys.
|
|
17
|
+
*/
|
|
18
|
+
const IDENTIFIER_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Rounds a value up to the nearest multiple of `alignment`.
|
|
22
|
+
*/
|
|
23
|
+
function roundUp(value: number, alignment: number): number {
|
|
24
|
+
return Math.ceil(value / alignment) * alignment;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns WGSL std140-like alignment and size metadata for a uniform type.
|
|
29
|
+
*/
|
|
30
|
+
function getTypeLayout(type: UniformType): { alignment: number; size: number } {
|
|
31
|
+
switch (type) {
|
|
32
|
+
case 'f32':
|
|
33
|
+
return { alignment: 4, size: 4 };
|
|
34
|
+
case 'vec2f':
|
|
35
|
+
return { alignment: 8, size: 8 };
|
|
36
|
+
case 'vec3f':
|
|
37
|
+
return { alignment: 16, size: 12 };
|
|
38
|
+
case 'vec4f':
|
|
39
|
+
return { alignment: 16, size: 16 };
|
|
40
|
+
case 'mat4x4f':
|
|
41
|
+
return { alignment: 16, size: 64 };
|
|
42
|
+
default:
|
|
43
|
+
throw new Error(`Unsupported uniform type: ${type satisfies never}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Type guard for explicitly typed uniform objects.
|
|
49
|
+
*/
|
|
50
|
+
function isTypedUniformValue(value: UniformValue): value is UniformTypedInput {
|
|
51
|
+
return typeof value === 'object' && value !== null && 'type' in value && 'value' in value;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validates numeric tuple input with a fixed length.
|
|
56
|
+
*/
|
|
57
|
+
function isTuple(value: unknown, size: number): value is number[] {
|
|
58
|
+
return (
|
|
59
|
+
Array.isArray(value) &&
|
|
60
|
+
value.length === size &&
|
|
61
|
+
value.every((entry) => typeof entry === 'number' && Number.isFinite(entry))
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Type guard for accepted 4x4 matrix uniform values.
|
|
67
|
+
*/
|
|
68
|
+
function isMat4Value(value: unknown): value is UniformMat4Value {
|
|
69
|
+
if (value instanceof Float32Array) {
|
|
70
|
+
return value.length === 16;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return (
|
|
74
|
+
Array.isArray(value) &&
|
|
75
|
+
value.length === 16 &&
|
|
76
|
+
value.every((entry) => typeof entry === 'number' && Number.isFinite(entry))
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Asserts that a name can be safely used as a WGSL identifier.
|
|
82
|
+
*
|
|
83
|
+
* @param name - Candidate uniform/texture name.
|
|
84
|
+
* @throws {Error} When the identifier is invalid.
|
|
85
|
+
*/
|
|
86
|
+
export function assertUniformName(name: string): void {
|
|
87
|
+
if (!IDENTIFIER_PATTERN.test(name)) {
|
|
88
|
+
throw new Error(`Invalid uniform name: ${name}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Infers the WGSL type tag from a runtime uniform value.
|
|
94
|
+
*
|
|
95
|
+
* @param value - Uniform input value.
|
|
96
|
+
* @returns Inferred uniform type.
|
|
97
|
+
* @throws {Error} When the value does not match any supported shape.
|
|
98
|
+
*/
|
|
99
|
+
export function inferUniformType(value: UniformValue): UniformType {
|
|
100
|
+
if (isTypedUniformValue(value)) {
|
|
101
|
+
return value.type;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (typeof value === 'number') {
|
|
105
|
+
return 'f32';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (Array.isArray(value)) {
|
|
109
|
+
if (value.length === 2) {
|
|
110
|
+
return 'vec2f';
|
|
111
|
+
}
|
|
112
|
+
if (value.length === 3) {
|
|
113
|
+
return 'vec3f';
|
|
114
|
+
}
|
|
115
|
+
if (value.length === 4) {
|
|
116
|
+
return 'vec4f';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
throw new Error('Uniform value must resolve to f32, vec2f, vec3f, vec4f or mat4x4f');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Validates that a uniform value matches an explicit uniform type declaration.
|
|
125
|
+
*
|
|
126
|
+
* @param type - Declared WGSL type.
|
|
127
|
+
* @param value - Runtime value to validate.
|
|
128
|
+
* @throws {Error} When the value shape is incompatible with the declared type.
|
|
129
|
+
*/
|
|
130
|
+
export function assertUniformValueForType(type: UniformType, value: UniformValue): void {
|
|
131
|
+
const input = isTypedUniformValue(value) ? value.value : value;
|
|
132
|
+
|
|
133
|
+
if (type === 'f32') {
|
|
134
|
+
if (typeof input !== 'number' || !Number.isFinite(input)) {
|
|
135
|
+
throw new Error('Uniform f32 value must be a finite number');
|
|
136
|
+
}
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (type === 'vec2f') {
|
|
141
|
+
if (!isTuple(input, 2)) {
|
|
142
|
+
throw new Error('Uniform vec2f value must be a tuple with 2 numbers');
|
|
143
|
+
}
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (type === 'vec3f') {
|
|
148
|
+
if (!isTuple(input, 3)) {
|
|
149
|
+
throw new Error('Uniform vec3f value must be a tuple with 3 numbers');
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (type === 'vec4f') {
|
|
155
|
+
if (!isTuple(input, 4)) {
|
|
156
|
+
throw new Error('Uniform vec4f value must be a tuple with 4 numbers');
|
|
157
|
+
}
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
if (!isMat4Value(input)) {
|
|
162
|
+
throw new Error('Uniform mat4x4f value must contain 16 numbers');
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Resolves a deterministic packed uniform buffer layout from a uniform map.
|
|
168
|
+
*
|
|
169
|
+
* @param uniforms - Input uniform definitions.
|
|
170
|
+
* @returns Sorted layout with byte offsets and final buffer byte length.
|
|
171
|
+
*/
|
|
172
|
+
export function resolveUniformLayout(uniforms: UniformMap): UniformLayout {
|
|
173
|
+
const names = Object.keys(uniforms).sort();
|
|
174
|
+
let offset = 0;
|
|
175
|
+
const entries: UniformLayoutEntry[] = [];
|
|
176
|
+
const byName: Record<string, UniformLayoutEntry> = {};
|
|
177
|
+
|
|
178
|
+
for (const name of names) {
|
|
179
|
+
assertUniformName(name);
|
|
180
|
+
const type = inferUniformType(uniforms[name] as UniformValue);
|
|
181
|
+
const { alignment, size } = getTypeLayout(type);
|
|
182
|
+
offset = roundUp(offset, alignment);
|
|
183
|
+
|
|
184
|
+
const entry: UniformLayoutEntry = {
|
|
185
|
+
name,
|
|
186
|
+
type,
|
|
187
|
+
offset,
|
|
188
|
+
size
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
entries.push(entry);
|
|
192
|
+
byName[name] = entry;
|
|
193
|
+
offset += size;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const byteLength = Math.max(16, roundUp(offset, 16));
|
|
197
|
+
return { entries, byName, byteLength };
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Writes one validated uniform value directly into the output float buffer.
|
|
202
|
+
*/
|
|
203
|
+
function writeUniformValue(
|
|
204
|
+
type: UniformType,
|
|
205
|
+
value: UniformValue,
|
|
206
|
+
data: Float32Array,
|
|
207
|
+
base: number
|
|
208
|
+
): void {
|
|
209
|
+
const input = isTypedUniformValue(value) ? value.value : value;
|
|
210
|
+
assertUniformValueForType(type, value);
|
|
211
|
+
|
|
212
|
+
if (type === 'f32') {
|
|
213
|
+
data[base] = input as number;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (type === 'mat4x4f') {
|
|
218
|
+
const matrix = input as UniformMat4Value;
|
|
219
|
+
if (matrix instanceof Float32Array) {
|
|
220
|
+
for (let index = 0; index < 16; index += 1) {
|
|
221
|
+
data[base + index] = matrix[index] ?? 0;
|
|
222
|
+
}
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
for (let index = 0; index < 16; index += 1) {
|
|
227
|
+
data[base + index] = matrix[index] ?? 0;
|
|
228
|
+
}
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const tuple = input as number[];
|
|
233
|
+
const length = type === 'vec2f' ? 2 : type === 'vec3f' ? 3 : 4;
|
|
234
|
+
for (let index = 0; index < length; index += 1) {
|
|
235
|
+
data[base + index] = tuple[index] ?? 0;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Packs uniforms into a newly allocated `Float32Array`.
|
|
241
|
+
*
|
|
242
|
+
* @param uniforms - Uniform values to pack.
|
|
243
|
+
* @param layout - Target layout definition.
|
|
244
|
+
* @returns Packed float buffer sized to `layout.byteLength`.
|
|
245
|
+
*/
|
|
246
|
+
export function packUniforms(uniforms: UniformMap, layout: UniformLayout): Float32Array {
|
|
247
|
+
const data = new Float32Array(layout.byteLength / 4);
|
|
248
|
+
packUniformsInto(uniforms, layout, data);
|
|
249
|
+
return data;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Packs uniforms into an existing output buffer and zeroes missing values.
|
|
254
|
+
*
|
|
255
|
+
* @param uniforms - Uniform values to pack.
|
|
256
|
+
* @param layout - Target layout metadata.
|
|
257
|
+
* @param data - Destination float buffer.
|
|
258
|
+
* @throws {Error} When `data` size does not match the required layout size.
|
|
259
|
+
*/
|
|
260
|
+
export function packUniformsInto(
|
|
261
|
+
uniforms: UniformMap,
|
|
262
|
+
layout: UniformLayout,
|
|
263
|
+
data: Float32Array
|
|
264
|
+
): void {
|
|
265
|
+
const requiredLength = layout.byteLength / 4;
|
|
266
|
+
if (data.length !== requiredLength) {
|
|
267
|
+
throw new Error(
|
|
268
|
+
`Uniform output buffer size mismatch. Expected ${requiredLength}, got ${data.length}`
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
data.fill(0);
|
|
273
|
+
for (const entry of layout.entries) {
|
|
274
|
+
const raw = uniforms[entry.name];
|
|
275
|
+
if (raw === undefined) {
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const base = entry.offset / 4;
|
|
280
|
+
writeUniformValue(entry.type, raw, data, base);
|
|
281
|
+
}
|
|
282
|
+
}
|
package/src/lib/index.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { FullscreenPass, type FullscreenPassOptions } from './FullscreenPass.js';
|
|
2
|
+
|
|
3
|
+
const FULLSCREEN_BLIT_SHADER = `
|
|
4
|
+
struct MotionGPUVertexOut {
|
|
5
|
+
@builtin(position) position: vec4f,
|
|
6
|
+
@location(0) uv: vec2f,
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
@group(0) @binding(0) var motiongpuBlitSampler: sampler;
|
|
10
|
+
@group(0) @binding(1) var motiongpuBlitTexture: texture_2d<f32>;
|
|
11
|
+
|
|
12
|
+
@vertex
|
|
13
|
+
fn motiongpuBlitVertex(@builtin(vertex_index) index: u32) -> MotionGPUVertexOut {
|
|
14
|
+
var positions = array<vec2f, 3>(
|
|
15
|
+
vec2f(-1.0, -3.0),
|
|
16
|
+
vec2f(-1.0, 1.0),
|
|
17
|
+
vec2f(3.0, 1.0)
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
let position = positions[index];
|
|
21
|
+
var out: MotionGPUVertexOut;
|
|
22
|
+
out.position = vec4f(position, 0.0, 1.0);
|
|
23
|
+
out.uv = (position + vec2f(1.0, 1.0)) * 0.5;
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@fragment
|
|
28
|
+
fn motiongpuBlitFragment(in: MotionGPUVertexOut) -> @location(0) vec4f {
|
|
29
|
+
return textureSample(motiongpuBlitTexture, motiongpuBlitSampler, in.uv);
|
|
30
|
+
}
|
|
31
|
+
`;
|
|
32
|
+
|
|
33
|
+
export type BlitPassOptions = FullscreenPassOptions;
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Fullscreen texture blit pass.
|
|
37
|
+
*/
|
|
38
|
+
export class BlitPass extends FullscreenPass {
|
|
39
|
+
protected getProgram(): string {
|
|
40
|
+
return FULLSCREEN_BLIT_SHADER;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
constructor(options: BlitPassOptions = {}) {
|
|
44
|
+
super(options);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
protected getVertexEntryPoint(): string {
|
|
48
|
+
return 'motiongpuBlitVertex';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
protected getFragmentEntryPoint(): string {
|
|
52
|
+
return 'motiongpuBlitFragment';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import { assertComputeContract, extractWorkgroupSize } from '../core/compute-shader.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Dispatch context provided to dynamic dispatch callbacks.
|
|
5
|
+
*/
|
|
6
|
+
export interface ComputeDispatchContext {
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
time: number;
|
|
10
|
+
delta: number;
|
|
11
|
+
workgroupSize: [number, number, number];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Options for constructing a `ComputePass`.
|
|
16
|
+
*/
|
|
17
|
+
export interface ComputePassOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Compute shader WGSL source code.
|
|
20
|
+
* Must declare `@compute @workgroup_size(...) fn compute(@builtin(global_invocation_id) ...)`.
|
|
21
|
+
*/
|
|
22
|
+
compute: string;
|
|
23
|
+
/**
|
|
24
|
+
* Dispatch workgroup counts.
|
|
25
|
+
* - Static tuple: `[x]`, `[x, y]`, or `[x, y, z]`
|
|
26
|
+
* - `'auto'`: derived from canvas size / workgroup size
|
|
27
|
+
* - Function: dynamic dispatch based on frame context
|
|
28
|
+
*/
|
|
29
|
+
dispatch?:
|
|
30
|
+
| [number, number?, number?]
|
|
31
|
+
| 'auto'
|
|
32
|
+
| ((ctx: ComputeDispatchContext) => [number, number, number]);
|
|
33
|
+
/**
|
|
34
|
+
* Enables/disables this compute pass.
|
|
35
|
+
*/
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Compute pass class used within the render graph.
|
|
41
|
+
*
|
|
42
|
+
* Validates compute shader contract, parses workgroup size,
|
|
43
|
+
* and resolves dispatch dimensions. Does **not** manage GPU resources
|
|
44
|
+
* (that responsibility belongs to the renderer).
|
|
45
|
+
*/
|
|
46
|
+
export class ComputePass {
|
|
47
|
+
/**
|
|
48
|
+
* Enables/disables this pass without removing it from graph.
|
|
49
|
+
*/
|
|
50
|
+
enabled: boolean;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Discriminant flag for render graph to identify compute passes.
|
|
54
|
+
*/
|
|
55
|
+
readonly isCompute = true as const;
|
|
56
|
+
|
|
57
|
+
private compute: string;
|
|
58
|
+
private workgroupSize: [number, number, number];
|
|
59
|
+
private dispatch: ComputePassOptions['dispatch'];
|
|
60
|
+
|
|
61
|
+
constructor(options: ComputePassOptions) {
|
|
62
|
+
assertComputeContract(options.compute);
|
|
63
|
+
const workgroupSize = extractWorkgroupSize(options.compute);
|
|
64
|
+
this.compute = options.compute;
|
|
65
|
+
this.workgroupSize = workgroupSize;
|
|
66
|
+
this.dispatch = options.dispatch ?? 'auto';
|
|
67
|
+
this.enabled = options.enabled ?? true;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Replaces current compute shader and updates workgroup size.
|
|
72
|
+
*
|
|
73
|
+
* @param compute - New compute shader WGSL source.
|
|
74
|
+
* @throws {Error} When shader does not match the compute contract.
|
|
75
|
+
*/
|
|
76
|
+
setCompute(compute: string): void {
|
|
77
|
+
assertComputeContract(compute);
|
|
78
|
+
const workgroupSize = extractWorkgroupSize(compute);
|
|
79
|
+
this.compute = compute;
|
|
80
|
+
this.workgroupSize = workgroupSize;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Updates dispatch strategy.
|
|
85
|
+
*/
|
|
86
|
+
setDispatch(dispatch: ComputePassOptions['dispatch']): void {
|
|
87
|
+
this.dispatch = dispatch ?? 'auto';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Returns current compute shader source.
|
|
92
|
+
*/
|
|
93
|
+
getCompute(): string {
|
|
94
|
+
return this.compute;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Returns parsed workgroup size from current compute shader.
|
|
99
|
+
*/
|
|
100
|
+
getWorkgroupSize(): [number, number, number] {
|
|
101
|
+
return [...this.workgroupSize];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Resolves dispatch workgroup counts for current frame.
|
|
106
|
+
*
|
|
107
|
+
* @param ctx - Dispatch context with canvas dimensions and timing.
|
|
108
|
+
* @returns Tuple [x, y, z] workgroup counts.
|
|
109
|
+
*/
|
|
110
|
+
resolveDispatch(ctx: ComputeDispatchContext): [number, number, number] {
|
|
111
|
+
if (this.dispatch === 'auto') {
|
|
112
|
+
return [
|
|
113
|
+
Math.ceil(ctx.width / this.workgroupSize[0]),
|
|
114
|
+
Math.ceil(ctx.height / this.workgroupSize[1]),
|
|
115
|
+
Math.ceil(1 / this.workgroupSize[2])
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (typeof this.dispatch === 'function') {
|
|
120
|
+
return this.dispatch(ctx);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (Array.isArray(this.dispatch)) {
|
|
124
|
+
return [this.dispatch[0], this.dispatch[1] ?? 1, this.dispatch[2] ?? 1];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return [1, 1, 1];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Releases resources (no-op since GPU resource lifecycle is renderer-managed).
|
|
132
|
+
*/
|
|
133
|
+
dispose(): void {
|
|
134
|
+
// No-op — GPU resources are managed by the renderer.
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RenderPass,
|
|
3
|
+
RenderPassContext,
|
|
4
|
+
RenderPassFlags,
|
|
5
|
+
RenderPassInputSlot,
|
|
6
|
+
RenderPassOutputSlot
|
|
7
|
+
} from '../core/types.js';
|
|
8
|
+
import { BlitPass } from './BlitPass.js';
|
|
9
|
+
|
|
10
|
+
export interface CopyPassOptions extends RenderPassFlags {
|
|
11
|
+
enabled?: boolean;
|
|
12
|
+
needsSwap?: boolean;
|
|
13
|
+
input?: RenderPassInputSlot;
|
|
14
|
+
output?: RenderPassOutputSlot;
|
|
15
|
+
filter?: GPUFilterMode;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Texture copy pass with fullscreen-blit fallback.
|
|
20
|
+
*/
|
|
21
|
+
export class CopyPass implements RenderPass {
|
|
22
|
+
enabled: boolean;
|
|
23
|
+
needsSwap: boolean;
|
|
24
|
+
input: RenderPassInputSlot;
|
|
25
|
+
output: RenderPassOutputSlot;
|
|
26
|
+
clear: boolean;
|
|
27
|
+
clearColor: [number, number, number, number];
|
|
28
|
+
preserve: boolean;
|
|
29
|
+
private readonly fallbackBlit: BlitPass;
|
|
30
|
+
|
|
31
|
+
constructor(options: CopyPassOptions = {}) {
|
|
32
|
+
this.enabled = options.enabled ?? true;
|
|
33
|
+
this.needsSwap = options.needsSwap ?? true;
|
|
34
|
+
this.input = options.input ?? 'source';
|
|
35
|
+
this.output = options.output ?? (this.needsSwap ? 'target' : 'source');
|
|
36
|
+
this.clear = options.clear ?? false;
|
|
37
|
+
this.clearColor = options.clearColor ?? [0, 0, 0, 1];
|
|
38
|
+
this.preserve = options.preserve ?? true;
|
|
39
|
+
this.fallbackBlit = new BlitPass({
|
|
40
|
+
enabled: true,
|
|
41
|
+
needsSwap: false,
|
|
42
|
+
input: this.input,
|
|
43
|
+
output: this.output,
|
|
44
|
+
...(options.filter !== undefined ? { filter: options.filter } : {})
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
setSize(width: number, height: number): void {
|
|
49
|
+
this.fallbackBlit.setSize(width, height);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
render(context: RenderPassContext): void {
|
|
53
|
+
const source = context.input;
|
|
54
|
+
const target = context.output;
|
|
55
|
+
const canDirectCopy =
|
|
56
|
+
context.clear === false &&
|
|
57
|
+
context.preserve === true &&
|
|
58
|
+
source.texture !== target.texture &&
|
|
59
|
+
source.texture !== context.canvas.texture &&
|
|
60
|
+
target.texture !== context.canvas.texture &&
|
|
61
|
+
source.width === target.width &&
|
|
62
|
+
source.height === target.height &&
|
|
63
|
+
source.format === target.format;
|
|
64
|
+
|
|
65
|
+
if (canDirectCopy) {
|
|
66
|
+
context.commandEncoder.copyTextureToTexture(
|
|
67
|
+
{ texture: source.texture },
|
|
68
|
+
{ texture: target.texture },
|
|
69
|
+
{ width: source.width, height: source.height, depthOrArrayLayers: 1 }
|
|
70
|
+
);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
this.fallbackBlit.render(context);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
dispose(): void {
|
|
78
|
+
this.fallbackBlit.dispose();
|
|
79
|
+
}
|
|
80
|
+
}
|