aboutsummaryrefslogtreecommitdiff
path: root/src/pipeline/graphics_pipeline/builder.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/pipeline/graphics_pipeline/builder.rs')
-rw-r--r--src/pipeline/graphics_pipeline/builder.rs1952
1 files changed, 1952 insertions, 0 deletions
diff --git a/src/pipeline/graphics_pipeline/builder.rs b/src/pipeline/graphics_pipeline/builder.rs
new file mode 100644
index 0000000..5f9a770
--- /dev/null
+++ b/src/pipeline/graphics_pipeline/builder.rs
@@ -0,0 +1,1952 @@
+// Copyright (c) 2017 The vulkano developers
+// Licensed under the Apache License, Version 2.0
+// <LICENSE-APACHE or
+// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
+// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
+// at your option. All files in the project carrying such
+// notice may not be copied, modified, or distributed except
+// according to those terms.
+
+// TODO: graphics pipeline params are deprecated, but are still the primary implementation in order
+// to avoid duplicating code, so we hide the warnings for now
+#![allow(deprecated)]
+
+use crate::check_errors;
+use crate::descriptor_set::layout::DescriptorSetDesc;
+use crate::descriptor_set::layout::DescriptorSetLayout;
+use crate::device::Device;
+use crate::image::SampleCount;
+use crate::pipeline::blend::AttachmentBlend;
+use crate::pipeline::blend::AttachmentsBlend;
+use crate::pipeline::blend::Blend;
+use crate::pipeline::blend::LogicOp;
+use crate::pipeline::cache::PipelineCache;
+use crate::pipeline::depth_stencil::Compare;
+use crate::pipeline::depth_stencil::DepthBounds;
+use crate::pipeline::depth_stencil::DepthStencil;
+use crate::pipeline::graphics_pipeline::GraphicsPipeline;
+use crate::pipeline::graphics_pipeline::GraphicsPipelineCreationError;
+use crate::pipeline::graphics_pipeline::Inner as GraphicsPipelineInner;
+use crate::pipeline::input_assembly::PrimitiveTopology;
+use crate::pipeline::layout::PipelineLayout;
+use crate::pipeline::layout::PipelineLayoutPcRange;
+use crate::pipeline::raster::CullMode;
+use crate::pipeline::raster::DepthBiasControl;
+use crate::pipeline::raster::FrontFace;
+use crate::pipeline::raster::PolygonMode;
+use crate::pipeline::raster::Rasterization;
+use crate::pipeline::shader::EntryPointAbstract;
+use crate::pipeline::shader::GraphicsEntryPoint;
+use crate::pipeline::shader::GraphicsShaderType;
+use crate::pipeline::shader::SpecializationConstants;
+use crate::pipeline::vertex::BufferlessDefinition;
+use crate::pipeline::vertex::BuffersDefinition;
+use crate::pipeline::vertex::Vertex;
+use crate::pipeline::vertex::VertexDefinition;
+use crate::pipeline::vertex::VertexInputRate;
+use crate::pipeline::viewport::Scissor;
+use crate::pipeline::viewport::Viewport;
+use crate::pipeline::viewport::ViewportsState;
+use crate::render_pass::Subpass;
+use crate::OomError;
+use crate::VulkanObject;
+use smallvec::SmallVec;
+use std::collections::hash_map::{Entry, HashMap};
+use std::mem;
+use std::mem::MaybeUninit;
+use std::ptr;
+use std::sync::Arc;
+use std::u32;
+
+/// Prototype for a `GraphicsPipeline`.
+// TODO: we can optimize this by filling directly the raw vk structs
+pub struct GraphicsPipelineBuilder<'vs, 'tcs, 'tes, 'gs, 'fs, Vdef, Vss, Tcss, Tess, Gss, Fss> {
+ vertex_definition: Vdef,
+ vertex_shader: Option<(GraphicsEntryPoint<'vs>, Vss)>,
+ input_assembly: ash::vk::PipelineInputAssemblyStateCreateInfo,
+ // Note: the `input_assembly_topology` member is temporary in order to not lose information
+ // about the number of patches per primitive.
+ input_assembly_topology: PrimitiveTopology,
+ tessellation: Option<TessInfo<'tcs, 'tes, Tcss, Tess>>,
+ geometry_shader: Option<(GraphicsEntryPoint<'gs>, Gss)>,
+ viewport: Option<ViewportsState>,
+ raster: Rasterization,
+ multisample: ash::vk::PipelineMultisampleStateCreateInfo,
+ fragment_shader: Option<(GraphicsEntryPoint<'fs>, Fss)>,
+ depth_stencil: DepthStencil,
+ blend: Blend,
+ subpass: Option<Subpass>,
+ cache: Option<Arc<PipelineCache>>,
+}
+
+// Additional parameters if tessellation is used.
+#[derive(Clone, Debug)]
+struct TessInfo<'tcs, 'tes, Tcss, Tess> {
+ tessellation_control_shader: (GraphicsEntryPoint<'tcs>, Tcss),
+ tessellation_evaluation_shader: (GraphicsEntryPoint<'tes>, Tess),
+}
+
+impl
+ GraphicsPipelineBuilder<
+ 'static,
+ 'static,
+ 'static,
+ 'static,
+ 'static,
+ BufferlessDefinition,
+ (),
+ (),
+ (),
+ (),
+ (),
+ >
+{
+ /// Builds a new empty builder.
+ pub(super) fn new() -> Self {
+ GraphicsPipelineBuilder {
+ vertex_definition: BufferlessDefinition,
+ vertex_shader: None,
+ input_assembly: ash::vk::PipelineInputAssemblyStateCreateInfo {
+ topology: PrimitiveTopology::TriangleList.into(),
+ ..Default::default()
+ },
+ input_assembly_topology: PrimitiveTopology::TriangleList,
+ tessellation: None,
+ geometry_shader: None,
+ viewport: None,
+ raster: Default::default(),
+ multisample: ash::vk::PipelineMultisampleStateCreateInfo::default(),
+ fragment_shader: None,
+ depth_stencil: DepthStencil::disabled(),
+ blend: Blend::pass_through(),
+ subpass: None,
+ cache: None,
+ }
+ }
+}
+
+impl<'vs, 'tcs, 'tes, 'gs, 'fs, Vdef, Vss, Tcss, Tess, Gss, Fss>
+ GraphicsPipelineBuilder<'vs, 'tcs, 'tes, 'gs, 'fs, Vdef, Vss, Tcss, Tess, Gss, Fss>
+where
+ Vdef: VertexDefinition,
+ Vss: SpecializationConstants,
+ Tcss: SpecializationConstants,
+ Tess: SpecializationConstants,
+ Gss: SpecializationConstants,
+ Fss: SpecializationConstants,
+{
+ /// Builds the graphics pipeline, using an inferred a pipeline layout.
+ pub fn build(
+ self,
+ device: Arc<Device>,
+ ) -> Result<GraphicsPipeline<Vdef>, GraphicsPipelineCreationError> {
+ self.with_auto_layout(device, &[])
+ }
+
+ /// Builds the graphics pipeline, using an inferred pipeline layout with some dynamic buffers.
+ ///
+ /// Configures the inferred layout for each descriptor `(set, binding)` in `dynamic_buffers` to accept dynamic
+ /// buffers.
+ pub fn with_auto_layout(
+ self,
+ device: Arc<Device>,
+ dynamic_buffers: &[(usize, usize)],
+ ) -> Result<GraphicsPipeline<Vdef>, GraphicsPipelineCreationError> {
+ let (descriptor_set_layout_descs, push_constant_ranges) = {
+ let stages: SmallVec<[&GraphicsEntryPoint; 5]> = std::array::IntoIter::new([
+ self.vertex_shader.as_ref().map(|s| &s.0),
+ self.tessellation
+ .as_ref()
+ .map(|s| &s.tessellation_control_shader.0),
+ self.tessellation
+ .as_ref()
+ .map(|s| &s.tessellation_evaluation_shader.0),
+ self.geometry_shader.as_ref().map(|s| &s.0),
+ self.fragment_shader.as_ref().map(|s| &s.0),
+ ])
+ .flatten()
+ .collect();
+
+ for (output, input) in stages.iter().zip(stages.iter().skip(1)) {
+ if let Err(err) = input.input().matches(output.output()) {
+ return Err(GraphicsPipelineCreationError::ShaderStagesMismatch(err));
+ }
+ }
+
+ let mut descriptor_set_layout_descs = stages
+ .iter()
+ .try_fold(vec![], |total, shader| -> Result<_, ()> {
+ DescriptorSetDesc::union_multiple(&total, shader.descriptor_set_layout_descs())
+ })
+ .expect("Can't be union'd");
+ DescriptorSetDesc::tweak_multiple(
+ &mut descriptor_set_layout_descs,
+ dynamic_buffers.into_iter().cloned(),
+ );
+
+ // We want to union each push constant range into a set of ranges that do not have intersecting stage flags.
+ // e.g. The range [0, 16) is either made available to Vertex | Fragment or we only make [0, 16) available to
+ // Vertex and a subrange available to Fragment, like [0, 8)
+ let mut range_map = HashMap::new();
+ for stage in stages.iter() {
+ if let Some(range) = stage.push_constant_range() {
+ match range_map.entry((range.offset, range.size)) {
+ Entry::Vacant(entry) => {
+ entry.insert(range.stages);
+ },
+ Entry::Occupied(mut entry) => {
+ *entry.get_mut() = *entry.get() | range.stages;
+ },
+ }
+ }
+ }
+ let push_constant_ranges: Vec<_> = range_map
+ .iter()
+ .map(|((offset, size), stages)| {
+ PipelineLayoutPcRange { offset: *offset, size: *size, stages: *stages }
+ })
+ .collect();
+
+ (descriptor_set_layout_descs, push_constant_ranges)
+ };
+
+ let descriptor_set_layouts = descriptor_set_layout_descs
+ .into_iter()
+ .map(|desc| Ok(Arc::new(DescriptorSetLayout::new(device.clone(), desc)?)))
+ .collect::<Result<Vec<_>, OomError>>()?;
+ let pipeline_layout = Arc::new(
+ PipelineLayout::new(device.clone(), descriptor_set_layouts, push_constant_ranges)
+ .unwrap(),
+ );
+ self.with_pipeline_layout(device, pipeline_layout)
+ }
+
+ /// Builds the graphics pipeline.
+ ///
+ /// Does the same as `build`, except that `build` automatically builds the pipeline layout
+ /// object corresponding to the union of your shaders while this function allows you to specify
+ /// the pipeline layout.
+ pub fn with_pipeline_layout(
+ mut self,
+ device: Arc<Device>,
+ pipeline_layout: Arc<PipelineLayout>,
+ ) -> Result<GraphicsPipeline<Vdef>, GraphicsPipelineCreationError> {
+ // TODO: return errors instead of panicking if missing param
+
+ let fns = device.fns();
+
+ // Checking that the pipeline layout matches the shader stages.
+ // TODO: more details in the errors
+
+ {
+ let shader = &self.vertex_shader.as_ref().unwrap().0;
+ pipeline_layout.ensure_superset_of(
+ shader.descriptor_set_layout_descs(),
+ shader.push_constant_range(),
+ )?;
+ }
+
+ if let Some(ref geometry_shader) = self.geometry_shader {
+ let shader = &geometry_shader.0;
+ pipeline_layout.ensure_superset_of(
+ shader.descriptor_set_layout_descs(),
+ shader.push_constant_range(),
+ )?;
+ }
+
+ if let Some(ref tess) = self.tessellation {
+ {
+ let shader = &tess.tessellation_control_shader.0;
+ pipeline_layout.ensure_superset_of(
+ shader.descriptor_set_layout_descs(),
+ shader.push_constant_range(),
+ )?;
+ }
+
+ {
+ let shader = &tess.tessellation_evaluation_shader.0;
+ pipeline_layout.ensure_superset_of(
+ shader.descriptor_set_layout_descs(),
+ shader.push_constant_range(),
+ )?;
+ }
+ }
+
+ if let Some(ref fragment_shader) = self.fragment_shader {
+ let shader = &fragment_shader.0;
+ pipeline_layout.ensure_superset_of(
+ shader.descriptor_set_layout_descs(),
+ shader.push_constant_range(),
+ )?;
+
+ // Check that the subpass can accept the output of the fragment shader.
+ // TODO: If there is no fragment shader, what should be checked then? The previous stage?
+ if !self
+ .subpass
+ .as_ref()
+ .unwrap()
+ .is_compatible_with(shader.output())
+ {
+ return Err(GraphicsPipelineCreationError::FragmentShaderRenderPassIncompatible);
+ }
+ }
+
+ // Will contain the list of dynamic states. Filled throughout this function.
+ let mut dynamic_states: SmallVec<[ash::vk::DynamicState; 8]> = SmallVec::new();
+
+ // Creating the specialization constants of the various stages.
+ let vertex_shader_specialization = {
+ let shader = self.vertex_shader.as_ref().unwrap();
+ let spec_descriptors = Vss::descriptors();
+ if spec_descriptors != shader.0.spec_constants() {
+ return Err(GraphicsPipelineCreationError::IncompatibleSpecializationConstants);
+ }
+
+ let constants = &shader.1;
+ ash::vk::SpecializationInfo {
+ map_entry_count: spec_descriptors.len() as u32,
+ p_map_entries: spec_descriptors.as_ptr() as *const _,
+ data_size: mem::size_of_val(constants),
+ p_data: constants as *const Vss as *const _,
+ }
+ };
+
+ let tess_shader_specialization = if let Some(ref tess) = self.tessellation {
+ let tcs_spec = {
+ let shader = &tess.tessellation_control_shader;
+ let spec_descriptors = Tcss::descriptors();
+ if spec_descriptors != shader.0.spec_constants() {
+ return Err(GraphicsPipelineCreationError::IncompatibleSpecializationConstants);
+ }
+
+ let constants = &shader.1;
+ ash::vk::SpecializationInfo {
+ map_entry_count: spec_descriptors.len() as u32,
+ p_map_entries: spec_descriptors.as_ptr() as *const _,
+ data_size: mem::size_of_val(constants),
+ p_data: constants as *const Tcss as *const _,
+ }
+ };
+ let tes_spec = {
+ let shader = &tess.tessellation_evaluation_shader;
+ let spec_descriptors = Tess::descriptors();
+ if spec_descriptors != shader.0.spec_constants() {
+ return Err(GraphicsPipelineCreationError::IncompatibleSpecializationConstants);
+ }
+
+ let constants = &shader.1;
+ ash::vk::SpecializationInfo {
+ map_entry_count: spec_descriptors.len() as u32,
+ p_map_entries: spec_descriptors.as_ptr() as *const _,
+ data_size: mem::size_of_val(constants),
+ p_data: constants as *const Tess as *const _,
+ }
+ };
+ Some((tcs_spec, tes_spec))
+ } else {
+ None
+ };
+
+ let geometry_shader_specialization = if let Some(ref shader) = self.geometry_shader {
+ let spec_descriptors = Gss::descriptors();
+ if spec_descriptors != shader.0.spec_constants() {
+ return Err(GraphicsPipelineCreationError::IncompatibleSpecializationConstants);
+ }
+
+ let constants = &shader.1;
+ Some(ash::vk::SpecializationInfo {
+ map_entry_count: spec_descriptors.len() as u32,
+ p_map_entries: spec_descriptors.as_ptr() as *const _,
+ data_size: mem::size_of_val(constants),
+ p_data: constants as *const Gss as *const _,
+ })
+ } else {
+ None
+ };
+
+ let fragment_shader_specialization = if let Some(ref shader) = self.fragment_shader {
+ let spec_descriptors = Fss::descriptors();
+ if spec_descriptors != shader.0.spec_constants() {
+ return Err(GraphicsPipelineCreationError::IncompatibleSpecializationConstants);
+ }
+
+ let constants = &shader.1;
+ Some(ash::vk::SpecializationInfo {
+ map_entry_count: spec_descriptors.len() as u32,
+ p_map_entries: spec_descriptors.as_ptr() as *const _,
+ data_size: mem::size_of_val(constants),
+ p_data: constants as *const Fss as *const _,
+ })
+ } else {
+ None
+ };
+
+ // List of shader stages.
+ let stages = {
+ let mut stages = SmallVec::<[_; 5]>::new();
+
+ match self.vertex_shader.as_ref().unwrap().0.ty() {
+ GraphicsShaderType::Vertex => {}
+ _ => return Err(GraphicsPipelineCreationError::WrongShaderType),
+ };
+
+ stages.push(ash::vk::PipelineShaderStageCreateInfo {
+ flags: ash::vk::PipelineShaderStageCreateFlags::empty(),
+ stage: ash::vk::ShaderStageFlags::VERTEX,
+ module: self
+ .vertex_shader
+ .as_ref()
+ .unwrap()
+ .0
+ .module()
+ .internal_object(),
+ p_name: self.vertex_shader.as_ref().unwrap().0.name().as_ptr(),
+ p_specialization_info: &vertex_shader_specialization as *const _,
+ ..Default::default()
+ });
+
+ if let Some(ref tess) = self.tessellation {
+ // FIXME: must check that the control shader and evaluation shader are compatible
+
+ if !device.enabled_features().tessellation_shader {
+ return Err(GraphicsPipelineCreationError::TessellationShaderFeatureNotEnabled);
+ }
+
+ match tess.tessellation_control_shader.0.ty() {
+ GraphicsShaderType::TessellationControl => {}
+ _ => return Err(GraphicsPipelineCreationError::WrongShaderType),
+ };
+
+ match tess.tessellation_evaluation_shader.0.ty() {
+ GraphicsShaderType::TessellationEvaluation => {}
+ _ => return Err(GraphicsPipelineCreationError::WrongShaderType),
+ };
+
+ stages.push(ash::vk::PipelineShaderStageCreateInfo {
+ flags: ash::vk::PipelineShaderStageCreateFlags::empty(),
+ stage: ash::vk::ShaderStageFlags::TESSELLATION_CONTROL,
+ module: tess
+ .tessellation_control_shader
+ .0
+ .module()
+ .internal_object(),
+ p_name: tess.tessellation_control_shader.0.name().as_ptr(),
+ p_specialization_info: &tess_shader_specialization.as_ref().unwrap().0
+ as *const _,
+ ..Default::default()
+ });
+
+ stages.push(ash::vk::PipelineShaderStageCreateInfo {
+ flags: ash::vk::PipelineShaderStageCreateFlags::empty(),
+ stage: ash::vk::ShaderStageFlags::TESSELLATION_EVALUATION,
+ module: tess
+ .tessellation_evaluation_shader
+ .0
+ .module()
+ .internal_object(),
+ p_name: tess.tessellation_evaluation_shader.0.name().as_ptr(),
+ p_specialization_info: &tess_shader_specialization.as_ref().unwrap().1
+ as *const _,
+ ..Default::default()
+ });
+ }
+
+ if let Some(ref geometry_shader) = self.geometry_shader {
+ if !device.enabled_features().geometry_shader {
+ return Err(GraphicsPipelineCreationError::GeometryShaderFeatureNotEnabled);
+ }
+
+ match geometry_shader.0.ty() {
+ GraphicsShaderType::Geometry(_) => {}
+ _ => return Err(GraphicsPipelineCreationError::WrongShaderType),
+ };
+
+ stages.push(ash::vk::PipelineShaderStageCreateInfo {
+ flags: ash::vk::PipelineShaderStageCreateFlags::empty(),
+ stage: ash::vk::ShaderStageFlags::GEOMETRY,
+ module: geometry_shader.0.module().internal_object(),
+ p_name: geometry_shader.0.name().as_ptr(),
+ p_specialization_info: geometry_shader_specialization.as_ref().unwrap()
+ as *const _,
+ ..Default::default()
+ });
+ }
+
+ if let Some(ref fragment_shader) = self.fragment_shader {
+ match fragment_shader.0.ty() {
+ GraphicsShaderType::Fragment => {}
+ _ => return Err(GraphicsPipelineCreationError::WrongShaderType),
+ };
+
+ stages.push(ash::vk::PipelineShaderStageCreateInfo {
+ flags: ash::vk::PipelineShaderStageCreateFlags::empty(),
+ stage: ash::vk::ShaderStageFlags::FRAGMENT,
+ module: fragment_shader.0.module().internal_object(),
+ p_name: fragment_shader.0.name().as_ptr(),
+ p_specialization_info: fragment_shader_specialization.as_ref().unwrap()
+ as *const _,
+ ..Default::default()
+ });
+ }
+
+ stages
+ };
+
+ // Vertex input.
+ let vertex_input = self
+ .vertex_definition
+ .definition(self.vertex_shader.as_ref().unwrap().0.input())?;
+
+ let (binding_descriptions, binding_divisor_descriptions) = {
+ let mut binding_descriptions = SmallVec::<[_; 8]>::new();
+ let mut binding_divisor_descriptions = SmallVec::<[_; 8]>::new();
+
+ for (binding, binding_desc) in vertex_input.bindings() {
+ if binding
+ >= device
+ .physical_device()
+ .properties()
+ .max_vertex_input_bindings
+ {
+ return Err(
+ GraphicsPipelineCreationError::MaxVertexInputBindingsExceeded {
+ max: device
+ .physical_device()
+ .properties()
+ .max_vertex_input_bindings,
+ obtained: binding,
+ },
+ );
+ }
+
+ if binding_desc.stride
+ > device
+ .physical_device()
+ .properties()
+ .max_vertex_input_binding_stride
+ {
+ return Err(
+ GraphicsPipelineCreationError::MaxVertexInputBindingStrideExceeded {
+ binding,
+ max: device
+ .physical_device()
+ .properties()
+ .max_vertex_input_binding_stride,
+ obtained: binding_desc.stride,
+ },
+ );
+ }
+
+ binding_descriptions.push(ash::vk::VertexInputBindingDescription {
+ binding,
+ stride: binding_desc.stride,
+ input_rate: binding_desc.input_rate.into(),
+ });
+
+ if let VertexInputRate::Instance { divisor } = binding_desc.input_rate {
+ if divisor != 1 {
+ if !device
+ .enabled_features()
+ .vertex_attribute_instance_rate_divisor
+ {
+ return Err(GraphicsPipelineCreationError::VertexAttributeInstanceRateDivisorFeatureNotEnabled);
+ }
+
+ if divisor == 0
+ && !device
+ .enabled_features()
+ .vertex_attribute_instance_rate_zero_divisor
+ {
+ return Err(GraphicsPipelineCreationError::VertexAttributeInstanceRateZeroDivisorFeatureNotEnabled);
+ }
+
+ if divisor
+ > device
+ .physical_device()
+ .properties()
+ .max_vertex_attrib_divisor
+ .unwrap()
+ {
+ return Err(
+ GraphicsPipelineCreationError::MaxVertexAttribDivisorExceeded {
+ binding,
+ max: device
+ .physical_device()
+ .properties()
+ .max_vertex_attrib_divisor
+ .unwrap(),
+ obtained: divisor,
+ },
+ );
+ }
+
+ binding_divisor_descriptions.push(
+ ash::vk::VertexInputBindingDivisorDescriptionEXT { binding, divisor },
+ )
+ }
+ }
+ }
+
+ if binding_descriptions.len()
+ > device
+ .physical_device()
+ .properties()
+ .max_vertex_input_bindings as usize
+ {
+ return Err(
+ GraphicsPipelineCreationError::MaxVertexInputBindingsExceeded {
+ max: device
+ .physical_device()
+ .properties()
+ .max_vertex_input_bindings,
+ obtained: binding_descriptions.len() as u32,
+ },
+ );
+ }
+
+ (binding_descriptions, binding_divisor_descriptions)
+ };
+
+ let attribute_descriptions = {
+ let mut attribute_descriptions = SmallVec::<[_; 8]>::new();
+
+ for (location, attribute_desc) in vertex_input.attributes() {
+ // TODO: check attribute format support
+
+ if attribute_desc.offset
+ > device
+ .physical_device()
+ .properties()
+ .max_vertex_input_attribute_offset
+ {
+ return Err(
+ GraphicsPipelineCreationError::MaxVertexInputAttributeOffsetExceeded {
+ max: device
+ .physical_device()
+ .properties()
+ .max_vertex_input_attribute_offset,
+ obtained: attribute_desc.offset,
+ },
+ );
+ }
+
+ attribute_descriptions.push(ash::vk::VertexInputAttributeDescription {
+ location,
+ binding: attribute_desc.binding,
+ format: attribute_desc.format.into(),
+ offset: attribute_desc.offset,
+ });
+ }
+
+ if attribute_descriptions.len()
+ > device
+ .physical_device()
+ .properties()
+ .max_vertex_input_attributes as usize
+ {
+ return Err(
+ GraphicsPipelineCreationError::MaxVertexInputAttributesExceeded {
+ max: device
+ .physical_device()
+ .properties()
+ .max_vertex_input_attributes,
+ obtained: attribute_descriptions.len(),
+ },
+ );
+ }
+
+ attribute_descriptions
+ };
+
+ let vertex_input_divisor_state = if !binding_divisor_descriptions.is_empty() {
+ Some(ash::vk::PipelineVertexInputDivisorStateCreateInfoEXT {
+ vertex_binding_divisor_count: binding_divisor_descriptions.len() as u32,
+ p_vertex_binding_divisors: binding_divisor_descriptions.as_ptr(),
+ ..Default::default()
+ })
+ } else {
+ None
+ };
+
+ let vertex_input_state = ash::vk::PipelineVertexInputStateCreateInfo {
+ p_next: if let Some(next) = vertex_input_divisor_state.as_ref() {
+ next as *const _ as *const _
+ } else {
+ ptr::null()
+ },
+ flags: ash::vk::PipelineVertexInputStateCreateFlags::empty(),
+ vertex_binding_description_count: binding_descriptions.len() as u32,
+ p_vertex_binding_descriptions: binding_descriptions.as_ptr(),
+ vertex_attribute_description_count: attribute_descriptions.len() as u32,
+ p_vertex_attribute_descriptions: attribute_descriptions.as_ptr(),
+ ..Default::default()
+ };
+
+ if self.input_assembly.primitive_restart_enable != ash::vk::FALSE
+ && !self.input_assembly_topology.supports_primitive_restart()
+ {
+ return Err(
+ GraphicsPipelineCreationError::PrimitiveDoesntSupportPrimitiveRestart {
+ primitive: self.input_assembly_topology,
+ },
+ );
+ }
+
+ // TODO: should check from the tess eval shader instead of the input assembly
+ if let Some(ref gs) = self.geometry_shader {
+ match gs.0.ty() {
+ GraphicsShaderType::Geometry(primitives) => {
+ if !primitives.matches(self.input_assembly_topology) {
+ return Err(
+ GraphicsPipelineCreationError::TopologyNotMatchingGeometryShader,
+ );
+ }
+ }
+ _ => return Err(GraphicsPipelineCreationError::WrongShaderType),
+ }
+ }
+
+ let tessellation = match self.input_assembly_topology {
+ PrimitiveTopology::PatchList { vertices_per_patch } => {
+ if self.tessellation.is_none() {
+ return Err(GraphicsPipelineCreationError::InvalidPrimitiveTopology);
+ }
+ if vertices_per_patch
+ > device
+ .physical_device()
+ .properties()
+ .max_tessellation_patch_size
+ {
+ return Err(GraphicsPipelineCreationError::MaxTessellationPatchSizeExceeded);
+ }
+
+ Some(ash::vk::PipelineTessellationStateCreateInfo {
+ flags: ash::vk::PipelineTessellationStateCreateFlags::empty(),
+ patch_control_points: vertices_per_patch,
+ ..Default::default()
+ })
+ }
+ _ => {
+ if self.tessellation.is_some() {
+ return Err(GraphicsPipelineCreationError::InvalidPrimitiveTopology);
+ }
+
+ None
+ }
+ };
+
+ let (vp_vp, vp_sc, vp_num) = match *self.viewport.as_ref().unwrap() {
+ ViewportsState::Fixed { ref data } => (
+ data.iter()
+ .map(|e| e.0.clone().into())
+ .collect::<SmallVec<[ash::vk::Viewport; 4]>>(),
+ data.iter()
+ .map(|e| e.1.clone().into())
+ .collect::<SmallVec<[ash::vk::Rect2D; 4]>>(),
+ data.len() as u32,
+ ),
+ ViewportsState::DynamicViewports { ref scissors } => {
+ let num = scissors.len() as u32;
+ let scissors = scissors
+ .iter()
+ .map(|e| e.clone().into())
+ .collect::<SmallVec<[ash::vk::Rect2D; 4]>>();
+ dynamic_states.push(ash::vk::DynamicState::VIEWPORT);
+ (SmallVec::new(), scissors, num)
+ }
+ ViewportsState::DynamicScissors { ref viewports } => {
+ let num = viewports.len() as u32;
+ let viewports = viewports
+ .iter()
+ .map(|e| e.clone().into())
+ .collect::<SmallVec<[ash::vk::Viewport; 4]>>();
+ dynamic_states.push(ash::vk::DynamicState::SCISSOR);
+ (viewports, SmallVec::new(), num)
+ }
+ ViewportsState::Dynamic { num } => {
+ dynamic_states.push(ash::vk::DynamicState::VIEWPORT);
+ dynamic_states.push(ash::vk::DynamicState::SCISSOR);
+ (SmallVec::new(), SmallVec::new(), num)
+ }
+ };
+
+ if vp_num > 1 && !device.enabled_features().multi_viewport {
+ return Err(GraphicsPipelineCreationError::MultiViewportFeatureNotEnabled);
+ }
+
+ if vp_num > device.physical_device().properties().max_viewports {
+ return Err(GraphicsPipelineCreationError::MaxViewportsExceeded {
+ obtained: vp_num,
+ max: device.physical_device().properties().max_viewports,
+ });
+ }
+
+ for vp in vp_vp.iter() {
+ if vp.width
+ > device
+ .physical_device()
+ .properties()
+ .max_viewport_dimensions[0] as f32
+ || vp.height
+ > device
+ .physical_device()
+ .properties()
+ .max_viewport_dimensions[1] as f32
+ {
+ return Err(GraphicsPipelineCreationError::MaxViewportDimensionsExceeded);
+ }
+
+ if vp.x
+ < device
+ .physical_device()
+ .properties()
+ .viewport_bounds_range[0]
+ || vp.x + vp.width
+ > device
+ .physical_device()
+ .properties()
+ .viewport_bounds_range[1]
+ || vp.y
+ < device
+ .physical_device()
+ .properties()
+ .viewport_bounds_range[0]
+ || vp.y + vp.height
+ > device
+ .physical_device()
+ .properties()
+ .viewport_bounds_range[1]
+ {
+ return Err(GraphicsPipelineCreationError::ViewportBoundsExceeded);
+ }
+ }
+
+ let viewport_info = ash::vk::PipelineViewportStateCreateInfo {
+ flags: ash::vk::PipelineViewportStateCreateFlags::empty(),
+ viewport_count: vp_num,
+ p_viewports: if vp_vp.is_empty() {
+ ptr::null()
+ } else {
+ vp_vp.as_ptr()
+ }, // validation layer crashes if you just pass the pointer
+ scissor_count: vp_num,
+ p_scissors: if vp_sc.is_empty() {
+ ptr::null()
+ } else {
+ vp_sc.as_ptr()
+ }, // validation layer crashes if you just pass the pointer
+ ..Default::default()
+ };
+
+ if let Some(line_width) = self.raster.line_width {
+ if line_width != 1.0 && !device.enabled_features().wide_lines {
+ return Err(GraphicsPipelineCreationError::WideLinesFeatureNotEnabled);
+ }
+ } else {
+ dynamic_states.push(ash::vk::DynamicState::LINE_WIDTH);
+ }
+
+ let (db_enable, db_const, db_clamp, db_slope) = match self.raster.depth_bias {
+ DepthBiasControl::Dynamic => {
+ dynamic_states.push(ash::vk::DynamicState::DEPTH_BIAS);
+ (ash::vk::TRUE, 0.0, 0.0, 0.0)
+ }
+ DepthBiasControl::Disabled => (ash::vk::FALSE, 0.0, 0.0, 0.0),
+ DepthBiasControl::Static(bias) => {
+ if bias.clamp != 0.0 && !device.enabled_features().depth_bias_clamp {
+ return Err(GraphicsPipelineCreationError::DepthBiasClampFeatureNotEnabled);
+ }
+
+ (
+ ash::vk::TRUE,
+ bias.constant_factor,
+ bias.clamp,
+ bias.slope_factor,
+ )
+ }
+ };
+
+ if self.raster.depth_clamp && !device.enabled_features().depth_clamp {
+ return Err(GraphicsPipelineCreationError::DepthClampFeatureNotEnabled);
+ }
+
+ if self.raster.polygon_mode != PolygonMode::Fill
+ && !device.enabled_features().fill_mode_non_solid
+ {
+ return Err(GraphicsPipelineCreationError::FillModeNonSolidFeatureNotEnabled);
+ }
+
+ let rasterization = ash::vk::PipelineRasterizationStateCreateInfo {
+ flags: ash::vk::PipelineRasterizationStateCreateFlags::empty(),
+ depth_clamp_enable: if self.raster.depth_clamp {
+ ash::vk::TRUE
+ } else {
+ ash::vk::FALSE
+ },
+ rasterizer_discard_enable: if self.raster.rasterizer_discard {
+ ash::vk::TRUE
+ } else {
+ ash::vk::FALSE
+ },
+ polygon_mode: self.raster.polygon_mode.into(),
+ cull_mode: self.raster.cull_mode.into(),
+ front_face: self.raster.front_face.into(),
+ depth_bias_enable: db_enable,
+ depth_bias_constant_factor: db_const,
+ depth_bias_clamp: db_clamp,
+ depth_bias_slope_factor: db_slope,
+ line_width: self.raster.line_width.unwrap_or(1.0),
+ ..Default::default()
+ };
+
+ self.multisample.rasterization_samples = self
+ .subpass
+ .as_ref()
+ .unwrap()
+ .num_samples()
+ .unwrap_or(SampleCount::Sample1)
+ .into();
+ if self.multisample.sample_shading_enable != ash::vk::FALSE {
+ debug_assert!(
+ self.multisample.min_sample_shading >= 0.0
+ && self.multisample.min_sample_shading <= 1.0
+ );
+ if !device.enabled_features().sample_rate_shading {
+ return Err(GraphicsPipelineCreationError::SampleRateShadingFeatureNotEnabled);
+ }
+ }
+ if self.multisample.alpha_to_one_enable != ash::vk::FALSE {
+ if !device.enabled_features().alpha_to_one {
+ return Err(GraphicsPipelineCreationError::AlphaToOneFeatureNotEnabled);
+ }
+ }
+
+ let depth_stencil = {
+ let db = match self.depth_stencil.depth_bounds_test {
+ DepthBounds::Disabled => (ash::vk::FALSE, 0.0, 0.0),
+ DepthBounds::Fixed(ref range) => {
+ if !device.enabled_features().depth_bounds {
+ return Err(GraphicsPipelineCreationError::DepthBoundsFeatureNotEnabled);
+ }
+
+ (ash::vk::TRUE, range.start, range.end)
+ }
+ DepthBounds::Dynamic => {
+ if !device.enabled_features().depth_bounds {
+ return Err(GraphicsPipelineCreationError::DepthBoundsFeatureNotEnabled);
+ }
+
+ dynamic_states.push(ash::vk::DynamicState::DEPTH_BOUNDS);
+
+ (ash::vk::TRUE, 0.0, 1.0)
+ }
+ };
+
+ match (
+ self.depth_stencil.stencil_front.compare_mask,
+ self.depth_stencil.stencil_back.compare_mask,
+ ) {
+ (Some(_), Some(_)) => (),
+ (None, None) => {
+ dynamic_states.push(ash::vk::DynamicState::STENCIL_COMPARE_MASK);
+ }
+ _ => return Err(GraphicsPipelineCreationError::WrongStencilState),
+ };
+
+ match (
+ self.depth_stencil.stencil_front.write_mask,
+ self.depth_stencil.stencil_back.write_mask,
+ ) {
+ (Some(_), Some(_)) => (),
+ (None, None) => {
+ dynamic_states.push(ash::vk::DynamicState::STENCIL_WRITE_MASK);
+ }
+ _ => return Err(GraphicsPipelineCreationError::WrongStencilState),
+ };
+
+ match (
+ self.depth_stencil.stencil_front.reference,
+ self.depth_stencil.stencil_back.reference,
+ ) {
+ (Some(_), Some(_)) => (),
+ (None, None) => {
+ dynamic_states.push(ash::vk::DynamicState::STENCIL_REFERENCE);
+ }
+ _ => return Err(GraphicsPipelineCreationError::WrongStencilState),
+ };
+
+ if self.depth_stencil.depth_write
+ && !self.subpass.as_ref().unwrap().has_writable_depth()
+ {
+ return Err(GraphicsPipelineCreationError::NoDepthAttachment);
+ }
+
+ if self.depth_stencil.depth_compare != Compare::Always
+ && !self.subpass.as_ref().unwrap().has_depth()
+ {
+ return Err(GraphicsPipelineCreationError::NoDepthAttachment);
+ }
+
+ if (!self.depth_stencil.stencil_front.always_keep()
+ || !self.depth_stencil.stencil_back.always_keep())
+ && !self.subpass.as_ref().unwrap().has_stencil()
+ {
+ return Err(GraphicsPipelineCreationError::NoStencilAttachment);
+ }
+
+ // FIXME: stencil writability
+
+ ash::vk::PipelineDepthStencilStateCreateInfo {
+ flags: ash::vk::PipelineDepthStencilStateCreateFlags::empty(),
+ depth_test_enable: if !self.depth_stencil.depth_write
+ && self.depth_stencil.depth_compare == Compare::Always
+ {
+ ash::vk::FALSE
+ } else {
+ ash::vk::TRUE
+ },
+ depth_write_enable: if self.depth_stencil.depth_write {
+ ash::vk::TRUE
+ } else {
+ ash::vk::FALSE
+ },
+ depth_compare_op: self.depth_stencil.depth_compare.into(),
+ depth_bounds_test_enable: db.0,
+ stencil_test_enable: if self.depth_stencil.stencil_front.always_keep()
+ && self.depth_stencil.stencil_back.always_keep()
+ {
+ ash::vk::FALSE
+ } else {
+ ash::vk::TRUE
+ },
+ front: ash::vk::StencilOpState {
+ fail_op: self.depth_stencil.stencil_front.fail_op.into(),
+ pass_op: self.depth_stencil.stencil_front.pass_op.into(),
+ depth_fail_op: self.depth_stencil.stencil_front.depth_fail_op.into(),
+ compare_op: self.depth_stencil.stencil_front.compare.into(),
+ compare_mask: self
+ .depth_stencil
+ .stencil_front
+ .compare_mask
+ .unwrap_or(u32::MAX),
+ write_mask: self
+ .depth_stencil
+ .stencil_front
+ .write_mask
+ .unwrap_or(u32::MAX),
+ reference: self.depth_stencil.stencil_front.reference.unwrap_or(0),
+ },
+ back: ash::vk::StencilOpState {
+ fail_op: self.depth_stencil.stencil_back.fail_op.into(),
+ pass_op: self.depth_stencil.stencil_back.pass_op.into(),
+ depth_fail_op: self.depth_stencil.stencil_back.depth_fail_op.into(),
+ compare_op: self.depth_stencil.stencil_back.compare.into(),
+ compare_mask: self
+ .depth_stencil
+ .stencil_back
+ .compare_mask
+ .unwrap_or(u32::MAX),
+ write_mask: self
+ .depth_stencil
+ .stencil_back
+ .write_mask
+ .unwrap_or(u32::MAX),
+ reference: self.depth_stencil.stencil_back.reference.unwrap_or(0),
+ },
+ min_depth_bounds: db.1,
+ max_depth_bounds: db.2,
+ ..Default::default()
+ }
+ };
+
+ let blend_atch: SmallVec<[ash::vk::PipelineColorBlendAttachmentState; 8]> = {
+ let num_atch = self.subpass.as_ref().unwrap().num_color_attachments();
+
+ match self.blend.attachments {
+ AttachmentsBlend::Collective(blend) => {
+ (0..num_atch).map(|_| blend.clone().into()).collect()
+ }
+ AttachmentsBlend::Individual(blend) => {
+ if blend.len() != num_atch as usize {
+ return Err(
+ GraphicsPipelineCreationError::MismatchBlendingAttachmentsCount,
+ );
+ }
+
+ if !device.enabled_features().independent_blend {
+ return Err(
+ GraphicsPipelineCreationError::IndependentBlendFeatureNotEnabled,
+ );
+ }
+
+ blend.iter().map(|b| b.clone().into()).collect()
+ }
+ }
+ };
+
+ let blend = ash::vk::PipelineColorBlendStateCreateInfo {
+ flags: ash::vk::PipelineColorBlendStateCreateFlags::empty(),
+ logic_op_enable: if self.blend.logic_op.is_some() {
+ if !device.enabled_features().logic_op {
+ return Err(GraphicsPipelineCreationError::LogicOpFeatureNotEnabled);
+ }
+ ash::vk::TRUE
+ } else {
+ ash::vk::FALSE
+ },
+ logic_op: self.blend.logic_op.unwrap_or(Default::default()).into(),
+ attachment_count: blend_atch.len() as u32,
+ p_attachments: blend_atch.as_ptr(),
+ blend_constants: if let Some(c) = self.blend.blend_constants {
+ c
+ } else {
+ dynamic_states.push(ash::vk::DynamicState::BLEND_CONSTANTS);
+ [0.0, 0.0, 0.0, 0.0]
+ },
+ ..Default::default()
+ };
+
+ let dynamic_states = if !dynamic_states.is_empty() {
+ Some(ash::vk::PipelineDynamicStateCreateInfo {
+ flags: ash::vk::PipelineDynamicStateCreateFlags::empty(),
+ dynamic_state_count: dynamic_states.len() as u32,
+ p_dynamic_states: dynamic_states.as_ptr(),
+ ..Default::default()
+ })
+ } else {
+ None
+ };
+
+ if let Some(multiview) = self
+ .subpass
+ .as_ref()
+ .unwrap()
+ .render_pass()
+ .desc()
+ .multiview()
+ .as_ref()
+ {
+ if multiview.used_layer_count() > 0 {
+ if self.geometry_shader.is_some()
+ && !device
+ .physical_device()
+ .supported_features()
+ .multiview_geometry_shader
+ {
+ return Err(GraphicsPipelineCreationError::MultiviewGeometryShaderNotSupported);
+ }
+
+ if self.tessellation.is_some()
+ && !device
+ .physical_device()
+ .supported_features()
+ .multiview_tessellation_shader
+ {
+ return Err(
+ GraphicsPipelineCreationError::MultiviewTessellationShaderNotSupported,
+ );
+ }
+ }
+ }
+
+ let pipeline = unsafe {
+ let infos = ash::vk::GraphicsPipelineCreateInfo {
+ flags: ash::vk::PipelineCreateFlags::empty(), // TODO: some flags are available but none are critical
+ stage_count: stages.len() as u32,
+ p_stages: stages.as_ptr(),
+ p_vertex_input_state: &vertex_input_state,
+ p_input_assembly_state: &self.input_assembly,
+ p_tessellation_state: tessellation
+ .as_ref()
+ .map(|t| t as *const _)
+ .unwrap_or(ptr::null()),
+ p_viewport_state: &viewport_info,
+ p_rasterization_state: &rasterization,
+ p_multisample_state: &self.multisample,
+ p_depth_stencil_state: &depth_stencil,
+ p_color_blend_state: &blend,
+ p_dynamic_state: dynamic_states
+ .as_ref()
+ .map(|s| s as *const _)
+ .unwrap_or(ptr::null()),
+ layout: pipeline_layout.internal_object(),
+ render_pass: self
+ .subpass
+ .as_ref()
+ .unwrap()
+ .render_pass()
+ .inner()
+ .internal_object(),
+ subpass: self.subpass.as_ref().unwrap().index(),
+ base_pipeline_handle: ash::vk::Pipeline::null(), // TODO:
+ base_pipeline_index: -1, // TODO:
+ ..Default::default()
+ };
+
+ let cache_handle = match self.cache {
+ Some(cache) => cache.internal_object(),
+ None => ash::vk::PipelineCache::null(),
+ };
+
+ let mut output = MaybeUninit::uninit();
+ check_errors(fns.v1_0.create_graphics_pipelines(
+ device.internal_object(),
+ cache_handle,
+ 1,
+ &infos,
+ ptr::null(),
+ output.as_mut_ptr(),
+ ))?;
+ output.assume_init()
+ };
+
+ // Some drivers return `VK_SUCCESS` but provide a null handle if they
+ // fail to create the pipeline (due to invalid shaders, etc)
+ // This check ensures that we don't create an invalid `GraphicsPipeline` instance
+ if pipeline == ash::vk::Pipeline::null() {
+ panic!("vkCreateGraphicsPipelines provided a NULL handle");
+ }
+
+ Ok(GraphicsPipeline {
+ inner: GraphicsPipelineInner {
+ device: device.clone(),
+ pipeline,
+ },
+ layout: pipeline_layout,
+ subpass: self.subpass.take().unwrap(),
+ vertex_definition: self.vertex_definition,
+ vertex_input,
+
+ dynamic_line_width: self.raster.line_width.is_none(),
+ dynamic_viewport: self.viewport.as_ref().unwrap().dynamic_viewports(),
+ dynamic_scissor: self.viewport.as_ref().unwrap().dynamic_scissors(),
+ dynamic_depth_bias: self.raster.depth_bias.is_dynamic(),
+ dynamic_depth_bounds: self.depth_stencil.depth_bounds_test.is_dynamic(),
+ dynamic_stencil_compare_mask: self.depth_stencil.stencil_back.compare_mask.is_none(),
+ dynamic_stencil_write_mask: self.depth_stencil.stencil_back.write_mask.is_none(),
+ dynamic_stencil_reference: self.depth_stencil.stencil_back.reference.is_none(),
+ dynamic_blend_constants: self.blend.blend_constants.is_none(),
+
+ num_viewports: self.viewport.as_ref().unwrap().num_viewports(),
+ })
+ }
+
+ // TODO: add build_with_cache method
+}
+
+impl<'vs, 'tcs, 'tes, 'gs, 'fs, Vdef, Vss, Tcss, Tess, Gss, Fss>
+ GraphicsPipelineBuilder<'vs, 'tcs, 'tes, 'gs, 'fs, Vdef, Vss, Tcss, Tess, Gss, Fss>
+{
+ // TODO: add pipeline derivate system
+
+ /// Sets the vertex input.
+ #[inline]
+ pub fn vertex_input<T>(
+ self,
+ vertex_definition: T,
+ ) -> GraphicsPipelineBuilder<'vs, 'tcs, 'tes, 'gs, 'fs, T, Vss, Tcss, Tess, Gss, Fss> {
+ GraphicsPipelineBuilder {
+ vertex_definition,
+ vertex_shader: self.vertex_shader,
+ input_assembly: self.input_assembly,
+ input_assembly_topology: self.input_assembly_topology,
+ tessellation: self.tessellation,
+ geometry_shader: self.geometry_shader,
+ viewport: self.viewport,
+ raster: self.raster,
+ multisample: self.multisample,
+ fragment_shader: self.fragment_shader,
+ depth_stencil: self.depth_stencil,
+ blend: self.blend,
+ subpass: self.subpass,
+ cache: self.cache,
+ }
+ }
+
+ /// Sets the vertex input to a single vertex buffer.
+ ///
+ /// You will most likely need to explicitly specify the template parameter to the type of a
+ /// vertex.
+ #[inline]
+ pub fn vertex_input_single_buffer<V: Vertex>(
+ self,
+ ) -> GraphicsPipelineBuilder<
+ 'vs,
+ 'tcs,
+ 'tes,
+ 'gs,
+ 'fs,
+ BuffersDefinition,
+ Vss,
+ Tcss,
+ Tess,
+ Gss,
+ Fss,
+ > {
+ self.vertex_input(BuffersDefinition::new().vertex::<V>())
+ }
+
+ /// Sets the vertex shader to use.
+ // TODO: correct specialization constants
+ #[inline]
+ pub fn vertex_shader<'vs2, Vss2>(
+ self,
+ shader: GraphicsEntryPoint<'vs2>,
+ specialization_constants: Vss2,
+ ) -> GraphicsPipelineBuilder<'vs2, 'tcs, 'tes, 'gs, 'fs, Vdef, Vss2, Tcss, Tess, Gss, Fss>
+ where
+ Vss2: SpecializationConstants,
+ {
+ GraphicsPipelineBuilder {
+ vertex_definition: self.vertex_definition,
+ vertex_shader: Some((shader, specialization_constants)),
+ input_assembly: self.input_assembly,
+ input_assembly_topology: self.input_assembly_topology,
+ tessellation: self.tessellation,
+ geometry_shader: self.geometry_shader,
+ viewport: self.viewport,
+ raster: self.raster,
+ multisample: self.multisample,
+ fragment_shader: self.fragment_shader,
+ depth_stencil: self.depth_stencil,
+ blend: self.blend,
+ subpass: self.subpass,
+ cache: self.cache,
+ }
+ }
+
+ /// Sets whether primitive restart if enabled.
+ #[inline]
+ pub fn primitive_restart(mut self, enabled: bool) -> Self {
+ self.input_assembly.primitive_restart_enable = if enabled {
+ ash::vk::TRUE
+ } else {
+ ash::vk::FALSE
+ };
+
+ self
+ }
+
+ /// Sets the topology of the primitives that are expected by the pipeline.
+ #[inline]
+ pub fn primitive_topology(mut self, topology: PrimitiveTopology) -> Self {
+ self.input_assembly_topology = topology;
+ self.input_assembly.topology = topology.into();
+ self
+ }
+
+ /// Sets the topology of the primitives to a list of points.
+ ///
+ /// > **Note**: This is equivalent to
+ /// > `self.primitive_topology(PrimitiveTopology::PointList)`.
+ #[inline]
+ pub fn point_list(self) -> Self {
+ self.primitive_topology(PrimitiveTopology::PointList)
+ }
+
+ /// Sets the topology of the primitives to a list of lines.
+ ///
+ /// > **Note**: This is equivalent to
+ /// > `self.primitive_topology(PrimitiveTopology::LineList)`.
+ #[inline]
+ pub fn line_list(self) -> Self {
+ self.primitive_topology(PrimitiveTopology::LineList)
+ }
+
+ /// Sets the topology of the primitives to a line strip.
+ ///
+ /// > **Note**: This is equivalent to
+ /// > `self.primitive_topology(PrimitiveTopology::LineStrip)`.
+ #[inline]
+ pub fn line_strip(self) -> Self {
+ self.primitive_topology(PrimitiveTopology::LineStrip)
+ }
+
+ /// Sets the topology of the primitives to a list of triangles. Note that this is the default.
+ ///
+ /// > **Note**: This is equivalent to
+ /// > `self.primitive_topology(PrimitiveTopology::TriangleList)`.
+ #[inline]
+ pub fn triangle_list(self) -> Self {
+ self.primitive_topology(PrimitiveTopology::TriangleList)
+ }
+
+ /// Sets the topology of the primitives to a triangle strip.
+ ///
+ /// > **Note**: This is equivalent to
+ /// > `self.primitive_topology(PrimitiveTopology::TriangleStrip)`.
+ #[inline]
+ pub fn triangle_strip(self) -> Self {
+ self.primitive_topology(PrimitiveTopology::TriangleStrip)
+ }
+
+ /// Sets the topology of the primitives to a fan of triangles.
+ ///
+ /// > **Note**: This is equivalent to
+ /// > `self.primitive_topology(PrimitiveTopology::TriangleFan)`.
+ #[inline]
+ pub fn triangle_fan(self) -> Self {
+ self.primitive_topology(PrimitiveTopology::TriangleFan)
+ }
+
+ /// Sets the topology of the primitives to a list of lines with adjacency information.
+ ///
+ /// > **Note**: This is equivalent to
+ /// > `self.primitive_topology(PrimitiveTopology::LineListWithAdjacency)`.
+ #[inline]
+ pub fn line_list_with_adjacency(self) -> Self {
+ self.primitive_topology(PrimitiveTopology::LineListWithAdjacency)
+ }
+
+ /// Sets the topology of the primitives to a line strip with adjacency information.
+ ///
+ /// > **Note**: This is equivalent to
+ /// > `self.primitive_topology(PrimitiveTopology::LineStripWithAdjacency)`.
+ #[inline]
+ pub fn line_strip_with_adjacency(self) -> Self {
+ self.primitive_topology(PrimitiveTopology::LineStripWithAdjacency)
+ }
+
+ /// Sets the topology of the primitives to a list of triangles with adjacency information.
+ ///
+ /// > **Note**: This is equivalent to
+ /// > `self.primitive_topology(PrimitiveTopology::TriangleListWithAdjacency)`.
+ #[inline]
+ pub fn triangle_list_with_adjacency(self) -> Self {
+ self.primitive_topology(PrimitiveTopology::TriangleListWithAdjacency)
+ }
+
+ /// Sets the topology of the primitives to a triangle strip with adjacency information`
+ ///
+ /// > **Note**: This is equivalent to
+ /// > `self.primitive_topology(PrimitiveTopology::TriangleStripWithAdjacency)`.
+ #[inline]
+ pub fn triangle_strip_with_adjacency(self) -> Self {
+ self.primitive_topology(PrimitiveTopology::TriangleStripWithAdjacency)
+ }
+
+ /// Sets the topology of the primitives to a list of patches. Can only be used and must be used
+ /// with a tessellation shader.
+ ///
+ /// > **Note**: This is equivalent to
+ /// > `self.primitive_topology(PrimitiveTopology::PatchList { vertices_per_patch })`.
+ #[inline]
+ pub fn patch_list(self, vertices_per_patch: u32) -> Self {
+ self.primitive_topology(PrimitiveTopology::PatchList { vertices_per_patch })
+ }
+
+ /// Sets the tessellation shaders to use.
+ // TODO: correct specialization constants
+ #[inline]
+ pub fn tessellation_shaders<'tcs2, 'tes2, Tcss2, Tess2>(
+ self,
+ tessellation_control_shader: GraphicsEntryPoint<'tcs2>,
+ tessellation_control_shader_spec_constants: Tcss2,
+ tessellation_evaluation_shader: GraphicsEntryPoint<'tes2>,
+ tessellation_evaluation_shader_spec_constants: Tess2,
+ ) -> GraphicsPipelineBuilder<'vs, 'tcs2, 'tes2, 'gs, 'fs, Vdef, Vss, Tcss2, Tess2, Gss, Fss>
+ where
+ Tcss2: SpecializationConstants,
+ Tess2: SpecializationConstants,
+ {
+ GraphicsPipelineBuilder {
+ vertex_definition: self.vertex_definition,
+ vertex_shader: self.vertex_shader,
+ input_assembly: self.input_assembly,
+ input_assembly_topology: self.input_assembly_topology,
+ tessellation: Some(TessInfo {
+ tessellation_control_shader: (
+ tessellation_control_shader,
+ tessellation_control_shader_spec_constants,
+ ),
+ tessellation_evaluation_shader: (
+ tessellation_evaluation_shader,
+ tessellation_evaluation_shader_spec_constants,
+ ),
+ }),
+ geometry_shader: self.geometry_shader,
+ viewport: self.viewport,
+ raster: self.raster,
+ multisample: self.multisample,
+ fragment_shader: self.fragment_shader,
+ depth_stencil: self.depth_stencil,
+ blend: self.blend,
+ subpass: self.subpass,
+ cache: self.cache,
+ }
+ }
+
+ /// Sets the tessellation shaders stage as disabled. This is the default.
+ #[inline]
+ pub fn tessellation_shaders_disabled(mut self) -> Self {
+ self.tessellation = None;
+ self
+ }
+
+ /// Sets the geometry shader to use.
+ // TODO: correct specialization constants
+ #[inline]
+ pub fn geometry_shader<'gs2, Gss2>(
+ self,
+ shader: GraphicsEntryPoint<'gs2>,
+ specialization_constants: Gss2,
+ ) -> GraphicsPipelineBuilder<'vs, 'tcs, 'tes, 'gs2, 'fs, Vdef, Vss, Tcss, Tess, Gss2, Fss>
+ where
+ Gss2: SpecializationConstants,
+ {
+ GraphicsPipelineBuilder {
+ vertex_definition: self.vertex_definition,
+ vertex_shader: self.vertex_shader,
+ input_assembly: self.input_assembly,
+ input_assembly_topology: self.input_assembly_topology,
+ tessellation: self.tessellation,
+ geometry_shader: Some((shader, specialization_constants)),
+ viewport: self.viewport,
+ raster: self.raster,
+ multisample: self.multisample,
+ fragment_shader: self.fragment_shader,
+ depth_stencil: self.depth_stencil,
+ blend: self.blend,
+ subpass: self.subpass,
+ cache: self.cache,
+ }
+ }
+
+ /// Sets the geometry shader stage as disabled. This is the default.
+ #[inline]
+ pub fn geometry_shader_disabled(mut self) -> Self {
+ self.geometry_shader = None;
+ self
+ }
+
+ /// Sets the viewports to some value, and the scissor boxes to boxes that always cover the
+ /// whole viewport.
+ #[inline]
+ pub fn viewports<I>(self, viewports: I) -> Self
+ where
+ I: IntoIterator<Item = Viewport>,
+ {
+ self.viewports_scissors(viewports.into_iter().map(|v| (v, Scissor::irrelevant())))
+ }
+
+ /// Sets the characteristics of viewports and scissor boxes in advance.
+ #[inline]
+ pub fn viewports_scissors<I>(mut self, viewports: I) -> Self
+ where
+ I: IntoIterator<Item = (Viewport, Scissor)>,
+ {
+ self.viewport = Some(ViewportsState::Fixed {
+ data: viewports.into_iter().collect(),
+ });
+ self
+ }
+
+ /// Sets the scissor boxes to some values, and viewports to dynamic. The viewports will
+ /// need to be set before drawing.
+ #[inline]
+ pub fn viewports_dynamic_scissors_fixed<I>(mut self, scissors: I) -> Self
+ where
+ I: IntoIterator<Item = Scissor>,
+ {
+ self.viewport = Some(ViewportsState::DynamicViewports {
+ scissors: scissors.into_iter().collect(),
+ });
+ self
+ }
+
+ /// Sets the viewports to dynamic, and the scissor boxes to boxes that always cover the whole
+ /// viewport. The viewports will need to be set before drawing.
+ #[inline]
+ pub fn viewports_dynamic_scissors_irrelevant(mut self, num: u32) -> Self {
+ self.viewport = Some(ViewportsState::DynamicViewports {
+ scissors: (0..num).map(|_| Scissor::irrelevant()).collect(),
+ });
+ self
+ }
+
+ /// Sets the viewports to some values, and scissor boxes to dynamic. The scissor boxes will
+ /// need to be set before drawing.
+ #[inline]
+ pub fn viewports_fixed_scissors_dynamic<I>(mut self, viewports: I) -> Self
+ where
+ I: IntoIterator<Item = Viewport>,
+ {
+ self.viewport = Some(ViewportsState::DynamicScissors {
+ viewports: viewports.into_iter().collect(),
+ });
+ self
+ }
+
+ /// Sets the viewports and scissor boxes to dynamic. They will both need to be set before
+ /// drawing.
+ #[inline]
+ pub fn viewports_scissors_dynamic(mut self, num: u32) -> Self {
+ self.viewport = Some(ViewportsState::Dynamic { num });
+ self
+ }
+
+ /// If true, then the depth value of the vertices will be clamped to the range `[0.0 ; 1.0]`.
+ /// If false, fragments whose depth is outside of this range will be discarded before the
+ /// fragment shader even runs.
+ #[inline]
+ pub fn depth_clamp(mut self, clamp: bool) -> Self {
+ self.raster.depth_clamp = clamp;
+ self
+ }
+
+ // TODO: this won't work correctly
+ /*/// Disables the fragment shader stage.
+ #[inline]
+ pub fn rasterizer_discard(mut self) -> Self {
+ self.rasterization.rasterizer_discard. = true;
+ self
+ }*/
+
+ /// Sets the front-facing faces to counter-clockwise faces. This is the default.
+ ///
+ /// Triangles whose vertices are oriented counter-clockwise on the screen will be considered
+ /// as facing their front. Otherwise they will be considered as facing their back.
+ #[inline]
+ pub fn front_face_counter_clockwise(mut self) -> Self {
+ self.raster.front_face = FrontFace::CounterClockwise;
+ self
+ }
+
+ /// Sets the front-facing faces to clockwise faces.
+ ///
+ /// Triangles whose vertices are oriented clockwise on the screen will be considered
+ /// as facing their front. Otherwise they will be considered as facing their back.
+ #[inline]
+ pub fn front_face_clockwise(mut self) -> Self {
+ self.raster.front_face = FrontFace::Clockwise;
+ self
+ }
+
+ /// Sets backface culling as disabled. This is the default.
+ #[inline]
+ pub fn cull_mode_disabled(mut self) -> Self {
+ self.raster.cull_mode = CullMode::None;
+ self
+ }
+
+ /// Sets backface culling to front faces. The front faces (as chosen with the `front_face_*`
+ /// methods) will be discarded by the GPU when drawing.
+ #[inline]
+ pub fn cull_mode_front(mut self) -> Self {
+ self.raster.cull_mode = CullMode::Front;
+ self
+ }
+
+ /// Sets backface culling to back faces. Faces that are not facing the front (as chosen with
+ /// the `front_face_*` methods) will be discarded by the GPU when drawing.
+ #[inline]
+ pub fn cull_mode_back(mut self) -> Self {
+ self.raster.cull_mode = CullMode::Back;
+ self
+ }
+
+ /// Sets backface culling to both front and back faces. All the faces will be discarded.
+ ///
+ /// > **Note**: This option exists for the sake of completeness. It has no known practical
+ /// > usage.
+ #[inline]
+ pub fn cull_mode_front_and_back(mut self) -> Self {
+ self.raster.cull_mode = CullMode::FrontAndBack;
+ self
+ }
+
+ /// Sets the polygon mode to "fill". This is the default.
+ #[inline]
+ pub fn polygon_mode_fill(mut self) -> Self {
+ self.raster.polygon_mode = PolygonMode::Fill;
+ self
+ }
+
+ /// Sets the polygon mode to "line". Triangles will each be turned into three lines.
+ #[inline]
+ pub fn polygon_mode_line(mut self) -> Self {
+ self.raster.polygon_mode = PolygonMode::Line;
+ self
+ }
+
+ /// Sets the polygon mode to "point". Triangles and lines will each be turned into three points.
+ #[inline]
+ pub fn polygon_mode_point(mut self) -> Self {
+ self.raster.polygon_mode = PolygonMode::Point;
+ self
+ }
+
+ /// Sets the width of the lines, if the GPU needs to draw lines. The default is `1.0`.
+ #[inline]
+ pub fn line_width(mut self, value: f32) -> Self {
+ self.raster.line_width = Some(value);
+ self
+ }
+
+ /// Sets the width of the lines as dynamic, which means that you will need to set this value
+ /// when drawing.
+ #[inline]
+ pub fn line_width_dynamic(mut self) -> Self {
+ self.raster.line_width = None;
+ self
+ }
+
+ // TODO: missing DepthBiasControl
+
+ /// Disables sample shading. The fragment shader will only be run once per fragment (ie. per
+ /// pixel) and not once by sample. The output will then be copied in all of the covered
+ /// samples.
+ ///
+ /// Sample shading is disabled by default.
+ #[inline]
+ pub fn sample_shading_disabled(mut self) -> Self {
+ self.multisample.sample_shading_enable = ash::vk::FALSE;
+ self
+ }
+
+ /// Enables sample shading. The fragment shader will be run once per sample at the borders of
+ /// the object you're drawing.
+ ///
+ /// Enabling sampling shading requires the `sample_rate_shading` feature to be enabled on the
+ /// device.
+ ///
+ /// The `min_fract` parameter is the minimum fraction of samples shading. For example if its
+ /// value is 0.5, then the fragment shader will run for at least half of the samples. The other
+ /// half of the samples will get their values determined automatically.
+ ///
+ /// Sample shading is disabled by default.
+ ///
+ /// # Panic
+ ///
+ /// - Panics if `min_fract` is not between 0.0 and 1.0.
+ ///
+ #[inline]
+ pub fn sample_shading_enabled(mut self, min_fract: f32) -> Self {
+ assert!(min_fract >= 0.0 && min_fract <= 1.0);
+ self.multisample.sample_shading_enable = ash::vk::TRUE;
+ self.multisample.min_sample_shading = min_fract;
+ self
+ }
+
+ // TODO: doc
+ pub fn alpha_to_coverage_disabled(mut self) -> Self {
+ self.multisample.alpha_to_coverage_enable = ash::vk::FALSE;
+ self
+ }
+
+ // TODO: doc
+ pub fn alpha_to_coverage_enabled(mut self) -> Self {
+ self.multisample.alpha_to_coverage_enable = ash::vk::TRUE;
+ self
+ }
+
+ /// Disables alpha-to-one.
+ ///
+ /// Alpha-to-one is disabled by default.
+ #[inline]
+ pub fn alpha_to_one_disabled(mut self) -> Self {
+ self.multisample.alpha_to_one_enable = ash::vk::FALSE;
+ self
+ }
+
+ /// Enables alpha-to-one. The alpha component of the first color output of the fragment shader
+ /// will be replaced by the value `1.0`.
+ ///
+ /// Enabling alpha-to-one requires the `alpha_to_one` feature to be enabled on the device.
+ ///
+ /// Alpha-to-one is disabled by default.
+ #[inline]
+ pub fn alpha_to_one_enabled(mut self) -> Self {
+ self.multisample.alpha_to_one_enable = ash::vk::TRUE;
+ self
+ }
+
+ // TODO: rasterizationSamples and pSampleMask
+
+ /// Sets the fragment shader to use.
+ ///
+ /// The fragment shader is run once for each pixel that is covered by each primitive.
+ // TODO: correct specialization constants
+ #[inline]
+ pub fn fragment_shader<'fs2, Fss2>(
+ self,
+ shader: GraphicsEntryPoint<'fs2>,
+ specialization_constants: Fss2,
+ ) -> GraphicsPipelineBuilder<'vs, 'tcs, 'tes, 'gs, 'fs2, Vdef, Vss, Tcss, Tess, Gss, Fss2>
+ where
+ Fss2: SpecializationConstants,
+ {
+ GraphicsPipelineBuilder {
+ vertex_definition: self.vertex_definition,
+ vertex_shader: self.vertex_shader,
+ input_assembly: self.input_assembly,
+ input_assembly_topology: self.input_assembly_topology,
+ tessellation: self.tessellation,
+ geometry_shader: self.geometry_shader,
+ viewport: self.viewport,
+ raster: self.raster,
+ multisample: self.multisample,
+ fragment_shader: Some((shader, specialization_constants)),
+ depth_stencil: self.depth_stencil,
+ blend: self.blend,
+ subpass: self.subpass,
+ cache: self.cache,
+ }
+ }
+
+ /// Sets the depth/stencil configuration. This function may be removed in the future.
+ #[inline]
+ pub fn depth_stencil(mut self, depth_stencil: DepthStencil) -> Self {
+ self.depth_stencil = depth_stencil;
+ self
+ }
+
+ /// Sets the depth/stencil tests as disabled.
+ ///
+ /// > **Note**: This is a shortcut for all the other `depth_*` and `depth_stencil_*` methods
+ /// > of the builder.
+ #[inline]
+ pub fn depth_stencil_disabled(mut self) -> Self {
+ self.depth_stencil = DepthStencil::disabled();
+ self
+ }
+
+ /// Sets the depth/stencil tests as a simple depth test and no stencil test.
+ ///
+ /// > **Note**: This is a shortcut for setting the depth test to `Less`, the depth write Into
+ /// > ` true` and disable the stencil test.
+ #[inline]
+ pub fn depth_stencil_simple_depth(mut self) -> Self {
+ self.depth_stencil = DepthStencil::simple_depth_test();
+ self
+ }
+
+ /// Sets whether the depth buffer will be written.
+ #[inline]
+ pub fn depth_write(mut self, write: bool) -> Self {
+ self.depth_stencil.depth_write = write;
+ self
+ }
+
+ // TODO: missing tons of depth-stencil stuff
+
+ #[inline]
+ pub fn blend_collective(mut self, blend: AttachmentBlend) -> Self {
+ self.blend.attachments = AttachmentsBlend::Collective(blend);
+ self
+ }
+
+ #[inline]
+ pub fn blend_individual<I>(mut self, blend: I) -> Self
+ where
+ I: IntoIterator<Item = AttachmentBlend>,
+ {
+ self.blend.attachments = AttachmentsBlend::Individual(blend.into_iter().collect());
+ self
+ }
+
+ /// Each fragment shader output will have its value directly written to the framebuffer
+ /// attachment. This is the default.
+ #[inline]
+ pub fn blend_pass_through(self) -> Self {
+ self.blend_collective(AttachmentBlend::pass_through())
+ }
+
+ #[inline]
+ pub fn blend_alpha_blending(self) -> Self {
+ self.blend_collective(AttachmentBlend::alpha_blending())
+ }
+
+ #[inline]
+ pub fn blend_logic_op(mut self, logic_op: LogicOp) -> Self {
+ self.blend.logic_op = Some(logic_op);
+ self
+ }
+
+ /// Sets the logic operation as disabled. This is the default.
+ #[inline]
+ pub fn blend_logic_op_disabled(mut self) -> Self {
+ self.blend.logic_op = None;
+ self
+ }
+
+ /// Sets the blend constant. The default is `[0.0, 0.0, 0.0, 0.0]`.
+ ///
+ /// The blend constant is used for some blending calculations. It is irrelevant otherwise.
+ #[inline]
+ pub fn blend_constants(mut self, constants: [f32; 4]) -> Self {
+ self.blend.blend_constants = Some(constants);
+ self
+ }
+
+ /// Sets the blend constant value as dynamic. Its value will need to be set before drawing.
+ ///
+ /// The blend constant is used for some blending calculations. It is irrelevant otherwise.
+ #[inline]
+ pub fn blend_constants_dynamic(mut self) -> Self {
+ self.blend.blend_constants = None;
+ self
+ }
+
+ /// Sets the render pass subpass to use.
+ #[inline]
+ pub fn render_pass(self, subpass: Subpass) -> Self {
+ GraphicsPipelineBuilder {
+ vertex_definition: self.vertex_definition,
+ vertex_shader: self.vertex_shader,
+ input_assembly: self.input_assembly,
+ input_assembly_topology: self.input_assembly_topology,
+ tessellation: self.tessellation,
+ geometry_shader: self.geometry_shader,
+ viewport: self.viewport,
+ raster: self.raster,
+ multisample: self.multisample,
+ fragment_shader: self.fragment_shader,
+ depth_stencil: self.depth_stencil,
+ blend: self.blend,
+ subpass: Some(subpass),
+ cache: self.cache,
+ }
+ }
+
+ /// Enable caching of this pipeline via a PipelineCache object.
+ ///
+ /// If this pipeline already exists in the cache it will be used, if this is a new
+ /// pipeline it will be inserted into the cache. The implementation handles the
+ /// PipelineCache.
+ #[inline]
+ pub fn build_with_cache(mut self, pipeline_cache: Arc<PipelineCache>) -> Self {
+ self.cache = Some(pipeline_cache);
+ self
+ }
+}
+
+impl<'vs, 'tcs, 'tes, 'gs, 'fs, Vdef, Vss, Tcss, Tess, Gss, Fss> Clone
+ for GraphicsPipelineBuilder<'vs, 'tcs, 'tes, 'gs, 'fs, Vdef, Vss, Tcss, Tess, Gss, Fss>
+where
+ Vdef: Clone,
+ Vss: Clone,
+ Tcss: Clone,
+ Tess: Clone,
+ Gss: Clone,
+ Fss: Clone,
+{
+ fn clone(&self) -> Self {
+ GraphicsPipelineBuilder {
+ vertex_definition: self.vertex_definition.clone(),
+ vertex_shader: self.vertex_shader.clone(),
+ input_assembly: unsafe { ptr::read(&self.input_assembly) },
+ input_assembly_topology: self.input_assembly_topology,
+ tessellation: self.tessellation.clone(),
+ geometry_shader: self.geometry_shader.clone(),
+ viewport: self.viewport.clone(),
+ raster: self.raster.clone(),
+ multisample: self.multisample,
+ fragment_shader: self.fragment_shader.clone(),
+ depth_stencil: self.depth_stencil.clone(),
+ blend: self.blend.clone(),
+ subpass: self.subpass.clone(),
+ cache: self.cache.clone(),
+ }
+ }
+}