Block creating mesh shader pipelines when the feature is not available (#8298)

This commit is contained in:
Andy Leiserson 2025-10-04 00:12:52 -07:00 committed by GitHub
parent b3c6a0b5cc
commit 334170b21e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 96 additions and 17 deletions

View File

@ -3,11 +3,15 @@ use std::{
process::Stdio, process::Stdio,
}; };
use wgpu::util::DeviceExt; use wgpu::{util::DeviceExt, Backends};
use wgpu_test::{ use wgpu_test::{
gpu_test, GpuTestConfiguration, GpuTestInitializer, TestParameters, TestingContext, fail, gpu_test, FailureCase, GpuTestConfiguration, GpuTestInitializer, TestParameters,
TestingContext,
}; };
/// Backends that support mesh shaders
const MESH_SHADER_BACKENDS: Backends = Backends::DX12.union(Backends::VULKAN);
pub fn all_tests(tests: &mut Vec<GpuTestInitializer>) { pub fn all_tests(tests: &mut Vec<GpuTestInitializer>) {
tests.extend([ tests.extend([
MESH_PIPELINE_BASIC_MESH, MESH_PIPELINE_BASIC_MESH,
@ -19,6 +23,7 @@ pub fn all_tests(tests: &mut Vec<GpuTestInitializer>) {
MESH_MULTI_DRAW_INDIRECT_COUNT, MESH_MULTI_DRAW_INDIRECT_COUNT,
MESH_PIPELINE_BASIC_MESH_NO_DRAW, MESH_PIPELINE_BASIC_MESH_NO_DRAW,
MESH_PIPELINE_BASIC_TASK_MESH_FRAG_NO_DRAW, MESH_PIPELINE_BASIC_TASK_MESH_FRAG_NO_DRAW,
MESH_DISABLED,
]); ]);
} }
@ -97,21 +102,44 @@ fn get_shaders(
device: &wgpu::Device, device: &wgpu::Device,
backend: wgpu::Backend, backend: wgpu::Backend,
test_name: &str, test_name: &str,
) -> (wgpu::ShaderModule, wgpu::ShaderModule, wgpu::ShaderModule) { info: &MeshPipelineTestInfo,
) -> (
Option<wgpu::ShaderModule>,
wgpu::ShaderModule,
Option<wgpu::ShaderModule>,
) {
// On backends that don't support mesh shaders, or for the MESH_DISABLED
// test, compile a dummy shader so we can construct a structurally valid
// pipeline description and test that `create_mesh_pipeline` fails.
// (In the case that the platform does support mesh shaders, the dummy
// shader is used to avoid requiring EXPERIMENTAL_PASSTHROUGH_SHADERS.)
let dummy_shader = device.create_shader_module(wgpu::include_wgsl!("non_mesh.wgsl"));
if backend == wgpu::Backend::Vulkan { if backend == wgpu::Backend::Vulkan {
( (
compile_glsl(device, "task"), info.use_task.then(|| compile_glsl(device, "task")),
compile_glsl(device, "mesh"), if info.use_mesh {
compile_glsl(device, "frag"), compile_glsl(device, "mesh")
} else {
dummy_shader
},
info.use_frag.then(|| compile_glsl(device, "frag")),
) )
} else if backend == wgpu::Backend::Dx12 { } else if backend == wgpu::Backend::Dx12 {
( (
compile_hlsl(device, "Task", "as", test_name), info.use_task
compile_hlsl(device, "Mesh", "ms", test_name), .then(|| compile_hlsl(device, "Task", "as", test_name)),
compile_hlsl(device, "Frag", "ps", test_name), if info.use_mesh {
compile_hlsl(device, "Mesh", "ms", test_name)
} else {
dummy_shader
},
info.use_frag
.then(|| compile_hlsl(device, "Frag", "ps", test_name)),
) )
} else { } else {
unreachable!() assert!(!MESH_SHADER_BACKENDS.contains(Backends::from(backend)));
assert!(!info.use_task && !info.use_mesh && !info.use_frag);
(None, dummy_shader, None)
} }
} }
@ -146,6 +174,7 @@ fn create_depth(
struct MeshPipelineTestInfo { struct MeshPipelineTestInfo {
use_task: bool, use_task: bool,
use_mesh: bool,
use_frag: bool, use_frag: bool,
draw: bool, draw: bool,
} }
@ -158,16 +187,11 @@ fn hash_testing_context(ctx: &TestingContext) -> u64 {
fn mesh_pipeline_build(ctx: &TestingContext, info: MeshPipelineTestInfo) { fn mesh_pipeline_build(ctx: &TestingContext, info: MeshPipelineTestInfo) {
let backend = ctx.adapter.get_info().backend; let backend = ctx.adapter.get_info().backend;
if backend != wgpu::Backend::Vulkan && backend != wgpu::Backend::Dx12 {
return;
}
let device = &ctx.device; let device = &ctx.device;
let (_depth_image, depth_view, depth_state) = create_depth(device); let (_depth_image, depth_view, depth_state) = create_depth(device);
let test_hash = hash_testing_context(ctx).to_string(); let test_hash = hash_testing_context(ctx).to_string();
let (task, mesh, frag) = get_shaders(device, backend, &test_hash); let (task, mesh, frag) = get_shaders(device, backend, &test_hash, &info);
let task = if info.use_task { Some(task) } else { None };
let frag = if info.use_frag { Some(frag) } else { None };
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None, label: None,
bind_group_layouts: &[], bind_group_layouts: &[],
@ -246,7 +270,15 @@ fn mesh_draw(ctx: &TestingContext, draw_type: DrawType) {
let device = &ctx.device; let device = &ctx.device;
let (_depth_image, depth_view, depth_state) = create_depth(device); let (_depth_image, depth_view, depth_state) = create_depth(device);
let test_hash = hash_testing_context(ctx).to_string(); let test_hash = hash_testing_context(ctx).to_string();
let (task, mesh, frag) = get_shaders(device, backend, &test_hash); let info = MeshPipelineTestInfo {
use_task: true,
use_mesh: true,
use_frag: true,
draw: true,
};
let (task, mesh, frag) = get_shaders(device, backend, &test_hash, &info);
let task = task.unwrap();
let frag = frag.unwrap();
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None, label: None,
bind_group_layouts: &[], bind_group_layouts: &[],
@ -343,6 +375,7 @@ fn mesh_draw(ctx: &TestingContext, draw_type: DrawType) {
fn default_gpu_test_config(draw_type: DrawType) -> GpuTestConfiguration { fn default_gpu_test_config(draw_type: DrawType) -> GpuTestConfiguration {
GpuTestConfiguration::new().parameters( GpuTestConfiguration::new().parameters(
TestParameters::default() TestParameters::default()
.skip(FailureCase::backend(!MESH_SHADER_BACKENDS))
.test_features_limits() .test_features_limits()
.features( .features(
wgpu::Features::EXPERIMENTAL_MESH_SHADER wgpu::Features::EXPERIMENTAL_MESH_SHADER
@ -365,6 +398,7 @@ pub static MESH_PIPELINE_BASIC_MESH: GpuTestConfiguration =
&ctx, &ctx,
MeshPipelineTestInfo { MeshPipelineTestInfo {
use_task: false, use_task: false,
use_mesh: true,
use_frag: false, use_frag: false,
draw: true, draw: true,
}, },
@ -377,6 +411,7 @@ pub static MESH_PIPELINE_BASIC_TASK_MESH: GpuTestConfiguration =
&ctx, &ctx,
MeshPipelineTestInfo { MeshPipelineTestInfo {
use_task: true, use_task: true,
use_mesh: true,
use_frag: false, use_frag: false,
draw: true, draw: true,
}, },
@ -389,6 +424,7 @@ pub static MESH_PIPELINE_BASIC_MESH_FRAG: GpuTestConfiguration =
&ctx, &ctx,
MeshPipelineTestInfo { MeshPipelineTestInfo {
use_task: false, use_task: false,
use_mesh: true,
use_frag: true, use_frag: true,
draw: true, draw: true,
}, },
@ -401,6 +437,7 @@ pub static MESH_PIPELINE_BASIC_TASK_MESH_FRAG: GpuTestConfiguration =
&ctx, &ctx,
MeshPipelineTestInfo { MeshPipelineTestInfo {
use_task: true, use_task: true,
use_mesh: true,
use_frag: true, use_frag: true,
draw: true, draw: true,
}, },
@ -413,6 +450,7 @@ pub static MESH_PIPELINE_BASIC_MESH_NO_DRAW: GpuTestConfiguration =
&ctx, &ctx,
MeshPipelineTestInfo { MeshPipelineTestInfo {
use_task: false, use_task: false,
use_mesh: true,
use_frag: false, use_frag: false,
draw: false, draw: false,
}, },
@ -425,6 +463,7 @@ pub static MESH_PIPELINE_BASIC_TASK_MESH_FRAG_NO_DRAW: GpuTestConfiguration =
&ctx, &ctx,
MeshPipelineTestInfo { MeshPipelineTestInfo {
use_task: true, use_task: true,
use_mesh: true,
use_frag: true, use_frag: true,
draw: false, draw: false,
}, },
@ -447,3 +486,30 @@ pub static MESH_MULTI_DRAW_INDIRECT_COUNT: GpuTestConfiguration =
default_gpu_test_config(DrawType::MultiIndirectCount).run_sync(|ctx| { default_gpu_test_config(DrawType::MultiIndirectCount).run_sync(|ctx| {
mesh_draw(&ctx, DrawType::MultiIndirectCount); mesh_draw(&ctx, DrawType::MultiIndirectCount);
}); });
/// When the mesh shading feature is disabled, calls to `create_mesh_pipeline`
/// should be rejected. This should be the case on all backends, not just the
/// ones where the feature could be turned on.
#[gpu_test]
pub static MESH_DISABLED: GpuTestConfiguration = GpuTestConfiguration::new().run_sync(|ctx| {
fail(
&ctx.device,
|| {
mesh_pipeline_build(
&ctx,
MeshPipelineTestInfo {
use_task: false,
use_mesh: false,
use_frag: false,
draw: true,
},
);
},
Some(concat![
"Features Features { ",
"features_wgpu: FeaturesWGPU(EXPERIMENTAL_MESH_SHADER), ",
"features_webgpu: FeaturesWebGPU(0x0) ",
"} are required but not enabled on the device",
]),
)
});

View File

@ -0,0 +1,11 @@
@vertex
fn vs_main(@builtin(vertex_index) in_vertex_index: u32) -> @builtin(position) vec4<f32> {
let x = f32(i32(in_vertex_index) - 1);
let y = f32(i32(in_vertex_index & 1u) * 2 - 1);
return vec4<f32>(x, y, 0.0, 1.0);
}
@fragment
fn fs_main() -> @location(0) vec4<f32> {
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
}

View File

@ -4063,6 +4063,8 @@ impl Device {
}; };
} }
pipeline::RenderPipelineVertexProcessor::Mesh(ref task, ref mesh) => { pipeline::RenderPipelineVertexProcessor::Mesh(ref task, ref mesh) => {
self.require_features(wgt::Features::EXPERIMENTAL_MESH_SHADER)?;
task_stage = if let Some(task) = task { task_stage = if let Some(task) = task {
let stage_desc = &task.stage; let stage_desc = &task.stage;
let stage = wgt::ShaderStages::TASK; let stage = wgt::ShaderStages::TASK;