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 #[error(
317 "Location[{location}] {var}'s index exceeds the `max_color_attachments` limit ({limit})"
318 )]
319 ColorAttachmentLocationTooLarge {
320 location: u32,
321 var: InterfaceVar,
322 limit: u32,
323 },
324}
325
326impl WebGpuError for StageError {
327 fn webgpu_error_type(&self) -> ErrorType {
328 let e: &dyn WebGpuError = match self {
329 Self::Binding(_, e) => e,
330 Self::InvalidResource(e) => e,
331 Self::Filtering {
332 texture: _,
333 sampler: _,
334 error,
335 } => error,
336 Self::Input {
337 location: _,
338 var: _,
339 error,
340 } => error,
341 Self::InvalidWorkgroupSize { .. }
342 | Self::TooManyVaryings { .. }
343 | Self::MissingEntryPoint(..)
344 | Self::NoEntryPointFound
345 | Self::MultipleEntryPointsFound
346 | Self::ColorAttachmentLocationTooLarge { .. } => return ErrorType::Validation,
347 };
348 e.webgpu_error_type()
349 }
350}
351
352pub fn map_storage_format_to_naga(format: wgt::TextureFormat) -> Option<naga::StorageFormat> {
353 use naga::StorageFormat as Sf;
354 use wgt::TextureFormat as Tf;
355
356 Some(match format {
357 Tf::R8Unorm => Sf::R8Unorm,
358 Tf::R8Snorm => Sf::R8Snorm,
359 Tf::R8Uint => Sf::R8Uint,
360 Tf::R8Sint => Sf::R8Sint,
361
362 Tf::R16Uint => Sf::R16Uint,
363 Tf::R16Sint => Sf::R16Sint,
364 Tf::R16Float => Sf::R16Float,
365 Tf::Rg8Unorm => Sf::Rg8Unorm,
366 Tf::Rg8Snorm => Sf::Rg8Snorm,
367 Tf::Rg8Uint => Sf::Rg8Uint,
368 Tf::Rg8Sint => Sf::Rg8Sint,
369
370 Tf::R32Uint => Sf::R32Uint,
371 Tf::R32Sint => Sf::R32Sint,
372 Tf::R32Float => Sf::R32Float,
373 Tf::Rg16Uint => Sf::Rg16Uint,
374 Tf::Rg16Sint => Sf::Rg16Sint,
375 Tf::Rg16Float => Sf::Rg16Float,
376 Tf::Rgba8Unorm => Sf::Rgba8Unorm,
377 Tf::Rgba8Snorm => Sf::Rgba8Snorm,
378 Tf::Rgba8Uint => Sf::Rgba8Uint,
379 Tf::Rgba8Sint => Sf::Rgba8Sint,
380 Tf::Bgra8Unorm => Sf::Bgra8Unorm,
381
382 Tf::Rgb10a2Uint => Sf::Rgb10a2Uint,
383 Tf::Rgb10a2Unorm => Sf::Rgb10a2Unorm,
384 Tf::Rg11b10Ufloat => Sf::Rg11b10Ufloat,
385
386 Tf::R64Uint => Sf::R64Uint,
387 Tf::Rg32Uint => Sf::Rg32Uint,
388 Tf::Rg32Sint => Sf::Rg32Sint,
389 Tf::Rg32Float => Sf::Rg32Float,
390 Tf::Rgba16Uint => Sf::Rgba16Uint,
391 Tf::Rgba16Sint => Sf::Rgba16Sint,
392 Tf::Rgba16Float => Sf::Rgba16Float,
393
394 Tf::Rgba32Uint => Sf::Rgba32Uint,
395 Tf::Rgba32Sint => Sf::Rgba32Sint,
396 Tf::Rgba32Float => Sf::Rgba32Float,
397
398 Tf::R16Unorm => Sf::R16Unorm,
399 Tf::R16Snorm => Sf::R16Snorm,
400 Tf::Rg16Unorm => Sf::Rg16Unorm,
401 Tf::Rg16Snorm => Sf::Rg16Snorm,
402 Tf::Rgba16Unorm => Sf::Rgba16Unorm,
403 Tf::Rgba16Snorm => Sf::Rgba16Snorm,
404
405 _ => return None,
406 })
407}
408
409pub fn map_storage_format_from_naga(format: naga::StorageFormat) -> wgt::TextureFormat {
410 use naga::StorageFormat as Sf;
411 use wgt::TextureFormat as Tf;
412
413 match format {
414 Sf::R8Unorm => Tf::R8Unorm,
415 Sf::R8Snorm => Tf::R8Snorm,
416 Sf::R8Uint => Tf::R8Uint,
417 Sf::R8Sint => Tf::R8Sint,
418
419 Sf::R16Uint => Tf::R16Uint,
420 Sf::R16Sint => Tf::R16Sint,
421 Sf::R16Float => Tf::R16Float,
422 Sf::Rg8Unorm => Tf::Rg8Unorm,
423 Sf::Rg8Snorm => Tf::Rg8Snorm,
424 Sf::Rg8Uint => Tf::Rg8Uint,
425 Sf::Rg8Sint => Tf::Rg8Sint,
426
427 Sf::R32Uint => Tf::R32Uint,
428 Sf::R32Sint => Tf::R32Sint,
429 Sf::R32Float => Tf::R32Float,
430 Sf::Rg16Uint => Tf::Rg16Uint,
431 Sf::Rg16Sint => Tf::Rg16Sint,
432 Sf::Rg16Float => Tf::Rg16Float,
433 Sf::Rgba8Unorm => Tf::Rgba8Unorm,
434 Sf::Rgba8Snorm => Tf::Rgba8Snorm,
435 Sf::Rgba8Uint => Tf::Rgba8Uint,
436 Sf::Rgba8Sint => Tf::Rgba8Sint,
437 Sf::Bgra8Unorm => Tf::Bgra8Unorm,
438
439 Sf::Rgb10a2Uint => Tf::Rgb10a2Uint,
440 Sf::Rgb10a2Unorm => Tf::Rgb10a2Unorm,
441 Sf::Rg11b10Ufloat => Tf::Rg11b10Ufloat,
442
443 Sf::R64Uint => Tf::R64Uint,
444 Sf::Rg32Uint => Tf::Rg32Uint,
445 Sf::Rg32Sint => Tf::Rg32Sint,
446 Sf::Rg32Float => Tf::Rg32Float,
447 Sf::Rgba16Uint => Tf::Rgba16Uint,
448 Sf::Rgba16Sint => Tf::Rgba16Sint,
449 Sf::Rgba16Float => Tf::Rgba16Float,
450
451 Sf::Rgba32Uint => Tf::Rgba32Uint,
452 Sf::Rgba32Sint => Tf::Rgba32Sint,
453 Sf::Rgba32Float => Tf::Rgba32Float,
454
455 Sf::R16Unorm => Tf::R16Unorm,
456 Sf::R16Snorm => Tf::R16Snorm,
457 Sf::Rg16Unorm => Tf::Rg16Unorm,
458 Sf::Rg16Snorm => Tf::Rg16Snorm,
459 Sf::Rgba16Unorm => Tf::Rgba16Unorm,
460 Sf::Rgba16Snorm => Tf::Rgba16Snorm,
461 }
462}
463
464impl Resource {
465 fn check_binding_use(&self, entry: &BindGroupLayoutEntry) -> Result<(), BindingError> {
466 match self.ty {
467 ResourceType::Buffer { size } => {
468 let min_size = match entry.ty {
469 BindingType::Buffer {
470 ty,
471 has_dynamic_offset: _,
472 min_binding_size,
473 } => {
474 let class = match ty {
475 wgt::BufferBindingType::Uniform => naga::AddressSpace::Uniform,
476 wgt::BufferBindingType::Storage { read_only } => {
477 let mut naga_access = naga::StorageAccess::LOAD;
478 naga_access.set(naga::StorageAccess::STORE, !read_only);
479 naga::AddressSpace::Storage {
480 access: naga_access,
481 }
482 }
483 };
484 if self.class != class {
485 return Err(BindingError::WrongAddressSpace {
486 binding: class,
487 shader: self.class,
488 });
489 }
490 min_binding_size
491 }
492 _ => {
493 return Err(BindingError::WrongType {
494 binding: (&entry.ty).into(),
495 shader: (&self.ty).into(),
496 })
497 }
498 };
499 match min_size {
500 Some(non_zero) if non_zero < size => {
501 return Err(BindingError::WrongBufferSize {
502 buffer_size: size,
503 min_binding_size: non_zero,
504 })
505 }
506 _ => (),
507 }
508 }
509 ResourceType::Sampler { comparison } => match entry.ty {
510 BindingType::Sampler(ty) => {
511 if (ty == wgt::SamplerBindingType::Comparison) != comparison {
512 return Err(BindingError::WrongSamplerComparison);
513 }
514 }
515 _ => {
516 return Err(BindingError::WrongType {
517 binding: (&entry.ty).into(),
518 shader: (&self.ty).into(),
519 })
520 }
521 },
522 ResourceType::Texture {
523 dim,
524 arrayed,
525 class,
526 } => {
527 let view_dimension = match entry.ty {
528 BindingType::Texture { view_dimension, .. }
529 | BindingType::StorageTexture { view_dimension, .. } => view_dimension,
530 BindingType::ExternalTexture => wgt::TextureViewDimension::D2,
531 _ => {
532 return Err(BindingError::WrongTextureViewDimension {
533 dim,
534 is_array: false,
535 binding: entry.ty,
536 })
537 }
538 };
539 if arrayed {
540 match (dim, view_dimension) {
541 (naga::ImageDimension::D2, wgt::TextureViewDimension::D2Array) => (),
542 (naga::ImageDimension::Cube, wgt::TextureViewDimension::CubeArray) => (),
543 _ => {
544 return Err(BindingError::WrongTextureViewDimension {
545 dim,
546 is_array: true,
547 binding: entry.ty,
548 })
549 }
550 }
551 } else {
552 match (dim, view_dimension) {
553 (naga::ImageDimension::D1, wgt::TextureViewDimension::D1) => (),
554 (naga::ImageDimension::D2, wgt::TextureViewDimension::D2) => (),
555 (naga::ImageDimension::D3, wgt::TextureViewDimension::D3) => (),
556 (naga::ImageDimension::Cube, wgt::TextureViewDimension::Cube) => (),
557 _ => {
558 return Err(BindingError::WrongTextureViewDimension {
559 dim,
560 is_array: false,
561 binding: entry.ty,
562 })
563 }
564 }
565 }
566 let expected_class = match entry.ty {
567 BindingType::Texture {
568 sample_type,
569 view_dimension: _,
570 multisampled: multi,
571 } => match sample_type {
572 wgt::TextureSampleType::Float { .. } => naga::ImageClass::Sampled {
573 kind: naga::ScalarKind::Float,
574 multi,
575 },
576 wgt::TextureSampleType::Sint => naga::ImageClass::Sampled {
577 kind: naga::ScalarKind::Sint,
578 multi,
579 },
580 wgt::TextureSampleType::Uint => naga::ImageClass::Sampled {
581 kind: naga::ScalarKind::Uint,
582 multi,
583 },
584 wgt::TextureSampleType::Depth => naga::ImageClass::Depth { multi },
585 },
586 BindingType::StorageTexture {
587 access,
588 format,
589 view_dimension: _,
590 } => {
591 let naga_format = map_storage_format_to_naga(format)
592 .ok_or(BindingError::BadStorageFormat(format))?;
593 let naga_access = match access {
594 wgt::StorageTextureAccess::ReadOnly => naga::StorageAccess::LOAD,
595 wgt::StorageTextureAccess::WriteOnly => naga::StorageAccess::STORE,
596 wgt::StorageTextureAccess::ReadWrite => {
597 naga::StorageAccess::LOAD | naga::StorageAccess::STORE
598 }
599 wgt::StorageTextureAccess::Atomic => {
600 naga::StorageAccess::ATOMIC
601 | naga::StorageAccess::LOAD
602 | naga::StorageAccess::STORE
603 }
604 };
605 naga::ImageClass::Storage {
606 format: naga_format,
607 access: naga_access,
608 }
609 }
610 BindingType::ExternalTexture => naga::ImageClass::External,
611 _ => {
612 return Err(BindingError::WrongType {
613 binding: (&entry.ty).into(),
614 shader: (&self.ty).into(),
615 })
616 }
617 };
618 if class != expected_class {
619 return Err(BindingError::WrongTextureClass {
620 binding: expected_class,
621 shader: class,
622 });
623 }
624 }
625 ResourceType::AccelerationStructure { vertex_return } => match entry.ty {
626 BindingType::AccelerationStructure {
627 vertex_return: entry_vertex_return,
628 } if vertex_return == entry_vertex_return => (),
629 _ => {
630 return Err(BindingError::WrongType {
631 binding: (&entry.ty).into(),
632 shader: (&self.ty).into(),
633 })
634 }
635 },
636 };
637
638 Ok(())
639 }
640
641 fn derive_binding_type(
642 &self,
643 is_reffed_by_sampler_in_entrypoint: bool,
644 ) -> Result<BindingType, BindingError> {
645 Ok(match self.ty {
646 ResourceType::Buffer { size } => BindingType::Buffer {
647 ty: match self.class {
648 naga::AddressSpace::Uniform => wgt::BufferBindingType::Uniform,
649 naga::AddressSpace::Storage { access } => wgt::BufferBindingType::Storage {
650 read_only: access == naga::StorageAccess::LOAD,
651 },
652 _ => return Err(BindingError::WrongBufferAddressSpace { space: self.class }),
653 },
654 has_dynamic_offset: false,
655 min_binding_size: Some(size),
656 },
657 ResourceType::Sampler { comparison } => BindingType::Sampler(if comparison {
658 wgt::SamplerBindingType::Comparison
659 } else {
660 wgt::SamplerBindingType::Filtering
661 }),
662 ResourceType::Texture {
663 dim,
664 arrayed,
665 class,
666 } => {
667 let view_dimension = match dim {
668 naga::ImageDimension::D1 => wgt::TextureViewDimension::D1,
669 naga::ImageDimension::D2 if arrayed => wgt::TextureViewDimension::D2Array,
670 naga::ImageDimension::D2 => wgt::TextureViewDimension::D2,
671 naga::ImageDimension::D3 => wgt::TextureViewDimension::D3,
672 naga::ImageDimension::Cube if arrayed => wgt::TextureViewDimension::CubeArray,
673 naga::ImageDimension::Cube => wgt::TextureViewDimension::Cube,
674 };
675 match class {
676 naga::ImageClass::Sampled { multi, kind } => BindingType::Texture {
677 sample_type: match kind {
678 naga::ScalarKind::Float => wgt::TextureSampleType::Float {
679 filterable: is_reffed_by_sampler_in_entrypoint,
680 },
681 naga::ScalarKind::Sint => wgt::TextureSampleType::Sint,
682 naga::ScalarKind::Uint => wgt::TextureSampleType::Uint,
683 naga::ScalarKind::AbstractInt
684 | naga::ScalarKind::AbstractFloat
685 | naga::ScalarKind::Bool => unreachable!(),
686 },
687 view_dimension,
688 multisampled: multi,
689 },
690 naga::ImageClass::Depth { multi } => BindingType::Texture {
691 sample_type: wgt::TextureSampleType::Depth,
692 view_dimension,
693 multisampled: multi,
694 },
695 naga::ImageClass::Storage { format, access } => BindingType::StorageTexture {
696 access: {
697 const LOAD_STORE: naga::StorageAccess =
698 naga::StorageAccess::LOAD.union(naga::StorageAccess::STORE);
699 match access {
700 naga::StorageAccess::LOAD => wgt::StorageTextureAccess::ReadOnly,
701 naga::StorageAccess::STORE => wgt::StorageTextureAccess::WriteOnly,
702 LOAD_STORE => wgt::StorageTextureAccess::ReadWrite,
703 _ if access.contains(naga::StorageAccess::ATOMIC) => {
704 wgt::StorageTextureAccess::Atomic
705 }
706 _ => unreachable!(),
707 }
708 },
709 view_dimension,
710 format: {
711 let f = map_storage_format_from_naga(format);
712 let original = map_storage_format_to_naga(f)
713 .ok_or(BindingError::BadStorageFormat(f))?;
714 debug_assert_eq!(format, original);
715 f
716 },
717 },
718 naga::ImageClass::External => BindingType::ExternalTexture,
719 }
720 }
721 ResourceType::AccelerationStructure { vertex_return } => {
722 BindingType::AccelerationStructure { vertex_return }
723 }
724 })
725 }
726}
727
728impl NumericType {
729 fn from_vertex_format(format: wgt::VertexFormat) -> Self {
730 use naga::{Scalar, VectorSize as Vs};
731 use wgt::VertexFormat as Vf;
732
733 let (dim, scalar) = match format {
734 Vf::Uint8 | Vf::Uint16 | Vf::Uint32 => (NumericDimension::Scalar, Scalar::U32),
735 Vf::Uint8x2 | Vf::Uint16x2 | Vf::Uint32x2 => {
736 (NumericDimension::Vector(Vs::Bi), Scalar::U32)
737 }
738 Vf::Uint32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::U32),
739 Vf::Uint8x4 | Vf::Uint16x4 | Vf::Uint32x4 => {
740 (NumericDimension::Vector(Vs::Quad), Scalar::U32)
741 }
742 Vf::Sint8 | Vf::Sint16 | Vf::Sint32 => (NumericDimension::Scalar, Scalar::I32),
743 Vf::Sint8x2 | Vf::Sint16x2 | Vf::Sint32x2 => {
744 (NumericDimension::Vector(Vs::Bi), Scalar::I32)
745 }
746 Vf::Sint32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::I32),
747 Vf::Sint8x4 | Vf::Sint16x4 | Vf::Sint32x4 => {
748 (NumericDimension::Vector(Vs::Quad), Scalar::I32)
749 }
750 Vf::Unorm8 | Vf::Unorm16 | Vf::Snorm8 | Vf::Snorm16 | Vf::Float16 | Vf::Float32 => {
751 (NumericDimension::Scalar, Scalar::F32)
752 }
753 Vf::Unorm8x2
754 | Vf::Snorm8x2
755 | Vf::Unorm16x2
756 | Vf::Snorm16x2
757 | Vf::Float16x2
758 | Vf::Float32x2 => (NumericDimension::Vector(Vs::Bi), Scalar::F32),
759 Vf::Float32x3 => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
760 Vf::Unorm8x4
761 | Vf::Snorm8x4
762 | Vf::Unorm16x4
763 | Vf::Snorm16x4
764 | Vf::Float16x4
765 | Vf::Float32x4
766 | Vf::Unorm10_10_10_2
767 | Vf::Unorm8x4Bgra => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
768 Vf::Float64 => (NumericDimension::Scalar, Scalar::F64),
769 Vf::Float64x2 => (NumericDimension::Vector(Vs::Bi), Scalar::F64),
770 Vf::Float64x3 => (NumericDimension::Vector(Vs::Tri), Scalar::F64),
771 Vf::Float64x4 => (NumericDimension::Vector(Vs::Quad), Scalar::F64),
772 };
773
774 NumericType {
775 dim,
776 scalar,
779 }
780 }
781
782 fn from_texture_format(format: wgt::TextureFormat) -> Self {
783 use naga::{Scalar, VectorSize as Vs};
784 use wgt::TextureFormat as Tf;
785
786 let (dim, scalar) = match format {
787 Tf::R8Unorm | Tf::R8Snorm | Tf::R16Float | Tf::R32Float => {
788 (NumericDimension::Scalar, Scalar::F32)
789 }
790 Tf::R8Uint | Tf::R16Uint | Tf::R32Uint => (NumericDimension::Scalar, Scalar::U32),
791 Tf::R8Sint | Tf::R16Sint | Tf::R32Sint => (NumericDimension::Scalar, Scalar::I32),
792 Tf::Rg8Unorm | Tf::Rg8Snorm | Tf::Rg16Float | Tf::Rg32Float => {
793 (NumericDimension::Vector(Vs::Bi), Scalar::F32)
794 }
795 Tf::R64Uint => (NumericDimension::Scalar, Scalar::U64),
796 Tf::Rg8Uint | Tf::Rg16Uint | Tf::Rg32Uint => {
797 (NumericDimension::Vector(Vs::Bi), Scalar::U32)
798 }
799 Tf::Rg8Sint | Tf::Rg16Sint | Tf::Rg32Sint => {
800 (NumericDimension::Vector(Vs::Bi), Scalar::I32)
801 }
802 Tf::R16Snorm | Tf::R16Unorm => (NumericDimension::Scalar, Scalar::F32),
803 Tf::Rg16Snorm | Tf::Rg16Unorm => (NumericDimension::Vector(Vs::Bi), Scalar::F32),
804 Tf::Rgba16Snorm | Tf::Rgba16Unorm => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
805 Tf::Rgba8Unorm
806 | Tf::Rgba8UnormSrgb
807 | Tf::Rgba8Snorm
808 | Tf::Bgra8Unorm
809 | Tf::Bgra8UnormSrgb
810 | Tf::Rgb10a2Unorm
811 | Tf::Rgba16Float
812 | Tf::Rgba32Float => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
813 Tf::Rgba8Uint | Tf::Rgba16Uint | Tf::Rgba32Uint | Tf::Rgb10a2Uint => {
814 (NumericDimension::Vector(Vs::Quad), Scalar::U32)
815 }
816 Tf::Rgba8Sint | Tf::Rgba16Sint | Tf::Rgba32Sint => {
817 (NumericDimension::Vector(Vs::Quad), Scalar::I32)
818 }
819 Tf::Rg11b10Ufloat => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
820 Tf::Stencil8
821 | Tf::Depth16Unorm
822 | Tf::Depth32Float
823 | Tf::Depth32FloatStencil8
824 | Tf::Depth24Plus
825 | Tf::Depth24PlusStencil8 => {
826 panic!("Unexpected depth format")
827 }
828 Tf::NV12 => panic!("Unexpected nv12 format"),
829 Tf::P010 => panic!("Unexpected p010 format"),
830 Tf::Rgb9e5Ufloat => (NumericDimension::Vector(Vs::Tri), Scalar::F32),
831 Tf::Bc1RgbaUnorm
832 | Tf::Bc1RgbaUnormSrgb
833 | Tf::Bc2RgbaUnorm
834 | Tf::Bc2RgbaUnormSrgb
835 | Tf::Bc3RgbaUnorm
836 | Tf::Bc3RgbaUnormSrgb
837 | Tf::Bc7RgbaUnorm
838 | Tf::Bc7RgbaUnormSrgb
839 | Tf::Etc2Rgb8A1Unorm
840 | Tf::Etc2Rgb8A1UnormSrgb
841 | Tf::Etc2Rgba8Unorm
842 | Tf::Etc2Rgba8UnormSrgb => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
843 Tf::Bc4RUnorm | Tf::Bc4RSnorm | Tf::EacR11Unorm | Tf::EacR11Snorm => {
844 (NumericDimension::Scalar, Scalar::F32)
845 }
846 Tf::Bc5RgUnorm | Tf::Bc5RgSnorm | Tf::EacRg11Unorm | Tf::EacRg11Snorm => {
847 (NumericDimension::Vector(Vs::Bi), Scalar::F32)
848 }
849 Tf::Bc6hRgbUfloat | Tf::Bc6hRgbFloat | Tf::Etc2Rgb8Unorm | Tf::Etc2Rgb8UnormSrgb => {
850 (NumericDimension::Vector(Vs::Tri), Scalar::F32)
851 }
852 Tf::Astc {
853 block: _,
854 channel: _,
855 } => (NumericDimension::Vector(Vs::Quad), Scalar::F32),
856 };
857
858 NumericType {
859 dim,
860 scalar,
863 }
864 }
865
866 fn is_subtype_of(&self, other: &NumericType) -> bool {
867 if self.scalar.width > other.scalar.width {
868 return false;
869 }
870 if self.scalar.kind != other.scalar.kind {
871 return false;
872 }
873 match (self.dim, other.dim) {
874 (NumericDimension::Scalar, NumericDimension::Scalar) => true,
875 (NumericDimension::Scalar, NumericDimension::Vector(_)) => true,
876 (NumericDimension::Vector(s0), NumericDimension::Vector(s1)) => s0 <= s1,
877 (NumericDimension::Matrix(c0, r0), NumericDimension::Matrix(c1, r1)) => {
878 c0 == c1 && r0 == r1
879 }
880 _ => false,
881 }
882 }
883}
884
885pub fn check_texture_format(
887 format: wgt::TextureFormat,
888 output: &NumericType,
889) -> Result<(), NumericType> {
890 let nt = NumericType::from_texture_format(format);
891 if nt.is_subtype_of(output) {
892 Ok(())
893 } else {
894 Err(nt)
895 }
896}
897
898pub enum BindingLayoutSource<'a> {
899 Derived(Box<ArrayVec<bgl::EntryMap, { hal::MAX_BIND_GROUPS }>>),
903 Provided(ArrayVec<&'a bgl::EntryMap, { hal::MAX_BIND_GROUPS }>),
907}
908
909impl<'a> BindingLayoutSource<'a> {
910 pub fn new_derived(limits: &wgt::Limits) -> Self {
911 let mut array = ArrayVec::new();
912 for _ in 0..limits.max_bind_groups {
913 array.push(Default::default());
914 }
915 BindingLayoutSource::Derived(Box::new(array))
916 }
917}
918
919pub type StageIo = FastHashMap<wgt::ShaderLocation, InterfaceVar>;
920
921impl Interface {
922 fn populate(
923 list: &mut Vec<Varying>,
924 binding: Option<&naga::Binding>,
925 ty: naga::Handle<naga::Type>,
926 arena: &naga::UniqueArena<naga::Type>,
927 ) {
928 let numeric_ty = match arena[ty].inner {
929 naga::TypeInner::Scalar(scalar) => NumericType {
930 dim: NumericDimension::Scalar,
931 scalar,
932 },
933 naga::TypeInner::Vector { size, scalar } => NumericType {
934 dim: NumericDimension::Vector(size),
935 scalar,
936 },
937 naga::TypeInner::Matrix {
938 columns,
939 rows,
940 scalar,
941 } => NumericType {
942 dim: NumericDimension::Matrix(columns, rows),
943 scalar,
944 },
945 naga::TypeInner::Struct { ref members, .. } => {
946 for member in members {
947 Self::populate(list, member.binding.as_ref(), member.ty, arena);
948 }
949 return;
950 }
951 ref other => {
952 log::warn!("Unexpected varying type: {other:?}");
957 return;
958 }
959 };
960
961 let varying = match binding {
962 Some(&naga::Binding::Location {
963 location,
964 interpolation,
965 sampling,
966 .. }) => Varying::Local {
968 location,
969 iv: InterfaceVar {
970 ty: numeric_ty,
971 interpolation,
972 sampling,
973 },
974 },
975 Some(&naga::Binding::BuiltIn(built_in)) => Varying::BuiltIn(built_in),
976 None => {
977 log::error!("Missing binding for a varying");
978 return;
979 }
980 };
981 list.push(varying);
982 }
983
984 pub fn new(module: &naga::Module, info: &naga::valid::ModuleInfo, limits: wgt::Limits) -> Self {
985 let mut resources = naga::Arena::new();
986 let mut resource_mapping = FastHashMap::default();
987 for (var_handle, var) in module.global_variables.iter() {
988 let bind = match var.binding {
989 Some(br) => br,
990 _ => continue,
991 };
992 let naga_ty = &module.types[var.ty].inner;
993
994 let inner_ty = match *naga_ty {
995 naga::TypeInner::BindingArray { base, .. } => &module.types[base].inner,
996 ref ty => ty,
997 };
998
999 let ty = match *inner_ty {
1000 naga::TypeInner::Image {
1001 dim,
1002 arrayed,
1003 class,
1004 } => ResourceType::Texture {
1005 dim,
1006 arrayed,
1007 class,
1008 },
1009 naga::TypeInner::Sampler { comparison } => ResourceType::Sampler { comparison },
1010 naga::TypeInner::AccelerationStructure { vertex_return } => {
1011 ResourceType::AccelerationStructure { vertex_return }
1012 }
1013 ref other => ResourceType::Buffer {
1014 size: wgt::BufferSize::new(other.size(module.to_ctx()) as u64).unwrap(),
1015 },
1016 };
1017 let handle = resources.append(
1018 Resource {
1019 name: var.name.clone(),
1020 bind,
1021 ty,
1022 class: var.space,
1023 },
1024 Default::default(),
1025 );
1026 resource_mapping.insert(var_handle, handle);
1027 }
1028
1029 let mut entry_points = FastHashMap::default();
1030 entry_points.reserve(module.entry_points.len());
1031 for (index, entry_point) in module.entry_points.iter().enumerate() {
1032 let info = info.get_entry_point(index);
1033 let mut ep = EntryPoint::default();
1034 for arg in entry_point.function.arguments.iter() {
1035 Self::populate(&mut ep.inputs, arg.binding.as_ref(), arg.ty, &module.types);
1036 }
1037 if let Some(ref result) = entry_point.function.result {
1038 Self::populate(
1039 &mut ep.outputs,
1040 result.binding.as_ref(),
1041 result.ty,
1042 &module.types,
1043 );
1044 }
1045
1046 for (var_handle, var) in module.global_variables.iter() {
1047 let usage = info[var_handle];
1048 if !usage.is_empty() && var.binding.is_some() {
1049 ep.resources.push(resource_mapping[&var_handle]);
1050 }
1051 }
1052
1053 for key in info.sampling_set.iter() {
1054 ep.sampling_pairs
1055 .insert((resource_mapping[&key.image], resource_mapping[&key.sampler]));
1056 }
1057 ep.dual_source_blending = info.dual_source_blending;
1058 ep.workgroup_size = entry_point.workgroup_size;
1059
1060 entry_points.insert((entry_point.stage, entry_point.name.clone()), ep);
1061 }
1062
1063 Self {
1064 limits,
1065 resources,
1066 entry_points,
1067 }
1068 }
1069
1070 pub fn finalize_entry_point_name(
1071 &self,
1072 stage_bit: wgt::ShaderStages,
1073 entry_point_name: Option<&str>,
1074 ) -> Result<String, StageError> {
1075 let stage = Self::shader_stage_from_stage_bit(stage_bit);
1076 entry_point_name
1077 .map(|ep| ep.to_string())
1078 .map(Ok)
1079 .unwrap_or_else(|| {
1080 let mut entry_points = self
1081 .entry_points
1082 .keys()
1083 .filter_map(|(ep_stage, name)| (ep_stage == &stage).then_some(name));
1084 let first = entry_points.next().ok_or(StageError::NoEntryPointFound)?;
1085 if entry_points.next().is_some() {
1086 return Err(StageError::MultipleEntryPointsFound);
1087 }
1088 Ok(first.clone())
1089 })
1090 }
1091
1092 pub(crate) fn shader_stage_from_stage_bit(stage_bit: wgt::ShaderStages) -> naga::ShaderStage {
1093 match stage_bit {
1094 wgt::ShaderStages::VERTEX => naga::ShaderStage::Vertex,
1095 wgt::ShaderStages::FRAGMENT => naga::ShaderStage::Fragment,
1096 wgt::ShaderStages::COMPUTE => naga::ShaderStage::Compute,
1097 _ => unreachable!(),
1098 }
1099 }
1100
1101 pub fn check_stage(
1102 &self,
1103 layouts: &mut BindingLayoutSource<'_>,
1104 shader_binding_sizes: &mut FastHashMap<naga::ResourceBinding, wgt::BufferSize>,
1105 entry_point_name: &str,
1106 stage_bit: wgt::ShaderStages,
1107 inputs: StageIo,
1108 compare_function: Option<wgt::CompareFunction>,
1109 ) -> Result<StageIo, StageError> {
1110 let shader_stage = Self::shader_stage_from_stage_bit(stage_bit);
1113 let pair = (shader_stage, entry_point_name.to_string());
1114 let entry_point = match self.entry_points.get(&pair) {
1115 Some(some) => some,
1116 None => return Err(StageError::MissingEntryPoint(pair.1)),
1117 };
1118 let (_stage, entry_point_name) = pair;
1119
1120 for &handle in entry_point.resources.iter() {
1122 let res = &self.resources[handle];
1123 let result = 'err: {
1124 match layouts {
1125 BindingLayoutSource::Provided(layouts) => {
1126 if let ResourceType::Buffer { size } = res.ty {
1128 match shader_binding_sizes.entry(res.bind) {
1129 Entry::Occupied(e) => {
1130 *e.into_mut() = size.max(*e.get());
1131 }
1132 Entry::Vacant(e) => {
1133 e.insert(size);
1134 }
1135 }
1136 }
1137
1138 let Some(map) = layouts.get(res.bind.group as usize) else {
1139 break 'err Err(BindingError::Missing);
1140 };
1141
1142 let Some(entry) = map.get(res.bind.binding) else {
1143 break 'err Err(BindingError::Missing);
1144 };
1145
1146 if !entry.visibility.contains(stage_bit) {
1147 break 'err Err(BindingError::Invisible);
1148 }
1149
1150 res.check_binding_use(entry)
1151 }
1152 BindingLayoutSource::Derived(layouts) => {
1153 let Some(map) = layouts.get_mut(res.bind.group as usize) else {
1154 break 'err Err(BindingError::Missing);
1155 };
1156
1157 let ty = match res.derive_binding_type(
1158 entry_point
1159 .sampling_pairs
1160 .iter()
1161 .any(|&(im, _samp)| im == handle),
1162 ) {
1163 Ok(ty) => ty,
1164 Err(error) => break 'err Err(error),
1165 };
1166
1167 match map.entry(res.bind.binding) {
1168 indexmap::map::Entry::Occupied(e) if e.get().ty != ty => {
1169 break 'err Err(BindingError::InconsistentlyDerivedType)
1170 }
1171 indexmap::map::Entry::Occupied(e) => {
1172 e.into_mut().visibility |= stage_bit;
1173 }
1174 indexmap::map::Entry::Vacant(e) => {
1175 e.insert(BindGroupLayoutEntry {
1176 binding: res.bind.binding,
1177 ty,
1178 visibility: stage_bit,
1179 count: None,
1180 });
1181 }
1182 }
1183 Ok(())
1184 }
1185 }
1186 };
1187 if let Err(error) = result {
1188 return Err(StageError::Binding(res.bind, error));
1189 }
1190 }
1191
1192 if let BindingLayoutSource::Provided(layouts) = layouts {
1197 for &(texture_handle, sampler_handle) in entry_point.sampling_pairs.iter() {
1198 let texture_bind = &self.resources[texture_handle].bind;
1199 let sampler_bind = &self.resources[sampler_handle].bind;
1200 let texture_layout = layouts[texture_bind.group as usize]
1201 .get(texture_bind.binding)
1202 .unwrap();
1203 let sampler_layout = layouts[sampler_bind.group as usize]
1204 .get(sampler_bind.binding)
1205 .unwrap();
1206 assert!(texture_layout.visibility.contains(stage_bit));
1207 assert!(sampler_layout.visibility.contains(stage_bit));
1208
1209 let sampler_filtering = matches!(
1210 sampler_layout.ty,
1211 BindingType::Sampler(wgt::SamplerBindingType::Filtering)
1212 );
1213 let texture_sample_type = match texture_layout.ty {
1214 BindingType::Texture { sample_type, .. } => sample_type,
1215 BindingType::ExternalTexture => {
1216 wgt::TextureSampleType::Float { filterable: true }
1217 }
1218 _ => unreachable!(),
1219 };
1220
1221 let error = match (sampler_filtering, texture_sample_type) {
1222 (true, wgt::TextureSampleType::Float { filterable: false }) => {
1223 Some(FilteringError::Float)
1224 }
1225 (true, wgt::TextureSampleType::Sint) => Some(FilteringError::Integer),
1226 (true, wgt::TextureSampleType::Uint) => Some(FilteringError::Integer),
1227 _ => None,
1228 };
1229
1230 if let Some(error) = error {
1231 return Err(StageError::Filtering {
1232 texture: *texture_bind,
1233 sampler: *sampler_bind,
1234 error,
1235 });
1236 }
1237 }
1238 }
1239
1240 if shader_stage == naga::ShaderStage::Compute {
1242 let max_workgroup_size_limits = [
1243 self.limits.max_compute_workgroup_size_x,
1244 self.limits.max_compute_workgroup_size_y,
1245 self.limits.max_compute_workgroup_size_z,
1246 ];
1247 let total_invocations = entry_point.workgroup_size.iter().product::<u32>();
1248
1249 if entry_point.workgroup_size.contains(&0)
1250 || total_invocations > self.limits.max_compute_invocations_per_workgroup
1251 || entry_point.workgroup_size[0] > max_workgroup_size_limits[0]
1252 || entry_point.workgroup_size[1] > max_workgroup_size_limits[1]
1253 || entry_point.workgroup_size[2] > max_workgroup_size_limits[2]
1254 {
1255 return Err(StageError::InvalidWorkgroupSize {
1256 current: entry_point.workgroup_size,
1257 current_total: total_invocations,
1258 limit: max_workgroup_size_limits,
1259 total: self.limits.max_compute_invocations_per_workgroup,
1260 });
1261 }
1262 }
1263
1264 let mut inter_stage_components = 0;
1265
1266 for input in entry_point.inputs.iter() {
1268 match *input {
1269 Varying::Local { location, ref iv } => {
1270 let result =
1271 inputs
1272 .get(&location)
1273 .ok_or(InputError::Missing)
1274 .and_then(|provided| {
1275 let (compatible, num_components) = match shader_stage {
1276 naga::ShaderStage::Vertex => {
1279 let is_compatible =
1280 iv.ty.scalar.kind == provided.ty.scalar.kind;
1281 (is_compatible, 0)
1283 }
1284 naga::ShaderStage::Fragment => {
1285 if iv.interpolation != provided.interpolation {
1286 return Err(InputError::InterpolationMismatch(
1287 provided.interpolation,
1288 ));
1289 }
1290 if iv.sampling != provided.sampling {
1291 return Err(InputError::SamplingMismatch(
1292 provided.sampling,
1293 ));
1294 }
1295 (
1296 iv.ty.is_subtype_of(&provided.ty),
1297 iv.ty.dim.num_components(),
1298 )
1299 }
1300 naga::ShaderStage::Compute => (false, 0),
1301 naga::ShaderStage::Task | naga::ShaderStage::Mesh => {
1303 unreachable!()
1304 }
1305 };
1306 if compatible {
1307 Ok(num_components)
1308 } else {
1309 Err(InputError::WrongType(provided.ty))
1310 }
1311 });
1312 match result {
1313 Ok(num_components) => {
1314 inter_stage_components += num_components;
1315 }
1316 Err(error) => {
1317 return Err(StageError::Input {
1318 location,
1319 var: iv.clone(),
1320 error,
1321 })
1322 }
1323 }
1324 }
1325 Varying::BuiltIn(_) => {}
1326 }
1327 }
1328
1329 match shader_stage {
1330 naga::ShaderStage::Vertex => {
1331 for output in entry_point.outputs.iter() {
1332 inter_stage_components += match *output {
1334 Varying::Local { ref iv, .. } => iv.ty.dim.num_components(),
1335 Varying::BuiltIn(_) => 0,
1336 };
1337
1338 if let Some(
1339 cmp @ wgt::CompareFunction::Equal | cmp @ wgt::CompareFunction::NotEqual,
1340 ) = compare_function
1341 {
1342 if let Varying::BuiltIn(naga::BuiltIn::Position { invariant: false }) =
1343 *output
1344 {
1345 log::warn!(
1346 concat!(
1347 "Vertex shader with entry point {} outputs a ",
1348 "@builtin(position) without the @invariant attribute and ",
1349 "is used in a pipeline with {cmp:?}. On some machines, ",
1350 "this can cause bad artifacting as {cmp:?} assumes the ",
1351 "values output from the vertex shader exactly match the ",
1352 "value in the depth buffer. The @invariant attribute on the ",
1353 "@builtin(position) vertex output ensures that the exact ",
1354 "same pixel depths are used every render."
1355 ),
1356 entry_point_name,
1357 cmp = cmp
1358 );
1359 }
1360 }
1361 }
1362 }
1363 naga::ShaderStage::Fragment => {
1364 for output in &entry_point.outputs {
1365 let &Varying::Local { location, ref iv } = output else {
1366 continue;
1367 };
1368 if location >= self.limits.max_color_attachments {
1369 return Err(StageError::ColorAttachmentLocationTooLarge {
1370 location,
1371 var: iv.clone(),
1372 limit: self.limits.max_color_attachments,
1373 });
1374 }
1375 }
1376 }
1377 _ => (),
1378 }
1379
1380 if inter_stage_components > self.limits.max_inter_stage_shader_components {
1381 return Err(StageError::TooManyVaryings {
1382 used: inter_stage_components,
1383 limit: self.limits.max_inter_stage_shader_components,
1384 });
1385 }
1386
1387 let outputs = entry_point
1388 .outputs
1389 .iter()
1390 .filter_map(|output| match *output {
1391 Varying::Local { location, ref iv } => Some((location, iv.clone())),
1392 Varying::BuiltIn(_) => None,
1393 })
1394 .collect();
1395
1396 Ok(outputs)
1397 }
1398
1399 pub fn fragment_uses_dual_source_blending(
1400 &self,
1401 entry_point_name: &str,
1402 ) -> Result<bool, StageError> {
1403 let pair = (naga::ShaderStage::Fragment, entry_point_name.to_string());
1404 self.entry_points
1405 .get(&pair)
1406 .ok_or(StageError::MissingEntryPoint(pair.1))
1407 .map(|ep| ep.dual_source_blending)
1408 }
1409}
1410
1411pub fn validate_color_attachment_bytes_per_sample(
1413 attachment_formats: impl Iterator<Item = Option<wgt::TextureFormat>>,
1414 limit: u32,
1415) -> Result<(), u32> {
1416 let mut total_bytes_per_sample: u32 = 0;
1417 for format in attachment_formats {
1418 let Some(format) = format else {
1419 continue;
1420 };
1421
1422 let byte_cost = format.target_pixel_byte_cost().unwrap();
1423 let alignment = format.target_component_alignment().unwrap();
1424
1425 total_bytes_per_sample = total_bytes_per_sample.next_multiple_of(alignment);
1426 total_bytes_per_sample += byte_cost;
1427 }
1428
1429 if total_bytes_per_sample > limit {
1430 return Err(total_bytes_per_sample);
1431 }
1432
1433 Ok(())
1434}