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