-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add KHR_mesh_primitive_restart #2569
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
donmccurdy
wants to merge
7
commits into
KhronosGroup:main
Choose a base branch
from
CesiumGS:donmccurdy/KHR_mesh_primitive_restart
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+77
−0
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
9264483
EXT_mesh_primitive_restart: Performance optimizations and schema updates
donmccurdy 8f3d099
Rename to KHR_mesh_primitive_restart
donmccurdy e5cf57b
Restore deleted files
donmccurdy 7be0b3c
Reformat implementation note
donmccurdy 3114ddb
Address PR feedback
donmccurdy d23a73d
Update copyright banner
donmccurdy 9811e84
Wording: 'modifies the restriction' -> 'relaxes the restriction'
donmccurdy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
77 changes: 77 additions & 0 deletions
77
extensions/2.0/Khronos/KHR_mesh_primitive_restart/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| <!-- | ||
| Copyright 2026 The Khronos Group Inc. | ||
| SPDX-License-Identifier: LicenseRef-KhronosSpecCopyright | ||
| --> | ||
|
|
||
| # KHR_mesh_primitive_restart | ||
|
|
||
| ## Contributors | ||
|
|
||
| - Paul Connelly, Bentley Systems, [@pmconne](https://github.com/pmconne) | ||
| - Don McCurdy, Bentley Systems, [@donmccurdy](https://github.com/donmccurdy) | ||
| - TODO | ||
|
|
||
| ## Status | ||
|
|
||
| Draft | ||
|
|
||
| ## Dependencies | ||
|
|
||
| Written against the glTF 2.0 spec. | ||
|
|
||
| ## Overview | ||
|
|
||
| "Primitive restart" is a feature of the input assembly stage that restarts the current primitive when the vertex index value is the maximum possible value for a given index buffer type. For example, the line strip primitive usually produces one continuous connected series of line segments, but with primitive restart enabled, a maximal vertex index value (e.g., 65535 for unsigned 16-bit integer indices) indicates the beginning of a new line string disconnected from those preceding it. Primitive restart can be useful for batching multiple line strips, line loops, triangle strips, or triangle fans into a single draw call. Alternatively, batching can be achieved by decomposing the primitives into lines or triangles, but this may introduce many redundant vertices, increase the amount of data required to describe the geometry, or discard useful topological information. | ||
|
|
||
| glTF 2.0 explicitly prohibits index buffers from containing maximal index values because support for primitive restart varies amongst graphics APIs. Per [section 3.7.2.1](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview) of the spec, | ||
|
|
||
| > `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 relaxes 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. | ||
|
|
||
| Because the extension does not provide a way to specify fallback indices without restart indices, assets that use the extension must specify it in `extensionsRequired` array - the extension is not optional. | ||
|
|
||
| > [!NOTE] | ||
| > Implementations on graphics APIs without primitive restart may still support the extension, by rewriting primitive indices. Compared to processing a larger number of primitives and accessors, the extension may still provide performance advantages even for these implementations. | ||
|
|
||
| ## Extending Mesh Indices | ||
|
donmccurdy marked this conversation as resolved.
|
||
|
|
||
| When the `KHR_mesh_primitive_restart` extension is supported, `indices` accessors may contain the maximum possible index values, as primitive restart indices, for the following primitive draw modes: | ||
|
|
||
| - `2 LINE_LOOP` | ||
| - `3 LINE_STRIP` | ||
| - `5 TRIANGLE_STRIP` | ||
| - `6 TRIANGLE_FAN` | ||
|
|
||
| The applicable primitive restart index is determined by the accessor component type: | ||
|
|
||
| | `accessor.componentType` | restart index | | ||
| | ---------------------------- | ------------- | | ||
| | `5121` (UNSIGNED_BYTE) | `255` | | ||
|
donmccurdy marked this conversation as resolved.
|
||
| | `5123` (UNSIGNED_SHORT) | `65535` | | ||
| | `5125` (UNSIGNED_INT) | `4294967295` | | ||
|
|
||
| ## Example | ||
|
|
||
| Consider the simple example of a pair of line strings with a total of 5 vertices where vertices 0 and 1 make up the first line string and vertices 2, 3, and 4 make up the second. An unsigned byte index buffer representing these line strings using primitive restart would look like the following, where index 255 marks the disconnect between the two line strings: | ||
|
|
||
| ``` | ||
| [0, 1, 255, 2, 3, 4] | ||
| ``` | ||
|
|
||
| Without `KHR_mesh_primitive_restart`, this pair of line strings would require two separate mesh primitives - one per line string - with two separate indices accessors, splitting at (and omitting) the prohibited primitive restart index: | ||
|
|
||
| ``` | ||
| [0, 1, 255, 2, 3, 4] | ||
| [0, 1] [2, 3, 4] | ||
| ``` | ||
|
|
||
| For large collections of line strings and other primitive topologies, encoding indices with restart indices can greatly reduce the number of mesh primitives and accessors required, and the associated JSON data. | ||
|
|
||
| ## JSON Schema | ||
|
|
||
| The `"KHR_mesh_primitive_restart"` string must be added to the root-level `extensionsUsed` and `extensionsRequired` arrays. The extension is always required. No additional extensions are added to meshes or mesh primitives; all mesh primitives with applicable draw modes are permitted to use primitive restart indices. | ||
|
|
||
| ## Known Implementations | ||
|
|
||
| - TODO | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would you consider relaxing "must specify" to "should specify" in
extensionsRequired? If not, could the implementation note(s) at least acknowledge the tradeoffs below?The mandate here creates an unusual failure mode: viewers that could render the content correctly refuse to load it. CesiumJS, for example, validates
extensionsRequiredagainst a whitelist and only added this extension in 1.142 (June 2026). Every earlier version will reject the glTF as containing an unsupported glTF extension, even though restart values would have rendered fine in CesiumJS powered by WebGL2 (WebGL 2 hasPRIMITIVE_RESTART_FIXED_INDEXpermanently enabled). We confirmed this through testing. The predecessor EXT wasextensionsUsed-only, so those assets load everywhere.The
extensionsRequiredmandate seems to be the correct classification (no fallback indices, likeKHR_mesh_quantization). But unlike quantization, most unaware renderers on modern APIs can render restart properly. The tradeoff of making this required or not required seems to be receiving an error on capable viewers vs. rendering possible garbage on genuinely restart-less runtimes (mostly WebGL 1-era).Back to "must specify" vs "should specify"...
"Should specify" phrasing would also preserve the guidance that assets really should list this in
extensionsRequiredby default. It just leaves room for an author with a good reason to deviate deliberately.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The extension explicitly breaks the base glTF 2.0 spec so it can't be optional. Various pipeline tools may rightfully assume that every index value refers to a real vertex.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A WebGL 1 based viewer would likely not display the assets correctly (except for special cases), and any spec-compliant renderer could, in theory, check the indices and bail out with an error if one of the invalid indices is found. (No renderer will do this, but more realisitically, one could imagine some geometry processing library manually accessing
p = positions[indices[i]], causing an out-of-bounds access for the unexpected indices)There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood.
A brief note for implementers might still help. On APIs where restart is always enabled, supporting this extension may require no rendering implementation changes at all. Recognizing the extension name may suffice (CPU-side index consumers aside), as it did for CesiumJS. An implementation note might encourage runtimes to quickly declare support rather than reject assets they could already render.