rayzee 6.4.0 → 7.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 +24 -5
- package/dist/rayzee.es.js +4953 -4225
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +157 -236
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineDefaults.js +29 -13
- package/src/PathTracerApp.js +119 -26
- package/src/Pipeline/PipelineContext.js +1 -2
- package/src/Pipeline/RenderPipeline.js +1 -1
- package/src/Pipeline/RenderStage.js +1 -1
- package/src/Processor/CameraOptimizer.js +0 -5
- package/src/Processor/GeometryExtractor.js +22 -1
- package/src/Processor/KernelManager.js +277 -0
- package/src/Processor/PackedRayBuffer.js +265 -0
- package/src/Processor/QueueManager.js +173 -0
- package/src/Processor/SceneProcessor.js +1 -0
- package/src/Processor/ShaderBuilder.js +11 -316
- package/src/Processor/StorageTexturePool.js +29 -15
- package/src/Processor/TextureCreator.js +6 -0
- package/src/Processor/VRAMTracker.js +169 -0
- package/src/Processor/utils.js +11 -110
- package/src/RenderSettings.js +1 -3
- package/src/Stages/ASVGF.js +76 -20
- package/src/Stages/BilateralFilter.js +34 -10
- package/src/Stages/EdgeFilter.js +2 -3
- package/src/Stages/MotionVector.js +16 -9
- package/src/Stages/NormalDepth.js +17 -5
- package/src/Stages/PathTracer.js +671 -1456
- package/src/Stages/PathTracerStage.js +1451 -0
- package/src/Stages/SSRC.js +32 -15
- package/src/Stages/Variance.js +35 -12
- package/src/TSL/BVHTraversal.js +7 -1
- package/src/TSL/Common.js +12 -2
- package/src/TSL/CompactKernel.js +110 -0
- package/src/TSL/DebugKernel.js +98 -0
- package/src/TSL/Environment.js +13 -11
- package/src/TSL/ExtendKernel.js +75 -0
- package/src/TSL/FinalWriteKernel.js +121 -0
- package/src/TSL/GenerateKernel.js +109 -0
- package/src/TSL/LightsSampling.js +2 -2
- package/src/TSL/MaterialTransmission.js +32 -2
- package/src/TSL/PathTracerCore.js +43 -912
- package/src/TSL/ShadeKernel.js +873 -0
- package/src/TSL/Struct.js +5 -0
- package/src/TSL/Subsurface.js +232 -0
- package/src/TSL/patches.js +81 -4
- package/src/index.js +3 -0
- package/src/managers/CameraManager.js +1 -1
- package/src/managers/DenoisingManager.js +40 -75
- package/src/managers/EnvironmentManager.js +30 -39
- package/src/managers/MaterialDataManager.js +60 -1
- package/src/managers/OverlayManager.js +7 -22
- package/src/managers/UniformManager.js +1 -3
- package/src/managers/helpers/TileHelper.js +2 -2
- package/src/Stages/AdaptiveSampling.js +0 -483
- package/src/TSL/PathTracer.js +0 -384
- package/src/managers/TileManager.js +0 -298
|
@@ -297,6 +297,7 @@ export class SceneProcessor {
|
|
|
297
297
|
// Store other extracted data
|
|
298
298
|
this.materials = extractedData.materials;
|
|
299
299
|
this.materialCount = this.materials.length; // Store material count for feature scanning
|
|
300
|
+
this.materialTriangleCounts = extractedData.materialTriangleCounts; // Per-material tri count for sort-bin remap
|
|
300
301
|
this.meshes = extractedData.meshes;
|
|
301
302
|
this.meshTriangleRanges = extractedData.meshTriangleRanges; // Per-mesh { start, count } for TLAS/BLAS
|
|
302
303
|
this.maps = extractedData.maps;
|
|
@@ -1,110 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* ShaderBuilder.js
|
|
3
|
-
* Owns TSL shader graph construction and compute node management
|
|
4
|
-
* for the path tracing pipeline.
|
|
2
|
+
* ShaderBuilder.js — shared scene texture-node factory for the path tracer.
|
|
5
3
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Texture nodes are created once and updated in-place via .value mutation
|
|
11
|
-
* to preserve compiled shader graph references.
|
|
4
|
+
* Creates the texture/storage nodes the wavefront kernels read (environment, material map
|
|
5
|
+
* arrays, previous-frame MRT, adaptive-sampling, gobo/IES) and configures the module-level
|
|
6
|
+
* shadow/alpha/gobo/IES shader state. Nodes are created once and updated in-place via
|
|
7
|
+
* .value mutation to preserve compiled shader-graph references.
|
|
12
8
|
*/
|
|
13
9
|
|
|
14
|
-
import {
|
|
15
|
-
localId, workgroupId } from 'three/tsl';
|
|
16
|
-
import { TextureNode } from 'three/webgpu';
|
|
10
|
+
import { texture } from 'three/tsl';
|
|
17
11
|
import { LinearFilter, DataArrayTexture } from 'three';
|
|
18
|
-
import { pathTracerMain } from '../TSL/PathTracer.js';
|
|
19
12
|
import { setShadowAlbedoMaps, setAlphaShadowsUniform } from '../TSL/LightsDirect.js';
|
|
20
13
|
import { setGoboMapsTexture, setIESProfilesTexture } from '../TSL/LightsCore.js';
|
|
21
|
-
import { BuildTimer } from './BuildTimer.js';
|
|
22
|
-
|
|
23
|
-
const WG_SIZE = 8;
|
|
24
14
|
|
|
25
15
|
export class ShaderBuilder {
|
|
26
16
|
|
|
27
17
|
constructor() {
|
|
28
18
|
|
|
29
|
-
// Single compute node (no dual ping-pong — copy approach)
|
|
30
|
-
this.computeNode = null;
|
|
31
|
-
|
|
32
19
|
// Previous-frame texture nodes (sample from MRT RenderTarget)
|
|
33
20
|
this.prevColorTexNode = null;
|
|
34
21
|
this.prevNormalDepthTexNode = null;
|
|
35
22
|
this.prevAlbedoTexNode = null;
|
|
36
23
|
|
|
37
|
-
// Adaptive sampling texture (updated per-frame from context)
|
|
38
|
-
this.adaptiveSamplingTexNode = null;
|
|
39
|
-
|
|
40
|
-
// Tile offset uniforms — pixel origin of the active tile region
|
|
41
|
-
// Dispatch covers only tile-sized workgroups; offset maps them to image space
|
|
42
|
-
this.tileOffsetX = uniform( 0, 'int' );
|
|
43
|
-
this.tileOffsetY = uniform( 0, 'int' );
|
|
44
|
-
|
|
45
|
-
// Render dimensions for edge-workgroup bounds checking
|
|
46
|
-
this.renderWidth = uniform( 1920, 'int' );
|
|
47
|
-
this.renderHeight = uniform( 1080, 'int' );
|
|
48
|
-
|
|
49
|
-
// Dispatch dimensions
|
|
50
|
-
this._dispatchX = 0;
|
|
51
|
-
this._dispatchY = 0;
|
|
52
|
-
|
|
53
|
-
// Reused per-frame dispatchSize array — avoids GC pressure from
|
|
54
|
-
// allocating [x,y,z] on every setFullScreenDispatch/setTileDispatch call.
|
|
55
|
-
// WebGPUBackend only reads indices 0..2 of this array during compute dispatch.
|
|
56
|
-
this._dispatchSize = [ 0, 0, 1 ];
|
|
57
|
-
|
|
58
24
|
// Scene texture nodes cache (for in-place updates on model change)
|
|
59
25
|
this._sceneTextureNodes = null;
|
|
60
26
|
|
|
61
|
-
// Whether the GPU compute pipeline has been compiled (via a real dispatch).
|
|
62
|
-
// Reset on setupCompute() rebuilds and on dispose().
|
|
63
|
-
this._compiled = false;
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Creates the full compute shader graph from scratch.
|
|
69
|
-
*
|
|
70
|
-
* @param {Object} config
|
|
71
|
-
* @param {Object} config.stage - PathTracer instance
|
|
72
|
-
* @param {Object} config.storageTextures - StorageTexturePool
|
|
73
|
-
*/
|
|
74
|
-
setupCompute( config ) {
|
|
75
|
-
|
|
76
|
-
const { stage, storageTextures } = config;
|
|
77
|
-
|
|
78
|
-
const timer = new BuildTimer( 'setupCompute' );
|
|
79
|
-
|
|
80
|
-
timer.start( 'Create texture nodes' );
|
|
81
|
-
const textureNodes = this._createTextureNodes( stage, storageTextures );
|
|
82
|
-
timer.end( 'Create texture nodes' );
|
|
83
|
-
|
|
84
|
-
timer.start( 'Build compute node (TSL)' );
|
|
85
|
-
|
|
86
|
-
const width = storageTextures.renderWidth;
|
|
87
|
-
const height = storageTextures.renderHeight;
|
|
88
|
-
this._dispatchX = Math.ceil( width / WG_SIZE );
|
|
89
|
-
this._dispatchY = Math.ceil( height / WG_SIZE );
|
|
90
|
-
|
|
91
|
-
this.renderWidth.value = width;
|
|
92
|
-
this.renderHeight.value = height;
|
|
93
|
-
|
|
94
|
-
const writeTex = storageTextures.getWriteTextures();
|
|
95
|
-
|
|
96
|
-
this.computeNode = this._buildComputeNode(
|
|
97
|
-
stage, textureNodes,
|
|
98
|
-
writeTex.color, writeTex.normalDepth, writeTex.albedo
|
|
99
|
-
);
|
|
100
|
-
|
|
101
|
-
// New compute node → needs a fresh GPU pipeline compile
|
|
102
|
-
this._compiled = false;
|
|
103
|
-
|
|
104
|
-
timer.end( 'Build compute node (TSL)' );
|
|
105
|
-
|
|
106
|
-
timer.print();
|
|
107
|
-
|
|
108
27
|
}
|
|
109
28
|
|
|
110
29
|
updateSceneTextures( stage ) {
|
|
@@ -165,98 +84,10 @@ export class ShaderBuilder {
|
|
|
165
84
|
|
|
166
85
|
}
|
|
167
86
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if ( this.computeNode ) {
|
|
174
|
-
|
|
175
|
-
this._dispatchSize[ 0 ] = this._dispatchX;
|
|
176
|
-
this._dispatchSize[ 1 ] = this._dispatchY;
|
|
177
|
-
this.computeNode.dispatchSize = this._dispatchSize;
|
|
178
|
-
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
this.renderWidth.value = width;
|
|
182
|
-
this.renderHeight.value = height;
|
|
183
|
-
|
|
184
|
-
// Reset tile offset (full-screen)
|
|
185
|
-
this.tileOffsetX.value = 0;
|
|
186
|
-
this.tileOffsetY.value = 0;
|
|
187
|
-
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Set dispatch to cover only the active tile region.
|
|
192
|
-
* Adjusts dispatch count and tile offset so threads map directly to tile pixels.
|
|
193
|
-
* @param {number} offsetX - Tile origin X in pixels
|
|
194
|
-
* @param {number} offsetY - Tile origin Y in pixels
|
|
195
|
-
* @param {number} tileWidth - Tile width in pixels
|
|
196
|
-
* @param {number} tileHeight - Tile height in pixels
|
|
197
|
-
*/
|
|
198
|
-
setTileDispatch( offsetX, offsetY, tileWidth, tileHeight ) {
|
|
199
|
-
|
|
200
|
-
this.tileOffsetX.value = offsetX;
|
|
201
|
-
this.tileOffsetY.value = offsetY;
|
|
202
|
-
|
|
203
|
-
const dispatchX = Math.ceil( tileWidth / WG_SIZE );
|
|
204
|
-
const dispatchY = Math.ceil( tileHeight / WG_SIZE );
|
|
205
|
-
|
|
206
|
-
if ( this.computeNode ) {
|
|
207
|
-
|
|
208
|
-
this._dispatchSize[ 0 ] = dispatchX;
|
|
209
|
-
this._dispatchSize[ 1 ] = dispatchY;
|
|
210
|
-
this.computeNode.dispatchSize = this._dispatchSize;
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
/**
|
|
217
|
-
* Reset dispatch to full-screen (no tiling).
|
|
218
|
-
*/
|
|
219
|
-
setFullScreenDispatch() {
|
|
220
|
-
|
|
221
|
-
this.tileOffsetX.value = 0;
|
|
222
|
-
this.tileOffsetY.value = 0;
|
|
223
|
-
|
|
224
|
-
if ( this.computeNode ) {
|
|
225
|
-
|
|
226
|
-
this._dispatchSize[ 0 ] = this._dispatchX;
|
|
227
|
-
this._dispatchSize[ 1 ] = this._dispatchY;
|
|
228
|
-
this.computeNode.dispatchSize = this._dispatchSize;
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* Front-load GPU compute pipeline creation via a single dispatch.
|
|
236
|
-
*
|
|
237
|
-
* Three.js WebGPU has no `createComputePipelineAsync` path — compute
|
|
238
|
-
* pipelines always compile synchronously on first `renderer.compute(node)`.
|
|
239
|
-
* Calling this at build time (while a "Compiling shaders…" status is
|
|
240
|
-
* already visible) moves the stall off the first animate frame.
|
|
241
|
-
*
|
|
242
|
-
* The dispatch writes to ping-pong storage textures whose contents are
|
|
243
|
-
* discarded by the subsequent `reset()` (frame counter back to 0 →
|
|
244
|
-
* `hasPreviousAccumulated = 0` → prev textures are not read).
|
|
245
|
-
*
|
|
246
|
-
* @param {object} renderer - WebGPURenderer
|
|
247
|
-
*/
|
|
248
|
-
forceCompile( renderer ) {
|
|
249
|
-
|
|
250
|
-
if ( this._compiled || ! this.computeNode || ! renderer ) return;
|
|
251
|
-
|
|
252
|
-
this._compiled = true;
|
|
253
|
-
renderer.compute( this.computeNode );
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
// ===== PRIVATE =====
|
|
258
|
-
|
|
259
|
-
_createTextureNodes( stage, storageTextures ) {
|
|
87
|
+
// Creates the shared scene texture nodes (env, material maps, prev-frame, adaptive, gobo, IES)
|
|
88
|
+
// + configures the module-level shadow/alpha/gobo/IES shader state read by the wavefront kernels.
|
|
89
|
+
// Call from setupMaterial before the kernels are built.
|
|
90
|
+
createSceneTextureNodes( stage, storageTextures ) {
|
|
260
91
|
|
|
261
92
|
const triStorage = stage.triangleStorageNode;
|
|
262
93
|
const bvhStorage = stage.bvhStorageNode;
|
|
@@ -270,14 +101,6 @@ export class ShaderBuilder {
|
|
|
270
101
|
|
|
271
102
|
const envTex = texture( stage.environment.environmentTexture );
|
|
272
103
|
|
|
273
|
-
// Adaptive sampling texture
|
|
274
|
-
const adaptiveSamplingTex = new TextureNode();
|
|
275
|
-
this.adaptiveSamplingTexNode = adaptiveSamplingTex;
|
|
276
|
-
|
|
277
|
-
// Environment importance sampling CDF — packed storage buffer
|
|
278
|
-
// Layout: [marginal (envResolution.y floats) | conditional (envResolution.x * envResolution.y floats)]
|
|
279
|
-
const envCDFStorage = stage.environment.envCDFStorageNode;
|
|
280
|
-
|
|
281
104
|
// Previous-frame texture nodes — initialized from readTarget textures
|
|
282
105
|
const readTextures = storageTextures.getReadTextures();
|
|
283
106
|
this.prevColorTexNode = texture( readTextures.color );
|
|
@@ -319,7 +142,7 @@ export class ShaderBuilder {
|
|
|
319
142
|
|
|
320
143
|
const result = {
|
|
321
144
|
triStorage, bvhStorage, matStorage, lightBufferStorage,
|
|
322
|
-
envTex,
|
|
145
|
+
envTex,
|
|
323
146
|
albedoMapsTex, normalMapsTex, bumpMapsTex,
|
|
324
147
|
metalnessMapsTex, roughnessMapsTex, emissiveMapsTex, displacementMapsTex,
|
|
325
148
|
goboMapsTex, iesProfilesTex,
|
|
@@ -330,140 +153,12 @@ export class ShaderBuilder {
|
|
|
330
153
|
|
|
331
154
|
}
|
|
332
155
|
|
|
333
|
-
/**
|
|
334
|
-
* Build a single compute node.
|
|
335
|
-
* Previous-frame reads use texture() nodes bound to MRT RenderTarget textures.
|
|
336
|
-
*/
|
|
337
|
-
_buildComputeNode( stage, textureNodes,
|
|
338
|
-
writeColorTex, writeNDTex, writeAlbedoTex ) {
|
|
339
|
-
|
|
340
|
-
const {
|
|
341
|
-
triStorage, bvhStorage, matStorage, lightBufferStorage,
|
|
342
|
-
envTex, adaptiveSamplingTex, envCDFStorage,
|
|
343
|
-
albedoMapsTex, normalMapsTex, bumpMapsTex,
|
|
344
|
-
metalnessMapsTex, roughnessMapsTex, emissiveMapsTex, displacementMapsTex,
|
|
345
|
-
} = textureNodes;
|
|
346
|
-
|
|
347
|
-
const tileOffsetX = this.tileOffsetX;
|
|
348
|
-
const tileOffsetY = this.tileOffsetY;
|
|
349
|
-
const renderWidth = this.renderWidth;
|
|
350
|
-
const renderHeight = this.renderHeight;
|
|
351
|
-
|
|
352
|
-
const prevColorTexNode = this.prevColorTexNode;
|
|
353
|
-
const prevNormalDepthTexNode = this.prevNormalDepthTexNode;
|
|
354
|
-
const prevAlbedoTexNode = this.prevAlbedoTexNode;
|
|
355
|
-
|
|
356
|
-
const computeFn = Fn( () => {
|
|
357
|
-
|
|
358
|
-
// Map thread to image-space pixel via tile offset
|
|
359
|
-
const gx = tileOffsetX.add( int( workgroupId.x ).mul( WG_SIZE ) ).add( int( localId.x ) );
|
|
360
|
-
const gy = tileOffsetY.add( int( workgroupId.y ).mul( WG_SIZE ) ).add( int( localId.y ) );
|
|
361
|
-
|
|
362
|
-
// Bounds check only needed for edge workgroups that overshoot render dimensions
|
|
363
|
-
If( gx.lessThan( renderWidth ).and( gy.lessThan( renderHeight ) ), () => {
|
|
364
|
-
|
|
365
|
-
const pixelCoord = vec2( float( gx ).add( 0.5 ), float( gy ).add( 0.5 ) );
|
|
366
|
-
|
|
367
|
-
pathTracerMain( {
|
|
368
|
-
pixelCoord,
|
|
369
|
-
writeColorTex, writeNDTex, writeAlbedoTex,
|
|
370
|
-
// Previous-frame textures from MRT RenderTarget (sampled via texture())
|
|
371
|
-
prevAccumTexture: prevColorTexNode,
|
|
372
|
-
prevNormalDepthTexture: prevNormalDepthTexNode,
|
|
373
|
-
prevAlbedoTexture: prevAlbedoTexNode,
|
|
374
|
-
resolution: stage.resolution,
|
|
375
|
-
frame: stage.frame,
|
|
376
|
-
samplesPerPixel: stage.samplesPerPixel,
|
|
377
|
-
visMode: stage.visMode,
|
|
378
|
-
cameraWorldMatrix: stage.cameraWorldMatrix,
|
|
379
|
-
cameraProjectionMatrixInverse: stage.cameraProjectionMatrixInverse,
|
|
380
|
-
cameraViewMatrix: stage.cameraViewMatrix,
|
|
381
|
-
cameraProjectionMatrix: stage.cameraProjectionMatrix,
|
|
382
|
-
bvhBuffer: bvhStorage,
|
|
383
|
-
triangleBuffer: triStorage,
|
|
384
|
-
materialBuffer: matStorage,
|
|
385
|
-
albedoMaps: albedoMapsTex,
|
|
386
|
-
normalMaps: normalMapsTex,
|
|
387
|
-
bumpMaps: bumpMapsTex,
|
|
388
|
-
metalnessMaps: metalnessMapsTex,
|
|
389
|
-
roughnessMaps: roughnessMapsTex,
|
|
390
|
-
emissiveMaps: emissiveMapsTex,
|
|
391
|
-
displacementMaps: displacementMapsTex,
|
|
392
|
-
directionalLightsBuffer: stage.directionalLightsBufferNode,
|
|
393
|
-
numDirectionalLights: stage.numDirectionalLights,
|
|
394
|
-
areaLightsBuffer: stage.areaLightsBufferNode,
|
|
395
|
-
numAreaLights: stage.numAreaLights,
|
|
396
|
-
pointLightsBuffer: stage.pointLightsBufferNode,
|
|
397
|
-
numPointLights: stage.numPointLights,
|
|
398
|
-
spotLightsBuffer: stage.spotLightsBufferNode,
|
|
399
|
-
numSpotLights: stage.numSpotLights,
|
|
400
|
-
envTexture: envTex,
|
|
401
|
-
environmentIntensity: stage.environmentIntensity,
|
|
402
|
-
envMatrix: stage.environmentMatrix,
|
|
403
|
-
envCDFBuffer: envCDFStorage,
|
|
404
|
-
envTotalSum: stage.envTotalSum,
|
|
405
|
-
envCompensationDelta: stage.envCompensationDelta,
|
|
406
|
-
envResolution: stage.envResolution,
|
|
407
|
-
enableEnvironmentLight: stage.enableEnvironment,
|
|
408
|
-
useEnvMapIS: stage.useEnvMapIS,
|
|
409
|
-
groundProjectionEnabled: stage.groundProjectionEnabled,
|
|
410
|
-
groundProjectionRadius: stage.groundProjectionRadius,
|
|
411
|
-
groundProjectionHeight: stage.groundProjectionHeight,
|
|
412
|
-
maxBounceCount: stage.maxBounces,
|
|
413
|
-
transmissiveBounces: stage.transmissiveBounces,
|
|
414
|
-
showBackground: stage.showBackground,
|
|
415
|
-
transparentBackground: stage.transparentBackground,
|
|
416
|
-
backgroundIntensity: stage.backgroundIntensity,
|
|
417
|
-
fireflyThreshold: stage.fireflyThreshold,
|
|
418
|
-
globalIlluminationIntensity: stage.globalIlluminationIntensity,
|
|
419
|
-
enableEmissiveTriangleSampling: stage.enableEmissiveTriangleSampling,
|
|
420
|
-
emissiveTriangleBuffer: lightBufferStorage,
|
|
421
|
-
emissiveTriangleCount: stage.emissiveTriangleCount,
|
|
422
|
-
emissiveTotalPower: stage.emissiveTotalPower,
|
|
423
|
-
emissiveBoost: stage.emissiveBoost,
|
|
424
|
-
emissiveVec4Offset: stage.emissiveVec4Offset,
|
|
425
|
-
lightBVHBuffer: lightBufferStorage,
|
|
426
|
-
lightBVHNodeCount: stage.lightBVHNodeCount,
|
|
427
|
-
debugVisScale: stage.debugVisScale,
|
|
428
|
-
enableAccumulation: stage.enableAccumulation,
|
|
429
|
-
hasPreviousAccumulated: stage.hasPreviousAccumulated,
|
|
430
|
-
accumulationAlpha: stage.accumulationAlpha,
|
|
431
|
-
cameraIsMoving: stage.cameraIsMoving,
|
|
432
|
-
useAdaptiveSampling: stage.useAdaptiveSampling,
|
|
433
|
-
adaptiveSamplingTexture: adaptiveSamplingTex,
|
|
434
|
-
adaptiveSamplingMin: stage.adaptiveSamplingMin,
|
|
435
|
-
adaptiveSamplingMax: stage.adaptiveSamplingMax,
|
|
436
|
-
enableDOF: stage.enableDOF,
|
|
437
|
-
focalLength: stage.focalLength,
|
|
438
|
-
aperture: stage.aperture,
|
|
439
|
-
focusDistance: stage.focusDistance,
|
|
440
|
-
sceneScale: stage.sceneScale,
|
|
441
|
-
apertureScale: stage.apertureScale,
|
|
442
|
-
anamorphicRatio: stage.anamorphicRatio,
|
|
443
|
-
} );
|
|
444
|
-
|
|
445
|
-
} );
|
|
446
|
-
|
|
447
|
-
} );
|
|
448
|
-
|
|
449
|
-
return computeFn().compute(
|
|
450
|
-
[ this._dispatchX, this._dispatchY, 1 ],
|
|
451
|
-
[ WG_SIZE, WG_SIZE, 1 ]
|
|
452
|
-
);
|
|
453
|
-
|
|
454
|
-
}
|
|
455
|
-
|
|
456
156
|
dispose() {
|
|
457
157
|
|
|
458
|
-
this.computeNode?.dispose();
|
|
459
|
-
|
|
460
|
-
this.computeNode = null;
|
|
461
158
|
this.prevColorTexNode = null;
|
|
462
159
|
this.prevNormalDepthTexNode = null;
|
|
463
160
|
this.prevAlbedoTexNode = null;
|
|
464
|
-
this.adaptiveSamplingTexNode = null;
|
|
465
161
|
this._sceneTextureNodes = null;
|
|
466
|
-
this._compiled = false;
|
|
467
162
|
|
|
468
163
|
}
|
|
469
164
|
|
|
@@ -13,11 +13,12 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { StorageTexture, RenderTarget } from 'three/webgpu';
|
|
16
|
-
import { RGBAFormat, FloatType, LinearFilter, NearestFilter } from 'three';
|
|
16
|
+
import { RGBAFormat, FloatType, LinearFilter, NearestFilter, Box2, Vector2 } from 'three';
|
|
17
|
+
import { MAX_STORAGE_TEXTURE_SIZE } from '../EngineDefaults.js';
|
|
17
18
|
|
|
18
|
-
function createWriteStorageTex(
|
|
19
|
+
function createWriteStorageTex() {
|
|
19
20
|
|
|
20
|
-
const tex = new StorageTexture(
|
|
21
|
+
const tex = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
21
22
|
tex.type = FloatType;
|
|
22
23
|
tex.format = RGBAFormat;
|
|
23
24
|
tex.minFilter = LinearFilter;
|
|
@@ -44,6 +45,9 @@ export class StorageTexturePool {
|
|
|
44
45
|
this.renderWidth = 0;
|
|
45
46
|
this.renderHeight = 0;
|
|
46
47
|
|
|
48
|
+
// Reused srcRegion for copies out of the over-allocated StorageTextures.
|
|
49
|
+
this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
|
|
50
|
+
|
|
47
51
|
if ( width > 0 && height > 0 ) {
|
|
48
52
|
|
|
49
53
|
this.create( width, height );
|
|
@@ -59,10 +63,10 @@ export class StorageTexturePool {
|
|
|
59
63
|
this.renderWidth = width;
|
|
60
64
|
this.renderHeight = height;
|
|
61
65
|
|
|
62
|
-
// Write-only StorageTextures
|
|
63
|
-
this.writeColor = createWriteStorageTex(
|
|
64
|
-
this.writeNormalDepth = createWriteStorageTex(
|
|
65
|
-
this.writeAlbedo = createWriteStorageTex(
|
|
66
|
+
// Write-only StorageTextures allocated at max — never resized (see resize crash fix).
|
|
67
|
+
this.writeColor = createWriteStorageTex();
|
|
68
|
+
this.writeNormalDepth = createWriteStorageTex();
|
|
69
|
+
this.writeAlbedo = createWriteStorageTex();
|
|
66
70
|
|
|
67
71
|
// Readable MRT RenderTarget (3 color attachments, no depth/stencil)
|
|
68
72
|
this.readTarget = new RenderTarget( width, height, {
|
|
@@ -85,13 +89,22 @@ export class StorageTexturePool {
|
|
|
85
89
|
|
|
86
90
|
ensureSize( width, height ) {
|
|
87
91
|
|
|
88
|
-
if (
|
|
92
|
+
if ( ! this.writeColor ) {
|
|
89
93
|
|
|
90
94
|
this.create( width, height );
|
|
91
95
|
return true;
|
|
92
96
|
|
|
93
97
|
}
|
|
94
98
|
|
|
99
|
+
if ( this.renderWidth !== width || this.renderHeight !== height ) {
|
|
100
|
+
|
|
101
|
+
// Resize only the readTarget — never dispose/recreate the write StorageTextures
|
|
102
|
+
// (that destroys the texture the compute bind group holds → submit crash).
|
|
103
|
+
this.setSize( width, height );
|
|
104
|
+
return true;
|
|
105
|
+
|
|
106
|
+
}
|
|
107
|
+
|
|
95
108
|
return false;
|
|
96
109
|
|
|
97
110
|
}
|
|
@@ -131,9 +144,13 @@ export class StorageTexturePool {
|
|
|
131
144
|
*/
|
|
132
145
|
copyToReadTargets( renderer ) {
|
|
133
146
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
147
|
+
// Source write StorageTextures are over-allocated at the max size; the Box2 region
|
|
148
|
+
// restricts the copy to the active render size so source and destination extents match.
|
|
149
|
+
this._srcRegion.max.set( this.renderWidth, this.renderHeight );
|
|
150
|
+
|
|
151
|
+
renderer.copyTextureToTexture( this.writeColor, this.readTarget.textures[ 0 ], this._srcRegion );
|
|
152
|
+
renderer.copyTextureToTexture( this.writeNormalDepth, this.readTarget.textures[ 1 ], this._srcRegion );
|
|
153
|
+
renderer.copyTextureToTexture( this.writeAlbedo, this.readTarget.textures[ 2 ], this._srcRegion );
|
|
137
154
|
|
|
138
155
|
}
|
|
139
156
|
|
|
@@ -163,10 +180,7 @@ export class StorageTexturePool {
|
|
|
163
180
|
this.renderWidth = width;
|
|
164
181
|
this.renderHeight = height;
|
|
165
182
|
|
|
166
|
-
|
|
167
|
-
this.writeNormalDepth?.setSize( width, height );
|
|
168
|
-
this.writeAlbedo?.setSize( width, height );
|
|
169
|
-
|
|
183
|
+
// Write StorageTextures stay at max allocation — never resized (see resize crash fix).
|
|
170
184
|
if ( this.readTarget ) {
|
|
171
185
|
|
|
172
186
|
this.readTarget.setSize( width, height );
|
|
@@ -965,6 +965,12 @@ export class TextureCreator {
|
|
|
965
965
|
bumpMapMatrices[ 4 ], bumpMapMatrices[ 5 ], bumpMapMatrices[ 6 ], 1,
|
|
966
966
|
displacementMapMatrices[ 0 ], displacementMapMatrices[ 1 ], displacementMapMatrices[ 2 ], displacementMapMatrices[ 3 ],
|
|
967
967
|
displacementMapMatrices[ 4 ], displacementMapMatrices[ 5 ], displacementMapMatrices[ 6 ], 1,
|
|
968
|
+
// Slot 27: subsurface (subsurfaceColor.rgb, subsurface weight)
|
|
969
|
+
mat.subsurfaceColor?.r ?? 1, mat.subsurfaceColor?.g ?? 1, mat.subsurfaceColor?.b ?? 1, mat.subsurface ?? 0,
|
|
970
|
+
// Slot 28: subsurface (subsurfaceRadius.rgb, subsurfaceRadiusScale)
|
|
971
|
+
mat.subsurfaceRadius?.[ 0 ] ?? 1, mat.subsurfaceRadius?.[ 1 ] ?? 0.2, mat.subsurfaceRadius?.[ 2 ] ?? 0.1, mat.subsurfaceRadiusScale ?? 1,
|
|
972
|
+
// Slot 29: subsurface (anisotropy g, reserved)
|
|
973
|
+
mat.subsurfaceAnisotropy ?? 0, 0, 0, 0,
|
|
968
974
|
];
|
|
969
975
|
|
|
970
976
|
data.set( materialData, stride );
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* VRAMTracker.js — current/peak GPU memory accounting.
|
|
3
|
+
*
|
|
4
|
+
* Measures ACTUAL live bytes (attribute.array.byteLength, texture dims × format/type)
|
|
5
|
+
* rather than re-deriving allocation formulas, so it never drifts when strides,
|
|
6
|
+
* capacity rounding, or layouts change. Providers are thunks that read current state,
|
|
7
|
+
* so they survive reallocation (resize, scene/material/env reload). A per-pass WeakSet
|
|
8
|
+
* dedupes by resource identity, so overlapping registrations never double-count.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
RGBAFormat, RGBFormat, RGFormat, RedFormat,
|
|
13
|
+
FloatType, HalfFloatType, UnsignedByteType, ByteType,
|
|
14
|
+
UnsignedShortType, ShortType, UnsignedIntType, IntType,
|
|
15
|
+
} from 'three';
|
|
16
|
+
|
|
17
|
+
const CHANNELS = { [ RGBAFormat ]: 4, [ RGBFormat ]: 3, [ RGFormat ]: 2, [ RedFormat ]: 1 };
|
|
18
|
+
const TYPE_BYTES = {
|
|
19
|
+
[ FloatType ]: 4, [ HalfFloatType ]: 2,
|
|
20
|
+
[ UnsignedByteType ]: 1, [ ByteType ]: 1,
|
|
21
|
+
[ UnsignedShortType ]: 2, [ ShortType ]: 2,
|
|
22
|
+
[ UnsignedIntType ]: 4, [ IntType ]: 4,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function texelBytes( tex ) {
|
|
26
|
+
|
|
27
|
+
return ( CHANNELS[ tex.format ] ?? 4 ) * ( TYPE_BYTES[ tex.type ] ?? 4 );
|
|
28
|
+
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Exact byte size of a storage/buffer attribute's backing typed array. */
|
|
32
|
+
export function bufferBytes( attr ) {
|
|
33
|
+
|
|
34
|
+
return attr?.array?.byteLength || 0;
|
|
35
|
+
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Estimated GPU byte size of a Texture/StorageTexture/DataArrayTexture/RenderTarget. */
|
|
39
|
+
export function textureBytes( tex ) {
|
|
40
|
+
|
|
41
|
+
if ( ! tex ) return 0;
|
|
42
|
+
|
|
43
|
+
if ( tex.isRenderTarget ) {
|
|
44
|
+
|
|
45
|
+
const list = tex.textures?.length ? tex.textures : [ tex.texture ];
|
|
46
|
+
const w = tex.width || 0, h = tex.height || 0, d = tex.depth || 1;
|
|
47
|
+
let sum = 0;
|
|
48
|
+
for ( const t of list ) if ( t ) sum += w * h * d * texelBytes( t );
|
|
49
|
+
return sum;
|
|
50
|
+
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const img = tex.image || {};
|
|
54
|
+
const w = img.width ?? tex.width ?? 0;
|
|
55
|
+
const h = img.height ?? tex.height ?? 0;
|
|
56
|
+
const d = img.depth ?? 1;
|
|
57
|
+
return w * h * d * texelBytes( tex );
|
|
58
|
+
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export class VRAMTracker {
|
|
62
|
+
|
|
63
|
+
constructor() {
|
|
64
|
+
|
|
65
|
+
this._providers = [];
|
|
66
|
+
this.current = 0;
|
|
67
|
+
this.peak = 0;
|
|
68
|
+
this.byCategory = {};
|
|
69
|
+
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} category - grouping label in the report
|
|
74
|
+
* @param {Function} fn - returns a resource or array of resources: buffer
|
|
75
|
+
* attributes (`.array`), textures/render targets (`.isTexture`/`.isRenderTarget`),
|
|
76
|
+
* or synthetic `{ bytes }` for sizes with no inspectable object. Return falsy to skip.
|
|
77
|
+
*/
|
|
78
|
+
register( category, fn ) {
|
|
79
|
+
|
|
80
|
+
this._providers.push( { category, fn } );
|
|
81
|
+
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
measure() {
|
|
85
|
+
|
|
86
|
+
const seen = new WeakSet();
|
|
87
|
+
const byCategory = {};
|
|
88
|
+
let total = 0;
|
|
89
|
+
|
|
90
|
+
for ( const { category, fn } of this._providers ) {
|
|
91
|
+
|
|
92
|
+
let resources;
|
|
93
|
+
try {
|
|
94
|
+
|
|
95
|
+
resources = fn();
|
|
96
|
+
|
|
97
|
+
} catch {
|
|
98
|
+
|
|
99
|
+
resources = null;
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if ( ! resources ) continue;
|
|
104
|
+
|
|
105
|
+
let bytes = 0;
|
|
106
|
+
for ( const r of ( Array.isArray( resources ) ? resources : [ resources ] ) ) {
|
|
107
|
+
|
|
108
|
+
bytes += this._resourceBytes( r, seen );
|
|
109
|
+
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
byCategory[ category ] = ( byCategory[ category ] || 0 ) + bytes;
|
|
113
|
+
total += bytes;
|
|
114
|
+
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
this.byCategory = byCategory;
|
|
118
|
+
this.current = total;
|
|
119
|
+
if ( total > this.peak ) this.peak = total;
|
|
120
|
+
|
|
121
|
+
return { current: total, peak: this.peak, byCategory };
|
|
122
|
+
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
_resourceBytes( r, seen ) {
|
|
126
|
+
|
|
127
|
+
if ( ! r ) return 0;
|
|
128
|
+
|
|
129
|
+
// synthetic { bytes } (e.g. attributeArray-backed histograms)
|
|
130
|
+
if ( typeof r.bytes === 'number' && ! r.isTexture && ! r.isRenderTarget ) return r.bytes;
|
|
131
|
+
|
|
132
|
+
// buffer attribute — dedupe by backing array (rw/ro nodes share one buffer)
|
|
133
|
+
if ( r.array && r.array.byteLength != null ) {
|
|
134
|
+
|
|
135
|
+
if ( seen.has( r.array ) ) return 0;
|
|
136
|
+
seen.add( r.array );
|
|
137
|
+
return r.array.byteLength;
|
|
138
|
+
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// texture / render target — dedupe by object identity
|
|
142
|
+
if ( r.isRenderTarget || r.isTexture ) {
|
|
143
|
+
|
|
144
|
+
if ( seen.has( r ) ) return 0;
|
|
145
|
+
seen.add( r );
|
|
146
|
+
return textureBytes( r );
|
|
147
|
+
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return 0;
|
|
151
|
+
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Drop the high-water mark to the current value (call when a new render begins). */
|
|
155
|
+
resetPeak() {
|
|
156
|
+
|
|
157
|
+
this.peak = this.current;
|
|
158
|
+
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
getReport() {
|
|
162
|
+
|
|
163
|
+
const mb = ( b ) => ( b / 1048576 ).toFixed( 1 );
|
|
164
|
+
const parts = Object.entries( this.byCategory ).map( ( [ k, v ] ) => `${k}=${mb( v )}` );
|
|
165
|
+
return `VRAM current=${mb( this.current )}MB peak=${mb( this.peak )}MB [${parts.join( ' ' )}]`;
|
|
166
|
+
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
}
|