rayzee 5.5.0 → 5.6.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.
- package/dist/rayzee.es.js +2064 -2014
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +5 -5
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/Passes/AIUpscaler.js +22 -0
- package/src/Passes/OIDNDenoiser.js +93 -28
- package/src/Pipeline/RenderPipeline.js +3 -0
- package/src/Stages/ASVGF.js +18 -0
- package/src/Stages/AdaptiveSampling.js +2 -0
- package/src/Stages/AutoExposure.js +162 -86
- package/src/Stages/BilateralFilter.js +2 -0
- package/src/Stages/Display.js +1 -0
- package/src/Stages/EdgeFilter.js +1 -0
- package/src/Stages/MotionVector.js +1 -0
- package/src/Stages/SSRC.js +6 -0
- package/src/Stages/Variance.js +3 -0
- package/src/managers/DenoisingManager.js +68 -14
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { Fn, wgslFn, vec4, float, int, uint, ivec2, uvec2, uniform, If, max,
|
|
2
|
-
textureLoad, textureStore,
|
|
3
|
-
attributeArray } from 'three/tsl';
|
|
2
|
+
textureLoad, textureStore, workgroupBarrier, localId, workgroupId,
|
|
3
|
+
attributeArray, atomicAdd, atomicStore, atomicLoad, Loop } from 'three/tsl';
|
|
4
4
|
import { RenderTarget, TextureNode, StorageTexture, ReadbackBuffer } from 'three/webgpu';
|
|
5
5
|
import { FloatType, RGBAFormat, NearestFilter } from 'three';
|
|
6
6
|
import { RenderStage, StageExecutionMode } from '../Pipeline/RenderStage.js';
|
|
7
7
|
import { luminance } from '../TSL/Common.js';
|
|
8
8
|
|
|
9
|
+
// ── Histogram constants ────────────────────────────────────
|
|
10
|
+
const NUM_BINS = 256;
|
|
11
|
+
const MIN_LOG_LUM = - 8.0; // ln(~0.00034) — very dark
|
|
12
|
+
const MAX_LOG_LUM = 6.0; // ln(~403) — bright specular
|
|
13
|
+
const LOG_LUM_RANGE = MAX_LOG_LUM - MIN_LOG_LUM; // 14 nats ≈ 20 stops
|
|
14
|
+
const BIN_WIDTH = LOG_LUM_RANGE / NUM_BINS;
|
|
15
|
+
const WEIGHT_SCALE = 10000; // float → uint quantisation for metering weights
|
|
16
|
+
|
|
17
|
+
// ── Metering ────────────────────────────────────────────────
|
|
18
|
+
// Centre-weighted Gaussian is the only mode — spot and uniform
|
|
19
|
+
// are unnecessary given the percentile clipping already handles
|
|
20
|
+
// extreme highlights/shadows. The centerWeight uniform controls
|
|
21
|
+
// the Gaussian falloff steepness.
|
|
22
|
+
|
|
9
23
|
// ── wgslFn helpers ──────────────────────────────────────────
|
|
10
24
|
|
|
11
25
|
/**
|
|
@@ -45,31 +59,30 @@ const adaptExposure = /*@__PURE__*/ wgslFn( `
|
|
|
45
59
|
` );
|
|
46
60
|
|
|
47
61
|
/**
|
|
48
|
-
* WebGPU Auto-Exposure Stage
|
|
62
|
+
* WebGPU Auto-Exposure Stage — Histogram-Based with Centre-Weighted Metering
|
|
49
63
|
*
|
|
50
64
|
* GPU-based automatic exposure control with human eye-like adaptation.
|
|
51
|
-
* Uses
|
|
65
|
+
* Uses histogram-based luminance analysis with percentile clipping
|
|
66
|
+
* and centre-weighted spatial metering for robust exposure estimation.
|
|
52
67
|
*
|
|
53
68
|
* Algorithm:
|
|
54
|
-
* 1. Downsample (compute): full res → 64×64 log-luminance
|
|
55
|
-
* 2.
|
|
56
|
-
* Single workgroup of 256 threads
|
|
57
|
-
*
|
|
58
|
-
* 3.
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
* WebGPU advantage: async readback (no GPU pipeline stall).
|
|
65
|
-
* 1-frame delay is imperceptible for slowly-changing exposure.
|
|
69
|
+
* 1. Downsample (compute): full res → 64×64 log-luminance grid
|
|
70
|
+
* 2. Histogram (compute): build 256-bin weighted histogram from the 64×64
|
|
71
|
+
* grid. Single workgroup of 256 threads; each loads 16 texels, applies
|
|
72
|
+
* centre-weighted Gaussian, and scatters via atomicAdd into a storage buffer.
|
|
73
|
+
* 3. Analyze (compute): single thread reads the histogram, computes CDF,
|
|
74
|
+
* extracts percentile-clipped weighted mean (ignoring bottom/top
|
|
75
|
+
* extremes), and writes the geometric mean to a 1×1 storage texture.
|
|
76
|
+
* 4. Adaptation (compute): temporal smoothing with prev exposure; writes
|
|
77
|
+
* vec4(exposure, luminance, targetExposure, 1) into a 1-element buffer.
|
|
78
|
+
* 5. Async readback via `renderer.getArrayBufferAsync(attr, ReadbackBuffer)`.
|
|
66
79
|
*
|
|
67
80
|
* Execution: ALWAYS
|
|
68
81
|
*
|
|
69
82
|
* Events listened:
|
|
70
83
|
* pipeline:reset — reset temporal history
|
|
71
84
|
* autoexposure:toggle — enable/disable
|
|
72
|
-
* autoexposure:updateParameters — update key value, speeds, bounds
|
|
85
|
+
* autoexposure:updateParameters — update key value, speeds, bounds, percentiles
|
|
73
86
|
*
|
|
74
87
|
* Textures published: (none — publishes state, not textures)
|
|
75
88
|
* Textures read: edgeFiltering:output > asvgf:output > pathtracer:color
|
|
@@ -101,6 +114,12 @@ export class AutoExposure extends RenderStage {
|
|
|
101
114
|
this.isFirstFrameU = uniform( 1.0 ); // 1.0 = true
|
|
102
115
|
this.previousExposureU = uniform( options.initialExposure ?? 1.0 );
|
|
103
116
|
|
|
117
|
+
// ── Histogram & metering uniforms ────────────────
|
|
118
|
+
|
|
119
|
+
this.lowPercentileU = uniform( options.lowPercentile ?? 0.10 );
|
|
120
|
+
this.highPercentileU = uniform( options.highPercentile ?? 0.90 );
|
|
121
|
+
this.centerWeightU = uniform( options.centerWeight ?? 8.0 );
|
|
122
|
+
|
|
104
123
|
// ── Input resolution uniforms (for downsample compute) ──
|
|
105
124
|
|
|
106
125
|
this.inputResW = uniform( 1 );
|
|
@@ -149,14 +168,14 @@ export class AutoExposure extends RenderStage {
|
|
|
149
168
|
this._downsampleStorageTex.minFilter = NearestFilter;
|
|
150
169
|
this._downsampleStorageTex.magFilter = NearestFilter;
|
|
151
170
|
|
|
152
|
-
// 1×1 StorageTexture for
|
|
171
|
+
// 1×1 StorageTexture for histogram analysis output
|
|
153
172
|
this._reductionStorageTex = new StorageTexture( 1, 1 );
|
|
154
173
|
this._reductionStorageTex.type = FloatType;
|
|
155
174
|
this._reductionStorageTex.format = RGBAFormat;
|
|
156
175
|
this._reductionStorageTex.minFilter = NearestFilter;
|
|
157
176
|
this._reductionStorageTex.magFilter = NearestFilter;
|
|
158
177
|
|
|
159
|
-
// 1×1 RenderTarget — readable copy of
|
|
178
|
+
// 1×1 RenderTarget — readable copy of analysis output (cross-dispatch reads
|
|
160
179
|
// from StorageTexture return zeros — must copy to RenderTarget first)
|
|
161
180
|
this._reductionReadTarget = new RenderTarget( 1, 1, rtOpts );
|
|
162
181
|
|
|
@@ -167,6 +186,9 @@ export class AutoExposure extends RenderStage {
|
|
|
167
186
|
this._readbackBuffer = new ReadbackBuffer( 16 );
|
|
168
187
|
this._readbackBuffer.name = 'AutoExposureAdaptation';
|
|
169
188
|
|
|
189
|
+
// ── Histogram storage buffer (atomic uint, 256 bins) ─────
|
|
190
|
+
this._histogramBuffer = attributeArray( NUM_BINS, 'uint' ).toAtomic();
|
|
191
|
+
|
|
170
192
|
}
|
|
171
193
|
|
|
172
194
|
// ──────────────────────────────────────────────────
|
|
@@ -176,7 +198,8 @@ export class AutoExposure extends RenderStage {
|
|
|
176
198
|
_buildCompute() {
|
|
177
199
|
|
|
178
200
|
this._buildDownsampleCompute();
|
|
179
|
-
this.
|
|
201
|
+
this._buildHistogramCompute();
|
|
202
|
+
this._buildHistogramAnalyzeCompute();
|
|
180
203
|
this._buildAdaptationCompute();
|
|
181
204
|
|
|
182
205
|
}
|
|
@@ -257,94 +280,146 @@ export class AutoExposure extends RenderStage {
|
|
|
257
280
|
}
|
|
258
281
|
|
|
259
282
|
/**
|
|
260
|
-
*
|
|
283
|
+
* Histogram Build (compute): 64×64 downsample → 256-bin weighted histogram
|
|
261
284
|
*
|
|
262
|
-
* Single workgroup of 256 threads. Each thread
|
|
263
|
-
*
|
|
264
|
-
*
|
|
285
|
+
* Single workgroup of 256 threads. Each thread processes 16 texels from
|
|
286
|
+
* the downsample grid, applies spatial metering weight, and atomically
|
|
287
|
+
* scatters into the histogram storage buffer.
|
|
265
288
|
*
|
|
266
|
-
*
|
|
289
|
+
* Phase 1: Clear all 256 bins (one per thread)
|
|
290
|
+
* Phase 2: Build histogram with metering-weighted atomic scatter
|
|
267
291
|
*/
|
|
268
|
-
|
|
292
|
+
_buildHistogramCompute() {
|
|
269
293
|
|
|
270
294
|
const downsampleTex = this._downsampleTarget.texture;
|
|
271
|
-
const
|
|
295
|
+
const histogram = this._histogramBuffer;
|
|
296
|
+
const centerWeight = this.centerWeightU;
|
|
272
297
|
|
|
273
298
|
const WGSIZE = 256;
|
|
274
299
|
const TEXELS_PER_THREAD = 16; // 4096 / 256
|
|
275
300
|
const TEX_SIZE = 64;
|
|
276
301
|
|
|
277
|
-
const
|
|
278
|
-
const sharedCount = workgroupArray( 'float', WGSIZE );
|
|
279
|
-
|
|
280
|
-
const reductionFn = Fn( () => {
|
|
302
|
+
const computeFn = Fn( () => {
|
|
281
303
|
|
|
282
304
|
const tid = localId.x;
|
|
283
305
|
|
|
284
|
-
// ── Phase 1:
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
const threadCount = float( 0.0 ).toVar();
|
|
306
|
+
// ── Phase 1: Clear histogram ──────────────────
|
|
307
|
+
atomicStore( histogram.element( tid ), uint( 0 ) );
|
|
308
|
+
workgroupBarrier();
|
|
288
309
|
|
|
289
|
-
|
|
310
|
+
// ── Phase 2: Build histogram ──────────────────
|
|
311
|
+
for ( let t = 0; t < TEXELS_PER_THREAD; t ++ ) {
|
|
290
312
|
|
|
291
|
-
const linearIdx = tid.mul( TEXELS_PER_THREAD ).add(
|
|
313
|
+
const linearIdx = tid.mul( TEXELS_PER_THREAD ).add( t );
|
|
292
314
|
const px = linearIdx.mod( TEX_SIZE );
|
|
293
315
|
const py = linearIdx.div( TEX_SIZE );
|
|
316
|
+
|
|
294
317
|
const data = textureLoad( downsampleTex, ivec2( int( px ), int( py ) ) );
|
|
318
|
+
const logLumSum = data.x;
|
|
319
|
+
const validCount = data.y;
|
|
320
|
+
|
|
321
|
+
If( validCount.greaterThan( 0.0 ), () => {
|
|
295
322
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
323
|
+
// Per-cell average log-luminance (natural log, matches downsample output)
|
|
324
|
+
const avgLogLum = logLumSum.div( validCount );
|
|
325
|
+
|
|
326
|
+
// Map to histogram bin [0, NUM_BINS-1]
|
|
327
|
+
const normalized = avgLogLum.sub( float( MIN_LOG_LUM ) ).div( float( LOG_LUM_RANGE ) );
|
|
328
|
+
const bin = uint( normalized.mul( float( NUM_BINS ) ).floor().clamp( 0.0, float( NUM_BINS - 1 ) ) );
|
|
329
|
+
|
|
330
|
+
// ── Centre-weighted metering ──────────
|
|
331
|
+
const uvx = float( px ).add( 0.5 ).div( float( TEX_SIZE ) );
|
|
332
|
+
const uvy = float( py ).add( 0.5 ).div( float( TEX_SIZE ) );
|
|
333
|
+
const dx = uvx.sub( 0.5 );
|
|
334
|
+
const dy = uvy.sub( 0.5 );
|
|
335
|
+
const dist2 = dx.mul( dx ).add( dy.mul( dy ) );
|
|
336
|
+
|
|
337
|
+
// Gaussian falloff: 1.0 at centre, ~0.02 at corners
|
|
338
|
+
const weight = dist2.mul( centerWeight ).negate().exp();
|
|
339
|
+
|
|
340
|
+
const weightUint = uint( weight.mul( float( WEIGHT_SCALE ) ) );
|
|
341
|
+
atomicAdd( histogram.element( bin ), weightUint );
|
|
342
|
+
|
|
343
|
+
} );
|
|
299
344
|
|
|
300
345
|
}
|
|
301
346
|
|
|
302
|
-
|
|
303
|
-
sharedCount.element( tid ).assign( threadCount );
|
|
347
|
+
} );
|
|
304
348
|
|
|
305
|
-
|
|
306
|
-
// JS for-loop unrolls at shader build time
|
|
349
|
+
this._histogramComputeNode = computeFn().compute( [ 1, 1, 1 ], [ WGSIZE, 1, 1 ] );
|
|
307
350
|
|
|
308
|
-
|
|
351
|
+
}
|
|
309
352
|
|
|
310
|
-
|
|
353
|
+
/**
|
|
354
|
+
* Histogram Analysis (compute): extract percentile-clipped geometric mean
|
|
355
|
+
*
|
|
356
|
+
* Single thread. Reads the 256-bin histogram, computes the CDF, clips
|
|
357
|
+
* the bottom and top percentiles, and computes the weighted geometric
|
|
358
|
+
* mean of luminance within the accepted range.
|
|
359
|
+
*
|
|
360
|
+
* Output: StorageTexture(1×1) = vec4(geometricMean, totalCount, avgLogLum, 1)
|
|
361
|
+
*/
|
|
362
|
+
_buildHistogramAnalyzeCompute() {
|
|
311
363
|
|
|
312
|
-
|
|
364
|
+
const histogram = this._histogramBuffer;
|
|
365
|
+
const outputTex = this._reductionStorageTex;
|
|
366
|
+
const lowPercentile = this.lowPercentileU;
|
|
367
|
+
const highPercentile = this.highPercentileU;
|
|
313
368
|
|
|
314
|
-
|
|
315
|
-
sharedLogSum.element( tid.add( uint( stride ) ) )
|
|
316
|
-
);
|
|
317
|
-
sharedCount.element( tid ).addAssign(
|
|
318
|
-
sharedCount.element( tid.add( uint( stride ) ) )
|
|
319
|
-
);
|
|
369
|
+
const computeFn = Fn( () => {
|
|
320
370
|
|
|
321
|
-
|
|
371
|
+
// ── Pass 1: compute total weight ──────────────
|
|
372
|
+
const totalWeight = float( 0.0 ).toVar();
|
|
322
373
|
|
|
323
|
-
}
|
|
374
|
+
Loop( NUM_BINS, ( { i } ) => {
|
|
324
375
|
|
|
325
|
-
|
|
376
|
+
totalWeight.addAssign( float( atomicLoad( histogram.element( i ) ) ) );
|
|
326
377
|
|
|
327
|
-
|
|
378
|
+
} );
|
|
379
|
+
|
|
380
|
+
// Percentile thresholds (in quantised weight units)
|
|
381
|
+
const lowThreshold = totalWeight.mul( lowPercentile );
|
|
382
|
+
const highThreshold = totalWeight.mul( highPercentile );
|
|
383
|
+
|
|
384
|
+
// ── Pass 2: percentile-clipped weighted mean ──
|
|
385
|
+
const cumWeight = float( 0.0 ).toVar();
|
|
386
|
+
const logLumAccum = float( 0.0 ).toVar();
|
|
387
|
+
const validWeight = float( 0.0 ).toVar();
|
|
388
|
+
const prevCum = float( 0.0 ).toVar();
|
|
389
|
+
|
|
390
|
+
Loop( NUM_BINS, ( { i } ) => {
|
|
391
|
+
|
|
392
|
+
const binWeight = float( atomicLoad( histogram.element( i ) ) );
|
|
393
|
+
prevCum.assign( cumWeight );
|
|
394
|
+
cumWeight.addAssign( binWeight );
|
|
328
395
|
|
|
329
|
-
|
|
396
|
+
// Include bin if it overlaps the [lowThreshold, highThreshold] range
|
|
397
|
+
If( prevCum.lessThan( highThreshold ).and( cumWeight.greaterThan( lowThreshold ) ), () => {
|
|
330
398
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
399
|
+
// Bin centre in log-luminance space
|
|
400
|
+
const binCenter = float( MIN_LOG_LUM ).add(
|
|
401
|
+
float( i ).add( 0.5 ).mul( float( BIN_WIDTH ) )
|
|
402
|
+
);
|
|
403
|
+
logLumAccum.addAssign( binCenter.mul( binWeight ) );
|
|
404
|
+
validWeight.addAssign( binWeight );
|
|
336
405
|
|
|
337
|
-
|
|
338
|
-
outputTex,
|
|
339
|
-
uvec2( uint( 0 ), uint( 0 ) ),
|
|
340
|
-
vec4( geometricMean, totalCount, avgLogLum, 1.0 )
|
|
341
|
-
).toWriteOnly();
|
|
406
|
+
} );
|
|
342
407
|
|
|
343
408
|
} );
|
|
344
409
|
|
|
410
|
+
const safeWeight = max( validWeight, float( 1.0 ) );
|
|
411
|
+
const avgLogLum = logLumAccum.div( safeWeight );
|
|
412
|
+
const geometricMean = avgLogLum.exp();
|
|
413
|
+
|
|
414
|
+
textureStore(
|
|
415
|
+
outputTex,
|
|
416
|
+
uvec2( uint( 0 ), uint( 0 ) ),
|
|
417
|
+
vec4( geometricMean, totalWeight, avgLogLum, 1.0 )
|
|
418
|
+
).toWriteOnly();
|
|
419
|
+
|
|
345
420
|
} );
|
|
346
421
|
|
|
347
|
-
this.
|
|
422
|
+
this._histogramAnalyzeNode = computeFn().compute( 1, [ 1, 1, 1 ] );
|
|
348
423
|
|
|
349
424
|
}
|
|
350
425
|
|
|
@@ -352,7 +427,7 @@ export class AutoExposure extends RenderStage {
|
|
|
352
427
|
* Adaptation (compute): temporal smoothing
|
|
353
428
|
*
|
|
354
429
|
* Single-thread compute dispatch [1, 1, 1], workgroup [1, 1, 1].
|
|
355
|
-
* Reads geometric mean from
|
|
430
|
+
* Reads geometric mean from analysis RenderTarget, applies asymmetric
|
|
356
431
|
* temporal smoothing using the previous-exposure uniform, and writes
|
|
357
432
|
* vec4(exposure, luminance, targetExposure, 1) into a 1-element storage
|
|
358
433
|
* buffer which the CPU reads via getArrayBufferAsync + ReadbackBuffer.
|
|
@@ -372,7 +447,7 @@ export class AutoExposure extends RenderStage {
|
|
|
372
447
|
|
|
373
448
|
const computeFn = Fn( () => {
|
|
374
449
|
|
|
375
|
-
// Read geometric mean from
|
|
450
|
+
// Read geometric mean from histogram analysis result (1×1 RenderTarget)
|
|
376
451
|
const geoMean = textureLoad( reductionTex, ivec2( int( 0 ), int( 0 ) ) ).x;
|
|
377
452
|
|
|
378
453
|
const result = adaptExposure(
|
|
@@ -404,16 +479,7 @@ export class AutoExposure extends RenderStage {
|
|
|
404
479
|
|
|
405
480
|
} );
|
|
406
481
|
|
|
407
|
-
this.on( 'autoexposure:updateParameters', ( data ) =>
|
|
408
|
-
|
|
409
|
-
if ( ! data ) return;
|
|
410
|
-
if ( data.keyValue !== undefined ) this.keyValueU.value = data.keyValue;
|
|
411
|
-
if ( data.minExposure !== undefined ) this.minExposureU.value = data.minExposure;
|
|
412
|
-
if ( data.maxExposure !== undefined ) this.maxExposureU.value = data.maxExposure;
|
|
413
|
-
if ( data.adaptSpeedBright !== undefined ) this.adaptSpeedBrightU.value = data.adaptSpeedBright;
|
|
414
|
-
if ( data.adaptSpeedDark !== undefined ) this.adaptSpeedDarkU.value = data.adaptSpeedDark;
|
|
415
|
-
|
|
416
|
-
} );
|
|
482
|
+
this.on( 'autoexposure:updateParameters', ( data ) => data && this.updateParameters( data ) );
|
|
417
483
|
|
|
418
484
|
}
|
|
419
485
|
|
|
@@ -450,12 +516,16 @@ export class AutoExposure extends RenderStage {
|
|
|
450
516
|
this.renderer.compute( this._downsampleComputeNode );
|
|
451
517
|
this.renderer.copyTextureToTexture( this._downsampleStorageTex, this._downsampleTarget.texture );
|
|
452
518
|
|
|
453
|
-
// ── Pass 2:
|
|
519
|
+
// ── Pass 2: Histogram build (compute) ───────────────
|
|
520
|
+
|
|
521
|
+
this.renderer.compute( this._histogramComputeNode );
|
|
522
|
+
|
|
523
|
+
// ── Pass 3: Histogram analysis → 1×1 result ─────────
|
|
454
524
|
|
|
455
|
-
this.renderer.compute( this.
|
|
525
|
+
this.renderer.compute( this._histogramAnalyzeNode );
|
|
456
526
|
this.renderer.copyTextureToTexture( this._reductionStorageTex, this._reductionReadTarget.texture );
|
|
457
527
|
|
|
458
|
-
// ── Pass
|
|
528
|
+
// ── Pass 4: Temporal adaptation (compute) ───────────
|
|
459
529
|
|
|
460
530
|
this._reductionReadTexNode.value = this._reductionReadTarget.texture;
|
|
461
531
|
this.renderer.compute( this._adaptationComputeNode );
|
|
@@ -578,7 +648,7 @@ export class AutoExposure extends RenderStage {
|
|
|
578
648
|
|
|
579
649
|
setSize( /* width, height */ ) {
|
|
580
650
|
|
|
581
|
-
// Downsample and
|
|
651
|
+
// Downsample and histogram targets are fixed-size (64×64 → 256 bins → 1×1)
|
|
582
652
|
// No resizing needed — the downsample compute shader reads input
|
|
583
653
|
// resolution from uniforms and computes block sizes dynamically.
|
|
584
654
|
|
|
@@ -611,19 +681,25 @@ export class AutoExposure extends RenderStage {
|
|
|
611
681
|
if ( params.maxExposure !== undefined ) this.maxExposureU.value = params.maxExposure;
|
|
612
682
|
if ( params.adaptSpeedBright !== undefined ) this.adaptSpeedBrightU.value = params.adaptSpeedBright;
|
|
613
683
|
if ( params.adaptSpeedDark !== undefined ) this.adaptSpeedDarkU.value = params.adaptSpeedDark;
|
|
684
|
+
if ( params.lowPercentile !== undefined ) this.lowPercentileU.value = params.lowPercentile;
|
|
685
|
+
if ( params.highPercentile !== undefined ) this.highPercentileU.value = params.highPercentile;
|
|
686
|
+
if ( params.centerWeight !== undefined ) this.centerWeightU.value = params.centerWeight;
|
|
614
687
|
|
|
615
688
|
}
|
|
616
689
|
|
|
617
690
|
dispose() {
|
|
618
691
|
|
|
619
692
|
this._downsampleComputeNode?.dispose();
|
|
620
|
-
this.
|
|
693
|
+
this._histogramComputeNode?.dispose();
|
|
694
|
+
this._histogramAnalyzeNode?.dispose();
|
|
621
695
|
this._adaptationComputeNode?.dispose();
|
|
622
696
|
this._downsampleTarget?.dispose();
|
|
623
697
|
this._downsampleStorageTex?.dispose();
|
|
624
698
|
this._reductionStorageTex?.dispose();
|
|
625
699
|
this._reductionReadTarget?.dispose();
|
|
626
700
|
this._readbackBuffer?.dispose();
|
|
701
|
+
this._inputTexNode?.dispose();
|
|
702
|
+
this._reductionReadTexNode?.dispose();
|
|
627
703
|
|
|
628
704
|
}
|
|
629
705
|
|
package/src/Stages/Display.js
CHANGED
package/src/Stages/EdgeFilter.js
CHANGED
package/src/Stages/SSRC.js
CHANGED
|
@@ -194,6 +194,12 @@ export class SSRC extends RenderStage {
|
|
|
194
194
|
this._prevNDTexA.dispose();
|
|
195
195
|
this._prevNDTexB.dispose();
|
|
196
196
|
this._outputTex.dispose();
|
|
197
|
+
this._colorTexNode?.dispose();
|
|
198
|
+
this._ndTexNode?.dispose();
|
|
199
|
+
this._motionTexNode?.dispose();
|
|
200
|
+
this._readCacheTexNode?.dispose();
|
|
201
|
+
this._readPrevNDTexNode?.dispose();
|
|
202
|
+
this._readPass1CacheTexNode?.dispose();
|
|
197
203
|
|
|
198
204
|
}
|
|
199
205
|
|
package/src/Stages/Variance.js
CHANGED
|
@@ -371,6 +371,9 @@ export class Variance extends RenderStage {
|
|
|
371
371
|
this._computeNodeB?.dispose();
|
|
372
372
|
this._storageTexA?.dispose();
|
|
373
373
|
this._storageTexB?.dispose();
|
|
374
|
+
this._colorTexNode?.dispose();
|
|
375
|
+
this._readTexNodeA?.dispose();
|
|
376
|
+
this._readTexNodeB?.dispose();
|
|
374
377
|
|
|
375
378
|
}
|
|
376
379
|
|
|
@@ -54,6 +54,17 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
54
54
|
this._lastRenderWidth = 0;
|
|
55
55
|
this._lastRenderHeight = 0;
|
|
56
56
|
|
|
57
|
+
// Track the current completion-chain listener so it can be removed on re-trigger
|
|
58
|
+
this._pendingStartUpscaler = null;
|
|
59
|
+
|
|
60
|
+
// Bound event forwarding handlers (stored for removal on re-setup / dispose)
|
|
61
|
+
this._denoiserStartHandler = null;
|
|
62
|
+
this._denoiserEndHandler = null;
|
|
63
|
+
this._upscalerResChangedHandler = null;
|
|
64
|
+
this._upscalerStartHandler = null;
|
|
65
|
+
this._upscalerProgressHandler = null;
|
|
66
|
+
this._upscalerEndHandler = null;
|
|
67
|
+
|
|
57
68
|
}
|
|
58
69
|
|
|
59
70
|
_createDenoiserCanvas( mainCanvas ) {
|
|
@@ -146,11 +157,13 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
146
157
|
|
|
147
158
|
this.denoiser.enabled = DEFAULT_STATE.enableOIDN;
|
|
148
159
|
|
|
149
|
-
// Forward lifecycle events
|
|
150
|
-
this.
|
|
151
|
-
this.dispatchEvent( { type: EngineEvents.DENOISING_START } )
|
|
152
|
-
this.
|
|
153
|
-
this.dispatchEvent( { type: EngineEvents.DENOISING_END } )
|
|
160
|
+
// Forward lifecycle events (store refs for removal on re-setup / dispose)
|
|
161
|
+
this._denoiserStartHandler = () =>
|
|
162
|
+
this.dispatchEvent( { type: EngineEvents.DENOISING_START } );
|
|
163
|
+
this._denoiserEndHandler = () =>
|
|
164
|
+
this.dispatchEvent( { type: EngineEvents.DENOISING_END } );
|
|
165
|
+
this.denoiser.addEventListener( 'start', this._denoiserStartHandler );
|
|
166
|
+
this.denoiser.addEventListener( 'end', this._denoiserEndHandler );
|
|
154
167
|
|
|
155
168
|
}
|
|
156
169
|
|
|
@@ -189,15 +202,19 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
189
202
|
|
|
190
203
|
this.upscaler.enabled = DEFAULT_STATE.enableUpscaler || false;
|
|
191
204
|
|
|
192
|
-
// Forward lifecycle events
|
|
193
|
-
this.
|
|
194
|
-
this.dispatchEvent( { type: 'resolution_changed', width: e.width, height: e.height } )
|
|
195
|
-
this.
|
|
196
|
-
this.dispatchEvent( { type: EngineEvents.UPSCALING_START } )
|
|
197
|
-
this.
|
|
198
|
-
this.dispatchEvent( { type: EngineEvents.UPSCALING_PROGRESS, progress: e.progress } )
|
|
199
|
-
this.
|
|
200
|
-
this.dispatchEvent( { type: EngineEvents.UPSCALING_END } )
|
|
205
|
+
// Forward lifecycle events (store refs for removal on re-setup / dispose)
|
|
206
|
+
this._upscalerResChangedHandler = ( e ) =>
|
|
207
|
+
this.dispatchEvent( { type: 'resolution_changed', width: e.width, height: e.height } );
|
|
208
|
+
this._upscalerStartHandler = () =>
|
|
209
|
+
this.dispatchEvent( { type: EngineEvents.UPSCALING_START } );
|
|
210
|
+
this._upscalerProgressHandler = ( e ) =>
|
|
211
|
+
this.dispatchEvent( { type: EngineEvents.UPSCALING_PROGRESS, progress: e.progress } );
|
|
212
|
+
this._upscalerEndHandler = () =>
|
|
213
|
+
this.dispatchEvent( { type: EngineEvents.UPSCALING_END } );
|
|
214
|
+
this.upscaler.addEventListener( 'resolution_changed', this._upscalerResChangedHandler );
|
|
215
|
+
this.upscaler.addEventListener( 'start', this._upscalerStartHandler );
|
|
216
|
+
this.upscaler.addEventListener( 'progress', this._upscalerProgressHandler );
|
|
217
|
+
this.upscaler.addEventListener( 'end', this._upscalerEndHandler );
|
|
201
218
|
|
|
202
219
|
}
|
|
203
220
|
|
|
@@ -353,8 +370,23 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
353
370
|
* @param {Function} params.isStillComplete - () => boolean, guard for async race
|
|
354
371
|
* @param {import('../Pipeline/PipelineContext.js').PipelineContext} params.context
|
|
355
372
|
*/
|
|
373
|
+
_cleanupCompletionListener() {
|
|
374
|
+
|
|
375
|
+
if ( this._pendingStartUpscaler && this.denoiser ) {
|
|
376
|
+
|
|
377
|
+
this.denoiser.removeEventListener( 'end', this._pendingStartUpscaler );
|
|
378
|
+
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
this._pendingStartUpscaler = null;
|
|
382
|
+
|
|
383
|
+
}
|
|
384
|
+
|
|
356
385
|
onRenderComplete( { isStillComplete, context } ) {
|
|
357
386
|
|
|
387
|
+
// Remove any stale completion-chain listener from a previous render cycle
|
|
388
|
+
this._cleanupCompletionListener();
|
|
389
|
+
|
|
358
390
|
// Show post-process canvas if any post-process is enabled
|
|
359
391
|
if ( ( this.denoiser?.enabled || this.upscaler?.enabled ) && this.denoiserCanvas ) {
|
|
360
392
|
|
|
@@ -365,6 +397,8 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
365
397
|
// Chain: denoise first (if enabled), then upscale (if enabled)
|
|
366
398
|
const startUpscaler = () => {
|
|
367
399
|
|
|
400
|
+
this._pendingStartUpscaler = null;
|
|
401
|
+
|
|
368
402
|
if ( ! isStillComplete() ) return;
|
|
369
403
|
|
|
370
404
|
if ( this.upscaler?.enabled ) {
|
|
@@ -377,6 +411,7 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
377
411
|
|
|
378
412
|
if ( this.denoiser?.enabled ) {
|
|
379
413
|
|
|
414
|
+
this._pendingStartUpscaler = startUpscaler;
|
|
380
415
|
this.denoiser.addEventListener( 'end', startUpscaler, { once: true } );
|
|
381
416
|
this.denoiser.start();
|
|
382
417
|
|
|
@@ -401,6 +436,9 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
401
436
|
*/
|
|
402
437
|
abort( mainCanvas ) {
|
|
403
438
|
|
|
439
|
+
// Remove stale completion-chain listener before aborting
|
|
440
|
+
this._cleanupCompletionListener();
|
|
441
|
+
|
|
404
442
|
if ( mainCanvas ) mainCanvas.style.opacity = '1';
|
|
405
443
|
|
|
406
444
|
if ( this.upscaler ) this.upscaler.abort();
|
|
@@ -416,8 +454,13 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
416
454
|
|
|
417
455
|
dispose() {
|
|
418
456
|
|
|
457
|
+
// Remove pending completion-chain listener
|
|
458
|
+
this._cleanupCompletionListener();
|
|
459
|
+
|
|
419
460
|
if ( this.denoiser ) {
|
|
420
461
|
|
|
462
|
+
if ( this._denoiserStartHandler ) this.denoiser.removeEventListener( 'start', this._denoiserStartHandler );
|
|
463
|
+
if ( this._denoiserEndHandler ) this.denoiser.removeEventListener( 'end', this._denoiserEndHandler );
|
|
421
464
|
this.denoiser.dispose();
|
|
422
465
|
this.denoiser = null;
|
|
423
466
|
|
|
@@ -425,11 +468,22 @@ export class DenoisingManager extends EventDispatcher {
|
|
|
425
468
|
|
|
426
469
|
if ( this.upscaler ) {
|
|
427
470
|
|
|
471
|
+
if ( this._upscalerResChangedHandler ) this.upscaler.removeEventListener( 'resolution_changed', this._upscalerResChangedHandler );
|
|
472
|
+
if ( this._upscalerStartHandler ) this.upscaler.removeEventListener( 'start', this._upscalerStartHandler );
|
|
473
|
+
if ( this._upscalerProgressHandler ) this.upscaler.removeEventListener( 'progress', this._upscalerProgressHandler );
|
|
474
|
+
if ( this._upscalerEndHandler ) this.upscaler.removeEventListener( 'end', this._upscalerEndHandler );
|
|
428
475
|
this.upscaler.dispose();
|
|
429
476
|
this.upscaler = null;
|
|
430
477
|
|
|
431
478
|
}
|
|
432
479
|
|
|
480
|
+
this._denoiserStartHandler = null;
|
|
481
|
+
this._denoiserEndHandler = null;
|
|
482
|
+
this._upscalerResChangedHandler = null;
|
|
483
|
+
this._upscalerStartHandler = null;
|
|
484
|
+
this._upscalerProgressHandler = null;
|
|
485
|
+
this._upscalerEndHandler = null;
|
|
486
|
+
|
|
433
487
|
if ( this.denoiserCanvas?.parentNode ) {
|
|
434
488
|
|
|
435
489
|
this.denoiserCanvas.parentNode.removeChild( this.denoiserCanvas );
|