1use alloc::{
72 format,
73 string::{String, ToString},
74 vec::Vec,
75};
76use core::fmt::{Error as FmtError, Write};
77
78use crate::{arena::Handle, ir, proc::index, valid::ModuleInfo};
79
80mod keywords;
81pub mod sampler;
82mod writer;
83
84pub use writer::Writer;
85
86pub type Slot = u8;
87pub type InlineSamplerIndex = u8;
88
89#[derive(Clone, Debug, PartialEq, Eq, Hash)]
90#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
91#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
92pub enum BindSamplerTarget {
93 Resource(Slot),
94 Inline(InlineSamplerIndex),
95}
96
97#[derive(Clone, Debug, PartialEq, Eq, Hash)]
103#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
104#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
105pub struct BindExternalTextureTarget {
106 pub planes: [Slot; 3],
107 pub params: Slot,
108}
109
110#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
111#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
112#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
113#[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))]
114pub struct BindTarget {
115 pub buffer: Option<Slot>,
116 pub texture: Option<Slot>,
117 pub sampler: Option<BindSamplerTarget>,
118 pub external_texture: Option<BindExternalTextureTarget>,
119 pub mutable: bool,
120}
121
122#[cfg(any(feature = "serialize", feature = "deserialize"))]
123#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
124#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
125struct BindingMapSerialization {
126 resource_binding: crate::ResourceBinding,
127 bind_target: BindTarget,
128}
129
130#[cfg(feature = "deserialize")]
131fn deserialize_binding_map<'de, D>(deserializer: D) -> Result<BindingMap, D::Error>
132where
133 D: serde::Deserializer<'de>,
134{
135 use serde::Deserialize;
136
137 let vec = Vec::<BindingMapSerialization>::deserialize(deserializer)?;
138 let mut map = BindingMap::default();
139 for item in vec {
140 map.insert(item.resource_binding, item.bind_target);
141 }
142 Ok(map)
143}
144
145pub type BindingMap = alloc::collections::BTreeMap<crate::ResourceBinding, BindTarget>;
147
148#[derive(Clone, Debug, Default, Hash, Eq, PartialEq)]
149#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
150#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
151#[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))]
152pub struct EntryPointResources {
153 #[cfg_attr(
154 feature = "deserialize",
155 serde(deserialize_with = "deserialize_binding_map")
156 )]
157 pub resources: BindingMap,
158
159 pub push_constant_buffer: Option<Slot>,
160
161 pub sizes_buffer: Option<Slot>,
165}
166
167pub type EntryPointResourceMap = alloc::collections::BTreeMap<String, EntryPointResources>;
168
169enum ResolvedBinding {
170 BuiltIn(crate::BuiltIn),
171 Attribute(u32),
172 Color {
173 location: u32,
174 blend_src: Option<u32>,
175 },
176 User {
177 prefix: &'static str,
178 index: u32,
179 interpolation: Option<ResolvedInterpolation>,
180 },
181 Resource(BindTarget),
182}
183
184#[derive(Copy, Clone)]
185enum ResolvedInterpolation {
186 CenterPerspective,
187 CenterNoPerspective,
188 CentroidPerspective,
189 CentroidNoPerspective,
190 SamplePerspective,
191 SampleNoPerspective,
192 Flat,
193}
194
195#[derive(Debug, thiserror::Error)]
198pub enum Error {
199 #[error(transparent)]
200 Format(#[from] FmtError),
201 #[error("bind target {0:?} is empty")]
202 UnimplementedBindTarget(BindTarget),
203 #[error("composing of {0:?} is not implemented yet")]
204 UnsupportedCompose(Handle<crate::Type>),
205 #[error("operation {0:?} is not implemented yet")]
206 UnsupportedBinaryOp(crate::BinaryOperator),
207 #[error("standard function '{0}' is not implemented yet")]
208 UnsupportedCall(String),
209 #[error("feature '{0}' is not implemented yet")]
210 FeatureNotImplemented(String),
211 #[error("internal naga error: module should not have validated: {0}")]
212 GenericValidation(String),
213 #[error("BuiltIn {0:?} is not supported")]
214 UnsupportedBuiltIn(crate::BuiltIn),
215 #[error("capability {0:?} is not supported")]
216 CapabilityNotSupported(crate::valid::Capabilities),
217 #[error("attribute '{0}' is not supported for target MSL version")]
218 UnsupportedAttribute(String),
219 #[error("function '{0}' is not supported for target MSL version")]
220 UnsupportedFunction(String),
221 #[error("can not use writeable storage buffers in fragment stage prior to MSL 1.2")]
222 UnsupportedWriteableStorageBuffer,
223 #[error("can not use writeable storage textures in {0:?} stage prior to MSL 1.2")]
224 UnsupportedWriteableStorageTexture(ir::ShaderStage),
225 #[error("can not use read-write storage textures prior to MSL 1.2")]
226 UnsupportedRWStorageTexture,
227 #[error("array of '{0}' is not supported for target MSL version")]
228 UnsupportedArrayOf(String),
229 #[error("array of type '{0:?}' is not supported")]
230 UnsupportedArrayOfType(Handle<crate::Type>),
231 #[error("ray tracing is not supported prior to MSL 2.3")]
232 UnsupportedRayTracing,
233 #[error("overrides should not be present at this stage")]
234 Override,
235 #[error("bitcasting to {0:?} is not supported")]
236 UnsupportedBitCast(crate::TypeInner),
237 #[error(transparent)]
238 ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError),
239 #[error("entry point with stage {0:?} and name '{1}' not found")]
240 EntryPointNotFound(ir::ShaderStage, String),
241}
242
243#[derive(Clone, Debug, PartialEq, thiserror::Error)]
244#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
245#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
246pub enum EntryPointError {
247 #[error("global '{0}' doesn't have a binding")]
248 MissingBinding(String),
249 #[error("mapping of {0:?} is missing")]
250 MissingBindTarget(crate::ResourceBinding),
251 #[error("mapping for push constants is missing")]
252 MissingPushConstants,
253 #[error("mapping for sizes buffer is missing")]
254 MissingSizesBuffer,
255}
256
257#[derive(Clone, Copy, Debug)]
266enum LocationMode {
267 VertexInput,
269
270 VertexOutput,
272
273 FragmentInput,
275
276 FragmentOutput,
278
279 Uniform,
281}
282
283#[derive(Clone, Debug, Hash, PartialEq, Eq)]
284#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
285#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
286#[cfg_attr(feature = "deserialize", serde(default))]
287pub struct Options {
288 pub lang_version: (u8, u8),
290 pub per_entry_point_map: EntryPointResourceMap,
292 pub inline_samplers: Vec<sampler::InlineSampler>,
294 pub spirv_cross_compatibility: bool,
296 pub fake_missing_bindings: bool,
298 pub bounds_check_policies: index::BoundsCheckPolicies,
300 pub zero_initialize_workgroup_memory: bool,
302 pub force_loop_bounding: bool,
305}
306
307impl Default for Options {
308 fn default() -> Self {
309 Options {
310 lang_version: (1, 0),
311 per_entry_point_map: EntryPointResourceMap::default(),
312 inline_samplers: Vec::new(),
313 spirv_cross_compatibility: false,
314 fake_missing_bindings: true,
315 bounds_check_policies: index::BoundsCheckPolicies::default(),
316 zero_initialize_workgroup_memory: true,
317 force_loop_bounding: true,
318 }
319 }
320}
321
322#[repr(u32)]
325#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
326#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
327#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
328pub enum VertexFormat {
329 Uint8 = 0,
331 Uint8x2 = 1,
333 Uint8x4 = 2,
335 Sint8 = 3,
337 Sint8x2 = 4,
339 Sint8x4 = 5,
341 Unorm8 = 6,
343 Unorm8x2 = 7,
345 Unorm8x4 = 8,
347 Snorm8 = 9,
349 Snorm8x2 = 10,
351 Snorm8x4 = 11,
353 Uint16 = 12,
355 Uint16x2 = 13,
357 Uint16x4 = 14,
359 Sint16 = 15,
361 Sint16x2 = 16,
363 Sint16x4 = 17,
365 Unorm16 = 18,
367 Unorm16x2 = 19,
369 Unorm16x4 = 20,
371 Snorm16 = 21,
373 Snorm16x2 = 22,
375 Snorm16x4 = 23,
377 Float16 = 24,
379 Float16x2 = 25,
381 Float16x4 = 26,
383 Float32 = 27,
385 Float32x2 = 28,
387 Float32x3 = 29,
389 Float32x4 = 30,
391 Uint32 = 31,
393 Uint32x2 = 32,
395 Uint32x3 = 33,
397 Uint32x4 = 34,
399 Sint32 = 35,
401 Sint32x2 = 36,
403 Sint32x3 = 37,
405 Sint32x4 = 38,
407 #[cfg_attr(
409 any(feature = "serialize", feature = "deserialize"),
410 serde(rename = "unorm10-10-10-2")
411 )]
412 Unorm10_10_10_2 = 43,
413 #[cfg_attr(
415 any(feature = "serialize", feature = "deserialize"),
416 serde(rename = "unorm8x4-bgra")
417 )]
418 Unorm8x4Bgra = 44,
419}
420
421#[derive(Debug, Clone, PartialEq, Eq, Hash)]
424#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
425#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
426pub struct AttributeMapping {
427 pub shader_location: u32,
429 pub offset: u32,
431 pub format: VertexFormat,
437}
438
439#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
442#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
443#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
444pub struct VertexBufferMapping {
445 pub id: u32,
447 pub stride: u32,
449 pub indexed_by_vertex: bool,
452 pub attributes: Vec<AttributeMapping>,
454}
455
456#[derive(Debug, Default, Clone)]
458#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
459#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
460#[cfg_attr(feature = "deserialize", serde(default))]
461pub struct PipelineOptions {
462 pub entry_point: Option<(ir::ShaderStage, String)>,
470
471 pub allow_and_force_point_size: bool,
478
479 pub vertex_pulling_transform: bool,
487
488 pub vertex_buffer_mappings: Vec<VertexBufferMapping>,
491}
492
493impl Options {
494 fn resolve_local_binding(
495 &self,
496 binding: &crate::Binding,
497 mode: LocationMode,
498 ) -> Result<ResolvedBinding, Error> {
499 match *binding {
500 crate::Binding::BuiltIn(mut built_in) => {
501 match built_in {
502 crate::BuiltIn::Position { ref mut invariant } => {
503 if *invariant && self.lang_version < (2, 1) {
504 return Err(Error::UnsupportedAttribute("invariant".to_string()));
505 }
506
507 if !matches!(mode, LocationMode::VertexOutput) {
510 *invariant = false;
511 }
512 }
513 crate::BuiltIn::BaseInstance if self.lang_version < (1, 2) => {
514 return Err(Error::UnsupportedAttribute("base_instance".to_string()));
515 }
516 crate::BuiltIn::InstanceIndex if self.lang_version < (1, 2) => {
517 return Err(Error::UnsupportedAttribute("instance_id".to_string()));
518 }
519 crate::BuiltIn::PrimitiveIndex if self.lang_version < (2, 2) => {
522 return Err(Error::UnsupportedAttribute("primitive_id".to_string()));
523 }
524 _ => {}
525 }
526
527 Ok(ResolvedBinding::BuiltIn(built_in))
528 }
529 crate::Binding::Location {
530 location,
531 interpolation,
532 sampling,
533 blend_src,
534 } => match mode {
535 LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)),
536 LocationMode::FragmentOutput => {
537 if blend_src.is_some() && self.lang_version < (1, 2) {
538 return Err(Error::UnsupportedAttribute("blend_src".to_string()));
539 }
540 Ok(ResolvedBinding::Color {
541 location,
542 blend_src,
543 })
544 }
545 LocationMode::VertexOutput | LocationMode::FragmentInput => {
546 Ok(ResolvedBinding::User {
547 prefix: if self.spirv_cross_compatibility {
548 "locn"
549 } else {
550 "loc"
551 },
552 index: location,
553 interpolation: {
554 let interpolation = interpolation.unwrap();
558 let sampling = sampling.unwrap_or(crate::Sampling::Center);
559 Some(ResolvedInterpolation::from_binding(interpolation, sampling))
560 },
561 })
562 }
563 LocationMode::Uniform => Err(Error::GenericValidation(format!(
564 "Unexpected Binding::Location({location}) for the Uniform mode"
565 ))),
566 },
567 }
568 }
569
570 fn get_entry_point_resources(&self, ep: &crate::EntryPoint) -> Option<&EntryPointResources> {
571 self.per_entry_point_map.get(&ep.name)
572 }
573
574 fn get_resource_binding_target(
575 &self,
576 ep: &crate::EntryPoint,
577 res_binding: &crate::ResourceBinding,
578 ) -> Option<&BindTarget> {
579 self.get_entry_point_resources(ep)
580 .and_then(|res| res.resources.get(res_binding))
581 }
582
583 fn resolve_resource_binding(
584 &self,
585 ep: &crate::EntryPoint,
586 res_binding: &crate::ResourceBinding,
587 ) -> Result<ResolvedBinding, EntryPointError> {
588 let target = self.get_resource_binding_target(ep, res_binding);
589 match target {
590 Some(target) => Ok(ResolvedBinding::Resource(target.clone())),
591 None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
592 prefix: "fake",
593 index: 0,
594 interpolation: None,
595 }),
596 None => Err(EntryPointError::MissingBindTarget(*res_binding)),
597 }
598 }
599
600 fn resolve_push_constants(
601 &self,
602 ep: &crate::EntryPoint,
603 ) -> Result<ResolvedBinding, EntryPointError> {
604 let slot = self
605 .get_entry_point_resources(ep)
606 .and_then(|res| res.push_constant_buffer);
607 match slot {
608 Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
609 buffer: Some(slot),
610 ..Default::default()
611 })),
612 None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
613 prefix: "fake",
614 index: 0,
615 interpolation: None,
616 }),
617 None => Err(EntryPointError::MissingPushConstants),
618 }
619 }
620
621 fn resolve_sizes_buffer(
622 &self,
623 ep: &crate::EntryPoint,
624 ) -> Result<ResolvedBinding, EntryPointError> {
625 let slot = self
626 .get_entry_point_resources(ep)
627 .and_then(|res| res.sizes_buffer);
628 match slot {
629 Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
630 buffer: Some(slot),
631 ..Default::default()
632 })),
633 None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
634 prefix: "fake",
635 index: 0,
636 interpolation: None,
637 }),
638 None => Err(EntryPointError::MissingSizesBuffer),
639 }
640 }
641}
642
643impl ResolvedBinding {
644 fn as_inline_sampler<'a>(&self, options: &'a Options) -> Option<&'a sampler::InlineSampler> {
645 match *self {
646 Self::Resource(BindTarget {
647 sampler: Some(BindSamplerTarget::Inline(index)),
648 ..
649 }) => Some(&options.inline_samplers[index as usize]),
650 _ => None,
651 }
652 }
653
654 fn try_fmt<W: Write>(&self, out: &mut W) -> Result<(), Error> {
655 write!(out, " [[")?;
656 match *self {
657 Self::BuiltIn(built_in) => {
658 use crate::BuiltIn as Bi;
659 let name = match built_in {
660 Bi::Position { invariant: false } => "position",
661 Bi::Position { invariant: true } => "position, invariant",
662 Bi::BaseInstance => "base_instance",
664 Bi::BaseVertex => "base_vertex",
665 Bi::ClipDistance => "clip_distance",
666 Bi::InstanceIndex => "instance_id",
667 Bi::PointSize => "point_size",
668 Bi::VertexIndex => "vertex_id",
669 Bi::FragDepth => "depth(any)",
671 Bi::PointCoord => "point_coord",
672 Bi::FrontFacing => "front_facing",
673 Bi::PrimitiveIndex => "primitive_id",
674 Bi::SampleIndex => "sample_id",
675 Bi::SampleMask => "sample_mask",
676 Bi::GlobalInvocationId => "thread_position_in_grid",
678 Bi::LocalInvocationId => "thread_position_in_threadgroup",
679 Bi::LocalInvocationIndex => "thread_index_in_threadgroup",
680 Bi::WorkGroupId => "threadgroup_position_in_grid",
681 Bi::WorkGroupSize => "dispatch_threads_per_threadgroup",
682 Bi::NumWorkGroups => "threadgroups_per_grid",
683 Bi::NumSubgroups => "simdgroups_per_threadgroup",
685 Bi::SubgroupId => "simdgroup_index_in_threadgroup",
686 Bi::SubgroupSize => "threads_per_simdgroup",
687 Bi::SubgroupInvocationId => "thread_index_in_simdgroup",
688 Bi::CullDistance | Bi::ViewIndex | Bi::DrawID => {
689 return Err(Error::UnsupportedBuiltIn(built_in))
690 }
691 };
692 write!(out, "{name}")?;
693 }
694 Self::Attribute(index) => write!(out, "attribute({index})")?,
695 Self::Color {
696 location,
697 blend_src,
698 } => {
699 if let Some(blend_src) = blend_src {
700 write!(out, "color({location}) index({blend_src})")?
701 } else {
702 write!(out, "color({location})")?
703 }
704 }
705 Self::User {
706 prefix,
707 index,
708 interpolation,
709 } => {
710 write!(out, "user({prefix}{index})")?;
711 if let Some(interpolation) = interpolation {
712 write!(out, ", ")?;
713 interpolation.try_fmt(out)?;
714 }
715 }
716 Self::Resource(ref target) => {
717 if let Some(id) = target.buffer {
718 write!(out, "buffer({id})")?;
719 } else if let Some(id) = target.texture {
720 write!(out, "texture({id})")?;
721 } else if let Some(BindSamplerTarget::Resource(id)) = target.sampler {
722 write!(out, "sampler({id})")?;
723 } else {
724 return Err(Error::UnimplementedBindTarget(target.clone()));
725 }
726 }
727 }
728 write!(out, "]]")?;
729 Ok(())
730 }
731}
732
733impl ResolvedInterpolation {
734 const fn from_binding(interpolation: crate::Interpolation, sampling: crate::Sampling) -> Self {
735 use crate::Interpolation as I;
736 use crate::Sampling as S;
737
738 match (interpolation, sampling) {
739 (I::Perspective, S::Center) => Self::CenterPerspective,
740 (I::Perspective, S::Centroid) => Self::CentroidPerspective,
741 (I::Perspective, S::Sample) => Self::SamplePerspective,
742 (I::Linear, S::Center) => Self::CenterNoPerspective,
743 (I::Linear, S::Centroid) => Self::CentroidNoPerspective,
744 (I::Linear, S::Sample) => Self::SampleNoPerspective,
745 (I::Flat, _) => Self::Flat,
746 _ => unreachable!(),
747 }
748 }
749
750 fn try_fmt<W: Write>(self, out: &mut W) -> Result<(), Error> {
751 let identifier = match self {
752 Self::CenterPerspective => "center_perspective",
753 Self::CenterNoPerspective => "center_no_perspective",
754 Self::CentroidPerspective => "centroid_perspective",
755 Self::CentroidNoPerspective => "centroid_no_perspective",
756 Self::SamplePerspective => "sample_perspective",
757 Self::SampleNoPerspective => "sample_no_perspective",
758 Self::Flat => "flat",
759 };
760 out.write_str(identifier)?;
761 Ok(())
762 }
763}
764
765pub struct TranslationInfo {
768 pub entry_point_names: Vec<Result<String, EntryPointError>>,
773}
774
775pub fn write_string(
776 module: &crate::Module,
777 info: &ModuleInfo,
778 options: &Options,
779 pipeline_options: &PipelineOptions,
780) -> Result<(String, TranslationInfo), Error> {
781 let mut w = Writer::new(String::new());
782 let info = w.write(module, info, options, pipeline_options)?;
783 Ok((w.finish(), info))
784}
785
786#[test]
787fn test_error_size() {
788 assert_eq!(size_of::<Error>(), 40);
789}