software-mansion / software-mansion/TypeGPU

impr: Origin chain

Open
#2,421 1 comment 1 reaction 1 assignee View on GitHub

@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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.