naga/back/spv/
mod.rs

1/*!
2Backend for [SPIR-V][spv] (Standard Portable Intermediate Representation).
3
4[spv]: https://www.khronos.org/registry/SPIR-V/
5*/
6
7mod block;
8mod helpers;
9mod image;
10mod index;
11mod instructions;
12mod layout;
13mod ray;
14mod recyclable;
15mod selection;
16mod subgroup;
17mod writer;
18
19pub use spirv::Capability;
20
21use crate::arena::Handle;
22use crate::proc::{BoundsCheckPolicies, TypeResolution};
23
24use spirv::Word;
25use std::ops;
26use thiserror::Error;
27
28#[derive(Clone)]
29struct PhysicalLayout {
30    magic_number: Word,
31    version: Word,
32    generator: Word,
33    bound: Word,
34    instruction_schema: Word,
35}
36
37#[derive(Default)]
38struct LogicalLayout {
39    capabilities: Vec<Word>,
40    extensions: Vec<Word>,
41    ext_inst_imports: Vec<Word>,
42    memory_model: Vec<Word>,
43    entry_points: Vec<Word>,
44    execution_modes: Vec<Word>,
45    debugs: Vec<Word>,
46    annotations: Vec<Word>,
47    declarations: Vec<Word>,
48    function_declarations: Vec<Word>,
49    function_definitions: Vec<Word>,
50}
51
52struct Instruction {
53    op: spirv::Op,
54    wc: u32,
55    type_id: Option<Word>,
56    result_id: Option<Word>,
57    operands: Vec<Word>,
58}
59
60const BITS_PER_BYTE: crate::Bytes = 8;
61
62#[derive(Clone, Debug, Error)]
63pub enum Error {
64    #[error("The requested entry point couldn't be found")]
65    EntryPointNotFound,
66    #[error("target SPIRV-{0}.{1} is not supported")]
67    UnsupportedVersion(u8, u8),
68    #[error("using {0} requires at least one of the capabilities {1:?}, but none are available")]
69    MissingCapabilities(&'static str, Vec<Capability>),
70    #[error("unimplemented {0}")]
71    FeatureNotImplemented(&'static str),
72    #[error("module is not validated properly: {0}")]
73    Validation(&'static str),
74    #[error("overrides should not be present at this stage")]
75    Override,
76}
77
78#[derive(Default)]
79struct IdGenerator(Word);
80
81impl IdGenerator {
82    fn next(&mut self) -> Word {
83        self.0 += 1;
84        self.0
85    }
86}
87
88#[derive(Debug, Clone)]
89pub struct DebugInfo<'a> {
90    pub source_code: &'a str,
91    pub file_name: &'a std::path::Path,
92}
93
94/// A SPIR-V block to which we are still adding instructions.
95///
96/// A `Block` represents a SPIR-V block that does not yet have a termination
97/// instruction like `OpBranch` or `OpReturn`.
98///
99/// The `OpLabel` that starts the block is implicit. It will be emitted based on
100/// `label_id` when we write the block to a `LogicalLayout`.
101///
102/// To terminate a `Block`, pass the block and the termination instruction to
103/// `Function::consume`. This takes ownership of the `Block` and transforms it
104/// into a `TerminatedBlock`.
105struct Block {
106    label_id: Word,
107    body: Vec<Instruction>,
108}
109
110/// A SPIR-V block that ends with a termination instruction.
111struct TerminatedBlock {
112    label_id: Word,
113    body: Vec<Instruction>,
114}
115
116impl Block {
117    const fn new(label_id: Word) -> Self {
118        Block {
119            label_id,
120            body: Vec::new(),
121        }
122    }
123}
124
125struct LocalVariable {
126    id: Word,
127    instruction: Instruction,
128}
129
130struct ResultMember {
131    id: Word,
132    type_id: Word,
133    built_in: Option<crate::BuiltIn>,
134}
135
136struct EntryPointContext {
137    argument_ids: Vec<Word>,
138    results: Vec<ResultMember>,
139}
140
141#[derive(Default)]
142struct Function {
143    signature: Option<Instruction>,
144    parameters: Vec<FunctionArgument>,
145    variables: crate::FastHashMap<Handle<crate::LocalVariable>, LocalVariable>,
146    blocks: Vec<TerminatedBlock>,
147    entry_point_context: Option<EntryPointContext>,
148}
149
150impl Function {
151    fn consume(&mut self, mut block: Block, termination: Instruction) {
152        block.body.push(termination);
153        self.blocks.push(TerminatedBlock {
154            label_id: block.label_id,
155            body: block.body,
156        })
157    }
158
159    fn parameter_id(&self, index: u32) -> Word {
160        match self.entry_point_context {
161            Some(ref context) => context.argument_ids[index as usize],
162            None => self.parameters[index as usize]
163                .instruction
164                .result_id
165                .unwrap(),
166        }
167    }
168}
169
170/// Characteristics of a SPIR-V `OpTypeImage` type.
171///
172/// SPIR-V requires non-composite types to be unique, including images. Since we
173/// use `LocalType` for this deduplication, it's essential that `LocalImageType`
174/// be equal whenever the corresponding `OpTypeImage`s would be. To reduce the
175/// likelihood of mistakes, we use fields that correspond exactly to the
176/// operands of an `OpTypeImage` instruction, using the actual SPIR-V types
177/// where practical.
178#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
179struct LocalImageType {
180    sampled_type: crate::ScalarKind,
181    dim: spirv::Dim,
182    flags: ImageTypeFlags,
183    image_format: spirv::ImageFormat,
184}
185
186bitflags::bitflags! {
187    /// Flags corresponding to the boolean(-ish) parameters to OpTypeImage.
188    #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
189    pub struct ImageTypeFlags: u8 {
190        const DEPTH = 0x1;
191        const ARRAYED = 0x2;
192        const MULTISAMPLED = 0x4;
193        const SAMPLED = 0x8;
194    }
195}
196
197impl LocalImageType {
198    /// Construct a `LocalImageType` from the fields of a `TypeInner::Image`.
199    fn from_inner(dim: crate::ImageDimension, arrayed: bool, class: crate::ImageClass) -> Self {
200        let make_flags = |multi: bool, other: ImageTypeFlags| -> ImageTypeFlags {
201            let mut flags = other;
202            flags.set(ImageTypeFlags::ARRAYED, arrayed);
203            flags.set(ImageTypeFlags::MULTISAMPLED, multi);
204            flags
205        };
206
207        let dim = spirv::Dim::from(dim);
208
209        match class {
210            crate::ImageClass::Sampled { kind, multi } => LocalImageType {
211                sampled_type: kind,
212                dim,
213                flags: make_flags(multi, ImageTypeFlags::SAMPLED),
214                image_format: spirv::ImageFormat::Unknown,
215            },
216            crate::ImageClass::Depth { multi } => LocalImageType {
217                sampled_type: crate::ScalarKind::Float,
218                dim,
219                flags: make_flags(multi, ImageTypeFlags::DEPTH | ImageTypeFlags::SAMPLED),
220                image_format: spirv::ImageFormat::Unknown,
221            },
222            crate::ImageClass::Storage { format, access: _ } => LocalImageType {
223                sampled_type: crate::ScalarKind::from(format),
224                dim,
225                flags: make_flags(false, ImageTypeFlags::empty()),
226                image_format: format.into(),
227            },
228        }
229    }
230}
231
232/// A SPIR-V type constructed during code generation.
233///
234/// This is the variant of [`LookupType`] used to represent types that might not
235/// be available in the arena. Variants are present here for one of two reasons:
236///
237/// -   They represent types synthesized during code generation, as explained
238///     in the documentation for [`LookupType`].
239///
240/// -   They represent types for which SPIR-V forbids duplicate `OpType...`
241///     instructions, requiring deduplication.
242///
243/// This is not a complete copy of [`TypeInner`]: for example, SPIR-V generation
244/// never synthesizes new struct types, so `LocalType` has nothing for that.
245///
246/// Each `LocalType` variant should be handled identically to its analogous
247/// `TypeInner` variant. You can use the [`make_local`] function to help with
248/// this, by converting everything possible to a `LocalType` before inspecting
249/// it.
250///
251/// ## `LocalType` equality and SPIR-V `OpType` uniqueness
252///
253/// The definition of `Eq` on `LocalType` is carefully chosen to help us follow
254/// certain SPIR-V rules. SPIR-V ยง2.8 requires some classes of `OpType...`
255/// instructions to be unique; for example, you can't have two `OpTypeInt 32 1`
256/// instructions in the same module. All 32-bit signed integers must use the
257/// same type id.
258///
259/// All SPIR-V types that must be unique can be represented as a `LocalType`,
260/// and two `LocalType`s are always `Eq` if SPIR-V would require them to use the
261/// same `OpType...` instruction. This lets us avoid duplicates by recording the
262/// ids of the type instructions we've already generated in a hash table,
263/// [`Writer::lookup_type`], keyed by `LocalType`.
264///
265/// As another example, [`LocalImageType`], stored in the `LocalType::Image`
266/// variant, is designed to help us deduplicate `OpTypeImage` instructions. See
267/// its documentation for details.
268///
269/// `LocalType` also includes variants like `Pointer` that do not need to be
270/// unique - but it is harmless to avoid the duplication.
271///
272/// As it always must, the `Hash` implementation respects the `Eq` relation.
273///
274/// [`TypeInner`]: crate::TypeInner
275#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
276enum LocalType {
277    /// A scalar, vector, or pointer to one of those.
278    Value {
279        /// If `None`, this represents a scalar type. If `Some`, this represents
280        /// a vector type of the given size.
281        vector_size: Option<crate::VectorSize>,
282        scalar: crate::Scalar,
283        pointer_space: Option<spirv::StorageClass>,
284    },
285    /// A matrix of floating-point values.
286    Matrix {
287        columns: crate::VectorSize,
288        rows: crate::VectorSize,
289        width: crate::Bytes,
290    },
291    Pointer {
292        base: Handle<crate::Type>,
293        class: spirv::StorageClass,
294    },
295    Image(LocalImageType),
296    SampledImage {
297        image_type_id: Word,
298    },
299    Sampler,
300    /// Equivalent to a [`LocalType::Pointer`] whose `base` is a Naga IR [`BindingArray`]. SPIR-V
301    /// permits duplicated `OpTypePointer` ids, so it's fine to have two different [`LocalType`]
302    /// representations for pointer types.
303    ///
304    /// [`BindingArray`]: crate::TypeInner::BindingArray
305    PointerToBindingArray {
306        base: Handle<crate::Type>,
307        size: u32,
308        space: crate::AddressSpace,
309    },
310    BindingArray {
311        base: Handle<crate::Type>,
312        size: u32,
313    },
314    AccelerationStructure,
315    RayQuery,
316}
317
318/// A type encountered during SPIR-V generation.
319///
320/// In the process of writing SPIR-V, we need to synthesize various types for
321/// intermediate results and such: pointer types, vector/matrix component types,
322/// or even booleans, which usually appear in SPIR-V code even when they're not
323/// used by the module source.
324///
325/// However, we can't use `crate::Type` or `crate::TypeInner` for these, as the
326/// type arena may not contain what we need (it only contains types used
327/// directly by other parts of the IR), and the IR module is immutable, so we
328/// can't add anything to it.
329///
330/// So for local use in the SPIR-V writer, we use this type, which holds either
331/// a handle into the arena, or a [`LocalType`] containing something synthesized
332/// locally.
333///
334/// This is very similar to the [`proc::TypeResolution`] enum, with `LocalType`
335/// playing the role of `TypeInner`. However, `LocalType` also has other
336/// properties needed for SPIR-V generation; see the description of
337/// [`LocalType`] for details.
338///
339/// [`proc::TypeResolution`]: crate::proc::TypeResolution
340#[derive(Debug, PartialEq, Hash, Eq, Copy, Clone)]
341enum LookupType {
342    Handle(Handle<crate::Type>),
343    Local(LocalType),
344}
345
346impl From<LocalType> for LookupType {
347    fn from(local: LocalType) -> Self {
348        Self::Local(local)
349    }
350}
351
352#[derive(Debug, PartialEq, Clone, Hash, Eq)]
353struct LookupFunctionType {
354    parameter_type_ids: Vec<Word>,
355    return_type_id: Word,
356}
357
358fn make_local(inner: &crate::TypeInner) -> Option<LocalType> {
359    Some(match *inner {
360        crate::TypeInner::Scalar(scalar) | crate::TypeInner::Atomic(scalar) => LocalType::Value {
361            vector_size: None,
362            scalar,
363            pointer_space: None,
364        },
365        crate::TypeInner::Vector { size, scalar } => LocalType::Value {
366            vector_size: Some(size),
367            scalar,
368            pointer_space: None,
369        },
370        crate::TypeInner::Matrix {
371            columns,
372            rows,
373            scalar,
374        } => LocalType::Matrix {
375            columns,
376            rows,
377            width: scalar.width,
378        },
379        crate::TypeInner::Pointer { base, space } => LocalType::Pointer {
380            base,
381            class: helpers::map_storage_class(space),
382        },
383        crate::TypeInner::ValuePointer {
384            size,
385            scalar,
386            space,
387        } => LocalType::Value {
388            vector_size: size,
389            scalar,
390            pointer_space: Some(helpers::map_storage_class(space)),
391        },
392        crate::TypeInner::Image {
393            dim,
394            arrayed,
395            class,
396        } => LocalType::Image(LocalImageType::from_inner(dim, arrayed, class)),
397        crate::TypeInner::Sampler { comparison: _ } => LocalType::Sampler,
398        crate::TypeInner::AccelerationStructure => LocalType::AccelerationStructure,
399        crate::TypeInner::RayQuery => LocalType::RayQuery,
400        crate::TypeInner::Array { .. }
401        | crate::TypeInner::Struct { .. }
402        | crate::TypeInner::BindingArray { .. } => return None,
403    })
404}
405
406#[derive(Debug)]
407enum Dimension {
408    Scalar,
409    Vector,
410    Matrix,
411}
412
413/// A map from evaluated [`Expression`](crate::Expression)s to their SPIR-V ids.
414///
415/// When we emit code to evaluate a given `Expression`, we record the
416/// SPIR-V id of its value here, under its `Handle<Expression>` index.
417///
418/// A `CachedExpressions` value can be indexed by a `Handle<Expression>` value.
419///
420/// [emit]: index.html#expression-evaluation-time-and-scope
421#[derive(Default)]
422struct CachedExpressions {
423    ids: Vec<Word>,
424}
425impl CachedExpressions {
426    fn reset(&mut self, length: usize) {
427        self.ids.clear();
428        self.ids.resize(length, 0);
429    }
430}
431impl ops::Index<Handle<crate::Expression>> for CachedExpressions {
432    type Output = Word;
433    fn index(&self, h: Handle<crate::Expression>) -> &Word {
434        let id = &self.ids[h.index()];
435        if *id == 0 {
436            unreachable!("Expression {:?} is not cached!", h);
437        }
438        id
439    }
440}
441impl ops::IndexMut<Handle<crate::Expression>> for CachedExpressions {
442    fn index_mut(&mut self, h: Handle<crate::Expression>) -> &mut Word {
443        let id = &mut self.ids[h.index()];
444        if *id != 0 {
445            unreachable!("Expression {:?} is already cached!", h);
446        }
447        id
448    }
449}
450impl recyclable::Recyclable for CachedExpressions {
451    fn recycle(self) -> Self {
452        CachedExpressions {
453            ids: self.ids.recycle(),
454        }
455    }
456}
457
458#[derive(Eq, Hash, PartialEq)]
459enum CachedConstant {
460    Literal(crate::proc::HashableLiteral),
461    Composite {
462        ty: LookupType,
463        constituent_ids: Vec<Word>,
464    },
465    ZeroValue(Word),
466}
467
468#[derive(Clone)]
469struct GlobalVariable {
470    /// ID of the OpVariable that declares the global.
471    ///
472    /// If you need the variable's value, use [`access_id`] instead of this
473    /// field. If we wrapped the Naga IR `GlobalVariable`'s type in a struct to
474    /// comply with Vulkan's requirements, then this points to the `OpVariable`
475    /// with the synthesized struct type, whereas `access_id` points to the
476    /// field of said struct that holds the variable's actual value.
477    ///
478    /// This is used to compute the `access_id` pointer in function prologues,
479    /// and used for `ArrayLength` expressions, which do need the struct.
480    ///
481    /// [`access_id`]: GlobalVariable::access_id
482    var_id: Word,
483
484    /// For `AddressSpace::Handle` variables, this ID is recorded in the function
485    /// prelude block (and reset before every function) as `OpLoad` of the variable.
486    /// It is then used for all the global ops, such as `OpImageSample`.
487    handle_id: Word,
488
489    /// Actual ID used to access this variable.
490    /// For wrapped buffer variables, this ID is `OpAccessChain` into the
491    /// wrapper. Otherwise, the same as `var_id`.
492    ///
493    /// Vulkan requires that globals in the `StorageBuffer` and `Uniform` storage
494    /// classes must be structs with the `Block` decoration, but WGSL and Naga IR
495    /// make no such requirement. So for such variables, we generate a wrapper struct
496    /// type with a single element of the type given by Naga, generate an
497    /// `OpAccessChain` for that member in the function prelude, and use that pointer
498    /// to refer to the global in the function body. This is the id of that access,
499    /// updated for each function in `write_function`.
500    access_id: Word,
501}
502
503impl GlobalVariable {
504    const fn dummy() -> Self {
505        Self {
506            var_id: 0,
507            handle_id: 0,
508            access_id: 0,
509        }
510    }
511
512    const fn new(id: Word) -> Self {
513        Self {
514            var_id: id,
515            handle_id: 0,
516            access_id: 0,
517        }
518    }
519
520    /// Prepare `self` for use within a single function.
521    fn reset_for_function(&mut self) {
522        self.handle_id = 0;
523        self.access_id = 0;
524    }
525}
526
527struct FunctionArgument {
528    /// Actual instruction of the argument.
529    instruction: Instruction,
530    handle_id: Word,
531}
532
533/// Tracks the expressions for which the backend emits the following instructions:
534/// - OpConstantTrue
535/// - OpConstantFalse
536/// - OpConstant
537/// - OpConstantComposite
538/// - OpConstantNull
539struct ExpressionConstnessTracker {
540    inner: bit_set::BitSet,
541}
542
543impl ExpressionConstnessTracker {
544    fn from_arena(arena: &crate::Arena<crate::Expression>) -> Self {
545        let mut inner = bit_set::BitSet::new();
546        for (handle, expr) in arena.iter() {
547            let insert = match *expr {
548                crate::Expression::Literal(_)
549                | crate::Expression::ZeroValue(_)
550                | crate::Expression::Constant(_) => true,
551                crate::Expression::Compose { ref components, .. } => {
552                    components.iter().all(|h| inner.contains(h.index()))
553                }
554                crate::Expression::Splat { value, .. } => inner.contains(value.index()),
555                _ => false,
556            };
557            if insert {
558                inner.insert(handle.index());
559            }
560        }
561        Self { inner }
562    }
563
564    fn is_const(&self, value: Handle<crate::Expression>) -> bool {
565        self.inner.contains(value.index())
566    }
567}
568
569/// General information needed to emit SPIR-V for Naga statements.
570struct BlockContext<'w> {
571    /// The writer handling the module to which this code belongs.
572    writer: &'w mut Writer,
573
574    /// The [`Module`](crate::Module) for which we're generating code.
575    ir_module: &'w crate::Module,
576
577    /// The [`Function`](crate::Function) for which we're generating code.
578    ir_function: &'w crate::Function,
579
580    /// Information module validation produced about
581    /// [`ir_function`](BlockContext::ir_function).
582    fun_info: &'w crate::valid::FunctionInfo,
583
584    /// The [`spv::Function`](Function) to which we are contributing SPIR-V instructions.
585    function: &'w mut Function,
586
587    /// SPIR-V ids for expressions we've evaluated.
588    cached: CachedExpressions,
589
590    /// The `Writer`'s temporary vector, for convenience.
591    temp_list: Vec<Word>,
592
593    /// Tracks the constness of `Expression`s residing in `self.ir_function.expressions`
594    expression_constness: ExpressionConstnessTracker,
595}
596
597impl BlockContext<'_> {
598    fn gen_id(&mut self) -> Word {
599        self.writer.id_gen.next()
600    }
601
602    fn get_type_id(&mut self, lookup_type: LookupType) -> Word {
603        self.writer.get_type_id(lookup_type)
604    }
605
606    fn get_expression_type_id(&mut self, tr: &TypeResolution) -> Word {
607        self.writer.get_expression_type_id(tr)
608    }
609
610    fn get_index_constant(&mut self, index: Word) -> Word {
611        self.writer.get_constant_scalar(crate::Literal::U32(index))
612    }
613
614    fn get_scope_constant(&mut self, scope: Word) -> Word {
615        self.writer
616            .get_constant_scalar(crate::Literal::I32(scope as _))
617    }
618
619    fn get_pointer_id(
620        &mut self,
621        handle: Handle<crate::Type>,
622        class: spirv::StorageClass,
623    ) -> Result<Word, Error> {
624        self.writer
625            .get_pointer_id(&self.ir_module.types, handle, class)
626    }
627}
628
629#[derive(Clone, Copy, Default)]
630struct LoopContext {
631    continuing_id: Option<Word>,
632    break_id: Option<Word>,
633}
634
635pub struct Writer {
636    physical_layout: PhysicalLayout,
637    logical_layout: LogicalLayout,
638    id_gen: IdGenerator,
639
640    /// The set of capabilities modules are permitted to use.
641    ///
642    /// This is initialized from `Options::capabilities`.
643    capabilities_available: Option<crate::FastHashSet<Capability>>,
644
645    /// The set of capabilities used by this module.
646    ///
647    /// If `capabilities_available` is `Some`, then this is always a subset of
648    /// that.
649    capabilities_used: crate::FastIndexSet<Capability>,
650
651    /// The set of spirv extensions used.
652    extensions_used: crate::FastIndexSet<&'static str>,
653
654    debugs: Vec<Instruction>,
655    annotations: Vec<Instruction>,
656    flags: WriterFlags,
657    bounds_check_policies: BoundsCheckPolicies,
658    zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode,
659    void_type: Word,
660    //TODO: convert most of these into vectors, addressable by handle indices
661    lookup_type: crate::FastHashMap<LookupType, Word>,
662    lookup_function: crate::FastHashMap<Handle<crate::Function>, Word>,
663    lookup_function_type: crate::FastHashMap<LookupFunctionType, Word>,
664    /// Indexed by const-expression handle indexes
665    constant_ids: Vec<Word>,
666    cached_constants: crate::FastHashMap<CachedConstant, Word>,
667    global_variables: Vec<GlobalVariable>,
668    binding_map: BindingMap,
669
670    // Cached expressions are only meaningful within a BlockContext, but we
671    // retain the table here between functions to save heap allocations.
672    saved_cached: CachedExpressions,
673
674    gl450_ext_inst_id: Word,
675
676    // Just a temporary list of SPIR-V ids
677    temp_list: Vec<Word>,
678}
679
680bitflags::bitflags! {
681    #[derive(Clone, Copy, Debug, Eq, PartialEq)]
682    pub struct WriterFlags: u32 {
683        /// Include debug labels for everything.
684        const DEBUG = 0x1;
685        /// Flip Y coordinate of `BuiltIn::Position` output.
686        const ADJUST_COORDINATE_SPACE = 0x2;
687        /// Emit `OpName` for input/output locations.
688        /// Contrary to spec, some drivers treat it as semantic, not allowing
689        /// any conflicts.
690        const LABEL_VARYINGS = 0x4;
691        /// Emit `PointSize` output builtin to vertex shaders, which is
692        /// required for drawing with `PointList` topology.
693        const FORCE_POINT_SIZE = 0x8;
694        /// Clamp `BuiltIn::FragDepth` output between 0 and 1.
695        const CLAMP_FRAG_DEPTH = 0x10;
696    }
697}
698
699#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
700#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
701#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
702pub struct BindingInfo {
703    /// If the binding is an unsized binding array, this overrides the size.
704    pub binding_array_size: Option<u32>,
705}
706
707// Using `BTreeMap` instead of `HashMap` so that we can hash itself.
708pub type BindingMap = std::collections::BTreeMap<crate::ResourceBinding, BindingInfo>;
709
710#[derive(Clone, Copy, Debug, PartialEq, Eq)]
711pub enum ZeroInitializeWorkgroupMemoryMode {
712    /// Via `VK_KHR_zero_initialize_workgroup_memory` or Vulkan 1.3
713    Native,
714    /// Via assignments + barrier
715    Polyfill,
716    None,
717}
718
719#[derive(Debug, Clone)]
720pub struct Options<'a> {
721    /// (Major, Minor) target version of the SPIR-V.
722    pub lang_version: (u8, u8),
723
724    /// Configuration flags for the writer.
725    pub flags: WriterFlags,
726
727    /// Map of resources to information about the binding.
728    pub binding_map: BindingMap,
729
730    /// If given, the set of capabilities modules are allowed to use. Code that
731    /// requires capabilities beyond these is rejected with an error.
732    ///
733    /// If this is `None`, all capabilities are permitted.
734    pub capabilities: Option<crate::FastHashSet<Capability>>,
735
736    /// How should generate code handle array, vector, matrix, or image texel
737    /// indices that are out of range?
738    pub bounds_check_policies: BoundsCheckPolicies,
739
740    /// Dictates the way workgroup variables should be zero initialized
741    pub zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode,
742
743    pub debug_info: Option<DebugInfo<'a>>,
744}
745
746impl<'a> Default for Options<'a> {
747    fn default() -> Self {
748        let mut flags = WriterFlags::ADJUST_COORDINATE_SPACE
749            | WriterFlags::LABEL_VARYINGS
750            | WriterFlags::CLAMP_FRAG_DEPTH;
751        if cfg!(debug_assertions) {
752            flags |= WriterFlags::DEBUG;
753        }
754        Options {
755            lang_version: (1, 0),
756            flags,
757            binding_map: BindingMap::default(),
758            capabilities: None,
759            bounds_check_policies: crate::proc::BoundsCheckPolicies::default(),
760            zero_initialize_workgroup_memory: ZeroInitializeWorkgroupMemoryMode::Polyfill,
761            debug_info: None,
762        }
763    }
764}
765
766// A subset of options meant to be changed per pipeline.
767#[derive(Debug, Clone)]
768#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
769#[cfg_attr(feature = "deserialize", derive(serde::Deserialize))]
770pub struct PipelineOptions {
771    /// The stage of the entry point.
772    pub shader_stage: crate::ShaderStage,
773    /// The name of the entry point.
774    ///
775    /// If no entry point that matches is found while creating a [`Writer`], a error will be thrown.
776    pub entry_point: String,
777}
778
779pub fn write_vec(
780    module: &crate::Module,
781    info: &crate::valid::ModuleInfo,
782    options: &Options,
783    pipeline_options: Option<&PipelineOptions>,
784) -> Result<Vec<u32>, Error> {
785    let mut words: Vec<u32> = Vec::new();
786    let mut w = Writer::new(options)?;
787
788    w.write(
789        module,
790        info,
791        pipeline_options,
792        &options.debug_info,
793        &mut words,
794    )?;
795    Ok(words)
796}