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/fields.js
DELETED
|
@@ -1,585 +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/fields/index.ts
|
|
342
|
-
var FIELD_FNS = {
|
|
343
|
-
// 绕心漩涡:切向速度,离心得越远越慢
|
|
344
|
-
vortex: `
|
|
345
|
-
fn fieldAt(p: vec2f, t: f32) -> vec2f {
|
|
346
|
-
let r = length(p) + 0.12;
|
|
347
|
-
return vec2f(-p.y, p.x) / r * 1.4;
|
|
348
|
-
}`,
|
|
349
|
-
// curl noise:值噪声的旋度,无散度,像真实的湍流
|
|
350
|
-
curl: `
|
|
351
|
-
fn hash(p: vec2f) -> f32 {
|
|
352
|
-
return fract(sin(dot(p, vec2f(127.1, 311.7))) * 43758.5453);
|
|
353
|
-
}
|
|
354
|
-
fn vnoise(p: vec2f) -> f32 {
|
|
355
|
-
let i = floor(p);
|
|
356
|
-
let f = fract(p);
|
|
357
|
-
let u = f * f * (3.0 - 2.0 * f);
|
|
358
|
-
return mix(mix(hash(i), hash(i + vec2f(1.0, 0.0)), u.x), mix(hash(i + vec2f(0.0, 1.0)), hash(i + vec2f(1.0, 1.0)), u.x), u.y);
|
|
359
|
-
}
|
|
360
|
-
fn fieldAt(p: vec2f, t: f32) -> vec2f {
|
|
361
|
-
let s = 2.2;
|
|
362
|
-
let e = 0.01;
|
|
363
|
-
let n1 = vnoise(p * s + vec2f(0.0, t * 0.15));
|
|
364
|
-
let n2 = vnoise(p * s + vec2f(17.3, t * 0.15));
|
|
365
|
-
let dx = vnoise(p * s + vec2f(e, 0.0) + vec2f(0.0, t * 0.15)) - n1;
|
|
366
|
-
let dy = vnoise(p * s + vec2f(0.0, e) + vec2f(17.3, t * 0.15)) - n2;
|
|
367
|
-
return normalize(vec2f(dy, -dx) / e + vec2f(1e-5));
|
|
368
|
-
}`,
|
|
369
|
-
// 双涡:左右反向旋转,中间有剪切层
|
|
370
|
-
twin: `
|
|
371
|
-
fn fieldAt(p: vec2f, t: f32) -> vec2f {
|
|
372
|
-
let s = select(-1.0, 1.0, p.x > 0.0);
|
|
373
|
-
let c = vec2f(0.55 * s, 0.0);
|
|
374
|
-
let r = length(p - c) + 0.1;
|
|
375
|
-
let swirl = vec2f(-(p - c).y, (p - c).x) / r;
|
|
376
|
-
return swirl * s * 1.3 + vec2f(0.0, sin(t * 0.4) * 0.2);
|
|
377
|
-
}`
|
|
378
|
-
};
|
|
379
|
-
var AWG = 32;
|
|
380
|
-
async function flow(config = {}) {
|
|
381
|
-
const {
|
|
382
|
-
count: N = 131072,
|
|
383
|
-
mapSize = 1024,
|
|
384
|
-
field = "curl",
|
|
385
|
-
speed = 4e-3,
|
|
386
|
-
decay = 0.045,
|
|
387
|
-
deposit = 1,
|
|
388
|
-
seed = "flow",
|
|
389
|
-
colormap = "ice"
|
|
390
|
-
} = config;
|
|
391
|
-
const seedHash = typeof seed === "string" ? hashStr(seed) : seed ?? 11;
|
|
392
|
-
const ctx = await GpuContext.get();
|
|
393
|
-
const device = ctx.device;
|
|
394
|
-
const posBuf = await Buffer.create("vec2f", N);
|
|
395
|
-
{
|
|
396
|
-
const rand = mulberry32(seedHash);
|
|
397
|
-
const p = new Float32Array(N * 2);
|
|
398
|
-
for (let i = 0; i < N; i++) {
|
|
399
|
-
p[i * 2] = rand() * 1.8 - 0.9;
|
|
400
|
-
p[i * 2 + 1] = rand() * 1.8 - 0.9;
|
|
401
|
-
}
|
|
402
|
-
posBuf.write(p);
|
|
403
|
-
}
|
|
404
|
-
const trail = await PingPong.create({ t: "f32" }, mapSize * mapSize);
|
|
405
|
-
const trailA = trail.current.t;
|
|
406
|
-
const trailB = trail.other.t;
|
|
407
|
-
const uniform = device.createBuffer({ size: AWG, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
|
408
|
-
const u = { count: N, pad: 0, speed, worldHalf: 1, deposit, time: 0, p0: 0, p1: 0 };
|
|
409
|
-
const writeUniform = () => {
|
|
410
|
-
const b = new ArrayBuffer(AWG);
|
|
411
|
-
const v = new DataView(b);
|
|
412
|
-
v.setUint32(0, u.count, true);
|
|
413
|
-
v.setUint32(4, u.pad, true);
|
|
414
|
-
v.setFloat32(8, u.speed, true);
|
|
415
|
-
v.setFloat32(12, u.worldHalf, true);
|
|
416
|
-
v.setFloat32(16, u.deposit, true);
|
|
417
|
-
v.setFloat32(20, u.time, true);
|
|
418
|
-
device.queue.writeBuffer(uniform, 0, b);
|
|
419
|
-
};
|
|
420
|
-
writeUniform();
|
|
421
|
-
const diffuseUniform = device.createBuffer({ size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
|
|
422
|
-
device.queue.writeBuffer(diffuseUniform, 0, new Uint32Array([mapSize, mapSize]));
|
|
423
|
-
device.queue.writeBuffer(diffuseUniform, 8, new Float32Array([1 - decay, 0]));
|
|
424
|
-
const compile = async (code, label) => {
|
|
425
|
-
const m = device.createShaderModule({ code, label });
|
|
426
|
-
const info = await m.getCompilationInfo();
|
|
427
|
-
const errors = info.messages.filter((x) => x.type === "error");
|
|
428
|
-
if (errors.length > 0) throw new CompileError(label, errors.map((x) => ({ line: x.lineNum, msg: x.message })), 0);
|
|
429
|
-
return m;
|
|
430
|
-
};
|
|
431
|
-
const mAdvect = await compile(advectWgsl(FIELD_FNS[field], mapSize), `fields-advect(${field})`);
|
|
432
|
-
const mDiffuse = await compile(diffuseWgsl(), "fields-diffuse");
|
|
433
|
-
const pAdvect = device.createComputePipeline({ layout: "auto", compute: { module: mAdvect, entryPoint: "main" } });
|
|
434
|
-
const pDiffuse = device.createComputePipeline({ layout: "auto", compute: { module: mDiffuse, entryPoint: "main" } });
|
|
435
|
-
const bgAdvect = (t) => device.createBindGroup({
|
|
436
|
-
layout: pAdvect.getBindGroupLayout(0),
|
|
437
|
-
entries: [
|
|
438
|
-
{ binding: 0, resource: { buffer: uniform } },
|
|
439
|
-
{ binding: 1, resource: { buffer: posBuf.gpuBuffer } },
|
|
440
|
-
{ binding: 2, resource: { buffer: t.gpuBuffer } }
|
|
441
|
-
]
|
|
442
|
-
});
|
|
443
|
-
const bgDiffuse = (read, write) => device.createBindGroup({
|
|
444
|
-
layout: pDiffuse.getBindGroupLayout(0),
|
|
445
|
-
entries: [
|
|
446
|
-
{ binding: 0, resource: { buffer: diffuseUniform } },
|
|
447
|
-
{ binding: 1, resource: { buffer: read.gpuBuffer } },
|
|
448
|
-
{ binding: 2, resource: { buffer: write.gpuBuffer } }
|
|
449
|
-
]
|
|
450
|
-
});
|
|
451
|
-
let renderer = null;
|
|
452
|
-
let frame = 0;
|
|
453
|
-
let time = 0;
|
|
454
|
-
let lastFps = 0;
|
|
455
|
-
let fFrames = 0;
|
|
456
|
-
let fAcc = 0;
|
|
457
|
-
let fLast = performance.now();
|
|
458
|
-
return {
|
|
459
|
-
async attach(canvas) {
|
|
460
|
-
const dpr = Math.min(window.devicePixelRatio || 1, 2);
|
|
461
|
-
canvas.width = Math.max(1, Math.floor(canvas.clientWidth * dpr));
|
|
462
|
-
canvas.height = Math.max(1, Math.floor(canvas.clientHeight * dpr));
|
|
463
|
-
renderer = await MapRenderer.create(canvas, { width: mapSize, height: mapSize, maxV: 5, gamma: 0.65, colormap });
|
|
464
|
-
},
|
|
465
|
-
tick() {
|
|
466
|
-
time += 1 / 60;
|
|
467
|
-
u.time = time;
|
|
468
|
-
writeUniform();
|
|
469
|
-
const readT = trail.current.t;
|
|
470
|
-
const writeT = trail.other.t;
|
|
471
|
-
const enc = device.createCommandEncoder();
|
|
472
|
-
const pass = enc.beginComputePass();
|
|
473
|
-
pass.setPipeline(pAdvect);
|
|
474
|
-
pass.setBindGroup(0, bgAdvect(readT));
|
|
475
|
-
pass.dispatchWorkgroups(Math.ceil(N / 64));
|
|
476
|
-
pass.setPipeline(pDiffuse);
|
|
477
|
-
pass.setBindGroup(0, bgDiffuse(readT, writeT));
|
|
478
|
-
pass.dispatchWorkgroups(Math.ceil(mapSize * mapSize / 64));
|
|
479
|
-
pass.end();
|
|
480
|
-
device.queue.submit([enc.finish()]);
|
|
481
|
-
renderer?.render(writeT);
|
|
482
|
-
trail.swap();
|
|
483
|
-
frame++;
|
|
484
|
-
fFrames++;
|
|
485
|
-
const now = performance.now();
|
|
486
|
-
fAcc += now - fLast;
|
|
487
|
-
fLast = now;
|
|
488
|
-
if (fAcc >= 500) {
|
|
489
|
-
lastFps = fFrames / (fAcc / 1e3);
|
|
490
|
-
fFrames = 0;
|
|
491
|
-
fAcc = 0;
|
|
492
|
-
}
|
|
493
|
-
},
|
|
494
|
-
stats() {
|
|
495
|
-
return { fps: lastFps };
|
|
496
|
-
},
|
|
497
|
-
async sampleTrail() {
|
|
498
|
-
return await trail.current.t.read();
|
|
499
|
-
},
|
|
500
|
-
destroy() {
|
|
501
|
-
posBuf.destroy();
|
|
502
|
-
trail.destroy();
|
|
503
|
-
uniform.destroy();
|
|
504
|
-
diffuseUniform.destroy();
|
|
505
|
-
}
|
|
506
|
-
};
|
|
507
|
-
}
|
|
508
|
-
function advectWgsl(fieldFn, mapSize) {
|
|
509
|
-
return (
|
|
510
|
-
/* wgsl */
|
|
511
|
-
`
|
|
512
|
-
struct Params {
|
|
513
|
-
count: u32, _pad: u32,
|
|
514
|
-
speed: f32, worldHalf: f32, deposit: f32, time: f32,
|
|
515
|
-
_p0: f32, _p1: f32,
|
|
516
|
-
};
|
|
517
|
-
@group(0) @binding(0) var<uniform> params: Params;
|
|
518
|
-
@group(0) @binding(1) var<storage, read_write> pos: array<vec2f>;
|
|
519
|
-
@group(0) @binding(2) var<storage, read_write> trail: array<f32>;
|
|
520
|
-
|
|
521
|
-
const TRAIL_W: u32 = ${mapSize}u;
|
|
522
|
-
${fieldFn}
|
|
523
|
-
|
|
524
|
-
@compute @workgroup_size(64)
|
|
525
|
-
fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|
526
|
-
let i = gid.x;
|
|
527
|
-
if (i >= params.count) { return; }
|
|
528
|
-
let p = pos[i];
|
|
529
|
-
let v = fieldAt(p, params.time) * params.speed;
|
|
530
|
-
var np = p + v;
|
|
531
|
-
let h = params.worldHalf * 0.98;
|
|
532
|
-
if (abs(np.x) > h || abs(np.y) > h) {
|
|
533
|
-
// \u51FA\u754C\u91CD\u751F:\u968F\u673A\u6492\u56DE(\u786E\u5B9A\u6027 hash,\u514D\u989D\u5916\u968F\u673A\u6E90)
|
|
534
|
-
let r1 = fract(sin(f32(i) * 12.9898 + params.time * 78.233) * 43758.5453);
|
|
535
|
-
let r2 = fract(sin(f32(i) * 78.233 + params.time * 12.9898) * 24634.6345);
|
|
536
|
-
np = vec2f(r1, r2) * 1.8 - 0.9;
|
|
537
|
-
}
|
|
538
|
-
pos[i] = np;
|
|
539
|
-
let w = TRAIL_W;
|
|
540
|
-
let tx = min(u32((np.x * 0.5 + 0.5) * f32(w)), w - 1u);
|
|
541
|
-
let ty = min(u32((np.y * 0.5 + 0.5) * f32(w)), w - 1u);
|
|
542
|
-
let t = ty * w + tx;
|
|
543
|
-
trail[t] = trail[t] + params.deposit;
|
|
544
|
-
}
|
|
545
|
-
`
|
|
546
|
-
);
|
|
547
|
-
}
|
|
548
|
-
function diffuseWgsl() {
|
|
549
|
-
return (
|
|
550
|
-
/* wgsl */
|
|
551
|
-
`
|
|
552
|
-
@group(0) @binding(0) var<uniform> vp: vec4f; // w, h, keep, pad
|
|
553
|
-
@group(0) @binding(1) var<storage, read> src: array<f32>;
|
|
554
|
-
@group(0) @binding(2) var<storage, read_write> dst: array<f32>;
|
|
555
|
-
|
|
556
|
-
@compute @workgroup_size(64)
|
|
557
|
-
fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|
558
|
-
let i = gid.x;
|
|
559
|
-
let w = u32(vp.x);
|
|
560
|
-
let h = u32(vp.y);
|
|
561
|
-
if (i >= w * h) { return; }
|
|
562
|
-
let x = i32(i % w);
|
|
563
|
-
let y = i32(i / w);
|
|
564
|
-
let c = src[i] * 2.0;
|
|
565
|
-
var s = c;
|
|
566
|
-
s = s + src[u32(clamp(y - 1, 0, i32(h) - 1)) * w + u32(clamp(x, 0, i32(w) - 1))];
|
|
567
|
-
s = s + src[u32(clamp(y + 1, 0, i32(h) - 1)) * w + u32(clamp(x, 0, i32(w) - 1))];
|
|
568
|
-
s = s + src[u32(clamp(y, 0, i32(h) - 1)) * w + u32(clamp(x - 1, 0, i32(w) - 1))];
|
|
569
|
-
s = s + src[u32(clamp(y, 0, i32(h) - 1)) * w + u32(clamp(x + 1, 0, i32(w) - 1))];
|
|
570
|
-
dst[i] = (s / 6.0 * 0.5 + src[i] * 0.5) * vp.z;
|
|
571
|
-
}
|
|
572
|
-
`
|
|
573
|
-
);
|
|
574
|
-
}
|
|
575
|
-
function hashStr(s) {
|
|
576
|
-
let h = 2166136261;
|
|
577
|
-
for (let i = 0; i < s.length; i++) {
|
|
578
|
-
h ^= s.charCodeAt(i);
|
|
579
|
-
h = Math.imul(h, 16777619);
|
|
580
|
-
}
|
|
581
|
-
return h >>> 0;
|
|
582
|
-
}
|
|
583
|
-
export {
|
|
584
|
-
flow
|
|
585
|
-
};
|