rayzee 9.5.0 → 11.0.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.
Files changed (78) hide show
  1. package/README.md +22 -20
  2. package/dist/{ArchiveImporter-xuUJiklP.js → ArchiveImporter-BRQvLggb.js} +12 -13
  3. package/dist/{BidirectionalIntegrator-C5PKjJoT.js → BidirectionalIntegrator-9lZQxFVH.js} +87 -87
  4. package/dist/BufferLayout-D_IBo2ci.js +150 -0
  5. package/dist/{ColorManagement-CheBCPaW.js → ColorManagement-oeEk7oSu.js} +171 -171
  6. package/dist/{NeuralRetouch-CW11yLZ_.js → NeuralRetouch-BCXylThH.js} +3 -3
  7. package/dist/{NeuralSuperRes-BekaecYg.js → NeuralSuperRes-sZAeS142.js} +3 -3
  8. package/dist/{PathTracer-Ccl9KOuF.js → PathTracer-CQPW06Mm.js} +2911 -2669
  9. package/dist/{PhysicalSky-Dv2PMFGW.js → PhysicalSky-BvqCw_dy.js} +2 -2
  10. package/dist/{RayzeeRenderer-BQxnzCTD.js → RayzeeRenderer-COpwotpU.js} +338 -329
  11. package/dist/{SolarLimb-ken-JbDK.js → SolarLimb-CPaeZVwz.js} +10 -10
  12. package/dist/{ToneMapGPU-BHi5JzIq.js → ToneMapGPU-CjU3osDd.js} +1 -1
  13. package/dist/{ViewTransforms-D0rTLBsX.js → ViewTransforms-CYN7bq2w.js} +2 -2
  14. package/dist/addons/archives.es.js +1 -1
  15. package/dist/addons/bidirectional.es.js +1 -1
  16. package/dist/addons/color.es.js +1 -1
  17. package/dist/addons/physical-sky.es.js +1 -1
  18. package/dist/rayzee-core.es.js +11 -11
  19. package/dist/rayzee.es.js +734 -599
  20. package/dist/rayzee.umd.js +102 -102
  21. package/dist/sceneCachePolicy-BSSsKY98.js +443 -0
  22. package/package.json +1 -1
  23. package/src/Color/ViewTransforms.js +5 -1
  24. package/src/EngineDefaults.js +59 -1032
  25. package/src/Headless.js +6 -5
  26. package/src/Passes/OIDNTemporalHistory.js +1 -1
  27. package/src/PathTracerApp.js +5 -3
  28. package/src/Processor/AssetLoader.js +6 -5
  29. package/src/Processor/BVHBuilder.js +10 -31
  30. package/src/Processor/BVHLeafFold.js +4 -6
  31. package/src/Processor/BVHRefitter.js +3 -46
  32. package/src/Processor/BufferLayout.js +291 -0
  33. package/src/Processor/EmissiveTriangleBuilder.js +1 -1
  34. package/src/Processor/GeometryExtractor.js +3 -3
  35. package/src/Processor/HostMemory.js +7 -1
  36. package/src/Processor/InstanceTable.js +1 -1
  37. package/src/Processor/MaterialPacking.js +2 -1
  38. package/src/Processor/PBRT/PBRTSceneBuilder.js +1 -1
  39. package/src/Processor/QueueManager.js +28 -3
  40. package/src/Processor/SceneProcessor.js +4 -2
  41. package/src/Processor/SplitBLAS.js +1 -1
  42. package/src/Processor/StorageTexturePool.js +22 -1
  43. package/src/Processor/TLASBuilder.js +1 -3
  44. package/src/Processor/TextureBuckets.js +237 -0
  45. package/src/Processor/TextureCreator.js +10 -1
  46. package/src/Processor/Workers/TexturesWorker.js +5 -6
  47. package/src/RayzeeRenderer.js +29 -32
  48. package/src/RenderSettings.js +26 -28
  49. package/src/Stages/ASVGF.js +8 -7
  50. package/src/Stages/AutoExposure.js +14 -5
  51. package/src/Stages/BilateralFilter.js +2 -1
  52. package/src/Stages/DenoiserSettings.js +182 -0
  53. package/src/Stages/EdgeFilter.js +2 -1
  54. package/src/Stages/MotionVector.js +1 -1
  55. package/src/Stages/NRD.js +2 -3
  56. package/src/Stages/NormalDepth.js +3 -2
  57. package/src/Stages/PathTracer.js +72 -4
  58. package/src/Stages/PathTracerStage.js +2 -1
  59. package/src/Stages/Variance.js +1 -1
  60. package/src/TSL/BVHTraversal.js +4 -1
  61. package/src/TSL/Common.js +1 -1
  62. package/src/TSL/EmissiveSampling.js +1 -1
  63. package/src/TSL/LightVisibility.js +111 -0
  64. package/src/TSL/LightsSampling.js +151 -15
  65. package/src/TSL/Random.js +1 -1
  66. package/src/TSL/ShadeKernel.js +88 -3
  67. package/src/TSL/TextureSampling.js +1 -1
  68. package/src/core.js +5 -9
  69. package/src/index.js +10 -21
  70. package/src/integrators/BidirectionalIntegrator.js +2 -1
  71. package/src/managers/CameraManager.js +17 -7
  72. package/src/managers/DenoisingManager.js +14 -10
  73. package/src/managers/EnvironmentManager.js +32 -13
  74. package/src/managers/MaterialDataManager.js +3 -3
  75. package/src/managers/UniformManager.js +2 -2
  76. package/dist/EngineDefaults-DH3bI5t-.js +0 -844
  77. package/dist/sceneCachePolicy-BmwYOYDu.js +0 -183
  78. /package/dist/{Logger-BnHghprG.js → Logger-dDDGj9Av.js} +0 -0
@@ -39,6 +39,12 @@ export const COUNTER = {
39
39
  /** The light guide's counts: 16 direction bins × 64 × 64 disc cells (TSL/LightGuide.js). */
40
40
  export const GUIDE_COUNTER_WORDS = 16 * 64 * 64;
41
41
 
42
+ /** Learned light visibility (TSL/LightVisibility.js): tries and visible per cell and light kind, learned then fresh. */
43
+ export const VISIBILITY_BASE = COUNTER.GUIDE + GUIDE_COUNTER_WORDS;
44
+ export const VISIBILITY_CELLS = 1 << 17;
45
+ export const VISIBILITY_KINDS = 4;
46
+ export const VISIBILITY_COUNTER_WORDS = VISIBILITY_CELLS * VISIBILITY_KINDS * 2;
47
+
42
48
  export const ENERGY_SCALE = 64;
43
49
  export const ENERGY_RAY_CLAMP = 65535;
44
50
 
@@ -72,6 +78,7 @@ export class QueueManager {
72
78
  this._renderer = renderer;
73
79
  this.capacity = 0;
74
80
  this.counters = null;
81
+ this._visibilityTable = false;
75
82
  // A/B alternate: one read by current bounce, other written by compaction
76
83
  this.activeIndices = null;
77
84
  this.activeIndicesRO = null;
@@ -94,9 +101,7 @@ export class QueueManager {
94
101
  this.dispose();
95
102
  this.capacity = capacity;
96
103
 
97
- // explicit attribute (not attributeArray) so it can be referenced for async readback
98
- this._countersAttr = new StorageInstancedBufferAttribute( new Uint32Array( COUNTER.GUIDE + GUIDE_COUNTER_WORDS ), 1 );
99
- this.counters = storage( this._countersAttr, 'uint' ).toAtomic();
104
+ this._allocateCounters();
100
105
 
101
106
  // per-bounce snapshots for the async readback: [0, MAX) ACTIVE_RAY_COUNT, [MAX, 2·MAX) ACTIVE_ENERGY,
102
107
  // then the same pair for the bidirectional light pass
@@ -144,6 +149,26 @@ export class QueueManager {
144
149
 
145
150
  }
146
151
 
152
+ _allocateCounters() {
153
+
154
+ // explicit attribute (not attributeArray) so it can be referenced for async readback
155
+ const words = VISIBILITY_BASE + ( this._visibilityTable ? 2 * VISIBILITY_COUNTER_WORDS : 0 );
156
+ this._countersAttr = new StorageInstancedBufferAttribute( new Uint32Array( words ), 1 );
157
+ this.counters = storage( this._countersAttr, 'uint' ).toAtomic();
158
+
159
+ }
160
+
161
+ /** Room for the learned light visibility in the counter buffer; kernels bound to the old buffer must rebuild. */
162
+ setVisibilityTable( enabled ) {
163
+
164
+ if ( this._visibilityTable === !! enabled ) return;
165
+ this._visibilityTable = !! enabled;
166
+ if ( ! this._countersAttr ) return;
167
+ freeStorageAttribute( this._renderer, this._countersAttr );
168
+ this._allocateCounters();
169
+
170
+ }
171
+
147
172
  // returns true if reallocation occurred
148
173
  resize( capacity ) {
149
174
 
@@ -17,8 +17,10 @@ import { BuildTimer } from './BuildTimer.js';
17
17
  import { createLogger, fmt, workerLogLevel } from '../utils/Logger.js';
18
18
  import { SRGBColorSpace } from 'three';
19
19
  import {
20
- TRIANGLE_DATA_LAYOUT, TEXTURE_CONSTANTS, getTextureBucketId, packTextureIndex, planTextureBuckets,
21
- packNormalOct, BVH_LEAF_MARKERS, BVH_FOLDED_FIRST_LIMIT, assertBVHIndexFits, TLAS_PLACEMENT_MASK, TRI_MATERIAL_MASK } from '../EngineDefaults.js';
20
+ TRIANGLE_DATA_LAYOUT, packNormalOct, TRI_MATERIAL_MASK, BVH_LEAF_MARKERS, BVH_FOLDED_FIRST_LIMIT,
21
+ assertBVHIndexFits, TLAS_PLACEMENT_MASK,
22
+ } from './BufferLayout.js';
23
+ import { TEXTURE_CONSTANTS, getTextureBucketId, packTextureIndex, planTextureBuckets } from './TextureBuckets.js';
22
24
  import { ISSUE_CODES } from '../EngineIssues.js';
23
25
  import { BLAS_CACHE_FORMAT, openBLASCache, saveBLASCache, templateChecksums, readNodesInto, readOrder } from '../Storage/BLASCache.js';
24
26
  import { sharedStorage } from '../Storage/shared.js';
@@ -1,4 +1,4 @@
1
- import { TRIANGLE_DATA_LAYOUT } from '../EngineDefaults.js';
1
+ import { TRIANGLE_DATA_LAYOUT } from './BufferLayout.js';
2
2
  import { rebaseNodes } from './BVHLeafFold.js';
3
3
 
4
4
  const FLOATS_PER_NODE = 16;
@@ -14,9 +14,30 @@
14
14
 
15
15
  import { StorageTexture, RenderTarget } from 'three/webgpu';
16
16
  import { RGBAFormat, FloatType, LinearFilter, NearestFilter, Box2, Vector2 } from 'three';
17
- import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
18
17
  import { createLogger, fmt } from '../utils/Logger.js';
19
18
 
19
+ // Hard ceiling the engine supports for the reserved (pre-allocated) render size. 4K (3840×2160)
20
+ // fits within 4096². Raising the reserved size to this pins ~1.5 GB of MRT textures — opt-in only.
21
+ export const MAX_RESERVABLE_RENDER_SIZE = 4096;
22
+
23
+ // Reserved render size: every per-resolution compute StorageTexture + aux buffer is pre-allocated at
24
+ // this SQUARE size and never resized at runtime (works around three.js StorageTexture-resize bugs —
25
+ // see TSL/patches history). Render resolution must not exceed it; the engine warns + ignores larger.
26
+ // It is a LIVE binding (mutable) so it can be raised (e.g. for 4K) via setReservedRenderSize(); consumers
27
+ // read it inside their constructors, and stages already built at a lower value are re-initialised in place
28
+ // by PathTracerApp.setReservedRenderResolution() — which is the device-gated API hosts should call, at any
29
+ // point in the lifecycle. Default 2048 (zero VRAM regression). See docs/internal/specs/wavefront-chunked-pool.md.
30
+ export let MAX_STORAGE_TEXTURE_SIZE = 2048;
31
+
32
+ // Set the reserved render size. MUST be called before pipeline construction (stages pre-allocate at
33
+ // this value and cannot resize). Clamped to [256, MAX_RESERVABLE_RENDER_SIZE]. Returns the applied value.
34
+ export function setReservedRenderSize( px ) {
35
+
36
+ MAX_STORAGE_TEXTURE_SIZE = Math.max( 256, Math.min( MAX_RESERVABLE_RENDER_SIZE, Math.floor( px ) || 256 ) );
37
+ return MAX_STORAGE_TEXTURE_SIZE;
38
+
39
+ }
40
+
20
41
  const log = createLogger( 'gpu' );
21
42
 
22
43
  // Compute-output StorageTexture at the reserved size, never resized (see above). LinearFilter is
@@ -15,9 +15,7 @@
15
15
  * known before its subtree is built, which is what lets the write be single-pass.
16
16
  */
17
17
 
18
- import {
19
- BVH_LEAF_MARKERS, assertBVHIndexFits, bvhIndexView, TLAS_LEAF_IDENTITY, TLAS_PLACEMENT_MASK
20
- } from '../EngineDefaults.js';
18
+ import { BVH_LEAF_MARKERS, assertBVHIndexFits, bvhIndexView, TLAS_LEAF_IDENTITY, TLAS_PLACEMENT_MASK } from './BufferLayout.js';
21
19
  import { invertAffineInto, isIdentityAt } from './InstanceTable.js';
22
20
 
23
21
  const FLOATS_PER_NODE = 16;
@@ -0,0 +1,237 @@
1
+ // Texture processing constants
2
+ export const TEXTURE_CONSTANTS = {
3
+ VEC4_PER_BVH_NODE: 4,
4
+ FLOATS_PER_VEC4: 4,
5
+ MIN_TEXTURE_WIDTH: 4,
6
+ MAX_CONCURRENT_WORKERS: Math.min( typeof navigator !== 'undefined' ? navigator.hardwareConcurrency || 4 : 4, 6 ),
7
+ BUFFER_POOL_SIZE: 20,
8
+ CANVAS_POOL_SIZE: 12,
9
+ CACHE_SIZE_LIMIT: 50,
10
+ // Hardware ceiling for a single texture-array dimension (WebGPU maxTextureDimension2D
11
+ // guaranteed minimum). The configurable maxTextureSize setting is clamped to this.
12
+ MAX_TEXTURE_SIZE: 8192,
13
+ // Default cap applied when no maxTextureSize is supplied (engine standalone use).
14
+ DEFAULT_MAX_TEXTURE_SIZE: 4096,
15
+ // Per-map-type ceiling before bucketing; the real cap is per (pool, bucket) in _bucketTextures.
16
+ MAX_TEXTURES_LIMIT: 4 * 256,
17
+ // Array shapes per colorSpace pool. Material maps are grouped into this many
18
+ // (width, height) classes so a small or oddly-shaped map no longer pays a large
19
+ // neighbour's footprint. 4 → ~8 bound material arrays (4 sRGB + 4 linear).
20
+ MATERIAL_BUCKET_COUNT: 4,
21
+ // Packing stride: a map's stored index encodes bucketId * BUCKET_LAYER_STRIDE + layer.
22
+ // Also the per-bucket layer cap. Kept at the WebGPU portable maxTextureArrayLayers floor (256)
23
+ // so a consolidated bucket (which merges several map types of one size) stays portable.
24
+ BUCKET_LAYER_STRIDE: 256,
25
+ };
26
+
27
+ // An RGBA8 row upload must be a multiple of 256 bytes, so bucket widths land on 64 texels.
28
+ const BUCKET_WIDTH_ALIGN = 64;
29
+
30
+ export function alignBucketWidth( width, maxTextureSize ) {
31
+
32
+ const aligned = Math.ceil( Math.max( 1, width ) / BUCKET_WIDTH_ALIGN ) * BUCKET_WIDTH_ALIGN;
33
+ return Math.min( maxTextureSize, Math.max( TEXTURE_CONSTANTS.MIN_TEXTURE_WIDTH, aligned ) );
34
+
35
+ }
36
+
37
+ function bucketOf( units ) {
38
+
39
+ let width = 0, height = 0, count = 0;
40
+ for ( const unit of units ) {
41
+
42
+ width = Math.max( width, unit.width );
43
+ height = Math.max( height, unit.height );
44
+ count += unit.count;
45
+
46
+ }
47
+
48
+ return { units, width, height, count };
49
+
50
+ }
51
+
52
+ // Footprint times layer count.
53
+ function bucketCost( bucket, maxTextureSize ) {
54
+
55
+ return alignBucketWidth( bucket.width, maxTextureSize ) * bucket.height * bucket.count * 4;
56
+
57
+ }
58
+
59
+ function totalCost( buckets, maxTextureSize ) {
60
+
61
+ return buckets.reduce( ( sum, b ) => sum + bucketCost( b, maxTextureSize ), 0 );
62
+
63
+ }
64
+
65
+ // Repeatedly fuse the pair of buckets whose union wastes least, until `count` remain.
66
+ function mergeDown( buckets, count, maxTextureSize ) {
67
+
68
+ let current = buckets.slice();
69
+
70
+ while ( current.length > count ) {
71
+
72
+ let best = null;
73
+
74
+ for ( let i = 0; i < current.length; i ++ ) {
75
+
76
+ for ( let j = i + 1; j < current.length; j ++ ) {
77
+
78
+ const fused = bucketOf( current[ i ].units.concat( current[ j ].units ) );
79
+ const delta = bucketCost( fused, maxTextureSize )
80
+ - bucketCost( current[ i ], maxTextureSize ) - bucketCost( current[ j ], maxTextureSize );
81
+ if ( ! best || delta < best.delta ) best = { i, j, delta, fused };
82
+
83
+ }
84
+
85
+ }
86
+
87
+ current = current.filter( ( _, k ) => k !== best.i && k !== best.j ).concat( [ best.fused ] );
88
+
89
+ }
90
+
91
+ return current;
92
+
93
+ }
94
+
95
+ // The pre-shape-aware layout, kept as a rival opening: square scenes still do well on it.
96
+ function ladderBuckets( units, count, maxTextureSize ) {
97
+
98
+ const ladder = [];
99
+ for ( let i = count - 1; i >= 0; i -- ) {
100
+
101
+ ladder.push( Math.max( TEXTURE_CONSTANTS.MIN_TEXTURE_WIDTH, Math.round( maxTextureSize / Math.pow( 2, i ) ) ) );
102
+
103
+ }
104
+
105
+ const bins = ladder.map( () => [] );
106
+
107
+ for ( const unit of units ) {
108
+
109
+ const longest = Math.pow( 2, Math.ceil( Math.log2( Math.max( unit.width, unit.height ) ) ) );
110
+ let slot = ladder.findIndex( size => longest <= size );
111
+ if ( slot < 0 ) slot = ladder.length - 1;
112
+ bins[ slot ].push( unit );
113
+
114
+ }
115
+
116
+ return bins.filter( bin => bin.length ).map( bucketOf );
117
+
118
+ }
119
+
120
+ // Both openings above are greedy and stop short on mixed aspects; moving one shape at a time
121
+ // to whichever bucket makes the pool cheapest recovers the difference.
122
+ function refine( buckets, maxTextureSize ) {
123
+
124
+ let current = buckets;
125
+
126
+ for ( let pass = 0; pass < 8; pass ++ ) {
127
+
128
+ let improved = false;
129
+
130
+ for ( const unit of current.flatMap( b => b.units ) ) {
131
+
132
+ const from = current.findIndex( b => b.units.includes( unit ) );
133
+ if ( from < 0 ) continue;
134
+
135
+ for ( let to = 0; to < current.length; to ++ ) {
136
+
137
+ if ( to === from ) continue;
138
+
139
+ const moved = [];
140
+ for ( let i = 0; i < current.length; i ++ ) {
141
+
142
+ if ( i === from ) {
143
+
144
+ const rest = current[ i ].units.filter( u => u !== unit );
145
+ if ( rest.length ) moved.push( bucketOf( rest ) );
146
+
147
+ } else if ( i === to ) moved.push( bucketOf( current[ i ].units.concat( [ unit ] ) ) );
148
+ else moved.push( current[ i ] );
149
+
150
+ }
151
+
152
+ if ( totalCost( moved, maxTextureSize ) < totalCost( current, maxTextureSize ) ) {
153
+
154
+ current = moved;
155
+ improved = true;
156
+ break;
157
+
158
+ }
159
+
160
+ }
161
+
162
+ }
163
+
164
+ if ( ! improved ) break;
165
+
166
+ }
167
+
168
+ return current;
169
+
170
+ }
171
+
172
+ /**
173
+ * Choose up to `count` array shapes for one colorSpace pool. Grouping by longest edge alone
174
+ * forces a 2000x453 banner into a 2048x2048 array; grouping on both axes does not.
175
+ *
176
+ * @param {Array<{width: number, height: number}>} sizes - every texture in the pool
177
+ * @param {number} maxTextureSize
178
+ * @param {number} [count]
179
+ * @returns {Array<{width: number, height: number}>} ascending by footprint
180
+ */
181
+ export function planTextureBuckets( sizes, maxTextureSize, count = TEXTURE_CONSTANTS.MATERIAL_BUCKET_COUNT ) {
182
+
183
+ const distinct = new Map();
184
+
185
+ for ( const { width, height } of sizes ) {
186
+
187
+ const w = Math.min( maxTextureSize, Math.max( 1, width || 1 ) );
188
+ const h = Math.min( maxTextureSize, Math.max( 1, height || 1 ) );
189
+ const key = `${w}x${h}`;
190
+ const seen = distinct.get( key );
191
+ if ( seen ) seen.count ++;
192
+ else distinct.set( key, { width: w, height: h, count: 1 } );
193
+
194
+ }
195
+
196
+ const units = [ ...distinct.values() ];
197
+ let buckets = units.map( unit => bucketOf( [ unit ] ) );
198
+
199
+ if ( buckets.length > count ) {
200
+
201
+ const ladder = ladderBuckets( units, count, maxTextureSize );
202
+ const merged = mergeDown( buckets, count, maxTextureSize );
203
+ buckets = totalCost( ladder, maxTextureSize ) < totalCost( merged, maxTextureSize ) ? ladder : merged;
204
+ buckets = refine( buckets, maxTextureSize );
205
+
206
+ }
207
+
208
+ return buckets
209
+ .map( b => ( { width: alignBucketWidth( b.width, maxTextureSize ), height: b.height } ) )
210
+ .sort( ( a, b ) => a.width * a.height - b.width * b.height );
211
+
212
+ }
213
+
214
+ /**
215
+ * Index of the cheapest planned bucket that can hold a texture at its native size.
216
+ * A texture larger than every bucket lands in the largest and is downscaled there.
217
+ *
218
+ * @param {number} width
219
+ * @param {number} height
220
+ * @param {Array<{width: number, height: number}>} shapes - from {@link planTextureBuckets}
221
+ * @returns {number}
222
+ */
223
+ export function getTextureBucketId( width, height, shapes ) {
224
+
225
+ const w = Math.max( 1, width || 1 );
226
+ const h = Math.max( 1, height || 1 );
227
+ for ( let i = 0; i < shapes.length; i ++ ) if ( shapes[ i ].width >= w && shapes[ i ].height >= h ) return i;
228
+ return Math.max( 0, shapes.length - 1 );
229
+
230
+ }
231
+
232
+ // Pack (bucketId, layer) into the single int slot a material map index occupies.
233
+ export function packTextureIndex( bucketId, layer ) {
234
+
235
+ return bucketId * TEXTURE_CONSTANTS.BUCKET_LAYER_STRIDE + layer;
236
+
237
+ }
@@ -1,5 +1,6 @@
1
1
  import { DataArrayTexture, RGBAFormat, LinearFilter, UnsignedByteType, SRGBColorSpace, LinearSRGBColorSpace, RepeatWrapping, FloatType, HalfFloatType } from "three";
2
- import { alignBucketWidth, TEXTURE_CONSTANTS, MEMORY_CONSTANTS, MATERIAL_DATA_LAYOUT, BVH_LEAF_MARKERS, assertBVHIndexFits, bvhIndexView } from '../EngineDefaults.js';
2
+ import { MATERIAL_DATA_LAYOUT, BVH_LEAF_MARKERS, assertBVHIndexFits, bvhIndexView } from './BufferLayout.js';
3
+ import { alignBucketWidth, TEXTURE_CONSTANTS } from './TextureBuckets.js';
3
4
  import { packMaterial } from './MaterialPacking.js';
4
5
  import TexturesWorker from './Workers/TexturesWorker.js?worker&inline';
5
6
  import PackWorker from './Workers/PackWorker.js?worker&inline';
@@ -12,6 +13,14 @@ import { createWorker, hardwareThreads, hasWorkers } from '../Platform.js';
12
13
 
13
14
  const log = createLogger( 'textures' );
14
15
 
16
+ // Memory management constants
17
+ export const MEMORY_CONSTANTS = {
18
+ MAX_BUFFER_MEMORY: 1024 * 1024 * 1024,
19
+ MAX_TEXTURE_MEMORY: 2048 * 1024 * 1024,
20
+ CLEANUP_THRESHOLD: 0.8,
21
+ STREAM_BATCH_SIZE: 4
22
+ };
23
+
15
24
  // Below this a pack is cheaper than starting a worker for it.
16
25
  const WORKER_PACK_BYTES = 8 * 2 ** 20;
17
26
 
@@ -1,8 +1,9 @@
1
+ import { TEXTURE_CONSTANTS, alignBucketWidth } from '../TextureBuckets.js';
2
+
1
3
  let canvas, ctx;
2
4
 
3
5
  // Memory limits and chunking configuration
4
6
  const MEMORY_LIMITS = {
5
- MAX_TEXTURE_DIMENSION: 8192, // Hardware ceiling (WebGPU maxTextureDimension2D guaranteed min); the per-scene maxTextureSize setting is the actual knob
6
7
  CHUNK_SIZE: 8, // Optimized: Process textures in chunks of 8 for better memory locality
7
8
  ADAPTIVE_CHUNK_SIZE: true, // Enable adaptive chunk sizing based on texture dimensions
8
9
  MEMORY_SAFETY_FACTOR: 0.8 // Use only 80% of estimated available memory
@@ -136,7 +137,7 @@ self.onmessage = async function ( e ) {
136
137
  function initializeWorker( maxTextureSize ) {
137
138
 
138
139
  // Initialize OffscreenCanvas with optimal settings
139
- const size = Math.min( maxTextureSize, MEMORY_LIMITS.MAX_TEXTURE_DIMENSION );
140
+ const size = Math.min( maxTextureSize, TEXTURE_CONSTANTS.MAX_TEXTURE_SIZE );
140
141
  canvas = new OffscreenCanvas( size, size );
141
142
  ctx = canvas.getContext( '2d', {
142
143
  willReadFrequently: true,
@@ -706,7 +707,7 @@ function calculateOptimalDimensions( textures, maxTextureSize ) {
706
707
  }
707
708
 
708
709
  // Scale down proportionally only when the cap is exceeded; native res is kept below it.
709
- const cap = Math.min( maxTextureSize, MEMORY_LIMITS.MAX_TEXTURE_DIMENSION );
710
+ const cap = Math.min( maxTextureSize, TEXTURE_CONSTANTS.MAX_TEXTURE_SIZE );
710
711
  const longest = Math.max( maxWidth, maxHeight );
711
712
  if ( longest > cap ) {
712
713
 
@@ -716,9 +717,7 @@ function calculateOptimalDimensions( textures, maxTextureSize ) {
716
717
 
717
718
  }
718
719
 
719
- // An RGBA8 row upload must be a multiple of 256 bytes, so widths land on 64 texels.
720
- // Mirrors alignBucketWidth in EngineDefaults (not importable from worker context).
721
- maxWidth = Math.min( cap, Math.max( 4, Math.ceil( Math.max( 1, maxWidth ) / 64 ) * 64 ) );
720
+ maxWidth = alignBucketWidth( maxWidth, cap );
722
721
 
723
722
  return { maxWidth, maxHeight: Math.max( 1, maxHeight ) };
724
723
 
@@ -9,7 +9,9 @@ import { PathTracer } from './Stages/PathTracer.js';
9
9
  import { Compositor } from './Stages/Compositor.js';
10
10
  import { RenderPipeline } from './Pipeline/RenderPipeline.js';
11
11
  import { CompletionTracker } from './Pipeline/CompletionTracker.js';
12
- import { ENGINE_DEFAULTS as DEFAULT_STATE, TRIANGLE_DATA_LAYOUT, BVH_LEAF_MARKERS, MAX_STORAGE_TEXTURE_SIZE, MAX_RESERVABLE_RENDER_SIZE, setReservedRenderSize, getRenderProfile, SPILL_TRIANGLE_BUDGET, SPILL_PLACEMENT_BUDGET } from './EngineDefaults.js';
12
+ import { ENGINE_DEFAULTS as DEFAULT_STATE } from './EngineDefaults.js';
13
+ import { TRIANGLE_DATA_LAYOUT, BVH_LEAF_MARKERS } from './Processor/BufferLayout.js';
14
+ import { MAX_STORAGE_TEXTURE_SIZE, MAX_RESERVABLE_RENDER_SIZE, setReservedRenderSize } from './Processor/StorageTexturePool.js';
13
15
  import { updateStats, updateLoading, resetLoading, setStatusCallback, getDisplaySamples, disposeObjectFromMemory, disposeRenderer } from './Processor/utils.js';
14
16
  import { BuildTimer } from './Processor/BuildTimer.js';
15
17
  import { TextureReadback } from './Processor/TextureReadback.js';
@@ -24,10 +26,10 @@ import { toneMapToRGBA8 } from './Processor/ToneMapCPU.js';
24
26
  import { PackedToneMapper } from './Processor/ToneMapGPU.js';
25
27
  import { BasicColor } from './Color/BasicColor.js';
26
28
  import { setActiveColorManagement } from './Color/ActiveColor.js';
27
- import { getViewTransform } from './Color/ViewTransforms.js';
29
+ import { getViewTransform, DEFAULT_VIEW } from './Color/ViewTransforms.js';
28
30
  import { AssetLoader } from './Processor/AssetLoader.js';
29
31
  import { SceneProcessor } from './Processor/SceneProcessor.js';
30
- import { deviceMemoryGB } from './Processor/HostMemory.js';
32
+ import { deviceMemoryGB, SPILL_TRIANGLE_BUDGET, SPILL_PLACEMENT_BUDGET } from './Processor/HostMemory.js';
31
33
  import { createHeadlessCanvas } from './HeadlessCanvas.js';
32
34
 
33
35
  // Managers
@@ -154,8 +156,6 @@ export class RayzeeRenderer extends EventDispatcher {
154
156
  * with renderFrames() or renderUntilComplete()
155
157
  * @param {boolean} [options.strict=false] - Throw at the point of degradation instead of
156
158
  * rendering a plausible wrong image. See EngineIssues.js; read `app.issues` when off.
157
- * @param {string} [options.profile='viewer'] - Which tuning to apply where the viewer's
158
- * product decisions differ from the physical answer. See RENDER_PROFILES.
159
159
  * @param {number} [options.maxSceneBytes] - refuse a scene whose estimated host memory is
160
160
  * above this. The default refuses where the renderer process would be killed instead of
161
161
  * throwing; raise it deliberately, on a fresh browser. See HostMemory.js.
@@ -206,9 +206,12 @@ export class RayzeeRenderer extends EventDispatcher {
206
206
  this._unlitRestore = null;
207
207
  this._applyingSceneMetadata = false;
208
208
 
209
- // Before the settings: the profile supplies some of their defaults.
210
- this._profile = getRenderProfile( options.profile );
211
- this._profileName = options.profile ?? 'viewer';
209
+ if ( options.profile !== undefined ) {
210
+
211
+ throw new Error( 'the `profile` option was removed: the engine has one set of defaults. For the old \'physical\' '
212
+ + 'profile set `areaLightIntensityScale: 1` and `dofMode: \'physical\'` through `settings`.' );
213
+
214
+ }
212
215
 
213
216
  // First, so no subsystem can degrade unrecorded.
214
217
  this._issues = new IssueLog( {
@@ -224,15 +227,7 @@ export class RayzeeRenderer extends EventDispatcher {
224
227
  setActiveColorManagement( this.color );
225
228
 
226
229
  // ── Settings (single source of truth for all render parameters) ──
227
- this.settings = new RenderSettings(
228
- {
229
- ...DEFAULT_STATE,
230
- environmentRotation: this._profile.environmentRotation,
231
- saturation: this._profile.saturation,
232
- dofMode: this._profile.dofMode,
233
- },
234
- { issues: this._issues }
235
- );
230
+ this.settings = new RenderSettings( DEFAULT_STATE, { issues: this._issues } );
236
231
 
237
232
  // ── Core objects (populated in init) ──
238
233
  this.renderer = null;
@@ -243,8 +238,6 @@ export class RayzeeRenderer extends EventDispatcher {
243
238
  // ── Asset pipeline ──
244
239
  this.assetLoader = null;
245
240
  this._sdf = null;
246
- // Max material-texture dimension (longest edge); applied on each scene build.
247
- this._maxTextureSize = DEFAULT_STATE.maxTextureSize;
248
241
 
249
242
  // ── Pipeline & stages ──
250
243
  this.pipeline = null;
@@ -1096,24 +1089,24 @@ export class RayzeeRenderer extends EventDispatcher {
1096
1089
  */
1097
1090
  async setMaxTextureSize( size, { reprocess = true } = {} ) {
1098
1091
 
1099
- const prev = this._maxTextureSize;
1100
- const clamped = this._sdf?.setMaxTextureSize( size );
1101
- this._maxTextureSize = clamped ?? size;
1092
+ const prev = this.settings.get( 'maxTextureSize' );
1093
+ const applied = this._sdf?.setMaxTextureSize( size ) ?? size;
1094
+ this.settings.set( 'maxTextureSize', applied, { reset: false } );
1102
1095
  if ( typeof this.stages?.pathTracer?.sdfs?.setMaxTextureSize === 'function' ) {
1103
1096
 
1104
- this.stages.pathTracer.sdfs.setMaxTextureSize( this._maxTextureSize );
1097
+ this.stages.pathTracer.sdfs.setMaxTextureSize( applied );
1105
1098
 
1106
1099
  }
1107
1100
 
1108
1101
  // Reprocess the loaded scene so the new cap takes effect immediately.
1109
- if ( reprocess && this._maxTextureSize !== prev && this._sdf?.triangles && ! this._loadingInProgress ) {
1102
+ if ( reprocess && applied !== prev && this._sdf?.triangles && ! this._loadingInProgress ) {
1110
1103
 
1111
1104
  this._loadingInProgress = true;
1112
1105
  try {
1113
1106
 
1114
1107
  await this.loadSceneData();
1115
1108
  this.reset();
1116
- this.dispatchEvent( { type: EngineEvents.TEXTURES_REPROCESSED, maxTextureSize: this._maxTextureSize } );
1109
+ this.dispatchEvent( { type: EngineEvents.TEXTURES_REPROCESSED, maxTextureSize: applied } );
1117
1110
 
1118
1111
  } finally {
1119
1112
 
@@ -1411,7 +1404,7 @@ export class RayzeeRenderer extends EventDispatcher {
1411
1404
 
1412
1405
  // Build BVH
1413
1406
  timer.start( 'BVH build (SceneProcessor)' );
1414
- this._sdf.setMaxTextureSize( this._maxTextureSize );
1407
+ this._sdf.setMaxTextureSize( this.settings.get( 'maxTextureSize' ) );
1415
1408
  await this._sdf.buildBVH( this.meshScene, { sceneKey: this.assetLoader?.sceneSourceKey ?? null, progressive: this._progressiveSpill() } );
1416
1409
  this.assetLoader?.flushPendingGraph( this._sdf.performanceMetrics.totalProcessingTime );
1417
1410
  timer.end( 'BVH build (SceneProcessor)' );
@@ -2452,8 +2445,7 @@ export class RayzeeRenderer extends EventDispatcher {
2452
2445
 
2453
2446
  /**
2454
2447
  * What produced the current image, as plain JSON, to keep beside a render: engine and three.js
2455
- * versions, the profile by name and by value (a name's values can change between releases), the
2456
- * adapter, every live setting with its source, the colour pipeline, and the modes that decide
2448
+ * versions, the adapter, every live setting with its source, the colour pipeline, and the modes that decide
2457
2449
  * whether the render reproduces.
2458
2450
  * @returns {Object}
2459
2451
  */
@@ -2465,7 +2457,6 @@ export class RayzeeRenderer extends EventDispatcher {
2465
2457
  return toPortable( {
2466
2458
  engine: VERSION,
2467
2459
  three: REVISION,
2468
- profile: { name: this._profileName, values: { ...this._profile } },
2469
2460
  adapter: this.adapterInfo ?? null,
2470
2461
  mode: {
2471
2462
  headless: this._headless,
@@ -3566,7 +3557,7 @@ export class RayzeeRenderer extends EventDispatcher {
3566
3557
  RectAreaLightNode.setLTC( RectAreaLightTexturesLib.init() );
3567
3558
 
3568
3559
  this.renderer.outputColorSpace = SRGBColorSpace;
3569
- this.renderer.toneMapping = this._profile.toneMapping;
3560
+ this.renderer.toneMapping = DEFAULT_VIEW;
3570
3561
  this.renderer.toneMappingExposure = 1.0;
3571
3562
  this.renderer.setPixelRatio( 1.0 );
3572
3563
 
@@ -3592,7 +3583,10 @@ export class RayzeeRenderer extends EventDispatcher {
3592
3583
  // Spread into defaults, so only pass it when the host actually set one.
3593
3584
  ...( this._maxSceneBytes === undefined ? {} : { maxSceneBytes: this._maxSceneBytes } ),
3594
3585
  } );
3595
- this.assetLoader = new AssetLoader( this.meshScene, this.camera, null, { issues: this._issues, profile: this._profile } );
3586
+ this.assetLoader = new AssetLoader( this.meshScene, this.camera, null, {
3587
+ issues: this._issues,
3588
+ areaLightIntensityScale: () => this.settings.get( 'areaLightIntensityScale' ),
3589
+ } );
3596
3590
  this.assetLoader.setRenderer( this.renderer );
3597
3591
  this.assetLoader.createFloorPlane();
3598
3592
 
@@ -3699,7 +3693,10 @@ export class RayzeeRenderer extends EventDispatcher {
3699
3693
 
3700
3694
  _createStages() {
3701
3695
 
3702
- this.stages.pathTracer = new PathTracer( this.renderer, this.scene, this.camera, { hostMemoryGB: this._hostMemoryGB } );
3696
+ this.stages.pathTracer = new PathTracer( this.renderer, this.scene, this.camera, {
3697
+ hostMemoryGB: this._hostMemoryGB,
3698
+ sortMaterials: this.settings.get( 'wavefrontSortMaterials' ),
3699
+ } );
3703
3700
  this._extraStages = this._createExtraStages();
3704
3701
  this.stages.compositor = new Compositor( this.renderer, {
3705
3702
  saturation: this.settings.get( 'saturation' ) ?? DEFAULT_STATE.saturation,