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