software-mansion / software-mansion/TypeGPU
impr: Origin chain
Open
@cieplypolar is already working on this.
Since Apr 28, 2026.
- #2422 by @cieplypolar — closed without merging
- Dominant language
- TypeScript
- Stars
- 3.2k
- Forks
- 122
- Avg merge
- 3d 5h
- Merged PRs (30d)
- 34
Description
The purpose of this issue is to introduce concept of the origin chain. It should help with forbidding the following
const i = 0;
const iRef = d.ref(i);
and would make consumption tracking easier.
Origin chaining is allowing the snippet to be origin field value.
origin: Origin | Snippet
If origin field is a snippet then it should reference the highest snippet in a chain (snippet with origin: Origin) .
This table shows current and future behavior of snippets (we examine returned value):
| description | current | after | notes |
|---|---|---|---|
| basic | |||
const a = 1; return a; |
[..., i32, 'constant'] |
[..., i32, snippet(1, i32, 'constant')] |
It is required for the d.ref case mentioned above |
const a = 1; const b = a; return b; |
[..., i32, 'constant'] |
[..., i32, snippet(1, i32, 'constant')] |
Note that b 'references' a in the code, but the origin points to the highest snippet in the chain. |
fn([i32])((a) => return a); |
[..., i32, 'argument'] |
[..., i32, 'argument'] |
Non-ref arguments are copied, so a is the highest origin |
fn([i32])((a) => {
const b = a;
return b;
})
|
[..., i32, 'runtime'] |
[..., i32, snippet(a, i32, 'argument')] |
|
return getOne(); |
[..., i32, 'runtime'] |
[..., i32, 'runtime'] |
Here, we can potentially run into issue with getById, calls should be represented by separate snippets |
const b = getOne(); return b; |
[..., i32, 'runtime'] |
[..., i32, snippet('getOne()', i32, 'runtime')]
|
|
return vec2i(7); |
[..., vec2i, 'constant'] |
[..., vec2i, 'constant'] |
|
const a = vec2i(7); return a; |
[..., vec2i, 'this-function'] |
[..., vec2i, snippet(vec2i(7), vec2i, 'constant')] |
|
const a = vec2i(7); const b = a; return b; |
[..., iptr<function,vec2i,rw>, 'this-function'] |
[..., iptr<function,vec2i,rw>, snippet(vec2i(7), vec2i, 'constant')] |
Note, how datatype has changed |
fn([vec2i])((a) => return a); |
[..., vec2i, 'argument'] |
[..., vec2i, 'argument'] |
Uninterrupted flow :) |
fn([vec2i])((a) => {
const b = a;
return b;
})
|
[..., vec2i, 'argument'] |
[..., vec2i, snippet(a, vec2i, 'argument')] |
Implicit pointer btw, but currently cannot take pointer to te argument. TODO: carefully handle this case |
fn([vec2i])((a) => return a.x); |
[..., i32, 'argument'] |
[..., i32, 'argument'] |
Uninterrupted flow :) |
fn([vec2i])((a) => {
const b = a.x;
return b;
})
|
[..., i32, 'runtime'] |
[..., i32, snippet('a.x', i32, 'argument')] |
Comparing to the above flow, origin has changed. Maybe it should remain 'runtime' |
return getV(); |
[..., vec3f, 'runtime'] |
[..., vec3f, 'runtime'] |
|
const b = getV(); return b; |
[..., vec3f, 'this-function'] |
[..., vec3f, snippet('getV()', vec3f, 'runtime')]
|
Cannot really put 'this-function' origin here, what if we assign the result to a buffer? |
| uniform | |||
return buf.$; |
[..., f32, 'runtime'] |
[..., f32, 'runtime'] |
Buffer element is naturally ephemeral |
const b = buf.$; return b; |
[..., f32, 'runtime'] |
[..., f32, snippet('buf', f32, 'runtime')] |
On the contrary to the function call, we should distribute only 1 reference to buffer access snippet |
return buf.$; |
[..., vec3f, 'uniform'] |
[..., vec3f, 'uniform'] |
Buffer element is NOT naturally ephemeral |
const b = buf.$; return b; |
[..., iptr<uniform,vec3f,r>, 'uniform'] |
[..., iptr<uniform,vec3f,r>, snippet('buf', vec3f, 'uniform')]
|
|
| readonly | |||
return buf.$; |
[..., f32, 'runtime'] |
[..., f32, 'runtime'] |
|
const b = buf.$; return b; |
[..., f32, 'runtime'] |
[..., f32, snippet('buf', f32, 'runtime')] |
|
return buf.$; |
[..., vec3f, 'readonly'] |
[..., vec3f, 'readonly'] |
|
const b = buf.$; return b; |
[..., iptr<storage,vec3f,r>, 'readonly'] |
[..., iptr<storage,vec3f,r>, snippet('buf', vec3f, 'readonly')]
|
|
| mutable | |||
return buf.$; |
[..., f32, 'runtime'] |
[..., f32, 'runtime'] |
|
const b = buf.$; return b; |
[..., f32, 'runtime'] |
[..., f32, snippet('buf', f32, 'runtime')] |
|
return buf.$; |
[..., vec3f, 'mutable'] |
[..., vec3f, 'mutable'] |
|
const b = buf.$; return b; |
[..., iptr<storage,vec3f,rw>, 'mutable'] |
[..., iptr<storage,vec3f,rw>, snippet('buf', vec3f, 'mutable')]
|
|
| private | |||
return pv.$; |
[..., f32, 'runtime'] |
[..., f32, 'runtime'] |
|
const b = pv.$; return b; |
[..., f32, 'runtime'] |
[..., f32, snippet('pv', f32, 'runtime')] |
|
return pv.$; |
[..., vec3f, 'private'] |
[..., vec3f, 'private'] |
|
const b = pv.$; return b; |
[..., iptr<private,vec3f,rw>, 'private'] |
[..., iptr<private,vec3f,rw>, snippet('pv', vec3f, 'private')]
|
|
| workgroup | |||
return wgv.$; |
[..., f32, 'runtime'] |
[..., f32, 'runtime'] |
|
const b = wgv.$; return b; |
[..., f32, 'runtime'] |
[..., f32, snippet('wgv', f32, 'runtime')] |
|
return wgv.$; |
[..., vec3f, 'workgroup'] |
[..., vec3f, 'workgroup'] |
|
const b = wgv.$; return b; |
[..., iptr<workgroup,vec3f,rw>, 'workgroup'] |
[..., iptr<workgroup,vec3f,rw>, snippet('wgv', vec3f, 'workgroup')]
|
|
| handle | |||
return layout.$.s; |
[..., sampler, 'handle'] |
[..., sampler, 'handle'] |
For now, you cannot assign sampler or texture to a variable |
| tgpu.const | |||
return c.$; |
[..., u32, 'constant'] |
[..., u32, 'constant'] |
|
const b = c.$; return b; |
[..., u32, 'constant'] |
[..., u32, snippet('c', u32, 'constant')] |
|
return c.$; |
[..., vec3f, 'constant-tgpu-const-ref'] |
[..., vec3f, 'constant-tgpu-const-ref'] |
|
const b = c.$; return b; |
[..., vec3f, 'constant-tgpu-const-ref'] |
[..., vec3f, snippet('c', vec3f, 'constant-tgpu-const-ref')]
|
|
fn([i32])((i) => return arr.$[i]); |
[..., f32, 'runtime'] |
[..., f32, 'runtime'] |
|
fn([i32])((i) => {
const b = arr.$[i];
return b;
})
|
[..., f32, 'runtime'] |
[..., f32, snippet('arr[i]', f32, 'runtime')]
|
|
fn([i32])((i) => return arr.$[i]); |
[..., vec3f, 'runtime-tgpu-const-ref'] |
[..., vec3f, 'runtime-tgpu-const-ref'] |
|
fn([i32])((i) => {
const b = arr.$[i];
return b;
})
|
[..., vec3f, 'runtime-tgpu-const-ref'] |
[..., vec3f, snippet('arr[i]', vec3f, 'runtime-tgpu-const-ref')]
|
|
| struct | |||
return buf.$.count; |
[..., u32, 'runtime'] |
[..., u32, 'runtime'] |
|
const b = buf.$.count; return b; |
[..., u32, 'runtime'] |
[..., u32, snippet('buf.count', u32, 'runtime')]
|
Access to struct should be distributed as reference to one unique snippet |
return buf.$.pos; |
[..., vec3f, 'uniform'] |
[..., vec3f, 'uniform'] |
|
const b = buf.$.pos; return b; |
[..., iptr<uniform,vec3f,r>, 'uniform'] |
[..., iptr<uniform,vec3f,r>, snippet('buf.$.pos', vec3f, 'uniform')]
|
|
| swizzle | |||
fn([vec3f])((v) => return v.x); |
[..., f32, 'argument'] |
[..., f32, 'argument'] |
|
fn([vec3f])((v) => {
const b = v.x;
return b;
})
|
[..., f32, 'runtime'] |
[..., f32, snippet('v.x', f32, 'runtime')] |
|
fn([vec3f])((v) => return v.xy); |
[..., vec2f, 'runtime'] |
[..., vec2f, 'runtime'] |
|
fn([vec3f])((v) => {
const b = v.xy;
return b;
})
|
[..., vec2f, 'this-function'] |
[..., vec2f, snippet('v.xy', vec2f, 'runtime')]
|
|
| d.ref | |||
const b = d.ref(u32(0)); return b; |
[..., ptr<function,u32,rw>, 'function'] |
[..., ptr<function,u32,rw>, snippet(u32(0), u32, 'constant')] |
Not sure about that |
const b = d.ref(vec2f()); return b; |
[..., ptr<function,vec2f,rw>, 'function'] |
[..., ptr<function,vec2f,rw>, snippet(vec2f(0), vec2f, 'constant')] |
|
| mixed origins in complex types | |||
return vec4f(7).xz; |
[..., vec2f, 'constant'] |
[..., vec2f, 'constant'] |
|
const b = vec4f(7).xz; return b; |
[..., vec2f, 'this-function'] |
[..., vec2f, snippet(vec4f(7).xz, vec2f, 'constant')] |
|
return vec4f(buf.$, 1).xw; |
[..., vec2f, 'runtime'] |
[..., vec2f, 'runtime'] |
|
const b = vec4f(buf.$, 1).xw; return b; |
[..., vec2f, 'this-function'] |
[..., vec2f, snippet(vec4f(buf.$, 1), vec2f, 'runtime')] |
Constructors perform deep copy :)) |
const a = Boid(); return a.pos; |
[..., vec2f, 'this-function'] |
[..., vec2f, 'this-function'] |
Not sure about that because there is nothing to be referenced |
const a = Boid(); const b = a.pos; return b; |
[..., iptr<function,vec2f,rw>, 'this-function'] |
[..., iptr<function,vec2f,rw>, snippet(a.pos, vec2f, 'this-function')] |
|
const a = Boid({ pos: buf.$, ... });
return a.pos;
|
[..., vec2f, 'this-function'] |
[..., vec2f, 'this-function'] |
Again, constructors perform deep copy |
const a = Boid({ pos: buf.$, ... });
const b = a.pos;
return b;
|
[..., iptr<function,vec2f,rw>, 'this-function'] |
[..., iptr<function,vec2f,rw>, snippet(a.pos, vec2f, 'this-function')] |
|
TODO:
- add more tests with d.ref (maybe examining last statement will allow to check inter state of the d.ref)
- rethink table -> only this-function changes to ExtendedOrigin (for example {consumed: boolean})
getSnippetById would return snippet with extended origin field
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.