rayzee 7.11.1 → 7.12.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 +1 -1
- package/dist/rayzee.es.js +1544 -1355
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +18 -18
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineDefaults.js +24 -1
- package/src/PathTracerApp.js +14 -0
- package/src/Pipeline/CompletionTracker.js +4 -1
- package/src/Processor/KernelManager.js +0 -1
- package/src/Processor/QueueManager.js +8 -1
- package/src/RenderSettings.js +9 -0
- package/src/Stages/PathTracer.js +284 -34
- package/src/TSL/BVHTraversal.js +10 -3
- package/src/TSL/Debugger.js +1 -1
- package/src/TSL/FinalWriteKernel.js +86 -3
- package/src/TSL/GenerateKernel.js +74 -46
- package/src/TSL/ShadeKernel.js +14 -1
- package/src/managers/UniformManager.js +15 -0
|
@@ -4,13 +4,15 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
Fn, wgslFn, float, vec2, vec4, int, uint, uvec2,
|
|
7
|
-
If, mix, select, texture, textureStore, length,
|
|
7
|
+
If, mix, select, texture, textureStore, length, atomicAdd,
|
|
8
8
|
localId, workgroupId,
|
|
9
9
|
} from 'three/tsl';
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
12
|
readRayRadiance, readGBuffer, gbDecodeNormalDepth, gbDecodeAlbedo,
|
|
13
13
|
} from '../Processor/PackedRayBuffer.js';
|
|
14
|
+
import { luminance } from './Common.js';
|
|
15
|
+
import { COUNTER } from '../Processor/QueueManager.js';
|
|
14
16
|
|
|
15
17
|
const WG_SIZE = 16;
|
|
16
18
|
|
|
@@ -41,10 +43,18 @@ export function buildFinalWriteKernel( params ) {
|
|
|
41
43
|
// Clean-aux normal (1 = temporally accumulate + renormalize the aux normal). On only for clean-aux
|
|
42
44
|
// OIDN models (calb_cnrm/high, alb_nrm/balanced); off for fast/ASVGF which want the bump normal.
|
|
43
45
|
cleanAuxNormalEnabled,
|
|
46
|
+
// Tier-1 convergence early-stop: per-pixel Welford luminance second moment + converged-pixel counter.
|
|
47
|
+
counters, m2BufferRW, useAdaptiveSampling, noiseThreshold, darkNoiseFloor, adaptiveMinSamples,
|
|
48
|
+
// Tier-2 per-pixel freeze: streakBufferRW = per-pixel freeze-candidate streak (stamped here);
|
|
49
|
+
// frozenMaskRO = dilated frozen mask from buildActivePixels (pass-through gates on it, not streak).
|
|
50
|
+
usePixelFreeze, pixelFreezeThreshold, pixelFreezeStability, streakBufferRW, frozenMaskRO,
|
|
44
51
|
} = params;
|
|
45
52
|
|
|
46
53
|
const auxOn = auxGBufferEnabled.greaterThan( uint( 0 ) );
|
|
47
54
|
const cleanAuxNormalOn = cleanAuxNormalEnabled.greaterThan( uint( 0 ) );
|
|
55
|
+
// useAdaptiveSampling is registered via UniformManager.ub() → an int 0/1 uniform, so compare against int(0).
|
|
56
|
+
const convOn = useAdaptiveSampling.greaterThan( int( 0 ) );
|
|
57
|
+
const adaptiveOn = usePixelFreeze.greaterThan( int( 0 ) );
|
|
48
58
|
|
|
49
59
|
const computeFn = Fn( () => {
|
|
50
60
|
|
|
@@ -60,6 +70,11 @@ export function buildFinalWriteKernel( params ) {
|
|
|
60
70
|
const finalColor = sampleColor.xyz.toVar();
|
|
61
71
|
const outputAlpha = select( transparentBackground, sampleColor.w, float( 1.0 ) ).toVar();
|
|
62
72
|
|
|
73
|
+
// A pixel frozen in a prior frame was skipped by Generate, so its rayBuffer sample is stale — pass
|
|
74
|
+
// the accumulated colour through unchanged (below) instead of mixing it. Inert unless adaptiveOn.
|
|
75
|
+
const wasFrozen = adaptiveOn.and( frame.greaterThan( uint( 0 ) ) )
|
|
76
|
+
.and( frozenMaskRO.element( rayID ).equal( uint( 1 ) ) ).toVar();
|
|
77
|
+
|
|
63
78
|
// MRT comes from the per-pixel G-buffer (rayID == pixelIndex). Half-packed: decode.
|
|
64
79
|
// auxOn gates the decode + stores so a no-denoiser frame does no G-buffer read and no aux writes.
|
|
65
80
|
const finalNormalDepth = vec4( 0.0 ).toVar();
|
|
@@ -81,7 +96,8 @@ export function buildFinalWriteKernel( params ) {
|
|
|
81
96
|
|
|
82
97
|
const prevAccumSample = texture( prevAccumTexture, prevUV, 0 ).toVar();
|
|
83
98
|
|
|
84
|
-
|
|
99
|
+
// Frozen pixels pass prev colour through unchanged (stale sample); active pixels accumulate.
|
|
100
|
+
finalColor.assign( select( wasFrozen, prevAccumSample.xyz, mix( prevAccumSample.xyz, sampleColor.xyz, accumulationAlpha ) ) );
|
|
85
101
|
If( auxOn, () => {
|
|
86
102
|
|
|
87
103
|
// Albedo averages cleanly (it's a colour).
|
|
@@ -109,7 +125,74 @@ export function buildFinalWriteKernel( params ) {
|
|
|
109
125
|
|
|
110
126
|
If( transparentBackground, () => {
|
|
111
127
|
|
|
112
|
-
outputAlpha.assign( mix( prevAccumSample.w, sampleColor.w, accumulationAlpha ) );
|
|
128
|
+
outputAlpha.assign( select( wasFrozen, prevAccumSample.w, mix( prevAccumSample.w, sampleColor.w, accumulationAlpha ) ) );
|
|
129
|
+
|
|
130
|
+
} );
|
|
131
|
+
|
|
132
|
+
} );
|
|
133
|
+
|
|
134
|
+
// Tier-1 convergence: per-pixel running second moment of LUMINANCE (Welford). luminance() is linear,
|
|
135
|
+
// so luminance(running-mean color) == running-mean luminance == E[L]; m2 tracks E[L²] under the SAME
|
|
136
|
+
// global 1/(frame+1) alpha (NO per-pixel alpha). sampleVar = E[L²]-E[L]²; varOfMean = sampleVar/(N);
|
|
137
|
+
// relErr = SE(mean)/mean. A pixel counts as converged once frame>=minSamples AND relErr<threshold.
|
|
138
|
+
// The m2 write runs every frame (incl. frame 0, where alpha==1 self-inits it → no explicit clear).
|
|
139
|
+
// Sits AFTER the accumulation mix (finalColor is the mean) and BEFORE the visMode-11 mutation.
|
|
140
|
+
If( convOn, () => {
|
|
141
|
+
|
|
142
|
+
const sampleLum = luminance( sampleColor.xyz );
|
|
143
|
+
const prevM2 = m2BufferRW.element( rayID ).toVar();
|
|
144
|
+
const m2 = mix( prevM2, sampleLum.mul( sampleLum ), accumulationAlpha ).toVar();
|
|
145
|
+
m2BufferRW.element( rayID ).assign( m2 );
|
|
146
|
+
|
|
147
|
+
const meanLum = luminance( finalColor ).toVar();
|
|
148
|
+
const sampleVar = m2.sub( meanLum.mul( meanLum ) ).max( float( 0 ) );
|
|
149
|
+
const varOfMean = sampleVar.div( float( frame ).add( 1.0 ) );
|
|
150
|
+
// absSE = absolute standard error of the mean luminance; relErr = its ratio to the mean.
|
|
151
|
+
// Combined criterion: bright pixels converge on relErr<threshold; dark/dim pixels (where relErr
|
|
152
|
+
// stays high forever) converge on absSE<absFloor, since their absolute noise is imperceptible.
|
|
153
|
+
const absSE = varOfMean.sqrt().toVar();
|
|
154
|
+
const relErr = absSE.div( meanLum.add( float( 1e-4 ) ) );
|
|
155
|
+
|
|
156
|
+
If( frame.greaterThanEqual( uint( adaptiveMinSamples ) ).and(
|
|
157
|
+
relErr.lessThan( noiseThreshold ).or( absSE.lessThan( darkNoiseFloor ) )
|
|
158
|
+
), () => {
|
|
159
|
+
|
|
160
|
+
atomicAdd( counters.element( uint( COUNTER.CONVERGED_COUNT ) ), uint( 1 ) );
|
|
161
|
+
|
|
162
|
+
} );
|
|
163
|
+
|
|
164
|
+
// Maintain the freeze streak on the relErr-only predicate (NO absFloor — it bakes dim regions dark
|
|
165
|
+
// when it permanently freezes a pixel). Monotonic: streak>=K freezes for the run, so global alpha stays exact.
|
|
166
|
+
If( adaptiveOn, () => {
|
|
167
|
+
|
|
168
|
+
If( frame.equal( uint( 0 ) ), () => {
|
|
169
|
+
|
|
170
|
+
// Frame 0 (post-reset/camera-move) clears the frozen state so each run re-freezes fresh.
|
|
171
|
+
streakBufferRW.element( rayID ).assign( uint( 0 ) );
|
|
172
|
+
|
|
173
|
+
} );
|
|
174
|
+
|
|
175
|
+
If( wasFrozen, () => {
|
|
176
|
+
|
|
177
|
+
// Already frozen (passed through above) — count it.
|
|
178
|
+
atomicAdd( counters.element( uint( COUNTER.FROZEN_COUNT ) ), uint( 1 ) );
|
|
179
|
+
|
|
180
|
+
} );
|
|
181
|
+
|
|
182
|
+
If( wasFrozen.not().and( frame.greaterThan( uint( 0 ) ) ), () => {
|
|
183
|
+
|
|
184
|
+
const freezeCandidate = frame.greaterThanEqual( uint( adaptiveMinSamples ) )
|
|
185
|
+
.and( relErr.lessThan( pixelFreezeThreshold ) );
|
|
186
|
+
const newStreak = select( freezeCandidate, streakBufferRW.element( rayID ).add( uint( 1 ) ), uint( 0 ) ).toVar();
|
|
187
|
+
streakBufferRW.element( rayID ).assign( newStreak );
|
|
188
|
+
// Just reached K → frozen next frame; count it now so the frozen population is current.
|
|
189
|
+
If( newStreak.greaterThanEqual( uint( pixelFreezeStability ) ), () => {
|
|
190
|
+
|
|
191
|
+
atomicAdd( counters.element( uint( COUNTER.FROZEN_COUNT ) ), uint( 1 ) );
|
|
192
|
+
|
|
193
|
+
} );
|
|
194
|
+
|
|
195
|
+
} );
|
|
113
196
|
|
|
114
197
|
} );
|
|
115
198
|
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* GenerateKernel.js — wavefront primary ray generation
|
|
2
|
+
* GenerateKernel.js — wavefront primary ray generation.
|
|
3
|
+
* Two dispatch modes from one builder: 2D screen-space (default) and 1D list-driven over the active-pixel
|
|
4
|
+
* list (Tier-2 per-pixel freeze).
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
import {
|
|
6
8
|
Fn, float, vec2, vec3, vec4, int, uint,
|
|
7
|
-
If, select,
|
|
9
|
+
If, select, instanceIndex, atomicLoad,
|
|
8
10
|
localId, workgroupId,
|
|
9
11
|
} from 'three/tsl';
|
|
10
12
|
|
|
@@ -16,7 +18,7 @@ import {
|
|
|
16
18
|
|
|
17
19
|
import { generateRayFromCamera } from './BVHTraversal.js';
|
|
18
20
|
import { Ray } from './Struct.js';
|
|
19
|
-
import { RAY_FLAG } from '../Processor/QueueManager.js';
|
|
21
|
+
import { RAY_FLAG, COUNTER } from '../Processor/QueueManager.js';
|
|
20
22
|
import {
|
|
21
23
|
writeRayOriginMeta, writeRayDirFlags, writeRayThroughputPdf,
|
|
22
24
|
writeRayRadiance, writeGBuffer,
|
|
@@ -36,68 +38,94 @@ export function buildGenerateKernel( params ) {
|
|
|
36
38
|
transmissiveBounces, // per-ray refraction budget (megakernel parity: PathTracerCore.js:606)
|
|
37
39
|
transparentBackground, // alpha inits to 1 here (megakernel parity: PathTracerCore.js:554) — env-escape-without-opaque zeroes it in Shade
|
|
38
40
|
auxGBufferEnabled, // live uniform: 1 = init the per-pixel G-buffer (denoiser on), 0 = skip it
|
|
41
|
+
// listDriven: 1D dispatch over the active-pixel list (activeIndicesRO[tid] = pixelID) instead of 2D.
|
|
42
|
+
listDriven = false, activeIndicesRO = null, counters = null,
|
|
39
43
|
} = params;
|
|
40
44
|
|
|
41
45
|
const auxOn = auxGBufferEnabled.greaterThan( uint( 0 ) );
|
|
42
46
|
|
|
43
|
-
|
|
47
|
+
// Shared ray-gen body for a pixel (gx, gy) — identical in both dispatch modes.
|
|
48
|
+
const emitRay = ( gx, gy ) => {
|
|
49
|
+
|
|
50
|
+
const pixelCoord = vec2( float( gx ).add( 0.5 ), float( gy ).add( 0.5 ) );
|
|
51
|
+
const pixelIndex = gy.mul( int( resolution.x ) ).add( gx );
|
|
52
|
+
// One ray per pixel: rayID is the pixel index.
|
|
53
|
+
const rayID = uint( pixelIndex );
|
|
54
|
+
|
|
55
|
+
const screenPosition = pixelCoord.div( resolution ).mul( 2.0 ).sub( 1.0 ).toVar();
|
|
56
|
+
screenPosition.y.assign( screenPosition.y.negate() );
|
|
57
|
+
|
|
58
|
+
const baseSeed = getDecorrelatedSeed( { pixelCoord, rayIndex: int( 0 ), frame } ).toVar();
|
|
59
|
+
const seed = pcgHash( { state: baseSeed } ).toVar();
|
|
60
|
+
|
|
61
|
+
// Sample index 1 (not 0) so the AA sub-pixel jitter draws a DIFFERENT STBN cell
|
|
62
|
+
// than the first-bounce BSDF sample (ShadeKernel uses sampleIndex 0). Every bounce
|
|
63
|
+
// samples at index 0, so index 1 is collision-free — this decorrelates the sub-pixel
|
|
64
|
+
// position from the first scatter direction (they were reading the identical cell).
|
|
65
|
+
const stratifiedJitter = getStratifiedSample( pixelCoord, int( 1 ), int( 1 ), seed, resolution, frame ).toVar();
|
|
66
|
+
|
|
67
|
+
const jitterScale = vec2( 2.0 ).div( resolution );
|
|
68
|
+
const jitter = stratifiedJitter.sub( 0.5 ).mul( jitterScale );
|
|
69
|
+
const jitteredScreenPosition = screenPosition.add( jitter );
|
|
70
|
+
|
|
71
|
+
const ray = Ray.wrap( generateRayFromCamera(
|
|
72
|
+
jitteredScreenPosition, seed,
|
|
73
|
+
cameraWorldMatrix, cameraProjectionMatrixInverse,
|
|
74
|
+
enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio,
|
|
75
|
+
) );
|
|
76
|
+
|
|
77
|
+
writeRayOriginMeta( rayBufferRW, rayID, ray.origin, int( 0 ), int( 0 ) );
|
|
78
|
+
// A fresh camera ray is NOT redirected — REDIRECTED stays clear so it sees the direct backdrop;
|
|
79
|
+
// ShadeKernel sets REDIRECTED on the first direction-changing interaction (see RAY_FLAG.REDIRECTED).
|
|
80
|
+
writeRayDirFlags( rayBufferRW, rayID, ray.direction, uint( RAY_FLAG.ACTIVE ) );
|
|
81
|
+
// pdf inits to 0 = prevBouncePdf (megakernel parity PathTracerCore.js:556). The bounce>0 env/emissive
|
|
82
|
+
// MIS gate skips until an opaque scatter writes a real combinedPdf; free bounces preserve it.
|
|
83
|
+
writeRayThroughputPdf( rayBufferRW, rayID, vec4( 1.0, 1.0, 1.0, 0.0 ).xyz, float( 0.0 ) );
|
|
84
|
+
// Alpha inits to 1 in transparent-bg mode (megakernel parity: PathTracerCore.js:554). Shade zeroes
|
|
85
|
+
// it only on env-escape-without-opaque; a ray that dies inside geometry (e.g. SSS walk termination)
|
|
86
|
+
// keeps alpha 1 → solid. Non-transparent mode is inert (FinalWrite forces alpha 1).
|
|
87
|
+
writeRayRadiance( rayBufferRW, rayID, vec4( vec3( 0.0 ), select( transparentBackground, float( 1.0 ), float( 0.0 ) ) ) );
|
|
88
|
+
|
|
89
|
+
If( auxOn, () => {
|
|
90
|
+
|
|
91
|
+
// default: normal +Z, depth 1 (far), black albedo (background/miss)
|
|
92
|
+
writeGBuffer( gBufferRW, uint( pixelIndex ), vec3( 0.0, 0.0, 1.0 ), float( 1.0 ), vec3( 0.0 ) );
|
|
44
93
|
|
|
45
|
-
|
|
46
|
-
const gy = int( workgroupId.y ).mul( WG_SIZE ).add( int( localId.y ) );
|
|
94
|
+
} );
|
|
47
95
|
|
|
48
|
-
|
|
96
|
+
writeMediumStack( rayBufferRW, rayID, uint( 0 ), uint( transmissiveBounces ), float( 1.0 ), float( 1.0 ), float( 1.0 ) );
|
|
49
97
|
|
|
50
|
-
|
|
51
|
-
const pixelIndex = gy.mul( int( resolution.x ) ).add( gx );
|
|
52
|
-
// One ray per pixel: rayID is the pixel index.
|
|
53
|
-
const rayID = uint( pixelIndex );
|
|
98
|
+
rngBufferRW.element( rayID ).assign( seed );
|
|
54
99
|
|
|
55
|
-
|
|
56
|
-
screenPosition.y.assign( screenPosition.y.negate() );
|
|
100
|
+
};
|
|
57
101
|
|
|
58
|
-
|
|
59
|
-
const seed = pcgHash( { state: baseSeed } ).toVar();
|
|
102
|
+
const computeFn = Fn( () => {
|
|
60
103
|
|
|
61
|
-
|
|
62
|
-
// than the first-bounce BSDF sample (ShadeKernel uses sampleIndex 0). Every bounce
|
|
63
|
-
// samples at index 0, so index 1 is collision-free — this decorrelates the sub-pixel
|
|
64
|
-
// position from the first scatter direction (they were reading the identical cell).
|
|
65
|
-
const stratifiedJitter = getStratifiedSample( pixelCoord, int( 1 ), int( 1 ), seed, resolution, frame ).toVar();
|
|
104
|
+
if ( listDriven ) {
|
|
66
105
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
106
|
+
const tid = instanceIndex;
|
|
107
|
+
// Grid is over-sized from a stale readback; bound on the live ENTERING_COUNT so extra threads no-op.
|
|
108
|
+
If( tid.lessThan( atomicLoad( counters.element( uint( COUNTER.ENTERING_COUNT ) ) ) ), () => {
|
|
70
109
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
) );
|
|
110
|
+
const pixelIndex = int( activeIndicesRO.element( tid ) );
|
|
111
|
+
const gx = pixelIndex.mod( int( resolution.x ) );
|
|
112
|
+
const gy = pixelIndex.div( int( resolution.x ) );
|
|
113
|
+
emitRay( gx, gy );
|
|
76
114
|
|
|
77
|
-
|
|
78
|
-
// A fresh camera ray is NOT redirected — REDIRECTED stays clear so it sees the direct backdrop;
|
|
79
|
-
// ShadeKernel sets REDIRECTED on the first direction-changing interaction (see RAY_FLAG.REDIRECTED).
|
|
80
|
-
writeRayDirFlags( rayBufferRW, rayID, ray.direction, uint( RAY_FLAG.ACTIVE ) );
|
|
81
|
-
// pdf inits to 0 = prevBouncePdf (megakernel parity PathTracerCore.js:556). The bounce>0 env/emissive
|
|
82
|
-
// MIS gate skips until an opaque scatter writes a real combinedPdf; free bounces preserve it.
|
|
83
|
-
writeRayThroughputPdf( rayBufferRW, rayID, vec4( 1.0, 1.0, 1.0, 0.0 ).xyz, float( 0.0 ) );
|
|
84
|
-
// Alpha inits to 1 in transparent-bg mode (megakernel parity: PathTracerCore.js:554). Shade zeroes
|
|
85
|
-
// it only on env-escape-without-opaque; a ray that dies inside geometry (e.g. SSS walk termination)
|
|
86
|
-
// keeps alpha 1 → solid. Non-transparent mode is inert (FinalWrite forces alpha 1).
|
|
87
|
-
writeRayRadiance( rayBufferRW, rayID, vec4( vec3( 0.0 ), select( transparentBackground, float( 1.0 ), float( 0.0 ) ) ) );
|
|
115
|
+
} );
|
|
88
116
|
|
|
89
|
-
|
|
117
|
+
} else {
|
|
90
118
|
|
|
91
|
-
|
|
92
|
-
|
|
119
|
+
const gx = int( workgroupId.x ).mul( WG_SIZE ).add( int( localId.x ) );
|
|
120
|
+
const gy = int( workgroupId.y ).mul( WG_SIZE ).add( int( localId.y ) );
|
|
93
121
|
|
|
94
|
-
|
|
122
|
+
If( gx.lessThan( renderWidth ).and( gy.lessThan( renderHeight ) ), () => {
|
|
95
123
|
|
|
96
|
-
|
|
124
|
+
emitRay( gx, gy );
|
|
97
125
|
|
|
98
|
-
|
|
126
|
+
} );
|
|
99
127
|
|
|
100
|
-
}
|
|
128
|
+
}
|
|
101
129
|
|
|
102
130
|
} );
|
|
103
131
|
|
package/src/TSL/ShadeKernel.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
If, Loop, normalize, max, exp, log, clamp, dot, length, select,
|
|
11
11
|
instanceIndex,
|
|
12
12
|
sampler,
|
|
13
|
-
atomicAdd, atomicLoad, uintBitsToFloat,
|
|
13
|
+
atomicAdd, atomicLoad, atomicStore, uintBitsToFloat,
|
|
14
14
|
Return,
|
|
15
15
|
} from 'three/tsl';
|
|
16
16
|
|
|
@@ -137,6 +137,19 @@ export function buildShadeKernel( params ) {
|
|
|
137
137
|
|
|
138
138
|
const threadIdx = instanceIndex;
|
|
139
139
|
|
|
140
|
+
// Folds the former resetActiveCounter 1-thread kernel: thread 0 zeroes the survivor counter
|
|
141
|
+
// before compact re-counts it. Kept ABOVE the ENTERING_COUNT guard so it fires even at bound 0.
|
|
142
|
+
// Safe: shade never touches ACTIVE_RAY_COUNT, and the shade→compact dispatch boundary publishes it.
|
|
143
|
+
if ( counters ) {
|
|
144
|
+
|
|
145
|
+
If( threadIdx.equal( uint( 0 ) ), () => {
|
|
146
|
+
|
|
147
|
+
atomicStore( counters.element( uint( COUNTER.ACTIVE_RAY_COUNT ) ), uint( 0 ) );
|
|
148
|
+
|
|
149
|
+
} );
|
|
150
|
+
|
|
151
|
+
}
|
|
152
|
+
|
|
140
153
|
// bound on ENTERING_COUNT so an over-sized margin dispatch is safe
|
|
141
154
|
const bound = counters ? atomicLoad( counters.element( uint( COUNTER.ENTERING_COUNT ) ) ) : maxRayCount;
|
|
142
155
|
If( threadIdx.greaterThanEqual( bound ), () => {
|
|
@@ -167,6 +167,21 @@ export class UniformManager {
|
|
|
167
167
|
u( 'visMode', DEFAULT_STATE.debugMode, 'int' );
|
|
168
168
|
u( 'debugVisScale', DEFAULT_STATE.debugVisScale, 'float' );
|
|
169
169
|
|
|
170
|
+
// Tier-1 convergence early-stop (FinalWrite reads these; live-toggled, no shader rebuild)
|
|
171
|
+
ub( 'useAdaptiveSampling', DEFAULT_STATE.useAdaptiveSampling );
|
|
172
|
+
u( 'noiseThreshold', DEFAULT_STATE.noiseThreshold, 'float' );
|
|
173
|
+
u( 'darkNoiseFloor', DEFAULT_STATE.darkNoiseFloor, 'float' );
|
|
174
|
+
u( 'adaptiveMinSamples', DEFAULT_STATE.adaptiveMinSamples, 'int' );
|
|
175
|
+
// CPU-only (read in PathTracer._isConvergedComplete, not bound to any kernel); registered here for
|
|
176
|
+
// the settings/configureForMode plumbing + the _defineUniformGetters accessor.
|
|
177
|
+
u( 'adaptiveStopFraction', DEFAULT_STATE.adaptiveStopFraction, 'float' );
|
|
178
|
+
|
|
179
|
+
// Tier-2 per-pixel freeze. usePixelFreeze gates both FinalWrite (stamp + pass-through) and render()'s
|
|
180
|
+
// frozen-compaction dispatch path.
|
|
181
|
+
ub( 'usePixelFreeze', DEFAULT_STATE.usePixelFreeze );
|
|
182
|
+
u( 'pixelFreezeThreshold', DEFAULT_STATE.pixelFreezeThreshold, 'float' );
|
|
183
|
+
u( 'pixelFreezeStability', DEFAULT_STATE.pixelFreezeStability, 'int' );
|
|
184
|
+
|
|
170
185
|
// Accumulation
|
|
171
186
|
ub( 'enableAccumulation', true );
|
|
172
187
|
u( 'accumulationAlpha', 0.0, 'float' );
|