rayzee 7.16.0 → 7.18.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 +2817 -2537
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +55 -55
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineDefaults.js +27 -11
- package/src/Passes/OIDNDenoiser.js +17 -14
- package/src/PathTracerApp.js +86 -13
- 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 +69 -35
- 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/RenderSettings.js +1 -1
- package/src/Stages/ASVGF.js +38 -0
- package/src/Stages/BilateralFilter.js +23 -0
- package/src/Stages/EdgeFilter.js +18 -0
- package/src/Stages/MotionVector.js +23 -0
- package/src/Stages/NormalDepth.js +22 -0
- package/src/Stages/PathTracer.js +324 -169
- package/src/Stages/PathTracerStage.js +17 -14
- package/src/Stages/Variance.js +23 -0
- package/src/TSL/FinalWriteKernel.js +30 -18
- package/src/TSL/GenerateKernel.js +28 -17
- package/src/TSL/ShadeKernel.js +186 -87
- 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/UniformManager.js +1 -1
- 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
|
|
|
@@ -184,11 +185,26 @@ export const ENGINE_DEFAULTS = {
|
|
|
184
185
|
// only round-trip exactly when both sides agree.
|
|
185
186
|
export const ALBEDO_EPS = 0.01;
|
|
186
187
|
|
|
187
|
-
//
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
188
|
+
// Hard ceiling the engine supports for the reserved (pre-allocated) render size. 4K (3840×2160)
|
|
189
|
+
// fits within 4096². Raising the reserved size to this pins ~1.5 GB of MRT textures — opt-in only.
|
|
190
|
+
export const MAX_RESERVABLE_RENDER_SIZE = 4096;
|
|
191
|
+
|
|
192
|
+
// Reserved render size: every per-resolution compute StorageTexture + aux buffer is pre-allocated at
|
|
193
|
+
// this SQUARE size and never resized at runtime (works around three.js StorageTexture-resize bugs —
|
|
194
|
+
// see TSL/patches history). Render resolution must not exceed it; the engine warns + ignores larger.
|
|
195
|
+
// It is a LIVE binding (mutable) so the host can raise it (e.g. for 4K) BEFORE the pipeline/stages are
|
|
196
|
+
// constructed via setReservedRenderSize(); consumers read it inside their constructors. Default 2048
|
|
197
|
+
// (zero VRAM regression). See docs/internal/specs/wavefront-chunked-pool.md.
|
|
198
|
+
export let MAX_STORAGE_TEXTURE_SIZE = 2048;
|
|
199
|
+
|
|
200
|
+
// Set the reserved render size. MUST be called before pipeline construction (stages pre-allocate at
|
|
201
|
+
// this value and cannot resize). Clamped to [256, MAX_RESERVABLE_RENDER_SIZE]. Returns the applied value.
|
|
202
|
+
export function setReservedRenderSize( px ) {
|
|
203
|
+
|
|
204
|
+
MAX_STORAGE_TEXTURE_SIZE = Math.max( 256, Math.min( MAX_RESERVABLE_RENDER_SIZE, Math.floor( px ) || 256 ) );
|
|
205
|
+
return MAX_STORAGE_TEXTURE_SIZE;
|
|
206
|
+
|
|
207
|
+
}
|
|
192
208
|
|
|
193
209
|
export const ASVGF_QUALITY_PRESETS = {
|
|
194
210
|
// phiColor / phiDepth are RELATIVE tolerances (fractions). Bigger = more
|
|
@@ -575,11 +591,11 @@ export const PRODUCTION_RENDER_CONFIG = {
|
|
|
575
591
|
renderMode: 1, enableAlphaShadows: true,
|
|
576
592
|
enableOIDN: true, oidnQuality: 'balance',
|
|
577
593
|
interactionModeEnabled: false,
|
|
578
|
-
//
|
|
594
|
+
// √-norm convergence reaches a true ~all-converged gate, so stop at 0.98 — Cycles-faithful, leaving
|
|
595
|
+
// the unbiased residual for OIDN.
|
|
579
596
|
useAdaptiveSampling: true,
|
|
580
597
|
noiseThreshold: 0.1,
|
|
581
|
-
|
|
582
|
-
adaptiveStopFraction: 0.9,
|
|
598
|
+
adaptiveStopFraction: 0.98,
|
|
583
599
|
usePixelFreeze: true,
|
|
584
600
|
};
|
|
585
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
|
@@ -16,9 +16,10 @@ import { AutoExposure } from './Stages/AutoExposure.js';
|
|
|
16
16
|
import { Compositor } from './Stages/Compositor.js';
|
|
17
17
|
import { RenderPipeline } from './Pipeline/RenderPipeline.js';
|
|
18
18
|
import { CompletionTracker } from './Pipeline/CompletionTracker.js';
|
|
19
|
-
import { ENGINE_DEFAULTS as DEFAULT_STATE, PRODUCTION_RENDER_CONFIG, INTERACTIVE_RENDER_CONFIG, MAX_STORAGE_TEXTURE_SIZE } from './EngineDefaults.js';
|
|
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
20
|
import { updateStats, updateLoading, resetLoading, setStatusCallback, getDisplaySamples, disposeObjectFromMemory } 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';
|
|
@@ -57,6 +58,9 @@ const _appsByCanvas = new WeakMap();
|
|
|
57
58
|
*
|
|
58
59
|
* Extends EventDispatcher for event-driven communication with stores/UI.
|
|
59
60
|
*/
|
|
61
|
+
|
|
62
|
+
const log = createLogger( 'engine' );
|
|
63
|
+
|
|
60
64
|
export class PathTracerApp extends EventDispatcher {
|
|
61
65
|
|
|
62
66
|
/**
|
|
@@ -79,7 +83,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
79
83
|
|
|
80
84
|
} catch ( err ) {
|
|
81
85
|
|
|
82
|
-
|
|
86
|
+
log.warn( 'prior canvas owner dispose failed', err );
|
|
83
87
|
|
|
84
88
|
}
|
|
85
89
|
|
|
@@ -188,7 +192,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
188
192
|
|
|
189
193
|
} catch ( err ) {
|
|
190
194
|
|
|
191
|
-
|
|
195
|
+
log.warn( 'failed to remove listener', type, err );
|
|
192
196
|
|
|
193
197
|
}
|
|
194
198
|
|
|
@@ -222,7 +226,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
222
226
|
this.stages.pathTracer.setupMaterial();
|
|
223
227
|
|
|
224
228
|
this.isInitialized = true;
|
|
225
|
-
|
|
229
|
+
log.debug( 'WebGPU path tracer app initialized' );
|
|
226
230
|
|
|
227
231
|
return this;
|
|
228
232
|
|
|
@@ -256,7 +260,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
256
260
|
|
|
257
261
|
this._animRefitInFlight = true;
|
|
258
262
|
this.refitBVH( positions )
|
|
259
|
-
.catch( err =>
|
|
263
|
+
.catch( err => log.error( 'animation refit error:', err ) )
|
|
260
264
|
.finally( () => {
|
|
261
265
|
|
|
262
266
|
this._animRefitInFlight = false;
|
|
@@ -384,7 +388,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
384
388
|
|
|
385
389
|
if ( this._deviceLost ) return;
|
|
386
390
|
this._deviceLost = true;
|
|
387
|
-
|
|
391
|
+
log.error( `WebGPU device lost (${info?.reason || 'unknown'}): ${info?.message || ''}` );
|
|
388
392
|
this.stopAnimation();
|
|
389
393
|
this.dispatchEvent( { type: EngineEvents.DEVICE_LOST, reason: info?.reason, message: info?.message } );
|
|
390
394
|
|
|
@@ -545,7 +549,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
545
549
|
|
|
546
550
|
} catch ( err ) {
|
|
547
551
|
|
|
548
|
-
|
|
552
|
+
log.warn( 'failed to clear TSL texture singleton listeners', err );
|
|
549
553
|
|
|
550
554
|
}
|
|
551
555
|
|
|
@@ -803,7 +807,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
803
807
|
// Tag the primary (replace-loaded) model so it appears in the scene-object list.
|
|
804
808
|
this._tagPrimarySceneObject();
|
|
805
809
|
|
|
806
|
-
const timer = new BuildTimer( '
|
|
810
|
+
const timer = new BuildTimer( '', { namespace: 'scene', level: 'info' } );
|
|
807
811
|
const environmentTexture = this.meshScene.environment;
|
|
808
812
|
|
|
809
813
|
// Environment CDF build in parallel with BVH
|
|
@@ -856,7 +860,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
856
860
|
|
|
857
861
|
} catch ( err ) {
|
|
858
862
|
|
|
859
|
-
|
|
863
|
+
log.warn( 'raster fallback precompile failed', err );
|
|
860
864
|
|
|
861
865
|
}
|
|
862
866
|
|
|
@@ -896,7 +900,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
896
900
|
this.stages.compositor.setTransparentBackground( this.settings.get( 'transparentBackground' ) );
|
|
897
901
|
timer.end( 'Apply settings' );
|
|
898
902
|
|
|
899
|
-
timer.print();
|
|
903
|
+
timer.print( this._sceneSummaryParts() );
|
|
900
904
|
resetLoading();
|
|
901
905
|
|
|
902
906
|
this._initAnimationAndTransforms();
|
|
@@ -906,6 +910,23 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
906
910
|
|
|
907
911
|
}
|
|
908
912
|
|
|
913
|
+
/** Counts for the single `[scene]` summary line emitted after a scene build. */
|
|
914
|
+
_sceneSummaryParts() {
|
|
915
|
+
|
|
916
|
+
const pt = this.stages.pathTracer;
|
|
917
|
+
const meshes = this._sdf?.instanceTable?.entries?.filter( Boolean ).length ?? 0;
|
|
918
|
+
const maps = this._sdf?.geometryExtractor?.maps?.length ?? 0;
|
|
919
|
+
|
|
920
|
+
return [
|
|
921
|
+
fmt.count( pt.triangleCount, 'tri' ),
|
|
922
|
+
meshes ? fmt.count( meshes, 'mesh', 'meshes' ) : null,
|
|
923
|
+
fmt.count( pt.materialData.materialCount, 'material' ),
|
|
924
|
+
maps ? fmt.count( maps, 'map' ) : null,
|
|
925
|
+
fmt.count( pt.bvhNodeCount, 'BVH node' ),
|
|
926
|
+
];
|
|
927
|
+
|
|
928
|
+
}
|
|
929
|
+
|
|
909
930
|
// ═══════════════════════════════════════════════════════════════
|
|
910
931
|
// Dynamic scene objects (add / remove / list / visibility)
|
|
911
932
|
//
|
|
@@ -1202,6 +1223,56 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1202
1223
|
// Resize
|
|
1203
1224
|
// ═══════════════════════════════════════════════════════════════
|
|
1204
1225
|
|
|
1226
|
+
/**
|
|
1227
|
+
* Raise (or lower) the reserved render size — the square dimension every compute StorageTexture + aux
|
|
1228
|
+
* buffer is pre-allocated at. Needed to enable resolutions above the 2048 default (e.g. 4K). The request
|
|
1229
|
+
* is device-capped: 4K reservation (~1.5 GB of MRT textures) is only granted on GPUs with ample VRAM +
|
|
1230
|
+
* a large storage-buffer binding limit; weaker devices clamp to 2048.
|
|
1231
|
+
*
|
|
1232
|
+
* MUST be called before the pipeline/stages are constructed (they pre-allocate at this value and cannot
|
|
1233
|
+
* resize). Calling it after init only takes effect on the next full pipeline (re)build.
|
|
1234
|
+
* @param {number} requestedPx desired reserved size (longest edge)
|
|
1235
|
+
* @returns {number} the applied reserved size
|
|
1236
|
+
*/
|
|
1237
|
+
setReservedRenderResolution( requestedPx ) {
|
|
1238
|
+
|
|
1239
|
+
const limits = this.renderer?.backend?.device?.limits;
|
|
1240
|
+
const maxBinding = limits?.maxStorageBufferBindingSize || ( 128 * 1024 * 1024 );
|
|
1241
|
+
const deviceMemGB = ( typeof navigator !== 'undefined' && navigator.deviceMemory ) || 4;
|
|
1242
|
+
// 4K reserve pins the accum MRT (~1.5 GB) + aux; only grant it on clearly-capable GPUs.
|
|
1243
|
+
const deviceSafeMax = ( deviceMemGB >= 8 && maxBinding >= 1024 * 1024 * 1024 )
|
|
1244
|
+
? MAX_RESERVABLE_RENDER_SIZE : 2048;
|
|
1245
|
+
const prev = MAX_STORAGE_TEXTURE_SIZE;
|
|
1246
|
+
const applied = setReservedRenderSize( Math.min( requestedPx, deviceSafeMax ) );
|
|
1247
|
+
|
|
1248
|
+
// If the pipeline is already built and the reserved size changed, re-init the reserved GPU storage in
|
|
1249
|
+
// place: each stage recreates its pre-allocated StorageTextures at the new size + rebuilds its compute
|
|
1250
|
+
// pipelines. Stage OBJECTS, manager refs and event wiring are preserved (so no re-subscription needed);
|
|
1251
|
+
// scene geometry buffers are resolution-independent and reused. Rendering is paused across the swap so
|
|
1252
|
+
// no in-flight dispatch references a disposed texture.
|
|
1253
|
+
if ( applied !== prev && this.stages?.pathTracer?._packedBuffers ) {
|
|
1254
|
+
|
|
1255
|
+
const wasPaused = this.pauseRendering;
|
|
1256
|
+
this.pauseRendering = true;
|
|
1257
|
+
try {
|
|
1258
|
+
|
|
1259
|
+
for ( const stage of Object.values( this.stages ) ) stage?.reallocateReservedStorage?.();
|
|
1260
|
+
|
|
1261
|
+
} finally {
|
|
1262
|
+
|
|
1263
|
+
this.pauseRendering = wasPaused;
|
|
1264
|
+
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
this.reset();
|
|
1268
|
+
this.dispatchEvent( { type: 'reserved_render_size_changed', size: applied } );
|
|
1269
|
+
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
return applied;
|
|
1273
|
+
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1205
1276
|
/**
|
|
1206
1277
|
* Guard against render resolutions the compute pipeline can't support.
|
|
1207
1278
|
* Per-resolution StorageTextures are pre-allocated at MAX_STORAGE_TEXTURE_SIZE
|
|
@@ -1212,7 +1283,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1212
1283
|
|
|
1213
1284
|
if ( width > MAX_STORAGE_TEXTURE_SIZE || height > MAX_STORAGE_TEXTURE_SIZE ) {
|
|
1214
1285
|
|
|
1215
|
-
|
|
1286
|
+
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}.` );
|
|
1216
1287
|
return false;
|
|
1217
1288
|
|
|
1218
1289
|
}
|
|
@@ -1311,7 +1382,6 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1311
1382
|
// engine defaults when a config doesn't override them.
|
|
1312
1383
|
this.stages.pathTracer?.setUniform( 'useAdaptiveSampling', config.useAdaptiveSampling ?? false );
|
|
1313
1384
|
this.stages.pathTracer?.setUniform( 'noiseThreshold', config.noiseThreshold ?? DEFAULT_STATE.noiseThreshold );
|
|
1314
|
-
this.stages.pathTracer?.setUniform( 'darkNoiseFloor', config.darkNoiseFloor ?? DEFAULT_STATE.darkNoiseFloor );
|
|
1315
1385
|
this.stages.pathTracer?.setUniform( 'adaptiveStopFraction', config.adaptiveStopFraction ?? DEFAULT_STATE.adaptiveStopFraction );
|
|
1316
1386
|
this.stages.pathTracer?.setUniform( 'adaptiveMinSamples', config.adaptiveMinSamples ?? DEFAULT_STATE.adaptiveMinSamples );
|
|
1317
1387
|
|
|
@@ -1339,6 +1409,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1339
1409
|
|
|
1340
1410
|
if ( options.canvasWidth && options.canvasHeight ) {
|
|
1341
1411
|
|
|
1412
|
+
// Raise the reserved storage first so a > 2048 final-render resolution (4K) fits (device-capped,
|
|
1413
|
+
// in-place re-init). No-op when the size already fits.
|
|
1414
|
+
this.setReservedRenderResolution( Math.max( options.canvasWidth, options.canvasHeight ) );
|
|
1342
1415
|
this.setCanvasSize( options.canvasWidth, options.canvasHeight );
|
|
1343
1416
|
|
|
1344
1417
|
}
|
|
@@ -1766,7 +1839,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1766
1839
|
this._handleDeviceLost( info );
|
|
1767
1840
|
|
|
1768
1841
|
} );
|
|
1769
|
-
gpuDevice.onuncapturederror = ( event ) =>
|
|
1842
|
+
gpuDevice.onuncapturederror = ( event ) => log.error( 'WebGPU uncaptured error:', event.error );
|
|
1770
1843
|
|
|
1771
1844
|
}
|
|
1772
1845
|
|
|
@@ -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
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import { Vector3, Vector2, Color, Matrix3, Matrix4, FrontSide, BackSide, DoubleSide, RGBAFormat } from "three";
|
|
2
2
|
import { TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
|
|
3
|
+
import { createLogger, fmt, warnOnce } from '../utils/Logger.js';
|
|
4
|
+
|
|
5
|
+
const log = createLogger( 'geometry' );
|
|
3
6
|
|
|
4
7
|
const MAX_TEXTURES_LIMIT = 128;
|
|
5
8
|
|
|
@@ -169,7 +172,8 @@ export class GeometryExtractor {
|
|
|
169
172
|
if ( material.depthWrite === false ) {
|
|
170
173
|
|
|
171
174
|
material.depthWrite = true;
|
|
172
|
-
|
|
175
|
+
// Fires per offending material; the fix is identical every time, so warn once.
|
|
176
|
+
warnOnce( log, 'depthWrite', 'material had depthWrite disabled — enabled for rastered rendering' );
|
|
173
177
|
|
|
174
178
|
}
|
|
175
179
|
|
|
@@ -758,10 +762,12 @@ export class GeometryExtractor {
|
|
|
758
762
|
logStats() {
|
|
759
763
|
|
|
760
764
|
const usedBytes = this.currentTriangleIndex * TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE * 4;
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
+
|
|
766
|
+
log.debug( fmt.list( [
|
|
767
|
+
`${fmt.count( this.currentTriangleIndex, 'tri' )} (${fmt.mb( usedBytes )})`,
|
|
768
|
+
fmt.count( this.materials.length, 'material' ),
|
|
769
|
+
fmt.count( this.maps.length, 'map' ),
|
|
770
|
+
] ) );
|
|
765
771
|
|
|
766
772
|
}
|
|
767
773
|
|