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