Skip to content

Add KHR_mesh_primitive_restart#2569

Open
donmccurdy wants to merge 7 commits into
KhronosGroup:mainfrom
CesiumGS:donmccurdy/KHR_mesh_primitive_restart
Open

Add KHR_mesh_primitive_restart#2569
donmccurdy wants to merge 7 commits into
KhronosGroup:mainfrom
CesiumGS:donmccurdy/KHR_mesh_primitive_restart

Conversation

@donmccurdy

@donmccurdy donmccurdy commented Apr 16, 2026

Copy link
Copy Markdown
Member

Motivation

glTF 2.0 includes seven primitive modes:

  • 0 POINTS
  • 1 LINES
  • 2 LINE_LOOP
  • 3 LINE_STRIP
  • 4 TRIANGLES
  • 5 TRIANGLE_STRIP
  • 6 TRIANGLE_FAN

Four of these (LINE_LOOP, LINE_STRIP, TRIANGLE_STRIP, TRIANGLE_FAN) define topologies that may contain any number of vertices. Each glTF mesh primitive defines a single contiguous topology for these types, because primitive restart values — which would instruct the graphics API to begin a new topological primitive, without a new draw call — are disallowed by the core specification for maximum compatibility (see: WebGL 1).

This PR proposes a KHR_mesh_primitive_restart extension, which can be added to any glTF asset, removing the restriction against primitive restart values. The mechanism and implementation are intentionally very similar to KHR_mesh_quantization, which removes restrictions on particular vertex attribute component types.

Primitive restart values allow a scene to contain arbitrary numbers of line loops, line strips, triangle strips, and triangle fans, without requiring arbitrary numbers of mesh primitives, along with costly processing and draw calls. This also greatly reduces the amount of JSON (primitive and accessor) definitions required to represent such scenes.

Background

The existing EXT_mesh_primitive_restart allows primitive restart values, but does so by encoding each primitive topology as a separate glTF mesh primitive, defined in JSON and having a distinct indices accessor. The extension adds indices with restart values as metadata, allowing runtimes to stitch these primitives together as a single draw call, but the runtime must still download and parse data for the original N primitives. Unfortunately, the performance implications of this approach are not ideal. See CesiumGS#100.

EXT vs. KHR

I'm open to modifying the existing EXT_mesh_primitive_restart extension, but modifying an existing EXT extension would be an unusual step. While I believe it has only very limited implementations today, initial feedback has been on the side of avoiding changes to an existing EXT extension.

For that reason, and hoping the approach here might be broadly useful for anyone using these primitive topologies in glTF 2.0, I'm proposing a simplified/optimized approach as a new extension, KHR_mesh_primitive_restart.

Fallback

If a runtime doesn't support primitive restart values, it would need to pre-process the index list. But this optimizes the extension for present-day and future use, where primitive restart support is standard, and imposes the cost of fallback only on the small (and shrinking) cases where primitive restart is not supported in hardware. I believe that's an important improvement for adoption and production use of the extension.

Fallback may not necessarily require separate draw calls for each topological primitive. Multi-draw Indirect (MDI) would be an alternative (if there exists a graphic API that supports MDI but not primitive restart values). I'm not familiar with how a path-tracing renderer would want to handle primitive restart indices.

Samples

PrimitiveRestartLineStrip.glb.zip

hilbert3d

Figure: A Hilbert space-filling curve, rendered in 3D. The asset is structured as a glTF scene and a single mesh primitive, containing a series of LINE_STRIP topologies separated by primitive restart values. Created with glTF Transform.

In some viewers (including https://gltf-viewer.donmccurdy.com/) the model will display correctly without any modifications to support KHR_mesh_primitive_restart, because primitive restart is already enabled by default by the WebGL 2 API.

Additional, simpler samples for line strip, line loop, triangle strip, and triangle fan topologies:

The line strip and line loop examples currently work in three.js, but not the triangle strip and triangle fan samples. All four work in PlayCanvas, shown below:

topology preview
line strip line-strip
line loop line-loop
triangle strip triangle-strip
triangle fan triangle-fan

Implementations

Comment thread extensions/2.0/Khronos/KHR_mesh_primitive_restart/README.md Outdated
Comment thread extensions/2.0/Khronos/KHR_mesh_primitive_restart/README.md
Comment thread extensions/2.0/Khronos/KHR_mesh_primitive_restart/README.md Outdated
Comment thread extensions/2.0/Khronos/KHR_mesh_primitive_restart/README.md
Comment thread extensions/2.0/Khronos/KHR_mesh_primitive_restart/README.md Outdated
Comment thread extensions/2.0/Khronos/KHR_mesh_primitive_restart/README.md Outdated
This was referenced Apr 30, 2026
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@donmccurdy

Copy link
Copy Markdown
Member Author

I've updated the PR description including simple test assets for all four topology types (line strip, line loop, triangle strip, triangle fan). Will address open comments on the PR next - thanks all!

donmccurdy and others added 2 commits June 24, 2026 10:18
Co-authored-by: Adam Morris <adam@cesium.com>
- Prefer 'restart index' to 'restart value'
- Clarify that maximum possible index values are still restricted for list topologies
- Minor cleanup
Comment thread extensions/2.0/Khronos/KHR_mesh_primitive_restart/README.md Outdated
> `indices` accessor **MUST NOT** contain the maximum possible value for the component type used (i.e., 255 for unsigned bytes, 65535 for unsigned shorts, 4294967295 for unsigned ints).

This extension removes the restriction above, allowing `indices` accessors to contain the maximum possible value for the component type in select primitive draw modes, and specifying that these values indicate primitive restart commands.
This extension modifies the restriction above, allowing `indices` accessors to contain the maximum possible value for the component type in select primitive draw modes, and specifying that these values indicate primitive restart commands.

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.

This is a potentially a nitpick, but it is better to use "relaxes" instead of "modifies" here. Modifies is a neutral term, whereas relaxes is a more technically precise term. Stating, "relaxes the restriction," helps to clearly establish the change in constraints adding to overall clarity.

@donmccurdy donmccurdy Jun 24, 2026

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.

That's OK with me, so long as we think it's clear that "relaxes" does not imply removing the restriction entirely (list topologies are still subject to it). Personally I find "relaxes" to be clear in context. I'd also be fine with @lexaknyazev's earlier suggestion of "adjusts", but that's a similarly neutral term, so I'll let you both decide. :)

(For now, I've pushed a change to "relaxes")

@DRx3D DRx3D Jun 24, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@weegeekps : Copyright date should be the earliest date that the software is first publicly published (including distribution). It is allowed to include a range of years to indicate the most recent significant update. So something like: (c) 2025-2026 Bentley Systems, Incorporated. (or whatever is the correct years & owner). If there are multiple owners, then multiples lines are used.

@donmccurdy

Copy link
Copy Markdown
Member Author

In addition to the new samples linked above, I've pushed changes based on the suggestions and feedback. No intended changes in meaning or implementation, all changes are clarifications. I believe all outstanding feedback has been addressed, thanks @lexaknyazev and @weegeekps!

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.

5 participants