Skip to content

Officially document + prove support for KHR_mesh_primitive_restart#13634

Open
markschlosseratbentley wants to merge 5 commits into
mainfrom
KHR_mesh_primitive_restart
Open

Officially document + prove support for KHR_mesh_primitive_restart#13634
markschlosseratbentley wants to merge 5 commits into
mainfrom
KHR_mesh_primitive_restart

Conversation

@markschlosseratbentley

@markschlosseratbentley markschlosseratbentley commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Description

Officially clarifies support for KHR_mesh_primitive_restart in CesiumJS. This extension allows many line strips, line loops, triangle strips, or triangle fans to be batched into a single glTF primitive, with inline "restart" index values (the max value for the index component type) separating the topological primitives.

Support was already de facto working. The loader passes inline restart values through untouched, WebGL 2 interprets them natively (PRIMITIVE_RESTART_FIXED_INDEX is always on for indexed draws), and the extensionsRequired whitelist entry was added in #13478. This PR makes support official with no loader changes. See actual changes in below section.

image

Screenshot demonstrating KHR_mesh_primitive_restart functioning properly in a CesiumJS Sandcastle.

Dev Sandcastle

Check out the deployed Dev Sandcastle here.

Changes

  • Adds two GltfLoaderSpec tests verifying restart values survive loading intact and the styling data loads alongside them.
  • Updates Model.js JSDoc: adds KHR_mesh_primitive_restart to the supported extensions list (with a WebGL 1 caveat, see below) and fixes the stale EXT_mesh_primitive_restart link.
  • Adds a development sandcastle using the official sample assets from the Khronos spec PR, with a dropdown for the 5 sample models and a WebGL 2 / WebGL 1 context toggle to demonstrate the limitation below.

Known limitations

  • WebGL 1: restart relies on WebGL 2's built-in primitive-restart behavior. On WebGL 1 contexts (requestWebgl1: true or automatic fallback), restart indices are treated as ordinary vertex indices and affected primitives render incorrectly. A CPU-side index-splitting fallback is possible but out of scope for at least this PR. This limitation is documented in the Model.js JSDoc; demonstrable via the dev sandcastle's context toggle.

Issue number and link

Fixes #13629

Testing plan

  • npx gulp test --includeName "KHR_mesh_primitive_restart" — 2 new specs pass; the 6 existing EXT_mesh_primitive_restart specs still pass.
  • Ran the new dev sandcastle and visually verified all 5 sample models (line strips, line loops, triangle strips, triangle fans, and the Hilbert curve with 32-bit indices) render correctly on WebGL 2, and that the WebGL 1 toggle shows a failure to render.

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

AI acknowledgment

  • I used AI to generate content in this PR
  • If yes, I have reviewed the AI-generated content before submitting

If yes, I used the following Tools(s) and/or Service(s):

GitHub Copilot

If yes, I used the following Model(s):

Claude Fable 5

… and add a Sandcastle for visual verification of support.
@github-actions

Copy link
Copy Markdown
Contributor

Thank you for the pull request, @markschlosseratbentley!

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


const models = {
"Hilbert Curve (LINE_STRIP, 32-bit indices)": {
url: "../../SampleData/models/PrimitiveRestart/PrimitiveRestartLineStrip.glb",

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 touched base with @donmccurdy on the inclusion of these test assets in this dev Cesium Sandcastle and he seemed to think it was fine to include them. However, we were not sure how Cesium-included assets are licensed in general. Anyone have any insights?

Context: these test assets were created by @donmccurdy and were provided as test data for this Khronos spec PR: KhronosGroup/glTF#2569

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.

I don't know the answer, and am not a lawyer, but would think that unless there is specific license information for a model, the "root" license (Apache 2) would apply.

The main question might therefore be: Under which license would Don like to publish them?

(The point is: I think that there could be some README.md or LICENSE.txt in that folder with whatever license should apply. At least that's done for some of the 'Specs' models, e.g. https://github.com/CesiumGS/cesium/blob/0d0c35fad1cc05ed0560f0d1ec6a9197baaef84e/Specs/Data/Models/glTF-2.0/AnimatedTriangle/README.md )

And now for something completely different: I don't see a difference between the WebGL 1 and 2 versions in terms of primitive restart. (There is a difference, in terms of (anti)aliasing, so I think that they are indeed different contexts, but the geometry looks the same for me...)

Cesium Restart Sample

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.

@javagl Interesting, thanks for the webgl1 + 2 observation. I tested on Firefox on macOS. What browser/OS are you using?

@donmccurdy donmccurdy Jul 17, 2026

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.

The main question might therefore be: Under which license would Don like to publish them?

I'm comfortable contributing these under any license conventional for non-code assets in CesiumJS. I might suggest CC BY if there's no prior convention.

If there are no objections, I can just add that license to these files as XMP data (gltf-transform xmp in.glb out.glb), no need for sidecar files.

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.

@markschlosseratbentley This was on Windows 11 with some AMD Radeon (Laptop) card (on Chrome, if that might matter). Of course, it's nothing that affects this PR. It might even be an AMD driver bug 🤪 and things unexpectedly working is a nice "problem" to have.

@donmccurdy No strong preference (details are up to you anyhow) but... 1. for that license information to even be accessible, one needs a library/loader/viewer that supports the extension, and 2. one has to explicitly and dedicatedly look for it, exactly there (which isn't self-evident - I cannt remember having seen license infos embedded in XMP).

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.

Small addendum: I haven't looked at the details of the models, but for the other models, there is the expected difference (i.e. the rendering is "broken" on WebGL 1). Only the "hilbert curve" is working. (Are the other ones using non-32-bit indices by any chance? That might be a path to an explanation...)

@markschlosseratbentley markschlosseratbentley Jul 17, 2026

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 believe the Hilbert curve is the only UNSIGNED_INT model and others are 16-bit, which indeed could explain things. After some research, on ANGLE/D3D11 (working theory):

  • D3D11 strip-cut appears to be always on (0xFFFF/0xFFFFFFFF per index format, no obvious disable API). Found some docs here.
  • WebGL 1 has no primitive restart, so 0xFFFF must act as a normal vertex index, but D3D11 would cut on it. ANGLE therefore rewrites 16-bit buffers containing 0xFFFF as 32-bit, casting each 0xFFFF to 0x0000FFFF (see ANGLE UsePrimitiveRestartWorkaround code here). No cut fires; the value is fetched as vertex 65535 (out of range), resulting in broken rendering...
  • UNSIGNED_INT is excluded from that workaround so 0xFFFFFFFF hits the always-on cut and restart coincidentally works.

Based on this, WebGL 1 behavior is implementation-defined / undefined: macOS Firefox/Chrome render nothing, but Safari renders correctly (Metal probably behaves similar to D3D11 in this regard...).

I will update this PR's docs to clarify webgl1 behavior is "implementation-defined / undefined."

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.

That's quite some archeology there 🙂 Given that WebGL 1 support in CesiumJS is ... maybe not completely dropped, but quickly phasing out, and everything that is not running on Windows 3.1 and Mosaic supports WebGL 2 anyhow, it's just a matter of setting the expectations right for people with really old setups.

@donmccurdy donmccurdy Jul 19, 2026

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.

There's also .asset.extras.license, which might be more widely-used than the XMP extension (Sketchfab exports used extras, in particular). But no objection to a separate license file, in that case I'd suggest this one:

https://github.com/KhronosGroup/glTF-Sample-Assets/blob/main/LICENSES/CC-BY-4.0.txt

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.

Thanks @donmccurdy and @javagl. Based on this licensing discussion, I additionally followed what I found to be an existing repo convention: added a CC-BY 4.0 entry to the root LICENSE.md (matching other Apps/SampleData models like "Perc Lead Mine"). I did this because that LICENSE.md file is clearly distributed and accessible in a release zip of CesiumJS. Providing license alongside distribution for CC-BY 4.0 is required.

I also placed a README.md in the model folder, which hasn't always been done it seems, but makes sense for clarity, I think.

@donmccurdy I credited you in both places, so LMK if you have any issues with this.

* {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit/README.md|KHR_materials_unlit}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/pull/2569|KHR_mesh_primitive_restart}

@markschlosseratbentley markschlosseratbentley Jul 17, 2026

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.

Since this PR does not include any functional changes to CesiumJS, I did not see a need to include an entry in CHANGES.md, leaving it just to inline documentation here. However, I am open to adding a CHANGES.md entry if someone thinks it's necessary and/or desirable. @ggetz?

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.

I agree it isn't strictly necessary – but I'd be tempted to include it, as many CesiumJS users probably aren't aware of the extension otherwise and might benefit from knowing that it exists and CesiumJS supports it.

Something like:

- Added support for the [`KHR_mesh_primitive_restart`](https://github.com/KhronosGroup/glTF/pull/2569) glTF extension. [#13634](https://github.com/CesiumGS/cesium/pull/13634)

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.

Thanks for the feedback; I think this makes sense. Added.

@donmccurdy donmccurdy 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.

Left minor comments only - LGTM!

* {@link https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit/README.md|KHR_materials_unlit}
* </li>
* <li>
* {@link https://github.com/KhronosGroup/glTF/pull/2569|KHR_mesh_primitive_restart}

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.

I agree it isn't strictly necessary – but I'd be tempted to include it, as many CesiumJS users probably aren't aware of the extension otherwise and might benefit from knowing that it exists and CesiumJS supports it.

Something like:

- Added support for the [`KHR_mesh_primitive_restart`](https://github.com/KhronosGroup/glTF/pull/2569) glTF extension. [#13634](https://github.com/CesiumGS/cesium/pull/13634)

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.

Finalize CesiumJS support for KHR_mesh_primitive_restart

3 participants