rayzee 7.7.0 → 7.9.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-D9GImjGj.js → BVHSubtreeWorker-sNzvxn66.js} +2 -2
- package/dist/assets/{BVHSubtreeWorker-D9GImjGj.js.map → BVHSubtreeWorker-sNzvxn66.js.map} +1 -1
- package/dist/assets/{BVHWorker-CNJ0UBQz.js → BVHWorker-CiVdFrwe.js} +2 -2
- package/dist/assets/{BVHWorker-CNJ0UBQz.js.map → BVHWorker-CiVdFrwe.js.map} +1 -1
- package/dist/rayzee.es.js +2057 -2035
- 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 +45 -1
- package/src/EngineEvents.js +1 -0
- package/src/Passes/OIDNDenoiser.js +25 -1
- package/src/PathTracerApp.js +36 -0
- package/src/Processor/AssetLoader.js +3 -0
- package/src/Processor/GeometryExtractor.js +35 -9
- package/src/Processor/PackedRayBuffer.js +15 -1
- package/src/Processor/QueueManager.js +13 -1
- package/src/Processor/SceneProcessor.js +176 -113
- package/src/Processor/ShaderBuilder.js +6 -24
- package/src/Processor/TextureCreator.js +16 -32
- package/src/Processor/Workers/BVHWorker.js +6 -1
- package/src/Stages/EdgeFilter.js +4 -3
- package/src/Stages/MotionVector.js +4 -4
- package/src/Stages/NormalDepth.js +20 -30
- package/src/Stages/PathTracer.js +20 -40
- package/src/TSL/DebugKernel.js +0 -2
- package/src/TSL/Debugger.js +1 -8
- package/src/TSL/Displacement.js +10 -10
- package/src/TSL/GenerateKernel.js +5 -1
- package/src/TSL/LightsDirect.js +8 -9
- package/src/TSL/LightsIndirect.js +15 -9
- package/src/TSL/LightsSampling.js +9 -19
- package/src/TSL/Random.js +11 -1
- package/src/TSL/ShadeKernel.js +1 -6
- package/src/TSL/TextureSampling.js +155 -34
- package/src/managers/EnvironmentManager.js +11 -0
- package/src/managers/MaterialDataManager.js +71 -39
- package/src/managers/RenderTargetManager.js +0 -522
package/package.json
CHANGED
package/src/EngineDefaults.js
CHANGED
|
@@ -492,9 +492,53 @@ export const TEXTURE_CONSTANTS = {
|
|
|
492
492
|
// guaranteed minimum). The configurable maxTextureSize setting is clamped to this.
|
|
493
493
|
MAX_TEXTURE_SIZE: 8192,
|
|
494
494
|
// Default cap applied when no maxTextureSize is supplied (engine standalone use).
|
|
495
|
-
DEFAULT_MAX_TEXTURE_SIZE: 4096
|
|
495
|
+
DEFAULT_MAX_TEXTURE_SIZE: 4096,
|
|
496
|
+
// Max layers (textures) per bucket array. Also the packing stride for (bucket, layer).
|
|
497
|
+
MAX_TEXTURES_LIMIT: 128,
|
|
498
|
+
// Size buckets per colorSpace pool. Material maps are grouped into this many
|
|
499
|
+
// longest-edge size classes so a small map no longer pays a large neighbour's
|
|
500
|
+
// footprint. 4 → ~8 bound material arrays (4 sRGB + 4 linear).
|
|
501
|
+
MATERIAL_BUCKET_COUNT: 4,
|
|
502
|
+
// Packing stride: a map's stored index encodes bucketId * BUCKET_LAYER_STRIDE + layer.
|
|
503
|
+
// Also the per-bucket layer cap. Kept at the WebGPU portable maxTextureArrayLayers floor (256)
|
|
504
|
+
// so a consolidated bucket (which merges several map types of one size) stays portable.
|
|
505
|
+
BUCKET_LAYER_STRIDE: 256,
|
|
496
506
|
};
|
|
497
507
|
|
|
508
|
+
// Longest-edge size ladder for a given cap, ascending.
|
|
509
|
+
// cap=4096, count=4 → [512, 1024, 2048, 4096].
|
|
510
|
+
export function getTextureBucketSizes( maxTextureSize, count = TEXTURE_CONSTANTS.MATERIAL_BUCKET_COUNT ) {
|
|
511
|
+
|
|
512
|
+
const sizes = [];
|
|
513
|
+
for ( let i = count - 1; i >= 0; i -- ) {
|
|
514
|
+
|
|
515
|
+
sizes.push( Math.max( TEXTURE_CONSTANTS.MIN_TEXTURE_WIDTH, Math.round( maxTextureSize / Math.pow( 2, i ) ) ) );
|
|
516
|
+
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return sizes;
|
|
520
|
+
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// Bucket index for a texture, by its power-of-2 longest edge vs the cap's ladder.
|
|
524
|
+
// Larger-than-cap maps land in the top bucket (downscaled to cap, as before).
|
|
525
|
+
export function getTextureBucketId( width, height, maxTextureSize, count = TEXTURE_CONSTANTS.MATERIAL_BUCKET_COUNT ) {
|
|
526
|
+
|
|
527
|
+
const longest = Math.max( width || 1, height || 1 );
|
|
528
|
+
const pot = Math.pow( 2, Math.ceil( Math.log2( longest ) ) );
|
|
529
|
+
const sizes = getTextureBucketSizes( maxTextureSize, count );
|
|
530
|
+
for ( let i = 0; i < sizes.length; i ++ ) if ( pot <= sizes[ i ] ) return i;
|
|
531
|
+
return sizes.length - 1;
|
|
532
|
+
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// Pack (bucketId, layer) into the single int slot a material map index occupies.
|
|
536
|
+
export function packTextureIndex( bucketId, layer ) {
|
|
537
|
+
|
|
538
|
+
return bucketId * TEXTURE_CONSTANTS.BUCKET_LAYER_STRIDE + layer;
|
|
539
|
+
|
|
540
|
+
}
|
|
541
|
+
|
|
498
542
|
// Default texture matrix for materials
|
|
499
543
|
export const DEFAULT_TEXTURE_MATRIX = [ 0, 0, 1, 1, 0, 0, 0, 1 ];
|
|
500
544
|
|
package/src/EngineEvents.js
CHANGED
|
@@ -142,6 +142,9 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
142
142
|
|
|
143
143
|
this.currentTZAUrl = null;
|
|
144
144
|
this.unet = null;
|
|
145
|
+
// A start() requested while the UNet is still loading is deferred here and fired
|
|
146
|
+
// once loading finishes, instead of being silently dropped.
|
|
147
|
+
this._pendingStart = false;
|
|
145
148
|
|
|
146
149
|
// Initialize asynchronously
|
|
147
150
|
this._initialize().catch( error => {
|
|
@@ -257,6 +260,15 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
257
260
|
|
|
258
261
|
this.state.isLoading = false;
|
|
259
262
|
|
|
263
|
+
// Fire a start() that arrived mid-load. Guard on unet+enabled so a failed load
|
|
264
|
+
// or a disable during loading doesn't kick off a denoise.
|
|
265
|
+
if ( this._pendingStart && this.unet && this.enabled ) {
|
|
266
|
+
|
|
267
|
+
this._pendingStart = false;
|
|
268
|
+
this.start();
|
|
269
|
+
|
|
270
|
+
}
|
|
271
|
+
|
|
260
272
|
}
|
|
261
273
|
|
|
262
274
|
}
|
|
@@ -302,8 +314,17 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
302
314
|
|
|
303
315
|
async start() {
|
|
304
316
|
|
|
305
|
-
if ( ! this.enabled || this.state.isDenoising
|
|
317
|
+
if ( ! this.enabled || this.state.isDenoising ) {
|
|
318
|
+
|
|
319
|
+
return false;
|
|
320
|
+
|
|
321
|
+
}
|
|
306
322
|
|
|
323
|
+
// UNet weights still loading — defer the start (fired from _setupUNetDenoiser's
|
|
324
|
+
// finally) rather than silently dropping this frame's denoise request.
|
|
325
|
+
if ( this.state.isLoading ) {
|
|
326
|
+
|
|
327
|
+
this._pendingStart = true;
|
|
307
328
|
return false;
|
|
308
329
|
|
|
309
330
|
}
|
|
@@ -870,6 +891,9 @@ export class OIDNDenoiser extends EventDispatcher {
|
|
|
870
891
|
|
|
871
892
|
abort() {
|
|
872
893
|
|
|
894
|
+
// Cancel any start deferred during loading so a reset supersedes it.
|
|
895
|
+
this._pendingStart = false;
|
|
896
|
+
|
|
873
897
|
if ( ! this.enabled || ! this.state.isDenoising ) return;
|
|
874
898
|
|
|
875
899
|
// Signal abort to current operation
|
package/src/PathTracerApp.js
CHANGED
|
@@ -157,6 +157,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
157
157
|
// Tracked listeners for clean dispose()
|
|
158
158
|
this._trackedListeners = [];
|
|
159
159
|
this._disposed = false;
|
|
160
|
+
this._deviceLost = false;
|
|
160
161
|
|
|
161
162
|
}
|
|
162
163
|
|
|
@@ -230,6 +231,9 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
230
231
|
*/
|
|
231
232
|
animate() {
|
|
232
233
|
|
|
234
|
+
// Device lost: stop the loop rather than rescheduling render() on a dead device.
|
|
235
|
+
if ( this._deviceLost ) return;
|
|
236
|
+
|
|
233
237
|
this.animationManagerId = requestAnimationFrame( () => this.animate() );
|
|
234
238
|
|
|
235
239
|
if ( this._loadingInProgress || this._sdf?.isProcessing ) {
|
|
@@ -368,9 +372,25 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
368
372
|
|
|
369
373
|
}
|
|
370
374
|
|
|
375
|
+
/**
|
|
376
|
+
* Handle GPU device loss: halt the render loop and notify hosts so they can surface a
|
|
377
|
+
* "renderer lost — reload" prompt. Full auto-recovery would require rebuilding every GPU
|
|
378
|
+
* resource, so this deliberately stops cleanly rather than attempting to re-init.
|
|
379
|
+
*/
|
|
380
|
+
_handleDeviceLost( info ) {
|
|
381
|
+
|
|
382
|
+
if ( this._deviceLost ) return;
|
|
383
|
+
this._deviceLost = true;
|
|
384
|
+
console.error( `WebGPU device lost (${info?.reason || 'unknown'}): ${info?.message || ''}` );
|
|
385
|
+
this.stopAnimation();
|
|
386
|
+
this.dispatchEvent( { type: EngineEvents.DEVICE_LOST, reason: info?.reason, message: info?.message } );
|
|
387
|
+
|
|
388
|
+
}
|
|
389
|
+
|
|
371
390
|
/** Wakes the animation loop if it was stopped due to idle. */
|
|
372
391
|
wake() {
|
|
373
392
|
|
|
393
|
+
if ( this._deviceLost ) return;
|
|
374
394
|
if ( ! this.animationManagerId && this.isInitialized && ! this._paused ) this.animate();
|
|
375
395
|
|
|
376
396
|
}
|
|
@@ -1385,6 +1405,22 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1385
1405
|
|
|
1386
1406
|
await this.renderer.init();
|
|
1387
1407
|
|
|
1408
|
+
// Detect GPU device loss (dGPU/iGPU switch, driver reset, TDR watchdog on heavy
|
|
1409
|
+
// compute). Without this the rAF loop keeps calling render() on a dead device,
|
|
1410
|
+
// spewing errors forever. reason 'destroyed' during dispose() is intentional teardown.
|
|
1411
|
+
const gpuDevice = this.renderer.backend?.device;
|
|
1412
|
+
if ( gpuDevice?.lost ) {
|
|
1413
|
+
|
|
1414
|
+
gpuDevice.lost.then( ( info ) => {
|
|
1415
|
+
|
|
1416
|
+
if ( this._disposed ) return;
|
|
1417
|
+
this._handleDeviceLost( info );
|
|
1418
|
+
|
|
1419
|
+
} );
|
|
1420
|
+
gpuDevice.onuncapturederror = ( event ) => console.error( 'WebGPU uncaptured error:', event.error );
|
|
1421
|
+
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1388
1424
|
RectAreaLightNode.setLTC( RectAreaLightTexturesLib.init() );
|
|
1389
1425
|
|
|
1390
1426
|
this.renderer.outputColorSpace = SRGBColorSpace;
|
|
@@ -292,6 +292,9 @@ export class AssetLoader extends EventDispatcher {
|
|
|
292
292
|
|
|
293
293
|
if ( ! this.loaderCache.texture ) this.loaderCache.texture = new TextureLoader();
|
|
294
294
|
texture = await this.loaderCache.texture.loadAsync( url );
|
|
295
|
+
// LDR env maps (jpg/png/webp) are authored in sRGB; tag them so the backend
|
|
296
|
+
// decodes to linear. HDR/EXR are already linear and keep the loader's setting.
|
|
297
|
+
texture.colorSpace = SRGBColorSpace;
|
|
295
298
|
|
|
296
299
|
}
|
|
297
300
|
|
|
@@ -45,8 +45,12 @@ export class GeometryExtractor {
|
|
|
45
45
|
|
|
46
46
|
this.resetArrays();
|
|
47
47
|
|
|
48
|
-
//
|
|
49
|
-
|
|
48
|
+
// Pre-count the exact triangle total so the buffer is allocated once.
|
|
49
|
+
// Doubling-with-copy rounded up to the next power of two (wasting ~1GB on
|
|
50
|
+
// an 8.5M-tri scene) and held old+new during the copy (~3GB transient),
|
|
51
|
+
// which overflowed the browser's ArrayBuffer allocator. Exact avoids both.
|
|
52
|
+
const totalTriangles = this._countTriangles( object );
|
|
53
|
+
this._triangleCapacity = Math.max( 1024, totalTriangles );
|
|
50
54
|
this.triangleData = new Float32Array( this._triangleCapacity * TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE );
|
|
51
55
|
this.currentTriangleIndex = 0;
|
|
52
56
|
|
|
@@ -58,19 +62,41 @@ export class GeometryExtractor {
|
|
|
58
62
|
|
|
59
63
|
}
|
|
60
64
|
|
|
61
|
-
//
|
|
65
|
+
// Sum the exact triangle count over every mesh extract() will process
|
|
66
|
+
// (mirrors traverseObject/processMesh guards) so we can allocate once.
|
|
67
|
+
_countTriangles( object ) {
|
|
68
|
+
|
|
69
|
+
let count = 0;
|
|
70
|
+
|
|
71
|
+
if ( object.isMesh && object.geometry && object.material ) {
|
|
72
|
+
|
|
73
|
+
const indices = object.geometry.index;
|
|
74
|
+
const positions = object.geometry.attributes.position;
|
|
75
|
+
if ( indices ) count += Math.ceil( indices.count / 3 );
|
|
76
|
+
else if ( positions ) count += Math.ceil( positions.count / 3 );
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if ( object.children ) {
|
|
81
|
+
|
|
82
|
+
for ( const child of object.children ) count += this._countTriangles( child );
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return count;
|
|
87
|
+
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Safety net only: extract() pre-counts and allocates exactly, so this should
|
|
91
|
+
// not fire. Grow to exactly `needed` (never double) to avoid a memory spike.
|
|
62
92
|
_ensureCapacity( needed ) {
|
|
63
93
|
|
|
64
94
|
if ( needed <= this._triangleCapacity ) return;
|
|
65
95
|
|
|
66
|
-
|
|
67
|
-
let newCapacity = this._triangleCapacity;
|
|
68
|
-
while ( newCapacity < needed ) newCapacity *= 2;
|
|
69
|
-
|
|
70
|
-
const newData = new Float32Array( newCapacity * TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE );
|
|
96
|
+
const newData = new Float32Array( needed * TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE );
|
|
71
97
|
newData.set( this.triangleData );
|
|
72
98
|
this.triangleData = newData;
|
|
73
|
-
this._triangleCapacity =
|
|
99
|
+
this._triangleCapacity = needed;
|
|
74
100
|
|
|
75
101
|
}
|
|
76
102
|
|
|
@@ -38,6 +38,16 @@ let _cap = 0;
|
|
|
38
38
|
|
|
39
39
|
const soa = ( id, slot ) => ( slot === 0 ? id : id.add( slot * _cap ) );
|
|
40
40
|
|
|
41
|
+
// Free a compute storage attribute's GPU buffer. Nulling the JS reference alone leaks it —
|
|
42
|
+
// these attributes hang off no geometry, so nothing triggers the renderer's attribute cleanup.
|
|
43
|
+
// Deleting from the renderer's attribute map calls backend.destroyAttribute(). Guarded no-op
|
|
44
|
+
// when the attribute was never uploaded or no renderer was provided (e.g. unit tests).
|
|
45
|
+
export function freeStorageAttribute( renderer, attr ) {
|
|
46
|
+
|
|
47
|
+
if ( attr ) renderer?._attributes?.delete?.( attr );
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
|
|
41
51
|
export class PackedRayBuffer {
|
|
42
52
|
|
|
43
53
|
// Capacity maxRays would allocate (mirrors allocate()/resize()). 1.25× headroom, NO pow2 rounding —
|
|
@@ -49,9 +59,10 @@ export class PackedRayBuffer {
|
|
|
49
59
|
|
|
50
60
|
}
|
|
51
61
|
|
|
52
|
-
constructor( maxRays = 0 ) {
|
|
62
|
+
constructor( maxRays = 0, renderer = null ) {
|
|
53
63
|
|
|
54
64
|
this.capacity = 0;
|
|
65
|
+
this._renderer = renderer;
|
|
55
66
|
this._attrs = {};
|
|
56
67
|
|
|
57
68
|
// Each: { rw: StorageBufferNode, ro: StorageBufferNode } over one shared GPU buffer.
|
|
@@ -118,6 +129,9 @@ export class PackedRayBuffer {
|
|
|
118
129
|
|
|
119
130
|
dispose() {
|
|
120
131
|
|
|
132
|
+
freeStorageAttribute( this._renderer, this._attrs.ray );
|
|
133
|
+
freeStorageAttribute( this._renderer, this._attrs.rng );
|
|
134
|
+
freeStorageAttribute( this._renderer, this._attrs.hit );
|
|
121
135
|
this._attrs = {};
|
|
122
136
|
this.rayBuffer = null;
|
|
123
137
|
this.rngBuffer = null;
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { storage } from 'three/tsl';
|
|
6
6
|
import { StorageInstancedBufferAttribute } from 'three/webgpu';
|
|
7
|
+
import { freeStorageAttribute } from './PackedRayBuffer.js';
|
|
7
8
|
|
|
8
9
|
/** Counter indices — must match ResetCounters kernel */
|
|
9
10
|
export const COUNTER = {
|
|
@@ -33,8 +34,9 @@ export class QueueManager {
|
|
|
33
34
|
/**
|
|
34
35
|
* @param {number} maxRays - Maximum number of rays (typically width * height)
|
|
35
36
|
*/
|
|
36
|
-
constructor( maxRays = 0 ) {
|
|
37
|
+
constructor( maxRays = 0, renderer = null ) {
|
|
37
38
|
|
|
39
|
+
this._renderer = renderer;
|
|
38
40
|
this.capacity = 0;
|
|
39
41
|
this.counters = null;
|
|
40
42
|
// A/B alternate: one read by current bounce, other written by compaction
|
|
@@ -197,6 +199,16 @@ export class QueueManager {
|
|
|
197
199
|
|
|
198
200
|
dispose() {
|
|
199
201
|
|
|
202
|
+
// Free the GPU buffers before dropping the node references (nulling alone leaks them).
|
|
203
|
+
freeStorageAttribute( this._renderer, this._countersAttr );
|
|
204
|
+
freeStorageAttribute( this._renderer, this._bounceCountsAttr );
|
|
205
|
+
freeStorageAttribute( this._renderer, this._attrA );
|
|
206
|
+
freeStorageAttribute( this._renderer, this._attrB );
|
|
207
|
+
freeStorageAttribute( this._renderer, this._sortAttr );
|
|
208
|
+
freeStorageAttribute( this._renderer, this._sortGlobalHistAttr );
|
|
209
|
+
this._countersAttr = this._bounceCountsAttr = null;
|
|
210
|
+
this._attrA = this._attrB = this._sortAttr = this._sortGlobalHistAttr = null;
|
|
211
|
+
|
|
200
212
|
this.counters = null;
|
|
201
213
|
this.activeIndices = null;
|
|
202
214
|
this.activeIndicesRO = null;
|