rayzee 7.22.1 → 7.22.3
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/dist/rayzee.es.js +37 -16
- package/dist/rayzee.umd.js +2 -2
- package/package.json +1 -1
- package/src/EngineDefaults.js +4 -3
- package/src/PathTracerApp.js +119 -29
- package/src/Stages/PathTracer.js +7 -2
- package/src/index.js +1 -0
package/package.json
CHANGED
package/src/EngineDefaults.js
CHANGED
|
@@ -199,9 +199,10 @@ export const MAX_RESERVABLE_RENDER_SIZE = 4096;
|
|
|
199
199
|
// Reserved render size: every per-resolution compute StorageTexture + aux buffer is pre-allocated at
|
|
200
200
|
// this SQUARE size and never resized at runtime (works around three.js StorageTexture-resize bugs —
|
|
201
201
|
// see TSL/patches history). Render resolution must not exceed it; the engine warns + ignores larger.
|
|
202
|
-
// It is a LIVE binding (mutable) so
|
|
203
|
-
//
|
|
204
|
-
// (
|
|
202
|
+
// It is a LIVE binding (mutable) so it can be raised (e.g. for 4K) via setReservedRenderSize(); consumers
|
|
203
|
+
// read it inside their constructors, and stages already built at a lower value are re-initialised in place
|
|
204
|
+
// by PathTracerApp.setReservedRenderResolution() — which is the device-gated API hosts should call, at any
|
|
205
|
+
// point in the lifecycle. Default 2048 (zero VRAM regression). See docs/internal/specs/wavefront-chunked-pool.md.
|
|
205
206
|
export let MAX_STORAGE_TEXTURE_SIZE = 2048;
|
|
206
207
|
|
|
207
208
|
// Set the reserved render size. MUST be called before pipeline construction (stages pre-allocate at
|
package/src/PathTracerApp.js
CHANGED
|
@@ -114,6 +114,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
114
114
|
// ── Pipeline & stages ──
|
|
115
115
|
this.pipeline = null;
|
|
116
116
|
|
|
117
|
+
this._pendingReservedRenderSize = null;
|
|
118
|
+
|
|
117
119
|
/**
|
|
118
120
|
* Named access to all pipeline stages.
|
|
119
121
|
* Advanced consumers can reach into stages for fine-grained control.
|
|
@@ -146,7 +148,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
146
148
|
// ── State ──
|
|
147
149
|
this.isInitialized = false;
|
|
148
150
|
this.pauseRendering = false;
|
|
149
|
-
this.
|
|
151
|
+
this._pathTracerEnabled = true;
|
|
152
|
+
this._rasterPrecompile = null;
|
|
150
153
|
this.animationManagerId = null;
|
|
151
154
|
this.needsReset = false;
|
|
152
155
|
this._loadingInProgress = false;
|
|
@@ -219,6 +222,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
219
222
|
async init() {
|
|
220
223
|
|
|
221
224
|
await this._initRenderer();
|
|
225
|
+
this._applyPendingReservedRenderSize();
|
|
222
226
|
this._initCameraManager();
|
|
223
227
|
this._initScenes();
|
|
224
228
|
this._initAssetPipeline();
|
|
@@ -841,21 +845,16 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
841
845
|
this.stages.pathTracer.setupMaterial();
|
|
842
846
|
timer.end( 'Material setup (TSL compile)' );
|
|
843
847
|
|
|
844
|
-
|
|
845
|
-
// animate frame is snappy. Wavefront compute kernels compile lazily on their first dispatch.
|
|
846
|
-
timer.start( 'Pipeline precompile' );
|
|
847
|
-
try {
|
|
848
|
-
|
|
849
|
-
await this.renderer.compileAsync( this.meshScene, this.cameraManager.camera );
|
|
848
|
+
this._rasterPrecompile = null;
|
|
850
849
|
|
|
851
|
-
|
|
850
|
+
if ( ! this._pathTracerEnabled ) {
|
|
852
851
|
|
|
853
|
-
|
|
852
|
+
timer.start( 'Pipeline precompile' );
|
|
853
|
+
await this.precompileRaster();
|
|
854
|
+
timer.end( 'Pipeline precompile' );
|
|
854
855
|
|
|
855
856
|
}
|
|
856
857
|
|
|
857
|
-
timer.end( 'Pipeline precompile' );
|
|
858
|
-
|
|
859
858
|
// Wait for CDF
|
|
860
859
|
if ( cdfPromise ) {
|
|
861
860
|
|
|
@@ -1237,34 +1236,53 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1237
1236
|
* is device-capped: 4K reservation (~1.5 GB of MRT textures) is only granted on GPUs with ample VRAM +
|
|
1238
1237
|
* a large storage-buffer binding limit; weaker devices clamp to 2048.
|
|
1239
1238
|
*
|
|
1240
|
-
*
|
|
1241
|
-
*
|
|
1239
|
+
* Callable at any point in the lifecycle:
|
|
1240
|
+
* - before init(): recorded, then applied during init() before the stages are constructed, so they
|
|
1241
|
+
* pre-allocate at the raised size directly. The device gate cannot run until the device exists, so
|
|
1242
|
+
* the value returned here is the request, not the verdict — read getReservedRenderResolution() after
|
|
1243
|
+
* init(), or listen for `reserved_render_size_changed`.
|
|
1244
|
+
* - after init(): applied immediately, re-initialising the reserved GPU storage in place.
|
|
1242
1245
|
* @param {number} requestedPx desired reserved size (longest edge)
|
|
1243
1246
|
* @param {Object} [opts]
|
|
1244
1247
|
* @param {boolean} [opts.allowLower=false] permit lowering, paying a rebuild, to reclaim VRAM
|
|
1245
|
-
* @returns {number} the applied reserved size
|
|
1248
|
+
* @returns {number} the applied reserved size (the pending request when called before init)
|
|
1246
1249
|
*/
|
|
1247
1250
|
setReservedRenderResolution( requestedPx, { allowLower = false } = {} ) {
|
|
1248
1251
|
|
|
1249
|
-
const limits = this.renderer?.backend?.device?.limits;
|
|
1250
|
-
const maxBinding = limits?.maxStorageBufferBindingSize || ( 128 * 1024 * 1024 );
|
|
1251
|
-
const deviceMemGB = ( typeof navigator !== 'undefined' && navigator.deviceMemory ) || 4;
|
|
1252
|
-
// 4K reserve pins the accum MRT (~1.5 GB) + aux; only grant it on clearly-capable GPUs.
|
|
1253
|
-
const deviceSafeMax = ( deviceMemGB >= 8 && maxBinding >= 1024 * 1024 * 1024 )
|
|
1254
|
-
? MAX_RESERVABLE_RENDER_SIZE : 2048;
|
|
1255
1252
|
const prev = MAX_STORAGE_TEXTURE_SIZE;
|
|
1256
1253
|
|
|
1257
1254
|
// Monotonic up: UI-driven callers request whatever the current view needs, so honouring decreases made
|
|
1258
1255
|
// the reserve oscillate on every preview↔render switch and paid a full kernel rebuild each time.
|
|
1259
1256
|
const target = allowLower ? requestedPx : Math.max( requestedPx, prev );
|
|
1257
|
+
|
|
1258
|
+
// No device yet: the gate below has nothing to interrogate and would clamp a 4K-capable GPU to 2048.
|
|
1259
|
+
// init() replays the request once the device exists, still before the stages allocate.
|
|
1260
|
+
if ( ! this.renderer ) {
|
|
1261
|
+
|
|
1262
|
+
this._pendingReservedRenderSize = { requestedPx, allowLower };
|
|
1263
|
+
return Math.max( 256, Math.min( MAX_RESERVABLE_RENDER_SIZE, Math.floor( target ) || 256 ) );
|
|
1264
|
+
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
const limits = this.renderer.backend?.device?.limits;
|
|
1268
|
+
const maxBinding = limits?.maxStorageBufferBindingSize || ( 128 * 1024 * 1024 );
|
|
1269
|
+
const deviceMemGB = ( typeof navigator !== 'undefined' && navigator.deviceMemory ) || 4;
|
|
1270
|
+
// 4K reserve pins the accum MRT (~1.5 GB) + aux; only grant it on clearly-capable GPUs.
|
|
1271
|
+
const deviceSafeMax = ( deviceMemGB >= 8 && maxBinding >= 1024 * 1024 * 1024 )
|
|
1272
|
+
? MAX_RESERVABLE_RENDER_SIZE : 2048;
|
|
1260
1273
|
const applied = setReservedRenderSize( Math.min( target, deviceSafeMax ) );
|
|
1261
1274
|
|
|
1262
|
-
//
|
|
1263
|
-
//
|
|
1264
|
-
//
|
|
1265
|
-
//
|
|
1266
|
-
|
|
1267
|
-
|
|
1275
|
+
// Gate the realloc on the textures that EXIST, not on how the binding moved: a raise applied while
|
|
1276
|
+
// nothing was allocated leaves `applied === prev` for every later call, so keying off that let the
|
|
1277
|
+
// first ineffective call poison every effective one after it (issue #9). The path tracer's write MRT
|
|
1278
|
+
// witnesses the reserve the stages were last built at.
|
|
1279
|
+
const allocated = this.stages?.pathTracer?.storageTextures?.writeColor?.image?.width ?? applied;
|
|
1280
|
+
|
|
1281
|
+
// Re-init the reserved GPU storage in place: each stage recreates its pre-allocated StorageTextures at
|
|
1282
|
+
// the new size + rebuilds its compute pipelines. Stage OBJECTS, manager refs and event wiring are
|
|
1283
|
+
// preserved (so no re-subscription needed); scene geometry buffers are resolution-independent and
|
|
1284
|
+
// reused. Rendering is paused across the swap so no in-flight dispatch references a disposed texture.
|
|
1285
|
+
if ( allocated !== applied ) {
|
|
1268
1286
|
|
|
1269
1287
|
const wasPaused = this.pauseRendering;
|
|
1270
1288
|
this.pauseRendering = true;
|
|
@@ -1298,6 +1316,31 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1298
1316
|
|
|
1299
1317
|
}
|
|
1300
1318
|
|
|
1319
|
+
/**
|
|
1320
|
+
* Replay a setReservedRenderResolution() call made before init(): after the device exists, so the gate is
|
|
1321
|
+
* evaluated against the real GPU, and before _initPipeline() constructs the stages, which read the reserve
|
|
1322
|
+
* in their constructors. The event carries the device's verdict — the only authoritative answer a pre-init
|
|
1323
|
+
* caller can get.
|
|
1324
|
+
*/
|
|
1325
|
+
_applyPendingReservedRenderSize() {
|
|
1326
|
+
|
|
1327
|
+
const pending = this._pendingReservedRenderSize;
|
|
1328
|
+
if ( ! pending ) return;
|
|
1329
|
+
|
|
1330
|
+
this._pendingReservedRenderSize = null;
|
|
1331
|
+
|
|
1332
|
+
const applied = this.setReservedRenderResolution( pending.requestedPx, { allowLower: pending.allowLower } );
|
|
1333
|
+
|
|
1334
|
+
if ( applied < pending.requestedPx ) {
|
|
1335
|
+
|
|
1336
|
+
log.warn( `reserved render size ${fmt.n( pending.requestedPx )}px was capped to ${fmt.n( applied )}px by this device's limits — renders above ${fmt.n( applied )}px will be declined.` );
|
|
1337
|
+
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
this.dispatchEvent( { type: 'reserved_render_size_changed', size: applied } );
|
|
1341
|
+
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1301
1344
|
/**
|
|
1302
1345
|
* The current reserved (pre-allocated) square render size in px.
|
|
1303
1346
|
* @returns {number}
|
|
@@ -1318,7 +1361,7 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1318
1361
|
|
|
1319
1362
|
if ( width > MAX_STORAGE_TEXTURE_SIZE || height > MAX_STORAGE_TEXTURE_SIZE ) {
|
|
1320
1363
|
|
|
1321
|
-
log.warn( `render resolution ${width}×${height} exceeds the ${MAX_STORAGE_TEXTURE_SIZE}px
|
|
1364
|
+
log.warn( `render resolution ${width}×${height} exceeds the ${MAX_STORAGE_TEXTURE_SIZE}px reserve (compute storage textures are pre-allocated at ${MAX_STORAGE_TEXTURE_SIZE}px). Ignoring resize — raise the reserve with setReservedRenderResolution( ${Math.max( width, height )} ) first, or use a resolution ≤ ${MAX_STORAGE_TEXTURE_SIZE}.` );
|
|
1322
1365
|
return false;
|
|
1323
1366
|
|
|
1324
1367
|
}
|
|
@@ -1364,10 +1407,19 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1364
1407
|
|
|
1365
1408
|
}
|
|
1366
1409
|
|
|
1410
|
+
/**
|
|
1411
|
+
* Set the render resolution in pixels, applied immediately (unlike the debounced onResize()).
|
|
1412
|
+
* @param {number} width
|
|
1413
|
+
* @param {number} height
|
|
1414
|
+
* @returns {{width: number, height: number}|null} the size now in effect, or null if the request was
|
|
1415
|
+
* declined — zero, or above the reserved render size (raise it with setReservedRenderResolution).
|
|
1416
|
+
* A declined request leaves the previous size in place, so ignoring this return renders at the
|
|
1417
|
+
* wrong resolution with nothing but a warning to show for it.
|
|
1418
|
+
*/
|
|
1367
1419
|
setCanvasSize( width, height ) {
|
|
1368
1420
|
|
|
1369
|
-
if ( width === 0 || height === 0 ) return;
|
|
1370
|
-
if ( ! this._isRenderSizeSupported( width, height ) ) return;
|
|
1421
|
+
if ( width === 0 || height === 0 ) return null;
|
|
1422
|
+
if ( ! this._isRenderSizeSupported( width, height ) ) return null;
|
|
1371
1423
|
|
|
1372
1424
|
this.renderer.setPixelRatio( 1.0 );
|
|
1373
1425
|
this.renderer.setSize( width, height, false );
|
|
@@ -1377,6 +1429,8 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1377
1429
|
clearTimeout( this._resizeDebounceTimer );
|
|
1378
1430
|
this._applyRenderResize( width, height );
|
|
1379
1431
|
|
|
1432
|
+
return { width, height };
|
|
1433
|
+
|
|
1380
1434
|
}
|
|
1381
1435
|
|
|
1382
1436
|
// ═══════════════════════════════════════════════════════════════
|
|
@@ -1864,6 +1918,42 @@ export class PathTracerApp extends EventDispatcher {
|
|
|
1864
1918
|
|
|
1865
1919
|
}
|
|
1866
1920
|
|
|
1921
|
+
/**
|
|
1922
|
+
* When false, `animate()` rasters `meshScene` instead of path tracing. Those raster pipelines
|
|
1923
|
+
* are compiled on the switch rather than at load — `compileAsync` costs seconds on a
|
|
1924
|
+
* many-material model, and path tracing, the default, never uses them.
|
|
1925
|
+
* @returns {boolean}
|
|
1926
|
+
*/
|
|
1927
|
+
get pathTracerEnabled() {
|
|
1928
|
+
|
|
1929
|
+
return this._pathTracerEnabled;
|
|
1930
|
+
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
set pathTracerEnabled( value ) {
|
|
1934
|
+
|
|
1935
|
+
this._pathTracerEnabled = value;
|
|
1936
|
+
if ( ! value ) this.precompileRaster();
|
|
1937
|
+
|
|
1938
|
+
}
|
|
1939
|
+
|
|
1940
|
+
/**
|
|
1941
|
+
* Warms the raster fallback's pipelines. Once per loaded model; concurrent callers await the
|
|
1942
|
+
* same compile rather than racing past a flag set before it finishes.
|
|
1943
|
+
* @returns {Promise<void>}
|
|
1944
|
+
*/
|
|
1945
|
+
precompileRaster() {
|
|
1946
|
+
|
|
1947
|
+
if ( ! this.renderer || ! this.meshScene ) return Promise.resolve();
|
|
1948
|
+
|
|
1949
|
+
this._rasterPrecompile ??= this.renderer
|
|
1950
|
+
.compileAsync( this.meshScene, this.cameraManager.camera )
|
|
1951
|
+
.catch( err => log.warn( 'raster fallback precompile failed', err ) );
|
|
1952
|
+
|
|
1953
|
+
return this._rasterPrecompile;
|
|
1954
|
+
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1867
1957
|
/**
|
|
1868
1958
|
* Whether a model/environment load is currently in progress.
|
|
1869
1959
|
* @returns {boolean}
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -761,10 +761,10 @@ export class PathTracer extends PathTracerStage {
|
|
|
761
761
|
// MAX_STORAGE_TEXTURE_SIZE and can't be resized, so recreate the pool at the (new) live reserved size and
|
|
762
762
|
// rebuild the wavefront kernels in place. The stage OBJECT is unchanged, so manager/event refs stay valid;
|
|
763
763
|
// only GPU textures + compute pipelines are rebuilt. Scene buffers (BVH/tri/material) are resolution-
|
|
764
|
-
// independent and untouched. Caller must have rendering paused.
|
|
764
|
+
// independent and untouched. Caller must have rendering paused.
|
|
765
765
|
reallocateReservedStorage() {
|
|
766
766
|
|
|
767
|
-
if ( ! this.
|
|
767
|
+
if ( ! this.storageTextures.writeColor ) return;
|
|
768
768
|
|
|
769
769
|
// Clamp into the (possibly just-lowered) reserve: create() sizes the write StorageTextures at
|
|
770
770
|
// MAX_STORAGE_TEXTURE_SIZE but the readTarget at what's passed here, and the tracked render size still
|
|
@@ -777,6 +777,11 @@ export class PathTracer extends PathTracerStage {
|
|
|
777
777
|
this.storageTextures.create( w, h );
|
|
778
778
|
this.resolution.value.set( w, h );
|
|
779
779
|
|
|
780
|
+
// No kernels before the first scene with materials — nothing to rebuild, and a build here would compile
|
|
781
|
+
// against 0 materials. The pool above is recreated regardless: it is allocated at construction, so a
|
|
782
|
+
// reserve raised before the first model load must still reach it or every later copy overflows it.
|
|
783
|
+
if ( ! this._packedBuffers || ! ( this.materialData?.materialCount > 0 ) ) return;
|
|
784
|
+
|
|
780
785
|
// Rebuild kernels: re-references the fresh write textures + recreates the per-pixel aux buffers
|
|
781
786
|
// (m2/streak/frozenMask) at the new maxPixels. Prev-frame nodes are repointed per-frame in render().
|
|
782
787
|
if ( this._kernelManager ) this._kernelManager.dispose();
|