@aethervtt/shard-shader 0.0.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/baseline/fragment-kernel.d.ts +15 -0
- package/dist/baseline/fragment-kernel.js +114 -0
- package/dist/baseline/rewrite.d.ts +20 -0
- package/dist/baseline/rewrite.js +436 -0
- package/dist/baseline/wgsl.d.ts +106 -0
- package/dist/baseline/wgsl.js +355 -0
- package/dist/data-marks.d.ts +23 -0
- package/dist/data-marks.js +40 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/layout.d.ts +7 -0
- package/dist/layout.js +100 -64
- package/dist/library.d.ts +21 -1
- package/dist/library.js +132 -36
- package/package.json +9 -5
package/dist/library.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { ShardError } from '@aethervtt/shard-core';
|
|
1
2
|
import type { GpuContext } from '@aethervtt/shard-gpu';
|
|
2
3
|
import type { Platform } from '@aethervtt/shard-platform';
|
|
4
|
+
import type { RetargetedBinding } from './baseline/rewrite.js';
|
|
3
5
|
export interface LinkRequest {
|
|
4
6
|
/** Module with the entry points, e.g. `shard::pbr::main` or `project::water`. */
|
|
5
7
|
root: string;
|
|
@@ -20,6 +22,8 @@ export interface LinkedShader {
|
|
|
20
22
|
code: string;
|
|
21
23
|
/** Maps a character offset in `code` back to the module that produced it. */
|
|
22
24
|
locate(offset: number): SourceLocation | undefined;
|
|
25
|
+
/** Baseline variants (0064): the bindings the rewrite retargeted, for the engine's layouts. */
|
|
26
|
+
bindings?: readonly RetargetedBinding[];
|
|
23
27
|
}
|
|
24
28
|
/** One linked variant, for `ShaderLibrary.preload`. */
|
|
25
29
|
export interface BakedShader {
|
|
@@ -39,13 +43,18 @@ export interface ShaderBake {
|
|
|
39
43
|
* under `shard::`, plugins under their own package, a project's `shaders/` folder under `project::`.
|
|
40
44
|
*/
|
|
41
45
|
export declare class ShaderLibrary {
|
|
46
|
+
/** Module sources as they link: `@data` marks stripped (0064). */
|
|
42
47
|
private readonly sources;
|
|
48
|
+
/** Each module's `@data` declarations, for baseline variants. */
|
|
49
|
+
private readonly data;
|
|
43
50
|
/** Where each module came from, for error messages (a file path, or the module path). */
|
|
44
51
|
private readonly origins;
|
|
45
52
|
private readonly linkCache;
|
|
46
53
|
private readonly variants;
|
|
47
54
|
private readonly listeners;
|
|
48
55
|
private version;
|
|
56
|
+
/** Bumps whenever a module is registered or changes: what renderers compare to retry failures. */
|
|
57
|
+
get revision(): number;
|
|
49
58
|
private readonly preloaded;
|
|
50
59
|
/** Every variant linked or served from the bake, for `bake()`. */
|
|
51
60
|
private readonly used;
|
|
@@ -92,6 +101,12 @@ export declare class ShaderLibrary {
|
|
|
92
101
|
*/
|
|
93
102
|
module(gpu: GpuContext, request: LinkRequest): GPUShaderModule | undefined;
|
|
94
103
|
private rebuild;
|
|
104
|
+
/**
|
|
105
|
+
* Why a variant has no module to give: its code failed to link or compile, and no earlier version
|
|
106
|
+
* compiled either. Undefined while it compiles, once it has a module, and for unknown variants.
|
|
107
|
+
* Renderers draw with a fallback then instead of skipping the draw forever (0061).
|
|
108
|
+
*/
|
|
109
|
+
failure(request: LinkRequest, gpu?: GpuContext): ShardError | undefined;
|
|
95
110
|
/** Waits until every variant requested through `module()` has finished compiling. */
|
|
96
111
|
whenIdle(): Promise<void>;
|
|
97
112
|
/** Module graph and hooks, for agents. */
|
|
@@ -105,7 +120,12 @@ export declare class ShaderLibrary {
|
|
|
105
120
|
};
|
|
106
121
|
defines: string[];
|
|
107
122
|
};
|
|
108
|
-
/**
|
|
123
|
+
/** Every module a variant links: its root's imports, and the overrides'. */
|
|
124
|
+
private closureOf;
|
|
125
|
+
/**
|
|
126
|
+
* Of the source of every module a variant links. A baseline variant's also covers its `@data`
|
|
127
|
+
* declarations and the rewrite's version, which change its code and not its modules' text.
|
|
128
|
+
*/
|
|
109
129
|
private hashOf;
|
|
110
130
|
private doLink;
|
|
111
131
|
}
|
package/dist/library.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { ShardError } from "@aethervtt/shard-core";
|
|
2
|
+
import { BASELINE_REWRITE_VERSION, collectData } from "./data-marks.js";
|
|
2
3
|
import { applyHooks, findHooks } from "./hooks.js";
|
|
3
4
|
let wesl;
|
|
4
5
|
const loadWesl = () => {
|
|
@@ -22,13 +23,20 @@ function lineColumn(text, offset) {
|
|
|
22
23
|
return { line, column: offset - lineStart + 1 };
|
|
23
24
|
}
|
|
24
25
|
class ShaderLibrary {
|
|
26
|
+
/** Module sources as they link: `@data` marks stripped (0064). */
|
|
25
27
|
sources = /* @__PURE__ */ new Map();
|
|
28
|
+
/** Each module's `@data` declarations, for baseline variants. */
|
|
29
|
+
data = /* @__PURE__ */ new Map();
|
|
26
30
|
/** Where each module came from, for error messages (a file path, or the module path). */
|
|
27
31
|
origins = /* @__PURE__ */ new Map();
|
|
28
32
|
linkCache = /* @__PURE__ */ new Map();
|
|
29
33
|
variants = /* @__PURE__ */ new Map();
|
|
30
34
|
listeners = /* @__PURE__ */ new Set();
|
|
31
35
|
version = 0;
|
|
36
|
+
/** Bumps whenever a module is registered or changes: what renderers compare to retry failures. */
|
|
37
|
+
get revision() {
|
|
38
|
+
return this.version;
|
|
39
|
+
}
|
|
32
40
|
preloaded = /* @__PURE__ */ new Map();
|
|
33
41
|
/** Every variant linked or served from the bake, for `bake()`. */
|
|
34
42
|
used = /* @__PURE__ */ new Map();
|
|
@@ -62,8 +70,11 @@ class ShaderLibrary {
|
|
|
62
70
|
hint: "Use lowercase `package::dir::name`, e.g. `project::water`."
|
|
63
71
|
});
|
|
64
72
|
}
|
|
65
|
-
|
|
66
|
-
this.sources.
|
|
73
|
+
const { code, data } = collectData(source, path);
|
|
74
|
+
if (this.sources.get(path) === code && sameData(this.data.get(path), data)) return;
|
|
75
|
+
this.sources.set(path, code);
|
|
76
|
+
if (data.length > 0) this.data.set(path, data);
|
|
77
|
+
else this.data.delete(path);
|
|
67
78
|
this.origins.set(path, origin ?? path);
|
|
68
79
|
this.version++;
|
|
69
80
|
this.linkCache.clear();
|
|
@@ -156,10 +167,13 @@ class ShaderLibrary {
|
|
|
156
167
|
* the error is reported through the GPU context and the old module stays in use.
|
|
157
168
|
*/
|
|
158
169
|
module(gpu, request) {
|
|
170
|
+
if (gpu.tier === "baseline" && !request.defines?.BASELINE) {
|
|
171
|
+
request = { ...request, defines: { ...request.defines, BASELINE: true } };
|
|
172
|
+
}
|
|
159
173
|
const key = variantKey(request);
|
|
160
174
|
let state = this.variants.get(key);
|
|
161
175
|
if (!state) {
|
|
162
|
-
state = { good: void 0, pending: void 0, version: -1, gpu, request };
|
|
176
|
+
state = { good: void 0, pending: void 0, version: -1, gpu, request, failed: void 0 };
|
|
163
177
|
this.variants.set(key, state);
|
|
164
178
|
}
|
|
165
179
|
const s = state;
|
|
@@ -178,28 +192,43 @@ class ShaderLibrary {
|
|
|
178
192
|
if (s.good?.code !== linked.code) {
|
|
179
193
|
s.pending = linked.code;
|
|
180
194
|
const error = await compile(gpu, linked);
|
|
181
|
-
if (error)
|
|
182
|
-
|
|
195
|
+
if (error) {
|
|
196
|
+
s.failed = labelled(error, request.label);
|
|
197
|
+
gpu.reportError(s.failed);
|
|
198
|
+
} else {
|
|
199
|
+
s.failed = void 0;
|
|
183
200
|
s.good = {
|
|
184
201
|
code: linked.code,
|
|
185
202
|
module: moduleFor(gpu, linked),
|
|
186
203
|
linked,
|
|
187
204
|
generation: gpu.generation
|
|
188
205
|
};
|
|
189
|
-
|
|
206
|
+
}
|
|
207
|
+
} else s.failed = void 0;
|
|
190
208
|
s.pending = void 0;
|
|
191
209
|
},
|
|
192
210
|
(err) => {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
request.label
|
|
197
|
-
)
|
|
211
|
+
s.failed = labelled(
|
|
212
|
+
err instanceof ShardError ? err : new ShardError("shader/link", String(err)),
|
|
213
|
+
request.label
|
|
198
214
|
);
|
|
215
|
+
gpu.reportError(s.failed);
|
|
199
216
|
s.pending = void 0;
|
|
200
217
|
}
|
|
201
218
|
);
|
|
202
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Why a variant has no module to give: its code failed to link or compile, and no earlier version
|
|
222
|
+
* compiled either. Undefined while it compiles, once it has a module, and for unknown variants.
|
|
223
|
+
* Renderers draw with a fallback then instead of skipping the draw forever (0061).
|
|
224
|
+
*/
|
|
225
|
+
failure(request, gpu) {
|
|
226
|
+
if (gpu?.tier === "baseline" && !request.defines?.BASELINE) {
|
|
227
|
+
request = { ...request, defines: { ...request.defines, BASELINE: true } };
|
|
228
|
+
}
|
|
229
|
+
const s = this.variants.get(variantKey(request));
|
|
230
|
+
return s && !s.good && s.pending === void 0 ? s.failed : void 0;
|
|
231
|
+
}
|
|
203
232
|
/** Waits until every variant requested through `module()` has finished compiling. */
|
|
204
233
|
async whenIdle() {
|
|
205
234
|
for (let i = 0; i < 200; i++) {
|
|
@@ -221,18 +250,28 @@ class ShaderLibrary {
|
|
|
221
250
|
defines: [...new Set(reachable.flatMap((m) => definesOf(this.sources.get(m) ?? "")))].sort()
|
|
222
251
|
};
|
|
223
252
|
}
|
|
224
|
-
/**
|
|
225
|
-
|
|
226
|
-
const imports = (p) => importsOf(this.sources.get(p) ?? "");
|
|
253
|
+
/** Every module a variant links: its root's imports, and the overrides'. */
|
|
254
|
+
closureOf(request) {
|
|
255
|
+
const imports = (p) => importsOf(this.sources.get(p) ?? "", request.defines ?? {});
|
|
227
256
|
const paths = /* @__PURE__ */ new Set();
|
|
228
257
|
for (const root of [request.root, ...request.overrides ?? []])
|
|
229
258
|
for (const p of closure(root, imports)) paths.add(p);
|
|
259
|
+
return [...paths].sort();
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Of the source of every module a variant links. A baseline variant's also covers its `@data`
|
|
263
|
+
* declarations and the rewrite's version, which change its code and not its modules' text.
|
|
264
|
+
*/
|
|
265
|
+
hashOf(request) {
|
|
266
|
+
const paths = this.closureOf(request);
|
|
230
267
|
let a = 2166136261;
|
|
231
268
|
let b = 2538058380;
|
|
232
|
-
|
|
233
|
-
|
|
269
|
+
const baseline = request.defines?.BASELINE ? `baseline ${BASELINE_REWRITE_VERSION} ${JSON.stringify(paths.map((p) => this.data.get(p) ?? []))}
|
|
270
|
+
` : "";
|
|
271
|
+
for (const path of [...paths, ...baseline ? [""] : []]) {
|
|
272
|
+
const text = path ? `${path}
|
|
234
273
|
${this.sources.get(path) ?? ""}
|
|
235
|
-
|
|
274
|
+
` : baseline;
|
|
236
275
|
for (let i = 0; i < text.length; i++) {
|
|
237
276
|
const c = text.charCodeAt(i);
|
|
238
277
|
a = Math.imul(a ^ c, 16777619);
|
|
@@ -254,7 +293,11 @@ ${this.sources.get(path) ?? ""}
|
|
|
254
293
|
);
|
|
255
294
|
}
|
|
256
295
|
}
|
|
257
|
-
|
|
296
|
+
const defines = request.defines ?? {};
|
|
297
|
+
for (const path of closure(
|
|
298
|
+
request.root,
|
|
299
|
+
(p) => importsOf(this.sources.get(p) ?? "", defines)
|
|
300
|
+
)) {
|
|
258
301
|
if (!this.sources.has(path)) {
|
|
259
302
|
const importer = [...this.sources].find(([, src]) => importsOf(src).includes(path))?.[0];
|
|
260
303
|
throw new ShardError("shader/link-unknown-module", `Unknown shader module "${path}"`, {
|
|
@@ -302,27 +345,39 @@ ${this.sources.get(path) ?? ""}
|
|
|
302
345
|
this.origins.get(request.root)
|
|
303
346
|
);
|
|
304
347
|
}
|
|
305
|
-
const code = linked.dest;
|
|
306
|
-
this.used.set(key, { key, hash, code });
|
|
307
348
|
const origins = this.origins;
|
|
349
|
+
const locate = (offset) => {
|
|
350
|
+
try {
|
|
351
|
+
const pos = linked.sourceMap.destToSrc(offset);
|
|
352
|
+
const srcPath = pos.src.path ?? "";
|
|
353
|
+
const match = /^\.\/([a-z_][a-z0-9_]*)\/(.*)$/.exec(srcPath);
|
|
354
|
+
const module = fileToModule.get(`${root.pkg}|${srcPath}`) ?? (match ? fileToModule.get(`${match[1]}|./${match[2]}`) : void 0) ?? srcPath;
|
|
355
|
+
const { line, column } = lineColumn(pos.src.text, pos.position);
|
|
356
|
+
return { module: origins.get(module) ?? module, line, column };
|
|
357
|
+
} catch {
|
|
358
|
+
return void 0;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
if (!request.defines?.BASELINE) {
|
|
362
|
+
const code = linked.dest;
|
|
363
|
+
this.used.set(key, { key, hash, code });
|
|
364
|
+
return { key, code, locate };
|
|
365
|
+
}
|
|
366
|
+
const { rewriteForBaseline } = await import("./baseline/rewrite.js");
|
|
367
|
+
const data = this.closureOf(request).flatMap((p) => this.data.get(p) ?? []);
|
|
368
|
+
const rewritten = rewriteForBaseline(linked.dest, data, locate, request.label);
|
|
369
|
+
this.used.set(key, { key, hash, code: rewritten.code });
|
|
308
370
|
return {
|
|
309
371
|
key,
|
|
310
|
-
code,
|
|
311
|
-
locate(offset)
|
|
312
|
-
|
|
313
|
-
const pos = linked.sourceMap.destToSrc(offset);
|
|
314
|
-
const srcPath = pos.src.path ?? "";
|
|
315
|
-
const match = /^\.\/([a-z_][a-z0-9_]*)\/(.*)$/.exec(srcPath);
|
|
316
|
-
const module = fileToModule.get(`${root.pkg}|${srcPath}`) ?? (match ? fileToModule.get(`${match[1]}|./${match[2]}`) : void 0) ?? srcPath;
|
|
317
|
-
const { line, column } = lineColumn(pos.src.text, pos.position);
|
|
318
|
-
return { module: origins.get(module) ?? module, line, column };
|
|
319
|
-
} catch {
|
|
320
|
-
return void 0;
|
|
321
|
-
}
|
|
322
|
-
}
|
|
372
|
+
code: rewritten.code,
|
|
373
|
+
locate: (offset) => locate(rewritten.toLinked(offset)),
|
|
374
|
+
bindings: rewritten.bindings
|
|
323
375
|
};
|
|
324
376
|
}
|
|
325
377
|
}
|
|
378
|
+
function sameData(a, b) {
|
|
379
|
+
return JSON.stringify(a ?? []) === JSON.stringify(b);
|
|
380
|
+
}
|
|
326
381
|
const moduleCache = /* @__PURE__ */ new WeakMap();
|
|
327
382
|
function moduleFor(gpu, linked) {
|
|
328
383
|
let cache = moduleCache.get(gpu);
|
|
@@ -372,10 +427,13 @@ function variantKey(request) {
|
|
|
372
427
|
const defines = Object.entries(request.defines ?? {}).filter(([, on]) => on).map(([name]) => name).sort();
|
|
373
428
|
return `${request.root}|${defines.join(",")}|${(request.overrides ?? []).join(",")}`;
|
|
374
429
|
}
|
|
375
|
-
function importsOf(source) {
|
|
430
|
+
function importsOf(source, defines) {
|
|
376
431
|
const out = [];
|
|
377
|
-
for (const m of source.matchAll(
|
|
378
|
-
|
|
432
|
+
for (const m of source.matchAll(
|
|
433
|
+
/^\s*((?:@\w+\s*\((?:[^()]|\([^()]*\))*\)\s*)*)import\s+([^;]+);/gm
|
|
434
|
+
)) {
|
|
435
|
+
if (defines && m[1] && !attributesHold(m[1], defines)) continue;
|
|
436
|
+
const spec = m[2].replace(/\s+/g, "");
|
|
379
437
|
const braced = /^(.*?)::\{(.*)\}$/.exec(spec);
|
|
380
438
|
if (braced) {
|
|
381
439
|
for (const item of braced[2].split(",")) {
|
|
@@ -389,6 +447,44 @@ function importsOf(source) {
|
|
|
389
447
|
}
|
|
390
448
|
return [...new Set(out.filter((p) => p.includes("::") && !p.startsWith("constants")))];
|
|
391
449
|
}
|
|
450
|
+
function attributesHold(attributes, defines) {
|
|
451
|
+
for (const m of attributes.matchAll(/@if\s*\(((?:[^()]|\([^()]*\))*)\)/g))
|
|
452
|
+
if (!evaluateCondition(m[1], defines)) return false;
|
|
453
|
+
return true;
|
|
454
|
+
}
|
|
455
|
+
function evaluateCondition(expression, defines) {
|
|
456
|
+
const tokens = expression.match(/[A-Za-z_]\w*|&&|\|\||[!()]/g) ?? [];
|
|
457
|
+
let i = 0;
|
|
458
|
+
const or = () => {
|
|
459
|
+
let v = and();
|
|
460
|
+
while (tokens[i] === "||") {
|
|
461
|
+
i++;
|
|
462
|
+
v = and() || v;
|
|
463
|
+
}
|
|
464
|
+
return v;
|
|
465
|
+
};
|
|
466
|
+
const and = () => {
|
|
467
|
+
let v = unary();
|
|
468
|
+
while (tokens[i] === "&&") {
|
|
469
|
+
i++;
|
|
470
|
+
v = unary() && v;
|
|
471
|
+
}
|
|
472
|
+
return v;
|
|
473
|
+
};
|
|
474
|
+
const unary = () => {
|
|
475
|
+
const t = tokens[i++];
|
|
476
|
+
if (t === "!") return !unary();
|
|
477
|
+
if (t === "(") {
|
|
478
|
+
const v = or();
|
|
479
|
+
i++;
|
|
480
|
+
return v;
|
|
481
|
+
}
|
|
482
|
+
if (t === "true") return true;
|
|
483
|
+
if (t === "false" || t === void 0) return false;
|
|
484
|
+
return defines[t] === true;
|
|
485
|
+
};
|
|
486
|
+
return or();
|
|
487
|
+
}
|
|
392
488
|
function definesOf(source) {
|
|
393
489
|
const out = [];
|
|
394
490
|
for (const m of source.matchAll(/@(?:if|elif)\s*\(([^)]*)\)/g)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aethervtt/shard-shader",
|
|
3
|
-
"version": "0.0
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
".": {
|
|
9
9
|
"types": "./dist/index.d.ts",
|
|
10
10
|
"default": "./dist/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./fragment-kernel": {
|
|
13
|
+
"types": "./dist/baseline/fragment-kernel.d.ts",
|
|
14
|
+
"default": "./dist/baseline/fragment-kernel.js"
|
|
11
15
|
}
|
|
12
16
|
},
|
|
13
17
|
"files": [
|
|
@@ -16,9 +20,9 @@
|
|
|
16
20
|
"README.md"
|
|
17
21
|
],
|
|
18
22
|
"dependencies": {
|
|
19
|
-
"@aethervtt/shard-core": "0.0
|
|
20
|
-
"@aethervtt/shard-gpu": "0.0
|
|
21
|
-
"@aethervtt/shard-platform": "0.0
|
|
23
|
+
"@aethervtt/shard-core": "0.2.0",
|
|
24
|
+
"@aethervtt/shard-gpu": "0.2.0",
|
|
25
|
+
"@aethervtt/shard-platform": "0.2.0",
|
|
22
26
|
"wesl": "0.7.31"
|
|
23
27
|
},
|
|
24
28
|
"repository": {
|
|
@@ -31,6 +35,6 @@
|
|
|
31
35
|
"provenance": true
|
|
32
36
|
},
|
|
33
37
|
"shard": {
|
|
34
|
-
"buildSha": "
|
|
38
|
+
"buildSha": "32c22a3944ed23f5d1baa90bf7cc9fae724f5fd8"
|
|
35
39
|
}
|
|
36
40
|
}
|