@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,178 @@
1
+ import { buildBvh, type PackedBvh } from "./bvh.js";
2
+
3
+ /** What the page sends the worker: its own copy of the positions, transferred. */
4
+ export interface BvhRequest {
5
+ id: number;
6
+ positions: Float32Array;
7
+ triangleCount: number;
8
+ }
9
+
10
+ /** The hierarchy back, its buffers transferred, or why it could not be built. */
11
+ export type BvhResponse =
12
+ | { id: number; nodes: ArrayBuffer; nodeCount: number; order: Uint32Array; maxDepth: number }
13
+ | { id: number; error: string };
14
+
15
+ /**
16
+ * What the renderer needs from a worker: the shape of a Web `Worker`. Any transport that carries structured clones and
17
+ * transfer lists fits, such as a `worker_threads` thread behind an adapter.
18
+ */
19
+ export interface BvhWorker {
20
+ postMessage(message: BvhRequest, transfer?: Transferable[]): void;
21
+ terminate(): void;
22
+ onmessage: ((event: { data: BvhResponse }) => void) | null;
23
+ onerror: ((event: { message?: string; preventDefault?(): void }) => void) | null;
24
+ }
25
+
26
+ export type BvhWorkerFactory = () => BvhWorker;
27
+
28
+ /**
29
+ * Below this many triangles the hierarchy is built on the page: it takes a few milliseconds, less than copying the
30
+ * positions to a worker and the hierarchy back.
31
+ */
32
+ export const BVH_WORKER_MIN_TRIANGLES = 50_000;
33
+
34
+ /** The worker this package ships, next to this module. Replace it when the bundler cannot follow the URL. */
35
+ export function createDefaultBvhWorker(): BvhWorker {
36
+ if (typeof Worker === "undefined")
37
+ throw new Error("Web Workers are unavailable here: pass `createBvhWorker`, or `bvhWorker: false`.");
38
+
39
+ return new Worker(new URL("./bvhWorker.js", import.meta.url), { type: "module" }) as unknown as BvhWorker;
40
+ }
41
+
42
+ /** The worker's side of the exchange, apart from the worker itself so it can be run anywhere. */
43
+ export function respondToBvhRequest(request: BvhRequest): { response: BvhResponse; transfer: Transferable[] } {
44
+ try {
45
+ const bvh = buildBvh(request.positions, request.triangleCount);
46
+
47
+ return {
48
+ response: { id: request.id, ...bvh },
49
+ transfer: [bvh.nodes, bvh.order.buffer],
50
+ };
51
+ } catch(error) {
52
+ const message = error instanceof Error ? error.message : String(error);
53
+
54
+ return { response: { id: request.id, error: message }, transfer: [] };
55
+ }
56
+ }
57
+
58
+ interface Pending {
59
+ positions: Float32Array;
60
+ triangleCount: number;
61
+ resolve: (bvh: PackedBvh) => void;
62
+ reject: (error: Error) => void;
63
+ }
64
+
65
+ /**
66
+ * Builds hierarchies in a worker, so a large scene does not hold the page while it is split. The worker is started on
67
+ * the first build that needs it and kept for the next ones. If it cannot be started or fails to load, every build from
68
+ * then on happens on the page, with the same result: the worker only moves the work, it never changes it.
69
+ */
70
+ export class BvhBuilder {
71
+ private worker: BvhWorker | null = null;
72
+ private failed = false;
73
+ private disposed = false;
74
+ private next = 0;
75
+ private readonly pending = new Map<number, Pending>();
76
+
77
+ constructor(
78
+ private readonly enabled: boolean,
79
+ private readonly createWorker: BvhWorkerFactory = createDefaultBvhWorker,
80
+ private readonly minTriangles = BVH_WORKER_MIN_TRIANGLES) {}
81
+
82
+ /** Whether builds go to the worker: it is enabled and has not failed. */
83
+ get usesWorker(): boolean {
84
+ return this.enabled && !this.failed && !this.disposed;
85
+ }
86
+
87
+ build(positions: Float32Array, triangleCount: number): Promise<PackedBvh> {
88
+ if (this.disposed) return Promise.reject(abortError());
89
+
90
+ const worker = this.usesWorker && triangleCount >= this.minTriangles ? this.connect() : null;
91
+
92
+ if (!worker) return Promise.resolve(buildBvh(positions, triangleCount));
93
+
94
+ const id = this.next++;
95
+ // The page still uploads the positions, so the worker gets a copy it can own.
96
+ const copy = positions.slice(0, triangleCount * 9);
97
+
98
+ return new Promise<PackedBvh>((resolve, reject) => {
99
+ this.pending.set(id, { positions, triangleCount, resolve, reject });
100
+ worker.postMessage({ id, positions: copy, triangleCount }, [copy.buffer]);
101
+ });
102
+ }
103
+
104
+ /** Stops the worker. Builds still waiting for it reject with an `AbortError`. */
105
+ dispose() {
106
+ this.disposed = true;
107
+ this.worker?.terminate();
108
+ this.worker = null;
109
+
110
+ for (const pending of this.pending.values()) pending.reject(abortError());
111
+
112
+ this.pending.clear();
113
+ }
114
+
115
+ private connect(): BvhWorker | null {
116
+ if (this.worker) return this.worker;
117
+
118
+ let worker: BvhWorker;
119
+
120
+ try {
121
+ worker = this.createWorker();
122
+ } catch(error) {
123
+ this.fail(error instanceof Error ? error.message : String(error));
124
+
125
+ return null;
126
+ }
127
+
128
+ worker.onmessage = event => this.receive(event.data);
129
+
130
+ worker.onerror = event => {
131
+ event.preventDefault?.();
132
+ this.fail(event.message || "the worker failed to load");
133
+ };
134
+
135
+ this.worker = worker;
136
+
137
+ return worker;
138
+ }
139
+
140
+ private receive(response: BvhResponse) {
141
+ const pending = this.pending.get(response.id);
142
+
143
+ if (!pending) return;
144
+
145
+ this.pending.delete(response.id);
146
+
147
+ if ("error" in response) pending.reject(new Error(`GPU renderer: building the BVH failed: ${response.error}`));
148
+ else pending.resolve({
149
+ nodes: response.nodes, nodeCount: response.nodeCount, order: response.order, maxDepth: response.maxDepth,
150
+ });
151
+ }
152
+
153
+ /** The worker is gone for good: what it was building, and everything after, is built on the page instead. */
154
+ private fail(reason: string) {
155
+ if (!this.failed)
156
+ console.warn(`GPU renderer: the BVH worker is unavailable (${reason}); building on the main thread instead.`);
157
+
158
+ this.failed = true;
159
+ this.worker?.terminate();
160
+ this.worker = null;
161
+
162
+ const waiting = [...this.pending.values()];
163
+
164
+ this.pending.clear();
165
+
166
+ for (const pending of waiting) {
167
+ try {
168
+ pending.resolve(buildBvh(pending.positions, pending.triangleCount));
169
+ } catch(error) {
170
+ pending.reject(error instanceof Error ? error : new Error(String(error)));
171
+ }
172
+ }
173
+ }
174
+ }
175
+
176
+ function abortError(): Error {
177
+ return Object.assign(new Error("The renderer was disposed."), { name: "AbortError" });
178
+ }
@@ -0,0 +1,13 @@
1
+ // The worker `createDefaultBvhWorker` starts: it builds one hierarchy per message, off the page's thread.
2
+ import { respondToBvhRequest, type BvhRequest, type BvhResponse } from "./bvhBuilder.js";
3
+
4
+ const scope = self as unknown as {
5
+ onmessage: ((event: MessageEvent<BvhRequest>) => void) | null;
6
+ postMessage(message: BvhResponse, transfer: Transferable[]): void;
7
+ };
8
+
9
+ scope.onmessage = event => {
10
+ const { response, transfer } = respondToBvhRequest(event.data);
11
+
12
+ scope.postMessage(response, transfer);
13
+ };
package/src/camera.ts ADDED
@@ -0,0 +1,77 @@
1
+ import { Matrix4, OrthographicCamera, PerspectiveCamera } from "three";
2
+ import type { CameraState } from "./upstream.js";
3
+
4
+ /**
5
+ * Everything the renderer derives from a camera snapshot. Rays are cast through the inverse of the camera's own
6
+ * projection, so zoom, film offsets, `setViewOffset` and any custom projection come out exactly as Three draws them,
7
+ * and so do the near and far planes, which clip a camera ray the way they clip a rasterized fragment.
8
+ */
9
+ export interface CameraMatrices {
10
+ /** Column-major, as Three stores them. */
11
+ cameraWorld: number[];
12
+ view: number[];
13
+ projection: number[];
14
+ inverseProjection: number[];
15
+ orthographic: boolean;
16
+ near: number;
17
+ far: number;
18
+ /** Vertical field of view in degrees, for the lens; 50 when the snapshot has none. */
19
+ fov: number;
20
+ position: number[];
21
+ }
22
+
23
+ /** The camera's projection: the snapshot's own when it has one, otherwise rebuilt from its parameters. */
24
+ export function cameraProjection(camera: CameraState, aspect: number): Matrix4 {
25
+ if (camera.projectionMatrix) return new Matrix4().fromArray(camera.projectionMatrix);
26
+
27
+ const projection = camera.type === "orthographic"
28
+ ? new OrthographicCamera(camera.left, camera.right, camera.top, camera.bottom, camera.near, camera.far)
29
+ : new PerspectiveCamera(camera.fov ?? 50, camera.aspect ?? aspect, camera.near, camera.far);
30
+
31
+ projection.zoom = camera.zoom ?? 1;
32
+ projection.updateProjectionMatrix();
33
+
34
+ return projection.projectionMatrix;
35
+ }
36
+
37
+ export function cameraMatrices(camera: CameraState, width: number, height: number): CameraMatrices {
38
+ const world = new Matrix4().fromArray(camera.matrixWorld);
39
+ const projection = cameraProjection(camera, width / height);
40
+ const orthographic = camera.type === "orthographic" || (projection.elements[11] === 0 && projection.elements[15] === 1);
41
+
42
+ return {
43
+ cameraWorld: world.toArray(),
44
+ view: world.clone().invert().toArray(),
45
+ projection: projection.toArray(),
46
+ inverseProjection: projection.clone().invert().toArray(),
47
+ orthographic,
48
+ near: camera.near,
49
+ far: camera.far,
50
+ fov: camera.fov ?? 50,
51
+ position: [camera.matrixWorld[12], camera.matrixWorld[13], camera.matrixWorld[14]],
52
+ };
53
+ }
54
+
55
+ /** A column-major 4x4 matrix times a point, with the w it produced. */
56
+ export function transform4(matrix: ArrayLike<number>, x: number, y: number, z: number, w = 1): number[] {
57
+ return [
58
+ matrix[0] * x + matrix[4] * y + matrix[8] * z + matrix[12] * w,
59
+ matrix[1] * x + matrix[5] * y + matrix[9] * z + matrix[13] * w,
60
+ matrix[2] * x + matrix[6] * y + matrix[10] * z + matrix[14] * w,
61
+ matrix[3] * x + matrix[7] * y + matrix[11] * z + matrix[15] * w,
62
+ ];
63
+ }
64
+
65
+ /** A point in clip space back to world space, through the camera that produced it. */
66
+ export function clipToWorld(camera: CameraMatrices, clip: readonly number[]): number[] {
67
+ const view = transform4(camera.inverseProjection, clip[0], clip[1], clip[2], clip[3]);
68
+ const w = view[3] || 1e-30;
69
+
70
+ return transform4(camera.cameraWorld, view[0] / w, view[1] / w, view[2] / w).slice(0, 3);
71
+ }
72
+
73
+ export function worldToClip(camera: CameraMatrices, point: readonly number[]): number[] {
74
+ const view = transform4(camera.view, point[0], point[1], point[2]);
75
+
76
+ return transform4(camera.projection, view[0], view[1], view[2], view[3]);
77
+ }
package/src/canvas.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { GpuRenderer } from "./renderer.js";
2
+
3
+ /**
4
+ * Sends the renderer's output to a canvas. It configures the WebGPU context, sizes the canvas to the render
5
+ * resolution and presents after every submit, so the image refines in place while it converges.
6
+ *
7
+ * Returns the function that detaches it.
8
+ */
9
+ export function attachCanvas(renderer: GpuRenderer, canvas: HTMLCanvasElement): () => void {
10
+ renderer.setOutputCanvas(canvas);
11
+
12
+ return () => renderer.setOutputCanvas(null);
13
+ }