blob: 5f9a770712a21b6e75976bad64b16b0e87afe09b [file] [log] [blame]
// 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(),
}
}
}