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