rayzee 9.0.0 → 9.1.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 +139 -8
- package/dist/{NeuralRetouch-CYOJ21Wn.js → NeuralRetouch-BRquqfmZ.js} +1 -1
- package/dist/{NeuralSuperRes-CQLCLpcC.js → NeuralSuperRes-CbNvQjCw.js} +3 -2
- package/dist/{ToneMapGPU-BVE852iN.js → ToneMapGPU-DpUZDMbQ.js} +2 -1
- package/dist/rayzee.es.js +10344 -5334
- package/dist/rayzee.umd.js +265 -265
- package/package.json +1 -1
- package/src/AssetConfig.js +4 -0
- package/src/EngineDefaults.js +38 -56
- package/src/EngineEvents.js +3 -0
- package/src/EngineIssues.js +6 -0
- package/src/Headless.js +3 -1
- package/src/Passes/AIUpscaler.js +2 -1
- package/src/Passes/OIDNDenoiser.js +3 -6
- package/src/PathTracerApp.js +290 -19
- package/src/Processor/ArchiveCache.js +120 -0
- package/src/Processor/ArchiveReader.js +179 -32
- package/src/Processor/AssetLoader.js +514 -76
- package/src/Processor/AtmosphereModel.js +465 -0
- package/src/Processor/BVHBuilder.js +4 -1
- package/src/Processor/BVHLeafFold.js +100 -0
- package/src/Processor/BVHRefitter.js +98 -214
- package/src/Processor/ChunkedRecords.js +189 -4
- package/src/Processor/EmissiveTriangleBuilder.js +8 -0
- package/src/Processor/GeometryExtractor.js +238 -40
- package/src/Processor/InstanceTable.js +55 -37
- package/src/Processor/PBRT/PBRTCurves.js +104 -27
- package/src/Processor/PBRT/PBRTParser.js +132 -22
- package/src/Processor/PBRT/PBRTSceneBuilder.js +192 -49
- package/src/Processor/PBRT/buffers.js +27 -0
- package/src/Processor/PBRT/index.js +55 -15
- package/src/Processor/PackedRayBuffer.js +10 -13
- package/src/Processor/ParallelBVHBuilder.js +36 -15
- package/src/Processor/PhysicalSky.js +308 -0
- package/src/Processor/QueueManager.js +1 -0
- package/src/Processor/SceneProcessor.js +1132 -181
- package/src/Processor/SplitBLAS.js +249 -0
- package/src/Processor/SunPosition.js +41 -0
- package/src/Processor/TextureCreator.js +21 -0
- package/src/Processor/Workers/AIUpscalerWorker.js +79 -52
- package/src/Processor/Workers/BVHWorker.js +17 -13
- package/src/Processor/ZipReader.js +207 -0
- package/src/RenderSettings.js +38 -0
- package/src/SceneState/SceneState.js +367 -0
- package/src/SceneState/portable.js +52 -0
- package/src/Stages/NormalDepth.js +7 -6
- package/src/Stages/PathTracer.js +108 -4
- package/src/Stages/PathTracerStage.js +251 -104
- package/src/Storage/BLASCache.js +190 -0
- package/src/Storage/CDFCache.js +77 -0
- package/src/Storage/DownloadCache.js +266 -0
- package/src/Storage/GeometrySpill.js +217 -0
- package/src/Storage/SceneGraphCodec.js +503 -0
- package/src/Storage/SpillStore.js +112 -0
- package/src/Storage/StorageManager.js +934 -0
- package/src/Storage/StorageOps.js +137 -0
- package/src/Storage/StorageWorker.js +37 -0
- package/src/Storage/events.js +29 -0
- package/src/Storage/identity.js +77 -0
- package/src/Storage/index.js +9 -0
- package/src/Storage/inlineTransport.js +24 -0
- package/src/Storage/locks.js +99 -0
- package/src/Storage/openStorage.js +87 -0
- package/src/Storage/sceneCachePolicy.js +12 -0
- package/src/Storage/shared.js +9 -0
- package/src/Storage/transport.js +62 -0
- package/src/TSL/Atmosphere.js +684 -0
- package/src/TSL/BVHTraversal.js +288 -239
- package/src/TSL/Common.js +11 -2
- package/src/TSL/Displacement.js +6 -6
- package/src/TSL/EmissiveSampling.js +5 -5
- package/src/TSL/EnvironmentCDF.js +176 -0
- package/src/TSL/GenerateKernel.js +3 -3
- package/src/TSL/HitFacet.js +4 -4
- package/src/TSL/LightsDirect.js +8 -8
- package/src/TSL/LightsSampling.js +55 -37
- package/src/TSL/Random.js +1 -0
- package/src/TSL/ShadeKernel.js +75 -19
- package/src/TSL/Sun.js +40 -0
- package/src/TSL/TextureSampling.js +6 -7
- package/src/index.js +18 -0
- package/src/managers/CameraManager.js +169 -1
- package/src/managers/EnvironmentManager.js +263 -33
- package/src/managers/GoboManager.js +19 -8
- package/src/managers/IESManager.js +3 -3
- package/src/managers/LightManager.js +98 -5
- package/src/managers/MaterialDataManager.js +22 -2
- package/src/managers/TransformManager.js +26 -0
- package/src/managers/UniformManager.js +5 -3
- package/src/managers/VideoRenderManager.js +3 -1
- package/src/managers/timeline/CameraTrack.js +22 -0
- package/src/managers/timeline/TimelineManager.js +14 -0
- package/src/version.js +3 -0
- package/src/Processor/ProceduralSky.js +0 -291
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* needed. Enable only if a scene comes out z-mirrored against a known reference.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { freeNow, resized } from './buffers.js';
|
|
17
18
|
import {
|
|
18
19
|
Group, Mesh, InstancedMesh, PerspectiveCamera, OrthographicCamera, Matrix4, Vector3, Quaternion,
|
|
19
20
|
BufferGeometry, Float32BufferAttribute, Uint32BufferAttribute, SphereGeometry,
|
|
@@ -50,9 +51,7 @@ function grow( array, needed ) {
|
|
|
50
51
|
if ( needed <= array.length ) return array;
|
|
51
52
|
let length = array.length || 1;
|
|
52
53
|
while ( length < needed ) length *= 2;
|
|
53
|
-
|
|
54
|
-
out.set( array );
|
|
55
|
-
return out;
|
|
54
|
+
return resized( array, length );
|
|
56
55
|
|
|
57
56
|
}
|
|
58
57
|
|
|
@@ -76,6 +75,11 @@ const DEFAULT_TRIANGLE_BUDGET = 45_000_000;
|
|
|
76
75
|
// Placements are their own budget: geometry is shared, but each costs a TLAS leaf and an
|
|
77
76
|
// instance record. isCoastline's 5.09M loaded and rendered at 23 fps.
|
|
78
77
|
const DEFAULT_PLACEMENT_BUDGET = 6_000_000;
|
|
78
|
+
// With the memory spill the triangles and BVH leave the heap as the build goes, so the host
|
|
79
|
+
// passes these instead. The whole 15-part Moana subset (55.7M / 7.0M) loads cold under them, at
|
|
80
|
+
// an 8.1 GB preflight estimate; the preflight, not the budget, refuses anything larger.
|
|
81
|
+
export const SPILL_TRIANGLE_BUDGET = 60_000_000;
|
|
82
|
+
export const SPILL_PLACEMENT_BUDGET = 8_000_000;
|
|
79
83
|
// Non-instanced shapes merge into a few large meshes: isPalmRig declares 173,338 of them,
|
|
80
84
|
// and one BufferGeometry + Mesh each measured 3.3 GB — the loader OOM'd before tracing.
|
|
81
85
|
const DEFAULT_MERGE_MIN_SHAPES = 256;
|
|
@@ -86,6 +90,20 @@ const MERGE_BATCH_TRIANGLES = 500_000;
|
|
|
86
90
|
// a crossed pair of ribbons is 16 triangles a curve.
|
|
87
91
|
const DEFAULT_CURVE_STEPS = 2;
|
|
88
92
|
const DEFAULT_CURVE_SIDES = { flat: 1, ribbon: 1, cylinder: 2 };
|
|
93
|
+
// A strip segment may stray 5 % of the half-width from the curve: 37 % of Moana's curve
|
|
94
|
+
// triangles go, at 3 % frame time in a view full of them. 0.1 took 48 % for 6 %.
|
|
95
|
+
const DEFAULT_CURVE_TOLERANCE = 0.05;
|
|
96
|
+
|
|
97
|
+
/** Bumped whenever the same scene files build a different graph, so a stored graph is not reused. */
|
|
98
|
+
export const PBRT_BUILD_REVISION = 4;
|
|
99
|
+
|
|
100
|
+
function samePlacements( a, b ) {
|
|
101
|
+
|
|
102
|
+
if ( a.count !== b.count ) return false;
|
|
103
|
+
for ( let i = 0, n = a.count * 16; i < n; i ++ ) if ( a.matrices[ i ] !== b.matrices[ i ] ) return false;
|
|
104
|
+
return true;
|
|
105
|
+
|
|
106
|
+
}
|
|
89
107
|
|
|
90
108
|
/** Pixels out of an HTMLImageElement / ImageBitmap, which expose none of their own. */
|
|
91
109
|
function pixelsFromDrawable( image, srgb ) {
|
|
@@ -160,6 +178,7 @@ export class PBRTSceneBuilder {
|
|
|
160
178
|
this.mergeShapesAbove = resolvers.mergeShapesAbove ?? DEFAULT_MERGE_MIN_SHAPES;
|
|
161
179
|
this.curveSteps = resolvers.curveSteps ?? DEFAULT_CURVE_STEPS;
|
|
162
180
|
this.curveSides = resolvers.curveSides ?? null;
|
|
181
|
+
this.curveTolerance = resolvers.curveTolerance ?? DEFAULT_CURVE_TOLERANCE;
|
|
163
182
|
this.resolvePLY = resolvers.resolvePLY || ( async () => null );
|
|
164
183
|
this.resolveImage = resolvers.resolveImage || ( async () => null );
|
|
165
184
|
this.resolveEnvironment = resolvers.resolveEnvironment || resolvers.resolveImage || ( async () => null );
|
|
@@ -189,6 +208,9 @@ export class PBRTSceneBuilder {
|
|
|
189
208
|
async build( ir ) {
|
|
190
209
|
|
|
191
210
|
this.ir = ir;
|
|
211
|
+
// Built again for their moving placements after the static ones, so never freed early.
|
|
212
|
+
this._keepShapes = new Set();
|
|
213
|
+
for ( const { name } of ir.animatedInstances ?? [] ) for ( const shape of ir.objects.get( name ) ?? [] ) this._keepShapes.add( shape );
|
|
192
214
|
this._recoveredColors = 0;
|
|
193
215
|
this.reportedMeshes = 0;
|
|
194
216
|
this.triangleCount = 0; // stored triangles — a shared geometry counts once
|
|
@@ -333,9 +355,53 @@ export class PBRTSceneBuilder {
|
|
|
333
355
|
const scratch = new Float64Array( 16 );
|
|
334
356
|
const matrix = new Matrix4();
|
|
335
357
|
|
|
336
|
-
|
|
358
|
+
const place = ( list, geometry, material, rel, shapeType, materialType, tris ) => {
|
|
359
|
+
|
|
360
|
+
const affordable = Math.max( 0, this.maxPlacements - this.placementCount );
|
|
361
|
+
const count = Math.min( list.count, affordable );
|
|
362
|
+
if ( count < list.count ) this.skippedForBudget += list.count - count;
|
|
363
|
+
if ( count === 0 ) return;
|
|
364
|
+
|
|
365
|
+
const mesh = new InstancedMesh( geometry, material, count );
|
|
366
|
+
mesh.name = `instance_${n ++}`;
|
|
367
|
+
mesh.frustumCulled = false;
|
|
368
|
+
|
|
369
|
+
for ( let i = 0; i < count; i ++ ) {
|
|
370
|
+
|
|
371
|
+
if ( rel ) M.multiplyInto( scratch, list.matrices, i * 16, rel );
|
|
372
|
+
else for ( let e = 0; e < 16; e ++ ) scratch[ e ] = list.matrices[ i * 16 + e ];
|
|
373
|
+
mesh.setMatrixAt( i, matrix.fromArray( this.convertHandedness ? M.multiply( FLIP_Z, scratch ) : scratch ) );
|
|
374
|
+
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
mesh.instanceMatrix.needsUpdate = true;
|
|
378
|
+
group.add( mesh );
|
|
379
|
+
|
|
380
|
+
this.placementCount += count;
|
|
381
|
+
this.reportedMeshes += count;
|
|
382
|
+
if ( this.report.length < MAX_REPORT_ROWS ) this.report.push( {
|
|
383
|
+
mesh: `${mesh.name} ×${count}`,
|
|
384
|
+
shape: shapeType,
|
|
385
|
+
material: materialType,
|
|
386
|
+
color: '#' + material.color.getHexString(),
|
|
387
|
+
map: material.map ? 'yes' : '-',
|
|
388
|
+
uv: geometry.getAttribute( 'uv' ) ? 'yes' : 'NO',
|
|
389
|
+
normals: geometry.getAttribute( 'normal' ) ? 'yes' : 'NO',
|
|
390
|
+
emissive: material.emissiveIntensity > 0 ? `#${material.emissive.getHexString()}×${material.emissiveIntensity}` : '-',
|
|
391
|
+
size: `instanced`,
|
|
392
|
+
tris,
|
|
393
|
+
} );
|
|
394
|
+
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
const placeBatch = ( list, batch ) => {
|
|
398
|
+
|
|
399
|
+
if ( batch && batch.triangles > 0 ) place( list, this._batchGeometry( batch ), batch.material, null, 'merged', '-', batch.triangles );
|
|
400
|
+
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
for ( const { name, list, template } of this._samePlacementGroups( ir ) ) {
|
|
337
404
|
|
|
338
|
-
const template = ir.objects.get( name );
|
|
339
405
|
if ( ! template ) {
|
|
340
406
|
|
|
341
407
|
// Counted, not just warned: these are placements that silently leave the scene,
|
|
@@ -346,6 +412,13 @@ export class PBRTSceneBuilder {
|
|
|
346
412
|
|
|
347
413
|
}
|
|
348
414
|
|
|
415
|
+
// A template's own small shapes share its placements, so they merge in its space: the
|
|
416
|
+
// eight Pandanus trees in Moana hold 22,965 leaves each. A .ply may be shared with
|
|
417
|
+
// other templates and stays whole.
|
|
418
|
+
let inline = 0;
|
|
419
|
+
for ( const shape of template ) if ( shape.type !== 'plymesh' ) inline ++;
|
|
420
|
+
const batches = inline >= 2 ? new Map() : null;
|
|
421
|
+
|
|
349
422
|
for ( const shape of template ) {
|
|
350
423
|
|
|
351
424
|
if ( this._overBudget() ) {
|
|
@@ -361,47 +434,80 @@ export class PBRTSceneBuilder {
|
|
|
361
434
|
] );
|
|
362
435
|
if ( ! geometry ) continue;
|
|
363
436
|
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
437
|
+
if ( batches && shape.type !== 'plymesh' && geometry.getAttribute( 'position' ).count <= MERGE_VERTEX_LIMIT ) {
|
|
438
|
+
|
|
439
|
+
this._geometryCache.delete( shape );
|
|
440
|
+
const full = this._appendToBatch( batches, shape, geometry, sharedMaterial, shape.relativeCTM || shape.ctm );
|
|
441
|
+
this._releaseMerged( shape, geometry );
|
|
442
|
+
placeBatch( list, full );
|
|
443
|
+
continue;
|
|
369
444
|
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
const tris = this._accountGeometry( geometry );
|
|
370
448
|
const material = this._materialForGeometry( shape, geometry, sharedMaterial, `instances_${name}` );
|
|
371
|
-
|
|
372
|
-
mesh.name = `instance_${n ++}`;
|
|
373
|
-
mesh.frustumCulled = false;
|
|
449
|
+
place( list, geometry, material, shape.relativeCTM || shape.ctm, shape.type, shape.material?.type || 'diffuse', tris );
|
|
374
450
|
|
|
375
|
-
|
|
376
|
-
for ( let i = 0; i < count; i ++ ) {
|
|
451
|
+
}
|
|
377
452
|
|
|
378
|
-
|
|
379
|
-
mesh.setMatrixAt( i, matrix.fromArray( this.convertHandedness ? M.multiply( FLIP_Z, scratch ) : scratch ) );
|
|
453
|
+
if ( batches ) for ( const slot of batches.values() ) {
|
|
380
454
|
|
|
381
|
-
|
|
455
|
+
placeBatch( list, slot[ 0 ] );
|
|
456
|
+
placeBatch( list, slot[ 1 ] );
|
|
457
|
+
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// Copied into each InstancedMesh: 576 MB at 9M placements, held to the end otherwise.
|
|
461
|
+
freeNow( list.matrices );
|
|
462
|
+
freeNow( list.matricesEnd );
|
|
463
|
+
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Templates placed at exactly the same transforms, as one template. Each Pandanus tree
|
|
470
|
+
* includes ten leaf files; kept apart they were ten overlapping instances a ray entered
|
|
471
|
+
* one after another, and the tree rendered 60 % slower than with its leaves world-baked.
|
|
472
|
+
* @private
|
|
473
|
+
*/
|
|
474
|
+
_samePlacementGroups( ir ) {
|
|
382
475
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
476
|
+
const groups = [];
|
|
477
|
+
const buckets = new Map();
|
|
478
|
+
|
|
479
|
+
for ( const [ name, list ] of ir.instances ) {
|
|
480
|
+
|
|
481
|
+
const template = ir.objects.get( name );
|
|
482
|
+
if ( ! template || list.matricesEnd ) {
|
|
483
|
+
|
|
484
|
+
groups.push( { name, list, template } );
|
|
485
|
+
continue;
|
|
486
|
+
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const key = `${list.count}:${Array.prototype.join.call( list.matrices.subarray( 0, 16 ), ',' )}`;
|
|
490
|
+
let bucket = buckets.get( key );
|
|
491
|
+
if ( ! bucket ) buckets.set( key, bucket = [] );
|
|
492
|
+
|
|
493
|
+
const same = bucket.find( g => samePlacements( g.list, list ) );
|
|
494
|
+
if ( same ) {
|
|
495
|
+
|
|
496
|
+
if ( same.template === ir.objects.get( same.name ) ) same.template = same.template.slice();
|
|
497
|
+
for ( const shape of template ) same.template.push( shape );
|
|
498
|
+
freeNow( list.matrices );
|
|
499
|
+
continue;
|
|
400
500
|
|
|
401
501
|
}
|
|
402
502
|
|
|
503
|
+
const group = { name, list, template };
|
|
504
|
+
bucket.push( group );
|
|
505
|
+
groups.push( group );
|
|
506
|
+
|
|
403
507
|
}
|
|
404
508
|
|
|
509
|
+
return groups;
|
|
510
|
+
|
|
405
511
|
}
|
|
406
512
|
|
|
407
513
|
/** Moving placements, one Group each: an InstancedMesh cannot move a single instance. */
|
|
@@ -637,6 +743,35 @@ export class PBRTSceneBuilder {
|
|
|
637
743
|
*/
|
|
638
744
|
_mergeShape( shape, geometry, sharedMaterial, group ) {
|
|
639
745
|
|
|
746
|
+
this.reportedMeshes ++;
|
|
747
|
+
const world = this.convertHandedness ? M.multiply( FLIP_Z, shape.ctm ) : shape.ctm;
|
|
748
|
+
const full = this._appendToBatch( this._batches, shape, geometry, sharedMaterial, world );
|
|
749
|
+
this._releaseMerged( shape, geometry );
|
|
750
|
+
if ( full ) this._flushBatch( full, group );
|
|
751
|
+
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
/**
|
|
755
|
+
* A merged shape's arrays are in its batch now and nothing reads it again: freed at once, not at
|
|
756
|
+
* a GC the build reaches with every one still held — isIronwoodA1's leaves were 3.9 GB twice over.
|
|
757
|
+
* @private
|
|
758
|
+
*/
|
|
759
|
+
_releaseMerged( shape, geometry ) {
|
|
760
|
+
|
|
761
|
+
if ( this._keepShapes?.has( shape ) ) return;
|
|
762
|
+
for ( const name in geometry.attributes ) freeNow( geometry.attributes[ name ].array );
|
|
763
|
+
freeNow( geometry.index?.array );
|
|
764
|
+
for ( const name in shape.params ) if ( ArrayBuffer.isView( shape.params[ name ]?.value ) ) freeNow( shape.params[ name ].value );
|
|
765
|
+
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
/**
|
|
769
|
+
* Copy one shape's triangles, moved by `transform`, into the batch for its material.
|
|
770
|
+
* @returns {object|null} the batch, taken out of `batches`, once it is full
|
|
771
|
+
* @private
|
|
772
|
+
*/
|
|
773
|
+
_appendToBatch( batches, shape, geometry, sharedMaterial, transform ) {
|
|
774
|
+
|
|
640
775
|
const material = this._materialForGeometry( shape, geometry, sharedMaterial, 'merged shape' );
|
|
641
776
|
const position = geometry.getAttribute( 'position' );
|
|
642
777
|
const normal = geometry.getAttribute( 'normal' );
|
|
@@ -646,11 +781,10 @@ export class PBRTSceneBuilder {
|
|
|
646
781
|
|
|
647
782
|
// Charged directly: a merged shape stores its own copy even when two share one .ply.
|
|
648
783
|
this.triangleCount += ( index ? index.count : vertices ) / 3;
|
|
649
|
-
this.reportedMeshes ++;
|
|
650
784
|
this.mergedShapes ++;
|
|
651
785
|
|
|
652
|
-
let slot =
|
|
653
|
-
if ( ! slot )
|
|
786
|
+
let slot = batches.get( material );
|
|
787
|
+
if ( ! slot ) batches.set( material, slot = [ null, null ] );
|
|
654
788
|
const k = uv ? 1 : 0;
|
|
655
789
|
let batch = slot[ k ];
|
|
656
790
|
if ( ! batch ) slot[ k ] = batch = {
|
|
@@ -662,7 +796,7 @@ export class PBRTSceneBuilder {
|
|
|
662
796
|
vertexCount: 0, indexCount: 0, triangles: 0
|
|
663
797
|
};
|
|
664
798
|
|
|
665
|
-
const world =
|
|
799
|
+
const world = transform;
|
|
666
800
|
const inv = M.invert( world );
|
|
667
801
|
const base = batch.vertexCount;
|
|
668
802
|
|
|
@@ -711,11 +845,26 @@ export class PBRTSceneBuilder {
|
|
|
711
845
|
|
|
712
846
|
if ( batch.triangles >= MERGE_BATCH_TRIANGLES ) {
|
|
713
847
|
|
|
714
|
-
this._flushBatch( batch, group );
|
|
715
848
|
slot[ k ] = null;
|
|
849
|
+
return batch;
|
|
716
850
|
|
|
717
851
|
}
|
|
718
852
|
|
|
853
|
+
return null;
|
|
854
|
+
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
/** A batch's triangles as one geometry; the batch's own arrays are released. @private */
|
|
858
|
+
_batchGeometry( batch ) {
|
|
859
|
+
|
|
860
|
+
const geometry = new BufferGeometry();
|
|
861
|
+
geometry.setAttribute( 'position', new Float32BufferAttribute( resized( batch.positions, batch.vertexCount * 3 ), 3 ) );
|
|
862
|
+
geometry.setAttribute( 'normal', new Float32BufferAttribute( resized( batch.normals, batch.vertexCount * 3 ), 3 ) );
|
|
863
|
+
if ( batch.uvs ) geometry.setAttribute( 'uv', new Float32BufferAttribute( resized( batch.uvs, batch.vertexCount * 2 ), 2 ) );
|
|
864
|
+
geometry.setIndex( new Uint32BufferAttribute( resized( batch.indices, batch.indexCount ), 1 ) );
|
|
865
|
+
batch.positions = batch.normals = batch.uvs = batch.indices = null;
|
|
866
|
+
return geometry;
|
|
867
|
+
|
|
719
868
|
}
|
|
720
869
|
|
|
721
870
|
/** Turn one accumulated batch into a single Mesh at the scene origin. @private */
|
|
@@ -723,12 +872,7 @@ export class PBRTSceneBuilder {
|
|
|
723
872
|
|
|
724
873
|
if ( ! batch || batch.triangles === 0 ) return;
|
|
725
874
|
|
|
726
|
-
const geometry =
|
|
727
|
-
geometry.setAttribute( 'position', new Float32BufferAttribute( batch.positions.slice( 0, batch.vertexCount * 3 ), 3 ) );
|
|
728
|
-
geometry.setAttribute( 'normal', new Float32BufferAttribute( batch.normals.slice( 0, batch.vertexCount * 3 ), 3 ) );
|
|
729
|
-
if ( batch.uvs ) geometry.setAttribute( 'uv', new Float32BufferAttribute( batch.uvs.slice( 0, batch.vertexCount * 2 ), 2 ) );
|
|
730
|
-
geometry.setIndex( new Uint32BufferAttribute( batch.indices.slice( 0, batch.indexCount ), 1 ) );
|
|
731
|
-
|
|
875
|
+
const geometry = this._batchGeometry( batch );
|
|
732
876
|
const mesh = new Mesh( geometry, batch.material );
|
|
733
877
|
mesh.name = `merged_${this._mergedBatches ++}`;
|
|
734
878
|
mesh.frustumCulled = false;
|
|
@@ -748,8 +892,6 @@ export class PBRTSceneBuilder {
|
|
|
748
892
|
tris: batch.triangles
|
|
749
893
|
} );
|
|
750
894
|
|
|
751
|
-
batch.positions = batch.normals = batch.uvs = batch.indices = null;
|
|
752
|
-
|
|
753
895
|
}
|
|
754
896
|
|
|
755
897
|
_buildGeometry( shape ) {
|
|
@@ -833,7 +975,8 @@ export class PBRTSceneBuilder {
|
|
|
833
975
|
width1: pFloat( params, 'width1', width ),
|
|
834
976
|
N: params.N?.value || null,
|
|
835
977
|
steps: this.curveSteps,
|
|
836
|
-
sides: this.curveSides ?? DEFAULT_CURVE_SIDES[ type ] ?? 1
|
|
978
|
+
sides: this.curveSides ?? DEFAULT_CURVE_SIDES[ type ] ?? 1,
|
|
979
|
+
tolerance: this.curveTolerance
|
|
837
980
|
} );
|
|
838
981
|
|
|
839
982
|
if ( ! built ) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A dropped ArrayBuffer is freed at the next major GC, which a parse allocating gigabytes of
|
|
3
|
+
* scene text and growing arrays reaches late: the 80M-triangle Moana parse held ~4 GB of such
|
|
4
|
+
* garbage. Transferring a buffer away frees it at once.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const whole = view => view.byteOffset === 0 && view.byteLength === view.buffer.byteLength
|
|
8
|
+
&& typeof view.buffer.transfer === 'function'
|
|
9
|
+
&& ! ( typeof SharedArrayBuffer !== 'undefined' && view.buffer instanceof SharedArrayBuffer );
|
|
10
|
+
|
|
11
|
+
/** Frees a view's memory now. Only for a view that alone holds its whole buffer. */
|
|
12
|
+
export function freeNow( view ) {
|
|
13
|
+
|
|
14
|
+
if ( view && whole( view ) ) view.buffer.transfer( 0 );
|
|
15
|
+
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** `view` at `length` elements, the old memory let go at once; zero-filled when it grows. */
|
|
19
|
+
export function resized( view, length ) {
|
|
20
|
+
|
|
21
|
+
const Type = view.constructor;
|
|
22
|
+
if ( whole( view ) ) return new Type( view.buffer.transfer( length * Type.BYTES_PER_ELEMENT ) );
|
|
23
|
+
const out = new Type( length );
|
|
24
|
+
out.set( length < view.length ? view.subarray( 0, length ) : view );
|
|
25
|
+
return out;
|
|
26
|
+
|
|
27
|
+
}
|
|
@@ -14,12 +14,13 @@
|
|
|
14
14
|
* await loadObject3D(group);
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
+
import { freeNow } from './buffers.js';
|
|
17
18
|
import { PBRTParser } from './PBRTParser.js';
|
|
18
19
|
import { PBRTSceneBuilder } from './PBRTSceneBuilder.js';
|
|
19
20
|
import { findFrameSequence, FrameSequenceMerger, motionFromShutter, SEQUENCE_FPS } from './PBRTAnimation.js';
|
|
20
21
|
|
|
21
22
|
export { PBRTParser } from './PBRTParser.js';
|
|
22
|
-
export { PBRTSceneBuilder } from './PBRTSceneBuilder.js';
|
|
23
|
+
export { PBRTSceneBuilder, PBRT_BUILD_REVISION, SPILL_TRIANGLE_BUDGET, SPILL_PLACEMENT_BUDGET } from './PBRTSceneBuilder.js';
|
|
23
24
|
export { tokenize } from './PBRTTokenizer.js';
|
|
24
25
|
export { findFrameSequence, SEQUENCE_FPS } from './PBRTAnimation.js';
|
|
25
26
|
|
|
@@ -105,6 +106,16 @@ export class VirtualFS {
|
|
|
105
106
|
|
|
106
107
|
}
|
|
107
108
|
|
|
109
|
+
/** The first `bytes` of a record, reading no more of a lazy one than that when the source can. */
|
|
110
|
+
async readHead( rec, bytes ) {
|
|
111
|
+
|
|
112
|
+
if ( ! rec ) return null;
|
|
113
|
+
if ( rec.bytes ) return rec.bytes.subarray( 0, bytes );
|
|
114
|
+
if ( ! rec.lazy || ! this.source ) return null;
|
|
115
|
+
return this.source.readHead ? this.source.readHead( rec.key, bytes ) : this.source.read( rec.key );
|
|
116
|
+
|
|
117
|
+
}
|
|
118
|
+
|
|
108
119
|
/** Resolve and read in one step; `dir` is joined first, as Include does. */
|
|
109
120
|
async readPath( path, dir = null ) {
|
|
110
121
|
|
|
@@ -120,12 +131,15 @@ export class VirtualFS {
|
|
|
120
131
|
|
|
121
132
|
}
|
|
122
133
|
|
|
123
|
-
|
|
134
|
+
/** `bytes`, when given, are what a read of it returned, freed now rather than at the next GC. */
|
|
135
|
+
releasePath( path, dir = null, bytes = null ) {
|
|
124
136
|
|
|
125
137
|
const rec = ( dir !== null && this.findRecord( joinPath( dir, path ) ) ) || this.findRecord( path );
|
|
126
138
|
// Only a lazy record, which can be read again. Releasing a resident one would drop a
|
|
127
139
|
// fragment that a later Include still needs — isHibiscusYoung pulls one in six times.
|
|
128
|
-
if ( rec?.lazy )
|
|
140
|
+
if ( ! rec?.lazy ) return;
|
|
141
|
+
this.release( rec );
|
|
142
|
+
freeNow( bytes );
|
|
129
143
|
|
|
130
144
|
}
|
|
131
145
|
|
|
@@ -180,6 +194,7 @@ function findBytes( bytes, needle, from = 0 ) {
|
|
|
180
194
|
}
|
|
181
195
|
|
|
182
196
|
const ASCII = s => Uint8Array.from( s, c => c.charCodeAt( 0 ) );
|
|
197
|
+
const ENTRY_HEAD_BYTES = 4 * 1024 * 1024;
|
|
183
198
|
const WORLD_BEGIN = ASCII( 'WorldBegin' );
|
|
184
199
|
const INCLUDE_WORDS = [ ASCII( 'Include' ), ASCII( 'Import' ) ];
|
|
185
200
|
|
|
@@ -311,6 +326,11 @@ export async function pickEntryPathFrom( vfs ) {
|
|
|
311
326
|
/**
|
|
312
327
|
* Candidate scene files for a VirtualFS, best first. Each candidate is read to test it and
|
|
313
328
|
* dropped again, so a lazily-indexed archive is never materialised just to rank its entries.
|
|
329
|
+
*
|
|
330
|
+
* WorldBegin may only follow the scene-wide options, so a scene declares it within its first
|
|
331
|
+
* megabytes: the heads decide, and when they name one scene no file is read whole — the full
|
|
332
|
+
* reads were 15 GB and 40 s for a 17-part Moana archive before its parse began. Heads that name
|
|
333
|
+
* none, or several, fall back to reading everything, as the ranking of several needs.
|
|
314
334
|
*/
|
|
315
335
|
export async function listEntryPathsFrom( vfs ) {
|
|
316
336
|
|
|
@@ -318,18 +338,34 @@ export async function listEntryPathsFrom( vfs ) {
|
|
|
318
338
|
if ( pbrts.length === 0 ) return [];
|
|
319
339
|
if ( pbrts.length === 1 ) return [ pbrts[ 0 ].key ];
|
|
320
340
|
|
|
321
|
-
const
|
|
322
|
-
const worlds = new Map();
|
|
323
|
-
for ( const rec of pbrts ) {
|
|
341
|
+
const scan = async ( read ) => {
|
|
324
342
|
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
if ( rec.lazy ) vfs.release( rec ); // read again later if it is actually used
|
|
343
|
+
const included = new Set();
|
|
344
|
+
const worlds = new Map();
|
|
345
|
+
let cut = false;
|
|
346
|
+
for ( const rec of pbrts ) {
|
|
330
347
|
|
|
331
|
-
|
|
348
|
+
const bytes = await read( rec );
|
|
349
|
+
if ( ! bytes ) continue;
|
|
350
|
+
worlds.set( rec.key, hasWorldBegin( bytes ) );
|
|
351
|
+
includedBasenames( bytes, included );
|
|
352
|
+
if ( bytes.length >= ENTRY_HEAD_BYTES ) cut = true;
|
|
353
|
+
if ( rec.lazy ) {
|
|
354
|
+
|
|
355
|
+
vfs.release( rec ); // read again later if it is actually used
|
|
356
|
+
freeNow( bytes );
|
|
357
|
+
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return { included, worlds, cut };
|
|
363
|
+
|
|
364
|
+
};
|
|
332
365
|
|
|
366
|
+
const heads = await scan( rec => vfs.readHead( rec, ENTRY_HEAD_BYTES ) );
|
|
367
|
+
const scenes = [ ...heads.worlds.values() ].filter( Boolean ).length;
|
|
368
|
+
const { included, worlds } = scenes === 1 || ! heads.cut ? heads : await scan( rec => vfs.read( rec ) );
|
|
333
369
|
return rankEntryPaths( pbrts.map( r => r.key ), worlds, included );
|
|
334
370
|
|
|
335
371
|
}
|
|
@@ -377,11 +413,14 @@ export async function loadPBRTScene( args ) {
|
|
|
377
413
|
resolveInclude: ( include, currentDir ) => vfs.readPath( include, currentDir ),
|
|
378
414
|
// Depth-first, so this keeps only the open include chain live rather than every
|
|
379
415
|
// scene file at once — the difference between 5.7 GB resident and a few MB.
|
|
380
|
-
releaseInclude: ( include, currentDir ) => vfs.releasePath( include, currentDir ),
|
|
381
|
-
maxPlacements: args.maxPlacements
|
|
416
|
+
releaseInclude: ( include, currentDir, bytes ) => vfs.releasePath( include, currentDir, bytes ),
|
|
417
|
+
maxPlacements: args.maxPlacements,
|
|
418
|
+
instanceIncludes: args.instanceIncludes
|
|
382
419
|
} );
|
|
383
420
|
|
|
384
|
-
|
|
421
|
+
const ir = await parser.parse( bytes, path.includes( '/' ) ? path.slice( 0, path.lastIndexOf( '/' ) ) : '' );
|
|
422
|
+
vfs.releasePath( path, null, bytes );
|
|
423
|
+
return ir;
|
|
385
424
|
|
|
386
425
|
};
|
|
387
426
|
|
|
@@ -450,6 +489,7 @@ export async function loadPBRTScene( args ) {
|
|
|
450
489
|
mergeShapesAbove: args.mergeShapesAbove,
|
|
451
490
|
curveSteps: args.curveSteps,
|
|
452
491
|
curveSides: args.curveSides,
|
|
492
|
+
curveTolerance: args.curveTolerance,
|
|
453
493
|
resolvePLY: async ( filename ) => {
|
|
454
494
|
|
|
455
495
|
const rec = vfs.findRecord( filename );
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Packed buffer manager for wavefront path tracing — one storage buffer per data category.
|
|
3
3
|
* RAY/HIT are SoA-within-a-buffer (field `slot` of element `id` lives at `id + slot*_cap`).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* Both are GPU-only: every lane is written by a kernel before anything reads it and none
|
|
6
6
|
* is ever read back, so they carry no CPU array (see gpuOnlyStorageAttribute).
|
|
7
7
|
*/
|
|
8
8
|
|
|
@@ -17,7 +17,7 @@ import { createLogger, fmt } from '../utils/Logger.js';
|
|
|
17
17
|
const log = createLogger( 'gpu' );
|
|
18
18
|
|
|
19
19
|
export const RAY_STRIDE = 7;
|
|
20
|
-
export const HIT_STRIDE =
|
|
20
|
+
export const HIT_STRIDE = 3;
|
|
21
21
|
// Per-pixel G-buffer (first-hit MRT staging): 1 uvec4/pixel — half-packed normal/depth/albedo
|
|
22
22
|
// (pack2x16, no f32 bitcast); read by FinalWrite:
|
|
23
23
|
// .x=packSnorm2x16(normal.xy) .y=packSnorm2x16(normal.z, depth) .z=packUnorm2x16(albedo.rg)
|
|
@@ -42,6 +42,9 @@ export const HIT = {
|
|
|
42
42
|
// The geometric normal rides as one oct16 word (it was interpolated from oct16 triangle
|
|
43
43
|
// normals to begin with), which leaves the instance leaf a full 32-bit lane of its own.
|
|
44
44
|
NORMAL_MAT: 1, // uvec4(octNormal, matIndex, instanceLeaf + 1, 0)
|
|
45
|
+
// The path's RNG state, here rather than in a buffer of its own: Shade was at the device's
|
|
46
|
+
// 10 storage buffers, and the second triangle buffer needed the slot. Extend never writes it.
|
|
47
|
+
RNG: 2, // uvec4(rngState, 0, 0, 0)
|
|
45
48
|
};
|
|
46
49
|
|
|
47
50
|
// SoA region stride, baked into the shader graph at build time; single instance, rebuilt on resize.
|
|
@@ -73,7 +76,6 @@ export class PackedRayBuffer {
|
|
|
73
76
|
|
|
74
77
|
// Each: { rw: StorageBufferNode, ro: StorageBufferNode } over one shared GPU buffer.
|
|
75
78
|
this.rayBuffer = null;
|
|
76
|
-
this.rngBuffer = null;
|
|
77
79
|
this.hitBuffer = null;
|
|
78
80
|
|
|
79
81
|
if ( capacity > 0 ) this.allocate( capacity );
|
|
@@ -96,13 +98,6 @@ export class PackedRayBuffer {
|
|
|
96
98
|
ro: storage( rayAttr, 'vec4' ).toReadOnly(),
|
|
97
99
|
};
|
|
98
100
|
|
|
99
|
-
const rngAttr = gpuOnlyStorageAttribute( capacity, 1, Uint32Array );
|
|
100
|
-
this._attrs.rng = rngAttr;
|
|
101
|
-
this.rngBuffer = {
|
|
102
|
-
rw: storage( rngAttr, 'uint' ),
|
|
103
|
-
ro: storage( rngAttr, 'uint' ).toReadOnly(),
|
|
104
|
-
};
|
|
105
|
-
|
|
106
101
|
const hitCount = capacity * HIT_STRIDE;
|
|
107
102
|
const hitAttr = gpuOnlyStorageAttribute( hitCount, 4, Uint32Array );
|
|
108
103
|
this._attrs.hit = hitAttr;
|
|
@@ -112,7 +107,7 @@ export class PackedRayBuffer {
|
|
|
112
107
|
};
|
|
113
108
|
|
|
114
109
|
// Kept for the [gpu] startup summary in PathTracer._buildWavefrontKernels.
|
|
115
|
-
this.rayBytes = rayCount * 16
|
|
110
|
+
this.rayBytes = rayCount * 16;
|
|
116
111
|
this.hitBytes = hitCount * 16;
|
|
117
112
|
this.totalBytes = this.rayBytes + this.hitBytes;
|
|
118
113
|
|
|
@@ -132,11 +127,9 @@ export class PackedRayBuffer {
|
|
|
132
127
|
dispose() {
|
|
133
128
|
|
|
134
129
|
freeStorageAttribute( this._renderer, this._attrs.ray );
|
|
135
|
-
freeStorageAttribute( this._renderer, this._attrs.rng );
|
|
136
130
|
freeStorageAttribute( this._renderer, this._attrs.hit );
|
|
137
131
|
this._attrs = {};
|
|
138
132
|
this.rayBuffer = null;
|
|
139
|
-
this.rngBuffer = null;
|
|
140
133
|
this.hitBuffer = null;
|
|
141
134
|
this.capacity = 0;
|
|
142
135
|
|
|
@@ -285,6 +278,10 @@ export const readHitInstanceLeaf = ( buf, id ) =>
|
|
|
285
278
|
export const readHitFacet = ( buf, id ) =>
|
|
286
279
|
buf.element( soa( id, HIT.NORMAL_MAT ) ).w;
|
|
287
280
|
|
|
281
|
+
export const readRngState = ( buf, id ) => buf.element( soa( id, HIT.RNG ) ).x;
|
|
282
|
+
|
|
283
|
+
export const writeRngState = ( buf, id, state ) => buf.element( soa( id, HIT.RNG ) ).assign( uvec4( state, 0, 0, 0 ) );
|
|
284
|
+
|
|
288
285
|
export const writeHitPacked = ( buf, id, distance, triIndex, baryU, baryV, normal, matIndex, instanceLeaf, facet = uint( 0 ) ) => {
|
|
289
286
|
|
|
290
287
|
buf.element( soa( id, HIT.DIST_TRI_BARY ) )
|