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
package/src/process.ts ADDED
@@ -0,0 +1,368 @@
1
+ import { WGPUComputePass } from './WGPUComputePass';
2
+ import {
3
+ hdrForwardWGSL,
4
+ hdrInverseWGSL,
5
+ hdrTransferFuncCPU,
6
+ hdrTransferFuncInverseCPU,
7
+ type HDRTransfer
8
+ } from './hdrTransfer';
9
+ import { sanitizeWGSL } from './sanitize';
10
+ export { avgLogLum, hdrTransferFuncCPU, hdrTransferFuncInverseCPU } from './hdrTransfer';
11
+ export type { HDRTransfer } from './hdrTransfer';
12
+
13
+ export class Tile {
14
+ constructor(
15
+ public x: number,
16
+ public y: number,
17
+ public width: number,
18
+ public height: number
19
+ ) {}
20
+ }
21
+
22
+ export class GPUDataProcess {
23
+ private _inputPassAux;
24
+ private _inputPassColor;
25
+ private _outputPass;
26
+ private _copyPass;
27
+
28
+ private _isInputTexture?: boolean;
29
+
30
+ constructor(
31
+ private _device: GPUDevice,
32
+ private _isHDR: boolean,
33
+ private _hdrTransfer: HDRTransfer = 'pu'
34
+ ) {
35
+ const commonUniforms = [
36
+ {
37
+ label: 'inputScale',
38
+ type: 'f32',
39
+ data: new Float32Array([1])
40
+ },
41
+ {
42
+ label: 'inputSize',
43
+ type: 'vec2i',
44
+ data: new Int32Array(2)
45
+ },
46
+ {
47
+ label: 'outputSize',
48
+ type: 'vec2i',
49
+ data: new Int32Array(2)
50
+ },
51
+ {
52
+ label: 'inputOffset',
53
+ type: 'vec2i',
54
+ data: new Int32Array(2)
55
+ }
56
+ ];
57
+ this._inputPassAux = new WGPUComputePass('inputPassAux', this._device, {
58
+ inputs: ['color', 'albedo', 'normal'],
59
+ outputs: ['color', 'albedo', 'normal'],
60
+ uniforms: commonUniforms,
61
+ csDefine: '',
62
+ csMain: ``
63
+ });
64
+ this._inputPassColor = new WGPUComputePass('inputPassColor', this._device, {
65
+ inputs: ['color'],
66
+ outputs: ['color'],
67
+ uniforms: commonUniforms,
68
+ csDefine: '',
69
+ csMain: ``
70
+ });
71
+ this._outputPass = new WGPUComputePass('outputPass', this._device, {
72
+ inputs: ['color', 'raw'],
73
+ outputs: ['color'],
74
+ uniforms: [
75
+ {
76
+ label: 'inputScale',
77
+ type: 'f32',
78
+ data: new Float32Array([1])
79
+ },
80
+ {
81
+ label: 'inputSize',
82
+ type: 'vec2i',
83
+ data: new Int32Array(2)
84
+ },
85
+ {
86
+ label: 'outputSize',
87
+ type: 'vec2i',
88
+ data: new Int32Array(2)
89
+ },
90
+ {
91
+ label: 'imageSize',
92
+ type: 'vec2i',
93
+ data: new Int32Array(2)
94
+ },
95
+ {
96
+ label: 'inputOffset',
97
+ type: 'vec2i',
98
+ data: new Int32Array(2)
99
+ },
100
+ {
101
+ label: 'outputOffset',
102
+ type: 'vec2i',
103
+ data: new Int32Array(2)
104
+ }
105
+ ],
106
+ csDefine: '',
107
+ csMain: ``
108
+ });
109
+ this._copyPass = new WGPUComputePass('copyPass', this._device, {
110
+ inputs: ['color'],
111
+ outputs: ['color'],
112
+ autoUpdateOutputBuffer: false,
113
+ uniforms: [
114
+ {
115
+ label: 'size',
116
+ type: 'vec2i',
117
+ data: new Int32Array(2)
118
+ }
119
+ ],
120
+ csMain: /*wgsl*/ `
121
+ let outIdx = i32(globalId.x + globalId.y * u32(size.x));
122
+ out_color[outIdx] = textureLoad(in_color, globalId.xy, 0);
123
+ `
124
+ });
125
+ this._inputPassAux.setOutputParams({
126
+ color: { channels: 3 },
127
+ albedo: { channels: 3 },
128
+ normal: { channels: 3 }
129
+ });
130
+ this._inputPassColor.setOutputParams({
131
+ color: { channels: 3 }
132
+ });
133
+ this._outputPass.setOutputParams({
134
+ color: { channels: 4 }
135
+ });
136
+ }
137
+
138
+ private _updatePasses(isInputTexture: boolean, denoiseAlpha = false) {
139
+ if (
140
+ this._isInputTexture != undefined &&
141
+ this._isInputTexture === isInputTexture
142
+ ) {
143
+ return;
144
+ }
145
+
146
+ this._isInputTexture = isInputTexture;
147
+ const isHDR = this._isHDR;
148
+ const commonCSDefine = hdrForwardWGSL(this._hdrTransfer) + sanitizeWGSL;
149
+ function readInputCode(inputName: string) {
150
+ return isInputTexture
151
+ ? `textureLoad(in_${inputName}, vec2u(inputPosition), 0)`
152
+ : `in_${inputName}[inIdx]`;
153
+ }
154
+ const commonCSMain = /* wgsl */ `
155
+ let x = i32(globalId.x);
156
+ let y = i32(globalId.y);
157
+ let inputPosition = clamp(
158
+ vec2i(x, y) + inputOffset,
159
+ vec2i(0),
160
+ inputSize - vec2i(1)
161
+ );
162
+ let inIdx = inputPosition.y * inputSize.x + inputPosition.x;
163
+ let col = ${readInputCode('color')};
164
+
165
+ let outIdx = y * outputSize.x + x;
166
+
167
+ if (${denoiseAlpha}) {
168
+ // Denoise the inversed alpha. Or the anti aliased edge will be too dark after denoised
169
+ out_color[outIdx] = sanitize(vec3f(1.0 - col.a), 0.0, 1.0);
170
+ }
171
+ else if (${isHDR}) {
172
+ let scaled = sanitize(col.rgb * inputScale, 0.0, FLT_MAX);
173
+ out_color[outIdx] = vec3f(HDRForward(scaled.r), HDRForward(scaled.g), HDRForward(scaled.b));
174
+ }
175
+ else {
176
+ out_color[outIdx] = sanitize(col.rgb, 0.0, 1.0);
177
+ }
178
+ `;
179
+ this._inputPassAux.setCSCode({
180
+ csDefine: commonCSDefine,
181
+ csMain: /* wgsl */ `
182
+ ${commonCSMain}
183
+ let alb = ${readInputCode('albedo')};
184
+ let nor = ${readInputCode('normal')};
185
+ // Normals arrive [0, 1]-encoded, so OIDN's [-1, 1] clamp becomes [0, 1].
186
+ out_normal[outIdx] = sanitize(nor.rgb, 0.0, 1.0);
187
+ out_albedo[outIdx] = sanitize(alb.rgb, 0.0, 1.0);
188
+ `
189
+ });
190
+
191
+ this._inputPassColor.setCSCode({
192
+ csDefine: commonCSDefine,
193
+ csMain: /* wgsl */ `
194
+ ${commonCSMain}
195
+ `
196
+ });
197
+ this._outputPass.setCSCode({
198
+ csDefine: hdrInverseWGSL(this._hdrTransfer) + sanitizeWGSL,
199
+ csMain: /* wgsl */ `
200
+ let x = i32(globalId.x);
201
+ let y = i32(globalId.y);
202
+ if (x >= outputSize.x || y >= outputSize.y) {
203
+ return;
204
+ }
205
+ let inIdx = (y + inputOffset.y) * inputSize.x + x + inputOffset.x;
206
+ let outIdx = (y + outputOffset.y) * imageSize.x + x + outputOffset.x;
207
+ let col = in_color[inIdx];
208
+ let raw = ${
209
+ isInputTexture
210
+ ? 'textureLoad(in_raw, globalId.xy + vec2u(outputOffset), 0)'
211
+ : 'in_raw[outIdx]'
212
+ };
213
+
214
+ // The network output may contain negative values or NaNs.
215
+ let denoised = sanitize(col.rgb, 0.0, ${isHDR && !denoiseAlpha ? 'FLT_MAX' : '1.0'});
216
+ if (${denoiseAlpha}) {
217
+ out_color[outIdx] = vec4f(raw.rgb, 1.0 - denoised.r);
218
+ }
219
+ else if (${isHDR}) {
220
+ out_color[outIdx] = vec4f(
221
+ vec3f(HDRInverse(denoised.r), HDRInverse(denoised.g), HDRInverse(denoised.b)) / inputScale,
222
+ // Pick the alpha
223
+ raw.a
224
+ );
225
+ }
226
+ else {
227
+ out_color[outIdx] = vec4f(denoised, raw.a);
228
+ }
229
+ `
230
+ });
231
+ }
232
+
233
+ setImageSize(w: number, h: number) {
234
+ this._inputPassAux.setUniform('inputSize', new Int32Array([w, h]));
235
+ this._inputPassColor.setUniform('inputSize', new Int32Array([w, h]));
236
+ this._outputPass.setUniform('imageSize', new Int32Array([w, h]));
237
+ this._outputPass.setSize(w, h);
238
+ this._copyPass.setSize(w, h);
239
+ this._copyPass.setUniform('size', new Int32Array([w, h]));
240
+ }
241
+
242
+ setInputTile(tile: Tile) {
243
+ const size = new Int32Array([tile.width, tile.height]);
244
+ [this._inputPassAux, this._inputPassColor].forEach((inputPass) => {
245
+ inputPass.setUniform('inputOffset', new Int32Array([tile.x, tile.y]));
246
+ inputPass.setUniform('outputSize', size);
247
+ inputPass.setSize(size[0], size[1]);
248
+ });
249
+
250
+ this._outputPass.setUniform('inputSize', size);
251
+ }
252
+
253
+ setOutputTile(dstTile: Tile, srcTile: Tile) {
254
+ const outputPass = this._outputPass;
255
+ const size = new Int32Array([dstTile.width, dstTile.height]);
256
+ const dx = dstTile.x - srcTile.x;
257
+ const dy = dstTile.y - srcTile.y;
258
+ outputPass.setUniform('outputSize', size);
259
+ outputPass.setUniform('inputOffset', new Int32Array([dx, dy]));
260
+ outputPass.setUniform(
261
+ 'outputOffset',
262
+ new Int32Array([dstTile.x, dstTile.y])
263
+ );
264
+ outputPass.setExecuteSize(size[0], size[1]);
265
+ }
266
+
267
+ forward(
268
+ colorBuffer: GPUBuffer | GPUTexture,
269
+ // TODO optional albedo and normal.
270
+ albedoBuffer: GPUBuffer | GPUTexture | undefined,
271
+ normalBuffer: GPUBuffer | GPUTexture | undefined,
272
+
273
+ denoiseAlpha: boolean | undefined
274
+ ) {
275
+ const isInputTexture = colorBuffer instanceof GPUTexture;
276
+ this._updatePasses(isInputTexture, denoiseAlpha);
277
+
278
+ const inputPassAux = this._inputPassAux;
279
+ const inputPassColor = this._inputPassColor;
280
+ const commandEncoder = this._device.createCommandEncoder();
281
+
282
+ function createInput(bufferOrTex: GPUBuffer | GPUTexture) {
283
+ return bufferOrTex instanceof GPUTexture
284
+ ? {
285
+ texture: bufferOrTex,
286
+ channels: 4
287
+ }
288
+ : {
289
+ buffer: bufferOrTex,
290
+ channels: 4
291
+ };
292
+ }
293
+ if (albedoBuffer && normalBuffer) {
294
+ inputPassAux.createPass(commandEncoder, {
295
+ color: createInput(colorBuffer),
296
+ albedo: createInput(albedoBuffer),
297
+ normal: createInput(normalBuffer)
298
+ });
299
+ } else {
300
+ inputPassColor.createPass(commandEncoder, {
301
+ color: createInput(colorBuffer)
302
+ });
303
+ }
304
+ this._device.queue.submit([commandEncoder.finish()]);
305
+
306
+ return albedoBuffer && normalBuffer
307
+ ? {
308
+ color: inputPassAux.getOutput('color'),
309
+ albedo: inputPassAux.getOutput('albedo'),
310
+ normal: inputPassAux.getOutput('normal')
311
+ }
312
+ : {
313
+ color: inputPassColor.getOutput('color')
314
+ };
315
+ }
316
+
317
+ inverse(buffer: GPUBuffer, raw: GPUBuffer | GPUTexture) {
318
+ const device = this._device;
319
+
320
+ const commandEncoder = device.createCommandEncoder();
321
+ const outputGPUPass = this._outputPass;
322
+
323
+ outputGPUPass.createPass(commandEncoder, {
324
+ color: { buffer: buffer, channels: 4 },
325
+ raw:
326
+ raw instanceof GPUBuffer
327
+ ? { buffer: raw, channels: 4 }
328
+ : { texture: raw, channels: 4 }
329
+ });
330
+ this._device.queue.submit([commandEncoder.finish()]);
331
+
332
+ return outputGPUPass.getOutput('color');
333
+ }
334
+
335
+ copyInputDataToOutput(inputColorBuffer: GPUBuffer | GPUTexture) {
336
+ const encoder = this._device.createCommandEncoder();
337
+ const outputGPUPass = this._outputPass;
338
+ const colorBuffer = outputGPUPass.getOutput('color');
339
+ const copyPass = this._copyPass;
340
+
341
+ if (inputColorBuffer instanceof GPUTexture) {
342
+ copyPass.setOutputBuffers({
343
+ color: colorBuffer
344
+ });
345
+ copyPass.createPass(encoder, {
346
+ color: { texture: inputColorBuffer, channels: 4 }
347
+ });
348
+ } else {
349
+ encoder.copyBufferToBuffer(
350
+ inputColorBuffer,
351
+ 0,
352
+ colorBuffer,
353
+ 0,
354
+ colorBuffer.size
355
+ );
356
+ }
357
+
358
+ this._device.queue.submit([encoder.finish()]);
359
+ }
360
+
361
+ dispose() {
362
+ this._outputPass.dispose();
363
+ this._inputPassAux.dispose();
364
+ this._inputPassColor.dispose();
365
+ // copyPass borrows outputPass's target and must not destroy it again.
366
+ this._copyPass.dispose(false);
367
+ }
368
+ }
@@ -0,0 +1,94 @@
1
+ export type OIDNResourceKind =
2
+ | 'gpu-buffer'
3
+ | 'gpu-query-set'
4
+ | 'ml-context'
5
+ | 'ml-graph'
6
+ | 'ml-tensor';
7
+
8
+ export interface OIDNResourceStats {
9
+ created: number;
10
+ destroyed: number;
11
+ live: number;
12
+ peakLive: number;
13
+ }
14
+
15
+ export interface OIDNResourceSnapshot {
16
+ live: number;
17
+ created: number;
18
+ destroyed: number;
19
+ peakLive: number;
20
+ pending: number;
21
+ byKind: Partial<Record<OIDNResourceKind, OIDNResourceStats>>;
22
+ }
23
+
24
+ interface MutableResourceStats extends OIDNResourceStats {
25
+ resources: Set<object>;
26
+ }
27
+
28
+ /**
29
+ * Counts explicitly owned browser resources. Keeping the ownership registry in
30
+ * production makes cache eviction and failure cleanup testable without relying
31
+ * on JS heap measurements, which do not include WebGPU allocations.
32
+ */
33
+ export class OIDNResourceTracker {
34
+ private _stats = new Map<OIDNResourceKind, MutableResourceStats>();
35
+
36
+ track<T extends object>(kind: OIDNResourceKind, resource: T): T {
37
+ let stats = this._stats.get(kind);
38
+ if (!stats) {
39
+ stats = {
40
+ created: 0,
41
+ destroyed: 0,
42
+ live: 0,
43
+ peakLive: 0,
44
+ resources: new Set()
45
+ };
46
+ this._stats.set(kind, stats);
47
+ }
48
+ if (stats.resources.has(resource)) return resource;
49
+ stats.resources.add(resource);
50
+ stats.created++;
51
+ stats.live++;
52
+ stats.peakLive = Math.max(stats.peakLive, stats.live);
53
+ return resource;
54
+ }
55
+
56
+ release(
57
+ kind: OIDNResourceKind,
58
+ resource: object | undefined,
59
+ destroy: () => void
60
+ ) {
61
+ if (!resource) return false;
62
+ const stats = this._stats.get(kind);
63
+ if (!stats?.resources.delete(resource)) return false;
64
+ try {
65
+ destroy();
66
+ } finally {
67
+ stats.destroyed++;
68
+ stats.live--;
69
+ }
70
+ return true;
71
+ }
72
+
73
+ snapshot(pending = 0): OIDNResourceSnapshot {
74
+ let live = 0;
75
+ let created = 0;
76
+ let destroyed = 0;
77
+ let peakLive = 0;
78
+ const byKind: OIDNResourceSnapshot['byKind'] = {};
79
+ for (const [kind, stats] of this._stats) {
80
+ const entry = {
81
+ created: stats.created,
82
+ destroyed: stats.destroyed,
83
+ live: stats.live,
84
+ peakLive: stats.peakLive
85
+ };
86
+ byKind[kind] = entry;
87
+ live += entry.live;
88
+ created += entry.created;
89
+ destroyed += entry.destroyed;
90
+ peakLive += entry.peakLive;
91
+ }
92
+ return { live, created, destroyed, peakLive, pending, byKind };
93
+ }
94
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * OIDN's input and output sanitizing: NaN becomes 0, then values are clamped
3
+ * to their valid range. HDR values are bounded by FLT_MAX, so +inf becomes
4
+ * FLT_MAX. Used before the network, by auto-exposure, and on the network
5
+ * output before the inverse transfer.
6
+ */
7
+ export const FLT_MAX = 3.4028234663852886e38;
8
+
9
+ export function sanitize(value: number, lo: number, hi: number) {
10
+ return Number.isNaN(value) ? 0 : Math.min(Math.max(value, lo), hi);
11
+ }
12
+
13
+ /**
14
+ * WGSL declarations of `FLT_MAX` and `fn sanitize(v: vec3f, lo: f32, hi: f32)`.
15
+ * WGSL compilers may assume floats are finite, so NaN and infinity are
16
+ * detected from the bits rather than with comparisons. FLT_MAX is spelled out
17
+ * so the native core does not depend on scriptc's number formatting.
18
+ */
19
+ export const sanitizeWGSL = /* wgsl */ `
20
+ const FLT_MAX = 3.4028234663852886e38;
21
+ fn sanitize(v: vec3f, lo: f32, hi: f32) -> vec3f {
22
+ let bits = bitcast<vec3u>(v);
23
+ let magnitude = bits & vec3u(0x7fffffffu);
24
+ let negative = (bits & vec3u(0x80000000u)) != vec3u(0u);
25
+ let infinite = select(vec3f(hi), vec3f(lo), negative);
26
+ let clamped = select(clamp(v, vec3f(lo), vec3f(hi)), infinite, magnitude == vec3u(0x7f800000u));
27
+ return select(clamped, vec3f(0.0), magnitude > vec3u(0x7f800000u));
28
+ }
29
+ `;