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 multisample = MultisampleState {
548                rasterization_samples: info.samples,
549                ..Default::default()
550            };
551
552            let push_constants = merge_push_constant_ranges(&push_constants);
553
554            Ok(Self {
555                descriptor_bindings,
556                descriptor_info,
557                device,
558                info,
559                input_attachments,
560                layout,
561                name: None,
562                push_constants,
563                shader_modules,
564                state: GraphicPipelineState {
565                    layout,
566                    multisample,
567                    stages,
568                    vertex_input,
569                },
570            })
571        }
572    }
573
574    /// Sets the debugging name assigned to this pipeline.
575    pub fn with_name(mut this: Self, name: impl Into<String>) -> Self {
576        this.name = Some(name.into());
577        this
578    }
579}
580
581impl Drop for GraphicPipeline {
582    #[profiling::function]
583    fn drop(&mut self) {
584        if panicking() {
585            return;
586        }
587
588        unsafe {
589            self.device.destroy_pipeline_layout(self.layout, None);
590        }
591
592        for shader_module in self.shader_modules.drain(..) {
593            unsafe {
594                self.device.destroy_shader_module(shader_module, None);
595            }
596        }
597    }
598}
599
600/// Information used to create a [`GraphicPipeline`] instance.
601#[derive(Builder, Clone, Copy, Debug, Eq, Hash, PartialEq)]
602#[builder(
603    build_fn(
604        private,
605        name = "fallible_build",
606        error = "GraphicPipelineInfoBuilderError"
607    ),
608    derive(Clone, Copy, Debug),
609    pattern = "owned"
610)]
611#[non_exhaustive]
612pub struct GraphicPipelineInfo {
613    /// The number of descriptors to allocate for a given binding when using bindless (unbounded)
614    /// syntax.
615    ///
616    /// The default is `8192`.
617    ///
618    /// # Examples
619    ///
620    /// Basic usage (GLSL):
621    ///
622    /// ```
623    /// # inline_spirv::inline_spirv!(r#"
624    /// #version 460 core
625    /// #extension GL_EXT_nonuniform_qualifier : require
626    ///
627    /// layout(set = 0, binding = 0) uniform sampler2D my_binding[];
628    ///
629    /// void main()
630    /// {
631    ///     // my_binding will have space for 8,192 images by default
632    /// }
633    /// # "#, frag);
634    /// ```
635    #[builder(default = "8192")]
636    pub bindless_descriptor_count: u32,
637
638    /// Specifies color blend state used when rasterization is enabled for any color attachments
639    /// accessed during rendering.
640    ///
641    /// The default value is [`BlendMode::REPLACE`].
642    #[builder(default)]
643    pub blend: BlendMode,
644
645    /// Bitmask controlling triangle culling.
646    ///
647    /// The default value is `vk::CullModeFlags::BACK`.
648    #[builder(default = "vk::CullModeFlags::BACK")]
649    pub cull_mode: vk::CullModeFlags,
650
651    /// Interpret polygon front-facing orientation.
652    ///
653    /// The default value is `vk::FrontFace::COUNTER_CLOCKWISE`.
654    #[builder(default = "vk::FrontFace::COUNTER_CLOCKWISE")]
655    pub front_face: vk::FrontFace,
656
657    /// Control polygon rasterization mode.
658    ///
659    /// The default value is `vk::PolygonMode::FILL`.
660    #[builder(default = "vk::PolygonMode::FILL")]
661    pub polygon_mode: vk::PolygonMode,
662
663    /// Input primitive topology.
664    ///
665    /// The default value is `vk::PrimitiveTopology::TRIANGLE_LIST`.
666    #[builder(default = "vk::PrimitiveTopology::TRIANGLE_LIST")]
667    pub topology: vk::PrimitiveTopology,
668
669    /// Multisampling antialias mode.
670    ///
671    /// The default value is `SampleCount::Type1`.
672    ///
673    /// See [multisampling](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#primsrast-multisampling).
674    #[builder(default = "SampleCount::Type1")]
675    pub samples: SampleCount,
676}
677
678impl GraphicPipelineInfo {
679    /// Creates a default `GraphicPipelineInfoBuilder`.
680    #[allow(clippy::new_ret_no_self)]
681    pub fn builder() -> GraphicPipelineInfoBuilder {
682        Default::default()
683    }
684
685    /// Converts a `GraphicPipelineInfo` into a `GraphicPipelineInfoBuilder`.
686    #[inline(always)]
687    pub fn to_builder(self) -> GraphicPipelineInfoBuilder {
688        GraphicPipelineInfoBuilder {
689            bindless_descriptor_count: Some(self.bindless_descriptor_count),
690            blend: Some(self.blend),
691            cull_mode: Some(self.cull_mode),
692            front_face: Some(self.front_face),
693            polygon_mode: Some(self.polygon_mode),
694            topology: Some(self.topology),
695            samples: Some(self.samples),
696        }
697    }
698}
699
700impl Default for GraphicPipelineInfo {
701    fn default() -> Self {
702        Self {
703            bindless_descriptor_count: 8192,
704            blend: BlendMode::REPLACE,
705            cull_mode: vk::CullModeFlags::BACK,
706            front_face: vk::FrontFace::COUNTER_CLOCKWISE,
707            polygon_mode: vk::PolygonMode::FILL,
708            topology: vk::PrimitiveTopology::TRIANGLE_LIST,
709            samples: SampleCount::Type1,
710        }
711    }
712}
713
714impl From<GraphicPipelineInfoBuilder> for GraphicPipelineInfo {
715    fn from(info: GraphicPipelineInfoBuilder) -> Self {
716        info.build()
717    }
718}
719
720impl GraphicPipelineInfoBuilder {
721    /// Builds a new `GraphicPipelineInfo`.
722    #[inline(always)]
723    pub fn build(self) -> GraphicPipelineInfo {
724        let res = self.fallible_build();
725
726        #[cfg(test)]
727        let res = res.unwrap();
728
729        #[cfg(not(test))]
730        let res = unsafe { res.unwrap_unchecked() };
731
732        res
733    }
734}
735
736#[derive(Debug)]
737struct GraphicPipelineInfoBuilderError;
738
739impl From<UninitializedFieldError> for GraphicPipelineInfoBuilderError {
740    fn from(_: UninitializedFieldError) -> Self {
741        Self
742    }
743}
744
745#[derive(Debug)]
746pub(super) struct GraphicPipelineState {
747    pub layout: vk::PipelineLayout,
748    pub multisample: MultisampleState,
749    pub stages: Vec<Stage>,
750    pub vertex_input: VertexInputState,
751}
752
753#[derive(Debug, Default)]
754pub(super) struct MultisampleState {
755    pub alpha_to_coverage_enable: bool,
756    pub alpha_to_one_enable: bool,
757    pub flags: vk::PipelineMultisampleStateCreateFlags,
758    pub min_sample_shading: f32,
759    pub rasterization_samples: SampleCount,
760    pub sample_mask: Vec<u32>,
761    pub sample_shading_enable: bool,
762}
763
764#[derive(Debug)]
765pub(super) struct Stage {
766    pub flags: vk::ShaderStageFlags,
767    pub module: vk::ShaderModule,
768    pub name: CString,
769    pub specialization_info: Option<SpecializationInfo>,
770}
771
772/// Specifies stencil mode during rasterization.
773///
774/// See
775/// [stencil test](https://registry.khronos.org/vulkan/specs/1.3-extensions/html/vkspec.html#fragops-stencil).
776#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
777pub struct StencilMode {
778    /// The action performed on samples that fail the stencil test.
779    pub fail_op: vk::StencilOp,
780
781    /// The action performed on samples that pass both the depth and stencil tests.
782    pub pass_op: vk::StencilOp,
783
784    /// The action performed on samples that pass the stencil test and fail the depth test.
785    pub depth_fail_op: vk::StencilOp,
786
787    /// The comparison operator used in the stencil test.
788    pub compare_op: vk::CompareOp,
789
790    /// The bits of the unsigned integer stencil values participating in the stencil test.
791    pub compare_mask: u32,
792
793    /// The bits of the unsigned integer stencil values updated by the stencil test in the stencil
794    /// framebuffer attachment.
795    pub write_mask: u32,
796
797    /// An unsigned integer stencil reference value that is used in the unsigned stencil comparison.
798    pub reference: u32,
799}
800
801impl StencilMode {
802    /// Specifes a stencil mode which is has no effect.
803    pub const IGNORE: Self = Self {
804        fail_op: vk::StencilOp::KEEP,
805        pass_op: vk::StencilOp::KEEP,
806        depth_fail_op: vk::StencilOp::KEEP,
807        compare_op: vk::CompareOp::NEVER,
808        compare_mask: 0,
809        write_mask: 0,
810        reference: 0,
811    };
812
813    fn into_vk(self) -> vk::StencilOpState {
814        vk::StencilOpState {
815            fail_op: self.fail_op,
816            pass_op: self.pass_op,
817            depth_fail_op: self.depth_fail_op,
818            compare_op: self.compare_op,
819            compare_mask: self.compare_mask,
820            write_mask: self.write_mask,
821            reference: self.reference,
822        }
823    }
824}
825
826impl Default for StencilMode {
827    fn default() -> Self {
828        Self::IGNORE
829    }
830}
831
832#[derive(Clone, Debug, Default)]
833pub(super) struct VertexInputState {
834    pub vertex_binding_descriptions: Vec<vk::VertexInputBindingDescription>,
835    pub vertex_attribute_descriptions: Vec<vk::VertexInputAttributeDescription>,
836}
837
838#[cfg(test)]
839mod tests {
840    use super::*;
841
842    type Info = GraphicPipelineInfo;
843    type Builder = GraphicPipelineInfoBuilder;
844
845    #[test]
846    pub fn graphic_pipeline_info() {
847        let info = Info::default();
848        let builder = info.to_builder().build();
849
850        assert_eq!(info, builder);
851    }
852
853    #[test]
854    pub fn graphic_pipeline_info_builder() {
855        let info = Info::default();
856        let builder = Builder::default().build();
857
858        assert_eq!(info, builder);
859    }
860}