Skip to content

Mesh shading/dx12 backend #8110

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 22 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fe68bf2
Features and draw commands added
SupaMaggie70Incorporated Aug 15, 2025
7558c76
Tried to implement the pipeline creation (completely untested)
SupaMaggie70Incorporated Aug 15, 2025
302d3c4
Fixed clippy issues
SupaMaggie70Incorporated Aug 15, 2025
794fa2a
Fixed something I think
SupaMaggie70Incorporated Aug 16, 2025
be5ccb0
A little bit of work on the mesh shader example (currently doesn't wo…
SupaMaggie70Incorporated Aug 16, 2025
e5bd6b2
Reached a new kind of error state
SupaMaggie70Incorporated Aug 16, 2025
50a80f4
Fixed an alignment issue
SupaMaggie70Incorporated Aug 16, 2025
01dca60
DirectX 12 mesh shaders working :party:
SupaMaggie70Incorporated Aug 16, 2025
ef36e29
Removed stupid change and updated changelog
SupaMaggie70Incorporated Aug 16, 2025
4e83a8c
Fixed typo
SupaMaggie70Incorporated Aug 16, 2025
99e4fd9
Added backends option to example framework
SupaMaggie70Incorporated Aug 16, 2025
c941407
Removed silly no write fragment shader from tests to see if anything …
SupaMaggie70Incorporated Aug 16, 2025
2a52d6e
Tried to make mesh shader tests run elsewhere too
SupaMaggie70Incorporated Aug 16, 2025
313c270
Removed printlns and checked that dx12 mesh shader tests run
SupaMaggie70Incorporated Aug 16, 2025
d93830c
Documented very strange issue
SupaMaggie70Incorporated Aug 16, 2025
bfbf6ee
I'm so lost
SupaMaggie70Incorporated Aug 16, 2025
62b582e
Fixed stupid typos
SupaMaggie70Incorporated Aug 16, 2025
684ef2c
Fixed all issues
SupaMaggie70Incorporated Aug 16, 2025
35f422d
Removed unnecessary example stuff, updated tests
SupaMaggie70Incorporated Aug 16, 2025
0d800b0
Updated typos.toml
SupaMaggie70Incorporated Aug 16, 2025
fafa2c8
Updated limits
SupaMaggie70Incorporated Aug 16, 2025
5d18b20
Merge branch 'trunk' into mesh-shading/dx12-backend
SupaMaggie70Incorporated Aug 17, 2025
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ By @Vecvec in [#7913](https://github.com/gfx-rs/wgpu/pull/7913).
#### DX12

- Allow disabling waiting for latency waitable object. By @marcpabst in [#7400](https://github.com/gfx-rs/wgpu/pull/7400)
- Add mesh shader support, including to the example. By @SupaMaggie70Incorporated in [#8110](https://github.com/gfx-rs/wgpu/issues/7219)

### Bug Fixes

Expand Down
9 changes: 7 additions & 2 deletions examples/features/src/framework.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ pub trait Example: 'static + Sized {
wgpu::Limits::downlevel_webgl2_defaults() // These downlevel limits will allow the code to run on all possible hardware
}

fn supported_backends() -> wgpu::Backends {
wgpu::Backends::all()
}

fn init(
config: &wgpu::SurfaceConfiguration,
adapter: &wgpu::Adapter,
Expand Down Expand Up @@ -268,9 +272,10 @@ impl ExampleContext {
async fn init_async<E: Example>(surface: &mut SurfaceWrapper, window: Arc<Window>) -> Self {
log::info!("Initializing wgpu...");

let instance = wgpu::Instance::new(&wgpu::InstanceDescriptor::from_env_or_default());
let mut instance_descriptor = wgpu::InstanceDescriptor::from_env_or_default();
instance_descriptor.backends &= E::supported_backends();
let instance = wgpu::Instance::new(&instance_descriptor);
surface.pre_adapter(&instance, window);

let adapter = get_adapter_with_capabilities_or_from_env(
&instance,
&E::required_features(),
Expand Down
65 changes: 58 additions & 7 deletions examples/features/src/mesh_shader/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ fn compile_glsl(
.spawn()
.expect("Failed to call glslc");
cmd.stdin.as_ref().unwrap().write_all(data).unwrap();
println!("{shader_stage}");
let output = cmd.wait_with_output().expect("Error waiting for glslc");
assert!(output.status.success());
unsafe {
Expand All @@ -32,6 +31,39 @@ fn compile_glsl(
))
}
}
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();
unsafe {
device.create_shader_module_passthrough(wgpu::ShaderModuleDescriptorPassthrough::Dxil(
wgpu::ShaderModuleDescriptorDxil {
entry_point: entry.to_owned(),
label: None,
source: &file,
num_workgroups: (0, 0, 0),
},
))
}
}

pub struct Example {
pipeline: wgpu::RenderPipeline,
Expand All @@ -43,16 +75,27 @@ impl crate::framework::Example for Example {
device: &wgpu::Device,
_queue: &wgpu::Queue,
) -> Self {
let features = device.features();
let (ts, ms, fs) = if features.contains(wgpu::Features::SPIRV_SHADER_PASSTHROUGH) {
(
compile_glsl(device, include_bytes!("shader.task"), "task"),
compile_glsl(device, include_bytes!("shader.mesh"), "mesh"),
compile_glsl(device, include_bytes!("shader.frag"), "frag"),
)
} else if features.contains(wgpu::Features::HLSL_DXIL_SHADER_PASSTHROUGH) {
(
compile_hlsl(device, "Task", "as"),
compile_hlsl(device, "Mesh", "ms"),
compile_hlsl(device, "Frag", "ps"),
)
} else {
panic!("Device must support SPIRV_SHADER_PASSTHROUGH or HLSL_DXIL_SHADER_PASSTHROUGH");
};
let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[],
push_constant_ranges: &[],
});
let (ts, ms, fs) = (
compile_glsl(device, include_bytes!("shader.task"), "task"),
compile_glsl(device, include_bytes!("shader.mesh"), "mesh"),
compile_glsl(device, include_bytes!("shader.frag"), "frag"),
);
let pipeline = device.create_mesh_pipeline(&wgpu::MeshPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
Expand Down Expand Up @@ -119,11 +162,19 @@ impl crate::framework::Example for Example {
Default::default()
}
fn required_features() -> wgpu::Features {
wgpu::Features::EXPERIMENTAL_MESH_SHADER | wgpu::Features::SPIRV_SHADER_PASSTHROUGH
wgpu::Features::EXPERIMENTAL_MESH_SHADER
}
fn required_limits() -> wgpu::Limits {
wgpu::Limits::defaults().using_recommended_minimum_mesh_shader_values()
}
fn optional_features() -> wgpu::Features {
wgpu::Features::SPIRV_SHADER_PASSTHROUGH | wgpu::Features::HLSL_DXIL_SHADER_PASSTHROUGH
}
// This is because the passthrough features are optional despite at least one
// being required
fn supported_backends() -> wgpu::Backends {
wgpu::Backends::VULKAN | wgpu::Backends::DX12
}
fn resize(
&mut self,
_config: &wgpu::SurfaceConfiguration,
Expand Down
53 changes: 53 additions & 0 deletions examples/features/src/mesh_shader/shader.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
struct OutVertex {
float4 Position : SV_POSITION;
float4 Color: COLOR;
};
struct OutPrimitive {
float4 ColorMask : COLOR_MASK : PRIMITIVE;
bool CullPrimitive: SV_CullPrimitive;
};
struct InVertex {
float4 Color: COLOR;
};
struct InPrimitive {
float4 ColorMask : COLOR_MASK : PRIMITIVE;
};
struct PayloadData {
float4 ColorMask;
bool Visible;
};


static const float4 positions[3] = {float4(0., 1.0, 0., 1.0), float4(-1.0, -1.0, 0., 1.0), float4(1.0, -1.0, 0., 1.0)};
static const float4 colors[3] = {float4(0., 1., 0., 1.), float4(0., 0., 1., 1.), float4(1., 0., 0., 1.)};

groupshared PayloadData outPayload;

[numthreads(1, 1, 1)]
void Task() {
outPayload.ColorMask = float4(1.0, 1.0, 0.0, 1.0);
outPayload.Visible = true;
DispatchMesh(3, 1, 1, outPayload);
}

[outputtopology("triangle")]
[numthreads(1, 1, 1)]
void Mesh(out indices uint3 triangles[1], out vertices OutVertex vertices[3], out primitives OutPrimitive primitives[1], in payload PayloadData payload) {
SetMeshOutputCounts(3, 1);

vertices[0].Position = positions[0];
vertices[1].Position = positions[1];
vertices[2].Position = positions[2];

vertices[0].Color = colors[0] * payload.ColorMask;
vertices[1].Color = colors[1] * payload.ColorMask;
vertices[2].Color = colors[2] * payload.ColorMask;

triangles[0] = uint3(0, 1, 2);
primitives[0].ColorMask = float4(1.0, 0.0, 0.0, 1.0);
primitives[0].CullPrimitive = !payload.Visible;
}

float4 Frag(InVertex vertex, InPrimitive primitive) : SV_Target {
return vertex.Color * primitive.ColorMask;
}
6 changes: 3 additions & 3 deletions examples/features/src/mesh_shader/shader.mesh
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,18 @@ vertexOutput[];
layout(location = 1) perprimitiveEXT out PrimitiveOutput { vec4 colorMask; }
primitiveOutput[];

shared uint sharedData;

layout(triangles, max_vertices = 3, max_primitives = 1) out;
void main() {
sharedData = 5;
SetMeshOutputsEXT(3, 1);

gl_MeshVerticesEXT[0].gl_Position = positions[0];
gl_MeshVerticesEXT[1].gl_Position = positions[1];
gl_MeshVerticesEXT[2].gl_Position = positions[2];

vertexOutput[0].color = colors[0] * payloadData.colorMask;
vertexOutput[1].color = colors[1] * payloadData.colorMask;
vertexOutput[2].color = colors[2] * payloadData.colorMask;

gl_PrimitiveTriangleIndicesEXT[gl_LocalInvocationIndex] = uvec3(0, 1, 2);
primitiveOutput[0].colorMask = vec4(1.0, 0.0, 1.0, 1.0);
gl_MeshPrimitivesEXT[0].gl_CullPrimitiveEXT = !payloadData.visible;
Expand Down
2 changes: 1 addition & 1 deletion examples/features/src/mesh_shader/shader.task
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#version 450
#extension GL_EXT_mesh_shader : require

layout(local_size_x = 4, local_size_y = 1, local_size_z = 1) in;
layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in;

struct TaskPayload {
vec4 colorMask;
Expand Down
3 changes: 2 additions & 1 deletion naga/src/back/hlsl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,8 @@ impl crate::ShaderStage {
Self::Vertex => "vs",
Self::Fragment => "ps",
Self::Compute => "cs",
Self::Task | Self::Mesh => unreachable!(),
Self::Task => "as",
Self::Mesh => "ms",
}
}
}
Expand Down
41 changes: 41 additions & 0 deletions tests/tests/wgpu-gpu/mesh_shader/basic.hlsl
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
struct OutVertex {
float4 Position : SV_POSITION;
float4 Color: COLOR;
};
struct InVertex {
float4 Color: COLOR;
};


static const float4 positions[3] = {float4(0., 1.0, 0., 1.0), float4(-1.0, -1.0, 0., 1.0), float4(1.0, -1.0, 0., 1.0)};
static const float4 colors[3] = {float4(0., 1., 0., 1.), float4(0., 0., 1., 1.), float4(1., 0., 0., 1.)};

struct EmptyPayload {
uint _nullField;
};
groupshared EmptyPayload _emptyPayload;

[numthreads(4, 1, 1)]
void Task() {
DispatchMesh(1, 1, 1, _emptyPayload);
}

[outputtopology("triangle")]
[numthreads(1, 1, 1)]
void Mesh(out indices uint3 triangles[1], out vertices OutVertex vertices[3], in payload EmptyPayload _emptyPayload) {
SetMeshOutputCounts(3, 1);

vertices[0].Position = positions[0];
vertices[1].Position = positions[1];
vertices[2].Position = positions[2];

vertices[0].Color = colors[0];
vertices[1].Color = colors[1];
vertices[2].Color = colors[2];

triangles[0] = uint3(0, 1, 2);
}

float4 Frag(InVertex vertex) : SV_Target {
return vertex.Color;
}
Loading
Loading