rayzee 11.5.0 → 11.7.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 (41) hide show
  1. package/README.md +14 -14
  2. package/dist/{ArchiveImporter-29Nl1567.js → ArchiveImporter-DYcRXeKE.js} +2 -2
  3. package/dist/{BidirectionalIntegrator-CJay8tXK.js → BidirectionalIntegrator-BvjRpbTP.js} +289 -289
  4. package/dist/{EngineEvents-Ci8dVWCP.js → EngineEvents-CLckhYbL.js} +1 -0
  5. package/dist/{NeuralRetouch-KqGVPV7u.js → NeuralRetouch-ClaMiKYg.js} +1 -1
  6. package/dist/{NeuralSuperRes-BHxhYzlD.js → NeuralSuperRes-WNEXun9s.js} +2 -2
  7. package/dist/{PathTracer-Cldt0mXi.js → PathTracer-B9wZPPVL.js} +3197 -3167
  8. package/dist/{PhysicalSky-CUWrTcmT.js → PhysicalSky-DcjItqKi.js} +1 -1
  9. package/dist/{RayzeeRenderer-Dd-Dit0v.js → RayzeeRenderer-p-NeE6E2.js} +405 -437
  10. package/dist/addons/archives.es.js +1 -1
  11. package/dist/addons/bidirectional.es.js +1 -1
  12. package/dist/addons/physical-sky.es.js +1 -1
  13. package/dist/rayzee-core.es.js +5 -5
  14. package/dist/rayzee.es.js +1003 -1686
  15. package/dist/rayzee.umd.js +104 -143
  16. package/dist/{sceneCachePolicy--ta6moJu.js → sceneCachePolicy-B9w3BnaP.js} +2 -2
  17. package/package.json +2 -2
  18. package/src/EngineEvents.js +2 -0
  19. package/src/Passes/OIDNDenoiser.js +106 -100
  20. package/src/PathTracerApp.js +8 -19
  21. package/src/Pipeline/BindingAudit.js +2 -3
  22. package/src/Pipeline/PipelineContext.js +0 -1
  23. package/src/Pipeline/RenderPipeline.js +1 -2
  24. package/src/Pipeline/RenderStage.js +1 -1
  25. package/src/Processor/PackedRayBuffer.js +2 -2
  26. package/src/Processor/StorageTexturePool.js +1 -1
  27. package/src/Processor/TextureReadback.js +18 -1
  28. package/src/RayzeeRenderer.js +5 -5
  29. package/src/Stages/DenoiserSettings.js +5 -81
  30. package/src/Stages/MotionVector.js +21 -215
  31. package/src/Stages/NormalDepth.js +5 -5
  32. package/src/Stages/PathTracer.js +3 -11
  33. package/src/Stages/PathTracerStage.js +4 -17
  34. package/src/TSL/FinalWriteKernel.js +2 -2
  35. package/src/TSL/ShadeKernel.js +3 -3
  36. package/src/index.js +1 -1
  37. package/src/managers/DenoisingManager.js +93 -187
  38. package/src/Stages/ASVGF.js +0 -932
  39. package/src/Stages/BilateralFilter.js +0 -486
  40. package/src/Stages/EdgeFilter.js +0 -545
  41. package/src/Stages/Variance.js +0 -443
@@ -1,486 +0,0 @@
1
- import { Fn, wgslFn, vec3, vec4, float, int, uint, ivec2, uvec2, uniform, If, max, mix, sqrt,
2
- textureLoad, textureStore, localId, workgroupId } from 'three/tsl';
3
- import { RenderTarget, TextureNode, StorageTexture } from 'three/webgpu';
4
- import { HalfFloatType, RGBAFormat, LinearFilter, Box2, Vector2 } from 'three';
5
- import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
6
- import { luminance } from '../TSL/Common.js';
7
- import { MAX_STORAGE_TEXTURE_SIZE } from '../Processor/StorageTexturePool.js';
8
- import { ALBEDO_EPS } from './DenoiserSettings.js';
9
-
10
- // SVGF bilateral edge-stopping weight. All three φ params are relative
11
- // tolerances (unitless fractions) so the filter is scale-invariant across
12
- // scenes, HDR ranges, and camera distances. sigmaL is precomputed by the
13
- // caller as phiLum * √variance / albedoLum + ε, compensating for the
14
- // 1/albedo noise amplification introduced by demodulation.
15
- const bilateralWeight = /*@__PURE__*/ wgslFn( `
16
- fn bilateralWeight(
17
- centerLum: f32, sLum: f32,
18
- centerNormal: vec3f, sNormal: vec3f,
19
- centerDepth: f32, sDepth: f32,
20
- centerColor: vec3f, sColor: vec3f,
21
- kernelW: f32,
22
- sigmaL: f32, phiNorm: f32, phiDep: f32, phiCol: f32
23
- ) -> f32 {
24
-
25
- let lumW = exp( -abs( centerLum - sLum ) / sigmaL );
26
- // clamp dot to [0,1]: miss-ray normals decode to (-1,-1,-1) with
27
- // dot=3 → pow saturates to +inf → inf*0 = NaN. See project_tsl_pitfalls.
28
- // max(phiNorm, 1e-6): pow(0.0, 0.0) is undefined in WGSL and returned NaN on ~12%
29
- // of pixels when phiNormal was set to 0.
30
- let normW = pow( clamp( dot( centerNormal, sNormal ), 0.0, 1.0 ), max( phiNorm, 1e-6 ) );
31
- let depW = exp( -abs( centerDepth - sDepth ) / max( centerDepth * phiDep, 0.001 ) );
32
- let maxDiff = max( max( abs( centerColor.x - sColor.x ),
33
- abs( centerColor.y - sColor.y ) ),
34
- abs( centerColor.z - sColor.z ) );
35
- let avgLum = max( ( centerLum + sLum ) * 0.5, 0.0001 );
36
- let colW = exp( -( maxDiff / avgLum ) / max( phiCol, 0.0001 ) );
37
- return kernelW * lumW * normW * depW * colW;
38
-
39
- }
40
- ` );
41
-
42
- /**
43
- * BilateralFilter — 5×5 à-trous wavelet, edge-preserving, multi-iteration.
44
- *
45
- * Reads asvgf:demodulated (lighting), filters in demodulated space across
46
- * `iterations` ping-pong passes with step size 2^i, multiplies by albedo on
47
- * the final pass to remodulate. φ params are relative tolerances.
48
- *
49
- * Publishes: bilateralFiltering:output (modulated)
50
- * Reads: asvgf:demodulated (or fallback), pathtracer:normalDepth,
51
- * pathtracer:albedo, variance:output
52
- */
53
- export class BilateralFilter extends RenderStage {
54
-
55
- constructor( renderer, options = {} ) {
56
-
57
- super( 'BilateralFiltering', {
58
- ...options,
59
- executionMode: StageExecutionMode.ALWAYS
60
- } );
61
-
62
- this.renderer = renderer;
63
- this.inputTextureName = options.inputTextureName || 'asvgf:demodulated';
64
- this.normalDepthTextureName = options.normalDepthTextureName || 'pathtracer:normalDepth';
65
- // Mapped (normal/bump-perturbed) normal for the normal edge-stop — geometric
66
- // normals are flat across a normal-mapped surface so normW can't preserve bump detail.
67
- this.shadingNormalTextureName = options.shadingNormalTextureName || 'pathtracer:shadingNormal';
68
- this.albedoTextureName = options.albedoTextureName || 'pathtracer:albedo';
69
- this.varianceTextureName = options.varianceTextureName || 'variance:output';
70
- this.iterations = options.iterations ?? 4;
71
-
72
- // All φ are relative tolerances (fractions of mean/depth). Bigger =
73
- // more permissive blending across edges.
74
- this.phiColor = uniform( options.phiColor ?? 0.5 );
75
- this.phiNormal = uniform( options.phiNormal ?? 128.0 );
76
- this.phiDepth = uniform( options.phiDepth ?? 0.05 );
77
- this.phiLuminance = uniform( options.phiLuminance ?? 4.0 );
78
- // Blend Variance's spatial-variance channel into sigma_l (1 = max(temporal, spatial),
79
- // 0 = temporal-only). Widens the luminance gate where history is thin but the
80
- // neighbourhood is noisy (disocclusion). Default on — validated −1.7% RMSE @4spp.
81
- this.spatialVarianceWeight = uniform( options.spatialVarianceWeight ?? 1.0 );
82
- this.stepSizeU = uniform( 1, 'int' );
83
- // 1 on the final iteration → multiply by albedo to remodulate.
84
- this.isLastIterationU = uniform( 0, 'int' );
85
- // 1 on pass 0 → seed variance from variance:output; later passes read the carried value.
86
- this.isFirstIterationU = uniform( 1, 'int' );
87
- this.resW = uniform( options.width || 1 );
88
- this.resH = uniform( options.height || 1 );
89
-
90
- this._readTexNode = new TextureNode();
91
- this._normalDepthTexNode = new TextureNode();
92
- this._shadingNormalTexNode = new TextureNode();
93
- this._albedoTexNode = new TextureNode();
94
- this._varianceTexNode = new TextureNode();
95
- // pathtracer:color, read only for its alpha (coverage) on the final pass.
96
- this._srcAlphaTexNode = new TextureNode();
97
-
98
- const w = options.width || 1;
99
- const h = options.height || 1;
100
-
101
- // Pre-allocate StorageTextures at max — defensive against three.js #33061
102
- // (TSL compute pipeline keeps a stale GPUTextureView after setSize()).
103
-
104
- // LinearFilter required for textureLoad codegen on StorageTextures.
105
- this._storageTexA = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
106
- this._storageTexA.type = HalfFloatType;
107
- this._storageTexA.format = RGBAFormat;
108
- this._storageTexA.minFilter = LinearFilter;
109
- this._storageTexA.magFilter = LinearFilter;
110
-
111
- this._storageTexB = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
112
- this._storageTexB.type = HalfFloatType;
113
- this._storageTexB.format = RGBAFormat;
114
- this._storageTexB.minFilter = LinearFilter;
115
- this._storageTexB.magFilter = LinearFilter;
116
-
117
- this._srcRegion = new Box2( new Vector2( 0, 0 ), new Vector2( 0, 0 ) );
118
-
119
- // Active-size RT published downstream; over-allocated storage tex sampled
120
- // by UV would read the wrong region.
121
- this._outputTarget = new RenderTarget( w, h, {
122
- type: HalfFloatType,
123
- format: RGBAFormat,
124
- minFilter: LinearFilter,
125
- magFilter: LinearFilter,
126
- depthBuffer: false,
127
- stencilBuffer: false
128
- } );
129
-
130
- this._compiled = false;
131
-
132
- this._dispatchX = Math.ceil( w / 8 );
133
- this._dispatchY = Math.ceil( h / 8 );
134
-
135
- this._buildCompute();
136
-
137
- }
138
-
139
- // One compute node per ping-pong write direction.
140
- _buildCompute() {
141
-
142
- this._computeNodeA = this._buildComputeForDirection( this._storageTexA );
143
- this._computeNodeB = this._buildComputeForDirection( this._storageTexB );
144
-
145
- }
146
-
147
- _buildComputeForDirection( writeStorageTex ) {
148
-
149
- const readTexNode = this._readTexNode;
150
- const ndTexNode = this._normalDepthTexNode;
151
- const snTexNode = this._shadingNormalTexNode;
152
- const albedoTexNode = this._albedoTexNode;
153
- const varTexNode = this._varianceTexNode;
154
- const srcAlphaTexNode = this._srcAlphaTexNode;
155
- const phiColor = this.phiColor;
156
- const phiNormal = this.phiNormal;
157
- const phiDepth = this.phiDepth;
158
- const phiLuminance = this.phiLuminance;
159
- const spatialVarianceWeight = this.spatialVarianceWeight;
160
- const stepSize = this.stepSizeU;
161
- const isLastIterationU = this.isLastIterationU;
162
- const isFirstIterationU = this.isFirstIterationU;
163
- const resW = this.resW;
164
- const resH = this.resH;
165
-
166
- // 5×5 A-trous kernel weights (Gaussian approx, sum = 1.0)
167
- const kernel = [
168
- 1.0 / 256.0, 4.0 / 256.0, 6.0 / 256.0, 4.0 / 256.0, 1.0 / 256.0,
169
- 4.0 / 256.0, 16.0 / 256.0, 24.0 / 256.0, 16.0 / 256.0, 4.0 / 256.0,
170
- 6.0 / 256.0, 24.0 / 256.0, 36.0 / 256.0, 24.0 / 256.0, 6.0 / 256.0,
171
- 4.0 / 256.0, 16.0 / 256.0, 24.0 / 256.0, 16.0 / 256.0, 4.0 / 256.0,
172
- 1.0 / 256.0, 4.0 / 256.0, 6.0 / 256.0, 4.0 / 256.0, 1.0 / 256.0,
173
- ];
174
-
175
- const WG_SIZE = 8;
176
-
177
- const computeFn = Fn( () => {
178
-
179
- const gx = int( workgroupId.x ).mul( WG_SIZE ).add( int( localId.x ) );
180
- const gy = int( workgroupId.y ).mul( WG_SIZE ).add( int( localId.y ) );
181
-
182
- If( gx.lessThan( int( resW ) ).and( gy.lessThan( int( resH ) ) ), () => {
183
-
184
- const coord = ivec2( gx, gy );
185
- const centerSample = textureLoad( readTexNode, coord );
186
- const centerColor = centerSample.xyz;
187
- const centerND = textureLoad( ndTexNode, coord );
188
- // Normal edge-stop reads the mapped (shading) normal; depth gate stays geometric.
189
- const centerNormal = textureLoad( snTexNode, coord ).xyz.mul( 2.0 ).sub( 1.0 );
190
- const centerDepth = centerND.w;
191
- const centerLum = luminance( centerColor );
192
- const centerSafeAlbedo = max( textureLoad( albedoTexNode, coord ).xyz, vec3( ALBEDO_EPS ) );
193
- const centerAlbedoLum = max( luminance( centerSafeAlbedo ), float( ALBEDO_EPS ) ).toVar();
194
-
195
- // Variance is filtered alongside colour and carried between passes in the
196
- // ping-pong's spare alpha, so sigma_l tightens as the signal smooths. Without
197
- // it every pass re-read the unfiltered variance:output and filter strength
198
- // never backed off as the render converged.
199
- //
200
- // Stored as the ROOT: converged variance reaches ~1e-6, a half-float subnormal
201
- // that flushes to zero on some drivers. select() rather than If() — an If
202
- // around a texture read lets the inactive branch contaminate the result.
203
- // .z = temporal, .w = spatial 3×3; blending toward max widens sigma_l where
204
- // history is thin.
205
- const isFirst = isFirstIterationU.equal( int( 1 ) );
206
- const seedVariance = ( at ) => {
207
-
208
- const v = textureLoad( varTexNode, at );
209
- return mix( v.z, max( v.z, v.w ), spatialVarianceWeight );
210
-
211
- };
212
-
213
- const centerVariance = isFirst.select(
214
- seedVariance( coord ),
215
- centerSample.w.mul( centerSample.w )
216
- ).toVar();
217
-
218
- // sigma_l = phiLum * √variance / albedoLum + ε. Dividing by
219
- // albedoLum compensates for the 1/albedo noise amplification
220
- // from demodulation — otherwise dark materials get an
221
- // under-estimated sigma → over-strict luminance gate → no
222
- // blending → silhouette dark-outline artifact.
223
- const sigmaL = phiLuminance
224
- .mul( sqrt( max( centerVariance, float( 0.0 ) ) ) )
225
- .div( centerAlbedoLum )
226
- .add( float( 0.0001 ) );
227
-
228
- const colorSum = vec3( 0.0 ).toVar();
229
- const weightSum = float( 0.0 ).toVar();
230
- // Squared weights, normalised by weightSum² — the variance of a weighted mean.
231
- const varianceSum = float( 0.0 ).toVar();
232
-
233
- // 5×5 à-trous kernel (Gaussian-approx, Σ=1)
234
- for ( let iy = 0; iy < 5; iy ++ ) {
235
-
236
- for ( let ix = 0; ix < 5; ix ++ ) {
237
-
238
- const dx = ix - 2;
239
- const dy = iy - 2;
240
- const kw = kernel[ iy * 5 + ix ];
241
-
242
- const sx = gx.add( stepSize.mul( dx ) )
243
- .clamp( int( 0 ), int( resW ).sub( 1 ) );
244
- const sy = gy.add( stepSize.mul( dy ) )
245
- .clamp( int( 0 ), int( resH ).sub( 1 ) );
246
-
247
- const sSample = textureLoad( readTexNode, ivec2( sx, sy ) );
248
- const sColor = sSample.xyz;
249
- const sND = textureLoad( ndTexNode, ivec2( sx, sy ) );
250
- const sNormal = textureLoad( snTexNode, ivec2( sx, sy ) ).xyz.mul( 2.0 ).sub( 1.0 );
251
- const sDepth = sND.w;
252
- const sLum = luminance( sColor );
253
-
254
- const w = bilateralWeight(
255
- centerLum, sLum,
256
- centerNormal, sNormal,
257
- centerDepth, sDepth,
258
- centerColor, sColor,
259
- float( kw ),
260
- sigmaL, phiNormal, phiDepth, phiColor
261
- );
262
-
263
- const sVariance = isFirst.select(
264
- seedVariance( ivec2( sx, sy ) ),
265
- sSample.w.mul( sSample.w )
266
- );
267
-
268
- colorSum.addAssign( sColor.mul( w ) );
269
- weightSum.addAssign( w );
270
- varianceSum.addAssign( w.mul( w ).mul( max( sVariance, float( 0.0 ) ) ) );
271
-
272
- }
273
-
274
- }
275
-
276
- const filtered = colorSum.div( max( weightSum, float( 0.0001 ) ) );
277
- const filteredVariance = varianceSum
278
- .div( max( weightSum.mul( weightSum ), float( 1e-8 ) ) );
279
-
280
- // Remodulate by albedo only on the final iteration so the
281
- // inner ping-pong stays in demodulated space.
282
- const isLast = isLastIterationU.equal( int( 1 ) );
283
- const output = isLast.select( filtered.mul( centerSafeAlbedo ), filtered );
284
-
285
- // The last pass restores the path tracer's coverage — Compositor reads .w as
286
- // image alpha when transparentBackground is on.
287
- const carriedAlpha = isLast.select(
288
- textureLoad( srcAlphaTexNode, coord ).w,
289
- sqrt( max( filteredVariance, float( 0.0 ) ) )
290
- );
291
-
292
- textureStore(
293
- writeStorageTex,
294
- uvec2( uint( gx ), uint( gy ) ),
295
- vec4( output, carriedAlpha )
296
- ).toWriteOnly();
297
-
298
- } );
299
-
300
- } );
301
-
302
- return computeFn().compute(
303
- [ this._dispatchX, this._dispatchY, 1 ],
304
- [ WG_SIZE, WG_SIZE, 1 ]
305
- );
306
-
307
- }
308
-
309
- render( context ) {
310
-
311
- if ( ! this.enabled ) return;
312
-
313
- const inputTex = context.getTexture( this.inputTextureName )
314
- || context.getTexture( 'asvgf:output' )
315
- || context.getTexture( 'pathtracer:color' );
316
- const ndTex = context.getTexture( this.normalDepthTextureName );
317
- // Fall back to geometric normalDepth if the mapped normal isn't published.
318
- const snTex = context.getTexture( this.shadingNormalTextureName ) || ndTex;
319
- const albedoTex = context.getTexture( this.albedoTextureName );
320
- const varTex = context.getTexture( this.varianceTextureName );
321
- const srcAlphaTex = context.getTexture( 'pathtracer:color' );
322
-
323
- if ( ! inputTex ) return;
324
-
325
- const img = inputTex.image;
326
- if ( img && img.width > 0 && img.height > 0 ) {
327
-
328
- // Compare against an active-size RT, not the fixed-2048 StorageTexture.
329
- if ( img.width !== this._outputTarget.width ||
330
- img.height !== this._outputTarget.height ) {
331
-
332
- this.setSize( img.width, img.height );
333
-
334
- }
335
-
336
- }
337
-
338
- // RenderTarget textures — safe to bind before first-compile.
339
- if ( ndTex ) this._normalDepthTexNode.value = ndTex;
340
- if ( snTex ) this._shadingNormalTexNode.value = snTex;
341
- if ( albedoTex ) this._albedoTexNode.value = albedoTex;
342
- // Bind these BEFORE compile too: two TextureNodes still holding the default empty
343
- // texture when the node builder runs share one GPU binding, which then resolves to
344
- // whichever is assigned last. That aliased varTexNode onto the colour input.
345
- //
346
- // Safe only because inputTex is a RenderTarget texture, which produces the same
347
- // `textureLoad(t, coord, level)` codegen as EmptyTexture — the form the StorageTexture
348
- // ping-pong needs on passes 1+. Binding an actual StorageTexture here would emit the
349
- // no-level form and later reads would return zero. EdgeFilter must NOT do this: its
350
- // pass-0 input IS a StorageTexture.
351
- this._readTexNode.value = inputTex;
352
- if ( varTex ) this._varianceTexNode.value = varTex;
353
- if ( srcAlphaTex ) this._srcAlphaTexNode.value = srcAlphaTex;
354
-
355
- // First-frame compile while StorageTexture-typed nodes still hold
356
- // EmptyTexture — codegen then emits textureLoad with the level
357
- // parameter, which the runtime requires for non-zero reads.
358
- if ( ! this._compiled ) {
359
-
360
- this.renderer.compute( this._computeNodeA );
361
- this.renderer.compute( this._computeNodeB );
362
- this._compiled = true;
363
-
364
- }
365
-
366
- // À-trous iterations: step size 2^i, ping-pong write direction.
367
- // Last iteration multiplies by albedo to remodulate.
368
- let readTex = inputTex;
369
- let writeNode = this._computeNodeA;
370
- let nextWriteNode = this._computeNodeB;
371
-
372
- for ( let i = 0; i < this.iterations; i ++ ) {
373
-
374
- this.stepSizeU.value = 1 << i;
375
- this._readTexNode.value = readTex;
376
- this.isLastIterationU.value = ( i === this.iterations - 1 ) ? 1 : 0;
377
- this.isFirstIterationU.value = i === 0 ? 1 : 0;
378
-
379
- this.renderer.compute( writeNode );
380
-
381
- readTex = ( writeNode === this._computeNodeA )
382
- ? this._storageTexA
383
- : this._storageTexB;
384
-
385
- const tmp = writeNode;
386
- writeNode = nextWriteNode;
387
- nextWriteNode = tmp;
388
-
389
- }
390
-
391
- // Copy the final result out of the over-allocated StorageTexture into
392
- // the active-size RenderTarget; downstream stages UV-sample the latter.
393
- this._srcRegion.max.set( this._outputTarget.width, this._outputTarget.height );
394
- this.renderer.copyTextureToTexture( readTex, this._outputTarget.texture, this._srcRegion );
395
-
396
- context.setTexture( 'bilateralFiltering:output', this._outputTarget.texture );
397
-
398
- }
399
-
400
- // Accepts the same keys as ASVGF presets; unknown keys ignored.
401
- updateParameters( params ) {
402
-
403
- if ( ! params ) return;
404
- if ( params.phiColor !== undefined ) this.phiColor.value = params.phiColor;
405
- if ( params.phiNormal !== undefined ) this.phiNormal.value = params.phiNormal;
406
- if ( params.phiDepth !== undefined ) this.phiDepth.value = params.phiDepth;
407
- if ( params.phiLuminance !== undefined ) this.phiLuminance.value = params.phiLuminance;
408
- if ( params.spatialVarianceWeight !== undefined ) this.spatialVarianceWeight.value = params.spatialVarianceWeight;
409
- if ( params.atrousIterations !== undefined ) this.iterations = params.atrousIterations;
410
-
411
- }
412
-
413
- setSize( width, height ) {
414
-
415
- // StorageTextures stay at their max allocation (see constructor).
416
- this._outputTarget.setSize( width, height );
417
- this._outputTarget.texture.needsUpdate = true;
418
- this.resW.value = width;
419
- this.resH.value = height;
420
-
421
- // Update dispatch dimensions
422
- this._dispatchX = Math.ceil( width / 8 );
423
- this._dispatchY = Math.ceil( height / 8 );
424
- this._computeNodeA.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
425
- this._computeNodeB.dispatchSize = [ this._dispatchX, this._dispatchY, 1 ];
426
-
427
- }
428
-
429
- // Free the 2048² StorageTextures when disabled; three.js re-creates them on the next dispatch
430
- // after re-enable (no temporal state to re-anchor). See ASVGF.releaseGPUMemory.
431
- releaseGPUMemory() {
432
-
433
- this._storageTexA?.dispose();
434
- this._storageTexB?.dispose();
435
- // Render-res RT texture (dispose .texture, not the RT — RT.dispose() doesn't free it here).
436
- this.context?.removeTexture( 'bilateralFiltering:output' );
437
- this._outputTarget?.texture?.dispose();
438
-
439
- }
440
-
441
- reset() {
442
-
443
- // No temporal state to reset
444
-
445
- }
446
-
447
- // Reserved-storage change (e.g. 4K toggle): recreate the MAX-preallocated StorageTextures at the new live
448
- // reserved size + rebuild the compute nodes in place. Same stage object, so refs/wiring stay valid.
449
- reallocateReservedStorage() {
450
-
451
- this._computeNodeA?.dispose();
452
- this._computeNodeB?.dispose();
453
- this._storageTexA?.dispose();
454
- this._storageTexB?.dispose();
455
- const mk = () => {
456
-
457
- const t = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
458
- t.type = HalfFloatType; t.format = RGBAFormat; t.minFilter = LinearFilter; t.magFilter = LinearFilter;
459
- return t;
460
-
461
- };
462
-
463
- this._storageTexA = mk();
464
- this._storageTexB = mk();
465
- this._buildCompute();
466
- this._compiled = false;
467
-
468
- }
469
-
470
- dispose() {
471
-
472
- this._computeNodeA?.dispose();
473
- this._computeNodeB?.dispose();
474
- this._storageTexA?.dispose();
475
- this._storageTexB?.dispose();
476
- this._outputTarget?.dispose();
477
- this._readTexNode?.dispose();
478
- this._normalDepthTexNode?.dispose();
479
- this._shadingNormalTexNode?.dispose();
480
- this._albedoTexNode?.dispose();
481
- this._varianceTexNode?.dispose();
482
- this._srcAlphaTexNode?.dispose();
483
-
484
- }
485
-
486
- }