@tinypoly/gpu-renderer 0.2.4 → 0.3.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 (111) hide show
  1. package/ARCHITECTURE.md +48 -10
  2. package/README.md +2 -0
  3. package/dist/accumulator.d.ts.map +1 -1
  4. package/dist/accumulator.js +1 -0
  5. package/dist/accumulator.js.map +1 -1
  6. package/dist/denoise.js +2 -2
  7. package/dist/denoise.js.map +1 -1
  8. package/dist/device.d.ts.map +1 -1
  9. package/dist/device.js +9 -0
  10. package/dist/device.js.map +1 -1
  11. package/dist/frameLayout.d.ts.map +1 -1
  12. package/dist/frameLayout.js +5 -0
  13. package/dist/frameLayout.js.map +1 -1
  14. package/dist/geometry.d.ts +48 -8
  15. package/dist/geometry.d.ts.map +1 -1
  16. package/dist/geometry.js +409 -134
  17. package/dist/geometry.js.map +1 -1
  18. package/dist/grass.d.ts.map +1 -1
  19. package/dist/grass.js +2 -1
  20. package/dist/grass.js.map +1 -1
  21. package/dist/imageResolve.js +2 -2
  22. package/dist/imageResolve.js.map +1 -1
  23. package/dist/index.d.ts +1 -0
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +1 -0
  26. package/dist/index.js.map +1 -1
  27. package/dist/instancing.d.ts +100 -0
  28. package/dist/instancing.d.ts.map +1 -0
  29. package/dist/instancing.js +286 -0
  30. package/dist/instancing.js.map +1 -0
  31. package/dist/lensPass.d.ts +2 -0
  32. package/dist/lensPass.d.ts.map +1 -1
  33. package/dist/lensPass.js +7 -4
  34. package/dist/lensPass.js.map +1 -1
  35. package/dist/lights.d.ts.map +1 -1
  36. package/dist/lights.js +8 -5
  37. package/dist/lights.js.map +1 -1
  38. package/dist/memoryProbe.d.ts +76 -0
  39. package/dist/memoryProbe.d.ts.map +1 -0
  40. package/dist/memoryProbe.js +197 -0
  41. package/dist/memoryProbe.js.map +1 -0
  42. package/dist/oidnPass.d.ts +6 -0
  43. package/dist/oidnPass.d.ts.map +1 -1
  44. package/dist/oidnPass.js +18 -1
  45. package/dist/oidnPass.js.map +1 -1
  46. package/dist/pathTracePass.d.ts +15 -2
  47. package/dist/pathTracePass.d.ts.map +1 -1
  48. package/dist/pathTracePass.js +38 -16
  49. package/dist/pathTracePass.js.map +1 -1
  50. package/dist/renderer.d.ts +13 -0
  51. package/dist/renderer.d.ts.map +1 -1
  52. package/dist/renderer.js +248 -41
  53. package/dist/renderer.js.map +1 -1
  54. package/dist/sceneUpload.d.ts +26 -8
  55. package/dist/sceneUpload.d.ts.map +1 -1
  56. package/dist/sceneUpload.js +97 -27
  57. package/dist/sceneUpload.js.map +1 -1
  58. package/dist/shaderModule.d.ts.map +1 -1
  59. package/dist/shaderModule.js +2 -1
  60. package/dist/shaderModule.js.map +1 -1
  61. package/dist/skyBake.d.ts.map +1 -1
  62. package/dist/skyBake.js +5 -3
  63. package/dist/skyBake.js.map +1 -1
  64. package/dist/texturePack.d.ts +43 -3
  65. package/dist/texturePack.d.ts.map +1 -1
  66. package/dist/texturePack.js +209 -37
  67. package/dist/texturePack.js.map +1 -1
  68. package/dist/wgsl/bvh.wgsl.d.ts +5 -1
  69. package/dist/wgsl/bvh.wgsl.d.ts.map +1 -1
  70. package/dist/wgsl/bvh.wgsl.js +105 -15
  71. package/dist/wgsl/bvh.wgsl.js.map +1 -1
  72. package/dist/wgsl/common.wgsl.d.ts.map +1 -1
  73. package/dist/wgsl/common.wgsl.js +92 -11
  74. package/dist/wgsl/common.wgsl.js.map +1 -1
  75. package/dist/wgsl/lens.wgsl.d.ts +1 -1
  76. package/dist/wgsl/lens.wgsl.d.ts.map +1 -1
  77. package/dist/wgsl/lens.wgsl.js +14 -2
  78. package/dist/wgsl/lens.wgsl.js.map +1 -1
  79. package/dist/wgsl/surface.wgsl.d.ts +1 -1
  80. package/dist/wgsl/surface.wgsl.d.ts.map +1 -1
  81. package/dist/wgsl/surface.wgsl.js +24 -14
  82. package/dist/wgsl/surface.wgsl.js.map +1 -1
  83. package/dist/wgsl/texture.wgsl.d.ts +1 -1
  84. package/dist/wgsl/texture.wgsl.d.ts.map +1 -1
  85. package/dist/wgsl/texture.wgsl.js +11 -0
  86. package/dist/wgsl/texture.wgsl.js.map +1 -1
  87. package/package.json +1 -1
  88. package/src/accumulator.ts +1 -0
  89. package/src/denoise.ts +2 -2
  90. package/src/device.ts +12 -0
  91. package/src/frameLayout.ts +5 -0
  92. package/src/geometry.ts +527 -133
  93. package/src/grass.ts +2 -1
  94. package/src/imageResolve.ts +2 -2
  95. package/src/index.ts +1 -0
  96. package/src/instancing.ts +415 -0
  97. package/src/lensPass.ts +9 -4
  98. package/src/lights.ts +9 -5
  99. package/src/memoryProbe.ts +275 -0
  100. package/src/oidnPass.ts +21 -1
  101. package/src/pathTracePass.ts +42 -18
  102. package/src/renderer.ts +308 -47
  103. package/src/sceneUpload.ts +139 -36
  104. package/src/shaderModule.ts +2 -1
  105. package/src/skyBake.ts +5 -3
  106. package/src/texturePack.ts +274 -43
  107. package/src/wgsl/bvh.wgsl.ts +105 -15
  108. package/src/wgsl/common.wgsl.ts +92 -11
  109. package/src/wgsl/lens.wgsl.ts +14 -2
  110. package/src/wgsl/surface.wgsl.ts +24 -14
  111. package/src/wgsl/texture.wgsl.ts +11 -0
@@ -1,17 +1,19 @@
1
- import type { PackedBvh } from "./bvh.js";
2
1
  import type { PackedGeometry } from "./geometry.js";
3
2
  import { packLights, type PackedLights } from "./lights.js";
4
3
  import { MATERIAL_WORDS, materialProperties, writeMaterial } from "./materialLayout.js";
5
- import { NO_TEXTURE, packTextures, type PackedTextures } from "./texturePack.js";
4
+ import { NO_TEXTURE, packTextures, type PackedTextures, type TextureUpload } from "./texturePack.js";
6
5
  import type { PackedEnvironment } from "./environment.js";
7
6
 
8
7
  /** Bytes each buffer of a scene takes on the device. Computed without a device, so a host can check it up front. */
9
8
  export interface SceneByteLength {
10
9
  positions: number;
11
10
  materials: number;
12
- /** Vertex normals, the UV sets, vertex colours and shader varyings, in one buffer. */
11
+ /** The UV sets, vertex colours and shader varyings, in one buffer. */
13
12
  attributes: number;
14
- /** One material index per triangle, then the hierarchy's triangle order and its inverse, in one buffer. */
13
+ /**
14
+ * One material index per triangle, the hierarchy's triangle order and its inverse, the instance of every instanced
15
+ * triangle, the three vertices of every stored one and the vertices' normals, in one buffer.
16
+ */
15
17
  indices: number;
16
18
  nodes: number;
17
19
  lights: number;
@@ -39,7 +41,14 @@ export interface SceneBuffers {
39
41
  orderOffset: number;
40
42
  /** Where each triangle's position in the leaf order starts inside `indices`. */
41
43
  leafOffset: number;
44
+ /** Where the instance of every instanced triangle, the corners and the normals start inside `indices`. */
45
+ instanceOffset: number;
46
+ cornerOffset: number;
47
+ normalOffset: number;
48
+ /** Every triangle a hit can name: the scene's own, then each instance's. */
42
49
  triangleCount: number;
50
+ /** The scene's own triangles, which lead the numbering. */
51
+ sceneTriangles: number;
43
52
  /** Everything the integrator can aim a shadow ray at. */
44
53
  lightCount: number;
45
54
  /** How many of those are emissive triangles, which lead the buffer. */
@@ -79,23 +88,39 @@ const MINIMUM = Math.max(64, MATERIAL_WORDS * 4);
79
88
 
80
89
  const sized = (bytes: number) => Math.max(MINIMUM, bytes);
81
90
 
91
+ /** What a scene's hierarchy hands the upload: its nodes, and the stored triangle at each position of its leaf order. */
92
+ export interface UploadHierarchy {
93
+ nodes: ArrayBuffer;
94
+ order: Uint32Array;
95
+ }
96
+
82
97
  const attributeBytes = (geometry: PackedGeometry) =>
83
- geometry.normals.byteLength + geometry.uvs.byteLength + (geometry.colors?.byteLength ?? 0)
84
- + (geometry.varyings?.byteLength ?? 0);
98
+ geometry.uvs.byteLength + (geometry.colors?.byteLength ?? 0) + (geometry.varyings?.byteLength ?? 0);
99
+
100
+ /** Triangles a hit can name, and the ones stored: the scene's own and every prototype's. */
101
+ function triangleCounts(geometry: PackedGeometry) {
102
+ const instanced = geometry.instancing?.materials.length ?? 0;
103
+ const stored = geometry.triangleCount + (geometry.instancing?.triangleCount ?? 0);
104
+
105
+ return { instanced, stored, named: geometry.triangleCount + instanced };
106
+ }
85
107
 
86
108
  export function sceneByteLength(
87
109
  geometry: PackedGeometry,
88
- bvh: PackedBvh,
110
+ bvh: UploadHierarchy,
89
111
  lights: PackedLights = packLights(geometry),
90
- textures: Pick<PackedTextures, "data"> = { data: new Uint32Array(4) }): SceneByteLength {
112
+ textures: Pick<PackedTextures, "data"> | Pick<TextureUpload, "byteLength"> = { data: new Uint32Array(4) }): SceneByteLength {
113
+ const { instanced, stored, named } = triangleCounts(geometry);
114
+
91
115
  const lengths = {
92
- positions: sized(geometry.positions.byteLength),
116
+ positions: sized(stored * 36),
93
117
  attributes: sized(attributeBytes(geometry)),
94
- indices: sized(geometry.materials.byteLength + 2 * bvh.order.byteLength),
118
+ // Materials of every named triangle, the order both ways, each instanced one's instance, corners and normals.
119
+ indices: sized((named + 2 * stored + instanced + 3 * stored + geometry.vertexCount) * 4),
95
120
  materials: sized(Math.max(1, geometry.materialCount) * MATERIAL_WORDS * 4),
96
121
  nodes: sized(bvh.nodes.byteLength),
97
122
  lights: sized(lights.entries.byteLength),
98
- textures: sized(textures.data.byteLength),
123
+ textures: sized("data" in textures ? textures.data.byteLength : textures.byteLength),
99
124
  };
100
125
 
101
126
  const values = Object.values(lengths);
@@ -107,6 +132,54 @@ export function sceneByteLength(
107
132
  };
108
133
  }
109
134
 
135
+ /** A unit normal in two signed 16 bit halves of one word, folded onto an octahedron; zero is kept apart as NO_NORMAL. */
136
+ export const NO_NORMAL = 0x80008000;
137
+
138
+ export function octahedralNormal(x: number, y: number, z: number): number {
139
+ const length = Math.abs(x) + Math.abs(y) + Math.abs(z);
140
+
141
+ if (!(length > 0) || !Number.isFinite(length)) return NO_NORMAL;
142
+
143
+ let u = x / length, v = y / length;
144
+
145
+ if (z < 0) {
146
+ const fu = (1 - Math.abs(v)) * (u >= 0 ? 1 : -1);
147
+
148
+ v = (1 - Math.abs(u)) * (v >= 0 ? 1 : -1);
149
+ u = fu;
150
+ }
151
+
152
+ const qu = Math.round(Math.min(1, Math.max(-1, u)) * 32767);
153
+ const qv = Math.round(Math.min(1, Math.max(-1, v)) * 32767);
154
+
155
+ return ((qu & 0xffff) | ((qv & 0xffff) << 16)) >>> 0;
156
+ }
157
+
158
+ /** Every vertex normal, folded into one word each. */
159
+ function encodedNormals(geometry: PackedGeometry): Uint32Array {
160
+ const words = new Uint32Array(geometry.vertexCount);
161
+ const n = geometry.normals;
162
+
163
+ for (let vertex = 0; vertex < geometry.vertexCount; vertex++)
164
+ words[vertex] = octahedralNormal(n[vertex * 3], n[vertex * 3 + 1], n[vertex * 3 + 2]);
165
+
166
+ return words;
167
+ }
168
+
169
+ /** The instance every instanced triangle belongs to, in the order they are numbered. */
170
+ function instanceOfTriangles(geometry: PackedGeometry): Uint32Array {
171
+ const instancing = geometry.instancing;
172
+ const words = new Uint32Array(instancing?.materials.length ?? 0);
173
+
174
+ instancing?.instances.forEach((instance, k) => {
175
+ const from = instance.firstTriangle - geometry.triangleCount;
176
+
177
+ words.fill(k, from, from + instancing.prototypes[instance.prototype].count);
178
+ });
179
+
180
+ return words;
181
+ }
182
+
110
183
  /** Materials as the shader reads them: one `Material` struct each, laid out by `materialLayout.ts`. */
111
184
  export function packMaterials(geometry: PackedGeometry): ArrayBuffer {
112
185
  const count = Math.max(1, geometry.materialCount);
@@ -127,17 +200,23 @@ export function environmentPower(environment: PackedEnvironment | null): number
127
200
 
128
201
  /**
129
202
  * The positions in the hierarchy's leaf order, which is the order its traversal reads them in, so a leaf is one run of
130
- * the buffer; and where each triangle went, for everything that reads a triangle by its own number.
203
+ * the buffer; and where each stored triangle went, for everything that reads a triangle by its own number. A stored
204
+ * triangle past the scene's own is a prototype's, in object space.
131
205
  */
132
206
  export function leafOrder(
133
- positions: Float32Array, order: Uint32Array): { positions: Float32Array; inverse: Uint32Array } {
207
+ geometry: Pick<PackedGeometry, "positions" | "triangleCount" | "instancing">,
208
+ order: Uint32Array): { positions: Float32Array; inverse: Uint32Array } {
134
209
  const ordered = new Float32Array(order.length * 9);
135
210
  const inverse = new Uint32Array(order.length);
211
+ const scene = geometry.triangleCount;
212
+ const prototypes = geometry.instancing?.positions ?? new Float32Array(0);
136
213
 
137
214
  for (let at = 0; at < order.length; at++) {
138
215
  const triangle = order[at];
216
+ const source = triangle < scene ? geometry.positions : prototypes;
217
+ const from = (triangle < scene ? triangle : triangle - scene) * 9;
139
218
 
140
- ordered.set(positions.subarray(triangle * 9, triangle * 9 + 9), at * 9);
219
+ ordered.set(source.subarray(from, from + 9), at * 9);
141
220
  inverse[triangle] = at;
142
221
  }
143
222
 
@@ -147,18 +226,18 @@ export function leafOrder(
147
226
  export function uploadScene(
148
227
  device: GPUDevice,
149
228
  geometry: PackedGeometry,
150
- bvh: PackedBvh,
151
- textures: PackedTextures = packTextures({ meshes: [], lights: [], textures: [] }),
229
+ bvh: UploadHierarchy,
230
+ textures: PackedTextures | TextureUpload = packTextures({ meshes: [], lights: [], textures: [] }),
152
231
  lights: PackedLights = packLights(geometry),
153
232
  environment: PackedEnvironment | null = null,
154
233
  environmentCdf = textures.extraOffset): SceneBuffers {
155
234
  const lengths = sceneByteLength(geometry, bvh, lights, textures);
156
235
 
157
- const create = (size: number): GPUBuffer =>
158
- device.createBuffer({ size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
236
+ const create = (size: number, label: string): GPUBuffer =>
237
+ device.createBuffer({ label: `scene ${label}`, size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
159
238
 
160
- const write = (data: ArrayBufferView, size: number): GPUBuffer => {
161
- const buffer = create(size);
239
+ const write = (data: ArrayBufferView, size: number, label: string): GPUBuffer => {
240
+ const buffer = create(size, label);
162
241
 
163
242
  device.queue.writeBuffer(buffer, 0, data);
164
243
 
@@ -166,8 +245,8 @@ export function uploadScene(
166
245
  };
167
246
 
168
247
  /** Arrays of the same kind, one after the other, so they cost one binding between them. */
169
- const join = (parts: ArrayBufferView[], size: number): GPUBuffer => {
170
- const buffer = create(size);
248
+ const join = (parts: ArrayBufferView[], size: number, label: string): GPUBuffer => {
249
+ const buffer = create(size, label);
171
250
  let offset = 0;
172
251
 
173
252
  for (const part of parts) {
@@ -181,28 +260,52 @@ export function uploadScene(
181
260
 
182
261
  const colors = geometry.colors ?? new Float32Array(0);
183
262
  const varyings = geometry.varyings ?? new Float32Array(0);
184
- const leaves = leafOrder(geometry.positions, bvh.order);
263
+ const leaves = leafOrder(geometry, bvh.order);
264
+ const { instanced, stored, named } = triangleCounts(geometry);
265
+ const instanceOf = instanceOfTriangles(geometry);
266
+ const orderOffset = named;
267
+ const leafOffset = orderOffset + stored;
268
+ const instanceOffset = leafOffset + stored;
269
+ const cornerOffset = instanceOffset + instanced;
270
+ const normalOffset = cornerOffset + 3 * stored;
271
+
272
+ const texturesBuffer = create(lengths.textures, "textures");
273
+
274
+ if ("write" in textures) textures.write(device, texturesBuffer);
275
+ else device.queue.writeBuffer(texturesBuffer, 0, textures.data);
185
276
 
186
277
  const buffers = {
187
- positions: write(leaves.positions, lengths.positions),
188
- attributes: join([geometry.normals, geometry.uvs, colors, varyings], lengths.attributes),
189
- indices: join([geometry.materials, bvh.order, leaves.inverse], lengths.indices),
190
- materials: write(new Uint8Array(packMaterials(geometry)), lengths.materials),
191
- nodes: write(new Uint8Array(bvh.nodes), lengths.nodes),
192
- lights: write(new Uint8Array(lights.entries), lengths.lights),
193
- textures: write(textures.data, lengths.textures),
278
+ positions: write(leaves.positions, lengths.positions, "positions"),
279
+ attributes: join([geometry.uvs, colors, varyings], lengths.attributes, "attributes"),
280
+ indices: join([
281
+ geometry.materials,
282
+ geometry.instancing?.materials ?? new Uint32Array(0),
283
+ bvh.order,
284
+ leaves.inverse,
285
+ instanceOf,
286
+ geometry.corners,
287
+ encodedNormals(geometry),
288
+ ], lengths.indices, "indices"),
289
+ materials: write(new Uint8Array(packMaterials(geometry)), lengths.materials, "materials"),
290
+ nodes: write(new Uint8Array(bvh.nodes), lengths.nodes, "bvh nodes"),
291
+ lights: write(new Uint8Array(lights.entries), lengths.lights, "lights"),
292
+ textures: texturesBuffer,
194
293
  };
195
294
 
196
295
  return {
197
296
  bytes: lengths,
198
297
  ...buffers,
199
- uvOffset: geometry.normals.length,
200
- colorOffset: geometry.normals.length + geometry.uvs.length,
201
- varyingOffset: geometry.normals.length + geometry.uvs.length + colors.length,
298
+ uvOffset: 0,
299
+ colorOffset: geometry.uvs.length,
300
+ varyingOffset: geometry.uvs.length + colors.length,
202
301
  hasColors: colors.length > 0,
203
- orderOffset: geometry.materials.length,
204
- leafOffset: geometry.materials.length + bvh.order.length,
205
- triangleCount: geometry.triangleCount,
302
+ orderOffset,
303
+ leafOffset,
304
+ instanceOffset,
305
+ cornerOffset,
306
+ normalOffset,
307
+ triangleCount: named,
308
+ sceneTriangles: geometry.triangleCount,
206
309
  lightCount: lights.count,
207
310
  areaLightCount: lights.areaCount,
208
311
  lightCdf: lights.cdfAt,
@@ -13,7 +13,8 @@ export function createShaderModule(device: GPUDevice, label: string, code: strin
13
13
  if (message.type === "error") console.error(text);
14
14
  else if (message.type === "warning") console.warn(text);
15
15
  }
16
- });
16
+ // A device lost while compiling rejects here too; the loss itself reaches the renderer through `device.lost`.
17
+ }, () => undefined);
17
18
 
18
19
  return module;
19
20
  }
package/src/skyBake.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { compileProgram, type ShaderProgram } from "./shaderProgram.js";
2
2
  import { createShaderModule } from "./shaderModule.js";
3
3
  import type { UniformValue } from "./upstream.js";
4
+ import { traversalConstants } from "./pathTracePass.js";
4
5
  import { BVH_WGSL } from "./wgsl/bvh.wgsl.js";
5
6
  import { COMMON_WGSL } from "./wgsl/common.wgsl.js";
6
7
  import { RANDOM_WGSL } from "./wgsl/random.wgsl.js";
@@ -16,7 +17,7 @@ const WORKGROUP = 8;
16
17
  * goes to the binding the path tracer accumulates into, which is the one read-write buffer the support code declares.
17
18
  */
18
19
  export function skyBakeSource(program: ShaderProgram, width: number, height: number): string {
19
- return `${COMMON_WGSL}${RANDOM_WGSL}${BVH_WGSL}${SAMPLING_WGSL}${TEXTURE_WGSL}${SHADER_SUPPORT_WGSL}
20
+ return `${traversalConstants(false)}${COMMON_WGSL}${RANDOM_WGSL}${BVH_WGSL}${SAMPLING_WGSL}${TEXTURE_WGSL}${SHADER_SUPPORT_WGSL}
20
21
  ${program.wgsl}
21
22
 
22
23
  @compute @workgroup_size(${WORKGROUP}, ${WORKGROUP})
@@ -72,13 +73,14 @@ export async function bakeSkyOnDevice(
72
73
  compute: { module, entryPoint: "main" },
73
74
  });
74
75
 
75
- const output = device.createBuffer({ size: texels * 16, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC });
76
+ const output = device.createBuffer({ label: "sky bake", size: texels * 16, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC });
76
77
 
77
78
  const data = device.createBuffer({
79
+ label: "sky bake",
78
80
  size: Math.max(16, words.byteLength), usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
79
81
  });
80
82
 
81
- const staging = device.createBuffer({ size: texels * 16, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST });
83
+ const staging = device.createBuffer({ label: "sky bake", size: texels * 16, usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST });
82
84
 
83
85
  try {
84
86
  device.queue.writeBuffer(data, 0, words);