@simulatte/webgpu 0.3.1 → 0.3.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/CHANGELOG.md +27 -12
- package/LICENSE +191 -0
- package/README.md +55 -41
- package/api-contract.md +67 -49
- package/architecture.md +317 -0
- package/assets/package-layers.svg +3 -3
- package/docs/doe-api-reference.html +1842 -0
- package/doe-api-design.md +237 -0
- package/examples/doe-api/README.md +19 -0
- package/examples/doe-api/buffers-readback.js +3 -2
- package/examples/{doe-routines/compute-once-like-input.js → doe-api/compute-one-shot-like-input.js} +1 -1
- package/examples/{doe-routines/compute-once-matmul.js → doe-api/compute-one-shot-matmul.js} +2 -2
- package/examples/{doe-routines/compute-once-multiple-inputs.js → doe-api/compute-one-shot-multiple-inputs.js} +1 -1
- package/examples/{doe-routines/compute-once.js → doe-api/compute-one-shot.js} +1 -1
- package/examples/doe-api/{compile-and-dispatch.js → kernel-create-and-dispatch.js} +4 -6
- package/examples/doe-api/{compute-dispatch.js → kernel-run.js} +4 -6
- package/headless-webgpu-comparison.md +3 -3
- package/jsdoc-style-guide.md +435 -0
- package/native/doe_napi.c +1481 -84
- package/package.json +18 -6
- package/prebuilds/darwin-arm64/doe_napi.node +0 -0
- package/prebuilds/darwin-arm64/libwebgpu_doe.dylib +0 -0
- package/prebuilds/darwin-arm64/metadata.json +5 -5
- package/prebuilds/linux-x64/metadata.json +1 -1
- package/scripts/generate-doe-api-docs.js +1607 -0
- package/scripts/generate-readme-assets.js +3 -3
- package/src/build_metadata.js +7 -4
- package/src/bun-ffi.js +1229 -474
- package/src/bun.js +5 -1
- package/src/compute.d.ts +16 -7
- package/src/compute.js +84 -53
- package/src/full.d.ts +16 -7
- package/src/full.js +12 -10
- package/src/index.js +679 -1324
- package/src/runtime_cli.js +17 -17
- package/src/shared/capabilities.js +144 -0
- package/src/shared/compiler-errors.js +78 -0
- package/src/shared/encoder-surface.js +295 -0
- package/src/shared/full-surface.js +514 -0
- package/src/shared/public-surface.js +82 -0
- package/src/shared/resource-lifecycle.js +120 -0
- package/src/shared/validation.js +495 -0
- package/src/webgpu_constants.js +30 -0
- package/support-contracts.md +2 -2
- package/compat-scope.md +0 -46
- package/layering-plan.md +0 -259
- package/src/auto_bind_group_layout.js +0 -32
- package/src/doe.d.ts +0 -184
- package/src/doe.js +0 -641
- package/zig-source-inventory.md +0 -468
|
@@ -0,0 +1,1842 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
|
+
<title>Doe API reference</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
--bg: #06111a;
|
|
10
|
+
--bg-2: #0d1b2a;
|
|
11
|
+
--panel: rgba(8, 15, 26, 0.76);
|
|
12
|
+
--panel-2: rgba(12, 24, 38, 0.92);
|
|
13
|
+
--line: rgba(160, 196, 255, 0.16);
|
|
14
|
+
--text: #eef6ff;
|
|
15
|
+
--muted: #93a8bf;
|
|
16
|
+
--hot: #fb7185;
|
|
17
|
+
--cold: #22d3ee;
|
|
18
|
+
--gold: #facc15;
|
|
19
|
+
--success: #34d399;
|
|
20
|
+
--paper: rgba(255, 255, 255, 0.03);
|
|
21
|
+
--shadow: 0 30px 80px rgba(0, 0, 0, 0.35);
|
|
22
|
+
--radius: 28px;
|
|
23
|
+
--radius-sm: 18px;
|
|
24
|
+
--mono: "SFMono-Regular", Menlo, Consolas, monospace;
|
|
25
|
+
--sans: "Instrument Sans", "Inter", "Segoe UI", system-ui, sans-serif;
|
|
26
|
+
}
|
|
27
|
+
* { box-sizing: border-box; }
|
|
28
|
+
html { scroll-behavior: smooth; }
|
|
29
|
+
body {
|
|
30
|
+
margin: 0;
|
|
31
|
+
font-family: var(--sans);
|
|
32
|
+
color: var(--text);
|
|
33
|
+
background:
|
|
34
|
+
radial-gradient(circle at top left, rgba(34, 211, 238, 0.22), transparent 32%),
|
|
35
|
+
radial-gradient(circle at top right, rgba(251, 113, 133, 0.18), transparent 28%),
|
|
36
|
+
linear-gradient(160deg, var(--bg) 0%, var(--bg-2) 100%);
|
|
37
|
+
min-height: 100vh;
|
|
38
|
+
}
|
|
39
|
+
a { color: inherit; }
|
|
40
|
+
code, pre, textarea { font-family: var(--mono); }
|
|
41
|
+
.shell {
|
|
42
|
+
display: grid;
|
|
43
|
+
grid-template-columns: 248px minmax(0, 1fr);
|
|
44
|
+
gap: 20px;
|
|
45
|
+
width: min(1380px, calc(100vw - 40px));
|
|
46
|
+
margin: 20px auto 56px;
|
|
47
|
+
}
|
|
48
|
+
.rail {
|
|
49
|
+
position: sticky;
|
|
50
|
+
top: 24px;
|
|
51
|
+
height: calc(100vh - 48px);
|
|
52
|
+
border: 1px solid var(--line);
|
|
53
|
+
border-radius: var(--radius);
|
|
54
|
+
background: linear-gradient(180deg, rgba(7, 13, 23, 0.92), rgba(10, 19, 32, 0.84));
|
|
55
|
+
box-shadow: var(--shadow);
|
|
56
|
+
padding: 20px;
|
|
57
|
+
display: flex;
|
|
58
|
+
flex-direction: column;
|
|
59
|
+
gap: 20px;
|
|
60
|
+
backdrop-filter: blur(20px);
|
|
61
|
+
}
|
|
62
|
+
.brand {
|
|
63
|
+
display: grid;
|
|
64
|
+
gap: 8px;
|
|
65
|
+
}
|
|
66
|
+
.brand h1 {
|
|
67
|
+
margin: 0;
|
|
68
|
+
font-size: 1.4rem;
|
|
69
|
+
letter-spacing: -0.03em;
|
|
70
|
+
}
|
|
71
|
+
.brand p {
|
|
72
|
+
margin: 0;
|
|
73
|
+
color: var(--muted);
|
|
74
|
+
line-height: 1.5;
|
|
75
|
+
font-size: 0.95rem;
|
|
76
|
+
}
|
|
77
|
+
.search {
|
|
78
|
+
width: 100%;
|
|
79
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
80
|
+
border-radius: 14px;
|
|
81
|
+
background: rgba(255, 255, 255, 0.05);
|
|
82
|
+
color: var(--text);
|
|
83
|
+
padding: 12px 14px;
|
|
84
|
+
font: inherit;
|
|
85
|
+
}
|
|
86
|
+
.navGroup {
|
|
87
|
+
display: grid;
|
|
88
|
+
gap: 8px;
|
|
89
|
+
}
|
|
90
|
+
.navGroup h2 {
|
|
91
|
+
margin: 0 0 4px;
|
|
92
|
+
color: var(--muted);
|
|
93
|
+
font-size: 0.75rem;
|
|
94
|
+
text-transform: uppercase;
|
|
95
|
+
letter-spacing: 0.14em;
|
|
96
|
+
}
|
|
97
|
+
.navGroup a {
|
|
98
|
+
text-decoration: none;
|
|
99
|
+
color: var(--text);
|
|
100
|
+
padding: 10px 12px;
|
|
101
|
+
border-radius: 12px;
|
|
102
|
+
background: rgba(255, 255, 255, 0.03);
|
|
103
|
+
border: 1px solid transparent;
|
|
104
|
+
transition: 160ms ease;
|
|
105
|
+
}
|
|
106
|
+
.navGroup a:hover {
|
|
107
|
+
border-color: rgba(255, 255, 255, 0.12);
|
|
108
|
+
transform: translateX(3px);
|
|
109
|
+
}
|
|
110
|
+
.navMeta {
|
|
111
|
+
margin-top: auto;
|
|
112
|
+
color: var(--muted);
|
|
113
|
+
font-size: 0.85rem;
|
|
114
|
+
line-height: 1.6;
|
|
115
|
+
}
|
|
116
|
+
main {
|
|
117
|
+
display: grid;
|
|
118
|
+
gap: 24px;
|
|
119
|
+
}
|
|
120
|
+
.hero,
|
|
121
|
+
.panel,
|
|
122
|
+
.supportPanel {
|
|
123
|
+
border: 1px solid var(--line);
|
|
124
|
+
border-radius: var(--radius);
|
|
125
|
+
background: linear-gradient(180deg, rgba(10, 18, 30, 0.86), rgba(13, 23, 37, 0.78));
|
|
126
|
+
box-shadow: var(--shadow);
|
|
127
|
+
backdrop-filter: blur(18px);
|
|
128
|
+
}
|
|
129
|
+
.hero {
|
|
130
|
+
padding: 36px;
|
|
131
|
+
overflow: hidden;
|
|
132
|
+
position: relative;
|
|
133
|
+
}
|
|
134
|
+
.hero::after {
|
|
135
|
+
content: "";
|
|
136
|
+
position: absolute;
|
|
137
|
+
inset: auto -60px -60px auto;
|
|
138
|
+
width: 240px;
|
|
139
|
+
height: 240px;
|
|
140
|
+
border-radius: 999px;
|
|
141
|
+
background: radial-gradient(circle, rgba(250, 204, 21, 0.3), transparent 68%);
|
|
142
|
+
pointer-events: none;
|
|
143
|
+
}
|
|
144
|
+
.eyebrow {
|
|
145
|
+
color: var(--gold);
|
|
146
|
+
font-size: 0.78rem;
|
|
147
|
+
text-transform: uppercase;
|
|
148
|
+
letter-spacing: 0.18em;
|
|
149
|
+
margin-bottom: 10px;
|
|
150
|
+
}
|
|
151
|
+
.hero h2 {
|
|
152
|
+
margin: 0;
|
|
153
|
+
max-width: 12ch;
|
|
154
|
+
font-size: clamp(2.3rem, 4.8vw, 4.6rem);
|
|
155
|
+
letter-spacing: -0.06em;
|
|
156
|
+
line-height: 0.98;
|
|
157
|
+
}
|
|
158
|
+
.hero p {
|
|
159
|
+
max-width: 680px;
|
|
160
|
+
color: var(--muted);
|
|
161
|
+
line-height: 1.75;
|
|
162
|
+
font-size: 1rem;
|
|
163
|
+
margin: 18px 0 0;
|
|
164
|
+
white-space: pre-line;
|
|
165
|
+
}
|
|
166
|
+
.heroGrid {
|
|
167
|
+
display: grid;
|
|
168
|
+
grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.9fr);
|
|
169
|
+
gap: 24px;
|
|
170
|
+
align-items: end;
|
|
171
|
+
}
|
|
172
|
+
.heroStats {
|
|
173
|
+
display: grid;
|
|
174
|
+
gap: 14px;
|
|
175
|
+
}
|
|
176
|
+
.heroStat {
|
|
177
|
+
border-radius: 18px;
|
|
178
|
+
padding: 18px;
|
|
179
|
+
background: rgba(255, 255, 255, 0.04);
|
|
180
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
181
|
+
}
|
|
182
|
+
.heroStat strong {
|
|
183
|
+
display: block;
|
|
184
|
+
font-size: 1.65rem;
|
|
185
|
+
letter-spacing: -0.04em;
|
|
186
|
+
}
|
|
187
|
+
.heroStat span {
|
|
188
|
+
color: var(--muted);
|
|
189
|
+
display: block;
|
|
190
|
+
margin-top: 6px;
|
|
191
|
+
line-height: 1.5;
|
|
192
|
+
}
|
|
193
|
+
.sectionHeader {
|
|
194
|
+
display: flex;
|
|
195
|
+
justify-content: space-between;
|
|
196
|
+
gap: 16px;
|
|
197
|
+
align-items: end;
|
|
198
|
+
margin-bottom: 18px;
|
|
199
|
+
}
|
|
200
|
+
.sectionHeader h2 {
|
|
201
|
+
margin: 0;
|
|
202
|
+
font-size: 1.7rem;
|
|
203
|
+
letter-spacing: -0.04em;
|
|
204
|
+
}
|
|
205
|
+
.sectionHeader p {
|
|
206
|
+
margin: 0;
|
|
207
|
+
color: var(--muted);
|
|
208
|
+
max-width: 720px;
|
|
209
|
+
line-height: 1.6;
|
|
210
|
+
}
|
|
211
|
+
.panel {
|
|
212
|
+
padding: 28px;
|
|
213
|
+
}
|
|
214
|
+
.statusGrid,
|
|
215
|
+
.apiGrid,
|
|
216
|
+
.examplesGrid {
|
|
217
|
+
display: grid;
|
|
218
|
+
gap: 18px;
|
|
219
|
+
}
|
|
220
|
+
.statusGrid {
|
|
221
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
222
|
+
}
|
|
223
|
+
.statusCard,
|
|
224
|
+
.apiCard,
|
|
225
|
+
.exampleCard {
|
|
226
|
+
border-radius: 22px;
|
|
227
|
+
background: rgba(255, 255, 255, 0.04);
|
|
228
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
229
|
+
}
|
|
230
|
+
.statusCard {
|
|
231
|
+
padding: 18px;
|
|
232
|
+
}
|
|
233
|
+
.statusCard dt {
|
|
234
|
+
font-size: 0.76rem;
|
|
235
|
+
text-transform: uppercase;
|
|
236
|
+
letter-spacing: 0.16em;
|
|
237
|
+
color: var(--muted);
|
|
238
|
+
}
|
|
239
|
+
.statusCard dd {
|
|
240
|
+
margin: 12px 0 0;
|
|
241
|
+
font-size: 1.2rem;
|
|
242
|
+
letter-spacing: -0.03em;
|
|
243
|
+
}
|
|
244
|
+
.apiGrid {
|
|
245
|
+
grid-template-columns: minmax(0, 1fr);
|
|
246
|
+
}
|
|
247
|
+
.apiCard {
|
|
248
|
+
padding: 20px;
|
|
249
|
+
display: grid;
|
|
250
|
+
gap: 16px;
|
|
251
|
+
}
|
|
252
|
+
.apiHeader {
|
|
253
|
+
display: flex;
|
|
254
|
+
justify-content: space-between;
|
|
255
|
+
gap: 16px;
|
|
256
|
+
align-items: start;
|
|
257
|
+
}
|
|
258
|
+
.apiHeader h3,
|
|
259
|
+
.exampleTop h3 {
|
|
260
|
+
margin: 4px 0 0;
|
|
261
|
+
font-size: 1.3rem;
|
|
262
|
+
letter-spacing: -0.03em;
|
|
263
|
+
}
|
|
264
|
+
.signature,
|
|
265
|
+
.filename {
|
|
266
|
+
white-space: nowrap;
|
|
267
|
+
border-radius: 999px;
|
|
268
|
+
padding: 8px 12px;
|
|
269
|
+
background: rgba(34, 211, 238, 0.1);
|
|
270
|
+
border: 1px solid rgba(34, 211, 238, 0.24);
|
|
271
|
+
color: #baf7ff;
|
|
272
|
+
font-size: 0.84rem;
|
|
273
|
+
}
|
|
274
|
+
.summary,
|
|
275
|
+
.detailsBody p,
|
|
276
|
+
.outputMeta {
|
|
277
|
+
margin: 0;
|
|
278
|
+
color: var(--muted);
|
|
279
|
+
line-height: 1.6;
|
|
280
|
+
}
|
|
281
|
+
.contractGrid {
|
|
282
|
+
display: grid;
|
|
283
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
284
|
+
gap: 14px;
|
|
285
|
+
margin: 0;
|
|
286
|
+
}
|
|
287
|
+
.contractGrid div {
|
|
288
|
+
padding: 14px;
|
|
289
|
+
border-radius: 16px;
|
|
290
|
+
background: rgba(255, 255, 255, 0.03);
|
|
291
|
+
border: 1px solid rgba(255, 255, 255, 0.07);
|
|
292
|
+
}
|
|
293
|
+
.contractGrid dt {
|
|
294
|
+
color: var(--gold);
|
|
295
|
+
font-size: 0.78rem;
|
|
296
|
+
text-transform: uppercase;
|
|
297
|
+
letter-spacing: 0.14em;
|
|
298
|
+
margin-bottom: 8px;
|
|
299
|
+
}
|
|
300
|
+
.contractGrid dd {
|
|
301
|
+
margin: 0;
|
|
302
|
+
line-height: 1.55;
|
|
303
|
+
}
|
|
304
|
+
.notes {
|
|
305
|
+
margin: 0;
|
|
306
|
+
padding-left: 18px;
|
|
307
|
+
color: #d6e7fb;
|
|
308
|
+
display: grid;
|
|
309
|
+
gap: 8px;
|
|
310
|
+
}
|
|
311
|
+
.apiExample summary {
|
|
312
|
+
cursor: pointer;
|
|
313
|
+
color: #d6e7fb;
|
|
314
|
+
}
|
|
315
|
+
.apiExample pre,
|
|
316
|
+
.outputBlock,
|
|
317
|
+
.editor {
|
|
318
|
+
margin: 0;
|
|
319
|
+
background: rgba(3, 7, 13, 0.92);
|
|
320
|
+
border-radius: 18px;
|
|
321
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
322
|
+
color: #dbebff;
|
|
323
|
+
font-size: 0.9rem;
|
|
324
|
+
line-height: 1.55;
|
|
325
|
+
}
|
|
326
|
+
.apiExample pre,
|
|
327
|
+
.outputBlock {
|
|
328
|
+
padding: 16px;
|
|
329
|
+
overflow: auto;
|
|
330
|
+
}
|
|
331
|
+
.linkRow,
|
|
332
|
+
.exampleLinks,
|
|
333
|
+
.buttonRow {
|
|
334
|
+
display: flex;
|
|
335
|
+
flex-wrap: wrap;
|
|
336
|
+
gap: 10px;
|
|
337
|
+
align-items: center;
|
|
338
|
+
}
|
|
339
|
+
.linkRow a,
|
|
340
|
+
.exampleLinks a,
|
|
341
|
+
.ghostLink {
|
|
342
|
+
text-decoration: none;
|
|
343
|
+
color: #c7dbf7;
|
|
344
|
+
padding: 8px 12px;
|
|
345
|
+
border-radius: 999px;
|
|
346
|
+
background: rgba(255, 255, 255, 0.03);
|
|
347
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
348
|
+
}
|
|
349
|
+
.examplesGrid {
|
|
350
|
+
grid-template-columns: minmax(0, 1fr);
|
|
351
|
+
}
|
|
352
|
+
.exampleCard {
|
|
353
|
+
padding: 20px;
|
|
354
|
+
display: grid;
|
|
355
|
+
gap: 14px;
|
|
356
|
+
}
|
|
357
|
+
.exampleTop {
|
|
358
|
+
display: flex;
|
|
359
|
+
justify-content: space-between;
|
|
360
|
+
gap: 16px;
|
|
361
|
+
align-items: start;
|
|
362
|
+
}
|
|
363
|
+
.editor {
|
|
364
|
+
width: 100%;
|
|
365
|
+
min-height: 240px;
|
|
366
|
+
resize: vertical;
|
|
367
|
+
padding: 18px;
|
|
368
|
+
white-space: pre;
|
|
369
|
+
}
|
|
370
|
+
.runButton,
|
|
371
|
+
.ghostButton {
|
|
372
|
+
appearance: none;
|
|
373
|
+
border: none;
|
|
374
|
+
cursor: pointer;
|
|
375
|
+
border-radius: 999px;
|
|
376
|
+
padding: 11px 15px;
|
|
377
|
+
font: inherit;
|
|
378
|
+
color: var(--text);
|
|
379
|
+
}
|
|
380
|
+
.runButton {
|
|
381
|
+
background: linear-gradient(135deg, var(--cold), #60a5fa);
|
|
382
|
+
color: #041521;
|
|
383
|
+
font-weight: 700;
|
|
384
|
+
}
|
|
385
|
+
.ghostButton {
|
|
386
|
+
background: rgba(255, 255, 255, 0.06);
|
|
387
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
388
|
+
}
|
|
389
|
+
.outputShell {
|
|
390
|
+
display: grid;
|
|
391
|
+
gap: 12px;
|
|
392
|
+
}
|
|
393
|
+
.metricChips {
|
|
394
|
+
display: flex;
|
|
395
|
+
flex-wrap: wrap;
|
|
396
|
+
gap: 10px;
|
|
397
|
+
}
|
|
398
|
+
.metricChip,
|
|
399
|
+
.valuePill {
|
|
400
|
+
border-radius: 999px;
|
|
401
|
+
padding: 8px 12px;
|
|
402
|
+
background: var(--paper);
|
|
403
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
404
|
+
color: #d6e7fb;
|
|
405
|
+
font-size: 0.85rem;
|
|
406
|
+
}
|
|
407
|
+
.chartCanvas {
|
|
408
|
+
width: 100%;
|
|
409
|
+
height: 220px;
|
|
410
|
+
display: block;
|
|
411
|
+
border-radius: 18px;
|
|
412
|
+
background: rgba(3, 7, 13, 0.92);
|
|
413
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
414
|
+
}
|
|
415
|
+
.valueGrid {
|
|
416
|
+
display: flex;
|
|
417
|
+
flex-wrap: wrap;
|
|
418
|
+
gap: 10px;
|
|
419
|
+
}
|
|
420
|
+
.editorDetails {
|
|
421
|
+
border-radius: 18px;
|
|
422
|
+
background: var(--paper);
|
|
423
|
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
424
|
+
overflow: hidden;
|
|
425
|
+
}
|
|
426
|
+
.editorDetails summary {
|
|
427
|
+
cursor: pointer;
|
|
428
|
+
padding: 14px 16px;
|
|
429
|
+
color: #d6e7fb;
|
|
430
|
+
font-weight: 600;
|
|
431
|
+
}
|
|
432
|
+
.editorShell {
|
|
433
|
+
padding: 0 16px 16px;
|
|
434
|
+
}
|
|
435
|
+
.hidden {
|
|
436
|
+
display: none !important;
|
|
437
|
+
}
|
|
438
|
+
.footerNote {
|
|
439
|
+
color: var(--muted);
|
|
440
|
+
line-height: 1.7;
|
|
441
|
+
}
|
|
442
|
+
@media (max-width: 1180px) {
|
|
443
|
+
.shell {
|
|
444
|
+
grid-template-columns: 1fr;
|
|
445
|
+
}
|
|
446
|
+
.rail {
|
|
447
|
+
position: static;
|
|
448
|
+
height: auto;
|
|
449
|
+
}
|
|
450
|
+
.apiGrid,
|
|
451
|
+
.examplesGrid,
|
|
452
|
+
.statusGrid,
|
|
453
|
+
.heroGrid,
|
|
454
|
+
.contractGrid {
|
|
455
|
+
grid-template-columns: 1fr;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
</style>
|
|
459
|
+
</head>
|
|
460
|
+
<body>
|
|
461
|
+
<div class="shell">
|
|
462
|
+
<aside class="rail">
|
|
463
|
+
<div class="brand">
|
|
464
|
+
<div class="eyebrow">Doe API</div>
|
|
465
|
+
<h1>Interactive reference</h1>
|
|
466
|
+
<p>Generated from the real Doe source docs, type surface, and shipped examples.</p>
|
|
467
|
+
</div>
|
|
468
|
+
<input class="search" id="search" type="search" placeholder="Search API and examples" />
|
|
469
|
+
<nav class="navGroup">
|
|
470
|
+
<h2>Jump to</h2>
|
|
471
|
+
<a href="#overview">Overview</a>
|
|
472
|
+
<a href="#runtime">Runtime check</a>
|
|
473
|
+
<a href="#api">API reference</a>
|
|
474
|
+
<a href="#examples">Live examples</a>
|
|
475
|
+
</nav>
|
|
476
|
+
<div class="navGroup">
|
|
477
|
+
<h2>Primary APIs</h2>
|
|
478
|
+
<a href="#doe.requestDevice">doe.requestDevice</a><a href="#doe.bind">doe.bind</a><a href="#gpu.buffer.create">gpu.buffer.create</a><a href="#gpu.buffer.read">gpu.buffer.read</a><a href="#gpu.kernel.run">gpu.kernel.run</a><a href="#gpu.kernel.create">gpu.kernel.create</a><a href="#DoeKernel">DoeKernel</a><a href="#DoeKernel.dispatch">kernel.dispatch</a><a href="#gpu.compute">gpu.compute</a>
|
|
479
|
+
</div>
|
|
480
|
+
<div class="navMeta">
|
|
481
|
+
<div>Output: <code>docs/doe-api-reference.html</code></div>
|
|
482
|
+
<div>Source docs: <code>@simulatte/webgpu-doe/src/index.js</code></div>
|
|
483
|
+
<div>Type surface: <code>@simulatte/webgpu-doe/src/index.d.ts</code></div>
|
|
484
|
+
<div>Examples: <code>examples/doe-api/</code></div>
|
|
485
|
+
</div>
|
|
486
|
+
</aside>
|
|
487
|
+
<main>
|
|
488
|
+
<section class="hero" id="overview">
|
|
489
|
+
<div class="heroGrid">
|
|
490
|
+
<div>
|
|
491
|
+
<div class="eyebrow">2026 package docs</div>
|
|
492
|
+
<h2>Doe API, as code and as contract.</h2>
|
|
493
|
+
<p>`@simulatte/webgpu` is Fawn's headless WebGPU package for Node.js and Bun: use
|
|
494
|
+
the raw WebGPU API through `requestDevice()` and `device.*`, or move up to the
|
|
495
|
+
Doe API when you want the same runtime with less setup. Browser DOM/canvas
|
|
496
|
+
ownership lives in the separate `nursery/fawn-browser` lane.
|
|
497
|
+
|
|
498
|
+
Terminology in this README is deliberate:
|
|
499
|
+
|
|
500
|
+
- `Doe runtime` means the Zig/native WebGPU runtime underneath the package
|
|
501
|
+
- `Doe API` means the explicit JS convenience surface under `doe`, `gpu.buffer.*`,
|
|
502
|
+
`gpu.kernel.run(...)`, `gpu.kernel.create(...)`, and `gpu.compute(...)`
|
|
503
|
+
|
|
504
|
+
The current implemented helper contract is documented in
|
|
505
|
+
[api-contract.md](./api-contract.md). The proposed naming cleanup for the Doe
|
|
506
|
+
helper surface is documented in [doe-api-design.md](./doe-api-design.md).
|
|
507
|
+
The generated interactive Doe API reference lives at
|
|
508
|
+
[docs/doe-api-reference.html](./docs/doe-api-reference.html).
|
|
509
|
+
|
|
510
|
+
The same helper layer is now also published separately as
|
|
511
|
+
`@simulatte/webgpu-doe` when you want the Doe API as an independent package
|
|
512
|
+
boundary.</p>
|
|
513
|
+
</div>
|
|
514
|
+
<div class="heroStats">
|
|
515
|
+
<div class="heroStat">
|
|
516
|
+
<strong>9</strong>
|
|
517
|
+
<span>public Doe API entries documented from current JSDoc and type shape</span>
|
|
518
|
+
</div>
|
|
519
|
+
<div class="heroStat">
|
|
520
|
+
<strong>7</strong>
|
|
521
|
+
<span>shipped Doe examples, live-editable and runnable in a browser with WebGPU</span>
|
|
522
|
+
</div>
|
|
523
|
+
<div class="heroStat">
|
|
524
|
+
<strong>1 page</strong>
|
|
525
|
+
<span>API reference, live examples, and runtime status in one self-contained artifact</span>
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
</section>
|
|
530
|
+
|
|
531
|
+
<section class="panel" id="runtime">
|
|
532
|
+
<div class="sectionHeader">
|
|
533
|
+
<div>
|
|
534
|
+
<div class="eyebrow">Runtime check</div>
|
|
535
|
+
<h2>Can this browser run the examples?</h2>
|
|
536
|
+
</div>
|
|
537
|
+
<p>The page executes the shipped Doe examples through a browser-side Doe demo adapter over WebGPU. It runs real WGSL and real GPU work when WebGPU is available.</p>
|
|
538
|
+
</div>
|
|
539
|
+
<div class="statusGrid">
|
|
540
|
+
<dl class="statusCard">
|
|
541
|
+
<dt>WebGPU</dt>
|
|
542
|
+
<dd id="status-webgpu">Checking…</dd>
|
|
543
|
+
</dl>
|
|
544
|
+
<dl class="statusCard">
|
|
545
|
+
<dt>Adapter</dt>
|
|
546
|
+
<dd id="status-adapter">Pending</dd>
|
|
547
|
+
</dl>
|
|
548
|
+
<dl class="statusCard">
|
|
549
|
+
<dt>Device</dt>
|
|
550
|
+
<dd id="status-device">Pending</dd>
|
|
551
|
+
</dl>
|
|
552
|
+
</div>
|
|
553
|
+
</section>
|
|
554
|
+
|
|
555
|
+
<section class="panel" id="api">
|
|
556
|
+
<div class="sectionHeader">
|
|
557
|
+
<div>
|
|
558
|
+
<div class="eyebrow">API reference</div>
|
|
559
|
+
<h2>Current shipped Doe surface</h2>
|
|
560
|
+
</div>
|
|
561
|
+
<p>Each card is generated from the public JSDoc in <code>@simulatte/webgpu-doe/src/index.js</code> and linked back to the implementation and type surface.</p>
|
|
562
|
+
</div>
|
|
563
|
+
<div class="apiGrid">
|
|
564
|
+
|
|
565
|
+
<article class="apiCard searchTarget" data-search="doe.requestdevice doe.requestdevice(options?) -> promise<gpu> request a device and return the bound doe api in one step. doe api namespace. optional package-local request options. a promise for the bound gpu helper object. this calls the package-local requestdevice(...) implementation and then wraps the resulting raw device in the bound doe api. throws if this namespace was created without a requestdevice implementation. gpu.device exposes the underlying raw device when you need lower-level control. see doe.bind(device) when you already have a raw device." id="doe.requestDevice">
|
|
566
|
+
<div class="apiHeader">
|
|
567
|
+
<div>
|
|
568
|
+
<div class="eyebrow">Doe API namespace.</div>
|
|
569
|
+
<h3>doe.requestDevice</h3>
|
|
570
|
+
</div>
|
|
571
|
+
<code class="signature">doe.requestDevice(options?) -> Promise<gpu></code>
|
|
572
|
+
</div>
|
|
573
|
+
<p class="summary">Request a device and return the bound Doe API in one step.</p>
|
|
574
|
+
<dl class="contractGrid">
|
|
575
|
+
<div><dt>Input</dt><dd>Optional package-local request options.</dd></div>
|
|
576
|
+
<div><dt>Returns</dt><dd>A promise for the bound gpu helper object.</dd></div>
|
|
577
|
+
</dl>
|
|
578
|
+
<div class="detailsBody"><p>This calls the package-local requestDevice(...) implementation and</p><p>then wraps the resulting raw device in the bound Doe API.</p></div>
|
|
579
|
+
<ul class="notes"><li>Throws if this namespace was created without a requestDevice implementation.</li><li>gpu.device exposes the underlying raw device when you need lower-level control.</li><li>See doe.bind(device) when you already have a raw device.</li></ul>
|
|
580
|
+
<details class="apiExample"><summary>JSDoc example</summary><pre><code>const gpu = await doe.requestDevice();</code></pre></details>
|
|
581
|
+
<div class="linkRow">
|
|
582
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.js">Source</a>
|
|
583
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.d.ts">Types</a>
|
|
584
|
+
</div>
|
|
585
|
+
</article>
|
|
586
|
+
|
|
587
|
+
<article class="apiCard searchTarget" data-search="doe.bind doe.bind(device) -> gpu wrap an existing device in the bound doe api. doe api namespace. a raw device returned by the package surface. the bound gpu helper object for that device. use this when you need the raw device first, but still want to opt into doe helpers afterward. no async work happens here; it only wraps the device you already have. see doe.requestdevice(...) for the one-step helper entrypoint." id="doe.bind">
|
|
588
|
+
<div class="apiHeader">
|
|
589
|
+
<div>
|
|
590
|
+
<div class="eyebrow">Doe API namespace.</div>
|
|
591
|
+
<h3>doe.bind</h3>
|
|
592
|
+
</div>
|
|
593
|
+
<code class="signature">doe.bind(device) -> gpu</code>
|
|
594
|
+
</div>
|
|
595
|
+
<p class="summary">Wrap an existing device in the bound Doe API.</p>
|
|
596
|
+
<dl class="contractGrid">
|
|
597
|
+
<div><dt>Input</dt><dd>A raw device returned by the package surface.</dd></div>
|
|
598
|
+
<div><dt>Returns</dt><dd>The bound gpu helper object for that device.</dd></div>
|
|
599
|
+
</dl>
|
|
600
|
+
<div class="detailsBody"><p>Use this when you need the raw device first, but still want to opt into</p><p>Doe helpers afterward.</p></div>
|
|
601
|
+
<ul class="notes"><li>No async work happens here; it only wraps the device you already have.</li><li>See doe.requestDevice(...) for the one-step helper entrypoint.</li></ul>
|
|
602
|
+
<details class="apiExample"><summary>JSDoc example</summary><pre><code>const device = await requestDevice();
|
|
603
|
+
const gpu = doe.bind(device);</code></pre></details>
|
|
604
|
+
<div class="linkRow">
|
|
605
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.js">Source</a>
|
|
606
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.d.ts">Types</a>
|
|
607
|
+
</div>
|
|
608
|
+
</article>
|
|
609
|
+
|
|
610
|
+
<article class="apiCard searchTarget" data-search="gpu.buffer.create gpu.buffer.create(options) -> gpubuffer create a buffer with explicit size and doe usage tokens. doe api gpu.buffer. a buffer size, usage, and optional label or mapping flag. a gpu buffer with doe usage metadata attached when possible. this is the explicit doe helper over device.createbuffer(...). it accepts doe usage tokens such as storagereadwrite, and when data is provided it allocates and uploads in one step. doe remembers the resulting binding access so later helper calls can infer how the buffer should be bound. when data is provided, usage defaults to storageread. raw numeric usage flags are allowed here for explicit control. buffers created with raw numeric flags may later require { buffer, access }." id="gpu.buffer.create">
|
|
611
|
+
<div class="apiHeader">
|
|
612
|
+
<div>
|
|
613
|
+
<div class="eyebrow">Doe API gpu.buffer.</div>
|
|
614
|
+
<h3>gpu.buffer.create</h3>
|
|
615
|
+
</div>
|
|
616
|
+
<code class="signature">gpu.buffer.create(options) -> GPUBuffer</code>
|
|
617
|
+
</div>
|
|
618
|
+
<p class="summary">Create a buffer with explicit size and Doe usage tokens.</p>
|
|
619
|
+
<dl class="contractGrid">
|
|
620
|
+
<div><dt>Input</dt><dd>A buffer size, usage, and optional label or mapping flag.</dd></div>
|
|
621
|
+
<div><dt>Returns</dt><dd>A GPU buffer with Doe usage metadata attached when possible.</dd></div>
|
|
622
|
+
</dl>
|
|
623
|
+
<div class="detailsBody"><p>This is the explicit Doe helper over device.createBuffer(...). It</p><p>accepts Doe usage tokens such as storageReadWrite, and when data</p><p>is provided it allocates and uploads in one step. Doe remembers the</p><p>resulting binding access so later helper calls can infer how the</p><p>buffer should be bound.</p></div>
|
|
624
|
+
<ul class="notes"><li>When data is provided, usage defaults to storageRead.</li><li>Raw numeric usage flags are allowed here for explicit control.</li><li>Buffers created with raw numeric flags may later require { buffer, access }.</li></ul>
|
|
625
|
+
<details class="apiExample"><summary>JSDoc example</summary><pre><code>const src = gpu.buffer.create({ data: new Float32Array([1, 2, 3, 4]) });
|
|
626
|
+
const dst = gpu.buffer.create({ size: src.size, usage: "storageReadWrite" });</code></pre></details>
|
|
627
|
+
<div class="linkRow">
|
|
628
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.js">Source</a>
|
|
629
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.d.ts">Types</a>
|
|
630
|
+
</div>
|
|
631
|
+
</article>
|
|
632
|
+
|
|
633
|
+
<article class="apiCard searchTarget" data-search="gpu.buffer.read gpu.buffer.read(options) -> promise<typedarray> read a buffer back into a typed array. doe api gpu.buffer. a source buffer, a typed-array constructor, and optional offset or size. a promise for a newly allocated typed array. this reads gpu buffer contents back to js. if the buffer is already mappable for read, doe maps it directly; otherwise doe stages the copy through a temporary readback buffer. options.offset and options.size let you read a subrange. the typed-array constructor must accept a plain arraybuffer. see raw buffer.mapasync(...) when you need manual readback control." id="gpu.buffer.read">
|
|
634
|
+
<div class="apiHeader">
|
|
635
|
+
<div>
|
|
636
|
+
<div class="eyebrow">Doe API gpu.buffer.</div>
|
|
637
|
+
<h3>gpu.buffer.read</h3>
|
|
638
|
+
</div>
|
|
639
|
+
<code class="signature">gpu.buffer.read(options) -> Promise<TypedArray></code>
|
|
640
|
+
</div>
|
|
641
|
+
<p class="summary">Read a buffer back into a typed array.</p>
|
|
642
|
+
<dl class="contractGrid">
|
|
643
|
+
<div><dt>Input</dt><dd>A source buffer, a typed-array constructor, and optional offset or size.</dd></div>
|
|
644
|
+
<div><dt>Returns</dt><dd>A promise for a newly allocated typed array.</dd></div>
|
|
645
|
+
</dl>
|
|
646
|
+
<div class="detailsBody"><p>This reads GPU buffer contents back to JS. If the buffer is already</p><p>mappable for read, Doe maps it directly; otherwise Doe stages the copy</p><p>through a temporary readback buffer.</p></div>
|
|
647
|
+
<ul class="notes"><li>options.offset and options.size let you read a subrange.</li><li>The typed-array constructor must accept a plain ArrayBuffer.</li><li>See raw buffer.mapAsync(...) when you need manual readback control.</li></ul>
|
|
648
|
+
<details class="apiExample"><summary>JSDoc example</summary><pre><code>const out = await gpu.buffer.read(dst, Float32Array);</code></pre></details>
|
|
649
|
+
<div class="linkRow">
|
|
650
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.js">Source</a>
|
|
651
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.d.ts">Types</a>
|
|
652
|
+
</div>
|
|
653
|
+
</article>
|
|
654
|
+
|
|
655
|
+
<article class="apiCard searchTarget" data-search="gpu.kernel.run gpu.kernel.run(options) -> promise<void> compile and dispatch a one-off compute job. doe api gpu.kernel. wgsl source, bindings, workgroups, and an optional entry point or label. a promise that resolves after submission completes. this is the explicit one-shot compute path. it builds the pipeline for the provided shader, dispatches once, and waits for completion. workgroups may be number, [x, y], or [x, y, z]. bare buffers without doe helper metadata require { buffer, access }. see gpu.kernel.create(...) when you will reuse the shader shape. see gpu.compute(...) for the narrower typed-array workflow." id="gpu.kernel.run">
|
|
656
|
+
<div class="apiHeader">
|
|
657
|
+
<div>
|
|
658
|
+
<div class="eyebrow">Doe API gpu.kernel.</div>
|
|
659
|
+
<h3>gpu.kernel.run</h3>
|
|
660
|
+
</div>
|
|
661
|
+
<code class="signature">gpu.kernel.run(options) -> Promise<void></code>
|
|
662
|
+
</div>
|
|
663
|
+
<p class="summary">Compile and dispatch a one-off compute job.</p>
|
|
664
|
+
<dl class="contractGrid">
|
|
665
|
+
<div><dt>Input</dt><dd>WGSL source, bindings, workgroups, and an optional entry point or label.</dd></div>
|
|
666
|
+
<div><dt>Returns</dt><dd>A promise that resolves after submission completes.</dd></div>
|
|
667
|
+
</dl>
|
|
668
|
+
<div class="detailsBody"><p>This is the explicit one-shot compute path. It builds the pipeline for</p><p>the provided shader, dispatches once, and waits for completion.</p></div>
|
|
669
|
+
<ul class="notes"><li>workgroups may be number, [x, y], or [x, y, z].</li><li>Bare buffers without Doe helper metadata require { buffer, access }.</li><li>See gpu.kernel.create(...) when you will reuse the shader shape.</li><li>See gpu.compute(...) for the narrower typed-array workflow.</li></ul>
|
|
670
|
+
<details class="apiExample"><summary>JSDoc example</summary><pre><code>await gpu.kernel.run({
|
|
671
|
+
code,
|
|
672
|
+
bindings: [src, dst],
|
|
673
|
+
workgroups: 1,
|
|
674
|
+
});</code></pre></details>
|
|
675
|
+
<div class="linkRow">
|
|
676
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.js">Source</a>
|
|
677
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.d.ts">Types</a>
|
|
678
|
+
</div>
|
|
679
|
+
</article>
|
|
680
|
+
|
|
681
|
+
<article class="apiCard searchTarget" data-search="gpu.kernel.create gpu.kernel.create(options) -> doekernel compile a reusable compute kernel. doe api gpu.kernel. wgsl source, an optional entry point, and an initial binding shape. a doekernel object with dispatch(...). this creates the shader module, bind-group layout, and compute pipeline once so the same wgsl shape can be dispatched repeatedly. binding access is inferred from the bindings passed at compile time. see kernel.dispatch(...) to run the compiled kernel. see gpu.kernel.run(...) when reuse does not matter." id="gpu.kernel.create">
|
|
682
|
+
<div class="apiHeader">
|
|
683
|
+
<div>
|
|
684
|
+
<div class="eyebrow">Doe API gpu.kernel.</div>
|
|
685
|
+
<h3>gpu.kernel.create</h3>
|
|
686
|
+
</div>
|
|
687
|
+
<code class="signature">gpu.kernel.create(options) -> DoeKernel</code>
|
|
688
|
+
</div>
|
|
689
|
+
<p class="summary">Compile a reusable compute kernel.</p>
|
|
690
|
+
<dl class="contractGrid">
|
|
691
|
+
<div><dt>Input</dt><dd>WGSL source, an optional entry point, and an initial binding shape.</dd></div>
|
|
692
|
+
<div><dt>Returns</dt><dd>A DoeKernel object with dispatch(...).</dd></div>
|
|
693
|
+
</dl>
|
|
694
|
+
<div class="detailsBody"><p>This creates the shader module, bind-group layout, and compute</p><p>pipeline once so the same WGSL shape can be dispatched repeatedly.</p></div>
|
|
695
|
+
<ul class="notes"><li>Binding access is inferred from the bindings passed at compile time.</li><li>See kernel.dispatch(...) to run the compiled kernel.</li><li>See gpu.kernel.run(...) when reuse does not matter.</li></ul>
|
|
696
|
+
<details class="apiExample"><summary>JSDoc example</summary><pre><code>const kernel = gpu.kernel.create({
|
|
697
|
+
code,
|
|
698
|
+
bindings: [src, dst],
|
|
699
|
+
});</code></pre></details>
|
|
700
|
+
<div class="linkRow">
|
|
701
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.js">Source</a>
|
|
702
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.d.ts">Types</a>
|
|
703
|
+
</div>
|
|
704
|
+
</article>
|
|
705
|
+
|
|
706
|
+
<article class="apiCard searchTarget" data-search="doekernel class doekernel reusable compute kernel compiled by gpu.kernel.create(...). doe api gpu.kernel. created from wgsl source, an entry point, and an initial binding shape. a reusable kernel object with dispatch(...). this object keeps the compiled pipeline and bind-group layout for a repeated wgsl compute shape. use it when you will dispatch the same shader more than once and want to avoid recompiling on every call. see gpu.kernel.run(...) for the one-shot explicit path. see gpu.compute(...) for the narrower typed-array workflow. instances are returned through the bound doe api and are not exported directly." id="DoeKernel">
|
|
707
|
+
<div class="apiHeader">
|
|
708
|
+
<div>
|
|
709
|
+
<div class="eyebrow">Doe API gpu.kernel.</div>
|
|
710
|
+
<h3>DoeKernel</h3>
|
|
711
|
+
</div>
|
|
712
|
+
<code class="signature">class DoeKernel</code>
|
|
713
|
+
</div>
|
|
714
|
+
<p class="summary">Reusable compute kernel compiled by gpu.kernel.create(...).</p>
|
|
715
|
+
<dl class="contractGrid">
|
|
716
|
+
<div><dt>Input</dt><dd>Created from WGSL source, an entry point, and an initial binding shape.</dd></div>
|
|
717
|
+
<div><dt>Returns</dt><dd>A reusable kernel object with dispatch(...).</dd></div>
|
|
718
|
+
</dl>
|
|
719
|
+
<div class="detailsBody"><p>This object keeps the compiled pipeline and bind-group layout for a repeated</p><p>WGSL compute shape. Use it when you will dispatch the same shader more than</p><p>once and want to avoid recompiling on every call.</p></div>
|
|
720
|
+
<ul class="notes"><li>See gpu.kernel.run(...) for the one-shot explicit path.</li><li>See gpu.compute(...) for the narrower typed-array workflow.</li><li>Instances are returned through the bound Doe API and are not exported directly.</li></ul>
|
|
721
|
+
<details class="apiExample"><summary>JSDoc example</summary><pre><code>const kernel = gpu.kernel.create({
|
|
722
|
+
code,
|
|
723
|
+
bindings: [src, dst],
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
await kernel.dispatch({
|
|
727
|
+
bindings: [src, dst],
|
|
728
|
+
workgroups: 1,
|
|
729
|
+
});</code></pre></details>
|
|
730
|
+
<div class="linkRow">
|
|
731
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.js">Source</a>
|
|
732
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.d.ts">Types</a>
|
|
733
|
+
</div>
|
|
734
|
+
</article>
|
|
735
|
+
|
|
736
|
+
<article class="apiCard searchTarget" data-search="kernel.dispatch kernel.dispatch(options) -> promise<void> dispatch this compiled kernel once. doe api gpu.kernel. a binding list, workgroup counts, and an optional label. a promise that resolves after submission completes. this records one compute pass for the compiled pipeline, submits it, and waits for completion when the underlying queue exposes onsubmittedworkdone(). workgroups may be number, [x, y], or [x, y, z]. bare buffers without doe helper metadata require { buffer, access }. see gpu.kernel.run(...) when you do not need reuse." id="DoeKernel.dispatch">
|
|
737
|
+
<div class="apiHeader">
|
|
738
|
+
<div>
|
|
739
|
+
<div class="eyebrow">Doe API gpu.kernel.</div>
|
|
740
|
+
<h3>kernel.dispatch</h3>
|
|
741
|
+
</div>
|
|
742
|
+
<code class="signature">kernel.dispatch(options) -> Promise<void></code>
|
|
743
|
+
</div>
|
|
744
|
+
<p class="summary">Dispatch this compiled kernel once.</p>
|
|
745
|
+
<dl class="contractGrid">
|
|
746
|
+
<div><dt>Input</dt><dd>A binding list, workgroup counts, and an optional label.</dd></div>
|
|
747
|
+
<div><dt>Returns</dt><dd>A promise that resolves after submission completes.</dd></div>
|
|
748
|
+
</dl>
|
|
749
|
+
<div class="detailsBody"><p>This records one compute pass for the compiled pipeline, submits it, and</p><p>waits for completion when the underlying queue exposes</p><p>onSubmittedWorkDone().</p></div>
|
|
750
|
+
<ul class="notes"><li>workgroups may be number, [x, y], or [x, y, z].</li><li>Bare buffers without Doe helper metadata require { buffer, access }.</li><li>See gpu.kernel.run(...) when you do not need reuse.</li></ul>
|
|
751
|
+
<details class="apiExample"><summary>JSDoc example</summary><pre><code>await kernel.dispatch({
|
|
752
|
+
bindings: [src, dst],
|
|
753
|
+
workgroups: [4, 1, 1],
|
|
754
|
+
});</code></pre></details>
|
|
755
|
+
<div class="linkRow">
|
|
756
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.js">Source</a>
|
|
757
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.d.ts">Types</a>
|
|
758
|
+
</div>
|
|
759
|
+
</article>
|
|
760
|
+
|
|
761
|
+
<article class="apiCard searchTarget" data-search="gpu.compute gpu.compute(options) -> promise<typedarray> run a one-shot typed-array compute workflow. doe api gpu.compute. wgsl source, typed-array or buffer inputs, an output spec, and workgroups. a promise for the requested typed-array output. this is the most opinionated doe helper. it creates temporary buffers as needed, uploads host data, dispatches the compute shader once, reads back the requested output, and destroys temporary resources before returning. raw numeric usage flags are accepted only when explicit doe access is also provided. output size defaults from likeinput or the first input when possible. see gpu.kernel.run(...) or gpu.kernel.create(...) when you need explicit resource ownership." id="gpu.compute">
|
|
762
|
+
<div class="apiHeader">
|
|
763
|
+
<div>
|
|
764
|
+
<div class="eyebrow">Doe API gpu.compute.</div>
|
|
765
|
+
<h3>gpu.compute</h3>
|
|
766
|
+
</div>
|
|
767
|
+
<code class="signature">gpu.compute(options) -> Promise<TypedArray></code>
|
|
768
|
+
</div>
|
|
769
|
+
<p class="summary">Run a one-shot typed-array compute workflow.</p>
|
|
770
|
+
<dl class="contractGrid">
|
|
771
|
+
<div><dt>Input</dt><dd>WGSL source, typed-array or buffer inputs, an output spec, and workgroups.</dd></div>
|
|
772
|
+
<div><dt>Returns</dt><dd>A promise for the requested typed-array output.</dd></div>
|
|
773
|
+
</dl>
|
|
774
|
+
<div class="detailsBody"><p>This is the most opinionated Doe helper. It creates temporary buffers</p><p>as needed, uploads host data, dispatches the compute shader once,</p><p>reads back the requested output, and destroys temporary resources</p><p>before returning.</p></div>
|
|
775
|
+
<ul class="notes"><li>Raw numeric usage flags are accepted only when explicit Doe access is also provided.</li><li>Output size defaults from likeInput or the first input when possible.</li><li>See gpu.kernel.run(...) or gpu.kernel.create(...) when you need explicit resource ownership.</li></ul>
|
|
776
|
+
<details class="apiExample"><summary>JSDoc example</summary><pre><code>const out = await gpu.compute({
|
|
777
|
+
code,
|
|
778
|
+
inputs: [new Float32Array([1, 2, 3, 4])],
|
|
779
|
+
output: { type: Float32Array },
|
|
780
|
+
workgroups: 1,
|
|
781
|
+
});</code></pre></details>
|
|
782
|
+
<div class="linkRow">
|
|
783
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.js">Source</a>
|
|
784
|
+
<a href="https://github.com/clocksmith/fawn/tree/main/nursery/webgpu-doe/src/index.d.ts">Types</a>
|
|
785
|
+
</div>
|
|
786
|
+
</article>
|
|
787
|
+
</div>
|
|
788
|
+
</section>
|
|
789
|
+
|
|
790
|
+
<section class="panel" id="examples">
|
|
791
|
+
<div class="sectionHeader">
|
|
792
|
+
<div>
|
|
793
|
+
<div class="eyebrow">Live examples</div>
|
|
794
|
+
<h2>Shipped examples that actually run</h2>
|
|
795
|
+
</div>
|
|
796
|
+
<p>These editors start from the real files in <code>examples/doe-api/</code>. Run them as-is, tweak them inline, or use them to compare the explicit kernel path against the one-shot <code>gpu.compute(...)</code> helper.</p>
|
|
797
|
+
</div>
|
|
798
|
+
<div class="examplesGrid">
|
|
799
|
+
|
|
800
|
+
<article class="exampleCard searchTarget" data-search="buffer create + readback create a doe-managed buffer from host data, then read it back through gpu.buffer.read(...). buffers-readback.js gpu.buffer.create gpu.buffer.read" data-accent="buffer" id="example-buffers-readback.js">
|
|
801
|
+
<div class="exampleTop">
|
|
802
|
+
<div>
|
|
803
|
+
<div class="eyebrow">Example</div>
|
|
804
|
+
<h3>Buffer create + readback</h3>
|
|
805
|
+
</div>
|
|
806
|
+
<code class="filename">buffers-readback.js</code>
|
|
807
|
+
</div>
|
|
808
|
+
<p class="summary">Create a Doe-managed buffer from host data, then read it back through gpu.buffer.read(...).</p>
|
|
809
|
+
<div class="exampleLinks">
|
|
810
|
+
<a href="#gpu.buffer.create">gpu.buffer.create</a><a href="#gpu.buffer.read">gpu.buffer.read</a>
|
|
811
|
+
</div>
|
|
812
|
+
<div class="buttonRow">
|
|
813
|
+
<button type="button" class="runButton" data-run-example="buffers-readback.js">Run example</button>
|
|
814
|
+
<button type="button" class="ghostButton" data-reset-example="buffers-readback.js">Reset</button>
|
|
815
|
+
<button type="button" class="ghostButton" data-copy-example="buffers-readback.js">Copy</button>
|
|
816
|
+
<a class="ghostLink" href="../examples/doe-api/buffers-readback.js">Open source</a>
|
|
817
|
+
</div>
|
|
818
|
+
<div class="outputShell">
|
|
819
|
+
<div class="outputMeta" data-output-meta="buffers-readback.js">Ready to run in a browser with WebGPU.</div>
|
|
820
|
+
<div class="metricChips" data-output-stats="buffers-readback.js"></div>
|
|
821
|
+
<canvas class="chartCanvas" data-output-chart="buffers-readback.js" width="960" height="220"></canvas>
|
|
822
|
+
<div class="valueGrid" data-output-values="buffers-readback.js"></div>
|
|
823
|
+
<pre class="outputBlock" data-output-text="buffers-readback.js"></pre>
|
|
824
|
+
</div>
|
|
825
|
+
<details class="editorDetails">
|
|
826
|
+
<summary>View or edit source</summary>
|
|
827
|
+
<div class="editorShell">
|
|
828
|
+
<textarea class="editor" data-example-editor="buffers-readback.js" spellcheck="false">import { doe } from "@simulatte/webgpu/compute";
|
|
829
|
+
|
|
830
|
+
const gpu = await doe.requestDevice();
|
|
831
|
+
const src = gpu.buffer.create({
|
|
832
|
+
data: new Float32Array([1, 2, 3, 4]),
|
|
833
|
+
usage: ["storageRead", "readback"],
|
|
834
|
+
});
|
|
835
|
+
|
|
836
|
+
const result = await gpu.buffer.read({ buffer: src, type: Float32Array });
|
|
837
|
+
console.log(JSON.stringify(Array.from(result)));</textarea>
|
|
838
|
+
</div>
|
|
839
|
+
</details>
|
|
840
|
+
</article>
|
|
841
|
+
|
|
842
|
+
<article class="exampleCard searchTarget" data-search="one-off kernel run use gpu.kernel.run(...) when you want explicit buffers but do not need to keep compiled kernel state. kernel-run.js gpu.kernel.run gpu.buffer.create gpu.buffer.read" data-accent="kernel" id="example-kernel-run.js">
|
|
843
|
+
<div class="exampleTop">
|
|
844
|
+
<div>
|
|
845
|
+
<div class="eyebrow">Example</div>
|
|
846
|
+
<h3>One-off kernel run</h3>
|
|
847
|
+
</div>
|
|
848
|
+
<code class="filename">kernel-run.js</code>
|
|
849
|
+
</div>
|
|
850
|
+
<p class="summary">Use gpu.kernel.run(...) when you want explicit buffers but do not need to keep compiled kernel state.</p>
|
|
851
|
+
<div class="exampleLinks">
|
|
852
|
+
<a href="#gpu.kernel.run">gpu.kernel.run</a><a href="#gpu.buffer.create">gpu.buffer.create</a><a href="#gpu.buffer.read">gpu.buffer.read</a>
|
|
853
|
+
</div>
|
|
854
|
+
<div class="buttonRow">
|
|
855
|
+
<button type="button" class="runButton" data-run-example="kernel-run.js">Run example</button>
|
|
856
|
+
<button type="button" class="ghostButton" data-reset-example="kernel-run.js">Reset</button>
|
|
857
|
+
<button type="button" class="ghostButton" data-copy-example="kernel-run.js">Copy</button>
|
|
858
|
+
<a class="ghostLink" href="../examples/doe-api/kernel-run.js">Open source</a>
|
|
859
|
+
</div>
|
|
860
|
+
<div class="outputShell">
|
|
861
|
+
<div class="outputMeta" data-output-meta="kernel-run.js">Ready to run in a browser with WebGPU.</div>
|
|
862
|
+
<div class="metricChips" data-output-stats="kernel-run.js"></div>
|
|
863
|
+
<canvas class="chartCanvas" data-output-chart="kernel-run.js" width="960" height="220"></canvas>
|
|
864
|
+
<div class="valueGrid" data-output-values="kernel-run.js"></div>
|
|
865
|
+
<pre class="outputBlock" data-output-text="kernel-run.js"></pre>
|
|
866
|
+
</div>
|
|
867
|
+
<details class="editorDetails">
|
|
868
|
+
<summary>View or edit source</summary>
|
|
869
|
+
<div class="editorShell">
|
|
870
|
+
<textarea class="editor" data-example-editor="kernel-run.js" spellcheck="false">import { doe } from "@simulatte/webgpu/compute";
|
|
871
|
+
|
|
872
|
+
const gpu = await doe.requestDevice();
|
|
873
|
+
const src = gpu.buffer.create({ data: new Float32Array([1, 2, 3, 4]) });
|
|
874
|
+
const dst = gpu.buffer.create({ size: src.size, usage: "storageReadWrite" });
|
|
875
|
+
|
|
876
|
+
await gpu.kernel.run({
|
|
877
|
+
code: `
|
|
878
|
+
@group(0) @binding(0) var<storage, read> src: array<f32>;
|
|
879
|
+
@group(0) @binding(1) var<storage, read_write> dst: array<f32>;
|
|
880
|
+
|
|
881
|
+
@compute @workgroup_size(4)
|
|
882
|
+
fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|
883
|
+
let i = gid.x;
|
|
884
|
+
dst[i] = src[i] * 2.0;
|
|
885
|
+
}
|
|
886
|
+
`,
|
|
887
|
+
bindings: [src, dst],
|
|
888
|
+
workgroups: 1,
|
|
889
|
+
});
|
|
890
|
+
|
|
891
|
+
const result = await gpu.buffer.read({ buffer: dst, type: Float32Array });
|
|
892
|
+
console.log(JSON.stringify(Array.from(result)));</textarea>
|
|
893
|
+
</div>
|
|
894
|
+
</details>
|
|
895
|
+
</article>
|
|
896
|
+
|
|
897
|
+
<article class="exampleCard searchTarget" data-search="reusable kernel dispatch compile a doekernel once with gpu.kernel.create(...), then dispatch it explicitly. kernel-create-and-dispatch.js gpu.kernel.create doekernel doekernel.dispatch" data-accent="kernel" id="example-kernel-create-and-dispatch.js">
|
|
898
|
+
<div class="exampleTop">
|
|
899
|
+
<div>
|
|
900
|
+
<div class="eyebrow">Example</div>
|
|
901
|
+
<h3>Reusable kernel dispatch</h3>
|
|
902
|
+
</div>
|
|
903
|
+
<code class="filename">kernel-create-and-dispatch.js</code>
|
|
904
|
+
</div>
|
|
905
|
+
<p class="summary">Compile a DoeKernel once with gpu.kernel.create(...), then dispatch it explicitly.</p>
|
|
906
|
+
<div class="exampleLinks">
|
|
907
|
+
<a href="#gpu.kernel.create">gpu.kernel.create</a><a href="#DoeKernel">DoeKernel</a><a href="#DoeKernel.dispatch">DoeKernel.dispatch</a>
|
|
908
|
+
</div>
|
|
909
|
+
<div class="buttonRow">
|
|
910
|
+
<button type="button" class="runButton" data-run-example="kernel-create-and-dispatch.js">Run example</button>
|
|
911
|
+
<button type="button" class="ghostButton" data-reset-example="kernel-create-and-dispatch.js">Reset</button>
|
|
912
|
+
<button type="button" class="ghostButton" data-copy-example="kernel-create-and-dispatch.js">Copy</button>
|
|
913
|
+
<a class="ghostLink" href="../examples/doe-api/kernel-create-and-dispatch.js">Open source</a>
|
|
914
|
+
</div>
|
|
915
|
+
<div class="outputShell">
|
|
916
|
+
<div class="outputMeta" data-output-meta="kernel-create-and-dispatch.js">Ready to run in a browser with WebGPU.</div>
|
|
917
|
+
<div class="metricChips" data-output-stats="kernel-create-and-dispatch.js"></div>
|
|
918
|
+
<canvas class="chartCanvas" data-output-chart="kernel-create-and-dispatch.js" width="960" height="220"></canvas>
|
|
919
|
+
<div class="valueGrid" data-output-values="kernel-create-and-dispatch.js"></div>
|
|
920
|
+
<pre class="outputBlock" data-output-text="kernel-create-and-dispatch.js"></pre>
|
|
921
|
+
</div>
|
|
922
|
+
<details class="editorDetails">
|
|
923
|
+
<summary>View or edit source</summary>
|
|
924
|
+
<div class="editorShell">
|
|
925
|
+
<textarea class="editor" data-example-editor="kernel-create-and-dispatch.js" spellcheck="false">import { doe } from "@simulatte/webgpu/compute";
|
|
926
|
+
|
|
927
|
+
const gpu = await doe.requestDevice();
|
|
928
|
+
const src = gpu.buffer.create({ data: new Float32Array([1, 2, 3, 4]) });
|
|
929
|
+
const dst = gpu.buffer.create({ size: src.size, usage: "storageReadWrite" });
|
|
930
|
+
|
|
931
|
+
const kernel = gpu.kernel.create({
|
|
932
|
+
code: `
|
|
933
|
+
@group(0) @binding(0) var<storage, read> src: array<f32>;
|
|
934
|
+
@group(0) @binding(1) var<storage, read_write> dst: array<f32>;
|
|
935
|
+
|
|
936
|
+
@compute @workgroup_size(4)
|
|
937
|
+
fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|
938
|
+
let i = gid.x;
|
|
939
|
+
dst[i] = src[i] * 5.0;
|
|
940
|
+
}
|
|
941
|
+
`,
|
|
942
|
+
bindings: [src, dst],
|
|
943
|
+
workgroups: 1,
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
await kernel.dispatch({
|
|
947
|
+
bindings: [src, dst],
|
|
948
|
+
workgroups: 1,
|
|
949
|
+
});
|
|
950
|
+
|
|
951
|
+
const result = await gpu.buffer.read({ buffer: dst, type: Float32Array });
|
|
952
|
+
console.log(JSON.stringify(Array.from(result)));</textarea>
|
|
953
|
+
</div>
|
|
954
|
+
</details>
|
|
955
|
+
</article>
|
|
956
|
+
|
|
957
|
+
<article class="exampleCard searchTarget" data-search="one-shot compute run the opinionated gpu.compute(...) helper with one typed-array input and inferred output sizing. compute-one-shot.js gpu.compute" data-accent="compute" id="example-compute-one-shot.js">
|
|
958
|
+
<div class="exampleTop">
|
|
959
|
+
<div>
|
|
960
|
+
<div class="eyebrow">Example</div>
|
|
961
|
+
<h3>One-shot compute</h3>
|
|
962
|
+
</div>
|
|
963
|
+
<code class="filename">compute-one-shot.js</code>
|
|
964
|
+
</div>
|
|
965
|
+
<p class="summary">Run the opinionated gpu.compute(...) helper with one typed-array input and inferred output sizing.</p>
|
|
966
|
+
<div class="exampleLinks">
|
|
967
|
+
<a href="#gpu.compute">gpu.compute</a>
|
|
968
|
+
</div>
|
|
969
|
+
<div class="buttonRow">
|
|
970
|
+
<button type="button" class="runButton" data-run-example="compute-one-shot.js">Run example</button>
|
|
971
|
+
<button type="button" class="ghostButton" data-reset-example="compute-one-shot.js">Reset</button>
|
|
972
|
+
<button type="button" class="ghostButton" data-copy-example="compute-one-shot.js">Copy</button>
|
|
973
|
+
<a class="ghostLink" href="../examples/doe-api/compute-one-shot.js">Open source</a>
|
|
974
|
+
</div>
|
|
975
|
+
<div class="outputShell">
|
|
976
|
+
<div class="outputMeta" data-output-meta="compute-one-shot.js">Ready to run in a browser with WebGPU.</div>
|
|
977
|
+
<div class="metricChips" data-output-stats="compute-one-shot.js"></div>
|
|
978
|
+
<canvas class="chartCanvas" data-output-chart="compute-one-shot.js" width="960" height="220"></canvas>
|
|
979
|
+
<div class="valueGrid" data-output-values="compute-one-shot.js"></div>
|
|
980
|
+
<pre class="outputBlock" data-output-text="compute-one-shot.js"></pre>
|
|
981
|
+
</div>
|
|
982
|
+
<details class="editorDetails">
|
|
983
|
+
<summary>View or edit source</summary>
|
|
984
|
+
<div class="editorShell">
|
|
985
|
+
<textarea class="editor" data-example-editor="compute-one-shot.js" spellcheck="false">import { doe } from "@simulatte/webgpu/compute";
|
|
986
|
+
|
|
987
|
+
const gpu = await doe.requestDevice();
|
|
988
|
+
|
|
989
|
+
const result = await gpu.compute({
|
|
990
|
+
code: `
|
|
991
|
+
@group(0) @binding(0) var<storage, read> src: array<f32>;
|
|
992
|
+
@group(0) @binding(1) var<storage, read_write> dst: array<f32>;
|
|
993
|
+
|
|
994
|
+
@compute @workgroup_size(4)
|
|
995
|
+
fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|
996
|
+
let i = gid.x;
|
|
997
|
+
dst[i] = src[i] * 3.0;
|
|
998
|
+
}
|
|
999
|
+
`,
|
|
1000
|
+
inputs: [new Float32Array([1, 2, 3, 4])],
|
|
1001
|
+
output: {
|
|
1002
|
+
type: Float32Array,
|
|
1003
|
+
},
|
|
1004
|
+
workgroups: 1,
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
console.log(JSON.stringify(Array.from(result)));</textarea>
|
|
1008
|
+
</div>
|
|
1009
|
+
</details>
|
|
1010
|
+
</article>
|
|
1011
|
+
|
|
1012
|
+
<article class="exampleCard searchTarget" data-search="one-shot compute with likeinput use gpu.compute(...) with a uniform input and likeinput sizing to keep output shape explicit. compute-one-shot-like-input.js gpu.compute" data-accent="compute" id="example-compute-one-shot-like-input.js">
|
|
1013
|
+
<div class="exampleTop">
|
|
1014
|
+
<div>
|
|
1015
|
+
<div class="eyebrow">Example</div>
|
|
1016
|
+
<h3>One-shot compute with likeInput</h3>
|
|
1017
|
+
</div>
|
|
1018
|
+
<code class="filename">compute-one-shot-like-input.js</code>
|
|
1019
|
+
</div>
|
|
1020
|
+
<p class="summary">Use gpu.compute(...) with a uniform input and likeInput sizing to keep output shape explicit.</p>
|
|
1021
|
+
<div class="exampleLinks">
|
|
1022
|
+
<a href="#gpu.compute">gpu.compute</a>
|
|
1023
|
+
</div>
|
|
1024
|
+
<div class="buttonRow">
|
|
1025
|
+
<button type="button" class="runButton" data-run-example="compute-one-shot-like-input.js">Run example</button>
|
|
1026
|
+
<button type="button" class="ghostButton" data-reset-example="compute-one-shot-like-input.js">Reset</button>
|
|
1027
|
+
<button type="button" class="ghostButton" data-copy-example="compute-one-shot-like-input.js">Copy</button>
|
|
1028
|
+
<a class="ghostLink" href="../examples/doe-api/compute-one-shot-like-input.js">Open source</a>
|
|
1029
|
+
</div>
|
|
1030
|
+
<div class="outputShell">
|
|
1031
|
+
<div class="outputMeta" data-output-meta="compute-one-shot-like-input.js">Ready to run in a browser with WebGPU.</div>
|
|
1032
|
+
<div class="metricChips" data-output-stats="compute-one-shot-like-input.js"></div>
|
|
1033
|
+
<canvas class="chartCanvas" data-output-chart="compute-one-shot-like-input.js" width="960" height="220"></canvas>
|
|
1034
|
+
<div class="valueGrid" data-output-values="compute-one-shot-like-input.js"></div>
|
|
1035
|
+
<pre class="outputBlock" data-output-text="compute-one-shot-like-input.js"></pre>
|
|
1036
|
+
</div>
|
|
1037
|
+
<details class="editorDetails">
|
|
1038
|
+
<summary>View or edit source</summary>
|
|
1039
|
+
<div class="editorShell">
|
|
1040
|
+
<textarea class="editor" data-example-editor="compute-one-shot-like-input.js" spellcheck="false">import { doe } from "@simulatte/webgpu/compute";
|
|
1041
|
+
|
|
1042
|
+
const gpu = await doe.requestDevice();
|
|
1043
|
+
|
|
1044
|
+
const result = await gpu.compute({
|
|
1045
|
+
code: `
|
|
1046
|
+
struct Scale {
|
|
1047
|
+
value: f32,
|
|
1048
|
+
};
|
|
1049
|
+
|
|
1050
|
+
@group(0) @binding(0) var<uniform> scale: Scale;
|
|
1051
|
+
@group(0) @binding(1) var<storage, read> src: array<f32>;
|
|
1052
|
+
@group(0) @binding(2) var<storage, read_write> dst: array<f32>;
|
|
1053
|
+
|
|
1054
|
+
@compute @workgroup_size(4)
|
|
1055
|
+
fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|
1056
|
+
let i = gid.x;
|
|
1057
|
+
dst[i] = src[i] * scale.value;
|
|
1058
|
+
}
|
|
1059
|
+
`,
|
|
1060
|
+
inputs: [
|
|
1061
|
+
{
|
|
1062
|
+
data: new Float32Array([2]),
|
|
1063
|
+
usage: "uniform",
|
|
1064
|
+
access: "uniform",
|
|
1065
|
+
},
|
|
1066
|
+
new Float32Array([1, 2, 3, 4]),
|
|
1067
|
+
],
|
|
1068
|
+
output: {
|
|
1069
|
+
type: Float32Array,
|
|
1070
|
+
likeInput: 1,
|
|
1071
|
+
},
|
|
1072
|
+
workgroups: [1, 1],
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
console.log(JSON.stringify(Array.from(result)));</textarea>
|
|
1076
|
+
</div>
|
|
1077
|
+
</details>
|
|
1078
|
+
</article>
|
|
1079
|
+
|
|
1080
|
+
<article class="exampleCard searchTarget" data-search="one-shot compute with multiple inputs feed multiple typed-array inputs through gpu.compute(...) while keeping the shader and result explicit. compute-one-shot-multiple-inputs.js gpu.compute" data-accent="compute" id="example-compute-one-shot-multiple-inputs.js">
|
|
1081
|
+
<div class="exampleTop">
|
|
1082
|
+
<div>
|
|
1083
|
+
<div class="eyebrow">Example</div>
|
|
1084
|
+
<h3>One-shot compute with multiple inputs</h3>
|
|
1085
|
+
</div>
|
|
1086
|
+
<code class="filename">compute-one-shot-multiple-inputs.js</code>
|
|
1087
|
+
</div>
|
|
1088
|
+
<p class="summary">Feed multiple typed-array inputs through gpu.compute(...) while keeping the shader and result explicit.</p>
|
|
1089
|
+
<div class="exampleLinks">
|
|
1090
|
+
<a href="#gpu.compute">gpu.compute</a>
|
|
1091
|
+
</div>
|
|
1092
|
+
<div class="buttonRow">
|
|
1093
|
+
<button type="button" class="runButton" data-run-example="compute-one-shot-multiple-inputs.js">Run example</button>
|
|
1094
|
+
<button type="button" class="ghostButton" data-reset-example="compute-one-shot-multiple-inputs.js">Reset</button>
|
|
1095
|
+
<button type="button" class="ghostButton" data-copy-example="compute-one-shot-multiple-inputs.js">Copy</button>
|
|
1096
|
+
<a class="ghostLink" href="../examples/doe-api/compute-one-shot-multiple-inputs.js">Open source</a>
|
|
1097
|
+
</div>
|
|
1098
|
+
<div class="outputShell">
|
|
1099
|
+
<div class="outputMeta" data-output-meta="compute-one-shot-multiple-inputs.js">Ready to run in a browser with WebGPU.</div>
|
|
1100
|
+
<div class="metricChips" data-output-stats="compute-one-shot-multiple-inputs.js"></div>
|
|
1101
|
+
<canvas class="chartCanvas" data-output-chart="compute-one-shot-multiple-inputs.js" width="960" height="220"></canvas>
|
|
1102
|
+
<div class="valueGrid" data-output-values="compute-one-shot-multiple-inputs.js"></div>
|
|
1103
|
+
<pre class="outputBlock" data-output-text="compute-one-shot-multiple-inputs.js"></pre>
|
|
1104
|
+
</div>
|
|
1105
|
+
<details class="editorDetails">
|
|
1106
|
+
<summary>View or edit source</summary>
|
|
1107
|
+
<div class="editorShell">
|
|
1108
|
+
<textarea class="editor" data-example-editor="compute-one-shot-multiple-inputs.js" spellcheck="false">import { doe } from "@simulatte/webgpu/compute";
|
|
1109
|
+
|
|
1110
|
+
const gpu = await doe.requestDevice();
|
|
1111
|
+
|
|
1112
|
+
const result = await gpu.compute({
|
|
1113
|
+
code: `
|
|
1114
|
+
@group(0) @binding(0) var<storage, read> lhs: array<f32>;
|
|
1115
|
+
@group(0) @binding(1) var<storage, read> rhs: array<f32>;
|
|
1116
|
+
@group(0) @binding(2) var<storage, read_write> dst: array<f32>;
|
|
1117
|
+
|
|
1118
|
+
@compute @workgroup_size(4)
|
|
1119
|
+
fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|
1120
|
+
let i = gid.x;
|
|
1121
|
+
dst[i] = lhs[i] + rhs[i];
|
|
1122
|
+
}
|
|
1123
|
+
`,
|
|
1124
|
+
inputs: [
|
|
1125
|
+
new Float32Array([1, 2, 3, 4]),
|
|
1126
|
+
new Float32Array([10, 20, 30, 40]),
|
|
1127
|
+
],
|
|
1128
|
+
output: {
|
|
1129
|
+
type: Float32Array,
|
|
1130
|
+
},
|
|
1131
|
+
workgroups: 1,
|
|
1132
|
+
});
|
|
1133
|
+
|
|
1134
|
+
console.log(JSON.stringify(Array.from(result)));</textarea>
|
|
1135
|
+
</div>
|
|
1136
|
+
</details>
|
|
1137
|
+
</article>
|
|
1138
|
+
|
|
1139
|
+
<article class="exampleCard searchTarget" data-search="one-shot compute matmul run a larger matrix multiply through gpu.compute(...) with explicit tensor dimensions and output size. compute-one-shot-matmul.js gpu.compute" data-accent="compute" id="example-compute-one-shot-matmul.js">
|
|
1140
|
+
<div class="exampleTop">
|
|
1141
|
+
<div>
|
|
1142
|
+
<div class="eyebrow">Example</div>
|
|
1143
|
+
<h3>One-shot compute matmul</h3>
|
|
1144
|
+
</div>
|
|
1145
|
+
<code class="filename">compute-one-shot-matmul.js</code>
|
|
1146
|
+
</div>
|
|
1147
|
+
<p class="summary">Run a larger matrix multiply through gpu.compute(...) with explicit tensor dimensions and output size.</p>
|
|
1148
|
+
<div class="exampleLinks">
|
|
1149
|
+
<a href="#gpu.compute">gpu.compute</a>
|
|
1150
|
+
</div>
|
|
1151
|
+
<div class="buttonRow">
|
|
1152
|
+
<button type="button" class="runButton" data-run-example="compute-one-shot-matmul.js">Run example</button>
|
|
1153
|
+
<button type="button" class="ghostButton" data-reset-example="compute-one-shot-matmul.js">Reset</button>
|
|
1154
|
+
<button type="button" class="ghostButton" data-copy-example="compute-one-shot-matmul.js">Copy</button>
|
|
1155
|
+
<a class="ghostLink" href="../examples/doe-api/compute-one-shot-matmul.js">Open source</a>
|
|
1156
|
+
</div>
|
|
1157
|
+
<div class="outputShell">
|
|
1158
|
+
<div class="outputMeta" data-output-meta="compute-one-shot-matmul.js">Ready to run in a browser with WebGPU.</div>
|
|
1159
|
+
<div class="metricChips" data-output-stats="compute-one-shot-matmul.js"></div>
|
|
1160
|
+
<canvas class="chartCanvas" data-output-chart="compute-one-shot-matmul.js" width="960" height="220"></canvas>
|
|
1161
|
+
<div class="valueGrid" data-output-values="compute-one-shot-matmul.js"></div>
|
|
1162
|
+
<pre class="outputBlock" data-output-text="compute-one-shot-matmul.js"></pre>
|
|
1163
|
+
</div>
|
|
1164
|
+
<details class="editorDetails">
|
|
1165
|
+
<summary>View or edit source</summary>
|
|
1166
|
+
<div class="editorShell">
|
|
1167
|
+
<textarea class="editor" data-example-editor="compute-one-shot-matmul.js" spellcheck="false">import { doe } from "@simulatte/webgpu/compute";
|
|
1168
|
+
|
|
1169
|
+
const gpu = await doe.requestDevice();
|
|
1170
|
+
const M = 256;
|
|
1171
|
+
const K = 512;
|
|
1172
|
+
const N = 256;
|
|
1173
|
+
|
|
1174
|
+
const lhs = Float32Array.from({ length: M * K }, (_, i) => (i % 17) / 17);
|
|
1175
|
+
const rhs = Float32Array.from({ length: K * N }, (_, i) => (i % 13) / 13);
|
|
1176
|
+
const dims = new Uint32Array([M, K, N, 0]);
|
|
1177
|
+
|
|
1178
|
+
const result = await gpu.compute({
|
|
1179
|
+
code: `
|
|
1180
|
+
struct Dims {
|
|
1181
|
+
m: u32,
|
|
1182
|
+
k: u32,
|
|
1183
|
+
n: u32,
|
|
1184
|
+
_pad: u32,
|
|
1185
|
+
};
|
|
1186
|
+
|
|
1187
|
+
@group(0) @binding(0) var<uniform> dims: Dims;
|
|
1188
|
+
@group(0) @binding(1) var<storage, read> lhs: array<f32>;
|
|
1189
|
+
@group(0) @binding(2) var<storage, read> rhs: array<f32>;
|
|
1190
|
+
@group(0) @binding(3) var<storage, read_write> out: array<f32>;
|
|
1191
|
+
|
|
1192
|
+
@compute @workgroup_size(8, 8)
|
|
1193
|
+
fn main(@builtin(global_invocation_id) gid: vec3u) {
|
|
1194
|
+
let row = gid.y;
|
|
1195
|
+
let col = gid.x;
|
|
1196
|
+
if (row >= dims.m || col >= dims.n) {
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
var acc = 0.0;
|
|
1201
|
+
for (var i = 0u; i < dims.k; i = i + 1u) {
|
|
1202
|
+
acc += lhs[row * dims.k + i] * rhs[i * dims.n + col];
|
|
1203
|
+
}
|
|
1204
|
+
out[row * dims.n + col] = acc;
|
|
1205
|
+
}
|
|
1206
|
+
`,
|
|
1207
|
+
inputs: [
|
|
1208
|
+
{ data: dims, usage: "uniform", access: "uniform" },
|
|
1209
|
+
lhs,
|
|
1210
|
+
rhs,
|
|
1211
|
+
],
|
|
1212
|
+
output: {
|
|
1213
|
+
type: Float32Array,
|
|
1214
|
+
size: M * N * Float32Array.BYTES_PER_ELEMENT,
|
|
1215
|
+
},
|
|
1216
|
+
workgroups: [Math.ceil(N / 8), Math.ceil(M / 8)],
|
|
1217
|
+
});
|
|
1218
|
+
|
|
1219
|
+
console.log(JSON.stringify(Array.from(result.subarray(0, 8), (value) => Number(value.toFixed(4)))));</textarea>
|
|
1220
|
+
</div>
|
|
1221
|
+
</details>
|
|
1222
|
+
</article>
|
|
1223
|
+
</div>
|
|
1224
|
+
</section>
|
|
1225
|
+
|
|
1226
|
+
<section class="supportPanel panel">
|
|
1227
|
+
<div class="sectionHeader">
|
|
1228
|
+
<div>
|
|
1229
|
+
<div class="eyebrow">Generated from</div>
|
|
1230
|
+
<h2>Source-of-truth inputs</h2>
|
|
1231
|
+
</div>
|
|
1232
|
+
<p>The page is generated, not hand-maintained. When the Doe API changes, regenerate this artifact from the current source, types, examples, and README contract language.</p>
|
|
1233
|
+
</div>
|
|
1234
|
+
<p class="footerNote">
|
|
1235
|
+
Inputs: <code>README.md</code>, <code>@simulatte/webgpu-doe/src/index.js</code>, <code>@simulatte/webgpu-doe/src/index.d.ts</code>, and
|
|
1236
|
+
the shipped Doe example files in <code>examples/doe-api/</code>. Generated by
|
|
1237
|
+
<code>scripts/generate-doe-api-docs.js</code>.
|
|
1238
|
+
</p>
|
|
1239
|
+
</section>
|
|
1240
|
+
</main>
|
|
1241
|
+
</div>
|
|
1242
|
+
|
|
1243
|
+
<script id="doe-api-data" type="application/json">{"intro":"`@simulatte/webgpu` is Fawn's headless WebGPU package for Node.js and Bun: use\nthe raw WebGPU API through `requestDevice()` and `device.*`, or move up to the\nDoe API when you want the same runtime with less setup. Browser DOM/canvas\nownership lives in the separate `nursery/fawn-browser` lane.\n\nTerminology in this README is deliberate:\n\n- `Doe runtime` means the Zig/native WebGPU runtime underneath the package\n- `Doe API` means the explicit JS convenience surface under `doe`, `gpu.buffer.*`,\n `gpu.kernel.run(...)`, `gpu.kernel.create(...)`, and `gpu.compute(...)`\n\nThe current implemented helper contract is documented in\n[api-contract.md](./api-contract.md). The proposed naming cleanup for the Doe\nhelper surface is documented in [doe-api-design.md](./doe-api-design.md).\nThe generated interactive Doe API reference lives at\n[docs/doe-api-reference.html](./docs/doe-api-reference.html).\n\nThe same helper layer is now also published separately as\n`@simulatte/webgpu-doe` when you want the Doe API as an independent package\nboundary.","apiEntries":[{"id":"doe.requestDevice","title":"doe.requestDevice","signature":"doe.requestDevice(options?) -> Promise<gpu>","doc":{"summary":"Request a device and return the bound Doe API in one step.","surface":"Doe API namespace.","input":"Optional package-local request options.","returns":"A promise for the bound gpu helper object.","details":["This calls the package-local requestDevice(...) implementation and","then wraps the resulting raw device in the bound Doe API."],"notes":["Throws if this namespace was created without a requestDevice implementation.","gpu.device exposes the underlying raw device when you need lower-level control.","See doe.bind(device) when you already have a raw device."],"example":"const gpu = await doe.requestDevice();"},"sourcePath":"@simulatte/webgpu-doe/src/index.js","typePath":"@simulatte/webgpu-doe/src/index.d.ts","sourceLabel":"Source","typeLabel":"Types","hasTypeHint":true,"tokens":"doe.requestdevice doe.requestdevice(options?) -> promise<gpu> request a device and return the bound doe api in one step. doe api namespace. optional package-local request options. a promise for the bound gpu helper object. this calls the package-local requestdevice(...) implementation and then wraps the resulting raw device in the bound doe api. throws if this namespace was created without a requestdevice implementation. gpu.device exposes the underlying raw device when you need lower-level control. see doe.bind(device) when you already have a raw device."},{"id":"doe.bind","title":"doe.bind","signature":"doe.bind(device) -> gpu","doc":{"summary":"Wrap an existing device in the bound Doe API.","surface":"Doe API namespace.","input":"A raw device returned by the package surface.","returns":"The bound gpu helper object for that device.","details":["Use this when you need the raw device first, but still want to opt into","Doe helpers afterward."],"notes":["No async work happens here; it only wraps the device you already have.","See doe.requestDevice(...) for the one-step helper entrypoint."],"example":"const device = await requestDevice();\nconst gpu = doe.bind(device);"},"sourcePath":"@simulatte/webgpu-doe/src/index.js","typePath":"@simulatte/webgpu-doe/src/index.d.ts","sourceLabel":"Source","typeLabel":"Types","hasTypeHint":true,"tokens":"doe.bind doe.bind(device) -> gpu wrap an existing device in the bound doe api. doe api namespace. a raw device returned by the package surface. the bound gpu helper object for that device. use this when you need the raw device first, but still want to opt into doe helpers afterward. no async work happens here; it only wraps the device you already have. see doe.requestdevice(...) for the one-step helper entrypoint."},{"id":"gpu.buffer.create","title":"gpu.buffer.create","signature":"gpu.buffer.create(options) -> GPUBuffer","doc":{"summary":"Create a buffer with explicit size and Doe usage tokens.","surface":"Doe API gpu.buffer.","input":"A buffer size, usage, and optional label or mapping flag.","returns":"A GPU buffer with Doe usage metadata attached when possible.","details":["This is the explicit Doe helper over device.createBuffer(...). It","accepts Doe usage tokens such as storageReadWrite, and when data","is provided it allocates and uploads in one step. Doe remembers the","resulting binding access so later helper calls can infer how the","buffer should be bound."],"notes":["When data is provided, usage defaults to storageRead.","Raw numeric usage flags are allowed here for explicit control.","Buffers created with raw numeric flags may later require { buffer, access }."],"example":"const src = gpu.buffer.create({ data: new Float32Array([1, 2, 3, 4]) });\nconst dst = gpu.buffer.create({ size: src.size, usage: \"storageReadWrite\" });"},"sourcePath":"@simulatte/webgpu-doe/src/index.js","typePath":"@simulatte/webgpu-doe/src/index.d.ts","sourceLabel":"Source","typeLabel":"Types","hasTypeHint":true,"tokens":"gpu.buffer.create gpu.buffer.create(options) -> gpubuffer create a buffer with explicit size and doe usage tokens. doe api gpu.buffer. a buffer size, usage, and optional label or mapping flag. a gpu buffer with doe usage metadata attached when possible. this is the explicit doe helper over device.createbuffer(...). it accepts doe usage tokens such as storagereadwrite, and when data is provided it allocates and uploads in one step. doe remembers the resulting binding access so later helper calls can infer how the buffer should be bound. when data is provided, usage defaults to storageread. raw numeric usage flags are allowed here for explicit control. buffers created with raw numeric flags may later require { buffer, access }."},{"id":"gpu.buffer.read","title":"gpu.buffer.read","signature":"gpu.buffer.read(options) -> Promise<TypedArray>","doc":{"summary":"Read a buffer back into a typed array.","surface":"Doe API gpu.buffer.","input":"A source buffer, a typed-array constructor, and optional offset or size.","returns":"A promise for a newly allocated typed array.","details":["This reads GPU buffer contents back to JS. If the buffer is already","mappable for read, Doe maps it directly; otherwise Doe stages the copy","through a temporary readback buffer."],"notes":["options.offset and options.size let you read a subrange.","The typed-array constructor must accept a plain ArrayBuffer.","See raw buffer.mapAsync(...) when you need manual readback control."],"example":"const out = await gpu.buffer.read(dst, Float32Array);"},"sourcePath":"@simulatte/webgpu-doe/src/index.js","typePath":"@simulatte/webgpu-doe/src/index.d.ts","sourceLabel":"Source","typeLabel":"Types","hasTypeHint":true,"tokens":"gpu.buffer.read gpu.buffer.read(options) -> promise<typedarray> read a buffer back into a typed array. doe api gpu.buffer. a source buffer, a typed-array constructor, and optional offset or size. a promise for a newly allocated typed array. this reads gpu buffer contents back to js. if the buffer is already mappable for read, doe maps it directly; otherwise doe stages the copy through a temporary readback buffer. options.offset and options.size let you read a subrange. the typed-array constructor must accept a plain arraybuffer. see raw buffer.mapasync(...) when you need manual readback control."},{"id":"gpu.kernel.run","title":"gpu.kernel.run","signature":"gpu.kernel.run(options) -> Promise<void>","doc":{"summary":"Compile and dispatch a one-off compute job.","surface":"Doe API gpu.kernel.","input":"WGSL source, bindings, workgroups, and an optional entry point or label.","returns":"A promise that resolves after submission completes.","details":["This is the explicit one-shot compute path. It builds the pipeline for","the provided shader, dispatches once, and waits for completion."],"notes":["workgroups may be number, [x, y], or [x, y, z].","Bare buffers without Doe helper metadata require { buffer, access }.","See gpu.kernel.create(...) when you will reuse the shader shape.","See gpu.compute(...) for the narrower typed-array workflow."],"example":"await gpu.kernel.run({\n code,\n bindings: [src, dst],\n workgroups: 1,\n});"},"sourcePath":"@simulatte/webgpu-doe/src/index.js","typePath":"@simulatte/webgpu-doe/src/index.d.ts","sourceLabel":"Source","typeLabel":"Types","hasTypeHint":true,"tokens":"gpu.kernel.run gpu.kernel.run(options) -> promise<void> compile and dispatch a one-off compute job. doe api gpu.kernel. wgsl source, bindings, workgroups, and an optional entry point or label. a promise that resolves after submission completes. this is the explicit one-shot compute path. it builds the pipeline for the provided shader, dispatches once, and waits for completion. workgroups may be number, [x, y], or [x, y, z]. bare buffers without doe helper metadata require { buffer, access }. see gpu.kernel.create(...) when you will reuse the shader shape. see gpu.compute(...) for the narrower typed-array workflow."},{"id":"gpu.kernel.create","title":"gpu.kernel.create","signature":"gpu.kernel.create(options) -> DoeKernel","doc":{"summary":"Compile a reusable compute kernel.","surface":"Doe API gpu.kernel.","input":"WGSL source, an optional entry point, and an initial binding shape.","returns":"A DoeKernel object with dispatch(...).","details":["This creates the shader module, bind-group layout, and compute","pipeline once so the same WGSL shape can be dispatched repeatedly."],"notes":["Binding access is inferred from the bindings passed at compile time.","See kernel.dispatch(...) to run the compiled kernel.","See gpu.kernel.run(...) when reuse does not matter."],"example":"const kernel = gpu.kernel.create({\n code,\n bindings: [src, dst],\n});"},"sourcePath":"@simulatte/webgpu-doe/src/index.js","typePath":"@simulatte/webgpu-doe/src/index.d.ts","sourceLabel":"Source","typeLabel":"Types","hasTypeHint":true,"tokens":"gpu.kernel.create gpu.kernel.create(options) -> doekernel compile a reusable compute kernel. doe api gpu.kernel. wgsl source, an optional entry point, and an initial binding shape. a doekernel object with dispatch(...). this creates the shader module, bind-group layout, and compute pipeline once so the same wgsl shape can be dispatched repeatedly. binding access is inferred from the bindings passed at compile time. see kernel.dispatch(...) to run the compiled kernel. see gpu.kernel.run(...) when reuse does not matter."},{"id":"DoeKernel","title":"DoeKernel","signature":"class DoeKernel","doc":{"summary":"Reusable compute kernel compiled by gpu.kernel.create(...).","surface":"Doe API gpu.kernel.","input":"Created from WGSL source, an entry point, and an initial binding shape.","returns":"A reusable kernel object with dispatch(...).","details":["This object keeps the compiled pipeline and bind-group layout for a repeated","WGSL compute shape. Use it when you will dispatch the same shader more than","once and want to avoid recompiling on every call."],"notes":["See gpu.kernel.run(...) for the one-shot explicit path.","See gpu.compute(...) for the narrower typed-array workflow.","Instances are returned through the bound Doe API and are not exported directly."],"example":"const kernel = gpu.kernel.create({\n code,\n bindings: [src, dst],\n});\n\nawait kernel.dispatch({\n bindings: [src, dst],\n workgroups: 1,\n});"},"sourcePath":"@simulatte/webgpu-doe/src/index.js","typePath":"@simulatte/webgpu-doe/src/index.d.ts","sourceLabel":"Source","typeLabel":"Types","hasTypeHint":true,"tokens":"doekernel class doekernel reusable compute kernel compiled by gpu.kernel.create(...). doe api gpu.kernel. created from wgsl source, an entry point, and an initial binding shape. a reusable kernel object with dispatch(...). this object keeps the compiled pipeline and bind-group layout for a repeated wgsl compute shape. use it when you will dispatch the same shader more than once and want to avoid recompiling on every call. see gpu.kernel.run(...) for the one-shot explicit path. see gpu.compute(...) for the narrower typed-array workflow. instances are returned through the bound doe api and are not exported directly."},{"id":"DoeKernel.dispatch","title":"kernel.dispatch","signature":"kernel.dispatch(options) -> Promise<void>","doc":{"summary":"Dispatch this compiled kernel once.","surface":"Doe API gpu.kernel.","input":"A binding list, workgroup counts, and an optional label.","returns":"A promise that resolves after submission completes.","details":["This records one compute pass for the compiled pipeline, submits it, and","waits for completion when the underlying queue exposes","onSubmittedWorkDone()."],"notes":["workgroups may be number, [x, y], or [x, y, z].","Bare buffers without Doe helper metadata require { buffer, access }.","See gpu.kernel.run(...) when you do not need reuse."],"example":"await kernel.dispatch({\n bindings: [src, dst],\n workgroups: [4, 1, 1],\n});"},"sourcePath":"@simulatte/webgpu-doe/src/index.js","typePath":"@simulatte/webgpu-doe/src/index.d.ts","sourceLabel":"Source","typeLabel":"Types","hasTypeHint":true,"tokens":"kernel.dispatch kernel.dispatch(options) -> promise<void> dispatch this compiled kernel once. doe api gpu.kernel. a binding list, workgroup counts, and an optional label. a promise that resolves after submission completes. this records one compute pass for the compiled pipeline, submits it, and waits for completion when the underlying queue exposes onsubmittedworkdone(). workgroups may be number, [x, y], or [x, y, z]. bare buffers without doe helper metadata require { buffer, access }. see gpu.kernel.run(...) when you do not need reuse."},{"id":"gpu.compute","title":"gpu.compute","signature":"gpu.compute(options) -> Promise<TypedArray>","doc":{"summary":"Run a one-shot typed-array compute workflow.","surface":"Doe API gpu.compute.","input":"WGSL source, typed-array or buffer inputs, an output spec, and workgroups.","returns":"A promise for the requested typed-array output.","details":["This is the most opinionated Doe helper. It creates temporary buffers","as needed, uploads host data, dispatches the compute shader once,","reads back the requested output, and destroys temporary resources","before returning."],"notes":["Raw numeric usage flags are accepted only when explicit Doe access is also provided.","Output size defaults from likeInput or the first input when possible.","See gpu.kernel.run(...) or gpu.kernel.create(...) when you need explicit resource ownership."],"example":"const out = await gpu.compute({\n code,\n inputs: [new Float32Array([1, 2, 3, 4])],\n output: { type: Float32Array },\n workgroups: 1,\n});"},"sourcePath":"@simulatte/webgpu-doe/src/index.js","typePath":"@simulatte/webgpu-doe/src/index.d.ts","sourceLabel":"Source","typeLabel":"Types","hasTypeHint":true,"tokens":"gpu.compute gpu.compute(options) -> promise<typedarray> run a one-shot typed-array compute workflow. doe api gpu.compute. wgsl source, typed-array or buffer inputs, an output spec, and workgroups. a promise for the requested typed-array output. this is the most opinionated doe helper. it creates temporary buffers as needed, uploads host data, dispatches the compute shader once, reads back the requested output, and destroys temporary resources before returning. raw numeric usage flags are accepted only when explicit doe access is also provided. output size defaults from likeinput or the first input when possible. see gpu.kernel.run(...) or gpu.kernel.create(...) when you need explicit resource ownership."}],"examples":[{"filename":"buffers-readback.js","title":"Buffer create + readback","summary":"Create a Doe-managed buffer from host data, then read it back through gpu.buffer.read(...).","apiIds":["gpu.buffer.create","gpu.buffer.read"],"accent":"buffer","source":"import { doe } from \"@simulatte/webgpu/compute\";\n\nconst gpu = await doe.requestDevice();\nconst src = gpu.buffer.create({\n data: new Float32Array([1, 2, 3, 4]),\n usage: [\"storageRead\", \"readback\"],\n});\n\nconst result = await gpu.buffer.read({ buffer: src, type: Float32Array });\nconsole.log(JSON.stringify(Array.from(result)));","runnableSource":"const gpu = await doe.requestDevice();\nconst src = gpu.buffer.create({\n data: new Float32Array([1, 2, 3, 4]),\n usage: [\"storageRead\", \"readback\"],\n});\n\nconst result = await gpu.buffer.read({ buffer: src, type: Float32Array });\nconsole.log(JSON.stringify(Array.from(result)));","tokens":"buffer create + readback create a doe-managed buffer from host data, then read it back through gpu.buffer.read(...). buffers-readback.js gpu.buffer.create gpu.buffer.read"},{"filename":"kernel-run.js","title":"One-off kernel run","summary":"Use gpu.kernel.run(...) when you want explicit buffers but do not need to keep compiled kernel state.","apiIds":["gpu.kernel.run","gpu.buffer.create","gpu.buffer.read"],"accent":"kernel","source":"import { doe } from \"@simulatte/webgpu/compute\";\n\nconst gpu = await doe.requestDevice();\nconst src = gpu.buffer.create({ data: new Float32Array([1, 2, 3, 4]) });\nconst dst = gpu.buffer.create({ size: src.size, usage: \"storageReadWrite\" });\n\nawait gpu.kernel.run({\n code: `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let i = gid.x;\n dst[i] = src[i] * 2.0;\n }\n `,\n bindings: [src, dst],\n workgroups: 1,\n});\n\nconst result = await gpu.buffer.read({ buffer: dst, type: Float32Array });\nconsole.log(JSON.stringify(Array.from(result)));","runnableSource":"const gpu = await doe.requestDevice();\nconst src = gpu.buffer.create({ data: new Float32Array([1, 2, 3, 4]) });\nconst dst = gpu.buffer.create({ size: src.size, usage: \"storageReadWrite\" });\n\nawait gpu.kernel.run({\n code: `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let i = gid.x;\n dst[i] = src[i] * 2.0;\n }\n `,\n bindings: [src, dst],\n workgroups: 1,\n});\n\nconst result = await gpu.buffer.read({ buffer: dst, type: Float32Array });\nconsole.log(JSON.stringify(Array.from(result)));","tokens":"one-off kernel run use gpu.kernel.run(...) when you want explicit buffers but do not need to keep compiled kernel state. kernel-run.js gpu.kernel.run gpu.buffer.create gpu.buffer.read"},{"filename":"kernel-create-and-dispatch.js","title":"Reusable kernel dispatch","summary":"Compile a DoeKernel once with gpu.kernel.create(...), then dispatch it explicitly.","apiIds":["gpu.kernel.create","DoeKernel","DoeKernel.dispatch"],"accent":"kernel","source":"import { doe } from \"@simulatte/webgpu/compute\";\n\nconst gpu = await doe.requestDevice();\nconst src = gpu.buffer.create({ data: new Float32Array([1, 2, 3, 4]) });\nconst dst = gpu.buffer.create({ size: src.size, usage: \"storageReadWrite\" });\n\nconst kernel = gpu.kernel.create({\n code: `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let i = gid.x;\n dst[i] = src[i] * 5.0;\n }\n `,\n bindings: [src, dst],\n workgroups: 1,\n});\n\nawait kernel.dispatch({\n bindings: [src, dst],\n workgroups: 1,\n});\n\nconst result = await gpu.buffer.read({ buffer: dst, type: Float32Array });\nconsole.log(JSON.stringify(Array.from(result)));","runnableSource":"const gpu = await doe.requestDevice();\nconst src = gpu.buffer.create({ data: new Float32Array([1, 2, 3, 4]) });\nconst dst = gpu.buffer.create({ size: src.size, usage: \"storageReadWrite\" });\n\nconst kernel = gpu.kernel.create({\n code: `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let i = gid.x;\n dst[i] = src[i] * 5.0;\n }\n `,\n bindings: [src, dst],\n workgroups: 1,\n});\n\nawait kernel.dispatch({\n bindings: [src, dst],\n workgroups: 1,\n});\n\nconst result = await gpu.buffer.read({ buffer: dst, type: Float32Array });\nconsole.log(JSON.stringify(Array.from(result)));","tokens":"reusable kernel dispatch compile a doekernel once with gpu.kernel.create(...), then dispatch it explicitly. kernel-create-and-dispatch.js gpu.kernel.create doekernel doekernel.dispatch"},{"filename":"compute-one-shot.js","title":"One-shot compute","summary":"Run the opinionated gpu.compute(...) helper with one typed-array input and inferred output sizing.","apiIds":["gpu.compute"],"accent":"compute","source":"import { doe } from \"@simulatte/webgpu/compute\";\n\nconst gpu = await doe.requestDevice();\n\nconst result = await gpu.compute({\n code: `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let i = gid.x;\n dst[i] = src[i] * 3.0;\n }\n `,\n inputs: [new Float32Array([1, 2, 3, 4])],\n output: {\n type: Float32Array,\n },\n workgroups: 1,\n});\n\nconsole.log(JSON.stringify(Array.from(result)));","runnableSource":"const gpu = await doe.requestDevice();\n\nconst result = await gpu.compute({\n code: `\n @group(0) @binding(0) var<storage, read> src: array<f32>;\n @group(0) @binding(1) var<storage, read_write> dst: array<f32>;\n\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let i = gid.x;\n dst[i] = src[i] * 3.0;\n }\n `,\n inputs: [new Float32Array([1, 2, 3, 4])],\n output: {\n type: Float32Array,\n },\n workgroups: 1,\n});\n\nconsole.log(JSON.stringify(Array.from(result)));","tokens":"one-shot compute run the opinionated gpu.compute(...) helper with one typed-array input and inferred output sizing. compute-one-shot.js gpu.compute"},{"filename":"compute-one-shot-like-input.js","title":"One-shot compute with likeInput","summary":"Use gpu.compute(...) with a uniform input and likeInput sizing to keep output shape explicit.","apiIds":["gpu.compute"],"accent":"compute","source":"import { doe } from \"@simulatte/webgpu/compute\";\n\nconst gpu = await doe.requestDevice();\n\nconst result = await gpu.compute({\n code: `\n struct Scale {\n value: f32,\n };\n\n @group(0) @binding(0) var<uniform> scale: Scale;\n @group(0) @binding(1) var<storage, read> src: array<f32>;\n @group(0) @binding(2) var<storage, read_write> dst: array<f32>;\n\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let i = gid.x;\n dst[i] = src[i] * scale.value;\n }\n `,\n inputs: [\n {\n data: new Float32Array([2]),\n usage: \"uniform\",\n access: \"uniform\",\n },\n new Float32Array([1, 2, 3, 4]),\n ],\n output: {\n type: Float32Array,\n likeInput: 1,\n },\n workgroups: [1, 1],\n});\n\nconsole.log(JSON.stringify(Array.from(result)));","runnableSource":"const gpu = await doe.requestDevice();\n\nconst result = await gpu.compute({\n code: `\n struct Scale {\n value: f32,\n };\n\n @group(0) @binding(0) var<uniform> scale: Scale;\n @group(0) @binding(1) var<storage, read> src: array<f32>;\n @group(0) @binding(2) var<storage, read_write> dst: array<f32>;\n\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let i = gid.x;\n dst[i] = src[i] * scale.value;\n }\n `,\n inputs: [\n {\n data: new Float32Array([2]),\n usage: \"uniform\",\n access: \"uniform\",\n },\n new Float32Array([1, 2, 3, 4]),\n ],\n output: {\n type: Float32Array,\n likeInput: 1,\n },\n workgroups: [1, 1],\n});\n\nconsole.log(JSON.stringify(Array.from(result)));","tokens":"one-shot compute with likeinput use gpu.compute(...) with a uniform input and likeinput sizing to keep output shape explicit. compute-one-shot-like-input.js gpu.compute"},{"filename":"compute-one-shot-multiple-inputs.js","title":"One-shot compute with multiple inputs","summary":"Feed multiple typed-array inputs through gpu.compute(...) while keeping the shader and result explicit.","apiIds":["gpu.compute"],"accent":"compute","source":"import { doe } from \"@simulatte/webgpu/compute\";\n\nconst gpu = await doe.requestDevice();\n\nconst result = await gpu.compute({\n code: `\n @group(0) @binding(0) var<storage, read> lhs: array<f32>;\n @group(0) @binding(1) var<storage, read> rhs: array<f32>;\n @group(0) @binding(2) var<storage, read_write> dst: array<f32>;\n\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let i = gid.x;\n dst[i] = lhs[i] + rhs[i];\n }\n `,\n inputs: [\n new Float32Array([1, 2, 3, 4]),\n new Float32Array([10, 20, 30, 40]),\n ],\n output: {\n type: Float32Array,\n },\n workgroups: 1,\n});\n\nconsole.log(JSON.stringify(Array.from(result)));","runnableSource":"const gpu = await doe.requestDevice();\n\nconst result = await gpu.compute({\n code: `\n @group(0) @binding(0) var<storage, read> lhs: array<f32>;\n @group(0) @binding(1) var<storage, read> rhs: array<f32>;\n @group(0) @binding(2) var<storage, read_write> dst: array<f32>;\n\n @compute @workgroup_size(4)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let i = gid.x;\n dst[i] = lhs[i] + rhs[i];\n }\n `,\n inputs: [\n new Float32Array([1, 2, 3, 4]),\n new Float32Array([10, 20, 30, 40]),\n ],\n output: {\n type: Float32Array,\n },\n workgroups: 1,\n});\n\nconsole.log(JSON.stringify(Array.from(result)));","tokens":"one-shot compute with multiple inputs feed multiple typed-array inputs through gpu.compute(...) while keeping the shader and result explicit. compute-one-shot-multiple-inputs.js gpu.compute"},{"filename":"compute-one-shot-matmul.js","title":"One-shot compute matmul","summary":"Run a larger matrix multiply through gpu.compute(...) with explicit tensor dimensions and output size.","apiIds":["gpu.compute"],"accent":"compute","source":"import { doe } from \"@simulatte/webgpu/compute\";\n\nconst gpu = await doe.requestDevice();\nconst M = 256;\nconst K = 512;\nconst N = 256;\n\nconst lhs = Float32Array.from({ length: M * K }, (_, i) => (i % 17) / 17);\nconst rhs = Float32Array.from({ length: K * N }, (_, i) => (i % 13) / 13);\nconst dims = new Uint32Array([M, K, N, 0]);\n\nconst result = await gpu.compute({\n code: `\n struct Dims {\n m: u32,\n k: u32,\n n: u32,\n _pad: u32,\n };\n\n @group(0) @binding(0) var<uniform> dims: Dims;\n @group(0) @binding(1) var<storage, read> lhs: array<f32>;\n @group(0) @binding(2) var<storage, read> rhs: array<f32>;\n @group(0) @binding(3) var<storage, read_write> out: array<f32>;\n\n @compute @workgroup_size(8, 8)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let row = gid.y;\n let col = gid.x;\n if (row >= dims.m || col >= dims.n) {\n return;\n }\n\n var acc = 0.0;\n for (var i = 0u; i < dims.k; i = i + 1u) {\n acc += lhs[row * dims.k + i] * rhs[i * dims.n + col];\n }\n out[row * dims.n + col] = acc;\n }\n `,\n inputs: [\n { data: dims, usage: \"uniform\", access: \"uniform\" },\n lhs,\n rhs,\n ],\n output: {\n type: Float32Array,\n size: M * N * Float32Array.BYTES_PER_ELEMENT,\n },\n workgroups: [Math.ceil(N / 8), Math.ceil(M / 8)],\n});\n\nconsole.log(JSON.stringify(Array.from(result.subarray(0, 8), (value) => Number(value.toFixed(4)))));","runnableSource":"const gpu = await doe.requestDevice();\nconst M = 256;\nconst K = 512;\nconst N = 256;\n\nconst lhs = Float32Array.from({ length: M * K }, (_, i) => (i % 17) / 17);\nconst rhs = Float32Array.from({ length: K * N }, (_, i) => (i % 13) / 13);\nconst dims = new Uint32Array([M, K, N, 0]);\n\nconst result = await gpu.compute({\n code: `\n struct Dims {\n m: u32,\n k: u32,\n n: u32,\n _pad: u32,\n };\n\n @group(0) @binding(0) var<uniform> dims: Dims;\n @group(0) @binding(1) var<storage, read> lhs: array<f32>;\n @group(0) @binding(2) var<storage, read> rhs: array<f32>;\n @group(0) @binding(3) var<storage, read_write> out: array<f32>;\n\n @compute @workgroup_size(8, 8)\n fn main(@builtin(global_invocation_id) gid: vec3u) {\n let row = gid.y;\n let col = gid.x;\n if (row >= dims.m || col >= dims.n) {\n return;\n }\n\n var acc = 0.0;\n for (var i = 0u; i < dims.k; i = i + 1u) {\n acc += lhs[row * dims.k + i] * rhs[i * dims.n + col];\n }\n out[row * dims.n + col] = acc;\n }\n `,\n inputs: [\n { data: dims, usage: \"uniform\", access: \"uniform\" },\n lhs,\n rhs,\n ],\n output: {\n type: Float32Array,\n size: M * N * Float32Array.BYTES_PER_ELEMENT,\n },\n workgroups: [Math.ceil(N / 8), Math.ceil(M / 8)],\n});\n\nconsole.log(JSON.stringify(Array.from(result.subarray(0, 8), (value) => Number(value.toFixed(4)))));","tokens":"one-shot compute matmul run a larger matrix multiply through gpu.compute(...) with explicit tensor dimensions and output size. compute-one-shot-matmul.js gpu.compute"}]}</script>
|
|
1244
|
+
<script type="module">
|
|
1245
|
+
const DOC_DATA = JSON.parse(document.getElementById("doe-api-data").textContent);
|
|
1246
|
+
const outputState = new Map();
|
|
1247
|
+
let doePromise = null;
|
|
1248
|
+
|
|
1249
|
+
function setStatus(id, text) {
|
|
1250
|
+
document.getElementById(id).textContent = text;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
function resolveBufferUsageToken(token, combined = false) {
|
|
1254
|
+
switch (token) {
|
|
1255
|
+
case "upload":
|
|
1256
|
+
return GPUBufferUsage.COPY_DST;
|
|
1257
|
+
case "readback":
|
|
1258
|
+
return combined
|
|
1259
|
+
? GPUBufferUsage.COPY_SRC
|
|
1260
|
+
: GPUBufferUsage.COPY_SRC | GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ;
|
|
1261
|
+
case "uniform":
|
|
1262
|
+
return GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST;
|
|
1263
|
+
case "storageRead":
|
|
1264
|
+
return GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST;
|
|
1265
|
+
case "storageReadWrite":
|
|
1266
|
+
return GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST | GPUBufferUsage.COPY_SRC;
|
|
1267
|
+
default:
|
|
1268
|
+
throw new Error(`Unknown Doe buffer usage token: ${token}`);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
function resolveBufferUsage(usage) {
|
|
1273
|
+
if (typeof usage === "number") return usage;
|
|
1274
|
+
if (typeof usage === "string") return resolveBufferUsageToken(usage);
|
|
1275
|
+
if (Array.isArray(usage)) {
|
|
1276
|
+
const combined = usage.length > 1;
|
|
1277
|
+
return usage.reduce((mask, token) => mask | (
|
|
1278
|
+
typeof token === "number"
|
|
1279
|
+
? token
|
|
1280
|
+
: resolveBufferUsageToken(token, combined)
|
|
1281
|
+
), 0);
|
|
1282
|
+
}
|
|
1283
|
+
throw new Error("Doe buffer usage must be a number, string, or string array.");
|
|
1284
|
+
}
|
|
1285
|
+
|
|
1286
|
+
function inferBindingAccessToken(token) {
|
|
1287
|
+
switch (token) {
|
|
1288
|
+
case "uniform":
|
|
1289
|
+
return "uniform";
|
|
1290
|
+
case "storageRead":
|
|
1291
|
+
return "storageRead";
|
|
1292
|
+
case "storageReadWrite":
|
|
1293
|
+
return "storageReadWrite";
|
|
1294
|
+
default:
|
|
1295
|
+
return null;
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
function inferBindingAccess(usage) {
|
|
1300
|
+
if (typeof usage === "number" || usage == null) return null;
|
|
1301
|
+
const tokens = typeof usage === "string"
|
|
1302
|
+
? [usage]
|
|
1303
|
+
: Array.isArray(usage)
|
|
1304
|
+
? usage.filter((token) => typeof token !== "number")
|
|
1305
|
+
: null;
|
|
1306
|
+
if (!tokens) return null;
|
|
1307
|
+
const inferred = [...new Set(tokens.map(inferBindingAccessToken).filter(Boolean))];
|
|
1308
|
+
if (inferred.length > 1) {
|
|
1309
|
+
throw new Error(`Doe buffer usage cannot imply multiple binding access modes: ${inferred.join(", ")}`);
|
|
1310
|
+
}
|
|
1311
|
+
return inferred[0] ?? null;
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
function normalizeDataView(data) {
|
|
1315
|
+
if (ArrayBuffer.isView(data)) {
|
|
1316
|
+
return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
|
|
1317
|
+
}
|
|
1318
|
+
if (data instanceof ArrayBuffer) {
|
|
1319
|
+
return new Uint8Array(data);
|
|
1320
|
+
}
|
|
1321
|
+
throw new Error("Doe buffer data must be an ArrayBuffer or ArrayBufferView.");
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
function resolveBufferSize(source, meta) {
|
|
1325
|
+
if (source && typeof source === "object" && typeof source.size === "number") {
|
|
1326
|
+
return source.size;
|
|
1327
|
+
}
|
|
1328
|
+
if (meta.has(source)) {
|
|
1329
|
+
return meta.get(source).size;
|
|
1330
|
+
}
|
|
1331
|
+
if (ArrayBuffer.isView(source)) {
|
|
1332
|
+
return source.byteLength;
|
|
1333
|
+
}
|
|
1334
|
+
if (source instanceof ArrayBuffer) {
|
|
1335
|
+
return source.byteLength;
|
|
1336
|
+
}
|
|
1337
|
+
throw new Error("Doe buffer-like source must expose a byte size or be ArrayBuffer-backed data.");
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
function normalizeWorkgroups(workgroups) {
|
|
1341
|
+
if (typeof workgroups === "number") return [workgroups, 1, 1];
|
|
1342
|
+
if (Array.isArray(workgroups) && workgroups.length === 2) return [workgroups[0], workgroups[1], 1];
|
|
1343
|
+
if (Array.isArray(workgroups) && workgroups.length === 3) return workgroups;
|
|
1344
|
+
throw new Error("Doe workgroups must be a number, [x, y], or [x, y, z].");
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
function validatePositiveInteger(value, label) {
|
|
1348
|
+
if (!Number.isInteger(value) || value < 1) {
|
|
1349
|
+
throw new Error(`${label} must be a positive integer.`);
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
function createBrowserDoe(device) {
|
|
1354
|
+
const bufferMeta = new WeakMap();
|
|
1355
|
+
|
|
1356
|
+
function rememberBuffer(buffer, usage, size) {
|
|
1357
|
+
bufferMeta.set(buffer, {
|
|
1358
|
+
bindingAccess: inferBindingAccess(usage),
|
|
1359
|
+
size,
|
|
1360
|
+
});
|
|
1361
|
+
return buffer;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
function inferredBindingAccessForBuffer(buffer) {
|
|
1365
|
+
return bufferMeta.get(buffer)?.bindingAccess ?? null;
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
function validateWorkgroups(workgroups) {
|
|
1369
|
+
const normalized = normalizeWorkgroups(workgroups);
|
|
1370
|
+
const [x, y, z] = normalized;
|
|
1371
|
+
validatePositiveInteger(x, "Doe workgroups.x");
|
|
1372
|
+
validatePositiveInteger(y, "Doe workgroups.y");
|
|
1373
|
+
validatePositiveInteger(z, "Doe workgroups.z");
|
|
1374
|
+
return normalized;
|
|
1375
|
+
}
|
|
1376
|
+
|
|
1377
|
+
function normalizeBinding(binding, index) {
|
|
1378
|
+
const entry = binding && typeof binding === "object" && "buffer" in binding
|
|
1379
|
+
? binding
|
|
1380
|
+
: { buffer: binding };
|
|
1381
|
+
const access = entry.access ?? inferredBindingAccessForBuffer(entry.buffer);
|
|
1382
|
+
if (!access) {
|
|
1383
|
+
throw new Error(
|
|
1384
|
+
"Doe binding access is required for buffers without Doe helper usage metadata. " +
|
|
1385
|
+
"Pass { buffer, access } or create the buffer through gpu.buffer.create(...) with a bindable usage token."
|
|
1386
|
+
);
|
|
1387
|
+
}
|
|
1388
|
+
return {
|
|
1389
|
+
binding: index,
|
|
1390
|
+
buffer: entry.buffer,
|
|
1391
|
+
access,
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
function bindGroupLayoutEntry(binding) {
|
|
1396
|
+
const bufferType = binding.access === "uniform"
|
|
1397
|
+
? "uniform"
|
|
1398
|
+
: binding.access === "storageRead"
|
|
1399
|
+
? "read-only-storage"
|
|
1400
|
+
: "storage";
|
|
1401
|
+
return {
|
|
1402
|
+
binding: binding.binding,
|
|
1403
|
+
visibility: GPUShaderStage.COMPUTE,
|
|
1404
|
+
buffer: { type: bufferType },
|
|
1405
|
+
};
|
|
1406
|
+
}
|
|
1407
|
+
|
|
1408
|
+
function bindGroupEntry(binding) {
|
|
1409
|
+
return {
|
|
1410
|
+
binding: binding.binding,
|
|
1411
|
+
resource: { buffer: binding.buffer },
|
|
1412
|
+
};
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
class BrowserDoeKernel {
|
|
1416
|
+
constructor(pipeline, layout) {
|
|
1417
|
+
this.pipeline = pipeline;
|
|
1418
|
+
this.layout = layout;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
async dispatch(options) {
|
|
1422
|
+
const bindings = (options.bindings ?? []).map(normalizeBinding);
|
|
1423
|
+
const workgroups = validateWorkgroups(options.workgroups);
|
|
1424
|
+
const bindGroup = device.createBindGroup({
|
|
1425
|
+
layout: this.layout,
|
|
1426
|
+
entries: bindings.map(bindGroupEntry),
|
|
1427
|
+
});
|
|
1428
|
+
const encoder = device.createCommandEncoder({ label: options.label ?? undefined });
|
|
1429
|
+
const pass = encoder.beginComputePass({ label: options.label ?? undefined });
|
|
1430
|
+
pass.setPipeline(this.pipeline);
|
|
1431
|
+
if (bindings.length > 0) {
|
|
1432
|
+
pass.setBindGroup(0, bindGroup);
|
|
1433
|
+
}
|
|
1434
|
+
pass.dispatchWorkgroups(workgroups[0], workgroups[1], workgroups[2]);
|
|
1435
|
+
pass.end();
|
|
1436
|
+
device.queue.submit([encoder.finish()]);
|
|
1437
|
+
await device.queue.onSubmittedWorkDone();
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
function createBuffer(options) {
|
|
1442
|
+
if (!options || typeof options !== "object") {
|
|
1443
|
+
throw new Error("Doe buffer options must be an object.");
|
|
1444
|
+
}
|
|
1445
|
+
if (options.data != null) {
|
|
1446
|
+
const view = normalizeDataView(options.data);
|
|
1447
|
+
const usage = options.usage ?? "storageRead";
|
|
1448
|
+
const size = options.size ?? view.byteLength;
|
|
1449
|
+
const buffer = rememberBuffer(device.createBuffer({
|
|
1450
|
+
label: options.label ?? undefined,
|
|
1451
|
+
size,
|
|
1452
|
+
usage: resolveBufferUsage(usage),
|
|
1453
|
+
mappedAtCreation: false,
|
|
1454
|
+
}), usage, size);
|
|
1455
|
+
device.queue.writeBuffer(buffer, 0, view);
|
|
1456
|
+
return buffer;
|
|
1457
|
+
}
|
|
1458
|
+
validatePositiveInteger(options.size, "Doe buffer size");
|
|
1459
|
+
return rememberBuffer(device.createBuffer({
|
|
1460
|
+
label: options.label ?? undefined,
|
|
1461
|
+
size: options.size,
|
|
1462
|
+
usage: resolveBufferUsage(options.usage),
|
|
1463
|
+
mappedAtCreation: options.mappedAtCreation ?? false,
|
|
1464
|
+
}), options.usage, options.size);
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
async function readBuffer(options) {
|
|
1468
|
+
if (!options || typeof options !== "object") {
|
|
1469
|
+
throw new Error("Doe buffer.read options must be an object.");
|
|
1470
|
+
}
|
|
1471
|
+
const buffer = options.buffer;
|
|
1472
|
+
const type = options.type;
|
|
1473
|
+
if (!buffer || typeof buffer !== "object") {
|
|
1474
|
+
throw new Error("Doe buffer.read requires a buffer.");
|
|
1475
|
+
}
|
|
1476
|
+
if (typeof type !== "function") {
|
|
1477
|
+
throw new Error("Doe buffer.read type must be a typed-array constructor.");
|
|
1478
|
+
}
|
|
1479
|
+
const fullSize = resolveBufferSize(buffer, bufferMeta);
|
|
1480
|
+
const offset = options.offset ?? 0;
|
|
1481
|
+
const size = options.size ?? Math.max(0, fullSize - offset);
|
|
1482
|
+
const staging = device.createBuffer({
|
|
1483
|
+
label: options.label ?? undefined,
|
|
1484
|
+
size,
|
|
1485
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
|
|
1486
|
+
});
|
|
1487
|
+
const encoder = device.createCommandEncoder({ label: options.label ?? undefined });
|
|
1488
|
+
encoder.copyBufferToBuffer(buffer, offset, staging, 0, size);
|
|
1489
|
+
device.queue.submit([encoder.finish()]);
|
|
1490
|
+
await device.queue.onSubmittedWorkDone();
|
|
1491
|
+
await staging.mapAsync(GPUMapMode.READ);
|
|
1492
|
+
const copy = staging.getMappedRange().slice(0);
|
|
1493
|
+
staging.unmap();
|
|
1494
|
+
staging.destroy();
|
|
1495
|
+
return new type(copy);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
function createKernel(options) {
|
|
1499
|
+
const bindings = (options.bindings ?? []).map(normalizeBinding);
|
|
1500
|
+
const shader = device.createShaderModule({ code: options.code });
|
|
1501
|
+
const bindGroupLayout = device.createBindGroupLayout({
|
|
1502
|
+
entries: bindings.map(bindGroupLayoutEntry),
|
|
1503
|
+
});
|
|
1504
|
+
const pipelineLayout = device.createPipelineLayout({
|
|
1505
|
+
bindGroupLayouts: [bindGroupLayout],
|
|
1506
|
+
});
|
|
1507
|
+
const pipeline = device.createComputePipeline({
|
|
1508
|
+
layout: pipelineLayout,
|
|
1509
|
+
compute: {
|
|
1510
|
+
module: shader,
|
|
1511
|
+
entryPoint: options.entryPoint ?? "main",
|
|
1512
|
+
},
|
|
1513
|
+
});
|
|
1514
|
+
return new BrowserDoeKernel(pipeline, bindGroupLayout);
|
|
1515
|
+
}
|
|
1516
|
+
|
|
1517
|
+
async function runKernel(options) {
|
|
1518
|
+
const kernel = createKernel(options);
|
|
1519
|
+
await kernel.dispatch({
|
|
1520
|
+
bindings: options.bindings ?? [],
|
|
1521
|
+
workgroups: options.workgroups,
|
|
1522
|
+
label: options.label,
|
|
1523
|
+
});
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
function usesRawNumericFlags(usage) {
|
|
1527
|
+
return typeof usage === "number" || (Array.isArray(usage) && usage.some((token) => typeof token === "number"));
|
|
1528
|
+
}
|
|
1529
|
+
|
|
1530
|
+
function assertLayer3Usage(usage, access, path) {
|
|
1531
|
+
if (usesRawNumericFlags(usage) && !access) {
|
|
1532
|
+
throw new Error(`Doe ${path} accepts raw numeric usage flags only when explicit access is also provided.`);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
function normalizeOnceInput(input, index) {
|
|
1537
|
+
if (ArrayBuffer.isView(input) || input instanceof ArrayBuffer) {
|
|
1538
|
+
const buffer = createBuffer({ data: input });
|
|
1539
|
+
return {
|
|
1540
|
+
binding: buffer,
|
|
1541
|
+
buffer,
|
|
1542
|
+
byteLength: resolveBufferSize(input, bufferMeta),
|
|
1543
|
+
owned: true,
|
|
1544
|
+
};
|
|
1545
|
+
}
|
|
1546
|
+
if (input && typeof input === "object" && "data" in input) {
|
|
1547
|
+
assertLayer3Usage(input.usage, input.access, `compute input ${index} usage`);
|
|
1548
|
+
const buffer = createBuffer({
|
|
1549
|
+
data: input.data,
|
|
1550
|
+
usage: input.usage ?? "storageRead",
|
|
1551
|
+
label: input.label,
|
|
1552
|
+
});
|
|
1553
|
+
return {
|
|
1554
|
+
binding: input.access ? { buffer, access: input.access } : buffer,
|
|
1555
|
+
buffer,
|
|
1556
|
+
byteLength: resolveBufferSize(input.data, bufferMeta),
|
|
1557
|
+
owned: true,
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
if (input && typeof input === "object" && "buffer" in input) {
|
|
1561
|
+
return {
|
|
1562
|
+
binding: input,
|
|
1563
|
+
buffer: input.buffer,
|
|
1564
|
+
byteLength: resolveBufferSize(input.buffer, bufferMeta),
|
|
1565
|
+
owned: false,
|
|
1566
|
+
};
|
|
1567
|
+
}
|
|
1568
|
+
if (input && typeof input === "object") {
|
|
1569
|
+
return {
|
|
1570
|
+
binding: input,
|
|
1571
|
+
buffer: input,
|
|
1572
|
+
byteLength: resolveBufferSize(input, bufferMeta),
|
|
1573
|
+
owned: false,
|
|
1574
|
+
};
|
|
1575
|
+
}
|
|
1576
|
+
throw new Error(`Doe compute input ${index} must be data, a Doe input spec, or a buffer.`);
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
function normalizeOnceOutput(output, inputs) {
|
|
1580
|
+
if (!output || typeof output !== "object") {
|
|
1581
|
+
throw new Error("Doe compute output is required.");
|
|
1582
|
+
}
|
|
1583
|
+
if (typeof output.type !== "function") {
|
|
1584
|
+
throw new Error("Doe compute output.type must be a typed-array constructor.");
|
|
1585
|
+
}
|
|
1586
|
+
const fallbackInputIndex = inputs.length > 0 ? 0 : null;
|
|
1587
|
+
const likeInputIndex = output.likeInput ?? fallbackInputIndex;
|
|
1588
|
+
const size = output.size ?? (
|
|
1589
|
+
likeInputIndex != null && inputs[likeInputIndex]
|
|
1590
|
+
? inputs[likeInputIndex].byteLength
|
|
1591
|
+
: null
|
|
1592
|
+
);
|
|
1593
|
+
if (!(size > 0)) {
|
|
1594
|
+
throw new Error("Doe compute output size must be provided or derived from likeInput.");
|
|
1595
|
+
}
|
|
1596
|
+
assertLayer3Usage(output.usage, output.access, "compute output usage");
|
|
1597
|
+
const buffer = createBuffer({
|
|
1598
|
+
size,
|
|
1599
|
+
usage: output.usage ?? "storageReadWrite",
|
|
1600
|
+
label: output.label,
|
|
1601
|
+
});
|
|
1602
|
+
return {
|
|
1603
|
+
binding: output.access ? { buffer, access: output.access } : buffer,
|
|
1604
|
+
buffer,
|
|
1605
|
+
type: output.type,
|
|
1606
|
+
readOptions: output.read ?? {},
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
async function compute(options) {
|
|
1611
|
+
const inputs = (options.inputs ?? []).map((input, index) => normalizeOnceInput(input, index));
|
|
1612
|
+
const output = normalizeOnceOutput(options.output, inputs);
|
|
1613
|
+
validateWorkgroups(options.workgroups);
|
|
1614
|
+
try {
|
|
1615
|
+
await runKernel({
|
|
1616
|
+
code: options.code,
|
|
1617
|
+
entryPoint: options.entryPoint,
|
|
1618
|
+
bindings: [...inputs.map((input) => input.binding), output.binding],
|
|
1619
|
+
workgroups: options.workgroups,
|
|
1620
|
+
label: options.label,
|
|
1621
|
+
});
|
|
1622
|
+
return await readBuffer({ buffer: output.buffer, type: output.type, ...output.readOptions });
|
|
1623
|
+
} finally {
|
|
1624
|
+
output.buffer.destroy?.();
|
|
1625
|
+
for (const input of inputs) {
|
|
1626
|
+
if (input.owned) {
|
|
1627
|
+
input.buffer.destroy?.();
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
return {
|
|
1634
|
+
device,
|
|
1635
|
+
buffer: {
|
|
1636
|
+
create: createBuffer,
|
|
1637
|
+
read: readBuffer,
|
|
1638
|
+
},
|
|
1639
|
+
kernel: {
|
|
1640
|
+
run: runKernel,
|
|
1641
|
+
create: createKernel,
|
|
1642
|
+
},
|
|
1643
|
+
compute,
|
|
1644
|
+
};
|
|
1645
|
+
}
|
|
1646
|
+
|
|
1647
|
+
async function ensureDoe() {
|
|
1648
|
+
if (doePromise) return doePromise;
|
|
1649
|
+
if (!("gpu" in navigator)) {
|
|
1650
|
+
throw new Error("WebGPU is unavailable in this browser.");
|
|
1651
|
+
}
|
|
1652
|
+
setStatus("status-webgpu", "WebGPU available");
|
|
1653
|
+
doePromise = (async () => {
|
|
1654
|
+
const adapter = await navigator.gpu.requestAdapter();
|
|
1655
|
+
if (!adapter) {
|
|
1656
|
+
throw new Error("No WebGPU adapter was returned.");
|
|
1657
|
+
}
|
|
1658
|
+
setStatus("status-adapter", adapter.name || "Adapter ready");
|
|
1659
|
+
const device = await adapter.requestDevice();
|
|
1660
|
+
setStatus("status-device", "Device ready");
|
|
1661
|
+
return {
|
|
1662
|
+
requestDevice: async () => createBrowserDoe(device),
|
|
1663
|
+
bind: (rawDevice) => createBrowserDoe(rawDevice),
|
|
1664
|
+
};
|
|
1665
|
+
})();
|
|
1666
|
+
return doePromise;
|
|
1667
|
+
}
|
|
1668
|
+
|
|
1669
|
+
function parseConsoleOutput(lines) {
|
|
1670
|
+
const trimmed = lines.join("\n").trim();
|
|
1671
|
+
if (!trimmed) return { text: "No console output.", data: null };
|
|
1672
|
+
try {
|
|
1673
|
+
return {
|
|
1674
|
+
text: trimmed,
|
|
1675
|
+
data: JSON.parse(trimmed),
|
|
1676
|
+
};
|
|
1677
|
+
} catch {
|
|
1678
|
+
return { text: trimmed, data: null };
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
|
|
1682
|
+
function clearChart(canvas) {
|
|
1683
|
+
const context = canvas.getContext("2d");
|
|
1684
|
+
context.clearRect(0, 0, canvas.width, canvas.height);
|
|
1685
|
+
}
|
|
1686
|
+
|
|
1687
|
+
function renderViz(statsTarget, canvas, valuesTarget, data) {
|
|
1688
|
+
statsTarget.innerHTML = "";
|
|
1689
|
+
valuesTarget.innerHTML = "";
|
|
1690
|
+
clearChart(canvas);
|
|
1691
|
+
if (!Array.isArray(data) || data.length === 0 || !data.every((value) => typeof value === "number")) {
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
const count = data.length;
|
|
1696
|
+
const min = Math.min(...data);
|
|
1697
|
+
const max = Math.max(...data);
|
|
1698
|
+
const mean = data.reduce((sum, value) => sum + value, 0) / count;
|
|
1699
|
+
const stats = [
|
|
1700
|
+
`count ${count}`,
|
|
1701
|
+
`min ${Number(min.toFixed(4))}`,
|
|
1702
|
+
`max ${Number(max.toFixed(4))}`,
|
|
1703
|
+
`mean ${Number(mean.toFixed(4))}`,
|
|
1704
|
+
];
|
|
1705
|
+
for (const stat of stats) {
|
|
1706
|
+
const chip = document.createElement("div");
|
|
1707
|
+
chip.className = "metricChip";
|
|
1708
|
+
chip.textContent = stat;
|
|
1709
|
+
statsTarget.appendChild(chip);
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
for (const value of data.slice(0, 16)) {
|
|
1713
|
+
const pill = document.createElement("div");
|
|
1714
|
+
pill.className = "valuePill";
|
|
1715
|
+
pill.textContent = Number(value.toFixed(4)).toString();
|
|
1716
|
+
valuesTarget.appendChild(pill);
|
|
1717
|
+
}
|
|
1718
|
+
|
|
1719
|
+
const context = canvas.getContext("2d");
|
|
1720
|
+
const { width, height } = canvas;
|
|
1721
|
+
const padX = 26;
|
|
1722
|
+
const padY = 24;
|
|
1723
|
+
const range = max - min || 1;
|
|
1724
|
+
const stepX = count > 1 ? (width - padX * 2) / (count - 1) : 0;
|
|
1725
|
+
|
|
1726
|
+
context.clearRect(0, 0, width, height);
|
|
1727
|
+
context.strokeStyle = "rgba(255,255,255,0.12)";
|
|
1728
|
+
context.lineWidth = 1;
|
|
1729
|
+
context.beginPath();
|
|
1730
|
+
context.moveTo(padX, height - padY);
|
|
1731
|
+
context.lineTo(width - padX, height - padY);
|
|
1732
|
+
context.moveTo(padX, padY);
|
|
1733
|
+
context.lineTo(padX, height - padY);
|
|
1734
|
+
context.stroke();
|
|
1735
|
+
|
|
1736
|
+
context.beginPath();
|
|
1737
|
+
data.forEach((value, index) => {
|
|
1738
|
+
const x = padX + index * stepX;
|
|
1739
|
+
const y = height - padY - ((value - min) / range) * (height - padY * 2);
|
|
1740
|
+
if (index === 0) {
|
|
1741
|
+
context.moveTo(x, y);
|
|
1742
|
+
} else {
|
|
1743
|
+
context.lineTo(x, y);
|
|
1744
|
+
}
|
|
1745
|
+
});
|
|
1746
|
+
context.strokeStyle = "rgba(34, 211, 238, 0.95)";
|
|
1747
|
+
context.lineWidth = 3;
|
|
1748
|
+
context.stroke();
|
|
1749
|
+
|
|
1750
|
+
context.lineTo(width - padX, height - padY);
|
|
1751
|
+
context.lineTo(padX, height - padY);
|
|
1752
|
+
context.closePath();
|
|
1753
|
+
context.fillStyle = "rgba(34, 211, 238, 0.12)";
|
|
1754
|
+
context.fill();
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
function attachSearch() {
|
|
1758
|
+
const input = document.getElementById("search");
|
|
1759
|
+
const targets = [...document.querySelectorAll(".searchTarget")];
|
|
1760
|
+
input.addEventListener("input", () => {
|
|
1761
|
+
const query = input.value.trim().toLowerCase();
|
|
1762
|
+
for (const target of targets) {
|
|
1763
|
+
const haystack = target.dataset.search || "";
|
|
1764
|
+
target.classList.toggle("hidden", query !== "" && !haystack.includes(query));
|
|
1765
|
+
}
|
|
1766
|
+
});
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1769
|
+
function attachExampleControls() {
|
|
1770
|
+
for (const example of DOC_DATA.examples) {
|
|
1771
|
+
const editor = document.querySelector(`[data-example-editor="${CSS.escape(example.filename)}"]`);
|
|
1772
|
+
const meta = document.querySelector(`[data-output-meta="${CSS.escape(example.filename)}"]`);
|
|
1773
|
+
const text = document.querySelector(`[data-output-text="${CSS.escape(example.filename)}"]`);
|
|
1774
|
+
const stats = document.querySelector(`[data-output-stats="${CSS.escape(example.filename)}"]`);
|
|
1775
|
+
const chart = document.querySelector(`[data-output-chart="${CSS.escape(example.filename)}"]`);
|
|
1776
|
+
const values = document.querySelector(`[data-output-values="${CSS.escape(example.filename)}"]`);
|
|
1777
|
+
|
|
1778
|
+
document.querySelector(`[data-reset-example="${CSS.escape(example.filename)}"]`).addEventListener("click", () => {
|
|
1779
|
+
editor.value = example.source;
|
|
1780
|
+
meta.textContent = "Reset to shipped example.";
|
|
1781
|
+
text.textContent = "";
|
|
1782
|
+
stats.innerHTML = "";
|
|
1783
|
+
values.innerHTML = "";
|
|
1784
|
+
clearChart(chart);
|
|
1785
|
+
});
|
|
1786
|
+
|
|
1787
|
+
document.querySelector(`[data-copy-example="${CSS.escape(example.filename)}"]`).addEventListener("click", async () => {
|
|
1788
|
+
await navigator.clipboard.writeText(editor.value);
|
|
1789
|
+
meta.textContent = "Copied example source.";
|
|
1790
|
+
});
|
|
1791
|
+
|
|
1792
|
+
document.querySelector(`[data-run-example="${CSS.escape(example.filename)}"]`).addEventListener("click", async () => {
|
|
1793
|
+
meta.textContent = "Running…";
|
|
1794
|
+
text.textContent = "";
|
|
1795
|
+
stats.innerHTML = "";
|
|
1796
|
+
values.innerHTML = "";
|
|
1797
|
+
clearChart(chart);
|
|
1798
|
+
try {
|
|
1799
|
+
const doe = await ensureDoe();
|
|
1800
|
+
const logs = [];
|
|
1801
|
+
const scopedConsole = {
|
|
1802
|
+
log(...args) {
|
|
1803
|
+
logs.push(args.map((value) => typeof value === "string" ? value : JSON.stringify(value)).join(" "));
|
|
1804
|
+
},
|
|
1805
|
+
};
|
|
1806
|
+
const runnable = editor.value.replace(/^import\s+\{\s*doe\s*\}\s+from\s+"@simulatte\/webgpu\/compute";\n\n?/, "");
|
|
1807
|
+
const fn = new Function("doe", "console", `return (async () => {\n${runnable}\n})();`);
|
|
1808
|
+
await fn(doe, scopedConsole);
|
|
1809
|
+
const parsed = parseConsoleOutput(logs);
|
|
1810
|
+
meta.textContent = "Ran successfully.";
|
|
1811
|
+
text.textContent = parsed.text;
|
|
1812
|
+
renderViz(stats, chart, values, parsed.data);
|
|
1813
|
+
} catch (error) {
|
|
1814
|
+
meta.textContent = "Run failed.";
|
|
1815
|
+
text.textContent = error && error.stack ? error.stack : String(error);
|
|
1816
|
+
}
|
|
1817
|
+
});
|
|
1818
|
+
}
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
async function initRuntimeStatus() {
|
|
1822
|
+
if (!("gpu" in navigator)) {
|
|
1823
|
+
setStatus("status-webgpu", "Unavailable");
|
|
1824
|
+
setStatus("status-adapter", "No WebGPU");
|
|
1825
|
+
setStatus("status-device", "No WebGPU");
|
|
1826
|
+
return;
|
|
1827
|
+
}
|
|
1828
|
+
setStatus("status-webgpu", "Available");
|
|
1829
|
+
try {
|
|
1830
|
+
await ensureDoe();
|
|
1831
|
+
} catch (error) {
|
|
1832
|
+
setStatus("status-adapter", "Failed");
|
|
1833
|
+
setStatus("status-device", error?.message || "Unavailable");
|
|
1834
|
+
}
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
attachSearch();
|
|
1838
|
+
attachExampleControls();
|
|
1839
|
+
initRuntimeStatus();
|
|
1840
|
+
</script>
|
|
1841
|
+
</body>
|
|
1842
|
+
</html>
|