wgpu_core/
validation.rs

1use crate::{device::bgl, resource::InvalidResourceError, FastHashMap, FastHashSet};
2use arrayvec::ArrayVec;
3use std::{collections::hash_map::Entry, fmt};
4use thiserror::Error;
5use wgt::{BindGroupLayoutEntry, BindingType};
6
7#[derive(Debug)]
8enum ResourceType {
9    Buffer {
10        size: wgt::BufferSize,
11    },
12    Texture {
13        dim: naga::ImageDimension,
14        arrayed: bool,
15        class: naga::ImageClass,
16    },
17    Sampler {
18        comparison: bool,
19    },
20    AccelerationStructure,
21}
22
23#[derive(Clone, Debug)]
24pub enum BindingTypeName {
25    Buffer,
26    Texture,
27    Sampler,
28    AccelerationStructure,
29}
30
31impl From<&ResourceType> for BindingTypeName {
32    fn from(ty: &ResourceType) -> BindingTypeName {
33        match ty {
34            ResourceType::Buffer { .. } => BindingTypeName::Buffer,
35            ResourceType::Texture { .. } => BindingTypeName::Texture,
36            ResourceType::Sampler { .. } => BindingTypeName::Sampler,
37            ResourceType::AccelerationStructure { .. } => BindingTypeName::AccelerationStructure,
38        }
39    }
40}
41
42impl From<&BindingType> for BindingTypeName {
43    fn from(ty: &BindingType) -> BindingTypeName {
44        match ty {
45            BindingType::Buffer { .. } => BindingTypeName::Buffer,
46            BindingType::Texture { .. } => BindingTypeName::Texture,
47            BindingType::StorageTexture { .. } => BindingTypeName::Texture,
48            BindingType::Sampler { .. } => BindingTypeName::Sampler,
49            BindingType::AccelerationStructure { .. } => BindingTypeName::AccelerationStructure,
50        }
51    }
52}
53
54#[derive(Debug)]
55struct Resource {
56    #[allow(unused)]
57    name: Option<String>,
58    bind: naga::ResourceBinding,
59    ty: ResourceType,
60    class: naga::AddressSpace,
61}
62
63#[derive(Clone, Copy, Debug)]
64enum NumericDimension {
65    Scalar,
66    Vector(naga::VectorSize),
67    Matrix(naga::VectorSize, naga::VectorSize),
68}
69
70impl fmt::Display for NumericDimension {
71    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
72        match *self {
73            Self::Scalar => write!(f, ""),
74            Self::Vector(size) => write!(f, "x{}", size as u8),
75            Self::Matrix(columns, rows) => write!(f, "x{}{}", columns as u8, rows as u8),
76        }
77    }
78}
79
80impl NumericDimension {
81    fn num_components(&self) -> u32 {
82        match *self {
83            Self::Scalar => 1,
84            Self::Vector(size) => size as u32,
85            Self::Matrix(w, h) => w as u32 * h as u32,
86        }
87    }
88}
89
90#[derive(Clone, Copy, Debug)]
91pub struct NumericType {
92    dim: NumericDimension,
93    scalar: naga::Scalar,
94}
95
96impl fmt::Display for NumericType {
97    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
98        write!(
99            f,
100            "{:?}{}{}",
101            self.scalar.kind,
102            self.scalar.width * 8,
103            self.dim
104        )
105    }
106}
107
108#[derive(Clone, Debug)]
109pub struct InterfaceVar {
110    pub ty: NumericType,
111    interpolation: Option<naga::Interpolation>,
112    sampling: Option<naga::Sampling>,
113}
114
115impl InterfaceVar {
116    pub fn vertex_attribute(format: wgt::VertexFormat) -> Self {
117        InterfaceVar {
118            ty: NumericType::from_vertex_format(format),
119            interpolation: None,
120            sampling: None,
121        }
122    }
123}
124
125impl fmt::Display for InterfaceVar {
126    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
127        write!(
128            f,
129            "{} interpolated as {:?} with sampling {:?}",
130            self.ty, self.interpolation, self.sampling
131        )
132    }
133}
134
135#[derive(Debug)]
136enum Varying {
137    Local { location: u32, iv: InterfaceVar },
138    BuiltIn(naga::BuiltIn),
139}
140
141#[allow(unused)]
142#[derive(Debug)]
143struct SpecializationConstant {
144    id: u32,
145    ty: NumericType,
146}
147
148#[derive(Debug, Default)]
149struct EntryPoint {
150    inputs: Vec<Varying>,
151    outputs: Vec<Varying>,
152    resources: Vec<naga::Handle<Resource>>,
153    #[allow(unused)]
154    spec_constants: Vec<SpecializationConstant>,
155    sampling_pairs: FastHashSet<(naga::Handle<Resource>, naga::Handle<Resource>)>,
156    workgroup_size: [u32; 3],
157    dual_source_blending: bool,
158}
159
160#[derive(Debug)]
161pub struct Interface {
162    limits: wgt::Limits,
163    resources: naga::Arena<Resource>,
164    entry_points: FastHashMap<(naga::ShaderStage, String), EntryPoint>,
165}
166
167#[derive(Clone, Debug, Error)]
168#[non_exhaustive]
169pub enum BindingError {
170    #[error("Binding is missing from the pipeline layout")]
171    Missing,
172    #[error("Visibility flags don't include the shader stage")]
173    Invisible,
174    #[error(
175        "Type on the shader side ({shader:?}) does not match the pipeline binding ({binding:?})"
176    )]
177    WrongType {
178        binding: BindingTypeName,
179        shader: BindingTypeName,
180    },
181    #[error("Storage class {binding:?} doesn't match the shader {shader:?}")]
182    WrongAddressSpace {
183        binding: naga::AddressSpace,
184        shader: naga::AddressSpace,
185    },
186    #[error("Address space {space:?} is not a valid Buffer address space")]
187    WrongBufferAddressSpace { space: naga::AddressSpace },
188    #[error("Buffer structure size {buffer_size}, added to one element of an unbound array, if it's the last field, ended up greater than the given `min_binding_size`, which is {min_binding_size}")]
189    WrongBufferSize {
190        buffer_size: wgt::BufferSize,
191        min_binding_size: wgt::BufferSize,
192    },
193    #[error("View dimension {dim:?} (is array: {is_array}) doesn't match the binding {binding:?}")]
194    WrongTextureViewDimension {
195        dim: naga::ImageDimension,
196        is_array: bool,
197        binding: BindingType,
198    },
199    #[error("Texture class {binding:?} doesn't match the shader {shader:?}")]
200    WrongTextureClass {
201        binding: naga::ImageClass,
202        shader: naga::ImageClass,
203    },
204    #[error("Comparison flag doesn't match the shader")]
205    WrongSamplerComparison,
206    #[error("Derived bind group layout type is not consistent between stages")]
207    InconsistentlyDerivedType,
208    #[error("Texture format {0:?} is not supported for storage use")]
209    BadStorageFormat(wgt::TextureFormat),
210}
211
212#[derive(Clone, Debug, Error)]
213#[non_exhaustive]
214pub enum FilteringError {
215    #[error("Integer textures can't be sampled with a filtering sampler")]
216    Integer,
217    #[error("Non-filterable float textures can't be sampled with a filtering sampler")]
218    Float,
219}
220
221#[derive(Clone, Debug, Error)]
222#[non_exhaustive]
223pub enum InputError {
224    #[error("Input is not provided by the earlier stage in the pipeline")]
225    Missing,
226    #[error("Input type is not compatible with the provided {0}")]
227    WrongType(NumericType),
228    #[error("Input interpolation doesn't match provided {0:?}")]
229    InterpolationMismatch(Option<naga::Interpolation>),
230    #[error("Input sampling doesn't match provided {0:?}")]
231    SamplingMismatch(Option<naga::Sampling>),
232}
233
234/// Errors produced when validating a programmable stage of a pipeline.
235#[derive(Clone, Debug, Error)]
236#[non_exhaustive]
237pub enum StageError {
238    #[error(
239        "Shader entry point's workgroup size {current:?} ({current_total} total invocations) must be less or equal to the per-dimension limit {limit:?} and the total invocation limit {total}"
240    )]
241    InvalidWorkgroupSize {
242        current: [u32; 3],
243        current_total: u32,
244        limit: [u32; 3],
245        total: u32,
246    },
247    #[error("Shader uses {used} inter-stage components above the limit of {limit}")]
248    TooManyVaryings { used: u32, limit: u32 },
249    #[error("Unable to find entry point '{0}'")]
250    MissingEntryPoint(String),
251    #[error("Shader global {0:?} is not available in the pipeline layout")]
252    Binding(naga::ResourceBinding, #[source] BindingError),
253    #[error("Unable to filter the texture ({texture:?}) by the sampler ({sampler:?})")]
254    Filtering {
255        texture: naga::ResourceBinding,
256        sampler: naga::ResourceBinding,
257        #[source]
258        error: FilteringError,
259    },
260    #[error("Location[{location}] {var} is not provided by the previous stage outputs")]
261    Input {
262        location: wgt::ShaderLocation,
263        var: InterfaceVar,
264        #[source]
265        error: InputError,
266    },
267    #[error(
268        "Unable to select an entry point: no entry point was found in the provided shader module"
269    )]
270    NoEntryPointFound,
271    #[error(
272        "Unable to select an entry point: \
273        multiple entry points were found in the provided shader module, \
274        but no entry point was specified"
275    )]
276    MultipleEntryPointsFound,
277    #[error(transparent)]
278    InvalidResource(#[from] InvalidResourceError),
279}
280
281pub fn map_storage_format_to_naga(format: wgt::TextureFormat) -> Option<naga::StorageFormat> {
282    use naga::StorageFormat as Sf;
283    use wgt::TextureFormat as Tf;
284
285    Some(match format {
286        Tf::R8Unorm => Sf::R8Unorm,
287        Tf::R8Snorm => Sf::R8Snorm,
288        Tf::R8Uint => Sf::R8Uint,
289        Tf::R8Sint => Sf::R8Sint,
290
291        Tf::R16Uint => Sf::R16Uint,
292        Tf::R16Sint => Sf::R16Sint,
293        Tf::R16Float => Sf::R16Float,
294        Tf::Rg8Unorm => Sf::Rg8Unorm,
295        Tf::Rg8Snorm => Sf::Rg8Snorm,
296        Tf::Rg8Uint => Sf::Rg8Uint,
297        Tf::Rg8Sint => Sf::Rg8Sint,
298
299        Tf::R32Uint => Sf::R32Uint,
300        Tf::R32Sint => Sf::R32Sint,
301        Tf::R32Float => Sf::R32Float,
302        Tf::Rg16Uint => Sf::Rg16Uint,
303        Tf::Rg16Sint => Sf::Rg16Sint,
304        Tf::Rg16Float => Sf::Rg16Float,
305        Tf::Rgba8Unorm => Sf::Rgba8Unorm,
306        Tf::Rgba8Snorm => Sf::Rgba8Snorm,
307        Tf::Rgba8Uint => Sf::Rgba8Uint,
308        Tf::Rgba8Sint => Sf::Rgba8Sint,
309        Tf::Bgra8Unorm => Sf::Bgra8Unorm,
310
311        Tf::Rgb10a2Uint => Sf::Rgb10a2Uint,
312        Tf::Rgb10a2Unorm => Sf::Rgb10a2Unorm,
313        Tf::Rg11b10Ufloat => Sf::Rg11b10Ufloat,
314
315        Tf::R64Uint => Sf::R64Uint,
316        Tf::Rg32Uint => Sf::Rg32Uint,
317        Tf::Rg32Sint => Sf::Rg32Sint,
318        Tf::Rg32Float => Sf::Rg32Float,
319        Tf::Rgba16Uint => Sf::Rgba16Uint,
320        Tf::Rgba16Sint => Sf::Rgba16Sint,
321        Tf::Rgba16Float => Sf::Rgba16Float,
322
323        Tf::Rgba32Uint => Sf::Rgba32Uint,
324        Tf::Rgba32Sint => Sf::Rgba32Sint,
325        Tf::Rgba32Float => Sf::Rgba32Float,
326
327        Tf::R16Unorm => Sf::R16Unorm,
328        Tf::R16Snorm => Sf::R16Snorm,
329        Tf::Rg16Unorm => Sf::Rg16Unorm,
330        Tf::Rg16Snorm => Sf::Rg16Snorm,
331        Tf::Rgba16Unorm => Sf::Rgba16Unorm,
332        Tf::Rgba16Snorm => Sf::Rgba16Snorm,
333
334        _ => return None,
335    })
336}
337
338pub fn map_storage_format_from_naga(format: naga::StorageFormat) -> wgt::TextureFormat {
339    use naga::StorageFormat as Sf;
340    use wgt::TextureFormat as Tf;
341
342    match format {
343        Sf::R8Unorm => Tf::R8Unorm,
344        Sf::R8Snorm => Tf::R8Snorm,
345        Sf::R8Uint => Tf::R8Uint,
346        Sf::R8Sint => Tf::R8Sint,
347
348        Sf::R16Uint => Tf::R16Uint,
349        Sf::R16Sint => Tf::R16Sint,
350        Sf::R16Float => Tf::R16Float,
351        Sf::Rg8Unorm => Tf::Rg8Unorm,
352        Sf::Rg8Snorm => Tf::Rg8Snorm,
353        Sf::Rg8Uint => Tf::Rg8Uint,
354        Sf::Rg8Sint => Tf::Rg8Sint,
355
356        Sf::R32Uint => Tf::R32Uint,
357        Sf::R32Sint => Tf::R32Sint,
358        Sf::R32Float => Tf::R32Float,
359        Sf::Rg16Uint => Tf::Rg16Uint,
360        Sf::Rg16Sint => Tf::Rg16Sint,
361        Sf::Rg16Float => Tf::Rg16Float,
362        Sf::Rgba8Unorm => Tf::Rgba8Unorm,
363        Sf::Rgba8Snorm => Tf::Rgba8Snorm,
364        Sf::Rgba8Uint => Tf::Rgba8Uint,
365        Sf::Rgba8Sint => Tf::Rgba8Sint,
366        Sf::Bgra8Unorm => Tf::Bgra8Unorm,
367
368        Sf::Rgb10a2Uint => Tf::Rgb10a2Uint,
369        Sf::Rgb10a2Unorm => Tf::Rgb10a2Unorm,
370        Sf::Rg11b10Ufloat => Tf::Rg11b10Ufloat,
371
372        Sf::R64Uint => Tf::R64Uint,
373        Sf::Rg32Uint => Tf::Rg32Uint,
374        Sf::Rg32Sint => Tf::Rg32Sint,
375        Sf::Rg32Float => Tf::Rg32Float,
376        Sf::Rgba16Uint => Tf::Rgba16Uint,
377        Sf::Rgba16Sint => Tf::Rgba16Sint,
378        Sf::Rgba16Float => Tf::Rgba16Float,
379
380        Sf::Rgba32Uint => Tf::Rgba32Uint,
381        Sf::Rgba32Sint => Tf::Rgba32Sint,
382        Sf::Rgba32Float => Tf::Rgba32Float,
383
384        Sf::R16Unorm => Tf::R16Unorm,
385        Sf::R16Snorm => Tf::R16Snorm,
386        Sf::Rg16Unorm => Tf::Rg16Unorm,
387        Sf::Rg16Snorm => Tf::Rg16Snorm,
388        Sf::Rgba16Unorm => Tf::Rgba16Unorm,
389        Sf::Rgba16Snorm => Tf::Rgba16Snorm,
390    }
391}
392
393impl Resource {
394    fn check_binding_use(&self, entry: &BindGroupLayoutEntry) -> Result<(), BindingError> {
395        match self.ty {
396            ResourceType::Buffer { size } => {
397                let min_size = match entry.ty {
398                    BindingType::Buffer {
399                        ty,
400                        has_dynamic_offset: _,
401                        min_binding_size,
402                    } => {
403                        let class = match ty {
404                            wgt::BufferBindingType::Uniform => naga::AddressSpace::Uniform,
405                            wgt::BufferBindingType::Storage { read_only } => {
406                                let mut naga_access = naga::StorageAccess::LOAD;
407                                naga_access.set(naga::StorageAccess::STORE, !read_only);
408                                naga::AddressSpace::Storage {
409                                    access: naga_access,
410                                }
411                            }
412                        };
413                        if self.class != class {
414                            return Err(BindingError::WrongAddressSpace {
415                                binding: class,
416                                shader: self.class,
417                            });
418                        }
419                        min_binding_size
420                    }
421                    _ => {
422                        return Err(BindingError::WrongType {
423                            binding: (&entry.ty).into(),
424                            shader: (&self.ty).into(),
425                        })
426                    }
427                };
428                match min_size {
429                    Some(non_zero) if non_zero < size => {
430                        return Err(BindingError::WrongBufferSize {
431                            buffer_size: size,
432                            min_binding_size: non_zero,
433                        })
434                    }
435                    _ => (),
436                }
437            }
438            ResourceType::Sampler { comparison } => match entry.ty {
439                BindingType::Sampler(ty) => {
440                    if (ty == wgt::SamplerBindingType::Comparison) != comparison {
441                        return Err(BindingError::WrongSamplerComparison);
442                    }
443                }
444                _ => {
445                    return Err(BindingError::WrongType {
446                        binding: (&entry.ty).into(),
447                        shader: (&self.ty).into(),
448                    })
449                }
450            },
451            ResourceType::Texture {
452                dim,
453                arrayed,
454                class,
455            } => {
456                let view_dimension = match entry.ty {
457                    BindingType::Texture { view_dimension, .. }
458                    | BindingType::StorageTexture { view_dimension, .. } => view_dimension,
459                    _ => {
460                        return Err(BindingError::WrongTextureViewDimension {
461                            dim,
462                            is_array: false,
463                            binding: entry.ty,
464                        })
465                    }
466                };
467                if arrayed {
468                    match (dim, view_dimension) {
469                        (naga::ImageDimension::D2, wgt::TextureViewDimension::D2Array) => (),
470                        (naga::ImageDimension::Cube, wgt::TextureViewDimension::CubeArray) => (),
471                        _ => {
472                            return Err(BindingError::WrongTextureViewDimension {
473                                dim,
474                                is_array: true,
475                                binding: entry.ty,
476                            })
477                        }
478                    }
479                } else {
480                    match (dim, view_dimension) {
481                        (naga::ImageDimension::D1, wgt::TextureViewDimension::D1) => (),
482                        (naga::ImageDimension::D2, wgt::TextureViewDimension::D2) => (),
483                        (naga::ImageDimension::D3, wgt::TextureViewDimension::D3) => (),
484                        (naga::ImageDimension::Cube, wgt::TextureViewDimension::Cube) => (),
485                        _ => {
486                            return Err(BindingError::WrongTextureViewDimension {
487                                dim,
488                                is_array: false,
489                                binding: entry.ty,
490                            })
491                        }
492                    }
493                }
494                let expected_class = match entry.ty {
495                    BindingType::Texture {
496                        sample_type,
497                        view_dimension: _,
498                        multisampled: multi,
499                    } => match sample_type {
500                        wgt::TextureSampleType::Float { .. } => naga::ImageClass::Sampled {
501                            kind: naga::ScalarKind::Float,
502                            multi,
503                        },
504                        wgt::TextureSampleType::Sint => naga::ImageClass::Sampled {
505                            kind: naga::ScalarKind::Sint,
506                            multi,
507                        },
508                        wgt::TextureSampleType::Uint => naga::ImageClass::Sampled {
509                            kind: naga::ScalarKind::Uint,
510                            multi,
511                        },
512                        wgt::TextureSampleType::Depth => naga::ImageClass::Depth { multi },
513                    },
514                    BindingType::StorageTexture {
515                        access,
516                        format,
517                        view_dimension: _,
518                    } => {
519                        let naga_format = map_storage_format_to_naga(format)
520                            .ok_or(BindingError::BadStorageFormat(format))?;
521                        let naga_access = match access {
522                            wgt::StorageTextureAccess::ReadOnly => naga::StorageAccess::LOAD,
523                            wgt::StorageTextureAccess::WriteOnly => naga::StorageAccess::STORE,
524                            wgt::StorageTextureAccess::ReadWrite => {
525                                naga::StorageAccess::LOAD | naga::StorageAccess::STORE
526                            }
527                            wgt::StorageTextureAccess::Atomic => {
528                                naga::StorageAccess::ATOMIC
529                                    | naga::StorageAccess::LOAD
530                                    | naga::StorageAccess::STORE
531                            }
532                        };
533                        naga::ImageClass::Storage {
534                            format: naga_format,
535                            access: naga_access,
536                        }
537                    }
538                    _ => {
539                        return Err(BindingError::WrongType {
540                            binding: (&entry.ty).into(),
541                            shader: (&self.ty).into(),
542                        })
543                    }
544                };
545                if class != expected_class {
546                    return Err(BindingError::WrongTextureClass {
547                        binding: expected_class,
548                        shader: class,
549                    });
550                }
551            }
552            ResourceType::AccelerationStructure => match entry.ty {
553                BindingType::AccelerationStructure => (),
554                _ => {
555                    return Err(BindingError::WrongType {
556                        binding: (&entry.ty).into(),
557                        shader: (&self.ty).into(),
558                    })
559                }
560            },
561        };
562
563        Ok(())
564    }
565
566    fn derive_binding_type(
567        &self,
568        is_reffed_by_sampler_in_entrypoint: bool,
569    ) -> Result<BindingType, BindingError> {
570        Ok(match self.ty {
571            ResourceType::Buffer { size } => BindingType::Buffer {
572                ty: match self.class {
573                    naga::AddressSpace::Uniform => wgt::BufferBindingType::Uniform,
574                    naga::AddressSpace::Storage { access } => wgt::BufferBindingType::Storage {
575                        read_only: access == naga::StorageAccess::LOAD,
576                    },
577                    _ => return Err(BindingError::WrongBufferAddressSpace { space: self.class }),
578                },
579                has_dynamic_offset: false,
580                min_binding_size: Some(size),
581            },
582            ResourceType::Sampler { comparison } => BindingType::Sampler(if comparison {
583                wgt::SamplerBindingType::Comparison
584            } else {
585                wgt::SamplerBindingType::Filtering
586            }),
587            ResourceType::Texture {
588                dim,
589                arrayed,
590                class,
591            } => {
592                let view_dimension = match dim {
593                    naga::ImageDimension::D1 => wgt::TextureViewDimension::D1,
594                    naga::ImageDimension::D2 if arrayed => wgt::TextureViewDimension::D2Array,
595                    naga::ImageDimension::D2 => wgt::TextureViewDimension::D2,
596                    naga::ImageDimension::D3 => wgt::TextureViewDimension::D3,
597                    naga::ImageDimension::Cube if arrayed => wgt::TextureViewDimension::CubeArray,
598                    naga::ImageDimension::Cube => wgt::TextureViewDimension::Cube,
599                };
600                match class {
601                    naga::ImageClass::Sampled { multi, kind } => BindingType::Texture {
602                        sample_type: match kind {
603                            naga::ScalarKind::Float => wgt::TextureSampleType::Float {
604                                filterable: is_reffed_by_sampler_in_entrypoint,
605                            },
606                            naga::ScalarKind::Sint => wgt::TextureSampleType::Sint,
607                            naga::ScalarKind::Uint => wgt::TextureSampleType::Uint,
608                            naga::ScalarKind::AbstractInt
609                            | naga::ScalarKind::AbstractFloat
610                            | naga::ScalarKind::Bool => unreachable!(),
611                        },
612                        view_dimension,
613                        multisampled: multi,
614                    },
615                    naga::ImageClass::Depth { multi } => BindingType::Texture {
616                        sample_type: wgt::TextureSampleType::Depth,
617                        view_dimension,
618                        multisampled: multi,
619                    },
620                    naga::ImageClass::Storage { format, access } => BindingType::StorageTexture {
621                        access: {
622                            const LOAD_STORE: naga::StorageAccess =
623                                naga::StorageAccess::LOAD.union(naga::StorageAccess::STORE);
624                            match access {
625                                naga::StorageAccess::LOAD => wgt::StorageTextureAccess::ReadOnly,
626                                naga::StorageAccess::STORE => wgt::StorageTextureAccess::WriteOnly,
627                                LOAD_STORE => wgt::StorageTextureAccess::ReadWrite,
628                                _ if access.contains(naga::StorageAccess::ATOMIC) => {
629                                    wgt::StorageTextureAccess::Atomic
630                                }
631                                _ => unreachable!(),
632                            }
633                        },
634                        view_dimension,
635                        format: {
636                            let f = map_storage_format_from_naga(format);
637                            let original = map_storage_format_to_naga(f)
638                                .ok_or(BindingError::BadStorageFormat(f))?;
639                            debug_assert_eq!(format, original);
640                            f
641                        },
642                    },
643                }
644            }
645            ResourceType::AccelerationStructure => BindingType::AccelerationStructure,
646        })
647    }
648}
649
650impl NumericType {
651    fn from_vertex_format(format: wgt::VertexFormat) -> Self {
652        use naga::{Scalar, VectorSize as Vs};
653        use wgt::VertexFormat as Vf;
654
655        let (dim, scalar) = match format {
656            Vf::Uint8 | Vf::Uint16 | Vf::Uint32 => (NumericDimension::Scalar, Scalar::U32),
657            Vf::Uint8x2 | Vf::Uint16x2 | Vf::Uint32x2 => {
658                (NumericDimension::Vector(Vs::Bi), Scalar::U32)
659            }
660            Vf::Uint32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::U32),
661            Vf::Uint8x4 | Vf::Uint16x4 | Vf::Uint32x4 => {
662                (NumericDimension::Vector(Vs::Quad), Scalar::U32)
663            }
664            Vf::Sint8 | Vf::Sint16 | Vf::Sint32 => (NumericDimension::Scalar, Scalar::I32),
665            Vf::Sint8x2 | Vf::Sint16x2 | Vf::Sint32x2 => {
666                (NumericDimension::Vector(Vs::Bi), Scalar::I32)
667            }
668            Vf::Sint32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::I32),
669            Vf::Sint8x4 | Vf::Sint16x4 | Vf::Sint32x4 => {
670                (NumericDimension::Vector(Vs::Quad), Scalar::I32)
671            }
672            Vf::Unorm8 | Vf::Unorm16 | Vf::Snorm8 | Vf::Snorm16 | Vf::Float16 | Vf::Float32 => {
673                (NumericDimension::Scalar, Scalar::F32)
674            }
675            Vf::Unorm8x2
676            | Vf::Snorm8x2
677            | Vf::Unorm16x2
678            | Vf::Snorm16x2
679            | Vf::Float16x2
680            | Vf::Float32x2 => (NumericDimension::Vector(Vs::Bi), Scalar::F32),
681            Vf::Float32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
682            Vf::Unorm8x4
683            | Vf::Snorm8x4
684            | Vf::Unorm16x4
685            | Vf::Snorm16x4
686            | Vf::Float16x4
687            | Vf::Float32x4
688            | Vf::Unorm10_10_10_2
689            | Vf::Unorm8x4Bgra => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
690            Vf::Float64 => (NumericDimension::Scalar, Scalar::F64),
691            Vf::Float64x2 => (NumericDimension::Vector(Vs::Bi), Scalar::F64),
692            Vf::Float64x3 => (NumericDimension::Vector(Vs::Tri), Scalar::F64),
693            Vf::Float64x4 => (NumericDimension::Vector(Vs::Quad), Scalar::F64),
694        };
695
696        NumericType {
697            dim,
698            //Note: Shader always sees data as int, uint, or float.
699            // It doesn't know if the original is normalized in a tighter form.
700            scalar,
701        }
702    }
703
704    fn from_texture_format(format: wgt::TextureFormat) -> Self {
705        use naga::{Scalar, VectorSize as Vs};
706        use wgt::TextureFormat as Tf;
707
708        let (dim, scalar) = match format {
709            Tf::R8Unorm | Tf::R8Snorm | Tf::R16Float | Tf::R32Float => {
710                (NumericDimension::Scalar, Scalar::F32)
711            }
712            Tf::R8Uint | Tf::R16Uint | Tf::R32Uint => (NumericDimension::Scalar, Scalar::U32),
713            Tf::R8Sint | Tf::R16Sint | Tf::R32Sint => (NumericDimension::Scalar, Scalar::I32),
714            Tf::Rg8Unorm | Tf::Rg8Snorm | Tf::Rg16Float | Tf::Rg32Float => {
715                (NumericDimension::Vector(Vs::Bi), Scalar::F32)
716            }
717            Tf::R64Uint => (NumericDimension::Scalar, Scalar::U64),
718            Tf::Rg8Uint | Tf::Rg16Uint | Tf::Rg32Uint => {
719                (NumericDimension::Vector(Vs::Bi), Scalar::U32)
720            }
721            Tf::Rg8Sint | Tf::Rg16Sint | Tf::Rg32Sint => {
722                (NumericDimension::Vector(Vs::Bi), Scalar::I32)
723            }
724            Tf::R16Snorm | Tf::R16Unorm => (NumericDimension::Scalar, Scalar::F32),
725            Tf::Rg16Snorm | Tf::Rg16Unorm => (NumericDimension::Vector(Vs::Bi), Scalar::F32),
726            Tf::Rgba16Snorm | Tf::Rgba16Unorm => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
727            Tf::Rgba8Unorm
728            | Tf::Rgba8UnormSrgb
729            | Tf::Rgba8Snorm
730            | Tf::Bgra8Unorm
731            | Tf::Bgra8UnormSrgb
732            | Tf::Rgb10a2Unorm
733            | Tf::Rgba16Float
734            | Tf::Rgba32Float => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
735            Tf::Rgba8Uint | Tf::Rgba16Uint | Tf::Rgba32Uint | Tf::Rgb10a2Uint => {
736                (NumericDimension::Vector(Vs::Quad), Scalar::U32)
737            }
738            Tf::Rgba8Sint | Tf::Rgba16Sint | Tf::Rgba32Sint => {
739                (NumericDimension::Vector(Vs::Quad), Scalar::I32)
740            }
741            Tf::Rg11b10Ufloat => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
742            Tf::Stencil8
743            | Tf::Depth16Unorm
744            | Tf::Depth32Float
745            | Tf::Depth32FloatStencil8
746            | Tf::Depth24Plus
747            | Tf::Depth24PlusStencil8 => {
748                panic!("Unexpected depth format")
749            }
750            Tf::NV12 => panic!("Unexpected nv12 format"),
751            Tf::Rgb9e5Ufloat => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
752            Tf::Bc1RgbaUnorm
753            | Tf::Bc1RgbaUnormSrgb
754            | Tf::Bc2RgbaUnorm
755            | Tf::Bc2RgbaUnormSrgb
756            | Tf::Bc3RgbaUnorm
757            | Tf::Bc3RgbaUnormSrgb
758            | Tf::Bc7RgbaUnorm
759            | Tf::Bc7RgbaUnormSrgb
760            | Tf::Etc2Rgb8A1Unorm
761            | Tf::Etc2Rgb8A1UnormSrgb
762            | Tf::Etc2Rgba8Unorm
763            | Tf::Etc2Rgba8UnormSrgb => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
764            Tf::Bc4RUnorm | Tf::Bc4RSnorm | Tf::EacR11Unorm | Tf::EacR11Snorm => {
765                (NumericDimension::Scalar, Scalar::F32)
766            }
767            Tf::Bc5RgUnorm | Tf::Bc5RgSnorm | Tf::EacRg11Unorm | Tf::EacRg11Snorm => {
768                (NumericDimension::Vector(Vs::Bi), Scalar::F32)
769            }
770            Tf::Bc6hRgbUfloat | Tf::Bc6hRgbFloat | Tf::Etc2Rgb8Unorm | Tf::Etc2Rgb8UnormSrgb => {
771                (NumericDimension::Vector(Vs::Tri), Scalar::F32)
772            }
773            Tf::Astc {
774                block: _,
775                channel: _,
776            } => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
777        };
778
779        NumericType {
780            dim,
781            //Note: Shader always sees data as int, uint, or float.
782            // It doesn't know if the original is normalized in a tighter form.
783            scalar,
784        }
785    }
786
787    fn is_subtype_of(&self, other: &NumericType) -> bool {
788        if self.scalar.width > other.scalar.width {
789            return false;
790        }
791        if self.scalar.kind != other.scalar.kind {
792            return false;
793        }
794        match (self.dim, other.dim) {
795            (NumericDimension::Scalar, NumericDimension::Scalar) => true,
796            (NumericDimension::Scalar, NumericDimension::Vector(_)) => true,
797            (NumericDimension::Vector(s0), NumericDimension::Vector(s1)) => s0 <= s1,
798            (NumericDimension::Matrix(c0, r0), NumericDimension::Matrix(c1, r1)) => {
799                c0 == c1 && r0 == r1
800            }
801            _ => false,
802        }
803    }
804
805    fn is_compatible_with(&self, other: &NumericType) -> bool {
806        if self.scalar.kind != other.scalar.kind {
807            return false;
808        }
809        match (self.dim, other.dim) {
810            (NumericDimension::Scalar, NumericDimension::Scalar) => true,
811            (NumericDimension::Scalar, NumericDimension::Vector(_)) => true,
812            (NumericDimension::Vector(_), NumericDimension::Vector(_)) => true,
813            (NumericDimension::Matrix(..), NumericDimension::Matrix(..)) => true,
814            _ => false,
815        }
816    }
817}
818
819/// Return true if the fragment `format` is covered by the provided `output`.
820pub fn check_texture_format(
821    format: wgt::TextureFormat,
822    output: &NumericType,
823) -> Result<(), NumericType> {
824    let nt = NumericType::from_texture_format(format);
825    if nt.is_subtype_of(output) {
826        Ok(())
827    } else {
828        Err(nt)
829    }
830}
831
832pub enum BindingLayoutSource<'a> {
833    /// The binding layout is derived from the pipeline layout.
834    ///
835    /// This will be filled in by the shader binding validation, as it iterates the shader's interfaces.
836    Derived(Box<ArrayVec<bgl::EntryMap, { hal::MAX_BIND_GROUPS }>>),
837    /// The binding layout is provided by the user in BGLs.
838    ///
839    /// This will be validated against the shader's interfaces.
840    Provided(ArrayVec<&'a bgl::EntryMap, { hal::MAX_BIND_GROUPS }>),
841}
842
843impl<'a> BindingLayoutSource<'a> {
844    pub fn new_derived(limits: &wgt::Limits) -> Self {
845        let mut array = ArrayVec::new();
846        for _ in 0..limits.max_bind_groups {
847            array.push(Default::default());
848        }
849        BindingLayoutSource::Derived(Box::new(array))
850    }
851}
852
853pub type StageIo = FastHashMap<wgt::ShaderLocation, InterfaceVar>;
854
855impl Interface {
856    fn populate(
857        list: &mut Vec<Varying>,
858        binding: Option<&naga::Binding>,
859        ty: naga::Handle<naga::Type>,
860        arena: &naga::UniqueArena<naga::Type>,
861    ) {
862        let numeric_ty = match arena[ty].inner {
863            naga::TypeInner::Scalar(scalar) => NumericType {
864                dim: NumericDimension::Scalar,
865                scalar,
866            },
867            naga::TypeInner::Vector { size, scalar } => NumericType {
868                dim: NumericDimension::Vector(size),
869                scalar,
870            },
871            naga::TypeInner::Matrix {
872                columns,
873                rows,
874                scalar,
875            } => NumericType {
876                dim: NumericDimension::Matrix(columns, rows),
877                scalar,
878            },
879            naga::TypeInner::Struct { ref members, .. } => {
880                for member in members {
881                    Self::populate(list, member.binding.as_ref(), member.ty, arena);
882                }
883                return;
884            }
885            ref other => {
886                //Note: technically this should be at least `log::error`, but
887                // the reality is - every shader coming from `glslc` outputs an array
888                // of clip distances and hits this path :(
889                // So we lower it to `log::warn` to be less annoying.
890                log::warn!("Unexpected varying type: {:?}", other);
891                return;
892            }
893        };
894
895        let varying = match binding {
896            Some(&naga::Binding::Location {
897                location,
898                interpolation,
899                sampling,
900                .. // second_blend_source
901            }) => Varying::Local {
902                location,
903                iv: InterfaceVar {
904                    ty: numeric_ty,
905                    interpolation,
906                    sampling,
907                },
908            },
909            Some(&naga::Binding::BuiltIn(built_in)) => Varying::BuiltIn(built_in),
910            None => {
911                log::error!("Missing binding for a varying");
912                return;
913            }
914        };
915        list.push(varying);
916    }
917
918    pub fn new(module: &naga::Module, info: &naga::valid::ModuleInfo, limits: wgt::Limits) -> Self {
919        let mut resources = naga::Arena::new();
920        let mut resource_mapping = FastHashMap::default();
921        for (var_handle, var) in module.global_variables.iter() {
922            let bind = match var.binding {
923                Some(ref br) => br.clone(),
924                _ => continue,
925            };
926            let naga_ty = &module.types[var.ty].inner;
927
928            let inner_ty = match *naga_ty {
929                naga::TypeInner::BindingArray { base, .. } => &module.types[base].inner,
930                ref ty => ty,
931            };
932
933            let ty = match *inner_ty {
934                naga::TypeInner::Image {
935                    dim,
936                    arrayed,
937                    class,
938                } => ResourceType::Texture {
939                    dim,
940                    arrayed,
941                    class,
942                },
943                naga::TypeInner::Sampler { comparison } => ResourceType::Sampler { comparison },
944                naga::TypeInner::AccelerationStructure => ResourceType::AccelerationStructure,
945                ref other => ResourceType::Buffer {
946                    size: wgt::BufferSize::new(other.size(module.to_ctx()) as u64).unwrap(),
947                },
948            };
949            let handle = resources.append(
950                Resource {
951                    name: var.name.clone(),
952                    bind,
953                    ty,
954                    class: var.space,
955                },
956                Default::default(),
957            );
958            resource_mapping.insert(var_handle, handle);
959        }
960
961        let mut entry_points = FastHashMap::default();
962        entry_points.reserve(module.entry_points.len());
963        for (index, entry_point) in module.entry_points.iter().enumerate() {
964            let info = info.get_entry_point(index);
965            let mut ep = EntryPoint::default();
966            for arg in entry_point.function.arguments.iter() {
967                Self::populate(&mut ep.inputs, arg.binding.as_ref(), arg.ty, &module.types);
968            }
969            if let Some(ref result) = entry_point.function.result {
970                Self::populate(
971                    &mut ep.outputs,
972                    result.binding.as_ref(),
973                    result.ty,
974                    &module.types,
975                );
976            }
977
978            for (var_handle, var) in module.global_variables.iter() {
979                let usage = info[var_handle];
980                if !usage.is_empty() && var.binding.is_some() {
981                    ep.resources.push(resource_mapping[&var_handle]);
982                }
983            }
984
985            for key in info.sampling_set.iter() {
986                ep.sampling_pairs
987                    .insert((resource_mapping[&key.image], resource_mapping[&key.sampler]));
988            }
989            ep.dual_source_blending = info.dual_source_blending;
990            ep.workgroup_size = entry_point.workgroup_size;
991
992            entry_points.insert((entry_point.stage, entry_point.name.clone()), ep);
993        }
994
995        Self {
996            limits,
997            resources,
998            entry_points,
999        }
1000    }
1001
1002    pub fn finalize_entry_point_name(
1003        &self,
1004        stage_bit: wgt::ShaderStages,
1005        entry_point_name: Option<&str>,
1006    ) -> Result<String, StageError> {
1007        let stage = Self::shader_stage_from_stage_bit(stage_bit);
1008        entry_point_name
1009            .map(|ep| ep.to_string())
1010            .map(Ok)
1011            .unwrap_or_else(|| {
1012                let mut entry_points = self
1013                    .entry_points
1014                    .keys()
1015                    .filter_map(|(ep_stage, name)| (ep_stage == &stage).then_some(name));
1016                let first = entry_points.next().ok_or(StageError::NoEntryPointFound)?;
1017                if entry_points.next().is_some() {
1018                    return Err(StageError::MultipleEntryPointsFound);
1019                }
1020                Ok(first.clone())
1021            })
1022    }
1023
1024    pub(crate) fn shader_stage_from_stage_bit(stage_bit: wgt::ShaderStages) -> naga::ShaderStage {
1025        match stage_bit {
1026            wgt::ShaderStages::VERTEX => naga::ShaderStage::Vertex,
1027            wgt::ShaderStages::FRAGMENT => naga::ShaderStage::Fragment,
1028            wgt::ShaderStages::COMPUTE => naga::ShaderStage::Compute,
1029            _ => unreachable!(),
1030        }
1031    }
1032
1033    pub fn check_stage(
1034        &self,
1035        layouts: &mut BindingLayoutSource<'_>,
1036        shader_binding_sizes: &mut FastHashMap<naga::ResourceBinding, wgt::BufferSize>,
1037        entry_point_name: &str,
1038        stage_bit: wgt::ShaderStages,
1039        inputs: StageIo,
1040        compare_function: Option<wgt::CompareFunction>,
1041    ) -> Result<StageIo, StageError> {
1042        // Since a shader module can have multiple entry points with the same name,
1043        // we need to look for one with the right execution model.
1044        let shader_stage = Self::shader_stage_from_stage_bit(stage_bit);
1045        let pair = (shader_stage, entry_point_name.to_string());
1046        let entry_point = match self.entry_points.get(&pair) {
1047            Some(some) => some,
1048            None => return Err(StageError::MissingEntryPoint(pair.1)),
1049        };
1050        let (_stage, entry_point_name) = pair;
1051
1052        // check resources visibility
1053        for &handle in entry_point.resources.iter() {
1054            let res = &self.resources[handle];
1055            let result = 'err: {
1056                match layouts {
1057                    BindingLayoutSource::Provided(layouts) => {
1058                        // update the required binding size for this buffer
1059                        if let ResourceType::Buffer { size } = res.ty {
1060                            match shader_binding_sizes.entry(res.bind.clone()) {
1061                                Entry::Occupied(e) => {
1062                                    *e.into_mut() = size.max(*e.get());
1063                                }
1064                                Entry::Vacant(e) => {
1065                                    e.insert(size);
1066                                }
1067                            }
1068                        }
1069
1070                        let Some(map) = layouts.get(res.bind.group as usize) else {
1071                            break 'err Err(BindingError::Missing);
1072                        };
1073
1074                        let Some(entry) = map.get(res.bind.binding) else {
1075                            break 'err Err(BindingError::Missing);
1076                        };
1077
1078                        if !entry.visibility.contains(stage_bit) {
1079                            break 'err Err(BindingError::Invisible);
1080                        }
1081
1082                        res.check_binding_use(entry)
1083                    }
1084                    BindingLayoutSource::Derived(layouts) => {
1085                        let Some(map) = layouts.get_mut(res.bind.group as usize) else {
1086                            break 'err Err(BindingError::Missing);
1087                        };
1088
1089                        let ty = match res.derive_binding_type(
1090                            entry_point
1091                                .sampling_pairs
1092                                .iter()
1093                                .any(|&(im, _samp)| im == handle),
1094                        ) {
1095                            Ok(ty) => ty,
1096                            Err(error) => break 'err Err(error),
1097                        };
1098
1099                        match map.entry(res.bind.binding) {
1100                            indexmap::map::Entry::Occupied(e) if e.get().ty != ty => {
1101                                break 'err Err(BindingError::InconsistentlyDerivedType)
1102                            }
1103                            indexmap::map::Entry::Occupied(e) => {
1104                                e.into_mut().visibility |= stage_bit;
1105                            }
1106                            indexmap::map::Entry::Vacant(e) => {
1107                                e.insert(BindGroupLayoutEntry {
1108                                    binding: res.bind.binding,
1109                                    ty,
1110                                    visibility: stage_bit,
1111                                    count: None,
1112                                });
1113                            }
1114                        }
1115                        Ok(())
1116                    }
1117                }
1118            };
1119            if let Err(error) = result {
1120                return Err(StageError::Binding(res.bind.clone(), error));
1121            }
1122        }
1123
1124        // Check the compatibility between textures and samplers
1125        //
1126        // We only need to do this if the binding layout is provided by the user, as derived
1127        // layouts will inherently be correctly tagged.
1128        if let BindingLayoutSource::Provided(layouts) = layouts {
1129            for &(texture_handle, sampler_handle) in entry_point.sampling_pairs.iter() {
1130                let texture_bind = &self.resources[texture_handle].bind;
1131                let sampler_bind = &self.resources[sampler_handle].bind;
1132                let texture_layout = layouts[texture_bind.group as usize]
1133                    .get(texture_bind.binding)
1134                    .unwrap();
1135                let sampler_layout = layouts[sampler_bind.group as usize]
1136                    .get(sampler_bind.binding)
1137                    .unwrap();
1138                assert!(texture_layout.visibility.contains(stage_bit));
1139                assert!(sampler_layout.visibility.contains(stage_bit));
1140
1141                let sampler_filtering = matches!(
1142                    sampler_layout.ty,
1143                    BindingType::Sampler(wgt::SamplerBindingType::Filtering)
1144                );
1145                let texture_sample_type = match texture_layout.ty {
1146                    BindingType::Texture { sample_type, .. } => sample_type,
1147                    _ => unreachable!(),
1148                };
1149
1150                let error = match (sampler_filtering, texture_sample_type) {
1151                    (true, wgt::TextureSampleType::Float { filterable: false }) => {
1152                        Some(FilteringError::Float)
1153                    }
1154                    (true, wgt::TextureSampleType::Sint) => Some(FilteringError::Integer),
1155                    (true, wgt::TextureSampleType::Uint) => Some(FilteringError::Integer),
1156                    _ => None,
1157                };
1158
1159                if let Some(error) = error {
1160                    return Err(StageError::Filtering {
1161                        texture: texture_bind.clone(),
1162                        sampler: sampler_bind.clone(),
1163                        error,
1164                    });
1165                }
1166            }
1167        }
1168
1169        // check workgroup size limits
1170        if shader_stage == naga::ShaderStage::Compute {
1171            let max_workgroup_size_limits = [
1172                self.limits.max_compute_workgroup_size_x,
1173                self.limits.max_compute_workgroup_size_y,
1174                self.limits.max_compute_workgroup_size_z,
1175            ];
1176            let total_invocations = entry_point.workgroup_size.iter().product::<u32>();
1177
1178            if entry_point.workgroup_size.iter().any(|&s| s == 0)
1179                || total_invocations > self.limits.max_compute_invocations_per_workgroup
1180                || entry_point.workgroup_size[0] > max_workgroup_size_limits[0]
1181                || entry_point.workgroup_size[1] > max_workgroup_size_limits[1]
1182                || entry_point.workgroup_size[2] > max_workgroup_size_limits[2]
1183            {
1184                return Err(StageError::InvalidWorkgroupSize {
1185                    current: entry_point.workgroup_size,
1186                    current_total: total_invocations,
1187                    limit: max_workgroup_size_limits,
1188                    total: self.limits.max_compute_invocations_per_workgroup,
1189                });
1190            }
1191        }
1192
1193        let mut inter_stage_components = 0;
1194
1195        // check inputs compatibility
1196        for input in entry_point.inputs.iter() {
1197            match *input {
1198                Varying::Local { location, ref iv } => {
1199                    let result =
1200                        inputs
1201                            .get(&location)
1202                            .ok_or(InputError::Missing)
1203                            .and_then(|provided| {
1204                                let (compatible, num_components) = match shader_stage {
1205                                    // For vertex attributes, there are defaults filled out
1206                                    // by the driver if data is not provided.
1207                                    naga::ShaderStage::Vertex => {
1208                                        // vertex inputs don't count towards inter-stage
1209                                        (iv.ty.is_compatible_with(&provided.ty), 0)
1210                                    }
1211                                    naga::ShaderStage::Fragment => {
1212                                        if iv.interpolation != provided.interpolation {
1213                                            return Err(InputError::InterpolationMismatch(
1214                                                provided.interpolation,
1215                                            ));
1216                                        }
1217                                        if iv.sampling != provided.sampling {
1218                                            return Err(InputError::SamplingMismatch(
1219                                                provided.sampling,
1220                                            ));
1221                                        }
1222                                        (
1223                                            iv.ty.is_subtype_of(&provided.ty),
1224                                            iv.ty.dim.num_components(),
1225                                        )
1226                                    }
1227                                    naga::ShaderStage::Compute => (false, 0),
1228                                };
1229                                if compatible {
1230                                    Ok(num_components)
1231                                } else {
1232                                    Err(InputError::WrongType(provided.ty))
1233                                }
1234                            });
1235                    match result {
1236                        Ok(num_components) => {
1237                            inter_stage_components += num_components;
1238                        }
1239                        Err(error) => {
1240                            return Err(StageError::Input {
1241                                location,
1242                                var: iv.clone(),
1243                                error,
1244                            })
1245                        }
1246                    }
1247                }
1248                Varying::BuiltIn(_) => {}
1249            }
1250        }
1251
1252        if shader_stage == naga::ShaderStage::Vertex {
1253            for output in entry_point.outputs.iter() {
1254                //TODO: count builtins towards the limit?
1255                inter_stage_components += match *output {
1256                    Varying::Local { ref iv, .. } => iv.ty.dim.num_components(),
1257                    Varying::BuiltIn(_) => 0,
1258                };
1259
1260                if let Some(
1261                    cmp @ wgt::CompareFunction::Equal | cmp @ wgt::CompareFunction::NotEqual,
1262                ) = compare_function
1263                {
1264                    if let Varying::BuiltIn(naga::BuiltIn::Position { invariant: false }) = *output
1265                    {
1266                        log::warn!(
1267                            "Vertex shader with entry point {entry_point_name} outputs a @builtin(position) without the @invariant \
1268                            attribute and is used in a pipeline with {cmp:?}. On some machines, this can cause bad artifacting as {cmp:?} assumes \
1269                            the values output from the vertex shader exactly match the value in the depth buffer. The @invariant attribute on the \
1270                            @builtin(position) vertex output ensures that the exact same pixel depths are used every render."
1271                        );
1272                    }
1273                }
1274            }
1275        }
1276
1277        if inter_stage_components > self.limits.max_inter_stage_shader_components {
1278            return Err(StageError::TooManyVaryings {
1279                used: inter_stage_components,
1280                limit: self.limits.max_inter_stage_shader_components,
1281            });
1282        }
1283
1284        let outputs = entry_point
1285            .outputs
1286            .iter()
1287            .filter_map(|output| match *output {
1288                Varying::Local { location, ref iv } => Some((location, iv.clone())),
1289                Varying::BuiltIn(_) => None,
1290            })
1291            .collect();
1292        Ok(outputs)
1293    }
1294
1295    pub fn fragment_uses_dual_source_blending(
1296        &self,
1297        entry_point_name: &str,
1298    ) -> Result<bool, StageError> {
1299        let pair = (naga::ShaderStage::Fragment, entry_point_name.to_string());
1300        self.entry_points
1301            .get(&pair)
1302            .ok_or(StageError::MissingEntryPoint(pair.1))
1303            .map(|ep| ep.dual_source_blending)
1304    }
1305}
1306
1307// https://gpuweb.github.io/gpuweb/#abstract-opdef-calculating-color-attachment-bytes-per-sample
1308pub fn validate_color_attachment_bytes_per_sample(
1309    attachment_formats: impl Iterator<Item = Option<wgt::TextureFormat>>,
1310    limit: u32,
1311) -> Result<(), u32> {
1312    let mut total_bytes_per_sample: u32 = 0;
1313    for format in attachment_formats {
1314        let Some(format) = format else {
1315            continue;
1316        };
1317
1318        let byte_cost = format.target_pixel_byte_cost().unwrap();
1319        let alignment = format.target_component_alignment().unwrap();
1320
1321        total_bytes_per_sample = total_bytes_per_sample.next_multiple_of(alignment);
1322        total_bytes_per_sample += byte_cost;
1323    }
1324
1325    if total_bytes_per_sample > limit {
1326        return Err(total_bytes_per_sample);
1327    }
1328
1329    Ok(())
1330}