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, 3) => {
531 return Err(Error::UnsupportedAttribute("primitive_id".to_string()));
532 }
533 crate::BuiltIn::ViewIndex if self.lang_version < (2, 2) => {
537 return Err(Error::UnsupportedAttribute("amplification_id".to_string()));
538 }
539 crate::BuiltIn::Barycentric if self.lang_version < (2, 3) => {
542 return Err(Error::UnsupportedAttribute("barycentric_coord".to_string()));
543 }
544 _ => {}
545 }
546
547 Ok(ResolvedBinding::BuiltIn(built_in))
548 }
549 crate::Binding::Location {
550 location,
551 interpolation,
552 sampling,
553 blend_src,
554 per_primitive: _,
555 } => match mode {
556 LocationMode::VertexInput => Ok(ResolvedBinding::Attribute(location)),
557 LocationMode::FragmentOutput => {
558 if blend_src.is_some() && self.lang_version < (1, 2) {
559 return Err(Error::UnsupportedAttribute("blend_src".to_string()));
560 }
561 Ok(ResolvedBinding::Color {
562 location,
563 blend_src,
564 })
565 }
566 LocationMode::VertexOutput | LocationMode::FragmentInput => {
567 Ok(ResolvedBinding::User {
568 prefix: if self.spirv_cross_compatibility {
569 "locn"
570 } else {
571 "loc"
572 },
573 index: location,
574 interpolation: {
575 let interpolation = interpolation.unwrap();
579 let sampling = sampling.unwrap_or(crate::Sampling::Center);
580 Some(ResolvedInterpolation::from_binding(interpolation, sampling))
581 },
582 })
583 }
584 LocationMode::Uniform => Err(Error::GenericValidation(format!(
585 "Unexpected Binding::Location({location}) for the Uniform mode"
586 ))),
587 },
588 }
589 }
590
591 fn get_entry_point_resources(&self, ep: &crate::EntryPoint) -> Option<&EntryPointResources> {
592 self.per_entry_point_map.get(&ep.name)
593 }
594
595 fn get_resource_binding_target(
596 &self,
597 ep: &crate::EntryPoint,
598 res_binding: &crate::ResourceBinding,
599 ) -> Option<&BindTarget> {
600 self.get_entry_point_resources(ep)
601 .and_then(|res| res.resources.get(res_binding))
602 }
603
604 fn resolve_resource_binding(
605 &self,
606 ep: &crate::EntryPoint,
607 res_binding: &crate::ResourceBinding,
608 ) -> Result<ResolvedBinding, EntryPointError> {
609 let target = self.get_resource_binding_target(ep, res_binding);
610 match target {
611 Some(target) => Ok(ResolvedBinding::Resource(target.clone())),
612 None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
613 prefix: "fake",
614 index: 0,
615 interpolation: None,
616 }),
617 None => Err(EntryPointError::MissingBindTarget(*res_binding)),
618 }
619 }
620
621 fn resolve_push_constants(
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.push_constant_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::MissingPushConstants),
639 }
640 }
641
642 fn resolve_sizes_buffer(
643 &self,
644 ep: &crate::EntryPoint,
645 ) -> Result<ResolvedBinding, EntryPointError> {
646 let slot = self
647 .get_entry_point_resources(ep)
648 .and_then(|res| res.sizes_buffer);
649 match slot {
650 Some(slot) => Ok(ResolvedBinding::Resource(BindTarget {
651 buffer: Some(slot),
652 ..Default::default()
653 })),
654 None if self.fake_missing_bindings => Ok(ResolvedBinding::User {
655 prefix: "fake",
656 index: 0,
657 interpolation: None,
658 }),
659 None => Err(EntryPointError::MissingSizesBuffer),
660 }
661 }
662}
663
664impl ResolvedBinding {
665 fn as_inline_sampler<'a>(&self, options: &'a Options) -> Option<&'a sampler::InlineSampler> {
666 match *self {
667 Self::Resource(BindTarget {
668 sampler: Some(BindSamplerTarget::Inline(index)),
669 ..
670 }) => Some(&options.inline_samplers[index as usize]),
671 _ => None,
672 }
673 }
674
675 fn try_fmt<W: Write>(&self, out: &mut W) -> Result<(), Error> {
676 write!(out, " [[")?;
677 match *self {
678 Self::BuiltIn(built_in) => {
679 use crate::BuiltIn as Bi;
680 let name = match built_in {
681 Bi::Position { invariant: false } => "position",
682 Bi::Position { invariant: true } => "position, invariant",
683 Bi::ViewIndex => "amplification_id",
684 Bi::BaseInstance => "base_instance",
686 Bi::BaseVertex => "base_vertex",
687 Bi::ClipDistance => "clip_distance",
688 Bi::InstanceIndex => "instance_id",
689 Bi::PointSize => "point_size",
690 Bi::VertexIndex => "vertex_id",
691 Bi::FragDepth => "depth(any)",
693 Bi::PointCoord => "point_coord",
694 Bi::FrontFacing => "front_facing",
695 Bi::PrimitiveIndex => "primitive_id",
696 Bi::Barycentric => "barycentric_coord",
697 Bi::SampleIndex => "sample_id",
698 Bi::SampleMask => "sample_mask",
699 Bi::GlobalInvocationId => "thread_position_in_grid",
701 Bi::LocalInvocationId => "thread_position_in_threadgroup",
702 Bi::LocalInvocationIndex => "thread_index_in_threadgroup",
703 Bi::WorkGroupId => "threadgroup_position_in_grid",
704 Bi::WorkGroupSize => "dispatch_threads_per_threadgroup",
705 Bi::NumWorkGroups => "threadgroups_per_grid",
706 Bi::NumSubgroups => "simdgroups_per_threadgroup",
708 Bi::SubgroupId => "simdgroup_index_in_threadgroup",
709 Bi::SubgroupSize => "threads_per_simdgroup",
710 Bi::SubgroupInvocationId => "thread_index_in_simdgroup",
711 Bi::CullDistance | Bi::DrawID => {
712 return Err(Error::UnsupportedBuiltIn(built_in))
713 }
714 Bi::CullPrimitive => "primitive_culled",
715 Bi::PointIndex | Bi::LineIndices | Bi::TriangleIndices => unimplemented!(),
717 Bi::MeshTaskSize => unreachable!(),
718 };
719 write!(out, "{name}")?;
720 }
721 Self::Attribute(index) => write!(out, "attribute({index})")?,
722 Self::Color {
723 location,
724 blend_src,
725 } => {
726 if let Some(blend_src) = blend_src {
727 write!(out, "color({location}) index({blend_src})")?
728 } else {
729 write!(out, "color({location})")?
730 }
731 }
732 Self::User {
733 prefix,
734 index,
735 interpolation,
736 } => {
737 write!(out, "user({prefix}{index})")?;
738 if let Some(interpolation) = interpolation {
739 write!(out, ", ")?;
740 interpolation.try_fmt(out)?;
741 }
742 }
743 Self::Resource(ref target) => {
744 if let Some(id) = target.buffer {
745 write!(out, "buffer({id})")?;
746 } else if let Some(id) = target.texture {
747 write!(out, "texture({id})")?;
748 } else if let Some(BindSamplerTarget::Resource(id)) = target.sampler {
749 write!(out, "sampler({id})")?;
750 } else {
751 return Err(Error::UnimplementedBindTarget(target.clone()));
752 }
753 }
754 }
755 write!(out, "]]")?;
756 Ok(())
757 }
758}
759
760impl ResolvedInterpolation {
761 const fn from_binding(interpolation: crate::Interpolation, sampling: crate::Sampling) -> Self {
762 use crate::Interpolation as I;
763 use crate::Sampling as S;
764
765 match (interpolation, sampling) {
766 (I::Perspective, S::Center) => Self::CenterPerspective,
767 (I::Perspective, S::Centroid) => Self::CentroidPerspective,
768 (I::Perspective, S::Sample) => Self::SamplePerspective,
769 (I::Linear, S::Center) => Self::CenterNoPerspective,
770 (I::Linear, S::Centroid) => Self::CentroidNoPerspective,
771 (I::Linear, S::Sample) => Self::SampleNoPerspective,
772 (I::Flat, _) => Self::Flat,
773 _ => unreachable!(),
774 }
775 }
776
777 fn try_fmt<W: Write>(self, out: &mut W) -> Result<(), Error> {
778 let identifier = match self {
779 Self::CenterPerspective => "center_perspective",
780 Self::CenterNoPerspective => "center_no_perspective",
781 Self::CentroidPerspective => "centroid_perspective",
782 Self::CentroidNoPerspective => "centroid_no_perspective",
783 Self::SamplePerspective => "sample_perspective",
784 Self::SampleNoPerspective => "sample_no_perspective",
785 Self::Flat => "flat",
786 };
787 out.write_str(identifier)?;
788 Ok(())
789 }
790}
791
792pub struct TranslationInfo {
795 pub entry_point_names: Vec<Result<String, EntryPointError>>,
800}
801
802pub fn write_string(
803 module: &crate::Module,
804 info: &ModuleInfo,
805 options: &Options,
806 pipeline_options: &PipelineOptions,
807) -> Result<(String, TranslationInfo), Error> {
808 let mut w = Writer::new(String::new());
809 let info = w.write(module, info, options, pipeline_options)?;
810 Ok((w.finish(), info))
811}
812
813#[test]
814fn test_error_size() {
815 assert_eq!(size_of::<Error>(), 40);
816}