rayzee 11.2.1 → 11.3.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 +58 -29
- package/dist/ArchiveImporter-D735xX-4.js +7860 -0
- package/dist/BidirectionalIntegrator-M89c0AlP.js +954 -0
- package/dist/{BufferLayout-D_IBo2ci.js → BufferLayout-xPRH9jYD.js} +41 -5
- package/dist/{NeuralRetouch-Cr-otSW8.js → NeuralRetouch-DiBH63kz.js} +1 -1
- package/dist/{NeuralSuperRes-B7E3_M0t.js → NeuralSuperRes-CZNQP13W.js} +1 -1
- package/dist/{PathTracer-D6nbknj5.js → PathTracer-BXCCVrPK.js} +4371 -4612
- package/dist/{PhysicalSky-Dq5VSiCQ.js → PhysicalSky-DqmaS0lb.js} +2 -2
- package/dist/{RayzeeRenderer-DbjqO6v-.js → RayzeeRenderer-DUSMFhmc.js} +426 -363
- package/dist/SolarLimb-bZE6y63S.js +762 -0
- package/dist/addons/archives.es.js +1 -1
- package/dist/addons/bidirectional.es.js +1 -1
- package/dist/addons/physical-sky.es.js +1 -1
- package/dist/rayzee-core.es.js +7 -7
- package/dist/rayzee.es.js +472 -468
- package/dist/rayzee.umd.js +106 -105
- package/dist/sceneCachePolicy-BAib5fCp.js +1602 -0
- package/package.json +1 -1
- package/src/PathTracerApp.js +4 -3
- package/src/Processor/ArchiveImporter.js +173 -94
- package/src/Processor/ArchiveReader.js +20 -23
- package/src/Processor/AssetLoader.js +48 -1
- package/src/Processor/BVHRefitter.js +109 -13
- package/src/Processor/BufferLayout.js +115 -0
- package/src/Processor/ChunkedRecords.js +3 -2
- package/src/Processor/EmissiveTriangleBuilder.js +6 -6
- package/src/Processor/GeometryExtractor.js +189 -79
- package/src/Processor/HostMemory.js +39 -9
- package/src/Processor/InstanceTable.js +714 -42
- package/src/Processor/PBRT/PBRTCurves.js +224 -6
- package/src/Processor/PBRT/PBRTMaterials.js +3 -0
- package/src/Processor/PBRT/PBRTSceneBuilder.js +82 -25
- package/src/Processor/PackedRayBuffer.js +5 -2
- package/src/Processor/SceneProcessor.js +747 -141
- package/src/Processor/TLASBuilder.js +32 -17
- package/src/Processor/USD/USDLayer.js +1149 -0
- package/src/Processor/USD/USDScene.js +1655 -0
- package/src/Processor/USD/USDStage.js +682 -0
- package/src/Processor/USD/USDText.js +674 -0
- package/src/Processor/USD/index.js +138 -0
- package/src/Processor/Workers/BVHRefitWorker.js +3 -3
- package/src/RayzeeRenderer.js +167 -26
- package/src/Stages/NormalDepth.js +7 -12
- package/src/Stages/PathTracer.js +12 -17
- package/src/Stages/PathTracerStage.js +273 -96
- package/src/Storage/GeometrySpill.js +46 -14
- package/src/Storage/SceneGraphCodec.js +17 -3
- package/src/Storage/SpillStore.js +9 -0
- package/src/TSL/BVHTraversal.js +183 -7
- package/src/TSL/Common.js +57 -13
- package/src/TSL/ShadeKernel.js +3 -2
- package/src/TSL/TextureSampling.js +11 -8
- package/src/TSL/patches.js +84 -0
- package/src/integrators/BidirectionalIntegrator.js +20 -2
- package/src/managers/DenoisingManager.js +4 -4
- package/src/managers/InteractionManager.js +19 -6
- package/src/managers/MaterialDataManager.js +22 -5
- package/src/managers/UniformManager.js +3 -0
- package/dist/ArchiveImporter-D_x863yu.js +0 -5198
- package/dist/BidirectionalIntegrator-DmkxxEOR.js +0 -946
- package/dist/SolarLimb-CPaeZVwz.js +0 -721
- package/dist/sceneCachePolicy-C4-4qwt6.js +0 -485
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* USD scene loader for multi-file scenes in a folder or archive: layers are read only as composition reaches them, and
|
|
3
|
+
* the stage is built by the pbrt importer's builder, so instancing, budgets and curve tessellation are shared.
|
|
4
|
+
*
|
|
5
|
+
* const files = new USDFiles( paths, read );
|
|
6
|
+
* const { group, environment, cameras } = await loadUSDScene( { files, entryPath, resolveImage, resolveEnvironment } );
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { PBRTSceneBuilder } from '../PBRT/PBRTSceneBuilder.js';
|
|
10
|
+
import { USDStage } from './USDStage.js';
|
|
11
|
+
import { USDSceneReader } from './USDScene.js';
|
|
12
|
+
|
|
13
|
+
export { USDFiles, USDStage } from './USDStage.js';
|
|
14
|
+
|
|
15
|
+
export const USD_LAYER = /\.(usd|usda|usdc)$/i;
|
|
16
|
+
|
|
17
|
+
/** USD layers that could be a scene's root, best first: the shallowest, then the shortest name. */
|
|
18
|
+
export function listUSDRootLayers( paths ) {
|
|
19
|
+
|
|
20
|
+
const layers = paths.filter( p => USD_LAYER.test( p ) && ! p.split( '/' ).pop().startsWith( '._' ) );
|
|
21
|
+
if ( layers.length === 0 ) return [];
|
|
22
|
+
const depth = p => p.split( '/' ).length;
|
|
23
|
+
const top = Math.min( ...layers.map( depth ) );
|
|
24
|
+
return layers.filter( p => depth( p ) === top ).sort( ( a, b ) => {
|
|
25
|
+
|
|
26
|
+
const na = a.split( '/' ).pop(), nb = b.split( '/' ).pop();
|
|
27
|
+
return na.length - nb.length || ( na < nb ? - 1 : na > nb ? 1 : 0 );
|
|
28
|
+
|
|
29
|
+
} );
|
|
30
|
+
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** The parts a large scene loads by: prims under the root's top prims that bring in sibling directories (Moana's elements/). */
|
|
34
|
+
export async function listUSDParts( stage, listing ) {
|
|
35
|
+
|
|
36
|
+
const parts = new Map();
|
|
37
|
+
const root = stage.pseudoRoot;
|
|
38
|
+
const rootDir = stage.root.id.includes( '/' ) ? stage.root.id.slice( 0, stage.root.id.lastIndexOf( '/' ) ) : '';
|
|
39
|
+
for ( const top of root.childNames() ) {
|
|
40
|
+
|
|
41
|
+
for ( const [ , spec, layer ] of ( await stage.child( root, top ) )?.sites ?? [] ) {
|
|
42
|
+
|
|
43
|
+
for ( const [ name, child ] of spec.children ?? [] ) {
|
|
44
|
+
|
|
45
|
+
for ( const key of [ 'references', 'payload' ] ) {
|
|
46
|
+
|
|
47
|
+
const op = child.arcs?.[ key ];
|
|
48
|
+
for ( const ref of [ ...( op?.explicit ?? [] ), ...( op?.prepend ?? [] ), ...( op?.append ?? [] ), ...( op?.add ?? [] ) ] ) {
|
|
49
|
+
|
|
50
|
+
const file = stage.files.resolve( ref.asset, layer.path );
|
|
51
|
+
if ( ! file?.includes( '/' ) ) continue;
|
|
52
|
+
const prefix = file.slice( 0, file.lastIndexOf( '/' ) );
|
|
53
|
+
if ( prefix === rootDir || ! prefix.startsWith( rootDir ) ) continue;
|
|
54
|
+
if ( ! parts.has( prefix ) ) parts.set( prefix, { name, prefix, files: 0, bytes: 0, scenes: 1 } );
|
|
55
|
+
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// A library referenced on its own beside the parts is not one.
|
|
67
|
+
const byParent = new Map();
|
|
68
|
+
for ( const part of parts.values() ) {
|
|
69
|
+
|
|
70
|
+
const parent = part.prefix.slice( 0, part.prefix.lastIndexOf( '/' ) );
|
|
71
|
+
if ( ! byParent.has( parent ) ) byParent.set( parent, [] );
|
|
72
|
+
byParent.get( parent ).push( part );
|
|
73
|
+
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const siblings = [ ...byParent.values() ].sort( ( a, b ) => b.length - a.length )[ 0 ] ?? [];
|
|
77
|
+
for ( const part of siblings ) {
|
|
78
|
+
|
|
79
|
+
for ( const entry of listing ) {
|
|
80
|
+
|
|
81
|
+
if ( ! entry.path.startsWith( `${part.prefix}/` ) || ! USD_LAYER.test( entry.path ) ) continue;
|
|
82
|
+
part.files ++;
|
|
83
|
+
part.bytes += entry.size;
|
|
84
|
+
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return siblings.sort( ( a, b ) => a.bytes - b.bytes );
|
|
90
|
+
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function loadUSDScene( args ) {
|
|
94
|
+
|
|
95
|
+
const warnings = [];
|
|
96
|
+
const warn = message => warnings.push( message );
|
|
97
|
+
const builder = new PBRTSceneBuilder( {
|
|
98
|
+
maxTriangles: args.maxTriangles, maxPlacements: args.maxPlacements, mergeShapesAbove: args.mergeShapesAbove,
|
|
99
|
+
curveSteps: args.curveSteps, curveSides: args.curveSides, curveTolerance: args.curveTolerance,
|
|
100
|
+
} );
|
|
101
|
+
|
|
102
|
+
const parseStart = performance.now();
|
|
103
|
+
const stage = new USDStage( args.files, { warn } );
|
|
104
|
+
await stage.open( args.entryPath );
|
|
105
|
+
const reader = new USDSceneReader( stage, {
|
|
106
|
+
maxTriangles: builder.maxTriangles, maxPlacements: builder.maxPlacements, curveSteps: builder.curveSteps,
|
|
107
|
+
resolveImage: args.resolveImage, warn,
|
|
108
|
+
} );
|
|
109
|
+
const read = await reader.read();
|
|
110
|
+
if ( stage.omittedArcs > 0 ) warn( `${stage.omittedArcs} reference(s) into the parts left out of this load skipped` );
|
|
111
|
+
const parseMs = performance.now() - parseStart;
|
|
112
|
+
|
|
113
|
+
const buildStart = performance.now();
|
|
114
|
+
const built = await builder.build( read.ir );
|
|
115
|
+
for ( const object of [ ...read.cameras, ...read.lights ] ) built.group.add( object );
|
|
116
|
+
|
|
117
|
+
let environment = null;
|
|
118
|
+
if ( read.dome ) {
|
|
119
|
+
|
|
120
|
+
const texture = await args.resolveEnvironment( read.dome.file );
|
|
121
|
+
if ( texture ) environment = { texture, intensity: read.dome.intensity, rotation: read.dome.rotation };
|
|
122
|
+
else warn( `USD dome light texture "${read.dome.file}" could not be read` );
|
|
123
|
+
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
...built,
|
|
128
|
+
environment,
|
|
129
|
+
cameras: read.cameras,
|
|
130
|
+
lights: read.lights,
|
|
131
|
+
cost: read.cost,
|
|
132
|
+
fitNote: reader.fitNote,
|
|
133
|
+
warnings: [ ...warnings, ...built.warnings ],
|
|
134
|
+
parseMs,
|
|
135
|
+
buildMs: performance.now() - buildStart,
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
}
|
|
@@ -46,9 +46,9 @@ self.onmessage = function ( e ) {
|
|
|
46
46
|
|
|
47
47
|
const startTime = performance.now();
|
|
48
48
|
|
|
49
|
-
const { blasRanges, tlasNodeCount } = e.data;
|
|
50
|
-
if ( blasRanges ) refitter.refitPartial( bvhData, triData, blasRanges, tlasNodeCount );
|
|
51
|
-
else refitter.refit( bvhData, triData, nodeCount );
|
|
49
|
+
const { blasRanges, tlasNodeCount, groupRange, treeNodeCount, recordNodeStart = - 1 } = e.data;
|
|
50
|
+
if ( blasRanges ) refitter.refitPartial( bvhData, triData, blasRanges, tlasNodeCount, groupRange, recordNodeStart );
|
|
51
|
+
else refitter.refit( bvhData, triData, treeNodeCount ?? nodeCount, recordNodeStart );
|
|
52
52
|
|
|
53
53
|
self.postMessage( {
|
|
54
54
|
type: 'refitComplete',
|
package/src/RayzeeRenderer.js
CHANGED
|
@@ -11,6 +11,7 @@ import { RenderPipeline } from './Pipeline/RenderPipeline.js';
|
|
|
11
11
|
import { CompletionTracker } from './Pipeline/CompletionTracker.js';
|
|
12
12
|
import { ENGINE_DEFAULTS as DEFAULT_STATE } from './EngineDefaults.js';
|
|
13
13
|
import { TRIANGLE_DATA_LAYOUT, BVH_LEAF_MARKERS } from './Processor/BufferLayout.js';
|
|
14
|
+
import { usesTextureCoordinates } from './Processor/GeometryExtractor.js';
|
|
14
15
|
import { MAX_STORAGE_TEXTURE_SIZE, MAX_RESERVABLE_RENDER_SIZE, setReservedRenderSize } from './Processor/StorageTexturePool.js';
|
|
15
16
|
import { updateStats, updateLoading, resetLoading, setStatusCallback, getDisplaySamples, disposeObjectFromMemory, disposeRenderer } from './Processor/utils.js';
|
|
16
17
|
import { BuildTimer } from './Processor/BuildTimer.js';
|
|
@@ -28,9 +29,9 @@ import { PackedToneMapper } from './Processor/ToneMapGPU.js';
|
|
|
28
29
|
import { BasicColor } from './Color/BasicColor.js';
|
|
29
30
|
import { setActiveColorManagement } from './Color/ActiveColor.js';
|
|
30
31
|
import { getViewTransform, DEFAULT_VIEW } from './Color/ViewTransforms.js';
|
|
31
|
-
import { AssetLoader } from './Processor/AssetLoader.js';
|
|
32
|
+
import { AssetLoader, limitEnvironmentWidth } from './Processor/AssetLoader.js';
|
|
32
33
|
import { SceneProcessor } from './Processor/SceneProcessor.js';
|
|
33
|
-
import { deviceMemoryGB, SPILL_TRIANGLE_BUDGET, SPILL_PLACEMENT_BUDGET } from './Processor/HostMemory.js';
|
|
34
|
+
import { deviceMemoryGB, SPILL_TRIANGLE_BUDGET, SPILL_PLACEMENT_BUDGET, SPILL_ENVIRONMENT_WIDTH } from './Processor/HostMemory.js';
|
|
34
35
|
import { createHeadlessCanvas } from './HeadlessCanvas.js';
|
|
35
36
|
|
|
36
37
|
// Managers
|
|
@@ -52,6 +53,8 @@ const partOf = ( { element, pbrtEntry } = {} ) => ( {
|
|
|
52
53
|
// One app per canvas — auto-dispose a prior owner if the caller double-
|
|
53
54
|
// instantiates (StrictMode, HMR, etc.) so its rAF loop can't burn CPU.
|
|
54
55
|
const _appsByCanvas = new WeakMap();
|
|
56
|
+
// Quiet time after an edit before memory spill puts back on disk what the edit read back.
|
|
57
|
+
const RESPILL_AFTER_MS = 30_000;
|
|
55
58
|
|
|
56
59
|
const KNOWN_EVENTS = new Set( [ ...Object.values( EngineEvents ), ...Object.values( LEGACY_EVENT_NAMES ) ] );
|
|
57
60
|
const _warnedEvents = new Set();
|
|
@@ -165,9 +168,11 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
165
168
|
* @param {false|'auto'|Object} [options.storage] - on-disk storage; defaults to
|
|
166
169
|
* `configureAssets( { storage } )`, or off under `strict` unless that was set. 'auto' opens it through
|
|
167
170
|
* {@link setStorageOpener} (rayzee/addons/storage); a host-supplied StorageManager stays the host's to dispose.
|
|
168
|
-
* @param {boolean} [options.memorySpill=
|
|
169
|
-
*
|
|
170
|
-
*
|
|
171
|
+
* @param {'auto'|boolean} [options.memorySpill='auto'] - build a static scene through disk: its triangle records,
|
|
172
|
+
* BLAS nodes and three.js geometry leave memory as they are built (see {@link ensureSceneResident}), scene archives
|
|
173
|
+
* load past the default budgets, and an environment wider than SPILL_ENVIRONMENT_WIDTH is box-filtered down.
|
|
174
|
+
* 'auto' does so only for a scene whose in-memory estimate passes SAFE_SCENE_BYTES; true for every scene, false
|
|
175
|
+
* never. Needs storage; skipped for animated scenes. See {@link setMemorySpill}.
|
|
171
176
|
*
|
|
172
177
|
* The engine dispatches `EngineEvents.FRAME` after each animate() iteration so hosts can
|
|
173
178
|
* tick external instrumentation (e.g. a stats panel) without coupling the engine to it.
|
|
@@ -198,7 +203,8 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
198
203
|
this._hostMemoryGB = options.hostMemoryGB;
|
|
199
204
|
this._storageOption = options.storage;
|
|
200
205
|
this._storageOpener = null;
|
|
201
|
-
this._memorySpill = options.memorySpill === true;
|
|
206
|
+
this._memorySpill = options.memorySpill === true || options.memorySpill === false ? options.memorySpill : 'auto';
|
|
207
|
+
this._spillPlan = null;
|
|
202
208
|
/** @type {?Object} on-disk storage (a StorageManager), null when off or unavailable */
|
|
203
209
|
this.storage = null;
|
|
204
210
|
this._storageRelease = null;
|
|
@@ -708,6 +714,8 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
708
714
|
this.stopAnimation();
|
|
709
715
|
clearTimeout( this._resizeDebounceTimer );
|
|
710
716
|
this._resizeDebounceTimer = null;
|
|
717
|
+
clearTimeout( this._respillTimer );
|
|
718
|
+
this._respillTimer = null;
|
|
711
719
|
|
|
712
720
|
this._removeTrackedListeners();
|
|
713
721
|
setStatusCallback( null );
|
|
@@ -1175,6 +1183,8 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
1175
1183
|
if ( this._loadingInProgress ) throw this._busyError( 'PathTracerApp' );
|
|
1176
1184
|
|
|
1177
1185
|
this._loadingInProgress = true;
|
|
1186
|
+
// The previous scene's sky width must not narrow this one's while it loads.
|
|
1187
|
+
if ( this.assetLoader ) this.assetLoader.maxEnvironmentWidth = this._memorySpill === true ? SPILL_ENVIRONMENT_WIDTH : 0;
|
|
1178
1188
|
|
|
1179
1189
|
try {
|
|
1180
1190
|
|
|
@@ -1189,6 +1199,8 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
1189
1199
|
// (The old primary was already released by releaseTargetModel() in loadFn.)
|
|
1190
1200
|
this._clearAppendedModels();
|
|
1191
1201
|
this._modelReplaced();
|
|
1202
|
+
// Before the scene's own environment starts loading: a spilling scene's is narrowed as it decodes.
|
|
1203
|
+
this._planSpill();
|
|
1192
1204
|
await this.loadSceneData( { pendingEnvironment: this._beginSceneMetadataEnvironment() } );
|
|
1193
1205
|
this._maybeSpill();
|
|
1194
1206
|
this.pipeline?.eventBus.emit( 'pipeline:lightingChanged' );
|
|
@@ -1215,6 +1227,7 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
1215
1227
|
} finally {
|
|
1216
1228
|
|
|
1217
1229
|
this._loadingInProgress = false;
|
|
1230
|
+
this._spillPlan = null;
|
|
1218
1231
|
|
|
1219
1232
|
}
|
|
1220
1233
|
|
|
@@ -1445,11 +1458,22 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
1445
1458
|
updateLoading( { status: "Transferring data to GPU...", progress: 86 } );
|
|
1446
1459
|
await new Promise( r => setTimeout( r, 0 ) );
|
|
1447
1460
|
timer.start( 'GPU data transfer' );
|
|
1461
|
+
await this._sdf.uploadChunks( this._chunkUploader() );
|
|
1448
1462
|
|
|
1449
1463
|
// Re-read rather than reusing the snapshot above: a pendingEnvironment may have landed
|
|
1450
1464
|
// during the BVH build, and the snapshot is then a disposed texture.
|
|
1451
1465
|
if ( ! this._sdf.uploadToPathTracer( this.stages.pathTracer, this.lightManager, this.meshScene, hdri(), { keepUserLights } ) ) return false;
|
|
1452
1466
|
|
|
1467
|
+
// A bucket at a time, each awaited (PathTracerStage#drainUploads): left to the first frame, three.js sends every
|
|
1468
|
+
// layer at once, and Chrome keeps a pool that size (724 MB for 24155522.glb).
|
|
1469
|
+
for ( const texture of [ ...( this._sdf.srgbBucketTextures ?? [] ), ...( this._sdf.linearBucketTextures ?? [] ) ] ) {
|
|
1470
|
+
|
|
1471
|
+
if ( ! texture ) continue;
|
|
1472
|
+
this.renderer.initTexture( texture );
|
|
1473
|
+
await this.stages.pathTracer.drainUploads();
|
|
1474
|
+
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1453
1477
|
// Patch per-mesh visibility into the TLAS leaves we just uploaded
|
|
1454
1478
|
this.stages.pathTracer._meshRefs = this.stages.pathTracer._collectMeshRefs( this.meshScene );
|
|
1455
1479
|
this.stages.pathTracer.setMeshVisibilityData( this.stages.pathTracer._meshRefs );
|
|
@@ -1724,43 +1748,124 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
1724
1748
|
// Dynamic cameras (add / remove)
|
|
1725
1749
|
// ═══════════════════════════════════════════════════════════════
|
|
1726
1750
|
|
|
1727
|
-
/**
|
|
1751
|
+
/** How scenes use the disk: 'auto', true or false (see the constructor's `memorySpill`). */
|
|
1752
|
+
get memorySpill() {
|
|
1753
|
+
|
|
1754
|
+
return this._memorySpill;
|
|
1755
|
+
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
/** Sets {@link memorySpill}; the next load or rebuild follows it. */
|
|
1759
|
+
setMemorySpill( mode ) {
|
|
1760
|
+
|
|
1761
|
+
this._memorySpill = mode === true || mode === false ? mode : 'auto';
|
|
1762
|
+
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1765
|
+
/** Whether the current scene was built through disk. */
|
|
1766
|
+
get sceneSpilled() {
|
|
1767
|
+
|
|
1768
|
+
return !! this._sdf?.spilling;
|
|
1769
|
+
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
/** Whether the model's three.js geometry is on disk, so picking skips it until {@link ensureSceneResident}. */
|
|
1773
|
+
get geometryOnDisk() {
|
|
1774
|
+
|
|
1775
|
+
return this._sdf?.geometryOnDisk === true;
|
|
1776
|
+
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
/**
|
|
1780
|
+
* Triangle and placement budgets for a scene archive: raised whenever a build could spill, since the preflight
|
|
1781
|
+
* refuses what cannot fit either way. @private
|
|
1782
|
+
*/
|
|
1728
1783
|
_sceneBudgets( options ) {
|
|
1729
1784
|
|
|
1730
|
-
if (
|
|
1785
|
+
if ( this._memorySpill === false || ! this.storage ) return options;
|
|
1731
1786
|
return { maxTriangles: SPILL_TRIANGLE_BUDGET, maxPlacements: SPILL_PLACEMENT_BUDGET, ...options };
|
|
1732
1787
|
|
|
1733
1788
|
}
|
|
1734
1789
|
|
|
1735
1790
|
/**
|
|
1736
|
-
*
|
|
1737
|
-
*
|
|
1791
|
+
* Decides whether the scene about to be built spills: static scenes only, since a deforming clip refits every
|
|
1792
|
+
* frame. Held for the build that follows ({@link _progressiveSpill}). @private
|
|
1738
1793
|
*/
|
|
1794
|
+
_planSpill() {
|
|
1795
|
+
|
|
1796
|
+
const offered = this._memorySpill !== false && !! this.storage && ( this.assetLoader?.animations?.length ?? 0 ) === 0;
|
|
1797
|
+
const spill = offered && ( this._memorySpill === true || this._sdf.needsSpill( this.meshScene ) );
|
|
1798
|
+
if ( this.assetLoader ) this.assetLoader.maxEnvironmentWidth = spill ? SPILL_ENVIRONMENT_WIDTH : 0;
|
|
1799
|
+
// An importer installs its own sky at the end of its read, before this.
|
|
1800
|
+
const sky = spill ? this.meshScene?.environment : null;
|
|
1801
|
+
const image = sky?.image;
|
|
1802
|
+
if ( image ) limitEnvironmentWidth( sky, SPILL_ENVIRONMENT_WIDTH );
|
|
1803
|
+
if ( sky && sky.image !== image ) {
|
|
1804
|
+
|
|
1805
|
+
// three.js keeps an uploaded texture's size, so the old one goes.
|
|
1806
|
+
sky.dispose();
|
|
1807
|
+
sky.needsUpdate = true;
|
|
1808
|
+
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
if ( spill && this._memorySpill === 'auto' ) log.info( 'scene is past the in-memory line: building it through disk' );
|
|
1812
|
+
this._spillPlan = spill;
|
|
1813
|
+
return spill;
|
|
1814
|
+
|
|
1815
|
+
}
|
|
1816
|
+
|
|
1817
|
+
/** @private */
|
|
1739
1818
|
_progressiveSpill() {
|
|
1740
1819
|
|
|
1741
|
-
|
|
1820
|
+
const spill = this._spillPlan ?? this._planSpill();
|
|
1821
|
+
this._spillPlan = null;
|
|
1822
|
+
return spill ? { storage: this.storage, uploader: this._chunkUploader() } : null;
|
|
1823
|
+
|
|
1824
|
+
}
|
|
1825
|
+
|
|
1826
|
+
/** @private */
|
|
1827
|
+
_chunkUploader() {
|
|
1828
|
+
|
|
1742
1829
|
const stage = this.stages.pathTracer;
|
|
1743
|
-
return
|
|
1830
|
+
return ( records, kind, options ) => kind === 'triangles' ? stage.createTriangleChunkUploader( records, options ) : stage.createChunkUploader( records );
|
|
1744
1831
|
|
|
1745
1832
|
}
|
|
1746
1833
|
|
|
1747
|
-
/**
|
|
1834
|
+
/** The rest of a spilling scene's spill, once it is on the GPU, in the background. @private */
|
|
1748
1835
|
_maybeSpill() {
|
|
1749
1836
|
|
|
1750
|
-
if ( ! this.
|
|
1751
|
-
this._sdf
|
|
1837
|
+
if ( ! this._sdf?.spilling || ! this.storage ) return;
|
|
1838
|
+
this._sdf.spillAfterLoad().catch( error => this._issues.warn( ISSUE_CODES.STORAGE_WRITE_FAILED, `memory spill failed: ${error.message}` ) );
|
|
1839
|
+
this._sdf.spillToDisk( this.storage )
|
|
1752
1840
|
.then( result => result && this.dispatchEvent( { type: EngineEvents.SCENE_SPILLED, ...result } ) )
|
|
1753
1841
|
.catch( error => this._issues.warn( ISSUE_CODES.STORAGE_WRITE_FAILED, `memory spill failed: ${error.message}` ) );
|
|
1754
1842
|
|
|
1755
1843
|
}
|
|
1756
1844
|
|
|
1845
|
+
/** Once edits have been quiet for {@link RESPILL_AFTER_MS}, puts back on disk what they read back. @private */
|
|
1846
|
+
_scheduleRespill() {
|
|
1847
|
+
|
|
1848
|
+
if ( ! this._sdf?.spilling ) return;
|
|
1849
|
+
clearTimeout( this._respillTimer );
|
|
1850
|
+
this._respillTimer = setTimeout( () => {
|
|
1851
|
+
|
|
1852
|
+
this._respillTimer = null;
|
|
1853
|
+
this._sdf?.respill().catch( error => this._issues.warn( ISSUE_CODES.STORAGE_WRITE_FAILED, `memory spill failed: ${error.message}` ) );
|
|
1854
|
+
|
|
1855
|
+
}, RESPILL_AFTER_MS );
|
|
1856
|
+
this._respillTimer.unref?.();
|
|
1857
|
+
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1757
1860
|
/**
|
|
1758
|
-
* Reads back what {@link options.memorySpill} moved to disk
|
|
1759
|
-
*
|
|
1861
|
+
* Reads back what {@link options.memorySpill} moved to disk: triangle records, BLAS nodes and the three.js geometry
|
|
1862
|
+
* (whose arrays are empty until then). The engine does this itself before its own readers; a host calling
|
|
1863
|
+
* {@link refitBLASes}, or reading the model's geometry, on a spilled scene awaits it first.
|
|
1760
1864
|
*/
|
|
1761
1865
|
async ensureSceneResident() {
|
|
1762
1866
|
|
|
1763
1867
|
if ( this._sdf?.spilled ) await this._sdf.ensureResident();
|
|
1868
|
+
await this._sdf?.ensureGeometryResident();
|
|
1764
1869
|
|
|
1765
1870
|
}
|
|
1766
1871
|
|
|
@@ -1885,15 +1990,26 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
1885
1990
|
* drags along any other object sharing the same geometry.
|
|
1886
1991
|
*
|
|
1887
1992
|
* @param {number[]} meshIndices - indices into {@link sceneMeshes}
|
|
1888
|
-
* @returns {{ refitTimeMs: number, placements: number }}
|
|
1993
|
+
* @returns {?{ refitTimeMs: number, placements: number }} null when the move waits for a spilled scene's data
|
|
1889
1994
|
*/
|
|
1890
1995
|
updateMeshTransforms( meshIndices ) {
|
|
1891
1996
|
|
|
1997
|
+
// A spilled scene's matrix lists, TLAS or copy records are on disk: the move lands once they are back.
|
|
1998
|
+
if ( this._sdf?.needsPageInForMove( meshIndices ) ) {
|
|
1999
|
+
|
|
2000
|
+
this._sdf.ensureMovable( meshIndices )
|
|
2001
|
+
.then( () => this.updateMeshTransforms( meshIndices ) )
|
|
2002
|
+
.catch( error => this._issues.warn( ISSUE_CODES.STORAGE_READ_FAILED, `reading instance matrices back failed: ${error.message}` ) );
|
|
2003
|
+
return null;
|
|
2004
|
+
|
|
2005
|
+
}
|
|
2006
|
+
|
|
1892
2007
|
this._notePlacementsMoving( meshIndices );
|
|
1893
2008
|
const result = this._sdf.updateMeshTransforms( meshIndices );
|
|
1894
2009
|
|
|
1895
|
-
this.stages.pathTracer.updateBufferRanges( [],
|
|
2010
|
+
this.stages.pathTracer.updateBufferRanges( [], this._sdf.takeMoveRanges() );
|
|
1896
2011
|
this.reset( false, { motion: true } );
|
|
2012
|
+
this._scheduleRespill();
|
|
1897
2013
|
|
|
1898
2014
|
return result;
|
|
1899
2015
|
|
|
@@ -3198,9 +3314,7 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
3198
3314
|
*/
|
|
3199
3315
|
setMeshVisibility( meshIndex, visible ) {
|
|
3200
3316
|
|
|
3201
|
-
this.stages.pathTracer?.updateMeshVisibility( meshIndex, visible );
|
|
3202
|
-
this._refreshEmissiveForVisibility();
|
|
3203
|
-
this.reset();
|
|
3317
|
+
this._whenTLASEditable( () => this.stages.pathTracer?.updateMeshVisibility( meshIndex, visible ) );
|
|
3204
3318
|
|
|
3205
3319
|
}
|
|
3206
3320
|
|
|
@@ -3210,9 +3324,25 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
3210
3324
|
*/
|
|
3211
3325
|
updateAllMeshVisibility() {
|
|
3212
3326
|
|
|
3213
|
-
this.stages.pathTracer?.updateAllMeshVisibility();
|
|
3214
|
-
|
|
3215
|
-
|
|
3327
|
+
this._whenTLASEditable( () => this.stages.pathTracer?.updateAllMeshVisibility() );
|
|
3328
|
+
|
|
3329
|
+
}
|
|
3330
|
+
|
|
3331
|
+
/** Applies a visibility edit, then resets — once a spilled TLAS is back in memory. @private */
|
|
3332
|
+
_whenTLASEditable( edit ) {
|
|
3333
|
+
|
|
3334
|
+
const apply = () => {
|
|
3335
|
+
|
|
3336
|
+
edit();
|
|
3337
|
+
this._refreshEmissiveForVisibility();
|
|
3338
|
+
this.reset();
|
|
3339
|
+
this._scheduleRespill();
|
|
3340
|
+
|
|
3341
|
+
};
|
|
3342
|
+
|
|
3343
|
+
const ready = this._sdf?.whenTLASEditable();
|
|
3344
|
+
if ( ! ready ) return apply();
|
|
3345
|
+
ready.then( apply ).catch( error => this._issues.warn( ISSUE_CODES.STORAGE_READ_FAILED, `reading the TLAS back failed: ${error.message}` ) );
|
|
3216
3346
|
|
|
3217
3347
|
}
|
|
3218
3348
|
|
|
@@ -3472,7 +3602,18 @@ export class RayzeeRenderer extends EventDispatcher {
|
|
|
3472
3602
|
async rebuildMaterials( scene ) {
|
|
3473
3603
|
|
|
3474
3604
|
await this.ensureSceneResident();
|
|
3475
|
-
|
|
3605
|
+
const stage = this.stages.pathTracer;
|
|
3606
|
+
await stage?.rebuildMaterials( scene || this.meshScene );
|
|
3607
|
+
|
|
3608
|
+
// Loaded without texture coordinates, and a material has a texture now: they go up again from the records.
|
|
3609
|
+
if ( stage?.triangleStorageNode?.withoutUV && this._sdf?.triangles && usesTextureCoordinates( scene || this.meshScene ) ) {
|
|
3610
|
+
|
|
3611
|
+
this._sdf.textureCoordinates = true;
|
|
3612
|
+
stage.setTriangleData( this._sdf.triangles, this._sdf.triangleCount, { textureCoordinates: true } );
|
|
3613
|
+
stage.setupMaterial();
|
|
3614
|
+
|
|
3615
|
+
}
|
|
3616
|
+
|
|
3476
3617
|
this.reset();
|
|
3477
3618
|
|
|
3478
3619
|
}
|
|
@@ -169,21 +169,16 @@ export class NormalDepth extends RenderStage {
|
|
|
169
169
|
|
|
170
170
|
}
|
|
171
171
|
|
|
172
|
-
if (
|
|
172
|
+
if ( ! this._triStorageNode || ! this._bvhStorageNode ) {
|
|
173
173
|
|
|
174
|
-
//
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
shade: storage( pt.triangleShadeAttr, 'uvec4', pt.triangleShadeAttr.count ).toReadOnly(),
|
|
178
|
-
};
|
|
174
|
+
// The path tracer's layout, split into parts as it is: uvec4 triangles keep packed lanes' exact bits.
|
|
175
|
+
const nodes = pt.sceneStorageNodes?.();
|
|
176
|
+
if ( nodes ) {
|
|
179
177
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if ( pt.bvhStorageAttr && ! this._bvhStorageNode ) {
|
|
178
|
+
this._triStorageNode = nodes.triangles;
|
|
179
|
+
this._bvhStorageNode = nodes.bvh;
|
|
183
180
|
|
|
184
|
-
|
|
185
|
-
pt.bvhStorageAttr, 'vec4', pt.bvhStorageAttr.count
|
|
186
|
-
).toReadOnly();
|
|
181
|
+
}
|
|
187
182
|
|
|
188
183
|
}
|
|
189
184
|
|
package/src/Stages/PathTracer.js
CHANGED
|
@@ -57,7 +57,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
57
57
|
this._packedBuffers = null;
|
|
58
58
|
this._queueManager = null;
|
|
59
59
|
this._kernelManager = null;
|
|
60
|
-
this._gBufferAttr = null; // per-pixel first-hit MRT (ND + albedo); see _buildWavefrontKernels
|
|
61
60
|
this._m2Attr = null; // per-pixel running mean of luminance² for the Tier-1 convergence early-stop
|
|
62
61
|
this._streakAttr = null; // Tier-2: per-pixel freeze-candidate streak (u32); frozen := streak >= K
|
|
63
62
|
// Tier-2 dilation: per-pixel frozen mask (1=skip), written race-free in buildActivePixels; a frozen pixel
|
|
@@ -209,7 +208,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
209
208
|
t.register( 'gbuffer', () => {
|
|
210
209
|
|
|
211
210
|
const a = [];
|
|
212
|
-
if ( this._gBufferAttr ) a.push( this._gBufferAttr );
|
|
213
211
|
if ( this._m2Attr ) a.push( this._m2Attr );
|
|
214
212
|
if ( this._streakAttr ) a.push( this._streakAttr );
|
|
215
213
|
if ( this._frozenMaskAttr ) a.push( this._frozenMaskAttr );
|
|
@@ -226,7 +224,8 @@ export class PathTracer extends PathTracerStage {
|
|
|
226
224
|
} );
|
|
227
225
|
|
|
228
226
|
// Scene geometry (triangle data, two-level BVH, light BVH + emissive)
|
|
229
|
-
|
|
227
|
+
// The light data shares the materials' buffer and is counted with them.
|
|
228
|
+
t.register( 'geometry', () => [ ...( this._geoParts?.attrs ?? [] ), ...( this._shadeParts?.attrs ?? [] ), ...( this._bvhParts?.attrs ?? [ this.bvhStorageAttr ] ) ] );
|
|
230
229
|
|
|
231
230
|
// Material storage buffer + per-property texture arrays
|
|
232
231
|
t.register( 'materials', () => {
|
|
@@ -1369,8 +1368,9 @@ export class PathTracer extends PathTracerStage {
|
|
|
1369
1368
|
_computePathBudget() {
|
|
1370
1369
|
|
|
1371
1370
|
const RAY_BYTES = RAY_STRIDE * 16;
|
|
1372
|
-
|
|
1373
|
-
const
|
|
1371
|
+
// The hit buffer carries the G-buffer as its last region.
|
|
1372
|
+
const HIT_BYTES = ( ( this._integrator?.hitStride ?? HIT_STRIDE ) + GBUFFER_STRIDE ) * 16;
|
|
1373
|
+
const bytesPerPath = RAY_BYTES + HIT_BYTES
|
|
1374
1374
|
+ 4 + 4 /* activeIndices A/B */ + 4;
|
|
1375
1375
|
|
|
1376
1376
|
const limits = this.renderer?.backend?.device?.limits;
|
|
@@ -1493,15 +1493,12 @@ export class PathTracer extends PathTracerStage {
|
|
|
1493
1493
|
|
|
1494
1494
|
}
|
|
1495
1495
|
|
|
1496
|
-
// Per-CHUNK first-hit G-buffer (LOCAL slot r, size B), 1 uvec4/slot half-packed (pack2x16)
|
|
1497
|
-
// Generate + Shade(bounce 0), read by Shade + FinalWrite within the
|
|
1498
|
-
// uint (not f32): packed lanes can hit the NaN exponent range (snorm 1.0 →
|
|
1499
|
-
// canonicalize; u32 stores the bits verbatim.
|
|
1500
|
-
const
|
|
1501
|
-
|
|
1502
|
-
this._gBufferAttr = gpuOnlyStorageAttribute( gBufferVec4s, 4, Uint32Array );
|
|
1503
|
-
const gBufferRW = storage( this._gBufferAttr, 'uvec4' );
|
|
1504
|
-
const gBufferRO = storage( this._gBufferAttr, 'uvec4' ).toReadOnly();
|
|
1496
|
+
// Per-CHUNK first-hit G-buffer (LOCAL slot r, size B), 1 uvec4/slot half-packed (pack2x16), the hit buffer's
|
|
1497
|
+
// last region (PackedRayBuffer). Written by Generate + Shade(bounce 0), read by Shade + FinalWrite within the
|
|
1498
|
+
// SAME chunk, so per-chunk suffices. uint (not f32): packed lanes can hit the NaN exponent range (snorm 1.0 →
|
|
1499
|
+
// 0x7FFF) that an f32 store may canonicalize; u32 stores the bits verbatim.
|
|
1500
|
+
const gBufferRW = this._packedBuffers.hitBuffer.rw;
|
|
1501
|
+
const gBufferRO = this._packedBuffers.hitBuffer.ro;
|
|
1505
1502
|
|
|
1506
1503
|
// Per-PIXEL persistent buffers (m2/streak/frozenMask) are GLOBAL-pixel-indexed (p = pixelBase + r) and
|
|
1507
1504
|
// must span the whole frame across chunks AND persist across frames, so they're sized to the reserved
|
|
@@ -1842,7 +1839,7 @@ export class PathTracer extends PathTracerStage {
|
|
|
1842
1839
|
const freshTri = this.triangleStorageNode;
|
|
1843
1840
|
const freshMat = this.materialData.materialStorageNode;
|
|
1844
1841
|
const freshEnvCDF = texture( this.environment.envCDFTexture ); // independent CDF texture node; refreshed in _refreshWfTextureNodes
|
|
1845
|
-
const freshLight = this.
|
|
1842
|
+
const freshLight = this.lightDataNode;
|
|
1846
1843
|
// Independent texture nodes (never compiled elsewhere) avoid Three.js TextureNode caching across pipelines; refreshed via _refreshWfTextureNodes.
|
|
1847
1844
|
const _mat = this.materialData;
|
|
1848
1845
|
const _env = this.environment;
|
|
@@ -2231,7 +2228,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
2231
2228
|
this._packedBuffers?.dispose();
|
|
2232
2229
|
this._queueManager?.dispose();
|
|
2233
2230
|
this._kernelManager?.dispose();
|
|
2234
|
-
this._gBufferAttr?.dispose?.();
|
|
2235
2231
|
this._m2Attr?.dispose?.();
|
|
2236
2232
|
this._streakAttr?.dispose?.();
|
|
2237
2233
|
this._frozenMaskAttr?.dispose?.();
|
|
@@ -2241,7 +2237,6 @@ export class PathTracer extends PathTracerStage {
|
|
|
2241
2237
|
this._packedBuffers = null;
|
|
2242
2238
|
this._queueManager = null;
|
|
2243
2239
|
this._kernelManager = null;
|
|
2244
|
-
this._gBufferAttr = null;
|
|
2245
2240
|
this._m2Attr = null;
|
|
2246
2241
|
this._streakAttr = null;
|
|
2247
2242
|
this._frozenMaskAttr = null;
|