@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,173 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
RenderPass,
|
|
3
|
+
RenderPassContext,
|
|
4
|
+
RenderPassFlags,
|
|
5
|
+
RenderPassInputSlot,
|
|
6
|
+
RenderPassOutputSlot
|
|
7
|
+
} from '../core/types.js';
|
|
8
|
+
|
|
9
|
+
export interface FullscreenPassOptions extends RenderPassFlags {
|
|
10
|
+
enabled?: boolean;
|
|
11
|
+
needsSwap?: boolean;
|
|
12
|
+
input?: RenderPassInputSlot;
|
|
13
|
+
output?: RenderPassOutputSlot;
|
|
14
|
+
filter?: GPUFilterMode;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Shared base for fullscreen texture sampling passes.
|
|
19
|
+
*/
|
|
20
|
+
export abstract class FullscreenPass implements RenderPass {
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
needsSwap: boolean;
|
|
23
|
+
input: RenderPassInputSlot;
|
|
24
|
+
output: RenderPassOutputSlot;
|
|
25
|
+
clear: boolean;
|
|
26
|
+
clearColor: [number, number, number, number];
|
|
27
|
+
preserve: boolean;
|
|
28
|
+
private readonly filter: GPUFilterMode;
|
|
29
|
+
private device: GPUDevice | null = null;
|
|
30
|
+
private sampler: GPUSampler | null = null;
|
|
31
|
+
private bindGroupLayout: GPUBindGroupLayout | null = null;
|
|
32
|
+
private shaderModule: GPUShaderModule | null = null;
|
|
33
|
+
private readonly pipelineByFormat = new Map<GPUTextureFormat, GPURenderPipeline>();
|
|
34
|
+
private bindGroupByView = new WeakMap<GPUTextureView, GPUBindGroup>();
|
|
35
|
+
|
|
36
|
+
protected constructor(options: FullscreenPassOptions = {}) {
|
|
37
|
+
this.enabled = options.enabled ?? true;
|
|
38
|
+
this.needsSwap = options.needsSwap ?? true;
|
|
39
|
+
this.input = options.input ?? 'source';
|
|
40
|
+
this.output = options.output ?? (this.needsSwap ? 'target' : 'source');
|
|
41
|
+
this.clear = options.clear ?? false;
|
|
42
|
+
this.clearColor = options.clearColor ?? [0, 0, 0, 1];
|
|
43
|
+
this.preserve = options.preserve ?? true;
|
|
44
|
+
this.filter = options.filter ?? 'linear';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
protected abstract getProgram(): string;
|
|
48
|
+
protected abstract getVertexEntryPoint(): string;
|
|
49
|
+
protected abstract getFragmentEntryPoint(): string;
|
|
50
|
+
|
|
51
|
+
protected invalidateFullscreenCache(): void {
|
|
52
|
+
this.shaderModule = null;
|
|
53
|
+
this.pipelineByFormat.clear();
|
|
54
|
+
this.bindGroupByView = new WeakMap();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
private ensureResources(
|
|
58
|
+
device: GPUDevice,
|
|
59
|
+
format: GPUTextureFormat
|
|
60
|
+
): {
|
|
61
|
+
sampler: GPUSampler;
|
|
62
|
+
bindGroupLayout: GPUBindGroupLayout;
|
|
63
|
+
pipeline: GPURenderPipeline;
|
|
64
|
+
} {
|
|
65
|
+
if (this.device !== device) {
|
|
66
|
+
this.device = device;
|
|
67
|
+
this.sampler = null;
|
|
68
|
+
this.bindGroupLayout = null;
|
|
69
|
+
this.invalidateFullscreenCache();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!this.sampler) {
|
|
73
|
+
this.sampler = device.createSampler({
|
|
74
|
+
magFilter: this.filter,
|
|
75
|
+
minFilter: this.filter,
|
|
76
|
+
addressModeU: 'clamp-to-edge',
|
|
77
|
+
addressModeV: 'clamp-to-edge'
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!this.bindGroupLayout) {
|
|
82
|
+
this.bindGroupLayout = device.createBindGroupLayout({
|
|
83
|
+
entries: [
|
|
84
|
+
{
|
|
85
|
+
binding: 0,
|
|
86
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
87
|
+
sampler: { type: 'filtering' }
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
binding: 1,
|
|
91
|
+
visibility: GPUShaderStage.FRAGMENT,
|
|
92
|
+
texture: {
|
|
93
|
+
sampleType: 'float',
|
|
94
|
+
viewDimension: '2d',
|
|
95
|
+
multisampled: false
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
]
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (!this.shaderModule) {
|
|
103
|
+
this.shaderModule = device.createShaderModule({ code: this.getProgram() });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
let pipeline = this.pipelineByFormat.get(format);
|
|
107
|
+
if (!pipeline) {
|
|
108
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
109
|
+
bindGroupLayouts: [this.bindGroupLayout]
|
|
110
|
+
});
|
|
111
|
+
pipeline = device.createRenderPipeline({
|
|
112
|
+
layout: pipelineLayout,
|
|
113
|
+
vertex: {
|
|
114
|
+
module: this.shaderModule,
|
|
115
|
+
entryPoint: this.getVertexEntryPoint()
|
|
116
|
+
},
|
|
117
|
+
fragment: {
|
|
118
|
+
module: this.shaderModule,
|
|
119
|
+
entryPoint: this.getFragmentEntryPoint(),
|
|
120
|
+
targets: [{ format }]
|
|
121
|
+
},
|
|
122
|
+
primitive: { topology: 'triangle-list' }
|
|
123
|
+
});
|
|
124
|
+
this.pipelineByFormat.set(format, pipeline);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
sampler: this.sampler,
|
|
129
|
+
bindGroupLayout: this.bindGroupLayout,
|
|
130
|
+
pipeline
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setSize(width: number, height: number): void {
|
|
135
|
+
void width;
|
|
136
|
+
void height;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
protected renderFullscreen(context: RenderPassContext): void {
|
|
140
|
+
const { sampler, bindGroupLayout, pipeline } = this.ensureResources(
|
|
141
|
+
context.device,
|
|
142
|
+
context.output.format
|
|
143
|
+
);
|
|
144
|
+
const inputView = context.input.view;
|
|
145
|
+
let bindGroup = this.bindGroupByView.get(inputView);
|
|
146
|
+
if (!bindGroup) {
|
|
147
|
+
bindGroup = context.device.createBindGroup({
|
|
148
|
+
layout: bindGroupLayout,
|
|
149
|
+
entries: [
|
|
150
|
+
{ binding: 0, resource: sampler },
|
|
151
|
+
{ binding: 1, resource: inputView }
|
|
152
|
+
]
|
|
153
|
+
});
|
|
154
|
+
this.bindGroupByView.set(inputView, bindGroup);
|
|
155
|
+
}
|
|
156
|
+
const pass = context.beginRenderPass();
|
|
157
|
+
pass.setPipeline(pipeline);
|
|
158
|
+
pass.setBindGroup(0, bindGroup);
|
|
159
|
+
pass.draw(3);
|
|
160
|
+
pass.end();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
render(context: RenderPassContext): void {
|
|
164
|
+
this.renderFullscreen(context);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
dispose(): void {
|
|
168
|
+
this.device = null;
|
|
169
|
+
this.sampler = null;
|
|
170
|
+
this.bindGroupLayout = null;
|
|
171
|
+
this.invalidateFullscreenCache();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { assertComputeContract, extractWorkgroupSize } from '../core/compute-shader.js';
|
|
2
|
+
import type { ComputePassOptions, ComputeDispatchContext } from './ComputePass.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for constructing a `PingPongComputePass`.
|
|
6
|
+
*/
|
|
7
|
+
export interface PingPongComputePassOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Compute shader WGSL source code.
|
|
10
|
+
*/
|
|
11
|
+
compute: string;
|
|
12
|
+
/**
|
|
13
|
+
* Target texture key from `material.textures`.
|
|
14
|
+
* The engine will auto-generate `{target}A` and `{target}B` bindings.
|
|
15
|
+
*/
|
|
16
|
+
target: string;
|
|
17
|
+
/**
|
|
18
|
+
* Number of compute iterations per frame. Default: 1.
|
|
19
|
+
*/
|
|
20
|
+
iterations?: number;
|
|
21
|
+
/**
|
|
22
|
+
* Dispatch workgroup counts (same as ComputePass).
|
|
23
|
+
*/
|
|
24
|
+
dispatch?: ComputePassOptions['dispatch'];
|
|
25
|
+
/**
|
|
26
|
+
* Enables/disables this pass.
|
|
27
|
+
*/
|
|
28
|
+
enabled?: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Ping-pong compute pass for iterative GPU simulations.
|
|
33
|
+
*
|
|
34
|
+
* Manages two texture buffers (A/B) and alternates between them each iteration,
|
|
35
|
+
* enabling read-from-previous-write patterns commonly used in fluid simulations,
|
|
36
|
+
* reaction-diffusion, and particle systems.
|
|
37
|
+
*/
|
|
38
|
+
export class PingPongComputePass {
|
|
39
|
+
/**
|
|
40
|
+
* Enables/disables this pass without removing it from graph.
|
|
41
|
+
*/
|
|
42
|
+
enabled: boolean;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Discriminant flag for render graph to identify compute passes.
|
|
46
|
+
*/
|
|
47
|
+
readonly isCompute = true as const;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Discriminant flag to identify ping-pong compute passes.
|
|
51
|
+
*/
|
|
52
|
+
readonly isPingPong = true as const;
|
|
53
|
+
|
|
54
|
+
private compute: string;
|
|
55
|
+
private target: string;
|
|
56
|
+
private iterations: number;
|
|
57
|
+
private dispatch: ComputePassOptions['dispatch'];
|
|
58
|
+
private workgroupSize: [number, number, number];
|
|
59
|
+
private frameCount: number = 0;
|
|
60
|
+
|
|
61
|
+
constructor(options: PingPongComputePassOptions) {
|
|
62
|
+
assertComputeContract(options.compute);
|
|
63
|
+
const workgroupSize = extractWorkgroupSize(options.compute);
|
|
64
|
+
this.compute = options.compute;
|
|
65
|
+
this.target = options.target;
|
|
66
|
+
this.iterations = PingPongComputePass.assertIterations(options.iterations ?? 1);
|
|
67
|
+
this.dispatch = options.dispatch ?? 'auto';
|
|
68
|
+
this.enabled = options.enabled ?? true;
|
|
69
|
+
this.workgroupSize = workgroupSize;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
private static assertIterations(count: number): number {
|
|
73
|
+
if (!Number.isFinite(count) || count < 1 || !Number.isInteger(count)) {
|
|
74
|
+
throw new Error(
|
|
75
|
+
`PingPongComputePass iterations must be a positive integer >= 1, got ${count}`
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
return count;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Returns the texture key holding the latest result.
|
|
83
|
+
* Alternates between `{target}A` and `{target}B` based on total iteration parity.
|
|
84
|
+
*/
|
|
85
|
+
getCurrentOutput(): string {
|
|
86
|
+
const totalIterations = this.frameCount * this.iterations;
|
|
87
|
+
return totalIterations % 2 === 0 ? `${this.target}A` : `${this.target}B`;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Advances the internal frame counter (called by renderer after each frame's iterations).
|
|
92
|
+
*/
|
|
93
|
+
advanceFrame(): void {
|
|
94
|
+
this.frameCount += 1;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Replaces compute shader and updates workgroup size.
|
|
99
|
+
*/
|
|
100
|
+
setCompute(compute: string): void {
|
|
101
|
+
assertComputeContract(compute);
|
|
102
|
+
const workgroupSize = extractWorkgroupSize(compute);
|
|
103
|
+
this.compute = compute;
|
|
104
|
+
this.workgroupSize = workgroupSize;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Updates iteration count.
|
|
109
|
+
*
|
|
110
|
+
* @param count - Must be >= 1.
|
|
111
|
+
*/
|
|
112
|
+
setIterations(count: number): void {
|
|
113
|
+
this.iterations = PingPongComputePass.assertIterations(count);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Updates dispatch strategy.
|
|
118
|
+
*/
|
|
119
|
+
setDispatch(dispatch: ComputePassOptions['dispatch']): void {
|
|
120
|
+
this.dispatch = dispatch ?? 'auto';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Returns the target texture key.
|
|
125
|
+
*/
|
|
126
|
+
getTarget(): string {
|
|
127
|
+
return this.target;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Returns the current iteration count.
|
|
132
|
+
*/
|
|
133
|
+
getIterations(): number {
|
|
134
|
+
return this.iterations;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Returns current compute shader source.
|
|
139
|
+
*/
|
|
140
|
+
getCompute(): string {
|
|
141
|
+
return this.compute;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Returns parsed workgroup size.
|
|
146
|
+
*/
|
|
147
|
+
getWorkgroupSize(): [number, number, number] {
|
|
148
|
+
return [...this.workgroupSize];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Resolves dispatch workgroup counts for current frame.
|
|
153
|
+
*/
|
|
154
|
+
resolveDispatch(ctx: ComputeDispatchContext): [number, number, number] {
|
|
155
|
+
if (this.dispatch === 'auto') {
|
|
156
|
+
return [
|
|
157
|
+
Math.ceil(ctx.width / this.workgroupSize[0]),
|
|
158
|
+
Math.ceil(ctx.height / this.workgroupSize[1]),
|
|
159
|
+
Math.ceil(1 / this.workgroupSize[2])
|
|
160
|
+
];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (typeof this.dispatch === 'function') {
|
|
164
|
+
return this.dispatch(ctx);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (Array.isArray(this.dispatch)) {
|
|
168
|
+
return [this.dispatch[0], this.dispatch[1] ?? 1, this.dispatch[2] ?? 1];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return [1, 1, 1];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Releases resources (no-op, GPU lifecycle is renderer-managed).
|
|
176
|
+
*/
|
|
177
|
+
dispose(): void {
|
|
178
|
+
// No-op
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { FullscreenPass, type FullscreenPassOptions } from './FullscreenPass.js';
|
|
2
|
+
|
|
3
|
+
const SHADER_PASS_CONTRACT =
|
|
4
|
+
/\bfn\s+shade\s*\(\s*inputColor\s*:\s*vec4f\s*,\s*uv\s*:\s*vec2f\s*\)\s*->\s*vec4f/;
|
|
5
|
+
|
|
6
|
+
export interface ShaderPassOptions extends FullscreenPassOptions {
|
|
7
|
+
fragment: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function buildShaderPassProgram(fragment: string): string {
|
|
11
|
+
if (!SHADER_PASS_CONTRACT.test(fragment)) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
'ShaderPass fragment must declare `fn shade(inputColor: vec4f, uv: vec2f) -> vec4f`.'
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return `
|
|
18
|
+
struct MotionGPUVertexOut {
|
|
19
|
+
@builtin(position) position: vec4f,
|
|
20
|
+
@location(0) uv: vec2f,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
@group(0) @binding(0) var motiongpuShaderPassSampler: sampler;
|
|
24
|
+
@group(0) @binding(1) var motiongpuShaderPassTexture: texture_2d<f32>;
|
|
25
|
+
|
|
26
|
+
@vertex
|
|
27
|
+
fn motiongpuShaderPassVertex(@builtin(vertex_index) index: u32) -> MotionGPUVertexOut {
|
|
28
|
+
var positions = array<vec2f, 3>(
|
|
29
|
+
vec2f(-1.0, -3.0),
|
|
30
|
+
vec2f(-1.0, 1.0),
|
|
31
|
+
vec2f(3.0, 1.0)
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
let position = positions[index];
|
|
35
|
+
var out: MotionGPUVertexOut;
|
|
36
|
+
out.position = vec4f(position, 0.0, 1.0);
|
|
37
|
+
out.uv = (position + vec2f(1.0, 1.0)) * 0.5;
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
${fragment}
|
|
42
|
+
|
|
43
|
+
@fragment
|
|
44
|
+
fn motiongpuShaderPassFragment(in: MotionGPUVertexOut) -> @location(0) vec4f {
|
|
45
|
+
let inputColor = textureSample(motiongpuShaderPassTexture, motiongpuShaderPassSampler, in.uv);
|
|
46
|
+
return shade(inputColor, in.uv);
|
|
47
|
+
}
|
|
48
|
+
`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Fullscreen programmable shader pass.
|
|
53
|
+
*/
|
|
54
|
+
export class ShaderPass extends FullscreenPass {
|
|
55
|
+
private fragment: string;
|
|
56
|
+
private program: string;
|
|
57
|
+
|
|
58
|
+
constructor(options: ShaderPassOptions) {
|
|
59
|
+
super(options);
|
|
60
|
+
this.fragment = options.fragment;
|
|
61
|
+
this.program = buildShaderPassProgram(options.fragment);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Replaces current shader fragment and invalidates pipeline cache.
|
|
66
|
+
*/
|
|
67
|
+
setFragment(fragment: string): void {
|
|
68
|
+
const nextProgram = buildShaderPassProgram(fragment);
|
|
69
|
+
this.fragment = fragment;
|
|
70
|
+
this.program = nextProgram;
|
|
71
|
+
this.invalidateFullscreenCache();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getFragment(): string {
|
|
75
|
+
return this.fragment;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
protected getProgram(): string {
|
|
79
|
+
return this.program;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
protected getVertexEntryPoint(): string {
|
|
83
|
+
return 'motiongpuShaderPassVertex';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
protected getFragmentEntryPoint(): string {
|
|
87
|
+
return 'motiongpuShaderPassFragment';
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export { BlitPass, type BlitPassOptions } from './BlitPass.js';
|
|
2
|
+
export { CopyPass, type CopyPassOptions } from './CopyPass.js';
|
|
3
|
+
export { ShaderPass, type ShaderPassOptions } from './ShaderPass.js';
|
|
4
|
+
export {
|
|
5
|
+
ComputePass,
|
|
6
|
+
type ComputePassOptions,
|
|
7
|
+
type ComputeDispatchContext
|
|
8
|
+
} from './ComputePass.js';
|
|
9
|
+
export { PingPongComputePass, type PingPongComputePassOptions } from './PingPongComputePass.js';
|