Skip to content

feat: Add 3D mesh endpoint to @typegpu/geometry - #2997

Open
reczkok wants to merge 18 commits into
mainfrom
feat/geometry-meshes
Open

reczkok wants to merge 18 commits into
mainfrom
feat/geometry-meshes

Conversation

@reczkok

@reczkok reczkok commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Adds meshes to @typegpu/geometry. Geometry is a vertex schema + vertexAt (and optionally indexAt), so primitives and transformations compose before anything needs a buffer

  • plane, box, sphere, cylinder, torus + custom parametric surfaces
  • transform, map, attach extra attributes and concat geometries
  • evaluate directly in shaders, bake into buffers or bake just the indices while keeping vertices procedural
  • baked meshes can be updated through compute and used as geometry inputs again
  • includes an animated primitives example with shadows

Interactive cloth example and docs will follow in separate PRs

@github-actions

github-actions Bot commented Sep 8, 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/typegpu@95e1ad5b475d387e4be815d777fd62084225691d
https://pkg.pr.new/software-mansion/TypeGPU/@typegpu/react@95e1ad5b475d387e4be815d777fd62084225691d

benchmark
view benchmark

commit
view commit

@github-actions

github-actions Bot commented Sep 8, 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 Sep 8, 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.10, 2.31, 4.72, 7.36, 8.25, 13.13, 25.75, 25.85]
  line [1.04, 2.09, 4.47, 7.36, 8.22, 13.16, 27.02, 27.90]
  line [0.96, 1.99, 4.57, 6.88, 8.32, 13.29, 26.47, 27.50]
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.35, 0.61, 0.77, 0.92, 1.29, 1.28, 1.50, 1.75]
  line [0.32, 0.53, 0.73, 0.86, 1.22, 1.33, 1.52, 1.67]
  line [0.33, 0.57, 0.73, 0.90, 1.18, 1.23, 1.46, 1.67]
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 [0.83, 2.22, 3.88, 7.59, 13.52, 28.41, 58.55, 121.08]
  line [0.87, 2.21, 3.99, 8.15, 15.02, 27.56, 58.74, 120.44]
  line [1.07, 2.45, 4.29, 7.80, 13.28, 27.54, 59.57, 119.95]
Loading

@deluksic

deluksic commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

Please take a look at my (almost finished) branch, if you would like to incorporate any of it, or steal techniques

https://github.com/deluksic/TypeGPU/tree/de/geometry-spheres (it is more than spheres)

@reczkok
reczkok force-pushed the feat/geometry-meshes branch from 58c4fcb to 043c0ab Compare October 22, 2026 11:58
@reczkok
reczkok marked this pull request as ready for review October 22, 2026 11:59
Copilot AI balanced review requested due to automatic review settings October 22, 2026 11:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Baked-index consistency and primitive boundaries during concatenation need correction before approval.

2 open issues
1 file not reviewed
  • pnpm-lock.yaml: Generated file
What changed in this PR

Adds a composable meshes API to @typegpu/geometry, supporting procedural 3D geometry, shader evaluation, and GPU buffer baking.

Changes:

  • Introduces primitives, parametric surfaces, and geometry transformations and composition.
  • Adds full and index-only baking, asynchronous preparation, and compute-driven vertex updates.
  • Adds tests and an animated mesh gallery with shadows.
File Description
pnpm-lock.yaml Records geometry testing dependencies.
packages/​typegpu-geometry/​vitest.config.mts Configures shader-aware unit tests.
packages/​typegpu-geometry/​tsconfig.test.json Enables test type checking.
packages/​typegpu-geometry/​tests/​updates.test.ts Tests baking updates, bindings, and dispatch.
packages/​typegpu-geometry/​tests/​mesh.test.ts Tests primitives and geometry composition.
packages/​typegpu-geometry/​tests/​fixtures.ts Adds shared geometry and shader fixtures.
packages/​typegpu-geometry/​tests/​bake.test.ts Tests baking, buffer ownership, and pipeline injection.
packages/​typegpu-geometry/​src/​mesh/​primitives.ts Implements five primitives and surface evaluators.
packages/​typegpu-geometry/​src/​mesh/​parametric.ts Implements grid sampling and estimated normals.
packages/​typegpu-geometry/​src/​mesh/​math.ts Adds coordinate-frame helpers.
packages/​typegpu-geometry/​src/​mesh/​index.ts Exports the mesh API.
packages/​typegpu-geometry/​src/​mesh/​geometry.ts Defines geometry contracts and vertex layouts.
packages/​typegpu-geometry/​src/​mesh/​fill.ts Implements compute-based buffer filling.
packages/​typegpu-geometry/​src/​mesh/​combinators.ts Implements transforms, mapping, attachment, and concatenation.
packages/​typegpu-geometry/​src/​mesh/​bake.ts Implements baking and baked-mesh operations.
packages/​typegpu-geometry/​src/​index.ts Exposes the meshes namespace.
packages/​typegpu-geometry/​package.json Adds test scripts and dependencies.
apps/​typegpu-docs/​tests/​individual-example-tests/​mesh-primitives.test.ts Tests example shader resolution.
apps/​typegpu-docs/​src/​examples/​rendering/​mesh-primitives/​spring.ts Defines an animated procedural spring.
apps/​typegpu-docs/​src/​examples/​rendering/​mesh-primitives/​shading.ts Implements lighting and shadow sampling.
apps/​typegpu-docs/​src/​examples/​rendering/​mesh-primitives/​meta.json Registers the example metadata.
apps/​typegpu-docs/​src/​examples/​rendering/​mesh-primitives/​index.ts Sets up rendering, animation, and cleanup.
apps/​typegpu-docs/​src/​examples/​rendering/​mesh-primitives/​index.html Provides the example canvas.
apps/​typegpu-docs/​src/​examples/​rendering/​mesh-primitives/​geometry.ts Adds placement and color helpers.
apps/​typegpu-docs/​src/​examples/​rendering/​mesh-primitives/​gallery.ts Arranges the primitive gallery.
apps/​typegpu-docs/​src/​examples/​rendering/​mesh-primitives/​blob.ts Defines an animated parametric blob.

Review effort: Balanced

Comment thread packages/typegpu-geometry/src/mesh/bake.ts
Comment thread packages/typegpu-geometry/src/mesh/combinators.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No critical issues — two consistency/robustness points inline.

Reviewed changes

Reviewed the full meshes namespace in @typegpu/geometry and the new mesh-primitives example at 043c0ab.

  • meshes geometry model — Geometry/IndexedGeometry (schema + vertexAt/indexAt), Surface default schema, isIndexed, WeakMap-cached vertex layouts.
  • Primitives & parametric — plane/box/sphere/cylinder/torus, the surfaces helpers, and grid sampling with finite-difference normals.
  • Combinators — transform (inverse-transpose normal matrix + winding reversal on reflection), map, attach, unindexed, concat.
  • Baking — bake/bakeAsync and bakeIndices/bakeIndicesAsync, compute-driven fills, buffer ownership and destroy semantics.
  • Example & tests — animated shadowed gallery example; CPU geometry/winding tests, bake and update tests, and the example WGSL snapshot.

Verification: packages/typegpu-geometry tests (26 passed), the mesh-primitives example snapshot (1 passed), test:types, oxfmt --check, and oxlint --max-warnings=0 all pass locally.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

Comment thread packages/typegpu-geometry/src/mesh/bake.ts
Comment thread packages/typegpu-geometry/src/mesh/combinators.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in the latest commits — one earlier thread (the concat boundary) still stands; the rest of this delta is clean.

Reviewed changes

Reviewed the 7 commits pushed since the prior pullfrog review at 043c0ab (350d0ca6..0b0b53a).

  • Build migrated to tsdown — replaced unbuild with a tsdown.config.ts and updated package.json exports/dependencies to match the sibling packages.
  • bevel join added — new lines/joins/bevel.ts, registered in joins, and miterJoinLimitSlot is now exported.
  • Normal matrix hardened — transform now uses a sign-corrected cofactor matrix instead of the inverse transpose, so flattening transforms keep valid normals and collapsed normals stay zero.
  • Fill recording unified — fill.ts gained recordFills, so bake/updateVertices record vertex and index fills into a single compute pass and honour a caller-supplied encoder/pass without submitting.
  • Buffer offsets added — bake/bakeIndices accept baseVertex/firstIndex to pack several meshes into shared buffers; the baked indexAt now reads the baked index buffer (resolves the earlier stale-indexAt thread).
  • sphere options renamed — segments/rings → radialSegments/heightSegments, matching cylinder; callers and tests updated.
  • Tests — new lines.test.ts, offset-baking cases in bake.test.ts, and a flattening-normals case in mesh.test.ts.

Verification at 0b0b53a: @typegpu/geometry tests (31 passed), the mesh-primitives example snapshot (1 passed), test:types, and the tsdown build all pass.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in the re-reviewed commits — the earlier concat boundary thread remains open, so this is not an approval.

Reviewed changes

Re-reviewed the 7 commits pushed after 043c0ab0 (through 0b0b53a1) that make up this synchronize event's delta.

  • Build migrated to tsdown — unbuild and build.config.ts were replaced by a tsdown.config.ts using unplugin-typegpu/rolldown and unbundle: true, matching @typegpu/sdf and @typegpu/sort, with the dependent package.json exports/main and the lockfile updated.
  • bevel join added — lines/joins/bevel.ts was added and registered in joins, and miterJoinLimitSlot is now re-exported.
  • Normal matrix hardened — transform now uses a sign-corrected cofactor matrix, so flattening transforms keep valid normals and reflected normals stay outward.
  • Fill recording unified — fill.ts gained RecordOptions and recordFills, so bake and updateVertices record vertex and index fills into a single compute pass and honour a caller-supplied encoder or pass without submitting.
  • Buffer offsets added — bake/bakeIndices accept baseVertex/firstIndex, and the baked indexAt reads the baked index buffer.
  • sphere options renamed — segments/rings were renamed to radialSegments/heightSegments, matching cylinder.
  • Tests — added lines.test.ts, plus offset-baking and flattening-normals cases.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in the commits reviewed since the last pullfrog review — the earlier concat boundary thread remains open, so this is not an approval.

Reviewed changes

Re-reviewed the 7 commits pushed after 043c0ab (through 0b0b53a1): the tsdown build migration, the bevel join, the hardened normal matrix, unified fill recording, buffer-offset baking, and the sphere option rename.

  • Build migrated to tsdown — build.config.ts was replaced by tsdown.config.ts (unplugin-typegpu/rolldown, unbundle: true, ESM), with the dependent package.json publish exports/main and dev dependencies updated to match @typegpu/sdf/@typegpu/sort.
  • bevel join added — lines/joins/bevel.ts cuts the outside of a turn with a straight edge, and miterJoinLimitSlot is now re-exported from the joins barrel.
  • Normal matrix hardened — transform now builds a sign-corrected cofactor matrix instead of the inverse transpose, so transforms that flatten or reflect a shape keep valid outward normals rather than collapsing or flipping them.
  • Fill recording unified — fill.ts gained RecordOptions/recordFills, so bake and updateVertices record vertex and index fills into a single compute pass and honour a caller-supplied encoder or pass without submitting.
  • Buffer offsets added — bake/bakeIndices accept baseVertex/firstIndex so several meshes can pack into shared buffers, and the baked indexAt now reads the baked index buffer.
  • sphere options renamed — segments/rings became radialSegments/heightSegments, matching cylinder; callers and tests were updated.
  • Tests — added lines.test.ts (bevel geometry and slot configuration) plus offset-baking, single-pass recording, and flattening-normals cases.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ℹ️ No new issues in the follow-up commit — the earlier concat boundary thread remains open, so this is not an approval.

Reviewed changes

Reviewed the single commit pushed after the prior pullfrog review (0b0b53a → 95e1ad5b).

  • Example helpers widened — paint/place in the mesh-primitives example now take d.InferInput<d.Vec3f> (arrays, tuples, or vectors) and normalise to d.vec3f internally, so callers pass plain arrays and gallery.ts no longer imports d.

Pullfrog  | Fix it ➔ | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@aleksanderkatan aleksanderkatan left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Enormous PR!

I leave mostly nits and questions

/** Default vertex schema for primitives */
export const Surface = d.struct({ position: d.vec3f, normal: d.vec3f, uv: d.vec2f });

export type Topology = 'triangle-list' | 'line-list' | 'point-list';

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about 'triangle-strip'? I assume that some combinators would break.
Can we make it work in the future, and throw in combinators that don't support it? Or is it not worth it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah i kinda ignored it since it introduces a lot of complexity and tbh I didn't see it a lot in the wild, whether is worth it is a good question I do not have a good answer too


export type Topology = 'triangle-list' | 'line-list' | 'point-list';

export interface Geometry<V extends d.AnyWgslData = typeof Surface> {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: d.AnyWgslData lets through types that won't work, like pointers or comparison sampler, can we tighten this type?
I'd allow here anything that can be returned by 'use gpu' functions. bool won't work when baked into a buffer, but it will be fine if called in runtime.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add analogous tests for bakeIndices?

Comment on lines +6 to +7
function windingAgreesWithNormals(g: meshes.IndexedGeometry) {
for (let t = 0; t < g.indexCount; t += 3) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit (so that we don't accidentally add a wrong shape to it.each)

Suggested change
function windingAgreesWithNormals(g: meshes.IndexedGeometry) {
for (let t = 0; t < g.indexCount; t += 3) {
function windingAgreesWithNormals(g: meshes.IndexedGeometry) {
if (g.topology !== 'triangle-list') throw new Error('Invalid topology.');
for (let t = 0; t < g.indexCount; t += 3) {

import { d, tgpu, type TgpuVertexLayout } from 'typegpu';

/** Default vertex schema for primitives */
export const Surface = d.struct({ position: d.vec3f, normal: d.vec3f, uv: d.vec2f });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This type does not really make sense for non 'triangle-list' topologies, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean does it work? Yes. Does it make a lot of sense? Nah not really but you cannot do much with the line and point topologies in webgpu anyways

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Free test

  it('throws when attaching a field that is already present', () => {
    const Paint = d.struct({ position: d.vec3f });
    expect(() =>
      meshes.attach(meshes.plane(), Paint, (s) => {
        'use gpu';
        return Paint({ position: d.vec3f(s.uv, 0) });
      }),
    ).toThrowErrorMatchingInlineSnapshot(
      `[Error: attach cannot replace the existing field 'position'; use map instead]`,
    );
  });

const fan = radialSegments * 3;

return {
...grid,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: A comment explaining why we override indexing that is technically correct would be nice

heightSegments = 1,
caps = true,
}: CylinderOptions = {}): IndexedGeometry {
const side = sampleGrid(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use surfaces.cylinder here? Same question about surfaces.torus

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, we can - will do


return {
initAsync: async () => {
if (count > 0) await pipeline.initAsync();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: We had a soft convention of always including blocks in if, while etc., are we dropping it?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, will add them

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

Reviewed the delta since the prior pullfrog review at 95e1ad5b — the single "address review" commit 4330f17.

  • Mesh generics tightened — ConstructibleSchema (Extract<d.AnyWgslData, () => unknown>) moved into geometry.ts, exported, and applied in place of d.AnyWgslData across Geometry, IndexedGeometry, isIndexed, layoutOf, map, unindexed, concat, and the bake/bakeIndices family.
  • Primitives reuse surface helpers — cylinder's side and torus now call surfaces.cylinder/surfaces.torus; verified algebraically identical to the inline math they replace, including the surfaces.torus(u, v, 0, 1) unit normal. sphere now documents why it skips the pole triangles.
  • Brace convention restored — single-statement if/await bodies in fill.ts, bake.ts, primitives.ts, and combinators.ts now use block braces.
  • Tests added — bakeIndices analogues (procedural vertexAt, index-only injection, offset baking, encoder recording), an attach field-collision test, and a topology guard in windingAgreesWithNormals.

Verification at 4330f17: @typegpu/geometry tests (36 passed), the mesh-primitives example snapshot (1 passed), and pnpm --filter @typegpu/geometry test:types (both tsconfigs) all pass.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

This branch has not been deployed

No deployments
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.

4 participants