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