rayzee 5.4.0 → 5.4.1

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.
@@ -43,11 +43,11 @@ export class EnvironmentManager {
43
43
  this.environmentTexture = this._envPlaceholder;
44
44
  this.envTexSize = new Vector2();
45
45
 
46
- // CDF storage buffers
47
- this.envMarginalStorageAttr = null;
48
- this.envMarginalStorageNode = null;
49
- this.envConditionalStorageAttr = null;
50
- this.envConditionalStorageNode = null;
46
+ // CDF storage buffer (marginal + conditional packed into one buffer).
47
+ // Layout: [ marginal (envResolution.y floats) | conditional (envResolution.x * envResolution.y floats) ]
48
+ // Conditional offset is the marginal length, which equals envResolution.y at runtime.
49
+ this.envCDFStorageAttr = null;
50
+ this.envCDFStorageNode = null;
51
51
  this._initCDFStorageBuffers();
52
52
 
53
53
  // Environment rotation
@@ -177,74 +177,52 @@ export class EnvironmentManager {
177
177
 
178
178
  }
179
179
 
180
- // ===== CDF STORAGE BUFFERS =====
180
+ // ===== CDF STORAGE BUFFER =====
181
181
 
182
182
  /**
183
- * Initialize CDF storage buffers with placeholder data.
184
- * Must be called before shader compilation so the nodes exist in the graph.
183
+ * Initialize the packed CDF storage buffer with placeholder data.
184
+ * Must be called before shader compilation so the node exists in the graph.
185
+ *
186
+ * Layout: [ marginal (size = envResolution.y) | conditional (size = envResolution.x * envResolution.y) ]
187
+ * Placeholder shape is a 1x2 env map: marginal=[0,1], conditional=[0,0,1,1].
185
188
  * @private
186
189
  */
187
190
  _initCDFStorageBuffers() {
188
191
 
189
- // Marginal: 1 float per entry, default placeholder
190
- const marginalPlaceholder = new Float32Array( [ 0, 1 ] );
191
- this.envMarginalStorageAttr = new StorageInstancedBufferAttribute( marginalPlaceholder, 1 );
192
- this.envMarginalStorageNode = storage( this.envMarginalStorageAttr, 'float', 2 ).toReadOnly();
193
-
194
- // Conditional: 1 float per entry, default placeholder
195
- const conditionalPlaceholder = new Float32Array( [ 0, 0, 1, 1 ] );
196
- this.envConditionalStorageAttr = new StorageInstancedBufferAttribute( conditionalPlaceholder, 1 );
197
- this.envConditionalStorageNode = storage( this.envConditionalStorageAttr, 'float', 4 ).toReadOnly();
198
-
199
- }
200
-
201
- /**
202
- * Update marginal CDF storage buffer from Float32Array.
203
- */
204
- setEnvMarginalData( floatData ) {
205
-
206
- if ( ! floatData ) return;
207
-
208
- this.envMarginalStorageAttr = new StorageInstancedBufferAttribute( floatData, 1 );
209
- this.envMarginalStorageNode.value = this.envMarginalStorageAttr;
210
- this.envMarginalStorageNode.bufferCount = floatData.length;
211
-
212
- }
213
-
214
- /**
215
- * Update conditional CDF storage buffer from Float32Array.
216
- */
217
- setEnvConditionalData( floatData ) {
218
-
219
- if ( ! floatData ) return;
220
-
221
- this.envConditionalStorageAttr = new StorageInstancedBufferAttribute( floatData, 1 );
222
- this.envConditionalStorageNode.value = this.envConditionalStorageAttr;
223
- this.envConditionalStorageNode.bufferCount = floatData.length;
192
+ const placeholder = new Float32Array( [ 0, 1, 0, 0, 1, 1 ] );
193
+ this.envCDFStorageAttr = new StorageInstancedBufferAttribute( placeholder, 1 );
194
+ this.envCDFStorageNode = storage( this.envCDFStorageAttr, 'float', placeholder.length ).toReadOnly();
224
195
 
225
196
  }
226
197
 
227
198
  /**
228
- * Update both CDF storage buffers from equirectHdrInfo.
199
+ * Update the packed CDF storage buffer from equirectHdrInfo.
200
+ * Concatenates marginal + conditional into one buffer.
229
201
  * @private
230
202
  */
231
203
  _updateCDFStorageBuffers() {
232
204
 
233
- this.setEnvMarginalData( this.equirectHdrInfo.marginalData );
234
- this.setEnvConditionalData( this.equirectHdrInfo.conditionalData );
205
+ const marginal = this.equirectHdrInfo.marginalData;
206
+ const conditional = this.equirectHdrInfo.conditionalData;
207
+ if ( ! marginal || ! conditional ) return;
208
+
209
+ const combined = new Float32Array( marginal.length + conditional.length );
210
+ combined.set( marginal, 0 );
211
+ combined.set( conditional, marginal.length );
212
+
213
+ this.envCDFStorageAttr = new StorageInstancedBufferAttribute( combined, 1 );
214
+ this.envCDFStorageNode.value = this.envCDFStorageAttr;
215
+ this.envCDFStorageNode.bufferCount = combined.length;
235
216
 
236
217
  }
237
218
 
238
219
  /**
239
- * Get CDF storage nodes for shader graph.
240
- * @returns {{ marginalNode: StorageNode, conditionalNode: StorageNode }}
220
+ * Get the packed CDF storage node for shader graph.
221
+ * @returns {{ cdfNode: StorageNode }}
241
222
  */
242
223
  getCDFStorageNodes() {
243
224
 
244
- return {
245
- marginalNode: this.envMarginalStorageNode,
246
- conditionalNode: this.envConditionalStorageNode,
247
- };
225
+ return { cdfNode: this.envCDFStorageNode };
248
226
 
249
227
  }
250
228
 
@@ -559,10 +537,8 @@ export class EnvironmentManager {
559
537
 
560
538
  this.proceduralSkyRenderer = null;
561
539
  this.simpleSkyRenderer = null;
562
- this.envMarginalStorageAttr = null;
563
- this.envMarginalStorageNode = null;
564
- this.envConditionalStorageAttr = null;
565
- this.envConditionalStorageNode = null;
540
+ this.envCDFStorageAttr = null;
541
+ this.envCDFStorageNode = null;
566
542
  this._envPlaceholder?.dispose();
567
543
  this._envPlaceholder = null;
568
544
  this.environmentTexture = null;
@@ -237,6 +237,9 @@ export class UniformManager {
237
237
  u( 'emissiveTriangleCount', 0, 'int' );
238
238
  u( 'emissiveTotalPower', 0.0, 'float' );
239
239
  u( 'lightBVHNodeCount', 0, 'int' );
240
+ // Offset (in vec4 elements) within the packed light buffer where emissive
241
+ // triangle data starts. Equals lightBVHNodeCount * LBVH_STRIDE; computed on upload.
242
+ u( 'emissiveVec4Offset', 0, 'int' );
240
243
 
241
244
  // Render mode
242
245
  u( 'renderMode', DEFAULT_STATE.renderMode, 'int' );
@@ -1,87 +0,0 @@
1
- /**
2
- * Proxy-enhanced struct factory for TSL.
3
- *
4
- * TSL structs require `.get('fieldName')` for member access, but GLSL-style
5
- * dot notation (`.fieldName`) is more natural and matches the ported code.
6
- *
7
- * This utility wraps TSL's `struct()` so that:
8
- * - Direct construction: `MyStruct({...}).toVar('x')` → `.fieldName` works automatically
9
- * - Fn return values: `MyStruct.wrap(someFn(...))` → `.fieldName` works automatically
10
- *
11
- * Internally, property access for known struct member names is redirected to `.get('name')`.
12
- * Swizzle properties (x, y, z, w, etc.), Node methods (.add, .assign, etc.), and other
13
- * standard properties pass through to the underlying node unmodified.
14
- */
15
-
16
- import { struct as _struct } from 'three/tsl';
17
-
18
- /**
19
- * Creates a Proxy around a TSL node that redirects struct member access to `.get('name')`.
20
- * Also intercepts `.toVar()` to ensure the resulting VarNode is also proxy-wrapped.
21
- */
22
- function createStructProxy( node, memberSet ) {
23
-
24
- return new Proxy( node, {
25
-
26
- get( target, prop, receiver ) {
27
-
28
- // Intercept known struct member names
29
- if ( typeof prop === 'string' && memberSet.has( prop ) ) {
30
-
31
- return target.get( prop );
32
-
33
- }
34
-
35
- const val = Reflect.get( target, prop, receiver );
36
-
37
- // Intercept .toVar() to proxy-wrap the result
38
- if ( prop === 'toVar' && typeof val === 'function' ) {
39
-
40
- return ( ...args ) => createStructProxy( val.apply( target, args ), memberSet );
41
-
42
- }
43
-
44
- return val;
45
-
46
- }
47
-
48
- } );
49
-
50
- }
51
-
52
- /**
53
- * Drop-in replacement for TSL's `struct()` that returns a proxy-enhanced factory.
54
- *
55
- * The returned factory:
56
- * - Creates struct nodes where `.toVar()` results support dot-notation field access
57
- * - Has `.wrap(node)` method to proxy-wrap Fn return values for field access
58
- * - Has `.layout` and `.isStruct` matching the original TSL struct API
59
- *
60
- * @param {Object} members - Struct member layout (e.g., { didHit: 'bool', dst: 'float' })
61
- * @param {string|null} name - Optional struct name
62
- * @returns {Function} Enhanced struct factory
63
- */
64
- export function struct( members, name = null ) {
65
-
66
- const factory = _struct( members, name );
67
- const memberSet = new Set( Object.keys( members ) );
68
-
69
- const wrappedFactory = ( ...args ) => {
70
-
71
- const node = factory( ...args );
72
- return createStructProxy( node, memberSet );
73
-
74
- };
75
-
76
- wrappedFactory.layout = factory.layout;
77
- wrappedFactory.isStruct = true;
78
-
79
- /**
80
- * Wrap an existing node (e.g., Fn return value) with struct field access proxy.
81
- * Usage: `const hit = HitInfo.wrap(traverseBVH(...).toVar('hit'));`
82
- */
83
- wrappedFactory.wrap = ( node ) => createStructProxy( node, memberSet );
84
-
85
- return wrappedFactory;
86
-
87
- }
@@ -1,60 +0,0 @@
1
- /**
2
- * Monkey-patch to disable WGSL global-variable promotion for compute shaders.
3
- *
4
- * Three.js r184 introduced `WGSLNodeBuilder.allowGlobalVariables = true` which
5
- * emits `.toVar()` declarations at WGSL module scope as `var<private> name : T`
6
- * instead of function-local `var name : T` inside `fn main()` (as r183 did).
7
- *
8
- * For shaders with hundreds of `.toVar()` calls inside loops (e.g. our BVH
9
- * traversal + BRDF path tracer), `var<private>` increases GPU register pressure
10
- * because the Dawn/Chromium WGSL compiler cannot aggressively register-allocate
11
- * variables with a stable per-invocation memory address. We measured a ~8% fps
12
- * regression (120 → 110) on the path tracer after upgrading r183 → r184 that
13
- * traced entirely to GPU execution, not CPU.
14
- *
15
- * This patch wraps `WebGPUBackend.createNodeBuilder` so every newly constructed
16
- * node builder reports `allowGlobalVariables = false`, restoring r183's
17
- * function-scoped `var` emission inside `fn main()`. No behavior change —
18
- * WGSL spec guarantees `var<private>` and function-local `var` are semantically
19
- * equivalent for per-invocation storage; only the compiler's register-allocation
20
- * latitude differs.
21
- *
22
- * Relevant upstream lines:
23
- * - `node_modules/three/src/renderers/webgpu/nodes/WGSLNodeBuilder.js:247`
24
- * (`this.allowGlobalVariables = true`)
25
- * - `...WGSLNodeBuilder.js:2458` (module-scope vars block)
26
- * - `...WGSLNodeBuilder.js:2467` (function-body vars block)
27
- *
28
- * Revisit if upstream adds an official opt-out or fixes register pressure.
29
- * Import this module once at app startup (side-effect only).
30
- */
31
-
32
- import { WebGPUBackend } from 'three/webgpu';
33
-
34
- const _origCreateNodeBuilder = WebGPUBackend.prototype.createNodeBuilder;
35
-
36
- // WGSLNodeBuilder's `allowGlobalVariables` switch is ONLY consumed by the
37
- // compute-shader template (see `_getWGSLComputeCode`). The vertex/fragment
38
- // templates always emit `shaderData.vars` at module scope and therefore
39
- // REQUIRE `allowGlobalVariables=true` (emitting function-local `var` at
40
- // module scope is invalid WGSL and crashes pipeline creation with
41
- // "Invalid ShaderModule"). We install a per-instance accessor that returns
42
- // `false` only when the builder is for a compute node (material === null)
43
- // and `true` otherwise, so render pipelines keep r184 behavior untouched.
44
- WebGPUBackend.prototype.createNodeBuilder = function ( object, renderer ) {
45
-
46
- const builder = _origCreateNodeBuilder.call( this, object, renderer );
47
-
48
- Object.defineProperty( builder, 'allowGlobalVariables', {
49
- get() {
50
-
51
- return this.material !== null;
52
-
53
- },
54
- set() { /* ignore — the value is derived from material presence */ },
55
- configurable: true,
56
- } );
57
-
58
- return builder;
59
-
60
- };