wgpu_core/
validation.rs

1use alloc::{
2    boxed::Box,
3    string::{String, ToString as _},
4    vec::Vec,
5};
6use core::fmt;
7
8use arrayvec::ArrayVec;
9use hashbrown::hash_map::Entry;
10use shader_io_deductions::{display_deductions_as_optional_list, MaxVertexShaderOutputDeduction};
11use thiserror::Error;
12use wgt::{
13    error::{ErrorType, WebGpuError},
14    BindGroupLayoutEntry, BindingType,
15};
16
17use crate::{
18    device::bgl, resource::InvalidResourceError,
19    validation::shader_io_deductions::MaxFragmentShaderInputDeduction, FastHashMap, FastHashSet,
20};
21
22pub mod shader_io_deductions;
23
24#[derive(Debug)]
25enum ResourceType {
26    Buffer {
27        size: wgt::BufferSize,
28    },
29    Texture {
30        dim: naga::ImageDimension,
31        arrayed: bool,
32        class: naga::ImageClass,
33    },
34    Sampler {
35        comparison: bool,
36    },
37    AccelerationStructure {
38        vertex_return: bool,
39    },
40}
41
42#[derive(Clone, Debug)]
43pub enum BindingTypeName {
44    Buffer,
45    Texture,
46    Sampler,
47    AccelerationStructure,
48    ExternalTexture,
49}
50
51impl From<&ResourceType> for BindingTypeName {
52    fn from(ty: &ResourceType) -> BindingTypeName {
53        match ty {
54            ResourceType::Buffer { .. } => BindingTypeName::Buffer,
55            ResourceType::Texture {
56                class: naga::ImageClass::External,
57                ..
58            } => BindingTypeName::ExternalTexture,
59            ResourceType::Texture { .. } => BindingTypeName::Texture,
60            ResourceType::Sampler { .. } => BindingTypeName::Sampler,
61            ResourceType::AccelerationStructure { .. } => BindingTypeName::AccelerationStructure,
62        }
63    }
64}
65
66impl From<&BindingType> for BindingTypeName {
67    fn from(ty: &BindingType) -> BindingTypeName {
68        match ty {
69            BindingType::Buffer { .. } => BindingTypeName::Buffer,
70            BindingType::Texture { .. } => BindingTypeName::Texture,
71            BindingType::StorageTexture { .. } => BindingTypeName::Texture,
72            BindingType::Sampler { .. } => BindingTypeName::Sampler,
73            BindingType::AccelerationStructure { .. } => BindingTypeName::AccelerationStructure,
74            BindingType::ExternalTexture => BindingTypeName::ExternalTexture,
75        }
76    }
77}
78
79#[derive(Debug)]
80struct Resource {
81    #[allow(unused)]
82    name: Option<String>,
83    bind: naga::ResourceBinding,
84    ty: ResourceType,
85    class: naga::AddressSpace,
86}
87
88#[derive(Clone, Copy, Debug)]
89enum NumericDimension {
90    Scalar,
91    Vector(naga::VectorSize),
92    Matrix(naga::VectorSize, naga::VectorSize),
93}
94
95impl fmt::Display for NumericDimension {
96    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
97        match *self {
98            Self::Scalar => write!(f, ""),
99            Self::Vector(size) => write!(f, "x{}", size as u8),
100            Self::Matrix(columns, rows) => write!(f, "x{}{}", columns as u8, rows as u8),
101        }
102    }
103}
104
105#[derive(Clone, Copy, Debug)]
106pub struct NumericType {
107    dim: NumericDimension,
108    scalar: naga::Scalar,
109}
110
111impl fmt::Display for NumericType {
112    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
113        write!(
114            f,
115            "{:?}{}{}",
116            self.scalar.kind,
117            self.scalar.width * 8,
118            self.dim
119        )
120    }
121}
122
123#[derive(Clone, Debug)]
124pub struct InterfaceVar {
125    pub ty: NumericType,
126    interpolation: Option<naga::Interpolation>,
127    sampling: Option<naga::Sampling>,
128    per_primitive: bool,
129}
130
131impl InterfaceVar {
132    pub fn vertex_attribute(format: wgt::VertexFormat) -> Self {
133        InterfaceVar {
134            ty: NumericType::from_vertex_format(format),
135            interpolation: None,
136            sampling: None,
137            per_primitive: false,
138        }
139    }
140}
141
142impl fmt::Display for InterfaceVar {
143    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
144        write!(
145            f,
146            "{} interpolated as {:?} with sampling {:?}",
147            self.ty, self.interpolation, self.sampling
148        )
149    }
150}
151
152#[derive(Debug)]
153enum Varying {
154    Local { location: u32, iv: InterfaceVar },
155    BuiltIn(naga::BuiltIn),
156}
157
158#[allow(unused)]
159#[derive(Debug)]
160struct SpecializationConstant {
161    id: u32,
162    ty: NumericType,
163}
164
165#[derive(Debug)]
166struct EntryPointMeshInfo {
167    max_vertices: u32,
168    max_primitives: u32,
169}
170
171#[derive(Debug, Default)]
172struct EntryPoint {
173    inputs: Vec<Varying>,
174    outputs: Vec<Varying>,
175    resources: Vec<naga::Handle<Resource>>,
176    #[allow(unused)]
177    spec_constants: Vec<SpecializationConstant>,
178    sampling_pairs: FastHashSet<(naga::Handle<Resource>, naga::Handle<Resource>)>,
179    workgroup_size: [u32; 3],
180    dual_source_blending: bool,
181    task_payload_size: Option<u32>,
182    mesh_info: Option<EntryPointMeshInfo>,
183}
184
185#[derive(Debug)]
186pub struct Interface {
187    limits: wgt::Limits,
188    resources: naga::Arena<Resource>,
189    entry_points: FastHashMap<(naga::ShaderStage, String), EntryPoint>,
190}
191
192#[derive(Clone, Debug, Error)]
193#[non_exhaustive]
194pub enum BindingError {
195    #[error("Binding is missing from the pipeline layout")]
196    Missing,
197    #[error("Visibility flags don't include the shader stage")]
198    Invisible,
199    #[error(
200        "Type on the shader side ({shader:?}) does not match the pipeline binding ({binding:?})"
201    )]
202    WrongType {
203        binding: BindingTypeName,
204        shader: BindingTypeName,
205    },
206    #[error("Storage class {binding:?} doesn't match the shader {shader:?}")]
207    WrongAddressSpace {
208        binding: naga::AddressSpace,
209        shader: naga::AddressSpace,
210    },
211    #[error("Address space {space:?} is not a valid Buffer address space")]
212    WrongBufferAddressSpace { space: naga::AddressSpace },
213    #[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}")]
214    WrongBufferSize {
215        buffer_size: wgt::BufferSize,
216        min_binding_size: wgt::BufferSize,
217    },
218    #[error("View dimension {dim:?} (is array: {is_array}) doesn't match the binding {binding:?}")]
219    WrongTextureViewDimension {
220        dim: naga::ImageDimension,
221        is_array: bool,
222        binding: BindingType,
223    },
224    #[error("Texture class {binding:?} doesn't match the shader {shader:?}")]
225    WrongTextureClass {
226        binding: naga::ImageClass,
227        shader: naga::ImageClass,
228    },
229    #[error("Comparison flag doesn't match the shader")]
230    WrongSamplerComparison,
231    #[error("Derived bind group layout type is not consistent between stages")]
232    InconsistentlyDerivedType,
233    #[error("Texture format {0:?} is not supported for storage use")]
234    BadStorageFormat(wgt::TextureFormat),
235}
236
237impl WebGpuError for BindingError {
238    fn webgpu_error_type(&self) -> ErrorType {
239        ErrorType::Validation
240    }
241}
242
243#[derive(Clone, Debug, Error)]
244#[non_exhaustive]
245pub enum FilteringError {
246    #[error("Integer textures can't be sampled with a filtering sampler")]
247    Integer,
248    #[error("Non-filterable float textures can't be sampled with a filtering sampler")]
249    Float,
250}
251
252impl WebGpuError for FilteringError {
253    fn webgpu_error_type(&self) -> ErrorType {
254        ErrorType::Validation
255    }
256}
257
258#[derive(Clone, Debug, Error)]
259#[non_exhaustive]
260pub enum InputError {
261    #[error("Input is not provided by the earlier stage in the pipeline")]
262    Missing,
263    #[error("Input type is not compatible with the provided {0}")]
264    WrongType(NumericType),
265    #[error("Input interpolation doesn't match provided {0:?}")]
266    InterpolationMismatch(Option<naga::Interpolation>),
267    #[error("Input sampling doesn't match provided {0:?}")]
268    SamplingMismatch(Option<naga::Sampling>),
269    #[error("Pipeline input has per_primitive={pipeline_input}, but shader expects per_primitive={shader}")]
270    WrongPerPrimitive { pipeline_input: bool, shader: bool },
271}
272
273impl WebGpuError for InputError {
274    fn webgpu_error_type(&self) -> ErrorType {
275        ErrorType::Validation
276    }
277}
278
279/// Errors produced when validating a programmable stage of a pipeline.
280#[derive(Clone, Debug, Error)]
281#[non_exhaustive]
282pub enum StageError {
283    #[error(
284        "Shader entry point's workgroup size {current:?} ({current_total} total invocations) must be less or equal to the per-dimension
285        limit `Limits::{per_dimension_limit}` of {limit:?} and the total invocation limit `Limits::{total_limit}` of {total}"
286    )]
287    InvalidWorkgroupSize {
288        current: [u32; 3],
289        current_total: u32,
290        limit: [u32; 3],
291        total: u32,
292        per_dimension_limit: &'static str,
293        total_limit: &'static str,
294    },
295    #[error("Unable to find entry point '{0}'")]
296    MissingEntryPoint(String),
297    #[error("Shader global {0:?} is not available in the pipeline layout")]
298    Binding(naga::ResourceBinding, #[source] BindingError),
299    #[error("Unable to filter the texture ({texture:?}) by the sampler ({sampler:?})")]
300    Filtering {
301        texture: naga::ResourceBinding,
302        sampler: naga::ResourceBinding,
303        #[source]
304        error: FilteringError,
305    },
306    #[error("Location[{location}] {var} is not provided by the previous stage outputs")]
307    Input {
308        location: wgt::ShaderLocation,
309        var: InterfaceVar,
310        #[source]
311        error: InputError,
312    },
313    #[error(
314        "Unable to select an entry point: no entry point was found in the provided shader module"
315    )]
316    NoEntryPointFound,
317    #[error(
318        "Unable to select an entry point: \
319        multiple entry points were found in the provided shader module, \
320        but no entry point was specified"
321    )]
322    MultipleEntryPointsFound,
323    #[error(transparent)]
324    InvalidResource(#[from] InvalidResourceError),
325    #[error(
326        "vertex shader output location Location[{location}] ({var}) exceeds the \
327        `max_inter_stage_shader_variables` limit ({}, 0-based){}",
328        // NOTE: Remember: the limit is 0-based for indices.
329        limit - 1,
330        display_deductions_as_optional_list(deductions, |d| d.for_location())
331    )]
332    VertexOutputLocationTooLarge {
333        location: u32,
334        var: InterfaceVar,
335        limit: u32,
336        deductions: Vec<MaxVertexShaderOutputDeduction>,
337    },
338    #[error(
339        "found {num_found} user-defined vertex shader output variables, which exceeds the \
340        `max_inter_stage_shader_variables` limit ({limit}){}",
341        display_deductions_as_optional_list(deductions, |d| d.for_variables())
342    )]
343    TooManyUserDefinedVertexOutputs {
344        num_found: u32,
345        limit: u32,
346        deductions: Vec<MaxVertexShaderOutputDeduction>,
347    },
348    #[error(
349        "fragment shader input location Location[{location}] ({var}) exceeds the \
350        `max_inter_stage_shader_variables` limit ({}, 0-based){}",
351        // NOTE: Remember: the limit is 0-based for indices.
352        limit - 1,
353        // NOTE: WebGPU spec. validation for fragment inputs is expressed in terms of variables
354        // (unlike vertex outputs), so we use `MaxFragmentShaderInputDeduction::for_variables` here
355        // (and not a non-existent `for_locations`).
356        display_deductions_as_optional_list(deductions, |d| d.for_variables())
357    )]
358    FragmentInputLocationTooLarge {
359        location: u32,
360        var: InterfaceVar,
361        limit: u32,
362        deductions: Vec<MaxFragmentShaderInputDeduction>,
363    },
364    #[error(
365        "found {num_found} user-defined fragment shader input variables, which exceeds the \
366        `max_inter_stage_shader_variables` limit ({limit}){}",
367        display_deductions_as_optional_list(deductions, |d| d.for_variables())
368    )]
369    TooManyUserDefinedFragmentInputs {
370        num_found: u32,
371        limit: u32,
372        deductions: Vec<MaxFragmentShaderInputDeduction>,
373    },
374    #[error(
375        "Location[{location}] {var}'s index exceeds the `max_color_attachments` limit ({limit})"
376    )]
377    ColorAttachmentLocationTooLarge {
378        location: u32,
379        var: InterfaceVar,
380        limit: u32,
381    },
382    #[error("Mesh shaders are limited to {limit} output vertices by `Limits::max_mesh_output_vertices`, but the shader has a maximum number of {value}")]
383    TooManyMeshVertices { limit: u32, value: u32 },
384    #[error("Mesh shaders are limited to {limit} output primitives by `Limits::max_mesh_output_primitives`, but the shader has a maximum number of {value}")]
385    TooManyMeshPrimitives { limit: u32, value: u32 },
386    #[error("Mesh or task shaders are limited to {limit} bytes of task payload by `Limits::max_task_payload_size`, but the shader has a task payload of size {value}")]
387    TaskPayloadTooLarge { limit: u32, value: u32 },
388    #[error("Mesh shader's task payload has size ({shader:?}), which doesn't match the payload declared in the task stage ({input:?})")]
389    TaskPayloadMustMatch {
390        input: Option<u32>,
391        shader: Option<u32>,
392    },
393    #[error("Primitive index can only be used in a fragment shader if the preceding shader was a vertex shader or a mesh shader that writes to primitive index.")]
394    InvalidPrimitiveIndex,
395    #[error("If a mesh shader writes to primitive index, it must be read by the fragment shader.")]
396    MissingPrimitiveIndex,
397    #[error("DrawId cannot be used in the same pipeline as a task shader")]
398    DrawIdError,
399}
400
401impl WebGpuError for StageError {
402    fn webgpu_error_type(&self) -> ErrorType {
403        let e: &dyn WebGpuError = match self {
404            Self::Binding(_, e) => e,
405            Self::InvalidResource(e) => e,
406            Self::Filtering {
407                texture: _,
408                sampler: _,
409                error,
410            } => error,
411            Self::Input {
412                location: _,
413                var: _,
414                error,
415            } => error,
416            Self::InvalidWorkgroupSize { .. }
417            | Self::MissingEntryPoint(..)
418            | Self::NoEntryPointFound
419            | Self::MultipleEntryPointsFound
420            | Self::VertexOutputLocationTooLarge { .. }
421            | Self::TooManyUserDefinedVertexOutputs { .. }
422            | Self::FragmentInputLocationTooLarge { .. }
423            | Self::TooManyUserDefinedFragmentInputs { .. }
424            | Self::ColorAttachmentLocationTooLarge { .. }
425            | Self::TooManyMeshVertices { .. }
426            | Self::TooManyMeshPrimitives { .. }
427            | Self::TaskPayloadTooLarge { .. }
428            | Self::TaskPayloadMustMatch { .. }
429            | Self::InvalidPrimitiveIndex
430            | Self::MissingPrimitiveIndex
431            | Self::DrawIdError => return ErrorType::Validation,
432        };
433        e.webgpu_error_type()
434    }
435}
436
437pub fn map_storage_format_to_naga(format: wgt::TextureFormat) -> Option<naga::StorageFormat> {
438    use naga::StorageFormat as Sf;
439    use wgt::TextureFormat as Tf;
440
441    Some(match format {
442        Tf::R8Unorm => Sf::R8Unorm,
443        Tf::R8Snorm => Sf::R8Snorm,
444        Tf::R8Uint => Sf::R8Uint,
445        Tf::R8Sint => Sf::R8Sint,
446
447        Tf::R16Uint => Sf::R16Uint,
448        Tf::R16Sint => Sf::R16Sint,
449        Tf::R16Float => Sf::R16Float,
450        Tf::Rg8Unorm => Sf::Rg8Unorm,
451        Tf::Rg8Snorm => Sf::Rg8Snorm,
452        Tf::Rg8Uint => Sf::Rg8Uint,
453        Tf::Rg8Sint => Sf::Rg8Sint,
454
455        Tf::R32Uint => Sf::R32Uint,
456        Tf::R32Sint => Sf::R32Sint,
457        Tf::R32Float => Sf::R32Float,
458        Tf::Rg16Uint => Sf::Rg16Uint,
459        Tf::Rg16Sint => Sf::Rg16Sint,
460        Tf::Rg16Float => Sf::Rg16Float,
461        Tf::Rgba8Unorm => Sf::Rgba8Unorm,
462        Tf::Rgba8Snorm => Sf::Rgba8Snorm,
463        Tf::Rgba8Uint => Sf::Rgba8Uint,
464        Tf::Rgba8Sint => Sf::Rgba8Sint,
465        Tf::Bgra8Unorm => Sf::Bgra8Unorm,
466
467        Tf::Rgb10a2Uint => Sf::Rgb10a2Uint,
468        Tf::Rgb10a2Unorm => Sf::Rgb10a2Unorm,
469        Tf::Rg11b10Ufloat => Sf::Rg11b10Ufloat,
470
471        Tf::R64Uint => Sf::R64Uint,
472        Tf::Rg32Uint => Sf::Rg32Uint,
473        Tf::Rg32Sint => Sf::Rg32Sint,
474        Tf::Rg32Float => Sf::Rg32Float,
475        Tf::Rgba16Uint => Sf::Rgba16Uint,
476        Tf::Rgba16Sint => Sf::Rgba16Sint,
477        Tf::Rgba16Float => Sf::Rgba16Float,
478
479        Tf::Rgba32Uint => Sf::Rgba32Uint,
480        Tf::Rgba32Sint => Sf::Rgba32Sint,
481        Tf::Rgba32Float => Sf::Rgba32Float,
482
483        Tf::R16Unorm => Sf::R16Unorm,
484        Tf::R16Snorm => Sf::R16Snorm,
485        Tf::Rg16Unorm => Sf::Rg16Unorm,
486        Tf::Rg16Snorm => Sf::Rg16Snorm,
487        Tf::Rgba16Unorm => Sf::Rgba16Unorm,
488        Tf::Rgba16Snorm => Sf::Rgba16Snorm,
489
490        _ => return None,
491    })
492}
493
494pub fn map_storage_format_from_naga(format: naga::StorageFormat) -> wgt::TextureFormat {
495    use naga::StorageFormat as Sf;
496    use wgt::TextureFormat as Tf;
497
498    match format {
499        Sf::R8Unorm => Tf::R8Unorm,
500        Sf::R8Snorm => Tf::R8Snorm,
501        Sf::R8Uint => Tf::R8Uint,
502        Sf::R8Sint => Tf::R8Sint,
503
504        Sf::R16Uint => Tf::R16Uint,
505        Sf::R16Sint => Tf::R16Sint,
506        Sf::R16Float => Tf::R16Float,
507        Sf::Rg8Unorm => Tf::Rg8Unorm,
508        Sf::Rg8Snorm => Tf::Rg8Snorm,
509        Sf::Rg8Uint => Tf::Rg8Uint,
510        Sf::Rg8Sint => Tf::Rg8Sint,
511
512        Sf::R32Uint => Tf::R32Uint,
513        Sf::R32Sint => Tf::R32Sint,
514        Sf::R32Float => Tf::R32Float,
515        Sf::Rg16Uint => Tf::Rg16Uint,
516        Sf::Rg16Sint => Tf::Rg16Sint,
517        Sf::Rg16Float => Tf::Rg16Float,
518        Sf::Rgba8Unorm => Tf::Rgba8Unorm,
519        Sf::Rgba8Snorm => Tf::Rgba8Snorm,
520        Sf::Rgba8Uint => Tf::Rgba8Uint,
521        Sf::Rgba8Sint => Tf::Rgba8Sint,
522        Sf::Bgra8Unorm => Tf::Bgra8Unorm,
523
524        Sf::Rgb10a2Uint => Tf::Rgb10a2Uint,
525        Sf::Rgb10a2Unorm => Tf::Rgb10a2Unorm,
526        Sf::Rg11b10Ufloat => Tf::Rg11b10Ufloat,
527
528        Sf::R64Uint => Tf::R64Uint,
529        Sf::Rg32Uint => Tf::Rg32Uint,
530        Sf::Rg32Sint => Tf::Rg32Sint,
531        Sf::Rg32Float => Tf::Rg32Float,
532        Sf::Rgba16Uint => Tf::Rgba16Uint,
533        Sf::Rgba16Sint => Tf::Rgba16Sint,
534        Sf::Rgba16Float => Tf::Rgba16Float,
535
536        Sf::Rgba32Uint => Tf::Rgba32Uint,
537        Sf::Rgba32Sint => Tf::Rgba32Sint,
538        Sf::Rgba32Float => Tf::Rgba32Float,
539
540        Sf::R16Unorm => Tf::R16Unorm,
541        Sf::R16Snorm => Tf::R16Snorm,
542        Sf::Rg16Unorm => Tf::Rg16Unorm,
543        Sf::Rg16Snorm => Tf::Rg16Snorm,
544        Sf::Rgba16Unorm => Tf::Rgba16Unorm,
545        Sf::Rgba16Snorm => Tf::Rgba16Snorm,
546    }
547}
548
549impl Resource {
550    fn check_binding_use(&self, entry: &BindGroupLayoutEntry) -> Result<(), BindingError> {
551        match self.ty {
552            ResourceType::Buffer { size } => {
553                let min_size = match entry.ty {
554                    BindingType::Buffer {
555                        ty,
556                        has_dynamic_offset: _,
557                        min_binding_size,
558                    } => {
559                        let class = match ty {
560                            wgt::BufferBindingType::Uniform => naga::AddressSpace::Uniform,
561                            wgt::BufferBindingType::Storage { read_only } => {
562                                let mut naga_access = naga::StorageAccess::LOAD;
563                                naga_access.set(naga::StorageAccess::STORE, !read_only);
564                                naga::AddressSpace::Storage {
565                                    access: naga_access,
566                                }
567                            }
568                        };
569                        if self.class != class {
570                            return Err(BindingError::WrongAddressSpace {
571                                binding: class,
572                                shader: self.class,
573                            });
574                        }
575                        min_binding_size
576                    }
577                    _ => {
578                        return Err(BindingError::WrongType {
579                            binding: (&entry.ty).into(),
580                            shader: (&self.ty).into(),
581                        })
582                    }
583                };
584                match min_size {
585                    Some(non_zero) if non_zero < size => {
586                        return Err(BindingError::WrongBufferSize {
587                            buffer_size: size,
588                            min_binding_size: non_zero,
589                        })
590                    }
591                    _ => (),
592                }
593            }
594            ResourceType::Sampler { comparison } => match entry.ty {
595                BindingType::Sampler(ty) => {
596                    if (ty == wgt::SamplerBindingType::Comparison) != comparison {
597                        return Err(BindingError::WrongSamplerComparison);
598                    }
599                }
600                _ => {
601                    return Err(BindingError::WrongType {
602                        binding: (&entry.ty).into(),
603                        shader: (&self.ty).into(),
604                    })
605                }
606            },
607            ResourceType::Texture {
608                dim,
609                arrayed,
610                class,
611            } => {
612                let view_dimension = match entry.ty {
613                    BindingType::Texture { view_dimension, .. }
614                    | BindingType::StorageTexture { view_dimension, .. } => view_dimension,
615                    BindingType::ExternalTexture => wgt::TextureViewDimension::D2,
616                    _ => {
617                        return Err(BindingError::WrongTextureViewDimension {
618                            dim,
619                            is_array: false,
620                            binding: entry.ty,
621                        })
622                    }
623                };
624                if arrayed {
625                    match (dim, view_dimension) {
626                        (naga::ImageDimension::D2, wgt::TextureViewDimension::D2Array) => (),
627                        (naga::ImageDimension::Cube, wgt::TextureViewDimension::CubeArray) => (),
628                        _ => {
629                            return Err(BindingError::WrongTextureViewDimension {
630                                dim,
631                                is_array: true,
632                                binding: entry.ty,
633                            })
634                        }
635                    }
636                } else {
637                    match (dim, view_dimension) {
638                        (naga::ImageDimension::D1, wgt::TextureViewDimension::D1) => (),
639                        (naga::ImageDimension::D2, wgt::TextureViewDimension::D2) => (),
640                        (naga::ImageDimension::D3, wgt::TextureViewDimension::D3) => (),
641                        (naga::ImageDimension::Cube, wgt::TextureViewDimension::Cube) => (),
642                        _ => {
643                            return Err(BindingError::WrongTextureViewDimension {
644                                dim,
645                                is_array: false,
646                                binding: entry.ty,
647                            })
648                        }
649                    }
650                }
651                let expected_class = match entry.ty {
652                    BindingType::Texture {
653                        sample_type,
654                        view_dimension: _,
655                        multisampled: multi,
656                    } => match sample_type {
657                        wgt::TextureSampleType::Float { .. } => naga::ImageClass::Sampled {
658                            kind: naga::ScalarKind::Float,
659                            multi,
660                        },
661                        wgt::TextureSampleType::Sint => naga::ImageClass::Sampled {
662                            kind: naga::ScalarKind::Sint,
663                            multi,
664                        },
665                        wgt::TextureSampleType::Uint => naga::ImageClass::Sampled {
666                            kind: naga::ScalarKind::Uint,
667                            multi,
668                        },
669                        wgt::TextureSampleType::Depth => naga::ImageClass::Depth { multi },
670                    },
671                    BindingType::StorageTexture {
672                        access,
673                        format,
674                        view_dimension: _,
675                    } => {
676                        let naga_format = map_storage_format_to_naga(format)
677                            .ok_or(BindingError::BadStorageFormat(format))?;
678                        let naga_access = match access {
679                            wgt::StorageTextureAccess::ReadOnly => naga::StorageAccess::LOAD,
680                            wgt::StorageTextureAccess::WriteOnly => naga::StorageAccess::STORE,
681                            wgt::StorageTextureAccess::ReadWrite => {
682                                naga::StorageAccess::LOAD | naga::StorageAccess::STORE
683                            }
684                            wgt::StorageTextureAccess::Atomic => {
685                                naga::StorageAccess::ATOMIC
686                                    | naga::StorageAccess::LOAD
687                                    | naga::StorageAccess::STORE
688                            }
689                        };
690                        naga::ImageClass::Storage {
691                            format: naga_format,
692                            access: naga_access,
693                        }
694                    }
695                    BindingType::ExternalTexture => naga::ImageClass::External,
696                    _ => {
697                        return Err(BindingError::WrongType {
698                            binding: (&entry.ty).into(),
699                            shader: (&self.ty).into(),
700                        })
701                    }
702                };
703                if class != expected_class {
704                    return Err(BindingError::WrongTextureClass {
705                        binding: expected_class,
706                        shader: class,
707                    });
708                }
709            }
710            ResourceType::AccelerationStructure { vertex_return } => match entry.ty {
711                BindingType::AccelerationStructure {
712                    vertex_return: entry_vertex_return,
713                } if vertex_return == entry_vertex_return => (),
714                _ => {
715                    return Err(BindingError::WrongType {
716                        binding: (&entry.ty).into(),
717                        shader: (&self.ty).into(),
718                    })
719                }
720            },
721        };
722
723        Ok(())
724    }
725
726    fn derive_binding_type(
727        &self,
728        is_reffed_by_sampler_in_entrypoint: bool,
729    ) -> Result<BindingType, BindingError> {
730        Ok(match self.ty {
731            ResourceType::Buffer { size } => BindingType::Buffer {
732                ty: match self.class {
733                    naga::AddressSpace::Uniform => wgt::BufferBindingType::Uniform,
734                    naga::AddressSpace::Storage { access } => wgt::BufferBindingType::Storage {
735                        read_only: access == naga::StorageAccess::LOAD,
736                    },
737                    _ => return Err(BindingError::WrongBufferAddressSpace { space: self.class }),
738                },
739                has_dynamic_offset: false,
740                min_binding_size: Some(size),
741            },
742            ResourceType::Sampler { comparison } => BindingType::Sampler(if comparison {
743                wgt::SamplerBindingType::Comparison
744            } else {
745                wgt::SamplerBindingType::Filtering
746            }),
747            ResourceType::Texture {
748                dim,
749                arrayed,
750                class,
751            } => {
752                let view_dimension = match dim {
753                    naga::ImageDimension::D1 => wgt::TextureViewDimension::D1,
754                    naga::ImageDimension::D2 if arrayed => wgt::TextureViewDimension::D2Array,
755                    naga::ImageDimension::D2 => wgt::TextureViewDimension::D2,
756                    naga::ImageDimension::D3 => wgt::TextureViewDimension::D3,
757                    naga::ImageDimension::Cube if arrayed => wgt::TextureViewDimension::CubeArray,
758                    naga::ImageDimension::Cube => wgt::TextureViewDimension::Cube,
759                };
760                match class {
761                    naga::ImageClass::Sampled { multi, kind } => BindingType::Texture {
762                        sample_type: match kind {
763                            naga::ScalarKind::Float => wgt::TextureSampleType::Float {
764                                filterable: is_reffed_by_sampler_in_entrypoint,
765                            },
766                            naga::ScalarKind::Sint => wgt::TextureSampleType::Sint,
767                            naga::ScalarKind::Uint => wgt::TextureSampleType::Uint,
768                            naga::ScalarKind::AbstractInt
769                            | naga::ScalarKind::AbstractFloat
770                            | naga::ScalarKind::Bool => unreachable!(),
771                        },
772                        view_dimension,
773                        multisampled: multi,
774                    },
775                    naga::ImageClass::Depth { multi } => BindingType::Texture {
776                        sample_type: wgt::TextureSampleType::Depth,
777                        view_dimension,
778                        multisampled: multi,
779                    },
780                    naga::ImageClass::Storage { format, access } => BindingType::StorageTexture {
781                        access: {
782                            const LOAD_STORE: naga::StorageAccess =
783                                naga::StorageAccess::LOAD.union(naga::StorageAccess::STORE);
784                            match access {
785                                naga::StorageAccess::LOAD => wgt::StorageTextureAccess::ReadOnly,
786                                naga::StorageAccess::STORE => wgt::StorageTextureAccess::WriteOnly,
787                                LOAD_STORE => wgt::StorageTextureAccess::ReadWrite,
788                                _ if access.contains(naga::StorageAccess::ATOMIC) => {
789                                    wgt::StorageTextureAccess::Atomic
790                                }
791                                _ => unreachable!(),
792                            }
793                        },
794                        view_dimension,
795                        format: {
796                            let f = map_storage_format_from_naga(format);
797                            let original = map_storage_format_to_naga(f)
798                                .ok_or(BindingError::BadStorageFormat(f))?;
799                            debug_assert_eq!(format, original);
800                            f
801                        },
802                    },
803                    naga::ImageClass::External => BindingType::ExternalTexture,
804                }
805            }
806            ResourceType::AccelerationStructure { vertex_return } => {
807                BindingType::AccelerationStructure { vertex_return }
808            }
809        })
810    }
811}
812
813impl NumericType {
814    fn from_vertex_format(format: wgt::VertexFormat) -> Self {
815        use naga::{Scalar, VectorSize as Vs};
816        use wgt::VertexFormat as Vf;
817
818        let (dim, scalar) = match format {
819            Vf::Uint8 | Vf::Uint16 | Vf::Uint32 => (NumericDimension::Scalar, Scalar::U32),
820            Vf::Uint8x2 | Vf::Uint16x2 | Vf::Uint32x2 => {
821                (NumericDimension::Vector(Vs::Bi), Scalar::U32)
822            }
823            Vf::Uint32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::U32),
824            Vf::Uint8x4 | Vf::Uint16x4 | Vf::Uint32x4 => {
825                (NumericDimension::Vector(Vs::Quad), Scalar::U32)
826            }
827            Vf::Sint8 | Vf::Sint16 | Vf::Sint32 => (NumericDimension::Scalar, Scalar::I32),
828            Vf::Sint8x2 | Vf::Sint16x2 | Vf::Sint32x2 => {
829                (NumericDimension::Vector(Vs::Bi), Scalar::I32)
830            }
831            Vf::Sint32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::I32),
832            Vf::Sint8x4 | Vf::Sint16x4 | Vf::Sint32x4 => {
833                (NumericDimension::Vector(Vs::Quad), Scalar::I32)
834            }
835            Vf::Unorm8 | Vf::Unorm16 | Vf::Snorm8 | Vf::Snorm16 | Vf::Float16 | Vf::Float32 => {
836                (NumericDimension::Scalar, Scalar::F32)
837            }
838            Vf::Unorm8x2
839            | Vf::Snorm8x2
840            | Vf::Unorm16x2
841            | Vf::Snorm16x2
842            | Vf::Float16x2
843            | Vf::Float32x2 => (NumericDimension::Vector(Vs::Bi), Scalar::F32),
844            Vf::Float32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
845            Vf::Unorm8x4
846            | Vf::Snorm8x4
847            | Vf::Unorm16x4
848            | Vf::Snorm16x4
849            | Vf::Float16x4
850            | Vf::Float32x4
851            | Vf::Unorm10_10_10_2
852            | Vf::Unorm8x4Bgra => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
853            Vf::Float64 => (NumericDimension::Scalar, Scalar::F64),
854            Vf::Float64x2 => (NumericDimension::Vector(Vs::Bi), Scalar::F64),
855            Vf::Float64x3 => (NumericDimension::Vector(Vs::Tri), Scalar::F64),
856            Vf::Float64x4 => (NumericDimension::Vector(Vs::Quad), Scalar::F64),
857        };
858
859        NumericType {
860            dim,
861            //Note: Shader always sees data as int, uint, or float.
862            // It doesn't know if the original is normalized in a tighter form.
863            scalar,
864        }
865    }
866
867    fn from_texture_format(format: wgt::TextureFormat) -> Self {
868        use naga::{Scalar, VectorSize as Vs};
869        use wgt::TextureFormat as Tf;
870
871        let (dim, scalar) = match format {
872            Tf::R8Unorm | Tf::R8Snorm | Tf::R16Float | Tf::R32Float => {
873                (NumericDimension::Scalar, Scalar::F32)
874            }
875            Tf::R8Uint | Tf::R16Uint | Tf::R32Uint => (NumericDimension::Scalar, Scalar::U32),
876            Tf::R8Sint | Tf::R16Sint | Tf::R32Sint => (NumericDimension::Scalar, Scalar::I32),
877            Tf::Rg8Unorm | Tf::Rg8Snorm | Tf::Rg16Float | Tf::Rg32Float => {
878                (NumericDimension::Vector(Vs::Bi), Scalar::F32)
879            }
880            Tf::R64Uint => (NumericDimension::Scalar, Scalar::U64),
881            Tf::Rg8Uint | Tf::Rg16Uint | Tf::Rg32Uint => {
882                (NumericDimension::Vector(Vs::Bi), Scalar::U32)
883            }
884            Tf::Rg8Sint | Tf::Rg16Sint | Tf::Rg32Sint => {
885                (NumericDimension::Vector(Vs::Bi), Scalar::I32)
886            }
887            Tf::R16Snorm | Tf::R16Unorm => (NumericDimension::Scalar, Scalar::F32),
888            Tf::Rg16Snorm | Tf::Rg16Unorm => (NumericDimension::Vector(Vs::Bi), Scalar::F32),
889            Tf::Rgba16Snorm | Tf::Rgba16Unorm => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
890            Tf::Rgba8Unorm
891            | Tf::Rgba8UnormSrgb
892            | Tf::Rgba8Snorm
893            | Tf::Bgra8Unorm
894            | Tf::Bgra8UnormSrgb
895            | Tf::Rgb10a2Unorm
896            | Tf::Rgba16Float
897            | Tf::Rgba32Float => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
898            Tf::Rgba8Uint | Tf::Rgba16Uint | Tf::Rgba32Uint | Tf::Rgb10a2Uint => {
899                (NumericDimension::Vector(Vs::Quad), Scalar::U32)
900            }
901            Tf::Rgba8Sint | Tf::Rgba16Sint | Tf::Rgba32Sint => {
902                (NumericDimension::Vector(Vs::Quad), Scalar::I32)
903            }
904            Tf::Rg11b10Ufloat => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
905            Tf::Stencil8
906            | Tf::Depth16Unorm
907            | Tf::Depth32Float
908            | Tf::Depth32FloatStencil8
909            | Tf::Depth24Plus
910            | Tf::Depth24PlusStencil8 => {
911                panic!("Unexpected depth format")
912            }
913            Tf::NV12 => panic!("Unexpected nv12 format"),
914            Tf::P010 => panic!("Unexpected p010 format"),
915            Tf::Rgb9e5Ufloat => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
916            Tf::Bc1RgbaUnorm
917            | Tf::Bc1RgbaUnormSrgb
918            | Tf::Bc2RgbaUnorm
919            | Tf::Bc2RgbaUnormSrgb
920            | Tf::Bc3RgbaUnorm
921            | Tf::Bc3RgbaUnormSrgb
922            | Tf::Bc7RgbaUnorm
923            | Tf::Bc7RgbaUnormSrgb
924            | Tf::Etc2Rgb8A1Unorm
925            | Tf::Etc2Rgb8A1UnormSrgb
926            | Tf::Etc2Rgba8Unorm
927            | Tf::Etc2Rgba8UnormSrgb => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
928            Tf::Bc4RUnorm | Tf::Bc4RSnorm | Tf::EacR11Unorm | Tf::EacR11Snorm => {
929                (NumericDimension::Scalar, Scalar::F32)
930            }
931            Tf::Bc5RgUnorm | Tf::Bc5RgSnorm | Tf::EacRg11Unorm | Tf::EacRg11Snorm => {
932                (NumericDimension::Vector(Vs::Bi), Scalar::F32)
933            }
934            Tf::Bc6hRgbUfloat | Tf::Bc6hRgbFloat | Tf::Etc2Rgb8Unorm | Tf::Etc2Rgb8UnormSrgb => {
935                (NumericDimension::Vector(Vs::Tri), Scalar::F32)
936            }
937            Tf::Astc {
938                block: _,
939                channel: _,
940            } => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
941        };
942
943        NumericType {
944            dim,
945            //Note: Shader always sees data as int, uint, or float.
946            // It doesn't know if the original is normalized in a tighter form.
947            scalar,
948        }
949    }
950
951    fn is_subtype_of(&self, other: &NumericType) -> bool {
952        if self.scalar.width > other.scalar.width {
953            return false;
954        }
955        if self.scalar.kind != other.scalar.kind {
956            return false;
957        }
958        match (self.dim, other.dim) {
959            (NumericDimension::Scalar, NumericDimension::Scalar) => true,
960            (NumericDimension::Scalar, NumericDimension::Vector(_)) => true,
961            (NumericDimension::Vector(s0), NumericDimension::Vector(s1)) => s0 <= s1,
962            (NumericDimension::Matrix(c0, r0), NumericDimension::Matrix(c1, r1)) => {
963                c0 == c1 && r0 == r1
964            }
965            _ => false,
966        }
967    }
968}
969
970/// Return true if the fragment `format` is covered by the provided `output`.
971pub fn check_texture_format(
972    format: wgt::TextureFormat,
973    output: &NumericType,
974) -> Result<(), NumericType> {
975    let nt = NumericType::from_texture_format(format);
976    if nt.is_subtype_of(output) {
977        Ok(())
978    } else {
979        Err(nt)
980    }
981}
982
983pub enum BindingLayoutSource<'a> {
984    /// The binding layout is derived from the pipeline layout.
985    ///
986    /// This will be filled in by the shader binding validation, as it iterates the shader's interfaces.
987    Derived(Box<ArrayVec<bgl::EntryMap, { hal::MAX_BIND_GROUPS }>>),
988    /// The binding layout is provided by the user in BGLs.
989    ///
990    /// This will be validated against the shader's interfaces.
991    Provided(ArrayVec<&'a bgl::EntryMap, { hal::MAX_BIND_GROUPS }>),
992}
993
994impl<'a> BindingLayoutSource<'a> {
995    pub fn new_derived(limits: &wgt::Limits) -> Self {
996        let mut array = ArrayVec::new();
997        for _ in 0..limits.max_bind_groups {
998            array.push(Default::default());
999        }
1000        BindingLayoutSource::Derived(Box::new(array))
1001    }
1002}
1003
1004#[derive(Debug, Clone, Default)]
1005pub struct StageIo {
1006    pub varyings: FastHashMap<wgt::ShaderLocation, InterfaceVar>,
1007    /// This must match between mesh & task shaders
1008    pub task_payload_size: Option<u32>,
1009    /// Fragment shaders cannot input primitive index on mesh shaders that don't output it on DX12.
1010    /// Therefore, we track between shader stages if primitive index is written (or if vertex shader
1011    /// is used).
1012    ///
1013    /// This is Some if it was a mesh shader.
1014    pub primitive_index: Option<bool>,
1015}
1016
1017impl Interface {
1018    fn populate(
1019        list: &mut Vec<Varying>,
1020        binding: Option<&naga::Binding>,
1021        ty: naga::Handle<naga::Type>,
1022        arena: &naga::UniqueArena<naga::Type>,
1023    ) {
1024        let numeric_ty = match arena[ty].inner {
1025            naga::TypeInner::Scalar(scalar) => NumericType {
1026                dim: NumericDimension::Scalar,
1027                scalar,
1028            },
1029            naga::TypeInner::Vector { size, scalar } => NumericType {
1030                dim: NumericDimension::Vector(size),
1031                scalar,
1032            },
1033            naga::TypeInner::Matrix {
1034                columns,
1035                rows,
1036                scalar,
1037            } => NumericType {
1038                dim: NumericDimension::Matrix(columns, rows),
1039                scalar,
1040            },
1041            naga::TypeInner::Struct { ref members, .. } => {
1042                for member in members {
1043                    Self::populate(list, member.binding.as_ref(), member.ty, arena);
1044                }
1045                return;
1046            }
1047            ref other => {
1048                //Note: technically this should be at least `log::error`, but
1049                // the reality is - every shader coming from `glslc` outputs an array
1050                // of clip distances and hits this path :(
1051                // So we lower it to `log::debug` to be less annoying as
1052                // there's nothing the user can do about it.
1053                log::debug!("Unexpected varying type: {other:?}");
1054                return;
1055            }
1056        };
1057
1058        let varying = match binding {
1059            Some(&naga::Binding::Location {
1060                location,
1061                interpolation,
1062                sampling,
1063                per_primitive,
1064                blend_src: _,
1065            }) => Varying::Local {
1066                location,
1067                iv: InterfaceVar {
1068                    ty: numeric_ty,
1069                    interpolation,
1070                    sampling,
1071                    per_primitive,
1072                },
1073            },
1074            Some(&naga::Binding::BuiltIn(built_in)) => Varying::BuiltIn(built_in),
1075            None => {
1076                log::error!("Missing binding for a varying");
1077                return;
1078            }
1079        };
1080        list.push(varying);
1081    }
1082
1083    pub fn new(module: &naga::Module, info: &naga::valid::ModuleInfo, limits: wgt::Limits) -> Self {
1084        let mut resources = naga::Arena::new();
1085        let mut resource_mapping = FastHashMap::default();
1086        for (var_handle, var) in module.global_variables.iter() {
1087            let bind = match var.binding {
1088                Some(br) => br,
1089                _ => continue,
1090            };
1091            let naga_ty = &module.types[var.ty].inner;
1092
1093            let inner_ty = match *naga_ty {
1094                naga::TypeInner::BindingArray { base, .. } => &module.types[base].inner,
1095                ref ty => ty,
1096            };
1097
1098            let ty = match *inner_ty {
1099                naga::TypeInner::Image {
1100                    dim,
1101                    arrayed,
1102                    class,
1103                } => ResourceType::Texture {
1104                    dim,
1105                    arrayed,
1106                    class,
1107                },
1108                naga::TypeInner::Sampler { comparison } => ResourceType::Sampler { comparison },
1109                naga::TypeInner::AccelerationStructure { vertex_return } => {
1110                    ResourceType::AccelerationStructure { vertex_return }
1111                }
1112                ref other => ResourceType::Buffer {
1113                    size: wgt::BufferSize::new(other.size(module.to_ctx()) as u64).unwrap(),
1114                },
1115            };
1116            let handle = resources.append(
1117                Resource {
1118                    name: var.name.clone(),
1119                    bind,
1120                    ty,
1121                    class: var.space,
1122                },
1123                Default::default(),
1124            );
1125            resource_mapping.insert(var_handle, handle);
1126        }
1127
1128        let mut entry_points = FastHashMap::default();
1129        entry_points.reserve(module.entry_points.len());
1130        for (index, entry_point) in module.entry_points.iter().enumerate() {
1131            let info = info.get_entry_point(index);
1132            let mut ep = EntryPoint::default();
1133            for arg in entry_point.function.arguments.iter() {
1134                Self::populate(&mut ep.inputs, arg.binding.as_ref(), arg.ty, &module.types);
1135            }
1136            if let Some(ref result) = entry_point.function.result {
1137                Self::populate(
1138                    &mut ep.outputs,
1139                    result.binding.as_ref(),
1140                    result.ty,
1141                    &module.types,
1142                );
1143            }
1144
1145            for (var_handle, var) in module.global_variables.iter() {
1146                let usage = info[var_handle];
1147                if !usage.is_empty() && var.binding.is_some() {
1148                    ep.resources.push(resource_mapping[&var_handle]);
1149                }
1150            }
1151
1152            for key in info.sampling_set.iter() {
1153                ep.sampling_pairs
1154                    .insert((resource_mapping[&key.image], resource_mapping[&key.sampler]));
1155            }
1156            ep.dual_source_blending = info.dual_source_blending;
1157            ep.workgroup_size = entry_point.workgroup_size;
1158
1159            if let Some(task_payload) = entry_point.task_payload {
1160                ep.task_payload_size = Some(
1161                    module.types[module.global_variables[task_payload].ty]
1162                        .inner
1163                        .size(module.to_ctx()),
1164                );
1165            }
1166            if let Some(ref mesh_info) = entry_point.mesh_info {
1167                ep.mesh_info = Some(EntryPointMeshInfo {
1168                    max_vertices: mesh_info.max_vertices,
1169                    max_primitives: mesh_info.max_primitives,
1170                });
1171                Self::populate(
1172                    &mut ep.outputs,
1173                    None,
1174                    mesh_info.vertex_output_type,
1175                    &module.types,
1176                );
1177                Self::populate(
1178                    &mut ep.outputs,
1179                    None,
1180                    mesh_info.primitive_output_type,
1181                    &module.types,
1182                );
1183            }
1184
1185            entry_points.insert((entry_point.stage, entry_point.name.clone()), ep);
1186        }
1187
1188        Self {
1189            limits,
1190            resources,
1191            entry_points,
1192        }
1193    }
1194
1195    pub fn finalize_entry_point_name(
1196        &self,
1197        stage: naga::ShaderStage,
1198        entry_point_name: Option<&str>,
1199    ) -> Result<String, StageError> {
1200        entry_point_name
1201            .map(|ep| ep.to_string())
1202            .map(Ok)
1203            .unwrap_or_else(|| {
1204                let mut entry_points = self
1205                    .entry_points
1206                    .keys()
1207                    .filter_map(|(ep_stage, name)| (ep_stage == &stage).then_some(name));
1208                let first = entry_points.next().ok_or(StageError::NoEntryPointFound)?;
1209                if entry_points.next().is_some() {
1210                    return Err(StageError::MultipleEntryPointsFound);
1211                }
1212                Ok(first.clone())
1213            })
1214    }
1215
1216    /// Among other things, this implements some validation logic defined by the WebGPU spec. at
1217    /// <https://www.w3.org/TR/webgpu/#abstract-opdef-validating-inter-stage-interfaces>.
1218    pub fn check_stage(
1219        &self,
1220        layouts: &mut BindingLayoutSource<'_>,
1221        shader_binding_sizes: &mut FastHashMap<naga::ResourceBinding, wgt::BufferSize>,
1222        entry_point_name: &str,
1223        shader_stage: ShaderStageForValidation,
1224        inputs: StageIo,
1225    ) -> Result<StageIo, StageError> {
1226        // Since a shader module can have multiple entry points with the same name,
1227        // we need to look for one with the right execution model.
1228        let pair = (shader_stage.to_naga(), entry_point_name.to_string());
1229        let entry_point = match self.entry_points.get(&pair) {
1230            Some(some) => some,
1231            None => return Err(StageError::MissingEntryPoint(pair.1)),
1232        };
1233        let (_, entry_point_name) = pair;
1234
1235        let stage_bit = shader_stage.to_wgt_bit();
1236
1237        // check resources visibility
1238        for &handle in entry_point.resources.iter() {
1239            let res = &self.resources[handle];
1240            let result = 'err: {
1241                match layouts {
1242                    BindingLayoutSource::Provided(layouts) => {
1243                        // update the required binding size for this buffer
1244                        if let ResourceType::Buffer { size } = res.ty {
1245                            match shader_binding_sizes.entry(res.bind) {
1246                                Entry::Occupied(e) => {
1247                                    *e.into_mut() = size.max(*e.get());
1248                                }
1249                                Entry::Vacant(e) => {
1250                                    e.insert(size);
1251                                }
1252                            }
1253                        }
1254
1255                        let Some(map) = layouts.get(res.bind.group as usize) else {
1256                            break 'err Err(BindingError::Missing);
1257                        };
1258
1259                        let Some(entry) = map.get(res.bind.binding) else {
1260                            break 'err Err(BindingError::Missing);
1261                        };
1262
1263                        if !entry.visibility.contains(stage_bit) {
1264                            break 'err Err(BindingError::Invisible);
1265                        }
1266
1267                        res.check_binding_use(entry)
1268                    }
1269                    BindingLayoutSource::Derived(layouts) => {
1270                        let Some(map) = layouts.get_mut(res.bind.group as usize) else {
1271                            break 'err Err(BindingError::Missing);
1272                        };
1273
1274                        let ty = match res.derive_binding_type(
1275                            entry_point
1276                                .sampling_pairs
1277                                .iter()
1278                                .any(|&(im, _samp)| im == handle),
1279                        ) {
1280                            Ok(ty) => ty,
1281                            Err(error) => break 'err Err(error),
1282                        };
1283
1284                        match map.entry(res.bind.binding) {
1285                            indexmap::map::Entry::Occupied(e) if e.get().ty != ty => {
1286                                break 'err Err(BindingError::InconsistentlyDerivedType)
1287                            }
1288                            indexmap::map::Entry::Occupied(e) => {
1289                                e.into_mut().visibility |= stage_bit;
1290                            }
1291                            indexmap::map::Entry::Vacant(e) => {
1292                                e.insert(BindGroupLayoutEntry {
1293                                    binding: res.bind.binding,
1294                                    ty,
1295                                    visibility: stage_bit,
1296                                    count: None,
1297                                });
1298                            }
1299                        }
1300                        Ok(())
1301                    }
1302                }
1303            };
1304            if let Err(error) = result {
1305                return Err(StageError::Binding(res.bind, error));
1306            }
1307        }
1308
1309        // Check the compatibility between textures and samplers
1310        //
1311        // We only need to do this if the binding layout is provided by the user, as derived
1312        // layouts will inherently be correctly tagged.
1313        if let BindingLayoutSource::Provided(layouts) = layouts {
1314            for &(texture_handle, sampler_handle) in entry_point.sampling_pairs.iter() {
1315                let texture_bind = &self.resources[texture_handle].bind;
1316                let sampler_bind = &self.resources[sampler_handle].bind;
1317                let texture_layout = layouts[texture_bind.group as usize]
1318                    .get(texture_bind.binding)
1319                    .unwrap();
1320                let sampler_layout = layouts[sampler_bind.group as usize]
1321                    .get(sampler_bind.binding)
1322                    .unwrap();
1323                assert!(texture_layout.visibility.contains(stage_bit));
1324                assert!(sampler_layout.visibility.contains(stage_bit));
1325
1326                let sampler_filtering = matches!(
1327                    sampler_layout.ty,
1328                    BindingType::Sampler(wgt::SamplerBindingType::Filtering)
1329                );
1330                let texture_sample_type = match texture_layout.ty {
1331                    BindingType::Texture { sample_type, .. } => sample_type,
1332                    BindingType::ExternalTexture => {
1333                        wgt::TextureSampleType::Float { filterable: true }
1334                    }
1335                    _ => unreachable!(),
1336                };
1337
1338                let error = match (sampler_filtering, texture_sample_type) {
1339                    (true, wgt::TextureSampleType::Float { filterable: false }) => {
1340                        Some(FilteringError::Float)
1341                    }
1342                    (true, wgt::TextureSampleType::Sint) => Some(FilteringError::Integer),
1343                    (true, wgt::TextureSampleType::Uint) => Some(FilteringError::Integer),
1344                    _ => None,
1345                };
1346
1347                if let Some(error) = error {
1348                    return Err(StageError::Filtering {
1349                        texture: *texture_bind,
1350                        sampler: *sampler_bind,
1351                        error,
1352                    });
1353                }
1354            }
1355        }
1356
1357        // check workgroup size limits
1358        if shader_stage.to_naga().compute_like() {
1359            let (
1360                max_workgroup_size_limits,
1361                max_workgroup_size_total,
1362                per_dimension_limit,
1363                total_limit,
1364            ) = match shader_stage.to_naga() {
1365                naga::ShaderStage::Compute => (
1366                    [
1367                        self.limits.max_compute_workgroup_size_x,
1368                        self.limits.max_compute_workgroup_size_y,
1369                        self.limits.max_compute_workgroup_size_z,
1370                    ],
1371                    self.limits.max_compute_invocations_per_workgroup,
1372                    "max_compute_workgroup_size_*",
1373                    "max_compute_invocations_per_workgroup",
1374                ),
1375                naga::ShaderStage::Task => (
1376                    [
1377                        self.limits.max_task_invocations_per_dimension,
1378                        self.limits.max_task_invocations_per_dimension,
1379                        self.limits.max_task_invocations_per_dimension,
1380                    ],
1381                    self.limits.max_task_invocations_per_workgroup,
1382                    "max_task_invocations_per_dimension",
1383                    "max_task_invocations_per_workgroup",
1384                ),
1385                naga::ShaderStage::Mesh => (
1386                    [
1387                        self.limits.max_mesh_invocations_per_dimension,
1388                        self.limits.max_mesh_invocations_per_dimension,
1389                        self.limits.max_mesh_invocations_per_dimension,
1390                    ],
1391                    self.limits.max_mesh_invocations_per_workgroup,
1392                    "max_mesh_invocations_per_dimension",
1393                    "max_mesh_invocations_per_workgroup",
1394                ),
1395                _ => unreachable!(),
1396            };
1397            let total_invocations = entry_point.workgroup_size.iter().product::<u32>();
1398
1399            let workgroup_size_is_zero = entry_point.workgroup_size.contains(&0);
1400            let too_many_invocations = total_invocations > max_workgroup_size_total;
1401            let dimension_too_large = entry_point.workgroup_size[0] > max_workgroup_size_limits[0]
1402                || entry_point.workgroup_size[1] > max_workgroup_size_limits[1]
1403                || entry_point.workgroup_size[2] > max_workgroup_size_limits[2];
1404            if workgroup_size_is_zero || too_many_invocations || dimension_too_large {
1405                return Err(StageError::InvalidWorkgroupSize {
1406                    current: entry_point.workgroup_size,
1407                    current_total: total_invocations,
1408                    limit: max_workgroup_size_limits,
1409                    total: max_workgroup_size_total,
1410                    per_dimension_limit,
1411                    total_limit,
1412                });
1413            }
1414        }
1415
1416        let mut this_stage_primitive_index = false;
1417        let mut has_draw_id = false;
1418
1419        // check inputs compatibility
1420        for input in entry_point.inputs.iter() {
1421            match *input {
1422                Varying::Local { location, ref iv } => {
1423                    let result = inputs
1424                        .varyings
1425                        .get(&location)
1426                        .ok_or(InputError::Missing)
1427                        .and_then(|provided| {
1428                            let (compatible, per_primitive_correct) = match shader_stage.to_naga() {
1429                                // For vertex attributes, there are defaults filled out
1430                                // by the driver if data is not provided.
1431                                naga::ShaderStage::Vertex => {
1432                                    let is_compatible =
1433                                        iv.ty.scalar.kind == provided.ty.scalar.kind;
1434                                    // vertex inputs don't count towards inter-stage
1435                                    (is_compatible, !iv.per_primitive)
1436                                }
1437                                naga::ShaderStage::Fragment => {
1438                                    if iv.interpolation != provided.interpolation {
1439                                        return Err(InputError::InterpolationMismatch(
1440                                            provided.interpolation,
1441                                        ));
1442                                    }
1443                                    if iv.sampling != provided.sampling {
1444                                        return Err(InputError::SamplingMismatch(
1445                                            provided.sampling,
1446                                        ));
1447                                    }
1448                                    (
1449                                        iv.ty.is_subtype_of(&provided.ty),
1450                                        iv.per_primitive == provided.per_primitive,
1451                                    )
1452                                }
1453                                // These can't have varying inputs
1454                                naga::ShaderStage::Compute
1455                                | naga::ShaderStage::Task
1456                                | naga::ShaderStage::Mesh => (false, false),
1457                            };
1458                            if !compatible {
1459                                return Err(InputError::WrongType(provided.ty));
1460                            } else if !per_primitive_correct {
1461                                return Err(InputError::WrongPerPrimitive {
1462                                    pipeline_input: provided.per_primitive,
1463                                    shader: iv.per_primitive,
1464                                });
1465                            }
1466                            Ok(())
1467                        });
1468
1469                    if let Err(error) = result {
1470                        return Err(StageError::Input {
1471                            location,
1472                            var: iv.clone(),
1473                            error,
1474                        });
1475                    }
1476                }
1477                Varying::BuiltIn(naga::BuiltIn::PrimitiveIndex) => {
1478                    this_stage_primitive_index = true;
1479                }
1480                Varying::BuiltIn(naga::BuiltIn::DrawID) => {
1481                    has_draw_id = true;
1482                }
1483                Varying::BuiltIn(_) => {}
1484            }
1485        }
1486
1487        match shader_stage {
1488            ShaderStageForValidation::Vertex {
1489                topology,
1490                compare_function,
1491            } => {
1492                let mut max_vertex_shader_output_variables =
1493                    self.limits.max_inter_stage_shader_variables;
1494                let mut max_vertex_shader_output_location = max_vertex_shader_output_variables - 1;
1495
1496                let point_list_deduction = if topology == wgt::PrimitiveTopology::PointList {
1497                    Some(MaxVertexShaderOutputDeduction::PointListPrimitiveTopology)
1498                } else {
1499                    None
1500                };
1501
1502                let deductions = point_list_deduction.into_iter();
1503
1504                for deduction in deductions.clone() {
1505                    // NOTE: Deductions, in the current version of the spec. we implement, do not
1506                    // ever exceed the minimum variables available.
1507                    max_vertex_shader_output_variables = max_vertex_shader_output_variables
1508                        .checked_sub(deduction.for_variables())
1509                        .unwrap();
1510                    max_vertex_shader_output_location = max_vertex_shader_output_location
1511                        .checked_sub(deduction.for_location())
1512                        .unwrap();
1513                }
1514
1515                let mut num_user_defined_outputs = 0;
1516
1517                for output in entry_point.outputs.iter() {
1518                    match *output {
1519                        Varying::Local { ref iv, location } => {
1520                            if location > max_vertex_shader_output_location {
1521                                return Err(StageError::VertexOutputLocationTooLarge {
1522                                    location,
1523                                    var: iv.clone(),
1524                                    limit: self.limits.max_inter_stage_shader_variables,
1525                                    deductions: deductions.collect(),
1526                                });
1527                            }
1528                            num_user_defined_outputs += 1;
1529                        }
1530                        Varying::BuiltIn(_) => {}
1531                    };
1532
1533                    if let Some(
1534                        cmp @ wgt::CompareFunction::Equal | cmp @ wgt::CompareFunction::NotEqual,
1535                    ) = compare_function
1536                    {
1537                        if let Varying::BuiltIn(naga::BuiltIn::Position { invariant: false }) =
1538                            *output
1539                        {
1540                            log::warn!(
1541                                concat!(
1542                                    "Vertex shader with entry point {} outputs a ",
1543                                    "@builtin(position) without the @invariant attribute and ",
1544                                    "is used in a pipeline with {cmp:?}. On some machines, ",
1545                                    "this can cause bad artifacting as {cmp:?} assumes the ",
1546                                    "values output from the vertex shader exactly match the ",
1547                                    "value in the depth buffer. The @invariant attribute on the ",
1548                                    "@builtin(position) vertex output ensures that the exact ",
1549                                    "same pixel depths are used every render."
1550                                ),
1551                                entry_point_name,
1552                                cmp = cmp
1553                            );
1554                        }
1555                    }
1556                }
1557
1558                if num_user_defined_outputs > max_vertex_shader_output_variables {
1559                    return Err(StageError::TooManyUserDefinedVertexOutputs {
1560                        num_found: num_user_defined_outputs,
1561                        limit: self.limits.max_inter_stage_shader_variables,
1562                        deductions: deductions.collect(),
1563                    });
1564                }
1565            }
1566            ShaderStageForValidation::Fragment => {
1567                let mut max_fragment_shader_input_variables =
1568                    self.limits.max_inter_stage_shader_variables;
1569
1570                let deductions = entry_point.inputs.iter().filter_map(|output| match output {
1571                    Varying::Local { .. } => None,
1572                    Varying::BuiltIn(builtin) => {
1573                        MaxFragmentShaderInputDeduction::from_inter_stage_builtin(*builtin).or_else(
1574                            || {
1575                                unreachable!(
1576                                    concat!(
1577                                        "unexpected built-in provided; ",
1578                                        "{:?} is not used for fragment stage input",
1579                                    ),
1580                                    builtin
1581                                )
1582                            },
1583                        )
1584                    }
1585                });
1586
1587                for deduction in deductions.clone() {
1588                    // NOTE: Deductions, in the current version of the spec. we implement, do not
1589                    // ever exceed the minimum variables available.
1590                    max_fragment_shader_input_variables = max_fragment_shader_input_variables
1591                        .checked_sub(deduction.for_variables())
1592                        .unwrap();
1593                }
1594
1595                let mut num_user_defined_inputs = 0;
1596
1597                for output in entry_point.inputs.iter() {
1598                    match *output {
1599                        Varying::Local { ref iv, location } => {
1600                            if location >= self.limits.max_inter_stage_shader_variables {
1601                                return Err(StageError::FragmentInputLocationTooLarge {
1602                                    location,
1603                                    var: iv.clone(),
1604                                    limit: self.limits.max_inter_stage_shader_variables,
1605                                    deductions: deductions.collect(),
1606                                });
1607                            }
1608                            num_user_defined_inputs += 1;
1609                        }
1610                        Varying::BuiltIn(_) => {}
1611                    };
1612                }
1613
1614                if num_user_defined_inputs > max_fragment_shader_input_variables {
1615                    return Err(StageError::TooManyUserDefinedFragmentInputs {
1616                        num_found: num_user_defined_inputs,
1617                        limit: self.limits.max_inter_stage_shader_variables,
1618                        deductions: deductions.collect(),
1619                    });
1620                }
1621
1622                for output in &entry_point.outputs {
1623                    let &Varying::Local { location, ref iv } = output else {
1624                        continue;
1625                    };
1626                    if location >= self.limits.max_color_attachments {
1627                        return Err(StageError::ColorAttachmentLocationTooLarge {
1628                            location,
1629                            var: iv.clone(),
1630                            limit: self.limits.max_color_attachments,
1631                        });
1632                    }
1633                }
1634            }
1635            _ => (),
1636        }
1637
1638        if let Some(ref mesh_info) = entry_point.mesh_info {
1639            if mesh_info.max_vertices > self.limits.max_mesh_output_vertices {
1640                return Err(StageError::TooManyMeshVertices {
1641                    limit: self.limits.max_mesh_output_vertices,
1642                    value: mesh_info.max_vertices,
1643                });
1644            }
1645            if mesh_info.max_primitives > self.limits.max_mesh_output_primitives {
1646                return Err(StageError::TooManyMeshPrimitives {
1647                    limit: self.limits.max_mesh_output_primitives,
1648                    value: mesh_info.max_primitives,
1649                });
1650            }
1651        }
1652        if let Some(task_payload_size) = entry_point.task_payload_size {
1653            if task_payload_size > self.limits.max_task_payload_size {
1654                return Err(StageError::TaskPayloadTooLarge {
1655                    limit: self.limits.max_task_payload_size,
1656                    value: task_payload_size,
1657                });
1658            }
1659        }
1660        if shader_stage.to_naga() == naga::ShaderStage::Mesh
1661            && entry_point.task_payload_size != inputs.task_payload_size
1662        {
1663            return Err(StageError::TaskPayloadMustMatch {
1664                input: inputs.task_payload_size,
1665                shader: entry_point.task_payload_size,
1666            });
1667        }
1668
1669        // Fragment shader primitive index is treated like a varying
1670        if shader_stage.to_naga() == naga::ShaderStage::Fragment
1671            && this_stage_primitive_index
1672            && inputs.primitive_index == Some(false)
1673        {
1674            return Err(StageError::InvalidPrimitiveIndex);
1675        } else if shader_stage.to_naga() == naga::ShaderStage::Fragment
1676            && !this_stage_primitive_index
1677            && inputs.primitive_index == Some(true)
1678        {
1679            return Err(StageError::MissingPrimitiveIndex);
1680        }
1681        if shader_stage.to_naga() == naga::ShaderStage::Mesh
1682            && inputs.task_payload_size.is_some()
1683            && has_draw_id
1684        {
1685            return Err(StageError::DrawIdError);
1686        }
1687
1688        let outputs = entry_point
1689            .outputs
1690            .iter()
1691            .filter_map(|output| match *output {
1692                Varying::Local { location, ref iv } => Some((location, iv.clone())),
1693                Varying::BuiltIn(_) => None,
1694            })
1695            .collect();
1696
1697        Ok(StageIo {
1698            task_payload_size: entry_point.task_payload_size,
1699            varyings: outputs,
1700            primitive_index: if shader_stage.to_naga() == naga::ShaderStage::Mesh {
1701                Some(this_stage_primitive_index)
1702            } else {
1703                None
1704            },
1705        })
1706    }
1707
1708    pub fn fragment_uses_dual_source_blending(
1709        &self,
1710        entry_point_name: &str,
1711    ) -> Result<bool, StageError> {
1712        let pair = (naga::ShaderStage::Fragment, entry_point_name.to_string());
1713        self.entry_points
1714            .get(&pair)
1715            .ok_or(StageError::MissingEntryPoint(pair.1))
1716            .map(|ep| ep.dual_source_blending)
1717    }
1718}
1719
1720/// Validate a list of color attachment formats against `maxColorAttachmentBytesPerSample`.
1721///
1722/// The color attachments can be from a render pass descriptor or a pipeline descriptor.
1723///
1724/// Implements <https://gpuweb.github.io/gpuweb/#abstract-opdef-calculating-color-attachment-bytes-per-sample>.
1725pub fn validate_color_attachment_bytes_per_sample(
1726    attachment_formats: impl IntoIterator<Item = wgt::TextureFormat>,
1727    limit: u32,
1728) -> Result<(), crate::command::ColorAttachmentError> {
1729    let mut total_bytes_per_sample: u32 = 0;
1730    for format in attachment_formats {
1731        let byte_cost = format.target_pixel_byte_cost().unwrap();
1732        let alignment = format.target_component_alignment().unwrap();
1733
1734        total_bytes_per_sample = total_bytes_per_sample.next_multiple_of(alignment);
1735        total_bytes_per_sample += byte_cost;
1736    }
1737
1738    if total_bytes_per_sample > limit {
1739        return Err(
1740            crate::command::ColorAttachmentError::TooManyBytesPerSample {
1741                total: total_bytes_per_sample,
1742                limit,
1743            },
1744        );
1745    }
1746
1747    Ok(())
1748}
1749
1750pub enum ShaderStageForValidation {
1751    Vertex {
1752        topology: wgt::PrimitiveTopology,
1753        compare_function: Option<wgt::CompareFunction>,
1754    },
1755    Mesh,
1756    Fragment,
1757    Compute,
1758    Task,
1759}
1760
1761impl ShaderStageForValidation {
1762    pub fn to_naga(&self) -> naga::ShaderStage {
1763        match self {
1764            Self::Vertex { .. } => naga::ShaderStage::Vertex,
1765            Self::Mesh => naga::ShaderStage::Mesh,
1766            Self::Fragment => naga::ShaderStage::Fragment,
1767            Self::Compute => naga::ShaderStage::Compute,
1768            Self::Task => naga::ShaderStage::Task,
1769        }
1770    }
1771
1772    pub fn to_wgt_bit(&self) -> wgt::ShaderStages {
1773        match self {
1774            Self::Vertex { .. } => wgt::ShaderStages::VERTEX,
1775            Self::Mesh { .. } => wgt::ShaderStages::MESH,
1776            Self::Fragment { .. } => wgt::ShaderStages::FRAGMENT,
1777            Self::Compute => wgt::ShaderStages::COMPUTE,
1778            Self::Task => wgt::ShaderStages::TASK,
1779        }
1780    }
1781}