wgpu-kit 1.1.0 → 1.1.2
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.cn.md +79 -7
- package/README.md +80 -9
- package/dist/core/buffer.js +17 -5
- package/dist/core/codes.d.ts +35 -0
- package/dist/core/codes.js +34 -0
- package/dist/core/context.js +16 -4
- package/dist/core/errors.d.ts +18 -6
- package/dist/core/errors.js +28 -13
- package/dist/core/kernel.d.ts +2 -0
- package/dist/core/kernel.js +48 -17
- package/dist/core/layout.js +7 -6
- package/dist/core/pack.d.ts +37 -0
- package/dist/core/pack.js +35 -0
- package/dist/core/pingpong.js +1 -1
- package/dist/core/raw.js +6 -6
- package/dist/core/schema.d.ts +71 -0
- package/dist/core/schema.js +118 -0
- package/dist/core/shader.d.ts +4 -0
- package/dist/core/shader.js +38 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +13 -0
- package/dist/media.js +3 -3
- package/dist/observe.js +2 -2
- package/dist/packs/fields/index.d.ts +11 -1
- package/dist/packs/fields/index.js +27 -3
- package/dist/packs/grid/index.js +5 -5
- package/dist/packs/image/index.js +4 -4
- package/dist/packs/life/boids.js +3 -3
- package/dist/packs/life/physarum.js +3 -3
- package/dist/packs/life/turing.js +3 -3
- package/dist/packs/particles/config.js +4 -4
- package/dist/packs/particles/grid.js +39 -58
- package/dist/packs/particles/index.js +42 -56
- package/dist/packs/particles/presets.js +1 -1
- package/package.json +7 -2
package/README.cn.md
CHANGED
|
@@ -51,7 +51,7 @@ await integrate.run({ pos, vel }, { dt: 0.02 });
|
|
|
51
51
|
|
|
52
52
|
| 导入 | 用途 |
|
|
53
53
|
| --- | --- |
|
|
54
|
-
| `wgpu-kit` | elementKernel 核心 + Buffer / PingPong / rawKernel |
|
|
54
|
+
| `wgpu-kit` | elementKernel 核心 + Buffer / PingPong / rawKernel + **类型化 schema** + **pack 平台** |
|
|
55
55
|
| `wgpu-kit/particles` | 粒子生命:力矩阵预设、自适应世界、热更新 |
|
|
56
56
|
| `wgpu-kit/life` | 图灵斑图 / 粘菌 / Boids / 软体触手 |
|
|
57
57
|
| `wgpu-kit/fields` | 向量场平迹 |
|
|
@@ -66,14 +66,75 @@ await integrate.run({ pos, vel }, { dt: 0.02 });
|
|
|
66
66
|
|
|
67
67
|
*life 包:图灵斑图 / 粘菌 / Boids / 软体触手 — [打开演示](https://nanfengw0w.github.io/wgpu-kit/life.html)。*
|
|
68
68
|
|
|
69
|
+
## 类型化 schema
|
|
70
|
+
|
|
71
|
+
WGSL 仍是 WGSL,但**字段表不再是会写错的字符串**。声明一次,TS 行类型、
|
|
72
|
+
WGSL struct 代码和 GPU 缓冲全部同源;拼错字段是编辑器里的红线,不是运行时错误:
|
|
73
|
+
|
|
74
|
+
```ts
|
|
75
|
+
import { defineSchema, elementKernel } from 'wgpu-kit';
|
|
76
|
+
|
|
77
|
+
const Boid = defineSchema({ pos: 'vec2f', vel: 'vec2f', species: 'u32' });
|
|
78
|
+
type Boid = SchemaInfer<typeof Boid.fields>; // { pos: {x,y}, vel: {x,y}, species: number }
|
|
79
|
+
|
|
80
|
+
const bufs = await Boid.buffers(count);
|
|
81
|
+
bufs.pos.write([{ x: 1, y: 2 }, /* … */]); // ❌ 写成 `{ z: 0 }` 编译期就报
|
|
82
|
+
const k = elementKernel({ state: Boid.fields, code: 'fn userFn(idx: u32) { … }' });
|
|
83
|
+
await k.run(bufs.raws());
|
|
84
|
+
const rows = await bufs.pos.read(); // 返回带类型的行对象
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
诚实边界:WGSL 函数体**内部**的拼写错误仍由 WGSL 编译器报错(带你的行号映射)。
|
|
88
|
+
完整的 WGSL 类型检查是编译器工程;本层消灭的是 JS/WGSL **schema 漂移**和
|
|
89
|
+
无类型的 buffer 读写。
|
|
90
|
+
|
|
91
|
+
## 平台,不是功能列表
|
|
92
|
+
|
|
93
|
+
内置包没有任何特权。`definePack` 就是内置包自己用的契约——统一生命周期、
|
|
94
|
+
统计、自验证 `probe()` 和注册表:
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
import { definePack, registerPack, listPacks } from 'wgpu-kit';
|
|
98
|
+
|
|
99
|
+
const orbit = definePack({
|
|
100
|
+
name: 'orbit',
|
|
101
|
+
description: 'my N-body toy',
|
|
102
|
+
create: async (config) => {
|
|
103
|
+
// … 用 elementKernel / rawKernel 搭你的模拟 …
|
|
104
|
+
return {
|
|
105
|
+
tick() { /* … */ },
|
|
106
|
+
async probe() { return { energyDrift: 0.003 }; }, // verify harness 会收集
|
|
107
|
+
destroy() { /* … */ },
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
registerPack(orbit);
|
|
112
|
+
listPacks(); // [{ name: 'particles', … }, { name: 'fields', … }, { name: 'orbit', … }]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
`probe()` 是平台的关键约定:第三方包在验证 harness 里享受与内置包完全相同
|
|
116
|
+
的待遇——正确性是契约的一部分,不是恩赐。
|
|
117
|
+
|
|
69
118
|
## 数字(全部可复现)
|
|
70
119
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
120
|
+
三种都是真实数字,量的是不同的东西,**别混着读**(双口径全表见
|
|
121
|
+
[docs/BENCHMARK.md](docs/BENCHMARK.md),由 `npm run bench` 生成):
|
|
122
|
+
|
|
123
|
+
- **显示帧率**:playground 里实际看到的 fps,由浏览器节流(探针:URL 加
|
|
124
|
+
`?verify=10` 自报);
|
|
125
|
+
- **管线饱和吞吐**:3 帧在途泵送——每帧提交不等完成、在途满 3 帧排空一次,
|
|
126
|
+
这是 GPU 的持续吞吐上限(`npm run bench` 的"管线 fps"列);
|
|
127
|
+
- **同步延迟**:每帧 `tick()` 后等 GPU 完成——单帧往返上界,用于算法 A/B。
|
|
128
|
+
|
|
129
|
+
| 指标 | 数值 | 口径 | 环境 |
|
|
130
|
+
| --- | --- | --- | --- |
|
|
131
|
+
| 粒子端到端 | 200,000 @ ~120fps · 66,000 @ ~144fps | 显示 | RTX 4060 Laptop,playground 探针 |
|
|
132
|
+
| 粒子计算(grid)同步 | 16k → 200k:3.6 → 36ms/帧 | 同步 | `npm run bench` → docs/BENCHMARK.md |
|
|
133
|
+
| 邻域算法 | grid 近似 O(N),66k 时比暴力快 8.5× | 同步 A/B | 同会话 |
|
|
134
|
+
| 库体积 | core gzip ~15kB,含类型化 schema 与 pack 平台(共享上下文构建) | — | gzip 预算由 build 强制 |
|
|
135
|
+
|
|
136
|
+
所以:如果你用每帧 `device.queue.onSubmittedWorkDone()` 去测 grid@200k,
|
|
137
|
+
看到的会是 ~30ms——那是同步延迟列,和 120fps 不矛盾。
|
|
77
138
|
|
|
78
139
|
## 三条设计铁律
|
|
79
140
|
|
|
@@ -81,6 +142,17 @@ await integrate.run({ pos, vel }, { dt: 0.02 });
|
|
|
81
142
|
2. **错误说人话**——WGSL 编译失败映射回你的代码行号;
|
|
82
143
|
3. **基准即文档**——所有数字可复现;gzip 体积预算由 `npm run build` 强制核对。
|
|
83
144
|
|
|
145
|
+
## 验证
|
|
146
|
+
|
|
147
|
+
41+ 自动化探针在真实 GPU 上经 headless Chromium harness 运行(随库附带:
|
|
148
|
+
`tests/` + `scripts/verify.mjs`)——包括**物理等价性回归**(n2 / tiled / grid
|
|
149
|
+
三种邻域算法结构发散即构建失败),以及扫描不变量与冻结带检测(确定性拦截
|
|
150
|
+
半格失效类 bug)。
|
|
151
|
+
|
|
152
|
+
**CI 中**:正确性子集(smoke / packages / grid 不变量)在每次 push 时于
|
|
153
|
+
Chrome 的 SwiftShader WebGPU 上运行——无需 GPU——物理正确性不会在机器之间
|
|
154
|
+
悄悄回退。fps 类探针在 CPU 适配器上没有意义,按设计只在真机上跑。
|
|
155
|
+
|
|
84
156
|
## 支持矩阵
|
|
85
157
|
|
|
86
158
|
| 浏览器 | 状态 |
|
package/README.md
CHANGED
|
@@ -91,7 +91,7 @@ to compile, the error points at **your line**.
|
|
|
91
91
|
|
|
92
92
|
| import | purpose |
|
|
93
93
|
| --- | --- |
|
|
94
|
-
| `wgpu-kit` | elementKernel core + Buffer / PingPong / rawKernel |
|
|
94
|
+
| `wgpu-kit` | elementKernel core + Buffer / PingPong / rawKernel + **typed schemas** + **pack platform** |
|
|
95
95
|
| `wgpu-kit/particles` | particle life: presets, adaptive world, live updates |
|
|
96
96
|
| `wgpu-kit/life` | Turing patterns / Physarum / Boids / Tentacles |
|
|
97
97
|
| `wgpu-kit/fields` | vector-field advection trails |
|
|
@@ -106,16 +106,81 @@ to compile, the error points at **your line**.
|
|
|
106
106
|
|
|
107
107
|
*The life pack: Turing patterns / Physarum / Boids / Tentacles — [open the demo](https://nanfengw0w.github.io/wgpu-kit/life.html).*
|
|
108
108
|
|
|
109
|
+
## Type-safe schemas
|
|
110
|
+
|
|
111
|
+
WGSL stays WGSL, but the *field table* stops being a string you can get wrong.
|
|
112
|
+
Declare once — TypeScript row types, WGSL struct code and GPU buffers all come
|
|
113
|
+
from the same declaration; a typo'd field is a red squiggle, not a runtime error:
|
|
114
|
+
|
|
115
|
+
```ts
|
|
116
|
+
import { defineSchema, elementKernel } from 'wgpu-kit';
|
|
117
|
+
|
|
118
|
+
const Boid = defineSchema({ pos: 'vec2f', vel: 'vec2f', species: 'u32' });
|
|
119
|
+
type Boid = SchemaInfer<typeof Boid.fields>; // { pos: {x,y}, vel: {x,y}, species: number }
|
|
120
|
+
|
|
121
|
+
const bufs = await Boid.buffers(count);
|
|
122
|
+
bufs.pos.write([{ x: 1, y: 2 }, /* … */]); // ❌ `{ z: 0 }` fails at compile time
|
|
123
|
+
const k = elementKernel({ state: Boid.fields, code: 'fn userFn(idx: u32) { … }' });
|
|
124
|
+
await k.run(bufs.raws());
|
|
125
|
+
const rows = await bufs.pos.read(); // typed rows back
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Honest boundary: errors inside your WGSL function body are still caught by the
|
|
129
|
+
WGSL compiler (with your-line mapping). Full WGSL type-checking is a compiler
|
|
130
|
+
project; what this layer eliminates is JS/WGSL **schema drift** and untyped
|
|
131
|
+
buffer I/O.
|
|
132
|
+
|
|
133
|
+
## A platform, not a feature list
|
|
134
|
+
|
|
135
|
+
The built-in packs are not privileged. `definePack` is the same contract they
|
|
136
|
+
use — lifecycle, stats, a self-verification `probe()` and a registry:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
import { definePack, registerPack, listPacks } from 'wgpu-kit';
|
|
140
|
+
|
|
141
|
+
const orbit = definePack({
|
|
142
|
+
name: 'orbit',
|
|
143
|
+
description: 'my N-body toy',
|
|
144
|
+
create: async (config) => {
|
|
145
|
+
// … build your sim from elementKernel / rawKernel …
|
|
146
|
+
return {
|
|
147
|
+
tick() { /* … */ },
|
|
148
|
+
async probe() { return { energyDrift: 0.003 }; }, // verify harness collects this
|
|
149
|
+
destroy() { /* … */ },
|
|
150
|
+
};
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
registerPack(orbit);
|
|
154
|
+
listPacks(); // [{ name: 'particles', … }, { name: 'fields', … }, { name: 'orbit', … }]
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
`probe()` is the platform deal: a third-party pack gets the same treatment in
|
|
158
|
+
the verification harness as the built-ins — correctness is part of the
|
|
159
|
+
contract, not a courtesy.
|
|
160
|
+
|
|
109
161
|
## Numbers (reproducible)
|
|
110
162
|
|
|
111
|
-
|
|
163
|
+
Two honest measurement conventions — both real, measuring different things. **Don't
|
|
164
|
+
mix them up** (see [docs/BENCHMARK.md](docs/BENCHMARK.md) for both, generated by
|
|
165
|
+
`npm run bench`):
|
|
166
|
+
|
|
167
|
+
- **Display fps** — what you actually see in the playground, paced by the
|
|
168
|
+
browser (probe: append `?verify=10` to any playground URL — it self-reports
|
|
169
|
+
fps and GPU errors).
|
|
170
|
+
- **Pipeline saturation** — bounded 3-frame-in-flight pump: `tick()` without
|
|
171
|
+
waiting, drain every 3 frames. The GPU's sustained throughput ceiling.
|
|
172
|
+
- **Sync latency** — `tick()` then wait for GPU completion every frame. Upper
|
|
173
|
+
bound on per-frame round-trip; used for same-session algorithm A/B.
|
|
112
174
|
|
|
113
|
-
| metric | value | environment |
|
|
114
|
-
| --- | --- | --- |
|
|
115
|
-
| particles end-to-end | 200,000 @
|
|
116
|
-
| particle compute (grid) | 16k→
|
|
117
|
-
| neighborhood algorithms | grid ~O(N), 8.5× faster than brute force at 66k |
|
|
118
|
-
| bundle size | core gzip ~
|
|
175
|
+
| metric | value | convention | environment |
|
|
176
|
+
| --- | --- | --- | --- |
|
|
177
|
+
| particles end-to-end | 200,000 @ ~120fps · 66,000 @ ~144fps | display | RTX 4060 Laptop, playground probe |
|
|
178
|
+
| particle compute (grid), sync | 16k → 200k: 3.6 → 36 ms/frame | sync | `npm run bench` → docs/BENCHMARK.md |
|
|
179
|
+
| neighborhood algorithms | grid ~O(N), 8.5× faster than brute force at 66k | sync A/B | same-session |
|
|
180
|
+
| bundle size | core gzip ~15kB incl. typed schemas + pack platform (all entries share one context) | — | enforced by `npm run build` |
|
|
181
|
+
|
|
182
|
+
So yes: if you benchmark grid @200k with a per-frame `device.queue.onSubmittedWorkDone()`
|
|
183
|
+
you will see ~30ms — that is the sync-latency column, not a contradiction.
|
|
119
184
|
|
|
120
185
|
## Three design rules
|
|
121
186
|
|
|
@@ -130,7 +195,13 @@ All fps numbers are **visible frames** — every `tick()` renders fresh state.
|
|
|
130
195
|
41+ automated probes run on a real GPU via a headless Chromium harness
|
|
131
196
|
(included: `tests/` + `scripts/verify.mjs`) — including a **physics
|
|
132
197
|
equivalence regression** that fails the build if the neighborhood algorithms
|
|
133
|
-
(n2 / tiled / grid) ever produce divergent structures
|
|
198
|
+
(n2 / tiled / grid) ever produce divergent structures, plus scan-invariant and
|
|
199
|
+
frozen-band checks that catch partial-grid failures deterministically.
|
|
200
|
+
|
|
201
|
+
**In CI:** the correctness subset (smoke / packages / grid invariants) runs on
|
|
202
|
+
every push against Chrome's SwiftShader WebGPU — no GPU required — so the
|
|
203
|
+
physics cannot silently regress between machines. fps-class probes are
|
|
204
|
+
meaningless on a CPU adapter and stay on real hardware by design.
|
|
134
205
|
|
|
135
206
|
## Support matrix
|
|
136
207
|
|
package/dist/core/buffer.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TYPES } from "./layout.js";
|
|
2
2
|
import { GpuContext } from "./context.js";
|
|
3
|
-
import { UsageError } from "./errors.js";
|
|
3
|
+
import { ERR, UsageError } from "./errors.js";
|
|
4
4
|
const TYPED_CTORS = {
|
|
5
5
|
Float32Array, Int32Array, Uint32Array,
|
|
6
6
|
};
|
|
@@ -16,6 +16,7 @@ export class Buffer {
|
|
|
16
16
|
#ctx;
|
|
17
17
|
#byteLength;
|
|
18
18
|
#stride;
|
|
19
|
+
#reading = null;
|
|
19
20
|
#staging = null;
|
|
20
21
|
constructor(ctx, kind, length, gpuBuffer) {
|
|
21
22
|
this.#ctx = ctx;
|
|
@@ -27,11 +28,11 @@ export class Buffer {
|
|
|
27
28
|
}
|
|
28
29
|
static async create(kind, length) {
|
|
29
30
|
if (!Number.isInteger(length) || length <= 0) {
|
|
30
|
-
throw new UsageError(`Buffer
|
|
31
|
+
throw new UsageError(ERR.BUFFER_CREATE, `Buffer length must be a positive integer, got: ${String(length)}`);
|
|
31
32
|
}
|
|
32
33
|
const def = TYPES[kind];
|
|
33
34
|
if (!def)
|
|
34
|
-
throw new UsageError(
|
|
35
|
+
throw new UsageError(ERR.BUFFER_CREATE, `Unknown Buffer kind "${String(kind)}". Available: ${Object.keys(TYPES).join(", ")}`);
|
|
35
36
|
const ctx = await GpuContext.get();
|
|
36
37
|
const gpuBuffer = ctx.device.createBuffer({
|
|
37
38
|
size: length * def.stride,
|
|
@@ -45,11 +46,11 @@ export class Buffer {
|
|
|
45
46
|
const def = TYPES[this.kind];
|
|
46
47
|
const ctor = TYPED_CTORS[def.typed];
|
|
47
48
|
if (!(data instanceof ctor)) {
|
|
48
|
-
throw new UsageError(`Buffer<${this.kind}>.write
|
|
49
|
+
throw new UsageError(ERR.BUFFER_WRITE, `Buffer<${this.kind}>.write expects ${def.typed}, got ${data.constructor?.name ?? typeof data}`);
|
|
49
50
|
}
|
|
50
51
|
const expected = this.length * def.comps;
|
|
51
52
|
if (data.length !== expected) {
|
|
52
|
-
throw new UsageError(`Buffer<${this.kind}>[${this.length}].write
|
|
53
|
+
throw new UsageError(ERR.BUFFER_WRITE, `Buffer<${this.kind}>[${this.length}].write expects ${expected} components, got ${data.length}`);
|
|
53
54
|
}
|
|
54
55
|
if (def.stride === def.size || def.comps === 1) {
|
|
55
56
|
this.#ctx.device.queue.writeBuffer(this.gpuBuffer, 0, data);
|
|
@@ -67,6 +68,17 @@ export class Buffer {
|
|
|
67
68
|
}
|
|
68
69
|
/** GPU → CPU:内部 staging buffer + mapAsync,mapAsync 的异步陷阱由库承担 */
|
|
69
70
|
async read() {
|
|
71
|
+
if (this.#reading)
|
|
72
|
+
return this.#reading;
|
|
73
|
+
this.#reading = this.#doRead();
|
|
74
|
+
try {
|
|
75
|
+
return await this.#reading;
|
|
76
|
+
}
|
|
77
|
+
finally {
|
|
78
|
+
this.#reading = null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async #doRead() {
|
|
70
82
|
const def = TYPES[this.kind];
|
|
71
83
|
if (!this.#staging) {
|
|
72
84
|
this.#staging = this.#ctx.device.createBuffer({
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes for programmatic identification of wgpu-kit errors.
|
|
3
|
+
* These are stable string constants — safe to switch on in user code.
|
|
4
|
+
*/
|
|
5
|
+
export declare const ERR: {
|
|
6
|
+
/** WebGPU is unavailable or the adapter could not be acquired */
|
|
7
|
+
readonly WGPU_UNAVAILABLE: "ERR_WGPU_UNAVAILABLE";
|
|
8
|
+
/** WGSL compilation failed */
|
|
9
|
+
readonly COMPILE: "ERR_COMPILE";
|
|
10
|
+
/** Invalid argument passed by the caller */
|
|
11
|
+
readonly USAGE: "ERR_USAGE";
|
|
12
|
+
/** A required uniform field is missing or has an invalid value */
|
|
13
|
+
readonly UNIFORM_FIELD: "ERR_UNIFORM_FIELD";
|
|
14
|
+
/** Uniform scalar types are not yet supported */
|
|
15
|
+
readonly UNIFORM_UNSUPPORTED: "ERR_UNIFORM_UNSUPPORTED";
|
|
16
|
+
/** workgroupSize is outside the valid range */
|
|
17
|
+
readonly WORKGROUP_SIZE: "ERR_WORKGROUP_SIZE";
|
|
18
|
+
/** A resource (Buffer) is missing from the resources map */
|
|
19
|
+
readonly RESOURCE_MISSING: "ERR_RESOURCE_MISSING";
|
|
20
|
+
/** A resource type does not match the kernel declaration */
|
|
21
|
+
readonly RESOURCE_TYPE: "ERR_RESOURCE_TYPE";
|
|
22
|
+
/** Resource lengths are inconsistent across state/inputs */
|
|
23
|
+
readonly RESOURCE_LENGTH: "ERR_RESOURCE_LENGTH";
|
|
24
|
+
/** Buffer.create received an invalid kind or length */
|
|
25
|
+
readonly BUFFER_CREATE: "ERR_BUFFER_CREATE";
|
|
26
|
+
/** Buffer.write type or component count mismatch */
|
|
27
|
+
readonly BUFFER_WRITE: "ERR_BUFFER_WRITE";
|
|
28
|
+
/** MediaRecorder is unavailable or the recording failed */
|
|
29
|
+
readonly MEDIA: "ERR_MEDIA";
|
|
30
|
+
/** timestamp-query is not supported on this device */
|
|
31
|
+
readonly TIMESTAMP_UNSUPPORTED: "ERR_TIMESTAMP_UNSUPPORTED";
|
|
32
|
+
/** A generic library error that doesn't fit any specific code */
|
|
33
|
+
readonly GENERIC: "ERR_GENERIC";
|
|
34
|
+
};
|
|
35
|
+
export type ErrorCode = (typeof ERR)[keyof typeof ERR];
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error codes for programmatic identification of wgpu-kit errors.
|
|
3
|
+
* These are stable string constants — safe to switch on in user code.
|
|
4
|
+
*/
|
|
5
|
+
export const ERR = {
|
|
6
|
+
/** WebGPU is unavailable or the adapter could not be acquired */
|
|
7
|
+
WGPU_UNAVAILABLE: 'ERR_WGPU_UNAVAILABLE',
|
|
8
|
+
/** WGSL compilation failed */
|
|
9
|
+
COMPILE: 'ERR_COMPILE',
|
|
10
|
+
/** Invalid argument passed by the caller */
|
|
11
|
+
USAGE: 'ERR_USAGE',
|
|
12
|
+
/** A required uniform field is missing or has an invalid value */
|
|
13
|
+
UNIFORM_FIELD: 'ERR_UNIFORM_FIELD',
|
|
14
|
+
/** Uniform scalar types are not yet supported */
|
|
15
|
+
UNIFORM_UNSUPPORTED: 'ERR_UNIFORM_UNSUPPORTED',
|
|
16
|
+
/** workgroupSize is outside the valid range */
|
|
17
|
+
WORKGROUP_SIZE: 'ERR_WORKGROUP_SIZE',
|
|
18
|
+
/** A resource (Buffer) is missing from the resources map */
|
|
19
|
+
RESOURCE_MISSING: 'ERR_RESOURCE_MISSING',
|
|
20
|
+
/** A resource type does not match the kernel declaration */
|
|
21
|
+
RESOURCE_TYPE: 'ERR_RESOURCE_TYPE',
|
|
22
|
+
/** Resource lengths are inconsistent across state/inputs */
|
|
23
|
+
RESOURCE_LENGTH: 'ERR_RESOURCE_LENGTH',
|
|
24
|
+
/** Buffer.create received an invalid kind or length */
|
|
25
|
+
BUFFER_CREATE: 'ERR_BUFFER_CREATE',
|
|
26
|
+
/** Buffer.write type or component count mismatch */
|
|
27
|
+
BUFFER_WRITE: 'ERR_BUFFER_WRITE',
|
|
28
|
+
/** MediaRecorder is unavailable or the recording failed */
|
|
29
|
+
MEDIA: 'ERR_MEDIA',
|
|
30
|
+
/** timestamp-query is not supported on this device */
|
|
31
|
+
TIMESTAMP_UNSUPPORTED: 'ERR_TIMESTAMP_UNSUPPORTED',
|
|
32
|
+
/** A generic library error that doesn't fit any specific code */
|
|
33
|
+
GENERIC: 'ERR_GENERIC',
|
|
34
|
+
};
|
package/dist/core/context.js
CHANGED
|
@@ -6,9 +6,17 @@ import { WebGPUUnavailableError } from "./errors.js";
|
|
|
6
6
|
export class GpuContext {
|
|
7
7
|
device;
|
|
8
8
|
adapterInfo;
|
|
9
|
-
|
|
9
|
+
/**
|
|
10
|
+
* 必须持有 adapter 强引用:adapter 是 JS 侧到 Dawn Instance 的锚。若被 GC
|
|
11
|
+
* 回收,设备的异步操作(mapAsync / getCompilationInfo)会随机 abort
|
|
12
|
+
* "A valid external Instance reference no longer exists" —— 无头 SwiftShader
|
|
13
|
+
* 上必现的重负载页面死亡根因(CI gpu-probes 第 2~7 跑的事故链)。
|
|
14
|
+
*/
|
|
15
|
+
#adapter;
|
|
16
|
+
constructor(device, adapterInfo, adapter) {
|
|
10
17
|
this.device = device;
|
|
11
18
|
this.adapterInfo = adapterInfo;
|
|
19
|
+
this.#adapter = adapter;
|
|
12
20
|
}
|
|
13
21
|
static #singleton = null;
|
|
14
22
|
/** 仅供设备丢失自动重建(observe.watchDevice)使用:重置单例 */
|
|
@@ -26,9 +34,13 @@ export class GpuContext {
|
|
|
26
34
|
}
|
|
27
35
|
static async #create() {
|
|
28
36
|
if (typeof navigator === 'undefined' || !('gpu' in navigator) || !navigator.gpu) {
|
|
29
|
-
throw new WebGPUUnavailableError('navigator.gpu
|
|
37
|
+
throw new WebGPUUnavailableError('navigator.gpu is not available');
|
|
30
38
|
}
|
|
31
|
-
|
|
39
|
+
// 机器有真 GPU 时 high-performance 命中独显;无 GPU 的 CI/虚拟机/远程桌面
|
|
40
|
+
// 场景再退到 forceFallbackAdapter(SwiftShader 软件适配器)——正确性一致,
|
|
41
|
+
// 只是慢,让探针能在任何机器上跑起来而不是直接报"无 WebGPU"。
|
|
42
|
+
const adapter = (await navigator.gpu.requestAdapter({ powerPreference: 'high-performance' })) ??
|
|
43
|
+
(await navigator.gpu.requestAdapter({ forceFallbackAdapter: true }));
|
|
32
44
|
if (!adapter)
|
|
33
45
|
throw new WebGPUUnavailableError('requestAdapter() 返回 null');
|
|
34
46
|
const info = adapter.info;
|
|
@@ -49,7 +61,7 @@ export class GpuContext {
|
|
|
49
61
|
requiredLimits[key] = supported;
|
|
50
62
|
}
|
|
51
63
|
const device = await adapter.requestDevice({ label: 'wgpu-kit', requiredLimits });
|
|
52
|
-
return new GpuContext(device, label);
|
|
64
|
+
return new GpuContext(device, label, adapter);
|
|
53
65
|
}
|
|
54
66
|
/** device lost 时 reject;调用方可 await 做清理/提示 */
|
|
55
67
|
get lost() {
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
|
-
|
|
1
|
+
import { type ErrorCode } from './codes.ts';
|
|
2
|
+
/**
|
|
3
|
+
* Error hierarchy for wgpu-kit. All errors extend WgpuKitError and carry a
|
|
4
|
+
* stable machine-readable `code` for programmatic handling.
|
|
5
|
+
*/
|
|
2
6
|
export declare class WgpuKitError extends Error {
|
|
3
|
-
|
|
7
|
+
/** Stable error code, e.g. 'ERR_WGPU_UNAVAILABLE' — safe to switch on. */
|
|
8
|
+
readonly code: ErrorCode;
|
|
9
|
+
constructor(code: ErrorCode, message: string);
|
|
4
10
|
}
|
|
5
|
-
/**
|
|
11
|
+
/** WebGPU is unavailable or the adapter could not be acquired. */
|
|
6
12
|
export declare class WebGPUUnavailableError extends WgpuKitError {
|
|
7
13
|
constructor(reason: string);
|
|
8
14
|
}
|
|
9
|
-
/** WGSL
|
|
15
|
+
/** WGSL compilation failed. Line numbers are mapped back to user code. */
|
|
10
16
|
export declare class CompileError extends WgpuKitError {
|
|
11
17
|
constructor(kernelName: string, messages: readonly {
|
|
12
18
|
line: number;
|
|
13
19
|
msg: string;
|
|
14
20
|
}[], userCodeOffset: number);
|
|
15
21
|
}
|
|
16
|
-
/**
|
|
22
|
+
/** Caller passed an invalid argument (missing resource, type mismatch, bad length, etc.). */
|
|
17
23
|
export declare class UsageError extends WgpuKitError {
|
|
24
|
+
constructor(code: ErrorCode, message: string);
|
|
18
25
|
}
|
|
19
|
-
/**
|
|
26
|
+
/** Compute pipeline creation failed validation (e.g. too many storage buffers). */
|
|
27
|
+
export declare class PipelineError extends WgpuKitError {
|
|
28
|
+
constructor(label: string, detail: string);
|
|
29
|
+
}
|
|
30
|
+
export { ERR, type ErrorCode } from './codes.ts';
|
|
31
|
+
/** Create a compute pipeline with pushErrorScope to surface async validation errors. */
|
|
20
32
|
export declare function createComputePipelineChecked(device: GPUDevice, module: GPUShaderModule, label: string, entryPoint?: string): Promise<GPUComputePipeline>;
|
package/dist/core/errors.js
CHANGED
|
@@ -1,42 +1,57 @@
|
|
|
1
|
-
|
|
1
|
+
import { ERR } from "./codes.js";
|
|
2
|
+
/**
|
|
3
|
+
* Error hierarchy for wgpu-kit. All errors extend WgpuKitError and carry a
|
|
4
|
+
* stable machine-readable `code` for programmatic handling.
|
|
5
|
+
*/
|
|
2
6
|
export class WgpuKitError extends Error {
|
|
3
|
-
|
|
7
|
+
/** Stable error code, e.g. 'ERR_WGPU_UNAVAILABLE' — safe to switch on. */
|
|
8
|
+
code;
|
|
9
|
+
constructor(code, message) {
|
|
4
10
|
super(message);
|
|
5
11
|
this.name = new.target.name;
|
|
12
|
+
this.code = code;
|
|
6
13
|
}
|
|
7
14
|
}
|
|
8
|
-
/**
|
|
15
|
+
/** WebGPU is unavailable or the adapter could not be acquired. */
|
|
9
16
|
export class WebGPUUnavailableError extends WgpuKitError {
|
|
10
17
|
constructor(reason) {
|
|
11
|
-
super(
|
|
12
|
-
'
|
|
13
|
-
' 可用 navigator.gpu 是否存在快速判断。');
|
|
18
|
+
super(ERR.WGPU_UNAVAILABLE, `WebGPU is unavailable: ${reason}\n` +
|
|
19
|
+
' Check: 1) Use Chrome/Edge 113+ or Safari 18+. 2) Enable WebGPU in headless mode. 3) Verify GPU drivers and hardware acceleration.');
|
|
14
20
|
}
|
|
15
21
|
}
|
|
16
|
-
/** WGSL
|
|
22
|
+
/** WGSL compilation failed. Line numbers are mapped back to user code. */
|
|
17
23
|
export class CompileError extends WgpuKitError {
|
|
18
24
|
constructor(kernelName, messages, userCodeOffset) {
|
|
19
25
|
const mapped = messages
|
|
20
26
|
.map((m) => {
|
|
21
27
|
const userLine = m.line - userCodeOffset;
|
|
22
|
-
const where = userLine > 0 ?
|
|
28
|
+
const where = userLine > 0 ? `your code, line ${userLine}` : `generated code, line ${m.line} (library issue — please file an issue)`;
|
|
23
29
|
return ` ${where}: ${m.msg}`;
|
|
24
30
|
})
|
|
25
31
|
.join('\n');
|
|
26
|
-
super(`kernel "${kernelName}" WGSL
|
|
32
|
+
super(ERR.COMPILE, `kernel "${kernelName}" WGSL compilation failed:\n${mapped}`);
|
|
27
33
|
}
|
|
28
34
|
}
|
|
29
|
-
/**
|
|
35
|
+
/** Caller passed an invalid argument (missing resource, type mismatch, bad length, etc.). */
|
|
30
36
|
export class UsageError extends WgpuKitError {
|
|
37
|
+
constructor(code, message) {
|
|
38
|
+
super(code, message);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/** Compute pipeline creation failed validation (e.g. too many storage buffers). */
|
|
42
|
+
export class PipelineError extends WgpuKitError {
|
|
43
|
+
constructor(label, detail) {
|
|
44
|
+
super(ERR.GENERIC, `compute pipeline "${label}" creation failed: ${detail}`);
|
|
45
|
+
}
|
|
31
46
|
}
|
|
32
|
-
|
|
47
|
+
export { ERR } from "./codes.js";
|
|
48
|
+
/** Create a compute pipeline with pushErrorScope to surface async validation errors. */
|
|
33
49
|
export async function createComputePipelineChecked(device, module, label, entryPoint = 'main') {
|
|
34
50
|
device.pushErrorScope('validation');
|
|
35
51
|
const pipeline = device.createComputePipeline({ layout: 'auto', compute: { module, entryPoint } });
|
|
36
52
|
const err = await device.popErrorScope();
|
|
37
53
|
if (err) {
|
|
38
|
-
throw new
|
|
39
|
-
常见原因:storage buffer 数超过每阶段上限(可向本库提 issue 申请 limits 支持)`);
|
|
54
|
+
throw new PipelineError(label, err.message);
|
|
40
55
|
}
|
|
41
56
|
return pipeline;
|
|
42
57
|
}
|
package/dist/core/kernel.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ interface NormalizedSpec {
|
|
|
40
40
|
inputs: Array<readonly [string, ScalarKind]>;
|
|
41
41
|
uniforms: Array<readonly [string, ScalarKind]>;
|
|
42
42
|
code: string;
|
|
43
|
+
/** 入口点静态使用的 binding 号('auto' 布局语义,见生成处的注释) */
|
|
44
|
+
usedBindings: Set<number>;
|
|
43
45
|
}
|
|
44
46
|
/** 纯函数:规范校验 + WGSL 代码生成。单测直接覆盖,不碰 GPU。 */
|
|
45
47
|
export declare function generateElementKernel(spec: ElementKernelSpec): {
|