feat: Transfer protocol for TypeGPU resources (on souls) - #2797
Conversation
|
pkg.pr.new packages benchmark commit |
Resolution Time Benchmark---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Random Branching (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.86, 1.79, 3.78, 6.41, 7.10, 11.77, 21.18, 24.56]
line [0.96, 1.86, 4.11, 6.12, 7.66, 12.71, 21.08, 22.54]
line [0.99, 1.98, 4.31, 6.40, 7.68, 11.27, 21.33, 24.47]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Linear Recursion (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.29, 0.50, 0.66, 0.75, 1.00, 1.05, 1.29, 1.43]
line [0.27, 0.49, 0.67, 0.81, 1.15, 1.17, 1.35, 1.51]
line [0.30, 0.53, 0.71, 0.87, 1.15, 1.24, 1.48, 1.66]
---
config:
themeVariables:
xyChart:
plotColorPalette: "#E63946, #3B82F6, #059669"
---
xychart
title "Full Tree (🔴 PR | 🔵 main | 🟢 release)"
x-axis "max depth" [1, 2, 3, 4, 5, 6, 7, 8]
y-axis "time (ms)"
line [0.81, 2.05, 3.26, 6.95, 11.90, 25.26, 52.16, 107.81]
line [0.77, 2.09, 3.59, 6.70, 11.92, 25.48, 53.22, 109.90]
line [0.81, 2.13, 3.86, 6.47, 12.96, 26.03, 55.68, 113.56]
|
Bundle size comparison (
|
| 🟢 Decreased | ➖ Unchanged | 🔴 Increased (max 0.24%) | ❔ Unknown |
|---|---|---|---|
| 0 | 300 | 22 | 1 |
import * as ... in PR vs import * as ... in target (did bundle size increase?):
| Test | tsdown |
|---|---|
| d_isInvariantAttrib.ts | 784 B |
import { ... } in PR vs import * as ... in PR (is the library tree-Shakeable?):
| Test | tsdown |
|---|---|
| tgpu_init.ts | 267.34 kB ( |
| tgpu_initFromDevice.ts | 266.80 kB ( |
| tgpu_resolve.ts | 167.89 kB ( |
| tgpu_resolveWithContext.ts | 167.82 kB ( |
| tgpu_bindGroupLayout.ts | 73.57 kB ( |
| tgpu_mutableAccessor.ts | 68.30 kB ( |
| tgpu_accessor.ts | 68.29 kB ( |
| tgpu_privateVar.ts | 66.85 kB ( |
| tgpu_workgroupVar.ts | 66.85 kB ( |
| tgpu_const.ts | 66.40 kB ( |
| tgpu_lazy.ts | 66.21 kB ( |
| tgpu_fragmentFn.ts | 38.72 kB ( |
| tgpu_fn.ts | 38.65 kB ( |
| tgpu_vertexFn.ts | 38.53 kB ( |
| tgpu_computeFn.ts | 38.24 kB ( |
| tgpu_vertexLayout.ts | 27.36 kB ( |
| tgpu_comptime.ts | 14.97 kB ( |
| tgpu_unroll.ts | 1.68 kB ( |
| tgpu_slot.ts | 1.63 kB ( |
If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu.
There was a problem hiding this comment.
This is a solid, well-tested addition of the cross-runtime transfer protocol. The snapshot/restore split is clean, the tests cover all the major resource kinds, and the JSI-specific type guards are a nice touch for React Native.
I have a few small suggestions around TgpuBindGroupImpl / bind-group restoration that are worth double-checking before merge, but nothing that blocks the overall approach. The relevant unit tests (serial, serializeDataSchema, computePipeline, renderPipeline, internal/typeGuards, root) all pass locally.
Kimi K2 (free via Pullfrog for OSS) | 𝕏
| this[$soul] = { | ||
| type: 'bind-group', | ||
| // Undefined only in rootless `tgpu.resolve()`, where the group is never unwrapped | ||
| device: root?.device as GPUDevice, |
There was a problem hiding this comment.
This as GPUDevice hides the fact that device can be undefined when the bind group is created rootlessly via tgpu.resolve(). TgpuDeviceOwningSoul requires device: GPUDevice, so the type system will treat this as a real device even when it isn't. Consider making device optional in the soul type for rootless resources, or using a separate soul shape, so the runtime value matches the type.
| for (const key of Object.keys(layout.entries)) { | ||
| if (layout.entries[key] !== null && !(key in entries)) { | ||
| throw new MissingBindingError(getName(layout), key); | ||
| if (!raw) { |
There was a problem hiding this comment.
Skipping the missing-entry check when raw is provided makes sense for restored/materialized bind groups, but it means the constructor now allows bind groups with incomplete entries as long as a raw GPUBindGroup is passed. Since TgpuBindGroupImpl is exported, this is a bit of a footgun. Could you add a clarifying comment that this path is only for restoration, or consider validating anyway when raw comes from an untrusted source?

Replaces #2732, rebuilt on top of #2796. With souls in place the protocol got much simpler: a snapshot is just a structured copy of the resource's soul
tgpu.fn, entry functions,tgpu.comptime) stay runtime-local