rayzee 8.6.0 → 8.7.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 +1 -53
- package/dist/{DLSSNeural-BhBCBgKf.js → NeuralRetouch-CYOJ21Wn.js} +31 -31
- package/dist/{DLSSSuperRes-BCA7F_k5.js → NeuralSuperRes-Dc6jRYke.js} +23 -23
- package/dist/{ToneMapGPU-BzcLheoR.js → ToneMapGPU-BVE852iN.js} +2 -2
- package/dist/rayzee.es.js +3552 -2990
- package/dist/rayzee.umd.js +17 -17
- package/package.json +1 -1
- package/src/AssetConfig.js +6 -5
- package/src/Passes/OIDNDenoiser.js +13 -5
- package/src/PathTracerApp.js +97 -2
- package/src/Processor/AssetLoader.js +14 -8
- package/src/Processor/EmissiveTriangleBuilder.js +70 -0
- package/src/Processor/PBRT/PBRTAnimation.js +646 -0
- package/src/Processor/PBRT/PBRTParser.js +179 -52
- package/src/Processor/PBRT/PBRTSceneBuilder.js +181 -25
- package/src/Processor/PBRT/index.js +73 -14
- package/src/Processor/SceneProcessor.js +22 -0
- package/src/Stages/PathTracerStage.js +16 -1
- package/src/index.js +3 -3
- package/src/managers/AnimationManager.js +202 -8
- package/src/managers/DenoisingManager.js +60 -44
- package/src/{dlss/DLSSNeural.js → neural/NeuralRetouch.js} +42 -41
- package/src/{dlss/DLSSSuperRes.js → neural/NeuralSuperRes.js} +27 -26
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* AnimationManager — Drives
|
|
2
|
+
* AnimationManager — Drives animation clips for the path tracer.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* a
|
|
4
|
+
* Deforming clips (a SkinnedMesh or morph track) return a per-mesh position reader for
|
|
5
|
+
* PathTracerApp.refitBVH(). Rigid clips return nothing and hand the moved meshes to
|
|
6
|
+
* `applyPoseCallback`, which updates placements only — a refit would bake world positions into
|
|
7
|
+
* triangles that other placements share.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
import { AnimationMixer, EventDispatcher, Timer, Vector3, LoopRepeat, LoopOnce } from 'three';
|
|
10
|
+
import { AnimationMixer, EventDispatcher, Timer, Vector3, LoopRepeat, LoopOnce, PropertyBinding } from 'three';
|
|
10
11
|
import { EngineEvents } from '../EngineEvents.js';
|
|
11
12
|
|
|
12
13
|
export class AnimationManager extends EventDispatcher {
|
|
@@ -31,9 +32,20 @@ export class AnimationManager extends EventDispatcher {
|
|
|
31
32
|
this._savedTimeScale = 1;
|
|
32
33
|
this.onFinished = null; // callback when a non-looping clip ends
|
|
33
34
|
|
|
35
|
+
this._rigid = false;
|
|
36
|
+
this._plans = null; // per clip: { meshes: Int32Array, visibility: boolean, cameras: Camera[] }
|
|
37
|
+
this._allPlan = null;
|
|
38
|
+
this._activeClip = 0;
|
|
39
|
+
this._slotOf = null; // meshIndex -> row in the two caches below, or -1
|
|
40
|
+
this._lastWorld = null; // world matrix last handed to applyPoseCallback, 16 per row
|
|
41
|
+
this._lastVisible = null; // 0 / 1, or 2 before the first pose
|
|
42
|
+
|
|
34
43
|
/** Injected by PathTracerApp — wakes the render loop after play/resume. */
|
|
35
44
|
this.wakeCallback = null;
|
|
36
45
|
|
|
46
|
+
/** Injected by PathTracerApp — receives `{ meshIndices, visibilityChanged, cameras }`. */
|
|
47
|
+
this.applyPoseCallback = null;
|
|
48
|
+
|
|
37
49
|
}
|
|
38
50
|
|
|
39
51
|
/**
|
|
@@ -88,6 +100,10 @@ export class AnimationManager extends EventDispatcher {
|
|
|
88
100
|
|
|
89
101
|
this.actions = actions;
|
|
90
102
|
|
|
103
|
+
this._rigid = ! meshes.some( m => m?.isSkinnedMesh ) &&
|
|
104
|
+
! animations.some( clip => clip.tracks.some( t => t.name.includes( 'morphTargetInfluences' ) ) );
|
|
105
|
+
this._planClips( animations );
|
|
106
|
+
|
|
91
107
|
// Listen for non-looping clip completion
|
|
92
108
|
this.mixer.addEventListener( 'finished', () => {
|
|
93
109
|
|
|
@@ -121,7 +137,154 @@ export class AnimationManager extends EventDispatcher {
|
|
|
121
137
|
this._meshPositions = [];
|
|
122
138
|
|
|
123
139
|
const skinnedCount = meshes.filter( m => m.isSkinnedMesh ).length;
|
|
124
|
-
console.debug( `[AnimationManager] Init: ${animations.length} clips, ${meshes.length} meshes (${skinnedCount} skinned), ${offset} triangles` );
|
|
140
|
+
console.debug( `[AnimationManager] Init: ${animations.length} clips, ${meshes.length} meshes (${skinnedCount} skinned), ${offset} triangles, ${this._rigid ? 'rigid' : 'deforming'}` );
|
|
141
|
+
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Which meshes and cameras each clip moves, directly or through an ancestor. @private */
|
|
145
|
+
_planClips( animations ) {
|
|
146
|
+
|
|
147
|
+
const root = this._mixerRoot;
|
|
148
|
+
const byName = new Map();
|
|
149
|
+
// First name in pre-order wins, as in PropertyBinding.findNode.
|
|
150
|
+
root.traverse?.( node => {
|
|
151
|
+
|
|
152
|
+
if ( ! byName.has( node.name ) ) byName.set( node.name, node );
|
|
153
|
+
byName.set( node.uuid, node );
|
|
154
|
+
|
|
155
|
+
} );
|
|
156
|
+
|
|
157
|
+
this._plans = animations.map( clip => {
|
|
158
|
+
|
|
159
|
+
const animated = new Set();
|
|
160
|
+
let visibility = false;
|
|
161
|
+
for ( const track of clip.tracks ) {
|
|
162
|
+
|
|
163
|
+
const { nodeName, propertyName } = PropertyBinding.parseTrackName( track.name );
|
|
164
|
+
const node = byName.get( nodeName );
|
|
165
|
+
if ( ! node ) continue;
|
|
166
|
+
animated.add( node );
|
|
167
|
+
if ( propertyName === 'visible' ) visibility = true;
|
|
168
|
+
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const meshes = [];
|
|
172
|
+
for ( let m = 0; m < this._meshes.length; m ++ ) {
|
|
173
|
+
|
|
174
|
+
for ( let o = this._meshes[ m ]; o; o = o.parent ) {
|
|
175
|
+
|
|
176
|
+
if ( animated.has( o ) ) {
|
|
177
|
+
|
|
178
|
+
meshes.push( m );
|
|
179
|
+
break;
|
|
180
|
+
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const cameras = new Set();
|
|
188
|
+
for ( const node of animated ) node.traverse( o => o.isCamera && cameras.add( o ) );
|
|
189
|
+
|
|
190
|
+
return { meshes: Int32Array.from( meshes ), visibility, cameras: [ ...cameras ] };
|
|
191
|
+
|
|
192
|
+
} );
|
|
193
|
+
|
|
194
|
+
this._allPlan = null;
|
|
195
|
+
if ( ! this._rigid ) return;
|
|
196
|
+
|
|
197
|
+
this._slotOf = new Int32Array( this._meshes.length ).fill( - 1 );
|
|
198
|
+
let rows = 0;
|
|
199
|
+
for ( const plan of this._plans ) for ( const m of plan.meshes ) if ( this._slotOf[ m ] < 0 ) this._slotOf[ m ] = rows ++;
|
|
200
|
+
|
|
201
|
+
// Unknown until the first pose, which therefore sends every animated mesh.
|
|
202
|
+
this._lastWorld = new Float64Array( rows * 16 ).fill( NaN );
|
|
203
|
+
this._lastVisible = new Uint8Array( rows ).fill( 2 );
|
|
204
|
+
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** The plan for a clip index, or the union of every clip for -1. @private */
|
|
208
|
+
_planFor( clipIndex ) {
|
|
209
|
+
|
|
210
|
+
if ( ! this._plans ) return null;
|
|
211
|
+
if ( clipIndex >= 0 ) return this._plans[ clipIndex ] ?? null;
|
|
212
|
+
|
|
213
|
+
if ( ! this._allPlan ) {
|
|
214
|
+
|
|
215
|
+
const meshes = new Set(), cameras = new Set();
|
|
216
|
+
let visibility = false;
|
|
217
|
+
for ( const plan of this._plans ) {
|
|
218
|
+
|
|
219
|
+
plan.meshes.forEach( m => meshes.add( m ) );
|
|
220
|
+
plan.cameras.forEach( c => cameras.add( c ) );
|
|
221
|
+
visibility ||= plan.visibility;
|
|
222
|
+
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
this._allPlan = { meshes: Int32Array.from( meshes ), visibility, cameras: [ ...cameras ] };
|
|
226
|
+
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return this._allPlan;
|
|
230
|
+
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/** Send meshes whose world matrix or visibility changed since the last pose. @private */
|
|
234
|
+
_applyRigidPose( clipIndex ) {
|
|
235
|
+
|
|
236
|
+
const plan = this._planFor( clipIndex );
|
|
237
|
+
if ( ! plan ) return;
|
|
238
|
+
|
|
239
|
+
const moved = [];
|
|
240
|
+
let visibilityChanged = false;
|
|
241
|
+
const last = this._lastWorld, lastVisible = this._lastVisible;
|
|
242
|
+
|
|
243
|
+
for ( const m of plan.meshes ) {
|
|
244
|
+
|
|
245
|
+
const mesh = this._meshes[ m ];
|
|
246
|
+
const row = this._slotOf[ m ];
|
|
247
|
+
const e = mesh.matrixWorld.elements;
|
|
248
|
+
const o = row * 16;
|
|
249
|
+
|
|
250
|
+
for ( let i = 0; i < 16; i ++ ) {
|
|
251
|
+
|
|
252
|
+
if ( last[ o + i ] !== e[ i ] ) {
|
|
253
|
+
|
|
254
|
+
for ( let j = 0; j < 16; j ++ ) last[ o + j ] = e[ j ];
|
|
255
|
+
moved.push( m );
|
|
256
|
+
break;
|
|
257
|
+
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if ( plan.visibility || lastVisible[ row ] === 2 ) {
|
|
263
|
+
|
|
264
|
+
let visible = 1;
|
|
265
|
+
for ( let n = mesh; n; n = n.parent ) if ( ! n.visible ) {
|
|
266
|
+
|
|
267
|
+
visible = 0;
|
|
268
|
+
break;
|
|
269
|
+
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if ( lastVisible[ row ] !== visible ) {
|
|
273
|
+
|
|
274
|
+
lastVisible[ row ] = visible;
|
|
275
|
+
visibilityChanged = true;
|
|
276
|
+
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if ( moved.length || visibilityChanged || plan.cameras.length ) {
|
|
284
|
+
|
|
285
|
+
this.applyPoseCallback?.( { meshIndices: moved, visibilityChanged, cameras: plan.cameras } );
|
|
286
|
+
|
|
287
|
+
}
|
|
125
288
|
|
|
126
289
|
}
|
|
127
290
|
|
|
@@ -145,6 +308,7 @@ export class AnimationManager extends EventDispatcher {
|
|
|
145
308
|
|
|
146
309
|
}
|
|
147
310
|
|
|
311
|
+
this._activeClip = clipIndex;
|
|
148
312
|
this.timer.reset();
|
|
149
313
|
this.isPlaying = true;
|
|
150
314
|
this.wakeCallback?.();
|
|
@@ -188,10 +352,19 @@ export class AnimationManager extends EventDispatcher {
|
|
|
188
352
|
|
|
189
353
|
if ( ! this.mixer ) return;
|
|
190
354
|
|
|
355
|
+
// Restores every animated property to its loaded value.
|
|
191
356
|
this.mixer.stopAllAction();
|
|
192
357
|
this.mixer.timeScale = this._savedTimeScale || 1;
|
|
193
358
|
this.timer.reset();
|
|
194
359
|
this.isPlaying = false;
|
|
360
|
+
|
|
361
|
+
if ( this._rigid ) {
|
|
362
|
+
|
|
363
|
+
this._mixerRoot.updateMatrixWorld( true );
|
|
364
|
+
this._applyRigidPose( - 1 );
|
|
365
|
+
|
|
366
|
+
}
|
|
367
|
+
|
|
195
368
|
this.dispatchEvent( { type: EngineEvents.ANIMATION_STOPPED } );
|
|
196
369
|
|
|
197
370
|
}
|
|
@@ -229,12 +402,15 @@ export class AnimationManager extends EventDispatcher {
|
|
|
229
402
|
*
|
|
230
403
|
* @param {number} time - Absolute time in seconds
|
|
231
404
|
* @param {number} [clipIndex=0] - Clip to evaluate, or -1 for all active
|
|
232
|
-
* @returns {function(number): Float32Array|null} Per-mesh reader for refitBVH, or null if no
|
|
405
|
+
* @returns {function(number): Float32Array|null} Per-mesh reader for refitBVH, or null if no
|
|
406
|
+
* mixer or the clips are rigid
|
|
233
407
|
*/
|
|
234
408
|
seekTo( time, clipIndex = 0 ) {
|
|
235
409
|
|
|
236
410
|
if ( ! this.mixer || this.actions.length === 0 ) return null;
|
|
237
411
|
|
|
412
|
+
this._activeClip = clipIndex;
|
|
413
|
+
|
|
238
414
|
// Ensure the target action(s) are active so setTime evaluates them.
|
|
239
415
|
// Actions must NOT be paused — setTime() calls update() internally,
|
|
240
416
|
// which skips paused actions entirely.
|
|
@@ -279,7 +455,8 @@ export class AnimationManager extends EventDispatcher {
|
|
|
279
455
|
* Advance animation and prepare deformed positions.
|
|
280
456
|
* Call once per frame from the animate loop.
|
|
281
457
|
*
|
|
282
|
-
* @returns {function(number): Float32Array|null} Per-mesh reader for refitBVH, or null if not
|
|
458
|
+
* @returns {function(number): Float32Array|null} Per-mesh reader for refitBVH, or null if not
|
|
459
|
+
* playing or the clips are rigid
|
|
283
460
|
*/
|
|
284
461
|
update() {
|
|
285
462
|
|
|
@@ -312,6 +489,16 @@ export class AnimationManager extends EventDispatcher {
|
|
|
312
489
|
// once here, on mixerRoot rather than the scene, keeps unrelated static objects out of it.
|
|
313
490
|
this._mixerRoot.updateMatrixWorld( true );
|
|
314
491
|
|
|
492
|
+
if ( this._rigid ) {
|
|
493
|
+
|
|
494
|
+
this._applyRigidPose( this._activeClip );
|
|
495
|
+
return null;
|
|
496
|
+
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
const cameras = this._planFor( this._activeClip )?.cameras;
|
|
500
|
+
if ( cameras?.length ) this.applyPoseCallback?.( { meshIndices: [], visibilityChanged: false, cameras } );
|
|
501
|
+
|
|
315
502
|
return m => this._computeMeshPositions( m );
|
|
316
503
|
|
|
317
504
|
}
|
|
@@ -446,6 +633,13 @@ export class AnimationManager extends EventDispatcher {
|
|
|
446
633
|
this._meshPositions = null;
|
|
447
634
|
this._skinnedCache = null;
|
|
448
635
|
this._clipsCache = null;
|
|
636
|
+
this._rigid = false;
|
|
637
|
+
this._plans = null;
|
|
638
|
+
this._allPlan = null;
|
|
639
|
+
this._activeClip = 0;
|
|
640
|
+
this._slotOf = null;
|
|
641
|
+
this._lastWorld = null;
|
|
642
|
+
this._lastVisible = null;
|
|
449
643
|
|
|
450
644
|
}
|
|
451
645
|
|
|
@@ -4,9 +4,9 @@ import { AIUpscaler } from '../Passes/AIUpscaler.js';
|
|
|
4
4
|
import { EngineEvents } from '../EngineEvents.js';
|
|
5
5
|
import { createLogger } from '../utils/Logger.js';
|
|
6
6
|
|
|
7
|
-
// The neural passes live in `../
|
|
8
|
-
// share one namespace: `rayzee.log.only( '
|
|
9
|
-
const
|
|
7
|
+
// The neural passes live in `../neural/` but report through the manager that drives them, so they
|
|
8
|
+
// share one namespace: `rayzee.log.only( 'neural' )` shows the whole chain.
|
|
9
|
+
const neuralLog = createLogger( 'neural' );
|
|
10
10
|
import { ENGINE_DEFAULTS as DEFAULT_STATE, ASVGF_QUALITY_PRESETS, NRD_DEFAULTS, NRD_QUALITY_PRESETS, NRD_PRESET_KEYS } from '../EngineDefaults.js';
|
|
11
11
|
|
|
12
12
|
// A refresh slower than this is a slideshow, not a live view, so the cadence swaps to a cheaper
|
|
@@ -112,16 +112,16 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
112
112
|
|
|
113
113
|
this.denoiser = null;
|
|
114
114
|
this.upscaler = null;
|
|
115
|
-
// Which model the AI upscaler runs. 'esrgan' is the ONNX chain; '
|
|
115
|
+
// Which model the AI upscaler runs. 'esrgan' is the ONNX chain; 'neural' is the neural
|
|
116
116
|
// super-resolution pass, which is fixed at 2x and needs a denoised source.
|
|
117
117
|
this.upscalerBackend = 'esrgan';
|
|
118
|
-
this.
|
|
118
|
+
this._neuralUpscaler = null;
|
|
119
119
|
// The neural-rendering (detail) pass is independent of the upscaler: it changes appearance,
|
|
120
120
|
// not resolution, and runs last because its result cannot be read back.
|
|
121
121
|
this.neuralRendering = false;
|
|
122
122
|
this.neuralRenderingSettings = {};
|
|
123
|
-
this.
|
|
124
|
-
this.
|
|
123
|
+
this._retouch = null;
|
|
124
|
+
this._superResModule = null;
|
|
125
125
|
this._neuralPostBusy = false;
|
|
126
126
|
|
|
127
127
|
// The tier the finished image uses. The loaded tier is not always this one: while the
|
|
@@ -162,6 +162,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
162
162
|
// Bound event forwarding handlers (stored for removal on re-setup / dispose)
|
|
163
163
|
this._denoiserStartHandler = null;
|
|
164
164
|
this._denoiserEndHandler = null;
|
|
165
|
+
this._denoiserTileHandler = null;
|
|
165
166
|
this._upscalerResChangedHandler = null;
|
|
166
167
|
this._upscalerStartHandler = null;
|
|
167
168
|
this._upscalerProgressHandler = null;
|
|
@@ -298,8 +299,21 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
298
299
|
|
|
299
300
|
};
|
|
300
301
|
|
|
302
|
+
// The loop has stopped by the time a final denoise runs, so each tile has to redraw the
|
|
303
|
+
// canvas itself. Not through the display-refresh callback: that restarts the loop, which a
|
|
304
|
+
// video export drives by hand.
|
|
305
|
+
this._denoiserTileHandler = e => {
|
|
306
|
+
|
|
307
|
+
if ( e.continuous ) return;
|
|
308
|
+
this._publishOutput();
|
|
309
|
+
const ctx = this.pipeline?.context;
|
|
310
|
+
if ( this._stages.compositor && ctx ) this._stages.compositor.render( ctx );
|
|
311
|
+
|
|
312
|
+
};
|
|
313
|
+
|
|
301
314
|
this.denoiser.addEventListener( 'start', this._denoiserStartHandler );
|
|
302
315
|
this.denoiser.addEventListener( 'end', this._denoiserEndHandler );
|
|
316
|
+
this.denoiser.addEventListener( 'tileProgress', this._denoiserTileHandler );
|
|
303
317
|
|
|
304
318
|
}
|
|
305
319
|
|
|
@@ -804,20 +818,20 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
804
818
|
}
|
|
805
819
|
|
|
806
820
|
/**
|
|
807
|
-
* Selects the AI upscaler's model. '
|
|
821
|
+
* Selects the AI upscaler's model. 'neural' is fixed at 2x and reads the denoised picture, so it
|
|
808
822
|
* is a no-op without a denoiser — feeding it raw Monte-Carlo noise measurably loses to bilinear.
|
|
809
823
|
*
|
|
810
|
-
* @param {'esrgan'|'
|
|
824
|
+
* @param {'esrgan'|'neural'} backend
|
|
811
825
|
*/
|
|
812
826
|
setUpscalerBackend( backend ) {
|
|
813
827
|
|
|
814
|
-
const next = backend === '
|
|
828
|
+
const next = backend === 'neural' ? 'neural' : 'esrgan';
|
|
815
829
|
if ( next === this.upscalerBackend ) return;
|
|
816
830
|
|
|
817
831
|
this.upscalerBackend = next;
|
|
818
832
|
// Whatever is on screen came from the model being switched away from.
|
|
819
833
|
this._restoreRenderDisplay();
|
|
820
|
-
if ( next !== '
|
|
834
|
+
if ( next !== 'neural' ) this._releaseNeuralUpscaler();
|
|
821
835
|
|
|
822
836
|
}
|
|
823
837
|
|
|
@@ -847,15 +861,15 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
847
861
|
|
|
848
862
|
}
|
|
849
863
|
|
|
850
|
-
|
|
864
|
+
_releaseNeuralUpscaler() {
|
|
851
865
|
|
|
852
|
-
this.
|
|
853
|
-
this.
|
|
866
|
+
this._neuralUpscaler?.dispose();
|
|
867
|
+
this._neuralUpscaler = null;
|
|
854
868
|
|
|
855
869
|
// The frame packer lives on the module, not on the upscaler, and holds two buffers the size
|
|
856
870
|
// of the render. Nothing else reaches it, so this is its only release point. Guarded on the
|
|
857
871
|
// module having been loaded, so a teardown never pulls in 1 MB of runtime to call a no-op.
|
|
858
|
-
this.
|
|
872
|
+
this._superResModule?.releaseDenoisedReader();
|
|
859
873
|
|
|
860
874
|
}
|
|
861
875
|
|
|
@@ -866,17 +880,17 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
866
880
|
if ( settings ) this.neuralRenderingSettings = { ...this.neuralRenderingSettings, ...settings };
|
|
867
881
|
if ( ! this.neuralRendering ) {
|
|
868
882
|
|
|
869
|
-
this.
|
|
883
|
+
this._releaseNeuralRetouch();
|
|
870
884
|
this._restoreRenderDisplay();
|
|
871
885
|
|
|
872
886
|
}
|
|
873
887
|
|
|
874
888
|
}
|
|
875
889
|
|
|
876
|
-
|
|
890
|
+
_releaseNeuralRetouch() {
|
|
877
891
|
|
|
878
|
-
this.
|
|
879
|
-
this.
|
|
892
|
+
this._retouch?.dispose();
|
|
893
|
+
this._retouch = null;
|
|
880
894
|
|
|
881
895
|
}
|
|
882
896
|
|
|
@@ -894,7 +908,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
894
908
|
*/
|
|
895
909
|
async _runNeuralPost( isStillComplete ) {
|
|
896
910
|
|
|
897
|
-
const wantSR = this.upscaler?.enabled && this.upscalerBackend === '
|
|
911
|
+
const wantSR = this.upscaler?.enabled && this.upscalerBackend === 'neural';
|
|
898
912
|
const wantNR = this.neuralRendering;
|
|
899
913
|
if ( ! wantSR && ! wantNR ) return;
|
|
900
914
|
|
|
@@ -904,14 +918,14 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
904
918
|
|
|
905
919
|
if ( ! this.upscalerCanvas ) {
|
|
906
920
|
|
|
907
|
-
|
|
921
|
+
neuralLog.warn( 'neural pass skipped: the engine has no overlay canvas to present on' );
|
|
908
922
|
return;
|
|
909
923
|
|
|
910
924
|
}
|
|
911
925
|
|
|
912
926
|
if ( ! this.pipeline?.context?.getTexture( 'oidn:output' ) ) {
|
|
913
927
|
|
|
914
|
-
|
|
928
|
+
neuralLog.warn( 'neural pass skipped: no denoised picture — it needs OIDN' );
|
|
915
929
|
return;
|
|
916
930
|
|
|
917
931
|
}
|
|
@@ -921,26 +935,26 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
921
935
|
|
|
922
936
|
try {
|
|
923
937
|
|
|
924
|
-
const sr = await import( '../
|
|
925
|
-
this.
|
|
938
|
+
const sr = await import( '../neural/NeuralSuperRes.js' );
|
|
939
|
+
this._superResModule = sr;
|
|
926
940
|
|
|
927
941
|
// The detail pass has a size ceiling — above it a run takes minutes and then loses every GPU
|
|
928
942
|
// device in the page. Running it FIRST is what keeps it under: at render size it sees a
|
|
929
943
|
// quarter of the pixels it would after a 2x upscale, and the render reserve already caps
|
|
930
944
|
// that at 2048. The check stays as a floor under a raised reserve.
|
|
931
|
-
const nrModule = wantNR ? await import( '../
|
|
945
|
+
const nrModule = wantNR ? await import( '../neural/NeuralRetouch.js' ) : null;
|
|
932
946
|
const srcSize = this.denoiser?._outTexSize;
|
|
933
947
|
const nrPixels = ( srcSize?.width ?? 0 ) * ( srcSize?.height ?? 0 );
|
|
934
|
-
const runNR = wantNR && nrPixels <= nrModule.
|
|
948
|
+
const runNR = wantNR && nrPixels <= nrModule.RETOUCH_MAX_PIXELS;
|
|
935
949
|
|
|
936
950
|
if ( wantNR && ! runNR ) {
|
|
937
951
|
|
|
938
|
-
|
|
952
|
+
neuralLog.warn(
|
|
939
953
|
`Neural rendering skipped: the render is ${( nrPixels / 1e6 ).toFixed( 1 )} MP, above the ` +
|
|
940
|
-
`${( nrModule.
|
|
954
|
+
`${( nrModule.RETOUCH_MAX_PIXELS / 1e6 ).toFixed( 1 )} MP the pass survives.` +
|
|
941
955
|
( wantSR ? ' The upscale still ran.' : '' )
|
|
942
956
|
);
|
|
943
|
-
this.
|
|
957
|
+
this._releaseNeuralRetouch();
|
|
944
958
|
|
|
945
959
|
}
|
|
946
960
|
|
|
@@ -961,16 +975,16 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
961
975
|
|
|
962
976
|
if ( wantSR ) {
|
|
963
977
|
|
|
964
|
-
if ( this.
|
|
965
|
-
&& ( this.
|
|
978
|
+
if ( this._neuralUpscaler
|
|
979
|
+
&& ( this._neuralUpscaler.inputWidth !== source.width || this._neuralUpscaler.inputHeight !== source.height ) ) {
|
|
966
980
|
|
|
967
|
-
this.
|
|
981
|
+
this._releaseNeuralUpscaler();
|
|
968
982
|
|
|
969
983
|
}
|
|
970
984
|
|
|
971
|
-
if ( ! this.
|
|
985
|
+
if ( ! this._neuralUpscaler ) {
|
|
972
986
|
|
|
973
|
-
this.
|
|
987
|
+
this._neuralUpscaler = await sr.NeuralSuperRes.create( { width: source.width, height: source.height } );
|
|
974
988
|
|
|
975
989
|
}
|
|
976
990
|
|
|
@@ -979,23 +993,23 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
979
993
|
let image = source;
|
|
980
994
|
|
|
981
995
|
// Detail first, upscale second. The detail pass hands back scene-referred light (see
|
|
982
|
-
// `
|
|
996
|
+
// the runtime's `rayzee-patch` edits), so the upscaler still gets the linear HDR it expects —
|
|
983
997
|
// which is what makes this order possible at all.
|
|
984
998
|
if ( runNR ) {
|
|
985
999
|
|
|
986
1000
|
const result = await nrModule.enhanceFrame( {
|
|
987
1001
|
source: image,
|
|
988
1002
|
settings: this.neuralRenderingSettings,
|
|
989
|
-
instance: this.
|
|
1003
|
+
instance: this._retouch,
|
|
990
1004
|
exposure: tone.exposure,
|
|
991
1005
|
tone: wantSR ? null : tone,
|
|
992
1006
|
} );
|
|
993
|
-
this.
|
|
1007
|
+
this._retouch = result.instance;
|
|
994
1008
|
image = result;
|
|
995
1009
|
|
|
996
1010
|
}
|
|
997
1011
|
|
|
998
|
-
if ( wantSR ) image = await this.
|
|
1012
|
+
if ( wantSR ) image = await this._neuralUpscaler.upscaleToRGBA8( image, tone );
|
|
999
1013
|
|
|
1000
1014
|
if ( ! isStillComplete() ) {
|
|
1001
1015
|
|
|
@@ -1024,7 +1038,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1024
1038
|
|
|
1025
1039
|
} catch ( error ) {
|
|
1026
1040
|
|
|
1027
|
-
|
|
1041
|
+
neuralLog.warn( 'neural pass failed', error );
|
|
1028
1042
|
|
|
1029
1043
|
} finally {
|
|
1030
1044
|
|
|
@@ -1060,7 +1074,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1060
1074
|
|
|
1061
1075
|
// One owner of the overlay canvas, so the neural chain and the ONNX upscaler are
|
|
1062
1076
|
// exclusive. The neural chain also runs on its own when only its detail pass is on.
|
|
1063
|
-
if ( this.neuralRendering || ( this.upscaler?.enabled && this.upscalerBackend === '
|
|
1077
|
+
if ( this.neuralRendering || ( this.upscaler?.enabled && this.upscalerBackend === 'neural' ) ) {
|
|
1064
1078
|
|
|
1065
1079
|
this._runNeuralPost( isStillComplete );
|
|
1066
1080
|
return;
|
|
@@ -1184,8 +1198,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1184
1198
|
// Remove pending completion-chain listener
|
|
1185
1199
|
this._cleanupCompletionListener();
|
|
1186
1200
|
// Owns a second GPUDevice plus 3 MB of weights, so it must not outlive the manager.
|
|
1187
|
-
this.
|
|
1188
|
-
this.
|
|
1201
|
+
this._releaseNeuralUpscaler();
|
|
1202
|
+
this._releaseNeuralRetouch();
|
|
1189
1203
|
|
|
1190
1204
|
// Before the denoiser destroys the picture the Compositor would otherwise still sample.
|
|
1191
1205
|
this._unpublishOutput();
|
|
@@ -1194,6 +1208,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1194
1208
|
|
|
1195
1209
|
if ( this._denoiserStartHandler ) this.denoiser.removeEventListener( 'start', this._denoiserStartHandler );
|
|
1196
1210
|
if ( this._denoiserEndHandler ) this.denoiser.removeEventListener( 'end', this._denoiserEndHandler );
|
|
1211
|
+
if ( this._denoiserTileHandler ) this.denoiser.removeEventListener( 'tileProgress', this._denoiserTileHandler );
|
|
1197
1212
|
this.denoiser.dispose();
|
|
1198
1213
|
this.denoiser = null;
|
|
1199
1214
|
|
|
@@ -1207,15 +1222,16 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
1207
1222
|
if ( this._upscalerEndHandler ) this.upscaler.removeEventListener( 'end', this._upscalerEndHandler );
|
|
1208
1223
|
this.upscaler.dispose();
|
|
1209
1224
|
this.upscaler = null;
|
|
1210
|
-
// Which model the AI upscaler runs. 'esrgan' is the ONNX chain; '
|
|
1225
|
+
// Which model the AI upscaler runs. 'esrgan' is the ONNX chain; 'neural' is the neural
|
|
1211
1226
|
// super-resolution pass, which is fixed at 2x and needs a denoised source.
|
|
1212
1227
|
this.upscalerBackend = 'esrgan';
|
|
1213
|
-
this.
|
|
1228
|
+
this._neuralUpscaler = null;
|
|
1214
1229
|
|
|
1215
1230
|
}
|
|
1216
1231
|
|
|
1217
1232
|
this._denoiserStartHandler = null;
|
|
1218
1233
|
this._denoiserEndHandler = null;
|
|
1234
|
+
this._denoiserTileHandler = null;
|
|
1219
1235
|
this._upscalerResChangedHandler = null;
|
|
1220
1236
|
this._upscalerStartHandler = null;
|
|
1221
1237
|
this._upscalerProgressHandler = null;
|