Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
103 commits
Select commit Hold shift + click to select a range
0622940
[spv-out] Support for mesh shaders
cwfitzgerald Dec 12, 2025
dc69d86
Update mesh_shader.rs
inner-daemons Dec 13, 2025
dfd360f
Tried one thing
inner-daemons Dec 14, 2025
c1973fe
Updated snapshots
inner-daemons Dec 14, 2025
50c2aa9
Tried another thing
inner-daemons Dec 14, 2025
7749867
Removed per primitive stuff + cull primitive
inner-daemons Dec 14, 2025
ca3f93a
Testing new thing
inner-daemons Dec 14, 2025
4fbfc60
Ahh well I think I'm done for the night
inner-daemons Dec 14, 2025
7551f6a
Slight improvements
inner-daemons Dec 14, 2025
3225030
Fixed another comment
inner-daemons Dec 14, 2025
c34c474
Added note on feature
inner-daemons Dec 14, 2025
18164ee
Preparing for merge as is
inner-daemons Dec 14, 2025
0b9bf09
Ok I'm tired
inner-daemons Dec 14, 2025
da72786
Blah blah blah
inner-daemons Dec 14, 2025
8862d56
Updated loop logic
inner-daemons Dec 14, 2025
0286af7
Tried something else
inner-daemons Dec 14, 2025
7cc51c9
Tried another little fix
inner-daemons Dec 14, 2025
3818efe
Tried something new
inner-daemons Dec 14, 2025
ea50cb0
Told it to skip instead of expect a failure
inner-daemons Dec 14, 2025
8d9a451
Redocumented feature, made tests run on AMD
inner-daemons Dec 15, 2025
3edc37c
Removed obseleted files, updated changelog, updated shaders
inner-daemons Dec 15, 2025
6ffd2d5
Added task shader to the changelog entry
inner-daemons Dec 15, 2025
094a5ac
Enabled debugigng
inner-daemons Dec 15, 2025
6417327
Fixed typo
inner-daemons Dec 15, 2025
2e863e2
Trying with better aligned task payload stuff
inner-daemons Dec 15, 2025
05eada6
Made the tests actually run on LLVMPIPE
inner-daemons Dec 15, 2025
6dc1fd0
Testing on LLVMPIPE if removing task payload reads does anything
inner-daemons Dec 15, 2025
883a443
Undid test that didnt work
inner-daemons Dec 15, 2025
3457ed5
Tried making it write a barrier
inner-daemons Dec 15, 2025
cad1bdf
Wrote another barrier I guess
inner-daemons Dec 15, 2025
4927043
Gonna see if this one does anything
inner-daemons Dec 15, 2025
19af909
Jeez im stupid
inner-daemons Dec 15, 2025
3d7327f
Removed debugging files
inner-daemons Dec 15, 2025
c4574e7
Fixed the example shader sorta
inner-daemons Dec 15, 2025
ff46752
Blah blah blah
inner-daemons Dec 15, 2025
a5324ee
Merge remote-tracking branch 'upstream/trunk' into mesh-shading/spv-w…
inner-daemons Dec 15, 2025
c36f9f8
Final cleanup
inner-daemons Dec 15, 2025
4c29d13
Added new mesh shader tasks
inner-daemons Dec 16, 2025
c08d00a
Fixed test
inner-daemons Dec 16, 2025
7703aef
Fixed some test shenanigans
inner-daemons Dec 16, 2025
90624c8
Seeing if this breaks anything
inner-daemons Dec 16, 2025
307f908
Tried to fix one issue
inner-daemons Dec 16, 2025
69a1d50
Initial setup
inner-daemons Dec 17, 2025
3611377
More scaffolding
inner-daemons Dec 17, 2025
f0235fb
Temporary changes while I pivot
inner-daemons Dec 17, 2025
1e33465
A little more progress made here
inner-daemons Dec 17, 2025
f09af9b
Task shaders are now good I think
inner-daemons Dec 17, 2025
03b46f7
Some more work, it mostly compiles now
inner-daemons Dec 17, 2025
e8188cb
Merge remote-tracking branch 'upstream/trunk' into mesh-shading/hlsl-…
inner-daemons Dec 17, 2025
12551f4
Fixed warnings
inner-daemons Dec 17, 2025
445382b
Ran taplo fmt, fixed a warning
inner-daemons Dec 17, 2025
1b48851
Many shaders now build properly
inner-daemons Dec 18, 2025
dbdff84
It flippin works!!
inner-daemons Dec 18, 2025
c875a58
Merge remote-tracking branch 'upstream/trunk' into mesh-shading/hlsl-…
inner-daemons Dec 18, 2025
e31b4f9
Hahaha its valid
inner-daemons Dec 18, 2025
82db42d
Final tweaks
inner-daemons Dec 18, 2025
f47a220
Fixed missing commas in some shaders
inner-daemons Dec 18, 2025
85f65a5
Fixed final test
inner-daemons Dec 18, 2025
2a8396f
Zero initialized stuff
inner-daemons Dec 18, 2025
cced603
Initial commit adding miscellaneous changes from msl-write and hlsl-w…
inner-daemons Dec 18, 2025
61a7e95
Same as previous commit
inner-daemons Dec 18, 2025
5bef12b
Fixed divergence issue
inner-daemons Dec 18, 2025
3b04e7a
Removed some unnecessary barriers
inner-daemons Dec 18, 2025
7576625
Zero init workgroup memory
inner-daemons Dec 18, 2025
ffc0939
Added limits validation
inner-daemons Dec 18, 2025
9435f58
Merge remote-tracking branch 'upstream/trunk' into mesh-shading/spv-f…
inner-daemons Dec 18, 2025
d66e920
Added changelog
inner-daemons Dec 18, 2025
ba8f67b
Handled overflow, removed todo
inner-daemons Dec 18, 2025
cb41edd
Amazing stuff here on display
inner-daemons Dec 18, 2025
28b684d
Undid barrier generation
inner-daemons Dec 19, 2025
b91d24b
Fixed some stuff up
inner-daemons Dec 19, 2025
12158d3
Fixed the thing
inner-daemons Dec 19, 2025
f8f10e6
Lets see if this fixes llvmpipe
inner-daemons Dec 19, 2025
c6e7b48
Also this commit fixes llvmpipe maybe
inner-daemons Dec 19, 2025
9b1ea2c
Unfortunate but not too unexpected at this point
inner-daemons Dec 19, 2025
b2e9657
Pushing changes even though they're broken
inner-daemons Dec 19, 2025
f32c091
Updated feature to say to use ShaderRuntimeChecks::unchecked()
inner-daemons Dec 19, 2025
503038c
Reverted to old method for snapshots
inner-daemons Jan 6, 2026
192120b
Merge remote-tracking branch 'upstream/trunk' into mesh-shading/hlsl-…
inner-daemons Jan 6, 2026
b012e5e
Updated spirv snapshot
inner-daemons Jan 6, 2026
2ac1478
Refactored nested function outer into its own function
inner-daemons Jan 6, 2026
2793e15
Tweaked a comment
inner-daemons Jan 6, 2026
ed1f75c
Merge remote-tracking branch 'upstream/trunk' into mesh-shading/spv-f…
inner-daemons Jan 6, 2026
49c98a1
Updated snapshots and took some changes from the hlsl writer
inner-daemons Jan 6, 2026
3ef0910
Snapshots
inner-daemons Jan 6, 2026
3cfc8fe
2 quick tweaks
inner-daemons Jan 6, 2026
fe58d2d
Merge remote-tracking branch 'upstream/trunk' into mesh-shading/hlsl-…
inner-daemons Jan 15, 2026
65cd586
Update naga/src/back/hlsl/writer.rs
inner-daemons Jan 15, 2026
20ff07d
Fixes round 1 (cleaned up small things)
inner-daemons Jan 15, 2026
33066c7
Made mesh shader actually use the payload input
inner-daemons Jan 15, 2026
647afcf
Refactored a bunch of stuff into a new file
inner-daemons Jan 15, 2026
91bc3cb
Made hal require shader model 6.5 to expose support
inner-daemons Jan 15, 2026
ffc6cce
Merge remote-tracking branch 'upstream/trunk' into mesh-shading/spv-f…
inner-daemons Jan 15, 2026
6d4fe94
Merge branch 'mesh-shading/spv-fixes' into mesh-shading/hlsl-write-id
inner-daemons Jan 15, 2026
fc3d916
Made naga check for proper shader model
inner-daemons Jan 15, 2026
526dec4
Updated framework with suggestions by Connor in #8752
inner-daemons Jan 15, 2026
a97cf46
Moved the task runtime limits into naga::back
inner-daemons Jan 15, 2026
ab4625b
Merge branch 'mesh-shading/spv-fixes' into mesh-shading/hlsl-write-id
inner-daemons Jan 15, 2026
7b907ad
Added validation for the task shader's dispatched workgroup count
inner-daemons Jan 15, 2026
34b53fb
Blindly trying to pass limits in
inner-daemons Jan 15, 2026
2e31c0e
Added limits field
inner-daemons Jan 15, 2026
29f630a
Added changelog entry
inner-daemons Jan 15, 2026
f17979a
Fixed some checks
inner-daemons Jan 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ Bottom level categories:
- Added support for obtaining `AdapterInfo` from `Device`. By @sagudev in [#8807](https://github.com/gfx-rs/wgpu/pull/8807).
- Added `Limits::or_worse_values_from`. By @atlv24 in [#8870](https://github.com/gfx-rs/wgpu/pull/8870).

### General

#### DX12

- Full support for mesh shaders in HLSL/DX12. By @inner-daemons in #8752.

### Bug Fixes

#### General
Expand All @@ -60,6 +66,9 @@ Bottom level categories:
- The validator checks that override-sized arrays have a positive size, if overrides have been resolved. By @andyleiserson in [#8822](https://github.com/gfx-rs/wgpu/pull/8822).
- Fix some cases where f16 constants were not working. By @andyleiserson in [#8816](https://github.com/gfx-rs/wgpu/pull/8816).

#### Vulkan
- Fixed a variety of mesh shader SPIR-V writer issues from the original implementation. By @inner-daemons in [#8756](https://github.com/gfx-rs/wgpu/pull/8756)

#### GLES

- `DisplayHandle` should now be passed to `InstanceDescriptor` for correct EGL initialization on Wayland. By @MarijnS95 in [#8012](https://github.com/gfx-rs/wgpu/pull/8012)
Expand Down
4 changes: 1 addition & 3 deletions docs/api-specs/mesh_shading.md
Original file line number Diff line number Diff line change
Expand Up @@ -130,13 +130,11 @@ A task shader entry point must have a `@workgroup_size` attribute, meeting the s

A 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.

A task shader entry point must return a `vec3<u32>` value. 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.
A 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.

Each 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;
and `@builtin(num_workgroups)` matches the task shader workgroup's return value. 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.

Task shaders must return a value of type `vec3<u32>` decorated with `@builtin(mesh_task_size)`.

Task shaders can use compute and subgroup builtin inputs, in addition to `view_index` and `draw_id`.

### Mesh shader
Expand Down
12 changes: 7 additions & 5 deletions examples/features/src/framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,11 +260,13 @@ impl ExampleContext {
async fn init_async<E: Example>(surface: &mut SurfaceWrapper, window: Arc<Window>) -> Self {
log::info!("Initializing wgpu...");

let instance_descriptor = wgpu::InstanceDescriptor::from_env_or_default()
.with_display_handle(Box::new(
// TODO: Use event_loop.owned_display_handle() with winit 0.30
window.clone(),
));
let mut instance_descriptor = wgpu::InstanceDescriptor::default();
// Use static DXC by default so we can utilize the latest features
instance_descriptor.backend_options.dx12.shader_compiler = wgpu::Dx12Compiler::StaticDxc;
let instance_descriptor = instance_descriptor.with_env().with_display_handle(Box::new(
// TODO: Use event_loop.owned_display_handle() with winit 0.30
window.clone(),
));
let instance = wgpu::Instance::new(instance_descriptor);
surface.pre_adapter(&instance, window);
let adapter = get_adapter_with_capabilities_or_from_env(
Expand Down
52 changes: 9 additions & 43 deletions examples/features/src/mesh_shader/mod.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,14 @@
// Same as in mesh shader tests
fn compile_wgsl(device: &wgpu::Device) -> wgpu::ShaderModule {
device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
})
}
fn compile_hlsl(device: &wgpu::Device, entry: &str, stage_str: &str) -> wgpu::ShaderModule {
let out_path = format!(
"{}/src/mesh_shader/shader.{stage_str}.cso",
env!("CARGO_MANIFEST_DIR")
);
let cmd = std::process::Command::new("dxc")
.args([
"-T",
&format!("{stage_str}_6_5"),
"-E",
entry,
&format!("{}/src/mesh_shader/shader.hlsl", env!("CARGO_MANIFEST_DIR")),
"-Fo",
&out_path,
])
.output()
.unwrap();
if !cmd.status.success() {
panic!("DXC failed:\n{}", String::from_utf8(cmd.stderr).unwrap());
}
let file = std::fs::read(&out_path).unwrap();
std::fs::remove_file(out_path).unwrap();
// Workgroup memory zero initialization can be expensive for mesh shaders
unsafe {
device.create_shader_module_passthrough(wgpu::ShaderModuleDescriptorPassthrough {
entry_point: entry.to_owned(),
label: None,
num_workgroups: (1, 1, 1),
dxil: Some(std::borrow::Cow::Owned(file)),
..Default::default()
})
device.create_shader_module_trusted(
wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
},
wgpu::ShaderRuntimeChecks::unchecked(),
)
}
}

Expand All @@ -61,22 +35,14 @@ impl crate::framework::Example for Example {
_queue: &wgpu::Queue,
) -> Self {
let (ts, ms, fs, ts_name, ms_name, fs_name) = match adapter.get_info().backend {
wgpu::Backend::Vulkan => (
wgpu::Backend::Vulkan | wgpu::Backend::Dx12 => (
compile_wgsl(device),
compile_wgsl(device),
compile_wgsl(device),
"ts_main",
"ms_main",
"fs_main",
),
wgpu::Backend::Dx12 => (
compile_hlsl(device, "Task", "as"),
compile_hlsl(device, "Mesh", "ms"),
compile_hlsl(device, "Frag", "ps"),
"main",
"main",
"main",
),
wgpu::Backend::Metal => (
compile_msl(device, "taskShader"),
compile_msl(device, "meshShader"),
Expand Down
53 changes: 0 additions & 53 deletions examples/features/src/mesh_shader/shader.hlsl

This file was deleted.

46 changes: 26 additions & 20 deletions examples/features/src/mesh_shader/shader.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,15 @@ var<workgroup> workgroupData: f32;

@task
@payload(taskPayload)
@workgroup_size(1)
fn ts_main() -> @builtin(mesh_task_size) vec3<u32> {
workgroupData = 1.0;
taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0);
taskPayload.visible = true;
return vec3(1, 1, 1);
@workgroup_size(64)
fn ts_main(@builtin(local_invocation_id) thread_id: vec3<u32>) -> @builtin(mesh_task_size) vec3<u32> {
if thread_id.x == 0 {
workgroupData = 1.0;
taskPayload.colorMask = vec4(1.0, 1.0, 0.0, 1.0);
taskPayload.visible = true;
return vec3(1, 1, 1);
}
return vec3(0, 0, 0);
}

struct MeshOutput {
Expand All @@ -52,24 +55,27 @@ var<workgroup> mesh_output: MeshOutput;

@mesh(mesh_output)
@payload(taskPayload)
@workgroup_size(1)
fn ms_main() {
mesh_output.vertex_count = 3;
mesh_output.primitive_count = 1;
workgroupData = 2.0;
@workgroup_size(64)
fn ms_main(@builtin(local_invocation_id) thread_id: vec3<u32>) {
if thread_id.x == 0 {
mesh_output.vertex_count = 3;
mesh_output.primitive_count = 1;
workgroupData = 2.0;

mesh_output.vertices[0].position = positions[0];
mesh_output.vertices[0].color = colors[0] * taskPayload.colorMask;
mesh_output.vertices[0].position = positions[0];
mesh_output.vertices[0].color = colors[0] * taskPayload.colorMask;

mesh_output.vertices[1].position = positions[1];
mesh_output.vertices[1].color = colors[1] * taskPayload.colorMask;
mesh_output.vertices[1].position = positions[1];
mesh_output.vertices[1].color = colors[1] * taskPayload.colorMask;

mesh_output.vertices[2].position = positions[2];
mesh_output.vertices[2].color = colors[2] * taskPayload.colorMask;
mesh_output.vertices[2].position = positions[2];
mesh_output.vertices[2].color = colors[2] * taskPayload.colorMask;

mesh_output.primitives[0].indices = vec3<u32>(0, 1, 2);
mesh_output.primitives[0].cull = !taskPayload.visible;
mesh_output.primitives[0].colorMask = vec4<f32>(1.0, 0.0, 1.0, 1.0);
mesh_output.primitives[0].indices = vec3<u32>(0, 1, 2);
mesh_output.primitives[0].cull = !taskPayload.visible;
mesh_output.primitives[0].colorMask = vec4<f32>(1.0, 0.0, 1.0, 1.0);
return;
}
}

@fragment
Expand Down
4 changes: 4 additions & 0 deletions naga-test/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,10 @@ impl SpirvOutParameters {
ray_query_initialization_tracking: true,
debug_info,
use_storage_input_output_16: self.use_storage_input_output_16,
task_runtime_limits: Some(naga::back::TaskRuntimeLimits {
max_mesh_workgroups_per_dim: 256,
max_mesh_workgroups_total: 1024,
}),
}
}
}
Expand Down
12 changes: 11 additions & 1 deletion naga/hlsl-snapshots/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ pub struct Config {
pub vertex: Vec<ConfigItem>,
pub fragment: Vec<ConfigItem>,
pub compute: Vec<ConfigItem>,
pub task: Vec<ConfigItem>,
pub mesh: Vec<ConfigItem>,
}

impl Config {
Expand All @@ -59,6 +61,8 @@ impl Config {
vertex: Default::default(),
fragment: Default::default(),
compute: Default::default(),
task: Default::default(),
mesh: Default::default(),
}
}

Expand All @@ -85,8 +89,14 @@ impl Config {
vertex,
fragment,
compute,
task,
mesh,
} = self;
vertex.is_empty() && fragment.is_empty() && compute.is_empty()
vertex.is_empty()
&& fragment.is_empty()
&& compute.is_empty()
&& task.is_empty()
&& mesh.is_empty()
}
}

Expand Down
12 changes: 7 additions & 5 deletions naga/src/back/hlsl/conv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,8 +149,8 @@ impl crate::StorageFormat {
}

impl crate::BuiltIn {
pub(super) fn to_hlsl_str(self) -> Result<&'static str, Error> {
Ok(match self {
pub(super) fn to_hlsl_str(self) -> Result<Option<&'static str>, Error> {
Ok(Some(match self {
Self::Position { .. } => "SV_Position",
// vertex
Self::ClipDistance => "SV_ClipDistance",
Expand Down Expand Up @@ -186,13 +186,15 @@ impl crate::BuiltIn {
return Err(Error::Custom(format!("Unsupported builtin {self:?}")))
}
Self::CullPrimitive => "SV_CullPrimitive",
Self::PointIndex | Self::LineIndices | Self::TriangleIndices => unimplemented!(),
Self::MeshTaskSize
| Self::VertexCount
| Self::PrimitiveCount
| Self::Vertices
| Self::Primitives => unreachable!(),
})
| Self::Primitives
| Self::PointIndex
| Self::LineIndices
| Self::TriangleIndices => return Ok(None),
}))
}
}

Expand Down
Loading