oidn-webgpu 0.0.0-stage → 0.6.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 (95) hide show
  1. package/CHANGELOG.md +182 -0
  2. package/LICENSE +21 -0
  3. package/README.md +89 -2
  4. package/dist/oidn.js +6140 -0
  5. package/dist/oidn.umd.cjs +1513 -0
  6. package/lib/UNet.d.ts +161 -0
  7. package/lib/UNet.js +536 -0
  8. package/lib/UNet.js.map +1 -0
  9. package/lib/WGPUComputePass.d.ts +62 -0
  10. package/lib/WGPUComputePass.js +278 -0
  11. package/lib/WGPUComputePass.js.map +1 -0
  12. package/lib/WGPUFullQuadPass.d.ts +50 -0
  13. package/lib/WGPUFullQuadPass.js +261 -0
  14. package/lib/WGPUFullQuadPass.js.map +1 -0
  15. package/lib/backend.d.ts +1 -0
  16. package/lib/backend.js +39 -0
  17. package/lib/backend.js.map +1 -0
  18. package/lib/finalRgbShader.d.ts +13 -0
  19. package/lib/finalRgbShader.js +160 -0
  20. package/lib/finalRgbShader.js.map +1 -0
  21. package/lib/gpuTypes.d.ts +158 -0
  22. package/lib/gpuTypes.js +27 -0
  23. package/lib/gpuTypes.js.map +1 -0
  24. package/lib/graphOptimizer.d.ts +54 -0
  25. package/lib/graphOptimizer.js +216 -0
  26. package/lib/graphOptimizer.js.map +1 -0
  27. package/lib/hdr.d.ts +31 -0
  28. package/lib/hdr.js +340 -0
  29. package/lib/hdr.js.map +1 -0
  30. package/lib/hdrTransfer.d.ts +22 -0
  31. package/lib/hdrTransfer.js +128 -0
  32. package/lib/hdrTransfer.js.map +1 -0
  33. package/lib/main.d.ts +57 -0
  34. package/lib/main.js +24 -0
  35. package/lib/main.js.map +1 -0
  36. package/lib/modelSpec.d.ts +80 -0
  37. package/lib/modelSpec.js +270 -0
  38. package/lib/modelSpec.js.map +1 -0
  39. package/lib/native/denoise.d.ts +25 -0
  40. package/lib/native/denoise.js +223 -0
  41. package/lib/native/denoise.js.map +1 -0
  42. package/lib/native/program.d.ts +53 -0
  43. package/lib/native/program.js +310 -0
  44. package/lib/native/program.js.map +1 -0
  45. package/lib/native/recordingDevice.d.ts +125 -0
  46. package/lib/native/recordingDevice.js +247 -0
  47. package/lib/native/recordingDevice.js.map +1 -0
  48. package/lib/nativeUNet.d.ts +25 -0
  49. package/lib/nativeUNet.js +184 -0
  50. package/lib/nativeUNet.js.map +1 -0
  51. package/lib/nativeUNetCore.d.ts +202 -0
  52. package/lib/nativeUNetCore.js +2945 -0
  53. package/lib/nativeUNetCore.js.map +1 -0
  54. package/lib/process.d.ts +37 -0
  55. package/lib/process.js +328 -0
  56. package/lib/process.js.map +1 -0
  57. package/lib/resourceTracker.d.ts +26 -0
  58. package/lib/resourceTracker.js +65 -0
  59. package/lib/resourceTracker.js.map +1 -0
  60. package/lib/sanitize.d.ts +15 -0
  61. package/lib/sanitize.js +28 -0
  62. package/lib/sanitize.js.map +1 -0
  63. package/lib/tileScheduler.d.ts +61 -0
  64. package/lib/tileScheduler.js +199 -0
  65. package/lib/tileScheduler.js.map +1 -0
  66. package/lib/tza.d.ts +15 -0
  67. package/lib/tza.js +114 -0
  68. package/lib/tza.js.map +1 -0
  69. package/lib/webnnUNet.d.ts +52 -0
  70. package/lib/webnnUNet.js +535 -0
  71. package/lib/webnnUNet.js.map +1 -0
  72. package/package.json +43 -4
  73. package/src/UNet.ts +861 -0
  74. package/src/WGPUComputePass.ts +382 -0
  75. package/src/WGPUFullQuadPass.ts +348 -0
  76. package/src/backend.ts +42 -0
  77. package/src/finalRgbShader.ts +186 -0
  78. package/src/gpuTypes.ts +179 -0
  79. package/src/graphOptimizer.ts +301 -0
  80. package/src/hdr.ts +398 -0
  81. package/src/hdrTransfer.ts +169 -0
  82. package/src/main.ts +126 -0
  83. package/src/modelSpec.ts +414 -0
  84. package/src/native/core-profile.json +40 -0
  85. package/src/native/denoise.ts +268 -0
  86. package/src/native/program.ts +326 -0
  87. package/src/native/recordingDevice.ts +341 -0
  88. package/src/nativeUNet.ts +269 -0
  89. package/src/nativeUNetCore.ts +3735 -0
  90. package/src/process.ts +368 -0
  91. package/src/resourceTracker.ts +94 -0
  92. package/src/sanitize.ts +29 -0
  93. package/src/tileScheduler.ts +330 -0
  94. package/src/tza.ts +136 -0
  95. package/src/webnnUNet.ts +812 -0
@@ -0,0 +1,382 @@
1
+ function isStorageParamsEqual(
2
+ params: WGPUComputePassOutput,
3
+ other: WGPUComputePassOutput
4
+ ) {
5
+ return params.channels === other.channels;
6
+ }
7
+
8
+ export const WORKGROUP_SIZE = 8;
9
+
10
+ export interface Uniform {
11
+ label: string;
12
+ type: string;
13
+ data: Float32Array | Int32Array | Uint32Array;
14
+ }
15
+ export interface WGPUComputePassInput {
16
+ buffer?: GPUBuffer;
17
+ texture?: GPUTexture;
18
+ channels: number;
19
+ }
20
+
21
+ export interface WGPUComputePassOutput {
22
+ channels: number;
23
+ }
24
+
25
+ export class WGPUComputePass<I extends string, O extends string> {
26
+ autoUpdateOutputBuffer = true;
27
+
28
+ private _label;
29
+
30
+ private _device;
31
+ private _outputBuffers: Record<
32
+ string,
33
+ {
34
+ buffer: GPUBuffer;
35
+ params: WGPUComputePassOutput;
36
+ }
37
+ > = {};
38
+
39
+ private _pipeline!: GPUComputePipeline;
40
+ private _bindGroups: GPUBindGroup[] = [];
41
+ private _needsUpdatePipeline = true;
42
+ private _needsResizeBuffer = true;
43
+
44
+ private _inputs: string[] = [];
45
+ private _outputs: string[] = [];
46
+ private _uniforms: Uniform[] = [];
47
+ private _uniformBuffers: Record<string, GPUBuffer> = {};
48
+
49
+ private _width = 10;
50
+ private _height = 10;
51
+
52
+ private _execWidth?: number;
53
+ private _execHeight?: number;
54
+
55
+ private _csCode = '';
56
+ private _csMain;
57
+ private _csDefine;
58
+
59
+ private _groupOffsets = {
60
+ inputs: 0,
61
+ uniforms: 1,
62
+ outputs: 2
63
+ };
64
+
65
+ constructor(
66
+ label: string,
67
+ device: GPUDevice,
68
+ opts: {
69
+ inputs: I[];
70
+ outputs: O[];
71
+ csMain: string;
72
+ csDefine?: string;
73
+ uniforms: Uniform[];
74
+ autoUpdateOutputBuffer?: boolean;
75
+ }
76
+ ) {
77
+ this._label = label;
78
+ this._device = device;
79
+ this._csMain = opts.csMain;
80
+ this._csDefine = opts.csDefine;
81
+ this._inputs = opts.inputs;
82
+ this._outputs = opts.outputs;
83
+ this._uniforms = opts.uniforms;
84
+ this.autoUpdateOutputBuffer = opts.autoUpdateOutputBuffer ?? true;
85
+
86
+ opts.uniforms.forEach((uniform) => {
87
+ this._uniformBuffers[uniform.label] = device.createBuffer({
88
+ label: this._label,
89
+ size: uniform.data.byteLength,
90
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
91
+ });
92
+ this._device.queue.writeBuffer(
93
+ this._uniformBuffers[uniform.label],
94
+ 0,
95
+ uniform.data
96
+ );
97
+ });
98
+ }
99
+
100
+ setCSCode({ csDefine, csMain }: { csDefine: string; csMain: string }) {
101
+ this._csDefine = csDefine;
102
+ this._csMain = csMain;
103
+ this._needsUpdatePipeline = true;
104
+ }
105
+
106
+ setSize(width: number, height: number) {
107
+ width = Math.ceil(width);
108
+ height = Math.ceil(height);
109
+ const sizeChanged = width !== this._width || height !== this._height;
110
+ this._width = width;
111
+ this._height = height;
112
+ if (sizeChanged) {
113
+ this._needsResizeBuffer = true;
114
+ this._needsUpdatePipeline = true;
115
+ }
116
+ }
117
+
118
+ setExecuteSize(width: number, height: number) {
119
+ width = Math.ceil(width);
120
+ height = Math.ceil(height);
121
+ this._execWidth = width;
122
+ this._execHeight = height;
123
+ }
124
+
125
+ setOutputParams(outputParams: Record<O, WGPUComputePassOutput>) {
126
+ if (this.autoUpdateOutputBuffer) {
127
+ this._updateOutputBuffers(outputParams);
128
+ }
129
+ this._needsUpdatePipeline = true;
130
+ }
131
+
132
+ setOutputBuffers(outputBuffers: Record<O, GPUBuffer>) {
133
+ this._outputBuffers = Object.keys(outputBuffers).reduce((obj, key) => {
134
+ obj[key] = {
135
+ buffer: outputBuffers[key as O],
136
+ params: { channels: 4 }
137
+ };
138
+ return obj;
139
+ }, {} as WGPUComputePass<I, O>['_outputBuffers']);
140
+ }
141
+
142
+ setUniform(label: string, data: Float32Array | Int32Array | Uint32Array) {
143
+ const buffer = this._uniformBuffers[label];
144
+ this._device.queue.writeBuffer(buffer, 0, data);
145
+ }
146
+
147
+ getOutput(name: O) {
148
+ if (this._needsResizeBuffer && this.autoUpdateOutputBuffer) {
149
+ this._resizeOutputBuffers();
150
+ this._needsResizeBuffer = false;
151
+ }
152
+ return this._outputBuffers[name].buffer;
153
+ }
154
+
155
+ dispose(destroyOutputBuffers = true) {
156
+ Object.keys(this._uniformBuffers).forEach((key) => {
157
+ (this._uniformBuffers as any)[key].destroy();
158
+ });
159
+ if (destroyOutputBuffers) {
160
+ Object.keys(this._outputBuffers).forEach((key) => {
161
+ (this._outputBuffers as any)[key].buffer.destroy();
162
+ });
163
+ }
164
+ }
165
+
166
+ private _createBuffer(params: WGPUComputePassOutput) {
167
+ // const byteLength = this._width * this._height * params.channels * 4;
168
+ // Buffer data needs to be aligned with 8, 16
169
+ const byteLength = this._width * this._height * 4 * 4;
170
+ return this._device.createBuffer({
171
+ label: this._label,
172
+ // webgpu needs buffer at least 80 bytes.
173
+ size: Math.max(byteLength, 80),
174
+ usage:
175
+ GPUBufferUsage.STORAGE |
176
+ GPUBufferUsage.COPY_DST |
177
+ GPUBufferUsage.COPY_SRC
178
+ });
179
+ }
180
+
181
+ private _resizeOutputBuffers() {
182
+ const outputBuffers = this._outputBuffers;
183
+ for (const key in outputBuffers) {
184
+ const { buffer, params } = outputBuffers[key];
185
+ buffer.destroy();
186
+ outputBuffers[key].buffer = this._createBuffer(params);
187
+ }
188
+ }
189
+
190
+ private _updateOutputBuffers(
191
+ outputParams: Record<string, WGPUComputePassOutput>
192
+ ) {
193
+ const outputBuffers = this._outputBuffers;
194
+ for (const key in outputParams) {
195
+ const params = outputParams[key];
196
+ if (
197
+ !isStorageParamsEqual(
198
+ params,
199
+ outputBuffers[key]?.params || ({} as WGPUComputePassOutput)
200
+ )
201
+ ) {
202
+ outputBuffers[key]?.buffer.destroy();
203
+ const buffer = this._createBuffer(params);
204
+ outputBuffers[key] = {
205
+ buffer,
206
+ params
207
+ };
208
+ }
209
+ }
210
+ }
211
+
212
+ private _updatePipeline(
213
+ inputParams: Record<string, WGPUComputePassInput>,
214
+ inputTypes: Record<string, 'texture' | 'buffer'>
215
+ ) {
216
+ if (!this._needsUpdatePipeline) {
217
+ return;
218
+ }
219
+ this._needsUpdatePipeline = false;
220
+ const device = this._device;
221
+ const csCode = this._getFullCs(inputParams, inputTypes);
222
+ if (csCode === this._csCode) {
223
+ return;
224
+ }
225
+ this._csCode = csCode;
226
+ this._pipeline = device.createComputePipeline({
227
+ label: this._label,
228
+ layout: 'auto',
229
+ compute: {
230
+ module: device.createShaderModule({
231
+ label: this._label,
232
+ code: csCode
233
+ }),
234
+ entryPoint: 'main'
235
+ }
236
+ });
237
+ this._updateBindGroups();
238
+ }
239
+
240
+ private _getFullCs(
241
+ inputParams: Record<string, WGPUComputePassInput>,
242
+ inputTypes: Record<string, 'texture' | 'buffer'>
243
+ ) {
244
+ const inputs = this._inputs;
245
+ const uniforms = this._uniforms;
246
+ let offset = 0;
247
+ const groupOffsets = (this._groupOffsets = {
248
+ inputs: 0,
249
+ uniforms: 0,
250
+ outputs: 0
251
+ });
252
+ if (inputs.length > 0) {
253
+ offset++;
254
+ }
255
+ if (uniforms.length > 0) {
256
+ groupOffsets.uniforms = offset;
257
+ offset++;
258
+ }
259
+ groupOffsets.outputs = offset;
260
+ const cs = `
261
+ ${inputs
262
+ .sort()
263
+ .map((inputName, idx) => {
264
+ const bindingPrefix = `@group(${groupOffsets.inputs}) @binding(${idx}) `;
265
+ const varName = `in_${inputName}`;
266
+ // TODO more channels option.
267
+ return inputTypes[inputName] === 'texture'
268
+ ? `${bindingPrefix} var ${varName}: texture_2d<f32>;`
269
+ : `${bindingPrefix} var<storage, read> ${varName}: array<vec${inputParams[inputName].channels}f>;`;
270
+ })
271
+ .join('\n')}
272
+ ${this._uniforms
273
+ .map(
274
+ (uniform, idx) =>
275
+ `@group(${groupOffsets.uniforms}) @binding(${idx}) var<uniform> ${uniform.label}: ${uniform.type};`
276
+ )
277
+ .join('\n')}
278
+
279
+ ${this._outputs
280
+ .map(
281
+ (name, idx) =>
282
+ `@group(${groupOffsets.outputs}) @binding(${idx}) var<storage, read_write> out_${name}: array<vec${this._outputBuffers[name].params.channels}f>;`
283
+ )
284
+ .join('\n')}
285
+ ${this._csDefine ?? ''}
286
+ @compute @workgroup_size(${WORKGROUP_SIZE}, ${WORKGROUP_SIZE}, 1)
287
+ fn main(@builtin(global_invocation_id) globalId: vec3u) {
288
+ ${this._csMain}
289
+ }
290
+ `;
291
+
292
+ return cs;
293
+ }
294
+
295
+ private _updateBindGroups() {
296
+ const bindGroups: GPUBindGroup[] = [];
297
+ const device = this._device;
298
+ const groupOffsets = this._groupOffsets;
299
+
300
+ if (this._uniforms.length > 0) {
301
+ bindGroups[groupOffsets.uniforms] = device.createBindGroup({
302
+ label: this._label,
303
+ layout: this._pipeline.getBindGroupLayout(groupOffsets.uniforms),
304
+ entries: this._uniforms.map(
305
+ (uniform, idx) =>
306
+ ({
307
+ binding: idx,
308
+ resource: {
309
+ buffer: this._uniformBuffers[uniform.label]
310
+ }
311
+ } as GPUBindGroupEntry)
312
+ )
313
+ });
314
+ }
315
+
316
+ this._bindGroups = bindGroups;
317
+ }
318
+
319
+ createPass(
320
+ commandEncoder: GPUCommandEncoder,
321
+ inputs: Record<I, WGPUComputePassInput>
322
+ ) {
323
+ if (this._needsResizeBuffer && this.autoUpdateOutputBuffer) {
324
+ this._resizeOutputBuffers();
325
+ this._needsResizeBuffer = false;
326
+ }
327
+
328
+ const inputTypes = this._inputs.reduce((obj, inputName) => {
329
+ obj[inputName] = inputs[inputName as I].buffer ? 'buffer' : 'texture';
330
+ return obj;
331
+ }, {} as Record<string, 'texture' | 'buffer'>);
332
+
333
+ this._updatePipeline(inputs, inputTypes);
334
+
335
+ const groupOffsets = this._groupOffsets;
336
+ // TODO createBindGroup every time?
337
+ if (this._inputs.length > 0) {
338
+ this._bindGroups[groupOffsets.inputs] = this._device.createBindGroup({
339
+ label: this._label,
340
+ layout: this._pipeline.getBindGroupLayout(groupOffsets.inputs),
341
+ entries: this._inputs.map((inputName, idx) => ({
342
+ binding: idx,
343
+ // TODO
344
+ resource: inputs[inputName as I].buffer
345
+ ? {
346
+ buffer: inputs[inputName as I].buffer!
347
+ }
348
+ : inputs[inputName as I].texture!.createView()
349
+ }))
350
+ });
351
+ }
352
+
353
+ // Outputs
354
+ this._bindGroups[groupOffsets.outputs] = this._device.createBindGroup({
355
+ label: this._label,
356
+ layout: this._pipeline.getBindGroupLayout(groupOffsets.outputs),
357
+ entries: this._outputs.map((bufferName, idx) => ({
358
+ binding: idx,
359
+ resource: {
360
+ buffer: this._outputBuffers[bufferName].buffer
361
+ }
362
+ }))
363
+ });
364
+
365
+ // Begin the render pass
366
+ const computePass = commandEncoder.beginComputePass();
367
+
368
+ // Draw a full quad
369
+ computePass.setPipeline(this._pipeline);
370
+ // Bind groups
371
+ this._bindGroups.forEach((bindGroup, idx) => {
372
+ computePass.setBindGroup(idx, bindGroup);
373
+ });
374
+ computePass.dispatchWorkgroups(
375
+ Math.ceil((this._execWidth ?? this._width) / WORKGROUP_SIZE),
376
+ Math.ceil((this._execHeight ?? this._height) / WORKGROUP_SIZE),
377
+ 1
378
+ );
379
+ // End the render pass
380
+ computePass.end();
381
+ }
382
+ }
@@ -0,0 +1,348 @@
1
+ export const fullScreenQuadVertexShaderWGSL = /*wgsl */ `
2
+ @vertex
3
+ fn main(
4
+ @builtin(vertex_index) vertexIndex: u32
5
+ ) -> @builtin(position) vec4f {
6
+ const pos = array(
7
+ vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0),
8
+ vec2(-1.0, 1.0), vec2(1.0, -1.0), vec2(1.0, 1.0),
9
+ );
10
+
11
+ return vec4<f32>(pos[vertexIndex], 0.0, 1.0);
12
+ }
13
+ `;
14
+
15
+ function isTextureParamsEqual(
16
+ params: WGPUFullQuadPassOutput,
17
+ other: WGPUFullQuadPassOutput
18
+ ) {
19
+ return params.format === other.format;
20
+ }
21
+
22
+ export interface Uniform {
23
+ label: string;
24
+ type: string;
25
+ data: Float32Array | Int32Array | Uint32Array;
26
+ }
27
+
28
+ export interface WGPUFullQuadPassOutput {
29
+ format: GPUTextureFormat;
30
+ }
31
+ export class WGPUFullQuadPass<I extends string, O extends string> {
32
+ private _label;
33
+
34
+ private _device;
35
+ private _outputTextures: Record<
36
+ string,
37
+ {
38
+ texture: GPUTexture;
39
+ params: WGPUFullQuadPassOutput;
40
+ }
41
+ > = {};
42
+
43
+ private _pipeline!: GPURenderPipeline;
44
+ private _bindGroups: GPUBindGroup[] = [];
45
+ private _needsUpdatePipeline = true;
46
+ /**
47
+ * When render to canvas
48
+ */
49
+ private _renderToScreen?: {
50
+ screenTexture: GPUTexture;
51
+ presentationFormat: GPUTextureFormat;
52
+ };
53
+ private _inputs: string[] = [];
54
+ private _outputs: string[] = [];
55
+ private _uniforms: Uniform[] = [];
56
+ private _uniformBuffers: Record<string, GPUBuffer> = {};
57
+
58
+ private _width = 10;
59
+ private _height = 10;
60
+
61
+ private _fsCode = '';
62
+ private _fsMain;
63
+ private _fsDefine;
64
+
65
+ constructor(
66
+ label: string,
67
+ device: GPUDevice,
68
+ opts: {
69
+ inputs: I[];
70
+ outputs: O[];
71
+ fsMain: string;
72
+ fsDefine?: string;
73
+ uniforms: Uniform[];
74
+ }
75
+ ) {
76
+ this._label = label;
77
+ this._device = device;
78
+ this._fsMain = opts.fsMain;
79
+ this._fsDefine = opts.fsDefine;
80
+ this._inputs = opts.inputs;
81
+ this._outputs = opts.outputs;
82
+ this._uniforms = opts.uniforms;
83
+
84
+ opts.uniforms.forEach((uniform) => {
85
+ this._uniformBuffers[uniform.label] = device.createBuffer({
86
+ label: this._label,
87
+ size: uniform.data.byteLength,
88
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
89
+ });
90
+ this._device.queue.writeBuffer(
91
+ this._uniformBuffers[uniform.label],
92
+ 0,
93
+ uniform.data
94
+ );
95
+ });
96
+ }
97
+
98
+ setSize(width: number, height: number) {
99
+ width = Math.ceil(width);
100
+ height = Math.ceil(height);
101
+ const sizeChanged = width !== this._width || height !== this._height;
102
+ this._width = width;
103
+ this._height = height;
104
+ if (sizeChanged) {
105
+ this._resizeOutputTextures();
106
+ this._needsUpdatePipeline = true;
107
+ }
108
+ }
109
+
110
+ setOutputParams(outputParams: Record<O, WGPUFullQuadPassOutput>) {
111
+ this._renderToScreen = undefined;
112
+ this._updateOutputTextures(outputParams);
113
+ this._needsUpdatePipeline = true;
114
+ }
115
+
116
+ setRenderToScreen(
117
+ screenTexture: GPUTexture,
118
+ presentationFormat: GPUTextureFormat
119
+ ) {
120
+ this._renderToScreen = {
121
+ screenTexture,
122
+ presentationFormat
123
+ };
124
+ }
125
+
126
+ setUniform(label: string, data: Float32Array | Int32Array | Uint32Array) {
127
+ const buffer = this._uniformBuffers[label];
128
+ this._device.queue.writeBuffer(buffer, 0, data);
129
+ }
130
+
131
+ getOutputTexture(name: O) {
132
+ return this._outputTextures[name].texture;
133
+ }
134
+
135
+ dispose() {
136
+ Object.keys(this._uniformBuffers).forEach((key) => {
137
+ (this._uniformBuffers as any)[key].destroy();
138
+ });
139
+ Object.keys(this._outputTextures).forEach((key) => {
140
+ (this._outputTextures as any)[key].texture.destroy();
141
+ });
142
+ }
143
+
144
+ private _createTexture(params: WGPUFullQuadPassOutput) {
145
+ return this._device.createTexture({
146
+ label: this._label,
147
+ size: {
148
+ width: this._width,
149
+ height: this._height,
150
+ depthOrArrayLayers: 1
151
+ },
152
+ format: params.format,
153
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING
154
+ });
155
+ }
156
+
157
+ private _resizeOutputTextures() {
158
+ const outputTextures = this._outputTextures;
159
+ for (const key in outputTextures) {
160
+ const { texture, params } = outputTextures[key];
161
+ texture.destroy();
162
+ outputTextures[key].texture = this._createTexture(params);
163
+ }
164
+ }
165
+
166
+ private _updateOutputTextures(
167
+ outputParams: Record<string, WGPUFullQuadPassOutput>
168
+ ) {
169
+ const outputTextures = this._outputTextures;
170
+ for (const key in outputParams) {
171
+ const params = outputParams[key];
172
+ if (
173
+ !isTextureParamsEqual(
174
+ params,
175
+ outputTextures[key]?.params || ({} as WGPUFullQuadPassOutput)
176
+ )
177
+ ) {
178
+ outputTextures[key]?.texture.destroy();
179
+ const texture = this._createTexture(params);
180
+ outputTextures[key] = {
181
+ texture,
182
+ params
183
+ };
184
+ }
185
+ }
186
+ }
187
+
188
+ private _updatePipeline() {
189
+ if (!this._needsUpdatePipeline) {
190
+ return;
191
+ }
192
+ this._needsUpdatePipeline = false;
193
+ const device = this._device;
194
+ const fsCode = this._getFullFs();
195
+ if (fsCode === this._fsCode) {
196
+ return;
197
+ }
198
+ const { screenTexture, presentationFormat } = this._renderToScreen || {};
199
+ this._fsCode = fsCode;
200
+ this._pipeline = device.createRenderPipeline({
201
+ label: this._label,
202
+ layout: 'auto',
203
+ vertex: {
204
+ module: device.createShaderModule({
205
+ label: this._label,
206
+ code: fullScreenQuadVertexShaderWGSL
207
+ }),
208
+ entryPoint: 'main'
209
+ },
210
+ fragment: {
211
+ module: device.createShaderModule({
212
+ label: this._label,
213
+ code: fsCode
214
+ }),
215
+ entryPoint: 'main',
216
+ targets: screenTexture
217
+ ? [
218
+ {
219
+ format: presentationFormat!
220
+ }
221
+ ]
222
+ : this._outputs.map((key) => ({
223
+ format: this._outputTextures[key].params.format
224
+ }))
225
+ },
226
+ primitive: {
227
+ topology: 'triangle-list'
228
+ }
229
+ });
230
+ this._updateBindGroups();
231
+ }
232
+
233
+ private _getFullFs() {
234
+ const inputs = this._inputs;
235
+ const hasInputs = inputs.length > 0;
236
+ const fs = `
237
+ ${inputs
238
+ .sort()
239
+ .map(
240
+ (textureName, idx) =>
241
+ `@group(0) @binding(${idx}) var ${textureName}: texture_2d<f32>;`
242
+ )
243
+ .join('\n')}
244
+ ${this._uniforms
245
+ .map(
246
+ (uniform, idx) =>
247
+ `@group(${hasInputs ? 1 : 0}) @binding(${idx}) var<uniform> ${
248
+ uniform.label
249
+ }: ${uniform.type};`
250
+ )
251
+ .join('\n')}
252
+
253
+ struct FSOutput {
254
+ ${this._outputs
255
+ .map((name, idx) => `@location(${idx}) ${name}: vec4f,`)
256
+ .join('\n')}
257
+ }
258
+ ${this._fsDefine ?? ''}
259
+ @fragment
260
+ fn main(
261
+ @builtin(position) coord: vec4f
262
+ ) -> FSOutput {
263
+ var uv = vec2i(floor(coord.xy));
264
+ var output: FSOutput;
265
+ ${this._fsMain}
266
+ return output;
267
+ }
268
+ `;
269
+
270
+ return fs;
271
+ }
272
+
273
+ private _updateBindGroups() {
274
+ const bindGroups: GPUBindGroup[] = [];
275
+ const device = this._device;
276
+
277
+ //TODO
278
+ const uniformBindGroupIndex = this._inputs.length > 0 ? 1 : 0;
279
+ if (this._uniforms.length > 0) {
280
+ bindGroups[uniformBindGroupIndex] = device.createBindGroup({
281
+ label: this._label,
282
+ layout: this._pipeline.getBindGroupLayout(uniformBindGroupIndex),
283
+ entries: this._uniforms.map(
284
+ (uniform, idx) =>
285
+ ({
286
+ binding: idx,
287
+ resource: {
288
+ buffer: this._uniformBuffers[uniform.label]
289
+ }
290
+ } satisfies GPUBindGroupEntry)
291
+ )
292
+ });
293
+ }
294
+
295
+ this._bindGroups = bindGroups;
296
+ }
297
+
298
+ createPass(
299
+ commandEncoder: GPUCommandEncoder,
300
+ inputTextures: Record<I, GPUTexture>
301
+ ) {
302
+ this._updatePipeline();
303
+
304
+ // TODO createBindGrou every time?
305
+ if (this._inputs.length > 0) {
306
+ this._bindGroups[0] = this._device.createBindGroup({
307
+ label: this._label,
308
+ layout: this._pipeline.getBindGroupLayout(0),
309
+ entries: this._inputs.map((textureName, idx) => ({
310
+ binding: idx,
311
+ // TODO
312
+ resource: inputTextures[textureName as I].createView()
313
+ }))
314
+ });
315
+ }
316
+ // Begin the render pass
317
+ const renderPass = commandEncoder.beginRenderPass({
318
+ colorAttachments: this._renderToScreen
319
+ ? [
320
+ {
321
+ view: this._renderToScreen.screenTexture.createView(),
322
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
323
+ storeOp: 'store' as GPUStoreOp,
324
+ loadOp: 'clear' as GPULoadOp
325
+ }
326
+ ]
327
+ : this._outputs.map(
328
+ (textureName) =>
329
+ ({
330
+ view: this._outputTextures[textureName].texture.createView(),
331
+ clearValue: { r: 0, g: 0, b: 0, a: 0 },
332
+ loadOp: 'clear' as GPULoadOp,
333
+ storeOp: 'store' as GPUStoreOp
334
+ } satisfies GPURenderPassColorAttachment)
335
+ )
336
+ });
337
+
338
+ // Draw a full quad
339
+ renderPass.setPipeline(this._pipeline);
340
+ // Bind groups
341
+ this._bindGroups.forEach((bindGroup, idx) => {
342
+ renderPass.setBindGroup(idx, bindGroup);
343
+ });
344
+ renderPass.draw(6, 1, 0, 0);
345
+ // End the render pass
346
+ renderPass.end();
347
+ }
348
+ }