wgpu-kit 0.9.11 → 1.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.
- package/README.md +7 -0
- package/README.zh-CN.md +3 -3
- package/dist/core/buffer.d.ts +1 -1
- package/dist/core/buffer.js +109 -0
- package/dist/core/context.d.ts +2 -0
- package/dist/core/context.js +62 -0
- package/dist/core/errors.js +42 -0
- package/dist/core/kernel.js +197 -0
- package/dist/core/layout.d.ts +5 -1
- package/dist/core/layout.js +69 -0
- package/dist/core/pingpong.js +61 -0
- package/dist/core/raw.js +38 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +11 -1510
- package/dist/interop/three.js +29 -0
- package/dist/media.js +70 -0
- package/dist/observe.d.ts +19 -0
- package/dist/observe.js +76 -0
- package/dist/packs/fields/index.js +230 -0
- package/dist/packs/image/index.js +250 -0
- package/dist/packs/life/boids.js +367 -0
- package/dist/packs/life/index.js +8 -0
- package/dist/packs/life/map.js +110 -0
- package/dist/packs/life/physarum.js +228 -0
- package/dist/packs/life/tentacles.js +238 -0
- package/dist/packs/life/turing.js +203 -0
- package/dist/packs/particles/config.d.ts +1 -0
- package/dist/packs/particles/config.js +36 -0
- package/dist/packs/particles/grid.js +248 -0
- package/dist/packs/particles/index.js +361 -0
- package/dist/packs/particles/presets.js +75 -0
- package/dist/packs/particles/render.js +116 -0
- package/dist/packs/particles/wgsl.js +175 -0
- package/dist/react/index.js +36 -0
- package/dist/vite.js +27 -28
- package/package.json +1 -1
- package/dist/fields.js +0 -585
- package/dist/image.js +0 -318
- package/dist/life.js +0 -1407
- package/dist/particles.js +0 -1245
- package/dist/react.js +0 -1289
- package/dist/three.js +0 -33
package/dist/life.js
DELETED
|
@@ -1,1407 +0,0 @@
|
|
|
1
|
-
// src/core/errors.ts
|
|
2
|
-
var WgpuKitError = class extends Error {
|
|
3
|
-
constructor(message) {
|
|
4
|
-
super(message);
|
|
5
|
-
this.name = new.target.name;
|
|
6
|
-
}
|
|
7
|
-
};
|
|
8
|
-
var WebGPUUnavailableError = class extends WgpuKitError {
|
|
9
|
-
constructor(reason) {
|
|
10
|
-
super(
|
|
11
|
-
`\u5F53\u524D\u73AF\u5883\u4E0D\u53EF\u7528 WebGPU: ${reason}
|
|
12
|
-
\u6392\u67E5:\u2460 \u6D4F\u89C8\u5668\u9700 Chrome/Edge 113+ \u6216 Safari 18+;\u2461 \u65E0\u5934\u73AF\u5883\u9700\u5F00\u542F WebGPU;\u2462 \u68C0\u67E5 GPU \u9A71\u52A8\u4E0E\u786C\u4EF6\u52A0\u901F\u8BBE\u7F6E\u3002
|
|
13
|
-
\u53EF\u7528 navigator.gpu \u662F\u5426\u5B58\u5728\u5FEB\u901F\u5224\u65AD\u3002`
|
|
14
|
-
);
|
|
15
|
-
}
|
|
16
|
-
};
|
|
17
|
-
var CompileError = class extends WgpuKitError {
|
|
18
|
-
constructor(kernelName, messages, userCodeOffset) {
|
|
19
|
-
const mapped = messages.map((m) => {
|
|
20
|
-
const userLine = m.line - userCodeOffset;
|
|
21
|
-
const where = userLine > 0 ? `\u7528\u6237\u4EE3\u7801\u7B2C ${userLine} \u884C` : `\u751F\u6210\u4EE3\u7801\u7B2C ${m.line} \u884C(\u5E93\u7684\u95EE\u9898,\u6B22\u8FCE\u62A5 issue)`;
|
|
22
|
-
return ` ${where}: ${m.msg}`;
|
|
23
|
-
}).join("\n");
|
|
24
|
-
super(`kernel "${kernelName}" WGSL \u7F16\u8BD1\u5931\u8D25:
|
|
25
|
-
${mapped}`);
|
|
26
|
-
}
|
|
27
|
-
};
|
|
28
|
-
var UsageError = class extends WgpuKitError {
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
// src/core/context.ts
|
|
32
|
-
var GpuContext = class _GpuContext {
|
|
33
|
-
device;
|
|
34
|
-
adapterInfo;
|
|
35
|
-
constructor(device, adapterInfo) {
|
|
36
|
-
this.device = device;
|
|
37
|
-
this.adapterInfo = adapterInfo;
|
|
38
|
-
}
|
|
39
|
-
static #singleton = null;
|
|
40
|
-
static get() {
|
|
41
|
-
if (!_GpuContext.#singleton) {
|
|
42
|
-
_GpuContext.#singleton = _GpuContext.#create().catch((e) => {
|
|
43
|
-
_GpuContext.#singleton = null;
|
|
44
|
-
throw e;
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
return _GpuContext.#singleton;
|
|
48
|
-
}
|
|
49
|
-
static async #create() {
|
|
50
|
-
if (typeof navigator === "undefined" || !("gpu" in navigator) || !navigator.gpu) {
|
|
51
|
-
throw new WebGPUUnavailableError("navigator.gpu \u4E0D\u5B58\u5728");
|
|
52
|
-
}
|
|
53
|
-
const adapter = await navigator.gpu.requestAdapter({ powerPreference: "high-performance" });
|
|
54
|
-
if (!adapter) throw new WebGPUUnavailableError("requestAdapter() \u8FD4\u56DE null");
|
|
55
|
-
const info = adapter.info;
|
|
56
|
-
const label = info ? [info.vendor, info.architecture, info.description].filter(Boolean).join(" / ") || "unknown" : "unknown";
|
|
57
|
-
const requiredLimits = {};
|
|
58
|
-
const want = [
|
|
59
|
-
"maxStorageBuffersPerShaderStage",
|
|
60
|
-
"maxStorageBuffersInVertexStage",
|
|
61
|
-
"maxStorageBufferBindingSize"
|
|
62
|
-
];
|
|
63
|
-
for (const key of want) {
|
|
64
|
-
const supported = adapter.limits[key];
|
|
65
|
-
if (typeof supported === "number") requiredLimits[key] = supported;
|
|
66
|
-
}
|
|
67
|
-
const device = await adapter.requestDevice({ label: "wgpu-kit", requiredLimits });
|
|
68
|
-
return new _GpuContext(device, label);
|
|
69
|
-
}
|
|
70
|
-
/** device lost 时 reject;调用方可 await 做清理/提示 */
|
|
71
|
-
get lost() {
|
|
72
|
-
return this.device.lost;
|
|
73
|
-
}
|
|
74
|
-
/** 等待队列中已提交的全部 GPU 工作完成(测试/读回前同步用) */
|
|
75
|
-
async sync() {
|
|
76
|
-
await this.device.queue.onSubmittedWorkDone();
|
|
77
|
-
}
|
|
78
|
-
};
|
|
79
|
-
|
|
80
|
-
// src/core/layout.ts
|
|
81
|
-
var TYPES = {
|
|
82
|
-
f32: { size: 4, align: 4, comps: 1, typed: "Float32Array", wgsl: "f32" },
|
|
83
|
-
i32: { size: 4, align: 4, comps: 1, typed: "Int32Array", wgsl: "i32" },
|
|
84
|
-
u32: { size: 4, align: 4, comps: 1, typed: "Uint32Array", wgsl: "u32" },
|
|
85
|
-
vec2f: { size: 8, align: 8, comps: 2, typed: "Float32Array", wgsl: "vec2f" },
|
|
86
|
-
vec2i: { size: 8, align: 8, comps: 2, typed: "Int32Array", wgsl: "vec2i" },
|
|
87
|
-
vec2u: { size: 8, align: 8, comps: 2, typed: "Uint32Array", wgsl: "vec2u" },
|
|
88
|
-
vec3f: { size: 12, align: 16, comps: 3, typed: "Float32Array", wgsl: "vec3f" },
|
|
89
|
-
vec4f: { size: 16, align: 16, comps: 4, typed: "Float32Array", wgsl: "vec4f" }
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
// src/core/buffer.ts
|
|
93
|
-
var TYPED_CTORS = {
|
|
94
|
-
Float32Array,
|
|
95
|
-
Int32Array,
|
|
96
|
-
Uint32Array
|
|
97
|
-
};
|
|
98
|
-
var Buffer = class _Buffer {
|
|
99
|
-
kind;
|
|
100
|
-
length;
|
|
101
|
-
gpuBuffer;
|
|
102
|
-
#ctx;
|
|
103
|
-
#byteLength;
|
|
104
|
-
#staging = null;
|
|
105
|
-
constructor(ctx, kind, length, gpuBuffer) {
|
|
106
|
-
this.#ctx = ctx;
|
|
107
|
-
this.kind = kind;
|
|
108
|
-
this.length = length;
|
|
109
|
-
this.gpuBuffer = gpuBuffer;
|
|
110
|
-
this.#byteLength = length * TYPES[kind].size;
|
|
111
|
-
}
|
|
112
|
-
static async create(kind, length) {
|
|
113
|
-
if (!Number.isInteger(length) || length <= 0) {
|
|
114
|
-
throw new UsageError(`Buffer \u957F\u5EA6\u5FC5\u987B\u662F\u6B63\u6574\u6570,\u6536\u5230: ${String(length)}`);
|
|
115
|
-
}
|
|
116
|
-
const def = TYPES[kind];
|
|
117
|
-
if (!def) throw new UsageError(`\u672A\u77E5\u7C7B\u578B "${String(kind)}",\u53EF\u7528: ${Object.keys(TYPES).join(", ")}`);
|
|
118
|
-
const ctx = await GpuContext.get();
|
|
119
|
-
const gpuBuffer = ctx.device.createBuffer({
|
|
120
|
-
size: length * def.size,
|
|
121
|
-
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST,
|
|
122
|
-
label: `wgpu-kit Buffer<${kind}>[${length}]`
|
|
123
|
-
});
|
|
124
|
-
return new _Buffer(ctx, kind, length, gpuBuffer);
|
|
125
|
-
}
|
|
126
|
-
/** 校验并写入(CPU → GPU) */
|
|
127
|
-
write(data) {
|
|
128
|
-
const ctor = TYPED_CTORS[TYPES[this.kind].typed];
|
|
129
|
-
if (!(data instanceof ctor)) {
|
|
130
|
-
throw new UsageError(`Buffer<${this.kind}>.write \u9700\u8981 ${TYPES[this.kind].typed},\u6536\u5230 ${data.constructor?.name ?? typeof data}`);
|
|
131
|
-
}
|
|
132
|
-
const expected = this.length * TYPES[this.kind].comps;
|
|
133
|
-
if (data.length !== expected) {
|
|
134
|
-
throw new UsageError(`Buffer<${this.kind}>[${this.length}].write \u9700\u8981 ${expected} \u4E2A\u5206\u91CF,\u6536\u5230 ${data.length}`);
|
|
135
|
-
}
|
|
136
|
-
this.#ctx.device.queue.writeBuffer(this.gpuBuffer, 0, data);
|
|
137
|
-
}
|
|
138
|
-
/** GPU → CPU:内部 staging buffer + mapAsync,mapAsync 的异步陷阱由库承担 */
|
|
139
|
-
async read() {
|
|
140
|
-
const def = TYPES[this.kind];
|
|
141
|
-
if (!this.#staging) {
|
|
142
|
-
this.#staging = this.#ctx.device.createBuffer({
|
|
143
|
-
size: this.#byteLength,
|
|
144
|
-
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
|
|
145
|
-
label: `wgpu-kit staging[${this.length}]`
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
const enc = this.#ctx.device.createCommandEncoder();
|
|
149
|
-
enc.copyBufferToBuffer(this.gpuBuffer, 0, this.#staging, 0, this.#byteLength);
|
|
150
|
-
this.#ctx.device.queue.submit([enc.finish()]);
|
|
151
|
-
await this.#staging.mapAsync(GPUMapMode.READ);
|
|
152
|
-
const ab = this.#staging.getMappedRange().slice(0);
|
|
153
|
-
this.#staging.unmap();
|
|
154
|
-
if (def.typed === "Float32Array") return new Float32Array(ab);
|
|
155
|
-
if (def.typed === "Int32Array") return new Int32Array(ab);
|
|
156
|
-
return new Uint32Array(ab);
|
|
157
|
-
}
|
|
158
|
-
destroy() {
|
|
159
|
-
if (this.#staging) {
|
|
160
|
-
this.#staging.destroy();
|
|
161
|
-
this.#staging = null;
|
|
162
|
-
}
|
|
163
|
-
this.gpuBuffer.destroy();
|
|
164
|
-
}
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
// src/core/pingpong.ts
|
|
168
|
-
var PingPong = class _PingPong {
|
|
169
|
-
#sides;
|
|
170
|
-
#names;
|
|
171
|
-
#length;
|
|
172
|
-
#kinds;
|
|
173
|
-
#index = 0;
|
|
174
|
-
constructor(names, kinds, length, a, b) {
|
|
175
|
-
this.#names = names;
|
|
176
|
-
this.#kinds = kinds;
|
|
177
|
-
this.#length = length;
|
|
178
|
-
this.#sides = [a, b];
|
|
179
|
-
}
|
|
180
|
-
static async create(kinds, length) {
|
|
181
|
-
const names = Object.keys(kinds);
|
|
182
|
-
if (names.length === 0) throw new Error("PingPong \u81F3\u5C11\u9700\u8981\u4E00\u4E2A\u5B57\u6BB5");
|
|
183
|
-
const make = async () => {
|
|
184
|
-
const side = {};
|
|
185
|
-
for (const name of names) side[name] = await Buffer.create(kinds[name], length);
|
|
186
|
-
return side;
|
|
187
|
-
};
|
|
188
|
-
return new _PingPong(names, kinds, length, await make(), await make());
|
|
189
|
-
}
|
|
190
|
-
/** 当前帧的数据侧(渲染/读回用) */
|
|
191
|
-
get current() {
|
|
192
|
-
return this.#sides[this.#index];
|
|
193
|
-
}
|
|
194
|
-
/** 另一侧(kernel 写入目标) */
|
|
195
|
-
get other() {
|
|
196
|
-
return this.#sides[1 - this.#index];
|
|
197
|
-
}
|
|
198
|
-
/** 帧末翻转 */
|
|
199
|
-
swap() {
|
|
200
|
-
this.#index = 1 - this.#index;
|
|
201
|
-
}
|
|
202
|
-
/** 以 (写侧, 读侧) 调用 fn 后自动 swap 的语法糖 */
|
|
203
|
-
async runWith(fn) {
|
|
204
|
-
await fn(this.other, this.current);
|
|
205
|
-
this.swap();
|
|
206
|
-
}
|
|
207
|
-
destroy() {
|
|
208
|
-
for (const side of this.#sides) for (const b of Object.values(side)) b.destroy();
|
|
209
|
-
}
|
|
210
|
-
/** 克隆一份同构 PingPong(同字段同长度) */
|
|
211
|
-
async clone() {
|
|
212
|
-
return _PingPong.create(this.#kinds, this.#length);
|
|
213
|
-
}
|
|
214
|
-
get names() {
|
|
215
|
-
return this.#names;
|
|
216
|
-
}
|
|
217
|
-
};
|
|
218
|
-
|
|
219
|
-
// src/packs/life/map.ts
|
|
220
|
-
var COLORMAPS = {
|
|
221
|
-
mono: "vec3f(v)",
|
|
222
|
-
amber: `vec3f(
|
|
223
|
-
1.35 * v * v,
|
|
224
|
-
0.9 * v * v * v + 0.25 * v * (1.0 - v),
|
|
225
|
-
0.15 * v * v * v
|
|
226
|
-
)`,
|
|
227
|
-
ice: `vec3f(0.15 * v * v, 0.55 * v * v + 0.2 * v, 1.1 * v)`,
|
|
228
|
-
duotone: `mix(vec3f(0.02, 0.03, 0.08), vec3f(0.42, 0.78, 1.0), v) + vec3f(0.9, 0.6, 0.25) * v * v * v * 0.6`
|
|
229
|
-
};
|
|
230
|
-
function mapFragment(colormap) {
|
|
231
|
-
return (
|
|
232
|
-
/* wgsl */
|
|
233
|
-
`
|
|
234
|
-
struct VsOut {
|
|
235
|
-
@builtin(position) pos: vec4f,
|
|
236
|
-
@location(0) uv: vec2f,
|
|
237
|
-
};
|
|
238
|
-
@group(0) @binding(0) var<uniform> vp: vec4f; // w, h, maxV, gamma
|
|
239
|
-
@group(0) @binding(1) var<storage, read> map: array<f32>;
|
|
240
|
-
|
|
241
|
-
@vertex
|
|
242
|
-
fn vs(@builtin(vertex_index) v: u32) -> VsOut {
|
|
243
|
-
var p = array<vec2f, 3>(vec2f(-1.0, -1.0), vec2f(3.0, -1.0), vec2f(-1.0, 3.0));
|
|
244
|
-
var out: VsOut;
|
|
245
|
-
out.pos = vec4f(p[v], 0.0, 1.0);
|
|
246
|
-
out.uv = (p[v] + vec2f(1.0)) * 0.5;
|
|
247
|
-
out.uv = vec2f(out.uv.x, 1.0 - out.uv.y); // buffer \u884C 0 = \u753B\u9762\u9876\u90E8
|
|
248
|
-
return out;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
@fragment
|
|
252
|
-
fn fs(in: VsOut) -> @location(0) vec4f {
|
|
253
|
-
let w = u32(vp.x);
|
|
254
|
-
let x = min(u32(in.uv.x * vp.x), w - 1u);
|
|
255
|
-
let y = min(u32(in.uv.y * vp.y), u32(vp.y) - 1u);
|
|
256
|
-
let v = pow(clamp(abs(map[y * w + x]) * vp.z, 0.0, 1.0), vp.w);
|
|
257
|
-
let c = ${COLORMAPS[colormap]};
|
|
258
|
-
return vec4f(c, 1.0);
|
|
259
|
-
}
|
|
260
|
-
`
|
|
261
|
-
);
|
|
262
|
-
}
|
|
263
|
-
var MapRenderer = class _MapRenderer {
|
|
264
|
-
#ctx;
|
|
265
|
-
#gpuCtx;
|
|
266
|
-
#pipeline;
|
|
267
|
-
#uniform;
|
|
268
|
-
#bgCache = /* @__PURE__ */ new Map();
|
|
269
|
-
#ids = /* @__PURE__ */ new WeakMap();
|
|
270
|
-
constructor(ctx, gpuCtx, pipeline, uniform) {
|
|
271
|
-
this.#ctx = ctx;
|
|
272
|
-
this.#gpuCtx = gpuCtx;
|
|
273
|
-
this.#pipeline = pipeline;
|
|
274
|
-
this.#uniform = uniform;
|
|
275
|
-
}
|
|
276
|
-
static async create(canvas, opts) {
|
|
277
|
-
const ctx = await GpuContext.get();
|
|
278
|
-
const gpuCtx = canvas.getContext("webgpu");
|
|
279
|
-
if (!gpuCtx) throw new Error('canvas.getContext("webgpu") \u8FD4\u56DE\u7A7A');
|
|
280
|
-
const format = navigator.gpu.getPreferredCanvasFormat();
|
|
281
|
-
gpuCtx.configure({ device: ctx.device, format, alphaMode: "opaque" });
|
|
282
|
-
const module = ctx.device.createShaderModule({ code: mapFragment(opts.colormap), label: "life-map-render" });
|
|
283
|
-
const info = await module.getCompilationInfo();
|
|
284
|
-
const errors = info.messages.filter((m) => m.type === "error");
|
|
285
|
-
if (errors.length > 0) throw new CompileError("life-map-render", errors.map((m) => ({ line: m.lineNum, msg: m.message })), 0);
|
|
286
|
-
const pipeline = ctx.device.createRenderPipeline({
|
|
287
|
-
layout: "auto",
|
|
288
|
-
vertex: { module, entryPoint: "vs" },
|
|
289
|
-
fragment: { module, entryPoint: "fs", targets: [{ format }] },
|
|
290
|
-
primitive: { topology: "triangle-list" }
|
|
291
|
-
});
|
|
292
|
-
const uniform = ctx.device.createBuffer({ size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
|
293
|
-
ctx.device.queue.writeBuffer(uniform, 0, new Float32Array([opts.width, opts.height, opts.maxV, opts.gamma ?? 1]));
|
|
294
|
-
return new _MapRenderer(ctx, gpuCtx, pipeline, uniform);
|
|
295
|
-
}
|
|
296
|
-
render(map) {
|
|
297
|
-
let id = this.#ids.get(map.gpuBuffer);
|
|
298
|
-
if (id === void 0) {
|
|
299
|
-
id = this.#bgCache.size + 1;
|
|
300
|
-
this.#ids.set(map.gpuBuffer, id);
|
|
301
|
-
}
|
|
302
|
-
let bg = this.#bgCache.get(id);
|
|
303
|
-
if (!bg) {
|
|
304
|
-
bg = this.#ctx.device.createBindGroup({
|
|
305
|
-
layout: this.#pipeline.getBindGroupLayout(0),
|
|
306
|
-
entries: [
|
|
307
|
-
{ binding: 0, resource: { buffer: this.#uniform } },
|
|
308
|
-
{ binding: 1, resource: { buffer: map.gpuBuffer } }
|
|
309
|
-
]
|
|
310
|
-
});
|
|
311
|
-
this.#bgCache.set(id, bg);
|
|
312
|
-
}
|
|
313
|
-
const enc = this.#ctx.device.createCommandEncoder();
|
|
314
|
-
const pass = enc.beginRenderPass({
|
|
315
|
-
colorAttachments: [{
|
|
316
|
-
view: this.#gpuCtx.getCurrentTexture().createView(),
|
|
317
|
-
clearValue: { r: 0, g: 0, b: 0, a: 1 },
|
|
318
|
-
loadOp: "clear",
|
|
319
|
-
storeOp: "store"
|
|
320
|
-
}]
|
|
321
|
-
});
|
|
322
|
-
pass.setPipeline(this.#pipeline);
|
|
323
|
-
pass.setBindGroup(0, bg);
|
|
324
|
-
pass.draw(3);
|
|
325
|
-
pass.end();
|
|
326
|
-
this.#ctx.device.queue.submit([enc.finish()]);
|
|
327
|
-
}
|
|
328
|
-
};
|
|
329
|
-
|
|
330
|
-
// src/packs/particles/presets.ts
|
|
331
|
-
function mulberry32(seed) {
|
|
332
|
-
let s = seed | 0;
|
|
333
|
-
return () => {
|
|
334
|
-
s = s + 1831565813 | 0;
|
|
335
|
-
let t = Math.imul(s ^ s >>> 15, 1 | s);
|
|
336
|
-
t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t;
|
|
337
|
-
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
338
|
-
};
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
// src/packs/life/turing.ts
|
|
342
|
-
var PRESETS = {
|
|
343
|
-
coral: { f: 0.0545, k: 0.062 },
|
|
344
|
-
mitosis: { f: 0.0367, k: 0.0649 },
|
|
345
|
-
spots: { f: 0.03, k: 0.062 },
|
|
346
|
-
waves: { f: 0.014, k: 0.045 }
|
|
347
|
-
};
|
|
348
|
-
var USIZE = 32;
|
|
349
|
-
async function turing(config = {}) {
|
|
350
|
-
const size = config.size ?? 512;
|
|
351
|
-
const preset = config.preset ?? "coral";
|
|
352
|
-
const p = preset === "custom" ? { f: config.feed ?? 0.037, k: config.kill ?? 0.06 } : PRESETS[preset];
|
|
353
|
-
const steps = config.steps ?? 12;
|
|
354
|
-
const seedHash = typeof config.seed === "string" ? hashStr(config.seed) : config.seed ?? 42;
|
|
355
|
-
const colormap = config.colormap ?? "duotone";
|
|
356
|
-
const ctx = await GpuContext.get();
|
|
357
|
-
const device = ctx.device;
|
|
358
|
-
const state = await PingPong.create({ st: "vec2f" }, size * size);
|
|
359
|
-
const sideA = state.current.st;
|
|
360
|
-
const sideB = state.other.st;
|
|
361
|
-
{
|
|
362
|
-
const rand = mulberry32(seedHash);
|
|
363
|
-
const init = new Float32Array(size * size * 2);
|
|
364
|
-
for (let i = 0; i < size * size; i++) init[i * 2] = 1;
|
|
365
|
-
for (let s = 0; s < 10; s++) {
|
|
366
|
-
const cx = 40 + Math.floor(rand() * (size - 80));
|
|
367
|
-
const cy = 40 + Math.floor(rand() * (size - 80));
|
|
368
|
-
const r = 3 + Math.floor(rand() * 6);
|
|
369
|
-
for (let y = cy - r; y <= cy + r; y++) {
|
|
370
|
-
for (let x = cx - r; x <= cx + r; x++) {
|
|
371
|
-
const idx = (y + size) % size * size + (x + size) % size;
|
|
372
|
-
init[idx * 2] = 0.5;
|
|
373
|
-
init[idx * 2 + 1] = 0.25;
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
sideA.write(init);
|
|
378
|
-
}
|
|
379
|
-
const uniform = device.createBuffer({ size: USIZE, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
|
380
|
-
const params = { f: p.f, k: p.k, dA: 1, dB: 0.5, dt: 1 };
|
|
381
|
-
const writeUniform = () => {
|
|
382
|
-
const buf = new ArrayBuffer(USIZE);
|
|
383
|
-
const v = new DataView(buf);
|
|
384
|
-
v.setUint32(0, size, true);
|
|
385
|
-
v.setUint32(4, size, true);
|
|
386
|
-
v.setFloat32(8, params.f, true);
|
|
387
|
-
v.setFloat32(12, params.k, true);
|
|
388
|
-
v.setFloat32(16, params.dA, true);
|
|
389
|
-
v.setFloat32(20, params.dB, true);
|
|
390
|
-
v.setFloat32(24, params.dt, true);
|
|
391
|
-
device.queue.writeBuffer(uniform, 0, buf);
|
|
392
|
-
};
|
|
393
|
-
writeUniform();
|
|
394
|
-
const module = device.createShaderModule({ code: updateWgsl(), label: "turing-update" });
|
|
395
|
-
const info = await module.getCompilationInfo();
|
|
396
|
-
const errors = info.messages.filter((m) => m.type === "error");
|
|
397
|
-
if (errors.length > 0) throw new CompileError("turing-update", errors.map((m) => ({ line: m.lineNum, msg: m.message })), 0);
|
|
398
|
-
const pipeline = device.createComputePipeline({ layout: "auto", compute: { module, entryPoint: "main" } });
|
|
399
|
-
const bg = (read, write) => device.createBindGroup({
|
|
400
|
-
layout: pipeline.getBindGroupLayout(0),
|
|
401
|
-
entries: [
|
|
402
|
-
{ binding: 0, resource: { buffer: uniform } },
|
|
403
|
-
{ binding: 1, resource: { buffer: read.gpuBuffer } },
|
|
404
|
-
{ binding: 2, resource: { buffer: write.gpuBuffer } }
|
|
405
|
-
]
|
|
406
|
-
});
|
|
407
|
-
const bgAB = bg(sideA, sideB);
|
|
408
|
-
const bgBA = bg(sideB, sideA);
|
|
409
|
-
let renderer = null;
|
|
410
|
-
let frame = 0;
|
|
411
|
-
let lastFps = 0;
|
|
412
|
-
let fFrames = 0;
|
|
413
|
-
let fAcc = 0;
|
|
414
|
-
let fLast = performance.now();
|
|
415
|
-
return {
|
|
416
|
-
config: { size, preset, feed: params.f, kill: params.k, steps },
|
|
417
|
-
async attach(canvas) {
|
|
418
|
-
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
419
|
-
canvas.width = Math.max(1, Math.floor(canvas.clientWidth * dpr));
|
|
420
|
-
canvas.height = Math.max(1, Math.floor(canvas.clientHeight * dpr));
|
|
421
|
-
renderer = await MapRenderer.create(canvas, { width: size, height: size, maxV: 0.9, gamma: 0.85, colormap });
|
|
422
|
-
},
|
|
423
|
-
tick() {
|
|
424
|
-
const base = frame * steps;
|
|
425
|
-
const enc = device.createCommandEncoder();
|
|
426
|
-
const pass = enc.beginComputePass();
|
|
427
|
-
pass.setPipeline(pipeline);
|
|
428
|
-
for (let s = 0; s < steps; s++) {
|
|
429
|
-
pass.setBindGroup(0, (base + s) % 2 === 0 ? bgAB : bgBA);
|
|
430
|
-
pass.dispatchWorkgroups(Math.ceil(size * size / 64));
|
|
431
|
-
}
|
|
432
|
-
pass.end();
|
|
433
|
-
device.queue.submit([enc.finish()]);
|
|
434
|
-
renderer?.render((base + steps - 1) % 2 === 0 ? sideB : sideA);
|
|
435
|
-
for (let s = 0; s < steps; s++) state.swap();
|
|
436
|
-
frame++;
|
|
437
|
-
fFrames++;
|
|
438
|
-
const now = performance.now();
|
|
439
|
-
fAcc += now - fLast;
|
|
440
|
-
fLast = now;
|
|
441
|
-
if (fAcc >= 500) {
|
|
442
|
-
lastFps = fFrames / (fAcc / 1e3);
|
|
443
|
-
fFrames = 0;
|
|
444
|
-
fAcc = 0;
|
|
445
|
-
}
|
|
446
|
-
},
|
|
447
|
-
sprinkle(count = 6) {
|
|
448
|
-
const rand = mulberry32(seedHash + frame >>> 0);
|
|
449
|
-
const patch = new Float32Array(size * size * 2);
|
|
450
|
-
for (let s = 0; s < count; s++) {
|
|
451
|
-
const cx = Math.floor(rand() * size);
|
|
452
|
-
const cy = Math.floor(rand() * size);
|
|
453
|
-
const r = 2 + Math.floor(rand() * 5);
|
|
454
|
-
for (let y = cy - r; y <= cy + r; y++) {
|
|
455
|
-
for (let x = cx - r; x <= cx + r; x++) {
|
|
456
|
-
const idx = (y + size) % size * size + (x + size) % size;
|
|
457
|
-
patch[idx * 2] = 0.5;
|
|
458
|
-
patch[idx * 2 + 1] = 0.25;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
device.queue.writeBuffer(state.current.st.gpuBuffer, 0, patch);
|
|
463
|
-
},
|
|
464
|
-
stats() {
|
|
465
|
-
return { fps: lastFps };
|
|
466
|
-
},
|
|
467
|
-
async sampleB() {
|
|
468
|
-
const data = await state.current.st.read();
|
|
469
|
-
const out = new Float32Array(size * size);
|
|
470
|
-
for (let i = 0; i < out.length; i++) out[i] = data[i * 2 + 1] ?? 0;
|
|
471
|
-
return out;
|
|
472
|
-
},
|
|
473
|
-
destroy() {
|
|
474
|
-
state.destroy();
|
|
475
|
-
uniform.destroy();
|
|
476
|
-
}
|
|
477
|
-
};
|
|
478
|
-
}
|
|
479
|
-
function updateWgsl() {
|
|
480
|
-
return (
|
|
481
|
-
/* wgsl */
|
|
482
|
-
`
|
|
483
|
-
struct Params {
|
|
484
|
-
w: u32, h: u32,
|
|
485
|
-
f: f32, k: f32, dA: f32, dB: f32, dt: f32,
|
|
486
|
-
};
|
|
487
|
-
@group(0) @binding(0) var<uniform> params: Params;
|
|
488
|
-
@group(0) @binding(1) var<storage, read> src: array<vec2f>;
|
|
489
|
-
@group(0) @binding(2) var<storage, read_write> dst: array<vec2f>;
|
|
490
|
-
|
|
491
|
-
fn at(x: i32, y: i32) -> vec2f {
|
|
492
|
-
let w = i32(params.w);
|
|
493
|
-
let h = i32(params.h);
|
|
494
|
-
let xi = (x + w) % w;
|
|
495
|
-
let yi = (y + h) % h;
|
|
496
|
-
return src[u32(yi) * u32(w) + u32(xi)];
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
@compute @workgroup_size(64)
|
|
500
|
-
fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|
501
|
-
let i = gid.x;
|
|
502
|
-
if (i >= params.w * params.h) { return; }
|
|
503
|
-
let x = i32(i % params.w);
|
|
504
|
-
let y = i32(i / params.w);
|
|
505
|
-
|
|
506
|
-
// 9 \u70B9 Laplacian:\u6B63\u4EA4 0.2,\u5BF9\u89D2 0.05
|
|
507
|
-
var lapA = -1.0 * at(x, y).x;
|
|
508
|
-
var lapB = -1.0 * at(x, y).y;
|
|
509
|
-
for (var dy = -1; dy <= 1; dy++) {
|
|
510
|
-
for (var dx = -1; dx <= 1; dx++) {
|
|
511
|
-
if (dx == 0 && dy == 0) { continue; }
|
|
512
|
-
let s = at(x + dx, y + dy);
|
|
513
|
-
let wgt = select(0.05, 0.2, dx == 0 || dy == 0);
|
|
514
|
-
lapA = lapA + wgt * s.x;
|
|
515
|
-
lapB = lapB + wgt * s.y;
|
|
516
|
-
}
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
let a = at(x, y).x;
|
|
520
|
-
let b = at(x, y).y;
|
|
521
|
-
var a2 = a + (params.dA * lapA - a * b * b + params.f * (1.0 - a)) * params.dt;
|
|
522
|
-
var b2 = b + (params.dB * lapB + a * b * b - (params.k + params.f) * b) * params.dt;
|
|
523
|
-
a2 = clamp(a2, 0.0, 1.0);
|
|
524
|
-
b2 = clamp(b2, 0.0, 1.0);
|
|
525
|
-
dst[i] = vec2f(a2, b2);
|
|
526
|
-
}
|
|
527
|
-
`
|
|
528
|
-
);
|
|
529
|
-
}
|
|
530
|
-
function hashStr(s) {
|
|
531
|
-
let h = 2166136261;
|
|
532
|
-
for (let i = 0; i < s.length; i++) {
|
|
533
|
-
h ^= s.charCodeAt(i);
|
|
534
|
-
h = Math.imul(h, 16777619);
|
|
535
|
-
}
|
|
536
|
-
return h >>> 0;
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
// src/packs/life/physarum.ts
|
|
540
|
-
var AWG = 32;
|
|
541
|
-
async function physarum(config = {}) {
|
|
542
|
-
const {
|
|
543
|
-
agents: N = 1e5,
|
|
544
|
-
mapSize = 1024,
|
|
545
|
-
sensorAngle = 0.5,
|
|
546
|
-
sensorDist = 0.012,
|
|
547
|
-
turnAngle = 0.45,
|
|
548
|
-
step = 3e-3,
|
|
549
|
-
decay = 0.06,
|
|
550
|
-
seed = "physarum",
|
|
551
|
-
colormap = "amber"
|
|
552
|
-
} = config;
|
|
553
|
-
const seedHash = typeof seed === "string" ? hashStr2(seed) : seed ?? 7;
|
|
554
|
-
const ctx = await GpuContext.get();
|
|
555
|
-
const device = ctx.device;
|
|
556
|
-
const posBuf = await Buffer.create("vec2f", N);
|
|
557
|
-
const angBuf = await Buffer.create("f32", N);
|
|
558
|
-
{
|
|
559
|
-
const rand = mulberry32(seedHash);
|
|
560
|
-
const p = new Float32Array(N * 2);
|
|
561
|
-
const a = new Float32Array(N);
|
|
562
|
-
for (let i = 0; i < N; i++) {
|
|
563
|
-
const t = rand() * Math.PI * 2;
|
|
564
|
-
const r = Math.sqrt(rand()) * 0.08;
|
|
565
|
-
p[i * 2] = Math.cos(t) * r;
|
|
566
|
-
p[i * 2 + 1] = Math.sin(t) * r;
|
|
567
|
-
a[i] = t;
|
|
568
|
-
}
|
|
569
|
-
posBuf.write(p);
|
|
570
|
-
angBuf.write(a);
|
|
571
|
-
}
|
|
572
|
-
const trail = await PingPong.create({ t: "f32" }, mapSize * mapSize);
|
|
573
|
-
const trailA = trail.current.t;
|
|
574
|
-
const trailB = trail.other.t;
|
|
575
|
-
const uniform = device.createBuffer({ size: AWG, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
|
576
|
-
const writeUniform = () => {
|
|
577
|
-
const buf = new ArrayBuffer(AWG);
|
|
578
|
-
const v = new DataView(buf);
|
|
579
|
-
v.setUint32(0, N, true);
|
|
580
|
-
v.setUint32(4, 0, true);
|
|
581
|
-
v.setFloat32(8, sensorAngle, true);
|
|
582
|
-
v.setFloat32(12, sensorDist, true);
|
|
583
|
-
v.setFloat32(16, turnAngle, true);
|
|
584
|
-
v.setFloat32(20, step, true);
|
|
585
|
-
v.setFloat32(24, 1, true);
|
|
586
|
-
v.setFloat32(28, 1, true);
|
|
587
|
-
device.queue.writeBuffer(uniform, 0, buf);
|
|
588
|
-
};
|
|
589
|
-
writeUniform();
|
|
590
|
-
const diffuseUniform = device.createBuffer({ size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
|
591
|
-
device.queue.writeBuffer(diffuseUniform, 0, new Uint32Array([mapSize, mapSize]));
|
|
592
|
-
device.queue.writeBuffer(diffuseUniform, 8, new Float32Array([1 - decay, 0]));
|
|
593
|
-
const compile = async (code, label) => {
|
|
594
|
-
const m = device.createShaderModule({ code, label });
|
|
595
|
-
const info = await m.getCompilationInfo();
|
|
596
|
-
const errors = info.messages.filter((x) => x.type === "error");
|
|
597
|
-
if (errors.length > 0) throw new CompileError(label, errors.map((x) => ({ line: x.lineNum, msg: x.message })), 0);
|
|
598
|
-
return m;
|
|
599
|
-
};
|
|
600
|
-
const mAgent = await compile(agentWgsl(mapSize), "physarum-agent");
|
|
601
|
-
const mDiffuse = await compile(diffuseWgsl(), "physarum-diffuse");
|
|
602
|
-
const pAgent = device.createComputePipeline({ layout: "auto", compute: { module: mAgent, entryPoint: "main" } });
|
|
603
|
-
const pDiffuse = device.createComputePipeline({ layout: "auto", compute: { module: mDiffuse, entryPoint: "main" } });
|
|
604
|
-
const bgAgent = (t) => device.createBindGroup({
|
|
605
|
-
layout: pAgent.getBindGroupLayout(0),
|
|
606
|
-
entries: [
|
|
607
|
-
{ binding: 0, resource: { buffer: uniform } },
|
|
608
|
-
{ binding: 1, resource: { buffer: posBuf.gpuBuffer } },
|
|
609
|
-
{ binding: 2, resource: { buffer: angBuf.gpuBuffer } },
|
|
610
|
-
{ binding: 3, resource: { buffer: t.gpuBuffer } }
|
|
611
|
-
]
|
|
612
|
-
});
|
|
613
|
-
const bgDiffuse = (read, write) => device.createBindGroup({
|
|
614
|
-
layout: pDiffuse.getBindGroupLayout(0),
|
|
615
|
-
entries: [
|
|
616
|
-
{ binding: 0, resource: { buffer: diffuseUniform } },
|
|
617
|
-
{ binding: 1, resource: { buffer: read.gpuBuffer } },
|
|
618
|
-
{ binding: 2, resource: { buffer: write.gpuBuffer } }
|
|
619
|
-
]
|
|
620
|
-
});
|
|
621
|
-
let renderer = null;
|
|
622
|
-
let frame = 0;
|
|
623
|
-
let lastFps = 0;
|
|
624
|
-
let fFrames = 0;
|
|
625
|
-
let fAcc = 0;
|
|
626
|
-
let fLast = performance.now();
|
|
627
|
-
return {
|
|
628
|
-
async attach(canvas) {
|
|
629
|
-
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
630
|
-
canvas.width = Math.max(1, Math.floor(canvas.clientWidth * dpr));
|
|
631
|
-
canvas.height = Math.max(1, Math.floor(canvas.clientHeight * dpr));
|
|
632
|
-
renderer = await MapRenderer.create(canvas, { width: mapSize, height: mapSize, maxV: 6, gamma: 0.6, colormap });
|
|
633
|
-
},
|
|
634
|
-
tick() {
|
|
635
|
-
const readT = trail.current.t;
|
|
636
|
-
const writeT = trail.other.t;
|
|
637
|
-
const enc = device.createCommandEncoder();
|
|
638
|
-
const pass = enc.beginComputePass();
|
|
639
|
-
pass.setPipeline(pAgent);
|
|
640
|
-
pass.setBindGroup(0, bgAgent(readT));
|
|
641
|
-
pass.dispatchWorkgroups(Math.ceil(N / 64));
|
|
642
|
-
pass.setPipeline(pDiffuse);
|
|
643
|
-
pass.setBindGroup(0, bgDiffuse(readT, writeT));
|
|
644
|
-
pass.dispatchWorkgroups(Math.ceil(mapSize * mapSize / 64));
|
|
645
|
-
pass.end();
|
|
646
|
-
device.queue.submit([enc.finish()]);
|
|
647
|
-
renderer?.render(writeT);
|
|
648
|
-
trail.swap();
|
|
649
|
-
frame++;
|
|
650
|
-
fFrames++;
|
|
651
|
-
const now = performance.now();
|
|
652
|
-
fAcc += now - fLast;
|
|
653
|
-
fLast = now;
|
|
654
|
-
if (fAcc >= 500) {
|
|
655
|
-
lastFps = fFrames / (fAcc / 1e3);
|
|
656
|
-
fFrames = 0;
|
|
657
|
-
fAcc = 0;
|
|
658
|
-
}
|
|
659
|
-
},
|
|
660
|
-
stats() {
|
|
661
|
-
return { fps: lastFps };
|
|
662
|
-
},
|
|
663
|
-
async sampleTrail() {
|
|
664
|
-
return await trail.current.t.read();
|
|
665
|
-
},
|
|
666
|
-
destroy() {
|
|
667
|
-
posBuf.destroy();
|
|
668
|
-
angBuf.destroy();
|
|
669
|
-
trail.destroy();
|
|
670
|
-
uniform.destroy();
|
|
671
|
-
diffuseUniform.destroy();
|
|
672
|
-
}
|
|
673
|
-
};
|
|
674
|
-
}
|
|
675
|
-
function agentWgsl(trailW) {
|
|
676
|
-
return (
|
|
677
|
-
/* wgsl */
|
|
678
|
-
`
|
|
679
|
-
const TRAIL_W: u32 = ${trailW}u;
|
|
680
|
-
const TRAIL_MASK: u32 = ${trailW - 1}u;
|
|
681
|
-
struct Params {
|
|
682
|
-
count: u32, _pad: u32,
|
|
683
|
-
sensorAngle: f32, sensorDist: f32, turnAngle: f32, step: f32,
|
|
684
|
-
deposit: f32, worldHalf: f32,
|
|
685
|
-
};
|
|
686
|
-
@group(0) @binding(0) var<uniform> params: Params;
|
|
687
|
-
@group(0) @binding(1) var<storage, read_write> pos: array<vec2f>;
|
|
688
|
-
@group(0) @binding(2) var<storage, read_write> ang: array<f32>;
|
|
689
|
-
@group(0) @binding(3) var<storage, read_write> trail: array<f32>;
|
|
690
|
-
|
|
691
|
-
fn texel(p: vec2f) -> u32 {
|
|
692
|
-
let x = min(u32((p.x * 0.5 + 0.5) * f32(TRAIL_W)), TRAIL_MASK);
|
|
693
|
-
let y = min(u32((p.y * 0.5 + 0.5) * f32(TRAIL_W)), TRAIL_MASK);
|
|
694
|
-
return y * TRAIL_W + x;
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
fn sense(p: vec2f, a: f32) -> f32 {
|
|
698
|
-
return trail[texel(p + vec2f(cos(a), sin(a)) * params.sensorDist)];
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
@compute @workgroup_size(64)
|
|
702
|
-
fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|
703
|
-
let i = gid.x;
|
|
704
|
-
if (i >= params.count) { return; }
|
|
705
|
-
let p = pos[i];
|
|
706
|
-
var a = ang[i];
|
|
707
|
-
|
|
708
|
-
let fwd = sense(p, a);
|
|
709
|
-
let left = sense(p, a - params.sensorAngle);
|
|
710
|
-
let right = sense(p, a + params.sensorAngle);
|
|
711
|
-
|
|
712
|
-
let jitter = (f32((i * 2654435761u + 1u) % 100u) / 100.0 - 0.5) * 0.1;
|
|
713
|
-
if (fwd > left && fwd > right) {
|
|
714
|
-
// \u76F4\u884C
|
|
715
|
-
} else if (left > right) {
|
|
716
|
-
a = a - params.turnAngle;
|
|
717
|
-
} else if (right > left) {
|
|
718
|
-
a = a + params.turnAngle;
|
|
719
|
-
}
|
|
720
|
-
a = a + jitter * 0.1;
|
|
721
|
-
|
|
722
|
-
var np = p + vec2f(cos(a), sin(a)) * params.step;
|
|
723
|
-
let h = params.worldHalf;
|
|
724
|
-
if (abs(np.x) > h || abs(np.y) > h) {
|
|
725
|
-
np = clamp(np, vec2f(-h), vec2f(h));
|
|
726
|
-
a = a + 3.14159265; // \u649E\u5899\u6389\u5934
|
|
727
|
-
}
|
|
728
|
-
pos[i] = np;
|
|
729
|
-
ang[i] = a;
|
|
730
|
-
let t = texel(np);
|
|
731
|
-
trail[t] = trail[t] + params.deposit;
|
|
732
|
-
}
|
|
733
|
-
`
|
|
734
|
-
);
|
|
735
|
-
}
|
|
736
|
-
function diffuseWgsl() {
|
|
737
|
-
return (
|
|
738
|
-
/* wgsl */
|
|
739
|
-
`
|
|
740
|
-
@group(0) @binding(0) var<uniform> vp: vec4f; // w, h, keep(1-decay), pad
|
|
741
|
-
@group(0) @binding(1) var<storage, read> src: array<f32>;
|
|
742
|
-
@group(0) @binding(2) var<storage, read_write> dst: array<f32>;
|
|
743
|
-
|
|
744
|
-
@compute @workgroup_size(64)
|
|
745
|
-
fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|
746
|
-
let i = gid.x;
|
|
747
|
-
let w = u32(vp.x);
|
|
748
|
-
let h = u32(vp.y);
|
|
749
|
-
if (i >= w * h) { return; }
|
|
750
|
-
let x = i32(i % w);
|
|
751
|
-
let y = i32(i / w);
|
|
752
|
-
|
|
753
|
-
// 4 \u90BB\u57DF + \u81EA\u8EAB \u6DF7\u5408(\u6269\u6563),\u518D\u4E58 keep(\u8870\u51CF)
|
|
754
|
-
let c = src[i];
|
|
755
|
-
var s = c * 2.0;
|
|
756
|
-
s = s + src[u32(clamp(y - 1, 0, i32(h) - 1)) * w + u32(clamp(x, 0, i32(w) - 1))];
|
|
757
|
-
s = s + src[u32(clamp(y + 1, 0, i32(h) - 1)) * w + u32(clamp(x, 0, i32(w) - 1))];
|
|
758
|
-
s = s + src[u32(clamp(y, 0, i32(h) - 1)) * w + u32(clamp(x - 1, 0, i32(w) - 1))];
|
|
759
|
-
s = s + src[u32(clamp(y, 0, i32(h) - 1)) * w + u32(clamp(x + 1, 0, i32(w) - 1))];
|
|
760
|
-
let blurred = s / 6.0;
|
|
761
|
-
dst[i] = mix(blurred, c, 0.5) * vp.z;
|
|
762
|
-
}
|
|
763
|
-
`
|
|
764
|
-
);
|
|
765
|
-
}
|
|
766
|
-
function hashStr2(s) {
|
|
767
|
-
let h = 2166136261;
|
|
768
|
-
for (let i = 0; i < s.length; i++) {
|
|
769
|
-
h ^= s.charCodeAt(i);
|
|
770
|
-
h = Math.imul(h, 16777619);
|
|
771
|
-
}
|
|
772
|
-
return h >>> 0;
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
// src/packs/life/boids.ts
|
|
776
|
-
var WG = 64;
|
|
777
|
-
async function boids(config = {}) {
|
|
778
|
-
const {
|
|
779
|
-
count: N = 3e3,
|
|
780
|
-
perception = 0.05,
|
|
781
|
-
maxSpeed = 0.012,
|
|
782
|
-
wSep = 1.6,
|
|
783
|
-
wAli = 1,
|
|
784
|
-
wCoh = 0.8,
|
|
785
|
-
size = 9e-3,
|
|
786
|
-
seed = "boids"
|
|
787
|
-
} = config;
|
|
788
|
-
const seedHash = typeof seed === "string" ? hashStr3(seed) : seed ?? 3;
|
|
789
|
-
const gridSize = Math.max(4, Math.ceil(2 / perception));
|
|
790
|
-
const ctx = await GpuContext.get();
|
|
791
|
-
const device = ctx.device;
|
|
792
|
-
const pp = await PingPong.create({ pos: "vec2f", vel: "vec2f" }, N);
|
|
793
|
-
const sideA = { pos: pp.current.pos, vel: pp.current.vel };
|
|
794
|
-
const sideB = { pos: pp.other.pos, vel: pp.other.vel };
|
|
795
|
-
{
|
|
796
|
-
const rand = mulberry32(seedHash);
|
|
797
|
-
const p = new Float32Array(N * 2);
|
|
798
|
-
const v = new Float32Array(N * 2);
|
|
799
|
-
for (let i = 0; i < N; i++) {
|
|
800
|
-
const t = rand() * Math.PI * 2;
|
|
801
|
-
const r = Math.sqrt(rand()) * 0.6;
|
|
802
|
-
p[i * 2] = Math.cos(t) * r;
|
|
803
|
-
p[i * 2 + 1] = Math.sin(t) * r;
|
|
804
|
-
const va = rand() * Math.PI * 2;
|
|
805
|
-
v[i * 2] = Math.cos(va) * maxSpeed * 0.6;
|
|
806
|
-
v[i * 2 + 1] = Math.sin(va) * maxSpeed * 0.6;
|
|
807
|
-
}
|
|
808
|
-
sideA.pos.write(p);
|
|
809
|
-
sideA.vel.write(v);
|
|
810
|
-
}
|
|
811
|
-
const USIZE3 = 48;
|
|
812
|
-
const uniform = device.createBuffer({ size: USIZE3, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
|
813
|
-
const writeUniform = () => {
|
|
814
|
-
const b = new ArrayBuffer(USIZE3);
|
|
815
|
-
const v = new DataView(b);
|
|
816
|
-
v.setUint32(0, N, true);
|
|
817
|
-
v.setUint32(4, gridSize, true);
|
|
818
|
-
v.setFloat32(8, perception, true);
|
|
819
|
-
v.setFloat32(12, maxSpeed, true);
|
|
820
|
-
v.setFloat32(16, wSep, true);
|
|
821
|
-
v.setFloat32(20, wAli, true);
|
|
822
|
-
v.setFloat32(24, wCoh, true);
|
|
823
|
-
v.setFloat32(28, 1 / 60, true);
|
|
824
|
-
v.setFloat32(32, 1, true);
|
|
825
|
-
device.queue.writeBuffer(uniform, 0, b);
|
|
826
|
-
};
|
|
827
|
-
writeUniform();
|
|
828
|
-
const cells = gridSize * gridSize;
|
|
829
|
-
const cellCount = await Buffer.create("u32", cells);
|
|
830
|
-
const cellStart = await Buffer.create("u32", cells);
|
|
831
|
-
const cellFill = await Buffer.create("u32", cells);
|
|
832
|
-
const order = await Buffer.create("u32", N);
|
|
833
|
-
cellCount.write(new Uint32Array(cells));
|
|
834
|
-
const module = device.createShaderModule({ code: boidsWgsl(size), label: "boids" });
|
|
835
|
-
const info = await module.getCompilationInfo();
|
|
836
|
-
const errors = info.messages.filter((m) => m.type === "error");
|
|
837
|
-
if (errors.length > 0) throw new CompileError("boids", errors.map((m) => ({ line: m.lineNum, msg: m.message })), 0);
|
|
838
|
-
const pCounts = device.createComputePipeline({ layout: "auto", compute: { module, entryPoint: "main_counts" } });
|
|
839
|
-
const pScan = device.createComputePipeline({ layout: "auto", compute: { module, entryPoint: "main_scan" } });
|
|
840
|
-
const pScatter = device.createComputePipeline({ layout: "auto", compute: { module, entryPoint: "main_scatter" } });
|
|
841
|
-
const pForce = device.createComputePipeline({ layout: "auto", compute: { module, entryPoint: "main_force" } });
|
|
842
|
-
const pRender = device.createRenderPipeline({
|
|
843
|
-
layout: "auto",
|
|
844
|
-
vertex: { module, entryPoint: "vs" },
|
|
845
|
-
fragment: { module, entryPoint: "fs", targets: [{ format: navigator.gpu.getPreferredCanvasFormat() }] },
|
|
846
|
-
primitive: { topology: "triangle-list" }
|
|
847
|
-
});
|
|
848
|
-
const bgCounts = (read) => device.createBindGroup({
|
|
849
|
-
layout: pCounts.getBindGroupLayout(0),
|
|
850
|
-
entries: [
|
|
851
|
-
{ binding: 0, resource: { buffer: uniform } },
|
|
852
|
-
{ binding: 1, resource: { buffer: read.pos.gpuBuffer } },
|
|
853
|
-
{ binding: 2, resource: { buffer: cellCount.gpuBuffer } }
|
|
854
|
-
]
|
|
855
|
-
});
|
|
856
|
-
const bgScan = device.createBindGroup({
|
|
857
|
-
layout: pScan.getBindGroupLayout(0),
|
|
858
|
-
entries: [
|
|
859
|
-
{ binding: 0, resource: { buffer: uniform } },
|
|
860
|
-
{ binding: 2, resource: { buffer: cellCount.gpuBuffer } },
|
|
861
|
-
{ binding: 3, resource: { buffer: cellStart.gpuBuffer } },
|
|
862
|
-
{ binding: 4, resource: { buffer: cellFill.gpuBuffer } }
|
|
863
|
-
]
|
|
864
|
-
});
|
|
865
|
-
const bgScatter = (read) => device.createBindGroup({
|
|
866
|
-
layout: pScatter.getBindGroupLayout(0),
|
|
867
|
-
entries: [
|
|
868
|
-
{ binding: 0, resource: { buffer: uniform } },
|
|
869
|
-
{ binding: 1, resource: { buffer: read.pos.gpuBuffer } },
|
|
870
|
-
{ binding: 4, resource: { buffer: cellFill.gpuBuffer } },
|
|
871
|
-
{ binding: 5, resource: { buffer: order.gpuBuffer } }
|
|
872
|
-
]
|
|
873
|
-
});
|
|
874
|
-
const bgForce = (read, write) => device.createBindGroup({
|
|
875
|
-
layout: pForce.getBindGroupLayout(0),
|
|
876
|
-
entries: [
|
|
877
|
-
{ binding: 0, resource: { buffer: uniform } },
|
|
878
|
-
{ binding: 1, resource: { buffer: read.pos.gpuBuffer } },
|
|
879
|
-
{ binding: 6, resource: { buffer: read.vel.gpuBuffer } },
|
|
880
|
-
{ binding: 7, resource: { buffer: write.pos.gpuBuffer } },
|
|
881
|
-
{ binding: 8, resource: { buffer: write.vel.gpuBuffer } },
|
|
882
|
-
{ binding: 3, resource: { buffer: cellStart.gpuBuffer } },
|
|
883
|
-
{ binding: 4, resource: { buffer: cellFill.gpuBuffer } },
|
|
884
|
-
{ binding: 5, resource: { buffer: order.gpuBuffer } }
|
|
885
|
-
]
|
|
886
|
-
});
|
|
887
|
-
const bgRender = (read) => device.createBindGroup({
|
|
888
|
-
layout: pRender.getBindGroupLayout(0),
|
|
889
|
-
entries: [
|
|
890
|
-
{ binding: 0, resource: { buffer: uniform } },
|
|
891
|
-
{ binding: 1, resource: { buffer: read.pos.gpuBuffer } },
|
|
892
|
-
{ binding: 6, resource: { buffer: read.vel.gpuBuffer } }
|
|
893
|
-
]
|
|
894
|
-
});
|
|
895
|
-
let gpuCtx = null;
|
|
896
|
-
let frame = 0;
|
|
897
|
-
let lastFps = 0;
|
|
898
|
-
let fFrames = 0;
|
|
899
|
-
let fAcc = 0;
|
|
900
|
-
let fLast = performance.now();
|
|
901
|
-
return {
|
|
902
|
-
async attach(canvas) {
|
|
903
|
-
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
904
|
-
canvas.width = Math.max(1, Math.floor(canvas.clientWidth * dpr));
|
|
905
|
-
canvas.height = Math.max(1, Math.floor(canvas.clientHeight * dpr));
|
|
906
|
-
const c = canvas.getContext("webgpu");
|
|
907
|
-
if (!c) throw new Error('canvas.getContext("webgpu") \u8FD4\u56DE\u7A7A');
|
|
908
|
-
c.configure({ device, format: navigator.gpu.getPreferredCanvasFormat(), alphaMode: "opaque" });
|
|
909
|
-
gpuCtx = c;
|
|
910
|
-
},
|
|
911
|
-
tick() {
|
|
912
|
-
const useAB = frame % 2 === 0;
|
|
913
|
-
const read = useAB ? sideA : sideB;
|
|
914
|
-
const write = useAB ? sideB : sideA;
|
|
915
|
-
const enc = device.createCommandEncoder();
|
|
916
|
-
const pass = enc.beginComputePass();
|
|
917
|
-
pass.setPipeline(pCounts);
|
|
918
|
-
pass.setBindGroup(0, bgCounts(read));
|
|
919
|
-
pass.dispatchWorkgroups(Math.ceil(N / WG));
|
|
920
|
-
pass.setPipeline(pScan);
|
|
921
|
-
pass.setBindGroup(0, bgScan);
|
|
922
|
-
pass.dispatchWorkgroups(1);
|
|
923
|
-
pass.setPipeline(pScatter);
|
|
924
|
-
pass.setBindGroup(0, bgScatter(read));
|
|
925
|
-
pass.dispatchWorkgroups(Math.ceil(N / WG));
|
|
926
|
-
pass.setPipeline(pForce);
|
|
927
|
-
pass.setBindGroup(0, bgForce(read, write));
|
|
928
|
-
pass.dispatchWorkgroups(Math.ceil(N / WG));
|
|
929
|
-
pass.end();
|
|
930
|
-
device.queue.submit([enc.finish()]);
|
|
931
|
-
if (gpuCtx) {
|
|
932
|
-
const bg = bgRender(write);
|
|
933
|
-
const re = enc2(device, gpuCtx, pRender, bg, N);
|
|
934
|
-
device.queue.submit([re]);
|
|
935
|
-
}
|
|
936
|
-
pp.swap();
|
|
937
|
-
frame++;
|
|
938
|
-
fFrames++;
|
|
939
|
-
const now = performance.now();
|
|
940
|
-
fAcc += now - fLast;
|
|
941
|
-
fLast = now;
|
|
942
|
-
if (fAcc >= 500) {
|
|
943
|
-
lastFps = fFrames / (fAcc / 1e3);
|
|
944
|
-
fFrames = 0;
|
|
945
|
-
fAcc = 0;
|
|
946
|
-
}
|
|
947
|
-
},
|
|
948
|
-
stats() {
|
|
949
|
-
return { fps: lastFps };
|
|
950
|
-
},
|
|
951
|
-
buffers() {
|
|
952
|
-
return { pos: pp.current.pos, vel: pp.current.vel };
|
|
953
|
-
},
|
|
954
|
-
destroy() {
|
|
955
|
-
pp.destroy();
|
|
956
|
-
cellCount.destroy();
|
|
957
|
-
cellStart.destroy();
|
|
958
|
-
cellFill.destroy();
|
|
959
|
-
order.destroy();
|
|
960
|
-
uniform.destroy();
|
|
961
|
-
}
|
|
962
|
-
};
|
|
963
|
-
}
|
|
964
|
-
function enc2(device, gpuCtx, pipeline, bg, n) {
|
|
965
|
-
const enc = device.createCommandEncoder();
|
|
966
|
-
const pass = enc.beginRenderPass({
|
|
967
|
-
colorAttachments: [{
|
|
968
|
-
view: gpuCtx.getCurrentTexture().createView(),
|
|
969
|
-
clearValue: { r: 0.012, g: 0.016, b: 0.03, a: 1 },
|
|
970
|
-
loadOp: "clear",
|
|
971
|
-
storeOp: "store"
|
|
972
|
-
}]
|
|
973
|
-
});
|
|
974
|
-
pass.setPipeline(pipeline);
|
|
975
|
-
pass.setBindGroup(0, bg);
|
|
976
|
-
pass.draw(3, n);
|
|
977
|
-
pass.end();
|
|
978
|
-
return enc.finish();
|
|
979
|
-
}
|
|
980
|
-
function boidsWgsl(pointSize) {
|
|
981
|
-
return (
|
|
982
|
-
/* wgsl */
|
|
983
|
-
`
|
|
984
|
-
struct Params {
|
|
985
|
-
count: u32, gridSize: u32,
|
|
986
|
-
perception: f32, maxSpeed: f32, wSep: f32, wAli: f32, wCoh: f32,
|
|
987
|
-
dt: f32, worldHalf: f32, _p: f32,
|
|
988
|
-
};
|
|
989
|
-
// \u7EDF\u4E00\u7ED1\u5B9A\u5E03\u5C40(\u6A21\u5757\u7EA7\u552F\u4E00\u58F0\u660E,\u56DB\u4E2A\u5165\u53E3\u6309\u9700\u5F15\u7528):
|
|
990
|
-
// 0 uniform | 1 posIn | 2 cellCount(atomic) | 3 cellStart | 4 cellFill(atomic)
|
|
991
|
-
// 5 order | 6 velIn | 7 posOut | 8 velOut
|
|
992
|
-
@group(0) @binding(0) var<uniform> params: Params;
|
|
993
|
-
@group(0) @binding(1) var<storage, read> posIn: array<vec2f>;
|
|
994
|
-
@group(0) @binding(2) var<storage, read_write> cellCount: array<atomic<u32>>;
|
|
995
|
-
@group(0) @binding(3) var<storage, read_write> cellStart: array<u32>;
|
|
996
|
-
@group(0) @binding(4) var<storage, read_write> cellFill: array<atomic<u32>>;
|
|
997
|
-
@group(0) @binding(5) var<storage, read_write> order: array<u32>;
|
|
998
|
-
@group(0) @binding(6) var<storage, read> velIn: array<vec2f>;
|
|
999
|
-
@group(0) @binding(7) var<storage, read_write> posOut: array<vec2f>;
|
|
1000
|
-
@group(0) @binding(8) var<storage, read_write> velOut: array<vec2f>;
|
|
1001
|
-
|
|
1002
|
-
fn cellOf(p: vec2f) -> u32 {
|
|
1003
|
-
let g = i32(params.gridSize);
|
|
1004
|
-
let span = params.worldHalf * 2.0;
|
|
1005
|
-
let cx = clamp(i32(floor((p.x + params.worldHalf) / span * f32(g))), 0, g - 1);
|
|
1006
|
-
let cy = clamp(i32(floor((p.y + params.worldHalf) / span * f32(g))), 0, g - 1);
|
|
1007
|
-
return u32(cy) * u32(g) + u32(cx);
|
|
1008
|
-
}
|
|
1009
|
-
|
|
1010
|
-
@compute @workgroup_size(${WG})
|
|
1011
|
-
fn main_counts(@builtin(global_invocation_id) gid: vec3u) {
|
|
1012
|
-
let i = gid.x;
|
|
1013
|
-
if (i >= params.count) { return; }
|
|
1014
|
-
atomicAdd(&cellCount[cellOf(posIn[i])], 1u);
|
|
1015
|
-
}
|
|
1016
|
-
|
|
1017
|
-
var<workgroup> partial: array<u32, 256>;
|
|
1018
|
-
@compute @workgroup_size(256)
|
|
1019
|
-
fn main_scan(@builtin(local_invocation_id) lid: vec3u) {
|
|
1020
|
-
let tid = lid.x;
|
|
1021
|
-
let cells = params.gridSize * params.gridSize;
|
|
1022
|
-
let chunks = (cells + 255u) / 256u;
|
|
1023
|
-
var local = 0u;
|
|
1024
|
-
for (var c = 0u; c < chunks; c++) {
|
|
1025
|
-
let idx = c * 256u + tid;
|
|
1026
|
-
if (idx < cells) { local = local + atomicLoad(&cellCount[idx]); }
|
|
1027
|
-
}
|
|
1028
|
-
partial[tid] = local;
|
|
1029
|
-
workgroupBarrier();
|
|
1030
|
-
var offset = 1u;
|
|
1031
|
-
loop {
|
|
1032
|
-
if (offset >= 256u) { break; }
|
|
1033
|
-
var v = 0u;
|
|
1034
|
-
if (tid >= offset) { v = partial[tid - offset]; }
|
|
1035
|
-
workgroupBarrier();
|
|
1036
|
-
if (tid >= offset) { partial[tid] = partial[tid] + v; }
|
|
1037
|
-
workgroupBarrier();
|
|
1038
|
-
offset = offset << 1u;
|
|
1039
|
-
}
|
|
1040
|
-
var run = 0u;
|
|
1041
|
-
if (tid > 0u) { run = partial[tid - 1u]; }
|
|
1042
|
-
for (var c = 0u; c < chunks; c++) {
|
|
1043
|
-
let idx = c * 256u + tid;
|
|
1044
|
-
if (idx < cells) {
|
|
1045
|
-
cellStart[idx] = run;
|
|
1046
|
-
atomicStore(&cellFill[idx], run);
|
|
1047
|
-
run = run + atomicLoad(&cellCount[idx]);
|
|
1048
|
-
atomicStore(&cellCount[idx], 0u);
|
|
1049
|
-
}
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
|
|
1053
|
-
@compute @workgroup_size(${WG})
|
|
1054
|
-
fn main_scatter(@builtin(global_invocation_id) gid: vec3u) {
|
|
1055
|
-
let i = gid.x;
|
|
1056
|
-
if (i >= params.count) { return; }
|
|
1057
|
-
let slot = atomicAdd(&cellFill[cellOf(posIn[i])], 1u);
|
|
1058
|
-
order[slot] = i;
|
|
1059
|
-
}
|
|
1060
|
-
|
|
1061
|
-
@compute @workgroup_size(${WG})
|
|
1062
|
-
fn main_force(@builtin(global_invocation_id) gid: vec3u) {
|
|
1063
|
-
let i = gid.x;
|
|
1064
|
-
if (i >= params.count) { return; }
|
|
1065
|
-
let myPos = posIn[i];
|
|
1066
|
-
let myVel = velIn[i];
|
|
1067
|
-
var sep = vec2f(0.0);
|
|
1068
|
-
var ali = vec2f(0.0);
|
|
1069
|
-
var coh = vec2f(0.0);
|
|
1070
|
-
var n = 0u;
|
|
1071
|
-
let g = i32(params.gridSize);
|
|
1072
|
-
let cellSize = params.worldHalf * 2.0 / f32(g);
|
|
1073
|
-
var cx = clamp(i32(floor((myPos.x + params.worldHalf) / cellSize)), 0, g - 1);
|
|
1074
|
-
var cy = clamp(i32(floor((myPos.y + params.worldHalf) / cellSize)), 0, g - 1);
|
|
1075
|
-
let p2 = params.perception * params.perception;
|
|
1076
|
-
|
|
1077
|
-
for (var dy = -1; dy <= 1; dy++) {
|
|
1078
|
-
for (var dx = -1; dx <= 1; dx++) {
|
|
1079
|
-
let nx = cx + dx;
|
|
1080
|
-
let ny = cy + dy;
|
|
1081
|
-
if (nx < 0 || ny < 0 || nx >= g || ny >= g) { continue; }
|
|
1082
|
-
let c = u32(ny) * u32(g) + u32(nx);
|
|
1083
|
-
let s = cellStart[c];
|
|
1084
|
-
let e = atomicLoad(&cellFill[c]);
|
|
1085
|
-
for (var k = s; k < e; k++) {
|
|
1086
|
-
let j = order[k];
|
|
1087
|
-
if (j == i) { continue; }
|
|
1088
|
-
let rel = posIn[j] - myPos;
|
|
1089
|
-
let d2 = dot(rel, rel);
|
|
1090
|
-
if (d2 > p2) { continue; }
|
|
1091
|
-
let d = sqrt(d2) + 1e-5;
|
|
1092
|
-
sep = sep + (myPos - posIn[j]) / d;
|
|
1093
|
-
ali = ali + velIn[j];
|
|
1094
|
-
coh = coh + posIn[j];
|
|
1095
|
-
n = n + 1u;
|
|
1096
|
-
}
|
|
1097
|
-
}
|
|
1098
|
-
}
|
|
1099
|
-
|
|
1100
|
-
var vel = myVel;
|
|
1101
|
-
if (n > 0u) {
|
|
1102
|
-
let nf = f32(n);
|
|
1103
|
-
ali = ali / nf;
|
|
1104
|
-
coh = coh / nf - myPos;
|
|
1105
|
-
vel = myVel + (sep * params.wSep + ali * params.wAli + coh * params.wCoh) * 0.016;
|
|
1106
|
-
}
|
|
1107
|
-
let sp = length(vel);
|
|
1108
|
-
if (sp > params.maxSpeed) { vel = vel / sp * params.maxSpeed; }
|
|
1109
|
-
if (sp < params.maxSpeed * 0.35) { vel = vel / max(sp, 1e-5) * params.maxSpeed * 0.35; }
|
|
1110
|
-
|
|
1111
|
-
var pos = myPos + vel;
|
|
1112
|
-
let h = params.worldHalf;
|
|
1113
|
-
pos = ((pos + h) % (2.0 * h) + 2.0 * h) % (2.0 * h) - h;
|
|
1114
|
-
posOut[i] = pos;
|
|
1115
|
-
velOut[i] = vel;
|
|
1116
|
-
}
|
|
1117
|
-
|
|
1118
|
-
// ---- \u6E32\u67D3:\u671D\u5411\u901F\u5EA6\u65B9\u5411\u7684\u4E09\u89D2 ----
|
|
1119
|
-
struct VsOut {
|
|
1120
|
-
@builtin(position) clip: vec4f,
|
|
1121
|
-
@location(0) color: vec3f,
|
|
1122
|
-
};
|
|
1123
|
-
|
|
1124
|
-
@vertex
|
|
1125
|
-
fn vs(@builtin(vertex_index) v: u32, @builtin(instance_index) inst: u32) -> VsOut {
|
|
1126
|
-
var shape = array<vec2f, 3>(vec2f(${(pointSize * 1.6).toFixed(4)}, 0.0), vec2f(${(-pointSize).toFixed(4)}, ${(pointSize * 0.45).toFixed(4)}), vec2f(${(-pointSize).toFixed(4)}, ${(-pointSize * 0.45).toFixed(4)}));
|
|
1127
|
-
let a = atan2(velIn[inst].y, velIn[inst].x);
|
|
1128
|
-
let c = cos(a);
|
|
1129
|
-
let s = sin(a);
|
|
1130
|
-
let l = shape[v];
|
|
1131
|
-
var out: VsOut;
|
|
1132
|
-
out.clip = vec4f(posIn[inst] + vec2f(l.x * c - l.y * s, l.x * s + l.y * c), 0.0, 1.0);
|
|
1133
|
-
let sp = length(velIn[inst]) / params.maxSpeed;
|
|
1134
|
-
out.color = mix(vec3f(0.12, 0.2, 0.45), vec3f(0.4, 0.9, 1.0), clamp(sp, 0.0, 1.0));
|
|
1135
|
-
return out;
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
@fragment
|
|
1139
|
-
fn fs(in: VsOut) -> @location(0) vec4f {
|
|
1140
|
-
return vec4f(in.color, 1.0);
|
|
1141
|
-
}
|
|
1142
|
-
`
|
|
1143
|
-
);
|
|
1144
|
-
}
|
|
1145
|
-
function hashStr3(s) {
|
|
1146
|
-
let h = 2166136261;
|
|
1147
|
-
for (let i = 0; i < s.length; i++) {
|
|
1148
|
-
h ^= s.charCodeAt(i);
|
|
1149
|
-
h = Math.imul(h, 16777619);
|
|
1150
|
-
}
|
|
1151
|
-
return h >>> 0;
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
// src/packs/life/tentacles.ts
|
|
1155
|
-
var USIZE2 = 32;
|
|
1156
|
-
async function tentacles(config = {}) {
|
|
1157
|
-
const {
|
|
1158
|
-
chains = 48,
|
|
1159
|
-
segments = 64,
|
|
1160
|
-
segLen = 0.018,
|
|
1161
|
-
gravity = 35e-5,
|
|
1162
|
-
damping = 0.985,
|
|
1163
|
-
iterations = 10,
|
|
1164
|
-
thickness = 6e-3,
|
|
1165
|
-
colorCycle = 0.35
|
|
1166
|
-
} = config;
|
|
1167
|
-
const N = chains * segments;
|
|
1168
|
-
const ctx = await GpuContext.get();
|
|
1169
|
-
const device = ctx.device;
|
|
1170
|
-
const pp = await PingPong.create({ pos: "vec2f" }, N);
|
|
1171
|
-
const prev = await Buffer.create("vec2f", N);
|
|
1172
|
-
{
|
|
1173
|
-
const p = new Float32Array(N * 2);
|
|
1174
|
-
pp.current.pos.write(p);
|
|
1175
|
-
prev.write(p);
|
|
1176
|
-
}
|
|
1177
|
-
const uniform = device.createBuffer({ size: USIZE2, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
|
1178
|
-
let time = 0;
|
|
1179
|
-
const writeUniform = () => {
|
|
1180
|
-
const b = new ArrayBuffer(USIZE2);
|
|
1181
|
-
const v = new DataView(b);
|
|
1182
|
-
v.setUint32(0, N, true);
|
|
1183
|
-
v.setUint32(4, segments, true);
|
|
1184
|
-
v.setFloat32(8, segLen, true);
|
|
1185
|
-
v.setFloat32(12, gravity, true);
|
|
1186
|
-
v.setFloat32(16, damping, true);
|
|
1187
|
-
v.setFloat32(20, time, true);
|
|
1188
|
-
v.setFloat32(24, 0.45, true);
|
|
1189
|
-
v.setUint32(28, iterations, true);
|
|
1190
|
-
device.queue.writeBuffer(uniform, 0, b);
|
|
1191
|
-
};
|
|
1192
|
-
const module = device.createShaderModule({ code: tentaclesWgsl(thickness, colorCycle), label: "tentacles" });
|
|
1193
|
-
const info = await module.getCompilationInfo();
|
|
1194
|
-
const errors = info.messages.filter((m) => m.type === "error");
|
|
1195
|
-
if (errors.length > 0) throw new CompileError("tentacles", errors.map((m) => ({ line: m.lineNum, msg: m.message })), 0);
|
|
1196
|
-
const pIntegrate = device.createComputePipeline({ layout: "auto", compute: { module, entryPoint: "main_integrate" } });
|
|
1197
|
-
const pConstraint = device.createComputePipeline({ layout: "auto", compute: { module, entryPoint: "main_constraint" } });
|
|
1198
|
-
const pRender = device.createRenderPipeline({
|
|
1199
|
-
layout: "auto",
|
|
1200
|
-
vertex: { module, entryPoint: "vs" },
|
|
1201
|
-
fragment: { module, entryPoint: "fs", targets: [{ format: navigator.gpu.getPreferredCanvasFormat() }] },
|
|
1202
|
-
primitive: { topology: "triangle-list" }
|
|
1203
|
-
});
|
|
1204
|
-
const bgIntegrate = (read, write) => device.createBindGroup({
|
|
1205
|
-
layout: pIntegrate.getBindGroupLayout(0),
|
|
1206
|
-
entries: [
|
|
1207
|
-
{ binding: 0, resource: { buffer: uniform } },
|
|
1208
|
-
{ binding: 1, resource: { buffer: read.gpuBuffer } },
|
|
1209
|
-
{ binding: 2, resource: { buffer: prev.gpuBuffer } },
|
|
1210
|
-
{ binding: 3, resource: { buffer: write.gpuBuffer } }
|
|
1211
|
-
]
|
|
1212
|
-
});
|
|
1213
|
-
const bgConstraint = (read, write) => device.createBindGroup({
|
|
1214
|
-
layout: pConstraint.getBindGroupLayout(0),
|
|
1215
|
-
entries: [
|
|
1216
|
-
{ binding: 0, resource: { buffer: uniform } },
|
|
1217
|
-
{ binding: 1, resource: { buffer: read.gpuBuffer } },
|
|
1218
|
-
{ binding: 2, resource: { buffer: write.gpuBuffer } }
|
|
1219
|
-
]
|
|
1220
|
-
});
|
|
1221
|
-
const bgRenderCache = /* @__PURE__ */ new Map();
|
|
1222
|
-
const ids = /* @__PURE__ */ new WeakMap();
|
|
1223
|
-
const bgRender = (pos) => {
|
|
1224
|
-
let id = ids.get(pos.gpuBuffer);
|
|
1225
|
-
if (id === void 0) {
|
|
1226
|
-
id = bgRenderCache.size + 1;
|
|
1227
|
-
ids.set(pos.gpuBuffer, id);
|
|
1228
|
-
}
|
|
1229
|
-
let bg = bgRenderCache.get(id);
|
|
1230
|
-
if (!bg) {
|
|
1231
|
-
bg = device.createBindGroup({
|
|
1232
|
-
layout: pRender.getBindGroupLayout(0),
|
|
1233
|
-
entries: [
|
|
1234
|
-
{ binding: 0, resource: { buffer: uniform } },
|
|
1235
|
-
{ binding: 1, resource: { buffer: pos.gpuBuffer } }
|
|
1236
|
-
]
|
|
1237
|
-
});
|
|
1238
|
-
bgRenderCache.set(id, bg);
|
|
1239
|
-
}
|
|
1240
|
-
return bg;
|
|
1241
|
-
};
|
|
1242
|
-
let gpuCtx = null;
|
|
1243
|
-
let frame = 0;
|
|
1244
|
-
let lastFps = 0;
|
|
1245
|
-
let fFrames = 0;
|
|
1246
|
-
let fAcc = 0;
|
|
1247
|
-
let fLast = performance.now();
|
|
1248
|
-
return {
|
|
1249
|
-
async attach(canvas) {
|
|
1250
|
-
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
1251
|
-
canvas.width = Math.max(1, Math.floor(canvas.clientWidth * dpr));
|
|
1252
|
-
canvas.height = Math.max(1, Math.floor(canvas.clientHeight * dpr));
|
|
1253
|
-
const c = canvas.getContext("webgpu");
|
|
1254
|
-
if (!c) throw new Error('canvas.getContext("webgpu") \u8FD4\u56DE\u7A7A');
|
|
1255
|
-
c.configure({ device, format: navigator.gpu.getPreferredCanvasFormat(), alphaMode: "opaque" });
|
|
1256
|
-
gpuCtx = c;
|
|
1257
|
-
},
|
|
1258
|
-
tick() {
|
|
1259
|
-
time += 1 / 60;
|
|
1260
|
-
writeUniform();
|
|
1261
|
-
const enc = device.createCommandEncoder();
|
|
1262
|
-
const pass = enc.beginComputePass();
|
|
1263
|
-
pass.setPipeline(pIntegrate);
|
|
1264
|
-
pass.setBindGroup(0, bgIntegrate(pp.current.pos, pp.other.pos));
|
|
1265
|
-
pass.dispatchWorkgroups(Math.ceil(N / 64));
|
|
1266
|
-
pass.end();
|
|
1267
|
-
device.queue.submit([enc.finish()]);
|
|
1268
|
-
pp.swap();
|
|
1269
|
-
const enc22 = device.createCommandEncoder();
|
|
1270
|
-
const pass2 = enc22.beginComputePass();
|
|
1271
|
-
pass2.setPipeline(pConstraint);
|
|
1272
|
-
for (let r = 0; r < iterations; r++) {
|
|
1273
|
-
pass2.setBindGroup(0, bgConstraint(pp.current.pos, pp.other.pos));
|
|
1274
|
-
pass2.dispatchWorkgroups(Math.ceil(N / 64));
|
|
1275
|
-
pp.swap();
|
|
1276
|
-
}
|
|
1277
|
-
pass2.end();
|
|
1278
|
-
device.queue.submit([enc22.finish()]);
|
|
1279
|
-
if (gpuCtx) {
|
|
1280
|
-
const re = device.createCommandEncoder();
|
|
1281
|
-
const rp = re.beginRenderPass({
|
|
1282
|
-
colorAttachments: [{
|
|
1283
|
-
view: gpuCtx.getCurrentTexture().createView(),
|
|
1284
|
-
clearValue: { r: 0.012, g: 0.016, b: 0.03, a: 1 },
|
|
1285
|
-
loadOp: "clear",
|
|
1286
|
-
storeOp: "store"
|
|
1287
|
-
}]
|
|
1288
|
-
});
|
|
1289
|
-
rp.setPipeline(pRender);
|
|
1290
|
-
rp.setBindGroup(0, bgRender(pp.current.pos));
|
|
1291
|
-
rp.draw(6, N);
|
|
1292
|
-
rp.end();
|
|
1293
|
-
device.queue.submit([re.finish()]);
|
|
1294
|
-
}
|
|
1295
|
-
frame++;
|
|
1296
|
-
fFrames++;
|
|
1297
|
-
const now = performance.now();
|
|
1298
|
-
fAcc += now - fLast;
|
|
1299
|
-
fLast = now;
|
|
1300
|
-
if (fAcc >= 500) {
|
|
1301
|
-
lastFps = fFrames / (fAcc / 1e3);
|
|
1302
|
-
fFrames = 0;
|
|
1303
|
-
fAcc = 0;
|
|
1304
|
-
}
|
|
1305
|
-
},
|
|
1306
|
-
stats() {
|
|
1307
|
-
return { fps: lastFps };
|
|
1308
|
-
},
|
|
1309
|
-
buffers() {
|
|
1310
|
-
return { pos: pp.current.pos };
|
|
1311
|
-
},
|
|
1312
|
-
destroy() {
|
|
1313
|
-
pp.destroy();
|
|
1314
|
-
prev.destroy();
|
|
1315
|
-
uniform.destroy();
|
|
1316
|
-
}
|
|
1317
|
-
};
|
|
1318
|
-
}
|
|
1319
|
-
function tentaclesWgsl(thickness, colorCycle) {
|
|
1320
|
-
return (
|
|
1321
|
-
/* wgsl */
|
|
1322
|
-
`
|
|
1323
|
-
struct Params {
|
|
1324
|
-
count: u32, segments: u32,
|
|
1325
|
-
segLen: f32, gravity: f32, damping: f32, time: f32,
|
|
1326
|
-
anchorR: f32, iterations: u32,
|
|
1327
|
-
};
|
|
1328
|
-
@group(0) @binding(0) var<uniform> params: Params;
|
|
1329
|
-
@group(0) @binding(1) var<storage, read> posIn: array<vec2f>;
|
|
1330
|
-
@group(0) @binding(2) var<storage, read_write> posOut: array<vec2f>;
|
|
1331
|
-
@group(0) @binding(3) var<storage, read_write> prev: array<vec2f>;
|
|
1332
|
-
|
|
1333
|
-
fn anchorPos(chain: u32, t: f32) -> vec2f {
|
|
1334
|
-
let phase = f32(chain) * 2.399963; // \u9EC4\u91D1\u89D2:\u94FE\u4E0E\u94FE\u6C38\u4E0D\u91CD\u53E0\u6392\u5E03
|
|
1335
|
-
let x = cos(t * 0.7 + phase) * params.anchorR;
|
|
1336
|
-
let y = sin(t * 1.13 + phase * 1.7) * params.anchorR * 0.55;
|
|
1337
|
-
return vec2f(x, y);
|
|
1338
|
-
}
|
|
1339
|
-
|
|
1340
|
-
@compute @workgroup_size(64)
|
|
1341
|
-
fn main_integrate(@builtin(global_invocation_id) gid: vec3u) {
|
|
1342
|
-
let i = gid.x;
|
|
1343
|
-
if (i >= params.count) { return; }
|
|
1344
|
-
let segs = params.segments;
|
|
1345
|
-
let k = i % segs;
|
|
1346
|
-
let chain = i / segs;
|
|
1347
|
-
if (k == 0u) {
|
|
1348
|
-
let anchor = anchorPos(chain, params.time);
|
|
1349
|
-
posOut[i] = anchor;
|
|
1350
|
-
prev[i] = anchor;
|
|
1351
|
-
return;
|
|
1352
|
-
}
|
|
1353
|
-
let vel = (posIn[i] - prev[i]) * params.damping;
|
|
1354
|
-
prev[i] = posIn[i];
|
|
1355
|
-
posOut[i] = posIn[i] + vel + vec2f(0.0, -params.gravity);
|
|
1356
|
-
}
|
|
1357
|
-
|
|
1358
|
-
// \u8DDF\u968F\u7EA6\u675F:\u8BFB posIn \u5199 posOut(\u5355\u7EBF\u7A0B\u5355\u6570\u636E,\u96F6\u7ADE\u6001)
|
|
1359
|
-
@compute @workgroup_size(64)
|
|
1360
|
-
fn main_constraint(@builtin(global_invocation_id) gid: vec3u) {
|
|
1361
|
-
let i = gid.x;
|
|
1362
|
-
if (i >= params.count) { return; }
|
|
1363
|
-
let segs = params.segments;
|
|
1364
|
-
let k = i % segs;
|
|
1365
|
-
if (k == 0u) { posOut[i] = posIn[i]; return; }
|
|
1366
|
-
let d = posIn[i] - posIn[i - 1u];
|
|
1367
|
-
let l = length(d) + 1e-6;
|
|
1368
|
-
posOut[i] = posIn[i - 1u] + d / l * params.segLen;
|
|
1369
|
-
}
|
|
1370
|
-
|
|
1371
|
-
struct VsOut {
|
|
1372
|
-
@builtin(position) clip: vec4f,
|
|
1373
|
-
@location(0) color: vec3f,
|
|
1374
|
-
};
|
|
1375
|
-
@group(0) @binding(0) var<uniform> vp: Params;
|
|
1376
|
-
@group(0) @binding(1) var<storage, read> posR: array<vec2f>;
|
|
1377
|
-
|
|
1378
|
-
@vertex
|
|
1379
|
-
fn vs(@builtin(vertex_index) v: u32, @builtin(instance_index) inst: u32) -> VsOut {
|
|
1380
|
-
let segs = vp.segments;
|
|
1381
|
-
let k = inst % segs;
|
|
1382
|
-
let chain = inst / segs;
|
|
1383
|
-
let taper = 1.0 - f32(k) / f32(segs);
|
|
1384
|
-
let r = ${(thickness * 1.1).toFixed(5)} * (0.25 + 0.75 * taper);
|
|
1385
|
-
var corner = array<vec2f, 6>(
|
|
1386
|
-
vec2f(-r, -r), vec2f(r, -r), vec2f(-r, r),
|
|
1387
|
-
vec2f(-r, r), vec2f(r, -r), vec2f(r, r)
|
|
1388
|
-
);
|
|
1389
|
-
var out: VsOut;
|
|
1390
|
-
out.clip = vec4f(posR[inst] + corner[v], 0.0, 1.0);
|
|
1391
|
-
out.color = 0.5 + 0.5 * cos(f32(chain) * ${colorCycle.toFixed(3)} + vec3f(0.0, 2.1, 4.2)) * (0.4 + 0.6 * taper);
|
|
1392
|
-
return out;
|
|
1393
|
-
}
|
|
1394
|
-
|
|
1395
|
-
@fragment
|
|
1396
|
-
fn fs(in: VsOut) -> @location(0) vec4f {
|
|
1397
|
-
return vec4f(in.color, 1.0);
|
|
1398
|
-
}
|
|
1399
|
-
`
|
|
1400
|
-
);
|
|
1401
|
-
}
|
|
1402
|
-
export {
|
|
1403
|
-
boids,
|
|
1404
|
-
physarum,
|
|
1405
|
-
tentacles,
|
|
1406
|
-
turing
|
|
1407
|
-
};
|