@woosh/meep-engine 3.34.0 → 3.35.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/package.json +1 -1
- package/src/shade/device/timing/profile/GPUProfileSession.d.ts +25 -11
- package/src/shade/device/timing/profile/GPUProfileSession.d.ts.map +1 -1
- package/src/shade/device/timing/profile/GPUProfileSession.js +553 -474
- package/src/shade/playground/avboit/main.d.ts.map +1 -1
- package/src/shade/playground/avboit/main.js +888 -916
- package/src/shade/playground/particle_ecs/README.md +13 -7
- package/src/shade/playground/particle_ecs/bonfire_effects.d.ts +5 -5
- package/src/shade/playground/particle_ecs/bonfire_effects.js +5 -5
- package/src/shade/playground/particle_ecs/effects/smoke.json +1859 -1859
- package/src/shade/playground/particle_ecs/effects/soot.json +1605 -1605
- package/src/shade/playground/particle_system/README.md +1 -2
- package/src/shade/playground/profile_hotkey.d.ts.map +1 -1
- package/src/shade/playground/profile_hotkey.js +4 -40
- package/src/shade/playground/skinned_blas_refit/main.d.ts.map +1 -1
- package/src/shade/playground/skinned_blas_refit/main.js +4 -39
- package/src/shade/playground/volumetrics_froxel/main.d.ts.map +1 -1
- package/src/shade/playground/volumetrics_froxel/main.js +4 -39
- package/src/shade/playground/volumetrics_froxel/measure.d.ts.map +1 -1
- package/src/shade/playground/volumetrics_froxel/measure.js +2 -4
- package/src/shade/renderer/Renderer.d.ts +1 -1
- package/src/shade/renderer/Renderer.d.ts.map +1 -1
- package/src/shade/renderer/Renderer.js +3 -1
- package/src/shade/renderer/light/GPULightCollection.d.ts +9 -1
- package/src/shade/renderer/light/GPULightCollection.d.ts.map +1 -1
- package/src/shade/renderer/light/GPULightCollection.js +41 -0
- package/src/shade/renderer/light/environment/shader_project_environment_sh.d.ts +37 -0
- package/src/shade/renderer/light/environment/shader_project_environment_sh.d.ts.map +1 -0
- package/src/shade/renderer/light/environment/shader_project_environment_sh.js +105 -0
- package/src/shade/renderer/particles/DESIGN.md +65 -8
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts +7 -1
- package/src/shade/renderer/particles/GPUParticleSystem.d.ts.map +1 -1
- package/src/shade/renderer/particles/GPUParticleSystem.js +1247 -1244
- package/src/shade/renderer/particles/graph_particles_avboit.d.ts +7 -1
- package/src/shade/renderer/particles/graph_particles_avboit.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph_particles_avboit.js +9 -0
- package/src/shade/renderer/particles/shaders/chunk_particle_billboard_vertex.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_billboard_vertex.js +4 -2
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +15 -9
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +29 -11
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting_model.d.ts +24 -0
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting_model.d.ts.map +1 -0
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting_model.js +45 -0
- package/src/shade/renderer/particles/shaders/chunk_particle_render_math.d.ts +3 -0
- package/src/shade/renderer/particles/shaders/chunk_particle_render_math.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_render_math.js +38 -0
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_avboit_draw.js +190 -185
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts +8 -0
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/avboit/AVBOITSideChannel.js +5 -0
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts +8 -1
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.d.ts.map +1 -1
- package/src/shade/renderer/rasterize/native/avboit/graph_rasterize_meshes_transparent_avboit.js +9 -0
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts +13 -3
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/GPUViewVolumetrics.js +9 -1
- package/src/shade/renderer/volumetrics/chunk_volumetrics_irradiance_sh.d.ts +39 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_irradiance_sh.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/chunk_volumetrics_irradiance_sh.js +107 -0
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.d.ts +15 -0
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.js +44 -0
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.d.ts +19 -0
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.js +48 -0
- package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.d.ts +29 -0
- package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.js +90 -0
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi_brick4.d.ts +10 -0
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi_brick4.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi_brick4.js +55 -0
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi_ibl.d.ts +10 -0
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi_ibl.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi_ibl.js +47 -0
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi_lpv.d.ts +10 -0
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi_lpv.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi_lpv.js +63 -0
- package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.d.ts +17 -0
- package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.d.ts.map +1 -0
- package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.js +26 -0
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts +8 -1
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/graph_build_volumetrics.js +50 -2
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.d.ts.map +1 -1
- package/src/shade/renderer/volumetrics/shader_volumetrics_build_lighting.js +488 -436
- package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts +1 -1
- package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.d.ts.map +1 -1
- package/src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js +254 -63
|
@@ -32,7 +32,7 @@ export class WGSLJavaScriptCompiler {
|
|
|
32
32
|
compile(ast: Tree): string;
|
|
33
33
|
}
|
|
34
34
|
export type WgslType = {
|
|
35
|
-
kind: 'primitive' | 'vec' | 'mat' | 'array' | 'struct' | 'texture_2d' | 'sampler' | 'atomic' | 'unknown';
|
|
35
|
+
kind: 'primitive' | 'abstract' | 'vec' | 'mat' | 'array' | 'struct' | 'texture_2d' | 'sampler' | 'atomic' | 'unknown';
|
|
36
36
|
name?: string;
|
|
37
37
|
size?: number;
|
|
38
38
|
cols?: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"WGSLJavaScriptCompiler.d.ts","sourceRoot":"","sources":["../../../../../src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"WGSLJavaScriptCompiler.d.ts","sourceRoot":"","sources":["../../../../../src/shade/wgsl/emulator/WGSLJavaScriptCompiler.js"],"names":[],"mappings":"AAuiBA;IAEI;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,oBAFa,MAAM,CAMlB;CACJ;;UAnkBa,WAAW,GAAG,UAAU,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,SAAS;WAC/G,MAAM;WACN,MAAM;WACN,MAAM;WACN,MAAM;aACN,MAAM;cACN,QAAQ;YACR,MAAM"}
|
|
@@ -7,7 +7,7 @@ import LineBuilder from "../../../core/codegen/LineBuilder.js";
|
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
9
|
* @typedef {object} WgslType
|
|
10
|
-
* @property {'primitive' | 'vec' | 'mat' | 'array' | 'struct' | 'texture_2d' | 'sampler' | 'atomic' | 'unknown'} kind
|
|
10
|
+
* @property {'primitive' | 'abstract' | 'vec' | 'mat' | 'array' | 'struct' | 'texture_2d' | 'sampler' | 'atomic' | 'unknown'} kind
|
|
11
11
|
* @property {string} [name]
|
|
12
12
|
* @property {number} [size]
|
|
13
13
|
* @property {number} [cols]
|
|
@@ -19,6 +19,19 @@ import LineBuilder from "../../../core/codegen/LineBuilder.js";
|
|
|
19
19
|
|
|
20
20
|
const T_UNKNOWN = Object.freeze({ kind: 'unknown' });
|
|
21
21
|
const T_I32 = Object.freeze({ kind: 'primitive', name: 'i32' });
|
|
22
|
+
const T_F32 = Object.freeze({ kind: 'primitive', name: 'f32' });
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* WGSL's two abstract numeric types. A bare literal has one (`2` is an AbstractInt, `0.5` an
|
|
26
|
+
* AbstractFloat), arithmetic between abstract operands keeps one, and so does a `const` declared
|
|
27
|
+
* without a type — at any scope. An abstract value becomes concrete where it meets a concrete
|
|
28
|
+
* operand, which gives it that operand's type, or where it is bound, passed, returned or stored.
|
|
29
|
+
* See {@link compileExpression} and {@link concretizeOperands}.
|
|
30
|
+
*
|
|
31
|
+
* An AbstractFloat is a binary64, so a const-expression of them is plain JS arithmetic.
|
|
32
|
+
*/
|
|
33
|
+
const T_ABSTRACT_INT = Object.freeze({ kind: 'abstract', name: 'AbstractInt' });
|
|
34
|
+
const T_ABSTRACT_FLOAT = Object.freeze({ kind: 'abstract', name: 'AbstractFloat' });
|
|
22
35
|
|
|
23
36
|
const VECTOR_ALIASES = {
|
|
24
37
|
vec2i: { size: 2, scalar: 'i32' },
|
|
@@ -328,9 +341,9 @@ function literalToJs(text) {
|
|
|
328
341
|
/**
|
|
329
342
|
* The tracked type of a literal token, as far as its own spelling settles it.
|
|
330
343
|
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
344
|
+
* A suffix names a concrete type. Without one the literal is abstract: a bare `2043` is an
|
|
345
|
+
* AbstractInt and a bare `0.5` an AbstractFloat, and each takes a concrete type from where it is
|
|
346
|
+
* used (see {@link T_ABSTRACT_INT}).
|
|
334
347
|
*
|
|
335
348
|
* The suffix matters because operator selection reads these types. `index / 2043u` is truncating
|
|
336
349
|
* integer division in WGSL and plain division in JavaScript, and the two differ for every pair
|
|
@@ -359,10 +372,10 @@ function literalType(text) {
|
|
|
359
372
|
}
|
|
360
373
|
|
|
361
374
|
if (/[.eE]/.test(t) && !/^0[xX]/.test(t)) {
|
|
362
|
-
return
|
|
375
|
+
return T_ABSTRACT_FLOAT;
|
|
363
376
|
}
|
|
364
377
|
|
|
365
|
-
return
|
|
378
|
+
return T_ABSTRACT_INT;
|
|
366
379
|
}
|
|
367
380
|
|
|
368
381
|
// =========================================================================
|
|
@@ -1025,13 +1038,20 @@ function collectGlobalValue(c, node) {
|
|
|
1025
1038
|
const name = ident.text;
|
|
1026
1039
|
let type = type_node !== null ? parseTypeSpecifier(type_node) : T_UNKNOWN;
|
|
1027
1040
|
|
|
1028
|
-
const
|
|
1041
|
+
// A `const` without a type stays abstract, as it does in a function. An `override` is always
|
|
1042
|
+
// concrete, and an untyped one concretizes as a `let` does.
|
|
1043
|
+
const is_override = /\boverride\b/.test(node.text.slice(0, ident.startIndex - node.startIndex));
|
|
1044
|
+
const keeps_abstract = type_node === null && !is_override;
|
|
1045
|
+
|
|
1046
|
+
const expr = keeps_abstract
|
|
1047
|
+
? compileExpressionAbstract(c, value_node)
|
|
1048
|
+
: compileExpression(c, value_node);
|
|
1029
1049
|
|
|
1030
1050
|
// Infer the type from the RHS when there's no explicit annotation (mirrors the local
|
|
1031
1051
|
// `let`/`var` path). Without this a `const M = mat3x3<f32>(...)` is left T_UNKNOWN, so a
|
|
1032
1052
|
// later `M * v` can't tell M is a matrix and emits a vector multiply → length mismatch.
|
|
1033
1053
|
if (type === T_UNKNOWN || type.kind === 'unknown') {
|
|
1034
|
-
type = expr.type;
|
|
1054
|
+
type = keeps_abstract ? expr.type : concreteDefault(expr.type);
|
|
1035
1055
|
}
|
|
1036
1056
|
|
|
1037
1057
|
c.scope.declare(name, type);
|
|
@@ -1263,6 +1283,7 @@ function emitVariableOrValueStatement(c, node) {
|
|
|
1263
1283
|
let kind = 'let';
|
|
1264
1284
|
let opt_ident = null;
|
|
1265
1285
|
let template = null;
|
|
1286
|
+
let is_wgsl_const = false;
|
|
1266
1287
|
|
|
1267
1288
|
if (var_decl !== null) {
|
|
1268
1289
|
// WGSL `var` is the mutable form → JS `let`.
|
|
@@ -1274,6 +1295,7 @@ function emitVariableOrValueStatement(c, node) {
|
|
|
1274
1295
|
// Tree-sitter exposes the keyword as an unnamed leading token; read it from the source.
|
|
1275
1296
|
kind = 'const';
|
|
1276
1297
|
opt_ident = opt_ident_direct;
|
|
1298
|
+
is_wgsl_const = textBeforeFirstNamed(node).trim() === 'const';
|
|
1277
1299
|
}
|
|
1278
1300
|
|
|
1279
1301
|
const ident = namedChildByType(opt_ident, 'ident');
|
|
@@ -1282,11 +1304,15 @@ function emitVariableOrValueStatement(c, node) {
|
|
|
1282
1304
|
let type = type_spec !== null ? parseTypeSpecifier(type_spec) : T_UNKNOWN;
|
|
1283
1305
|
|
|
1284
1306
|
if (expr_node !== null) {
|
|
1285
|
-
const
|
|
1286
|
-
//
|
|
1287
|
-
//
|
|
1307
|
+
// A `const` without a type keeps an abstract initializer abstract: `const A = 2;` is an
|
|
1308
|
+
// AbstractInt, and `A * x` with an f32 x is an f32 product. A `let` or `var` concretizes
|
|
1309
|
+
// it, so `let x = 3;` is an i32 and `let h = 0.5;` an f32.
|
|
1310
|
+
const keeps_abstract = is_wgsl_const && type_spec === null;
|
|
1311
|
+
const expr = keeps_abstract
|
|
1312
|
+
? compileExpressionAbstract(c, expr_node)
|
|
1313
|
+
: compileExpression(c, expr_node);
|
|
1288
1314
|
if (type === T_UNKNOWN || type.kind === 'unknown') {
|
|
1289
|
-
type =
|
|
1315
|
+
type = keeps_abstract ? expr.type : concreteDefault(expr.type);
|
|
1290
1316
|
}
|
|
1291
1317
|
c.scope.declare(name, type);
|
|
1292
1318
|
c.out.add(`${kind} ${identToJs(name)} = ${expr.code};`);
|
|
@@ -1542,7 +1568,7 @@ function emitForInitInline(c, node) {
|
|
|
1542
1568
|
let type = type_spec !== null ? parseTypeSpecifier(type_spec) : T_UNKNOWN;
|
|
1543
1569
|
if (type.kind === 'unknown' && init !== null) {
|
|
1544
1570
|
// `for (var i = 0; ...)`: a bare integer literal concretises to i32, as in a `let`
|
|
1545
|
-
type = init.
|
|
1571
|
+
type = concreteDefault(init.type);
|
|
1546
1572
|
}
|
|
1547
1573
|
c.scope.declare(name, type);
|
|
1548
1574
|
const value = init !== null ? init.code : defaultValueForType(type);
|
|
@@ -1758,12 +1784,38 @@ function emitFunctionCallStatement(c, node) {
|
|
|
1758
1784
|
// =========================================================================
|
|
1759
1785
|
|
|
1760
1786
|
/**
|
|
1761
|
-
* Compile an expression
|
|
1787
|
+
* Compile an expression whose value is used as a concrete one: bound, passed, returned, stored,
|
|
1788
|
+
* indexed with, branched on.
|
|
1789
|
+
*
|
|
1790
|
+
* An AbstractFloat result becomes an f32 here, which is what a WGSL AbstractFloat converts to
|
|
1791
|
+
* everywhere but an f16 context. Doing it by default, rather than at each place WGSL converts,
|
|
1792
|
+
* means a use this compiler has not thought of rounds like the device rather than staying
|
|
1793
|
+
* more accurate than it. An AbstractInt result is left as it is: its value is the same in every
|
|
1794
|
+
* type it can take, and a `let` still has to see it is abstract to make it an i32.
|
|
1795
|
+
*
|
|
1796
|
+
* {@link compileExpressionAbstract} is the form for the few places an abstract value stays
|
|
1797
|
+
* abstract: the operands of an operator, and the initializer of a `const` without a type.
|
|
1798
|
+
*
|
|
1762
1799
|
* @param {Compiler} c
|
|
1763
1800
|
* @param {Node} node
|
|
1764
|
-
* @returns {{ code: string, type: WgslType }}
|
|
1801
|
+
* @returns {{ code: string, type: WgslType, value?: number }}
|
|
1765
1802
|
*/
|
|
1766
1803
|
function compileExpression(c, node) {
|
|
1804
|
+
const e = compileExpressionAbstract(c, node);
|
|
1805
|
+
return isAbstractFloat(e.type) ? convertAbstract(e, 'f32') : e;
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
/**
|
|
1809
|
+
* Compile an expression node (any wrapper or specific kind), leaving an abstract result abstract.
|
|
1810
|
+
*
|
|
1811
|
+
* `value` is carried alongside a literal, negated or parenthesised, so that converting it to f32
|
|
1812
|
+
* is a constant fold rather than a call at run time.
|
|
1813
|
+
*
|
|
1814
|
+
* @param {Compiler} c
|
|
1815
|
+
* @param {Node} node
|
|
1816
|
+
* @returns {{ code: string, type: WgslType, value?: number }}
|
|
1817
|
+
*/
|
|
1818
|
+
function compileExpressionAbstract(c, node) {
|
|
1767
1819
|
const u = unwrapExpression(node);
|
|
1768
1820
|
|
|
1769
1821
|
// Binary operations
|
|
@@ -1809,15 +1861,15 @@ function compileExpression(c, node) {
|
|
|
1809
1861
|
|
|
1810
1862
|
// Primary subkinds
|
|
1811
1863
|
if (u.type === 'literal' || u.type === 'int_literal' || u.type === 'float_literal' || u.type === 'bool_literal') {
|
|
1812
|
-
// A bare
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
return { code
|
|
1864
|
+
// A bare literal is abstract (see literalType), and takes its concrete type where it is used
|
|
1865
|
+
const code = literalToJs(u.text);
|
|
1866
|
+
const type = literalType(u.text);
|
|
1867
|
+
return isAbstractType(type) ? { code, type, value: Number(code) } : { code, type };
|
|
1816
1868
|
}
|
|
1817
1869
|
if (u.type === 'paren_expression') {
|
|
1818
1870
|
const inner = namedChildByType(u, 'expression');
|
|
1819
|
-
const e =
|
|
1820
|
-
return { code: `(${e.code})`, type: e.type,
|
|
1871
|
+
const e = compileExpressionAbstract(c, inner);
|
|
1872
|
+
return { code: `(${e.code})`, type: e.type, value: e.value };
|
|
1821
1873
|
}
|
|
1822
1874
|
if (u.type === 'call_expression') {
|
|
1823
1875
|
const call_phrase = namedChildByType(u, 'call_phrase');
|
|
@@ -1849,7 +1901,7 @@ function compileExpression(c, node) {
|
|
|
1849
1901
|
function compileBinaryExpression(c, node, op_type, mode) {
|
|
1850
1902
|
const named = node.namedChildren;
|
|
1851
1903
|
if (named.length === 1) {
|
|
1852
|
-
return
|
|
1904
|
+
return compileExpressionAbstract(c, named[0]);
|
|
1853
1905
|
}
|
|
1854
1906
|
|
|
1855
1907
|
let op_text = null;
|
|
@@ -1870,8 +1922,10 @@ function compileBinaryExpression(c, node, op_type, mode) {
|
|
|
1870
1922
|
return { code: node.text, type: T_UNKNOWN };
|
|
1871
1923
|
}
|
|
1872
1924
|
|
|
1873
|
-
const left =
|
|
1874
|
-
|
|
1925
|
+
const [left, right] = concretizeOperands(
|
|
1926
|
+
compileExpressionAbstract(c, left_node),
|
|
1927
|
+
compileExpressionAbstract(c, right_node)
|
|
1928
|
+
);
|
|
1875
1929
|
|
|
1876
1930
|
if (mode === 'arith') {
|
|
1877
1931
|
const helper = ARITHMETIC_OP[op_text];
|
|
@@ -1908,11 +1962,10 @@ function compileBinaryExpression(c, node, op_type, mode) {
|
|
|
1908
1962
|
if (f32_scalar !== undefined && isScalarF32(arith_type)) {
|
|
1909
1963
|
actual_helper = f32_scalar;
|
|
1910
1964
|
}
|
|
1965
|
+
// arithmetic on two AbstractInts is still AbstractInt, so `(1 + 2) / 2` is 1
|
|
1911
1966
|
return {
|
|
1912
1967
|
code: `wgsl.${actual_helper}(${left.code}, ${right.code})`,
|
|
1913
1968
|
type: arith_type,
|
|
1914
|
-
// arithmetic on two AbstractInts is still AbstractInt: `(1 + 2) / 2` is 1
|
|
1915
|
-
abstract_int: left.abstract_int === true && right.abstract_int === true,
|
|
1916
1969
|
};
|
|
1917
1970
|
}
|
|
1918
1971
|
}
|
|
@@ -2009,20 +2062,114 @@ function isIntegerType(t) {
|
|
|
2009
2062
|
return false;
|
|
2010
2063
|
}
|
|
2011
2064
|
|
|
2012
|
-
/** True if
|
|
2013
|
-
function
|
|
2014
|
-
return
|
|
2065
|
+
/** True if `t` is AbstractInt or AbstractFloat. */
|
|
2066
|
+
function isAbstractType(t) {
|
|
2067
|
+
return t !== undefined && t !== null && t.kind === 'abstract';
|
|
2068
|
+
}
|
|
2069
|
+
|
|
2070
|
+
/** True if `t` is AbstractInt. */
|
|
2071
|
+
function isAbstractInt(t) {
|
|
2072
|
+
return isAbstractType(t) && t.name === 'AbstractInt';
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
/** True if `t` is AbstractFloat. */
|
|
2076
|
+
function isAbstractFloat(t) {
|
|
2077
|
+
return isAbstractType(t) && t.name === 'AbstractFloat';
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
/**
|
|
2081
|
+
* The type a declaration that must be concrete gives an initializer of type `t`: an AbstractInt
|
|
2082
|
+
* becomes i32 and an AbstractFloat f32, as in `let x = 3;` and `let h = 0.5;`. Anything else is
|
|
2083
|
+
* already concrete, or untracked.
|
|
2084
|
+
* @param {WgslType} t
|
|
2085
|
+
* @returns {WgslType}
|
|
2086
|
+
*/
|
|
2087
|
+
function concreteDefault(t) {
|
|
2088
|
+
if (isAbstractInt(t)) return T_I32;
|
|
2089
|
+
if (isAbstractFloat(t)) return T_F32;
|
|
2090
|
+
return t;
|
|
2091
|
+
}
|
|
2092
|
+
|
|
2093
|
+
/**
|
|
2094
|
+
* An abstract operand converted to the concrete scalar type it meets.
|
|
2095
|
+
*
|
|
2096
|
+
* Only a float target changes the value. An AbstractFloat becoming an f32 is rounded to one, and
|
|
2097
|
+
* so is an AbstractInt too large for f32 to hold. A literal is folded at compile time, and is
|
|
2098
|
+
* emitted unchanged when f32 holds it already, which most literals in a shader are. An integer
|
|
2099
|
+
* target changes nothing: an AbstractInt has the same value in i32 and u32, and an AbstractFloat
|
|
2100
|
+
* cannot meet an integer in a valid shader.
|
|
2101
|
+
*
|
|
2102
|
+
* @param {{ code: string, type: WgslType, value?: number }} operand
|
|
2103
|
+
* @param {string} scalar the concrete scalar type's name
|
|
2104
|
+
* @returns {{ code: string, type: WgslType }}
|
|
2105
|
+
*/
|
|
2106
|
+
function convertAbstract(operand, scalar) {
|
|
2107
|
+
if (!isAbstractType(operand.type)) return operand;
|
|
2108
|
+
|
|
2109
|
+
const type = scalar === 'f32' ? T_F32 : { kind: 'primitive', name: scalar };
|
|
2110
|
+
|
|
2111
|
+
if (scalar === 'f32') {
|
|
2112
|
+
if (operand.value === undefined) {
|
|
2113
|
+
return { code: `wgsl.f32(${operand.code})`, type };
|
|
2114
|
+
}
|
|
2115
|
+
const rounded = Math.fround(operand.value);
|
|
2116
|
+
return { code: rounded === operand.value ? operand.code : `(${rounded})`, type };
|
|
2117
|
+
}
|
|
2118
|
+
|
|
2119
|
+
if (scalar === 'f16') {
|
|
2120
|
+
return { code: `wgsl.f16(${operand.code})`, type };
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
return { code: operand.code, type };
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
/**
|
|
2127
|
+
* The operands of a binary operator, with an abstract one converted to the type of the concrete one
|
|
2128
|
+
* it meets, as WGSL converts it. `A * x` with an f32 x is an f32 product and `A * x` with a u32 x a
|
|
2129
|
+
* wrapping u32 one, for the same `const A = 2;`. Two abstract operands stay abstract: that is a
|
|
2130
|
+
* const-expression, evaluated at full precision.
|
|
2131
|
+
*
|
|
2132
|
+
* An operand the compiler cannot type is almost always a concrete value it cannot see into, like a
|
|
2133
|
+
* struct field or an array element. An AbstractFloat meeting one is taken to meet an f32, since a
|
|
2134
|
+
* float literal can only meet a float and f16 is rare — the type a float literal used to have here
|
|
2135
|
+
* regardless. An AbstractInt stays as it is, since the other side could be any of four types.
|
|
2136
|
+
*
|
|
2137
|
+
* @param {{ code: string, type: WgslType, value?: number }} left
|
|
2138
|
+
* @param {{ code: string, type: WgslType, value?: number }} right
|
|
2139
|
+
* @returns {Array<{ code: string, type: WgslType, value?: number }>}
|
|
2140
|
+
*/
|
|
2141
|
+
function concretizeOperands(left, right) {
|
|
2142
|
+
const left_abstract = isAbstractType(left.type);
|
|
2143
|
+
const right_abstract = isAbstractType(right.type);
|
|
2144
|
+
|
|
2145
|
+
if (left_abstract === right_abstract) {
|
|
2146
|
+
return [left, right];
|
|
2147
|
+
}
|
|
2148
|
+
|
|
2149
|
+
const abstract = left_abstract ? left : right;
|
|
2150
|
+
const other = left_abstract ? right.type : left.type;
|
|
2151
|
+
|
|
2152
|
+
let scalar = null;
|
|
2153
|
+
if (other && other.kind === 'primitive') scalar = other.name;
|
|
2154
|
+
else if (other && (other.kind === 'vec' || other.kind === 'mat')) scalar = other.scalar;
|
|
2155
|
+
else if (isAbstractFloat(abstract.type)) scalar = 'f32';
|
|
2156
|
+
|
|
2157
|
+
if (scalar === null || scalar === 'bool') {
|
|
2158
|
+
return [left, right];
|
|
2159
|
+
}
|
|
2160
|
+
|
|
2161
|
+
const converted = convertAbstract(abstract, scalar);
|
|
2162
|
+
return left_abstract ? [converted, right] : [left, converted];
|
|
2015
2163
|
}
|
|
2016
2164
|
|
|
2017
2165
|
/**
|
|
2018
2166
|
* True if WGSL evaluates `left / right` as integer division. Both operands integer-typed is the
|
|
2019
|
-
* obvious case
|
|
2020
|
-
*
|
|
2021
|
-
*
|
|
2022
|
-
* `index / (3)` truncate too.
|
|
2167
|
+
* obvious case, and an abstract operand has already taken the other's type by now, so `index / 3`
|
|
2168
|
+
* truncates. Two AbstractInts divide as AbstractInt, so `7 / 2` is 3 even where an f32 is expected.
|
|
2169
|
+
* Negation and parentheses keep a literal abstract: `index / -3` and `index / (3)` truncate too.
|
|
2023
2170
|
*/
|
|
2024
2171
|
function integerDivision(left, right) {
|
|
2025
|
-
const isInt = (operand) => isIntegerType(operand.type) || operand.
|
|
2172
|
+
const isInt = (operand) => isIntegerType(operand.type) || isAbstractInt(operand.type);
|
|
2026
2173
|
return isInt(left) && isInt(right);
|
|
2027
2174
|
}
|
|
2028
2175
|
|
|
@@ -2087,7 +2234,8 @@ function scalarTypeSuffix(scalar) {
|
|
|
2087
2234
|
|
|
2088
2235
|
/**
|
|
2089
2236
|
* Pick the result type of an element-wise arithmetic op given the operand types.
|
|
2090
|
-
* Vec wins over scalar; matrix wins over scalar; scalar
|
|
2237
|
+
* Vec wins over scalar; matrix wins over scalar; a concrete scalar over an abstract one. Two
|
|
2238
|
+
* abstract operands give an abstract result, a float one if either is.
|
|
2091
2239
|
*/
|
|
2092
2240
|
function inferBinaryArithResultType(a, b) {
|
|
2093
2241
|
const isVec = (t) => t && t.kind === 'vec';
|
|
@@ -2100,6 +2248,9 @@ function inferBinaryArithResultType(a, b) {
|
|
|
2100
2248
|
if (isMat(b)) return b;
|
|
2101
2249
|
if (isPrim(a)) return a;
|
|
2102
2250
|
if (isPrim(b)) return b;
|
|
2251
|
+
if (isAbstractType(a) && isAbstractType(b)) {
|
|
2252
|
+
return isAbstractFloat(a) || isAbstractFloat(b) ? T_ABSTRACT_FLOAT : T_ABSTRACT_INT;
|
|
2253
|
+
}
|
|
2103
2254
|
return T_UNKNOWN;
|
|
2104
2255
|
}
|
|
2105
2256
|
|
|
@@ -2119,6 +2270,8 @@ function inferRawBinaryResultType(a, b, op) {
|
|
|
2119
2270
|
if (b && b.kind === 'primitive') return b;
|
|
2120
2271
|
if (a && a.kind === 'vec') return a;
|
|
2121
2272
|
if (b && b.kind === 'vec') return b;
|
|
2273
|
+
// `1 << 8` is still an AbstractInt
|
|
2274
|
+
if (isAbstractInt(a) && isAbstractInt(b)) return T_ABSTRACT_INT;
|
|
2122
2275
|
return T_UNKNOWN;
|
|
2123
2276
|
}
|
|
2124
2277
|
|
|
@@ -2127,12 +2280,18 @@ function compileUnaryExpression(c, node) {
|
|
|
2127
2280
|
const prefix = textBeforeFirstNamed(node).trim();
|
|
2128
2281
|
|
|
2129
2282
|
if (prefix === '') {
|
|
2130
|
-
return
|
|
2283
|
+
return compileExpressionAbstract(c, named[0]);
|
|
2131
2284
|
}
|
|
2132
2285
|
|
|
2133
|
-
const operand =
|
|
2286
|
+
const operand = compileExpressionAbstract(c, named[named.length - 1]);
|
|
2134
2287
|
|
|
2135
|
-
if (prefix === '-')
|
|
2288
|
+
if (prefix === '-') {
|
|
2289
|
+
return {
|
|
2290
|
+
code: `wgsl.neg(${operand.code})`,
|
|
2291
|
+
type: operand.type,
|
|
2292
|
+
value: operand.value === undefined ? undefined : -operand.value,
|
|
2293
|
+
};
|
|
2294
|
+
}
|
|
2136
2295
|
if (prefix === '!') return { code: `!${operand.code}`, type: operand.type };
|
|
2137
2296
|
if (prefix === '~') return { code: `wgsl.not(${operand.code})`, type: operand.type };
|
|
2138
2297
|
if (prefix === '*' || prefix === '&') return { code: operand.code, type: operand.type };
|
|
@@ -2144,8 +2303,8 @@ function compileSingularExpression(c, node) {
|
|
|
2144
2303
|
const named = node.namedChildren;
|
|
2145
2304
|
const head = named[0];
|
|
2146
2305
|
const head_result = head.type === 'primary_expression'
|
|
2147
|
-
?
|
|
2148
|
-
:
|
|
2306
|
+
? compileExpressionAbstract(c, unwrapExpression(head))
|
|
2307
|
+
: compileExpressionAbstract(c, head);
|
|
2149
2308
|
|
|
2150
2309
|
let code = head_result.code;
|
|
2151
2310
|
let type = head_result.type;
|
|
@@ -2158,9 +2317,9 @@ function compileSingularExpression(c, node) {
|
|
|
2158
2317
|
type = next.type;
|
|
2159
2318
|
}
|
|
2160
2319
|
|
|
2161
|
-
// a bare literal reached through a singular expression is still
|
|
2162
|
-
// access specifier on it is not
|
|
2163
|
-
return { code, type,
|
|
2320
|
+
// a bare literal reached through a singular expression is still a literal; anything with an
|
|
2321
|
+
// access specifier on it is not one
|
|
2322
|
+
return { code, type, value: named.length === 1 ? head_result.value : undefined };
|
|
2164
2323
|
}
|
|
2165
2324
|
|
|
2166
2325
|
/**
|
|
@@ -2266,15 +2425,27 @@ function compileCallPhraseTyped(c, node) {
|
|
|
2266
2425
|
const template = namedChildByType(ident_node, 'template_list');
|
|
2267
2426
|
|
|
2268
2427
|
/** @type {Array<{ code: string, type: WgslType }>} */
|
|
2269
|
-
|
|
2428
|
+
let arg_results = [];
|
|
2270
2429
|
if (arg_list !== null) {
|
|
2271
2430
|
const comma_list = namedChildByType(arg_list, 'expression_comma_list');
|
|
2272
2431
|
if (comma_list !== null) {
|
|
2273
2432
|
for (const arg of namedChildrenByType(comma_list, 'expression')) {
|
|
2274
|
-
arg_results.push(
|
|
2433
|
+
arg_results.push(compileExpressionAbstract(c, arg));
|
|
2275
2434
|
}
|
|
2276
2435
|
}
|
|
2277
2436
|
}
|
|
2437
|
+
|
|
2438
|
+
// A call's arguments are concrete values, as in compileExpression, except for an element-wise
|
|
2439
|
+
// builtin all of whose arguments are abstract: WGSL evaluates `sqrt(2.0)` as an AbstractFloat.
|
|
2440
|
+
const const_evaluated = ELEMENT_WISE_BUILTIN.has(callee_name)
|
|
2441
|
+
&& !c.functions.has(callee_name)
|
|
2442
|
+
&& arg_results.length > 0
|
|
2443
|
+
&& arg_results.every(a => isAbstractType(a.type));
|
|
2444
|
+
|
|
2445
|
+
if (!const_evaluated) {
|
|
2446
|
+
arg_results = arg_results.map(a => isAbstractFloat(a.type) ? convertAbstract(a, 'f32') : a);
|
|
2447
|
+
}
|
|
2448
|
+
|
|
2278
2449
|
const args = arg_results.map(a => a.code);
|
|
2279
2450
|
|
|
2280
2451
|
// 1) Vec aliases: vec3f, vec4i, vec2h, etc.
|
|
@@ -2430,26 +2601,47 @@ function compileCallPhraseTyped(c, node) {
|
|
|
2430
2601
|
*/
|
|
2431
2602
|
const INTEGER_TYPED_BUILTIN = new Set(['abs', 'firstLeadingBit', 'firstTrailingBit']);
|
|
2432
2603
|
|
|
2604
|
+
/**
|
|
2605
|
+
* Element-wise builtins: the result has the type of the arguments. Most are vector-preserving
|
|
2606
|
+
* math; subgroupBroadcastFirst is not math, but has the same shape.
|
|
2607
|
+
* @type {Set<string>}
|
|
2608
|
+
*/
|
|
2609
|
+
const ELEMENT_WISE_BUILTIN = new Set([
|
|
2610
|
+
'abs', 'sign', 'floor', 'ceil', 'round', 'trunc', 'fract', 'sqrt', 'inverseSqrt',
|
|
2611
|
+
'exp', 'exp2', 'log', 'log2', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan',
|
|
2612
|
+
'pow', 'atan2', 'min', 'max', 'step', 'clamp', 'saturate', 'mix', 'smoothstep',
|
|
2613
|
+
'normalize', 'reflect', 'fma',
|
|
2614
|
+
'subgroupBroadcastFirst',
|
|
2615
|
+
]);
|
|
2616
|
+
|
|
2617
|
+
/**
|
|
2618
|
+
* The common type of a builtin's same-typed arguments: the first vector or matrix, else the first
|
|
2619
|
+
* concrete scalar — `max(0, x)` with a u32 x is a u32 — else their abstract type if all of them
|
|
2620
|
+
* are abstract, else the first argument's.
|
|
2621
|
+
* @param {Array<{ type: WgslType }>} args
|
|
2622
|
+
* @returns {WgslType}
|
|
2623
|
+
*/
|
|
2624
|
+
function commonArgumentType(args) {
|
|
2625
|
+
for (const a of args) {
|
|
2626
|
+
if (a.type && (a.type.kind === 'vec' || a.type.kind === 'mat')) return a.type;
|
|
2627
|
+
}
|
|
2628
|
+
for (const a of args) {
|
|
2629
|
+
if (a.type && a.type.kind === 'primitive') return a.type;
|
|
2630
|
+
}
|
|
2631
|
+
if (args.length > 0 && args.every(a => isAbstractType(a.type))) {
|
|
2632
|
+
return args.some(a => isAbstractFloat(a.type)) ? T_ABSTRACT_FLOAT : T_ABSTRACT_INT;
|
|
2633
|
+
}
|
|
2634
|
+
if (args[0] && args[0].type) return args[0].type;
|
|
2635
|
+
return T_UNKNOWN;
|
|
2636
|
+
}
|
|
2637
|
+
|
|
2433
2638
|
/**
|
|
2434
2639
|
* Best-effort return type for WGSL builtins commonly used in compute shaders.
|
|
2435
2640
|
* Handles the cases needed for accurate swizzle / member-access codegen downstream.
|
|
2436
2641
|
*/
|
|
2437
2642
|
function inferBuiltinReturnType(name, args) {
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
'abs', 'sign', 'floor', 'ceil', 'round', 'trunc', 'fract', 'sqrt', 'inverseSqrt',
|
|
2441
|
-
'exp', 'exp2', 'log', 'log2', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan',
|
|
2442
|
-
'pow', 'atan2', 'min', 'max', 'step', 'clamp', 'saturate', 'mix', 'smoothstep',
|
|
2443
|
-
'normalize', 'reflect', 'fma',
|
|
2444
|
-
// not math, but the same shape: the result has the type of its one argument
|
|
2445
|
-
'subgroupBroadcastFirst',
|
|
2446
|
-
]);
|
|
2447
|
-
if (ELEMENT_WISE.has(name)) {
|
|
2448
|
-
for (const a of args) {
|
|
2449
|
-
if (a.type && (a.type.kind === 'vec' || a.type.kind === 'mat')) return a.type;
|
|
2450
|
-
}
|
|
2451
|
-
if (args[0] && args[0].type) return args[0].type;
|
|
2452
|
-
return T_UNKNOWN;
|
|
2643
|
+
if (ELEMENT_WISE_BUILTIN.has(name)) {
|
|
2644
|
+
return commonArgumentType(args);
|
|
2453
2645
|
}
|
|
2454
2646
|
if (name === 'length' || name === 'distance' || name === 'dot' || name === 'determinant') {
|
|
2455
2647
|
return { kind: 'primitive', name: 'f32' };
|
|
@@ -2461,9 +2653,8 @@ function inferBuiltinReturnType(name, args) {
|
|
|
2461
2653
|
}
|
|
2462
2654
|
if (name === 'all' || name === 'any') return { kind: 'primitive', name: 'bool' };
|
|
2463
2655
|
if (name === 'select') {
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
return T_UNKNOWN;
|
|
2656
|
+
// the two values; the third argument is the condition
|
|
2657
|
+
return commonArgumentType(args.slice(0, 2));
|
|
2467
2658
|
}
|
|
2468
2659
|
if (name === 'textureLoad' || name === 'textureSampleLevel' || name === 'textureSample' || name === 'textureSampleGrad') {
|
|
2469
2660
|
return { kind: 'vec', size: 4, scalar: 'f32' };
|