Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 77 additions & 0 deletions extensions/2.0/Khronos/KHR_mesh_primitive_restart/README.md
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.

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 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 extensionsRequired against 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 has PRIMITIVE_RESTART_FIXED_INDEX permanently enabled). We confirmed this through testing. The predecessor EXT was extensionsUsed-only, so those assets load everywhere.

The extensionsRequired mandate seems to be the correct classification (no fallback indices, like KHR_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 extensionsRequired by default. It just leaves room for an author with a good reason to deviate deliberately.

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

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.

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)

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.

The extension explicitly breaks the base glTF 2.0 spec so it can't be optional.

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.


> [!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
Comment thread
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`&nbsp;(UNSIGNED_BYTE) | `255` |
Comment thread
donmccurdy marked this conversation as resolved.
| `5123`&nbsp;(UNSIGNED_SHORT) | `65535` |
| `5125`&nbsp;(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
Loading