Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions apps/typegpu-docs/src/content/docs/apis/data-schemas.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,31 @@ Schemas can also be called to create instances of the types they represent.

TypeGPU provides data schemas for scalar, vector and matrix types, as well as constructors for struct and array schemas.

:::danger
WGSL has no `undefined` or `null`.

If you are tempted to write the following (or any code that relies on `undefined` in JS):
```ts twoslash
// @noUncheckedIndexedAccess: true
import { tgpu, d } from 'typegpu';

const arr = tgpu.const(d.arrayOf(d.u32, 7), [0, 1, 2, 3, 4, 5, 6]);
// ---cut---
const length = () => {
'use gpu';
let i = 0;
let e = arr.$[i];
// ^?
while (!!e) {
i++;
e = arr.$[i];
}
return i;
}
```
Be aware that this is undefined behavior in WGSL.
:::

## Scalars, vectors and matrices

### Constructors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ describe('tgsl parsing test example', () => {
}

fn negateStruct(input: Schema) -> Schema {
let result = Schema(!(input.vec2b), !(input.vec4b), !(input.vec3b), !input.bool);
let result = Schema(!input.vec2b, !input.vec4b, !input.vec3b, !input.bool);
return result;
}

Expand Down Expand Up @@ -64,12 +64,12 @@ describe('tgsl parsing test example', () => {
s = (s && true);
s = (s && true);
let vec = vec3<bool>(true, false, true);
s = (s && all(!(vec) == negate(vec)));
s = (s && all(!vec == negate(vec)));
let inputStruct = Schema(vec2<bool>(false, true), vec4<bool>(false, true, false, true), vec3<bool>(true, true, false), true);
let resultStruct = negateStruct(inputStruct);
s = (s && all(!(inputStruct.vec2b) == resultStruct.vec2b));
s = (s && all(!(inputStruct.vec4b) == resultStruct.vec4b));
s = (s && all(!(inputStruct.vec3b) == resultStruct.vec3b));
s = (s && all(!inputStruct.vec2b == resultStruct.vec2b));
s = (s && all(!inputStruct.vec4b == resultStruct.vec4b));
s = (s && all(!inputStruct.vec3b == resultStruct.vec3b));
s = (s && (!inputStruct.bool == resultStruct.bool));
return s;
}
Expand Down
50 changes: 42 additions & 8 deletions packages/typegpu/src/data/numeric.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,56 @@
import { $internal } from '../shared/symbols.ts';
import type { AbstractFloat, AbstractInt, Bool, F16, F32, I32, U16, U32 } from './wgslTypes.ts';
import {
isBool,
type AbstractFloat,
type AbstractInt,
type Bool,
type F16,
type F32,
type I32,
type U16,
type U32,
} from './wgslTypes.ts';
import { callableSchema } from '../core/function/createCallableSchema.ts';
import { FiniteMathAssumptionError } from '../errors.ts';
import { FiniteMathAssumptionError, SignatureNotSupportedError, WgslTypeError } from '../errors.ts';
import { isSnippetNumeric } from './snippet.ts';
import { UnknownData } from './dataTypes.ts';

const boolCast = callableSchema({
name: 'bool',
schema: () => bool,
argTypes: (arg) => (arg ? [arg] : []),
normalImpl(v?: number | boolean) {
if (v === undefined) {
return false;
}
normalImpl(v: number | boolean = false) {
if (typeof v === 'boolean') {
return v;
}
return !!v;

if (typeof v === 'number') {
if (!Number.isFinite(v)) {
throw new FiniteMathAssumptionError(v, bool);
}
return Boolean(v);
}

throw new Error(
`Invalid argument type for 'd.bool'. Got '${typeof v}', expected number or boolean`,
);
},
codegenImpl: (ctx, [v]) => {
// check if zero arguments were passed
if (v === undefined) {
return ctx.gen.typeInstantiation(bool, []);
}

if (isBool(v.dataType) || isSnippetNumeric(v)) {
return ctx.gen.typeInstantiation(bool, [v]);
}

if (v.dataType === UnknownData) {
throw new WgslTypeError("Unknown argument type for 'd.bool'.");
}

throw new SignatureNotSupportedError([v.dataType], [bool, u32, i32, f32, f16]);
},
codegenImpl: (ctx, [v]) => ctx.gen.typeInstantiation(bool, v ? [v] : []),
});

/**
Expand Down
7 changes: 6 additions & 1 deletion packages/typegpu/src/data/wgslTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,8 @@ export type mBaseForVec<T extends vecBase> = T extends v2f
* Boolean schema representing a single WGSL bool value.
* Cannot be used inside buffers as it is not host-shareable.
*/
export interface Bool extends BaseData, DualFn<(v?: number | boolean) => boolean> {
export interface Bool
extends BaseData, DualFn<((v: number | boolean) => boolean) & (() => boolean)> {
readonly type: 'bool';

// Type-tokens, not available at runtime
Expand Down Expand Up @@ -1532,6 +1533,10 @@ export function isVecInstance(value: unknown): value is AnyVecInstance {
return isMarkedInternal(v) && typeof v.kind === 'string' && v.kind.startsWith('vec');
}

export function isVecBoolInstance(value: unknown): value is v2b | v3b | v4b {
return isVecInstance(value) && value.kind.includes('b');
}

export function isVec2(value: unknown): value is Vec2f | Vec2h | Vec2i | Vec2u {
const v = value as AnyWgslData | undefined;
return isMarkedInternal(v) && typeof v.type === 'string' && v.type.startsWith('vec2');
Expand Down
79 changes: 23 additions & 56 deletions packages/typegpu/src/std/boolean.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,14 @@ import {
type AnyWgslData,
type BaseData,
isBool,
isNumericSchema,
isVec,
isVecBool,
isVecBoolInstance,
isVecInstance,
type v2b,
type v3b,
type v4b,
} from '../data/wgslTypes.ts';
import { SignatureNotSupportedError } from '../errors.ts';
import { unify } from '../tgsl/conversion.ts';
import { sub } from './operators.ts';

Expand Down Expand Up @@ -191,82 +191,49 @@ export const ge = dualImpl({

// logical ops

type VecInstanceToBooleanVecInstance<T extends AnyVecInstance> = T extends AnyVec2Instance
? v2b
: T extends AnyVec3Instance
? v3b
: v4b;

function cpuNot(value: boolean): boolean;
function cpuNot(value: number): boolean;
function cpuNot<T extends AnyVecInstance>(value: T): VecInstanceToBooleanVecInstance<T>;
function cpuNot(value: unknown): boolean;
function cpuNot(value: unknown): boolean | AnyBooleanVecInstance {
if (typeof value === 'number' && isNaN(value)) {
return false;
function cpuNot<T extends AnyBooleanVecInstance>(value: T): T;
function cpuNot<T extends AnyBooleanVecInstance | boolean>(value: T): T {
if (typeof value === 'boolean') {
return !value as T;
}

if (isVecInstance(value)) {
if (value.length === 2) {
return vec2b(cpuNot(value.x), cpuNot(value.y));
}
if (value.length === 3) {
return vec3b(cpuNot(value.x), cpuNot(value.y), cpuNot(value.z));
}
if (value.length === 4) {
return vec4b(cpuNot(value.x), cpuNot(value.y), cpuNot(value.z), cpuNot(value.w));
}
if (!isVecBoolInstance(value)) {
throw new Error(`'std.not' requires a boolean or boolean vector.`);
}

return !value;
switch (value.length) {
case 2:
return vec2b(cpuNot(value.x), cpuNot(value.y)) as T;
case 3:
return vec3b(cpuNot(value.x), cpuNot(value.y), cpuNot(value.z)) as T;
case 4:
return vec4b(cpuNot(value.x), cpuNot(value.y), cpuNot(value.z), cpuNot(value.w)) as T;
}
}

/**
* Returns the logical negation of the given value.
* For scalars (bool, number), returns `!value`.
* For booleans returns `!value`.
* For boolean vectors, returns **component-wise** `!value`.
* For numeric vectors, returns a boolean vector with component-wise truthiness negation.
* For all other types, returns the truthiness negation (in WGSL, this applies only if the value is known at compile-time).
* @example
* not(true) // returns false
* not(-1) // returns false
* not(0) // returns true
* not(vec3b(true, true, false)) // returns vec3b(false, false, true)
* not(vec3f(1.0, 0.0, -1.0)) // returns vec3b(false, true, false)
* not({a: 1882}) // returns false
* not(NaN) // returns false **as in WGSL**
*/
export const not = dualImpl({
name: 'not',
signature: (arg) => {
const returnType = isVec(arg) ? correspondingBooleanVectorSchema(arg) : bool;
if (!isBool(arg) && !isVecBool(arg)) {
throw new SignatureNotSupportedError([arg], [bool, vec2b, vec3b, vec4b]);
}

return {
argTypes: [arg],
returnType,
returnType: arg,
};
},
normalImpl: cpuNot,
codegenImpl: (_ctx, [arg]) => {
const { dataType } = arg;

if (isBool(dataType)) {
return stitch`!${arg}`;
}
if (isNumericSchema(dataType)) {
return stitch`!bool(${arg})`;
}

if (isVecBool(dataType)) {
return stitch`!(${arg})`;
}

if (isVec(dataType)) {
const vecConstructorStr = `vec${dataType.componentCount}<bool>`;
return stitch`!(${vecConstructorStr}(${arg}))`;
}

return 'false';
},
codegenImpl: (_ctx, [arg]) => stitch`!${arg}`,
sideEffects: false,
});

Expand Down
23 changes: 10 additions & 13 deletions packages/typegpu/src/tgsl/wgslGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { $gpuCallable, $internal, $providing, isMarkedInternal } from '../shared
import { safeStringify } from '../shared/stringify.ts';
import { pow } from '../std/numeric.ts';
import { add, div, mul, neg, sub } from '../std/operators.ts';
import { eq, ne, lt, le, gt, ge } from '../std/boolean.ts';
import { eq, ne, lt, le, gt, ge, not } from '../std/boolean.ts';

import {
isGPUCallable,
Expand Down Expand Up @@ -183,20 +183,17 @@ const unaryOpCodeToCodegen = {

const argStr = ctx.resolveSnippet(argExpr).value;

if (wgsl.isBool(argExpr.dataType)) {
return snip(`!${argStr}`, bool, 'runtime', argExpr.possibleSideEffects);
}
if (wgsl.isNumericSchema(argExpr.dataType)) {
const resultStr = `!bool(${argStr})`;
const nanGuardedStr = // abstractFloat will be resolved as comptime known value
argExpr.dataType.type === 'f32' || argExpr.dataType.type === 'f16'
? `(((bitcast<u32>(${argExpr.dataType.type === 'f16' ? `f32(${argStr})` : argStr}) << 1u) - 1u) >= 0xff000000)`
: resultStr;

return snip(nanGuardedStr, bool, 'runtime', argExpr.possibleSideEffects);
if (!wgsl.isBool(argExpr.dataType)) {
throw new WgslTypeError(
`Unary operator ! requires boolean operand. Got ${String(argExpr.dataType)}.${
wgsl.isVecBool(argExpr.dataType)
? ` For component-wise negation, use 'std.${not.toString()}'.`
: ''
}`,
);
}

return snip(false, bool, 'constant', false);
return snip(`!${argStr}`, bool, 'runtime', argExpr.possibleSideEffects);
},
} satisfies Partial<Record<tinyest.UnaryOperator, (...args: never[]) => unknown>>;

Expand Down
Loading
Loading