|
| 1 | +# Primitive Index |
| 2 | + |
| 3 | +The WebGPU feature `primitive-index` introduces the ability to use the `primitive_index` builtin in fragment shaders in WGSL. |
| 4 | + |
| 5 | +## Motivation |
| 6 | + |
| 7 | +The `primitive_index` is a unique integer for each primitive (triangle, line, or point) in a single drawn instance. This is useful in a variety of effects, from simple uses like computing flat shading to more complex uses such as advanced material effects or supporting modern virtualized geometry pipelines. |
| 8 | + |
| 9 | +It is a common feature in the underlying native APIs (D3D, Metal, Vulkan, OpenGL), and as a result supporting this builtin increases the number of shaders that can be directly ported to the web. |
| 10 | + |
| 11 | +## Status |
| 12 | +This feature has been approved by the working group and added to the WebGPU and WGSL specs. |
| 13 | + |
| 14 | +## Native API Availability |
| 15 | +| Platform | Type | Notes | |
| 16 | +|----------|------|-------| |
| 17 | +| SPIR-V | 32-bit integer | Requires geometry shaders, mesh shaders, or raytracing. Available as the `PrimitiveID` builtin | |
| 18 | +| HLSL | u32 | Requires D3D10. Available as the `SV_PrimitiveID` semantic | |
| 19 | +| GLSL | i32 | Requires GLSL 1.50 and later, ESSL 3.2. (ESSL 3.1 with GL_EXT_geometry_shader). Available as the `gl_primitiveID` builtin | |
| 20 | +| Metal | u32 | Requires Metal 2.2 on MacOS or Metal 2.3 on iOS. Available as `[[primitive_id]]` | |
| 21 | + |
| 22 | +Due to non-universal availability, use of this builtin will be gated behind a WebGPU feature, `primitive-index`. Also, the `enable primitive_index` statement will be required when using this builtin with WGSL. |
| 23 | + |
| 24 | +## Behavior |
| 25 | + * `primitive_index` is restricted to `fragment` shaders. |
| 26 | + * The index of the first primitive is zero, incrementing by one for each subsequent primitive. |
| 27 | + * The `primitive_index` resets to zero between each instance drawn. |
| 28 | + * The `primitive_index` value is uniform across the primitive. |
| 29 | + * Primitive restart has no effect on the value of variables decorated with `primitive_index`. |
| 30 | + * HLSL specifies that if the primitive id overflows (exceeds 2^32 – 1), it wraps to 0. |
| 31 | + * This should not apply to WebGPU since all applicable draw call count arguments are unsigned 32 bit integers, and thus can never exceed that. |
| 32 | + * There is no support for automatically generating a primitive index for adjacent primitives. |
| 33 | + * For an adjacent primitive, the index is only maintained for the internal non-adjacent primitives. |
| 34 | + |
| 35 | +All of the topologies in `GPUPrimitiveTopology` are supported. (Generally, adjacency topologies would |
| 36 | +not be supported but WebGPU does not have any adjacency topologies). |
| 37 | + |
| 38 | +| Topology | Primitive | |
| 39 | +|----------|-----------| |
| 40 | +| point-list | Each vertex is a primitive | |
| 41 | +| line-list | Each vertex pair is a primitive | |
| 42 | +| line-strip | Each adjacent vertex pair is a primitive | |
| 43 | +| triangle-list | Each vertex triplet is a primitive | |
| 44 | +| triangle-strip | Each group of 3 adjacent vertices is a primitive | |
| 45 | + |
| 46 | +## WGSL Specification |
| 47 | +This extension adds a new `builtin_value_name` entry for `primitive_index`. |
| 48 | +An entry is added to the _Built-in input and output values_ table: |
| 49 | + * _Name_: `primitive_index` |
| 50 | + * _Stage_: `fragment` |
| 51 | + * _Direction_: `input` |
| 52 | + * _Type_: `u32` |
| 53 | + * _Extension_: `primitive_index` |
| 54 | + |
| 55 | +## Example usage |
| 56 | + |
| 57 | +Enabling the `primitive-index` feature for a `GPUDevice`: |
| 58 | + |
| 59 | +```js |
| 60 | +const adapter = await navigator.gpu.requestAdapter(); |
| 61 | + |
| 62 | +const requestedFeatures = []; |
| 63 | +if (adapter.features.has('primitive-index')) { |
| 64 | + requestedFeatures.psuh('primitive-index'); |
| 65 | +} else { |
| 66 | + // Use an alternate code path or communicate error to user. |
| 67 | +} |
| 68 | + |
| 69 | +const device = await adapter.requestDevice({ requestedFeatures }); |
| 70 | +``` |
| 71 | + |
| 72 | +Using `primitive_index` builtin in a WGSL shader: |
| 73 | + |
| 74 | +```wgsl |
| 75 | +enable primitive_index |
| 76 | +
|
| 77 | +@fragment fn fs_main(@builtin(primitive_index) prim_index: u32) -> @location(0) vec4f { |
| 78 | + return vec4f(f32(prim_index)); |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +## Alternatives considered |
| 83 | + |
| 84 | +The value that the `primitive_index` provides is impossible to emulate in all situations without API support. The most practical route would be to calculate the index from the existing `vertex_index` builtin, but this does not support indexed geometry. Given that indexed geometry is the most common format for large meshes and can reduce memory requirements significantly, this is not a viable restriction. |
| 85 | + |
| 86 | +## References |
| 87 | +* [GLSL gl_PrimitiveID](https://registry.khronos.org/OpenGL-Refpages/gl4/html/gl_PrimitiveID.xhtml) |
| 88 | + * [GL_EXT_geometry_shader](https://registry.khronos.org/OpenGL/extensions/EXT/EXT_geometry_shader.txt) |
| 89 | +* [ESSL gl_PrimitiveID](https://registry.khronos.org/OpenGL-Refpages/es3/html/gl_PrimitiveID.xhtml) |
| 90 | +* [HLSL PrimitiveId](https://learn.microsoft.com/en-us/windows/win32/direct3d11/d3d10-graphics-programming-guide-input-assembler-stage-using#primitiveid) |
| 91 | +* [HLSL SV_PrimitiveId](https://learn.microsoft.com/en-us/windows/win32/direct3dhlsl/dx-graphics-hlsl-semantics) |
| 92 | + * [HLSL FunctionalSpec](https://microsoft.github.io/DirectX-Specs/d3d/archive/D3D11_3_FunctionalSpec.htm#:~:text=declaration%20for%20Shaders.-,8.17%20PrimitiveID,-PrimitiveID%20is%20a) |
| 93 | +* [Metal p.119](https://developer.apple.com/metal/Metal-Shading-Language-Specification.pdf) |
| 94 | +* [Vulkan PrimitiveId](https://registry.khronos.org/vulkan/specs/latest/man/html/PrimitiveId.html) |
0 commit comments