@tinypoly/gpu-renderer 0.0.0-stage → 0.0.1

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 (204) hide show
  1. package/ARCHITECTURE.md +339 -0
  2. package/LICENSE +21 -0
  3. package/README.md +284 -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 +296 -0
  11. package/dist/bvh.js.map +1 -0
  12. package/dist/bvhBuilder.d.ts +72 -0
  13. package/dist/bvhBuilder.d.ts.map +1 -0
  14. package/dist/bvhBuilder.js +126 -0
  15. package/dist/bvhBuilder.js.map +1 -0
  16. package/dist/bvhWorker.d.ts +2 -0
  17. package/dist/bvhWorker.d.ts.map +1 -0
  18. package/dist/bvhWorker.js +8 -0
  19. package/dist/bvhWorker.js.map +1 -0
  20. package/dist/camera.d.ts +29 -0
  21. package/dist/camera.d.ts.map +1 -0
  22. package/dist/camera.js +48 -0
  23. package/dist/camera.js.map +1 -0
  24. package/dist/canvas.d.ts +9 -0
  25. package/dist/canvas.d.ts.map +1 -0
  26. package/dist/canvas.js +11 -0
  27. package/dist/canvas.js.map +1 -0
  28. package/dist/denoise.d.ts +69 -0
  29. package/dist/denoise.d.ts.map +1 -0
  30. package/dist/denoise.js +455 -0
  31. package/dist/denoise.js.map +1 -0
  32. package/dist/device.d.ts +37 -0
  33. package/dist/device.d.ts.map +1 -0
  34. package/dist/device.js +52 -0
  35. package/dist/device.js.map +1 -0
  36. package/dist/dispatchScheduler.d.ts +43 -0
  37. package/dist/dispatchScheduler.d.ts.map +1 -0
  38. package/dist/dispatchScheduler.js +49 -0
  39. package/dist/dispatchScheduler.js.map +1 -0
  40. package/dist/environment.d.ts +39 -0
  41. package/dist/environment.d.ts.map +1 -0
  42. package/dist/environment.js +168 -0
  43. package/dist/environment.js.map +1 -0
  44. package/dist/frameLayout.d.ts +4 -0
  45. package/dist/frameLayout.d.ts.map +1 -0
  46. package/dist/frameLayout.js +58 -0
  47. package/dist/frameLayout.js.map +1 -0
  48. package/dist/geometry.d.ts +75 -0
  49. package/dist/geometry.d.ts.map +1 -0
  50. package/dist/geometry.js +799 -0
  51. package/dist/geometry.js.map +1 -0
  52. package/dist/index.d.ts +27 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +19 -0
  55. package/dist/index.js.map +1 -0
  56. package/dist/lights.d.ts +64 -0
  57. package/dist/lights.d.ts.map +1 -0
  58. package/dist/lights.js +264 -0
  59. package/dist/lights.js.map +1 -0
  60. package/dist/materialLayout.d.ts +143 -0
  61. package/dist/materialLayout.d.ts.map +1 -0
  62. package/dist/materialLayout.js +297 -0
  63. package/dist/materialLayout.js.map +1 -0
  64. package/dist/math.d.ts +10 -0
  65. package/dist/math.d.ts.map +1 -0
  66. package/dist/math.js +40 -0
  67. package/dist/math.js.map +1 -0
  68. package/dist/pathTracePass.d.ts +27 -0
  69. package/dist/pathTracePass.d.ts.map +1 -0
  70. package/dist/pathTracePass.js +83 -0
  71. package/dist/pathTracePass.js.map +1 -0
  72. package/dist/planarCapture.d.ts +38 -0
  73. package/dist/planarCapture.d.ts.map +1 -0
  74. package/dist/planarCapture.js +163 -0
  75. package/dist/planarCapture.js.map +1 -0
  76. package/dist/presentPass.d.ts +31 -0
  77. package/dist/presentPass.d.ts.map +1 -0
  78. package/dist/presentPass.js +78 -0
  79. package/dist/presentPass.js.map +1 -0
  80. package/dist/renderer.d.ts +209 -0
  81. package/dist/renderer.d.ts.map +1 -0
  82. package/dist/renderer.js +847 -0
  83. package/dist/renderer.js.map +1 -0
  84. package/dist/sceneAssets.d.ts +24 -0
  85. package/dist/sceneAssets.d.ts.map +1 -0
  86. package/dist/sceneAssets.js +77 -0
  87. package/dist/sceneAssets.js.map +1 -0
  88. package/dist/sceneUpload.d.ts +61 -0
  89. package/dist/sceneUpload.d.ts.map +1 -0
  90. package/dist/sceneUpload.js +98 -0
  91. package/dist/sceneUpload.js.map +1 -0
  92. package/dist/shaderModule.d.ts +7 -0
  93. package/dist/shaderModule.d.ts.map +1 -0
  94. package/dist/shaderModule.js +19 -0
  95. package/dist/shaderModule.js.map +1 -0
  96. package/dist/shaderProgram.d.ts +124 -0
  97. package/dist/shaderProgram.d.ts.map +1 -0
  98. package/dist/shaderProgram.js +1030 -0
  99. package/dist/shaderProgram.js.map +1 -0
  100. package/dist/structLayout.d.ts +28 -0
  101. package/dist/structLayout.d.ts.map +1 -0
  102. package/dist/structLayout.js +58 -0
  103. package/dist/structLayout.js.map +1 -0
  104. package/dist/texturePack.d.ts +54 -0
  105. package/dist/texturePack.d.ts.map +1 -0
  106. package/dist/texturePack.js +86 -0
  107. package/dist/texturePack.js.map +1 -0
  108. package/dist/upstream.d.ts +7 -0
  109. package/dist/upstream.d.ts.map +1 -0
  110. package/dist/upstream.js +11 -0
  111. package/dist/upstream.js.map +1 -0
  112. package/dist/volumetric.d.ts +10 -0
  113. package/dist/volumetric.d.ts.map +1 -0
  114. package/dist/volumetric.js +34 -0
  115. package/dist/volumetric.js.map +1 -0
  116. package/dist/wgsl/bsdf.wgsl.d.ts +13 -0
  117. package/dist/wgsl/bsdf.wgsl.d.ts.map +1 -0
  118. package/dist/wgsl/bsdf.wgsl.js +619 -0
  119. package/dist/wgsl/bsdf.wgsl.js.map +1 -0
  120. package/dist/wgsl/bvh.wgsl.d.ts +3 -0
  121. package/dist/wgsl/bvh.wgsl.d.ts.map +1 -0
  122. package/dist/wgsl/bvh.wgsl.js +218 -0
  123. package/dist/wgsl/bvh.wgsl.js.map +1 -0
  124. package/dist/wgsl/common.wgsl.d.ts +3 -0
  125. package/dist/wgsl/common.wgsl.d.ts.map +1 -0
  126. package/dist/wgsl/common.wgsl.js +193 -0
  127. package/dist/wgsl/common.wgsl.js.map +1 -0
  128. package/dist/wgsl/environment.wgsl.d.ts +10 -0
  129. package/dist/wgsl/environment.wgsl.d.ts.map +1 -0
  130. package/dist/wgsl/environment.wgsl.js +145 -0
  131. package/dist/wgsl/environment.wgsl.js.map +1 -0
  132. package/dist/wgsl/lights.wgsl.d.ts +10 -0
  133. package/dist/wgsl/lights.wgsl.d.ts.map +1 -0
  134. package/dist/wgsl/lights.wgsl.js +434 -0
  135. package/dist/wgsl/lights.wgsl.js.map +1 -0
  136. package/dist/wgsl/pathTrace.wgsl.d.ts +14 -0
  137. package/dist/wgsl/pathTrace.wgsl.d.ts.map +1 -0
  138. package/dist/wgsl/pathTrace.wgsl.js +1020 -0
  139. package/dist/wgsl/pathTrace.wgsl.js.map +1 -0
  140. package/dist/wgsl/present.wgsl.d.ts +7 -0
  141. package/dist/wgsl/present.wgsl.d.ts.map +1 -0
  142. package/dist/wgsl/present.wgsl.js +154 -0
  143. package/dist/wgsl/present.wgsl.js.map +1 -0
  144. package/dist/wgsl/random.wgsl.d.ts +9 -0
  145. package/dist/wgsl/random.wgsl.d.ts.map +1 -0
  146. package/dist/wgsl/random.wgsl.js +91 -0
  147. package/dist/wgsl/random.wgsl.js.map +1 -0
  148. package/dist/wgsl/sampling.wgsl.d.ts +3 -0
  149. package/dist/wgsl/sampling.wgsl.d.ts.map +1 -0
  150. package/dist/wgsl/sampling.wgsl.js +31 -0
  151. package/dist/wgsl/sampling.wgsl.js.map +1 -0
  152. package/dist/wgsl/shaderSupport.wgsl.d.ts +6 -0
  153. package/dist/wgsl/shaderSupport.wgsl.d.ts.map +1 -0
  154. package/dist/wgsl/shaderSupport.wgsl.js +120 -0
  155. package/dist/wgsl/shaderSupport.wgsl.js.map +1 -0
  156. package/dist/wgsl/surface.wgsl.d.ts +10 -0
  157. package/dist/wgsl/surface.wgsl.d.ts.map +1 -0
  158. package/dist/wgsl/surface.wgsl.js +794 -0
  159. package/dist/wgsl/surface.wgsl.js.map +1 -0
  160. package/dist/wgsl/texture.wgsl.d.ts +11 -0
  161. package/dist/wgsl/texture.wgsl.d.ts.map +1 -0
  162. package/dist/wgsl/texture.wgsl.js +132 -0
  163. package/dist/wgsl/texture.wgsl.js.map +1 -0
  164. package/package.json +77 -3
  165. package/src/accumulator.ts +64 -0
  166. package/src/bvh.ts +336 -0
  167. package/src/bvhBuilder.ts +178 -0
  168. package/src/bvhWorker.ts +13 -0
  169. package/src/camera.ts +77 -0
  170. package/src/canvas.ts +13 -0
  171. package/src/denoise.ts +514 -0
  172. package/src/device.ts +81 -0
  173. package/src/dispatchScheduler.ts +71 -0
  174. package/src/environment.ts +233 -0
  175. package/src/frameLayout.ts +59 -0
  176. package/src/geometry.ts +1067 -0
  177. package/src/index.ts +64 -0
  178. package/src/lights.ts +365 -0
  179. package/src/materialLayout.ts +408 -0
  180. package/src/math.ts +48 -0
  181. package/src/pathTracePass.ts +97 -0
  182. package/src/planarCapture.ts +225 -0
  183. package/src/presentPass.ts +99 -0
  184. package/src/renderer.ts +1145 -0
  185. package/src/sceneAssets.ts +118 -0
  186. package/src/sceneUpload.ts +185 -0
  187. package/src/shaderModule.ts +19 -0
  188. package/src/shaderProgram.ts +1370 -0
  189. package/src/structLayout.ts +93 -0
  190. package/src/texturePack.ts +152 -0
  191. package/src/upstream.ts +56 -0
  192. package/src/volumetric.ts +42 -0
  193. package/src/wgsl/bsdf.wgsl.ts +618 -0
  194. package/src/wgsl/bvh.wgsl.ts +217 -0
  195. package/src/wgsl/common.wgsl.ts +193 -0
  196. package/src/wgsl/environment.wgsl.ts +144 -0
  197. package/src/wgsl/lights.wgsl.ts +433 -0
  198. package/src/wgsl/pathTrace.wgsl.ts +1019 -0
  199. package/src/wgsl/present.wgsl.ts +153 -0
  200. package/src/wgsl/random.wgsl.ts +90 -0
  201. package/src/wgsl/sampling.wgsl.ts +30 -0
  202. package/src/wgsl/shaderSupport.wgsl.ts +119 -0
  203. package/src/wgsl/surface.wgsl.ts +793 -0
  204. package/src/wgsl/texture.wgsl.ts +131 -0
@@ -0,0 +1,118 @@
1
+ import { environmentFromImage, type PackedEnvironment } from "./environment.js";
2
+ import { compileShaderPrograms, type ShaderPrograms } from "./shaderProgram.js";
3
+ import { CpuComputePipeline, CpuEnvironment } from "./upstream.js";
4
+ import type { SerializedMaterial, SerializedScene, SerializedTexture, UniformValue } from "./upstream.js";
5
+
6
+ /**
7
+ * What a scene needs before it can be packed, beyond the snapshot itself: the outputs of the compute simulations its
8
+ * materials declare, its procedural sky baked into an image, and its shaders translated. All of it depends on the scene
9
+ * alone, so it is prepared once per scene and kept while only the camera or the settings change.
10
+ */
11
+ export interface SceneAssets {
12
+ /** The snapshot with every simulation's outputs bound to the uniforms that read them. */
13
+ scene: SerializedScene;
14
+ programs: ShaderPrograms;
15
+ sky: ProceduralSkyEnvironment | null;
16
+ }
17
+
18
+ export interface ProceduralSkyEnvironment {
19
+ environment: PackedEnvironment;
20
+ /** The strength the sky lights the scene with; its background ignores it. */
21
+ intensity: number;
22
+ /** Whether the sky paints the background at all. */
23
+ background: boolean;
24
+ uniforms: Record<string, UniformValue>;
25
+ }
26
+
27
+ /** How wide the sky is baked. Its foreground, the sharp part, is evaluated per pixel instead. */
28
+ const SKY_WIDTH = 256;
29
+
30
+ /** Runs the CPU renderer's compute simulations, as its scene pipeline does, and binds their outputs. */
31
+ function bakeComputations(source: SerializedScene): SerializedScene {
32
+ const copies = new Map<SerializedMaterial, SerializedMaterial>();
33
+ const textures: SerializedTexture[] = [...source.textures];
34
+ const known = new Set(textures.map(texture => texture.id));
35
+ const computed = new Map<string, Record<string, SerializedTexture>>();
36
+
37
+ const copy = (material: SerializedMaterial) => {
38
+ let result = copies.get(material);
39
+
40
+ if (result) return result;
41
+
42
+ result = material.shader
43
+ ? { ...material, shader: { ...material.shader, uniforms: { ...material.shader.uniforms } } }
44
+ : material;
45
+
46
+ copies.set(material, result);
47
+
48
+ const { compute } = material;
49
+
50
+ if (compute && result.shader) {
51
+ let outputs = computed.get(compute.id);
52
+
53
+ if (!outputs) {
54
+ const pipeline = new CpuComputePipeline(compute.pipeline, compute.id, compute.data ?? {});
55
+
56
+ if (compute.snapshot) pipeline.restore(compute.snapshot);
57
+ else for (const _step of pipeline.tick(material.time ?? 0, 1 / 60)) { /* runs to completion */ }
58
+
59
+ outputs = pipeline.outputs();
60
+ computed.set(compute.id, outputs);
61
+ }
62
+
63
+ for (const [uniform, texture] of Object.entries(outputs)) {
64
+ result.shader.uniforms[uniform] = { texture: texture.id };
65
+
66
+ if (!known.has(texture.id)) {
67
+ textures.push(texture);
68
+ known.add(texture.id);
69
+ }
70
+ }
71
+ }
72
+
73
+ return result;
74
+ };
75
+
76
+ return {
77
+ ...source,
78
+ textures,
79
+ meshes: source.meshes.map(mesh => ({
80
+ ...mesh,
81
+ materials: mesh.materials.map(material => (material ? copy(material) : material)),
82
+ })),
83
+ };
84
+ }
85
+
86
+ /** Bakes a procedural sky with the CPU renderer's own interpreter, at the size its environment uses. */
87
+ function bakeSky(scene: SerializedScene): ProceduralSkyEnvironment | null {
88
+ const sky = scene.sky;
89
+
90
+ if (!sky) return null;
91
+
92
+ const generator = CpuEnvironment.fromProcedural(sky, SKY_WIDTH);
93
+ let step = generator.next();
94
+
95
+ while (!step.done) step = generator.next();
96
+
97
+ // The snapshot is the environment's public face; its texture is the baked panorama.
98
+ const texture = step.value.snapshot().texture;
99
+
100
+ if (!texture || !(texture.data instanceof Float32Array)) return null;
101
+
102
+ const environment = environmentFromImage(texture.data, texture.width, texture.height, "procedural-sky");
103
+
104
+ if (!environment) return null;
105
+
106
+ return { environment, intensity: sky.intensity, background: sky.background, uniforms: sky.uniforms };
107
+ }
108
+
109
+ export async function prepareSceneAssets(source: SerializedScene): Promise<SceneAssets> {
110
+ const scene = source.meshes.some(mesh => mesh.materials.some(material => material?.compute))
111
+ ? bakeComputations(source)
112
+ : source;
113
+
114
+ const sky = bakeSky(scene);
115
+ const programs = await compileShaderPrograms(scene, scene.sky?.foregroundShader);
116
+
117
+ return { scene, programs, sky };
118
+ }
@@ -0,0 +1,185 @@
1
+ import type { PackedBvh } from "./bvh.js";
2
+ import type { PackedGeometry } from "./geometry.js";
3
+ import { packLights, type PackedLights } from "./lights.js";
4
+ import { MATERIAL_WORDS, materialProperties, writeMaterial } from "./materialLayout.js";
5
+ import { NO_TEXTURE, packTextures, type PackedTextures } from "./texturePack.js";
6
+ import type { PackedEnvironment } from "./environment.js";
7
+
8
+ /** Bytes each buffer of a scene takes on the device. Computed without a device, so a host can check it up front. */
9
+ export interface SceneByteLength {
10
+ positions: number;
11
+ materials: number;
12
+ /** Vertex normals, the UV sets, vertex colours and shader varyings, in one buffer. */
13
+ attributes: number;
14
+ /** One material index per triangle and then the hierarchy's triangle order, in one buffer. */
15
+ indices: number;
16
+ nodes: number;
17
+ lights: number;
18
+ textures: number;
19
+ total: number;
20
+ /** The largest single binding, which is what a device limit applies to. */
21
+ largest: number;
22
+ }
23
+
24
+ export interface SceneBuffers {
25
+ /** What the upload takes on the device, so the host can compare it with the device's limits. */
26
+ bytes: SceneByteLength;
27
+ positions: GPUBuffer;
28
+ materials: GPUBuffer;
29
+ attributes: GPUBuffer;
30
+ indices: GPUBuffer;
31
+ nodes: GPUBuffer;
32
+ lights: GPUBuffer;
33
+ textures: GPUBuffer;
34
+ /** Where the UV sets, the vertex colours and the varyings start inside `attributes`, and the order inside `indices`. */
35
+ uvOffset: number;
36
+ colorOffset: number;
37
+ varyingOffset: number;
38
+ hasColors: boolean;
39
+ orderOffset: number;
40
+ triangleCount: number;
41
+ /** Everything the integrator can aim a shadow ray at. */
42
+ lightCount: number;
43
+ /** How many of those are emissive triangles, which lead the buffer. */
44
+ areaLightCount: number;
45
+ /** Radiance an ambient or hemisphere light adds, which no shadow ray can reach. */
46
+ ambient: PackedLights["ambient"];
47
+ /** Where the surrounding image and its distributions ended up, for the frame uniform. */
48
+ environment: {
49
+ texture: number;
50
+ cdf: number;
51
+ width: number;
52
+ height: number;
53
+ total: number;
54
+ pick: number;
55
+ };
56
+ destroy(): void;
57
+ }
58
+
59
+ /**
60
+ * WebGPU rejects an empty binding, and one smaller than a single element of its array, which an empty scene would
61
+ * otherwise produce for the hierarchy's 32 byte nodes. This covers the largest element any binding holds.
62
+ */
63
+ const MINIMUM = Math.max(64, MATERIAL_WORDS * 4);
64
+
65
+ const sized = (bytes: number) => Math.max(MINIMUM, bytes);
66
+
67
+ const attributeBytes = (geometry: PackedGeometry) =>
68
+ geometry.normals.byteLength + geometry.uvs.byteLength + (geometry.colors?.byteLength ?? 0)
69
+ + (geometry.varyings?.byteLength ?? 0);
70
+
71
+ export function sceneByteLength(
72
+ geometry: PackedGeometry,
73
+ bvh: PackedBvh,
74
+ lights: PackedLights = packLights(geometry),
75
+ textures: Pick<PackedTextures, "data"> = { data: new Uint32Array(4) }): SceneByteLength {
76
+ const lengths = {
77
+ positions: sized(geometry.positions.byteLength),
78
+ attributes: sized(attributeBytes(geometry)),
79
+ indices: sized(geometry.materials.byteLength + bvh.order.byteLength),
80
+ materials: sized(Math.max(1, geometry.materialCount) * MATERIAL_WORDS * 4),
81
+ nodes: sized(bvh.nodes.byteLength),
82
+ lights: sized(lights.entries.byteLength),
83
+ textures: sized(textures.data.byteLength),
84
+ };
85
+
86
+ const values = Object.values(lengths);
87
+
88
+ return {
89
+ ...lengths,
90
+ total: values.reduce((sum, value) => sum + value, 0),
91
+ largest: Math.max(...values),
92
+ };
93
+ }
94
+
95
+ /** Materials as the shader reads them: one `Material` struct each, laid out by `materialLayout.ts`. */
96
+ export function packMaterials(geometry: PackedGeometry): ArrayBuffer {
97
+ const count = Math.max(1, geometry.materialCount);
98
+ const packed = new ArrayBuffer(count * MATERIAL_WORDS * 4);
99
+
100
+ // A scene whose meshes carry no material still needs one entry for the shader to index.
101
+ const all = geometry.materialCount ? geometry.materialProperties : [materialProperties(undefined)];
102
+
103
+ all.forEach((properties, index) => writeMaterial(packed, index, properties, geometry.materialPlacements?.[index]));
104
+
105
+ return packed;
106
+ }
107
+
108
+ /** The power a surrounding image radiates over the whole sphere, which is what weighs it against the scene's lights. */
109
+ export function environmentPower(environment: PackedEnvironment | null): number {
110
+ return environment ? environment.total * 2 * Math.PI ** 2 / (environment.width * environment.height) : 0;
111
+ }
112
+
113
+ export function uploadScene(
114
+ device: GPUDevice,
115
+ geometry: PackedGeometry,
116
+ bvh: PackedBvh,
117
+ textures: PackedTextures = packTextures({ meshes: [], lights: [], textures: [] }),
118
+ lights: PackedLights = packLights(geometry),
119
+ environment: PackedEnvironment | null = null,
120
+ environmentCdf = textures.extraOffset): SceneBuffers {
121
+ const lengths = sceneByteLength(geometry, bvh, lights, textures);
122
+
123
+ const create = (size: number): GPUBuffer =>
124
+ device.createBuffer({ size, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST });
125
+
126
+ const write = (data: ArrayBufferView, size: number): GPUBuffer => {
127
+ const buffer = create(size);
128
+
129
+ device.queue.writeBuffer(buffer, 0, data);
130
+
131
+ return buffer;
132
+ };
133
+
134
+ /** Arrays of the same kind, one after the other, so they cost one binding between them. */
135
+ const join = (parts: ArrayBufferView[], size: number): GPUBuffer => {
136
+ const buffer = create(size);
137
+ let offset = 0;
138
+
139
+ for (const part of parts) {
140
+ if (part.byteLength) device.queue.writeBuffer(buffer, offset, part);
141
+
142
+ offset += part.byteLength;
143
+ }
144
+
145
+ return buffer;
146
+ };
147
+
148
+ const colors = geometry.colors ?? new Float32Array(0);
149
+ const varyings = geometry.varyings ?? new Float32Array(0);
150
+
151
+ const buffers = {
152
+ positions: write(geometry.positions, lengths.positions),
153
+ attributes: join([geometry.normals, geometry.uvs, colors, varyings], lengths.attributes),
154
+ indices: join([geometry.materials, bvh.order], lengths.indices),
155
+ materials: write(new Uint8Array(packMaterials(geometry)), lengths.materials),
156
+ nodes: write(new Uint8Array(bvh.nodes), lengths.nodes),
157
+ lights: write(new Uint8Array(lights.entries), lengths.lights),
158
+ textures: write(textures.data, lengths.textures),
159
+ };
160
+
161
+ return {
162
+ bytes: lengths,
163
+ ...buffers,
164
+ uvOffset: geometry.normals.length,
165
+ colorOffset: geometry.normals.length + geometry.uvs.length,
166
+ varyingOffset: geometry.normals.length + geometry.uvs.length + colors.length,
167
+ hasColors: colors.length > 0,
168
+ orderOffset: geometry.materials.length,
169
+ triangleCount: geometry.triangleCount,
170
+ lightCount: lights.count,
171
+ areaLightCount: lights.areaCount,
172
+ ambient: lights.ambient,
173
+ environment: {
174
+ texture: environment ? textures.indexOf.get(environment.texture.id) ?? NO_TEXTURE : NO_TEXTURE,
175
+ cdf: environmentCdf,
176
+ width: environment?.width ?? 0,
177
+ height: environment?.height ?? 0,
178
+ total: environment?.total ?? 0,
179
+ pick: lights.environmentPick,
180
+ },
181
+ destroy() {
182
+ for (const buffer of Object.values(buffers)) buffer.destroy();
183
+ },
184
+ };
185
+ }
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Creating a shader module never throws: a WGSL mistake only surfaces later, as a pipeline that fails to build or a
3
+ * device error with no line number. Asking for the compilation messages turns it back into something readable, which
4
+ * is the difference between a five minute fix and an afternoon.
5
+ */
6
+ export function createShaderModule(device: GPUDevice, label: string, code: string): GPUShaderModule {
7
+ const module = device.createShaderModule({ label, code });
8
+
9
+ void module.getCompilationInfo?.().then(info => {
10
+ for (const message of info.messages) {
11
+ const text = `GPU renderer: ${label} shader ${message.type} on line ${message.lineNum}: ${message.message}`;
12
+
13
+ if (message.type === "error") console.error(text);
14
+ else if (message.type === "warning") console.warn(text);
15
+ }
16
+ });
17
+
18
+ return module;
19
+ }