Skip to content

Commit a26de12

Browse files
fix(core): add fragment-stage max_inter_stage_shader_variables validation
1 parent bfb1532 commit a26de12

2 files changed

Lines changed: 181 additions & 1 deletion

File tree

wgpu-core/src/validation.rs

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,10 @@ use wgt::{
1414
BindGroupLayoutEntry, BindingType,
1515
};
1616

17-
use crate::{device::bgl, resource::InvalidResourceError, FastHashMap, FastHashSet};
17+
use crate::{
18+
device::bgl, resource::InvalidResourceError,
19+
validation::shader_io_deductions::MaxFragmentShaderInputDeduction, FastHashMap, FastHashSet,
20+
};
1821

1922
pub mod shader_io_deductions;
2023

@@ -354,6 +357,32 @@ pub enum StageError {
354357
limit: u32,
355358
deductions: Vec<MaxVertexShaderOutputDeduction>,
356359
},
360+
#[error(
361+
"fragment shader input location Location[{location}] ({var}) exceeds the \
362+
`max_inter_stage_shader_variables` limit ({}, 0-based){}",
363+
// NOTE: Remember: the limit is 0-based for indices.
364+
limit - 1,
365+
// NOTE: WebGPU spec. validation for fragment inputs is expressed in terms of variables
366+
// (unlike vertex outputs), so we use `MaxFragmentShaderInputDeduction::for_variables` here
367+
// (and not a non-existent `for_locations`).
368+
display_deductions_as_optional_list(deductions, |d| d.for_variables())
369+
)]
370+
FragmentInputLocationTooLarge {
371+
location: u32,
372+
var: InterfaceVar,
373+
limit: u32,
374+
deductions: Vec<MaxFragmentShaderInputDeduction>,
375+
},
376+
#[error(
377+
"found {num_found} user-defined fragment shader input variables, which exceeds the \
378+
`max_inter_stage_shader_variables` limit ({limit}){}",
379+
display_deductions_as_optional_list(deductions, |d| d.for_variables())
380+
)]
381+
TooManyUserDefinedFragmentInputs {
382+
num_found: u32,
383+
limit: u32,
384+
deductions: Vec<MaxFragmentShaderInputDeduction>,
385+
},
357386
#[error(
358387
"Location[{location}] {var}'s index exceeds the `max_color_attachments` limit ({limit})"
359388
)]
@@ -403,6 +432,8 @@ impl WebGpuError for StageError {
403432
| Self::MultipleEntryPointsFound
404433
| Self::VertexOutputLocationTooLarge { .. }
405434
| Self::TooManyUserDefinedVertexOutputs { .. }
435+
| Self::FragmentInputLocationTooLarge { .. }
436+
| Self::TooManyUserDefinedFragmentInputs { .. }
406437
| Self::ColorAttachmentLocationTooLarge { .. }
407438
| Self::TooManyMeshVertices { .. }
408439
| Self::TooManyMeshPrimitives { .. }
@@ -1552,6 +1583,62 @@ impl Interface {
15521583
}
15531584
}
15541585
ShaderStageForValidation::Fragment => {
1586+
let mut max_fragment_shader_input_variables =
1587+
self.limits.max_inter_stage_shader_variables;
1588+
1589+
let deductions = entry_point.inputs.iter().filter_map(|output| match output {
1590+
Varying::Local { .. } => None,
1591+
Varying::BuiltIn(builtin) => {
1592+
MaxFragmentShaderInputDeduction::from_inter_stage_builtin(*builtin).or_else(
1593+
|| {
1594+
unreachable!(
1595+
concat!(
1596+
"unexpected built-in provided; ",
1597+
"{:?} is not used for fragment stage input",
1598+
),
1599+
builtin
1600+
)
1601+
},
1602+
)
1603+
}
1604+
});
1605+
1606+
for deduction in deductions.clone() {
1607+
// NOTE: Deductions, in the current version of the spec. we implement, do not
1608+
// ever exceed the minimum variables available.
1609+
max_fragment_shader_input_variables = max_fragment_shader_input_variables
1610+
.checked_sub(deduction.for_variables())
1611+
.unwrap();
1612+
}
1613+
1614+
let mut num_user_defined_inputs = 0;
1615+
1616+
for output in entry_point.inputs.iter() {
1617+
match *output {
1618+
Varying::Local { ref iv, location } => {
1619+
if location >= max_fragment_shader_input_variables {
1620+
return Err(StageError::FragmentInputLocationTooLarge {
1621+
location,
1622+
var: iv.clone(),
1623+
limit: self.limits.max_inter_stage_shader_variables,
1624+
deductions: deductions.collect(),
1625+
});
1626+
}
1627+
num_user_defined_inputs += 1;
1628+
inter_stage_components += iv.ty.dim.num_components()
1629+
}
1630+
Varying::BuiltIn(_) => {}
1631+
};
1632+
}
1633+
1634+
if num_user_defined_inputs > max_fragment_shader_input_variables {
1635+
return Err(StageError::TooManyUserDefinedFragmentInputs {
1636+
num_found: num_user_defined_inputs,
1637+
limit: self.limits.max_inter_stage_shader_variables,
1638+
deductions: deductions.collect(),
1639+
});
1640+
}
1641+
15551642
for output in &entry_point.outputs {
15561643
let &Varying::Local { location, ref iv } = output else {
15571644
continue;

wgpu-core/src/validation/shader_io_deductions.rs

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,99 @@ impl MaxVertexShaderOutputDeduction {
2828
}
2929
}
3030

31+
/// Max shader I/O variable deductions for vertex shader output. Used by
32+
/// [`StageError::TooManyUserDefinedFragmentInputs`] and
33+
/// [`StageError::FragmentInputLocationTooLarge`].
34+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
35+
pub enum MaxFragmentShaderInputDeduction {
36+
InterStageBuiltIn(InterStageBuiltIn),
37+
}
38+
39+
impl MaxFragmentShaderInputDeduction {
40+
pub fn for_variables(self) -> u32 {
41+
match self {
42+
Self::InterStageBuiltIn(builtin) => match builtin {
43+
InterStageBuiltIn::FrontFacing
44+
| InterStageBuiltIn::SampleIndex
45+
| InterStageBuiltIn::SampleMask
46+
| InterStageBuiltIn::PrimitiveIndex
47+
| InterStageBuiltIn::SubgroupInvocationId
48+
| InterStageBuiltIn::SubgroupSize
49+
| InterStageBuiltIn::ViewIndex
50+
| InterStageBuiltIn::PointCoord => 1,
51+
InterStageBuiltIn::Barycentric => 3,
52+
InterStageBuiltIn::Position => 4,
53+
},
54+
}
55+
}
56+
57+
pub fn from_inter_stage_builtin(builtin: naga::BuiltIn) -> Option<Self> {
58+
use naga::BuiltIn;
59+
60+
Some(Self::InterStageBuiltIn(match builtin {
61+
BuiltIn::FrontFacing => InterStageBuiltIn::FrontFacing,
62+
BuiltIn::SampleIndex => InterStageBuiltIn::SampleIndex,
63+
BuiltIn::SampleMask => InterStageBuiltIn::SampleMask,
64+
BuiltIn::PrimitiveIndex => InterStageBuiltIn::PrimitiveIndex,
65+
BuiltIn::SubgroupSize => InterStageBuiltIn::SubgroupSize,
66+
BuiltIn::SubgroupInvocationId => InterStageBuiltIn::SubgroupInvocationId,
67+
68+
BuiltIn::PointCoord => InterStageBuiltIn::PointCoord,
69+
BuiltIn::Barycentric => InterStageBuiltIn::Barycentric,
70+
BuiltIn::Position { .. } => InterStageBuiltIn::Position,
71+
BuiltIn::ViewIndex => InterStageBuiltIn::ViewIndex,
72+
73+
BuiltIn::BaseInstance
74+
| BuiltIn::BaseVertex
75+
| BuiltIn::ClipDistance
76+
| BuiltIn::CullDistance
77+
| BuiltIn::InstanceIndex
78+
| BuiltIn::PointSize
79+
| BuiltIn::VertexIndex
80+
| BuiltIn::DrawID
81+
| BuiltIn::FragDepth
82+
| BuiltIn::GlobalInvocationId
83+
| BuiltIn::LocalInvocationId
84+
| BuiltIn::LocalInvocationIndex
85+
| BuiltIn::WorkGroupId
86+
| BuiltIn::WorkGroupSize
87+
| BuiltIn::NumWorkGroups
88+
| BuiltIn::NumSubgroups
89+
| BuiltIn::SubgroupId
90+
| BuiltIn::MeshTaskSize
91+
| BuiltIn::CullPrimitive
92+
| BuiltIn::PointIndex
93+
| BuiltIn::LineIndices
94+
| BuiltIn::TriangleIndices
95+
| BuiltIn::VertexCount
96+
| BuiltIn::Vertices
97+
| BuiltIn::PrimitiveCount
98+
| BuiltIn::Primitives => return None,
99+
}))
100+
}
101+
}
102+
103+
/// A [`naga::BuiltIn`] that counts towards
104+
/// a [`MaxFragmentShaderInputDeduction::InterStageBuiltIn`].
105+
///
106+
/// See also <https://www.w3.org/TR/webgpu/#inter-stage-builtins>.
107+
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
108+
pub enum InterStageBuiltIn {
109+
// Standard for WebGPU
110+
FrontFacing,
111+
SampleIndex,
112+
SampleMask,
113+
PrimitiveIndex,
114+
SubgroupInvocationId,
115+
SubgroupSize,
116+
117+
// Non-standard
118+
PointCoord,
119+
Barycentric,
120+
Position,
121+
ViewIndex,
122+
}
123+
31124
pub(in crate::validation) fn display_deductions_as_optional_list<T>(
32125
deductions: &[T],
33126
accessor: fn(&T) -> u32,

0 commit comments

Comments
 (0)