use super::PipelineShaderStageCreateInfo;
use crate::{
descriptor_set::layout::{
DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateFlags,
DescriptorSetLayoutCreateInfo, DescriptorType,
},
device::{Device, DeviceOwned, DeviceOwnedDebugWrapper, Properties},
instance::InstanceOwnedDebugWrapper,
macros::{impl_id_counter, vulkan_bitflags},
shader::{DescriptorBindingRequirements, ShaderStage, ShaderStages},
Validated, ValidationError, VulkanError, VulkanObject,
};
use ahash::HashMap;
use smallvec::SmallVec;
use std::{
array,
cmp::max,
collections::hash_map::Entry,
error::Error,
fmt::{Display, Formatter, Write},
mem::MaybeUninit,
num::NonZeroU64,
ptr,
sync::Arc,
};
#[derive(Debug)]
pub struct PipelineLayout {
handle: ash::vk::PipelineLayout,
device: InstanceOwnedDebugWrapper<Arc<Device>>,
id: NonZeroU64,
flags: PipelineLayoutCreateFlags,
set_layouts: Vec<DeviceOwnedDebugWrapper<Arc<DescriptorSetLayout>>>,
push_constant_ranges: Vec<PushConstantRange>,
push_constant_ranges_disjoint: Vec<PushConstantRange>,
}
impl PipelineLayout {
pub fn new(
device: Arc<Device>,
create_info: PipelineLayoutCreateInfo,
) -> Result<Arc<PipelineLayout>, Validated<VulkanError>> {
Self::validate_new(&device, &create_info)?;
unsafe { Ok(Self::new_unchecked(device, create_info)?) }
}
fn validate_new(
device: &Device,
create_info: &PipelineLayoutCreateInfo,
) -> Result<(), Box<ValidationError>> {
create_info
.validate(device)
.map_err(|err| err.add_context("create_info"))?;
Ok(())
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
pub unsafe fn new_unchecked(
device: Arc<Device>,
create_info: PipelineLayoutCreateInfo,
) -> Result<Arc<PipelineLayout>, VulkanError> {
let &PipelineLayoutCreateInfo {
flags,
ref set_layouts,
ref push_constant_ranges,
_ne: _,
} = &create_info;
let set_layouts_vk: SmallVec<[_; 4]> = set_layouts.iter().map(|l| l.handle()).collect();
let push_constant_ranges_vk: 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_vk = ash::vk::PipelineLayoutCreateInfo {
flags: flags.into(),
set_layout_count: set_layouts_vk.len() as u32,
p_set_layouts: set_layouts_vk.as_ptr(),
push_constant_range_count: push_constant_ranges_vk.len() as u32,
p_push_constant_ranges: push_constant_ranges_vk.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_vk,
ptr::null(),
output.as_mut_ptr(),
)
.result()
.map_err(VulkanError::from)?;
output.assume_init()
};
Ok(Self::from_handle(device, handle, create_info))
}
#[inline]
pub unsafe fn from_handle(
device: Arc<Device>,
handle: ash::vk::PipelineLayout,
create_info: PipelineLayoutCreateInfo,
) -> Arc<PipelineLayout> {
let PipelineLayoutCreateInfo {
flags,
set_layouts,
mut push_constant_ranges,
_ne: _,
} = create_info;
push_constant_ranges.sort_unstable_by_key(|range| {
(
range.offset,
range.size,
ash::vk::ShaderStageFlags::from(range.stages),
)
});
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() {
if range.offset > min_offset {
max_offset = max_offset.min(range.offset);
break;
} else if range.offset + range.size > min_offset {
max_offset = max_offset.min(range.offset + range.size);
stages |= range.stages;
}
}
if stages.is_empty() {
break;
}
push_constant_ranges_disjoint.push(PushConstantRange {
stages,
offset: min_offset,
size: max_offset - min_offset,
});
min_offset = max_offset;
}
}
Arc::new(PipelineLayout {
handle,
device: InstanceOwnedDebugWrapper(device),
id: Self::next_id(),
flags,
set_layouts: set_layouts
.into_iter()
.map(DeviceOwnedDebugWrapper)
.collect(),
push_constant_ranges,
push_constant_ranges_disjoint,
})
}
#[inline]
pub fn flags(&self) -> PipelineLayoutCreateFlags {
self.flags
}
#[inline]
pub fn set_layouts(&self) -> &[Arc<DescriptorSetLayout>] {
DeviceOwnedDebugWrapper::cast_slice_inner(&self.set_layouts)
}
#[inline]
pub fn push_constant_ranges(&self) -> &[PushConstantRange] {
&self.push_constant_ranges
}
#[inline]
pub(crate) fn push_constant_ranges_disjoint(&self) -> &[PushConstantRange] {
&self.push_constant_ranges_disjoint
}
#[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)
})
}
pub(crate) fn ensure_compatible_with_shader<'a>(
&self,
descriptor_requirements: impl IntoIterator<
Item = ((u32, u32), &'a DescriptorBindingRequirements),
>,
push_constant_range: Option<&PushConstantRange>,
) -> Result<(), Box<ValidationError>> {
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(Box::new(ValidationError {
problem: format!(
"the requirements for descriptor set {} binding {} were not met: \
no such binding exists in the pipeline layout",
set_num, binding_num,
)
.into(),
..Default::default()
}));
}
};
if let Err(error) = layout_binding.ensure_compatible_with_shader(reqs) {
return Err(Box::new(ValidationError {
problem: format!(
"the requirements for descriptor set {} binding {} were not met: {}",
set_num, binding_num, error,
)
.into(),
..Default::default()
}));
}
}
if let Some(range) = push_constant_range {
for own_range in self.push_constant_ranges.iter() {
if range.stages.intersects(own_range.stages) && (range.offset < own_range.offset || own_range.offset + own_range.size < range.offset + range.size)
{
return Err(Box::new(ValidationError {
problem: "the required push constant range is larger than the \
push constant range in the pipeline layout"
.into(),
..Default::default()
}));
}
}
}
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);
#[derive(Clone, Debug)]
pub struct PipelineLayoutCreateInfo {
pub flags: PipelineLayoutCreateFlags,
pub set_layouts: Vec<Arc<DescriptorSetLayout>>,
pub push_constant_ranges: Vec<PushConstantRange>,
pub _ne: crate::NonExhaustive,
}
impl Default for PipelineLayoutCreateInfo {
#[inline]
fn default() -> Self {
Self {
flags: PipelineLayoutCreateFlags::empty(),
set_layouts: Vec::new(),
push_constant_ranges: Vec::new(),
_ne: crate::NonExhaustive(()),
}
}
}
impl PipelineLayoutCreateInfo {
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let properties = device.physical_device().properties();
let &Self {
flags,
ref set_layouts,
ref push_constant_ranges,
_ne: _,
} = self;
flags.validate_device(device).map_err(|err| {
err.add_context("flags")
.set_vuids(&["VUID-VkPipelineLayoutCreateInfo-flags-parameter"])
})?;
if set_layouts.len() > properties.max_bound_descriptor_sets as usize {
return Err(Box::new(ValidationError {
context: "set_layouts".into(),
problem: "the length exceeds the `max_bound_descriptor_sets` limit".into(),
vuids: &["VUID-VkPipelineLayoutCreateInfo-setLayoutCount-00286"],
..Default::default()
}));
}
struct DescriptorLimit {
descriptor_types: &'static [DescriptorType],
get_limit_all: fn(&Properties) -> Option<u32>,
limit_name_all: &'static str,
vuids_all: &'static [&'static str],
get_limit_not_uab: fn(&Properties) -> u32,
limit_name_not_uab: &'static str,
vuids_not_uab: &'static [&'static str],
}
const PER_STAGE_DESCRIPTOR_LIMITS: [DescriptorLimit; 8] = [
DescriptorLimit {
descriptor_types: &[
DescriptorType::Sampler,
DescriptorType::CombinedImageSampler,
],
get_limit_all: |p| p.max_per_stage_descriptor_update_after_bind_samplers,
limit_name_all: "max_per_stage_descriptor_update_after_bind_samplers",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03022"],
get_limit_not_uab: |p| p.max_per_stage_descriptor_samplers,
limit_name_not_uab: "max_per_stage_descriptor_samplers",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03016"],
},
DescriptorLimit {
descriptor_types: &[
DescriptorType::UniformBuffer,
DescriptorType::UniformBufferDynamic,
],
get_limit_all: |p| p.max_per_stage_descriptor_update_after_bind_uniform_buffers,
limit_name_all: "max_per_stage_descriptor_update_after_bind_uniform_buffers",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03023"],
get_limit_not_uab: |p| p.max_per_stage_descriptor_uniform_buffers,
limit_name_not_uab: "max_per_stage_descriptor_uniform_buffers",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03017"],
},
DescriptorLimit {
descriptor_types: &[
DescriptorType::StorageBuffer,
DescriptorType::StorageBufferDynamic,
],
get_limit_all: |p| p.max_per_stage_descriptor_update_after_bind_storage_buffers,
limit_name_all: "max_per_stage_descriptor_update_after_bind_storage_buffers",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03024"],
get_limit_not_uab: |p| p.max_per_stage_descriptor_storage_buffers,
limit_name_not_uab: "max_per_stage_descriptor_storage_buffers",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03018"],
},
DescriptorLimit {
descriptor_types: &[
DescriptorType::CombinedImageSampler,
DescriptorType::SampledImage,
DescriptorType::UniformTexelBuffer,
],
get_limit_all: |p| p.max_per_stage_descriptor_update_after_bind_sampled_images,
limit_name_all: "max_per_stage_descriptor_update_after_bind_sampled_images",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03025"],
get_limit_not_uab: |p| p.max_per_stage_descriptor_sampled_images,
limit_name_not_uab: "max_per_stage_descriptor_sampled_images",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-06939"],
},
DescriptorLimit {
descriptor_types: &[
DescriptorType::StorageImage,
DescriptorType::StorageTexelBuffer,
],
get_limit_all: |p| p.max_per_stage_descriptor_update_after_bind_storage_images,
limit_name_all: "max_per_stage_descriptor_update_after_bind_storage_images",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03026"],
get_limit_not_uab: |p| p.max_per_stage_descriptor_storage_images,
limit_name_not_uab: "max_per_stage_descriptor_storage_images",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03020"],
},
DescriptorLimit {
descriptor_types: &[DescriptorType::InputAttachment],
get_limit_all: |p| p.max_per_stage_descriptor_update_after_bind_input_attachments,
limit_name_all: "max_per_stage_descriptor_update_after_bind_input_attachments",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03027"],
get_limit_not_uab: |p| p.max_per_stage_descriptor_input_attachments,
limit_name_not_uab: "max_per_stage_descriptor_input_attachments",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03021"],
},
DescriptorLimit {
descriptor_types: &[DescriptorType::InlineUniformBlock],
get_limit_all: |p| {
p.max_per_stage_descriptor_update_after_bind_inline_uniform_blocks
},
limit_name_all: "max_per_stage_descriptor_update_after_bind_inline_uniform_blocks",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-02215"],
get_limit_not_uab: |p| {
p.max_per_stage_descriptor_inline_uniform_blocks
.unwrap_or(0)
},
limit_name_not_uab: "max_per_stage_descriptor_inline_uniform_blocks",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-02214"],
},
DescriptorLimit {
descriptor_types: &[DescriptorType::AccelerationStructure],
get_limit_all: |p| {
p.max_per_stage_descriptor_update_after_bind_acceleration_structures
},
limit_name_all:
"max_per_stage_descriptor_update_after_bind_acceleration_structures",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03572"],
get_limit_not_uab: |p| {
p.max_per_stage_descriptor_acceleration_structures
.unwrap_or(0)
},
limit_name_not_uab: "max_per_stage_descriptor_acceleration_structures",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03571"],
},
];
const TOTAL_DESCRIPTOR_LIMITS: [DescriptorLimit; 10] = [
DescriptorLimit {
descriptor_types: &[
DescriptorType::Sampler,
DescriptorType::CombinedImageSampler,
],
get_limit_all: |p| p.max_descriptor_set_update_after_bind_samplers,
limit_name_all: "max_descriptor_set_update_after_bind_samplers",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-pSetLayouts-03036"],
get_limit_not_uab: |p| p.max_descriptor_set_samplers,
limit_name_not_uab: "max_descriptor_set_samplers",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03028"],
},
DescriptorLimit {
descriptor_types: &[DescriptorType::UniformBuffer],
get_limit_all: |p| p.max_descriptor_set_update_after_bind_uniform_buffers,
limit_name_all: "max_descriptor_set_update_after_bind_uniform_buffers",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-pSetLayouts-03037"],
get_limit_not_uab: |p| p.max_descriptor_set_uniform_buffers,
limit_name_not_uab: "max_descriptor_set_uniform_buffers",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03029"],
},
DescriptorLimit {
descriptor_types: &[DescriptorType::UniformBufferDynamic],
get_limit_all: |p| p.max_descriptor_set_update_after_bind_uniform_buffers_dynamic,
limit_name_all: "max_descriptor_set_update_after_bind_uniform_buffers_dynamic",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-pSetLayouts-03038"],
get_limit_not_uab: |p| p.max_descriptor_set_uniform_buffers_dynamic,
limit_name_not_uab: "max_descriptor_set_uniform_buffers_dynamic",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03030"],
},
DescriptorLimit {
descriptor_types: &[DescriptorType::StorageBuffer],
get_limit_all: |p| p.max_descriptor_set_update_after_bind_storage_buffers,
limit_name_all: "max_descriptor_set_update_after_bind_storage_buffers",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-pSetLayouts-03039"],
get_limit_not_uab: |p| p.max_descriptor_set_storage_buffers,
limit_name_not_uab: "max_descriptor_set_storage_buffers",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03031"],
},
DescriptorLimit {
descriptor_types: &[DescriptorType::StorageBufferDynamic],
get_limit_all: |p| p.max_descriptor_set_update_after_bind_storage_buffers_dynamic,
limit_name_all: "max_descriptor_set_update_after_bind_storage_buffers_dynamic",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-pSetLayouts-03040"],
get_limit_not_uab: |p| p.max_descriptor_set_storage_buffers_dynamic,
limit_name_not_uab: "max_descriptor_set_storage_buffers_dynamic",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03032"],
},
DescriptorLimit {
descriptor_types: &[
DescriptorType::CombinedImageSampler,
DescriptorType::SampledImage,
DescriptorType::UniformTexelBuffer,
],
get_limit_all: |p| p.max_descriptor_set_update_after_bind_sampled_images,
limit_name_all: "max_descriptor_set_update_after_bind_sampled_images",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-pSetLayouts-03041"],
get_limit_not_uab: |p| p.max_descriptor_set_sampled_images,
limit_name_not_uab: "max_descriptor_set_sampled_images",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03033"],
},
DescriptorLimit {
descriptor_types: &[
DescriptorType::StorageImage,
DescriptorType::StorageTexelBuffer,
],
get_limit_all: |p| p.max_descriptor_set_update_after_bind_storage_images,
limit_name_all: "max_descriptor_set_update_after_bind_storage_images",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-pSetLayouts-03042"],
get_limit_not_uab: |p| p.max_descriptor_set_storage_images,
limit_name_not_uab: "max_descriptor_set_storage_images",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03034"],
},
DescriptorLimit {
descriptor_types: &[DescriptorType::InputAttachment],
get_limit_all: |p| p.max_descriptor_set_update_after_bind_input_attachments,
limit_name_all: "max_descriptor_set_update_after_bind_input_attachments",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-pSetLayouts-03043"],
get_limit_not_uab: |p| p.max_descriptor_set_input_attachments,
limit_name_not_uab: "max_descriptor_set_input_attachments",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03035"],
},
DescriptorLimit {
descriptor_types: &[DescriptorType::InlineUniformBlock],
get_limit_all: |p| p.max_descriptor_set_update_after_bind_inline_uniform_blocks,
limit_name_all: "max_descriptor_set_update_after_bind_inline_uniform_blocks",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-02217"],
get_limit_not_uab: |p| p.max_descriptor_set_inline_uniform_blocks.unwrap_or(0),
limit_name_not_uab: "max_descriptor_set_inline_uniform_blocks",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-02216"],
},
DescriptorLimit {
descriptor_types: &[DescriptorType::AccelerationStructure],
get_limit_all: |p| p.max_descriptor_set_update_after_bind_acceleration_structures,
limit_name_all: "max_descriptor_set_update_after_bind_acceleration_structures",
vuids_all: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03574"],
get_limit_not_uab: |p| p.max_descriptor_set_acceleration_structures.unwrap_or(0),
limit_name_not_uab: "max_descriptor_set_acceleration_structures",
vuids_not_uab: &["VUID-VkPipelineLayoutCreateInfo-descriptorType-03573"],
},
];
let mut per_stage_descriptors_all: [HashMap<ShaderStage, u32>;
PER_STAGE_DESCRIPTOR_LIMITS.len()] = array::from_fn(|_| HashMap::default());
let mut per_stage_descriptors_not_uab: [HashMap<ShaderStage, u32>;
PER_STAGE_DESCRIPTOR_LIMITS.len()] = array::from_fn(|_| HashMap::default());
let mut total_descriptors_all = [0; TOTAL_DESCRIPTOR_LIMITS.len()];
let mut total_descriptors_not_uab = [0; TOTAL_DESCRIPTOR_LIMITS.len()];
let mut has_push_descriptor_set = false;
for (_set_num, set_layout) in set_layouts.iter().enumerate() {
assert_eq!(device, set_layout.device().as_ref());
if set_layout
.flags()
.intersects(DescriptorSetLayoutCreateFlags::PUSH_DESCRIPTOR)
{
if has_push_descriptor_set {
return Err(Box::new(ValidationError {
context: "set_layouts".into(),
problem: "contains more than one descriptor set layout whose flags \
include `DescriptorSetLayoutCreateFlags::PUSH_DESCRIPTOR`"
.into(),
vuids: &["VUID-VkPipelineLayoutCreateInfo-pSetLayouts-00293"],
..Default::default()
}));
}
has_push_descriptor_set = true;
}
let is_not_uab = !set_layout
.flags()
.intersects(DescriptorSetLayoutCreateFlags::UPDATE_AFTER_BIND_POOL);
for layout_binding in set_layout.bindings().values() {
let &DescriptorSetLayoutBinding {
binding_flags: _,
descriptor_type,
descriptor_count,
stages,
immutable_samplers: _,
_ne: _,
} = layout_binding;
for ((limit, count_all), count_not_uab) in PER_STAGE_DESCRIPTOR_LIMITS
.iter()
.zip(&mut per_stage_descriptors_all)
.zip(&mut per_stage_descriptors_not_uab)
{
if limit.descriptor_types.contains(&descriptor_type) {
for stage in stages {
*count_all.entry(stage).or_default() += descriptor_count;
if is_not_uab {
*count_not_uab.entry(stage).or_default() += descriptor_count;
}
}
}
}
for ((limit, count_all), count_not_uab) in TOTAL_DESCRIPTOR_LIMITS
.iter()
.zip(&mut total_descriptors_all)
.zip(&mut total_descriptors_not_uab)
{
if limit.descriptor_types.contains(&descriptor_type) {
*count_all += descriptor_count;
if is_not_uab {
*count_not_uab += descriptor_count;
}
}
}
}
}
for ((limit, count_all), count_not_uab) in PER_STAGE_DESCRIPTOR_LIMITS
.iter()
.zip(per_stage_descriptors_all)
.zip(per_stage_descriptors_not_uab)
{
let limit_not_uab = (limit.get_limit_not_uab)(properties);
if let Some((max_stage, max_count)) = count_not_uab.into_iter().max_by_key(|(_, c)| *c)
{
if max_count > limit_not_uab {
return Err(Box::new(ValidationError {
context: "set_layouts".into(),
problem: format!(
"the combined number of {} descriptors, \
belonging to descriptor set layouts without the \
`DescriptorSetLayoutCreateFlags::UPDATE_AFTER_BIND_POOL` flag, \
accessible to the `ShaderStage::{:?}` stage, \
exceeds the `{}` limit",
limit.descriptor_types[1..].iter().fold(
format!("`DescriptorType::{:?}`", limit.descriptor_types[0]),
|mut s, dt| {
write!(s, " + `DescriptorType::{:?}`", dt).unwrap();
s
}
),
max_stage,
limit.limit_name_not_uab,
)
.into(),
vuids: limit.vuids_not_uab,
..Default::default()
}));
}
}
let limit_all = match (limit.get_limit_all)(properties) {
Some(x) => x,
None => continue,
};
if let Some((max_stage, max_count)) = count_all.into_iter().max_by_key(|(_, c)| *c) {
if max_count > limit_all {
return Err(Box::new(ValidationError {
context: "set_layouts".into(),
problem: format!(
"the combined number of {} descriptors, \
accessible to the `ShaderStage::{:?}` stage, \
exceeds the `{}` limit",
limit.descriptor_types[1..].iter().fold(
format!("`DescriptorType::{:?}`", limit.descriptor_types[0]),
|mut s, dt| {
write!(s, " + `DescriptorType::{:?}`", dt).unwrap();
s
}
),
max_stage,
limit.limit_name_all,
)
.into(),
vuids: limit.vuids_all,
..Default::default()
}));
}
}
}
for ((limit, count_all), count_not_uab) in TOTAL_DESCRIPTOR_LIMITS
.iter()
.zip(total_descriptors_all)
.zip(total_descriptors_not_uab)
{
let limit_not_uab = (limit.get_limit_not_uab)(properties);
if count_not_uab > limit_not_uab {
return Err(Box::new(ValidationError {
context: "set_layouts".into(),
problem: format!(
"the combined number of {} descriptors, \
belonging to descriptor set layouts without the \
`DescriptorSetLayoutCreateFlags::UPDATE_AFTER_BIND_POOL` flag, \
accessible across all shader stages, \
exceeds the `{}` limit",
limit.descriptor_types[1..].iter().fold(
format!("`DescriptorType::{:?}`", limit.descriptor_types[0]),
|mut s, dt| {
write!(s, " + `DescriptorType::{:?}`", dt).unwrap();
s
}
),
limit.limit_name_not_uab,
)
.into(),
vuids: limit.vuids_not_uab,
..Default::default()
}));
}
let limit_all = match (limit.get_limit_all)(properties) {
Some(x) => x,
None => continue,
};
if count_all > limit_all {
return Err(Box::new(ValidationError {
context: "set_layouts".into(),
problem: format!(
"the combined number of {} descriptors, \
accessible across all shader stages, \
exceeds the `{}` limit",
limit.descriptor_types[1..].iter().fold(
format!("`DescriptorType::{:?}`", limit.descriptor_types[0]),
|mut s, dt| {
write!(s, " + `DescriptorType::{:?}`", dt).unwrap();
s
}
),
limit.limit_name_all,
)
.into(),
vuids: limit.vuids_all,
..Default::default()
}));
}
}
let mut seen_stages = ShaderStages::empty();
for (range_index, range) in push_constant_ranges.iter().enumerate() {
range
.validate(device)
.map_err(|err| err.add_context(format!("push_constant_ranges[{}]", range_index)))?;
let &PushConstantRange {
stages,
offset: _,
size: _,
} = range;
if seen_stages.intersects(stages) {
return Err(Box::new(ValidationError {
context: "push_constant_ranges".into(),
problem: "contains more than one range with the same stage".into(),
vuids: &["VUID-VkPipelineLayoutCreateInfo-pPushConstantRanges-00292"],
..Default::default()
}));
}
seen_stages |= stages;
}
Ok(())
}
}
vulkan_bitflags! {
#[non_exhaustive]
PipelineLayoutCreateFlags = PipelineLayoutCreateFlags(u32);
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PushConstantRange {
pub stages: ShaderStages,
pub offset: u32,
pub size: u32,
}
impl Default for PushConstantRange {
#[inline]
fn default() -> Self {
Self {
stages: ShaderStages::empty(),
offset: 0,
size: 0,
}
}
}
impl PushConstantRange {
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let &Self {
stages,
offset,
size,
} = self;
stages.validate_device(device).map_err(|err| {
err.add_context("stages")
.set_vuids(&["VUID-VkPushConstantRange-stageFlags-parameter"])
})?;
if stages.is_empty() {
return Err(Box::new(ValidationError {
context: "stages".into(),
problem: "is empty".into(),
vuids: &["VUID-VkPushConstantRange-stageFlags-requiredbitmask"],
..Default::default()
}));
}
let max_push_constants_size = device
.physical_device()
.properties()
.max_push_constants_size;
if offset >= max_push_constants_size {
return Err(Box::new(ValidationError {
context: "offset".into(),
problem: "is not less than the `max_push_constants_size` limit".into(),
vuids: &["VUID-VkPushConstantRange-offset-00294"],
..Default::default()
}));
}
if offset % 4 != 0 {
return Err(Box::new(ValidationError {
context: "offset".into(),
problem: "is not a multiple of 4".into(),
vuids: &["VUID-VkPushConstantRange-offset-00295"],
..Default::default()
}));
}
if size == 0 {
return Err(Box::new(ValidationError {
context: "size".into(),
problem: "is zero".into(),
vuids: &["VUID-VkPushConstantRange-size-00296"],
..Default::default()
}));
}
if size % 4 != 0 {
return Err(Box::new(ValidationError {
context: "size".into(),
problem: "is not a multiple of 4".into(),
vuids: &["VUID-VkPushConstantRange-size-00297"],
..Default::default()
}));
}
if size > max_push_constants_size - offset {
return Err(Box::new(ValidationError {
problem: "`size` is greater than `max_push_constants_size` limit minus `offset`"
.into(),
vuids: &["VUID-VkPushConstantRange-size-00298"],
..Default::default()
}));
}
Ok(())
}
}
#[derive(Clone, Debug)]
pub struct PipelineDescriptorSetLayoutCreateInfo {
pub flags: PipelineLayoutCreateFlags,
pub set_layouts: Vec<DescriptorSetLayoutCreateInfo>,
pub push_constant_ranges: Vec<PushConstantRange>,
}
impl PipelineDescriptorSetLayoutCreateInfo {
pub fn from_stages<'a>(
stages: impl IntoIterator<Item = &'a PipelineShaderStageCreateInfo>,
) -> Self {
let mut descriptor_binding_requirements: HashMap<
(u32, u32),
DescriptorBindingRequirements,
> = HashMap::default();
let mut max_set_num = 0;
let mut push_constant_ranges: Vec<PushConstantRange> = Vec::new();
for stage in stages {
let entry_point_info = stage.entry_point.info();
for (&(set_num, binding_num), reqs) in &entry_point_info.descriptor_binding_requirements
{
max_set_num = max(max_set_num, set_num);
match descriptor_binding_requirements.entry((set_num, binding_num)) {
Entry::Occupied(entry) => {
entry.into_mut().merge(reqs).expect("Could not produce an intersection of the shader descriptor requirements");
}
Entry::Vacant(entry) => {
entry.insert(reqs.clone());
}
}
}
if let Some(range) = &entry_point_info.push_constant_requirements {
if let Some(existing_range) =
push_constant_ranges.iter_mut().find(|existing_range| {
existing_range.offset == range.offset && existing_range.size == range.size
})
{
existing_range.stages |= range.stages;
} else {
push_constant_ranges.push(*range)
}
}
}
let mut set_layouts =
vec![DescriptorSetLayoutCreateInfo::default(); max_set_num as usize + 1];
for ((set_num, binding_num), reqs) in descriptor_binding_requirements {
set_layouts[set_num as usize]
.bindings
.insert(binding_num, DescriptorSetLayoutBinding::from(&reqs));
}
Self {
flags: PipelineLayoutCreateFlags::empty(),
set_layouts,
push_constant_ranges,
}
}
pub fn into_pipeline_layout_create_info(
self,
device: Arc<Device>,
) -> Result<PipelineLayoutCreateInfo, IntoPipelineLayoutCreateInfoError> {
let PipelineDescriptorSetLayoutCreateInfo {
flags,
set_layouts,
push_constant_ranges,
} = self;
let set_layouts = set_layouts
.into_iter()
.enumerate()
.map(|(set_num, create_info)| {
DescriptorSetLayout::new(device.clone(), create_info).map_err(|error| {
IntoPipelineLayoutCreateInfoError {
set_num: set_num as u32,
error,
}
})
})
.collect::<Result<Vec<_>, _>>()?;
Ok(PipelineLayoutCreateInfo {
flags,
set_layouts,
push_constant_ranges,
_ne: crate::NonExhaustive(()),
})
}
}
#[derive(Clone, Debug)]
pub struct IntoPipelineLayoutCreateInfoError {
pub set_num: u32,
pub error: Validated<VulkanError>,
}
impl Display for IntoPipelineLayoutCreateInfoError {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"an error occurred while creating a descriptor set layout for set number {}",
self.set_num
)
}
}
impl Error for IntoPipelineLayoutCreateInfoError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
Some(&self.error)
}
}
#[cfg(test)]
mod tests {
use super::PipelineLayout;
use crate::{
pipeline::layout::{PipelineLayoutCreateInfo, PushConstantRange},
shader::ShaderStages,
};
#[test]
fn push_constant_ranges_disjoint() {
let test_cases = [
(
&[
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,
},
][..],
),
(
&[
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,
},
][..],
),
(
&[
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);
}
}
}