Skip to content

Commit 58a3be6

Browse files
pmconneaaronfranke
andauthored
EXT_mesh_primitive_restart (#2478)
* schema * README (wip) * README * tweak * Add constraints; remove issue link * React to feedback * define fall back on constraint violation * Adjust language, prohibit morph targets * Add link to primitiveGroup schema * EXT_mesh_primitive_restart * schema formatting * Fix POSITIONS accessor index * Fix other POSITIONS attribute index * rm empty line Co-authored-by: Aaron Franke <arnfranke@yahoo.com> * End file with newline * upgrade JSON schema version * Fallback is not required in case of constraint violation * Add known implementation * Remove name * /s/Draft/Complete * Remove copyright --------- Co-authored-by: Aaron Franke <arnfranke@yahoo.com>
1 parent 16c7ad5 commit 58a3be6

File tree

3 files changed

+223
-0
lines changed

3 files changed

+223
-0
lines changed
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
# EXT_mesh_primitive_restart
2+
3+
## Contributors
4+
5+
* Paul Connelly, Bentley Systems, [@pmconne](https://github.com/pmconne)
6+
7+
## Status
8+
9+
Complete
10+
11+
## Dependencies
12+
13+
Written against the glTF 2.0 spec.
14+
15+
## Overview
16+
17+
"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, increasing the amount of data required to describe the geometry.
18+
19+
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,
20+
21+
> `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).
22+
23+
This extension permits the above prohibition to be selectively relaxed, while providing a trivial fallback for implementations that don't support primitive restart.
24+
25+
## Example
26+
27+
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:
28+
29+
```
30+
[0, 1, 255, 2, 3, 4]
31+
```
32+
33+
Over this same buffer, we can alternatively produce 2 separate accessors - one per line string - splitting at (and omitting) the prohibited primitive restart value:
34+
35+
```
36+
[0, 1, 255, 2, 3, 4]
37+
[0, 1] [2, 3, 4]
38+
```
39+
40+
This permits two equivalent representations of the geometry without duplicating any binary data. In glTF, the accessors look like this:
41+
42+
```json
43+
"accessors": [
44+
{
45+
"bufferView": 0,
46+
"count": 2,
47+
"componentType": 5121,
48+
"type": "SCALAR",
49+
"name": "Line string 1 indices"
50+
},
51+
{
52+
"bufferView": 0,
53+
"byteOffset": 3,
54+
"count": 3,
55+
"componentType": 5121,
56+
"type": "SCALAR",
57+
"name": "Line string 2 indices"
58+
},
59+
{
60+
"bufferView": 0,
61+
"componentType": 5121,
62+
"count": 6,
63+
"type": "SCALAR",
64+
"name": "All indices"
65+
},
66+
{
67+
"bufferView": 1,
68+
"componentType": 5126,
69+
"count": 5,
70+
"type": "VEC3",
71+
"max": [
72+
0.5,
73+
0.5,
74+
0.0
75+
],
76+
"min": [
77+
-0.5,
78+
-0.5,
79+
0.0
80+
],
81+
"name": "Positions accessor"
82+
}
83+
],
84+
```
85+
86+
The mesh looks like this:
87+
88+
```json
89+
"meshes": [
90+
{
91+
"primitives": [
92+
{
93+
"attributes": {
94+
"POSITION": 3
95+
},
96+
"indices": 0,
97+
"material": 0,
98+
"mode": 3
99+
},
100+
{
101+
"attributes": {
102+
"POSITION": 3
103+
},
104+
"indices": 1,
105+
"material": 0,
106+
"mode": 3
107+
}
108+
],
109+
"extensions": {
110+
"EXT_mesh_primitive_restart": {
111+
"primitiveGroups": [
112+
{
113+
"primitives": [
114+
0,
115+
1
116+
],
117+
"indices": 2
118+
}
119+
]
120+
}
121+
}
122+
}
123+
],
124+
```
125+
126+
By default, this mesh draws two separate line strip primitives, each using its own `indices` accessor. The `EXT_mesh_primitive_restart` extension specifies that both primitives can be replaced with a single one using a combined `indices` accessor containing primitive restart values.
127+
128+
## glTF Schema Updates
129+
130+
The `EXT_mesh_primitive_restart` extension is applied to a mesh. Its `primitiveGroups` property is a list of groups of primitives that can be replaced with a single primitive using primitive restart. Each group is described by a list of indices into `mesh.primitives`, along with the index of the accessor that supplies the vertex indices for the replacement primitive.
131+
132+
## Constraints
133+
134+
The extension is subject to the following constraints. Violation of any constraint renders the entire extension invalid, in which case the extension **SHOULD** be ignored and the `mesh.primitives` objects **SHOULD** be rendered as defined in the glTF 2.0 specification.
135+
136+
- A given primitive index **MUST NOT** appear in more than one primitive group.
137+
138+
- Each primitive index in a primitive group **MUST NOT** appear more than once in that group.
139+
140+
- Each primitive in each group **MUST** use one of the following topology types, as specified by the `mode` property: 2 (line loop), 3 (line strip), 5 (triangle strip), or 6 (triangle fan). No other topology types are permitted.
141+
142+
- All primitives in a given group **MUST** have identical property values (e.g., attributes, material, mode, etc), with the exception of `indices`. This includes the `extensions` property - e.g., if any primitive in a group has a `KHR_materials_variants` extension object, then all other primitives in the same group **MUST** have that extension with identical content.
143+
144+
- Each primitive in each group **MUST** define an `indices` property, i.e., they **MUST** use indexed geometry.
145+
146+
- The `indices` accessor specified by each primitive group **MUST** be a valid index accessor as per the base glTF 2.0 specification, i.e., their types **MUST** be scalar, their component types **MUST** be any of the unsigned integer types, and their buffer views (if defined) **MUST NOT** be used for any purpose other than vertex indices.
147+
148+
- Primitives referred to by this extension **MUST NOT** have morph targets.
149+
150+
## JSON Schema
151+
152+
- [EXT_mesh_primitive_restart.schema.json](schema/EXT_mesh_primitive_restart.schema.json)
153+
- [primitiveGroup.schema.json](schema/primitiveGroup.schema.json)
154+
155+
## Known Implementations
156+
157+
- [iTwin.js](https://github.com/iTwin/itwinjs-core/pull/8312)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "EXT_mesh_primitive_restart",
4+
"title": "EXT_mesh_primitive_restart glTF Mesh Primitive Extension",
5+
"type": "object",
6+
"description": "glTF extension enabling the use of primitive restart values in index buffers",
7+
"allOf": [
8+
{
9+
"$ref": "glTFProperty.schema.json"
10+
}
11+
],
12+
"properties": {
13+
"primitiveGroups": {
14+
"type": "array",
15+
"items": {
16+
"$ref": "primitiveGroup.schema.json"
17+
},
18+
"minItems": 1,
19+
"description": "The list of groups of primitives that can be drawn using a single index buffer with primitive restart"
20+
},
21+
"extensions": {},
22+
"extras": {}
23+
},
24+
"required": [
25+
"primitiveGroups"
26+
]
27+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"$schema": "https://json-schema.org/draft/2020-12/schema",
3+
"$id": "primitiveGroup",
4+
"title": "Primitive Group",
5+
"type": "object",
6+
"description": "A group of primitives that can be drawn together using a single index buffer containing restart values",
7+
"allOf": [
8+
{
9+
"$ref": "glTFProperty.schema.json"
10+
}
11+
],
12+
"properties": {
13+
"primitives": {
14+
"type": "array",
15+
"items": {
16+
"$ref": "glTFid.schema.json"
17+
},
18+
"uniqueItems": true,
19+
"minItems": 1,
20+
"description": "The indices of the primitives that will be combined and drawn using a single set of indices",
21+
"gltf_detailedDescription": "The indices of the primitives that will be combined and drawn using a single set of indices. All properties except for `indices` (e.g., `material`, `mode`, and `attributes`) will be obtained from the first primitive in the list, and all other primitives in the list **MUST** have identical values for those properties. The `mode` must be TRIANGLE_FAN, TRIANGLE_STRIP, LINE_LOOP, or LINE_STRIP."
22+
},
23+
"indices": {
24+
"allOf": [
25+
{
26+
"$ref": "glTFid.schema.json"
27+
}
28+
],
29+
"description": "The index of the accessor that contains the vertex indices",
30+
"gltf_detailedDescription": "The index of the accessor that contains the vertex indices. The accessor **MUST** have `SCALAR` type and an unsigned integer component type. The indices are permitted to include the maximal index value for the component type, indicating the start of a new primitive."
31+
},
32+
"extensions": {},
33+
"extras": {}
34+
},
35+
"required": [
36+
"primitives",
37+
"indices"
38+
]
39+
}

0 commit comments

Comments
 (0)