blob: 8fc7bcf6d165871d63f24b2e82345134ab08e60f [file] [log] [blame] [edit]
// Copyright (c) 2016 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.
//! The layout of descriptor sets and push constants used by a pipeline.
//!
//! # Overview
//!
//! The layout itself only *describes* the descriptors and push constants, and does not contain
//! their content itself. Instead, you can think of it as a `struct` definition that states which
//! members there are, what types they have, and in what order.
//! One could imagine a Rust definition somewhat like this:
//!
//! ```text
//! #[repr(C)]
//! struct MyPipelineLayout {
//! push_constants: Pc,
//! descriptor_set0: Ds0,
//! descriptor_set1: Ds1,
//! descriptor_set2: Ds2,
//! descriptor_set3: Ds3,
//! }
//! ```
//!
//! Of course, a pipeline layout is created at runtime, unlike a Rust type.
//!
//! # Layout compatibility
//!
//! When binding descriptor sets or setting push constants, you must provide a pipeline layout.
//! This layout is used to decide where in memory Vulkan should write the new data. The
//! descriptor sets and push constants can later be read by dispatch or draw calls, but only if
//! the bound pipeline being used for the command has a layout that is *compatible* with the layout
//! that was used to bind the resources.
//!
//! *Compatible* means that the pipeline layout must be the same object, or a different layout in
//! which the push constant ranges and descriptor set layouts were be identically defined.
//! However, Vulkan allows for partial compatibility as well. In the `struct` analogy used above,
//! one could imagine that using a different definition would leave some members with the same
//! offset and size within the struct as in the old definition, while others are no longer
//! positioned correctly. For example, if a new, incompatible type were used for `Ds1`, then the
//! `descriptor_set1`, `descriptor_set2` and `descriptor_set3` members would no longer be correct,
//! but `descriptor_set0` and `push_constants` would remain accessible in the new layout.
//! Because of this behaviour, the following rules apply to compatibility between the layouts used
//! in subsequent descriptor set binding calls:
//!
//! - An incompatible definition of `Pc` invalidates all bound descriptor sets.
//! - An incompatible definition of `DsN` invalidates all bound descriptor sets *N* and higher.
//! - If *N* is the highest set being assigned in a bind command, and it and all lower sets
//! have compatible definitions, including the push constants, then descriptor sets above *N*
//! remain valid.
//!
//! [`SyncCommandBufferBuilder`](crate::command_buffer::synced::SyncCommandBufferBuilder) keeps
//! track of this state and will automatically remove descriptor sets that have been invalidated
//! by incompatible layouts in subsequent binding commands.
//!
//! # Creating pipeline layouts
//!
//! A pipeline layout is a Vulkan object type, represented in Vulkano with the `PipelineLayout`
//! type. Each pipeline that you create holds a pipeline layout object.
use crate::{
descriptor_set::layout::{DescriptorRequirementsNotMet, DescriptorSetLayout, DescriptorType},
device::{Device, DeviceOwned},
macros::impl_id_counter,
shader::{DescriptorBindingRequirements, ShaderStages},
OomError, RequirementNotMet, RequiresOneOf, VulkanError, VulkanObject,
};
use smallvec::SmallVec;
use std::{
error::Error,
fmt::{Display, Error as FmtError, Formatter},
mem::MaybeUninit,
num::NonZeroU64,
ptr,
sync::Arc,
};
/// Describes the layout of descriptor sets and push constants that are made available to shaders.
#[derive(Debug)]
pub struct PipelineLayout {
handle: ash::vk::PipelineLayout,
device: Arc<Device>,
id: NonZeroU64,
set_layouts: Vec<Arc<DescriptorSetLayout>>,
push_constant_ranges: Vec<PushConstantRange>,
push_constant_ranges_disjoint: Vec<PushConstantRange>,
}
impl PipelineLayout {
/// Creates a new `PipelineLayout`.
///
/// # Panics
///
/// - Panics if an element of `create_info.push_constant_ranges` has an empty `stages` value.
/// - Panics if an element of `create_info.push_constant_ranges` has an `offset` or `size`
/// that's not divisible by 4.
/// - Panics if an element of `create_info.push_constant_ranges` has an `size` of zero.
pub fn new(
device: Arc<Device>,
mut create_info: PipelineLayoutCreateInfo,
) -> Result<Arc<PipelineLayout>, PipelineLayoutCreationError> {
Self::validate(&device, &mut create_info)?;
let handle = unsafe { Self::create(&device, &create_info)? };
let PipelineLayoutCreateInfo {
set_layouts,
mut push_constant_ranges,
_ne: _,
} = create_info;
// Sort the ranges for the purpose of comparing for equality.
// The stage mask is guaranteed to be unique, so it's a suitable sorting key.
push_constant_ranges.sort_unstable_by_key(|range| {
(
range.offset,
range.size,
ash::vk::ShaderStageFlags::from(range.stages),
)
});
let push_constant_ranges_disjoint =
Self::create_push_constant_ranges_disjoint(&push_constant_ranges);
Ok(Arc::new(PipelineLayout {
handle,
device,
id: Self::next_id(),
set_layouts,
push_constant_ranges,
push_constant_ranges_disjoint,
}))
}
fn create_push_constant_ranges_disjoint(
push_constant_ranges: &Vec<PushConstantRange>,
) -> Vec<PushConstantRange> {
let mut push_constant_ranges_disjoint: Vec<PushConstantRange> =
Vec::with_capacity(push_constant_ranges.len());
if !push_constant_ranges.is_empty() {
let mut min_offset = push_constant_ranges[0].offset;
loop {
let mut max_offset = u32::MAX;
let mut stages = ShaderStages::empty();
for range in push_constant_ranges.iter() {
// new start (begin next time from it)
if range.offset > min_offset {
max_offset = max_offset.min(range.offset);
break;
} else if range.offset + range.size > min_offset {
// inside the range, include the stage
// use the minimum of the end of all ranges that are overlapping
max_offset = max_offset.min(range.offset + range.size);
stages |= range.stages;
}
}
// finished all stages
if stages.is_empty() {
break;
}
push_constant_ranges_disjoint.push(PushConstantRange {
stages,
offset: min_offset,
size: max_offset - min_offset,
});
// prepare for next range
min_offset = max_offset;
}
}
push_constant_ranges_disjoint
}
fn validate(
device: &Device,
create_info: &mut PipelineLayoutCreateInfo,
) -> Result<(), PipelineLayoutCreationError> {
let &mut PipelineLayoutCreateInfo {
ref set_layouts,
ref push_constant_ranges,
_ne: _,
} = create_info;
let properties = device.physical_device().properties();
/* Check descriptor set layouts */
// VUID-VkPipelineLayoutCreateInfo-setLayoutCount-00286
if set_layouts.len() > properties.max_bound_descriptor_sets as usize {
return Err(
PipelineLayoutCreationError::MaxBoundDescriptorSetsExceeded {
provided: set_layouts.len() as u32,
max_supported: properties.max_bound_descriptor_sets,
},
);
}
{
let mut num_resources = Counter::default();
let mut num_samplers = Counter::default();
let mut num_uniform_buffers = Counter::default();
let mut num_uniform_buffers_dynamic = 0;
let mut num_storage_buffers = Counter::default();
let mut num_storage_buffers_dynamic = 0;
let mut num_sampled_images = Counter::default();
let mut num_storage_images = Counter::default();
let mut num_input_attachments = Counter::default();
let mut push_descriptor_set = None;
for (set_num, set_layout) in set_layouts.iter().enumerate() {
let set_num = set_num as u32;
if set_layout.push_descriptor() {
// VUID-VkPipelineLayoutCreateInfo-pSetLayouts-00293
if push_descriptor_set.is_some() {
return Err(PipelineLayoutCreationError::SetLayoutsPushDescriptorMultiple);
} else {
push_descriptor_set = Some(set_num);
}
}
for layout_binding in set_layout.bindings().values() {
num_resources.increment(layout_binding.descriptor_count, layout_binding.stages);
match layout_binding.descriptor_type {
DescriptorType::Sampler => {
num_samplers
.increment(layout_binding.descriptor_count, layout_binding.stages);
}
DescriptorType::CombinedImageSampler => {
num_samplers
.increment(layout_binding.descriptor_count, layout_binding.stages);
num_sampled_images
.increment(layout_binding.descriptor_count, layout_binding.stages);
}
DescriptorType::SampledImage | DescriptorType::UniformTexelBuffer => {
num_sampled_images
.increment(layout_binding.descriptor_count, layout_binding.stages);
}
DescriptorType::StorageImage | DescriptorType::StorageTexelBuffer => {
num_storage_images
.increment(layout_binding.descriptor_count, layout_binding.stages);
}
DescriptorType::UniformBuffer => {
num_uniform_buffers
.increment(layout_binding.descriptor_count, layout_binding.stages);
}
DescriptorType::UniformBufferDynamic => {
num_uniform_buffers
.increment(layout_binding.descriptor_count, layout_binding.stages);
num_uniform_buffers_dynamic += 1;
}
DescriptorType::StorageBuffer => {
num_storage_buffers
.increment(layout_binding.descriptor_count, layout_binding.stages);
}
DescriptorType::StorageBufferDynamic => {
num_storage_buffers
.increment(layout_binding.descriptor_count, layout_binding.stages);
num_storage_buffers_dynamic += 1;
}
DescriptorType::InputAttachment => {
num_input_attachments
.increment(layout_binding.descriptor_count, layout_binding.stages);
}
}
}
}
if num_resources.max_per_stage() > properties.max_per_stage_resources {
return Err(PipelineLayoutCreationError::MaxPerStageResourcesExceeded {
provided: num_resources.max_per_stage(),
max_supported: properties.max_per_stage_resources,
});
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03016
if num_samplers.max_per_stage() > properties.max_per_stage_descriptor_samplers {
return Err(
PipelineLayoutCreationError::MaxPerStageDescriptorSamplersExceeded {
provided: num_samplers.max_per_stage(),
max_supported: properties.max_per_stage_descriptor_samplers,
},
);
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03017
if num_uniform_buffers.max_per_stage()
> properties.max_per_stage_descriptor_uniform_buffers
{
return Err(
PipelineLayoutCreationError::MaxPerStageDescriptorUniformBuffersExceeded {
provided: num_uniform_buffers.max_per_stage(),
max_supported: properties.max_per_stage_descriptor_uniform_buffers,
},
);
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03018
if num_storage_buffers.max_per_stage()
> properties.max_per_stage_descriptor_storage_buffers
{
return Err(
PipelineLayoutCreationError::MaxPerStageDescriptorStorageBuffersExceeded {
provided: num_storage_buffers.max_per_stage(),
max_supported: properties.max_per_stage_descriptor_storage_buffers,
},
);
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03019
if num_sampled_images.max_per_stage()
> properties.max_per_stage_descriptor_sampled_images
{
return Err(
PipelineLayoutCreationError::MaxPerStageDescriptorSampledImagesExceeded {
provided: num_sampled_images.max_per_stage(),
max_supported: properties.max_per_stage_descriptor_sampled_images,
},
);
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03020
if num_storage_images.max_per_stage()
> properties.max_per_stage_descriptor_storage_images
{
return Err(
PipelineLayoutCreationError::MaxPerStageDescriptorStorageImagesExceeded {
provided: num_storage_images.max_per_stage(),
max_supported: properties.max_per_stage_descriptor_storage_images,
},
);
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03021
if num_input_attachments.max_per_stage()
> properties.max_per_stage_descriptor_input_attachments
{
return Err(
PipelineLayoutCreationError::MaxPerStageDescriptorInputAttachmentsExceeded {
provided: num_input_attachments.max_per_stage(),
max_supported: properties.max_per_stage_descriptor_input_attachments,
},
);
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03028
if num_samplers.total > properties.max_descriptor_set_samplers {
return Err(
PipelineLayoutCreationError::MaxDescriptorSetSamplersExceeded {
provided: num_samplers.total,
max_supported: properties.max_descriptor_set_samplers,
},
);
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03029
if num_uniform_buffers.total > properties.max_descriptor_set_uniform_buffers {
return Err(
PipelineLayoutCreationError::MaxDescriptorSetUniformBuffersExceeded {
provided: num_uniform_buffers.total,
max_supported: properties.max_descriptor_set_uniform_buffers,
},
);
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03030
if num_uniform_buffers_dynamic > properties.max_descriptor_set_uniform_buffers_dynamic {
return Err(
PipelineLayoutCreationError::MaxDescriptorSetUniformBuffersDynamicExceeded {
provided: num_uniform_buffers_dynamic,
max_supported: properties.max_descriptor_set_uniform_buffers_dynamic,
},
);
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03031
if num_storage_buffers.total > properties.max_descriptor_set_storage_buffers {
return Err(
PipelineLayoutCreationError::MaxDescriptorSetStorageBuffersExceeded {
provided: num_storage_buffers.total,
max_supported: properties.max_descriptor_set_storage_buffers,
},
);
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03032
if num_storage_buffers_dynamic > properties.max_descriptor_set_storage_buffers_dynamic {
return Err(
PipelineLayoutCreationError::MaxDescriptorSetStorageBuffersDynamicExceeded {
provided: num_storage_buffers_dynamic,
max_supported: properties.max_descriptor_set_storage_buffers_dynamic,
},
);
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03033
if num_sampled_images.total > properties.max_descriptor_set_sampled_images {
return Err(
PipelineLayoutCreationError::MaxDescriptorSetSampledImagesExceeded {
provided: num_sampled_images.total,
max_supported: properties.max_descriptor_set_sampled_images,
},
);
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03034
if num_storage_images.total > properties.max_descriptor_set_storage_images {
return Err(
PipelineLayoutCreationError::MaxDescriptorSetStorageImagesExceeded {
provided: num_storage_images.total,
max_supported: properties.max_descriptor_set_storage_images,
},
);
}
// VUID-VkPipelineLayoutCreateInfo-descriptorType-03035
if num_input_attachments.total > properties.max_descriptor_set_input_attachments {
return Err(
PipelineLayoutCreationError::MaxDescriptorSetInputAttachmentsExceeded {
provided: num_input_attachments.total,
max_supported: properties.max_descriptor_set_input_attachments,
},
);
}
}
/* Check push constant ranges */
push_constant_ranges
.iter()
.try_fold(ShaderStages::empty(), |total, range| {
let &PushConstantRange {
stages,
offset,
size,
} = range;
// VUID-VkPushConstantRange-stageFlags-parameter
stages.validate_device(device)?;
// VUID-VkPushConstantRange-stageFlags-requiredbitmask
assert!(!stages.is_empty());
// VUID-VkPushConstantRange-offset-00295
assert!(offset % 4 == 0);
// VUID-VkPushConstantRange-size-00296
assert!(size != 0);
// VUID-VkPushConstantRange-size-00297
assert!(size % 4 == 0);
// VUID-VkPushConstantRange-offset-00294
// VUID-VkPushConstantRange-size-00298
if offset + size > properties.max_push_constants_size {
return Err(PipelineLayoutCreationError::MaxPushConstantsSizeExceeded {
provided: offset + size,
max_supported: properties.max_push_constants_size,
});
}
// VUID-VkPipelineLayoutCreateInfo-pPushConstantRanges-00292
if !(total & stages).is_empty() {
return Err(PipelineLayoutCreationError::PushConstantRangesStageMultiple);
}
Ok(total | stages)
})?;
Ok(())
}
unsafe fn create(
device: &Device,
create_info: &PipelineLayoutCreateInfo,
) -> Result<ash::vk::PipelineLayout, PipelineLayoutCreationError> {
let PipelineLayoutCreateInfo {
set_layouts,
push_constant_ranges,
_ne: _,
} = create_info;
let set_layouts: SmallVec<[_; 4]> = set_layouts.iter().map(|l| l.handle()).collect();
let push_constant_ranges: SmallVec<[_; 4]> = push_constant_ranges
.iter()
.map(|range| ash::vk::PushConstantRange {
stage_flags: range.stages.into(),
offset: range.offset,
size: range.size,
})
.collect();
let create_info = ash::vk::PipelineLayoutCreateInfo {
flags: ash::vk::PipelineLayoutCreateFlags::empty(),
set_layout_count: set_layouts.len() as u32,
p_set_layouts: set_layouts.as_ptr(),
push_constant_range_count: push_constant_ranges.len() as u32,
p_push_constant_ranges: push_constant_ranges.as_ptr(),
..Default::default()
};
let handle = {
let fns = device.fns();
let mut output = MaybeUninit::uninit();
(fns.v1_0.create_pipeline_layout)(
device.handle(),
&create_info,
ptr::null(),
output.as_mut_ptr(),
)
.result()
.map_err(VulkanError::from)?;
output.assume_init()
};
Ok(handle)
}
/// Creates a new `PipelineLayout` from a raw object handle.
///
/// # Safety
///
/// - `handle` must be a valid Vulkan object handle created from `device`.
/// - `create_info` must match the info used to create the object.
#[inline]
pub unsafe fn from_handle(
device: Arc<Device>,
handle: ash::vk::PipelineLayout,
create_info: PipelineLayoutCreateInfo,
) -> Arc<PipelineLayout> {
let PipelineLayoutCreateInfo {
set_layouts,
push_constant_ranges,
_ne: _,
} = create_info;
let push_constant_ranges_disjoint =
Self::create_push_constant_ranges_disjoint(&push_constant_ranges);
Arc::new(PipelineLayout {
handle,
device,
id: Self::next_id(),
set_layouts,
push_constant_ranges,
push_constant_ranges_disjoint,
})
}
/// Returns the descriptor set layouts this pipeline layout was created from.
#[inline]
pub fn set_layouts(&self) -> &[Arc<DescriptorSetLayout>] {
&self.set_layouts
}
/// Returns a slice containing the push constant ranges this pipeline layout was created from.
///
/// The ranges are guaranteed to be sorted deterministically by offset, size, then stages.
/// This means that two slices containing the same elements will always have the same order.
#[inline]
pub fn push_constant_ranges(&self) -> &[PushConstantRange] {
&self.push_constant_ranges
}
/// Returns a slice containing the push constant ranges in with all disjoint stages.
///
/// For example, if we have these `push_constant_ranges`:
/// - `offset=0, size=4, stages=vertex`
/// - `offset=0, size=12, stages=fragment`
///
/// The returned value will be:
/// - `offset=0, size=4, stages=vertex|fragment`
/// - `offset=4, size=8, stages=fragment`
///
/// The ranges are guaranteed to be sorted deterministically by offset, and
/// guaranteed to be disjoint, meaning that there is no overlap between the ranges.
#[inline]
pub(crate) fn push_constant_ranges_disjoint(&self) -> &[PushConstantRange] {
&self.push_constant_ranges_disjoint
}
/// Returns whether `self` is compatible with `other` for the given number of sets.
#[inline]
pub fn is_compatible_with(&self, other: &PipelineLayout, num_sets: u32) -> bool {
let num_sets = num_sets as usize;
assert!(num_sets >= self.set_layouts.len());
if self == other {
return true;
}
if self.push_constant_ranges != other.push_constant_ranges {
return false;
}
let other_sets = match other.set_layouts.get(0..num_sets) {
Some(x) => x,
None => return false,
};
self.set_layouts
.iter()
.zip(other_sets)
.all(|(self_set_layout, other_set_layout)| {
self_set_layout.is_compatible_with(other_set_layout)
})
}
/// Makes sure that `self` is a superset of the provided descriptor set layouts and push
/// constant ranges. Returns an `Err` if this is not the case.
pub fn ensure_compatible_with_shader<'a>(
&self,
descriptor_requirements: impl IntoIterator<
Item = ((u32, u32), &'a DescriptorBindingRequirements),
>,
push_constant_range: Option<&PushConstantRange>,
) -> Result<(), PipelineLayoutSupersetError> {
for ((set_num, binding_num), reqs) in descriptor_requirements.into_iter() {
let layout_binding = self
.set_layouts
.get(set_num as usize)
.and_then(|set_layout| set_layout.bindings().get(&binding_num));
let layout_binding = match layout_binding {
Some(x) => x,
None => {
return Err(PipelineLayoutSupersetError::DescriptorMissing {
set_num,
binding_num,
})
}
};
if let Err(error) = layout_binding.ensure_compatible_with_shader(reqs) {
return Err(PipelineLayoutSupersetError::DescriptorRequirementsNotMet {
set_num,
binding_num,
error,
});
}
}
// FIXME: check push constants
if let Some(range) = push_constant_range {
for own_range in self.push_constant_ranges.iter() {
if range.stages.intersects(own_range.stages) && // check if it shares any stages
(range.offset < own_range.offset || // our range must start before and end after the given range
own_range.offset + own_range.size < range.offset + range.size)
{
return Err(PipelineLayoutSupersetError::PushConstantRange {
first_range: *own_range,
second_range: *range,
});
}
}
}
Ok(())
}
}
impl Drop for PipelineLayout {
#[inline]
fn drop(&mut self) {
unsafe {
let fns = self.device.fns();
(fns.v1_0.destroy_pipeline_layout)(self.device.handle(), self.handle, ptr::null());
}
}
}
unsafe impl VulkanObject for PipelineLayout {
type Handle = ash::vk::PipelineLayout;
#[inline]
fn handle(&self) -> Self::Handle {
self.handle
}
}
unsafe impl DeviceOwned for PipelineLayout {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
impl_id_counter!(PipelineLayout);
/// Error that can happen when creating a pipeline layout.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PipelineLayoutCreationError {
/// Not enough memory.
OomError(OomError),
RequirementNotMet {
required_for: &'static str,
requires_one_of: RequiresOneOf,
},
/// The number of elements in `set_layouts` is greater than the
/// [`max_bound_descriptor_sets`](crate::device::Properties::max_bound_descriptor_sets) limit.
MaxBoundDescriptorSetsExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::Sampler`],
/// [`DescriptorType::CombinedImageSampler`] and [`DescriptorType::UniformTexelBuffer`]
/// descriptors than the
/// [`max_descriptor_set_samplers`](crate::device::Properties::max_descriptor_set_samplers)
/// limit.
MaxDescriptorSetSamplersExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::UniformBuffer`] descriptors than the
/// [`max_descriptor_set_uniform_buffers`](crate::device::Properties::max_descriptor_set_uniform_buffers)
/// limit.
MaxDescriptorSetUniformBuffersExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::UniformBufferDynamic`] descriptors than the
/// [`max_descriptor_set_uniform_buffers_dynamic`](crate::device::Properties::max_descriptor_set_uniform_buffers_dynamic)
/// limit.
MaxDescriptorSetUniformBuffersDynamicExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::StorageBuffer`] descriptors than the
/// [`max_descriptor_set_storage_buffers`](crate::device::Properties::max_descriptor_set_storage_buffers)
/// limit.
MaxDescriptorSetStorageBuffersExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::StorageBufferDynamic`] descriptors than the
/// [`max_descriptor_set_storage_buffers_dynamic`](crate::device::Properties::max_descriptor_set_storage_buffers_dynamic)
/// limit.
MaxDescriptorSetStorageBuffersDynamicExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::SampledImage`] and
/// [`DescriptorType::CombinedImageSampler`] descriptors than the
/// [`max_descriptor_set_sampled_images`](crate::device::Properties::max_descriptor_set_sampled_images)
/// limit.
MaxDescriptorSetSampledImagesExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::StorageImage`] and
/// [`DescriptorType::StorageTexelBuffer`] descriptors than the
/// [`max_descriptor_set_storage_images`](crate::device::Properties::max_descriptor_set_storage_images)
/// limit.
MaxDescriptorSetStorageImagesExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::InputAttachment`] descriptors than the
/// [`max_descriptor_set_input_attachments`](crate::device::Properties::max_descriptor_set_input_attachments)
/// limit.
MaxDescriptorSetInputAttachmentsExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more bound resources in a single stage than the
/// [`max_per_stage_resources`](crate::device::Properties::max_per_stage_resources)
/// limit.
MaxPerStageResourcesExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::Sampler`] and
/// [`DescriptorType::CombinedImageSampler`] descriptors in a single stage than the
/// [`max_per_stage_descriptor_samplers`](crate::device::Properties::max_per_stage_descriptor_samplers)
/// limit.
MaxPerStageDescriptorSamplersExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::UniformBuffer`] and
/// [`DescriptorType::UniformBufferDynamic`] descriptors in a single stage than the
/// [`max_per_stage_descriptor_uniform_buffers`](crate::device::Properties::max_per_stage_descriptor_uniform_buffers)
/// limit.
MaxPerStageDescriptorUniformBuffersExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::StorageBuffer`] and
/// [`DescriptorType::StorageBufferDynamic`] descriptors in a single stage than the
/// [`max_per_stage_descriptor_storage_buffers`](crate::device::Properties::max_per_stage_descriptor_storage_buffers)
/// limit.
MaxPerStageDescriptorStorageBuffersExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::SampledImage`],
/// [`DescriptorType::CombinedImageSampler`] and [`DescriptorType::UniformTexelBuffer`]
/// descriptors in a single stage than the
/// [`max_per_stage_descriptor_sampled_images`](crate::device::Properties::max_per_stage_descriptor_sampled_images)
/// limit.
MaxPerStageDescriptorSampledImagesExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::StorageImage`] and
/// [`DescriptorType::StorageTexelBuffer`] descriptors in a single stage than the
/// [`max_per_stage_descriptor_storage_images`](crate::device::Properties::max_per_stage_descriptor_storage_images)
/// limit.
MaxPerStageDescriptorStorageImagesExceeded { provided: u32, max_supported: u32 },
/// The `set_layouts` contain more [`DescriptorType::InputAttachment`] descriptors in a single
/// stage than the
/// [`max_per_stage_descriptor_input_attachments`](crate::device::Properties::max_per_stage_descriptor_input_attachments)
/// limit.
MaxPerStageDescriptorInputAttachmentsExceeded { provided: u32, max_supported: u32 },
/// An element in `push_constant_ranges` has an `offset + size` greater than the
/// [`max_push_constants_size`](crate::device::Properties::max_push_constants_size) limit.
MaxPushConstantsSizeExceeded { provided: u32, max_supported: u32 },
/// A shader stage appears in multiple elements of `push_constant_ranges`.
PushConstantRangesStageMultiple,
/// Multiple elements of `set_layouts` have `push_descriptor` enabled.
SetLayoutsPushDescriptorMultiple,
}
impl Error for PipelineLayoutCreationError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::OomError(err) => Some(err),
_ => None,
}
}
}
impl Display for PipelineLayoutCreationError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
match self {
Self::OomError(_) => write!(f, "not enough memory available"),
Self::RequirementNotMet {
required_for,
requires_one_of,
} => write!(
f,
"a requirement was not met for: {}; requires one of: {}",
required_for, requires_one_of,
),
Self::MaxBoundDescriptorSetsExceeded {
provided,
max_supported,
} => write!(
f,
"the number of elements in `set_layouts` ({}) is greater than the \
`max_bound_descriptor_sets` limit ({})",
provided, max_supported,
),
Self::MaxDescriptorSetSamplersExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::Sampler` and \
`DescriptorType::CombinedImageSampler` descriptors ({}) than the \
`max_descriptor_set_samplers` limit ({})",
provided, max_supported,
),
Self::MaxDescriptorSetUniformBuffersExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::UniformBuffer` descriptors ({}) \
than the `max_descriptor_set_uniform_buffers` limit ({})",
provided, max_supported,
),
Self::MaxDescriptorSetUniformBuffersDynamicExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::UniformBufferDynamic` descriptors \
({}) than the `max_descriptor_set_uniform_buffers_dynamic` limit ({})",
provided, max_supported,
),
Self::MaxDescriptorSetStorageBuffersExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::StorageBuffer` descriptors ({}) \
than the `max_descriptor_set_storage_buffers` limit ({})",
provided, max_supported,
),
Self::MaxDescriptorSetStorageBuffersDynamicExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::StorageBufferDynamic` descriptors \
({}) than the `max_descriptor_set_storage_buffers_dynamic` limit ({})",
provided, max_supported,
),
Self::MaxDescriptorSetSampledImagesExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::SampledImage`, \
`DescriptorType::CombinedImageSampler` and `DescriptorType::UniformTexelBuffer` \
descriptors ({}) than the `max_descriptor_set_sampled_images` limit ({})",
provided, max_supported,
),
Self::MaxDescriptorSetStorageImagesExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::StorageImage` and \
`DescriptorType::StorageTexelBuffer` descriptors ({}) than the \
`max_descriptor_set_storage_images` limit ({})",
provided, max_supported,
),
Self::MaxDescriptorSetInputAttachmentsExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::InputAttachment` descriptors ({}) \
than the `max_descriptor_set_input_attachments` limit ({})",
provided, max_supported,
),
Self::MaxPerStageResourcesExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more bound resources ({}) in a single stage than the \
`max_per_stage_resources` limit ({})",
provided, max_supported,
),
Self::MaxPerStageDescriptorSamplersExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::Sampler` and \
`DescriptorType::CombinedImageSampler` descriptors ({}) in a single stage than the \
`max_per_stage_descriptor_set_samplers` limit ({})",
provided, max_supported,
),
Self::MaxPerStageDescriptorUniformBuffersExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::UniformBuffer` and \
`DescriptorType::UniformBufferDynamic` descriptors ({}) in a single stage than the \
`max_per_stage_descriptor_set_uniform_buffers` limit ({})",
provided, max_supported,
),
Self::MaxPerStageDescriptorStorageBuffersExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::StorageBuffer` and \
`DescriptorType::StorageBufferDynamic` descriptors ({}) in a single stage than the \
`max_per_stage_descriptor_set_storage_buffers` limit ({})",
provided, max_supported,
),
Self::MaxPerStageDescriptorSampledImagesExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::SampledImage`, \
`DescriptorType::CombinedImageSampler` and `DescriptorType::UniformTexelBuffer` \
descriptors ({}) in a single stage than the \
`max_per_stage_descriptor_set_sampled_images` limit ({})",
provided, max_supported,
),
Self::MaxPerStageDescriptorStorageImagesExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::StorageImage` and \
`DescriptorType::StorageTexelBuffer` descriptors ({}) in a single stage than the \
`max_per_stage_descriptor_set_storage_images` limit ({})",
provided, max_supported,
),
Self::MaxPerStageDescriptorInputAttachmentsExceeded {
provided,
max_supported,
} => write!(
f,
"the `set_layouts` contain more `DescriptorType::InputAttachment` descriptors ({}) \
in a single stage than the `max_per_stage_descriptor_set_input_attachments` limit \
({})",
provided, max_supported,
),
Self::MaxPushConstantsSizeExceeded {
provided,
max_supported,
} => write!(
f,
"an element in `push_constant_ranges` has an `offset + size` ({}) greater than the \
`max_push_constants_size` limit ({})",
provided, max_supported,
),
Self::PushConstantRangesStageMultiple => write!(
f,
"a shader stage appears in multiple elements of `push_constant_ranges`",
),
Self::SetLayoutsPushDescriptorMultiple => write!(
f,
"multiple elements of `set_layouts` have `push_descriptor` enabled",
),
}
}
}
impl From<OomError> for PipelineLayoutCreationError {
fn from(err: OomError) -> PipelineLayoutCreationError {
PipelineLayoutCreationError::OomError(err)
}
}
impl From<VulkanError> for PipelineLayoutCreationError {
fn from(err: VulkanError) -> PipelineLayoutCreationError {
match err {
err @ VulkanError::OutOfHostMemory => {
PipelineLayoutCreationError::OomError(OomError::from(err))
}
err @ VulkanError::OutOfDeviceMemory => {
PipelineLayoutCreationError::OomError(OomError::from(err))
}
_ => panic!("unexpected error: {:?}", err),
}
}
}
impl From<RequirementNotMet> for PipelineLayoutCreationError {
fn from(err: RequirementNotMet) -> Self {
Self::RequirementNotMet {
required_for: err.required_for,
requires_one_of: err.requires_one_of,
}
}
}
/// Error when checking whether a pipeline layout is a superset of another one.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum PipelineLayoutSupersetError {
DescriptorMissing {
set_num: u32,
binding_num: u32,
},
DescriptorRequirementsNotMet {
set_num: u32,
binding_num: u32,
error: DescriptorRequirementsNotMet,
},
PushConstantRange {
first_range: PushConstantRange,
second_range: PushConstantRange,
},
}
impl Error for PipelineLayoutSupersetError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
PipelineLayoutSupersetError::DescriptorRequirementsNotMet { error, .. } => Some(error),
_ => None,
}
}
}
impl Display for PipelineLayoutSupersetError {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
match self {
PipelineLayoutSupersetError::DescriptorRequirementsNotMet {
set_num,
binding_num,
..
} => write!(
f,
"the descriptor at set {} binding {} does not meet the requirements",
set_num, binding_num,
),
PipelineLayoutSupersetError::DescriptorMissing {
set_num,
binding_num,
} => write!(
f,
"a descriptor at set {} binding {} is required by the shaders, but is missing from \
the pipeline layout",
set_num, binding_num,
),
PipelineLayoutSupersetError::PushConstantRange {
first_range,
second_range,
} => {
writeln!(f, "our range did not completely encompass the other range")?;
writeln!(f, " our stages: {:?}", first_range.stages)?;
writeln!(
f,
" our range: {} - {}",
first_range.offset,
first_range.offset + first_range.size,
)?;
writeln!(f, " other stages: {:?}", second_range.stages)?;
write!(
f,
" other range: {} - {}",
second_range.offset,
second_range.offset + second_range.size,
)
}
}
}
}
/// Parameters to create a new `PipelineLayout`.
#[derive(Clone, Debug)]
pub struct PipelineLayoutCreateInfo {
/// The descriptor set layouts that should be part of the pipeline layout.
///
/// They are provided in order of set number.
///
/// The default value is empty.
pub set_layouts: Vec<Arc<DescriptorSetLayout>>,
/// The ranges of push constants that the pipeline will access.
///
/// A shader stage can only appear in one element of the list, but it is possible to combine
/// ranges for multiple shader stages if they are the same.
///
/// The default value is empty.
pub push_constant_ranges: Vec<PushConstantRange>,
pub _ne: crate::NonExhaustive,
}
impl Default for PipelineLayoutCreateInfo {
#[inline]
fn default() -> Self {
Self {
set_layouts: Vec::new(),
push_constant_ranges: Vec::new(),
_ne: crate::NonExhaustive(()),
}
}
}
/// Description of a range of the push constants of a pipeline layout.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PushConstantRange {
/// The stages which can access this range. A stage can access at most one push constant range.
///
/// The default value is [`ShaderStages::empty()`], which must be overridden.
pub stages: ShaderStages,
/// Offset in bytes from the start of the push constants to this range.
///
/// The value must be a multiple of 4.
///
/// The default value is `0`.
pub offset: u32,
/// Size in bytes of the range.
///
/// The value must be a multiple of 4, and not 0.
///
/// The default value is `0`, which must be overridden.
pub size: u32,
}
impl Default for PushConstantRange {
#[inline]
fn default() -> Self {
Self {
stages: ShaderStages::empty(),
offset: 0,
size: 0,
}
}
}
// Helper struct for the main function.
#[derive(Default)]
struct Counter {
total: u32,
compute: u32,
vertex: u32,
geometry: u32,
tess_ctl: u32,
tess_eval: u32,
frag: u32,
}
impl Counter {
fn increment(&mut self, num: u32, stages: ShaderStages) {
self.total += num;
if stages.intersects(ShaderStages::COMPUTE) {
self.compute += num;
}
if stages.intersects(ShaderStages::VERTEX) {
self.vertex += num;
}
if stages.intersects(ShaderStages::TESSELLATION_CONTROL) {
self.tess_ctl += num;
}
if stages.intersects(ShaderStages::TESSELLATION_EVALUATION) {
self.tess_eval += num;
}
if stages.intersects(ShaderStages::GEOMETRY) {
self.geometry += num;
}
if stages.intersects(ShaderStages::FRAGMENT) {
self.frag += num;
}
}
fn max_per_stage(&self) -> u32 {
[
self.compute,
self.vertex,
self.tess_ctl,
self.tess_eval,
self.geometry,
self.frag,
]
.into_iter()
.max()
.unwrap_or(0)
}
}
#[cfg(test)]
mod tests {
use super::PipelineLayout;
use crate::{
pipeline::layout::{PipelineLayoutCreateInfo, PushConstantRange},
shader::ShaderStages,
};
#[test]
fn push_constant_ranges_disjoint() {
let test_cases = [
// input:
// - `0..12`, stage=fragment
// - `0..40`, stage=vertex
//
// output:
// - `0..12`, stage=fragment|vertex
// - `12..40`, stage=vertex
(
&[
PushConstantRange {
stages: ShaderStages::FRAGMENT,
offset: 0,
size: 12,
},
PushConstantRange {
stages: ShaderStages::VERTEX,
offset: 0,
size: 40,
},
][..],
&[
PushConstantRange {
stages: ShaderStages::VERTEX | ShaderStages::FRAGMENT,
offset: 0,
size: 12,
},
PushConstantRange {
stages: ShaderStages::VERTEX,
offset: 12,
size: 28,
},
][..],
),
// input:
// - `0..12`, stage=fragment
// - `4..40`, stage=vertex
//
// output:
// - `0..4`, stage=fragment
// - `4..12`, stage=fragment|vertex
// - `12..40`, stage=vertex
(
&[
PushConstantRange {
stages: ShaderStages::FRAGMENT,
offset: 0,
size: 12,
},
PushConstantRange {
stages: ShaderStages::VERTEX,
offset: 4,
size: 36,
},
][..],
&[
PushConstantRange {
stages: ShaderStages::FRAGMENT,
offset: 0,
size: 4,
},
PushConstantRange {
stages: ShaderStages::FRAGMENT | ShaderStages::VERTEX,
offset: 4,
size: 8,
},
PushConstantRange {
stages: ShaderStages::VERTEX,
offset: 12,
size: 28,
},
][..],
),
// input:
// - `0..12`, stage=fragment
// - `8..20`, stage=compute
// - `4..16`, stage=vertex
// - `8..32`, stage=tess_ctl
//
// output:
// - `0..4`, stage=fragment
// - `4..8`, stage=fragment|vertex
// - `8..16`, stage=fragment|vertex|compute|tess_ctl
// - `16..20`, stage=compute|tess_ctl
// - `20..32` stage=tess_ctl
(
&[
PushConstantRange {
stages: ShaderStages::FRAGMENT,
offset: 0,
size: 12,
},
PushConstantRange {
stages: ShaderStages::COMPUTE,
offset: 8,
size: 12,
},
PushConstantRange {
stages: ShaderStages::VERTEX,
offset: 4,
size: 12,
},
PushConstantRange {
stages: ShaderStages::TESSELLATION_CONTROL,
offset: 8,
size: 24,
},
][..],
&[
PushConstantRange {
stages: ShaderStages::FRAGMENT,
offset: 0,
size: 4,
},
PushConstantRange {
stages: ShaderStages::FRAGMENT | ShaderStages::VERTEX,
offset: 4,
size: 4,
},
PushConstantRange {
stages: ShaderStages::VERTEX
| ShaderStages::FRAGMENT
| ShaderStages::COMPUTE
| ShaderStages::TESSELLATION_CONTROL,
offset: 8,
size: 4,
},
PushConstantRange {
stages: ShaderStages::VERTEX
| ShaderStages::COMPUTE
| ShaderStages::TESSELLATION_CONTROL,
offset: 12,
size: 4,
},
PushConstantRange {
stages: ShaderStages::COMPUTE | ShaderStages::TESSELLATION_CONTROL,
offset: 16,
size: 4,
},
PushConstantRange {
stages: ShaderStages::TESSELLATION_CONTROL,
offset: 20,
size: 12,
},
][..],
),
];
let (device, _) = gfx_dev_and_queue!();
for (input, expected) in test_cases {
let layout = PipelineLayout::new(
device.clone(),
PipelineLayoutCreateInfo {
push_constant_ranges: input.into(),
..Default::default()
},
)
.unwrap();
assert_eq!(layout.push_constant_ranges_disjoint.as_slice(), expected);
}
}
}
/* TODO: restore
#[cfg(test)]
mod tests {
use std::iter;
use std::sync::Arc;
use descriptor::descriptor::ShaderStages;
use descriptor::descriptor_set::DescriptorSetLayout;
use descriptor::pipeline_layout::sys::PipelineLayout;
use descriptor::pipeline_layout::sys::PipelineLayoutCreationError;
#[test]
fn empty() {
let (device, _) = gfx_dev_and_queue!();
let _layout = PipelineLayout::new(&device, iter::empty(), iter::empty()).unwrap();
}
#[test]
fn wrong_device_panic() {
let (device1, _) = gfx_dev_and_queue!();
let (device2, _) = gfx_dev_and_queue!();
let set = match DescriptorSetLayout::raw(device1, iter::empty()) {
Ok(s) => Arc::new(s),
Err(_) => return
};
assert_should_panic!({
let _ = PipelineLayout::new(&device2, Some(&set), iter::empty());
});
}
#[test]
fn invalid_push_constant_stages() {
let (device, _) = gfx_dev_and_queue!();
let push_constant = (0, 8, ShaderStages::empty());
match PipelineLayout::new(&device, iter::empty(), Some(push_constant)) {
Err(PipelineLayoutCreationError::InvalidPushConstant) => (),
_ => panic!()
}
}
#[test]
fn invalid_push_constant_size1() {
let (device, _) = gfx_dev_and_queue!();
let push_constant = (0, 0, ShaderStages::all_graphics());
match PipelineLayout::new(&device, iter::empty(), Some(push_constant)) {
Err(PipelineLayoutCreationError::InvalidPushConstant) => (),
_ => panic!()
}
}
#[test]
fn invalid_push_constant_size2() {
let (device, _) = gfx_dev_and_queue!();
let push_constant = (0, 11, ShaderStages::all_graphics());
match PipelineLayout::new(&device, iter::empty(), Some(push_constant)) {
Err(PipelineLayoutCreationError::InvalidPushConstant) => (),
_ => panic!()
}
}
}
*/