rayzee 7.16.0 → 7.18.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/assets/BVHSubtreeWorker-D7KMbrQ-.js +2 -0
- package/dist/assets/BVHSubtreeWorker-D7KMbrQ-.js.map +1 -0
- package/dist/assets/BVHWorker-DOaGn6uN.js +2 -0
- package/dist/assets/BVHWorker-DOaGn6uN.js.map +1 -0
- package/dist/rayzee.es.js +2817 -2537
- package/dist/rayzee.es.js.map +1 -1
- package/dist/rayzee.umd.js +55 -55
- package/dist/rayzee.umd.js.map +1 -1
- package/package.json +1 -1
- package/src/EngineDefaults.js +27 -11
- package/src/Passes/OIDNDenoiser.js +17 -14
- package/src/PathTracerApp.js +86 -13
- package/src/Processor/BVHBuilder.js +11 -7
- package/src/Processor/BuildTimer.js +20 -6
- package/src/Processor/EmissiveTriangleBuilder.js +11 -7
- package/src/Processor/GeometryExtractor.js +11 -5
- package/src/Processor/KernelManager.js +48 -1
- package/src/Processor/PackedRayBuffer.js +69 -35
- package/src/Processor/ParallelBVHBuilder.js +9 -6
- package/src/Processor/QueueManager.js +6 -8
- package/src/Processor/SceneProcessor.js +47 -21
- package/src/Processor/ShaderBuilder.js +4 -1
- package/src/Processor/StorageTexturePool.js +4 -1
- package/src/Processor/Workers/BVHWorker.js +12 -6
- package/src/RenderSettings.js +1 -1
- package/src/Stages/ASVGF.js +38 -0
- package/src/Stages/BilateralFilter.js +23 -0
- package/src/Stages/EdgeFilter.js +18 -0
- package/src/Stages/MotionVector.js +23 -0
- package/src/Stages/NormalDepth.js +22 -0
- package/src/Stages/PathTracer.js +324 -169
- package/src/Stages/PathTracerStage.js +17 -14
- package/src/Stages/Variance.js +23 -0
- package/src/TSL/FinalWriteKernel.js +30 -18
- package/src/TSL/GenerateKernel.js +28 -17
- package/src/TSL/ShadeKernel.js +186 -87
- package/src/TSL/TextureSampling.js +12 -2
- package/src/index.js +3 -0
- package/src/managers/EnvironmentManager.js +12 -8
- package/src/managers/MaterialDataManager.js +12 -9
- package/src/managers/UniformManager.js +1 -1
- package/src/utils/Logger.js +291 -0
- package/dist/assets/BVHSubtreeWorker-sNzvxn66.js +0 -2
- package/dist/assets/BVHSubtreeWorker-sNzvxn66.js.map +0 -1
- package/dist/assets/BVHWorker-CiVdFrwe.js +0 -2
- package/dist/assets/BVHWorker-CiVdFrwe.js.map +0 -1
|
@@ -25,6 +25,9 @@ import { LightSerializer } from '../Processor/LightSerializer';
|
|
|
25
25
|
// Constants
|
|
26
26
|
import { ENGINE_DEFAULTS as DEFAULT_STATE } from '../EngineDefaults.js';
|
|
27
27
|
import { getAssetConfig } from '../AssetConfig.js';
|
|
28
|
+
import { createLogger, fmt } from '../utils/Logger.js';
|
|
29
|
+
|
|
30
|
+
const log = createLogger( 'pathtracer' );
|
|
28
31
|
|
|
29
32
|
/**
|
|
30
33
|
* Data layout constants
|
|
@@ -377,7 +380,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
377
380
|
|
|
378
381
|
this.stbnScalarTexture = configure( tex );
|
|
379
382
|
stbnScalarTextureNode.value = tex;
|
|
380
|
-
|
|
383
|
+
log.debug( `STBN scalar atlas ${fmt.px( tex.image.width, tex.image.height )}` );
|
|
381
384
|
|
|
382
385
|
} );
|
|
383
386
|
|
|
@@ -385,7 +388,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
385
388
|
|
|
386
389
|
this.stbnVec2Texture = configure( tex );
|
|
387
390
|
stbnVec2TextureNode.value = tex;
|
|
388
|
-
|
|
391
|
+
log.debug( `STBN vec2 atlas ${fmt.px( tex.image.width, tex.image.height )}` );
|
|
389
392
|
|
|
390
393
|
} );
|
|
391
394
|
|
|
@@ -548,7 +551,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
548
551
|
|
|
549
552
|
this.directionalLightsData = mockMaterial.uniforms.directionalLights.value;
|
|
550
553
|
|
|
551
|
-
|
|
554
|
+
log.debug( `sun added as directional light · intensity ${scaledSunIntensity.toFixed( 2 )}` );
|
|
552
555
|
|
|
553
556
|
}
|
|
554
557
|
|
|
@@ -716,7 +719,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
716
719
|
|
|
717
720
|
this.triangleCount = triangleCount;
|
|
718
721
|
|
|
719
|
-
|
|
722
|
+
log.debug( `${fmt.n( this.triangleCount )} triangles (storage buffer)` );
|
|
720
723
|
|
|
721
724
|
}
|
|
722
725
|
|
|
@@ -744,7 +747,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
744
747
|
}
|
|
745
748
|
|
|
746
749
|
this.bvhNodeCount = Math.floor( vec4Count / BVH_VEC4_PER_NODE );
|
|
747
|
-
|
|
750
|
+
log.debug( `${fmt.n( this.bvhNodeCount )} BVH nodes (storage buffer)` );
|
|
748
751
|
|
|
749
752
|
}
|
|
750
753
|
|
|
@@ -983,14 +986,14 @@ export class PathTracerStage extends RenderStage {
|
|
|
983
986
|
|
|
984
987
|
if ( ! this.triangleStorageNode ) {
|
|
985
988
|
|
|
986
|
-
|
|
989
|
+
log.error( 'triangle data required' );
|
|
987
990
|
return;
|
|
988
991
|
|
|
989
992
|
}
|
|
990
993
|
|
|
991
994
|
if ( ! this.bvhStorageNode ) {
|
|
992
995
|
|
|
993
|
-
|
|
996
|
+
log.error( 'BVH data required' );
|
|
994
997
|
return;
|
|
995
998
|
|
|
996
999
|
}
|
|
@@ -1348,7 +1351,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
1348
1351
|
this.emissiveTriangleCount.value = count;
|
|
1349
1352
|
this.emissiveTotalPower.value = totalPower;
|
|
1350
1353
|
this._rebuildLightBuffer();
|
|
1351
|
-
|
|
1354
|
+
log.debug( `${fmt.n( count )} emissive triangles · totalPower ${totalPower.toFixed( 4 )} (storage buffer)` );
|
|
1352
1355
|
|
|
1353
1356
|
}
|
|
1354
1357
|
|
|
@@ -1359,7 +1362,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
1359
1362
|
this._lbvhDataCache = nodeData;
|
|
1360
1363
|
this.lightBVHNodeCount.value = nodeCount;
|
|
1361
1364
|
this._rebuildLightBuffer();
|
|
1362
|
-
|
|
1365
|
+
log.debug( `light BVH ${fmt.n( nodeCount )} nodes` );
|
|
1363
1366
|
|
|
1364
1367
|
}
|
|
1365
1368
|
|
|
@@ -1402,7 +1405,7 @@ export class PathTracerStage extends RenderStage {
|
|
|
1402
1405
|
|
|
1403
1406
|
try {
|
|
1404
1407
|
|
|
1405
|
-
|
|
1408
|
+
log.debug( 'material rebuild started' );
|
|
1406
1409
|
|
|
1407
1410
|
await this.sdfs.rebuildMaterials( scene );
|
|
1408
1411
|
this.updateSceneUniforms();
|
|
@@ -1410,20 +1413,20 @@ export class PathTracerStage extends RenderStage {
|
|
|
1410
1413
|
this.updateLights();
|
|
1411
1414
|
this.reset();
|
|
1412
1415
|
|
|
1413
|
-
|
|
1416
|
+
log.debug( 'material rebuild complete' );
|
|
1414
1417
|
|
|
1415
1418
|
} catch ( error ) {
|
|
1416
1419
|
|
|
1417
|
-
|
|
1420
|
+
log.error( 'material rebuild failed:', error );
|
|
1418
1421
|
|
|
1419
1422
|
try {
|
|
1420
1423
|
|
|
1421
|
-
|
|
1424
|
+
log.warn( 'attempting recovery by resetting the path tracer' );
|
|
1422
1425
|
this.reset();
|
|
1423
1426
|
|
|
1424
1427
|
} catch ( recoveryError ) {
|
|
1425
1428
|
|
|
1426
|
-
|
|
1429
|
+
log.error( 'recovery failed:', recoveryError );
|
|
1427
1430
|
|
|
1428
1431
|
}
|
|
1429
1432
|
|
package/src/Stages/Variance.js
CHANGED
|
@@ -404,6 +404,29 @@ export class Variance extends RenderStage {
|
|
|
404
404
|
|
|
405
405
|
}
|
|
406
406
|
|
|
407
|
+
// Reserved-storage change (e.g. 4K toggle): recreate the MAX-preallocated StorageTextures at the new live
|
|
408
|
+
// reserved size + rebuild the compute nodes in place. Same stage object, so refs/wiring stay valid.
|
|
409
|
+
reallocateReservedStorage() {
|
|
410
|
+
|
|
411
|
+
this._computeNodeA?.dispose();
|
|
412
|
+
this._computeNodeB?.dispose();
|
|
413
|
+
this._storageTexA?.dispose();
|
|
414
|
+
this._storageTexB?.dispose();
|
|
415
|
+
const mk = () => {
|
|
416
|
+
|
|
417
|
+
const t = new StorageTexture( MAX_STORAGE_TEXTURE_SIZE, MAX_STORAGE_TEXTURE_SIZE );
|
|
418
|
+
t.type = FloatType; t.format = RGBAFormat; t.minFilter = LinearFilter; t.magFilter = LinearFilter;
|
|
419
|
+
return t;
|
|
420
|
+
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
this._storageTexA = mk();
|
|
424
|
+
this._storageTexB = mk();
|
|
425
|
+
this._buildCompute();
|
|
426
|
+
this._compiled = false;
|
|
427
|
+
|
|
428
|
+
}
|
|
429
|
+
|
|
407
430
|
dispose() {
|
|
408
431
|
|
|
409
432
|
this._computeNodeA?.dispose();
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* FinalWriteKernel.js — wavefront final output: temporal accumulation + MRT StorageTexture writes (16×16, 2D).
|
|
3
|
+
*
|
|
4
|
+
* Chunked path pool (docs/internal/specs/wavefront-chunked-pool.md): runs 2D over one row band
|
|
5
|
+
* (renderWidth × chunkRows). A thread's band-local row `localGy` maps to global row `gy = chunkRowBase + localGy`.
|
|
6
|
+
* Path state + the per-chunk G-buffer are read at the LOCAL slot `rayID = localGy·W + gx`; the persistent
|
|
7
|
+
* per-pixel buffers (m2/streak/frozenMask) and the output texture use the GLOBAL pixel `p = gy·W + gx`.
|
|
3
8
|
*/
|
|
4
9
|
|
|
5
10
|
import {
|
|
@@ -35,7 +40,8 @@ export function buildFinalWriteKernel( params ) {
|
|
|
35
40
|
enableAccumulation, hasPreviousAccumulated, accumulationAlpha, cameraIsMoving,
|
|
36
41
|
transparentBackground,
|
|
37
42
|
prevAccumTexture, prevAlbedoTexture, prevNormalDepthTexture,
|
|
38
|
-
renderWidth,
|
|
43
|
+
renderWidth,
|
|
44
|
+
chunkRowBase, chunkRows, // row band offset (global first row) + row count for this chunk
|
|
39
45
|
visMode,
|
|
40
46
|
// Aux MRT (normalDepth + albedo) feeds only the denoiser/OIDN. Gated by a live uniform (1 = denoiser
|
|
41
47
|
// on): when off, skip the G-buffer decode, the prev-frame aux mix, and the two aux stores.
|
|
@@ -44,7 +50,8 @@ export function buildFinalWriteKernel( params ) {
|
|
|
44
50
|
// OIDN models (calb_cnrm/high, alb_nrm/balanced); off for fast/ASVGF which want the bump normal.
|
|
45
51
|
cleanAuxNormalEnabled,
|
|
46
52
|
// Tier-1 convergence early-stop: per-pixel Welford luminance second moment + converged-pixel counter.
|
|
47
|
-
counters, m2BufferRW, useAdaptiveSampling, noiseThreshold,
|
|
53
|
+
counters, m2BufferRW, useAdaptiveSampling, noiseThreshold, adaptiveMinSamples,
|
|
54
|
+
|
|
48
55
|
// Tier-2 per-pixel freeze: streakBufferRW = per-pixel freeze-candidate streak (stamped here);
|
|
49
56
|
// frozenMaskRO = dilated frozen mask from buildActivePixels (pass-through gates on it, not streak).
|
|
50
57
|
usePixelFreeze, pixelFreezeThreshold, pixelFreezeStability, streakBufferRW, frozenMaskRO,
|
|
@@ -59,12 +66,13 @@ export function buildFinalWriteKernel( params ) {
|
|
|
59
66
|
const computeFn = Fn( () => {
|
|
60
67
|
|
|
61
68
|
const gx = int( workgroupId.x ).mul( WG_SIZE ).add( int( localId.x ) );
|
|
62
|
-
const
|
|
69
|
+
const localGy = int( workgroupId.y ).mul( WG_SIZE ).add( int( localId.y ) );
|
|
63
70
|
|
|
64
|
-
If( gx.lessThan( renderWidth ).and(
|
|
71
|
+
If( gx.lessThan( renderWidth ).and( localGy.lessThan( chunkRows ) ), () => {
|
|
65
72
|
|
|
66
|
-
const
|
|
67
|
-
const rayID = uint(
|
|
73
|
+
const gy = localGy.add( chunkRowBase ); // GLOBAL row
|
|
74
|
+
const rayID = uint( localGy.mul( renderWidth ).add( gx ) ); // LOCAL path/gBuffer slot
|
|
75
|
+
const pixelId = uint( gy.mul( renderWidth ).add( gx ) ); // GLOBAL pixel (persistent buffers + texture)
|
|
68
76
|
|
|
69
77
|
const sampleColor = readRayRadiance( rayBufferRO, rayID );
|
|
70
78
|
const finalColor = sampleColor.xyz.toVar();
|
|
@@ -73,9 +81,9 @@ export function buildFinalWriteKernel( params ) {
|
|
|
73
81
|
// A pixel frozen in a prior frame was skipped by Generate, so its rayBuffer sample is stale — pass
|
|
74
82
|
// the accumulated colour through unchanged (below) instead of mixing it. Inert unless adaptiveOn.
|
|
75
83
|
const wasFrozen = adaptiveOn.and( frame.greaterThan( uint( 0 ) ) )
|
|
76
|
-
.and( frozenMaskRO.element(
|
|
84
|
+
.and( frozenMaskRO.element( pixelId ).equal( uint( 1 ) ) ).toVar();
|
|
77
85
|
|
|
78
|
-
// MRT comes from the per-
|
|
86
|
+
// MRT comes from the per-chunk G-buffer (LOCAL slot). Half-packed: decode.
|
|
79
87
|
// auxOn gates the decode + stores so a no-denoiser frame does no G-buffer read and no aux writes.
|
|
80
88
|
const finalNormalDepth = vec4( 0.0 ).toVar();
|
|
81
89
|
const finalAlbedo = vec4( 0.0 ).xyz.toVar();
|
|
@@ -134,15 +142,15 @@ export function buildFinalWriteKernel( params ) {
|
|
|
134
142
|
// Tier-1 convergence: per-pixel running second moment of LUMINANCE (Welford). luminance() is linear,
|
|
135
143
|
// so luminance(running-mean color) == running-mean luminance == E[L]; m2 tracks E[L²] under the SAME
|
|
136
144
|
// global 1/(frame+1) alpha (NO per-pixel alpha). sampleVar = E[L²]-E[L]²; varOfMean = sampleVar/(N);
|
|
137
|
-
//
|
|
145
|
+
// absSE = SE(mean); a pixel converges once frame>=minSamples AND the √-normalized error < threshold.
|
|
138
146
|
// The m2 write runs every frame (incl. frame 0, where alpha==1 self-inits it → no explicit clear).
|
|
139
147
|
// Sits AFTER the accumulation mix (finalColor is the mean) and BEFORE the visMode-11 mutation.
|
|
140
148
|
If( convOn, () => {
|
|
141
149
|
|
|
142
150
|
const sampleLum = luminance( sampleColor.xyz );
|
|
143
|
-
const prevM2 = m2BufferRW.element(
|
|
151
|
+
const prevM2 = m2BufferRW.element( pixelId ).toVar();
|
|
144
152
|
const m2 = mix( prevM2, sampleLum.mul( sampleLum ), accumulationAlpha ).toVar();
|
|
145
|
-
m2BufferRW.element(
|
|
153
|
+
m2BufferRW.element( pixelId ).assign( m2 );
|
|
146
154
|
|
|
147
155
|
const meanLum = luminance( finalColor ).toVar();
|
|
148
156
|
const sampleVar = m2.sub( meanLum.mul( meanLum ) ).max( float( 0 ) );
|
|
@@ -151,11 +159,15 @@ export function buildFinalWriteKernel( params ) {
|
|
|
151
159
|
// Combined criterion: bright pixels converge on relErr<threshold; dark/dim pixels (where relErr
|
|
152
160
|
// stays high forever) converge on absSE<absFloor, since their absolute noise is imperceptible.
|
|
153
161
|
const absSE = varOfMean.sqrt().toVar();
|
|
154
|
-
const relErr = absSE.div( meanLum.add( float( 1e-4 ) ) );
|
|
162
|
+
const relErr = absSE.div( meanLum.add( float( 1e-4 ) ) ); // Tier-2 freeze predicate reads this
|
|
163
|
+
|
|
164
|
+
// Cycles-style convergence: normalize the standard error by √luminance for dim pixels (lum<1),
|
|
165
|
+
// by luminance for bright ones. The √ loosens the relative bar in shadows so dark pixels converge
|
|
166
|
+
// on their small absolute noise instead of stalling forever on a pure relative bar.
|
|
167
|
+
const convNorm = select( meanLum.lessThan( float( 1.0 ) ), meanLum.sqrt(), meanLum );
|
|
168
|
+
const converged = absSE.div( convNorm.add( float( 1e-4 ) ) ).lessThan( noiseThreshold );
|
|
155
169
|
|
|
156
|
-
If( frame.greaterThanEqual( uint( adaptiveMinSamples ) ).and(
|
|
157
|
-
relErr.lessThan( noiseThreshold ).or( absSE.lessThan( darkNoiseFloor ) )
|
|
158
|
-
), () => {
|
|
170
|
+
If( frame.greaterThanEqual( uint( adaptiveMinSamples ) ).and( converged ), () => {
|
|
159
171
|
|
|
160
172
|
atomicAdd( counters.element( uint( COUNTER.CONVERGED_COUNT ) ), uint( 1 ) );
|
|
161
173
|
|
|
@@ -168,7 +180,7 @@ export function buildFinalWriteKernel( params ) {
|
|
|
168
180
|
If( frame.equal( uint( 0 ) ), () => {
|
|
169
181
|
|
|
170
182
|
// Frame 0 (post-reset/camera-move) clears the frozen state so each run re-freezes fresh.
|
|
171
|
-
streakBufferRW.element(
|
|
183
|
+
streakBufferRW.element( pixelId ).assign( uint( 0 ) );
|
|
172
184
|
|
|
173
185
|
} );
|
|
174
186
|
|
|
@@ -183,8 +195,8 @@ export function buildFinalWriteKernel( params ) {
|
|
|
183
195
|
|
|
184
196
|
const freezeCandidate = frame.greaterThanEqual( uint( adaptiveMinSamples ) )
|
|
185
197
|
.and( relErr.lessThan( pixelFreezeThreshold ) );
|
|
186
|
-
const newStreak = select( freezeCandidate, streakBufferRW.element(
|
|
187
|
-
streakBufferRW.element(
|
|
198
|
+
const newStreak = select( freezeCandidate, streakBufferRW.element( pixelId ).add( uint( 1 ) ), uint( 0 ) ).toVar();
|
|
199
|
+
streakBufferRW.element( pixelId ).assign( newStreak );
|
|
188
200
|
// Just reached K → frozen next frame; count it now so the frozen population is current.
|
|
189
201
|
If( newStreak.greaterThanEqual( uint( pixelFreezeStability ) ), () => {
|
|
190
202
|
|
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
* GenerateKernel.js — wavefront primary ray generation.
|
|
3
3
|
* Two dispatch modes from one builder: 2D screen-space (default) and 1D list-driven over the active-pixel
|
|
4
4
|
* list (Tier-2 per-pixel freeze).
|
|
5
|
+
*
|
|
6
|
+
* Chunked path pool (docs/internal/specs/wavefront-chunked-pool.md): the image is streamed through a fixed
|
|
7
|
+
* device-budget pool in row bands. A kernel thread maps to a LOCAL path slot `rayID` (∈ [0,B)); the global
|
|
8
|
+
* pixel is `pixelBase + rayID` where `pixelBase = chunkRowBase · renderWidth`. Path state / gBuffer are
|
|
9
|
+
* written at the LOCAL slot; camera ray + RNG seed use the GLOBAL pixel coord (so a pixel's sample sequence
|
|
10
|
+
* is stable across chunks and frames).
|
|
5
11
|
*/
|
|
6
12
|
|
|
7
13
|
import {
|
|
@@ -22,7 +28,7 @@ import { RAY_FLAG, COUNTER } from '../Processor/QueueManager.js';
|
|
|
22
28
|
import {
|
|
23
29
|
writeRayOriginMeta, writeRayDirFlags, writeRayThroughputPdf,
|
|
24
30
|
writeRayRadiance, writeGBuffer,
|
|
25
|
-
writeMediumStack,
|
|
31
|
+
writeMediumStack, writeFeatureThroughput,
|
|
26
32
|
} from '../Processor/PackedRayBuffer.js';
|
|
27
33
|
|
|
28
34
|
const WG_SIZE = 16;
|
|
@@ -34,23 +40,21 @@ export function buildGenerateKernel( params ) {
|
|
|
34
40
|
resolution, frame,
|
|
35
41
|
cameraWorldMatrix, cameraProjectionMatrixInverse,
|
|
36
42
|
enableDOF, focalLength, aperture, focusDistance, sceneScale, apertureScale, anamorphicRatio,
|
|
37
|
-
renderWidth,
|
|
43
|
+
renderWidth,
|
|
44
|
+
chunkRowBase, chunkRows, // row band offset (global first row) + row count for this chunk
|
|
38
45
|
transmissiveBounces, // per-ray refraction budget (megakernel parity: PathTracerCore.js:606)
|
|
39
46
|
transparentBackground, // alpha inits to 1 here (megakernel parity: PathTracerCore.js:554) — env-escape-without-opaque zeroes it in Shade
|
|
40
47
|
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] =
|
|
48
|
+
// listDriven: 1D dispatch over the active-pixel list (activeIndicesRO[tid] = LOCAL slot) instead of 2D.
|
|
42
49
|
listDriven = false, activeIndicesRO = null, counters = null,
|
|
43
50
|
} = params;
|
|
44
51
|
|
|
45
52
|
const auxOn = auxGBufferEnabled.greaterThan( uint( 0 ) );
|
|
46
53
|
|
|
47
|
-
// Shared ray-gen body
|
|
48
|
-
const emitRay = ( gx, gy ) => {
|
|
54
|
+
// Shared ray-gen body. (gx, gy) is the GLOBAL pixel; rayID is the LOCAL path-slot (both dispatch modes).
|
|
55
|
+
const emitRay = ( gx, gy, rayID ) => {
|
|
49
56
|
|
|
50
57
|
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
58
|
|
|
55
59
|
const screenPosition = pixelCoord.div( resolution ).mul( 2.0 ).sub( 1.0 ).toVar();
|
|
56
60
|
screenPosition.y.assign( screenPosition.y.negate() );
|
|
@@ -88,8 +92,10 @@ export function buildGenerateKernel( params ) {
|
|
|
88
92
|
|
|
89
93
|
If( auxOn, () => {
|
|
90
94
|
|
|
91
|
-
// default: normal +Z, depth 1 (far), black albedo (background/miss)
|
|
92
|
-
writeGBuffer( gBufferRW,
|
|
95
|
+
// default: normal +Z, depth 1 (far), black albedo (background/miss). Per-CHUNK G-buffer → LOCAL slot.
|
|
96
|
+
writeGBuffer( gBufferRW, rayID, vec3( 0.0, 0.0, 1.0 ), float( 1.0 ), vec3( 0.0 ) );
|
|
97
|
+
// DDFA: seed the see-through aux tint to white (no tint yet). RMW preserves slot-5 xyz (sigmaA).
|
|
98
|
+
writeFeatureThroughput( rayBufferRW, rayID, vec3( 1.0 ) );
|
|
93
99
|
|
|
94
100
|
} );
|
|
95
101
|
|
|
@@ -107,21 +113,26 @@ export function buildGenerateKernel( params ) {
|
|
|
107
113
|
// Grid is over-sized from a stale readback; bound on the live ENTERING_COUNT so extra threads no-op.
|
|
108
114
|
If( tid.lessThan( atomicLoad( counters.element( uint( COUNTER.ENTERING_COUNT ) ) ) ), () => {
|
|
109
115
|
|
|
110
|
-
|
|
111
|
-
const
|
|
112
|
-
const
|
|
113
|
-
|
|
116
|
+
// active list holds LOCAL slots; global pixel = pixelBase + local slot.
|
|
117
|
+
const localSlot = int( activeIndicesRO.element( tid ) );
|
|
118
|
+
const globalPixel = localSlot.add( chunkRowBase.mul( renderWidth ) );
|
|
119
|
+
const gx = globalPixel.mod( renderWidth );
|
|
120
|
+
const gy = globalPixel.div( renderWidth );
|
|
121
|
+
emitRay( gx, gy, uint( localSlot ) );
|
|
114
122
|
|
|
115
123
|
} );
|
|
116
124
|
|
|
117
125
|
} else {
|
|
118
126
|
|
|
127
|
+
// 2D grid over (renderWidth × chunkRows). local_gy is the band-local row; global gy adds chunkRowBase.
|
|
119
128
|
const gx = int( workgroupId.x ).mul( WG_SIZE ).add( int( localId.x ) );
|
|
120
|
-
const
|
|
129
|
+
const localGy = int( workgroupId.y ).mul( WG_SIZE ).add( int( localId.y ) );
|
|
121
130
|
|
|
122
|
-
If( gx.lessThan( renderWidth ).and(
|
|
131
|
+
If( gx.lessThan( renderWidth ).and( localGy.lessThan( chunkRows ) ), () => {
|
|
123
132
|
|
|
124
|
-
|
|
133
|
+
const gy = localGy.add( chunkRowBase );
|
|
134
|
+
const localRayID = uint( localGy.mul( renderWidth ).add( gx ) );
|
|
135
|
+
emitRay( gx, gy, localRayID );
|
|
125
136
|
|
|
126
137
|
} );
|
|
127
138
|
|