typegpu 0.12.5 → 0.12.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/common/index.d.ts +1 -0
  2. package/common/index.js +1 -0
  3. package/common/writeChannels.d.ts +31 -0
  4. package/common/writeChannels.js +32 -0
  5. package/common/writeSoA.js +16 -0
  6. package/core/buffer/buffer.js +47 -11
  7. package/core/commandEncoder/attachments.js +5 -2
  8. package/core/commandEncoder/computePass.d.ts +13 -0
  9. package/core/commandEncoder/computePass.js +5 -2
  10. package/core/commandEncoder/renderPass.d.ts +18 -4
  11. package/core/commandEncoder/renderPass.js +6 -2
  12. package/core/declare/tgpuDeclare.d.ts +11 -0
  13. package/core/declare/tgpuDeclare.js +1 -1
  14. package/core/function/autoIO.js +4 -2
  15. package/core/function/ioSchema.d.ts +7 -2
  16. package/core/function/ioSchema.js +41 -15
  17. package/core/function/tgpuFragmentFn.d.ts +1 -1
  18. package/core/function/tgpuFragmentFn.js +2 -1
  19. package/core/function/tgpuVertexFn.js +2 -1
  20. package/core/immediate/immediateVar.d.ts +52 -0
  21. package/core/immediate/immediateVar.js +149 -0
  22. package/core/pipeline/computePipeline.d.ts +17 -0
  23. package/core/pipeline/computePipeline.js +23 -9
  24. package/core/pipeline/connectAttachmentToShader.d.ts +2 -2
  25. package/core/pipeline/connectAttachmentToShader.js +16 -21
  26. package/core/pipeline/connectTargetsToShader.d.ts +5 -1
  27. package/core/pipeline/connectTargetsToShader.js +23 -18
  28. package/core/pipeline/drawState.d.ts +8 -4
  29. package/core/pipeline/drawState.js +41 -51
  30. package/core/pipeline/priors.js +1 -0
  31. package/core/pipeline/renderPipeline.d.ts +22 -4
  32. package/core/pipeline/renderPipeline.js +25 -13
  33. package/core/resolve/namespace.js +2 -2
  34. package/core/root/init.js +3 -0
  35. package/core/root/rootTypes.d.ts +17 -2
  36. package/core/slot/accessor.d.ts +1 -1
  37. package/core/slot/accessor.js +72 -5
  38. package/core/slot/slotTypes.d.ts +2 -1
  39. package/core/texture/texture.d.ts +20 -14
  40. package/core/texture/texture.js +78 -69
  41. package/core/texture/textureUtils.d.ts +3 -6
  42. package/core/texture/textureUtils.js +142 -165
  43. package/core/texture/textureWrite.d.ts +61 -0
  44. package/core/texture/textureWrite.js +59 -0
  45. package/data/autoStruct.d.ts +1 -1
  46. package/data/autoStruct.js +8 -4
  47. package/data/dataIO.d.ts +2 -2
  48. package/data/deepEqual.js +19 -9
  49. package/data/matrix.js +10 -3
  50. package/data/partialIO.d.ts +8 -1
  51. package/data/partialIO.js +36 -19
  52. package/data/ref.js +2 -2
  53. package/data/snippet.d.ts +11 -5
  54. package/data/snippet.js +9 -4
  55. package/data/wgslTypes.d.ts +4 -1
  56. package/errors.d.ts +7 -0
  57. package/errors.js +15 -0
  58. package/indexNamedExports.d.ts +5 -2
  59. package/indexNamedExports.js +2 -1
  60. package/internal.d.ts +2 -1
  61. package/internal.js +1 -1
  62. package/nameUtils.d.ts +7 -0
  63. package/nameUtils.js +60 -0
  64. package/package.json +2 -2
  65. package/resolutionCtx.d.ts +5 -0
  66. package/resolutionCtx.js +11 -0
  67. package/serial/restore.d.ts +3 -1
  68. package/serial/restore.js +2 -0
  69. package/shared/meta.js +1 -1
  70. package/shared/symbols.js +1 -1
  71. package/shared/tseynit.d.ts +1 -0
  72. package/shared/tseynit.js +80 -44
  73. package/std/atomic.d.ts +13 -0
  74. package/std/atomic.js +63 -29
  75. package/std/boolean.js +9 -10
  76. package/std/derivative.js +14 -9
  77. package/std/index.d.ts +4 -4
  78. package/std/index.js +5 -5
  79. package/std/numeric.d.ts +5 -1
  80. package/std/numeric.js +3 -3
  81. package/std/operators.js +9 -4
  82. package/std/packing.d.ts +61 -1
  83. package/std/packing.js +214 -25
  84. package/std/subgroup.d.ts +4 -0
  85. package/std/subgroup.js +34 -0
  86. package/std/texture.d.ts +15 -2
  87. package/std/texture.js +48 -0
  88. package/tgpuUnstable.d.ts +1 -0
  89. package/tgpuUnstable.js +1 -0
  90. package/tgsl/accessIndex.js +19 -23
  91. package/tgsl/conversion.d.ts +2 -0
  92. package/tgsl/conversion.js +3 -0
  93. package/tgsl/forOfUtils.js +6 -6
  94. package/tgsl/generationHelpers.js +2 -2
  95. package/tgsl/shaderGenerator.d.ts +1 -1
  96. package/tgsl/wgslGenerator.d.ts +5 -0
  97. package/tgsl/wgslGenerator.js +288 -58
  98. package/types.d.ts +9 -1
  99. package/types.js +5 -0
package/nameUtils.js CHANGED
@@ -178,6 +178,66 @@ export const bannedTokens = new Set([
178
178
  'uniform',
179
179
  'storage',
180
180
  ]);
181
+ /**
182
+ * Predeclared enumerants that appear as template parameters, e.g. `var<storage, read_write>` or
183
+ * `texture_storage_2d<rgba8unorm, write>`. A module-scope or local declaration with one of these names
184
+ * would shadow the enumerant, so a template using it would refer to the declaration instead.
185
+ * Unlike `bannedTokens`, these remain valid as struct member names, which never shadow anything.
186
+ */
187
+ export const templateEnumerants = new Set([
188
+ // address spaces
189
+ 'function',
190
+ 'private',
191
+ 'workgroup',
192
+ 'uniform',
193
+ 'storage',
194
+ 'immediate',
195
+ // access modes
196
+ 'read',
197
+ 'write',
198
+ 'read_write',
199
+ // texel formats
200
+ 'rgba8unorm',
201
+ 'rgba8snorm',
202
+ 'rgba8uint',
203
+ 'rgba8sint',
204
+ 'rgba16unorm',
205
+ 'rgba16snorm',
206
+ 'rgba16uint',
207
+ 'rgba16sint',
208
+ 'rgba16float',
209
+ 'rg8unorm',
210
+ 'rg8snorm',
211
+ 'rg8uint',
212
+ 'rg8sint',
213
+ 'rg16unorm',
214
+ 'rg16snorm',
215
+ 'rg16uint',
216
+ 'rg16sint',
217
+ 'rg16float',
218
+ 'r8unorm',
219
+ 'r8snorm',
220
+ 'r8uint',
221
+ 'r8sint',
222
+ 'r16unorm',
223
+ 'r16snorm',
224
+ 'r16uint',
225
+ 'r16sint',
226
+ 'r16float',
227
+ 'r32uint',
228
+ 'r32sint',
229
+ 'r32float',
230
+ 'rg32uint',
231
+ 'rg32sint',
232
+ 'rg32float',
233
+ 'rgba32uint',
234
+ 'rgba32sint',
235
+ 'rgba32float',
236
+ 'bgra8unorm',
237
+ 'rgb10a2unorm',
238
+ 'rgb10a2uint',
239
+ 'rg11b10ufloat',
240
+ ]);
181
241
  export const builtins = new Set([
182
242
  // constructors
183
243
  'array',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typegpu",
3
- "version": "0.12.5",
3
+ "version": "0.12.7",
4
4
  "description": "A thin layer between JS and WebGPU/WGSL that improves development experience and allows for faster iteration.",
5
5
  "keywords": [
6
6
  "compute",
@@ -50,7 +50,7 @@
50
50
  "dependencies": {
51
51
  "tsover-runtime": "^0.0.7",
52
52
  "typed-binary": "^4.3.3",
53
- "tinyest": "~0.3.3"
53
+ "tinyest": "~0.3.4"
54
54
  },
55
55
  "engines": {
56
56
  "node": ">=12.20.0"
@@ -1,3 +1,4 @@
1
+ import type { TgpuImmediateVar } from './core/immediate/immediateVar.ts';
1
2
  import type { Namespace } from './core/resolve/namespace.ts';
2
3
  import type { Configurable, ExperimentalTgpuRoot } from './core/root/rootTypes.ts';
3
4
  import { type Eventual, type SlotValuePair, type TgpuLazy, type TgpuSlot } from './core/slot/slotTypes.ts';
@@ -84,6 +85,8 @@ export declare class ResolutionCtxImpl implements ResolutionCtx {
84
85
  readonly enableExtensions: WgslEnableExtension[] | undefined;
85
86
  expectedType: BaseData | undefined;
86
87
  constructor(opts: ResolutionCtxImplOptions);
88
+ registerImmediate(immediate: TgpuImmediateVar): void;
89
+ get usedImmediate(): TgpuImmediateVar | undefined;
87
90
  isIdentifierBanned(name: string): boolean;
88
91
  isIdentifierTaken(name: string, scope: 'global' | 'block'): boolean;
89
92
  makeUniqueIdentifier(primer: string | undefined, scope: 'global' | 'block'): string;
@@ -158,6 +161,7 @@ export interface ResolvedDeclaration {
158
161
  * @param usedBindGroupLayouts - List of used `tgpu.bindGroupLayout`s.
159
162
  * @param catchall - Automatically constructed bind group for buffer usages and buffer bindings, preceded by its index.
160
163
  * @param logResources - Buffers and information about used console.logs needed to decode the raw data.
164
+ * @param usedImmediate - The immediate variable used by the shader, if any.
161
165
  */
162
166
  export interface ResolutionResult {
163
167
  code: string;
@@ -165,6 +169,7 @@ export interface ResolutionResult {
165
169
  usedBindGroupLayouts: TgpuBindGroupLayout[];
166
170
  catchall: [number, TgpuBindGroup] | undefined;
167
171
  logResources: LogResources | undefined;
172
+ usedImmediate: TgpuImmediateVar | undefined;
168
173
  }
169
174
  export declare function resolve(item: Wgsl, options: ResolutionCtxImplOptions): ResolutionResult;
170
175
  export {};
package/resolutionCtx.js CHANGED
@@ -334,6 +334,7 @@ export class ResolutionCtxImpl {
334
334
  // --
335
335
  enableExtensions;
336
336
  expectedType;
337
+ #usedImmediate;
337
338
  /**
338
339
  * A counter used to generate unique identifiers for globally-scoped definitions in the 'random' strategy.
339
340
  */
@@ -345,6 +346,15 @@ export class ResolutionCtxImpl {
345
346
  this.gen = opts.shaderGenerator ?? new WgslGenerator();
346
347
  this.gen.initGenerator(this);
347
348
  }
349
+ registerImmediate(immediate) {
350
+ if (this.#usedImmediate !== undefined && this.#usedImmediate !== immediate) {
351
+ throw new Error(`Cannot use both immediate variables '${getName(this.#usedImmediate) ?? '<unnamed>'}' and '${getName(immediate) ?? '<unnamed>'}' in a single shader. At most one immediate variable is allowed per resolution.`);
352
+ }
353
+ this.#usedImmediate = immediate;
354
+ }
355
+ get usedImmediate() {
356
+ return this.#usedImmediate;
357
+ }
348
358
  isIdentifierBanned(name) {
349
359
  return bannedTokens.has(name);
350
360
  }
@@ -924,5 +934,6 @@ export function resolve(item, options) {
924
934
  usedBindGroupLayouts,
925
935
  catchall,
926
936
  logResources: ctx.logResources,
937
+ usedImmediate: ctx.usedImmediate,
927
938
  };
928
939
  }
@@ -2,6 +2,7 @@ import { type TgpuBuffer, type TgpuBufferSoul } from '../core/buffer/buffer.ts';
2
2
  import type { BaseData } from '../data/wgslTypes.ts';
3
3
  import { TgpuBufferBindingImpl, type TgpuBufferBindingSoul } from '../core/buffer/bufferBinding.ts';
4
4
  import { type TgpuConstSoul } from '../core/constant/tgpuConstant.ts';
5
+ import { type ImmediateData, type TgpuImmediateVarSoul } from '../core/immediate/immediateVar.ts';
5
6
  import { type TgpuComputePipelineSoul } from '../core/pipeline/computePipeline.ts';
6
7
  import { type TgpuRenderPipelineSoul } from '../core/pipeline/renderPipeline.ts';
7
8
  import type { TgpuQuerySetSoul } from '../core/querySet/querySet.ts';
@@ -17,7 +18,7 @@ import { type TgpuBindGroupLayoutSoul, type TgpuBindGroupSoul } from '../tgpuBin
17
18
  import type { TgpuSoul } from '../shared/soul.ts';
18
19
  import type { RestoreContext } from './types.ts';
19
20
  /** Every soul that can cross a device boundary, discriminated by `type` */
20
- export type TgpuResourceSoul = TgpuBufferSoul | TgpuBufferBindingSoul | TgpuTextureSoul | TgpuSamplerSoul | TgpuQuerySetSoul | TgpuBindGroupLayoutSoul | TgpuBindGroupSoul | TgpuVertexLayoutSoul | TgpuConstSoul | TgpuSlotSoul | TgpuAccessorSoul | TgpuComputePipelineSoul | TgpuRenderPipelineSoul | TgpuGuardedComputePipelineSoul | TgpuRootSoul;
21
+ export type TgpuResourceSoul = TgpuBufferSoul | TgpuBufferBindingSoul | TgpuTextureSoul | TgpuSamplerSoul | TgpuQuerySetSoul | TgpuBindGroupLayoutSoul | TgpuBindGroupSoul | TgpuVertexLayoutSoul | TgpuConstSoul | TgpuImmediateVarSoul | TgpuSlotSoul | TgpuAccessorSoul | TgpuComputePipelineSoul | TgpuRenderPipelineSoul | TgpuGuardedComputePipelineSoul | TgpuRootSoul;
21
22
  type Restorer<TSoul extends TgpuSoul> = (soul: TSoul, ctx: RestoreContext) => unknown;
22
23
  declare function restoreBufferBinding(soul: TgpuBufferBindingSoul): TgpuBufferBindingImpl<"uniform" | "mutable" | "readonly", BaseData>;
23
24
  /**
@@ -37,6 +38,7 @@ export declare const soulRestorers: {
37
38
  'bind-group': Restorer<TgpuBindGroupSoul>;
38
39
  'vertex-layout': (soul: TgpuVertexLayoutSoul) => import("../indexNamedExports.ts").TgpuVertexLayout<import("../data/wgslTypes.ts").WgslArray<AnyWgslData>> | import("../indexNamedExports.ts").TgpuVertexLayout<import("../data/dataTypes.ts").Disarray<AnyData>>;
39
40
  const: (soul: TgpuConstSoul) => import("../indexNamedExports.ts").TgpuConst<AnyData>;
41
+ 'immediate-var': (soul: TgpuImmediateVarSoul<ImmediateData>) => import("../indexNamedExports.ts").TgpuImmediateVar<ImmediateData>;
40
42
  slot: (soul: TgpuSlotSoul) => import("../indexNamedExports.ts").TgpuSlot<unknown>;
41
43
  accessor: (soul: TgpuAccessorSoul) => import("../indexNamedExports.ts").TgpuAccessor<AnyData>;
42
44
  'mutable-accessor': (soul: TgpuAccessorSoul) => TgpuMutableAccessor<AnyData>;
package/serial/restore.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { INTERNAL_applyBufferUsages, } from "../core/buffer/buffer.js";
2
2
  import { TgpuBufferBindingImpl } from "../core/buffer/bufferBinding.js";
3
3
  import { constant } from "../core/constant/tgpuConstant.js";
4
+ import { immediateVar, } from "../core/immediate/immediateVar.js";
4
5
  import { INTERNAL_restoreComputePipeline, } from "../core/pipeline/computePipeline.js";
5
6
  import { INTERNAL_restoreRenderPipeline, } from "../core/pipeline/renderPipeline.js";
6
7
  import { INTERNAL_restoreGuardedComputePipeline, INTERNAL_restoreRoot } from "../core/root/init.js";
@@ -57,6 +58,7 @@ export const soulRestorers = {
57
58
  throw new Error('TypeGPU vertex layout payload could not be reconstructed.');
58
59
  },
59
60
  const: (soul) => constant(soul.dataType, soul.value),
61
+ 'immediate-var': (soul) => immediateVar(soul.dataType, soul.defaultValue),
60
62
  slot: (soul) => slot(soul.defaultValue),
61
63
  accessor: (soul) => accessor(soul.schema, soul.defaultValue),
62
64
  'mutable-accessor': (soul) => mutableAccessor(soul.schema, soul.defaultValue),
package/shared/meta.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "0.12.5";
1
+ const version = "0.12.7";
2
2
  import { DEV, TEST } from "./env.js";
3
3
  import { $getNameForward, $soul, isMarkedInternal } from "./symbols.js";
4
4
  import { normalizeMetadata } from "./normalizeMetadata.js";
package/shared/symbols.js CHANGED
@@ -1,4 +1,4 @@
1
- const version = "0.12.5";
1
+ const version = "0.12.7";
2
2
  export const $internal = Symbol(`typegpu:${version}:$internal`);
3
3
  /** A plain record of all definitional state of a resource, surviving transfer between runtimes */
4
4
  export const $soul = Symbol(`typegpu:${version}:$soul`);
@@ -1,2 +1,3 @@
1
1
  import * as tinyest from 'tinyest';
2
2
  export declare function stringifyNode(node: tinyest.AnyNode): string;
3
+ export declare function stringifyObjectProperty([key, value, computed]: tinyest.ObjectProperty): string;
package/shared/tseynit.js CHANGED
@@ -2,77 +2,106 @@ import * as tinyest from 'tinyest';
2
2
  const { NodeTypeCatalog: NODE } = tinyest;
3
3
  export function stringifyNode(node) {
4
4
  if (isExpression(node)) {
5
- return stringifyExpression(node, '');
5
+ return stringifyExpression(node);
6
6
  }
7
7
  return stringifyStatement(node, '');
8
8
  }
9
- function stringifyStatement(node, ident) {
9
+ function stringifyStatement(node, indent) {
10
10
  if (isExpression(node)) {
11
- return `${ident}${stringifyExpression(node, ident)};`;
11
+ return `${indent}${stringifyExpression(node)};`;
12
12
  }
13
13
  if (node[0] === NODE.block) {
14
- const statements = node[1].map((n) => stringifyStatement(n, ident + ' '));
15
- return `{\n${statements.join('\n')}\n${ident}}`;
14
+ const statements = node[1].map((n) => stringifyStatement(n, indent + ' '));
15
+ return `{\n${statements.join('\n')}\n${indent}}`;
16
16
  }
17
17
  if (node[0] === NODE.return) {
18
- const expr = node[1] === undefined ? '' : ` ${stringifyExpression(node[1], '')}`;
19
- return `${ident}return${expr};`;
18
+ const expr = node[1] === undefined ? '' : ` ${stringifyExpression(node[1])}`;
19
+ return `${indent}return${expr};`;
20
20
  }
21
21
  if (node[0] === NODE.if) {
22
- const cond = stringifyExpression(node[1], ident);
23
- const then = stringifyStatement(node[2], ident);
24
- const base = `${ident}if (${cond}) ${then}`;
22
+ const cond = stringifyExpression(node[1]);
23
+ const then = stringifyStatement(node[2], indent);
24
+ const base = `${indent}if (${cond}) ${then}`;
25
25
  if (node[3] !== undefined) {
26
- return `${base} else ${stringifyStatement(node[3], ident)}`;
26
+ return `${base} else ${stringifyStatement(node[3], indent)}`;
27
27
  }
28
28
  return base;
29
29
  }
30
30
  if (node[0] === NODE.let) {
31
+ const decl = `${indent}let ${stringifyExpression(node[1])}`;
31
32
  if (node[2] !== undefined) {
32
- return `${ident}let ${node[1]} = ${stringifyExpression(node[2], ident)};`;
33
+ return `${decl} = ${stringifyExpression(node[2])};`;
33
34
  }
34
- return `${ident}let ${node[1]};`;
35
+ return `${decl};`;
35
36
  }
36
37
  if (node[0] === NODE.const) {
38
+ const decl = `${indent}const ${stringifyExpression(node[1])}`;
37
39
  if (node[2] !== undefined) {
38
- return `${ident}const ${node[1]} = ${stringifyExpression(node[2], ident)};`;
40
+ return `${decl} = ${stringifyExpression(node[2])};`;
39
41
  }
40
- return `${ident}const ${node[1]};`;
42
+ return `${decl};`;
41
43
  }
42
44
  if (node[0] === NODE.for) {
43
45
  const init = node[1] ? stringifyStatement(node[1], '') : ';';
44
- const cond = node[2] ? stringifyExpression(node[2], ident) : '';
46
+ const cond = node[2] ? stringifyExpression(node[2]) : '';
45
47
  const update = node[3] ? stringifyStatement(node[3], '') : '';
46
- const body = stringifyStatement(node[4], ident);
47
- return `${ident}for (${init} ${cond}; ${update.slice(0, -1) /* trim the ';' */}) ${body}`;
48
+ const body = stringifyStatement(node[4], indent);
49
+ return `${indent}for (${init} ${cond}; ${update.slice(0, -1) /* trim the ';' */}) ${body}`;
48
50
  }
49
51
  if (node[0] === NODE.while) {
50
- const cond = stringifyExpression(node[1], ident);
51
- const body = stringifyStatement(node[2], ident);
52
- return `${ident}while (${cond}) ${body}`;
52
+ const cond = stringifyExpression(node[1]);
53
+ const body = stringifyStatement(node[2], indent);
54
+ return `${indent}while (${cond}) ${body}`;
53
55
  }
54
56
  if (node[0] === NODE.continue) {
55
- return `${ident}continue;`;
57
+ return `${indent}continue;`;
56
58
  }
57
59
  if (node[0] === NODE.break) {
58
- return `${ident}break;`;
60
+ return `${indent}break;`;
59
61
  }
60
62
  if (node[0] === NODE.forOf) {
61
63
  const leftKind = node[1][0] === NODE.const ? 'const' : 'let';
62
- const leftName = node[1][1];
63
- const right = stringifyExpression(node[2], ident);
64
- const body = stringifyStatement(node[3], ident);
65
- return `${ident}for (${leftKind} ${leftName} of ${right}) ${body}`;
64
+ const leftName = stringifyExpression(node[1][1]);
65
+ const right = stringifyExpression(node[2]);
66
+ const body = stringifyStatement(node[3], indent);
67
+ return `${indent}for (${leftKind} ${leftName} of ${right}) ${body}`;
68
+ }
69
+ if (node[0] === NODE.switch) {
70
+ const discriminant = stringifyExpression(node[1]);
71
+ const cases = node[2].map((c) => {
72
+ const test = c[0] === null ? 'default' : `case ${stringifyExpression(c[0])}`;
73
+ if (c[1].length === 0) {
74
+ return `${test}:`;
75
+ }
76
+ const consequents = c[1].map((s) => stringifyStatement(s, indent + ' '));
77
+ return `${test}:\n${consequents.join('\n')}`;
78
+ });
79
+ return `${indent}switch (${discriminant}) {\n${cases.map((c) => `${indent + ' '}${c}`).join('\n')}\n${indent}}`;
66
80
  }
67
81
  assertExhaustive(node);
68
82
  }
69
- function stringifyExpression(node, ident) {
83
+ const JS_IDENTIFIER = /^[$_\p{ID_Start}][$_\u{200c}\u{200d}\p{ID_Continue}]*$/u;
84
+ function stringifyObjectPropertyKey(key) {
85
+ return JS_IDENTIFIER.test(key) ? key : JSON.stringify(key);
86
+ }
87
+ export function stringifyObjectProperty([key, value, computed]) {
88
+ const keyStr = computed ? `[${stringifyExpression(key)}]` : stringifyObjectPropertyKey(key);
89
+ const valueStr = stringifyExpression(value);
90
+ return `${keyStr}: ${valueStr}`;
91
+ }
92
+ function stringifyExpression(node) {
70
93
  if (typeof node === 'string') {
71
94
  return node;
72
95
  }
73
96
  if (typeof node === 'boolean') {
74
97
  return `${node}`;
75
98
  }
99
+ if (node[0] === NODE.identifier) {
100
+ return node[1];
101
+ }
102
+ if (node[0] === NODE.booleanLiteral) {
103
+ return `${node[1]}`;
104
+ }
76
105
  if (node[0] === NODE.numericLiteral) {
77
106
  return node[1];
78
107
  }
@@ -80,49 +109,52 @@ function stringifyExpression(node, ident) {
80
109
  return JSON.stringify(node[1]);
81
110
  }
82
111
  if (node[0] === NODE.arrayExpr) {
83
- const elements = node[1].map((n) => stringifyExpression(n, ident));
112
+ const elements = node[1].map((n) => stringifyExpression(n));
84
113
  return `[${elements.join(', ')}]`;
85
114
  }
86
115
  if (node[0] === NODE.binaryExpr) {
87
- return `${wrapIfComplex(node[1], ident)} ${node[2]} ${wrapIfComplex(node[3], ident)}`;
116
+ return `${wrapIfComplex(node[1])} ${node[2]} ${wrapIfComplex(node[3])}`;
88
117
  }
89
118
  if (node[0] === NODE.assignmentExpr) {
90
- return `${stringifyExpression(node[1], ident)} ${node[2]} ${stringifyExpression(node[3], ident)}`;
119
+ return `${stringifyExpression(node[1])} ${node[2]} ${stringifyExpression(node[3])}`;
91
120
  }
92
121
  if (node[0] === NODE.logicalExpr) {
93
- return `${wrapIfComplex(node[1], ident)} ${node[2]} ${wrapIfComplex(node[3], ident)}`;
122
+ return `${wrapIfComplex(node[1])} ${node[2]} ${wrapIfComplex(node[3])}`;
94
123
  }
95
124
  if (node[0] === NODE.unaryExpr) {
96
125
  // Unary word operators like void, instanceof and delete require a space
97
126
  const sep = node[1].length > 1 ? ' ' : '';
98
- return `${node[1]}${sep}${wrapIfComplex(node[2], ident)}`;
127
+ return `${node[1]}${sep}${wrapIfComplex(node[2])}`;
99
128
  }
100
129
  if (node[0] === NODE.call) {
101
- const callee = wrapIfComplex(node[1], ident);
102
- const args = node[2].map((n) => stringifyExpression(n, ident)).join(', ');
130
+ const callee = wrapIfComplex(node[1]);
131
+ const args = node[2].map((n) => stringifyExpression(n)).join(', ');
103
132
  return `${callee}(${args})`;
104
133
  }
105
134
  if (node[0] === NODE.memberAccess) {
106
135
  if (Array.isArray(node[1]) && node[1][0] === NODE.numericLiteral) {
107
- return `(${stringifyExpression(node[1], ident)}).${node[2]}`;
136
+ return `(${stringifyExpression(node[1])}).${stringifyExpression(node[2])}`;
108
137
  }
109
- return `${wrapIfComplex(node[1], ident)}.${node[2]}`;
138
+ return `${wrapIfComplex(node[1])}.${stringifyExpression(node[2])}`;
110
139
  }
111
140
  if (node[0] === NODE.indexAccess) {
112
- return `${wrapIfComplex(node[1], ident)}[${stringifyExpression(node[2], ident)}]`;
141
+ return `${wrapIfComplex(node[1])}[${stringifyExpression(node[2])}]`;
113
142
  }
114
143
  if (node[0] === NODE.preUpdate) {
115
- return `${node[1]}${wrapIfComplex(node[2], ident)}`;
144
+ return `${node[1]}${wrapIfComplex(node[2])}`;
116
145
  }
117
146
  if (node[0] === NODE.postUpdate) {
118
- return `${wrapIfComplex(node[2], ident)}${node[1]}`;
147
+ return `${wrapIfComplex(node[2])}${node[1]}`;
119
148
  }
120
149
  if (node[0] === NODE.objectExpr) {
121
- const entries = Object.entries(node[1]).map(([key, val]) => `${key}: ${stringifyExpression(val, ident)}`);
150
+ if (Array.isArray(node[1])) {
151
+ return `{ ${node[1].map(stringifyObjectProperty).join(', ')} }`;
152
+ }
153
+ const entries = Object.entries(node[1]).map(([key, val]) => `${stringifyObjectPropertyKey(key)}: ${stringifyExpression(val)}`);
122
154
  return `{ ${entries.join(', ')} }`;
123
155
  }
124
156
  if (node[0] === NODE.conditionalExpr) {
125
- return `${wrapIfComplex(node[1], ident)} ? ${wrapIfComplex(node[2], ident)} : ${wrapIfComplex(node[3], ident)}`;
157
+ return `${wrapIfComplex(node[1])} ? ${wrapIfComplex(node[2])} : ${wrapIfComplex(node[3])}`;
126
158
  }
127
159
  if (node[0] === NODE.nullLiteral) {
128
160
  return 'null';
@@ -135,6 +167,8 @@ function assertExhaustive(value) {
135
167
  function isExpression(node) {
136
168
  if (typeof node === 'string' ||
137
169
  typeof node === 'boolean' ||
170
+ node[0] === NODE.identifier ||
171
+ node[0] === NODE.booleanLiteral ||
138
172
  node[0] === NODE.numericLiteral ||
139
173
  node[0] === NODE.stringLiteral ||
140
174
  node[0] === NODE.arrayExpr ||
@@ -163,12 +197,14 @@ const SIMPLE_NODES = [
163
197
  NODE.arrayExpr, // [] make things not ambiguous
164
198
  NODE.stringLiteral,
165
199
  NODE.numericLiteral,
200
+ NODE.identifier,
201
+ NODE.booleanLiteral,
166
202
  ];
167
203
  /**
168
204
  * Stringifies expression, and wraps it in parentheses if they cannot be trivially omitted
169
205
  */
170
- function wrapIfComplex(node, ident) {
171
- const s = stringifyExpression(node, ident);
206
+ function wrapIfComplex(node) {
207
+ const s = stringifyExpression(node);
172
208
  if (typeof node === 'string' || typeof node === 'boolean') {
173
209
  return s;
174
210
  }
package/std/atomic.d.ts CHANGED
@@ -1,8 +1,15 @@
1
+ import { type _ref as ref } from '../data/ref.ts';
1
2
  import { type atomicI32, type atomicU32 } from '../data/wgslTypes.ts';
2
3
  type AnyAtomic = atomicI32 | atomicU32;
3
4
  export declare const workgroupBarrier: import("../types.ts").DualFn<(...args: never[]) => unknown>;
4
5
  export declare const storageBarrier: import("../types.ts").DualFn<(...args: never[]) => unknown>;
5
6
  export declare const textureBarrier: import("../types.ts").DualFn<(...args: never[]) => unknown>;
7
+ interface WorkgroupUniformLoad {
8
+ <T extends AnyAtomic>(value: T | ref<T>): number;
9
+ <T>(value: ref<T>): T;
10
+ <T>(value: T): T;
11
+ }
12
+ export declare const workgroupUniformLoad: import("../types.ts").DualFn<WorkgroupUniformLoad>;
6
13
  export declare const atomicLoad: import("../types.ts").DualFn<(<T extends AnyAtomic>(a: T) => number)>;
7
14
  export declare const atomicStore: import("../types.ts").DualFn<(<T extends AnyAtomic>(a: T, value: number) => void)>;
8
15
  export declare const atomicAdd: import("../types.ts").DualFn<(<T extends AnyAtomic>(a: T, value: number) => number)>;
@@ -12,4 +19,10 @@ export declare const atomicMin: import("../types.ts").DualFn<(<T extends AnyAtom
12
19
  export declare const atomicAnd: import("../types.ts").DualFn<(<T extends AnyAtomic>(a: T, value: number) => number)>;
13
20
  export declare const atomicOr: import("../types.ts").DualFn<(<T extends AnyAtomic>(a: T, value: number) => number)>;
14
21
  export declare const atomicXor: import("../types.ts").DualFn<(<T extends AnyAtomic>(a: T, value: number) => number)>;
22
+ export declare const atomicExchange: import("../types.ts").DualFn<(<T extends AnyAtomic>(a: T, value: number) => number)>;
23
+ type AtomicCompareExchangeResult = {
24
+ old_value: number;
25
+ exchanged: boolean;
26
+ };
27
+ export declare const atomicCompareExchangeWeak: import("../types.ts").DualFn<(<T extends AnyAtomic>(a: T, compare: number, value: number) => AtomicCompareExchangeResult)>;
15
28
  export {};
package/std/atomic.js CHANGED
@@ -1,6 +1,9 @@
1
1
  import { dualImpl } from "../core/function/dualImpl.js";
2
2
  import { stitch } from "../core/resolve/stitch.js";
3
- import { i32, u32 } from "../data/numeric.js";
3
+ import { unptr } from "../data/dataTypes.js";
4
+ import { bool, i32, u32 } from "../data/numeric.js";
5
+ import { derefSnippet } from "../data/ref.js";
6
+ import { abstruct } from "../data/struct.js";
4
7
  import { isAtomic, Void, } from "../data/wgslTypes.js";
5
8
  import { safeStringify } from "../shared/stringify.js";
6
9
  export const workgroupBarrier = dualImpl({
@@ -24,33 +27,40 @@ export const textureBarrier = dualImpl({
24
27
  codegenImpl: () => 'textureBarrier()',
25
28
  sideEffects: true,
26
29
  });
27
- const atomicNormalError = 'Atomic operations are not supported outside of CODEGEN mode.';
28
- export const atomicLoad = dualImpl({
29
- name: 'atomicLoad',
30
- normalImpl: atomicNormalError,
31
- signature: (a) => {
32
- if (!isAtomic(a)) {
33
- throw new Error(`Invalid atomic type: ${safeStringify(a)}`);
34
- }
35
- return { argTypes: [a], returnType: a.inner };
30
+ export const workgroupUniformLoad = dualImpl({
31
+ name: 'workgroupUniformLoad',
32
+ normalImpl: 'workgroupUniformLoad is not supported outside of CODEGEN mode.',
33
+ signature: (value) => {
34
+ const inner = unptr(value);
35
+ return {
36
+ argTypes: [value],
37
+ returnType: isAtomic(inner) ? inner.inner : inner,
38
+ };
36
39
  },
37
- codegenImpl: (_ctx, [a]) => stitch `atomicLoad(&${a})`,
40
+ codegenImpl: (_ctx, [value]) => stitch `workgroupUniformLoad(&${derefSnippet(value)})`,
38
41
  sideEffects: true,
39
42
  });
40
- const atomicActionSignature = (a) => {
41
- if (!isAtomic(a)) {
43
+ const atomicNormalError = 'Atomic operations are not supported outside of CODEGEN mode.';
44
+ const unwrapAtomic = (a) => {
45
+ const inner = unptr(a);
46
+ if (!isAtomic(inner)) {
42
47
  throw new Error(`Invalid atomic type: ${safeStringify(a)}`);
43
48
  }
44
- return {
45
- argTypes: [a, a.inner.type === 'u32' ? u32 : i32],
46
- returnType: Void,
47
- };
49
+ return inner;
48
50
  };
51
+ export const atomicLoad = dualImpl({
52
+ name: 'atomicLoad',
53
+ normalImpl: atomicNormalError,
54
+ signature: (a) => ({ argTypes: [a], returnType: unwrapAtomic(a).inner }),
55
+ codegenImpl: (_ctx, [a]) => stitch `atomicLoad(&${derefSnippet(a)})`,
56
+ sideEffects: true,
57
+ });
58
+ const atomicActionSignature = (a) => ({
59
+ argTypes: [a, unwrapAtomic(a).inner.type === 'u32' ? u32 : i32],
60
+ returnType: Void,
61
+ });
49
62
  const atomicOpSignature = (a) => {
50
- if (!isAtomic(a)) {
51
- throw new Error(`Invalid atomic type: ${safeStringify(a)}`);
52
- }
53
- const paramType = a.inner.type === 'u32' ? u32 : i32;
63
+ const paramType = unwrapAtomic(a).inner.type === 'u32' ? u32 : i32;
54
64
  return {
55
65
  argTypes: [a, paramType],
56
66
  returnType: paramType,
@@ -60,55 +70,79 @@ export const atomicStore = dualImpl({
60
70
  name: 'atomicStore',
61
71
  normalImpl: atomicNormalError,
62
72
  signature: atomicActionSignature,
63
- codegenImpl: (_ctx, [a, value]) => stitch `atomicStore(&${a}, ${value})`,
73
+ codegenImpl: (_ctx, [a, value]) => stitch `atomicStore(&${derefSnippet(a)}, ${value})`,
64
74
  sideEffects: true,
65
75
  });
66
76
  export const atomicAdd = dualImpl({
67
77
  name: 'atomicAdd',
68
78
  normalImpl: atomicNormalError,
69
79
  signature: atomicOpSignature,
70
- codegenImpl: (_ctx, [a, value]) => stitch `atomicAdd(&${a}, ${value})`,
80
+ codegenImpl: (_ctx, [a, value]) => stitch `atomicAdd(&${derefSnippet(a)}, ${value})`,
71
81
  sideEffects: true,
72
82
  });
73
83
  export const atomicSub = dualImpl({
74
84
  name: 'atomicSub',
75
85
  normalImpl: atomicNormalError,
76
86
  signature: atomicOpSignature,
77
- codegenImpl: (_ctx, [a, value]) => stitch `atomicSub(&${a}, ${value})`,
87
+ codegenImpl: (_ctx, [a, value]) => stitch `atomicSub(&${derefSnippet(a)}, ${value})`,
78
88
  sideEffects: true,
79
89
  });
80
90
  export const atomicMax = dualImpl({
81
91
  name: 'atomicMax',
82
92
  normalImpl: atomicNormalError,
83
93
  signature: atomicOpSignature,
84
- codegenImpl: (_ctx, [a, value]) => stitch `atomicMax(&${a}, ${value})`,
94
+ codegenImpl: (_ctx, [a, value]) => stitch `atomicMax(&${derefSnippet(a)}, ${value})`,
85
95
  sideEffects: true,
86
96
  });
87
97
  export const atomicMin = dualImpl({
88
98
  name: 'atomicMin',
89
99
  normalImpl: atomicNormalError,
90
100
  signature: atomicOpSignature,
91
- codegenImpl: (_ctx, [a, value]) => stitch `atomicMin(&${a}, ${value})`,
101
+ codegenImpl: (_ctx, [a, value]) => stitch `atomicMin(&${derefSnippet(a)}, ${value})`,
92
102
  sideEffects: true,
93
103
  });
94
104
  export const atomicAnd = dualImpl({
95
105
  name: 'atomicAnd',
96
106
  normalImpl: atomicNormalError,
97
107
  signature: atomicOpSignature,
98
- codegenImpl: (_ctx, [a, value]) => stitch `atomicAnd(&${a}, ${value})`,
108
+ codegenImpl: (_ctx, [a, value]) => stitch `atomicAnd(&${derefSnippet(a)}, ${value})`,
99
109
  sideEffects: true,
100
110
  });
101
111
  export const atomicOr = dualImpl({
102
112
  name: 'atomicOr',
103
113
  normalImpl: atomicNormalError,
104
114
  signature: atomicOpSignature,
105
- codegenImpl: (_ctx, [a, value]) => stitch `atomicOr(&${a}, ${value})`,
115
+ codegenImpl: (_ctx, [a, value]) => stitch `atomicOr(&${derefSnippet(a)}, ${value})`,
106
116
  sideEffects: true,
107
117
  });
108
118
  export const atomicXor = dualImpl({
109
119
  name: 'atomicXor',
110
120
  normalImpl: atomicNormalError,
111
121
  signature: atomicOpSignature,
112
- codegenImpl: (_ctx, [a, value]) => stitch `atomicXor(&${a}, ${value})`,
122
+ codegenImpl: (_ctx, [a, value]) => stitch `atomicXor(&${derefSnippet(a)}, ${value})`,
123
+ sideEffects: true,
124
+ });
125
+ export const atomicExchange = dualImpl({
126
+ name: 'atomicExchange',
127
+ normalImpl: atomicNormalError,
128
+ signature: atomicOpSignature,
129
+ codegenImpl: (_ctx, [a, value]) => stitch `atomicExchange(&${derefSnippet(a)}, ${value})`,
130
+ sideEffects: true,
131
+ });
132
+ const AtomicCompareExchangeResults = {
133
+ i32: abstruct({ old_value: i32, exchanged: bool }),
134
+ u32: abstruct({ old_value: u32, exchanged: bool }),
135
+ };
136
+ export const atomicCompareExchangeWeak = dualImpl({
137
+ name: 'atomicCompareExchangeWeak',
138
+ normalImpl: atomicNormalError,
139
+ signature: (a) => {
140
+ const inner = unwrapAtomic(a).inner.type === 'u32' ? u32 : i32;
141
+ return {
142
+ argTypes: [a, inner, inner],
143
+ returnType: AtomicCompareExchangeResults[inner.type],
144
+ };
145
+ },
146
+ codegenImpl: (_ctx, [a, compare, value]) => stitch `atomicCompareExchangeWeak(&${derefSnippet(a)}, ${compare}, ${value})`,
113
147
  sideEffects: true,
114
148
  });