Skip to content

BufferPrimitiveCollection: Add 3D renderers#13213

Merged
lilleyse merged 26 commits intomainfrom
donmccurdy/feat/bufferprimitivecollection-render-gl
Mar 11, 2026
Merged

BufferPrimitiveCollection: Add 3D renderers#13213
lilleyse merged 26 commits intomainfrom
donmccurdy/feat/bufferprimitivecollection-render-gl

Conversation

@donmccurdy
Copy link
Copy Markdown
Member

@donmccurdy donmccurdy commented Feb 17, 2026

Description

Highlights

Implements default renderers on top of the data model proposed in #13212: BufferPointCollection, BufferPolylineCollection, and BufferPolygonCollection. Styling is limited right now, and does not include clamping to terrain or height reference offsets.

Implementation

Each of the three (point/line/polygon) renderers follows exactly the same rendering process:

  1. (first render) Allocate typed arrays for index and vertex attributes
  2. Fill typed arrays from collection's dirty range
  3. Create buffers on first render, or update the dirty range of existing buffers on subsequent renders
  4. (first render) Create render state, uniforms, and shaders
  5. Add draw command to frame state

Because BufferPrimitiveCollections have a fixed capacity, it's very efficient to allocate GPU resources for that capacity on first render, and then to add or update primitives incrementally, repeating steps 2–5 above. On my laptop, for example, it's certainly feasible to update position and color of 20,000 small polylines each frame at 60 FPS. More detailed benchmarks in #13156 (comment).

Notes

I expect we'll want to add more features to these renderers over time, however ... an important design goal for this PR and #13212 is that the data models for each primitive type can be reused for multiple renderers. Draping polylines and polygons on terrain will require a distinct renderer.

I have mixed feelings about putting appearance-related properties directly on the BufferPrimitive subclasses, as I've done here with methods like polyline.setOutlineColor(color). If these APIs support multiple renderers, it might be nice to have some way to extend styling features without extending the core data model, like: (reverted)

This would require that (1) all primitives in a collection share the same material type, but not the same material uniforms, and (2) that the collection be able to serialize the material into the buffer similarly to other per-primitive properties. I've left that as a potential future improvement for now.

Aside - I've also been wondering about putting .showOffset / .showCount properties on each collection, which would enable scrubbing through a collection (sorted by time) at basically zero performance cost. But I'm not sure how/if that pattern would work in clamped renderers, so it's not included here yet.

Issue number and link

Testing plan

Added unit tests for rendering all topologies. Shared sandcastles in comments below.

Author checklist

  • I have submitted a Contributor License Agreement
  • I have added my name to CONTRIBUTORS.md
  • I have updated CHANGES.md with a short summary of my change
  • I have added or updated unit tests to ensure consistent code coverage
  • I have updated the inline documentation, and included code examples where relevant
  • I have performed a self-review of my code

PR Dependency Tree

This tree was auto-generated by Charcoal

@github-actions
Copy link
Copy Markdown
Contributor

Thank you for the pull request, @donmccurdy!

✅ We can confirm we have a CLA on file for you.

@donmccurdy donmccurdy self-assigned this Feb 17, 2026
@donmccurdy donmccurdy force-pushed the donmccurdy/feat/bufferprimitivecollection branch from 7559040 to 6773154 Compare February 17, 2026 20:04
@donmccurdy donmccurdy force-pushed the donmccurdy/feat/bufferprimitivecollection-render-gl branch 2 times, most recently from e665b09 to a4bf666 Compare February 17, 2026 20:51
@donmccurdy donmccurdy force-pushed the donmccurdy/feat/bufferprimitivecollection branch from d3bce7c to f3706de Compare February 17, 2026 21:41
@donmccurdy donmccurdy force-pushed the donmccurdy/feat/bufferprimitivecollection-render-gl branch from a4bf666 to e274227 Compare February 17, 2026 21:41
- name: format code
run: npm run prettier-check
- name: build
run: npm run build
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We have to build before running npm run tsc so the type checker can see built outputs from *.glsl files.

* @param {number} vertexOffset
* @param {number} vertexCount
*/
VertexArray.prototype.copyAttributeFromRange = function (
Copy link
Copy Markdown
Member Author

@donmccurdy donmccurdy Feb 18, 2026

Choose a reason for hiding this comment

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

These methods could be moved somewhere else, but seemed like they might be generally helpful? The use case here is to hold an array of the same size as the buffer in memory, and to re-upload only the dirty range after updates.

Avoids memory allocation (and potential GC stalls) during rendering. This would be harder to guarantee if the array were created dynamically in the render loop, or sized to only include the updated range of elements.

@donmccurdy donmccurdy marked this pull request as ready for review February 18, 2026 17:09
@donmccurdy donmccurdy changed the title BufferPrimitiveCollection: Add GL primitive renderers BufferPrimitiveCollection: Add primitive renderers Feb 25, 2026
@donmccurdy donmccurdy force-pushed the donmccurdy/feat/bufferprimitivecollection branch from f3706de to f530c58 Compare February 27, 2026 21:27
@donmccurdy donmccurdy force-pushed the donmccurdy/feat/bufferprimitivecollection-render-gl branch 2 times, most recently from bb865d4 to 2a2eebe Compare February 27, 2026 22:08
@donmccurdy donmccurdy force-pushed the donmccurdy/feat/bufferprimitivecollection branch from 0bc2cbe to af8a62d Compare February 27, 2026 22:08
@donmccurdy donmccurdy force-pushed the donmccurdy/feat/bufferprimitivecollection-render-gl branch from 2a2eebe to 48874f5 Compare February 27, 2026 22:08
@donmccurdy donmccurdy changed the title BufferPrimitiveCollection: Add primitive renderers BufferPrimitiveCollection: Add 3D renderers Mar 2, 2026
@donmccurdy donmccurdy force-pushed the donmccurdy/feat/bufferprimitivecollection branch 2 times, most recently from 56da7cf to 496f6eb Compare March 2, 2026 19:42
@donmccurdy donmccurdy force-pushed the donmccurdy/feat/bufferprimitivecollection-render-gl branch from a410541 to 04babfd Compare March 2, 2026 19:42
Base automatically changed from donmccurdy/feat/bufferprimitivecollection to main March 3, 2026 15:55
@donmccurdy
Copy link
Copy Markdown
Member Author

Commands were created, but nothing rendered until I transformed the collection bounding sphere to world space before assigning it to DrawCommand.boundingVolume.

@danielzhong in earlier versions I'd declared collection.modelMatrix, but did not implement it, and the property was removed from #13212 before merging. Not all existing primitive collections have this property so I just wasn't sure it was needed — when used in 3D Tiles, is the model matrix used to represent transforms present in the glTF scene's nodes? Or another transform implicit in the tiling scheme? We can certainly add it back if so. /cc @lilleyse

@donmccurdy
Copy link
Copy Markdown
Member Author

donmccurdy commented Mar 5, 2026

I'm considering changing the styling API from this pattern...

collection.get(i, point);
point.pixelSize = 6;
point.setColor(Color.WHITE);
point.outlineWidth = 2;
point.setOutlineColor(Color.RED);

... to something like this ...

collection.get(i, point);

const material = new BufferPointOutlineMaterial({
  pixelSize: 6,
  color: Color.WHITE,
  outlineWidth: 2,
  outlineColor: Color.RED
});

point.setMaterial(material);

All primitives in a collection must share the same material type, but each primitive may have different material properties without affecting batching. Calls to point.setMaterial(material) would not store a reference to the material class, but would immediately pack the material's uniforms into a buffer. The collection would be initialized with the material type (or a default material type) so it knows how to pack/unpack and render the material.

Reasoning:

  1. It will be difficult to match the styling features of existing CesiumJS APIs (outline, glow, dashed lines, arrows...) if all of these options must be added to the underlying BufferPrimitive class.
  2. Ideally BufferPrimitive classes and existing point/polyline/polygon APIs could share the same material implementations.

I don't think (2) fits our short-term delivery timelines... it's not obvious how to batch effectively with existing materials using distinct uniforms, and existing materials don't appear to cover all primitive types (points, lines, polygons). So we'll likely need separate classes e.g. BufferPolylineOutlineMaterial for now, hopefully to be consolidated later?

/cc @danielzhong @lilleyse

@donmccurdy
Copy link
Copy Markdown
Member Author

Updates:

  • Reverted styling API from BufferPrimitive classes (I'd included a draft styling API in earlier versions of this PR)
  • Restored collection.modelMatrix property
  • Added collection.boundingVolumeWC property
  • Added caching for DrawCommand in render context

@danielzhong @lilleyse I think this PR should be ready for review when you have the chance! There's still a bit missing here: certain styling features are implemented in shaders but readonly in JS, blending and polygon offset aren't exposed, modelMatrix and culling could use more testing. But I think this might be a good scope to merge next, then it's easier to continue with these next steps in parallel:

  • loading vector tiles and previewing in Cesium.js
  • adding the styling API
  • adding the GPU lookup rendering API

@donmccurdy
Copy link
Copy Markdown
Member Author

donmccurdy commented Mar 9, 2026

@danielzhong @lilleyse Ready for review when either of you have the chance, thanks!

I have started unit tests and a .destroy() implementation to clean up GPU resources in a separate local branch, but I think that will likely be another PR. Hoping to get this PR in sooner, so that other branches can have visual output without needing to branch off this PR.

UPDATE: Added unit tests and GPU resource cleanup / .destroy() to this PR.

// @ts-expect-error Requires https://github.com/CesiumGS/cesium/pull/13203.
usage: BufferUsage.STATIC_DRAW,
// @ts-expect-error Requires https://github.com/CesiumGS/cesium/pull/13203.
indexDatatype: IndexDatatype.UNSIGNED_INT,
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.

Would it be possible to use a smaller indexDatatype depending on vertex count?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

So probably not exposing a separate indexDatatype option but just choosing based on the max vertex count allocated? That works yes, done! ✅

Aside: In-memory hole and triangle indices are (currently) offsets relative to each polygon's own vertices. So technically we could use 'byte' index types if no single polygon has >255 vertices. But (1) the collection doesn't currently know the per-polygon max vertex count, just the per-collection max vertex count, and (2) I can imagine we might make these offsets absolute within the collection later, depending on the vector tile format and loading process.

@donmccurdy
Copy link
Copy Markdown
Member Author

donmccurdy commented Mar 10, 2026

@lilleyse
Copy link
Copy Markdown
Contributor

The latest changes look good. And thanks for sending over the sandcastles, the GeoJSON one is super fast!

@lilleyse lilleyse added this pull request to the merge queue Mar 11, 2026
Merged via the queue into main with commit 7878464 Mar 11, 2026
9 checks passed
@lilleyse lilleyse deleted the donmccurdy/feat/bufferprimitivecollection-render-gl branch March 11, 2026 15:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants