rayzee 7.17.0 → 7.19.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 -1
- package/dist/assets/BVHSubtreeWorker-D7KMbrQ-.js +2 -0
- package/dist/assets/BVHSubtreeWorker-D7KMbrQ-.js.map +1 -0
- package/dist/assets/BVHWorker-DOaGn6uN.js +2 -0
- package/dist/assets/BVHWorker-DOaGn6uN.js.map +1 -0
- package/dist/rayzee.es.js +2388 -2138
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +49 -49
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineDefaults.js +7 -6
- package/src/Passes/OIDNDenoiser.js +17 -14
- package/src/PathTracerApp.js +63 -56
- package/src/Processor/BVHBuilder.js +11 -7
- package/src/Processor/BuildTimer.js +20 -6
- package/src/Processor/EmissiveTriangleBuilder.js +11 -7
- package/src/Processor/GeometryExtractor.js +11 -5
- package/src/Processor/KernelManager.js +48 -1
- package/src/Processor/PackedRayBuffer.js +20 -12
- package/src/Processor/ParallelBVHBuilder.js +9 -6
- package/src/Processor/QueueManager.js +6 -8
- package/src/Processor/SceneProcessor.js +47 -21
- package/src/Processor/ShaderBuilder.js +4 -1
- package/src/Processor/StorageTexturePool.js +4 -1
- package/src/Processor/Workers/BVHWorker.js +12 -6
- package/src/Processor/utils.js +30 -0
- package/src/RenderSettings.js +1 -1
- package/src/SceneHelpers.js +11 -1
- package/src/Stages/PathTracer.js +15 -4
- package/src/Stages/PathTracerStage.js +17 -14
- package/src/TSL/FinalWriteKernel.js +11 -6
- package/src/TSL/ShadeKernel.js +46 -24
- package/src/TSL/TextureSampling.js +12 -2
- package/src/index.js +3 -0
- package/src/managers/EnvironmentManager.js +12 -8
- package/src/managers/MaterialDataManager.js +12 -9
- package/src/managers/OverlayManager.js +105 -48
- package/src/managers/UniformManager.js +1 -1
- package/src/managers/helpers/OutlineHelper.js +42 -40
- package/src/managers/helpers/TransformGizmoHelper.js +61 -0
- package/src/managers/helpers/ViewOverlayRenderer.js +140 -0
- package/src/managers/helpers/overlaySurface.js +53 -0
- package/src/utils/Logger.js +291 -0
- package/dist/assets/BVHSubtreeWorker-sNzvxn66.js +0 -2
- package/dist/assets/BVHSubtreeWorker-sNzvxn66.js.map +0 -1
- package/dist/assets/BVHWorker-CiVdFrwe.js +0 -2
- package/dist/assets/BVHWorker-CiVdFrwe.js.map +0 -1
package/package.json
CHANGED
package/src/EngineDefaults.js
CHANGED
|
@@ -84,15 +84,16 @@ export const ENGINE_DEFAULTS = {
|
|
|
84
84
|
transmissiveBounces: 5,
|
|
85
85
|
maxSubsurfaceSteps: 8, // interactive default: low cap (bounded random-walk SSS)
|
|
86
86
|
|
|
87
|
+
maxTransparentBounces: 32, // guard: alpha skips are free bounces, else cutout foliage eats the loop budget
|
|
88
|
+
|
|
87
89
|
// Adaptive sampling (Blender-style): stop the frame once enough pixels drop below the noise threshold.
|
|
88
90
|
useAdaptiveSampling: true,
|
|
89
|
-
noiseThreshold: 0.02, // per-pixel noise below which a pixel is converged
|
|
90
|
-
darkNoiseFloor: 0.003, // extra absolute-noise floor so dark pixels can converge too
|
|
91
|
+
noiseThreshold: 0.02, // √-luminance-normalized per-pixel noise below which a pixel is converged
|
|
91
92
|
adaptiveMinSamples: 8, // min samples before adaptive sampling can trigger
|
|
92
93
|
adaptiveStopFraction: 0.95, // retire the frame once this fraction of pixels has converged
|
|
93
94
|
// Per-pixel freeze: skip tracing pixels that individually converged (noise threshold only — no dark floor,
|
|
94
95
|
// which would bake dim regions too dark). Naturally engages only on static/idle views.
|
|
95
|
-
usePixelFreeze:
|
|
96
|
+
usePixelFreeze: true,
|
|
96
97
|
pixelFreezeThreshold: 0.02, // per-pixel noise below which a pixel becomes a freeze candidate
|
|
97
98
|
pixelFreezeStability: 8, // consecutive candidate frames before a pixel freezes
|
|
98
99
|
|
|
@@ -590,11 +591,11 @@ export const PRODUCTION_RENDER_CONFIG = {
|
|
|
590
591
|
renderMode: 1, enableAlphaShadows: true,
|
|
591
592
|
enableOIDN: true, oidnQuality: 'balance',
|
|
592
593
|
interactionModeEnabled: false,
|
|
593
|
-
//
|
|
594
|
+
// √-norm convergence reaches a true ~all-converged gate, so stop at 0.98 — Cycles-faithful, leaving
|
|
595
|
+
// the unbiased residual for OIDN.
|
|
594
596
|
useAdaptiveSampling: true,
|
|
595
597
|
noiseThreshold: 0.1,
|
|
596
|
-
|
|
597
|
-
adaptiveStopFraction: 0.9,
|
|
598
|
+
adaptiveStopFraction: 0.98,
|
|
598
599
|
usePixelFreeze: true,
|
|
599
600
|
};
|
|
600
601
|
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { EventDispatcher, ACESFilmicToneMapping } from 'three';
|
|
2
|
+
import { createLogger, fmt } from '../utils/Logger.js';
|
|
3
|
+
|
|
4
|
+
const log = createLogger( 'oidn' );
|
|
2
5
|
|
|
3
6
|
let _initUNetFromURL = null;
|
|
4
7
|
let _tfEngine = null;
|
|
@@ -36,7 +39,7 @@ function removeOidnTfjsBackend() {
|
|
|
36
39
|
|
|
37
40
|
} catch ( e ) {
|
|
38
41
|
|
|
39
|
-
|
|
42
|
+
log.warn( 'failed to clear cached TFJS backend', e );
|
|
40
43
|
|
|
41
44
|
}
|
|
42
45
|
|
|
@@ -149,7 +152,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
149
152
|
// Initialize asynchronously
|
|
150
153
|
this._initialize().catch( error => {
|
|
151
154
|
|
|
152
|
-
|
|
155
|
+
log.error( 'init failed:', error );
|
|
153
156
|
this.dispatchEvent( { type: 'error', error } );
|
|
154
157
|
|
|
155
158
|
} );
|
|
@@ -249,11 +252,11 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
249
252
|
|
|
250
253
|
this.currentTZAUrl = tzaUrl;
|
|
251
254
|
this.dispatchEvent( { type: 'loaded' } );
|
|
252
|
-
|
|
255
|
+
log.debug( 'UNet weights loaded:', tzaUrl );
|
|
253
256
|
|
|
254
257
|
} catch ( error ) {
|
|
255
258
|
|
|
256
|
-
|
|
259
|
+
log.error( 'UNet weights failed to load:', error );
|
|
257
260
|
this.dispatchEvent( { type: 'error', error: new Error( `Denoiser loading failed: ${error.message}` ) } );
|
|
258
261
|
|
|
259
262
|
} finally {
|
|
@@ -340,7 +343,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
340
343
|
this.input.style.opacity = '0';
|
|
341
344
|
|
|
342
345
|
const duration = performance.now() - startTime;
|
|
343
|
-
|
|
346
|
+
log.debug( `denoise complete in ${fmt.ms( duration )} · quality ${this.quality}` );
|
|
344
347
|
|
|
345
348
|
}
|
|
346
349
|
|
|
@@ -367,11 +370,11 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
367
370
|
|
|
368
371
|
if ( error.name === 'AbortError' ) {
|
|
369
372
|
|
|
370
|
-
|
|
373
|
+
log.debug( 'denoise aborted' );
|
|
371
374
|
|
|
372
375
|
} else {
|
|
373
376
|
|
|
374
|
-
|
|
377
|
+
log.error( 'denoise error:', error );
|
|
375
378
|
|
|
376
379
|
}
|
|
377
380
|
|
|
@@ -408,7 +411,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
408
411
|
|
|
409
412
|
if ( ! this.getGPUTextures ) {
|
|
410
413
|
|
|
411
|
-
|
|
414
|
+
log.warn( 'GPU mode enabled but getGPUTextures not provided' );
|
|
412
415
|
return false;
|
|
413
416
|
|
|
414
417
|
}
|
|
@@ -416,7 +419,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
416
419
|
const textures = this.getGPUTextures();
|
|
417
420
|
if ( ! textures?.color ) {
|
|
418
421
|
|
|
419
|
-
|
|
422
|
+
log.warn( 'GPU textures not ready yet' );
|
|
420
423
|
return false;
|
|
421
424
|
|
|
422
425
|
}
|
|
@@ -424,7 +427,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
424
427
|
const device = this.gpuDevice;
|
|
425
428
|
if ( ! device ) {
|
|
426
429
|
|
|
427
|
-
|
|
430
|
+
log.warn( 'gpuDevice not available' );
|
|
428
431
|
return false;
|
|
429
432
|
|
|
430
433
|
}
|
|
@@ -828,7 +831,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
828
831
|
const device = this.gpuDevice;
|
|
829
832
|
if ( ! device ) {
|
|
830
833
|
|
|
831
|
-
|
|
834
|
+
log.error( 'gpuDevice not available for output readback' );
|
|
832
835
|
return;
|
|
833
836
|
|
|
834
837
|
}
|
|
@@ -909,7 +912,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
909
912
|
this.state.isDenoising = false;
|
|
910
913
|
this.dispatchEvent( { type: 'end' } );
|
|
911
914
|
|
|
912
|
-
|
|
915
|
+
log.debug( 'denoise aborted' );
|
|
913
916
|
|
|
914
917
|
}
|
|
915
918
|
|
|
@@ -927,7 +930,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
927
930
|
// Reinitialize denoiser if tile size changes relative to image size
|
|
928
931
|
this._setupUNetDenoiser().catch( error => {
|
|
929
932
|
|
|
930
|
-
|
|
933
|
+
log.error( 'reinitialize after size change failed:', error );
|
|
931
934
|
|
|
932
935
|
} );
|
|
933
936
|
|
|
@@ -975,7 +978,7 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
975
978
|
// Remove all event listeners
|
|
976
979
|
this.removeAllListeners?.();
|
|
977
980
|
|
|
978
|
-
|
|
981
|
+
log.debug( 'disposed' );
|
|
979
982
|
|
|
980
983
|
}
|
|
981
984
|
|
package/src/PathTracerApp.js
CHANGED
|
@@ -17,8 +17,9 @@ import { Compositor } from './Stages/Compositor.js';
|
|
|
17
17
|
import { RenderPipeline } from './Pipeline/RenderPipeline.js';
|
|
18
18
|
import { CompletionTracker } from './Pipeline/CompletionTracker.js';
|
|
19
19
|
import { ENGINE_DEFAULTS as DEFAULT_STATE, PRODUCTION_RENDER_CONFIG, INTERACTIVE_RENDER_CONFIG, MAX_STORAGE_TEXTURE_SIZE, MAX_RESERVABLE_RENDER_SIZE, setReservedRenderSize } from './EngineDefaults.js';
|
|
20
|
-
import { updateStats, updateLoading, resetLoading, setStatusCallback, getDisplaySamples, disposeObjectFromMemory } from './Processor/utils.js';
|
|
20
|
+
import { updateStats, updateLoading, resetLoading, setStatusCallback, getDisplaySamples, disposeObjectFromMemory, disposeRenderer } from './Processor/utils.js';
|
|
21
21
|
import { BuildTimer } from './Processor/BuildTimer.js';
|
|
22
|
+
import { createLogger, fmt } from './utils/Logger.js';
|
|
22
23
|
import { InteractionManager } from './managers/InteractionManager.js';
|
|
23
24
|
import { EngineEvents } from './EngineEvents.js';
|
|
24
25
|
import { AssetLoader } from './Processor/AssetLoader.js';
|
|
@@ -34,6 +35,7 @@ import { DenoisingManager } from './managers/DenoisingManager.js';
|
|
|
34
35
|
import { OverlayManager } from './managers/OverlayManager.js';
|
|
35
36
|
import { AnimationManager } from './managers/AnimationManager.js';
|
|
36
37
|
import { TransformManager } from './managers/TransformManager.js';
|
|
38
|
+
import { TransformGizmoHelper } from './managers/helpers/TransformGizmoHelper.js';
|
|
37
39
|
|
|
38
40
|
// One app per canvas — auto-dispose a prior owner if the caller double-
|
|
39
41
|
// instantiates (StrictMode, HMR, etc.) so its rAF loop can't burn CPU.
|
|
@@ -57,6 +59,9 @@ const _appsByCanvas = new WeakMap();
|
|
|
57
59
|
*
|
|
58
60
|
* Extends EventDispatcher for event-driven communication with stores/UI.
|
|
59
61
|
*/
|
|
62
|
+
|
|
63
|
+
const log = createLogger( 'engine' );
|
|
64
|
+
|
|
60
65
|
export class PathTracerApp extends EventDispatcher {
|
|
61
66
|
|
|
62
67
|
/**
|
|
@@ -79,7 +84,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
79
84
|
|
|
80
85
|
} catch ( err ) {
|
|
81
86
|
|
|
82
|
-
|
|
87
|
+
log.warn( 'prior canvas owner dispose failed', err );
|
|
83
88
|
|
|
84
89
|
}
|
|
85
90
|
|
|
@@ -188,7 +193,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
188
193
|
|
|
189
194
|
} catch ( err ) {
|
|
190
195
|
|
|
191
|
-
|
|
196
|
+
log.warn( 'failed to remove listener', type, err );
|
|
192
197
|
|
|
193
198
|
}
|
|
194
199
|
|
|
@@ -212,7 +217,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
212
217
|
this._initScenes();
|
|
213
218
|
this._initAssetPipeline();
|
|
214
219
|
this._initPipeline();
|
|
215
|
-
this._initManagers();
|
|
220
|
+
await this._initManagers();
|
|
216
221
|
this._wireEvents();
|
|
217
222
|
|
|
218
223
|
// Seed path tracer with minimal empty scene data
|
|
@@ -222,7 +227,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
222
227
|
this.stages.pathTracer.setupMaterial();
|
|
223
228
|
|
|
224
229
|
this.isInitialized = true;
|
|
225
|
-
|
|
230
|
+
log.debug( 'WebGPU path tracer app initialized' );
|
|
226
231
|
|
|
227
232
|
return this;
|
|
228
233
|
|
|
@@ -256,7 +261,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
256
261
|
|
|
257
262
|
this._animRefitInFlight = true;
|
|
258
263
|
this.refitBVH( positions )
|
|
259
|
-
.catch( err =>
|
|
264
|
+
.catch( err => log.error( 'animation refit error:', err ) )
|
|
260
265
|
.finally( () => {
|
|
261
266
|
|
|
262
267
|
this._animRefitInFlight = false;
|
|
@@ -384,7 +389,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
384
389
|
|
|
385
390
|
if ( this._deviceLost ) return;
|
|
386
391
|
this._deviceLost = true;
|
|
387
|
-
|
|
392
|
+
log.error( `WebGPU device lost (${info?.reason || 'unknown'}): ${info?.message || ''}` );
|
|
388
393
|
this.stopAnimation();
|
|
389
394
|
this.dispatchEvent( { type: EngineEvents.DEVICE_LOST, reason: info?.reason, message: info?.message } );
|
|
390
395
|
|
|
@@ -507,35 +512,19 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
507
512
|
this.scene?.clear();
|
|
508
513
|
this.scene = null;
|
|
509
514
|
|
|
510
|
-
// Three.js 0.184
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
//
|
|
514
|
-
//
|
|
515
|
-
//
|
|
516
|
-
//
|
|
517
|
-
//
|
|
515
|
+
// Three.js 0.184 leak (confirmed via heap-snapshot retainer analysis): the
|
|
516
|
+
// Textures manager (one per renderer) registers a per-texture 'dispose'
|
|
517
|
+
// listener that closes over `this = Textures` — which transitively captures
|
|
518
|
+
// backend → renderer. These listeners are removed only when the texture
|
|
519
|
+
// itself is destroyed. For module-level singletons like EmptyTexture (new
|
|
520
|
+
// Texture in TextureNode.js) and its CubeTexture counterpart, the texture is
|
|
521
|
+
// never destroyed, so every renderer ever created leaks through the
|
|
522
|
+
// singleton's listener array.
|
|
518
523
|
//
|
|
519
|
-
//
|
|
520
|
-
//
|
|
521
|
-
//
|
|
522
|
-
//
|
|
523
|
-
// EmptyTexture (new Texture in TextureNode.js) and its CubeTexture
|
|
524
|
-
// counterpart, the texture is never destroyed, so every renderer ever
|
|
525
|
-
// created leaks through the singleton's listener array.
|
|
526
|
-
//
|
|
527
|
-
// Both workarounds are safe when only a single PathTracerApp is active at a
|
|
528
|
-
// time. If you run multiple in parallel, reset listeners only on the renderer
|
|
529
|
-
// being disposed (not the shared singletons).
|
|
530
|
-
if ( this.renderer?._canvasTarget && this.renderer._onCanvasTargetResize ) {
|
|
531
|
-
|
|
532
|
-
this.renderer._canvasTarget.removeEventListener(
|
|
533
|
-
'resize',
|
|
534
|
-
this.renderer._onCanvasTargetResize
|
|
535
|
-
);
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
-
|
|
524
|
+
// Safe when only a single PathTracerApp is active at a time. If you run
|
|
525
|
+
// multiple in parallel, reset listeners only on the renderer being disposed
|
|
526
|
+
// (not the shared singletons). The sibling _canvasTarget leak is handled by
|
|
527
|
+
// disposeRenderer().
|
|
539
528
|
try {
|
|
540
529
|
|
|
541
530
|
const emptyTex = _tslTexture().value;
|
|
@@ -545,12 +534,11 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
545
534
|
|
|
546
535
|
} catch ( err ) {
|
|
547
536
|
|
|
548
|
-
|
|
537
|
+
log.warn( 'failed to clear TSL texture singleton listeners', err );
|
|
549
538
|
|
|
550
539
|
}
|
|
551
540
|
|
|
552
|
-
this.renderer
|
|
553
|
-
if ( this.renderer ) this.renderer._canvasTarget = null;
|
|
541
|
+
disposeRenderer( this.renderer );
|
|
554
542
|
this.renderer = null;
|
|
555
543
|
|
|
556
544
|
this.stages = {};
|
|
@@ -803,7 +791,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
803
791
|
// Tag the primary (replace-loaded) model so it appears in the scene-object list.
|
|
804
792
|
this._tagPrimarySceneObject();
|
|
805
793
|
|
|
806
|
-
const timer = new BuildTimer( '
|
|
794
|
+
const timer = new BuildTimer( '', { namespace: 'scene', level: 'info' } );
|
|
807
795
|
const environmentTexture = this.meshScene.environment;
|
|
808
796
|
|
|
809
797
|
// Environment CDF build in parallel with BVH
|
|
@@ -856,7 +844,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
856
844
|
|
|
857
845
|
} catch ( err ) {
|
|
858
846
|
|
|
859
|
-
|
|
847
|
+
log.warn( 'raster fallback precompile failed', err );
|
|
860
848
|
|
|
861
849
|
}
|
|
862
850
|
|
|
@@ -896,7 +884,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
896
884
|
this.stages.compositor.setTransparentBackground( this.settings.get( 'transparentBackground' ) );
|
|
897
885
|
timer.end( 'Apply settings' );
|
|
898
886
|
|
|
899
|
-
timer.print();
|
|
887
|
+
timer.print( this._sceneSummaryParts() );
|
|
900
888
|
resetLoading();
|
|
901
889
|
|
|
902
890
|
this._initAnimationAndTransforms();
|
|
@@ -906,6 +894,23 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
906
894
|
|
|
907
895
|
}
|
|
908
896
|
|
|
897
|
+
/** Counts for the single `[scene]` summary line emitted after a scene build. */
|
|
898
|
+
_sceneSummaryParts() {
|
|
899
|
+
|
|
900
|
+
const pt = this.stages.pathTracer;
|
|
901
|
+
const meshes = this._sdf?.instanceTable?.entries?.filter( Boolean ).length ?? 0;
|
|
902
|
+
const maps = this._sdf?.geometryExtractor?.maps?.length ?? 0;
|
|
903
|
+
|
|
904
|
+
return [
|
|
905
|
+
fmt.count( pt.triangleCount, 'tri' ),
|
|
906
|
+
meshes ? fmt.count( meshes, 'mesh', 'meshes' ) : null,
|
|
907
|
+
fmt.count( pt.materialData.materialCount, 'material' ),
|
|
908
|
+
maps ? fmt.count( maps, 'map' ) : null,
|
|
909
|
+
fmt.count( pt.bvhNodeCount, 'BVH node' ),
|
|
910
|
+
];
|
|
911
|
+
|
|
912
|
+
}
|
|
913
|
+
|
|
909
914
|
// ═══════════════════════════════════════════════════════════════
|
|
910
915
|
// Dynamic scene objects (add / remove / list / visibility)
|
|
911
916
|
//
|
|
@@ -1262,7 +1267,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1262
1267
|
|
|
1263
1268
|
if ( width > MAX_STORAGE_TEXTURE_SIZE || height > MAX_STORAGE_TEXTURE_SIZE ) {
|
|
1264
1269
|
|
|
1265
|
-
|
|
1270
|
+
log.warn( `render resolution ${width}×${height} exceeds the ${MAX_STORAGE_TEXTURE_SIZE}px limit (compute storage textures are pre-allocated at ${MAX_STORAGE_TEXTURE_SIZE}px). Ignoring resize — use a resolution ≤ ${MAX_STORAGE_TEXTURE_SIZE}.` );
|
|
1266
1271
|
return false;
|
|
1267
1272
|
|
|
1268
1273
|
}
|
|
@@ -1283,13 +1288,6 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1283
1288
|
this.cameraManager.camera.aspect = width / height;
|
|
1284
1289
|
this.cameraManager.camera.updateProjectionMatrix();
|
|
1285
1290
|
|
|
1286
|
-
// Overlay helpers always render at display resolution
|
|
1287
|
-
const dpr = window.devicePixelRatio || 1;
|
|
1288
|
-
this.overlayManager?.setSize(
|
|
1289
|
-
Math.round( width * dpr ),
|
|
1290
|
-
Math.round( height * dpr )
|
|
1291
|
-
);
|
|
1292
|
-
|
|
1293
1291
|
const lastW = this.denoisingManager?._lastRenderWidth ?? 0;
|
|
1294
1292
|
const lastH = this.denoisingManager?._lastRenderHeight ?? 0;
|
|
1295
1293
|
if ( width === lastW && height === lastH ) return;
|
|
@@ -1361,7 +1359,6 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1361
1359
|
// engine defaults when a config doesn't override them.
|
|
1362
1360
|
this.stages.pathTracer?.setUniform( 'useAdaptiveSampling', config.useAdaptiveSampling ?? false );
|
|
1363
1361
|
this.stages.pathTracer?.setUniform( 'noiseThreshold', config.noiseThreshold ?? DEFAULT_STATE.noiseThreshold );
|
|
1364
|
-
this.stages.pathTracer?.setUniform( 'darkNoiseFloor', config.darkNoiseFloor ?? DEFAULT_STATE.darkNoiseFloor );
|
|
1365
1362
|
this.stages.pathTracer?.setUniform( 'adaptiveStopFraction', config.adaptiveStopFraction ?? DEFAULT_STATE.adaptiveStopFraction );
|
|
1366
1363
|
this.stages.pathTracer?.setUniform( 'adaptiveMinSamples', config.adaptiveMinSamples ?? DEFAULT_STATE.adaptiveMinSamples );
|
|
1367
1364
|
|
|
@@ -1819,7 +1816,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1819
1816
|
this._handleDeviceLost( info );
|
|
1820
1817
|
|
|
1821
1818
|
} );
|
|
1822
|
-
gpuDevice.onuncapturederror = ( event ) =>
|
|
1819
|
+
gpuDevice.onuncapturederror = ( event ) => log.error( 'WebGPU uncaptured error:', event.error );
|
|
1823
1820
|
|
|
1824
1821
|
}
|
|
1825
1822
|
|
|
@@ -1885,7 +1882,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1885
1882
|
|
|
1886
1883
|
}
|
|
1887
1884
|
|
|
1888
|
-
_initManagers() {
|
|
1885
|
+
async _initManagers() {
|
|
1889
1886
|
|
|
1890
1887
|
this.interactionManager = new InteractionManager( {
|
|
1891
1888
|
scene: this.meshScene,
|
|
@@ -1909,7 +1906,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1909
1906
|
onReset: () => this.reset(),
|
|
1910
1907
|
} );
|
|
1911
1908
|
this._setupDenoisingManager();
|
|
1912
|
-
this._setupOverlayManager();
|
|
1909
|
+
await this._setupOverlayManager();
|
|
1913
1910
|
|
|
1914
1911
|
this.transformManager = new TransformManager( {
|
|
1915
1912
|
camera: this.cameraManager.camera,
|
|
@@ -1918,6 +1915,10 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1918
1915
|
app: this,
|
|
1919
1916
|
} );
|
|
1920
1917
|
|
|
1918
|
+
// The gizmo is part of the scene overlay layer, so it draws on the same
|
|
1919
|
+
// view-resolution surface as the light helpers and the outline.
|
|
1920
|
+
this.overlayManager.register( 'transform', new TransformGizmoHelper( this.transformManager ) );
|
|
1921
|
+
|
|
1921
1922
|
// Wire cross-manager dependencies
|
|
1922
1923
|
this.interactionManager.setDependencies( {
|
|
1923
1924
|
overlayManager: this.overlayManager,
|
|
@@ -2189,13 +2190,12 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2189
2190
|
|
|
2190
2191
|
this.scene.updateMatrixWorld();
|
|
2191
2192
|
this.overlayManager?.render();
|
|
2192
|
-
this.transformManager?.render( this.renderer );
|
|
2193
2193
|
|
|
2194
2194
|
}
|
|
2195
2195
|
|
|
2196
|
-
_setupOverlayManager() {
|
|
2196
|
+
async _setupOverlayManager() {
|
|
2197
2197
|
|
|
2198
|
-
this.overlayManager = new OverlayManager( this.
|
|
2198
|
+
this.overlayManager = new OverlayManager( this.cameraManager.camera );
|
|
2199
2199
|
this.overlayManager.setupDefaultHelpers( {
|
|
2200
2200
|
helperScene: this._sceneHelpers,
|
|
2201
2201
|
meshScene: this.meshScene,
|
|
@@ -2206,6 +2206,13 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
2206
2206
|
renderHeight: this.denoisingManager?._lastRenderHeight || this.canvas.clientHeight || 1,
|
|
2207
2207
|
} );
|
|
2208
2208
|
|
|
2209
|
+
// Helpers draw at the size the canvas is displayed at, not the size it is
|
|
2210
|
+
// rendered at. Shares the main device, so this is a swapchain, not a context.
|
|
2211
|
+
await this.overlayManager.initViewRenderer( {
|
|
2212
|
+
device: this.renderer.backend?.device,
|
|
2213
|
+
sizeSource: this.canvas,
|
|
2214
|
+
} );
|
|
2215
|
+
|
|
2209
2216
|
this._container = this._container || this.canvas.parentNode || null;
|
|
2210
2217
|
this.overlayManager.mount( this._container );
|
|
2211
2218
|
|
|
@@ -2,6 +2,10 @@ import { TreeletOptimizer } from './TreeletOptimizer.js';
|
|
|
2
2
|
import { ReinsertionOptimizer } from './ReinsertionOptimizer.js';
|
|
3
3
|
import { fetchAsWorker } from './Workers/fetchAsWorker.js';
|
|
4
4
|
import BVH_WORKER_URL from './Workers/BVHWorker.js?worker&url';
|
|
5
|
+
// Logger is worker-safe (globalThis only, storage access guarded), unlike Constants.js below.
|
|
6
|
+
import { createLogger, fmt } from '../utils/Logger.js';
|
|
7
|
+
|
|
8
|
+
const log = createLogger( 'bvh' );
|
|
5
9
|
|
|
6
10
|
// Inline copy of TRIANGLE_DATA_LAYOUT (mirrors Constants.js).
|
|
7
11
|
// Cannot import Constants.js because BVHBuilder runs inside BVHWorker
|
|
@@ -403,7 +407,7 @@ export class BVHBuilder {
|
|
|
403
407
|
|
|
404
408
|
const triangleCount = this.totalTriangles;
|
|
405
409
|
const useShared = typeof SharedArrayBuffer !== 'undefined';
|
|
406
|
-
|
|
410
|
+
log.debug( `SharedArrayBuffer ${useShared ? 'enabled' : 'unavailable (using transfer fallback)'}` );
|
|
407
411
|
|
|
408
412
|
// Pre-allocate SharedArrayBuffer for reordered output so worker
|
|
409
413
|
// writes directly to shared memory (no transfer needed on return).
|
|
@@ -491,14 +495,14 @@ export class BVHBuilder {
|
|
|
491
495
|
|
|
492
496
|
fetchAsWorker( BVH_WORKER_URL ).then( setupWorker ).catch( () => {
|
|
493
497
|
|
|
494
|
-
|
|
498
|
+
log.warn( 'worker fetch fallback failed, using synchronous build' );
|
|
495
499
|
resolve( this._buildSyncAndFlatten( triangles, depth, progressCallback ) );
|
|
496
500
|
|
|
497
501
|
} );
|
|
498
502
|
|
|
499
503
|
} else {
|
|
500
504
|
|
|
501
|
-
|
|
505
|
+
log.warn( 'worker creation failed, falling back to synchronous build:', error );
|
|
502
506
|
resolve( this._buildSyncAndFlatten( triangles, depth, progressCallback ) );
|
|
503
507
|
|
|
504
508
|
}
|
|
@@ -619,7 +623,7 @@ export class BVHBuilder {
|
|
|
619
623
|
|
|
620
624
|
} catch ( error ) {
|
|
621
625
|
|
|
622
|
-
|
|
626
|
+
log.error( `treelet optimizer failed in pass ${pass + 1}:`, error );
|
|
623
627
|
break;
|
|
624
628
|
|
|
625
629
|
}
|
|
@@ -663,7 +667,7 @@ export class BVHBuilder {
|
|
|
663
667
|
|
|
664
668
|
} catch ( error ) {
|
|
665
669
|
|
|
666
|
-
|
|
670
|
+
log.error( 'reinsertion optimizer failed:', error );
|
|
667
671
|
|
|
668
672
|
}
|
|
669
673
|
|
|
@@ -710,8 +714,8 @@ export class BVHBuilder {
|
|
|
710
714
|
|
|
711
715
|
const total = this.splitStats.totalBuildTime;
|
|
712
716
|
const s = this.splitStats;
|
|
713
|
-
|
|
714
|
-
|
|
717
|
+
log.debug(
|
|
718
|
+
`${fmt.n( n )} tris → ${fmt.n( this.totalNodes )} nodes in ${fmt.ms( total )}` +
|
|
715
719
|
` | SAH ${s.sahSplits} objMed ${s.objectMedianSplits} spatMed ${s.spatialMedianSplits} failed ${s.failedSplits}` +
|
|
716
720
|
( s.treeletsProcessed ? ` | treelets ${s.treeletsImproved}/${s.treeletsProcessed} improved` : '' ) +
|
|
717
721
|
( s.reinsertionsApplied ? ` | reinsertions ${s.reinsertionsApplied}` : '' )
|
|
@@ -1,17 +1,25 @@
|
|
|
1
|
+
import { createLogger, fmt } from '../utils/Logger.js';
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* BuildTimer - Tracks and logs timing for all build pipeline steps.
|
|
5
|
+
*
|
|
6
|
+
* Nested timers default to `debug` so a normal load only shows the top-level
|
|
7
|
+
* summary; pass `{ level: 'info' }` for the one phase worth a visible line.
|
|
8
|
+
*
|
|
3
9
|
* Usage:
|
|
4
|
-
* const timer = new BuildTimer();
|
|
10
|
+
* const timer = new BuildTimer( '', { namespace: 'scene', level: 'info' } );
|
|
5
11
|
* timer.start('stepName');
|
|
6
12
|
* // ... work ...
|
|
7
13
|
* timer.end('stepName');
|
|
8
|
-
* timer.print(); //
|
|
14
|
+
* timer.print( [ '18,016 tris' ] ); // one summary line, steps folded into a group
|
|
9
15
|
*/
|
|
10
16
|
export class BuildTimer {
|
|
11
17
|
|
|
12
|
-
constructor( label = 'Build' ) {
|
|
18
|
+
constructor( label = 'Build', { namespace = 'build', level = 'debug' } = {} ) {
|
|
13
19
|
|
|
14
20
|
this.label = label;
|
|
21
|
+
this.level = level;
|
|
22
|
+
this.logger = createLogger( namespace );
|
|
15
23
|
this.entries = new Map();
|
|
16
24
|
this.order = [];
|
|
17
25
|
this.totalStart = performance.now();
|
|
@@ -47,11 +55,14 @@ export class BuildTimer {
|
|
|
47
55
|
|
|
48
56
|
}
|
|
49
57
|
|
|
50
|
-
|
|
58
|
+
/**
|
|
59
|
+
* @param {string[]} [parts] - extra summary segments (counts, sizes) shown before the total
|
|
60
|
+
*/
|
|
61
|
+
print( parts = [] ) {
|
|
51
62
|
|
|
52
63
|
const totalDuration = performance.now() - this.totalStart;
|
|
53
64
|
|
|
54
|
-
const
|
|
65
|
+
const steps = this.order
|
|
55
66
|
.map( name => {
|
|
56
67
|
|
|
57
68
|
const dur = this.entries.get( name )?.duration ?? 0;
|
|
@@ -60,7 +71,10 @@ export class BuildTimer {
|
|
|
60
71
|
} )
|
|
61
72
|
.filter( Boolean );
|
|
62
73
|
|
|
63
|
-
|
|
74
|
+
const headline = fmt.list( [ this.label, ...parts, fmt.ms( totalDuration ) ] );
|
|
75
|
+
|
|
76
|
+
if ( this.level === 'info' ) this.logger.summary( headline, steps );
|
|
77
|
+
else this.logger.debug( fmt.list( [ headline, steps.join( ' · ' ) ] ) );
|
|
64
78
|
|
|
65
79
|
return { steps: Object.fromEntries( this.order.map( n => [ n, Math.round( this.entries.get( n )?.duration ?? 0 ) ] ) ), total: Math.round( totalDuration ) };
|
|
66
80
|
|
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
import { DataTexture, RGBAFormat, FloatType, NearestFilter } from 'three';
|
|
10
10
|
import { TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
|
|
11
11
|
import { LightBVHBuilder } from './LightBVHBuilder.js';
|
|
12
|
+
import { createLogger, fmt } from '../utils/Logger.js';
|
|
13
|
+
|
|
14
|
+
const log = createLogger( 'emissive' );
|
|
12
15
|
|
|
13
16
|
export class EmissiveTriangleBuilder {
|
|
14
17
|
|
|
@@ -41,8 +44,6 @@ export class EmissiveTriangleBuilder {
|
|
|
41
44
|
*/
|
|
42
45
|
extractEmissiveTriangles( triangleData, materials, triangleCount ) {
|
|
43
46
|
|
|
44
|
-
console.log( '[EmissiveTriangleBuilder] Extracting emissive triangles...' );
|
|
45
|
-
|
|
46
47
|
this.emissiveTriangles = [];
|
|
47
48
|
this.totalEmissivePower = 0;
|
|
48
49
|
this._totalTriangleCount = triangleCount;
|
|
@@ -135,8 +136,11 @@ export class EmissiveTriangleBuilder {
|
|
|
135
136
|
|
|
136
137
|
this.emissiveCount = this.emissiveTriangles.length;
|
|
137
138
|
|
|
138
|
-
|
|
139
|
-
|
|
139
|
+
if ( this.emissiveCount > 0 ) {
|
|
140
|
+
|
|
141
|
+
log.debug( `${fmt.n( this.emissiveCount )} emissive tris (${( this.emissiveCount / triangleCount * 100 ).toFixed( 2 )}%) · total power ${this.totalEmissivePower.toFixed( 2 )}` );
|
|
142
|
+
|
|
143
|
+
}
|
|
140
144
|
|
|
141
145
|
// Build data arrays
|
|
142
146
|
this._buildDataArrays();
|
|
@@ -333,7 +337,7 @@ export class EmissiveTriangleBuilder {
|
|
|
333
337
|
texture.minFilter = NearestFilter;
|
|
334
338
|
texture.magFilter = NearestFilter;
|
|
335
339
|
|
|
336
|
-
|
|
340
|
+
log.debug( `emissive texture ${fmt.px( width, height )} · ${fmt.n( this.emissiveCount )} entries` );
|
|
337
341
|
|
|
338
342
|
return texture;
|
|
339
343
|
|
|
@@ -375,7 +379,7 @@ export class EmissiveTriangleBuilder {
|
|
|
375
379
|
|
|
376
380
|
}
|
|
377
381
|
|
|
378
|
-
|
|
382
|
+
log.debug( `emissive raw data ${fmt.n( this.emissiveCount )} entries (${fmt.mb( data.byteLength )})` );
|
|
379
383
|
|
|
380
384
|
return data;
|
|
381
385
|
|
|
@@ -611,7 +615,7 @@ export class EmissiveTriangleBuilder {
|
|
|
611
615
|
}
|
|
612
616
|
|
|
613
617
|
this.emissiveTriangleData = data;
|
|
614
|
-
|
|
618
|
+
log.debug( `rebuilt sorted emissive data · ${fmt.n( n )} entries` );
|
|
615
619
|
|
|
616
620
|
}
|
|
617
621
|
|