three-gpu-pathtracer 0.0.24 → 0.0.26
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 +53 -530
- package/build/index.module.js +262 -50
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +261 -48
- package/build/index.umd.cjs.map +1 -1
- package/package.json +29 -11
- package/src/core/WebGLPathTracer.js +8 -0
- package/src/core/utils/sceneUpdateUtils.js +8 -12
- package/src/detectors/PrecisionMaterial.js +9 -9
- package/src/index.js +1 -0
- package/src/objects/PhysicalSpotLight.js +2 -0
- package/src/shader/bsdf/bsdf_functions.glsl.js +0 -1
- package/src/textures/BlueNoiseTexture.js +6 -6
- package/src/textures/ProceduralEquirectTexture.js +8 -7
- package/src/textures/turquinMetal.png +0 -0
- package/src/uniforms/EquirectHdrInfoUniform.js +9 -4
- package/src/uniforms/FloatAttributeTextureArray.js +11 -11
- package/src/uniforms/MaterialsTexture.js +9 -9
- package/src/webgpu/API.md +762 -0
- package/src/webgpu/AtlasTexture.js +471 -0
- package/src/webgpu/BlurredEnvMapGenerator.js +129 -0
- package/src/webgpu/EquirectBackgroundInfo.js +97 -0
- package/src/webgpu/EquirectHdrInfoNode.js +151 -0
- package/src/webgpu/LightsInfoNode.js +224 -0
- package/src/webgpu/MegaKernelPathTracer.js +277 -0
- package/src/webgpu/PathTracerBackend.js +216 -0
- package/src/webgpu/TurquinTexture.js +169 -0
- package/src/webgpu/WaveFrontPathTracer.js +586 -0
- package/src/webgpu/WebGPUPathTracer.js +1302 -0
- package/src/webgpu/compute/ComputeKernel.js +92 -0
- package/src/webgpu/compute/CopyBufferKernel.js +40 -0
- package/src/webgpu/compute/PathTracerMegaKernel.js +470 -0
- package/src/webgpu/compute/SampleDebugKernel.js +51 -0
- package/src/webgpu/compute/TallySampleCountsKernel.js +111 -0
- package/src/webgpu/compute/ZeroOutBufferKernel.js +35 -0
- package/src/webgpu/compute/ZeroOutKernel.js +31 -0
- package/src/webgpu/compute/wavefront/LogicKernel.js +349 -0
- package/src/webgpu/compute/wavefront/MaterialKernel.js +371 -0
- package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +72 -0
- package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +38 -0
- package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +66 -0
- package/src/webgpu/compute/wavefront/TraceRayKernel.js +71 -0
- package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +67 -0
- package/src/webgpu/compute/wavefront/structs.js +155 -0
- package/src/webgpu/constants.js +62 -0
- package/src/webgpu/denoise/OIDNDenoiser.js +431 -0
- package/src/webgpu/index.d.ts +176 -0
- package/src/webgpu/index.js +11 -0
- package/src/webgpu/materials/GltfCompliantMaterial.js +608 -0
- package/src/webgpu/materials/GraphMaterial.js +331 -0
- package/src/webgpu/materials/PathtracingMaterial.js +89 -0
- package/src/webgpu/materials/RenderToScreenMaterial.js +150 -0
- package/src/webgpu/materials/debug/AtlasDebugMaterial.js +41 -0
- package/src/webgpu/materials/debug/SampleDensityMaterial.js +61 -0
- package/src/webgpu/nodes/PathtracerBVHComputeData.js +1037 -0
- package/src/webgpu/nodes/debugBounds.wgsl.js +228 -0
- package/src/webgpu/nodes/eon.wgsl.js +262 -0
- package/src/webgpu/nodes/ggx.wgsl.js +190 -0
- package/src/webgpu/nodes/lights.wgsl.js +203 -0
- package/src/webgpu/nodes/material.wgsl.js +990 -0
- package/src/webgpu/nodes/rand/bluedither.wgsl.js +92 -0
- package/src/webgpu/nodes/rand/pcg.wgsl.js +81 -0
- package/src/webgpu/nodes/rand/sobol.wgsl.js +287 -0
- package/src/webgpu/nodes/random.wgsl.js +20 -0
- package/src/webgpu/nodes/reset.wgsl.js +21 -0
- package/src/webgpu/nodes/sampling.wgsl.js +193 -0
- package/src/webgpu/nodes/sheen.wgsl.js +148 -0
- package/src/webgpu/nodes/structs.wgsl.js +272 -0
- package/src/webgpu/nodes/utils.wgsl.js +366 -0
- package/src/webgpu/shims/ArrayCameraShim.js +74 -0
- package/src/webgpu/shims/EquirectCameraShim.js +37 -0
- package/src/webgpu/shims/PhysicalCameraShim.js +151 -0
- package/src/webgpu/upscale/FSRUpscaler.js +108 -0
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
import { Matrix4, StorageBufferAttribute, Vector2 } from 'three/webgpu';
|
|
2
|
+
import { PopulatePixelIndicesKernel } from './compute/wavefront/PopulatePixelIndicesKernel.js';
|
|
3
|
+
import { LogicKernel } from './compute/wavefront/LogicKernel.js';
|
|
4
|
+
import { MaterialKernel } from './compute/wavefront/MaterialKernel.js';
|
|
5
|
+
import { TraceRayKernel } from './compute/wavefront/TraceRayKernel.js';
|
|
6
|
+
import { TraceShadowRayKernel } from './compute/wavefront/TraceShadowRayKernel.js';
|
|
7
|
+
import { QueueLengthToDispatchKernel } from './compute/wavefront/QueueLengthToDispatchKernel.js';
|
|
8
|
+
import { ResetSlotsKernel } from './compute/wavefront/ResetSlotsKernel.js';
|
|
9
|
+
import { ZeroOutBufferKernel } from './compute/ZeroOutBufferKernel.js';
|
|
10
|
+
import { CopyBufferKernel } from './compute/CopyBufferKernel.js';
|
|
11
|
+
import { EquirectHdrInfoNode } from './EquirectHdrInfoNode.js';
|
|
12
|
+
import { EquirectBackgroundInfo } from './EquirectBackgroundInfo.js';
|
|
13
|
+
import { LightsInfoNode } from './LightsInfoNode.js';
|
|
14
|
+
import { rayDataStruct, traceQueuedRayStruct, intersectionResultStruct, rayQueueStruct, pixelQueueStruct } from './compute/wavefront/structs.js';
|
|
15
|
+
import { PathTracerBackend } from './PathTracerBackend.js';
|
|
16
|
+
import { FILTER_GLOSSY_DISABLED } from './nodes/material.wgsl.js';
|
|
17
|
+
import {
|
|
18
|
+
PrimeSampleCountersKernel,
|
|
19
|
+
TallySampleCountsKernel,
|
|
20
|
+
SAMPLE_COUNTER_LENGTH,
|
|
21
|
+
SAMPLE_COUNTER_MAX,
|
|
22
|
+
SAMPLE_COUNTER_MIN,
|
|
23
|
+
SAMPLE_COUNTER_PIXEL_COUNT,
|
|
24
|
+
SAMPLE_COUNTER_TOTAL_HI,
|
|
25
|
+
SAMPLE_COUNTER_TOTAL_LO,
|
|
26
|
+
U32_RANGE,
|
|
27
|
+
} from './compute/TallySampleCountsKernel.js';
|
|
28
|
+
|
|
29
|
+
// set the buffers to the max possible size supported by default (128MB)
|
|
30
|
+
// TODO: this can be increased based on platform.
|
|
31
|
+
const MAX_BUFFER_SIZE = 134217728;
|
|
32
|
+
|
|
33
|
+
// the persistent path-slot pool is sized off the ray data struct; every other per-slot buffer
|
|
34
|
+
// ( queues, intersections ) is sized to the same count so a full pool can never overflow them
|
|
35
|
+
const MAX_RAY_DATA_COUNT = Math.floor( MAX_BUFFER_SIZE / ( rayDataStruct.getLength() * 4 ) );
|
|
36
|
+
|
|
37
|
+
const LOW_RES_ITERATIONS = 5;
|
|
38
|
+
|
|
39
|
+
export class WaveFrontPathTracer extends PathTracerBackend {
|
|
40
|
+
|
|
41
|
+
constructor( renderer ) {
|
|
42
|
+
|
|
43
|
+
super( renderer );
|
|
44
|
+
|
|
45
|
+
// options
|
|
46
|
+
this.seed = 0;
|
|
47
|
+
this.envInfo = new EquirectHdrInfoNode();
|
|
48
|
+
this.backgroundInfo = new EquirectBackgroundInfo();
|
|
49
|
+
this.lightsInfo = new LightsInfoNode();
|
|
50
|
+
|
|
51
|
+
// The path slot pool, sized to the frame budget and resized in the render loop as the budget
|
|
52
|
+
// changes. Every per-slot buffer is allocated to the same count so a full pool can never
|
|
53
|
+
// overflow them.
|
|
54
|
+
this.rayCount = 0;
|
|
55
|
+
|
|
56
|
+
// persistent per-path state, one slot per in-flight path
|
|
57
|
+
this.rayDataStorage = null;
|
|
58
|
+
|
|
59
|
+
// append-only trace queues, prefixed by an atomic length header
|
|
60
|
+
this.rayQueue = null;
|
|
61
|
+
this.shadowRayQueue = null;
|
|
62
|
+
|
|
63
|
+
// per-queue-slot trace results, indexed by the ray's position in its queue
|
|
64
|
+
this.rayIntersectionsStorage = null;
|
|
65
|
+
this.shadowRayIntersectionsStorage = null;
|
|
66
|
+
|
|
67
|
+
// pixel indices waiting for a free path slot, sized to the resolution
|
|
68
|
+
this.pixelQueue = null;
|
|
69
|
+
|
|
70
|
+
// reduction target for the per pixel sample counts, read back asynchronously
|
|
71
|
+
this.sampleCountersStorage = new StorageBufferAttribute( new Uint32Array( SAMPLE_COUNTER_LENGTH ), SAMPLE_COUNTER_LENGTH );
|
|
72
|
+
this.sampleCountersStorage.name = 'Sample Counters';
|
|
73
|
+
this._samplesPromise = null;
|
|
74
|
+
|
|
75
|
+
// kernels
|
|
76
|
+
this.populatePixelIndicesKernel = new PopulatePixelIndicesKernel().setWorkgroupSize( 8, 8, 1 );
|
|
77
|
+
this.logicKernel = new LogicKernel().setWorkgroupSize( 64, 1, 1 );
|
|
78
|
+
this.materialKernel = new MaterialKernel().setWorkgroupSize( 64, 1, 1 );
|
|
79
|
+
this.traceRayKernel = new TraceRayKernel().setWorkgroupSize( 64, 1, 1 );
|
|
80
|
+
this.traceShadowRayKernel = new TraceShadowRayKernel().setWorkgroupSize( 64, 1, 1 );
|
|
81
|
+
this.rayDispatchConverter = new QueueLengthToDispatchKernel().setWorkgroupSize( 1, 1, 1 );
|
|
82
|
+
this.shadowDispatchConverter = new QueueLengthToDispatchKernel().setWorkgroupSize( 1, 1, 1 );
|
|
83
|
+
this.primeSampleCountersKernel = new PrimeSampleCountersKernel().setWorkgroupSize( 1, 1, 1 );
|
|
84
|
+
this.tallySampleCountsKernel = new TallySampleCountsKernel().setWorkgroupSize( 8, 8, 1 );
|
|
85
|
+
|
|
86
|
+
// pool resizing: carry live slots into a reallocated pool and idle the rest
|
|
87
|
+
this.resetSlotsKernel = new ResetSlotsKernel().setWorkgroupSize( 64, 1, 1 );
|
|
88
|
+
this.copyRayDataKernel = new CopyBufferKernel( rayDataStruct ).setWorkgroupSize( 64, 1, 1 );
|
|
89
|
+
|
|
90
|
+
// bind the shared env / lights providers so the kernels' proxies resolve even before they're set
|
|
91
|
+
this.logicKernel.envInfo = this.envInfo;
|
|
92
|
+
this.logicKernel.backgroundInfo = this.backgroundInfo;
|
|
93
|
+
this.logicKernel.lightsInfo = this.lightsInfo;
|
|
94
|
+
|
|
95
|
+
// clear kernels
|
|
96
|
+
this.zeroDispatchKernel = new ZeroOutBufferKernel().setWorkgroupSize( 1, 1, 1 );
|
|
97
|
+
|
|
98
|
+
// later
|
|
99
|
+
this.volumeKernel = null;
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
resetSeed() {
|
|
104
|
+
|
|
105
|
+
this.seed = 0;
|
|
106
|
+
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
reset() {
|
|
110
|
+
|
|
111
|
+
super.reset();
|
|
112
|
+
this._samplesPromise = null;
|
|
113
|
+
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
setBVHData( bvhData ) {
|
|
117
|
+
|
|
118
|
+
this.materialKernel.bvhData = bvhData;
|
|
119
|
+
this.materialKernel.needsUpdate = true;
|
|
120
|
+
|
|
121
|
+
this.traceRayKernel.bvhData = bvhData;
|
|
122
|
+
this.traceRayKernel.needsUpdate = true;
|
|
123
|
+
|
|
124
|
+
this.traceShadowRayKernel.bvhData = bvhData;
|
|
125
|
+
this.traceShadowRayKernel.needsUpdate = true;
|
|
126
|
+
|
|
127
|
+
this.reset();
|
|
128
|
+
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
rebuild() {
|
|
132
|
+
|
|
133
|
+
super.rebuild();
|
|
134
|
+
|
|
135
|
+
// MaterialKernel bakes in the camera ray function, so a camera swap needs a recompile
|
|
136
|
+
this.materialKernel.needsUpdate = true;
|
|
137
|
+
this.reset();
|
|
138
|
+
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
setRandom( random ) {
|
|
142
|
+
|
|
143
|
+
this.logicKernel.context.random = random;
|
|
144
|
+
this.logicKernel.needsUpdate = true;
|
|
145
|
+
|
|
146
|
+
this.materialKernel.context.random = random;
|
|
147
|
+
this.materialKernel.needsUpdate = true;
|
|
148
|
+
|
|
149
|
+
this.traceRayKernel.context.random = random;
|
|
150
|
+
this.traceRayKernel.needsUpdate = true;
|
|
151
|
+
|
|
152
|
+
this.traceShadowRayKernel.context.random = random;
|
|
153
|
+
this.traceShadowRayKernel.needsUpdate = true;
|
|
154
|
+
|
|
155
|
+
this.reset();
|
|
156
|
+
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
setMaterial( material ) {
|
|
160
|
+
|
|
161
|
+
this.materialKernel.material = material.getData();
|
|
162
|
+
this.materialKernel.needsUpdate = true;
|
|
163
|
+
this.reset();
|
|
164
|
+
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
setFilterGlossy( value ) {
|
|
168
|
+
|
|
169
|
+
// the kernel takes the inverted threshold, mirroring Cycles
|
|
170
|
+
this.materialKernel.filterGlossy = value === 0 ? FILTER_GLOSSY_DISABLED : 1 / value;
|
|
171
|
+
this.reset();
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
setClamping( direct, indirect ) {
|
|
176
|
+
|
|
177
|
+
this.logicKernel.clampDirect = direct;
|
|
178
|
+
this.logicKernel.clampIndirect = indirect;
|
|
179
|
+
this.reset();
|
|
180
|
+
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
setTransmissiveBackground( value ) {
|
|
184
|
+
|
|
185
|
+
this.logicKernel.transmissiveBackground = value;
|
|
186
|
+
this.reset();
|
|
187
|
+
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
setEnvironment( envMap ) {
|
|
191
|
+
|
|
192
|
+
this.envInfo.updateFrom( envMap );
|
|
193
|
+
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
setLights( lights ) {
|
|
197
|
+
|
|
198
|
+
this.lightsInfo.updateFrom( this.renderer, lights );
|
|
199
|
+
this.reset();
|
|
200
|
+
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
setEnvironmentParams( envMapIntensity, envMapRotation ) {
|
|
204
|
+
|
|
205
|
+
const { envInfo } = this;
|
|
206
|
+
const rotationMatrix = new Matrix4().makeRotationFromEuler( envMapRotation ).invert();
|
|
207
|
+
envInfo.rotationNode.value.setFromMatrix4( rotationMatrix );
|
|
208
|
+
envInfo.intensityNode.value = envMapIntensity;
|
|
209
|
+
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
setMultipleImportanceSampling( enabled ) {
|
|
213
|
+
|
|
214
|
+
this.logicKernel.misEnabled = enabled ? 1 : 0;
|
|
215
|
+
this.reset();
|
|
216
|
+
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
setBackground( background ) {
|
|
220
|
+
|
|
221
|
+
const { backgroundInfo } = this;
|
|
222
|
+
backgroundInfo.dispose();
|
|
223
|
+
backgroundInfo.map = background;
|
|
224
|
+
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
setBackgroundParams(
|
|
228
|
+
backgroundIntensity,
|
|
229
|
+
backgroundRotation,
|
|
230
|
+
backgroundBlurriness,
|
|
231
|
+
) {
|
|
232
|
+
|
|
233
|
+
const { backgroundInfo } = this;
|
|
234
|
+
const rotationMatrix = new Matrix4().makeRotationFromEuler( backgroundRotation ).invert();
|
|
235
|
+
backgroundInfo.rotationNode.value.copy( rotationMatrix );
|
|
236
|
+
backgroundInfo.intensity = backgroundIntensity;
|
|
237
|
+
backgroundInfo.blur = backgroundBlurriness;
|
|
238
|
+
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
dispose() {
|
|
242
|
+
|
|
243
|
+
super.dispose();
|
|
244
|
+
|
|
245
|
+
this.envInfo.dispose();
|
|
246
|
+
this.lightsInfo.dispose();
|
|
247
|
+
|
|
248
|
+
this.rayDataStorage?.dispose();
|
|
249
|
+
this.rayQueue?.dispose();
|
|
250
|
+
this.shadowRayQueue?.dispose();
|
|
251
|
+
this.rayIntersectionsStorage?.dispose();
|
|
252
|
+
this.shadowRayIntersectionsStorage?.dispose();
|
|
253
|
+
this.sampleCountersStorage.dispose();
|
|
254
|
+
this.pixelQueue?.dispose();
|
|
255
|
+
|
|
256
|
+
// TODO: the dispatch converters own their indirect dispatch buffers, they should dispose of them themselves
|
|
257
|
+
this.rayDispatchConverter.outputDispatch.dispose();
|
|
258
|
+
this.shadowDispatchConverter.outputDispatch.dispose();
|
|
259
|
+
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// The queue holds every pixel not owned by a slot. It is sized to the full pixel count so slots
|
|
263
|
+
// retired by a shrink can always return theirs.
|
|
264
|
+
_updatePixelQueue( width, height ) {
|
|
265
|
+
|
|
266
|
+
const size = pixelQueueStruct.getLength() + Math.max( width * height, 1 );
|
|
267
|
+
if ( ! this.pixelQueue || this.pixelQueue.array.length < size ) {
|
|
268
|
+
|
|
269
|
+
this.pixelQueue?.dispose();
|
|
270
|
+
this.pixelQueue = new StorageBufferAttribute( new Float32Array( size ), size );
|
|
271
|
+
this.pixelQueue.name = 'Pixel Queue';
|
|
272
|
+
|
|
273
|
+
// the queue buffer object changed, so kernels bound to it must rebuild
|
|
274
|
+
this.materialKernel.pixelQueue = this.pixelQueue;
|
|
275
|
+
this.materialKernel.needsUpdate = true;
|
|
276
|
+
this.populatePixelIndicesKernel.pixelQueue = this.pixelQueue;
|
|
277
|
+
this.populatePixelIndicesKernel.needsUpdate = true;
|
|
278
|
+
this.resetSlotsKernel.pixelQueue = this.pixelQueue;
|
|
279
|
+
this.resetSlotsKernel.needsUpdate = true;
|
|
280
|
+
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Reallocates the per slot buffers to "count" slots. Slots that survive carry their ray data
|
|
286
|
+
// over, dropped slots return their pixel to the queue, and added slots take one from it. The
|
|
287
|
+
// trace queues and results only live within a frame, so they start empty.
|
|
288
|
+
_resizePool( count ) {
|
|
289
|
+
|
|
290
|
+
const { renderer, resetSlotsKernel, copyRayDataKernel } = this;
|
|
291
|
+
const previousCount = this.rayCount;
|
|
292
|
+
|
|
293
|
+
resetSlotsKernel.pixelQueue = this.pixelQueue;
|
|
294
|
+
if ( count < previousCount ) {
|
|
295
|
+
|
|
296
|
+
resetSlotsKernel.rayDataStorage = this.rayDataStorage;
|
|
297
|
+
resetSlotsKernel.start = count;
|
|
298
|
+
resetSlotsKernel.end = previousCount;
|
|
299
|
+
resetSlotsKernel.addingSlots = false;
|
|
300
|
+
renderer.compute( resetSlotsKernel.kernel, resetSlotsKernel.getDispatchSize( previousCount - count, 1, 1 ) );
|
|
301
|
+
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const rayDataStorage = new StorageBufferAttribute( count, rayDataStruct.getLength() );
|
|
305
|
+
rayDataStorage.name = 'Ray Data';
|
|
306
|
+
|
|
307
|
+
const queueSize = rayQueueStruct.getLength() + count * traceQueuedRayStruct.getLength();
|
|
308
|
+
const rayQueue = new StorageBufferAttribute( new Float32Array( queueSize ), queueSize );
|
|
309
|
+
rayQueue.name = 'Ray Queue';
|
|
310
|
+
|
|
311
|
+
const shadowRayQueue = new StorageBufferAttribute( new Float32Array( queueSize ), queueSize );
|
|
312
|
+
shadowRayQueue.name = 'Shadow Ray Queue';
|
|
313
|
+
|
|
314
|
+
const rayIntersectionsStorage = new StorageBufferAttribute( count, intersectionResultStruct.getLength() );
|
|
315
|
+
rayIntersectionsStorage.name = 'Ray Intersections';
|
|
316
|
+
|
|
317
|
+
const shadowRayIntersectionsStorage = new StorageBufferAttribute( count, intersectionResultStruct.getLength() );
|
|
318
|
+
shadowRayIntersectionsStorage.name = 'Shadow Ray Intersections';
|
|
319
|
+
|
|
320
|
+
const copyCount = Math.min( count, previousCount );
|
|
321
|
+
if ( copyCount > 0 ) {
|
|
322
|
+
|
|
323
|
+
copyRayDataKernel.source = this.rayDataStorage;
|
|
324
|
+
copyRayDataKernel.target = rayDataStorage;
|
|
325
|
+
copyRayDataKernel.count = copyCount;
|
|
326
|
+
renderer.compute( copyRayDataKernel.kernel, copyRayDataKernel.getDispatchSize( copyCount, 1, 1 ) );
|
|
327
|
+
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
this.rayDataStorage?.dispose();
|
|
331
|
+
this.rayQueue?.dispose();
|
|
332
|
+
this.shadowRayQueue?.dispose();
|
|
333
|
+
this.rayIntersectionsStorage?.dispose();
|
|
334
|
+
this.shadowRayIntersectionsStorage?.dispose();
|
|
335
|
+
|
|
336
|
+
this.rayDataStorage = rayDataStorage;
|
|
337
|
+
this.rayQueue = rayQueue;
|
|
338
|
+
this.shadowRayQueue = shadowRayQueue;
|
|
339
|
+
this.rayIntersectionsStorage = rayIntersectionsStorage;
|
|
340
|
+
this.shadowRayIntersectionsStorage = shadowRayIntersectionsStorage;
|
|
341
|
+
this.rayCount = count;
|
|
342
|
+
|
|
343
|
+
// the buffer objects changed, so kernels bound to them must rebuild
|
|
344
|
+
this.logicKernel.needsUpdate = true;
|
|
345
|
+
this.materialKernel.needsUpdate = true;
|
|
346
|
+
this.traceRayKernel.needsUpdate = true;
|
|
347
|
+
this.traceShadowRayKernel.needsUpdate = true;
|
|
348
|
+
this.populatePixelIndicesKernel.needsUpdate = true;
|
|
349
|
+
this.resetSlotsKernel.needsUpdate = true;
|
|
350
|
+
|
|
351
|
+
if ( count > previousCount ) {
|
|
352
|
+
|
|
353
|
+
resetSlotsKernel.rayDataStorage = rayDataStorage;
|
|
354
|
+
resetSlotsKernel.start = previousCount;
|
|
355
|
+
resetSlotsKernel.end = count;
|
|
356
|
+
resetSlotsKernel.addingSlots = true;
|
|
357
|
+
renderer.compute( resetSlotsKernel.kernel, resetSlotsKernel.getDispatchSize( count - previousCount, 1, 1 ) );
|
|
358
|
+
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// The slot count the current budget asks for, capped by the pool limit and the pixel count
|
|
364
|
+
_getRequestedSlotCount( pixelCount ) {
|
|
365
|
+
|
|
366
|
+
return Math.min( MAX_RAY_DATA_COUNT, pixelCount, Math.max( 1, Math.floor( this.frameBudget ) ) );
|
|
367
|
+
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Resizes the pool when the budget changes. Paths in flight past a smaller budget are dropped.
|
|
371
|
+
_applyBudget( pixelCount ) {
|
|
372
|
+
|
|
373
|
+
const requested = this._getRequestedSlotCount( pixelCount );
|
|
374
|
+
if ( requested !== this.rayCount ) {
|
|
375
|
+
|
|
376
|
+
this._resizePool( requested );
|
|
377
|
+
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
*createRenderTask() {
|
|
383
|
+
|
|
384
|
+
const {
|
|
385
|
+
renderer,
|
|
386
|
+
maxTransparentBounces,
|
|
387
|
+
|
|
388
|
+
logicKernel,
|
|
389
|
+
materialKernel,
|
|
390
|
+
traceRayKernel,
|
|
391
|
+
traceShadowRayKernel,
|
|
392
|
+
rayDispatchConverter,
|
|
393
|
+
shadowDispatchConverter,
|
|
394
|
+
zeroDispatchKernel,
|
|
395
|
+
populatePixelIndicesKernel,
|
|
396
|
+
} = this;
|
|
397
|
+
|
|
398
|
+
// a resize resets, recreating this task, so the dimensions and pixel queue hold for its
|
|
399
|
+
// lifetime. The slot pool starts tight, at the budget, and is resized in the loop below.
|
|
400
|
+
const targetDimensions = new Vector2();
|
|
401
|
+
this.getSize( targetDimensions );
|
|
402
|
+
|
|
403
|
+
// referenced via "this" since the buffers may be replaced here and in the loop
|
|
404
|
+
this._updatePixelQueue( targetDimensions.x, targetDimensions.y );
|
|
405
|
+
|
|
406
|
+
// the populate pass below overwrites whatever the resize carried over
|
|
407
|
+
const pixelCount = targetDimensions.x * targetDimensions.y;
|
|
408
|
+
this._applyBudget( pixelCount );
|
|
409
|
+
|
|
410
|
+
// reset the trace queues — only the length header needs zeroing
|
|
411
|
+
zeroDispatchKernel.target = this.rayQueue;
|
|
412
|
+
renderer.compute( zeroDispatchKernel.kernel, [ 1 ] );
|
|
413
|
+
|
|
414
|
+
zeroDispatchKernel.target = this.shadowRayQueue;
|
|
415
|
+
renderer.compute( zeroDispatchKernel.kernel, [ 1 ] );
|
|
416
|
+
|
|
417
|
+
// assign every path slot a pixel and park the overflow pixels in the pixel queue
|
|
418
|
+
populatePixelIndicesKernel.rayDataStorage = this.rayDataStorage;
|
|
419
|
+
populatePixelIndicesKernel.pixelQueue = this.pixelQueue;
|
|
420
|
+
populatePixelIndicesKernel.frameBudget = this.rayCount;
|
|
421
|
+
populatePixelIndicesKernel.targetDimensions.copy( targetDimensions );
|
|
422
|
+
renderer.compute( populatePixelIndicesKernel.kernel, populatePixelIndicesKernel.getDispatchSize( targetDimensions.x, targetDimensions.y, 1 ) );
|
|
423
|
+
|
|
424
|
+
// advance the sequence once per task so each render pass starts from fresh
|
|
425
|
+
// noise unless "stableNoise" has reset it
|
|
426
|
+
this.seed ++;
|
|
427
|
+
|
|
428
|
+
while ( true ) {
|
|
429
|
+
|
|
430
|
+
// TODO: this only makes sense if we can cap the number rays per iteration to the full
|
|
431
|
+
// frame
|
|
432
|
+
const iter = this.lowResMode ? Math.min( this.maxBounces, LOW_RES_ITERATIONS ) : 1;
|
|
433
|
+
|
|
434
|
+
for ( let i = 0; i < iter; i ++ ) {
|
|
435
|
+
|
|
436
|
+
// Swap targets to support devices without <rgba32float, read_write> textures
|
|
437
|
+
// Copy latest data to a new outputTarget to keep the appearance
|
|
438
|
+
// TODO: this full resolution copy runs every frame - remove it by writing terminated
|
|
439
|
+
// samples to both targets or gating the swap on devices that support read_write
|
|
440
|
+
renderer.copyTextureToTexture( this.outputTarget, this.prevOutputTarget );
|
|
441
|
+
[ this.outputTarget, this.prevOutputTarget ] = [ this.prevOutputTarget, this.outputTarget ];
|
|
442
|
+
logicKernel.prevOutputTarget = this.prevOutputTarget;
|
|
443
|
+
logicKernel.outputTarget = this.outputTarget;
|
|
444
|
+
logicKernel.sampleCountTarget = this.sampleCountTarget;
|
|
445
|
+
|
|
446
|
+
// Step 1: resolve last frame's trace results — accumulate NEE / emission / env, terminate
|
|
447
|
+
// finished paths into the output, and pick the next NEE light for each live path
|
|
448
|
+
logicKernel.rayDataStorage = this.rayDataStorage;
|
|
449
|
+
logicKernel.rayIntersectionsStorage = this.rayIntersectionsStorage;
|
|
450
|
+
logicKernel.shadowRayIntersectionsStorage = this.shadowRayIntersectionsStorage;
|
|
451
|
+
logicKernel.maxBounces = this.maxBounces;
|
|
452
|
+
logicKernel.rayCount = this.rayCount;
|
|
453
|
+
renderer.compute( logicKernel.kernel, logicKernel.getDispatchSize( this.rayCount, 1, 1 ) );
|
|
454
|
+
|
|
455
|
+
// the trace results are consumed, so only the slot state has to survive a resize
|
|
456
|
+
this._applyBudget( pixelCount );
|
|
457
|
+
|
|
458
|
+
const {
|
|
459
|
+
rayDataStorage,
|
|
460
|
+
rayQueue,
|
|
461
|
+
shadowRayQueue,
|
|
462
|
+
rayIntersectionsStorage,
|
|
463
|
+
shadowRayIntersectionsStorage,
|
|
464
|
+
rayCount,
|
|
465
|
+
} = this;
|
|
466
|
+
|
|
467
|
+
// Step 2: reset the trace queues for this frame's population
|
|
468
|
+
zeroDispatchKernel.target = rayQueue;
|
|
469
|
+
renderer.compute( zeroDispatchKernel.kernel, [ 1 ] );
|
|
470
|
+
|
|
471
|
+
zeroDispatchKernel.target = shadowRayQueue;
|
|
472
|
+
renderer.compute( zeroDispatchKernel.kernel, [ 1 ] );
|
|
473
|
+
|
|
474
|
+
// Step 3: evaluate materials — spawn camera rays for freed slots, sample the bsdf, and
|
|
475
|
+
// enqueue this frame's bounce + shadow rays
|
|
476
|
+
materialKernel.rayDataStorage = rayDataStorage;
|
|
477
|
+
materialKernel.rayQueue = rayQueue;
|
|
478
|
+
materialKernel.shadowRayQueue = shadowRayQueue;
|
|
479
|
+
materialKernel.pixelQueue = this.pixelQueue;
|
|
480
|
+
materialKernel.sampleCountTarget = this.sampleCountTarget;
|
|
481
|
+
materialKernel.seed = this.seed;
|
|
482
|
+
materialKernel.maxSamples = this.maxSamples;
|
|
483
|
+
materialKernel.rayCount = rayCount;
|
|
484
|
+
materialKernel.maxTransparentBounces = maxTransparentBounces;
|
|
485
|
+
materialKernel.maxBounces = this.maxBounces;
|
|
486
|
+
materialKernel.targetDimensions.copy( targetDimensions );
|
|
487
|
+
renderer.compute( materialKernel.kernel, materialKernel.getDispatchSize( rayCount, 1, 1 ) );
|
|
488
|
+
|
|
489
|
+
// Step 4: convert the queue lengths into indirect dispatch sizes and trace
|
|
490
|
+
rayDispatchConverter.queue = rayQueue;
|
|
491
|
+
renderer.compute( rayDispatchConverter.kernel, [ 1, 1, 1 ] );
|
|
492
|
+
|
|
493
|
+
traceRayKernel.rayQueue = rayQueue;
|
|
494
|
+
traceRayKernel.rayIntersectionsStorage = rayIntersectionsStorage;
|
|
495
|
+
renderer.compute( traceRayKernel.kernel, rayDispatchConverter.outputDispatch );
|
|
496
|
+
|
|
497
|
+
shadowDispatchConverter.queue = shadowRayQueue;
|
|
498
|
+
renderer.compute( shadowDispatchConverter.kernel, [ 1, 1, 1 ] );
|
|
499
|
+
|
|
500
|
+
traceShadowRayKernel.shadowRayQueue = shadowRayQueue;
|
|
501
|
+
traceShadowRayKernel.shadowRayIntersectionsStorage = shadowRayIntersectionsStorage;
|
|
502
|
+
renderer.compute( traceShadowRayKernel.kernel, shadowDispatchConverter.outputDispatch );
|
|
503
|
+
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
yield;
|
|
507
|
+
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// Reduces the per pixel sample counts and reads them back. Runs a full resolution pass, so it
|
|
513
|
+
// only happens when asked for.
|
|
514
|
+
getSampleCountsAsync() {
|
|
515
|
+
|
|
516
|
+
// share the in flight measurement rather than dispatching another
|
|
517
|
+
if ( this._samplesPromise === null ) {
|
|
518
|
+
|
|
519
|
+
const promise = this._measureSampleCounts().finally( () => {
|
|
520
|
+
|
|
521
|
+
// a reset may have started a newer measurement in the meantime
|
|
522
|
+
if ( this._samplesPromise === promise ) {
|
|
523
|
+
|
|
524
|
+
this._samplesPromise = null;
|
|
525
|
+
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
} );
|
|
529
|
+
|
|
530
|
+
this._samplesPromise = promise;
|
|
531
|
+
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
return this._samplesPromise;
|
|
535
|
+
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
async _measureSampleCounts() {
|
|
539
|
+
|
|
540
|
+
const {
|
|
541
|
+
renderer,
|
|
542
|
+
sampleCountTarget,
|
|
543
|
+
sampleCountersStorage,
|
|
544
|
+
primeSampleCountersKernel,
|
|
545
|
+
tallySampleCountsKernel,
|
|
546
|
+
} = this;
|
|
547
|
+
|
|
548
|
+
if ( ! renderer.initialized || this.lowResMode ) {
|
|
549
|
+
|
|
550
|
+
return { min: 0, max: 0, avg: 0 };
|
|
551
|
+
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
primeSampleCountersKernel.counters = sampleCountersStorage;
|
|
555
|
+
renderer.compute( primeSampleCountersKernel.kernel, [ 1, 1, 1 ] );
|
|
556
|
+
|
|
557
|
+
tallySampleCountsKernel.counters = sampleCountersStorage;
|
|
558
|
+
tallySampleCountsKernel.sampleCountTarget = sampleCountTarget;
|
|
559
|
+
renderer.compute(
|
|
560
|
+
tallySampleCountsKernel.kernel,
|
|
561
|
+
tallySampleCountsKernel.getDispatchSize( sampleCountTarget.width, sampleCountTarget.height ),
|
|
562
|
+
);
|
|
563
|
+
|
|
564
|
+
const buffer = await renderer.getArrayBufferAsync( sampleCountersStorage );
|
|
565
|
+
const counters = new Uint32Array( buffer );
|
|
566
|
+
const pixelCount = counters[ SAMPLE_COUNTER_PIXEL_COUNT ];
|
|
567
|
+
|
|
568
|
+
// no camera ray has been dispatched yet, so there is nothing to average over
|
|
569
|
+
if ( pixelCount === 0 ) {
|
|
570
|
+
|
|
571
|
+
return { min: 0, max: 0, avg: 0 };
|
|
572
|
+
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// the total is accumulated as a split 64 bit value to survive high sample counts
|
|
576
|
+
const total = counters[ SAMPLE_COUNTER_TOTAL_HI ] * U32_RANGE + counters[ SAMPLE_COUNTER_TOTAL_LO ];
|
|
577
|
+
|
|
578
|
+
return {
|
|
579
|
+
min: counters[ SAMPLE_COUNTER_MIN ],
|
|
580
|
+
max: counters[ SAMPLE_COUNTER_MAX ],
|
|
581
|
+
avg: total / pixelCount,
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
}
|