From e5068dd58affb8aae7ce853e7606e67056fe3feb Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 00:22:14 +0200 Subject: [PATCH 01/59] Tracking referentiality --- .../typegpu/src/core/buffer/bufferUsage.ts | 14 +-- .../typegpu/src/core/constant/tgpuConstant.ts | 6 +- .../typegpu/src/core/declare/tgpuDeclare.ts | 2 +- .../typegpu/src/core/function/dualImpl.ts | 6 +- packages/typegpu/src/core/function/fnCore.ts | 24 +++-- packages/typegpu/src/core/function/tgpuFn.ts | 12 ++- .../src/core/pipeline/computePipeline.ts | 2 +- .../src/core/pipeline/renderPipeline.ts | 2 +- .../typegpu/src/core/resolve/tgpuResolve.ts | 1 + packages/typegpu/src/core/root/init.ts | 8 +- packages/typegpu/src/core/sampler/sampler.ts | 16 ++-- packages/typegpu/src/core/slot/accessor.ts | 30 +++++-- packages/typegpu/src/core/slot/slotTypes.ts | 6 +- .../src/core/texture/externalTexture.ts | 4 +- packages/typegpu/src/core/texture/texture.ts | 18 ++-- packages/typegpu/src/core/valueProxyUtils.ts | 7 +- .../typegpu/src/core/variable/tgpuVariable.ts | 5 +- packages/typegpu/src/data/array.ts | 4 +- packages/typegpu/src/data/disarray.ts | 4 +- packages/typegpu/src/data/matrix.ts | 24 ++++- packages/typegpu/src/data/snippet.ts | 17 +++- packages/typegpu/src/data/vectorImpl.ts | 6 +- packages/typegpu/src/data/wgslTypes.ts | 21 +++++ packages/typegpu/src/resolutionCtx.ts | 44 +++++++--- packages/typegpu/src/std/atomic.ts | 52 ++++++++--- packages/typegpu/src/std/boolean.ts | 2 +- packages/typegpu/src/std/derivative.ts | 22 +++-- packages/typegpu/src/std/discard.ts | 2 +- packages/typegpu/src/std/extensions.ts | 2 +- packages/typegpu/src/std/matrix.ts | 30 +++++-- packages/typegpu/src/std/numeric.ts | 5 +- packages/typegpu/src/std/packing.ts | 8 +- packages/typegpu/src/std/texture.ts | 15 ++-- .../src/tgsl/consoleLog/logGenerator.ts | 8 +- packages/typegpu/src/tgsl/conversion.ts | 20 +++-- .../typegpu/src/tgsl/generationHelpers.ts | 25 ++++-- packages/typegpu/src/tgsl/wgslGenerator.ts | 87 ++++++++++++++----- packages/typegpu/tests/resolve.test.ts | 4 +- .../typegpu/tests/tgsl/conversion.test.ts | 47 +++++----- .../tests/tgsl/generationHelpers.test.ts | 46 +++++----- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 4 +- 41 files changed, 467 insertions(+), 195 deletions(-) diff --git a/packages/typegpu/src/core/buffer/bufferUsage.ts b/packages/typegpu/src/core/buffer/bufferUsage.ts index 4c3578a500..3dc0a2fd76 100644 --- a/packages/typegpu/src/core/buffer/bufferUsage.ts +++ b/packages/typegpu/src/core/buffer/bufferUsage.ts @@ -1,7 +1,11 @@ import type { AnyData } from '../../data/dataTypes.ts'; import { schemaCallWrapper } from '../../data/schemaCallWrapper.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; -import type { AnyWgslData, BaseData } from '../../data/wgslTypes.ts'; +import { + type AnyWgslData, + type BaseData, + isNaturallyRef, +} from '../../data/wgslTypes.ts'; import { IllegalBufferAccessError } from '../../errors.ts'; import { getExecMode, inCodegenMode, isInsideTgpuFn } from '../../execMode.ts'; import { isUsableAsStorage, type StorageFlag } from '../../extension.ts'; @@ -126,7 +130,7 @@ class TgpuFixedBufferImpl< };`, ); - return snip(id, dataType); + return snip(id, dataType, /* ref */ isNaturallyRef(dataType)); } toString(): string { @@ -139,7 +143,7 @@ class TgpuFixedBufferImpl< return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, dataType); + return snip(this, dataType, /* ref */ isNaturallyRef(dataType)); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `${this.usage}:${getName(this) ?? ''}.$`, @@ -246,7 +250,7 @@ export class TgpuLaidOutBufferImpl< };`, ); - return snip(id, dataType); + return snip(id, dataType, /* ref */ isNaturallyRef(dataType)); } toString(): string { @@ -259,7 +263,7 @@ export class TgpuLaidOutBufferImpl< return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, schema); + return snip(this, schema, /* ref */ isNaturallyRef(schema)); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `${this.usage}:${getName(this) ?? ''}.$`, diff --git a/packages/typegpu/src/core/constant/tgpuConstant.ts b/packages/typegpu/src/core/constant/tgpuConstant.ts index 475b3afa08..955a050b3a 100644 --- a/packages/typegpu/src/core/constant/tgpuConstant.ts +++ b/packages/typegpu/src/core/constant/tgpuConstant.ts @@ -1,5 +1,5 @@ import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; -import type { AnyWgslData } from '../../data/wgslTypes.ts'; +import { type AnyWgslData, isNaturallyRef } from '../../data/wgslTypes.ts'; import { inCodegenMode } from '../../execMode.ts'; import type { TgpuNamable } from '../../shared/meta.ts'; import { getName, setName } from '../../shared/meta.ts'; @@ -67,7 +67,7 @@ class TgpuConstImpl ctx.addDeclaration(`const ${id}: ${resolvedDataType} = ${resolvedValue};`); - return snip(id, this.dataType); + return snip(id, this.dataType, /* ref */ isNaturallyRef(this.dataType)); } toString() { @@ -80,7 +80,7 @@ class TgpuConstImpl return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, dataType); + return snip(this, dataType, /* ref */ isNaturallyRef(dataType)); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `const:${getName(this) ?? ''}.$`, diff --git a/packages/typegpu/src/core/declare/tgpuDeclare.ts b/packages/typegpu/src/core/declare/tgpuDeclare.ts index f36d707e81..d1688fcc7c 100644 --- a/packages/typegpu/src/core/declare/tgpuDeclare.ts +++ b/packages/typegpu/src/core/declare/tgpuDeclare.ts @@ -60,7 +60,7 @@ class TgpuDeclareImpl implements TgpuDeclare, SelfResolvable { ); ctx.addDeclaration(replacedDeclaration); - return snip('', Void); + return snip('', Void, false); } toString() { diff --git a/packages/typegpu/src/core/function/dualImpl.ts b/packages/typegpu/src/core/function/dualImpl.ts index 3981948ebf..03b69bbde0 100644 --- a/packages/typegpu/src/core/function/dualImpl.ts +++ b/packages/typegpu/src/core/function/dualImpl.ts @@ -10,6 +10,7 @@ import { setName } from '../../shared/meta.ts'; import { $internal } from '../../shared/symbols.ts'; import { tryConvertSnippet } from '../../tgsl/conversion.ts'; import type { AnyData } from '../../data/dataTypes.ts'; +import { isNaturallyRef } from '../../data/wgslTypes.ts'; function isKnownAtComptime(value: unknown): boolean { return typeof value !== 'string' && getOwnSnippet(value) === undefined; @@ -78,10 +79,13 @@ export function dualImpl unknown>( return snip( options.normalImpl(...converted.map((s) => s.value) as never[]), returnType, + // Functions give up ownership of their return value + /* ref */ false, ); } - return snip(options.codegenImpl(...converted), returnType); + // Functions give up ownership of their return value + return snip(options.codegenImpl(...converted), returnType, /* ref */ false); }; const impl = ((...args: Parameters) => { diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index bd38acadbe..5853880879 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -7,6 +7,7 @@ import { type Snippet, } from '../../data/snippet.ts'; import { + isPtr, isWgslData, isWgslStruct, Void, @@ -135,7 +136,9 @@ export function createFnCore( } ctx.addDeclaration(`${fnAttribute}fn ${id}${header}${body}`); - return snip(id, returnType); + // 'ref' of this snippet is unused, but I guess for the sake of + // completeness, this is a reference to a function, so it's a ref. + return snip(id, returnType, /* ref */ true); } // get data generated by the plugin @@ -188,11 +191,19 @@ export function createFnCore( for (const [i, argType] of argTypes.entries()) { const astParam = ast.params[i]; + // We know if arguments are passed by reference or by value, because we + // enforce that based on the whether the argument is a pointer or not. + // + // It still applies for shell-less functions, since we determine the type + // of the argument based on the argument's referentiality. + // In other words, if we pass a reference to a function, it's typed as a pointer, + // otherwise it's typed as a value. + const ref = isPtr(argType); switch (astParam?.type) { case FuncParameterType.identifier: { const rawName = astParam.name; - const snippet = snip(ctx.makeNameValid(rawName), argType); + const snippet = snip(ctx.makeNameValid(rawName), argType, ref); args.push(snippet); if (snippet.value !== rawName) { argAliases.push([rawName, snippet]); @@ -200,7 +211,7 @@ export function createFnCore( break; } case FuncParameterType.destructuredObject: { - args.push(snip(`_arg_${i}`, argType)); + args.push(snip(`_arg_${i}`, argType, ref)); argAliases.push(...astParam.props.map(({ name, alias }) => [ alias, @@ -208,13 +219,14 @@ export function createFnCore( `_arg_${i}.${name}`, (argTypes[i] as WgslStruct) .propTypes[name], + ref, ), ] as [string, Snippet] )); break; } case undefined: - args.push(snip(`_arg_${i}`, argType)); + args.push(snip(`_arg_${i}`, argType, ref)); } } @@ -232,7 +244,9 @@ export function createFnCore( }`, ); - return snip(id, actualReturnType); + // 'ref' of this snippet is unused, but I guess for the sake of + // completeness, this is a reference to a function, so it's a ref. + return snip(id, actualReturnType, /* ref */ true); }, }; diff --git a/packages/typegpu/src/core/function/tgpuFn.ts b/packages/typegpu/src/core/function/tgpuFn.ts index bd034746c4..6804e4c35d 100644 --- a/packages/typegpu/src/core/function/tgpuFn.ts +++ b/packages/typegpu/src/core/function/tgpuFn.ts @@ -238,7 +238,8 @@ function createFn( throw new ExecutionError(err, [fn]); } }), - (...args) => snip(new FnCall(fn, args), shell.returnType), + // Functions give up ownership of their return value (so ref is false) + (...args) => snip(new FnCall(fn, args), shell.returnType, /* ref */ false), 'tgpuFnCall', shell.argTypes, ); @@ -297,7 +298,9 @@ function createBoundFunction( const call = createDualImpl>( (...args) => innerFn(...args), - (...args) => snip(new FnCall(fn, args), innerFn.shell.returnType), + // Functions give up ownership of their return value (so ref is false) + (...args) => + snip(new FnCall(fn, args), innerFn.shell.returnType, /* ref */ false), 'tgpuFnCall', innerFn.shell.argTypes, ); @@ -332,7 +335,8 @@ class FnCall implements SelfResolvable { this.#fn = fn; this.#params = params; this[$getNameForward] = fn; - this[$ownSnippet] = snip(this, this.#fn.shell.returnType); + // Functions give up ownership of their return value (so ref is false) + this[$ownSnippet] = snip(this, this.#fn.shell.returnType, /* ref */ false); } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { @@ -342,6 +346,8 @@ class FnCall implements SelfResolvable { return snip( stitch`${ctx.resolve(this.#fn).value}(${this.#params})`, this.#fn.shell.returnType, + // Functions give up ownership of their return value (so ref is false) + /* ref */ false, ); }); } diff --git a/packages/typegpu/src/core/pipeline/computePipeline.ts b/packages/typegpu/src/core/pipeline/computePipeline.ts index 6322f85ff8..e66d5d94fd 100644 --- a/packages/typegpu/src/core/pipeline/computePipeline.ts +++ b/packages/typegpu/src/core/pipeline/computePipeline.ts @@ -226,7 +226,7 @@ class ComputePipelineCore implements SelfResolvable { [$resolve](ctx: ResolutionCtx) { return ctx.withSlots(this._slotBindings, () => { ctx.resolve(this._entryFn); - return snip('', Void); + return snip('', Void, /* ref */ false); }); } diff --git a/packages/typegpu/src/core/pipeline/renderPipeline.ts b/packages/typegpu/src/core/pipeline/renderPipeline.ts index c471c30199..6e3031db53 100644 --- a/packages/typegpu/src/core/pipeline/renderPipeline.ts +++ b/packages/typegpu/src/core/pipeline/renderPipeline.ts @@ -674,7 +674,7 @@ class RenderPipelineCore implements SelfResolvable { if (fragmentFn) { ctx.resolve(fragmentFn); } - return snip('', Void); + return snip('', Void, /* ref */ false); }), ); } diff --git a/packages/typegpu/src/core/resolve/tgpuResolve.ts b/packages/typegpu/src/core/resolve/tgpuResolve.ts index 146d9a8d42..79c16a7f28 100644 --- a/packages/typegpu/src/core/resolve/tgpuResolve.ts +++ b/packages/typegpu/src/core/resolve/tgpuResolve.ts @@ -107,6 +107,7 @@ export function resolveWithContext( return snip( replaceExternalsInWgsl(ctx, dependencies, template ?? ''), Void, + /* ref */ false, ); }, diff --git a/packages/typegpu/src/core/root/init.ts b/packages/typegpu/src/core/root/init.ts index 89663f4060..f4f107ff0f 100644 --- a/packages/typegpu/src/core/root/init.ts +++ b/packages/typegpu/src/core/root/init.ts @@ -41,6 +41,7 @@ import { type VertexFlag, } from '../buffer/buffer.ts'; import { + type TgpuBufferShorthand, TgpuBufferShorthandImpl, type TgpuMutable, type TgpuReadonly, @@ -112,7 +113,12 @@ class WithBindingImpl implements WithBinding { with( slot: TgpuSlot | TgpuAccessor, - value: T | TgpuFn<() => T> | TgpuBufferUsage | Infer, + value: + | T + | TgpuFn<() => T> + | TgpuBufferUsage + | TgpuBufferShorthand + | Infer, ): WithBinding { return new WithBindingImpl(this._getRoot, [ ...this._slotBindings, diff --git a/packages/typegpu/src/core/sampler/sampler.ts b/packages/typegpu/src/core/sampler/sampler.ts index a2991e9bfc..e7aa27b2dd 100644 --- a/packages/typegpu/src/core/sampler/sampler.ts +++ b/packages/typegpu/src/core/sampler/sampler.ts @@ -156,7 +156,7 @@ export class TgpuLaidOutSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - [$ownSnippet] = snip(this, this as any); + [$ownSnippet] = snip(this, this as any, /* ref */ true); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -168,7 +168,7 @@ export class TgpuLaidOutSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ true); } toString() { @@ -188,7 +188,7 @@ export class TgpuLaidOutComparisonSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - [$ownSnippet] = snip(this, this as any); + [$ownSnippet] = snip(this, this as any, /* ref */ true); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -200,7 +200,7 @@ export class TgpuLaidOutComparisonSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ true); } toString() { @@ -238,7 +238,7 @@ class TgpuFixedSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - [$ownSnippet] = snip(this, this as any); + [$ownSnippet] = snip(this, this as any, /* ref */ true); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -256,7 +256,7 @@ class TgpuFixedSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ true); } $name(label: string) { @@ -293,7 +293,7 @@ class TgpuFixedComparisonSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - [$ownSnippet] = snip(this, this as any); + [$ownSnippet] = snip(this, this as any, /* ref */ true); $name(label: string) { setName(this, label); @@ -313,7 +313,7 @@ class TgpuFixedComparisonSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ true); } toString() { diff --git a/packages/typegpu/src/core/slot/accessor.ts b/packages/typegpu/src/core/slot/accessor.ts index dc711a4211..7274c9bbb8 100644 --- a/packages/typegpu/src/core/slot/accessor.ts +++ b/packages/typegpu/src/core/slot/accessor.ts @@ -1,5 +1,5 @@ import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; -import type { AnyWgslData } from '../../data/wgslTypes.ts'; +import { type AnyWgslData, isNaturallyRef } from '../../data/wgslTypes.ts'; import { getResolutionCtx, inCodegenMode } from '../../execMode.ts'; import { getName } from '../../shared/meta.ts'; import type { Infer, InferGPU } from '../../shared/repr.ts'; @@ -10,7 +10,15 @@ import { $ownSnippet, $resolve, } from '../../shared/symbols.ts'; -import type { ResolutionCtx, SelfResolvable } from '../../types.ts'; +import { + isBufferUsage, + type ResolutionCtx, + type SelfResolvable, +} from '../../types.ts'; +import { + isBufferShorthand, + type TgpuBufferShorthand, +} from '../buffer/bufferShorthand.ts'; import type { TgpuBufferUsage } from '../buffer/bufferUsage.ts'; import { isTgpuFn, type TgpuFn } from '../function/tgpuFn.ts'; import { @@ -26,7 +34,11 @@ import type { TgpuAccessor, TgpuSlot } from './slotTypes.ts'; export function accessor( schema: T, - defaultValue?: TgpuFn<() => T> | TgpuBufferUsage | Infer, + defaultValue?: + | TgpuFn<() => T> + | TgpuBufferUsage + | TgpuBufferShorthand + | Infer, ): TgpuAccessor { return new TgpuAccessorImpl(schema, defaultValue); } @@ -40,13 +52,16 @@ export class TgpuAccessorImpl readonly [$internal] = true; readonly [$getNameForward]: unknown; readonly resourceType = 'accessor'; - readonly slot: TgpuSlot T> | TgpuBufferUsage | Infer>; + readonly slot: TgpuSlot< + TgpuFn<() => T> | TgpuBufferUsage | TgpuBufferShorthand | Infer + >; constructor( public readonly schema: T, public readonly defaultValue: | TgpuFn<() => T> | TgpuBufferUsage + | TgpuBufferShorthand | Infer | undefined = undefined, ) { @@ -75,7 +90,11 @@ export class TgpuAccessorImpl return value[$internal].gpuImpl(); } - return snip(value, this.schema); + if (isBufferUsage(value) || isBufferShorthand(value)) { + return snip(value, this.schema, /* ref */ true); + } + + return snip(value, this.schema, /* ref */ isNaturallyRef(this.schema)); } $name(label: string) { @@ -106,6 +125,7 @@ export class TgpuAccessorImpl return snip( ctx.resolve(snippet.value, snippet.dataType).value, snippet.dataType as T, + snippet.ref, ); } } diff --git a/packages/typegpu/src/core/slot/slotTypes.ts b/packages/typegpu/src/core/slot/slotTypes.ts index efe335745a..4008dfbc41 100644 --- a/packages/typegpu/src/core/slot/slotTypes.ts +++ b/packages/typegpu/src/core/slot/slotTypes.ts @@ -2,6 +2,7 @@ import type { AnyData } from '../../data/dataTypes.ts'; import type { TgpuNamable } from '../../shared/meta.ts'; import type { GPUValueOf, Infer, InferGPU } from '../../shared/repr.ts'; import { $gpuValueOf, $internal, $providing } from '../../shared/symbols.ts'; +import type { TgpuBufferShorthand } from '../buffer/bufferShorthand.ts'; import type { TgpuFn } from '../function/tgpuFn.ts'; import type { TgpuBufferUsage } from './../buffer/bufferUsage.ts'; @@ -50,9 +51,12 @@ export interface TgpuAccessor extends TgpuNamable { readonly defaultValue: | TgpuFn<() => T> | TgpuBufferUsage + | TgpuBufferShorthand | Infer | undefined; - readonly slot: TgpuSlot T> | TgpuBufferUsage | Infer>; + readonly slot: TgpuSlot< + TgpuFn<() => T> | TgpuBufferUsage | TgpuBufferShorthand | Infer + >; readonly [$gpuValueOf]: InferGPU; readonly value: InferGPU; diff --git a/packages/typegpu/src/core/texture/externalTexture.ts b/packages/typegpu/src/core/texture/externalTexture.ts index 50ee907922..9ebf41c4d3 100644 --- a/packages/typegpu/src/core/texture/externalTexture.ts +++ b/packages/typegpu/src/core/texture/externalTexture.ts @@ -37,7 +37,7 @@ export class TgpuExternalTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - [$ownSnippet] = snip(this, this as any); + [$ownSnippet] = snip(this, this as any, /* ref */ true); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -49,7 +49,7 @@ export class TgpuExternalTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ true); } toString() { diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index c184e3e25d..8cb81c8933 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -496,7 +496,7 @@ class TgpuFixedStorageTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - [$ownSnippet] = snip(this, this as any); + [$ownSnippet] = snip(this, this as any, /* ref */ true); $name(label: string): this { this._texture.$name(label); @@ -523,7 +523,7 @@ class TgpuFixedStorageTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ true); } toString() { @@ -550,7 +550,7 @@ export class TgpuLaidOutStorageTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - [$ownSnippet] = snip(this, this as any); + [$ownSnippet] = snip(this, this as any, /* ref */ true); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -565,7 +565,7 @@ export class TgpuLaidOutStorageTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ true); } toString() { @@ -611,7 +611,7 @@ class TgpuFixedSampledTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - [$ownSnippet] = snip(this, this as any); + [$ownSnippet] = snip(this, this as any, /* ref */ true); $name(label: string): this { this._texture.$name(label); @@ -645,7 +645,7 @@ class TgpuFixedSampledTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ true); } toString() { @@ -672,7 +672,7 @@ export class TgpuLaidOutSampledTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - [$ownSnippet] = snip(this, this as any); + [$ownSnippet] = snip(this, this as any, /* ref */ true); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -691,7 +691,7 @@ export class TgpuLaidOutSampledTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ true); } ctx.addDeclaration( @@ -702,7 +702,7 @@ export class TgpuLaidOutSampledTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - return snip(id, this as any); + return snip(id, this as any, /* ref */ true); } toString() { diff --git a/packages/typegpu/src/core/valueProxyUtils.ts b/packages/typegpu/src/core/valueProxyUtils.ts index 6cf30fe172..b4132a94dd 100644 --- a/packages/typegpu/src/core/valueProxyUtils.ts +++ b/packages/typegpu/src/core/valueProxyUtils.ts @@ -1,5 +1,6 @@ import type { AnyData } from '../data/dataTypes.ts'; import { snip, type Snippet } from '../data/snippet.ts'; +import { isNaturallyRef } from '../data/wgslTypes.ts'; import { getGPUValue } from '../getGPUValue.ts'; import { $internal, $ownSnippet, $resolve } from '../shared/symbols.ts'; import { getTypeForPropAccess } from '../tgsl/generationHelpers.ts'; @@ -37,12 +38,14 @@ export const valueProxyHandler: ProxyHandler< return undefined; } + const ref = targetSnippet.ref && isNaturallyRef(propType); + return new Proxy({ [$internal]: true, [$resolve]: (ctx) => - snip(`${ctx.resolve(target).value}.${String(prop)}`, propType), + snip(`${ctx.resolve(target).value}.${String(prop)}`, propType, ref), get [$ownSnippet]() { - return snip(this, propType); + return snip(this, propType, ref); }, toString: () => `${String(target)}.${prop}`, }, valueProxyHandler); diff --git a/packages/typegpu/src/core/variable/tgpuVariable.ts b/packages/typegpu/src/core/variable/tgpuVariable.ts index d82bb154c8..395800ebfc 100644 --- a/packages/typegpu/src/core/variable/tgpuVariable.ts +++ b/packages/typegpu/src/core/variable/tgpuVariable.ts @@ -1,5 +1,6 @@ import type { AnyData } from '../../data/dataTypes.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; +import { isNaturallyRef } from '../../data/wgslTypes.ts'; import { IllegalVarAccessError } from '../../errors.ts'; import { getExecMode, isInsideTgpuFn } from '../../execMode.ts'; import type { TgpuNamable } from '../../shared/meta.ts'; @@ -103,7 +104,7 @@ class TgpuVarImpl ctx.addDeclaration(`${pre};`); } - return snip(id, this.#dataType); + return snip(id, this.#dataType, /* ref */ isNaturallyRef(this.#dataType)); } $name(label: string) { @@ -121,7 +122,7 @@ class TgpuVarImpl return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, dataType); + return snip(this, dataType, /* ref */ isNaturallyRef(dataType)); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `var:${getName(this) ?? ''}.$`, diff --git a/packages/typegpu/src/data/array.ts b/packages/typegpu/src/data/array.ts index 52873b4cb6..04227102d5 100644 --- a/packages/typegpu/src/data/array.ts +++ b/packages/typegpu/src/data/array.ts @@ -53,7 +53,8 @@ export const arrayOf = createDualImpl( // Marking so the WGSL generator lets this function through partial[$internal] = true; - return snip(partial, UnknownData); + // Why ref? It's a function. + return snip(partial, UnknownData, /* ref*/ true); } if (typeof elementCount.value !== 'number') { @@ -65,6 +66,7 @@ export const arrayOf = createDualImpl( return snip( cpu_arrayOf(elementType.value as AnyWgslData, elementCount.value), elementType.value as AnyWgslData, + /* ref */ false, ); }, 'arrayOf', diff --git a/packages/typegpu/src/data/disarray.ts b/packages/typegpu/src/data/disarray.ts index 68cf26317a..62f1696e04 100644 --- a/packages/typegpu/src/data/disarray.ts +++ b/packages/typegpu/src/data/disarray.ts @@ -59,7 +59,8 @@ export const disarrayOf = createDualImpl( // Marking so the WGSL generator lets this function through partial[$internal] = true; - return snip(partial, UnknownData); + // Why ref? It's a function. + return snip(partial, UnknownData, /* ref */ false); } if (typeof elementCount.value !== 'number') { @@ -71,6 +72,7 @@ export const disarrayOf = createDualImpl( return snip( cpu_disarrayOf(elementType.value as AnyWgslData, elementCount.value), elementType.value as AnyWgslData, + /* ref */ false, ); }, 'disarrayOf', diff --git a/packages/typegpu/src/data/matrix.ts b/packages/typegpu/src/data/matrix.ts index 507231ef97..aadfc83ed7 100644 --- a/packages/typegpu/src/data/matrix.ts +++ b/packages/typegpu/src/data/matrix.ts @@ -93,7 +93,11 @@ function createMatSchema< }, // CODEGEN implementation (...args) => - snip(stitch`${options.type}(${args})`, schema as unknown as AnyData), + snip( + stitch`${options.type}(${args})`, + schema as unknown as AnyData, + /* ref */ false, + ), options.type, ); @@ -178,6 +182,7 @@ abstract class mat2x2Impl extends MatBase .join(', ') })`, mat2x2f, + /* ref */ false, ); } @@ -327,6 +332,7 @@ abstract class mat3x3Impl extends MatBase this[5] }, ${this[6]}, ${this[8]}, ${this[9]}, ${this[10]})`, mat3x3f, + /* ref */ false, ); } @@ -525,6 +531,7 @@ abstract class mat4x4Impl extends MatBase .join(', ') })`, mat4x4f, + /* ref */ false, ); } @@ -553,7 +560,7 @@ export const identity2 = createDualImpl( // CPU implementation () => mat2x2f(1, 0, 0, 1), // CODEGEN implementation - () => snip('mat2x2f(1, 0, 0, 1)', mat2x2f), + () => snip('mat2x2f(1, 0, 0, 1)', mat2x2f, /* ref */ false), 'identity2', ); @@ -565,7 +572,7 @@ export const identity3 = createDualImpl( // CPU implementation () => mat3x3f(1, 0, 0, 0, 1, 0, 0, 0, 1), // CODEGEN implementation - () => snip('mat3x3f(1, 0, 0, 0, 1, 0, 0, 0, 1)', mat3x3f), + () => snip('mat3x3f(1, 0, 0, 0, 1, 0, 0, 0, 1)', mat3x3f, /* ref */ false), 'identity3', ); @@ -578,7 +585,11 @@ export const identity4 = createDualImpl( () => mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1), // CODEGEN implementation () => - snip('mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)', mat4x4f), + snip( + 'mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)', + mat4x4f, + /* ref */ false, + ), 'identity4', ); @@ -608,6 +619,7 @@ export const translation4 = createDualImpl( snip( stitch`mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ${v}.x, ${v}.y, ${v}.z, 1)`, mat4x4f, + /* ref */ false, ), 'translation4', ); @@ -632,6 +644,7 @@ export const scaling4 = createDualImpl( snip( stitch`mat4x4f(${v}.x, 0, 0, 0, 0, ${v}.y, 0, 0, 0, 0, ${v}.z, 0, 0, 0, 0, 1)`, mat4x4f, + /* ref */ false, ), 'scaling4', ); @@ -656,6 +669,7 @@ export const rotationX4 = createDualImpl( snip( stitch`mat4x4f(1, 0, 0, 0, 0, cos(${a}), sin(${a}), 0, 0, -sin(${a}), cos(${a}), 0, 0, 0, 0, 1)`, mat4x4f, + /* ref */ false, ), 'rotationX4', ); @@ -680,6 +694,7 @@ export const rotationY4 = createDualImpl( snip( stitch`mat4x4f(cos(${a}), 0, -sin(${a}), 0, 0, 1, 0, 0, sin(${a}), 0, cos(${a}), 0, 0, 0, 0, 1)`, mat4x4f, + /* ref */ false, ), 'rotationY4', ); @@ -704,6 +719,7 @@ export const rotationZ4 = createDualImpl( snip( stitch`mat4x4f(cos(${a}), sin(${a}), 0, 0, -sin(${a}), cos(${a}), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)`, mat4x4f, + /* ref */ false, ), 'rotationZ4', ); diff --git a/packages/typegpu/src/data/snippet.ts b/packages/typegpu/src/data/snippet.ts index bd0b651705..831f44ed32 100644 --- a/packages/typegpu/src/data/snippet.ts +++ b/packages/typegpu/src/data/snippet.ts @@ -10,6 +10,7 @@ export interface Snippet { * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float */ readonly dataType: AnyData | UnknownData; + readonly ref: boolean; } export interface ResolvedSnippet { @@ -19,6 +20,7 @@ export interface ResolvedSnippet { * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float */ readonly dataType: AnyData; + readonly ref: boolean; } export type MapValueToSnippet = { [K in keyof T]: Snippet }; @@ -27,6 +29,7 @@ class SnippetImpl implements Snippet { constructor( readonly value: unknown, readonly dataType: AnyData | UnknownData, + readonly ref: boolean, ) {} } @@ -38,11 +41,20 @@ export function isSnippetNumeric(snippet: Snippet) { return isNumericSchema(snippet.dataType); } -export function snip(value: string, dataType: AnyData): ResolvedSnippet; -export function snip(value: unknown, dataType: AnyData | UnknownData): Snippet; +export function snip( + value: string, + dataType: AnyData, + ref: boolean, +): ResolvedSnippet; +export function snip( + value: unknown, + dataType: AnyData | UnknownData, + ref: boolean, +): Snippet; export function snip( value: unknown, dataType: AnyData | UnknownData, + ref: boolean, ): Snippet | ResolvedSnippet { if (DEV && isSnippet(value)) { // An early error, but not worth checking every time in production @@ -53,5 +65,6 @@ export function snip( value, // We don't care about attributes in snippet land, so we discard that information. undecorate(dataType as AnyData), + ref, ); } diff --git a/packages/typegpu/src/data/vectorImpl.ts b/packages/typegpu/src/data/vectorImpl.ts index 2e58e9cc68..177cab491b 100644 --- a/packages/typegpu/src/data/vectorImpl.ts +++ b/packages/typegpu/src/data/vectorImpl.ts @@ -41,12 +41,12 @@ export abstract class VecBase extends Array implements SelfResolvable { [$resolve](): ResolvedSnippet { const schema = this[$internal].elementSchema; if (this.every((e) => !e)) { - return snip(`${this.kind}()`, schema); + return snip(`${this.kind}()`, schema, /* ref */ false); } if (this.every((e) => this[0] === e)) { - return snip(`${this.kind}(${this[0]})`, schema); + return snip(`${this.kind}(${this[0]})`, schema, /* ref */ false); } - return snip(`${this.kind}(${this.join(', ')})`, schema); + return snip(`${this.kind}(${this.join(', ')})`, schema, /* ref */ false); } toString() { diff --git a/packages/typegpu/src/data/wgslTypes.ts b/packages/typegpu/src/data/wgslTypes.ts index 9e2e02ecd9..16cde50c06 100644 --- a/packages/typegpu/src/data/wgslTypes.ts +++ b/packages/typegpu/src/data/wgslTypes.ts @@ -25,6 +25,7 @@ import type { } from '../shared/symbols.ts'; import { $internal } from '../shared/symbols.ts'; import type { Prettify, SwapNever } from '../shared/utilityTypes.ts'; +import { isMarkedInternal } from '../types.ts'; import type { DualFn } from './dualFn.ts'; type DecoratedLocation = Decorated; @@ -1867,3 +1868,23 @@ export function isHalfPrecisionSchema( type === 'vec4h') ); } + +const valueTypes = [ + 'abstractInt', + 'abstractFloat', + 'f32', + 'f16', + 'i32', + 'u32', + 'bool', +]; + +/** + * Returns true for schemas that are naturally referential in JS. + * @param schema + * @returns + */ +export function isNaturallyRef(schema: unknown): boolean { + return isMarkedInternal(schema) && + !valueTypes.includes((schema as BaseData)?.type); +} diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index b228db5349..f83df61193 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -24,7 +24,12 @@ import { getAttributesString } from './data/attributes.ts'; import { type AnyData, isData, UnknownData } from './data/dataTypes.ts'; import { bool } from './data/numeric.ts'; import { type ResolvedSnippet, snip, type Snippet } from './data/snippet.ts'; -import { isWgslArray, isWgslStruct, Void } from './data/wgslTypes.ts'; +import { + isNaturallyRef, + isWgslArray, + isWgslStruct, + Void, +} from './data/wgslTypes.ts'; import { invariant, MissingSlotValueError, @@ -656,7 +661,8 @@ export class ResolutionCtxImpl implements ResolutionCtx { // If we got here, no item with the given slot-to-value combo exists in cache yet let result: ResolvedSnippet; if (isData(item)) { - result = snip(resolveData(this, item), Void); + // Ref is arbitrary, as we're resolving a schema + result = snip(resolveData(this, item), Void, /* ref */ true); } else if (isDerived(item) || isSlot(item)) { result = this.resolve(this.unwrap(item)); } else if (isSelfResolvable(item)) { @@ -721,6 +727,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { return snip( `${[...this._declarations].join('\n\n')}${result.value}`, Void, + /* ref */ false, // arbitrary ); } finally { this.popMode('codegen'); @@ -746,13 +753,13 @@ export class ResolutionCtxImpl implements ResolutionCtx { ); if (reinterpretedType.type === 'abstractInt') { - return snip(`${item}`, realSchema); + return snip(`${item}`, realSchema, /* ref */ false); } if (reinterpretedType.type === 'u32') { - return snip(`${item}u`, realSchema); + return snip(`${item}u`, realSchema, /* ref */ false); } if (reinterpretedType.type === 'i32') { - return snip(`${item}i`, realSchema); + return snip(`${item}i`, realSchema, /* ref */ false); } const exp = item.toExponential(); @@ -764,21 +771,21 @@ export class ResolutionCtxImpl implements ResolutionCtx { // Just picking the shorter one const base = exp.length < decimal.length ? exp : decimal; if (reinterpretedType.type === 'f32') { - return snip(`${base}f`, realSchema); + return snip(`${base}f`, realSchema, /* ref */ false); } if (reinterpretedType.type === 'f16') { - return snip(`${base}h`, realSchema); + return snip(`${base}h`, realSchema, /* ref */ false); } - return snip(base, realSchema); + return snip(base, realSchema, /* ref */ false); } if (typeof item === 'boolean') { - return snip(item ? 'true' : 'false', bool); + return snip(item ? 'true' : 'false', bool, /* ref */ false); } if (typeof item === 'string') { // Already resolved - return snip(item, Void); + return snip(item, Void, /* ref */ false); } if (schema && isWgslArray(schema)) { @@ -797,9 +804,16 @@ export class ResolutionCtxImpl implements ResolutionCtx { const elementTypeString = this.resolve(schema.elementType); return snip( stitch`array<${elementTypeString}, ${schema.elementCount}>(${ - item.map((element) => snip(element, schema.elementType as AnyData)) + item.map((element) => + snip( + element, + schema.elementType as AnyData, + /* ref */ isNaturallyRef(schema.elementType), + ) + ) })`, schema, + /* ref */ false, ); } @@ -807,6 +821,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { return snip( stitch`array(${item.map((element) => this.resolve(element))})`, UnknownData, + /* ref */ false, ) as ResolvedSnippet; } @@ -814,10 +829,15 @@ export class ResolutionCtxImpl implements ResolutionCtx { return snip( stitch`${this.resolve(schema)}(${ Object.entries(schema.propTypes).map(([key, propType]) => - snip((item as Infer)[key], propType as AnyData) + snip( + (item as Infer)[key], + propType as AnyData, + /* ref */ false, + ) ) })`, schema, + /* ref */ false, // a new struct, not referenced from anywhere ); } diff --git a/packages/typegpu/src/std/atomic.ts b/packages/typegpu/src/std/atomic.ts index f822f74b26..e24e0c2bd4 100644 --- a/packages/typegpu/src/std/atomic.ts +++ b/packages/typegpu/src/std/atomic.ts @@ -16,7 +16,7 @@ export const workgroupBarrier = createDualImpl( // CPU implementation () => console.warn('workgroupBarrier is a no-op outside of CODEGEN mode.'), // CODEGEN implementation - () => snip('workgroupBarrier()', Void), + () => snip('workgroupBarrier()', Void, /* ref */ false), 'workgroupBarrier', ); @@ -24,7 +24,7 @@ export const storageBarrier = createDualImpl( // CPU implementation () => console.warn('storageBarrier is a no-op outside of CODEGEN mode.'), // CODEGEN implementation - () => snip('storageBarrier()', Void), + () => snip('storageBarrier()', Void, /* ref */ false), 'storageBarrier', ); @@ -32,7 +32,7 @@ export const textureBarrier = createDualImpl( // CPU implementation () => console.warn('textureBarrier is a no-op outside of CODEGEN mode.'), // CODEGEN implementation - () => snip('textureBarrier()', Void), + () => snip('textureBarrier()', Void, /* ref */ false), 'textureBarrier', ); @@ -46,7 +46,7 @@ export const atomicLoad = createDualImpl( // CODEGEN implementation (a) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicLoad(&${a})`, a.dataType.inner); + return snip(stitch`atomicLoad(&${a})`, a.dataType.inner, /* ref */ false); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -69,7 +69,7 @@ export const atomicStore = createDualImpl( `Invalid atomic type: ${safeStringify(a.dataType)}`, ); } - return snip(stitch`atomicStore(&${a}, ${value})`, Void); + return snip(stitch`atomicStore(&${a}, ${value})`, Void, /* ref */ false); }, 'atomicStore', ); @@ -91,7 +91,11 @@ export const atomicAdd = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicAdd(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicAdd(&${a}, ${value})`, + a.dataType.inner, + /* ref */ false, + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -111,7 +115,11 @@ export const atomicSub = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicSub(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicSub(&${a}, ${value})`, + a.dataType.inner, + /* ref */ false, + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -131,7 +139,11 @@ export const atomicMax = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicMax(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicMax(&${a}, ${value})`, + a.dataType.inner, + /* ref */ false, + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -151,7 +163,11 @@ export const atomicMin = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicMin(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicMin(&${a}, ${value})`, + a.dataType.inner, + /* ref */ false, + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -171,7 +187,11 @@ export const atomicAnd = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicAnd(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicAnd(&${a}, ${value})`, + a.dataType.inner, + /* ref */ false, + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -191,7 +211,11 @@ export const atomicOr = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicOr(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicOr(&${a}, ${value})`, + a.dataType.inner, + /* ref */ false, + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -211,7 +235,11 @@ export const atomicXor = createDualImpl( // CODEGEN implementation (a, value) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicXor(&${a}, ${value})`, a.dataType.inner); + return snip( + stitch`atomicXor(&${a}, ${value})`, + a.dataType.inner, + /* ref */ false, + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, diff --git a/packages/typegpu/src/std/boolean.ts b/packages/typegpu/src/std/boolean.ts index 973c3f498f..243b08fe05 100644 --- a/packages/typegpu/src/std/boolean.ts +++ b/packages/typegpu/src/std/boolean.ts @@ -283,7 +283,7 @@ export const isCloseTo = dualImpl({ return false; }, // GPU implementation - codegenImpl: (lhs, rhs, precision = snip(0.01, f32)) => { + codegenImpl: (lhs, rhs, precision = snip(0.01, f32, /* ref */ false)) => { if (isSnippetNumeric(lhs) && isSnippetNumeric(rhs)) { return stitch`(abs(f32(${lhs}) - f32(${rhs})) <= ${precision})`; } diff --git a/packages/typegpu/src/std/derivative.ts b/packages/typegpu/src/std/derivative.ts index f29408b287..9f14eb8902 100644 --- a/packages/typegpu/src/std/derivative.ts +++ b/packages/typegpu/src/std/derivative.ts @@ -11,7 +11,7 @@ function cpuDpdx(value: T): T { export const dpdx = createDualImpl( cpuDpdx, - (value) => snip(stitch`dpdx(${value})`, value.dataType), + (value) => snip(stitch`dpdx(${value})`, value.dataType, /* ref */ false), 'dpdx', ); @@ -25,7 +25,8 @@ function cpuDpdxCoarse( export const dpdxCoarse = createDualImpl( cpuDpdxCoarse, - (value) => snip(stitch`dpdxCoarse(${value})`, value.dataType), + (value) => + snip(stitch`dpdxCoarse(${value})`, value.dataType, /* ref */ false), 'dpdxCoarse', ); @@ -37,7 +38,7 @@ function cpuDpdxFine(value: T): T { export const dpdxFine = createDualImpl( cpuDpdxFine, - (value) => snip(stitch`dpdxFine(${value})`, value.dataType), + (value) => snip(stitch`dpdxFine(${value})`, value.dataType, /* ref */ false), 'dpdxFine', ); @@ -49,7 +50,7 @@ function cpuDpdy(value: T): T { export const dpdy = createDualImpl( cpuDpdy, - (value) => snip(stitch`dpdy(${value})`, value.dataType), + (value) => snip(stitch`dpdy(${value})`, value.dataType, /* ref */ false), 'dpdy', ); @@ -63,7 +64,8 @@ function cpuDpdyCoarse( export const dpdyCoarse = createDualImpl( cpuDpdyCoarse, - (value) => snip(stitch`dpdyCoarse(${value})`, value.dataType), + (value) => + snip(stitch`dpdyCoarse(${value})`, value.dataType, /* ref */ false), 'dpdyCoarse', ); @@ -75,7 +77,7 @@ function cpuDpdyFine(value: T): T { export const dpdyFine = createDualImpl( cpuDpdyFine, - (value) => snip(stitch`dpdyFine(${value})`, value.dataType), + (value) => snip(stitch`dpdyFine(${value})`, value.dataType, /* ref */ false), 'dpdyFine', ); @@ -87,7 +89,7 @@ function cpuFwidth(value: T): T { export const fwidth = createDualImpl( cpuFwidth, - (value) => snip(stitch`fwidth(${value})`, value.dataType), + (value) => snip(stitch`fwidth(${value})`, value.dataType, /* ref */ false), 'fwidth', ); @@ -101,7 +103,8 @@ function cpuFwidthCoarse( export const fwidthCoarse = createDualImpl( cpuFwidthCoarse, - (value) => snip(stitch`fwidthCoarse(${value})`, value.dataType), + (value) => + snip(stitch`fwidthCoarse(${value})`, value.dataType, /* ref */ false), 'fwidthCoarse', ); @@ -115,6 +118,7 @@ function cpuFwidthFine( export const fwidthFine = createDualImpl( cpuFwidthFine, - (value) => snip(stitch`fwidthFine(${value})`, value.dataType), + (value) => + snip(stitch`fwidthFine(${value})`, value.dataType, /* ref */ false), 'fwidthFine', ); diff --git a/packages/typegpu/src/std/discard.ts b/packages/typegpu/src/std/discard.ts index e959775868..4b6b35bdda 100644 --- a/packages/typegpu/src/std/discard.ts +++ b/packages/typegpu/src/std/discard.ts @@ -10,6 +10,6 @@ export const discard = createDualImpl( ); }, // GPU - () => snip('discard;', Void), + () => snip('discard;', Void, /* ref */ false), 'discard', ); diff --git a/packages/typegpu/src/std/extensions.ts b/packages/typegpu/src/std/extensions.ts index 78d1829e71..536ba74600 100644 --- a/packages/typegpu/src/std/extensions.ts +++ b/packages/typegpu/src/std/extensions.ts @@ -28,7 +28,7 @@ export const extensionEnabled: DualFn< `extensionEnabled has to be called with a string literal representing a valid WGSL extension name. Got: ${value}`, ); } - return snip(jsImpl(value as WgslExtension), bool); + return snip(jsImpl(value as WgslExtension), bool, /* ref */ false); }; const impl = (extensionName: WgslExtension) => { diff --git a/packages/typegpu/src/std/matrix.ts b/packages/typegpu/src/std/matrix.ts index 86e0e169a2..4266f9d525 100644 --- a/packages/typegpu/src/std/matrix.ts +++ b/packages/typegpu/src/std/matrix.ts @@ -40,7 +40,11 @@ export const translate4 = createDualImpl( (matrix: m4x4f, vector: v3f) => cpuMul(cpuTranslation4(vector), matrix), // GPU implementation (matrix, vector) => - snip(stitch`(${gpuTranslation4(vector)} * ${matrix})`, matrix.dataType), + snip( + stitch`(${gpuTranslation4(vector)} * ${matrix})`, + matrix.dataType, + /* ref */ false, + ), 'translate4', ); @@ -55,7 +59,11 @@ export const scale4 = createDualImpl( (matrix: m4x4f, vector: v3f) => cpuMul(cpuScaling4(vector), matrix), // GPU implementation (matrix, vector) => - snip(stitch`(${(gpuScaling4(vector))} * ${matrix})`, matrix.dataType), + snip( + stitch`(${(gpuScaling4(vector))} * ${matrix})`, + matrix.dataType, + /* ref */ false, + ), 'scale4', ); @@ -70,7 +78,11 @@ export const rotateX4 = createDualImpl( (matrix: m4x4f, angle: number) => cpuMul(cpuRotationX4(angle), matrix), // GPU implementation (matrix, angle) => - snip(stitch`(${(gpuRotationX4(angle))} * ${matrix})`, matrix.dataType), + snip( + stitch`(${(gpuRotationX4(angle))} * ${matrix})`, + matrix.dataType, + /* ref */ false, + ), 'rotateX4', ); @@ -85,7 +97,11 @@ export const rotateY4 = createDualImpl( (matrix: m4x4f, angle: number) => cpuMul(cpuRotationY4(angle), matrix), // GPU implementation (matrix, angle) => - snip(stitch`(${(gpuRotationY4(angle))} * ${matrix})`, matrix.dataType), + snip( + stitch`(${(gpuRotationY4(angle))} * ${matrix})`, + matrix.dataType, + /* ref */ false, + ), 'rotateY4', ); @@ -100,6 +116,10 @@ export const rotateZ4 = createDualImpl( (matrix: m4x4f, angle: number) => cpuMul(cpuRotationZ4(angle), matrix), // GPU implementation (matrix, angle) => - snip(stitch`(${(gpuRotationZ4(angle))} * ${matrix})`, matrix.dataType), + snip( + stitch`(${(gpuRotationZ4(angle))} * ${matrix})`, + matrix.dataType, + /* ref */ false, + ), 'rotateZ4', ); diff --git a/packages/typegpu/src/std/numeric.ts b/packages/typegpu/src/std/numeric.ts index d3243c29b0..64b1071a94 100644 --- a/packages/typegpu/src/std/numeric.ts +++ b/packages/typegpu/src/std/numeric.ts @@ -609,7 +609,7 @@ export const frexp: FrexpOverload = createDualImpl( ); } - return snip(stitch`frexp(${value})`, returnType); + return snip(stitch`frexp(${value})`, returnType, /* ref */ false); }, 'frexp', ); @@ -974,7 +974,8 @@ export const refract = createDualImpl( ); }, // GPU implementation - (e1, e2, e3) => snip(stitch`refract(${e1}, ${e2}, ${e3})`, e1.dataType), + (e1, e2, e3) => + snip(stitch`refract(${e1}, ${e2}, ${e3})`, e1.dataType, /* ref */ false), 'refract', (e1, e2, e3) => [ e1.dataType as AnyWgslData, diff --git a/packages/typegpu/src/std/packing.ts b/packages/typegpu/src/std/packing.ts index af12a7452a..fa9ffcf443 100644 --- a/packages/typegpu/src/std/packing.ts +++ b/packages/typegpu/src/std/packing.ts @@ -20,7 +20,7 @@ export const unpack2x16float = createDualImpl( return vec2f(reader.readFloat16(), reader.readFloat16()); }, // GPU implementation - (e) => snip(stitch`unpack2x16float(${e})`, vec2f), + (e) => snip(stitch`unpack2x16float(${e})`, vec2f, /* ref */ false), 'unpack2x16float', ); @@ -39,7 +39,7 @@ export const pack2x16float = createDualImpl( return u32(reader.readUint32()); }, // GPU implementation - (e) => snip(stitch`pack2x16float(${e})`, u32), + (e) => snip(stitch`pack2x16float(${e})`, u32, /* ref */ false), 'pack2x16float', ); @@ -62,7 +62,7 @@ export const unpack4x8unorm = createDualImpl( ); }, // GPU implementation - (e) => snip(stitch`unpack4x8unorm(${e})`, vec4f), + (e) => snip(stitch`unpack4x8unorm(${e})`, vec4f, /* ref */ false), 'unpack4x8unorm', ); @@ -83,6 +83,6 @@ export const pack4x8unorm = createDualImpl( return u32(reader.readUint32()); }, // GPU implementation - (e) => snip(stitch`pack4x8unorm(${e})`, u32), + (e) => snip(stitch`pack4x8unorm(${e})`, u32, /* ref */ false), 'pack4x8unorm', ); diff --git a/packages/typegpu/src/std/texture.ts b/packages/typegpu/src/std/texture.ts index 76ab4c2c99..af8e9b2ee9 100644 --- a/packages/typegpu/src/std/texture.ts +++ b/packages/typegpu/src/std/texture.ts @@ -127,7 +127,7 @@ export const textureSample: TextureSampleOverload = createDualImpl( ); }, // CODEGEN implementation - (...args) => snip(stitch`textureSample(${args})`, vec4f), + (...args) => snip(stitch`textureSample(${args})`, vec4f, /* ref */ false), 'textureSample', ); @@ -197,7 +197,7 @@ export const textureSampleBias: TextureSampleBiasOverload = createDualImpl( ); }, // CODEGEN implementation - (...args) => snip(stitch`textureSampleBias(${args})`, vec4f), + (...args) => snip(stitch`textureSampleBias(${args})`, vec4f, /* ref */ false), 'textureSampleBias', ); @@ -247,7 +247,8 @@ export const textureSampleLevel: TextureSampleLevelOverload = createDualImpl( ); }, // CODEGEN implementation - (...args) => snip(stitch`textureSampleLevel(${args})`, vec4f), + (...args) => + snip(stitch`textureSampleLevel(${args})`, vec4f, /* ref */ false), 'textureSampleLevel', ); @@ -336,6 +337,7 @@ export const textureLoad: TextureLoadOverload = createDualImpl( 'texelDataType' in textureInfo ? textureInfo.texelDataType : channelDataToInstance[textureInfo.channelDataType.type], + /* ref */ false, ); }, 'textureLoad', @@ -378,7 +380,7 @@ export const textureStore: TextureStoreOverload = createDualImpl( ); }, // CODEGEN implementation - (...args) => snip(stitch`textureStore(${args})`, Void), + (...args) => snip(stitch`textureStore(${args})`, Void, /* ref */ false), 'textureStore', ); @@ -438,6 +440,7 @@ export const textureDimensions: TextureDimensionsOverload = createDualImpl( return snip( stitch`textureDimensions(${[texture, level]})`, dim === '1d' ? u32 : dim === '3d' ? vec3u : vec2u, + /* ref */ false, ); }, 'textureDimensions', @@ -503,7 +506,8 @@ export const textureSampleCompare: TextureSampleCompareOverload = ); }, // CODEGEN implementation - (...args) => snip(stitch`textureSampleCompare(${args})`, f32), + (...args) => + snip(stitch`textureSampleCompare(${args})`, f32, /* ref */ false), 'textureSampleCompare', ); @@ -530,6 +534,7 @@ export const textureSampleBaseClampToEdge: TextureSampleBaseClampToEdge = snip( stitch`textureSampleBaseClampToEdge(${args})`, vec4f, + /* ref */ false, ), 'textureSampleBaseClampToEdge', ); diff --git a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts index d491434729..2943dd27bf 100644 --- a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts +++ b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts @@ -41,7 +41,7 @@ export class LogGeneratorNullImpl implements LogGenerator { console.warn( "'console.log' is currently only supported in compute pipelines.", ); - return snip('/* console.log() */', Void); + return snip('/* console.log() */', Void, /* ref */ false); } } @@ -101,7 +101,11 @@ export class LogGeneratorImpl implements LogGenerator { ), ); - return snip(stitch`${ctx.resolve(logFn).value}(${nonStringArgs})`, Void); + return snip( + stitch`${ctx.resolve(logFn).value}(${nonStringArgs})`, + Void, + /* ref */ false, + ); } get logResources(): LogResources | undefined { diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index a4502c9f3a..06f66f495a 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -229,14 +229,20 @@ function applyActionToSnippet( targetType: AnyData, ): Snippet { if (action.action === 'none') { - return snip(snippet.value, targetType); + return snip( + snippet.value, + targetType, + // if it was a ref, then it's still a ref + /* ref */ snippet.ref, + ); } switch (action.action) { case 'ref': - return snip(stitch`&${snippet}`, targetType); + return snip(stitch`&${snippet}`, targetType, /* ref */ true); case 'deref': - return snip(stitch`*${snippet}`, targetType); + // Dereferencing a pointer does not return a copy of the value, it's still a reference. + return snip(stitch`*${snippet}`, targetType, /* ref */ true); case 'cast': { // Casting means calling the schema with the snippet as an argument. return (targetType as unknown as (val: Snippet) => Snippet)(snippet); @@ -313,12 +319,16 @@ export function tryConvertSnippet( verbose = true, ): Snippet { if (targetDataType === snippet.dataType) { - return snip(snippet.value, targetDataType); + return snip(snippet.value, targetDataType, /* ref */ snippet.ref); } if (snippet.dataType.type === 'unknown') { // This is it, it's now or never. We expect a specific type, and we're going to get it - return snip(stitch`${snip(snippet.value, targetDataType)}`, targetDataType); + return snip( + stitch`${snip(snippet.value, targetDataType, /* ref */ snippet.ref)}`, + targetDataType, + /* ref */ snippet.ref, + ); } const converted = convertToCommonType([snippet], [targetDataType], verbose); diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 6022c4525f..4e883b0197 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -2,6 +2,7 @@ import { type AnyData, isDisarray, isUnstruct, + undecorate, UnknownData, } from '../data/dataTypes.ts'; import { mat2x2f, mat3x3f, mat4x4f } from '../data/matrix.ts'; @@ -111,7 +112,8 @@ export function getTypeForPropAccess( propName: string, ): AnyData | UnknownData { if (isWgslStruct(targetType) || isUnstruct(targetType)) { - return targetType.propTypes[propName] as AnyData ?? UnknownData; + const propType = targetType.propTypes[propName]; + return propType ? undecorate(propType) as AnyData : UnknownData; } if (targetType === bool || isNumericSchema(targetType)) { @@ -176,9 +178,9 @@ export function numericLiteralToSnippet(value: number): Snippet { `The integer ${value} exceeds the safe integer range and may have lost precision.`, ); } - return snip(value, abstractInt); + return snip(value, abstractInt, /* ref */ false); } - return snip(value, abstractFloat); + return snip(value, abstractFloat, /* ref */ false); } export function concretize(type: T): T | F32 | I32 { @@ -195,7 +197,11 @@ export function concretize(type: T): T | F32 | I32 { export function concretizeSnippets(args: Snippet[]): Snippet[] { return args.map((snippet) => - snip(snippet.value, concretize(snippet.dataType as AnyWgslData)) + snip( + snippet.value, + concretize(snippet.dataType as AnyWgslData), + /* ref */ snippet.ref, + ) ); } @@ -243,7 +249,8 @@ export function coerceToSnippet(value: unknown): Snippet { } if (isVecInstance(value) || isMatInstance(value)) { - return snip(value, kindToSchema[value.kind]); + // It's a reference to an external value, so `ref` is true + return snip(value, kindToSchema[value.kind], /* ref */ true); } if ( @@ -252,7 +259,7 @@ export function coerceToSnippet(value: unknown): Snippet { typeof value === 'undefined' || value === null ) { // Nothing representable in WGSL as-is, so unknown - return snip(value, UnknownData); + return snip(value, UnknownData, /* ref */ true); } if (typeof value === 'number') { @@ -260,8 +267,10 @@ export function coerceToSnippet(value: unknown): Snippet { } if (typeof value === 'boolean') { - return snip(value, bool); + // It's a primitive, so `ref` is false + return snip(value, bool, /* ref */ false); } - return snip(value, UnknownData); + // Hard to determine referentiality, so let's assume it's true + return snip(value, UnknownData, /* ref */ true); } diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index d9aca617dd..dbc6d48f4e 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -163,7 +163,11 @@ ${this.ctx.pre}}`; id: string, dataType: wgsl.AnyWgslData | UnknownData, ): Snippet { - const snippet = snip(this.ctx.makeNameValid(id), dataType); + const snippet = snip( + this.ctx.makeNameValid(id), + dataType, + /* ref */ wgsl.isNaturallyRef(dataType), + ); this.ctx.defineVariable(id, snippet); return snippet; } @@ -208,7 +212,7 @@ ${this.ctx.pre}}`; } if (typeof expression === 'boolean') { - return snip(expression, bool); + return snip(expression, bool, /* ref */ false); } if ( @@ -245,6 +249,8 @@ ${this.ctx.pre}}`; ? `(${lhsStr} ${op} ${rhsStr})` : `${lhsStr} ${op} ${rhsStr}`, type, + // Result of an operation, so not a reference to anything + /* ref */ false, ); } @@ -254,7 +260,8 @@ ${this.ctx.pre}}`; const argExpr = this.expression(arg); const argStr = this.ctx.resolve(argExpr.value).value; - return snip(`${argStr}${op}`, argExpr.dataType); + // Result of an operation, so not a reference to anything + return snip(`${argStr}${op}`, argExpr.dataType, /* ref */ false); } if (expression[0] === NODE.unaryExpr) { @@ -264,7 +271,8 @@ ${this.ctx.pre}}`; const argStr = this.ctx.resolve(argExpr.value).value; const type = operatorToType(argExpr.dataType, op); - return snip(`${op}${argStr}`, type); + // Result of an operation, so not a reference to anything + return snip(`${op}${argStr}`, type, /* ref */ false); } if (expression[0] === NODE.memberAccess) { @@ -273,21 +281,22 @@ ${this.ctx.pre}}`; const target = this.expression(targetNode); if (target.value === console) { - return snip(new ConsoleLog(), UnknownData); + return snip(new ConsoleLog(), UnknownData, /* ref */ true); } if ( infixKinds.includes(target.dataType.type) && property in infixOperators ) { - return { - value: new InfixDispatch( + return snip( + new InfixDispatch( property, target, infixOperators[property as InfixOperator][$internal].gpuImpl, ), - dataType: UnknownData, - }; + UnknownData, + /* ref */ true, + ); } if (target.dataType.type === 'unknown') { @@ -301,9 +310,15 @@ ${this.ctx.pre}}`; } if (wgsl.isPtr(target.dataType)) { + const propType = getTypeForPropAccess( + target.dataType.inner as AnyData, + property, + ); + return snip( `(*${this.ctx.resolve(target.value).value}).${property}`, - getTypeForPropAccess(target.dataType.inner as AnyData, property), + propType, + /* ref */ wgsl.isNaturallyRef(propType), ); } @@ -313,14 +328,23 @@ ${this.ctx.pre}}`; return snip( `arrayLength(&${this.ctx.resolve(target.value).value})`, u32, + /* ref */ false, ); } - return snip(String(target.dataType.elementCount), abstractInt); + return snip( + String(target.dataType.elementCount), + abstractInt, + /* ref */ false, + ); } if (wgsl.isMat(target.dataType) && property === 'columns') { - return snip(new MatrixColumnsAccess(target), UnknownData); + return snip( + new MatrixColumnsAccess(target), + UnknownData, + /* ref */ true, + ); } if ( @@ -331,9 +355,11 @@ ${this.ctx.pre}}`; return coerceToSnippet((target.value as any)[property]); } + const propType = getTypeForPropAccess(target.dataType, property); return snip( `${this.ctx.resolve(target.value).value}.${property}`, - getTypeForPropAccess(target.dataType, property), + propType, + /* ref */ target.ref && wgsl.isNaturallyRef(propType), ); } @@ -346,9 +372,14 @@ ${this.ctx.pre}}`; this.ctx.resolve(property.value, property.dataType).value; if (target.value instanceof MatrixColumnsAccess) { + const propType = getTypeForIndexAccess( + target.value.matrix.dataType as AnyData, + ); + return snip( stitch`${target.value.matrix}[${propertyStr}]`, - getTypeForIndexAccess(target.value.matrix.dataType as AnyData), + propType, + /* ref */ target.ref, ); } const targetStr = this.ctx.resolve(target.value, target.dataType).value; @@ -377,17 +408,23 @@ ${this.ctx.pre}}`; } if (wgsl.isPtr(target.dataType)) { + const propType = getTypeForIndexAccess( + target.dataType.inner as AnyData, + ); return snip( `(*${targetStr})[${propertyStr}]`, - getTypeForIndexAccess(target.dataType.inner as AnyData), + propType, + /* ref */ wgsl.isNaturallyRef(propType), ); } + const propType = isData(target.dataType) + ? getTypeForIndexAccess(target.dataType) + : UnknownData; return snip( `${targetStr}[${propertyStr}]`, - isData(target.dataType) - ? getTypeForIndexAccess(target.dataType) - : UnknownData, + propType, + /* ref */ target.ref && wgsl.isNaturallyRef(propType), ); } @@ -421,6 +458,8 @@ ${this.ctx.pre}}`; return snip( `${this.ctx.resolve(callee.value).value}()`, callee.value, + // A new struct, so not a reference + /* ref */ false, ); } @@ -434,6 +473,8 @@ ${this.ctx.pre}}`; return snip( this.ctx.resolve(arg.value, callee.value).value, callee.value, + // A new struct, so not a reference + /* ref */ false, ); } @@ -457,7 +498,11 @@ ${this.ctx.pre}}`; if (shellless) { return this.ctx.withResetIndentLevel(() => { const snippet = this.ctx.resolve(shellless); - return snip(stitch`${snippet.value}(${args})`, snippet.dataType); + return snip( + stitch`${snippet.value}(${args})`, + snippet.dataType, + /* ref */ false, + ); }); } @@ -563,6 +608,7 @@ ${this.ctx.pre}}`; return snip( stitch`${this.ctx.resolve(structType).value}(${convertedSnippets})`, structType, + /* ref */ false, ); } @@ -618,11 +664,12 @@ ${this.ctx.pre}}`; elemType as wgsl.AnyWgslData, values.length, ) as wgsl.AnyWgslData, + /* ref */ false, ); } if (expression[0] === NODE.stringLiteral) { - return snip(expression[1], UnknownData); + return snip(expression[1], UnknownData, /* ref */ true); } if (expression[0] === NODE.preUpdate) { diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index f6cedea60e..177abba86a 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -57,7 +57,7 @@ describe('tgpu resolve', () => { [$gpuValueOf]: { [$internal]: true, get [$ownSnippet]() { - return snip(this, d.f32); + return snip(this, d.f32, false); }, [$resolve]: (ctx: ResolutionCtx) => ctx.resolve(intensity), } as unknown as number, @@ -67,7 +67,7 @@ describe('tgpu resolve', () => { ctx.addDeclaration( `@group(0) @binding(0) var ${name}: f32;`, ); - return snip(name, d.f32); + return snip(name, d.f32, false); }, get value(): number { diff --git a/packages/typegpu/tests/tgsl/conversion.test.ts b/packages/typegpu/tests/tgsl/conversion.test.ts index ff610ab964..c63d14bfae 100644 --- a/packages/typegpu/tests/tgsl/conversion.test.ts +++ b/packages/typegpu/tests/tgsl/conversion.test.ts @@ -185,13 +185,13 @@ describe('getBestConversion', () => { }); describe('convertToCommonType', () => { - const snippetF32 = snip('2.22', d.f32); - const snippetI32 = snip('-12', d.i32); - const snippetU32 = snip('33', d.u32); - const snippetAbsFloat = snip('1.1', abstractFloat); - const snippetAbsInt = snip('1', abstractInt); - const snippetPtrF32 = snip('ptr_f32', d.ptrPrivate(d.f32)); - const snippetUnknown = snip('?', UnknownData); + const snippetF32 = snip('2.22', d.f32, false); + const snippetI32 = snip('-12', d.i32, false); + const snippetU32 = snip('33', d.u32, false); + const snippetAbsFloat = snip('1.1', abstractFloat, false); + const snippetAbsInt = snip('1', abstractInt, false); + const snippetPtrF32 = snip('ptr_f32', d.ptrPrivate(d.f32), true); + const snippetUnknown = snip('?', UnknownData, true); it('converts identical types', () => { const result = convertToCommonType([snippetF32, snippetF32]); @@ -241,7 +241,7 @@ describe('convertToCommonType', () => { }); it('returns undefined for incompatible types', () => { - const snippetVec2f = snip('v2', d.vec2f); + const snippetVec2f = snip('v2', d.vec2f, false); const result = convertToCommonType([snippetF32, snippetVec2f]); expect(result).toBeUndefined(); }); @@ -281,7 +281,10 @@ describe('convertToCommonType', () => { }); it('handles void gracefully', () => { - const result = convertToCommonType([snippetF32, snip('void', d.Void)]); + const result = convertToCommonType([ + snippetF32, + snip('void', d.Void, false), + ]); expect(result).toBeUndefined(); }); @@ -301,10 +304,10 @@ describe('convertStructValues', () => { it('maps values matching types exactly', () => { const snippets: Record = { - a: snip('1.0', d.f32), - b: snip('2', d.i32), - c: snip('vec2f(1.0, 1.0)', d.vec2f), - d: snip('true', d.bool), + a: snip('1.0', d.f32, false), + b: snip('2', d.i32, false), + c: snip('vec2f(1.0, 1.0)', d.vec2f, false), + d: snip('true', d.bool, false), }; const res = convertStructValues(structType, snippets); expect(res.length).toBe(4); @@ -316,25 +319,25 @@ describe('convertStructValues', () => { it('maps values requiring implicit casts and warns', () => { const snippets: Record = { - a: snip('1', d.i32), // i32 -> f32 (cast) - b: snip('2', d.u32), // u32 -> i32 (cast) - c: snip('2.22', d.f32), - d: snip('true', d.bool), + a: snip('1', d.i32, false), // i32 -> f32 (cast) + b: snip('2', d.u32, false), // u32 -> i32 (cast) + c: snip('2.22', d.f32, false), + d: snip('true', d.bool, false), }; const res = convertStructValues(structType, snippets); expect(res.length).toBe(4); - expect(res[0]).toEqual(snip('f32(1)', d.f32)); // Cast applied - expect(res[1]).toEqual(snip('i32(2)', d.i32)); // Cast applied + expect(res[0]).toEqual(snip('f32(1)', d.f32, false)); // Cast applied + expect(res[1]).toEqual(snip('i32(2)', d.i32, false)); // Cast applied expect(res[2]).toEqual(snippets.c); expect(res[3]).toEqual(snippets.d); }); it('throws on missing property', () => { const snippets: Record = { - a: snip('1.0', d.f32), + a: snip('1.0', d.f32, false), // b is missing - c: snip('vec2f(1.0, 1.0)', d.vec2f), - d: snip('true', d.bool), + c: snip('vec2f(1.0, 1.0)', d.vec2f, false), + d: snip('true', d.bool, false), }; expect(() => convertStructValues(structType, snippets)).toThrow( /Missing property b/, diff --git a/packages/typegpu/tests/tgsl/generationHelpers.test.ts b/packages/typegpu/tests/tgsl/generationHelpers.test.ts index 9cf27f8d82..087dd07371 100644 --- a/packages/typegpu/tests/tgsl/generationHelpers.test.ts +++ b/packages/typegpu/tests/tgsl/generationHelpers.test.ts @@ -64,30 +64,32 @@ describe('generationHelpers', () => { describe('numericLiteralToSnippet', () => { it('should convert numeric literals to correct snippets', () => { expect(numericLiteralToSnippet(1)).toEqual( - snip(1, abstractInt), + snip(1, abstractInt, false), ); expect(numericLiteralToSnippet(1.1)).toEqual( - snip(1.1, abstractFloat), + snip(1.1, abstractFloat, false), ); expect(numericLiteralToSnippet(1e10)).toEqual( - snip(1e10, abstractInt), + snip(1e10, abstractInt, false), ); expect(numericLiteralToSnippet(0.5)).toEqual( - snip(0.5, abstractFloat), + snip(0.5, abstractFloat, false), ); expect(numericLiteralToSnippet(-45)).toEqual( - snip(-45, abstractInt), + snip(-45, abstractInt, false), ); expect(numericLiteralToSnippet(0x1A)).toEqual( - snip(0x1A, abstractInt), + snip(0x1A, abstractInt, false), ); - expect(numericLiteralToSnippet(0b101)).toEqual(snip(5, abstractInt)); + expect(numericLiteralToSnippet(0b101)).toEqual( + snip(5, abstractInt, false), + ); }); }); @@ -142,21 +144,21 @@ describe('generationHelpers', () => { const arr = arrayOf(f32, 2); it('coerces JS numbers', () => { - expect(coerceToSnippet(1)).toEqual(snip(1, abstractInt)); - expect(coerceToSnippet(2.5)).toEqual(snip(2.5, abstractFloat)); - expect(coerceToSnippet(-10)).toEqual(snip(-10, abstractInt)); - expect(coerceToSnippet(0.0)).toEqual(snip(0, abstractInt)); + expect(coerceToSnippet(1)).toEqual(snip(1, abstractInt, false)); + expect(coerceToSnippet(2.5)).toEqual(snip(2.5, abstractFloat, false)); + expect(coerceToSnippet(-10)).toEqual(snip(-10, abstractInt, false)); + expect(coerceToSnippet(0.0)).toEqual(snip(0, abstractInt, false)); }); it('coerces JS booleans', () => { - expect(coerceToSnippet(true)).toEqual(snip(true, bool)); - expect(coerceToSnippet(false)).toEqual(snip(false, bool)); + expect(coerceToSnippet(true)).toEqual(snip(true, bool, false)); + expect(coerceToSnippet(false)).toEqual(snip(false, bool, false)); }); it(`coerces schemas to UnknownData (as they're not instance types)`, () => { - expect(coerceToSnippet(f32)).toEqual(snip(f32, UnknownData)); - expect(coerceToSnippet(vec3i)).toEqual(snip(vec3i, UnknownData)); - expect(coerceToSnippet(arr)).toEqual(snip(arr, UnknownData)); + expect(coerceToSnippet(f32)).toEqual(snip(f32, UnknownData, true)); + expect(coerceToSnippet(vec3i)).toEqual(snip(vec3i, UnknownData, true)); + expect(coerceToSnippet(arr)).toEqual(snip(arr, UnknownData, true)); }); it('coerces arrays to unknown', () => { @@ -180,12 +182,14 @@ describe('generationHelpers', () => { }); it('returns UnknownData for other types', () => { - expect(coerceToSnippet('foo')).toEqual(snip('foo', UnknownData)); - expect(coerceToSnippet({})).toEqual(snip({}, UnknownData)); - expect(coerceToSnippet(null)).toEqual(snip(null, UnknownData)); - expect(coerceToSnippet(undefined)).toEqual(snip(undefined, UnknownData)); + expect(coerceToSnippet('foo')).toEqual(snip('foo', UnknownData, true)); + expect(coerceToSnippet({})).toEqual(snip({}, UnknownData, true)); + expect(coerceToSnippet(null)).toEqual(snip(null, UnknownData, true)); + expect(coerceToSnippet(undefined)).toEqual( + snip(undefined, UnknownData, true), + ); const fn = () => {}; - expect(coerceToSnippet(fn)).toEqual(snip(fn, UnknownData)); + expect(coerceToSnippet(fn)).toEqual(snip(fn, UnknownData, true)); }); }); }); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index be4d5c5a2e..4e04ffca64 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -284,7 +284,7 @@ describe('wgslGenerator', () => { } const args = astInfo.ast.params.map((arg) => - snip((arg as { type: 'i'; name: string }).name, d.u32) + snip((arg as { type: 'i'; name: string }).name, d.u32, false) ); provideCtx(ctx, () => { @@ -467,7 +467,7 @@ describe('wgslGenerator', () => { provideCtx(ctx, () => { ctx[$internal].itemStateStack.pushFunctionScope( - [snip('idx', d.u32)], + [snip('idx', d.u32, false)], {}, d.f32, astInfo.externals ?? {}, From c5189be34eb0249e6c9439f268cdc46ee7219b55 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 14:38:28 +0200 Subject: [PATCH 02/59] More progress on the implementation --- .../examples/rendering/ray-marching/index.ts | 2 + .../typegpu/src/core/function/dualImpl.ts | 28 ++++-- .../src/core/function/shelllessImpl.ts | 2 + packages/typegpu/src/core/function/tgpuFn.ts | 24 +++-- packages/typegpu/src/data/dualFn.ts | 8 +- packages/typegpu/src/data/ptr.ts | 1 + packages/typegpu/src/data/wgslTypes.ts | 3 +- packages/typegpu/src/tgsl/conversion.ts | 2 +- packages/typegpu/src/tgsl/shellless.ts | 39 ++++++-- packages/typegpu/src/tgsl/wgslGenerator.ts | 96 ++++++++++++------- packages/typegpu/tests/array.test.ts | 2 +- .../typegpu/tests/tgsl/consoleLog.test.ts | 2 +- packages/typegpu/tests/tgsl/shellless.test.ts | 27 ++++++ .../typegpu/tests/tgsl/wgslGenerator.test.ts | 6 +- packages/typegpu/tests/tgslFn.test.ts | 13 +-- 15 files changed, 176 insertions(+), 79 deletions(-) diff --git a/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts b/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts index 452ca74b2b..3ff96aad05 100644 --- a/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts @@ -127,8 +127,10 @@ const rayMarch = (ro: d.v3f, rd: d.v3f): Shape => { }); for (let i = 0; i < MAX_STEPS; i++) { + // TODO: Detect this pattern??? I think we can do this in unplugin const p = std.add(ro, std.mul(rd, dO)); const scene = getSceneDist(p); + // p is not being used afterwards dO += scene.dist; if (dO > MAX_DIST || scene.dist < SURF_DIST) { diff --git a/packages/typegpu/src/core/function/dualImpl.ts b/packages/typegpu/src/core/function/dualImpl.ts index 03b69bbde0..9220f27937 100644 --- a/packages/typegpu/src/core/function/dualImpl.ts +++ b/packages/typegpu/src/core/function/dualImpl.ts @@ -10,7 +10,6 @@ import { setName } from '../../shared/meta.ts'; import { $internal } from '../../shared/symbols.ts'; import { tryConvertSnippet } from '../../tgsl/conversion.ts'; import type { AnyData } from '../../data/dataTypes.ts'; -import { isNaturallyRef } from '../../data/wgslTypes.ts'; function isKnownAtComptime(value: unknown): boolean { return typeof value !== 'string' && getOwnSnippet(value) === undefined; @@ -53,6 +52,12 @@ interface DualImplOptions unknown> { | (( ...inArgTypes: MapValueToDataType> ) => { argTypes: AnyData[]; returnType: AnyData }); + /** + * Whether the function should skip trying to execute the "normal" implementation if + * all arguments are known at compile time. + * @default false + */ + readonly noComptime?: boolean | undefined; readonly ignoreImplicitCastWarning?: boolean | undefined; } @@ -67,15 +72,17 @@ export function dualImpl unknown>( : options.signature; const argSnippets = args as MapValueToSnippet>; - const converted = argSnippets.map((s, idx) => - tryConvertSnippet( - s, - argTypes[idx] as AnyData, - !options.ignoreImplicitCastWarning, - ) - ) as MapValueToSnippet>; + const converted = argSnippets.map((s, idx) => { + const argType = argTypes[idx] as AnyData | undefined; + if (!argType) { + throw new Error('Function called with invalid arguments'); + } + return tryConvertSnippet(s, argType, !options.ignoreImplicitCastWarning); + }) as MapValueToSnippet>; - if (converted.every((s) => isKnownAtComptime(s.value))) { + if ( + !options.noComptime && converted.every((s) => isKnownAtComptime(s.value)) + ) { return snip( options.normalImpl(...converted.map((s) => s.value) as never[]), returnType, @@ -101,6 +108,9 @@ export function dualImpl unknown>( value: { jsImpl: options.normalImpl, gpuImpl, + strictSignature: typeof options.signature !== 'function' + ? options.signature + : undefined, argConversionHint: 'keep', }, }); diff --git a/packages/typegpu/src/core/function/shelllessImpl.ts b/packages/typegpu/src/core/function/shelllessImpl.ts index 02b249f602..14e4f15c86 100644 --- a/packages/typegpu/src/core/function/shelllessImpl.ts +++ b/packages/typegpu/src/core/function/shelllessImpl.ts @@ -23,6 +23,7 @@ import { createFnCore } from './fnCore.ts'; */ export interface ShelllessImpl extends SelfResolvable { readonly resourceType: 'shellless-impl'; + readonly argTypes: AnyData[]; readonly [$getNameForward]: unknown; } @@ -36,6 +37,7 @@ export function createShelllessImpl( [$internal]: true, [$getNameForward]: core, resourceType: 'shellless-impl' as const, + argTypes, [$resolve](ctx: ResolutionCtx): ResolvedSnippet { return core.resolve(ctx, argTypes, undefined); diff --git a/packages/typegpu/src/core/function/tgpuFn.ts b/packages/typegpu/src/core/function/tgpuFn.ts index 6804e4c35d..b06fc53d7c 100644 --- a/packages/typegpu/src/core/function/tgpuFn.ts +++ b/packages/typegpu/src/core/function/tgpuFn.ts @@ -8,7 +8,7 @@ import { import { schemaCallWrapper } from '../../data/schemaCallWrapper.ts'; import { Void } from '../../data/wgslTypes.ts'; import { ExecutionError } from '../../errors.ts'; -import { provideInsideTgpuFn } from '../../execMode.ts'; +import { getResolutionCtx, provideInsideTgpuFn } from '../../execMode.ts'; import type { TgpuNamable } from '../../shared/meta.ts'; import { getName, setName } from '../../shared/meta.ts'; import type { Infer } from '../../shared/repr.ts'; @@ -35,7 +35,7 @@ import { type TgpuAccessor, type TgpuSlot, } from '../slot/slotTypes.ts'; -import { createDualImpl } from './dualImpl.ts'; +import { createDualImpl, dualImpl } from './dualImpl.ts'; import { createFnCore, type FnCore } from './fnCore.ts'; import type { AnyFn, @@ -214,8 +214,11 @@ function createFn( }, } as This; - const call = createDualImpl>( - (...args) => + const call = dualImpl>({ + name: 'tgpuFnCall', + noComptime: true, + signature: { argTypes: shell.argTypes, returnType: shell.returnType }, + normalImpl: (...args) => provideInsideTgpuFn(() => { try { if (typeof implementation === 'string') { @@ -239,10 +242,14 @@ function createFn( } }), // Functions give up ownership of their return value (so ref is false) - (...args) => snip(new FnCall(fn, args), shell.returnType, /* ref */ false), - 'tgpuFnCall', - shell.argTypes, - ); + codegenImpl: (...args) => { + // biome-ignore lint/style/noNonNullAssertion: it's there + const ctx = getResolutionCtx()!; + return ctx.withResetIndentLevel(() => + stitch`${ctx.resolve(fn).value}(${args})` + ); + }, + }); const fn = Object.assign(call, fnBase as This) as unknown as TgpuFn< ImplSchema @@ -321,6 +328,7 @@ function createBoundFunction( return fn; } +// TODO: Perhaps remove class FnCall implements SelfResolvable { readonly [$internal] = true; readonly [$ownSnippet]: Snippet; diff --git a/packages/typegpu/src/data/dualFn.ts b/packages/typegpu/src/data/dualFn.ts index d208460552..e63e3a330e 100644 --- a/packages/typegpu/src/data/dualFn.ts +++ b/packages/typegpu/src/data/dualFn.ts @@ -1,13 +1,19 @@ import type { $internal } from '../shared/symbols.ts'; import type { FnArgsConversionHint } from '../types.ts'; +import type { AnyData } from './dataTypes.ts'; import type { MapValueToSnippet, Snippet } from './snippet.ts'; -export type DualFn unknown> = +export type DualFn< + TImpl extends (...args: never[]) => unknown = (...args: never[]) => unknown, +> = & TImpl & { readonly [$internal]: { jsImpl: TImpl; gpuImpl: (...args: MapValueToSnippet>) => Snippet; argConversionHint: FnArgsConversionHint; + strictSignature?: + | { argTypes: AnyData[]; returnType: AnyData } + | undefined; }; }; diff --git a/packages/typegpu/src/data/ptr.ts b/packages/typegpu/src/data/ptr.ts index ed5c28f1a7..00403e2ad2 100644 --- a/packages/typegpu/src/data/ptr.ts +++ b/packages/typegpu/src/data/ptr.ts @@ -53,5 +53,6 @@ function INTERNAL_createPtr< addressSpace, inner, access, + toString: () => `ptr<${addressSpace}, ${inner}, ${access}>`, } as Ptr; } diff --git a/packages/typegpu/src/data/wgslTypes.ts b/packages/typegpu/src/data/wgslTypes.ts index 16cde50c06..9dd9f04ded 100644 --- a/packages/typegpu/src/data/wgslTypes.ts +++ b/packages/typegpu/src/data/wgslTypes.ts @@ -1571,7 +1571,8 @@ export type StorableData = | ScalarData | VecData | MatData - | Atomic + | Atomic + | Atomic | WgslArray | WgslStruct; diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 06f66f495a..230ad1cf7d 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -335,7 +335,7 @@ export function tryConvertSnippet( if (!converted) { throw new WgslTypeError( - `Cannot convert value of type '${snippet.dataType.type}' to type '${targetDataType.type}'`, + `Cannot convert value of type '${snippet.dataType}' to type '${targetDataType.type}'`, ); } diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index 0dfc7dc059..50b7e2c380 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -3,39 +3,58 @@ import { type ShelllessImpl, } from '../core/function/shelllessImpl.ts'; import type { AnyData } from '../data/dataTypes.ts'; +import { ptrFn } from '../data/ptr.ts'; import type { Snippet } from '../data/snippet.ts'; +import { isPtr, type StorableData } from '../data/wgslTypes.ts'; import { getMetaData } from '../shared/meta.ts'; import { concretize } from './generationHelpers.ts'; -interface ShelllessVariant { - argTypes: AnyData[]; - value: ShelllessImpl; -} - type AnyFn = (...args: never[]) => unknown; +function shallowEqualSchemas(a: AnyData, b: AnyData): boolean { + if (a.type !== b.type) return false; + if (a.type === 'ptr' && b.type === 'ptr') { + return a.access === b.access && + a.addressSpace === b.addressSpace && + shallowEqualSchemas(a.inner, b.inner); + } + if (a.type === 'array' && b.type === 'array') { + return a.elementCount === b.elementCount && + shallowEqualSchemas(a.elementType as AnyData, b.elementType as AnyData); + } + return true; +} + export class ShelllessRepository { - cache = new Map(); + cache = new Map(); get(fn: AnyFn, argSnippets: Snippet[]): ShelllessImpl | undefined { const meta = getMetaData(fn); if (!meta?.ast) return undefined; - const argTypes = argSnippets.map((s) => concretize(s.dataType as AnyData)); + const argTypes = argSnippets.map((s) => { + const type = concretize(s.dataType as AnyData); + return s.ref && !isPtr(type) ? ptrFn(type as StorableData) : type; + }); let cache = this.cache.get(fn); if (cache) { const variant = cache.find((v) => - v.argTypes.every((t, i) => t === argTypes[i]) + v.argTypes.length === argTypes.length && + v.argTypes.every((t, i) => + shallowEqualSchemas(t, argTypes[i] as AnyData) + ) ); - if (variant) return variant.value; + if (variant) { + return variant; + } } else { cache = []; this.cache.set(fn, cache); } const shellless = createShelllessImpl(argTypes, fn); - cache.push({ argTypes, value: shellless }); + cache.push(shellless); return shellless; } } diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index dbc6d48f4e..fcd251c3a8 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -34,6 +34,7 @@ import { } from './generationHelpers.ts'; import type { ShaderGenerator } from './shaderGenerator.ts'; import { safeStringify } from '../shared/safeStringify.ts'; +import type { DualFn } from '../data/dualFn.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -284,21 +285,6 @@ ${this.ctx.pre}}`; return snip(new ConsoleLog(), UnknownData, /* ref */ true); } - if ( - infixKinds.includes(target.dataType.type) && - property in infixOperators - ) { - return snip( - new InfixDispatch( - property, - target, - infixOperators[property as InfixOperator][$internal].gpuImpl, - ), - UnknownData, - /* ref */ true, - ); - } - if (target.dataType.type === 'unknown') { // No idea what the type is, so we act on the snippet's value and try to guess @@ -309,37 +295,49 @@ ${this.ctx.pre}}`; return coerceToSnippet(propValue); } + let targetDataType = target.dataType; + let isPtr = false; + if (wgsl.isPtr(target.dataType)) { - const propType = getTypeForPropAccess( - target.dataType.inner as AnyData, - property, - ); + isPtr = true; + targetDataType = target.dataType.inner as AnyData; + } + if ( + infixKinds.includes(targetDataType.type) && + property in infixOperators + ) { return snip( - `(*${this.ctx.resolve(target.value).value}).${property}`, - propType, - /* ref */ wgsl.isNaturallyRef(propType), + new InfixDispatch( + property, + target, + infixOperators[property as InfixOperator][$internal].gpuImpl, + ), + UnknownData, + /* ref */ true, ); } - if (wgsl.isWgslArray(target.dataType) && property === 'length') { - if (target.dataType.elementCount === 0) { + if (wgsl.isWgslArray(targetDataType) && property === 'length') { + if (targetDataType.elementCount === 0) { // Dynamically-sized array return snip( - `arrayLength(&${this.ctx.resolve(target.value).value})`, + isPtr + ? `arrayLength(${this.ctx.resolve(target.value).value})` + : `arrayLength(&${this.ctx.resolve(target.value).value})`, u32, /* ref */ false, ); } return snip( - String(target.dataType.elementCount), + String(targetDataType.elementCount), abstractInt, /* ref */ false, ); } - if (wgsl.isMat(target.dataType) && property === 'columns') { + if (wgsl.isMat(targetDataType) && property === 'columns') { return snip( new MatrixColumnsAccess(target), UnknownData, @@ -348,16 +346,18 @@ ${this.ctx.pre}}`; } if ( - wgsl.isVec(target.dataType) && wgsl.isVecInstance(target.value) + wgsl.isVec(targetDataType) && wgsl.isVecInstance(target.value) ) { // We're operating on a vector that's known at resolution time // biome-ignore lint/suspicious/noExplicitAny: it's probably a swizzle return coerceToSnippet((target.value as any)[property]); } - const propType = getTypeForPropAccess(target.dataType, property); + const propType = getTypeForPropAccess(targetDataType, property); return snip( - `${this.ctx.resolve(target.value).value}.${property}`, + isPtr + ? `(*${this.ctx.resolve(target.value).value}).${property}` + : `${this.ctx.resolve(target.value).value}.${property}`, propType, /* ref */ target.ref && wgsl.isNaturallyRef(propType), ); @@ -496,10 +496,15 @@ ${this.ctx.pre}}`; args, ); if (shellless) { + const converted = args.map((s, idx) => { + const argType = shellless.argTypes[idx] as AnyData; + return tryConvertSnippet(s, argType, /* verbose */ false); + }); + return this.ctx.withResetIndentLevel(() => { const snippet = this.ctx.resolve(shellless); return snip( - stitch`${snippet.value}(${args})`, + stitch`${snippet.value}(${converted})`, snippet.dataType, /* ref */ false, ); @@ -517,10 +522,27 @@ ${this.ctx.pre}}`; const argConversionHint = callee.value[$internal] ?.argConversionHint as FnArgsConversionHint ?? 'keep'; + const strictSignature = (callee.value as DualFn)[$internal] + ?.strictSignature; + try { let convertedArguments: Snippet[]; - if (Array.isArray(argConversionHint)) { + if (strictSignature) { + // The function's signature does not depend on the context, so it can be used to + // give a hint to the argument expressions that a specific type is expected. + convertedArguments = argNodes.map((arg, i) => { + const argType = strictSignature.argTypes[i]; + if (!argType) { + throw new WgslTypeError( + `Function '${ + getName(callee.value) + }' was called with too many arguments`, + ); + } + return this.typedExpression(arg, argType); + }); + } else if (Array.isArray(argConversionHint)) { // The hint is an array of schemas. convertedArguments = argNodes.map((arg, i) => { const argType = argConversionHint[i]; @@ -566,9 +588,13 @@ ${this.ctx.pre}}`; ); } return fnRes; - } catch (error) { - throw new ResolutionError(error, [{ - toString: () => getName(callee.value), + } catch (err) { + if (err instanceof ResolutionError) { + throw err; + } + + throw new ResolutionError(err, [{ + toString: () => `fn:${getName(callee.value)}`, }]); } } diff --git a/packages/typegpu/tests/array.test.ts b/packages/typegpu/tests/array.test.ts index 230c3eac95..abd0a90ebd 100644 --- a/packages/typegpu/tests/array.test.ts +++ b/packages/typegpu/tests/array.test.ts @@ -255,7 +255,7 @@ describe('array', () => { [Error: Resolution of the following tree failed: - - fn:foo - - arrayOf: Cannot create array schema with count unknown at compile-time: 'count'] + - fn:arrayOf: Cannot create array schema with count unknown at compile-time: 'count'] `); }); diff --git a/packages/typegpu/tests/tgsl/consoleLog.test.ts b/packages/typegpu/tests/tgsl/consoleLog.test.ts index 1938646fbb..1f84f058c9 100644 --- a/packages/typegpu/tests/tgsl/consoleLog.test.ts +++ b/packages/typegpu/tests/tgsl/consoleLog.test.ts @@ -299,7 +299,7 @@ describe('wgslGenerator with console.log', () => { - computePipeline:pipeline - computePipelineCore - computeFn:fn - - consoleLog: Logged data needs to fit in 252 bytes (one of the logs requires 256 bytes). Consider increasing the limit by passing appropriate options to tgpu.init().] + - fn:consoleLog: Logged data needs to fit in 252 bytes (one of the logs requires 256 bytes). Consider increasing the limit by passing appropriate options to tgpu.init().] `); }); }); diff --git a/packages/typegpu/tests/tgsl/shellless.test.ts b/packages/typegpu/tests/tgsl/shellless.test.ts index 23e17d0802..b961cbb2ac 100644 --- a/packages/typegpu/tests/tgsl/shellless.test.ts +++ b/packages/typegpu/tests/tgsl/shellless.test.ts @@ -165,4 +165,31 @@ describe('shellless', () => { }" `); }); + + it('generates pointer type to handle references', () => { + const advance = (pos: d.v3f, vel: d.v3f) => { + 'kernel'; + pos.x += vel.x; + pos.y += vel.y; + pos.z += vel.z; + }; + + const main = tgpu.fn([])(() => { + const pos = d.vec3f(0, 0, 0); + advance(pos, d.vec3f(1, 2, 3)); + }); + + expect(asWgsl(main)).toMatchInlineSnapshot(` + "fn advance(pos: ptr, vel: vec3f) { + (*pos).x += vel.x; + (*pos).y += vel.y; + (*pos).z += vel.z; + } + + fn main() { + var pos = vec3f(); + advance(&pos, vec3f(1, 2, 3)); + }" + `); + }); }); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 4e04ffca64..613f54ff5c 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -872,7 +872,7 @@ describe('wgslGenerator', () => { [Error: Resolution of the following tree failed: - - fn:testFn - - internalTestFn: Cannot convert value of type 'array' to type 'vec2f'] + - fn:internalTestFn: Cannot convert value of type 'arrayOf(i32, 3)' to type 'vec2f'] `); }); @@ -886,7 +886,7 @@ describe('wgslGenerator', () => { [Error: Resolution of the following tree failed: - - fn:testFn - - translate4: Cannot read properties of undefined (reading 'dataType')] + - fn:translate4: Cannot read properties of undefined (reading 'dataType')] `); }); @@ -902,7 +902,7 @@ describe('wgslGenerator', () => { [Error: Resolution of the following tree failed: - - fn:testFn - - vec4f: Cannot convert value of type 'array' to type 'f32'] + - fn:vec4f: Cannot convert value of type 'arrayOf(i32, 4)' to type 'f32'] `); }); diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index 5ee7988635..ee1a77e926 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -973,10 +973,9 @@ describe('tgsl fn when using plugin', () => { [Error: Resolution of the following tree failed: - - fn:bar - - call:foo - fn:foo - - call:bar: Recursive function fn:bar detected. Recursion is not allowed on the GPU.] - `); + - fn:bar: Recursive function fn:bar detected. Recursion is not allowed on the GPU.] + `); }); it('throws when it detects a cyclic dependency (when using slots)', () => { @@ -996,13 +995,10 @@ describe('tgsl fn when using plugin', () => { [Error: Resolution of the following tree failed: - - fn:one - - call:two - fn:two - - call:three - fn:three - - call:inner - fn:inner - - call:one: Recursive function fn:one detected. Recursion is not allowed on the GPU.] + - fn:one: Recursive function fn:one detected. Recursion is not allowed on the GPU.] `); }); @@ -1028,9 +1024,8 @@ describe('tgsl fn when using plugin', () => { [Error: Resolution of the following tree failed: - - fn:one - - call:fallbackFn - fn:fallbackFn - - call:one: Recursive function fn:one detected. Recursion is not allowed on the GPU.] + - fn:one: Recursive function fn:one detected. Recursion is not allowed on the GPU.] `); const boundOne = one.with(flagSlot, true); From eb8f520033183466b42fff1a783c1d8224ad2a7a Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 15:02:39 +0200 Subject: [PATCH 03/59] Mostly works now --- packages/typegpu/src/data/dataTypes.ts | 15 ++ packages/typegpu/src/data/vector.ts | 4 +- packages/typegpu/src/std/operators.ts | 7 +- .../tests/examples/individual/3d-fish.test.ts | 10 +- .../examples/individual/caustics.test.ts | 21 ++- .../individual/cubemap-reflection.test.ts | 40 +++--- .../individual/fluid-with-atomics.test.ts | 132 +++++++++--------- .../examples/individual/ray-marching.test.ts | 78 +++++++---- 8 files changed, 177 insertions(+), 130 deletions(-) diff --git a/packages/typegpu/src/data/dataTypes.ts b/packages/typegpu/src/data/dataTypes.ts index 145d7f398d..5566481da0 100644 --- a/packages/typegpu/src/data/dataTypes.ts +++ b/packages/typegpu/src/data/dataTypes.ts @@ -128,6 +128,21 @@ export function undecorate(data: AnyData): AnyData { return data; } +export function unptr(data: AnyData): AnyData { + if (data.type === 'ptr') { + return data.inner as AnyData; + } + return data; +} + +export function toStorable(schema: AnyData): AnyData { + return undecorate(unptr(undecorate(schema))); +} + +export function toStorables(schemas: T): T { + return schemas.map(toStorable) as T; +} + const looseTypeLiterals = [ 'unstruct', 'disarray', diff --git a/packages/typegpu/src/data/vector.ts b/packages/typegpu/src/data/vector.ts index 2ced533621..dfebf7432e 100644 --- a/packages/typegpu/src/data/vector.ts +++ b/packages/typegpu/src/data/vector.ts @@ -1,7 +1,7 @@ import { dualImpl } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; import { $repr } from '../shared/symbols.ts'; -import { type AnyData, undecorate } from './dataTypes.ts'; +import { type AnyData, toStorable } from './dataTypes.ts'; import { bool, f16, f32, i32, u32 } from './numeric.ts'; import { Vec2bImpl, @@ -311,7 +311,7 @@ function makeVecSchema( name: type, signature: (...args) => ({ argTypes: args.map((arg) => { - const argType = undecorate(arg); + const argType = toStorable(arg); return isVec(argType) ? argType : primitive; }), returnType: schema as AnyData, diff --git a/packages/typegpu/src/std/operators.ts b/packages/typegpu/src/std/operators.ts index f050d36877..a140592e2d 100644 --- a/packages/typegpu/src/std/operators.ts +++ b/packages/typegpu/src/std/operators.ts @@ -1,5 +1,6 @@ import { dualImpl } from '../core/function/dualImpl.ts'; import { stitch, stitchWithExactTypes } from '../core/resolve/stitch.ts'; +import { toStorables } from '../data/dataTypes.ts'; import { abstractFloat, f16, f32 } from '../data/numeric.ts'; import { vecTypeToConstructor } from '../data/vector.ts'; import { VectorOps } from '../data/vectorOps.ts'; @@ -54,7 +55,8 @@ function cpuAdd(lhs: number | NumVec | Mat, rhs: number | NumVec | Mat) { export const add = dualImpl({ name: 'add', signature: (...args) => { - const uargs = unify(args) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; return { argTypes: uargs, returnType: isNumericSchema(uargs[0]) ? uargs[1] : uargs[0], @@ -138,7 +140,8 @@ function cpuMul(lhs: number | NumVec | Mat, rhs: number | NumVec | Mat) { export const mul = dualImpl({ name: 'mul', signature: (...args) => { - const uargs = unify(args) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; const returnType = isNumericSchema(uargs[0]) // Scalar * Scalar/Vector/Matrix ? uargs[1] diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index b614daf176..56ebcf2fc9 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -118,10 +118,10 @@ describe('3d fish example', () => { @group(0) @binding(2) var mouseRay_5: MouseRay_6; - fn projectPointOnLine_8(point: vec3f, line: Line3_7) -> vec3f { - var pointVector = (point - line.origin); - var projection = dot(pointVector, line.dir); - return (line.origin + (line.dir * projection)); + fn projectPointOnLine_8(point: ptr, line: ptr) -> vec3f { + var pointVector = (*point - (*line).origin); + var projection = dot(pointVector, (*line).dir); + return ((*line).origin + ((*line).dir * projection)); } @group(0) @binding(3) var timePassed_9: f32; @@ -182,7 +182,7 @@ describe('3d fish example', () => { } } if ((mouseRay_5.activated == 1)) { - var proj = projectPointOnLine_8(fishData.position, mouseRay_5.line); + var proj = projectPointOnLine_8(&fishData.position, &mouseRay_5.line); var diff = (fishData.position - proj); var limit = 0.9; var str = (pow(2, clamp((limit - length(diff)), 0, limit)) - 1); diff --git a/packages/typegpu/tests/examples/individual/caustics.test.ts b/packages/typegpu/tests/examples/individual/caustics.test.ts index f4a1f36cd6..c7979f2cd5 100644 --- a/packages/typegpu/tests/examples/individual/caustics.test.ts +++ b/packages/typegpu/tests/examples/individual/caustics.test.ts @@ -106,34 +106,41 @@ describe('caustics example', () => { return mix(x, X, smoothPartial.x); } - fn caustics_7(uv: vec2f, time2: f32, profile: vec3f) -> vec3f { + fn caustics_7(uv: ptr, time2: f32, profile: vec3f) -> vec3f { + var distortion = sample_8(vec3f((*uv * 0.5), (time2 * 0.2))); + var uv2 = (*uv + distortion); + var noise = abs(sample_8(vec3f((uv2 * 5), time2))); + return pow(vec3f((1 - noise)), profile); + } + + fn caustics_17(uv: vec2f, time2: f32, profile: vec3f) -> vec3f { var distortion = sample_8(vec3f((uv * 0.5), (time2 * 0.2))); var uv2 = (uv + distortion); var noise = abs(sample_8(vec3f((uv2 * 5), time2))); return pow(vec3f((1 - noise)), profile); } - fn rotateXY_17(angle2: f32) -> mat2x2f { + fn rotateXY_18(angle2: f32) -> mat2x2f { return mat2x2f(vec2f(cos(angle2), sin(angle2)), vec2f(-sin(angle2), cos(angle2))); } - struct mainFragment_Input_18 { + struct mainFragment_Input_19 { @location(0) uv: vec2f, } - @fragment fn mainFragment_3(_arg_0: mainFragment_Input_18) -> @location(0) vec4f { + @fragment fn mainFragment_3(_arg_0: mainFragment_Input_19) -> @location(0) vec4f { var skewMat = mat2x2f(vec2f(0.9800665974617004, 0.19866932928562164), vec2f(((-0.19866933079506122 * 10) + (_arg_0.uv.x * 3)), 4.900332889206208)); var skewedUv = (skewMat * _arg_0.uv); var tile = tilePattern_5((skewedUv * tileDensity_4)); var albedo = mix(vec3f(0.10000000149011612), vec3f(1), tile); var cuv = vec2f(((_arg_0.uv.x * (pow((_arg_0.uv.y * 1.5), 3) + 0.1)) * 5), (pow((((_arg_0.uv.y * 1.5) + 0.1) * 1.5), 3) * 1)); - var c1 = (caustics_7(cuv, (time_6 * 0.2), vec3f(4, 4, 1)) * vec3f(0.4000000059604645, 0.6499999761581421, 1)); - var c2 = (caustics_7((cuv * 2), (time_6 * 0.4), vec3f(16, 1, 4)) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)); + var c1 = (caustics_7(&cuv, (time_6 * 0.2), vec3f(4, 4, 1)) * vec3f(0.4000000059604645, 0.6499999761581421, 1)); + var c2 = (caustics_17((cuv * 2), (time_6 * 0.4), vec3f(16, 1, 4)) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)); var blendCoord = vec3f((_arg_0.uv * vec2f(5, 10)), ((time_6 * 0.2) + 5)); var blend = saturate((sample_8(blendCoord) + 0.3)); var noFogColor = (albedo * mix(vec3f(0.20000000298023224, 0.5, 1), (c1 + c2), blend)); var fog = min((pow(_arg_0.uv.y, 0.5) * 1.2), 1); - var godRayUv = ((rotateXY_17(-0.3) * _arg_0.uv) * vec2f(15, 3)); + var godRayUv = ((rotateXY_18(-0.3) * _arg_0.uv) * vec2f(15, 3)); var godRayFactor = pow(_arg_0.uv.y, 1); var godRay1 = ((sample_8(vec3f(godRayUv, (time_6 * 0.5))) + 1) * (vec3f(0.18000000715255737, 0.30000001192092896, 0.5) * godRayFactor)); var godRay2 = ((sample_8(vec3f((godRayUv * 2), (time_6 * 0.3))) + 1) * (vec3f(0.18000000715255737, 0.30000001192092896, 0.5) * (godRayFactor * 0.4))); diff --git a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts index ceee363f04..ba7ee088b1 100644 --- a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts +++ b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts @@ -29,29 +29,29 @@ describe('cubemap reflection example', () => { @group(0) @binding(0) var prevVertices_1: array; - fn unpackVec2u_3(packed: vec2u) -> vec4f { - var xy = unpack2x16float(packed.x); - var zw = unpack2x16float(packed.y); + fn unpackVec2u_3(packed: ptr) -> vec4f { + var xy = unpack2x16float((*packed).x); + var zw = unpack2x16float((*packed).y); return vec4f(xy, zw); } - fn calculateMidpoint_4(v1: vec4f, v2: vec4f) -> vec4f { - return vec4f((0.5 * (v1.xyz + v2.xyz)), 1); + fn calculateMidpoint_4(v1: ptr, v2: ptr) -> vec4f { + return vec4f((0.5 * ((*v1).xyz + (*v2).xyz)), 1); } @group(0) @binding(2) var smoothFlag_5: u32; - fn getAverageNormal_6(v1: vec4f, v2: vec4f, v3: vec4f) -> vec4f { - var edge1 = (v2.xyz - v1.xyz); - var edge2 = (v3.xyz - v1.xyz); + fn getAverageNormal_6(v1: ptr, v2: ptr, v3: ptr) -> vec4f { + var edge1 = ((*v2).xyz - (*v1).xyz); + var edge2 = ((*v3).xyz - (*v1).xyz); return normalize(vec4f(cross(edge1, edge2), 0)); } @group(0) @binding(1) var nextVertices_7: array; - fn packVec2u_8(toPack: vec4f) -> vec2u { - var xy = pack2x16float(toPack.xy); - var zw = pack2x16float(toPack.zw); + fn packVec2u_8(toPack: ptr) -> vec2u { + var xy = pack2x16float((*toPack).xy); + var zw = pack2x16float((*toPack).zw); return vec2u(xy, zw); } @@ -66,12 +66,12 @@ describe('cubemap reflection example', () => { return; } var baseIndexPrev = (triangleIndex * 3); - var v1 = unpackVec2u_3(prevVertices_1[baseIndexPrev].position); - var v2 = unpackVec2u_3(prevVertices_1[(baseIndexPrev + 1)].position); - var v3 = unpackVec2u_3(prevVertices_1[(baseIndexPrev + 2)].position); - var v12 = vec4f(normalize(calculateMidpoint_4(v1, v2).xyz), 1); - var v23 = vec4f(normalize(calculateMidpoint_4(v2, v3).xyz), 1); - var v31 = vec4f(normalize(calculateMidpoint_4(v3, v1).xyz), 1); + var v1 = unpackVec2u_3(&prevVertices_1[baseIndexPrev].position); + var v2 = unpackVec2u_3(&prevVertices_1[(baseIndexPrev + 1)].position); + var v3 = unpackVec2u_3(&prevVertices_1[(baseIndexPrev + 2)].position); + var v12 = vec4f(normalize(calculateMidpoint_4(&v1, &v2).xyz), 1); + var v23 = vec4f(normalize(calculateMidpoint_4(&v2, &v3).xyz), 1); + var v31 = vec4f(normalize(calculateMidpoint_4(&v3, &v1).xyz), 1); var newVertices = array(v1, v12, v31, v2, v23, v12, v3, v31, v23, v12, v23, v31); var baseIndexNext = (triangleIndex * 12); for (var i = 0u; (i < 12); i++) { @@ -79,12 +79,12 @@ describe('cubemap reflection example', () => { var triBase = (i - (i % 3)); var normal = reprojectedVertex; if ((smoothFlag_5 == 0)) { - normal = getAverageNormal_6(newVertices[triBase], newVertices[(triBase + 1)], newVertices[(triBase + 2)]); + normal = getAverageNormal_6(&newVertices[triBase], &newVertices[(triBase + 1)], &newVertices[(triBase + 2)]); } var outIndex = (baseIndexNext + i); var nextVertex = nextVertices_7[outIndex]; - nextVertex.position = packVec2u_8(reprojectedVertex); - nextVertex.normal = packVec2u_8(normal); + nextVertex.position = packVec2u_8(&reprojectedVertex); + nextVertex.normal = packVec2u_8(&normal); nextVertices_7[outIndex] = nextVertex; } } diff --git a/packages/typegpu/tests/examples/individual/fluid-with-atomics.test.ts b/packages/typegpu/tests/examples/individual/fluid-with-atomics.test.ts index 25bc0548b6..fcac496ffe 100644 --- a/packages/typegpu/tests/examples/individual/fluid-with-atomics.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-with-atomics.test.ts @@ -17,94 +17,94 @@ describe('fluid with atomics example', () => { }, device); expect(shaderCodes).toMatchInlineSnapshot(` - "@group(0) @binding(0) var size_5: vec2u; + "@group(0) @binding(0) var size_6: vec2u; - fn getIndex_4(x: u32, y: u32) -> u32 { - var h = size_5.y; - var w = size_5.x; + fn getIndex_5(x: u32, y: u32) -> u32 { + var h = size_6.y; + var w = size_6.x; return (((y % h) * w) + (x % w)); } - @group(0) @binding(1) var nextState_6: array, 1048576>; + @group(0) @binding(1) var currentStateBuffer_7: array; - fn updateCell_3(x: u32, y: u32, value: u32) { - atomicStore(&nextState_6[getIndex_4(x, y)], value); + fn getCell_4(x: u32, y: u32) -> u32 { + return currentStateBuffer_7[getIndex_5(x, y)]; } - @group(0) @binding(2) var currentStateBuffer_9: array; + fn isClearCell_3(x: u32, y: u32) -> bool { + return ((getCell_4(x, y) >> 24) == 4); + } + + @group(0) @binding(2) var nextState_9: array, 1048576>; - fn getCell_8(x: u32, y: u32) -> u32 { - return currentStateBuffer_9[getIndex_4(x, y)]; + fn updateCell_8(x: u32, y: u32, value: u32) { + atomicStore(&nextState_9[getIndex_5(x, y)], value); } - fn isClearCell_7(x: u32, y: u32) -> bool { - return ((getCell_8(x, y) >> 24) == 4); + fn isWall_10(x: u32, y: u32) -> bool { + return ((getCell_4(x, y) >> 24) == 1); } - const MAX_WATER_LEVEL_11: u32 = 16777215; + const MAX_WATER_LEVEL_12: u32 = 16777215; - fn persistFlags_10(x: u32, y: u32) { - var cell = getCell_8(x, y); - var waterLevel = (cell & MAX_WATER_LEVEL_11); + fn persistFlags_11(x: u32, y: u32) { + var cell = getCell_4(x, y); + var waterLevel = (cell & MAX_WATER_LEVEL_12); var flags = (cell >> 24); - updateCell_3(x, y, ((flags << 24) | waterLevel)); - } - - fn isWall_12(x: u32, y: u32) -> bool { - return ((getCell_8(x, y) >> 24) == 1); + updateCell_8(x, y, ((flags << 24) | waterLevel)); } - fn getCellNext_14(x: u32, y: u32) -> u32 { - return atomicLoad(&nextState_6[getIndex_4(x, y)]); + fn isWaterSource_13(x: u32, y: u32) -> bool { + return ((getCell_4(x, y) >> 24) == 2); } - fn addToCell_13(x: u32, y: u32, value: u32) { - var cell = getCellNext_14(x, y); - var waterLevel = (cell & MAX_WATER_LEVEL_11); - var newWaterLevel = min((waterLevel + value), MAX_WATER_LEVEL_11); - atomicAdd(&nextState_6[getIndex_4(x, y)], (newWaterLevel - waterLevel)); + fn getCellNext_15(x: u32, y: u32) -> u32 { + return atomicLoad(&nextState_9[getIndex_5(x, y)]); } - fn isWaterSource_15(x: u32, y: u32) -> bool { - return ((getCell_8(x, y) >> 24) == 2); + fn addToCell_14(x: u32, y: u32, value: u32) { + var cell = getCellNext_15(x, y); + var waterLevel = (cell & MAX_WATER_LEVEL_12); + var newWaterLevel = min((waterLevel + value), MAX_WATER_LEVEL_12); + atomicAdd(&nextState_9[getIndex_5(x, y)], (newWaterLevel - waterLevel)); } fn isWaterDrain_16(x: u32, y: u32) -> bool { - return ((getCell_8(x, y) >> 24) == 3); + return ((getCell_4(x, y) >> 24) == 3); } - fn subtractFromCell_17(x: u32, y: u32, value: u32) { - var cell = getCellNext_14(x, y); - var waterLevel = (cell & MAX_WATER_LEVEL_11); - var newWaterLevel = max((waterLevel - min(value, waterLevel)), 0); - atomicSub(&nextState_6[getIndex_4(x, y)], (waterLevel - newWaterLevel)); + fn getWaterLevel_17(x: u32, y: u32) -> u32 { + return (getCell_4(x, y) & MAX_WATER_LEVEL_12); } - fn getWaterLevel_18(x: u32, y: u32) -> u32 { - return (getCell_8(x, y) & MAX_WATER_LEVEL_11); + fn subtractFromCell_18(x: u32, y: u32, value: u32) { + var cell = getCellNext_15(x, y); + var waterLevel = (cell & MAX_WATER_LEVEL_12); + var newWaterLevel = max((waterLevel - min(value, waterLevel)), 0); + atomicSub(&nextState_9[getIndex_5(x, y)], (waterLevel - newWaterLevel)); } fn checkForFlagsAndBounds_2(x: u32, y: u32) -> bool { - if (isClearCell_7(x, y)) { - updateCell_3(x, y, 0); + if (isClearCell_3(x, y)) { + updateCell_8(x, y, 0); return true; } - if (isWall_12(x, y)) { - persistFlags_10(x, y); + if (isWall_10(x, y)) { + persistFlags_11(x, y); return true; } - if (isWaterSource_15(x, y)) { - persistFlags_10(x, y); - addToCell_13(x, y, 20); + if (isWaterSource_13(x, y)) { + persistFlags_11(x, y); + addToCell_14(x, y, 20); return false; } if (isWaterDrain_16(x, y)) { - persistFlags_10(x, y); - updateCell_3(x, y, (3 << 24)); + persistFlags_11(x, y); + updateCell_8(x, y, (3 << 24)); return true; } - if (((((y == 0) || (y == (size_5.y - 1))) || (x == 0)) || (x == (size_5.x - 1)))) { - subtractFromCell_17(x, y, getWaterLevel_18(x, y)); + if (((((y == 0) || (y == (size_6.y - 1))) || (x == 0)) || (x == (size_6.x - 1)))) { + subtractFromCell_18(x, y, getWaterLevel_17(x, y)); return true; } return false; @@ -131,18 +131,18 @@ describe('fluid with atomics example', () => { if (checkForFlagsAndBounds_2(x, y)) { return; } - var remainingWater = getWaterLevel_18(x, y); + var remainingWater = getWaterLevel_17(x, y); if ((remainingWater == 0)) { return; } - if (!isWall_12(x, (y - 1))) { - var waterLevelBelow = getWaterLevel_18(x, (y - 1)); + if (!isWall_10(x, (y - 1))) { + var waterLevelBelow = getWaterLevel_17(x, (y - 1)); var stable = getStableStateBelow_19(remainingWater, waterLevelBelow); if ((waterLevelBelow < stable)) { var change = (stable - waterLevelBelow); var flow = min(change, viscosity_22); - subtractFromCell_17(x, y, flow); - addToCell_13(x, (y - 1), flow); + subtractFromCell_18(x, y, flow); + addToCell_14(x, (y - 1), flow); remainingWater -= flow; } } @@ -150,38 +150,38 @@ describe('fluid with atomics example', () => { return; } var waterLevelBefore = remainingWater; - if (!isWall_12((x - 1), y)) { - var flowRaw = (i32(waterLevelBefore) - i32(getWaterLevel_18((x - 1), y))); + if (!isWall_10((x - 1), y)) { + var flowRaw = (i32(waterLevelBefore) - i32(getWaterLevel_17((x - 1), y))); if ((flowRaw > 0)) { var change = max(min(4, remainingWater), u32((f32(flowRaw) / 4f))); var flow = min(change, viscosity_22); - subtractFromCell_17(x, y, flow); - addToCell_13((x - 1), y, flow); + subtractFromCell_18(x, y, flow); + addToCell_14((x - 1), y, flow); remainingWater -= flow; } } if ((remainingWater == 0)) { return; } - if (!isWall_12((x + 1), y)) { - var flowRaw = (i32(waterLevelBefore) - i32(getWaterLevel_18((x + 1), y))); + if (!isWall_10((x + 1), y)) { + var flowRaw = (i32(waterLevelBefore) - i32(getWaterLevel_17((x + 1), y))); if ((flowRaw > 0)) { var change = max(min(4, remainingWater), u32((f32(flowRaw) / 4f))); var flow = min(change, viscosity_22); - subtractFromCell_17(x, y, flow); - addToCell_13((x + 1), y, flow); + subtractFromCell_18(x, y, flow); + addToCell_14((x + 1), y, flow); remainingWater -= flow; } } if ((remainingWater == 0)) { return; } - if (!isWall_12(x, (y + 1))) { - var stable = getStableStateBelow_19(getWaterLevel_18(x, (y + 1)), remainingWater); + if (!isWall_10(x, (y + 1))) { + var stable = getStableStateBelow_19(getWaterLevel_17(x, (y + 1)), remainingWater); if ((stable < remainingWater)) { var flow = min((remainingWater - stable), viscosity_22); - subtractFromCell_17(x, y, flow); - addToCell_13(x, (y + 1), flow); + subtractFromCell_18(x, y, flow); + addToCell_14(x, (y + 1), flow); remainingWater -= flow; } } diff --git a/packages/typegpu/tests/examples/individual/ray-marching.test.ts b/packages/typegpu/tests/examples/individual/ray-marching.test.ts index ed3fe6084e..334961c93a 100644 --- a/packages/typegpu/tests/examples/individual/ray-marching.test.ts +++ b/packages/typegpu/tests/examples/individual/ray-marching.test.ts @@ -52,18 +52,18 @@ describe('ray-marching example', () => { return min(min(d1, d2), d3); } - fn smoothShapeUnion_11(a: Shape_6, b: Shape_6, k: f32) -> Shape_6 { - var h = (max((k - abs((a.dist - b.dist))), 0) / k); + fn smoothShapeUnion_11(a: ptr, b: ptr, k: f32) -> Shape_6 { + var h = (max((k - abs(((*a).dist - (*b).dist))), 0) / k); var m = (h * h); - var dist = (min(a.dist, b.dist) - ((m * k) * 0.25)); - var weight = (m + select(0, (1 - m), (a.dist > b.dist))); - var color = mix(a.color, b.color, weight); + var dist = (min((*a).dist, (*b).dist) - ((m * k) * 0.25)); + var weight = (m + select(0, (1 - m), ((*a).dist > (*b).dist))); + var color = mix((*a).color, (*b).color, weight); return Shape_6(color, dist); } - fn getMorphingShape_8(p: vec3f, t: f32) -> Shape_6 { + fn getMorphingShape_8(p: ptr, t: f32) -> Shape_6 { var center = vec3f(0, 2, 6); - var localP = (p - center); + var localP = (*p - center); var rotMatZ = mat4x4f(cos(-t), sin(-t), 0, 0, -sin(-t), cos(-t), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); var rotMatX = mat4x4f(1, 0, 0, 0, 0, cos((-t * 0.6)), sin((-t * 0.6)), 0, 0, -sin((-t * 0.6)), cos((-t * 0.6)), 0, 0, 0, 0, 1); var rotatedP = (rotMatZ * (rotMatX * vec4f(localP, 1))).xyz; @@ -73,8 +73,8 @@ describe('ray-marching example', () => { var sphere1 = Shape_6(vec3f(0.4000000059604645, 0.5, 1), sdSphere_9((localP - sphere1Offset), 0.5)); var sphere2 = Shape_6(vec3f(1, 0.800000011920929, 0.20000000298023224), sdSphere_9((localP - sphere2Offset), 0.3)); var box = Shape_6(vec3f(1, 0.30000001192092896, 0.30000001192092896), sdBoxFrame3d_10(rotatedP, boxSize, 0.1)); - var spheres = smoothShapeUnion_11(sphere1, sphere2, 0.1); - return smoothShapeUnion_11(spheres, box, 0.2); + var spheres = smoothShapeUnion_11(&sphere1, &sphere2, 0.1); + return smoothShapeUnion_11(&spheres, &box, 0.2); } @group(0) @binding(1) var time_12: f32; @@ -88,22 +88,22 @@ describe('ray-marching example', () => { return (dot(p, n) + h); } - fn shapeUnion_15(a: Shape_6, b: Shape_6) -> Shape_6 { - return Shape_6(select(a.color, b.color, (a.dist > b.dist)), min(a.dist, b.dist)); + fn shapeUnion_15(a: ptr, b: ptr) -> Shape_6 { + return Shape_6(select((*a).color, (*b).color, ((*a).dist > (*b).dist)), min((*a).dist, (*b).dist)); } - fn getSceneDist_7(p: vec3f) -> Shape_6 { + fn getSceneDist_7(p: ptr) -> Shape_6 { var shape = getMorphingShape_8(p, time_12); - var floor = Shape_6(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard_13((p.xz * 2))), sdPlane_14(p, vec3f(0, 1, 0), 0)); - return shapeUnion_15(shape, floor); + var floor = Shape_6(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard_13(((*p).xz * 2))), sdPlane_14(*p, vec3f(0, 1, 0), 0)); + return shapeUnion_15(&shape, &floor); } - fn rayMarch_5(ro: vec3f, rd: vec3f) -> Shape_6 { + fn rayMarch_5(ro: ptr, rd: ptr) -> Shape_6 { var dO = 0f; var result = Shape_6(vec3f(), 30); for (var i = 0; (i < 1000); i++) { - var p = (ro + (rd * dO)); - var scene = getSceneDist_7(p); + var p = (*ro + (*rd * dO)); + var scene = getSceneDist_7(&p); dO += scene.dist; if (((dO > 30) || (scene.dist < 1e-3))) { result.dist = dO; @@ -114,28 +114,50 @@ describe('ray-marching example', () => { return result; } - fn getNormal_16(p: vec3f) -> vec3f { + fn getMorphingShape_18(p: vec3f, t: f32) -> Shape_6 { + var center = vec3f(0, 2, 6); + var localP = (p - center); + var rotMatZ = mat4x4f(cos(-t), sin(-t), 0, 0, -sin(-t), cos(-t), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + var rotMatX = mat4x4f(1, 0, 0, 0, 0, cos((-t * 0.6)), sin((-t * 0.6)), 0, 0, -sin((-t * 0.6)), cos((-t * 0.6)), 0, 0, 0, 0, 1); + var rotatedP = (rotMatZ * (rotMatX * vec4f(localP, 1))).xyz; + var boxSize = vec3f(0.699999988079071); + var sphere1Offset = vec3f((cos((t * 2)) * 0.8), (sin((t * 3)) * 0.3), (sin((t * 2)) * 0.8)); + var sphere2Offset = vec3f((cos(((t * 2) + 3.14)) * 0.8), (sin(((t * 3) + 1.57)) * 0.3), (sin(((t * 2) + 3.14)) * 0.8)); + var sphere1 = Shape_6(vec3f(0.4000000059604645, 0.5, 1), sdSphere_9((localP - sphere1Offset), 0.5)); + var sphere2 = Shape_6(vec3f(1, 0.800000011920929, 0.20000000298023224), sdSphere_9((localP - sphere2Offset), 0.3)); + var box = Shape_6(vec3f(1, 0.30000001192092896, 0.30000001192092896), sdBoxFrame3d_10(rotatedP, boxSize, 0.1)); + var spheres = smoothShapeUnion_11(&sphere1, &sphere2, 0.1); + return smoothShapeUnion_11(&spheres, &box, 0.2); + } + + fn getSceneDist_17(p: vec3f) -> Shape_6 { + var shape = getMorphingShape_18(p, time_12); + var floor = Shape_6(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard_13((p.xz * 2))), sdPlane_14(p, vec3f(0, 1, 0), 0)); + return shapeUnion_15(&shape, &floor); + } + + fn getNormal_16(p: ptr) -> vec3f { var dist = getSceneDist_7(p).dist; var e = 0.01; - var n = vec3f((getSceneDist_7((p + vec3f(e, 0, 0))).dist - dist), (getSceneDist_7((p + vec3f(0, e, 0))).dist - dist), (getSceneDist_7((p + vec3f(0, 0, e))).dist - dist)); + var n = vec3f((getSceneDist_17((*p + vec3f(e, 0, 0))).dist - dist), (getSceneDist_17((*p + vec3f(0, e, 0))).dist - dist), (getSceneDist_17((*p + vec3f(0, 0, e))).dist - dist)); return normalize(n); } - fn getOrbitingLightPos_17(t: f32) -> vec3f { + fn getOrbitingLightPos_19(t: f32) -> vec3f { var radius = 3f; var height = 6f; var speed = 1f; return vec3f((cos((t * speed)) * radius), (height + (sin((t * speed)) * radius)), 4); } - fn softShadow_18(ro: vec3f, rd: vec3f, minT: f32, maxT: f32, k: f32) -> f32 { + fn softShadow_20(ro: ptr, rd: ptr, minT: f32, maxT: f32, k: f32) -> f32 { var res = 1f; var t = minT; for (var i = 0; (i < 100); i++) { if ((t >= maxT)) { break; } - var h = getSceneDist_7((ro + (rd * t))).dist; + var h = getSceneDist_17((*ro + (*rd * t))).dist; if ((h < 1e-3)) { return 0; } @@ -145,26 +167,26 @@ describe('ray-marching example', () => { return res; } - struct fragmentMain_Input_19 { + struct fragmentMain_Input_21 { @location(0) uv: vec2f, } - @fragment fn fragmentMain_3(input: fragmentMain_Input_19) -> @location(0) vec4f { + @fragment fn fragmentMain_3(input: fragmentMain_Input_21) -> @location(0) vec4f { var uv = ((input.uv * 2) - 1); uv.x *= (resolution_4.x / resolution_4.y); var ro = vec3f(0, 2, 3); var rd = normalize(vec3f(uv.x, uv.y, 1)); - var march = rayMarch_5(ro, rd); + var march = rayMarch_5(&ro, &rd); var fog = pow(min((march.dist / 30f), 1), 0.7); var p = (ro + (rd * march.dist)); - var n = getNormal_16(p); - var lightPos = getOrbitingLightPos_17(time_12); + var n = getNormal_16(&p); + var lightPos = getOrbitingLightPos_19(time_12); var l = normalize((lightPos - p)); var diff = max(dot(n, l), 0); var shadowRo = p; var shadowRd = l; var shadowDist = length((lightPos - p)); - var shadow = softShadow_18(shadowRo, shadowRd, 0.1, shadowDist, 16); + var shadow = softShadow_20(&shadowRo, &shadowRd, 0.1, shadowDist, 16); var litColor = (march.color * diff); var finalColor = mix((litColor * 0.5), litColor, shadow); return mix(vec4f(finalColor, 1), vec4f(0.699999988079071, 0.800000011920929, 0.8999999761581421, 1), fog); From 5312d1df1654fa9ac1b78bb35645f356049d352b Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 16:12:29 +0200 Subject: [PATCH 04/59] Track ref address space --- .../typegpu/src/core/buffer/bufferUsage.ts | 34 ++++++++-- .../typegpu/src/core/constant/tgpuConstant.ts | 12 ++-- .../typegpu/src/core/declare/tgpuDeclare.ts | 2 +- .../typegpu/src/core/function/dualImpl.ts | 12 ++-- packages/typegpu/src/core/function/fnCore.ts | 10 +-- packages/typegpu/src/core/function/tgpuFn.ts | 16 +++-- .../src/core/pipeline/computePipeline.ts | 2 +- .../src/core/pipeline/renderPipeline.ts | 2 +- .../typegpu/src/core/resolve/tgpuResolve.ts | 2 +- packages/typegpu/src/core/sampler/sampler.ts | 16 ++--- packages/typegpu/src/core/slot/accessor.ts | 24 ++++++- .../src/core/texture/externalTexture.ts | 4 +- packages/typegpu/src/core/texture/texture.ts | 18 ++--- packages/typegpu/src/core/valueProxyUtils.ts | 4 +- .../typegpu/src/core/variable/tgpuVariable.ts | 9 ++- packages/typegpu/src/data/array.ts | 5 +- packages/typegpu/src/data/disarray.ts | 4 +- packages/typegpu/src/data/matrix.ts | 25 +++---- packages/typegpu/src/data/snippet.ts | 14 ++-- packages/typegpu/src/data/vectorImpl.ts | 6 +- packages/typegpu/src/resolutionCtx.ts | 37 +++++----- packages/typegpu/src/std/atomic.ts | 32 +++++---- packages/typegpu/src/std/boolean.ts | 2 +- packages/typegpu/src/std/derivative.ts | 21 +++--- packages/typegpu/src/std/discard.ts | 2 +- packages/typegpu/src/std/extensions.ts | 2 +- packages/typegpu/src/std/matrix.ts | 10 +-- packages/typegpu/src/std/numeric.ts | 8 ++- packages/typegpu/src/std/packing.ts | 8 +-- packages/typegpu/src/std/texture.ts | 17 ++--- .../src/tgsl/consoleLog/logGenerator.ts | 4 +- packages/typegpu/src/tgsl/conversion.ts | 4 +- .../typegpu/src/tgsl/generationHelpers.ts | 14 ++-- packages/typegpu/src/tgsl/wgslGenerator.ts | 38 +++++------ .../tests/examples/individual/oklab.test.ts | 2 +- packages/typegpu/tests/resolve.test.ts | 4 +- .../typegpu/tests/tgsl/conversion.test.ts | 48 +++++++------ .../tests/tgsl/generationHelpers.test.ts | 68 +++++++++++++------ .../typegpu/tests/tgsl/wgslGenerator.test.ts | 8 ++- 39 files changed, 326 insertions(+), 224 deletions(-) diff --git a/packages/typegpu/src/core/buffer/bufferUsage.ts b/packages/typegpu/src/core/buffer/bufferUsage.ts index 3dc0a2fd76..35129bb499 100644 --- a/packages/typegpu/src/core/buffer/bufferUsage.ts +++ b/packages/typegpu/src/core/buffer/bufferUsage.ts @@ -130,7 +130,13 @@ class TgpuFixedBufferImpl< };`, ); - return snip(id, dataType, /* ref */ isNaturallyRef(dataType)); + return snip( + id, + dataType, + /* ref */ isNaturallyRef(dataType) + ? this.usage === 'uniform' ? 'uniform' : 'storage' + : undefined, + ); } toString(): string { @@ -139,11 +145,18 @@ class TgpuFixedBufferImpl< get [$gpuValueOf](): InferGPU { const dataType = this.buffer.dataType; + const usage = this.usage; return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, dataType, /* ref */ isNaturallyRef(dataType)); + return snip( + this, + dataType, + /* ref */ isNaturallyRef(dataType) + ? usage === 'uniform' ? 'uniform' : 'storage' + : undefined, + ); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `${this.usage}:${getName(this) ?? ''}.$`, @@ -250,7 +263,13 @@ export class TgpuLaidOutBufferImpl< };`, ); - return snip(id, dataType, /* ref */ isNaturallyRef(dataType)); + return snip( + id, + dataType, + /* ref */ isNaturallyRef(dataType) + ? this.usage === 'uniform' ? 'uniform' : 'storage' + : undefined, + ); } toString(): string { @@ -259,11 +278,18 @@ export class TgpuLaidOutBufferImpl< get [$gpuValueOf](): InferGPU { const schema = this.dataType as unknown as AnyData; + const usage = this.usage; return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, schema, /* ref */ isNaturallyRef(schema)); + return snip( + this, + schema, + /* ref */ isNaturallyRef(schema) + ? usage === 'uniform' ? 'uniform' : 'storage' + : undefined, + ); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `${this.usage}:${getName(this) ?? ''}.$`, diff --git a/packages/typegpu/src/core/constant/tgpuConstant.ts b/packages/typegpu/src/core/constant/tgpuConstant.ts index 955a050b3a..9d561ec140 100644 --- a/packages/typegpu/src/core/constant/tgpuConstant.ts +++ b/packages/typegpu/src/core/constant/tgpuConstant.ts @@ -1,5 +1,6 @@ +import { schemaCallWrapper } from '../../data/schemaCallWrapper.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; -import { type AnyWgslData, isNaturallyRef } from '../../data/wgslTypes.ts'; +import type { AnyWgslData } from '../../data/wgslTypes.ts'; import { inCodegenMode } from '../../execMode.ts'; import type { TgpuNamable } from '../../shared/meta.ts'; import { getName, setName } from '../../shared/meta.ts'; @@ -67,7 +68,10 @@ class TgpuConstImpl ctx.addDeclaration(`const ${id}: ${resolvedDataType} = ${resolvedValue};`); - return snip(id, this.dataType, /* ref */ isNaturallyRef(this.dataType)); + // Why not a ref? + // 1. On the WGSL side, we cannot take pointers to constants. + // 2. On the JS side, we copy the constant each time we access it, so we're safe. + return snip(id, this.dataType, /* ref */ undefined); } toString() { @@ -80,7 +84,7 @@ class TgpuConstImpl return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, dataType, /* ref */ isNaturallyRef(dataType)); + return snip(this, dataType, /* ref */ undefined); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `const:${getName(this) ?? ''}.$`, @@ -92,7 +96,7 @@ class TgpuConstImpl return this[$gpuValueOf]; } - return this.#value; + return schemaCallWrapper(this.dataType, this.#value); } get $(): InferGPU { diff --git a/packages/typegpu/src/core/declare/tgpuDeclare.ts b/packages/typegpu/src/core/declare/tgpuDeclare.ts index d1688fcc7c..e364ab0c70 100644 --- a/packages/typegpu/src/core/declare/tgpuDeclare.ts +++ b/packages/typegpu/src/core/declare/tgpuDeclare.ts @@ -60,7 +60,7 @@ class TgpuDeclareImpl implements TgpuDeclare, SelfResolvable { ); ctx.addDeclaration(replacedDeclaration); - return snip('', Void, false); + return snip('', Void, /* ref */ undefined); } toString() { diff --git a/packages/typegpu/src/core/function/dualImpl.ts b/packages/typegpu/src/core/function/dualImpl.ts index 9220f27937..a6b3265aac 100644 --- a/packages/typegpu/src/core/function/dualImpl.ts +++ b/packages/typegpu/src/core/function/dualImpl.ts @@ -86,13 +86,17 @@ export function dualImpl unknown>( return snip( options.normalImpl(...converted.map((s) => s.value) as never[]), returnType, - // Functions give up ownership of their return value - /* ref */ false, + // Why no ref? Functions give up ownership of their return value + /* ref */ undefined, ); } - // Functions give up ownership of their return value - return snip(options.codegenImpl(...converted), returnType, /* ref */ false); + return snip( + options.codegenImpl(...converted), + returnType, + // Why no ref? Functions give up ownership of their return value + /* ref */ undefined, + ); }; const impl = ((...args: Parameters) => { diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index 5853880879..b1d1f51ede 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -136,9 +136,7 @@ export function createFnCore( } ctx.addDeclaration(`${fnAttribute}fn ${id}${header}${body}`); - // 'ref' of this snippet is unused, but I guess for the sake of - // completeness, this is a reference to a function, so it's a ref. - return snip(id, returnType, /* ref */ true); + return snip(id, returnType, /* ref */ undefined); } // get data generated by the plugin @@ -198,7 +196,7 @@ export function createFnCore( // of the argument based on the argument's referentiality. // In other words, if we pass a reference to a function, it's typed as a pointer, // otherwise it's typed as a value. - const ref = isPtr(argType); + const ref = isPtr(argType) ? 'function' : undefined; switch (astParam?.type) { case FuncParameterType.identifier: { @@ -244,9 +242,7 @@ export function createFnCore( }`, ); - // 'ref' of this snippet is unused, but I guess for the sake of - // completeness, this is a reference to a function, so it's a ref. - return snip(id, actualReturnType, /* ref */ true); + return snip(id, actualReturnType, /* ref */ undefined); }, }; diff --git a/packages/typegpu/src/core/function/tgpuFn.ts b/packages/typegpu/src/core/function/tgpuFn.ts index b06fc53d7c..eb7d88d053 100644 --- a/packages/typegpu/src/core/function/tgpuFn.ts +++ b/packages/typegpu/src/core/function/tgpuFn.ts @@ -305,9 +305,9 @@ function createBoundFunction( const call = createDualImpl>( (...args) => innerFn(...args), - // Functions give up ownership of their return value (so ref is false) (...args) => - snip(new FnCall(fn, args), innerFn.shell.returnType, /* ref */ false), + // Why no ref? Functions give up ownership of their return value + snip(new FnCall(fn, args), innerFn.shell.returnType, /* ref */ undefined), 'tgpuFnCall', innerFn.shell.argTypes, ); @@ -343,8 +343,12 @@ class FnCall implements SelfResolvable { this.#fn = fn; this.#params = params; this[$getNameForward] = fn; - // Functions give up ownership of their return value (so ref is false) - this[$ownSnippet] = snip(this, this.#fn.shell.returnType, /* ref */ false); + this[$ownSnippet] = snip( + this, + this.#fn.shell.returnType, + // Why no ref? Functions give up ownership of their return value (so ref is false) + /* ref */ undefined, + ); } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { @@ -354,8 +358,8 @@ class FnCall implements SelfResolvable { return snip( stitch`${ctx.resolve(this.#fn).value}(${this.#params})`, this.#fn.shell.returnType, - // Functions give up ownership of their return value (so ref is false) - /* ref */ false, + // Why no ref? Functions give up ownership of their return value (so ref is false) + /* ref */ undefined, ); }); } diff --git a/packages/typegpu/src/core/pipeline/computePipeline.ts b/packages/typegpu/src/core/pipeline/computePipeline.ts index e66d5d94fd..c7a4483969 100644 --- a/packages/typegpu/src/core/pipeline/computePipeline.ts +++ b/packages/typegpu/src/core/pipeline/computePipeline.ts @@ -226,7 +226,7 @@ class ComputePipelineCore implements SelfResolvable { [$resolve](ctx: ResolutionCtx) { return ctx.withSlots(this._slotBindings, () => { ctx.resolve(this._entryFn); - return snip('', Void, /* ref */ false); + return snip('', Void, /* ref */ undefined); }); } diff --git a/packages/typegpu/src/core/pipeline/renderPipeline.ts b/packages/typegpu/src/core/pipeline/renderPipeline.ts index 6e3031db53..26e21dd267 100644 --- a/packages/typegpu/src/core/pipeline/renderPipeline.ts +++ b/packages/typegpu/src/core/pipeline/renderPipeline.ts @@ -674,7 +674,7 @@ class RenderPipelineCore implements SelfResolvable { if (fragmentFn) { ctx.resolve(fragmentFn); } - return snip('', Void, /* ref */ false); + return snip('', Void, /* ref */ undefined); }), ); } diff --git a/packages/typegpu/src/core/resolve/tgpuResolve.ts b/packages/typegpu/src/core/resolve/tgpuResolve.ts index 79c16a7f28..c02d1d0716 100644 --- a/packages/typegpu/src/core/resolve/tgpuResolve.ts +++ b/packages/typegpu/src/core/resolve/tgpuResolve.ts @@ -107,7 +107,7 @@ export function resolveWithContext( return snip( replaceExternalsInWgsl(ctx, dependencies, template ?? ''), Void, - /* ref */ false, + /* ref */ undefined, ); }, diff --git a/packages/typegpu/src/core/sampler/sampler.ts b/packages/typegpu/src/core/sampler/sampler.ts index e7aa27b2dd..04a1b2c408 100644 --- a/packages/typegpu/src/core/sampler/sampler.ts +++ b/packages/typegpu/src/core/sampler/sampler.ts @@ -156,7 +156,7 @@ export class TgpuLaidOutSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - [$ownSnippet] = snip(this, this as any, /* ref */ true); + [$ownSnippet] = snip(this, this as any, /* ref */ 'handle'); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -168,7 +168,7 @@ export class TgpuLaidOutSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - return snip(id, this as any, /* ref */ true); + return snip(id, this as any, /* ref */ 'handle'); } toString() { @@ -188,7 +188,7 @@ export class TgpuLaidOutComparisonSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - [$ownSnippet] = snip(this, this as any, /* ref */ true); + [$ownSnippet] = snip(this, this as any, /* ref */ 'handle'); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -200,7 +200,7 @@ export class TgpuLaidOutComparisonSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - return snip(id, this as any, /* ref */ true); + return snip(id, this as any, /* ref */ 'handle'); } toString() { @@ -238,7 +238,7 @@ class TgpuFixedSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - [$ownSnippet] = snip(this, this as any, /* ref */ true); + [$ownSnippet] = snip(this, this as any, /* ref */ 'handle'); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -256,7 +256,7 @@ class TgpuFixedSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - return snip(id, this as any, /* ref */ true); + return snip(id, this as any, /* ref */ 'handle'); } $name(label: string) { @@ -293,7 +293,7 @@ class TgpuFixedComparisonSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - [$ownSnippet] = snip(this, this as any, /* ref */ true); + [$ownSnippet] = snip(this, this as any, /* ref */ 'handle'); $name(label: string) { setName(this, label); @@ -313,7 +313,7 @@ class TgpuFixedComparisonSamplerImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper sampler schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have sampler schemas - return snip(id, this as any, /* ref */ true); + return snip(id, this as any, /* ref */ 'handle'); } toString() { diff --git a/packages/typegpu/src/core/slot/accessor.ts b/packages/typegpu/src/core/slot/accessor.ts index 7274c9bbb8..b93b066f50 100644 --- a/packages/typegpu/src/core/slot/accessor.ts +++ b/packages/typegpu/src/core/slot/accessor.ts @@ -1,5 +1,6 @@ +import { schemaCallWrapper } from '../../data/schemaCallWrapper.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; -import { type AnyWgslData, isNaturallyRef } from '../../data/wgslTypes.ts'; +import type { AnyWgslData } from '../../data/wgslTypes.ts'; import { getResolutionCtx, inCodegenMode } from '../../execMode.ts'; import { getName } from '../../shared/meta.ts'; import type { Infer, InferGPU } from '../../shared/repr.ts'; @@ -11,6 +12,7 @@ import { $resolve, } from '../../shared/symbols.ts'; import { + getOwnSnippet, isBufferUsage, type ResolutionCtx, type SelfResolvable, @@ -91,10 +93,26 @@ export class TgpuAccessorImpl } if (isBufferUsage(value) || isBufferShorthand(value)) { - return snip(value, this.schema, /* ref */ true); + return snip( + value, + this.schema, + /* ref */ value.resourceType === 'uniform' || + value.resourceType === 'buffer-usage' && value.usage === 'uniform' + ? 'uniform' + : 'storage', + ); } - return snip(value, this.schema, /* ref */ isNaturallyRef(this.schema)); + const ownSnippet = getOwnSnippet(value); + if (ownSnippet) { + return ownSnippet; + } + + // Doing a deep copy each time so that we don't have to deal with refs + return schemaCallWrapper( + this.schema, + snip(value, this.schema, /* ref */ undefined), + ); } $name(label: string) { diff --git a/packages/typegpu/src/core/texture/externalTexture.ts b/packages/typegpu/src/core/texture/externalTexture.ts index 9ebf41c4d3..4a455d9a4f 100644 --- a/packages/typegpu/src/core/texture/externalTexture.ts +++ b/packages/typegpu/src/core/texture/externalTexture.ts @@ -37,7 +37,7 @@ export class TgpuExternalTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - [$ownSnippet] = snip(this, this as any, /* ref */ true); + [$ownSnippet] = snip(this, this as any, /* ref */ 'handle'); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -49,7 +49,7 @@ export class TgpuExternalTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - return snip(id, this as any, /* ref */ true); + return snip(id, this as any, /* ref */ 'handle'); } toString() { diff --git a/packages/typegpu/src/core/texture/texture.ts b/packages/typegpu/src/core/texture/texture.ts index 8cb81c8933..9dd53f3d85 100644 --- a/packages/typegpu/src/core/texture/texture.ts +++ b/packages/typegpu/src/core/texture/texture.ts @@ -496,7 +496,7 @@ class TgpuFixedStorageTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - [$ownSnippet] = snip(this, this as any, /* ref */ true); + [$ownSnippet] = snip(this, this as any, /* ref */ 'handle'); $name(label: string): this { this._texture.$name(label); @@ -523,7 +523,7 @@ class TgpuFixedStorageTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - return snip(id, this as any, /* ref */ true); + return snip(id, this as any, /* ref */ 'handle'); } toString() { @@ -550,7 +550,7 @@ export class TgpuLaidOutStorageTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - [$ownSnippet] = snip(this, this as any, /* ref */ true); + [$ownSnippet] = snip(this, this as any, /* ref */ 'handle'); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -565,7 +565,7 @@ export class TgpuLaidOutStorageTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - return snip(id, this as any, /* ref */ true); + return snip(id, this as any, /* ref */ 'handle'); } toString() { @@ -611,7 +611,7 @@ class TgpuFixedSampledTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - [$ownSnippet] = snip(this, this as any, /* ref */ true); + [$ownSnippet] = snip(this, this as any, /* ref */ 'handle'); $name(label: string): this { this._texture.$name(label); @@ -645,7 +645,7 @@ class TgpuFixedSampledTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - return snip(id, this as any, /* ref */ true); + return snip(id, this as any, /* ref */ 'handle'); } toString() { @@ -672,7 +672,7 @@ export class TgpuLaidOutSampledTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - [$ownSnippet] = snip(this, this as any, /* ref */ true); + [$ownSnippet] = snip(this, this as any, /* ref */ 'handle'); [$resolve](ctx: ResolutionCtx): ResolvedSnippet { const id = ctx.getUniqueName(this); @@ -691,7 +691,7 @@ export class TgpuLaidOutSampledTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - return snip(id, this as any, /* ref */ true); + return snip(id, this as any, /* ref */ 'handle'); } ctx.addDeclaration( @@ -702,7 +702,7 @@ export class TgpuLaidOutSampledTextureImpl // TODO: do not treat self-resolvable as wgsl data (when we have proper texture schemas) // biome-ignore lint/suspicious/noExplicitAny: This is necessary until we have texture schemas - return snip(id, this as any, /* ref */ true); + return snip(id, this as any, /* ref */ 'handle'); } toString() { diff --git a/packages/typegpu/src/core/valueProxyUtils.ts b/packages/typegpu/src/core/valueProxyUtils.ts index b4132a94dd..b0ad54531c 100644 --- a/packages/typegpu/src/core/valueProxyUtils.ts +++ b/packages/typegpu/src/core/valueProxyUtils.ts @@ -38,7 +38,9 @@ export const valueProxyHandler: ProxyHandler< return undefined; } - const ref = targetSnippet.ref && isNaturallyRef(propType); + const ref = targetSnippet.ref !== undefined && isNaturallyRef(propType) + ? targetSnippet.ref + : undefined; return new Proxy({ [$internal]: true, diff --git a/packages/typegpu/src/core/variable/tgpuVariable.ts b/packages/typegpu/src/core/variable/tgpuVariable.ts index 395800ebfc..68474c84d2 100644 --- a/packages/typegpu/src/core/variable/tgpuVariable.ts +++ b/packages/typegpu/src/core/variable/tgpuVariable.ts @@ -104,7 +104,11 @@ class TgpuVarImpl ctx.addDeclaration(`${pre};`); } - return snip(id, this.#dataType, /* ref */ isNaturallyRef(this.#dataType)); + return snip( + id, + this.#dataType, + /* ref */ isNaturallyRef(this.#dataType) ? this.#scope : undefined, + ); } $name(label: string) { @@ -118,11 +122,12 @@ class TgpuVarImpl get [$gpuValueOf](): InferGPU { const dataType = this.#dataType; + const ref = isNaturallyRef(dataType) ? this.#scope : undefined; return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, dataType, /* ref */ isNaturallyRef(dataType)); + return snip(this, dataType, ref); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `var:${getName(this) ?? ''}.$`, diff --git a/packages/typegpu/src/data/array.ts b/packages/typegpu/src/data/array.ts index 04227102d5..eebad4110f 100644 --- a/packages/typegpu/src/data/array.ts +++ b/packages/typegpu/src/data/array.ts @@ -53,8 +53,7 @@ export const arrayOf = createDualImpl( // Marking so the WGSL generator lets this function through partial[$internal] = true; - // Why ref? It's a function. - return snip(partial, UnknownData, /* ref*/ true); + return snip(partial, UnknownData, /* ref*/ undefined); } if (typeof elementCount.value !== 'number') { @@ -66,7 +65,7 @@ export const arrayOf = createDualImpl( return snip( cpu_arrayOf(elementType.value as AnyWgslData, elementCount.value), elementType.value as AnyWgslData, - /* ref */ false, + /* ref */ undefined, ); }, 'arrayOf', diff --git a/packages/typegpu/src/data/disarray.ts b/packages/typegpu/src/data/disarray.ts index 62f1696e04..e69678fd95 100644 --- a/packages/typegpu/src/data/disarray.ts +++ b/packages/typegpu/src/data/disarray.ts @@ -60,7 +60,7 @@ export const disarrayOf = createDualImpl( partial[$internal] = true; // Why ref? It's a function. - return snip(partial, UnknownData, /* ref */ false); + return snip(partial, UnknownData, /* ref */ undefined); } if (typeof elementCount.value !== 'number') { @@ -72,7 +72,7 @@ export const disarrayOf = createDualImpl( return snip( cpu_disarrayOf(elementType.value as AnyWgslData, elementCount.value), elementType.value as AnyWgslData, - /* ref */ false, + /* ref */ undefined, ); }, 'disarrayOf', diff --git a/packages/typegpu/src/data/matrix.ts b/packages/typegpu/src/data/matrix.ts index aadfc83ed7..8c83bfe828 100644 --- a/packages/typegpu/src/data/matrix.ts +++ b/packages/typegpu/src/data/matrix.ts @@ -96,7 +96,7 @@ function createMatSchema< snip( stitch`${options.type}(${args})`, schema as unknown as AnyData, - /* ref */ false, + /* ref */ undefined, ), options.type, ); @@ -182,7 +182,7 @@ abstract class mat2x2Impl extends MatBase .join(', ') })`, mat2x2f, - /* ref */ false, + /* ref */ undefined, ); } @@ -332,7 +332,7 @@ abstract class mat3x3Impl extends MatBase this[5] }, ${this[6]}, ${this[8]}, ${this[9]}, ${this[10]})`, mat3x3f, - /* ref */ false, + /* ref */ undefined, ); } @@ -531,7 +531,7 @@ abstract class mat4x4Impl extends MatBase .join(', ') })`, mat4x4f, - /* ref */ false, + /* ref */ undefined, ); } @@ -560,7 +560,7 @@ export const identity2 = createDualImpl( // CPU implementation () => mat2x2f(1, 0, 0, 1), // CODEGEN implementation - () => snip('mat2x2f(1, 0, 0, 1)', mat2x2f, /* ref */ false), + () => snip('mat2x2f(1, 0, 0, 1)', mat2x2f, /* ref */ undefined), 'identity2', ); @@ -572,7 +572,8 @@ export const identity3 = createDualImpl( // CPU implementation () => mat3x3f(1, 0, 0, 0, 1, 0, 0, 0, 1), // CODEGEN implementation - () => snip('mat3x3f(1, 0, 0, 0, 1, 0, 0, 0, 1)', mat3x3f, /* ref */ false), + () => + snip('mat3x3f(1, 0, 0, 0, 1, 0, 0, 0, 1)', mat3x3f, /* ref */ undefined), 'identity3', ); @@ -588,7 +589,7 @@ export const identity4 = createDualImpl( snip( 'mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)', mat4x4f, - /* ref */ false, + /* ref */ undefined, ), 'identity4', ); @@ -619,7 +620,7 @@ export const translation4 = createDualImpl( snip( stitch`mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ${v}.x, ${v}.y, ${v}.z, 1)`, mat4x4f, - /* ref */ false, + /* ref */ undefined, ), 'translation4', ); @@ -644,7 +645,7 @@ export const scaling4 = createDualImpl( snip( stitch`mat4x4f(${v}.x, 0, 0, 0, 0, ${v}.y, 0, 0, 0, 0, ${v}.z, 0, 0, 0, 0, 1)`, mat4x4f, - /* ref */ false, + /* ref */ undefined, ), 'scaling4', ); @@ -669,7 +670,7 @@ export const rotationX4 = createDualImpl( snip( stitch`mat4x4f(1, 0, 0, 0, 0, cos(${a}), sin(${a}), 0, 0, -sin(${a}), cos(${a}), 0, 0, 0, 0, 1)`, mat4x4f, - /* ref */ false, + /* ref */ undefined, ), 'rotationX4', ); @@ -694,7 +695,7 @@ export const rotationY4 = createDualImpl( snip( stitch`mat4x4f(cos(${a}), 0, -sin(${a}), 0, 0, 1, 0, 0, sin(${a}), 0, cos(${a}), 0, 0, 0, 0, 1)`, mat4x4f, - /* ref */ false, + /* ref */ undefined, ), 'rotationY4', ); @@ -719,7 +720,7 @@ export const rotationZ4 = createDualImpl( snip( stitch`mat4x4f(cos(${a}), sin(${a}), 0, 0, -sin(${a}), cos(${a}), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)`, mat4x4f, - /* ref */ false, + /* ref */ undefined, ), 'rotationZ4', ); diff --git a/packages/typegpu/src/data/snippet.ts b/packages/typegpu/src/data/snippet.ts index 831f44ed32..7342f57f8b 100644 --- a/packages/typegpu/src/data/snippet.ts +++ b/packages/typegpu/src/data/snippet.ts @@ -1,7 +1,7 @@ import { undecorate } from './dataTypes.ts'; import type { AnyData, UnknownData } from './dataTypes.ts'; import { DEV } from '../shared/env.ts'; -import { isNumericSchema } from './wgslTypes.ts'; +import { type AddressSpace, isNumericSchema } from './wgslTypes.ts'; export interface Snippet { readonly value: unknown; @@ -10,7 +10,7 @@ export interface Snippet { * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float */ readonly dataType: AnyData | UnknownData; - readonly ref: boolean; + readonly ref: AddressSpace | undefined; } export interface ResolvedSnippet { @@ -20,7 +20,7 @@ export interface ResolvedSnippet { * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float */ readonly dataType: AnyData; - readonly ref: boolean; + readonly ref: AddressSpace | undefined; } export type MapValueToSnippet = { [K in keyof T]: Snippet }; @@ -29,7 +29,7 @@ class SnippetImpl implements Snippet { constructor( readonly value: unknown, readonly dataType: AnyData | UnknownData, - readonly ref: boolean, + readonly ref: AddressSpace | undefined, ) {} } @@ -44,17 +44,17 @@ export function isSnippetNumeric(snippet: Snippet) { export function snip( value: string, dataType: AnyData, - ref: boolean, + ref: AddressSpace | undefined, ): ResolvedSnippet; export function snip( value: unknown, dataType: AnyData | UnknownData, - ref: boolean, + ref: AddressSpace | undefined, ): Snippet; export function snip( value: unknown, dataType: AnyData | UnknownData, - ref: boolean, + ref: AddressSpace | undefined, ): Snippet | ResolvedSnippet { if (DEV && isSnippet(value)) { // An early error, but not worth checking every time in production diff --git a/packages/typegpu/src/data/vectorImpl.ts b/packages/typegpu/src/data/vectorImpl.ts index 177cab491b..fefd4bef26 100644 --- a/packages/typegpu/src/data/vectorImpl.ts +++ b/packages/typegpu/src/data/vectorImpl.ts @@ -41,12 +41,12 @@ export abstract class VecBase extends Array implements SelfResolvable { [$resolve](): ResolvedSnippet { const schema = this[$internal].elementSchema; if (this.every((e) => !e)) { - return snip(`${this.kind}()`, schema, /* ref */ false); + return snip(`${this.kind}()`, schema, /* ref */ undefined); } if (this.every((e) => this[0] === e)) { - return snip(`${this.kind}(${this[0]})`, schema, /* ref */ false); + return snip(`${this.kind}(${this[0]})`, schema, /* ref */ undefined); } - return snip(`${this.kind}(${this.join(', ')})`, schema, /* ref */ false); + return snip(`${this.kind}(${this.join(', ')})`, schema, /* ref */ undefined); } toString() { diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index f83df61193..600d0c47e9 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -24,12 +24,7 @@ import { getAttributesString } from './data/attributes.ts'; import { type AnyData, isData, UnknownData } from './data/dataTypes.ts'; import { bool } from './data/numeric.ts'; import { type ResolvedSnippet, snip, type Snippet } from './data/snippet.ts'; -import { - isNaturallyRef, - isWgslArray, - isWgslStruct, - Void, -} from './data/wgslTypes.ts'; +import { isWgslArray, isWgslStruct, Void } from './data/wgslTypes.ts'; import { invariant, MissingSlotValueError, @@ -662,7 +657,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { let result: ResolvedSnippet; if (isData(item)) { // Ref is arbitrary, as we're resolving a schema - result = snip(resolveData(this, item), Void, /* ref */ true); + result = snip(resolveData(this, item), Void, /* ref */ undefined); } else if (isDerived(item) || isSlot(item)) { result = this.resolve(this.unwrap(item)); } else if (isSelfResolvable(item)) { @@ -727,7 +722,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { return snip( `${[...this._declarations].join('\n\n')}${result.value}`, Void, - /* ref */ false, // arbitrary + /* ref */ undefined, // arbitrary ); } finally { this.popMode('codegen'); @@ -753,13 +748,13 @@ export class ResolutionCtxImpl implements ResolutionCtx { ); if (reinterpretedType.type === 'abstractInt') { - return snip(`${item}`, realSchema, /* ref */ false); + return snip(`${item}`, realSchema, /* ref */ undefined); } if (reinterpretedType.type === 'u32') { - return snip(`${item}u`, realSchema, /* ref */ false); + return snip(`${item}u`, realSchema, /* ref */ undefined); } if (reinterpretedType.type === 'i32') { - return snip(`${item}i`, realSchema, /* ref */ false); + return snip(`${item}i`, realSchema, /* ref */ undefined); } const exp = item.toExponential(); @@ -771,21 +766,21 @@ export class ResolutionCtxImpl implements ResolutionCtx { // Just picking the shorter one const base = exp.length < decimal.length ? exp : decimal; if (reinterpretedType.type === 'f32') { - return snip(`${base}f`, realSchema, /* ref */ false); + return snip(`${base}f`, realSchema, /* ref */ undefined); } if (reinterpretedType.type === 'f16') { - return snip(`${base}h`, realSchema, /* ref */ false); + return snip(`${base}h`, realSchema, /* ref */ undefined); } - return snip(base, realSchema, /* ref */ false); + return snip(base, realSchema, /* ref */ undefined); } if (typeof item === 'boolean') { - return snip(item ? 'true' : 'false', bool, /* ref */ false); + return snip(item ? 'true' : 'false', bool, /* ref */ undefined); } if (typeof item === 'string') { // Already resolved - return snip(item, Void, /* ref */ false); + return snip(item, Void, /* ref */ undefined); } if (schema && isWgslArray(schema)) { @@ -808,12 +803,12 @@ export class ResolutionCtxImpl implements ResolutionCtx { snip( element, schema.elementType as AnyData, - /* ref */ isNaturallyRef(schema.elementType), + /* ref */ undefined, ) ) })`, schema, - /* ref */ false, + /* ref */ undefined, ); } @@ -821,7 +816,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { return snip( stitch`array(${item.map((element) => this.resolve(element))})`, UnknownData, - /* ref */ false, + /* ref */ undefined, ) as ResolvedSnippet; } @@ -832,12 +827,12 @@ export class ResolutionCtxImpl implements ResolutionCtx { snip( (item as Infer)[key], propType as AnyData, - /* ref */ false, + /* ref */ undefined, ) ) })`, schema, - /* ref */ false, // a new struct, not referenced from anywhere + /* ref */ undefined, // a new struct, not referenced from anywhere ); } diff --git a/packages/typegpu/src/std/atomic.ts b/packages/typegpu/src/std/atomic.ts index e24e0c2bd4..dd3ca51077 100644 --- a/packages/typegpu/src/std/atomic.ts +++ b/packages/typegpu/src/std/atomic.ts @@ -16,7 +16,7 @@ export const workgroupBarrier = createDualImpl( // CPU implementation () => console.warn('workgroupBarrier is a no-op outside of CODEGEN mode.'), // CODEGEN implementation - () => snip('workgroupBarrier()', Void, /* ref */ false), + () => snip('workgroupBarrier()', Void, /* ref */ undefined), 'workgroupBarrier', ); @@ -24,7 +24,7 @@ export const storageBarrier = createDualImpl( // CPU implementation () => console.warn('storageBarrier is a no-op outside of CODEGEN mode.'), // CODEGEN implementation - () => snip('storageBarrier()', Void, /* ref */ false), + () => snip('storageBarrier()', Void, /* ref */ undefined), 'storageBarrier', ); @@ -32,7 +32,7 @@ export const textureBarrier = createDualImpl( // CPU implementation () => console.warn('textureBarrier is a no-op outside of CODEGEN mode.'), // CODEGEN implementation - () => snip('textureBarrier()', Void, /* ref */ false), + () => snip('textureBarrier()', Void, /* ref */ undefined), 'textureBarrier', ); @@ -46,7 +46,11 @@ export const atomicLoad = createDualImpl( // CODEGEN implementation (a) => { if (isWgslData(a.dataType) && a.dataType.type === 'atomic') { - return snip(stitch`atomicLoad(&${a})`, a.dataType.inner, /* ref */ false); + return snip( + stitch`atomicLoad(&${a})`, + a.dataType.inner, + /* ref */ undefined, + ); } throw new Error( `Invalid atomic type: ${safeStringify(a.dataType)}`, @@ -69,7 +73,11 @@ export const atomicStore = createDualImpl( `Invalid atomic type: ${safeStringify(a.dataType)}`, ); } - return snip(stitch`atomicStore(&${a}, ${value})`, Void, /* ref */ false); + return snip( + stitch`atomicStore(&${a}, ${value})`, + Void, + /* ref */ undefined, + ); }, 'atomicStore', ); @@ -94,7 +102,7 @@ export const atomicAdd = createDualImpl( return snip( stitch`atomicAdd(&${a}, ${value})`, a.dataType.inner, - /* ref */ false, + /* ref */ undefined, ); } throw new Error( @@ -118,7 +126,7 @@ export const atomicSub = createDualImpl( return snip( stitch`atomicSub(&${a}, ${value})`, a.dataType.inner, - /* ref */ false, + /* ref */ undefined, ); } throw new Error( @@ -142,7 +150,7 @@ export const atomicMax = createDualImpl( return snip( stitch`atomicMax(&${a}, ${value})`, a.dataType.inner, - /* ref */ false, + /* ref */ undefined, ); } throw new Error( @@ -166,7 +174,7 @@ export const atomicMin = createDualImpl( return snip( stitch`atomicMin(&${a}, ${value})`, a.dataType.inner, - /* ref */ false, + /* ref */ undefined, ); } throw new Error( @@ -190,7 +198,7 @@ export const atomicAnd = createDualImpl( return snip( stitch`atomicAnd(&${a}, ${value})`, a.dataType.inner, - /* ref */ false, + /* ref */ undefined, ); } throw new Error( @@ -214,7 +222,7 @@ export const atomicOr = createDualImpl( return snip( stitch`atomicOr(&${a}, ${value})`, a.dataType.inner, - /* ref */ false, + /* ref */ undefined, ); } throw new Error( @@ -238,7 +246,7 @@ export const atomicXor = createDualImpl( return snip( stitch`atomicXor(&${a}, ${value})`, a.dataType.inner, - /* ref */ false, + /* ref */ undefined, ); } throw new Error( diff --git a/packages/typegpu/src/std/boolean.ts b/packages/typegpu/src/std/boolean.ts index 243b08fe05..429664a657 100644 --- a/packages/typegpu/src/std/boolean.ts +++ b/packages/typegpu/src/std/boolean.ts @@ -283,7 +283,7 @@ export const isCloseTo = dualImpl({ return false; }, // GPU implementation - codegenImpl: (lhs, rhs, precision = snip(0.01, f32, /* ref */ false)) => { + codegenImpl: (lhs, rhs, precision = snip(0.01, f32, /* ref */ undefined)) => { if (isSnippetNumeric(lhs) && isSnippetNumeric(rhs)) { return stitch`(abs(f32(${lhs}) - f32(${rhs})) <= ${precision})`; } diff --git a/packages/typegpu/src/std/derivative.ts b/packages/typegpu/src/std/derivative.ts index 9f14eb8902..6ffd45a64e 100644 --- a/packages/typegpu/src/std/derivative.ts +++ b/packages/typegpu/src/std/derivative.ts @@ -11,7 +11,7 @@ function cpuDpdx(value: T): T { export const dpdx = createDualImpl( cpuDpdx, - (value) => snip(stitch`dpdx(${value})`, value.dataType, /* ref */ false), + (value) => snip(stitch`dpdx(${value})`, value.dataType, /* ref */ undefined), 'dpdx', ); @@ -26,7 +26,7 @@ function cpuDpdxCoarse( export const dpdxCoarse = createDualImpl( cpuDpdxCoarse, (value) => - snip(stitch`dpdxCoarse(${value})`, value.dataType, /* ref */ false), + snip(stitch`dpdxCoarse(${value})`, value.dataType, /* ref */ undefined), 'dpdxCoarse', ); @@ -38,7 +38,8 @@ function cpuDpdxFine(value: T): T { export const dpdxFine = createDualImpl( cpuDpdxFine, - (value) => snip(stitch`dpdxFine(${value})`, value.dataType, /* ref */ false), + (value) => + snip(stitch`dpdxFine(${value})`, value.dataType, /* ref */ undefined), 'dpdxFine', ); @@ -50,7 +51,7 @@ function cpuDpdy(value: T): T { export const dpdy = createDualImpl( cpuDpdy, - (value) => snip(stitch`dpdy(${value})`, value.dataType, /* ref */ false), + (value) => snip(stitch`dpdy(${value})`, value.dataType, /* ref */ undefined), 'dpdy', ); @@ -65,7 +66,7 @@ function cpuDpdyCoarse( export const dpdyCoarse = createDualImpl( cpuDpdyCoarse, (value) => - snip(stitch`dpdyCoarse(${value})`, value.dataType, /* ref */ false), + snip(stitch`dpdyCoarse(${value})`, value.dataType, /* ref */ undefined), 'dpdyCoarse', ); @@ -77,7 +78,8 @@ function cpuDpdyFine(value: T): T { export const dpdyFine = createDualImpl( cpuDpdyFine, - (value) => snip(stitch`dpdyFine(${value})`, value.dataType, /* ref */ false), + (value) => + snip(stitch`dpdyFine(${value})`, value.dataType, /* ref */ undefined), 'dpdyFine', ); @@ -89,7 +91,8 @@ function cpuFwidth(value: T): T { export const fwidth = createDualImpl( cpuFwidth, - (value) => snip(stitch`fwidth(${value})`, value.dataType, /* ref */ false), + (value) => + snip(stitch`fwidth(${value})`, value.dataType, /* ref */ undefined), 'fwidth', ); @@ -104,7 +107,7 @@ function cpuFwidthCoarse( export const fwidthCoarse = createDualImpl( cpuFwidthCoarse, (value) => - snip(stitch`fwidthCoarse(${value})`, value.dataType, /* ref */ false), + snip(stitch`fwidthCoarse(${value})`, value.dataType, /* ref */ undefined), 'fwidthCoarse', ); @@ -119,6 +122,6 @@ function cpuFwidthFine( export const fwidthFine = createDualImpl( cpuFwidthFine, (value) => - snip(stitch`fwidthFine(${value})`, value.dataType, /* ref */ false), + snip(stitch`fwidthFine(${value})`, value.dataType, /* ref */ undefined), 'fwidthFine', ); diff --git a/packages/typegpu/src/std/discard.ts b/packages/typegpu/src/std/discard.ts index 4b6b35bdda..4084f9ac55 100644 --- a/packages/typegpu/src/std/discard.ts +++ b/packages/typegpu/src/std/discard.ts @@ -10,6 +10,6 @@ export const discard = createDualImpl( ); }, // GPU - () => snip('discard;', Void, /* ref */ false), + () => snip('discard;', Void, /* ref */ undefined), 'discard', ); diff --git a/packages/typegpu/src/std/extensions.ts b/packages/typegpu/src/std/extensions.ts index 536ba74600..caccb98eea 100644 --- a/packages/typegpu/src/std/extensions.ts +++ b/packages/typegpu/src/std/extensions.ts @@ -28,7 +28,7 @@ export const extensionEnabled: DualFn< `extensionEnabled has to be called with a string literal representing a valid WGSL extension name. Got: ${value}`, ); } - return snip(jsImpl(value as WgslExtension), bool, /* ref */ false); + return snip(jsImpl(value as WgslExtension), bool, /* ref */ undefined); }; const impl = (extensionName: WgslExtension) => { diff --git a/packages/typegpu/src/std/matrix.ts b/packages/typegpu/src/std/matrix.ts index 4266f9d525..bc3a30e5c6 100644 --- a/packages/typegpu/src/std/matrix.ts +++ b/packages/typegpu/src/std/matrix.ts @@ -43,7 +43,7 @@ export const translate4 = createDualImpl( snip( stitch`(${gpuTranslation4(vector)} * ${matrix})`, matrix.dataType, - /* ref */ false, + /* ref */ undefined, ), 'translate4', ); @@ -62,7 +62,7 @@ export const scale4 = createDualImpl( snip( stitch`(${(gpuScaling4(vector))} * ${matrix})`, matrix.dataType, - /* ref */ false, + /* ref */ undefined, ), 'scale4', ); @@ -81,7 +81,7 @@ export const rotateX4 = createDualImpl( snip( stitch`(${(gpuRotationX4(angle))} * ${matrix})`, matrix.dataType, - /* ref */ false, + /* ref */ undefined, ), 'rotateX4', ); @@ -100,7 +100,7 @@ export const rotateY4 = createDualImpl( snip( stitch`(${(gpuRotationY4(angle))} * ${matrix})`, matrix.dataType, - /* ref */ false, + /* ref */ undefined, ), 'rotateY4', ); @@ -119,7 +119,7 @@ export const rotateZ4 = createDualImpl( snip( stitch`(${(gpuRotationZ4(angle))} * ${matrix})`, matrix.dataType, - /* ref */ false, + /* ref */ undefined, ), 'rotateZ4', ); diff --git a/packages/typegpu/src/std/numeric.ts b/packages/typegpu/src/std/numeric.ts index 64b1071a94..7264910aad 100644 --- a/packages/typegpu/src/std/numeric.ts +++ b/packages/typegpu/src/std/numeric.ts @@ -609,7 +609,7 @@ export const frexp: FrexpOverload = createDualImpl( ); } - return snip(stitch`frexp(${value})`, returnType, /* ref */ false); + return snip(stitch`frexp(${value})`, returnType, /* ref */ undefined); }, 'frexp', ); @@ -975,7 +975,11 @@ export const refract = createDualImpl( }, // GPU implementation (e1, e2, e3) => - snip(stitch`refract(${e1}, ${e2}, ${e3})`, e1.dataType, /* ref */ false), + snip( + stitch`refract(${e1}, ${e2}, ${e3})`, + e1.dataType, + /* ref */ undefined, + ), 'refract', (e1, e2, e3) => [ e1.dataType as AnyWgslData, diff --git a/packages/typegpu/src/std/packing.ts b/packages/typegpu/src/std/packing.ts index fa9ffcf443..bdf7cec6cb 100644 --- a/packages/typegpu/src/std/packing.ts +++ b/packages/typegpu/src/std/packing.ts @@ -20,7 +20,7 @@ export const unpack2x16float = createDualImpl( return vec2f(reader.readFloat16(), reader.readFloat16()); }, // GPU implementation - (e) => snip(stitch`unpack2x16float(${e})`, vec2f, /* ref */ false), + (e) => snip(stitch`unpack2x16float(${e})`, vec2f, /* ref */ undefined), 'unpack2x16float', ); @@ -39,7 +39,7 @@ export const pack2x16float = createDualImpl( return u32(reader.readUint32()); }, // GPU implementation - (e) => snip(stitch`pack2x16float(${e})`, u32, /* ref */ false), + (e) => snip(stitch`pack2x16float(${e})`, u32, /* ref */ undefined), 'pack2x16float', ); @@ -62,7 +62,7 @@ export const unpack4x8unorm = createDualImpl( ); }, // GPU implementation - (e) => snip(stitch`unpack4x8unorm(${e})`, vec4f, /* ref */ false), + (e) => snip(stitch`unpack4x8unorm(${e})`, vec4f, /* ref */ undefined), 'unpack4x8unorm', ); @@ -83,6 +83,6 @@ export const pack4x8unorm = createDualImpl( return u32(reader.readUint32()); }, // GPU implementation - (e) => snip(stitch`pack4x8unorm(${e})`, u32, /* ref */ false), + (e) => snip(stitch`pack4x8unorm(${e})`, u32, /* ref */ undefined), 'pack4x8unorm', ); diff --git a/packages/typegpu/src/std/texture.ts b/packages/typegpu/src/std/texture.ts index af8e9b2ee9..bffc64a82e 100644 --- a/packages/typegpu/src/std/texture.ts +++ b/packages/typegpu/src/std/texture.ts @@ -127,7 +127,7 @@ export const textureSample: TextureSampleOverload = createDualImpl( ); }, // CODEGEN implementation - (...args) => snip(stitch`textureSample(${args})`, vec4f, /* ref */ false), + (...args) => snip(stitch`textureSample(${args})`, vec4f, /* ref */ undefined), 'textureSample', ); @@ -197,7 +197,8 @@ export const textureSampleBias: TextureSampleBiasOverload = createDualImpl( ); }, // CODEGEN implementation - (...args) => snip(stitch`textureSampleBias(${args})`, vec4f, /* ref */ false), + (...args) => + snip(stitch`textureSampleBias(${args})`, vec4f, /* ref */ undefined), 'textureSampleBias', ); @@ -248,7 +249,7 @@ export const textureSampleLevel: TextureSampleLevelOverload = createDualImpl( }, // CODEGEN implementation (...args) => - snip(stitch`textureSampleLevel(${args})`, vec4f, /* ref */ false), + snip(stitch`textureSampleLevel(${args})`, vec4f, /* ref */ undefined), 'textureSampleLevel', ); @@ -337,7 +338,7 @@ export const textureLoad: TextureLoadOverload = createDualImpl( 'texelDataType' in textureInfo ? textureInfo.texelDataType : channelDataToInstance[textureInfo.channelDataType.type], - /* ref */ false, + /* ref */ undefined, ); }, 'textureLoad', @@ -380,7 +381,7 @@ export const textureStore: TextureStoreOverload = createDualImpl( ); }, // CODEGEN implementation - (...args) => snip(stitch`textureStore(${args})`, Void, /* ref */ false), + (...args) => snip(stitch`textureStore(${args})`, Void, /* ref */ undefined), 'textureStore', ); @@ -440,7 +441,7 @@ export const textureDimensions: TextureDimensionsOverload = createDualImpl( return snip( stitch`textureDimensions(${[texture, level]})`, dim === '1d' ? u32 : dim === '3d' ? vec3u : vec2u, - /* ref */ false, + /* ref */ undefined, ); }, 'textureDimensions', @@ -507,7 +508,7 @@ export const textureSampleCompare: TextureSampleCompareOverload = }, // CODEGEN implementation (...args) => - snip(stitch`textureSampleCompare(${args})`, f32, /* ref */ false), + snip(stitch`textureSampleCompare(${args})`, f32, /* ref */ undefined), 'textureSampleCompare', ); @@ -534,7 +535,7 @@ export const textureSampleBaseClampToEdge: TextureSampleBaseClampToEdge = snip( stitch`textureSampleBaseClampToEdge(${args})`, vec4f, - /* ref */ false, + /* ref */ undefined, ), 'textureSampleBaseClampToEdge', ); diff --git a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts index 2943dd27bf..3a5336901e 100644 --- a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts +++ b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts @@ -41,7 +41,7 @@ export class LogGeneratorNullImpl implements LogGenerator { console.warn( "'console.log' is currently only supported in compute pipelines.", ); - return snip('/* console.log() */', Void, /* ref */ false); + return snip('/* console.log() */', Void, /* ref */ undefined); } } @@ -104,7 +104,7 @@ export class LogGeneratorImpl implements LogGenerator { return snip( stitch`${ctx.resolve(logFn).value}(${nonStringArgs})`, Void, - /* ref */ false, + /* ref */ undefined, ); } diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 230ad1cf7d..bd03cd52d6 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -239,10 +239,10 @@ function applyActionToSnippet( switch (action.action) { case 'ref': - return snip(stitch`&${snippet}`, targetType, /* ref */ true); + return snip(stitch`&${snippet}`, targetType, /* ref */ snippet.ref); case 'deref': // Dereferencing a pointer does not return a copy of the value, it's still a reference. - return snip(stitch`*${snippet}`, targetType, /* ref */ true); + return snip(stitch`*${snippet}`, targetType, /* ref */ snippet.ref); case 'cast': { // Casting means calling the schema with the snippet as an argument. return (targetType as unknown as (val: Snippet) => Snippet)(snippet); diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 4e883b0197..5ddc0aa23d 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -178,9 +178,9 @@ export function numericLiteralToSnippet(value: number): Snippet { `The integer ${value} exceeds the safe integer range and may have lost precision.`, ); } - return snip(value, abstractInt, /* ref */ false); + return snip(value, abstractInt, /* ref */ undefined); } - return snip(value, abstractFloat, /* ref */ false); + return snip(value, abstractFloat, /* ref */ undefined); } export function concretize(type: T): T | F32 | I32 { @@ -249,8 +249,7 @@ export function coerceToSnippet(value: unknown): Snippet { } if (isVecInstance(value) || isMatInstance(value)) { - // It's a reference to an external value, so `ref` is true - return snip(value, kindToSchema[value.kind], /* ref */ true); + return snip(value, kindToSchema[value.kind], /* ref */ undefined); } if ( @@ -259,7 +258,7 @@ export function coerceToSnippet(value: unknown): Snippet { typeof value === 'undefined' || value === null ) { // Nothing representable in WGSL as-is, so unknown - return snip(value, UnknownData, /* ref */ true); + return snip(value, UnknownData, /* ref */ undefined); } if (typeof value === 'number') { @@ -268,9 +267,8 @@ export function coerceToSnippet(value: unknown): Snippet { if (typeof value === 'boolean') { // It's a primitive, so `ref` is false - return snip(value, bool, /* ref */ false); + return snip(value, bool, /* ref */ undefined); } - // Hard to determine referentiality, so let's assume it's true - return snip(value, UnknownData, /* ref */ true); + return snip(value, UnknownData, /* ref */ undefined); } diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index fcd251c3a8..424895888f 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -167,7 +167,7 @@ ${this.ctx.pre}}`; const snippet = snip( this.ctx.makeNameValid(id), dataType, - /* ref */ wgsl.isNaturallyRef(dataType), + /* ref */ wgsl.isNaturallyRef(dataType) ? 'function' : undefined, ); this.ctx.defineVariable(id, snippet); return snippet; @@ -213,7 +213,7 @@ ${this.ctx.pre}}`; } if (typeof expression === 'boolean') { - return snip(expression, bool, /* ref */ false); + return snip(expression, bool, /* ref */ undefined); } if ( @@ -251,7 +251,7 @@ ${this.ctx.pre}}`; : `${lhsStr} ${op} ${rhsStr}`, type, // Result of an operation, so not a reference to anything - /* ref */ false, + /* ref */ undefined, ); } @@ -262,7 +262,7 @@ ${this.ctx.pre}}`; const argStr = this.ctx.resolve(argExpr.value).value; // Result of an operation, so not a reference to anything - return snip(`${argStr}${op}`, argExpr.dataType, /* ref */ false); + return snip(`${argStr}${op}`, argExpr.dataType, /* ref */ undefined); } if (expression[0] === NODE.unaryExpr) { @@ -273,7 +273,7 @@ ${this.ctx.pre}}`; const type = operatorToType(argExpr.dataType, op); // Result of an operation, so not a reference to anything - return snip(`${op}${argStr}`, type, /* ref */ false); + return snip(`${op}${argStr}`, type, /* ref */ undefined); } if (expression[0] === NODE.memberAccess) { @@ -282,7 +282,7 @@ ${this.ctx.pre}}`; const target = this.expression(targetNode); if (target.value === console) { - return snip(new ConsoleLog(), UnknownData, /* ref */ true); + return snip(new ConsoleLog(), UnknownData, /* ref */ undefined); } if (target.dataType.type === 'unknown') { @@ -314,7 +314,7 @@ ${this.ctx.pre}}`; infixOperators[property as InfixOperator][$internal].gpuImpl, ), UnknownData, - /* ref */ true, + /* ref */ target.ref, ); } @@ -326,14 +326,14 @@ ${this.ctx.pre}}`; ? `arrayLength(${this.ctx.resolve(target.value).value})` : `arrayLength(&${this.ctx.resolve(target.value).value})`, u32, - /* ref */ false, + /* ref */ undefined, ); } return snip( String(targetDataType.elementCount), abstractInt, - /* ref */ false, + /* ref */ undefined, ); } @@ -341,7 +341,7 @@ ${this.ctx.pre}}`; return snip( new MatrixColumnsAccess(target), UnknownData, - /* ref */ true, + /* ref */ target.ref, ); } @@ -359,7 +359,7 @@ ${this.ctx.pre}}`; ? `(*${this.ctx.resolve(target.value).value}).${property}` : `${this.ctx.resolve(target.value).value}.${property}`, propType, - /* ref */ target.ref && wgsl.isNaturallyRef(propType), + /* ref */ wgsl.isNaturallyRef(propType) ? target.ref : undefined, ); } @@ -414,7 +414,7 @@ ${this.ctx.pre}}`; return snip( `(*${targetStr})[${propertyStr}]`, propType, - /* ref */ wgsl.isNaturallyRef(propType), + /* ref */ wgsl.isNaturallyRef(propType) ? target.ref : undefined, ); } @@ -424,7 +424,7 @@ ${this.ctx.pre}}`; return snip( `${targetStr}[${propertyStr}]`, propType, - /* ref */ target.ref && wgsl.isNaturallyRef(propType), + /* ref */ wgsl.isNaturallyRef(propType) ? target.ref : undefined, ); } @@ -459,7 +459,7 @@ ${this.ctx.pre}}`; `${this.ctx.resolve(callee.value).value}()`, callee.value, // A new struct, so not a reference - /* ref */ false, + /* ref */ undefined, ); } @@ -474,7 +474,7 @@ ${this.ctx.pre}}`; this.ctx.resolve(arg.value, callee.value).value, callee.value, // A new struct, so not a reference - /* ref */ false, + /* ref */ undefined, ); } @@ -506,7 +506,7 @@ ${this.ctx.pre}}`; return snip( stitch`${snippet.value}(${converted})`, snippet.dataType, - /* ref */ false, + /* ref */ undefined, ); }); } @@ -634,7 +634,7 @@ ${this.ctx.pre}}`; return snip( stitch`${this.ctx.resolve(structType).value}(${convertedSnippets})`, structType, - /* ref */ false, + /* ref */ undefined, ); } @@ -690,12 +690,12 @@ ${this.ctx.pre}}`; elemType as wgsl.AnyWgslData, values.length, ) as wgsl.AnyWgslData, - /* ref */ false, + /* ref */ undefined, ); } if (expression[0] === NODE.stringLiteral) { - return snip(expression[1], UnknownData, /* ref */ true); + return snip(expression[1], UnknownData, /* ref */ undefined); } if (expression[0] === NODE.preUpdate) { diff --git a/packages/typegpu/tests/examples/individual/oklab.test.ts b/packages/typegpu/tests/examples/individual/oklab.test.ts index dcb3b2f5d2..96ec1cb138 100644 --- a/packages/typegpu/tests/examples/individual/oklab.test.ts +++ b/packages/typegpu/tests/examples/individual/oklab.test.ts @@ -193,7 +193,7 @@ describe('oklab example', () => { } fn gamutClipAdaptiveL05_8(lab: vec3f) -> vec3f { - var alpha = 0.2f; + var alpha = 0.20000000298023224f; var L = lab.x; var eps = 1e-5; var C = max(eps, length(lab.yz)); diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index 177abba86a..b9c1a5b347 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -57,7 +57,7 @@ describe('tgpu resolve', () => { [$gpuValueOf]: { [$internal]: true, get [$ownSnippet]() { - return snip(this, d.f32, false); + return snip(this, d.f32, /* ref */ undefined); }, [$resolve]: (ctx: ResolutionCtx) => ctx.resolve(intensity), } as unknown as number, @@ -67,7 +67,7 @@ describe('tgpu resolve', () => { ctx.addDeclaration( `@group(0) @binding(0) var ${name}: f32;`, ); - return snip(name, d.f32, false); + return snip(name, d.f32, /* ref */ undefined); }, get value(): number { diff --git a/packages/typegpu/tests/tgsl/conversion.test.ts b/packages/typegpu/tests/tgsl/conversion.test.ts index c63d14bfae..0bcc7e7166 100644 --- a/packages/typegpu/tests/tgsl/conversion.test.ts +++ b/packages/typegpu/tests/tgsl/conversion.test.ts @@ -185,13 +185,17 @@ describe('getBestConversion', () => { }); describe('convertToCommonType', () => { - const snippetF32 = snip('2.22', d.f32, false); - const snippetI32 = snip('-12', d.i32, false); - const snippetU32 = snip('33', d.u32, false); - const snippetAbsFloat = snip('1.1', abstractFloat, false); - const snippetAbsInt = snip('1', abstractInt, false); - const snippetPtrF32 = snip('ptr_f32', d.ptrPrivate(d.f32), true); - const snippetUnknown = snip('?', UnknownData, true); + const snippetF32 = snip('2.22', d.f32, /* ref */ undefined); + const snippetI32 = snip('-12', d.i32, /* ref */ undefined); + const snippetU32 = snip('33', d.u32, /* ref */ undefined); + const snippetAbsFloat = snip('1.1', abstractFloat, /* ref */ undefined); + const snippetAbsInt = snip('1', abstractInt, /* ref */ undefined); + const snippetPtrF32 = snip( + 'ptr_f32', + d.ptrPrivate(d.f32), + /* ref */ 'function', + ); + const snippetUnknown = snip('?', UnknownData, /* ref */ undefined); it('converts identical types', () => { const result = convertToCommonType([snippetF32, snippetF32]); @@ -241,7 +245,7 @@ describe('convertToCommonType', () => { }); it('returns undefined for incompatible types', () => { - const snippetVec2f = snip('v2', d.vec2f, false); + const snippetVec2f = snip('v2', d.vec2f, /* ref */ undefined); const result = convertToCommonType([snippetF32, snippetVec2f]); expect(result).toBeUndefined(); }); @@ -283,7 +287,7 @@ describe('convertToCommonType', () => { it('handles void gracefully', () => { const result = convertToCommonType([ snippetF32, - snip('void', d.Void, false), + snip('void', d.Void, /* ref */ undefined), ]); expect(result).toBeUndefined(); }); @@ -304,10 +308,10 @@ describe('convertStructValues', () => { it('maps values matching types exactly', () => { const snippets: Record = { - a: snip('1.0', d.f32, false), - b: snip('2', d.i32, false), - c: snip('vec2f(1.0, 1.0)', d.vec2f, false), - d: snip('true', d.bool, false), + a: snip('1.0', d.f32, /* ref */ undefined), + b: snip('2', d.i32, /* ref */ undefined), + c: snip('vec2f(1.0, 1.0)', d.vec2f, /* ref */ undefined), + d: snip('true', d.bool, /* ref */ undefined), }; const res = convertStructValues(structType, snippets); expect(res.length).toBe(4); @@ -319,25 +323,25 @@ describe('convertStructValues', () => { it('maps values requiring implicit casts and warns', () => { const snippets: Record = { - a: snip('1', d.i32, false), // i32 -> f32 (cast) - b: snip('2', d.u32, false), // u32 -> i32 (cast) - c: snip('2.22', d.f32, false), - d: snip('true', d.bool, false), + a: snip('1', d.i32, /* ref */ undefined), // i32 -> f32 (cast) + b: snip('2', d.u32, /* ref */ undefined), // u32 -> i32 (cast) + c: snip('2.22', d.f32, /* ref */ undefined), + d: snip('true', d.bool, /* ref */ undefined), }; const res = convertStructValues(structType, snippets); expect(res.length).toBe(4); - expect(res[0]).toEqual(snip('f32(1)', d.f32, false)); // Cast applied - expect(res[1]).toEqual(snip('i32(2)', d.i32, false)); // Cast applied + expect(res[0]).toEqual(snip('f32(1)', d.f32, /* ref */ undefined)); // Cast applied + expect(res[1]).toEqual(snip('i32(2)', d.i32, /* ref */ undefined)); // Cast applied expect(res[2]).toEqual(snippets.c); expect(res[3]).toEqual(snippets.d); }); it('throws on missing property', () => { const snippets: Record = { - a: snip('1.0', d.f32, false), + a: snip('1.0', d.f32, /* ref */ undefined), // b is missing - c: snip('vec2f(1.0, 1.0)', d.vec2f, false), - d: snip('true', d.bool, false), + c: snip('vec2f(1.0, 1.0)', d.vec2f, /* ref */ undefined), + d: snip('true', d.bool, /* ref */ undefined), }; expect(() => convertStructValues(structType, snippets)).toThrow( /Missing property b/, diff --git a/packages/typegpu/tests/tgsl/generationHelpers.test.ts b/packages/typegpu/tests/tgsl/generationHelpers.test.ts index 087dd07371..c4dc4bf58f 100644 --- a/packages/typegpu/tests/tgsl/generationHelpers.test.ts +++ b/packages/typegpu/tests/tgsl/generationHelpers.test.ts @@ -64,31 +64,31 @@ describe('generationHelpers', () => { describe('numericLiteralToSnippet', () => { it('should convert numeric literals to correct snippets', () => { expect(numericLiteralToSnippet(1)).toEqual( - snip(1, abstractInt, false), + snip(1, abstractInt, /* ref */ undefined), ); expect(numericLiteralToSnippet(1.1)).toEqual( - snip(1.1, abstractFloat, false), + snip(1.1, abstractFloat, /* ref */ undefined), ); expect(numericLiteralToSnippet(1e10)).toEqual( - snip(1e10, abstractInt, false), + snip(1e10, abstractInt, /* ref */ undefined), ); expect(numericLiteralToSnippet(0.5)).toEqual( - snip(0.5, abstractFloat, false), + snip(0.5, abstractFloat, /* ref */ undefined), ); expect(numericLiteralToSnippet(-45)).toEqual( - snip(-45, abstractInt, false), + snip(-45, abstractInt, /* ref */ undefined), ); expect(numericLiteralToSnippet(0x1A)).toEqual( - snip(0x1A, abstractInt, false), + snip(0x1A, abstractInt, /* ref */ undefined), ); expect(numericLiteralToSnippet(0b101)).toEqual( - snip(5, abstractInt, false), + snip(5, abstractInt, /* ref */ undefined), ); }); }); @@ -144,21 +144,39 @@ describe('generationHelpers', () => { const arr = arrayOf(f32, 2); it('coerces JS numbers', () => { - expect(coerceToSnippet(1)).toEqual(snip(1, abstractInt, false)); - expect(coerceToSnippet(2.5)).toEqual(snip(2.5, abstractFloat, false)); - expect(coerceToSnippet(-10)).toEqual(snip(-10, abstractInt, false)); - expect(coerceToSnippet(0.0)).toEqual(snip(0, abstractInt, false)); + expect(coerceToSnippet(1)).toEqual( + snip(1, abstractInt, /* ref */ undefined), + ); + expect(coerceToSnippet(2.5)).toEqual( + snip(2.5, abstractFloat, /* ref */ undefined), + ); + expect(coerceToSnippet(-10)).toEqual( + snip(-10, abstractInt, /* ref */ undefined), + ); + expect(coerceToSnippet(0.0)).toEqual( + snip(0, abstractInt, /* ref */ undefined), + ); }); it('coerces JS booleans', () => { - expect(coerceToSnippet(true)).toEqual(snip(true, bool, false)); - expect(coerceToSnippet(false)).toEqual(snip(false, bool, false)); + expect(coerceToSnippet(true)).toEqual( + snip(true, bool, /* ref */ undefined), + ); + expect(coerceToSnippet(false)).toEqual( + snip(false, bool, /* ref */ undefined), + ); }); it(`coerces schemas to UnknownData (as they're not instance types)`, () => { - expect(coerceToSnippet(f32)).toEqual(snip(f32, UnknownData, true)); - expect(coerceToSnippet(vec3i)).toEqual(snip(vec3i, UnknownData, true)); - expect(coerceToSnippet(arr)).toEqual(snip(arr, UnknownData, true)); + expect(coerceToSnippet(f32)).toEqual( + snip(f32, UnknownData, /* ref */ undefined), + ); + expect(coerceToSnippet(vec3i)).toEqual( + snip(vec3i, UnknownData, /* ref */ undefined), + ); + expect(coerceToSnippet(arr)).toEqual( + snip(arr, UnknownData, /* ref */ undefined), + ); }); it('coerces arrays to unknown', () => { @@ -182,14 +200,22 @@ describe('generationHelpers', () => { }); it('returns UnknownData for other types', () => { - expect(coerceToSnippet('foo')).toEqual(snip('foo', UnknownData, true)); - expect(coerceToSnippet({})).toEqual(snip({}, UnknownData, true)); - expect(coerceToSnippet(null)).toEqual(snip(null, UnknownData, true)); + expect(coerceToSnippet('foo')).toEqual( + snip('foo', UnknownData, /* ref */ undefined), + ); + expect(coerceToSnippet({})).toEqual( + snip({}, UnknownData, /* ref */ undefined), + ); + expect(coerceToSnippet(null)).toEqual( + snip(null, UnknownData, /* ref */ undefined), + ); expect(coerceToSnippet(undefined)).toEqual( - snip(undefined, UnknownData, true), + snip(undefined, UnknownData, /* ref */ undefined), ); const fn = () => {}; - expect(coerceToSnippet(fn)).toEqual(snip(fn, UnknownData, true)); + expect(coerceToSnippet(fn)).toEqual( + snip(fn, UnknownData, /* ref */ undefined), + ); }); }); }); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 613f54ff5c..0d0c21cad9 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -284,7 +284,11 @@ describe('wgslGenerator', () => { } const args = astInfo.ast.params.map((arg) => - snip((arg as { type: 'i'; name: string }).name, d.u32, false) + snip( + (arg as { type: 'i'; name: string }).name, + d.u32, + /* ref */ undefined, + ) ); provideCtx(ctx, () => { @@ -467,7 +471,7 @@ describe('wgslGenerator', () => { provideCtx(ctx, () => { ctx[$internal].itemStateStack.pushFunctionScope( - [snip('idx', d.u32, false)], + [snip('idx', d.u32, /* ref */ undefined)], {}, d.f32, astInfo.externals ?? {}, From 52124a71295ea9bc3ccad30f48f04729c1c411ab Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 17:10:43 +0200 Subject: [PATCH 05/59] Enforcing copy when assigning --- .../src/examples/rendering/3d-fish/compute.ts | 2 +- .../src/examples/rendering/3d-fish/index.ts | 4 +-- .../rendering/cubemap-reflection/icosphere.ts | 14 +++++----- .../examples/rendering/ray-marching/index.ts | 2 +- .../src/examples/simple/vaporrave/index.ts | 2 +- .../examples/simulation/boids-next/index.ts | 2 +- .../fluid-double-buffering/index.ts | 6 ++--- .../examples/simulation/gravity/compute.ts | 4 +-- .../simulation/stable-fluid/simulation.ts | 4 +-- .../examples/tests/wgsl-resolution/index.ts | 2 +- packages/typegpu/src/data/ptr.ts | 2 +- packages/typegpu/src/data/vector.ts | 8 +++++- packages/typegpu/src/data/wgslTypes.ts | 9 +++++++ packages/typegpu/src/tgsl/shellless.ts | 16 ++++++++--- packages/typegpu/src/tgsl/wgslGenerator.ts | 27 +++++++++++++++---- .../tests/examples/individual/3d-fish.test.ts | 2 +- .../individual/cubemap-reflection.test.ts | 2 +- .../examples/individual/stable-fluid.test.ts | 2 +- .../examples/individual/vaporrave.test.ts | 2 +- 19 files changed, 77 insertions(+), 35 deletions(-) diff --git a/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts b/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts index 5ad9b7fa70..031778ec53 100644 --- a/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts +++ b/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts @@ -130,5 +130,5 @@ export const computeShader = tgpu['~unstable'].computeFn({ fishData.direction, ); fishData.position = std.add(fishData.position, translation); - layout.$.nextFishData[fishIndex] = fishData; + layout.$.nextFishData[fishIndex] = ModelData(fishData); }); diff --git a/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts b/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts index 740cf6b873..10d53682a5 100644 --- a/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/3d-fish/index.ts @@ -118,8 +118,8 @@ const randomizeFishPositionsDispatch = prepareDispatch(root, (x) => { applySeaFog: 1, applySeaDesaturation: 1, }); - buffer0mutable.$[x] = data; - buffer1mutable.$[x] = data; + buffer0mutable.$[x] = ModelData(data); + buffer1mutable.$[x] = ModelData(data); }); const randomizeFishPositions = () => { diff --git a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts index 35b093f54e..e1a29a1186 100644 --- a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts +++ b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts @@ -133,7 +133,7 @@ export class IcosphereGenerator { in: { gid: d.builtin.globalInvocationId }, workgroupSize: [WORKGROUP_SIZE, 1, 1], })((input) => { - const triangleCount = d.u32(std.arrayLength(prevVertices.value) / 3); + const triangleCount = d.u32(prevVertices.$.length / 3); const triangleIndex = input.gid.x + input.gid.y * MAX_DISPATCH; if (triangleIndex >= triangleCount) { return; @@ -142,13 +142,13 @@ export class IcosphereGenerator { const baseIndexPrev = triangleIndex * 3; const v1 = unpackVec2u( - prevVertices.value[baseIndexPrev].position, + prevVertices.$[baseIndexPrev].position, ); const v2 = unpackVec2u( - prevVertices.value[baseIndexPrev + 1].position, + prevVertices.$[baseIndexPrev + 1].position, ); const v3 = unpackVec2u( - prevVertices.value[baseIndexPrev + 2].position, + prevVertices.$[baseIndexPrev + 2].position, ); const v12 = d.vec4f( @@ -189,7 +189,7 @@ export class IcosphereGenerator { const triBase = i - (i % 3); let normal = reprojectedVertex; - if (smoothFlag.value === 0) { + if (smoothFlag.$ === 0) { normal = getAverageNormal( newVertices[triBase], newVertices[triBase + 1], @@ -198,12 +198,12 @@ export class IcosphereGenerator { } const outIndex = baseIndexNext + i; - const nextVertex = nextVertices.value[outIndex]; + const nextVertex = nextVertices.$[outIndex]; nextVertex.position = packVec2u(reprojectedVertex); nextVertex.normal = packVec2u(normal); - nextVertices.value[outIndex] = nextVertex; + nextVertices.$[outIndex] = ComputeVertex(nextVertex); } }); diff --git a/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts b/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts index 3ff96aad05..c5260a6388 100644 --- a/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts @@ -135,7 +135,7 @@ const rayMarch = (ro: d.v3f, rd: d.v3f): Shape => { if (dO > MAX_DIST || scene.dist < SURF_DIST) { result.dist = dO; - result.color = scene.color; + result.color = d.vec3f(scene.color); break; } } diff --git a/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts b/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts index 183712439a..171923a170 100644 --- a/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts +++ b/apps/typegpu-docs/src/examples/simple/vaporrave/index.ts @@ -91,7 +91,7 @@ const rayMarch = tgpu.fn( if (scene.dist < c.SURF_DIST) { result.dist = distOrigin; - result.color = scene.color; + result.color = d.vec3f(scene.color); break; } } diff --git a/apps/typegpu-docs/src/examples/simulation/boids-next/index.ts b/apps/typegpu-docs/src/examples/simulation/boids-next/index.ts index 6af3580f77..433bd134a2 100644 --- a/apps/typegpu-docs/src/examples/simulation/boids-next/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/boids-next/index.ts @@ -249,7 +249,7 @@ const mainCompute = tgpu['~unstable'].computeFn({ instanceInfo.position = std.add(instanceInfo.position, instanceInfo.velocity); - nextTrianglePos.value[index] = instanceInfo; + nextTrianglePos.value[index] = TriangleData(instanceInfo); }); const computePipeline = root['~unstable'] diff --git a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts index d2cf6ae204..9e7570b561 100644 --- a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts @@ -49,7 +49,7 @@ const getCell = (x: number, y: number): d.v4f => { const setCell = (x: number, y: number, value: d.v4f) => { 'kernel'; const index = coordsToIndex(x, y); - outputGridSlot.$[index] = value; + outputGridSlot.$[index] = d.vec4f(value); }; const setVelocity = (x: number, y: number, velocity: d.v2f) => { @@ -202,7 +202,7 @@ const mainInitWorld = tgpu['~unstable'].computeFn({ } } - outputGridSlot.$[index] = value; + outputGridSlot.$[index] = d.vec4f(value); }); const mainMoveObstacles = tgpu['~unstable'].computeFn({ workgroupSize: [1] })( @@ -366,7 +366,7 @@ const mainCompute = tgpu['~unstable'].computeFn({ const minInflow = getMinimumInFlow(x, y); next.z = std.max(minInflow, next.z); - outputGridSlot.$[index] = next; + outputGridSlot.$[index] = d.vec4f(next); }); const OBSTACLE_BOX = 0; diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts index 54042282d1..cf173bfb73 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts @@ -122,7 +122,7 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ } } - computeLayout.$.outState[input.gid.x] = updatedCurrent; + computeLayout.$.outState[input.gid.x] = CelestialBody(updatedCurrent); }); export const computeGravityShader = tgpu['~unstable'].computeFn({ @@ -182,5 +182,5 @@ export const computeGravityShader = tgpu['~unstable'].computeFn({ ); } - computeLayout.$.outState[input.gid.x] = updatedCurrent; + computeLayout.$.outState[input.gid.x] = CelestialBody(updatedCurrent); }); diff --git a/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts b/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts index 2bcf0ee7a9..aac8c5060b 100644 --- a/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts +++ b/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts @@ -111,10 +111,10 @@ export const advectFn = tgpu['~unstable'].computeFn({ const clampedPos = std.clamp( prevPos, d.vec2f(-0.5), - d.vec2f(std.sub(d.vec2f(texSize.xy), d.vec2f(0.5))), + d.vec2f(texSize.xy).sub(d.vec2f(0.5)), ); const normalizedPos = std.div( - std.add(clampedPos, d.vec2f(0.5)), + clampedPos.add(d.vec2f(0.5)), d.vec2f(texSize.xy), ); diff --git a/apps/typegpu-docs/src/examples/tests/wgsl-resolution/index.ts b/apps/typegpu-docs/src/examples/tests/wgsl-resolution/index.ts index b9a69e70a6..7f2e51cecc 100644 --- a/apps/typegpu-docs/src/examples/tests/wgsl-resolution/index.ts +++ b/apps/typegpu-docs/src/examples/tests/wgsl-resolution/index.ts @@ -165,7 +165,7 @@ const mainCompute = tgpu['~unstable'].computeFn({ instanceInfo.position = std.add(instanceInfo.position, instanceInfo.velocity); - nextTrianglePos.value[index] = instanceInfo; + nextTrianglePos.value[index] = TriangleData(instanceInfo); }).$name('compute shader'); // WGSL resolution diff --git a/packages/typegpu/src/data/ptr.ts b/packages/typegpu/src/data/ptr.ts index 00403e2ad2..4fc9c4a641 100644 --- a/packages/typegpu/src/data/ptr.ts +++ b/packages/typegpu/src/data/ptr.ts @@ -38,7 +38,7 @@ export function ptrHandle( return INTERNAL_createPtr('handle', inner, 'read'); } -function INTERNAL_createPtr< +export function INTERNAL_createPtr< TAddressSpace extends AddressSpace, TInner extends StorableData, TAccess extends Access, diff --git a/packages/typegpu/src/data/vector.ts b/packages/typegpu/src/data/vector.ts index dfebf7432e..00a256599e 100644 --- a/packages/typegpu/src/data/vector.ts +++ b/packages/typegpu/src/data/vector.ts @@ -317,7 +317,13 @@ function makeVecSchema( returnType: schema as AnyData, }), normalImpl: cpuConstruct, - codegenImpl: (...args) => stitch`${type}(${args})`, + codegenImpl: (...args) => { + if (args.length === 1 && args[0]?.dataType === schema) { + // Already typed as the schema + return stitch`${args[0]}`; + } + return stitch`${type}(${args})`; + }, }); const schema: diff --git a/packages/typegpu/src/data/wgslTypes.ts b/packages/typegpu/src/data/wgslTypes.ts index 9dd9f04ded..96a56b6754 100644 --- a/packages/typegpu/src/data/wgslTypes.ts +++ b/packages/typegpu/src/data/wgslTypes.ts @@ -1361,6 +1361,15 @@ export type AddressSpace = | 'handle'; export type Access = 'read' | 'write' | 'read-write'; +export const addressSpaceToDefaultAccess = { + uniform: 'read', + storage: 'read', + workgroup: 'read-write', + private: 'read-write', + function: 'read-write', + handle: 'read', +} as const; + export interface Ptr< TAddr extends AddressSpace = AddressSpace, TInner extends StorableData = StorableData, diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index 50b7e2c380..7a593fe1f1 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -3,9 +3,13 @@ import { type ShelllessImpl, } from '../core/function/shelllessImpl.ts'; import type { AnyData } from '../data/dataTypes.ts'; -import { ptrFn } from '../data/ptr.ts'; +import { INTERNAL_createPtr } from '../data/ptr.ts'; import type { Snippet } from '../data/snippet.ts'; -import { isPtr, type StorableData } from '../data/wgslTypes.ts'; +import { + addressSpaceToDefaultAccess, + isPtr, + type StorableData, +} from '../data/wgslTypes.ts'; import { getMetaData } from '../shared/meta.ts'; import { concretize } from './generationHelpers.ts'; @@ -34,7 +38,13 @@ export class ShelllessRepository { const argTypes = argSnippets.map((s) => { const type = concretize(s.dataType as AnyData); - return s.ref && !isPtr(type) ? ptrFn(type as StorableData) : type; + return s.ref !== undefined && !isPtr(type) + ? INTERNAL_createPtr( + s.ref, + type as StorableData, + addressSpaceToDefaultAccess[s.ref], + ) + : type; }); let cache = this.cache.get(fn); diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 424895888f..773a9675d6 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -8,6 +8,7 @@ import { isData, isLooseData, MatrixColumnsAccess, + toStorable, UnknownData, } from '../data/dataTypes.ts'; import { abstractInt, bool, u32 } from '../data/numeric.ts'; @@ -222,19 +223,27 @@ ${this.ctx.pre}}`; expression[0] === NODE.assignmentExpr ) { // Logical/Binary/Assignment Expression - const [_, lhs, op, rhs] = expression; + const [exprType, lhs, op, rhs] = expression; const lhsExpr = this.expression(lhs); const rhsExpr = this.expression(rhs); + if (lhsExpr.dataType.type === 'unknown') { + throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type`); + } + + if (rhsExpr.dataType.type === 'unknown') { + throw new WgslTypeError( + `Right-hand side of '${op}' is of unknown type`, + ); + } + const codegen = opCodeToCodegen[op as keyof typeof opCodeToCodegen]; if (codegen) { return codegen(lhsExpr, rhsExpr); } - const forcedType = expression[0] === NODE.assignmentExpr - ? lhsExpr.dataType.type === 'ptr' - ? [lhsExpr.dataType.inner as AnyData] - : [lhsExpr.dataType as AnyData] + const forcedType = exprType === NODE.assignmentExpr + ? [toStorable(lhsExpr.dataType)] : undefined; const [convLhs, convRhs] = @@ -245,6 +254,14 @@ ${this.ctx.pre}}`; const rhsStr = this.ctx.resolve(convRhs.value, convRhs.dataType).value; const type = operatorToType(convLhs.dataType, op, convRhs.dataType); + if (exprType === NODE.assignmentExpr && rhsExpr.ref !== undefined) { + throw new WgslTypeError( + `'${lhsStr} = ${rhsStr}' is invalid, because references cannot be assigned.\n-----\nTry '${lhsStr} = ${ + this.ctx.resolve(rhsExpr.dataType).value + }(${rhsStr})' instead.\n-----`, + ); + } + return snip( parenthesizedOps.includes(op) ? `(${lhsStr} ${op} ${rhsStr})` diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index 56ebcf2fc9..59e7596891 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -118,7 +118,7 @@ describe('3d fish example', () => { @group(0) @binding(2) var mouseRay_5: MouseRay_6; - fn projectPointOnLine_8(point: ptr, line: ptr) -> vec3f { + fn projectPointOnLine_8(point: ptr, line: ptr) -> vec3f { var pointVector = (*point - (*line).origin); var projection = dot(pointVector, (*line).dir); return ((*line).origin + ((*line).dir * projection)); diff --git a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts index ba7ee088b1..3840ef0855 100644 --- a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts +++ b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts @@ -29,7 +29,7 @@ describe('cubemap reflection example', () => { @group(0) @binding(0) var prevVertices_1: array; - fn unpackVec2u_3(packed: ptr) -> vec4f { + fn unpackVec2u_3(packed: ptr) -> vec4f { var xy = unpack2x16float((*packed).x); var zw = unpack2x16float((*packed).y); return vec4f(xy, zw); diff --git a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts index 3bec7a3622..87f78e9d1c 100644 --- a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts +++ b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts @@ -46,7 +46,7 @@ describe('stable-fluid example', () => { var velocity = textureLoad(src_1, pixelPos, 0); var timeStep = simParams_3.dt; var prevPos = (vec2f(pixelPos) - (timeStep * velocity.xy)); - var clampedPos = clamp(prevPos, vec2f(-0.5), vec2f((vec2f(texSize.xy) - vec2f(0.5)))); + var clampedPos = clamp(prevPos, vec2f(-0.5), (vec2f(texSize.xy) - vec2f(0.5))); var normalizedPos = ((clampedPos + vec2f(0.5)) / vec2f(texSize.xy)); var prevVelocity = textureSampleLevel(src_1, linSampler_5, normalizedPos, 0); textureStore(dst_2, pixelPos, prevVelocity); diff --git a/packages/typegpu/tests/examples/individual/vaporrave.test.ts b/packages/typegpu/tests/examples/individual/vaporrave.test.ts index 0d9eb78a7c..0fe6b16fa1 100644 --- a/packages/typegpu/tests/examples/individual/vaporrave.test.ts +++ b/packages/typegpu/tests/examples/individual/vaporrave.test.ts @@ -194,7 +194,7 @@ describe('vaporrave example', () => { var p = ((rd * distOrigin) + ro); var scene = getSceneRay_7(p); var sphereDist = getSphere_12(p, sphereColorUniform_21, vec3f(0, 6, 12), sphereAngleUniform_22); - glow = ((vec3f(sphereColorUniform_21) * exp(-sphereDist.dist)) + glow); + glow = ((sphereColorUniform_21 * exp(-sphereDist.dist)) + glow); distOrigin += scene.dist; if ((distOrigin > 19)) { result.dist = 19; From 07ed41173f67ba6029e6d9d07291e8b48c0dda8d Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 18:09:46 +0200 Subject: [PATCH 06/59] JS const with ref value is WGSL let with pointer --- .../src/examples/rendering/3d-fish/render.ts | 4 +- .../rendering/xor-dev-runner/index.ts | 2 +- packages/typegpu/src/core/valueProxyUtils.ts | 22 ++--- .../typegpu/src/tgsl/generationHelpers.ts | 62 ++++++++++++- packages/typegpu/src/tgsl/wgslGenerator.ts | 26 +++++- packages/typegpu/tests/accessor.test.ts | 36 +++----- packages/typegpu/tests/bufferUsage.test.ts | 76 +++++++-------- packages/typegpu/tests/derived.test.ts | 41 ++++----- .../tests/examples/individual/3d-fish.test.ts | 10 +- .../examples/individual/boids-next.test.ts | 38 ++++---- .../individual/cubemap-reflection.test.ts | 12 +-- .../individual/fluid-double-buffering.test.ts | 92 +++++++++---------- .../tests/examples/individual/gravity.test.ts | 28 +++--- .../examples/individual/matrix-next.test.ts | 18 ++-- .../examples/individual/perlin-noise.test.ts | 4 +- .../examples/individual/ray-marching.test.ts | 6 +- .../examples/individual/simple-shadow.test.ts | 16 ++-- .../individual/wgsl-resolution.test.ts | 38 ++++---- packages/typegpu/tests/indent.test.ts | 14 +-- packages/typegpu/tests/slot.test.ts | 4 +- .../typegpu/tests/std/matrix/rotate.test.ts | 19 ++-- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 16 ++-- packages/typegpu/tests/variable.test.ts | 32 +++---- 23 files changed, 325 insertions(+), 291 deletions(-) diff --git a/apps/typegpu-docs/src/examples/rendering/3d-fish/render.ts b/apps/typegpu-docs/src/examples/rendering/3d-fish/render.ts index f9a65909c8..0ff46783c4 100644 --- a/apps/typegpu-docs/src/examples/rendering/3d-fish/render.ts +++ b/apps/typegpu-docs/src/examples/rendering/3d-fish/render.ts @@ -136,7 +136,7 @@ export const fragmentShader = tgpu['~unstable'].fragmentFn({ std.sub(layout.$.camera.position.xyz, input.worldPosition), ); - let desaturatedColor = lightedColor; + let desaturatedColor = d.vec3f(lightedColor); if (input.applySeaDesaturation === 1) { const desaturationFactor = -std.atan2((distanceFromCamera - 5) / 10, 1) / 3; @@ -148,7 +148,7 @@ export const fragmentShader = tgpu['~unstable'].fragmentFn({ desaturatedColor = hsvToRgb(hsv); } - let foggedColor = desaturatedColor; + let foggedColor = d.vec3f(desaturatedColor); if (input.applySeaFog === 1) { const fogParameter = std.max(0, (distanceFromCamera - 1.5) * 0.2); const fogFactor = fogParameter / (1 + fogParameter); diff --git a/apps/typegpu-docs/src/examples/rendering/xor-dev-runner/index.ts b/apps/typegpu-docs/src/examples/rendering/xor-dev-runner/index.ts index 93b5f2cf14..06e1dac7ce 100644 --- a/apps/typegpu-docs/src/examples/rendering/xor-dev-runner/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/xor-dev-runner/index.ts @@ -78,7 +78,7 @@ const fragmentMain = tgpu['~unstable'].fragmentFn({ const p = sub(mul(z, dir), scale.$); p.x -= time.$ + 3; p.z -= time.$ + 3; - let q = p; + let q = d.vec3f(p); let prox = p.y; for (let i = 40.1; i > 0.01; i *= 0.2) { q = sub(i * 0.9, abs(sub(mod(q, i + i), i))); diff --git a/packages/typegpu/src/core/valueProxyUtils.ts b/packages/typegpu/src/core/valueProxyUtils.ts index b0ad54531c..fb106350b2 100644 --- a/packages/typegpu/src/core/valueProxyUtils.ts +++ b/packages/typegpu/src/core/valueProxyUtils.ts @@ -1,9 +1,7 @@ -import type { AnyData } from '../data/dataTypes.ts'; -import { snip, type Snippet } from '../data/snippet.ts'; -import { isNaturallyRef } from '../data/wgslTypes.ts'; +import type { Snippet } from '../data/snippet.ts'; import { getGPUValue } from '../getGPUValue.ts'; import { $internal, $ownSnippet, $resolve } from '../shared/symbols.ts'; -import { getTypeForPropAccess } from '../tgsl/generationHelpers.ts'; +import { accessProp } from '../tgsl/generationHelpers.ts'; import { getOwnSnippet, type SelfResolvable, @@ -31,24 +29,16 @@ export const valueProxyHandler: ProxyHandler< } const targetSnippet = getOwnSnippet(target) as Snippet; - const targetDataType = targetSnippet.dataType as AnyData; - const propType = getTypeForPropAccess(targetDataType, String(prop)); - if (propType.type === 'unknown') { + const accessed = accessProp(targetSnippet, String(prop)); + if (!accessed) { // Prop was not found, must be missing from this object return undefined; } - const ref = targetSnippet.ref !== undefined && isNaturallyRef(propType) - ? targetSnippet.ref - : undefined; - return new Proxy({ [$internal]: true, - [$resolve]: (ctx) => - snip(`${ctx.resolve(target).value}.${String(prop)}`, propType, ref), - get [$ownSnippet]() { - return snip(this, propType, ref); - }, + [$resolve]: () => accessed, + [$ownSnippet]: accessed, toString: () => `${String(target)}.${prop}`, }, valueProxyHandler); }, diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 5ddc0aa23d..c2af32f5e0 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -15,7 +15,12 @@ import { i32, u32, } from '../data/numeric.ts'; -import { isSnippet, snip, type Snippet } from '../data/snippet.ts'; +import { + isSnippet, + type ResolvedSnippet, + snip, + type Snippet, +} from '../data/snippet.ts'; import { vec2b, vec2f, @@ -38,12 +43,14 @@ import { type F32, type I32, isMatInstance, + isNaturallyRef, isNumericSchema, isVec, isVecInstance, isWgslArray, isWgslStruct, } from '../data/wgslTypes.ts'; +import { getResolutionCtx } from '../execMode.ts'; import { getOwnSnippet, type ResolutionCtx } from '../types.ts'; import type { ShelllessRepository } from './shellless.ts'; @@ -140,6 +147,59 @@ export function getTypeForPropAccess( return UnknownData; } +export function accessProp( + target: Snippet, + propName: string, +): ResolvedSnippet | undefined { + // biome-ignore lint/style/noNonNullAssertion: it's there + const ctx = getResolutionCtx()!; + + if (isWgslStruct(target.dataType) || isUnstruct(target.dataType)) { + let propType = target.dataType.propTypes[propName]; + if (!propType) { + return undefined; + } + propType = undecorate(propType); + + return snip( + `${ctx.resolve(target.value, target.dataType).value}.${propName}`, + propType, + /* ref */ target.ref !== undefined && isNaturallyRef(propType) + ? target.ref + : undefined, + ); + } + + if (target.dataType.type === 'bool' || isNumericSchema(target.dataType)) { + // No props to be accessed here + return undefined; + } + + const propLength = propName.length; + if ( + isVec(target.dataType) && + propLength >= 1 && + propLength <= 4 + ) { + const swizzleTypeChar = target.dataType.type.includes('bool') + ? 'b' + : (target.dataType.type[4] as SwizzleableType); + const swizzleType = + swizzleLenToType[swizzleTypeChar][propLength as SwizzleLength]; + if (!swizzleType) { + return undefined; + } + return snip( + `${ctx.resolve(target.value, target.dataType).value}.${propName}`, + swizzleType, + // Swizzling creates new vectors (unless they're on the lhs of an assignment, but that's not yet supported in WGSL) + /* ref */ undefined, + ); + } + + return undefined; +} + const indexableTypeToResult = { mat2x2f: vec2f, mat3x3f: vec3f, diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 773a9675d6..67926f2900 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -36,6 +36,7 @@ import { import type { ShaderGenerator } from './shaderGenerator.ts'; import { safeStringify } from '../shared/safeStringify.ts'; import type { DualFn } from '../data/dualFn.ts'; +import { ptrFn } from '../data/ptr.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -794,7 +795,8 @@ ${this.ctx.pre}else ${alternate}`; } if (statement[0] === NODE.let || statement[0] === NODE.const) { - const [_, rawId, rawValue] = statement; + let varType = 'var'; + const [stmtType, rawId, rawValue] = statement; const eq = rawValue !== undefined ? this.expression(rawValue) : undefined; if (!eq) { @@ -809,12 +811,28 @@ ${this.ctx.pre}else ${alternate}`; ); } + let dataType = eq.dataType as wgsl.AnyWgslData; + // Assigning a reference to a `const` variable means we store the pointer + // of the rhs. + if (eq.ref !== undefined && !wgsl.isPtr(dataType)) { + if (stmtType === NODE.let) { + throw new WgslTypeError( + `Cannot assign a reference to a let variable ('${rawId}'). Use const instead, or copy the right-hand side.`, + ); + } + + varType = 'let'; + if (!wgsl.isPtr(dataType)) { + dataType = ptrFn(concretize(dataType) as wgsl.StorableData); + } + } + const snippet = this.blockVariable( rawId, - concretize(eq.dataType as wgsl.AnyWgslData), + concretize(dataType), ); - return stitchWithExactTypes`${this.ctx.pre}var ${snippet - .value as string} = ${eq};`; + return stitchWithExactTypes`${this.ctx.pre}${varType} ${snippet + .value as string} = ${tryConvertSnippet(eq, dataType, false)};`; } if (statement[0] === NODE.block) { diff --git a/packages/typegpu/tests/accessor.test.ts b/packages/typegpu/tests/accessor.test.ts index b3f6bd21f7..b52d9a7712 100644 --- a/packages/typegpu/tests/accessor.test.ts +++ b/packages/typegpu/tests/accessor.test.ts @@ -152,30 +152,22 @@ describe('tgpu.accessor', () => { const color3X = colorAccessorFn.value.x; }); - const resolved = tgpu.resolve({ - externals: { main }, - names: 'strict', - }); - - expect(parse(resolved)).toBe( - parse(/* wgsl */ ` - @group(0) @binding(0) var redUniform: vec3f; - - fn getColor() -> vec3f { - return vec3f(1, 0, 0); - } + expect(asWgsl(main)).toMatchInlineSnapshot(` + "@group(0) @binding(0) var redUniform: vec3f; - fn main() { - var color = vec3f(1, 0, 0); - var color2 = redUniform; - var color3 = getColor(); + fn getColor() -> vec3f { + return vec3f(1, 0, 0); + } - var colorX = 1; - var color2X = redUniform.x; - var color3X = getColor().x; - } - `), - ); + fn main() { + var color = vec3f(1, 0, 0); + let color2 = &redUniform; + var color3 = getColor(); + var colorX = 1; + var color2X = redUniform.x; + var color3X = getColor().x; + }" + `); }); it('retains type information', ({ root }) => { diff --git a/packages/typegpu/tests/bufferUsage.test.ts b/packages/typegpu/tests/bufferUsage.test.ts index 21738f40fa..10e8d2a9c3 100644 --- a/packages/typegpu/tests/bufferUsage.test.ts +++ b/packages/typegpu/tests/bufferUsage.test.ts @@ -1,5 +1,5 @@ import { describe, expect, expectTypeOf } from 'vitest'; -import { parse } from './utils/parseResolved.ts'; +import { asWgsl, parse } from './utils/parseResolved.ts'; import tgpu from '../src/index.ts'; @@ -80,20 +80,19 @@ describe('TgpuBufferUniform', () => { names: 'strict', }); - expect(parse(resolved)).toBe( - parse(` - struct Boid { - pos: vec3f, - vel: vec3u, - } + expect(asWgsl(func)).toMatchInlineSnapshot(` + "struct Boid { + pos: vec3f, + vel: vec3u, + } - @group(0) @binding(0) var boid: Boid; + @group(0) @binding(0) var boid: Boid; - fn func() { - var pos = boid.pos; - var velX = boid.vel.x; - }`), - ); + fn func() { + let pos = &boid.pos; + var velX = boid.vel.x; + }" + `); }); }); @@ -172,20 +171,19 @@ describe('TgpuBufferMutable', () => { names: 'strict', }); - expect(parse(resolved)).toBe( - parse(` - struct Boid { - pos: vec3f, - vel: vec3u, - } + expect(asWgsl(func)).toMatchInlineSnapshot(` + "struct Boid { + pos: vec3f, + vel: vec3u, + } - @group(0) @binding(0) var boid: Boid; + @group(0) @binding(0) var boid: Boid; - fn func() { - var pos = boid.pos; - var velX = boid.vel.x; - }`), - ); + fn func() { + let pos = &boid.pos; + var velX = boid.vel.x; + }" + `); }); describe('simulate mode', () => { @@ -277,25 +275,19 @@ describe('TgpuBufferReadonly', () => { const velX = boidReadonly.value.vel.x; }); - const resolved = tgpu.resolve({ - externals: { func }, - names: 'strict', - }); + expect(asWgsl(func)).toMatchInlineSnapshot(` + "struct Boid { + pos: vec3f, + vel: vec3u, + } - expect(parse(resolved)).toBe( - parse(` - struct Boid { - pos: vec3f, - vel: vec3u, - } + @group(0) @binding(0) var boid: Boid; - @group(0) @binding(0) var boid: Boid; - - fn func() { - var pos = boid.pos; - var velX = boid.vel.x; - }`), - ); + fn func() { + let pos = &boid.pos; + var velX = boid.vel.x; + }" + `); }); it('cannot be accessed via .$ or .value top-level', ({ root }) => { diff --git a/packages/typegpu/tests/derived.test.ts b/packages/typegpu/tests/derived.test.ts index 69a95c0c5b..61743e050a 100644 --- a/packages/typegpu/tests/derived.test.ts +++ b/packages/typegpu/tests/derived.test.ts @@ -3,7 +3,7 @@ import * as d from '../src/data/index.ts'; import tgpu, { type TgpuDerived } from '../src/index.ts'; import { mul } from '../src/std/index.ts'; import { it } from './utils/extendedIt.ts'; -import { parse, parseResolved } from './utils/parseResolved.ts'; +import { asWgsl, parse, parseResolved } from './utils/parseResolved.ts'; describe('TgpuDerived', () => { it('memoizes results of transitive "derived"', () => { @@ -136,30 +136,23 @@ describe('TgpuDerived', () => { const velX_ = derivedDerivedUniformSlot.value.vel.x; }); - const resolved = tgpu.resolve({ - externals: { func }, - names: 'strict', - }); - - expect(parse(resolved)).toBe( - parse(` - struct Boid { - pos: vec3f, - vel: vec3u, - } - - @group(0) @binding(0) var boid: Boid; - - fn func(){ - var pos = vec3f(2, 4, 6); - var posX = 2; - var vel = boid.vel; - var velX = boid.vel.x; + expect(asWgsl(func)).toMatchInlineSnapshot(` + "struct Boid { + pos: vec3f, + vel: vec3u, + } - var vel_ = boid.vel; - var velX_ = boid.vel.x; - }`), - ); + @group(0) @binding(0) var boid: Boid; + + fn func() { + var pos = vec3f(2, 4, 6); + var posX = 2; + let vel = &boid.vel; + var velX = boid.vel.x; + let vel_ = &boid.vel; + var velX_ = boid.vel.x; + }" + `); }); // TODO: rethink this behavior of derived returning a function, diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index 59e7596891..2edce12bbc 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -274,8 +274,8 @@ describe('3d fish example', () => { var translationMatrix = mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, currentModelData.position.x, currentModelData.position.y, currentModelData.position.z, 1); var worldPosition = (translationMatrix * (yawMatrix * (pitchMatrix * (scaleMatrix * vec4f(wavedVertex.position, 1))))); var worldNormal = normalize((yawMatrix * (pitchMatrix * vec4f(wavedVertex.normal, 1))).xyz); - var worldPositionUniform = worldPosition; - var canvasPosition = (camera_6.projection * (camera_6.view * worldPositionUniform)); + let worldPositionUniform = &worldPosition; + var canvasPosition = (camera_6.projection * (camera_6.view * *worldPositionUniform)); return vertexShader_Output_8(worldPosition.xyz, worldNormal, canvasPosition, currentModelData.variant, input.textureUV, currentModelData.applySeaFog, currentModelData.applySeaDesaturation); } @@ -400,10 +400,10 @@ describe('3d fish example', () => { @fragment fn fragmentShader_10(input: fragmentShader_Input_16) -> @location(0) vec4f { var textureColorWithAlpha = sampleTexture_11(input.textureUV); - var textureColor = textureColorWithAlpha.xyz; - var ambient = (0.5 * (textureColor * vec3f(0.800000011920929, 0.800000011920929, 1))); + let textureColor = &textureColorWithAlpha.xyz; + var ambient = (0.5 * (*textureColor * vec3f(0.800000011920929, 0.800000011920929, 1))); var cosTheta = dot(input.worldNormal, vec3f(-0.2357022613286972, 0.9428090453147888, -0.2357022613286972)); - var diffuse = (max(0, cosTheta) * (textureColor * vec3f(0.800000011920929, 0.800000011920929, 1))); + var diffuse = (max(0, cosTheta) * (*textureColor * vec3f(0.800000011920929, 0.800000011920929, 1))); var viewSource = normalize((camera_6.position.xyz - input.worldPosition)); var reflectSource = normalize(reflect((-1 * vec3f(-0.2357022613286972, 0.9428090453147888, -0.2357022613286972)), input.worldNormal)); var specularStrength = pow(max(0, dot(viewSource, reflectSource)), 16); diff --git a/packages/typegpu/tests/examples/individual/boids-next.test.ts b/packages/typegpu/tests/examples/individual/boids-next.test.ts index a66d077aae..4793182d01 100644 --- a/packages/typegpu/tests/examples/individual/boids-next.test.ts +++ b/packages/typegpu/tests/examples/individual/boids-next.test.ts @@ -43,7 +43,7 @@ describe('boids next example', () => { @compute @workgroup_size(1) fn mainCompute_0(input: mainCompute_Input_6) { var index = input.gid.x; - var instanceInfo = currentTrianglePos_1[index]; + let instanceInfo = ¤tTrianglePos_1[index]; var separation = vec2f(); var alignment = vec2f(); var cohesion = vec2f(); @@ -53,17 +53,17 @@ describe('boids next example', () => { if ((i == index)) { continue; } - var other = currentTrianglePos_1[i]; - var dist = distance(instanceInfo.position, other.position); + let other = ¤tTrianglePos_1[i]; + var dist = distance((*instanceInfo).position, (*other).position); if ((dist < paramsBuffer_3.separationDistance)) { - separation = (separation + (instanceInfo.position - other.position)); + separation = (separation + ((*instanceInfo).position - (*other).position)); } if ((dist < paramsBuffer_3.alignmentDistance)) { - alignment = (alignment + other.velocity); + alignment = (alignment + (*other).velocity); alignmentCount++; } if ((dist < paramsBuffer_3.cohesionDistance)) { - cohesion = (cohesion + other.position); + cohesion = (cohesion + (*other).position); cohesionCount++; } } @@ -72,27 +72,27 @@ describe('boids next example', () => { } if ((cohesionCount > 0)) { cohesion = ((1f / f32(cohesionCount)) * cohesion); - cohesion = (cohesion - instanceInfo.position); + cohesion = (cohesion - (*instanceInfo).position); } var velocity = (paramsBuffer_3.separationStrength * separation); velocity = (velocity + (paramsBuffer_3.alignmentStrength * alignment)); velocity = (velocity + (paramsBuffer_3.cohesionStrength * cohesion)); - instanceInfo.velocity = (instanceInfo.velocity + velocity); - instanceInfo.velocity = (clamp(length(instanceInfo.velocity), 0, 0.01) * normalize(instanceInfo.velocity)); - if ((instanceInfo.position.x > 1.03)) { - instanceInfo.position.x = (-1 - 0.03); + (*instanceInfo).velocity = ((*instanceInfo).velocity + velocity); + (*instanceInfo).velocity = (clamp(length((*instanceInfo).velocity), 0, 0.01) * normalize((*instanceInfo).velocity)); + if (((*instanceInfo).position.x > 1.03)) { + (*instanceInfo).position.x = (-1 - 0.03); } - if ((instanceInfo.position.y > 1.03)) { - instanceInfo.position.y = (-1 - 0.03); + if (((*instanceInfo).position.y > 1.03)) { + (*instanceInfo).position.y = (-1 - 0.03); } - if ((instanceInfo.position.x < (-1 - 0.03))) { - instanceInfo.position.x = 1.03; + if (((*instanceInfo).position.x < (-1 - 0.03))) { + (*instanceInfo).position.x = 1.03; } - if ((instanceInfo.position.y < (-1 - 0.03))) { - instanceInfo.position.y = 1.03; + if (((*instanceInfo).position.y < (-1 - 0.03))) { + (*instanceInfo).position.y = 1.03; } - instanceInfo.position = (instanceInfo.position + instanceInfo.velocity); - nextTrianglePos_5[index] = instanceInfo; + (*instanceInfo).position = ((*instanceInfo).position + (*instanceInfo).velocity); + nextTrianglePos_5[index] = *instanceInfo; } fn getRotationFromVelocity_1(velocity: vec2f) -> f32 { diff --git a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts index 3840ef0855..dfa505a587 100644 --- a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts +++ b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts @@ -75,17 +75,17 @@ describe('cubemap reflection example', () => { var newVertices = array(v1, v12, v31, v2, v23, v12, v3, v31, v23, v12, v23, v31); var baseIndexNext = (triangleIndex * 12); for (var i = 0u; (i < 12); i++) { - var reprojectedVertex = newVertices[i]; + let reprojectedVertex = &newVertices[i]; var triBase = (i - (i % 3)); var normal = reprojectedVertex; if ((smoothFlag_5 == 0)) { - normal = getAverageNormal_6(&newVertices[triBase], &newVertices[(triBase + 1)], &newVertices[(triBase + 2)]); + *normal = getAverageNormal_6(&newVertices[triBase], &newVertices[(triBase + 1)], &newVertices[(triBase + 2)]); } var outIndex = (baseIndexNext + i); - var nextVertex = nextVertices_7[outIndex]; - nextVertex.position = packVec2u_8(&reprojectedVertex); - nextVertex.normal = packVec2u_8(&normal); - nextVertices_7[outIndex] = nextVertex; + let nextVertex = &nextVertices_7[outIndex]; + (*nextVertex).position = packVec2u_8(reprojectedVertex); + (*nextVertex).normal = packVec2u_8(normal); + nextVertices_7[outIndex] = *nextVertex; } } diff --git a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts index 01c094bef5..ba64ede91e 100644 --- a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts @@ -35,14 +35,14 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_4(x: i32, y: i32) -> bool { for (var obsIdx = 0; (obsIdx < 4); obsIdx++) { - var obs = obstacles_5[obsIdx]; - if ((obs.enabled == 0)) { + let obs = &obstacles_5[obsIdx]; + if (((*obs).enabled == 0)) { continue; } - var minX = max(0, (obs.center.x - i32((f32(obs.size.x) / 2f)))); - var maxX = min(256, (obs.center.x + i32((f32(obs.size.x) / 2f)))); - var minY = max(0, (obs.center.y - i32((f32(obs.size.y) / 2f)))); - var maxY = min(256, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + var minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + var maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + var minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + var maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -119,14 +119,14 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_11(x: i32, y: i32) -> bool { for (var obsIdx = 0; (obsIdx < 4); obsIdx++) { - var obs = obstacles_12[obsIdx]; - if ((obs.enabled == 0)) { + let obs = &obstacles_12[obsIdx]; + if (((*obs).enabled == 0)) { continue; } - var minX = max(0, (obs.center.x - i32((f32(obs.size.x) / 2f)))); - var maxX = min(256, (obs.center.x + i32((f32(obs.size.x) / 2f)))); - var minY = max(0, (obs.center.y - i32((f32(obs.size.y) / 2f)))); - var maxY = min(256, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + var minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + var maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + var minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + var maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -156,7 +156,7 @@ describe('fluid double buffering example', () => { return item_15(); } - fn computeVelocity_8(x: i32, y: i32) -> vec2f { + fn computeVelocity_8(x: i32, y: i32) -> ptr { var gravityCost = 0.5; var neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); var cell = getCell_6(x, y); @@ -164,25 +164,25 @@ describe('fluid double buffering example', () => { var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; for (var i = 0; (i < 4); i++) { - var offset = neighborOffsets[i]; - var neighborDensity = getCell_6((x + offset.x), (y + offset.y)); - var cost = (neighborDensity.z + (f32(offset.y) * gravityCost)); - if (!isValidFlowOut_9((x + offset.x), (y + offset.y))) { + let offset = &neighborOffsets[i]; + var neighborDensity = getCell_6((x + (*offset).x), (y + (*offset).y)); + var cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); + if (!isValidFlowOut_9((x + (*offset).x), (y + (*offset).y))) { continue; } if ((cost == leastCost)) { - dirChoices[dirChoiceCount] = vec2f(f32(offset.x), f32(offset.y)); + dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); dirChoiceCount++; } else { if ((cost < leastCost)) { leastCost = cost; - dirChoices[0] = vec2f(f32(offset.x), f32(offset.y)); + dirChoices[0] = vec2f(f32((*offset).x), f32((*offset).y)); dirChoiceCount = 1; } } } - var leastCostDir = dirChoices[u32((randFloat01_14() * f32(dirChoiceCount)))]; + let leastCostDir = &dirChoices[u32((randFloat01_14() * f32(dirChoiceCount)))]; return leastCostDir; } @@ -238,8 +238,8 @@ describe('fluid double buffering example', () => { randSeed2_3(vec2f(f32(index), time_2)); var next = getCell_6(x, y); var nextVelocity = computeVelocity_8(x, y); - next.x = nextVelocity.x; - next.y = nextVelocity.y; + next.x = (*nextVelocity).x; + next.y = (*nextVelocity).y; next.z = flowFromCell_16(x, y, x, y); next.z += flowFromCell_16(x, y, x, (y + 1)); next.z += flowFromCell_16(x, y, x, (y - 1)); @@ -286,14 +286,14 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_11(x: i32, y: i32) -> bool { for (var obsIdx = 0; (obsIdx < 4); obsIdx++) { - var obs = obstacles_12[obsIdx]; - if ((obs.enabled == 0)) { + let obs = &obstacles_12[obsIdx]; + if (((*obs).enabled == 0)) { continue; } - var minX = max(0, (obs.center.x - i32((f32(obs.size.x) / 2f)))); - var maxX = min(256, (obs.center.x + i32((f32(obs.size.x) / 2f)))); - var minY = max(0, (obs.center.y - i32((f32(obs.size.y) / 2f)))); - var maxY = min(256, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + var minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + var maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + var minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + var maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -323,7 +323,7 @@ describe('fluid double buffering example', () => { return item_15(); } - fn computeVelocity_8(x: i32, y: i32) -> vec2f { + fn computeVelocity_8(x: i32, y: i32) -> ptr { var gravityCost = 0.5; var neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); var cell = getCell_6(x, y); @@ -331,25 +331,25 @@ describe('fluid double buffering example', () => { var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; for (var i = 0; (i < 4); i++) { - var offset = neighborOffsets[i]; - var neighborDensity = getCell_6((x + offset.x), (y + offset.y)); - var cost = (neighborDensity.z + (f32(offset.y) * gravityCost)); - if (!isValidFlowOut_9((x + offset.x), (y + offset.y))) { + let offset = &neighborOffsets[i]; + var neighborDensity = getCell_6((x + (*offset).x), (y + (*offset).y)); + var cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); + if (!isValidFlowOut_9((x + (*offset).x), (y + (*offset).y))) { continue; } if ((cost == leastCost)) { - dirChoices[dirChoiceCount] = vec2f(f32(offset.x), f32(offset.y)); + dirChoices[dirChoiceCount] = vec2f(f32((*offset).x), f32((*offset).y)); dirChoiceCount++; } else { if ((cost < leastCost)) { leastCost = cost; - dirChoices[0] = vec2f(f32(offset.x), f32(offset.y)); + dirChoices[0] = vec2f(f32((*offset).x), f32((*offset).y)); dirChoiceCount = 1; } } } - var leastCostDir = dirChoices[u32((randFloat01_14() * f32(dirChoiceCount)))]; + let leastCostDir = &dirChoices[u32((randFloat01_14() * f32(dirChoiceCount)))]; return leastCostDir; } @@ -405,8 +405,8 @@ describe('fluid double buffering example', () => { randSeed2_3(vec2f(f32(index), time_2)); var next = getCell_6(x, y); var nextVelocity = computeVelocity_8(x, y); - next.x = nextVelocity.x; - next.y = nextVelocity.y; + next.x = (*nextVelocity).x; + next.y = (*nextVelocity).y; next.z = flowFromCell_16(x, y, x, y); next.z += flowFromCell_16(x, y, x, (y + 1)); next.z += flowFromCell_16(x, y, x, (y - 1)); @@ -448,14 +448,14 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_6(x: i32, y: i32) -> bool { for (var obsIdx = 0; (obsIdx < 4); obsIdx++) { - var obs = obstacles_7[obsIdx]; - if ((obs.enabled == 0)) { + let obs = &obstacles_7[obsIdx]; + if (((*obs).enabled == 0)) { continue; } - var minX = max(0, (obs.center.x - i32((f32(obs.size.x) / 2f)))); - var maxX = min(256, (obs.center.x + i32((f32(obs.size.x) / 2f)))); - var minY = max(0, (obs.center.y - i32((f32(obs.size.y) / 2f)))); - var maxY = min(256, (obs.center.y + i32((f32(obs.size.y) / 2f)))); + var minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + var maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + var minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + var maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -471,8 +471,8 @@ describe('fluid double buffering example', () => { var x = i32((input.uv.x * 256)); var y = i32((input.uv.y * 256)); var index = coordsToIndex_4(x, y); - var cell = gridAlphaBuffer_5[index]; - var density = max(0, cell.z); + let cell = &gridAlphaBuffer_5[index]; + var density = max(0, (*cell).z); var obstacleColor = vec4f(0.10000000149011612, 0.10000000149011612, 0.10000000149011612, 1); var background = vec4f(0.8999999761581421, 0.8999999761581421, 0.8999999761581421, 1); var firstColor = vec4f(0.20000000298023224, 0.6000000238418579, 1, 1); diff --git a/packages/typegpu/tests/examples/individual/gravity.test.ts b/packages/typegpu/tests/examples/individual/gravity.test.ts index eead8785a2..7b8d920f03 100644 --- a/packages/typegpu/tests/examples/individual/gravity.test.ts +++ b/packages/typegpu/tests/examples/individual/gravity.test.ts @@ -65,7 +65,7 @@ describe('gravity example', () => { @compute @workgroup_size(1) fn computeCollisionsShader_0(input: computeCollisionsShader_Input_7) { var currentId = input.gid.x; var current = CelestialBody_2(inState_1[currentId].destroyed, inState_1[currentId].position, inState_1[currentId].velocity, inState_1[currentId].mass, inState_1[currentId].radiusMultiplier, inState_1[currentId].collisionBehavior, inState_1[currentId].textureIndex, inState_1[currentId].ambientLightFactor); - var updatedCurrent = current; + let updatedCurrent = ¤t; if ((current.destroyed == 0)) { for (var i = 0; (i < celestialBodiesCount_3); i++) { var otherId = u32(i); @@ -75,25 +75,25 @@ describe('gravity example', () => { } if (((current.collisionBehavior == 1) && (other.collisionBehavior == 1))) { if (isSmaller_5(currentId, otherId)) { - updatedCurrent.position = (other.position + ((radiusOf_4(current) + radiusOf_4(other)) * normalize((current.position - other.position)))); + (*updatedCurrent).position = (other.position + ((radiusOf_4(current) + radiusOf_4(other)) * normalize((current.position - other.position)))); } - updatedCurrent.velocity = (0.99 * (updatedCurrent.velocity - (((((2 * other.mass) / (current.mass + other.mass)) * dot((current.velocity - other.velocity), (current.position - other.position))) / pow(distance(current.position, other.position), 2)) * (current.position - other.position)))); + (*updatedCurrent).velocity = (0.99 * ((*updatedCurrent).velocity - (((((2 * other.mass) / (current.mass + other.mass)) * dot((current.velocity - other.velocity), (current.position - other.position))) / pow(distance(current.position, other.position), 2)) * (current.position - other.position)))); } else { var isCurrentAbsorbed = ((current.collisionBehavior == 1) || ((current.collisionBehavior == 2) && isSmaller_5(currentId, otherId))); if (isCurrentAbsorbed) { - updatedCurrent.destroyed = 1; + (*updatedCurrent).destroyed = 1; } else { - var m1 = updatedCurrent.mass; + var m1 = (*updatedCurrent).mass; var m2 = other.mass; - updatedCurrent.velocity = (((m1 / (m1 + m2)) * updatedCurrent.velocity) + ((m2 / (m1 + m2)) * other.velocity)); - updatedCurrent.mass = (m1 + m2); + (*updatedCurrent).velocity = (((m1 / (m1 + m2)) * (*updatedCurrent).velocity) + ((m2 / (m1 + m2)) * other.velocity)); + (*updatedCurrent).mass = (m1 + m2); } } } } - outState_6[input.gid.x] = updatedCurrent; + outState_6[input.gid.x] = *updatedCurrent; } struct CelestialBody_2 { @@ -131,7 +131,7 @@ describe('gravity example', () => { @compute @workgroup_size(1) fn computeGravityShader_0(input: computeGravityShader_Input_8) { var current = CelestialBody_2(inState_1[input.gid.x].destroyed, inState_1[input.gid.x].position, inState_1[input.gid.x].velocity, inState_1[input.gid.x].mass, inState_1[input.gid.x].radiusMultiplier, inState_1[input.gid.x].collisionBehavior, inState_1[input.gid.x].textureIndex, inState_1[input.gid.x].ambientLightFactor); var dt = (time_3.passed * time_3.multiplier); - var updatedCurrent = current; + let updatedCurrent = ¤t; if ((current.destroyed == 0)) { for (var i = 0; (i < celestialBodiesCount_5); i++) { var other = CelestialBody_2(inState_1[i].destroyed, inState_1[i].position, inState_1[i].velocity, inState_1[i].mass, inState_1[i].radiusMultiplier, inState_1[i].collisionBehavior, inState_1[i].textureIndex, inState_1[i].ambientLightFactor); @@ -141,11 +141,11 @@ describe('gravity example', () => { var dist = max((radiusOf_6(current) + radiusOf_6(other)), distance(current.position, other.position)); var gravityForce = (((current.mass * other.mass) / dist) / dist); var direction = normalize((other.position - current.position)); - updatedCurrent.velocity = (updatedCurrent.velocity + (((gravityForce / current.mass) * dt) * direction)); + (*updatedCurrent).velocity = ((*updatedCurrent).velocity + (((gravityForce / current.mass) * dt) * direction)); } - updatedCurrent.position = (updatedCurrent.position + (dt * updatedCurrent.velocity)); + (*updatedCurrent).position = ((*updatedCurrent).position + (dt * (*updatedCurrent).velocity)); } - outState_7[input.gid.x] = updatedCurrent; + outState_7[input.gid.x] = *updatedCurrent; } struct Camera_2 { @@ -228,8 +228,8 @@ describe('gravity example', () => { @vertex fn mainVertex_0(input: mainVertex_Input_7) -> mainVertex_Output_6 { var currentBody = CelestialBody_2(celestialBodies_1[input.instanceIndex].destroyed, celestialBodies_1[input.instanceIndex].position, celestialBodies_1[input.instanceIndex].velocity, celestialBodies_1[input.instanceIndex].mass, celestialBodies_1[input.instanceIndex].radiusMultiplier, celestialBodies_1[input.instanceIndex].collisionBehavior, celestialBodies_1[input.instanceIndex].textureIndex, celestialBodies_1[input.instanceIndex].ambientLightFactor); var worldPosition = ((radiusOf_3(currentBody) * input.position.xyz) + currentBody.position); - var camera = camera_4; - var positionOnCanvas = (camera.projection * (camera.view * vec4f(worldPosition, 1))); + let camera = &camera_4; + var positionOnCanvas = ((*camera).projection * ((*camera).view * vec4f(worldPosition, 1))); return mainVertex_Output_6(positionOnCanvas, input.uv, input.normal, worldPosition, currentBody.textureIndex, currentBody.destroyed, currentBody.ambientLightFactor); } diff --git a/packages/typegpu/tests/examples/individual/matrix-next.test.ts b/packages/typegpu/tests/examples/individual/matrix-next.test.ts index 8a76158511..e3850021f4 100644 --- a/packages/typegpu/tests/examples/individual/matrix-next.test.ts +++ b/packages/typegpu/tests/examples/individual/matrix-next.test.ts @@ -51,8 +51,8 @@ describe('matrix(next) example', () => { } @compute @workgroup_size(16, 16) fn computeSharedMemory_0(input: computeSharedMemory_Input_10) { - var dimensions = dimensions_1; - var numTiles = u32((f32(((dimensions.firstColumnCount + 16) - 1)) / 16f)); + let dimensions = &dimensions_1; + var numTiles = u32((f32((((*dimensions).firstColumnCount + 16) - 1)) / 16f)); var globalRow = ((input.wid.x * 16) + input.lid.x); var globalCol = ((input.wid.y * 16) + input.lid.y); var localRow = input.lid.x; @@ -62,20 +62,20 @@ describe('matrix(next) example', () => { for (var tileIndex = 0u; (tileIndex < numTiles); tileIndex++) { var matrixACol = ((tileIndex * 16) + localCol); var valueA = 0; - if (((globalRow < dimensions.firstRowCount) && (matrixACol < dimensions.firstColumnCount))) { - var indexA = getIndex_4(globalRow, matrixACol, dimensions.firstColumnCount); + if (((globalRow < (*dimensions).firstRowCount) && (matrixACol < (*dimensions).firstColumnCount))) { + var indexA = getIndex_4(globalRow, matrixACol, (*dimensions).firstColumnCount); valueA = firstMatrix_5[indexA]; } tileA_6[tileIdx] = valueA; var matrixBRow = ((tileIndex * 16) + localRow); var valueB = 0; - if (((matrixBRow < dimensions.firstColumnCount) && (globalCol < dimensions.secondColumnCount))) { - var indexB = getIndex_4(matrixBRow, globalCol, dimensions.secondColumnCount); + if (((matrixBRow < (*dimensions).firstColumnCount) && (globalCol < (*dimensions).secondColumnCount))) { + var indexB = getIndex_4(matrixBRow, globalCol, (*dimensions).secondColumnCount); valueB = secondMatrix_7[indexB]; } tileB_8[tileIdx] = valueB; workgroupBarrier(); - var effectiveTileSize = min(16, (dimensions.firstColumnCount - (tileIndex * 16))); + var effectiveTileSize = min(16, ((*dimensions).firstColumnCount - (tileIndex * 16))); for (var k = 0u; (k < effectiveTileSize); k++) { var tileA_element = tileA_6[getTileIndex_3(localRow, k)]; var tileB_element = tileB_8[getTileIndex_3(k, localCol)]; @@ -83,8 +83,8 @@ describe('matrix(next) example', () => { } workgroupBarrier(); } - if (((globalRow < dimensions.firstRowCount) && (globalCol < dimensions.secondColumnCount))) { - var outputIndex = getIndex_4(globalRow, globalCol, dimensions.secondColumnCount); + if (((globalRow < (*dimensions).firstRowCount) && (globalCol < (*dimensions).secondColumnCount))) { + var outputIndex = getIndex_4(globalRow, globalCol, (*dimensions).secondColumnCount); resultMatrix_9[outputIndex] = accumulatedResult; } }" diff --git a/packages/typegpu/tests/examples/individual/perlin-noise.test.ts b/packages/typegpu/tests/examples/individual/perlin-noise.test.ts index f57dd9cce4..315272f9e2 100644 --- a/packages/typegpu/tests/examples/individual/perlin-noise.test.ts +++ b/packages/typegpu/tests/examples/individual/perlin-noise.test.ts @@ -58,8 +58,8 @@ describe('perlin noise example', () => { } @compute @workgroup_size(1, 1, 1) fn mainCompute_0(input: mainCompute_Input_9) { - var size = size_1; - var idx = ((input.gid.x + (input.gid.y * size.x)) + ((input.gid.z * size.x) * size.y)); + let size = &size_1; + var idx = ((input.gid.x + (input.gid.y * (*size).x)) + ((input.gid.z * (*size).x) * (*size).y)); memory_2[idx] = computeJunctionGradient_3(vec3i(input.gid.xyz)); } diff --git a/packages/typegpu/tests/examples/individual/ray-marching.test.ts b/packages/typegpu/tests/examples/individual/ray-marching.test.ts index 334961c93a..f4319de22c 100644 --- a/packages/typegpu/tests/examples/individual/ray-marching.test.ts +++ b/packages/typegpu/tests/examples/individual/ray-marching.test.ts @@ -183,10 +183,10 @@ describe('ray-marching example', () => { var lightPos = getOrbitingLightPos_19(time_12); var l = normalize((lightPos - p)); var diff = max(dot(n, l), 0); - var shadowRo = p; - var shadowRd = l; + let shadowRo = &p; + let shadowRd = &l; var shadowDist = length((lightPos - p)); - var shadow = softShadow_20(&shadowRo, &shadowRd, 0.1, shadowDist, 16); + var shadow = softShadow_20(shadowRo, shadowRd, 0.1, shadowDist, 16); var litColor = (march.color * diff); var finalColor = mix((litColor * 0.5), litColor, shadow); return mix(vec4f(finalColor, 1), vec4f(0.699999988079071, 0.800000011920929, 0.8999999761581421, 1), fog); diff --git a/packages/typegpu/tests/examples/individual/simple-shadow.test.ts b/packages/typegpu/tests/examples/individual/simple-shadow.test.ts index 1b382bf08b..6e2cc933fd 100644 --- a/packages/typegpu/tests/examples/individual/simple-shadow.test.ts +++ b/packages/typegpu/tests/examples/individual/simple-shadow.test.ts @@ -87,11 +87,11 @@ describe('simple shadow example', () => { } @vertex fn mainVert_0(_arg_0: mainVert_Input_7) -> mainVert_Output_6 { - var modelMatrixUniform = instanceInfo_1.modelMatrix; - var worldPos = (modelMatrixUniform * _arg_0.position); + let modelMatrixUniform = &instanceInfo_1.modelMatrix; + var worldPos = (*modelMatrixUniform * _arg_0.position); var viewPos = (cameraUniform_4.view * worldPos); var clipPos = (cameraUniform_4.projection * viewPos); - var transformedNormal = (modelMatrixUniform * _arg_0.normal); + var transformedNormal = (*modelMatrixUniform * _arg_0.normal); return mainVert_Output_6(clipPos, transformedNormal, worldPos.xyz); } @@ -125,7 +125,7 @@ describe('simple shadow example', () => { } @fragment fn mainFrag_8(_arg_0: mainFrag_Input_17) -> @location(0) vec4f { - var instanceInfo = instanceInfo_1; + let instanceInfo = &instanceInfo_1; var N = normalize(_arg_0.normal.xyz); var L = normalize(-(light_9.direction)); var V = normalize((cameraUniform_4.position - _arg_0.worldPos)); @@ -140,11 +140,11 @@ describe('simple shadow example', () => { if (!inBounds) { shadowFactor = 1; } - var ambient = (instanceInfo.material.ambient * light_9.color); + var ambient = ((*instanceInfo).material.ambient * light_9.color); var diff = max(0, dot(N, L)); - var diffuse = ((instanceInfo.material.diffuse * light_9.color) * diff); - var spec = pow(max(0, dot(V, R)), instanceInfo.material.shininess); - var specular = ((instanceInfo.material.specular * light_9.color) * spec); + var diffuse = (((*instanceInfo).material.diffuse * light_9.color) * diff); + var spec = pow(max(0, dot(V, R)), (*instanceInfo).material.shininess); + var specular = (((*instanceInfo).material.specular * light_9.color) * spec); var lit = ((diffuse + specular) * shadowFactor); var finalColor = (ambient + lit); if ((paramsUniform_15.shadowOnly == 1)) { diff --git a/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts b/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts index 9ca5b822b6..14530aeb8a 100644 --- a/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts +++ b/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts @@ -84,7 +84,7 @@ describe('wgsl resolution example', () => { @compute @workgroup_size(1) fn compute_shader_8(input: compute_shader_Input_14) { var index = input.gid.x; - var instanceInfo = currentTrianglePos_9[index]; + let instanceInfo = ¤tTrianglePos_9[index]; var separation = vec2f(); var alignment = vec2f(); var cohesion = vec2f(); @@ -94,17 +94,17 @@ describe('wgsl resolution example', () => { if ((i == index)) { continue; } - var other = currentTrianglePos_9[i]; - var dist = distance(instanceInfo.position, other.position); + let other = ¤tTrianglePos_9[i]; + var dist = distance((*instanceInfo).position, (*other).position); if ((dist < paramsBuffer_11.separationDistance)) { - separation = (separation + (instanceInfo.position - other.position)); + separation = (separation + ((*instanceInfo).position - (*other).position)); } if ((dist < paramsBuffer_11.alignmentDistance)) { - alignment = (alignment + other.velocity); + alignment = (alignment + (*other).velocity); alignmentCount++; } if ((dist < paramsBuffer_11.cohesionDistance)) { - cohesion = (cohesion + other.position); + cohesion = (cohesion + (*other).position); cohesionCount++; } } @@ -113,27 +113,27 @@ describe('wgsl resolution example', () => { } if ((cohesionCount > 0)) { cohesion = ((1f / f32(cohesionCount)) * cohesion); - cohesion = (cohesion - instanceInfo.position); + cohesion = (cohesion - (*instanceInfo).position); } var velocity = (paramsBuffer_11.separationStrength * separation); velocity = (velocity + (paramsBuffer_11.alignmentStrength * alignment)); velocity = (velocity + (paramsBuffer_11.cohesionStrength * cohesion)); - instanceInfo.velocity = (instanceInfo.velocity + velocity); - instanceInfo.velocity = (clamp(length(instanceInfo.velocity), 0, 0.01) * normalize(instanceInfo.velocity)); - if ((instanceInfo.position.x > 1.03)) { - instanceInfo.position.x = (-1 - 0.03); + (*instanceInfo).velocity = ((*instanceInfo).velocity + velocity); + (*instanceInfo).velocity = (clamp(length((*instanceInfo).velocity), 0, 0.01) * normalize((*instanceInfo).velocity)); + if (((*instanceInfo).position.x > 1.03)) { + (*instanceInfo).position.x = (-1 - 0.03); } - if ((instanceInfo.position.y > 1.03)) { - instanceInfo.position.y = (-1 - 0.03); + if (((*instanceInfo).position.y > 1.03)) { + (*instanceInfo).position.y = (-1 - 0.03); } - if ((instanceInfo.position.x < (-1 - 0.03))) { - instanceInfo.position.x = 1.03; + if (((*instanceInfo).position.x < (-1 - 0.03))) { + (*instanceInfo).position.x = 1.03; } - if ((instanceInfo.position.y < (-1 - 0.03))) { - instanceInfo.position.y = 1.03; + if (((*instanceInfo).position.y < (-1 - 0.03))) { + (*instanceInfo).position.y = 1.03; } - instanceInfo.position = (instanceInfo.position + instanceInfo.velocity); - nextTrianglePos_13[index] = instanceInfo; + (*instanceInfo).position = ((*instanceInfo).position + (*instanceInfo).velocity); + nextTrianglePos_13[index] = *instanceInfo; }" `); }); diff --git a/packages/typegpu/tests/indent.test.ts b/packages/typegpu/tests/indent.test.ts index bf9760d3db..66e2053416 100644 --- a/packages/typegpu/tests/indent.test.ts +++ b/packages/typegpu/tests/indent.test.ts @@ -116,8 +116,8 @@ describe('indents', () => { fn main_0() { for (var i = 0; (i < 100); i++) { - var particle = systemData_1.particles[i]; - systemData_1.particles[i] = updateParicle_4(particle, systemData_1.gravity, systemData_1.deltaTime); + let particle = &systemData_1.particles[i]; + systemData_1.particles[i] = updateParicle_4(*particle, systemData_1.gravity, systemData_1.deltaTime); } }" `); @@ -243,8 +243,8 @@ describe('indents', () => { fn main_0() { incrementCounter_1(); for (var i = 0; (i < 100); i++) { - var particle = systemData_3.particles[i]; - systemData_3.particles[i] = updateParticle_7(particle, systemData_3.gravity, systemData_3.deltaTime); + let particle = &systemData_3.particles[i]; + systemData_3.particles[i] = updateParticle_7(*particle, systemData_3.gravity, systemData_3.deltaTime); } }" `); @@ -404,15 +404,15 @@ describe('indents', () => { } @vertex fn someVertex_0(input: someVertex_Input_6) -> someVertex_Output_5 { - var uniBoid = boids_1; + let uniBoid = &boids_1; for (var i = 0; (i < -1); i++) { var someVal = textureSample(smoothRender_3, sampler_4, vec2f()); if ((someVal.x > 0.5)) { - var newPos = (uniBoid.position + vec4f(1, 2, 3, 4)); + var newPos = ((*uniBoid).position + vec4f(1, 2, 3, 4)); } else { while (true) { - var newPos = (uniBoid.position + vec4f(1, 2, 3, 4)); + var newPos = ((*uniBoid).position + vec4f(1, 2, 3, 4)); if ((newPos.x > 0)) { var evenNewer = (newPos + input.position); } diff --git a/packages/typegpu/tests/slot.test.ts b/packages/typegpu/tests/slot.test.ts index 867db5213a..137a5b4533 100644 --- a/packages/typegpu/tests/slot.test.ts +++ b/packages/typegpu/tests/slot.test.ts @@ -345,9 +345,9 @@ describe('tgpu.slot', () => { fn func() { var pos = vec3f(1, 2, 3); var posX = 1; - var vel = boid.vel; + let vel = &boid.vel; var velX = boid.vel.x; - var vel_ = boid.vel; + let vel_ = &boid.vel; var velX_ = boid.vel.x; var color = getColor(); }" diff --git a/packages/typegpu/tests/std/matrix/rotate.test.ts b/packages/typegpu/tests/std/matrix/rotate.test.ts index 4a9feea398..f4aad2c4dd 100644 --- a/packages/typegpu/tests/std/matrix/rotate.test.ts +++ b/packages/typegpu/tests/std/matrix/rotate.test.ts @@ -3,7 +3,7 @@ import { mat4x4f, vec4f } from '../../../src/data/index.ts'; import tgpu from '../../../src/index.ts'; import { isCloseTo, mul } from '../../../src/std/index.ts'; import { rotateX4, rotateY4, rotateZ4 } from '../../../src/std/matrix.ts'; -import { parse, parseResolved } from '../../utils/parseResolved.ts'; +import { asWgsl, parse, parseResolved } from '../../utils/parseResolved.ts'; describe('rotate', () => { it('generates correct WGSL for rotateX4 with custom matrix', () => { @@ -14,17 +14,12 @@ describe('rotate', () => { const resultExpression = rotateX4(M, angle); }); - expect(parseResolved({ rotateFn })).toBe( - parse( - `fn rotateFn() { - var angle = 4; - var resultExpression = ( - mat4x4f(1, 0, 0, 0, 0, cos(angle), sin(angle), 0, 0, -sin(angle), cos(angle), 0, 0, 0, 0, 1) * - mat4x4f(1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1) - ); - }`, - ), - ); + expect(asWgsl(rotateFn)).toMatchInlineSnapshot(` + "fn rotateFn() { + var angle = 4; + var resultExpression = (mat4x4f(1, 0, 0, 0, 0, cos(angle), sin(angle), 0, 0, -sin(angle), cos(angle), 0, 0, 0, 0, 1) * mat4x4f(1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1)); + }" + `); }); it('generates correct WGSL for rotateY4 with custom matrix', () => { diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 0d0c21cad9..f5d65bd97b 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -851,14 +851,14 @@ describe('wgslGenerator', () => { while (i < 10) i += 1; }); - expect(parseResolved({ main })).toBe(parse(` - fn main() { + expect(asWgsl(main)).toMatchInlineSnapshot(` + "fn main() { var i = 0; - while((i < 10)){ + while ((i < 10)) { i += 1; } - } - `)); + }" + `); }); it('throws error when incorrectly initializing function', () => { @@ -1102,8 +1102,8 @@ describe('wgslGenerator', () => { expect(asWgsl(testFn)).toMatchInlineSnapshot(` "fn testFn() { var matrix = mat4x4f(); - var column = matrix[1]; - var element = column[0]; + let column = &matrix[1]; + var element = (*column)[0]; var directElement = matrix[1][0]; }" `); @@ -1123,7 +1123,7 @@ describe('wgslGenerator', () => { var matrix: mat4x4f; fn testFn() { - var element = matrix[index]; + let element = &matrix[index]; }" `); }); diff --git a/packages/typegpu/tests/variable.test.ts b/packages/typegpu/tests/variable.test.ts index 27f7427ee9..da0491809b 100644 --- a/packages/typegpu/tests/variable.test.ts +++ b/packages/typegpu/tests/variable.test.ts @@ -6,7 +6,7 @@ import type { import * as d from '../src/data/index.ts'; import * as std from '../src/std/index.ts'; import tgpu from '../src/index.ts'; -import { parse, parseResolved } from './utils/parseResolved.ts'; +import { asWgsl, parse, parseResolved } from './utils/parseResolved.ts'; describe('tgpu.privateVar|tgpu.workgroupVar', () => { it('should inject variable declaration when used in functions', () => { @@ -117,26 +117,20 @@ describe('tgpu.privateVar|tgpu.workgroupVar', () => { const velX = boid.value.vel.x; }); - const resolved = tgpu.resolve({ - externals: { func }, - names: 'strict', - }); - - expect(parse(resolved)).toBe( - parse(` - struct Boid { - pos: vec3f, - vel: vec3u, - } + expect(asWgsl(func)).toMatchInlineSnapshot(` + "struct Boid { + pos: vec3f, + vel: vec3u, + } - var boid: Boid = Boid(vec3f(1, 2, 3), vec3u(4, 5, 6)); + var boid: Boid = Boid(vec3f(1, 2, 3), vec3u(4, 5, 6)); - fn func() { - var pos = boid; - var vel = boid.vel; - var velX = boid.vel.x; - }`), - ); + fn func() { + let pos = &boid; + let vel = &boid.vel; + var velX = boid.vel.x; + }" + `); }); it('supports atomic operations on workgroupVar atomics accessed via .$', () => { From ba2a4ffef92e4f5a45f930f5f98278371cd80cf6 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 18:52:15 +0200 Subject: [PATCH 07/59] Fix ptr return types, and invalid ref and deref op order --- .../rendering/cubemap-reflection/icosphere.ts | 2 +- .../rendering/disco/shaders/fragment.ts | 2 +- .../examples/simulation/boids-next/index.ts | 2 +- packages/typegpu/src/std/numeric.ts | 12 ++-- packages/typegpu/src/tgsl/conversion.ts | 4 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 64 +++++++++++-------- packages/typegpu/tests/accessor.test.ts | 2 +- packages/typegpu/tests/array.test.ts | 2 +- packages/typegpu/tests/bufferUsage.test.ts | 6 +- packages/typegpu/tests/derived.test.ts | 4 +- .../tests/examples/individual/3d-fish.test.ts | 14 ++-- .../examples/individual/boids-next.test.ts | 34 +++++----- .../examples/individual/caustics.test.ts | 6 +- .../individual/cubemap-reflection.test.ts | 24 +++---- .../individual/fluid-double-buffering.test.ts | 34 +++++----- .../tests/examples/individual/gravity.test.ts | 10 +-- .../examples/individual/matrix-next.test.ts | 2 +- .../examples/individual/perlin-noise.test.ts | 2 +- .../examples/individual/ray-marching.test.ts | 32 +++++----- .../examples/individual/simple-shadow.test.ts | 8 +-- .../individual/tgsl-parsing-test.test.ts | 16 ++--- .../individual/wgsl-resolution.test.ts | 6 +- packages/typegpu/tests/indent.test.ts | 10 +-- packages/typegpu/tests/slot.test.ts | 4 +- .../typegpu/tests/tgsl/conversion.test.ts | 2 +- packages/typegpu/tests/tgsl/shellless.test.ts | 2 +- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 6 +- packages/typegpu/tests/tgslFn.test.ts | 2 +- packages/typegpu/tests/variable.test.ts | 4 +- 29 files changed, 167 insertions(+), 151 deletions(-) diff --git a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts index e1a29a1186..97d4112c90 100644 --- a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts +++ b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts @@ -188,7 +188,7 @@ export class IcosphereGenerator { const reprojectedVertex = newVertices[i]; const triBase = i - (i % 3); - let normal = reprojectedVertex; + let normal = d.vec4f(reprojectedVertex); if (smoothFlag.$ === 0) { normal = getAverageNormal( newVertices[triBase], diff --git a/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts b/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts index 5631c45c65..646a4c9b39 100644 --- a/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts +++ b/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts @@ -129,7 +129,7 @@ export const mainFragment4 = tgpu['~unstable'].fragmentFn({ std.abs(std.fract(aspectUv.x * 1.2) - 0.5), std.abs(std.fract(aspectUv.y * 1.2) - 0.5), ).mul(2).sub(1); - aspectUv = mirroredUv; + aspectUv = d.vec2f(mirroredUv); const originalUv = aspectUv; let accumulatedColor = d.vec3f(0, 0, 0); const time = timeAccess.$; diff --git a/apps/typegpu-docs/src/examples/simulation/boids-next/index.ts b/apps/typegpu-docs/src/examples/simulation/boids-next/index.ts index 433bd134a2..53f2e825ae 100644 --- a/apps/typegpu-docs/src/examples/simulation/boids-next/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/boids-next/index.ts @@ -182,7 +182,7 @@ const mainCompute = tgpu['~unstable'].computeFn({ workgroupSize: [1], })((input) => { const index = input.gid.x; - const instanceInfo = currentTrianglePos.value[index]; + const instanceInfo = TriangleData(currentTrianglePos.value[index]); let separation = d.vec2f(); let alignment = d.vec2f(); let cohesion = d.vec2f(); diff --git a/packages/typegpu/src/std/numeric.ts b/packages/typegpu/src/std/numeric.ts index 7264910aad..de7b522adc 100644 --- a/packages/typegpu/src/std/numeric.ts +++ b/packages/typegpu/src/std/numeric.ts @@ -1,5 +1,6 @@ import { createDualImpl, dualImpl } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; +import { toStorable } from '../data/dataTypes.ts'; import { smoothstepScalar } from '../data/numberOps.ts'; import { abstractFloat, @@ -718,10 +719,13 @@ function cpuLength(value: T): number { export const length = dualImpl({ name: 'length', - signature: (arg) => ({ - argTypes: [arg], - returnType: isHalfPrecisionSchema(arg) ? f16 : f32, - }), + signature: (arg) => { + const sarg = toStorable(arg); + return ({ + argTypes: [sarg], + returnType: isHalfPrecisionSchema(sarg) ? f16 : f32, + }); + }, normalImpl: cpuLength, codegenImpl: (arg) => stitch`length(${arg})`, }); diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index bd03cd52d6..75fdbbfed5 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -239,10 +239,10 @@ function applyActionToSnippet( switch (action.action) { case 'ref': - return snip(stitch`&${snippet}`, targetType, /* ref */ snippet.ref); + return snip(stitch`(&${snippet})`, targetType, /* ref */ snippet.ref); case 'deref': // Dereferencing a pointer does not return a copy of the value, it's still a reference. - return snip(stitch`*${snippet}`, targetType, /* ref */ snippet.ref); + return snip(stitch`(*${snippet})`, targetType, /* ref */ snippet.ref); case 'cast': { // Casting means calling the schema with the snippet as an argument. return (targetType as unknown as (val: Snippet) => Snippet)(snippet); diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 67926f2900..870a42375c 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -26,6 +26,7 @@ import { tryConvertSnippet, } from './conversion.ts'; import { + accessProp, coerceToSnippet, concretize, type GenerationCtx, @@ -297,7 +298,7 @@ ${this.ctx.pre}}`; if (expression[0] === NODE.memberAccess) { // Member Access const [_, targetNode, property] = expression; - const target = this.expression(targetNode); + let target = this.expression(targetNode); if (target.value === console) { return snip(new ConsoleLog(), UnknownData, /* ref */ undefined); @@ -313,16 +314,13 @@ ${this.ctx.pre}}`; return coerceToSnippet(propValue); } - let targetDataType = target.dataType; - let isPtr = false; - if (wgsl.isPtr(target.dataType)) { - isPtr = true; - targetDataType = target.dataType.inner as AnyData; + // De-referencing the pointer + target = tryConvertSnippet(target, target.dataType.inner, false); } if ( - infixKinds.includes(targetDataType.type) && + infixKinds.includes(target.dataType.type) && property in infixOperators ) { return snip( @@ -336,26 +334,24 @@ ${this.ctx.pre}}`; ); } - if (wgsl.isWgslArray(targetDataType) && property === 'length') { - if (targetDataType.elementCount === 0) { + if (wgsl.isWgslArray(target.dataType) && property === 'length') { + if (target.dataType.elementCount === 0) { // Dynamically-sized array return snip( - isPtr - ? `arrayLength(${this.ctx.resolve(target.value).value})` - : `arrayLength(&${this.ctx.resolve(target.value).value})`, + `arrayLength(&${this.ctx.resolve(target.value).value})`, u32, /* ref */ undefined, ); } return snip( - String(targetDataType.elementCount), + String(target.dataType.elementCount), abstractInt, /* ref */ undefined, ); } - if (wgsl.isMat(targetDataType) && property === 'columns') { + if (wgsl.isMat(target.dataType) && property === 'columns') { return snip( new MatrixColumnsAccess(target), UnknownData, @@ -364,21 +360,22 @@ ${this.ctx.pre}}`; } if ( - wgsl.isVec(targetDataType) && wgsl.isVecInstance(target.value) + wgsl.isVec(target.dataType) && wgsl.isVecInstance(target.value) ) { // We're operating on a vector that's known at resolution time // biome-ignore lint/suspicious/noExplicitAny: it's probably a swizzle return coerceToSnippet((target.value as any)[property]); } - const propType = getTypeForPropAccess(targetDataType, property); - return snip( - isPtr - ? `(*${this.ctx.resolve(target.value).value}).${property}` - : `${this.ctx.resolve(target.value).value}.${property}`, - propType, - /* ref */ wgsl.isNaturallyRef(propType) ? target.ref : undefined, - ); + const accessed = accessProp(target, property); + if (!accessed) { + throw new Error( + `Property '${property}' not found on type ${ + this.ctx.resolve(target.dataType) + }`, + ); + } + return accessed; } if (expression[0] === NODE.indexAccess) { @@ -747,13 +744,19 @@ ${this.ctx.pre}}`; if (returnNode !== undefined) { const expectedReturnType = this.ctx.topFunctionReturnType; - const returnSnippet = expectedReturnType + let returnSnippet = expectedReturnType ? this.typedExpression( returnNode, expectedReturnType, ) : this.expression(returnNode); + returnSnippet = tryConvertSnippet( + returnSnippet, + toStorable(returnSnippet.dataType as wgsl.StorableData), + false, + ); + invariant( returnSnippet.dataType.type !== 'unknown', 'Return type should be known', @@ -814,10 +817,19 @@ ${this.ctx.pre}else ${alternate}`; let dataType = eq.dataType as wgsl.AnyWgslData; // Assigning a reference to a `const` variable means we store the pointer // of the rhs. - if (eq.ref !== undefined && !wgsl.isPtr(dataType)) { + if (eq.ref !== undefined) { if (stmtType === NODE.let) { + const rhsStr = this.ctx.resolve(eq.value).value; + const rhsTypeStr = + this.ctx.resolve(toStorable(eq.dataType as wgsl.StorableData)) + .value; + throw new WgslTypeError( - `Cannot assign a reference to a let variable ('${rawId}'). Use const instead, or copy the right-hand side.`, + `'let ${rawId} = ${rhsStr}' is invalid, because references cannot be assigned to 'let' variable declarations. +----- +- Try 'let ${rawId} = ${rhsTypeStr}(${rhsStr})' if you need to reassign '${rawId}' later +- Try 'const ${rawId} = ${rhsTypeStr}(${rhsStr})' if you won't reassign '${rawId}' later. +-----`, ); } diff --git a/packages/typegpu/tests/accessor.test.ts b/packages/typegpu/tests/accessor.test.ts index b52d9a7712..9ee5614a14 100644 --- a/packages/typegpu/tests/accessor.test.ts +++ b/packages/typegpu/tests/accessor.test.ts @@ -161,7 +161,7 @@ describe('tgpu.accessor', () => { fn main() { var color = vec3f(1, 0, 0); - let color2 = &redUniform; + let color2 = (&redUniform); var color3 = getColor(); var colorX = 1; var color2X = redUniform.x; diff --git a/packages/typegpu/tests/array.test.ts b/packages/typegpu/tests/array.test.ts index abd0a90ebd..bcf27b4e09 100644 --- a/packages/typegpu/tests/array.test.ts +++ b/packages/typegpu/tests/array.test.ts @@ -431,7 +431,7 @@ describe('array.length', () => { "@group(0) @binding(0) var values: array; fn testFn() -> u32 { - return arrayLength(&values); + return arrayLength((&values)); }" `); }); diff --git a/packages/typegpu/tests/bufferUsage.test.ts b/packages/typegpu/tests/bufferUsage.test.ts index 10e8d2a9c3..b7f1ae8e99 100644 --- a/packages/typegpu/tests/bufferUsage.test.ts +++ b/packages/typegpu/tests/bufferUsage.test.ts @@ -89,7 +89,7 @@ describe('TgpuBufferUniform', () => { @group(0) @binding(0) var boid: Boid; fn func() { - let pos = &boid.pos; + let pos = (&boid.pos); var velX = boid.vel.x; }" `); @@ -180,7 +180,7 @@ describe('TgpuBufferMutable', () => { @group(0) @binding(0) var boid: Boid; fn func() { - let pos = &boid.pos; + let pos = (&boid.pos); var velX = boid.vel.x; }" `); @@ -284,7 +284,7 @@ describe('TgpuBufferReadonly', () => { @group(0) @binding(0) var boid: Boid; fn func() { - let pos = &boid.pos; + let pos = (&boid.pos); var velX = boid.vel.x; }" `); diff --git a/packages/typegpu/tests/derived.test.ts b/packages/typegpu/tests/derived.test.ts index 61743e050a..871b22ffc1 100644 --- a/packages/typegpu/tests/derived.test.ts +++ b/packages/typegpu/tests/derived.test.ts @@ -147,9 +147,9 @@ describe('TgpuDerived', () => { fn func() { var pos = vec3f(2, 4, 6); var posX = 2; - let vel = &boid.vel; + let vel = (&boid.vel); var velX = boid.vel.x; - let vel_ = &boid.vel; + let vel_ = (&boid.vel); var velX_ = boid.vel.x; }" `); diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index 2edce12bbc..1dad65eeb6 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -119,7 +119,7 @@ describe('3d fish example', () => { @group(0) @binding(2) var mouseRay_5: MouseRay_6; fn projectPointOnLine_8(point: ptr, line: ptr) -> vec3f { - var pointVector = (*point - (*line).origin); + var pointVector = ((*point) - (*line).origin); var projection = dot(pointVector, (*line).dir); return ((*line).origin + ((*line).dir * projection)); } @@ -182,7 +182,7 @@ describe('3d fish example', () => { } } if ((mouseRay_5.activated == 1)) { - var proj = projectPointOnLine_8(&fishData.position, &mouseRay_5.line); + var proj = projectPointOnLine_8((&fishData.position), (&mouseRay_5.line)); var diff = (fishData.position - proj); var limit = 0.9; var str = (pow(2, clamp((limit - length(diff)), 0, limit)) - 1); @@ -274,8 +274,8 @@ describe('3d fish example', () => { var translationMatrix = mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, currentModelData.position.x, currentModelData.position.y, currentModelData.position.z, 1); var worldPosition = (translationMatrix * (yawMatrix * (pitchMatrix * (scaleMatrix * vec4f(wavedVertex.position, 1))))); var worldNormal = normalize((yawMatrix * (pitchMatrix * vec4f(wavedVertex.normal, 1))).xyz); - let worldPositionUniform = &worldPosition; - var canvasPosition = (camera_6.projection * (camera_6.view * *worldPositionUniform)); + let worldPositionUniform = (&worldPosition); + var canvasPosition = (camera_6.projection * (camera_6.view * (*worldPositionUniform))); return vertexShader_Output_8(worldPosition.xyz, worldNormal, canvasPosition, currentModelData.variant, input.textureUV, currentModelData.applySeaFog, currentModelData.applySeaDesaturation); } @@ -400,10 +400,10 @@ describe('3d fish example', () => { @fragment fn fragmentShader_10(input: fragmentShader_Input_16) -> @location(0) vec4f { var textureColorWithAlpha = sampleTexture_11(input.textureUV); - let textureColor = &textureColorWithAlpha.xyz; - var ambient = (0.5 * (*textureColor * vec3f(0.800000011920929, 0.800000011920929, 1))); + var textureColor = textureColorWithAlpha.xyz; + var ambient = (0.5 * (textureColor * vec3f(0.800000011920929, 0.800000011920929, 1))); var cosTheta = dot(input.worldNormal, vec3f(-0.2357022613286972, 0.9428090453147888, -0.2357022613286972)); - var diffuse = (max(0, cosTheta) * (*textureColor * vec3f(0.800000011920929, 0.800000011920929, 1))); + var diffuse = (max(0, cosTheta) * (textureColor * vec3f(0.800000011920929, 0.800000011920929, 1))); var viewSource = normalize((camera_6.position.xyz - input.worldPosition)); var reflectSource = normalize(reflect((-1 * vec3f(-0.2357022613286972, 0.9428090453147888, -0.2357022613286972)), input.worldNormal)); var specularStrength = pow(max(0, dot(viewSource, reflectSource)), 16); diff --git a/packages/typegpu/tests/examples/individual/boids-next.test.ts b/packages/typegpu/tests/examples/individual/boids-next.test.ts index 4793182d01..28b7502469 100644 --- a/packages/typegpu/tests/examples/individual/boids-next.test.ts +++ b/packages/typegpu/tests/examples/individual/boids-next.test.ts @@ -43,7 +43,7 @@ describe('boids next example', () => { @compute @workgroup_size(1) fn mainCompute_0(input: mainCompute_Input_6) { var index = input.gid.x; - let instanceInfo = ¤tTrianglePos_1[index]; + var instanceInfo = currentTrianglePos_1[index]; var separation = vec2f(); var alignment = vec2f(); var cohesion = vec2f(); @@ -53,10 +53,10 @@ describe('boids next example', () => { if ((i == index)) { continue; } - let other = ¤tTrianglePos_1[i]; - var dist = distance((*instanceInfo).position, (*other).position); + let other = (¤tTrianglePos_1[i]); + var dist = distance(instanceInfo.position, (*other).position); if ((dist < paramsBuffer_3.separationDistance)) { - separation = (separation + ((*instanceInfo).position - (*other).position)); + separation = (separation + (instanceInfo.position - (*other).position)); } if ((dist < paramsBuffer_3.alignmentDistance)) { alignment = (alignment + (*other).velocity); @@ -72,27 +72,27 @@ describe('boids next example', () => { } if ((cohesionCount > 0)) { cohesion = ((1f / f32(cohesionCount)) * cohesion); - cohesion = (cohesion - (*instanceInfo).position); + cohesion = (cohesion - instanceInfo.position); } var velocity = (paramsBuffer_3.separationStrength * separation); velocity = (velocity + (paramsBuffer_3.alignmentStrength * alignment)); velocity = (velocity + (paramsBuffer_3.cohesionStrength * cohesion)); - (*instanceInfo).velocity = ((*instanceInfo).velocity + velocity); - (*instanceInfo).velocity = (clamp(length((*instanceInfo).velocity), 0, 0.01) * normalize((*instanceInfo).velocity)); - if (((*instanceInfo).position.x > 1.03)) { - (*instanceInfo).position.x = (-1 - 0.03); + instanceInfo.velocity = (instanceInfo.velocity + velocity); + instanceInfo.velocity = (clamp(length(instanceInfo.velocity), 0, 0.01) * normalize(instanceInfo.velocity)); + if ((instanceInfo.position.x > 1.03)) { + instanceInfo.position.x = (-1 - 0.03); } - if (((*instanceInfo).position.y > 1.03)) { - (*instanceInfo).position.y = (-1 - 0.03); + if ((instanceInfo.position.y > 1.03)) { + instanceInfo.position.y = (-1 - 0.03); } - if (((*instanceInfo).position.x < (-1 - 0.03))) { - (*instanceInfo).position.x = 1.03; + if ((instanceInfo.position.x < (-1 - 0.03))) { + instanceInfo.position.x = 1.03; } - if (((*instanceInfo).position.y < (-1 - 0.03))) { - (*instanceInfo).position.y = 1.03; + if ((instanceInfo.position.y < (-1 - 0.03))) { + instanceInfo.position.y = 1.03; } - (*instanceInfo).position = ((*instanceInfo).position + (*instanceInfo).velocity); - nextTrianglePos_5[index] = *instanceInfo; + instanceInfo.position = (instanceInfo.position + instanceInfo.velocity); + nextTrianglePos_5[index] = instanceInfo; } fn getRotationFromVelocity_1(velocity: vec2f) -> f32 { diff --git a/packages/typegpu/tests/examples/individual/caustics.test.ts b/packages/typegpu/tests/examples/individual/caustics.test.ts index c7979f2cd5..c0ae88dece 100644 --- a/packages/typegpu/tests/examples/individual/caustics.test.ts +++ b/packages/typegpu/tests/examples/individual/caustics.test.ts @@ -107,8 +107,8 @@ describe('caustics example', () => { } fn caustics_7(uv: ptr, time2: f32, profile: vec3f) -> vec3f { - var distortion = sample_8(vec3f((*uv * 0.5), (time2 * 0.2))); - var uv2 = (*uv + distortion); + var distortion = sample_8(vec3f(((*uv) * 0.5), (time2 * 0.2))); + var uv2 = ((*uv) + distortion); var noise = abs(sample_8(vec3f((uv2 * 5), time2))); return pow(vec3f((1 - noise)), profile); } @@ -134,7 +134,7 @@ describe('caustics example', () => { var tile = tilePattern_5((skewedUv * tileDensity_4)); var albedo = mix(vec3f(0.10000000149011612), vec3f(1), tile); var cuv = vec2f(((_arg_0.uv.x * (pow((_arg_0.uv.y * 1.5), 3) + 0.1)) * 5), (pow((((_arg_0.uv.y * 1.5) + 0.1) * 1.5), 3) * 1)); - var c1 = (caustics_7(&cuv, (time_6 * 0.2), vec3f(4, 4, 1)) * vec3f(0.4000000059604645, 0.6499999761581421, 1)); + var c1 = (caustics_7((&cuv), (time_6 * 0.2), vec3f(4, 4, 1)) * vec3f(0.4000000059604645, 0.6499999761581421, 1)); var c2 = (caustics_17((cuv * 2), (time_6 * 0.4), vec3f(16, 1, 4)) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)); var blendCoord = vec3f((_arg_0.uv * vec2f(5, 10)), ((time_6 * 0.2) + 5)); var blend = saturate((sample_8(blendCoord) + 0.3)); diff --git a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts index dfa505a587..6cf1d7ef89 100644 --- a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts +++ b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts @@ -66,26 +66,26 @@ describe('cubemap reflection example', () => { return; } var baseIndexPrev = (triangleIndex * 3); - var v1 = unpackVec2u_3(&prevVertices_1[baseIndexPrev].position); - var v2 = unpackVec2u_3(&prevVertices_1[(baseIndexPrev + 1)].position); - var v3 = unpackVec2u_3(&prevVertices_1[(baseIndexPrev + 2)].position); - var v12 = vec4f(normalize(calculateMidpoint_4(&v1, &v2).xyz), 1); - var v23 = vec4f(normalize(calculateMidpoint_4(&v2, &v3).xyz), 1); - var v31 = vec4f(normalize(calculateMidpoint_4(&v3, &v1).xyz), 1); + var v1 = unpackVec2u_3((&prevVertices_1[baseIndexPrev].position)); + var v2 = unpackVec2u_3((&prevVertices_1[(baseIndexPrev + 1)].position)); + var v3 = unpackVec2u_3((&prevVertices_1[(baseIndexPrev + 2)].position)); + var v12 = vec4f(normalize(calculateMidpoint_4((&v1), (&v2)).xyz), 1); + var v23 = vec4f(normalize(calculateMidpoint_4((&v2), (&v3)).xyz), 1); + var v31 = vec4f(normalize(calculateMidpoint_4((&v3), (&v1)).xyz), 1); var newVertices = array(v1, v12, v31, v2, v23, v12, v3, v31, v23, v12, v23, v31); var baseIndexNext = (triangleIndex * 12); for (var i = 0u; (i < 12); i++) { - let reprojectedVertex = &newVertices[i]; + let reprojectedVertex = (&newVertices[i]); var triBase = (i - (i % 3)); - var normal = reprojectedVertex; + var normal = (*reprojectedVertex); if ((smoothFlag_5 == 0)) { - *normal = getAverageNormal_6(&newVertices[triBase], &newVertices[(triBase + 1)], &newVertices[(triBase + 2)]); + normal = getAverageNormal_6((&newVertices[triBase]), (&newVertices[(triBase + 1)]), (&newVertices[(triBase + 2)])); } var outIndex = (baseIndexNext + i); - let nextVertex = &nextVertices_7[outIndex]; + let nextVertex = (&nextVertices_7[outIndex]); (*nextVertex).position = packVec2u_8(reprojectedVertex); - (*nextVertex).normal = packVec2u_8(normal); - nextVertices_7[outIndex] = *nextVertex; + (*nextVertex).normal = packVec2u_8((&normal)); + nextVertices_7[outIndex] = (*nextVertex); } } diff --git a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts index ba64ede91e..b92aeec0d8 100644 --- a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts @@ -35,7 +35,7 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_4(x: i32, y: i32) -> bool { for (var obsIdx = 0; (obsIdx < 4); obsIdx++) { - let obs = &obstacles_5[obsIdx]; + let obs = (&obstacles_5[obsIdx]); if (((*obs).enabled == 0)) { continue; } @@ -119,7 +119,7 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_11(x: i32, y: i32) -> bool { for (var obsIdx = 0; (obsIdx < 4); obsIdx++) { - let obs = &obstacles_12[obsIdx]; + let obs = (&obstacles_12[obsIdx]); if (((*obs).enabled == 0)) { continue; } @@ -156,7 +156,7 @@ describe('fluid double buffering example', () => { return item_15(); } - fn computeVelocity_8(x: i32, y: i32) -> ptr { + fn computeVelocity_8(x: i32, y: i32) -> vec2f { var gravityCost = 0.5; var neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); var cell = getCell_6(x, y); @@ -164,7 +164,7 @@ describe('fluid double buffering example', () => { var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; for (var i = 0; (i < 4); i++) { - let offset = &neighborOffsets[i]; + let offset = (&neighborOffsets[i]); var neighborDensity = getCell_6((x + (*offset).x), (y + (*offset).y)); var cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); if (!isValidFlowOut_9((x + (*offset).x), (y + (*offset).y))) { @@ -182,8 +182,8 @@ describe('fluid double buffering example', () => { } } } - let leastCostDir = &dirChoices[u32((randFloat01_14() * f32(dirChoiceCount)))]; - return leastCostDir; + let leastCostDir = (&dirChoices[u32((randFloat01_14() * f32(dirChoiceCount)))]); + return (*leastCostDir); } fn flowFromCell_16(myX: i32, myY: i32, x: i32, y: i32) -> f32 { @@ -238,8 +238,8 @@ describe('fluid double buffering example', () => { randSeed2_3(vec2f(f32(index), time_2)); var next = getCell_6(x, y); var nextVelocity = computeVelocity_8(x, y); - next.x = (*nextVelocity).x; - next.y = (*nextVelocity).y; + next.x = nextVelocity.x; + next.y = nextVelocity.y; next.z = flowFromCell_16(x, y, x, y); next.z += flowFromCell_16(x, y, x, (y + 1)); next.z += flowFromCell_16(x, y, x, (y - 1)); @@ -286,7 +286,7 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_11(x: i32, y: i32) -> bool { for (var obsIdx = 0; (obsIdx < 4); obsIdx++) { - let obs = &obstacles_12[obsIdx]; + let obs = (&obstacles_12[obsIdx]); if (((*obs).enabled == 0)) { continue; } @@ -323,7 +323,7 @@ describe('fluid double buffering example', () => { return item_15(); } - fn computeVelocity_8(x: i32, y: i32) -> ptr { + fn computeVelocity_8(x: i32, y: i32) -> vec2f { var gravityCost = 0.5; var neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); var cell = getCell_6(x, y); @@ -331,7 +331,7 @@ describe('fluid double buffering example', () => { var dirChoices = array(vec2f(), vec2f(), vec2f(), vec2f()); var dirChoiceCount = 1; for (var i = 0; (i < 4); i++) { - let offset = &neighborOffsets[i]; + let offset = (&neighborOffsets[i]); var neighborDensity = getCell_6((x + (*offset).x), (y + (*offset).y)); var cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); if (!isValidFlowOut_9((x + (*offset).x), (y + (*offset).y))) { @@ -349,8 +349,8 @@ describe('fluid double buffering example', () => { } } } - let leastCostDir = &dirChoices[u32((randFloat01_14() * f32(dirChoiceCount)))]; - return leastCostDir; + let leastCostDir = (&dirChoices[u32((randFloat01_14() * f32(dirChoiceCount)))]); + return (*leastCostDir); } fn flowFromCell_16(myX: i32, myY: i32, x: i32, y: i32) -> f32 { @@ -405,8 +405,8 @@ describe('fluid double buffering example', () => { randSeed2_3(vec2f(f32(index), time_2)); var next = getCell_6(x, y); var nextVelocity = computeVelocity_8(x, y); - next.x = (*nextVelocity).x; - next.y = (*nextVelocity).y; + next.x = nextVelocity.x; + next.y = nextVelocity.y; next.z = flowFromCell_16(x, y, x, y); next.z += flowFromCell_16(x, y, x, (y + 1)); next.z += flowFromCell_16(x, y, x, (y - 1)); @@ -448,7 +448,7 @@ describe('fluid double buffering example', () => { fn isInsideObstacle_6(x: i32, y: i32) -> bool { for (var obsIdx = 0; (obsIdx < 4); obsIdx++) { - let obs = &obstacles_7[obsIdx]; + let obs = (&obstacles_7[obsIdx]); if (((*obs).enabled == 0)) { continue; } @@ -471,7 +471,7 @@ describe('fluid double buffering example', () => { var x = i32((input.uv.x * 256)); var y = i32((input.uv.y * 256)); var index = coordsToIndex_4(x, y); - let cell = &gridAlphaBuffer_5[index]; + let cell = (&gridAlphaBuffer_5[index]); var density = max(0, (*cell).z); var obstacleColor = vec4f(0.10000000149011612, 0.10000000149011612, 0.10000000149011612, 1); var background = vec4f(0.8999999761581421, 0.8999999761581421, 0.8999999761581421, 1); diff --git a/packages/typegpu/tests/examples/individual/gravity.test.ts b/packages/typegpu/tests/examples/individual/gravity.test.ts index 7b8d920f03..f644ad859f 100644 --- a/packages/typegpu/tests/examples/individual/gravity.test.ts +++ b/packages/typegpu/tests/examples/individual/gravity.test.ts @@ -65,7 +65,7 @@ describe('gravity example', () => { @compute @workgroup_size(1) fn computeCollisionsShader_0(input: computeCollisionsShader_Input_7) { var currentId = input.gid.x; var current = CelestialBody_2(inState_1[currentId].destroyed, inState_1[currentId].position, inState_1[currentId].velocity, inState_1[currentId].mass, inState_1[currentId].radiusMultiplier, inState_1[currentId].collisionBehavior, inState_1[currentId].textureIndex, inState_1[currentId].ambientLightFactor); - let updatedCurrent = ¤t; + let updatedCurrent = (¤t); if ((current.destroyed == 0)) { for (var i = 0; (i < celestialBodiesCount_3); i++) { var otherId = u32(i); @@ -93,7 +93,7 @@ describe('gravity example', () => { } } } - outState_6[input.gid.x] = *updatedCurrent; + outState_6[input.gid.x] = (*updatedCurrent); } struct CelestialBody_2 { @@ -131,7 +131,7 @@ describe('gravity example', () => { @compute @workgroup_size(1) fn computeGravityShader_0(input: computeGravityShader_Input_8) { var current = CelestialBody_2(inState_1[input.gid.x].destroyed, inState_1[input.gid.x].position, inState_1[input.gid.x].velocity, inState_1[input.gid.x].mass, inState_1[input.gid.x].radiusMultiplier, inState_1[input.gid.x].collisionBehavior, inState_1[input.gid.x].textureIndex, inState_1[input.gid.x].ambientLightFactor); var dt = (time_3.passed * time_3.multiplier); - let updatedCurrent = ¤t; + let updatedCurrent = (¤t); if ((current.destroyed == 0)) { for (var i = 0; (i < celestialBodiesCount_5); i++) { var other = CelestialBody_2(inState_1[i].destroyed, inState_1[i].position, inState_1[i].velocity, inState_1[i].mass, inState_1[i].radiusMultiplier, inState_1[i].collisionBehavior, inState_1[i].textureIndex, inState_1[i].ambientLightFactor); @@ -145,7 +145,7 @@ describe('gravity example', () => { } (*updatedCurrent).position = ((*updatedCurrent).position + (dt * (*updatedCurrent).velocity)); } - outState_7[input.gid.x] = *updatedCurrent; + outState_7[input.gid.x] = (*updatedCurrent); } struct Camera_2 { @@ -228,7 +228,7 @@ describe('gravity example', () => { @vertex fn mainVertex_0(input: mainVertex_Input_7) -> mainVertex_Output_6 { var currentBody = CelestialBody_2(celestialBodies_1[input.instanceIndex].destroyed, celestialBodies_1[input.instanceIndex].position, celestialBodies_1[input.instanceIndex].velocity, celestialBodies_1[input.instanceIndex].mass, celestialBodies_1[input.instanceIndex].radiusMultiplier, celestialBodies_1[input.instanceIndex].collisionBehavior, celestialBodies_1[input.instanceIndex].textureIndex, celestialBodies_1[input.instanceIndex].ambientLightFactor); var worldPosition = ((radiusOf_3(currentBody) * input.position.xyz) + currentBody.position); - let camera = &camera_4; + let camera = (&camera_4); var positionOnCanvas = ((*camera).projection * ((*camera).view * vec4f(worldPosition, 1))); return mainVertex_Output_6(positionOnCanvas, input.uv, input.normal, worldPosition, currentBody.textureIndex, currentBody.destroyed, currentBody.ambientLightFactor); } diff --git a/packages/typegpu/tests/examples/individual/matrix-next.test.ts b/packages/typegpu/tests/examples/individual/matrix-next.test.ts index e3850021f4..cdc334efef 100644 --- a/packages/typegpu/tests/examples/individual/matrix-next.test.ts +++ b/packages/typegpu/tests/examples/individual/matrix-next.test.ts @@ -51,7 +51,7 @@ describe('matrix(next) example', () => { } @compute @workgroup_size(16, 16) fn computeSharedMemory_0(input: computeSharedMemory_Input_10) { - let dimensions = &dimensions_1; + let dimensions = (&dimensions_1); var numTiles = u32((f32((((*dimensions).firstColumnCount + 16) - 1)) / 16f)); var globalRow = ((input.wid.x * 16) + input.lid.x); var globalCol = ((input.wid.y * 16) + input.lid.y); diff --git a/packages/typegpu/tests/examples/individual/perlin-noise.test.ts b/packages/typegpu/tests/examples/individual/perlin-noise.test.ts index 315272f9e2..bd9c961ca9 100644 --- a/packages/typegpu/tests/examples/individual/perlin-noise.test.ts +++ b/packages/typegpu/tests/examples/individual/perlin-noise.test.ts @@ -58,7 +58,7 @@ describe('perlin noise example', () => { } @compute @workgroup_size(1, 1, 1) fn mainCompute_0(input: mainCompute_Input_9) { - let size = &size_1; + let size = (&size_1); var idx = ((input.gid.x + (input.gid.y * (*size).x)) + ((input.gid.z * (*size).x) * (*size).y)); memory_2[idx] = computeJunctionGradient_3(vec3i(input.gid.xyz)); } diff --git a/packages/typegpu/tests/examples/individual/ray-marching.test.ts b/packages/typegpu/tests/examples/individual/ray-marching.test.ts index f4319de22c..28158ef0b5 100644 --- a/packages/typegpu/tests/examples/individual/ray-marching.test.ts +++ b/packages/typegpu/tests/examples/individual/ray-marching.test.ts @@ -63,7 +63,7 @@ describe('ray-marching example', () => { fn getMorphingShape_8(p: ptr, t: f32) -> Shape_6 { var center = vec3f(0, 2, 6); - var localP = (*p - center); + var localP = ((*p) - center); var rotMatZ = mat4x4f(cos(-t), sin(-t), 0, 0, -sin(-t), cos(-t), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); var rotMatX = mat4x4f(1, 0, 0, 0, 0, cos((-t * 0.6)), sin((-t * 0.6)), 0, 0, -sin((-t * 0.6)), cos((-t * 0.6)), 0, 0, 0, 0, 1); var rotatedP = (rotMatZ * (rotMatX * vec4f(localP, 1))).xyz; @@ -73,8 +73,8 @@ describe('ray-marching example', () => { var sphere1 = Shape_6(vec3f(0.4000000059604645, 0.5, 1), sdSphere_9((localP - sphere1Offset), 0.5)); var sphere2 = Shape_6(vec3f(1, 0.800000011920929, 0.20000000298023224), sdSphere_9((localP - sphere2Offset), 0.3)); var box = Shape_6(vec3f(1, 0.30000001192092896, 0.30000001192092896), sdBoxFrame3d_10(rotatedP, boxSize, 0.1)); - var spheres = smoothShapeUnion_11(&sphere1, &sphere2, 0.1); - return smoothShapeUnion_11(&spheres, &box, 0.2); + var spheres = smoothShapeUnion_11((&sphere1), (&sphere2), 0.1); + return smoothShapeUnion_11((&spheres), (&box), 0.2); } @group(0) @binding(1) var time_12: f32; @@ -94,16 +94,16 @@ describe('ray-marching example', () => { fn getSceneDist_7(p: ptr) -> Shape_6 { var shape = getMorphingShape_8(p, time_12); - var floor = Shape_6(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard_13(((*p).xz * 2))), sdPlane_14(*p, vec3f(0, 1, 0), 0)); - return shapeUnion_15(&shape, &floor); + var floor = Shape_6(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard_13(((*p).xz * 2))), sdPlane_14((*p), vec3f(0, 1, 0), 0)); + return shapeUnion_15((&shape), (&floor)); } fn rayMarch_5(ro: ptr, rd: ptr) -> Shape_6 { var dO = 0f; var result = Shape_6(vec3f(), 30); for (var i = 0; (i < 1000); i++) { - var p = (*ro + (*rd * dO)); - var scene = getSceneDist_7(&p); + var p = ((*ro) + ((*rd) * dO)); + var scene = getSceneDist_7((&p)); dO += scene.dist; if (((dO > 30) || (scene.dist < 1e-3))) { result.dist = dO; @@ -126,20 +126,20 @@ describe('ray-marching example', () => { var sphere1 = Shape_6(vec3f(0.4000000059604645, 0.5, 1), sdSphere_9((localP - sphere1Offset), 0.5)); var sphere2 = Shape_6(vec3f(1, 0.800000011920929, 0.20000000298023224), sdSphere_9((localP - sphere2Offset), 0.3)); var box = Shape_6(vec3f(1, 0.30000001192092896, 0.30000001192092896), sdBoxFrame3d_10(rotatedP, boxSize, 0.1)); - var spheres = smoothShapeUnion_11(&sphere1, &sphere2, 0.1); - return smoothShapeUnion_11(&spheres, &box, 0.2); + var spheres = smoothShapeUnion_11((&sphere1), (&sphere2), 0.1); + return smoothShapeUnion_11((&spheres), (&box), 0.2); } fn getSceneDist_17(p: vec3f) -> Shape_6 { var shape = getMorphingShape_18(p, time_12); var floor = Shape_6(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard_13((p.xz * 2))), sdPlane_14(p, vec3f(0, 1, 0), 0)); - return shapeUnion_15(&shape, &floor); + return shapeUnion_15((&shape), (&floor)); } fn getNormal_16(p: ptr) -> vec3f { var dist = getSceneDist_7(p).dist; var e = 0.01; - var n = vec3f((getSceneDist_17((*p + vec3f(e, 0, 0))).dist - dist), (getSceneDist_17((*p + vec3f(0, e, 0))).dist - dist), (getSceneDist_17((*p + vec3f(0, 0, e))).dist - dist)); + var n = vec3f((getSceneDist_17(((*p) + vec3f(e, 0, 0))).dist - dist), (getSceneDist_17(((*p) + vec3f(0, e, 0))).dist - dist), (getSceneDist_17(((*p) + vec3f(0, 0, e))).dist - dist)); return normalize(n); } @@ -157,7 +157,7 @@ describe('ray-marching example', () => { if ((t >= maxT)) { break; } - var h = getSceneDist_17((*ro + (*rd * t))).dist; + var h = getSceneDist_17(((*ro) + ((*rd) * t))).dist; if ((h < 1e-3)) { return 0; } @@ -176,15 +176,15 @@ describe('ray-marching example', () => { uv.x *= (resolution_4.x / resolution_4.y); var ro = vec3f(0, 2, 3); var rd = normalize(vec3f(uv.x, uv.y, 1)); - var march = rayMarch_5(&ro, &rd); + var march = rayMarch_5((&ro), (&rd)); var fog = pow(min((march.dist / 30f), 1), 0.7); var p = (ro + (rd * march.dist)); - var n = getNormal_16(&p); + var n = getNormal_16((&p)); var lightPos = getOrbitingLightPos_19(time_12); var l = normalize((lightPos - p)); var diff = max(dot(n, l), 0); - let shadowRo = &p; - let shadowRd = &l; + let shadowRo = (&p); + let shadowRd = (&l); var shadowDist = length((lightPos - p)); var shadow = softShadow_20(shadowRo, shadowRd, 0.1, shadowDist, 16); var litColor = (march.color * diff); diff --git a/packages/typegpu/tests/examples/individual/simple-shadow.test.ts b/packages/typegpu/tests/examples/individual/simple-shadow.test.ts index 6e2cc933fd..4306d166bc 100644 --- a/packages/typegpu/tests/examples/individual/simple-shadow.test.ts +++ b/packages/typegpu/tests/examples/individual/simple-shadow.test.ts @@ -87,11 +87,11 @@ describe('simple shadow example', () => { } @vertex fn mainVert_0(_arg_0: mainVert_Input_7) -> mainVert_Output_6 { - let modelMatrixUniform = &instanceInfo_1.modelMatrix; - var worldPos = (*modelMatrixUniform * _arg_0.position); + let modelMatrixUniform = (&instanceInfo_1.modelMatrix); + var worldPos = ((*modelMatrixUniform) * _arg_0.position); var viewPos = (cameraUniform_4.view * worldPos); var clipPos = (cameraUniform_4.projection * viewPos); - var transformedNormal = (*modelMatrixUniform * _arg_0.normal); + var transformedNormal = ((*modelMatrixUniform) * _arg_0.normal); return mainVert_Output_6(clipPos, transformedNormal, worldPos.xyz); } @@ -125,7 +125,7 @@ describe('simple shadow example', () => { } @fragment fn mainFrag_8(_arg_0: mainFrag_Input_17) -> @location(0) vec4f { - let instanceInfo = &instanceInfo_1; + let instanceInfo = (&instanceInfo_1); var N = normalize(_arg_0.normal.xyz); var L = normalize(-(light_9.direction)); var V = normalize((cameraUniform_4.position - _arg_0.worldPos)); diff --git a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts index a40e377576..4094bc8e79 100644 --- a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts +++ b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts @@ -163,7 +163,7 @@ describe('tgsl parsing test example', () => { } fn modifyNumFn_12(ptr: ptr) { - *ptr += 1; + (*ptr) += 1; } fn modifyVecFn_13(ptr: ptr) { @@ -181,7 +181,7 @@ describe('tgsl parsing test example', () => { var privateNum_16: u32; fn modifyNumPrivate_17(ptr: ptr) { - *ptr += 1; + (*ptr) += 1; } var privateVec_18: vec2f; @@ -199,19 +199,19 @@ describe('tgsl parsing test example', () => { fn pointersTest_11() -> bool { var s = true; var num = 0u; - modifyNumFn_12(&num); + modifyNumFn_12((&num)); s = (s && (num == 1)); var vec = vec2f(); - modifyVecFn_13(&vec); + modifyVecFn_13((&vec)); s = (s && all(vec == vec2f(1, 0))); var myStruct = SimpleStruct_14(); - modifyStructFn_15(&myStruct); + modifyStructFn_15((&myStruct)); s = (s && all(myStruct.vec == vec2f(1, 0))); - modifyNumPrivate_17(&privateNum_16); + modifyNumPrivate_17((&privateNum_16)); s = (s && (privateNum_16 == 1)); - modifyVecPrivate_19(&privateVec_18); + modifyVecPrivate_19((&privateVec_18)); s = (s && all(privateVec_18 == vec2f(1, 0))); - modifyStructPrivate_21(&privateStruct_20); + modifyStructPrivate_21((&privateStruct_20)); s = (s && all(privateStruct_20.vec == vec2f(1, 0))); return s; } diff --git a/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts b/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts index 14530aeb8a..50c30f420b 100644 --- a/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts +++ b/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts @@ -84,7 +84,7 @@ describe('wgsl resolution example', () => { @compute @workgroup_size(1) fn compute_shader_8(input: compute_shader_Input_14) { var index = input.gid.x; - let instanceInfo = ¤tTrianglePos_9[index]; + let instanceInfo = (¤tTrianglePos_9[index]); var separation = vec2f(); var alignment = vec2f(); var cohesion = vec2f(); @@ -94,7 +94,7 @@ describe('wgsl resolution example', () => { if ((i == index)) { continue; } - let other = ¤tTrianglePos_9[i]; + let other = (¤tTrianglePos_9[i]); var dist = distance((*instanceInfo).position, (*other).position); if ((dist < paramsBuffer_11.separationDistance)) { separation = (separation + ((*instanceInfo).position - (*other).position)); @@ -133,7 +133,7 @@ describe('wgsl resolution example', () => { (*instanceInfo).position.y = 1.03; } (*instanceInfo).position = ((*instanceInfo).position + (*instanceInfo).velocity); - nextTrianglePos_13[index] = *instanceInfo; + nextTrianglePos_13[index] = (*instanceInfo); }" `); }); diff --git a/packages/typegpu/tests/indent.test.ts b/packages/typegpu/tests/indent.test.ts index 66e2053416..35b2d54f77 100644 --- a/packages/typegpu/tests/indent.test.ts +++ b/packages/typegpu/tests/indent.test.ts @@ -116,8 +116,8 @@ describe('indents', () => { fn main_0() { for (var i = 0; (i < 100); i++) { - let particle = &systemData_1.particles[i]; - systemData_1.particles[i] = updateParicle_4(*particle, systemData_1.gravity, systemData_1.deltaTime); + let particle = (&systemData_1.particles[i]); + systemData_1.particles[i] = updateParicle_4((*particle), systemData_1.gravity, systemData_1.deltaTime); } }" `); @@ -243,8 +243,8 @@ describe('indents', () => { fn main_0() { incrementCounter_1(); for (var i = 0; (i < 100); i++) { - let particle = &systemData_3.particles[i]; - systemData_3.particles[i] = updateParticle_7(*particle, systemData_3.gravity, systemData_3.deltaTime); + let particle = (&systemData_3.particles[i]); + systemData_3.particles[i] = updateParticle_7((*particle), systemData_3.gravity, systemData_3.deltaTime); } }" `); @@ -404,7 +404,7 @@ describe('indents', () => { } @vertex fn someVertex_0(input: someVertex_Input_6) -> someVertex_Output_5 { - let uniBoid = &boids_1; + let uniBoid = (&boids_1); for (var i = 0; (i < -1); i++) { var someVal = textureSample(smoothRender_3, sampler_4, vec2f()); if ((someVal.x > 0.5)) { diff --git a/packages/typegpu/tests/slot.test.ts b/packages/typegpu/tests/slot.test.ts index 137a5b4533..bc59131f8b 100644 --- a/packages/typegpu/tests/slot.test.ts +++ b/packages/typegpu/tests/slot.test.ts @@ -345,9 +345,9 @@ describe('tgpu.slot', () => { fn func() { var pos = vec3f(1, 2, 3); var posX = 1; - let vel = &boid.vel; + let vel = (&boid.vel); var velX = boid.vel.x; - let vel_ = &boid.vel; + let vel_ = (&boid.vel); var velX_ = boid.vel.x; var color = getColor(); }" diff --git a/packages/typegpu/tests/tgsl/conversion.test.ts b/packages/typegpu/tests/tgsl/conversion.test.ts index 0bcc7e7166..5f3217254f 100644 --- a/packages/typegpu/tests/tgsl/conversion.test.ts +++ b/packages/typegpu/tests/tgsl/conversion.test.ts @@ -239,7 +239,7 @@ describe('convertToCommonType', () => { expect(result).toBeDefined(); expect(result?.length).toBe(2); expect(result?.[0]?.dataType).toBe(d.f32); - expect(result?.[0]?.value).toBe('*ptr_f32'); // Deref applied + expect(result?.[0]?.value).toBe('(*ptr_f32)'); // Deref applied expect(result?.[1]?.dataType).toBe(d.f32); expect(result?.[1]?.value).toBe('2.22'); }); diff --git a/packages/typegpu/tests/tgsl/shellless.test.ts b/packages/typegpu/tests/tgsl/shellless.test.ts index b961cbb2ac..a2e924ae50 100644 --- a/packages/typegpu/tests/tgsl/shellless.test.ts +++ b/packages/typegpu/tests/tgsl/shellless.test.ts @@ -188,7 +188,7 @@ describe('shellless', () => { fn main() { var pos = vec3f(); - advance(&pos, vec3f(1, 2, 3)); + advance((&pos), vec3f(1, 2, 3)); }" `); }); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index f5d65bd97b..3aeef0830c 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -919,7 +919,7 @@ describe('wgslGenerator', () => { expect(parseResolved({ increment })).toBe( parse(` fn increment(val: ptr) { - *val += 1; + (*val) += 1; }`), ); }); @@ -1102,7 +1102,7 @@ describe('wgslGenerator', () => { expect(asWgsl(testFn)).toMatchInlineSnapshot(` "fn testFn() { var matrix = mat4x4f(); - let column = &matrix[1]; + let column = (&matrix[1]); var element = (*column)[0]; var directElement = matrix[1][0]; }" @@ -1123,7 +1123,7 @@ describe('wgslGenerator', () => { var matrix: mat4x4f; fn testFn() { - let element = &matrix[index]; + let element = (&matrix[index]); }" `); }); diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index ee1a77e926..e58b649d21 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -672,7 +672,7 @@ describe('TGSL tgpu.fn function', () => { fn callAddOnes() { var someVec = vec3f(1, 2, 3); - addOnes(&someVec); + addOnes((&someVec)); }" `); }); diff --git a/packages/typegpu/tests/variable.test.ts b/packages/typegpu/tests/variable.test.ts index da0491809b..c3ed993516 100644 --- a/packages/typegpu/tests/variable.test.ts +++ b/packages/typegpu/tests/variable.test.ts @@ -126,8 +126,8 @@ describe('tgpu.privateVar|tgpu.workgroupVar', () => { var boid: Boid = Boid(vec3f(1, 2, 3), vec3u(4, 5, 6)); fn func() { - let pos = &boid; - let vel = &boid.vel; + let pos = (&boid); + let vel = (&boid.vel); var velX = boid.vel.x; }" `); From 7b7e5dc7ede94884e652654f68202af5306cec65 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 19:04:22 +0200 Subject: [PATCH 08/59] Const statements --- .../typegpu/src/core/function/dualImpl.ts | 8 ++--- packages/typegpu/src/tgsl/wgslGenerator.ts | 16 ++++++++- packages/typegpu/src/types.ts | 5 +++ packages/typegpu/tests/accessor.test.ts | 4 +-- packages/typegpu/tests/derived.test.ts | 2 +- .../tests/examples/individual/3d-fish.test.ts | 8 ++--- .../individual/box-raytracing.test.ts | 2 +- .../individual/fluid-double-buffering.test.ts | 14 ++++---- .../tests/examples/individual/oklab.test.ts | 6 ++-- .../examples/individual/ray-marching.test.ts | 8 ++--- .../examples/individual/stable-fluid.test.ts | 4 +-- .../individual/tgsl-parsing-test.test.ts | 2 +- packages/typegpu/tests/numeric.test.ts | 10 +++--- packages/typegpu/tests/slot.test.ts | 2 +- .../typegpu/tests/std/matrix/rotate.test.ts | 36 +++++++------------ .../typegpu/tests/tgsl/wgslGenerator.test.ts | 22 ++++++------ packages/typegpu/tests/tgslFn.test.ts | 8 ++--- 17 files changed, 81 insertions(+), 76 deletions(-) diff --git a/packages/typegpu/src/core/function/dualImpl.ts b/packages/typegpu/src/core/function/dualImpl.ts index a6b3265aac..dfb048d014 100644 --- a/packages/typegpu/src/core/function/dualImpl.ts +++ b/packages/typegpu/src/core/function/dualImpl.ts @@ -5,16 +5,12 @@ import { type Snippet, } from '../../data/snippet.ts'; import { inCodegenMode } from '../../execMode.ts'; -import { type FnArgsConversionHint, getOwnSnippet } from '../../types.ts'; +import { type FnArgsConversionHint, isKnownAtComptime } from '../../types.ts'; import { setName } from '../../shared/meta.ts'; import { $internal } from '../../shared/symbols.ts'; import { tryConvertSnippet } from '../../tgsl/conversion.ts'; import type { AnyData } from '../../data/dataTypes.ts'; -function isKnownAtComptime(value: unknown): boolean { - return typeof value !== 'string' && getOwnSnippet(value) === undefined; -} - export function createDualImpl unknown>( jsImpl: T, gpuImpl: (...args: MapValueToSnippet>) => Snippet, @@ -81,7 +77,7 @@ export function dualImpl unknown>( }) as MapValueToSnippet>; if ( - !options.noComptime && converted.every((s) => isKnownAtComptime(s.value)) + !options.noComptime && converted.every((s) => isKnownAtComptime(s)) ) { return snip( options.normalImpl(...converted.map((s) => s.value) as never[]), diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 870a42375c..5271160cd9 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -19,7 +19,11 @@ import { getName } from '../shared/meta.ts'; import { $internal } from '../shared/symbols.ts'; import { pow } from '../std/numeric.ts'; import { add, div, mul, sub } from '../std/operators.ts'; -import { type FnArgsConversionHint, isMarkedInternal } from '../types.ts'; +import { + type FnArgsConversionHint, + isKnownAtComptime, + isMarkedInternal, +} from '../types.ts'; import { convertStructValues, convertToCommonType, @@ -818,6 +822,7 @@ ${this.ctx.pre}else ${alternate}`; // Assigning a reference to a `const` variable means we store the pointer // of the rhs. if (eq.ref !== undefined) { + // Referential if (stmtType === NODE.let) { const rhsStr = this.ctx.resolve(eq.value).value; const rhsTypeStr = @@ -837,6 +842,15 @@ ${this.ctx.pre}else ${alternate}`; if (!wgsl.isPtr(dataType)) { dataType = ptrFn(concretize(dataType) as wgsl.StorableData); } + } else { + // Non-referential + if ( + stmtType === NODE.const && + !wgsl.isNaturallyRef(dataType) && + isKnownAtComptime(eq) + ) { + varType = 'const'; + } } const snippet = this.blockVariable( diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 61d243f6d6..f8ba33e64b 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -320,6 +320,11 @@ export function getOwnSnippet(value: unknown): Snippet | undefined { return (value as WithOwnSnippet)?.[$ownSnippet]; } +export function isKnownAtComptime(snippet: Snippet): boolean { + return typeof snippet.value !== 'string' && + getOwnSnippet(snippet.value) === undefined; +} + export function isWgsl(value: unknown): value is Wgsl { return ( typeof value === 'number' || diff --git a/packages/typegpu/tests/accessor.test.ts b/packages/typegpu/tests/accessor.test.ts index 9ee5614a14..933684e654 100644 --- a/packages/typegpu/tests/accessor.test.ts +++ b/packages/typegpu/tests/accessor.test.ts @@ -163,7 +163,7 @@ describe('tgpu.accessor', () => { var color = vec3f(1, 0, 0); let color2 = (&redUniform); var color3 = getColor(); - var colorX = 1; + const colorX = 1; var color2X = redUniform.x; var color3X = getColor().x; }" @@ -180,7 +180,7 @@ describe('tgpu.accessor', () => { expect(asWgsl(main)).toMatchInlineSnapshot(` "fn main() { - var foo = 1f; + const foo = 1f; }" `); }); diff --git a/packages/typegpu/tests/derived.test.ts b/packages/typegpu/tests/derived.test.ts index 871b22ffc1..06b1603936 100644 --- a/packages/typegpu/tests/derived.test.ts +++ b/packages/typegpu/tests/derived.test.ts @@ -146,7 +146,7 @@ describe('TgpuDerived', () => { fn func() { var pos = vec3f(2, 4, 6); - var posX = 2; + const posX = 2; let vel = (&boid.vel); var velX = boid.vel.x; let vel_ = (&boid.vel); diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index 1dad65eeb6..ecf8ec78dc 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -171,7 +171,7 @@ describe('3d fish example', () => { repulsion[i] = 1; var axisAquariumSize = (vec3f(10, 4, 10)[i] / 2f); var axisPosition = fishData.position[i]; - var distance = 0.1; + const distance = 0.1; if ((axisPosition > (axisAquariumSize - distance))) { var str = (axisPosition - (axisAquariumSize - distance)); wallRepulsion = (wallRepulsion - (str * repulsion)); @@ -184,7 +184,7 @@ describe('3d fish example', () => { if ((mouseRay_5.activated == 1)) { var proj = projectPointOnLine_8((&fishData.position), (&mouseRay_5.line)); var diff = (fishData.position - proj); - var limit = 0.9; + const limit = 0.9; var str = (pow(2, clamp((limit - length(diff)), 0, limit)) - 1); rayRepulsion = (str * normalize(diff)); } @@ -218,8 +218,8 @@ describe('3d fish example', () => { fn applySinWave_4(index: u32, vertex: PosAndNormal_3, time: f32) -> PosAndNormal_3 { var a = -60.1; - var b = 0.8; - var c = 6.1; + const b = 0.8; + const c = 6.1; var posMod = vec3f(); posMod.z = (sin((f32(index) + (((time / a) + vertex.position.x) / b))) / c); var coeff = (cos((f32(index) + (((time / a) + vertex.position.x) / b))) / c); diff --git a/packages/typegpu/tests/examples/individual/box-raytracing.test.ts b/packages/typegpu/tests/examples/individual/box-raytracing.test.ts index 6e16b57558..d1b1c17580 100644 --- a/packages/typegpu/tests/examples/individual/box-raytracing.test.ts +++ b/packages/typegpu/tests/examples/individual/box-raytracing.test.ts @@ -168,7 +168,7 @@ describe('box raytracing example', () => { } var linear = (vec3f(1) / invColor); var srgb = linearToSrgb_12(linear); - var gamma = 2.2; + const gamma = 2.2; var corrected = pow(srgb, vec3f((1f / gamma))); if (intersectionFound) { return (min(density, 1) * vec4f(min(corrected, vec3f(1)), 1)); diff --git a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts index b92aeec0d8..ca3a859fb1 100644 --- a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts @@ -157,7 +157,7 @@ describe('fluid double buffering example', () => { } fn computeVelocity_8(x: i32, y: i32) -> vec2f { - var gravityCost = 0.5; + const gravityCost = 0.5; var neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); var cell = getCell_6(x, y); var leastCost = cell.z; @@ -216,7 +216,7 @@ describe('fluid double buffering example', () => { @group(0) @binding(3) var sourceParams_18: item_19; fn getMinimumInFlow_17(x: i32, y: i32) -> f32 { - var gridSizeF = 256f; + const gridSizeF = 256f; var sourceRadius2 = max(1, (sourceParams_18.radius * gridSizeF)); var sourcePos = vec2f((sourceParams_18.center.x * gridSizeF), (sourceParams_18.center.y * gridSizeF)); if ((distance(vec2f(f32(x), f32(y)), sourcePos) < sourceRadius2)) { @@ -324,7 +324,7 @@ describe('fluid double buffering example', () => { } fn computeVelocity_8(x: i32, y: i32) -> vec2f { - var gravityCost = 0.5; + const gravityCost = 0.5; var neighborOffsets = array(vec2i(0, 1), vec2i(0, -1), vec2i(1, 0), vec2i(-1, 0)); var cell = getCell_6(x, y); var leastCost = cell.z; @@ -383,7 +383,7 @@ describe('fluid double buffering example', () => { @group(0) @binding(3) var sourceParams_18: item_19; fn getMinimumInFlow_17(x: i32, y: i32) -> f32 { - var gridSizeF = 256f; + const gridSizeF = 256f; var sourceRadius2 = max(1, (sourceParams_18.radius * gridSizeF)); var sourcePos = vec2f((sourceParams_18.center.x * gridSizeF), (sourceParams_18.center.y * gridSizeF)); if ((distance(vec2f(f32(x), f32(y)), sourcePos) < sourceRadius2)) { @@ -478,9 +478,9 @@ describe('fluid double buffering example', () => { var firstColor = vec4f(0.20000000298023224, 0.6000000238418579, 1, 1); var secondColor = vec4f(0.20000000298023224, 0.30000001192092896, 0.6000000238418579, 1); var thirdColor = vec4f(0.10000000149011612, 0.20000000298023224, 0.4000000059604645, 1); - var firstThreshold = 2f; - var secondThreshold = 10f; - var thirdThreshold = 20f; + const firstThreshold = 2f; + const secondThreshold = 10f; + const thirdThreshold = 20f; if (isInsideObstacle_6(x, y)) { return obstacleColor; } diff --git a/packages/typegpu/tests/examples/individual/oklab.test.ts b/packages/typegpu/tests/examples/individual/oklab.test.ts index 96ec1cb138..7dbc0ba073 100644 --- a/packages/typegpu/tests/examples/individual/oklab.test.ts +++ b/packages/typegpu/tests/examples/individual/oklab.test.ts @@ -136,7 +136,7 @@ describe('oklab example', () => { } fn findGamutIntersection_13(a: f32, b: f32, L1: f32, C1: f32, L0: f32, cusp: LC_12) -> f32 { - var FLT_MAX = 3.4028234663852886e+38f; + const FLT_MAX = 3.4028234663852886e+38f; var t = 0f; if (((((L1 - L0) * cusp.C) - ((cusp.L - L0) * C1)) <= 0)) { t = ((cusp.C * L0) / ((C1 * cusp.L) + (cusp.C * (L0 - L1)))); @@ -193,9 +193,9 @@ describe('oklab example', () => { } fn gamutClipAdaptiveL05_8(lab: vec3f) -> vec3f { - var alpha = 0.20000000298023224f; + const alpha = 0.20000000298023224f; var L = lab.x; - var eps = 1e-5; + const eps = 1e-5; var C = max(eps, length(lab.yz)); var a_ = (lab.y / C); var b_ = (lab.z / C); diff --git a/packages/typegpu/tests/examples/individual/ray-marching.test.ts b/packages/typegpu/tests/examples/individual/ray-marching.test.ts index 28158ef0b5..74e92b40e4 100644 --- a/packages/typegpu/tests/examples/individual/ray-marching.test.ts +++ b/packages/typegpu/tests/examples/individual/ray-marching.test.ts @@ -138,15 +138,15 @@ describe('ray-marching example', () => { fn getNormal_16(p: ptr) -> vec3f { var dist = getSceneDist_7(p).dist; - var e = 0.01; + const e = 0.01; var n = vec3f((getSceneDist_17(((*p) + vec3f(e, 0, 0))).dist - dist), (getSceneDist_17(((*p) + vec3f(0, e, 0))).dist - dist), (getSceneDist_17(((*p) + vec3f(0, 0, e))).dist - dist)); return normalize(n); } fn getOrbitingLightPos_19(t: f32) -> vec3f { - var radius = 3f; - var height = 6f; - var speed = 1f; + const radius = 3f; + const height = 6f; + const speed = 1f; return vec3f((cos((t * speed)) * radius), (height + (sin((t * speed)) * radius)), 4); } diff --git a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts index 87f78e9d1c..19e083f79f 100644 --- a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts +++ b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts @@ -240,14 +240,14 @@ describe('stable-fluid example', () => { } @fragment fn fragmentImageFn_3(input: fragmentImageFn_Input_7) -> @location(0) vec4f { - var pixelStep = 0.001953125f; + const pixelStep = 0.001953125f; var leftSample = textureSample(result_4, linSampler_5, vec2f((input.uv.x - pixelStep), input.uv.y)).x; var rightSample = textureSample(result_4, linSampler_5, vec2f((input.uv.x + pixelStep), input.uv.y)).x; var upSample = textureSample(result_4, linSampler_5, vec2f(input.uv.x, (input.uv.y + pixelStep))).x; var downSample = textureSample(result_4, linSampler_5, vec2f(input.uv.x, (input.uv.y - pixelStep))).x; var gradientX = (rightSample - leftSample); var gradientY = (upSample - downSample); - var distortStrength = 0.8; + const distortStrength = 0.8; var distortVector = vec2f(gradientX, gradientY); var distortedUV = (input.uv + (distortVector * vec2f(distortStrength, -distortStrength))); var outputColor = textureSample(background_6, linSampler_5, vec2f(distortedUV.x, (1 - distortedUV.y))); diff --git a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts index 4094bc8e79..b8d97b0ce0 100644 --- a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts +++ b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts @@ -198,7 +198,7 @@ describe('tgsl parsing test example', () => { fn pointersTest_11() -> bool { var s = true; - var num = 0u; + const num = 0u; modifyNumFn_12((&num)); s = (s && (num == 1)); var vec = vec2f(); diff --git a/packages/typegpu/tests/numeric.test.ts b/packages/typegpu/tests/numeric.test.ts index 67e77cdade..d06a010fe3 100644 --- a/packages/typegpu/tests/numeric.test.ts +++ b/packages/typegpu/tests/numeric.test.ts @@ -72,11 +72,11 @@ describe('TGSL', () => { expect(asWgsl(main)).toMatchInlineSnapshot(` "fn main() { - var f = 0f; - var h = 0h; - var i = 0i; - var u = 0u; - var b = false; + const f = 0f; + const h = 0h; + const i = 0i; + const u = 0u; + const b = false; }" `); }); diff --git a/packages/typegpu/tests/slot.test.ts b/packages/typegpu/tests/slot.test.ts index bc59131f8b..97d54f140f 100644 --- a/packages/typegpu/tests/slot.test.ts +++ b/packages/typegpu/tests/slot.test.ts @@ -344,7 +344,7 @@ describe('tgpu.slot', () => { fn func() { var pos = vec3f(1, 2, 3); - var posX = 1; + const posX = 1; let vel = (&boid.vel); var velX = boid.vel.x; let vel_ = (&boid.vel); diff --git a/packages/typegpu/tests/std/matrix/rotate.test.ts b/packages/typegpu/tests/std/matrix/rotate.test.ts index f4aad2c4dd..f43bb4bcfa 100644 --- a/packages/typegpu/tests/std/matrix/rotate.test.ts +++ b/packages/typegpu/tests/std/matrix/rotate.test.ts @@ -16,7 +16,7 @@ describe('rotate', () => { expect(asWgsl(rotateFn)).toMatchInlineSnapshot(` "fn rotateFn() { - var angle = 4; + const angle = 4; var resultExpression = (mat4x4f(1, 0, 0, 0, 0, cos(angle), sin(angle), 0, 0, -sin(angle), cos(angle), 0, 0, 0, 0, 1) * mat4x4f(1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1)); }" `); @@ -30,17 +30,12 @@ describe('rotate', () => { const resultExpression = rotateY4(M, angle); }); - expect(parseResolved({ rotateFn })).toBe( - parse( - `fn rotateFn() { - var angle = 4; - var resultExpression = ( - mat4x4f(cos(angle), 0, -sin(angle), 0, 0, 1, 0, 0, sin(angle), 0, cos(angle), 0, 0, 0, 0, 1) * - mat4x4f(1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1) - ); - }`, - ), - ); + expect(asWgsl(rotateFn)).toMatchInlineSnapshot(` + "fn rotateFn() { + const angle = 4; + var resultExpression = (mat4x4f(cos(angle), 0, -sin(angle), 0, 0, 1, 0, 0, sin(angle), 0, cos(angle), 0, 0, 0, 0, 1) * mat4x4f(1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1)); + }" + `); }); it('generates correct WGSL for rotateZ4 with custom matrix', () => { @@ -51,17 +46,12 @@ describe('rotate', () => { const resultExpression = rotateZ4(M, angle); }); - expect(parseResolved({ rotateFn })).toBe( - parse( - `fn rotateFn() { - var angle = 4; - var resultExpression = ( - mat4x4f(cos(angle), sin(angle), 0, 0, -sin(angle), cos(angle), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) * - mat4x4f(1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1) - ); - }`, - ), - ); + expect(asWgsl(rotateFn)).toMatchInlineSnapshot(` + "fn rotateFn() { + const angle = 4; + var resultExpression = (mat4x4f(cos(angle), sin(angle), 0, 0, -sin(angle), cos(angle), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1) * mat4x4f(1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1)); + }" + `); }); it('rotates around X correctly', () => { diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 3aeef0830c..af2a9895bc 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -566,7 +566,7 @@ describe('wgslGenerator', () => { expect(asWgsl(testFn)).toMatchInlineSnapshot(` "fn testFn() -> u32 { var a = 12u; - var b = 2.5f; + const b = 2.5f; a = u32(b); return a; }" @@ -933,8 +933,8 @@ describe('wgslGenerator', () => { expect(asWgsl(main)).toMatchInlineSnapshot(` "fn main() -> i32 { - var notAKeyword = 0; - var struct_1 = 1; + const notAKeyword = 0; + const struct_1 = 1; return struct_1; }" `); @@ -1014,9 +1014,9 @@ describe('wgslGenerator', () => { expect(asWgsl(main)).toMatchInlineSnapshot(` "fn main() { - var mut_1 = 1; - var mut_1_1 = 2; - var mut_1_2 = 2; + const mut_1 = 1; + const mut_1_1 = 2; + const mut_1_2 = 2; }" `); }); @@ -1041,8 +1041,8 @@ describe('wgslGenerator', () => { expect(asWgsl(power)).toMatchInlineSnapshot(` "fn power() { - var a = 10f; - var b = 3f; + const a = 10f; + const b = 3f; var n = pow(a, b); }" `); @@ -1056,7 +1056,7 @@ describe('wgslGenerator', () => { expect(asWgsl(power)).toMatchInlineSnapshot(` "fn power() { - var n = 16.; + const n = 16.; }" `); }); @@ -1070,8 +1070,8 @@ describe('wgslGenerator', () => { expect(asWgsl(power)).toMatchInlineSnapshot(` "fn power() { - var a = 3u; - var b = 5i; + const a = 3u; + const b = 5i; var m = pow(f32(a), f32(b)); }" `); diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index e58b649d21..a7a19bc29b 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -301,8 +301,8 @@ describe('TGSL tgpu.fn function', () => { @compute @workgroup_size(24) fn compute_fn(input: compute_fn_Input) { var index = input.gid.x; - var iterationF = 0f; - var sign = 0; + const iterationF = 0f; + const sign = 0; var change = vec4f(); }" `); @@ -328,8 +328,8 @@ describe('TGSL tgpu.fn function', () => { @compute @workgroup_size(24) fn compute_fn(_arg_0: compute_fn_Input) { var index = _arg_0.gid.x; - var iterationF = 0f; - var sign = 0; + const iterationF = 0f; + const sign = 0; var change = vec4f(); }" `); From 9a75b1e70b8eb35ff04885a04bf5c45338af042e Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 19:33:34 +0200 Subject: [PATCH 09/59] Not allowing references to be returned from a function (unless it's a reference to something define in that function) --- .../fluid-double-buffering/index.ts | 2 +- packages/typegpu/src/data/snippet.ts | 14 +++++++----- packages/typegpu/src/tgsl/shellless.ts | 8 ++++--- packages/typegpu/src/tgsl/wgslGenerator.ts | 22 ++++++++++++++++++- 4 files changed, 35 insertions(+), 11 deletions(-) diff --git a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts index 9e7570b561..62008f4f66 100644 --- a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts @@ -43,7 +43,7 @@ const coordsToIndex = (x: number, y: number) => { const getCell = (x: number, y: number): d.v4f => { 'kernel'; - return inputGridSlot.$[coordsToIndex(x, y)]; + return d.vec4f(inputGridSlot.$[coordsToIndex(x, y)]); }; const setCell = (x: number, y: number, value: d.v4f) => { diff --git a/packages/typegpu/src/data/snippet.ts b/packages/typegpu/src/data/snippet.ts index 7342f57f8b..4493fa8f7c 100644 --- a/packages/typegpu/src/data/snippet.ts +++ b/packages/typegpu/src/data/snippet.ts @@ -3,6 +3,8 @@ import type { AnyData, UnknownData } from './dataTypes.ts'; import { DEV } from '../shared/env.ts'; import { type AddressSpace, isNumericSchema } from './wgslTypes.ts'; +type RefSpace = AddressSpace | 'this-function' | undefined; + export interface Snippet { readonly value: unknown; /** @@ -10,7 +12,7 @@ export interface Snippet { * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float */ readonly dataType: AnyData | UnknownData; - readonly ref: AddressSpace | undefined; + readonly ref: RefSpace; } export interface ResolvedSnippet { @@ -20,7 +22,7 @@ export interface ResolvedSnippet { * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float */ readonly dataType: AnyData; - readonly ref: AddressSpace | undefined; + readonly ref: RefSpace; } export type MapValueToSnippet = { [K in keyof T]: Snippet }; @@ -29,7 +31,7 @@ class SnippetImpl implements Snippet { constructor( readonly value: unknown, readonly dataType: AnyData | UnknownData, - readonly ref: AddressSpace | undefined, + readonly ref: RefSpace, ) {} } @@ -44,17 +46,17 @@ export function isSnippetNumeric(snippet: Snippet) { export function snip( value: string, dataType: AnyData, - ref: AddressSpace | undefined, + ref: RefSpace, ): ResolvedSnippet; export function snip( value: unknown, dataType: AnyData | UnknownData, - ref: AddressSpace | undefined, + ref: RefSpace, ): Snippet; export function snip( value: unknown, dataType: AnyData | UnknownData, - ref: AddressSpace | undefined, + ref: RefSpace, ): Snippet | ResolvedSnippet { if (DEV && isSnippet(value)) { // An early error, but not worth checking every time in production diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index 7a593fe1f1..8703bb003c 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -38,11 +38,13 @@ export class ShelllessRepository { const argTypes = argSnippets.map((s) => { const type = concretize(s.dataType as AnyData); - return s.ref !== undefined && !isPtr(type) + const addressSpace = s.ref === 'this-function' ? 'function' : s.ref; + + return addressSpace !== undefined && !isPtr(type) ? INTERNAL_createPtr( - s.ref, + addressSpace, type as StorableData, - addressSpaceToDefaultAccess[s.ref], + addressSpaceToDefaultAccess[addressSpace], ) : type; }); diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 5271160cd9..fe7eecd06e 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -174,7 +174,7 @@ ${this.ctx.pre}}`; const snippet = snip( this.ctx.makeNameValid(id), dataType, - /* ref */ wgsl.isNaturallyRef(dataType) ? 'function' : undefined, + /* ref */ wgsl.isNaturallyRef(dataType) ? 'this-function' : undefined, ); this.ctx.defineVariable(id, snippet); return snippet; @@ -755,6 +755,26 @@ ${this.ctx.pre}}`; ) : this.expression(returnNode); + if ( + !expectedReturnType && + returnSnippet.ref && + returnSnippet.ref !== 'this-function' + ) { + const str = this.ctx.resolve( + returnSnippet.value, + returnSnippet.dataType, + ).value; + const typeStr = this.ctx.resolve( + toStorable(returnSnippet.dataType as wgsl.StorableData), + ).value; + throw new WgslTypeError( + `'return ${str};' is invalid, cannot return references. +----- +Try 'return ${typeStr}(${str});' instead. +-----`, + ); + } + returnSnippet = tryConvertSnippet( returnSnippet, toStorable(returnSnippet.dataType as wgsl.StorableData), From 5dc3884831752304075acafdaa4e8f4bf39ba1f6 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 19:59:24 +0200 Subject: [PATCH 10/59] Move member access exceptions to `accessProp` so that it's shared with the value proxies --- packages/typegpu/src/core/valueProxyUtils.ts | 2 +- packages/typegpu/src/data/index.ts | 5 +- .../typegpu/src/tgsl/generationHelpers.ts | 119 ++++++++++++------ packages/typegpu/src/tgsl/wgslGenerator.ts | 88 +------------ .../typegpu/tests/std/matrix/rotate.test.ts | 2 +- .../tests/tgsl/generationHelpers.test.ts | 67 +++++----- 6 files changed, 120 insertions(+), 163 deletions(-) diff --git a/packages/typegpu/src/core/valueProxyUtils.ts b/packages/typegpu/src/core/valueProxyUtils.ts index fb106350b2..7851a62330 100644 --- a/packages/typegpu/src/core/valueProxyUtils.ts +++ b/packages/typegpu/src/core/valueProxyUtils.ts @@ -37,7 +37,7 @@ export const valueProxyHandler: ProxyHandler< return new Proxy({ [$internal]: true, - [$resolve]: () => accessed, + [$resolve]: (ctx) => ctx.resolve(accessed.value, accessed.dataType), [$ownSnippet]: accessed, toString: () => `${String(target)}.${prop}`, }, valueProxyHandler); diff --git a/packages/typegpu/src/data/index.ts b/packages/typegpu/src/data/index.ts index 6c83224bc3..e679fd980b 100644 --- a/packages/typegpu/src/data/index.ts +++ b/packages/typegpu/src/data/index.ts @@ -2,7 +2,10 @@ * @module typegpu/data */ -import { type InfixOperator, infixOperators } from '../tgsl/wgslGenerator.ts'; +import { + type InfixOperator, + infixOperators, +} from '../tgsl/generationHelpers.ts'; import { $internal } from '../shared/symbols.ts'; import { MatBase } from './matrix.ts'; import { VecBase } from './vectorImpl.ts'; diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index c2af32f5e0..f1ae3d289d 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -1,7 +1,9 @@ import { type AnyData, + InfixDispatch, isDisarray, isUnstruct, + MatrixColumnsAccess, undecorate, UnknownData, } from '../data/dataTypes.ts'; @@ -15,12 +17,7 @@ import { i32, u32, } from '../data/numeric.ts'; -import { - isSnippet, - type ResolvedSnippet, - snip, - type Snippet, -} from '../data/snippet.ts'; +import { isSnippet, snip, type Snippet } from '../data/snippet.ts'; import { vec2b, vec2f, @@ -42,6 +39,7 @@ import { type AnyWgslData, type F32, type I32, + isMat, isMatInstance, isNaturallyRef, isNumericSchema, @@ -51,8 +49,14 @@ import { isWgslStruct, } from '../data/wgslTypes.ts'; import { getResolutionCtx } from '../execMode.ts'; -import { getOwnSnippet, type ResolutionCtx } from '../types.ts'; +import { + getOwnSnippet, + isKnownAtComptime, + type ResolutionCtx, +} from '../types.ts'; import type { ShelllessRepository } from './shellless.ts'; +import { add, div, mul, sub } from '../std/operators.ts'; +import { $internal } from '../shared/symbols.ts'; type SwizzleableType = 'f' | 'h' | 'i' | 'u' | 'b'; type SwizzleLength = 1 | 2 | 3 | 4; @@ -114,45 +118,84 @@ const kindToSchema = { mat4x4f: mat4x4f, } as const; -export function getTypeForPropAccess( - targetType: AnyData, - propName: string, -): AnyData | UnknownData { - if (isWgslStruct(targetType) || isUnstruct(targetType)) { - const propType = targetType.propTypes[propName]; - return propType ? undecorate(propType) as AnyData : UnknownData; - } +const infixKinds = [ + 'vec2f', + 'vec3f', + 'vec4f', + 'vec2h', + 'vec3h', + 'vec4h', + 'vec2i', + 'vec3i', + 'vec4i', + 'vec2u', + 'vec3u', + 'vec4u', + 'mat2x2f', + 'mat3x3f', + 'mat4x4f', +]; + +export const infixOperators = { + add, + sub, + mul, + div, +} as const; - if (targetType === bool || isNumericSchema(targetType)) { - // No props to be accessed here - return UnknownData; - } +export type InfixOperator = keyof typeof infixOperators; + +export function accessProp( + target: Snippet, + propName: string, +): Snippet | undefined { + // biome-ignore lint/style/noNonNullAssertion: it's there + const ctx = getResolutionCtx()!; - const propLength = propName.length; if ( - isVec(targetType) && - propLength >= 1 && - propLength <= 4 + infixKinds.includes(target.dataType.type) && + propName in infixOperators ) { - const swizzleTypeChar = targetType.type.includes('bool') - ? 'b' - : (targetType.type[4] as SwizzleableType); - const swizzleType = - swizzleLenToType[swizzleTypeChar][propLength as SwizzleLength]; - if (swizzleType) { - return swizzleType; + return snip( + new InfixDispatch( + propName, + target, + infixOperators[propName as InfixOperator][$internal].gpuImpl, + ), + UnknownData, + /* ref */ target.ref, + ); + } + + if (isWgslArray(target.dataType) && propName === 'length') { + if (target.dataType.elementCount === 0) { + // Dynamically-sized array + return snip( + `arrayLength(&${ctx.resolve(target.value).value})`, + u32, + /* ref */ undefined, + ); } + + return snip( + String(target.dataType.elementCount), + abstractInt, + /* ref */ undefined, + ); } - return UnknownData; -} + if (isMat(target.dataType) && propName === 'columns') { + return snip( + new MatrixColumnsAccess(target), + UnknownData, + /* ref */ target.ref, + ); + } -export function accessProp( - target: Snippet, - propName: string, -): ResolvedSnippet | undefined { - // biome-ignore lint/style/noNonNullAssertion: it's there - const ctx = getResolutionCtx()!; + if (isKnownAtComptime(target) || target.dataType.type === 'unknown') { + // biome-ignore lint/suspicious/noExplicitAny: we either know exactly what it is, or have no idea at all + return coerceToSnippet((target.value as any)[propName]); + } if (isWgslStruct(target.dataType) || isUnstruct(target.dataType)) { let propType = target.dataType.propTypes[propName]; diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index fe7eecd06e..52a29ab6bb 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -11,7 +11,7 @@ import { toStorable, UnknownData, } from '../data/dataTypes.ts'; -import { abstractInt, bool, u32 } from '../data/numeric.ts'; +import { bool } from '../data/numeric.ts'; import { isSnippet, snip, type Snippet } from '../data/snippet.ts'; import * as wgsl from '../data/wgslTypes.ts'; import { invariant, ResolutionError, WgslTypeError } from '../errors.ts'; @@ -35,7 +35,6 @@ import { concretize, type GenerationCtx, getTypeForIndexAccess, - getTypeForPropAccess, numericLiteralToSnippet, } from './generationHelpers.ts'; import type { ShaderGenerator } from './shaderGenerator.ts'; @@ -68,33 +67,6 @@ const parenthesizedOps = [ const binaryLogicalOps = ['&&', '||', '==', '!=', '<', '<=', '>', '>=']; -const infixKinds = [ - 'vec2f', - 'vec3f', - 'vec4f', - 'vec2h', - 'vec3h', - 'vec4h', - 'vec2i', - 'vec3i', - 'vec4i', - 'vec2u', - 'vec3u', - 'vec4u', - 'mat2x2f', - 'mat3x3f', - 'mat4x4f', -]; - -export const infixOperators = { - add, - sub, - mul, - div, -} as const; - -export type InfixOperator = keyof typeof infixOperators; - type Operator = | tinyest.BinaryOperator | tinyest.AssignmentOperator @@ -308,69 +280,11 @@ ${this.ctx.pre}}`; return snip(new ConsoleLog(), UnknownData, /* ref */ undefined); } - if (target.dataType.type === 'unknown') { - // No idea what the type is, so we act on the snippet's value and try to guess - - // biome-ignore lint/suspicious/noExplicitAny: we're inspecting the value, and it could be any value - const propValue = (target.value as any)[property]; - - // We try to extract any type information based on the prop's value - return coerceToSnippet(propValue); - } - if (wgsl.isPtr(target.dataType)) { // De-referencing the pointer target = tryConvertSnippet(target, target.dataType.inner, false); } - if ( - infixKinds.includes(target.dataType.type) && - property in infixOperators - ) { - return snip( - new InfixDispatch( - property, - target, - infixOperators[property as InfixOperator][$internal].gpuImpl, - ), - UnknownData, - /* ref */ target.ref, - ); - } - - if (wgsl.isWgslArray(target.dataType) && property === 'length') { - if (target.dataType.elementCount === 0) { - // Dynamically-sized array - return snip( - `arrayLength(&${this.ctx.resolve(target.value).value})`, - u32, - /* ref */ undefined, - ); - } - - return snip( - String(target.dataType.elementCount), - abstractInt, - /* ref */ undefined, - ); - } - - if (wgsl.isMat(target.dataType) && property === 'columns') { - return snip( - new MatrixColumnsAccess(target), - UnknownData, - /* ref */ target.ref, - ); - } - - if ( - wgsl.isVec(target.dataType) && wgsl.isVecInstance(target.value) - ) { - // We're operating on a vector that's known at resolution time - // biome-ignore lint/suspicious/noExplicitAny: it's probably a swizzle - return coerceToSnippet((target.value as any)[property]); - } - const accessed = accessProp(target, property); if (!accessed) { throw new Error( diff --git a/packages/typegpu/tests/std/matrix/rotate.test.ts b/packages/typegpu/tests/std/matrix/rotate.test.ts index f43bb4bcfa..8e400aef7b 100644 --- a/packages/typegpu/tests/std/matrix/rotate.test.ts +++ b/packages/typegpu/tests/std/matrix/rotate.test.ts @@ -3,7 +3,7 @@ import { mat4x4f, vec4f } from '../../../src/data/index.ts'; import tgpu from '../../../src/index.ts'; import { isCloseTo, mul } from '../../../src/std/index.ts'; import { rotateX4, rotateY4, rotateZ4 } from '../../../src/std/matrix.ts'; -import { asWgsl, parse, parseResolved } from '../../utils/parseResolved.ts'; +import { asWgsl } from '../../utils/parseResolved.ts'; describe('rotate', () => { it('generates correct WGSL for rotateX4 with custom matrix', () => { diff --git a/packages/typegpu/tests/tgsl/generationHelpers.test.ts b/packages/typegpu/tests/tgsl/generationHelpers.test.ts index c4dc4bf58f..9f57d7a542 100644 --- a/packages/typegpu/tests/tgsl/generationHelpers.test.ts +++ b/packages/typegpu/tests/tgsl/generationHelpers.test.ts @@ -1,4 +1,4 @@ -import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; +import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { arrayOf } from '../../src/data/array.ts'; import { mat2x2f, mat3x3f, mat4x4f } from '../../src/data/matrix.ts'; import { @@ -20,41 +20,23 @@ import { vec4h, } from '../../src/data/vector.ts'; import { + accessProp, coerceToSnippet, - type GenerationCtx, getTypeForIndexAccess, - getTypeForPropAccess, numericLiteralToSnippet, } from '../../src/tgsl/generationHelpers.ts'; import { UnknownData } from '../../src/data/dataTypes.ts'; import { snip } from '../../src/data/snippet.ts'; -import { CodegenState } from '../../src/types.ts'; import { INTERNAL_setCtx } from '../../src/execMode.ts'; - -const mockCtx = { - indent: () => '', - dedent: () => '', - pushBlockScope: () => {}, - popBlockScope: () => {}, - mode: new CodegenState(), - getById: vi.fn(), - defineVariable: vi.fn((id, dataType) => ({ value: id, dataType })), - resolve: vi.fn((val) => { - if ( - (typeof val === 'function' || typeof val === 'object') && - 'type' in val - ) { - return val.type; - } - return val; - }), - unwrap: vi.fn((val) => val), - pre: '', -} as unknown as GenerationCtx; +import { ResolutionCtxImpl } from '../../src/resolutionCtx.ts'; +import { namespace } from '../../src/core/resolve/namespace.ts'; describe('generationHelpers', () => { beforeEach(() => { - INTERNAL_setCtx(mockCtx); + const ctx = new ResolutionCtxImpl({ + namespace: namespace(), + }); + INTERNAL_setCtx(ctx); }); afterEach(() => { @@ -93,27 +75,42 @@ describe('generationHelpers', () => { }); }); - describe('getTypeForPropAccess', () => { + describe('accessProp', () => { const MyStruct = struct({ foo: f32, bar: vec3f, }); it('should return struct property types', () => { - expect(getTypeForPropAccess(MyStruct, 'foo')).toBe(f32); - expect(getTypeForPropAccess(MyStruct, 'bar')).toBe(vec3f); - expect(getTypeForPropAccess(MyStruct, 'notfound')).toBe(UnknownData); + const target = snip('foo', MyStruct, 'this-function'); + expect(accessProp(target, 'foo')).toStrictEqual( + snip('foo.foo', f32, /* ref */ undefined), + ); + expect(accessProp(target, 'bar')).toStrictEqual( + snip('foo.bar', vec3f, /* ref */ 'this-function'), + ); + expect(accessProp(target, 'notfound')).toStrictEqual(undefined); }); it('should return swizzle types on vectors', () => { - expect(getTypeForPropAccess(vec4f, 'x')).toBe(f32); - expect(getTypeForPropAccess(vec4f, 'yz')).toBe(vec2f); - expect(getTypeForPropAccess(vec4f, 'xyzw')).toBe(vec4f); + const target = snip('foo', vec4f, 'this-function'); + + expect(accessProp(target, 'x')).toStrictEqual( + snip('foo.x', f32, /* ref */ undefined), + ); + expect(accessProp(target, 'yz')).toStrictEqual( + snip('foo.yz', vec2f, /* ref */ undefined), + ); + expect(accessProp(target, 'xyzw')).toStrictEqual( + snip('foo.xyzw', vec4f, /* ref */ undefined), + ); }); it('should return UnknownData when applied to primitives or invalid', () => { - expect(getTypeForPropAccess(u32, 'x')).toBe(UnknownData); - expect(getTypeForPropAccess(bool, 'x')).toBe(UnknownData); + const target1 = snip('foo', u32, /* ref */ undefined); + const target2 = snip('foo', bool, /* ref */ undefined); + expect(accessProp(target1, 'x')).toBe(undefined); + expect(accessProp(target2, 'x')).toBe(undefined); }); }); From 755d13b43f9f481f0ac4021d695179e27c974211 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 20:42:58 +0200 Subject: [PATCH 11/59] Better indexing --- .../typegpu/src/tgsl/generationHelpers.ts | 85 +++++++++++++++---- packages/typegpu/src/tgsl/wgslGenerator.ts | 76 +++++------------ packages/typegpu/tests/accessor.test.ts | 2 +- packages/typegpu/tests/derived.test.ts | 2 +- packages/typegpu/tests/slot.test.ts | 2 +- .../tests/tgsl/generationHelpers.test.ts | 49 ++++++++--- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 4 +- 7 files changed, 130 insertions(+), 90 deletions(-) diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index f1ae3d289d..cf8b3f27e3 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -57,6 +57,7 @@ import { import type { ShelllessRepository } from './shellless.ts'; import { add, div, mul, sub } from '../std/operators.ts'; import { $internal } from '../shared/symbols.ts'; +import { stitch } from '../core/resolve/stitch.ts'; type SwizzleableType = 'f' | 'h' | 'i' | 'u' | 'b'; type SwizzleLength = 1 | 2 | 3 | 4; @@ -192,11 +193,6 @@ export function accessProp( ); } - if (isKnownAtComptime(target) || target.dataType.type === 'unknown') { - // biome-ignore lint/suspicious/noExplicitAny: we either know exactly what it is, or have no idea at all - return coerceToSnippet((target.value as any)[propName]); - } - if (isWgslStruct(target.dataType) || isUnstruct(target.dataType)) { let propType = target.dataType.propTypes[propName]; if (!propType) { @@ -232,14 +228,23 @@ export function accessProp( if (!swizzleType) { return undefined; } + return snip( - `${ctx.resolve(target.value, target.dataType).value}.${propName}`, + isKnownAtComptime(target) + // biome-ignore lint/suspicious/noExplicitAny: it's fine, the prop is there + ? (target.value as any)[propName] + : `${ctx.resolve(target.value, target.dataType).value}.${propName}`, swizzleType, // Swizzling creates new vectors (unless they're on the lhs of an assignment, but that's not yet supported in WGSL) /* ref */ undefined, ); } + if (isKnownAtComptime(target) || target.dataType.type === 'unknown') { + // biome-ignore lint/suspicious/noExplicitAny: we either know exactly what it is, or have no idea at all + return coerceToSnippet((target.value as any)[propName]); + } + return undefined; } @@ -249,27 +254,71 @@ const indexableTypeToResult = { mat4x4f: vec4f, } as const; -export function getTypeForIndexAccess( - dataType: AnyData, -): AnyData | UnknownData { +export function accessIndex( + target: Snippet, + index: Snippet, +): Snippet | undefined { + // biome-ignore lint/style/noNonNullAssertion: it's there + const ctx = getResolutionCtx()!; + // array - if (isWgslArray(dataType) || isDisarray(dataType)) { - return dataType.elementType as AnyData; + if (isWgslArray(target.dataType) || isDisarray(target.dataType)) { + const elementType = target.dataType.elementType as AnyData; + const targetStr = ctx.resolve(target.value, target.dataType).value; + const indexStr = ctx.resolve(index.value, index.dataType).value; + return snip( + `${targetStr}[${indexStr}]`, + elementType, + /* ref */ target.ref !== undefined && isNaturallyRef(elementType) + ? target.ref + : undefined, + ); } // vector - if (isVec(dataType)) { - return dataType.primitive; + if (isVec(target.dataType)) { + return snip( + isKnownAtComptime(target) && isKnownAtComptime(index) + // biome-ignore lint/suspicious/noExplicitAny: it's fine, it's there + ? (target.value as any)[index.value as any] + : stitch`${target}[${index}]`, + target.dataType.primitive, + /* ref */ undefined, + ); } - // matrix - if (dataType.type in indexableTypeToResult) { - return indexableTypeToResult[ - dataType.type as keyof typeof indexableTypeToResult + // matrix.columns + if (target.value instanceof MatrixColumnsAccess) { + const propType = indexableTypeToResult[ + target.value.matrix.dataType.type as keyof typeof indexableTypeToResult ]; + + return snip( + stitch`${target.value.matrix}[${index}]`, + propType, + /* ref */ target.ref, + ); } - return UnknownData; + // matrix + if (target.dataType.type in indexableTypeToResult) { + throw new Error( + "The only way of accessing matrix elements in TGSL is through the 'columns' property.", + ); + } + + if ( + (isKnownAtComptime(target) && isKnownAtComptime(index)) || + target.dataType.type === 'unknown' + ) { + // No idea what the type is, so we act on the snippet's value and try to guess + return coerceToSnippet( + // biome-ignore lint/suspicious/noExplicitAny: we're inspecting the value, and it could be any value + (target.value as any)[index.value as number], + ); + } + + return undefined; } export function numericLiteralToSnippet(value: number): Snippet { diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 52a29ab6bb..514337b290 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -5,13 +5,11 @@ import { type AnyData, ConsoleLog, InfixDispatch, - isData, isLooseData, - MatrixColumnsAccess, toStorable, UnknownData, } from '../data/dataTypes.ts'; -import { bool } from '../data/numeric.ts'; +import { bool, i32, u32 } from '../data/numeric.ts'; import { isSnippet, snip, type Snippet } from '../data/snippet.ts'; import * as wgsl from '../data/wgslTypes.ts'; import { invariant, ResolutionError, WgslTypeError } from '../errors.ts'; @@ -30,11 +28,10 @@ import { tryConvertSnippet, } from './conversion.ts'; import { + accessIndex, accessProp, - coerceToSnippet, concretize, type GenerationCtx, - getTypeForIndexAccess, numericLiteralToSnippet, } from './generationHelpers.ts'; import type { ShaderGenerator } from './shaderGenerator.ts'; @@ -299,66 +296,31 @@ ${this.ctx.pre}}`; if (expression[0] === NODE.indexAccess) { // Index Access const [_, targetNode, propertyNode] = expression; - const target = this.expression(targetNode); - const property = this.expression(propertyNode); - const propertyStr = - this.ctx.resolve(property.value, property.dataType).value; - - if (target.value instanceof MatrixColumnsAccess) { - const propType = getTypeForIndexAccess( - target.value.matrix.dataType as AnyData, - ); + let target = this.expression(targetNode); + const inProperty = this.expression(propertyNode); + const property = convertToCommonType( + [inProperty], + [u32, i32], + /* verbose */ false, + )?.[0] ?? inProperty; - return snip( - stitch`${target.value.matrix}[${propertyStr}]`, - propType, - /* ref */ target.ref, - ); + if (wgsl.isPtr(target.dataType)) { + // De-referencing the pointer + target = tryConvertSnippet(target, target.dataType.inner, false); } - const targetStr = this.ctx.resolve(target.value, target.dataType).value; - - if (target.dataType.type === 'unknown') { - // No idea what the type is, so we act on the snippet's value and try to guess - - if ( - Array.isArray(propertyNode) && propertyNode[0] === NODE.numericLiteral - ) { - return coerceToSnippet( - // biome-ignore lint/suspicious/noExplicitAny: we're inspecting the value, and it could be any value - (target.value as any)[propertyNode[1] as number], - ); - } - throw new Error( - `Cannot index value ${targetStr} of unknown type with index ${propertyStr}`, - ); - } + const accessed = accessIndex(target, property); + if (!accessed) { + const targetStr = this.ctx.resolve(target.value, target.dataType).value; + const propertyStr = + this.ctx.resolve(property.value, property.dataType).value; - if (wgsl.isMat(target.dataType)) { throw new Error( - "The only way of accessing matrix elements in TGSL is through the 'columns' property.", + `Cannot index value ${targetStr} with index ${propertyStr}`, ); } - if (wgsl.isPtr(target.dataType)) { - const propType = getTypeForIndexAccess( - target.dataType.inner as AnyData, - ); - return snip( - `(*${targetStr})[${propertyStr}]`, - propType, - /* ref */ wgsl.isNaturallyRef(propType) ? target.ref : undefined, - ); - } - - const propType = isData(target.dataType) - ? getTypeForIndexAccess(target.dataType) - : UnknownData; - return snip( - `${targetStr}[${propertyStr}]`, - propType, - /* ref */ wgsl.isNaturallyRef(propType) ? target.ref : undefined, - ); + return accessed; } if (expression[0] === NODE.numericLiteral) { diff --git a/packages/typegpu/tests/accessor.test.ts b/packages/typegpu/tests/accessor.test.ts index 933684e654..128ec3b5bd 100644 --- a/packages/typegpu/tests/accessor.test.ts +++ b/packages/typegpu/tests/accessor.test.ts @@ -163,7 +163,7 @@ describe('tgpu.accessor', () => { var color = vec3f(1, 0, 0); let color2 = (&redUniform); var color3 = getColor(); - const colorX = 1; + const colorX = 1f; var color2X = redUniform.x; var color3X = getColor().x; }" diff --git a/packages/typegpu/tests/derived.test.ts b/packages/typegpu/tests/derived.test.ts index 06b1603936..c7699e3497 100644 --- a/packages/typegpu/tests/derived.test.ts +++ b/packages/typegpu/tests/derived.test.ts @@ -146,7 +146,7 @@ describe('TgpuDerived', () => { fn func() { var pos = vec3f(2, 4, 6); - const posX = 2; + const posX = 2f; let vel = (&boid.vel); var velX = boid.vel.x; let vel_ = (&boid.vel); diff --git a/packages/typegpu/tests/slot.test.ts b/packages/typegpu/tests/slot.test.ts index 97d54f140f..3ae3f72ac5 100644 --- a/packages/typegpu/tests/slot.test.ts +++ b/packages/typegpu/tests/slot.test.ts @@ -344,7 +344,7 @@ describe('tgpu.slot', () => { fn func() { var pos = vec3f(1, 2, 3); - const posX = 1; + const posX = 1f; let vel = (&boid.vel); var velX = boid.vel.x; let vel_ = (&boid.vel); diff --git a/packages/typegpu/tests/tgsl/generationHelpers.test.ts b/packages/typegpu/tests/tgsl/generationHelpers.test.ts index 9f57d7a542..082ef0fdb3 100644 --- a/packages/typegpu/tests/tgsl/generationHelpers.test.ts +++ b/packages/typegpu/tests/tgsl/generationHelpers.test.ts @@ -20,9 +20,9 @@ import { vec4h, } from '../../src/data/vector.ts'; import { + accessIndex, accessProp, coerceToSnippet, - getTypeForIndexAccess, numericLiteralToSnippet, } from '../../src/tgsl/generationHelpers.ts'; import { UnknownData } from '../../src/data/dataTypes.ts'; @@ -114,26 +114,55 @@ describe('generationHelpers', () => { }); }); - describe('getTypeForIndexAccess', () => { + describe('accessIndex', () => { const arr = arrayOf(f32, 2); + const index = snip('0', u32, /* ref */ undefined); it('returns element type for arrays', () => { - expect(getTypeForIndexAccess(arr)).toBe(f32); + const target = snip('foo', arr, /* ref */ undefined); + expect(accessIndex(target, index)).toStrictEqual( + snip('foo[0]', f32, undefined), + ); }); it('returns vector component', () => { - expect(getTypeForIndexAccess(vec2i)).toBe(i32); - expect(getTypeForIndexAccess(vec4h)).toBe(f16); + const target1 = snip('foo', vec2i, /* ref */ undefined); + const target2 = snip('foo', vec4h, /* ref */ undefined); + expect(accessIndex(target1, index)).toStrictEqual( + snip('foo[0]', i32, undefined), + ); + expect(accessIndex(target2, index)).toStrictEqual( + snip('foo[0]', f16, undefined), + ); }); it('returns matrix column type', () => { - expect(getTypeForIndexAccess(mat2x2f)).toBe(vec2f); - expect(getTypeForIndexAccess(mat3x3f)).toBe(vec3f); - expect(getTypeForIndexAccess(mat4x4f)).toBe(vec4f); + const target1 = accessProp( + snip('foo', mat2x2f, /* ref */ undefined), + 'columns', + ); + const target2 = accessProp( + snip('foo', mat3x3f, /* ref */ undefined), + 'columns', + ); + const target3 = accessProp( + snip('foo', mat4x4f, /* ref */ undefined), + 'columns', + ); + expect(target1 && accessIndex(target1, index)).toStrictEqual( + snip('foo[0]', vec2f, undefined), + ); + expect(target2 && accessIndex(target2, index)).toStrictEqual( + snip('foo[0]', vec3f, undefined), + ); + expect(target3 && accessIndex(target3, index)).toStrictEqual( + snip('foo[0]', vec4f, undefined), + ); }); - it('returns UnknownData otherwise', () => { - expect(getTypeForIndexAccess(f32)).toBe(UnknownData); + it('returns undefined otherwise', () => { + const target = snip('foo', f32, /* ref */ undefined); + expect(accessIndex(target, index)).toBe(undefined); }); }); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index af2a9895bc..c8f3fb94f4 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -1118,9 +1118,9 @@ describe('wgslGenerator', () => { }); expect(asWgsl(testFn)).toMatchInlineSnapshot(` - "var index: u32; + "var matrix: mat4x4f; - var matrix: mat4x4f; + var index: u32; fn testFn() { let element = (&matrix[index]); From ec2657f2144909bc20452bce137fac00794e26c5 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 20:46:37 +0200 Subject: [PATCH 12/59] Indexing arrays at comptime --- .../typegpu/src/tgsl/generationHelpers.ts | 20 ++++++++----------- 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index cf8b3f27e3..887b930d71 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -150,9 +150,6 @@ export function accessProp( target: Snippet, propName: string, ): Snippet | undefined { - // biome-ignore lint/style/noNonNullAssertion: it's there - const ctx = getResolutionCtx()!; - if ( infixKinds.includes(target.dataType.type) && propName in infixOperators @@ -172,7 +169,7 @@ export function accessProp( if (target.dataType.elementCount === 0) { // Dynamically-sized array return snip( - `arrayLength(&${ctx.resolve(target.value).value})`, + stitch`arrayLength(&${target})`, u32, /* ref */ undefined, ); @@ -201,7 +198,7 @@ export function accessProp( propType = undecorate(propType); return snip( - `${ctx.resolve(target.value, target.dataType).value}.${propName}`, + stitch`${target}.${propName}`, propType, /* ref */ target.ref !== undefined && isNaturallyRef(propType) ? target.ref @@ -233,7 +230,7 @@ export function accessProp( isKnownAtComptime(target) // biome-ignore lint/suspicious/noExplicitAny: it's fine, the prop is there ? (target.value as any)[propName] - : `${ctx.resolve(target.value, target.dataType).value}.${propName}`, + : stitch`${target}.${propName}`, swizzleType, // Swizzling creates new vectors (unless they're on the lhs of an assignment, but that's not yet supported in WGSL) /* ref */ undefined, @@ -258,16 +255,15 @@ export function accessIndex( target: Snippet, index: Snippet, ): Snippet | undefined { - // biome-ignore lint/style/noNonNullAssertion: it's there - const ctx = getResolutionCtx()!; - // array if (isWgslArray(target.dataType) || isDisarray(target.dataType)) { const elementType = target.dataType.elementType as AnyData; - const targetStr = ctx.resolve(target.value, target.dataType).value; - const indexStr = ctx.resolve(index.value, index.dataType).value; + return snip( - `${targetStr}[${indexStr}]`, + isKnownAtComptime(target) && isKnownAtComptime(index) + // biome-ignore lint/suspicious/noExplicitAny: it's fine, it's there + ? (target.value as any)[index.value as any] + : stitch`${target}[${index}]`, elementType, /* ref */ target.ref !== undefined && isNaturallyRef(elementType) ? target.ref From 8497a35551f0834c8124481045f2181fe37177c7 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 21:40:11 +0200 Subject: [PATCH 13/59] Constant tracking --- .../tests/tgsl-parsing-test/pointers.ts | 15 ++-- .../typegpu/src/core/buffer/bufferUsage.ts | 8 +- .../typegpu/src/core/constant/tgpuConstant.ts | 4 +- .../typegpu/src/core/declare/tgpuDeclare.ts | 2 +- .../typegpu/src/core/function/dualImpl.ts | 9 +- packages/typegpu/src/core/function/fnCore.ts | 6 +- packages/typegpu/src/core/function/tgpuFn.ts | 6 +- .../src/core/pipeline/computePipeline.ts | 2 +- .../src/core/pipeline/renderPipeline.ts | 2 +- .../typegpu/src/core/resolve/tgpuResolve.ts | 2 +- packages/typegpu/src/core/slot/accessor.ts | 2 +- .../typegpu/src/core/variable/tgpuVariable.ts | 4 +- packages/typegpu/src/data/array.ts | 4 +- packages/typegpu/src/data/disarray.ts | 5 +- packages/typegpu/src/data/matrix.ts | 24 +++--- packages/typegpu/src/data/snippet.ts | 6 +- packages/typegpu/src/data/vectorImpl.ts | 6 +- packages/typegpu/src/resolutionCtx.ts | 30 +++---- packages/typegpu/src/std/atomic.ts | 24 +++--- packages/typegpu/src/std/boolean.ts | 6 +- packages/typegpu/src/std/derivative.ts | 18 ++-- packages/typegpu/src/std/discard.ts | 2 +- packages/typegpu/src/std/extensions.ts | 2 +- packages/typegpu/src/std/matrix.ts | 10 +-- packages/typegpu/src/std/numeric.ts | 4 +- packages/typegpu/src/std/packing.ts | 8 +- packages/typegpu/src/std/texture.ts | 16 ++-- .../src/tgsl/consoleLog/logGenerator.ts | 4 +- .../typegpu/src/tgsl/generationHelpers.ts | 39 +++++---- packages/typegpu/src/tgsl/shellless.ts | 6 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 46 ++++++---- packages/typegpu/tests/constant.test.ts | 2 +- packages/typegpu/tests/resolve.test.ts | 4 +- .../typegpu/tests/tgsl/conversion.test.ts | 42 +++++----- .../tests/tgsl/generationHelpers.test.ts | 84 +++++++++---------- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 8 +- 36 files changed, 245 insertions(+), 217 deletions(-) diff --git a/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts b/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts index 80d639a1b5..5c2818d27c 100644 --- a/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts +++ b/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts @@ -4,10 +4,10 @@ import * as std from 'typegpu/std'; const SimpleStruct = d.struct({ vec: d.vec2f }); -const modifyNumFn = tgpu.fn([d.ptrFn(d.u32)])((ptr) => { - // biome-ignore lint/style/noParameterAssign: it's just a test - ptr += 1; -}); +// const modifyNumFn = tgpu.fn([d.ptrFn(d.u32)])((ptr) => { +// // biome-ignore lint/style/noParameterAssign: it's just a test +// ptr += 1; +// }); const modifyVecFn = tgpu.fn([d.ptrFn(d.vec2f)])((ptr) => { ptr.x += 1; @@ -38,9 +38,10 @@ export const pointersTest = tgpu.fn([], d.bool)(() => { let s = true; // function pointers - const num = d.u32(); - modifyNumFn(num); - s = s && (num === 1); + // TODO: Uncomment this when boxed values are implemented + // const num = d.u32(); + // modifyNumFn(num); + // s = s && (num === 1); const vec = d.vec2f(); modifyVecFn(vec); diff --git a/packages/typegpu/src/core/buffer/bufferUsage.ts b/packages/typegpu/src/core/buffer/bufferUsage.ts index 35129bb499..080ba62e6d 100644 --- a/packages/typegpu/src/core/buffer/bufferUsage.ts +++ b/packages/typegpu/src/core/buffer/bufferUsage.ts @@ -135,7 +135,7 @@ class TgpuFixedBufferImpl< dataType, /* ref */ isNaturallyRef(dataType) ? this.usage === 'uniform' ? 'uniform' : 'storage' - : undefined, + : 'runtime', ); } @@ -155,7 +155,7 @@ class TgpuFixedBufferImpl< dataType, /* ref */ isNaturallyRef(dataType) ? usage === 'uniform' ? 'uniform' : 'storage' - : undefined, + : 'runtime', ); }, [$resolve]: (ctx) => ctx.resolve(this), @@ -268,7 +268,7 @@ export class TgpuLaidOutBufferImpl< dataType, /* ref */ isNaturallyRef(dataType) ? this.usage === 'uniform' ? 'uniform' : 'storage' - : undefined, + : 'runtime', ); } @@ -288,7 +288,7 @@ export class TgpuLaidOutBufferImpl< schema, /* ref */ isNaturallyRef(schema) ? usage === 'uniform' ? 'uniform' : 'storage' - : undefined, + : 'runtime', ); }, [$resolve]: (ctx) => ctx.resolve(this), diff --git a/packages/typegpu/src/core/constant/tgpuConstant.ts b/packages/typegpu/src/core/constant/tgpuConstant.ts index 9d561ec140..331fd45e05 100644 --- a/packages/typegpu/src/core/constant/tgpuConstant.ts +++ b/packages/typegpu/src/core/constant/tgpuConstant.ts @@ -71,7 +71,7 @@ class TgpuConstImpl // Why not a ref? // 1. On the WGSL side, we cannot take pointers to constants. // 2. On the JS side, we copy the constant each time we access it, so we're safe. - return snip(id, this.dataType, /* ref */ undefined); + return snip(id, this.dataType, /* ref */ 'constant'); } toString() { @@ -84,7 +84,7 @@ class TgpuConstImpl return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, dataType, /* ref */ undefined); + return snip(this, dataType, /* ref */ 'constant'); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `const:${getName(this) ?? ''}.$`, diff --git a/packages/typegpu/src/core/declare/tgpuDeclare.ts b/packages/typegpu/src/core/declare/tgpuDeclare.ts index e364ab0c70..aaa40c6f0e 100644 --- a/packages/typegpu/src/core/declare/tgpuDeclare.ts +++ b/packages/typegpu/src/core/declare/tgpuDeclare.ts @@ -60,7 +60,7 @@ class TgpuDeclareImpl implements TgpuDeclare, SelfResolvable { ); ctx.addDeclaration(replacedDeclaration); - return snip('', Void, /* ref */ undefined); + return snip('', Void, /* ref */ 'constant'); } toString() { diff --git a/packages/typegpu/src/core/function/dualImpl.ts b/packages/typegpu/src/core/function/dualImpl.ts index dfb048d014..87908af5b6 100644 --- a/packages/typegpu/src/core/function/dualImpl.ts +++ b/packages/typegpu/src/core/function/dualImpl.ts @@ -10,6 +10,7 @@ import { setName } from '../../shared/meta.ts'; import { $internal } from '../../shared/symbols.ts'; import { tryConvertSnippet } from '../../tgsl/conversion.ts'; import type { AnyData } from '../../data/dataTypes.ts'; +import { isNaturallyRef } from '../../data/wgslTypes.ts'; export function createDualImpl unknown>( jsImpl: T, @@ -82,16 +83,16 @@ export function dualImpl unknown>( return snip( options.normalImpl(...converted.map((s) => s.value) as never[]), returnType, - // Why no ref? Functions give up ownership of their return value - /* ref */ undefined, + // Functions give up ownership of their return value + /* ref */ 'constant', ); } return snip( options.codegenImpl(...converted), returnType, - // Why no ref? Functions give up ownership of their return value - /* ref */ undefined, + // Functions give up ownership of their return value + /* ref */ 'runtime', ); }; diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index b1d1f51ede..aaa96abaf2 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -136,7 +136,7 @@ export function createFnCore( } ctx.addDeclaration(`${fnAttribute}fn ${id}${header}${body}`); - return snip(id, returnType, /* ref */ undefined); + return snip(id, returnType, /* ref */ 'runtime'); } // get data generated by the plugin @@ -196,7 +196,7 @@ export function createFnCore( // of the argument based on the argument's referentiality. // In other words, if we pass a reference to a function, it's typed as a pointer, // otherwise it's typed as a value. - const ref = isPtr(argType) ? 'function' : undefined; + const ref = isPtr(argType) ? 'function' : 'runtime'; switch (astParam?.type) { case FuncParameterType.identifier: { @@ -242,7 +242,7 @@ export function createFnCore( }`, ); - return snip(id, actualReturnType, /* ref */ undefined); + return snip(id, actualReturnType, /* ref */ 'runtime'); }, }; diff --git a/packages/typegpu/src/core/function/tgpuFn.ts b/packages/typegpu/src/core/function/tgpuFn.ts index eb7d88d053..9bba86e898 100644 --- a/packages/typegpu/src/core/function/tgpuFn.ts +++ b/packages/typegpu/src/core/function/tgpuFn.ts @@ -307,7 +307,7 @@ function createBoundFunction( (...args) => innerFn(...args), (...args) => // Why no ref? Functions give up ownership of their return value - snip(new FnCall(fn, args), innerFn.shell.returnType, /* ref */ undefined), + snip(new FnCall(fn, args), innerFn.shell.returnType, /* ref */ 'runtime'), 'tgpuFnCall', innerFn.shell.argTypes, ); @@ -347,7 +347,7 @@ class FnCall implements SelfResolvable { this, this.#fn.shell.returnType, // Why no ref? Functions give up ownership of their return value (so ref is false) - /* ref */ undefined, + /* ref */ 'runtime', ); } @@ -359,7 +359,7 @@ class FnCall implements SelfResolvable { stitch`${ctx.resolve(this.#fn).value}(${this.#params})`, this.#fn.shell.returnType, // Why no ref? Functions give up ownership of their return value (so ref is false) - /* ref */ undefined, + /* ref */ 'runtime', ); }); } diff --git a/packages/typegpu/src/core/pipeline/computePipeline.ts b/packages/typegpu/src/core/pipeline/computePipeline.ts index c7a4483969..8448670f55 100644 --- a/packages/typegpu/src/core/pipeline/computePipeline.ts +++ b/packages/typegpu/src/core/pipeline/computePipeline.ts @@ -226,7 +226,7 @@ class ComputePipelineCore implements SelfResolvable { [$resolve](ctx: ResolutionCtx) { return ctx.withSlots(this._slotBindings, () => { ctx.resolve(this._entryFn); - return snip('', Void, /* ref */ undefined); + return snip('', Void, /* ref */ 'runtime'); }); } diff --git a/packages/typegpu/src/core/pipeline/renderPipeline.ts b/packages/typegpu/src/core/pipeline/renderPipeline.ts index 26e21dd267..da392cf02f 100644 --- a/packages/typegpu/src/core/pipeline/renderPipeline.ts +++ b/packages/typegpu/src/core/pipeline/renderPipeline.ts @@ -674,7 +674,7 @@ class RenderPipelineCore implements SelfResolvable { if (fragmentFn) { ctx.resolve(fragmentFn); } - return snip('', Void, /* ref */ undefined); + return snip('', Void, /* ref */ 'runtime'); }), ); } diff --git a/packages/typegpu/src/core/resolve/tgpuResolve.ts b/packages/typegpu/src/core/resolve/tgpuResolve.ts index c02d1d0716..89c991deb4 100644 --- a/packages/typegpu/src/core/resolve/tgpuResolve.ts +++ b/packages/typegpu/src/core/resolve/tgpuResolve.ts @@ -107,7 +107,7 @@ export function resolveWithContext( return snip( replaceExternalsInWgsl(ctx, dependencies, template ?? ''), Void, - /* ref */ undefined, + /* ref */ 'runtime', ); }, diff --git a/packages/typegpu/src/core/slot/accessor.ts b/packages/typegpu/src/core/slot/accessor.ts index b93b066f50..ae0d0bd4b8 100644 --- a/packages/typegpu/src/core/slot/accessor.ts +++ b/packages/typegpu/src/core/slot/accessor.ts @@ -111,7 +111,7 @@ export class TgpuAccessorImpl // Doing a deep copy each time so that we don't have to deal with refs return schemaCallWrapper( this.schema, - snip(value, this.schema, /* ref */ undefined), + snip(value, this.schema, /* ref */ 'constant'), ); } diff --git a/packages/typegpu/src/core/variable/tgpuVariable.ts b/packages/typegpu/src/core/variable/tgpuVariable.ts index 68474c84d2..12eec3ed23 100644 --- a/packages/typegpu/src/core/variable/tgpuVariable.ts +++ b/packages/typegpu/src/core/variable/tgpuVariable.ts @@ -107,7 +107,7 @@ class TgpuVarImpl return snip( id, this.#dataType, - /* ref */ isNaturallyRef(this.#dataType) ? this.#scope : undefined, + /* ref */ isNaturallyRef(this.#dataType) ? this.#scope : 'runtime', ); } @@ -122,7 +122,7 @@ class TgpuVarImpl get [$gpuValueOf](): InferGPU { const dataType = this.#dataType; - const ref = isNaturallyRef(dataType) ? this.#scope : undefined; + const ref = isNaturallyRef(dataType) ? this.#scope : 'runtime'; return new Proxy({ [$internal]: true, diff --git a/packages/typegpu/src/data/array.ts b/packages/typegpu/src/data/array.ts index eebad4110f..ff6430bf31 100644 --- a/packages/typegpu/src/data/array.ts +++ b/packages/typegpu/src/data/array.ts @@ -53,7 +53,7 @@ export const arrayOf = createDualImpl( // Marking so the WGSL generator lets this function through partial[$internal] = true; - return snip(partial, UnknownData, /* ref*/ undefined); + return snip(partial, UnknownData, /* ref*/ 'runtime'); } if (typeof elementCount.value !== 'number') { @@ -65,7 +65,7 @@ export const arrayOf = createDualImpl( return snip( cpu_arrayOf(elementType.value as AnyWgslData, elementCount.value), elementType.value as AnyWgslData, - /* ref */ undefined, + /* ref */ 'runtime', ); }, 'arrayOf', diff --git a/packages/typegpu/src/data/disarray.ts b/packages/typegpu/src/data/disarray.ts index e69678fd95..cfd0b3444d 100644 --- a/packages/typegpu/src/data/disarray.ts +++ b/packages/typegpu/src/data/disarray.ts @@ -59,8 +59,7 @@ export const disarrayOf = createDualImpl( // Marking so the WGSL generator lets this function through partial[$internal] = true; - // Why ref? It's a function. - return snip(partial, UnknownData, /* ref */ undefined); + return snip(partial, UnknownData, /* ref */ 'runtime'); } if (typeof elementCount.value !== 'number') { @@ -72,7 +71,7 @@ export const disarrayOf = createDualImpl( return snip( cpu_disarrayOf(elementType.value as AnyWgslData, elementCount.value), elementType.value as AnyWgslData, - /* ref */ undefined, + /* ref */ 'runtime', ); }, 'disarrayOf', diff --git a/packages/typegpu/src/data/matrix.ts b/packages/typegpu/src/data/matrix.ts index 8c83bfe828..7603316694 100644 --- a/packages/typegpu/src/data/matrix.ts +++ b/packages/typegpu/src/data/matrix.ts @@ -96,7 +96,7 @@ function createMatSchema< snip( stitch`${options.type}(${args})`, schema as unknown as AnyData, - /* ref */ undefined, + /* ref */ 'runtime', ), options.type, ); @@ -182,7 +182,7 @@ abstract class mat2x2Impl extends MatBase .join(', ') })`, mat2x2f, - /* ref */ undefined, + /* ref */ 'runtime', ); } @@ -332,7 +332,7 @@ abstract class mat3x3Impl extends MatBase this[5] }, ${this[6]}, ${this[8]}, ${this[9]}, ${this[10]})`, mat3x3f, - /* ref */ undefined, + /* ref */ 'runtime', ); } @@ -531,7 +531,7 @@ abstract class mat4x4Impl extends MatBase .join(', ') })`, mat4x4f, - /* ref */ undefined, + /* ref */ 'runtime', ); } @@ -560,7 +560,7 @@ export const identity2 = createDualImpl( // CPU implementation () => mat2x2f(1, 0, 0, 1), // CODEGEN implementation - () => snip('mat2x2f(1, 0, 0, 1)', mat2x2f, /* ref */ undefined), + () => snip('mat2x2f(1, 0, 0, 1)', mat2x2f, /* ref */ 'runtime'), 'identity2', ); @@ -573,7 +573,7 @@ export const identity3 = createDualImpl( () => mat3x3f(1, 0, 0, 0, 1, 0, 0, 0, 1), // CODEGEN implementation () => - snip('mat3x3f(1, 0, 0, 0, 1, 0, 0, 0, 1)', mat3x3f, /* ref */ undefined), + snip('mat3x3f(1, 0, 0, 0, 1, 0, 0, 0, 1)', mat3x3f, /* ref */ 'runtime'), 'identity3', ); @@ -589,7 +589,7 @@ export const identity4 = createDualImpl( snip( 'mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)', mat4x4f, - /* ref */ undefined, + /* ref */ 'runtime', ), 'identity4', ); @@ -620,7 +620,7 @@ export const translation4 = createDualImpl( snip( stitch`mat4x4f(1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, ${v}.x, ${v}.y, ${v}.z, 1)`, mat4x4f, - /* ref */ undefined, + /* ref */ 'runtime', ), 'translation4', ); @@ -645,7 +645,7 @@ export const scaling4 = createDualImpl( snip( stitch`mat4x4f(${v}.x, 0, 0, 0, 0, ${v}.y, 0, 0, 0, 0, ${v}.z, 0, 0, 0, 0, 1)`, mat4x4f, - /* ref */ undefined, + /* ref */ 'runtime', ), 'scaling4', ); @@ -670,7 +670,7 @@ export const rotationX4 = createDualImpl( snip( stitch`mat4x4f(1, 0, 0, 0, 0, cos(${a}), sin(${a}), 0, 0, -sin(${a}), cos(${a}), 0, 0, 0, 0, 1)`, mat4x4f, - /* ref */ undefined, + /* ref */ 'runtime', ), 'rotationX4', ); @@ -695,7 +695,7 @@ export const rotationY4 = createDualImpl( snip( stitch`mat4x4f(cos(${a}), 0, -sin(${a}), 0, 0, 1, 0, 0, sin(${a}), 0, cos(${a}), 0, 0, 0, 0, 1)`, mat4x4f, - /* ref */ undefined, + /* ref */ 'runtime', ), 'rotationY4', ); @@ -720,7 +720,7 @@ export const rotationZ4 = createDualImpl( snip( stitch`mat4x4f(cos(${a}), sin(${a}), 0, 0, -sin(${a}), cos(${a}), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)`, mat4x4f, - /* ref */ undefined, + /* ref */ 'runtime', ), 'rotationZ4', ); diff --git a/packages/typegpu/src/data/snippet.ts b/packages/typegpu/src/data/snippet.ts index 4493fa8f7c..496be4e293 100644 --- a/packages/typegpu/src/data/snippet.ts +++ b/packages/typegpu/src/data/snippet.ts @@ -3,7 +3,11 @@ import type { AnyData, UnknownData } from './dataTypes.ts'; import { DEV } from '../shared/env.ts'; import { type AddressSpace, isNumericSchema } from './wgslTypes.ts'; -type RefSpace = AddressSpace | 'this-function' | undefined; +export type RefSpace = AddressSpace | 'this-function' | 'runtime' | 'constant'; + +export function isRef(snippet: Snippet) { + return snippet.ref !== 'runtime' && snippet.ref !== 'constant'; +} export interface Snippet { readonly value: unknown; diff --git a/packages/typegpu/src/data/vectorImpl.ts b/packages/typegpu/src/data/vectorImpl.ts index fefd4bef26..266e86fd43 100644 --- a/packages/typegpu/src/data/vectorImpl.ts +++ b/packages/typegpu/src/data/vectorImpl.ts @@ -41,12 +41,12 @@ export abstract class VecBase extends Array implements SelfResolvable { [$resolve](): ResolvedSnippet { const schema = this[$internal].elementSchema; if (this.every((e) => !e)) { - return snip(`${this.kind}()`, schema, /* ref */ undefined); + return snip(`${this.kind}()`, schema, /* ref */ 'runtime'); } if (this.every((e) => this[0] === e)) { - return snip(`${this.kind}(${this[0]})`, schema, /* ref */ undefined); + return snip(`${this.kind}(${this[0]})`, schema, /* ref */ 'runtime'); } - return snip(`${this.kind}(${this.join(', ')})`, schema, /* ref */ undefined); + return snip(`${this.kind}(${this.join(', ')})`, schema, /* ref */ 'runtime'); } toString() { diff --git a/packages/typegpu/src/resolutionCtx.ts b/packages/typegpu/src/resolutionCtx.ts index 600d0c47e9..88d68f0fbf 100644 --- a/packages/typegpu/src/resolutionCtx.ts +++ b/packages/typegpu/src/resolutionCtx.ts @@ -657,7 +657,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { let result: ResolvedSnippet; if (isData(item)) { // Ref is arbitrary, as we're resolving a schema - result = snip(resolveData(this, item), Void, /* ref */ undefined); + result = snip(resolveData(this, item), Void, /* ref */ 'runtime'); } else if (isDerived(item) || isSlot(item)) { result = this.resolve(this.unwrap(item)); } else if (isSelfResolvable(item)) { @@ -722,7 +722,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { return snip( `${[...this._declarations].join('\n\n')}${result.value}`, Void, - /* ref */ undefined, // arbitrary + /* ref */ 'runtime', // arbitrary ); } finally { this.popMode('codegen'); @@ -748,13 +748,13 @@ export class ResolutionCtxImpl implements ResolutionCtx { ); if (reinterpretedType.type === 'abstractInt') { - return snip(`${item}`, realSchema, /* ref */ undefined); + return snip(`${item}`, realSchema, /* ref */ 'constant'); } if (reinterpretedType.type === 'u32') { - return snip(`${item}u`, realSchema, /* ref */ undefined); + return snip(`${item}u`, realSchema, /* ref */ 'constant'); } if (reinterpretedType.type === 'i32') { - return snip(`${item}i`, realSchema, /* ref */ undefined); + return snip(`${item}i`, realSchema, /* ref */ 'constant'); } const exp = item.toExponential(); @@ -766,21 +766,21 @@ export class ResolutionCtxImpl implements ResolutionCtx { // Just picking the shorter one const base = exp.length < decimal.length ? exp : decimal; if (reinterpretedType.type === 'f32') { - return snip(`${base}f`, realSchema, /* ref */ undefined); + return snip(`${base}f`, realSchema, /* ref */ 'constant'); } if (reinterpretedType.type === 'f16') { - return snip(`${base}h`, realSchema, /* ref */ undefined); + return snip(`${base}h`, realSchema, /* ref */ 'constant'); } - return snip(base, realSchema, /* ref */ undefined); + return snip(base, realSchema, /* ref */ 'constant'); } if (typeof item === 'boolean') { - return snip(item ? 'true' : 'false', bool, /* ref */ undefined); + return snip(item ? 'true' : 'false', bool, /* ref */ 'constant'); } if (typeof item === 'string') { // Already resolved - return snip(item, Void, /* ref */ undefined); + return snip(item, Void, /* ref */ 'runtime'); } if (schema && isWgslArray(schema)) { @@ -803,12 +803,12 @@ export class ResolutionCtxImpl implements ResolutionCtx { snip( element, schema.elementType as AnyData, - /* ref */ undefined, + /* ref */ 'runtime', ) ) })`, schema, - /* ref */ undefined, + /* ref */ 'runtime', ); } @@ -816,7 +816,7 @@ export class ResolutionCtxImpl implements ResolutionCtx { return snip( stitch`array(${item.map((element) => this.resolve(element))})`, UnknownData, - /* ref */ undefined, + /* ref */ 'runtime', ) as ResolvedSnippet; } @@ -827,12 +827,12 @@ export class ResolutionCtxImpl implements ResolutionCtx { snip( (item as Infer)[key], propType as AnyData, - /* ref */ undefined, + /* ref */ 'runtime', ) ) })`, schema, - /* ref */ undefined, // a new struct, not referenced from anywhere + /* ref */ 'runtime', // a new struct, not referenced from anywhere ); } diff --git a/packages/typegpu/src/std/atomic.ts b/packages/typegpu/src/std/atomic.ts index dd3ca51077..f22f5dd587 100644 --- a/packages/typegpu/src/std/atomic.ts +++ b/packages/typegpu/src/std/atomic.ts @@ -16,7 +16,7 @@ export const workgroupBarrier = createDualImpl( // CPU implementation () => console.warn('workgroupBarrier is a no-op outside of CODEGEN mode.'), // CODEGEN implementation - () => snip('workgroupBarrier()', Void, /* ref */ undefined), + () => snip('workgroupBarrier()', Void, /* ref */ 'runtime'), 'workgroupBarrier', ); @@ -24,7 +24,7 @@ export const storageBarrier = createDualImpl( // CPU implementation () => console.warn('storageBarrier is a no-op outside of CODEGEN mode.'), // CODEGEN implementation - () => snip('storageBarrier()', Void, /* ref */ undefined), + () => snip('storageBarrier()', Void, /* ref */ 'runtime'), 'storageBarrier', ); @@ -32,7 +32,7 @@ export const textureBarrier = createDualImpl( // CPU implementation () => console.warn('textureBarrier is a no-op outside of CODEGEN mode.'), // CODEGEN implementation - () => snip('textureBarrier()', Void, /* ref */ undefined), + () => snip('textureBarrier()', Void, /* ref */ 'runtime'), 'textureBarrier', ); @@ -49,7 +49,7 @@ export const atomicLoad = createDualImpl( return snip( stitch`atomicLoad(&${a})`, a.dataType.inner, - /* ref */ undefined, + /* ref */ 'runtime', ); } throw new Error( @@ -76,7 +76,7 @@ export const atomicStore = createDualImpl( return snip( stitch`atomicStore(&${a}, ${value})`, Void, - /* ref */ undefined, + /* ref */ 'runtime', ); }, 'atomicStore', @@ -102,7 +102,7 @@ export const atomicAdd = createDualImpl( return snip( stitch`atomicAdd(&${a}, ${value})`, a.dataType.inner, - /* ref */ undefined, + /* ref */ 'runtime', ); } throw new Error( @@ -126,7 +126,7 @@ export const atomicSub = createDualImpl( return snip( stitch`atomicSub(&${a}, ${value})`, a.dataType.inner, - /* ref */ undefined, + /* ref */ 'runtime', ); } throw new Error( @@ -150,7 +150,7 @@ export const atomicMax = createDualImpl( return snip( stitch`atomicMax(&${a}, ${value})`, a.dataType.inner, - /* ref */ undefined, + /* ref */ 'runtime', ); } throw new Error( @@ -174,7 +174,7 @@ export const atomicMin = createDualImpl( return snip( stitch`atomicMin(&${a}, ${value})`, a.dataType.inner, - /* ref */ undefined, + /* ref */ 'runtime', ); } throw new Error( @@ -198,7 +198,7 @@ export const atomicAnd = createDualImpl( return snip( stitch`atomicAnd(&${a}, ${value})`, a.dataType.inner, - /* ref */ undefined, + /* ref */ 'runtime', ); } throw new Error( @@ -222,7 +222,7 @@ export const atomicOr = createDualImpl( return snip( stitch`atomicOr(&${a}, ${value})`, a.dataType.inner, - /* ref */ undefined, + /* ref */ 'runtime', ); } throw new Error( @@ -246,7 +246,7 @@ export const atomicXor = createDualImpl( return snip( stitch`atomicXor(&${a}, ${value})`, a.dataType.inner, - /* ref */ undefined, + /* ref */ 'runtime', ); } throw new Error( diff --git a/packages/typegpu/src/std/boolean.ts b/packages/typegpu/src/std/boolean.ts index 429664a657..5a22168e0d 100644 --- a/packages/typegpu/src/std/boolean.ts +++ b/packages/typegpu/src/std/boolean.ts @@ -283,7 +283,11 @@ export const isCloseTo = dualImpl({ return false; }, // GPU implementation - codegenImpl: (lhs, rhs, precision = snip(0.01, f32, /* ref */ undefined)) => { + codegenImpl: ( + lhs, + rhs, + precision = snip(0.01, f32, /* ref */ 'constant'), + ) => { if (isSnippetNumeric(lhs) && isSnippetNumeric(rhs)) { return stitch`(abs(f32(${lhs}) - f32(${rhs})) <= ${precision})`; } diff --git a/packages/typegpu/src/std/derivative.ts b/packages/typegpu/src/std/derivative.ts index 6ffd45a64e..97d1bfaa6b 100644 --- a/packages/typegpu/src/std/derivative.ts +++ b/packages/typegpu/src/std/derivative.ts @@ -11,7 +11,7 @@ function cpuDpdx(value: T): T { export const dpdx = createDualImpl( cpuDpdx, - (value) => snip(stitch`dpdx(${value})`, value.dataType, /* ref */ undefined), + (value) => snip(stitch`dpdx(${value})`, value.dataType, /* ref */ 'runtime'), 'dpdx', ); @@ -26,7 +26,7 @@ function cpuDpdxCoarse( export const dpdxCoarse = createDualImpl( cpuDpdxCoarse, (value) => - snip(stitch`dpdxCoarse(${value})`, value.dataType, /* ref */ undefined), + snip(stitch`dpdxCoarse(${value})`, value.dataType, /* ref */ 'runtime'), 'dpdxCoarse', ); @@ -39,7 +39,7 @@ function cpuDpdxFine(value: T): T { export const dpdxFine = createDualImpl( cpuDpdxFine, (value) => - snip(stitch`dpdxFine(${value})`, value.dataType, /* ref */ undefined), + snip(stitch`dpdxFine(${value})`, value.dataType, /* ref */ 'runtime'), 'dpdxFine', ); @@ -51,7 +51,7 @@ function cpuDpdy(value: T): T { export const dpdy = createDualImpl( cpuDpdy, - (value) => snip(stitch`dpdy(${value})`, value.dataType, /* ref */ undefined), + (value) => snip(stitch`dpdy(${value})`, value.dataType, /* ref */ 'runtime'), 'dpdy', ); @@ -66,7 +66,7 @@ function cpuDpdyCoarse( export const dpdyCoarse = createDualImpl( cpuDpdyCoarse, (value) => - snip(stitch`dpdyCoarse(${value})`, value.dataType, /* ref */ undefined), + snip(stitch`dpdyCoarse(${value})`, value.dataType, /* ref */ 'runtime'), 'dpdyCoarse', ); @@ -79,7 +79,7 @@ function cpuDpdyFine(value: T): T { export const dpdyFine = createDualImpl( cpuDpdyFine, (value) => - snip(stitch`dpdyFine(${value})`, value.dataType, /* ref */ undefined), + snip(stitch`dpdyFine(${value})`, value.dataType, /* ref */ 'runtime'), 'dpdyFine', ); @@ -92,7 +92,7 @@ function cpuFwidth(value: T): T { export const fwidth = createDualImpl( cpuFwidth, (value) => - snip(stitch`fwidth(${value})`, value.dataType, /* ref */ undefined), + snip(stitch`fwidth(${value})`, value.dataType, /* ref */ 'runtime'), 'fwidth', ); @@ -107,7 +107,7 @@ function cpuFwidthCoarse( export const fwidthCoarse = createDualImpl( cpuFwidthCoarse, (value) => - snip(stitch`fwidthCoarse(${value})`, value.dataType, /* ref */ undefined), + snip(stitch`fwidthCoarse(${value})`, value.dataType, /* ref */ 'runtime'), 'fwidthCoarse', ); @@ -122,6 +122,6 @@ function cpuFwidthFine( export const fwidthFine = createDualImpl( cpuFwidthFine, (value) => - snip(stitch`fwidthFine(${value})`, value.dataType, /* ref */ undefined), + snip(stitch`fwidthFine(${value})`, value.dataType, /* ref */ 'runtime'), 'fwidthFine', ); diff --git a/packages/typegpu/src/std/discard.ts b/packages/typegpu/src/std/discard.ts index 4084f9ac55..2d5e25bccd 100644 --- a/packages/typegpu/src/std/discard.ts +++ b/packages/typegpu/src/std/discard.ts @@ -10,6 +10,6 @@ export const discard = createDualImpl( ); }, // GPU - () => snip('discard;', Void, /* ref */ undefined), + () => snip('discard;', Void, /* ref */ 'runtime'), 'discard', ); diff --git a/packages/typegpu/src/std/extensions.ts b/packages/typegpu/src/std/extensions.ts index caccb98eea..d42c616fc2 100644 --- a/packages/typegpu/src/std/extensions.ts +++ b/packages/typegpu/src/std/extensions.ts @@ -28,7 +28,7 @@ export const extensionEnabled: DualFn< `extensionEnabled has to be called with a string literal representing a valid WGSL extension name. Got: ${value}`, ); } - return snip(jsImpl(value as WgslExtension), bool, /* ref */ undefined); + return snip(jsImpl(value as WgslExtension), bool, /* ref */ 'constant'); }; const impl = (extensionName: WgslExtension) => { diff --git a/packages/typegpu/src/std/matrix.ts b/packages/typegpu/src/std/matrix.ts index bc3a30e5c6..ef643521bd 100644 --- a/packages/typegpu/src/std/matrix.ts +++ b/packages/typegpu/src/std/matrix.ts @@ -43,7 +43,7 @@ export const translate4 = createDualImpl( snip( stitch`(${gpuTranslation4(vector)} * ${matrix})`, matrix.dataType, - /* ref */ undefined, + /* ref */ 'runtime', ), 'translate4', ); @@ -62,7 +62,7 @@ export const scale4 = createDualImpl( snip( stitch`(${(gpuScaling4(vector))} * ${matrix})`, matrix.dataType, - /* ref */ undefined, + /* ref */ 'runtime', ), 'scale4', ); @@ -81,7 +81,7 @@ export const rotateX4 = createDualImpl( snip( stitch`(${(gpuRotationX4(angle))} * ${matrix})`, matrix.dataType, - /* ref */ undefined, + /* ref */ 'runtime', ), 'rotateX4', ); @@ -100,7 +100,7 @@ export const rotateY4 = createDualImpl( snip( stitch`(${(gpuRotationY4(angle))} * ${matrix})`, matrix.dataType, - /* ref */ undefined, + /* ref */ 'runtime', ), 'rotateY4', ); @@ -119,7 +119,7 @@ export const rotateZ4 = createDualImpl( snip( stitch`(${(gpuRotationZ4(angle))} * ${matrix})`, matrix.dataType, - /* ref */ undefined, + /* ref */ 'runtime', ), 'rotateZ4', ); diff --git a/packages/typegpu/src/std/numeric.ts b/packages/typegpu/src/std/numeric.ts index de7b522adc..68714a74ab 100644 --- a/packages/typegpu/src/std/numeric.ts +++ b/packages/typegpu/src/std/numeric.ts @@ -610,7 +610,7 @@ export const frexp: FrexpOverload = createDualImpl( ); } - return snip(stitch`frexp(${value})`, returnType, /* ref */ undefined); + return snip(stitch`frexp(${value})`, returnType, /* ref */ 'runtime'); }, 'frexp', ); @@ -982,7 +982,7 @@ export const refract = createDualImpl( snip( stitch`refract(${e1}, ${e2}, ${e3})`, e1.dataType, - /* ref */ undefined, + /* ref */ 'runtime', ), 'refract', (e1, e2, e3) => [ diff --git a/packages/typegpu/src/std/packing.ts b/packages/typegpu/src/std/packing.ts index bdf7cec6cb..6f7a88e9d8 100644 --- a/packages/typegpu/src/std/packing.ts +++ b/packages/typegpu/src/std/packing.ts @@ -20,7 +20,7 @@ export const unpack2x16float = createDualImpl( return vec2f(reader.readFloat16(), reader.readFloat16()); }, // GPU implementation - (e) => snip(stitch`unpack2x16float(${e})`, vec2f, /* ref */ undefined), + (e) => snip(stitch`unpack2x16float(${e})`, vec2f, /* ref */ 'runtime'), 'unpack2x16float', ); @@ -39,7 +39,7 @@ export const pack2x16float = createDualImpl( return u32(reader.readUint32()); }, // GPU implementation - (e) => snip(stitch`pack2x16float(${e})`, u32, /* ref */ undefined), + (e) => snip(stitch`pack2x16float(${e})`, u32, /* ref */ 'runtime'), 'pack2x16float', ); @@ -62,7 +62,7 @@ export const unpack4x8unorm = createDualImpl( ); }, // GPU implementation - (e) => snip(stitch`unpack4x8unorm(${e})`, vec4f, /* ref */ undefined), + (e) => snip(stitch`unpack4x8unorm(${e})`, vec4f, /* ref */ 'runtime'), 'unpack4x8unorm', ); @@ -83,6 +83,6 @@ export const pack4x8unorm = createDualImpl( return u32(reader.readUint32()); }, // GPU implementation - (e) => snip(stitch`pack4x8unorm(${e})`, u32, /* ref */ undefined), + (e) => snip(stitch`pack4x8unorm(${e})`, u32, /* ref */ 'runtime'), 'pack4x8unorm', ); diff --git a/packages/typegpu/src/std/texture.ts b/packages/typegpu/src/std/texture.ts index bffc64a82e..dd258ee51d 100644 --- a/packages/typegpu/src/std/texture.ts +++ b/packages/typegpu/src/std/texture.ts @@ -127,7 +127,7 @@ export const textureSample: TextureSampleOverload = createDualImpl( ); }, // CODEGEN implementation - (...args) => snip(stitch`textureSample(${args})`, vec4f, /* ref */ undefined), + (...args) => snip(stitch`textureSample(${args})`, vec4f, /* ref */ 'runtime'), 'textureSample', ); @@ -198,7 +198,7 @@ export const textureSampleBias: TextureSampleBiasOverload = createDualImpl( }, // CODEGEN implementation (...args) => - snip(stitch`textureSampleBias(${args})`, vec4f, /* ref */ undefined), + snip(stitch`textureSampleBias(${args})`, vec4f, /* ref */ 'runtime'), 'textureSampleBias', ); @@ -249,7 +249,7 @@ export const textureSampleLevel: TextureSampleLevelOverload = createDualImpl( }, // CODEGEN implementation (...args) => - snip(stitch`textureSampleLevel(${args})`, vec4f, /* ref */ undefined), + snip(stitch`textureSampleLevel(${args})`, vec4f, /* ref */ 'runtime'), 'textureSampleLevel', ); @@ -338,7 +338,7 @@ export const textureLoad: TextureLoadOverload = createDualImpl( 'texelDataType' in textureInfo ? textureInfo.texelDataType : channelDataToInstance[textureInfo.channelDataType.type], - /* ref */ undefined, + /* ref */ 'runtime', ); }, 'textureLoad', @@ -381,7 +381,7 @@ export const textureStore: TextureStoreOverload = createDualImpl( ); }, // CODEGEN implementation - (...args) => snip(stitch`textureStore(${args})`, Void, /* ref */ undefined), + (...args) => snip(stitch`textureStore(${args})`, Void, /* ref */ 'runtime'), 'textureStore', ); @@ -441,7 +441,7 @@ export const textureDimensions: TextureDimensionsOverload = createDualImpl( return snip( stitch`textureDimensions(${[texture, level]})`, dim === '1d' ? u32 : dim === '3d' ? vec3u : vec2u, - /* ref */ undefined, + /* ref */ 'runtime', ); }, 'textureDimensions', @@ -508,7 +508,7 @@ export const textureSampleCompare: TextureSampleCompareOverload = }, // CODEGEN implementation (...args) => - snip(stitch`textureSampleCompare(${args})`, f32, /* ref */ undefined), + snip(stitch`textureSampleCompare(${args})`, f32, /* ref */ 'runtime'), 'textureSampleCompare', ); @@ -535,7 +535,7 @@ export const textureSampleBaseClampToEdge: TextureSampleBaseClampToEdge = snip( stitch`textureSampleBaseClampToEdge(${args})`, vec4f, - /* ref */ undefined, + /* ref */ 'runtime', ), 'textureSampleBaseClampToEdge', ); diff --git a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts index 3a5336901e..283243e855 100644 --- a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts +++ b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts @@ -41,7 +41,7 @@ export class LogGeneratorNullImpl implements LogGenerator { console.warn( "'console.log' is currently only supported in compute pipelines.", ); - return snip('/* console.log() */', Void, /* ref */ undefined); + return snip('/* console.log() */', Void, /* ref */ 'runtime'); } } @@ -104,7 +104,7 @@ export class LogGeneratorImpl implements LogGenerator { return snip( stitch`${ctx.resolve(logFn).value}(${nonStringArgs})`, Void, - /* ref */ undefined, + /* ref */ 'runtime', ); } diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 887b930d71..de84391187 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -17,7 +17,7 @@ import { i32, u32, } from '../data/numeric.ts'; -import { isSnippet, snip, type Snippet } from '../data/snippet.ts'; +import { isRef, isSnippet, snip, type Snippet } from '../data/snippet.ts'; import { vec2b, vec2f, @@ -48,7 +48,6 @@ import { isWgslArray, isWgslStruct, } from '../data/wgslTypes.ts'; -import { getResolutionCtx } from '../execMode.ts'; import { getOwnSnippet, isKnownAtComptime, @@ -171,14 +170,14 @@ export function accessProp( return snip( stitch`arrayLength(&${target})`, u32, - /* ref */ undefined, + /* ref */ 'runtime', ); } return snip( - String(target.dataType.elementCount), + target.dataType.elementCount, abstractInt, - /* ref */ undefined, + /* ref */ 'constant', ); } @@ -200,9 +199,11 @@ export function accessProp( return snip( stitch`${target}.${propName}`, propType, - /* ref */ target.ref !== undefined && isNaturallyRef(propType) + /* ref */ isRef(target) && isNaturallyRef(propType) ? target.ref - : undefined, + : target.ref === 'constant' + ? 'constant' + : 'runtime', ); } @@ -233,7 +234,7 @@ export function accessProp( : stitch`${target}.${propName}`, swizzleType, // Swizzling creates new vectors (unless they're on the lhs of an assignment, but that's not yet supported in WGSL) - /* ref */ undefined, + /* ref */ target.ref === 'constant' ? 'constant' : 'runtime', ); } @@ -262,12 +263,14 @@ export function accessIndex( return snip( isKnownAtComptime(target) && isKnownAtComptime(index) // biome-ignore lint/suspicious/noExplicitAny: it's fine, it's there - ? (target.value as any)[index.value as any] + ? (target.value as any)[index.value as number] : stitch`${target}[${index}]`, elementType, - /* ref */ target.ref !== undefined && isNaturallyRef(elementType) + /* ref */ isRef(target) && isNaturallyRef(elementType) ? target.ref - : undefined, + : target.ref === 'constant' + ? 'constant' + : 'runtime', ); } @@ -279,7 +282,7 @@ export function accessIndex( ? (target.value as any)[index.value as any] : stitch`${target}[${index}]`, target.dataType.primitive, - /* ref */ undefined, + /* ref */ target.ref === 'constant' ? 'constant' : 'runtime', ); } @@ -326,9 +329,9 @@ export function numericLiteralToSnippet(value: number): Snippet { `The integer ${value} exceeds the safe integer range and may have lost precision.`, ); } - return snip(value, abstractInt, /* ref */ undefined); + return snip(value, abstractInt, /* ref */ 'constant'); } - return snip(value, abstractFloat, /* ref */ undefined); + return snip(value, abstractFloat, /* ref */ 'constant'); } export function concretize(type: T): T | F32 | I32 { @@ -397,7 +400,7 @@ export function coerceToSnippet(value: unknown): Snippet { } if (isVecInstance(value) || isMatInstance(value)) { - return snip(value, kindToSchema[value.kind], /* ref */ undefined); + return snip(value, kindToSchema[value.kind], /* ref */ 'constant'); } if ( @@ -406,7 +409,7 @@ export function coerceToSnippet(value: unknown): Snippet { typeof value === 'undefined' || value === null ) { // Nothing representable in WGSL as-is, so unknown - return snip(value, UnknownData, /* ref */ undefined); + return snip(value, UnknownData, /* ref */ 'constant'); } if (typeof value === 'number') { @@ -415,8 +418,8 @@ export function coerceToSnippet(value: unknown): Snippet { if (typeof value === 'boolean') { // It's a primitive, so `ref` is false - return snip(value, bool, /* ref */ undefined); + return snip(value, bool, /* ref */ 'constant'); } - return snip(value, UnknownData, /* ref */ undefined); + return snip(value, UnknownData, /* ref */ 'constant'); } diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index 8703bb003c..f59ae63a8d 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -38,7 +38,11 @@ export class ShelllessRepository { const argTypes = argSnippets.map((s) => { const type = concretize(s.dataType as AnyData); - const addressSpace = s.ref === 'this-function' ? 'function' : s.ref; + const addressSpace = s.ref === 'this-function' + ? 'function' + : s.ref === 'constant' || s.ref === 'runtime' + ? undefined + : s.ref; return addressSpace !== undefined && !isPtr(type) ? INTERNAL_createPtr( diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 514337b290..853aada4dd 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -10,7 +10,13 @@ import { UnknownData, } from '../data/dataTypes.ts'; import { bool, i32, u32 } from '../data/numeric.ts'; -import { isSnippet, snip, type Snippet } from '../data/snippet.ts'; +import { + isRef, + isSnippet, + type RefSpace, + snip, + type Snippet, +} from '../data/snippet.ts'; import * as wgsl from '../data/wgslTypes.ts'; import { invariant, ResolutionError, WgslTypeError } from '../errors.ts'; import { getName } from '../shared/meta.ts'; @@ -139,11 +145,16 @@ ${this.ctx.pre}}`; public blockVariable( id: string, dataType: wgsl.AnyWgslData | UnknownData, + ref: RefSpace, ): Snippet { const snippet = snip( this.ctx.makeNameValid(id), dataType, - /* ref */ wgsl.isNaturallyRef(dataType) ? 'this-function' : undefined, + /* ref */ wgsl.isNaturallyRef(dataType) + ? 'this-function' + : ref === 'constant' + ? 'constant' + : 'runtime', ); this.ctx.defineVariable(id, snippet); return snippet; @@ -189,7 +200,7 @@ ${this.ctx.pre}}`; } if (typeof expression === 'boolean') { - return snip(expression, bool, /* ref */ undefined); + return snip(expression, bool, /* ref */ 'constant'); } if ( @@ -229,7 +240,7 @@ ${this.ctx.pre}}`; const rhsStr = this.ctx.resolve(convRhs.value, convRhs.dataType).value; const type = operatorToType(convLhs.dataType, op, convRhs.dataType); - if (exprType === NODE.assignmentExpr && rhsExpr.ref !== undefined) { + if (exprType === NODE.assignmentExpr && isRef(rhsExpr)) { throw new WgslTypeError( `'${lhsStr} = ${rhsStr}' is invalid, because references cannot be assigned.\n-----\nTry '${lhsStr} = ${ this.ctx.resolve(rhsExpr.dataType).value @@ -243,7 +254,7 @@ ${this.ctx.pre}}`; : `${lhsStr} ${op} ${rhsStr}`, type, // Result of an operation, so not a reference to anything - /* ref */ undefined, + /* ref */ 'runtime', ); } @@ -254,7 +265,7 @@ ${this.ctx.pre}}`; const argStr = this.ctx.resolve(argExpr.value).value; // Result of an operation, so not a reference to anything - return snip(`${argStr}${op}`, argExpr.dataType, /* ref */ undefined); + return snip(`${argStr}${op}`, argExpr.dataType, /* ref */ 'runtime'); } if (expression[0] === NODE.unaryExpr) { @@ -265,7 +276,7 @@ ${this.ctx.pre}}`; const type = operatorToType(argExpr.dataType, op); // Result of an operation, so not a reference to anything - return snip(`${op}${argStr}`, type, /* ref */ undefined); + return snip(`${op}${argStr}`, type, /* ref */ 'runtime'); } if (expression[0] === NODE.memberAccess) { @@ -274,7 +285,7 @@ ${this.ctx.pre}}`; let target = this.expression(targetNode); if (target.value === console) { - return snip(new ConsoleLog(), UnknownData, /* ref */ undefined); + return snip(new ConsoleLog(), UnknownData, /* ref */ 'runtime'); } if (wgsl.isPtr(target.dataType)) { @@ -354,7 +365,7 @@ ${this.ctx.pre}}`; `${this.ctx.resolve(callee.value).value}()`, callee.value, // A new struct, so not a reference - /* ref */ undefined, + /* ref */ 'runtime', ); } @@ -369,7 +380,7 @@ ${this.ctx.pre}}`; this.ctx.resolve(arg.value, callee.value).value, callee.value, // A new struct, so not a reference - /* ref */ undefined, + /* ref */ 'runtime', ); } @@ -401,7 +412,7 @@ ${this.ctx.pre}}`; return snip( stitch`${snippet.value}(${converted})`, snippet.dataType, - /* ref */ undefined, + /* ref */ 'runtime', ); }); } @@ -529,7 +540,7 @@ ${this.ctx.pre}}`; return snip( stitch`${this.ctx.resolve(structType).value}(${convertedSnippets})`, structType, - /* ref */ undefined, + /* ref */ 'runtime', ); } @@ -585,12 +596,12 @@ ${this.ctx.pre}}`; elemType as wgsl.AnyWgslData, values.length, ) as wgsl.AnyWgslData, - /* ref */ undefined, + /* ref */ 'runtime', ); } if (expression[0] === NODE.stringLiteral) { - return snip(expression[1], UnknownData, /* ref */ undefined); + return snip(expression[1], UnknownData, /* ref */ 'runtime'); // arbitrary ref } if (expression[0] === NODE.preUpdate) { @@ -633,7 +644,7 @@ ${this.ctx.pre}}`; if ( !expectedReturnType && - returnSnippet.ref && + isRef(returnSnippet) && returnSnippet.ref !== 'this-function' ) { const str = this.ctx.resolve( @@ -717,7 +728,7 @@ ${this.ctx.pre}else ${alternate}`; let dataType = eq.dataType as wgsl.AnyWgslData; // Assigning a reference to a `const` variable means we store the pointer // of the rhs. - if (eq.ref !== undefined) { + if (isRef(eq)) { // Referential if (stmtType === NODE.let) { const rhsStr = this.ctx.resolve(eq.value).value; @@ -743,7 +754,7 @@ ${this.ctx.pre}else ${alternate}`; if ( stmtType === NODE.const && !wgsl.isNaturallyRef(dataType) && - isKnownAtComptime(eq) + eq.ref === 'constant' ) { varType = 'const'; } @@ -752,6 +763,7 @@ ${this.ctx.pre}else ${alternate}`; const snippet = this.blockVariable( rawId, concretize(dataType), + eq.ref, ); return stitchWithExactTypes`${this.ctx.pre}${varType} ${snippet .value as string} = ${tryConvertSnippet(eq, dataType, false)};`; diff --git a/packages/typegpu/tests/constant.test.ts b/packages/typegpu/tests/constant.test.ts index cedb25f080..d7a9119bb4 100644 --- a/packages/typegpu/tests/constant.test.ts +++ b/packages/typegpu/tests/constant.test.ts @@ -43,7 +43,7 @@ describe('tgpu.const', () => { fn func() { var pos = boid; var vel = boid.vel; - var velX = boid.vel.x; + const velX = boid.vel.x; }" `); }); diff --git a/packages/typegpu/tests/resolve.test.ts b/packages/typegpu/tests/resolve.test.ts index b9c1a5b347..010c55ef61 100644 --- a/packages/typegpu/tests/resolve.test.ts +++ b/packages/typegpu/tests/resolve.test.ts @@ -57,7 +57,7 @@ describe('tgpu resolve', () => { [$gpuValueOf]: { [$internal]: true, get [$ownSnippet]() { - return snip(this, d.f32, /* ref */ undefined); + return snip(this, d.f32, /* ref */ 'runtime'); }, [$resolve]: (ctx: ResolutionCtx) => ctx.resolve(intensity), } as unknown as number, @@ -67,7 +67,7 @@ describe('tgpu resolve', () => { ctx.addDeclaration( `@group(0) @binding(0) var ${name}: f32;`, ); - return snip(name, d.f32, /* ref */ undefined); + return snip(name, d.f32, /* ref */ 'runtime'); }, get value(): number { diff --git a/packages/typegpu/tests/tgsl/conversion.test.ts b/packages/typegpu/tests/tgsl/conversion.test.ts index 5f3217254f..4c45260e74 100644 --- a/packages/typegpu/tests/tgsl/conversion.test.ts +++ b/packages/typegpu/tests/tgsl/conversion.test.ts @@ -185,17 +185,17 @@ describe('getBestConversion', () => { }); describe('convertToCommonType', () => { - const snippetF32 = snip('2.22', d.f32, /* ref */ undefined); - const snippetI32 = snip('-12', d.i32, /* ref */ undefined); - const snippetU32 = snip('33', d.u32, /* ref */ undefined); - const snippetAbsFloat = snip('1.1', abstractFloat, /* ref */ undefined); - const snippetAbsInt = snip('1', abstractInt, /* ref */ undefined); + const snippetF32 = snip('2.22', d.f32, /* ref */ 'runtime'); + const snippetI32 = snip('-12', d.i32, /* ref */ 'runtime'); + const snippetU32 = snip('33', d.u32, /* ref */ 'runtime'); + const snippetAbsFloat = snip('1.1', abstractFloat, /* ref */ 'runtime'); + const snippetAbsInt = snip('1', abstractInt, /* ref */ 'runtime'); const snippetPtrF32 = snip( 'ptr_f32', d.ptrPrivate(d.f32), /* ref */ 'function', ); - const snippetUnknown = snip('?', UnknownData, /* ref */ undefined); + const snippetUnknown = snip('?', UnknownData, /* ref */ 'runtime'); it('converts identical types', () => { const result = convertToCommonType([snippetF32, snippetF32]); @@ -245,7 +245,7 @@ describe('convertToCommonType', () => { }); it('returns undefined for incompatible types', () => { - const snippetVec2f = snip('v2', d.vec2f, /* ref */ undefined); + const snippetVec2f = snip('v2', d.vec2f, /* ref */ 'runtime'); const result = convertToCommonType([snippetF32, snippetVec2f]); expect(result).toBeUndefined(); }); @@ -287,7 +287,7 @@ describe('convertToCommonType', () => { it('handles void gracefully', () => { const result = convertToCommonType([ snippetF32, - snip('void', d.Void, /* ref */ undefined), + snip('void', d.Void, /* ref */ 'runtime'), ]); expect(result).toBeUndefined(); }); @@ -308,10 +308,10 @@ describe('convertStructValues', () => { it('maps values matching types exactly', () => { const snippets: Record = { - a: snip('1.0', d.f32, /* ref */ undefined), - b: snip('2', d.i32, /* ref */ undefined), - c: snip('vec2f(1.0, 1.0)', d.vec2f, /* ref */ undefined), - d: snip('true', d.bool, /* ref */ undefined), + a: snip('1.0', d.f32, /* ref */ 'runtime'), + b: snip('2', d.i32, /* ref */ 'runtime'), + c: snip('vec2f(1.0, 1.0)', d.vec2f, /* ref */ 'runtime'), + d: snip('true', d.bool, /* ref */ 'runtime'), }; const res = convertStructValues(structType, snippets); expect(res.length).toBe(4); @@ -323,25 +323,25 @@ describe('convertStructValues', () => { it('maps values requiring implicit casts and warns', () => { const snippets: Record = { - a: snip('1', d.i32, /* ref */ undefined), // i32 -> f32 (cast) - b: snip('2', d.u32, /* ref */ undefined), // u32 -> i32 (cast) - c: snip('2.22', d.f32, /* ref */ undefined), - d: snip('true', d.bool, /* ref */ undefined), + a: snip('1', d.i32, /* ref */ 'runtime'), // i32 -> f32 (cast) + b: snip('2', d.u32, /* ref */ 'runtime'), // u32 -> i32 (cast) + c: snip('2.22', d.f32, /* ref */ 'runtime'), + d: snip('true', d.bool, /* ref */ 'runtime'), }; const res = convertStructValues(structType, snippets); expect(res.length).toBe(4); - expect(res[0]).toEqual(snip('f32(1)', d.f32, /* ref */ undefined)); // Cast applied - expect(res[1]).toEqual(snip('i32(2)', d.i32, /* ref */ undefined)); // Cast applied + expect(res[0]).toEqual(snip('f32(1)', d.f32, /* ref */ 'runtime')); // Cast applied + expect(res[1]).toEqual(snip('i32(2)', d.i32, /* ref */ 'runtime')); // Cast applied expect(res[2]).toEqual(snippets.c); expect(res[3]).toEqual(snippets.d); }); it('throws on missing property', () => { const snippets: Record = { - a: snip('1.0', d.f32, /* ref */ undefined), + a: snip('1.0', d.f32, /* ref */ 'runtime'), // b is missing - c: snip('vec2f(1.0, 1.0)', d.vec2f, /* ref */ undefined), - d: snip('true', d.bool, /* ref */ undefined), + c: snip('vec2f(1.0, 1.0)', d.vec2f, /* ref */ 'runtime'), + d: snip('true', d.bool, /* ref */ 'runtime'), }; expect(() => convertStructValues(structType, snippets)).toThrow( /Missing property b/, diff --git a/packages/typegpu/tests/tgsl/generationHelpers.test.ts b/packages/typegpu/tests/tgsl/generationHelpers.test.ts index 082ef0fdb3..06620c4754 100644 --- a/packages/typegpu/tests/tgsl/generationHelpers.test.ts +++ b/packages/typegpu/tests/tgsl/generationHelpers.test.ts @@ -46,31 +46,31 @@ describe('generationHelpers', () => { describe('numericLiteralToSnippet', () => { it('should convert numeric literals to correct snippets', () => { expect(numericLiteralToSnippet(1)).toEqual( - snip(1, abstractInt, /* ref */ undefined), + snip(1, abstractInt, /* ref */ 'constant'), ); expect(numericLiteralToSnippet(1.1)).toEqual( - snip(1.1, abstractFloat, /* ref */ undefined), + snip(1.1, abstractFloat, /* ref */ 'constant'), ); expect(numericLiteralToSnippet(1e10)).toEqual( - snip(1e10, abstractInt, /* ref */ undefined), + snip(1e10, abstractInt, /* ref */ 'constant'), ); expect(numericLiteralToSnippet(0.5)).toEqual( - snip(0.5, abstractFloat, /* ref */ undefined), + snip(0.5, abstractFloat, /* ref */ 'constant'), ); expect(numericLiteralToSnippet(-45)).toEqual( - snip(-45, abstractInt, /* ref */ undefined), + snip(-45, abstractInt, /* ref */ 'constant'), ); expect(numericLiteralToSnippet(0x1A)).toEqual( - snip(0x1A, abstractInt, /* ref */ undefined), + snip(0x1A, abstractInt, /* ref */ 'constant'), ); expect(numericLiteralToSnippet(0b101)).toEqual( - snip(5, abstractInt, /* ref */ undefined), + snip(5, abstractInt, /* ref */ 'constant'), ); }); }); @@ -84,7 +84,7 @@ describe('generationHelpers', () => { it('should return struct property types', () => { const target = snip('foo', MyStruct, 'this-function'); expect(accessProp(target, 'foo')).toStrictEqual( - snip('foo.foo', f32, /* ref */ undefined), + snip('foo.foo', f32, /* ref */ 'runtime'), ); expect(accessProp(target, 'bar')).toStrictEqual( snip('foo.bar', vec3f, /* ref */ 'this-function'), @@ -96,19 +96,19 @@ describe('generationHelpers', () => { const target = snip('foo', vec4f, 'this-function'); expect(accessProp(target, 'x')).toStrictEqual( - snip('foo.x', f32, /* ref */ undefined), + snip('foo.x', f32, /* ref */ 'runtime'), ); expect(accessProp(target, 'yz')).toStrictEqual( - snip('foo.yz', vec2f, /* ref */ undefined), + snip('foo.yz', vec2f, /* ref */ 'runtime'), ); expect(accessProp(target, 'xyzw')).toStrictEqual( - snip('foo.xyzw', vec4f, /* ref */ undefined), + snip('foo.xyzw', vec4f, /* ref */ 'runtime'), ); }); - it('should return UnknownData when applied to primitives or invalid', () => { - const target1 = snip('foo', u32, /* ref */ undefined); - const target2 = snip('foo', bool, /* ref */ undefined); + it('should return undefined when applied to primitives or invalid', () => { + const target1 = snip('foo', u32, /* ref */ 'runtime'); + const target2 = snip('foo', bool, /* ref */ 'runtime'); expect(accessProp(target1, 'x')).toBe(undefined); expect(accessProp(target2, 'x')).toBe(undefined); }); @@ -116,52 +116,52 @@ describe('generationHelpers', () => { describe('accessIndex', () => { const arr = arrayOf(f32, 2); - const index = snip('0', u32, /* ref */ undefined); + const index = snip('0', u32, /* ref */ 'runtime'); it('returns element type for arrays', () => { - const target = snip('foo', arr, /* ref */ undefined); + const target = snip('foo', arr, /* ref */ 'runtime'); expect(accessIndex(target, index)).toStrictEqual( - snip('foo[0]', f32, undefined), + snip('foo[0]', f32, 'runtime'), ); }); it('returns vector component', () => { - const target1 = snip('foo', vec2i, /* ref */ undefined); - const target2 = snip('foo', vec4h, /* ref */ undefined); + const target1 = snip('foo', vec2i, /* ref */ 'runtime'); + const target2 = snip('foo', vec4h, /* ref */ 'runtime'); expect(accessIndex(target1, index)).toStrictEqual( - snip('foo[0]', i32, undefined), + snip('foo[0]', i32, 'runtime'), ); expect(accessIndex(target2, index)).toStrictEqual( - snip('foo[0]', f16, undefined), + snip('foo[0]', f16, 'runtime'), ); }); it('returns matrix column type', () => { const target1 = accessProp( - snip('foo', mat2x2f, /* ref */ undefined), + snip('foo', mat2x2f, /* ref */ 'runtime'), 'columns', ); const target2 = accessProp( - snip('foo', mat3x3f, /* ref */ undefined), + snip('foo', mat3x3f, /* ref */ 'runtime'), 'columns', ); const target3 = accessProp( - snip('foo', mat4x4f, /* ref */ undefined), + snip('foo', mat4x4f, /* ref */ 'runtime'), 'columns', ); expect(target1 && accessIndex(target1, index)).toStrictEqual( - snip('foo[0]', vec2f, undefined), + snip('foo[0]', vec2f, 'runtime'), ); expect(target2 && accessIndex(target2, index)).toStrictEqual( - snip('foo[0]', vec3f, undefined), + snip('foo[0]', vec3f, 'runtime'), ); expect(target3 && accessIndex(target3, index)).toStrictEqual( - snip('foo[0]', vec4f, undefined), + snip('foo[0]', vec4f, 'runtime'), ); }); it('returns undefined otherwise', () => { - const target = snip('foo', f32, /* ref */ undefined); + const target = snip('foo', f32, /* ref */ 'runtime'); expect(accessIndex(target, index)).toBe(undefined); }); }); @@ -171,37 +171,37 @@ describe('generationHelpers', () => { it('coerces JS numbers', () => { expect(coerceToSnippet(1)).toEqual( - snip(1, abstractInt, /* ref */ undefined), + snip(1, abstractInt, /* ref */ 'constant'), ); expect(coerceToSnippet(2.5)).toEqual( - snip(2.5, abstractFloat, /* ref */ undefined), + snip(2.5, abstractFloat, /* ref */ 'constant'), ); expect(coerceToSnippet(-10)).toEqual( - snip(-10, abstractInt, /* ref */ undefined), + snip(-10, abstractInt, /* ref */ 'constant'), ); expect(coerceToSnippet(0.0)).toEqual( - snip(0, abstractInt, /* ref */ undefined), + snip(0, abstractInt, /* ref */ 'constant'), ); }); it('coerces JS booleans', () => { expect(coerceToSnippet(true)).toEqual( - snip(true, bool, /* ref */ undefined), + snip(true, bool, /* ref */ 'constant'), ); expect(coerceToSnippet(false)).toEqual( - snip(false, bool, /* ref */ undefined), + snip(false, bool, /* ref */ 'constant'), ); }); it(`coerces schemas to UnknownData (as they're not instance types)`, () => { expect(coerceToSnippet(f32)).toEqual( - snip(f32, UnknownData, /* ref */ undefined), + snip(f32, UnknownData, /* ref */ 'constant'), ); expect(coerceToSnippet(vec3i)).toEqual( - snip(vec3i, UnknownData, /* ref */ undefined), + snip(vec3i, UnknownData, /* ref */ 'constant'), ); expect(coerceToSnippet(arr)).toEqual( - snip(arr, UnknownData, /* ref */ undefined), + snip(arr, UnknownData, /* ref */ 'constant'), ); }); @@ -227,20 +227,20 @@ describe('generationHelpers', () => { it('returns UnknownData for other types', () => { expect(coerceToSnippet('foo')).toEqual( - snip('foo', UnknownData, /* ref */ undefined), + snip('foo', UnknownData, /* ref */ 'constant'), ); expect(coerceToSnippet({})).toEqual( - snip({}, UnknownData, /* ref */ undefined), + snip({}, UnknownData, /* ref */ 'constant'), ); expect(coerceToSnippet(null)).toEqual( - snip(null, UnknownData, /* ref */ undefined), + snip(null, UnknownData, /* ref */ 'constant'), ); expect(coerceToSnippet(undefined)).toEqual( - snip(undefined, UnknownData, /* ref */ undefined), + snip(undefined, UnknownData, /* ref */ 'constant'), ); const fn = () => {}; expect(coerceToSnippet(fn)).toEqual( - snip(fn, UnknownData, /* ref */ undefined), + snip(fn, UnknownData, /* ref */ 'constant'), ); }); }); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index c8f3fb94f4..11eabfa6f4 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -287,7 +287,7 @@ describe('wgslGenerator', () => { snip( (arg as { type: 'i'; name: string }).name, d.u32, - /* ref */ undefined, + /* ref */ 'runtime', ) ); @@ -310,7 +310,7 @@ describe('wgslGenerator', () => { // Check for: const vec = std.mix(d.vec4f(), testUsage.value.a, value); // ^ this part should be a vec4f ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('value', d.i32); + wgslGenerator.blockVariable('value', d.i32, 'runtime'); const res2 = wgslGenerator.expression( (astInfo.ast?.body[1][1] as tinyest.Const)[2], ); @@ -322,7 +322,7 @@ describe('wgslGenerator', () => { // ^ this part should be an atomic u32 // ^ this part should be void ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('vec', d.vec4f); + wgslGenerator.blockVariable('vec', d.vec4f, 'this-function'); const res3 = wgslGenerator.expression( (astInfo.ast?.body[1][2] as tinyest.Call)[2][0] as tinyest.Expression, ); @@ -471,7 +471,7 @@ describe('wgslGenerator', () => { provideCtx(ctx, () => { ctx[$internal].itemStateStack.pushFunctionScope( - [snip('idx', d.u32, /* ref */ undefined)], + [snip('idx', d.u32, /* ref */ 'runtime')], {}, d.f32, astInfo.externals ?? {}, From cc349ffd36ff918afa02d4baec8d2821430a3537 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 21:43:57 +0200 Subject: [PATCH 14/59] Infix --- .../examples/rendering/ray-marching/index.ts | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts b/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts index c5260a6388..b09ee86fe7 100644 --- a/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts +++ b/apps/typegpu-docs/src/examples/rendering/ray-marching/index.ts @@ -127,10 +127,8 @@ const rayMarch = (ro: d.v3f, rd: d.v3f): Shape => { }); for (let i = 0; i < MAX_STEPS; i++) { - // TODO: Detect this pattern??? I think we can do this in unplugin - const p = std.add(ro, std.mul(rd, dO)); + const p = ro.add(rd.mul(dO)); const scene = getSceneDist(p); - // p is not being used afterwards dO += scene.dist; if (dO > MAX_DIST || scene.dist < SURF_DIST) { @@ -156,7 +154,7 @@ const softShadow = ( for (let i = 0; i < 100; i++) { if (t >= maxT) break; - const h = getSceneDist(std.add(ro, std.mul(rd, t))).dist; + const h = getSceneDist(ro.add(rd.mul(t))).dist; if (h < 0.001) return 0; res = std.min(res, k * h / t); t += std.max(h, 0.001); @@ -171,9 +169,9 @@ const getNormal = (p: d.v3f): d.v3f => { const e = 0.01; const n = d.vec3f( - getSceneDist(std.add(p, d.vec3f(e, 0, 0))).dist - dist, - getSceneDist(std.add(p, d.vec3f(0, e, 0))).dist - dist, - getSceneDist(std.add(p, d.vec3f(0, 0, e))).dist - dist, + getSceneDist(p.add(d.vec3f(e, 0, 0))).dist - dist, + getSceneDist(p.add(d.vec3f(0, e, 0))).dist - dist, + getSceneDist(p.add(d.vec3f(0, 0, e))).dist - dist, ); return std.normalize(n); @@ -225,17 +223,17 @@ const fragmentMain = tgpu['~unstable'].fragmentFn({ // Lighting with orbiting light const lightPos = getOrbitingLightPos(time.$); - const l = std.normalize(std.sub(lightPos, p)); + const l = std.normalize(lightPos.sub(p)); const diff = std.max(std.dot(n, l), 0); // Soft shadows const shadowRo = p; const shadowRd = l; - const shadowDist = std.length(std.sub(lightPos, p)); + const shadowDist = std.length(lightPos.sub(p)); const shadow = softShadow(shadowRo, shadowRd, 0.1, shadowDist, d.f32(16)); // Combine lighting with shadows and color - const litColor = std.mul(march.color, diff); + const litColor = march.color.mul(diff); const finalColor = std.mix( std.mul(litColor, 0.5), // Shadow color litColor, // Lit color From 77ac0cc9ff8a1654e21719444d183f3929828d27 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 27 Sep 2025 22:12:51 +0200 Subject: [PATCH 15/59] Apply formatting --- packages/typegpu/src/core/function/dualImpl.ts | 1 - packages/typegpu/src/tgsl/wgslGenerator.ts | 6 +----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/typegpu/src/core/function/dualImpl.ts b/packages/typegpu/src/core/function/dualImpl.ts index 87908af5b6..88e3a83e61 100644 --- a/packages/typegpu/src/core/function/dualImpl.ts +++ b/packages/typegpu/src/core/function/dualImpl.ts @@ -10,7 +10,6 @@ import { setName } from '../../shared/meta.ts'; import { $internal } from '../../shared/symbols.ts'; import { tryConvertSnippet } from '../../tgsl/conversion.ts'; import type { AnyData } from '../../data/dataTypes.ts'; -import { isNaturallyRef } from '../../data/wgslTypes.ts'; export function createDualImpl unknown>( jsImpl: T, diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 853aada4dd..dfaaa3eed9 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -23,11 +23,7 @@ import { getName } from '../shared/meta.ts'; import { $internal } from '../shared/symbols.ts'; import { pow } from '../std/numeric.ts'; import { add, div, mul, sub } from '../std/operators.ts'; -import { - type FnArgsConversionHint, - isKnownAtComptime, - isMarkedInternal, -} from '../types.ts'; +import { type FnArgsConversionHint, isMarkedInternal } from '../types.ts'; import { convertStructValues, convertToCommonType, From aee85a68921005db71aa62b933921810bc657ad1 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 7 Oct 2025 16:30:14 +0200 Subject: [PATCH 16/59] A few tweaks --- packages/typegpu/src/data/wgslTypes.ts | 4 +- .../tests/examples/individual/blur.test.ts | 12 ++--- .../examples/individual/dispatch.test.ts | 2 +- .../individual/tgsl-parsing-test.test.ts | 49 ++++++++----------- .../typegpu/tests/tgsl/createDualImpl.test.ts | 4 +- 5 files changed, 31 insertions(+), 40 deletions(-) diff --git a/packages/typegpu/src/data/wgslTypes.ts b/packages/typegpu/src/data/wgslTypes.ts index c99ba6136c..a884dbf761 100644 --- a/packages/typegpu/src/data/wgslTypes.ts +++ b/packages/typegpu/src/data/wgslTypes.ts @@ -1,5 +1,4 @@ import type { TgpuNamable } from '../shared/meta.ts'; -import { isMarkedInternal } from '../shared/symbols.ts'; import type { ExtractInvalidSchemaError, Infer, @@ -24,9 +23,8 @@ import type { $validUniformSchema, $validVertexSchema, } from '../shared/symbols.ts'; -import { $internal } from '../shared/symbols.ts'; +import { $internal, isMarkedInternal } from '../shared/symbols.ts'; import type { Prettify, SwapNever } from '../shared/utilityTypes.ts'; -import { isMarkedInternal } from '../types.ts'; import type { DualFn } from './dualFn.ts'; import type { WgslExternalTexture, diff --git a/packages/typegpu/tests/examples/individual/blur.test.ts b/packages/typegpu/tests/examples/individual/blur.test.ts index dae2f9b231..6994f46d6c 100644 --- a/packages/typegpu/tests/examples/individual/blur.test.ts +++ b/packages/typegpu/tests/examples/individual/blur.test.ts @@ -42,17 +42,17 @@ describe('blur example', () => { } @compute @workgroup_size(32, 1, 1) fn computeFn_0(_arg_0: computeFn_Input_8) { - var settings2 = settingsUniform_1; - var filterOffset = i32((f32((settings2.filterDim - 1)) / 2f)); + let settings2 = (&settingsUniform_1); + var filterOffset = i32((f32(((*settings2).filterDim - 1)) / 2f)); var dims = vec2i(textureDimensions(inTexture_3)); - var baseIndex = (vec2i(((_arg_0.wid.xy * vec2u(settings2.blockDim, 4)) + (_arg_0.lid.xy * vec2u(4, 1)))) - vec2i(filterOffset, 0)); + var baseIndex = (vec2i(((_arg_0.wid.xy * vec2u((*settings2).blockDim, 4)) + (_arg_0.lid.xy * vec2u(4, 1)))) - vec2i(filterOffset, 0)); for (var r = 0; (r < 4); r++) { for (var c = 0; (c < 4); c++) { var loadIndex = (baseIndex + vec2i(c, r)); if ((flip_4 != 0)) { loadIndex = loadIndex.yx; } - tileData_5[r][((_arg_0.lid.x * 4) + u32(c))] = textureSampleLevel(inTexture_3, sampler_6, vec2f(((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims))), 0).xyz; + tileData_5[r][((_arg_0.lid.x * 4) + u32(c))] = textureSampleLevel(inTexture_3, sampler_6, ((vec2f(loadIndex) + vec2f(0.5)) / vec2f(dims)), 0).xyz; } } workgroupBarrier(); @@ -65,9 +65,9 @@ describe('blur example', () => { var center = (i32((4 * _arg_0.lid.x)) + c); if ((((center >= filterOffset) && (center < (128 - filterOffset))) && all((writeIndex < dims)))) { var acc = vec3f(); - for (var f = 0; (f < settings2.filterDim); f++) { + for (var f = 0; (f < (*settings2).filterDim); f++) { var i = ((center + f) - filterOffset); - acc = (acc + (tileData_5[r][i] * (1f / f32(settings2.filterDim)))); + acc = (acc + (tileData_5[r][i] * (1f / f32((*settings2).filterDim)))); } textureStore(outTexture_7, writeIndex, vec4f(acc, 1)); } diff --git a/packages/typegpu/tests/examples/individual/dispatch.test.ts b/packages/typegpu/tests/examples/individual/dispatch.test.ts index fb29619861..81bcc9de15 100644 --- a/packages/typegpu/tests/examples/individual/dispatch.test.ts +++ b/packages/typegpu/tests/examples/individual/dispatch.test.ts @@ -136,7 +136,7 @@ describe('tgsl parsing test example', () => { @group(1) @binding(0) var buffer_3: array; fn wrappedCallback_2(_arg_0: u32, _arg_1: u32, _arg_2: u32) { - for (var i = 0u; (i < arrayLength(&buffer_3)); i++) { + for (var i = 0u; (i < arrayLength((&buffer_3))); i++) { buffer_3[i] *= 2; } } diff --git a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts index b8d97b0ce0..ee427313e8 100644 --- a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts +++ b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts @@ -162,61 +162,54 @@ describe('tgsl parsing test example', () => { return s; } - fn modifyNumFn_12(ptr: ptr) { - (*ptr) += 1; - } - - fn modifyVecFn_13(ptr: ptr) { + fn modifyVecFn_12(ptr: ptr) { (*ptr).x += 1; } - struct SimpleStruct_14 { + struct SimpleStruct_13 { vec: vec2f, } - fn modifyStructFn_15(ptr: ptr) { + fn modifyStructFn_14(ptr: ptr) { (*ptr).vec.x += 1; } - var privateNum_16: u32; + var privateNum_15: u32; - fn modifyNumPrivate_17(ptr: ptr) { + fn modifyNumPrivate_16(ptr: ptr) { (*ptr) += 1; } - var privateVec_18: vec2f; + var privateVec_17: vec2f; - fn modifyVecPrivate_19(ptr: ptr) { + fn modifyVecPrivate_18(ptr: ptr) { (*ptr).x += 1; } - var privateStruct_20: SimpleStruct_14; + var privateStruct_19: SimpleStruct_13; - fn modifyStructPrivate_21(ptr: ptr) { + fn modifyStructPrivate_20(ptr: ptr) { (*ptr).vec.x += 1; } fn pointersTest_11() -> bool { var s = true; - const num = 0u; - modifyNumFn_12((&num)); - s = (s && (num == 1)); var vec = vec2f(); - modifyVecFn_13((&vec)); + modifyVecFn_12((&vec)); s = (s && all(vec == vec2f(1, 0))); - var myStruct = SimpleStruct_14(); - modifyStructFn_15((&myStruct)); + var myStruct = SimpleStruct_13(); + modifyStructFn_14((&myStruct)); s = (s && all(myStruct.vec == vec2f(1, 0))); - modifyNumPrivate_17((&privateNum_16)); - s = (s && (privateNum_16 == 1)); - modifyVecPrivate_19((&privateVec_18)); - s = (s && all(privateVec_18 == vec2f(1, 0))); - modifyStructPrivate_21((&privateStruct_20)); - s = (s && all(privateStruct_20.vec == vec2f(1, 0))); + modifyNumPrivate_16((&privateNum_15)); + s = (s && (privateNum_15 == 1)); + modifyVecPrivate_18((&privateVec_17)); + s = (s && all(privateVec_17 == vec2f(1, 0))); + modifyStructPrivate_20((&privateStruct_19)); + s = (s && all(privateStruct_19.vec == vec2f(1, 0))); return s; } - @group(0) @binding(0) var result_22: i32; + @group(0) @binding(0) var result_21: i32; @compute @workgroup_size(1) fn computeRunTests_0() { var s = true; @@ -226,10 +219,10 @@ describe('tgsl parsing test example', () => { s = (s && arrayAndStructConstructorsTest_8()); s = (s && pointersTest_11()); if (s) { - result_22 = 1; + result_21 = 1; } else { - result_22 = 0; + result_21 = 0; } }" `); diff --git a/packages/typegpu/tests/tgsl/createDualImpl.test.ts b/packages/typegpu/tests/tgsl/createDualImpl.test.ts index b9b09bd30a..94d8c10e11 100644 --- a/packages/typegpu/tests/tgsl/createDualImpl.test.ts +++ b/packages/typegpu/tests/tgsl/createDualImpl.test.ts @@ -41,7 +41,7 @@ describe('dualImpl', () => { expect(asWgsl(myFn)).toMatchInlineSnapshot(` "fn myFn() { - var a = 5; + const a = 5; }" `); }); @@ -122,7 +122,7 @@ describe('dualImpl', () => { [Error: Resolution of the following tree failed: - - fn:myFn - - myDualImpl: Division by zero] + - fn:myDualImpl: Division by zero] `); }); }); From 5173502cc69f59b705687882f609be70708cae79 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 7 Oct 2025 16:45:18 +0200 Subject: [PATCH 17/59] Update wgslGenerator.ts --- packages/typegpu/src/tgsl/wgslGenerator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index fe75996af8..0f5442b3d9 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -738,7 +738,7 @@ ${this.ctx.pre}else ${alternate}`; `'let ${rawId} = ${rhsStr}' is invalid, because references cannot be assigned to 'let' variable declarations. ----- - Try 'let ${rawId} = ${rhsTypeStr}(${rhsStr})' if you need to reassign '${rawId}' later -- Try 'const ${rawId} = ${rhsTypeStr}(${rhsStr})' if you won't reassign '${rawId}' later. +- Try 'const ${rawId} = ${rhsStr}' if you won't reassign '${rawId}' later. -----`, ); } From c3bf598e47a289f1a02ce6ef44c876ccf8539316 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 7 Oct 2025 22:12:39 +0200 Subject: [PATCH 18/59] Fixes --- .../typegpu/src/core/buffer/bufferUsage.ts | 16 +- packages/typegpu/src/data/snippet.ts | 37 ++- packages/typegpu/src/data/wgslTypes.ts | 9 - packages/typegpu/src/std/numeric.ts | 227 +++++++++++------- packages/typegpu/src/std/operators.ts | 16 +- packages/typegpu/src/tgsl/shellless.ts | 22 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 3 +- .../examples/individual/liquid-glass.test.ts | 4 +- .../examples/individual/slime-mold-3d.test.ts | 10 +- .../typegpu/tests/tgsl/memberAccess.test.ts | 82 +++++++ packages/typegpu/tests/utils/parseResolved.ts | 60 ++++- 11 files changed, 335 insertions(+), 151 deletions(-) create mode 100644 packages/typegpu/tests/tgsl/memberAccess.test.ts diff --git a/packages/typegpu/src/core/buffer/bufferUsage.ts b/packages/typegpu/src/core/buffer/bufferUsage.ts index 080ba62e6d..e7eeb878c0 100644 --- a/packages/typegpu/src/core/buffer/bufferUsage.ts +++ b/packages/typegpu/src/core/buffer/bufferUsage.ts @@ -133,9 +133,7 @@ class TgpuFixedBufferImpl< return snip( id, dataType, - /* ref */ isNaturallyRef(dataType) - ? this.usage === 'uniform' ? 'uniform' : 'storage' - : 'runtime', + /* ref */ isNaturallyRef(dataType) ? this.usage : 'runtime', ); } @@ -153,9 +151,7 @@ class TgpuFixedBufferImpl< return snip( this, dataType, - /* ref */ isNaturallyRef(dataType) - ? usage === 'uniform' ? 'uniform' : 'storage' - : 'runtime', + /* ref */ isNaturallyRef(dataType) ? usage : 'runtime', ); }, [$resolve]: (ctx) => ctx.resolve(this), @@ -266,9 +262,7 @@ export class TgpuLaidOutBufferImpl< return snip( id, dataType, - /* ref */ isNaturallyRef(dataType) - ? this.usage === 'uniform' ? 'uniform' : 'storage' - : 'runtime', + /* ref */ isNaturallyRef(dataType) ? this.usage : 'runtime', ); } @@ -286,9 +280,7 @@ export class TgpuLaidOutBufferImpl< return snip( this, schema, - /* ref */ isNaturallyRef(schema) - ? usage === 'uniform' ? 'uniform' : 'storage' - : 'runtime', + /* ref */ isNaturallyRef(schema) ? usage : 'runtime', ); }, [$resolve]: (ctx) => ctx.resolve(this), diff --git a/packages/typegpu/src/data/snippet.ts b/packages/typegpu/src/data/snippet.ts index 496be4e293..ad6db811ff 100644 --- a/packages/typegpu/src/data/snippet.ts +++ b/packages/typegpu/src/data/snippet.ts @@ -1,14 +1,45 @@ import { undecorate } from './dataTypes.ts'; import type { AnyData, UnknownData } from './dataTypes.ts'; import { DEV } from '../shared/env.ts'; -import { type AddressSpace, isNumericSchema } from './wgslTypes.ts'; +import { isNumericSchema } from './wgslTypes.ts'; -export type RefSpace = AddressSpace | 'this-function' | 'runtime' | 'constant'; +export type RefSpace = + | 'uniform' + | 'readonly' // equivalent to ptr + | 'mutable' // equivalent to ptr + | 'workgroup' + | 'private' + | 'function' + | 'handle' + // more specific version of 'function', telling us that the ref is + // to a value defined in the function + | 'this-function' + // not a ref to anything, known at runtime + | 'runtime' + // not a ref to anything, known at pipeline creation time + // (not to be confused with 'comptime') + // note that this doesn't automatically mean the value can be stored in a `const` + // variable, more so that it's valid to do so in WGSL (but not necessarily safe to do in TGSL) + | 'constant'; + +export function isSpaceRef(space: RefSpace) { + return space !== 'runtime' && space !== 'constant'; +} export function isRef(snippet: Snippet) { - return snippet.ref !== 'runtime' && snippet.ref !== 'constant'; + return isSpaceRef(snippet.ref); } +export const refSpaceToPtrParams = { + uniform: { space: 'uniform', access: 'read' }, + readonly: { space: 'storage', access: 'read' }, + mutable: { space: 'storage', access: 'read-write' }, + workgroup: { space: 'workgroup', access: 'read-write' }, + private: { space: 'private', access: 'read-write' }, + function: { space: 'function', access: 'read-write' }, + 'this-function': { space: 'function', access: 'read-write' }, +} as const; + export interface Snippet { readonly value: unknown; /** diff --git a/packages/typegpu/src/data/wgslTypes.ts b/packages/typegpu/src/data/wgslTypes.ts index a884dbf761..00dc030e6a 100644 --- a/packages/typegpu/src/data/wgslTypes.ts +++ b/packages/typegpu/src/data/wgslTypes.ts @@ -1365,15 +1365,6 @@ export type AddressSpace = | 'handle'; export type Access = 'read' | 'write' | 'read-write'; -export const addressSpaceToDefaultAccess = { - uniform: 'read', - storage: 'read', - workgroup: 'read-write', - private: 'read-write', - function: 'read-write', - handle: 'read', -} as const; - export interface Ptr< TAddr extends AddressSpace = AddressSpace, TInner extends StorableData = StorableData, diff --git a/packages/typegpu/src/std/numeric.ts b/packages/typegpu/src/std/numeric.ts index cdb974bf28..afeea1be5f 100644 --- a/packages/typegpu/src/std/numeric.ts +++ b/packages/typegpu/src/std/numeric.ts @@ -4,7 +4,7 @@ import { MissingCpuImplError, } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; -import { toStorable } from '../data/dataTypes.ts'; +import { AnyData, toStorable, toStorables } from '../data/dataTypes.ts'; import { smoothstepScalar } from '../data/numberOps.ts'; import { abstractFloat, @@ -65,9 +65,17 @@ function cpuAbs(value: T): T { return VectorOps.abs[value.kind](value) as T; } +const unaryStorableSignature = (arg: AnyData) => { + const sarg = toStorable(arg); + return { + argTypes: [sarg], + returnType: sarg, + }; +}; + export const abs = dualImpl({ name: 'abs', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAbs, codegenImpl: (value) => stitch`abs(${value})`, }); @@ -83,7 +91,7 @@ function cpuAcos(value: T): T { export const acos = dualImpl({ name: 'acos', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAcos, codegenImpl: (value) => stitch`acos(${value})`, }); @@ -99,7 +107,7 @@ function cpuAcosh(value: T): T { export const acosh = dualImpl({ name: 'acosh', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAcosh, codegenImpl: (value) => stitch`acosh(${value})`, }); @@ -115,7 +123,7 @@ function cpuAsin(value: T): T { export const asin = dualImpl({ name: 'asin', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAsin, codegenImpl: (value) => stitch`asin(${value})`, }); @@ -131,7 +139,7 @@ function cpuAsinh(value: T): T { export const asinh = dualImpl({ name: 'asinh', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAsinh, codegenImpl: (value) => stitch`asinh(${value})`, }); @@ -147,7 +155,7 @@ function cpuAtan(value: T): T { export const atan = dualImpl({ name: 'atan', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAtan, codegenImpl: (value) => stitch`atan(${value})`, }); @@ -163,7 +171,7 @@ function cpuAtanh(value: T): T { export const atanh = dualImpl({ name: 'atanh', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuAtanh, codegenImpl: (value) => stitch`atanh(${value})`, }); @@ -183,7 +191,8 @@ function cpuAtan2(y: T, x: T): T { export const atan2 = dualImpl({ name: 'atan2', signature: (...args) => { - const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; return ({ argTypes: uargs, returnType: uargs[0], @@ -204,7 +213,7 @@ function cpuCeil(value: T): T { export const ceil = dualImpl({ name: 'ceil', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuCeil, codegenImpl: (value) => stitch`ceil(${value})`, }); @@ -225,7 +234,8 @@ function cpuClamp(value: T, low: T, high: T): T { export const clamp = dualImpl({ name: 'clamp', signature: (...args) => { - const uargs = unify(args) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; return { argTypes: uargs, returnType: uargs[0] }; }, normalImpl: cpuClamp, @@ -243,7 +253,7 @@ function cpuCos(value: T): T { export const cos = dualImpl({ name: 'cos', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuCos, codegenImpl: (value) => stitch`cos(${value})`, }); @@ -259,7 +269,7 @@ function cpuCosh(value: T): T { export const cosh = dualImpl({ name: 'cosh', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuCosh, codegenImpl: (value) => stitch`cosh(${value})`, }); @@ -274,7 +284,7 @@ function cpuCountLeadingZeros( export const countLeadingZeros = dualImpl({ name: 'countLeadingZeros', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for countLeadingZeros not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`countLeadingZeros(${value})`, @@ -290,7 +300,7 @@ function cpuCountOneBits( export const countOneBits = dualImpl({ name: 'countOneBits', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for countOneBits not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`countOneBits(${value})`, @@ -306,7 +316,7 @@ function cpuCountTrailingZeros( export const countTrailingZeros = dualImpl({ name: 'countTrailingZeros', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for countTrailingZeros not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`countTrailingZeros(${value})`, @@ -314,7 +324,10 @@ export const countTrailingZeros = dualImpl({ export const cross = dualImpl({ name: 'cross', - signature: (lhs, rhs) => ({ argTypes: [lhs, rhs], returnType: lhs }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ argTypes: sargs, returnType: sargs[0] }); + }, normalImpl: (a: T, b: T): T => VectorOps.cross[a.kind](a, b), codegenImpl: (a, b) => stitch`cross(${a}, ${b})`, @@ -333,7 +346,7 @@ function cpuDegrees(value: T): T { export const degrees = dualImpl({ name: 'degrees', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuDegrees, codegenImpl: (value) => stitch`degrees(${value})`, }); @@ -341,7 +354,7 @@ export const degrees = dualImpl({ export const determinant = dualImpl<(value: AnyMatInstance) => number>({ name: 'determinant', // TODO: The return type is potentially wrong here, it should return whatever the matrix element type is. - signature: (arg) => ({ argTypes: [arg], returnType: f32 }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for determinant not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`determinant(${value})`, @@ -363,20 +376,26 @@ function cpuDistance( export const distance = dualImpl({ name: 'distance', - signature: (lhs, rhs) => ({ - argTypes: [lhs, rhs], - returnType: isHalfPrecisionSchema(lhs) ? f16 : f32, - }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ + argTypes: sargs, + returnType: isHalfPrecisionSchema(sargs[0]) ? f16 : f32, + }); + }, normalImpl: cpuDistance, codegenImpl: (a, b) => stitch`distance(${a}, ${b})`, }); export const dot = dualImpl({ name: 'dot', - signature: (e1, e2) => ({ - argTypes: [e1, e2], - returnType: (e1 as VecData).primitive, - }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ + argTypes: sargs, + returnType: (sargs[0] as VecData).primitive, + }); + }, normalImpl: (lhs: T, rhs: T): number => VectorOps.dot[lhs.kind](lhs, rhs), codegenImpl: (lhs, rhs) => stitch`dot(${lhs}, ${rhs})`, @@ -384,7 +403,7 @@ export const dot = dualImpl({ export const dot4U8Packed = dualImpl<(e1: number, e2: number) => number>({ name: 'dot4U8Packed', - signature: (lhs, rhs) => ({ argTypes: [u32, u32], returnType: u32 }), + signature: { argTypes: [u32, u32], returnType: u32 }, normalImpl: 'CPU implementation for dot4U8Packed not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e1, e2) => stitch`dot4U8Packed(${e1}, ${e2})`, @@ -392,7 +411,7 @@ export const dot4U8Packed = dualImpl<(e1: number, e2: number) => number>({ export const dot4I8Packed = dualImpl<(e1: number, e2: number) => number>({ name: 'dot4I8Packed', - signature: (lhs, rhs) => ({ argTypes: [u32, u32], returnType: i32 }), + signature: { argTypes: [u32, u32], returnType: i32 }, normalImpl: 'CPU implementation for dot4I8Packed not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e1, e2) => stitch`dot4I8Packed(${e1}, ${e2})`, @@ -409,7 +428,7 @@ function cpuExp(value: T): T { export const exp = dualImpl({ name: 'exp', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuExp, codegenImpl: (value) => stitch`exp(${value})`, }); @@ -425,7 +444,7 @@ function cpuExp2(value: T): T { export const exp2 = dualImpl({ name: 'exp2', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuExp2, codegenImpl: (value) => stitch`exp2(${value})`, }); @@ -446,10 +465,13 @@ function cpuExtractBits( export const extractBits = dualImpl({ name: 'extractBits', - signature: (arg, offset, count) => ({ - argTypes: [arg, u32, u32], - returnType: arg, - }), + signature: (arg, _offset, _count) => { + const sarg = toStorable(arg); + return ({ + argTypes: [sarg, u32, u32], + returnType: sarg, + }); + }, normalImpl: 'CPU implementation for extractBits not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e, offset, count) => @@ -460,10 +482,13 @@ export const faceForward = dualImpl< (e1: T, e2: T, e3: T) => T >({ name: 'faceForward', - signature: (arg1, arg2, arg3) => ({ - argTypes: [arg1, arg2, arg3], - returnType: arg1, - }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ + argTypes: sargs, + returnType: sargs[0], + }); + }, normalImpl: 'CPU implementation for faceForward not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e1, e2, e3) => stitch`faceForward(${e1}, ${e2}, ${e3})`, @@ -479,7 +504,7 @@ function cpuFirstLeadingBit( export const firstLeadingBit = dualImpl({ name: 'firstLeadingBit', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for firstLeadingBit not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`firstLeadingBit(${value})`, @@ -495,7 +520,7 @@ function cpuFirstTrailingBit( export const firstTrailingBit = dualImpl({ name: 'firstTrailingBit', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for firstTrailingBit not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`firstTrailingBit(${value})`, @@ -512,7 +537,7 @@ function cpuFloor(value: T): T { export const floor = dualImpl({ name: 'floor', - signature: (...argTypes) => ({ argTypes, returnType: argTypes[0] }), + signature: unaryStorableSignature, normalImpl: cpuFloor, codegenImpl: (arg) => stitch`floor(${arg})`, }); @@ -534,10 +559,13 @@ function cpuFma( export const fma = dualImpl({ name: 'fma', - signature: (arg1, arg2, arg3) => ({ - argTypes: [arg1, arg2, arg3], - returnType: arg1, - }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ + argTypes: sargs, + returnType: sargs[0], + }); + }, normalImpl: cpuFma, codegenImpl: (e1, e2, e3) => stitch`fma(${e1}, ${e2}, ${e3})`, }); @@ -553,7 +581,7 @@ function cpuFract(value: T): T { export const fract = dualImpl({ name: 'fract', - signature: (...argTypes) => ({ argTypes, returnType: argTypes[0] }), + signature: unaryStorableSignature, normalImpl: cpuFract, codegenImpl: (a) => stitch`fract(${a})`, }); @@ -626,10 +654,13 @@ function cpuInsertBits( export const insertBits = dualImpl({ name: 'insertBits', - signature: (e, newbits, offset, count) => ({ - argTypes: [e, newbits, u32, u32], - returnType: e, - }), + signature: (e, newbits, _offset, _count) => { + const se = toStorable(e); + return ({ + argTypes: [se, toStorable(newbits), u32, u32], + returnType: se, + }); + }, normalImpl: 'CPU implementation for insertBits not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e, newbits, offset, count) => @@ -649,7 +680,7 @@ function cpuInverseSqrt(value: T): T { export const inverseSqrt = dualImpl({ name: 'inverseSqrt', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuInverseSqrt, codegenImpl: (value) => stitch`inverseSqrt(${value})`, }); @@ -667,22 +698,23 @@ function cpuLdexp( export const ldexp = dualImpl({ name: 'ldexp', - signature: (e1, e2) => { - switch (e1.type) { + signature: (e1, _e2) => { + const se1 = toStorable(e1); + switch (se1.type) { case 'abstractFloat': - return { argTypes: [abstractFloat, abstractInt], returnType: e1 }; + return { argTypes: [se1, abstractInt], returnType: se1 }; case 'f32': case 'f16': - return { argTypes: [e1, i32], returnType: e1 }; + return { argTypes: [se1, i32], returnType: se1 }; case 'vec2f': case 'vec2h': - return { argTypes: [e1, vec2i], returnType: e1 }; + return { argTypes: [se1, vec2i], returnType: se1 }; case 'vec3f': case 'vec3h': - return { argTypes: [e1, vec3i], returnType: e1 }; + return { argTypes: [se1, vec3i], returnType: se1 }; case 'vec4f': case 'vec4h': - return { argTypes: [e1, vec4i], returnType: e1 }; + return { argTypes: [se1, vec4i], returnType: se1 }; default: throw new Error( `Unsupported data type for ldexp: ${e1.type}. Supported types are abstractFloat, f32, f16, vec2f, vec2h, vec3f, vec3h, vec4f, vec4h.`, @@ -727,7 +759,7 @@ function cpuLog(value: T): T { export const log = dualImpl({ name: 'log', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuLog, codegenImpl: (value) => stitch`log(${value})`, }); @@ -743,7 +775,7 @@ function cpuLog2(value: T): T { export const log2 = dualImpl({ name: 'log2', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuLog2, codegenImpl: (value) => stitch`log2(${value})`, }); @@ -760,7 +792,8 @@ function cpuMax(a: T, b: T): T { export const max = dualImpl({ name: 'max', signature: (...args) => { - const uargs = unify(args) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; return ({ argTypes: uargs, returnType: uargs[0], @@ -782,7 +815,8 @@ function cpuMin(a: T, b: T): T { export const min = dualImpl({ name: 'min', signature: (...args) => { - const uargs = unify(args) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; return ({ argTypes: uargs, returnType: uargs[0], @@ -818,7 +852,14 @@ function cpuMix( export const mix = dualImpl({ name: 'mix', - signature: (e1, e2, e3) => ({ argTypes: [e1, e2, e3], returnType: e1 }), + signature: (...args) => { + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; + return ({ + argTypes: uargs, + returnType: uargs[0], + }); + }, normalImpl: cpuMix, codegenImpl: (e1, e2, e3) => stitch`mix(${e1}, ${e2}, ${e3})`, }); @@ -854,15 +895,16 @@ function cpuModf( export const modf: ModfOverload = dualImpl({ name: 'modf', signature: (e) => { - const returnType = ModfResult[e.type as keyof typeof ModfResult]; + const se = toStorable(e); + const returnType = ModfResult[se.type as keyof typeof ModfResult]; if (!returnType) { throw new Error( - `Unsupported data type for modf: ${e.type}. Supported types are f32, f16, abstractFloat, vec2f, vec3f, vec4f, vec2h, vec3h, vec4h.`, + `Unsupported data type for modf: ${se.type}. Supported types are f32, f16, abstractFloat, vec2f, vec3f, vec4f, vec2h, vec3h, vec4h.`, ); } - return { argTypes: [e], returnType }; + return { argTypes: [se], returnType }; }, normalImpl: 'CPU implementation for modf not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', @@ -871,7 +913,7 @@ export const modf: ModfOverload = dualImpl({ export const normalize = dualImpl({ name: 'normalize', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: (v: T): T => VectorOps.normalize[v.kind](v), codegenImpl: (v) => stitch`normalize(${v})`, @@ -898,7 +940,8 @@ function powCpu( export const pow = dualImpl({ name: 'pow', signature: (...args) => { - const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; return { argTypes: uargs, returnType: isNumericSchema(uargs[0]) ? uargs[1] : uargs[0], @@ -917,7 +960,7 @@ function cpuQuantizeToF16( export const quantizeToF16 = dualImpl({ name: 'quantizeToF16', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for quantizeToF16 not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`quantizeToF16(${value})`, @@ -937,7 +980,8 @@ function cpuRadians(value: T): T { export const radians = dualImpl({ name: 'radians', signature: (...args) => { - const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; return ({ argTypes: uargs, returnType: uargs[0] }); }, normalImpl: cpuRadians, @@ -946,7 +990,10 @@ export const radians = dualImpl({ export const reflect = dualImpl({ name: 'reflect', - signature: (lhs, rhs) => ({ argTypes: [lhs, rhs], returnType: lhs }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ argTypes: sargs, returnType: sargs[0] }); + }, normalImpl: (e1: T, e2: T): T => sub(e1, mul(2 * dot(e2, e1), e2)), codegenImpl: (e1, e2) => stitch`reflect(${e1}, ${e2})`, @@ -981,7 +1028,7 @@ function cpuReverseBits(value: T): T { export const reverseBits = dualImpl({ name: 'reverseBits', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for reverseBits not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`reverseBits(${value})`, @@ -1000,7 +1047,7 @@ function cpuRound(value: T): T { export const round = dualImpl({ name: 'round', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuRound, codegenImpl: (value) => stitch`round(${value})`, }); @@ -1018,7 +1065,7 @@ function cpuSaturate(value: T): T { export const saturate = dualImpl({ name: 'saturate', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuSaturate, codegenImpl: (value) => stitch`saturate(${value})`, }); @@ -1034,7 +1081,7 @@ function cpuSign(e: T): T { export const sign = dualImpl({ name: 'sign', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuSign, codegenImpl: (e) => stitch`sign(${e})`, }); @@ -1050,7 +1097,7 @@ function cpuSin(value: T): T { export const sin = dualImpl({ name: 'sin', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuSin, codegenImpl: (value) => stitch`sin(${value})`, }); @@ -1068,7 +1115,7 @@ function cpuSinh(value: T): T { export const sinh = dualImpl({ name: 'sinh', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuSinh, codegenImpl: (value) => stitch`sinh(${value})`, }); @@ -1100,10 +1147,13 @@ function cpuSmoothstep( export const smoothstep = dualImpl({ name: 'smoothstep', - signature: (edge0, edge1, x) => ({ - argTypes: [edge0, edge1, x], - returnType: x, - }), + signature: (...args) => { + const sargs = toStorables(args); + return ({ + argTypes: sargs, + returnType: sargs[2], + }); + }, normalImpl: cpuSmoothstep, codegenImpl: (edge0, edge1, x) => stitch`smoothstep(${edge0}, ${edge1}, ${x})`, @@ -1120,7 +1170,7 @@ function cpuSqrt(value: T): T { export const sqrt = dualImpl({ name: 'sqrt', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuSqrt, codegenImpl: (value) => stitch`sqrt(${value})`, }); @@ -1139,7 +1189,8 @@ function cpuStep(edge: T, x: T): T { export const step = dualImpl({ name: 'step', signature: (...args) => { - const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; return { argTypes: uargs, returnType: uargs[0] }; }, normalImpl: cpuStep, @@ -1159,7 +1210,7 @@ function cpuTan(value: T): T { export const tan = dualImpl({ name: 'tan', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuTan, codegenImpl: (value) => stitch`tan(${value})`, }); @@ -1175,14 +1226,14 @@ function cpuTanh(value: T): T { export const tanh = dualImpl({ name: 'tanh', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: cpuTanh, codegenImpl: (value) => stitch`tanh(${value})`, }); export const transpose = dualImpl<(e: T) => T>({ name: 'transpose', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for transpose not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e) => stitch`transpose(${e})`, @@ -1196,7 +1247,7 @@ function cpuTrunc(value: T): T { export const trunc = dualImpl({ name: 'trunc', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: unaryStorableSignature, normalImpl: 'CPU implementation for trunc not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`trunc(${value})`, diff --git a/packages/typegpu/src/std/operators.ts b/packages/typegpu/src/std/operators.ts index a140592e2d..89893de636 100644 --- a/packages/typegpu/src/std/operators.ts +++ b/packages/typegpu/src/std/operators.ts @@ -1,6 +1,6 @@ import { dualImpl } from '../core/function/dualImpl.ts'; import { stitch, stitchWithExactTypes } from '../core/resolve/stitch.ts'; -import { toStorables } from '../data/dataTypes.ts'; +import { toStorable, toStorables } from '../data/dataTypes.ts'; import { abstractFloat, f16, f32 } from '../data/numeric.ts'; import { vecTypeToConstructor } from '../data/vector.ts'; import { VectorOps } from '../data/vectorOps.ts'; @@ -188,7 +188,8 @@ function cpuDiv(lhs: NumVec | number, rhs: NumVec | number): NumVec | number { export const div = dualImpl({ name: 'div', signature: (...args) => { - const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; return ({ argTypes: uargs, returnType: isNumericSchema(uargs[0]) ? uargs[1] : uargs[0], @@ -213,7 +214,8 @@ type ModOverload = { export const mod: ModOverload = dualImpl({ name: 'mod', signature: (...args) => { - const uargs = unify(args) ?? args; + const sargs = toStorables(args); + const uargs = unify(sargs) ?? sargs; return { argTypes: uargs, returnType: isNumericSchema(uargs[0]) ? uargs[1] : uargs[0], @@ -256,7 +258,13 @@ function cpuNeg(value: NumVec | number): NumVec | number { export const neg = dualImpl({ name: 'neg', - signature: (arg) => ({ argTypes: [arg], returnType: arg }), + signature: (arg) => { + const sarg = toStorable(arg); + return { + argTypes: [sarg], + returnType: sarg, + }; + }, normalImpl: cpuNeg, codegenImpl: (arg) => stitch`-(${arg})`, }); diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index 22c28b7cd8..002d9246c4 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -4,12 +4,8 @@ import { } from '../core/function/shelllessImpl.ts'; import type { AnyData } from '../data/dataTypes.ts'; import { INTERNAL_createPtr } from '../data/ptr.ts'; -import type { Snippet } from '../data/snippet.ts'; -import { - addressSpaceToDefaultAccess, - isPtr, - type StorableData, -} from '../data/wgslTypes.ts'; +import { refSpaceToPtrParams, type Snippet } from '../data/snippet.ts'; +import { isPtr, type StorableData } from '../data/wgslTypes.ts'; import { getMetaData, getName } from '../shared/meta.ts'; import { concretize } from './generationHelpers.ts'; @@ -48,17 +44,15 @@ export class ShelllessRepository { const argTypes = (argSnippets ?? []).map((s) => { const type = concretize(s.dataType as AnyData); - const addressSpace = s.ref === 'this-function' - ? 'function' - : s.ref === 'constant' || s.ref === 'runtime' - ? undefined - : s.ref; + const ptrParams = s.ref in refSpaceToPtrParams + ? refSpaceToPtrParams[s.ref as keyof typeof refSpaceToPtrParams] + : undefined; - return addressSpace !== undefined && !isPtr(type) + return ptrParams !== undefined && !isPtr(type) ? INTERNAL_createPtr( - addressSpace, + ptrParams.space, type as StorableData, - addressSpaceToDefaultAccess[addressSpace], + ptrParams.access, ) : type; }); diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 0f5442b3d9..97bedc0978 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -13,6 +13,7 @@ import { bool, i32, u32 } from '../data/numeric.ts'; import { isRef, isSnippet, + isSpaceRef, type RefSpace, snip, type Snippet, @@ -148,7 +149,7 @@ ${this.ctx.pre}}`; this.ctx.makeNameValid(id), dataType, /* ref */ wgsl.isNaturallyRef(dataType) - ? 'this-function' + ? isSpaceRef(ref) ? ref : 'this-function' : ref === 'constant' ? 'constant' : 'runtime', diff --git a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts index 03024e2175..a1b829f74c 100644 --- a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts +++ b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts @@ -105,7 +105,7 @@ describe('liquid-glass example', () => { @group(0) @binding(3) var sampler_11: sampler; - fn sampleWithChromaticAberration_12(tex: ptr>, uv: vec2f, offset: f32, dir: ptr, blur: f32) -> vec3f { + fn sampleWithChromaticAberration_12(tex: texture_2d, uv: vec2f, offset: f32, dir: ptr, blur: f32) -> vec3f { var samples = array(); for (var i = 0; (i < 3); i++) { var channelOffset = ((*dir) * ((f32(i) - 1) * offset)); @@ -140,7 +140,7 @@ describe('liquid-glass example', () => { var featherUV = (paramsUniform_5.edgeFeather / f32(max(texDim.x, texDim.y))); var weights = calculateWeights_9(sdfDist, paramsUniform_5.start, paramsUniform_5.end, featherUV); var blurSample = textureSampleBias(sampledView_8, sampler_11, _arg_0.uv, paramsUniform_5.blur); - var refractedSample = sampleWithChromaticAberration_12((&sampledView_8), (_arg_0.uv + (dir * (paramsUniform_5.refractionStrength * normalizedDist))), (paramsUniform_5.chromaticStrength * normalizedDist), (&dir), (paramsUniform_5.blur * paramsUniform_5.edgeBlurMultiplier)); + var refractedSample = sampleWithChromaticAberration_12(sampledView_8, (_arg_0.uv + (dir * (paramsUniform_5.refractionStrength * normalizedDist))), (paramsUniform_5.chromaticStrength * normalizedDist), (&dir), (paramsUniform_5.blur * paramsUniform_5.edgeBlurMultiplier)); var normalSample = textureSampleLevel(sampledView_8, sampler_11, _arg_0.uv, 0); var tint = TintParams_13(paramsUniform_5.tintColor, paramsUniform_5.tintStrength); var tintedBlur = applyTint_14(blurSample.xyz, (&tint)); diff --git a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts index c039de0443..352c2456ff 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts @@ -168,7 +168,7 @@ describe('slime mold 3d example', () => { axis = vec3f(0, 0, 1); } } - return (*normalize(cross(dir, axis))); + return normalize(cross((*dir), axis)); } struct Params_12 { @@ -187,17 +187,17 @@ describe('slime mold 3d example', () => { totalWeight: f32, } - fn sense3D_9(pos: ptr, direction: ptr) -> SenseResult_13 { + fn sense3D_9(pos: ptr, direction: ptr) -> SenseResult_13 { var dims = textureDimensions(oldState_4); var dimsf = vec3f(dims); var weightedDir = vec3f(); var totalWeight = 0f; var perp1 = getPerpendicular_10(direction); - var perp2 = cross(direction, perp1); + var perp2 = cross((*direction), perp1); const numSamples = 8; for (var i = 0; (i < numSamples); i++) { var theta = (((f32(i) / f32(numSamples)) * 2) * 3.141592653589793); - var coneOffset = ((perp1 * cos(theta)) + ((*perp2) * sin(theta))); + var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); var sensorDir = normalize(((*direction) + (coneOffset * sin(params_11.sensorAngle)))); var sensorPos = ((*pos) + (sensorDir * params_11.sensorDistance)); var sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - vec3f(1)))); @@ -325,7 +325,7 @@ describe('slime mold 3d example', () => { } fn rayBoxIntersection_6(rayOrigin: ptr, rayDir: ptr, boxMin: ptr, boxMax: ptr) -> RayBoxResult_7 { - var invDir = (vec3f(1) / rayDir); + var invDir = (vec3f(1) / (*rayDir)); var t0 = (((*boxMin) - (*rayOrigin)) * invDir); var t1 = (((*boxMax) - (*rayOrigin)) * invDir); var tmin = min(t0, t1); diff --git a/packages/typegpu/tests/tgsl/memberAccess.test.ts b/packages/typegpu/tests/tgsl/memberAccess.test.ts new file mode 100644 index 0000000000..6594c98fec --- /dev/null +++ b/packages/typegpu/tests/tgsl/memberAccess.test.ts @@ -0,0 +1,82 @@ +import { describe } from 'vitest'; +import { it } from '../utils/extendedIt.ts'; +import { expectSnippetOf } from '../utils/parseResolved.ts'; +import * as d from '../../src/data/index.ts'; +import { snip } from '../../src/data/snippet.ts'; +import tgpu from '../../src/index.ts'; + +describe('Member Access', () => { + const Boid = d.struct({ + pos: d.vec3f, + }); + + it('should access member properties of literals', () => { + expectSnippetOf(() => { + 'kernel'; + Boid().pos; + }).toStrictEqual(snip('Boid().pos', d.vec3f, 'runtime')); + + expectSnippetOf(() => { + 'kernel'; + Boid().pos.xyz; + }).toStrictEqual(snip('Boid().pos.xyz', d.vec3f, 'runtime')); + }); + + it('should access member properties of externals', () => { + const boid = Boid({ pos: d.vec3f(1, 2, 3) }); + + expectSnippetOf(() => { + 'kernel'; + boid.pos; + }).toStrictEqual(snip(d.vec3f(1, 2, 3), d.vec3f, 'constant')); + + expectSnippetOf(() => { + 'kernel'; + boid.pos.zyx; + }).toStrictEqual(snip(d.vec3f(3, 2, 1), d.vec3f, 'constant')); + }); + + it('should access member properties of variables', () => { + const boidVar = tgpu.privateVar(Boid); + + expectSnippetOf(() => { + 'kernel'; + boidVar.$.pos; + }).toStrictEqual(snip('boidVar.pos', d.vec3f, 'private')); + + expectSnippetOf(() => { + 'kernel'; + boidVar.$.pos.xyz; + }).toStrictEqual(snip('boidVar.pos.xyz', d.vec3f, 'runtime')); // < swizzles are new objects + }); + + it('derefs access to local variables with proper address space', () => { + expectSnippetOf(() => { + 'kernel'; + // Creating a new Boid instance + const boid = Boid(); + // Taking a reference that is local to this function + const boidRef = boid; + boidRef.pos; + }).toStrictEqual(snip('(*boidRef).pos', d.vec3f, 'this-function')); + }); + + it('derefs access to storage with proper address space', ({ root }) => { + const boidReadonly = root.createReadonly(Boid); + const boidMutable = root.createMutable(Boid); + + expectSnippetOf(() => { + 'kernel'; + // Taking a reference to a storage variable + const boidRef = boidReadonly.$; + boidRef.pos; + }).toStrictEqual(snip('(*boidRef).pos', d.vec3f, 'readonly')); + + expectSnippetOf(() => { + 'kernel'; + // Taking a reference to a storage variable + const boidRef = boidMutable.$; + boidRef.pos; + }).toStrictEqual(snip('(*boidRef).pos', d.vec3f, 'mutable')); + }); +}); diff --git a/packages/typegpu/tests/utils/parseResolved.ts b/packages/typegpu/tests/utils/parseResolved.ts index 5b5219501a..dd45bf5d9c 100644 --- a/packages/typegpu/tests/utils/parseResolved.ts +++ b/packages/typegpu/tests/utils/parseResolved.ts @@ -9,6 +9,8 @@ import { CodegenState, type Wgsl } from '../../src/types.ts'; import { getMetaData } from '../../src/shared/meta.ts'; import wgslGenerator from '../../src/tgsl/wgslGenerator.ts'; import { namespace } from '../../src/core/resolve/namespace.ts'; +import type { Snippet } from '../../src/data/snippet.ts'; +import { $internal } from '../../src/shared/symbols.ts'; /** * Just a shorthand for tgpu.resolve @@ -23,37 +25,69 @@ export function asWgsl(...values: unknown[]): string { }); } -export function expectDataTypeOf( - cb: () => unknown, -): Assertion { +function extractSnippetFromKernel(cb: () => unknown): Snippet { const ctx = new ResolutionCtxImpl({ namespace: namespace({ names: 'strict' }), }); - const dataType = provideCtx( + return provideCtx( ctx, () => { + let pushedFnScope = false; try { + const meta = getMetaData(cb); + + if (!meta || !meta.ast) { + throw new Error('No metadata found for the kernel'); + } + ctx.pushMode(new CodegenState()); - // Extracting the first expression from the block - const statements = (getMetaData(cb)?.ast?.body as tinyest.Block)[1]; - if (statements.length !== 1) { + ctx[$internal].itemStateStack.pushItem(); + ctx[$internal].itemStateStack.pushFunctionScope( + [], + {}, + undefined, + meta.externals ?? {}, + ); + ctx.pushBlockScope(); + pushedFnScope = true; + + // Extracting the last expression from the block + const statements = meta.ast.body[1] ?? []; + if (statements.length === 0) { throw new Error( - `Expected exactly one expression, got ${statements.length}`, + `Expected at least one expression, got ${statements.length}`, ); } wgslGenerator.initGenerator(ctx); - const exprSnippet = wgslGenerator.expression( - statements[0] as tinyest.Expression, + // Prewarming statements + for (const statement of statements) { + wgslGenerator.statement(statement); + } + return wgslGenerator.expression( + statements[statements.length - 1] as tinyest.Expression, ); - - return exprSnippet.dataType; } finally { + if (pushedFnScope) { + ctx.popBlockScope(); + ctx[$internal].itemStateStack.popFunctionScope(); + ctx[$internal].itemStateStack.popItem(); + } ctx.popMode('codegen'); } }, ); +} - return expect(dataType); +export function expectSnippetOf( + cb: () => unknown, +): Assertion { + return expect(extractSnippetFromKernel(cb)); +} + +export function expectDataTypeOf( + cb: () => unknown, +): Assertion { + return expect(extractSnippetFromKernel(cb).dataType); } From 40b3f7a171d33c1cb5356ec3ba1293aacdda4a81 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 7 Oct 2025 22:29:42 +0200 Subject: [PATCH 19/59] Self review --- packages/typegpu/src/core/function/tgpuFn.ts | 77 +++++--------------- packages/typegpu/src/core/slot/accessor.ts | 7 +- packages/typegpu/src/std/numeric.ts | 2 +- 3 files changed, 22 insertions(+), 64 deletions(-) diff --git a/packages/typegpu/src/core/function/tgpuFn.ts b/packages/typegpu/src/core/function/tgpuFn.ts index 036696f04c..cd83970ce4 100644 --- a/packages/typegpu/src/core/function/tgpuFn.ts +++ b/packages/typegpu/src/core/function/tgpuFn.ts @@ -1,10 +1,6 @@ import type { AnyData } from '../../data/dataTypes.ts'; import type { DualFn } from '../../data/dualFn.ts'; -import { - type ResolvedSnippet, - snip, - type Snippet, -} from '../../data/snippet.ts'; +import type { ResolvedSnippet } from '../../data/snippet.ts'; import { schemaCallWrapper } from '../../data/schemaCallWrapper.ts'; import { Void } from '../../data/wgslTypes.ts'; import { ExecutionError } from '../../errors.ts'; @@ -16,7 +12,6 @@ import type { Infer } from '../../shared/repr.ts'; import { $getNameForward, $internal, - $ownSnippet, $providing, $resolve, } from '../../shared/symbols.ts'; @@ -36,7 +31,7 @@ import { type TgpuAccessor, type TgpuSlot, } from '../slot/slotTypes.ts'; -import { createDualImpl, dualImpl } from './dualImpl.ts'; +import { dualImpl } from './dualImpl.ts'; import { createFnCore, type FnCore } from './fnCore.ts'; import type { AnyFn, @@ -242,7 +237,6 @@ function createFn( throw new ExecutionError(err, [fn]); } }), - // Functions give up ownership of their return value (so ref is false) codegenImpl: (...args) => { // biome-ignore lint/style/noNonNullAssertion: it's there const ctx = getResolutionCtx()!; @@ -304,14 +298,22 @@ function createBoundFunction( }, }; - const call = createDualImpl>( - (...args) => innerFn(...args), - (...args) => - // Why no ref? Functions give up ownership of their return value - snip(new FnCall(fn, args), innerFn.shell.returnType, /* ref */ 'runtime'), - 'tgpuFnCall', - innerFn.shell.argTypes, - ); + const call = dualImpl>({ + name: 'tgpuFnCall', + noComptime: true, + signature: { + argTypes: innerFn.shell.argTypes, + returnType: innerFn.shell.returnType, + }, + normalImpl: innerFn, + codegenImpl: (...args) => { + // biome-ignore lint/style/noNonNullAssertion: it's there + const ctx = getResolutionCtx()!; + return ctx.withResetIndentLevel(() => + stitch`${ctx.resolve(fn).value}(${args})` + ); + }, + }); const fn = Object.assign(call, fnBase) as unknown as TgpuFn; fn[$internal].implementation = innerFn[$internal].implementation; @@ -324,48 +326,5 @@ function createBoundFunction( }, }); - fn[$internal].implementation = innerFn[$internal].implementation; - return fn; } - -// TODO: Perhaps remove -class FnCall implements SelfResolvable { - readonly [$internal] = true; - readonly [$ownSnippet]: Snippet; - readonly [$getNameForward]: unknown; - readonly #fn: TgpuFnBase; - readonly #params: Snippet[]; - - constructor( - fn: TgpuFnBase, - params: Snippet[], - ) { - this.#fn = fn; - this.#params = params; - this[$getNameForward] = fn; - this[$ownSnippet] = snip( - this, - this.#fn.shell.returnType, - // Why no ref? Functions give up ownership of their return value (so ref is false) - /* ref */ 'runtime', - ); - } - - [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - // We need to reset the indentation level during function body resolution to ignore the indentation level of the function call - return ctx.withResetIndentLevel(() => { - // TODO: Resolve the params first, then the function (just for consistency) - return snip( - stitch`${ctx.resolve(this.#fn).value}(${this.#params})`, - this.#fn.shell.returnType, - // Why no ref? Functions give up ownership of their return value (so ref is false) - /* ref */ 'runtime', - ); - }); - } - - toString() { - return `call:${getName(this) ?? ''}`; - } -} diff --git a/packages/typegpu/src/core/slot/accessor.ts b/packages/typegpu/src/core/slot/accessor.ts index ae0d0bd4b8..0b6cc73d3a 100644 --- a/packages/typegpu/src/core/slot/accessor.ts +++ b/packages/typegpu/src/core/slot/accessor.ts @@ -96,10 +96,9 @@ export class TgpuAccessorImpl return snip( value, this.schema, - /* ref */ value.resourceType === 'uniform' || - value.resourceType === 'buffer-usage' && value.usage === 'uniform' - ? 'uniform' - : 'storage', + /* ref */ value.resourceType === 'buffer-usage' + ? value.usage + : value.resourceType, ); } diff --git a/packages/typegpu/src/std/numeric.ts b/packages/typegpu/src/std/numeric.ts index afeea1be5f..2db3ae0148 100644 --- a/packages/typegpu/src/std/numeric.ts +++ b/packages/typegpu/src/std/numeric.ts @@ -4,7 +4,7 @@ import { MissingCpuImplError, } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; -import { AnyData, toStorable, toStorables } from '../data/dataTypes.ts'; +import { type AnyData, toStorable, toStorables } from '../data/dataTypes.ts'; import { smoothstepScalar } from '../data/numberOps.ts'; import { abstractFloat, From 7c5900bd4337fd61f3a77cd57bf6e1da0a7ed50e Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 7 Oct 2025 22:36:36 +0200 Subject: [PATCH 20/59] Update accessor.ts --- packages/typegpu/src/core/slot/accessor.ts | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/packages/typegpu/src/core/slot/accessor.ts b/packages/typegpu/src/core/slot/accessor.ts index 0b6cc73d3a..8cb7926827 100644 --- a/packages/typegpu/src/core/slot/accessor.ts +++ b/packages/typegpu/src/core/slot/accessor.ts @@ -13,14 +13,10 @@ import { } from '../../shared/symbols.ts'; import { getOwnSnippet, - isBufferUsage, type ResolutionCtx, type SelfResolvable, } from '../../types.ts'; -import { - isBufferShorthand, - type TgpuBufferShorthand, -} from '../buffer/bufferShorthand.ts'; +import type { TgpuBufferShorthand } from '../buffer/bufferShorthand.ts'; import type { TgpuBufferUsage } from '../buffer/bufferUsage.ts'; import { isTgpuFn, type TgpuFn } from '../function/tgpuFn.ts'; import { @@ -92,16 +88,6 @@ export class TgpuAccessorImpl return value[$internal].gpuImpl(); } - if (isBufferUsage(value) || isBufferShorthand(value)) { - return snip( - value, - this.schema, - /* ref */ value.resourceType === 'buffer-usage' - ? value.usage - : value.resourceType, - ); - } - const ownSnippet = getOwnSnippet(value); if (ownSnippet) { return ownSnippet; From 50741ce3a6f5a352d608a1618dfd376000ac2cef Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 7 Oct 2025 22:55:52 +0200 Subject: [PATCH 21/59] More tweaks --- .../examples/simulation/gravity/compute.ts | 19 +++++++++---------- packages/typegpu/src/std/boolean.ts | 9 +++++++-- .../typegpu/src/tgsl/generationHelpers.ts | 1 - .../tests/examples/individual/gravity.test.ts | 15 +++++++-------- 4 files changed, 23 insertions(+), 21 deletions(-) diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts index cf173bfb73..f03cfda446 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts @@ -41,7 +41,6 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ destroyed: computeLayout.$.inState[currentId].destroyed, }); - const updatedCurrent = current; if (current.destroyed === 0) { for (let i = 0; i < computeLayout.$.celestialBodiesCount; i++) { const otherId = d.u32(i); @@ -76,7 +75,7 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ // bounce occurs // push the smaller object outside if (isSmaller(currentId, otherId)) { - updatedCurrent.position = std.add( + current.position = std.add( other.position, std.mul( radiusOf(current) + radiusOf(other), @@ -85,10 +84,10 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ ); } // bounce with tiny damping - updatedCurrent.velocity = std.mul( + current.velocity = std.mul( 0.99, std.sub( - updatedCurrent.velocity, + current.velocity, std.mul( (((2 * other.mass) / (current.mass + other.mass)) * std.dot( @@ -107,22 +106,22 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ isSmaller(currentId, otherId)); if (isCurrentAbsorbed) { // absorbed by the other - updatedCurrent.destroyed = 1; + current.destroyed = 1; } else { // absorbs the other - const m1 = updatedCurrent.mass; + const m1 = current.mass; const m2 = other.mass; - updatedCurrent.velocity = std.add( - std.mul(m1 / (m1 + m2), updatedCurrent.velocity), + current.velocity = std.add( + std.mul(m1 / (m1 + m2), current.velocity), std.mul(m2 / (m1 + m2), other.velocity), ); - updatedCurrent.mass = m1 + m2; + current.mass = m1 + m2; } } } } - computeLayout.$.outState[input.gid.x] = CelestialBody(updatedCurrent); + computeLayout.$.outState[input.gid.x] = CelestialBody(current); }); export const computeGravityShader = tgpu['~unstable'].computeFn({ diff --git a/packages/typegpu/src/std/boolean.ts b/packages/typegpu/src/std/boolean.ts index 5a22168e0d..f18f6010f1 100644 --- a/packages/typegpu/src/std/boolean.ts +++ b/packages/typegpu/src/std/boolean.ts @@ -1,6 +1,6 @@ import { dualImpl } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; -import type { AnyData } from '../data/dataTypes.ts'; +import { type AnyData, toStorables } from '../data/dataTypes.ts'; import { bool, f32 } from '../data/numeric.ts'; import { isSnippetNumeric, snip } from '../data/snippet.ts'; import { vec2b, vec3b, vec4b } from '../data/vector.ts'; @@ -19,6 +19,7 @@ import { type v4b, } from '../data/wgslTypes.ts'; import { $internal } from '../shared/symbols.ts'; +import { unify } from '../tgsl/conversion.ts'; import { sub } from './operators.ts'; function correspondingBooleanVectorSchema(dataType: AnyData) { @@ -337,7 +338,11 @@ function cpuSelect( */ export const select = dualImpl({ name: 'select', - signature: (...argTypes) => ({ argTypes, returnType: argTypes[0] }), + signature: (...args) => { + const [f, t, cond] = toStorables(args); + const [uf, ut] = unify<[AnyData, AnyData]>([f, t]) ?? [f, t]; + return ({ argTypes: [uf, ut, cond], returnType: uf }); + }, normalImpl: cpuSelect, codegenImpl: (f, t, cond) => stitch`select(${f}, ${t}, ${cond})`, }); diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index de84391187..a3ac12c5cd 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -417,7 +417,6 @@ export function coerceToSnippet(value: unknown): Snippet { } if (typeof value === 'boolean') { - // It's a primitive, so `ref` is false return snip(value, bool, /* ref */ 'constant'); } diff --git a/packages/typegpu/tests/examples/individual/gravity.test.ts b/packages/typegpu/tests/examples/individual/gravity.test.ts index 3b08bfd923..6a7599a209 100644 --- a/packages/typegpu/tests/examples/individual/gravity.test.ts +++ b/packages/typegpu/tests/examples/individual/gravity.test.ts @@ -65,7 +65,6 @@ describe('gravity example', () => { @compute @workgroup_size(1) fn computeCollisionsShader_0(input: computeCollisionsShader_Input_7) { var currentId = input.gid.x; var current = CelestialBody_2(inState_1[currentId].destroyed, inState_1[currentId].position, inState_1[currentId].velocity, inState_1[currentId].mass, inState_1[currentId].radiusMultiplier, inState_1[currentId].collisionBehavior, inState_1[currentId].textureIndex, inState_1[currentId].ambientLightFactor); - let updatedCurrent = (¤t); if ((current.destroyed == 0)) { for (var i = 0; (i < celestialBodiesCount_3); i++) { var otherId = u32(i); @@ -75,25 +74,25 @@ describe('gravity example', () => { } if (((current.collisionBehavior == 1) && (other.collisionBehavior == 1))) { if (isSmaller_5(currentId, otherId)) { - (*updatedCurrent).position = (other.position + ((radiusOf_4(current) + radiusOf_4(other)) * normalize((current.position - other.position)))); + current.position = (other.position + ((radiusOf_4(current) + radiusOf_4(other)) * normalize((current.position - other.position)))); } - (*updatedCurrent).velocity = (0.99 * ((*updatedCurrent).velocity - (((((2 * other.mass) / (current.mass + other.mass)) * dot((current.velocity - other.velocity), (current.position - other.position))) / pow(distance(current.position, other.position), 2)) * (current.position - other.position)))); + current.velocity = (0.99 * (current.velocity - (((((2 * other.mass) / (current.mass + other.mass)) * dot((current.velocity - other.velocity), (current.position - other.position))) / pow(distance(current.position, other.position), 2)) * (current.position - other.position)))); } else { var isCurrentAbsorbed = ((current.collisionBehavior == 1) || ((current.collisionBehavior == 2) && isSmaller_5(currentId, otherId))); if (isCurrentAbsorbed) { - (*updatedCurrent).destroyed = 1; + current.destroyed = 1; } else { - var m1 = (*updatedCurrent).mass; + var m1 = current.mass; var m2 = other.mass; - (*updatedCurrent).velocity = (((m1 / (m1 + m2)) * (*updatedCurrent).velocity) + ((m2 / (m1 + m2)) * other.velocity)); - (*updatedCurrent).mass = (m1 + m2); + current.velocity = (((m1 / (m1 + m2)) * current.velocity) + ((m2 / (m1 + m2)) * other.velocity)); + current.mass = (m1 + m2); } } } } - outState_6[input.gid.x] = (*updatedCurrent); + outState_6[input.gid.x] = current; } struct CelestialBody_2 { From 51c29190bff0d92910cb7bb6236106ee0d242096 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Wed, 8 Oct 2025 19:08:43 +0200 Subject: [PATCH 22/59] Apply suggestion from @aleksanderkatan Co-authored-by: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> --- .../src/examples/simulation/stable-fluid/simulation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts b/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts index 4daab60b92..4e1ce784ca 100644 --- a/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts +++ b/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts @@ -111,7 +111,7 @@ export const advectFn = tgpu['~unstable'].computeFn({ const clampedPos = std.clamp( prevPos, d.vec2f(-0.5), - d.vec2f(texSize.xy).sub(d.vec2f(0.5)), + d.vec2f(texSize.xy).sub(0.5), ); const normalizedPos = std.div( clampedPos.add(d.vec2f(0.5)), From b035d450a63c57e03e5ca5eff931c75b93021588 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Wed, 8 Oct 2025 19:08:59 +0200 Subject: [PATCH 23/59] Apply suggestion from @aleksanderkatan Co-authored-by: Aleksander Katan <56294622+aleksanderkatan@users.noreply.github.com> --- .../src/examples/simulation/stable-fluid/simulation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts b/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts index 4e1ce784ca..574ddafd7b 100644 --- a/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts +++ b/apps/typegpu-docs/src/examples/simulation/stable-fluid/simulation.ts @@ -114,7 +114,7 @@ export const advectFn = tgpu['~unstable'].computeFn({ d.vec2f(texSize.xy).sub(0.5), ); const normalizedPos = std.div( - clampedPos.add(d.vec2f(0.5)), + clampedPos.add(0.5), d.vec2f(texSize.xy), ); From 3932b2f426bca60411092f1719c52e4609a43e7d Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 9 Oct 2025 11:56:22 +0200 Subject: [PATCH 24/59] Update stable-fluid.test.ts --- .../typegpu/tests/examples/individual/stable-fluid.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts index 19e083f79f..fbebf8820f 100644 --- a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts +++ b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts @@ -46,8 +46,8 @@ describe('stable-fluid example', () => { var velocity = textureLoad(src_1, pixelPos, 0); var timeStep = simParams_3.dt; var prevPos = (vec2f(pixelPos) - (timeStep * velocity.xy)); - var clampedPos = clamp(prevPos, vec2f(-0.5), (vec2f(texSize.xy) - vec2f(0.5))); - var normalizedPos = ((clampedPos + vec2f(0.5)) / vec2f(texSize.xy)); + var clampedPos = clamp(prevPos, vec2f(-0.5), (vec2f(texSize.xy) - 0.5)); + var normalizedPos = ((clampedPos + 0.5) / vec2f(texSize.xy)); var prevVelocity = textureSampleLevel(src_1, linSampler_5, normalizedPos, 0); textureStore(dst_2, pixelPos, prevVelocity); } From 62936f8b03e2e4ba52bb14d9b78eff9243b00552 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 9 Oct 2025 12:30:43 +0200 Subject: [PATCH 25/59] Simplify 3D Fish compute --- .../src/examples/rendering/3d-fish/compute.ts | 67 ++++++------------- .../tests/examples/individual/3d-fish.test.ts | 42 ++++++------ 2 files changed, 44 insertions(+), 65 deletions(-) diff --git a/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts b/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts index 031778ec53..2eba9f3fdd 100644 --- a/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts +++ b/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts @@ -2,7 +2,7 @@ import tgpu from 'typegpu'; import * as d from 'typegpu/data'; import * as std from 'typegpu/std'; import * as p from './params.ts'; -import { computeBindGroupLayout as layout, ModelData } from './schemas.ts'; +import { computeBindGroupLayout as layout } from './schemas.ts'; import { projectPointOnLine } from './tgsl-helpers.ts'; export const computeShader = tgpu['~unstable'].computeFn({ @@ -10,17 +10,7 @@ export const computeShader = tgpu['~unstable'].computeFn({ workgroupSize: [p.workGroupSize], })((input) => { const fishIndex = input.gid.x; - // TODO: replace it with struct copy when Chromium is fixed - const fishData = ModelData({ - position: layout.$.currentFishData[fishIndex].position, - direction: layout.$.currentFishData[fishIndex].direction, - scale: layout.$.currentFishData[fishIndex].scale, - variant: layout.$.currentFishData[fishIndex].variant, - applySeaDesaturation: - layout.$.currentFishData[fishIndex].applySeaDesaturation, - applySeaFog: layout.$.currentFishData[fishIndex].applySeaFog, - applySinWave: layout.$.currentFishData[fishIndex].applySinWave, - }); + const fishData = layout.$.currentFishData[fishIndex]; let separation = d.vec3f(); let alignment = d.vec3f(); let alignmentCount = 0; @@ -34,16 +24,7 @@ export const computeShader = tgpu['~unstable'].computeFn({ continue; } - // TODO: replace it with struct copy when Chromium is fixed - const other = ModelData({ - position: layout.$.currentFishData[i].position, - direction: layout.$.currentFishData[i].direction, - scale: layout.$.currentFishData[i].scale, - variant: layout.$.currentFishData[i].variant, - applySeaDesaturation: layout.$.currentFishData[i].applySeaDesaturation, - applySeaFog: layout.$.currentFishData[i].applySeaFog, - applySinWave: layout.$.currentFishData[i].applySinWave, - }); + const other = layout.$.currentFishData[i]; const dist = std.length(std.sub(fishData.position, other.position)); if (dist < layout.$.fishBehavior.separationDist) { separation = std.add( @@ -99,36 +80,32 @@ export const computeShader = tgpu['~unstable'].computeFn({ rayRepulsion = std.mul(str, std.normalize(diff)); } - fishData.direction = std.add( - fishData.direction, - std.mul(layout.$.fishBehavior.separationStr, separation), + let direction = d.vec3f(fishData.direction); + + direction = direction.add( + separation.mul(layout.$.fishBehavior.separationStr), ); - fishData.direction = std.add( - fishData.direction, - std.mul(layout.$.fishBehavior.alignmentStr, alignment), + direction = direction.add( + alignment.mul(layout.$.fishBehavior.alignmentStr), ); - fishData.direction = std.add( - fishData.direction, - std.mul(layout.$.fishBehavior.cohesionStr, cohesion), + direction = direction.add( + cohesion.mul(layout.$.fishBehavior.cohesionStr), ); - fishData.direction = std.add( - fishData.direction, - std.mul(p.fishWallRepulsionStrength, wallRepulsion), + direction = direction.add( + wallRepulsion.mul(p.fishWallRepulsionStrength), ); - fishData.direction = std.add( - fishData.direction, - std.mul(p.fishMouseRayRepulsionStrength, rayRepulsion), + direction = direction.add( + rayRepulsion.mul(p.fishMouseRayRepulsionStrength), ); - - fishData.direction = std.mul( - std.clamp(std.length(fishData.direction), 0.0, 0.01), - std.normalize(fishData.direction), + direction = std.normalize(direction).mul( + std.clamp(std.length(fishData.direction), 0, 0.01), ); - const translation = std.mul( + const translation = direction.mul( d.f32(std.min(999, layout.$.timePassed)) / 8, - fishData.direction, ); - fishData.position = std.add(fishData.position, translation); - layout.$.nextFishData[fishIndex] = ModelData(fishData); + + const nextFishData = layout.$.nextFishData[fishIndex]; + nextFishData.position = fishData.position.add(translation); + nextFishData.direction = d.vec3f(direction); }); diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index 7dd822d1ac..f005368439 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -118,7 +118,7 @@ describe('3d fish example', () => { @group(0) @binding(2) var mouseRay_5: MouseRay_6; - fn projectPointOnLine_8(point: ptr, line: ptr) -> vec3f { + fn projectPointOnLine_8(point: ptr, line: ptr) -> vec3f { var pointVector = ((*point) - (*line).origin); var projection = dot(pointVector, (*line).dir); return ((*line).origin + ((*line).dir * projection)); @@ -134,7 +134,7 @@ describe('3d fish example', () => { @compute @workgroup_size(256) fn computeShader_0(input: computeShader_Input_11) { var fishIndex = input.gid.x; - var fishData = ModelData_2(currentFishData_1[fishIndex].position, currentFishData_1[fishIndex].direction, currentFishData_1[fishIndex].scale, currentFishData_1[fishIndex].variant, currentFishData_1[fishIndex].applySinWave, currentFishData_1[fishIndex].applySeaFog, currentFishData_1[fishIndex].applySeaDesaturation); + let fishData = (¤tFishData_1[fishIndex]); var separation = vec3f(); var alignment = vec3f(); var alignmentCount = 0; @@ -146,17 +146,17 @@ describe('3d fish example', () => { if ((u32(i) == fishIndex)) { continue; } - var other = ModelData_2(currentFishData_1[i].position, currentFishData_1[i].direction, currentFishData_1[i].scale, currentFishData_1[i].variant, currentFishData_1[i].applySinWave, currentFishData_1[i].applySeaFog, currentFishData_1[i].applySeaDesaturation); - var dist = length((fishData.position - other.position)); + let other = (¤tFishData_1[i]); + var dist = length(((*fishData).position - (*other).position)); if ((dist < fishBehavior_3.separationDist)) { - separation = (separation + (fishData.position - other.position)); + separation = (separation + ((*fishData).position - (*other).position)); } if ((dist < fishBehavior_3.alignmentDist)) { - alignment = (alignment + other.direction); + alignment = (alignment + (*other).direction); alignmentCount = (alignmentCount + 1); } if ((dist < fishBehavior_3.cohesionDist)) { - cohesion = (cohesion + other.position); + cohesion = (cohesion + (*other).position); cohesionCount = (cohesionCount + 1); } } @@ -164,13 +164,13 @@ describe('3d fish example', () => { alignment = ((1f / f32(alignmentCount)) * alignment); } if ((cohesionCount > 0)) { - cohesion = (((1f / f32(cohesionCount)) * cohesion) - fishData.position); + cohesion = (((1f / f32(cohesionCount)) * cohesion) - (*fishData).position); } for (var i = 0; (i < 3); i += 1) { var repulsion = vec3f(); repulsion[i] = 1; var axisAquariumSize = (vec3f(10, 4, 10)[i] / 2f); - var axisPosition = fishData.position[i]; + var axisPosition = (*fishData).position[i]; const distance = 0.1; if ((axisPosition > (axisAquariumSize - distance))) { var str = (axisPosition - (axisAquariumSize - distance)); @@ -182,21 +182,23 @@ describe('3d fish example', () => { } } if ((mouseRay_5.activated == 1)) { - var proj = projectPointOnLine_8((&fishData.position), (&mouseRay_5.line)); - var diff = (fishData.position - proj); + var proj = projectPointOnLine_8((&(*fishData).position), (&mouseRay_5.line)); + var diff = ((*fishData).position - proj); const limit = 0.9; var str = (pow(2, clamp((limit - length(diff)), 0, limit)) - 1); rayRepulsion = (str * normalize(diff)); } - fishData.direction = (fishData.direction + (fishBehavior_3.separationStr * separation)); - fishData.direction = (fishData.direction + (fishBehavior_3.alignmentStr * alignment)); - fishData.direction = (fishData.direction + (fishBehavior_3.cohesionStr * cohesion)); - fishData.direction = (fishData.direction + (1e-4 * wallRepulsion)); - fishData.direction = (fishData.direction + (5e-4 * rayRepulsion)); - fishData.direction = (clamp(length(fishData.direction), 0, 0.01) * normalize(fishData.direction)); - var translation = ((min(999, timePassed_9) / 8f) * fishData.direction); - fishData.position = (fishData.position + translation); - nextFishData_10[fishIndex] = fishData; + var direction = (*fishData).direction; + direction = (direction + (separation * fishBehavior_3.separationStr)); + direction = (direction + (alignment * fishBehavior_3.alignmentStr)); + direction = (direction + (cohesion * fishBehavior_3.cohesionStr)); + direction = (direction + (wallRepulsion * 1e-4)); + direction = (direction + (rayRepulsion * 5e-4)); + direction = (normalize(direction) * clamp(length((*fishData).direction), 0, 0.01)); + var translation = (direction * (min(999, timePassed_9) / 8f)); + let nextFishData = (&nextFishData_10[fishIndex]); + (*nextFishData).position = ((*fishData).position + translation); + (*nextFishData).direction = direction; } struct ModelData_2 { From 14c72823b200aeba7b0b5db68edfa7156dada035 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 9 Oct 2025 15:33:52 +0200 Subject: [PATCH 26/59] Update compute.ts --- .../src/examples/rendering/3d-fish/compute.ts | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts b/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts index 2eba9f3fdd..a166f37ec1 100644 --- a/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts +++ b/apps/typegpu-docs/src/examples/rendering/3d-fish/compute.ts @@ -25,24 +25,21 @@ export const computeShader = tgpu['~unstable'].computeFn({ } const other = layout.$.currentFishData[i]; - const dist = std.length(std.sub(fishData.position, other.position)); + const dist = std.length(fishData.position.sub(other.position)); if (dist < layout.$.fishBehavior.separationDist) { - separation = std.add( - separation, - std.sub(fishData.position, other.position), - ); + separation = separation.add(fishData.position.sub(other.position)); } if (dist < layout.$.fishBehavior.alignmentDist) { - alignment = std.add(alignment, other.direction); + alignment = alignment.add(other.direction); alignmentCount = alignmentCount + 1; } if (dist < layout.$.fishBehavior.cohesionDist) { - cohesion = std.add(cohesion, other.position); + cohesion = cohesion.add(other.position); cohesionCount = cohesionCount + 1; } } if (alignmentCount > 0) { - alignment = std.mul(1 / d.f32(alignmentCount), alignment); + alignment = alignment.mul(1 / d.f32(alignmentCount)); } if (cohesionCount > 0) { cohesion = std.sub( @@ -60,12 +57,12 @@ export const computeShader = tgpu['~unstable'].computeFn({ if (axisPosition > axisAquariumSize - distance) { const str = axisPosition - (axisAquariumSize - distance); - wallRepulsion = std.sub(wallRepulsion, std.mul(str, repulsion)); + wallRepulsion = wallRepulsion.sub(repulsion.mul(str)); } if (axisPosition < -axisAquariumSize + distance) { const str = -axisAquariumSize + distance - axisPosition; - wallRepulsion = std.add(wallRepulsion, std.mul(str, repulsion)); + wallRepulsion = wallRepulsion.add(repulsion.mul(str)); } } @@ -74,10 +71,10 @@ export const computeShader = tgpu['~unstable'].computeFn({ fishData.position, layout.$.mouseRay.line, ); - const diff = std.sub(fishData.position, proj); + const diff = fishData.position.sub(proj); const limit = p.fishMouseRayRepulsionDistance; const str = std.pow(2, std.clamp(limit - std.length(diff), 0, limit)) - 1; - rayRepulsion = std.mul(str, std.normalize(diff)); + rayRepulsion = std.normalize(diff).mul(str); } let direction = d.vec3f(fishData.direction); From e90832d1abc1dab2b891d12d31a8efe708bf0cf9 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 9 Oct 2025 16:11:50 +0200 Subject: [PATCH 27/59] Review fixes --- packages/typegpu/src/data/vectorImpl.ts | 2 +- packages/typegpu/src/tgsl/consoleLog/logGenerator.ts | 4 +--- packages/typegpu/src/tgsl/conversion.ts | 4 +++- packages/typegpu/src/tgsl/generationHelpers.ts | 6 ------ packages/typegpu/src/tgsl/shellless.ts | 4 ++++ .../typegpu/tests/examples/individual/3d-fish.test.ts | 8 ++++---- packages/typegpu/tests/tgsl/consoleLog.test.ts | 4 ++-- 7 files changed, 15 insertions(+), 17 deletions(-) diff --git a/packages/typegpu/src/data/vectorImpl.ts b/packages/typegpu/src/data/vectorImpl.ts index 266e86fd43..f0b7ce5d59 100644 --- a/packages/typegpu/src/data/vectorImpl.ts +++ b/packages/typegpu/src/data/vectorImpl.ts @@ -41,7 +41,7 @@ export abstract class VecBase extends Array implements SelfResolvable { [$resolve](): ResolvedSnippet { const schema = this[$internal].elementSchema; if (this.every((e) => !e)) { - return snip(`${this.kind}()`, schema, /* ref */ 'runtime'); + return snip(`${this.kind}()`, schema, /* ref */ 'constant'); } if (this.every((e) => this[0] === e)) { return snip(`${this.kind}(${this[0]})`, schema, /* ref */ 'runtime'); diff --git a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts index 283243e855..de90012caf 100644 --- a/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts +++ b/packages/typegpu/src/tgsl/consoleLog/logGenerator.ts @@ -38,9 +38,7 @@ export class LogGeneratorNullImpl implements LogGenerator { return undefined; } generateLog(): Snippet { - console.warn( - "'console.log' is currently only supported in compute pipelines.", - ); + console.warn("'console.log' is only supported when resolving pipelines."); return snip('/* console.log() */', Void, /* ref */ 'runtime'); } } diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 75fdbbfed5..5d2cfcde25 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -335,7 +335,9 @@ export function tryConvertSnippet( if (!converted) { throw new WgslTypeError( - `Cannot convert value of type '${snippet.dataType}' to type '${targetDataType.type}'`, + `Cannot convert value of type '${ + String(snippet.dataType) + }' to type '${targetDataType.type}'`, ); } diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index a3ac12c5cd..d88c37f78d 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -42,7 +42,6 @@ import { isMat, isMatInstance, isNaturallyRef, - isNumericSchema, isVec, isVecInstance, isWgslArray, @@ -207,11 +206,6 @@ export function accessProp( ); } - if (target.dataType.type === 'bool' || isNumericSchema(target.dataType)) { - // No props to be accessed here - return undefined; - } - const propLength = propName.length; if ( isVec(target.dataType) && diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index 002d9246c4..6d8e295a73 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -22,6 +22,10 @@ function shallowEqualSchemas(a: AnyData, b: AnyData): boolean { return a.elementCount === b.elementCount && shallowEqualSchemas(a.elementType as AnyData, b.elementType as AnyData); } + if (a.type === 'struct' && b.type === 'struct') { + // Only structs with the same identity are considered equal + return a === b; + } return true; } diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index f005368439..9c5653a7bd 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -161,7 +161,7 @@ describe('3d fish example', () => { } } if ((alignmentCount > 0)) { - alignment = ((1f / f32(alignmentCount)) * alignment); + alignment = (alignment * (1f / f32(alignmentCount))); } if ((cohesionCount > 0)) { cohesion = (((1f / f32(cohesionCount)) * cohesion) - (*fishData).position); @@ -174,11 +174,11 @@ describe('3d fish example', () => { const distance = 0.1; if ((axisPosition > (axisAquariumSize - distance))) { var str = (axisPosition - (axisAquariumSize - distance)); - wallRepulsion = (wallRepulsion - (str * repulsion)); + wallRepulsion = (wallRepulsion - (repulsion * str)); } if ((axisPosition < (-axisAquariumSize + distance))) { var str = ((-axisAquariumSize + distance) - axisPosition); - wallRepulsion = (wallRepulsion + (str * repulsion)); + wallRepulsion = (wallRepulsion + (repulsion * str)); } } if ((mouseRay_5.activated == 1)) { @@ -186,7 +186,7 @@ describe('3d fish example', () => { var diff = ((*fishData).position - proj); const limit = 0.9; var str = (pow(2, clamp((limit - length(diff)), 0, limit)) - 1); - rayRepulsion = (str * normalize(diff)); + rayRepulsion = (normalize(diff) * str); } var direction = (*fishData).direction; direction = (direction + (separation * fishBehavior_3.separationStr)); diff --git a/packages/typegpu/tests/tgsl/consoleLog.test.ts b/packages/typegpu/tests/tgsl/consoleLog.test.ts index e2129b5ce3..d7f6ea6d1a 100644 --- a/packages/typegpu/tests/tgsl/consoleLog.test.ts +++ b/packages/typegpu/tests/tgsl/consoleLog.test.ts @@ -33,7 +33,7 @@ describe('wgslGenerator with console.log', () => { `); expect(consoleWarnSpy).toHaveBeenCalledWith( - "'console.log' is currently only supported in compute pipelines.", + "'console.log' is only supported when resolving pipelines.", ); expect(consoleWarnSpy).toHaveBeenCalledTimes(1); }); @@ -265,7 +265,7 @@ describe('wgslGenerator with console.log', () => { .createPipeline(); expect(asWgsl(pipeline)).toMatchInlineSnapshot(` - + "@group(0) @binding(0) var indexBuffer: atomic; struct SerializedLogData { From 1e2df56dd78e70b4663aa8db2e1abcf39a1838c7 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Mon, 13 Oct 2025 13:15:12 +0200 Subject: [PATCH 28/59] Updates after changing 'kernel' to 'use gpu' --- .../typegpu/tests/tgsl/memberAccess.test.ts | 18 +++++++++--------- packages/typegpu/tests/tgsl/shellless.test.ts | 2 +- packages/typegpu/tests/utils/parseResolved.ts | 8 ++++---- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/typegpu/tests/tgsl/memberAccess.test.ts b/packages/typegpu/tests/tgsl/memberAccess.test.ts index 6594c98fec..a8192e18b5 100644 --- a/packages/typegpu/tests/tgsl/memberAccess.test.ts +++ b/packages/typegpu/tests/tgsl/memberAccess.test.ts @@ -12,12 +12,12 @@ describe('Member Access', () => { it('should access member properties of literals', () => { expectSnippetOf(() => { - 'kernel'; + 'use gpu'; Boid().pos; }).toStrictEqual(snip('Boid().pos', d.vec3f, 'runtime')); expectSnippetOf(() => { - 'kernel'; + 'use gpu'; Boid().pos.xyz; }).toStrictEqual(snip('Boid().pos.xyz', d.vec3f, 'runtime')); }); @@ -26,12 +26,12 @@ describe('Member Access', () => { const boid = Boid({ pos: d.vec3f(1, 2, 3) }); expectSnippetOf(() => { - 'kernel'; + 'use gpu'; boid.pos; }).toStrictEqual(snip(d.vec3f(1, 2, 3), d.vec3f, 'constant')); expectSnippetOf(() => { - 'kernel'; + 'use gpu'; boid.pos.zyx; }).toStrictEqual(snip(d.vec3f(3, 2, 1), d.vec3f, 'constant')); }); @@ -40,19 +40,19 @@ describe('Member Access', () => { const boidVar = tgpu.privateVar(Boid); expectSnippetOf(() => { - 'kernel'; + 'use gpu'; boidVar.$.pos; }).toStrictEqual(snip('boidVar.pos', d.vec3f, 'private')); expectSnippetOf(() => { - 'kernel'; + 'use gpu'; boidVar.$.pos.xyz; }).toStrictEqual(snip('boidVar.pos.xyz', d.vec3f, 'runtime')); // < swizzles are new objects }); it('derefs access to local variables with proper address space', () => { expectSnippetOf(() => { - 'kernel'; + 'use gpu'; // Creating a new Boid instance const boid = Boid(); // Taking a reference that is local to this function @@ -66,14 +66,14 @@ describe('Member Access', () => { const boidMutable = root.createMutable(Boid); expectSnippetOf(() => { - 'kernel'; + 'use gpu'; // Taking a reference to a storage variable const boidRef = boidReadonly.$; boidRef.pos; }).toStrictEqual(snip('(*boidRef).pos', d.vec3f, 'readonly')); expectSnippetOf(() => { - 'kernel'; + 'use gpu'; // Taking a reference to a storage variable const boidRef = boidMutable.$; boidRef.pos; diff --git a/packages/typegpu/tests/tgsl/shellless.test.ts b/packages/typegpu/tests/tgsl/shellless.test.ts index b9a4686312..418f5b2638 100644 --- a/packages/typegpu/tests/tgsl/shellless.test.ts +++ b/packages/typegpu/tests/tgsl/shellless.test.ts @@ -160,7 +160,7 @@ describe('shellless', () => { it('generates pointer type to handle references', () => { const advance = (pos: d.v3f, vel: d.v3f) => { - 'kernel'; + 'use gpu'; pos.x += vel.x; pos.y += vel.y; pos.z += vel.z; diff --git a/packages/typegpu/tests/utils/parseResolved.ts b/packages/typegpu/tests/utils/parseResolved.ts index dd45bf5d9c..c8ee7bf91d 100644 --- a/packages/typegpu/tests/utils/parseResolved.ts +++ b/packages/typegpu/tests/utils/parseResolved.ts @@ -25,7 +25,7 @@ export function asWgsl(...values: unknown[]): string { }); } -function extractSnippetFromKernel(cb: () => unknown): Snippet { +function extractSnippetFromFn(cb: () => unknown): Snippet { const ctx = new ResolutionCtxImpl({ namespace: namespace({ names: 'strict' }), }); @@ -38,7 +38,7 @@ function extractSnippetFromKernel(cb: () => unknown): Snippet { const meta = getMetaData(cb); if (!meta || !meta.ast) { - throw new Error('No metadata found for the kernel'); + throw new Error('No metadata found for the function'); } ctx.pushMode(new CodegenState()); @@ -83,11 +83,11 @@ function extractSnippetFromKernel(cb: () => unknown): Snippet { export function expectSnippetOf( cb: () => unknown, ): Assertion { - return expect(extractSnippetFromKernel(cb)); + return expect(extractSnippetFromFn(cb)); } export function expectDataTypeOf( cb: () => unknown, ): Assertion { - return expect(extractSnippetFromKernel(cb).dataType); + return expect(extractSnippetFromFn(cb).dataType); } From bbc05089364d96adac09d9d468d58ba17ce4cdc1 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 16 Oct 2025 09:52:56 +0200 Subject: [PATCH 29/59] feat: Better constant handling for ref/value tracking (#1801) --- .../typegpu/src/core/constant/tgpuConstant.ts | 62 +++++++++++++----- packages/typegpu/src/data/snippet.ts | 3 +- .../typegpu/src/tgsl/generationHelpers.ts | 12 ++-- packages/typegpu/src/tgsl/shellless.ts | 11 ++++ packages/typegpu/src/tgsl/wgslGenerator.ts | 51 ++++++++++----- packages/typegpu/tests/constant.test.ts | 63 ++++++++++++++++--- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 4 +- 7 files changed, 160 insertions(+), 46 deletions(-) diff --git a/packages/typegpu/src/core/constant/tgpuConstant.ts b/packages/typegpu/src/core/constant/tgpuConstant.ts index 331fd45e05..3680b6832c 100644 --- a/packages/typegpu/src/core/constant/tgpuConstant.ts +++ b/packages/typegpu/src/core/constant/tgpuConstant.ts @@ -1,6 +1,5 @@ -import { schemaCallWrapper } from '../../data/schemaCallWrapper.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; -import type { AnyWgslData } from '../../data/wgslTypes.ts'; +import { type AnyWgslData, isNaturallyRef } from '../../data/wgslTypes.ts'; import { inCodegenMode } from '../../execMode.ts'; import type { TgpuNamable } from '../../shared/meta.ts'; import { getName, setName } from '../../shared/meta.ts'; @@ -18,11 +17,17 @@ import { valueProxyHandler } from '../valueProxyUtils.ts'; // Public API // ---------- +type DeepReadonly = T extends { [$internal]: unknown } ? T + : T extends unknown[] ? ReadonlyArray> + : T extends Record + ? { readonly [K in keyof T]: DeepReadonly } + : T; + export interface TgpuConst extends TgpuNamable { - readonly [$gpuValueOf]: InferGPU; - readonly value: InferGPU; - readonly $: InferGPU; + readonly [$gpuValueOf]: DeepReadonly>; + readonly value: DeepReadonly>; + readonly $: DeepReadonly>; readonly [$internal]: { /** Makes it differentiable on the type level. Does not exist at runtime. */ @@ -44,16 +49,35 @@ export function constant( // Implementation // -------------- +function deepFreeze(object: T): T { + // Retrieve the property names defined on object + const propNames = Reflect.ownKeys(object); + + // Freeze properties before freezing self + for (const name of propNames) { + // biome-ignore lint/suspicious/noExplicitAny: chill TypeScript + const value = (object as any)[name]; + + if ((value && typeof value === 'object') || typeof value === 'function') { + deepFreeze(value); + } + } + + return Object.freeze(object); +} + class TgpuConstImpl implements TgpuConst, SelfResolvable { readonly [$internal] = {}; - readonly #value: InferGPU; + readonly #value: DeepReadonly>; constructor( public readonly dataType: TDataType, value: InferGPU, ) { - this.#value = value; + this.#value = value && typeof value === 'object' + ? deepFreeze(value) as DeepReadonly> + : value as DeepReadonly>; } $name(label: string) { @@ -71,35 +95,43 @@ class TgpuConstImpl // Why not a ref? // 1. On the WGSL side, we cannot take pointers to constants. // 2. On the JS side, we copy the constant each time we access it, so we're safe. - return snip(id, this.dataType, /* ref */ 'constant'); + return snip( + id, + this.dataType, + /* ref */ isNaturallyRef(this.dataType) ? 'constant-ref' : 'constant', + ); } toString() { return `const:${getName(this) ?? ''}`; } - get [$gpuValueOf](): InferGPU { + get [$gpuValueOf](): DeepReadonly> { const dataType = this.dataType; return new Proxy({ [$internal]: true, get [$ownSnippet]() { - return snip(this, dataType, /* ref */ 'constant'); + return snip( + this, + dataType, + /* ref */ isNaturallyRef(dataType) ? 'constant-ref' : 'constant', + ); }, [$resolve]: (ctx) => ctx.resolve(this), toString: () => `const:${getName(this) ?? ''}.$`, - }, valueProxyHandler) as InferGPU; + }, valueProxyHandler) as DeepReadonly>; } - get value(): InferGPU { + get $(): DeepReadonly> { if (inCodegenMode()) { return this[$gpuValueOf]; } - return schemaCallWrapper(this.dataType, this.#value); + return this.#value; } - get $(): InferGPU { - return this.value; + get value(): DeepReadonly> { + return this.$; } } diff --git a/packages/typegpu/src/data/snippet.ts b/packages/typegpu/src/data/snippet.ts index ad6db811ff..8d8b7a9823 100644 --- a/packages/typegpu/src/data/snippet.ts +++ b/packages/typegpu/src/data/snippet.ts @@ -20,7 +20,8 @@ export type RefSpace = // (not to be confused with 'comptime') // note that this doesn't automatically mean the value can be stored in a `const` // variable, more so that it's valid to do so in WGSL (but not necessarily safe to do in TGSL) - | 'constant'; + | 'constant' + | 'constant-ref'; export function isSpaceRef(space: RefSpace) { return space !== 'runtime' && space !== 'constant'; diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index d88c37f78d..642660735e 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -200,7 +200,7 @@ export function accessProp( propType, /* ref */ isRef(target) && isNaturallyRef(propType) ? target.ref - : target.ref === 'constant' + : target.ref === 'constant' || target.ref === 'constant-ref' ? 'constant' : 'runtime', ); @@ -228,7 +228,9 @@ export function accessProp( : stitch`${target}.${propName}`, swizzleType, // Swizzling creates new vectors (unless they're on the lhs of an assignment, but that's not yet supported in WGSL) - /* ref */ target.ref === 'constant' ? 'constant' : 'runtime', + /* ref */ target.ref === 'constant' || target.ref === 'constant-ref' + ? 'constant' + : 'runtime', ); } @@ -262,7 +264,7 @@ export function accessIndex( elementType, /* ref */ isRef(target) && isNaturallyRef(elementType) ? target.ref - : target.ref === 'constant' + : target.ref === 'constant' || target.ref === 'constant-ref' ? 'constant' : 'runtime', ); @@ -276,7 +278,9 @@ export function accessIndex( ? (target.value as any)[index.value as any] : stitch`${target}[${index}]`, target.dataType.primitive, - /* ref */ target.ref === 'constant' ? 'constant' : 'runtime', + /* ref */ target.ref === 'constant' || target.ref === 'constant-ref' + ? 'constant' + : 'runtime', ); } diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index 6d8e295a73..9380fa737d 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -6,6 +6,7 @@ import type { AnyData } from '../data/dataTypes.ts'; import { INTERNAL_createPtr } from '../data/ptr.ts'; import { refSpaceToPtrParams, type Snippet } from '../data/snippet.ts'; import { isPtr, type StorableData } from '../data/wgslTypes.ts'; +import { getResolutionCtx } from '../execMode.ts'; import { getMetaData, getName } from '../shared/meta.ts'; import { concretize } from './generationHelpers.ts'; @@ -52,6 +53,16 @@ export class ShelllessRepository { ? refSpaceToPtrParams[s.ref as keyof typeof refSpaceToPtrParams] : undefined; + if (s.ref === 'constant-ref') { + // biome-ignore lint/style/noNonNullAssertion: it's there + const ctx = getResolutionCtx()!; + throw new Error( + `Cannot pass constant references as function arguments. Explicitly copy them by wrapping them in a schema: '${ + ctx.resolve(type).value + }(...)'`, + ); + } + return ptrParams !== undefined && !isPtr(type) ? INTERNAL_createPtr( ptrParams.space, diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 18c68ca3b2..c760094151 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -141,18 +141,26 @@ ${this.ctx.pre}}`; } public blockVariable( + varType: 'var' | 'let' | 'const', id: string, dataType: wgsl.AnyWgslData | UnknownData, ref: RefSpace, ): Snippet { + let varRef: RefSpace = 'runtime'; + if (ref === 'constant-ref') { + // Even types that aren't naturally referential (like vectors or structs) should + // be treated as constant references when assigned to a const. + varRef = 'constant-ref'; + } else if (wgsl.isNaturallyRef(dataType)) { + varRef = isSpaceRef(ref) ? ref : 'this-function'; + } else if (ref === 'constant' && varType === 'const') { + varRef = 'constant'; + } + const snippet = snip( this.ctx.makeNameValid(id), dataType, - /* ref */ wgsl.isNaturallyRef(dataType) - ? isSpaceRef(ref) ? ref : 'this-function' - : ref === 'constant' - ? 'constant' - : 'runtime', + /* ref */ varRef, ); this.ctx.defineVariable(id, snippet); return snippet; @@ -238,12 +246,20 @@ ${this.ctx.pre}}`; const rhsStr = this.ctx.resolve(convRhs.value, convRhs.dataType).value; const type = operatorToType(convLhs.dataType, op, convRhs.dataType); - if (exprType === NODE.assignmentExpr && isRef(rhsExpr)) { - throw new WgslTypeError( - `'${lhsStr} = ${rhsStr}' is invalid, because references cannot be assigned.\n-----\nTry '${lhsStr} = ${ - this.ctx.resolve(rhsExpr.dataType).value - }(${rhsStr})' instead.\n-----`, - ); + if (exprType === NODE.assignmentExpr) { + if (convLhs.ref === 'constant' || convLhs.ref === 'constant-ref') { + throw new WgslTypeError( + `'${lhsStr} = ${rhsStr}' is invalid, because ${lhsStr} is a constant.`, + ); + } + + if (isRef(rhsExpr)) { + throw new WgslTypeError( + `'${lhsStr} = ${rhsStr}' is invalid, because references cannot be assigned.\n-----\nTry '${lhsStr} = ${ + this.ctx.resolve(rhsExpr.dataType).value + }(${rhsStr})' instead.\n-----`, + ); + } } return snip( @@ -708,7 +724,7 @@ ${this.ctx.pre}else ${alternate}`; } if (statement[0] === NODE.let || statement[0] === NODE.const) { - let varType = 'var'; + let varType: 'var' | 'let' | 'const' = 'var'; const [stmtType, rawId, rawValue] = statement; const eq = rawValue !== undefined ? this.expression(rawValue) : undefined; @@ -744,9 +760,13 @@ ${this.ctx.pre}else ${alternate}`; ); } - varType = 'let'; - if (!wgsl.isPtr(dataType)) { - dataType = ptrFn(concretize(dataType) as wgsl.StorableData); + if (eq.ref === 'constant-ref') { + varType = 'const'; + } else { + varType = 'let'; + if (!wgsl.isPtr(dataType)) { + dataType = ptrFn(concretize(dataType) as wgsl.StorableData); + } } } else { // Non-referential @@ -760,6 +780,7 @@ ${this.ctx.pre}else ${alternate}`; } const snippet = this.blockVariable( + varType, rawId, concretize(dataType), eq.ref, diff --git a/packages/typegpu/tests/constant.test.ts b/packages/typegpu/tests/constant.test.ts index 2fbc73d37c..2b84e086a6 100644 --- a/packages/typegpu/tests/constant.test.ts +++ b/packages/typegpu/tests/constant.test.ts @@ -3,8 +3,13 @@ import * as d from '../src/data/index.ts'; import tgpu from '../src/index.ts'; import { asWgsl } from './utils/parseResolved.ts'; +const Boid = d.struct({ + pos: d.vec3f, + vel: d.vec3u, +}); + describe('tgpu.const', () => { - it('should inject const declaration when used in functions', () => { + it('should inject const declaration when used in shelled WGSL functions', () => { const x = tgpu.const(d.u32, 2); const fn1 = tgpu.fn([], d.u32)`() { return x; }`.$uses({ x }); @@ -15,12 +20,7 @@ describe('tgpu.const', () => { `); }); - it('allows accessing constants in tgsl through .value', () => { - const Boid = d.struct({ - pos: d.vec3f, - vel: d.vec3u, - }); - + it('allows accessing constants in TypeGPU functions through .$', () => { const boid = tgpu.const(Boid, { pos: d.vec3f(1, 2, 3), vel: d.vec3u(4, 5, 6), @@ -41,10 +41,55 @@ describe('tgpu.const', () => { const boid: Boid = Boid(vec3f(1, 2, 3), vec3u(4, 5, 6)); fn func() { - var pos = boid; - var vel = boid.vel; + const pos = boid; + const vel = boid.vel; const velX = boid.vel.x; }" `); }); + + it('cannot be passed directly to shellless functions', () => { + const fn1 = (v: d.v3f) => { + 'use gpu'; + return v.x * v.y * v.z; + }; + + const foo = tgpu.const(d.vec3f, d.vec3f(1, 2, 3)); + const fn2 = () => { + 'use gpu'; + return fn1(foo.$); + }; + + expect(() => asWgsl(fn2)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:fn2 + - fn*:fn2: Cannot pass constant references as function arguments. Explicitly copy them by wrapping them in a schema: 'vec3f(...)'] + `); + }); + + it('cannot be mutated', () => { + const boid = tgpu.const(Boid, { + pos: d.vec3f(1, 2, 3), + vel: d.vec3u(4, 5, 6), + }); + + const fn = () => { + 'use gpu'; + // @ts-expect-error: Cannot assign to read-only property + boid.$.pos = d.vec3f(0, 0, 0); + }; + + expect(() => asWgsl(fn)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:fn + - fn*:fn: 'boid.pos = vec3f()' is invalid, because boid.pos is a constant.] + `); + + // Since we freeze the object, we cannot mutate when running the function in JS either + expect(() => fn()).toThrowErrorMatchingInlineSnapshot( + `[TypeError: Cannot assign to read only property 'pos' of object '#']`, + ); + }); }); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index c205d99b41..61e3c944bf 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -320,7 +320,7 @@ describe('wgslGenerator', () => { // Check for: const vec = std.mix(d.vec4f(), testUsage.value.a, value); // ^ this part should be a vec4f ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('value', d.i32, 'runtime'); + wgslGenerator.blockVariable('var', 'value', d.i32, 'runtime'); const res2 = wgslGenerator.expression( (astInfo.ast?.body[1][1] as tinyest.Const)[2], ); @@ -332,7 +332,7 @@ describe('wgslGenerator', () => { // ^ this part should be an atomic u32 // ^ this part should be void ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('vec', d.vec4f, 'this-function'); + wgslGenerator.blockVariable('var', 'vec', d.vec4f, 'this-function'); const res3 = wgslGenerator.expression( (astInfo.ast?.body[1][2] as tinyest.Call)[2][0] as tinyest.Expression, ); From 3824a3aef88ae3a9016c8b95d7e4019ac3f546f4 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 30 Oct 2025 16:54:35 +0100 Subject: [PATCH 30/59] Update snapshots --- .../tests/examples/individual/disco.test.ts | 6 +++--- .../tests/examples/individual/slime-mold.test.ts | 16 ++++++++-------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/typegpu/tests/examples/individual/disco.test.ts b/packages/typegpu/tests/examples/individual/disco.test.ts index 7f4aa9baf8..cb09d3cf6e 100644 --- a/packages/typegpu/tests/examples/individual/disco.test.ts +++ b/packages/typegpu/tests/examples/individual/disco.test.ts @@ -69,12 +69,12 @@ describe('disco example', () => { @fragment fn mainFragment_3(_arg_0: mainFragment_Input_9) -> @location(0) vec4f { { var aspectUv = aspectCorrected_4(_arg_0.uv); - var originalUv = aspectUv; + let originalUv = (&aspectUv); var accumulatedColor = vec3f(); for (var iteration = 0; (iteration < 5); iteration++) { aspectUv = (fract((aspectUv * (1.3 * sin(time_6)))) - 0.5); - var radialLength = (length(aspectUv) * exp((-length(originalUv) * 2))); - var paletteColor = palette_7((length(originalUv) + (time_6 * 0.9))); + var radialLength = (length(aspectUv) * exp((-length((*originalUv)) * 2))); + var paletteColor = palette_7((length((*originalUv)) + (time_6 * 0.9))); radialLength = (sin(((radialLength * 8) + time_6)) / 8f); radialLength = abs(radialLength); radialLength = smoothstep(0, 0.1, radialLength); diff --git a/packages/typegpu/tests/examples/individual/slime-mold.test.ts b/packages/typegpu/tests/examples/individual/slime-mold.test.ts index 5b9bd30c9d..385bb8e182 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold.test.ts @@ -150,10 +150,10 @@ describe('slime mold example', () => { @group(0) @binding(1) var params_10: Params_11; - fn sense_9(pos: vec2f, angle: f32, sensorAngleOffset: f32) -> f32 { + fn sense_9(pos: ptr, angle: f32, sensorAngleOffset: f32) -> f32 { var sensorAngle = (angle + sensorAngleOffset); var sensorDir = vec2f(cos(sensorAngle), sin(sensorAngle)); - var sensorPos = (pos + (sensorDir * params_10.sensorDistance)); + var sensorPos = ((*pos) + (sensorDir * params_10.sensorDistance)); var dims = textureDimensions(oldState_4); var dimsf = vec2f(dims); var sensorPosInt = vec2u(clamp(sensorPos, vec2f(), (dimsf - vec2f(1)))); @@ -175,12 +175,12 @@ describe('slime mold example', () => { } randSeed_1(((f32(_arg_0.gid.x) / 2e+5f) + 0.1)); var dims = textureDimensions(oldState_4); - var agent = agentsData_5[_arg_0.gid.x]; + let agent = (&agentsData_5[_arg_0.gid.x]); var random = randFloat01_7(); - var weightForward = sense_9(agent.position, agent.angle, 0); - var weightLeft = sense_9(agent.position, agent.angle, params_10.sensorAngle); - var weightRight = sense_9(agent.position, agent.angle, -params_10.sensorAngle); - var angle = agent.angle; + var weightForward = sense_9((&(*agent).position), (*agent).angle, 0); + var weightLeft = sense_9((&(*agent).position), (*agent).angle, params_10.sensorAngle); + var weightRight = sense_9((&(*agent).position), (*agent).angle, -params_10.sensorAngle); + var angle = (*agent).angle; if (((weightForward > weightLeft) && (weightForward > weightRight))) { } @@ -200,7 +200,7 @@ describe('slime mold example', () => { } } var dir = vec2f(cos(angle), sin(angle)); - var newPos = (agent.position + (dir * (params_10.moveSpeed * deltaTime_12))); + var newPos = ((*agent).position + (dir * (params_10.moveSpeed * deltaTime_12))); var dimsf = vec2f(dims); if (((((newPos.x < 0) || (newPos.x > dimsf.x)) || (newPos.y < 0)) || (newPos.y > dimsf.y))) { newPos = clamp(newPos, vec2f(), (dimsf - vec2f(1))); From b4f2c0b0f0b537a2183ade1c34b3af8018a653a5 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Fri, 31 Oct 2025 10:29:06 +0100 Subject: [PATCH 31/59] Rename ref to origin --- .../typegpu/src/core/buffer/bufferUsage.ts | 10 ++-- .../typegpu/src/core/constant/tgpuConstant.ts | 9 ++-- packages/typegpu/src/core/slot/accessor.ts | 2 +- .../typegpu/src/core/variable/tgpuVariable.ts | 6 +-- packages/typegpu/src/data/snippet.ts | 28 +++++------ packages/typegpu/src/data/wgslTypes.ts | 12 +++-- packages/typegpu/src/tgsl/conversion.ts | 12 ++--- .../typegpu/src/tgsl/generationHelpers.ts | 46 +++++++++++-------- packages/typegpu/src/tgsl/shellless.ts | 8 ++-- packages/typegpu/src/tgsl/wgslGenerator.ts | 44 +++++++++--------- 10 files changed, 96 insertions(+), 81 deletions(-) diff --git a/packages/typegpu/src/core/buffer/bufferUsage.ts b/packages/typegpu/src/core/buffer/bufferUsage.ts index e7eeb878c0..926e0acd61 100644 --- a/packages/typegpu/src/core/buffer/bufferUsage.ts +++ b/packages/typegpu/src/core/buffer/bufferUsage.ts @@ -4,7 +4,7 @@ import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; import { type AnyWgslData, type BaseData, - isNaturallyRef, + isNaturallyEphemeral, } from '../../data/wgslTypes.ts'; import { IllegalBufferAccessError } from '../../errors.ts'; import { getExecMode, inCodegenMode, isInsideTgpuFn } from '../../execMode.ts'; @@ -133,7 +133,7 @@ class TgpuFixedBufferImpl< return snip( id, dataType, - /* ref */ isNaturallyRef(dataType) ? this.usage : 'runtime', + isNaturallyEphemeral(dataType) ? 'runtime' : this.usage, ); } @@ -151,7 +151,7 @@ class TgpuFixedBufferImpl< return snip( this, dataType, - /* ref */ isNaturallyRef(dataType) ? usage : 'runtime', + isNaturallyEphemeral(dataType) ? 'runtime' : usage, ); }, [$resolve]: (ctx) => ctx.resolve(this), @@ -262,7 +262,7 @@ export class TgpuLaidOutBufferImpl< return snip( id, dataType, - /* ref */ isNaturallyRef(dataType) ? this.usage : 'runtime', + isNaturallyEphemeral(dataType) ? 'runtime' : this.usage, ); } @@ -280,7 +280,7 @@ export class TgpuLaidOutBufferImpl< return snip( this, schema, - /* ref */ isNaturallyRef(schema) ? usage : 'runtime', + isNaturallyEphemeral(schema) ? 'runtime' : usage, ); }, [$resolve]: (ctx) => ctx.resolve(this), diff --git a/packages/typegpu/src/core/constant/tgpuConstant.ts b/packages/typegpu/src/core/constant/tgpuConstant.ts index 3680b6832c..5399be2b54 100644 --- a/packages/typegpu/src/core/constant/tgpuConstant.ts +++ b/packages/typegpu/src/core/constant/tgpuConstant.ts @@ -1,5 +1,8 @@ import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; -import { type AnyWgslData, isNaturallyRef } from '../../data/wgslTypes.ts'; +import { + type AnyWgslData, + isNaturallyEphemeral, +} from '../../data/wgslTypes.ts'; import { inCodegenMode } from '../../execMode.ts'; import type { TgpuNamable } from '../../shared/meta.ts'; import { getName, setName } from '../../shared/meta.ts'; @@ -98,7 +101,7 @@ class TgpuConstImpl return snip( id, this.dataType, - /* ref */ isNaturallyRef(this.dataType) ? 'constant-ref' : 'constant', + isNaturallyEphemeral(this.dataType) ? 'constant' : 'constant-ref', ); } @@ -115,7 +118,7 @@ class TgpuConstImpl return snip( this, dataType, - /* ref */ isNaturallyRef(dataType) ? 'constant-ref' : 'constant', + isNaturallyEphemeral(dataType) ? 'constant' : 'constant-ref', ); }, [$resolve]: (ctx) => ctx.resolve(this), diff --git a/packages/typegpu/src/core/slot/accessor.ts b/packages/typegpu/src/core/slot/accessor.ts index 8cb7926827..5f8c07c60c 100644 --- a/packages/typegpu/src/core/slot/accessor.ts +++ b/packages/typegpu/src/core/slot/accessor.ts @@ -128,7 +128,7 @@ export class TgpuAccessorImpl return snip( ctx.resolve(snippet.value, snippet.dataType).value, snippet.dataType as T, - snippet.ref, + snippet.origin, ); } } diff --git a/packages/typegpu/src/core/variable/tgpuVariable.ts b/packages/typegpu/src/core/variable/tgpuVariable.ts index 12eec3ed23..eaacfbd5fb 100644 --- a/packages/typegpu/src/core/variable/tgpuVariable.ts +++ b/packages/typegpu/src/core/variable/tgpuVariable.ts @@ -1,6 +1,6 @@ import type { AnyData } from '../../data/dataTypes.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; -import { isNaturallyRef } from '../../data/wgslTypes.ts'; +import { isNaturallyEphemeral } from '../../data/wgslTypes.ts'; import { IllegalVarAccessError } from '../../errors.ts'; import { getExecMode, isInsideTgpuFn } from '../../execMode.ts'; import type { TgpuNamable } from '../../shared/meta.ts'; @@ -107,7 +107,7 @@ class TgpuVarImpl return snip( id, this.#dataType, - /* ref */ isNaturallyRef(this.#dataType) ? this.#scope : 'runtime', + isNaturallyEphemeral(this.#dataType) ? 'runtime' : this.#scope, ); } @@ -122,7 +122,7 @@ class TgpuVarImpl get [$gpuValueOf](): InferGPU { const dataType = this.#dataType; - const ref = isNaturallyRef(dataType) ? this.#scope : 'runtime'; + const ref = isNaturallyEphemeral(dataType) ? 'runtime' : this.#scope; return new Proxy({ [$internal]: true, diff --git a/packages/typegpu/src/data/snippet.ts b/packages/typegpu/src/data/snippet.ts index 8d8b7a9823..07a656c48e 100644 --- a/packages/typegpu/src/data/snippet.ts +++ b/packages/typegpu/src/data/snippet.ts @@ -3,7 +3,7 @@ import type { AnyData, UnknownData } from './dataTypes.ts'; import { DEV } from '../shared/env.ts'; import { isNumericSchema } from './wgslTypes.ts'; -export type RefSpace = +export type Origin = | 'uniform' | 'readonly' // equivalent to ptr | 'mutable' // equivalent to ptr @@ -19,19 +19,19 @@ export type RefSpace = // not a ref to anything, known at pipeline creation time // (not to be confused with 'comptime') // note that this doesn't automatically mean the value can be stored in a `const` - // variable, more so that it's valid to do so in WGSL (but not necessarily safe to do in TGSL) + // variable, more so that it's valid to do so in WGSL (but not necessarily safe to do in JS shaders) | 'constant' | 'constant-ref'; -export function isSpaceRef(space: RefSpace) { - return space !== 'runtime' && space !== 'constant'; +export function isEphemeralOrigin(space: Origin) { + return space === 'runtime' || space !== 'constant'; } -export function isRef(snippet: Snippet) { - return isSpaceRef(snippet.ref); +export function isEphemeralSnippet(snippet: Snippet) { + return isEphemeralOrigin(snippet.origin); } -export const refSpaceToPtrParams = { +export const originToPtrParams = { uniform: { space: 'uniform', access: 'read' }, readonly: { space: 'storage', access: 'read' }, mutable: { space: 'storage', access: 'read-write' }, @@ -48,7 +48,7 @@ export interface Snippet { * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float */ readonly dataType: AnyData | UnknownData; - readonly ref: RefSpace; + readonly origin: Origin; } export interface ResolvedSnippet { @@ -58,7 +58,7 @@ export interface ResolvedSnippet { * E.g. `1.1` is assignable to `f32`, but `1.1` itself is an abstract float */ readonly dataType: AnyData; - readonly ref: RefSpace; + readonly origin: Origin; } export type MapValueToSnippet = { [K in keyof T]: Snippet }; @@ -67,7 +67,7 @@ class SnippetImpl implements Snippet { constructor( readonly value: unknown, readonly dataType: AnyData | UnknownData, - readonly ref: RefSpace, + readonly origin: Origin, ) {} } @@ -82,17 +82,17 @@ export function isSnippetNumeric(snippet: Snippet) { export function snip( value: string, dataType: AnyData, - ref: RefSpace, + origin: Origin, ): ResolvedSnippet; export function snip( value: unknown, dataType: AnyData | UnknownData, - ref: RefSpace, + origin: Origin, ): Snippet; export function snip( value: unknown, dataType: AnyData | UnknownData, - ref: RefSpace, + origin: Origin, ): Snippet | ResolvedSnippet { if (DEV && isSnippet(value)) { // An early error, but not worth checking every time in production @@ -103,6 +103,6 @@ export function snip( value, // We don't care about attributes in snippet land, so we discard that information. undecorate(dataType as AnyData), - ref, + origin, ); } diff --git a/packages/typegpu/src/data/wgslTypes.ts b/packages/typegpu/src/data/wgslTypes.ts index 9eb0060cb1..6acf14b8f2 100644 --- a/packages/typegpu/src/data/wgslTypes.ts +++ b/packages/typegpu/src/data/wgslTypes.ts @@ -1904,7 +1904,7 @@ export function isHalfPrecisionSchema( ); } -const valueTypes = [ +const ephemeralTypes = [ 'abstractInt', 'abstractFloat', 'f32', @@ -1915,11 +1915,13 @@ const valueTypes = [ ]; /** - * Returns true for schemas that are naturally referential in JS. + * Returns true for schemas that are not naturally referential in JS (primitives). * @param schema * @returns */ -export function isNaturallyRef(schema: unknown): boolean { - return isMarkedInternal(schema) && - !valueTypes.includes((schema as BaseData)?.type); +export function isNaturallyEphemeral(schema: unknown): boolean { + return ( + !isMarkedInternal(schema) || + ephemeralTypes.includes((schema as BaseData)?.type) + ); } diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 5d2cfcde25..4c4183d5c8 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -233,16 +233,16 @@ function applyActionToSnippet( snippet.value, targetType, // if it was a ref, then it's still a ref - /* ref */ snippet.ref, + /* origin */ snippet.origin, ); } switch (action.action) { case 'ref': - return snip(stitch`(&${snippet})`, targetType, /* ref */ snippet.ref); + return snip(stitch`(&${snippet})`, targetType, snippet.origin); case 'deref': // Dereferencing a pointer does not return a copy of the value, it's still a reference. - return snip(stitch`(*${snippet})`, targetType, /* ref */ snippet.ref); + return snip(stitch`(*${snippet})`, targetType, snippet.origin); case 'cast': { // Casting means calling the schema with the snippet as an argument. return (targetType as unknown as (val: Snippet) => Snippet)(snippet); @@ -319,15 +319,15 @@ export function tryConvertSnippet( verbose = true, ): Snippet { if (targetDataType === snippet.dataType) { - return snip(snippet.value, targetDataType, /* ref */ snippet.ref); + return snip(snippet.value, targetDataType, snippet.origin); } if (snippet.dataType.type === 'unknown') { // This is it, it's now or never. We expect a specific type, and we're going to get it return snip( - stitch`${snip(snippet.value, targetDataType, /* ref */ snippet.ref)}`, + stitch`${snip(snippet.value, targetDataType, snippet.origin)}`, targetDataType, - /* ref */ snippet.ref, + snippet.origin, ); } diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 8790146545..e247ed1aa7 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -17,7 +17,12 @@ import { i32, u32, } from '../data/numeric.ts'; -import { isRef, isSnippet, snip, type Snippet } from '../data/snippet.ts'; +import { + isEphemeralSnippet, + isSnippet, + snip, + type Snippet, +} from '../data/snippet.ts'; import { vec2b, vec2f, @@ -41,7 +46,7 @@ import { type I32, isMat, isMatInstance, - isNaturallyRef, + isNaturallyEphemeral, isVec, isVecInstance, isWgslArray, @@ -159,7 +164,7 @@ export function accessProp( infixOperators[propName as InfixOperator][$internal].gpuImpl, ), UnknownData, - /* ref */ target.ref, + /* origin */ target.origin, ); } @@ -184,7 +189,7 @@ export function accessProp( return snip( new MatrixColumnsAccess(target), UnknownData, - /* ref */ target.ref, + /* origin */ target.origin, ); } @@ -198,9 +203,9 @@ export function accessProp( return snip( stitch`${target}.${propName}`, propType, - /* ref */ isRef(target) && isNaturallyRef(propType) - ? target.ref - : target.ref === 'constant' || target.ref === 'constant-ref' + /* origin */ isEphemeralSnippet(target) && isNaturallyEphemeral(propType) + ? target.origin + : target.origin === 'constant' || target.origin === 'constant-ref' ? 'constant' : 'runtime', ); @@ -228,7 +233,8 @@ export function accessProp( : stitch`${target}.${propName}`, swizzleType, // Swizzling creates new vectors (unless they're on the lhs of an assignment, but that's not yet supported in WGSL) - /* ref */ target.ref === 'constant' || target.ref === 'constant-ref' + /* origin */ target.origin === 'constant' || + target.origin === 'constant-ref' ? 'constant' : 'runtime', ); @@ -262,9 +268,10 @@ export function accessIndex( ? (target.value as any)[index.value as number] : stitch`${target}[${index}]`, elementType, - /* ref */ isRef(target) && isNaturallyRef(elementType) - ? target.ref - : target.ref === 'constant' || target.ref === 'constant-ref' + /* origin */ isEphemeralSnippet(target) && + isNaturallyEphemeral(elementType) + ? target.origin + : target.origin === 'constant' || target.origin === 'constant-ref' ? 'constant' : 'runtime', ); @@ -278,7 +285,8 @@ export function accessIndex( ? (target.value as any)[index.value as any] : stitch`${target}[${index}]`, target.dataType.primitive, - /* ref */ target.ref === 'constant' || target.ref === 'constant-ref' + /* origin */ target.origin === 'constant' || + target.origin === 'constant-ref' ? 'constant' : 'runtime', ); @@ -293,7 +301,7 @@ export function accessIndex( return snip( stitch`${target.value.matrix}[${index}]`, propType, - /* ref */ target.ref, + /* origin */ target.origin, ); } @@ -320,7 +328,7 @@ export function accessIndex( export function numericLiteralToSnippet(value: number): Snippet { if (value >= 2 ** 63 || value < -(2 ** 63)) { - return snip(value, abstractFloat); + return snip(value, abstractFloat, /* ref */ 'constant'); } // WGSL AbstractInt uses 64-bit precision, but JS numbers are only safe up to 2^53 - 1. // Warn when values exceed this range to prevent precision loss. @@ -352,7 +360,7 @@ export function concretizeSnippets(args: Snippet[]): Snippet[] { snip( snippet.value, concretize(snippet.dataType as AnyWgslData), - /* ref */ snippet.ref, + /* origin */ snippet.origin, ) ); } @@ -401,7 +409,7 @@ export function coerceToSnippet(value: unknown): Snippet { } if (isVecInstance(value) || isMatInstance(value)) { - return snip(value, kindToSchema[value.kind], /* ref */ 'constant'); + return snip(value, kindToSchema[value.kind], /* origin */ 'constant'); } if ( @@ -410,7 +418,7 @@ export function coerceToSnippet(value: unknown): Snippet { typeof value === 'undefined' || value === null ) { // Nothing representable in WGSL as-is, so unknown - return snip(value, UnknownData, /* ref */ 'constant'); + return snip(value, UnknownData, /* origin */ 'constant'); } if (typeof value === 'number') { @@ -418,8 +426,8 @@ export function coerceToSnippet(value: unknown): Snippet { } if (typeof value === 'boolean') { - return snip(value, bool, /* ref */ 'constant'); + return snip(value, bool, /* origin */ 'constant'); } - return snip(value, UnknownData, /* ref */ 'constant'); + return snip(value, UnknownData, /* origin */ 'constant'); } diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index 9380fa737d..4f9f85c9df 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -4,7 +4,7 @@ import { } from '../core/function/shelllessImpl.ts'; import type { AnyData } from '../data/dataTypes.ts'; import { INTERNAL_createPtr } from '../data/ptr.ts'; -import { refSpaceToPtrParams, type Snippet } from '../data/snippet.ts'; +import { originToPtrParams, type Snippet } from '../data/snippet.ts'; import { isPtr, type StorableData } from '../data/wgslTypes.ts'; import { getResolutionCtx } from '../execMode.ts'; import { getMetaData, getName } from '../shared/meta.ts'; @@ -49,11 +49,11 @@ export class ShelllessRepository { const argTypes = (argSnippets ?? []).map((s) => { const type = concretize(s.dataType as AnyData); - const ptrParams = s.ref in refSpaceToPtrParams - ? refSpaceToPtrParams[s.ref as keyof typeof refSpaceToPtrParams] + const ptrParams = s.origin in originToPtrParams + ? originToPtrParams[s.origin as keyof typeof originToPtrParams] : undefined; - if (s.ref === 'constant-ref') { + if (s.origin === 'constant-ref') { // biome-ignore lint/style/noNonNullAssertion: it's there const ctx = getResolutionCtx()!; throw new Error( diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 044456b35e..f48a947845 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -11,10 +11,10 @@ import { } from '../data/dataTypes.ts'; import { bool, i32, u32 } from '../data/numeric.ts'; import { - isRef, + isEphemeralOrigin, + isEphemeralSnippet, isSnippet, - isSpaceRef, - type RefSpace, + type Origin, snip, type Snippet, } from '../data/snippet.ts'; @@ -144,23 +144,23 @@ ${this.ctx.pre}}`; varType: 'var' | 'let' | 'const', id: string, dataType: wgsl.AnyWgslData | UnknownData, - ref: RefSpace, + origin: Origin, ): Snippet { - let varRef: RefSpace = 'runtime'; - if (ref === 'constant-ref') { + let varOrigin: Origin = 'runtime'; + if (origin === 'constant-ref') { // Even types that aren't naturally referential (like vectors or structs) should // be treated as constant references when assigned to a const. - varRef = 'constant-ref'; - } else if (wgsl.isNaturallyRef(dataType)) { - varRef = isSpaceRef(ref) ? ref : 'this-function'; - } else if (ref === 'constant' && varType === 'const') { - varRef = 'constant'; + varOrigin = 'constant-ref'; + } else if (!wgsl.isNaturallyEphemeral(dataType)) { + varOrigin = isEphemeralOrigin(origin) ? 'this-function' : origin; + } else if (origin === 'constant' && varType === 'const') { + varOrigin = 'constant'; } const snippet = snip( this.ctx.makeNameValid(id), dataType, - /* ref */ varRef, + /* origin */ varOrigin, ); this.ctx.defineVariable(id, snippet); return snippet; @@ -247,13 +247,15 @@ ${this.ctx.pre}}`; const type = operatorToType(convLhs.dataType, op, convRhs.dataType); if (exprType === NODE.assignmentExpr) { - if (convLhs.ref === 'constant' || convLhs.ref === 'constant-ref') { + if ( + convLhs.origin === 'constant' || convLhs.origin === 'constant-ref' + ) { throw new WgslTypeError( `'${lhsStr} = ${rhsStr}' is invalid, because ${lhsStr} is a constant.`, ); } - if (isRef(rhsExpr)) { + if (!isEphemeralSnippet(rhsExpr)) { throw new WgslTypeError( `'${lhsStr} = ${rhsStr}' is invalid, because references cannot be assigned.\n-----\nTry '${lhsStr} = ${ this.ctx.resolve(rhsExpr.dataType).value @@ -659,8 +661,8 @@ ${this.ctx.pre}}`; if ( !expectedReturnType && - isRef(returnSnippet) && - returnSnippet.ref !== 'this-function' + !isEphemeralSnippet(returnSnippet) && + returnSnippet.origin !== 'this-function' ) { const str = this.ctx.resolve( returnSnippet.value, @@ -743,7 +745,7 @@ ${this.ctx.pre}else ${alternate}`; let dataType = eq.dataType as wgsl.AnyWgslData; // Assigning a reference to a `const` variable means we store the pointer // of the rhs. - if (isRef(eq)) { + if (!isEphemeralSnippet(eq)) { // Referential if (stmtType === NODE.let) { const rhsStr = this.ctx.resolve(eq.value).value; @@ -760,7 +762,7 @@ ${this.ctx.pre}else ${alternate}`; ); } - if (eq.ref === 'constant-ref') { + if (eq.origin === 'constant-ref') { varType = 'const'; } else { varType = 'let'; @@ -772,8 +774,8 @@ ${this.ctx.pre}else ${alternate}`; // Non-referential if ( stmtType === NODE.const && - !wgsl.isNaturallyRef(dataType) && - eq.ref === 'constant' + wgsl.isNaturallyEphemeral(dataType) && + eq.origin === 'constant' ) { varType = 'const'; } @@ -783,7 +785,7 @@ ${this.ctx.pre}else ${alternate}`; varType, rawId, concretize(dataType), - eq.ref, + eq.origin, ); return stitchWithExactTypes`${this.ctx.pre}${varType} ${snippet .value as string} = ${tryConvertSnippet(eq, dataType, false)};`; From beb2295f433b3e568d3484f760fae1953cfd83e0 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Sat, 1 Nov 2025 21:17:40 +0100 Subject: [PATCH 32/59] Explicit refs --- .../examples/image-processing/blur/index.ts | 4 +- .../tests/tgsl-parsing-test/pointers.ts | 37 +++--- packages/typegpu-noise/src/generator.ts | 2 +- .../typegpu/src/core/buffer/bufferUsage.ts | 2 +- packages/typegpu/src/core/function/fnCore.ts | 6 +- .../typegpu/src/core/variable/tgpuVariable.ts | 6 +- packages/typegpu/src/data/index.ts | 1 + packages/typegpu/src/data/ref.ts | 123 ++++++++++++++++++ packages/typegpu/src/data/snippet.ts | 12 +- packages/typegpu/src/data/wgslTypes.ts | 3 +- packages/typegpu/src/shared/symbols.ts | 6 + packages/typegpu/src/tgsl/conversion.ts | 22 +++- .../typegpu/src/tgsl/generationHelpers.ts | 30 ++++- packages/typegpu/src/tgsl/shellless.ts | 15 +-- packages/typegpu/src/tgsl/wgslGenerator.ts | 57 ++++++-- packages/typegpu/tests/array.test.ts | 4 +- .../typegpu/tests/bindGroupLayout.test.ts | 32 +++++ packages/typegpu/tests/data/ptr.test.ts | 6 +- .../tests/examples/individual/3d-fish.test.ts | 10 +- .../examples/individual/caustics.test.ts | 21 +-- .../individual/cubemap-reflection.test.ts | 42 +++--- .../examples/individual/liquid-glass.test.ts | 22 ++-- .../tests/examples/individual/oklab.test.ts | 8 +- .../examples/individual/ray-marching.test.ts | 78 ++++------- .../examples/individual/slime-mold-3d.test.ts | 32 ++--- .../examples/individual/slime-mold.test.ts | 10 +- .../individual/tgsl-parsing-test.test.ts | 49 ++++--- packages/typegpu/tests/indent.test.ts | 38 +++--- packages/typegpu/tests/namespace.test.ts | 2 +- packages/typegpu/tests/ref.test.ts | 46 +++++++ .../tests/tgsl/generationHelpers.test.ts | 76 +++++------ .../typegpu/tests/tgsl/memberAccess.test.ts | 2 +- packages/typegpu/tests/tgsl/shellless.test.ts | 12 +- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 4 +- packages/typegpu/tests/tgslFn.test.ts | 13 +- tsconfig.base.json | 1 + 36 files changed, 550 insertions(+), 284 deletions(-) create mode 100644 packages/typegpu/src/data/ref.ts create mode 100644 packages/typegpu/tests/ref.test.ts diff --git a/apps/typegpu-docs/src/examples/image-processing/blur/index.ts b/apps/typegpu-docs/src/examples/image-processing/blur/index.ts index 7c7dd83f25..31d7f65b62 100644 --- a/apps/typegpu-docs/src/examples/image-processing/blur/index.ts +++ b/apps/typegpu-docs/src/examples/image-processing/blur/index.ts @@ -66,9 +66,7 @@ const ioLayout = tgpu.bindGroupLayout({ outTexture: { storageTexture: d.textureStorage2d('rgba8unorm') }, }); -const tileData = tgpu['~unstable'].workgroupVar( - d.arrayOf(d.arrayOf(d.vec3f, 128), 4), -); +const tileData = tgpu.workgroupVar(d.arrayOf(d.arrayOf(d.vec3f, 128), 4)); const computeFn = tgpu['~unstable'].computeFn({ in: { diff --git a/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts b/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts index e4a0e8359f..0a2e16d699 100644 --- a/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts +++ b/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts @@ -4,31 +4,31 @@ import * as std from 'typegpu/std'; const SimpleStruct = d.struct({ vec: d.vec2f }); -// const modifyNumFn = tgpu.fn([d.ptrFn(d.u32)])((ptr) => { -// ptr += 1; -// }); +const modifyNumFn = tgpu.fn([d.ptrFn(d.u32)])((ptr) => { + ptr.$ += 1; +}); const modifyVecFn = tgpu.fn([d.ptrFn(d.vec2f)])((ptr) => { - ptr.x += 1; + ptr.$.x += 1; }); const modifyStructFn = tgpu.fn([d.ptrFn(SimpleStruct)])((ptr) => { - ptr.vec.x += 1; + ptr.$.vec.x += 1; }); const privateNum = tgpu.privateVar(d.u32); const modifyNumPrivate = tgpu.fn([d.ptrPrivate(d.u32)])((ptr) => { - ptr += 1; + ptr.$ += 1; }); const privateVec = tgpu.privateVar(d.vec2f); const modifyVecPrivate = tgpu.fn([d.ptrPrivate(d.vec2f)])((ptr) => { - ptr.x += 1; + ptr.$.x += 1; }); const privateStruct = tgpu.privateVar(SimpleStruct); const modifyStructPrivate = tgpu.fn([d.ptrPrivate(SimpleStruct)])((ptr) => { - ptr.vec.x += 1; + ptr.$.vec.x += 1; }); // TODO: replace `s = s &&` with `s &&=` when implemented @@ -36,27 +36,26 @@ export const pointersTest = tgpu.fn([], d.bool)(() => { let s = true; // function pointers - // TODO: Uncomment this when boxed values are implemented - // const num = d.u32(); - // modifyNumFn(num); - // s = s && (num === 1); + const num = d.ref(d.u32()); + modifyNumFn(num); + s = s && (num.$ === 1); - const vec = d.vec2f(); + const vec = d.ref(d.vec2f()); modifyVecFn(vec); - s = s && std.allEq(vec, d.vec2f(1, 0)); + s = s && std.allEq(vec.$, d.vec2f(1, 0)); - const myStruct = SimpleStruct(); + const myStruct = d.ref(SimpleStruct()); modifyStructFn(myStruct); - s = s && std.allEq(myStruct.vec, d.vec2f(1, 0)); + s = s && std.allEq(myStruct.$.vec, d.vec2f(1, 0)); // private pointers - modifyNumPrivate(privateNum.$); + modifyNumPrivate(privateNum); s = s && (privateNum.$ === 1); - modifyVecPrivate(privateVec.$); + modifyVecPrivate(privateVec); s = s && std.allEq(privateVec.$, d.vec2f(1, 0)); - modifyStructPrivate(privateStruct.$); + modifyStructPrivate(privateStruct); s = s && std.allEq(privateStruct.$.vec, d.vec2f(1, 0)); return s; diff --git a/packages/typegpu-noise/src/generator.ts b/packages/typegpu-noise/src/generator.ts index c05c0f2035..67774b1a55 100644 --- a/packages/typegpu-noise/src/generator.ts +++ b/packages/typegpu-noise/src/generator.ts @@ -25,7 +25,7 @@ export const BPETER: StatefulGenerator = (() => { }), seed2: tgpu.fn([d.vec2f])((value) => { - seed.value = value; + seed.value = d.vec2f(value); }), seed3: tgpu.fn([d.vec3f])((value) => { diff --git a/packages/typegpu/src/core/buffer/bufferUsage.ts b/packages/typegpu/src/core/buffer/bufferUsage.ts index 926e0acd61..b83e880aae 100644 --- a/packages/typegpu/src/core/buffer/bufferUsage.ts +++ b/packages/typegpu/src/core/buffer/bufferUsage.ts @@ -271,7 +271,7 @@ export class TgpuLaidOutBufferImpl< } get [$gpuValueOf](): InferGPU { - const schema = this.dataType as unknown as AnyData; + const schema = this.dataType as AnyData; const usage = this.usage; return new Proxy({ diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index 049316c835..669aa7e3dd 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -196,7 +196,11 @@ export function createFnCore( // of the argument based on the argument's referentiality. // In other words, if we pass a reference to a function, it's typed as a pointer, // otherwise it's typed as a value. - const ref = isPtr(argType) ? 'function' : 'runtime'; + const ref = isPtr(argType) + ? argType.addressSpace === 'storage' + ? argType.access === 'read' ? 'readonly' : 'mutable' + : argType.addressSpace + : 'argument'; switch (astParam?.type) { case FuncParameterType.identifier: { diff --git a/packages/typegpu/src/core/variable/tgpuVariable.ts b/packages/typegpu/src/core/variable/tgpuVariable.ts index eaacfbd5fb..b8ec5cf0c8 100644 --- a/packages/typegpu/src/core/variable/tgpuVariable.ts +++ b/packages/typegpu/src/core/variable/tgpuVariable.ts @@ -1,4 +1,5 @@ import type { AnyData } from '../../data/dataTypes.ts'; +import type { ref } from '../../data/ref.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; import { isNaturallyEphemeral } from '../../data/wgslTypes.ts'; import { IllegalVarAccessError } from '../../errors.ts'; @@ -9,6 +10,7 @@ import type { InferGPU } from '../../shared/repr.ts'; import { $gpuValueOf, $internal, + $isRef, $ownSnippet, $resolve, } from '../../shared/symbols.ts'; @@ -25,7 +27,7 @@ export type VariableScope = 'private' | 'workgroup'; export interface TgpuVar< TScope extends VariableScope = VariableScope, TDataType extends AnyData = AnyData, -> extends TgpuNamable { +> extends TgpuNamable, ref> { readonly [$gpuValueOf]: InferGPU; value: InferGPU; $: InferGPU; @@ -76,6 +78,7 @@ export function isVariable( class TgpuVarImpl implements TgpuVar, SelfResolvable { readonly [$internal] = {}; + readonly [$isRef]: true; readonly #scope: TScope; readonly #dataType: TDataType; readonly #initialValue: InferGPU | undefined; @@ -85,6 +88,7 @@ class TgpuVarImpl dataType: TDataType, initialValue?: InferGPU | undefined, ) { + this[$isRef] = true; this.#scope = scope; this.#dataType = dataType; this.#initialValue = initialValue; diff --git a/packages/typegpu/src/data/index.ts b/packages/typegpu/src/data/index.ts index 90f2b6a42b..d41be82d14 100644 --- a/packages/typegpu/src/data/index.ts +++ b/packages/typegpu/src/data/index.ts @@ -193,6 +193,7 @@ export { unstruct } from './unstruct.ts'; export { mat2x2f, mat3x3f, mat4x4f, matToArray } from './matrix.ts'; export * from './vertexFormatData.ts'; export { atomic } from './atomic.ts'; +export { ref } from './ref.ts'; export { align, type AnyAttribute, diff --git a/packages/typegpu/src/data/ref.ts b/packages/typegpu/src/data/ref.ts new file mode 100644 index 0000000000..98f7fc49b2 --- /dev/null +++ b/packages/typegpu/src/data/ref.ts @@ -0,0 +1,123 @@ +import { stitch } from '../core/resolve/stitch.ts'; +import { inCodegenMode } from '../execMode.ts'; +import { setName } from '../shared/meta.ts'; +import { $internal, $isRef, $ownSnippet, $resolve } from '../shared/symbols.ts'; +import type { ResolutionCtx, SelfResolvable } from '../types.ts'; +import { UnknownData } from './dataTypes.ts'; +import type { DualFn } from './dualFn.ts'; +import { INTERNAL_createPtr } from './ptr.ts'; +import { + type OriginToPtrParams, + originToPtrParams, + type ResolvedSnippet, + snip, + type Snippet, +} from './snippet.ts'; +import { type Ptr, type StorableData } from './wgslTypes.ts'; + +// ---------- +// Public API +// ---------- + +export interface ref { + readonly [$internal]: unknown; + readonly [$isRef]: true; + $: T; +} + +// TODO: Restrict calls to this function only from within TypeGPU functions +export const ref: DualFn<(value: T) => ref> = (() => { + const gpuImpl = (value: Snippet) => { + return snip(new RefOnGPU(value), UnknownData, /* origin */ 'runtime'); + }; + + const jsImpl = (value: T) => new refImpl(value); + + const impl = (value: T) => { + if (inCodegenMode()) { + return gpuImpl(value as Snippet); + } + return jsImpl(value); + }; + + setName(impl, 'ref'); + impl.toString = () => 'ref'; + Object.defineProperty(impl, $internal, { + value: { + jsImpl, + gpuImpl, + strictSignature: undefined, + argConversionHint: 'keep', + }, + }); + + return impl as unknown as DualFn<(value: T) => ref>; +})(); + +// -------------- +// Implementation +// -------------- + +class refImpl implements ref { + readonly #value: T | string; + readonly [$internal]: true; + readonly [$isRef]: true; + + constructor(value: T | string) { + this.#value = value; + this[$internal] = true; + this[$isRef] = true; + } + + get $(): T { + return this.#value as T; + } +} + +export class RefOnGPU { + readonly snippet: Snippet; + readonly [$internal]: true; + + constructor(snippet: Snippet) { + this.snippet = snippet; + this[$internal] = true; + } + + toString(): string { + return `ref:${this.snippet.value}`; + } +} + +export class RefOperator implements SelfResolvable { + readonly [$internal]: true; + readonly snippet: Snippet; + readonly #ptrType: Ptr; + + constructor(snippet: Snippet) { + this[$internal] = true; + this.snippet = snippet; + + const ptrParams = + originToPtrParams[this.snippet.origin as keyof OriginToPtrParams]; + + if (!ptrParams) { + throw new Error( + `Cannot take a reference of a value with origin ${this.snippet.origin}`, + ); + } + + this.#ptrType = INTERNAL_createPtr( + ptrParams.space, + this.snippet.dataType as StorableData, + ptrParams.access, + ); + } + + get [$ownSnippet](): Snippet { + return snip(this, this.#ptrType, this.snippet.origin); + } + + [$resolve](ctx: ResolutionCtx): ResolvedSnippet { + return snip(stitch`(&${this.snippet})`, this.#ptrType, this.snippet.origin); + } +} diff --git a/packages/typegpu/src/data/snippet.ts b/packages/typegpu/src/data/snippet.ts index 07a656c48e..3c6a834acf 100644 --- a/packages/typegpu/src/data/snippet.ts +++ b/packages/typegpu/src/data/snippet.ts @@ -11,9 +11,11 @@ export type Origin = | 'private' | 'function' | 'handle' - // more specific version of 'function', telling us that the ref is - // to a value defined in the function - | 'this-function' + // is an argument (or part of an argument) given to the + // function we're resolving. This includes primitives, to + // catch cases where we update an argument's primitive member + // prop, e.g.: `vec.x += 1;` + | 'argument' // not a ref to anything, known at runtime | 'runtime' // not a ref to anything, known at pipeline creation time @@ -24,7 +26,7 @@ export type Origin = | 'constant-ref'; export function isEphemeralOrigin(space: Origin) { - return space === 'runtime' || space !== 'constant'; + return space === 'runtime' || space === 'constant' || space === 'argument'; } export function isEphemeralSnippet(snippet: Snippet) { @@ -38,8 +40,8 @@ export const originToPtrParams = { workgroup: { space: 'workgroup', access: 'read-write' }, private: { space: 'private', access: 'read-write' }, function: { space: 'function', access: 'read-write' }, - 'this-function': { space: 'function', access: 'read-write' }, } as const; +export type OriginToPtrParams = typeof originToPtrParams; export interface Snippet { readonly value: unknown; diff --git a/packages/typegpu/src/data/wgslTypes.ts b/packages/typegpu/src/data/wgslTypes.ts index 6acf14b8f2..9e97a723e8 100644 --- a/packages/typegpu/src/data/wgslTypes.ts +++ b/packages/typegpu/src/data/wgslTypes.ts @@ -32,6 +32,7 @@ import type { WgslTexture, } from './texture.ts'; import type { WgslComparisonSampler, WgslSampler } from './sampler.ts'; +import type { ref } from './ref.ts'; type DecoratedLocation = Decorated; @@ -1377,7 +1378,7 @@ export interface Ptr< readonly access: TAccess; // Type-tokens, not available at runtime - readonly [$repr]: Infer; + readonly [$repr]: ref>; readonly [$invalidSchemaReason]: 'Pointers are not host-shareable'; // --- } diff --git a/packages/typegpu/src/shared/symbols.ts b/packages/typegpu/src/shared/symbols.ts index bce790d4c9..a7ea396819 100644 --- a/packages/typegpu/src/shared/symbols.ts +++ b/packages/typegpu/src/shared/symbols.ts @@ -69,6 +69,12 @@ export const $invalidSchemaReason = Symbol( `typegpu:${version}:$invalidSchemaReason`, ); +/** + * A symbol that identifies objects that act as references + * (values returned from d.ref(), buffer usages, ...) + */ +export const $isRef = Symbol(`typegpu:${version}:$isRef`); + export function isMarkedInternal( value: unknown, ): value is { [$internal]: Record | true } { diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 4c4183d5c8..084be6ede4 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -1,6 +1,7 @@ import { stitch } from '../core/resolve/stitch.ts'; import type { AnyData, UnknownData } from '../data/dataTypes.ts'; import { undecorate } from '../data/dataTypes.ts'; +import { RefOperator } from '../data/ref.ts'; import { snip, type Snippet } from '../data/snippet.ts'; import { type AnyWgslData, @@ -8,6 +9,8 @@ import { type F32, type I32, isMat, + isNaturallyEphemeral, + isPtr, isVec, type U32, type WgslStruct, @@ -223,6 +226,20 @@ export function getBestConversion( return undefined; } +export function derefSnippet(snippet: Snippet): Snippet { + invariant(isPtr(snippet.dataType), 'Only pointers can be dereferenced'); + + const innerType = snippet.dataType.inner; + // Dereferencing a pointer does not return a copy of the value, it's still a reference. + const origin = isNaturallyEphemeral(innerType) ? 'runtime' : snippet.origin; + + if (snippet.value instanceof RefOperator) { + return snip(stitch`${snippet.value.snippet}`, innerType, origin); + } + + return snip(stitch`(*${snippet})`, innerType, origin); +} + function applyActionToSnippet( snippet: Snippet, action: ConversionResultAction, @@ -239,10 +256,9 @@ function applyActionToSnippet( switch (action.action) { case 'ref': - return snip(stitch`(&${snippet})`, targetType, snippet.origin); + return snip(new RefOperator(snippet), targetType, snippet.origin); case 'deref': - // Dereferencing a pointer does not return a copy of the value, it's still a reference. - return snip(stitch`(*${snippet})`, targetType, snippet.origin); + return derefSnippet(snippet); case 'cast': { // Casting means calling the schema with the snippet as an argument. return (targetType as unknown as (val: Snippet) => Snippet)(snippet); diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index e247ed1aa7..d75a85f7a9 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -47,6 +47,7 @@ import { isMat, isMatInstance, isNaturallyEphemeral, + isPtr, isVec, isVecInstance, isWgslArray, @@ -61,6 +62,7 @@ import type { ShelllessRepository } from './shellless.ts'; import { add, div, mul, sub } from '../std/operators.ts'; import { $internal } from '../shared/symbols.ts'; import { stitch } from '../core/resolve/stitch.ts'; +import { derefSnippet } from './conversion.ts'; type SwizzleableType = 'f' | 'h' | 'i' | 'u' | 'b'; type SwizzleLength = 1 | 2 | 3 | 4; @@ -203,7 +205,10 @@ export function accessProp( return snip( stitch`${target}.${propName}`, propType, - /* origin */ isEphemeralSnippet(target) && isNaturallyEphemeral(propType) + /* origin */ target.origin === 'argument' + ? 'argument' + : !isEphemeralSnippet(target) && + !isNaturallyEphemeral(propType) ? target.origin : target.origin === 'constant' || target.origin === 'constant-ref' ? 'constant' @@ -211,6 +216,19 @@ export function accessProp( ); } + if (isPtr(target.dataType)) { + const derefed = derefSnippet(target); + + if (propName === '$') { + // Dereference pointer + return derefed; + } + + // Sometimes values that are typed as pointers aren't instances of `d.ref`, so we + // allow access to member props as if it wasn't a pointer. + return accessProp(derefed, propName); + } + const propLength = propName.length; if ( isVec(target.dataType) && @@ -233,8 +251,10 @@ export function accessProp( : stitch`${target}.${propName}`, swizzleType, // Swizzling creates new vectors (unless they're on the lhs of an assignment, but that's not yet supported in WGSL) - /* origin */ target.origin === 'constant' || - target.origin === 'constant-ref' + /* origin */ target.origin === 'argument' && propLength === 1 + ? 'argument' + : target.origin === 'constant' || + target.origin === 'constant-ref' ? 'constant' : 'runtime', ); @@ -268,8 +288,8 @@ export function accessIndex( ? (target.value as any)[index.value as number] : stitch`${target}[${index}]`, elementType, - /* origin */ isEphemeralSnippet(target) && - isNaturallyEphemeral(elementType) + /* origin */ !isEphemeralSnippet(target) && + !isNaturallyEphemeral(elementType) ? target.origin : target.origin === 'constant' || target.origin === 'constant-ref' ? 'constant' diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index 4f9f85c9df..8bda0e635d 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -3,9 +3,7 @@ import { type ShelllessImpl, } from '../core/function/shelllessImpl.ts'; import type { AnyData } from '../data/dataTypes.ts'; -import { INTERNAL_createPtr } from '../data/ptr.ts'; -import { originToPtrParams, type Snippet } from '../data/snippet.ts'; -import { isPtr, type StorableData } from '../data/wgslTypes.ts'; +import type { Snippet } from '../data/snippet.ts'; import { getResolutionCtx } from '../execMode.ts'; import { getMetaData, getName } from '../shared/meta.ts'; import { concretize } from './generationHelpers.ts'; @@ -49,9 +47,6 @@ export class ShelllessRepository { const argTypes = (argSnippets ?? []).map((s) => { const type = concretize(s.dataType as AnyData); - const ptrParams = s.origin in originToPtrParams - ? originToPtrParams[s.origin as keyof typeof originToPtrParams] - : undefined; if (s.origin === 'constant-ref') { // biome-ignore lint/style/noNonNullAssertion: it's there @@ -63,13 +58,7 @@ export class ShelllessRepository { ); } - return ptrParams !== undefined && !isPtr(type) - ? INTERNAL_createPtr( - ptrParams.space, - type as StorableData, - ptrParams.access, - ) - : type; + return type; }); let cache = this.cache.get(fn); diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index f48a947845..384850f1c6 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -42,6 +42,7 @@ import { import type { ShaderGenerator } from './shaderGenerator.ts'; import type { DualFn } from '../data/dualFn.ts'; import { ptrFn } from '../data/ptr.ts'; +import { RefOnGPU, RefOperator } from '../data/ref.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -140,6 +141,20 @@ ${this.ctx.pre}}`; } } + public refVariable( + id: string, + dataType: wgsl.StorableData, + ): string { + const varName = this.ctx.makeNameValid(id); + const snippet = snip( + new RefOperator(snip(varName, dataType, 'function')), + ptrFn(dataType), + 'function', + ); + this.ctx.defineVariable(id, snippet); + return varName; + } + public blockVariable( varType: 'var' | 'let' | 'const', id: string, @@ -152,7 +167,7 @@ ${this.ctx.pre}}`; // be treated as constant references when assigned to a const. varOrigin = 'constant-ref'; } else if (!wgsl.isNaturallyEphemeral(dataType)) { - varOrigin = isEphemeralOrigin(origin) ? 'this-function' : origin; + varOrigin = isEphemeralOrigin(origin) ? 'function' : origin; } else if (origin === 'constant' && varType === 'const') { varOrigin = 'constant'; } @@ -219,6 +234,12 @@ ${this.ctx.pre}}`; const lhsExpr = this.expression(lhs); const rhsExpr = this.expression(rhs); + if (rhsExpr.value instanceof RefOnGPU) { + throw new WgslTypeError( + stitch`Cannot assign a ref to an existing variable '${lhsExpr}', define a new variable instead.`, + ); + } + if (lhsExpr.dataType.type === 'unknown') { throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type`); } @@ -255,6 +276,12 @@ ${this.ctx.pre}}`; ); } + if (lhsExpr.origin === 'argument') { + throw new WgslTypeError( + `'${lhsStr} ${op} ${rhsStr}' is invalid, because non-pointer arguments cannot be mutated.`, + ); + } + if (!isEphemeralSnippet(rhsExpr)) { throw new WgslTypeError( `'${lhsStr} = ${rhsStr}' is invalid, because references cannot be assigned.\n-----\nTry '${lhsStr} = ${ @@ -298,21 +325,16 @@ ${this.ctx.pre}}`; if (expression[0] === NODE.memberAccess) { // Member Access const [_, targetNode, property] = expression; - let target = this.expression(targetNode); + const target = this.expression(targetNode); if (target.value === console) { return snip(new ConsoleLog(property), UnknownData, /* ref */ 'runtime'); } - if (wgsl.isPtr(target.dataType)) { - // De-referencing the pointer - target = tryConvertSnippet(target, target.dataType.inner, false); - } - const accessed = accessProp(target, property); if (!accessed) { throw new Error( - `Property '${property}' not found on type ${ + stitch`Property '${property}' not found on value '${target}' of type ${ this.ctx.resolve(target.dataType) }`, ); @@ -662,7 +684,7 @@ ${this.ctx.pre}}`; if ( !expectedReturnType && !isEphemeralSnippet(returnSnippet) && - returnSnippet.origin !== 'this-function' + returnSnippet.origin !== 'function' ) { const str = this.ctx.resolve( returnSnippet.value, @@ -742,7 +764,24 @@ ${this.ctx.pre}else ${alternate}`; ); } + if (eq.value instanceof RefOnGPU) { + // We're assigning a newly created `d.ref()` + const refSnippet = eq.value.snippet; + const varName = this.refVariable( + rawId, + concretize(refSnippet.dataType as AnyData) as wgsl.StorableData, + ); + return stitchWithExactTypes`${this.ctx.pre}var ${varName} = ${ + tryConvertSnippet( + refSnippet, + refSnippet.dataType as wgsl.AnyWgslData, + false, + ) + };`; + } + let dataType = eq.dataType as wgsl.AnyWgslData; + // Assigning a reference to a `const` variable means we store the pointer // of the rhs. if (!isEphemeralSnippet(eq)) { diff --git a/packages/typegpu/tests/array.test.ts b/packages/typegpu/tests/array.test.ts index bcf27b4e09..c5fb265e2c 100644 --- a/packages/typegpu/tests/array.test.ts +++ b/packages/typegpu/tests/array.test.ts @@ -406,9 +406,7 @@ describe('array.length', () => { }); expect(asWgsl(testFn)).toMatchInlineSnapshot(` - "@group(0) @binding(0) var values: array; - - fn testFn() -> i32 { + "fn testFn() -> i32 { return 5; }" `); diff --git a/packages/typegpu/tests/bindGroupLayout.test.ts b/packages/typegpu/tests/bindGroupLayout.test.ts index 2272bb106d..15d3543887 100644 --- a/packages/typegpu/tests/bindGroupLayout.test.ts +++ b/packages/typegpu/tests/bindGroupLayout.test.ts @@ -229,6 +229,38 @@ describe('TgpuBindGroupLayout', () => { fn main() { textureLoad(fooTexture); }" `); }); + + it('takes a pointer to layout.$... if assigned to a const variable', () => { + const Boid = d.struct({ + pos: d.vec3f, + vel: d.vec3f, + }); + + const layout = tgpu.bindGroupLayout({ + boids: { storage: d.arrayOf(Boid), access: 'mutable' }, + }); + + const getFirst = () => { + 'use gpu'; + const boids = layout.$.boids; + // biome-ignore lint/style/noNonNullAssertion: it's definitely there + return Boid(boids[0]!); + }; + + expect(asWgsl(getFirst)).toMatchInlineSnapshot(` + "struct Boid { + pos: vec3f, + vel: vec3f, + } + + @group(0) @binding(0) var boids: array; + + fn getFirst() -> Boid { + let boids = (&boids); + return (*boids)[0]; + }" + `); + }); }); describe('TgpuBindGroup', () => { diff --git a/packages/typegpu/tests/data/ptr.test.ts b/packages/typegpu/tests/data/ptr.test.ts index 99bbf173e4..3fdeb32d3f 100644 --- a/packages/typegpu/tests/data/ptr.test.ts +++ b/packages/typegpu/tests/data/ptr.test.ts @@ -21,13 +21,13 @@ describe('d.ptrFn', () => { it('modifies reference types in JS', () => { const modifyVec = tgpu.fn([d.ptrFn(d.vec2f)])((ptr) => { - ptr.x += 1; + ptr.$.x += 1; }); const testFn = tgpu.fn([], d.vec2f)(() => { - const vec = d.vec2f(1, 2); + const vec = d.ref(d.vec2f(1, 2)); modifyVec(vec); - return vec; + return vec.$; }); expect(testFn()).toStrictEqual(d.vec2f(2, 2)); diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index 109fab5dfb..4d7a6748b8 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -120,10 +120,10 @@ describe('3d fish example', () => { @group(1) @binding(2) var mouseRay_7: MouseRay_8; - fn projectPointOnLine_10(point: ptr, line: ptr) -> vec3f { - var pointVector = ((*point) - (*line).origin); - var projection = dot(pointVector, (*line).dir); - return ((*line).origin + ((*line).dir * projection)); + fn projectPointOnLine_10(point: vec3f, line: Line3_9) -> vec3f { + var pointVector = (point - line.origin); + var projection = dot(pointVector, line.dir); + return (line.origin + (line.dir * projection)); } @group(1) @binding(3) var timePassed_11: f32; @@ -179,7 +179,7 @@ describe('3d fish example', () => { } } if ((mouseRay_7.activated == 1)) { - var proj = projectPointOnLine_10((&(*fishData).position), (&mouseRay_7.line)); + var proj = projectPointOnLine_10((*fishData).position, mouseRay_7.line); var diff = ((*fishData).position - proj); const limit = 0.9; var str = (pow(2, clamp((limit - length(diff)), 0, limit)) - 1); diff --git a/packages/typegpu/tests/examples/individual/caustics.test.ts b/packages/typegpu/tests/examples/individual/caustics.test.ts index a3f62cd880..56efdd284d 100644 --- a/packages/typegpu/tests/examples/individual/caustics.test.ts +++ b/packages/typegpu/tests/examples/individual/caustics.test.ts @@ -106,41 +106,34 @@ describe('caustics example', () => { return mix(x, X, smoothPartial.x); } - fn caustics_7(uv: ptr, time2: f32, profile: vec3f) -> vec3f { - var distortion = sample_8(vec3f(((*uv) * 0.5), (time2 * 0.2))); - var uv2 = ((*uv) + distortion); - var noise = abs(sample_8(vec3f((uv2 * 5), time2))); - return pow(vec3f((1 - noise)), profile); - } - - fn caustics_17(uv: vec2f, time2: f32, profile: vec3f) -> vec3f { + fn caustics_7(uv: vec2f, time2: f32, profile: vec3f) -> vec3f { var distortion = sample_8(vec3f((uv * 0.5), (time2 * 0.2))); var uv2 = (uv + distortion); var noise = abs(sample_8(vec3f((uv2 * 5), time2))); return pow(vec3f((1 - noise)), profile); } - fn rotateXY_18(angle2: f32) -> mat2x2f { + fn rotateXY_17(angle2: f32) -> mat2x2f { return mat2x2f(vec2f(cos(angle2), sin(angle2)), vec2f(-sin(angle2), cos(angle2))); } - struct mainFragment_Input_19 { + struct mainFragment_Input_18 { @location(0) uv: vec2f, } - @fragment fn mainFragment_3(_arg_0: mainFragment_Input_19) -> @location(0) vec4f { + @fragment fn mainFragment_3(_arg_0: mainFragment_Input_18) -> @location(0) vec4f { var skewMat = mat2x2f(vec2f(0.9800665974617004, 0.19866932928562164), vec2f(((-0.19866933079506122 * 10) + (_arg_0.uv.x * 3)), 4.900332889206208)); var skewedUv = (skewMat * _arg_0.uv); var tile = tilePattern_5((skewedUv * tileDensity_4)); var albedo = mix(vec3f(0.10000000149011612), vec3f(1), tile); var cuv = vec2f(((_arg_0.uv.x * (pow((_arg_0.uv.y * 1.5), 3) + 0.1)) * 5), (pow((((_arg_0.uv.y * 1.5) + 0.1) * 1.5), 3) * 1)); - var c1 = (caustics_7((&cuv), (time_6 * 0.2), vec3f(4, 4, 1)) * vec3f(0.4000000059604645, 0.6499999761581421, 1)); - var c2 = (caustics_17((cuv * 2), (time_6 * 0.4), vec3f(16, 1, 4)) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)); + var c1 = (caustics_7(cuv, (time_6 * 0.2), vec3f(4, 4, 1)) * vec3f(0.4000000059604645, 0.6499999761581421, 1)); + var c2 = (caustics_7((cuv * 2), (time_6 * 0.4), vec3f(16, 1, 4)) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)); var blendCoord = vec3f((_arg_0.uv * vec2f(5, 10)), ((time_6 * 0.2) + 5)); var blend = saturate((sample_8(blendCoord) + 0.3)); var noFogColor = (albedo * mix(vec3f(0.20000000298023224, 0.5, 1), (c1 + c2), blend)); var fog = min((pow(_arg_0.uv.y, 0.5) * 1.2), 1); - var godRayUv = ((rotateXY_18(-0.3) * _arg_0.uv) * vec2f(15, 3)); + var godRayUv = ((rotateXY_17(-0.3) * _arg_0.uv) * vec2f(15, 3)); var godRayFactor = pow(_arg_0.uv.y, 1); var godRay1 = ((sample_8(vec3f(godRayUv, (time_6 * 0.5))) + 1) * (vec3f(0.18000000715255737, 0.30000001192092896, 0.5) * godRayFactor)); var godRay2 = ((sample_8(vec3f((godRayUv * 2), (time_6 * 0.3))) + 1) * (vec3f(0.18000000715255737, 0.30000001192092896, 0.5) * (godRayFactor * 0.4))); diff --git a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts index 3657d44634..6532da6c10 100644 --- a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts +++ b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts @@ -29,21 +29,21 @@ describe('cubemap reflection example', () => { @group(0) @binding(0) var prevVertices_1: array; - fn unpackVec2u_3(packed: ptr) -> vec4f { - var xy = unpack2x16float((*packed).x); - var zw = unpack2x16float((*packed).y); + fn unpackVec2u_3(packed: vec2u) -> vec4f { + var xy = unpack2x16float(packed.x); + var zw = unpack2x16float(packed.y); return vec4f(xy, zw); } - fn calculateMidpoint_4(v1: ptr, v2: ptr) -> vec4f { - return vec4f((0.5 * ((*v1).xyz + (*v2).xyz)), 1); + fn calculateMidpoint_4(v1: vec4f, v2: vec4f) -> vec4f { + return vec4f((0.5 * (v1.xyz + v2.xyz)), 1); } @group(0) @binding(2) var smoothFlag_5: u32; - fn getAverageNormal_6(v1: ptr, v2: ptr, v3: ptr) -> vec4f { - var edge1 = ((*v2).xyz - (*v1).xyz); - var edge2 = ((*v3).xyz - (*v1).xyz); + fn getAverageNormal_6(v1: vec4f, v2: vec4f, v3: vec4f) -> vec4f { + var edge1 = (v2.xyz - v1.xyz); + var edge2 = (v3.xyz - v1.xyz); return normalize(vec4f(cross(edge1, edge2), 0)); } @@ -55,23 +55,29 @@ describe('cubemap reflection example', () => { return vec2u(xy, zw); } - struct computeFn_Input_9 { + fn packVec2u_9(toPack: vec4f) -> vec2u { + var xy = pack2x16float(toPack.xy); + var zw = pack2x16float(toPack.zw); + return vec2u(xy, zw); + } + + struct computeFn_Input_10 { @builtin(global_invocation_id) gid: vec3u, } - @compute @workgroup_size(256, 1, 1) fn computeFn_0(input: computeFn_Input_9) { + @compute @workgroup_size(256, 1, 1) fn computeFn_0(input: computeFn_Input_10) { var triangleCount = u32((f32(arrayLength(&prevVertices_1)) / 3f)); var triangleIndex = (input.gid.x + (input.gid.y * 65535)); if ((triangleIndex >= triangleCount)) { return; } var baseIndexPrev = (triangleIndex * 3); - var v1 = unpackVec2u_3((&prevVertices_1[baseIndexPrev].position)); - var v2 = unpackVec2u_3((&prevVertices_1[(baseIndexPrev + 1)].position)); - var v3 = unpackVec2u_3((&prevVertices_1[(baseIndexPrev + 2)].position)); - var v12 = vec4f(normalize(calculateMidpoint_4((&v1), (&v2)).xyz), 1); - var v23 = vec4f(normalize(calculateMidpoint_4((&v2), (&v3)).xyz), 1); - var v31 = vec4f(normalize(calculateMidpoint_4((&v3), (&v1)).xyz), 1); + var v1 = unpackVec2u_3(prevVertices_1[baseIndexPrev].position); + var v2 = unpackVec2u_3(prevVertices_1[(baseIndexPrev + 1)].position); + var v3 = unpackVec2u_3(prevVertices_1[(baseIndexPrev + 2)].position); + var v12 = vec4f(normalize(calculateMidpoint_4(v1, v2).xyz), 1); + var v23 = vec4f(normalize(calculateMidpoint_4(v2, v3).xyz), 1); + var v31 = vec4f(normalize(calculateMidpoint_4(v3, v1).xyz), 1); var newVertices = array(v1, v12, v31, v2, v23, v12, v3, v31, v23, v12, v23, v31); var baseIndexNext = (triangleIndex * 12); for (var i = 0u; (i < 12); i++) { @@ -79,12 +85,12 @@ describe('cubemap reflection example', () => { var triBase = (i - (i % 3)); var normal = (*reprojectedVertex); if ((smoothFlag_5 == 0)) { - normal = getAverageNormal_6((&newVertices[triBase]), (&newVertices[(triBase + 1)]), (&newVertices[(triBase + 2)])); + normal = getAverageNormal_6(newVertices[triBase], newVertices[(triBase + 1)], newVertices[(triBase + 2)]); } var outIndex = (baseIndexNext + i); let nextVertex = (&nextVertices_7[outIndex]); (*nextVertex).position = packVec2u_8(reprojectedVertex); - (*nextVertex).normal = packVec2u_8((&normal)); + (*nextVertex).normal = packVec2u_9(normal); nextVertices_7[outIndex] = (*nextVertex); } } diff --git a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts index 3c27165b6e..f760347578 100644 --- a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts +++ b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts @@ -106,10 +106,10 @@ describe('liquid-glass example', () => { @group(0) @binding(3) var sampler_11: sampler; - fn sampleWithChromaticAberration_12(tex: texture_2d, sampler2: sampler, uv: vec2f, offset: f32, dir: ptr, blur: f32) -> vec3f { + fn sampleWithChromaticAberration_12(tex: texture_2d, sampler2: sampler, uv: vec2f, offset: f32, dir: vec2f, blur: f32) -> vec3f { var samples = array(); for (var i = 0; (i < 3); i++) { - var channelOffset = ((*dir) * ((f32(i) - 1) * offset)); + var channelOffset = (dir * ((f32(i) - 1) * offset)); samples[i] = textureSampleBias(tex, sampler2, (uv - channelOffset), blur).xyz; } return vec3f(samples[0].x, samples[1].y, samples[2].z); @@ -120,19 +120,15 @@ describe('liquid-glass example', () => { strength: f32, } - fn applyTint_14(color: vec3f, tint: ptr) -> vec4f { - return mix(vec4f(color, 1), vec4f((*tint).color, 1), (*tint).strength); + fn applyTint_14(color: vec3f, tint: TintParams_13) -> vec4f { + return mix(vec4f(color, 1), vec4f(tint.color, 1), tint.strength); } - fn applyTint_15(color: ptr, tint: ptr) -> vec4f { - return mix(vec4f((*color), 1), vec4f((*tint).color, 1), (*tint).strength); - } - - struct fragmentShader_Input_16 { + struct fragmentShader_Input_15 { @location(0) uv: vec2f, } - @fragment fn fragmentShader_3(_arg_0: fragmentShader_Input_16) -> @location(0) vec4f { + @fragment fn fragmentShader_3(_arg_0: fragmentShader_Input_15) -> @location(0) vec4f { var posInBoxSpace = (_arg_0.uv - mousePosUniform_4); var sdfDist = sdRoundedBox2d_7(posInBoxSpace, paramsUniform_5.rectDims, paramsUniform_5.radius); var dir = normalize((posInBoxSpace * paramsUniform_5.rectDims.yx)); @@ -141,11 +137,11 @@ describe('liquid-glass example', () => { var featherUV = (paramsUniform_5.edgeFeather / f32(max(texDim.x, texDim.y))); var weights = calculateWeights_9(sdfDist, paramsUniform_5.start, paramsUniform_5.end, featherUV); var blurSample = textureSampleBias(sampledView_8, sampler_11, _arg_0.uv, paramsUniform_5.blur); - var refractedSample = sampleWithChromaticAberration_12(sampledView_8, sampler_11, (_arg_0.uv + (dir * (paramsUniform_5.refractionStrength * normalizedDist))), (paramsUniform_5.chromaticStrength * normalizedDist), (&dir), (paramsUniform_5.blur * paramsUniform_5.edgeBlurMultiplier)); + var refractedSample = sampleWithChromaticAberration_12(sampledView_8, sampler_11, (_arg_0.uv + (dir * (paramsUniform_5.refractionStrength * normalizedDist))), (paramsUniform_5.chromaticStrength * normalizedDist), dir, (paramsUniform_5.blur * paramsUniform_5.edgeBlurMultiplier)); var normalSample = textureSampleLevel(sampledView_8, sampler_11, _arg_0.uv, 0); var tint = TintParams_13(paramsUniform_5.tintColor, paramsUniform_5.tintStrength); - var tintedBlur = applyTint_14(blurSample.xyz, (&tint)); - var tintedRing = applyTint_15((&refractedSample), (&tint)); + var tintedBlur = applyTint_14(blurSample.xyz, tint); + var tintedRing = applyTint_14(refractedSample, tint); return (((tintedBlur * weights.inside) + (tintedRing * weights.ring)) + (normalSample * weights.outside)); }" `); diff --git a/packages/typegpu/tests/examples/individual/oklab.test.ts b/packages/typegpu/tests/examples/individual/oklab.test.ts index c1e6a20646..2e3dc4ca6c 100644 --- a/packages/typegpu/tests/examples/individual/oklab.test.ts +++ b/packages/typegpu/tests/examples/individual/oklab.test.ts @@ -136,7 +136,7 @@ describe('oklab example', () => { } fn findGamutIntersection_13(a: f32, b: f32, L1: f32, C1: f32, L0: f32, cusp: LC_12) -> f32 { - let FLT_MAX = (&3.40282346e+38); + const FLT_MAX = 3.40282346e+38; var t = 0f; if (((((L1 - L0) * cusp.C) - ((cusp.L - L0) * C1)) <= 0)) { t = ((cusp.C * L0) / ((C1 * cusp.L) + (cusp.C * (L0 - L1)))); @@ -182,9 +182,9 @@ describe('oklab example', () => { var b22 = (((-0.0041960863 * ldt2) - (0.7034186147 * mdt2)) + (1.707614701 * sdt2)); var u_b = (b1 / ((b1 * b1) - ((0.5 * b2) * b22))); var t_b = (-b2 * u_b); - t_r = select((*FLT_MAX), t_r, (u_r >= 0)); - t_g = select((*FLT_MAX), t_g, (u_g >= 0)); - t_b = select((*FLT_MAX), t_b, (u_b >= 0)); + t_r = select(FLT_MAX, t_r, (u_r >= 0)); + t_g = select(FLT_MAX, t_g, (u_g >= 0)); + t_b = select(FLT_MAX, t_b, (u_b >= 0)); t += min(t_r, min(t_g, t_b)); } } diff --git a/packages/typegpu/tests/examples/individual/ray-marching.test.ts b/packages/typegpu/tests/examples/individual/ray-marching.test.ts index 74e92b40e4..09178ed1fd 100644 --- a/packages/typegpu/tests/examples/individual/ray-marching.test.ts +++ b/packages/typegpu/tests/examples/individual/ray-marching.test.ts @@ -52,18 +52,18 @@ describe('ray-marching example', () => { return min(min(d1, d2), d3); } - fn smoothShapeUnion_11(a: ptr, b: ptr, k: f32) -> Shape_6 { - var h = (max((k - abs(((*a).dist - (*b).dist))), 0) / k); + fn smoothShapeUnion_11(a: Shape_6, b: Shape_6, k: f32) -> Shape_6 { + var h = (max((k - abs((a.dist - b.dist))), 0) / k); var m = (h * h); - var dist = (min((*a).dist, (*b).dist) - ((m * k) * 0.25)); - var weight = (m + select(0, (1 - m), ((*a).dist > (*b).dist))); - var color = mix((*a).color, (*b).color, weight); + var dist = (min(a.dist, b.dist) - ((m * k) * 0.25)); + var weight = (m + select(0, (1 - m), (a.dist > b.dist))); + var color = mix(a.color, b.color, weight); return Shape_6(color, dist); } - fn getMorphingShape_8(p: ptr, t: f32) -> Shape_6 { + fn getMorphingShape_8(p: vec3f, t: f32) -> Shape_6 { var center = vec3f(0, 2, 6); - var localP = ((*p) - center); + var localP = (p - center); var rotMatZ = mat4x4f(cos(-t), sin(-t), 0, 0, -sin(-t), cos(-t), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); var rotMatX = mat4x4f(1, 0, 0, 0, 0, cos((-t * 0.6)), sin((-t * 0.6)), 0, 0, -sin((-t * 0.6)), cos((-t * 0.6)), 0, 0, 0, 0, 1); var rotatedP = (rotMatZ * (rotMatX * vec4f(localP, 1))).xyz; @@ -73,8 +73,8 @@ describe('ray-marching example', () => { var sphere1 = Shape_6(vec3f(0.4000000059604645, 0.5, 1), sdSphere_9((localP - sphere1Offset), 0.5)); var sphere2 = Shape_6(vec3f(1, 0.800000011920929, 0.20000000298023224), sdSphere_9((localP - sphere2Offset), 0.3)); var box = Shape_6(vec3f(1, 0.30000001192092896, 0.30000001192092896), sdBoxFrame3d_10(rotatedP, boxSize, 0.1)); - var spheres = smoothShapeUnion_11((&sphere1), (&sphere2), 0.1); - return smoothShapeUnion_11((&spheres), (&box), 0.2); + var spheres = smoothShapeUnion_11(sphere1, sphere2, 0.1); + return smoothShapeUnion_11(spheres, box, 0.2); } @group(0) @binding(1) var time_12: f32; @@ -88,22 +88,22 @@ describe('ray-marching example', () => { return (dot(p, n) + h); } - fn shapeUnion_15(a: ptr, b: ptr) -> Shape_6 { - return Shape_6(select((*a).color, (*b).color, ((*a).dist > (*b).dist)), min((*a).dist, (*b).dist)); + fn shapeUnion_15(a: Shape_6, b: Shape_6) -> Shape_6 { + return Shape_6(select(a.color, b.color, (a.dist > b.dist)), min(a.dist, b.dist)); } - fn getSceneDist_7(p: ptr) -> Shape_6 { + fn getSceneDist_7(p: vec3f) -> Shape_6 { var shape = getMorphingShape_8(p, time_12); - var floor = Shape_6(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard_13(((*p).xz * 2))), sdPlane_14((*p), vec3f(0, 1, 0), 0)); - return shapeUnion_15((&shape), (&floor)); + var floor = Shape_6(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard_13((p.xz * 2))), sdPlane_14(p, vec3f(0, 1, 0), 0)); + return shapeUnion_15(shape, floor); } - fn rayMarch_5(ro: ptr, rd: ptr) -> Shape_6 { + fn rayMarch_5(ro: vec3f, rd: vec3f) -> Shape_6 { var dO = 0f; var result = Shape_6(vec3f(), 30); for (var i = 0; (i < 1000); i++) { - var p = ((*ro) + ((*rd) * dO)); - var scene = getSceneDist_7((&p)); + var p = (ro + (rd * dO)); + var scene = getSceneDist_7(p); dO += scene.dist; if (((dO > 30) || (scene.dist < 1e-3))) { result.dist = dO; @@ -114,50 +114,28 @@ describe('ray-marching example', () => { return result; } - fn getMorphingShape_18(p: vec3f, t: f32) -> Shape_6 { - var center = vec3f(0, 2, 6); - var localP = (p - center); - var rotMatZ = mat4x4f(cos(-t), sin(-t), 0, 0, -sin(-t), cos(-t), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); - var rotMatX = mat4x4f(1, 0, 0, 0, 0, cos((-t * 0.6)), sin((-t * 0.6)), 0, 0, -sin((-t * 0.6)), cos((-t * 0.6)), 0, 0, 0, 0, 1); - var rotatedP = (rotMatZ * (rotMatX * vec4f(localP, 1))).xyz; - var boxSize = vec3f(0.699999988079071); - var sphere1Offset = vec3f((cos((t * 2)) * 0.8), (sin((t * 3)) * 0.3), (sin((t * 2)) * 0.8)); - var sphere2Offset = vec3f((cos(((t * 2) + 3.14)) * 0.8), (sin(((t * 3) + 1.57)) * 0.3), (sin(((t * 2) + 3.14)) * 0.8)); - var sphere1 = Shape_6(vec3f(0.4000000059604645, 0.5, 1), sdSphere_9((localP - sphere1Offset), 0.5)); - var sphere2 = Shape_6(vec3f(1, 0.800000011920929, 0.20000000298023224), sdSphere_9((localP - sphere2Offset), 0.3)); - var box = Shape_6(vec3f(1, 0.30000001192092896, 0.30000001192092896), sdBoxFrame3d_10(rotatedP, boxSize, 0.1)); - var spheres = smoothShapeUnion_11((&sphere1), (&sphere2), 0.1); - return smoothShapeUnion_11((&spheres), (&box), 0.2); - } - - fn getSceneDist_17(p: vec3f) -> Shape_6 { - var shape = getMorphingShape_18(p, time_12); - var floor = Shape_6(mix(vec3f(1), vec3f(0.20000000298023224), checkerBoard_13((p.xz * 2))), sdPlane_14(p, vec3f(0, 1, 0), 0)); - return shapeUnion_15((&shape), (&floor)); - } - - fn getNormal_16(p: ptr) -> vec3f { + fn getNormal_16(p: vec3f) -> vec3f { var dist = getSceneDist_7(p).dist; const e = 0.01; - var n = vec3f((getSceneDist_17(((*p) + vec3f(e, 0, 0))).dist - dist), (getSceneDist_17(((*p) + vec3f(0, e, 0))).dist - dist), (getSceneDist_17(((*p) + vec3f(0, 0, e))).dist - dist)); + var n = vec3f((getSceneDist_7((p + vec3f(e, 0, 0))).dist - dist), (getSceneDist_7((p + vec3f(0, e, 0))).dist - dist), (getSceneDist_7((p + vec3f(0, 0, e))).dist - dist)); return normalize(n); } - fn getOrbitingLightPos_19(t: f32) -> vec3f { + fn getOrbitingLightPos_17(t: f32) -> vec3f { const radius = 3f; const height = 6f; const speed = 1f; return vec3f((cos((t * speed)) * radius), (height + (sin((t * speed)) * radius)), 4); } - fn softShadow_20(ro: ptr, rd: ptr, minT: f32, maxT: f32, k: f32) -> f32 { + fn softShadow_18(ro: ptr, rd: ptr, minT: f32, maxT: f32, k: f32) -> f32 { var res = 1f; var t = minT; for (var i = 0; (i < 100); i++) { if ((t >= maxT)) { break; } - var h = getSceneDist_17(((*ro) + ((*rd) * t))).dist; + var h = getSceneDist_7(((*ro) + ((*rd) * t))).dist; if ((h < 1e-3)) { return 0; } @@ -167,26 +145,26 @@ describe('ray-marching example', () => { return res; } - struct fragmentMain_Input_21 { + struct fragmentMain_Input_19 { @location(0) uv: vec2f, } - @fragment fn fragmentMain_3(input: fragmentMain_Input_21) -> @location(0) vec4f { + @fragment fn fragmentMain_3(input: fragmentMain_Input_19) -> @location(0) vec4f { var uv = ((input.uv * 2) - 1); uv.x *= (resolution_4.x / resolution_4.y); var ro = vec3f(0, 2, 3); var rd = normalize(vec3f(uv.x, uv.y, 1)); - var march = rayMarch_5((&ro), (&rd)); + var march = rayMarch_5(ro, rd); var fog = pow(min((march.dist / 30f), 1), 0.7); var p = (ro + (rd * march.dist)); - var n = getNormal_16((&p)); - var lightPos = getOrbitingLightPos_19(time_12); + var n = getNormal_16(p); + var lightPos = getOrbitingLightPos_17(time_12); var l = normalize((lightPos - p)); var diff = max(dot(n, l), 0); let shadowRo = (&p); let shadowRd = (&l); var shadowDist = length((lightPos - p)); - var shadow = softShadow_20(shadowRo, shadowRd, 0.1, shadowDist, 16); + var shadow = softShadow_18(shadowRo, shadowRd, 0.1, shadowDist, 16); var litColor = (march.color * diff); var finalColor = mix((litColor * 0.5), litColor, shadow); return mix(vec4f(finalColor, 1), vec4f(0.699999988079071, 0.800000011920929, 0.8999999761581421, 1), fog); diff --git a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts index a064c7e6d4..f14d64c819 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts @@ -155,11 +155,11 @@ describe('slime mold 3d example', () => { return item_8(); } - fn getPerpendicular_10(dir: ptr) -> vec3f { + fn getPerpendicular_10(dir: vec3f) -> vec3f { var axis = vec3f(1, 0, 0); - var absX = abs((*dir).x); - var absY = abs((*dir).y); - var absZ = abs((*dir).z); + var absX = abs(dir.x); + var absY = abs(dir.y); + var absZ = abs(dir.z); if (((absY <= absX) && (absY <= absZ))) { axis = vec3f(0, 1, 0); } @@ -168,7 +168,7 @@ describe('slime mold 3d example', () => { axis = vec3f(0, 0, 1); } } - return normalize(cross((*dir), axis)); + return normalize(cross(dir, axis)); } struct Params_12 { @@ -187,19 +187,19 @@ describe('slime mold 3d example', () => { totalWeight: f32, } - fn sense3D_9(pos: ptr, direction: ptr) -> SenseResult_13 { + fn sense3D_9(pos: vec3f, direction: vec3f) -> SenseResult_13 { var dims = textureDimensions(oldState_4); var dimsf = vec3f(dims); var weightedDir = vec3f(); var totalWeight = 0f; var perp1 = getPerpendicular_10(direction); - var perp2 = cross((*direction), perp1); + var perp2 = cross(direction, perp1); const numSamples = 8; for (var i = 0; (i < numSamples); i++) { var theta = (((f32(i) / f32(numSamples)) * 2) * 3.141592653589793); var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); - var sensorDir = normalize(((*direction) + (coneOffset * sin(params_11.sensorAngle)))); - var sensorPos = ((*pos) + (sensorDir * params_11.sensorDistance)); + var sensorDir = normalize((direction + (coneOffset * sin(params_11.sensorAngle)))); + var sensorPos = (pos + (sensorDir * params_11.sensorDistance)); var sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - vec3f(1)))); var weight = textureLoad(oldState_4, sensorPosInt).x; weightedDir = (weightedDir + (sensorDir * weight)); @@ -247,13 +247,13 @@ describe('slime mold 3d example', () => { let agent = (&agentsData_5[_arg_0.gid.x]); var random = randFloat01_7(); var direction = normalize((*agent).direction); - var senseResult = sense3D_9((&(*agent).position), (&direction)); + var senseResult = sense3D_9((*agent).position, direction); if ((senseResult.totalWeight > 0.01)) { var targetDir = normalize(senseResult.weightedDir); direction = normalize((direction + (targetDir * (params_11.turnSpeed * params_11.deltaTime)))); } else { - var perp = getPerpendicular_10((&direction)); + var perp = getPerpendicular_10(direction); var randomOffset = (perp * ((((random * 2) - 1) * params_11.turnSpeed) * params_11.deltaTime)); direction = normalize((direction + randomOffset)); } @@ -325,10 +325,10 @@ describe('slime mold 3d example', () => { hit: bool, } - fn rayBoxIntersection_6(rayOrigin: ptr, rayDir: ptr, boxMin: ptr, boxMax: ptr) -> RayBoxResult_7 { - var invDir = (vec3f(1) / (*rayDir)); - var t0 = (((*boxMin) - (*rayOrigin)) * invDir); - var t1 = (((*boxMax) - (*rayOrigin)) * invDir); + fn rayBoxIntersection_6(rayOrigin: vec3f, rayDir: vec3f, boxMin: vec3f, boxMax: vec3f) -> RayBoxResult_7 { + var invDir = (vec3f(1) / rayDir); + var t0 = ((boxMin - rayOrigin) * invDir); + var t1 = ((boxMax - rayOrigin) * invDir); var tmin = min(t0, t1); var tmax = max(t0, t1); var tNear = max(max(tmin.x, tmin.y), tmin.z); @@ -356,7 +356,7 @@ describe('slime mold 3d example', () => { var rayDir = normalize((rayEnd - rayOrigin)); var boxMin = vec3f(); var boxMax = vec3f(256); - var isect = rayBoxIntersection_6((&rayOrigin), (&rayDir), (&boxMin), (&boxMax)); + var isect = rayBoxIntersection_6(rayOrigin, rayDir, boxMin, boxMax); if (!isect.hit) { return vec4f(); } diff --git a/packages/typegpu/tests/examples/individual/slime-mold.test.ts b/packages/typegpu/tests/examples/individual/slime-mold.test.ts index 385bb8e182..f2bbe84f03 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold.test.ts @@ -150,10 +150,10 @@ describe('slime mold example', () => { @group(0) @binding(1) var params_10: Params_11; - fn sense_9(pos: ptr, angle: f32, sensorAngleOffset: f32) -> f32 { + fn sense_9(pos: vec2f, angle: f32, sensorAngleOffset: f32) -> f32 { var sensorAngle = (angle + sensorAngleOffset); var sensorDir = vec2f(cos(sensorAngle), sin(sensorAngle)); - var sensorPos = ((*pos) + (sensorDir * params_10.sensorDistance)); + var sensorPos = (pos + (sensorDir * params_10.sensorDistance)); var dims = textureDimensions(oldState_4); var dimsf = vec2f(dims); var sensorPosInt = vec2u(clamp(sensorPos, vec2f(), (dimsf - vec2f(1)))); @@ -177,9 +177,9 @@ describe('slime mold example', () => { var dims = textureDimensions(oldState_4); let agent = (&agentsData_5[_arg_0.gid.x]); var random = randFloat01_7(); - var weightForward = sense_9((&(*agent).position), (*agent).angle, 0); - var weightLeft = sense_9((&(*agent).position), (*agent).angle, params_10.sensorAngle); - var weightRight = sense_9((&(*agent).position), (*agent).angle, -params_10.sensorAngle); + var weightForward = sense_9((*agent).position, (*agent).angle, 0); + var weightLeft = sense_9((*agent).position, (*agent).angle, params_10.sensorAngle); + var weightRight = sense_9((*agent).position, (*agent).angle, -params_10.sensorAngle); var angle = (*agent).angle; if (((weightForward > weightLeft) && (weightForward > weightRight))) { diff --git a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts index ee427313e8..43cc56187f 100644 --- a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts +++ b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts @@ -162,54 +162,61 @@ describe('tgsl parsing test example', () => { return s; } - fn modifyVecFn_12(ptr: ptr) { + fn modifyNumFn_12(ptr: ptr) { + (*ptr) += 1; + } + + fn modifyVecFn_13(ptr: ptr) { (*ptr).x += 1; } - struct SimpleStruct_13 { + struct SimpleStruct_14 { vec: vec2f, } - fn modifyStructFn_14(ptr: ptr) { + fn modifyStructFn_15(ptr: ptr) { (*ptr).vec.x += 1; } - var privateNum_15: u32; + var privateNum_16: u32; - fn modifyNumPrivate_16(ptr: ptr) { + fn modifyNumPrivate_17(ptr: ptr) { (*ptr) += 1; } - var privateVec_17: vec2f; + var privateVec_18: vec2f; - fn modifyVecPrivate_18(ptr: ptr) { + fn modifyVecPrivate_19(ptr: ptr) { (*ptr).x += 1; } - var privateStruct_19: SimpleStruct_13; + var privateStruct_20: SimpleStruct_14; - fn modifyStructPrivate_20(ptr: ptr) { + fn modifyStructPrivate_21(ptr: ptr) { (*ptr).vec.x += 1; } fn pointersTest_11() -> bool { var s = true; + var num = 0u; + modifyNumFn_12((&num)); + s = (s && (num == 1)); var vec = vec2f(); - modifyVecFn_12((&vec)); + modifyVecFn_13((&vec)); s = (s && all(vec == vec2f(1, 0))); - var myStruct = SimpleStruct_13(); - modifyStructFn_14((&myStruct)); + var myStruct = SimpleStruct_14(); + modifyStructFn_15((&myStruct)); s = (s && all(myStruct.vec == vec2f(1, 0))); - modifyNumPrivate_16((&privateNum_15)); - s = (s && (privateNum_15 == 1)); - modifyVecPrivate_18((&privateVec_17)); - s = (s && all(privateVec_17 == vec2f(1, 0))); - modifyStructPrivate_20((&privateStruct_19)); - s = (s && all(privateStruct_19.vec == vec2f(1, 0))); + modifyNumPrivate_17(privateNum_16); + s = (s && (privateNum_16 == 1)); + modifyVecPrivate_19(privateVec_18); + s = (s && all(privateVec_18 == vec2f(1, 0))); + modifyStructPrivate_21(privateStruct_20); + s = (s && all(privateStruct_20.vec == vec2f(1, 0))); return s; } - @group(0) @binding(0) var result_21: i32; + @group(0) @binding(0) var result_22: i32; @compute @workgroup_size(1) fn computeRunTests_0() { var s = true; @@ -219,10 +226,10 @@ describe('tgsl parsing test example', () => { s = (s && arrayAndStructConstructorsTest_8()); s = (s && pointersTest_11()); if (s) { - result_21 = 1; + result_22 = 1; } else { - result_21 = 0; + result_22 = 0; } }" `); diff --git a/packages/typegpu/tests/indent.test.ts b/packages/typegpu/tests/indent.test.ts index 973e285eac..c830d8a721 100644 --- a/packages/typegpu/tests/indent.test.ts +++ b/packages/typegpu/tests/indent.test.ts @@ -258,12 +258,13 @@ describe('indents', () => { [Particle, d.vec3f], Particle, )((particle, gravity) => { - if (particle.velocity.x > 0) { - particle.position = std.add(particle.position, particle.velocity); + const newParticle = Particle(particle); + if (newParticle.velocity.x > 0) { + newParticle.position = newParticle.position.add(newParticle.velocity); } else { - particle.position = std.add(particle.position, gravity); + newParticle.position = newParticle.position.add(gravity); } - return particle; + return newParticle; }); const code = tgpu.resolve({ externals: { updateParticle } }); @@ -274,13 +275,14 @@ describe('indents', () => { } fn updateParticle_0(particle: Particle_1, gravity: vec3f) -> Particle_1 { - if ((particle.velocity.x > 0)) { - particle.position = (particle.position + particle.velocity); + var newParticle = particle; + if ((newParticle.velocity.x > 0)) { + newParticle.position = (newParticle.position + newParticle.velocity); } else { - particle.position = (particle.position + gravity); + newParticle.position = (newParticle.position + gravity); } - return particle; + return newParticle; }" `); }); @@ -295,15 +297,18 @@ describe('indents', () => { [Particle, d.vec3f], Particle, )((particle, gravity) => { + const newParticle = Particle(particle); let iterations = 0; while (iterations < 10) { - particle.position = std.add(particle.position, particle.velocity); + newParticle.position = newParticle.position.add( + newParticle.velocity, + ); iterations += 1; - while (particle.position.x < 0) { - particle.position = std.add(particle.position, gravity); + while (newParticle.position.x < 0) { + newParticle.position = newParticle.position.add(gravity); } } - return particle; + return newParticle; }); const code = tgpu.resolve({ externals: { updateParticle } }); @@ -314,15 +319,16 @@ describe('indents', () => { } fn updateParticle_0(particle: Particle_1, gravity: vec3f) -> Particle_1 { + var newParticle = particle; var iterations = 0; while ((iterations < 10)) { - particle.position = (particle.position + particle.velocity); + newParticle.position = (newParticle.position + newParticle.velocity); iterations += 1; - while ((particle.position.x < 0)) { - particle.position = (particle.position + gravity); + while ((newParticle.position.x < 0)) { + newParticle.position = (newParticle.position + gravity); } } - return particle; + return newParticle; }" `); }); diff --git a/packages/typegpu/tests/namespace.test.ts b/packages/typegpu/tests/namespace.test.ts index 1c1f9a0652..c203acc215 100644 --- a/packages/typegpu/tests/namespace.test.ts +++ b/packages/typegpu/tests/namespace.test.ts @@ -43,7 +43,7 @@ describe('tgpu.namespace', () => { }); const updateBoid = tgpu.fn([d.ptrFn(Boid)])((boid) => { - boid.pos.x += 1; + boid.$.pos.x += 1; }); const names = tgpu['~unstable'].namespace(); diff --git a/packages/typegpu/tests/ref.test.ts b/packages/typegpu/tests/ref.test.ts new file mode 100644 index 0000000000..515948f1d8 --- /dev/null +++ b/packages/typegpu/tests/ref.test.ts @@ -0,0 +1,46 @@ +import * as d from 'typegpu/data'; +import { describe, expect } from 'vitest'; +import { it } from './utils/extendedIt.ts'; +import { asWgsl } from './utils/parseResolved.ts'; + +describe('ref', () => { + it.skip('fails when created outside of a TypeGPU function', () => { + expect(() => d.ref(0)).toThrowErrorMatchingInlineSnapshot(); + }); + + it('creates a regular looking variable in WGSL', () => { + const hello = () => { + 'use gpu'; + const ref = d.ref(0); + }; + + expect(asWgsl(hello)).toMatchInlineSnapshot(` + "fn hello() { + var ref_1 = 0; + }" + `); + }); + + it('fails when trying to assign a ref to an existing variable', () => { + const update = (value: d.ref) => { + 'use gpu'; + value.$ += 1; + }; + + const hello = () => { + 'use gpu'; + let foo = d.ref(0); + update(foo); + // Nuh-uh + foo = d.ref(1); + update(foo); + }; + + expect(() => asWgsl(hello)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:hello + - fn*:hello: Cannot assign a ref to an existing variable '(&foo)', define a new variable instead.] + `); + }); +}); diff --git a/packages/typegpu/tests/tgsl/generationHelpers.test.ts b/packages/typegpu/tests/tgsl/generationHelpers.test.ts index 06620c4754..27d269466a 100644 --- a/packages/typegpu/tests/tgsl/generationHelpers.test.ts +++ b/packages/typegpu/tests/tgsl/generationHelpers.test.ts @@ -46,31 +46,31 @@ describe('generationHelpers', () => { describe('numericLiteralToSnippet', () => { it('should convert numeric literals to correct snippets', () => { expect(numericLiteralToSnippet(1)).toEqual( - snip(1, abstractInt, /* ref */ 'constant'), + snip(1, abstractInt, /* origin */ 'constant'), ); expect(numericLiteralToSnippet(1.1)).toEqual( - snip(1.1, abstractFloat, /* ref */ 'constant'), + snip(1.1, abstractFloat, /* origin */ 'constant'), ); expect(numericLiteralToSnippet(1e10)).toEqual( - snip(1e10, abstractInt, /* ref */ 'constant'), + snip(1e10, abstractInt, /* origin */ 'constant'), ); expect(numericLiteralToSnippet(0.5)).toEqual( - snip(0.5, abstractFloat, /* ref */ 'constant'), + snip(0.5, abstractFloat, /* origin */ 'constant'), ); expect(numericLiteralToSnippet(-45)).toEqual( - snip(-45, abstractInt, /* ref */ 'constant'), + snip(-45, abstractInt, /* origin */ 'constant'), ); expect(numericLiteralToSnippet(0x1A)).toEqual( - snip(0x1A, abstractInt, /* ref */ 'constant'), + snip(0x1A, abstractInt, /* origin */ 'constant'), ); expect(numericLiteralToSnippet(0b101)).toEqual( - snip(5, abstractInt, /* ref */ 'constant'), + snip(5, abstractInt, /* origin */ 'constant'), ); }); }); @@ -82,33 +82,33 @@ describe('generationHelpers', () => { }); it('should return struct property types', () => { - const target = snip('foo', MyStruct, 'this-function'); + const target = snip('foo', MyStruct, 'function'); expect(accessProp(target, 'foo')).toStrictEqual( - snip('foo.foo', f32, /* ref */ 'runtime'), + snip('foo.foo', f32, /* origin */ 'runtime'), ); expect(accessProp(target, 'bar')).toStrictEqual( - snip('foo.bar', vec3f, /* ref */ 'this-function'), + snip('foo.bar', vec3f, /* origin */ 'function'), ); expect(accessProp(target, 'notfound')).toStrictEqual(undefined); }); it('should return swizzle types on vectors', () => { - const target = snip('foo', vec4f, 'this-function'); + const target = snip('foo', vec4f, 'function'); expect(accessProp(target, 'x')).toStrictEqual( - snip('foo.x', f32, /* ref */ 'runtime'), + snip('foo.x', f32, /* origin */ 'runtime'), ); expect(accessProp(target, 'yz')).toStrictEqual( - snip('foo.yz', vec2f, /* ref */ 'runtime'), + snip('foo.yz', vec2f, /* origin */ 'runtime'), ); expect(accessProp(target, 'xyzw')).toStrictEqual( - snip('foo.xyzw', vec4f, /* ref */ 'runtime'), + snip('foo.xyzw', vec4f, /* origin */ 'runtime'), ); }); it('should return undefined when applied to primitives or invalid', () => { - const target1 = snip('foo', u32, /* ref */ 'runtime'); - const target2 = snip('foo', bool, /* ref */ 'runtime'); + const target1 = snip('foo', u32, /* origin */ 'runtime'); + const target2 = snip('foo', bool, /* origin */ 'runtime'); expect(accessProp(target1, 'x')).toBe(undefined); expect(accessProp(target2, 'x')).toBe(undefined); }); @@ -116,18 +116,18 @@ describe('generationHelpers', () => { describe('accessIndex', () => { const arr = arrayOf(f32, 2); - const index = snip('0', u32, /* ref */ 'runtime'); + const index = snip('0', u32, /* origin */ 'runtime'); it('returns element type for arrays', () => { - const target = snip('foo', arr, /* ref */ 'runtime'); + const target = snip('foo', arr, /* origin */ 'runtime'); expect(accessIndex(target, index)).toStrictEqual( snip('foo[0]', f32, 'runtime'), ); }); it('returns vector component', () => { - const target1 = snip('foo', vec2i, /* ref */ 'runtime'); - const target2 = snip('foo', vec4h, /* ref */ 'runtime'); + const target1 = snip('foo', vec2i, /* origin */ 'runtime'); + const target2 = snip('foo', vec4h, /* origin */ 'runtime'); expect(accessIndex(target1, index)).toStrictEqual( snip('foo[0]', i32, 'runtime'), ); @@ -138,15 +138,15 @@ describe('generationHelpers', () => { it('returns matrix column type', () => { const target1 = accessProp( - snip('foo', mat2x2f, /* ref */ 'runtime'), + snip('foo', mat2x2f, /* origin */ 'runtime'), 'columns', ); const target2 = accessProp( - snip('foo', mat3x3f, /* ref */ 'runtime'), + snip('foo', mat3x3f, /* origin */ 'runtime'), 'columns', ); const target3 = accessProp( - snip('foo', mat4x4f, /* ref */ 'runtime'), + snip('foo', mat4x4f, /* origin */ 'runtime'), 'columns', ); expect(target1 && accessIndex(target1, index)).toStrictEqual( @@ -161,7 +161,7 @@ describe('generationHelpers', () => { }); it('returns undefined otherwise', () => { - const target = snip('foo', f32, /* ref */ 'runtime'); + const target = snip('foo', f32, /* origin */ 'runtime'); expect(accessIndex(target, index)).toBe(undefined); }); }); @@ -171,37 +171,37 @@ describe('generationHelpers', () => { it('coerces JS numbers', () => { expect(coerceToSnippet(1)).toEqual( - snip(1, abstractInt, /* ref */ 'constant'), + snip(1, abstractInt, /* origin */ 'constant'), ); expect(coerceToSnippet(2.5)).toEqual( - snip(2.5, abstractFloat, /* ref */ 'constant'), + snip(2.5, abstractFloat, /* origin */ 'constant'), ); expect(coerceToSnippet(-10)).toEqual( - snip(-10, abstractInt, /* ref */ 'constant'), + snip(-10, abstractInt, /* origin */ 'constant'), ); expect(coerceToSnippet(0.0)).toEqual( - snip(0, abstractInt, /* ref */ 'constant'), + snip(0, abstractInt, /* origin */ 'constant'), ); }); it('coerces JS booleans', () => { expect(coerceToSnippet(true)).toEqual( - snip(true, bool, /* ref */ 'constant'), + snip(true, bool, /* origin */ 'constant'), ); expect(coerceToSnippet(false)).toEqual( - snip(false, bool, /* ref */ 'constant'), + snip(false, bool, /* origin */ 'constant'), ); }); it(`coerces schemas to UnknownData (as they're not instance types)`, () => { expect(coerceToSnippet(f32)).toEqual( - snip(f32, UnknownData, /* ref */ 'constant'), + snip(f32, UnknownData, /* origin */ 'constant'), ); expect(coerceToSnippet(vec3i)).toEqual( - snip(vec3i, UnknownData, /* ref */ 'constant'), + snip(vec3i, UnknownData, /* origin */ 'constant'), ); expect(coerceToSnippet(arr)).toEqual( - snip(arr, UnknownData, /* ref */ 'constant'), + snip(arr, UnknownData, /* origin */ 'constant'), ); }); @@ -227,20 +227,20 @@ describe('generationHelpers', () => { it('returns UnknownData for other types', () => { expect(coerceToSnippet('foo')).toEqual( - snip('foo', UnknownData, /* ref */ 'constant'), + snip('foo', UnknownData, /* origin */ 'constant'), ); expect(coerceToSnippet({})).toEqual( - snip({}, UnknownData, /* ref */ 'constant'), + snip({}, UnknownData, /* origin */ 'constant'), ); expect(coerceToSnippet(null)).toEqual( - snip(null, UnknownData, /* ref */ 'constant'), + snip(null, UnknownData, /* origin */ 'constant'), ); expect(coerceToSnippet(undefined)).toEqual( - snip(undefined, UnknownData, /* ref */ 'constant'), + snip(undefined, UnknownData, /* origin */ 'constant'), ); const fn = () => {}; expect(coerceToSnippet(fn)).toEqual( - snip(fn, UnknownData, /* ref */ 'constant'), + snip(fn, UnknownData, /* origin */ 'constant'), ); }); }); diff --git a/packages/typegpu/tests/tgsl/memberAccess.test.ts b/packages/typegpu/tests/tgsl/memberAccess.test.ts index a8192e18b5..5e028aa4dd 100644 --- a/packages/typegpu/tests/tgsl/memberAccess.test.ts +++ b/packages/typegpu/tests/tgsl/memberAccess.test.ts @@ -58,7 +58,7 @@ describe('Member Access', () => { // Taking a reference that is local to this function const boidRef = boid; boidRef.pos; - }).toStrictEqual(snip('(*boidRef).pos', d.vec3f, 'this-function')); + }).toStrictEqual(snip('(*boidRef).pos', d.vec3f, 'function')); }); it('derefs access to storage with proper address space', ({ root }) => { diff --git a/packages/typegpu/tests/tgsl/shellless.test.ts b/packages/typegpu/tests/tgsl/shellless.test.ts index 418f5b2638..51e569c606 100644 --- a/packages/typegpu/tests/tgsl/shellless.test.ts +++ b/packages/typegpu/tests/tgsl/shellless.test.ts @@ -158,16 +158,16 @@ describe('shellless', () => { `); }); - it('generates pointer type to handle references', () => { - const advance = (pos: d.v3f, vel: d.v3f) => { + it('handles refs and generates pointer arguments for them', () => { + const advance = (pos: d.ref, vel: d.v3f) => { 'use gpu'; - pos.x += vel.x; - pos.y += vel.y; - pos.z += vel.z; + pos.$.x += vel.x; + pos.$.y += vel.y; + pos.$.z += vel.z; }; const main = tgpu.fn([])(() => { - const pos = d.vec3f(0, 0, 0); + const pos = d.ref(d.vec3f(0, 0, 0)); advance(pos, d.vec3f(1, 2, 3)); }); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index 4f8ec5579b..c2b1250339 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -332,7 +332,7 @@ describe('wgslGenerator', () => { // ^ this part should be an atomic u32 // ^ this part should be void ctx[$internal].itemStateStack.pushBlockScope(); - wgslGenerator.blockVariable('var', 'vec', d.vec4f, 'this-function'); + wgslGenerator.blockVariable('var', 'vec', d.vec4f, 'function'); const res3 = wgslGenerator.expression( (astInfo.ast?.body[1][2] as tinyest.Call)[2][0] as tinyest.Expression, ); @@ -938,7 +938,7 @@ describe('wgslGenerator', () => { it('generates correct code for pointer value assignment', () => { const increment = tgpu.fn([d.ptrFn(d.f32)])((val) => { - val += 1; + val.$ += 1; }); expect(asWgsl(increment)).toMatchInlineSnapshot(` diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index 557b9507f0..4642986351 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -645,9 +645,9 @@ describe('TGSL tgpu.fn function', () => { it('resolves a function with a pointer parameter', () => { const addOnes = tgpu.fn([d.ptrStorage(d.vec3f, 'read-write')])((ptr) => { - ptr.x += 1; - ptr.y += 1; - ptr.z += 1; + ptr.$.x += 1; + ptr.$.y += 1; + ptr.$.z += 1; }); expect(asWgsl(addOnes)).toMatchInlineSnapshot(` @@ -658,10 +658,11 @@ describe('TGSL tgpu.fn function', () => { }" `); - const callAddOnes = tgpu.fn([])(() => { - const someVec = d.vec3f(1, 2, 3); + const callAddOnes = () => { + 'use gpu'; + const someVec = d.ref(d.vec3f(1, 2, 3)); addOnes(someVec); - }); + }; expect(asWgsl(callAddOnes)).toMatchInlineSnapshot(` "fn addOnes(ptr: ptr) { diff --git a/tsconfig.base.json b/tsconfig.base.json index bb661755d4..60c668debb 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -16,6 +16,7 @@ "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "skipLibCheck": true, + "rootDir": "${configDir}/.", "types": ["@webgpu/types", "@vitest/browser/matchers"] }, "exclude": ["${configDir}/dist", "${configDir}/node_modules"] From 109dbe93baf34354268c0367b12bb85b1184d7e9 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Mon, 3 Nov 2025 14:44:25 +0100 Subject: [PATCH 33/59] Implicit function pointers don't cause shell-less functions to generate pointer params --- .../fluid-double-buffering/index.ts | 2 +- .../examples/simulation/slime-mold/index.ts | 2 -- packages/typegpu/src/data/ptr.ts | 2 ++ packages/typegpu/src/data/ref.ts | 22 ++++++++++++++++- packages/typegpu/src/data/wgslTypes.ts | 1 + packages/typegpu/src/errors.ts | 4 ++-- packages/typegpu/src/shared/symbols.ts | 4 ++++ packages/typegpu/src/std/array.ts | 3 ++- packages/typegpu/src/tgsl/conversion.ts | 16 +------------ .../typegpu/src/tgsl/generationHelpers.ts | 8 ++++++- packages/typegpu/src/tgsl/shellless.ts | 16 ++++++++++++- packages/typegpu/src/tgsl/wgslGenerator.ts | 24 ++++++++++++------- .../individual/cubemap-reflection.test.ts | 16 ++++--------- .../examples/individual/ray-marching.test.ts | 6 ++--- 14 files changed, 80 insertions(+), 46 deletions(-) diff --git a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts index 93ceb680ef..fb47dade46 100644 --- a/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/fluid-double-buffering/index.ts @@ -179,7 +179,7 @@ const computeVelocity = (x: number, y: number): d.v2f => { const leastCostDir = dirChoices[d.u32(randf.sample() * d.f32(dirChoiceCount))]; - return leastCostDir; + return d.vec2f(leastCostDir); }; const moveObstacles = () => { diff --git a/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts b/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts index ab68c910ee..70af959fe5 100644 --- a/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/slime-mold/index.ts @@ -289,8 +289,6 @@ function frame(now: number) { .with(renderBindGroups[1 - currentTexture]) .draw(3); - root['~unstable'].flush(); - currentTexture = 1 - currentTexture; requestAnimationFrame(frame); diff --git a/packages/typegpu/src/data/ptr.ts b/packages/typegpu/src/data/ptr.ts index 4fc9c4a641..99917c1a82 100644 --- a/packages/typegpu/src/data/ptr.ts +++ b/packages/typegpu/src/data/ptr.ts @@ -46,6 +46,7 @@ export function INTERNAL_createPtr< addressSpace: TAddressSpace, inner: TInner, access: TAccess, + implicit: boolean = false, ): Ptr { return { [$internal]: true, @@ -53,6 +54,7 @@ export function INTERNAL_createPtr< addressSpace, inner, access, + implicit, toString: () => `ptr<${addressSpace}, ${inner}, ${access}>`, } as Ptr; } diff --git a/packages/typegpu/src/data/ref.ts b/packages/typegpu/src/data/ref.ts index 98f7fc49b2..223ca4623d 100644 --- a/packages/typegpu/src/data/ref.ts +++ b/packages/typegpu/src/data/ref.ts @@ -1,4 +1,5 @@ import { stitch } from '../core/resolve/stitch.ts'; +import { invariant } from '../errors.ts'; import { inCodegenMode } from '../execMode.ts'; import { setName } from '../shared/meta.ts'; import { $internal, $isRef, $ownSnippet, $resolve } from '../shared/symbols.ts'; @@ -13,7 +14,12 @@ import { snip, type Snippet, } from './snippet.ts'; -import { type Ptr, type StorableData } from './wgslTypes.ts'; +import { + isNaturallyEphemeral, + isPtr, + type Ptr, + type StorableData, +} from './wgslTypes.ts'; // ---------- // Public API @@ -121,3 +127,17 @@ export class RefOperator implements SelfResolvable { return snip(stitch`(&${this.snippet})`, this.#ptrType, this.snippet.origin); } } + +export function derefSnippet(snippet: Snippet): Snippet { + invariant(isPtr(snippet.dataType), 'Only pointers can be dereferenced'); + + const innerType = snippet.dataType.inner; + // Dereferencing a pointer does not return a copy of the value, it's still a reference. + const origin = isNaturallyEphemeral(innerType) ? 'runtime' : snippet.origin; + + if (snippet.value instanceof RefOperator) { + return snip(stitch`${snippet.value.snippet}`, innerType, origin); + } + + return snip(stitch`(*${snippet})`, innerType, origin); +} diff --git a/packages/typegpu/src/data/wgslTypes.ts b/packages/typegpu/src/data/wgslTypes.ts index 9e97a723e8..1fd7ab1bc4 100644 --- a/packages/typegpu/src/data/wgslTypes.ts +++ b/packages/typegpu/src/data/wgslTypes.ts @@ -1376,6 +1376,7 @@ export interface Ptr< readonly inner: TInner; readonly addressSpace: TAddr; readonly access: TAccess; + readonly implicit: boolean; // Type-tokens, not available at runtime readonly [$repr]: ref>; diff --git a/packages/typegpu/src/errors.ts b/packages/typegpu/src/errors.ts index a4a9d6f9dd..1d4ff08abf 100644 --- a/packages/typegpu/src/errors.ts +++ b/packages/typegpu/src/errors.ts @@ -4,7 +4,7 @@ import type { TgpuVertexLayout } from './core/vertexLayout/vertexLayout.ts'; import type { AnyData, Disarray } from './data/dataTypes.ts'; import type { WgslArray } from './data/wgslTypes.ts'; import { getName, hasTinyestMetadata } from './shared/meta.ts'; -import { DEV } from './shared/env.ts'; +import { DEV, TEST } from './shared/env.ts'; import type { TgpuBindGroupLayout } from './tgpuBindGroupLayout.ts'; const prefix = 'Invariant failed'; @@ -22,7 +22,7 @@ export function invariant( } // In production we strip the message but still throw - if (!DEV) { + if (!DEV && !TEST) { throw new Error(prefix); } diff --git a/packages/typegpu/src/shared/symbols.ts b/packages/typegpu/src/shared/symbols.ts index a7ea396819..d8051d4d4c 100644 --- a/packages/typegpu/src/shared/symbols.ts +++ b/packages/typegpu/src/shared/symbols.ts @@ -75,6 +75,10 @@ export const $invalidSchemaReason = Symbol( */ export const $isRef = Symbol(`typegpu:${version}:$isRef`); +export function isRef(value: unknown): value is { [$isRef]: true } { + return !!(value as { [$isRef]: true })?.[$isRef]; +} + export function isMarkedInternal( value: unknown, ): value is { [$internal]: Record | true } { diff --git a/packages/typegpu/src/std/array.ts b/packages/typegpu/src/std/array.ts index 2ab46689f4..ec87853471 100644 --- a/packages/typegpu/src/std/array.ts +++ b/packages/typegpu/src/std/array.ts @@ -18,7 +18,8 @@ export const arrayLength = dualImpl({ returnType: sizeOfPointedToArray(ptrArg) > 0 ? abstractInt : u32, }); }, - normalImpl: (a: unknown[]) => a.length, + normalImpl: (a: unknown[] | ref) => + isRef(a) ? a.$.length : a.length, codegenImpl(a) { const length = sizeOfPointedToArray(a.dataType); return length > 0 ? String(length) : stitch`arrayLength(${a})`; diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 084be6ede4..b6fea55105 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -1,7 +1,7 @@ import { stitch } from '../core/resolve/stitch.ts'; import type { AnyData, UnknownData } from '../data/dataTypes.ts'; import { undecorate } from '../data/dataTypes.ts'; -import { RefOperator } from '../data/ref.ts'; +import { derefSnippet, RefOperator } from '../data/ref.ts'; import { snip, type Snippet } from '../data/snippet.ts'; import { type AnyWgslData, @@ -226,20 +226,6 @@ export function getBestConversion( return undefined; } -export function derefSnippet(snippet: Snippet): Snippet { - invariant(isPtr(snippet.dataType), 'Only pointers can be dereferenced'); - - const innerType = snippet.dataType.inner; - // Dereferencing a pointer does not return a copy of the value, it's still a reference. - const origin = isNaturallyEphemeral(innerType) ? 'runtime' : snippet.origin; - - if (snippet.value instanceof RefOperator) { - return snip(stitch`${snippet.value.snippet}`, innerType, origin); - } - - return snip(stitch`(*${snippet})`, innerType, origin); -} - function applyActionToSnippet( snippet: Snippet, action: ConversionResultAction, diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index d75a85f7a9..7b0b2fae67 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -62,7 +62,7 @@ import type { ShelllessRepository } from './shellless.ts'; import { add, div, mul, sub } from '../std/operators.ts'; import { $internal } from '../shared/symbols.ts'; import { stitch } from '../core/resolve/stitch.ts'; -import { derefSnippet } from './conversion.ts'; +import { derefSnippet } from '../data/ref.ts'; type SwizzleableType = 'f' | 'h' | 'i' | 'u' | 'b'; type SwizzleLength = 1 | 2 | 3 | 4; @@ -312,6 +312,12 @@ export function accessIndex( ); } + if (isPtr(target.dataType)) { + // Sometimes values that are typed as pointers aren't instances of `d.ref`, so we + // allow indexing as if it wasn't a pointer. + return accessIndex(derefSnippet(target), index); + } + // matrix.columns if (target.value instanceof MatrixColumnsAccess) { const propType = indexableTypeToResult[ diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index 8bda0e635d..cffa071cdd 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -4,6 +4,7 @@ import { } from '../core/function/shelllessImpl.ts'; import type { AnyData } from '../data/dataTypes.ts'; import type { Snippet } from '../data/snippet.ts'; +import { isPtr } from '../data/wgslTypes.ts'; import { getResolutionCtx } from '../execMode.ts'; import { getMetaData, getName } from '../shared/meta.ts'; import { concretize } from './generationHelpers.ts'; @@ -15,6 +16,7 @@ function shallowEqualSchemas(a: AnyData, b: AnyData): boolean { if (a.type === 'ptr' && b.type === 'ptr') { return a.access === b.access && a.addressSpace === b.addressSpace && + a.implicit === b.implicit && shallowEqualSchemas(a.inner, b.inner); } if (a.type === 'array' && b.type === 'array') { @@ -46,7 +48,7 @@ export class ShelllessRepository { } const argTypes = (argSnippets ?? []).map((s) => { - const type = concretize(s.dataType as AnyData); + let type = concretize(s.dataType as AnyData); if (s.origin === 'constant-ref') { // biome-ignore lint/style/noNonNullAssertion: it's there @@ -58,6 +60,18 @@ export class ShelllessRepository { ); } + if (isPtr(type) && type.implicit) { + // If the pointer was made implicitly (e.g. by assigning a reference to a const variable),// then we dereference the pointer before passing it to the function. The main reason for this, + // is that in TypeScript, the type of the function accepts a value, not the value wrapped in + // d.ref<> (so it's not considered mutable from the perspective of the function) + + // Example: + // const foo = layout.$.boids; + // bar(foo) + // ^^^ + type = type.inner; + } + return type; }); diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 384850f1c6..b3c4523456 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -41,7 +41,7 @@ import { } from './generationHelpers.ts'; import type { ShaderGenerator } from './shaderGenerator.ts'; import type { DualFn } from '../data/dualFn.ts'; -import { ptrFn } from '../data/ptr.ts'; +import { INTERNAL_createPtr, ptrFn } from '../data/ptr.ts'; import { RefOnGPU, RefOperator } from '../data/ref.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -345,7 +345,7 @@ ${this.ctx.pre}}`; if (expression[0] === NODE.indexAccess) { // Index Access const [_, targetNode, propertyNode] = expression; - let target = this.expression(targetNode); + const target = this.expression(targetNode); const inProperty = this.expression(propertyNode); const property = convertToCommonType( [inProperty], @@ -353,11 +353,6 @@ ${this.ctx.pre}}`; /* verbose */ false, )?.[0] ?? inProperty; - if (wgsl.isPtr(target.dataType)) { - // De-referencing the pointer - target = tryConvertSnippet(target, target.dataType.inner, false); - } - const accessed = accessIndex(target, property); if (!accessed) { const targetStr = this.ctx.resolve(target.value, target.dataType).value; @@ -764,6 +759,8 @@ ${this.ctx.pre}else ${alternate}`; ); } + let origin = eq.origin; + if (eq.value instanceof RefOnGPU) { // We're assigning a newly created `d.ref()` const refSnippet = eq.value.snippet; @@ -808,6 +805,17 @@ ${this.ctx.pre}else ${alternate}`; if (!wgsl.isPtr(dataType)) { dataType = ptrFn(concretize(dataType) as wgsl.StorableData); } + + if (!(eq.value instanceof RefOperator)) { + // If what we're assigning is something preceded by `&`, then it's a value + // created using `d.ref()`. Otherwise, it's an implicit pointer + dataType = INTERNAL_createPtr( + dataType.addressSpace, + dataType.inner, + dataType.access, + /* implicit */ true, + ); + } } } else { // Non-referential @@ -824,7 +832,7 @@ ${this.ctx.pre}else ${alternate}`; varType, rawId, concretize(dataType), - eq.origin, + origin, ); return stitchWithExactTypes`${this.ctx.pre}${varType} ${snippet .value as string} = ${tryConvertSnippet(eq, dataType, false)};`; diff --git a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts index 6532da6c10..b8e00997ca 100644 --- a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts +++ b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts @@ -49,23 +49,17 @@ describe('cubemap reflection example', () => { @group(0) @binding(1) var nextVertices_7: array; - fn packVec2u_8(toPack: ptr) -> vec2u { - var xy = pack2x16float((*toPack).xy); - var zw = pack2x16float((*toPack).zw); - return vec2u(xy, zw); - } - - fn packVec2u_9(toPack: vec4f) -> vec2u { + fn packVec2u_8(toPack: vec4f) -> vec2u { var xy = pack2x16float(toPack.xy); var zw = pack2x16float(toPack.zw); return vec2u(xy, zw); } - struct computeFn_Input_10 { + struct computeFn_Input_9 { @builtin(global_invocation_id) gid: vec3u, } - @compute @workgroup_size(256, 1, 1) fn computeFn_0(input: computeFn_Input_10) { + @compute @workgroup_size(256, 1, 1) fn computeFn_0(input: computeFn_Input_9) { var triangleCount = u32((f32(arrayLength(&prevVertices_1)) / 3f)); var triangleIndex = (input.gid.x + (input.gid.y * 65535)); if ((triangleIndex >= triangleCount)) { @@ -89,8 +83,8 @@ describe('cubemap reflection example', () => { } var outIndex = (baseIndexNext + i); let nextVertex = (&nextVertices_7[outIndex]); - (*nextVertex).position = packVec2u_8(reprojectedVertex); - (*nextVertex).normal = packVec2u_9(normal); + (*nextVertex).position = packVec2u_8((*reprojectedVertex)); + (*nextVertex).normal = packVec2u_8(normal); nextVertices_7[outIndex] = (*nextVertex); } } diff --git a/packages/typegpu/tests/examples/individual/ray-marching.test.ts b/packages/typegpu/tests/examples/individual/ray-marching.test.ts index 09178ed1fd..81f29d0587 100644 --- a/packages/typegpu/tests/examples/individual/ray-marching.test.ts +++ b/packages/typegpu/tests/examples/individual/ray-marching.test.ts @@ -128,14 +128,14 @@ describe('ray-marching example', () => { return vec3f((cos((t * speed)) * radius), (height + (sin((t * speed)) * radius)), 4); } - fn softShadow_18(ro: ptr, rd: ptr, minT: f32, maxT: f32, k: f32) -> f32 { + fn softShadow_18(ro: vec3f, rd: vec3f, minT: f32, maxT: f32, k: f32) -> f32 { var res = 1f; var t = minT; for (var i = 0; (i < 100); i++) { if ((t >= maxT)) { break; } - var h = getSceneDist_7(((*ro) + ((*rd) * t))).dist; + var h = getSceneDist_7((ro + (rd * t))).dist; if ((h < 1e-3)) { return 0; } @@ -164,7 +164,7 @@ describe('ray-marching example', () => { let shadowRo = (&p); let shadowRd = (&l); var shadowDist = length((lightPos - p)); - var shadow = softShadow_18(shadowRo, shadowRd, 0.1, shadowDist, 16); + var shadow = softShadow_18((*shadowRo), (*shadowRd), 0.1, shadowDist, 16); var litColor = (march.color * diff); var finalColor = mix((litColor * 0.5), litColor, shadow); return mix(vec4f(finalColor, 1), vec4f(0.699999988079071, 0.800000011920929, 0.8999999761581421, 1), fog); From 0fdadf5cba33c97e16793ad40486b7e16ed52be7 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Mon, 3 Nov 2025 15:13:01 +0100 Subject: [PATCH 34/59] Using std.neg when resolving unary `-` operator, and emitting `let` when assigning primitives to const variables --- packages/typegpu/src/tgsl/wgslGenerator.ts | 29 +++- packages/typegpu/tests/accessor.test.ts | 4 +- packages/typegpu/tests/bufferUsage.test.ts | 12 +- packages/typegpu/tests/derived.test.ts | 4 +- .../tests/examples/individual/3d-fish.test.ts | 74 ++++----- .../examples/individual/ascii-filter.test.ts | 12 +- .../tests/examples/individual/blur.test.ts | 6 +- .../examples/individual/boids-next.test.ts | 18 +- .../individual/box-raytracing.test.ts | 6 +- .../examples/individual/caustics.test.ts | 62 +++---- .../individual/cubemap-reflection.test.ts | 20 +-- .../tests/examples/individual/disco.test.ts | 6 +- .../individual/fluid-double-buffering.test.ts | 88 +++++----- .../individual/fluid-with-atomics.test.ts | 70 ++++---- .../tests/examples/individual/gravity.test.ts | 18 +- .../examples/individual/liquid-glass.test.ts | 12 +- .../examples/individual/log-test.test.ts | 12 +- .../examples/individual/matrix-next.test.ts | 28 ++-- .../individual/mnist-inference.test.ts | 24 +-- .../tests/examples/individual/oklab.test.ts | 156 +++++++++--------- .../examples/individual/perlin-noise.test.ts | 56 +++---- .../examples/individual/probability.test.ts | 136 +++++++-------- .../examples/individual/ray-marching.test.ts | 32 ++-- .../examples/individual/simple-shadow.test.ts | 10 +- .../examples/individual/slime-mold-3d.test.ts | 68 ++++---- .../examples/individual/slime-mold.test.ts | 28 ++-- .../tests/examples/individual/square.test.ts | 2 +- .../examples/individual/stable-fluid.test.ts | 34 ++-- .../individual/tgsl-parsing-test.test.ts | 14 +- .../examples/individual/vaporrave.test.ts | 74 ++++----- .../individual/wgsl-resolution.test.ts | 20 +-- .../individual/xor-dev-centrifuge-2.test.ts | 4 +- .../individual/xor-dev-runner.test.ts | 6 +- packages/typegpu/tests/indent.test.ts | 2 +- packages/typegpu/tests/slot.test.ts | 4 +- .../typegpu/tests/tgsl/createDualImpl.test.ts | 4 +- packages/typegpu/tests/tgsl/shellless.test.ts | 2 +- .../typegpu/tests/tgsl/wgslGenerator.test.ts | 8 +- packages/typegpu/tests/tgslFn.test.ts | 8 +- packages/typegpu/tests/variable.test.ts | 6 +- 40 files changed, 595 insertions(+), 584 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index b3c4523456..e1e8142148 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -25,7 +25,7 @@ import { isMarkedInternal } from '../shared/symbols.ts'; import { safeStringify } from '../shared/stringify.ts'; import { $internal } from '../shared/symbols.ts'; import { pow } from '../std/numeric.ts'; -import { add, div, mul, sub } from '../std/operators.ts'; +import { add, div, mul, neg, sub } from '../std/operators.ts'; import type { FnArgsConversionHint } from '../types.ts'; import { convertStructValues, @@ -98,7 +98,13 @@ function operatorToType< return lhs; } -const opCodeToCodegen = { +const unaryOpCodeToCodegen = { + '-': neg[$internal].gpuImpl, +} satisfies Partial< + Record unknown> +>; + +const binaryOpCodeToCodegen = { '+': add[$internal].gpuImpl, '-': sub[$internal].gpuImpl, '*': mul[$internal].gpuImpl, @@ -250,7 +256,8 @@ ${this.ctx.pre}}`; ); } - const codegen = opCodeToCodegen[op as keyof typeof opCodeToCodegen]; + const codegen = + binaryOpCodeToCodegen[op as keyof typeof binaryOpCodeToCodegen]; if (codegen) { return codegen(lhsExpr, rhsExpr); } @@ -315,6 +322,13 @@ ${this.ctx.pre}}`; // Unary Expression const [_, op, arg] = expression; const argExpr = this.expression(arg); + + const codegen = + unaryOpCodeToCodegen[op as keyof typeof unaryOpCodeToCodegen]; + if (codegen) { + return codegen(argExpr); + } + const argStr = this.ctx.resolve(argExpr.value).value; const type = operatorToType(argExpr.dataType, op); @@ -759,8 +773,6 @@ ${this.ctx.pre}else ${alternate}`; ); } - let origin = eq.origin; - if (eq.value instanceof RefOnGPU) { // We're assigning a newly created `d.ref()` const refSnippet = eq.value.snippet; @@ -821,10 +833,9 @@ ${this.ctx.pre}else ${alternate}`; // Non-referential if ( stmtType === NODE.const && - wgsl.isNaturallyEphemeral(dataType) && - eq.origin === 'constant' + wgsl.isNaturallyEphemeral(dataType) ) { - varType = 'const'; + varType = eq.origin === 'constant' ? 'const' : 'let'; } } @@ -832,7 +843,7 @@ ${this.ctx.pre}else ${alternate}`; varType, rawId, concretize(dataType), - origin, + eq.origin, ); return stitchWithExactTypes`${this.ctx.pre}${varType} ${snippet .value as string} = ${tryConvertSnippet(eq, dataType, false)};`; diff --git a/packages/typegpu/tests/accessor.test.ts b/packages/typegpu/tests/accessor.test.ts index dd96a48af7..d28989925c 100644 --- a/packages/typegpu/tests/accessor.test.ts +++ b/packages/typegpu/tests/accessor.test.ts @@ -139,8 +139,8 @@ describe('tgpu.accessor', () => { let color2 = (&redUniform); var color3 = getColor(); const colorX = 1f; - var color2X = redUniform.x; - var color3X = getColor().x; + let color2X = redUniform.x; + let color3X = getColor().x; }" `); }); diff --git a/packages/typegpu/tests/bufferUsage.test.ts b/packages/typegpu/tests/bufferUsage.test.ts index af93244c14..567415981d 100644 --- a/packages/typegpu/tests/bufferUsage.test.ts +++ b/packages/typegpu/tests/bufferUsage.test.ts @@ -41,7 +41,7 @@ describe('TgpuBufferUniform', () => { "@group(0) @binding(0) var param: f32; fn func() { - var x = param; + let x = param; }" `); }); @@ -70,7 +70,7 @@ describe('TgpuBufferUniform', () => { fn func() { let pos = (&boid.pos); - var velX = boid.vel.x; + let velX = boid.vel.x; }" `); }); @@ -110,7 +110,7 @@ describe('TgpuBufferMutable', () => { "@group(0) @binding(0) var param: f32; fn func() { - var x = param; + let x = param; }" `); }); @@ -141,7 +141,7 @@ describe('TgpuBufferMutable', () => { fn func() { let pos = (&boid.pos); - var velX = boid.vel.x; + let velX = boid.vel.x; }" `); }); @@ -200,7 +200,7 @@ describe('TgpuBufferReadonly', () => { "@group(0) @binding(0) var paramBuffer: f32; fn func() { - var x = paramBuffer; + let x = paramBuffer; }" `); }); @@ -230,7 +230,7 @@ describe('TgpuBufferReadonly', () => { fn func() { let pos = (&boid.pos); - var velX = boid.vel.x; + let velX = boid.vel.x; }" `); }); diff --git a/packages/typegpu/tests/derived.test.ts b/packages/typegpu/tests/derived.test.ts index 1381ec4e64..de9d9d04c3 100644 --- a/packages/typegpu/tests/derived.test.ts +++ b/packages/typegpu/tests/derived.test.ts @@ -152,9 +152,9 @@ describe('TgpuDerived', () => { var pos = vec3f(2, 4, 6); const posX = 2f; let vel = (&boid.vel); - var velX = boid.vel.x; + let velX = boid.vel.x; let vel_ = (&boid.vel); - var velX_ = boid.vel.x; + let velX_ = boid.vel.x; }" `); }); diff --git a/packages/typegpu/tests/examples/individual/3d-fish.test.ts b/packages/typegpu/tests/examples/individual/3d-fish.test.ts index 4d7a6748b8..6115b675ef 100644 --- a/packages/typegpu/tests/examples/individual/3d-fish.test.ts +++ b/packages/typegpu/tests/examples/individual/3d-fish.test.ts @@ -40,8 +40,8 @@ describe('3d fish example', () => { } fn item_8() -> f32 { - var a = dot(seed_6, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_6, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_6, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_6, vec2f(54.47856521606445, 345.8415222167969)); seed_6.x = fract((cos(a) * 136.8168)); seed_6.y = fract((cos(b) * 534.7645)); return seed_6.y; @@ -122,7 +122,7 @@ describe('3d fish example', () => { fn projectPointOnLine_10(point: vec3f, line: Line3_9) -> vec3f { var pointVector = (point - line.origin); - var projection = dot(pointVector, line.dir); + let projection = dot(pointVector, line.dir); return (line.origin + (line.dir * projection)); } @@ -144,7 +144,7 @@ describe('3d fish example', () => { continue; } let other = (¤tFishData_3[i]); - var dist = length(((*fishData).position - (*other).position)); + let dist = length(((*fishData).position - (*other).position)); if ((dist < fishBehavior_5.separationDist)) { separation = (separation + ((*fishData).position - (*other).position)); } @@ -166,15 +166,15 @@ describe('3d fish example', () => { for (var i = 0; (i < 3); i += 1) { var repulsion = vec3f(); repulsion[i] = 1; - var axisAquariumSize = (vec3f(10, 4, 10)[i] / 2f); - var axisPosition = (*fishData).position[i]; + let axisAquariumSize = (vec3f(10, 4, 10)[i] / 2f); + let axisPosition = (*fishData).position[i]; const distance = 0.1; if ((axisPosition > (axisAquariumSize - distance))) { - var str = (axisPosition - (axisAquariumSize - distance)); + let str = (axisPosition - (axisAquariumSize - distance)); wallRepulsion = (wallRepulsion - (repulsion * str)); } - if ((axisPosition < (-axisAquariumSize + distance))) { - var str = ((-axisAquariumSize + distance) - axisPosition); + if ((axisPosition < (-(axisAquariumSize) + distance))) { + let str = ((-(axisAquariumSize) + distance) - axisPosition); wallRepulsion = (wallRepulsion + (repulsion * str)); } } @@ -182,7 +182,7 @@ describe('3d fish example', () => { var proj = projectPointOnLine_10((*fishData).position, mouseRay_7.line); var diff = ((*fishData).position - proj); const limit = 0.9; - var str = (pow(2, clamp((limit - length(diff)), 0, limit)) - 1); + let str = (pow(2, clamp((limit - length(diff)), 0, limit)) - 1); rayRepulsion = (normalize(diff) * str); } var direction = (*fishData).direction; @@ -227,14 +227,14 @@ describe('3d fish example', () => { } fn applySinWave_4(index: u32, vertex: PosAndNormal_3, time: f32) -> PosAndNormal_3 { - var a = -60.1; + const a = -60.1; const b = 0.8; const c = 6.1; var posMod = vec3f(); posMod.z = (sin((f32(index) + (((time / a) + vertex.position.x) / b))) / c); - var coeff = (cos((f32(index) + (((time / a) + vertex.position.x) / b))) / c); + let coeff = (cos((f32(index) + (((time / a) + vertex.position.x) / b))) / c); var newOX = normalize(vec3f(1, 0, coeff)); - var newOZ = vec3f(-newOX.z, 0, newOX.x); + var newOZ = vec3f(-(newOX.z), 0, newOX.x); var newNormalXZ = ((newOX * vertex.normal.x) + (newOZ * vertex.normal.z)); var wavedNormal = vec3f(newNormalXZ.x, vertex.normal.y, newNormalXZ.z); var wavedPosition = (vertex.position + posMod); @@ -276,8 +276,8 @@ describe('3d fish example', () => { wavedVertex = applySinWave_4(input.instanceIndex, PosAndNormal_3(input.modelPosition, input.modelNormal), currentTime_5); } var direction = normalize(currentModelData.direction); - var yaw = (-atan2(direction.z, direction.x) + 3.141592653589793); - var pitch = asin(-direction.y); + let yaw = (-(atan2(direction.z, direction.x)) + 3.141592653589793); + let pitch = asin(-(direction.y)); var scaleMatrix = mat4x4f(vec3f(currentModelData.scale).x, 0, 0, 0, 0, vec3f(currentModelData.scale).y, 0, 0, 0, 0, vec3f(currentModelData.scale).z, 0, 0, 0, 0, 1); var pitchMatrix = mat4x4f(cos(pitch), sin(pitch), 0, 0, -sin(pitch), cos(pitch), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); var yawMatrix = mat4x4f(cos(yaw), 0, -sin(yaw), 0, 0, 1, 0, 0, sin(yaw), 0, cos(yaw), 0, 0, 0, 0, 1); @@ -294,12 +294,12 @@ describe('3d fish example', () => { @group(0) @binding(3) var sampler_12: sampler; fn rgbToHsv_13(rgb: vec3f) -> vec3f { - var r = rgb.x; - var g = rgb.y; - var b = rgb.z; - var maxC = max(r, max(g, b)); - var minC = min(r, min(g, b)); - var delta = (maxC - minC); + let r = rgb.x; + let g = rgb.y; + let b = rgb.z; + let maxC = max(r, max(g, b)); + let minC = min(r, min(g, b)); + let delta = (maxC - minC); var h = 0f; var s = 0f; if ((maxC == 0)) { @@ -308,7 +308,7 @@ describe('3d fish example', () => { else { s = (delta / maxC); } - var v = maxC; + let v = maxC; if ((maxC == minC)) { h = 0; } @@ -341,14 +341,14 @@ describe('3d fish example', () => { } fn hsvToRgb_14(hsv: vec3f) -> vec3f { - var h = hsv.x; - var s = hsv.y; - var v = hsv.z; - var i = floor((h * 6)); - var f = ((h * 6) - i); - var p = (v * (1 - s)); - var q = (v * (1 - (f * s))); - var t = (v * (1 - ((1 - f) * s))); + let h = hsv.x; + let s = hsv.y; + let v = hsv.z; + let i = floor((h * 6)); + let f = ((h * 6) - i); + let p = (v * (1 - s)); + let q = (v * (1 - (f * s))); + let t = (v * (1 - ((1 - f) * s))); var r = 0f; var g = 0f; var b = 0f; @@ -407,17 +407,17 @@ describe('3d fish example', () => { var textureColorWithAlpha = textureSample(modelTexture_11, sampler_12, input.textureUV); var textureColor = textureColorWithAlpha.xyz; var ambient = (0.5 * (textureColor * vec3f(0.800000011920929, 0.800000011920929, 1))); - var cosTheta = dot(input.worldNormal, vec3f(-0.2357022613286972, 0.9428090453147888, -0.2357022613286972)); + let cosTheta = dot(input.worldNormal, vec3f(-0.2357022613286972, 0.9428090453147888, -0.2357022613286972)); var diffuse = (max(0, cosTheta) * (textureColor * vec3f(0.800000011920929, 0.800000011920929, 1))); var viewSource = normalize((camera_6.position.xyz - input.worldPosition)); - var reflectSource = normalize(reflect((-1 * vec3f(-0.2357022613286972, 0.9428090453147888, -0.2357022613286972)), input.worldNormal)); - var specularStrength = pow(max(0, dot(viewSource, reflectSource)), 16); + var reflectSource = normalize(reflect(vec3f(0.2357022613286972, -0.9428090453147888, 0.2357022613286972), input.worldNormal)); + let specularStrength = pow(max(0, dot(viewSource, reflectSource)), 16); var specular = (specularStrength * vec3f(0.800000011920929, 0.800000011920929, 1)); var lightedColor = (ambient + (diffuse + specular)); - var distanceFromCamera = length((camera_6.position.xyz - input.worldPosition)); + let distanceFromCamera = length((camera_6.position.xyz - input.worldPosition)); var desaturatedColor = lightedColor; if ((input.applySeaDesaturation == 1)) { - var desaturationFactor = (-atan2(((distanceFromCamera - 5) / 10f), 1) / 3f); + let desaturationFactor = (-(atan2(((distanceFromCamera - 5) / 10f), 1)) / 3f); var hsv = rgbToHsv_13(desaturatedColor); hsv.y += (desaturationFactor / 2f); hsv.z += desaturationFactor; @@ -426,8 +426,8 @@ describe('3d fish example', () => { } var foggedColor = desaturatedColor; if ((input.applySeaFog == 1)) { - var fogParameter = max(0, ((distanceFromCamera - 1.5) * 0.2)); - var fogFactor = (fogParameter / (1 + fogParameter)); + let fogParameter = max(0, ((distanceFromCamera - 1.5) * 0.2)); + let fogFactor = (fogParameter / (1 + fogParameter)); foggedColor = mix(foggedColor, vec3f(0, 0.47843137383461, 0.800000011920929), fogFactor); } return vec4f(foggedColor.xyz, 1); diff --git a/packages/typegpu/tests/examples/individual/ascii-filter.test.ts b/packages/typegpu/tests/examples/individual/ascii-filter.test.ts index e634823292..7fd83cde15 100644 --- a/packages/typegpu/tests/examples/individual/ascii-filter.test.ts +++ b/packages/typegpu/tests/examples/individual/ascii-filter.test.ts @@ -50,7 +50,7 @@ describe('ascii filter example', () => { if (((((pos.x < 0) || (pos.x > 4)) || (pos.y < 0)) || (pos.y > 4))) { return 0; } - var a = u32((pos.x + (5 * pos.y))); + let a = u32((pos.x + (5 * pos.y))); return f32(((n >> a) & 1)); } @@ -64,12 +64,12 @@ describe('ascii filter example', () => { var uv2 = ((uvTransformBuffer_4 * (input.uv - 0.5)) + 0.5); var textureSize = vec2f(textureDimensions(externalTexture_5)); var pix = (uv2 * textureSize); - var cellSize = f32(glyphSize_6); - var halfCell = (cellSize * 0.5); + let cellSize = f32(glyphSize_6); + let halfCell = (cellSize * 0.5); var blockCoord = ((floor((pix / cellSize)) * cellSize) / textureSize); var color = textureSampleBaseClampToEdge(externalTexture_5, shaderSampler_7, blockCoord); - var rawGray = (((0.3 * color.x) + (0.59 * color.y)) + (0.11 * color.z)); - var gray = pow(rawGray, gammaCorrection_8); + let rawGray = (((0.3 * color.x) + (0.59 * color.y)) + (0.11 * color.z)); + let gray = pow(rawGray, gammaCorrection_8); var n = 4096u; if ((charsetExtended_9 == 0)) { if ((gray > 0.2)) { @@ -223,7 +223,7 @@ describe('ascii filter example', () => { } } var p = vec2f((((pix.x / halfCell) % 2) - 1), (((pix.y / halfCell) % 2) - 1)); - var charValue = characterFn_10(n, p); + let charValue = characterFn_10(n, p); var resultColor = vec3f(1); if ((displayMode_11 == 0)) { resultColor = (color * charValue).xyz; diff --git a/packages/typegpu/tests/examples/individual/blur.test.ts b/packages/typegpu/tests/examples/individual/blur.test.ts index fc90d0bc96..8448159c7f 100644 --- a/packages/typegpu/tests/examples/individual/blur.test.ts +++ b/packages/typegpu/tests/examples/individual/blur.test.ts @@ -43,7 +43,7 @@ describe('blur example', () => { @compute @workgroup_size(32, 1, 1) fn computeFn_0(_arg_0: computeFn_Input_8) { let settings2 = (&settingsUniform_1); - var filterOffset = i32((f32(((*settings2).filterDim - 1)) / 2f)); + let filterOffset = i32((f32(((*settings2).filterDim - 1)) / 2f)); var dims = vec2i(textureDimensions(inTexture_3)); var baseIndex = (vec2i(((_arg_0.wid.xy * vec2u((*settings2).blockDim, 4)) + (_arg_0.lid.xy * vec2u(4, 1)))) - vec2i(filterOffset, 0)); for (var r = 0; (r < 4); r++) { @@ -62,11 +62,11 @@ describe('blur example', () => { if ((flip_4 != 0)) { writeIndex = writeIndex.yx; } - var center = (i32((4 * _arg_0.lid.x)) + c); + let center = (i32((4 * _arg_0.lid.x)) + c); if ((((center >= filterOffset) && (center < (128 - filterOffset))) && all((writeIndex < dims)))) { var acc = vec3f(); for (var f = 0; (f < (*settings2).filterDim); f++) { - var i = ((center + f) - filterOffset); + let i = ((center + f) - filterOffset); acc = (acc + (tileData_5[r][i] * (1f / f32((*settings2).filterDim)))); } textureStore(outTexture_7, writeIndex, vec4f(acc, 1)); diff --git a/packages/typegpu/tests/examples/individual/boids-next.test.ts b/packages/typegpu/tests/examples/individual/boids-next.test.ts index 7e04c106f0..030fc36d1a 100644 --- a/packages/typegpu/tests/examples/individual/boids-next.test.ts +++ b/packages/typegpu/tests/examples/individual/boids-next.test.ts @@ -51,7 +51,7 @@ describe('boids next example', () => { continue; } let other = (¤tTrianglePos_3[i]); - var dist = distance(instanceInfo.position, (*other).position); + let dist = distance(instanceInfo.position, (*other).position); if ((dist < paramsBuffer_5.separationDistance)) { separation = (separation + (instanceInfo.position - (*other).position)); } @@ -77,15 +77,15 @@ describe('boids next example', () => { instanceInfo.velocity = (instanceInfo.velocity + velocity); instanceInfo.velocity = (clamp(length(instanceInfo.velocity), 0, 0.01) * normalize(instanceInfo.velocity)); if ((instanceInfo.position.x > 1.03)) { - instanceInfo.position.x = (-1 - 0.03); + instanceInfo.position.x = -1.03; } if ((instanceInfo.position.y > 1.03)) { - instanceInfo.position.y = (-1 - 0.03); + instanceInfo.position.y = -1.03; } - if ((instanceInfo.position.x < (-1 - 0.03))) { + if ((instanceInfo.position.x < -1.03)) { instanceInfo.position.x = 1.03; } - if ((instanceInfo.position.y < (-1 - 0.03))) { + if ((instanceInfo.position.y < -1.03)) { instanceInfo.position.y = 1.03; } instanceInfo.position = (instanceInfo.position + instanceInfo.velocity); @@ -104,12 +104,12 @@ describe('boids next example', () => { } fn getRotationFromVelocity_1(velocity: vec2f) -> f32 { - return -atan2(velocity.x, velocity.y); + return -(atan2(velocity.x, velocity.y)); } fn rotate_2(v: vec2f, angle: f32) -> vec2f { - var cos = cos(angle); - var sin = sin(angle); + let cos = cos(angle); + let sin = sin(angle); return vec2f(((v.x * cos) - (v.y * sin)), ((v.x * sin) + (v.y * cos))); } @@ -127,7 +127,7 @@ describe('boids next example', () => { } @vertex fn mainVert_0(input: mainVert_Input_5) -> mainVert_Output_4 { - var angle = getRotationFromVelocity_1(input.velocity); + let angle = getRotationFromVelocity_1(input.velocity); var rotated = rotate_2(input.v, angle); var pos = vec4f((rotated + input.center), 0, 1); var color = vec4f(((sin((colorPalette_3 + angle)) * 0.45) + 0.45), 1); diff --git a/packages/typegpu/tests/examples/individual/box-raytracing.test.ts b/packages/typegpu/tests/examples/individual/box-raytracing.test.ts index d1b1c17580..5ea38a1e01 100644 --- a/packages/typegpu/tests/examples/individual/box-raytracing.test.ts +++ b/packages/typegpu/tests/examples/individual/box-raytracing.test.ts @@ -36,7 +36,7 @@ describe('box raytracing example', () => { } @vertex fn mainVertex_0(input: mainVertex_Input_4) -> mainVertex_Output_3 { - var pos = array(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3)); + var pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); var rayWorldOrigin = (uniforms_1.invViewMatrix * vec4f(0, 0, 0, 1)).xyz; return mainVertex_Output_3(vec4f(pos[input.vertexIndex], 0, 1), rayWorldOrigin); } @@ -136,7 +136,7 @@ describe('box raytracing example', () => { var boxSize3 = vec3f(uniforms_1.boxSize); var halfBoxSize3 = (0.5 * boxSize3); var halfCanvasDims = (0.5 * uniforms_1.canvasDims); - var minDim = min(uniforms_1.canvasDims.x, uniforms_1.canvasDims.y); + let minDim = min(uniforms_1.canvasDims.x, uniforms_1.canvasDims.y); var viewCoords = ((input.position.xy - halfCanvasDims) / minDim); var ray = Ray_6(input.rayWorldOrigin, (uniforms_1.invViewMatrix * vec4f(normalize(vec3f(viewCoords, 1)), 0)).xyz); var bigBoxIntersection = getBoxIntersection_8(AxisAlignedBounds_7((-1 * halfBoxSize3), (vec3f(7) + halfBoxSize3)), ray); @@ -157,7 +157,7 @@ describe('box raytracing example', () => { var ijkScaled = vec3f(f32(i), f32(j), f32(k)); var intersection = getBoxIntersection_8(AxisAlignedBounds_7((ijkScaled - halfBoxSize3), (ijkScaled + halfBoxSize3)), ray); if (intersection.intersects) { - var boxDensity = (max(0, (intersection.tMax - intersection.tMin)) * pow(uniforms_1.materialDensity, 2)); + let boxDensity = (max(0, (intersection.tMax - intersection.tMin)) * pow(uniforms_1.materialDensity, 2)); density += boxDensity; invColor = (invColor + (boxDensity * (vec3f(1) / boxMatrix_10[i][j][k].albedo))); tMin = intersection.tMin; diff --git a/packages/typegpu/tests/examples/individual/caustics.test.ts b/packages/typegpu/tests/examples/individual/caustics.test.ts index 56efdd284d..e51a03ac11 100644 --- a/packages/typegpu/tests/examples/individual/caustics.test.ts +++ b/packages/typegpu/tests/examples/individual/caustics.test.ts @@ -27,7 +27,7 @@ describe('caustics example', () => { } @vertex fn mainVertex_0(_arg_0: mainVertex_Input_2) -> mainVertex_Output_1 { - var pos = array(vec2f(0, 0.800000011920929), vec2f(-0.8, -0.8), vec2f(0.8, -0.8)); + var pos = array(vec2f(0, 0.800000011920929), vec2f(-0.800000011920929), vec2f(0.800000011920929, -0.800000011920929)); var uv = array(vec2f(0.5, 1), vec2f(), vec2f(1, 0)); return mainVertex_Output_1(vec4f(pos[_arg_0.vertexIndex], 0, 1), uv[_arg_0.vertexIndex]); } @@ -37,7 +37,7 @@ describe('caustics example', () => { fn tilePattern_5(uv: vec2f) -> f32 { var tiledUv = fract(uv); var proximity = abs(((tiledUv * 2) - 1)); - var maxProximity = max(proximity.x, proximity.y); + let maxProximity = max(proximity.x, proximity.y); return saturate((pow((1 - maxProximity), 0.6) * 5)); } @@ -54,19 +54,19 @@ describe('caustics example', () => { } fn item_15() -> f32 { - var a = dot(seed_13, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_13, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_13, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_13, vec2f(54.47856521606445, 345.8415222167969)); seed_13.x = fract((cos(a) * 136.8168)); seed_13.y = fract((cos(b) * 534.7645)); return seed_13.y; } fn randOnUnitSphere_14() -> vec3f { - var z = ((2 * item_15()) - 1); - var oneMinusZSq = sqrt((1 - (z * z))); - var theta = (6.283185307179586 * item_15()); - var x = (cos(theta) * oneMinusZSq); - var y = (sin(theta) * oneMinusZSq); + let z = ((2 * item_15()) - 1); + let oneMinusZSq = sqrt((1 - (z * z))); + let theta = (6.283185307179586 * item_15()); + let x = (cos(theta) * oneMinusZSq); + let y = (sin(theta) * oneMinusZSq); return vec3f(x, y, z); } @@ -87,34 +87,34 @@ describe('caustics example', () => { fn sample_8(pos: vec3f) -> f32 { var minJunction = floor(pos); - var xyz = dotProdGrid_9(pos, minJunction); - var xyZ = dotProdGrid_9(pos, (minJunction + vec3f(0, 0, 1))); - var xYz = dotProdGrid_9(pos, (minJunction + vec3f(0, 1, 0))); - var xYZ = dotProdGrid_9(pos, (minJunction + vec3f(0, 1, 1))); - var Xyz = dotProdGrid_9(pos, (minJunction + vec3f(1, 0, 0))); - var XyZ = dotProdGrid_9(pos, (minJunction + vec3f(1, 0, 1))); - var XYz = dotProdGrid_9(pos, (minJunction + vec3f(1, 1, 0))); - var XYZ = dotProdGrid_9(pos, (minJunction + vec3f(1))); + let xyz = dotProdGrid_9(pos, minJunction); + let xyZ = dotProdGrid_9(pos, (minJunction + vec3f(0, 0, 1))); + let xYz = dotProdGrid_9(pos, (minJunction + vec3f(0, 1, 0))); + let xYZ = dotProdGrid_9(pos, (minJunction + vec3f(0, 1, 1))); + let Xyz = dotProdGrid_9(pos, (minJunction + vec3f(1, 0, 0))); + let XyZ = dotProdGrid_9(pos, (minJunction + vec3f(1, 0, 1))); + let XYz = dotProdGrid_9(pos, (minJunction + vec3f(1, 1, 0))); + let XYZ = dotProdGrid_9(pos, (minJunction + vec3f(1))); var partial = (pos - minJunction); var smoothPartial = quinticInterpolationImpl_16(partial); - var xy = mix(xyz, xyZ, smoothPartial.z); - var xY = mix(xYz, xYZ, smoothPartial.z); - var Xy = mix(Xyz, XyZ, smoothPartial.z); - var XY = mix(XYz, XYZ, smoothPartial.z); - var x = mix(xy, xY, smoothPartial.y); - var X = mix(Xy, XY, smoothPartial.y); + let xy = mix(xyz, xyZ, smoothPartial.z); + let xY = mix(xYz, xYZ, smoothPartial.z); + let Xy = mix(Xyz, XyZ, smoothPartial.z); + let XY = mix(XYz, XYZ, smoothPartial.z); + let x = mix(xy, xY, smoothPartial.y); + let X = mix(Xy, XY, smoothPartial.y); return mix(x, X, smoothPartial.x); } fn caustics_7(uv: vec2f, time2: f32, profile: vec3f) -> vec3f { - var distortion = sample_8(vec3f((uv * 0.5), (time2 * 0.2))); + let distortion = sample_8(vec3f((uv * 0.5), (time2 * 0.2))); var uv2 = (uv + distortion); - var noise = abs(sample_8(vec3f((uv2 * 5), time2))); + let noise = abs(sample_8(vec3f((uv2 * 5), time2))); return pow(vec3f((1 - noise)), profile); } fn rotateXY_17(angle2: f32) -> mat2x2f { - return mat2x2f(vec2f(cos(angle2), sin(angle2)), vec2f(-sin(angle2), cos(angle2))); + return mat2x2f(vec2f(cos(angle2), sin(angle2)), vec2f(-(sin(angle2)), cos(angle2))); } struct mainFragment_Input_18 { @@ -122,19 +122,19 @@ describe('caustics example', () => { } @fragment fn mainFragment_3(_arg_0: mainFragment_Input_18) -> @location(0) vec4f { - var skewMat = mat2x2f(vec2f(0.9800665974617004, 0.19866932928562164), vec2f(((-0.19866933079506122 * 10) + (_arg_0.uv.x * 3)), 4.900332889206208)); + var skewMat = mat2x2f(vec2f(0.9800665974617004, 0.19866932928562164), vec2f((-1.9866933079506122 + (_arg_0.uv.x * 3)), 4.900332889206208)); var skewedUv = (skewMat * _arg_0.uv); - var tile = tilePattern_5((skewedUv * tileDensity_4)); + let tile = tilePattern_5((skewedUv * tileDensity_4)); var albedo = mix(vec3f(0.10000000149011612), vec3f(1), tile); var cuv = vec2f(((_arg_0.uv.x * (pow((_arg_0.uv.y * 1.5), 3) + 0.1)) * 5), (pow((((_arg_0.uv.y * 1.5) + 0.1) * 1.5), 3) * 1)); var c1 = (caustics_7(cuv, (time_6 * 0.2), vec3f(4, 4, 1)) * vec3f(0.4000000059604645, 0.6499999761581421, 1)); var c2 = (caustics_7((cuv * 2), (time_6 * 0.4), vec3f(16, 1, 4)) * vec3f(0.18000000715255737, 0.30000001192092896, 0.5)); var blendCoord = vec3f((_arg_0.uv * vec2f(5, 10)), ((time_6 * 0.2) + 5)); - var blend = saturate((sample_8(blendCoord) + 0.3)); + let blend = saturate((sample_8(blendCoord) + 0.3)); var noFogColor = (albedo * mix(vec3f(0.20000000298023224, 0.5, 1), (c1 + c2), blend)); - var fog = min((pow(_arg_0.uv.y, 0.5) * 1.2), 1); + let fog = min((pow(_arg_0.uv.y, 0.5) * 1.2), 1); var godRayUv = ((rotateXY_17(-0.3) * _arg_0.uv) * vec2f(15, 3)); - var godRayFactor = pow(_arg_0.uv.y, 1); + let godRayFactor = pow(_arg_0.uv.y, 1); var godRay1 = ((sample_8(vec3f(godRayUv, (time_6 * 0.5))) + 1) * (vec3f(0.18000000715255737, 0.30000001192092896, 0.5) * godRayFactor)); var godRay2 = ((sample_8(vec3f((godRayUv * 2), (time_6 * 0.3))) + 1) * (vec3f(0.18000000715255737, 0.30000001192092896, 0.5) * (godRayFactor * 0.4))); var godRays = (godRay1 + godRay2); diff --git a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts index b8e00997ca..1854350617 100644 --- a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts +++ b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts @@ -50,8 +50,8 @@ describe('cubemap reflection example', () => { @group(0) @binding(1) var nextVertices_7: array; fn packVec2u_8(toPack: vec4f) -> vec2u { - var xy = pack2x16float(toPack.xy); - var zw = pack2x16float(toPack.zw); + let xy = pack2x16float(toPack.xy); + let zw = pack2x16float(toPack.zw); return vec2u(xy, zw); } @@ -60,12 +60,12 @@ describe('cubemap reflection example', () => { } @compute @workgroup_size(256, 1, 1) fn computeFn_0(input: computeFn_Input_9) { - var triangleCount = u32((f32(arrayLength(&prevVertices_1)) / 3f)); - var triangleIndex = (input.gid.x + (input.gid.y * 65535)); + let triangleCount = u32((f32(arrayLength(&prevVertices_1)) / 3f)); + let triangleIndex = (input.gid.x + (input.gid.y * 65535)); if ((triangleIndex >= triangleCount)) { return; } - var baseIndexPrev = (triangleIndex * 3); + let baseIndexPrev = (triangleIndex * 3); var v1 = unpackVec2u_3(prevVertices_1[baseIndexPrev].position); var v2 = unpackVec2u_3(prevVertices_1[(baseIndexPrev + 1)].position); var v3 = unpackVec2u_3(prevVertices_1[(baseIndexPrev + 2)].position); @@ -73,15 +73,15 @@ describe('cubemap reflection example', () => { var v23 = vec4f(normalize(calculateMidpoint_4(v2, v3).xyz), 1); var v31 = vec4f(normalize(calculateMidpoint_4(v3, v1).xyz), 1); var newVertices = array(v1, v12, v31, v2, v23, v12, v3, v31, v23, v12, v23, v31); - var baseIndexNext = (triangleIndex * 12); + let baseIndexNext = (triangleIndex * 12); for (var i = 0u; (i < 12); i++) { let reprojectedVertex = (&newVertices[i]); - var triBase = (i - (i % 3)); + let triBase = (i - (i % 3)); var normal = (*reprojectedVertex); if ((smoothFlag_5 == 0)) { normal = getAverageNormal_6(newVertices[triBase], newVertices[(triBase + 1)], newVertices[(triBase + 2)]); } - var outIndex = (baseIndexNext + i); + let outIndex = (baseIndexNext + i); let nextVertex = (&nextVertices_7[outIndex]); (*nextVertex).position = packVec2u_8((*reprojectedVertex)); (*nextVertex).normal = packVec2u_8(normal); @@ -178,11 +178,11 @@ describe('cubemap reflection example', () => { var normalizedNormal = normalize(input.normal.xyz); var normalizedLightDir = normalize(light_6.direction); var ambientLight = (material_8.ambient * (light_6.intensity * light_6.color)); - var diffuseFactor = max(dot(normalizedNormal, normalizedLightDir), 0); + let diffuseFactor = max(dot(normalizedNormal, normalizedLightDir), 0); var diffuseLight = (diffuseFactor * (material_8.diffuse * (light_6.intensity * light_6.color))); var viewDirection = normalize((camera_1.position.xyz - input.worldPos.xyz)); var reflectionDirection = reflect(-(normalizedLightDir), normalizedNormal); - var specularFactor = pow(max(dot(viewDirection, reflectionDirection), 0), material_8.shininess); + let specularFactor = pow(max(dot(viewDirection, reflectionDirection), 0), material_8.shininess); var specularLight = (specularFactor * (material_8.specular * (light_6.intensity * light_6.color))); var reflectionVector = reflect(-(viewDirection), normalizedNormal); var environmentColor = textureSample(cubemap_10, texSampler_11, reflectionVector); diff --git a/packages/typegpu/tests/examples/individual/disco.test.ts b/packages/typegpu/tests/examples/individual/disco.test.ts index cb09d3cf6e..33fd3e82fe 100644 --- a/packages/typegpu/tests/examples/individual/disco.test.ts +++ b/packages/typegpu/tests/examples/individual/disco.test.ts @@ -28,7 +28,7 @@ describe('disco example', () => { } @vertex fn mainVertex_0(_arg_0: mainVertex_Input_2) -> mainVertex_Output_1 { - var pos = array(vec2f(-1, 1), vec2f(-1, -1), vec2f(1, -1), vec2f(-1, 1), vec2f(1, -1), vec2f(1)); + var pos = array(vec2f(-1, 1), vec2f(-1), vec2f(1, -1), vec2f(-1, 1), vec2f(1, -1), vec2f(1)); var uv = array(vec2f(0, 1), vec2f(), vec2f(1, 0), vec2f(0, 1), vec2f(1, 0), vec2f(1)); return mainVertex_Output_1(vec4f(pos[_arg_0.vertexIndex], 0, 1), uv[_arg_0.vertexIndex]); } @@ -37,7 +37,7 @@ describe('disco example', () => { fn aspectCorrected_4(uv: vec2f) -> vec2f { var v = ((uv.xy - 0.5) * 2); - var aspect = (resolutionUniform_5.x / resolutionUniform_5.y); + let aspect = (resolutionUniform_5.x / resolutionUniform_5.y); if ((aspect > 1)) { v.x *= aspect; } @@ -73,7 +73,7 @@ describe('disco example', () => { var accumulatedColor = vec3f(); for (var iteration = 0; (iteration < 5); iteration++) { aspectUv = (fract((aspectUv * (1.3 * sin(time_6)))) - 0.5); - var radialLength = (length(aspectUv) * exp((-length((*originalUv)) * 2))); + var radialLength = (length(aspectUv) * exp((-(length((*originalUv))) * 2))); var paletteColor = palette_7((length((*originalUv)) + (time_6 * 0.9))); radialLength = (sin(((radialLength * 8) + time_6)) / 8f); radialLength = abs(radialLength); diff --git a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts index 632d422536..22b65c647d 100644 --- a/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-double-buffering.test.ts @@ -41,10 +41,10 @@ describe('fluid double buffering example', () => { if (((*obs).enabled == 0)) { continue; } - var minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); - var maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); - var minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); - var maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); + let minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + let maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + let minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + let maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -65,16 +65,16 @@ describe('fluid double buffering example', () => { @group(0) @binding(2) var gridBetaBuffer_9: array; fn wrappedCallback_2(xu: u32, yu: u32, _arg_2: u32) { - var x = i32(xu); - var y = i32(yu); - var index = coordsToIndex_3(x, y); + let x = i32(xu); + let y = i32(yu); + let index = coordsToIndex_3(x, y); var value = vec4f(); if (!isValidFlowOut_4(x, y)) { value = vec4f(); } else { if ((y < 128)) { - var depth = (1 - (f32(y) / 128f)); + let depth = (1 - (f32(y) / 128f)); value = vec4f(0, 0, (10 + (depth * 10)), 0); } } @@ -134,10 +134,10 @@ describe('fluid double buffering example', () => { if (((*obs).enabled == 0)) { continue; } - var minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); - var maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); - var minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); - var maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); + let minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + let maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + let minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + let maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -156,8 +156,8 @@ describe('fluid double buffering example', () => { } fn item_17() -> f32 { - var a = dot(seed_7, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_7, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_7, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_7, vec2f(54.47856521606445, 345.8415222167969)); seed_7.x = fract((cos(a) * 136.8168)); seed_7.y = fract((cos(b) * 534.7645)); return seed_7.y; @@ -177,7 +177,7 @@ describe('fluid double buffering example', () => { for (var i = 0; (i < 4); i++) { let offset = (&neighborOffsets[i]); var neighborDensity = getCell_8((x + (*offset).x), (y + (*offset).y)); - var cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); + let cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); if (!isValidFlowOut_11((x + (*offset).x), (y + (*offset).y))) { continue; } @@ -204,7 +204,7 @@ describe('fluid double buffering example', () => { var src = getCell_8(x, y); var destPos = vec2i((x + i32(src.x)), (y + i32(src.y))); var dest = getCell_8(destPos.x, destPos.y); - var diff = (src.z - dest.z); + let diff = (src.z - dest.z); var outFlow = min(max(0.01, (0.3 + (diff * 0.1))), src.z); if ((length(src.xy) < 0.5)) { outFlow = 0; @@ -228,7 +228,7 @@ describe('fluid double buffering example', () => { fn getMinimumInFlow_19(x: i32, y: i32) -> f32 { const gridSizeF = 256f; - var sourceRadius2 = max(1, (sourceParams_20.radius * gridSizeF)); + let sourceRadius2 = max(1, (sourceParams_20.radius * gridSizeF)); var sourcePos = vec2f((sourceParams_20.center.x * gridSizeF), (sourceParams_20.center.y * gridSizeF)); if ((distance(vec2f(f32(x), f32(y)), sourcePos) < sourceRadius2)) { return sourceParams_20.intensity; @@ -239,9 +239,9 @@ describe('fluid double buffering example', () => { @group(0) @binding(5) var gridAlphaBuffer_22: array; fn simulate_2(xu: u32, yu: u32, _arg_2: u32) { - var x = i32(xu); - var y = i32(yu); - var index = coordsToIndex_3(x, y); + let x = i32(xu); + let y = i32(yu); + let index = coordsToIndex_3(x, y); randSeed2_5(vec2f(f32(index), time_4)); var next = getCell_8(x, y); var nextVelocity = computeVelocity_10(x, y); @@ -252,7 +252,7 @@ describe('fluid double buffering example', () => { next.z += flowFromCell_18(x, y, x, (y - 1)); next.z += flowFromCell_18(x, y, (x + 1), y); next.z += flowFromCell_18(x, y, (x - 1), y); - var minInflow = getMinimumInFlow_19(x, y); + let minInflow = getMinimumInFlow_19(x, y); next.z = max(minInflow, next.z); gridAlphaBuffer_22[index] = next; } @@ -310,10 +310,10 @@ describe('fluid double buffering example', () => { if (((*obs).enabled == 0)) { continue; } - var minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); - var maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); - var minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); - var maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); + let minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + let maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + let minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + let maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -332,8 +332,8 @@ describe('fluid double buffering example', () => { } fn item_17() -> f32 { - var a = dot(seed_7, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_7, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_7, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_7, vec2f(54.47856521606445, 345.8415222167969)); seed_7.x = fract((cos(a) * 136.8168)); seed_7.y = fract((cos(b) * 534.7645)); return seed_7.y; @@ -353,7 +353,7 @@ describe('fluid double buffering example', () => { for (var i = 0; (i < 4); i++) { let offset = (&neighborOffsets[i]); var neighborDensity = getCell_8((x + (*offset).x), (y + (*offset).y)); - var cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); + let cost = (neighborDensity.z + (f32((*offset).y) * gravityCost)); if (!isValidFlowOut_11((x + (*offset).x), (y + (*offset).y))) { continue; } @@ -380,7 +380,7 @@ describe('fluid double buffering example', () => { var src = getCell_8(x, y); var destPos = vec2i((x + i32(src.x)), (y + i32(src.y))); var dest = getCell_8(destPos.x, destPos.y); - var diff = (src.z - dest.z); + let diff = (src.z - dest.z); var outFlow = min(max(0.01, (0.3 + (diff * 0.1))), src.z); if ((length(src.xy) < 0.5)) { outFlow = 0; @@ -404,7 +404,7 @@ describe('fluid double buffering example', () => { fn getMinimumInFlow_19(x: i32, y: i32) -> f32 { const gridSizeF = 256f; - var sourceRadius2 = max(1, (sourceParams_20.radius * gridSizeF)); + let sourceRadius2 = max(1, (sourceParams_20.radius * gridSizeF)); var sourcePos = vec2f((sourceParams_20.center.x * gridSizeF), (sourceParams_20.center.y * gridSizeF)); if ((distance(vec2f(f32(x), f32(y)), sourcePos) < sourceRadius2)) { return sourceParams_20.intensity; @@ -415,9 +415,9 @@ describe('fluid double buffering example', () => { @group(0) @binding(5) var gridBetaBuffer_22: array; fn simulate_2(xu: u32, yu: u32, _arg_2: u32) { - var x = i32(xu); - var y = i32(yu); - var index = coordsToIndex_3(x, y); + let x = i32(xu); + let y = i32(yu); + let index = coordsToIndex_3(x, y); randSeed2_5(vec2f(f32(index), time_4)); var next = getCell_8(x, y); var nextVelocity = computeVelocity_10(x, y); @@ -428,7 +428,7 @@ describe('fluid double buffering example', () => { next.z += flowFromCell_18(x, y, x, (y - 1)); next.z += flowFromCell_18(x, y, (x + 1), y); next.z += flowFromCell_18(x, y, (x - 1), y); - var minInflow = getMinimumInFlow_19(x, y); + let minInflow = getMinimumInFlow_19(x, y); next.z = max(minInflow, next.z); gridBetaBuffer_22[index] = next; } @@ -454,7 +454,7 @@ describe('fluid double buffering example', () => { } @vertex fn vertexMain_0(input: vertexMain_Input_2) -> vertexMain_Output_1 { - var pos = array(vec2f(1), vec2f(-1, 1), vec2f(1, -1), vec2f(-1, -1)); + var pos = array(vec2f(1), vec2f(-1, 1), vec2f(1, -1), vec2f(-1)); var uv = array(vec2f(1), vec2f(0, 1), vec2f(1, 0), vec2f()); return vertexMain_Output_1(vec4f(pos[input.idx].x, pos[input.idx].y, 0, 1), uv[input.idx]); } @@ -479,10 +479,10 @@ describe('fluid double buffering example', () => { if (((*obs).enabled == 0)) { continue; } - var minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); - var maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); - var minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); - var maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); + let minX = max(0, ((*obs).center.x - i32((f32((*obs).size.x) / 2f)))); + let maxX = min(256, ((*obs).center.x + i32((f32((*obs).size.x) / 2f)))); + let minY = max(0, ((*obs).center.y - i32((f32((*obs).size.y) / 2f)))); + let maxY = min(256, ((*obs).center.y + i32((f32((*obs).size.y) / 2f)))); if (((((x >= minX) && (x <= maxX)) && (y >= minY)) && (y <= maxY))) { return true; } @@ -495,11 +495,11 @@ describe('fluid double buffering example', () => { } @fragment fn fragmentMain_3(input: fragmentMain_Input_9) -> @location(0) vec4f { - var x = i32((input.uv.x * 256)); - var y = i32((input.uv.y * 256)); - var index = coordsToIndex_4(x, y); + let x = i32((input.uv.x * 256)); + let y = i32((input.uv.y * 256)); + let index = coordsToIndex_4(x, y); let cell = (&gridAlphaBuffer_5[index]); - var density = max(0, (*cell).z); + let density = max(0, (*cell).z); var obstacleColor = vec4f(0.10000000149011612, 0.10000000149011612, 0.10000000149011612, 1); var background = vec4f(0.8999999761581421, 0.8999999761581421, 0.8999999761581421, 1); var firstColor = vec4f(0.20000000298023224, 0.6000000238418579, 1, 1); @@ -515,7 +515,7 @@ describe('fluid double buffering example', () => { return background; } if ((density <= firstThreshold)) { - var t = (1 - pow((1 - (density / firstThreshold)), 2)); + let t = (1 - pow((1 - (density / firstThreshold)), 2)); return mix(background, firstColor, t); } if ((density <= secondThreshold)) { diff --git a/packages/typegpu/tests/examples/individual/fluid-with-atomics.test.ts b/packages/typegpu/tests/examples/individual/fluid-with-atomics.test.ts index fcac496ffe..60a50192b7 100644 --- a/packages/typegpu/tests/examples/individual/fluid-with-atomics.test.ts +++ b/packages/typegpu/tests/examples/individual/fluid-with-atomics.test.ts @@ -20,8 +20,8 @@ describe('fluid with atomics example', () => { "@group(0) @binding(0) var size_6: vec2u; fn getIndex_5(x: u32, y: u32) -> u32 { - var h = size_6.y; - var w = size_6.x; + let h = size_6.y; + let w = size_6.x; return (((y % h) * w) + (x % w)); } @@ -48,9 +48,9 @@ describe('fluid with atomics example', () => { const MAX_WATER_LEVEL_12: u32 = 16777215; fn persistFlags_11(x: u32, y: u32) { - var cell = getCell_4(x, y); - var waterLevel = (cell & MAX_WATER_LEVEL_12); - var flags = (cell >> 24); + let cell = getCell_4(x, y); + let waterLevel = (cell & MAX_WATER_LEVEL_12); + let flags = (cell >> 24); updateCell_8(x, y, ((flags << 24) | waterLevel)); } @@ -63,9 +63,9 @@ describe('fluid with atomics example', () => { } fn addToCell_14(x: u32, y: u32, value: u32) { - var cell = getCellNext_15(x, y); - var waterLevel = (cell & MAX_WATER_LEVEL_12); - var newWaterLevel = min((waterLevel + value), MAX_WATER_LEVEL_12); + let cell = getCellNext_15(x, y); + let waterLevel = (cell & MAX_WATER_LEVEL_12); + let newWaterLevel = min((waterLevel + value), MAX_WATER_LEVEL_12); atomicAdd(&nextState_9[getIndex_5(x, y)], (newWaterLevel - waterLevel)); } @@ -78,9 +78,9 @@ describe('fluid with atomics example', () => { } fn subtractFromCell_18(x: u32, y: u32, value: u32) { - var cell = getCellNext_15(x, y); - var waterLevel = (cell & MAX_WATER_LEVEL_12); - var newWaterLevel = max((waterLevel - min(value, waterLevel)), 0); + let cell = getCellNext_15(x, y); + let waterLevel = (cell & MAX_WATER_LEVEL_12); + let newWaterLevel = max((waterLevel - min(value, waterLevel)), 0); atomicSub(&nextState_9[getIndex_5(x, y)], (waterLevel - newWaterLevel)); } @@ -115,7 +115,7 @@ describe('fluid with atomics example', () => { const MAX_PRESSURE_21: u32 = 12; fn getStableStateBelow_19(upper: u32, lower: u32) -> u32 { - var totalMass = (upper + lower); + let totalMass = (upper + lower); if ((totalMass <= MAX_WATER_LEVEL_UNPRESSURIZED_20)) { return totalMass; } @@ -136,11 +136,11 @@ describe('fluid with atomics example', () => { return; } if (!isWall_10(x, (y - 1))) { - var waterLevelBelow = getWaterLevel_17(x, (y - 1)); - var stable = getStableStateBelow_19(remainingWater, waterLevelBelow); + let waterLevelBelow = getWaterLevel_17(x, (y - 1)); + let stable = getStableStateBelow_19(remainingWater, waterLevelBelow); if ((waterLevelBelow < stable)) { - var change = (stable - waterLevelBelow); - var flow = min(change, viscosity_22); + let change = (stable - waterLevelBelow); + let flow = min(change, viscosity_22); subtractFromCell_18(x, y, flow); addToCell_14(x, (y - 1), flow); remainingWater -= flow; @@ -149,12 +149,12 @@ describe('fluid with atomics example', () => { if ((remainingWater == 0)) { return; } - var waterLevelBefore = remainingWater; + let waterLevelBefore = remainingWater; if (!isWall_10((x - 1), y)) { - var flowRaw = (i32(waterLevelBefore) - i32(getWaterLevel_17((x - 1), y))); + let flowRaw = (i32(waterLevelBefore) - i32(getWaterLevel_17((x - 1), y))); if ((flowRaw > 0)) { - var change = max(min(4, remainingWater), u32((f32(flowRaw) / 4f))); - var flow = min(change, viscosity_22); + let change = max(min(4, remainingWater), u32((f32(flowRaw) / 4f))); + let flow = min(change, viscosity_22); subtractFromCell_18(x, y, flow); addToCell_14((x - 1), y, flow); remainingWater -= flow; @@ -164,10 +164,10 @@ describe('fluid with atomics example', () => { return; } if (!isWall_10((x + 1), y)) { - var flowRaw = (i32(waterLevelBefore) - i32(getWaterLevel_17((x + 1), y))); + let flowRaw = (i32(waterLevelBefore) - i32(getWaterLevel_17((x + 1), y))); if ((flowRaw > 0)) { - var change = max(min(4, remainingWater), u32((f32(flowRaw) / 4f))); - var flow = min(change, viscosity_22); + let change = max(min(4, remainingWater), u32((f32(flowRaw) / 4f))); + let flow = min(change, viscosity_22); subtractFromCell_18(x, y, flow); addToCell_14((x + 1), y, flow); remainingWater -= flow; @@ -177,9 +177,9 @@ describe('fluid with atomics example', () => { return; } if (!isWall_10(x, (y + 1))) { - var stable = getStableStateBelow_19(getWaterLevel_17(x, (y + 1)), remainingWater); + let stable = getStableStateBelow_19(getWaterLevel_17(x, (y + 1)), remainingWater); if ((stable < remainingWater)) { - var flow = min((remainingWater - stable), viscosity_22); + let flow = min((remainingWater - stable), viscosity_22); subtractFromCell_18(x, y, flow); addToCell_14(x, (y + 1), flow); remainingWater -= flow; @@ -209,14 +209,14 @@ describe('fluid with atomics example', () => { } @vertex fn vertex_0(input: vertex_Input_3) -> vertex_Output_2 { - var w = size_1.x; - var h = size_1.y; - var gridX = (input.idx % w); - var gridY = u32((f32(input.idx) / f32(w))); - var maxDim = max(w, h); - var x = (((2 * (f32(gridX) + input.squareData.x)) - f32(w)) / f32(maxDim)); - var y = (((2 * (f32(gridY) + input.squareData.y)) - f32(h)) / f32(maxDim)); - var cellFlags = (input.currentStateData >> 24); + let w = size_1.x; + let h = size_1.y; + let gridX = (input.idx % w); + let gridY = u32((f32(input.idx) / f32(w))); + let maxDim = max(w, h); + let x = (((2 * (f32(gridX) + input.squareData.x)) - f32(w)) / f32(maxDim)); + let y = (((2 * (f32(gridY) + input.squareData.y)) - f32(h)) / f32(maxDim)); + let cellFlags = (input.currentStateData >> 24); var cell = f32((input.currentStateData & 16777215)); if ((cellFlags == 1)) { cell = -1; @@ -244,11 +244,11 @@ describe('fluid with atomics example', () => { if ((input.cell == -3)) { return vec4f(1, 0, 0, 1); } - var normalized = min((input.cell / 255f), 1); + let normalized = min((input.cell / 255f), 1); if ((normalized == 0)) { return vec4f(); } - var res = (1f / (1 + exp((-(normalized - 0.2) * 10)))); + let res = (1f / (1 + exp((-((normalized - 0.2)) * 10)))); return vec4f(0, 0, res, res); }" `); diff --git a/packages/typegpu/tests/examples/individual/gravity.test.ts b/packages/typegpu/tests/examples/individual/gravity.test.ts index fdb1d13773..0704b52152 100644 --- a/packages/typegpu/tests/examples/individual/gravity.test.ts +++ b/packages/typegpu/tests/examples/individual/gravity.test.ts @@ -63,11 +63,11 @@ describe('gravity example', () => { } @compute @workgroup_size(1) fn computeCollisionsShader_0(input: computeCollisionsShader_Input_7) { - var currentId = input.gid.x; + let currentId = input.gid.x; var current = CelestialBody_2(inState_1[currentId].destroyed, inState_1[currentId].position, inState_1[currentId].velocity, inState_1[currentId].mass, inState_1[currentId].radiusMultiplier, inState_1[currentId].collisionBehavior, inState_1[currentId].textureIndex, inState_1[currentId].ambientLightFactor); if ((current.destroyed == 0)) { for (var i = 0; (i < celestialBodiesCount_3); i++) { - var otherId = u32(i); + let otherId = u32(i); var other = CelestialBody_2(inState_1[otherId].destroyed, inState_1[otherId].position, inState_1[otherId].velocity, inState_1[otherId].mass, inState_1[otherId].radiusMultiplier, inState_1[otherId].collisionBehavior, inState_1[otherId].textureIndex, inState_1[otherId].ambientLightFactor); if ((((((u32(i) == input.gid.x) || (other.destroyed == 1)) || (current.collisionBehavior == 0)) || (other.collisionBehavior == 0)) || (distance(current.position, other.position) >= (radiusOf_4(current) + radiusOf_4(other))))) { continue; @@ -79,13 +79,13 @@ describe('gravity example', () => { current.velocity = (0.99 * (current.velocity - (((((2 * other.mass) / (current.mass + other.mass)) * dot((current.velocity - other.velocity), (current.position - other.position))) / pow(distance(current.position, other.position), 2)) * (current.position - other.position)))); } else { - var isCurrentAbsorbed = ((current.collisionBehavior == 1) || ((current.collisionBehavior == 2) && isSmaller_5(currentId, otherId))); + let isCurrentAbsorbed = ((current.collisionBehavior == 1) || ((current.collisionBehavior == 2) && isSmaller_5(currentId, otherId))); if (isCurrentAbsorbed) { current.destroyed = 1; } else { - var m1 = current.mass; - var m2 = other.mass; + let m1 = current.mass; + let m2 = other.mass; current.velocity = (((m1 / (m1 + m2)) * current.velocity) + ((m2 / (m1 + m2)) * other.velocity)); current.mass = (m1 + m2); } @@ -129,7 +129,7 @@ describe('gravity example', () => { @compute @workgroup_size(1) fn computeGravityShader_0(input: computeGravityShader_Input_8) { var current = CelestialBody_2(inState_1[input.gid.x].destroyed, inState_1[input.gid.x].position, inState_1[input.gid.x].velocity, inState_1[input.gid.x].mass, inState_1[input.gid.x].radiusMultiplier, inState_1[input.gid.x].collisionBehavior, inState_1[input.gid.x].textureIndex, inState_1[input.gid.x].ambientLightFactor); - var dt = (time_3.passed * time_3.multiplier); + let dt = (time_3.passed * time_3.multiplier); let updatedCurrent = (¤t); if ((current.destroyed == 0)) { for (var i = 0; (i < celestialBodiesCount_5); i++) { @@ -137,8 +137,8 @@ describe('gravity example', () => { if (((u32(i) == input.gid.x) || (other.destroyed == 1))) { continue; } - var dist = max((radiusOf_6(current) + radiusOf_6(other)), distance(current.position, other.position)); - var gravityForce = (((current.mass * other.mass) / dist) / dist); + let dist = max((radiusOf_6(current) + radiusOf_6(other)), distance(current.position, other.position)); + let gravityForce = (((current.mass * other.mass) / dist) / dist); var direction = normalize((other.position - current.position)); (*updatedCurrent).velocity = ((*updatedCurrent).velocity + (((gravityForce / current.mass) * dt) * direction)); } @@ -259,7 +259,7 @@ describe('gravity example', () => { var ambient = (input.ambientLightFactor * (textureColor * lightColor)); var normal = input.normals; var lightDirection = normalize((lightSource_11 - input.worldPosition)); - var cosTheta = dot(normal, lightDirection); + let cosTheta = dot(normal, lightDirection); var diffuse = (max(0, cosTheta) * (textureColor * lightColor)); var litColor = (ambient + diffuse); return vec4f(litColor.xyz, 1); diff --git a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts index f760347578..4f2bce7391 100644 --- a/packages/typegpu/tests/examples/individual/liquid-glass.test.ts +++ b/packages/typegpu/tests/examples/individual/liquid-glass.test.ts @@ -98,9 +98,9 @@ describe('liquid-glass example', () => { } fn calculateWeights_9(sdfDist: f32, start: f32, end: f32, featherUV: f32) -> Weights_10 { - var inside = (1 - smoothstep((start - featherUV), (start + featherUV), sdfDist)); - var outside = smoothstep((end - featherUV), (end + featherUV), sdfDist); - var ring = max(0, ((1 - inside) - outside)); + let inside = (1 - smoothstep((start - featherUV), (start + featherUV), sdfDist)); + let outside = smoothstep((end - featherUV), (end + featherUV), sdfDist); + let ring = max(0, ((1 - inside) - outside)); return Weights_10(inside, ring, outside); } @@ -130,11 +130,11 @@ describe('liquid-glass example', () => { @fragment fn fragmentShader_3(_arg_0: fragmentShader_Input_15) -> @location(0) vec4f { var posInBoxSpace = (_arg_0.uv - mousePosUniform_4); - var sdfDist = sdRoundedBox2d_7(posInBoxSpace, paramsUniform_5.rectDims, paramsUniform_5.radius); + let sdfDist = sdRoundedBox2d_7(posInBoxSpace, paramsUniform_5.rectDims, paramsUniform_5.radius); var dir = normalize((posInBoxSpace * paramsUniform_5.rectDims.yx)); - var normalizedDist = ((sdfDist - paramsUniform_5.start) / (paramsUniform_5.end - paramsUniform_5.start)); + let normalizedDist = ((sdfDist - paramsUniform_5.start) / (paramsUniform_5.end - paramsUniform_5.start)); var texDim = textureDimensions(sampledView_8, 0); - var featherUV = (paramsUniform_5.edgeFeather / f32(max(texDim.x, texDim.y))); + let featherUV = (paramsUniform_5.edgeFeather / f32(max(texDim.x, texDim.y))); var weights = calculateWeights_9(sdfDist, paramsUniform_5.start, paramsUniform_5.end, featherUV); var blurSample = textureSampleBias(sampledView_8, sampler_11, _arg_0.uv, paramsUniform_5.blur); var refractedSample = sampleWithChromaticAberration_12(sampledView_8, sampler_11, (_arg_0.uv + (dir * (paramsUniform_5.refractionStrength * normalizedDist))), (paramsUniform_5.chromaticStrength * normalizedDist), dir, (paramsUniform_5.blur * paramsUniform_5.edgeBlurMultiplier)); diff --git a/packages/typegpu/tests/examples/individual/log-test.test.ts b/packages/typegpu/tests/examples/individual/log-test.test.ts index 6e5f6bc5d1..f833596a5a 100644 --- a/packages/typegpu/tests/examples/individual/log-test.test.ts +++ b/packages/typegpu/tests/examples/individual/log-test.test.ts @@ -798,14 +798,14 @@ describe('console log example', () => { fn wrappedCallback_2(_arg_0: u32, _arg_1: u32, _arg_2: u32) { log1_3(); log2_10(3.140000104904175); - log3_14(i32(-2000000000)); + log3_14(-2000000000); log4_17(3000000000); log5_20(true); log6_23(); log7_25(); - log8_27(vec2f(1.1, -2.2)); - log9_30(vec3f(10.1, -20.2, 30.3)); - log10_33(vec4f(100.1, -200.2, 300.3, -400.4)); + log8_27(vec2f(1.100000023841858, -2.200000047683716)); + log9_30(vec3f(10.100000381469727, -20.200000762939453, 30.299999237060547)); + log10_33(vec4f(100.0999984741211, -200.1999969482422, 300.29998779296875, -400.3999938964844)); log11_36(); log12_38(vec2i(-1, -2)); log13_41(vec3i(-1, -2, -3)); @@ -1494,7 +1494,7 @@ describe('console log example', () => { } @vertex fn mainVertex_0(input: mainVertex_Input_2) -> mainVertex_Output_1 { - var positions = array(vec2f(0, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5)); + var positions = array(vec2f(0, 0.5), vec2f(-0.5), vec2f(0.5, -0.5)); return mainVertex_Output_1(vec4f(positions[input.vertexIndex], 0, 1)); } @@ -1555,7 +1555,7 @@ describe('console log example', () => { } @vertex fn mainVertex_0(input: mainVertex_Input_2) -> mainVertex_Output_1 { - var positions = array(vec2f(0, 0.5), vec2f(-0.5, -0.5), vec2f(0.5, -0.5)); + var positions = array(vec2f(0, 0.5), vec2f(-0.5), vec2f(0.5, -0.5)); return mainVertex_Output_1(vec4f(positions[input.vertexIndex], 0, 1)); } diff --git a/packages/typegpu/tests/examples/individual/matrix-next.test.ts b/packages/typegpu/tests/examples/individual/matrix-next.test.ts index cdc334efef..ad6d245888 100644 --- a/packages/typegpu/tests/examples/individual/matrix-next.test.ts +++ b/packages/typegpu/tests/examples/individual/matrix-next.test.ts @@ -52,39 +52,39 @@ describe('matrix(next) example', () => { @compute @workgroup_size(16, 16) fn computeSharedMemory_0(input: computeSharedMemory_Input_10) { let dimensions = (&dimensions_1); - var numTiles = u32((f32((((*dimensions).firstColumnCount + 16) - 1)) / 16f)); - var globalRow = ((input.wid.x * 16) + input.lid.x); - var globalCol = ((input.wid.y * 16) + input.lid.y); - var localRow = input.lid.x; - var localCol = input.lid.y; - var tileIdx = getTileIndex_3(localRow, localCol); + let numTiles = u32((f32((((*dimensions).firstColumnCount + 16) - 1)) / 16f)); + let globalRow = ((input.wid.x * 16) + input.lid.x); + let globalCol = ((input.wid.y * 16) + input.lid.y); + let localRow = input.lid.x; + let localCol = input.lid.y; + let tileIdx = getTileIndex_3(localRow, localCol); var accumulatedResult = 0; for (var tileIndex = 0u; (tileIndex < numTiles); tileIndex++) { - var matrixACol = ((tileIndex * 16) + localCol); + let matrixACol = ((tileIndex * 16) + localCol); var valueA = 0; if (((globalRow < (*dimensions).firstRowCount) && (matrixACol < (*dimensions).firstColumnCount))) { - var indexA = getIndex_4(globalRow, matrixACol, (*dimensions).firstColumnCount); + let indexA = getIndex_4(globalRow, matrixACol, (*dimensions).firstColumnCount); valueA = firstMatrix_5[indexA]; } tileA_6[tileIdx] = valueA; - var matrixBRow = ((tileIndex * 16) + localRow); + let matrixBRow = ((tileIndex * 16) + localRow); var valueB = 0; if (((matrixBRow < (*dimensions).firstColumnCount) && (globalCol < (*dimensions).secondColumnCount))) { - var indexB = getIndex_4(matrixBRow, globalCol, (*dimensions).secondColumnCount); + let indexB = getIndex_4(matrixBRow, globalCol, (*dimensions).secondColumnCount); valueB = secondMatrix_7[indexB]; } tileB_8[tileIdx] = valueB; workgroupBarrier(); - var effectiveTileSize = min(16, ((*dimensions).firstColumnCount - (tileIndex * 16))); + let effectiveTileSize = min(16, ((*dimensions).firstColumnCount - (tileIndex * 16))); for (var k = 0u; (k < effectiveTileSize); k++) { - var tileA_element = tileA_6[getTileIndex_3(localRow, k)]; - var tileB_element = tileB_8[getTileIndex_3(k, localCol)]; + let tileA_element = tileA_6[getTileIndex_3(localRow, k)]; + let tileB_element = tileB_8[getTileIndex_3(k, localCol)]; accumulatedResult += (tileA_element * tileB_element); } workgroupBarrier(); } if (((globalRow < (*dimensions).firstRowCount) && (globalCol < (*dimensions).secondColumnCount))) { - var outputIndex = getIndex_4(globalRow, globalCol, (*dimensions).secondColumnCount); + let outputIndex = getIndex_4(globalRow, globalCol, (*dimensions).secondColumnCount); resultMatrix_9[outputIndex] = accumulatedResult; } }" diff --git a/packages/typegpu/tests/examples/individual/mnist-inference.test.ts b/packages/typegpu/tests/examples/individual/mnist-inference.test.ts index f0088c2dc6..ee0d80f42e 100644 --- a/packages/typegpu/tests/examples/individual/mnist-inference.test.ts +++ b/packages/typegpu/tests/examples/individual/mnist-inference.test.ts @@ -39,14 +39,14 @@ describe('mnist inference example', () => { } @compute @workgroup_size(1) fn defaultCompute_0(_arg_0: defaultCompute_Input_6) { - var inputSize = arrayLength(&input_1); - var i = _arg_0.gid.x; - var weightsOffset = (i * inputSize); + let inputSize = arrayLength(&input_1); + let i = _arg_0.gid.x; + let weightsOffset = (i * inputSize); var sum = 0f; for (var j = 0u; (j < inputSize); j++) { sum = fma(input_1[j], weights_2[(weightsOffset + j)], sum); } - var total = (sum + biases_3[i]); + let total = (sum + biases_3[i]); output_4[i] = relu_5(total); } @@ -74,20 +74,20 @@ describe('mnist inference example', () => { } @compute @workgroup_size(128) fn subgroupCompute_0(_arg_0: subgroupCompute_Input_7) { - var subgroupId = u32((f32(_arg_0.lid.x) / f32(_arg_0.ssize))); - var outputsPerWG = u32((f32(workgroupSize_1) / f32(_arg_0.ssize))); - var neuronIndex = ((_arg_0.wid.x * outputsPerWG) + subgroupId); - var outLen = arrayLength(&output_2); - var valid = (neuronIndex < outLen); - var inputSize = arrayLength(&input_3); + let subgroupId = u32((f32(_arg_0.lid.x) / f32(_arg_0.ssize))); + let outputsPerWG = u32((f32(workgroupSize_1) / f32(_arg_0.ssize))); + let neuronIndex = ((_arg_0.wid.x * outputsPerWG) + subgroupId); + let outLen = arrayLength(&output_2); + let valid = (neuronIndex < outLen); + let inputSize = arrayLength(&input_3); var partial = 0f; if (valid) { - var weightsOffset = (neuronIndex * inputSize); + let weightsOffset = (neuronIndex * inputSize); for (var j = _arg_0.sid; (j < inputSize); j += _arg_0.ssize) { partial = fma(input_3[j], weights_4[(weightsOffset + j)], partial); } } - var sum = subgroupAdd(partial); + let sum = subgroupAdd(partial); if ((valid && (_arg_0.sid == 0))) { output_2[neuronIndex] = relu_6((sum + biases_5[neuronIndex])); } diff --git a/packages/typegpu/tests/examples/individual/oklab.test.ts b/packages/typegpu/tests/examples/individual/oklab.test.ts index 2e3dc4ca6c..9850d12293 100644 --- a/packages/typegpu/tests/examples/individual/oklab.test.ts +++ b/packages/typegpu/tests/examples/individual/oklab.test.ts @@ -27,7 +27,7 @@ describe('oklab example', () => { } @vertex fn fullScreenTriangle_0(input: fullScreenTriangle_Input_2) -> fullScreenTriangle_Output_1 { - var pos = array(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3)); + var pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); return fullScreenTriangle_Output_1(vec4f(pos[input.vertexIndex], 0, 1), pos[input.vertexIndex]); } @@ -43,12 +43,12 @@ describe('oklab example', () => { } fn oklabToLinearRgb_7(lab: vec3f) -> vec3f { - var l_ = ((lab.x + (0.3963377774 * lab.y)) + (0.2158037573 * lab.z)); - var m_ = ((lab.x - (0.1055613458 * lab.y)) - (0.0638541728 * lab.z)); - var s_ = ((lab.x - (0.0894841775 * lab.y)) - (1.291485548 * lab.z)); - var l = ((l_ * l_) * l_); - var m = ((m_ * m_) * m_); - var s = ((s_ * s_) * s_); + let l_ = ((lab.x + (0.3963377774 * lab.y)) + (0.2158037573 * lab.z)); + let m_ = ((lab.x - (0.1055613458 * lab.y)) - (0.0638541728 * lab.z)); + let s_ = ((lab.x - (0.0894841775 * lab.y)) - (1.291485548 * lab.z)); + let l = ((l_ * l_) * l_); + let m = ((m_ * m_) * m_); + let s = ((s_ * s_) * s_); return vec3f((((4.0767416621 * l) - (3.3077115913 * m)) + (0.2309699292 * s)), (((-1.2684380046 * l) + (2.6097574011 * m)) - (0.3413193965 * s)), (((-0.0041960863 * l) - (0.7034186147 * m)) + (1.707614701 * s))); } @@ -93,26 +93,26 @@ describe('oklab example', () => { ws = 1.707614701; } } - var k_l = ((0.3963377774 * a) + (0.2158037573 * b)); - var k_m = ((-0.1055613458 * a) - (0.0638541728 * b)); - var k_s = ((-0.0894841775 * a) - (1.291485548 * b)); + let k_l = ((0.3963377774 * a) + (0.2158037573 * b)); + let k_m = ((-0.1055613458 * a) - (0.0638541728 * b)); + let k_s = ((-0.0894841775 * a) - (1.291485548 * b)); var S = ((((k0 + (k1 * a)) + (k2 * b)) + ((k3 * a) * a)) + ((k4 * a) * b)); { - var l_ = (1 + (S * k_l)); - var m_ = (1 + (S * k_m)); - var s_ = (1 + (S * k_s)); - var l = ((l_ * l_) * l_); - var m = ((m_ * m_) * m_); - var s = ((s_ * s_) * s_); - var l_dS = (((3 * k_l) * l_) * l_); - var m_dS = (((3 * k_m) * m_) * m_); - var s_dS = (((3 * k_s) * s_) * s_); - var l_dS2 = (((6 * k_l) * k_l) * l_); - var m_dS2 = (((6 * k_m) * k_m) * m_); - var s_dS2 = (((6 * k_s) * k_s) * s_); - var f = (((wl * l) + (wm * m)) + (ws * s)); - var f1 = (((wl * l_dS) + (wm * m_dS)) + (ws * s_dS)); - var f2 = (((wl * l_dS2) + (wm * m_dS2)) + (ws * s_dS2)); + let l_ = (1 + (S * k_l)); + let m_ = (1 + (S * k_m)); + let s_ = (1 + (S * k_s)); + let l = ((l_ * l_) * l_); + let m = ((m_ * m_) * m_); + let s = ((s_ * s_) * s_); + let l_dS = (((3 * k_l) * l_) * l_); + let m_dS = (((3 * k_m) * m_) * m_); + let s_dS = (((3 * k_s) * s_) * s_); + let l_dS2 = (((6 * k_l) * k_l) * l_); + let m_dS2 = (((6 * k_m) * k_m) * m_); + let s_dS2 = (((6 * k_s) * k_s) * s_); + let f = (((wl * l) + (wm * m)) + (ws * s)); + let f1 = (((wl * l_dS) + (wm * m_dS)) + (ws * s_dS)); + let f2 = (((wl * l_dS2) + (wm * m_dS2)) + (ws * s_dS2)); S = (S - ((f * f1) / ((f1 * f1) - ((0.5 * f) * f2)))); } return S; @@ -128,10 +128,10 @@ describe('oklab example', () => { } fn findCusp_9(a: f32, b: f32) -> LC_12 { - var S_cusp = computeMaxSaturation_10(a, b); + let S_cusp = computeMaxSaturation_10(a, b); var rgb_at_max = oklabToLinearRgb_7(vec3f(1, (S_cusp * a), (S_cusp * b))); - var L_cusp = cbrt_11((1f / max(max(rgb_at_max.x, rgb_at_max.y), rgb_at_max.z))); - var C_cusp = (L_cusp * S_cusp); + let L_cusp = cbrt_11((1f / max(max(rgb_at_max.x, rgb_at_max.y), rgb_at_max.z))); + let C_cusp = (L_cusp * S_cusp); return LC_12(L_cusp, C_cusp); } @@ -144,44 +144,44 @@ describe('oklab example', () => { else { t = ((cusp.C * (L0 - 1)) / ((C1 * (cusp.L - 1)) + (cusp.C * (L0 - L1)))); { - var dL = (L1 - L0); - var dC = C1; - var k_l = ((0.3963377774 * a) + (0.2158037573 * b)); - var k_m = ((-0.1055613458 * a) - (0.0638541728 * b)); - var k_s = ((-0.0894841775 * a) - (1.291485548 * b)); - var l_dt = (dL + (dC * k_l)); - var m_dt = (dL + (dC * k_m)); - var s_dt = (dL + (dC * k_s)); + let dL = (L1 - L0); + let dC = C1; + let k_l = ((0.3963377774 * a) + (0.2158037573 * b)); + let k_m = ((-0.1055613458 * a) - (0.0638541728 * b)); + let k_s = ((-0.0894841775 * a) - (1.291485548 * b)); + let l_dt = (dL + (dC * k_l)); + let m_dt = (dL + (dC * k_m)); + let s_dt = (dL + (dC * k_s)); { - var L = ((L0 * (1 - t)) + (t * L1)); - var C = (t * C1); - var l_ = (L + (C * k_l)); - var m_ = (L + (C * k_m)); - var s_ = (L + (C * k_s)); - var l = ((l_ * l_) * l_); - var m = ((m_ * m_) * m_); - var s = ((s_ * s_) * s_); - var ldt = (((3 * l_dt) * l_) * l_); - var mdt = (((3 * m_dt) * m_) * m_); - var sdt = (((3 * s_dt) * s_) * s_); - var ldt2 = (((6 * l_dt) * l_dt) * l_); - var mdt2 = (((6 * m_dt) * m_dt) * m_); - var sdt2 = (((6 * s_dt) * s_dt) * s_); - var r = ((((4.0767416621 * l) - (3.3077115913 * m)) + (0.2309699292 * s)) - 1); - var r1 = (((4.0767416621 * ldt) - (3.3077115913 * mdt)) + (0.2309699292 * sdt)); - var r2 = (((4.0767416621 * ldt2) - (3.3077115913 * mdt2)) + (0.2309699292 * sdt2)); - var u_r = (r1 / ((r1 * r1) - ((0.5 * r) * r2))); - var t_r = (-r * u_r); - var g = ((((-1.2684380046 * l) + (2.6097574011 * m)) - (0.3413193965 * s)) - 1); - var g1 = (((-1.2684380046 * ldt) + (2.6097574011 * mdt)) - (0.3413193965 * sdt)); - var g2 = (((-1.2684380046 * ldt2) + (2.6097574011 * mdt2)) - (0.3413193965 * sdt2)); - var u_g = (g1 / ((g1 * g1) - ((0.5 * g) * g2))); - var t_g = (-g * u_g); - var b2 = ((((-0.0041960863 * l) - (0.7034186147 * m)) + (1.707614701 * s)) - 1); - var b1 = (((-0.0041960863 * ldt) - (0.7034186147 * mdt)) + (1.707614701 * sdt)); - var b22 = (((-0.0041960863 * ldt2) - (0.7034186147 * mdt2)) + (1.707614701 * sdt2)); - var u_b = (b1 / ((b1 * b1) - ((0.5 * b2) * b22))); - var t_b = (-b2 * u_b); + let L = ((L0 * (1 - t)) + (t * L1)); + let C = (t * C1); + let l_ = (L + (C * k_l)); + let m_ = (L + (C * k_m)); + let s_ = (L + (C * k_s)); + let l = ((l_ * l_) * l_); + let m = ((m_ * m_) * m_); + let s = ((s_ * s_) * s_); + let ldt = (((3 * l_dt) * l_) * l_); + let mdt = (((3 * m_dt) * m_) * m_); + let sdt = (((3 * s_dt) * s_) * s_); + let ldt2 = (((6 * l_dt) * l_dt) * l_); + let mdt2 = (((6 * m_dt) * m_dt) * m_); + let sdt2 = (((6 * s_dt) * s_dt) * s_); + let r = ((((4.0767416621 * l) - (3.3077115913 * m)) + (0.2309699292 * s)) - 1); + let r1 = (((4.0767416621 * ldt) - (3.3077115913 * mdt)) + (0.2309699292 * sdt)); + let r2 = (((4.0767416621 * ldt2) - (3.3077115913 * mdt2)) + (0.2309699292 * sdt2)); + let u_r = (r1 / ((r1 * r1) - ((0.5 * r) * r2))); + var t_r = (-(r) * u_r); + let g = ((((-1.2684380046 * l) + (2.6097574011 * m)) - (0.3413193965 * s)) - 1); + let g1 = (((-1.2684380046 * ldt) + (2.6097574011 * mdt)) - (0.3413193965 * sdt)); + let g2 = (((-1.2684380046 * ldt2) + (2.6097574011 * mdt2)) - (0.3413193965 * sdt2)); + let u_g = (g1 / ((g1 * g1) - ((0.5 * g) * g2))); + var t_g = (-(g) * u_g); + let b2 = ((((-0.0041960863 * l) - (0.7034186147 * m)) + (1.707614701 * s)) - 1); + let b1 = (((-0.0041960863 * ldt) - (0.7034186147 * mdt)) + (1.707614701 * sdt)); + let b22 = (((-0.0041960863 * ldt2) - (0.7034186147 * mdt2)) + (1.707614701 * sdt2)); + let u_b = (b1 / ((b1 * b1) - ((0.5 * b2) * b22))); + var t_b = (-(b2) * u_b); t_r = select(FLT_MAX, t_r, (u_r >= 0)); t_g = select(FLT_MAX, t_g, (u_g >= 0)); t_b = select(FLT_MAX, t_b, (u_b >= 0)); @@ -194,18 +194,18 @@ describe('oklab example', () => { fn gamutClipAdaptiveL05_8(lab: vec3f) -> vec3f { const alpha = 0.20000000298023224f; - var L = lab.x; + let L = lab.x; const eps = 1e-5; - var C = max(eps, length(lab.yz)); - var a_ = (lab.y / C); - var b_ = (lab.z / C); - var Ld = (L - 0.5); - var e1 = ((0.5 + abs(Ld)) + (alpha * C)); - var L0 = (0.5 * (1 + (sign(Ld) * (e1 - sqrt(max(0, ((e1 * e1) - (2 * abs(Ld))))))))); + let C = max(eps, length(lab.yz)); + let a_ = (lab.y / C); + let b_ = (lab.z / C); + let Ld = (L - 0.5); + let e1 = ((0.5 + abs(Ld)) + (alpha * C)); + let L0 = (0.5 * (1 + (sign(Ld) * (e1 - sqrt(max(0, ((e1 * e1) - (2 * abs(Ld))))))))); var cusp = findCusp_9(a_, b_); - var t = clamp(findGamutIntersection_13(a_, b_, L, C, L0, cusp), 0, 1); - var L_clipped = mix(L0, L, t); - var C_clipped = (t * C); + let t = clamp(findGamutIntersection_13(a_, b_, L, C, L0, cusp), 0, 1); + let L_clipped = mix(L0, L, t); + let C_clipped = (t * C); return vec3f(L_clipped, (C_clipped * a_), (C_clipped * b_)); } @@ -226,14 +226,14 @@ describe('oklab example', () => { } @fragment fn mainFragment_3(input: mainFragment_Input_17) -> @location(0) vec4f { - var hue = uniforms_4.hue; + let hue = uniforms_4.hue; var pos = scaleView_6(input.uv); var lab = vec3f(pos.y, (pos.x * vec2f(cos(hue), sin(hue)))); var rgb = oklabToLinearRgb_7(lab); - var outOfGamut = (any((rgb < vec3f())) || any((rgb > vec3f(1)))); + let outOfGamut = (any((rgb < vec3f())) || any((rgb > vec3f(1)))); var clipLab = gamutClipAdaptiveL05_8(lab); var color = oklabToRgb_14(lab); - var patternScaled = ((item_16(input.uv, clipLab) * 0.1) + 0.9); + let patternScaled = ((item_16(input.uv, clipLab) * 0.1) + 0.9); return vec4f(select(color, (patternScaled * color), outOfGamut), 1); }" `); diff --git a/packages/typegpu/tests/examples/individual/perlin-noise.test.ts b/packages/typegpu/tests/examples/individual/perlin-noise.test.ts index 2d8e5a04c8..dce9943319 100644 --- a/packages/typegpu/tests/examples/individual/perlin-noise.test.ts +++ b/packages/typegpu/tests/examples/individual/perlin-noise.test.ts @@ -34,19 +34,19 @@ describe('perlin noise example', () => { } fn item_10() -> f32 { - var a = dot(seed_8, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_8, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_8, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_8, vec2f(54.47856521606445, 345.8415222167969)); seed_8.x = fract((cos(a) * 136.8168)); seed_8.y = fract((cos(b) * 534.7645)); return seed_8.y; } fn randOnUnitSphere_9() -> vec3f { - var z = ((2 * item_10()) - 1); - var oneMinusZSq = sqrt((1 - (z * z))); - var theta = (6.283185307179586 * item_10()); - var x = (cos(theta) * oneMinusZSq); - var y = (sin(theta) * oneMinusZSq); + let z = ((2 * item_10()) - 1); + let oneMinusZSq = sqrt((1 - (z * z))); + let theta = (6.283185307179586 * item_10()); + let x = (cos(theta) * oneMinusZSq); + let y = (sin(theta) * oneMinusZSq); return vec3f(x, y, z); } @@ -57,7 +57,7 @@ describe('perlin noise example', () => { fn mainCompute_2(x: u32, y: u32, z: u32) { let size = (&size_3); - var idx = ((x + (y * (*size).x)) + ((z * (*size).x) * (*size).y)); + let idx = ((x + (y * (*size).x)) + ((z * (*size).x) * (*size).y)); memory_4[idx] = computeJunctionGradient_5(vec3i(i32(x), i32(y), i32(z))); } @@ -98,9 +98,9 @@ describe('perlin noise example', () => { fn getJunctionGradient_8(pos: vec3i) -> vec3f { var size = vec3i(perlin3dCache__size_9.xyz); - var x = (((pos.x % size.x) + size.x) % size.x); - var y = (((pos.y % size.y) + size.y) % size.y); - var z = (((pos.z % size.z) + size.z) % size.z); + let x = (((pos.x % size.x) + size.x) % size.x); + let y = (((pos.y % size.y) + size.y) % size.y); + let z = (((pos.z % size.z) + size.z) % size.z); return perlin3dCache__memory_10[((x + (y * size.x)) + ((z * size.x) * size.y))]; } @@ -116,22 +116,22 @@ describe('perlin noise example', () => { fn sample_6(pos: vec3f) -> f32 { var minJunction = floor(pos); - var xyz = dotProdGrid_7(pos, minJunction); - var xyZ = dotProdGrid_7(pos, (minJunction + vec3f(0, 0, 1))); - var xYz = dotProdGrid_7(pos, (minJunction + vec3f(0, 1, 0))); - var xYZ = dotProdGrid_7(pos, (minJunction + vec3f(0, 1, 1))); - var Xyz = dotProdGrid_7(pos, (minJunction + vec3f(1, 0, 0))); - var XyZ = dotProdGrid_7(pos, (minJunction + vec3f(1, 0, 1))); - var XYz = dotProdGrid_7(pos, (minJunction + vec3f(1, 1, 0))); - var XYZ = dotProdGrid_7(pos, (minJunction + vec3f(1))); + let xyz = dotProdGrid_7(pos, minJunction); + let xyZ = dotProdGrid_7(pos, (minJunction + vec3f(0, 0, 1))); + let xYz = dotProdGrid_7(pos, (minJunction + vec3f(0, 1, 0))); + let xYZ = dotProdGrid_7(pos, (minJunction + vec3f(0, 1, 1))); + let Xyz = dotProdGrid_7(pos, (minJunction + vec3f(1, 0, 0))); + let XyZ = dotProdGrid_7(pos, (minJunction + vec3f(1, 0, 1))); + let XYz = dotProdGrid_7(pos, (minJunction + vec3f(1, 1, 0))); + let XYZ = dotProdGrid_7(pos, (minJunction + vec3f(1))); var partial = (pos - minJunction); var smoothPartial = quinticInterpolationImpl_11(partial); - var xy = mix(xyz, xyZ, smoothPartial.z); - var xY = mix(xYz, xYZ, smoothPartial.z); - var Xy = mix(Xyz, XyZ, smoothPartial.z); - var XY = mix(XYz, XYZ, smoothPartial.z); - var x = mix(xy, xY, smoothPartial.y); - var X = mix(Xy, XY, smoothPartial.y); + let xy = mix(xyz, xyZ, smoothPartial.z); + let xY = mix(xYz, xYZ, smoothPartial.z); + let Xy = mix(Xyz, XyZ, smoothPartial.z); + let XY = mix(XYz, XYZ, smoothPartial.z); + let x = mix(xy, xY, smoothPartial.y); + let X = mix(Xy, XY, smoothPartial.y); return mix(x, X, smoothPartial.x); } @@ -147,9 +147,9 @@ describe('perlin noise example', () => { @fragment fn mainFragment_3(input: mainFragment_Input_14) -> @location(0) vec4f { var uv = (gridSize_4 * input.uv); - var n = sample_6(vec3f(uv, time_5)); - var sharp = exponentialSharpen_12(n, sharpness_13); - var n01 = ((sharp * 0.5) + 0.5); + let n = sample_6(vec3f(uv, time_5)); + let sharp = exponentialSharpen_12(n, sharpness_13); + let n01 = ((sharp * 0.5) + 0.5); var dark = vec3f(0, 0.20000000298023224, 1); var light = vec3f(1, 0.30000001192092896, 0.5); return vec4f(mix(dark, light, n01), 1); diff --git a/packages/typegpu/tests/examples/individual/probability.test.ts b/packages/typegpu/tests/examples/individual/probability.test.ts index 7d74ae3f8e..f0d49d25c7 100644 --- a/packages/typegpu/tests/examples/individual/probability.test.ts +++ b/packages/typegpu/tests/examples/individual/probability.test.ts @@ -33,8 +33,8 @@ describe('probability distribution plot example', () => { } fn item_7() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; @@ -45,13 +45,13 @@ describe('probability distribution plot example', () => { } fn randNormal_8(mu: f32, sigma: f32) -> f32 { - var theta = (6.283185307179586 * randUniformExclusive_9()); - var R = sqrt((-2 * log(randUniformExclusive_9()))); + let theta = (6.283185307179586 * randUniformExclusive_9()); + let R = sqrt((-2 * log(randUniformExclusive_9()))); return (((R * sin(theta)) * sigma) + mu); } fn randInUnitSphere_6() -> vec3f { - var u = item_7(); + let u = item_7(); var v = vec3f(randNormal_8(0, 1), randNormal_8(0, 1), randNormal_8(0, 1)); var vNorm = normalize(v); return (vNorm * pow(u, 0.33)); @@ -62,7 +62,7 @@ describe('probability distribution plot example', () => { } @compute @workgroup_size(64) fn item_0(input: item_10) { - var id = input.gid.x; + let id = input.gid.x; if ((id >= arrayLength(&samplesBuffer_1))) { return; } @@ -85,19 +85,19 @@ describe('probability distribution plot example', () => { } fn item_7() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; } fn randOnUnitSphere_6() -> vec3f { - var z = ((2 * item_7()) - 1); - var oneMinusZSq = sqrt((1 - (z * z))); - var theta = (6.283185307179586 * item_7()); - var x = (cos(theta) * oneMinusZSq); - var y = (sin(theta) * oneMinusZSq); + let z = ((2 * item_7()) - 1); + let oneMinusZSq = sqrt((1 - (z * z))); + let theta = (6.283185307179586 * item_7()); + let x = (cos(theta) * oneMinusZSq); + let y = (sin(theta) * oneMinusZSq); return vec3f(x, y, z); } @@ -106,7 +106,7 @@ describe('probability distribution plot example', () => { } @compute @workgroup_size(64) fn item_0(input: item_8) { - var id = input.gid.x; + let id = input.gid.x; if ((id >= arrayLength(&samplesBuffer_1))) { return; } @@ -129,16 +129,16 @@ describe('probability distribution plot example', () => { } fn item_8() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; } fn randInUnitCircle_7() -> vec2f { - var radius = sqrt(item_8()); - var angle = (item_8() * 6.283185307179586); + let radius = sqrt(item_8()); + let angle = (item_8() * 6.283185307179586); return vec2f((cos(angle) * radius), (sin(angle) * radius)); } @@ -151,7 +151,7 @@ describe('probability distribution plot example', () => { } @compute @workgroup_size(64) fn item_0(input: item_9) { - var id = input.gid.x; + let id = input.gid.x; if ((id >= arrayLength(&samplesBuffer_1))) { return; } @@ -174,15 +174,15 @@ describe('probability distribution plot example', () => { } fn item_8() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; } fn randOnUnitCircle_7() -> vec2f { - var angle = (item_8() * 6.283185307179586); + let angle = (item_8() * 6.283185307179586); return vec2f(cos(angle), sin(angle)); } @@ -195,7 +195,7 @@ describe('probability distribution plot example', () => { } @compute @workgroup_size(64) fn item_0(input: item_9) { - var id = input.gid.x; + let id = input.gid.x; if ((id >= arrayLength(&samplesBuffer_1))) { return; } @@ -218,8 +218,8 @@ describe('probability distribution plot example', () => { } fn item_7() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; @@ -234,7 +234,7 @@ describe('probability distribution plot example', () => { } @compute @workgroup_size(64) fn item_0(input: item_8) { - var id = input.gid.x; + let id = input.gid.x; if ((id >= arrayLength(&samplesBuffer_1))) { return; } @@ -257,16 +257,16 @@ describe('probability distribution plot example', () => { } fn item_7() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; } fn randOnUnitCube_6() -> vec3f { - var face = u32((item_7() * 6)); - var axis = (face % 3); + let face = u32((item_7() * 6)); + let axis = (face % 3); var result = vec3f(); result[axis] = f32(select(0, 1, (face > 2))); result[((axis + 1) % 3)] = item_7(); @@ -279,7 +279,7 @@ describe('probability distribution plot example', () => { } @compute @workgroup_size(64) fn item_0(input: item_8) { - var id = input.gid.x; + let id = input.gid.x; if ((id >= arrayLength(&samplesBuffer_1))) { return; } @@ -302,8 +302,8 @@ describe('probability distribution plot example', () => { } fn item_9() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; @@ -314,13 +314,13 @@ describe('probability distribution plot example', () => { } fn randNormal_10(mu: f32, sigma: f32) -> f32 { - var theta = (6.283185307179586 * randUniformExclusive_11()); - var R = sqrt((-2 * log(randUniformExclusive_11()))); + let theta = (6.283185307179586 * randUniformExclusive_11()); + let R = sqrt((-2 * log(randUniformExclusive_11()))); return (((R * sin(theta)) * sigma) + mu); } fn randInUnitSphere_8() -> vec3f { - var u = item_9(); + let u = item_9(); var v = vec3f(randNormal_10(0, 1), randNormal_10(0, 1), randNormal_10(0, 1)); var vNorm = normalize(v); return (vNorm * pow(u, 0.33)); @@ -328,7 +328,7 @@ describe('probability distribution plot example', () => { fn randInUnitHemisphere_7(normal: vec3f) -> vec3f { var value = randInUnitSphere_8(); - var alignment = dot(normal, value); + let alignment = dot(normal, value); return (sign(alignment) * value); } @@ -341,7 +341,7 @@ describe('probability distribution plot example', () => { } @compute @workgroup_size(64) fn item_0(input: item_12) { - var id = input.gid.x; + let id = input.gid.x; if ((id >= arrayLength(&samplesBuffer_1))) { return; } @@ -364,25 +364,25 @@ describe('probability distribution plot example', () => { } fn item_9() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; } fn randOnUnitSphere_8() -> vec3f { - var z = ((2 * item_9()) - 1); - var oneMinusZSq = sqrt((1 - (z * z))); - var theta = (6.283185307179586 * item_9()); - var x = (cos(theta) * oneMinusZSq); - var y = (sin(theta) * oneMinusZSq); + let z = ((2 * item_9()) - 1); + let oneMinusZSq = sqrt((1 - (z * z))); + let theta = (6.283185307179586 * item_9()); + let x = (cos(theta) * oneMinusZSq); + let y = (sin(theta) * oneMinusZSq); return vec3f(x, y, z); } fn randOnUnitHemisphere_7(normal: vec3f) -> vec3f { var value = randOnUnitSphere_8(); - var alignment = dot(normal, value); + let alignment = dot(normal, value); return (sign(alignment) * value); } @@ -395,7 +395,7 @@ describe('probability distribution plot example', () => { } @compute @workgroup_size(64) fn item_0(input: item_10) { - var id = input.gid.x; + let id = input.gid.x; if ((id >= arrayLength(&samplesBuffer_1))) { return; } @@ -418,15 +418,15 @@ describe('probability distribution plot example', () => { } fn item_8() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; } fn randBernoulli_7(p: f32) -> f32 { - var u = item_8(); + let u = item_8(); return step(u, p); } @@ -439,7 +439,7 @@ describe('probability distribution plot example', () => { } @compute @workgroup_size(64) fn item_0(input: item_9) { - var id = input.gid.x; + let id = input.gid.x; if ((id >= arrayLength(&samplesBuffer_1))) { return; } @@ -462,8 +462,8 @@ describe('probability distribution plot example', () => { } fn item_8() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; @@ -482,7 +482,7 @@ describe('probability distribution plot example', () => { } @compute @workgroup_size(64) fn item_0(input: item_9) { - var id = input.gid.x; + let id = input.gid.x; if ((id >= arrayLength(&samplesBuffer_1))) { return; } @@ -505,8 +505,8 @@ describe('probability distribution plot example', () => { } fn item_9() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; @@ -517,8 +517,8 @@ describe('probability distribution plot example', () => { } fn randExponential_7(rate: f32) -> f32 { - var u = randUniformExclusive_8(); - return ((-1 / rate) * log(u)); + let u = randUniformExclusive_8(); + return ((-1f / rate) * log(u)); } fn prng_6() -> vec3f { @@ -530,7 +530,7 @@ describe('probability distribution plot example', () => { } @compute @workgroup_size(64) fn item_0(input: item_10) { - var id = input.gid.x; + let id = input.gid.x; if ((id >= arrayLength(&samplesBuffer_1))) { return; } @@ -553,8 +553,8 @@ describe('probability distribution plot example', () => { } fn item_9() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; @@ -565,8 +565,8 @@ describe('probability distribution plot example', () => { } fn randNormal_7(mu: f32, sigma: f32) -> f32 { - var theta = (6.283185307179586 * randUniformExclusive_8()); - var R = sqrt((-2 * log(randUniformExclusive_8()))); + let theta = (6.283185307179586 * randUniformExclusive_8()); + let R = sqrt((-2 * log(randUniformExclusive_8()))); return (((R * sin(theta)) * sigma) + mu); } @@ -579,7 +579,7 @@ describe('probability distribution plot example', () => { } @compute @workgroup_size(64) fn item_0(input: item_10) { - var id = input.gid.x; + let id = input.gid.x; if ((id >= arrayLength(&samplesBuffer_1))) { return; } @@ -602,8 +602,8 @@ describe('probability distribution plot example', () => { } fn item_9() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; @@ -614,7 +614,7 @@ describe('probability distribution plot example', () => { } fn randCauchy_7(x0: f32, gamma: f32) -> f32 { - var u = randUniformExclusive_8(); + let u = randUniformExclusive_8(); return (x0 + (gamma * tan((3.141592653589793 * (u - 0.5))))); } @@ -627,7 +627,7 @@ describe('probability distribution plot example', () => { } @compute @workgroup_size(64) fn item_0(input: item_10) { - var id = input.gid.x; + let id = input.gid.x; if ((id >= arrayLength(&samplesBuffer_1))) { return; } diff --git a/packages/typegpu/tests/examples/individual/ray-marching.test.ts b/packages/typegpu/tests/examples/individual/ray-marching.test.ts index 81f29d0587..fef3c9b3c9 100644 --- a/packages/typegpu/tests/examples/individual/ray-marching.test.ts +++ b/packages/typegpu/tests/examples/individual/ray-marching.test.ts @@ -27,7 +27,7 @@ describe('ray-marching example', () => { } @vertex fn vertexMain_0(_arg_0: vertexMain_Input_2) -> vertexMain_Output_1 { - var pos = array(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3)); + var pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); var uv = array(vec2f(), vec2f(2, 0), vec2f(0, 2)); return vertexMain_Output_1(vec4f(pos[_arg_0.idx], 0, 1), uv[_arg_0.idx]); } @@ -46,17 +46,17 @@ describe('ray-marching example', () => { fn sdBoxFrame3d_10(p: vec3f, size: vec3f, thickness: f32) -> f32 { var p1 = (abs(p) - size); var q = (abs((p1 + thickness)) - vec3f(thickness)); - var d1 = (length(max(vec3f(p1.x, q.y, q.z), vec3f())) + min(max(p1.x, max(q.y, q.z)), 0)); - var d2 = (length(max(vec3f(q.x, p1.y, q.z), vec3f())) + min(max(q.x, max(p1.y, q.z)), 0)); - var d3 = (length(max(vec3f(q.x, q.y, p1.z), vec3f())) + min(max(q.x, max(q.y, p1.z)), 0)); + let d1 = (length(max(vec3f(p1.x, q.y, q.z), vec3f())) + min(max(p1.x, max(q.y, q.z)), 0)); + let d2 = (length(max(vec3f(q.x, p1.y, q.z), vec3f())) + min(max(q.x, max(p1.y, q.z)), 0)); + let d3 = (length(max(vec3f(q.x, q.y, p1.z), vec3f())) + min(max(q.x, max(q.y, p1.z)), 0)); return min(min(d1, d2), d3); } fn smoothShapeUnion_11(a: Shape_6, b: Shape_6, k: f32) -> Shape_6 { - var h = (max((k - abs((a.dist - b.dist))), 0) / k); - var m = (h * h); - var dist = (min(a.dist, b.dist) - ((m * k) * 0.25)); - var weight = (m + select(0, (1 - m), (a.dist > b.dist))); + let h = (max((k - abs((a.dist - b.dist))), 0) / k); + let m = (h * h); + let dist = (min(a.dist, b.dist) - ((m * k) * 0.25)); + let weight = (m + select(0, (1 - m), (a.dist > b.dist))); var color = mix(a.color, b.color, weight); return Shape_6(color, dist); } @@ -64,8 +64,8 @@ describe('ray-marching example', () => { fn getMorphingShape_8(p: vec3f, t: f32) -> Shape_6 { var center = vec3f(0, 2, 6); var localP = (p - center); - var rotMatZ = mat4x4f(cos(-t), sin(-t), 0, 0, -sin(-t), cos(-t), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); - var rotMatX = mat4x4f(1, 0, 0, 0, 0, cos((-t * 0.6)), sin((-t * 0.6)), 0, 0, -sin((-t * 0.6)), cos((-t * 0.6)), 0, 0, 0, 0, 1); + var rotMatZ = mat4x4f(cos(-(t)), sin(-(t)), 0, 0, -sin(-(t)), cos(-(t)), 0, 0, 0, 0, 1, 0, 0, 0, 0, 1); + var rotMatX = mat4x4f(1, 0, 0, 0, 0, cos((-(t) * 0.6)), sin((-(t) * 0.6)), 0, 0, -sin((-(t) * 0.6)), cos((-(t) * 0.6)), 0, 0, 0, 0, 1); var rotatedP = (rotMatZ * (rotMatX * vec4f(localP, 1))).xyz; var boxSize = vec3f(0.699999988079071); var sphere1Offset = vec3f((cos((t * 2)) * 0.8), (sin((t * 3)) * 0.3), (sin((t * 2)) * 0.8)); @@ -115,7 +115,7 @@ describe('ray-marching example', () => { } fn getNormal_16(p: vec3f) -> vec3f { - var dist = getSceneDist_7(p).dist; + let dist = getSceneDist_7(p).dist; const e = 0.01; var n = vec3f((getSceneDist_7((p + vec3f(e, 0, 0))).dist - dist), (getSceneDist_7((p + vec3f(0, e, 0))).dist - dist), (getSceneDist_7((p + vec3f(0, 0, e))).dist - dist)); return normalize(n); @@ -135,7 +135,7 @@ describe('ray-marching example', () => { if ((t >= maxT)) { break; } - var h = getSceneDist_7((ro + (rd * t))).dist; + let h = getSceneDist_7((ro + (rd * t))).dist; if ((h < 1e-3)) { return 0; } @@ -155,16 +155,16 @@ describe('ray-marching example', () => { var ro = vec3f(0, 2, 3); var rd = normalize(vec3f(uv.x, uv.y, 1)); var march = rayMarch_5(ro, rd); - var fog = pow(min((march.dist / 30f), 1), 0.7); + let fog = pow(min((march.dist / 30f), 1), 0.7); var p = (ro + (rd * march.dist)); var n = getNormal_16(p); var lightPos = getOrbitingLightPos_17(time_12); var l = normalize((lightPos - p)); - var diff = max(dot(n, l), 0); + let diff = max(dot(n, l), 0); let shadowRo = (&p); let shadowRd = (&l); - var shadowDist = length((lightPos - p)); - var shadow = softShadow_18((*shadowRo), (*shadowRd), 0.1, shadowDist, 16); + let shadowDist = length((lightPos - p)); + let shadow = softShadow_18((*shadowRo), (*shadowRd), 0.1, shadowDist, 16); var litColor = (march.color * diff); var finalColor = mix((litColor * 0.5), litColor, shadow); return mix(vec4f(finalColor, 1), vec4f(0.699999988079071, 0.800000011920929, 0.8999999761581421, 1), fog); diff --git a/packages/typegpu/tests/examples/individual/simple-shadow.test.ts b/packages/typegpu/tests/examples/individual/simple-shadow.test.ts index 4306d166bc..d90cbead7a 100644 --- a/packages/typegpu/tests/examples/individual/simple-shadow.test.ts +++ b/packages/typegpu/tests/examples/individual/simple-shadow.test.ts @@ -134,16 +134,16 @@ describe('simple shadow example', () => { var ndc = (lp4.xyz / lp4.w); var uv = ((ndc.xy * 0.5) + 0.5); uv = vec2f(uv.x, (1 - uv.y)); - var currentDepth = ndc.z; - var inBounds = (all((uv >= vec2f())) && all((uv <= vec2f(1)))); + let currentDepth = ndc.z; + let inBounds = (all((uv >= vec2f())) && all((uv <= vec2f(1)))); var shadowFactor = textureSampleCompare(shadowMap_13, comparisonSampler_14, uv, currentDepth); if (!inBounds) { shadowFactor = 1; } var ambient = ((*instanceInfo).material.ambient * light_9.color); - var diff = max(0, dot(N, L)); + let diff = max(0, dot(N, L)); var diffuse = (((*instanceInfo).material.diffuse * light_9.color) * diff); - var spec = pow(max(0, dot(V, R)), (*instanceInfo).material.shininess); + let spec = pow(max(0, dot(V, R)), (*instanceInfo).material.shininess); var specular = (((*instanceInfo).material.specular * light_9.color) * spec); var lit = ((diffuse + specular) * shadowFactor); var finalColor = (ambient + lit); @@ -151,7 +151,7 @@ describe('simple shadow example', () => { return vec4f(vec3f(shadowFactor), 1); } if ((paramsUniform_15.lightDepth == 1)) { - var remappedDepth = clamp(((currentDepth - 0.2) / 0.49999999999999994f), 0, 1); + let remappedDepth = clamp(((currentDepth - 0.2) / 0.49999999999999994f), 0, 1); return vec4f(vec3f(remappedDepth), 1); } return vec4f(finalColor, 1); diff --git a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts index f14d64c819..7228618dc1 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold-3d.test.ts @@ -30,8 +30,8 @@ describe('slime mold 3d example', () => { } fn item_7() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; @@ -42,13 +42,13 @@ describe('slime mold 3d example', () => { } fn randNormal_8(mu: f32, sigma: f32) -> f32 { - var theta = (6.283185307179586 * randUniformExclusive_9()); - var R = sqrt((-2 * log(randUniformExclusive_9()))); + let theta = (6.283185307179586 * randUniformExclusive_9()); + let R = sqrt((-2 * log(randUniformExclusive_9()))); return (((R * sin(theta)) * sigma) + mu); } fn randInUnitSphere_6() -> vec3f { - var u = item_7(); + let u = item_7(); var v = vec3f(randNormal_8(0, 1), randNormal_8(0, 1), randNormal_8(0, 1)); var vNorm = normalize(v); return (vNorm * pow(u, 0.33)); @@ -112,15 +112,15 @@ describe('slime mold 3d example', () => { var samplePos = (vec3i(_arg_0.gid.xyz) + vec3i(offsetX, offsetY, offsetZ)); var dimsi = vec3i(dims); if (((((((samplePos.x >= 0) && (samplePos.x < dimsi.x)) && (samplePos.y >= 0)) && (samplePos.y < dimsi.y)) && (samplePos.z >= 0)) && (samplePos.z < dimsi.z))) { - var value = textureLoad(oldState_1, vec3u(samplePos)).x; + let value = textureLoad(oldState_1, vec3u(samplePos)).x; sum = (sum + value); count = (count + 1); } } } } - var blurred = (sum / count); - var newValue = saturate((blurred - params_2.evaporationRate)); + let blurred = (sum / count); + let newValue = saturate((blurred - params_2.evaporationRate)); textureStore(newState_4, _arg_0.gid.xyz, vec4f(newValue, 0, 0, 1)); } @@ -144,8 +144,8 @@ describe('slime mold 3d example', () => { @group(0) @binding(0) var agentsData_5: array; fn item_8() -> f32 { - var a = dot(seed_3, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_3, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_3, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_3, vec2f(54.47856521606445, 345.8415222167969)); seed_3.x = fract((cos(a) * 136.8168)); seed_3.y = fract((cos(b) * 534.7645)); return seed_3.y; @@ -157,9 +157,9 @@ describe('slime mold 3d example', () => { fn getPerpendicular_10(dir: vec3f) -> vec3f { var axis = vec3f(1, 0, 0); - var absX = abs(dir.x); - var absY = abs(dir.y); - var absZ = abs(dir.z); + let absX = abs(dir.x); + let absY = abs(dir.y); + let absZ = abs(dir.z); if (((absY <= absX) && (absY <= absZ))) { axis = vec3f(0, 1, 0); } @@ -196,12 +196,12 @@ describe('slime mold 3d example', () => { var perp2 = cross(direction, perp1); const numSamples = 8; for (var i = 0; (i < numSamples); i++) { - var theta = (((f32(i) / f32(numSamples)) * 2) * 3.141592653589793); + let theta = (((f32(i) / f32(numSamples)) * 2) * 3.141592653589793); var coneOffset = ((perp1 * cos(theta)) + (perp2 * sin(theta))); var sensorDir = normalize((direction + (coneOffset * sin(params_11.sensorAngle)))); var sensorPos = (pos + (sensorDir * params_11.sensorDistance)); var sensorPosInt = vec3u(clamp(sensorPos, vec3f(), (dimsf - vec3f(1)))); - var weight = textureLoad(oldState_4, sensorPosInt).x; + let weight = textureLoad(oldState_4, sensorPosInt).x; weightedDir = (weightedDir + (sensorDir * weight)); totalWeight = (totalWeight + weight); } @@ -213,13 +213,13 @@ describe('slime mold 3d example', () => { } fn randNormal_16(mu: f32, sigma: f32) -> f32 { - var theta = (6.283185307179586 * randUniformExclusive_17()); - var R = sqrt((-2 * log(randUniformExclusive_17()))); + let theta = (6.283185307179586 * randUniformExclusive_17()); + let R = sqrt((-2 * log(randUniformExclusive_17()))); return (((R * sin(theta)) * sigma) + mu); } fn randInUnitSphere_15() -> vec3f { - var u = item_8(); + let u = item_8(); var v = vec3f(randNormal_16(0, 1), randNormal_16(0, 1), randNormal_16(0, 1)); var vNorm = normalize(v); return (vNorm * pow(u, 0.33)); @@ -227,7 +227,7 @@ describe('slime mold 3d example', () => { fn randInUnitHemisphere_14(normal: vec3f) -> vec3f { var value = randInUnitSphere_15(); - var alignment = dot(normal, value); + let alignment = dot(normal, value); return (sign(alignment) * value); } @@ -245,7 +245,7 @@ describe('slime mold 3d example', () => { var dims = textureDimensions(oldState_4); var dimsf = vec3f(dims); let agent = (&agentsData_5[_arg_0.gid.x]); - var random = randFloat01_7(); + let random = randFloat01_7(); var direction = normalize((*agent).direction); var senseResult = sense3D_9((*agent).position, direction); if ((senseResult.totalWeight > 0.01)) { @@ -290,8 +290,8 @@ describe('slime mold 3d example', () => { direction = normalize(((randomDir * 0.3) + (toCenter * 0.7))); } agentsData_5[_arg_0.gid.x] = Agent_6(newPos, direction); - var oldState = textureLoad(oldState_4, vec3u(newPos)).x; - var newState = (oldState + 1); + let oldState = textureLoad(oldState_4, vec3u(newPos)).x; + let newState = (oldState + 1); textureStore(newState_18, vec3u(newPos), vec4f(newState, 0, 0, 1)); } @@ -331,9 +331,9 @@ describe('slime mold 3d example', () => { var t1 = ((boxMax - rayOrigin) * invDir); var tmin = min(t0, t1); var tmax = max(t0, t1); - var tNear = max(max(tmin.x, tmin.y), tmin.z); - var tFar = min(min(tmax.x, tmax.y), tmax.z); - var hit = ((tFar >= tNear) && (tFar >= 0)); + let tNear = max(max(tmin.x, tmin.y), tmin.z); + let tFar = min(min(tmax.x, tmax.y), tmax.z); + let hit = ((tFar >= tNear) && (tFar >= 0)); return RayBoxResult_7(tNear, tFar, hit); } @@ -360,10 +360,10 @@ describe('slime mold 3d example', () => { if (!isect.hit) { return vec4f(); } - var tStart = max(isect.tNear, 0); - var tEnd = isect.tFar; + let tStart = max(isect.tNear, 0); + let tEnd = isect.tFar; const numSteps = 128; - var stepSize = ((tEnd - tStart) / f32(numSteps)); + let stepSize = ((tEnd - tStart) / f32(numSteps)); const thresholdLo = 0.05999999865889549f; const thresholdHi = 0.25f; const gamma = 1.399999976158142f; @@ -376,18 +376,18 @@ describe('slime mold 3d example', () => { if ((transmittance <= TMin)) { break; } - var t = (tStart + ((f32(i) + 0.5) * stepSize)); + let t = (tStart + ((f32(i) + 0.5) * stepSize)); var pos = (rayOrigin + (rayDir * t)); var texCoord = (pos / vec3f(256)); - var sampleValue = textureSampleLevel(state_8, sampler_9, texCoord, 0).x; - var d0 = smoothstep(thresholdLo, thresholdHi, sampleValue); - var density = pow(d0, gamma); - var alphaSrc = (1 - exp(((-sigmaT * density) * stepSize))); + let sampleValue = textureSampleLevel(state_8, sampler_9, texCoord, 0).x; + let d0 = smoothstep(thresholdLo, thresholdHi, sampleValue); + let density = pow(d0, gamma); + let alphaSrc = (1 - exp(((-(sigmaT) * density) * stepSize))); var contrib = (albedo * alphaSrc); accum = (accum + (contrib * transmittance)); transmittance = (transmittance * (1 - alphaSrc)); } - var alpha = (1 - transmittance); + let alpha = (1 - transmittance); return vec4f(accum, alpha); }" `); diff --git a/packages/typegpu/tests/examples/individual/slime-mold.test.ts b/packages/typegpu/tests/examples/individual/slime-mold.test.ts index f2bbe84f03..dd3e52bcf8 100644 --- a/packages/typegpu/tests/examples/individual/slime-mold.test.ts +++ b/packages/typegpu/tests/examples/individual/slime-mold.test.ts @@ -30,16 +30,16 @@ describe('slime mold example', () => { } fn item_7() -> f32 { - var a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_5, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_5, vec2f(54.47856521606445, 345.8415222167969)); seed_5.x = fract((cos(a) * 136.8168)); seed_5.y = fract((cos(b) * 534.7645)); return seed_5.y; } fn randInUnitCircle_6() -> vec2f { - var radius = sqrt(item_7()); - var angle = (item_7() * 6.283185307179586); + let radius = sqrt(item_7()); + let angle = (item_7() * 6.283185307179586); return vec2f((cos(angle) * radius), (sin(angle) * radius)); } @@ -53,7 +53,7 @@ describe('slime mold example', () => { fn wrappedCallback_2(x: u32, _arg_1: u32, _arg_2: u32) { randSeed_3(((f32(x) / 2e+5f) + 0.1)); var pos = ((randInUnitCircle_6() * 140) + vec2f(150, 75)); - var angle = atan2((75 - pos.y), (150 - pos.x)); + let angle = atan2((75 - pos.y), (150 - pos.x)); agentsData_8[x] = Agent_9(pos, angle); } @@ -129,8 +129,8 @@ describe('slime mold example', () => { @group(0) @binding(0) var agentsData_5: array; fn item_8() -> f32 { - var a = dot(seed_3, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_3, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_3, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_3, vec2f(54.47856521606445, 345.8415222167969)); seed_3.x = fract((cos(a) * 136.8168)); seed_3.y = fract((cos(b) * 534.7645)); return seed_3.y; @@ -151,7 +151,7 @@ describe('slime mold example', () => { @group(0) @binding(1) var params_10: Params_11; fn sense_9(pos: vec2f, angle: f32, sensorAngleOffset: f32) -> f32 { - var sensorAngle = (angle + sensorAngleOffset); + let sensorAngle = (angle + sensorAngleOffset); var sensorDir = vec2f(cos(sensorAngle), sin(sensorAngle)); var sensorPos = (pos + (sensorDir * params_10.sensorDistance)); var dims = textureDimensions(oldState_4); @@ -176,10 +176,10 @@ describe('slime mold example', () => { randSeed_1(((f32(_arg_0.gid.x) / 2e+5f) + 0.1)); var dims = textureDimensions(oldState_4); let agent = (&agentsData_5[_arg_0.gid.x]); - var random = randFloat01_7(); - var weightForward = sense_9((*agent).position, (*agent).angle, 0); - var weightLeft = sense_9((*agent).position, (*agent).angle, params_10.sensorAngle); - var weightRight = sense_9((*agent).position, (*agent).angle, -params_10.sensorAngle); + let random = randFloat01_7(); + let weightForward = sense_9((*agent).position, (*agent).angle, 0); + let weightLeft = sense_9((*agent).position, (*agent).angle, params_10.sensorAngle); + let weightRight = sense_9((*agent).position, (*agent).angle, -(params_10.sensorAngle)); var angle = (*agent).angle; if (((weightForward > weightLeft) && (weightForward > weightRight))) { @@ -208,7 +208,7 @@ describe('slime mold example', () => { angle = (3.141592653589793 - angle); } if (((newPos.y <= 0) || (newPos.y >= (dimsf.y - 1)))) { - angle = -angle; + angle = -(angle); } angle += ((random - 0.5) * 0.1); } @@ -228,7 +228,7 @@ describe('slime mold example', () => { } @vertex fn fullScreenTriangle_0(input: fullScreenTriangle_Input_2) -> fullScreenTriangle_Output_1 { - var pos = array(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3)); + var pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); var uv = array(vec2f(0, 1), vec2f(2, 1), vec2f(0, -1)); return fullScreenTriangle_Output_1(vec4f(pos[input.vertexIndex], 0, 1), uv[input.vertexIndex]); } diff --git a/packages/typegpu/tests/examples/individual/square.test.ts b/packages/typegpu/tests/examples/individual/square.test.ts index f6a3e88edc..e2b509e83d 100644 --- a/packages/typegpu/tests/examples/individual/square.test.ts +++ b/packages/typegpu/tests/examples/individual/square.test.ts @@ -28,7 +28,7 @@ describe('square example', () => { } @vertex fn vertex_0(_arg_0: vertex_Input_2) -> vertex_Output_1 { - var vertices = array(vec2f(-1, -1), vec2f(1, -1), vec2f(1), vec2f(-1, 1)); + var vertices = array(vec2f(-1), vec2f(1, -1), vec2f(1), vec2f(-1, 1)); return vertex_Output_1(_arg_0.color, vec4f(vertices[_arg_0.idx], 0, 1)); } diff --git a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts index fbebf8820f..1d247cebc0 100644 --- a/packages/typegpu/tests/examples/individual/stable-fluid.test.ts +++ b/packages/typegpu/tests/examples/individual/stable-fluid.test.ts @@ -44,7 +44,7 @@ describe('stable-fluid example', () => { return; } var velocity = textureLoad(src_1, pixelPos, 0); - var timeStep = simParams_3.dt; + let timeStep = simParams_3.dt; var prevPos = (vec2f(pixelPos) - (timeStep * velocity.xy)); var clampedPos = clamp(prevPos, vec2f(-0.5), (vec2f(texSize.xy) - 0.5)); var normalizedPos = ((clampedPos + 0.5) / vec2f(texSize.xy)); @@ -84,10 +84,10 @@ describe('stable-fluid example', () => { var upVal = textureLoad(in_1, neighbors[1], 0); var rightVal = textureLoad(in_1, neighbors[2], 0); var downVal = textureLoad(in_1, neighbors[3], 0); - var timeStep = simParams_3.dt; - var viscosity = simParams_3.viscosity; - var diffuseRate = (viscosity * timeStep); - var blendFactor = (1f / (4 + diffuseRate)); + let timeStep = simParams_3.dt; + let viscosity = simParams_3.viscosity; + let diffuseRate = (viscosity * timeStep); + let blendFactor = (1f / (4 + diffuseRate)); var diffusedVal = (vec4f(blendFactor) * (((leftVal + rightVal) + (upVal + downVal)) + (diffuseRate * centerVal))); textureStore(out_5, pixelPos, diffusedVal); } @@ -116,7 +116,7 @@ describe('stable-fluid example', () => { var upVel = textureLoad(vel_1, neighbors[1], 0); var rightVel = textureLoad(vel_1, neighbors[2], 0); var downVel = textureLoad(vel_1, neighbors[3], 0); - var divergence = (0.5 * ((rightVel.x - leftVel.x) + (downVel.y - upVel.y))); + let divergence = (0.5 * ((rightVel.x - leftVel.x) + (downVel.y - upVel.y))); textureStore(div_3, pixelPos, vec4f(divergence, 0, 0, 1)); } @@ -146,8 +146,8 @@ describe('stable-fluid example', () => { var upPressure = textureLoad(x_1, neighbors[1], 0); var rightPressure = textureLoad(x_1, neighbors[2], 0); var downPressure = textureLoad(x_1, neighbors[3], 0); - var divergence = textureLoad(b_3, pixelPos, 0).x; - var newPressure = (0.25 * ((((leftPressure.x + rightPressure.x) + upPressure.x) + downPressure.x) - divergence)); + let divergence = textureLoad(b_3, pixelPos, 0).x; + let newPressure = (0.25 * ((((leftPressure.x + rightPressure.x) + upPressure.x) + downPressure.x) - divergence)); textureStore(out_4, pixelPos, vec4f(newPressure, 0, 0, 1)); } @@ -206,7 +206,7 @@ describe('stable-fluid example', () => { var texSize = textureDimensions(src_1); var pixelPos = input.gid.xy; var velocity = textureLoad(vel_2, pixelPos, 0).xy; - var timeStep = simParams_3.dt; + let timeStep = simParams_3.dt; var prevPos = (vec2f(pixelPos) - (timeStep * velocity)); var clampedPos = clamp(prevPos, vec2f(-0.5), (vec2f(texSize.xy) - vec2f(0.5))); var normalizedPos = ((clampedPos + vec2f(0.5)) / vec2f(texSize.xy)); @@ -224,7 +224,7 @@ describe('stable-fluid example', () => { } @vertex fn renderFn_0(input: renderFn_Input_2) -> renderFn_Output_1 { - var vertices = array(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3)); + var vertices = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); var texCoords = array(vec2f(), vec2f(2, 0), vec2f(0, 2)); return renderFn_Output_1(vec4f(vertices[input.idx], 0, 1), texCoords[input.idx]); } @@ -241,15 +241,15 @@ describe('stable-fluid example', () => { @fragment fn fragmentImageFn_3(input: fragmentImageFn_Input_7) -> @location(0) vec4f { const pixelStep = 0.001953125f; - var leftSample = textureSample(result_4, linSampler_5, vec2f((input.uv.x - pixelStep), input.uv.y)).x; - var rightSample = textureSample(result_4, linSampler_5, vec2f((input.uv.x + pixelStep), input.uv.y)).x; - var upSample = textureSample(result_4, linSampler_5, vec2f(input.uv.x, (input.uv.y + pixelStep))).x; - var downSample = textureSample(result_4, linSampler_5, vec2f(input.uv.x, (input.uv.y - pixelStep))).x; - var gradientX = (rightSample - leftSample); - var gradientY = (upSample - downSample); + let leftSample = textureSample(result_4, linSampler_5, vec2f((input.uv.x - pixelStep), input.uv.y)).x; + let rightSample = textureSample(result_4, linSampler_5, vec2f((input.uv.x + pixelStep), input.uv.y)).x; + let upSample = textureSample(result_4, linSampler_5, vec2f(input.uv.x, (input.uv.y + pixelStep))).x; + let downSample = textureSample(result_4, linSampler_5, vec2f(input.uv.x, (input.uv.y - pixelStep))).x; + let gradientX = (rightSample - leftSample); + let gradientY = (upSample - downSample); const distortStrength = 0.8; var distortVector = vec2f(gradientX, gradientY); - var distortedUV = (input.uv + (distortVector * vec2f(distortStrength, -distortStrength))); + var distortedUV = (input.uv + (distortVector * vec2f(distortStrength, -(distortStrength)))); var outputColor = textureSample(background_6, linSampler_5, vec2f(distortedUV.x, (1 - distortedUV.y))); return vec4f(outputColor.xyz, 1); }" diff --git a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts index 43cc56187f..142a9ca78a 100644 --- a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts +++ b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts @@ -47,10 +47,10 @@ describe('tgsl parsing test example', () => { s = (s && true); s = (s && !false); s = (s && true); - s = (s && all((vec3f(1, -1, 0) < vec3f(1, 1, -1)) == vec3(false, true, false))); - s = (s && all((vec3f(1, -1, 0) <= vec3f(1, 1, -1)) == vec3(true, true, false))); - s = (s && all((vec3f(1, -1, 0) > vec3f(1, 1, -1)) == vec3(false, false, true))); - s = (s && all((vec3f(1, -1, 0) >= vec3f(1, 1, -1)) == vec3(true, false, true))); + s = (s && true); + s = (s && true); + s = (s && true); + s = (s && true); s = (s && true); s = (s && true); s = (s && true); @@ -58,8 +58,8 @@ describe('tgsl parsing test example', () => { s = (s && !false); s = (s && true); s = (s && !false); - s = (s && all(select(vec2i(-1, -2), vec2i(1, 2), true) == vec2i(1, 2))); - s = (s && all(select(vec4i(-1, -2, -3, -4), vec4i(1, 2, 3, 4), vec4(true, true, false, false)) == vec4i(1, 2, -3, -4))); + s = (s && true); + s = (s && true); var vec = vec3(true, false, true); s = (s && all(!(vec) == negate_2(vec))); var inputStruct = Schema_3(vec2(false, true), vec4(false, true, false, true), vec3(true, true, false), true); @@ -104,7 +104,7 @@ describe('tgsl parsing test example', () => { s = (s && true); s = (s && all(abs(((mat2x2f(1, 2, 3, 4) * 2) * vec2f(1, 10)) - vec2f(62, 84)) <= (((mat2x2f(1, 2, 3, 4) * 2) * vec2f(1, 10)) - ((mat2x2f(1, 2, 3, 4) * 2) * vec2f(1, 10))) + 0.01)); s = (s && all(abs(((vec2f(1, 10) * mat2x2f(1, 2, 3, 4)) * -1) - vec2f(-21, -43)) <= (((vec2f(1, 10) * mat2x2f(1, 2, 3, 4)) * -1) - ((vec2f(1, 10) * mat2x2f(1, 2, 3, 4)) * -1)) + 0.01)); - s = (s && all(abs(((vec2f(1, 10) * -1) * mat2x2f(1, 2, 3, 4)) - vec2f(-21, -43)) <= (((vec2f(1, 10) * -1) * mat2x2f(1, 2, 3, 4)) - ((vec2f(1, 10) * -1) * mat2x2f(1, 2, 3, 4))) + 0.01)); + s = (s && all(abs((vec2f(-1, -10) * mat2x2f(1, 2, 3, 4)) - vec2f(-21, -43)) <= ((vec2f(-1, -10) * mat2x2f(1, 2, 3, 4)) - (vec2f(-1, -10) * mat2x2f(1, 2, 3, 4))) + 0.01)); s = (s && all((((((vec3f(1, 10, 100) * mat3x3f(0.5, 0, 0, 0, 0.5, 0, 0, 0, 0.5)) * -1) * mat3x3f(1, 2, 3, 4, 5, 6, 7, 8, 9)) * -1) * mat3x3f(2, 0, 0, 0, 2, 0, 0, 0, 2)) == vec3f(321, 654, 987))); s = (s && all((getVec_7() * getVec_7()) == vec3f(1, 4, 9))); s = (s && true); diff --git a/packages/typegpu/tests/examples/individual/vaporrave.test.ts b/packages/typegpu/tests/examples/individual/vaporrave.test.ts index 74fb275d74..81287bdc0d 100644 --- a/packages/typegpu/tests/examples/individual/vaporrave.test.ts +++ b/packages/typegpu/tests/examples/individual/vaporrave.test.ts @@ -32,19 +32,19 @@ describe('vaporrave example', () => { } fn item_9() -> f32 { - var a = dot(seed_7, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_7, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_7, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_7, vec2f(54.47856521606445, 345.8415222167969)); seed_7.x = fract((cos(a) * 136.8168)); seed_7.y = fract((cos(b) * 534.7645)); return seed_7.y; } fn randOnUnitSphere_8() -> vec3f { - var z = ((2 * item_9()) - 1); - var oneMinusZSq = sqrt((1 - (z * z))); - var theta = (6.283185307179586 * item_9()); - var x = (cos(theta) * oneMinusZSq); - var y = (sin(theta) * oneMinusZSq); + let z = ((2 * item_9()) - 1); + let oneMinusZSq = sqrt((1 - (z * z))); + let theta = (6.283185307179586 * item_9()); + let x = (cos(theta) * oneMinusZSq); + let y = (sin(theta) * oneMinusZSq); return vec3f(x, y, z); } @@ -54,7 +54,7 @@ describe('vaporrave example', () => { } fn wrappedCallback_2(x: u32, y: u32, z: u32) { - var idx = ((x + (y * 7)) + ((z * 7) * 7)); + let idx = ((x + (y * 7)) + ((z * 7) * 7)); memoryBuffer_3[idx] = computeJunctionGradient_4(vec3i(i32(x), i32(y), i32(z))); } @@ -79,7 +79,7 @@ describe('vaporrave example', () => { } @vertex fn vertexMain_0(_arg_0: vertexMain_Input_2) -> vertexMain_Output_1 { - var pos = array(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3)); + var pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); var uv = array(vec2f(), vec2f(2, 0), vec2f(0, 2)); return vertexMain_Output_1(vec4f(pos[_arg_0.idx], 0, 1), uv[_arg_0.idx]); } @@ -92,14 +92,14 @@ describe('vaporrave example', () => { } fn rotateXY_9(angle: f32) -> mat2x2f { - return mat2x2f(vec2f(cos(angle), sin(angle)), vec2f(-sin(angle), cos(angle))); + return mat2x2f(vec2f(cos(angle), sin(angle)), vec2f(-(sin(angle)), cos(angle))); } fn circles_8(uv: vec2f, angle: f32) -> vec3f { var uvRotated = (rotateXY_9(angle) * vec2f(uv.x, (uv.y - 12))); var uvNormalized = fract((vec2f(uvRotated.x, uvRotated.y) / 1.2)); var diff2 = pow((vec2f(0.5) - uvNormalized), vec2f(2)); - var distO = pow((diff2.x + diff2.y), 0.5); + let distO = pow((diff2.x + diff2.y), 0.5); return mix(vec3f(), vec3f(0.9200000166893005, 0.20999999344348907, 0.9599999785423279), exp((-5 * distO))); } @@ -110,11 +110,11 @@ describe('vaporrave example', () => { } fn rotateAroundZ_13(angle: f32) -> mat3x3f { - return mat3x3f(vec3f(cos(angle), sin(angle), 0), vec3f(-sin(angle), cos(angle), 0), vec3f(0, 0, 1)); + return mat3x3f(vec3f(cos(angle), sin(angle), 0), vec3f(-(sin(angle)), cos(angle), 0), vec3f(0, 0, 1)); } fn rotateAroundX_14(angle: f32) -> mat3x3f { - return mat3x3f(vec3f(1, 0, 0), vec3f(0, cos(angle), sin(angle)), vec3f(0, -sin(angle), cos(angle))); + return mat3x3f(vec3f(1, 0, 0), vec3f(0, cos(angle), sin(angle)), vec3f(0, -(sin(angle)), cos(angle))); } fn sdSphere_15(p: vec3f, radius: f32) -> f32 { @@ -125,9 +125,9 @@ describe('vaporrave example', () => { fn getJunctionGradient_18(pos: vec3i) -> vec3f { var size_i = vec3i(7); - var x = (((pos.x % size_i.x) + size_i.x) % size_i.x); - var y = (((pos.y % size_i.y) + size_i.y) % size_i.y); - var z = (((pos.z % size_i.z) + size_i.z) % size_i.z); + let x = (((pos.x % size_i.x) + size_i.x) % size_i.x); + let y = (((pos.y % size_i.y) + size_i.y) % size_i.y); + let z = (((pos.z % size_i.z) + size_i.z) % size_i.z); return memoryBuffer_19[((x + (y * size_i.x)) + ((z * size_i.x) * size_i.y))]; } @@ -143,32 +143,32 @@ describe('vaporrave example', () => { fn sample_16(pos: vec3f) -> f32 { var minJunction = floor(pos); - var xyz = dotProdGrid_17(pos, minJunction); - var xyZ = dotProdGrid_17(pos, (minJunction + vec3f(0, 0, 1))); - var xYz = dotProdGrid_17(pos, (minJunction + vec3f(0, 1, 0))); - var xYZ = dotProdGrid_17(pos, (minJunction + vec3f(0, 1, 1))); - var Xyz = dotProdGrid_17(pos, (minJunction + vec3f(1, 0, 0))); - var XyZ = dotProdGrid_17(pos, (minJunction + vec3f(1, 0, 1))); - var XYz = dotProdGrid_17(pos, (minJunction + vec3f(1, 1, 0))); - var XYZ = dotProdGrid_17(pos, (minJunction + vec3f(1))); + let xyz = dotProdGrid_17(pos, minJunction); + let xyZ = dotProdGrid_17(pos, (minJunction + vec3f(0, 0, 1))); + let xYz = dotProdGrid_17(pos, (minJunction + vec3f(0, 1, 0))); + let xYZ = dotProdGrid_17(pos, (minJunction + vec3f(0, 1, 1))); + let Xyz = dotProdGrid_17(pos, (minJunction + vec3f(1, 0, 0))); + let XyZ = dotProdGrid_17(pos, (minJunction + vec3f(1, 0, 1))); + let XYz = dotProdGrid_17(pos, (minJunction + vec3f(1, 1, 0))); + let XYZ = dotProdGrid_17(pos, (minJunction + vec3f(1))); var partial = (pos - minJunction); var smoothPartial = quinticInterpolationImpl_20(partial); - var xy = mix(xyz, xyZ, smoothPartial.z); - var xY = mix(xYz, xYZ, smoothPartial.z); - var Xy = mix(Xyz, XyZ, smoothPartial.z); - var XY = mix(XYz, XYZ, smoothPartial.z); - var x = mix(xy, xY, smoothPartial.y); - var X = mix(Xy, XY, smoothPartial.y); + let xy = mix(xyz, xyZ, smoothPartial.z); + let xY = mix(xYz, xYZ, smoothPartial.z); + let Xy = mix(Xyz, XyZ, smoothPartial.z); + let XY = mix(XYz, XYZ, smoothPartial.z); + let x = mix(xy, xY, smoothPartial.y); + let X = mix(Xy, XY, smoothPartial.y); return mix(x, X, smoothPartial.x); } fn getSphere_12(p: vec3f, sphereColor: vec3f, sphereCenter: vec3f, angle: f32) -> Ray_6 { var localP = (p - sphereCenter); - var rotMatZ = rotateAroundZ_13((-angle * 0.3)); - var rotMatX = rotateAroundX_14((-angle * 0.7)); + var rotMatZ = rotateAroundZ_13((-(angle) * 0.3)); + var rotMatX = rotateAroundX_14((-(angle) * 0.7)); var rotatedP = ((localP * rotMatZ) * rotMatX); - var radius = (3 + sin(angle)); - var rawDist = sdSphere_15(rotatedP, radius); + let radius = (3 + sin(angle)); + let rawDist = sdSphere_15(rotatedP, radius); var noise = 0f; if ((rawDist < 1)) { noise += sample_16((rotatedP + angle)); @@ -203,7 +203,7 @@ describe('vaporrave example', () => { var p = ((rd * distOrigin) + ro); var scene = getSceneRay_7(p); var sphereDist = getSphere_12(p, sphereColorUniform_21, vec3f(0, 6, 12), sphereAngleUniform_22); - glow = ((sphereColorUniform_21 * exp(-sphereDist.dist)) + glow); + glow = ((sphereColorUniform_21 * exp(-(sphereDist.dist))) + glow); distOrigin += scene.dist; if ((distOrigin > 19)) { result.dist = 19; @@ -230,9 +230,9 @@ describe('vaporrave example', () => { var ro = vec3f(0, 2, -1); var rd = normalize(vec3f(uv.x, uv.y, 1)); var march = rayMarch_5(ro, rd); - var y = (((rd * march.ray.dist) + ro).y - 2); + let y = (((rd * march.ray.dist) + ro).y - 2); var sky = mix(vec4f(0.10000000149011612, 0, 0.20000000298023224, 1), vec4f(0.2800000011920929, 0, 0.5400000214576721, 1), (y / 19f)); - var fog = min((march.ray.dist / 19f), 1); + let fog = min((march.ray.dist / 19f), 1); return mix(mix(vec4f(march.ray.color, 1), sky, fog), vec4f(march.glow, 1), glowIntensityUniform_25); }" `); diff --git a/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts b/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts index 50c30f420b..b6aa4341e1 100644 --- a/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts +++ b/packages/typegpu/tests/examples/individual/wgsl-resolution.test.ts @@ -19,12 +19,12 @@ describe('wgsl resolution example', () => { expect(wgslElement.innerText).toMatchInlineSnapshot(` "fn get_rotation_from_velocity_util_1(velocity: vec2f) -> f32 { - return -atan2(velocity.x, velocity.y); + return -(atan2(velocity.x, velocity.y)); } fn rotate_util_2(v: vec2f, angle: f32) -> vec2f { - var cos = cos(angle); - var sin = sin(angle); + let cos = cos(angle); + let sin = sin(angle); return vec2f(((v.x * cos) - (v.y * sin)), ((v.x * sin) + (v.y * cos))); } @@ -42,7 +42,7 @@ describe('wgsl resolution example', () => { } @vertex fn vertex_shader_0(input: vertex_shader_Input_5) -> vertex_shader_Output_4 { - var angle = get_rotation_from_velocity_util_1(input.velocity); + let angle = get_rotation_from_velocity_util_1(input.velocity); var rotated = rotate_util_2(input.v, angle); var pos = vec4f((rotated.x + input.center.x), (rotated.y + input.center.y), 0, 1); var color = vec4f(((sin((angle + colorPalette_3.x)) * 0.45) + 0.45), ((sin((angle + colorPalette_3.y)) * 0.45) + 0.45), ((sin((angle + colorPalette_3.z)) * 0.45) + 0.45), 1); @@ -83,7 +83,7 @@ describe('wgsl resolution example', () => { } @compute @workgroup_size(1) fn compute_shader_8(input: compute_shader_Input_14) { - var index = input.gid.x; + let index = input.gid.x; let instanceInfo = (¤tTrianglePos_9[index]); var separation = vec2f(); var alignment = vec2f(); @@ -95,7 +95,7 @@ describe('wgsl resolution example', () => { continue; } let other = (¤tTrianglePos_9[i]); - var dist = distance((*instanceInfo).position, (*other).position); + let dist = distance((*instanceInfo).position, (*other).position); if ((dist < paramsBuffer_11.separationDistance)) { separation = (separation + ((*instanceInfo).position - (*other).position)); } @@ -121,15 +121,15 @@ describe('wgsl resolution example', () => { (*instanceInfo).velocity = ((*instanceInfo).velocity + velocity); (*instanceInfo).velocity = (clamp(length((*instanceInfo).velocity), 0, 0.01) * normalize((*instanceInfo).velocity)); if (((*instanceInfo).position.x > 1.03)) { - (*instanceInfo).position.x = (-1 - 0.03); + (*instanceInfo).position.x = -1.03; } if (((*instanceInfo).position.y > 1.03)) { - (*instanceInfo).position.y = (-1 - 0.03); + (*instanceInfo).position.y = -1.03; } - if (((*instanceInfo).position.x < (-1 - 0.03))) { + if (((*instanceInfo).position.x < -1.03)) { (*instanceInfo).position.x = 1.03; } - if (((*instanceInfo).position.y < (-1 - 0.03))) { + if (((*instanceInfo).position.y < -1.03)) { (*instanceInfo).position.y = 1.03; } (*instanceInfo).position = ((*instanceInfo).position + (*instanceInfo).velocity); diff --git a/packages/typegpu/tests/examples/individual/xor-dev-centrifuge-2.test.ts b/packages/typegpu/tests/examples/individual/xor-dev-centrifuge-2.test.ts index ca64533ee9..3ba8d51f54 100644 --- a/packages/typegpu/tests/examples/individual/xor-dev-centrifuge-2.test.ts +++ b/packages/typegpu/tests/examples/individual/xor-dev-centrifuge-2.test.ts @@ -27,7 +27,7 @@ describe('xor dev centrifuge example', () => { } @vertex fn vertexMain_0(input: vertexMain_Input_2) -> vertexMain_Output_1 { - var pos = array(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3)); + var pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); return vertexMain_Output_1(vec4f(pos[input.vertexIndex], 0, 1), pos[input.vertexIndex]); } @@ -66,7 +66,7 @@ describe('xor dev centrifuge example', () => { p.y += cameraPos_6.y; var coords = vec3f(((atan2(p.y, p.x) * bigStrips_7) + time_8), ((p.z * dollyZoom_9) - (5 * time_8)), (length(p.xy) - 11)); var coords2 = (cos((coords + cos((coords * smallStrips_10)))) - 1); - var dd = ((length(vec4f(coords.z, coords2)) * 0.5) - 0.1); + let dd = ((length(vec4f(coords.z, coords2)) * 0.5) - 0.1); acc = (acc + ((1.2 - cos((color_11 * p.z))) / dd)); z += dd; } diff --git a/packages/typegpu/tests/examples/individual/xor-dev-runner.test.ts b/packages/typegpu/tests/examples/individual/xor-dev-runner.test.ts index 7496b9cd26..7be31efa78 100644 --- a/packages/typegpu/tests/examples/individual/xor-dev-runner.test.ts +++ b/packages/typegpu/tests/examples/individual/xor-dev-runner.test.ts @@ -27,7 +27,7 @@ describe('xor dev runner example', () => { } @vertex fn vertexMain_0(input: vertexMain_Input_2) -> vertexMain_Output_1 { - var pos = array(vec2f(-1, -1), vec2f(3, -1), vec2f(-1, 3)); + var pos = array(vec2f(-1), vec2f(3, -1), vec2f(-1, 3)); return vertexMain_Output_1(vec4f(pos[input.vertexIndex], 0, 1), pos[input.vertexIndex]); } @@ -44,7 +44,7 @@ describe('xor dev runner example', () => { } fn rotateXZ_9(angle: f32) -> mat3x3f { - return mat3x3f(vec3f(cos(angle), 0, sin(angle)), vec3f(0, 1, 0), vec3f(-sin(angle), 0, cos(angle))); + return mat3x3f(vec3f(cos(angle), 0, sin(angle)), vec3f(0, 1, 0), vec3f(-(sin(angle)), 0, cos(angle))); } @group(0) @binding(4) var shift_10: f32; @@ -71,7 +71,7 @@ describe('xor dev runner example', () => { var prox = p.y; for (var i = 40.1; (i > 0.01); i *= 0.2) { q = ((i * 0.9) - abs((mod_8(q, (i + i)) - i))); - var minQ = min(min(q.x, q.y), q.z); + let minQ = min(min(q.x, q.y), q.z); prox = max(prox, minQ); q = (q * rotateXZ_9(shift_10)); } diff --git a/packages/typegpu/tests/indent.test.ts b/packages/typegpu/tests/indent.test.ts index c830d8a721..0de956daa0 100644 --- a/packages/typegpu/tests/indent.test.ts +++ b/packages/typegpu/tests/indent.test.ts @@ -231,7 +231,7 @@ describe('indents', () => { } fn updateParticle_7(particle: Particle_5, gravity: vec3f, deltaTime: f32) -> Particle_5 { - var density = getDensityAt_8(particle.physics.position); + let density = getDensityAt_8(particle.physics.position); var force = (gravity * density); var newVelocity = (particle.physics.velocity + (force * deltaTime)); var newPosition = (particle.physics.position + (newVelocity * deltaTime)); diff --git a/packages/typegpu/tests/slot.test.ts b/packages/typegpu/tests/slot.test.ts index 4a1733ea0e..53fd6983c3 100644 --- a/packages/typegpu/tests/slot.test.ts +++ b/packages/typegpu/tests/slot.test.ts @@ -319,9 +319,9 @@ describe('tgpu.slot', () => { var pos = vec3f(1, 2, 3); const posX = 1f; let vel = (&boid.vel); - var velX = boid.vel.x; + let velX = boid.vel.x; let vel_ = (&boid.vel); - var velX_ = boid.vel.x; + let velX_ = boid.vel.x; var color = getColor(); }" `); diff --git a/packages/typegpu/tests/tgsl/createDualImpl.test.ts b/packages/typegpu/tests/tgsl/createDualImpl.test.ts index 94d8c10e11..d8d05292c2 100644 --- a/packages/typegpu/tests/tgsl/createDualImpl.test.ts +++ b/packages/typegpu/tests/tgsl/createDualImpl.test.ts @@ -77,7 +77,7 @@ describe('dualImpl', () => { expect(asWgsl(myFn)).toMatchInlineSnapshot(` "fn myFn() { - var a = fallback(2); + let a = fallback(2); }" `); }); @@ -98,7 +98,7 @@ describe('dualImpl', () => { expect(asWgsl(myFn)).toMatchInlineSnapshot(` "fn myFn() { - var a = fallback(2); + let a = fallback(2); }" `); }); diff --git a/packages/typegpu/tests/tgsl/shellless.test.ts b/packages/typegpu/tests/tgsl/shellless.test.ts index 51e569c606..32beadc815 100644 --- a/packages/typegpu/tests/tgsl/shellless.test.ts +++ b/packages/typegpu/tests/tgsl/shellless.test.ts @@ -94,7 +94,7 @@ describe('shellless', () => { } fn main() -> f32 { - var x = someFn(1, 2); + let x = someFn(1, 2); return x; }" `); diff --git a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts index c2b1250339..a9212eed94 100644 --- a/packages/typegpu/tests/tgsl/wgslGenerator.test.ts +++ b/packages/typegpu/tests/tgsl/wgslGenerator.test.ts @@ -1067,7 +1067,7 @@ describe('wgslGenerator', () => { "fn power() { const a = 10f; const b = 3f; - var n = pow(a, b); + let n = pow(a, b); }" `); }); @@ -1096,7 +1096,7 @@ describe('wgslGenerator', () => { "fn power() { const a = 3u; const b = 5i; - var m = pow(f32(a), f32(b)); + let m = pow(f32(a), f32(b)); }" `); }); @@ -1127,8 +1127,8 @@ describe('wgslGenerator', () => { "fn testFn() { var matrix = mat4x4f(); let column = (&matrix[1]); - var element = (*column)[0]; - var directElement = matrix[1][0]; + let element = (*column)[0]; + let directElement = matrix[1][0]; }" `); }); diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index 4642986351..39a0f4bb97 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -162,8 +162,8 @@ describe('TGSL tgpu.fn function', () => { } @vertex fn vertex_fn(input: vertex_fn_Input) -> vertex_fn_Output { - var vi = f32(input.vi); - var ii = f32(input.ii); + let vi = f32(input.vi); + let ii = f32(input.ii); var color = input.color; return vertex_fn_Output(vec4f(color.w, ii, vi, 1), vec2f(color.w, vi)); }" @@ -300,7 +300,7 @@ describe('TGSL tgpu.fn function', () => { } @compute @workgroup_size(24) fn compute_fn(input: compute_fn_Input) { - var index = input.gid.x; + let index = input.gid.x; const iterationF = 0f; const sign = 0; var change = vec4f(); @@ -327,7 +327,7 @@ describe('TGSL tgpu.fn function', () => { } @compute @workgroup_size(24) fn compute_fn(_arg_0: compute_fn_Input) { - var index = _arg_0.gid.x; + let index = _arg_0.gid.x; const iterationF = 0f; const sign = 0; var change = vec4f(); diff --git a/packages/typegpu/tests/variable.test.ts b/packages/typegpu/tests/variable.test.ts index 91c843d21a..7b6e183039 100644 --- a/packages/typegpu/tests/variable.test.ts +++ b/packages/typegpu/tests/variable.test.ts @@ -125,7 +125,7 @@ var x: array = array(s(1, vec2i(2, 3)), s(4, vec2i(5, 6))); fn func() { let pos = (&boid); let vel = (&boid.vel); - var velX = boid.vel.x; + let velX = boid.vel.x; }" `); }); @@ -142,8 +142,8 @@ var x: array = array(s(1, vec2i(2, 3)), s(4, vec2i(5, 6))); "var atomicCounter: atomic; fn func() { - var oldValue = atomicAdd(&atomicCounter, 1); - var currentValue = atomicLoad(&atomicCounter); + let oldValue = atomicAdd(&atomicCounter, 1); + let currentValue = atomicLoad(&atomicCounter); }" `); }); From 04fb153e5cff8d0742602a0df542956af7b404ec Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Mon, 3 Nov 2025 15:22:45 +0100 Subject: [PATCH 35/59] Fix Disco example --- .../rendering/disco/shaders/fragment.ts | 51 +++++++++++-------- packages/typegpu/src/std/array.ts | 2 + .../tests/examples/individual/disco.test.ts | 10 ++-- 3 files changed, 37 insertions(+), 26 deletions(-) diff --git a/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts b/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts index 646a4c9b39..59a12c7e1e 100644 --- a/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts +++ b/apps/typegpu-docs/src/examples/rendering/disco/shaders/fragment.ts @@ -4,13 +4,17 @@ import * as std from 'typegpu/std'; import { palette } from '../utils.ts'; import { resolutionAccess, timeAccess } from '../consts.ts'; -const aspectCorrected = tgpu.fn([d.vec2f], d.vec2f)((uv) => { - const v = uv.xy.sub(0.5).mul(2.0); +const aspectCorrected = (uv: d.v2f): d.v2f => { + 'use gpu'; + const v = uv.sub(0.5).mul(2); const aspect = resolutionAccess.$.x / resolutionAccess.$.y; - if (aspect > 1) v.x *= aspect; - else v.y /= aspect; + if (aspect > 1) { + v.x *= aspect; + } else { + v.y /= aspect; + } return v; -}); +}; const accumulate = tgpu.fn( [d.vec3f, d.vec3f, d.f32], @@ -22,21 +26,20 @@ export const mainFragment = tgpu['~unstable'].fragmentFn({ out: d.vec4f, })(({ uv }) => { { - let aspectUv = aspectCorrected(uv); - const originalUv = aspectUv; + const originalUv = aspectCorrected(uv); + + let aspectUv = d.vec2f(originalUv); let accumulatedColor = d.vec3f(); for (let iteration = 0.0; iteration < 5.0; iteration++) { - aspectUv = std.sub( - std.fract(std.mul(aspectUv, 1.3 * std.sin(timeAccess.$))), - 0.5, - ); + aspectUv = std.fract(aspectUv.mul(1.3 * std.sin(timeAccess.$))).sub(0.5); let radialLength = std.length(aspectUv) * std.exp(-std.length(originalUv) * 2); - const paletteColor = palette(std.length(originalUv) + timeAccess.$ * 0.9); radialLength = std.sin(radialLength * 8 + timeAccess.$) / 8; radialLength = std.abs(radialLength); radialLength = std.smoothstep(0.0, 0.1, radialLength); radialLength = 0.06 / radialLength; + + const paletteColor = palette(std.length(originalUv) + timeAccess.$ * 0.9); accumulatedColor = accumulate( accumulatedColor, paletteColor, @@ -53,8 +56,9 @@ export const mainFragment2 = tgpu['~unstable'].fragmentFn({ out: d.vec4f, })(({ uv }) => { { - let aspectUv = aspectCorrected(uv); - const originalUv = aspectUv; + const originalUv = aspectCorrected(uv); + let aspectUv = d.vec2f(originalUv); + let accumulatedColor = d.vec3f(); for (let iteration = 0.0; iteration < 3.0; iteration++) { aspectUv = std.fract(aspectUv.mul(-0.9)).sub(0.5); @@ -80,8 +84,9 @@ export const mainFragment3 = tgpu['~unstable'].fragmentFn({ in: { uv: d.vec2f }, out: d.vec4f, })(({ uv }) => { - let aspectUv = aspectCorrected(uv); - const originalUv = aspectUv; + const originalUv = aspectCorrected(uv); + let aspectUv = d.vec2f(originalUv); + let accumulatedColor = d.vec3f(); const baseAngle = timeAccess.$ * 0.3; const cosBaseAngle = std.cos(baseAngle); @@ -130,9 +135,10 @@ export const mainFragment4 = tgpu['~unstable'].fragmentFn({ std.abs(std.fract(aspectUv.y * 1.2) - 0.5), ).mul(2).sub(1); aspectUv = d.vec2f(mirroredUv); - const originalUv = aspectUv; + const originalUv = d.vec2f(aspectUv); let accumulatedColor = d.vec3f(0, 0, 0); const time = timeAccess.$; + for (let iteration = 0; iteration < 4; iteration++) { const iterationF32 = d.f32(iteration); // rotation + scale @@ -159,6 +165,7 @@ export const mainFragment4 = tgpu['~unstable'].fragmentFn({ ); accumulatedColor = accumulate(accumulatedColor, paletteColor, radialLength); } + return d.vec4f(accumulatedColor, 1.0); }); @@ -167,9 +174,10 @@ export const mainFragment5 = tgpu['~unstable'].fragmentFn({ in: { uv: d.vec2f }, out: d.vec4f, })(({ uv }) => { - let aspectUv = aspectCorrected(uv); - const originalUv = aspectUv; + const originalUv = aspectCorrected(uv); + let aspectUv = d.vec2f(originalUv); let accumulatedColor = d.vec3f(); + for (let iteration = 0; iteration < 3; iteration++) { const iterationF32 = d.f32(iteration); // swirl distortion @@ -203,7 +211,8 @@ export const mainFragment6 = tgpu['~unstable'].fragmentFn({ out: d.vec4f, })(({ uv }) => { let aspectUv = aspectCorrected(uv); - const originalUv = aspectUv; + const originalUv = d.vec2f(aspectUv); + let accumulatedColor = d.vec3f(0, 0, 0); const time = timeAccess.$; for (let iteration = 0; iteration < 5; iteration++) { @@ -245,7 +254,7 @@ export const mainFragment7 = tgpu['~unstable'].fragmentFn({ std.abs(std.fract(aspectUv.x * 1.5) - 0.5), std.abs(std.fract(aspectUv.y * 1.5) - 0.5), ).mul(2); - const originalUv = aspectUv; + const originalUv = d.vec2f(aspectUv); let accumulatedColor = d.vec3f(0, 0, 0); const time = timeAccess.$; for (let iteration = 0; iteration < 4; iteration++) { diff --git a/packages/typegpu/src/std/array.ts b/packages/typegpu/src/std/array.ts index ec87853471..0149f781f7 100644 --- a/packages/typegpu/src/std/array.ts +++ b/packages/typegpu/src/std/array.ts @@ -2,7 +2,9 @@ import { dualImpl } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; import { abstractInt, u32 } from '../data/numeric.ts'; import { ptrFn } from '../data/ptr.ts'; +import type { ref } from '../data/ref.ts'; import { isPtr, isWgslArray, type StorableData } from '../data/wgslTypes.ts'; +import { isRef } from '../shared/symbols.ts'; const sizeOfPointedToArray = (dataType: unknown) => isPtr(dataType) && isWgslArray(dataType.inner) diff --git a/packages/typegpu/tests/examples/individual/disco.test.ts b/packages/typegpu/tests/examples/individual/disco.test.ts index 33fd3e82fe..fb96f191b2 100644 --- a/packages/typegpu/tests/examples/individual/disco.test.ts +++ b/packages/typegpu/tests/examples/individual/disco.test.ts @@ -36,7 +36,7 @@ describe('disco example', () => { @group(0) @binding(0) var resolutionUniform_5: vec2f; fn aspectCorrected_4(uv: vec2f) -> vec2f { - var v = ((uv.xy - 0.5) * 2); + var v = ((uv - 0.5) * 2); let aspect = (resolutionUniform_5.x / resolutionUniform_5.y); if ((aspect > 1)) { v.x *= aspect; @@ -68,17 +68,17 @@ describe('disco example', () => { @fragment fn mainFragment_3(_arg_0: mainFragment_Input_9) -> @location(0) vec4f { { - var aspectUv = aspectCorrected_4(_arg_0.uv); - let originalUv = (&aspectUv); + var originalUv = aspectCorrected_4(_arg_0.uv); + var aspectUv = originalUv; var accumulatedColor = vec3f(); for (var iteration = 0; (iteration < 5); iteration++) { aspectUv = (fract((aspectUv * (1.3 * sin(time_6)))) - 0.5); - var radialLength = (length(aspectUv) * exp((-(length((*originalUv))) * 2))); - var paletteColor = palette_7((length((*originalUv)) + (time_6 * 0.9))); + var radialLength = (length(aspectUv) * exp((-(length(originalUv)) * 2))); radialLength = (sin(((radialLength * 8) + time_6)) / 8f); radialLength = abs(radialLength); radialLength = smoothstep(0, 0.1, radialLength); radialLength = (0.06f / radialLength); + var paletteColor = palette_7((length(originalUv) + (time_6 * 0.9))); accumulatedColor = accumulate_8(accumulatedColor, paletteColor, radialLength); } return vec4f(accumulatedColor, 1); From 2a6527e41a73561400f0bc642cddfab1a97bce59 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Mon, 3 Nov 2025 16:11:06 +0100 Subject: [PATCH 36/59] =?UTF-8?q?=F0=9F=A6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/typegpu/src/tgsl/conversion.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index b6fea55105..3cdf24f468 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -9,8 +9,6 @@ import { type F32, type I32, isMat, - isNaturallyEphemeral, - isPtr, isVec, type U32, type WgslStruct, From 59f2618273dd6dfcf5aa14864a672cb30b71717d Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Mon, 3 Nov 2025 17:35:13 +0100 Subject: [PATCH 37/59] Updating gravity example --- .../rendering/cubemap-reflection/icosphere.ts | 3 - .../examples/simulation/gravity/compute.ts | 77 +++++-------------- .../src/examples/simulation/gravity/index.ts | 1 + .../individual/cubemap-reflection.test.ts | 1 - .../tests/examples/individual/gravity.test.ts | 45 +++++------ 5 files changed, 45 insertions(+), 82 deletions(-) diff --git a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts index 7d548f2a17..92b576fc27 100644 --- a/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts +++ b/apps/typegpu-docs/src/examples/rendering/cubemap-reflection/icosphere.ts @@ -199,11 +199,8 @@ export class IcosphereGenerator { const outIndex = baseIndexNext + i; const nextVertex = nextVertices.$[outIndex]; - nextVertex.position = packVec2u(reprojectedVertex); nextVertex.normal = packVec2u(normal); - - nextVertices.$[outIndex] = ComputeVertex(nextVertex); } }); diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts index f03cfda446..cdb566df00 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts @@ -29,17 +29,7 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ workgroupSize: [1], })((input) => { const currentId = input.gid.x; - // TODO: replace it with struct copy when Chromium is fixed - const current = CelestialBody({ - position: computeLayout.$.inState[currentId].position, - velocity: computeLayout.$.inState[currentId].velocity, - mass: computeLayout.$.inState[currentId].mass, - collisionBehavior: computeLayout.$.inState[currentId].collisionBehavior, - textureIndex: computeLayout.$.inState[currentId].textureIndex, - radiusMultiplier: computeLayout.$.inState[currentId].radiusMultiplier, - ambientLightFactor: computeLayout.$.inState[currentId].ambientLightFactor, - destroyed: computeLayout.$.inState[currentId].destroyed, - }); + const current = CelestialBody(computeLayout.$.inState[currentId]); if (current.destroyed === 0) { for (let i = 0; i < computeLayout.$.celestialBodiesCount; i++) { @@ -128,58 +118,33 @@ export const computeGravityShader = tgpu['~unstable'].computeFn({ in: { gid: d.builtin.globalInvocationId }, workgroupSize: [1], })((input) => { - // TODO: replace it with struct copy when Chromium is fixed - const current = CelestialBody({ - position: computeLayout.$.inState[input.gid.x].position, - velocity: computeLayout.$.inState[input.gid.x].velocity, - mass: computeLayout.$.inState[input.gid.x].mass, - collisionBehavior: computeLayout.$.inState[input.gid.x].collisionBehavior, - textureIndex: computeLayout.$.inState[input.gid.x].textureIndex, - radiusMultiplier: computeLayout.$.inState[input.gid.x].radiusMultiplier, - ambientLightFactor: computeLayout.$.inState[input.gid.x].ambientLightFactor, - destroyed: computeLayout.$.inState[input.gid.x].destroyed, - }); + const current = computeLayout.$.inState[input.gid.x]; + const newCurrent = computeLayout.$.outState[input.gid.x]; const dt = timeAccess.$.passed * timeAccess.$.multiplier; - const updatedCurrent = current; - if (current.destroyed === 0) { - for (let i = 0; i < computeLayout.$.celestialBodiesCount; i++) { - // TODO: replace it with struct copy when Chromium is fixed - const other = CelestialBody({ - position: computeLayout.$.inState[i].position, - velocity: computeLayout.$.inState[i].velocity, - mass: computeLayout.$.inState[i].mass, - collisionBehavior: computeLayout.$.inState[i].collisionBehavior, - textureIndex: computeLayout.$.inState[i].textureIndex, - radiusMultiplier: computeLayout.$.inState[i].radiusMultiplier, - ambientLightFactor: computeLayout.$.inState[i].ambientLightFactor, - destroyed: computeLayout.$.inState[i].destroyed, - }); - - if (d.u32(i) === input.gid.x || other.destroyed === 1) { - continue; - } + if (current.destroyed === 1) { + return; + } - const dist = std.max( - radiusOf(current) + radiusOf(other), - std.distance(current.position, other.position), - ); - const gravityForce = (current.mass * other.mass) / dist / dist; + newCurrent.velocity = d.vec3f(current.velocity); + for (let i = 0; i < computeLayout.$.celestialBodiesCount; i++) { + const other = computeLayout.$.inState[i]; - const direction = std.normalize( - std.sub(other.position, current.position), - ); - updatedCurrent.velocity = std.add( - updatedCurrent.velocity, - std.mul((gravityForce / current.mass) * dt, direction), - ); + if (d.u32(i) === input.gid.x || other.destroyed === 1) { + continue; } - updatedCurrent.position = std.add( - updatedCurrent.position, - std.mul(dt, updatedCurrent.velocity), + const dist = std.max( + radiusOf(current) + radiusOf(other), + std.distance(current.position, other.position), + ); + const gravityForce = (current.mass * other.mass) / dist / dist; + + const direction = std.normalize(other.position.sub(current.position)); + newCurrent.velocity = newCurrent.velocity.add( + direction.mul((gravityForce / current.mass) * dt), ); } - computeLayout.$.outState[input.gid.x] = CelestialBody(updatedCurrent); + newCurrent.position = current.position.add(newCurrent.velocity.mul(dt)); }); diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/index.ts b/apps/typegpu-docs/src/examples/simulation/gravity/index.ts index 51c6d72777..d1e985aa4e 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/index.ts @@ -222,6 +222,7 @@ async function loadPreset(preset: Preset): Promise { const computeBufferB = root .createBuffer(d.arrayOf(CelestialBody, celestialBodies.length)) .$usage('storage'); + computeBufferB.copyFrom(computeBufferA); const computeCollisionsBindGroup = root.createBindGroup( computeLayout, diff --git a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts index 1854350617..a7c8cf9d1c 100644 --- a/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts +++ b/packages/typegpu/tests/examples/individual/cubemap-reflection.test.ts @@ -85,7 +85,6 @@ describe('cubemap reflection example', () => { let nextVertex = (&nextVertices_7[outIndex]); (*nextVertex).position = packVec2u_8((*reprojectedVertex)); (*nextVertex).normal = packVec2u_8(normal); - nextVertices_7[outIndex] = (*nextVertex); } } diff --git a/packages/typegpu/tests/examples/individual/gravity.test.ts b/packages/typegpu/tests/examples/individual/gravity.test.ts index 0704b52152..05242c8798 100644 --- a/packages/typegpu/tests/examples/individual/gravity.test.ts +++ b/packages/typegpu/tests/examples/individual/gravity.test.ts @@ -64,7 +64,7 @@ describe('gravity example', () => { @compute @workgroup_size(1) fn computeCollisionsShader_0(input: computeCollisionsShader_Input_7) { let currentId = input.gid.x; - var current = CelestialBody_2(inState_1[currentId].destroyed, inState_1[currentId].position, inState_1[currentId].velocity, inState_1[currentId].mass, inState_1[currentId].radiusMultiplier, inState_1[currentId].collisionBehavior, inState_1[currentId].textureIndex, inState_1[currentId].ambientLightFactor); + var current = inState_1[currentId]; if ((current.destroyed == 0)) { for (var i = 0; (i < celestialBodiesCount_3); i++) { let otherId = u32(i); @@ -108,43 +108,44 @@ describe('gravity example', () => { @group(1) @binding(1) var inState_1: array; - struct Time_4 { + @group(1) @binding(2) var outState_3: array; + + struct Time_5 { passed: f32, multiplier: f32, } - @group(0) @binding(0) var time_3: Time_4; + @group(0) @binding(0) var time_4: Time_5; - @group(1) @binding(0) var celestialBodiesCount_5: i32; + @group(1) @binding(0) var celestialBodiesCount_6: i32; - fn radiusOf_6(body: CelestialBody_2) -> f32 { + fn radiusOf_7(body: CelestialBody_2) -> f32 { return (pow(((body.mass * 0.75) / 3.141592653589793f), 0.333) * body.radiusMultiplier); } - @group(1) @binding(2) var outState_7: array; - struct computeGravityShader_Input_8 { @builtin(global_invocation_id) gid: vec3u, } @compute @workgroup_size(1) fn computeGravityShader_0(input: computeGravityShader_Input_8) { - var current = CelestialBody_2(inState_1[input.gid.x].destroyed, inState_1[input.gid.x].position, inState_1[input.gid.x].velocity, inState_1[input.gid.x].mass, inState_1[input.gid.x].radiusMultiplier, inState_1[input.gid.x].collisionBehavior, inState_1[input.gid.x].textureIndex, inState_1[input.gid.x].ambientLightFactor); - let dt = (time_3.passed * time_3.multiplier); - let updatedCurrent = (¤t); - if ((current.destroyed == 0)) { - for (var i = 0; (i < celestialBodiesCount_5); i++) { - var other = CelestialBody_2(inState_1[i].destroyed, inState_1[i].position, inState_1[i].velocity, inState_1[i].mass, inState_1[i].radiusMultiplier, inState_1[i].collisionBehavior, inState_1[i].textureIndex, inState_1[i].ambientLightFactor); - if (((u32(i) == input.gid.x) || (other.destroyed == 1))) { - continue; - } - let dist = max((radiusOf_6(current) + radiusOf_6(other)), distance(current.position, other.position)); - let gravityForce = (((current.mass * other.mass) / dist) / dist); - var direction = normalize((other.position - current.position)); - (*updatedCurrent).velocity = ((*updatedCurrent).velocity + (((gravityForce / current.mass) * dt) * direction)); + let current = (&inState_1[input.gid.x]); + let newCurrent = (&outState_3[input.gid.x]); + let dt = (time_4.passed * time_4.multiplier); + if (((*current).destroyed == 1)) { + return; + } + (*newCurrent).velocity = (*current).velocity; + for (var i = 0; (i < celestialBodiesCount_6); i++) { + let other = (&inState_1[i]); + if (((u32(i) == input.gid.x) || ((*other).destroyed == 1))) { + continue; } - (*updatedCurrent).position = ((*updatedCurrent).position + (dt * (*updatedCurrent).velocity)); + let dist = max((radiusOf_7((*current)) + radiusOf_7((*other))), distance((*current).position, (*other).position)); + let gravityForce = ((((*current).mass * (*other).mass) / dist) / dist); + var direction = normalize(((*other).position - (*current).position)); + (*newCurrent).velocity = ((*newCurrent).velocity + (direction * ((gravityForce / (*current).mass) * dt))); } - outState_7[input.gid.x] = (*updatedCurrent); + (*newCurrent).position = ((*current).position + ((*newCurrent).velocity * dt)); } struct Camera_2 { From f85150b2a765b96bfc2b1afd80c2262f8b1e5acc Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Mon, 3 Nov 2025 17:41:04 +0100 Subject: [PATCH 38/59] More updates --- .../examples/simulation/gravity/compute.ts | 32 +++++++++++++++---- .../src/examples/simulation/gravity/index.ts | 1 - 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts index cdb566df00..1a4642134b 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts @@ -29,7 +29,17 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ workgroupSize: [1], })((input) => { const currentId = input.gid.x; - const current = CelestialBody(computeLayout.$.inState[currentId]); + // TODO: replace it with struct copy when Chromium is fixed + const current = CelestialBody({ + position: computeLayout.$.inState[currentId].position, + velocity: computeLayout.$.inState[currentId].velocity, + mass: computeLayout.$.inState[currentId].mass, + collisionBehavior: computeLayout.$.inState[currentId].collisionBehavior, + textureIndex: computeLayout.$.inState[currentId].textureIndex, + radiusMultiplier: computeLayout.$.inState[currentId].radiusMultiplier, + ambientLightFactor: computeLayout.$.inState[currentId].ambientLightFactor, + destroyed: computeLayout.$.inState[currentId].destroyed, + }); if (current.destroyed === 0) { for (let i = 0; i < computeLayout.$.celestialBodiesCount; i++) { @@ -118,15 +128,24 @@ export const computeGravityShader = tgpu['~unstable'].computeFn({ in: { gid: d.builtin.globalInvocationId }, workgroupSize: [1], })((input) => { - const current = computeLayout.$.inState[input.gid.x]; - const newCurrent = computeLayout.$.outState[input.gid.x]; const dt = timeAccess.$.passed * timeAccess.$.multiplier; + const currentId = input.gid.x; + // TODO: replace it with struct copy when Chromium is fixed + const current = CelestialBody({ + position: computeLayout.$.inState[currentId].position, + velocity: computeLayout.$.inState[currentId].velocity, + mass: computeLayout.$.inState[currentId].mass, + collisionBehavior: computeLayout.$.inState[currentId].collisionBehavior, + textureIndex: computeLayout.$.inState[currentId].textureIndex, + radiusMultiplier: computeLayout.$.inState[currentId].radiusMultiplier, + ambientLightFactor: computeLayout.$.inState[currentId].ambientLightFactor, + destroyed: computeLayout.$.inState[currentId].destroyed, + }); if (current.destroyed === 1) { return; } - newCurrent.velocity = d.vec3f(current.velocity); for (let i = 0; i < computeLayout.$.celestialBodiesCount; i++) { const other = computeLayout.$.inState[i]; @@ -141,10 +160,11 @@ export const computeGravityShader = tgpu['~unstable'].computeFn({ const gravityForce = (current.mass * other.mass) / dist / dist; const direction = std.normalize(other.position.sub(current.position)); - newCurrent.velocity = newCurrent.velocity.add( + current.velocity = current.velocity.add( direction.mul((gravityForce / current.mass) * dt), ); } - newCurrent.position = current.position.add(newCurrent.velocity.mul(dt)); + current.position = current.position.add(current.velocity.mul(dt)); + computeLayout.$.outState[input.gid.x] = CelestialBody(current); }); diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/index.ts b/apps/typegpu-docs/src/examples/simulation/gravity/index.ts index d1e985aa4e..51c6d72777 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/index.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/index.ts @@ -222,7 +222,6 @@ async function loadPreset(preset: Preset): Promise { const computeBufferB = root .createBuffer(d.arrayOf(CelestialBody, celestialBodies.length)) .$usage('storage'); - computeBufferB.copyFrom(computeBufferA); const computeCollisionsBindGroup = root.createBindGroup( computeLayout, From 05270d0a4f2e5df1ac2561c08e4d0618ba5986c3 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Mon, 3 Nov 2025 17:41:24 +0100 Subject: [PATCH 39/59] Update gravity.test.ts --- .../tests/examples/individual/gravity.test.ts | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/packages/typegpu/tests/examples/individual/gravity.test.ts b/packages/typegpu/tests/examples/individual/gravity.test.ts index 05242c8798..1eed9087d6 100644 --- a/packages/typegpu/tests/examples/individual/gravity.test.ts +++ b/packages/typegpu/tests/examples/individual/gravity.test.ts @@ -64,7 +64,7 @@ describe('gravity example', () => { @compute @workgroup_size(1) fn computeCollisionsShader_0(input: computeCollisionsShader_Input_7) { let currentId = input.gid.x; - var current = inState_1[currentId]; + var current = CelestialBody_2(inState_1[currentId].destroyed, inState_1[currentId].position, inState_1[currentId].velocity, inState_1[currentId].mass, inState_1[currentId].radiusMultiplier, inState_1[currentId].collisionBehavior, inState_1[currentId].textureIndex, inState_1[currentId].ambientLightFactor); if ((current.destroyed == 0)) { for (var i = 0; (i < celestialBodiesCount_3); i++) { let otherId = u32(i); @@ -95,7 +95,14 @@ describe('gravity example', () => { outState_6[input.gid.x] = current; } - struct CelestialBody_2 { + struct Time_2 { + passed: f32, + multiplier: f32, + } + + @group(0) @binding(0) var time_1: Time_2; + + struct CelestialBody_4 { destroyed: u32, position: vec3f, velocity: vec3f, @@ -106,46 +113,39 @@ describe('gravity example', () => { ambientLightFactor: f32, } - @group(1) @binding(1) var inState_1: array; - - @group(1) @binding(2) var outState_3: array; - - struct Time_5 { - passed: f32, - multiplier: f32, - } - - @group(0) @binding(0) var time_4: Time_5; + @group(1) @binding(1) var inState_3: array; - @group(1) @binding(0) var celestialBodiesCount_6: i32; + @group(1) @binding(0) var celestialBodiesCount_5: i32; - fn radiusOf_7(body: CelestialBody_2) -> f32 { + fn radiusOf_6(body: CelestialBody_4) -> f32 { return (pow(((body.mass * 0.75) / 3.141592653589793f), 0.333) * body.radiusMultiplier); } + @group(1) @binding(2) var outState_7: array; + struct computeGravityShader_Input_8 { @builtin(global_invocation_id) gid: vec3u, } @compute @workgroup_size(1) fn computeGravityShader_0(input: computeGravityShader_Input_8) { - let current = (&inState_1[input.gid.x]); - let newCurrent = (&outState_3[input.gid.x]); - let dt = (time_4.passed * time_4.multiplier); - if (((*current).destroyed == 1)) { + let dt = (time_1.passed * time_1.multiplier); + let currentId = input.gid.x; + var current = CelestialBody_4(inState_3[currentId].destroyed, inState_3[currentId].position, inState_3[currentId].velocity, inState_3[currentId].mass, inState_3[currentId].radiusMultiplier, inState_3[currentId].collisionBehavior, inState_3[currentId].textureIndex, inState_3[currentId].ambientLightFactor); + if ((current.destroyed == 1)) { return; } - (*newCurrent).velocity = (*current).velocity; - for (var i = 0; (i < celestialBodiesCount_6); i++) { - let other = (&inState_1[i]); + for (var i = 0; (i < celestialBodiesCount_5); i++) { + let other = (&inState_3[i]); if (((u32(i) == input.gid.x) || ((*other).destroyed == 1))) { continue; } - let dist = max((radiusOf_7((*current)) + radiusOf_7((*other))), distance((*current).position, (*other).position)); - let gravityForce = ((((*current).mass * (*other).mass) / dist) / dist); - var direction = normalize(((*other).position - (*current).position)); - (*newCurrent).velocity = ((*newCurrent).velocity + (direction * ((gravityForce / (*current).mass) * dt))); + let dist = max((radiusOf_6(current) + radiusOf_6((*other))), distance(current.position, (*other).position)); + let gravityForce = (((current.mass * (*other).mass) / dist) / dist); + var direction = normalize(((*other).position - current.position)); + current.velocity = (current.velocity + (direction * ((gravityForce / current.mass) * dt))); } - (*newCurrent).position = ((*current).position + ((*newCurrent).velocity * dt)); + current.position = (current.position + (current.velocity * dt)); + outState_7[input.gid.x] = current; } struct Camera_2 { From 66b89cf6c92cf437ef80a12f9394b2909b79aa87 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Mon, 3 Nov 2025 17:45:15 +0100 Subject: [PATCH 40/59] Fixed! --- .../examples/simulation/gravity/compute.ts | 35 +++++++++---------- .../tests/examples/individual/gravity.test.ts | 23 ++++++------ 2 files changed, 28 insertions(+), 30 deletions(-) diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts index 1a4642134b..f847c869ea 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts @@ -142,29 +142,28 @@ export const computeGravityShader = tgpu['~unstable'].computeFn({ destroyed: computeLayout.$.inState[currentId].destroyed, }); - if (current.destroyed === 1) { - return; - } + if (current.destroyed === 0) { + for (let i = 0; i < computeLayout.$.celestialBodiesCount; i++) { + const other = computeLayout.$.inState[i]; - for (let i = 0; i < computeLayout.$.celestialBodiesCount; i++) { - const other = computeLayout.$.inState[i]; + if (d.u32(i) === input.gid.x || other.destroyed === 1) { + continue; + } - if (d.u32(i) === input.gid.x || other.destroyed === 1) { - continue; - } + const dist = std.max( + radiusOf(current) + radiusOf(other), + std.distance(current.position, other.position), + ); + const gravityForce = (current.mass * other.mass) / dist / dist; - const dist = std.max( - radiusOf(current) + radiusOf(other), - std.distance(current.position, other.position), - ); - const gravityForce = (current.mass * other.mass) / dist / dist; + const direction = std.normalize(other.position.sub(current.position)); + current.velocity = current.velocity.add( + direction.mul((gravityForce / current.mass) * dt), + ); + } - const direction = std.normalize(other.position.sub(current.position)); - current.velocity = current.velocity.add( - direction.mul((gravityForce / current.mass) * dt), - ); + current.position = current.position.add(current.velocity.mul(dt)); } - current.position = current.position.add(current.velocity.mul(dt)); computeLayout.$.outState[input.gid.x] = CelestialBody(current); }); diff --git a/packages/typegpu/tests/examples/individual/gravity.test.ts b/packages/typegpu/tests/examples/individual/gravity.test.ts index 1eed9087d6..4635c83431 100644 --- a/packages/typegpu/tests/examples/individual/gravity.test.ts +++ b/packages/typegpu/tests/examples/individual/gravity.test.ts @@ -131,20 +131,19 @@ describe('gravity example', () => { let dt = (time_1.passed * time_1.multiplier); let currentId = input.gid.x; var current = CelestialBody_4(inState_3[currentId].destroyed, inState_3[currentId].position, inState_3[currentId].velocity, inState_3[currentId].mass, inState_3[currentId].radiusMultiplier, inState_3[currentId].collisionBehavior, inState_3[currentId].textureIndex, inState_3[currentId].ambientLightFactor); - if ((current.destroyed == 1)) { - return; - } - for (var i = 0; (i < celestialBodiesCount_5); i++) { - let other = (&inState_3[i]); - if (((u32(i) == input.gid.x) || ((*other).destroyed == 1))) { - continue; + if ((current.destroyed == 0)) { + for (var i = 0; (i < celestialBodiesCount_5); i++) { + let other = (&inState_3[i]); + if (((u32(i) == input.gid.x) || ((*other).destroyed == 1))) { + continue; + } + let dist = max((radiusOf_6(current) + radiusOf_6((*other))), distance(current.position, (*other).position)); + let gravityForce = (((current.mass * (*other).mass) / dist) / dist); + var direction = normalize(((*other).position - current.position)); + current.velocity = (current.velocity + (direction * ((gravityForce / current.mass) * dt))); } - let dist = max((radiusOf_6(current) + radiusOf_6((*other))), distance(current.position, (*other).position)); - let gravityForce = (((current.mass * (*other).mass) / dist) / dist); - var direction = normalize(((*other).position - current.position)); - current.velocity = (current.velocity + (direction * ((gravityForce / current.mass) * dt))); + current.position = (current.position + (current.velocity * dt)); } - current.position = (current.position + (current.velocity * dt)); outState_7[input.gid.x] = current; } From 2ed5955af7c476da72ef84668b442c281b55f907 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Mon, 3 Nov 2025 18:23:19 +0100 Subject: [PATCH 41/59] Update Gravity code --- .../examples/simulation/gravity/compute.ts | 112 ++++++------------ .../examples/simulation/gravity/helpers.ts | 16 ++- .../src/examples/simulation/gravity/render.ts | 44 ++----- .../examples/simulation/gravity/schemas.ts | 1 + packages/typegpu/src/data/ref.ts | 8 +- .../tests/examples/individual/gravity.test.ts | 53 +++++---- 6 files changed, 97 insertions(+), 137 deletions(-) diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts index f847c869ea..eb6506fe06 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts @@ -9,18 +9,17 @@ const { none, bounce, merge } = collisionBehaviors; // tiebreaker function for merges and bounces const isSmaller = tgpu.fn([d.u32, d.u32], d.bool)((currentId, otherId) => { - if ( - computeLayout.$.inState[currentId].mass < - computeLayout.$.inState[otherId].mass - ) { + const current = computeLayout.$.inState[currentId]; + const other = computeLayout.$.inState[otherId]; + + if (current.mass < other.mass) { return true; } - if ( - computeLayout.$.inState[currentId].mass === - computeLayout.$.inState[otherId].mass - ) { + + if (current.mass === other.mass) { return currentId < otherId; } + return false; }); @@ -29,35 +28,18 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ workgroupSize: [1], })((input) => { const currentId = input.gid.x; - // TODO: replace it with struct copy when Chromium is fixed - const current = CelestialBody({ - position: computeLayout.$.inState[currentId].position, - velocity: computeLayout.$.inState[currentId].velocity, - mass: computeLayout.$.inState[currentId].mass, - collisionBehavior: computeLayout.$.inState[currentId].collisionBehavior, - textureIndex: computeLayout.$.inState[currentId].textureIndex, - radiusMultiplier: computeLayout.$.inState[currentId].radiusMultiplier, - ambientLightFactor: computeLayout.$.inState[currentId].ambientLightFactor, - destroyed: computeLayout.$.inState[currentId].destroyed, - }); + const current = CelestialBody(computeLayout.$.inState[currentId]); if (current.destroyed === 0) { - for (let i = 0; i < computeLayout.$.celestialBodiesCount; i++) { - const otherId = d.u32(i); - // TODO: replace it with struct copy when Chromium is fixed - const other = CelestialBody({ - position: computeLayout.$.inState[otherId].position, - velocity: computeLayout.$.inState[otherId].velocity, - mass: computeLayout.$.inState[otherId].mass, - collisionBehavior: computeLayout.$.inState[otherId].collisionBehavior, - textureIndex: computeLayout.$.inState[otherId].textureIndex, - radiusMultiplier: computeLayout.$.inState[otherId].radiusMultiplier, - ambientLightFactor: computeLayout.$.inState[otherId].ambientLightFactor, - destroyed: computeLayout.$.inState[otherId].destroyed, - }); + for ( + let otherId = d.u32(0); + otherId < d.u32(computeLayout.$.celestialBodiesCount); + otherId++ + ) { + const other = computeLayout.$.inState[otherId]; // no collision occurs... if ( - d.u32(i) === input.gid.x || // ...with itself + otherId === currentId || // ...with itself other.destroyed === 1 || // ...when other is destroyed current.collisionBehavior === none || // ...when current behavior is none other.collisionBehavior === none || // ...when other behavior is none @@ -75,30 +57,20 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ // bounce occurs // push the smaller object outside if (isSmaller(currentId, otherId)) { - current.position = std.add( - other.position, - std.mul( - radiusOf(current) + radiusOf(other), - std.normalize(std.sub(current.position, other.position)), - ), - ); + const dir = std.normalize(current.position.sub(other.position)); + current.position = other.position + .add(dir.mul(radiusOf(current) + radiusOf(other))); } + // bounce with tiny damping - current.velocity = std.mul( - 0.99, - std.sub( - current.velocity, - std.mul( - (((2 * other.mass) / (current.mass + other.mass)) * - std.dot( - std.sub(current.velocity, other.velocity), - std.sub(current.position, other.position), - )) / - std.pow(std.distance(current.position, other.position), 2), - std.sub(current.position, other.position), - ), - ), - ); + const posDiff = current.position.sub(other.position); + const velDiff = current.velocity.sub(other.velocity); + const posDiffFactor = + (((2 * other.mass) / (current.mass + other.mass)) * + std.dot(velDiff, posDiff)) / std.dot(posDiff, posDiff); + + current.velocity = current.velocity + .sub(posDiff.mul(posDiffFactor)).mul(0.99); } else { // merge occurs const isCurrentAbsorbed = current.collisionBehavior === bounce || @@ -112,8 +84,8 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ const m1 = current.mass; const m2 = other.mass; current.velocity = std.add( - std.mul(m1 / (m1 + m2), current.velocity), - std.mul(m2 / (m1 + m2), other.velocity), + current.velocity.mul(m1 / (m1 + m2)), + other.velocity.mul(m2 / (m1 + m2)), ); current.mass = m1 + m2; } @@ -121,7 +93,7 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ } } - computeLayout.$.outState[input.gid.x] = CelestialBody(current); + computeLayout.$.outState[currentId] = CelestialBody(current); }); export const computeGravityShader = tgpu['~unstable'].computeFn({ @@ -130,23 +102,17 @@ export const computeGravityShader = tgpu['~unstable'].computeFn({ })((input) => { const dt = timeAccess.$.passed * timeAccess.$.multiplier; const currentId = input.gid.x; - // TODO: replace it with struct copy when Chromium is fixed - const current = CelestialBody({ - position: computeLayout.$.inState[currentId].position, - velocity: computeLayout.$.inState[currentId].velocity, - mass: computeLayout.$.inState[currentId].mass, - collisionBehavior: computeLayout.$.inState[currentId].collisionBehavior, - textureIndex: computeLayout.$.inState[currentId].textureIndex, - radiusMultiplier: computeLayout.$.inState[currentId].radiusMultiplier, - ambientLightFactor: computeLayout.$.inState[currentId].ambientLightFactor, - destroyed: computeLayout.$.inState[currentId].destroyed, - }); + const current = CelestialBody(computeLayout.$.inState[currentId]); if (current.destroyed === 0) { - for (let i = 0; i < computeLayout.$.celestialBodiesCount; i++) { - const other = computeLayout.$.inState[i]; + for ( + let otherId = d.u32(0); + otherId < d.u32(computeLayout.$.celestialBodiesCount); + otherId++ + ) { + const other = computeLayout.$.inState[otherId]; - if (d.u32(i) === input.gid.x || other.destroyed === 1) { + if (otherId === currentId || other.destroyed === 1) { continue; } @@ -165,5 +131,5 @@ export const computeGravityShader = tgpu['~unstable'].computeFn({ current.position = current.position.add(current.velocity.mul(dt)); } - computeLayout.$.outState[input.gid.x] = CelestialBody(current); + computeLayout.$.outState[currentId] = CelestialBody(current); }); diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts b/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts index e6528ab728..58171f9b75 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts @@ -1,10 +1,13 @@ import { load } from '@loaders.gl/core'; import { OBJLoader } from '@loaders.gl/obj'; -import { tgpu, type TgpuRoot } from 'typegpu'; +import type { TgpuRoot } from 'typegpu'; import * as d from 'typegpu/data'; -import * as std from 'typegpu/std'; import { sphereTextureNames } from './enums.ts'; -import { CelestialBody, renderVertexLayout, SkyBoxVertex } from './schemas.ts'; +import { + type CelestialBody, + renderVertexLayout, + SkyBoxVertex, +} from './schemas.ts'; function vert( position: [number, number, number], @@ -163,6 +166,7 @@ export async function loadSphereTextures(root: TgpuRoot) { return texture; } -export const radiusOf = tgpu.fn([CelestialBody], d.f32)((body) => - std.pow((body.mass * 0.75) / Math.PI, 0.333) * body.radiusMultiplier -); +export const radiusOf = (body: CelestialBody): number => { + 'use gpu'; + return (((body.mass * 0.75) / Math.PI) ** 0.333) * body.radiusMultiplier; +}; diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/render.ts b/apps/typegpu-docs/src/examples/simulation/gravity/render.ts index 3ead80a8a4..b88d7e1238 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/render.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/render.ts @@ -4,7 +4,6 @@ import * as std from 'typegpu/std'; import { radiusOf } from './helpers.ts'; import { cameraAccess, - CelestialBody, filteringSamplerSlot, lightSourceAccess, renderBindGroupLayout as renderLayout, @@ -54,32 +53,17 @@ export const mainVertex = tgpu['~unstable'].vertexFn({ }, out: VertexOutput, })((input) => { - // TODO: replace it with struct copy when Chromium is fixed - const currentBody = CelestialBody({ - position: renderLayout.$.celestialBodies[input.instanceIndex].position, - velocity: renderLayout.$.celestialBodies[input.instanceIndex].velocity, - mass: renderLayout.$.celestialBodies[input.instanceIndex].mass, - collisionBehavior: - renderLayout.$.celestialBodies[input.instanceIndex].collisionBehavior, - textureIndex: - renderLayout.$.celestialBodies[input.instanceIndex].textureIndex, - radiusMultiplier: - renderLayout.$.celestialBodies[input.instanceIndex].radiusMultiplier, - ambientLightFactor: - renderLayout.$.celestialBodies[input.instanceIndex].ambientLightFactor, - destroyed: renderLayout.$.celestialBodies[input.instanceIndex].destroyed, - }); + const currentBody = renderLayout.$.celestialBodies[input.instanceIndex]; - const worldPosition = std.add( - std.mul(radiusOf(currentBody), input.position.xyz), - currentBody.position, + const worldPosition = currentBody.position.add( + input.position.xyz.mul(radiusOf(currentBody)), ); const camera = cameraAccess.$; - const positionOnCanvas = std.mul( - camera.projection, - std.mul(camera.view, d.vec4f(worldPosition, 1)), - ); + const positionOnCanvas = camera.projection + .mul(camera.view) + .mul(d.vec4f(worldPosition, 1)); + return { position: positionOnCanvas, uv: input.uv, @@ -107,22 +91,16 @@ export const mainFragment = tgpu['~unstable'].fragmentFn({ input.sphereTextureIndex, ).xyz; - const ambient = std.mul( - input.ambientLightFactor, - std.mul(textureColor, lightColor), - ); + const ambient = textureColor.mul(lightColor).mul(input.ambientLightFactor); const normal = input.normals; const lightDirection = std.normalize( - std.sub(lightSourceAccess.$, input.worldPosition), + lightSourceAccess.$.sub(input.worldPosition), ); const cosTheta = std.dot(normal, lightDirection); - const diffuse = std.mul( - std.max(0, cosTheta), - std.mul(textureColor, lightColor), - ); + const diffuse = textureColor.mul(lightColor).mul(std.max(0, cosTheta)); - const litColor = std.add(ambient, diffuse); + const litColor = ambient.add(diffuse); return d.vec4f(litColor.xyz, 1); }); diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/schemas.ts b/apps/typegpu-docs/src/examples/simulation/gravity/schemas.ts index 61f64bbcd0..3b3c444a88 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/schemas.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/schemas.ts @@ -2,6 +2,7 @@ import tgpu, { type TgpuSampler } from 'typegpu'; import * as d from 'typegpu/data'; import { Camera } from './setup-orbit-camera.ts'; +export type CelestialBody = d.Infer; export const CelestialBody = d.struct({ destroyed: d.u32, // boolean position: d.vec3f, diff --git a/packages/typegpu/src/data/ref.ts b/packages/typegpu/src/data/ref.ts index 223ca4623d..5b01985ce5 100644 --- a/packages/typegpu/src/data/ref.ts +++ b/packages/typegpu/src/data/ref.ts @@ -1,5 +1,5 @@ import { stitch } from '../core/resolve/stitch.ts'; -import { invariant } from '../errors.ts'; +import { invariant, WgslTypeError } from '../errors.ts'; import { inCodegenMode } from '../execMode.ts'; import { setName } from '../shared/meta.ts'; import { $internal, $isRef, $ownSnippet, $resolve } from '../shared/symbols.ts'; @@ -8,6 +8,7 @@ import { UnknownData } from './dataTypes.ts'; import type { DualFn } from './dualFn.ts'; import { INTERNAL_createPtr } from './ptr.ts'; import { + isEphemeralSnippet, type OriginToPtrParams, originToPtrParams, type ResolvedSnippet, @@ -34,6 +35,11 @@ export interface ref { // TODO: Restrict calls to this function only from within TypeGPU functions export const ref: DualFn<(value: T) => ref> = (() => { const gpuImpl = (value: Snippet) => { + if (!isEphemeralSnippet(value)) { + throw new WgslTypeError( + `Can't create refs from references. Copy the value first by wrapping it in its schema.`, + ); + } return snip(new RefOnGPU(value), UnknownData, /* origin */ 'runtime'); }; diff --git a/packages/typegpu/tests/examples/individual/gravity.test.ts b/packages/typegpu/tests/examples/individual/gravity.test.ts index 4635c83431..65a86bd6c8 100644 --- a/packages/typegpu/tests/examples/individual/gravity.test.ts +++ b/packages/typegpu/tests/examples/individual/gravity.test.ts @@ -47,10 +47,12 @@ describe('gravity example', () => { } fn isSmaller_5(currentId: u32, otherId: u32) -> bool { - if ((inState_1[currentId].mass < inState_1[otherId].mass)) { + let current = (&inState_1[currentId]); + let other = (&inState_1[otherId]); + if (((*current).mass < (*other).mass)) { return true; } - if ((inState_1[currentId].mass == inState_1[otherId].mass)) { + if (((*current).mass == (*other).mass)) { return (currentId < otherId); } return false; @@ -64,19 +66,22 @@ describe('gravity example', () => { @compute @workgroup_size(1) fn computeCollisionsShader_0(input: computeCollisionsShader_Input_7) { let currentId = input.gid.x; - var current = CelestialBody_2(inState_1[currentId].destroyed, inState_1[currentId].position, inState_1[currentId].velocity, inState_1[currentId].mass, inState_1[currentId].radiusMultiplier, inState_1[currentId].collisionBehavior, inState_1[currentId].textureIndex, inState_1[currentId].ambientLightFactor); + var current = inState_1[currentId]; if ((current.destroyed == 0)) { - for (var i = 0; (i < celestialBodiesCount_3); i++) { - let otherId = u32(i); - var other = CelestialBody_2(inState_1[otherId].destroyed, inState_1[otherId].position, inState_1[otherId].velocity, inState_1[otherId].mass, inState_1[otherId].radiusMultiplier, inState_1[otherId].collisionBehavior, inState_1[otherId].textureIndex, inState_1[otherId].ambientLightFactor); - if ((((((u32(i) == input.gid.x) || (other.destroyed == 1)) || (current.collisionBehavior == 0)) || (other.collisionBehavior == 0)) || (distance(current.position, other.position) >= (radiusOf_4(current) + radiusOf_4(other))))) { + for (var otherId = 0u; (otherId < u32(celestialBodiesCount_3)); otherId++) { + let other = (&inState_1[otherId]); + if ((((((otherId == currentId) || ((*other).destroyed == 1)) || (current.collisionBehavior == 0)) || ((*other).collisionBehavior == 0)) || (distance(current.position, (*other).position) >= (radiusOf_4(current) + radiusOf_4((*other)))))) { continue; } - if (((current.collisionBehavior == 1) && (other.collisionBehavior == 1))) { + if (((current.collisionBehavior == 1) && ((*other).collisionBehavior == 1))) { if (isSmaller_5(currentId, otherId)) { - current.position = (other.position + ((radiusOf_4(current) + radiusOf_4(other)) * normalize((current.position - other.position)))); + var dir = normalize((current.position - (*other).position)); + current.position = ((*other).position + (dir * (radiusOf_4(current) + radiusOf_4((*other))))); } - current.velocity = (0.99 * (current.velocity - (((((2 * other.mass) / (current.mass + other.mass)) * dot((current.velocity - other.velocity), (current.position - other.position))) / pow(distance(current.position, other.position), 2)) * (current.position - other.position)))); + var posDiff = (current.position - (*other).position); + var velDiff = (current.velocity - (*other).velocity); + let posDiffFactor = ((((2 * (*other).mass) / (current.mass + (*other).mass)) * dot(velDiff, posDiff)) / dot(posDiff, posDiff)); + current.velocity = ((current.velocity - (posDiff * posDiffFactor)) * 0.99); } else { let isCurrentAbsorbed = ((current.collisionBehavior == 1) || ((current.collisionBehavior == 2) && isSmaller_5(currentId, otherId))); @@ -85,14 +90,14 @@ describe('gravity example', () => { } else { let m1 = current.mass; - let m2 = other.mass; - current.velocity = (((m1 / (m1 + m2)) * current.velocity) + ((m2 / (m1 + m2)) * other.velocity)); + let m2 = (*other).mass; + current.velocity = ((current.velocity * (m1 / (m1 + m2))) + ((*other).velocity * (m2 / (m1 + m2)))); current.mass = (m1 + m2); } } } } - outState_6[input.gid.x] = current; + outState_6[currentId] = current; } struct Time_2 { @@ -130,11 +135,11 @@ describe('gravity example', () => { @compute @workgroup_size(1) fn computeGravityShader_0(input: computeGravityShader_Input_8) { let dt = (time_1.passed * time_1.multiplier); let currentId = input.gid.x; - var current = CelestialBody_4(inState_3[currentId].destroyed, inState_3[currentId].position, inState_3[currentId].velocity, inState_3[currentId].mass, inState_3[currentId].radiusMultiplier, inState_3[currentId].collisionBehavior, inState_3[currentId].textureIndex, inState_3[currentId].ambientLightFactor); + var current = inState_3[currentId]; if ((current.destroyed == 0)) { - for (var i = 0; (i < celestialBodiesCount_5); i++) { - let other = (&inState_3[i]); - if (((u32(i) == input.gid.x) || ((*other).destroyed == 1))) { + for (var otherId = 0u; (otherId < u32(celestialBodiesCount_5)); otherId++) { + let other = (&inState_3[otherId]); + if (((otherId == currentId) || ((*other).destroyed == 1))) { continue; } let dist = max((radiusOf_6(current) + radiusOf_6((*other))), distance(current.position, (*other).position)); @@ -144,7 +149,7 @@ describe('gravity example', () => { } current.position = (current.position + (current.velocity * dt)); } - outState_7[input.gid.x] = current; + outState_7[currentId] = current; } struct Camera_2 { @@ -227,11 +232,11 @@ describe('gravity example', () => { } @vertex fn mainVertex_0(input: mainVertex_Input_7) -> mainVertex_Output_6 { - var currentBody = CelestialBody_2(celestialBodies_1[input.instanceIndex].destroyed, celestialBodies_1[input.instanceIndex].position, celestialBodies_1[input.instanceIndex].velocity, celestialBodies_1[input.instanceIndex].mass, celestialBodies_1[input.instanceIndex].radiusMultiplier, celestialBodies_1[input.instanceIndex].collisionBehavior, celestialBodies_1[input.instanceIndex].textureIndex, celestialBodies_1[input.instanceIndex].ambientLightFactor); - var worldPosition = ((radiusOf_3(currentBody) * input.position.xyz) + currentBody.position); + let currentBody = (&celestialBodies_1[input.instanceIndex]); + var worldPosition = ((*currentBody).position + (input.position.xyz * radiusOf_3((*currentBody)))); let camera = (&camera_4); - var positionOnCanvas = ((*camera).projection * ((*camera).view * vec4f(worldPosition, 1))); - return mainVertex_Output_6(positionOnCanvas, input.uv, input.normal, worldPosition, currentBody.textureIndex, currentBody.destroyed, currentBody.ambientLightFactor); + var positionOnCanvas = (((*camera).projection * (*camera).view) * vec4f(worldPosition, 1)); + return mainVertex_Output_6(positionOnCanvas, input.uv, input.normal, worldPosition, (*currentBody).textureIndex, (*currentBody).destroyed, (*currentBody).ambientLightFactor); } @group(1) @binding(0) var celestialBodyTextures_9: texture_2d_array; @@ -256,11 +261,11 @@ describe('gravity example', () => { } var lightColor = vec3f(1, 0.8999999761581421, 0.8999999761581421); var textureColor = textureSample(celestialBodyTextures_9, sampler_10, input.uv, input.sphereTextureIndex).xyz; - var ambient = (input.ambientLightFactor * (textureColor * lightColor)); + var ambient = ((textureColor * lightColor) * input.ambientLightFactor); var normal = input.normals; var lightDirection = normalize((lightSource_11 - input.worldPosition)); let cosTheta = dot(normal, lightDirection); - var diffuse = (max(0, cosTheta) * (textureColor * lightColor)); + var diffuse = ((textureColor * lightColor) * max(0, cosTheta)); var litColor = (ambient + diffuse); return vec4f(litColor.xyz, 1); }" From 4ccdd96254ab94bc4207248d747ddca8446bff40 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 4 Nov 2025 12:32:40 +0100 Subject: [PATCH 42/59] Working on umiform refs --- .../examples/simulation/gravity/compute.ts | 9 ++--- .../examples/simulation/gravity/helpers.ts | 10 ++---- packages/typegpu/tests/ref.test.ts | 16 +++++++++ packages/typegpu/tests/tgsl/shellless.test.ts | 35 +++++++++++++++++-- 4 files changed, 57 insertions(+), 13 deletions(-) diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts index eb6506fe06..0b30b0555c 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts @@ -37,15 +37,15 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ otherId++ ) { const other = computeLayout.$.inState[otherId]; - // no collision occurs... if ( otherId === currentId || // ...with itself other.destroyed === 1 || // ...when other is destroyed current.collisionBehavior === none || // ...when current behavior is none other.collisionBehavior === none || // ...when other behavior is none std.distance(current.position, other.position) >= - radiusOf(current) + radiusOf(other) // ...when other is too far away + radiusOf(d.ref(current)) + radiusOf(d.ref(other)) // ...when other is too far away ) { + // no collision occurs... continue; } @@ -58,8 +58,9 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ // push the smaller object outside if (isSmaller(currentId, otherId)) { const dir = std.normalize(current.position.sub(other.position)); - current.position = other.position - .add(dir.mul(radiusOf(current) + radiusOf(other))); + current.position = other.position.add( + dir.mul(radiusOf(current) + radiusOf(other)), + ); } // bounce with tiny damping diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts b/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts index 58171f9b75..16ee9b4930 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts @@ -3,11 +3,7 @@ import { OBJLoader } from '@loaders.gl/obj'; import type { TgpuRoot } from 'typegpu'; import * as d from 'typegpu/data'; import { sphereTextureNames } from './enums.ts'; -import { - type CelestialBody, - renderVertexLayout, - SkyBoxVertex, -} from './schemas.ts'; +import { CelestialBody, renderVertexLayout, SkyBoxVertex } from './schemas.ts'; function vert( position: [number, number, number], @@ -166,7 +162,7 @@ export async function loadSphereTextures(root: TgpuRoot) { return texture; } -export const radiusOf = (body: CelestialBody): number => { +export const radiusOf = (body: d.ref): number => { 'use gpu'; - return (((body.mass * 0.75) / Math.PI) ** 0.333) * body.radiusMultiplier; + return (((body.$.mass * 0.75) / Math.PI) ** 0.333) * body.$.radiusMultiplier; }; diff --git a/packages/typegpu/tests/ref.test.ts b/packages/typegpu/tests/ref.test.ts index 515948f1d8..3ff62c65c0 100644 --- a/packages/typegpu/tests/ref.test.ts +++ b/packages/typegpu/tests/ref.test.ts @@ -43,4 +43,20 @@ describe('ref', () => { - fn*:hello: Cannot assign a ref to an existing variable '(&foo)', define a new variable instead.] `); }); + + it('fails when creating a ref with a reference', () => { + const hello = () => { + 'use gpu'; + const position = d.vec3f(1, 2, 3); + const foo = d.ref(position); + }; + + expect(() => asWgsl(hello)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:hello + - fn*:hello + - fn:ref: Can't create refs from references. Copy the value first by wrapping it in its schema.] + `); + }); }); diff --git a/packages/typegpu/tests/tgsl/shellless.test.ts b/packages/typegpu/tests/tgsl/shellless.test.ts index 32beadc815..164e4af15a 100644 --- a/packages/typegpu/tests/tgsl/shellless.test.ts +++ b/packages/typegpu/tests/tgsl/shellless.test.ts @@ -166,10 +166,11 @@ describe('shellless', () => { pos.$.z += vel.z; }; - const main = tgpu.fn([])(() => { + const main = () => { + 'use gpu'; const pos = d.ref(d.vec3f(0, 0, 0)); advance(pos, d.vec3f(1, 2, 3)); - }); + }; expect(asWgsl(main)).toMatchInlineSnapshot(` "fn advance(pos: ptr, vel: vec3f) { @@ -185,6 +186,36 @@ describe('shellless', () => { `); }); + it('generates uniform pointer params when passing a fixed uniform directly to a function', ({ root }) => { + const posUniform = root.createUniform(d.vec3f); + + const sumComponents = (vec: d.ref) => { + 'use gpu'; + return vec.$.x + vec.$.y + vec.$.z; + }; + + const main = () => { + 'use gpu'; + sumComponents(posUniform); + sumComponents(d.ref(posUniform.$.zyx)); + // sumComponents(&posUniform); + }; + + expect(asWgsl(main)).toMatchInlineSnapshot(` + "fn advance(pos: ptr, vel: vec3f) { + (*pos).x += vel.x; + (*pos).y += vel.y; + (*pos).z += vel.z; + } + + fn main() { + var pos = vec3f(); + var vel = vec3f(1, 2, 3); + advance((&pos), (&vel)); + }" + `); + }); + it('resolves when accepting no arguments', () => { const main = () => { 'use gpu'; From 07ee6b7c5ada9074c343d42472e524104af914c6 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 4 Nov 2025 21:10:11 +0100 Subject: [PATCH 43/59] Writing internal docs about shader generation --- apps/typegpu-docs/astro.config.mjs | 4 + .../docs/reference/shader-generation.mdx | 132 ++++++++++++++++++ 2 files changed, 136 insertions(+) create mode 100644 apps/typegpu-docs/src/content/docs/reference/shader-generation.mdx diff --git a/apps/typegpu-docs/astro.config.mjs b/apps/typegpu-docs/astro.config.mjs index 934f3ab512..df1081224f 100644 --- a/apps/typegpu-docs/astro.config.mjs +++ b/apps/typegpu-docs/astro.config.mjs @@ -274,6 +274,10 @@ export default defineConfig({ label: 'Naming Convention', slug: 'reference/naming-convention', }, + DEV && { + label: 'Shader Generation', + slug: 'reference/shader-generation', + }, typeDocSidebarGroup, ]), }, diff --git a/apps/typegpu-docs/src/content/docs/reference/shader-generation.mdx b/apps/typegpu-docs/src/content/docs/reference/shader-generation.mdx new file mode 100644 index 0000000000..a405a5e1d2 --- /dev/null +++ b/apps/typegpu-docs/src/content/docs/reference/shader-generation.mdx @@ -0,0 +1,132 @@ +--- +title: Shader Generation +draft: true +--- + +TypeGPU houses a very powerful shader generator, capable of generating efficient WGSL code that closely matches the input +JavaScript code. + +## The phases of code generation + +The whole end-to-end process of turning JS into WGSL can be split into two phases: +- Parse the JS code into an AST. +- Collapse each AST node into a [snippet](#snippets) depth first, gradually building up the final WGSL code. + +We found that we don't always have enough information to do both phases as a build step (before the code reaches the browser). +For example, the type of a struct could only be known at runtime, or could be imported from another file, which complicates static analysis: + +```ts twoslash +import * as d from 'typegpu/data'; + +declare const getUserSettings: () => Promise<{ halfPrecision: boolean }>; +// ---cut--- +const half = (await getUserSettings()).halfPrecision; + +// Determining the precision based on a runtime parameter +const vec3 = half ? d.vec3h : d.vec3f; + +const Boid = d.struct({ + pos: vec3, + vel: vec3, +}); + +const createBoid = () => { + 'use gpu'; + return Boid({ pos: vec3(), vel: vec3(0, 1, 0) }); +}; + +const boid = createBoid(); +// ^? +``` + +:::caution +We could do everything at runtime, transforming the code a bit so that the TypeGPU shader generator can have access to +a function's JS source code, along with references to values referenced from the outer scope. + +The code shipped to the browser could look like so: +```js +const createBoid = () => { + 'use gpu'; + return Boid({ pos: vec3(), vel: vec3(0, 1, 0) }); +}; + +// Associate metadata with the function, which the TypeGPU generator can later use +(globalThis.__TYPEGPU_META__ ??= new WeakMap()).set(createBoid, { + v: 1, + name: 'createBoid', + code: `() => { + 'use gpu'; + return Boid({ pos: vec3(), vel: vec3(0, 1, 0) }); + }`, + get externals() { return { Boid, vec3 }; }, +}); +``` + +However, parsing code at runtime requires both shipping the parser to the end user, and having to spend time parsing the code, +sacrificing load times and performance. +::: + +In order to avoid parsing at runtime while keeping the desired flexibility, we parse the AST at build time and compress it into +our custom format called [tinyest](https://npmjs.com/package/tinyest). It retains only information required for WGSL code +generation. + +The code shipped to the browser looks more like this: +```js +const createBoid = () => { + 'use gpu'; + return Boid({ pos: vec3(), vel: vec3(0, 1, 0) }); +}; + +(globalThis.__TYPEGPU_META__ ??= new WeakMap()).set(createBoid, { + v: 1, + name: 'createBoid', + // NOTE: Not meant to be read by humans + ast: {params:[],body:[0,[[10,[6,"Boid",[[104,{pos:[6,"vec3",[]],vel:[6,"vec3",[[5,"0"],[5,"1"],[5,"0"]]]}]]]]]],externalNames:["Boid","vec3"]}, + get externals() { return { Boid, vec3 }; }, +}); +``` + +## Snippets + +Snippets are the basis for TypeGPU shader code generation. They are immutable objects that hold three values: +- *value*: A piece of WGSL code, or something "resolvable" to a piece of WGSL code +- *dataType*: The inferred WGSL type of `value` [(more here)](#data-types) +- *origin*: An enumerable of where the value came from (if it's a reference to an existing value, or ephemeral) +[(more here)](#origins) + +```ts +// A simple snippet of a piece of WGSL code +const foo = snip( + /* value */ 'vec3f(1, 2, 3)', + /* dataType */ d.vec3f, + /* origin */ 'constant' +); // => Snippet + +// A simple snippet of something resolvable to a piece of WGSL code +const bar = snip( + /* value */ d.vec3f(1, 2, 3), + /* dataType */ d.vec3f, + /* origin */ 'constant' +); // => Snippet +``` + +If a snippet contains a value that isn't yet resolved WGSL, we defer that resolution as late as possible, so that we can +perform optimizations as we generate. For example, if we're evaluating the given expression `3 * 4`, we first interpret +both operands as snippets `snip(3, abstractInt, 'constant')` and `snip(4, abstractInt, 'constant')` respectively. +Since both are not yet resolved (or in other words, known at compile time), we can perform the multiplication at compile time, +resulting in a new snippet `snip(12, abstractInt, 'constant')`. + +:::note +If we were instead resolving eagerly, the resulting snippet would be `snip('3 * 4', abstractInt, 'constant')`. +::: + +### Data Types + +The data types that accompany snippets are just [TypeGPU Data Schemas](/TypeGPU/fundamentals/data-schemas). This information +can be used by parent expressions to generate different code. + +:::note +Data type inference is the basis for generating signatures for functions just from the arguments passed to them. +::: + +### Origins From 79252cb798d12eab5518251828e7f096bfdac8b8 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 4 Nov 2025 22:07:36 +0100 Subject: [PATCH 44/59] More useful refs --- .../examples/simulation/gravity/compute.ts | 4 +- .../src/examples/simulation/gravity/render.ts | 2 +- .../src/core/function/shelllessImpl.ts | 4 +- packages/typegpu/src/data/ptr.ts | 18 ++ packages/typegpu/src/data/ref.ts | 82 ++++-- packages/typegpu/src/tgsl/shellless.ts | 19 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 5 + packages/typegpu/tests/constant.test.ts | 4 +- .../tests/examples/individual/gravity.test.ts | 253 +++++++++++++++++- packages/typegpu/tests/ref.test.ts | 5 +- packages/typegpu/tests/tgsl/shellless.test.ts | 17 +- 11 files changed, 365 insertions(+), 48 deletions(-) diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts index 0b30b0555c..0ab8534b9c 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts @@ -59,7 +59,7 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ if (isSmaller(currentId, otherId)) { const dir = std.normalize(current.position.sub(other.position)); current.position = other.position.add( - dir.mul(radiusOf(current) + radiusOf(other)), + dir.mul(radiusOf(d.ref(current)) + radiusOf(d.ref(other))), ); } @@ -118,7 +118,7 @@ export const computeGravityShader = tgpu['~unstable'].computeFn({ } const dist = std.max( - radiusOf(current) + radiusOf(other), + radiusOf(d.ref(current)) + radiusOf(d.ref(other)), std.distance(current.position, other.position), ); const gravityForce = (current.mass * other.mass) / dist / dist; diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/render.ts b/apps/typegpu-docs/src/examples/simulation/gravity/render.ts index b88d7e1238..5a7de3e2b5 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/render.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/render.ts @@ -56,7 +56,7 @@ export const mainVertex = tgpu['~unstable'].vertexFn({ const currentBody = renderLayout.$.celestialBodies[input.instanceIndex]; const worldPosition = currentBody.position.add( - input.position.xyz.mul(radiusOf(currentBody)), + input.position.xyz.mul(radiusOf(d.ref(currentBody))), ); const camera = cameraAccess.$; diff --git a/packages/typegpu/src/core/function/shelllessImpl.ts b/packages/typegpu/src/core/function/shelllessImpl.ts index 14e4f15c86..7a824af85b 100644 --- a/packages/typegpu/src/core/function/shelllessImpl.ts +++ b/packages/typegpu/src/core/function/shelllessImpl.ts @@ -44,7 +44,9 @@ export function createShelllessImpl( }, toString(): string { - return `fn*:${getName(core) ?? ''}`; + return `fn*:${getName(core) ?? ''}(${ + argTypes.map((t) => t.toString()).join(', ') + })`; }, }; } diff --git a/packages/typegpu/src/data/ptr.ts b/packages/typegpu/src/data/ptr.ts index 99917c1a82..8f546923fb 100644 --- a/packages/typegpu/src/data/ptr.ts +++ b/packages/typegpu/src/data/ptr.ts @@ -1,4 +1,5 @@ import { $internal } from '../shared/symbols.ts'; +import { Origin, OriginToPtrParams, originToPtrParams } from './snippet.ts'; import type { Access, AddressSpace, Ptr, StorableData } from './wgslTypes.ts'; export function ptrFn( @@ -58,3 +59,20 @@ export function INTERNAL_createPtr< toString: () => `ptr<${addressSpace}, ${inner}, ${access}>`, } as Ptr; } + +export function createPtrFromOrigin( + origin: Origin, + innerDataType: StorableData, +): Ptr | undefined { + const ptrParams = originToPtrParams[origin as keyof OriginToPtrParams]; + + if (ptrParams) { + return INTERNAL_createPtr( + ptrParams.space, + innerDataType, + ptrParams.access, + ); + } + + return undefined; +} diff --git a/packages/typegpu/src/data/ref.ts b/packages/typegpu/src/data/ref.ts index 5b01985ce5..4c4f681221 100644 --- a/packages/typegpu/src/data/ref.ts +++ b/packages/typegpu/src/data/ref.ts @@ -1,20 +1,13 @@ import { stitch } from '../core/resolve/stitch.ts'; -import { invariant, WgslTypeError } from '../errors.ts'; +import { invariant } from '../errors.ts'; import { inCodegenMode } from '../execMode.ts'; import { setName } from '../shared/meta.ts'; import { $internal, $isRef, $ownSnippet, $resolve } from '../shared/symbols.ts'; import type { ResolutionCtx, SelfResolvable } from '../types.ts'; import { UnknownData } from './dataTypes.ts'; import type { DualFn } from './dualFn.ts'; -import { INTERNAL_createPtr } from './ptr.ts'; -import { - isEphemeralSnippet, - type OriginToPtrParams, - originToPtrParams, - type ResolvedSnippet, - snip, - type Snippet, -} from './snippet.ts'; +import { createPtrFromOrigin } from './ptr.ts'; +import { type ResolvedSnippet, snip, type Snippet } from './snippet.ts'; import { isNaturallyEphemeral, isPtr, @@ -29,17 +22,25 @@ import { export interface ref { readonly [$internal]: unknown; readonly [$isRef]: true; + + /** + * Derefences the reference, and gives access to the underlying value. + * + * @example ```ts + * const boid = Boid({ pos: d.vec3f(3, 2, 1) }); + * const posRef = d.ref(boid.pos); + * + * // Actually updates `boid.pos` + * posRef.$ = d.vec3f(1, 2, 3); + * console.log(boid.pos); // Output: vec3f(1, 2, 3) + * ``` + */ $: T; } // TODO: Restrict calls to this function only from within TypeGPU functions export const ref: DualFn<(value: T) => ref> = (() => { const gpuImpl = (value: Snippet) => { - if (!isEphemeralSnippet(value)) { - throw new WgslTypeError( - `Can't create refs from references. Copy the value first by wrapping it in its schema.`, - ); - } return snip(new RefOnGPU(value), UnknownData, /* origin */ 'runtime'); }; @@ -71,11 +72,11 @@ export const ref: DualFn<(value: T) => ref> = (() => { // -------------- class refImpl implements ref { - readonly #value: T | string; + #value: T; readonly [$internal]: true; readonly [$isRef]: true; - constructor(value: T | string) { + constructor(value: T) { this.#value = value; this[$internal] = true; this[$isRef] = true; @@ -84,20 +85,49 @@ class refImpl implements ref { get $(): T { return this.#value as T; } + + set $(value: T) { + if (value && typeof value === 'object') { + // Setting an object means updating the properties of the original object. + // e.g.: foo.$ = Boid(); + for (const key of Object.keys(value) as (keyof T)[]) { + this.#value[key] = value[key]; + } + } else { + this.#value = value; + } + } } export class RefOnGPU { - readonly snippet: Snippet; readonly [$internal]: true; + readonly snippet: Snippet; + /** + * Pointer params only exist if the ref was created from a reference (buttery-butter). + */ + readonly ptrType: Ptr | undefined; + constructor(snippet: Snippet) { - this.snippet = snippet; this[$internal] = true; + this.snippet = snippet; + this.ptrType = createPtrFromOrigin( + snippet.origin, + snippet.dataType as StorableData, + ); } toString(): string { return `ref:${this.snippet.value}`; } + + [$resolve](ctx: ResolutionCtx): ResolvedSnippet { + invariant( + !!this.ptrType, + 'RefOnGPU must have a pointer type when resolved', + ); + return snip(stitch`(&${this.snippet})`, this.ptrType, this.snippet.origin); + } } export class RefOperator implements SelfResolvable { @@ -109,20 +139,18 @@ export class RefOperator implements SelfResolvable { this[$internal] = true; this.snippet = snippet; - const ptrParams = - originToPtrParams[this.snippet.origin as keyof OriginToPtrParams]; + const ptrType = createPtrFromOrigin( + snippet.origin, + snippet.dataType as StorableData, + ); - if (!ptrParams) { + if (!ptrType) { throw new Error( `Cannot take a reference of a value with origin ${this.snippet.origin}`, ); } - this.#ptrType = INTERNAL_createPtr( - ptrParams.space, - this.snippet.dataType as StorableData, - ptrParams.access, - ); + this.#ptrType = ptrType; } get [$ownSnippet](): Snippet { diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index cffa071cdd..bca6141528 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -3,8 +3,10 @@ import { type ShelllessImpl, } from '../core/function/shelllessImpl.ts'; import type { AnyData } from '../data/dataTypes.ts'; +import { RefOnGPU } from '../data/ref.ts'; import type { Snippet } from '../data/snippet.ts'; import { isPtr } from '../data/wgslTypes.ts'; +import { WgslTypeError } from '../errors.ts'; import { getResolutionCtx } from '../execMode.ts'; import { getMetaData, getName } from '../shared/meta.ts'; import { concretize } from './generationHelpers.ts'; @@ -47,7 +49,22 @@ export class ShelllessRepository { ); } - const argTypes = (argSnippets ?? []).map((s) => { + const argTypes = (argSnippets ?? []).map((s, index) => { + if (s.value instanceof RefOnGPU) { + if (!s.value.ptrType) { + throw new WgslTypeError( + `d.ref() created with primitive types must be stored in a variable before use`, + ); + } + return s.value.ptrType; + } + + if (s.dataType.type === 'unknown') { + throw new Error( + `Passed illegal value ${s.value} as the #${index} argument to ${meta.name}(...)`, + ); + } + let type = concretize(s.dataType as AnyData); if (s.origin === 'constant-ref') { diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index aba6e044bb..a7f7f37583 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -782,6 +782,11 @@ ${this.ctx.pre}else ${alternate}`; if (eq.value instanceof RefOnGPU) { // We're assigning a newly created `d.ref()` + if (eq.value.ptrType) { + throw new WgslTypeError( + `Cannot store d.ref() in a variable if it references another value. Copy the value passed into d.ref() instead.`, + ); + } const refSnippet = eq.value.snippet; const varName = this.refVariable( rawId, diff --git a/packages/typegpu/tests/constant.test.ts b/packages/typegpu/tests/constant.test.ts index fc761776f3..2588f5513e 100644 --- a/packages/typegpu/tests/constant.test.ts +++ b/packages/typegpu/tests/constant.test.ts @@ -64,7 +64,7 @@ describe('tgpu.const', () => { [Error: Resolution of the following tree failed: - - fn*:fn2 - - fn*:fn2: Cannot pass constant references as function arguments. Explicitly copy them by wrapping them in a schema: 'vec3f(...)'] + - fn*:fn2(): Cannot pass constant references as function arguments. Explicitly copy them by wrapping them in a schema: 'vec3f(...)'] `); }); @@ -84,7 +84,7 @@ describe('tgpu.const', () => { [Error: Resolution of the following tree failed: - - fn*:fn - - fn*:fn: 'boid.pos = vec3f()' is invalid, because boid.pos is a constant.] + - fn*:fn(): 'boid.pos = vec3f()' is invalid, because boid.pos is a constant.] `); // Since we freeze the object, we cannot mutate when running the function in JS either diff --git a/packages/typegpu/tests/examples/individual/gravity.test.ts b/packages/typegpu/tests/examples/individual/gravity.test.ts index cc1637cdf2..068a93f803 100644 --- a/packages/typegpu/tests/examples/individual/gravity.test.ts +++ b/packages/typegpu/tests/examples/individual/gravity.test.ts @@ -26,6 +26,257 @@ describe('gravity example', () => { expectedCalls: 4, }, device); - expect(shaderCodes).toMatchInlineSnapshot(`""`); + expect(shaderCodes).toMatchInlineSnapshot(` + "struct CelestialBody_2 { + destroyed: u32, + position: vec3f, + velocity: vec3f, + mass: f32, + radiusMultiplier: f32, + collisionBehavior: u32, + textureIndex: u32, + ambientLightFactor: f32, + } + + @group(0) @binding(1) var inState_1: array; + + @group(0) @binding(0) var celestialBodiesCount_3: i32; + + fn radiusOf_4(body: ptr) -> f32 { + return (pow((((*body).mass * 0.75f) / 3.141592653589793f), 0.333f) * (*body).radiusMultiplier); + } + + fn radiusOf_5(body: ptr, read>) -> f32 { + return (pow((((*(*body)).mass * 0.75f) / 3.141592653589793f), 0.333f) * (*(*body)).radiusMultiplier); + } + + fn isSmaller_6(currentId: u32, otherId: u32) -> bool { + let current = (&inState_1[currentId]); + let other = (&inState_1[otherId]); + if (((*current).mass < (*other).mass)) { + return true; + } + if (((*current).mass == (*other).mass)) { + return (currentId < otherId); + } + return false; + } + + @group(0) @binding(2) var outState_7: array; + + struct computeCollisionsShader_Input_8 { + @builtin(global_invocation_id) gid: vec3u, + } + + @compute @workgroup_size(1) fn computeCollisionsShader_0(input: computeCollisionsShader_Input_8) { + let currentId = input.gid.x; + var current = inState_1[currentId]; + if ((current.destroyed == 0u)) { + for (var otherId = 0u; (otherId < u32(celestialBodiesCount_3)); otherId++) { + let other = (&inState_1[otherId]); + if ((((((otherId == currentId) || ((*other).destroyed == 1u)) || (current.collisionBehavior == 0u)) || ((*other).collisionBehavior == 0u)) || (distance(current.position, (*other).position) >= (radiusOf_4((¤t)) + radiusOf_5((&other)))))) { + continue; + } + if (((current.collisionBehavior == 1u) && ((*other).collisionBehavior == 1u))) { + if (isSmaller_6(currentId, otherId)) { + var dir = normalize((current.position - (*other).position)); + current.position = ((*other).position + (dir * (radiusOf_4((¤t)) + radiusOf_5((&other))))); + } + var posDiff = (current.position - (*other).position); + var velDiff = (current.velocity - (*other).velocity); + let posDiffFactor = ((((2f * (*other).mass) / (current.mass + (*other).mass)) * dot(velDiff, posDiff)) / dot(posDiff, posDiff)); + current.velocity = ((current.velocity - (posDiff * posDiffFactor)) * 0.99); + } + else { + let isCurrentAbsorbed = ((current.collisionBehavior == 1u) || ((current.collisionBehavior == 2u) && isSmaller_6(currentId, otherId))); + if (isCurrentAbsorbed) { + current.destroyed = 1u; + } + else { + let m1 = current.mass; + let m2 = (*other).mass; + current.velocity = ((current.velocity * (m1 / (m1 + m2))) + ((*other).velocity * (m2 / (m1 + m2)))); + current.mass = (m1 + m2); + } + } + } + } + outState_7[currentId] = current; + } + + struct Time_2 { + passed: f32, + multiplier: f32, + } + + @group(0) @binding(0) var time_1: Time_2; + + struct CelestialBody_4 { + destroyed: u32, + position: vec3f, + velocity: vec3f, + mass: f32, + radiusMultiplier: f32, + collisionBehavior: u32, + textureIndex: u32, + ambientLightFactor: f32, + } + + @group(1) @binding(1) var inState_3: array; + + @group(1) @binding(0) var celestialBodiesCount_5: i32; + + fn radiusOf_6(body: ptr) -> f32 { + return (pow((((*body).mass * 0.75f) / 3.141592653589793f), 0.333f) * (*body).radiusMultiplier); + } + + fn radiusOf_7(body: ptr, read>) -> f32 { + return (pow((((*(*body)).mass * 0.75f) / 3.141592653589793f), 0.333f) * (*(*body)).radiusMultiplier); + } + + @group(1) @binding(2) var outState_8: array; + + struct computeGravityShader_Input_9 { + @builtin(global_invocation_id) gid: vec3u, + } + + @compute @workgroup_size(1) fn computeGravityShader_0(input: computeGravityShader_Input_9) { + let dt = (time_1.passed * time_1.multiplier); + let currentId = input.gid.x; + var current = inState_3[currentId]; + if ((current.destroyed == 0u)) { + for (var otherId = 0u; (otherId < u32(celestialBodiesCount_5)); otherId++) { + let other = (&inState_3[otherId]); + if (((otherId == currentId) || ((*other).destroyed == 1u))) { + continue; + } + let dist = max((radiusOf_6((¤t)) + radiusOf_7((&other))), distance(current.position, (*other).position)); + let gravityForce = (((current.mass * (*other).mass) / dist) / dist); + var direction = normalize(((*other).position - current.position)); + current.velocity = (current.velocity + (direction * ((gravityForce / current.mass) * dt))); + } + current.position = (current.position + (current.velocity * dt)); + } + outState_8[currentId] = current; + } + + struct Camera_2 { + position: vec4f, + targetPos: vec4f, + view: mat4x4f, + projection: mat4x4f, + } + + @group(0) @binding(0) var camera_1: Camera_2; + + struct skyBoxVertex_Output_3 { + @builtin(position) pos: vec4f, + @location(0) texCoord: vec3f, + } + + struct skyBoxVertex_Input_4 { + @location(0) position: vec3f, + @location(1) uv: vec2f, + } + + @vertex fn skyBoxVertex_0(input: skyBoxVertex_Input_4) -> skyBoxVertex_Output_3 { + var viewPos = (camera_1.view * vec4f(input.position, 0f)).xyz; + return skyBoxVertex_Output_3((camera_1.projection * vec4f(viewPos, 1f)), input.position.xyz); + } + + @group(0) @binding(1) var item_6: texture_cube; + + @group(0) @binding(2) var sampler_7: sampler; + + struct skyBoxFragment_Input_8 { + @location(0) texCoord: vec3f, + } + + @fragment fn skyBoxFragment_5(input: skyBoxFragment_Input_8) -> @location(0) vec4f { + return textureSample(item_6, sampler_7, normalize(input.texCoord)); + } + + struct CelestialBody_2 { + destroyed: u32, + position: vec3f, + velocity: vec3f, + mass: f32, + radiusMultiplier: f32, + collisionBehavior: u32, + textureIndex: u32, + ambientLightFactor: f32, + } + + @group(1) @binding(1) var celestialBodies_1: array; + + fn radiusOf_3(body: ptr, read>) -> f32 { + return (pow((((*(*body)).mass * 0.75f) / 3.141592653589793f), 0.333f) * (*(*body)).radiusMultiplier); + } + + struct Camera_5 { + position: vec4f, + targetPos: vec4f, + view: mat4x4f, + projection: mat4x4f, + } + + @group(0) @binding(0) var camera_4: Camera_5; + + struct mainVertex_Output_6 { + @builtin(position) position: vec4f, + @location(0) uv: vec2f, + @location(1) normals: vec3f, + @location(2) worldPosition: vec3f, + @location(3) @interpolate(flat) sphereTextureIndex: u32, + @location(4) @interpolate(flat) destroyed: u32, + @location(5) ambientLightFactor: f32, + } + + struct mainVertex_Input_7 { + @location(0) position: vec3f, + @location(1) normal: vec3f, + @location(2) uv: vec2f, + @builtin(instance_index) instanceIndex: u32, + } + + @vertex fn mainVertex_0(input: mainVertex_Input_7) -> mainVertex_Output_6 { + let currentBody = (&celestialBodies_1[input.instanceIndex]); + var worldPosition = ((*currentBody).position + (input.position.xyz * radiusOf_3((¤tBody)))); + let camera = (&camera_4); + var positionOnCanvas = (((*camera).projection * (*camera).view) * vec4f(worldPosition, 1f)); + return mainVertex_Output_6(positionOnCanvas, input.uv, input.normal, worldPosition, (*currentBody).textureIndex, (*currentBody).destroyed, (*currentBody).ambientLightFactor); + } + + @group(1) @binding(0) var celestialBodyTextures_9: texture_2d_array; + + @group(0) @binding(1) var sampler_10: sampler; + + @group(0) @binding(2) var lightSource_11: vec3f; + + struct mainFragment_Input_12 { + @builtin(position) position: vec4f, + @location(0) uv: vec2f, + @location(1) normals: vec3f, + @location(2) worldPosition: vec3f, + @location(3) @interpolate(flat) sphereTextureIndex: u32, + @location(4) @interpolate(flat) destroyed: u32, + @location(5) ambientLightFactor: f32, + } + + @fragment fn mainFragment_8(input: mainFragment_Input_12) -> @location(0) vec4f { + if ((input.destroyed == 1u)) { + discard;; + } + var lightColor = vec3f(1, 0.8999999761581421, 0.8999999761581421); + var textureColor = textureSample(celestialBodyTextures_9, sampler_10, input.uv, input.sphereTextureIndex).xyz; + var ambient = ((textureColor * lightColor) * input.ambientLightFactor); + var normal = input.normals; + var lightDirection = normalize((lightSource_11 - input.worldPosition)); + let cosTheta = dot(normal, lightDirection); + var diffuse = ((textureColor * lightColor) * max(0f, cosTheta)); + var litColor = (ambient + diffuse); + return vec4f(litColor.xyz, 1f); + }" + `); }); }); diff --git a/packages/typegpu/tests/ref.test.ts b/packages/typegpu/tests/ref.test.ts index 3ff62c65c0..30d54e200e 100644 --- a/packages/typegpu/tests/ref.test.ts +++ b/packages/typegpu/tests/ref.test.ts @@ -40,7 +40,7 @@ describe('ref', () => { [Error: Resolution of the following tree failed: - - fn*:hello - - fn*:hello: Cannot assign a ref to an existing variable '(&foo)', define a new variable instead.] + - fn*:hello(): Cannot assign a ref to an existing variable '(&foo)', define a new variable instead.] `); }); @@ -55,8 +55,7 @@ describe('ref', () => { [Error: Resolution of the following tree failed: - - fn*:hello - - fn*:hello - - fn:ref: Can't create refs from references. Copy the value first by wrapping it in its schema.] + - fn*:hello(): Cannot store d.ref() in a variable if it references another value. Copy the value passed into d.ref() instead.] `); }); }); diff --git a/packages/typegpu/tests/tgsl/shellless.test.ts b/packages/typegpu/tests/tgsl/shellless.test.ts index cd561f8877..5512f1e64c 100644 --- a/packages/typegpu/tests/tgsl/shellless.test.ts +++ b/packages/typegpu/tests/tgsl/shellless.test.ts @@ -124,7 +124,7 @@ describe('shellless', () => { [Error: Resolution of the following tree failed: - - fn:main - - fn*:someFn: Expected function to have a single return type, got [u32, i32, f32]. Cast explicitly to the desired type.] + - fn*:someFn(f32, i32): Expected function to have a single return type, got [u32, i32, f32]. Cast explicitly to the desired type.] `); }); @@ -196,22 +196,19 @@ describe('shellless', () => { const main = () => { 'use gpu'; - sumComponents(posUniform); - sumComponents(d.ref(posUniform.$.zyx)); + sumComponents(d.ref(posUniform.$)); // sumComponents(&posUniform); }; expect(asWgsl(main)).toMatchInlineSnapshot(` - "fn advance(pos: ptr, vel: vec3f) { - (*pos).x += vel.x; - (*pos).y += vel.y; - (*pos).z += vel.z; + "@group(0) @binding(0) var posUniform: vec3f; + + fn sumComponents(vec: ptr) -> f32 { + return (((*vec).x + (*vec).y) + (*vec).z); } fn main() { - var pos = vec3f(); - var vel = vec3f(1, 2, 3); - advance((&pos), (&vel)); + sumComponents((&posUniform)); }" `); }); From c853f5c3f1ba5b8b9c6135107046f997a86e0d1d Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 4 Nov 2025 22:16:11 +0100 Subject: [PATCH 45/59] Simplify and document --- packages/typegpu/src/data/ref.ts | 59 +++++++--------------- packages/typegpu/src/tgsl/shellless.ts | 4 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 6 +-- 3 files changed, 23 insertions(+), 46 deletions(-) diff --git a/packages/typegpu/src/data/ref.ts b/packages/typegpu/src/data/ref.ts index 4c4f681221..be4aa2b56b 100644 --- a/packages/typegpu/src/data/ref.ts +++ b/packages/typegpu/src/data/ref.ts @@ -41,7 +41,7 @@ export interface ref { // TODO: Restrict calls to this function only from within TypeGPU functions export const ref: DualFn<(value: T) => ref> = (() => { const gpuImpl = (value: Snippet) => { - return snip(new RefOnGPU(value), UnknownData, /* origin */ 'runtime'); + return snip(new RefOperator(value), UnknownData, /* origin */ 'runtime'); }; const jsImpl = (value: T) => new refImpl(value); @@ -99,66 +99,43 @@ class refImpl implements ref { } } -export class RefOnGPU { +export class RefOperator implements SelfResolvable { readonly [$internal]: true; - readonly snippet: Snippet; + /** * Pointer params only exist if the ref was created from a reference (buttery-butter). + * + * @example + * ```ts + * const life = ref(42); // created from a value + * const boid = ref(layout.$.boids[0]); // created from a reference + * ``` */ readonly ptrType: Ptr | undefined; constructor(snippet: Snippet) { this[$internal] = true; this.snippet = snippet; - this.ptrType = createPtrFromOrigin( - snippet.origin, - snippet.dataType as StorableData, - ); - } - - toString(): string { - return `ref:${this.snippet.value}`; - } - [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - invariant( - !!this.ptrType, - 'RefOnGPU must have a pointer type when resolved', - ); - return snip(stitch`(&${this.snippet})`, this.ptrType, this.snippet.origin); - } -} - -export class RefOperator implements SelfResolvable { - readonly [$internal]: true; - readonly snippet: Snippet; - readonly #ptrType: Ptr; - - constructor(snippet: Snippet) { - this[$internal] = true; - this.snippet = snippet; - - const ptrType = createPtrFromOrigin( + this.ptrType = createPtrFromOrigin( snippet.origin, snippet.dataType as StorableData, ); - - if (!ptrType) { - throw new Error( - `Cannot take a reference of a value with origin ${this.snippet.origin}`, - ); - } - - this.#ptrType = ptrType; } get [$ownSnippet](): Snippet { - return snip(this, this.#ptrType, this.snippet.origin); + if (!this.ptrType) { + throw new Error(stitch`Cannot take a reference of ${this.snippet}`); + } + return snip(this, this.ptrType, this.snippet.origin); } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - return snip(stitch`(&${this.snippet})`, this.#ptrType, this.snippet.origin); + if (!this.ptrType) { + throw new Error(stitch`Cannot take a reference of ${this.snippet}`); + } + return snip(stitch`(&${this.snippet})`, this.ptrType, this.snippet.origin); } } diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index bca6141528..a05048c589 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -3,7 +3,7 @@ import { type ShelllessImpl, } from '../core/function/shelllessImpl.ts'; import type { AnyData } from '../data/dataTypes.ts'; -import { RefOnGPU } from '../data/ref.ts'; +import { RefOperator } from '../data/ref.ts'; import type { Snippet } from '../data/snippet.ts'; import { isPtr } from '../data/wgslTypes.ts'; import { WgslTypeError } from '../errors.ts'; @@ -50,7 +50,7 @@ export class ShelllessRepository { } const argTypes = (argSnippets ?? []).map((s, index) => { - if (s.value instanceof RefOnGPU) { + if (s.value instanceof RefOperator) { if (!s.value.ptrType) { throw new WgslTypeError( `d.ref() created with primitive types must be stored in a variable before use`, diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index a7f7f37583..a445314286 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -42,7 +42,7 @@ import { import type { ShaderGenerator } from './shaderGenerator.ts'; import type { DualFn } from '../data/dualFn.ts'; import { INTERNAL_createPtr, ptrFn } from '../data/ptr.ts'; -import { RefOnGPU, RefOperator } from '../data/ref.ts'; +import { RefOperator } from '../data/ref.ts'; import { constant } from '../core/constant/tgpuConstant.ts'; const { NodeTypeCatalog: NODE } = tinyest; @@ -241,7 +241,7 @@ ${this.ctx.pre}}`; const lhsExpr = this.expression(lhs); const rhsExpr = this.expression(rhs); - if (rhsExpr.value instanceof RefOnGPU) { + if (rhsExpr.value instanceof RefOperator) { throw new WgslTypeError( stitch`Cannot assign a ref to an existing variable '${lhsExpr}', define a new variable instead.`, ); @@ -780,7 +780,7 @@ ${this.ctx.pre}else ${alternate}`; ); } - if (eq.value instanceof RefOnGPU) { + if (eq.value instanceof RefOperator) { // We're assigning a newly created `d.ref()` if (eq.value.ptrType) { throw new WgslTypeError( From 21776b322b9c19b0cce9980d7d7d40ebbc6e4acd Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 4 Nov 2025 22:59:44 +0100 Subject: [PATCH 46/59] Test for updating a whole struct, returning refs --- packages/typegpu/src/data/ref.ts | 23 ++-- packages/typegpu/src/tgsl/conversion.ts | 9 +- .../typegpu/src/tgsl/generationHelpers.ts | 4 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 11 +- packages/typegpu/tests/ref.test.ts | 116 +++++++++++++++++- .../typegpu/tests/tgsl/conversion.test.ts | 19 ++- packages/typegpu/tests/tgsl/shellless.test.ts | 9 +- 7 files changed, 170 insertions(+), 21 deletions(-) diff --git a/packages/typegpu/src/data/ref.ts b/packages/typegpu/src/data/ref.ts index be4aa2b56b..cb34424b76 100644 --- a/packages/typegpu/src/data/ref.ts +++ b/packages/typegpu/src/data/ref.ts @@ -41,7 +41,15 @@ export interface ref { // TODO: Restrict calls to this function only from within TypeGPU functions export const ref: DualFn<(value: T) => ref> = (() => { const gpuImpl = (value: Snippet) => { - return snip(new RefOperator(value), UnknownData, /* origin */ 'runtime'); + const ptrType = createPtrFromOrigin( + value.origin, + value.dataType as StorableData, + ); + return snip( + new RefOperator(value, ptrType), + ptrType ?? UnknownData, + /* origin */ 'runtime', + ); }; const jsImpl = (value: T) => new refImpl(value); @@ -99,6 +107,11 @@ class refImpl implements ref { } } +/** + * The result of calling `d.ref(...)`. The code responsible for + * generating shader code can check if the value of a snippet is + * an instance of `RefOperator`, and act accordingly. + */ export class RefOperator implements SelfResolvable { readonly [$internal]: true; readonly snippet: Snippet; @@ -114,14 +127,10 @@ export class RefOperator implements SelfResolvable { */ readonly ptrType: Ptr | undefined; - constructor(snippet: Snippet) { + constructor(snippet: Snippet, ptrType: Ptr | undefined) { this[$internal] = true; this.snippet = snippet; - - this.ptrType = createPtrFromOrigin( - snippet.origin, - snippet.dataType as StorableData, - ); + this.ptrType = ptrType; } get [$ownSnippet](): Snippet { diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 3cdf24f468..1c63ca0d95 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -10,6 +10,7 @@ import { type I32, isMat, isVec, + Ptr, type U32, type WgslStruct, } from '../data/wgslTypes.ts'; @@ -73,6 +74,8 @@ function getImplicitConversionRank( if ( trueSrc.type === 'ptr' && + // Only dereferencing implicit pointers, otherwise we'd have a types mismatch between TS and WGSL + trueSrc.implicit && getAutoConversionRank(trueSrc.inner as AnyData, trueDst).rank < Number.POSITIVE_INFINITY ) { @@ -240,7 +243,11 @@ function applyActionToSnippet( switch (action.action) { case 'ref': - return snip(new RefOperator(snippet), targetType, snippet.origin); + return snip( + new RefOperator(snippet, targetType as Ptr), + targetType, + snippet.origin, + ); case 'deref': return derefSnippet(snippet); case 'cast': { diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 7b0b2fae67..05ce7e7feb 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -394,11 +394,11 @@ export function concretizeSnippets(args: Snippet[]): Snippet[] { export type GenerationCtx = ResolutionCtx & { readonly pre: string; /** - * Used by `generateTypedExpression` to signal downstream + * Used by `typedExpression` to signal downstream * expression resolution what type is expected of them. * * It is used exclusively for inferring the types of structs and arrays. - * It is modified exclusively by `generateTypedExpression` function. + * It is modified exclusively by `typedExpression` function. */ expectedType: AnyData | undefined; diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index a445314286..46705e70fd 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -153,9 +153,10 @@ ${this.ctx.pre}}`; dataType: wgsl.StorableData, ): string { const varName = this.ctx.makeNameValid(id); + const ptrType = ptrFn(dataType); const snippet = snip( - new RefOperator(snip(varName, dataType, 'function')), - ptrFn(dataType), + new RefOperator(snip(varName, dataType, 'function'), ptrType), + ptrType, 'function', ); this.ctx.defineVariable(id, snippet); @@ -697,6 +698,12 @@ ${this.ctx.pre}}`; ) : this.expression(returnNode); + if (returnSnippet.value instanceof RefOperator) { + throw new WgslTypeError( + stitch`Cannot return references, returning '${returnSnippet.value.snippet}'`, + ); + } + if ( !expectedReturnType && !isEphemeralSnippet(returnSnippet) && diff --git a/packages/typegpu/tests/ref.test.ts b/packages/typegpu/tests/ref.test.ts index 30d54e200e..373c7e434d 100644 --- a/packages/typegpu/tests/ref.test.ts +++ b/packages/typegpu/tests/ref.test.ts @@ -44,7 +44,7 @@ describe('ref', () => { `); }); - it('fails when creating a ref with a reference', () => { + it('fails when creating a ref with a reference, and assigning it to a variable', () => { const hello = () => { 'use gpu'; const position = d.vec3f(1, 2, 3); @@ -58,4 +58,118 @@ describe('ref', () => { - fn*:hello(): Cannot store d.ref() in a variable if it references another value. Copy the value passed into d.ref() instead.] `); }); + + it('allows updating a whole struct from another function', () => { + type Entity = d.Infer; + const Entity = d.struct({ pos: d.vec3f }); + + const clearEntity = (entity: d.ref) => { + 'use gpu'; + entity.$ = Entity({ pos: d.vec3f(0, 0, 0) }); + }; + + const main = () => { + 'use gpu'; + const entity = Entity({ pos: d.vec3f(1, 2, 3) }); + clearEntity(d.ref(entity)); + // entity.pos should be vec3f(0, 0, 0) + return entity; + }; + + // Works in JS + expect(main().pos).toStrictEqual(d.vec3f(0, 0, 0)); + + // And on the GPU + expect(asWgsl(main)).toMatchInlineSnapshot(` + "struct Entity { + pos: vec3f, + } + + fn clearEntity(entity: ptr) { + (*entity) = Entity(vec3f()); + } + + fn main() -> Entity { + var entity = Entity(vec3f(1, 2, 3)); + clearEntity((&entity)); + return entity; + }" + `); + }); + + it('allows updating a number from another function', () => { + const increment = (value: d.ref) => { + 'use gpu'; + value.$ += 1; + }; + + const main = () => { + 'use gpu'; + const value = d.ref(0); + increment(value); + return value.$; + }; + + // Works in JS + expect(main()).toBe(1); + + // And on the GPU + expect(asWgsl(main)).toMatchInlineSnapshot(` + "fn increment(value: ptr) { + (*value) += 1i; + } + + fn main() -> i32 { + var value = 0; + increment((&value)); + return value; + }" + `); + }); + + it('rejects passing d.ref created from non-refs directly into functions', () => { + const increment = (value: d.ref) => { + 'use gpu'; + value.$ += 1; + }; + + const main = () => { + 'use gpu'; + increment(d.ref(0)); + }; + + expect(() => asWgsl(main)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:main + - fn*:main(): d.ref() created with primitive types must be stored in a variable before use] + `); + }); + + it('fails when returning a ref', () => { + const foo = () => { + 'use gpu'; + const value = d.ref(0); + return value; + }; + + const bar = () => { + 'use gpu'; + return d.ref(0); + }; + + expect(() => asWgsl(foo)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:foo + - fn*:foo(): Cannot return references, returning 'value'] + `); + + expect(() => asWgsl(bar)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:bar + - fn*:bar(): Cannot return references, returning '0'] + `); + }); }); diff --git a/packages/typegpu/tests/tgsl/conversion.test.ts b/packages/typegpu/tests/tgsl/conversion.test.ts index 4c45260e74..9ffa12bdf7 100644 --- a/packages/typegpu/tests/tgsl/conversion.test.ts +++ b/packages/typegpu/tests/tgsl/conversion.test.ts @@ -14,6 +14,7 @@ import { UnknownData } from '../../src/data/dataTypes.ts'; import { ResolutionCtxImpl } from '../../src/resolutionCtx.ts'; import { namespace } from '../../src/core/resolve/namespace.ts'; import wgslGenerator from '../../src/tgsl/wgslGenerator.ts'; +import { INTERNAL_createPtr } from '../../src/data/ptr.ts'; const ctx = new ResolutionCtxImpl({ namespace: namespace({ names: 'strict' }), @@ -30,8 +31,20 @@ afterAll(() => { }); describe('getBestConversion', () => { - const ptrF32 = d.ptrPrivate(d.f32); - const ptrI32 = d.ptrPrivate(d.i32); + // d.ptrPrivate(d.f32) + const ptrF32 = INTERNAL_createPtr( + 'private', + d.f32, + 'read-write', + /* implicit */ true, + ); + // d.ptrPrivate(d.i32) + const ptrI32 = INTERNAL_createPtr( + 'private', + d.i32, + 'read-write', + /* implicit */ true, + ); it('returns result for identical types', () => { const res = getBestConversion([d.f32, d.f32]); @@ -192,7 +205,7 @@ describe('convertToCommonType', () => { const snippetAbsInt = snip('1', abstractInt, /* ref */ 'runtime'); const snippetPtrF32 = snip( 'ptr_f32', - d.ptrPrivate(d.f32), + INTERNAL_createPtr('private', d.f32, 'read-write', /* implicit */ true), /* ref */ 'function', ); const snippetUnknown = snip('?', UnknownData, /* ref */ 'runtime'); diff --git a/packages/typegpu/tests/tgsl/shellless.test.ts b/packages/typegpu/tests/tgsl/shellless.test.ts index 5512f1e64c..a3eb9f743b 100644 --- a/packages/typegpu/tests/tgsl/shellless.test.ts +++ b/packages/typegpu/tests/tgsl/shellless.test.ts @@ -186,7 +186,7 @@ describe('shellless', () => { `); }); - it('generates uniform pointer params when passing a fixed uniform directly to a function', ({ root }) => { + it('generates uniform pointer params when passing a fixed uniform ref to a function', ({ root }) => { const posUniform = root.createUniform(d.vec3f); const sumComponents = (vec: d.ref) => { @@ -197,16 +197,15 @@ describe('shellless', () => { const main = () => { 'use gpu'; sumComponents(d.ref(posUniform.$)); - // sumComponents(&posUniform); }; expect(asWgsl(main)).toMatchInlineSnapshot(` - "@group(0) @binding(0) var posUniform: vec3f; - - fn sumComponents(vec: ptr) -> f32 { + "fn sumComponents(vec: ptr) -> f32 { return (((*vec).x + (*vec).y) + (*vec).z); } + @group(0) @binding(0) var posUniform: vec3f; + fn main() { sumComponents((&posUniform)); }" From b1fe352db9d16bbb73bf64b5b585af07ccba5be8 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Tue, 4 Nov 2025 23:03:19 +0100 Subject: [PATCH 47/59] Updates --- packages/typegpu/src/data/ref.ts | 30 +++++++++++----------- packages/typegpu/src/tgsl/shellless.ts | 4 +-- packages/typegpu/src/tgsl/wgslGenerator.ts | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/typegpu/src/data/ref.ts b/packages/typegpu/src/data/ref.ts index cb34424b76..5555c0206e 100644 --- a/packages/typegpu/src/data/ref.ts +++ b/packages/typegpu/src/data/ref.ts @@ -41,6 +41,15 @@ export interface ref { // TODO: Restrict calls to this function only from within TypeGPU functions export const ref: DualFn<(value: T) => ref> = (() => { const gpuImpl = (value: Snippet) => { + /** + * Pointer type only exists if the ref was created from a reference (buttery-butter). + * + * @example + * ```ts + * const life = ref(42); // created from a value + * const boid = ref(layout.$.boids[0]); // created from a reference + * ``` + */ const ptrType = createPtrFromOrigin( value.origin, value.dataType as StorableData, @@ -116,35 +125,26 @@ export class RefOperator implements SelfResolvable { readonly [$internal]: true; readonly snippet: Snippet; - /** - * Pointer params only exist if the ref was created from a reference (buttery-butter). - * - * @example - * ```ts - * const life = ref(42); // created from a value - * const boid = ref(layout.$.boids[0]); // created from a reference - * ``` - */ - readonly ptrType: Ptr | undefined; + readonly #ptrType: Ptr | undefined; constructor(snippet: Snippet, ptrType: Ptr | undefined) { this[$internal] = true; this.snippet = snippet; - this.ptrType = ptrType; + this.#ptrType = ptrType; } get [$ownSnippet](): Snippet { - if (!this.ptrType) { + if (!this.#ptrType) { throw new Error(stitch`Cannot take a reference of ${this.snippet}`); } - return snip(this, this.ptrType, this.snippet.origin); + return snip(this, this.#ptrType, this.snippet.origin); } [$resolve](ctx: ResolutionCtx): ResolvedSnippet { - if (!this.ptrType) { + if (!this.#ptrType) { throw new Error(stitch`Cannot take a reference of ${this.snippet}`); } - return snip(stitch`(&${this.snippet})`, this.ptrType, this.snippet.origin); + return snip(stitch`(&${this.snippet})`, this.#ptrType, this.snippet.origin); } } diff --git a/packages/typegpu/src/tgsl/shellless.ts b/packages/typegpu/src/tgsl/shellless.ts index a05048c589..b265bf7344 100644 --- a/packages/typegpu/src/tgsl/shellless.ts +++ b/packages/typegpu/src/tgsl/shellless.ts @@ -51,12 +51,12 @@ export class ShelllessRepository { const argTypes = (argSnippets ?? []).map((s, index) => { if (s.value instanceof RefOperator) { - if (!s.value.ptrType) { + if (s.dataType.type === 'unknown') { throw new WgslTypeError( `d.ref() created with primitive types must be stored in a variable before use`, ); } - return s.value.ptrType; + return s.dataType; } if (s.dataType.type === 'unknown') { diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 46705e70fd..3978467414 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -789,7 +789,7 @@ ${this.ctx.pre}else ${alternate}`; if (eq.value instanceof RefOperator) { // We're assigning a newly created `d.ref()` - if (eq.value.ptrType) { + if (eq.dataType.type !== 'unknown') { throw new WgslTypeError( `Cannot store d.ref() in a variable if it references another value. Copy the value passed into d.ref() instead.`, ); From 5805cacac74ae95ea802e72220db11fef2798886 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Wed, 5 Nov 2025 16:53:54 +0100 Subject: [PATCH 48/59] Simplify implicit pointer dereferencing --- .../typegpu/src/core/function/dualImpl.ts | 8 +- packages/typegpu/src/data/dataTypes.ts | 4 - packages/typegpu/src/data/vector.ts | 7 +- packages/typegpu/src/std/boolean.ts | 5 +- packages/typegpu/src/std/numeric.ts | 211 ++++++++---------- packages/typegpu/src/std/operators.ts | 24 +- 6 files changed, 107 insertions(+), 152 deletions(-) diff --git a/packages/typegpu/src/core/function/dualImpl.ts b/packages/typegpu/src/core/function/dualImpl.ts index 7eff45c88b..2c24f91cb1 100644 --- a/packages/typegpu/src/core/function/dualImpl.ts +++ b/packages/typegpu/src/core/function/dualImpl.ts @@ -70,7 +70,13 @@ export function dualImpl unknown>( const gpuImpl = (...args: MapValueToSnippet>) => { const { argTypes, returnType } = typeof options.signature === 'function' ? options.signature( - ...args.map((s) => s.dataType) as MapValueToDataType>, + ...args.map((s) => { + // Dereference implicit pointers + if (s.dataType.type === 'ptr' && s.dataType.implicit) { + return s.dataType.inner; + } + return s.dataType; + }) as MapValueToDataType>, ) : options.signature; diff --git a/packages/typegpu/src/data/dataTypes.ts b/packages/typegpu/src/data/dataTypes.ts index b19523ccec..11216a7e20 100644 --- a/packages/typegpu/src/data/dataTypes.ts +++ b/packages/typegpu/src/data/dataTypes.ts @@ -146,10 +146,6 @@ export function toStorable(schema: AnyData): AnyData { return undecorate(unptr(undecorate(schema))); } -export function toStorables(schemas: T): T { - return schemas.map(toStorable) as T; -} - const looseTypeLiterals = [ 'unstruct', 'disarray', diff --git a/packages/typegpu/src/data/vector.ts b/packages/typegpu/src/data/vector.ts index 50c3aabb52..29d74a15ab 100644 --- a/packages/typegpu/src/data/vector.ts +++ b/packages/typegpu/src/data/vector.ts @@ -1,7 +1,7 @@ import { dualImpl } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; import { $repr } from '../shared/symbols.ts'; -import { type AnyData, toStorable } from './dataTypes.ts'; +import type { AnyData } from './dataTypes.ts'; import { bool, f16, f32, i32, u32 } from './numeric.ts'; import { Vec2bImpl, @@ -310,10 +310,7 @@ function makeVecSchema( const construct = dualImpl({ name: type, signature: (...args) => ({ - argTypes: args.map((arg) => { - const argType = toStorable(arg); - return isVec(argType) ? argType : primitive; - }), + argTypes: args.map((arg) => isVec(arg) ? arg : primitive), returnType: schema as AnyData, }), normalImpl: cpuConstruct, diff --git a/packages/typegpu/src/std/boolean.ts b/packages/typegpu/src/std/boolean.ts index 3157ec14be..a55469036a 100644 --- a/packages/typegpu/src/std/boolean.ts +++ b/packages/typegpu/src/std/boolean.ts @@ -1,6 +1,6 @@ import { dualImpl } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; -import { type AnyData, toStorables } from '../data/dataTypes.ts'; +import type { AnyData } from '../data/dataTypes.ts'; import { bool, f32 } from '../data/numeric.ts'; import { isSnippetNumeric, snip } from '../data/snippet.ts'; import { vec2b, vec3b, vec4b } from '../data/vector.ts'; @@ -338,8 +338,7 @@ function cpuSelect( */ export const select = dualImpl({ name: 'select', - signature: (...args) => { - const [f, t, cond] = toStorables(args); + signature: (f, t, cond) => { const [uf, ut] = unify([f, t]) ?? [f, t] as const; return ({ argTypes: [uf, ut, cond], returnType: uf }); }, diff --git a/packages/typegpu/src/std/numeric.ts b/packages/typegpu/src/std/numeric.ts index 2db3ae0148..50d1bb5fd8 100644 --- a/packages/typegpu/src/std/numeric.ts +++ b/packages/typegpu/src/std/numeric.ts @@ -4,7 +4,7 @@ import { MissingCpuImplError, } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; -import { type AnyData, toStorable, toStorables } from '../data/dataTypes.ts'; +import type { AnyData } from '../data/dataTypes.ts'; import { smoothstepScalar } from '../data/numberOps.ts'; import { abstractFloat, @@ -65,17 +65,16 @@ function cpuAbs(value: T): T { return VectorOps.abs[value.kind](value) as T; } -const unaryStorableSignature = (arg: AnyData) => { - const sarg = toStorable(arg); +const unaryIdentitySignature = (arg: AnyData) => { return { - argTypes: [sarg], - returnType: sarg, + argTypes: [arg], + returnType: arg, }; }; export const abs = dualImpl({ name: 'abs', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuAbs, codegenImpl: (value) => stitch`abs(${value})`, }); @@ -91,7 +90,7 @@ function cpuAcos(value: T): T { export const acos = dualImpl({ name: 'acos', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuAcos, codegenImpl: (value) => stitch`acos(${value})`, }); @@ -107,7 +106,7 @@ function cpuAcosh(value: T): T { export const acosh = dualImpl({ name: 'acosh', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuAcosh, codegenImpl: (value) => stitch`acosh(${value})`, }); @@ -123,7 +122,7 @@ function cpuAsin(value: T): T { export const asin = dualImpl({ name: 'asin', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuAsin, codegenImpl: (value) => stitch`asin(${value})`, }); @@ -139,7 +138,7 @@ function cpuAsinh(value: T): T { export const asinh = dualImpl({ name: 'asinh', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuAsinh, codegenImpl: (value) => stitch`asinh(${value})`, }); @@ -155,7 +154,7 @@ function cpuAtan(value: T): T { export const atan = dualImpl({ name: 'atan', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuAtan, codegenImpl: (value) => stitch`atan(${value})`, }); @@ -171,7 +170,7 @@ function cpuAtanh(value: T): T { export const atanh = dualImpl({ name: 'atanh', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuAtanh, codegenImpl: (value) => stitch`atanh(${value})`, }); @@ -191,8 +190,7 @@ function cpuAtan2(y: T, x: T): T { export const atan2 = dualImpl({ name: 'atan2', signature: (...args) => { - const sargs = toStorables(args); - const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; + const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; return ({ argTypes: uargs, returnType: uargs[0], @@ -213,7 +211,7 @@ function cpuCeil(value: T): T { export const ceil = dualImpl({ name: 'ceil', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuCeil, codegenImpl: (value) => stitch`ceil(${value})`, }); @@ -234,8 +232,7 @@ function cpuClamp(value: T, low: T, high: T): T { export const clamp = dualImpl({ name: 'clamp', signature: (...args) => { - const sargs = toStorables(args); - const uargs = unify(sargs) ?? sargs; + const uargs = unify(args) ?? args; return { argTypes: uargs, returnType: uargs[0] }; }, normalImpl: cpuClamp, @@ -253,7 +250,7 @@ function cpuCos(value: T): T { export const cos = dualImpl({ name: 'cos', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuCos, codegenImpl: (value) => stitch`cos(${value})`, }); @@ -269,7 +266,7 @@ function cpuCosh(value: T): T { export const cosh = dualImpl({ name: 'cosh', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuCosh, codegenImpl: (value) => stitch`cosh(${value})`, }); @@ -284,7 +281,7 @@ function cpuCountLeadingZeros( export const countLeadingZeros = dualImpl({ name: 'countLeadingZeros', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: 'CPU implementation for countLeadingZeros not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`countLeadingZeros(${value})`, @@ -300,7 +297,7 @@ function cpuCountOneBits( export const countOneBits = dualImpl({ name: 'countOneBits', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: 'CPU implementation for countOneBits not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`countOneBits(${value})`, @@ -316,7 +313,7 @@ function cpuCountTrailingZeros( export const countTrailingZeros = dualImpl({ name: 'countTrailingZeros', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: 'CPU implementation for countTrailingZeros not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`countTrailingZeros(${value})`, @@ -325,8 +322,7 @@ export const countTrailingZeros = dualImpl({ export const cross = dualImpl({ name: 'cross', signature: (...args) => { - const sargs = toStorables(args); - return ({ argTypes: sargs, returnType: sargs[0] }); + return ({ argTypes: args, returnType: args[0] }); }, normalImpl: (a: T, b: T): T => VectorOps.cross[a.kind](a, b), @@ -346,7 +342,7 @@ function cpuDegrees(value: T): T { export const degrees = dualImpl({ name: 'degrees', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuDegrees, codegenImpl: (value) => stitch`degrees(${value})`, }); @@ -354,7 +350,7 @@ export const degrees = dualImpl({ export const determinant = dualImpl<(value: AnyMatInstance) => number>({ name: 'determinant', // TODO: The return type is potentially wrong here, it should return whatever the matrix element type is. - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: 'CPU implementation for determinant not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`determinant(${value})`, @@ -377,10 +373,9 @@ function cpuDistance( export const distance = dualImpl({ name: 'distance', signature: (...args) => { - const sargs = toStorables(args); return ({ - argTypes: sargs, - returnType: isHalfPrecisionSchema(sargs[0]) ? f16 : f32, + argTypes: args, + returnType: isHalfPrecisionSchema(args[0]) ? f16 : f32, }); }, normalImpl: cpuDistance, @@ -389,13 +384,10 @@ export const distance = dualImpl({ export const dot = dualImpl({ name: 'dot', - signature: (...args) => { - const sargs = toStorables(args); - return ({ - argTypes: sargs, - returnType: (sargs[0] as VecData).primitive, - }); - }, + signature: (...args) => ({ + argTypes: args, + returnType: (args[0] as VecData).primitive, + }), normalImpl: (lhs: T, rhs: T): number => VectorOps.dot[lhs.kind](lhs, rhs), codegenImpl: (lhs, rhs) => stitch`dot(${lhs}, ${rhs})`, @@ -428,7 +420,7 @@ function cpuExp(value: T): T { export const exp = dualImpl({ name: 'exp', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuExp, codegenImpl: (value) => stitch`exp(${value})`, }); @@ -444,7 +436,7 @@ function cpuExp2(value: T): T { export const exp2 = dualImpl({ name: 'exp2', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuExp2, codegenImpl: (value) => stitch`exp2(${value})`, }); @@ -465,13 +457,10 @@ function cpuExtractBits( export const extractBits = dualImpl({ name: 'extractBits', - signature: (arg, _offset, _count) => { - const sarg = toStorable(arg); - return ({ - argTypes: [sarg, u32, u32], - returnType: sarg, - }); - }, + signature: (arg, _offset, _count) => ({ + argTypes: [arg, u32, u32], + returnType: arg, + }), normalImpl: 'CPU implementation for extractBits not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e, offset, count) => @@ -483,10 +472,9 @@ export const faceForward = dualImpl< >({ name: 'faceForward', signature: (...args) => { - const sargs = toStorables(args); return ({ - argTypes: sargs, - returnType: sargs[0], + argTypes: args, + returnType: args[0], }); }, normalImpl: @@ -504,7 +492,7 @@ function cpuFirstLeadingBit( export const firstLeadingBit = dualImpl({ name: 'firstLeadingBit', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: 'CPU implementation for firstLeadingBit not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`firstLeadingBit(${value})`, @@ -520,7 +508,7 @@ function cpuFirstTrailingBit( export const firstTrailingBit = dualImpl({ name: 'firstTrailingBit', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: 'CPU implementation for firstTrailingBit not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`firstTrailingBit(${value})`, @@ -537,7 +525,7 @@ function cpuFloor(value: T): T { export const floor = dualImpl({ name: 'floor', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuFloor, codegenImpl: (arg) => stitch`floor(${arg})`, }); @@ -559,13 +547,10 @@ function cpuFma( export const fma = dualImpl({ name: 'fma', - signature: (...args) => { - const sargs = toStorables(args); - return ({ - argTypes: sargs, - returnType: sargs[0], - }); - }, + signature: (...args) => ({ + argTypes: args, + returnType: args[0], + }), normalImpl: cpuFma, codegenImpl: (e1, e2, e3) => stitch`fma(${e1}, ${e2}, ${e3})`, }); @@ -581,7 +566,7 @@ function cpuFract(value: T): T { export const fract = dualImpl({ name: 'fract', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuFract, codegenImpl: (a) => stitch`fract(${a})`, }); @@ -654,13 +639,10 @@ function cpuInsertBits( export const insertBits = dualImpl({ name: 'insertBits', - signature: (e, newbits, _offset, _count) => { - const se = toStorable(e); - return ({ - argTypes: [se, toStorable(newbits), u32, u32], - returnType: se, - }); - }, + signature: (e, newbits, _offset, _count) => ({ + argTypes: [e, newbits, u32, u32], + returnType: e, + }), normalImpl: 'CPU implementation for insertBits not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e, newbits, offset, count) => @@ -680,7 +662,7 @@ function cpuInverseSqrt(value: T): T { export const inverseSqrt = dualImpl({ name: 'inverseSqrt', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuInverseSqrt, codegenImpl: (value) => stitch`inverseSqrt(${value})`, }); @@ -699,22 +681,21 @@ function cpuLdexp( export const ldexp = dualImpl({ name: 'ldexp', signature: (e1, _e2) => { - const se1 = toStorable(e1); - switch (se1.type) { + switch (e1.type) { case 'abstractFloat': - return { argTypes: [se1, abstractInt], returnType: se1 }; + return { argTypes: [e1, abstractInt], returnType: e1 }; case 'f32': case 'f16': - return { argTypes: [se1, i32], returnType: se1 }; + return { argTypes: [e1, i32], returnType: e1 }; case 'vec2f': case 'vec2h': - return { argTypes: [se1, vec2i], returnType: se1 }; + return { argTypes: [e1, vec2i], returnType: e1 }; case 'vec3f': case 'vec3h': - return { argTypes: [se1, vec3i], returnType: se1 }; + return { argTypes: [e1, vec3i], returnType: e1 }; case 'vec4f': case 'vec4h': - return { argTypes: [se1, vec4i], returnType: se1 }; + return { argTypes: [e1, vec4i], returnType: e1 }; default: throw new Error( `Unsupported data type for ldexp: ${e1.type}. Supported types are abstractFloat, f32, f16, vec2f, vec2h, vec3f, vec3h, vec4f, vec4h.`, @@ -737,13 +718,10 @@ function cpuLength(value: T): number { export const length = dualImpl({ name: 'length', - signature: (arg) => { - const sarg = toStorable(arg); - return ({ - argTypes: [sarg], - returnType: isHalfPrecisionSchema(sarg) ? f16 : f32, - }); - }, + signature: (arg) => ({ + argTypes: [arg], + returnType: isHalfPrecisionSchema(arg) ? f16 : f32, + }), normalImpl: cpuLength, codegenImpl: (arg) => stitch`length(${arg})`, }); @@ -759,7 +737,7 @@ function cpuLog(value: T): T { export const log = dualImpl({ name: 'log', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuLog, codegenImpl: (value) => stitch`log(${value})`, }); @@ -775,7 +753,7 @@ function cpuLog2(value: T): T { export const log2 = dualImpl({ name: 'log2', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuLog2, codegenImpl: (value) => stitch`log2(${value})`, }); @@ -792,8 +770,7 @@ function cpuMax(a: T, b: T): T { export const max = dualImpl({ name: 'max', signature: (...args) => { - const sargs = toStorables(args); - const uargs = unify(sargs) ?? sargs; + const uargs = unify(args) ?? args; return ({ argTypes: uargs, returnType: uargs[0], @@ -815,8 +792,7 @@ function cpuMin(a: T, b: T): T { export const min = dualImpl({ name: 'min', signature: (...args) => { - const sargs = toStorables(args); - const uargs = unify(sargs) ?? sargs; + const uargs = unify(args) ?? args; return ({ argTypes: uargs, returnType: uargs[0], @@ -853,8 +829,7 @@ function cpuMix( export const mix = dualImpl({ name: 'mix', signature: (...args) => { - const sargs = toStorables(args); - const uargs = unify(sargs) ?? sargs; + const uargs = unify(args) ?? args; return ({ argTypes: uargs, returnType: uargs[0], @@ -895,16 +870,15 @@ function cpuModf( export const modf: ModfOverload = dualImpl({ name: 'modf', signature: (e) => { - const se = toStorable(e); - const returnType = ModfResult[se.type as keyof typeof ModfResult]; + const returnType = ModfResult[e.type as keyof typeof ModfResult]; if (!returnType) { throw new Error( - `Unsupported data type for modf: ${se.type}. Supported types are f32, f16, abstractFloat, vec2f, vec3f, vec4f, vec2h, vec3h, vec4h.`, + `Unsupported data type for modf: ${e.type}. Supported types are f32, f16, abstractFloat, vec2f, vec3f, vec4f, vec2h, vec3h, vec4h.`, ); } - return { argTypes: [se], returnType }; + return { argTypes: [e], returnType }; }, normalImpl: 'CPU implementation for modf not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', @@ -913,7 +887,7 @@ export const modf: ModfOverload = dualImpl({ export const normalize = dualImpl({ name: 'normalize', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: (v: T): T => VectorOps.normalize[v.kind](v), codegenImpl: (v) => stitch`normalize(${v})`, @@ -940,8 +914,7 @@ function powCpu( export const pow = dualImpl({ name: 'pow', signature: (...args) => { - const sargs = toStorables(args); - const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; + const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; return { argTypes: uargs, returnType: isNumericSchema(uargs[0]) ? uargs[1] : uargs[0], @@ -960,7 +933,7 @@ function cpuQuantizeToF16( export const quantizeToF16 = dualImpl({ name: 'quantizeToF16', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: 'CPU implementation for quantizeToF16 not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`quantizeToF16(${value})`, @@ -980,8 +953,7 @@ function cpuRadians(value: T): T { export const radians = dualImpl({ name: 'radians', signature: (...args) => { - const sargs = toStorables(args); - const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; + const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; return ({ argTypes: uargs, returnType: uargs[0] }); }, normalImpl: cpuRadians, @@ -990,10 +962,7 @@ export const radians = dualImpl({ export const reflect = dualImpl({ name: 'reflect', - signature: (...args) => { - const sargs = toStorables(args); - return ({ argTypes: sargs, returnType: sargs[0] }); - }, + signature: (...args) => ({ argTypes: args, returnType: args[0] }), normalImpl: (e1: T, e2: T): T => sub(e1, mul(2 * dot(e2, e1), e2)), codegenImpl: (e1, e2) => stitch`reflect(${e1}, ${e2})`, @@ -1028,7 +997,7 @@ function cpuReverseBits(value: T): T { export const reverseBits = dualImpl({ name: 'reverseBits', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: 'CPU implementation for reverseBits not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`reverseBits(${value})`, @@ -1047,7 +1016,7 @@ function cpuRound(value: T): T { export const round = dualImpl({ name: 'round', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuRound, codegenImpl: (value) => stitch`round(${value})`, }); @@ -1065,7 +1034,7 @@ function cpuSaturate(value: T): T { export const saturate = dualImpl({ name: 'saturate', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuSaturate, codegenImpl: (value) => stitch`saturate(${value})`, }); @@ -1081,7 +1050,7 @@ function cpuSign(e: T): T { export const sign = dualImpl({ name: 'sign', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuSign, codegenImpl: (e) => stitch`sign(${e})`, }); @@ -1097,7 +1066,7 @@ function cpuSin(value: T): T { export const sin = dualImpl({ name: 'sin', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuSin, codegenImpl: (value) => stitch`sin(${value})`, }); @@ -1115,7 +1084,7 @@ function cpuSinh(value: T): T { export const sinh = dualImpl({ name: 'sinh', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuSinh, codegenImpl: (value) => stitch`sinh(${value})`, }); @@ -1147,13 +1116,10 @@ function cpuSmoothstep( export const smoothstep = dualImpl({ name: 'smoothstep', - signature: (...args) => { - const sargs = toStorables(args); - return ({ - argTypes: sargs, - returnType: sargs[2], - }); - }, + signature: (...args) => ({ + argTypes: args, + returnType: args[2], + }), normalImpl: cpuSmoothstep, codegenImpl: (edge0, edge1, x) => stitch`smoothstep(${edge0}, ${edge1}, ${x})`, @@ -1170,7 +1136,7 @@ function cpuSqrt(value: T): T { export const sqrt = dualImpl({ name: 'sqrt', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuSqrt, codegenImpl: (value) => stitch`sqrt(${value})`, }); @@ -1189,8 +1155,7 @@ function cpuStep(edge: T, x: T): T { export const step = dualImpl({ name: 'step', signature: (...args) => { - const sargs = toStorables(args); - const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; + const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; return { argTypes: uargs, returnType: uargs[0] }; }, normalImpl: cpuStep, @@ -1210,7 +1175,7 @@ function cpuTan(value: T): T { export const tan = dualImpl({ name: 'tan', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuTan, codegenImpl: (value) => stitch`tan(${value})`, }); @@ -1226,14 +1191,14 @@ function cpuTanh(value: T): T { export const tanh = dualImpl({ name: 'tanh', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: cpuTanh, codegenImpl: (value) => stitch`tanh(${value})`, }); export const transpose = dualImpl<(e: T) => T>({ name: 'transpose', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: 'CPU implementation for transpose not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (e) => stitch`transpose(${e})`, @@ -1247,7 +1212,7 @@ function cpuTrunc(value: T): T { export const trunc = dualImpl({ name: 'trunc', - signature: unaryStorableSignature, + signature: unaryIdentitySignature, normalImpl: 'CPU implementation for trunc not implemented yet. Please submit an issue at https://github.com/software-mansion/TypeGPU/issues', codegenImpl: (value) => stitch`trunc(${value})`, diff --git a/packages/typegpu/src/std/operators.ts b/packages/typegpu/src/std/operators.ts index e3db256ad8..521b69d17c 100644 --- a/packages/typegpu/src/std/operators.ts +++ b/packages/typegpu/src/std/operators.ts @@ -1,6 +1,5 @@ import { dualImpl } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; -import { toStorable, toStorables } from '../data/dataTypes.ts'; import { abstractFloat, f16, f32 } from '../data/numeric.ts'; import { vecTypeToConstructor } from '../data/vector.ts'; import { VectorOps } from '../data/vectorOps.ts'; @@ -55,8 +54,7 @@ function cpuAdd(lhs: number | NumVec | Mat, rhs: number | NumVec | Mat) { export const add = dualImpl({ name: 'add', signature: (...args) => { - const sargs = toStorables(args); - const uargs = unify(sargs) ?? sargs; + const uargs = unify(args) ?? args; return { argTypes: uargs, returnType: isNumericSchema(uargs[0]) ? uargs[1] : uargs[0], @@ -140,8 +138,7 @@ function cpuMul(lhs: number | NumVec | Mat, rhs: number | NumVec | Mat) { export const mul = dualImpl({ name: 'mul', signature: (...args) => { - const sargs = toStorables(args); - const uargs = unify(sargs) ?? sargs; + const uargs = unify(args) ?? args; const returnType = isNumericSchema(uargs[0]) // Scalar * Scalar/Vector/Matrix ? uargs[1] @@ -188,8 +185,7 @@ function cpuDiv(lhs: NumVec | number, rhs: NumVec | number): NumVec | number { export const div = dualImpl({ name: 'div', signature: (...args) => { - const sargs = toStorables(args); - const uargs = unify(sargs, [f32, f16, abstractFloat]) ?? sargs; + const uargs = unify(args, [f32, f16, abstractFloat]) ?? args; return ({ argTypes: uargs, returnType: isNumericSchema(uargs[0]) ? uargs[1] : uargs[0], @@ -214,8 +210,7 @@ type ModOverload = { export const mod: ModOverload = dualImpl({ name: 'mod', signature: (...args) => { - const sargs = toStorables(args); - const uargs = unify(sargs) ?? sargs; + const uargs = unify(args) ?? args; return { argTypes: uargs, returnType: isNumericSchema(uargs[0]) ? uargs[1] : uargs[0], @@ -258,13 +253,10 @@ function cpuNeg(value: NumVec | number): NumVec | number { export const neg = dualImpl({ name: 'neg', - signature: (arg) => { - const sarg = toStorable(arg); - return { - argTypes: [sarg], - returnType: sarg, - }; - }, + signature: (arg) => ({ + argTypes: [arg], + returnType: arg, + }), normalImpl: cpuNeg, codegenImpl: (arg) => stitch`-(${arg})`, }); From 685479d9a56ae5b771e8e40b8914042fc90e7e44 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 6 Nov 2025 14:15:01 +0100 Subject: [PATCH 49/59] More tests and restrictions --- .../typegpu/src/core/variable/tgpuVariable.ts | 3 --- packages/typegpu/src/data/ref.ts | 23 +++++++++++----- packages/typegpu/src/shared/symbols.ts | 10 ------- packages/typegpu/src/std/array.ts | 3 +-- .../typegpu/src/tgsl/generationHelpers.ts | 6 ++++- packages/typegpu/tests/ref.test.ts | 16 ++++++++++-- packages/typegpu/tests/tgsl/shellless.test.ts | 26 +++++++++++++++++++ 7 files changed, 62 insertions(+), 25 deletions(-) diff --git a/packages/typegpu/src/core/variable/tgpuVariable.ts b/packages/typegpu/src/core/variable/tgpuVariable.ts index b8ec5cf0c8..e1856d0716 100644 --- a/packages/typegpu/src/core/variable/tgpuVariable.ts +++ b/packages/typegpu/src/core/variable/tgpuVariable.ts @@ -10,7 +10,6 @@ import type { InferGPU } from '../../shared/repr.ts'; import { $gpuValueOf, $internal, - $isRef, $ownSnippet, $resolve, } from '../../shared/symbols.ts'; @@ -78,7 +77,6 @@ export function isVariable( class TgpuVarImpl implements TgpuVar, SelfResolvable { readonly [$internal] = {}; - readonly [$isRef]: true; readonly #scope: TScope; readonly #dataType: TDataType; readonly #initialValue: InferGPU | undefined; @@ -88,7 +86,6 @@ class TgpuVarImpl dataType: TDataType, initialValue?: InferGPU | undefined, ) { - this[$isRef] = true; this.#scope = scope; this.#dataType = dataType; this.#initialValue = initialValue; diff --git a/packages/typegpu/src/data/ref.ts b/packages/typegpu/src/data/ref.ts index 5555c0206e..b1fdebc5d6 100644 --- a/packages/typegpu/src/data/ref.ts +++ b/packages/typegpu/src/data/ref.ts @@ -2,7 +2,7 @@ import { stitch } from '../core/resolve/stitch.ts'; import { invariant } from '../errors.ts'; import { inCodegenMode } from '../execMode.ts'; import { setName } from '../shared/meta.ts'; -import { $internal, $isRef, $ownSnippet, $resolve } from '../shared/symbols.ts'; +import { $internal, $ownSnippet, $resolve } from '../shared/symbols.ts'; import type { ResolutionCtx, SelfResolvable } from '../types.ts'; import { UnknownData } from './dataTypes.ts'; import type { DualFn } from './dualFn.ts'; @@ -19,9 +19,15 @@ import { // Public API // ---------- +/** + * A reference to a value `T`. Can be passed to other functions to give them + * mutable access to the underlying value. + * + * Conceptually, it represents a WGSL pointer. + */ export interface ref { readonly [$internal]: unknown; - readonly [$isRef]: true; + readonly type: 'ref'; /** * Derefences the reference, and gives access to the underlying value. @@ -38,7 +44,6 @@ export interface ref { $: T; } -// TODO: Restrict calls to this function only from within TypeGPU functions export const ref: DualFn<(value: T) => ref> = (() => { const gpuImpl = (value: Snippet) => { /** @@ -84,19 +89,23 @@ export const ref: DualFn<(value: T) => ref> = (() => { return impl as unknown as DualFn<(value: T) => ref>; })(); +export function isRef(value: unknown | ref): value is ref { + return value instanceof refImpl; +} + // -------------- // Implementation // -------------- class refImpl implements ref { - #value: T; readonly [$internal]: true; - readonly [$isRef]: true; + readonly type: 'ref'; + #value: T; constructor(value: T) { - this.#value = value; this[$internal] = true; - this[$isRef] = true; + this.type = 'ref'; + this.#value = value; } get $(): T { diff --git a/packages/typegpu/src/shared/symbols.ts b/packages/typegpu/src/shared/symbols.ts index d8051d4d4c..bce790d4c9 100644 --- a/packages/typegpu/src/shared/symbols.ts +++ b/packages/typegpu/src/shared/symbols.ts @@ -69,16 +69,6 @@ export const $invalidSchemaReason = Symbol( `typegpu:${version}:$invalidSchemaReason`, ); -/** - * A symbol that identifies objects that act as references - * (values returned from d.ref(), buffer usages, ...) - */ -export const $isRef = Symbol(`typegpu:${version}:$isRef`); - -export function isRef(value: unknown): value is { [$isRef]: true } { - return !!(value as { [$isRef]: true })?.[$isRef]; -} - export function isMarkedInternal( value: unknown, ): value is { [$internal]: Record | true } { diff --git a/packages/typegpu/src/std/array.ts b/packages/typegpu/src/std/array.ts index 0149f781f7..3a00c4d21d 100644 --- a/packages/typegpu/src/std/array.ts +++ b/packages/typegpu/src/std/array.ts @@ -2,9 +2,8 @@ import { dualImpl } from '../core/function/dualImpl.ts'; import { stitch } from '../core/resolve/stitch.ts'; import { abstractInt, u32 } from '../data/numeric.ts'; import { ptrFn } from '../data/ptr.ts'; -import type { ref } from '../data/ref.ts'; +import { isRef, type ref } from '../data/ref.ts'; import { isPtr, isWgslArray, type StorableData } from '../data/wgslTypes.ts'; -import { isRef } from '../shared/symbols.ts'; const sizeOfPointedToArray = (dataType: unknown) => isPtr(dataType) && isWgslArray(dataType.inner) diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 05ce7e7feb..6f1f1cc2ec 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -62,7 +62,7 @@ import type { ShelllessRepository } from './shellless.ts'; import { add, div, mul, sub } from '../std/operators.ts'; import { $internal } from '../shared/symbols.ts'; import { stitch } from '../core/resolve/stitch.ts'; -import { derefSnippet } from '../data/ref.ts'; +import { derefSnippet, isRef } from '../data/ref.ts'; type SwizzleableType = 'f' | 'h' | 'i' | 'u' | 'b'; type SwizzleLength = 1 | 2 | 3 | 4; @@ -428,6 +428,10 @@ export function coerceToSnippet(value: unknown): Snippet { return value; } + if (isRef(value)) { + throw new Error('Cannot use refs (d.ref(...)) from the outer scope.'); + } + // Maybe the value can tell us what snippet it is const ownSnippet = getOwnSnippet(value); if (ownSnippet) { diff --git a/packages/typegpu/tests/ref.test.ts b/packages/typegpu/tests/ref.test.ts index 373c7e434d..c28a451a9a 100644 --- a/packages/typegpu/tests/ref.test.ts +++ b/packages/typegpu/tests/ref.test.ts @@ -4,8 +4,20 @@ import { it } from './utils/extendedIt.ts'; import { asWgsl } from './utils/parseResolved.ts'; describe('ref', () => { - it.skip('fails when created outside of a TypeGPU function', () => { - expect(() => d.ref(0)).toThrowErrorMatchingInlineSnapshot(); + it('fails when using a ref as an external', () => { + const sup = d.ref(0); + + const foo = () => { + 'use gpu'; + sup.$ += 1; + }; + + expect(() => asWgsl(foo)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:foo + - fn*:foo(): Cannot use refs (d.ref(...)) from the outer scope.] + `); }); it('creates a regular looking variable in WGSL', () => { diff --git a/packages/typegpu/tests/tgsl/shellless.test.ts b/packages/typegpu/tests/tgsl/shellless.test.ts index a3eb9f743b..4c0632ab19 100644 --- a/packages/typegpu/tests/tgsl/shellless.test.ts +++ b/packages/typegpu/tests/tgsl/shellless.test.ts @@ -186,6 +186,32 @@ describe('shellless', () => { `); }); + it('generates private pointer params when passing a private variable ref to a function', ({ root }) => { + const foo = tgpu.privateVar(d.vec3f); + + const sumComponents = (vec: d.ref) => { + 'use gpu'; + return vec.$.x + vec.$.y + vec.$.z; + }; + + const main = () => { + 'use gpu'; + sumComponents(d.ref(foo.$)); + }; + + expect(asWgsl(main)).toMatchInlineSnapshot(` + "fn sumComponents(vec: ptr) -> f32 { + return (((*vec).x + (*vec).y) + (*vec).z); + } + + var foo: vec3f; + + fn main() { + sumComponents((&foo)); + }" + `); + }); + it('generates uniform pointer params when passing a fixed uniform ref to a function', ({ root }) => { const posUniform = root.createUniform(d.vec3f); From 171af794f6c70559f0217cacdeb93f1fc1a23992 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 6 Nov 2025 15:28:30 +0100 Subject: [PATCH 50/59] More test coverage for argument origin tracking --- packages/typegpu/src/core/function/fnCore.ts | 39 +++-- .../typegpu/src/core/variable/tgpuVariable.ts | 3 +- packages/typegpu/src/data/ref.ts | 6 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 20 ++- .../tests/examples/individual/gravity.test.ts | 4 +- packages/typegpu/tests/ref.test.ts | 2 +- .../typegpu/tests/tgsl/argumentOrigin.test.ts | 148 ++++++++++++++++++ packages/typegpu/tests/tgslFn.test.ts | 8 +- 8 files changed, 203 insertions(+), 27 deletions(-) create mode 100644 packages/typegpu/tests/tgsl/argumentOrigin.test.ts diff --git a/packages/typegpu/src/core/function/fnCore.ts b/packages/typegpu/src/core/function/fnCore.ts index 669aa7e3dd..6bbd2ff736 100644 --- a/packages/typegpu/src/core/function/fnCore.ts +++ b/packages/typegpu/src/core/function/fnCore.ts @@ -7,6 +7,7 @@ import { type Snippet, } from '../../data/snippet.ts'; import { + isNaturallyEphemeral, isPtr, isWgslData, isWgslStruct, @@ -196,16 +197,18 @@ export function createFnCore( // of the argument based on the argument's referentiality. // In other words, if we pass a reference to a function, it's typed as a pointer, // otherwise it's typed as a value. - const ref = isPtr(argType) + const origin = isPtr(argType) ? argType.addressSpace === 'storage' ? argType.access === 'read' ? 'readonly' : 'mutable' : argType.addressSpace + : isNaturallyEphemeral(argType) + ? 'runtime' : 'argument'; switch (astParam?.type) { case FuncParameterType.identifier: { const rawName = astParam.name; - const snippet = snip(ctx.makeNameValid(rawName), argType, ref); + const snippet = snip(ctx.makeNameValid(rawName), argType, origin); args.push(snippet); if (snippet.value !== rawName) { argAliases.push([rawName, snippet]); @@ -213,22 +216,30 @@ export function createFnCore( break; } case FuncParameterType.destructuredObject: { - args.push(snip(`_arg_${i}`, argType, ref)); - argAliases.push(...astParam.props.map(({ name, alias }) => - [ + args.push(snip(`_arg_${i}`, argType, origin)); + argAliases.push(...astParam.props.map(({ name, alias }) => { + // Undecorating, as the struct type can contain builtins + const destrType = undecorate( + (argTypes[i] as WgslStruct).propTypes[name], + ); + + const destrOrigin = isPtr(destrType) + ? destrType.addressSpace === 'storage' + ? destrType.access === 'read' ? 'readonly' : 'mutable' + : destrType.addressSpace + : isNaturallyEphemeral(destrType) + ? 'runtime' + : 'argument'; + + return [ alias, - snip( - `_arg_${i}.${name}`, - (argTypes[i] as WgslStruct) - .propTypes[name], - ref, - ), - ] as [string, Snippet] - )); + snip(`_arg_${i}.${name}`, destrType, destrOrigin), + ] as [string, Snippet]; + })); break; } case undefined: - args.push(snip(`_arg_${i}`, argType, ref)); + args.push(snip(`_arg_${i}`, argType, origin)); } } diff --git a/packages/typegpu/src/core/variable/tgpuVariable.ts b/packages/typegpu/src/core/variable/tgpuVariable.ts index e1856d0716..eaacfbd5fb 100644 --- a/packages/typegpu/src/core/variable/tgpuVariable.ts +++ b/packages/typegpu/src/core/variable/tgpuVariable.ts @@ -1,5 +1,4 @@ import type { AnyData } from '../../data/dataTypes.ts'; -import type { ref } from '../../data/ref.ts'; import { type ResolvedSnippet, snip } from '../../data/snippet.ts'; import { isNaturallyEphemeral } from '../../data/wgslTypes.ts'; import { IllegalVarAccessError } from '../../errors.ts'; @@ -26,7 +25,7 @@ export type VariableScope = 'private' | 'workgroup'; export interface TgpuVar< TScope extends VariableScope = VariableScope, TDataType extends AnyData = AnyData, -> extends TgpuNamable, ref> { +> extends TgpuNamable { readonly [$gpuValueOf]: InferGPU; value: InferGPU; $: InferGPU; diff --git a/packages/typegpu/src/data/ref.ts b/packages/typegpu/src/data/ref.ts index b1fdebc5d6..846620bcc8 100644 --- a/packages/typegpu/src/data/ref.ts +++ b/packages/typegpu/src/data/ref.ts @@ -161,8 +161,10 @@ export function derefSnippet(snippet: Snippet): Snippet { invariant(isPtr(snippet.dataType), 'Only pointers can be dereferenced'); const innerType = snippet.dataType.inner; - // Dereferencing a pointer does not return a copy of the value, it's still a reference. - const origin = isNaturallyEphemeral(innerType) ? 'runtime' : snippet.origin; + const origin = + isNaturallyEphemeral(innerType) && snippet.origin !== 'argument' + ? 'runtime' + : snippet.origin; if (snippet.value instanceof RefOperator) { return snip(stitch`${snippet.value.snippet}`, innerType, origin); diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 3978467414..44090a0515 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -174,6 +174,8 @@ ${this.ctx.pre}}`; // Even types that aren't naturally referential (like vectors or structs) should // be treated as constant references when assigned to a const. varOrigin = 'constant-ref'; + } else if (origin === 'argument' && !wgsl.isNaturallyEphemeral(dataType)) { + varOrigin = 'argument'; } else if (!wgsl.isNaturallyEphemeral(dataType)) { varOrigin = isEphemeralOrigin(origin) ? 'function' : origin; } else if (origin === 'constant' && varType === 'const') { @@ -291,11 +293,22 @@ ${this.ctx.pre}}`; ); } + if ( + rhsExpr.origin === 'argument' && + !wgsl.isNaturallyEphemeral(rhsExpr.dataType) + ) { + throw new WgslTypeError( + `'${lhsStr} = ${rhsStr}' is invalid, because argument references cannot be assigned.\n-----\nTry '${lhsStr} = ${ + this.ctx.resolve(rhsExpr.dataType).value + }(${rhsStr})' to copy the value instead.\n-----`, + ); + } + if (!isEphemeralSnippet(rhsExpr)) { throw new WgslTypeError( `'${lhsStr} = ${rhsStr}' is invalid, because references cannot be assigned.\n-----\nTry '${lhsStr} = ${ this.ctx.resolve(rhsExpr.dataType).value - }(${rhsStr})' instead.\n-----`, + }(${rhsStr})' to copy the value instead.\n-----`, ); } } @@ -812,7 +825,10 @@ ${this.ctx.pre}else ${alternate}`; // Assigning a reference to a `const` variable means we store the pointer // of the rhs. - if (!isEphemeralSnippet(eq)) { + if ( + !isEphemeralSnippet(eq) || + (eq.origin === 'argument' && !wgsl.isNaturallyEphemeral(dataType)) + ) { // Referential if (stmtType === NODE.let) { const rhsStr = this.ctx.resolve(eq.value).value; diff --git a/packages/typegpu/tests/examples/individual/gravity.test.ts b/packages/typegpu/tests/examples/individual/gravity.test.ts index 068a93f803..408a1e907e 100644 --- a/packages/typegpu/tests/examples/individual/gravity.test.ts +++ b/packages/typegpu/tests/examples/individual/gravity.test.ts @@ -270,9 +270,9 @@ describe('gravity example', () => { var lightColor = vec3f(1, 0.8999999761581421, 0.8999999761581421); var textureColor = textureSample(celestialBodyTextures_9, sampler_10, input.uv, input.sphereTextureIndex).xyz; var ambient = ((textureColor * lightColor) * input.ambientLightFactor); - var normal = input.normals; + let normal = (&input.normals); var lightDirection = normalize((lightSource_11 - input.worldPosition)); - let cosTheta = dot(normal, lightDirection); + let cosTheta = dot((*normal), lightDirection); var diffuse = ((textureColor * lightColor) * max(0f, cosTheta)); var litColor = (ambient + diffuse); return vec4f(litColor.xyz, 1f); diff --git a/packages/typegpu/tests/ref.test.ts b/packages/typegpu/tests/ref.test.ts index c28a451a9a..4eccc77f4b 100644 --- a/packages/typegpu/tests/ref.test.ts +++ b/packages/typegpu/tests/ref.test.ts @@ -3,7 +3,7 @@ import { describe, expect } from 'vitest'; import { it } from './utils/extendedIt.ts'; import { asWgsl } from './utils/parseResolved.ts'; -describe('ref', () => { +describe('d.ref', () => { it('fails when using a ref as an external', () => { const sup = d.ref(0); diff --git a/packages/typegpu/tests/tgsl/argumentOrigin.test.ts b/packages/typegpu/tests/tgsl/argumentOrigin.test.ts new file mode 100644 index 0000000000..cebb6fba3a --- /dev/null +++ b/packages/typegpu/tests/tgsl/argumentOrigin.test.ts @@ -0,0 +1,148 @@ +import { describe, expect } from 'vitest'; +import * as d from '../../src/data/index.ts'; +import { it } from '../utils/extendedIt'; +import { asWgsl } from '../utils/parseResolved'; + +describe('function argument origin tracking', () => { + it('should allow mutation of primitive arguments', () => { + const foo = (a: number) => { + 'use gpu'; + a += 1; + }; + + const main = () => { + 'use gpu'; + foo(1); + }; + + expect(asWgsl(main)).toMatchInlineSnapshot(` + "fn foo(a: i32) { + a += 1i; + } + + fn main() { + foo(1i); + }" + `); + }); + + it('should allow mutation of destructured primitive arguments', () => { + const Foo = d.struct({ a: d.f32 }); + + const foo = ({ a }: { a: number }) => { + 'use gpu'; + a += 1; + }; + + const main = () => { + 'use gpu'; + foo(Foo({ a: 1 })); + }; + + expect(asWgsl(main)).toMatchInlineSnapshot(` + "struct Foo { + a: f32, + } + + fn foo(_arg_0: Foo) { + _arg_0.a += 1f; + } + + fn main() { + foo(Foo(1f)); + }" + `); + }); + + it('should fail on mutation of non-primitive arguments', () => { + const foo = (a: d.v3f) => { + 'use gpu'; + a.x += 1; + }; + + const main = () => { + 'use gpu'; + foo(d.vec3f(1, 2, 3)); + }; + + expect(() => asWgsl(main)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:main + - fn*:main() + - fn*:foo(vec3f): 'a.x += 1f' is invalid, because non-pointer arguments cannot be mutated.] + `); + }); + + it('should fail on transitive mutation of non-primitive arguments', () => { + const foo = (a: d.v3f) => { + 'use gpu'; + const b = a; + b.x += 1; + }; + + const main = () => { + 'use gpu'; + foo(d.vec3f(1, 2, 3)); + }; + + expect(() => asWgsl(main)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:main + - fn*:main() + - fn*:foo(vec3f): '(*b).x += 1f' is invalid, because non-pointer arguments cannot be mutated.] + `); + }); + + it('should fail on create a let variable from an argument reference', () => { + const foo = (a: d.v3f) => { + 'use gpu'; + let b = a; + b = d.vec3f(); + return b; + }; + + const main = () => { + 'use gpu'; + foo(d.vec3f(1, 2, 3)); + }; + + expect(() => asWgsl(main)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:main + - fn*:main() + - fn*:foo(vec3f): 'let b = a' is invalid, because references cannot be assigned to 'let' variable declarations. + ----- + - Try 'let b = vec3f(a)' if you need to reassign 'b' later + - Try 'const b = a' if you won't reassign 'b' later. + -----] + `); + }); + + it('should fail on assigning an argument reference to a variable', () => { + const foo = (a: d.v3f) => { + 'use gpu'; + let b = d.vec3f(); + b = a; + return b; + }; + + const main = () => { + 'use gpu'; + foo(d.vec3f(1, 2, 3)); + }; + + expect(() => asWgsl(main)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:main + - fn*:main() + - fn*:foo(vec3f): 'b = a' is invalid, because argument references cannot be assigned. + ----- + Try 'b = vec3f(a)' to copy the value instead. + -----] + `); + }); +}); diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index 6bee4caa2d..bf2bc02667 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -164,8 +164,8 @@ describe('TGSL tgpu.fn function', () => { @vertex fn vertex_fn(input: vertex_fn_Input) -> vertex_fn_Output { let vi = f32(input.vi); let ii = f32(input.ii); - var color = input.color; - return vertex_fn_Output(vec4f(color.w, ii, vi, 1f), vec2f(color.w, vi)); + let color = (&input.color); + return vertex_fn_Output(vec4f((*color).w, ii, vi, 1f), vec2f((*color).w, vi)); }" `); }); @@ -391,9 +391,9 @@ describe('TGSL tgpu.fn function', () => { } @fragment fn fragmentFn(input: fragmentFn_Input) -> fragmentFn_Output { - var pos = input.pos; + let pos = (&input.pos); var sampleMask = 0; - if (((input.sampleMask > 0u) && (pos.x > 0f))) { + if (((input.sampleMask > 0u) && ((*pos).x > 0f))) { sampleMask = 1i; } return fragmentFn_Output(u32(sampleMask), 1f, vec4f()); From c6b0537db55d5483571b39903f7c5597fd063e1b Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 6 Nov 2025 17:29:10 +0100 Subject: [PATCH 51/59] Update shader-generation.mdx --- .../docs/reference/shader-generation.mdx | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/apps/typegpu-docs/src/content/docs/reference/shader-generation.mdx b/apps/typegpu-docs/src/content/docs/reference/shader-generation.mdx index a405a5e1d2..ea970dc2e5 100644 --- a/apps/typegpu-docs/src/content/docs/reference/shader-generation.mdx +++ b/apps/typegpu-docs/src/content/docs/reference/shader-generation.mdx @@ -130,3 +130,63 @@ Data type inference is the basis for generating signatures for functions just fr ::: ### Origins + +Origins are enumerable values that describe where a value came from (or didn't come from). Used mainly for: +- Determining if we're using a value that refers to something else (to create an implicit pointer). This mimics the behavior we + expect in JS, and doesn't perform unwanted copies on data. Example: + ```ts + const foo = () => { + 'use gpu'; + // The type of both expressions is `Boid`, yet one is a + // reference to an existing value, and the other is a + // value-type (ephemeral) and would disappear if we didn't + // assign it to a variable or use it. + const firstBoid = layout.$.boids[0]; + const newBoid = Boid(); + const boidPos = newBoid.pos; + }; + ``` + Generates: + ```wgsl + fn foo() { + let firstBoid = (&boids[0]); // typed as ptr + var newBoid = Boid(); // typed as Boid + let boidPos = (&newBoid.pos); // typed as ptr + } + ``` +- Detecting illegal uses of our APIs. One example is mutating a value that was passed in as an argument. Since we want the developer to have control over + passing something as value or as reference (pointer), we have to limit the dev's ability to mutate values that were passed in as arguments if they didn't + use refs (pointer instances). Otherwise, the generated WGSL won't act as we expect. + ```ts + const advance = (pos: d.v3f) => { + 'use gpu'; + pos.x += 1; + }; + + const main = () => { + 'use gpu'; + const pos = d.vec3f(0, 0, 0); + advance(pos); + // pos.x === 1 in JS + }; + ``` + Generates: + ```wgsl + fn advance(pos: vec3f) { + pos.x += 1; + } + + fn main() { + let pos = vec3f(0, 0, 0); + advance(pos); + // pos.x === 0 in WGSL + } + ``` + +There are essentially three types of origins: +- **Ephemeral Origins**: These origins represent values that are created or derived from other values. They are typically used for creating new instances or + performing operations that produce new values. Examples include creating a new `Boid` instance or calculating a new position based on an existing one. These + include `'runtime'` and `'constant'` +- **Referential Origins**: These origins represent references to existing values. They are typically used for accessing or modifying existing data. Examples + include accessing the position of an existing `Boid` instance or modifying the position of an existing `Boid` instance. These include `'uniform'`, `'mutable'`, `'readonly'`, `'workgroup'`, `'private'`, `'function'`, `'handle'` and `'constant-ref'` +- **Argument Origins**: This group is dedicated to exactly one origin: 'argument'. It represents values that are passed as arguments to functions. From b3e979ee2ca14276e4d732aa07a423648a1d1225 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 6 Nov 2025 19:16:28 +0100 Subject: [PATCH 52/59] Update shader-generation.mdx --- .../src/content/docs/reference/shader-generation.mdx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/apps/typegpu-docs/src/content/docs/reference/shader-generation.mdx b/apps/typegpu-docs/src/content/docs/reference/shader-generation.mdx index ea970dc2e5..c9f62e76fd 100644 --- a/apps/typegpu-docs/src/content/docs/reference/shader-generation.mdx +++ b/apps/typegpu-docs/src/content/docs/reference/shader-generation.mdx @@ -143,6 +143,7 @@ Origins are enumerable values that describe where a value came from (or didn't c // assign it to a variable or use it. const firstBoid = layout.$.boids[0]; const newBoid = Boid(); + const boidPos = newBoid.pos; }; ``` @@ -151,6 +152,7 @@ Origins are enumerable values that describe where a value came from (or didn't c fn foo() { let firstBoid = (&boids[0]); // typed as ptr var newBoid = Boid(); // typed as Boid + let boidPos = (&newBoid.pos); // typed as ptr } ``` @@ -160,6 +162,11 @@ Origins are enumerable values that describe where a value came from (or didn't c ```ts const advance = (pos: d.v3f) => { 'use gpu'; + // `pos` has the origin 'argument'. Property accesses on arguments + // return snippets that also have the origin 'argument'. + // + // If we try to mutate a snippet that has the origin 'argument', + // we'll get a resolution error. pos.x += 1; }; @@ -186,7 +193,7 @@ Origins are enumerable values that describe where a value came from (or didn't c There are essentially three types of origins: - **Ephemeral Origins**: These origins represent values that are created or derived from other values. They are typically used for creating new instances or performing operations that produce new values. Examples include creating a new `Boid` instance or calculating a new position based on an existing one. These - include `'runtime'` and `'constant'` + include `'runtime'` and `'constant'`. - **Referential Origins**: These origins represent references to existing values. They are typically used for accessing or modifying existing data. Examples - include accessing the position of an existing `Boid` instance or modifying the position of an existing `Boid` instance. These include `'uniform'`, `'mutable'`, `'readonly'`, `'workgroup'`, `'private'`, `'function'`, `'handle'` and `'constant-ref'` + include accessing the position of an existing `Boid` instance or modifying the position of an existing `Boid` instance. These include `'uniform'`, `'mutable'`, `'readonly'`, `'workgroup'`, `'private'`, `'function'`, `'handle'` and `'constant-ref'`. - **Argument Origins**: This group is dedicated to exactly one origin: 'argument'. It represents values that are passed as arguments to functions. From e0b5cc7df8a4bc797cfb437e1f99f10357c95e7c Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 6 Nov 2025 19:31:09 +0100 Subject: [PATCH 53/59] =?UTF-8?q?=F0=9F=A6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../examples/simulation/gravity/helpers.ts | 6 +++++- packages/typegpu/src/data/ptr.ts | 6 +++++- packages/typegpu/src/tgsl/conversion.ts | 19 +++++++++---------- .../typegpu/tests/tgsl/argumentOrigin.test.ts | 4 ++-- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts b/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts index 16ee9b4930..24048eea50 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts @@ -3,7 +3,11 @@ import { OBJLoader } from '@loaders.gl/obj'; import type { TgpuRoot } from 'typegpu'; import * as d from 'typegpu/data'; import { sphereTextureNames } from './enums.ts'; -import { CelestialBody, renderVertexLayout, SkyBoxVertex } from './schemas.ts'; +import { + type CelestialBody, + renderVertexLayout, + SkyBoxVertex, +} from './schemas.ts'; function vert( position: [number, number, number], diff --git a/packages/typegpu/src/data/ptr.ts b/packages/typegpu/src/data/ptr.ts index 8f546923fb..35c45291c2 100644 --- a/packages/typegpu/src/data/ptr.ts +++ b/packages/typegpu/src/data/ptr.ts @@ -1,5 +1,9 @@ import { $internal } from '../shared/symbols.ts'; -import { Origin, OriginToPtrParams, originToPtrParams } from './snippet.ts'; +import { + type Origin, + type OriginToPtrParams, + originToPtrParams, +} from './snippet.ts'; import type { Access, AddressSpace, Ptr, StorableData } from './wgslTypes.ts'; export function ptrFn( diff --git a/packages/typegpu/src/tgsl/conversion.ts b/packages/typegpu/src/tgsl/conversion.ts index 1c63ca0d95..8a97451e0f 100644 --- a/packages/typegpu/src/tgsl/conversion.ts +++ b/packages/typegpu/src/tgsl/conversion.ts @@ -10,7 +10,7 @@ import { type I32, isMat, isVec, - Ptr, + type Ptr, type U32, type WgslStruct, } from '../data/wgslTypes.ts'; @@ -186,16 +186,15 @@ function findBestType( if (!bestResult) { return undefined; } - const actions: ConversionResultAction[] = bestResult.details.map(( - detail, - index, - ) => ({ - sourceIndex: index, - action: detail.action, - ...(detail.action === 'cast' && { - targetType: detail.targetType as U32 | F32 | I32 | F16, + const actions: ConversionResultAction[] = bestResult.details.map( + (detail, index) => ({ + sourceIndex: index, + action: detail.action, + ...(detail.action === 'cast' && { + targetType: detail.targetType as U32 | F32 | I32 | F16, + }), }), - })); + ); return { targetType: bestResult.type, diff --git a/packages/typegpu/tests/tgsl/argumentOrigin.test.ts b/packages/typegpu/tests/tgsl/argumentOrigin.test.ts index cebb6fba3a..fa964ae35d 100644 --- a/packages/typegpu/tests/tgsl/argumentOrigin.test.ts +++ b/packages/typegpu/tests/tgsl/argumentOrigin.test.ts @@ -1,7 +1,7 @@ import { describe, expect } from 'vitest'; import * as d from '../../src/data/index.ts'; -import { it } from '../utils/extendedIt'; -import { asWgsl } from '../utils/parseResolved'; +import { it } from '../utils/extendedIt.ts'; +import { asWgsl } from '../utils/parseResolved.ts'; describe('function argument origin tracking', () => { it('should allow mutation of primitive arguments', () => { From c31bb13bec3fa21bae012643216334300c21b1dd Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 6 Nov 2025 23:48:28 +0100 Subject: [PATCH 54/59] Better handling of arguments --- packages/typegpu-sdf/src/2d.ts | 2 +- packages/typegpu/src/data/ref.ts | 13 +- packages/typegpu/src/tgsl/wgslGenerator.ts | 62 +++- .../tests/examples/individual/gravity.test.ts | 4 +- .../examples/individual/jelly-slider.test.ts | 332 +++++++++--------- packages/typegpu/tests/ref.test.ts | 27 ++ .../typegpu/tests/tgsl/argumentOrigin.test.ts | 4 +- packages/typegpu/tests/tgslFn.test.ts | 8 +- 8 files changed, 261 insertions(+), 191 deletions(-) diff --git a/packages/typegpu-sdf/src/2d.ts b/packages/typegpu-sdf/src/2d.ts index 8b0c7a54fc..88ff0bf2a2 100644 --- a/packages/typegpu-sdf/src/2d.ts +++ b/packages/typegpu-sdf/src/2d.ts @@ -153,7 +153,7 @@ export const sdBezierApprox = tgpu.fn( }); export const sdPie = tgpu.fn([vec2f, vec2f, f32], f32)((p, c, r) => { - const p_w = p; + const p_w = vec2f(p); p_w.x = abs(p.x); const l = length(p_w) - r; const m = length(p_w.sub(c.mul(clamp(dot(p_w, c), 0, r)))); diff --git a/packages/typegpu/src/data/ref.ts b/packages/typegpu/src/data/ref.ts index 846620bcc8..5c5dfb9f7a 100644 --- a/packages/typegpu/src/data/ref.ts +++ b/packages/typegpu/src/data/ref.ts @@ -1,5 +1,5 @@ import { stitch } from '../core/resolve/stitch.ts'; -import { invariant } from '../errors.ts'; +import { invariant, WgslTypeError } from '../errors.ts'; import { inCodegenMode } from '../execMode.ts'; import { setName } from '../shared/meta.ts'; import { $internal, $ownSnippet, $resolve } from '../shared/symbols.ts'; @@ -46,6 +46,12 @@ export interface ref { export const ref: DualFn<(value: T) => ref> = (() => { const gpuImpl = (value: Snippet) => { + if (value.origin === 'argument') { + throw new WgslTypeError( + stitch`d.ref(${value}) is illegal, cannot take a reference of an argument. Copy the value locally first, and take a reference of the copy.`, + ); + } + /** * Pointer type only exists if the ref was created from a reference (buttery-butter). * @@ -161,10 +167,7 @@ export function derefSnippet(snippet: Snippet): Snippet { invariant(isPtr(snippet.dataType), 'Only pointers can be dereferenced'); const innerType = snippet.dataType.inner; - const origin = - isNaturallyEphemeral(innerType) && snippet.origin !== 'argument' - ? 'runtime' - : snippet.origin; + const origin = isNaturallyEphemeral(innerType) ? 'runtime' : snippet.origin; if (snippet.value instanceof RefOperator) { return snip(stitch`${snippet.value.snippet}`, innerType, origin); diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 44090a0515..4e5ef72472 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -717,6 +717,26 @@ ${this.ctx.pre}}`; ); } + // Arguments cannot be returned from functions without copying. A simple example why is: + // const identity = (x) => { + // 'use gpu'; + // return x; + // }; + // + // const foo = (arg: d.v3f) => { + // 'use gpu'; + // const marg = identity(arg); + // marg.x = 1; // 'marg's origin would be 'runtime', so we wouldn't be able to track this misuse. + // }; + if ( + returnSnippet.origin === 'argument' && + !isEphemeralSnippet(returnSnippet) + ) { + throw new WgslTypeError( + stitch`Cannot return references to arguments, returning '${returnSnippet}'. Copy the argument before returning it.`, + ); + } + if ( !expectedReturnType && !isEphemeralSnippet(returnSnippet) && @@ -794,6 +814,10 @@ ${this.ctx.pre}else ${alternate}`; ); } + const ephemeral = isEphemeralSnippet(eq); + let dataType = eq.dataType as wgsl.AnyWgslData; + const naturallyEphemeral = wgsl.isNaturallyEphemeral(dataType); + if (isLooseData(eq.dataType)) { throw new Error( `Cannot create variable '${rawId}' with loose data type.`, @@ -821,14 +845,9 @@ ${this.ctx.pre}else ${alternate}`; };`; } - let dataType = eq.dataType as wgsl.AnyWgslData; - // Assigning a reference to a `const` variable means we store the pointer // of the rhs. - if ( - !isEphemeralSnippet(eq) || - (eq.origin === 'argument' && !wgsl.isNaturallyEphemeral(dataType)) - ) { + if (!ephemeral) { // Referential if (stmtType === NODE.let) { const rhsStr = this.ctx.resolve(eq.value).value; @@ -866,11 +885,32 @@ ${this.ctx.pre}else ${alternate}`; } } else { // Non-referential - if ( - stmtType === NODE.const && - wgsl.isNaturallyEphemeral(dataType) - ) { - varType = eq.origin === 'constant' ? 'const' : 'let'; + + if (eq.origin === 'argument') { + if (stmtType === NODE.let) { + const rhsStr = this.ctx.resolve(eq.value).value; + const rhsTypeStr = + this.ctx.resolve(toStorable(eq.dataType as wgsl.StorableData)) + .value; + + throw new WgslTypeError( + `'let ${rawId} = ${rhsStr}' is invalid, because references to arguments cannot be assigned to 'let' variable declarations. +----- +- Try 'let ${rawId} = ${rhsTypeStr}(${rhsStr})' if you need to reassign '${rawId}' later +- Try 'const ${rawId} = ${rhsStr}' if you won't reassign '${rawId}' later. +-----`, + ); + } + varType = 'let'; + } + + if (stmtType === NODE.const) { + if (eq.origin === 'argument') { + // Arguments cannot be mutated, so we 'let' them be (kill me) + varType = 'let'; + } else if (naturallyEphemeral) { + varType = eq.origin === 'constant' ? 'const' : 'let'; + } } } diff --git a/packages/typegpu/tests/examples/individual/gravity.test.ts b/packages/typegpu/tests/examples/individual/gravity.test.ts index 408a1e907e..f74648f5a2 100644 --- a/packages/typegpu/tests/examples/individual/gravity.test.ts +++ b/packages/typegpu/tests/examples/individual/gravity.test.ts @@ -270,9 +270,9 @@ describe('gravity example', () => { var lightColor = vec3f(1, 0.8999999761581421, 0.8999999761581421); var textureColor = textureSample(celestialBodyTextures_9, sampler_10, input.uv, input.sphereTextureIndex).xyz; var ambient = ((textureColor * lightColor) * input.ambientLightFactor); - let normal = (&input.normals); + let normal = input.normals; var lightDirection = normalize((lightSource_11 - input.worldPosition)); - let cosTheta = dot((*normal), lightDirection); + let cosTheta = dot(normal, lightDirection); var diffuse = ((textureColor * lightColor) * max(0f, cosTheta)); var litColor = (ambient + diffuse); return vec4f(litColor.xyz, 1f); diff --git a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts index a4fd60a826..bbd4ce31ce 100644 --- a/packages/typegpu/tests/examples/individual/jelly-slider.test.ts +++ b/packages/typegpu/tests/examples/individual/jelly-slider.test.ts @@ -98,44 +98,44 @@ describe('jelly-slider example', () => { } fn getRay_8(ndc: vec2f) -> Ray_11 { - var clipPos = vec4f(ndc.x, ndc.y, -1, 1f); - var invView = cameraUniform_9.viewInv; - var invProj = cameraUniform_9.projInv; - var viewPos = (invProj * clipPos); + var clipPos = vec4f(ndc.x, ndc.y, -1f, 1f); + let invView = (&cameraUniform_9.viewInv); + let invProj = (&cameraUniform_9.projInv); + var viewPos = ((*invProj) * clipPos); var viewPosNormalized = vec4f((viewPos.xyz / viewPos.w), 1f); - var worldPos = (invView * viewPosNormalized); - var rayOrigin = invView[3].xyz; + var worldPos = ((*invView) * viewPosNormalized); + var rayOrigin = (*invView)[3i].xyz; var rayDir = normalize((worldPos.xyz - rayOrigin)); return Ray_11(rayOrigin, rayDir); } - fn sdRoundedBox2d_15(p: vec2f, size: vec2f, cornerRadius: f32) -> f32 { + fn sdPlane_14(p: vec3f, n: vec3f, h: f32) -> f32 { + return (dot(p, n) + h); + } + + fn sdRoundedBox2d_16(p: vec2f, size: vec2f, cornerRadius: f32) -> f32 { var d = ((abs(p) - size) + vec2f(cornerRadius)); return ((length(max(d, vec2f())) + min(max(d.x, d.y), 0f)) - cornerRadius); } - fn rectangleCutoutDist_14(position: vec2f) -> f32 { - var groundRoundness = 0.02; - return sdRoundedBox2d_15(position, vec2f((1f + groundRoundness), (0.2f + groundRoundness)), (0.2f + groundRoundness)); + fn rectangleCutoutDist_15(position: vec2f) -> f32 { + const groundRoundness = 0.02; + return sdRoundedBox2d_16(position, vec2f((1f + groundRoundness), (0.2f + groundRoundness)), (0.2f + groundRoundness)); } - fn opExtrudeY_16(p: vec3f, dd: f32, h: f32) -> f32 { + fn opExtrudeY_17(p: vec3f, dd: f32, h: f32) -> f32 { var w = vec2f(dd, (abs(p.y) - h)); return (min(max(w.x, w.y), 0f) + length(max(w, vec2f()))); } - fn opUnion_17(d1: f32, d2: f32) -> f32 { + fn opUnion_18(d1: f32, d2: f32) -> f32 { return min(d1, d2); } - fn sdPlane_18(p: vec3f, n: vec3f, h: f32) -> f32 { - return (dot(p, n) + h); - } - fn getMainSceneDist_13(position: vec3f) -> f32 { - var groundThickness = 0.03; - var groundRoundness = 0.02; - return opUnion_17(sdPlane_18(position, vec3f(0, 1, 0), 0.06f), (opExtrudeY_16(position, -rectangleCutoutDist_14(position.xz), (groundThickness - groundRoundness)) - groundRoundness)); + const groundThickness = 0.03; + const groundRoundness = 0.02; + return opUnion_18(sdPlane_14(position, vec3f(0, 1, 0), 0.06f), (opExtrudeY_17(position, -(rectangleCutoutDist_15(position.xz)), (groundThickness - groundRoundness)) - groundRoundness)); } @group(0) @binding(2) var item_20: vec4f; @@ -145,15 +145,15 @@ describe('jelly-slider example', () => { @group(0) @binding(4) var filteringSampler_23: sampler; fn renderPercentageOnGround_21(hitPosition: vec3f, center: vec3f, percentage: u32) -> vec4f { - var textWidth = 0.38; - var textHeight = 0.33; + const textWidth = 0.38; + const textHeight = 0.33; if (((abs((hitPosition.x - center.x)) > (textWidth * 0.5f)) || (abs((hitPosition.z - center.z)) > (textHeight * 0.5f)))) { return vec4f(); } - var localX = (hitPosition.x - center.x); - var localZ = (hitPosition.z - center.z); - var uvX = ((localX + (textWidth * 0.5f)) / textWidth); - var uvZ = ((localZ + (textHeight * 0.5f)) / textHeight); + let localX = (hitPosition.x - center.x); + let localZ = (hitPosition.z - center.z); + let uvX = ((localX + (textWidth * 0.5f)) / textWidth); + let uvZ = ((localZ + (textHeight * 0.5f)) / textHeight); if (((((uvX < 0f) || (uvX > 1f)) || (uvZ < 0f)) || (uvZ > 1f))) { return vec4f(); } @@ -170,8 +170,8 @@ describe('jelly-slider example', () => { @group(0) @binding(6) var bezierTexture_26: texture_2d; fn item_28() -> f32 { - var a = dot(seed_7, vec2f(23.140779495239258, 232.6168975830078)); - var b = dot(seed_7, vec2f(54.47856521606445, 345.8415222167969)); + let a = dot(seed_7, vec2f(23.140779495239258, 232.6168975830078)); + let b = dot(seed_7, vec2f(54.47856521606445, 345.8415222167969)); seed_7.x = fract((cos(a) * 136.8168f)); seed_7.y = fract((cos(b) * 534.7645f)); return seed_7.y; @@ -182,7 +182,7 @@ describe('jelly-slider example', () => { } fn getNormalFromSdf_30(position: vec3f, epsilon: f32) -> vec3f { - var k = vec3f(1f, -1, 0f); + var k = vec3f(1, -1, 0); var offset1 = (k.xyy * epsilon); var offset2 = (k.yyx * epsilon); var offset3 = (k.yxy * epsilon); @@ -209,26 +209,26 @@ describe('jelly-slider example', () => { } fn getFakeShadow_34(position: vec3f, lightDir: vec3f) -> vec3f { - var jellyColor = jellyColorUniform_31; - var endCapX = item_20.x; - if ((position.y < -0.03)) { - var fadeSharpness = 30; - var inset = 0.02; - var cutout = (rectangleCutoutDist_14(position.xz) + inset); - var edgeDarkening = saturate((1f - (cutout * f32(fadeSharpness)))); - var lightGradient = saturate((((-position.z * 4f) * lightDir.z) + 1f)); + let jellyColor = (&jellyColorUniform_31); + let endCapX = item_20.x; + if ((position.y < -0.03f)) { + const fadeSharpness = 30; + const inset = 0.02; + let cutout = (rectangleCutoutDist_15(position.xz) + inset); + let edgeDarkening = saturate((1f - (cutout * f32(fadeSharpness)))); + let lightGradient = saturate((((-(position.z) * 4f) * lightDir.z) + 1f)); return ((vec3f(1) * edgeDarkening) * (lightGradient * 0.5f)); } else { - var finalUV = vec2f((((position.x - ((position.z * lightDir.x) * sign(lightDir.z))) * 0.5f) + 0.5f), ((1f - ((-position.z / lightDir.z) * 0.5f)) - 0.2f)); + var finalUV = vec2f((((position.x - ((position.z * lightDir.x) * sign(lightDir.z))) * 0.5f) + 0.5f), ((1f - ((-(position.z) / lightDir.z) * 0.5f)) - 0.2f)); var data = textureSampleLevel(bezierTexture_26, filteringSampler_23, finalUV, 0); - var jellySaturation = mix(0, data.y, saturate(((position.x * 1.5f) + 1.1f))); - var shadowColor = mix(vec3f(), jellyColor.xyz, jellySaturation); - var contrast = ((20f * saturate(finalUV.y)) * (0.8f + (endCapX * 0.2f))); - var shadowOffset = -0.3; - var featherSharpness = 10; - var uvEdgeFeather = (((saturate((finalUV.x * f32(featherSharpness))) * saturate(((1f - finalUV.x) * f32(featherSharpness)))) * saturate(((1f - finalUV.y) * f32(featherSharpness)))) * saturate(finalUV.y)); - var influence = (saturate(((1f - lightDir.y) * 2f)) * uvEdgeFeather); + let jellySaturation = mix(0f, data.y, saturate(((position.x * 1.5f) + 1.1f))); + var shadowColor = mix(vec3f(), (*jellyColor).xyz, jellySaturation); + let contrast = ((20f * saturate(finalUV.y)) * (0.8f + (endCapX * 0.2f))); + const shadowOffset = -0.3; + const featherSharpness = 10; + let uvEdgeFeather = (((saturate((finalUV.x * f32(featherSharpness))) * saturate(((1f - finalUV.x) * f32(featherSharpness)))) * saturate(((1f - finalUV.y) * f32(featherSharpness)))) * saturate(finalUV.y)); + let influence = (saturate(((1f - lightDir.y) * 2f)) * uvEdgeFeather); return mix(vec3f(1), mix(shadowColor, vec3f(1), saturate(((data.x * contrast) + shadowOffset))), influence); } } @@ -236,10 +236,10 @@ describe('jelly-slider example', () => { fn calculateLighting_33(hitPosition: vec3f, normal: vec3f, rayOrigin: vec3f) -> vec3f { var lightDir = -(lightUniform_24.direction); var fakeShadow = getFakeShadow_34(hitPosition, lightDir); - var diffuse = max(dot(normal, lightDir), 0f); + let diffuse = max(dot(normal, lightDir), 0f); var viewDir = normalize((rayOrigin - hitPosition)); var reflectDir = reflect(-(lightDir), normal); - var specularFactor = pow(max(dot(viewDir, reflectDir), 0f), 10f); + let specularFactor = pow(max(dot(viewDir, reflectDir), 0f), 10f); var specular = (lightUniform_24.color * (specularFactor * 0.6f)); var baseColor = vec3f(0.8999999761581421); var directionalLight = (((baseColor * lightUniform_24.color) * diffuse) * fakeShadow); @@ -270,8 +270,8 @@ describe('jelly-slider example', () => { var uv = vec2f(((p.x - bbox.left) / (bbox.right - bbox.left)), ((bbox.top - p.y) / (bbox.top - bbox.bottom))); var clampedUV = saturate(uv); var sampledColor = textureSampleLevel(bezierTexture_26, filteringSampler_23, clampedUV, 0); - var segUnsigned = sampledColor.x; - var progress = sampledColor.y; + let segUnsigned = sampledColor.x; + let progress = sampledColor.y; var normal = sampledColor.zw; return LineInfo_42(progress, segUnsigned, normal); } @@ -288,37 +288,37 @@ describe('jelly-slider example', () => { return 1000000000; } var poly2D = sdInflatedPolyline2D_41(p); - var dist3D = (opExtrudeZ_43(position, poly2D.distance, 0.17f) - 0.024f); + let dist3D = (opExtrudeZ_43(position, poly2D.distance, 0.17f) - 0.024f); return dist3D; } fn getSceneDistForAO_37(position: vec3f) -> f32 { - var mainScene = getMainSceneDist_13(position); - var sliderApprox = sliderApproxDist_38(position); + let mainScene = getMainSceneDist_13(position); + let sliderApprox = sliderApproxDist_38(position); return min(mainScene, sliderApprox); } fn calculateAO_36(position: vec3f, normal: vec3f) -> f32 { var totalOcclusion = 0f; var sampleWeight = 1f; - var stepDistance = 0.03333333333333333; + const stepDistance = 0.03333333333333333; for (var i = 1; (i <= 3i); i++) { - var sampleHeight = (stepDistance * f32(i)); + let sampleHeight = (stepDistance * f32(i)); var samplePosition = (position + (normal * sampleHeight)); - var distanceToSurface = (getSceneDistForAO_37(samplePosition) - 5e-3f); - var occlusionContribution = max(0f, (sampleHeight - distanceToSurface)); + let distanceToSurface = (getSceneDistForAO_37(samplePosition) - 5e-3f); + let occlusionContribution = max(0f, (sampleHeight - distanceToSurface)); totalOcclusion += (occlusionContribution * sampleWeight); sampleWeight *= 0.5f; if ((totalOcclusion > 0.2f)) { break; } } - var rawAO = (1f - ((0.5f * totalOcclusion) / 0.1f)); + let rawAO = (1f - ((0.5f * totalOcclusion) / 0.1f)); return saturate(rawAO); } fn applyAO_35(litColor: vec3f, hitPosition: vec3f, normal: vec3f) -> vec4f { - var ao = calculateAO_36(hitPosition, normal); + let ao = calculateAO_36(hitPosition, normal); var finalColor = (litColor * ao); return vec4f(finalColor, 1f); } @@ -327,35 +327,35 @@ describe('jelly-slider example', () => { var hitPosition = (rayOrigin + (rayDirection * backgroundHitDist)); var percentageSample = renderPercentageOnGround_21(hitPosition, vec3f(0.7200000286102295, 0, 0), u32(((item_20.x + 0.43f) * 84f))); var highlights = 0f; - var highlightWidth = 1f; - var highlightHeight = 0.2; + const highlightWidth = 1f; + const highlightHeight = 0.2; var offsetX = 0f; var offsetZ = 0.05000000074505806f; - var lightDir = lightUniform_24.direction; - var causticScale = 0.2; - offsetX -= (lightDir.x * causticScale); - offsetZ += (lightDir.z * causticScale); - var endCapX = item_20.x; - var sliderStretch = ((endCapX + 1f) * 0.5f); + let lightDir = (&lightUniform_24.direction); + const causticScale = 0.2; + offsetX -= ((*lightDir).x * causticScale); + offsetZ += ((*lightDir).z * causticScale); + let endCapX = item_20.x; + let sliderStretch = ((endCapX + 1f) * 0.5f); if (((abs((hitPosition.x + offsetX)) < highlightWidth) && (abs((hitPosition.z + offsetZ)) < highlightHeight))) { - var uvX_orig = ((((hitPosition.x + offsetX) + (highlightWidth * 2f)) / highlightWidth) * 0.5f); - var uvZ_orig = ((((hitPosition.z + offsetZ) + (highlightHeight * 2f)) / highlightHeight) * 0.5f); + let uvX_orig = ((((hitPosition.x + offsetX) + (highlightWidth * 2f)) / highlightWidth) * 0.5f); + let uvZ_orig = ((((hitPosition.z + offsetZ) + (highlightHeight * 2f)) / highlightHeight) * 0.5f); var centeredUV = vec2f((uvX_orig - 0.5f), (uvZ_orig - 0.5f)); var finalUV = vec2f(centeredUV.x, (1f - (pow((abs((centeredUV.y - 0.5f)) * 2f), 2f) * 0.3f))); - var density = max(0f, ((textureSampleLevel(bezierTexture_26, filteringSampler_23, finalUV, 0).x - 0.25f) * 8f)); - var fadeX = smoothstep(0, -0.2, (hitPosition.x - endCapX)); - var fadeZ = (1f - pow((abs((centeredUV.y - 0.5f)) * 2f), 3f)); - var fadeStretch = saturate((1f - sliderStretch)); - var edgeFade = ((saturate(fadeX) * saturate(fadeZ)) * fadeStretch); - highlights = ((((pow(density, 3f) * edgeFade) * 3f) * (1f + lightDir.z)) / 1.5f); + let density = max(0f, ((textureSampleLevel(bezierTexture_26, filteringSampler_23, finalUV, 0).x - 0.25f) * 8f)); + let fadeX = smoothstep(0, -0.2, (hitPosition.x - endCapX)); + let fadeZ = (1f - pow((abs((centeredUV.y - 0.5f)) * 2f), 3f)); + let fadeStretch = saturate((1f - sliderStretch)); + let edgeFade = ((saturate(fadeX) * saturate(fadeZ)) * fadeStretch); + highlights = ((((pow(density, 3f) * edgeFade) * 3f) * (1f + (*lightDir).z)) / 1.5f); } - var originYBound = saturate((rayOrigin.y + 0.01f)); + let originYBound = saturate((rayOrigin.y + 0.01f)); var posOffset = (hitPosition + (vec3f(0, 1, 0) * ((offset * (originYBound / (1f + originYBound))) * (1f + (randFloat01_27() / 2f))))); var newNormal = getNormalMain_29(posOffset); - var jellyColor = jellyColorUniform_31; - var sqDist = sqLength_32((hitPosition - vec3f(endCapX, 0f, 0f))); - var bounceLight = (jellyColor.xyz * ((1f / ((sqDist * 15f) + 1f)) * 0.4f)); - var sideBounceLight = ((jellyColor.xyz * ((1f / ((sqDist * 40f) + 1f)) * 0.3f)) * abs(newNormal.z)); + let jellyColor = (&jellyColorUniform_31); + let sqDist = sqLength_32((hitPosition - vec3f(endCapX, 0f, 0f))); + var bounceLight = ((*jellyColor).xyz * ((1f / ((sqDist * 15f) + 1f)) * 0.4f)); + var sideBounceLight = (((*jellyColor).xyz * ((1f / ((sqDist * 40f) + 1f)) * 0.3f)) * abs(newNormal.z)); var litColor = calculateLighting_33(posOffset, newNormal, rayOrigin); var backgroundColor = ((applyAO_35((vec3f(1) * litColor), posOffset, newNormal) + vec4f(bounceLight, 0f)) + vec4f(sideBounceLight, 0f)); var textColor = saturate((backgroundColor.xyz * vec3f(0.5))); @@ -374,8 +374,8 @@ describe('jelly-slider example', () => { var t2 = ((boxMax - rayOrigin) * invDir); var tMinVec = min(t1, t2); var tMaxVec = max(t1, t2); - var tMin = max(max(tMinVec.x, tMinVec.y), tMinVec.z); - var tMax = min(min(tMaxVec.x, tMaxVec.y), tMaxVec.z); + let tMin = max(max(tMinVec.x, tMinVec.y), tMinVec.z); + let tMax = min(min(tMaxVec.x, tMaxVec.y), tMaxVec.z); var result = BoxIntersection_45(); result.hit = ((tMax >= tMin) && (tMax >= 0f)); result.tMin = tMin; @@ -386,21 +386,21 @@ describe('jelly-slider example', () => { fn sdPie_49(p: vec2f, c: vec2f, r: f32) -> f32 { var p_w = p; p_w.x = abs(p.x); - var l = (length(p_w) - r); - var m = length((p_w - (c * clamp(dot(p_w, c), 0f, r)))); + let l = (length(p_w) - r); + let m = length((p_w - (c * clamp(dot(p_w, c), 0f, r)))); return max(l, (m * sign(((c.y * p_w.x) - (c.x * p_w.y))))); } fn cap3D_48(position: vec3f) -> f32 { - var endCap = item_20; - var secondLastPoint = vec2f(endCap.x, endCap.y); - var lastPoint = vec2f(endCap.z, endCap.w); - var angle = atan2((lastPoint.y - secondLastPoint.y), (lastPoint.x - secondLastPoint.x)); - var rot = mat2x2f(cos(angle), -sin(angle), sin(angle), cos(angle)); + let endCap = (&item_20); + var secondLastPoint = vec2f((*endCap).x, (*endCap).y); + var lastPoint = vec2f((*endCap).z, (*endCap).w); + let angle = atan2((lastPoint.y - secondLastPoint.y), (lastPoint.x - secondLastPoint.x)); + var rot = mat2x2f(cos(angle), -(sin(angle)), sin(angle), cos(angle)); var pieP = (position - vec3f(secondLastPoint, 0f)); pieP = vec3f((rot * pieP.xy), pieP.z); - var hmm = sdPie_49(pieP.zx, vec2f(1, 0), 0.17f); - var extrudeEnd = (opExtrudeY_16(pieP, hmm, 1e-3f) - 0.024f); + let hmm = sdPie_49(pieP.zx, vec2f(1, 0), 0.17f); + let extrudeEnd = (opExtrudeY_17(pieP, hmm, 1e-3f) - 0.024f); return extrudeEnd; } @@ -411,7 +411,7 @@ describe('jelly-slider example', () => { finalDist = cap3D_48(position); } else { - var body = (opExtrudeZ_43(position, poly2D.distance, 0.17f) - 0.024f); + let body = (opExtrudeZ_43(position, poly2D.distance, 0.17f) - 0.024f); finalDist = body; } return LineInfo_42(poly2D.t, finalDist, poly2D.normal); @@ -424,7 +424,7 @@ describe('jelly-slider example', () => { } fn getSceneDist_46(position: vec3f) -> HitInfo_50 { - var mainScene = getMainSceneDist_13(position); + let mainScene = getMainSceneDist_13(position); var poly3D = sliderSdf3D_47(position); var hitInfo = HitInfo_50(); if ((poly3D.distance < mainScene)) { @@ -440,7 +440,7 @@ describe('jelly-slider example', () => { } fn getNormalFromSdf_54(position: vec3f, epsilon: f32) -> vec3f { - var k = vec3f(1f, -1, 0f); + var k = vec3f(1, -1, 0); var offset1 = (k.xyy * epsilon); var offset2 = (k.yyx * epsilon); var offset3 = (k.yxy * epsilon); @@ -459,22 +459,22 @@ describe('jelly-slider example', () => { fn getSliderNormal_52(position: vec3f, hitInfo: HitInfo_50) -> vec3f { var poly2D = sdInflatedPolyline2D_41(position.xy); - var gradient2D = poly2D.normal; - var threshold = 0.14450000000000002; - var absZ = abs(position.z); - var zDistance = max(0f, (((absZ - threshold) * 0.17f) / (0.17f - threshold))); - var edgeDistance = (0.024f - poly2D.distance); - var edgeContrib = 0.9; - var zContrib = (1f - edgeContrib); - var zDirection = sign(position.z); + let gradient2D = (&poly2D.normal); + const threshold = 0.14450000000000002; + let absZ = abs(position.z); + let zDistance = max(0f, (((absZ - threshold) * 0.17f) / (0.17f - threshold))); + let edgeDistance = (0.024f - poly2D.distance); + const edgeContrib = 0.9; + let zContrib = (1f - edgeContrib); + let zDirection = sign(position.z); var zAxisVector = vec3f(0f, 0f, zDirection); - var edgeBlendDistance = ((edgeContrib * 0.024f) + (zContrib * 0.17f)); - var blendFactor = smoothstep(edgeBlendDistance, 0, ((zDistance * zContrib) + (edgeDistance * edgeContrib))); - var normal2D = vec3f(gradient2D.xy, 0f); + let edgeBlendDistance = ((edgeContrib * 0.024f) + (zContrib * 0.17f)); + let blendFactor = smoothstep(edgeBlendDistance, 0, ((zDistance * zContrib) + (edgeDistance * edgeContrib))); + var normal2D = vec3f((*gradient2D).xy, 0f); var blendedNormal = mix(zAxisVector, normal2D, ((blendFactor * 0.5f) + 0.5f)); var normal = normalize(blendedNormal); if ((hitInfo.t > 0.94f)) { - var ratio = ((hitInfo.t - 0.94f) / 0.02f); + let ratio = ((hitInfo.t - 0.94f) / 0.02f); var fullNormal = getNormalCap_53(position); normal = normalize(mix(normal, fullNormal, ratio)); } @@ -489,7 +489,7 @@ describe('jelly-slider example', () => { } fn fresnelSchlick_55(cosTheta: f32, ior1: f32, ior2: f32) -> f32 { - var r0 = pow(((ior1 - ior2) / (ior1 + ior2)), 2f); + let r0 = pow(((ior1 - ior2) / (ior1 + ior2)), 2f); return (r0 + ((1f - r0) * pow((1f - cosTheta), 5f))); } @@ -513,7 +513,7 @@ describe('jelly-slider example', () => { } fn beerLambert_58(sigma: vec3f, dist: f32) -> vec3f { - return exp((sigma * -dist)); + return exp((sigma * -(dist))); } fn rayMarch_12(rayOrigin: vec3f, rayDirection: vec3f, uv: vec2f) -> vec4f { @@ -521,7 +521,7 @@ describe('jelly-slider example', () => { var backgroundDist = 0f; for (var i = 0; (i < 64i); i++) { var p = (rayOrigin + (rayDirection * backgroundDist)); - var hit = getMainSceneDist_13(p); + let hit = getMainSceneDist_13(p); backgroundDist += hit; if ((hit < 1e-3f)) { break; @@ -529,8 +529,8 @@ describe('jelly-slider example', () => { } var background = renderBackground_19(rayOrigin, rayDirection, backgroundDist, 0f); var bbox = getSliderBbox_39(); - var zDepth = 0.25f; - var sliderMin = vec3f(bbox.left, bbox.bottom, -zDepth); + const zDepth = 0.25f; + var sliderMin = vec3f(bbox.left, bbox.bottom, -(zDepth)); var sliderMax = vec3f(bbox.right, bbox.top, zDepth); var intersection = intersectBox_44(rayOrigin, rayDirection, sliderMin, sliderMax); if (!intersection.hit) { @@ -551,26 +551,26 @@ describe('jelly-slider example', () => { break; } var N = getNormal_51(hitPosition, hitInfo); - var I = rayDirection; - var cosi = min(1f, max(0f, dot(-(I), N))); - var F = fresnelSchlick_55(cosi, 1f, 1.4199999570846558f); + let I = rayDirection; + let cosi = min(1f, max(0f, dot(-(I), N))); + let F = fresnelSchlick_55(cosi, 1f, 1.4199999570846558f); var reflection = saturate(vec3f((hitPosition.y + 0.2f))); - var eta = 0.7042253521126761; - var k = (1f - ((eta * eta) * (1f - (cosi * cosi)))); + const eta = 0.7042253521126761; + let k = (1f - ((eta * eta) * (1f - (cosi * cosi)))); var refractedColor = vec3f(); if ((k > 0f)) { var refrDir = normalize(((I * eta) + (N * ((eta * cosi) - sqrt(k))))); var p = (hitPosition + (refrDir * 2e-3)); var exitPos = (p + (refrDir * 2e-3)); var env = rayMarchNoJelly_56(exitPos, refrDir); - var progress = hitInfo.t; - var jellyColor = jellyColorUniform_31; - var scatterTint = (jellyColor.xyz * 1.5); - var density = 20f; - var absorb = ((vec3f(1) - jellyColor.xyz) * density); - var T = beerLambert_58((absorb * pow(progress, 2f)), 0.08); + let progress = hitInfo.t; + let jellyColor = (&jellyColorUniform_31); + var scatterTint = ((*jellyColor).xyz * 1.5); + const density = 20f; + var absorb = ((vec3f(1) - (*jellyColor).xyz) * density); + var T = beerLambert_58((absorb * pow(progress, 2f)), 0.08f); var lightDir = -(lightUniform_24.direction); - var forward = max(0f, dot(lightDir, refrDir)); + let forward = max(0f, dot(lightDir, refrDir)); var scatter = (scatterTint * ((3f * forward) * pow(progress, 3f))); refractedColor = ((env * T) + scatter); } @@ -590,7 +590,7 @@ describe('jelly-slider example', () => { @fragment fn raymarchFn_3(_arg_0: raymarchFn_Input_59) -> @location(0) vec4f { randSeed2_5((randomUniform_4 * _arg_0.uv)); - var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -((_arg_0.uv.y * 2f) - 1f)); + var ndc = vec2f(((_arg_0.uv.x * 2f) - 1f), -(((_arg_0.uv.y * 2f) - 1f))); var ray = getRay_8(ndc); var color = rayMarch_12(ray.origin, ray.direction, _arg_0.uv); return vec4f(tanh((color.xyz * 1.3)), 1f); @@ -607,8 +607,8 @@ describe('jelly-slider example', () => { } @compute @workgroup_size(16, 16) fn taaResolveFn_0(_arg_0: taaResolveFn_Input_4) { - var currentColor = textureLoad(currentTexture_1, vec2u(_arg_0.gid.xy), 0); - var historyColor = textureLoad(historyTexture_2, vec2u(_arg_0.gid.xy), 0); + var currentColor = textureLoad(currentTexture_1, _arg_0.gid.xy, 0); + var historyColor = textureLoad(historyTexture_2, _arg_0.gid.xy, 0); var minColor = vec3f(9999); var maxColor = vec3f(-9999); var dimensions = textureDimensions(currentTexture_1); @@ -623,17 +623,17 @@ describe('jelly-slider example', () => { } var historyColorClamped = clamp(historyColor.xyz, minColor, maxColor); var uv = (vec2f(_arg_0.gid.xy) / vec2f(dimensions.xy)); - var textRegionMinX = 0.7099999785423279f; - var textRegionMaxX = 0.8500000238418579f; - var textRegionMinY = 0.4699999988079071f; - var textRegionMaxY = 0.550000011920929f; - var borderSize = 0.019999999552965164f; - var fadeInX = smoothstep((textRegionMinX - borderSize), (textRegionMinX + borderSize), uv.x); - var fadeOutX = (1f - smoothstep((textRegionMaxX - borderSize), (textRegionMaxX + borderSize), uv.x)); - var fadeInY = smoothstep((textRegionMinY - borderSize), (textRegionMinY + borderSize), uv.y); - var fadeOutY = (1f - smoothstep((textRegionMaxY - borderSize), (textRegionMaxY + borderSize), uv.y)); - var inTextRegion = (((fadeInX * fadeOutX) * fadeInY) * fadeOutY); - var blendFactor = mix(0.8999999761581421f, 0.699999988079071f, inTextRegion); + const textRegionMinX = 0.7099999785423279f; + const textRegionMaxX = 0.8500000238418579f; + const textRegionMinY = 0.4699999988079071f; + const textRegionMaxY = 0.550000011920929f; + const borderSize = 0.019999999552965164f; + let fadeInX = smoothstep((textRegionMinX - borderSize), (textRegionMinX + borderSize), uv.x); + let fadeOutX = (1f - smoothstep((textRegionMaxX - borderSize), (textRegionMaxX + borderSize), uv.x)); + let fadeInY = smoothstep((textRegionMinY - borderSize), (textRegionMinY + borderSize), uv.y); + let fadeOutY = (1f - smoothstep((textRegionMaxY - borderSize), (textRegionMaxY + borderSize), uv.y)); + let inTextRegion = (((fadeInX * fadeOutX) * fadeInY) * fadeOutY); + let blendFactor = mix(0.8999999761581421f, 0.699999988079071f, inTextRegion); var resolvedColor = vec4f(mix(currentColor.xyz, historyColorClamped, blendFactor), 1f); textureStore(outputTexture_3, vec2u(_arg_0.gid.x, _arg_0.gid.y), resolvedColor); } @@ -683,29 +683,29 @@ describe('jelly-slider example', () => { var b = ((A - (B * 2)) + C); var c = (a * 2f); var d = (A - pos); - var dotB = max(dot(b, b), 1e-4f); - var kk = (1f / dotB); - var kx = (kk * dot(a, b)); - var ky = ((kk * ((2f * dot(a, a)) + dot(d, b))) / 3f); - var kz = (kk * dot(d, a)); + let dotB = max(dot(b, b), 1e-4f); + let kk = (1f / dotB); + let kx = (kk * dot(a, b)); + let ky = ((kk * ((2f * dot(a, a)) + dot(d, b))) / 3f); + let kz = (kk * dot(d, a)); var res = 0f; - var p = (ky - (kx * kx)); - var p3 = ((p * p) * p); - var q = ((kx * (((2f * kx) * kx) - (3f * ky))) + kz); + let p = (ky - (kx * kx)); + let p3 = ((p * p) * p); + let q = ((kx * (((2f * kx) * kx) - (3f * ky))) + kz); var h = ((q * q) + (4f * p3)); if ((h >= 0f)) { h = sqrt(h); - var x = ((vec2f(h, -h) - q) * 0.5); + var x = ((vec2f(h, -(h)) - q) * 0.5); var uv = (sign(x) * pow(abs(x), vec2f(0.3333333432674408))); - var t = clamp(((uv.x + uv.y) - kx), 0f, 1f); + let t = clamp(((uv.x + uv.y) - kx), 0f, 1f); res = dot2_7((d + ((c + (b * t)) * t))); } else { - var z = sqrt(-p); - var v = (acos((q / ((p * z) * 2f))) / 3f); - var m = cos(v); - var n = (sin(v) * 1.732050808f); - var t = saturate(((vec3f((m + m), (-n - m), (n - m)) * z) - kx)); + let z = sqrt(-(p)); + let v = (acos((q / ((p * z) * 2f))) / 3f); + let m = cos(v); + let n = (sin(v) * 1.732050808f); + var t = saturate(((vec3f((m + m), (-(n) - m), (n - m)) * z) - kx)); res = min(dot2_7((d + ((c + (b * t.x)) * t.x))), dot2_7((d + ((c + (b * t.y)) * t.y)))); } return sqrt(res); @@ -718,24 +718,24 @@ describe('jelly-slider example', () => { var minDist = 1e+10f; var closestSegment = 0i; var closestT = 0f; - var epsilon = 0.029999999329447746f; + const epsilon = 0.029999999329447746f; var xOffset = vec2f(epsilon, 0f); var yOffset2 = vec2f(0f, epsilon); var xPlusDist = 1e+10f; var xMinusDist = 1e+10f; var yPlusDist = 1e+10f; var yMinusDist = 1e+10f; - for (var i = 0; (i < (17 - 1)); i++) { - var A = pointsView_4[i]; - var B = pointsView_4[(i + 1i)]; - var C = controlPointsView_5[i]; - var dist = sdBezier_6(sliderPos, A, C, B); + for (var i = 0; (i < 16i); i++) { + let A = (&pointsView_4[i]); + let B = (&pointsView_4[(i + 1i)]); + let C = (&controlPointsView_5[i]); + let dist = sdBezier_6(sliderPos, (*A), (*C), (*B)); if ((dist < minDist)) { minDist = dist; closestSegment = i; - var AB = (B - A); - var AP = (sliderPos - A); - var ABLength = length(AB); + var AB = ((*B) - (*A)); + var AP = (sliderPos - (*A)); + let ABLength = length(AB); if ((ABLength > 0f)) { closestT = clamp((dot(AP, AB) / (ABLength * ABLength)), 0f, 1f); } @@ -743,14 +743,14 @@ describe('jelly-slider example', () => { closestT = 0f; } } - xPlusDist = min(xPlusDist, sdBezier_6((sliderPos + xOffset), A, C, B)); - xMinusDist = min(xMinusDist, sdBezier_6((sliderPos - xOffset), A, C, B)); - yPlusDist = min(yPlusDist, sdBezier_6((sliderPos + yOffset2), A, C, B)); - yMinusDist = min(yMinusDist, sdBezier_6((sliderPos - yOffset2), A, C, B)); + xPlusDist = min(xPlusDist, sdBezier_6((sliderPos + xOffset), (*A), (*C), (*B))); + xMinusDist = min(xMinusDist, sdBezier_6((sliderPos - xOffset), (*A), (*C), (*B))); + yPlusDist = min(yPlusDist, sdBezier_6((sliderPos + yOffset2), (*A), (*C), (*B))); + yMinusDist = min(yMinusDist, sdBezier_6((sliderPos - yOffset2), (*A), (*C), (*B))); } - var overallProgress = ((f32(closestSegment) + closestT) / f32((17 - 1))); - var normalX = ((xPlusDist - xMinusDist) / (2f * epsilon)); - var normalY = ((yPlusDist - yMinusDist) / (2f * epsilon)); + let overallProgress = ((f32(closestSegment) + closestT) / 16f); + let normalX = ((xPlusDist - xMinusDist) / (2f * epsilon)); + let normalY = ((yPlusDist - yMinusDist) / (2f * epsilon)); textureStore(bezierWriteView_3, vec2u(x, y), vec4f(minDist, overallProgress, normalX, normalY)); } diff --git a/packages/typegpu/tests/ref.test.ts b/packages/typegpu/tests/ref.test.ts index 4eccc77f4b..c0fbdd231f 100644 --- a/packages/typegpu/tests/ref.test.ts +++ b/packages/typegpu/tests/ref.test.ts @@ -184,4 +184,31 @@ describe('d.ref', () => { - fn*:bar(): Cannot return references, returning '0'] `); }); + + it('fails when taking a reference of an argument', () => { + const advance = (value: d.ref) => { + 'use gpu'; + value.$.x += 1; + }; + + const foo = (hello: d.v3f) => { + 'use gpu'; + // Trying to cheat and mutate a non-ref argument by taking a reference of it here. + advance(d.ref(hello)); + }; + + const main = () => { + 'use gpu'; + foo(d.vec3f()); + }; + + expect(() => asWgsl(main)).toThrowErrorMatchingInlineSnapshot(` + [Error: Resolution of the following tree failed: + - + - fn*:main + - fn*:main() + - fn*:foo(vec3f) + - fn:ref: d.ref(hello) is illegal, cannot take a reference of an argument. Copy the value locally first, and take a reference of the copy.] + `); + }); }); diff --git a/packages/typegpu/tests/tgsl/argumentOrigin.test.ts b/packages/typegpu/tests/tgsl/argumentOrigin.test.ts index fa964ae35d..25a0d930d4 100644 --- a/packages/typegpu/tests/tgsl/argumentOrigin.test.ts +++ b/packages/typegpu/tests/tgsl/argumentOrigin.test.ts @@ -91,7 +91,7 @@ describe('function argument origin tracking', () => { - - fn*:main - fn*:main() - - fn*:foo(vec3f): '(*b).x += 1f' is invalid, because non-pointer arguments cannot be mutated.] + - fn*:foo(vec3f): 'b.x += 1f' is invalid, because non-pointer arguments cannot be mutated.] `); }); @@ -113,7 +113,7 @@ describe('function argument origin tracking', () => { - - fn*:main - fn*:main() - - fn*:foo(vec3f): 'let b = a' is invalid, because references cannot be assigned to 'let' variable declarations. + - fn*:foo(vec3f): 'let b = a' is invalid, because references to arguments cannot be assigned to 'let' variable declarations. ----- - Try 'let b = vec3f(a)' if you need to reassign 'b' later - Try 'const b = a' if you won't reassign 'b' later. diff --git a/packages/typegpu/tests/tgslFn.test.ts b/packages/typegpu/tests/tgslFn.test.ts index bf2bc02667..f6649b3ea7 100644 --- a/packages/typegpu/tests/tgslFn.test.ts +++ b/packages/typegpu/tests/tgslFn.test.ts @@ -164,8 +164,8 @@ describe('TGSL tgpu.fn function', () => { @vertex fn vertex_fn(input: vertex_fn_Input) -> vertex_fn_Output { let vi = f32(input.vi); let ii = f32(input.ii); - let color = (&input.color); - return vertex_fn_Output(vec4f((*color).w, ii, vi, 1f), vec2f((*color).w, vi)); + let color = input.color; + return vertex_fn_Output(vec4f(color.w, ii, vi, 1f), vec2f(color.w, vi)); }" `); }); @@ -391,9 +391,9 @@ describe('TGSL tgpu.fn function', () => { } @fragment fn fragmentFn(input: fragmentFn_Input) -> fragmentFn_Output { - let pos = (&input.pos); + let pos = input.pos; var sampleMask = 0; - if (((input.sampleMask > 0u) && ((*pos).x > 0f))) { + if (((input.sampleMask > 0u) && (pos.x > 0f))) { sampleMask = 1i; } return fragmentFn_Output(u32(sampleMask), 1f, vec4f()); From 6003bc30dc04b6642a1ff3cf76678dabd2b60183 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 6 Nov 2025 23:54:27 +0100 Subject: [PATCH 55/59] Update pointers.ts --- .../src/examples/tests/tgsl-parsing-test/pointers.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts b/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts index 0a2e16d699..66c0f89a4d 100644 --- a/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts +++ b/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts @@ -49,13 +49,13 @@ export const pointersTest = tgpu.fn([], d.bool)(() => { s = s && std.allEq(myStruct.$.vec, d.vec2f(1, 0)); // private pointers - modifyNumPrivate(privateNum); + modifyNumPrivate(d.ref(privateNum.$)); s = s && (privateNum.$ === 1); - modifyVecPrivate(privateVec); + modifyVecPrivate(d.ref(privateVec.$)); s = s && std.allEq(privateVec.$, d.vec2f(1, 0)); - modifyStructPrivate(privateStruct); + modifyStructPrivate(d.ref(privateStruct.$)); s = s && std.allEq(privateStruct.$.vec, d.vec2f(1, 0)); return s; From 74d129117bed3bd82434e3abbf26be35481678eb Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Fri, 7 Nov 2025 11:27:37 +0100 Subject: [PATCH 56/59] Fix for referencing implicit pointers --- .../examples/simulation/gravity/compute.ts | 6 +-- .../examples/simulation/gravity/helpers.ts | 4 +- .../src/examples/simulation/gravity/render.ts | 2 +- .../tests/tgsl-parsing-test/pointers.ts | 8 --- packages/typegpu/src/data/ptr.ts | 18 +++++++ packages/typegpu/src/data/ref.ts | 8 ++- packages/typegpu/src/tgsl/wgslGenerator.ts | 31 ++++++------ .../tests/examples/individual/gravity.test.ts | 50 ++++++++----------- .../individual/tgsl-parsing-test.test.ts | 32 +++++------- packages/typegpu/tests/ref.test.ts | 36 ++++++++++++- 10 files changed, 115 insertions(+), 80 deletions(-) diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts index 0ab8534b9c..eb79d0fa32 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/compute.ts @@ -43,7 +43,7 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ current.collisionBehavior === none || // ...when current behavior is none other.collisionBehavior === none || // ...when other behavior is none std.distance(current.position, other.position) >= - radiusOf(d.ref(current)) + radiusOf(d.ref(other)) // ...when other is too far away + radiusOf(current) + radiusOf(other) // ...when other is too far away ) { // no collision occurs... continue; @@ -59,7 +59,7 @@ export const computeCollisionsShader = tgpu['~unstable'].computeFn({ if (isSmaller(currentId, otherId)) { const dir = std.normalize(current.position.sub(other.position)); current.position = other.position.add( - dir.mul(radiusOf(d.ref(current)) + radiusOf(d.ref(other))), + dir.mul(radiusOf(current) + radiusOf(other)), ); } @@ -118,7 +118,7 @@ export const computeGravityShader = tgpu['~unstable'].computeFn({ } const dist = std.max( - radiusOf(d.ref(current)) + radiusOf(d.ref(other)), + radiusOf(current) + radiusOf(other), std.distance(current.position, other.position), ); const gravityForce = (current.mass * other.mass) / dist / dist; diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts b/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts index 24048eea50..58171f9b75 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/helpers.ts @@ -166,7 +166,7 @@ export async function loadSphereTextures(root: TgpuRoot) { return texture; } -export const radiusOf = (body: d.ref): number => { +export const radiusOf = (body: CelestialBody): number => { 'use gpu'; - return (((body.$.mass * 0.75) / Math.PI) ** 0.333) * body.$.radiusMultiplier; + return (((body.mass * 0.75) / Math.PI) ** 0.333) * body.radiusMultiplier; }; diff --git a/apps/typegpu-docs/src/examples/simulation/gravity/render.ts b/apps/typegpu-docs/src/examples/simulation/gravity/render.ts index 5a7de3e2b5..b88d7e1238 100644 --- a/apps/typegpu-docs/src/examples/simulation/gravity/render.ts +++ b/apps/typegpu-docs/src/examples/simulation/gravity/render.ts @@ -56,7 +56,7 @@ export const mainVertex = tgpu['~unstable'].vertexFn({ const currentBody = renderLayout.$.celestialBodies[input.instanceIndex]; const worldPosition = currentBody.position.add( - input.position.xyz.mul(radiusOf(d.ref(currentBody))), + input.position.xyz.mul(radiusOf(currentBody)), ); const camera = cameraAccess.$; diff --git a/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts b/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts index 66c0f89a4d..9c6b1fdbc0 100644 --- a/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts +++ b/apps/typegpu-docs/src/examples/tests/tgsl-parsing-test/pointers.ts @@ -16,11 +16,6 @@ const modifyStructFn = tgpu.fn([d.ptrFn(SimpleStruct)])((ptr) => { ptr.$.vec.x += 1; }); -const privateNum = tgpu.privateVar(d.u32); -const modifyNumPrivate = tgpu.fn([d.ptrPrivate(d.u32)])((ptr) => { - ptr.$ += 1; -}); - const privateVec = tgpu.privateVar(d.vec2f); const modifyVecPrivate = tgpu.fn([d.ptrPrivate(d.vec2f)])((ptr) => { ptr.$.x += 1; @@ -49,9 +44,6 @@ export const pointersTest = tgpu.fn([], d.bool)(() => { s = s && std.allEq(myStruct.$.vec, d.vec2f(1, 0)); // private pointers - modifyNumPrivate(d.ref(privateNum.$)); - s = s && (privateNum.$ === 1); - modifyVecPrivate(d.ref(privateVec.$)); s = s && std.allEq(privateVec.$, d.vec2f(1, 0)); diff --git a/packages/typegpu/src/data/ptr.ts b/packages/typegpu/src/data/ptr.ts index 35c45291c2..3db83c5d53 100644 --- a/packages/typegpu/src/data/ptr.ts +++ b/packages/typegpu/src/data/ptr.ts @@ -80,3 +80,21 @@ export function createPtrFromOrigin( return undefined; } + +export function implicitFrom(ptr: Ptr): Ptr { + return INTERNAL_createPtr( + ptr.addressSpace, + ptr.inner, + ptr.access, + /* implicit */ true, + ); +} + +export function explicitFrom(ptr: Ptr): Ptr { + return INTERNAL_createPtr( + ptr.addressSpace, + ptr.inner, + ptr.access, + /* implicit */ false, + ); +} diff --git a/packages/typegpu/src/data/ref.ts b/packages/typegpu/src/data/ref.ts index 5c5dfb9f7a..7bc7ca1f3a 100644 --- a/packages/typegpu/src/data/ref.ts +++ b/packages/typegpu/src/data/ref.ts @@ -6,7 +6,7 @@ import { $internal, $ownSnippet, $resolve } from '../shared/symbols.ts'; import type { ResolutionCtx, SelfResolvable } from '../types.ts'; import { UnknownData } from './dataTypes.ts'; import type { DualFn } from './dualFn.ts'; -import { createPtrFromOrigin } from './ptr.ts'; +import { createPtrFromOrigin, explicitFrom } from './ptr.ts'; import { type ResolvedSnippet, snip, type Snippet } from './snippet.ts'; import { isNaturallyEphemeral, @@ -52,6 +52,12 @@ export const ref: DualFn<(value: T) => ref> = (() => { ); } + if (value.dataType.type === 'ptr') { + // This can happen if we take a reference of an *implicit* pointer, one + // made by assigning a reference to a `const`. + return snip(value.value, explicitFrom(value.dataType), value.origin); + } + /** * Pointer type only exists if the ref was created from a reference (buttery-butter). * diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 4e5ef72472..097bd5a9d0 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -41,7 +41,7 @@ import { } from './generationHelpers.ts'; import type { ShaderGenerator } from './shaderGenerator.ts'; import type { DualFn } from '../data/dualFn.ts'; -import { INTERNAL_createPtr, ptrFn } from '../data/ptr.ts'; +import { createPtrFromOrigin, implicitFrom, ptrFn } from '../data/ptr.ts'; import { RefOperator } from '../data/ref.ts'; import { constant } from '../core/constant/tgpuConstant.ts'; @@ -869,18 +869,21 @@ ${this.ctx.pre}else ${alternate}`; } else { varType = 'let'; if (!wgsl.isPtr(dataType)) { - dataType = ptrFn(concretize(dataType) as wgsl.StorableData); + const ptrType = createPtrFromOrigin( + eq.origin, + concretize(dataType) as wgsl.StorableData, + ); + invariant( + ptrType !== undefined, + `Creating pointer type from origin ${eq.origin}`, + ); + dataType = ptrType; } if (!(eq.value instanceof RefOperator)) { // If what we're assigning is something preceded by `&`, then it's a value // created using `d.ref()`. Otherwise, it's an implicit pointer - dataType = INTERNAL_createPtr( - dataType.addressSpace, - dataType.inner, - dataType.access, - /* implicit */ true, - ); + dataType = implicitFrom(dataType); } } } else { @@ -932,13 +935,11 @@ ${this.ctx.pre}else ${alternate}`; const [_, init, condition, update, body] = statement; const [initStatement, conditionExpr, updateStatement] = this.ctx - .withResetIndentLevel( - () => [ - init ? this.statement(init) : undefined, - condition ? this.typedExpression(condition, bool) : undefined, - update ? this.statement(update) : undefined, - ], - ); + .withResetIndentLevel(() => [ + init ? this.statement(init) : undefined, + condition ? this.typedExpression(condition, bool) : undefined, + update ? this.statement(update) : undefined, + ]); const initStr = initStatement ? initStatement.slice(0, -1) : ''; const updateStr = updateStatement ? updateStatement.slice(0, -1) : ''; diff --git a/packages/typegpu/tests/examples/individual/gravity.test.ts b/packages/typegpu/tests/examples/individual/gravity.test.ts index f74648f5a2..d6b46c02dc 100644 --- a/packages/typegpu/tests/examples/individual/gravity.test.ts +++ b/packages/typegpu/tests/examples/individual/gravity.test.ts @@ -42,15 +42,11 @@ describe('gravity example', () => { @group(0) @binding(0) var celestialBodiesCount_3: i32; - fn radiusOf_4(body: ptr) -> f32 { - return (pow((((*body).mass * 0.75f) / 3.141592653589793f), 0.333f) * (*body).radiusMultiplier); + fn radiusOf_4(body: CelestialBody_2) -> f32 { + return (pow(((body.mass * 0.75f) / 3.141592653589793f), 0.333f) * body.radiusMultiplier); } - fn radiusOf_5(body: ptr, read>) -> f32 { - return (pow((((*(*body)).mass * 0.75f) / 3.141592653589793f), 0.333f) * (*(*body)).radiusMultiplier); - } - - fn isSmaller_6(currentId: u32, otherId: u32) -> bool { + fn isSmaller_5(currentId: u32, otherId: u32) -> bool { let current = (&inState_1[currentId]); let other = (&inState_1[otherId]); if (((*current).mass < (*other).mass)) { @@ -62,25 +58,25 @@ describe('gravity example', () => { return false; } - @group(0) @binding(2) var outState_7: array; + @group(0) @binding(2) var outState_6: array; - struct computeCollisionsShader_Input_8 { + struct computeCollisionsShader_Input_7 { @builtin(global_invocation_id) gid: vec3u, } - @compute @workgroup_size(1) fn computeCollisionsShader_0(input: computeCollisionsShader_Input_8) { + @compute @workgroup_size(1) fn computeCollisionsShader_0(input: computeCollisionsShader_Input_7) { let currentId = input.gid.x; var current = inState_1[currentId]; if ((current.destroyed == 0u)) { for (var otherId = 0u; (otherId < u32(celestialBodiesCount_3)); otherId++) { let other = (&inState_1[otherId]); - if ((((((otherId == currentId) || ((*other).destroyed == 1u)) || (current.collisionBehavior == 0u)) || ((*other).collisionBehavior == 0u)) || (distance(current.position, (*other).position) >= (radiusOf_4((¤t)) + radiusOf_5((&other)))))) { + if ((((((otherId == currentId) || ((*other).destroyed == 1u)) || (current.collisionBehavior == 0u)) || ((*other).collisionBehavior == 0u)) || (distance(current.position, (*other).position) >= (radiusOf_4(current) + radiusOf_4((*other)))))) { continue; } if (((current.collisionBehavior == 1u) && ((*other).collisionBehavior == 1u))) { - if (isSmaller_6(currentId, otherId)) { + if (isSmaller_5(currentId, otherId)) { var dir = normalize((current.position - (*other).position)); - current.position = ((*other).position + (dir * (radiusOf_4((¤t)) + radiusOf_5((&other))))); + current.position = ((*other).position + (dir * (radiusOf_4(current) + radiusOf_4((*other))))); } var posDiff = (current.position - (*other).position); var velDiff = (current.velocity - (*other).velocity); @@ -88,7 +84,7 @@ describe('gravity example', () => { current.velocity = ((current.velocity - (posDiff * posDiffFactor)) * 0.99); } else { - let isCurrentAbsorbed = ((current.collisionBehavior == 1u) || ((current.collisionBehavior == 2u) && isSmaller_6(currentId, otherId))); + let isCurrentAbsorbed = ((current.collisionBehavior == 1u) || ((current.collisionBehavior == 2u) && isSmaller_5(currentId, otherId))); if (isCurrentAbsorbed) { current.destroyed = 1u; } @@ -101,7 +97,7 @@ describe('gravity example', () => { } } } - outState_7[currentId] = current; + outState_6[currentId] = current; } struct Time_2 { @@ -126,21 +122,17 @@ describe('gravity example', () => { @group(1) @binding(0) var celestialBodiesCount_5: i32; - fn radiusOf_6(body: ptr) -> f32 { - return (pow((((*body).mass * 0.75f) / 3.141592653589793f), 0.333f) * (*body).radiusMultiplier); + fn radiusOf_6(body: CelestialBody_4) -> f32 { + return (pow(((body.mass * 0.75f) / 3.141592653589793f), 0.333f) * body.radiusMultiplier); } - fn radiusOf_7(body: ptr, read>) -> f32 { - return (pow((((*(*body)).mass * 0.75f) / 3.141592653589793f), 0.333f) * (*(*body)).radiusMultiplier); - } - - @group(1) @binding(2) var outState_8: array; + @group(1) @binding(2) var outState_7: array; - struct computeGravityShader_Input_9 { + struct computeGravityShader_Input_8 { @builtin(global_invocation_id) gid: vec3u, } - @compute @workgroup_size(1) fn computeGravityShader_0(input: computeGravityShader_Input_9) { + @compute @workgroup_size(1) fn computeGravityShader_0(input: computeGravityShader_Input_8) { let dt = (time_1.passed * time_1.multiplier); let currentId = input.gid.x; var current = inState_3[currentId]; @@ -150,14 +142,14 @@ describe('gravity example', () => { if (((otherId == currentId) || ((*other).destroyed == 1u))) { continue; } - let dist = max((radiusOf_6((¤t)) + radiusOf_7((&other))), distance(current.position, (*other).position)); + let dist = max((radiusOf_6(current) + radiusOf_6((*other))), distance(current.position, (*other).position)); let gravityForce = (((current.mass * (*other).mass) / dist) / dist); var direction = normalize(((*other).position - current.position)); current.velocity = (current.velocity + (direction * ((gravityForce / current.mass) * dt))); } current.position = (current.position + (current.velocity * dt)); } - outState_8[currentId] = current; + outState_7[currentId] = current; } struct Camera_2 { @@ -209,8 +201,8 @@ describe('gravity example', () => { @group(1) @binding(1) var celestialBodies_1: array; - fn radiusOf_3(body: ptr, read>) -> f32 { - return (pow((((*(*body)).mass * 0.75f) / 3.141592653589793f), 0.333f) * (*(*body)).radiusMultiplier); + fn radiusOf_3(body: CelestialBody_2) -> f32 { + return (pow(((body.mass * 0.75f) / 3.141592653589793f), 0.333f) * body.radiusMultiplier); } struct Camera_5 { @@ -241,7 +233,7 @@ describe('gravity example', () => { @vertex fn mainVertex_0(input: mainVertex_Input_7) -> mainVertex_Output_6 { let currentBody = (&celestialBodies_1[input.instanceIndex]); - var worldPosition = ((*currentBody).position + (input.position.xyz * radiusOf_3((¤tBody)))); + var worldPosition = ((*currentBody).position + (input.position.xyz * radiusOf_3((*currentBody)))); let camera = (&camera_4); var positionOnCanvas = (((*camera).projection * (*camera).view) * vec4f(worldPosition, 1f)); return mainVertex_Output_6(positionOnCanvas, input.uv, input.normal, worldPosition, (*currentBody).textureIndex, (*currentBody).destroyed, (*currentBody).ambientLightFactor); diff --git a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts index 4ae492f2d5..fffbda7cb4 100644 --- a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts +++ b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts @@ -178,24 +178,18 @@ describe('tgsl parsing test example', () => { (*ptr).vec.x += 1f; } - var privateNum_16: u32; - - fn modifyNumPrivate_17(ptr: ptr) { - (*ptr) += 1u; - } - - var privateVec_18: vec2f; - - fn modifyVecPrivate_19(ptr: ptr) { + fn modifyVecPrivate_16(ptr: ptr) { (*ptr).x += 1f; } - var privateStruct_20: SimpleStruct_14; + var privateVec_17: vec2f; - fn modifyStructPrivate_21(ptr: ptr) { + fn modifyStructPrivate_18(ptr: ptr) { (*ptr).vec.x += 1f; } + var privateStruct_19: SimpleStruct_14; + fn pointersTest_11() -> bool { var s = true; var num = 0u; @@ -207,16 +201,14 @@ describe('tgsl parsing test example', () => { var myStruct = SimpleStruct_14(); modifyStructFn_15((&myStruct)); s = (s && all(myStruct.vec == vec2f(1, 0))); - modifyNumPrivate_17(privateNum_16); - s = (s && (privateNum_16 == 1u)); - modifyVecPrivate_19(privateVec_18); - s = (s && all(privateVec_18 == vec2f(1, 0))); - modifyStructPrivate_21(privateStruct_20); - s = (s && all(privateStruct_20.vec == vec2f(1, 0))); + modifyVecPrivate_16((&privateVec_17)); + s = (s && all(privateVec_17 == vec2f(1, 0))); + modifyStructPrivate_18((&privateStruct_19)); + s = (s && all(privateStruct_19.vec == vec2f(1, 0))); return s; } - @group(0) @binding(0) var result_22: i32; + @group(0) @binding(0) var result_20: i32; @compute @workgroup_size(1) fn computeRunTests_0() { var s = true; @@ -226,10 +218,10 @@ describe('tgsl parsing test example', () => { s = (s && arrayAndStructConstructorsTest_8()); s = (s && pointersTest_11()); if (s) { - result_22 = 1i; + result_20 = 1i; } else { - result_22 = 0i; + result_20 = 0i; } }" `); diff --git a/packages/typegpu/tests/ref.test.ts b/packages/typegpu/tests/ref.test.ts index c0fbdd231f..f0b42be186 100644 --- a/packages/typegpu/tests/ref.test.ts +++ b/packages/typegpu/tests/ref.test.ts @@ -1,4 +1,5 @@ -import * as d from 'typegpu/data'; +import tgpu from '../src/index.ts'; +import * as d from '../src/data/index.ts'; import { describe, expect } from 'vitest'; import { it } from './utils/extendedIt.ts'; import { asWgsl } from './utils/parseResolved.ts'; @@ -211,4 +212,37 @@ describe('d.ref', () => { - fn:ref: d.ref(hello) is illegal, cannot take a reference of an argument. Copy the value locally first, and take a reference of the copy.] `); }); + + it('turns an implicit pointer into an explicit one', () => { + const layout = tgpu.bindGroupLayout({ + positions: { storage: d.arrayOf(d.vec3f) }, + }); + + const advance = (value: d.ref) => { + 'use gpu'; + value.$.x += 1; + }; + + const main = () => { + 'use gpu'; + // biome-ignore lint/style/noNonNullAssertion: it's there + const pos = layout.$.positions[0]!; + advance(d.ref(pos)); + d.ref(pos); + }; + + expect(asWgsl(main)).toMatchInlineSnapshot(` + "@group(0) @binding(0) var positions: array; + + fn advance(value: ptr) { + (*value).x += 1f; + } + + fn main() { + let pos = (&positions[0i]); + advance(pos); + pos; + }" + `); + }); }); From 5466542d153334bfb1fbc11e5ab73f1bbf0098d4 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 6 Nov 2025 13:45:41 +0100 Subject: [PATCH 57/59] feat: Inspect vector type in shader function --- packages/tinyest-for-wgsl/src/parsers.ts | 91 ++++--------------- packages/tinyest/src/nodes.ts | 7 +- .../typegpu/src/tgsl/generationHelpers.ts | 6 ++ packages/typegpu/src/tgsl/wgslGenerator.ts | 71 +++++++++++++-- packages/typegpu/src/types.ts | 3 +- .../individual/tgsl-parsing-test.test.ts | 8 +- packages/typegpu/tests/vector.test.ts | 39 ++++++++ 7 files changed, 140 insertions(+), 85 deletions(-) diff --git a/packages/tinyest-for-wgsl/src/parsers.ts b/packages/tinyest-for-wgsl/src/parsers.ts index 644ad99a50..ff619b204f 100644 --- a/packages/tinyest-for-wgsl/src/parsers.ts +++ b/packages/tinyest-for-wgsl/src/parsers.ts @@ -24,73 +24,6 @@ function isDeclared(ctx: Context, name: string) { return ctx.stack.some((scope) => scope.declaredNames.includes(name)); } -const BINARY_OP_MAP = { - '==': '==', - '!=': '!=', - '===': '==', - '!==': '!=', - '<': '<', - '<=': '<=', - '>': '>', - '>=': '>=', - '<<': '<<', - '>>': '>>', - get '>>>'(): never { - throw new Error('The `>>>` operator is unsupported in TGSL.'); - }, - '+': '+', - '-': '-', - '*': '*', - '/': '/', - '%': '%', - '|': '|', - '^': '^', - '&': '&', - get in(): never { - throw new Error('The `in` operator is unsupported in TGSL.'); - }, - get instanceof(): never { - throw new Error('The `instanceof` operator is unsupported in TGSL.'); - }, - '**': '**', - get '|>'(): never { - throw new Error('The `|>` operator is unsupported in TGSL.'); - }, -} as const; - -const LOGICAL_OP_MAP = { - '||': '||', - '&&': '&&', - get '??'(): never { - throw new Error('The `??` operator is unsupported in TGSL.'); - }, -} as const; - -const ASSIGNMENT_OP_MAP = { - '=': '=', - '+=': '+=', - '-=': '-=', - '*=': '*=', - '/=': '/=', - '%=': '%=', - '<<=': '<<=', - '>>=': '>>=', - get '>>>='(): never { - throw new Error('The `>>>=` operator is unsupported in TGSL.'); - }, - '|=': '|=', - '^=': '^=', - '&=': '&=', - get '**='(): never { - throw new Error('The `**=` operator is unsupported in TGSL.'); - }, - '||=': '||=', - '&&=': '&&=', - get '??='(): never { - throw new Error('The `??=` operator is unsupported in TGSL.'); - }, -} as const; - const Transpilers: Partial< { [Type in JsNode['type']]: ( @@ -144,24 +77,36 @@ const Transpilers: Partial< }, BinaryExpression(ctx, node) { - const wgslOp = BINARY_OP_MAP[node.operator]; const left = transpile(ctx, node.left) as tinyest.Expression; const right = transpile(ctx, node.right) as tinyest.Expression; - return [NODE.binaryExpr, left, wgslOp, right]; + return [ + NODE.binaryExpr, + left, + node.operator as tinyest.BinaryOperator, + right, + ]; }, LogicalExpression(ctx, node) { - const wgslOp = LOGICAL_OP_MAP[node.operator]; const left = transpile(ctx, node.left) as tinyest.Expression; const right = transpile(ctx, node.right) as tinyest.Expression; - return [NODE.logicalExpr, left, wgslOp, right]; + return [ + NODE.logicalExpr, + left, + node.operator as tinyest.LogicalOperator, + right, + ]; }, AssignmentExpression(ctx, node) { - const wgslOp = ASSIGNMENT_OP_MAP[node.operator as acorn.AssignmentOperator]; const left = transpile(ctx, node.left) as tinyest.Expression; const right = transpile(ctx, node.right) as tinyest.Expression; - return [NODE.assignmentExpr, left, wgslOp, right]; + return [ + NODE.assignmentExpr, + left, + node.operator as tinyest.AssignmentOperator, + right, + ]; }, UnaryExpression(ctx, node) { diff --git a/packages/tinyest/src/nodes.ts b/packages/tinyest/src/nodes.ts index 6f073ff321..a4e6c20c1d 100644 --- a/packages/tinyest/src/nodes.ts +++ b/packages/tinyest/src/nodes.ts @@ -118,12 +118,15 @@ export type Statement = export type BinaryOperator = | '==' | '!=' + | '===' + | '!==' | '<' | '<=' | '>' | '>=' | '<<' | '>>' + | '>>>' | '+' | '-' | '*' @@ -132,6 +135,8 @@ export type BinaryOperator = | '|' | '^' | '&' + | 'in' + | 'instanceof' | '**'; export type BinaryExpression = readonly [ @@ -164,7 +169,7 @@ export type AssignmentExpression = readonly [ rhs: Expression, ]; -export type LogicalOperator = '&&' | '||'; +export type LogicalOperator = '&&' | '||' | '??'; export type LogicalExpression = readonly [ type: NodeTypeCatalog['logicalExpr'], diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 6f1f1cc2ec..8ce6fa9665 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -229,6 +229,12 @@ export function accessProp( return accessProp(derefed, propName); } + if (isVec(target.dataType)) { + if (propName === 'kind') { + return snip(target.dataType.type, UnknownData, 'constant'); + } + } + const propLength = propName.length; if ( isVec(target.dataType) && diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index 097bd5a9d0..b2c7379102 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -26,7 +26,7 @@ import { safeStringify } from '../shared/stringify.ts'; import { $internal } from '../shared/symbols.ts'; import { pow } from '../std/numeric.ts'; import { add, div, mul, neg, sub } from '../std/operators.ts'; -import type { FnArgsConversionHint } from '../types.ts'; +import { type FnArgsConversionHint, isKnownAtComptime } from '../types.ts'; import { convertStructValues, convertToCommonType, @@ -50,6 +50,8 @@ const { NodeTypeCatalog: NODE } = tinyest; const parenthesizedOps = [ '==', '!=', + '===', + '!==', '<', '<=', '>', @@ -68,7 +70,48 @@ const parenthesizedOps = [ '||', ]; -const binaryLogicalOps = ['&&', '||', '==', '!=', '<', '<=', '>', '>=']; +const binaryLogicalOps = [ + '&&', + '||', + '==', + '!=', + '===', + '!==', + '<', + '<=', + '>', + '>=', +]; + +const OP_MAP = { + // + // binary + // + '===': '==', + '!==': '!=', + get '>>>'(): never { + throw new Error('The `>>>` operator is unsupported in TypeGPU functions.'); + }, + get in(): never { + throw new Error('The `in` operator is unsupported in TypeGPU functions.'); + }, + get instanceof(): never { + throw new Error( + 'The `instanceof` operator is unsupported in TypeGPU functions.', + ); + }, + get '|>'(): never { + throw new Error('The `|>` operator is unsupported in TypeGPU functions.'); + }, + // + // logical + // + '||': '||', + '&&': '&&', + get '??'(): never { + throw new Error('The `??` operator is unsupported in TypeGPU functions.'); + }, +} as Record; type Operator = | tinyest.BinaryOperator @@ -250,8 +293,24 @@ ${this.ctx.pre}}`; ); } + if (op === '==') { + throw new Error('Please use the === operator instead of =='); + } + + if ( + op === '===' && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr) + ) { + return snip( + lhsExpr.value === rhsExpr.value, + bool, + /* ref */ 'constant', + ); + } + if (lhsExpr.dataType.type === 'unknown') { - throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type`); + throw new WgslTypeError( + `Left-hand side of '${op}' is of unknown type`, + ); } if (rhsExpr.dataType.type === 'unknown') { @@ -315,8 +374,8 @@ ${this.ctx.pre}}`; return snip( parenthesizedOps.includes(op) - ? `(${lhsStr} ${op} ${rhsStr})` - : `${lhsStr} ${op} ${rhsStr}`, + ? `(${lhsStr} ${OP_MAP[op] ?? op} ${rhsStr})` + : `${lhsStr} ${OP_MAP[op] ?? op} ${rhsStr}`, type, // Result of an operation, so not a reference to anything /* ref */ 'runtime', @@ -670,7 +729,7 @@ ${this.ctx.pre}}`; } if (expression[0] === NODE.stringLiteral) { - return snip(expression[1], UnknownData, /* ref */ 'runtime'); // arbitrary ref + return snip(expression[1], UnknownData, /* origin */ 'constant'); } if (expression[0] === NODE.preUpdate) { diff --git a/packages/typegpu/src/types.ts b/packages/typegpu/src/types.ts index 31f310e694..01d07a1a16 100644 --- a/packages/typegpu/src/types.ts +++ b/packages/typegpu/src/types.ts @@ -318,7 +318,8 @@ export function getOwnSnippet(value: unknown): Snippet | undefined { } export function isKnownAtComptime(snippet: Snippet): boolean { - return typeof snippet.value !== 'string' && + return (typeof snippet.value !== 'string' || + snippet.dataType.type === 'unknown') && getOwnSnippet(snippet.value) === undefined; } diff --git a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts index fffbda7cb4..b2c3b71c35 100644 --- a/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts +++ b/packages/typegpu/tests/examples/individual/tgsl-parsing-test.test.ts @@ -36,10 +36,10 @@ describe('tgsl parsing test example', () => { fn logicalExpressionTests_1() -> bool { var s = true; - s = (s && (true == true)); - s = (s && (true == true)); - s = (s && (true == true)); - s = (s && (false == false)); + s = (s && true); + s = (s && true); + s = (s && true); + s = (s && true); s = (s && true); s = (s && !false); s = (s && true); diff --git a/packages/typegpu/tests/vector.test.ts b/packages/typegpu/tests/vector.test.ts index 36ecd96d9c..d5dacb338d 100644 --- a/packages/typegpu/tests/vector.test.ts +++ b/packages/typegpu/tests/vector.test.ts @@ -4,6 +4,7 @@ import { readData, writeData } from '../src/data/dataIO.ts'; import * as d from '../src/data/index.ts'; import { sizeOf } from '../src/data/sizeOf.ts'; import tgpu from '../src/index.ts'; +import * as std from '../src/std/index.ts'; import { asWgsl } from './utils/parseResolved.ts'; describe('constructors', () => { @@ -972,3 +973,41 @@ describe('v4b', () => { }); }); }); + +describe('type predicates', () => { + it('prunes branches', () => { + const ceil = (input: d.v3f | d.v3i): d.v3i => { + 'use gpu'; + if (input.kind === 'vec3f') { + return d.vec3i(std.ceil(input)); + } else { + return input; + } + }; + + const main = () => { + 'use gpu'; + const foo = ceil(d.vec3f(1, 2, 3)); + const bar = ceil(d.vec3i(1, 2, 3)); + }; + + expect(asWgsl(main)).toMatchInlineSnapshot(` + "fn ceil(input: vec3f) -> vec3i { + { + return vec3i(ceil(input)); + } + } + + fn ceil_1(input: vec3i) -> vec3i { + { + return input; + } + } + + fn main() { + var foo = ceil(vec3f(1, 2, 3)); + var bar = ceil_1(vec3i(1, 2, 3)); + }" + `); + }); +}); From 305f49e54cf546e5c661aab7ed686416008a2580 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 6 Nov 2025 19:25:26 +0100 Subject: [PATCH 58/59] Better --- packages/tinyest/src/nodes.ts | 4 +++- packages/typegpu/src/tgsl/generationHelpers.ts | 1 + packages/typegpu/src/tgsl/wgslGenerator.ts | 12 ++++++++++++ 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/packages/tinyest/src/nodes.ts b/packages/tinyest/src/nodes.ts index a4e6c20c1d..40bb78efab 100644 --- a/packages/tinyest/src/nodes.ts +++ b/packages/tinyest/src/nodes.ts @@ -160,7 +160,9 @@ export type AssignmentOperator = | '&=' | '**=' | '||=' - | '&&='; + | '&&=' + | '>>>=' + | '??='; export type AssignmentExpression = readonly [ type: NodeTypeCatalog['assignmentExpr'], diff --git a/packages/typegpu/src/tgsl/generationHelpers.ts b/packages/typegpu/src/tgsl/generationHelpers.ts index 8ce6fa9665..7726438648 100644 --- a/packages/typegpu/src/tgsl/generationHelpers.ts +++ b/packages/typegpu/src/tgsl/generationHelpers.ts @@ -230,6 +230,7 @@ export function accessProp( } if (isVec(target.dataType)) { + // Example: d.vec3f().kind === 'vec3f' if (propName === 'kind') { return snip(target.dataType.type, UnknownData, 'constant'); } diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index b2c7379102..e04ab37540 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -111,6 +111,18 @@ const OP_MAP = { get '??'(): never { throw new Error('The `??` operator is unsupported in TypeGPU functions.'); }, + // + // assignment + // + get '>>>='(): never { + throw new Error('The `>>>=` operator is unsupported in TypeGPU functions.'); + }, + get '**='(): never { + throw new Error('The `**=` operator is unsupported in TypeGPU functions.'); + }, + get '??='(): never { + throw new Error('The `??=` operator is unsupported in TypeGPU functions.'); + }, } as Record; type Operator = From 44c351bcf4501e0d4a903a21a06cd9a5843297b3 Mon Sep 17 00:00:00 2001 From: Iwo Plaza Date: Thu, 6 Nov 2025 19:28:47 +0100 Subject: [PATCH 59/59] Tweaks --- packages/typegpu/src/tgsl/wgslGenerator.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/typegpu/src/tgsl/wgslGenerator.ts b/packages/typegpu/src/tgsl/wgslGenerator.ts index e04ab37540..637d96df78 100644 --- a/packages/typegpu/src/tgsl/wgslGenerator.ts +++ b/packages/typegpu/src/tgsl/wgslGenerator.ts @@ -312,17 +312,11 @@ ${this.ctx.pre}}`; if ( op === '===' && isKnownAtComptime(lhsExpr) && isKnownAtComptime(rhsExpr) ) { - return snip( - lhsExpr.value === rhsExpr.value, - bool, - /* ref */ 'constant', - ); + return snip(lhsExpr.value === rhsExpr.value, bool, 'constant'); } if (lhsExpr.dataType.type === 'unknown') { - throw new WgslTypeError( - `Left-hand side of '${op}' is of unknown type`, - ); + throw new WgslTypeError(`Left-hand side of '${op}' is of unknown type`); } if (rhsExpr.dataType.type === 'unknown') {