Skip to content

Commit 21125b7

Browse files
committed
std.not
1 parent 4f793c1 commit 21125b7

4 files changed

Lines changed: 204 additions & 14 deletions

File tree

apps/typegpu-docs/tests/individual-example-tests/tgsl-parsing-test.test.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,8 @@ describe('tgsl parsing test example', () => {
4444
s = (s && true);
4545
s = (s && true);
4646
s = (s && true);
47-
s = (s && !false);
4847
s = (s && true);
49-
s = (s && !false);
5048
s = (s && true);
51-
s = (s && !false);
5249
s = (s && true);
5350
s = (s && true);
5451
s = (s && true);
@@ -58,9 +55,12 @@ describe('tgsl parsing test example', () => {
5855
s = (s && true);
5956
s = (s && true);
6057
s = (s && true);
61-
s = (s && !false);
6258
s = (s && true);
63-
s = (s && !false);
59+
s = (s && true);
60+
s = (s && true);
61+
s = (s && true);
62+
s = (s && true);
63+
s = (s && true);
6464
s = (s && true);
6565
s = (s && true);
6666
var vec = vec3<bool>(true, false, true);

packages/typegpu/src/std/boolean.ts

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,17 @@ import {
1313
type AnyVecInstance,
1414
type AnyWgslData,
1515
type BaseData,
16+
isBool,
17+
isNumericSchema,
18+
isVec,
19+
isVecBool,
1620
isVecInstance,
1721
type v2b,
1822
type v3b,
1923
type v4b,
2024
} from '../data/wgslTypes.ts';
2125
import { unify } from '../tgsl/conversion.ts';
26+
import { isKnownAtComptime } from '../types.ts';
2227
import { sub } from './operators.ts';
2328

2429
function correspondingBooleanVectorSchema(dataType: BaseData) {
@@ -164,19 +169,81 @@ export const ge = dualImpl({
164169

165170
// logical ops
166171

167-
const cpuNot = <T extends AnyBooleanVecInstance>(value: T): T => VectorOps.neg[value.kind](value);
172+
type VecInstanceToBooleanVecInstance<T extends AnyVecInstance> = T extends AnyVec2Instance
173+
? v2b
174+
: T extends AnyVec3Instance
175+
? v3b
176+
: v4b;
177+
178+
function cpuNot(value: boolean): boolean;
179+
function cpuNot(value: number): boolean;
180+
function cpuNot<T extends AnyVecInstance>(value: T): VecInstanceToBooleanVecInstance<T>;
181+
function cpuNot(value: unknown): boolean;
182+
function cpuNot(value: unknown): boolean | AnyBooleanVecInstance {
183+
if (isVecInstance(value)) {
184+
if (value.length === 2) {
185+
return vec2b(!value.x, !value.y);
186+
}
187+
if (value.length === 3) {
188+
return vec3b(!value.x, !value.y, !value.z);
189+
}
190+
return vec4b(!value.x, !value.y, !value.z, !value.w);
191+
}
192+
193+
return !value;
194+
}
168195

169196
/**
170-
* Returns **component-wise** `!value`.
197+
* Returns the logical negation of the given value.
198+
* For scalars (bool, number), returns `!value`.
199+
* For boolean vectors, returns **component-wise** `!value`.
200+
* For numeric vectors, returns a boolean vector with component-wise truthiness negation.
201+
* For all other types, returns the truthiness negation (in WGSL, this applies only if the value is known at compile-time).
171202
* @example
172-
* not(vec2b(false, true)) // returns vec2b(true, false)
203+
* not(true) // returns false
204+
* not(-1) // returns false
205+
* not(0) // returns true
173206
* not(vec3b(true, true, false)) // returns vec3b(false, false, true)
207+
* not(vec3f(1.0, 0.0, -1.0)) // returns vec3b(false, true, false)
208+
* not({a: 1882}) // returns false
174209
*/
175210
export const not = dualImpl({
176211
name: 'not',
177-
signature: (...argTypes) => ({ argTypes, returnType: argTypes[0] }),
212+
signature: (arg) => {
213+
const returnType = isVec(arg) ? correspondingBooleanVectorSchema(arg) : bool;
214+
return {
215+
argTypes: [arg],
216+
returnType,
217+
};
218+
},
178219
normalImpl: cpuNot,
179-
codegenImpl: (_ctx, [arg]) => stitch`!(${arg})`,
220+
codegenImpl: (ctx, [arg]) => {
221+
if (isKnownAtComptime(arg)) {
222+
return `${!arg.value}`;
223+
}
224+
225+
const { dataType } = arg;
226+
227+
if (isBool(dataType)) {
228+
return stitch`!${arg}`;
229+
}
230+
if (isNumericSchema(dataType)) {
231+
return stitch`!bool(${arg})`;
232+
}
233+
234+
if (isVecBool(dataType)) {
235+
return stitch`!(${arg})`;
236+
}
237+
238+
if (isVec(dataType)) {
239+
const vecConstructorStr = `vec${dataType.componentCount}<bool>`;
240+
return stitch`!${vecConstructorStr}(${arg})`;
241+
}
242+
243+
throw new Error(
244+
`\`std.not\` cannot determine truthiness for runtime value of type: ${ctx.resolve(dataType).value}.`,
245+
);
246+
},
180247
});
181248

182249
const cpuOr = <T extends AnyBooleanVecInstance>(lhs: T, rhs: T) => VectorOps.or[lhs.kind](lhs, rhs);
Lines changed: 126 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,133 @@
1-
import { describe, expect, it } from 'vitest';
2-
import { vec2b, vec3b, vec4b } from '../../../src/data/index.ts';
1+
import { describe, expect } from 'vitest';
2+
import { it } from 'typegpu-testing-utility';
3+
import { vec2b, vec2f, vec3b, vec3i, vec4b, vec4h, vec4u } from '../../../src/data/index.ts';
34
import { not } from '../../../src/std/boolean.ts';
5+
import tgpu, { d } from '../../../src/index.js';
46

5-
describe('neg', () => {
6-
it('negates', () => {
7+
describe('not', () => {
8+
it('negates booleans', () => {
9+
expect(not(true)).toBe(false);
10+
expect(not(false)).toBe(true);
11+
});
12+
13+
it('converts numbers to booleans and negates', () => {
14+
expect(not(0)).toBe(true);
15+
expect(not(-1)).toBe(false);
16+
expect(not(42)).toBe(false);
17+
});
18+
19+
it('negates boolean vectors', () => {
720
expect(not(vec2b(true, false))).toStrictEqual(vec2b(false, true));
821
expect(not(vec3b(false, false, true))).toStrictEqual(vec3b(true, true, false));
922
expect(not(vec4b(true, true, false, false))).toStrictEqual(vec4b(false, false, true, true));
1023
});
24+
25+
it('converts numeric vectors to booleans vectors and negates component-wise', () => {
26+
expect(not(vec2f(0.0, 1.0))).toStrictEqual(vec2b(true, false));
27+
expect(not(vec3i(0, 5, -1))).toStrictEqual(vec3b(true, false, false));
28+
expect(not(vec4u(0, 0, 1, 0))).toStrictEqual(vec4b(true, true, false, true));
29+
expect(not(vec4h(0, 3.14, 0, -2.5))).toStrictEqual(vec4b(true, false, true, false));
30+
});
31+
32+
it('negates truthiness check', () => {
33+
const s = {};
34+
expect(not(null)).toBe(true);
35+
expect(not(undefined)).toBe(true);
36+
expect(not(s)).toBe(false);
37+
});
38+
39+
it('generates correct WGSL on a boolean runtime-known argument', () => {
40+
const testFn = tgpu.fn(
41+
[d.bool],
42+
d.bool,
43+
)((v) => {
44+
return not(v);
45+
});
46+
expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(`
47+
"fn testFn(v: bool) -> bool {
48+
return !v;
49+
}"
50+
`);
51+
});
52+
53+
it('generates correct WGSL on a numeric runtime-known argument', () => {
54+
const testFn = tgpu.fn(
55+
[d.i32],
56+
d.bool,
57+
)((v) => {
58+
return not(v);
59+
});
60+
expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(`
61+
"fn testFn(v: i32) -> bool {
62+
return !bool(v);
63+
}"
64+
`);
65+
});
66+
67+
it('generates correct WGSL on a boolean vector runtime-known argument', () => {
68+
const testFn = tgpu.fn(
69+
[d.vec3b],
70+
d.vec3b,
71+
)((v) => {
72+
return not(v);
73+
});
74+
expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(`
75+
"fn testFn(v: vec3<bool>) -> vec3<bool> {
76+
return !(v);
77+
}"
78+
`);
79+
});
80+
81+
it('generates correct WGSL on a numeric vector runtime-known argument', () => {
82+
const testFn = tgpu.fn(
83+
[d.vec3f],
84+
d.vec3b,
85+
)((v) => {
86+
return not(v);
87+
});
88+
expect(tgpu.resolve([testFn])).toMatchInlineSnapshot(`
89+
"fn testFn(v: vec3f) -> vec3<bool> {
90+
return !vec3<bool>(v);
91+
}"
92+
`);
93+
});
94+
95+
it('evaluates at compile time for comptime-known arguments', () => {
96+
const getN = tgpu.comptime(() => 42);
97+
const slot = tgpu.slot<{ a?: number }>({});
98+
99+
const f = () => {
100+
'use gpu';
101+
if (not(getN()) && not(slot.$.a) && not(d.vec4f(1, 8, 8, 2)).x) {
102+
return 1;
103+
}
104+
return -1;
105+
};
106+
107+
expect(tgpu.resolve([f])).toMatchInlineSnapshot(`
108+
"fn f() -> i32 {
109+
if (((false && true) && false)) {
110+
return 1;
111+
}
112+
return -1;
113+
}"
114+
`);
115+
});
116+
117+
it('throws when cannot determine truthiness of argument at compile time', ({ root }) => {
118+
const buffer = root.createUniform(d.mat4x4f);
119+
const testFn = tgpu.fn(
120+
[],
121+
d.bool,
122+
)(() => {
123+
return not(buffer.$);
124+
});
125+
126+
expect(() => tgpu.resolve([testFn])).toThrowErrorMatchingInlineSnapshot(`
127+
[Error: Resolution of the following tree failed:
128+
- <root>
129+
- fn:testFn
130+
- fn:not: \`std.not\` cannot determine truthiness for runtime value of type: mat4x4f.]
131+
`);
132+
});
11133
});

packages/typegpu/tests/tgsl/wgslGenerator.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2111,6 +2111,7 @@ describe('wgslGenerator', () => {
21112111
)(() => {
21122112
return !buffer.$;
21132113
});
2114+
21142115
expect(() => tgpu.resolve([testFn1])).toThrowErrorMatchingInlineSnapshot(`
21152116
[Error: Resolution of the following tree failed:
21162117
- <root>

0 commit comments

Comments
 (0)