Skip to content

Commit be62e5a

Browse files
feat: std.copy (#2489)
1 parent 4c4d4e7 commit be62e5a

4 files changed

Lines changed: 371 additions & 20 deletions

File tree

apps/typegpu-docs/src/content/docs/apis/data-schemas.mdx

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ WGSL needs the `unrestricted_pointer_parameters` extension.
514514
This extension is enabled automatically when available.
515515
:::
516516

517-
## Copy constructors
517+
## References and copying data
518518

519519
One of the biggest differences between JavaScript and WGSL is the existence of value/reference types.
520520
Let's take a look at a simple TypeGPU function and the WGSL code it resolves to.
@@ -545,36 +545,32 @@ fn myFn(){
545545
}
546546
```
547547

548-
In JavaScript, `s2` is a reference to `s1` and therefore `s1` is modified when `s2` is modified.
549-
On the WGSL side, we make `s2` a reference to `s1` and modifying `s2.n` is translated to modifying `(*s2).n`.
548+
Even though modifying `s2` results in a change to `s1` in both cases, it happens in two entirely different ways:
550549

551-
Schemas can be used to create deep copies of values.
550+
- In JavaScript, `s2` is a reference to `s1`, so modifying `s2` also modifies `s1`.
551+
- On the WGSL side, we make `s2` a pointer to `s1`, so modifying `s2.n` is translated to modifying `(*s2).n`.
552552

553+
To force an explicit copy in both cases, use a schema when the exact type is known:
553554
```ts twoslash
554-
import tgpu, { d } from 'typegpu';
555+
import tgpu, { d, std } from 'typegpu';
555556
// ---cut---
556557
const MyStruct = d.struct({ n: d.u32 });
557558

558559
function myFn() {
559560
'use gpu';
560-
const s1 = MyStruct({ n: 0 });
561-
const s2 = MyStruct(s1); // deep copy of s1
562-
s2.n = 1; // s1 is not modified
561+
const struct = MyStruct({ n: 0 })
562+
const copy = MyStruct(struct);
563563
}
564564
```
565565

566-
The TypeGPU shader generator understands this and generates a simple assignment, which copies the value of `s1` into `s2`.
567-
568-
```wgsl
569-
// WGSL
570-
struct MyStruct {
571-
n: u32,
572-
}
573-
574-
fn myFn(){
575-
var s1 = MyStruct(0);
576-
var s2 = s1; // copies s1 into s2
577-
s2.n = 1; // s1 is not modified
566+
Or use `std.copy` when the schema may vary:
567+
```ts twoslash
568+
import tgpu, { d, std } from 'typegpu';
569+
// ---cut---
570+
function myFn(arg: d.v2u | d.v3u | d.v4u) {
571+
'use gpu';
572+
const vec = std.copy(arg);
573+
vec.x++;
578574
}
579575
```
580576

packages/typegpu/src/std/copy.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { dualImpl } from '../core/function/dualImpl.ts';
2+
import { stitch } from '../core/resolve/stitch.ts';
3+
import { isMatInstance, isVecInstance, WORKAROUND_getSchema } from '../data/wgslTypes.ts';
4+
5+
function cpuCopy<T>(e: T): T {
6+
if (isVecInstance(e) || isMatInstance(e)) {
7+
const schema = WORKAROUND_getSchema(e);
8+
return schema(e as never) as T;
9+
}
10+
11+
if (Array.isArray(e)) {
12+
return e.map(cpuCopy) as T;
13+
}
14+
15+
if (typeof e === 'object' && e !== null) {
16+
return Object.fromEntries(Object.entries(e).map(([key, value]) => [key, cpuCopy(value)])) as T;
17+
}
18+
19+
return e;
20+
}
21+
22+
export const copy = dualImpl({
23+
name: 'copy',
24+
signature: (arg) => {
25+
return {
26+
argTypes: [arg],
27+
returnType: arg,
28+
};
29+
},
30+
normalImpl: cpuCopy,
31+
codegenImpl(_ctx, [a]) {
32+
return stitch`${a}`;
33+
},
34+
});

packages/typegpu/src/std/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
// NOTE: This is a barrel file, internal files should not import things from this file
66

7+
export { copy } from './copy.ts';
8+
79
export { discard } from './discard.ts';
810

911
export {

0 commit comments

Comments
 (0)