Skip to content

feat(@typegpu/prune): Capture precompile() as an artifact other hosts can read - #3324

Closed
reczkok wants to merge 2 commits into
feat/typegpu-prunefrom
feat/prune-artifact
Closed

reczkok wants to merge 2 commits into
feat/typegpu-prunefrom
feat/prune-artifact

Conversation

@reczkok

@reczkok reczkok commented Nov 5, 2026

Copy link
Copy Markdown
Contributor

Stacked on #3303.

What

Splits prune's serializer in two, so that what it records can be read by hosts other than the pruned JS runtime:

  • capture(fn, options) runs the arrow on the recording root and returns an Artifact: plain JSON, prune's tables plus what only other hosts need.
  • emit(artifact, { async }) turns the artifact into the code a precompile() call becomes. serializePrecompiled is now capture then emit. The runtime Tables and __tgpuPruneAttach are unchanged.
  • @typegpu/prune/capture exports both, for build tools of native hosts.
  • typegpu gets one ~internal addition, getLogResources(pipeline). Log resources are not part of a pipeline's soul.

There is no new public TypeGPU API.

Why

A native host, such as wgpu through typegpu-wgpu (a local experiment that generates typed Rust bindings from TypeGPU modules), needs what prune already records: what TypeGPU asks WebGPU for. Today it reconstructs that by other means: resolveWithContext, an unreleased describeRenderPipeline, and patching LogGeneratorNullImpl so that logs survive resolving without a root. Capturing through the recording root gives prune and native hosts one IR between TypeGPU and WebGPU.

What the artifact adds to the tables

schemas serializeDataSchema output with every node annotated with size, align, struct name and offsets (packed for unstructs), and array stride. deserializeDataSchema ignores the annotations. Runtime-sized arrays have count: 0. Codecs are generated from these in emit, and only for the schemas the runtime uses.
layouts name, the schemas of buffer entries, and the $idx index
vertex layouts name, element schema
pipelines immediate (name and schema), logs (group, bindings, capacity, and calls with op and arg schemas). Render pipelines also get targetKeys, the fragment output of each target.
surface The arrow's result as a tree of references into the tables

Capture options: features, wgslLanguageFeatures (for immediate_address_space), stripLogs (default true), minify (default true) and names. emit still rejects pipelines that use immediates, as before.

Validation

  • Prune tests pass 28/28. The boids "same GPU work" test and the codec round-trip now go through emit, which builds codecs from deserialized schemas.
  • I rewrote typegpu-wgpu's extractor on capture locally. Every manifest field is read from artifacts, and live objects are only used to find exports and build pipelines on the recording root. Results:
    • The manifests of its three apps are byte-identical to before.
    • The only WGSL changes come from newer TypeGPU codegen (seed[0i] became seed.x in @typegpu/noise).
    • Its JS tests (12/12) and cargo test --workspace pass. That includes interop tests running the pipelines on a wgpu device, with logs, function calls, config/enableExtensions variants and hot reload.
    • Two apps also needed fixes from feat/pipeline-descriptor (unrolling comptime-known iterables, and generic std.mix). Those fixes are not part of this PR.

Notes

  • typegpu-wgpu captures one root per pipeline, because its resolve options (slots through root.with, enableExtensions as device features) are per pipeline. It merges the artifacts by structural identity.
  • TypeGPU gives the fixed-resource group (logs) the first free index. Hosts that want group indices unaffected by adding a log remap them, which the layout index makes possible.

They aren't part of the pipeline's soul, since logs are read back by the runtime that resolved the pipeline. Capturing a pipeline for another host needs them to describe where its console.log calls write.
… can read

Splits serialization into capture() and emit(). capture() runs an arrow on the recording root and describes what it created as plain data: prune's tables, plus annotated schemas, layout and vertex layout names, immediates, fragment target keys and console.log descriptions, which only hosts other than the pruned runtime need. emit() turns that into the code precompile() calls are replaced with, generating codecs only for the schemas the runtime moves values of.

Exported as @typegpu/prune/capture, for build tools of native hosts.
@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

pkg.pr.new

packages
Ready to be installed by your favorite package manager ⬇️

https://pkg.pr.new/software-mansion/TypeGPU/tgpu-gen@4451e07369bcf20004cef320e2775c6e9c21d49f
https://pkg.pr.new/software-mansion/TypeGPU/typegpu@4451e07369bcf20004cef320e2775c6e9c21d49f
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/cli@4451e07369bcf20004cef320e2775c6e9c21d49f
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/color@4451e07369bcf20004cef320e2775c6e9c21d49f
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/gl@4451e07369bcf20004cef320e2775c6e9c21d49f
https://pkg.pr.new/software-mansion/TypeGPU/unplugin-typegpu@4451e07369bcf20004cef320e2775c6e9c21d49f

benchmark
view benchmark

commit
view commit

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

Bundle size comparison (import * as ... in PR vs import * as ... in target):

🟢 Decreased ➖ Unchanged 🔴 Increased ❔ Unknown
0 355 0 0

No notable changes.

If you wish to run a comparison for other, slower bundlers, run the 'Tree-shake test' from the GitHub Actions menu.

@github-actions

github-actions Bot commented Nov 5, 2026

Copy link
Copy Markdown

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 [1.09, 2.33, 5.15, 8.54, 8.88, 15.43, 29.37, 30.90]
  line [1.10, 2.28, 4.73, 8.27, 8.56, 12.94, 28.88, 32.00]
  line [1.06, 2.20, 4.93, 7.58, 9.18, 12.80, 28.74, 25.48]
Loading
---
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.32, 0.56, 0.76, 0.91, 1.31, 1.35, 1.58, 1.74]
  line [0.31, 0.54, 0.73, 0.91, 1.25, 1.29, 1.50, 1.73]
  line [0.31, 0.55, 0.79, 0.97, 1.34, 1.35, 1.62, 1.76]
Loading
---
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 [1.01, 2.67, 4.17, 8.28, 14.81, 29.99, 66.12, 130.20]
  line [0.97, 2.33, 5.04, 8.88, 15.19, 29.82, 63.24, 130.57]
  line [1.05, 2.90, 4.75, 8.24, 14.95, 29.36, 63.72, 126.07]
Loading

@reczkok

reczkok commented Nov 6, 2026

Copy link
Copy Markdown
Contributor Author

clanker

@reczkok reczkok closed this Nov 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant