fix: reject fragment shader output locations > max_color_attachments limit

This commit is contained in:
Erich Gubler 2025-10-06 16:16:12 -04:00
parent 1f91bfe465
commit adf4b2f2de
4 changed files with 102 additions and 2 deletions

View File

@ -73,6 +73,12 @@ SamplerDescriptor {
}
```
### Bug Fixes
#### General
- Reject fragment shader output `location`s > `max_color_attachments` limit. By @ErichDonGubler in [#8316](https://github.com/gfx-rs/wgpu/pull/8316).
## v27.0.2 (2025-10-03)
### Bug Fixes

View File

@ -7,4 +7,5 @@ mod device;
mod experimental;
mod external_texture;
mod instance;
mod render_pipeline;
mod texture;

View File

@ -0,0 +1,70 @@
//! Tests of [`wgpu::RenderPipeline`] and related.
use wgpu_test::fail;
#[test]
fn reject_fragment_shader_output_over_max_color_attachments() {
let (device, _queue) = wgpu::Device::noop(&Default::default());
// NOTE: Vertex shader is a boring quad. The fragment shader is the interesting part.
let source = format!(
"\
@vertex
fn vert(@builtin(vertex_index) vertex_index : u32) -> @builtin(position) vec4f {{
var pos = array<vec2f, 3>(
vec2(0.0, 0.5),
vec2(-0.5, -0.5),
vec2(0.5, -0.5)
);
return vec4f(pos[vertex_index], 0.0, 1.0);
}}
@fragment
fn frag() -> @location({}) vec4f {{
return vec4(1.0, 0.0, 0.0, 1.0);
}}
",
device.limits().max_color_attachments
);
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: None,
source: wgpu::ShaderSource::Wgsl(source.into()),
});
let module = &module;
fail(
&device,
|| {
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
layout: None,
label: None,
vertex: wgpu::VertexState {
module,
entry_point: None,
compilation_options: Default::default(),
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module,
entry_point: None,
compilation_options: Default::default(),
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: None,
write_mask: Default::default(),
})],
}),
primitive: Default::default(),
depth_stencil: None,
multisample: Default::default(),
multiview: None,
cache: None,
})
},
Some(concat!(
"Location[8] Float32x4 interpolated as Some(Perspective) ",
"with sampling Some(Center)'s index exceeds the `max_color_attachments` limit (8)"
)),
);
}

View File

@ -313,6 +313,14 @@ pub enum StageError {
MultipleEntryPointsFound,
#[error(transparent)]
InvalidResource(#[from] InvalidResourceError),
#[error(
"Location[{location}] {var}'s index exceeds the `max_color_attachments` limit ({limit})"
)]
ColorAttachmentLocationTooLarge {
location: u32,
var: InterfaceVar,
limit: u32,
},
}
impl WebGpuError for StageError {
@ -334,7 +342,8 @@ impl WebGpuError for StageError {
| Self::TooManyVaryings { .. }
| Self::MissingEntryPoint(..)
| Self::NoEntryPointFound
| Self::MultipleEntryPointsFound => return ErrorType::Validation,
| Self::MultipleEntryPointsFound
| Self::ColorAttachmentLocationTooLarge { .. } => return ErrorType::Validation,
};
e.webgpu_error_type()
}
@ -1317,7 +1326,6 @@ impl Interface {
}
}
#[expect(clippy::single_match)]
match shader_stage {
naga::ShaderStage::Vertex => {
for output in entry_point.outputs.iter() {
@ -1352,6 +1360,20 @@ impl Interface {
}
}
}
naga::ShaderStage::Fragment => {
for output in &entry_point.outputs {
let &Varying::Local { location, ref iv } = output else {
continue;
};
if location >= self.limits.max_color_attachments {
return Err(StageError::ColorAttachmentLocationTooLarge {
location,
var: iv.clone(),
limit: self.limits.max_color_attachments,
});
}
}
}
_ => (),
}
@ -1370,6 +1392,7 @@ impl Interface {
Varying::BuiltIn(_) => None,
})
.collect();
Ok(outputs)
}