screen_13/driver/
graphic.rs

1//! Graphics pipeline types
2
3use {
4    super::{
5        DriverError,
6        device::Device,
7        image::SampleCount,
8        merge_push_constant_ranges,
9        shader::{
10            DescriptorBindingMap, PipelineDescriptorInfo, Shader, SpecializationInfo, align_spriv,
11        },
12    },
13    ash::vk,
14    derive_builder::{Builder, UninitializedFieldError},
15    log::{Level::Trace, log_enabled, trace, warn},
16    ordered_float::OrderedFloat,
17    std::{collections::HashSet, ffi::CString, sync::Arc, thread::panicking},
18};
19
20const RGBA_COLOR_COMPONENTS: vk::ColorComponentFlags = vk::ColorComponentFlags::from_raw(
21    vk::ColorComponentFlags::R.as_raw()
22        | vk::ColorComponentFlags::G.as_raw()
23        | vk::ColorComponentFlags::B.as_raw()
24        | vk::ColorComponentFlags::A.as_raw(),
25);
26
27/// Specifies color blend state used when rasterization is enabled for any color attachments
28/// accessed during rendering.
29///
30/// See
31/// [VkPipelineColorBlendAttachmentState](https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPipelineColorBlendAttachmentState.html).
32#[derive(Builder, Clone, Copy, Debug, Eq, Hash, PartialEq)]
33#[builder(
34    build_fn(private, name = "fallible_build", error = "BlendModeBuilderError"),
35    derive(Clone, Copy, Debug),
36    pattern = "owned"
37)]
38pub struct BlendMode {
39    /// Controls whether blending is enabled for the corresponding color attachment.
40    ///
41    /// If blending is not enabled, the source fragment’s color for that attachment is passed
42    /// through unmodified.
43    #[builder(default = "false")]
44    pub blend_enable: bool,
45
46    /// Selects which blend factor is used to determine the source factors.
47    #[builder(default = "vk::BlendFactor::SRC_COLOR")]
48    pub src_color_blend_factor: vk::BlendFactor,
49
50    /// Selects which blend factor is used to determine the destination factors.
51    #[builder(default = "vk::BlendFactor::ONE_MINUS_DST_COLOR")]
52    pub dst_color_blend_factor: vk::BlendFactor,
53
54    /// Selects which blend operation is used to calculate the RGB values to write to the color
55    /// attachment.
56    #[builder(default = "vk::BlendOp::ADD")]
57    pub color_blend_op: vk::BlendOp,
58
59    /// Selects which blend factor is used to determine the source factor.
60    #[builder(default = "vk::BlendFactor::ZERO")]
61    pub src_alpha_blend_factor: vk::BlendFactor,
62
63    /// Selects which blend factor is used to determine the destination factor.
64    #[builder(default = "vk::BlendFactor::ZERO")]
65    pub dst_alpha_blend_factor: vk::BlendFactor,
66
67    /// Selects which blend operation is used to calculate the alpha values to write to the color
68    /// attachment.
69    #[builder(default = "vk::BlendOp::ADD")]
70    pub alpha_blend_op: vk::BlendOp,
71
72    /// A bitmask of specifying which of the R, G, B, and/or A components are enabled for writing,
73    /// as described for the
74    /// [Color Write Mask](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#framebuffer-color-write-mask).
75    #[builder(default = "RGBA_COLOR_COMPONENTS")]
76    pub color_write_mask: vk::ColorComponentFlags,
77}
78
79impl BlendMode {
80    /// A commonly used blend mode for replacing color attachment values with new ones.
81    pub const REPLACE: Self = Self {
82        blend_enable: false,
83        src_color_blend_factor: vk::BlendFactor::SRC_COLOR,
84        dst_color_blend_factor: vk::BlendFactor::ONE_MINUS_DST_COLOR,
85        color_blend_op: vk::BlendOp::ADD,
86        src_alpha_blend_factor: vk::BlendFactor::ZERO,
87        dst_alpha_blend_factor: vk::BlendFactor::ZERO,
88        alpha_blend_op: vk::BlendOp::ADD,
89        color_write_mask: RGBA_COLOR_COMPONENTS,
90    };
91
92    /// A commonly used blend mode for blending color attachment values based on the alpha channel.
93    pub const ALPHA: Self = Self {
94        blend_enable: true,
95        src_color_blend_factor: vk::BlendFactor::SRC_ALPHA,
96        dst_color_blend_factor: vk::BlendFactor::ONE_MINUS_SRC_ALPHA,
97        color_blend_op: vk::BlendOp::ADD,
98        src_alpha_blend_factor: vk::BlendFactor::SRC_ALPHA,
99        dst_alpha_blend_factor: vk::BlendFactor::ONE_MINUS_SRC_ALPHA,
100        alpha_blend_op: vk::BlendOp::ADD,
101        color_write_mask: RGBA_COLOR_COMPONENTS,
102    };
103
104    /// A commonly used blend mode for blending color attachment values based on the alpha channel,
105    /// where the color components have been pre-multiplied with the alpha component value.
106    pub const PRE_MULTIPLIED_ALPHA: Self = Self {
107        blend_enable: true,
108        src_color_blend_factor: vk::BlendFactor::SRC_ALPHA,
109        dst_color_blend_factor: vk::BlendFactor::ONE_MINUS_SRC_ALPHA,
110        color_blend_op: vk::BlendOp::ADD,
111        src_alpha_blend_factor: vk::BlendFactor::ONE,
112        dst_alpha_blend_factor: vk::BlendFactor::ONE,
113        alpha_blend_op: vk::BlendOp::ADD,
114        color_write_mask: RGBA_COLOR_COMPONENTS,
115    };
116
117    /// Specifies a default blend mode which is not enabled.
118    #[allow(clippy::new_ret_no_self)]
119    pub fn new() -> BlendModeBuilder {
120        BlendModeBuilder::default()
121    }
122
123    pub(super) fn into_vk(self) -> vk::PipelineColorBlendAttachmentState {
124        vk::PipelineColorBlendAttachmentState {
125            blend_enable: if self.blend_enable {
126                vk::TRUE
127            } else {
128                vk::FALSE
129            },
130            src_color_blend_factor: self.src_color_blend_factor,
131            dst_color_blend_factor: self.dst_color_blend_factor,
132            color_blend_op: self.color_blend_op,
133            src_alpha_blend_factor: self.src_alpha_blend_factor,
134            dst_alpha_blend_factor: self.dst_alpha_blend_factor,
135            alpha_blend_op: self.alpha_blend_op,
136            color_write_mask: self.color_write_mask,
137        }
138    }
139}
140
141// the Builder derive Macro wants Default to be implemented for BlendMode
142impl Default for BlendMode {
143    fn default() -> Self {
144        Self::REPLACE
145    }
146}
147
148// HACK: https://github.com/colin-kiegel/rust-derive-builder/issues/56
149impl BlendModeBuilder {
150    /// Builds a new `BlendMode`.
151    pub fn build(self) -> BlendMode {
152        self.fallible_build().unwrap()
153    }
154}
155
156#[derive(Debug)]
157struct BlendModeBuilderError;
158
159impl From<UninitializedFieldError> for BlendModeBuilderError {
160    fn from(_: UninitializedFieldError) -> Self {
161        Self
162    }
163}
164
165/// Specifies the [depth bounds tests], [stencil test], and [depth test] pipeline state.
166///
167/// [depth bounds tests]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fragops-dbt
168/// [stencil test]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fragops-stencil
169/// [depth test]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fragops-depth
170#[derive(Builder, Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
171#[builder(
172    build_fn(
173        private,
174        name = "fallible_build",
175        error = "DepthStencilModeBuilderError"
176    ),
177    derive(Clone, Copy, Debug),
178    pattern = "owned"
179)]
180pub struct DepthStencilMode {
181    /// Control parameters of the stencil test.
182    pub back: StencilMode,
183
184    /// Controls whether [depth bounds testing] is enabled.
185    ///
186    /// [depth bounds testing]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fragops-dbt
187    pub bounds_test: bool,
188
189    /// A value specifying the comparison operator to use in the [depth comparison] step of the
190    /// [depth test].
191    ///
192    /// [depth comparison]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fragops-depth-comparison
193    /// [depth test]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fragops-depth
194    pub compare_op: vk::CompareOp,
195
196    /// Controls whether [depth testing] is enabled.
197    ///
198    /// [depth testing]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fragops-depth
199    pub depth_test: bool,
200
201    /// Controls whether [depth writes] are enabled when `depth_test` is `true`.
202    ///
203    /// Depth writes are always disabled when `depth_test` is `false`.
204    ///
205    /// [depth writes]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fragops-depth-write
206    pub depth_write: bool,
207
208    /// Control parameters of the stencil test.
209    pub front: StencilMode,
210
211    // Note: Using setter(into) so caller does not need our version of OrderedFloat
212    /// Minimum depth bound used in the [depth bounds test].
213    ///
214    /// [depth bounds test]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fragops-dbt
215    #[builder(setter(into))]
216    pub min: OrderedFloat<f32>,
217
218    // Note: Using setter(into) so caller does not need our version of OrderedFloat
219    /// Maximum depth bound used in the [depth bounds test].
220    ///
221    /// [depth bounds test]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fragops-dbt
222    #[builder(setter(into))]
223    pub max: OrderedFloat<f32>,
224
225    /// Controls whether [stencil testing] is enabled.
226    ///
227    /// [stencil testing]: https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fragops-stencil
228    pub stencil_test: bool,
229}
230
231impl DepthStencilMode {
232    /// A commonly used depth/stencil mode
233    pub const DEPTH_READ: Self = Self {
234        back: StencilMode::IGNORE,
235        bounds_test: true,
236        compare_op: vk::CompareOp::LESS,
237        depth_test: true,
238        depth_write: false,
239        front: StencilMode::IGNORE,
240        min: OrderedFloat(0.0),
241        max: OrderedFloat(1.0),
242        stencil_test: false,
243    };
244
245    /// A commonly used depth/stencil mode
246    pub const DEPTH_WRITE: Self = Self {
247        back: StencilMode::IGNORE,
248        bounds_test: true,
249        compare_op: vk::CompareOp::LESS,
250        depth_test: true,
251        depth_write: true,
252        front: StencilMode::IGNORE,
253        min: OrderedFloat(0.0),
254        max: OrderedFloat(1.0),
255        stencil_test: false,
256    };
257
258    /// Specifies a no-depth/no-stencil mode.
259    pub const IGNORE: Self = Self {
260        back: StencilMode::IGNORE,
261        bounds_test: false,
262        compare_op: vk::CompareOp::NEVER,
263        depth_test: false,
264        depth_write: false,
265        front: StencilMode::IGNORE,
266        min: OrderedFloat(0.0),
267        max: OrderedFloat(0.0),
268        stencil_test: false,
269    };
270
271    /// Specifies a default depth/stencil mode which is equal to [`DepthStencilMode::IGNORE`].
272    #[allow(clippy::new_ret_no_self)]
273    pub fn new() -> DepthStencilModeBuilder {
274        DepthStencilModeBuilder::default()
275    }
276
277    pub(super) fn into_vk(self) -> vk::PipelineDepthStencilStateCreateInfo<'static> {
278        vk::PipelineDepthStencilStateCreateInfo {
279            back: self.back.into_vk(),
280            depth_bounds_test_enable: self.bounds_test as _,
281            depth_compare_op: self.compare_op,
282            depth_test_enable: self.depth_test as _,
283            depth_write_enable: self.depth_write as _,
284            front: self.front.into_vk(),
285            max_depth_bounds: *self.max,
286            min_depth_bounds: *self.min,
287            stencil_test_enable: self.stencil_test as _,
288            ..Default::default()
289        }
290    }
291}
292
293// HACK: https://github.com/colin-kiegel/rust-derive-builder/issues/56
294impl DepthStencilModeBuilder {
295    /// Builds a new `DepthStencilMode`.
296    pub fn build(mut self) -> DepthStencilMode {
297        if self.back.is_none() {
298            self.back = Some(Default::default());
299        }
300
301        if self.bounds_test.is_none() {
302            self.bounds_test = Some(Default::default());
303        }
304
305        if self.compare_op.is_none() {
306            self.compare_op = Some(Default::default());
307        }
308
309        if self.depth_test.is_none() {
310            self.depth_test = Some(Default::default());
311        }
312
313        if self.depth_write.is_none() {
314            self.depth_write = Some(Default::default());
315        }
316
317        if self.front.is_none() {
318            self.front = Some(Default::default());
319        }
320
321        if self.min.is_none() {
322            self.min = Some(Default::default());
323        }
324
325        if self.max.is_none() {
326            self.max = Some(Default::default());
327        }
328
329        if self.stencil_test.is_none() {
330            self.stencil_test = Some(Default::default());
331        }
332
333        self.fallible_build()
334            .expect("All required fields set at initialization")
335    }
336}
337
338#[derive(Debug)]
339struct DepthStencilModeBuilderError;
340
341impl From<UninitializedFieldError> for DepthStencilModeBuilderError {
342    fn from(_: UninitializedFieldError) -> Self {
343        Self
344    }
345}
346
347/// Opaque representation of a [pipeline] object.
348///
349/// Also contains information about the object.
350///
351/// [pipeline]: https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkPipeline.html
352#[derive(Debug)]
353pub struct GraphicPipeline {
354    pub(crate) descriptor_bindings: DescriptorBindingMap,
355    pub(crate) descriptor_info: PipelineDescriptorInfo,
356    device: Arc<Device>,
357
358    /// Information used to create this object.
359    pub info: GraphicPipelineInfo,
360
361    pub(crate) input_attachments: Box<[u32]>,
362    pub(crate) layout: vk::PipelineLayout,
363
364    /// A descriptive name used in debugging messages.
365    pub name: Option<String>,
366
367    pub(crate) push_constants: Vec<vk::PushConstantRange>,
368    pub(crate) shader_modules: Vec<vk::ShaderModule>,
369    pub(super) state: GraphicPipelineState,
370}
371
372impl GraphicPipeline {
373    /// Creates a new graphic pipeline on the given device.
374    ///
375    /// The correct pipeline stages will be enabled based on the provided shaders. See [Shader] for
376    /// details on all available stages.
377    ///
378    /// # Panics
379    ///
380    /// If shader code is not a multiple of four bytes.
381    ///
382    /// # Examples
383    ///
384    /// Basic usage:
385    ///
386    /// ```no_run
387    /// # use std::sync::Arc;
388    /// # use ash::vk;
389    /// # use screen_13::driver::DriverError;
390    /// # use screen_13::driver::device::{Device, DeviceInfo};
391    /// # use screen_13::driver::graphic::{GraphicPipeline, GraphicPipelineInfo};
392    /// # use screen_13::driver::shader::Shader;
393    /// # fn main() -> Result<(), DriverError> {
394    /// # let device = Arc::new(Device::create_headless(DeviceInfo::default())?);
395    /// # let my_frag_code = [0u8; 1];
396    /// # let my_vert_code = [0u8; 1];
397    /// // shader code is raw SPIR-V code as bytes
398    /// let vert = Shader::new_vertex(my_vert_code.as_slice());
399    /// let frag = Shader::new_fragment(my_frag_code.as_slice());
400    /// let info = GraphicPipelineInfo::default();
401    /// let pipeline = GraphicPipeline::create(&device, info, [vert, frag])?;
402    ///
403    /// assert_eq!(pipeline.info.front_face, vk::FrontFace::COUNTER_CLOCKWISE);
404    /// # Ok(()) }
405    /// ```
406    #[profiling::function]
407    pub fn create<S>(
408        device: &Arc<Device>,
409        info: impl Into<GraphicPipelineInfo>,
410        shaders: impl IntoIterator<Item = S>,
411    ) -> Result<Self, DriverError>
412    where
413        S: Into<Shader>,
414    {
415        trace!("create");
416
417        let device = Arc::clone(device);
418        let info = info.into();
419        let shaders = shaders
420            .into_iter()
421            .map(|shader| shader.into())
422            .collect::<Vec<Shader>>();
423
424        let vertex_input = shaders
425            .iter()
426            .find(|shader| shader.stage == vk::ShaderStageFlags::VERTEX)
427            .expect("vertex shader not found")
428            .vertex_input();
429
430        // Check for proper stages because vulkan may not complain but this is bad
431        let has_fragment_stage = shaders
432            .iter()
433            .any(|shader| shader.stage.contains(vk::ShaderStageFlags::FRAGMENT));
434        let has_tesselation_stage = shaders.iter().any(|shader| {
435            shader
436                .stage
437                .contains(vk::ShaderStageFlags::TESSELLATION_CONTROL)
438        }) && shaders.iter().any(|shader| {
439            shader
440                .stage
441                .contains(vk::ShaderStageFlags::TESSELLATION_EVALUATION)
442        });
443        let has_geometry_stage = shaders
444            .iter()
445            .any(|shader| shader.stage.contains(vk::ShaderStageFlags::GEOMETRY));
446
447        debug_assert!(
448            has_fragment_stage || has_tesselation_stage || has_geometry_stage,
449            "invalid shader stage combination"
450        );
451
452        let mut descriptor_bindings = Shader::merge_descriptor_bindings(
453            shaders.iter().map(|shader| shader.descriptor_bindings()),
454        );
455        for (descriptor_info, _) in descriptor_bindings.values_mut() {
456            if descriptor_info.binding_count() == 0 {
457                descriptor_info.set_binding_count(info.bindless_descriptor_count);
458            }
459        }
460
461        let descriptor_info = PipelineDescriptorInfo::create(&device, &descriptor_bindings)?;
462        let descriptor_sets_layouts = descriptor_info
463            .layouts
464            .values()
465            .map(|descriptor_set_layout| **descriptor_set_layout)
466            .collect::<Box<[_]>>();
467
468        let push_constants = shaders
469            .iter()
470            .map(|shader| shader.push_constant_range())
471            .filter_map(|mut push_const| push_const.take())
472            .collect::<Vec<_>>();
473
474        let input_attachments = {
475            let (input, write) = shaders
476                .iter()
477                .find(|shader| shader.stage == vk::ShaderStageFlags::FRAGMENT)
478                .expect("fragment shader not found")
479                .attachments();
480            let (input, write) = (
481                input
482                    .collect::<HashSet<_>>()
483                    .into_iter()
484                    .collect::<Box<_>>(),
485                write.collect::<HashSet<_>>(),
486            );
487
488            if log_enabled!(Trace) {
489                for input in input.iter() {
490                    trace!("detected input attachment {input}");
491                }
492
493                for write in &write {
494                    trace!("detected write attachment {write}");
495                }
496            }
497
498            input
499        };
500
501        unsafe {
502            let layout = device
503                .create_pipeline_layout(
504                    &vk::PipelineLayoutCreateInfo::default()
505                        .set_layouts(&descriptor_sets_layouts)
506                        .push_constant_ranges(&push_constants),
507                    None,
508                )
509                .map_err(|err| {
510                    warn!("{err}");
511
512                    DriverError::Unsupported
513                })?;
514            let shader_info = shaders
515                .into_iter()
516                .map(|shader| {
517                    let shader_module = device
518                        .create_shader_module(
519                            &vk::ShaderModuleCreateInfo::default()
520                                .code(align_spriv(&shader.spirv)?),
521                            None,
522                        )
523                        .map_err(|err| {
524                            warn!("{err}");
525
526                            DriverError::Unsupported
527                        })?;
528                    let shader_stage = Stage {
529                        flags: shader.stage,
530                        module: shader_module,
531                        name: CString::new(shader.entry_name.as_str()).unwrap(),
532                        specialization_info: shader.specialization_info,
533                    };
534
535                    Result::<_, DriverError>::Ok((shader_module, shader_stage))
536                })
537                .collect::<Result<Vec<_>, _>>()?;
538            let mut shader_modules = vec![];
539            let mut stages = vec![];
540            shader_info
541                .into_iter()
542                .for_each(|(shader_module, shader_stage)| {
543                    shader_modules.push(shader_module);
544                    stages.push(shader_stage);
545                });
546
547            let mut multisample = MultisampleState {
548                alpha_to_coverage_enable: info.alpha_to_coverage,
549                alpha_to_one_enable: info.alpha_to_one,
550                rasterization_samples: info.samples,
551                ..Default::default()
552            };
553
554            if let Some(OrderedFloat(min_sample_shading)) = info.min_sample_shading {
555                #[cfg(debug_assertions)]
556                if info.samples.is_single() {
557                    // This combination of a single-sampled pipeline and minimum sample shading
558                    // does not make sense and should not be requested. In the future maybe this is
559                    // part of the MSAA value so it can't be specified.
560                    warn!("unsupported sample rate shading of single-sample pipeline");
561                }
562
563                // Callers should check this before attempting to use the feature
564                debug_assert!(
565                    device.physical_device.features_v1_0.sample_rate_shading,
566                    "unsupported sample rate shading feature"
567                );
568
569                multisample.sample_shading_enable = true;
570                multisample.min_sample_shading = min_sample_shading;
571            }
572
573            let push_constants = merge_push_constant_ranges(&push_constants);
574
575            Ok(Self {
576                descriptor_bindings,
577                descriptor_info,
578                device,
579                info,
580                input_attachments,
581                layout,
582                name: None,
583                push_constants,
584                shader_modules,
585                state: GraphicPipelineState {
586                    layout,
587                    multisample,
588                    stages,
589                    vertex_input,
590                },
591            })
592        }
593    }
594
595    /// Sets the debugging name assigned to this pipeline.
596    pub fn with_name(mut this: Self, name: impl Into<String>) -> Self {
597        this.name = Some(name.into());
598        this
599    }
600}
601
602impl Drop for GraphicPipeline {
603    #[profiling::function]
604    fn drop(&mut self) {
605        if panicking() {
606            return;
607        }
608
609        unsafe {
610            self.device.destroy_pipeline_layout(self.layout, None);
611        }
612
613        for shader_module in self.shader_modules.drain(..) {
614            unsafe {
615                self.device.destroy_shader_module(shader_module, None);
616            }
617        }
618    }
619}
620
621/// Information used to create a [`GraphicPipeline`] instance.
622#[derive(Builder, Clone, Copy, Debug, Eq, Hash, PartialEq)]
623#[builder(
624    build_fn(
625        private,
626        name = "fallible_build",
627        error = "GraphicPipelineInfoBuilderError"
628    ),
629    derive(Clone, Copy, Debug),
630    pattern = "owned"
631)]
632#[non_exhaustive]
633pub struct GraphicPipelineInfo {
634    /// Controls whether a temporary coverage value is generated based on the alpha component of the
635    /// fragment’s first color output.
636    #[builder(default)]
637    pub alpha_to_coverage: bool,
638
639    /// Controls whether the alpha component of the fragment’s first color output is replaced with
640    /// one.
641    #[builder(default)]
642    pub alpha_to_one: bool,
643
644    /// The number of descriptors to allocate for a given binding when using bindless (unbounded)
645    /// syntax.
646    ///
647    /// The default is `8192`.
648    ///
649    /// # Examples
650    ///
651    /// Basic usage (GLSL):
652    ///
653    /// ```
654    /// # inline_spirv::inline_spirv!(r#"
655    /// #version 460 core
656    /// #extension GL_EXT_nonuniform_qualifier : require
657    ///
658    /// layout(set = 0, binding = 0) uniform sampler2D my_binding[];
659    ///
660    /// void main()
661    /// {
662    ///     // my_binding will have space for 8,192 images by default
663    /// }
664    /// # "#, frag);
665    /// ```
666    #[builder(default = "8192")]
667    pub bindless_descriptor_count: u32,
668
669    /// Specifies color blend state used when rasterization is enabled for any color attachments
670    /// accessed during rendering.
671    ///
672    /// The default value is [`BlendMode::REPLACE`].
673    #[builder(default)]
674    pub blend: BlendMode,
675
676    /// Bitmask controlling triangle culling.
677    ///
678    /// The default value is `vk::CullModeFlags::BACK`.
679    #[builder(default = "vk::CullModeFlags::BACK")]
680    pub cull_mode: vk::CullModeFlags,
681
682    /// Interpret polygon front-facing orientation.
683    ///
684    /// The default value is `vk::FrontFace::COUNTER_CLOCKWISE`.
685    #[builder(default = "vk::FrontFace::COUNTER_CLOCKWISE")]
686    pub front_face: vk::FrontFace,
687
688    /// Specify a fraction of the minimum number of unique samples to process for each fragment.
689    #[builder(default, setter(into, strip_option))]
690    pub min_sample_shading: Option<OrderedFloat<f32>>,
691
692    /// Control polygon rasterization mode.
693    ///
694    /// The default value is `vk::PolygonMode::FILL`.
695    #[builder(default = "vk::PolygonMode::FILL")]
696    pub polygon_mode: vk::PolygonMode,
697
698    /// Input primitive topology.
699    ///
700    /// The default value is `vk::PrimitiveTopology::TRIANGLE_LIST`.
701    #[builder(default = "vk::PrimitiveTopology::TRIANGLE_LIST")]
702    pub topology: vk::PrimitiveTopology,
703
704    /// Multisampling antialias mode.
705    ///
706    /// The default value is `SampleCount::Type1`.
707    ///
708    /// See [multisampling](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#primsrast-multisampling).
709    #[builder(default = "SampleCount::Type1")]
710    pub samples: SampleCount,
711}
712
713impl GraphicPipelineInfo {
714    /// Creates a default `GraphicPipelineInfoBuilder`.
715    #[allow(clippy::new_ret_no_self)]
716    pub fn builder() -> GraphicPipelineInfoBuilder {
717        Default::default()
718    }
719
720    /// Converts a `GraphicPipelineInfo` into a `GraphicPipelineInfoBuilder`.
721    #[inline(always)]
722    pub fn to_builder(self) -> GraphicPipelineInfoBuilder {
723        GraphicPipelineInfoBuilder {
724            alpha_to_coverage: Some(self.alpha_to_coverage),
725            alpha_to_one: Some(self.alpha_to_one),
726            bindless_descriptor_count: Some(self.bindless_descriptor_count),
727            blend: Some(self.blend),
728            cull_mode: Some(self.cull_mode),
729            front_face: Some(self.front_face),
730            min_sample_shading: Some(self.min_sample_shading),
731            polygon_mode: Some(self.polygon_mode),
732            topology: Some(self.topology),
733            samples: Some(self.samples),
734        }
735    }
736}
737
738impl Default for GraphicPipelineInfo {
739    fn default() -> Self {
740        Self {
741            alpha_to_coverage: false,
742            alpha_to_one: false,
743            bindless_descriptor_count: 8192,
744            blend: BlendMode::REPLACE,
745            cull_mode: vk::CullModeFlags::BACK,
746            front_face: vk::FrontFace::COUNTER_CLOCKWISE,
747            min_sample_shading: None,
748            polygon_mode: vk::PolygonMode::FILL,
749            topology: vk::PrimitiveTopology::TRIANGLE_LIST,
750            samples: SampleCount::Type1,
751        }
752    }
753}
754
755impl From<GraphicPipelineInfoBuilder> for GraphicPipelineInfo {
756    fn from(info: GraphicPipelineInfoBuilder) -> Self {
757        info.build()
758    }
759}
760
761impl GraphicPipelineInfoBuilder {
762    /// Builds a new `GraphicPipelineInfo`.
763    #[inline(always)]
764    pub fn build(self) -> GraphicPipelineInfo {
765        let res = self.fallible_build();
766
767        #[cfg(test)]
768        let res = res.unwrap();
769
770        #[cfg(not(test))]
771        let res = unsafe { res.unwrap_unchecked() };
772
773        res
774    }
775}
776
777#[derive(Debug)]
778struct GraphicPipelineInfoBuilderError;
779
780impl From<UninitializedFieldError> for GraphicPipelineInfoBuilderError {
781    fn from(_: UninitializedFieldError) -> Self {
782        Self
783    }
784}
785
786#[derive(Debug)]
787pub(super) struct GraphicPipelineState {
788    pub layout: vk::PipelineLayout,
789    pub multisample: MultisampleState,
790    pub stages: Vec<Stage>,
791    pub vertex_input: VertexInputState,
792}
793
794#[derive(Debug, Default)]
795pub(super) struct MultisampleState {
796    pub alpha_to_coverage_enable: bool,
797    pub alpha_to_one_enable: bool,
798    pub flags: vk::PipelineMultisampleStateCreateFlags,
799    pub min_sample_shading: f32,
800    pub rasterization_samples: SampleCount,
801    pub sample_mask: Vec<u32>,
802    pub sample_shading_enable: bool,
803}
804
805#[derive(Debug)]
806pub(super) struct Stage {
807    pub flags: vk::ShaderStageFlags,
808    pub module: vk::ShaderModule,
809    pub name: CString,
810    pub specialization_info: Option<SpecializationInfo>,
811}
812
813/// Specifies stencil mode during rasterization.
814///
815/// See
816/// [stencil test](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fragops-stencil).
817#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
818pub struct StencilMode {
819    /// The action performed on samples that fail the stencil test.
820    pub fail_op: vk::StencilOp,
821
822    /// The action performed on samples that pass both the depth and stencil tests.
823    pub pass_op: vk::StencilOp,
824
825    /// The action performed on samples that pass the stencil test and fail the depth test.
826    pub depth_fail_op: vk::StencilOp,
827
828    /// The comparison operator used in the stencil test.
829    pub compare_op: vk::CompareOp,
830
831    /// The bits of the unsigned integer stencil values participating in the stencil test.
832    pub compare_mask: u32,
833
834    /// The bits of the unsigned integer stencil values updated by the stencil test in the stencil
835    /// framebuffer attachment.
836    pub write_mask: u32,
837
838    /// An unsigned integer stencil reference value that is used in the unsigned stencil comparison.
839    pub reference: u32,
840}
841
842impl StencilMode {
843    /// Specifes a stencil mode which is has no effect.
844    pub const IGNORE: Self = Self {
845        fail_op: vk::StencilOp::KEEP,
846        pass_op: vk::StencilOp::KEEP,
847        depth_fail_op: vk::StencilOp::KEEP,
848        compare_op: vk::CompareOp::NEVER,
849        compare_mask: 0,
850        write_mask: 0,
851        reference: 0,
852    };
853
854    fn into_vk(self) -> vk::StencilOpState {
855        vk::StencilOpState {
856            fail_op: self.fail_op,
857            pass_op: self.pass_op,
858            depth_fail_op: self.depth_fail_op,
859            compare_op: self.compare_op,
860            compare_mask: self.compare_mask,
861            write_mask: self.write_mask,
862            reference: self.reference,
863        }
864    }
865}
866
867impl Default for StencilMode {
868    fn default() -> Self {
869        Self::IGNORE
870    }
871}
872
873#[derive(Clone, Debug, Default)]
874pub(super) struct VertexInputState {
875    pub vertex_binding_descriptions: Vec<vk::VertexInputBindingDescription>,
876    pub vertex_attribute_descriptions: Vec<vk::VertexInputAttributeDescription>,
877}
878
879#[cfg(test)]
880mod tests {
881    use super::*;
882
883    type Info = GraphicPipelineInfo;
884    type Builder = GraphicPipelineInfoBuilder;
885
886    #[test]
887    pub fn graphic_pipeline_info() {
888        let info = Info::default();
889        let builder = info.to_builder().build();
890
891        assert_eq!(info, builder);
892    }
893
894    #[test]
895    pub fn graphic_pipeline_info_builder() {
896        let info = Info::default();
897        let builder = Builder::default().build();
898
899        assert_eq!(info, builder);
900    }
901}