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