wgpu-kit 0.9.10 → 1.0.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 (41) hide show
  1. package/README.md +8 -1
  2. package/dist/core/buffer.d.ts +1 -1
  3. package/dist/core/buffer.js +109 -0
  4. package/dist/core/context.d.ts +2 -0
  5. package/dist/core/context.js +62 -0
  6. package/dist/core/errors.js +42 -0
  7. package/dist/core/kernel.js +197 -0
  8. package/dist/core/layout.d.ts +5 -1
  9. package/dist/core/layout.js +69 -0
  10. package/dist/core/pingpong.js +61 -0
  11. package/dist/core/raw.js +38 -0
  12. package/dist/index.d.ts +2 -1
  13. package/dist/index.js +11 -484
  14. package/dist/interop/three.js +29 -0
  15. package/dist/media.js +70 -0
  16. package/dist/observe.d.ts +19 -0
  17. package/dist/observe.js +76 -0
  18. package/dist/packs/fields/index.js +230 -0
  19. package/dist/packs/image/index.js +250 -0
  20. package/dist/packs/life/boids.js +367 -0
  21. package/dist/packs/life/index.js +8 -0
  22. package/dist/packs/life/map.js +110 -0
  23. package/dist/packs/life/physarum.js +228 -0
  24. package/dist/packs/life/tentacles.js +238 -0
  25. package/dist/packs/life/turing.js +203 -0
  26. package/dist/packs/particles/config.d.ts +1 -0
  27. package/dist/packs/particles/config.js +36 -0
  28. package/dist/packs/particles/grid.js +248 -0
  29. package/dist/packs/particles/index.js +361 -0
  30. package/dist/packs/particles/presets.js +75 -0
  31. package/dist/packs/particles/render.js +116 -0
  32. package/dist/packs/particles/wgsl.js +175 -0
  33. package/dist/react/index.js +36 -0
  34. package/dist/vite.js +27 -28
  35. package/package.json +1 -1
  36. package/dist/fields.js +0 -585
  37. package/dist/image.js +0 -318
  38. package/dist/life.js +0 -1407
  39. package/dist/particles.js +0 -1245
  40. package/dist/react.js +0 -1289
  41. package/dist/three.js +0 -33
@@ -0,0 +1,75 @@
1
+ /** 粒子包的字段布局(species 独立成 u32 缓冲,方便按需着色) */
2
+ export const PARTICLE_FIELDS = {
3
+ pos: 'vec2f',
4
+ vel: 'vec2f',
5
+ species: 'u32',
6
+ };
7
+ /** 精选力矩阵(4×4,行=施加者,列=承受者;正值吸引)。调参原则:对角 0,正负平衡。 */
8
+ export const FORCE_PRESETS = {
9
+ /** 经典细胞:小团簇 + 缓慢迁移(spike 验证过的矩阵) */
10
+ cells: [
11
+ 0.0, 0.6, -0.4, 0.0,
12
+ -0.4, 0.0, 0.7, -0.2,
13
+ 0.5, -0.5, 0.0, 0.6,
14
+ -0.3, 0.4, -0.6, 0.0,
15
+ ],
16
+ /** 蛇形:链状结构与游动 */
17
+ snakes: [
18
+ 0.0, 0.7, 0.1, -0.5,
19
+ -0.3, 0.0, 0.8, -0.1,
20
+ 0.2, -0.4, 0.0, 0.7,
21
+ -0.6, 0.2, -0.3, 0.0,
22
+ ],
23
+ /** 轨道:环带与漩涡感 */
24
+ orbitals: [
25
+ 0.0, -0.5, 0.4, 0.2,
26
+ 0.5, 0.0, -0.6, 0.1,
27
+ -0.3, 0.6, 0.0, -0.4,
28
+ 0.1, -0.2, 0.5, 0.0,
29
+ ],
30
+ /** 病毒:捕食结构,红吃绿 */
31
+ viruses: [
32
+ 0.0, 0.9, -0.6, 0.1,
33
+ -0.5, 0.0, 0.3, -0.8,
34
+ 0.7, 0.2, 0.0, -0.3,
35
+ -0.2, 0.8, 0.4, 0.0,
36
+ ],
37
+ };
38
+ /** mulberry32:种子可复现(seed 支持字符串散列,URL 分享友好) */
39
+ export function mulberry32(seed) {
40
+ let s = seed | 0;
41
+ return () => {
42
+ s = (s + 0x6D2B79F5) | 0;
43
+ let t = Math.imul(s ^ (s >>> 15), 1 | s);
44
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
45
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
46
+ };
47
+ }
48
+ export function hashSeed(seed) {
49
+ let h = 2166136261;
50
+ for (let i = 0; i < seed.length; i++) {
51
+ h ^= seed.charCodeAt(i);
52
+ h = Math.imul(h, 16777619);
53
+ }
54
+ return h >>> 0;
55
+ }
56
+ /** 随机力矩阵(从种子生成,保证可复现) */
57
+ export function randomMatrix(seed) {
58
+ const rand = mulberry32(seed ^ 0x9E3779B9);
59
+ const m = Array.from({ length: 16 }, () => Math.round((rand() * 2 - 1) * 100) / 100);
60
+ for (let i = 0; i < 4; i++)
61
+ m[i * 4 + i] = 0;
62
+ return m;
63
+ }
64
+ export function resolveMatrix(forces, seed) {
65
+ if (forces === 'random')
66
+ return randomMatrix(seed);
67
+ if (typeof forces === 'string') {
68
+ const preset = FORCE_PRESETS[forces];
69
+ if (!preset) {
70
+ throw new Error(`未知力矩阵预设 "${forces}",可用: ${Object.keys(FORCE_PRESETS).join(', ')}, random`);
71
+ }
72
+ return preset;
73
+ }
74
+ return forces;
75
+ }
@@ -0,0 +1,116 @@
1
+ import { GpuContext } from "../../core/context.js";
2
+ import { Buffer } from "../../core/buffer.js";
3
+ import { CompileError } from "../../core/errors.js";
4
+ import { renderWgsl } from "./wgsl.js";
5
+ let nextId = 0;
6
+ const ids = new WeakMap();
7
+ const bufKey = (b) => {
8
+ let id = ids.get(b);
9
+ if (id === undefined) {
10
+ id = ++nextId;
11
+ ids.set(b, id);
12
+ }
13
+ return id;
14
+ };
15
+ /**
16
+ * 粒子渲染器:instanced quad + storage 只读直通(compute 产物零拷贝进 vertex 阶段)。
17
+ * 属于粒子包内部实现(不进 core);等第二、第三个包出现同类需求再考虑上提。
18
+ */
19
+ export class ParticlesRenderer {
20
+ #ctx;
21
+ #gpuCtx;
22
+ #format;
23
+ #pipeline;
24
+ #quad;
25
+ #idx;
26
+ #species;
27
+ #vel;
28
+ #colorMode;
29
+ #rs;
30
+ #bgCache = new Map();
31
+ #count;
32
+ constructor(ctx, gpuCtx, format, pipeline, quad, idx, species, count, rs, vel, colorMode) {
33
+ this.#ctx = ctx;
34
+ this.#gpuCtx = gpuCtx;
35
+ this.#format = format;
36
+ this.#pipeline = pipeline;
37
+ this.#quad = quad;
38
+ this.#idx = idx;
39
+ this.#species = species;
40
+ this.#count = count;
41
+ this.#rs = rs;
42
+ this.#vel = vel;
43
+ this.#colorMode = colorMode;
44
+ }
45
+ static async create(canvas, opts) {
46
+ const ctx = await GpuContext.get();
47
+ const gpuCtx = canvas.getContext('webgpu');
48
+ if (!gpuCtx)
49
+ throw new Error('canvas.getContext("webgpu") 返回空:该 canvas 已被其他后端占用?');
50
+ const format = navigator.gpu.getPreferredCanvasFormat();
51
+ gpuCtx.configure({ device: ctx.device, format, alphaMode: 'opaque' });
52
+ const module = ctx.device.createShaderModule({ code: renderWgsl(4, opts.color, opts.pointSize), label: 'particles-render' });
53
+ const rsUniform = ctx.device.createBuffer({ size: 16, usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST });
54
+ ctx.device.queue.writeBuffer(rsUniform, 0, new Float32Array([1 / opts.worldHalf, 0, 0, 0]));
55
+ const info = await module.getCompilationInfo();
56
+ const errors = info.messages.filter((m) => m.type === 'error');
57
+ if (errors.length > 0)
58
+ throw new CompileError('particles-render', errors.map((m) => ({ line: m.lineNum + 1, msg: m.message })), 0);
59
+ const pipeline = ctx.device.createRenderPipeline({
60
+ layout: 'auto',
61
+ vertex: {
62
+ module, entryPoint: 'vs',
63
+ buffers: [{ arrayStride: 8, attributes: [{ shaderLocation: 0, offset: 0, format: 'float32x2' }] }],
64
+ },
65
+ fragment: {
66
+ module, entryPoint: 'fs',
67
+ targets: [{ format, blend: { color: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' }, alpha: { srcFactor: 'one', dstFactor: 'one-minus-src-alpha' } } }],
68
+ },
69
+ primitive: { topology: 'triangle-list' },
70
+ });
71
+ const quad = ctx.device.createBuffer({ size: 8 * 4, usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST });
72
+ ctx.device.queue.writeBuffer(quad, 0, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]));
73
+ const idx = ctx.device.createBuffer({ size: 6 * 2, usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST });
74
+ ctx.device.queue.writeBuffer(idx, 0, new Uint16Array([0, 1, 2, 2, 1, 3]));
75
+ return new ParticlesRenderer(ctx, gpuCtx, format, pipeline, quad, idx, opts.species, opts.count, rsUniform, opts.vel ?? null, opts.color);
76
+ }
77
+ /** 渲染一帧(pos 来自 PingPong 当前侧;可传入覆盖数量) */
78
+ render(pos, vel = null, count = this.#count) {
79
+ const key = `${bufKey(pos.gpuBuffer)}:${vel ? bufKey(vel.gpuBuffer) : 0}`;
80
+ let bg = this.#bgCache.get(key);
81
+ if (!bg) {
82
+ bg = this.#ctx.device.createBindGroup({
83
+ layout: this.#pipeline.getBindGroupLayout(0),
84
+ entries: [
85
+ { binding: 0, resource: { buffer: pos.gpuBuffer } },
86
+ // 各 binding 按管线 layout 裁剪:velocity 模式的着色器不读 species,
87
+ // 物种模式不读 vel——auto layout 会剔除未使用的绑定,bind group 必须同步
88
+ ...(this.#colorMode === 'species' ? [{ binding: 1, resource: { buffer: this.#species.gpuBuffer } }] : []),
89
+ ...(this.#colorMode === 'velocity' && this.#vel && vel ? [{ binding: 2, resource: { buffer: vel.gpuBuffer } }] : []),
90
+ { binding: 3, resource: { buffer: this.#rs } },
91
+ ],
92
+ });
93
+ this.#bgCache.set(key, bg);
94
+ }
95
+ const enc = this.#ctx.device.createCommandEncoder();
96
+ const pass = enc.beginRenderPass({
97
+ colorAttachments: [{
98
+ view: this.#gpuCtx.getCurrentTexture().createView(),
99
+ clearValue: { r: 0.012, g: 0.014, b: 0.024, a: 1 },
100
+ loadOp: 'clear', storeOp: 'store',
101
+ }],
102
+ });
103
+ pass.setPipeline(this.#pipeline);
104
+ pass.setBindGroup(0, bg);
105
+ pass.setVertexBuffer(0, this.#quad);
106
+ pass.setIndexBuffer(this.#idx, 'uint16');
107
+ pass.drawIndexed(6, count);
108
+ pass.end();
109
+ this.#ctx.device.queue.submit([enc.finish()]);
110
+ }
111
+ destroy() {
112
+ this.#quad.destroy();
113
+ this.#idx.destroy();
114
+ this.#bgCache.clear();
115
+ }
116
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * 粒子模拟 WGSL 生成器。
3
+ * mode 是"路径对比驱动设计"的落点:
4
+ * - n2 每线程全量扫一遍:最简单,≤2 万粒子
5
+ * - tiled workgroup 共享内存分块加载:全局读取减少 64×,数万粒子(S3 基准)
6
+ * - grid spatial hash 两遍:10 万+(S4)
7
+ */
8
+ export const WORKGROUP = 64;
9
+ export function simWgsl(mode, speciesCount) {
10
+ const tiled = mode === 'tiled';
11
+ const common = /* wgsl */ `
12
+ struct Params {
13
+ count: u32,
14
+ _pad0: u32,
15
+ dt: f32,
16
+ rMax: f32,
17
+ beta: f32,
18
+ forceFactor: f32,
19
+ friction: f32,
20
+ worldHalf: f32,
21
+ wrapEdge: f32,
22
+ gridSize: u32,
23
+ cells: u32,
24
+ maxCand: u32,
25
+ };
26
+ @group(0) @binding(0) var<uniform> params: Params;
27
+ @group(0) @binding(1) var<storage, read> matrix: array<f32>;
28
+ @group(0) @binding(2) var<storage, read> species: array<u32>;
29
+ @group(0) @binding(3) var<storage, read> posIn: array<vec2f>;
30
+ @group(0) @binding(4) var<storage, read> velIn: array<vec2f>;
31
+ @group(0) @binding(5) var<storage, read_write> posOut: array<vec2f>;
32
+ @group(0) @binding(6) var<storage, read_write> velOut: array<vec2f>;
33
+
34
+ fn force(r: f32, a: f32) -> f32 {
35
+ if (r < params.beta) { return a / params.beta - 1.0; }
36
+ if (r < 1.0) { return a * (1.0 - abs(2.0 * r - 1.0 - params.beta) / (1.0 - params.beta)); }
37
+ return 0.0;
38
+ }
39
+ `;
40
+ // interact:lid 仅 tiled 模式需要(workgroup 内分块加载坐标)
41
+ const interactSig = tiled
42
+ ? 'fn interact(myIdx: u32, mySp: u32, myPos: vec2f, lid: u32) -> vec2f {'
43
+ : 'fn interact(myIdx: u32, mySp: u32, myPos: vec2f) -> vec2f {';
44
+ const body = tiled ? tiledBody(speciesCount) : n2Body(speciesCount);
45
+ const main = /* wgsl */ `
46
+ @compute @workgroup_size(${WORKGROUP})
47
+ fn main(@builtin(global_invocation_id) gid: vec3u${tiled ? ', @builtin(local_invocation_id) lid: vec3u' : ''}) {
48
+ let i = gid.x;
49
+ // 注意:workgroupBarrier 要求 uniform control flow——
50
+ // 越界线程也必须参与 barrier 循环,只能在最终写入处 guard(tiled 尾部 workgroup 的经典坑)
51
+ let ok = i < params.count;
52
+ let myIdx = min(i, params.count - 1u);
53
+ let mySp = species[myIdx];
54
+ let myPos = posIn[myIdx];
55
+ var accel = interact(myIdx, mySp, myPos${tiled ? ', lid.x' : ''});
56
+ accel = accel * params.forceFactor * params.rMax;
57
+ if (ok) {
58
+ var vel = (velIn[i] + accel * params.dt) * params.friction;
59
+ var pos = myPos + vel * params.dt;
60
+ let span = params.worldHalf * 2.0;
61
+ if (params.wrapEdge > 0.5) {
62
+ pos = ((pos + params.worldHalf) % span + span) % span - params.worldHalf;
63
+ } else {
64
+ pos = clamp(pos, vec2f(-params.worldHalf), vec2f(params.worldHalf));
65
+ }
66
+ posOut[i] = pos;
67
+ velOut[i] = vel;
68
+ }
69
+ }
70
+ `;
71
+ return `${common}${tiled ? TILE_DECLS : ''}\n${interactSig}${body}\n}\n${main}`;
72
+ }
73
+ function n2Body(speciesCount) {
74
+ return /* wgsl */ `
75
+ var accel = vec2f(0.0, 0.0);
76
+ let rMax2 = params.rMax * params.rMax;
77
+ for (var j = 0u; j < params.count; j++) {
78
+ if (j == myIdx) { continue; }
79
+ let rel = posIn[j] - myPos;
80
+ let d2 = dot(rel, rel);
81
+ if (d2 > rMax2) { continue; } // 距离平方 early-out:绝大多数对免开方
82
+ let d = sqrt(d2);
83
+ let r = d / params.rMax;
84
+ if (r > 0.0 && r < 1.0) {
85
+ let f = force(r, matrix[mySp * ${speciesCount}u + species[j]]);
86
+ accel = accel + rel / d * f;
87
+ }
88
+ }
89
+ return accel;
90
+ `;
91
+ }
92
+ function tiledBody(speciesCount) {
93
+ return /* wgsl */ `
94
+ var accel = vec2f(0.0, 0.0);
95
+ let rMax2 = params.rMax * params.rMax;
96
+ let tiles = (params.count + ${WORKGROUP}u - 1u) / ${WORKGROUP}u;
97
+ for (var t = 0u; t < tiles; t++) {
98
+ let loadIdx = t * ${WORKGROUP}u + lid;
99
+ tilePos[lid] = posIn[min(loadIdx, params.count - 1u)];
100
+ tileSp[lid] = species[min(loadIdx, params.count - 1u)];
101
+ workgroupBarrier();
102
+ let tileLen = min(${WORKGROUP}u, params.count - t * ${WORKGROUP}u);
103
+ for (var k = 0u; k < ${WORKGROUP}u; k++) {
104
+ if (k >= tileLen) { break; }
105
+ let j = t * ${WORKGROUP}u + k;
106
+ if (j == myIdx) { continue; }
107
+ let rel = tilePos[k] - myPos;
108
+ let d2 = dot(rel, rel);
109
+ if (d2 > rMax2) { continue; } // 距离平方 early-out
110
+ let d = sqrt(d2);
111
+ let r = d / params.rMax;
112
+ if (r > 0.0 && r < 1.0) {
113
+ let f = force(r, matrix[mySp * ${speciesCount}u + tileSp[k]]);
114
+ accel = accel + rel / d * f;
115
+ }
116
+ }
117
+ workgroupBarrier();
118
+ }
119
+ return accel;
120
+ `;
121
+ }
122
+ /** tiled 模式的 workgroup 共享内存声明 */
123
+ export const TILE_DECLS = /* wgsl */ `
124
+ var<workgroup> tilePos: array<vec2f, ${WORKGROUP}>;
125
+ var<workgroup> tileSp: array<u32, ${WORKGROUP}>;
126
+ `;
127
+ /** 渲染着色器:instanced quad + storage 只读直通(spike 验证的形态) */
128
+ export function renderWgsl(speciesCount, colorMode, pointSize) {
129
+ const palette = colorMode === 'species'
130
+ ? `const PALETTE = array<vec3f, ${speciesCount}>(
131
+ vec3f(1.00, 0.42, 0.24),
132
+ vec3f(0.36, 0.86, 0.56),
133
+ vec3f(0.36, 0.58, 1.00),
134
+ vec3f(0.98, 0.80, 0.30),
135
+ );`
136
+ : '';
137
+ const colorExpr = colorMode === 'velocity'
138
+ ? /* wgsl */ `
139
+ let speed = length(vel[inst]);
140
+ let t = 1.0 - exp(-speed * 40.0);
141
+ out.color = mix(vec3f(0.20, 0.32, 0.55), vec3f(1.0, 0.85, 0.45), t);
142
+ out.color = mix(out.color, vec3f(1.0, 0.95, 0.9), smoothstep(0.6, 1.0, t));`
143
+ : /* wgsl */ `
144
+ let sp = min(species[inst], ${speciesCount - 1}u);
145
+ out.color = PALETTE[sp];`;
146
+ const velBinding = colorMode === 'velocity' ? '\n@group(0) @binding(2) var<storage, read> vel: array<vec2f>;' : '';
147
+ return /* wgsl */ `
148
+ struct VsOut {
149
+ @builtin(position) clip: vec4f,
150
+ @location(0) uv: vec2f,
151
+ @location(1) color: vec3f,
152
+ };
153
+ ${palette}
154
+ @group(0) @binding(0) var<storage, read> pos: array<vec2f>;
155
+ @group(0) @binding(1) var<storage, read> species: array<u32>;${velBinding}
156
+ @group(0) @binding(3) var<uniform> rs: vec4f; // x = 1/worldHalf(相机缩放;binding 2 留给 velocity 模式的 vel)
157
+
158
+ @vertex
159
+ fn vs(@location(0) corner: vec2f, @builtin(instance_index) inst: u32) -> VsOut {
160
+ var out: VsOut;
161
+ out.clip = vec4f((pos[inst] + corner * ${pointSize.toFixed(4)}) * rs.x, 0.0, 1.0);
162
+ out.uv = corner;
163
+ ${colorExpr}
164
+ return out;
165
+ }
166
+
167
+ @fragment
168
+ fn fs(in: VsOut) -> @location(0) vec4f {
169
+ let d = length(in.uv);
170
+ if (d > 1.0) { discard; }
171
+ let alpha = smoothstep(1.0, 0.35, d);
172
+ return vec4f(in.color * alpha, alpha);
173
+ }
174
+ `;
175
+ }
@@ -0,0 +1,36 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from 'react';
3
+ import { particles } from "../packs/particles/index.js";
4
+ export function ParticleCanvas(props) {
5
+ const { className, style, onReady, ...config } = props;
6
+ const canvasRef = useRef(null);
7
+ useEffect(() => {
8
+ let raf = 0;
9
+ let disposed = false;
10
+ let sim = null;
11
+ void (async () => {
12
+ sim = await particles(config);
13
+ if (disposed || !canvasRef.current) {
14
+ sim.destroy();
15
+ return;
16
+ }
17
+ await sim.attach(canvasRef.current);
18
+ onReady?.(sim);
19
+ const loop = () => {
20
+ if (disposed)
21
+ return;
22
+ sim?.tick();
23
+ raf = requestAnimationFrame(loop);
24
+ };
25
+ raf = requestAnimationFrame(loop);
26
+ })();
27
+ return () => {
28
+ disposed = true;
29
+ cancelAnimationFrame(raf);
30
+ sim?.destroy();
31
+ sim = null;
32
+ };
33
+ // 刻意只在挂载时启动一次;config 变化请用 key 重建(声明式约定)
34
+ }, []);
35
+ return (_jsx("canvas", { ref: canvasRef, className: className, style: { width: '100%', height: '100%', display: 'block', background: '#05070c', ...style } }));
36
+ }
package/dist/vite.js CHANGED
@@ -1,30 +1,29 @@
1
- // src/vite.ts
2
- function wgpuKitHotReload() {
3
- return {
4
- name: "wgpu-kit:hot-reload",
5
- handleHotUpdate(ctx) {
6
- if (!ctx.file.endsWith(".wgsl")) return;
7
- void (async () => {
8
- const code = await ctx.read();
9
- ctx.server.ws.send({ type: "custom", event: "wgpu-kit:kernel", data: { file: ctx.file, code } });
10
- })();
11
- return [];
12
- }
13
- };
1
+ export function wgpuKitHotReload() {
2
+ return {
3
+ name: 'wgpu-kit:hot-reload',
4
+ handleHotUpdate(ctx) {
5
+ if (!ctx.file.endsWith('.wgsl'))
6
+ return;
7
+ void (async () => {
8
+ const code = await ctx.read();
9
+ ctx.server.ws.send({ type: 'custom', event: 'wgpu-kit:kernel', data: { file: ctx.file, code } });
10
+ })();
11
+ return []; // 阻止 Vite 默认整页刷新,替换由 hotKernel 接管
12
+ },
13
+ };
14
14
  }
15
- function hotKernel(kernel, hot, file) {
16
- if (!hot) return;
17
- const suffix = file.slice(file.lastIndexOf("/"));
18
- hot.on("wgpu-kit:kernel", (data) => {
19
- const d = data;
20
- if (typeof d?.code === "string" && typeof d.file === "string" && d.file.endsWith(suffix)) {
21
- void kernel.replace(d.code).catch((e) => {
22
- console.error("[wgpu-kit] \u70ED\u91CD\u8F7D\u5931\u8D25,\u4FDD\u7559\u65E7\u7248 kernel:", e.message);
23
- });
24
- }
25
- });
15
+ /** kernel 注册进热重载:对应 .wgsl 文件一变,自动 kernel.replace */
16
+ export function hotKernel(kernel, hot, file) {
17
+ if (!hot)
18
+ return;
19
+ const suffix = file.slice(file.lastIndexOf('/'));
20
+ hot.on('wgpu-kit:kernel', (data) => {
21
+ const d = data;
22
+ if (typeof d?.code === 'string' && typeof d.file === 'string' && d.file.endsWith(suffix)) {
23
+ void kernel.replace(d.code).catch((e) => {
24
+ // 热更新编译失败不打断运行,保留旧版并在控制台说人话
25
+ console.error('[wgpu-kit] 热重载失败,保留旧版 kernel:', e.message);
26
+ });
27
+ }
28
+ });
26
29
  }
27
- export {
28
- hotKernel,
29
- wgpuKitHotReload
30
- };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "wgpu-kit",
3
- "version": "0.9.10",
3
+ "version": "1.0.0",
4
4
  "description": "Browser GPGPU middle layer: 100k particles in 5 lines. WebGPU compute without the boilerplate.",
5
5
  "type": "module",
6
6
  "license": "MIT",