Add KHR_mesh_primitive_restart#2569
Conversation
|
|
||
| Because the extension does not provide a way to specify fallback indices without restart values, assets that use the extension must specify it in `extensionsRequired` array - the extension is not optional. | ||
|
|
||
| > **Implementation 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. |
There was a problem hiding this comment.
| > **Implementation 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. | |
| > [!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. |
|
|
||
| | `accessor.componentType` | restart value | | ||
| | ---------------------------- | ------------- | | ||
| | `5121` (UNSIGNED_BYTE) | `255` | |
There was a problem hiding this comment.
We probably need to provide some guidance on how to handle cases where the accessor component type chosen is not supported by the API or engine.
This will be an issue that the WebGPU implementations will likely run into if the creator of the glTF uses UNSIGNED_BYTE. WebGPU's implementation of primitive restart only supports uint16 and uint32.
There was a problem hiding this comment.
Isn't it a glTF 2.0 core issue? This extension does not add any new index data types.
There was a problem hiding this comment.
Yes, it is a core issue in glTF 2.0. My line of thinking here is that it might be worthwhile to provide an informative guidance to point people into the right direction, but in retrospect it probably isn't necessary.
There was a problem hiding this comment.
If anything, we might add a note about 8-bit indices to the main spec someday (just stating the obvious: convert to 16-bit if unsupported) but there's nothing to do about that in this extension.
|
|
||
| The applicable primitive restart value is determined by the accessor component type: | ||
|
|
||
| | `accessor.componentType` | restart value | |
There was a problem hiding this comment.
A minor nitpick:
| | `accessor.componentType` | restart value | | |
| | `accessor.componentType` | restart index | |
Much of the existing standards and literature refers to these values as the "restart index" and it is probably good if we use follow that.
|
|
||
| > **Implementation 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 |
There was a problem hiding this comment.
Do we need to include a normative statement in this section that explicitly states that if an engine uses a different index than the ones specified, implementers are responsible for the remapping?
Both OpenGL and Vulkan support setting an arbitrary value as the primitive restart index. OpenGL through the glPrimitiveRestartIndex() function, and Vulkan through the vkCmdSetPrimitiveRestartIndex() function provided in the VK_EXT_primitive_restart_index extension, which was promoted to core in Vulkan 1.3.
There was a problem hiding this comment.
if an engine uses a different index than the ones specified
It's out of the spec's scope. The file format spec defines what's stored in the file.
Besides, arbitrary restart indices are not widely supported and the mentioned Vulkan extension exists only for legacy emulation purposes; it is about one month old by the way.
|
|
||
| > `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. |
There was a problem hiding this comment.
Please change "removes" to "adjusts" (or similar) because the restriction must remain for list topologies.
| @@ -0,0 +1,76 @@ | |||
| <!-- | |||
| Copyright 2025 Bentley Systems, Incorporated | |||
|
|
Motivation
glTF 2.0 includes seven primitive modes:
0 POINTS1 LINES2 LINE_LOOP3 LINE_STRIP4 TRIANGLES5 TRIANGLE_STRIP6 TRIANGLE_FANFour 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_restartextension, which can be added to any glTF asset, removing the restriction against primitive restart values. The mechanism and implementation are intentionally very similar toKHR_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_restartallows 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_restartextension, 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
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.Implementations