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.
Files changed (46) hide show
  1. package/README.md +0 -1
  2. package/build/index.module.js +5 -3
  3. package/build/index.module.js.map +1 -1
  4. package/build/index.umd.cjs +4 -2
  5. package/build/index.umd.cjs.map +1 -1
  6. package/package.json +7 -7
  7. package/src/core/PathTracingSceneGenerator.js +1 -1
  8. package/src/uniforms/EquirectHdrInfoUniform.js +4 -2
  9. package/src/webgpu/API.md +167 -19
  10. package/src/webgpu/AtlasTexture.js +2 -3
  11. package/src/webgpu/EquirectHdrInfoNode.js +89 -48
  12. package/src/webgpu/LightsInfoNode.js +1 -1
  13. package/src/webgpu/MegaKernelPathTracer.js +10 -3
  14. package/src/webgpu/PathTracerBackend.js +172 -21
  15. package/src/webgpu/WaveFrontPathTracer.js +156 -71
  16. package/src/webgpu/WebGPUPathTracer.js +628 -48
  17. package/src/webgpu/compute/ComputeKernel.js +8 -0
  18. package/src/webgpu/compute/PathTracerMegaKernel.js +187 -36
  19. package/src/webgpu/compute/TallySampleCountsKernel.js +10 -10
  20. package/src/webgpu/compute/UnpackAuxKernel.js +48 -0
  21. package/src/webgpu/compute/ZeroOutKernel.js +3 -1
  22. package/src/webgpu/compute/targets.js +55 -0
  23. package/src/webgpu/compute/wavefront/LogicKernel.js +67 -20
  24. package/src/webgpu/compute/wavefront/MaterialKernel.js +153 -66
  25. package/src/webgpu/compute/wavefront/MediumKernel.js +154 -0
  26. package/src/webgpu/compute/wavefront/PopulatePixelIndicesKernel.js +5 -5
  27. package/src/webgpu/compute/wavefront/QueueLengthToDispatchKernel.js +5 -5
  28. package/src/webgpu/compute/wavefront/QueueMediumWalksKernel.js +52 -0
  29. package/src/webgpu/compute/wavefront/ResetSlotsKernel.js +6 -6
  30. package/src/webgpu/compute/wavefront/TraceRayKernel.js +11 -9
  31. package/src/webgpu/compute/wavefront/TraceShadowRayKernel.js +18 -25
  32. package/src/webgpu/compute/wavefront/structs.js +63 -37
  33. package/src/webgpu/constants.js +2 -2
  34. package/src/webgpu/denoise/OIDNDenoiser.js +81 -78
  35. package/src/webgpu/index.d.ts +41 -6
  36. package/src/webgpu/materials/GltfCompliantMaterial.js +20 -6
  37. package/src/webgpu/materials/debug/AuxDebugMaterial.js +54 -0
  38. package/src/webgpu/nodes/PathtracerBVHComputeData.js +35 -13
  39. package/src/webgpu/nodes/aux.wgsl.js +61 -0
  40. package/src/webgpu/nodes/eon.wgsl.js +1 -1
  41. package/src/webgpu/nodes/material.wgsl.js +2 -1
  42. package/src/webgpu/nodes/medium.wgsl.js +165 -0
  43. package/src/webgpu/nodes/random.wgsl.js +2 -0
  44. package/src/webgpu/nodes/sampling.wgsl.js +44 -3
  45. package/src/webgpu/nodes/structs.wgsl.js +7 -1
  46. package/src/webgpu/upscale/FSRUpscaler.js +1 -1
@@ -1,6 +1,11 @@
1
- import { ColorManagement, FloatType, RGBAFormat } from 'three';
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
- this.outputTarget = new StorageTexture( 1, 1, );
24
- this.outputTarget.format = RGBAFormat;
25
- this.outputTarget.type = FloatType;
26
- this.outputTarget.colorSpace = ColorManagement.workingColorSpace;
27
- this.outputTarget.name = 'Output #0';
28
- this.outputTarget.generateMipmaps = false;
29
-
30
- this.prevOutputTarget = new StorageTexture( 1, 1, );
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