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