wgpu_core/
validation.rs

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