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(feature = "deserialize")]
123#[derive(serde::Deserialize)]
124struct BindingMapSerialization {
125 resource_binding: crate::ResourceBinding,
126 bind_target: BindTarget,
127}
128
129#[cfg(feature = "deserialize")]
130fn deserialize_binding_map<'de, D>(deserializer: D) -> Result<BindingMap, D::Error>
131where
132 D: serde::Deserializer<'de>,
133{
134 use serde::Deserialize;
135
136 let vec = Vec::<BindingMapSerialization>::deserialize(deserializer)?;
137 let mut map = BindingMap::default();
138 for item in vec {
139 map.insert(item.resource_binding, item.bind_target);
140 }
141 Ok(map)
142}
143
144pub type BindingMap = alloc::collections::BTreeMap<crate::ResourceBinding, BindTarget>;
146
147#[derive(Clone, Debug, Default, Hash, Eq, PartialEq)]
148#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
149#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
150#[cfg_attr(any(feature = "serialize", feature = "deserialize"), serde(default))]
151pub struct EntryPointResources {
152 #[cfg_attr(
153 feature = "deserialize",
154 serde(deserialize_with = "deserialize_binding_map")
155 )]
156 pub resources: BindingMap,
157
158 pub push_constant_buffer: Option<Slot>,
159
160 pub sizes_buffer: Option<Slot>,
164}
165
166pub type EntryPointResourceMap = alloc::collections::BTreeMap<String, EntryPointResources>;
167
168enum ResolvedBinding {
169 BuiltIn(crate::BuiltIn),
170 Attribute(u32),
171 Color {
172 location: u32,
173 blend_src: Option<u32>,
174 },
175 User {
176 prefix: &'static str,
177 index: u32,
178 interpolation: Option<ResolvedInterpolation>,
179 },
180 Resource(BindTarget),
181}
182
183#[derive(Copy, Clone)]
184enum ResolvedInterpolation {
185 CenterPerspective,
186 CenterNoPerspective,
187 CentroidPerspective,
188 CentroidNoPerspective,
189 SamplePerspective,
190 SampleNoPerspective,
191 Flat,
192}
193
194#[derive(Debug, thiserror::Error)]
197pub enum Error {
198 #[error(transparent)]
199 Format(#[from] FmtError),
200 #[error("bind target {0:?} is empty")]
201 UnimplementedBindTarget(BindTarget),
202 #[error("composing of {0:?} is not implemented yet")]
203 UnsupportedCompose(Handle<crate::Type>),
204 #[error("operation {0:?} is not implemented yet")]
205 UnsupportedBinaryOp(crate::BinaryOperator),
206 #[error("standard function '{0}' is not implemented yet")]
207 UnsupportedCall(String),
208 #[error("feature '{0}' is not implemented yet")]
209 FeatureNotImplemented(String),
210 #[error("internal naga error: module should not have validated: {0}")]
211 GenericValidation(String),
212 #[error("BuiltIn {0:?} is not supported")]
213 UnsupportedBuiltIn(crate::BuiltIn),
214 #[error("capability {0:?} is not supported")]
215 CapabilityNotSupported(crate::valid::Capabilities),
216 #[error("attribute '{0}' is not supported for target MSL version")]
217 UnsupportedAttribute(String),
218 #[error("function '{0}' is not supported for target MSL version")]
219 UnsupportedFunction(String),
220 #[error("can not use writeable storage buffers in fragment stage prior to MSL 1.2")]
221 UnsupportedWriteableStorageBuffer,
222 #[error("can not use writeable storage textures in {0:?} stage prior to MSL 1.2")]
223 UnsupportedWriteableStorageTexture(ir::ShaderStage),
224 #[error("can not use read-write storage textures prior to MSL 1.2")]
225 UnsupportedRWStorageTexture,
226 #[error("array of '{0}' is not supported for target MSL version")]
227 UnsupportedArrayOf(String),
228 #[error("array of type '{0:?}' is not supported")]
229 UnsupportedArrayOfType(Handle<crate::Type>),
230 #[error("ray tracing is not supported prior to MSL 2.3")]
231 UnsupportedRayTracing,
232 #[error("overrides should not be present at this stage")]
233 Override,
234 #[error("bitcasting to {0:?} is not supported")]
235 UnsupportedBitCast(crate::TypeInner),
236 #[error(transparent)]
237 ResolveArraySizeError(#[from] crate::proc::ResolveArraySizeError),
238 #[error("entry point with stage {0:?} and name '{1}' not found")]
239 EntryPointNotFound(ir::ShaderStage, String),
240}
241
242#[derive(Clone, Debug, PartialEq, thiserror::Error)]
243#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
244#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
245pub enum EntryPointError {
246 #[error("global '{0}' doesn't have a binding")]
247 MissingBinding(String),
248 #[error("mapping of {0:?} is missing")]
249 MissingBindTarget(crate::ResourceBinding),
250 #[error("mapping for push constants is missing")]
251 MissingPushConstants,
252 #[error("mapping for sizes buffer is missing")]
253 MissingSizesBuffer,
254}
255
256#[derive(Clone, Copy, Debug)]
265enum LocationMode {
266 VertexInput,
268
269 VertexOutput,
271
272 FragmentInput,
274
275 FragmentOutput,
277
278 Uniform,
280}
281
282#[derive(Clone, Debug, Hash, PartialEq, Eq)]
283#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
284#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
285#[cfg_attr(feature = "deserialize", serde(default))]
286pub struct Options {
287 pub lang_version: (u8, u8),
289 pub per_entry_point_map: EntryPointResourceMap,
291 pub inline_samplers: Vec<sampler::InlineSampler>,
293 pub spirv_cross_compatibility: bool,
295 pub fake_missing_bindings: bool,
297 pub bounds_check_policies: index::BoundsCheckPolicies,
299 pub zero_initialize_workgroup_memory: bool,
301 pub force_loop_bounding: bool,
304}
305
306impl Default for Options {
307 fn default() -> Self {
308 Options {
309 lang_version: (1, 0),
310 per_entry_point_map: EntryPointResourceMap::default(),
311 inline_samplers: Vec::new(),
312 spirv_cross_compatibility: false,
313 fake_missing_bindings: true,
314 bounds_check_policies: index::BoundsCheckPolicies::default(),
315 zero_initialize_workgroup_memory: true,
316 force_loop_bounding: true,
317 }
318 }
319}
320
321#[repr(u32)]
324#[derive(Copy, Clone, Debug, Hash, Eq, PartialEq)]
325#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
326#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
327pub enum VertexFormat {
328 Uint8 = 0,
330 Uint8x2 = 1,
332 Uint8x4 = 2,
334 Sint8 = 3,
336 Sint8x2 = 4,
338 Sint8x4 = 5,
340 Unorm8 = 6,
342 Unorm8x2 = 7,
344 Unorm8x4 = 8,
346 Snorm8 = 9,
348 Snorm8x2 = 10,
350 Snorm8x4 = 11,
352 Uint16 = 12,
354 Uint16x2 = 13,
356 Uint16x4 = 14,
358 Sint16 = 15,
360 Sint16x2 = 16,
362 Sint16x4 = 17,
364 Unorm16 = 18,
366 Unorm16x2 = 19,
368 Unorm16x4 = 20,
370 Snorm16 = 21,
372 Snorm16x2 = 22,
374 Snorm16x4 = 23,
376 Float16 = 24,
378 Float16x2 = 25,
380 Float16x4 = 26,
382 Float32 = 27,
384 Float32x2 = 28,
386 Float32x3 = 29,
388 Float32x4 = 30,
390 Uint32 = 31,
392 Uint32x2 = 32,
394 Uint32x3 = 33,
396 Uint32x4 = 34,
398 Sint32 = 35,
400 Sint32x2 = 36,
402 Sint32x3 = 37,
404 Sint32x4 = 38,
406 #[cfg_attr(
408 any(feature = "serialize", feature = "deserialize"),
409 serde(rename = "unorm10-10-10-2")
410 )]
411 Unorm10_10_10_2 = 43,
412 #[cfg_attr(
414 any(feature = "serialize", feature = "deserialize"),
415 serde(rename = "unorm8x4-bgra")
416 )]
417 Unorm8x4Bgra = 44,
418}
419
420#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
422#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
423#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
424pub enum VertexBufferStepMode {
425 Constant,
426 #[default]
427 ByVertex,
428 ByInstance,
429}
430
431#[derive(Debug, Clone, PartialEq, Eq, Hash)]
434#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
435#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
436pub struct AttributeMapping {
437 pub shader_location: u32,
439 pub offset: u32,
441 pub format: VertexFormat,
447}
448
449#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)]
452#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
453#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
454pub struct VertexBufferMapping {
455 pub id: u32,
457 pub stride: u32,
459 pub step_mode: VertexBufferStepMode,
461 pub attributes: Vec<AttributeMapping>,
463}
464
465#[derive(Debug, Default, Clone)]
467#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
468#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
469#[cfg_attr(feature = "deserialize", serde(default))]
470pub struct PipelineOptions {
471 pub entry_point: Option<(ir::ShaderStage, String)>,
479
480 pub allow_and_force_point_size: bool,
487
488 pub vertex_pulling_transform: bool,
496
497 pub vertex_buffer_mappings: Vec<VertexBufferMapping>,
500}
501
502impl Options {
503 fn resolve_local_binding(
504 &self,
505 binding: &crate::Binding,
506 mode: LocationMode,
507 ) -> Result<ResolvedBinding, Error> {
508 match *binding {
509 crate::Binding::BuiltIn(mut built_in) => {
510 match built_in {
511 crate::BuiltIn::Position { ref mut invariant } => {
512 if *invariant && self.lang_version < (2, 1) {
513 return Err(Error::UnsupportedAttribute("invariant".to_string()));
514 }
515
516 if !matches!(mode, LocationMode::VertexOutput) {
519 *invariant = false;
520 }
521 }
522 crate::BuiltIn::BaseInstance if self.lang_version < (1, 2) => {
523 return Err(Error::UnsupportedAttribute("base_instance".to_string()));
524 }
525 crate::BuiltIn::InstanceIndex if self.lang_version < (1, 2) => {
526 return Err(Error::UnsupportedAttribute("instance_id".to_string()));
527 }
528 crate::BuiltIn::PrimitiveIndex if self.lang_version < (2, 2) => {
531 return Err(Error::UnsupportedAttribute("primitive_id".to_string()));
532 }
533 _ => {}
534 }
535
536 Ok(ResolvedBinding::BuiltIn(built_in))
537 }
538 crate::Binding::Location {
539 location,
540 interpolation,
541 sampling,
542 blend_src,
543 } => match mode {
544 LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)),
545 LocationMode::FragmentOutput => {
546 if blend_src.is_some() && self.lang_version < (1, 2) {
547 return Err(Error::UnsupportedAttribute("blend_src".to_string()));
548 }
549 Ok(ResolvedBinding::Color {
550 location,
551 blend_src,
552 })
553 }
554 LocationMode::VertexOutput | LocationMode::FragmentInput => {
555 Ok(ResolvedBinding::User {
556 prefix: if self.spirv_cross_compatibility {
557 "locn"
558 } else {
559 "loc"
560 },
561 index: location,
562 interpolation: {
563 let interpolation = interpolation.unwrap();
567 let sampling = sampling.unwrap_or(crate::Sampling::Center);
568 Some(ResolvedInterpolation::from_binding(interpolation, sampling))
569 },
570 })
571 }
572 LocationMode::Uniform => Err(Error::GenericValidation(format!(
573 "Unexpected Binding::Location({location}) for the Uniform mode"
574 ))),
575 },
576 }
577 }
578
579 fn get_entry_point_resources(&self, ep: &crate::EntryPoint) -> Option<&EntryPointResources> {
580 self.per_entry_point_map.get(&ep.name)
581 }
582
583 fn get_resource_binding_target(
584 &self,
585 ep: &crate::EntryPoint,
586 res_binding: &crate::ResourceBinding,
587 ) -> Option<&BindTarget> {
588 self.get_entry_point_resources(ep)
589 .and_then(|res| res.resources.get(res_binding))
590 }
591
592 fn resolve_resource_binding(
593 &self,
594 ep: &crate::EntryPoint,
595 res_binding: &crate::ResourceBinding,
596 ) -> Result<ResolvedBinding, EntryPointError> {
597 let target = self.get_resource_binding_target(ep, res_binding);
598 match target {
599 Some(target) => Ok(ResolvedBinding::Resource(target.clone())),
600 None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
601 prefix: "fake",
602 index: 0,
603 interpolation: None,
604 }),
605 None => Err(EntryPointError::MissingBindTarget(*res_binding)),
606 }
607 }
608
609 fn resolve_push_constants(
610 &self,
611 ep: &crate::EntryPoint,
612 ) -> Result<ResolvedBinding, EntryPointError> {
613 let slot = self
614 .get_entry_point_resources(ep)
615 .and_then(|res| res.push_constant_buffer);
616 match slot {
617 Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
618 buffer: Some(slot),
619 ..Default::default()
620 })),
621 None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
622 prefix: "fake",
623 index: 0,
624 interpolation: None,
625 }),
626 None => Err(EntryPointError::MissingPushConstants),
627 }
628 }
629
630 fn resolve_sizes_buffer(
631 &self,
632 ep: &crate::EntryPoint,
633 ) -> Result<ResolvedBinding, EntryPointError> {
634 let slot = self
635 .get_entry_point_resources(ep)
636 .and_then(|res| res.sizes_buffer);
637 match slot {
638 Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
639 buffer: Some(slot),
640 ..Default::default()
641 })),
642 None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
643 prefix: "fake",
644 index: 0,
645 interpolation: None,
646 }),
647 None => Err(EntryPointError::MissingSizesBuffer),
648 }
649 }
650}
651
652impl ResolvedBinding {
653 fn as_inline_sampler<'a>(&self, options: &'a Options) -> Option<&'a sampler::InlineSampler> {
654 match *self {
655 Self::Resource(BindTarget {
656 sampler: Some(BindSamplerTarget::Inline(index)),
657 ..
658 }) => Some(&options.inline_samplers[index as usize]),
659 _ => None,
660 }
661 }
662
663 fn try_fmt<W: Write>(&self, out: &mut W) -> Result<(), Error> {
664 write!(out, " [[")?;
665 match *self {
666 Self::BuiltIn(built_in) => {
667 use crate::BuiltIn as Bi;
668 let name = match built_in {
669 Bi::Position { invariant: false } => "position",
670 Bi::Position { invariant: true } => "position, invariant",
671 Bi::BaseInstance => "base_instance",
673 Bi::BaseVertex => "base_vertex",
674 Bi::ClipDistance => "clip_distance",
675 Bi::InstanceIndex => "instance_id",
676 Bi::PointSize => "point_size",
677 Bi::VertexIndex => "vertex_id",
678 Bi::FragDepth => "depth(any)",
680 Bi::PointCoord => "point_coord",
681 Bi::FrontFacing => "front_facing",
682 Bi::PrimitiveIndex => "primitive_id",
683 Bi::SampleIndex => "sample_id",
684 Bi::SampleMask => "sample_mask",
685 Bi::GlobalInvocationId => "thread_position_in_grid",
687 Bi::LocalInvocationId => "thread_position_in_threadgroup",
688 Bi::LocalInvocationIndex => "thread_index_in_threadgroup",
689 Bi::WorkGroupId => "threadgroup_position_in_grid",
690 Bi::WorkGroupSize => "dispatch_threads_per_threadgroup",
691 Bi::NumWorkGroups => "threadgroups_per_grid",
692 Bi::NumSubgroups => "simdgroups_per_threadgroup",
694 Bi::SubgroupId => "simdgroup_index_in_threadgroup",
695 Bi::SubgroupSize => "threads_per_simdgroup",
696 Bi::SubgroupInvocationId => "thread_index_in_simdgroup",
697 Bi::CullDistance | Bi::ViewIndex | Bi::DrawID => {
698 return Err(Error::UnsupportedBuiltIn(built_in))
699 }
700 };
701 write!(out, "{name}")?;
702 }
703 Self::Attribute(index) => write!(out, "attribute({index})")?,
704 Self::Color {
705 location,
706 blend_src,
707 } => {
708 if let Some(blend_src) = blend_src {
709 write!(out, "color({location}) index({blend_src})")?
710 } else {
711 write!(out, "color({location})")?
712 }
713 }
714 Self::User {
715 prefix,
716 index,
717 interpolation,
718 } => {
719 write!(out, "user({prefix}{index})")?;
720 if let Some(interpolation) = interpolation {
721 write!(out, ", ")?;
722 interpolation.try_fmt(out)?;
723 }
724 }
725 Self::Resource(ref target) => {
726 if let Some(id) = target.buffer {
727 write!(out, "buffer({id})")?;
728 } else if let Some(id) = target.texture {
729 write!(out, "texture({id})")?;
730 } else if let Some(BindSamplerTarget::Resource(id)) = target.sampler {
731 write!(out, "sampler({id})")?;
732 } else {
733 return Err(Error::UnimplementedBindTarget(target.clone()));
734 }
735 }
736 }
737 write!(out, "]]")?;
738 Ok(())
739 }
740}
741
742impl ResolvedInterpolation {
743 const fn from_binding(interpolation: crate::Interpolation, sampling: crate::Sampling) -> Self {
744 use crate::Interpolation as I;
745 use crate::Sampling as S;
746
747 match (interpolation, sampling) {
748 (I::Perspective, S::Center) => Self::CenterPerspective,
749 (I::Perspective, S::Centroid) => Self::CentroidPerspective,
750 (I::Perspective, S::Sample) => Self::SamplePerspective,
751 (I::Linear, S::Center) => Self::CenterNoPerspective,
752 (I::Linear, S::Centroid) => Self::CentroidNoPerspective,
753 (I::Linear, S::Sample) => Self::SampleNoPerspective,
754 (I::Flat, _) => Self::Flat,
755 _ => unreachable!(),
756 }
757 }
758
759 fn try_fmt<W: Write>(self, out: &mut W) -> Result<(), Error> {
760 let identifier = match self {
761 Self::CenterPerspective => "center_perspective",
762 Self::CenterNoPerspective => "center_no_perspective",
763 Self::CentroidPerspective => "centroid_perspective",
764 Self::CentroidNoPerspective => "centroid_no_perspective",
765 Self::SamplePerspective => "sample_perspective",
766 Self::SampleNoPerspective => "sample_no_perspective",
767 Self::Flat => "flat",
768 };
769 out.write_str(identifier)?;
770 Ok(())
771 }
772}
773
774pub struct TranslationInfo {
777 pub entry_point_names: Vec<Result<String, EntryPointError>>,
782}
783
784pub fn write_string(
785 module: &crate::Module,
786 info: &ModuleInfo,
787 options: &Options,
788 pipeline_options: &PipelineOptions,
789) -> Result<(String, TranslationInfo), Error> {
790 let mut w = Writer::new(String::new());
791 let info = w.write(module, info, options, pipeline_options)?;
792 Ok((w.finish(), info))
793}
794
795#[test]
796fn test_error_size() {
797 assert_eq!(size_of::<Error>(), 40);
798}