@multiplekex/shallot 0.1.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 (196) hide show
  1. package/dist/core/builder.d.ts +25 -0
  2. package/dist/core/builder.d.ts.map +1 -0
  3. package/dist/core/builder.js +88 -0
  4. package/dist/core/builder.js.map +1 -0
  5. package/dist/core/component.d.ts +29 -0
  6. package/dist/core/component.d.ts.map +1 -0
  7. package/dist/core/component.js +36 -0
  8. package/dist/core/component.js.map +1 -0
  9. package/dist/core/index.d.ts +13 -0
  10. package/dist/core/index.d.ts.map +1 -0
  11. package/dist/core/math.d.ts +32 -0
  12. package/dist/core/math.d.ts.map +1 -0
  13. package/dist/core/math.js +39 -0
  14. package/dist/core/math.js.map +1 -0
  15. package/dist/core/relation.d.ts +16 -0
  16. package/dist/core/relation.d.ts.map +1 -0
  17. package/dist/core/relation.js +32 -0
  18. package/dist/core/relation.js.map +1 -0
  19. package/dist/core/resource.d.ts +9 -0
  20. package/dist/core/resource.d.ts.map +1 -0
  21. package/dist/core/resource.js +12 -0
  22. package/dist/core/resource.js.map +1 -0
  23. package/dist/core/runtime.d.ts +13 -0
  24. package/dist/core/runtime.d.ts.map +1 -0
  25. package/dist/core/runtime.js +118 -0
  26. package/dist/core/runtime.js.map +1 -0
  27. package/dist/core/scheduler.d.ts +47 -0
  28. package/dist/core/scheduler.d.ts.map +1 -0
  29. package/dist/core/scheduler.js +138 -0
  30. package/dist/core/scheduler.js.map +1 -0
  31. package/dist/core/state.d.ts +62 -0
  32. package/dist/core/state.d.ts.map +1 -0
  33. package/dist/core/state.js +185 -0
  34. package/dist/core/state.js.map +1 -0
  35. package/dist/core/strings.d.ts +3 -0
  36. package/dist/core/strings.d.ts.map +1 -0
  37. package/dist/core/strings.js +11 -0
  38. package/dist/core/strings.js.map +1 -0
  39. package/dist/core/types.d.ts +33 -0
  40. package/dist/core/types.d.ts.map +1 -0
  41. package/dist/core/xml.d.ts +42 -0
  42. package/dist/core/xml.d.ts.map +1 -0
  43. package/dist/core/xml.js +349 -0
  44. package/dist/core/xml.js.map +1 -0
  45. package/dist/extras/arrows/index.d.ts +33 -0
  46. package/dist/extras/arrows/index.d.ts.map +1 -0
  47. package/dist/extras/arrows/index.js +288 -0
  48. package/dist/extras/arrows/index.js.map +1 -0
  49. package/dist/extras/index.d.ts +5 -0
  50. package/dist/extras/index.d.ts.map +1 -0
  51. package/dist/extras/index.js +31 -0
  52. package/dist/extras/index.js.map +1 -0
  53. package/dist/extras/lines/index.d.ts +36 -0
  54. package/dist/extras/lines/index.d.ts.map +1 -0
  55. package/dist/extras/lines/index.js +288 -0
  56. package/dist/extras/lines/index.js.map +1 -0
  57. package/dist/extras/orbit/index.d.ts +20 -0
  58. package/dist/extras/orbit/index.d.ts.map +1 -0
  59. package/dist/extras/orbit/index.js +93 -0
  60. package/dist/extras/orbit/index.js.map +1 -0
  61. package/dist/extras/text/index.d.ts +64 -0
  62. package/dist/extras/text/index.d.ts.map +1 -0
  63. package/dist/extras/text/index.js +423 -0
  64. package/dist/extras/text/index.js.map +1 -0
  65. package/dist/index.d.ts +4 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +187 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/rust/transforms/pkg/shallot_transforms.js +107 -0
  70. package/dist/rust/transforms/pkg/shallot_transforms.js.map +1 -0
  71. package/dist/standard/compute/graph.d.ts +37 -0
  72. package/dist/standard/compute/graph.d.ts.map +1 -0
  73. package/dist/standard/compute/graph.js +85 -0
  74. package/dist/standard/compute/graph.js.map +1 -0
  75. package/dist/standard/compute/index.d.ts +21 -0
  76. package/dist/standard/compute/index.d.ts.map +1 -0
  77. package/dist/standard/compute/index.js +81 -0
  78. package/dist/standard/compute/index.js.map +1 -0
  79. package/dist/standard/defaults.d.ts +3 -0
  80. package/dist/standard/defaults.d.ts.map +1 -0
  81. package/dist/standard/defaults.js +18 -0
  82. package/dist/standard/defaults.js.map +1 -0
  83. package/dist/standard/index.d.ts +8 -0
  84. package/dist/standard/index.d.ts.map +1 -0
  85. package/dist/standard/input/index.d.ts +5 -0
  86. package/dist/standard/input/index.d.ts.map +1 -0
  87. package/dist/standard/input/index.js +70 -0
  88. package/dist/standard/input/index.js.map +1 -0
  89. package/dist/standard/loading/index.d.ts +7 -0
  90. package/dist/standard/loading/index.d.ts.map +1 -0
  91. package/dist/standard/loading/index.js +91 -0
  92. package/dist/standard/loading/index.js.map +1 -0
  93. package/dist/standard/render/camera.d.ts +36 -0
  94. package/dist/standard/render/camera.d.ts.map +1 -0
  95. package/dist/standard/render/camera.js +71 -0
  96. package/dist/standard/render/camera.js.map +1 -0
  97. package/dist/standard/render/forward.d.ts +30 -0
  98. package/dist/standard/render/forward.d.ts.map +1 -0
  99. package/dist/standard/render/forward.js +158 -0
  100. package/dist/standard/render/forward.js.map +1 -0
  101. package/dist/standard/render/index.d.ts +22 -0
  102. package/dist/standard/render/index.d.ts.map +1 -0
  103. package/dist/standard/render/index.js +153 -0
  104. package/dist/standard/render/index.js.map +1 -0
  105. package/dist/standard/render/light.d.ts +25 -0
  106. package/dist/standard/render/light.d.ts.map +1 -0
  107. package/dist/standard/render/light.js +48 -0
  108. package/dist/standard/render/light.js.map +1 -0
  109. package/dist/standard/render/mesh/box.d.ts +3 -0
  110. package/dist/standard/render/mesh/box.d.ts.map +1 -0
  111. package/dist/standard/render/mesh/box.js +190 -0
  112. package/dist/standard/render/mesh/box.js.map +1 -0
  113. package/dist/standard/render/mesh/index.d.ts +52 -0
  114. package/dist/standard/render/mesh/index.d.ts.map +1 -0
  115. package/dist/standard/render/mesh/index.js +158 -0
  116. package/dist/standard/render/mesh/index.js.map +1 -0
  117. package/dist/standard/render/mesh/plane.d.ts +3 -0
  118. package/dist/standard/render/mesh/plane.d.ts.map +1 -0
  119. package/dist/standard/render/mesh/plane.js +33 -0
  120. package/dist/standard/render/mesh/plane.js.map +1 -0
  121. package/dist/standard/render/mesh/sphere.d.ts +3 -0
  122. package/dist/standard/render/mesh/sphere.d.ts.map +1 -0
  123. package/dist/standard/render/mesh/sphere.js +25 -0
  124. package/dist/standard/render/mesh/sphere.js.map +1 -0
  125. package/dist/standard/render/postprocess.d.ts +11 -0
  126. package/dist/standard/render/postprocess.d.ts.map +1 -0
  127. package/dist/standard/render/postprocess.js +190 -0
  128. package/dist/standard/render/postprocess.js.map +1 -0
  129. package/dist/standard/render/scene.d.ts +8 -0
  130. package/dist/standard/render/scene.d.ts.map +1 -0
  131. package/dist/standard/render/scene.js +67 -0
  132. package/dist/standard/render/scene.js.map +1 -0
  133. package/dist/standard/transforms/index.d.ts +27 -0
  134. package/dist/standard/transforms/index.d.ts.map +1 -0
  135. package/dist/standard/transforms/index.js +122 -0
  136. package/dist/standard/transforms/index.js.map +1 -0
  137. package/dist/standard/transforms/wasm.d.ts +17 -0
  138. package/dist/standard/transforms/wasm.d.ts.map +1 -0
  139. package/dist/standard/transforms/wasm.js +31 -0
  140. package/dist/standard/transforms/wasm.js.map +1 -0
  141. package/dist/standard/tween/easing.d.ts +5 -0
  142. package/dist/standard/tween/easing.d.ts.map +1 -0
  143. package/dist/standard/tween/easing.js +80 -0
  144. package/dist/standard/tween/easing.js.map +1 -0
  145. package/dist/standard/tween/index.d.ts +4 -0
  146. package/dist/standard/tween/index.d.ts.map +1 -0
  147. package/dist/standard/tween/sequence.d.ts +20 -0
  148. package/dist/standard/tween/sequence.d.ts.map +1 -0
  149. package/dist/standard/tween/sequence.js +95 -0
  150. package/dist/standard/tween/sequence.js.map +1 -0
  151. package/dist/standard/tween/tween.d.ts +28 -0
  152. package/dist/standard/tween/tween.d.ts.map +1 -0
  153. package/dist/standard/tween/tween.js +136 -0
  154. package/dist/standard/tween/tween.js.map +1 -0
  155. package/package.json +63 -0
  156. package/src/core/builder.ts +148 -0
  157. package/src/core/component.ts +71 -0
  158. package/src/core/index.ts +92 -0
  159. package/src/core/math.ts +128 -0
  160. package/src/core/relation.ts +46 -0
  161. package/src/core/resource.ts +18 -0
  162. package/src/core/runtime.ts +185 -0
  163. package/src/core/scheduler.ts +238 -0
  164. package/src/core/state.ts +295 -0
  165. package/src/core/strings.ts +10 -0
  166. package/src/core/types.ts +37 -0
  167. package/src/core/xml.ts +676 -0
  168. package/src/extras/arrows/index.ts +363 -0
  169. package/src/extras/index.ts +4 -0
  170. package/src/extras/lines/index.ts +368 -0
  171. package/src/extras/orbit/index.ts +133 -0
  172. package/src/extras/text/index.ts +641 -0
  173. package/src/index.ts +3 -0
  174. package/src/standard/compute/graph.ts +165 -0
  175. package/src/standard/compute/index.ts +116 -0
  176. package/src/standard/defaults.ts +17 -0
  177. package/src/standard/index.ts +7 -0
  178. package/src/standard/input/index.ts +142 -0
  179. package/src/standard/loading/index.ts +136 -0
  180. package/src/standard/render/camera.ts +87 -0
  181. package/src/standard/render/forward.ts +212 -0
  182. package/src/standard/render/index.ts +175 -0
  183. package/src/standard/render/light.ts +81 -0
  184. package/src/standard/render/mesh/box.ts +20 -0
  185. package/src/standard/render/mesh/index.ts +227 -0
  186. package/src/standard/render/mesh/plane.ts +11 -0
  187. package/src/standard/render/mesh/sphere.ts +40 -0
  188. package/src/standard/render/postprocess.ts +235 -0
  189. package/src/standard/render/scene.ts +116 -0
  190. package/src/standard/transforms/index.ts +184 -0
  191. package/src/standard/transforms/wasm.ts +61 -0
  192. package/src/standard/tween/easing.ts +169 -0
  193. package/src/standard/tween/index.ts +13 -0
  194. package/src/standard/tween/sequence.ts +142 -0
  195. package/src/standard/tween/tween.ts +265 -0
  196. package/src/vite-env.d.ts +6 -0
@@ -0,0 +1,227 @@
1
+ import { MAX_ENTITIES } from "../../../core";
2
+ import { setTraits, type FieldAccessor } from "../../../core/component";
3
+ import { createEntityIdBuffer } from "../../compute";
4
+ import { writeIndirect } from "../forward";
5
+ import { createBox } from "./box";
6
+ import { createSphere } from "./sphere";
7
+ import { createPlane } from "./plane";
8
+
9
+ export interface MeshData {
10
+ vertices: Float32Array<ArrayBuffer>;
11
+ indices: Uint16Array<ArrayBuffer>;
12
+ vertexCount: number;
13
+ indexCount: number;
14
+ }
15
+
16
+ export const MeshShape = {
17
+ Box: 0,
18
+ Sphere: 1,
19
+ Plane: 2,
20
+ } as const;
21
+
22
+ export function createGeometry(shape: number): MeshData {
23
+ switch (shape) {
24
+ case MeshShape.Sphere:
25
+ return createSphere();
26
+ case MeshShape.Plane:
27
+ return createPlane();
28
+ default:
29
+ return createBox();
30
+ }
31
+ }
32
+
33
+ export const MeshColors = {
34
+ data: new Float32Array(MAX_ENTITIES * 4),
35
+ };
36
+
37
+ export const MeshSizes = {
38
+ data: new Float32Array(MAX_ENTITIES * 4),
39
+ };
40
+
41
+ interface ColorProxy extends Array<number>, FieldAccessor {}
42
+
43
+ function colorProxy(): ColorProxy {
44
+ const data = MeshColors.data;
45
+
46
+ function getValue(eid: number): number {
47
+ const offset = eid * 4;
48
+ const r = Math.round(data[offset] * 255);
49
+ const g = Math.round(data[offset + 1] * 255);
50
+ const b = Math.round(data[offset + 2] * 255);
51
+ return (r << 16) | (g << 8) | b;
52
+ }
53
+
54
+ function setValue(eid: number, value: number): void {
55
+ const offset = eid * 4;
56
+ data[offset] = ((value >> 16) & 0xff) / 255;
57
+ data[offset + 1] = ((value >> 8) & 0xff) / 255;
58
+ data[offset + 2] = (value & 0xff) / 255;
59
+ data[offset + 3] = 1;
60
+ }
61
+
62
+ return new Proxy([] as unknown as ColorProxy, {
63
+ get(_, prop) {
64
+ if (prop === "get") return getValue;
65
+ if (prop === "set") return setValue;
66
+ const eid = Number(prop);
67
+ if (Number.isNaN(eid)) return undefined;
68
+ return getValue(eid);
69
+ },
70
+ set(_, prop, value) {
71
+ const eid = Number(prop);
72
+ if (Number.isNaN(eid)) return false;
73
+ setValue(eid, value);
74
+ return true;
75
+ },
76
+ });
77
+ }
78
+
79
+ interface SizeProxy extends Array<number>, FieldAccessor {}
80
+
81
+ function sizeProxy(component: number): SizeProxy {
82
+ const data = MeshSizes.data;
83
+
84
+ function getValue(eid: number): number {
85
+ return data[eid * 4 + component];
86
+ }
87
+
88
+ function setValue(eid: number, value: number): void {
89
+ data[eid * 4 + component] = value;
90
+ }
91
+
92
+ return new Proxy([] as unknown as SizeProxy, {
93
+ get(_, prop) {
94
+ if (prop === "get") return getValue;
95
+ if (prop === "set") return setValue;
96
+ const eid = Number(prop);
97
+ if (Number.isNaN(eid)) return undefined;
98
+ return getValue(eid);
99
+ },
100
+ set(_, prop, value) {
101
+ const eid = Number(prop);
102
+ if (Number.isNaN(eid)) return false;
103
+ setValue(eid, value);
104
+ return true;
105
+ },
106
+ });
107
+ }
108
+
109
+ export const Mesh: {
110
+ shape: number[];
111
+ color: ColorProxy;
112
+ sizeX: SizeProxy;
113
+ sizeY: SizeProxy;
114
+ sizeZ: SizeProxy;
115
+ } = {
116
+ shape: [],
117
+ color: colorProxy(),
118
+ sizeX: sizeProxy(0),
119
+ sizeY: sizeProxy(1),
120
+ sizeZ: sizeProxy(2),
121
+ };
122
+
123
+ setTraits(Mesh, {
124
+ defaults: () => ({
125
+ shape: MeshShape.Box,
126
+ color: 0xffffff,
127
+ sizeX: 1,
128
+ sizeY: 1,
129
+ sizeZ: 1,
130
+ }),
131
+ accessors: {
132
+ color: Mesh.color,
133
+ sizeX: Mesh.sizeX,
134
+ sizeY: Mesh.sizeY,
135
+ sizeZ: Mesh.sizeZ,
136
+ },
137
+ });
138
+
139
+ export interface MeshBuffers {
140
+ vertex: GPUBuffer;
141
+ index: GPUBuffer;
142
+ indexCount: number;
143
+ }
144
+
145
+ export function createMeshBuffers(device: GPUDevice, mesh: MeshData): MeshBuffers {
146
+ const vertex = device.createBuffer({
147
+ label: "vertex",
148
+ size: mesh.vertices.byteLength,
149
+ usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
150
+ });
151
+ device.queue.writeBuffer(vertex, 0, mesh.vertices);
152
+
153
+ const index = device.createBuffer({
154
+ label: "index",
155
+ size: mesh.indices.byteLength,
156
+ usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
157
+ });
158
+ device.queue.writeBuffer(index, 0, mesh.indices);
159
+
160
+ return { vertex, index, indexCount: mesh.indexCount };
161
+ }
162
+
163
+ export function collectByShape(entities: Iterable<number>): Map<number, number[]> {
164
+ const byShape = new Map<number, number[]>();
165
+ for (const eid of entities) {
166
+ const shape = Mesh.shape[eid];
167
+ let list = byShape.get(shape);
168
+ if (!list) {
169
+ list = [];
170
+ byShape.set(shape, list);
171
+ }
172
+ list.push(eid);
173
+ }
174
+ return byShape;
175
+ }
176
+
177
+ export interface ShapeBatch {
178
+ index: number;
179
+ buffers: MeshBuffers;
180
+ entityIds: GPUBuffer;
181
+ count: number;
182
+ }
183
+
184
+ export interface BatchState {
185
+ batches: Map<number, ShapeBatch>;
186
+ buffers: Map<number, MeshBuffers>;
187
+ }
188
+
189
+ export function updateBatches(
190
+ device: GPUDevice,
191
+ byShape: Map<number, number[]>,
192
+ state: BatchState,
193
+ indirect: GPUBuffer
194
+ ): void {
195
+ for (const [shape, entities] of byShape) {
196
+ let batch = state.batches.get(shape);
197
+ if (!batch) {
198
+ let buffers = state.buffers.get(shape);
199
+ if (!buffers) {
200
+ buffers = createMeshBuffers(device, createGeometry(shape));
201
+ state.buffers.set(shape, buffers);
202
+ }
203
+ batch = {
204
+ index: state.batches.size,
205
+ buffers,
206
+ entityIds: createEntityIdBuffer(device, MAX_ENTITIES),
207
+ count: 0,
208
+ };
209
+ state.batches.set(shape, batch);
210
+ }
211
+
212
+ device.queue.writeBuffer(batch.entityIds, 0, new Uint32Array(entities));
213
+ batch.count = entities.length;
214
+
215
+ writeIndirect(device, indirect, batch.index, {
216
+ indexCount: batch.buffers.indexCount,
217
+ instanceCount: entities.length,
218
+ firstIndex: 0,
219
+ baseVertex: 0,
220
+ firstInstance: 0,
221
+ });
222
+ }
223
+ }
224
+
225
+ export { createBox } from "./box";
226
+ export { createSphere } from "./sphere";
227
+ export { createPlane } from "./plane";
@@ -0,0 +1,11 @@
1
+ import type { MeshData } from "./index";
2
+
3
+ export function createPlane(): MeshData {
4
+ const vertices = new Float32Array([
5
+ -0.5, 0, 0.5, 0, 1, 0, 0.5, 0, 0.5, 0, 1, 0, 0.5, 0, -0.5, 0, 1, 0, -0.5, 0, -0.5, 0, 1, 0,
6
+ ]);
7
+
8
+ const indices = new Uint16Array([0, 1, 2, 0, 2, 3]);
9
+
10
+ return { vertices, indices, vertexCount: 4, indexCount: 6 };
11
+ }
@@ -0,0 +1,40 @@
1
+ import type { MeshData } from "./index";
2
+
3
+ export function createSphere(segments = 32, rings = 16): MeshData {
4
+ const vertices: number[] = [];
5
+ const indices: number[] = [];
6
+ const radius = 0.5;
7
+
8
+ for (let y = 0; y <= rings; y++) {
9
+ const v = y / rings;
10
+ const theta = v * Math.PI;
11
+
12
+ for (let x = 0; x <= segments; x++) {
13
+ const u = x / segments;
14
+ const phi = u * Math.PI * 2;
15
+
16
+ const nx = Math.sin(theta) * Math.cos(phi);
17
+ const ny = Math.cos(theta);
18
+ const nz = Math.sin(theta) * Math.sin(phi);
19
+
20
+ vertices.push(nx * radius, ny * radius, nz * radius, nx, ny, nz);
21
+ }
22
+ }
23
+
24
+ for (let y = 0; y < rings; y++) {
25
+ for (let x = 0; x < segments; x++) {
26
+ const a = y * (segments + 1) + x;
27
+ const b = a + segments + 1;
28
+
29
+ indices.push(a, a + 1, b);
30
+ indices.push(a + 1, b + 1, b);
31
+ }
32
+ }
33
+
34
+ return {
35
+ vertices: new Float32Array(vertices),
36
+ indices: new Uint16Array(indices),
37
+ vertexCount: (rings + 1) * (segments + 1),
38
+ indexCount: rings * segments * 6,
39
+ };
40
+ }
@@ -0,0 +1,235 @@
1
+ import type { ComputeNode, ExecutionContext } from "../compute";
2
+
3
+ const shader = /* wgsl */ `
4
+ struct VertexOutput {
5
+ @builtin(position) position: vec4f,
6
+ @location(0) uv: vec2f,
7
+ }
8
+
9
+ @vertex
10
+ fn vertexMain(@builtin(vertex_index) vertexIndex: u32) -> VertexOutput {
11
+ var positions = array<vec2f, 3>(
12
+ vec2f(-1.0, -1.0),
13
+ vec2f(3.0, -1.0),
14
+ vec2f(-1.0, 3.0)
15
+ );
16
+
17
+ let pos = positions[vertexIndex];
18
+
19
+ var output: VertexOutput;
20
+ output.position = vec4f(pos, 0.0, 1.0);
21
+ output.uv = (pos + 1.0) * 0.5;
22
+ output.uv.y = 1.0 - output.uv.y;
23
+ return output;
24
+ }
25
+
26
+ @group(0) @binding(0) var inputTexture: texture_2d<f32>;
27
+ @group(0) @binding(1) var inputSampler: sampler;
28
+
29
+ struct Uniforms {
30
+ exposure: f32,
31
+ vignetteStrength: f32,
32
+ vignetteInner: f32,
33
+ vignetteOuter: f32,
34
+ texelSizeX: f32,
35
+ texelSizeY: f32,
36
+ flags: u32,
37
+ _pad: u32,
38
+ }
39
+
40
+ @group(0) @binding(2) var<uniform> uniforms: Uniforms;
41
+
42
+ const FLAG_TONEMAP: u32 = 1u;
43
+ const FLAG_FXAA: u32 = 2u;
44
+ const FLAG_VIGNETTE: u32 = 4u;
45
+
46
+ fn aces(x: vec3f) -> vec3f {
47
+ let a = 2.51;
48
+ let b = 0.03;
49
+ let c = 2.43;
50
+ let d = 0.59;
51
+ let e = 0.14;
52
+ return saturate((x * (a * x + b)) / (x * (c * x + d) + e));
53
+ }
54
+
55
+ fn luma(color: vec3f) -> f32 {
56
+ return dot(color, vec3f(0.299, 0.587, 0.114));
57
+ }
58
+
59
+ const FXAA_REDUCE_MIN: f32 = 1.0 / 128.0;
60
+ const FXAA_REDUCE_MUL: f32 = 1.0 / 8.0;
61
+ const FXAA_SPAN_MAX: f32 = 8.0;
62
+
63
+ fn applyFXAA(uv: vec2f, colorM: vec3f) -> vec3f {
64
+ let texelSize = vec2f(uniforms.texelSizeX, uniforms.texelSizeY);
65
+
66
+ let colorNW = textureSample(inputTexture, inputSampler, uv + vec2f(-1.0, -1.0) * texelSize).rgb;
67
+ let colorNE = textureSample(inputTexture, inputSampler, uv + vec2f(1.0, -1.0) * texelSize).rgb;
68
+ let colorSW = textureSample(inputTexture, inputSampler, uv + vec2f(-1.0, 1.0) * texelSize).rgb;
69
+ let colorSE = textureSample(inputTexture, inputSampler, uv + vec2f(1.0, 1.0) * texelSize).rgb;
70
+
71
+ let lumaM = luma(colorM);
72
+ let lumaNW = luma(colorNW);
73
+ let lumaNE = luma(colorNE);
74
+ let lumaSW = luma(colorSW);
75
+ let lumaSE = luma(colorSE);
76
+
77
+ let lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));
78
+ let lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));
79
+
80
+ var dir: vec2f;
81
+ dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));
82
+ dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));
83
+
84
+ let dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * 0.25 * FXAA_REDUCE_MUL, FXAA_REDUCE_MIN);
85
+ let rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);
86
+ dir = clamp(dir * rcpDirMin, vec2f(-FXAA_SPAN_MAX), vec2f(FXAA_SPAN_MAX)) * texelSize;
87
+
88
+ let colorA = 0.5 * (
89
+ textureSample(inputTexture, inputSampler, uv + dir * (1.0 / 3.0 - 0.5)).rgb +
90
+ textureSample(inputTexture, inputSampler, uv + dir * (2.0 / 3.0 - 0.5)).rgb
91
+ );
92
+
93
+ let colorB = colorA * 0.5 + 0.25 * (
94
+ textureSample(inputTexture, inputSampler, uv + dir * -0.5).rgb +
95
+ textureSample(inputTexture, inputSampler, uv + dir * 0.5).rgb
96
+ );
97
+
98
+ let lumaB = luma(colorB);
99
+
100
+ if lumaB < lumaMin || lumaB > lumaMax {
101
+ return colorA;
102
+ }
103
+ return colorB;
104
+ }
105
+
106
+ fn applyVignette(color: vec3f, uv: vec2f) -> vec3f {
107
+ let center = vec2f(0.5, 0.5);
108
+ let dist = distance(uv, center);
109
+ let vignette = 1.0 - smoothstep(uniforms.vignetteInner, uniforms.vignetteOuter, dist) * uniforms.vignetteStrength;
110
+ return color * vignette;
111
+ }
112
+
113
+ @fragment
114
+ fn fragmentMain(@location(0) uv: vec2f) -> @location(0) vec4f {
115
+ var color = textureSample(inputTexture, inputSampler, uv).rgb;
116
+
117
+ if (uniforms.flags & FLAG_FXAA) != 0u {
118
+ color = applyFXAA(uv, color);
119
+ }
120
+
121
+ if (uniforms.flags & FLAG_TONEMAP) != 0u {
122
+ color = aces(color * uniforms.exposure);
123
+ }
124
+
125
+ if (uniforms.flags & FLAG_VIGNETTE) != 0u {
126
+ color = applyVignette(color, uv);
127
+ }
128
+
129
+ return vec4f(color, 1.0);
130
+ }
131
+ `;
132
+
133
+ const FLAG_TONEMAP = 1;
134
+ const FLAG_FXAA = 2;
135
+ const FLAG_VIGNETTE = 4;
136
+
137
+ export interface PostProcessUniforms {
138
+ tonemap: boolean;
139
+ exposure: number;
140
+ fxaa: boolean;
141
+ vignetteStrength: number;
142
+ vignetteInner: number;
143
+ vignetteOuter: number;
144
+ }
145
+
146
+ export function createPostProcessNode(uniforms: PostProcessUniforms): ComputeNode {
147
+ let pipeline: GPURenderPipeline | null = null;
148
+ let uniformBuffer: GPUBuffer | null = null;
149
+ let sampler: GPUSampler | null = null;
150
+
151
+ return {
152
+ id: "postprocess",
153
+ phase: "postprocess",
154
+ inputs: [{ id: "scene", access: "read" }],
155
+ outputs: [{ id: "framebuffer", access: "write" }],
156
+
157
+ execute(ctx: ExecutionContext) {
158
+ const { device, encoder, canvasView, format, context } = ctx;
159
+ const width = context.canvas.width;
160
+ const height = context.canvas.height;
161
+ const sceneView = ctx.getTextureView("scene")!;
162
+
163
+ if (!pipeline) {
164
+ const module = device.createShaderModule({ code: shader });
165
+ pipeline = device.createRenderPipeline({
166
+ layout: "auto",
167
+ vertex: { module, entryPoint: "vertexMain" },
168
+ fragment: {
169
+ module,
170
+ entryPoint: "fragmentMain",
171
+ targets: [{ format }],
172
+ },
173
+ primitive: { topology: "triangle-list" },
174
+ });
175
+ }
176
+
177
+ if (!uniformBuffer) {
178
+ uniformBuffer = device.createBuffer({
179
+ size: 32,
180
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
181
+ });
182
+ }
183
+
184
+ if (!sampler) {
185
+ sampler = device.createSampler({
186
+ magFilter: "linear",
187
+ minFilter: "linear",
188
+ });
189
+ }
190
+
191
+ let flags = 0;
192
+ if (uniforms.tonemap) flags |= FLAG_TONEMAP;
193
+ if (uniforms.fxaa) flags |= FLAG_FXAA;
194
+ if (uniforms.vignetteStrength > 0) flags |= FLAG_VIGNETTE;
195
+
196
+ const data = new ArrayBuffer(32);
197
+ const floats = new Float32Array(data);
198
+ const uints = new Uint32Array(data);
199
+ floats[0] = uniforms.exposure;
200
+ floats[1] = uniforms.vignetteStrength;
201
+ floats[2] = uniforms.vignetteInner;
202
+ floats[3] = uniforms.vignetteOuter;
203
+ floats[4] = 1.0 / width;
204
+ floats[5] = 1.0 / height;
205
+ uints[6] = flags;
206
+
207
+ device.queue.writeBuffer(uniformBuffer, 0, data);
208
+
209
+ const bindGroup = device.createBindGroup({
210
+ layout: pipeline.getBindGroupLayout(0),
211
+ entries: [
212
+ { binding: 0, resource: sceneView },
213
+ { binding: 1, resource: sampler },
214
+ { binding: 2, resource: { buffer: uniformBuffer } },
215
+ ],
216
+ });
217
+
218
+ const pass = encoder.beginRenderPass({
219
+ colorAttachments: [
220
+ {
221
+ view: canvasView,
222
+ loadOp: "clear" as const,
223
+ storeOp: "store" as const,
224
+ clearValue: { r: 0, g: 0, b: 0, a: 1 },
225
+ },
226
+ ],
227
+ });
228
+
229
+ pass.setPipeline(pipeline);
230
+ pass.setBindGroup(0, bindGroup);
231
+ pass.draw(3);
232
+ pass.end();
233
+ },
234
+ };
235
+ }
@@ -0,0 +1,116 @@
1
+ export const SCENE_UNIFORM_SIZE = 176; // viewProj (64) + cameraWorld (64) + ambient (16) + sunDir (16) + sunColor (16)
2
+ export const DEPTH_FORMAT: GPUTextureFormat = "depth24plus";
3
+
4
+ export function createSceneBuffer(device: GPUDevice): GPUBuffer {
5
+ return device.createBuffer({
6
+ label: "scene",
7
+ size: SCENE_UNIFORM_SIZE,
8
+ usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC,
9
+ });
10
+ }
11
+
12
+ export function ensureRenderTextures(
13
+ device: GPUDevice,
14
+ format: GPUTextureFormat,
15
+ width: number,
16
+ height: number,
17
+ textures: Map<string, GPUTexture>,
18
+ textureViews: Map<string, GPUTextureView>
19
+ ): void {
20
+ const existing = textures.get("scene");
21
+ if (existing && existing.width === width && existing.height === height) return;
22
+
23
+ existing?.destroy();
24
+ textures.get("depth")?.destroy();
25
+
26
+ const scene = device.createTexture({
27
+ label: "scene",
28
+ size: { width, height },
29
+ format,
30
+ usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING,
31
+ });
32
+
33
+ const depth = device.createTexture({
34
+ label: "depth",
35
+ size: { width, height },
36
+ format: DEPTH_FORMAT,
37
+ usage: GPUTextureUsage.RENDER_ATTACHMENT,
38
+ });
39
+
40
+ textures.set("scene", scene);
41
+ textureViews.set("scene", scene.createView());
42
+ textures.set("depth", depth);
43
+ textureViews.set("depth", depth.createView());
44
+ }
45
+
46
+ export function perspective(fov: number, aspect: number, near: number, far: number): Float32Array {
47
+ const f = 1 / Math.tan((fov * Math.PI) / 360);
48
+ const nf = 1 / (near - far);
49
+ return new Float32Array([
50
+ f / aspect,
51
+ 0,
52
+ 0,
53
+ 0,
54
+ 0,
55
+ f,
56
+ 0,
57
+ 0,
58
+ 0,
59
+ 0,
60
+ (far + near) * nf,
61
+ -1,
62
+ 0,
63
+ 0,
64
+ 2 * far * near * nf,
65
+ 0,
66
+ ]);
67
+ }
68
+
69
+ export function multiply(a: Float32Array, b: Float32Array): Float32Array {
70
+ const out = new Float32Array(16);
71
+ for (let i = 0; i < 4; i++) {
72
+ for (let j = 0; j < 4; j++) {
73
+ out[j * 4 + i] =
74
+ a[i] * b[j * 4] +
75
+ a[i + 4] * b[j * 4 + 1] +
76
+ a[i + 8] * b[j * 4 + 2] +
77
+ a[i + 12] * b[j * 4 + 3];
78
+ }
79
+ }
80
+ return out;
81
+ }
82
+
83
+ export function invert(m: Float32Array): Float32Array {
84
+ const out = new Float32Array(16);
85
+ const r00 = m[0],
86
+ r01 = m[1],
87
+ r02 = m[2];
88
+ const r10 = m[4],
89
+ r11 = m[5],
90
+ r12 = m[6];
91
+ const r20 = m[8],
92
+ r21 = m[9],
93
+ r22 = m[10];
94
+ const tx = m[12],
95
+ ty = m[13],
96
+ tz = m[14];
97
+
98
+ out[0] = r00;
99
+ out[1] = r10;
100
+ out[2] = r20;
101
+ out[3] = 0;
102
+ out[4] = r01;
103
+ out[5] = r11;
104
+ out[6] = r21;
105
+ out[7] = 0;
106
+ out[8] = r02;
107
+ out[9] = r12;
108
+ out[10] = r22;
109
+ out[11] = 0;
110
+ out[12] = -(r00 * tx + r01 * ty + r02 * tz);
111
+ out[13] = -(r10 * tx + r11 * ty + r12 * tz);
112
+ out[14] = -(r20 * tx + r21 * ty + r22 * tz);
113
+ out[15] = 1;
114
+
115
+ return out;
116
+ }