@tinypoly/gpu-renderer 0.0.0-stage → 0.0.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.
Files changed (194) hide show
  1. package/ARCHITECTURE.md +334 -0
  2. package/LICENSE +21 -0
  3. package/README.md +258 -2
  4. package/dist/accumulator.d.ts +27 -0
  5. package/dist/accumulator.d.ts.map +1 -0
  6. package/dist/accumulator.js +57 -0
  7. package/dist/accumulator.js.map +1 -0
  8. package/dist/bvh.d.ts +26 -0
  9. package/dist/bvh.d.ts.map +1 -0
  10. package/dist/bvh.js +207 -0
  11. package/dist/bvh.js.map +1 -0
  12. package/dist/camera.d.ts +29 -0
  13. package/dist/camera.d.ts.map +1 -0
  14. package/dist/camera.js +48 -0
  15. package/dist/camera.js.map +1 -0
  16. package/dist/canvas.d.ts +9 -0
  17. package/dist/canvas.d.ts.map +1 -0
  18. package/dist/canvas.js +11 -0
  19. package/dist/canvas.js.map +1 -0
  20. package/dist/denoise.d.ts +69 -0
  21. package/dist/denoise.d.ts.map +1 -0
  22. package/dist/denoise.js +455 -0
  23. package/dist/denoise.js.map +1 -0
  24. package/dist/device.d.ts +37 -0
  25. package/dist/device.d.ts.map +1 -0
  26. package/dist/device.js +52 -0
  27. package/dist/device.js.map +1 -0
  28. package/dist/dispatchScheduler.d.ts +43 -0
  29. package/dist/dispatchScheduler.d.ts.map +1 -0
  30. package/dist/dispatchScheduler.js +49 -0
  31. package/dist/dispatchScheduler.js.map +1 -0
  32. package/dist/environment.d.ts +39 -0
  33. package/dist/environment.d.ts.map +1 -0
  34. package/dist/environment.js +168 -0
  35. package/dist/environment.js.map +1 -0
  36. package/dist/frameLayout.d.ts +4 -0
  37. package/dist/frameLayout.d.ts.map +1 -0
  38. package/dist/frameLayout.js +58 -0
  39. package/dist/frameLayout.js.map +1 -0
  40. package/dist/geometry.d.ts +75 -0
  41. package/dist/geometry.d.ts.map +1 -0
  42. package/dist/geometry.js +671 -0
  43. package/dist/geometry.js.map +1 -0
  44. package/dist/index.d.ts +25 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +17 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/lights.d.ts +64 -0
  49. package/dist/lights.d.ts.map +1 -0
  50. package/dist/lights.js +264 -0
  51. package/dist/lights.js.map +1 -0
  52. package/dist/materialLayout.d.ts +143 -0
  53. package/dist/materialLayout.d.ts.map +1 -0
  54. package/dist/materialLayout.js +297 -0
  55. package/dist/materialLayout.js.map +1 -0
  56. package/dist/math.d.ts +10 -0
  57. package/dist/math.d.ts.map +1 -0
  58. package/dist/math.js +40 -0
  59. package/dist/math.js.map +1 -0
  60. package/dist/pathTracePass.d.ts +27 -0
  61. package/dist/pathTracePass.d.ts.map +1 -0
  62. package/dist/pathTracePass.js +83 -0
  63. package/dist/pathTracePass.js.map +1 -0
  64. package/dist/planarCapture.d.ts +38 -0
  65. package/dist/planarCapture.d.ts.map +1 -0
  66. package/dist/planarCapture.js +163 -0
  67. package/dist/planarCapture.js.map +1 -0
  68. package/dist/presentPass.d.ts +31 -0
  69. package/dist/presentPass.d.ts.map +1 -0
  70. package/dist/presentPass.js +78 -0
  71. package/dist/presentPass.js.map +1 -0
  72. package/dist/renderer.d.ts +196 -0
  73. package/dist/renderer.d.ts.map +1 -0
  74. package/dist/renderer.js +839 -0
  75. package/dist/renderer.js.map +1 -0
  76. package/dist/sceneAssets.d.ts +24 -0
  77. package/dist/sceneAssets.d.ts.map +1 -0
  78. package/dist/sceneAssets.js +77 -0
  79. package/dist/sceneAssets.js.map +1 -0
  80. package/dist/sceneUpload.d.ts +61 -0
  81. package/dist/sceneUpload.d.ts.map +1 -0
  82. package/dist/sceneUpload.js +98 -0
  83. package/dist/sceneUpload.js.map +1 -0
  84. package/dist/shaderModule.d.ts +7 -0
  85. package/dist/shaderModule.d.ts.map +1 -0
  86. package/dist/shaderModule.js +19 -0
  87. package/dist/shaderModule.js.map +1 -0
  88. package/dist/shaderProgram.d.ts +124 -0
  89. package/dist/shaderProgram.d.ts.map +1 -0
  90. package/dist/shaderProgram.js +1030 -0
  91. package/dist/shaderProgram.js.map +1 -0
  92. package/dist/structLayout.d.ts +28 -0
  93. package/dist/structLayout.d.ts.map +1 -0
  94. package/dist/structLayout.js +58 -0
  95. package/dist/structLayout.js.map +1 -0
  96. package/dist/texturePack.d.ts +54 -0
  97. package/dist/texturePack.d.ts.map +1 -0
  98. package/dist/texturePack.js +86 -0
  99. package/dist/texturePack.js.map +1 -0
  100. package/dist/upstream.d.ts +7 -0
  101. package/dist/upstream.d.ts.map +1 -0
  102. package/dist/upstream.js +11 -0
  103. package/dist/upstream.js.map +1 -0
  104. package/dist/volumetric.d.ts +10 -0
  105. package/dist/volumetric.d.ts.map +1 -0
  106. package/dist/volumetric.js +34 -0
  107. package/dist/volumetric.js.map +1 -0
  108. package/dist/wgsl/bsdf.wgsl.d.ts +13 -0
  109. package/dist/wgsl/bsdf.wgsl.d.ts.map +1 -0
  110. package/dist/wgsl/bsdf.wgsl.js +619 -0
  111. package/dist/wgsl/bsdf.wgsl.js.map +1 -0
  112. package/dist/wgsl/bvh.wgsl.d.ts +3 -0
  113. package/dist/wgsl/bvh.wgsl.d.ts.map +1 -0
  114. package/dist/wgsl/bvh.wgsl.js +218 -0
  115. package/dist/wgsl/bvh.wgsl.js.map +1 -0
  116. package/dist/wgsl/common.wgsl.d.ts +3 -0
  117. package/dist/wgsl/common.wgsl.d.ts.map +1 -0
  118. package/dist/wgsl/common.wgsl.js +193 -0
  119. package/dist/wgsl/common.wgsl.js.map +1 -0
  120. package/dist/wgsl/environment.wgsl.d.ts +10 -0
  121. package/dist/wgsl/environment.wgsl.d.ts.map +1 -0
  122. package/dist/wgsl/environment.wgsl.js +145 -0
  123. package/dist/wgsl/environment.wgsl.js.map +1 -0
  124. package/dist/wgsl/lights.wgsl.d.ts +10 -0
  125. package/dist/wgsl/lights.wgsl.d.ts.map +1 -0
  126. package/dist/wgsl/lights.wgsl.js +434 -0
  127. package/dist/wgsl/lights.wgsl.js.map +1 -0
  128. package/dist/wgsl/pathTrace.wgsl.d.ts +14 -0
  129. package/dist/wgsl/pathTrace.wgsl.d.ts.map +1 -0
  130. package/dist/wgsl/pathTrace.wgsl.js +1020 -0
  131. package/dist/wgsl/pathTrace.wgsl.js.map +1 -0
  132. package/dist/wgsl/present.wgsl.d.ts +7 -0
  133. package/dist/wgsl/present.wgsl.d.ts.map +1 -0
  134. package/dist/wgsl/present.wgsl.js +154 -0
  135. package/dist/wgsl/present.wgsl.js.map +1 -0
  136. package/dist/wgsl/random.wgsl.d.ts +9 -0
  137. package/dist/wgsl/random.wgsl.d.ts.map +1 -0
  138. package/dist/wgsl/random.wgsl.js +91 -0
  139. package/dist/wgsl/random.wgsl.js.map +1 -0
  140. package/dist/wgsl/sampling.wgsl.d.ts +3 -0
  141. package/dist/wgsl/sampling.wgsl.d.ts.map +1 -0
  142. package/dist/wgsl/sampling.wgsl.js +31 -0
  143. package/dist/wgsl/sampling.wgsl.js.map +1 -0
  144. package/dist/wgsl/shaderSupport.wgsl.d.ts +6 -0
  145. package/dist/wgsl/shaderSupport.wgsl.d.ts.map +1 -0
  146. package/dist/wgsl/shaderSupport.wgsl.js +120 -0
  147. package/dist/wgsl/shaderSupport.wgsl.js.map +1 -0
  148. package/dist/wgsl/surface.wgsl.d.ts +10 -0
  149. package/dist/wgsl/surface.wgsl.d.ts.map +1 -0
  150. package/dist/wgsl/surface.wgsl.js +794 -0
  151. package/dist/wgsl/surface.wgsl.js.map +1 -0
  152. package/dist/wgsl/texture.wgsl.d.ts +11 -0
  153. package/dist/wgsl/texture.wgsl.d.ts.map +1 -0
  154. package/dist/wgsl/texture.wgsl.js +132 -0
  155. package/dist/wgsl/texture.wgsl.js.map +1 -0
  156. package/package.json +76 -3
  157. package/src/accumulator.ts +64 -0
  158. package/src/bvh.ts +272 -0
  159. package/src/camera.ts +77 -0
  160. package/src/canvas.ts +13 -0
  161. package/src/denoise.ts +514 -0
  162. package/src/device.ts +81 -0
  163. package/src/dispatchScheduler.ts +71 -0
  164. package/src/environment.ts +233 -0
  165. package/src/frameLayout.ts +59 -0
  166. package/src/geometry.ts +899 -0
  167. package/src/index.ts +61 -0
  168. package/src/lights.ts +365 -0
  169. package/src/materialLayout.ts +408 -0
  170. package/src/math.ts +48 -0
  171. package/src/pathTracePass.ts +97 -0
  172. package/src/planarCapture.ts +225 -0
  173. package/src/presentPass.ts +99 -0
  174. package/src/renderer.ts +1117 -0
  175. package/src/sceneAssets.ts +118 -0
  176. package/src/sceneUpload.ts +185 -0
  177. package/src/shaderModule.ts +19 -0
  178. package/src/shaderProgram.ts +1370 -0
  179. package/src/structLayout.ts +93 -0
  180. package/src/texturePack.ts +152 -0
  181. package/src/upstream.ts +56 -0
  182. package/src/volumetric.ts +42 -0
  183. package/src/wgsl/bsdf.wgsl.ts +618 -0
  184. package/src/wgsl/bvh.wgsl.ts +217 -0
  185. package/src/wgsl/common.wgsl.ts +193 -0
  186. package/src/wgsl/environment.wgsl.ts +144 -0
  187. package/src/wgsl/lights.wgsl.ts +433 -0
  188. package/src/wgsl/pathTrace.wgsl.ts +1019 -0
  189. package/src/wgsl/present.wgsl.ts +153 -0
  190. package/src/wgsl/random.wgsl.ts +90 -0
  191. package/src/wgsl/sampling.wgsl.ts +30 -0
  192. package/src/wgsl/shaderSupport.wgsl.ts +119 -0
  193. package/src/wgsl/surface.wgsl.ts +793 -0
  194. package/src/wgsl/texture.wgsl.ts +131 -0
@@ -0,0 +1,225 @@
1
+ import { Matrix3, Matrix4, Plane, Vector3 } from "three";
2
+ import { cameraProjection } from "./camera.js";
3
+ import { createShaderModule } from "./shaderModule.js";
4
+ import type { CameraState, RenderBackgroundMode, SerializedMaterial, SerializedScene, SerializedTexture } from "./upstream.js";
5
+
6
+ /**
7
+ * The CPU renderer's planar captures (`userData.cpuRenderer.planarCapture`): a flat surface whose shader samples what is
8
+ * reflected in it and seen through it, as `tReflectionMap`, `tEnvironmentReflectionMap`, `tRefractionMap` and
9
+ * `tRefractionDepth`, through `reflectionMatrix` and `refractionMatrix`. The cameras, the clip planes and the matrices are
10
+ * `pipeline.js`'s own; the images are rendered by this renderer, path traced from those cameras.
11
+ */
12
+ export interface PlanarCapture {
13
+ /** The texture the image goes to, and the depth texture for the refraction capture. */
14
+ texture: string;
15
+ depth?: string;
16
+ camera: CameraState;
17
+ /** Whether the capture sees the scene, without its planar surfaces, or only the environment. */
18
+ scene: "scene" | "environment";
19
+ background: RenderBackgroundMode;
20
+ /** Drops what lies behind the plane, as the capture's clip plane does: `dot(xyz, p) + w < 0` is cut. */
21
+ clip?: number[];
22
+ }
23
+
24
+ export interface PlanarCaptures {
25
+ /** The scene with the capture textures in its table and each material's capture uniforms bound. */
26
+ scene: SerializedScene;
27
+ /** The scene the captures see: everything but the planar surfaces, which never appear in one. */
28
+ captureScene: SerializedScene;
29
+ captures: PlanarCapture[];
30
+ width: number;
31
+ height: number;
32
+ }
33
+
34
+ /** A capture is rendered at most this wide; it is a texture a shader blurs and distorts, not the image itself. */
35
+ const MAX_CAPTURE = 512;
36
+
37
+ function placeholder(id: string, width: number, height: number): SerializedTexture {
38
+ return {
39
+ id,
40
+ width,
41
+ height,
42
+ data: new Float32Array(width * height * 4),
43
+ colorSpace: "srgb-linear",
44
+ wrapS: 1001,
45
+ wrapT: 1001,
46
+ matrix: [1, 0, 0, 0, 1, 0, 0, 0, 1],
47
+ // The rows are written top first, as the CPU renderer writes them.
48
+ flipY: true,
49
+ channel: 0,
50
+ nearest: false,
51
+ };
52
+ }
53
+
54
+ /** The captures a scene needs from this camera, or null when it has no planar surface. */
55
+ export function planarCaptures(
56
+ source: SerializedScene, camera: CameraState, width: number, height: number): PlanarCaptures | null {
57
+ const planar = source.meshes.filter(mesh =>
58
+ !mesh.primitive && mesh.materials.some(material => material?.planarCapture && material.shader));
59
+
60
+ if (!planar.length) return null;
61
+
62
+ const scale = Math.min(1, MAX_CAPTURE / Math.max(width, height));
63
+ const captureWidth = Math.max(1, Math.round(width * scale));
64
+ const captureHeight = Math.max(1, Math.round(height * scale));
65
+ const textures = [...source.textures];
66
+ const captures: PlanarCapture[] = [];
67
+ const copies = new Map<SerializedMaterial, SerializedMaterial>();
68
+ const cameraWorld = new Matrix4().fromArray(camera.matrixWorld);
69
+ const projection = cameraProjection(camera, width / height);
70
+ const bias = new Matrix4().set(0.5, 0, 0, 0.5, 0, 0.5, 0, 0.5, 0, 0, 0.5, 0.5, 0, 0, 0, 1);
71
+
72
+ for (const mesh of planar) {
73
+ const model = new Matrix4().fromArray(mesh.matrixWorld);
74
+ const normal = new Vector3(0, 1, 0).applyMatrix3(new Matrix3().getNormalMatrix(model)).normalize();
75
+ const point = new Vector3().setFromMatrixPosition(model);
76
+ const plane = new Plane().setFromNormalAndCoplanarPoint(normal, point);
77
+ const { x, y, z } = normal, d = plane.constant;
78
+
79
+ const mirror = new Matrix4().set(
80
+ 1 - 2 * x * x, -2 * x * y, -2 * x * z, -2 * d * x,
81
+ -2 * y * x, 1 - 2 * y * y, -2 * y * z, -2 * d * y,
82
+ -2 * z * x, -2 * z * y, 1 - 2 * z * z, -2 * d * z,
83
+ 0, 0, 0, 1);
84
+
85
+ const reflected = { ...camera, matrixWorld: mirror.clone().multiply(cameraWorld).toArray() };
86
+ const side = plane.distanceToPoint(new Vector3().setFromMatrixPosition(cameraWorld)) >= 0 ? 1 : -1;
87
+
88
+ const textureMatrix = (world: number[]) => bias.clone().multiply(projection)
89
+ .multiply(new Matrix4().fromArray(world).invert()).multiply(model).toArray();
90
+
91
+ for (const material of mesh.materials) {
92
+ if (!material?.planarCapture || !material.shader) continue;
93
+
94
+ const id = material.planarCapture.id;
95
+
96
+ const copy: SerializedMaterial = {
97
+ ...material,
98
+ shader: { ...material.shader, uniforms: { ...material.shader.uniforms } },
99
+ };
100
+
101
+ const uniforms = (copy.shader as NonNullable<SerializedMaterial["shader"]>).uniforms;
102
+
103
+ const names = {
104
+ reflection: `${id}:tReflectionMap`,
105
+ environment: `${id}:tEnvironmentReflectionMap`,
106
+ refraction: `${id}:tRefractionMap`,
107
+ depth: `${id}:tRefractionDepth`,
108
+ };
109
+
110
+ Object.assign(uniforms, {
111
+ reflectionMatrix: textureMatrix(reflected.matrixWorld),
112
+ refractionMatrix: textureMatrix(camera.matrixWorld),
113
+ cameraNear: camera.near,
114
+ cameraFar: camera.far,
115
+ tReflectionMap: { texture: names.reflection },
116
+ tEnvironmentReflectionMap: { texture: names.environment },
117
+ tRefractionMap: { texture: names.refraction },
118
+ tRefractionDepth: { texture: names.depth },
119
+ });
120
+
121
+ copies.set(material, copy);
122
+
123
+ for (const name of Object.values(names)) textures.push(placeholder(name, captureWidth, captureHeight));
124
+
125
+ captures.push(
126
+ { texture: names.reflection, camera: reflected, scene: "scene", background: "transparent",
127
+ clip: [x * side, y * side, z * side, d * side + 0.01] },
128
+ { texture: names.environment, camera: reflected, scene: "environment", background: "environment" },
129
+ { texture: names.refraction, depth: names.depth, camera, scene: "scene", background: "environment",
130
+ clip: [-x * side, -y * side, -z * side, -d * side + 0.01] },
131
+ );
132
+ }
133
+ }
134
+
135
+ const scene: SerializedScene = {
136
+ ...source,
137
+ textures,
138
+ meshes: source.meshes.map(mesh => ({ ...mesh, materials: mesh.materials.map(m => (m && copies.get(m)) ?? m) })),
139
+ };
140
+
141
+ return {
142
+ scene,
143
+ captureScene: { ...source, meshes: source.meshes.filter(mesh => !planar.includes(mesh)) },
144
+ captures,
145
+ width: captureWidth,
146
+ height: captureHeight,
147
+ };
148
+ }
149
+
150
+ /** Writes a finished capture from its accumulation buffer into the texels of its texture, and the depth into another. */
151
+ const RESOLVE_WGSL = /* wgsl */ `
152
+ struct Resolve {
153
+ size: vec2<u32>,
154
+ color: u32,
155
+ depth: u32,
156
+ };
157
+
158
+ @group(0) @binding(0) var<uniform> resolve: Resolve;
159
+ @group(0) @binding(1) var<storage, read> accumulation: array<vec4<f32>>;
160
+ @group(0) @binding(2) var<storage, read_write> textures: array<u32>;
161
+
162
+ @compute @workgroup_size(64, 1, 1)
163
+ fn main(@builtin(global_invocation_id) id: vec3<u32>) {
164
+ let count = resolve.size.x * resolve.size.y;
165
+ let i = id.x;
166
+
167
+ if (i >= count) {
168
+ return;
169
+ }
170
+
171
+ let sum = accumulation[i];
172
+ let extra = accumulation[count + i];
173
+ let n = max(sum.w, 1.0);
174
+ // Linear light, premultiplied by the coverage, as the CPU renderer's capture holds it.
175
+ let color = vec4<f32>(sum.rgb / n, extra.x / n);
176
+
177
+ for (var c = 0u; c < 4u; c = c + 1u) {
178
+ textures[resolve.color + i * 4u + c] = bitcast<u32>(color[c]);
179
+ }
180
+
181
+ if (resolve.depth != 0xffffffffu) {
182
+ let depth = extra.y / n;
183
+
184
+ for (var c = 0u; c < 3u; c = c + 1u) {
185
+ textures[resolve.depth + i * 4u + c] = bitcast<u32>(depth);
186
+ }
187
+
188
+ textures[resolve.depth + i * 4u + 3u] = bitcast<u32>(1.0);
189
+ }
190
+ }
191
+ `;
192
+
193
+ export class CaptureResolvePass {
194
+ private readonly pipeline: GPUComputePipeline;
195
+ private readonly uniforms: GPUBuffer;
196
+
197
+ constructor(private readonly device: GPUDevice) {
198
+ const module = createShaderModule(device, "capture resolve", RESOLVE_WGSL);
199
+
200
+ this.pipeline = device.createComputePipeline({ layout: "auto", compute: { module, entryPoint: "main" } });
201
+ this.uniforms = device.createBuffer({ size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
202
+ }
203
+
204
+ /** `color` and `depth` are word offsets of the textures' texels; a depth of 0xffffffff writes none. */
205
+ encode(encoder: GPUCommandEncoder, accumulation: GPUBuffer, textures: GPUBuffer,
206
+ width: number, height: number, color: number, depth: number) {
207
+ this.device.queue.writeBuffer(this.uniforms, 0, new Uint32Array([width, height, color, depth]));
208
+
209
+ const group = this.device.createBindGroup({
210
+ layout: this.pipeline.getBindGroupLayout(0),
211
+ entries: [
212
+ { binding: 0, resource: { buffer: this.uniforms } },
213
+ { binding: 1, resource: { buffer: accumulation } },
214
+ { binding: 2, resource: { buffer: textures } },
215
+ ],
216
+ });
217
+
218
+ const pass = encoder.beginComputePass({ label: "capture resolve" });
219
+
220
+ pass.setPipeline(this.pipeline);
221
+ pass.setBindGroup(0, group);
222
+ pass.dispatchWorkgroups(Math.ceil(width * height / 64));
223
+ pass.end();
224
+ }
225
+ }
@@ -0,0 +1,99 @@
1
+ import { createShaderModule } from "./shaderModule.js";
2
+ import type { RenderToneMapping } from "./upstream.js";
3
+ import { PRESENT_WGSL } from "./wgsl/present.wgsl.js";
4
+
5
+ export interface PresentUniforms {
6
+ width: number;
7
+ height: number;
8
+ exposure: number;
9
+ toneMapping: RenderToneMapping;
10
+ vignette: number;
11
+ grain: number;
12
+ frameIndex: number;
13
+ transparent: boolean;
14
+ /** The denoiser's output, which the colour is read from instead of the accumulation when given. */
15
+ denoised?: GPUBuffer | null;
16
+ }
17
+
18
+ /** The order the shader's `toneMapping` switch expects. */
19
+ const TONE_MAPPING = ["aces", "agx", "neutral", "linear"] as const;
20
+
21
+ const PRESENT_BYTES = 48;
22
+
23
+ /** Resolves the accumulation buffer onto the canvas: divide by the sample count, expose, tone map, encode. */
24
+ export class PresentPass {
25
+ private readonly pipeline: GPURenderPipeline;
26
+ private readonly uniforms: GPUBuffer;
27
+ private readonly values = new ArrayBuffer(PRESENT_BYTES);
28
+ private readonly floats = new Float32Array(this.values);
29
+ private readonly integers = new Uint32Array(this.values);
30
+ private bindGroup: GPUBindGroup | null = null;
31
+ private accumulation: GPUBuffer | null = null;
32
+ private source: GPUBuffer | null = null;
33
+
34
+ constructor(private readonly device: GPUDevice, readonly format: GPUTextureFormat) {
35
+ const module = createShaderModule(device, "present", PRESENT_WGSL);
36
+
37
+ this.pipeline = device.createRenderPipeline({
38
+ layout: "auto",
39
+ vertex: { module, entryPoint: "vertexMain" },
40
+ fragment: { module, entryPoint: "fragmentMain", targets: [{ format }] },
41
+ primitive: { topology: "triangle-list" },
42
+ });
43
+ this.uniforms = device.createBuffer({
44
+ size: PRESENT_BYTES,
45
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
46
+ });
47
+ }
48
+
49
+ setAccumulation(accumulation: GPUBuffer) {
50
+ this.accumulation = accumulation;
51
+ this.bind(null);
52
+ }
53
+
54
+ private bind(denoised: GPUBuffer | null) {
55
+ if (!this.accumulation) return;
56
+
57
+ // Without a denoised image the accumulation stands in for it; the uniform says which one to read.
58
+ const source = denoised ?? this.accumulation;
59
+
60
+ if (this.bindGroup && source === this.source) return;
61
+
62
+ this.source = source;
63
+ this.bindGroup = this.device.createBindGroup({
64
+ layout: this.pipeline.getBindGroupLayout(0),
65
+ entries: [
66
+ { binding: 0, resource: { buffer: this.uniforms } },
67
+ { binding: 1, resource: { buffer: this.accumulation } },
68
+ { binding: 2, resource: { buffer: source } },
69
+ ],
70
+ });
71
+ }
72
+
73
+ encode(encoder: GPUCommandEncoder, view: GPUTextureView, present: PresentUniforms) {
74
+ this.bind(present.denoised ?? null);
75
+
76
+ if (!this.bindGroup) throw new Error("GPU renderer: the presentation pass has no accumulation buffer bound.");
77
+
78
+ this.integers[0] = present.width;
79
+ this.integers[1] = present.height;
80
+ this.floats[2] = present.exposure;
81
+ this.integers[3] = Math.max(0, TONE_MAPPING.indexOf(present.toneMapping));
82
+ this.floats[4] = present.vignette;
83
+ this.floats[5] = present.grain;
84
+ this.integers[6] = present.frameIndex;
85
+ this.integers[7] = present.transparent ? 1 : 0;
86
+ this.integers[8] = present.denoised ? 1 : 0;
87
+ this.device.queue.writeBuffer(this.uniforms, 0, this.values);
88
+
89
+ const pass = encoder.beginRenderPass({
90
+ label: "present",
91
+ colorAttachments: [{ view, loadOp: "clear", storeOp: "store", clearValue: { r: 0, g: 0, b: 0, a: 1 } }],
92
+ });
93
+
94
+ pass.setPipeline(this.pipeline);
95
+ pass.setBindGroup(0, this.bindGroup);
96
+ pass.draw(3);
97
+ pass.end();
98
+ }
99
+ }