Skip to content

Commit 51354a8

Browse files
impr: Matrix interface (#1289)
1 parent b798e6a commit 51354a8

File tree

6 files changed

+73
-30
lines changed

6 files changed

+73
-30
lines changed

apps/typegpu-docs/src/content/docs/reference/data-schema-cheatsheet.mdx

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,47 @@
22
title: Data Schema Cheatsheet
33
---
44

5-
## Numeric data types
5+
## Scalar data types
66

77
import { Code } from '@astrojs/starlight/components';
88

99
| Schema | JavaScript | WGSL |
1010
| --- | --- | --- |
1111
| <Code code="import { f32 } from 'typegpu/data';" lang="js" /> | `number` | `f32` |
12+
| <Code code="import { f16 } from 'typegpu/data';" lang="js" /> | `number` | `f16` |
1213
| <Code code="import { i32 } from 'typegpu/data';" lang="js" /> | `number` | `i32` |
1314
| <Code code="import { u32 } from 'typegpu/data';" lang="js" /> | `number` | `u32` |
1415
| <Code code="import { bool } from 'typegpu/data';" lang="js" /> | `boolean` | `bool` |
1516

1617
## Vector and matrix types
1718

18-
| Schema | Value constructors | WGSL equivalents |
19+
| <div style="width:5em">Schema</div> | Value constructors | <div style="width:5em">WGSL equivalents</div> |
1920
| --- | --- | --- |
2021
| `vec2u` | <ul><li>`vec2u(x: number, y: number)`</li> <li>`vec2u(xy: number)`</li> <li>`vec2u()`</li></ul> | vec2u, vec2\<u32\> |
2122
| `vec2f` | <ul><li>`vec2f(x: number, y: number)`</li> <li>`vec2f(xy: number)`</li> <li>`vec2f()`</li></ul> | vec2f, vec2\<f32\> |
2223
| `vec2i` | <ul><li>`vec2i(x: number, y: number)`</li> <li>`vec2i(xy: number)`</li> <li>`vec2i()`</li></ul> | vec2i, vec2\<i32\> |
2324
| `vec2h` | <ul><li>`vec2h(x: number, y: number)`</li> <li>`vec2h(xy: number)`</li> <li>`vec2h()`</li></ul> | vec2h, vec2\<f16\> |
25+
| `vec2b` | <ul><li>`vec2b(x: boolean, y: boolean)`</li> <li>`vec2b(xy: boolean)`</li> <li>`vec2b()`</li></ul> | vec2\<bool\> |
2426
| `vec3u` | <ul><li>`vec3u(x: number, y: number, z: number)`</li> <li>`vec3u(xyz: number)`</li> <li>`vec3u()`</li></ul> | vec3u, vec3\<u32\> |
2527
| `vec3f` | <ul><li>`vec3f(x: number, y: number, z: number)`</li> <li>`vec3f(xyz: number)`</li> <li>`vec3f()`</li></ul> | vec3f, vec3\<f32\> |
2628
| `vec3i` | <ul><li>`vec3i(x: number, y: number, z: number)`</li> <li>`vec3i(xyz: number)`</li> <li>`vec3i()`</li></ul> | vec3i, vec3\<i32\> |
2729
| `vec3h` | <ul><li>`vec3h(x: number, y: number, z: number)`</li> <li>`vec3h(xyz: number)`</li> <li>`vec3h()`</li></ul> | vec3h, vec3\<f16\> |
30+
| `vec3b` | <ul><li>`vec3b(x: boolean, y: boolean, z: boolean)`</li> <li>`vec3b(xyz: boolean)`</li> <li>`vec3b()`</li></ul> | vec3\<bool\> |
2831
| `vec4u` | <ul><li>`vec4u(x: number, y: number, z: number, w: number)`</li> <li>`vec4u(xyzw: number)`</li> <li>`vec4u()`</li></ul> | vec4u, vec4\<u32\> |
2932
| `vec4f` | <ul><li>`vec4f(x: number, y: number, z: number, w: number)`</li> <li>`vec4f(xyzw: number)`</li> <li>`vec4f()`</li></ul> | vec4f, vec4\<f32\> |
3033
| `vec4i` | <ul><li>`vec4i(x: number, y: number, z: number, w: number)`</li> <li>`vec4i(xyzw: number)`</li> <li>`vec4i()`</li></ul> | vec4i, vec4\<i32\> |
3134
| `vec4h` | <ul><li>`vec4h(x: number, y: number, z: number, w: number)`</li> <li>`vec4h(xyzw: number)`</li> <li>`vec4h()`</li></ul> | vec4h, vec4\<f16\> |
32-
| `mat2x2f` | <ul><li>`mat2x2f(...elements: number[])`</li> <li>`mat2x2f(...columns: vec2f[])`</li> <li>`mat2x2f()`</li></ul> | mat2x2f, mat2x2\<f32\> |
33-
| `mat3x3f` | <ul><li>`mat3x3f(...elements: number[])`</li> <li>`mat3x3f(...columns: vec3f[])`</li> <li>`mat3x3f()`</li></ul> | mat3x3f, mat3x3\<f32\> |
34-
| `mat4x4f` | <ul><li>`mat4x4f(...elements: number[])`</li> <li>`mat4x4f(...columns: vec4f[])`</li> <li>`mat4x4f()`</li></ul> | mat4x4f, mat4x4\<f32\> |
35+
| `vec4b` | <ul><li>`vec4b(x: boolean, y: boolean, z: boolean, w: boolean)`</li> <li>`vec4b(xyzw: boolean)`</li> <li>`vec4b()`</li></ul> | vec4\<bool\> |
36+
| `mat2x2f` | <ul><li>`mat2x2f(...elements: [number, number, number, number])`</li> <li>`mat2x2f(...columns: [v2f, v2f])`</li> <li>`mat2x2f()`</li></ul> | mat2x2f, mat2x2\<f32\> |
37+
| `mat3x3f` | <ul><li>`mat3x3f(...elements: [number, number, number, number, number, number, number, number, number])`</li> <li>`mat3x3f(...columns: [v3f, v3f, v3f])`</li> <li>`mat3x3f()`</li></ul> | mat3x3f, mat3x3\<f32\> |
38+
| `mat4x4f` | <ul><li>`mat4x4f(...elements: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number])`</li> <li>`mat4x4f(...columns: [v4f, v4f, v4f, v4f])`</li> <li>`mat4x4f()`</li></ul> | mat4x4f, mat4x4\<f32\> |
39+
40+
Apart from the listed constructors, all vectors can be created from any mix of other vectors and numbers, like this:
41+
42+
<Code code="
43+
import { vec2f, vec3f, vec4f } from 'typegpu/data';
44+
45+
const a = vec2f(1, 2); // 1, 2
46+
const b = vec3f(0, a); // 0, 1, 2
47+
const c = vec4f(b.xz, a.xx); // 0, 2, 1, 1
48+
" lang="js" />

packages/typegpu/src/data/matrix.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,15 @@ function createMatSchema<
7373
}
7474
}
7575

76-
// Fill the rest with zeros
76+
if (
77+
elements.length !== 0 &&
78+
elements.length !== options.columns * options.rows
79+
) {
80+
throw new Error(
81+
`'${options.type}' constructor called with invalid number of arguments.`,
82+
);
83+
}
84+
7785
for (let i = elements.length; i < options.columns * options.rows; ++i) {
7886
elements.push(0);
7987
}

packages/typegpu/src/data/vectorOps.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -500,8 +500,8 @@ export const VectorOps = {
500500
vec4u: binaryComponentWise4u((a, b) => a * b),
501501

502502
mat2x2f: (a: wgsl.m2x2f, b: wgsl.m2x2f) => {
503-
const a_ = a.columns as [wgsl.v2f, wgsl.v2f];
504-
const b_ = b.columns as [wgsl.v2f, wgsl.v2f];
503+
const a_ = a.columns;
504+
const b_ = b.columns;
505505

506506
return mat2x2f(
507507
a_[0].x * b_[0].x + a_[1].x * b_[0].y,
@@ -512,8 +512,8 @@ export const VectorOps = {
512512
},
513513

514514
mat3x3f: (a: wgsl.m3x3f, b: wgsl.m3x3f) => {
515-
const a_ = a.columns as [wgsl.v3f, wgsl.v3f, wgsl.v3f];
516-
const b_ = b.columns as [wgsl.v3f, wgsl.v3f, wgsl.v3f];
515+
const a_ = a.columns;
516+
const b_ = b.columns;
517517

518518
return mat3x3f(
519519
a_[0].x * b_[0].x + a_[1].x * b_[0].y + a_[2].x * b_[0].z,
@@ -529,8 +529,8 @@ export const VectorOps = {
529529
},
530530

531531
mat4x4f: (a: wgsl.m4x4f, b: wgsl.m4x4f) => {
532-
const a_ = a.columns as [wgsl.v4f, wgsl.v4f, wgsl.v4f, wgsl.v4f];
533-
const b_ = b.columns as [wgsl.v4f, wgsl.v4f, wgsl.v4f, wgsl.v4f];
532+
const a_ = a.columns;
533+
const b_ = b.columns;
534534

535535
return mat4x4f(
536536
a_[0].x * b_[0].x +
@@ -606,15 +606,15 @@ export const VectorOps = {
606606

607607
mulMxV: {
608608
mat2x2f: (m: wgsl.m2x2f, v: wgsl.v2f) => {
609-
const m_ = m.columns as [wgsl.v2f, wgsl.v2f];
609+
const m_ = m.columns;
610610
return vec2f(
611611
m_[0].x * v.x + m_[1].x * v.y,
612612
m_[0].y * v.x + m_[1].y * v.y,
613613
);
614614
},
615615

616616
mat3x3f: (m: wgsl.m3x3f, v: wgsl.v3f) => {
617-
const m_ = m.columns as [wgsl.v3f, wgsl.v3f, wgsl.v3f];
617+
const m_ = m.columns;
618618
return vec3f(
619619
m_[0].x * v.x + m_[1].x * v.y + m_[2].x * v.z,
620620
m_[0].y * v.x + m_[1].y * v.y + m_[2].y * v.z,
@@ -623,7 +623,7 @@ export const VectorOps = {
623623
},
624624

625625
mat4x4f: (m: wgsl.m4x4f, v: wgsl.v4f) => {
626-
const m_ = m.columns as [wgsl.v4f, wgsl.v4f, wgsl.v4f, wgsl.v4f];
626+
const m_ = m.columns;
627627
return vec4f(
628628
m_[0].x * v.x + m_[1].x * v.y + m_[2].x * v.z + m_[3].x * v.w,
629629
m_[0].y * v.x + m_[1].y * v.y + m_[2].y * v.z + m_[3].y * v.w,
@@ -641,15 +641,15 @@ export const VectorOps = {
641641

642642
mulVxM: {
643643
mat2x2f: (v: wgsl.v2f, m: wgsl.m2x2f) => {
644-
const m_ = m.columns as [wgsl.v2f, wgsl.v2f];
644+
const m_ = m.columns;
645645
return vec2f(
646646
v.x * m_[0].x + v.y * m_[0].y,
647647
v.x * m_[1].x + v.y * m_[1].y,
648648
);
649649
},
650650

651651
mat3x3f: (v: wgsl.v3f, m: wgsl.m3x3f) => {
652-
const m_ = m.columns as [wgsl.v3f, wgsl.v3f, wgsl.v3f];
652+
const m_ = m.columns;
653653
return vec3f(
654654
v.x * m_[0].x + v.y * m_[0].y + v.z * m_[0].z,
655655
v.x * m_[1].x + v.y * m_[1].y + v.z * m_[1].z,
@@ -658,7 +658,7 @@ export const VectorOps = {
658658
},
659659

660660
mat4x4f: (v: wgsl.v4f, m: wgsl.m4x4f) => {
661-
const m_ = m.columns as [wgsl.v4f, wgsl.v4f, wgsl.v4f, wgsl.v4f];
661+
const m_ = m.columns;
662662
return vec4f(
663663
v.x * m_[0].x + v.y * m_[0].y + v.z * m_[0].z + v.w * m_[0].w,
664664
v.x * m_[1].x + v.y * m_[1].y + v.z * m_[1].z + v.w * m_[1].w,

packages/typegpu/src/data/wgslTypes.ts

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -614,6 +614,7 @@ export interface matBase<TColumn> extends NumberArrayView {
614614
export interface mat2x2<TColumn> extends matBase<TColumn> {
615615
readonly length: 4;
616616
readonly kind: string;
617+
/* override */ readonly columns: readonly [TColumn, TColumn];
617618
[n: number]: number;
618619
}
619620

@@ -632,6 +633,7 @@ export interface m2x2f extends mat2x2<v2f> {
632633
export interface mat3x3<TColumn> extends matBase<TColumn> {
633634
readonly length: 12;
634635
readonly kind: string;
636+
/* override */ readonly columns: readonly [TColumn, TColumn, TColumn];
635637
[n: number]: number;
636638
}
637639

@@ -650,6 +652,12 @@ export interface m3x3f extends mat3x3<v3f> {
650652
export interface mat4x4<TColumn> extends matBase<TColumn> {
651653
readonly length: 16;
652654
readonly kind: string;
655+
/* override */ readonly columns: readonly [
656+
TColumn,
657+
TColumn,
658+
TColumn,
659+
TColumn,
660+
];
653661
[n: number]: number;
654662
}
655663

@@ -993,8 +1001,8 @@ export interface Mat2x2f {
9931001
readonly type: 'mat2x2f';
9941002
readonly [$repr]: m2x2f;
9951003

996-
(...elements: number[]): m2x2f;
997-
(...columns: v2f[]): m2x2f;
1004+
(...elements: [number, number, number, number]): m2x2f;
1005+
(...columns: [v2f, v2f]): m2x2f;
9981006
(): m2x2f;
9991007
}
10001008

@@ -1006,8 +1014,9 @@ export interface Mat3x3f {
10061014
readonly type: 'mat3x3f';
10071015
readonly [$repr]: m3x3f;
10081016

1009-
(...elements: number[]): m3x3f;
1010-
(...columns: v3f[]): m3x3f;
1017+
// deno-fmt-ignore
1018+
(...elements: [number, number, number, number, number, number, number, number, number]): m3x3f;
1019+
(...columns: [v3f, v3f, v3f]): m3x3f;
10111020
(): m3x3f;
10121021
}
10131022

@@ -1019,8 +1028,9 @@ export interface Mat4x4f {
10191028
readonly type: 'mat4x4f';
10201029
readonly [$repr]: m4x4f;
10211030

1022-
(...elements: number[]): m4x4f;
1023-
(...columns: v4f[]): m4x4f;
1031+
// deno-fmt-ignore
1032+
(...elements: [number, number, number, number, number, number, number, number, number, number, number, number, number, number, number, number]): m4x4f;
1033+
(...columns: [v4f, v4f, v4f, v4f]): m4x4f;
10241034
(): m4x4f;
10251035
}
10261036

packages/typegpu/tests/compiledIO.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,8 @@ describe('createCompileInstructions', () => {
183183
const dataView = new DataView(arr);
184184

185185
writer(dataView, 0, {
186-
transform: d.mat4x4f(...Array.from({ length: 16 }).map((_, i) => i)),
186+
// deno-fmt-ignore
187+
transform: d.mat4x4f(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15),
187188
});
188189

189190
expect([...new Float32Array(arr)]).toStrictEqual(
@@ -201,7 +202,7 @@ describe('createCompileInstructions', () => {
201202
const dataView = new DataView(arr);
202203

203204
writer(dataView, 0, {
204-
transform: d.mat3x3f(...Array.from({ length: 9 }).map((_, i) => i)),
205+
transform: d.mat3x3f(0, 1, 2, 3, 4, 5, 6, 7, 8),
205206
});
206207

207208
expect(arr.byteLength).toBe(48);
@@ -219,7 +220,7 @@ describe('createCompileInstructions', () => {
219220
const dataView = new DataView(arr);
220221

221222
writer(dataView, 0, {
222-
transform: d.mat2x2f(...Array.from({ length: 4 }).map((_, i) => i)),
223+
transform: d.mat2x2f(0, 1, 2, 3),
223224
});
224225

225226
expect(arr.byteLength).toBe(16);

packages/typegpu/tests/matrix.test.ts

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BufferReader, BufferWriter } from 'typed-binary';
2-
import { describe, expect, it } from 'vitest';
2+
import { describe, expect, expectTypeOf, it } from 'vitest';
33
import * as d from '../src/data/index.ts';
44
import tgpu from '../src/index.ts';
55

@@ -232,7 +232,7 @@ describe('mat3x3f', () => {
232232
});
233233

234234
it('should work with for...of', () => {
235-
const mat = d.mat3x3f(...Array(9).keys());
235+
const mat = d.mat3x3f(0, 1, 2, 3, 4, 5, 6, 7, 8);
236236
let i = 0;
237237
for (const x of mat) {
238238
expect(x).toBe(mat[i]);
@@ -373,7 +373,7 @@ describe('mat4x4f', () => {
373373
});
374374

375375
it('should work with for...of', () => {
376-
const mat = d.mat4x4f(...Array(16).keys());
376+
const mat = d.mat4x4f(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15);
377377
let i = 0;
378378
for (const x of mat) {
379379
expect(x).toBe(mat[i]);
@@ -382,4 +382,14 @@ describe('mat4x4f', () => {
382382
}
383383
expect(i).toBe(16);
384384
});
385+
386+
it('has correct column types', () => {
387+
expectTypeOf(d.mat2x2f().columns).toEqualTypeOf<readonly [d.v2f, d.v2f]>();
388+
expectTypeOf(d.mat3x3f().columns).toEqualTypeOf<
389+
readonly [d.v3f, d.v3f, d.v3f]
390+
>();
391+
expectTypeOf(d.mat4x4f().columns).toEqualTypeOf<
392+
readonly [d.v4f, d.v4f, d.v4f, d.v4f]
393+
>();
394+
});
385395
});

0 commit comments

Comments
 (0)