typegpu 0.11.8 → 0.12.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/builtin.d.ts +51 -53
- package/builtin.js +30 -34
- package/common/fullScreenTriangle.d.ts +2 -10
- package/common/fullScreenTriangle.js +20 -28
- package/common/index.d.ts +2 -9
- package/common/index.js +3 -12
- package/common/writeSoA.d.ts +11 -12
- package/common/writeSoA.js +88 -76
- package/core/buffer/buffer.d.ts +79 -62
- package/core/buffer/buffer.js +279 -205
- package/core/buffer/bufferBinding.d.ts +71 -0
- package/core/buffer/bufferBinding.js +154 -0
- package/core/buffer/bufferUsage.d.ts +14 -45
- package/core/buffer/bufferUsage.js +1 -171
- package/core/buffer/laidOutBuffer.d.ts +25 -0
- package/core/buffer/laidOutBuffer.js +55 -0
- package/core/commandEncoder/attachments.d.ts +125 -0
- package/core/commandEncoder/attachments.js +29 -0
- package/core/commandEncoder/commandEncoder.d.ts +53 -0
- package/core/commandEncoder/commandEncoder.js +56 -0
- package/core/commandEncoder/computePass.d.ts +58 -0
- package/core/commandEncoder/computePass.js +63 -0
- package/core/commandEncoder/renderPass.d.ts +121 -0
- package/core/commandEncoder/renderPass.js +196 -0
- package/core/constant/tgpuConstant.d.ts +26 -26
- package/core/constant/tgpuConstant.js +76 -65
- package/core/declare/tgpuDeclare.d.ts +3 -6
- package/core/declare/tgpuDeclare.js +37 -38
- package/core/function/autoIO.d.ts +61 -33
- package/core/function/autoIO.js +68 -73
- package/core/function/comptime.d.ts +11 -13
- package/core/function/comptime.js +60 -52
- package/core/function/createCallableSchema.d.ts +16 -0
- package/core/function/createCallableSchema.js +50 -40
- package/core/function/dualImpl.d.ts +44 -0
- package/core/function/dualImpl.js +71 -50
- package/core/function/entryInputRouter.d.ts +26 -0
- package/core/function/entryInputRouter.js +31 -33
- package/core/function/extractArgs.d.ts +38 -0
- package/core/function/extractArgs.js +202 -200
- package/core/function/fnCore.d.ts +44 -0
- package/core/function/fnCore.js +148 -92
- package/core/function/fnTypes.d.ts +31 -29
- package/core/function/fnTypes.js +1 -0
- package/core/function/ioSchema.d.ts +12 -10
- package/core/function/ioSchema.js +65 -47
- package/core/function/shelllessImpl.d.ts +9 -12
- package/core/function/shelllessImpl.js +18 -20
- package/core/function/templateUtils.d.ts +2 -0
- package/core/function/templateUtils.js +9 -8
- package/core/function/tgpuComputeFn.d.ts +26 -28
- package/core/function/tgpuComputeFn.js +47 -48
- package/core/function/tgpuFn.d.ts +34 -37
- package/core/function/tgpuFn.js +179 -153
- package/core/function/tgpuFragmentFn.d.ts +52 -53
- package/core/function/tgpuFragmentFn.js +63 -56
- package/core/function/tgpuVertexFn.d.ts +36 -39
- package/core/function/tgpuVertexFn.js +56 -53
- package/core/pipeline/computePipeline.d.ts +141 -53
- package/core/pipeline/computePipeline.js +291 -225
- package/core/pipeline/connectAttachmentToShader.d.ts +4 -0
- package/core/pipeline/connectAttachmentToShader.js +25 -22
- package/core/pipeline/connectTargetsToShader.d.ts +3 -0
- package/core/pipeline/connectTargetsToShader.js +29 -27
- package/core/pipeline/drawState.d.ts +52 -0
- package/core/pipeline/drawState.js +225 -0
- package/core/pipeline/limitsOverflow.d.ts +2 -0
- package/core/pipeline/limitsOverflow.js +13 -10
- package/core/pipeline/performanceTracker.d.ts +14 -0
- package/core/pipeline/performanceTracker.js +34 -0
- package/core/pipeline/pipelineUtils.d.ts +18 -0
- package/core/pipeline/pipelineUtils.js +54 -25
- package/core/pipeline/priors.d.ts +1 -0
- package/core/pipeline/priors.js +12 -0
- package/core/pipeline/renderPipeline.d.ts +246 -261
- package/core/pipeline/renderPipeline.js +487 -480
- package/core/pipeline/timeable.d.ts +28 -18
- package/core/pipeline/timeable.js +90 -52
- package/core/pipeline/typeGuards.d.ts +17 -0
- package/core/pipeline/typeGuards.js +52 -22
- package/core/querySet/querySet.d.ts +26 -21
- package/core/querySet/querySet.js +127 -103
- package/core/rawCodeSnippet/tgpuRawCodeSnippet.d.ts +13 -18
- package/core/rawCodeSnippet/tgpuRawCodeSnippet.js +90 -93
- package/core/resolve/externals.d.ts +25 -7
- package/core/resolve/externals.js +79 -62
- package/core/resolve/namespace.d.ts +22 -25
- package/core/resolve/namespace.js +16 -21
- package/core/resolve/resolvableString.d.ts +7 -0
- package/core/resolve/resolvableString.js +13 -0
- package/core/resolve/resolveData.d.ts +10 -0
- package/core/resolve/resolveData.js +162 -121
- package/core/resolve/stitch.d.ts +8 -0
- package/core/resolve/stitch.js +23 -21
- package/core/resolve/tgpuResolve.d.ts +55 -53
- package/core/resolve/tgpuResolve.js +106 -55
- package/core/root/configurableImpl.d.ts +10 -0
- package/core/root/configurableImpl.js +17 -19
- package/core/root/init.d.ts +76 -38
- package/core/root/init.js +346 -454
- package/core/root/rootTypes.d.ts +461 -627
- package/core/root/rootTypes.js +1 -0
- package/core/sampler/sampler.d.ts +50 -31
- package/core/sampler/sampler.js +120 -113
- package/core/simulate/tgpuSimulate.d.ts +9 -12
- package/core/simulate/tgpuSimulate.js +68 -71
- package/core/slot/accessor.d.ts +36 -13
- package/core/slot/accessor.js +138 -91
- package/core/slot/internalSlots.d.ts +2 -0
- package/core/slot/internalSlots.js +1 -6
- package/core/slot/lazy.d.ts +2 -6
- package/core/slot/lazy.js +71 -39
- package/core/slot/slot.d.ts +2 -6
- package/core/slot/slot.js +45 -38
- package/core/slot/slotTypes.d.ts +78 -80
- package/core/slot/slotTypes.js +11 -16
- package/core/texture/externalTexture.d.ts +20 -11
- package/core/texture/externalTexture.js +46 -46
- package/core/texture/texture.d.ts +152 -97
- package/core/texture/texture.js +407 -314
- package/core/texture/textureFormats.d.ts +42 -23
- package/core/texture/textureFormats.js +104 -75
- package/core/texture/textureProps.d.ts +7 -10
- package/core/texture/textureProps.js +1 -0
- package/core/texture/textureUtils.d.ts +8 -0
- package/core/texture/textureUtils.js +185 -179
- package/core/texture/usageExtension.d.ts +21 -18
- package/core/texture/usageExtension.js +13 -17
- package/core/unroll/tgpuUnroll.d.ts +20 -19
- package/core/unroll/tgpuUnroll.js +82 -91
- package/core/valueProxyUtils.d.ts +3 -0
- package/core/valueProxyUtils.js +52 -41
- package/core/variable/tgpuVariable.d.ts +26 -27
- package/core/variable/tgpuVariable.js +93 -96
- package/core/vertexLayout/connectAttributesToShader.d.ts +9 -0
- package/core/vertexLayout/connectAttributesToShader.js +61 -56
- package/core/vertexLayout/vertexAttribute.d.ts +21 -17
- package/core/vertexLayout/vertexAttribute.js +1 -0
- package/core/vertexLayout/vertexLayout.d.ts +24 -18
- package/core/vertexLayout/vertexLayout.js +130 -99
- package/core/whitespaces.d.ts +2 -0
- package/core/whitespaces.js +16 -0
- package/data/alignIO.d.ts +7 -0
- package/data/alignIO.js +13 -13
- package/data/alignmentOf.d.ts +5 -8
- package/data/alignmentOf.js +90 -75
- package/data/array.d.ts +6 -9
- package/data/array.js +48 -40
- package/data/atomic.d.ts +2 -7
- package/data/atomic.js +26 -23
- package/data/attributes.d.ts +19 -21
- package/data/attributes.js +192 -132
- package/data/autoStruct.d.ts +36 -3
- package/data/autoStruct.js +84 -81
- package/data/compiledIO.d.ts +5 -0
- package/data/compiledIO.js +231 -214
- package/data/dataIO.d.ts +13 -11
- package/data/dataIO.js +643 -589
- package/data/dataTypes.d.ts +83 -65
- package/data/dataTypes.js +66 -68
- package/data/deepEqual.d.ts +2 -7
- package/data/deepEqual.js +80 -55
- package/data/disarray.d.ts +6 -10
- package/data/disarray.js +52 -46
- package/data/float16Conversion.d.ts +4 -0
- package/data/float16Conversion.js +7 -0
- package/data/getLongestContiguousPrefix.d.ts +4 -8
- package/data/getLongestContiguousPrefix.js +6 -11
- package/data/index.d.ts +30 -31
- package/data/index.js +39 -172
- package/data/instanceToSchema.d.ts +24 -27
- package/data/instanceToSchema.js +1 -0
- package/data/isContiguous.d.ts +4 -8
- package/data/isContiguous.js +6 -11
- package/data/matrix.d.ts +21 -21
- package/data/matrix.js +513 -505
- package/data/numberOps.d.ts +6 -0
- package/data/numberOps.js +26 -20
- package/data/numeric.d.ts +21 -12
- package/data/numeric.js +287 -192
- package/data/offsetUtils.d.ts +9 -13
- package/data/offsetUtils.js +204 -144
- package/data/offsets.d.ts +8 -0
- package/data/offsets.js +33 -33
- package/data/partialIO.d.ts +13 -8
- package/data/partialIO.js +124 -113
- package/data/ptr.d.ts +12 -12
- package/data/ptr.js +42 -44
- package/data/ref.d.ts +40 -27
- package/data/ref.js +125 -95
- package/data/sampler.d.ts +99 -101
- package/data/sampler.js +17 -21
- package/data/schemaCallWrapper.d.ts +19 -0
- package/data/schemaCallWrapper.js +31 -26
- package/data/schemaMemoryLayout.d.ts +8 -0
- package/data/schemaMemoryLayout.js +213 -185
- package/data/sizeOf.d.ts +4 -8
- package/data/sizeOf.js +6 -11
- package/data/snippet.d.ts +96 -20
- package/data/snippet.js +75 -86
- package/data/struct.d.ts +4 -7
- package/data/struct.js +51 -39
- package/data/texture.d.ts +253 -250
- package/data/texture.js +238 -200
- package/data/unstruct.d.ts +2 -7
- package/data/unstruct.js +43 -37
- package/data/vector.d.ts +33 -21
- package/data/vector.js +234 -221
- package/data/vectorImpl.d.ts +231 -0
- package/data/vectorImpl.js +563 -515
- package/data/vectorOps.d.ts +63 -0
- package/data/vectorOps.js +642 -630
- package/data/vertexFormatData.d.ts +181 -184
- package/data/vertexFormatData.js +104 -106
- package/data/wgslTypes.d.ts +665 -600
- package/data/wgslTypes.js +220 -198
- package/errors.d.ts +50 -24
- package/errors.js +146 -120
- package/execMode.d.ts +16 -0
- package/execMode.js +47 -40
- package/extension.d.ts +13 -8
- package/extension.js +10 -16
- package/getGPUValue.d.ts +1 -0
- package/getGPUValue.js +2 -7
- package/index.d.ts +28 -68
- package/index.js +8 -28
- package/indexNamedExports.d.ts +69 -42
- package/indexNamedExports.js +25 -0
- package/internal.d.ts +16 -0
- package/internal.js +12 -0
- package/mathUtils.d.ts +5 -0
- package/mathUtils.js +7 -11
- package/memo.d.ts +11 -0
- package/memo.js +20 -22
- package/minify.d.ts +5 -0
- package/minify.js +93 -0
- package/nameUtils.d.ts +29 -0
- package/nameUtils.js +428 -398
- package/package.json +9 -3
- package/resolutionCtx.d.ts +162 -22
- package/resolutionCtx.js +911 -624
- package/serial/registry.d.ts +15 -0
- package/serial/registry.js +106 -0
- package/serial/restore.d.ts +49 -0
- package/serial/restore.js +68 -0
- package/serial/schema.d.ts +58 -0
- package/serial/schema.js +209 -0
- package/serial/types.d.ts +5 -0
- package/serial/types.js +1 -0
- package/shared/env.d.ts +9 -0
- package/shared/env.js +9 -12
- package/shared/generators.d.ts +4 -0
- package/shared/generators.js +10 -12
- package/shared/meta.d.ts +29 -14
- package/shared/meta.js +71 -55
- package/shared/normalizeMetadata.d.ts +32 -27
- package/shared/normalizeMetadata.js +23 -36
- package/shared/repr.d.ts +73 -55
- package/shared/repr.js +1 -0
- package/shared/soul.d.ts +17 -0
- package/shared/soul.js +1 -0
- package/shared/stringify.d.ts +2 -0
- package/shared/stringify.js +29 -20
- package/shared/symbols.d.ts +26 -20
- package/shared/symbols.js +79 -79
- package/shared/tseynit.d.ts +2 -0
- package/shared/tseynit.js +160 -75
- package/shared/utilityTypes.d.ts +30 -14
- package/shared/utilityTypes.js +2 -6
- package/shared/vertexFormat.d.ts +41 -43
- package/shared/vertexFormat.js +59 -62
- package/std/array.d.ts +2 -7
- package/std/array.js +18 -23
- package/std/atomic.d.ts +14 -18
- package/std/atomic.js +96 -95
- package/std/bitcast.d.ts +193 -10
- package/std/bitcast.js +309 -39
- package/std/boolean.d.ts +19 -33
- package/std/boolean.js +296 -288
- package/std/copy.d.ts +2 -7
- package/std/copy.js +27 -24
- package/std/derivative.d.ts +11 -15
- package/std/derivative.js +56 -79
- package/std/discard.d.ts +1 -6
- package/std/discard.js +7 -14
- package/std/environment.d.ts +19 -14
- package/std/environment.js +71 -51
- package/std/extensions.d.ts +2 -8
- package/std/extensions.js +10 -11
- package/std/index.d.ts +20 -24
- package/std/index.js +32 -187
- package/std/matrix.d.ts +6 -12
- package/std/matrix.js +60 -69
- package/std/numeric.d.ts +143 -146
- package/std/numeric.js +858 -749
- package/std/operators.d.ts +19 -27
- package/std/operators.js +227 -193
- package/std/packing.d.ts +5 -11
- package/std/packing.js +67 -75
- package/std/range.d.ts +6 -11
- package/std/range.js +48 -36
- package/std/subgroup.d.ts +33 -37
- package/std/subgroup.js +180 -217
- package/std/texture.d.ts +70 -38
- package/std/texture.js +176 -172
- package/taskQueue.d.ts +6 -0
- package/taskQueue.js +30 -0
- package/tgpu.d.ts +16 -0
- package/tgpu.js +17 -44
- package/tgpuBindGroupLayout.d.ts +132 -152
- package/tgpuBindGroupLayout.js +346 -268
- package/tgpuLogger.d.ts +34 -0
- package/tgpuLogger.js +63 -0
- package/tgpuUnstable.d.ts +16 -22
- package/tgpuUnstable.js +13 -35
- package/tgsl/accessIndex.d.ts +2 -0
- package/tgsl/accessIndex.js +76 -33
- package/tgsl/accessProp.d.ts +2 -0
- package/tgsl/accessProp.js +142 -100
- package/tgsl/accessStructProp.d.ts +5 -0
- package/tgsl/accessStructProp.js +18 -0
- package/tgsl/consoleLog/deserializers.d.ts +13 -0
- package/tgsl/consoleLog/deserializers.js +119 -103
- package/tgsl/consoleLog/logGenerator.d.ts +21 -0
- package/tgsl/consoleLog/logGenerator.js +85 -80
- package/tgsl/consoleLog/serializers.d.ts +21 -0
- package/tgsl/consoleLog/serializers.js +119 -100
- package/tgsl/consoleLog/types.d.ts +40 -39
- package/tgsl/consoleLog/types.js +1 -0
- package/tgsl/conversion.d.ts +33 -0
- package/tgsl/conversion.js +272 -193
- package/tgsl/forOfUtils.d.ts +15 -0
- package/tgsl/forOfUtils.js +85 -70
- package/tgsl/generationHelpers.d.ts +22 -39
- package/tgsl/generationHelpers.js +85 -58
- package/tgsl/infixDispatch.d.ts +70 -0
- package/tgsl/infixDispatch.js +48 -46
- package/tgsl/jsPolyfills.d.ts +16 -19
- package/tgsl/jsPolyfills.js +44 -42
- package/tgsl/makeDereferenceable.d.ts +48 -0
- package/tgsl/makeDereferenceable.js +86 -0
- package/tgsl/makeResolvable.d.ts +22 -0
- package/tgsl/makeResolvable.js +33 -0
- package/tgsl/shaderGenerator.d.ts +77 -17
- package/tgsl/shaderGenerator.js +1 -0
- package/tgsl/shellless.d.ts +6 -9
- package/tgsl/shellless.js +73 -43
- package/tgsl/wgslGenerator.d.ts +66 -59
- package/tgsl/wgslGenerator.js +1344 -747
- package/types.d.ts +287 -221
- package/types.js +61 -44
- package/unwrapper.d.ts +31 -26
- package/unwrapper.js +1 -0
- package/wgslExtensions.d.ts +3 -5
- package/wgslExtensions.js +12 -16
- package/_virtual/rolldown_runtime.js +0 -13
- package/core/buffer/bufferShorthand.d.ts +0 -50
- package/core/buffer/bufferShorthand.js +0 -55
- package/core/pipeline/applyPipelineState.js +0 -37
- package/package.js +0 -5
- package/tgsl/shaderGenerator_members.d.ts +0 -33
- package/tgsl/shaderGenerator_members.js +0 -12
package/tgsl/wgslGenerator.js
CHANGED
|
@@ -1,792 +1,1389 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { Void, isBool, isNaturallyEphemeral, isNumericSchema, isPtr, isVec, isWgslArray, isWgslStruct } from "../data/wgslTypes.js";
|
|
3
|
-
import { safeStringify } from "../shared/stringify.js";
|
|
4
|
-
import { getName } from "../shared/meta.js";
|
|
5
|
-
import { UnknownData, unptr } from "../data/dataTypes.js";
|
|
6
|
-
import { fallthroughCopyOrigin, isAlias, snip } from "../data/snippet.js";
|
|
7
|
-
import { ResolutionError, WgslTypeError, invariant } from "../errors.js";
|
|
8
|
-
import { isGPUCallable, isKnownAtComptime } from "../types.js";
|
|
1
|
+
import * as tinyest from 'tinyest';
|
|
9
2
|
import { stitch } from "../core/resolve/stitch.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import { convertStructValues, convertToCommonType, tryConvertSnippet } from "./conversion.js";
|
|
3
|
+
import { arrayOf } from "../data/array.js";
|
|
4
|
+
import { UnknownData, unptr } from "../data/dataTypes.js";
|
|
13
5
|
import { bool, i32, u32 } from "../data/numeric.js";
|
|
14
|
-
import { ArrayExpression, coerceToSnippet, concretize, numericLiteralToSnippet } from "./generationHelpers.js";
|
|
15
6
|
import { vec2u, vec3u, vec4u } from "../data/vector.js";
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
7
|
+
import { fallthroughCopyOrigin, isAlias, snip, } from "../data/snippet.js";
|
|
8
|
+
import * as wgsl from "../data/wgslTypes.js";
|
|
9
|
+
import { invariant, ResolutionError, WgslTypeError } from "../errors.js";
|
|
10
|
+
import { getName } from "../shared/meta.js";
|
|
11
|
+
import { $gpuCallable, $internal, $providing, isMarkedInternal } from "../shared/symbols.js";
|
|
12
|
+
import { safeStringify } from "../shared/stringify.js";
|
|
13
|
+
import { pow } from "../std/numeric.js";
|
|
18
14
|
import { add, div, mul, neg, sub } from "../std/operators.js";
|
|
19
|
-
import {
|
|
20
|
-
import {
|
|
15
|
+
import { eq, ne, lt, le, gt, ge, not } from "../std/boolean.js";
|
|
16
|
+
import { isGPUCallable, isKnownAtComptime, } from "../types.js";
|
|
17
|
+
import { convertStructValues, convertToCommonType, tryConvertSnippet } from "./conversion.js";
|
|
18
|
+
import { ArrayExpression, coerceToSnippet, concretize, numericLiteralToSnippet, } from "./generationHelpers.js";
|
|
21
19
|
import { accessIndex } from "./accessIndex.js";
|
|
20
|
+
import { accessProp } from "./accessProp.js";
|
|
21
|
+
import { resolveData } from "../core/resolve/resolveData.js";
|
|
22
|
+
import { createPtrFromOrigin, implicitFrom, ptrFn } from "../data/ptr.js";
|
|
23
|
+
import { _ref, RefOperator } from "../data/ref.js";
|
|
22
24
|
import { constant } from "../core/constant/tgpuConstant.js";
|
|
25
|
+
import { unroll, UnrollableIterable } from "../core/unroll/tgpuUnroll.js";
|
|
23
26
|
import { isGenericFn } from "../core/function/tgpuFn.js";
|
|
24
|
-
import {
|
|
25
|
-
import { pow } from "../std/numeric.js";
|
|
26
|
-
import { resolveData } from "../core/resolve/resolveData.js";
|
|
27
|
-
import { UnrollableIterable, unroll } from "../core/unroll/tgpuUnroll.js";
|
|
27
|
+
import { AutoStruct } from "../data/autoStruct.js";
|
|
28
28
|
import { mathToStd, supportedLogOps } from "./jsPolyfills.js";
|
|
29
|
+
import * as forOfUtils from "./forOfUtils.js";
|
|
29
30
|
import { isTgpuRange } from "../std/range.js";
|
|
30
|
-
import { getElementSnippet, getElementType, getLoopVarKind, getRangeSnippets } from "./forOfUtils.js";
|
|
31
31
|
import { stringifyNode } from "../shared/tseynit.js";
|
|
32
|
+
import { getAttributesString } from "../data/attributes.js";
|
|
32
33
|
import { validSelectBranchTypes } from "../std/boolean.js";
|
|
33
|
-
import
|
|
34
|
-
|
|
35
|
-
//#region src/tgsl/wgslGenerator.ts
|
|
34
|
+
import { isInfixDispatch } from "./infixDispatch.js";
|
|
35
|
+
import { logger } from "../tgpuLogger.js";
|
|
36
36
|
const { NodeTypeCatalog: NODE } = tinyest;
|
|
37
37
|
const parenthesizedOps = [
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
"||"
|
|
58
|
-
];
|
|
59
|
-
const binaryLogicalOps = [
|
|
60
|
-
"&&",
|
|
61
|
-
"||",
|
|
62
|
-
"==",
|
|
63
|
-
"!=",
|
|
64
|
-
"===",
|
|
65
|
-
"!==",
|
|
66
|
-
"<",
|
|
67
|
-
"<=",
|
|
68
|
-
">",
|
|
69
|
-
">="
|
|
70
|
-
];
|
|
71
|
-
const bitShiftOps = [
|
|
72
|
-
"<<",
|
|
73
|
-
">>",
|
|
74
|
-
"<<=",
|
|
75
|
-
">>="
|
|
38
|
+
'==',
|
|
39
|
+
'!=',
|
|
40
|
+
'===',
|
|
41
|
+
'!==',
|
|
42
|
+
'<',
|
|
43
|
+
'<=',
|
|
44
|
+
'>',
|
|
45
|
+
'>=',
|
|
46
|
+
'<<',
|
|
47
|
+
'>>',
|
|
48
|
+
'>>>',
|
|
49
|
+
'+',
|
|
50
|
+
'-',
|
|
51
|
+
'*',
|
|
52
|
+
'/',
|
|
53
|
+
'%',
|
|
54
|
+
'|',
|
|
55
|
+
'^',
|
|
56
|
+
'&',
|
|
76
57
|
];
|
|
58
|
+
const binaryLogicalOps = ['&&', '||', '==', '!=', '===', '!==', '<', '<=', '>', '>='];
|
|
59
|
+
const binaryRelationalOpToStdMap = {
|
|
60
|
+
'===': eq.toString(),
|
|
61
|
+
'!==': ne.toString(),
|
|
62
|
+
'<': lt.toString(),
|
|
63
|
+
'<=': le.toString(),
|
|
64
|
+
'>': gt.toString(),
|
|
65
|
+
'>=': ge.toString(),
|
|
66
|
+
};
|
|
67
|
+
const bitShiftOps = ['<<', '>>', '<<=', '>>=', '>>>', '>>>='];
|
|
77
68
|
const OP_MAP = {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
69
|
+
//
|
|
70
|
+
// binary
|
|
71
|
+
//
|
|
72
|
+
'===': '==',
|
|
73
|
+
'!==': '!=',
|
|
74
|
+
'>>>': '>>',
|
|
75
|
+
get in() {
|
|
76
|
+
throw new Error('The `in` operator is unsupported in TypeGPU functions.');
|
|
77
|
+
},
|
|
78
|
+
get instanceof() {
|
|
79
|
+
throw new Error('The `instanceof` operator is unsupported in TypeGPU functions.');
|
|
80
|
+
},
|
|
81
|
+
get '|>'() {
|
|
82
|
+
throw new Error('The `|>` operator is unsupported in TypeGPU functions.');
|
|
83
|
+
},
|
|
84
|
+
//
|
|
85
|
+
// logical
|
|
86
|
+
//
|
|
87
|
+
get '??'() {
|
|
88
|
+
throw new Error('The `??` operator is unsupported in TypeGPU functions.');
|
|
89
|
+
},
|
|
90
|
+
//
|
|
91
|
+
// assignment
|
|
92
|
+
//
|
|
93
|
+
'>>>=': '>>=',
|
|
94
|
+
get '**='() {
|
|
95
|
+
throw new Error('The `**=` operator is unsupported in TypeGPU functions.');
|
|
96
|
+
},
|
|
97
|
+
get '??='() {
|
|
98
|
+
throw new Error('The `??=` operator is unsupported in TypeGPU functions.');
|
|
99
|
+
},
|
|
100
|
+
get '&&='() {
|
|
101
|
+
throw new Error('The `&&=` operator is unsupported in TypeGPU functions.');
|
|
102
|
+
},
|
|
103
|
+
get '||='() {
|
|
104
|
+
throw new Error('The `||=` operator is unsupported in TypeGPU functions.');
|
|
105
|
+
},
|
|
110
106
|
};
|
|
111
107
|
function operatorToType(lhs, op, rhs) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
108
|
+
if (!rhs) {
|
|
109
|
+
if (op === '!') {
|
|
110
|
+
return bool;
|
|
111
|
+
}
|
|
112
|
+
return lhs;
|
|
113
|
+
}
|
|
114
|
+
if (binaryLogicalOps.includes(op)) {
|
|
115
|
+
return bool;
|
|
116
|
+
}
|
|
117
|
+
if (op === '=') {
|
|
118
|
+
return rhs;
|
|
119
|
+
}
|
|
120
|
+
return lhs;
|
|
119
121
|
}
|
|
120
122
|
const unaryOpCodeToCodegen = {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
123
|
+
'-': neg[$gpuCallable].call.bind(neg),
|
|
124
|
+
void: () => snip(undefined, wgsl.Void, 'constant', false),
|
|
125
|
+
'!': (ctx, [argExpr]) => {
|
|
126
|
+
if (argExpr === undefined) {
|
|
127
|
+
throw new Error('The unary operator `!` expects 1 argument, but 0 were provided.');
|
|
128
|
+
}
|
|
129
|
+
if (isKnownAtComptime(argExpr)) {
|
|
130
|
+
return snip(!argExpr.value, bool, 'constant', false);
|
|
131
|
+
}
|
|
132
|
+
const argStr = ctx.resolveSnippet(argExpr).value;
|
|
133
|
+
if (!wgsl.isBool(argExpr.dataType)) {
|
|
134
|
+
throw new WgslTypeError(`Unary operator ! requires boolean operand. Got ${String(argExpr.dataType)}.${wgsl.isVecBool(argExpr.dataType)
|
|
135
|
+
? ` For component-wise negation, use 'std.${not.toString()}'.`
|
|
136
|
+
: ''}`);
|
|
137
|
+
}
|
|
138
|
+
return snip(`!(${argStr})`, bool, 'runtime', argExpr.possibleSideEffects);
|
|
139
|
+
},
|
|
135
140
|
};
|
|
136
141
|
const binaryOpCodeToCodegen = {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
'+': add[$gpuCallable].call.bind(add),
|
|
143
|
+
'-': sub[$gpuCallable].call.bind(sub),
|
|
144
|
+
'*': mul[$gpuCallable].call.bind(mul),
|
|
145
|
+
'/': div[$gpuCallable].call.bind(div),
|
|
146
|
+
'**': pow[$gpuCallable].call.bind(pow),
|
|
142
147
|
};
|
|
143
148
|
const usageToVarTemplateMap = {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
private: 'private',
|
|
150
|
+
workgroup: 'workgroup',
|
|
151
|
+
uniform: 'uniform',
|
|
152
|
+
mutable: 'storage, read_write',
|
|
153
|
+
readonly: 'storage, read',
|
|
149
154
|
};
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
155
|
+
/**
|
|
156
|
+
* The block depth that we can expect when generating code in the function scope, not in any nested blocks.
|
|
157
|
+
*/
|
|
158
|
+
const functionInitialBlockDepth = 2;
|
|
159
|
+
export class WgslGenerator {
|
|
160
|
+
#ctx = undefined;
|
|
161
|
+
// used to detect `continue` and `break` nodes in loop body, as well as label
|
|
162
|
+
// unrolled blocks with comments
|
|
163
|
+
#unrollingChain = [];
|
|
164
|
+
static {
|
|
165
|
+
WgslGenerator.prototype.languageKey = 'wgsl';
|
|
166
|
+
}
|
|
167
|
+
initGenerator(ctx) {
|
|
168
|
+
if (this.#ctx !== undefined) {
|
|
169
|
+
throw new Error(`Cannot initialize shader generators twice. Create one generator per resolution.`);
|
|
170
|
+
}
|
|
171
|
+
this.#ctx = ctx;
|
|
172
|
+
}
|
|
173
|
+
get ctx() {
|
|
174
|
+
if (!this.#ctx) {
|
|
175
|
+
throw new Error('WGSL Generator has not yet been initialized. Please call initialize(ctx) before using the generator.');
|
|
176
|
+
}
|
|
177
|
+
return this.#ctx;
|
|
178
|
+
}
|
|
179
|
+
_block([_, statementNodes], allowInlining, externalMap) {
|
|
180
|
+
this.ctx.pushBlockScope();
|
|
181
|
+
try {
|
|
182
|
+
if (externalMap) {
|
|
183
|
+
const externals = Object.fromEntries(Object.entries(externalMap).map(([id, value]) => [id, coerceToSnippet(value)]));
|
|
184
|
+
this.ctx.setBlockExternals(externals);
|
|
185
|
+
}
|
|
186
|
+
let body = '';
|
|
187
|
+
/**
|
|
188
|
+
* True if any of the statements in the block define variables that would
|
|
189
|
+
* be scoped to the currently generated block. If not, we can safely inline it.
|
|
190
|
+
*/
|
|
191
|
+
let definesInNearestScope = false;
|
|
192
|
+
let endsWithControlFlow;
|
|
193
|
+
this.ctx.indent();
|
|
194
|
+
for (const statementNode of statementNodes) {
|
|
195
|
+
const statement = this._statement(statementNode);
|
|
196
|
+
if (statement.code.length > 0) {
|
|
197
|
+
body += `${statement.code}\n`;
|
|
198
|
+
}
|
|
199
|
+
definesInNearestScope ||= statement.definesInNearestScope ?? false;
|
|
200
|
+
if (statement.endsWithControlFlow !== undefined) {
|
|
201
|
+
endsWithControlFlow = statement.endsWithControlFlow;
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
this.ctx.dedent();
|
|
206
|
+
const willInline = allowInlining && !definesInNearestScope;
|
|
207
|
+
// Omitting the 'return;' at the end of the statement list if
|
|
208
|
+
// the 'return;' would be placed in the function body outside
|
|
209
|
+
// of any nested block.
|
|
210
|
+
if (this.ctx.blockDepth === functionInitialBlockDepth) {
|
|
211
|
+
body = body.replace(/[ ]*return\s*;\s*$/u, '');
|
|
212
|
+
}
|
|
213
|
+
if (body === '') {
|
|
214
|
+
return { code: '', endsWithControlFlow, definesInNearestScope: false };
|
|
215
|
+
}
|
|
216
|
+
if (willInline) {
|
|
217
|
+
return {
|
|
218
|
+
code: this.ctx.getDedented(body.trim()),
|
|
219
|
+
endsWithControlFlow,
|
|
220
|
+
definesInNearestScope,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
return {
|
|
224
|
+
code: `{\n${body}${this.ctx.pre}}`,
|
|
225
|
+
endsWithControlFlow,
|
|
226
|
+
// all defines will be scoped to the newly generated block
|
|
227
|
+
definesInNearestScope: false,
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
finally {
|
|
231
|
+
this.ctx.popBlockScope();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
_blockStatement(block, externalMap) {
|
|
235
|
+
const { code, ...properties } = this._block(block, /* allowInlining */ true, externalMap);
|
|
236
|
+
if (code === '') {
|
|
237
|
+
return { ...properties, code: '' };
|
|
238
|
+
}
|
|
239
|
+
return { ...properties, code: `${this.ctx.pre}${code}` };
|
|
240
|
+
}
|
|
241
|
+
refVariable(id, dataType) {
|
|
242
|
+
const varName = this.ctx.makeUniqueIdentifier(id, 'block');
|
|
243
|
+
const ptrType = ptrFn(dataType);
|
|
244
|
+
const snippet = snip(new RefOperator(snip(varName, dataType, 'function', false), ptrType), ptrType, 'function', false);
|
|
245
|
+
this.ctx.defineVariable(id, snippet);
|
|
246
|
+
return varName;
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Creates a variable declaration string.
|
|
250
|
+
* `keyword` may be a placeholder filled in later.
|
|
251
|
+
*/
|
|
252
|
+
_emitVarDecl(keyword, name, _dataType, rhsStr) {
|
|
253
|
+
return `${this.ctx.pre}${keyword} ${name} = ${rhsStr};`;
|
|
254
|
+
}
|
|
255
|
+
_identifier(id) {
|
|
256
|
+
if (!id) {
|
|
257
|
+
throw new Error('Cannot resolve an empty identifier');
|
|
258
|
+
}
|
|
259
|
+
if (id === 'undefined') {
|
|
260
|
+
return snip(undefined, wgsl.Void, 'constant', false);
|
|
261
|
+
}
|
|
262
|
+
const res = this.ctx.getById(id);
|
|
263
|
+
if (!res) {
|
|
264
|
+
throw new Error(`Identifier ${id} not found`);
|
|
265
|
+
}
|
|
266
|
+
return res;
|
|
267
|
+
}
|
|
268
|
+
_callShellless(callee, args) {
|
|
269
|
+
const isGeneric = isGenericFn(callee);
|
|
270
|
+
const slotPairs = isGeneric ? (callee[$providing]?.pairs ?? []) : [];
|
|
271
|
+
const callback = isGeneric ? callee[$internal].inner : callee;
|
|
272
|
+
const shelllessCall = this.ctx.withRenamed(callback, getName(callee), () => this.ctx.withSlots(slotPairs, () => {
|
|
273
|
+
const shellless = this.ctx.shelllessRepo.get(callback, args);
|
|
274
|
+
if (!shellless) {
|
|
275
|
+
return undefined;
|
|
276
|
+
}
|
|
277
|
+
const converted = args.map((s, idx) => {
|
|
278
|
+
const argType = shellless.argTypes[idx];
|
|
279
|
+
return tryConvertSnippet(this.ctx, s, argType, /* verbose */ false);
|
|
280
|
+
});
|
|
281
|
+
return this.ctx.withResetIndentLevel(() => {
|
|
282
|
+
const snippet = this.ctx.resolve(shellless);
|
|
283
|
+
return snip(stitch `${snippet.value}(${converted})`, snippet.dataType,
|
|
284
|
+
/* origin */ 'runtime');
|
|
285
|
+
});
|
|
286
|
+
}));
|
|
287
|
+
return shelllessCall;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* A wrapper for `generateExpression` that updates `ctx.expectedType`
|
|
291
|
+
* and tries to convert the result when it does not match the expected type.
|
|
292
|
+
*/
|
|
293
|
+
_typedExpression(expression, expectedType) {
|
|
294
|
+
const prevExpectedType = this.ctx.expectedType;
|
|
295
|
+
this.ctx.expectedType = expectedType;
|
|
296
|
+
try {
|
|
297
|
+
const result = this._expression(expression);
|
|
298
|
+
if (expectedType instanceof AutoStruct) {
|
|
299
|
+
// We provide a certain AutoStruct object to later
|
|
300
|
+
// investigate what props were accessed. No need to
|
|
301
|
+
// convert the result.
|
|
302
|
+
return result;
|
|
303
|
+
}
|
|
304
|
+
return tryConvertSnippet(this.ctx, result, expectedType);
|
|
305
|
+
}
|
|
306
|
+
finally {
|
|
307
|
+
this.ctx.expectedType = prevExpectedType;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
_expression(expression) {
|
|
311
|
+
if (typeof expression === 'string') {
|
|
312
|
+
return this._identifier(expression);
|
|
313
|
+
}
|
|
314
|
+
if (typeof expression === 'boolean') {
|
|
315
|
+
return snip(expression, bool, /* origin */ 'constant', false);
|
|
316
|
+
}
|
|
317
|
+
if (expression[0] === NODE.logicalExpr) {
|
|
318
|
+
const [_, lhs, op, rhs] = expression;
|
|
319
|
+
const lhsExpr = this._expression(lhs);
|
|
320
|
+
// Short Circuit Evaluation
|
|
321
|
+
if (isKnownAtComptime(lhsExpr)) {
|
|
322
|
+
const castToBool = wgsl.isBool(this.ctx.expectedType);
|
|
323
|
+
const evalRhs = op === '&&' ? lhsExpr.value : !lhsExpr.value;
|
|
324
|
+
if (!evalRhs) {
|
|
325
|
+
return castToBool
|
|
326
|
+
? snip(op === '||', bool, 'constant', false)
|
|
327
|
+
: coerceToSnippet(lhsExpr.value);
|
|
328
|
+
}
|
|
329
|
+
const rhsExpr = this._expression(rhs);
|
|
330
|
+
if (isKnownAtComptime(rhsExpr)) {
|
|
331
|
+
const rhsSnippet = coerceToSnippet(rhsExpr.value);
|
|
332
|
+
return castToBool ? tryConvertSnippet(this.ctx, rhsSnippet, bool, false) : rhsSnippet;
|
|
333
|
+
}
|
|
334
|
+
if (rhsExpr.dataType === UnknownData) {
|
|
335
|
+
throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type`);
|
|
336
|
+
}
|
|
337
|
+
// we can skip lhs
|
|
338
|
+
return castToBool ? tryConvertSnippet(this.ctx, rhsExpr, bool, false) : rhsExpr;
|
|
339
|
+
}
|
|
340
|
+
const rhsExpr = this._expression(rhs);
|
|
341
|
+
// they are not known at comptime
|
|
342
|
+
if (lhsExpr.dataType === UnknownData) {
|
|
343
|
+
throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type`);
|
|
344
|
+
}
|
|
345
|
+
if (!isKnownAtComptime(rhsExpr) && rhsExpr.dataType === UnknownData) {
|
|
346
|
+
throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type`);
|
|
347
|
+
}
|
|
348
|
+
if (!wgsl.isBool(lhsExpr.dataType) || !wgsl.isBool(rhsExpr.dataType)) {
|
|
349
|
+
throw new WgslTypeError(`Logical expression '${op}' requires boolean operands. Got '${String(lhsExpr.dataType)}' and '${String(rhsExpr.dataType)}'.`);
|
|
350
|
+
}
|
|
351
|
+
const lhsStr = this.ctx.resolveSnippet(lhsExpr).value;
|
|
352
|
+
const rhsStr = this.ctx.resolveSnippet(rhsExpr).value;
|
|
353
|
+
// hardcoded parentheses - operators not present in `parenthesizedOps`
|
|
354
|
+
return snip(`(${lhsStr} ${op} ${rhsStr})`, bool, 'runtime', lhsExpr.possibleSideEffects || rhsExpr.possibleSideEffects);
|
|
355
|
+
}
|
|
356
|
+
if (expression[0] === NODE.binaryExpr || expression[0] === NODE.assignmentExpr) {
|
|
357
|
+
// Binary/Assignment Expression
|
|
358
|
+
const [exprType, lhs, op, rhs] = expression;
|
|
359
|
+
const lhsExpr = this._expression(lhs);
|
|
360
|
+
const rhsExpr = this._expression(rhs);
|
|
361
|
+
if (rhsExpr.value instanceof RefOperator) {
|
|
362
|
+
throw new WgslTypeError(stitch `Cannot assign a ref to an existing variable '${stringifyNode(lhs)}', define a new variable instead.`);
|
|
363
|
+
}
|
|
364
|
+
if (op === '==') {
|
|
365
|
+
throw new Error('Please use the === operator instead of ==');
|
|
366
|
+
}
|
|
367
|
+
if (op === '!=') {
|
|
368
|
+
throw new Error('Please use the !== operator instead of !=');
|
|
369
|
+
}
|
|
370
|
+
const stdBinaryRelationalOp = binaryRelationalOpToStdMap[op];
|
|
371
|
+
if (stdBinaryRelationalOp && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr)) {
|
|
372
|
+
const left = lhsExpr.value;
|
|
373
|
+
const right = rhsExpr.value;
|
|
374
|
+
switch (op) {
|
|
375
|
+
case '===':
|
|
376
|
+
return snip(left === right, bool, 'constant', false);
|
|
377
|
+
case '!==':
|
|
378
|
+
return snip(left !== right, bool, 'constant', false);
|
|
379
|
+
}
|
|
380
|
+
if (typeof left !== 'number' || typeof right !== 'number') {
|
|
381
|
+
const bothVectors = wgsl.isVec(lhsExpr.dataType) && wgsl.isVec(rhsExpr.dataType);
|
|
382
|
+
throw new WgslTypeError(`Comparison '${op}' requires numeric operands.${bothVectors
|
|
383
|
+
? ` For component-wise comparison, use 'std.${stdBinaryRelationalOp}'.`
|
|
384
|
+
: ''}`);
|
|
385
|
+
}
|
|
386
|
+
switch (op) {
|
|
387
|
+
case '<':
|
|
388
|
+
return snip(left < right, bool, 'constant', false);
|
|
389
|
+
case '<=':
|
|
390
|
+
return snip(left <= right, bool, 'constant', false);
|
|
391
|
+
case '>':
|
|
392
|
+
return snip(left > right, bool, 'constant', false);
|
|
393
|
+
case '>=':
|
|
394
|
+
return snip(left >= right, bool, 'constant', false);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
if (lhsExpr.dataType === UnknownData) {
|
|
398
|
+
throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type`);
|
|
399
|
+
}
|
|
400
|
+
if (rhsExpr.dataType === UnknownData) {
|
|
401
|
+
throw new WgslTypeError(`Right-hand side of '${op}' is of unknown type`);
|
|
402
|
+
}
|
|
403
|
+
const codegen = binaryOpCodeToCodegen[op];
|
|
404
|
+
if (codegen) {
|
|
405
|
+
return codegen(this.ctx, [lhsExpr, rhsExpr]);
|
|
406
|
+
}
|
|
407
|
+
let convLhs;
|
|
408
|
+
let convRhs;
|
|
409
|
+
if (bitShiftOps.includes(op)) {
|
|
410
|
+
const lhsDataType = lhsExpr.dataType;
|
|
411
|
+
if (!wgsl.isInteger(lhsDataType) && !wgsl.isIntegerVec(lhsDataType)) {
|
|
412
|
+
throw new WgslTypeError(`Expression: ${stringifyNode(expression)}\nLeft-hand side of '${op}' must be an integer or vector of integers.\nGot ${this.ctx.resolve(lhsDataType).value}.`);
|
|
413
|
+
}
|
|
414
|
+
const lhsPrimitive = wgsl.isVec(lhsDataType) ? lhsDataType.primitive : lhsDataType;
|
|
415
|
+
if (['>>>', '>>>='].includes(op) && lhsPrimitive.type !== 'u32') {
|
|
416
|
+
throw new WgslTypeError(`Expression: ${stringifyNode(expression)}\nLeft-hand side of '${op}' must be an unsigned integer or vector of unsigned integers.\nGot ${this.ctx.resolve(lhsDataType).value}.\nUse ${op.slice(1)} instead.`);
|
|
417
|
+
}
|
|
418
|
+
if (['>>', '>>='].includes(op) && lhsPrimitive.type === 'u32') {
|
|
419
|
+
logger.warn('deprecated', `\nExpression: ${stringifyNode(expression)}\nUsing u32 or vecN<u32> as left-hand side of ${op} is deprecated.\nUse >${op} instead.`);
|
|
420
|
+
}
|
|
421
|
+
// rhs must be u32 (or vecN<u32> for vector lhs) according to the WGSL spec
|
|
422
|
+
let rhsTarget;
|
|
423
|
+
if (wgsl.isVec(lhsDataType)) {
|
|
424
|
+
const cc = lhsDataType.componentCount;
|
|
425
|
+
rhsTarget = cc === 2 ? vec2u : cc === 3 ? vec3u : vec4u;
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
rhsTarget = u32;
|
|
429
|
+
}
|
|
430
|
+
convRhs = tryConvertSnippet(this.ctx, rhsExpr, rhsTarget, false);
|
|
431
|
+
convLhs = lhsExpr;
|
|
432
|
+
}
|
|
433
|
+
else {
|
|
434
|
+
const forcedType = exprType === NODE.assignmentExpr ? [lhsExpr.dataType] : undefined;
|
|
435
|
+
[convLhs, convRhs] = convertToCommonType(this.ctx, [lhsExpr, rhsExpr], forcedType) ?? [
|
|
436
|
+
lhsExpr,
|
|
437
|
+
rhsExpr,
|
|
438
|
+
];
|
|
439
|
+
}
|
|
440
|
+
const type = operatorToType(convLhs.dataType, op, convRhs.dataType);
|
|
441
|
+
if (exprType === NODE.assignmentExpr) {
|
|
442
|
+
validateSnippetMutation(convLhs, expression);
|
|
443
|
+
this.tryMarkModified(lhs);
|
|
444
|
+
// Compound assignment operators are okay, e.g. +=, -=, *=, /=, ...
|
|
445
|
+
if (op === '=' && isAlias(rhsExpr) && !wgsl.isNaturallyEphemeral(rhsExpr.dataType)) {
|
|
446
|
+
throw new WgslTypeError(`'${stringifyNode(expression)}' is invalid, because references cannot be assigned.\n-----\nTry '${stringifyNode(lhs)} = ${this.ctx.resolve(unptr(rhsExpr.dataType)).value}(${stringifyNode(rhs)})' to copy the value instead.\n-----`);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (stdBinaryRelationalOp) {
|
|
450
|
+
const equalityCheck = ['===', '!=='].includes(op);
|
|
451
|
+
const correctOperandTypes = (wgsl.isNumericSchema(convLhs.dataType) && wgsl.isNumericSchema(convRhs.dataType)) ||
|
|
452
|
+
(equalityCheck && wgsl.isBool(convLhs.dataType) && wgsl.isBool(convRhs.dataType));
|
|
453
|
+
if (!correctOperandTypes) {
|
|
454
|
+
const bothVectors = wgsl.isVec(convLhs.dataType) && wgsl.isVec(convRhs.dataType);
|
|
455
|
+
throw new WgslTypeError(`Comparison '${op}' requires numeric${equalityCheck ? ' or boolean' : ''} operands. Got '${String(convLhs.dataType)}' and '${String(convRhs.dataType)}'.${bothVectors
|
|
456
|
+
? ` For component-wise comparison, use 'std.${stdBinaryRelationalOp}'.`
|
|
457
|
+
: ''}`);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
return snip(this.emitBinaryOp(convLhs, (OP_MAP[op] ?? op), convRhs), type,
|
|
461
|
+
// Result of an operation, so not a reference to anything
|
|
462
|
+
/* origin */ 'runtime', exprType === NODE.assignmentExpr ||
|
|
463
|
+
lhsExpr.possibleSideEffects ||
|
|
464
|
+
rhsExpr.possibleSideEffects);
|
|
465
|
+
}
|
|
466
|
+
if (expression[0] === NODE.postUpdate) {
|
|
467
|
+
throw new Error(`'${stringifyNode(expression)}' is invalid because update is only allowed as a statement.`);
|
|
468
|
+
}
|
|
469
|
+
if (expression[0] === NODE.unaryExpr) {
|
|
470
|
+
// Unary Expression
|
|
471
|
+
const [_, op, arg] = expression;
|
|
472
|
+
const argExpr = this._expression(arg);
|
|
473
|
+
const codegen = unaryOpCodeToCodegen[op];
|
|
474
|
+
if (codegen) {
|
|
475
|
+
return codegen(this.ctx, [argExpr]);
|
|
476
|
+
}
|
|
477
|
+
const argStr = this.ctx.resolveSnippet(argExpr).value;
|
|
478
|
+
const type = operatorToType(argExpr.dataType, op);
|
|
479
|
+
// Result of an operation, so not a reference to anything
|
|
480
|
+
return snip(`${op}${argStr}`, type, /* origin */ 'runtime', argExpr.possibleSideEffects);
|
|
481
|
+
}
|
|
482
|
+
if (expression[0] === NODE.memberAccess) {
|
|
483
|
+
// Member Access
|
|
484
|
+
const [_, targetNode, property] = expression;
|
|
485
|
+
const target = this._expression(targetNode);
|
|
486
|
+
const accessed = accessProp(target, property);
|
|
487
|
+
if (!accessed) {
|
|
488
|
+
throw new Error(`Property '${property}' not found on '${stringifyNode(targetNode)}'`);
|
|
489
|
+
}
|
|
490
|
+
return accessed;
|
|
491
|
+
}
|
|
492
|
+
if (expression[0] === NODE.indexAccess) {
|
|
493
|
+
// Index Access
|
|
494
|
+
const [_, targetNode, propertyNode] = expression;
|
|
495
|
+
const target = this._expression(targetNode);
|
|
496
|
+
const inProperty = this._expression(propertyNode);
|
|
497
|
+
const property = convertToCommonType(this.ctx, [inProperty], [u32, i32], /* verbose */ false)?.[0] ??
|
|
498
|
+
inProperty;
|
|
499
|
+
const accessed = accessIndex(target, property);
|
|
500
|
+
if (!accessed) {
|
|
501
|
+
throw new Error(`Index access '${stringifyNode(expression)}' is invalid. If the value is an array, to address this, consider one of the following approaches: (1) declare the array using 'tgpu.const', (2) store the array in a buffer, or (3) define the array within the GPU function scope.`);
|
|
502
|
+
}
|
|
503
|
+
return accessed;
|
|
504
|
+
}
|
|
505
|
+
if (expression[0] === NODE.numericLiteral) {
|
|
506
|
+
// Numeric Literal
|
|
507
|
+
const type = typeof expression[1] === 'string'
|
|
508
|
+
? numericLiteralToSnippet(parseNumericString(expression[1]))
|
|
509
|
+
: numericLiteralToSnippet(expression[1]);
|
|
510
|
+
invariant(type, `Expected ${stringifyNode(expression)} to be valid numeric literal`);
|
|
511
|
+
return type;
|
|
512
|
+
}
|
|
513
|
+
if (expression[0] === NODE.call) {
|
|
514
|
+
// Function Call
|
|
515
|
+
const [_, calleeNode, argNodes] = expression;
|
|
516
|
+
const _callee = this._expression(calleeNode);
|
|
517
|
+
const callee = mathToStd.has(_callee.value)
|
|
518
|
+
? snip(mathToStd.get(_callee.value), UnknownData, 'runtime', _callee.possibleSideEffects)
|
|
519
|
+
: _callee;
|
|
520
|
+
if (supportedLogOps().includes(callee.value)) {
|
|
521
|
+
return this.ctx.generateLog(callee.value, argNodes.map((arg) => this._expression(arg)));
|
|
522
|
+
}
|
|
523
|
+
if (wgsl.isWgslStruct(callee.value)) {
|
|
524
|
+
// Struct schema call.
|
|
525
|
+
if (argNodes.length > 1) {
|
|
526
|
+
throw new WgslTypeError('Struct schemas should always be called with at most 1 argument');
|
|
527
|
+
}
|
|
528
|
+
// No arguments `Struct()`, resolve struct name and return.
|
|
529
|
+
if (!argNodes[0]) {
|
|
530
|
+
// The schema becomes the data type.
|
|
531
|
+
return snip(`${this.ctx.resolve(callee.value).value}()`, callee.value,
|
|
532
|
+
// A new struct, so not a reference.
|
|
533
|
+
/* origin */ 'runtime', false);
|
|
534
|
+
}
|
|
535
|
+
const arg = this._typedExpression(argNodes[0], callee.value);
|
|
536
|
+
// Either `Struct({ x: 1, y: 2 })`, or `Struct(otherStruct)`.
|
|
537
|
+
// In both cases, we just let the argument resolve everything.
|
|
538
|
+
return snip(this.ctx.resolveSnippet(arg).value, callee.value,
|
|
539
|
+
// A new struct, so not a reference.
|
|
540
|
+
/* origin */ 'runtime', arg.possibleSideEffects);
|
|
541
|
+
}
|
|
542
|
+
if (wgsl.isWgslArray(callee.value)) {
|
|
543
|
+
// Array schema call.
|
|
544
|
+
if (argNodes.length > 1) {
|
|
545
|
+
throw new WgslTypeError('Array schemas should always be called with at most 1 argument');
|
|
546
|
+
}
|
|
547
|
+
// No arguments `array<...>()`, resolve array type and return.
|
|
548
|
+
if (!argNodes[0]) {
|
|
549
|
+
// The schema becomes the data type.
|
|
550
|
+
return this.typeInstantiation(callee.value, []);
|
|
551
|
+
}
|
|
552
|
+
const arg = this._typedExpression(argNodes[0], callee.value);
|
|
553
|
+
// `d.arrayOf(...)([...])`.
|
|
554
|
+
// We don't resolve the ArrayExpression object itself to
|
|
555
|
+
// avoid reference checks (we're copying so it's fine)
|
|
556
|
+
if (arg.value instanceof ArrayExpression) {
|
|
557
|
+
return this.typeInstantiation(callee.value, arg.value.elements);
|
|
558
|
+
}
|
|
559
|
+
// `d.arrayOf(...)(otherArr)`.
|
|
560
|
+
// We just let the argument resolve everything.
|
|
561
|
+
return snip(this.ctx.resolveSnippet(arg).value, callee.value,
|
|
562
|
+
// A new array, so not a reference.
|
|
563
|
+
/* origin */ 'runtime', arg.possibleSideEffects);
|
|
564
|
+
}
|
|
565
|
+
if (callee.value === constant) {
|
|
566
|
+
throw new Error('Constants cannot be defined within TypeGPU function scope. To address this, move the constant definition outside the function scope.');
|
|
567
|
+
}
|
|
568
|
+
if (isInfixDispatch(callee.value)) {
|
|
569
|
+
if (!argNodes[0]) {
|
|
570
|
+
throw new WgslTypeError(`An infix operator '${getName(callee.value.operator)}' was called without any arguments`);
|
|
571
|
+
}
|
|
572
|
+
const lhs = coerceToSnippet(callee.value.lhs);
|
|
573
|
+
const rhs = this._expression(argNodes[0]);
|
|
574
|
+
const callable = callee.value.operator[$gpuCallable];
|
|
575
|
+
return callable.call(this.ctx, [lhs, rhs]);
|
|
576
|
+
}
|
|
577
|
+
if ((callee.value === _ref || callee.value === unroll) && argNodes[0]) {
|
|
578
|
+
this.tryMarkModified(argNodes[0]);
|
|
579
|
+
}
|
|
580
|
+
if (isGPUCallable(callee.value)) {
|
|
581
|
+
const callable = callee.value[$gpuCallable];
|
|
582
|
+
const strictSignature = callable.strictSignature;
|
|
583
|
+
let convertedArguments;
|
|
584
|
+
if (strictSignature) {
|
|
585
|
+
// The function's signature does not depend on the context, so it can be used to
|
|
586
|
+
// give a hint to the argument expressions that a specific type is expected.
|
|
587
|
+
convertedArguments = argNodes.map((arg, i) => {
|
|
588
|
+
const argType = strictSignature.argTypes[i];
|
|
589
|
+
if (!argType) {
|
|
590
|
+
throw new WgslTypeError(`Call '${stringifyNode(expression)}' is invalid since the function expected fewer arguments`);
|
|
591
|
+
}
|
|
592
|
+
return this._typedExpression(arg, argType);
|
|
593
|
+
});
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
convertedArguments = argNodes.map((arg) => this._expression(arg));
|
|
597
|
+
}
|
|
598
|
+
try {
|
|
599
|
+
return callable.call(this.ctx, convertedArguments);
|
|
600
|
+
}
|
|
601
|
+
catch (err) {
|
|
602
|
+
if (err instanceof ResolutionError) {
|
|
603
|
+
throw err;
|
|
604
|
+
}
|
|
605
|
+
throw new ResolutionError(err, [
|
|
606
|
+
{
|
|
607
|
+
toString: () => `fn:${getName(callee.value)}`,
|
|
608
|
+
},
|
|
609
|
+
]);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
if (!isMarkedInternal(callee.value) || isGenericFn(callee.value)) {
|
|
613
|
+
const args = argNodes.map((arg) => this._expression(arg));
|
|
614
|
+
const result = this._callShellless(callee.value, args);
|
|
615
|
+
if (result) {
|
|
616
|
+
return result;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
// try to throw a descriptive error
|
|
620
|
+
const maybeMathMethod = Object.getOwnPropertyNames(Math).find((prop) => Math[prop] === callee.value);
|
|
621
|
+
if (maybeMathMethod) {
|
|
622
|
+
throw new Error(`Unsupported Math functionality 'Math.${maybeMathMethod}()'. Use an std alternative, or implement the function manually.`);
|
|
623
|
+
}
|
|
624
|
+
const maybeConsoleMethod = Object.getOwnPropertyNames(console).find((prop) => console[prop] === callee.value);
|
|
625
|
+
if (maybeConsoleMethod) {
|
|
626
|
+
throw new Error(`Unsupported console functionality 'console.${maybeConsoleMethod}()'.`);
|
|
627
|
+
}
|
|
628
|
+
throw new Error(`Function '${getName(callee.value) ?? String(callee.value)}' is not marked with the 'use gpu' directive and cannot be used in a shader`);
|
|
629
|
+
}
|
|
630
|
+
if (expression[0] === NODE.objectExpr) {
|
|
631
|
+
// Object Literal
|
|
632
|
+
const obj = expression[1];
|
|
633
|
+
const structType = this.ctx.expectedType;
|
|
634
|
+
if (structType instanceof AutoStruct) {
|
|
635
|
+
const entries = Object.fromEntries(Object.entries(obj).map(([key, value]) => {
|
|
636
|
+
let accessed = structType.accessProp(key);
|
|
637
|
+
let expr;
|
|
638
|
+
if (accessed) {
|
|
639
|
+
// Generating the expression expecting a specific type
|
|
640
|
+
expr = this._typedExpression(value, accessed.type);
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
// Generating the expression and inferring the type instead
|
|
644
|
+
expr = this._expression(value);
|
|
645
|
+
if (expr.dataType === UnknownData) {
|
|
646
|
+
throw new WgslTypeError(stitch `Property ${key} in object literal has a value of unknown type: '${expr}'`);
|
|
647
|
+
}
|
|
648
|
+
// Taking care of abstract numerics and implicit pointers
|
|
649
|
+
accessed = structType.provideProp(key, unptr(concretize(expr.dataType)));
|
|
650
|
+
}
|
|
651
|
+
return [accessed.prop, expr];
|
|
652
|
+
}));
|
|
653
|
+
const completeStruct = structType.completeStruct;
|
|
654
|
+
const convertedSnippets = convertStructValues(this.ctx, completeStruct, entries);
|
|
655
|
+
return snip(stitch `${this.ctx.resolve(structType).value}(${convertedSnippets})`, completeStruct,
|
|
656
|
+
/* origin */ 'runtime');
|
|
657
|
+
}
|
|
658
|
+
if (wgsl.isWgslStruct(structType)) {
|
|
659
|
+
const entries = Object.fromEntries(Object.entries(structType.propTypes).map(([key, value]) => {
|
|
660
|
+
const val = obj[key];
|
|
661
|
+
if (val === undefined) {
|
|
662
|
+
throw new WgslTypeError(`Missing property ${key} in object literal for struct ${structType}`);
|
|
663
|
+
}
|
|
664
|
+
const result = this._typedExpression(val, value);
|
|
665
|
+
return [key, result];
|
|
666
|
+
}));
|
|
667
|
+
const convertedSnippets = convertStructValues(this.ctx, structType, entries);
|
|
668
|
+
return snip(stitch `${this.ctx.resolve(structType).value}(${convertedSnippets})`, structType,
|
|
669
|
+
/* origin */ 'runtime', convertedSnippets.some((s) => s.possibleSideEffects));
|
|
670
|
+
}
|
|
671
|
+
throw new WgslTypeError(`No target type could be inferred for object '${stringifyNode(expression)}', please wrap the object in the corresponding schema.`);
|
|
672
|
+
}
|
|
673
|
+
if (expression[0] === NODE.arrayExpr) {
|
|
674
|
+
const [_, valueNodes] = expression;
|
|
675
|
+
// Array Expression
|
|
676
|
+
const arrType = this.ctx.expectedType;
|
|
677
|
+
let elemType;
|
|
678
|
+
let values;
|
|
679
|
+
if (wgsl.isWgslArray(arrType)) {
|
|
680
|
+
elemType = arrType.elementType;
|
|
681
|
+
// The array is typed, so its elements should be as well.
|
|
682
|
+
values = valueNodes.map((value) => this._typedExpression(value, elemType));
|
|
683
|
+
// Since it's an expected type, we enforce the length
|
|
684
|
+
if (values.length !== arrType.elementCount) {
|
|
685
|
+
throw new WgslTypeError(`Cannot create value of type '${arrType}' from an array of length: ${values.length}`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
// The array is not typed, so we try to guess the types.
|
|
690
|
+
const valuesSnippets = valueNodes.map((value) => this._expression(value));
|
|
691
|
+
if (valuesSnippets.length === 0) {
|
|
692
|
+
throw new WgslTypeError('Cannot infer the type of an empty array literal.');
|
|
693
|
+
}
|
|
694
|
+
const converted = convertToCommonType(this.ctx, valuesSnippets);
|
|
695
|
+
if (!converted) {
|
|
696
|
+
throw new WgslTypeError(`Values '${stringifyNode(expression)}' cannot be automatically converted to a common type. Consider wrapping the array in an appropriate schema`);
|
|
697
|
+
}
|
|
698
|
+
values = converted;
|
|
699
|
+
elemType = concretize(values[0]?.dataType);
|
|
700
|
+
}
|
|
701
|
+
const arrayType = arrayOf(elemType, values.length);
|
|
702
|
+
return snip(new ArrayExpression(arrayType, values), arrayType,
|
|
703
|
+
/* origin */ 'runtime', values.some((v) => v.possibleSideEffects));
|
|
704
|
+
}
|
|
705
|
+
if (expression[0] === NODE.conditionalExpr) {
|
|
706
|
+
// ternary operator
|
|
707
|
+
const [_, testNode, consequentNode, alternativeNode] = expression;
|
|
708
|
+
const test = this._expression(testNode);
|
|
709
|
+
if (isKnownAtComptime(test)) {
|
|
710
|
+
return test.value ? this._expression(consequentNode) : this._expression(alternativeNode);
|
|
711
|
+
}
|
|
712
|
+
else {
|
|
713
|
+
const convertedTest = tryConvertSnippet(this.ctx, test, bool, false);
|
|
714
|
+
const consequent = this._expression(consequentNode);
|
|
715
|
+
const alternative = this._expression(alternativeNode);
|
|
716
|
+
const [con, alt] = convertToCommonType(this.ctx, [consequent, alternative], validSelectBranchTypes) ?? [];
|
|
717
|
+
if (!con ||
|
|
718
|
+
!alt ||
|
|
719
|
+
consequent.possibleSideEffects ||
|
|
720
|
+
alternative.possibleSideEffects ||
|
|
721
|
+
(isAlias(consequent) && !wgsl.isNaturallyEphemeral(consequent.dataType)) ||
|
|
722
|
+
(isAlias(alternative) && !wgsl.isNaturallyEphemeral(alternative.dataType))) {
|
|
723
|
+
throw new Error(`Ternary operator '${stringifyNode(expression)}' is invalid. For more complex branching, please use 'std.select' or if/else statements.`);
|
|
724
|
+
}
|
|
725
|
+
return snip(stitch `select(${alt}, ${con}, ${convertedTest})`, con.dataType, 'runtime',
|
|
726
|
+
// this select has side-effects only if the condition has side-effects
|
|
727
|
+
test.possibleSideEffects);
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
if (expression[0] === NODE.stringLiteral) {
|
|
731
|
+
return snip(expression[1], UnknownData, /* origin */ 'constant', false);
|
|
732
|
+
}
|
|
733
|
+
if (expression[0] === NODE.preUpdate) {
|
|
734
|
+
throw new Error('Cannot use pre-updates in TypeGPU functions.');
|
|
735
|
+
}
|
|
736
|
+
assertExhaustive(expression);
|
|
737
|
+
}
|
|
738
|
+
declareGlobalConst(options) {
|
|
739
|
+
const resolvedDataType = this.ctx.resolve(options.dataType).value;
|
|
740
|
+
const resolvedValue = this.ctx.resolveSnippet(options.init).value;
|
|
741
|
+
this.ctx.addDeclaration(`const ${options.id}: ${resolvedDataType} = ${resolvedValue};`, options.id);
|
|
742
|
+
return snip(options.id, options.dataType, 'constant-immutable-def');
|
|
743
|
+
}
|
|
744
|
+
declareGlobalVar(options) {
|
|
745
|
+
let pre = '';
|
|
746
|
+
if (options.group !== undefined) {
|
|
747
|
+
pre += `@group(${options.group}) `;
|
|
748
|
+
}
|
|
749
|
+
if (options.binding !== undefined) {
|
|
750
|
+
pre += `@binding(${options.binding}) `;
|
|
751
|
+
}
|
|
752
|
+
if (options.scope in usageToVarTemplateMap) {
|
|
753
|
+
pre += `var<${usageToVarTemplateMap[options.scope]}> `;
|
|
754
|
+
}
|
|
755
|
+
else {
|
|
756
|
+
pre += `var `;
|
|
757
|
+
}
|
|
758
|
+
pre += `${options.id}: ${this.ctx.resolve(options.dataType).value}`;
|
|
759
|
+
this.ctx.addDeclaration(options.init ? `${pre} = ${this.ctx.resolveSnippet(options.init).value};` : `${pre};`, options.id);
|
|
760
|
+
return snip(options.id, options.dataType, options.scope);
|
|
761
|
+
}
|
|
762
|
+
functionDefinition(options) {
|
|
763
|
+
// Function body
|
|
764
|
+
invariant(this.ctx.blockDepth === functionInitialBlockDepth - 1, `Expecting exactly ${functionInitialBlockDepth - 1} block(s) before going into the first function block scope`);
|
|
765
|
+
let body = this._block(options.body, /* allowInlining */ false);
|
|
766
|
+
const scope = this.ctx.topFunctionScope;
|
|
767
|
+
invariant(scope, 'Expected function scope to be present');
|
|
768
|
+
const replacements = Object.fromEntries([...scope.placeholderForVariable.entries()].map(([variable, placeholder]) => [
|
|
769
|
+
placeholder,
|
|
770
|
+
scope.modifiedVariables.has(variable) ? 'var' : 'let',
|
|
771
|
+
]));
|
|
772
|
+
if (Object.keys(replacements).length > 0) {
|
|
773
|
+
const regex = new RegExp(Object.keys(replacements).join('|'), 'gi');
|
|
774
|
+
body.code = body.code.replace(regex, (match) => replacements[match] ?? '#ERR');
|
|
775
|
+
}
|
|
776
|
+
// Only after generating the body can we determine the return type
|
|
777
|
+
const returnType = options.determineReturnType();
|
|
778
|
+
const argList = options.args
|
|
779
|
+
// Stripping out unused arguments in entry functions
|
|
780
|
+
.filter((arg) => arg.used || options.functionType === 'normal')
|
|
781
|
+
.map((arg) => {
|
|
782
|
+
return `${getAttributesString(arg.decoratedType)}${arg.name}: ${this.ctx.resolve(arg.decoratedType).value}`;
|
|
783
|
+
})
|
|
784
|
+
.join(', ');
|
|
785
|
+
const head = returnType.type !== 'void'
|
|
786
|
+
? `(${argList}) -> ${getAttributesString(returnType)}${this.ctx.resolve(returnType).value} `
|
|
787
|
+
: `(${argList}) `;
|
|
788
|
+
let attributes = '';
|
|
789
|
+
if (options.functionType === 'compute') {
|
|
790
|
+
if (!options.workgroupSize) {
|
|
791
|
+
throw new Error('Compute shaders must have a workgroup size');
|
|
792
|
+
}
|
|
793
|
+
attributes = `@compute @workgroup_size(${options.workgroupSize.join(', ')}) `;
|
|
794
|
+
}
|
|
795
|
+
else if (options.functionType === 'vertex') {
|
|
796
|
+
attributes = `@vertex `;
|
|
797
|
+
}
|
|
798
|
+
else if (options.functionType === 'fragment') {
|
|
799
|
+
attributes = `@fragment `;
|
|
800
|
+
}
|
|
801
|
+
return `${attributes}fn ${options.name}${head}${body.code || '{}'}`;
|
|
802
|
+
}
|
|
803
|
+
/**
|
|
804
|
+
* Generates a WGSL type string for the given data type, and adds necessary
|
|
805
|
+
* definitions to the shader preamble. This shouldn't be called directly, only
|
|
806
|
+
* through `ctx.resolve` to properly cache the result.
|
|
807
|
+
*/
|
|
808
|
+
emitTypeAnnotation(data) {
|
|
809
|
+
return resolveData(this.ctx, data);
|
|
810
|
+
}
|
|
811
|
+
typeInstantiation(schema, args) {
|
|
812
|
+
if (args.length === 1 && args[0]?.dataType === schema) {
|
|
813
|
+
// Already of the desired type, e.g. `bool(false)` or `vec3f(vec3f(1, 2, 3))`
|
|
814
|
+
// We can make this snippet ephemeral, as we know it will be deep copied in JS
|
|
815
|
+
return snip(stitch `${args[0]}`, schema, fallthroughCopyOrigin(args[0].origin), args[0].possibleSideEffects);
|
|
816
|
+
}
|
|
817
|
+
// Creating a 'runtime' snippet, since it's instantiating a new value
|
|
818
|
+
return snip(stitch `${this.ctx.resolve(schema).value}(${args})`, schema, 'runtime', args.some((s) => s.possibleSideEffects));
|
|
819
|
+
}
|
|
820
|
+
numericLiteral(value, schema) {
|
|
821
|
+
if (!Number.isFinite(value)) {
|
|
822
|
+
throw new Error(`Value '${value}' (${schema.type}) cannot be resolved due to WGSL's Finite Math Assumption (see: https://www.w3.org/TR/WGSL/#finite-math-assumption). This value might be a result of a comptime-evaluated operation.`);
|
|
823
|
+
}
|
|
824
|
+
if (schema.type === 'abstractInt') {
|
|
825
|
+
return snip(`${value}`, schema, /* origin */ 'constant', false);
|
|
826
|
+
}
|
|
827
|
+
if (schema.type === 'u32') {
|
|
828
|
+
return snip(`${value}u`, schema, /* origin */ 'constant', false);
|
|
829
|
+
}
|
|
830
|
+
if (schema.type === 'i32') {
|
|
831
|
+
return snip(`${value}i`, schema, /* origin */ 'constant', false);
|
|
832
|
+
}
|
|
833
|
+
const exp = value.toExponential();
|
|
834
|
+
const decimal = schema.type === 'abstractFloat' && Number.isInteger(value) ? `${value}.` : `${value}`;
|
|
835
|
+
// Just picking the shorter one
|
|
836
|
+
const base = exp.length < decimal.length ? exp : decimal;
|
|
837
|
+
if (schema.type === 'f32') {
|
|
838
|
+
return snip(`${base}f`, schema, /* origin */ 'constant', false);
|
|
839
|
+
}
|
|
840
|
+
if (schema.type === 'f16') {
|
|
841
|
+
return snip(`${base}h`, schema, /* origin */ 'constant', false);
|
|
842
|
+
}
|
|
843
|
+
return snip(base, schema, /* origin */ 'constant', false);
|
|
844
|
+
}
|
|
845
|
+
emitCall(name, templateParams, args) {
|
|
846
|
+
const resolvedTemplateParams = templateParams
|
|
847
|
+
.map((arg) => this.ctx.resolveSnippet(arg).value)
|
|
848
|
+
.join(', ');
|
|
849
|
+
const resolvedArgs = args.map((arg) => this.ctx.resolveSnippet(arg).value).join(', ');
|
|
850
|
+
if (resolvedTemplateParams.length > 0) {
|
|
851
|
+
return `${name}<${resolvedTemplateParams}>(${resolvedArgs})`;
|
|
852
|
+
}
|
|
853
|
+
return `${name}(${resolvedArgs})`;
|
|
854
|
+
}
|
|
855
|
+
emitBinaryOp(lhs, op, rhs) {
|
|
856
|
+
const lhsStr = this.ctx.resolveSnippet(lhs).value;
|
|
857
|
+
const rhsStr = this.ctx.resolveSnippet(rhs).value;
|
|
858
|
+
return parenthesizedOps.includes(op)
|
|
859
|
+
? `(${lhsStr} ${op} ${rhsStr})`
|
|
860
|
+
: `${lhsStr} ${op} ${rhsStr}`;
|
|
861
|
+
}
|
|
862
|
+
_return(statement) {
|
|
863
|
+
const returnNode = statement[1];
|
|
864
|
+
if (returnNode !== undefined) {
|
|
865
|
+
const expectedReturnType = this.ctx.topFunctionReturnType;
|
|
866
|
+
let returnSnippet = expectedReturnType
|
|
867
|
+
? this._typedExpression(returnNode, expectedReturnType)
|
|
868
|
+
: this._expression(returnNode);
|
|
869
|
+
if (returnSnippet.value === undefined && wgsl.isVoid(returnSnippet.dataType)) {
|
|
870
|
+
this.ctx.reportReturnType(wgsl.Void);
|
|
871
|
+
return `${this.ctx.pre}return;`;
|
|
872
|
+
}
|
|
873
|
+
if (returnSnippet.value instanceof RefOperator) {
|
|
874
|
+
throw new WgslTypeError(`Cannot return '${stringifyNode(returnNode)}' because it is a d.ref`);
|
|
875
|
+
}
|
|
876
|
+
// Arguments cannot be returned from functions without copying. A simple example why is:
|
|
877
|
+
// const identity = (x) => {
|
|
878
|
+
// 'use gpu';
|
|
879
|
+
// return x;
|
|
880
|
+
// };
|
|
881
|
+
//
|
|
882
|
+
// const foo = (arg: d.v3f) => {
|
|
883
|
+
// 'use gpu';
|
|
884
|
+
// const marg = identity(arg);
|
|
885
|
+
// marg.x = 1; // 'marg's origin would be 'runtime', so we wouldn't be able to track this misuse.
|
|
886
|
+
// };
|
|
887
|
+
if (returnSnippet.origin === 'argument' &&
|
|
888
|
+
!wgsl.isNaturallyEphemeral(returnSnippet.dataType) &&
|
|
889
|
+
// Only restricting this use in non-entry functions, as the function
|
|
890
|
+
// is giving up ownership of all references anyway.
|
|
891
|
+
this.ctx.topFunctionScope?.functionType === 'normal') {
|
|
892
|
+
throw new WgslTypeError(`'${stringifyNode(statement)}' is invalid, cannot return references to arguments. Copy the argument before returning it.`);
|
|
893
|
+
}
|
|
894
|
+
if (
|
|
895
|
+
// The existence of `expectedReturnType` implies a function shell, which in turn implies that the
|
|
896
|
+
// value will be copied on return anyway
|
|
897
|
+
!expectedReturnType &&
|
|
898
|
+
isAlias(returnSnippet) &&
|
|
899
|
+
!wgsl.isNaturallyEphemeral(returnSnippet.dataType) &&
|
|
900
|
+
returnSnippet.origin !== 'local-def') {
|
|
901
|
+
const str = stringifyNode(returnNode);
|
|
902
|
+
const typeStr = this.ctx.resolve(unptr(returnSnippet.dataType)).value;
|
|
903
|
+
throw new WgslTypeError(`'return ${str};' is invalid, cannot return references.
|
|
499
904
|
-----
|
|
500
905
|
Try 'return ${typeStr}(${str});' instead.
|
|
501
906
|
-----`);
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
907
|
+
}
|
|
908
|
+
returnSnippet = tryConvertSnippet(this.ctx, returnSnippet, unptr(returnSnippet.dataType), false);
|
|
909
|
+
invariant(returnSnippet.dataType !== UnknownData, 'Return type should be known');
|
|
910
|
+
this.ctx.reportReturnType(returnSnippet.dataType);
|
|
911
|
+
return stitch `${this.ctx.pre}return ${returnSnippet};`;
|
|
912
|
+
}
|
|
913
|
+
this.ctx.reportReturnType(wgsl.Void);
|
|
914
|
+
return `${this.ctx.pre}return;`;
|
|
915
|
+
}
|
|
916
|
+
_letStatement(statement) {
|
|
917
|
+
const [_, rawId, eqNode] = statement;
|
|
918
|
+
if (eqNode === undefined) {
|
|
919
|
+
throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
|
|
920
|
+
}
|
|
921
|
+
const eq = this._expression(eqNode);
|
|
922
|
+
if (eq.value instanceof RefOperator) {
|
|
923
|
+
const rhsStr = stringifyNode(eqNode);
|
|
924
|
+
throw new WgslTypeError(`'let ${rawId} = ${rhsStr}' is invalid, cannot initialize 'let' variables with d.ref()
|
|
517
925
|
-----
|
|
518
|
-
- Try 'const ${rawId} = ${rhsStr
|
|
926
|
+
- Try 'const ${rawId} = ${rhsStr}'.
|
|
519
927
|
-----`);
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
928
|
+
}
|
|
929
|
+
const definitionDataType = eq.dataType;
|
|
930
|
+
if (definitionDataType === UnknownData) {
|
|
931
|
+
const rhsStr = stringifyNode(eqNode);
|
|
932
|
+
throw new WgslTypeError(`'let ${rawId} = ${rhsStr}' is invalid, cannot determine WGSL type of '${rhsStr}'
|
|
525
933
|
-----
|
|
526
|
-
- Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'let ${rawId} = Schema(${rhsStr
|
|
934
|
+
- Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'let ${rawId} = Schema(${rhsStr})'
|
|
527
935
|
-----`);
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
936
|
+
}
|
|
937
|
+
if (isAlias(eq) && !wgsl.isNaturallyEphemeral(eq.dataType)) {
|
|
938
|
+
// `let` declarations cannot store references
|
|
939
|
+
const rhsStr = stringifyNode(eqNode);
|
|
940
|
+
const rhsTypeStr = this.ctx.resolve(unptr(eq.dataType)).value;
|
|
941
|
+
throw new WgslTypeError(`'let ${rawId} = ${rhsStr}' is invalid, because references cannot be assigned to 'let' variable declarations.
|
|
533
942
|
-----
|
|
534
|
-
- Try 'let ${rawId} = ${rhsTypeStr}(${rhsStr
|
|
535
|
-
- Try 'const ${rawId} = ${rhsStr
|
|
943
|
+
- Try 'let ${rawId} = ${rhsTypeStr}(${rhsStr})' if you need to reassign '${rawId}' later
|
|
944
|
+
- Try 'const ${rawId} = ${rhsStr}' if you won't reassign '${rawId}' later.
|
|
536
945
|
-----`);
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
946
|
+
}
|
|
947
|
+
const concreteType = concretize(definitionDataType);
|
|
948
|
+
const snippet = snip(this.ctx.makeUniqueIdentifier(rawId, 'block'), concreteType,
|
|
949
|
+
/* origin */ 'local-def', false);
|
|
950
|
+
this.ctx.defineVariable(rawId, snippet);
|
|
951
|
+
const rhsSnippet = tryConvertSnippet(this.ctx, eq, definitionDataType, false);
|
|
952
|
+
const rhsStr = this.ctx.resolveSnippet(rhsSnippet).value;
|
|
953
|
+
// Even though the user defined a 'let' (expecting it to be reassigned), the
|
|
954
|
+
// reassignment might happen in a pruned branch, in which case we can generate
|
|
955
|
+
// more optimised code by emitting 'let' or 'const' instead of 'var'.
|
|
956
|
+
const scope = this.ctx.topFunctionScope;
|
|
957
|
+
invariant(scope, `Expected function scope to be present for ${rawId}`);
|
|
958
|
+
const emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`;
|
|
959
|
+
scope.placeholderForVariable.set(snippet, emittedVarType);
|
|
960
|
+
return {
|
|
961
|
+
code: this._emitVarDecl(emittedVarType, snippet.value, concreteType, rhsStr),
|
|
962
|
+
definesInNearestScope: true,
|
|
963
|
+
};
|
|
964
|
+
}
|
|
965
|
+
_constStatement(statement) {
|
|
966
|
+
const [_, rawId, eqNode] = statement;
|
|
967
|
+
if (eqNode === undefined) {
|
|
968
|
+
throw new Error(`'${stringifyNode(statement)}' is invalid because all variables need initializers.`);
|
|
969
|
+
}
|
|
970
|
+
const eq = this._expression(eqNode);
|
|
971
|
+
if (eq.value instanceof RefOperator) {
|
|
972
|
+
// We're assigning a newly created `d.ref()`
|
|
973
|
+
if (eq.dataType !== UnknownData) {
|
|
974
|
+
throw new WgslTypeError(`Cannot store d.ref() in a variable if it references another value. Copy the value passed into d.ref() instead.`);
|
|
975
|
+
}
|
|
976
|
+
const refSnippet = eq.value.snippet;
|
|
977
|
+
const varName = this.refVariable(rawId, concretize(refSnippet.dataType));
|
|
978
|
+
return {
|
|
979
|
+
code: stitch `${this.ctx.pre}var ${varName} = ${tryConvertSnippet(this.ctx, refSnippet, refSnippet.dataType, false)};`,
|
|
980
|
+
definesInNearestScope: true,
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
const rhsNaturallyEphemeral = wgsl.isNaturallyEphemeral(eq.dataType);
|
|
984
|
+
let varOrigin = 'local-def';
|
|
985
|
+
let varType = '<deferred>';
|
|
986
|
+
let definitionDataType = eq.dataType;
|
|
987
|
+
if (definitionDataType === UnknownData) {
|
|
988
|
+
const rhsStr = stringifyNode(eqNode);
|
|
989
|
+
throw new WgslTypeError(`'const ${rawId} = ${rhsStr}' is invalid, cannot determine WGSL type of '${rhsStr}'
|
|
566
990
|
-----
|
|
567
|
-
- Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'const ${rawId} = Schema(${rhsStr
|
|
991
|
+
- Try using or defining a schema that matches your desired value the most, and wrap the value with it: 'const ${rawId} = Schema(${rhsStr})'
|
|
568
992
|
-----`);
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
993
|
+
}
|
|
994
|
+
if (eq.origin === 'argument') {
|
|
995
|
+
// Arguments are immutable, so we 'let' them be (kill me)
|
|
996
|
+
varType = 'let';
|
|
997
|
+
// When we declare a new variable with a naturally ephemeral value (e.g. a scalar)
|
|
998
|
+
// the variable now loses the restrictions of an argument, and becomes just a regular
|
|
999
|
+
// variable. For vectors and other non-naturally ephemeral values, the restrictions of
|
|
1000
|
+
// arguments are kept.
|
|
1001
|
+
varOrigin = rhsNaturallyEphemeral ? 'local-def' : 'argument';
|
|
1002
|
+
}
|
|
1003
|
+
else if (eq.origin === 'constant-immutable-def') {
|
|
1004
|
+
varType = 'const';
|
|
1005
|
+
varOrigin = 'constant-immutable-def';
|
|
1006
|
+
}
|
|
1007
|
+
else if (eq.origin === 'runtime-immutable-def') {
|
|
1008
|
+
varType = 'let';
|
|
1009
|
+
varOrigin = 'runtime-immutable-def';
|
|
1010
|
+
}
|
|
1011
|
+
else if (rhsNaturallyEphemeral) {
|
|
1012
|
+
varType = eq.origin === 'constant' ? 'const' : 'let';
|
|
1013
|
+
// Constants are also local declarations. We lose some information here, meaning
|
|
1014
|
+
// when we look at a variable's snippet, we cannot tell if it's a constant or not.
|
|
1015
|
+
// This is mostly because we plan to determine this fact later, after all of the
|
|
1016
|
+
// function code has been processed, so at least currently, we lose that info.
|
|
1017
|
+
varOrigin = 'local-def';
|
|
1018
|
+
}
|
|
1019
|
+
else if (!isAlias(eq)) {
|
|
1020
|
+
// Not a reference, but also not naturally ephemeral, so we cannot guarantee it won't be mutated.
|
|
1021
|
+
// We defer the decision for now.
|
|
1022
|
+
varType = '<deferred>';
|
|
1023
|
+
varOrigin = 'local-def';
|
|
1024
|
+
}
|
|
1025
|
+
else {
|
|
1026
|
+
return this._aliasConstStatement(rawId, eqNode, eq);
|
|
1027
|
+
}
|
|
1028
|
+
const concreteType = concretize(definitionDataType);
|
|
1029
|
+
const snippet = snip(this.ctx.makeUniqueIdentifier(rawId, 'block'), concreteType,
|
|
1030
|
+
/* origin */ varOrigin, false);
|
|
1031
|
+
this.ctx.defineVariable(rawId, snippet);
|
|
1032
|
+
const rhsSnippet = tryConvertSnippet(this.ctx, eq, definitionDataType, false);
|
|
1033
|
+
const rhsStr = this.ctx.resolveSnippet(rhsSnippet).value;
|
|
1034
|
+
let emittedVarType;
|
|
1035
|
+
if (varType === '<deferred>') {
|
|
1036
|
+
const scope = this.ctx.topFunctionScope;
|
|
1037
|
+
invariant(scope, `Expected function scope to be present for ${rawId}`);
|
|
1038
|
+
emittedVarType = `#VAR_${scope.placeholderForVariable.size}#`;
|
|
1039
|
+
scope.placeholderForVariable.set(snippet, emittedVarType);
|
|
1040
|
+
}
|
|
1041
|
+
else {
|
|
1042
|
+
emittedVarType = varType;
|
|
1043
|
+
}
|
|
1044
|
+
return {
|
|
1045
|
+
code: this._emitVarDecl(emittedVarType, snippet.value, concreteType, rhsStr),
|
|
1046
|
+
definesInNearestScope: true,
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Handles `const x = <rhs>;` declarations in which the right-hand side aliases memory
|
|
1051
|
+
* that outlives the expression (a buffer, a local variable, an array element, ...).
|
|
1052
|
+
*
|
|
1053
|
+
* In WGSL we store an *implicit* pointer to that memory, so mutations done through `x`
|
|
1054
|
+
* affect the original. Languages without pointers (e.g. GLSL) override this.
|
|
1055
|
+
*/
|
|
1056
|
+
_aliasConstStatement(rawId, eqNode, eq) {
|
|
1057
|
+
// Assigning a reference to a `const` variable means we store the pointer
|
|
1058
|
+
// of the rhs.
|
|
1059
|
+
let definitionDataType = eq.dataType;
|
|
1060
|
+
if (!wgsl.isPtr(definitionDataType)) {
|
|
1061
|
+
const ptrType = createPtrFromOrigin(eq.origin, concretize(definitionDataType));
|
|
1062
|
+
invariant(ptrType !== undefined, `Creating pointer type from origin ${eq.origin}`);
|
|
1063
|
+
definitionDataType = ptrType;
|
|
1064
|
+
}
|
|
1065
|
+
// Making the pointer implicit, meaning the fact it's a pointer isn't
|
|
1066
|
+
// reflected in the JS source code.
|
|
1067
|
+
definitionDataType = implicitFrom(definitionDataType);
|
|
1068
|
+
this.tryMarkModified(eqNode);
|
|
1069
|
+
const concreteType = concretize(definitionDataType);
|
|
1070
|
+
const snippet = snip(this.ctx.makeUniqueIdentifier(rawId, 'block'), concreteType,
|
|
1071
|
+
// we pass on the origin
|
|
1072
|
+
/* origin */ eq.origin, false);
|
|
1073
|
+
this.ctx.defineVariable(rawId, snippet);
|
|
1074
|
+
const rhsSnippet = tryConvertSnippet(this.ctx, eq, definitionDataType, false);
|
|
1075
|
+
const rhsStr = this.ctx.resolveSnippet(rhsSnippet).value;
|
|
1076
|
+
return {
|
|
1077
|
+
code: this._emitVarDecl('let', snippet.value, concreteType, rhsStr),
|
|
1078
|
+
definesInNearestScope: true,
|
|
1079
|
+
};
|
|
1080
|
+
}
|
|
1081
|
+
_statement(statement) {
|
|
1082
|
+
if (typeof statement === 'string') {
|
|
1083
|
+
const id = this._identifier(statement);
|
|
1084
|
+
const resolved = id.value !== undefined && id.value !== null ? this.ctx.resolveSnippet(id).value : '';
|
|
1085
|
+
return { code: resolved ? `${this.ctx.pre}${resolved};` : '', definesInNearestScope: false };
|
|
1086
|
+
}
|
|
1087
|
+
if (typeof statement === 'boolean') {
|
|
1088
|
+
return {
|
|
1089
|
+
code: `${this.ctx.pre}${statement ? 'true' : 'false'};`,
|
|
1090
|
+
definesInNearestScope: false,
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
if (statement[0] === NODE.return) {
|
|
1094
|
+
return {
|
|
1095
|
+
code: this._return(statement),
|
|
1096
|
+
endsWithControlFlow: 'return',
|
|
1097
|
+
definesInNearestScope: false,
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
if (statement[0] === NODE.if) {
|
|
1101
|
+
const [_, condNode, consNode, altNode] = statement;
|
|
1102
|
+
const condition = this._typedExpression(condNode, bool);
|
|
1103
|
+
if (typeof condition.value === 'boolean') {
|
|
1104
|
+
// the condition is known at comptime
|
|
1105
|
+
let node = condition.value ? consNode : altNode;
|
|
1106
|
+
if (node === undefined) {
|
|
1107
|
+
return { code: '', definesInNearestScope: false };
|
|
1108
|
+
}
|
|
1109
|
+
if (!Array.isArray(node)) {
|
|
1110
|
+
node = blockifySingleStatement(node);
|
|
1111
|
+
}
|
|
1112
|
+
if (node[0] === NODE.block && node[1].length === 1 && node[1][0][0] === NODE.if) {
|
|
1113
|
+
// simplify 'if (true) { if (A) {B} } else {C}' to 'if (A) {B}'
|
|
1114
|
+
return this._statement(node[1][0]);
|
|
1115
|
+
}
|
|
1116
|
+
if (node[0] === NODE.if) {
|
|
1117
|
+
// simplify 'if (false) {A} else if (B) {C}' to 'if (B) {C}'
|
|
1118
|
+
return this._statement(node);
|
|
1119
|
+
}
|
|
1120
|
+
// simplify 'if (true) {A} else {B}' to '{A}'
|
|
1121
|
+
return this._blockStatement(blockifySingleStatement(node));
|
|
1122
|
+
}
|
|
1123
|
+
const consequent = this._block(blockifySingleStatement(consNode), /* allowInlining */ false);
|
|
1124
|
+
const alternate = !altNode
|
|
1125
|
+
? undefined
|
|
1126
|
+
: this._block(blockifySingleStatement(altNode), /* allowInlining */ false).code;
|
|
1127
|
+
if (!alternate) {
|
|
1128
|
+
return {
|
|
1129
|
+
code: stitch `${this.ctx.pre}if (${condition}) ${consequent.code || '{}'}`,
|
|
1130
|
+
definesInNearestScope: false,
|
|
1131
|
+
};
|
|
1132
|
+
}
|
|
1133
|
+
return {
|
|
1134
|
+
code: stitch `\
|
|
1135
|
+
${this.ctx.pre}if (${condition}) ${consequent.code || '{}'}
|
|
1136
|
+
${this.ctx.pre}else ${alternate}`,
|
|
1137
|
+
definesInNearestScope: false,
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
1140
|
+
if (statement[0] === NODE.let) {
|
|
1141
|
+
return this._letStatement(statement);
|
|
1142
|
+
}
|
|
1143
|
+
if (statement[0] === NODE.const) {
|
|
1144
|
+
return this._constStatement(statement);
|
|
1145
|
+
}
|
|
1146
|
+
if (statement[0] === NODE.block) {
|
|
1147
|
+
return this._blockStatement(statement);
|
|
1148
|
+
}
|
|
1149
|
+
if (statement[0] === NODE.for) {
|
|
1150
|
+
const [_, init, condition, update, body] = statement;
|
|
1151
|
+
const prevUnrollingChain = this.#unrollingChain;
|
|
1152
|
+
this.#unrollingChain = [];
|
|
1153
|
+
try {
|
|
1154
|
+
this.ctx.pushBlockScope();
|
|
1155
|
+
const [initStatement, conditionExpr, updateStatement] = this.ctx.withResetIndentLevel(() => [
|
|
1156
|
+
init ? this._statement(init).code : undefined,
|
|
1157
|
+
condition ? this._typedExpression(condition, bool) : undefined,
|
|
1158
|
+
update ? this._statement(update).code : undefined,
|
|
1159
|
+
]);
|
|
1160
|
+
const initStr = initStatement ? initStatement.slice(0, -1) : '';
|
|
1161
|
+
const updateStr = updateStatement ? updateStatement.slice(0, -1) : '';
|
|
1162
|
+
const bodyStr = this._block(blockifySingleStatement(body), /* allowInlining */ false).code;
|
|
1163
|
+
return {
|
|
1164
|
+
code: stitch `${this.ctx.pre}for (${initStr}; ${conditionExpr}; ${updateStr}) ${bodyStr || '{}'}`,
|
|
1165
|
+
definesInNearestScope: false,
|
|
1166
|
+
};
|
|
1167
|
+
}
|
|
1168
|
+
finally {
|
|
1169
|
+
this.#unrollingChain = prevUnrollingChain;
|
|
1170
|
+
this.ctx.popBlockScope();
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
if (statement[0] === NODE.while) {
|
|
1174
|
+
const prevUnrollingChain = this.#unrollingChain;
|
|
1175
|
+
this.#unrollingChain = [];
|
|
1176
|
+
try {
|
|
1177
|
+
const [_, condition, body] = statement;
|
|
1178
|
+
const condSnippet = this._typedExpression(condition, bool);
|
|
1179
|
+
const conditionStr = this.ctx.resolveSnippet(condSnippet).value;
|
|
1180
|
+
const bodyStr = this._block(blockifySingleStatement(body), /* allowInlining */ false).code;
|
|
1181
|
+
return {
|
|
1182
|
+
code: `${this.ctx.pre}while (${conditionStr}) ${bodyStr || '{}'}`,
|
|
1183
|
+
definesInNearestScope: false,
|
|
1184
|
+
};
|
|
1185
|
+
}
|
|
1186
|
+
finally {
|
|
1187
|
+
this.#unrollingChain = prevUnrollingChain;
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
if (statement[0] === NODE.forOf) {
|
|
1191
|
+
const [_, loopVar, iterable, body] = statement;
|
|
1192
|
+
if (loopVar[0] !== NODE.const) {
|
|
1193
|
+
throw new WgslTypeError('Only `for (const ... of ... )` loops are supported');
|
|
1194
|
+
}
|
|
1195
|
+
this.tryMarkModified(iterable); // overly-defensive, but let's not tempt fate
|
|
1196
|
+
let ctxIndent = false;
|
|
1197
|
+
const prevUnrollingChain = this.#unrollingChain;
|
|
1198
|
+
try {
|
|
1199
|
+
this.ctx.pushBlockScope();
|
|
1200
|
+
const iterableExpr = this._expression(iterable);
|
|
1201
|
+
const shouldUnroll = iterableExpr.value instanceof UnrollableIterable;
|
|
1202
|
+
const iterableSnippet = shouldUnroll ? iterableExpr.value.snippet : iterableExpr;
|
|
1203
|
+
const range = forOfUtils.getRangeSnippets(this.ctx, iterableSnippet, shouldUnroll);
|
|
1204
|
+
const originalLoopVarName = loopVar[1];
|
|
1205
|
+
const blockified = blockifySingleStatement(body);
|
|
1206
|
+
if (shouldUnroll) {
|
|
1207
|
+
if (!isKnownAtComptime(range.end)) {
|
|
1208
|
+
throw new Error('Cannot unroll loop. Length of iterable is unknown at comptime.');
|
|
1209
|
+
}
|
|
1210
|
+
const length = range.end.value;
|
|
1211
|
+
if (length === 0) {
|
|
1212
|
+
return { code: '', definesInNearestScope: false };
|
|
1213
|
+
}
|
|
1214
|
+
const { value } = iterableSnippet;
|
|
1215
|
+
const elements = isTgpuRange(value)
|
|
1216
|
+
? value.map((i) => coerceToSnippet(i))
|
|
1217
|
+
: value instanceof ArrayExpression
|
|
1218
|
+
? value.elements
|
|
1219
|
+
: Array.from({ length }, (_, i) => forOfUtils.getElementSnippet(iterableSnippet, snip(i, u32, 'constant')));
|
|
1220
|
+
const firstElement = elements[0];
|
|
1221
|
+
if (!isAlias(firstElement) && !wgsl.isNaturallyEphemeral(firstElement.dataType)) {
|
|
1222
|
+
throw new WgslTypeError(`Cannot unroll '${stringifyNode(iterable)}'. The elements of iterable are constructed in place but are not value types.`);
|
|
1223
|
+
}
|
|
1224
|
+
let blocksCode = '';
|
|
1225
|
+
let endsWithControlFlow;
|
|
1226
|
+
let definesInNearestScope = false;
|
|
1227
|
+
for (let i = 0; i < elements.length; i++) {
|
|
1228
|
+
const e = elements[i];
|
|
1229
|
+
this.#unrollingChain = [...prevUnrollingChain, i];
|
|
1230
|
+
const resolvedBlock = this._blockStatement(blockified, {
|
|
1231
|
+
[originalLoopVarName]: e,
|
|
1232
|
+
});
|
|
1233
|
+
definesInNearestScope ||= resolvedBlock.definesInNearestScope;
|
|
1234
|
+
blocksCode += `${this.ctx.pre}// unrolled iteration ${this.#unrollingChain.map((idx) => `#${idx}`).join(' / ')}\n${resolvedBlock.code}\n`;
|
|
1235
|
+
if (resolvedBlock.endsWithControlFlow !== undefined) {
|
|
1236
|
+
endsWithControlFlow = resolvedBlock.endsWithControlFlow;
|
|
1237
|
+
break;
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
return {
|
|
1241
|
+
code: `${blocksCode}${this.ctx.pre}// ---`,
|
|
1242
|
+
endsWithControlFlow,
|
|
1243
|
+
definesInNearestScope,
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
this.#unrollingChain = [];
|
|
1247
|
+
const index = this.ctx.makeUniqueIdentifier('i', 'block');
|
|
1248
|
+
const forHeaderStr = stitch `${this.ctx.pre}for (var ${index} = ${range.start}; ${index} ${range.comparison} ${range.end}; ${index} += ${range.step})`;
|
|
1249
|
+
let bodyStr = '';
|
|
1250
|
+
if (isTgpuRange(iterableSnippet.value)) {
|
|
1251
|
+
bodyStr = this._block(blockified, /* allowInlining */ false, {
|
|
1252
|
+
[originalLoopVarName]: snip(index, range.start.dataType, 'runtime', false), // range.start, .end , .step have the same dataType
|
|
1253
|
+
}).code;
|
|
1254
|
+
}
|
|
1255
|
+
else {
|
|
1256
|
+
this.ctx.indent();
|
|
1257
|
+
ctxIndent = true;
|
|
1258
|
+
const loopVarName = this.ctx.makeUniqueIdentifier(originalLoopVarName, 'block');
|
|
1259
|
+
const elementSnippet = forOfUtils.getElementSnippet(iterableSnippet, snip(index, u32, 'runtime'));
|
|
1260
|
+
const loopVarKind = forOfUtils.getLoopVarKind(elementSnippet);
|
|
1261
|
+
const elementType = forOfUtils.getElementType(elementSnippet, iterableSnippet);
|
|
1262
|
+
const loopVarDeclStr = stitch `${this.ctx.pre}${loopVarKind} ${loopVarName} = ${tryConvertSnippet(this.ctx, elementSnippet, elementType, false)};`;
|
|
1263
|
+
bodyStr = `{\n${loopVarDeclStr}\n${this._blockStatement(blockified, {
|
|
1264
|
+
[originalLoopVarName]: snip(loopVarName, elementType, elementSnippet.origin, false),
|
|
1265
|
+
}).code}\n`;
|
|
1266
|
+
this.ctx.dedent();
|
|
1267
|
+
bodyStr += `${this.ctx.pre}}`;
|
|
1268
|
+
ctxIndent = false;
|
|
1269
|
+
}
|
|
1270
|
+
return {
|
|
1271
|
+
code: stitch `${forHeaderStr} ${bodyStr.trim() || '{}'}`,
|
|
1272
|
+
definesInNearestScope: false,
|
|
1273
|
+
};
|
|
1274
|
+
}
|
|
1275
|
+
finally {
|
|
1276
|
+
if (ctxIndent) {
|
|
1277
|
+
this.ctx.dedent();
|
|
1278
|
+
}
|
|
1279
|
+
this.#unrollingChain = prevUnrollingChain;
|
|
1280
|
+
this.ctx.popBlockScope();
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
if (statement[0] === NODE.postUpdate) {
|
|
1284
|
+
// Post-update statement
|
|
1285
|
+
const [_, op, arg] = statement;
|
|
1286
|
+
const argExpr = this._expression(arg);
|
|
1287
|
+
const argStr = this.ctx.resolveSnippet(argExpr).value;
|
|
1288
|
+
validateSnippetMutation(argExpr, statement);
|
|
1289
|
+
this.tryMarkModified(arg);
|
|
1290
|
+
return { code: `${this.ctx.pre}${argStr}${op};`, definesInNearestScope: false };
|
|
1291
|
+
}
|
|
1292
|
+
if (statement[0] === NODE.continue) {
|
|
1293
|
+
if (this.#unrollingChain.length > 0) {
|
|
1294
|
+
throw new WgslTypeError('Cannot unroll loop containing `continue`');
|
|
1295
|
+
}
|
|
1296
|
+
return {
|
|
1297
|
+
code: `${this.ctx.pre}continue;`,
|
|
1298
|
+
endsWithControlFlow: 'continue',
|
|
1299
|
+
definesInNearestScope: false,
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
if (statement[0] === NODE.break) {
|
|
1303
|
+
if (this.#unrollingChain.length > 0) {
|
|
1304
|
+
throw new WgslTypeError('Cannot unroll loop containing `break`');
|
|
1305
|
+
}
|
|
1306
|
+
return {
|
|
1307
|
+
code: `${this.ctx.pre}break;`,
|
|
1308
|
+
endsWithControlFlow: 'break',
|
|
1309
|
+
definesInNearestScope: false,
|
|
1310
|
+
};
|
|
1311
|
+
}
|
|
1312
|
+
const expr = this._expression(statement);
|
|
1313
|
+
const resolved = expr.value !== undefined && expr.value !== null ? this.ctx.resolveSnippet(expr).value : '';
|
|
1314
|
+
return { code: resolved ? `${this.ctx.pre}${resolved};` : '', definesInNearestScope: false };
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Attempts a member access lookup to mark a variable as modified.
|
|
1318
|
+
* @example
|
|
1319
|
+
* // given `let a; a = 1;`
|
|
1320
|
+
* tryMarkModified('a') // `a` is marked in the function scope
|
|
1321
|
+
*
|
|
1322
|
+
* // given `const obj; obj.prop = 1;`
|
|
1323
|
+
* tryMarkModified('obj.prop') // `obj` is marked in the function scope
|
|
1324
|
+
*
|
|
1325
|
+
* // given `this.buffer.$;`
|
|
1326
|
+
* tryMarkModified('this.buffer.$') // `this` is not marked, since there is no placeholder for it
|
|
1327
|
+
*/
|
|
1328
|
+
tryMarkModified(expr) {
|
|
1329
|
+
if (!expr) {
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
const maybeObject = extractObject(expr);
|
|
1333
|
+
if (maybeObject !== undefined) {
|
|
1334
|
+
const snippet = this.ctx.getById(maybeObject);
|
|
1335
|
+
const scope = this.ctx.topFunctionScope;
|
|
1336
|
+
if (snippet && scope && scope.placeholderForVariable.has(snippet)) {
|
|
1337
|
+
scope.modifiedVariables.add(snippet);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
763
1342
|
function validateSnippetMutation(mutated, expr) {
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1343
|
+
if (mutated.origin === 'constant' ||
|
|
1344
|
+
mutated.origin === 'constant-immutable-def' ||
|
|
1345
|
+
mutated.origin === 'runtime-immutable-def') {
|
|
1346
|
+
if (isKnownAtComptime(mutated)) {
|
|
1347
|
+
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because the left side is defined outside of the shader, and therefore is immutable during its execution. Try using tgpu.privateVar or buffers.`);
|
|
1348
|
+
}
|
|
1349
|
+
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because the left side is a constant.`);
|
|
1350
|
+
}
|
|
1351
|
+
if (mutated.origin === 'uniform') {
|
|
1352
|
+
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because uniform buffers cannot be mutated.`);
|
|
1353
|
+
}
|
|
1354
|
+
if (mutated.origin === 'readonly') {
|
|
1355
|
+
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because readonly buffers cannot be mutated.`);
|
|
1356
|
+
}
|
|
1357
|
+
if (mutated.origin === 'argument') {
|
|
1358
|
+
throw new WgslTypeError(`'${stringifyNode(expr)}' is invalid, because non-pointer arguments cannot be mutated.`);
|
|
1359
|
+
}
|
|
771
1360
|
}
|
|
772
1361
|
function assertExhaustive(value) {
|
|
773
|
-
|
|
1362
|
+
throw new Error(`'${safeStringify(value)}' was not handled by the WGSL generator.`);
|
|
774
1363
|
}
|
|
775
1364
|
function parseNumericString(str) {
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
1365
|
+
// Hex literals
|
|
1366
|
+
if (/^0x[0-9a-f]+$/i.test(str)) {
|
|
1367
|
+
return Number.parseInt(str);
|
|
1368
|
+
}
|
|
1369
|
+
// Binary literals
|
|
1370
|
+
if (/^0b[01]+$/i.test(str)) {
|
|
1371
|
+
return Number.parseInt(str.slice(2), 2);
|
|
1372
|
+
}
|
|
1373
|
+
return Number.parseFloat(str);
|
|
779
1374
|
}
|
|
780
1375
|
function blockifySingleStatement(statement) {
|
|
781
|
-
|
|
1376
|
+
return typeof statement !== 'object' || statement[0] !== NODE.block
|
|
1377
|
+
? [NODE.block, [statement]]
|
|
1378
|
+
: statement;
|
|
782
1379
|
}
|
|
783
1380
|
function extractObject(expr) {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
1381
|
+
let object = expr;
|
|
1382
|
+
while (Array.isArray(object) &&
|
|
1383
|
+
(object[0] === NODE.memberAccess || object[0] === NODE.indexAccess)) {
|
|
1384
|
+
object = object[1];
|
|
1385
|
+
}
|
|
1386
|
+
if (typeof object === 'string') {
|
|
1387
|
+
return object;
|
|
1388
|
+
}
|
|
787
1389
|
}
|
|
788
|
-
const wgslGenerator = new WgslGenerator();
|
|
789
|
-
var wgslGenerator_default = wgslGenerator;
|
|
790
|
-
|
|
791
|
-
//#endregion
|
|
792
|
-
export { WgslGenerator, wgslGenerator_default as default };
|