Skip to content

Commit d747feb

Browse files
committed
review wip
1 parent 09ddbec commit d747feb

6 files changed

Lines changed: 77 additions & 30 deletions

File tree

docs/api-specs/mesh_shading.md

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ to breaking changes, suggestions for the API exposed by this should be posted on
1212
## Mesh shaders overview
1313

1414
### What are mesh shaders
15+
1516
Mesh 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.
1617

1718
Mesh 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.
@@ -21,7 +22,14 @@ Mesh shaders are most effective in scenes with many polygons. They can allow ski
2122
Mesh 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).
2223

2324
### Mesh shader pipeline
24-
A mesh shader pipeline is just like a standard render pipeline, except that the vertex shader stage is replaced by a mesh shader stage (and optionally a task shader stage). This functions as follows:
25+
26+
A mesh draw command like `RenderPass::draw_mesh_tasks` uses a `RenderPipeline` created using
27+
`Device::create_mesh_pipeline`, which runs the following stages:
28+
29+
- First, an optional **task shader stage** decides how many mesh shader grids to dispatch
30+
31+
32+
just like a standard render pipeline, except that the vertex shader stage is replaced by a mesh shader stage (and optionally a task shader stage). This functions as follows:
2533

2634
* If there is a task shader stage, task shader workgroups are invoked first, with the number of workgroups determined by the draw call. Each task shader workgroup outputs a workgroup size and a task payload. A dispatch group of mesh shaders with the given workgroup size is then invoked with the task payload as a parameter.
2735
* Otherwise, a single dispatch group of mesh shaders with workgroup size given by the draw call is invoked.
@@ -99,9 +107,17 @@ Using any of these features in a `wgsl` program will require adding the `enable
99107
Two 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-specific functionality, such as subgroup operations.
100108

101109
### Task shader
102-
This shader stage can be selected by marking a function with `@task`. Task shaders must return a `vec3<u32>` as their output type. Similar to compute shaders, task shaders run in a workgroup. The output must be uniform across all threads in a workgroup.
103110

104-
The output of this determines how many workgroups of mesh shaders will be dispatched. Once dispatched, global id variables will be local to the task shader workgroup dispatch, and mesh shaders won't know the position of their dispatch among all mesh shader dispatches unless this is passed through the payload. The output may be zero to skip dispatching any mesh shader workgroups for the task shader workgroup.
111+
A function with the `@task` attribute is a **task shader stage entry point**. A mesh shader pipeline may optionally specify a task shader entry point; if it does, then mesh draw commands using that pipeline dispatch a **mesh shader grid** of invocations running the task shader entry point to compute the sizes of mesh shader grids to dispatch, and to provide a payload value for each dispatch.
112+
113+
Dispatching a task shader runs a grid of workgroups, like a compute shader dispatch. The mesh draw command determines the number of workgroups along each axis of the grid.
114+
115+
A task shader entry point must return a `vec3<u32>`. All invocations within a task shader workgroup must return the same value, but different workgroups may return different values. After each task shader workgroup finishes, the implementation dispatches a grid of mesh shaders, whose size in workgroups is determined by the task shader workgroup's return value. If the task shader returns `vec3(0, 0, 0)`, then no mesh shaders are dispatched.
116+
117+
Each task shader workgroup produces an independent dispatch grid of mesh shaders: `@builtin` values like `workgroup_id`, `global_invocation_id` describe the position of the workgroup and invocation within that grid. Mesh shaders dispatched for other task shader workgroups are not included in the count.
118+
Similarly, `@builtin(num_workgroups)` matches the task shader workgroup's return value.
119+
120+
will be local to the task shader workgroup dispatch, and mesh shaders won't know the position of their dispatch among all mesh shader dispatches unless this is passed through the payload. The output may be zero to skip dispatching any mesh shader workgroups for the task shader workgroup.
105121

106122
Task shaders must be marked with `@payload(someVar)`, where `someVar` is global variable declared like `var<task_payload> someVar: <type>`. Task shaders may use `someVar` as if it is a read-write workgroup storage variable. This payload is passed to the mesh shader workgroup that is invoked.
107123

@@ -210,4 +226,4 @@ fn ms_main(@builtin(local_invocation_index) index: u32, @builtin(global_invocati
210226
fn fs_main(vertex: VertexOutput, primitive: PrimitiveInput) -> @location(0) vec4<f32> {
211227
return vertex.color * primitive.colorMask;
212228
}
213-
```
229+
```

naga/src/ir/mod.rs

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -320,14 +320,21 @@ pub enum ConservativeDepth {
320320
#[cfg_attr(feature = "serialize", derive(Serialize))]
321321
#[cfg_attr(feature = "deserialize", derive(Deserialize))]
322322
#[cfg_attr(feature = "arbitrary", derive(Arbitrary))]
323-
#[allow(missing_docs)] // The names are self evident
324323
pub enum ShaderStage {
324+
/// Render pipeline vertex shader.
325325
Vertex,
326-
Fragment,
327-
Compute,
328-
// Mesh shader stages
326+
327+
/// Render pipeline task shader.
329328
Task,
329+
330+
/// Render pipeline mesh shader.
330331
Mesh,
332+
333+
/// Render pipeline fragment shader.
334+
Fragment,
335+
336+
/// Compute pipeline shader.
337+
Compute,
331338
}
332339

333340
impl ShaderStage {
@@ -964,6 +971,9 @@ pub enum Binding {
964971

965972
/// Indexed location.
966973
///
974+
/// This is a value passed to a [`Fragment`] shader from a [`Vertex`] or
975+
/// [`Mesh`] shader.
976+
///
967977
/// Values passed from the [`Vertex`] stage to the [`Fragment`] stage must
968978
/// have their `interpolation` defaulted (i.e. not `None`) by the front end
969979
/// as appropriate for that language.
@@ -977,6 +987,7 @@ pub enum Binding {
977987
/// interpolation must be `Flat`.
978988
///
979989
/// [`Vertex`]: crate::ShaderStage::Vertex
990+
/// [`Mesh`]: crate::ShaderStage::Mesh
980991
/// [`Fragment`]: crate::ShaderStage::Fragment
981992
Location {
982993
location: u32,
@@ -1751,10 +1762,12 @@ pub enum Expression {
17511762
query: Handle<Expression>,
17521763
committed: bool,
17531764
},
1765+
17541766
/// Result of a [`SubgroupBallot`] statement.
17551767
///
17561768
/// [`SubgroupBallot`]: Statement::SubgroupBallot
17571769
SubgroupBallotResult,
1770+
17581771
/// Result of a [`SubgroupCollectiveOperation`] or [`SubgroupGather`] statement.
17591772
///
17601773
/// [`SubgroupCollectiveOperation`]: Statement::SubgroupCollectiveOperation
@@ -2343,7 +2356,9 @@ pub struct EntryPoint {
23432356
pub workgroup_size_overrides: Option<[Option<Handle<Expression>>; 3]>,
23442357
/// The entrance function.
23452358
pub function: Function,
2346-
/// The information relating to a mesh shader
2359+
/// Information for [`Mesh`] shaders.
2360+
///
2361+
/// [`Mesh`]: ShaderStage::Mesh
23472362
pub mesh_info: Option<MeshStageInfo>,
23482363
/// The unique global variable used as a task payload from task shader to mesh shader
23492364
pub task_payload: Option<Handle<GlobalVariable>>,

naga/src/valid/analyzer.rs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1151,7 +1151,7 @@ impl FunctionInfo {
11511151
let _ = self.add_ref(index);
11521152
let _ = self.add_ref(value);
11531153
let ty =
1154-
self.expressions[value.index()].ty.clone().handle().ok_or(
1154+
self.expressions[value.index()].ty.handle().ok_or(
11551155
FunctionError::InvalidMeshShaderOutputType(value).with_span(),
11561156
)?;
11571157

@@ -1210,6 +1210,7 @@ impl FunctionInfo {
12101210
Ok(combined_uniformity)
12111211
}
12121212

1213+
/// Note that this function supplies vertex
12131214
fn try_update_mesh_vertex_type(
12141215
&mut self,
12151216
ty: Handle<crate::Type>,
@@ -1244,14 +1245,15 @@ impl FunctionInfo {
12441245
Ok(())
12451246
}
12461247

1248+
/// Update this function's mesh shader info, given that it calls `callee`.
12471249
fn try_update_mesh_info(
12481250
&mut self,
1249-
other: &FunctionMeshShaderInfo,
1251+
callee: &FunctionMeshShaderInfo,
12501252
) -> Result<(), WithSpan<FunctionError>> {
1251-
if let &Some(ref other_vertex) = &other.vertex_type {
1253+
if let &Some(ref other_vertex) = &callee.vertex_type {
12521254
self.try_update_mesh_vertex_type(other_vertex.0, other_vertex.1)?;
12531255
}
1254-
if let &Some(ref other_primitive) = &other.vertex_type {
1256+
if let &Some(ref other_primitive) = &callee.vertex_type {
12551257
self.try_update_mesh_primitive_type(other_primitive.0, other_primitive.1)?;
12561258
}
12571259
Ok(())

naga/src/valid/interface.rs

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -167,10 +167,12 @@ fn storage_usage(access: crate::StorageAccess) -> GlobalUse {
167167
storage_usage
168168
}
169169

170+
/// An output from a mesh shader.
170171
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
171172
enum MeshOutputType {
172173
None,
173174
VertexOutput,
175+
///
174176
PrimitiveOutput,
175177
}
176178

@@ -856,13 +858,14 @@ impl super::Validator {
856858
{
857859
return Err(EntryPointError::UnexpectedMeshShaderEntryResult.with_span());
858860
}
859-
// Cannot have any other built-ins or @location outputs as those are per-vertex or per-primitive
860-
if ep.stage == crate::ShaderStage::Task
861-
&& (!result_built_ins.contains(&crate::BuiltIn::MeshTaskSize)
862-
|| result_built_ins.len() != 1
863-
|| !self.location_mask.is_empty())
864-
{
865-
return Err(EntryPointError::WrongTaskShaderEntryResult.with_span());
861+
// Task shaders must have a single `MeshTaskSize` output, and nothing else.
862+
if ep.stage == crate::ShaderStage::Task {
863+
let ok = result_built_ins.contains(&crate::BuiltIn::MeshTaskSize)
864+
&& result_built_ins.len() == 1
865+
&& self.location_mask.is_empty();
866+
if !ok {
867+
return Err(EntryPointError::WrongTaskShaderEntryResult.with_span());
868+
}
866869
}
867870
if !self.blend_src_mask.is_empty() {
868871
info.dual_source_blending = true;
@@ -960,8 +963,10 @@ impl super::Validator {
960963
}
961964
}
962965

966+
// If this is a `Mesh` entry point, check its interface.
963967
if let &Some(ref mesh_info) = &ep.mesh_info {
964-
// Technically it is allowed to not output anything
968+
// Mesh shaders don't return any value. All their results are supplied through
969+
// [`SetVertex`] and [`SetPrimitive`] calls.
965970
// TODO: check that only the allowed builtins are used here
966971
if let Some(used_vertex_type) = info.mesh_shader_info.vertex_type {
967972
if used_vertex_type.0 != mesh_info.vertex_output_type {

wgpu/src/api/render_pass.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,9 @@ impl RenderPass<'_> {
231231
self.inner.draw_indexed(indices, base_vertex, instances);
232232
}
233233

234-
/// Draws using a mesh shader pipeline.
234+
/// Draws using a mesh pipeline.
235235
///
236-
/// The current pipeline must be a mesh shader pipeline.
236+
/// The current pipeline must be a mesh pipeline.
237237
///
238238
/// If the current pipeline has a task shader, run it with an workgroup for
239239
/// every `vec3<u32>(i, j, k)` where `i`, `j`, and `k` are between `0` and
@@ -290,7 +290,7 @@ impl RenderPass<'_> {
290290
.draw_indexed_indirect(&indirect_buffer.inner, indirect_offset);
291291
}
292292

293-
/// Draws using a mesh shader pipeline,
293+
/// Draws using a mesh pipeline,
294294
/// based on the contents of the `indirect_buffer`
295295
///
296296
/// This is like calling [`RenderPass::draw_mesh_tasks`] but the contents of the call are specified in the `indirect_buffer`.

wgpu/src/api/render_pipeline.rs

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,15 @@ static_assertions::assert_impl_all!(FragmentState<'_>: Send, Sync);
152152
pub struct TaskState<'a> {
153153
/// The compiled shader module for this stage.
154154
pub module: &'a ShaderModule,
155-
/// The name of the entry point in the compiled shader to use.
155+
156+
/// The name of the task shader entry point in the shader module to use.
156157
///
157-
/// If [`Some`], there must be a vertex-stage shader entry point with this name in `module`.
158-
/// Otherwise, expect exactly one vertex-stage entry point in `module`, which will be
159-
/// selected.
158+
/// If [`Some`], there must be a task shader entry point with the given name
159+
/// in `module`. Otherwise, there must be exactly one task shader entry
160+
/// point in `module`, which will be selected.
160161
pub entry_point: Option<&'a str>,
161-
/// Advanced options for when this pipeline is compiled
162+
163+
/// Advanced options for when this pipeline is compiled.
162164
///
163165
/// This implements `Default`, and for most users can be set to `Default::default()`
164166
pub compilation_options: PipelineCompilationOptions<'a>,
@@ -299,8 +301,15 @@ pub struct MeshPipelineDescriptor<'a> {
299301
///
300302
/// [default layout]: https://www.w3.org/TR/webgpu/#default-pipeline-layout
301303
pub layout: Option<&'a PipelineLayout>,
302-
/// The compiled task stage and its entry point.
304+
305+
/// The mesh pipeline's task shader.
306+
///
307+
/// If this is `None`, the mesh pipeline has no task shader. Executing a
308+
/// mesh drawing command simply dispatches a grid of mesh shaders directly.
309+
///
310+
/// [`draw_mesh_tasks`]: RenderPass::draw_mesh_tasks
303311
pub task: Option<TaskState<'a>>,
312+
304313
/// The compiled mesh stage and its entry point
305314
pub mesh: MeshState<'a>,
306315
/// The properties of the pipeline at the primitive assembly and rasterization level.

0 commit comments

Comments
 (0)