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