rayzee 7.23.2 → 7.24.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 +96 -17
- package/dist/rayzee.es.js +162 -118
- package/dist/rayzee.umd.js +51 -51
- package/package.json +2 -2
- package/src/Passes/AIUpscaler.js +16 -8
- package/src/Passes/OIDNDenoiser.js +113 -54
- package/src/PathTracerApp.js +33 -0
- package/src/managers/DenoisingManager.js +18 -16
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rayzee",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.24.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Real-time WebGPU path tracing engine built on Three.js",
|
|
6
6
|
"main": "dist/rayzee.umd.js",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"three": ">=0.185.0"
|
|
39
39
|
},
|
|
40
40
|
"optionalDependencies": {
|
|
41
|
-
"oidn-web": ">=0.
|
|
41
|
+
"oidn-web": ">=0.4.0",
|
|
42
42
|
"onnxruntime-web": ">=1.0.0"
|
|
43
43
|
}
|
|
44
44
|
}
|
package/src/Passes/AIUpscaler.js
CHANGED
|
@@ -107,6 +107,8 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
107
107
|
this.hdr = false;
|
|
108
108
|
this.scaleFactor = options.scaleFactor || 2;
|
|
109
109
|
this.quality = options.quality || 'fast';
|
|
110
|
+
// refreshInput: () => void — re-renders the compositor so the WebGPU canvas is readable
|
|
111
|
+
this.refreshInput = options.refreshInput || null;
|
|
110
112
|
this.tileSize = options.tileSize || MODEL_CONFIG.TILE_SIZE;
|
|
111
113
|
this._tileSizeOverride = !! options.tileSize;
|
|
112
114
|
|
|
@@ -254,6 +256,14 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
254
256
|
|
|
255
257
|
}
|
|
256
258
|
|
|
259
|
+
// See OIDNDenoiser._revealOutput. Here the resize below also clears the bitmap.
|
|
260
|
+
_revealOutput() {
|
|
261
|
+
|
|
262
|
+
this.output.style.display = 'block';
|
|
263
|
+
this.input.style.opacity = '0';
|
|
264
|
+
|
|
265
|
+
}
|
|
266
|
+
|
|
257
267
|
async execute() {
|
|
258
268
|
|
|
259
269
|
if ( ! this.enabled ) return false;
|
|
@@ -261,10 +271,6 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
261
271
|
this.state.abortController = new AbortController();
|
|
262
272
|
this.state.isUpscaling = true;
|
|
263
273
|
|
|
264
|
-
// Show output canvas and hide input (matching OIDN lifecycle)
|
|
265
|
-
this.input.style.opacity = '0';
|
|
266
|
-
this.output.style.display = 'block';
|
|
267
|
-
|
|
268
274
|
// Capture source image SYNCHRONOUSLY before any async work.
|
|
269
275
|
// WebGPU canvas textures expire after each compositor frame,
|
|
270
276
|
// so we must grab the pixels before awaiting model load.
|
|
@@ -292,6 +298,7 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
292
298
|
ctx.imageSmoothingEnabled = true;
|
|
293
299
|
ctx.imageSmoothingQuality = 'high';
|
|
294
300
|
ctx.drawImage( this._backupCanvas, 0, 0, outW, outH );
|
|
301
|
+
this._revealOutput();
|
|
295
302
|
|
|
296
303
|
try {
|
|
297
304
|
|
|
@@ -467,10 +474,11 @@ export class AIUpscaler extends EventDispatcher {
|
|
|
467
474
|
|
|
468
475
|
if ( sourceCanvas && sourceCanvas !== this.output ) {
|
|
469
476
|
|
|
470
|
-
// Source is the WebGPU canvas — must copy to 2D canvas first
|
|
471
|
-
//
|
|
472
|
-
//
|
|
473
|
-
//
|
|
477
|
+
// Source is the WebGPU canvas — must copy to a 2D canvas first, and it only reads
|
|
478
|
+
// back non-empty straight after a compositor pass. Enforced here rather than left to
|
|
479
|
+
// the caller: any awaited GPU work in between silently yields a fully transparent
|
|
480
|
+
// capture, and the upscaler would then upscale nothing at all.
|
|
481
|
+
this.refreshInput?.();
|
|
474
482
|
const offscreen = document.createElement( 'canvas' );
|
|
475
483
|
offscreen.width = sourceCanvas.width;
|
|
476
484
|
offscreen.height = sourceCanvas.height;
|
|
@@ -4,17 +4,11 @@ import { createLogger, fmt } from '../utils/Logger.js';
|
|
|
4
4
|
const log = createLogger( 'oidn' );
|
|
5
5
|
|
|
6
6
|
let _initUNetFromURL = null;
|
|
7
|
-
let _tfEngine = null;
|
|
8
7
|
async function getInitUNetFromURL() {
|
|
9
8
|
|
|
10
9
|
if ( ! _initUNetFromURL ) {
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
import( 'oidn-web' ),
|
|
14
|
-
import( '@tensorflow/tfjs-core' )
|
|
15
|
-
] );
|
|
16
|
-
_initUNetFromURL = oidnMod.initUNetFromURL;
|
|
17
|
-
_tfEngine = tfMod.engine;
|
|
11
|
+
_initUNetFromURL = ( await import( 'oidn-web' ) ).initUNetFromURL;
|
|
18
12
|
|
|
19
13
|
}
|
|
20
14
|
|
|
@@ -22,29 +16,6 @@ async function getInitUNetFromURL() {
|
|
|
22
16
|
|
|
23
17
|
}
|
|
24
18
|
|
|
25
|
-
// oidn-web caches its WebGPUBackend in TFJS's global ENGINE under 'webgpu-oidn'.
|
|
26
|
-
// On dispose, drop it so the next instance binds to the new GPUDevice instead of
|
|
27
|
-
// reusing the destroyed one (which would produce black tiles).
|
|
28
|
-
function removeOidnTfjsBackend() {
|
|
29
|
-
|
|
30
|
-
if ( ! _tfEngine ) return;
|
|
31
|
-
try {
|
|
32
|
-
|
|
33
|
-
const eng = _tfEngine();
|
|
34
|
-
if ( eng?.registryFactory && 'webgpu-oidn' in eng.registryFactory ) {
|
|
35
|
-
|
|
36
|
-
eng.removeBackend( 'webgpu-oidn' );
|
|
37
|
-
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
} catch ( e ) {
|
|
41
|
-
|
|
42
|
-
log.warn( 'failed to clear cached TFJS backend', e );
|
|
43
|
-
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
}
|
|
47
|
-
|
|
48
19
|
import { TONE_MAP_FNS, linearToSRGB, applySaturation } from '../Processor/ToneMapCPU.js';
|
|
49
20
|
import { getAssetConfig } from '../AssetConfig.js';
|
|
50
21
|
|
|
@@ -52,21 +23,29 @@ import { getAssetConfig } from '../AssetConfig.js';
|
|
|
52
23
|
const _tmOut = new Float32Array( 3 );
|
|
53
24
|
|
|
54
25
|
const MODEL_CONFIG = {
|
|
55
|
-
// No cleanAux flag in oidn-web, so the blob is it — and must match what setCleanAuxNormal
|
|
56
|
-
// feeds: `
|
|
57
|
-
// is
|
|
26
|
+
// No cleanAux flag in oidn-web, so the blob is it — and it must match what setCleanAuxNormal
|
|
27
|
+
// feeds the aux MRT: an `alb_nrm` model wants the point-sampled normal, a `calb_cnrm` model
|
|
28
|
+
// wants the accumulated one. `cleanAux` is carried here rather than inferred from the tier
|
|
29
|
+
// name, which stopped being possible once a tier other than `fast` used a noisy-aux model.
|
|
30
|
+
//
|
|
31
|
+
// `fast-clean` is the same topology and channel width as `fast` (both 641 KB, widest conv 64)
|
|
32
|
+
// so it costs the same to run, and only differs in being trained for a clean guide.
|
|
58
33
|
QUALITY_MODELS: {
|
|
59
|
-
fast: 'rt_hdr_alb_nrm_small',
|
|
60
|
-
|
|
61
|
-
|
|
34
|
+
fast: { model: 'rt_hdr_alb_nrm_small', cleanAux: false },
|
|
35
|
+
'fast-clean': { model: 'rt_hdr_calb_cnrm_small', cleanAux: true },
|
|
36
|
+
balance: { model: 'rt_hdr_calb_cnrm', cleanAux: true },
|
|
37
|
+
high: { model: 'rt_hdr_calb_cnrm_large', cleanAux: true }
|
|
62
38
|
},
|
|
63
39
|
DEFAULT_OPTIONS: {
|
|
64
40
|
enableOIDN: true,
|
|
65
41
|
oidnQuality: 'fast',
|
|
66
42
|
debugGbufferMaps: true,
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
tileSize:
|
|
43
|
+
// A cap, not a fixed size — the effective tile is min( max( w, h ), this ), so a frame
|
|
44
|
+
// that fits in one tile pays no overlap padding at all. Every tile otherwise runs at
|
|
45
|
+
// tileSize + 2*overlap (96px, 112 for _large): at 1024²/high, 4x512 tiles measured
|
|
46
|
+
// 422ms against 229ms for one 1024 tile. 1024 caps the one-time activation
|
|
47
|
+
// allocation at ~430MB; beyond it larger frames tile and stay bounded.
|
|
48
|
+
tileSize: 1024
|
|
70
49
|
}
|
|
71
50
|
};
|
|
72
51
|
|
|
@@ -140,6 +119,8 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
140
119
|
// getToneMapping: () => number (Three.js ToneMapping constant)
|
|
141
120
|
this.backendParamsGetter = options.backendParams || null;
|
|
142
121
|
this.getGPUTextures = options.getGPUTextures || null;
|
|
122
|
+
// refreshInput: () => void — re-renders the compositor so the WebGPU canvas is readable
|
|
123
|
+
this.refreshInput = options.refreshInput || null;
|
|
143
124
|
this.getExposure = options.getExposure || ( () => 1.0 );
|
|
144
125
|
this.getToneMapping = options.getToneMapping || ( () => ACESFilmicToneMapping );
|
|
145
126
|
this.getSaturation = options.getSaturation || ( () => 1.0 );
|
|
@@ -179,8 +160,8 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
179
160
|
this.enabled = this.config.enableOIDN;
|
|
180
161
|
this.quality = this.config.oidnQuality;
|
|
181
162
|
this.debugGbufferMaps = this.config.debugGbufferMaps;
|
|
182
|
-
this.
|
|
183
|
-
//
|
|
163
|
+
this.maxTileSize = this.config.tileSize;
|
|
164
|
+
// The size actually baked into the live UNet, so a change forces a rebuild.
|
|
184
165
|
this._activeTileSize = 0;
|
|
185
166
|
|
|
186
167
|
// State management
|
|
@@ -257,6 +238,63 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
257
238
|
|
|
258
239
|
}
|
|
259
240
|
|
|
241
|
+
/**
|
|
242
|
+
* Whether the tier's model expects a clean (accumulated) auxiliary guide rather than a
|
|
243
|
+
* point-sampled one. The aux MRT has to be fed to match, or the guide fights the weights.
|
|
244
|
+
* @param {string} [quality] defaults to the active tier
|
|
245
|
+
* @returns {boolean}
|
|
246
|
+
*/
|
|
247
|
+
expectsCleanAux( quality = this.quality ) {
|
|
248
|
+
|
|
249
|
+
const { QUALITY_MODELS } = MODEL_CONFIG;
|
|
250
|
+
return ( QUALITY_MODELS[ quality ] || QUALITY_MODELS.balance ).cleanAux;
|
|
251
|
+
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Engine, precision, model, kernel, tile state and resource counts from the live UNet.
|
|
256
|
+
* @returns {Object|null} null before the weights load
|
|
257
|
+
*/
|
|
258
|
+
getRuntimeInfo() {
|
|
259
|
+
|
|
260
|
+
return this.unet?.getRuntimeInfo?.() ?? null;
|
|
261
|
+
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Arms per-layer GPU timestamping for the next denoise. Needs `timestamp-query` on the
|
|
266
|
+
* device; one denoise only, so call it again for each capture.
|
|
267
|
+
* @returns {boolean} whether the capture was armed
|
|
268
|
+
*/
|
|
269
|
+
profileNextDenoise() {
|
|
270
|
+
|
|
271
|
+
return this.unet?.profileNextExecution?.() ?? false;
|
|
272
|
+
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Per-layer GPU timings from the last denoise armed by {@link profileNextDenoise}.
|
|
277
|
+
* @returns {Promise<Object|null>}
|
|
278
|
+
*/
|
|
279
|
+
async getLastDenoiseProfile() {
|
|
280
|
+
|
|
281
|
+
return ( await this.unet?.getLastExecutionProfile?.() ) ?? null;
|
|
282
|
+
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Largest tile that still covers the frame, bounded by the configured cap. One tile means
|
|
287
|
+
* zero overlap, which is the cheapest configuration oidn-web has.
|
|
288
|
+
* @returns {number}
|
|
289
|
+
*/
|
|
290
|
+
_resolveTileSize() {
|
|
291
|
+
|
|
292
|
+
const longest = Math.max( this.output?.width || 0, this.output?.height || 0 );
|
|
293
|
+
if ( ! longest ) return this.maxTileSize;
|
|
294
|
+
return Math.min( longest, this.maxTileSize );
|
|
295
|
+
|
|
296
|
+
}
|
|
297
|
+
|
|
260
298
|
async _setupUNetDenoiser() {
|
|
261
299
|
|
|
262
300
|
if ( this.state.isLoading ) return;
|
|
@@ -264,8 +302,10 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
264
302
|
this.state.isLoading = true;
|
|
265
303
|
const tzaUrl = this._generateTzaUrl();
|
|
266
304
|
|
|
305
|
+
const tileSize = this._resolveTileSize();
|
|
306
|
+
|
|
267
307
|
// maxTileSize is a constructor arg, so comparing the URL alone left tileSize changes inert.
|
|
268
|
-
if ( this.currentTZAUrl === tzaUrl && this._activeTileSize ===
|
|
308
|
+
if ( this.currentTZAUrl === tzaUrl && this._activeTileSize === tileSize && this.unet ) {
|
|
269
309
|
|
|
270
310
|
this.state.isLoading = false;
|
|
271
311
|
return;
|
|
@@ -299,11 +339,14 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
299
339
|
this.unet = await initFn( tzaUrl, backendParams, {
|
|
300
340
|
aux: true,
|
|
301
341
|
hdr: true,
|
|
302
|
-
maxTileSize:
|
|
342
|
+
maxTileSize: tileSize,
|
|
343
|
+
// Adaptive tiling ignores maxTileSize, starts at 384, and cannot see that overlap
|
|
344
|
+
// padding only vanishes once a tile covers the image — so it settles smaller.
|
|
345
|
+
dynamicTile: false
|
|
303
346
|
} );
|
|
304
347
|
|
|
305
348
|
this.currentTZAUrl = tzaUrl;
|
|
306
|
-
this._activeTileSize =
|
|
349
|
+
this._activeTileSize = tileSize;
|
|
307
350
|
this.dispatchEvent( { type: 'loaded' } );
|
|
308
351
|
log.debug( 'UNet weights loaded:', tzaUrl );
|
|
309
352
|
|
|
@@ -333,8 +376,8 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
333
376
|
|
|
334
377
|
const { oidnWeightsBaseUrl } = getAssetConfig();
|
|
335
378
|
const { QUALITY_MODELS } = MODEL_CONFIG;
|
|
336
|
-
const
|
|
337
|
-
return `${oidnWeightsBaseUrl}${
|
|
379
|
+
const tier = QUALITY_MODELS[ this.quality ] || QUALITY_MODELS.balance;
|
|
380
|
+
return `${oidnWeightsBaseUrl}${tier.model}.tza`;
|
|
338
381
|
|
|
339
382
|
}
|
|
340
383
|
|
|
@@ -349,7 +392,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
349
392
|
Object.assign( this.config, newConfig );
|
|
350
393
|
this.quality = this.config.oidnQuality;
|
|
351
394
|
this.debugGbufferMaps = this.config.debugGbufferMaps;
|
|
352
|
-
this.
|
|
395
|
+
this.maxTileSize = this.config.tileSize;
|
|
353
396
|
|
|
354
397
|
// Reload denoiser if necessary
|
|
355
398
|
await this._setupUNetDenoiser();
|
|
@@ -376,6 +419,14 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
376
419
|
|
|
377
420
|
}
|
|
378
421
|
|
|
422
|
+
// The tile tracks the frame size, so a resolution change since the last build has to be
|
|
423
|
+
// rebuilt into the UNet before denoising — otherwise the frame tiles when it need not.
|
|
424
|
+
if ( this.unet && ! this.state.isLoading && this._activeTileSize !== this._resolveTileSize() ) {
|
|
425
|
+
|
|
426
|
+
await this._setupUNetDenoiser();
|
|
427
|
+
|
|
428
|
+
}
|
|
429
|
+
|
|
379
430
|
// UNet weights still loading — defer the start (fired from _setupUNetDenoiser's
|
|
380
431
|
// finally) rather than silently dropping this frame's denoise request.
|
|
381
432
|
if ( this.state.isLoading ) {
|
|
@@ -404,6 +455,15 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
404
455
|
|
|
405
456
|
}
|
|
406
457
|
|
|
458
|
+
// Revealing before the first paint shows the output's transparent bitmap — and its
|
|
459
|
+
// checker CSS background — over the now-hidden render canvas.
|
|
460
|
+
_revealOutput() {
|
|
461
|
+
|
|
462
|
+
this.output.style.display = 'block';
|
|
463
|
+
this.input.style.opacity = '0';
|
|
464
|
+
|
|
465
|
+
}
|
|
466
|
+
|
|
407
467
|
async execute() {
|
|
408
468
|
|
|
409
469
|
if ( ! this.enabled || ! this.unet ) return false;
|
|
@@ -411,8 +471,6 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
411
471
|
// Create abort controller for this execution
|
|
412
472
|
this.state.abortController = new AbortController();
|
|
413
473
|
this.state.isDenoising = true;
|
|
414
|
-
this.input.style.opacity = '0';
|
|
415
|
-
this.output.style.display = 'block';
|
|
416
474
|
|
|
417
475
|
try {
|
|
418
476
|
|
|
@@ -485,6 +543,12 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
485
543
|
|
|
486
544
|
}
|
|
487
545
|
|
|
546
|
+
// Capture the base now: the readback below awaits, and a presented WebGPU canvas only
|
|
547
|
+
// reads back non-empty straight after a compositor pass — hence the refresh.
|
|
548
|
+
this.refreshInput?.();
|
|
549
|
+
this.ctx.drawImage( this.input, 0, 0, width, height );
|
|
550
|
+
this._revealOutput();
|
|
551
|
+
|
|
488
552
|
// Ensure storage buffers are sized correctly (recreate on resolution change)
|
|
489
553
|
this._ensureGPUInputBuffers( width, height );
|
|
490
554
|
|
|
@@ -543,8 +607,6 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
543
607
|
if ( globalThis.__OIDN_NO_INPUT_SCALE ) this._oidnInputScale = 1.0;
|
|
544
608
|
this._applyColorScale( device, width * height );
|
|
545
609
|
|
|
546
|
-
// Draw the current noisy frame as the base — denoised tiles paint on top progressively
|
|
547
|
-
this.ctx.drawImage( this.input, 0, 0, width, height );
|
|
548
610
|
// Pass GPU storage buffers to oidn-web (GPUBuffer path, well-tested)
|
|
549
611
|
const config = {
|
|
550
612
|
color: { data: this._gpuInputBuffers.color, width, height },
|
|
@@ -1094,9 +1156,6 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
1094
1156
|
|
|
1095
1157
|
// Dispose resources
|
|
1096
1158
|
this.unet?.dispose();
|
|
1097
|
-
// Must precede renderer.dispose() so the GPUDevice is still alive when
|
|
1098
|
-
// TFJS tears down the cached backend's buffers/textures.
|
|
1099
|
-
removeOidnTfjsBackend();
|
|
1100
1159
|
this._destroyGPUInputBuffers();
|
|
1101
1160
|
this._colorScalePipeline = null;
|
|
1102
1161
|
|
package/src/PathTracerApp.js
CHANGED
|
@@ -1808,6 +1808,39 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1808
1808
|
|
|
1809
1809
|
}
|
|
1810
1810
|
|
|
1811
|
+
/**
|
|
1812
|
+
* Arms per-layer GPU timestamping inside the OIDN denoiser for the next denoise only.
|
|
1813
|
+
* Read the result with {@link PathTracerApp#getDenoiseProfile}.
|
|
1814
|
+
*
|
|
1815
|
+
* `getGPUTimings()` cannot see the denoise: oidn-web submits on its own command encoders,
|
|
1816
|
+
* outside the stages three.js times.
|
|
1817
|
+
*
|
|
1818
|
+
* @returns {boolean} whether the capture was armed
|
|
1819
|
+
*/
|
|
1820
|
+
profileNextDenoise() {
|
|
1821
|
+
|
|
1822
|
+
return this.denoisingManager?.denoiser?.profileNextDenoise() ?? false;
|
|
1823
|
+
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
/**
|
|
1827
|
+
* Per-layer GPU timings from the denoise armed by {@link PathTracerApp#profileNextDenoise},
|
|
1828
|
+
* plus the live engine/precision/model/tile diagnostics.
|
|
1829
|
+
*
|
|
1830
|
+
* @returns {Promise<{ profile: Object|null, runtime: Object|null }|null>}
|
|
1831
|
+
*/
|
|
1832
|
+
async getDenoiseProfile() {
|
|
1833
|
+
|
|
1834
|
+
const denoiser = this.denoisingManager?.denoiser;
|
|
1835
|
+
if ( ! denoiser ) return null;
|
|
1836
|
+
|
|
1837
|
+
return {
|
|
1838
|
+
profile: await denoiser.getLastDenoiseProfile(),
|
|
1839
|
+
runtime: denoiser.getRuntimeInfo(),
|
|
1840
|
+
};
|
|
1841
|
+
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1811
1844
|
/**
|
|
1812
1845
|
* Real GPU milliseconds for the last resolved frame, from WebGPU timestamp queries.
|
|
1813
1846
|
* Returns null unless {@link PathTracerApp#enableGPUTiming} was called.
|
|
@@ -138,6 +138,13 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
138
138
|
adapterInfo: null
|
|
139
139
|
} ),
|
|
140
140
|
|
|
141
|
+
refreshInput: () => {
|
|
142
|
+
|
|
143
|
+
const ctx = this.pipeline?.context;
|
|
144
|
+
if ( this._stages.compositor && ctx ) this._stages.compositor.render( ctx );
|
|
145
|
+
|
|
146
|
+
},
|
|
147
|
+
|
|
141
148
|
getGPUTextures: () => {
|
|
142
149
|
|
|
143
150
|
if ( ! pt?.storageTextures?.readTarget ) return null;
|
|
@@ -190,6 +197,13 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
190
197
|
|
|
191
198
|
},
|
|
192
199
|
|
|
200
|
+
refreshInput: () => {
|
|
201
|
+
|
|
202
|
+
const ctx = this.pipeline?.context;
|
|
203
|
+
if ( this._stages.compositor && ctx ) this._stages.compositor.render( ctx );
|
|
204
|
+
|
|
205
|
+
},
|
|
206
|
+
|
|
193
207
|
getGPUTextures: () => {
|
|
194
208
|
|
|
195
209
|
if ( ! pt?.storageTextures?.readTarget ) return null;
|
|
@@ -428,13 +442,6 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
428
442
|
// Remove any stale completion-chain listener from a previous render cycle
|
|
429
443
|
this._cleanupCompletionListener();
|
|
430
444
|
|
|
431
|
-
// Show post-process canvas if any post-process is enabled
|
|
432
|
-
if ( ( this.denoiser?.enabled || this.upscaler?.enabled ) && this.denoiserCanvas ) {
|
|
433
|
-
|
|
434
|
-
this.denoiserCanvas.style.display = 'block';
|
|
435
|
-
|
|
436
|
-
}
|
|
437
|
-
|
|
438
445
|
// Chain: denoise first (if enabled), then upscale (if enabled)
|
|
439
446
|
const startUpscaler = () => {
|
|
440
447
|
|
|
@@ -458,13 +465,6 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
458
465
|
|
|
459
466
|
} else {
|
|
460
467
|
|
|
461
|
-
// Re-render compositor stage so WebGPU canvas has valid content
|
|
462
|
-
if ( this.upscaler?.enabled && this._stages.compositor && context ) {
|
|
463
|
-
|
|
464
|
-
this._stages.compositor.render( context );
|
|
465
|
-
|
|
466
|
-
}
|
|
467
|
-
|
|
468
468
|
startUpscaler();
|
|
469
469
|
|
|
470
470
|
}
|
|
@@ -638,8 +638,10 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
638
638
|
setOIDNQuality( quality ) {
|
|
639
639
|
|
|
640
640
|
this.denoiser?.updateQuality( quality );
|
|
641
|
-
// Clean-aux normal follows the model
|
|
642
|
-
this._stages.pathTracer?.setCleanAuxNormal?.(
|
|
641
|
+
// Clean-aux normal follows the model, not the tier name — see OIDNDenoiser.QUALITY_MODELS.
|
|
642
|
+
this._stages.pathTracer?.setCleanAuxNormal?.(
|
|
643
|
+
!! this.denoiser?.enabled && !! this.denoiser?.expectsCleanAux( quality )
|
|
644
|
+
);
|
|
643
645
|
this._onPostProcessRefresh?.();
|
|
644
646
|
|
|
645
647
|
}
|