/src/wgpu/naga/src/valid/interface.rs
Line | Count | Source |
1 | | use alloc::vec::Vec; |
2 | | |
3 | | use bit_set::BitSet; |
4 | | |
5 | | use super::{ |
6 | | analyzer::{FunctionInfo, GlobalUse}, |
7 | | Capabilities, Disalignment, FunctionError, ImmediateError, ModuleInfo, |
8 | | }; |
9 | | use crate::arena::{Handle, UniqueArena}; |
10 | | use crate::span::{AddSpan as _, MapErrWithSpan as _, SpanProvider as _, WithSpan}; |
11 | | |
12 | | const MAX_WORKGROUP_SIZE: u32 = 0x4000; |
13 | | |
14 | | #[derive(Clone, Debug, thiserror::Error)] |
15 | | #[cfg_attr(test, derive(PartialEq))] |
16 | | pub enum GlobalVariableError { |
17 | | #[error("Usage isn't compatible with address space {0:?}")] |
18 | | InvalidUsage(crate::AddressSpace), |
19 | | #[error("Type isn't compatible with address space {0:?}")] |
20 | | InvalidType(crate::AddressSpace), |
21 | | #[error("Type {0:?} isn't compatible with binding arrays")] |
22 | | InvalidBindingArray(Handle<crate::Type>), |
23 | | #[error("Type flags {seen:?} do not meet the required {required:?}")] |
24 | | MissingTypeFlags { |
25 | | required: super::TypeFlags, |
26 | | seen: super::TypeFlags, |
27 | | }, |
28 | | #[error("Capability {0:?} is not supported")] |
29 | | UnsupportedCapability(Capabilities), |
30 | | #[error("Binding decoration is missing or not applicable")] |
31 | | InvalidBinding, |
32 | | #[error("Alignment requirements for address space {0:?} are not met by {1:?}")] |
33 | | Alignment( |
34 | | crate::AddressSpace, |
35 | | Handle<crate::Type>, |
36 | | #[source] Disalignment, |
37 | | ), |
38 | | #[error("Initializer must be an override-expression")] |
39 | | InitializerExprType, |
40 | | #[error("Initializer doesn't match the variable type")] |
41 | | InitializerType, |
42 | | #[error("Initializer can't be used with address space {0:?}")] |
43 | | InitializerNotAllowed(crate::AddressSpace), |
44 | | #[error("Storage address space doesn't support write-only access")] |
45 | | StorageAddressSpaceWriteOnlyNotSupported, |
46 | | #[error("Type is not valid for use as a immediate data")] |
47 | | InvalidImmediateType(#[source] ImmediateError), |
48 | | #[error("Task payload must not be zero-sized")] |
49 | | ZeroSizedTaskPayload, |
50 | | } |
51 | | |
52 | | #[derive(Clone, Debug, thiserror::Error)] |
53 | | #[cfg_attr(test, derive(PartialEq))] |
54 | | pub enum VaryingError { |
55 | | #[error("The type {0:?} does not match the varying")] |
56 | | InvalidType(Handle<crate::Type>), |
57 | | #[error("The type {0:?} cannot be used for user-defined entry point inputs or outputs")] |
58 | | NotIOShareableType(Handle<crate::Type>), |
59 | | #[error("Interpolation is not valid")] |
60 | | InvalidInterpolation, |
61 | | #[error("Cannot combine {interpolation:?} interpolation with the {sampling:?} sample type")] |
62 | | InvalidInterpolationSamplingCombination { |
63 | | interpolation: crate::Interpolation, |
64 | | sampling: crate::Sampling, |
65 | | }, |
66 | | #[error("Interpolation must be specified on vertex shader outputs and fragment shader inputs")] |
67 | | MissingInterpolation, |
68 | | #[error("Built-in {0:?} is not available at this stage")] |
69 | | InvalidBuiltInStage(crate::BuiltIn), |
70 | | #[error("Built-in type for {0:?} is invalid. Found {1:?}")] |
71 | | InvalidBuiltInType(crate::BuiltIn, crate::TypeInner), |
72 | | #[error("Entry point arguments and return values must all have bindings")] |
73 | | MissingBinding, |
74 | | #[error("Struct member {0} is missing a binding")] |
75 | | MemberMissingBinding(u32), |
76 | | #[error("Multiple bindings at location {location} are present")] |
77 | | BindingCollision { location: u32 }, |
78 | | #[error("Multiple bindings use the same `blend_src` {blend_src}")] |
79 | | BindingCollisionBlendSrc { blend_src: u32 }, |
80 | | #[error("Built-in {0:?} is present more than once")] |
81 | | DuplicateBuiltIn(crate::BuiltIn), |
82 | | #[error("Capability {0:?} is not supported")] |
83 | | UnsupportedCapability(Capabilities), |
84 | | #[error("The attribute {0:?} is only valid as an output for stage {1:?}")] |
85 | | InvalidInputAttributeInStage(&'static str, crate::ShaderStage), |
86 | | #[error("The attribute {0:?} is not valid for stage {1:?}")] |
87 | | InvalidAttributeInStage(&'static str, crate::ShaderStage), |
88 | | #[error("The `blend_src` attribute can only be used on location 0, only indices 0 and 1 are valid. Location was {location}, index was {blend_src}.")] |
89 | | InvalidBlendSrcIndex { location: u32, blend_src: u32 }, |
90 | | #[error("If `blend_src` is used, there must be exactly two outputs both with location 0, one with `blend_src(0)` and the other with `blend_src(1)`.")] |
91 | | IncompleteBlendSrcUsage, |
92 | | #[error("If `blend_src` is used, both outputs must have the same type. `blend_src(0)` has type {blend_src_0_type:?} and `blend_src(1)` has type {blend_src_1_type:?}.")] |
93 | | BlendSrcOutputTypeMismatch { |
94 | | blend_src_0_type: Handle<crate::Type>, |
95 | | blend_src_1_type: Handle<crate::Type>, |
96 | | }, |
97 | | #[error("Workgroup size is multi dimensional, `@builtin(subgroup_id)` and `@builtin(subgroup_invocation_id)` are not supported.")] |
98 | | InvalidMultiDimensionalSubgroupBuiltIn, |
99 | | #[error("The `@per_primitive` attribute can only be used in fragment shader inputs or mesh shader primitive outputs")] |
100 | | InvalidPerPrimitive, |
101 | | #[error("Non-builtin members of a mesh primitive output struct must be decorated with `@per_primitive`")] |
102 | | MissingPerPrimitive, |
103 | | } |
104 | | |
105 | | #[derive(Clone, Debug, thiserror::Error)] |
106 | | #[cfg_attr(test, derive(PartialEq))] |
107 | | pub enum EntryPointError { |
108 | | #[error("Multiple conflicting entry points")] |
109 | | Conflict, |
110 | | #[error("Vertex shaders must return a `@builtin(position)` output value")] |
111 | | MissingVertexOutputPosition, |
112 | | #[error("Early depth test is not applicable")] |
113 | | UnexpectedEarlyDepthTest, |
114 | | #[error("Workgroup size is not applicable")] |
115 | | UnexpectedWorkgroupSize, |
116 | | #[error("Workgroup size is out of range")] |
117 | | OutOfRangeWorkgroupSize, |
118 | | #[error("Uses operations forbidden at this stage")] |
119 | | ForbiddenStageOperations, |
120 | | #[error("Global variable {0:?} is used incorrectly as {1:?}")] |
121 | | InvalidGlobalUsage(Handle<crate::GlobalVariable>, GlobalUse), |
122 | | #[error("More than 1 immediate data variable is used")] |
123 | | MoreThanOneImmediateUsed, |
124 | | #[error("Bindings for {0:?} conflict with other resource")] |
125 | | BindingCollision(Handle<crate::GlobalVariable>), |
126 | | #[error("Argument {0} varying error")] |
127 | | Argument(u32, #[source] VaryingError), |
128 | | #[error(transparent)] |
129 | | Result(#[from] VaryingError), |
130 | | #[error("Location {location} interpolation of an integer has to be flat")] |
131 | | InvalidIntegerInterpolation { location: u32 }, |
132 | | #[error(transparent)] |
133 | | Function(#[from] FunctionError), |
134 | | #[error("Capability {0:?} is not supported")] |
135 | | UnsupportedCapability(Capabilities), |
136 | | |
137 | | #[error("mesh shader entry point missing mesh shader attributes")] |
138 | | ExpectedMeshShaderAttributes, |
139 | | #[error("Non mesh shader entry point cannot have mesh shader attributes")] |
140 | | UnexpectedMeshShaderAttributes, |
141 | | #[error("Non mesh/task shader entry point cannot have task payload attribute")] |
142 | | UnexpectedTaskPayload, |
143 | | #[error("Task payload must be declared with `var<task_payload>`")] |
144 | | TaskPayloadWrongAddressSpace, |
145 | | #[error("For a task payload to be used, it must be declared with @payload")] |
146 | | WrongTaskPayloadUsed, |
147 | | #[error("Task shader entry point must return @builtin(mesh_task_size) vec3<u32>")] |
148 | | WrongTaskShaderEntryResult, |
149 | | #[error("Task shaders must declare a task payload output")] |
150 | | ExpectedTaskPayload, |
151 | | #[error( |
152 | | "Mesh shader output variable must be a struct with fields that are all allowed builtins" |
153 | | )] |
154 | | BadMeshOutputVariableType, |
155 | | #[error("Mesh shader output variable fields must have types that are in accordance with the mesh shader spec")] |
156 | | BadMeshOutputVariableField, |
157 | | #[error("Mesh shader entry point cannot have a return type")] |
158 | | UnexpectedMeshShaderEntryResult, |
159 | | #[error( |
160 | | "Mesh output type must be a user-defined struct with fields in alignment with the mesh shader spec" |
161 | | )] |
162 | | InvalidMeshOutputType, |
163 | | #[error("Mesh primitive outputs must have exactly one of `@builtin(triangle_indices)`, `@builtin(line_indices)`, or `@builtin(point_index)`")] |
164 | | InvalidMeshPrimitiveOutputType, |
165 | | #[error("Mesh output global variable must live in the workgroup address space")] |
166 | | WrongMeshOutputAddressSpace, |
167 | | #[error("Task payload must be at least 4 bytes, but is {0} bytes")] |
168 | | TaskPayloadTooSmall(u32), |
169 | | } |
170 | | |
171 | 0 | fn storage_usage(access: crate::StorageAccess) -> GlobalUse { |
172 | 0 | let mut storage_usage = GlobalUse::QUERY; |
173 | 0 | if access.contains(crate::StorageAccess::LOAD) { |
174 | 0 | storage_usage |= GlobalUse::READ; |
175 | 0 | } |
176 | 0 | if access.contains(crate::StorageAccess::STORE) { |
177 | 0 | storage_usage |= GlobalUse::WRITE; |
178 | 0 | } |
179 | 0 | if access.contains(crate::StorageAccess::ATOMIC) { |
180 | 0 | storage_usage |= GlobalUse::ATOMIC; |
181 | 0 | } |
182 | 0 | storage_usage |
183 | 0 | } |
184 | | |
185 | | #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
186 | | enum MeshOutputType { |
187 | | None, |
188 | | VertexOutput, |
189 | | PrimitiveOutput, |
190 | | } |
191 | | |
192 | | struct VaryingContext<'a> { |
193 | | stage: crate::ShaderStage, |
194 | | output: bool, |
195 | | types: &'a UniqueArena<crate::Type>, |
196 | | type_info: &'a Vec<super::r#type::TypeInfo>, |
197 | | location_mask: &'a mut BitSet, |
198 | | blend_src_mask: &'a mut BitSet, |
199 | | built_ins: &'a mut crate::FastHashSet<crate::BuiltIn>, |
200 | | capabilities: Capabilities, |
201 | | flags: super::ValidationFlags, |
202 | | mesh_output_type: MeshOutputType, |
203 | | has_task_payload: bool, |
204 | | } |
205 | | |
206 | | impl VaryingContext<'_> { |
207 | 0 | fn validate_impl( |
208 | 0 | &mut self, |
209 | 0 | ep: &crate::EntryPoint, |
210 | 0 | ty: Handle<crate::Type>, |
211 | 0 | binding: &crate::Binding, |
212 | 0 | ) -> Result<(), VaryingError> { |
213 | | use crate::{BuiltIn as Bi, ShaderStage as St, TypeInner as Ti, VectorSize as Vs}; |
214 | | |
215 | 0 | let ty_inner = &self.types[ty].inner; |
216 | 0 | match *binding { |
217 | 0 | crate::Binding::BuiltIn(built_in) => { |
218 | | // Ignore the `invariant` field for the sake of duplicate checks, |
219 | | // but use the original in error messages. |
220 | 0 | let canonical = if let crate::BuiltIn::Position { .. } = built_in { |
221 | 0 | crate::BuiltIn::Position { invariant: false } |
222 | | } else { |
223 | 0 | built_in |
224 | | }; |
225 | | |
226 | 0 | if self.built_ins.contains(&canonical) { |
227 | 0 | return Err(VaryingError::DuplicateBuiltIn(built_in)); |
228 | 0 | } |
229 | 0 | self.built_ins.insert(canonical); |
230 | | |
231 | 0 | let required = match built_in { |
232 | 0 | Bi::ClipDistance => Capabilities::CLIP_DISTANCE, |
233 | 0 | Bi::CullDistance => Capabilities::CULL_DISTANCE, |
234 | 0 | Bi::PrimitiveIndex => Capabilities::PRIMITIVE_INDEX, |
235 | 0 | Bi::Barycentric => Capabilities::SHADER_BARYCENTRICS, |
236 | 0 | Bi::ViewIndex => Capabilities::MULTIVIEW, |
237 | 0 | Bi::SampleIndex => Capabilities::MULTISAMPLED_SHADING, |
238 | | Bi::NumSubgroups |
239 | | | Bi::SubgroupId |
240 | | | Bi::SubgroupSize |
241 | 0 | | Bi::SubgroupInvocationId => Capabilities::SUBGROUP, |
242 | 0 | _ => Capabilities::empty(), |
243 | | }; |
244 | 0 | if !self.capabilities.contains(required) { |
245 | 0 | return Err(VaryingError::UnsupportedCapability(required)); |
246 | 0 | } |
247 | | |
248 | 0 | if matches!( |
249 | 0 | built_in, |
250 | | crate::BuiltIn::SubgroupId | crate::BuiltIn::SubgroupInvocationId |
251 | 0 | ) && ep.workgroup_size[1..].iter().any(|&s| s > 1) |
252 | | { |
253 | 0 | return Err(VaryingError::InvalidMultiDimensionalSubgroupBuiltIn); |
254 | 0 | } |
255 | | |
256 | 0 | let (visible, type_good) = match built_in { |
257 | | Bi::BaseInstance | Bi::BaseVertex | Bi::InstanceIndex | Bi::VertexIndex => ( |
258 | 0 | self.stage == St::Vertex && !self.output, |
259 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::U32), |
260 | | ), |
261 | | Bi::DrawID => ( |
262 | | // Always allowed in task/vertex stage. Allowed in mesh stage if there is no task stage in the pipeline. |
263 | 0 | (self.stage == St::Vertex |
264 | 0 | || self.stage == St::Task |
265 | 0 | || (self.stage == St::Mesh && !self.has_task_payload)) |
266 | 0 | && !self.output, |
267 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::U32), |
268 | | ), |
269 | | Bi::ClipDistance | Bi::CullDistance => ( |
270 | 0 | (self.stage == St::Vertex || self.stage == St::Mesh) && self.output, |
271 | 0 | match *ty_inner { |
272 | 0 | Ti::Array { base, size, .. } => { |
273 | 0 | self.types[base].inner == Ti::Scalar(crate::Scalar::F32) |
274 | 0 | && match size { |
275 | 0 | crate::ArraySize::Constant(non_zero) => non_zero.get() <= 8, |
276 | 0 | _ => false, |
277 | | } |
278 | | } |
279 | 0 | _ => false, |
280 | | }, |
281 | | ), |
282 | | Bi::PointSize => ( |
283 | 0 | (self.stage == St::Vertex || self.stage == St::Mesh) && self.output, |
284 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::F32), |
285 | | ), |
286 | | Bi::PointCoord => ( |
287 | 0 | self.stage == St::Fragment && !self.output, |
288 | 0 | *ty_inner |
289 | 0 | == Ti::Vector { |
290 | 0 | size: Vs::Bi, |
291 | 0 | scalar: crate::Scalar::F32, |
292 | 0 | }, |
293 | | ), |
294 | | Bi::Position { .. } => ( |
295 | 0 | match self.stage { |
296 | 0 | St::Vertex | St::Mesh => self.output, |
297 | 0 | St::Fragment => !self.output, |
298 | 0 | St::Compute | St::Task => false, |
299 | | }, |
300 | 0 | *ty_inner |
301 | 0 | == Ti::Vector { |
302 | 0 | size: Vs::Quad, |
303 | 0 | scalar: crate::Scalar::F32, |
304 | 0 | }, |
305 | | ), |
306 | | Bi::ViewIndex => ( |
307 | 0 | match self.stage { |
308 | 0 | St::Vertex | St::Fragment | St::Task | St::Mesh => !self.output, |
309 | 0 | St::Compute => false, |
310 | | }, |
311 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::U32), |
312 | | ), |
313 | | Bi::FragDepth => ( |
314 | 0 | self.stage == St::Fragment && self.output, |
315 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::F32), |
316 | | ), |
317 | | Bi::FrontFacing => ( |
318 | 0 | self.stage == St::Fragment && !self.output, |
319 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::BOOL), |
320 | | ), |
321 | | Bi::PrimitiveIndex => ( |
322 | 0 | (self.stage == St::Fragment && !self.output) |
323 | 0 | || (self.stage == St::Mesh |
324 | 0 | && self.output |
325 | 0 | && self.mesh_output_type == MeshOutputType::PrimitiveOutput), |
326 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::U32), |
327 | | ), |
328 | | Bi::Barycentric => ( |
329 | 0 | self.stage == St::Fragment && !self.output, |
330 | 0 | *ty_inner |
331 | 0 | == Ti::Vector { |
332 | 0 | size: Vs::Tri, |
333 | 0 | scalar: crate::Scalar::F32, |
334 | 0 | }, |
335 | | ), |
336 | | Bi::SampleIndex => ( |
337 | 0 | self.stage == St::Fragment && !self.output, |
338 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::U32), |
339 | | ), |
340 | 0 | Bi::SampleMask => ( |
341 | 0 | self.stage == St::Fragment, |
342 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::U32), |
343 | 0 | ), |
344 | | Bi::LocalInvocationIndex => ( |
345 | 0 | self.stage.compute_like() && !self.output, |
346 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::U32), |
347 | | ), |
348 | | Bi::GlobalInvocationId |
349 | | | Bi::LocalInvocationId |
350 | | | Bi::WorkGroupId |
351 | | | Bi::WorkGroupSize |
352 | | | Bi::NumWorkGroups => ( |
353 | 0 | self.stage.compute_like() && !self.output, |
354 | 0 | *ty_inner |
355 | 0 | == Ti::Vector { |
356 | 0 | size: Vs::Tri, |
357 | 0 | scalar: crate::Scalar::U32, |
358 | 0 | }, |
359 | | ), |
360 | | Bi::NumSubgroups | Bi::SubgroupId => ( |
361 | 0 | self.stage.compute_like() && !self.output, |
362 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::U32), |
363 | | ), |
364 | | Bi::SubgroupSize | Bi::SubgroupInvocationId => ( |
365 | 0 | match self.stage { |
366 | 0 | St::Compute | St::Fragment | St::Task | St::Mesh => !self.output, |
367 | 0 | St::Vertex => false, |
368 | | }, |
369 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::U32), |
370 | | ), |
371 | 0 | Bi::CullPrimitive => ( |
372 | 0 | self.mesh_output_type == MeshOutputType::PrimitiveOutput, |
373 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::BOOL), |
374 | 0 | ), |
375 | 0 | Bi::PointIndex => ( |
376 | 0 | self.mesh_output_type == MeshOutputType::PrimitiveOutput, |
377 | 0 | *ty_inner == Ti::Scalar(crate::Scalar::U32), |
378 | 0 | ), |
379 | 0 | Bi::LineIndices => ( |
380 | 0 | self.mesh_output_type == MeshOutputType::PrimitiveOutput, |
381 | 0 | *ty_inner |
382 | 0 | == Ti::Vector { |
383 | 0 | size: Vs::Bi, |
384 | 0 | scalar: crate::Scalar::U32, |
385 | 0 | }, |
386 | 0 | ), |
387 | 0 | Bi::TriangleIndices => ( |
388 | 0 | self.mesh_output_type == MeshOutputType::PrimitiveOutput, |
389 | 0 | *ty_inner |
390 | 0 | == Ti::Vector { |
391 | 0 | size: Vs::Tri, |
392 | 0 | scalar: crate::Scalar::U32, |
393 | 0 | }, |
394 | 0 | ), |
395 | | Bi::MeshTaskSize => ( |
396 | 0 | self.stage == St::Task && self.output, |
397 | 0 | *ty_inner |
398 | 0 | == Ti::Vector { |
399 | 0 | size: Vs::Tri, |
400 | 0 | scalar: crate::Scalar::U32, |
401 | 0 | }, |
402 | | ), |
403 | | // Validated elsewhere, shouldn't be here |
404 | | Bi::VertexCount | Bi::PrimitiveCount | Bi::Vertices | Bi::Primitives => { |
405 | 0 | (false, true) |
406 | | } |
407 | | }; |
408 | 0 | match built_in { |
409 | | Bi::CullPrimitive |
410 | | | Bi::PointIndex |
411 | | | Bi::LineIndices |
412 | | | Bi::TriangleIndices |
413 | | | Bi::MeshTaskSize |
414 | | | Bi::VertexCount |
415 | | | Bi::PrimitiveCount |
416 | | | Bi::Vertices |
417 | | | Bi::Primitives => { |
418 | 0 | if !self.capabilities.contains(Capabilities::MESH_SHADER) { |
419 | 0 | return Err(VaryingError::UnsupportedCapability( |
420 | 0 | Capabilities::MESH_SHADER, |
421 | 0 | )); |
422 | 0 | } |
423 | | } |
424 | 0 | _ => (), |
425 | | } |
426 | | |
427 | 0 | if !visible { |
428 | 0 | return Err(VaryingError::InvalidBuiltInStage(built_in)); |
429 | 0 | } |
430 | 0 | if !type_good { |
431 | 0 | return Err(VaryingError::InvalidBuiltInType(built_in, ty_inner.clone())); |
432 | 0 | } |
433 | | } |
434 | | crate::Binding::Location { |
435 | 0 | location, |
436 | 0 | interpolation, |
437 | 0 | sampling, |
438 | 0 | blend_src, |
439 | 0 | per_primitive, |
440 | | } => { |
441 | 0 | if per_primitive && !self.capabilities.contains(Capabilities::MESH_SHADER) { |
442 | 0 | return Err(VaryingError::UnsupportedCapability( |
443 | 0 | Capabilities::MESH_SHADER, |
444 | 0 | )); |
445 | 0 | } |
446 | | // Only IO-shareable types may be stored in locations. |
447 | 0 | if !self.type_info[ty.index()] |
448 | 0 | .flags |
449 | 0 | .contains(super::TypeFlags::IO_SHAREABLE) |
450 | | { |
451 | 0 | return Err(VaryingError::NotIOShareableType(ty)); |
452 | 0 | } |
453 | | |
454 | | // Check whether `per_primitive` is appropriate for this stage and direction. |
455 | 0 | if self.mesh_output_type == MeshOutputType::PrimitiveOutput { |
456 | | // All mesh shader `Location` outputs must be `per_primitive`. |
457 | 0 | if !per_primitive { |
458 | 0 | return Err(VaryingError::MissingPerPrimitive); |
459 | 0 | } |
460 | 0 | } else if self.stage == crate::ShaderStage::Fragment && !self.output { |
461 | 0 | // Fragment stage inputs may be `per_primitive`. We'll only |
462 | 0 | // know if these are correct when the whole mesh pipeline is |
463 | 0 | // created and we're paired with a specific mesh or vertex |
464 | 0 | // shader. |
465 | 0 | } else if per_primitive { |
466 | | // All other `Location` bindings must not be `per_primitive`. |
467 | 0 | return Err(VaryingError::InvalidPerPrimitive); |
468 | 0 | } |
469 | | |
470 | 0 | if let Some(blend_src) = blend_src { |
471 | | // `blend_src` is only valid if dual source blending was explicitly enabled, |
472 | | // see https://www.w3.org/TR/WGSL/#extension-dual_source_blending |
473 | 0 | if !self |
474 | 0 | .capabilities |
475 | 0 | .contains(Capabilities::DUAL_SOURCE_BLENDING) |
476 | | { |
477 | 0 | return Err(VaryingError::UnsupportedCapability( |
478 | 0 | Capabilities::DUAL_SOURCE_BLENDING, |
479 | 0 | )); |
480 | 0 | } |
481 | 0 | if self.stage != crate::ShaderStage::Fragment { |
482 | 0 | return Err(VaryingError::InvalidAttributeInStage( |
483 | 0 | "blend_src", |
484 | 0 | self.stage, |
485 | 0 | )); |
486 | 0 | } |
487 | 0 | if !self.output { |
488 | 0 | return Err(VaryingError::InvalidInputAttributeInStage( |
489 | 0 | "blend_src", |
490 | 0 | self.stage, |
491 | 0 | )); |
492 | 0 | } |
493 | 0 | if (blend_src != 0 && blend_src != 1) || location != 0 { |
494 | 0 | return Err(VaryingError::InvalidBlendSrcIndex { |
495 | 0 | location, |
496 | 0 | blend_src, |
497 | 0 | }); |
498 | 0 | } |
499 | 0 | if !self.blend_src_mask.insert(blend_src as usize) { |
500 | 0 | return Err(VaryingError::BindingCollisionBlendSrc { blend_src }); |
501 | 0 | } |
502 | 0 | } else if !self.location_mask.insert(location as usize) |
503 | 0 | && self.flags.contains(super::ValidationFlags::BINDINGS) |
504 | | { |
505 | 0 | return Err(VaryingError::BindingCollision { location }); |
506 | 0 | } |
507 | | |
508 | 0 | if let Some(interpolation) = interpolation { |
509 | 0 | let invalid_sampling = match (interpolation, sampling) { |
510 | | (_, None) |
511 | | | ( |
512 | | crate::Interpolation::Perspective | crate::Interpolation::Linear, |
513 | | Some( |
514 | | crate::Sampling::Center |
515 | | | crate::Sampling::Centroid |
516 | | | crate::Sampling::Sample, |
517 | | ), |
518 | | ) |
519 | | | ( |
520 | | crate::Interpolation::Flat, |
521 | | Some(crate::Sampling::First | crate::Sampling::Either), |
522 | 0 | ) => None, |
523 | 0 | (_, Some(invalid_sampling)) => Some(invalid_sampling), |
524 | | }; |
525 | 0 | if let Some(sampling) = invalid_sampling { |
526 | 0 | return Err(VaryingError::InvalidInterpolationSamplingCombination { |
527 | 0 | interpolation, |
528 | 0 | sampling, |
529 | 0 | }); |
530 | 0 | } |
531 | 0 | } |
532 | | |
533 | 0 | let needs_interpolation = match self.stage { |
534 | 0 | crate::ShaderStage::Vertex => self.output, |
535 | 0 | crate::ShaderStage::Fragment => !self.output && !per_primitive, |
536 | 0 | crate::ShaderStage::Compute | crate::ShaderStage::Task => false, |
537 | 0 | crate::ShaderStage::Mesh => self.output, |
538 | | }; |
539 | | |
540 | | // It doesn't make sense to specify a sampling when `interpolation` is `Flat`, but |
541 | | // SPIR-V and GLSL both explicitly tolerate such combinations of decorators / |
542 | | // qualifiers, so we won't complain about that here. |
543 | 0 | let _ = sampling; |
544 | | |
545 | 0 | let required = match sampling { |
546 | 0 | Some(crate::Sampling::Sample) => Capabilities::MULTISAMPLED_SHADING, |
547 | 0 | _ => Capabilities::empty(), |
548 | | }; |
549 | 0 | if !self.capabilities.contains(required) { |
550 | 0 | return Err(VaryingError::UnsupportedCapability(required)); |
551 | 0 | } |
552 | | |
553 | 0 | match ty_inner.scalar_kind() { |
554 | | Some(crate::ScalarKind::Float) => { |
555 | 0 | if needs_interpolation && interpolation.is_none() { |
556 | 0 | return Err(VaryingError::MissingInterpolation); |
557 | 0 | } |
558 | | } |
559 | | Some(_) => { |
560 | 0 | if needs_interpolation && interpolation != Some(crate::Interpolation::Flat) |
561 | | { |
562 | 0 | return Err(VaryingError::InvalidInterpolation); |
563 | 0 | } |
564 | | } |
565 | 0 | None => return Err(VaryingError::InvalidType(ty)), |
566 | | } |
567 | | } |
568 | | } |
569 | | |
570 | 0 | Ok(()) |
571 | 0 | } |
572 | | |
573 | 0 | fn validate( |
574 | 0 | &mut self, |
575 | 0 | ep: &crate::EntryPoint, |
576 | 0 | ty: Handle<crate::Type>, |
577 | 0 | binding: Option<&crate::Binding>, |
578 | 0 | ) -> Result<(), WithSpan<VaryingError>> { |
579 | 0 | let span_context = self.types.get_span_context(ty); |
580 | 0 | match binding { |
581 | 0 | Some(binding) => self |
582 | 0 | .validate_impl(ep, ty, binding) |
583 | 0 | .map_err(|e| e.with_span_context(span_context)), |
584 | | None => { |
585 | 0 | let crate::TypeInner::Struct { ref members, .. } = self.types[ty].inner else { |
586 | 0 | if self.flags.contains(super::ValidationFlags::BINDINGS) { |
587 | 0 | return Err(VaryingError::MissingBinding.with_span()); |
588 | | } else { |
589 | 0 | return Ok(()); |
590 | | } |
591 | | }; |
592 | | |
593 | 0 | for (index, member) in members.iter().enumerate() { |
594 | 0 | let span_context = self.types.get_span_context(ty); |
595 | 0 | match member.binding { |
596 | | None => { |
597 | 0 | if self.flags.contains(super::ValidationFlags::BINDINGS) { |
598 | 0 | return Err(VaryingError::MemberMissingBinding(index as u32) |
599 | 0 | .with_span_context(span_context)); |
600 | 0 | } |
601 | | } |
602 | 0 | Some(ref binding) => self |
603 | 0 | .validate_impl(ep, member.ty, binding) |
604 | 0 | .map_err(|e| e.with_span_context(span_context))?, |
605 | | } |
606 | | } |
607 | | |
608 | 0 | if !self.blend_src_mask.is_empty() { |
609 | 0 | let span_context = self.types.get_span_context(ty); |
610 | | |
611 | | // If there's any blend_src usage, it must apply to all members of which there must be exactly 2. |
612 | 0 | if members.len() != 2 || self.blend_src_mask.len() != 2 { |
613 | 0 | return Err( |
614 | 0 | VaryingError::IncompleteBlendSrcUsage.with_span_context(span_context) |
615 | 0 | ); |
616 | 0 | } |
617 | | // Also, all members must have the same type. |
618 | 0 | if members[0].ty != members[1].ty { |
619 | 0 | return Err(VaryingError::BlendSrcOutputTypeMismatch { |
620 | 0 | blend_src_0_type: members[0].ty, |
621 | 0 | blend_src_1_type: members[1].ty, |
622 | 0 | } |
623 | 0 | .with_span_context(span_context)); |
624 | 0 | } |
625 | 0 | } |
626 | 0 | Ok(()) |
627 | | } |
628 | | } |
629 | 0 | } |
630 | | } |
631 | | |
632 | | impl super::Validator { |
633 | 0 | pub(super) fn validate_global_var( |
634 | 0 | &self, |
635 | 0 | var: &crate::GlobalVariable, |
636 | 0 | gctx: crate::proc::GlobalCtx, |
637 | 0 | mod_info: &ModuleInfo, |
638 | 0 | global_expr_kind: &crate::proc::ExpressionKindTracker, |
639 | 0 | ) -> Result<(), GlobalVariableError> { |
640 | | use super::TypeFlags; |
641 | | |
642 | 0 | log::debug!("var {var:?}"); |
643 | 0 | let inner_ty = match gctx.types[var.ty].inner { |
644 | | // A binding array is (mostly) supposed to behave the same as a |
645 | | // series of individually bound resources, so we can (mostly) |
646 | | // validate a `binding_array<T>` as if it were just a plain `T`. |
647 | 0 | crate::TypeInner::BindingArray { base, .. } => match var.space { |
648 | | crate::AddressSpace::Storage { .. } => { |
649 | 0 | if !self |
650 | 0 | .capabilities |
651 | 0 | .contains(Capabilities::STORAGE_BUFFER_BINDING_ARRAY) |
652 | | { |
653 | 0 | return Err(GlobalVariableError::UnsupportedCapability( |
654 | 0 | Capabilities::STORAGE_BUFFER_BINDING_ARRAY, |
655 | 0 | )); |
656 | 0 | } |
657 | 0 | base |
658 | | } |
659 | | crate::AddressSpace::Uniform => { |
660 | 0 | if !self |
661 | 0 | .capabilities |
662 | 0 | .contains(Capabilities::BUFFER_BINDING_ARRAY) |
663 | | { |
664 | 0 | return Err(GlobalVariableError::UnsupportedCapability( |
665 | 0 | Capabilities::BUFFER_BINDING_ARRAY, |
666 | 0 | )); |
667 | 0 | } |
668 | 0 | base |
669 | | } |
670 | | crate::AddressSpace::Handle => { |
671 | 0 | match gctx.types[base].inner { |
672 | 0 | crate::TypeInner::Image { class, .. } => match class { |
673 | | crate::ImageClass::Storage { .. } => { |
674 | 0 | if !self |
675 | 0 | .capabilities |
676 | 0 | .contains(Capabilities::STORAGE_TEXTURE_BINDING_ARRAY) |
677 | | { |
678 | 0 | return Err(GlobalVariableError::UnsupportedCapability( |
679 | 0 | Capabilities::STORAGE_TEXTURE_BINDING_ARRAY, |
680 | 0 | )); |
681 | 0 | } |
682 | | } |
683 | | crate::ImageClass::Sampled { .. } | crate::ImageClass::Depth { .. } => { |
684 | 0 | if !self |
685 | 0 | .capabilities |
686 | 0 | .contains(Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY) |
687 | | { |
688 | 0 | return Err(GlobalVariableError::UnsupportedCapability( |
689 | 0 | Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY, |
690 | 0 | )); |
691 | 0 | } |
692 | | } |
693 | | crate::ImageClass::External => { |
694 | | // This should have been rejected in `validate_type`. |
695 | 0 | unreachable!("binding arrays of external images are not supported"); |
696 | | } |
697 | | }, |
698 | | crate::TypeInner::Sampler { .. } => { |
699 | 0 | if !self |
700 | 0 | .capabilities |
701 | 0 | .contains(Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY) |
702 | | { |
703 | 0 | return Err(GlobalVariableError::UnsupportedCapability( |
704 | 0 | Capabilities::TEXTURE_AND_SAMPLER_BINDING_ARRAY, |
705 | 0 | )); |
706 | 0 | } |
707 | | } |
708 | | crate::TypeInner::AccelerationStructure { .. } => { |
709 | 0 | return Err(GlobalVariableError::InvalidBindingArray(base)); |
710 | | } |
711 | | crate::TypeInner::RayQuery { .. } => { |
712 | | // This should have been rejected in `validate_type`. |
713 | 0 | unreachable!("binding arrays of ray queries are not supported"); |
714 | | } |
715 | 0 | _ => { |
716 | 0 | // Fall through to the regular validation, which will reject `base` |
717 | 0 | // as invalid in `AddressSpace::Handle`. |
718 | 0 | } |
719 | | } |
720 | 0 | base |
721 | | } |
722 | 0 | _ => return Err(GlobalVariableError::InvalidUsage(var.space)), |
723 | | }, |
724 | 0 | _ => var.ty, |
725 | | }; |
726 | 0 | let type_info = &self.types[inner_ty.index()]; |
727 | | |
728 | 0 | let (required_type_flags, is_resource) = match var.space { |
729 | | crate::AddressSpace::Function => { |
730 | 0 | return Err(GlobalVariableError::InvalidUsage(var.space)) |
731 | | } |
732 | 0 | crate::AddressSpace::Storage { access } => { |
733 | 0 | if let Err((ty_handle, disalignment)) = type_info.storage_layout { |
734 | 0 | if self.flags.contains(super::ValidationFlags::STRUCT_LAYOUTS) { |
735 | 0 | return Err(GlobalVariableError::Alignment( |
736 | 0 | var.space, |
737 | 0 | ty_handle, |
738 | 0 | disalignment, |
739 | 0 | )); |
740 | 0 | } |
741 | 0 | } |
742 | 0 | if access == crate::StorageAccess::STORE { |
743 | 0 | return Err(GlobalVariableError::StorageAddressSpaceWriteOnlyNotSupported); |
744 | 0 | } |
745 | 0 | ( |
746 | 0 | TypeFlags::DATA | TypeFlags::HOST_SHAREABLE | TypeFlags::CREATION_RESOLVED, |
747 | 0 | true, |
748 | 0 | ) |
749 | | } |
750 | | crate::AddressSpace::Uniform => { |
751 | 0 | if let Err((ty_handle, disalignment)) = type_info.uniform_layout { |
752 | 0 | if self.flags.contains(super::ValidationFlags::STRUCT_LAYOUTS) { |
753 | 0 | return Err(GlobalVariableError::Alignment( |
754 | 0 | var.space, |
755 | 0 | ty_handle, |
756 | 0 | disalignment, |
757 | 0 | )); |
758 | 0 | } |
759 | 0 | } |
760 | 0 | ( |
761 | 0 | TypeFlags::DATA |
762 | 0 | | TypeFlags::COPY |
763 | 0 | | TypeFlags::SIZED |
764 | 0 | | TypeFlags::HOST_SHAREABLE |
765 | 0 | | TypeFlags::CREATION_RESOLVED, |
766 | 0 | true, |
767 | 0 | ) |
768 | | } |
769 | | crate::AddressSpace::Handle => { |
770 | 0 | match gctx.types[inner_ty].inner { |
771 | 0 | crate::TypeInner::Image { class, .. } => match class { |
772 | | crate::ImageClass::Storage { |
773 | | format: |
774 | | crate::StorageFormat::R16Unorm |
775 | | | crate::StorageFormat::R16Snorm |
776 | | | crate::StorageFormat::Rg16Unorm |
777 | | | crate::StorageFormat::Rg16Snorm |
778 | | | crate::StorageFormat::Rgba16Unorm |
779 | | | crate::StorageFormat::Rgba16Snorm, |
780 | | .. |
781 | | } => { |
782 | 0 | if !self |
783 | 0 | .capabilities |
784 | 0 | .contains(Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS) |
785 | | { |
786 | 0 | return Err(GlobalVariableError::UnsupportedCapability( |
787 | 0 | Capabilities::STORAGE_TEXTURE_16BIT_NORM_FORMATS, |
788 | 0 | )); |
789 | 0 | } |
790 | | } |
791 | 0 | _ => {} |
792 | | }, |
793 | | crate::TypeInner::Sampler { .. } |
794 | | | crate::TypeInner::AccelerationStructure { .. } |
795 | 0 | | crate::TypeInner::RayQuery { .. } => {} |
796 | | _ => { |
797 | 0 | return Err(GlobalVariableError::InvalidType(var.space)); |
798 | | } |
799 | | } |
800 | | |
801 | 0 | (TypeFlags::empty(), true) |
802 | | } |
803 | 0 | crate::AddressSpace::Private => ( |
804 | 0 | TypeFlags::CONSTRUCTIBLE | TypeFlags::CREATION_RESOLVED, |
805 | 0 | false, |
806 | 0 | ), |
807 | 0 | crate::AddressSpace::WorkGroup => (TypeFlags::DATA | TypeFlags::SIZED, false), |
808 | | crate::AddressSpace::TaskPayload => { |
809 | 0 | if !self.capabilities.contains(Capabilities::MESH_SHADER) { |
810 | 0 | return Err(GlobalVariableError::UnsupportedCapability( |
811 | 0 | Capabilities::MESH_SHADER, |
812 | 0 | )); |
813 | 0 | } |
814 | 0 | (TypeFlags::DATA | TypeFlags::SIZED, false) |
815 | | } |
816 | | crate::AddressSpace::Immediate => { |
817 | 0 | if !self.capabilities.contains(Capabilities::IMMEDIATES) { |
818 | 0 | return Err(GlobalVariableError::UnsupportedCapability( |
819 | 0 | Capabilities::IMMEDIATES, |
820 | 0 | )); |
821 | 0 | } |
822 | 0 | if let Err(ref err) = type_info.immediates_compatibility { |
823 | 0 | return Err(GlobalVariableError::InvalidImmediateType(err.clone())); |
824 | 0 | } |
825 | 0 | ( |
826 | 0 | TypeFlags::DATA |
827 | 0 | | TypeFlags::COPY |
828 | 0 | | TypeFlags::HOST_SHAREABLE |
829 | 0 | | TypeFlags::SIZED, |
830 | 0 | false, |
831 | 0 | ) |
832 | | } |
833 | | }; |
834 | | |
835 | 0 | if !type_info.flags.contains(required_type_flags) { |
836 | 0 | return Err(GlobalVariableError::MissingTypeFlags { |
837 | 0 | seen: type_info.flags, |
838 | 0 | required: required_type_flags, |
839 | 0 | }); |
840 | 0 | } |
841 | | |
842 | 0 | if is_resource != var.binding.is_some() { |
843 | 0 | if self.flags.contains(super::ValidationFlags::BINDINGS) { |
844 | 0 | return Err(GlobalVariableError::InvalidBinding); |
845 | 0 | } |
846 | 0 | } |
847 | | |
848 | 0 | if var.space == crate::AddressSpace::TaskPayload { |
849 | 0 | let ty = &gctx.types[var.ty].inner; |
850 | | // HLSL doesn't allow zero sized payloads. |
851 | 0 | if ty.try_size(gctx) == Some(0) { |
852 | 0 | return Err(GlobalVariableError::ZeroSizedTaskPayload); |
853 | 0 | } |
854 | 0 | } |
855 | | |
856 | 0 | if let Some(init) = var.init { |
857 | 0 | match var.space { |
858 | 0 | crate::AddressSpace::Private | crate::AddressSpace::Function => {} |
859 | | _ => { |
860 | 0 | return Err(GlobalVariableError::InitializerNotAllowed(var.space)); |
861 | | } |
862 | | } |
863 | | |
864 | 0 | if !global_expr_kind.is_const_or_override(init) { |
865 | 0 | return Err(GlobalVariableError::InitializerExprType); |
866 | 0 | } |
867 | | |
868 | 0 | if !gctx.compare_types( |
869 | 0 | &crate::proc::TypeResolution::Handle(var.ty), |
870 | 0 | &mod_info[init], |
871 | 0 | ) { |
872 | 0 | return Err(GlobalVariableError::InitializerType); |
873 | 0 | } |
874 | 0 | } |
875 | | |
876 | 0 | Ok(()) |
877 | 0 | } |
878 | | |
879 | | /// Validate the mesh shader output type `ty`, used as `mesh_output_type`. |
880 | 0 | fn validate_mesh_output_type( |
881 | 0 | &mut self, |
882 | 0 | ep: &crate::EntryPoint, |
883 | 0 | module: &crate::Module, |
884 | 0 | ty: Handle<crate::Type>, |
885 | 0 | mesh_output_type: MeshOutputType, |
886 | 0 | ) -> Result<(), WithSpan<EntryPointError>> { |
887 | 0 | if !matches!(module.types[ty].inner, crate::TypeInner::Struct { .. }) { |
888 | 0 | return Err(EntryPointError::InvalidMeshOutputType.with_span_handle(ty, &module.types)); |
889 | 0 | } |
890 | 0 | let mut result_built_ins = crate::FastHashSet::default(); |
891 | 0 | let mut ctx = VaryingContext { |
892 | 0 | stage: ep.stage, |
893 | 0 | output: true, |
894 | 0 | types: &module.types, |
895 | 0 | type_info: &self.types, |
896 | 0 | location_mask: &mut self.location_mask, |
897 | 0 | blend_src_mask: &mut self.blend_src_mask, |
898 | 0 | built_ins: &mut result_built_ins, |
899 | 0 | capabilities: self.capabilities, |
900 | 0 | flags: self.flags, |
901 | 0 | mesh_output_type, |
902 | 0 | has_task_payload: ep.task_payload.is_some(), |
903 | 0 | }; |
904 | 0 | ctx.validate(ep, ty, None) |
905 | 0 | .map_err_inner(|e| EntryPointError::Result(e).with_span())?; |
906 | 0 | if mesh_output_type == MeshOutputType::PrimitiveOutput { |
907 | 0 | let mut num_indices_builtins = 0; |
908 | 0 | if result_built_ins.contains(&crate::BuiltIn::PointIndex) { |
909 | 0 | num_indices_builtins += 1; |
910 | 0 | } |
911 | 0 | if result_built_ins.contains(&crate::BuiltIn::LineIndices) { |
912 | 0 | num_indices_builtins += 1; |
913 | 0 | } |
914 | 0 | if result_built_ins.contains(&crate::BuiltIn::TriangleIndices) { |
915 | 0 | num_indices_builtins += 1; |
916 | 0 | } |
917 | 0 | if num_indices_builtins != 1 { |
918 | 0 | return Err(EntryPointError::InvalidMeshPrimitiveOutputType |
919 | 0 | .with_span_handle(ty, &module.types)); |
920 | 0 | } |
921 | 0 | } else if mesh_output_type == MeshOutputType::VertexOutput |
922 | 0 | && !result_built_ins.contains(&crate::BuiltIn::Position { invariant: false }) |
923 | | { |
924 | 0 | return Err( |
925 | 0 | EntryPointError::MissingVertexOutputPosition.with_span_handle(ty, &module.types) |
926 | 0 | ); |
927 | 0 | } |
928 | | |
929 | 0 | Ok(()) |
930 | 0 | } |
931 | | |
932 | 0 | pub(super) fn validate_entry_point( |
933 | 0 | &mut self, |
934 | 0 | ep: &crate::EntryPoint, |
935 | 0 | module: &crate::Module, |
936 | 0 | mod_info: &ModuleInfo, |
937 | 0 | ) -> Result<FunctionInfo, WithSpan<EntryPointError>> { |
938 | 0 | if matches!( |
939 | 0 | ep.stage, |
940 | | crate::ShaderStage::Task | crate::ShaderStage::Mesh |
941 | 0 | ) && !self.capabilities.contains(Capabilities::MESH_SHADER) |
942 | | { |
943 | 0 | return Err( |
944 | 0 | EntryPointError::UnsupportedCapability(Capabilities::MESH_SHADER).with_span(), |
945 | 0 | ); |
946 | 0 | } |
947 | 0 | if ep.early_depth_test.is_some() { |
948 | 0 | let required = Capabilities::EARLY_DEPTH_TEST; |
949 | 0 | if !self.capabilities.contains(required) { |
950 | 0 | return Err( |
951 | 0 | EntryPointError::Result(VaryingError::UnsupportedCapability(required)) |
952 | 0 | .with_span(), |
953 | 0 | ); |
954 | 0 | } |
955 | | |
956 | 0 | if ep.stage != crate::ShaderStage::Fragment { |
957 | 0 | return Err(EntryPointError::UnexpectedEarlyDepthTest.with_span()); |
958 | 0 | } |
959 | 0 | } |
960 | | |
961 | 0 | if ep.stage.compute_like() { |
962 | 0 | if ep |
963 | 0 | .workgroup_size |
964 | 0 | .iter() |
965 | 0 | .any(|&s| s == 0 || s > MAX_WORKGROUP_SIZE) |
966 | | { |
967 | 0 | return Err(EntryPointError::OutOfRangeWorkgroupSize.with_span()); |
968 | 0 | } |
969 | 0 | } else if ep.workgroup_size != [0; 3] { |
970 | 0 | return Err(EntryPointError::UnexpectedWorkgroupSize.with_span()); |
971 | 0 | } |
972 | | |
973 | 0 | match (ep.stage, &ep.mesh_info) { |
974 | | (crate::ShaderStage::Mesh, &None) => { |
975 | 0 | return Err(EntryPointError::ExpectedMeshShaderAttributes.with_span()); |
976 | | } |
977 | 0 | (crate::ShaderStage::Mesh, &Some(..)) => {} |
978 | | (_, &Some(_)) => { |
979 | 0 | return Err(EntryPointError::UnexpectedMeshShaderAttributes.with_span()); |
980 | | } |
981 | 0 | (_, _) => {} |
982 | | } |
983 | | |
984 | 0 | let mut info = self |
985 | 0 | .validate_function(&ep.function, module, mod_info, true) |
986 | 0 | .map_err(WithSpan::into_other)?; |
987 | | |
988 | | // Validate the task shader payload. |
989 | 0 | match ep.stage { |
990 | | // Task shaders must produce a payload. |
991 | | crate::ShaderStage::Task => { |
992 | 0 | let Some(handle) = ep.task_payload else { |
993 | 0 | return Err(EntryPointError::ExpectedTaskPayload.with_span()); |
994 | | }; |
995 | 0 | if module.global_variables[handle].space != crate::AddressSpace::TaskPayload { |
996 | 0 | return Err(EntryPointError::TaskPayloadWrongAddressSpace |
997 | 0 | .with_span_handle(handle, &module.global_variables)); |
998 | 0 | } |
999 | 0 | info.insert_global_use(GlobalUse::READ | GlobalUse::WRITE, handle); |
1000 | | } |
1001 | | |
1002 | | // Mesh shaders may accept a payload. |
1003 | | crate::ShaderStage::Mesh => { |
1004 | 0 | if let Some(handle) = ep.task_payload { |
1005 | 0 | if module.global_variables[handle].space != crate::AddressSpace::TaskPayload { |
1006 | 0 | return Err(EntryPointError::TaskPayloadWrongAddressSpace |
1007 | 0 | .with_span_handle(handle, &module.global_variables)); |
1008 | 0 | } |
1009 | 0 | info.insert_global_use(GlobalUse::READ, handle); |
1010 | 0 | } |
1011 | 0 | if let Some(ref mesh_info) = ep.mesh_info { |
1012 | 0 | info.insert_global_use(GlobalUse::READ, mesh_info.output_variable); |
1013 | 0 | } |
1014 | | } |
1015 | | |
1016 | | // Other stages must not have a payload. |
1017 | | _ => { |
1018 | 0 | if let Some(handle) = ep.task_payload { |
1019 | 0 | return Err(EntryPointError::UnexpectedTaskPayload |
1020 | 0 | .with_span_handle(handle, &module.global_variables)); |
1021 | 0 | } |
1022 | | } |
1023 | | } |
1024 | | |
1025 | | { |
1026 | | use super::ShaderStages; |
1027 | | |
1028 | 0 | let stage_bit = match ep.stage { |
1029 | 0 | crate::ShaderStage::Vertex => ShaderStages::VERTEX, |
1030 | 0 | crate::ShaderStage::Fragment => ShaderStages::FRAGMENT, |
1031 | 0 | crate::ShaderStage::Compute => ShaderStages::COMPUTE, |
1032 | 0 | crate::ShaderStage::Mesh => ShaderStages::MESH, |
1033 | 0 | crate::ShaderStage::Task => ShaderStages::TASK, |
1034 | | }; |
1035 | | |
1036 | 0 | if !info.available_stages.contains(stage_bit) { |
1037 | 0 | return Err(EntryPointError::ForbiddenStageOperations.with_span()); |
1038 | 0 | } |
1039 | | } |
1040 | | |
1041 | 0 | self.location_mask.clear(); |
1042 | 0 | let mut argument_built_ins = crate::FastHashSet::default(); |
1043 | | // TODO: add span info to function arguments |
1044 | 0 | for (index, fa) in ep.function.arguments.iter().enumerate() { |
1045 | 0 | let mut ctx = VaryingContext { |
1046 | 0 | stage: ep.stage, |
1047 | 0 | output: false, |
1048 | 0 | types: &module.types, |
1049 | 0 | type_info: &self.types, |
1050 | 0 | location_mask: &mut self.location_mask, |
1051 | 0 | blend_src_mask: &mut self.blend_src_mask, |
1052 | 0 | built_ins: &mut argument_built_ins, |
1053 | 0 | capabilities: self.capabilities, |
1054 | 0 | flags: self.flags, |
1055 | 0 | mesh_output_type: MeshOutputType::None, |
1056 | 0 | has_task_payload: ep.task_payload.is_some(), |
1057 | 0 | }; |
1058 | 0 | ctx.validate(ep, fa.ty, fa.binding.as_ref()) |
1059 | 0 | .map_err_inner(|e| EntryPointError::Argument(index as u32, e).with_span())?; |
1060 | | } |
1061 | | |
1062 | 0 | self.location_mask.clear(); |
1063 | 0 | if let Some(ref fr) = ep.function.result { |
1064 | 0 | let mut result_built_ins = crate::FastHashSet::default(); |
1065 | 0 | let mut ctx = VaryingContext { |
1066 | 0 | stage: ep.stage, |
1067 | 0 | output: true, |
1068 | 0 | types: &module.types, |
1069 | 0 | type_info: &self.types, |
1070 | 0 | location_mask: &mut self.location_mask, |
1071 | 0 | blend_src_mask: &mut self.blend_src_mask, |
1072 | 0 | built_ins: &mut result_built_ins, |
1073 | 0 | capabilities: self.capabilities, |
1074 | 0 | flags: self.flags, |
1075 | 0 | mesh_output_type: MeshOutputType::None, |
1076 | 0 | has_task_payload: ep.task_payload.is_some(), |
1077 | 0 | }; |
1078 | 0 | ctx.validate(ep, fr.ty, fr.binding.as_ref()) |
1079 | 0 | .map_err_inner(|e| EntryPointError::Result(e).with_span())?; |
1080 | 0 | if ep.stage == crate::ShaderStage::Vertex |
1081 | 0 | && !result_built_ins.contains(&crate::BuiltIn::Position { invariant: false }) |
1082 | | { |
1083 | 0 | return Err(EntryPointError::MissingVertexOutputPosition.with_span()); |
1084 | 0 | } |
1085 | 0 | if ep.stage == crate::ShaderStage::Mesh { |
1086 | 0 | return Err(EntryPointError::UnexpectedMeshShaderEntryResult.with_span()); |
1087 | 0 | } |
1088 | | // Task shaders must have a single `MeshTaskSize` output, and nothing else. |
1089 | 0 | if ep.stage == crate::ShaderStage::Task { |
1090 | 0 | let ok = result_built_ins.contains(&crate::BuiltIn::MeshTaskSize) |
1091 | 0 | && result_built_ins.len() == 1 |
1092 | 0 | && self.location_mask.is_empty(); |
1093 | 0 | if !ok { |
1094 | 0 | return Err(EntryPointError::WrongTaskShaderEntryResult.with_span()); |
1095 | 0 | } |
1096 | 0 | } |
1097 | 0 | if !self.blend_src_mask.is_empty() { |
1098 | 0 | info.dual_source_blending = true; |
1099 | 0 | } |
1100 | 0 | } else if ep.stage == crate::ShaderStage::Vertex { |
1101 | 0 | return Err(EntryPointError::MissingVertexOutputPosition.with_span()); |
1102 | 0 | } else if ep.stage == crate::ShaderStage::Task { |
1103 | 0 | return Err(EntryPointError::WrongTaskShaderEntryResult.with_span()); |
1104 | 0 | } |
1105 | | |
1106 | | { |
1107 | 0 | let mut used_immediates = module |
1108 | 0 | .global_variables |
1109 | 0 | .iter() |
1110 | 0 | .filter(|&(_, var)| var.space == crate::AddressSpace::Immediate) |
1111 | 0 | .map(|(handle, _)| handle) |
1112 | 0 | .filter(|&handle| !info[handle].is_empty()); |
1113 | | // Check if there is more than one immediate data, and error if so. |
1114 | | // Use a loop for when returning multiple errors is supported. |
1115 | 0 | if let Some(handle) = used_immediates.nth(1) { |
1116 | 0 | return Err(EntryPointError::MoreThanOneImmediateUsed |
1117 | 0 | .with_span_handle(handle, &module.global_variables)); |
1118 | 0 | } |
1119 | | } |
1120 | | |
1121 | 0 | self.ep_resource_bindings.clear(); |
1122 | 0 | for (var_handle, var) in module.global_variables.iter() { |
1123 | 0 | let usage = info[var_handle]; |
1124 | 0 | if usage.is_empty() { |
1125 | 0 | continue; |
1126 | 0 | } |
1127 | | |
1128 | 0 | if var.space == crate::AddressSpace::TaskPayload { |
1129 | 0 | if ep.task_payload != Some(var_handle) { |
1130 | 0 | return Err(EntryPointError::WrongTaskPayloadUsed |
1131 | 0 | .with_span_handle(var_handle, &module.global_variables)); |
1132 | 0 | } |
1133 | 0 | let size = module.types[var.ty].inner.size(module.to_ctx()); |
1134 | 0 | if size < 4 { |
1135 | 0 | return Err(EntryPointError::TaskPayloadTooSmall(size) |
1136 | 0 | .with_span_handle(var_handle, &module.global_variables)); |
1137 | 0 | } |
1138 | 0 | } |
1139 | | |
1140 | 0 | let allowed_usage = match var.space { |
1141 | 0 | crate::AddressSpace::Function => unreachable!(), |
1142 | 0 | crate::AddressSpace::Uniform => GlobalUse::READ | GlobalUse::QUERY, |
1143 | 0 | crate::AddressSpace::Storage { access } => storage_usage(access), |
1144 | 0 | crate::AddressSpace::Handle => match module.types[var.ty].inner { |
1145 | 0 | crate::TypeInner::BindingArray { base, .. } => match module.types[base].inner { |
1146 | | crate::TypeInner::Image { |
1147 | 0 | class: crate::ImageClass::Storage { access, .. }, |
1148 | | .. |
1149 | 0 | } => storage_usage(access), |
1150 | 0 | _ => GlobalUse::READ | GlobalUse::QUERY, |
1151 | | }, |
1152 | | crate::TypeInner::Image { |
1153 | 0 | class: crate::ImageClass::Storage { access, .. }, |
1154 | | .. |
1155 | 0 | } => storage_usage(access), |
1156 | 0 | _ => GlobalUse::READ | GlobalUse::QUERY, |
1157 | | }, |
1158 | | crate::AddressSpace::Private | crate::AddressSpace::WorkGroup => { |
1159 | 0 | GlobalUse::READ | GlobalUse::WRITE | GlobalUse::QUERY |
1160 | | } |
1161 | | crate::AddressSpace::TaskPayload => { |
1162 | 0 | GlobalUse::READ |
1163 | 0 | | GlobalUse::QUERY |
1164 | 0 | | if ep.stage == crate::ShaderStage::Task { |
1165 | 0 | GlobalUse::WRITE |
1166 | | } else { |
1167 | 0 | GlobalUse::empty() |
1168 | | } |
1169 | | } |
1170 | 0 | crate::AddressSpace::Immediate => GlobalUse::READ, |
1171 | | }; |
1172 | 0 | if !allowed_usage.contains(usage) { |
1173 | 0 | log::warn!("\tUsage error for: {var:?}"); |
1174 | 0 | log::warn!("\tAllowed usage: {allowed_usage:?}, requested: {usage:?}"); |
1175 | 0 | return Err(EntryPointError::InvalidGlobalUsage(var_handle, usage) |
1176 | 0 | .with_span_handle(var_handle, &module.global_variables)); |
1177 | 0 | } |
1178 | | |
1179 | 0 | if let Some(ref bind) = var.binding { |
1180 | 0 | if !self.ep_resource_bindings.insert(*bind) { |
1181 | 0 | if self.flags.contains(super::ValidationFlags::BINDINGS) { |
1182 | 0 | return Err(EntryPointError::BindingCollision(var_handle) |
1183 | 0 | .with_span_handle(var_handle, &module.global_variables)); |
1184 | 0 | } |
1185 | 0 | } |
1186 | 0 | } |
1187 | | } |
1188 | | |
1189 | | // If this is a `Mesh` entry point, check its vertex and primitive output types. |
1190 | | // We verified previously that only mesh shaders can have `mesh_info`. |
1191 | 0 | if let &Some(ref mesh_info) = &ep.mesh_info { |
1192 | 0 | if module.global_variables[mesh_info.output_variable].space |
1193 | 0 | != crate::AddressSpace::WorkGroup |
1194 | | { |
1195 | 0 | return Err(EntryPointError::WrongMeshOutputAddressSpace.with_span()); |
1196 | 0 | } |
1197 | | |
1198 | 0 | let mut implied = module.analyze_mesh_shader_info(mesh_info.output_variable); |
1199 | 0 | if let Some(e) = implied.2 { |
1200 | 0 | return Err(e); |
1201 | 0 | } |
1202 | | |
1203 | 0 | if let Some(e) = mesh_info.max_vertices_override { |
1204 | 0 | if let crate::Expression::Override(o) = module.global_expressions[e] { |
1205 | 0 | if implied.1[0] != Some(o) { |
1206 | 0 | return Err(EntryPointError::BadMeshOutputVariableType.with_span()); |
1207 | 0 | } |
1208 | 0 | } |
1209 | 0 | } |
1210 | 0 | if let Some(e) = mesh_info.max_primitives_override { |
1211 | 0 | if let crate::Expression::Override(o) = module.global_expressions[e] { |
1212 | 0 | if implied.1[1] != Some(o) { |
1213 | 0 | return Err(EntryPointError::BadMeshOutputVariableType.with_span()); |
1214 | 0 | } |
1215 | 0 | } |
1216 | 0 | } |
1217 | | |
1218 | 0 | implied.0.max_vertices_override = mesh_info.max_vertices_override; |
1219 | 0 | implied.0.max_primitives_override = mesh_info.max_primitives_override; |
1220 | 0 | if implied.0 != *mesh_info { |
1221 | 0 | return Err(EntryPointError::BadMeshOutputVariableType.with_span()); |
1222 | 0 | } |
1223 | 0 | if mesh_info.topology == crate::MeshOutputTopology::Points |
1224 | 0 | && !self |
1225 | 0 | .capabilities |
1226 | 0 | .contains(Capabilities::MESH_SHADER_POINT_TOPOLOGY) |
1227 | | { |
1228 | 0 | return Err(EntryPointError::UnsupportedCapability( |
1229 | 0 | Capabilities::MESH_SHADER_POINT_TOPOLOGY, |
1230 | 0 | ) |
1231 | 0 | .with_span()); |
1232 | 0 | } |
1233 | | |
1234 | 0 | self.validate_mesh_output_type( |
1235 | 0 | ep, |
1236 | 0 | module, |
1237 | 0 | mesh_info.vertex_output_type, |
1238 | 0 | MeshOutputType::VertexOutput, |
1239 | 0 | )?; |
1240 | 0 | self.validate_mesh_output_type( |
1241 | 0 | ep, |
1242 | 0 | module, |
1243 | 0 | mesh_info.primitive_output_type, |
1244 | 0 | MeshOutputType::PrimitiveOutput, |
1245 | 0 | )?; |
1246 | 0 | } |
1247 | | |
1248 | 0 | Ok(info) |
1249 | 0 | } |
1250 | | } |