rayzee 8.1.0 → 8.2.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.
@@ -362,8 +362,12 @@ export class PathTracerApp extends EventDispatcher {
362
362
 
363
363
  }
364
364
 
365
+ const cameraMoved = this.needsReset;
365
366
  if ( this.needsReset ) {
366
367
 
368
+ // Before the reset, so the denoising manager's abort sees this frame as part of a move
369
+ // rather than deciding to hold on the first frame and dropping it on the second.
370
+ this.stages.pathTracer?.noteViewChanged();
367
371
  this.reset( true );
368
372
  this.needsReset = false;
369
373
 
@@ -404,6 +408,23 @@ export class PathTracerApp extends EventDispatcher {
404
408
 
405
409
  }
406
410
 
411
+ // A frame traced while a denoise is in flight is never seen: only denoised frames reach
412
+ // the canvas during a camera move, and the next denoise reads the newest frame anyway.
413
+ // Tracing it only takes the GPU away from the denoise the viewport is waiting on —
414
+ // inside a room that turned a 10 ms denoise into 185 ms of wall clock.
415
+ if ( this.denoisingManager?.skipsTrace() ) {
416
+
417
+ // The camera is still being dragged; without this the interaction timeout can
418
+ // expire inside a long denoise and drop the view out of interaction mode.
419
+ if ( cameraMoved ) this.stages.pathTracer.enterInteractionMode();
420
+ // Gizmos and outlines are drawn against the live camera, not the traced frame, so
421
+ // they would visibly lag the view if they only redrew on the frames that traced.
422
+ this._renderHelperOverlay();
423
+ this.dispatchEvent( { type: EngineEvents.FRAME } );
424
+ return;
425
+
426
+ }
427
+
407
428
  this.pipeline.render();
408
429
 
409
430
  if ( ! this.stages.pathTracer.isComplete ) {
@@ -540,7 +561,9 @@ export class PathTracerApp extends EventDispatcher {
540
561
 
541
562
  }
542
563
 
543
- this._abortPostProcess();
564
+ // Whatever is on screen stays until its replacement is ready, including while the camera
565
+ // moves: the denoising manager decides, since only it knows something is coming.
566
+ this._abortPostProcess( { keepDisplay: true } );
544
567
 
545
568
  this.completion.reset();
546
569
  this.wake();
@@ -713,6 +736,7 @@ export class PathTracerApp extends EventDispatcher {
713
736
 
714
737
  }
715
738
 
739
+ this.denoisingManager?.dropDisplay();
716
740
  this.reset();
717
741
  this.dispatchEvent( { type: 'SceneUnloaded' } );
718
742
 
@@ -962,6 +986,9 @@ export class PathTracerApp extends EventDispatcher {
962
986
  this._syncControlsAfterLoad();
963
987
  await this.loadSceneData( { pendingEnvironment: this._beginSceneMetadataEnvironment() } );
964
988
  this.pipeline?.eventBus.emit( 'autoexposure:resetHistory' );
989
+ // Not held: the first denoise of a new scene lands ~1.1 s after the load (upload and
990
+ // shader compilation come first), and a second of the previous model reads as a bug.
991
+ this.denoisingManager?.dropDisplay();
965
992
  this.reset();
966
993
  this.cameraManager.currentCameraIndex = 0;
967
994
  this.dispatchEvent( eventPayload );
@@ -994,6 +1021,7 @@ export class PathTracerApp extends EventDispatcher {
994
1021
 
995
1022
  this._clearAppendedModels();
996
1023
  this.assetLoader?.releaseTargetModel();
1024
+ this.denoisingManager?.dropDisplay();
997
1025
  this.reset();
998
1026
 
999
1027
  } catch ( cleanupError ) {
@@ -1853,6 +1881,10 @@ export class PathTracerApp extends EventDispatcher {
1853
1881
  const isProduction = mode === 'production';
1854
1882
  const config = isProduction ? PRODUCTION_RENDER_CONFIG : INTERACTIVE_RENDER_CONFIG;
1855
1883
 
1884
+ // First, before anything below can wake the loop or resize the renderer: a live-view
1885
+ // refresh landing in the middle of that raced the renderer's own output pass.
1886
+ this.denoisingManager?.setCadenceSuspended( isProduction );
1887
+
1856
1888
  this.cameraManager.controls.enabled = ! isProduction;
1857
1889
 
1858
1890
  // Anything with a SETTING_ROUTES entry must go through settings, not setUniform: set() early-returns on
@@ -1935,9 +1967,9 @@ export class PathTracerApp extends EventDispatcher {
1935
1967
 
1936
1968
  // Aborts any in-flight denoise/upscale and puts the denoiser canvas back at base resolution (the
1937
1969
  // upscaler leaves it enlarged), so the live canvas is what's on screen again.
1938
- _abortPostProcess() {
1970
+ _abortPostProcess( { keepDisplay = false } = {} ) {
1939
1971
 
1940
- this.denoisingManager?.abort( this.canvas );
1972
+ this.denoisingManager?.abort( this.canvas, { keepDisplay } );
1941
1973
 
1942
1974
  if ( this.denoisingManager?.restoreBaseResolution() ) {
1943
1975
 
@@ -2423,8 +2455,8 @@ export class PathTracerApp extends EventDispatcher {
2423
2455
  // ═══════════════════════════════════════════════════════════════
2424
2456
 
2425
2457
  /**
2426
- * Returns the canvas element with the final rendered image.
2427
- * Chooses the post-processing canvas when denoiser/upscaler are active.
2458
+ * Returns the canvas element holding the image on screen — denoised, graded and tone-mapped,
2459
+ * whatever is currently showing. The upscaler is the one thing that paints elsewhere.
2428
2460
  * @returns {HTMLCanvasElement|null}
2429
2461
  */
2430
2462
  getCanvas() {
@@ -2432,13 +2464,13 @@ export class PathTracerApp extends EventDispatcher {
2432
2464
  if ( ! this.renderer?.domElement ) return null;
2433
2465
 
2434
2466
  const dm = this.denoisingManager;
2435
- const usePostProcess = ( dm?.denoiser?.enabled || dm?.upscaler?.enabled )
2436
- && dm?.denoiserCanvas
2437
- && this.stages.pathTracer?.isComplete;
2467
+ const upscaled = dm?.upscaler?.enabled && dm?.upscalerCanvas
2468
+ && dm.upscalerCanvas.style.display !== 'none';
2438
2469
 
2439
- if ( usePostProcess ) return dm.denoiserCanvas;
2470
+ if ( upscaled ) return dm.upscalerCanvas;
2440
2471
 
2441
- // Re-render compositor stage so the WebGPU canvas has valid content
2472
+ // A presented WebGPU canvas only reads back what was drawn immediately before, so draw.
2473
+ // This also puts the denoised picture on it: the Compositor prefers it over the raw render.
2442
2474
  if ( this.stages.compositor && this.pipeline?.context ) {
2443
2475
 
2444
2476
  this.stages.compositor.render( this.pipeline.context );
@@ -3261,8 +3293,6 @@ export class PathTracerApp extends EventDispatcher {
3261
3293
  this.denoisingManager = new DenoisingManager( {
3262
3294
  renderer: this.renderer,
3263
3295
  mainCanvas: this.canvas,
3264
- scene: this.scene,
3265
- camera: this.cameraManager.camera,
3266
3296
  stages: {
3267
3297
  pathTracer: this.stages.pathTracer,
3268
3298
  normalDepth: this.stages.normalDepth,
@@ -3278,7 +3308,6 @@ export class PathTracerApp extends EventDispatcher {
3278
3308
  pipeline: this.pipeline,
3279
3309
  getExposure: () => this.settings.get( 'exposure' ) ?? 1.0,
3280
3310
  getSaturation: () => this.settings.get( 'saturation' ) ?? 1.0,
3281
- getTransparentBg: () => this.settings.get( 'transparentBackground' ) ?? false,
3282
3311
  } );
3283
3312
 
3284
3313
  this.denoisingManager.setupDenoiser();
@@ -67,6 +67,7 @@ export class Compositor extends RenderStage {
67
67
  _resolveSourceTexture( context ) {
68
68
 
69
69
  return context.getTexture( 'bloom:output' )
70
+ || context.getTexture( 'oidn:output' )
70
71
  || context.getTexture( 'edgeFiltering:output' )
71
72
  || context.getTexture( 'bilateralFiltering:output' )
72
73
  || context.getTexture( 'asvgf:output' )
@@ -30,6 +30,10 @@ import { createLogger, fmt } from '../utils/Logger.js';
30
30
 
31
31
  const log = createLogger( 'pathtracer' );
32
32
 
33
+ // How long after the last move the view still counts as moving. Matches CameraOptimizer's own
34
+ // settle delay, so the answer does not change when interaction mode is switched off.
35
+ const VIEW_SETTLE_MS = 100;
36
+
33
37
  /**
34
38
  * Data layout constants
35
39
  */
@@ -163,6 +167,8 @@ export class PathTracerStage extends RenderStage {
163
167
 
164
168
  // Track changes for event emission
165
169
  this.cameraChanged = false;
170
+ // When the view was last moved, for viewIsChanging.
171
+ this._lastViewChangeAt = - Infinity;
166
172
 
167
173
  // Update completion threshold
168
174
  this.updateCompletionThreshold();
@@ -704,6 +710,25 @@ export class PathTracerStage extends RenderStage {
704
710
 
705
711
  }
706
712
 
713
+ /**
714
+ * Whether the view is being moved right now — the camera or a gizmo — whether or not interaction
715
+ * mode is on. Interaction mode answers a narrower question (may quality be reduced) and is a
716
+ * switch a host can turn off; what a denoiser needs to know is only whether this frame is about
717
+ * to be thrown away by the next move.
718
+ */
719
+ get viewIsChanging() {
720
+
721
+ return this.interactionMode || performance.now() - this._lastViewChangeAt < VIEW_SETTLE_MS;
722
+
723
+ }
724
+
725
+ // Called by the host loop on a frame whose reset came from the view moving.
726
+ noteViewChanged() {
727
+
728
+ this._lastViewChangeAt = performance.now();
729
+
730
+ }
731
+
707
732
  // ===== TEXTURE SETTERS =====
708
733
 
709
734
  /**