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