three-gpu-pathtracer 0.0.27 → 0.0.28
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 +0 -1
- package/build/index.module.js +5 -3
- package/build/index.module.js.map +1 -1
- package/build/index.umd.cjs +4 -2
- package/build/index.umd.cjs.map +1 -1
- package/package.json +7 -7
- package/src/core/PathTracingSceneGenerator.js +1 -1
- package/src/uniforms/EquirectHdrInfoUniform.js +4 -2
- package/src/webgpu/API.md +167 -19
- package/src/webgpu/AtlasTexture.js +2 -3
- package/src/webgpu/EquirectHdrInfoNode.js +89 -48
- package/src/webgpu/LightsInfoNode.js +1 -1
- package/src/webgpu/MegaKernelPathTracer.js +10 -3
- package/src/webgpu/PathTracerBackend.js +172 -21
- package/src/webgpu/WaveFrontPathTracer.js +156 -71
- package/src/webgpu/WebGPUPathTracer.js +628 -48
- package/src/webgpu/compute/ComputeKernel.js +8 -0
- package/src/webgpu/compute/PathTracerMegaKernel.js +187 -36
- package/src/webgpu/compute/TallySampleCountsKernel.js +10 -10
- package/src/webgpu/compute/UnpackAuxKernel.js +48 -0
- package/src/webgpu/compute/ZeroOutKernel.js +3 -1
- package/src/webgpu/compute/targets.js +55 -0
- package/src/webgpu/compute/wavefront/LogicKernel.js +67 -20
- package/src/webgpu/compute/wavefront/MaterialKernel.js +153 -66
- package/src/webgpu/compute/wavefront/MediumKernel.js +154 -0
- package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +5 -5
- package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +5 -5
- package/src/webgpu/compute/wavefront/QueueMediumWalksKernel.js +52 -0
- package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +6 -6
- package/src/webgpu/compute/wavefront/TraceRayKernel.js +11 -9
- package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +18 -25
- package/src/webgpu/compute/wavefront/structs.js +63 -37
- package/src/webgpu/constants.js +2 -2
- package/src/webgpu/denoise/OIDNDenoiser.js +81 -78
- package/src/webgpu/index.d.ts +41 -6
- package/src/webgpu/materials/GltfCompliantMaterial.js +20 -6
- package/src/webgpu/materials/debug/AuxDebugMaterial.js +54 -0
- package/src/webgpu/nodes/PathtracerBVHComputeData.js +35 -13
- package/src/webgpu/nodes/aux.wgsl.js +61 -0
- package/src/webgpu/nodes/eon.wgsl.js +1 -1
- package/src/webgpu/nodes/material.wgsl.js +2 -1
- package/src/webgpu/nodes/medium.wgsl.js +165 -0
- package/src/webgpu/nodes/random.wgsl.js +2 -0
- package/src/webgpu/nodes/sampling.wgsl.js +44 -3
- package/src/webgpu/nodes/structs.wgsl.js +7 -1
- package/src/webgpu/upscale/FSRUpscaler.js +1 -1
|
@@ -1,6 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { RedIntegerFormat, StorageTexture, UnsignedIntType } from 'three/webgpu';
|
|
1
|
+
import { TimestampQuery } from 'three/webgpu';
|
|
3
2
|
import { ZeroOutKernel } from './compute/ZeroOutKernel.js';
|
|
3
|
+
import { createAuxTarget, createOutputTarget, createSampleCountTarget } from './compute/targets.js';
|
|
4
|
+
import { AUX_LAYERS } from './nodes/aux.wgsl.js';
|
|
5
|
+
|
|
6
|
+
// the async compile state of a compute node. A node with no state has not been requested.
|
|
7
|
+
const COMPILE_STARTED = 0;
|
|
8
|
+
const COMPILE_COMPLETE = 1;
|
|
4
9
|
|
|
5
10
|
export class PathTracerBackend {
|
|
6
11
|
|
|
@@ -12,36 +17,67 @@ export class PathTracerBackend {
|
|
|
12
17
|
// how much work a single update dispatches, in path slots. Lower values keep frames
|
|
13
18
|
// responsive on slower hardware; the unit is resolution independent.
|
|
14
19
|
this.frameBudget = 250000;
|
|
20
|
+
|
|
15
21
|
this.maxTransparentBounces = 15;
|
|
22
|
+
this.maxSubsurfaceBounces = 256;
|
|
16
23
|
this.lowResMode = false;
|
|
17
24
|
|
|
18
25
|
// stop taking samples once a pixel reaches this count. zero means no limit.
|
|
19
26
|
this.maxSamples = 0;
|
|
20
27
|
|
|
28
|
+
// whether kernels compile asynchronously before their first dispatch rather than on it
|
|
29
|
+
this.asyncCompile = false;
|
|
30
|
+
|
|
31
|
+
// whether the render task is waiting on its kernels to compile, during which nothing renders
|
|
32
|
+
this.compiling = false;
|
|
33
|
+
|
|
34
|
+
// the gpu time of the last timed update and the budget it ran with. "count" increments per measurement
|
|
35
|
+
this.timing = { count: 0, frameTime: 0, frameBudget: 0 };
|
|
36
|
+
|
|
21
37
|
this._renderTask = null;
|
|
38
|
+
this._compileState = new WeakMap();
|
|
22
39
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
this.
|
|
26
|
-
this.
|
|
27
|
-
|
|
28
|
-
this.outputTarget
|
|
29
|
-
|
|
30
|
-
this.
|
|
31
|
-
this.prevOutputTarget.format = RGBAFormat;
|
|
32
|
-
this.prevOutputTarget.type = FloatType;
|
|
33
|
-
this.prevOutputTarget.colorSpace = ColorManagement.workingColorSpace;
|
|
34
|
-
this.prevOutputTarget.name = 'Output #1';
|
|
35
|
-
this.prevOutputTarget.generateMipmaps = false;
|
|
36
|
-
|
|
37
|
-
this.sampleCountTarget = new StorageTexture( 1, 1, );
|
|
38
|
-
this.sampleCountTarget.format = RedIntegerFormat;
|
|
39
|
-
this.sampleCountTarget.type = UnsignedIntType;
|
|
40
|
-
this.sampleCountTarget.name = 'Sample Count';
|
|
41
|
-
this.sampleCountTarget.generateMipmaps = false;
|
|
40
|
+
// the timestamp ids of the dispatches in the update being timed, and whether a measurement is
|
|
41
|
+
// still reading back
|
|
42
|
+
this._timestampUIDs = null;
|
|
43
|
+
this._timingFrame = false;
|
|
44
|
+
|
|
45
|
+
this.outputTarget = createOutputTarget( 'Output #0' );
|
|
46
|
+
this.prevOutputTarget = createOutputTarget( 'Output #1' );
|
|
47
|
+
this.sampleCountTarget = createSampleCountTarget( 'Sample Count' );
|
|
42
48
|
|
|
43
49
|
this.sampleCountClearKernel = new ZeroOutKernel().setWorkgroupSize( 8, 8, 1 );
|
|
44
50
|
this.outputTargetClearKernel = new ZeroOutKernel().setWorkgroupSize( 8, 8, 1 );
|
|
51
|
+
this.auxTargetClearKernel = new ZeroOutKernel().setWorkgroupSize( 8, 8, 1 );
|
|
52
|
+
|
|
53
|
+
// the per pixel first hit albedo and normal, accumulated as the denoiser's auxiliary images only
|
|
54
|
+
// while asked for. The kernels bind the placeholder otherwise.
|
|
55
|
+
this.auxTarget = null;
|
|
56
|
+
this._auxPlaceholder = createAuxTarget();
|
|
57
|
+
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// The auxiliary images start over with the render, so the render restarts with them
|
|
61
|
+
setAccumulateAux( value ) {
|
|
62
|
+
|
|
63
|
+
if ( Boolean( this.auxTarget ) === value ) {
|
|
64
|
+
|
|
65
|
+
return;
|
|
66
|
+
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.auxTarget?.dispose();
|
|
70
|
+
this.auxTarget = null;
|
|
71
|
+
|
|
72
|
+
if ( value ) {
|
|
73
|
+
|
|
74
|
+
const { width, height } = this.outputTarget;
|
|
75
|
+
this.auxTarget = createAuxTarget( 'Auxiliary Images' );
|
|
76
|
+
this.auxTarget.setSize( width, height, AUX_LAYERS );
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
this.reset();
|
|
45
81
|
|
|
46
82
|
}
|
|
47
83
|
|
|
@@ -90,6 +126,56 @@ export class PathTracerBackend {
|
|
|
90
126
|
|
|
91
127
|
}
|
|
92
128
|
|
|
129
|
+
// Compiles the kernels asynchronously and yields until they're ready so the first dispatch doesn't
|
|
130
|
+
// block on a synchronous compile. A kernel is only requested once, so a render task started by a
|
|
131
|
+
// reset waits on a compile already in flight rather than dispatching before its pipeline exists.
|
|
132
|
+
*compileKernels( kernels ) {
|
|
133
|
+
|
|
134
|
+
const { renderer, _compileState } = this;
|
|
135
|
+
|
|
136
|
+
// three.js <= r185 has no async compute compile
|
|
137
|
+
if ( ! this.asyncCompile || ! renderer.compileComputeAsync ) {
|
|
138
|
+
|
|
139
|
+
return;
|
|
140
|
+
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// TODO: three.js resolves a second "compileComputeAsync" call for a pipeline still compiling
|
|
144
|
+
// immediately. If it returned the pending compile instead, this state tracking could be removed.
|
|
145
|
+
const computeNodes = kernels.map( kernel => kernel.kernel );
|
|
146
|
+
const requested = computeNodes.filter( computeNode => ! _compileState.has( computeNode ) );
|
|
147
|
+
if ( requested.length > 0 ) {
|
|
148
|
+
|
|
149
|
+
// let a frame present before the compile blocks the main thread
|
|
150
|
+
this.compiling = true;
|
|
151
|
+
yield;
|
|
152
|
+
|
|
153
|
+
requested.forEach( computeNode => _compileState.set( computeNode, COMPILE_STARTED ) );
|
|
154
|
+
renderer.compileComputeAsync( requested )
|
|
155
|
+
.catch( () => {
|
|
156
|
+
|
|
157
|
+
// a failed compile is retried, and reported, by the dispatch
|
|
158
|
+
|
|
159
|
+
} )
|
|
160
|
+
.finally( () => {
|
|
161
|
+
|
|
162
|
+
requested.forEach( computeNode => _compileState.set( computeNode, COMPILE_COMPLETE ) );
|
|
163
|
+
|
|
164
|
+
} );
|
|
165
|
+
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
while ( computeNodes.some( computeNode => _compileState.get( computeNode ) !== COMPILE_COMPLETE ) ) {
|
|
169
|
+
|
|
170
|
+
this.compiling = true;
|
|
171
|
+
yield;
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
this.compiling = false;
|
|
176
|
+
|
|
177
|
+
}
|
|
178
|
+
|
|
93
179
|
// Rebuild the render task after a shader-generating function changes.
|
|
94
180
|
rebuild() {
|
|
95
181
|
|
|
@@ -120,6 +206,7 @@ export class PathTracerBackend {
|
|
|
120
206
|
this.outputTarget.setSize( w, h );
|
|
121
207
|
this.prevOutputTarget.setSize( w, h );
|
|
122
208
|
this.sampleCountTarget.setSize( w, h );
|
|
209
|
+
this.auxTarget?.setSize( w, h, AUX_LAYERS );
|
|
123
210
|
|
|
124
211
|
this.reset();
|
|
125
212
|
return true;
|
|
@@ -136,6 +223,13 @@ export class PathTracerBackend {
|
|
|
136
223
|
|
|
137
224
|
}
|
|
138
225
|
|
|
226
|
+
// the most pixels a preview frame can render within the frame budget
|
|
227
|
+
getPreviewPixelBudget() {
|
|
228
|
+
|
|
229
|
+
return this.frameBudget;
|
|
230
|
+
|
|
231
|
+
}
|
|
232
|
+
|
|
139
233
|
update() {
|
|
140
234
|
|
|
141
235
|
const { renderer } = this;
|
|
@@ -151,8 +245,53 @@ export class PathTracerBackend {
|
|
|
151
245
|
|
|
152
246
|
}
|
|
153
247
|
|
|
248
|
+
// time this update unless the last measurement is still reading back
|
|
249
|
+
const timed = renderer.backend.trackTimestamp && ! this._timingFrame;
|
|
250
|
+
this._timestampUIDs = timed ? [] : null;
|
|
154
251
|
this._renderTask.next();
|
|
155
252
|
|
|
253
|
+
// an update that dispatched nothing, such as one waiting on kernels to compile, isn't kept
|
|
254
|
+
if ( timed && this._timestampUIDs.length > 0 ) {
|
|
255
|
+
|
|
256
|
+
this._measureFrameTime( this._timestampUIDs );
|
|
257
|
+
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
this._timestampUIDs = null;
|
|
261
|
+
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Dispatches a kernel, recording its timestamp id while the update is being timed
|
|
265
|
+
dispatch( kernel, dispatchSize ) {
|
|
266
|
+
|
|
267
|
+
const { renderer } = this;
|
|
268
|
+
renderer.compute( kernel.kernel, dispatchSize );
|
|
269
|
+
this._timestampUIDs?.push( renderer.backend.getTimestampUID( kernel.kernel ) );
|
|
270
|
+
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Reads back the gpu time of an update's dispatches into "timing"
|
|
274
|
+
async _measureFrameTime( uids ) {
|
|
275
|
+
|
|
276
|
+
// what the update ran with, read before the readback since it may change meanwhile
|
|
277
|
+
const { renderer, timing, frameBudget } = this;
|
|
278
|
+
const { backend } = renderer;
|
|
279
|
+
|
|
280
|
+
this._timingFrame = true;
|
|
281
|
+
await renderer.resolveTimestampsAsync( TimestampQuery.COMPUTE );
|
|
282
|
+
this._timingFrame = false;
|
|
283
|
+
|
|
284
|
+
// a resolve already in flight when the update dispatched won't hold its queries
|
|
285
|
+
if ( ! uids.every( uid => backend.hasTimestampQuery( uid ) ) ) {
|
|
286
|
+
|
|
287
|
+
return;
|
|
288
|
+
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
timing.frameTime = uids.reduce( ( sum, uid ) => sum + backend.getTimestamp( uid ), 0 );
|
|
292
|
+
timing.frameBudget = frameBudget;
|
|
293
|
+
timing.count ++;
|
|
294
|
+
|
|
156
295
|
}
|
|
157
296
|
|
|
158
297
|
*createRenderTask() {
|
|
@@ -199,6 +338,15 @@ export class PathTracerBackend {
|
|
|
199
338
|
sampleCountClearKernel.target = sampleCountTarget;
|
|
200
339
|
renderer.compute( sampleCountClearKernel.kernel, dispatchSize );
|
|
201
340
|
|
|
341
|
+
// pixels that never take another sample, such as ones the camera declines, would keep stale values
|
|
342
|
+
if ( this.auxTarget ) {
|
|
343
|
+
|
|
344
|
+
const { auxTargetClearKernel } = this;
|
|
345
|
+
auxTargetClearKernel.target = this.auxTarget;
|
|
346
|
+
renderer.compute( auxTargetClearKernel.kernel, auxTargetClearKernel.getDispatchSize( width, height, AUX_LAYERS ) );
|
|
347
|
+
|
|
348
|
+
}
|
|
349
|
+
|
|
202
350
|
this._renderTask = null;
|
|
203
351
|
|
|
204
352
|
}
|
|
@@ -210,6 +358,9 @@ export class PathTracerBackend {
|
|
|
210
358
|
this.sampleCountTarget.dispose();
|
|
211
359
|
this.sampleCountClearKernel.dispose();
|
|
212
360
|
this.outputTargetClearKernel.dispose();
|
|
361
|
+
this.auxTargetClearKernel.dispose();
|
|
362
|
+
this.auxTarget?.dispose();
|
|
363
|
+
this._auxPlaceholder.dispose();
|
|
213
364
|
|
|
214
365
|
this._renderTask = null;
|
|
215
366
|
|