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
|
@@ -24,10 +24,15 @@ import { SceneProcessor } from '../Processor/SceneProcessor.js';
|
|
|
24
24
|
import { LightSerializer } from '../Processor/LightSerializer';
|
|
25
25
|
|
|
26
26
|
// Constants
|
|
27
|
-
import { ENGINE_DEFAULTS as DEFAULT_STATE } from '../EngineDefaults.js';
|
|
27
|
+
import { ENGINE_DEFAULTS as DEFAULT_STATE, TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
|
|
28
|
+
import { TRI_GEO_ROWS, TRI_SHADE_ROWS } from '../TSL/Common.js';
|
|
28
29
|
import { getAssetConfig } from '../AssetConfig.js';
|
|
30
|
+
import { cachedObjectURL, DOWNLOAD_POLICY } from '../Storage/DownloadCache.js';
|
|
29
31
|
import { createLogger, fmt } from '../utils/Logger.js';
|
|
30
32
|
|
|
33
|
+
// Triangles converted per staging pass: 256K is 12 + 8 MB of staging, kept for the stage's life.
|
|
34
|
+
const TRIANGLE_UPLOAD_SLICE = 1 << 18;
|
|
35
|
+
|
|
31
36
|
const log = createLogger( 'pathtracer' );
|
|
32
37
|
|
|
33
38
|
// How long after the last move the view still counts as moving. Matches CameraOptimizer's own
|
|
@@ -114,26 +119,22 @@ export class PathTracerStage extends RenderStage {
|
|
|
114
119
|
// Initialize material data manager
|
|
115
120
|
this.materialData = new MaterialDataManager( this.sdfs );
|
|
116
121
|
this.materialData.callbacks.onReset = () => this.reset();
|
|
117
|
-
//
|
|
118
|
-
//
|
|
119
|
-
//
|
|
120
|
-
// path). The patch mutates it in place, then the GPU copy is refreshed.
|
|
122
|
+
// Material edits patch the per-triangle flags lane in the CPU records (`_triangleRecords`
|
|
123
|
+
// when chunked, `_triangleFlat` otherwise — not sdfs.triangleData, which the PathTracerApp
|
|
124
|
+
// build path never fills). The flags live in the shade rows, so only those go up again.
|
|
121
125
|
this.materialData.callbacks.getTriangleData = () => ( {
|
|
122
|
-
array: this.
|
|
126
|
+
array: this._triangleFlat,
|
|
123
127
|
records: this._triangleRecords,
|
|
124
128
|
count: this.triangleCount,
|
|
125
129
|
} );
|
|
126
130
|
this.materialData.callbacks.onTriangleDataChanged = () => {
|
|
127
131
|
|
|
128
|
-
if (
|
|
129
|
-
// A chunked attribute owns no CPU array for three.js to re-upload.
|
|
130
|
-
if ( this._triangleRecords ) uploadStorageChunks( this.renderer, this.triangleStorageAttr, this._triangleRecords.chunks );
|
|
131
|
-
else this.triangleStorageAttr.needsUpdate = true;
|
|
132
|
+
if ( this.triangleShadeAttr ) this._uploadTriangles( 0, this._triangleRecordCount, { geo: false } );
|
|
132
133
|
|
|
133
134
|
};
|
|
134
135
|
|
|
135
136
|
// Initialize environment manager
|
|
136
|
-
this.environment = new EnvironmentManager( this.scene, this.uniforms );
|
|
137
|
+
this.environment = new EnvironmentManager( this.scene, this.uniforms, this.renderer );
|
|
137
138
|
this.environment.callbacks.onReset = () => this.reset();
|
|
138
139
|
this.environment.callbacks.getSceneTextureNodes = () => this.shaderBuilder.getSceneTextureNodes();
|
|
139
140
|
|
|
@@ -183,9 +184,13 @@ export class PathTracerStage extends RenderStage {
|
|
|
183
184
|
*/
|
|
184
185
|
_initDataTextures() {
|
|
185
186
|
|
|
186
|
-
// Triangle data (
|
|
187
|
-
|
|
187
|
+
// Triangle data: two GPU buffers (TRI_GEO_ROWS / TRI_SHADE_ROWS rows a triangle), one
|
|
188
|
+
// { geo, shade } node pair, and the CPU records they were written from.
|
|
189
|
+
this.triangleGeoAttr = null;
|
|
190
|
+
this.triangleShadeAttr = null;
|
|
188
191
|
this._triangleRecords = null;
|
|
192
|
+
this._triangleFlat = null;
|
|
193
|
+
this._triangleRecordCount = 0;
|
|
189
194
|
this.triangleStorageNode = null;
|
|
190
195
|
this.triangleCount = 0;
|
|
191
196
|
|
|
@@ -238,6 +243,8 @@ export class PathTracerStage extends RenderStage {
|
|
|
238
243
|
// Per-triangle bit-trail map (root→leaf Light BVH path, 1 float per triangleIndex); packed
|
|
239
244
|
// after the emissive entries so the bounce-hit MIS path can re-walk the descent pdf.
|
|
240
245
|
this._bitTrailMapCache = null;
|
|
246
|
+
this._bitTrailOffset = 0;
|
|
247
|
+
this._bitTrailLength = 0;
|
|
241
248
|
|
|
242
249
|
// Per-mesh visibility is packed into the TLAS BLAS-pointer leaf's slot [2]
|
|
243
250
|
// (see TLASBuilder.flatten + BVHTraversal.js). The InstanceTable holds the
|
|
@@ -384,7 +391,22 @@ export class PathTracerStage extends RenderStage {
|
|
|
384
391
|
|
|
385
392
|
const { stbnScalarAtlas, stbnVec2Atlas } = getAssetConfig();
|
|
386
393
|
|
|
387
|
-
const
|
|
394
|
+
const load = async ( url ) => {
|
|
395
|
+
|
|
396
|
+
const source = await cachedObjectURL( url, { policy: DOWNLOAD_POLICY.REVALIDATE } );
|
|
397
|
+
try {
|
|
398
|
+
|
|
399
|
+
return await loader.loadAsync( source.url );
|
|
400
|
+
|
|
401
|
+
} finally {
|
|
402
|
+
|
|
403
|
+
source.release();
|
|
404
|
+
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
const scalarLoad = load( stbnScalarAtlas ).then( ( tex ) => {
|
|
388
410
|
|
|
389
411
|
this.stbnScalarTexture = configure( tex );
|
|
390
412
|
stbnScalarTextureNode.value = tex;
|
|
@@ -392,7 +414,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
392
414
|
|
|
393
415
|
} );
|
|
394
416
|
|
|
395
|
-
const vec2Load =
|
|
417
|
+
const vec2Load = load( stbnVec2Atlas ).then( ( tex ) => {
|
|
396
418
|
|
|
397
419
|
this.stbnVec2Texture = configure( tex );
|
|
398
420
|
stbnVec2TextureNode.value = tex;
|
|
@@ -543,36 +565,6 @@ export class PathTracerStage extends RenderStage {
|
|
|
543
565
|
this.spotLightsData = mockMaterial.uniforms.spotLights.value;
|
|
544
566
|
this.areaLightsData = mockMaterial.uniforms.areaLights.value;
|
|
545
567
|
|
|
546
|
-
// Add sun as directional light if procedural sky is active
|
|
547
|
-
if ( this.hasSun.value ) {
|
|
548
|
-
|
|
549
|
-
const scaledSunIntensity = this.environment.envParams.skySunIntensity * 950.0;
|
|
550
|
-
|
|
551
|
-
const sunLight = {
|
|
552
|
-
intensity: scaledSunIntensity,
|
|
553
|
-
color: { r: 1.0, g: 1.0, b: 1.0 },
|
|
554
|
-
userData: {
|
|
555
|
-
angle: this.sunAngularSize.value
|
|
556
|
-
},
|
|
557
|
-
updateMatrixWorld: () => {},
|
|
558
|
-
getWorldPosition: ( target ) => {
|
|
559
|
-
|
|
560
|
-
const sunDir = this.sunDirection.value;
|
|
561
|
-
return target.set( sunDir.x, sunDir.y, sunDir.z ).multiplyScalar( 1e10 );
|
|
562
|
-
|
|
563
|
-
}
|
|
564
|
-
};
|
|
565
|
-
|
|
566
|
-
this.lightSerializer.addDirectionalLight( sunLight );
|
|
567
|
-
this.lightSerializer.preprocessLights();
|
|
568
|
-
this.lightSerializer.updateShaderUniforms( mockMaterial );
|
|
569
|
-
|
|
570
|
-
this.directionalLightsData = mockMaterial.uniforms.directionalLights.value;
|
|
571
|
-
|
|
572
|
-
log.debug( `sun added as directional light · intensity ${scaledSunIntensity.toFixed( 2 )}` );
|
|
573
|
-
|
|
574
|
-
}
|
|
575
|
-
|
|
576
568
|
// Update TSL uniform buffer nodes from raw Float32Array data
|
|
577
569
|
this._updateLightBufferNodes();
|
|
578
570
|
|
|
@@ -729,54 +721,212 @@ export class PathTracerStage extends RenderStage {
|
|
|
729
721
|
// ===== TEXTURE SETTERS =====
|
|
730
722
|
|
|
731
723
|
/**
|
|
732
|
-
* Sets the triangle data
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
* @param {Uint32Array} triangleData - Packed triangle records (uvec4 lanes)
|
|
724
|
+
* Sets the triangle data. The records stay whole on the CPU and are split into the geo and
|
|
725
|
+
* shade GPU buffers as they go up. Later calls swap the nodes' attributes, which keeps the
|
|
726
|
+
* compiled shader graph.
|
|
727
|
+
* @param {Uint32Array|import('../Processor/ChunkedRecords.js').ChunkedRecords} triangleData
|
|
737
728
|
* @param {number} triangleCount - Number of triangles
|
|
738
729
|
*/
|
|
739
730
|
setTriangleData( triangleData, triangleCount ) {
|
|
740
731
|
|
|
741
732
|
if ( ! triangleData ) return;
|
|
742
733
|
|
|
743
|
-
// Past the ~2 GB array cap the records arrive as several chunks; they still become one
|
|
744
|
-
// GPU buffer, written at their running byte offsets, so no binding or shader changes.
|
|
745
734
|
const chunked = triangleData.chunks && triangleData.chunks.length > 1 ? triangleData : null;
|
|
746
735
|
const flat = chunked ? null : ( triangleData.chunks ? triangleData.chunks[ 0 ] : triangleData );
|
|
747
|
-
const
|
|
748
|
-
const
|
|
749
|
-
|
|
750
|
-
const makeAttr = () => chunked
|
|
751
|
-
? gpuOnlyStorageAttribute( vec4Count, 4, Uint32Array )
|
|
752
|
-
: new StorageInstancedBufferAttribute( flat, 4 );
|
|
736
|
+
const records = chunked ? chunked.recordCount : Math.ceil( flat.length / TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE );
|
|
737
|
+
const pre = chunked?._gpuUpload?.triangles ? chunked._gpuUpload : null;
|
|
738
|
+
const { geo, shade } = pre ?? this._allocateTriangleBuffers( records );
|
|
753
739
|
|
|
754
740
|
if ( this.triangleStorageNode ) {
|
|
755
741
|
|
|
756
|
-
|
|
757
|
-
this.
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
this.triangleStorageNode.value = this.triangleStorageAttr;
|
|
761
|
-
this.triangleStorageNode.bufferCount = vec4Count;
|
|
742
|
+
this.triangleStorageNode.geo.value = geo;
|
|
743
|
+
this.triangleStorageNode.geo.bufferCount = geo.count;
|
|
744
|
+
this.triangleStorageNode.shade.value = shade;
|
|
745
|
+
this.triangleStorageNode.shade.bufferCount = shade.count;
|
|
762
746
|
|
|
763
747
|
} else {
|
|
764
748
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
749
|
+
this.triangleStorageNode = {
|
|
750
|
+
geo: storage( geo, 'uvec4', geo.count ).toReadOnly(),
|
|
751
|
+
shade: storage( shade, 'uvec4', shade.count ).toReadOnly(),
|
|
752
|
+
};
|
|
768
753
|
|
|
769
754
|
}
|
|
770
755
|
|
|
756
|
+
this.triangleGeoAttr = geo;
|
|
757
|
+
this.triangleShadeAttr = shade;
|
|
771
758
|
this._triangleRecords = chunked;
|
|
772
|
-
|
|
773
|
-
|
|
759
|
+
this._triangleFlat = flat;
|
|
760
|
+
this._triangleRecordCount = records;
|
|
774
761
|
this.triangleCount = triangleCount;
|
|
762
|
+
this._uploadTriangles( 0, records, { skipChunks: pre?.uploaded } );
|
|
775
763
|
|
|
776
764
|
log.debug( `${fmt.n( this.triangleCount )} triangles (storage buffer)` );
|
|
777
765
|
|
|
778
766
|
}
|
|
779
767
|
|
|
768
|
+
/** GPU-only geo and shade buffers for `records` triangles, created now. @private */
|
|
769
|
+
_allocateTriangleBuffers( records ) {
|
|
770
|
+
|
|
771
|
+
const n = Math.max( 1, records );
|
|
772
|
+
this._assertFitsGPU( n * TRI_GEO_ROWS * 16 );
|
|
773
|
+
const geo = gpuOnlyStorageAttribute( n * TRI_GEO_ROWS, 4, Uint32Array );
|
|
774
|
+
const shade = gpuOnlyStorageAttribute( n * TRI_SHADE_ROWS, 4, Uint32Array );
|
|
775
|
+
this.renderer.backend.createStorageAttribute( geo );
|
|
776
|
+
this.renderer.backend.createStorageAttribute( shade );
|
|
777
|
+
return { geo, shade };
|
|
778
|
+
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
/**
|
|
782
|
+
* Writes triangles [start, start + count) of the CPU records into the geo and shade buffers:
|
|
783
|
+
* the first TRI_GEO_ROWS rows of each record to one, the rest to the other.
|
|
784
|
+
* @param {number} start
|
|
785
|
+
* @param {number} count
|
|
786
|
+
* @param {{ geo?: boolean, shade?: boolean, skipChunks?: Set<number>, source?: Uint32Array, sourceStart?: number }} [options]
|
|
787
|
+
* `source` writes those records instead of reading the stage's own (a chunk being handed over).
|
|
788
|
+
* @private
|
|
789
|
+
*/
|
|
790
|
+
_uploadTriangles( start, count, { geo = true, shade = true, skipChunks = null, source = null, sourceStart = 0, geoAttr = this.triangleGeoAttr, shadeAttr = this.triangleShadeAttr } = {} ) {
|
|
791
|
+
|
|
792
|
+
if ( count <= 0 || ! geoAttr ) return;
|
|
793
|
+
|
|
794
|
+
const backend = this.renderer.backend;
|
|
795
|
+
const geoBuffer = backend.get( geoAttr ).buffer;
|
|
796
|
+
const shadeBuffer = backend.get( shadeAttr ).buffer;
|
|
797
|
+
const LANES = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;
|
|
798
|
+
const GEO = TRI_GEO_ROWS * 4, SHADE = TRI_SHADE_ROWS * 4;
|
|
799
|
+
const records = this._triangleRecords;
|
|
800
|
+
const perChunk = source ? count : records ? records.recordsPerChunk : this._triangleRecordCount;
|
|
801
|
+
// One staging pair for every upload: a fresh one per chunk was churn that failed to
|
|
802
|
+
// allocate on a scene already at the edge of the address space.
|
|
803
|
+
const slice = TRIANGLE_UPLOAD_SLICE;
|
|
804
|
+
this._triangleStaging ??= { geo: new Uint32Array( slice * GEO ), shade: new Uint32Array( slice * SHADE ) };
|
|
805
|
+
const geoStage = this._triangleStaging.geo;
|
|
806
|
+
const shadeStage = this._triangleStaging.shade;
|
|
807
|
+
|
|
808
|
+
for ( let at = start, end = start + count; at < end; ) {
|
|
809
|
+
|
|
810
|
+
const k = source ? 0 : Math.floor( at / perChunk );
|
|
811
|
+
const chunkStart = source ? start : k * perChunk;
|
|
812
|
+
const chunkEnd = Math.min( end, chunkStart + perChunk );
|
|
813
|
+
if ( skipChunks?.has( k ) ) {
|
|
814
|
+
|
|
815
|
+
at = chunkEnd;
|
|
816
|
+
continue;
|
|
817
|
+
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const src = source ?? ( records ? records.chunks[ k ] : this._triangleFlat );
|
|
821
|
+
if ( ! src ) throw new Error( `triangle chunk ${k} is on disk and was never uploaded` );
|
|
822
|
+
|
|
823
|
+
const n = Math.min( slice, chunkEnd - at );
|
|
824
|
+
let s = ( source ? at - start + sourceStart : at - chunkStart ) * LANES;
|
|
825
|
+
for ( let i = 0, g = 0, h = 0; i < n; i ++, s += LANES ) {
|
|
826
|
+
|
|
827
|
+
if ( geo ) for ( let l = 0; l < GEO; l ++ ) geoStage[ g ++ ] = src[ s + l ];
|
|
828
|
+
if ( shade ) for ( let l = GEO; l < LANES; l ++ ) shadeStage[ h ++ ] = src[ s + l ];
|
|
829
|
+
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
if ( geo ) backend.device.queue.writeBuffer( geoBuffer, at * GEO * 4, geoStage, 0, n * GEO );
|
|
833
|
+
if ( shade ) backend.device.queue.writeBuffer( shadeBuffer, at * SHADE * 4, shadeStage, 0, n * SHADE );
|
|
834
|
+
at += n;
|
|
835
|
+
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* Allocates the GPU buffer for chunked `records` now and returns a function that writes one
|
|
842
|
+
* chunk into it, so a build can hand chunks over as it finishes them and let them go (the
|
|
843
|
+
* memory spill). setTriangleData / setBVHData then adopt that buffer rather than upload again.
|
|
844
|
+
* @param {import('../Processor/ChunkedRecords.js').ChunkedRecords} records
|
|
845
|
+
* @returns {function(number): void} uploads chunk k from `records.chunks[ k ]`
|
|
846
|
+
*/
|
|
847
|
+
createChunkUploader( records ) {
|
|
848
|
+
|
|
849
|
+
this._assertFitsGPU( records.recordCount * records.lanesPerRecord * records.LaneType.BYTES_PER_ELEMENT );
|
|
850
|
+
const attr = gpuOnlyStorageAttribute( records.recordCount * records.lanesPerRecord / 4, 4, records.LaneType );
|
|
851
|
+
const backend = this.renderer.backend;
|
|
852
|
+
backend.createStorageAttribute( attr );
|
|
853
|
+
const buffer = backend.get( attr ).buffer;
|
|
854
|
+
const stride = records.recordsPerChunk * records.lanesPerRecord * records.LaneType.BYTES_PER_ELEMENT;
|
|
855
|
+
const uploaded = new Set();
|
|
856
|
+
records._gpuUpload = { attr, uploaded };
|
|
857
|
+
|
|
858
|
+
return k => {
|
|
859
|
+
|
|
860
|
+
const chunk = records.chunks[ k ];
|
|
861
|
+
backend.device.queue.writeBuffer( buffer, k * stride, chunk, 0, chunk.length );
|
|
862
|
+
uploaded.add( k );
|
|
863
|
+
|
|
864
|
+
};
|
|
865
|
+
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
/**
|
|
869
|
+
* createChunkUploader for triangle records: both GPU buffers are allocated now and each
|
|
870
|
+
* chunk is split into them as it is handed over.
|
|
871
|
+
* @param {import('../Processor/ChunkedRecords.js').ChunkedRecords} records
|
|
872
|
+
* @returns {function(number): void}
|
|
873
|
+
*/
|
|
874
|
+
createTriangleChunkUploader( records ) {
|
|
875
|
+
|
|
876
|
+
const { geo, shade } = this._allocateTriangleBuffers( records.recordCount );
|
|
877
|
+
const uploaded = new Set();
|
|
878
|
+
records._gpuUpload = { triangles: true, geo, shade, uploaded };
|
|
879
|
+
const perChunk = records.recordsPerChunk;
|
|
880
|
+
|
|
881
|
+
return k => {
|
|
882
|
+
|
|
883
|
+
const first = k * perChunk;
|
|
884
|
+
const count = Math.min( perChunk, records.recordCount - first );
|
|
885
|
+
this._uploadTriangles( first, count, { source: records.chunks[ k ], geoAttr: geo, shadeAttr: shade } );
|
|
886
|
+
uploaded.add( k );
|
|
887
|
+
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
/**
|
|
893
|
+
* One storage buffer holds all of a scene's triangles, and one its BVH. Past the device's
|
|
894
|
+
* limit WebGPU hands back an invalid buffer and every write to it fails quietly.
|
|
895
|
+
* @private
|
|
896
|
+
*/
|
|
897
|
+
_assertFitsGPU( bytes ) {
|
|
898
|
+
|
|
899
|
+
const limits = this.renderer?.backend?.device?.limits;
|
|
900
|
+
const max = Math.min( limits?.maxBufferSize ?? Infinity, limits?.maxStorageBufferBindingSize ?? Infinity );
|
|
901
|
+
if ( bytes > max ) throw new Error( `the scene needs a ${fmt.mb( bytes )} GPU buffer, past the ${fmt.mb( max )} this GPU allows for one — load fewer triangles` );
|
|
902
|
+
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
/** Uploads chunked records, or — for a buffer a build already filled — whatever it has not. @private */
|
|
906
|
+
_uploadChunked( attr, records ) {
|
|
907
|
+
|
|
908
|
+
const pre = records._gpuUpload;
|
|
909
|
+
if ( ! pre || pre.attr !== attr ) {
|
|
910
|
+
|
|
911
|
+
uploadStorageChunks( this.renderer, attr, records.chunks );
|
|
912
|
+
return;
|
|
913
|
+
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
const buffer = this.renderer.backend.get( attr ).buffer;
|
|
917
|
+
const stride = records.recordsPerChunk * records.lanesPerRecord * records.LaneType.BYTES_PER_ELEMENT;
|
|
918
|
+
for ( let k = 0; k < records.chunks.length; k ++ ) {
|
|
919
|
+
|
|
920
|
+
if ( pre.uploaded.has( k ) ) continue;
|
|
921
|
+
const chunk = records.chunks[ k ];
|
|
922
|
+
if ( ! chunk ) throw new Error( `chunk ${k} is on disk and was never uploaded` );
|
|
923
|
+
this.renderer.backend.device.queue.writeBuffer( buffer, k * stride, chunk, 0, chunk.length );
|
|
924
|
+
pre.uploaded.add( k );
|
|
925
|
+
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
}
|
|
929
|
+
|
|
780
930
|
/**
|
|
781
931
|
* Sets the BVH data from raw Float32Array via storage buffer.
|
|
782
932
|
* @param {Float32Array} bvhImageData - Raw BVH data from DataTexture.image.data
|
|
@@ -791,9 +941,10 @@ export class PathTracerStage extends RenderStage {
|
|
|
791
941
|
const flat = chunked ? null : ( bvhImageData.chunks ? bvhImageData.chunks[ 0 ] : bvhImageData );
|
|
792
942
|
const lanes = chunked ? chunked.recordCount * chunked.lanesPerRecord : flat.length;
|
|
793
943
|
const vec4Count = lanes / 4;
|
|
944
|
+
this._assertFitsGPU( lanes * 4 );
|
|
794
945
|
|
|
795
946
|
const makeAttr = () => chunked
|
|
796
|
-
? gpuOnlyStorageAttribute( vec4Count, 4, Float32Array )
|
|
947
|
+
? chunked._gpuUpload?.attr ?? gpuOnlyStorageAttribute( vec4Count, 4, Float32Array )
|
|
797
948
|
: new StorageInstancedBufferAttribute( flat, 4 );
|
|
798
949
|
|
|
799
950
|
if ( this.bvhStorageNode ) {
|
|
@@ -809,8 +960,9 @@ export class PathTracerStage extends RenderStage {
|
|
|
809
960
|
|
|
810
961
|
}
|
|
811
962
|
|
|
963
|
+
this.bvhStorageAttr.foldedLeaves = bvhImageData.foldedLeaves === true;
|
|
812
964
|
this._bvhRecords = chunked;
|
|
813
|
-
if ( chunked )
|
|
965
|
+
if ( chunked ) this._uploadChunked( this.bvhStorageAttr, chunked );
|
|
814
966
|
|
|
815
967
|
this.bvhNodeCount = Math.floor( vec4Count / BVH_VEC4_PER_NODE );
|
|
816
968
|
log.debug( `${fmt.n( this.bvhNodeCount )} BVH nodes (storage buffer)` );
|
|
@@ -1019,17 +1171,12 @@ export class PathTracerStage extends RenderStage {
|
|
|
1019
1171
|
|
|
1020
1172
|
}
|
|
1021
1173
|
|
|
1022
|
-
/**
|
|
1174
|
+
/** Re-uploads every triangle from the CPU records, adopting `triangleData` as them when given. */
|
|
1023
1175
|
updateTriangleData( triangleData ) {
|
|
1024
1176
|
|
|
1025
|
-
if ( this._triangleRecords
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
return;
|
|
1029
|
-
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
this._updateStorageBuffer( this.triangleStorageAttr, triangleData?.chunks ? triangleData.chunks[ 0 ] : triangleData );
|
|
1177
|
+
if ( triangleData?.chunks?.length > 1 ) this._triangleRecords = triangleData;
|
|
1178
|
+
else if ( triangleData ) this._triangleFlat = triangleData.chunks ? triangleData.chunks[ 0 ] : triangleData;
|
|
1179
|
+
this._uploadTriangles( 0, this._triangleRecordCount );
|
|
1033
1180
|
|
|
1034
1181
|
}
|
|
1035
1182
|
|
|
@@ -1056,29 +1203,14 @@ export class PathTracerStage extends RenderStage {
|
|
|
1056
1203
|
*/
|
|
1057
1204
|
updateBufferRanges( triRanges, bvhRanges ) {
|
|
1058
1205
|
|
|
1059
|
-
if ( this.
|
|
1060
|
-
|
|
1061
|
-
if ( this._triangleRecords ) {
|
|
1062
|
-
|
|
1063
|
-
// Chunked: three.js cannot re-upload an attribute with no CPU array, so write
|
|
1064
|
-
// the dirty lanes straight into the GPU buffer.
|
|
1065
|
-
for ( const r of triRanges ) {
|
|
1066
|
-
|
|
1067
|
-
uploadStorageChunkRange( this.renderer, this.triangleStorageAttr, this._triangleRecords, r.offset, r.count );
|
|
1206
|
+
if ( this.triangleGeoAttr && triRanges.length > 0 ) {
|
|
1068
1207
|
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1208
|
+
// Ranges are in lanes of the CPU records; the GPU copy is written a triangle at a time.
|
|
1209
|
+
const lanes = TRIANGLE_DATA_LAYOUT.FLOATS_PER_TRIANGLE;
|
|
1210
|
+
for ( const r of triRanges ) {
|
|
1072
1211
|
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
for ( const r of triRanges ) {
|
|
1076
|
-
|
|
1077
|
-
this.triangleStorageAttr.addUpdateRange( r.offset, r.count );
|
|
1078
|
-
|
|
1079
|
-
}
|
|
1080
|
-
|
|
1081
|
-
this.triangleStorageAttr.version ++;
|
|
1212
|
+
const first = Math.floor( r.offset / lanes );
|
|
1213
|
+
this._uploadTriangles( first, Math.ceil( ( r.offset + r.count ) / lanes ) - first );
|
|
1082
1214
|
|
|
1083
1215
|
}
|
|
1084
1216
|
|
|
@@ -1438,7 +1570,12 @@ export class PathTracerStage extends RenderStage {
|
|
|
1438
1570
|
const LBVH_STRIDE = 4; // vec4s per LBVH node — must match LightBVHSampling.js
|
|
1439
1571
|
const lbvh = this._lbvhDataCache;
|
|
1440
1572
|
const emis = this._emissiveDataCache;
|
|
1441
|
-
|
|
1573
|
+
// The bit-trail map (4 B a triangle) lives only in the light buffer: without a new one, the
|
|
1574
|
+
// current one is carried over from there rather than kept as a second copy.
|
|
1575
|
+
const held = this.lightStorageAttr?.array;
|
|
1576
|
+
const trail = this._bitTrailMapCache
|
|
1577
|
+
?? ( held && this._bitTrailLength ? held.slice( this._bitTrailOffset, this._bitTrailOffset + this._bitTrailLength ) : null );
|
|
1578
|
+
this._bitTrailMapCache = null;
|
|
1442
1579
|
const lbvhLen = lbvh ? lbvh.length : 0;
|
|
1443
1580
|
const emisLen = emis ? emis.length : 0;
|
|
1444
1581
|
// Bit-trail map packs 4 trails per vec4 → pad to a vec4 boundary.
|
|
@@ -1479,6 +1616,8 @@ export class PathTracerStage extends RenderStage {
|
|
|
1479
1616
|
// Offset (in vec4 elements) where the bit-trail map starts (lbvhLen + emisLen are float
|
|
1480
1617
|
// counts, both multiples of 4, so this divides cleanly).
|
|
1481
1618
|
this.reverseMapVec4Offset.value = ( lbvhLen + emisLen ) / 4;
|
|
1619
|
+
this._bitTrailOffset = lbvhLen + emisLen;
|
|
1620
|
+
this._bitTrailLength = trail ? trail.length : 0;
|
|
1482
1621
|
|
|
1483
1622
|
}
|
|
1484
1623
|
|
|
@@ -1548,7 +1687,11 @@ export class PathTracerStage extends RenderStage {
|
|
|
1548
1687
|
log.debug( 'material rebuild started' );
|
|
1549
1688
|
|
|
1550
1689
|
await this.sdfs.rebuildMaterials( scene );
|
|
1551
|
-
|
|
1690
|
+
// Materials and textures only. `sdfs` is not the processor that built the scene
|
|
1691
|
+
// (PathTracerApp._sdf is), so re-uploading everything from it put its empty emissive
|
|
1692
|
+
// data and instance table in place of the scene's: emitters stopped being sampled.
|
|
1693
|
+
this.materialData.setMaterialData( this.sdfs.materialData, this.sdfs.materials?.map( m => m.sources ) );
|
|
1694
|
+
this.materialData.loadTexturesFromSdfs();
|
|
1552
1695
|
this.shaderBuilder.updateSceneTextures( this );
|
|
1553
1696
|
this.updateLights();
|
|
1554
1697
|
this.reset();
|
|
@@ -1607,8 +1750,12 @@ export class PathTracerStage extends RenderStage {
|
|
|
1607
1750
|
this.placeholderTexture?.dispose();
|
|
1608
1751
|
|
|
1609
1752
|
// Clear data references
|
|
1610
|
-
this.
|
|
1753
|
+
this.triangleGeoAttr = null;
|
|
1754
|
+
this.triangleShadeAttr = null;
|
|
1755
|
+
this._triangleStaging = null;
|
|
1611
1756
|
this._triangleRecords = null;
|
|
1757
|
+
this._triangleFlat = null;
|
|
1758
|
+
this._triangleRecordCount = 0;
|
|
1612
1759
|
this.triangleStorageNode = null;
|
|
1613
1760
|
this.bvhStorageAttr = null;
|
|
1614
1761
|
this._bvhRecords = null;
|