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
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
* dispatch interface that wraps `renderer.compute(node)`.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { createLogger, fmt } from '../utils/Logger.js';
|
|
12
|
+
|
|
13
|
+
const log = createLogger( 'wavefront' );
|
|
14
|
+
|
|
11
15
|
/** Default workgroup sizes per kernel type */
|
|
12
16
|
const WORKGROUP_SIZES = {
|
|
13
17
|
generate: [ 16, 16, 1 ], // 2D screen-space
|
|
@@ -62,6 +66,14 @@ export class KernelManager {
|
|
|
62
66
|
*/
|
|
63
67
|
this.profile = new Map();
|
|
64
68
|
|
|
69
|
+
/**
|
|
70
|
+
* First-dispatch (shader compilation) timings, buffered so a build reports
|
|
71
|
+
* one line instead of one per kernel. See _recordFirstDispatch.
|
|
72
|
+
* @type {Array<{name: string, ms: number}>}
|
|
73
|
+
*/
|
|
74
|
+
this._firstDispatches = [];
|
|
75
|
+
this._firstDispatchFlush = null;
|
|
76
|
+
|
|
65
77
|
// Initialize workgroup sizes from defaults
|
|
66
78
|
for ( const [ name, wgSize ] of Object.entries( WORKGROUP_SIZES ) ) {
|
|
67
79
|
|
|
@@ -71,6 +83,38 @@ export class KernelManager {
|
|
|
71
83
|
|
|
72
84
|
}
|
|
73
85
|
|
|
86
|
+
/**
|
|
87
|
+
* Buffers a kernel's first-dispatch cost and flushes the batch on the next macrotask,
|
|
88
|
+
* so the kernels compiled during one frame collapse into a single summary line.
|
|
89
|
+
*/
|
|
90
|
+
_recordFirstDispatch( name, ms ) {
|
|
91
|
+
|
|
92
|
+
this._firstDispatches.push( { name, ms } );
|
|
93
|
+
|
|
94
|
+
clearTimeout( this._firstDispatchFlush );
|
|
95
|
+
this._firstDispatchFlush = setTimeout( () => this._flushFirstDispatches(), 0 );
|
|
96
|
+
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_flushFirstDispatches() {
|
|
100
|
+
|
|
101
|
+
const batch = this._firstDispatches;
|
|
102
|
+
this._firstDispatches = [];
|
|
103
|
+
this._firstDispatchFlush = null;
|
|
104
|
+
if ( batch.length === 0 ) return;
|
|
105
|
+
|
|
106
|
+
const total = batch.reduce( ( sum, k ) => sum + k.ms, 0 );
|
|
107
|
+
const slowest = [ ...batch ].sort( ( a, b ) => b.ms - a.ms );
|
|
108
|
+
|
|
109
|
+
log.info( fmt.list( [
|
|
110
|
+
`${batch.length} kernel${batch.length === 1 ? '' : 's'} compiled in ${fmt.ms( total )}`,
|
|
111
|
+
`slowest ${slowest.slice( 0, 2 ).map( k => `${k.name} ${fmt.ms( k.ms )}` ).join( ', ' )}`,
|
|
112
|
+
] ) );
|
|
113
|
+
|
|
114
|
+
batch.forEach( k => log.debug( `kernel '${k.name}' first dispatch (incl. compilation) ${fmt.ms( k.ms )}` ) );
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
|
|
74
118
|
/**
|
|
75
119
|
* Register a pre-built compute node.
|
|
76
120
|
* @param {string} name - Kernel name (e.g. 'generate', 'extend')
|
|
@@ -106,7 +150,7 @@ export class KernelManager {
|
|
|
106
150
|
const t1 = performance.now();
|
|
107
151
|
timingEntry.compiledOnce = true;
|
|
108
152
|
timingEntry.lastDispatchMs = t1 - t0;
|
|
109
|
-
|
|
153
|
+
this._recordFirstDispatch( name, t1 - t0 );
|
|
110
154
|
|
|
111
155
|
} else if ( this.profiling ) {
|
|
112
156
|
|
|
@@ -266,6 +310,9 @@ export class KernelManager {
|
|
|
266
310
|
|
|
267
311
|
dispose() {
|
|
268
312
|
|
|
313
|
+
clearTimeout( this._firstDispatchFlush );
|
|
314
|
+
this._firstDispatchFlush = null;
|
|
315
|
+
this._firstDispatches = [];
|
|
269
316
|
this.kernels.clear();
|
|
270
317
|
this.timing.clear();
|
|
271
318
|
this.profile.clear();
|
|
@@ -4,10 +4,13 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import {
|
|
7
|
-
storage, uintBitsToFloat, floatBitsToUint, vec2, vec3, vec4, uvec4, uint, int,
|
|
7
|
+
storage, uintBitsToFloat, floatBitsToUint, vec2, vec3, vec4, uvec4, uint, int, float, clamp,
|
|
8
8
|
packSnorm2x16, packUnorm2x16, unpackSnorm2x16, unpackUnorm2x16,
|
|
9
9
|
} from 'three/tsl';
|
|
10
10
|
import { StorageInstancedBufferAttribute } from 'three/webgpu';
|
|
11
|
+
import { createLogger, fmt } from '../utils/Logger.js';
|
|
12
|
+
|
|
13
|
+
const log = createLogger( 'gpu' );
|
|
11
14
|
|
|
12
15
|
export const RAY_STRIDE = 7;
|
|
13
16
|
export const HIT_STRIDE = 2;
|
|
@@ -19,12 +22,12 @@ export const HIT_STRIDE = 2;
|
|
|
19
22
|
export const GBUFFER_STRIDE = 1;
|
|
20
23
|
|
|
21
24
|
export const RAY = {
|
|
22
|
-
ORIGIN_META: 0, // vec4(origin.xyz, uintBitsToFloat(perRayBounces | sssSteps<<8)); pixelIndex == rayID
|
|
25
|
+
ORIGIN_META: 0, // vec4(origin.xyz, uintBitsToFloat(perRayBounces | sssSteps<<8 | lobeCounts<<16)); pixelIndex == rayID
|
|
23
26
|
DIR_FLAGS: 1, // vec4(direction.xyz, uintBitsToFloat(bounceFlags))
|
|
24
27
|
THROUGHPUT_PDF: 2, // vec4(throughput.xyz, pdf)
|
|
25
28
|
RADIANCE_ALPHA: 3, // vec4(radiance.xyz, alpha)
|
|
26
29
|
MEDIUM_STACK: 4, // vec4(uintBitsToFloat(stackDepth|transTraversals<<8|wavelength<<16), ior1, ior2, ior3)
|
|
27
|
-
MEDIUM_SIGMA_A: 5, // vec4(sigmaA.xyz,
|
|
30
|
+
MEDIUM_SIGMA_A: 5, // vec4(sigmaA.xyz, featTP.w) — Beer-Lambert absorption (KHR_materials_volume + SSS) + DDFA feature-throughput tint
|
|
28
31
|
SSS_SIGMA_S: 6, // vec4(sigmaS.xyz, g) — SSS scattering coeff + Henyey-Greenstein anisotropy (sigmaS==0 ⇒ glass)
|
|
29
32
|
};
|
|
30
33
|
|
|
@@ -50,16 +53,11 @@ export function freeStorageAttribute( renderer, attr ) {
|
|
|
50
53
|
|
|
51
54
|
export class PackedRayBuffer {
|
|
52
55
|
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return Math.ceil( maxRays * 1.25 );
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
constructor( maxRays = 0, renderer = null ) {
|
|
56
|
+
// `capacity` is the exact paths-in-flight budget B (Blender-style path pool). It is DECOUPLED from
|
|
57
|
+
// image resolution and fixed for the device — the image is streamed through the pool in row bands
|
|
58
|
+
// (see docs/internal/specs/wavefront-chunked-pool.md), so the SoA stride _cap never changes with
|
|
59
|
+
// resolution and the wavefront kernels are built once (no resize-triggered WGSL regen).
|
|
60
|
+
constructor( capacity = 0, renderer = null ) {
|
|
63
61
|
|
|
64
62
|
this.capacity = 0;
|
|
65
63
|
this._renderer = renderer;
|
|
@@ -70,15 +68,14 @@ export class PackedRayBuffer {
|
|
|
70
68
|
this.rngBuffer = null;
|
|
71
69
|
this.hitBuffer = null;
|
|
72
70
|
|
|
73
|
-
if (
|
|
71
|
+
if ( capacity > 0 ) this.allocate( capacity );
|
|
74
72
|
|
|
75
73
|
}
|
|
76
74
|
|
|
77
|
-
allocate(
|
|
75
|
+
allocate( capacity ) {
|
|
78
76
|
|
|
79
77
|
this.dispose();
|
|
80
78
|
|
|
81
|
-
const capacity = Math.ceil( maxRays * 1.25 );
|
|
82
79
|
this.capacity = capacity;
|
|
83
80
|
_cap = capacity;
|
|
84
81
|
|
|
@@ -106,23 +103,20 @@ export class PackedRayBuffer {
|
|
|
106
103
|
ro: storage( hitAttr, 'vec4' ).toReadOnly(),
|
|
107
104
|
};
|
|
108
105
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
// Kept for the [gpu] startup summary in PathTracer._buildWavefrontKernels.
|
|
107
|
+
this.rayBytes = rayCount * 16 + capacity * 4;
|
|
108
|
+
this.hitBytes = hitCount * 16;
|
|
109
|
+
this.totalBytes = this.rayBytes + this.hitBytes;
|
|
112
110
|
|
|
113
|
-
|
|
114
|
-
`PackedRayBuffer: capacity=${capacity}, total=${totalMB.toFixed( 1 )} MB ` +
|
|
115
|
-
`(ray=${( rayCount * 16 / 1048576 ).toFixed( 0 )}MB hit=${( hitCount * 16 / 1048576 ).toFixed( 0 )}MB) [SoA ray/hit]`
|
|
116
|
-
);
|
|
111
|
+
log.debug( `ray pool capacity ${fmt.n( capacity )} · ${fmt.mb( this.totalBytes )} (rays ${fmt.mb( this.rayBytes )} · hits ${fmt.mb( this.hitBytes )})` );
|
|
117
112
|
|
|
118
113
|
}
|
|
119
114
|
|
|
120
|
-
// Reallocates only if
|
|
121
|
-
resize(
|
|
115
|
+
// Reallocates only if a larger budget capacity is requested; returns true if it did.
|
|
116
|
+
resize( capacity ) {
|
|
122
117
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
this.allocate( maxRays );
|
|
118
|
+
if ( capacity <= this.capacity && this.capacity > 0 ) return false;
|
|
119
|
+
this.allocate( capacity );
|
|
126
120
|
return true;
|
|
127
121
|
|
|
128
122
|
}
|
|
@@ -189,12 +183,15 @@ export const gbDecodeNormalDepth = ( packed ) => {
|
|
|
189
183
|
export const gbDecodeAlbedo = ( packed ) =>
|
|
190
184
|
vec3( unpackUnorm2x16( packed.z ), unpackUnorm2x16( packed.w ).x );
|
|
191
185
|
|
|
192
|
-
// .w packs per-ray bounce state: perRayBounces (bits 0-7) | sssSteps (bits 8-15)
|
|
193
|
-
//
|
|
194
|
-
|
|
186
|
+
// .w packs per-ray bounce state: perRayBounces (bits 0-7) | sssSteps (bits 8-15) | transparentCount
|
|
187
|
+
// (bits 16-21), each masked so an overrun can't bleed into its neighbour. pixelIndex is NOT stored —
|
|
188
|
+
// it equals rayID (one ray per pixel).
|
|
189
|
+
export const writeRayOriginMeta = ( buf, id, origin, bounces, sssSteps, transparentCount = uint( 0 ) ) =>
|
|
195
190
|
buf.element( soa( id, RAY.ORIGIN_META ) )
|
|
196
191
|
.assign( vec4( origin, uintBitsToFloat(
|
|
197
|
-
uint( bounces ).
|
|
192
|
+
uint( bounces ).bitAnd( uint( 0xFF ) )
|
|
193
|
+
.bitOr( uint( sssSteps ).bitAnd( uint( 0xFF ) ).shiftLeft( 8 ) )
|
|
194
|
+
.bitOr( uint( transparentCount ).bitAnd( uint( 0x3F ) ).shiftLeft( 16 ) )
|
|
198
195
|
) ) );
|
|
199
196
|
|
|
200
197
|
export const writeRayDirFlags = ( buf, id, direction, bounceFlags ) =>
|
|
@@ -258,20 +255,57 @@ export const writeMediumStack = ( buf, id, stackDepth, transTraversals, ior1, io
|
|
|
258
255
|
stackDepth.bitOr( transTraversals.shiftLeft( 8 ) ).bitOr( wavelength.shiftLeft( 16 ) )
|
|
259
256
|
), ior1, ior2, ior3 ) );
|
|
260
257
|
|
|
261
|
-
// Region 7: Beer-Lambert sigmaA of the active medium
|
|
258
|
+
// Region 7: Beer-Lambert sigmaA (.xyz) of the active medium + DDFA feature-throughput (.w).
|
|
259
|
+
// .xyz absorption is gated on stackDepth>0; .w carries the OIDN/ASVGF see-through aux tint (see below).
|
|
260
|
+
// Both writers RMW-preserve the other half so sigmaA (live in glass) and featTP never clobber each other.
|
|
262
261
|
export const readMediumSigmaA = ( buf, id ) => buf.element( soa( id, RAY.MEDIUM_SIGMA_A ) ).xyz;
|
|
263
262
|
|
|
264
|
-
export const writeMediumSigmaA = ( buf, id, sigmaA ) =>
|
|
265
|
-
|
|
263
|
+
export const writeMediumSigmaA = ( buf, id, sigmaA ) => {
|
|
264
|
+
|
|
265
|
+
const keepW = buf.element( soa( id, RAY.MEDIUM_SIGMA_A ) ).w.toVar();
|
|
266
|
+
buf.element( soa( id, RAY.MEDIUM_SIGMA_A ) ).assign( vec4( sigmaA, keepW ) );
|
|
267
|
+
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// DDFA (Deferred Denoising-Feature Aux) feature-throughput: a clean per-ray tint carried through smooth
|
|
271
|
+
// glass/mirror, committed into the OIDN/ASVGF albedo guide at the first diffuse-enough surface. Rides the
|
|
272
|
+
// otherwise-dead MEDIUM_SIGMA_A.w, packed 10/10/10 into 30 bits (top 2 bits zero ⇒ the float bit-pattern is
|
|
273
|
+
// always finite — never NaN/Inf). LDR [0,1] per channel; final albedo re-quantizes to unorm16 anyway.
|
|
274
|
+
const pack30 = ( tp ) => {
|
|
275
|
+
|
|
276
|
+
const c = clamp( tp, vec3( 0.0 ), vec3( 1.0 ) ).mul( 1023.0 ).add( 0.5 );
|
|
277
|
+
return uint( c.x ).bitOr( uint( c.y ).shiftLeft( 10 ) ).bitOr( uint( c.z ).shiftLeft( 20 ) );
|
|
278
|
+
|
|
279
|
+
};
|
|
280
|
+
|
|
281
|
+
const unpack30 = ( u ) => vec3(
|
|
282
|
+
float( u.bitAnd( uint( 1023 ) ) ),
|
|
283
|
+
float( u.shiftRight( 10 ).bitAnd( uint( 1023 ) ) ),
|
|
284
|
+
float( u.shiftRight( 20 ).bitAnd( uint( 1023 ) ) ),
|
|
285
|
+
).div( 1023.0 );
|
|
286
|
+
|
|
287
|
+
export const readFeatureThroughput = ( buf, id ) =>
|
|
288
|
+
unpack30( floatBitsToUint( buf.element( soa( id, RAY.MEDIUM_SIGMA_A ) ).w ) );
|
|
289
|
+
|
|
290
|
+
export const writeFeatureThroughput = ( buf, id, tp ) => {
|
|
291
|
+
|
|
292
|
+
const keepXYZ = buf.element( soa( id, RAY.MEDIUM_SIGMA_A ) ).xyz.toVar();
|
|
293
|
+
buf.element( soa( id, RAY.MEDIUM_SIGMA_A ) ).assign( vec4( keepXYZ, uintBitsToFloat( pack30( tp ) ) ) );
|
|
294
|
+
|
|
295
|
+
};
|
|
266
296
|
|
|
267
297
|
// Per-ray bounce state packed into ORIGIN_META.w (written by writeRayOriginMeta alongside the origin):
|
|
268
298
|
// perRayBounces = bits 0-7 (camera-bounce depth; the loop index can't track it once free bounces decouple it)
|
|
269
299
|
// sssSteps = bits 8-15 (SSS random-walk step counter)
|
|
300
|
+
// transparentCount = bits 16-21 (alpha-passthrough counter, max 63; see maxTransparentBounces)
|
|
270
301
|
export const readPathBounces = ( buf, id ) =>
|
|
271
302
|
int( floatBitsToUint( buf.element( soa( id, RAY.ORIGIN_META ) ).w ).bitAnd( 0xFF ) );
|
|
272
303
|
export const readSssSteps = ( buf, id ) =>
|
|
273
304
|
int( floatBitsToUint( buf.element( soa( id, RAY.ORIGIN_META ) ).w ).shiftRight( 8 ).bitAnd( 0xFF ) );
|
|
274
305
|
|
|
306
|
+
export const readTransparentCount = ( buf, id ) =>
|
|
307
|
+
int( floatBitsToUint( buf.element( soa( id, RAY.ORIGIN_META ) ).w ).shiftRight( 16 ).bitAnd( uint( 0x3F ) ) );
|
|
308
|
+
|
|
275
309
|
// Region 9: SSS sigmaS + Henyey-Greenstein g. sigmaS==0 marks glass (Beer-Lambert path, not random walk).
|
|
276
310
|
export const readSSSMedium = ( buf, id ) => {
|
|
277
311
|
|
|
@@ -10,6 +10,9 @@
|
|
|
10
10
|
import { fetchAsWorker } from './Workers/fetchAsWorker.js';
|
|
11
11
|
import BVH_WORKER_URL from './Workers/BVHWorker.js?worker&url';
|
|
12
12
|
import BVH_SUBTREE_WORKER_URL from './Workers/BVHSubtreeWorker.js?worker&url';
|
|
13
|
+
import { createLogger, fmt } from '../utils/Logger.js';
|
|
14
|
+
|
|
15
|
+
const log = createLogger( 'bvh' );
|
|
13
16
|
|
|
14
17
|
const FPT = 32; // FLOATS_PER_TRIANGLE
|
|
15
18
|
const PARALLEL_THRESHOLD = 50000;
|
|
@@ -34,7 +37,7 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
|
|
|
34
37
|
const numWorkers = Math.min( navigator.hardwareConcurrency || 4, MAX_PARALLEL_WORKERS );
|
|
35
38
|
const parallelDepth = Math.ceil( Math.log2( numWorkers * 2.5 + 1 ) );
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
log.debug( `parallel build ${fmt.n( triangleCount )} triangles · ${numWorkers} workers · parallelDepth ${parallelDepth}` );
|
|
38
41
|
|
|
39
42
|
return new Promise( ( resolve, reject ) => {
|
|
40
43
|
|
|
@@ -98,7 +101,7 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
|
|
|
98
101
|
|
|
99
102
|
if ( settled ) return;
|
|
100
103
|
settled = true;
|
|
101
|
-
|
|
104
|
+
log.warn( `parallel build failed (${reason}), falling back to single worker` );
|
|
102
105
|
cleanup();
|
|
103
106
|
// Copy from SharedArrayBuffer to regular ArrayBuffer for transfer
|
|
104
107
|
const restoredBuffer = new ArrayBuffer( sharedTriangleData.byteLength );
|
|
@@ -182,7 +185,7 @@ export function buildBVHParallel( triangles, depth, progressCallback, config ) {
|
|
|
182
185
|
|
|
183
186
|
} )().catch( ( error ) => {
|
|
184
187
|
|
|
185
|
-
|
|
188
|
+
log.warn( 'parallel build setup failed:', error );
|
|
186
189
|
reject( error );
|
|
187
190
|
|
|
188
191
|
} );
|
|
@@ -209,7 +212,7 @@ async function handlePhase2(
|
|
|
209
212
|
|
|
210
213
|
// No frontier tasks — top-level tree is the complete BVH
|
|
211
214
|
// Still need to reorder triangles via coordinator
|
|
212
|
-
|
|
215
|
+
log.debug( 'no frontier tasks, assembling with top-level tree only' );
|
|
213
216
|
coordinatorWorker.postMessage( {
|
|
214
217
|
type: 'assemble',
|
|
215
218
|
topFlatData,
|
|
@@ -225,7 +228,7 @@ async function handlePhase2(
|
|
|
225
228
|
|
|
226
229
|
}
|
|
227
230
|
|
|
228
|
-
|
|
231
|
+
log.debug( `phase 2 distributing ${frontierTasks.length} tasks across ${numWorkers} workers` );
|
|
229
232
|
|
|
230
233
|
// Distribute tasks using greedy least-loaded assignment
|
|
231
234
|
const sortedTasks = [ ...frontierTasks ].sort( ( a, b ) => ( b.end - b.start ) - ( a.end - a.start ) );
|
|
@@ -493,7 +496,7 @@ function buildSingleWorker( triangles, depth, progressCallback, config ) {
|
|
|
493
496
|
|
|
494
497
|
} )().catch( ( error ) => {
|
|
495
498
|
|
|
496
|
-
|
|
499
|
+
log.warn( 'single worker fallback failed:', error );
|
|
497
500
|
reject( error );
|
|
498
501
|
|
|
499
502
|
} );
|
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
import { storage } from 'three/tsl';
|
|
6
6
|
import { StorageInstancedBufferAttribute } from 'three/webgpu';
|
|
7
7
|
import { freeStorageAttribute } from './PackedRayBuffer.js';
|
|
8
|
+
import { createLogger, fmt } from '../utils/Logger.js';
|
|
9
|
+
|
|
10
|
+
const log = createLogger( 'gpu' );
|
|
8
11
|
|
|
9
12
|
/** Counter indices — must match ResetCounters kernel */
|
|
10
13
|
export const COUNTER = {
|
|
@@ -109,15 +112,10 @@ export class QueueManager {
|
|
|
109
112
|
|
|
110
113
|
this.pingPong = 0;
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
capacity * 4 * 3
|
|
115
|
-
);
|
|
115
|
+
// Kept for the [gpu] startup summary in PathTracer._buildWavefrontKernels.
|
|
116
|
+
this.totalBytes = COUNTER.COUNT * 4 + capacity * 4 * 3;
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
`QueueManager: Allocated capacity=${capacity}, ` +
|
|
119
|
-
`total=${( totalBytes / ( 1024 * 1024 ) ).toFixed( 1 )} MB`
|
|
120
|
-
);
|
|
118
|
+
log.debug( `queues capacity ${fmt.n( capacity )} · ${fmt.mb( this.totalBytes )}` );
|
|
121
119
|
|
|
122
120
|
}
|
|
123
121
|
|
|
@@ -9,12 +9,15 @@ import { GeometryExtractor } from './GeometryExtractor.js';
|
|
|
9
9
|
import { EmissiveTriangleBuilder } from './EmissiveTriangleBuilder.js';
|
|
10
10
|
import { updateLoading } from '../Processor/utils.js';
|
|
11
11
|
import { BuildTimer } from './BuildTimer.js';
|
|
12
|
+
import { createLogger, fmt, workerLogLevel } from '../utils/Logger.js';
|
|
12
13
|
import { SRGBColorSpace } from 'three';
|
|
13
14
|
import { TRIANGLE_DATA_LAYOUT, TEXTURE_CONSTANTS, getTextureBucketId, packTextureIndex } from '../EngineDefaults.js';
|
|
14
15
|
import { fetchAsWorker } from './Workers/fetchAsWorker.js';
|
|
15
16
|
import BVH_WORKER_URL from './Workers/BVHWorker.js?worker&url';
|
|
16
17
|
import BVH_REFIT_WORKER_URL from './Workers/BVHRefitWorker.js?worker&url';
|
|
17
18
|
|
|
19
|
+
const log = createLogger( 'scene' );
|
|
20
|
+
|
|
18
21
|
/**
|
|
19
22
|
* SceneProcessor - Processes scene geometry into GPU-ready data:
|
|
20
23
|
* BVH acceleration, texture atlas, material buffers.
|
|
@@ -170,11 +173,9 @@ export class SceneProcessor {
|
|
|
170
173
|
*/
|
|
171
174
|
_log( message, data ) {
|
|
172
175
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
176
|
+
// Visibility is the log level's job now; config.verbose no longer gates this.
|
|
177
|
+
if ( data !== undefined ) log.debug( message, data );
|
|
178
|
+
else log.debug( message );
|
|
178
179
|
|
|
179
180
|
}
|
|
180
181
|
|
|
@@ -194,7 +195,7 @@ export class SceneProcessor {
|
|
|
194
195
|
this.isProcessing = true;
|
|
195
196
|
this.processingStage = 'init';
|
|
196
197
|
|
|
197
|
-
const timer = new BuildTimer(
|
|
198
|
+
const timer = new BuildTimer( object.name ?? '', { namespace: 'scene' } );
|
|
198
199
|
|
|
199
200
|
try {
|
|
200
201
|
|
|
@@ -273,7 +274,7 @@ export class SceneProcessor {
|
|
|
273
274
|
} catch ( error ) {
|
|
274
275
|
|
|
275
276
|
this.processingStage = 'error';
|
|
276
|
-
|
|
277
|
+
log.error( 'processing failed:', error );
|
|
277
278
|
updateLoading( {
|
|
278
279
|
status: `Error: ${error.message}`,
|
|
279
280
|
progress: 100
|
|
@@ -357,7 +358,7 @@ export class SceneProcessor {
|
|
|
357
358
|
|
|
358
359
|
} catch ( error ) {
|
|
359
360
|
|
|
360
|
-
|
|
361
|
+
log.error( 'geometry extraction failed:', error );
|
|
361
362
|
updateLoading( {
|
|
362
363
|
status: `Extraction error: ${error.message}`,
|
|
363
364
|
progress: 25
|
|
@@ -449,6 +450,7 @@ export class SceneProcessor {
|
|
|
449
450
|
numBins: this.bvhBuilder.numBins,
|
|
450
451
|
maxBins: this.bvhBuilder.maxBins,
|
|
451
452
|
minBins: this.bvhBuilder.minBins,
|
|
453
|
+
logLevel: workerLogLevel(),
|
|
452
454
|
};
|
|
453
455
|
|
|
454
456
|
const totalTasks = poolTasks.length + parallelTasks.length;
|
|
@@ -487,7 +489,9 @@ export class SceneProcessor {
|
|
|
487
489
|
Promise.all( parallelPromises )
|
|
488
490
|
] );
|
|
489
491
|
|
|
490
|
-
// Store all results
|
|
492
|
+
// Store all results, summing per-mesh split stats for one aggregate BVH line
|
|
493
|
+
const blasStats = { sah: 0, objMed: 0, spatMed: 0, failed: 0, treeletsImproved: 0, treeletsProcessed: 0 };
|
|
494
|
+
|
|
491
495
|
for ( const { m, range, result } of [ ...poolResults, ...parallelResults ] ) {
|
|
492
496
|
|
|
493
497
|
if ( result.reorderedTriangles ) {
|
|
@@ -496,6 +500,18 @@ export class SceneProcessor {
|
|
|
496
500
|
|
|
497
501
|
}
|
|
498
502
|
|
|
503
|
+
const st = result.splitStats;
|
|
504
|
+
if ( st ) {
|
|
505
|
+
|
|
506
|
+
blasStats.sah += st.sahSplits ?? 0;
|
|
507
|
+
blasStats.objMed += st.objectMedianSplits ?? 0;
|
|
508
|
+
blasStats.spatMed += st.spatialMedianSplits ?? 0;
|
|
509
|
+
blasStats.failed += st.failedSplits ?? 0;
|
|
510
|
+
blasStats.treeletsImproved += st.treeletsImproved ?? 0;
|
|
511
|
+
blasStats.treeletsProcessed += st.treeletsProcessed ?? 0;
|
|
512
|
+
|
|
513
|
+
}
|
|
514
|
+
|
|
499
515
|
this.instanceTable.setEntry( {
|
|
500
516
|
meshIndex: m,
|
|
501
517
|
blasNodeCount: result.bvhData.length / 16,
|
|
@@ -552,7 +568,15 @@ export class SceneProcessor {
|
|
|
552
568
|
this._disposeRefitWorker();
|
|
553
569
|
|
|
554
570
|
const duration = performance.now() - startTime;
|
|
555
|
-
|
|
571
|
+
// One aggregate line for the whole two-level build; the workers' per-mesh
|
|
572
|
+
// detail sits a level below at `debug`.
|
|
573
|
+
log.debug( fmt.list( [
|
|
574
|
+
`${fmt.n( validEntries.length )} BLASes + TLAS`,
|
|
575
|
+
`${fmt.n( this.bvhData.length / 16 )} nodes`,
|
|
576
|
+
`SAH ${fmt.n( blasStats.sah )} · objMed ${blasStats.objMed} · spatMed ${blasStats.spatMed} · failed ${blasStats.failed}`,
|
|
577
|
+
blasStats.treeletsProcessed ? `treelets ${blasStats.treeletsImproved}/${blasStats.treeletsProcessed} improved` : null,
|
|
578
|
+
fmt.ms( duration ),
|
|
579
|
+
] ) );
|
|
556
580
|
|
|
557
581
|
updateLoading( {
|
|
558
582
|
status: "BVH construction complete",
|
|
@@ -561,7 +585,7 @@ export class SceneProcessor {
|
|
|
561
585
|
|
|
562
586
|
} catch ( error ) {
|
|
563
587
|
|
|
564
|
-
|
|
588
|
+
log.error( 'BVH build failed:', error );
|
|
565
589
|
updateLoading( {
|
|
566
590
|
status: `BVH error: ${error.message}`,
|
|
567
591
|
progress: 75
|
|
@@ -660,6 +684,7 @@ export class SceneProcessor {
|
|
|
660
684
|
numBins: opts.numBins,
|
|
661
685
|
maxBins: opts.maxBins,
|
|
662
686
|
minBins: opts.minBins,
|
|
687
|
+
logLevel: opts.logLevel,
|
|
663
688
|
}, [ meshTriData.buffer ] );
|
|
664
689
|
|
|
665
690
|
};
|
|
@@ -686,6 +711,7 @@ export class SceneProcessor {
|
|
|
686
711
|
bvhData: data.bvhData,
|
|
687
712
|
reorderedTriangles: data.triangles || null,
|
|
688
713
|
originalToBvh: data.originalToBvh || null,
|
|
714
|
+
splitStats: data.treeletStats || null,
|
|
689
715
|
}
|
|
690
716
|
} );
|
|
691
717
|
|
|
@@ -835,7 +861,7 @@ export class SceneProcessor {
|
|
|
835
861
|
|
|
836
862
|
} catch ( error ) {
|
|
837
863
|
|
|
838
|
-
|
|
864
|
+
log.error( 'texture creation failed:', error );
|
|
839
865
|
throw error;
|
|
840
866
|
|
|
841
867
|
}
|
|
@@ -877,7 +903,7 @@ export class SceneProcessor {
|
|
|
877
903
|
if ( seen !== undefined ) return packTextureIndex( bucket, seen );
|
|
878
904
|
if ( lists[ bucket ].length >= STRIDE ) {
|
|
879
905
|
|
|
880
|
-
|
|
906
|
+
log.warn( `texture bucket ${bucket} full (${STRIDE}); dropping a map` );
|
|
881
907
|
return - 1;
|
|
882
908
|
|
|
883
909
|
}
|
|
@@ -1089,7 +1115,7 @@ export class SceneProcessor {
|
|
|
1089
1115
|
|
|
1090
1116
|
} catch ( error ) {
|
|
1091
1117
|
|
|
1092
|
-
|
|
1118
|
+
log.warn( `error disposing ${prop}:`, error );
|
|
1093
1119
|
|
|
1094
1120
|
}
|
|
1095
1121
|
|
|
@@ -1157,7 +1183,7 @@ export class SceneProcessor {
|
|
|
1157
1183
|
|
|
1158
1184
|
} catch ( error ) {
|
|
1159
1185
|
|
|
1160
|
-
|
|
1186
|
+
log.error( 'material rebuild failed:', error );
|
|
1161
1187
|
throw error;
|
|
1162
1188
|
|
|
1163
1189
|
} finally {
|
|
@@ -1492,7 +1518,7 @@ export class SceneProcessor {
|
|
|
1492
1518
|
|
|
1493
1519
|
if ( ! this.triangleData ) {
|
|
1494
1520
|
|
|
1495
|
-
|
|
1521
|
+
log.error( 'failed to get triangle data' );
|
|
1496
1522
|
return false;
|
|
1497
1523
|
|
|
1498
1524
|
}
|
|
@@ -1501,7 +1527,7 @@ export class SceneProcessor {
|
|
|
1501
1527
|
|
|
1502
1528
|
if ( ! this.bvhData ) {
|
|
1503
1529
|
|
|
1504
|
-
|
|
1530
|
+
log.error( 'failed to get BVH data' );
|
|
1505
1531
|
return false;
|
|
1506
1532
|
|
|
1507
1533
|
}
|
|
@@ -1515,7 +1541,7 @@ export class SceneProcessor {
|
|
|
1515
1541
|
|
|
1516
1542
|
} else {
|
|
1517
1543
|
|
|
1518
|
-
|
|
1544
|
+
log.warn( 'no material data, using defaults' );
|
|
1519
1545
|
|
|
1520
1546
|
}
|
|
1521
1547
|
|
|
@@ -1862,7 +1888,7 @@ export class SceneProcessor {
|
|
|
1862
1888
|
|
|
1863
1889
|
if ( data.error ) {
|
|
1864
1890
|
|
|
1865
|
-
|
|
1891
|
+
log.error( `background BLAS rebuild failed (mesh ${meshIdx}):`, data.error );
|
|
1866
1892
|
return;
|
|
1867
1893
|
|
|
1868
1894
|
}
|
|
@@ -1876,7 +1902,7 @@ export class SceneProcessor {
|
|
|
1876
1902
|
|
|
1877
1903
|
worker.onerror = ( err ) => {
|
|
1878
1904
|
|
|
1879
|
-
|
|
1905
|
+
log.error( `background BLAS rebuild worker failed (mesh ${meshIdx}):`, err );
|
|
1880
1906
|
worker.terminate();
|
|
1881
1907
|
this._pendingRebuilds.delete( meshIdx );
|
|
1882
1908
|
|
|
@@ -1948,7 +1974,7 @@ export class SceneProcessor {
|
|
|
1948
1974
|
// for the same triangle set. If it differs, the buffer layout is invalid.
|
|
1949
1975
|
if ( newNodeCount !== entry.blasNodeCount ) {
|
|
1950
1976
|
|
|
1951
|
-
|
|
1977
|
+
log.warn( `background rebuild node count mismatch for mesh ${meshIdx} (${newNodeCount} vs ${entry.blasNodeCount}), skipping swap` );
|
|
1952
1978
|
return;
|
|
1953
1979
|
|
|
1954
1980
|
}
|
|
@@ -11,6 +11,9 @@ import { texture } from 'three/tsl';
|
|
|
11
11
|
import { LinearFilter, DataArrayTexture } from 'three';
|
|
12
12
|
import { setAlphaShadowsUniform } from '../TSL/LightsDirect.js';
|
|
13
13
|
import { setGoboMapsTexture, setIESProfilesTexture } from '../TSL/LightsCore.js';
|
|
14
|
+
import { createLogger } from '../utils/Logger.js';
|
|
15
|
+
|
|
16
|
+
const log = createLogger( 'shader' );
|
|
14
17
|
|
|
15
18
|
export class ShaderBuilder {
|
|
16
19
|
|
|
@@ -43,7 +46,7 @@ export class ShaderBuilder {
|
|
|
43
46
|
if ( stage.goboMaps && nodes.goboMapsTex ) nodes.goboMapsTex.value = stage.goboMaps;
|
|
44
47
|
if ( stage.iesProfiles && nodes.iesProfilesTex ) nodes.iesProfilesTex.value = stage.iesProfiles;
|
|
45
48
|
|
|
46
|
-
|
|
49
|
+
log.debug( 'scene textures updated in-place' );
|
|
47
50
|
|
|
48
51
|
}
|
|
49
52
|
|
|
@@ -15,6 +15,9 @@
|
|
|
15
15
|
import { StorageTexture, RenderTarget } from 'three/webgpu';
|
|
16
16
|
import { RGBAFormat, FloatType, LinearFilter, NearestFilter, Box2, Vector2 } from 'three';
|
|
17
17
|
import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
18
|
+
import { createLogger, fmt } from '../utils/Logger.js';
|
|
19
|
+
|
|
20
|
+
const log = createLogger( 'gpu' );
|
|
18
21
|
|
|
19
22
|
function createWriteStorageTex() {
|
|
20
23
|
|
|
@@ -83,7 +86,7 @@ export class StorageTexturePool {
|
|
|
83
86
|
this.readTarget.textures[ 1 ].name = 'gNormalDepth';
|
|
84
87
|
this.readTarget.textures[ 2 ].name = 'gAlbedo';
|
|
85
88
|
|
|
86
|
-
|
|
89
|
+
log.debug( `storage texture pool ${fmt.px( width, height )} — 3 write StorageTextures + 1 MRT RenderTarget` );
|
|
87
90
|
|
|
88
91
|
}
|
|
89
92
|
|