rayzee 11.2.1 → 11.4.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 +91 -34
- package/dist/ArchiveImporter-Cgyx5AF7.js +7860 -0
- package/dist/BidirectionalIntegrator-BKF-cwH2.js +954 -0
- package/dist/{BufferLayout-D_IBo2ci.js → BufferLayout-xPRH9jYD.js} +41 -5
- package/dist/{NeuralRetouch-Cr-otSW8.js → NeuralRetouch-C3Q2XqbF.js} +4 -3
- package/dist/{NeuralSuperRes-B7E3_M0t.js → NeuralSuperRes-DV5TtURP.js} +11 -5
- package/dist/{PathTracer-D6nbknj5.js → PathTracer-D3MvEaZK.js} +4384 -4618
- package/dist/{PhysicalSky-Dq5VSiCQ.js → PhysicalSky-CY4jiz6a.js} +2 -2
- package/dist/{RayzeeRenderer-DbjqO6v-.js → RayzeeRenderer-D23m6NqG.js} +486 -391
- package/dist/SolarLimb-bZE6y63S.js +762 -0
- package/dist/{ToneMapGPU-BOjpul7A.js → ToneMapGPU-CTDBvmmS.js} +31 -22
- 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 +1327 -838
- package/dist/rayzee.umd.js +146 -134
- package/dist/sceneCachePolicy-AKOEu2lf.js +1602 -0
- package/package.json +1 -1
- package/src/EngineDefaults.js +2 -2
- package/src/Passes/AIUpscaler.js +2 -0
- package/src/PathTracerApp.js +126 -19
- package/src/Pipeline/CompletionTracker.js +7 -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/ToneMapCPU.js +16 -4
- package/src/Processor/ToneMapGPU.js +30 -14
- 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 +221 -33
- package/src/RenderSettings.js +4 -2
- package/src/Stages/AutoExposure.js +484 -494
- package/src/Stages/Compositor.js +27 -6
- package/src/Stages/LocalExposure.js +546 -0
- package/src/Stages/NormalDepth.js +7 -12
- package/src/Stages/PathTracer.js +18 -21
- package/src/Stages/PathTracerStage.js +282 -103
- 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/index.js +1 -0
- package/src/integrators/BidirectionalIntegrator.js +20 -2
- package/src/managers/DenoisingManager.js +15 -19
- package/src/managers/InteractionManager.js +19 -6
- package/src/managers/MaterialDataManager.js +22 -5
- package/src/managers/UniformManager.js +3 -0
- package/src/managers/VideoRenderManager.js +30 -4
- package/src/neural/NeuralRetouch.js +1 -0
- package/src/neural/NeuralSuperRes.js +6 -1
- 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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { storage } from 'three/tsl';
|
|
2
|
-
import {
|
|
2
|
+
import { StorageParts } from '../TSL/patches.js';
|
|
3
3
|
import { StorageInstancedBufferAttribute } from 'three/webgpu';
|
|
4
4
|
import { Vector2, Matrix4 } from 'three';
|
|
5
5
|
|
|
@@ -21,8 +21,8 @@ import { LightSerializer } from '../Processor/LightSerializer';
|
|
|
21
21
|
|
|
22
22
|
// Constants
|
|
23
23
|
import { ENGINE_DEFAULTS as DEFAULT_STATE } from '../EngineDefaults.js';
|
|
24
|
-
import { TRIANGLE_DATA_LAYOUT } from '../Processor/BufferLayout.js';
|
|
25
|
-
import { TRI_GEO_ROWS, TRI_SHADE_ROWS } from '../TSL/Common.js';
|
|
24
|
+
import { TRIANGLE_DATA_LAYOUT, CLUSTER_COPY_HIDDEN, bvhIndexView } from '../Processor/BufferLayout.js';
|
|
25
|
+
import { TRI_GEO_ROWS, TRI_SHADE_ROWS, splitStorage } from '../TSL/Common.js';
|
|
26
26
|
import { createLogger, fmt } from '../utils/Logger.js';
|
|
27
27
|
|
|
28
28
|
// Triangles converted per staging pass: 256K is 12 + 8 MB of staging, kept for the stage's life.
|
|
@@ -30,6 +30,25 @@ const TRIANGLE_UPLOAD_SLICE = 1 << 18;
|
|
|
30
30
|
|
|
31
31
|
const log = createLogger( 'pathtracer' );
|
|
32
32
|
|
|
33
|
+
|
|
34
|
+
// Storage buffers the shading kernel binds besides the BVH and triangle parts: rays, hits (with the RNG state and the
|
|
35
|
+
// G-buffer), counters, active indices, and the scene data (materials, then lights). Measured on the compiled WGSL.
|
|
36
|
+
const SHADE_OTHER_BINDINGS = 5;
|
|
37
|
+
|
|
38
|
+
/** Read-only storage nodes over `parts`, one node when it is a single buffer. */
|
|
39
|
+
function readParts( parts, type ) {
|
|
40
|
+
|
|
41
|
+
return splitStorage( parts.attrs.map( ( attr ) => storage( attr, type, attr.count ).toReadOnly() ), parts.elementsPerPart, type );
|
|
42
|
+
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The `{ geo, shade }` pair kernels read triangles through (TSL/Common.js `triangleRow`). */
|
|
46
|
+
function triangleNodes( geo, shade ) {
|
|
47
|
+
|
|
48
|
+
return { geo: readParts( geo, 'uvec4' ), shade: readParts( shade, shade.withoutUV ? 'uvec2' : 'uvec4' ), withoutUV: shade.withoutUV === true };
|
|
49
|
+
|
|
50
|
+
}
|
|
51
|
+
|
|
33
52
|
// How long after the last move the view still counts as moving. Matches CameraOptimizer's own
|
|
34
53
|
// settle delay, so the answer does not change when interaction mode is switched off.
|
|
35
54
|
const VIEW_SETTLE_MS = 100;
|
|
@@ -96,6 +115,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
96
115
|
this.performanceMonitor = createPerformanceMonitor();
|
|
97
116
|
this.completionThreshold = 0;
|
|
98
117
|
this.renderLimitMode = 'frames';
|
|
118
|
+
this.renderTimeLimit = 0;
|
|
99
119
|
|
|
100
120
|
// Initialize data textures
|
|
101
121
|
this._initDataTextures();
|
|
@@ -113,6 +133,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
113
133
|
// Initialize material data manager
|
|
114
134
|
this.materialData = new MaterialDataManager( this.sdfs );
|
|
115
135
|
this.materialData.callbacks.onReset = () => this.reset();
|
|
136
|
+
this.materialData.callbacks.adoptMaterials = ( data ) => this._adoptMaterials( data );
|
|
116
137
|
// A material edit can switch the diffuse transmission lobe on or off, which the shade kernel compiles in.
|
|
117
138
|
this.materialData.callbacks.onMaterialFeaturesChanged = () => {
|
|
118
139
|
|
|
@@ -625,7 +646,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
625
646
|
* @param {Uint32Array|import('../Processor/ChunkedRecords.js').ChunkedRecords} triangleData
|
|
626
647
|
* @param {number} triangleCount - Number of triangles
|
|
627
648
|
*/
|
|
628
|
-
setTriangleData( triangleData, triangleCount ) {
|
|
649
|
+
setTriangleData( triangleData, triangleCount, { textureCoordinates = true } = {} ) {
|
|
629
650
|
|
|
630
651
|
if ( ! triangleData ) return;
|
|
631
652
|
|
|
@@ -633,45 +654,57 @@ export class PathTracerStage extends RenderStage {
|
|
|
633
654
|
const flat = chunked ? null : ( triangleData.chunks ? triangleData.chunks[ 0 ] : triangleData );
|
|
634
655
|
const records = chunked ? chunked.recordCount : Math.ceil( flat.length / TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE );
|
|
635
656
|
const pre = chunked?._gpuUpload?.triangles ? chunked._gpuUpload : null;
|
|
636
|
-
const { geo, shade } = pre ?? this._allocateTriangleBuffers( records );
|
|
657
|
+
const { geo, shade } = pre ?? this._allocateTriangleBuffers( records, { textureCoordinates } );
|
|
658
|
+
const withoutUV = shade.withoutUV === true;
|
|
659
|
+
const node = this.triangleStorageNode;
|
|
637
660
|
|
|
638
|
-
if (
|
|
661
|
+
if ( node && geo.attrs.length === 1 && shade.attrs.length === 1 && ! node.geo.isSplitStorage && ! node.shade.isSplitStorage && !! node.withoutUV === withoutUV ) {
|
|
639
662
|
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
663
|
+
node.geo.value = geo.attrs[ 0 ];
|
|
664
|
+
node.geo.bufferCount = geo.attrs[ 0 ].count;
|
|
665
|
+
node.shade.value = shade.attrs[ 0 ];
|
|
666
|
+
node.shade.bufferCount = shade.attrs[ 0 ].count;
|
|
644
667
|
|
|
645
668
|
} else {
|
|
646
669
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
shade: storage( shade, 'uvec4', shade.count ).toReadOnly(),
|
|
650
|
-
};
|
|
670
|
+
// Kernels rebuild with the scene (setupMaterial), so a new layout takes new nodes.
|
|
671
|
+
this.triangleStorageNode = triangleNodes( geo, shade );
|
|
651
672
|
|
|
652
673
|
}
|
|
653
674
|
|
|
654
|
-
this.
|
|
655
|
-
this.
|
|
675
|
+
this._geoParts = geo;
|
|
676
|
+
this._shadeParts = shade;
|
|
677
|
+
this.triangleGeoAttr = geo.attrs[ 0 ];
|
|
678
|
+
this.triangleShadeAttr = shade.attrs[ 0 ];
|
|
656
679
|
this._triangleRecords = chunked;
|
|
657
680
|
this._triangleFlat = flat;
|
|
658
681
|
this._triangleRecordCount = records;
|
|
659
682
|
this.triangleCount = triangleCount;
|
|
660
683
|
this._uploadTriangles( 0, records, { skipChunks: pre?.uploaded } );
|
|
684
|
+
// Adopted once: a later call with these records uploads them again, in the layout it asks for.
|
|
685
|
+
if ( pre ) chunked._gpuUpload = null;
|
|
661
686
|
|
|
662
687
|
log.debug( `${fmt.n( this.triangleCount )} triangles (storage buffer)` );
|
|
663
688
|
|
|
664
689
|
}
|
|
665
690
|
|
|
666
|
-
/**
|
|
667
|
-
|
|
691
|
+
/**
|
|
692
|
+
* GPU-only geo and shade buffers for `records` triangles, in as many parts as one binding's limit needs. Without
|
|
693
|
+
* texture coordinates the shade buffer keeps only flags and mesh index, a uvec2 a triangle (TSL/Common.js
|
|
694
|
+
* `triangleRow`): 24 B a triangle less, 2.65 GB on the whole Moana island, which samples no texture. @private
|
|
695
|
+
*/
|
|
696
|
+
_allocateTriangleBuffers( records, { textureCoordinates = true } = {} ) {
|
|
668
697
|
|
|
669
698
|
const n = Math.max( 1, records );
|
|
670
|
-
this.
|
|
671
|
-
const geo =
|
|
672
|
-
const shade =
|
|
673
|
-
|
|
674
|
-
|
|
699
|
+
const max = this._maxPartBytes();
|
|
700
|
+
const geo = new StorageParts( n * TRI_GEO_ROWS * 4, TRI_GEO_ROWS * 4, max, Uint32Array );
|
|
701
|
+
const shade = textureCoordinates
|
|
702
|
+
? new StorageParts( n * TRI_SHADE_ROWS * 4, TRI_SHADE_ROWS * 4, max, Uint32Array )
|
|
703
|
+
: new StorageParts( n * 2, 2, max, Uint32Array, 2 );
|
|
704
|
+
shade.withoutUV = ! textureCoordinates;
|
|
705
|
+
this._assertBindings( { geo: geo.attrs.length, shade: shade.attrs.length, bvh: 1 } );
|
|
706
|
+
geo.create( this.renderer.backend );
|
|
707
|
+
shade.create( this.renderer.backend );
|
|
675
708
|
return { geo, shade };
|
|
676
709
|
|
|
677
710
|
}
|
|
@@ -685,21 +718,22 @@ export class PathTracerStage extends RenderStage {
|
|
|
685
718
|
* `source` writes those records instead of reading the stage's own (a chunk being handed over).
|
|
686
719
|
* @private
|
|
687
720
|
*/
|
|
688
|
-
_uploadTriangles( start, count, { geo = true, shade = true, skipChunks = null, source = null, sourceStart = 0,
|
|
721
|
+
_uploadTriangles( start, count, { geo = true, shade = true, skipChunks = null, source = null, sourceStart = 0, geoParts = this._geoParts, shadeParts = this._shadeParts } = {} ) {
|
|
689
722
|
|
|
690
|
-
if ( count <= 0 || !
|
|
723
|
+
if ( count <= 0 || ! geoParts ) return;
|
|
691
724
|
|
|
692
725
|
const backend = this.renderer.backend;
|
|
693
|
-
const geoBuffer = backend.get( geoAttr ).buffer;
|
|
694
|
-
const shadeBuffer = backend.get( shadeAttr ).buffer;
|
|
695
726
|
const LANES = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;
|
|
696
|
-
const GEO = TRI_GEO_ROWS * 4
|
|
727
|
+
const GEO = TRI_GEO_ROWS * 4;
|
|
728
|
+
// Without texture coordinates only the flags and mesh index lanes go up.
|
|
729
|
+
const SHADE = shadeParts.withoutUV ? 2 : TRI_SHADE_ROWS * 4;
|
|
730
|
+
const SHADE_FROM = LANES - SHADE;
|
|
697
731
|
const records = this._triangleRecords;
|
|
698
732
|
const perChunk = source ? count : records ? records.recordsPerChunk : this._triangleRecordCount;
|
|
699
733
|
// One staging pair for every upload: a fresh one per chunk was churn that failed to
|
|
700
734
|
// allocate on a scene already at the edge of the address space.
|
|
701
735
|
const slice = TRIANGLE_UPLOAD_SLICE;
|
|
702
|
-
this._triangleStaging ??= { geo: new Uint32Array( slice * GEO ), shade: new Uint32Array( slice *
|
|
736
|
+
this._triangleStaging ??= { geo: new Uint32Array( slice * GEO ), shade: new Uint32Array( slice * TRI_SHADE_ROWS * 4 ) };
|
|
703
737
|
const geoStage = this._triangleStaging.geo;
|
|
704
738
|
const shadeStage = this._triangleStaging.shade;
|
|
705
739
|
|
|
@@ -723,12 +757,12 @@ export class PathTracerStage extends RenderStage {
|
|
|
723
757
|
for ( let i = 0, g = 0, h = 0; i < n; i ++, s += LANES ) {
|
|
724
758
|
|
|
725
759
|
if ( geo ) for ( let l = 0; l < GEO; l ++ ) geoStage[ g ++ ] = src[ s + l ];
|
|
726
|
-
if ( shade ) for ( let l =
|
|
760
|
+
if ( shade ) for ( let l = SHADE_FROM; l < LANES; l ++ ) shadeStage[ h ++ ] = src[ s + l ];
|
|
727
761
|
|
|
728
762
|
}
|
|
729
763
|
|
|
730
|
-
if ( geo )
|
|
731
|
-
if ( shade )
|
|
764
|
+
if ( geo ) geoParts.write( backend, at * GEO, geoStage, 0, n * GEO );
|
|
765
|
+
if ( shade ) shadeParts.write( backend, at * SHADE, shadeStage, 0, n * SHADE );
|
|
732
766
|
at += n;
|
|
733
767
|
|
|
734
768
|
}
|
|
@@ -740,38 +774,48 @@ export class PathTracerStage extends RenderStage {
|
|
|
740
774
|
* chunk into it, so a build can hand chunks over as it finishes them and let them go (the
|
|
741
775
|
* memory spill). setTriangleData / setBVHData then adopt that buffer rather than upload again.
|
|
742
776
|
* @param {import('../Processor/ChunkedRecords.js').ChunkedRecords} records
|
|
743
|
-
* @returns {function(number): void} uploads chunk k from `records.chunks[ k ]
|
|
777
|
+
* @returns {function(number): Promise<void>} uploads chunk k from `records.chunks[ k ]`; resolves with {@link drainUploads}
|
|
744
778
|
*/
|
|
745
779
|
createChunkUploader( records ) {
|
|
746
780
|
|
|
747
|
-
this.
|
|
748
|
-
const attr = gpuOnlyStorageAttribute( records.recordCount * records.lanesPerRecord / 4, 4, records.LaneType );
|
|
781
|
+
const parts = this._allocateBVHParts( records.recordCount * records.lanesPerRecord );
|
|
749
782
|
const backend = this.renderer.backend;
|
|
750
|
-
|
|
751
|
-
const buffer = backend.get( attr ).buffer;
|
|
752
|
-
const stride = records.recordsPerChunk * records.lanesPerRecord * records.LaneType.BYTES_PER_ELEMENT;
|
|
783
|
+
const lanesPerChunk = records.recordsPerChunk * records.lanesPerRecord;
|
|
753
784
|
const uploaded = new Set();
|
|
754
|
-
records._gpuUpload = {
|
|
785
|
+
records._gpuUpload = { parts, uploaded };
|
|
755
786
|
|
|
756
787
|
return k => {
|
|
757
788
|
|
|
758
789
|
const chunk = records.chunks[ k ];
|
|
759
|
-
|
|
790
|
+
parts.write( backend, k * lanesPerChunk, chunk, 0, chunk.length );
|
|
760
791
|
uploaded.add( k );
|
|
792
|
+
return this.drainUploads();
|
|
761
793
|
|
|
762
794
|
};
|
|
763
795
|
|
|
764
796
|
}
|
|
765
797
|
|
|
798
|
+
/**
|
|
799
|
+
* Resolves once the GPU has taken every upload so far. Chrome passes each upload through a shared memory pool that
|
|
800
|
+
* grows to the most ever sent before the GPU caught up and is kept until the page closes (2.2 GB on the Moana
|
|
801
|
+
* island); awaiting this after each chunk keeps it to one. Measured: 1 GB in 64 MB writes left 1 GB behind, paced
|
|
802
|
+
* 64 MB, in the same time.
|
|
803
|
+
*/
|
|
804
|
+
drainUploads() {
|
|
805
|
+
|
|
806
|
+
return this.renderer.backend.device.queue.onSubmittedWorkDone();
|
|
807
|
+
|
|
808
|
+
}
|
|
809
|
+
|
|
766
810
|
/**
|
|
767
811
|
* createChunkUploader for triangle records: both GPU buffers are allocated now and each
|
|
768
812
|
* chunk is split into them as it is handed over.
|
|
769
813
|
* @param {import('../Processor/ChunkedRecords.js').ChunkedRecords} records
|
|
770
|
-
* @returns {function(number): void}
|
|
814
|
+
* @returns {function(number): Promise<void>}
|
|
771
815
|
*/
|
|
772
|
-
createTriangleChunkUploader( records ) {
|
|
816
|
+
createTriangleChunkUploader( records, { textureCoordinates = true } = {} ) {
|
|
773
817
|
|
|
774
|
-
const { geo, shade } = this._allocateTriangleBuffers( records.recordCount );
|
|
818
|
+
const { geo, shade } = this._allocateTriangleBuffers( records.recordCount, { textureCoordinates } );
|
|
775
819
|
const uploaded = new Set();
|
|
776
820
|
records._gpuUpload = { triangles: true, geo, shade, uploaded };
|
|
777
821
|
const perChunk = records.recordsPerChunk;
|
|
@@ -780,46 +824,79 @@ export class PathTracerStage extends RenderStage {
|
|
|
780
824
|
|
|
781
825
|
const first = k * perChunk;
|
|
782
826
|
const count = Math.min( perChunk, records.recordCount - first );
|
|
783
|
-
this._uploadTriangles( first, count, { source: records.chunks[ k ],
|
|
827
|
+
this._uploadTriangles( first, count, { source: records.chunks[ k ], geoParts: geo, shadeParts: shade } );
|
|
784
828
|
uploaded.add( k );
|
|
829
|
+
return this.drainUploads();
|
|
785
830
|
|
|
786
831
|
};
|
|
787
832
|
|
|
788
833
|
}
|
|
789
834
|
|
|
835
|
+
/** Fresh read-only nodes over the scene's BVH and triangles, for a stage that builds a kernel of its own. */
|
|
836
|
+
sceneStorageNodes() {
|
|
837
|
+
|
|
838
|
+
if ( ! this.bvhStorageAttr || ! this._geoParts ) return null;
|
|
839
|
+
const bvh = this._bvhParts ? readParts( this._bvhParts, 'vec4' ) : storage( this.bvhStorageAttr, 'vec4', this.bvhStorageAttr.count ).toReadOnly();
|
|
840
|
+
return { bvh, triangles: triangleNodes( this._geoParts, this._shadeParts ) };
|
|
841
|
+
|
|
842
|
+
}
|
|
843
|
+
|
|
790
844
|
/**
|
|
791
|
-
*
|
|
792
|
-
*
|
|
845
|
+
* Most bytes one storage buffer may hold: past it WebGPU hands back an invalid buffer and every write to it fails
|
|
846
|
+
* quietly, so a larger store is split into parts. `bufferPartBytes` lowers it, for tests to force several parts.
|
|
793
847
|
* @private
|
|
794
848
|
*/
|
|
795
|
-
|
|
849
|
+
_maxPartBytes() {
|
|
796
850
|
|
|
797
851
|
const limits = this.renderer?.backend?.device?.limits;
|
|
798
852
|
const max = Math.min( limits?.maxBufferSize ?? Infinity, limits?.maxStorageBufferBindingSize ?? Infinity );
|
|
799
|
-
|
|
853
|
+
return Math.min( max, this.bufferPartBytes ?? Infinity );
|
|
800
854
|
|
|
801
855
|
}
|
|
802
856
|
|
|
803
|
-
/**
|
|
804
|
-
|
|
857
|
+
/**
|
|
858
|
+
* Every part of the tree and triangle stores is a binding of its own, and the shading kernel binds them all beside
|
|
859
|
+
* SHADE_OTHER_BINDINGS others, so past the device's per-stage limit no kernel could be built: refuse before upload.
|
|
860
|
+
* @private
|
|
861
|
+
*/
|
|
862
|
+
_assertBindings( counts ) {
|
|
863
|
+
|
|
864
|
+
// The latest allocation of each: a build allocates its triangles before its BVH, and the previous scene's parts
|
|
865
|
+
// must not count against the next one.
|
|
866
|
+
this._partCounts = { ...( this._partCounts ?? { bvh: 1, geo: 1, shade: 1 } ), ...counts };
|
|
867
|
+
const { bvh, geo, shade } = this._partCounts;
|
|
868
|
+
const limit = this.renderer?.backend?.device?.limits?.maxStorageBuffersPerShaderStage ?? 10;
|
|
869
|
+
if ( SHADE_OTHER_BINDINGS + bvh + geo + shade <= limit ) return;
|
|
870
|
+
throw new Error(
|
|
871
|
+
`the scene's BVH and triangles need ${bvh + geo + shade} GPU buffers (BVH ${bvh}, triangles ${geo} + ${shade}), ` +
|
|
872
|
+
`more than the ${limit - SHADE_OTHER_BINDINGS} one GPU program can bind beside the rest — load fewer triangles or copies`
|
|
873
|
+
);
|
|
874
|
+
|
|
875
|
+
}
|
|
805
876
|
|
|
806
|
-
|
|
807
|
-
|
|
877
|
+
/** GPU-only BVH buffers for `lanes` floats, in as many parts as one binding's limit needs. @private */
|
|
878
|
+
_allocateBVHParts( lanes ) {
|
|
808
879
|
|
|
809
|
-
|
|
810
|
-
|
|
880
|
+
const parts = new StorageParts( lanes, 16, this._maxPartBytes(), Float32Array );
|
|
881
|
+
this._assertBindings( { bvh: parts.attrs.length } );
|
|
882
|
+
parts.create( this.renderer.backend );
|
|
883
|
+
return parts;
|
|
811
884
|
|
|
812
|
-
|
|
885
|
+
}
|
|
813
886
|
|
|
814
|
-
|
|
815
|
-
|
|
887
|
+
/** Uploads chunked records into `parts`, or — for parts a build already filled — whatever it has not. @private */
|
|
888
|
+
_uploadChunked( parts, records ) {
|
|
889
|
+
|
|
890
|
+
const pre = records._gpuUpload?.parts === parts ? records._gpuUpload : null;
|
|
891
|
+
const lanesPerChunk = records.recordsPerChunk * records.lanesPerRecord;
|
|
892
|
+
const backend = this.renderer.backend;
|
|
816
893
|
for ( let k = 0; k < records.chunks.length; k ++ ) {
|
|
817
894
|
|
|
818
|
-
if ( pre
|
|
895
|
+
if ( pre?.uploaded.has( k ) ) continue;
|
|
819
896
|
const chunk = records.chunks[ k ];
|
|
820
897
|
if ( ! chunk ) throw new Error( `chunk ${k} is on disk and was never uploaded` );
|
|
821
|
-
|
|
822
|
-
pre
|
|
898
|
+
parts.write( backend, k * lanesPerChunk, chunk, 0, chunk.length );
|
|
899
|
+
pre?.uploaded.add( k );
|
|
823
900
|
|
|
824
901
|
}
|
|
825
902
|
|
|
@@ -839,28 +916,31 @@ export class PathTracerStage extends RenderStage {
|
|
|
839
916
|
const flat = chunked ? null : ( bvhImageData.chunks ? bvhImageData.chunks[ 0 ] : bvhImageData );
|
|
840
917
|
const lanes = chunked ? chunked.recordCount * chunked.lanesPerRecord : flat.length;
|
|
841
918
|
const vec4Count = lanes / 4;
|
|
842
|
-
this._assertFitsGPU( lanes * 4 );
|
|
843
919
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
920
|
+
// A flat array keeps a CPU-backed attribute (patches go up through its update ranges); chunked records go to
|
|
921
|
+
// GPU-only parts, as many as one binding's limit needs.
|
|
922
|
+
const parts = chunked ? chunked._gpuUpload?.parts ?? this._allocateBVHParts( lanes ) : null;
|
|
923
|
+
const attrs = parts ? parts.attrs : [ new StorageInstancedBufferAttribute( flat, 4 ) ];
|
|
924
|
+
if ( ! parts ) this._assertBindings( { bvh: 1 } );
|
|
847
925
|
|
|
848
|
-
if ( this.bvhStorageNode ) {
|
|
926
|
+
if ( this.bvhStorageNode && attrs.length === 1 && ! this.bvhStorageNode.isSplitStorage ) {
|
|
849
927
|
|
|
850
|
-
this.
|
|
851
|
-
this.bvhStorageNode.value = this.bvhStorageAttr;
|
|
928
|
+
this.bvhStorageNode.value = attrs[ 0 ];
|
|
852
929
|
this.bvhStorageNode.bufferCount = vec4Count;
|
|
853
930
|
|
|
854
931
|
} else {
|
|
855
932
|
|
|
856
|
-
this.
|
|
857
|
-
this.bvhStorageNode = storage( this.bvhStorageAttr, 'vec4', vec4Count ).toReadOnly();
|
|
933
|
+
this.bvhStorageNode = parts ? readParts( parts, 'vec4' ) : storage( attrs[ 0 ], 'vec4', vec4Count ).toReadOnly();
|
|
858
934
|
|
|
859
935
|
}
|
|
860
936
|
|
|
937
|
+
this.bvhStorageAttr = attrs[ 0 ];
|
|
861
938
|
this.bvhStorageAttr.foldedLeaves = bvhImageData.foldedLeaves === true;
|
|
939
|
+
this.bvhStorageAttr.copyRecordBase = bvhImageData.copyRecordBase;
|
|
940
|
+
this._bvhParts = parts;
|
|
862
941
|
this._bvhRecords = chunked;
|
|
863
|
-
if ( chunked ) this._uploadChunked(
|
|
942
|
+
if ( chunked ) this._uploadChunked( parts, chunked );
|
|
943
|
+
if ( chunked ) chunked._gpuUpload = null;
|
|
864
944
|
|
|
865
945
|
this.bvhNodeCount = Math.floor( vec4Count / BVH_VEC4_PER_NODE );
|
|
866
946
|
log.debug( `${fmt.n( this.bvhNodeCount )} BVH nodes (storage buffer)` );
|
|
@@ -913,6 +993,29 @@ export class PathTracerStage extends RenderStage {
|
|
|
913
993
|
|
|
914
994
|
}
|
|
915
995
|
|
|
996
|
+
this._patchGroupNodes();
|
|
997
|
+
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* Rewrites the group trees: a hidden member's child points at the empty leaf. Every copy of a group shares its
|
|
1002
|
+
* tree, as every instance of an InstancedMesh shares its visibility.
|
|
1003
|
+
* @private
|
|
1004
|
+
*/
|
|
1005
|
+
_patchGroupNodes() {
|
|
1006
|
+
|
|
1007
|
+
const table = this._instanceTable;
|
|
1008
|
+
if ( ! table?.groupNodeCount || ! this.bvhStorageAttr ) return;
|
|
1009
|
+
|
|
1010
|
+
table.computeGroupAABBs();
|
|
1011
|
+
const block = table.groupNodeBlock();
|
|
1012
|
+
const start = table.groupNodeStart;
|
|
1013
|
+
if ( this._bvhRecords ) this._bvhRecords.setRecords( start, block );
|
|
1014
|
+
else this.bvhStorageAttr.array.set( block, start * 16 );
|
|
1015
|
+
|
|
1016
|
+
this._dirtyBVHLeaves ??= new Set();
|
|
1017
|
+
for ( let n = 0; n < table.groupNodeCount; n ++ ) this._dirtyBVHLeaves.add( start + n );
|
|
1018
|
+
|
|
916
1019
|
}
|
|
917
1020
|
|
|
918
1021
|
/**
|
|
@@ -922,8 +1025,13 @@ export class PathTracerStage extends RenderStage {
|
|
|
922
1025
|
*/
|
|
923
1026
|
updateMeshVisibility( meshIndex, visible ) {
|
|
924
1027
|
|
|
925
|
-
|
|
926
|
-
|
|
1028
|
+
this._whenTLASResident( () => {
|
|
1029
|
+
|
|
1030
|
+
if ( ! this._patchTLASLeafVisibility( meshIndex, visible ) ) return;
|
|
1031
|
+
this._patchGroupNodes();
|
|
1032
|
+
this._flushBVHEdits();
|
|
1033
|
+
|
|
1034
|
+
} );
|
|
927
1035
|
|
|
928
1036
|
}
|
|
929
1037
|
|
|
@@ -935,8 +1043,30 @@ export class PathTracerStage extends RenderStage {
|
|
|
935
1043
|
|
|
936
1044
|
if ( ! this._meshRefs || ! this._instanceTable ) return;
|
|
937
1045
|
|
|
938
|
-
this.
|
|
939
|
-
|
|
1046
|
+
this._whenTLASResident( () => {
|
|
1047
|
+
|
|
1048
|
+
this._patchVisibilityFromMeshes( this._meshRefs );
|
|
1049
|
+
this._flushBVHEdits();
|
|
1050
|
+
|
|
1051
|
+
} );
|
|
1052
|
+
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
/**
|
|
1056
|
+
* Runs `edit` once the TLAS is in memory: a spilling build of clustered copies leaves it on disk (SceneProcessor
|
|
1057
|
+
* `_spillTLAS`), and the first edit reads it back, where it then stays.
|
|
1058
|
+
* @private
|
|
1059
|
+
*/
|
|
1060
|
+
_whenTLASResident( edit ) {
|
|
1061
|
+
|
|
1062
|
+
const records = this._bvhRecords, table = this._instanceTable;
|
|
1063
|
+
if ( ! records || ! table || records.isResident( 0, table.blasBase ) ) return edit();
|
|
1064
|
+
this._tlasReading ??= records.ensureResident( 0, table.blasBase ).finally( () => {
|
|
1065
|
+
|
|
1066
|
+
this._tlasReading = null;
|
|
1067
|
+
|
|
1068
|
+
} );
|
|
1069
|
+
this._tlasReading.then( edit );
|
|
940
1070
|
|
|
941
1071
|
}
|
|
942
1072
|
|
|
@@ -950,14 +1080,25 @@ export class PathTracerStage extends RenderStage {
|
|
|
950
1080
|
const table = this._instanceTable;
|
|
951
1081
|
if ( ! table || ! table.isSet?.[ meshIndex ] || ! this.bvhStorageAttr ) return false;
|
|
952
1082
|
|
|
953
|
-
const leaf = table.
|
|
1083
|
+
const leaf = table.leafOfPlacement( meshIndex );
|
|
954
1084
|
if ( leaf < 0 ) return false;
|
|
955
1085
|
|
|
956
1086
|
table.visible[ meshIndex ] = visible ? 1 : 0;
|
|
1087
|
+
// A group copy's leaf is shared by its members: it stays in while any of them is visible.
|
|
1088
|
+
const shown = table.entryVisible( table.entryOf( meshIndex ) );
|
|
957
1089
|
|
|
958
1090
|
// A chunked attribute owns no CPU array; write into the chunk that holds this leaf.
|
|
959
|
-
|
|
960
|
-
|
|
1091
|
+
const f = this._bvhRecords ? this._bvhRecords.chunkFor( leaf ) : this.bvhStorageAttr.array;
|
|
1092
|
+
const o = this._bvhRecords ? this._bvhRecords.baseOf( leaf ) : leaf * 16;
|
|
1093
|
+
if ( table.clusterCount ) {
|
|
1094
|
+
|
|
1095
|
+
const views = this._indexViews ??= new WeakMap();
|
|
1096
|
+
let idx = views.get( f );
|
|
1097
|
+
if ( ! idx ) views.set( f, idx = bvhIndexView( f ) );
|
|
1098
|
+
const w = o + table.copyWordSlot( table.tlasLeafIndex[ meshIndex ] );
|
|
1099
|
+
idx[ w ] = shown ? idx[ w ] & ~ CLUSTER_COPY_HIDDEN : ( idx[ w ] | CLUSTER_COPY_HIDDEN ) >>> 0;
|
|
1100
|
+
|
|
1101
|
+
} else f[ o + 2 ] = shown ? 1.0 : 0.0;
|
|
961
1102
|
|
|
962
1103
|
this._dirtyBVHLeaves ??= new Set();
|
|
963
1104
|
this._dirtyBVHLeaves.add( leaf );
|
|
@@ -999,7 +1140,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
999
1140
|
|
|
1000
1141
|
for ( const leaf of this._dirtyBVHLeaves ?? [] ) {
|
|
1001
1142
|
|
|
1002
|
-
|
|
1143
|
+
this._bvhParts.writeRecords( this.renderer.backend, this._bvhRecords, leaf * 16, 16 );
|
|
1003
1144
|
|
|
1004
1145
|
}
|
|
1005
1146
|
|
|
@@ -1094,7 +1235,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
1094
1235
|
|
|
1095
1236
|
if ( bvhData?.chunks?.length > 1 ) this._bvhRecords = bvhData;
|
|
1096
1237
|
if ( ranges ) this.updateBufferRanges( [], ranges );
|
|
1097
|
-
else
|
|
1238
|
+
else this._bvhParts.writeRecords( this.renderer.backend, this._bvhRecords, 0, this._bvhRecords.recordCount * this._bvhRecords.lanesPerRecord );
|
|
1098
1239
|
return;
|
|
1099
1240
|
|
|
1100
1241
|
}
|
|
@@ -1142,7 +1283,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
1142
1283
|
|
|
1143
1284
|
for ( const r of bvhRanges ) {
|
|
1144
1285
|
|
|
1145
|
-
|
|
1286
|
+
this._bvhParts.writeRecords( this.renderer.backend, this._bvhRecords, r.offset, r.count );
|
|
1146
1287
|
|
|
1147
1288
|
}
|
|
1148
1289
|
|
|
@@ -1396,21 +1537,22 @@ export class PathTracerStage extends RenderStage {
|
|
|
1396
1537
|
}
|
|
1397
1538
|
|
|
1398
1539
|
/**
|
|
1399
|
-
* Update completion threshold based on render mode. The ceiling holds in
|
|
1400
|
-
* uncapping it under a time budget lets a generous deadline silently slow every render.
|
|
1540
|
+
* Update completion threshold based on render mode. The ceiling holds in 'frames' and 'time' —
|
|
1541
|
+
* uncapping it under a time budget lets a generous deadline silently slow every render. Only
|
|
1542
|
+
* 'timeOnly' lifts it, and only while a deadline is armed: without one nothing would stop the render.
|
|
1401
1543
|
*/
|
|
1402
1544
|
updateCompletionThreshold() {
|
|
1403
1545
|
|
|
1404
|
-
this.completionThreshold =
|
|
1405
|
-
|
|
1406
|
-
this.maxSamples.value
|
|
1407
|
-
);
|
|
1546
|
+
this.completionThreshold = this.renderLimitMode === 'timeOnly' && this.renderTimeLimit > 0
|
|
1547
|
+
? Infinity
|
|
1548
|
+
: updateCompletionThreshold( this.renderMode.value, this.maxSamples.value );
|
|
1408
1549
|
|
|
1409
1550
|
}
|
|
1410
1551
|
|
|
1411
|
-
setRenderLimitMode( mode ) {
|
|
1552
|
+
setRenderLimitMode( mode, timeLimit = this.renderTimeLimit ) {
|
|
1412
1553
|
|
|
1413
1554
|
this.renderLimitMode = mode;
|
|
1555
|
+
this.renderTimeLimit = timeLimit;
|
|
1414
1556
|
this.updateCompletionThreshold();
|
|
1415
1557
|
|
|
1416
1558
|
}
|
|
@@ -1468,15 +1610,28 @@ export class PathTracerStage extends RenderStage {
|
|
|
1468
1610
|
setBlueNoiseTexture() {}
|
|
1469
1611
|
|
|
1470
1612
|
/**
|
|
1471
|
-
*
|
|
1472
|
-
*
|
|
1473
|
-
|
|
1613
|
+
* Takes the material block into the scene data buffer, in front of the light data, and hands the buffer back as
|
|
1614
|
+
* the material storage. @private
|
|
1615
|
+
*/
|
|
1616
|
+
_adoptMaterials( data ) {
|
|
1617
|
+
|
|
1618
|
+
this._materialFloats = data.length;
|
|
1619
|
+
this._pendingMaterials = data;
|
|
1620
|
+
this._rebuildLightBuffer();
|
|
1621
|
+
return { attr: this.lightStorageAttr, node: this.lightStorageNode };
|
|
1622
|
+
|
|
1623
|
+
}
|
|
1624
|
+
|
|
1625
|
+
/**
|
|
1626
|
+
* Rebuild the scene data buffer: [ materials | lightBVH (LBVH_STRIDE vec4s per node) | emissive (EMISSIVE_STRIDE
|
|
1627
|
+
* vec4s per entry) | bit-trail map ]. One binding for materials and lights: Shade's ten were all spoken for. The
|
|
1628
|
+
* light offsets are within the light data, which starts at `lightVec4Offset`.
|
|
1474
1629
|
*
|
|
1475
1630
|
* The compiled wavefront kernels bind the attribute that existed at kernel-build
|
|
1476
1631
|
* time and NEVER pick up a `lightStorageNode.value` reassignment — so runtime
|
|
1477
|
-
* updates MUST write in-place into the bound attribute
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
1632
|
+
* updates MUST write in-place into the bound attribute. Only grow-reallocate when the data outgrows capacity, and
|
|
1633
|
+
* flag it so the kernels get rebuilt against the new attribute. Materials are edited in place (MaterialDataManager)
|
|
1634
|
+
* and carried over as they stand; only a new material block replaces them.
|
|
1480
1635
|
* @private
|
|
1481
1636
|
*/
|
|
1482
1637
|
_rebuildLightBuffer() {
|
|
@@ -1484,9 +1639,12 @@ export class PathTracerStage extends RenderStage {
|
|
|
1484
1639
|
const LBVH_STRIDE = 4; // vec4s per LBVH node — must match LightBVHSampling.js
|
|
1485
1640
|
const lbvh = this._lbvhDataCache;
|
|
1486
1641
|
const emis = this._emissiveDataCache;
|
|
1642
|
+
const held = this.lightStorageAttr?.array;
|
|
1643
|
+
const matLen = this._materialFloats ?? 0;
|
|
1644
|
+
const materials = this._pendingMaterials ?? ( held && matLen ? held.subarray( 0, matLen ) : null );
|
|
1645
|
+
this._pendingMaterials = null;
|
|
1487
1646
|
// The bit-trail map (4 B a triangle) lives only in the light buffer: without a new one, the
|
|
1488
1647
|
// current one is carried over from there rather than kept as a second copy.
|
|
1489
|
-
const held = this.lightStorageAttr?.array;
|
|
1490
1648
|
const trail = this._bitTrailMapCache
|
|
1491
1649
|
?? ( held && this._bitTrailLength ? held.slice( this._bitTrailOffset, this._bitTrailOffset + this._bitTrailLength ) : null );
|
|
1492
1650
|
this._bitTrailMapCache = null;
|
|
@@ -1496,28 +1654,36 @@ export class PathTracerStage extends RenderStage {
|
|
|
1496
1654
|
const trailPadded = trail ? Math.ceil( trail.length / 4 ) * 4 : 0;
|
|
1497
1655
|
|
|
1498
1656
|
// Ensure at least a minimal non-empty buffer so GPU allocation remains valid.
|
|
1499
|
-
const totalLen = Math.max( lbvhLen + emisLen + trailPadded, 4 );
|
|
1657
|
+
const totalLen = Math.max( matLen + lbvhLen + emisLen + trailPadded, 4 );
|
|
1658
|
+
const reuse = this.lightStorageAttr && totalLen <= this.lightStorageAttr.array.length && this._builtMaterialFloats === matLen;
|
|
1500
1659
|
|
|
1501
|
-
if (
|
|
1660
|
+
if ( reuse ) {
|
|
1502
1661
|
|
|
1503
1662
|
// In-place update of the bound attribute. Stale floats past the new data
|
|
1504
1663
|
// are unreachable — all reads are gated by the count/offset uniforms.
|
|
1505
1664
|
const arr = this.lightStorageAttr.array;
|
|
1506
|
-
|
|
1507
|
-
if (
|
|
1508
|
-
if (
|
|
1665
|
+
const newMaterials = materials && materials.buffer !== arr.buffer;
|
|
1666
|
+
if ( newMaterials ) arr.set( materials, 0 );
|
|
1667
|
+
if ( lbvh ) arr.set( lbvh, matLen );
|
|
1668
|
+
if ( emis ) arr.set( emis, matLen + lbvhLen );
|
|
1669
|
+
if ( trail ) arr.set( trail, matLen + lbvhLen + emisLen );
|
|
1670
|
+
// A range, not the whole buffer: a material edit's own range may be pending beside it (pitfall 13).
|
|
1671
|
+
const from = newMaterials ? 0 : matLen;
|
|
1672
|
+
this.lightStorageAttr.addUpdateRange( from, totalLen - from );
|
|
1509
1673
|
this.lightStorageAttr.needsUpdate = true;
|
|
1510
1674
|
|
|
1511
1675
|
} else {
|
|
1512
1676
|
|
|
1513
1677
|
const combined = new Float32Array( totalLen );
|
|
1514
|
-
if (
|
|
1515
|
-
if (
|
|
1516
|
-
if (
|
|
1678
|
+
if ( materials ) combined.set( materials, 0 );
|
|
1679
|
+
if ( lbvh ) combined.set( lbvh, matLen );
|
|
1680
|
+
if ( emis ) combined.set( emis, matLen + lbvhLen );
|
|
1681
|
+
if ( trail ) combined.set( trail, matLen + lbvhLen + emisLen );
|
|
1517
1682
|
|
|
1518
1683
|
this.lightStorageAttr = new StorageInstancedBufferAttribute( combined, 4 );
|
|
1519
1684
|
this.lightStorageNode.value = this.lightStorageAttr;
|
|
1520
1685
|
this.lightStorageNode.bufferCount = combined.length / 4;
|
|
1686
|
+
if ( this.materialData && this.materialData.materialStorageNode === this.lightStorageNode ) this.materialData.materialStorageAttr = this.lightStorageAttr;
|
|
1521
1687
|
|
|
1522
1688
|
// Already-compiled kernels still bind the old attribute — request a rebuild.
|
|
1523
1689
|
// (During scene load this is a no-op: setupMaterial rebuilds kernels anyway.)
|
|
@@ -1525,16 +1691,29 @@ export class PathTracerStage extends RenderStage {
|
|
|
1525
1691
|
|
|
1526
1692
|
}
|
|
1527
1693
|
|
|
1694
|
+
this._builtMaterialFloats = matLen;
|
|
1695
|
+
this.lightVec4Offset.value = matLen / 4;
|
|
1528
1696
|
// Offset (in vec4 elements) where emissive data starts.
|
|
1529
1697
|
this.emissiveVec4Offset.value = ( this.lightBVHNodeCount.value || 0 ) * LBVH_STRIDE;
|
|
1530
1698
|
// Offset (in vec4 elements) where the bit-trail map starts (lbvhLen + emisLen are float
|
|
1531
1699
|
// counts, both multiples of 4, so this divides cleanly).
|
|
1532
1700
|
this.reverseMapVec4Offset.value = ( lbvhLen + emisLen ) / 4;
|
|
1533
|
-
this._bitTrailOffset = lbvhLen + emisLen;
|
|
1701
|
+
this._bitTrailOffset = matLen + lbvhLen + emisLen;
|
|
1534
1702
|
this._bitTrailLength = trail ? trail.length : 0;
|
|
1535
1703
|
|
|
1536
1704
|
}
|
|
1537
1705
|
|
|
1706
|
+
/**
|
|
1707
|
+
* The light data as kernels read it: the scene data buffer behind the materials. One binding with the materials,
|
|
1708
|
+
* whichever of the two a kernel reads.
|
|
1709
|
+
*/
|
|
1710
|
+
get lightDataNode() {
|
|
1711
|
+
|
|
1712
|
+
const node = this.lightStorageNode, base = this.lightVec4Offset;
|
|
1713
|
+
return { isLightData: true, node, element: ( index ) => node.element( base.add( index ) ) };
|
|
1714
|
+
|
|
1715
|
+
}
|
|
1716
|
+
|
|
1538
1717
|
setEmissiveTriangleData( emissiveData, count, totalPower = 0, bitTrailMap = null ) {
|
|
1539
1718
|
|
|
1540
1719
|
if ( ! emissiveData ) return;
|