wgpu_core/
validation.rs

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