wgpu_core/
validation.rs

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