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.
- package/README.md +22 -20
- package/dist/{ArchiveImporter-xuUJiklP.js → ArchiveImporter-BRQvLggb.js} +12 -13
- package/dist/{BidirectionalIntegrator-C5PKjJoT.js → BidirectionalIntegrator-9lZQxFVH.js} +87 -87
- package/dist/BufferLayout-D_IBo2ci.js +150 -0
- package/dist/{ColorManagement-CheBCPaW.js → ColorManagement-oeEk7oSu.js} +171 -171
- package/dist/{NeuralRetouch-CW11yLZ_.js → NeuralRetouch-BCXylThH.js} +3 -3
- package/dist/{NeuralSuperRes-BekaecYg.js → NeuralSuperRes-sZAeS142.js} +3 -3
- package/dist/{PathTracer-Ccl9KOuF.js → PathTracer-CQPW06Mm.js} +2911 -2669
- package/dist/{PhysicalSky-Dv2PMFGW.js → PhysicalSky-BvqCw_dy.js} +2 -2
- package/dist/{RayzeeRenderer-BQxnzCTD.js → RayzeeRenderer-COpwotpU.js} +338 -329
- package/dist/{SolarLimb-ken-JbDK.js → SolarLimb-CPaeZVwz.js} +10 -10
- package/dist/{ToneMapGPU-BHi5JzIq.js → ToneMapGPU-CjU3osDd.js} +1 -1
- package/dist/{ViewTransforms-D0rTLBsX.js → ViewTransforms-CYN7bq2w.js} +2 -2
- package/dist/addons/archives.es.js +1 -1
- package/dist/addons/bidirectional.es.js +1 -1
- package/dist/addons/color.es.js +1 -1
- package/dist/addons/physical-sky.es.js +1 -1
- package/dist/rayzee-core.es.js +11 -11
- package/dist/rayzee.es.js +734 -599
- package/dist/rayzee.umd.js +102 -102
- package/dist/sceneCachePolicy-BSSsKY98.js +443 -0
- package/package.json +1 -1
- package/src/Color/ViewTransforms.js +5 -1
- package/src/EngineDefaults.js +59 -1032
- package/src/Headless.js +6 -5
- package/src/Passes/OIDNTemporalHistory.js +1 -1
- package/src/PathTracerApp.js +5 -3
- package/src/Processor/AssetLoader.js +6 -5
- package/src/Processor/BVHBuilder.js +10 -31
- package/src/Processor/BVHLeafFold.js +4 -6
- package/src/Processor/BVHRefitter.js +3 -46
- package/src/Processor/BufferLayout.js +291 -0
- package/src/Processor/EmissiveTriangleBuilder.js +1 -1
- package/src/Processor/GeometryExtractor.js +3 -3
- package/src/Processor/HostMemory.js +7 -1
- package/src/Processor/InstanceTable.js +1 -1
- package/src/Processor/MaterialPacking.js +2 -1
- package/src/Processor/PBRT/PBRTSceneBuilder.js +1 -1
- package/src/Processor/QueueManager.js +28 -3
- package/src/Processor/SceneProcessor.js +4 -2
- package/src/Processor/SplitBLAS.js +1 -1
- package/src/Processor/StorageTexturePool.js +22 -1
- package/src/Processor/TLASBuilder.js +1 -3
- package/src/Processor/TextureBuckets.js +237 -0
- package/src/Processor/TextureCreator.js +10 -1
- package/src/Processor/Workers/TexturesWorker.js +5 -6
- package/src/RayzeeRenderer.js +29 -32
- package/src/RenderSettings.js +26 -28
- package/src/Stages/ASVGF.js +8 -7
- package/src/Stages/AutoExposure.js +14 -5
- package/src/Stages/BilateralFilter.js +2 -1
- package/src/Stages/DenoiserSettings.js +182 -0
- package/src/Stages/EdgeFilter.js +2 -1
- package/src/Stages/MotionVector.js +1 -1
- package/src/Stages/NRD.js +2 -3
- package/src/Stages/NormalDepth.js +3 -2
- package/src/Stages/PathTracer.js +72 -4
- package/src/Stages/PathTracerStage.js +2 -1
- package/src/Stages/Variance.js +1 -1
- package/src/TSL/BVHTraversal.js +4 -1
- package/src/TSL/Common.js +1 -1
- package/src/TSL/EmissiveSampling.js +1 -1
- package/src/TSL/LightVisibility.js +111 -0
- package/src/TSL/LightsSampling.js +151 -15
- package/src/TSL/Random.js +1 -1
- package/src/TSL/ShadeKernel.js +88 -3
- package/src/TSL/TextureSampling.js +1 -1
- package/src/core.js +5 -9
- package/src/index.js +10 -21
- package/src/integrators/BidirectionalIntegrator.js +2 -1
- package/src/managers/CameraManager.js +17 -7
- package/src/managers/DenoisingManager.js +14 -10
- package/src/managers/EnvironmentManager.js +32 -13
- package/src/managers/MaterialDataManager.js +3 -3
- package/src/managers/UniformManager.js +2 -2
- package/dist/EngineDefaults-DH3bI5t-.js +0 -844
- package/dist/sceneCachePolicy-BmwYOYDu.js +0 -183
- /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
|
-
|
|
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,
|
|
21
|
-
|
|
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';
|
|
@@ -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 {
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
|
package/src/RayzeeRenderer.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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.
|
|
1100
|
-
const
|
|
1101
|
-
this.
|
|
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(
|
|
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 &&
|
|
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:
|
|
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.
|
|
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
|
|
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 =
|
|
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, {
|
|
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, {
|
|
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,
|