wgpu/documentation/extensions/mesh_shading.rs
1/*!
2# 🧪Experimental🧪 Mesh Shading
3
4`wgpu` supports an experimental version of mesh shading when [`Features::EXPERIMENTAL_MESH_SHADER`] is enabled.
5The status of the implementation is documented in [the mesh-shading issue](https://github.com/gfx-rs/wgpu/issues/7197).
6
7**Note**: The features documented here may have major bugs in them and are expected to be subject
8to breaking changes. Suggestions for the API exposed by this should be posted on the issue above.
9
10## Mesh shaders overview
11
12### What are mesh shaders?
13
14Mesh shaders are a new kind of rasterization pipeline intended to address some of the shortfalls with the vertex shader pipeline. The core idea of mesh shaders is that the GPU decides how to render the many small parts of a scene instead of the CPU issuing a draw call for every small part or issuing an inefficient monolithic draw call for a large part of the scene.
15
16Mesh shaders are specifically designed to be used with **meshlet rendering**, a technique where every object is split into many subobjects called meshlets that are each rendered with their own parameters. With the standard vertex pipeline, each draw call specifies an exact number of primitives to render and the same parameters for all vertex shaders on an entire object (or even multiple objects). This doesn't leave room for different LODs for different parts of an object, for example a closer part having more detail, nor does it allow culling smaller sections (or primitives) of objects. With mesh shaders, each task workgroup might get assigned to a single object. It can then analyze the different meshlets(sections) of that object, determine which are visible and should actually be rendered, and for those meshlets determine what LOD to use based on the distance from the camera. It can then dispatch a mesh workgroup for each meshlet, with each mesh workgroup then reading the data for that specific LOD of its meshlet, determining which and how many vertices and primitives to output, determining which remaining primitives need to be culled, and passing the resulting primitives to the rasterizer.
17
18Mesh shaders are most effective in scenes with many polygons. They can allow skipping processing of entire groups of primitives that are facing away from the camera or otherwise occluded, which reduces the number of primitives that need to be processed by more than half in most cases, and they can reduce the number of primitives that need to be processed for more distant objects. Scenes that are not bottlenecked by geometry (perhaps instead by fragment processing or post processing) will not see much benefit from using them.
19
20Mesh shaders were first shown off in [NVIDIA's asteroids demo](https://www.youtube.com/watch?v=CRfZYJ_sk5E). Now, they form the basis for [Unreal Engine's Nanite](https://www.unrealengine.com/en-US/blog/unreal-engine-5-is-now-available-in-preview#Nanite).
21
22### Mesh shader pipeline
23
24With the current pipeline set to a mesh pipeline, a draw command like
25`render_pass.draw_mesh_tasks(x, y, z)` takes the following steps:
26
27- If the pipeline has a task shader stage:
28 - Dispatch a grid of task shader workgroups, where `x`, `y`, and `z` give
29 the number of workgroups along each axis of the grid. Each task shader
30 workgroup produces a mesh shader workgroup grid size `(mx, my, mz)` and a
31 task payload value `mp`.
32
33 - For each task shader workgroup, dispatch a grid of mesh shader workgroups,
34 where `mx`, `my`, and `mz` give the number of workgroups along each axis
35 of the grid. Pass `mp` to each of these workgroup's mesh shader
36 invocations.
37
38- Alternatively, if the pipeline does not have a task shader stage:
39 - Dispatch a single grid of mesh shader workgroups, where `x`, `y`, and `z`
40 give the number of workgroups along each axis of the grid. These mesh
41 shaders receive no task payload value.
42
43- Each mesh shader workgroup produces a list of output vertices, and a list of
44 primitives built from those vertices. The workgroup can supply per-primitive
45 values as well, if needed. Each primitive selects its vertices by index, like
46 an indexed draw call, from among the vertices generated by this workgroup.
47
48 Unlike a grid of ordinary compute shader workgroups collaborating to build
49 vertex and index data in common storage buffers, the vertices and primitives
50 produced by a mesh shader workgroup are entirely private to that workgroup,
51 and are not accessible by other workgroups.
52
53- Primitives produced by a mesh shader workgroup can have a culling flag. If a
54 primitive's culling flag is false, it is skipped during rasterization.
55
56- The primitives produced by all mesh shader workgroups are then rasterized in
57 the usual way, with each fragment shader invocation handling one pixel.
58
59 Attributes from the vertices produced by the mesh shader workgroup are
60 provided to the fragment shader with interpolation applied as appropriate.
61
62 If the mesh shader workgroup supplied per-primitive values, these are
63 available to each primitive's fragment shader invocations. Per-primitive
64 values are never interpolated; fragment shaders simply receive the values
65 the mesh shader workgroup associated with their primitive.
66
67## `wgpu` API
68
69### New `wgpu` functions
70
71[`Device::create_mesh_pipeline`] - Creates a mesh shader pipeline. This is very similar to creating a standard render pipeline, except that it takes a mesh shader state and optional task shader state instead of a vertex state. If the task state is omitted, during rendering the number of workgroups is passed directly from the draw call to the mesh shader state, with an empty payload.
72
73[`RenderPass::draw_mesh_tasks`] - Dispatches the mesh shader pipeline. This ignores render pipeline specific information, such as vertex buffer bindings and index buffer bindings. The dispatch size must adhere to the limits described below.
74
75[`RenderPass::draw_mesh_tasks_indirect`], [`RenderPass::multi_draw_mesh_tasks_indirect`] and [`RenderPass::multi_draw_mesh_tasks_indirect_count`] - Dispatches the mesh shader pipeline with dispatch size taken from a buffer. This ignores render pipeline specific information, such as vertex buffer bindings and index buffer bindings. The dispatch size must adhere to the limits described below. Analogous to `draw_indirect`, `multi_draw_indirect` and `multi_draw_indirect_count`. Requires the corresponding indirect feature to be enabled.
76
77An example of using mesh shaders to render a single triangle can be seen
78[here](https://github.com/gfx-rs/wgpu/tree/trunk/examples/features/src/mesh_shader).
79
80### Features
81
82- Using mesh shaders requires enabling [`Features::EXPERIMENTAL_MESH_SHADER`].
83- Using mesh shaders with multiview requires enabling [`Features::EXPERIMENTAL_MESH_SHADER_MULTIVIEW`].
84- Using mesh shaders with point primitives requires enabling [`Features::EXPERIMENTAL_MESH_SHADER_POINTS`].
85- Queries are unsupported
86- Primitive index support will be added once support lands in for them in general.
87
88### Limits
89
90> **NOTE**: More limits will be added when support is added to `naga`.
91
92- `Limits::max_task_mesh_workgroup_total_count` - the maximum total number of workgroups from a `draw_mesh_tasks` command or similar. The dimensions passed must be less than or equal to this limit when multiplied together.
93- `Limits::max_task_mesh_workgroups_per_dimension` - the maximum for each of the 3 workgroup dimensions in a `draw_mesh_tasks` command. Each dimension passed must be less than or equal to this limit.
94- `max_task_invocations_per_workgroup` - The maximum total number of threads in a task shader workgroup, given by `workgroupSize.x * workgroupSize.y * workgroupSize.z`.
95- `max_task_invocations_per_dimension` the maximum value for each of `workgroupSize.x`, `workgroupSize.y` and `workgroupSize.z` in task shader workgroups.
96- `max_mesh_invocations_per_workgroup` - The maximum total number of threads in a mesh shader workgroup, given by `workgroupSize.x * workgroupSize.y * workgroupSize.z`.
97- `max_mesh_invocations_per_dimension` the maximum value for each of `workgroupSize.x`, `workgroupSize.y` and `workgroupSize.z` in mesh shader workgroups.
98- `max_task_payload_size` - the size of an `var<task_payload>` variable, in bytes.
99- `max_mesh_output_vertices` - the maximum number of vertices that a single mesh shader workgroup may output.
100- `max_mesh_output_primitives` - the maximum number of primitives that a single mesh shader workgroup may output.
101- `max_mesh_multiview_count` - the maximum number of views used when multiview rendering with a mesh shader pipeline.
102- `max_mesh_output_layers` - the maximum number of output layers for a mesh shader pipeline.
103
104## Naga implementation
105
106### Supported frontends
107
108- 🛠️ WGSL
109- ❌ SPIR-V
110- 🚫 GLSL
111
112### Supported backends
113
114- 🛠️ SPIR-V
115- 🛠️ HLSL
116- ❌ MSL
117- 🚫 GLSL
118- 🚫 WGSL
119
120✔️ = Complete
121🛠️ = In progress
122❌ = Planned
123🚫 = Unplanned/impossible
124
125## `WGSL` extension specification
126
127The majority of changes relating to mesh shaders will be in WGSL and `naga`.
128
129Using any of these features in a `wgsl` program will require adding the `enable wgpu_mesh_shader;` directive to the top of a program.
130
131Two new shader stages will be added to `WGSL`. Fragment shaders are also modified slightly. Both task shaders and mesh shaders are allowed to use any compute-available functionality, including subgroup operations.
132
133### Task shader
134
135A function with the `@task` attribute is a **task shader entry point**. A mesh shader pipeline may optionally specify a task shader entry point, and if it does, mesh draw commands using that pipeline dispatch a **task shader grid** of workgroups running the task shader entry point. Like compute shader dispatches, the three-component size passed to `draw_mesh_tasks`, or drawn from the indirect buffer for its indirect variants, specifies the size of the task shader grid as the number of workgroups along each of the grid's three axes.
136
137A task shader entry point must have a `@workgroup_size` attribute, meeting the same requirements as one appearing on a compute shader entry point.
138
139A task shader entry point must also have a `@payload(G)` property, where `G` is the name of a global variable in the `task_payload` address space. Each task shader workgroup has its own instance of this variable, visible to all invocations in the workgroup. Whatever value the workgroup collectively stores in that global variable becomes the **task payload**, and is provided to all invocations in the mesh shader grid dispatched for the workgroup. A task payload variable must be at least 4 bytes in size.
140
141A task shader entry point must return a `vec3<u32>` value decorated with `@builtin(mesh_task_size)`. The return value of each workgroup's first invocation (that is, the one whose `local_invocation_index` is `0`) is taken as the size of a **mesh shader grid** to dispatch, measured in workgroups. (If the task shader entry point returns `vec3(0, 0, 0)`, then no mesh shaders are dispatched.) Mesh shader grids are described in the next section.
142
143The output of a task shader is set to zero if it violates either of the limits `max_task_mesh_workgroup_total_count` or `max_task_mesh_workgroups_per_dimension`.
144
145Each task shader workgroup dispatches an independent mesh shader grid: in mesh shader invocations, `@builtin` values like `workgroup_id` and `global_invocation_id` describe the position of the workgroup and invocation within that grid;
146and `@builtin(num_workgroups)` matches the task shader workgroup's return value. If this output violates any limits, it may be zeroed or cause undefined behavior, depending on the compilation options. Mesh shaders dispatched for other task shader workgroups are not included in the count. If it is necessary for a mesh shader to know which task shader workgroup dispatched it, the task shader can include its own workgroup id in the task payload.
147
148Task shaders can use compute and subgroup builtin inputs, in addition to `view_index` and `draw_id`.
149
150### Mesh shader
151
152A function with the `@mesh` attribute is a **mesh shader entry point**. Mesh shaders must not return anything.
153
154Like compute shaders, mesh shaders are invoked in a grid of workgroups, called a **mesh shader grid**. If the mesh shader pipeline has a task shader, then each task shader workgroup determines the size of a mesh shader grid to be dispatched, as described above. Otherwise, the three-component size passed to `draw_mesh_tasks`, or drawn from the indirect buffer for its indirect variants, specifies the size of the mesh shader grid directly, as the number of workgroups along each of the grid's three axes.
155
156If the mesh shader pipeline has a task shader entry point, then the pipeline's mesh shader entry point must also have a `@payload(G)` attribute, and the sizes of the variables must match. Mesh shader invocations can read from, but not write to, this variable, which is initialized to whatever value was written to it by the task shader workgroup that dispatched this mesh shader grid.
157
158If the mesh shader pipeline does not have a task shader entry point, then the mesh shader entry point must not have any `@payload` attribute.
159
160A mesh shader entry point must have the following attributes:
161
162- `@workgroup_size`: this has the same meaning as when it appears on a compute shader entry point.
163
164- `@mesh(VAR)`: Here, `VAR` represents a workgroup variable storing the output information.
165
166All mesh shader outputs are per-workgroup, and taken from the workgroup variable specified above. The type must have exactly 4 fields:
167
168- A field decorated with `@builtin(vertex_count)`, with type `u32`: this field represents the number of vertices that will be drawn
169- A field decorated with `@builtin(primitive_count)`, with type `u32`: this field represents the number of primitives that will be drawn
170- A field decorated with `@builtin(vertices)`, typed as an array of `V`, where `V` is the vertex output type as specified below
171- A field decorated with `@builtin(primitives)`, typed as an array of `P`, where `P` is the primitive output type as specified below
172
173For a vertex count `NV`, the first `NV` elements of the vertex array above are outputted. Therefore, `NV` must be less than or equal to the size of the vertex array. The same is true for primitives with `NP`.
174
175The vertex output type `V` must meet the same requirements as a struct type returned by a `@vertex` entry point: all members must have either `@builtin` or `@location` attributes, there must be a `@builtin(position)`, and so on.
176
177The primitive output type `P` must be a struct type, every member of which either has a `@location` or `@builtin` attribute. All members decorated with `@location` must also be decorated with `@per_primitive`, as must the corresponding fragment input. The `@per_primitive` decoration may only be applied to members decorated with `@location`. The following `@builtin` attributes are allowed:
178
179- `triangle_indices`, `line_indices`, or `point_index`: The annotated member must be of type `vec3<u32>`, `vec2<u32>`, or `u32`.
180
181 The member's components are indices (or, its value is an index) into the list of vertices generated by this workgroup, identifying the vertices of the primitive to be drawn. These indices must be less than the value of `numVertices` passed to `setMeshOutputs`.
182
183 The type `P` must contain exactly one member with one of these attributes, determining what sort of primitives the mesh shader generates.
184
185- `cull_primitive`: The annotated member must be of type `bool`. If it is true, then the primitive is skipped during rendering.
186
187The `@location` attributes of `P` and `V` must not overlap, since they are merged to produce the user-defined inputs to the fragment shader.
188
189Mesh shaders may write to the `primitive_index` builtin. This is treated just like a field decorated with `@location`, so if the mesh shader outputs `primitive_index` the fragment shader must input it, and if the fragment shader inputs it, the mesh shader must write it (unlike vertex shader pipelines).
190
191Mesh shaders can use compute and mesh shader builtin inputs, in addition to `view_index`, and if no task shader is present, `draw_id`.
192
193### Fragment shader
194
195Fragment shaders can access vertex output data as if it is from a vertex shader. They can also access primitive output data, provided the input is decorated with `@per_primitive`. The `@per_primitive` decoration may only be applied to inputs or struct members decorated with `@location`.
196
197The primitive state is part of the fragment input and must match the output of the mesh shader in the pipeline. Using `@per_primitive` also requires enabling the mesh shader extension. Additionally, the locations of vertex and primitive input cannot overlap.
198
199### Full example
200
201The following is a full example of WGSL shaders that could be used to create a mesh shader pipeline, showing off many of the features.
202
203```wgsl
204enable wgpu_mesh_shader;
205
206const positions = array(
207 vec4(0., 1., 0., 1.),
208 vec4(-1., -1., 0., 1.),
209 vec4(1., -1., 0., 1.)
210);
211const colors = array(
212 vec4(0., 1., 0., 1.),
213 vec4(0., 0., 1., 1.),
214 vec4(1., 0., 0., 1.)
215);
216
217struct TaskPayload {
218 colorMask: vec4<f32>,
219 visible: bool,
220}
221struct VertexOutput {
222 @builtin(position) position: vec4<f32>,
223 @location(0) color: vec4<f32>,
224}
225struct PrimitiveOutput {
226 @builtin(triangle_indices) indices: vec3<u32>,
227 @builtin(cull_primitive) cull: bool,
228 @per_primitive @location(1) colorMask: vec4<f32>,
229}
230struct PrimitiveInput {
231 @per_primitive @location(1) colorMask: vec4<f32>,
232}
233
234var<task_payload> taskPayload: TaskPayload;
235var<workgroup> workgroupData: f32;
236
237@task
238@payload(taskPayload)
239@workgroup_size(1)
240fn ts_main() -> @builtin(mesh_task_size) vec3<u32> {
241 workgroupData = 1.0;
242 taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0);
243 taskPayload.visible = true;
244 return vec3(1, 1, 1);
245}
246
247struct MeshOutput {
248 @builtin(vertices) vertices: array<VertexOutput, 3>,
249 @builtin(primitives) primitives: array<PrimitiveOutput, 1>,
250 @builtin(vertex_count) vertex_count: u32,
251 @builtin(primitive_count) primitive_count: u32,
252}
253
254var<workgroup> mesh_output: MeshOutput;
255
256@mesh(mesh_output)
257@payload(taskPayload)
258@workgroup_size(1)
259fn ms_main() {
260 mesh_output.vertex_count = 3;
261 mesh_output.primitive_count = 1;
262 workgroupData = 2.0;
263
264 mesh_output.vertices[0].position = positions[0];
265 mesh_output.vertices[0].color = colors[0] * taskPayload.colorMask;
266
267 mesh_output.vertices[1].position = positions[1];
268 mesh_output.vertices[1].color = colors[1] * taskPayload.colorMask;
269
270 mesh_output.vertices[2].position = positions[2];
271 mesh_output.vertices[2].color = colors[2] * taskPayload.colorMask;
272
273 mesh_output.primitives[0].indices = vec3<u32>(0, 1, 2);
274 mesh_output.primitives[0].cull = !taskPayload.visible;
275 mesh_output.primitives[0].colorMask = vec4<f32>(1.0, 0.0, 1.0, 1.0);
276}
277
278@fragment
279fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4<f32> {
280 return vertex.color * primitive.colorMask;
281}
282```
283
284*/
285
286use crate::{Device, Features, RenderPass};