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