#[allow(deprecated)]
pub use self::{
buffers::BuffersDefinition,
collection::VertexBuffersCollection,
definition::VertexDefinition,
impl_vertex::VertexMember,
vertex::{Vertex, VertexBufferDescription, VertexMemberInfo},
};
use super::color_blend::ColorComponents;
use crate::{
device::Device,
format::{Format, FormatFeatures},
pipeline::inout_interface::{ShaderInterfaceLocationInfo, ShaderInterfaceLocationWidth},
DeviceSize, Requires, RequiresAllOf, RequiresOneOf, ValidationError,
};
use foldhash::HashMap;
use smallvec::SmallVec;
mod buffers;
mod collection;
mod definition;
mod impl_vertex;
mod vertex;
#[derive(Clone, Debug)]
pub struct VertexInputState {
pub bindings: HashMap<u32, VertexInputBindingDescription>,
pub attributes: HashMap<u32, VertexInputAttributeDescription>,
pub _ne: crate::NonExhaustive,
}
impl Default for VertexInputState {
#[inline]
fn default() -> Self {
Self::new()
}
}
impl VertexInputState {
#[inline]
pub fn new() -> VertexInputState {
VertexInputState {
bindings: Default::default(),
attributes: Default::default(),
_ne: crate::NonExhaustive(()),
}
}
#[inline]
pub fn binding(mut self, binding: u32, description: VertexInputBindingDescription) -> Self {
self.bindings.insert(binding, description);
self
}
pub fn bindings(
mut self,
bindings: impl IntoIterator<Item = (u32, VertexInputBindingDescription)>,
) -> Self {
self.bindings = bindings.into_iter().collect();
self
}
#[inline]
pub fn attribute(
mut self,
location: u32,
description: VertexInputAttributeDescription,
) -> Self {
self.attributes.insert(location, description);
self
}
pub fn attributes(
mut self,
attributes: impl IntoIterator<Item = (u32, VertexInputAttributeDescription)>,
) -> Self {
self.attributes = attributes.into_iter().collect();
self
}
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let Self {
bindings,
attributes,
_ne: _,
} = self;
let properties = device.physical_device().properties();
if bindings.len() > properties.max_vertex_input_bindings as usize {
return Err(Box::new(ValidationError {
context: "bindings".into(),
problem: "the length exceeds the `max_vertex_input_bindings` limit".into(),
vuids: &[
"VUID-VkPipelineVertexInputStateCreateInfo-vertexBindingDescriptionCount-00613",
"VUID-vkCmdSetVertexInputEXT-vertexBindingDescriptionCount-04791",
],
..Default::default()
}));
}
for (&binding, binding_desc) in bindings {
if binding >= properties.max_vertex_input_bindings {
return Err(Box::new(ValidationError {
context: format!("bindings[{}]", binding).into(),
problem: format!(
"the binding {} exceeds the `max_vertex_input_bindings` limit",
binding
)
.into(),
vuids: &[
"VUID-VkVertexInputBindingDescription-binding-00618",
"VUID-VkVertexInputBindingDescription2EXT-binding-04796",
],
..Default::default()
}));
}
binding_desc
.validate(device)
.map_err(|err| err.add_context(format!("bindings[{}]", binding)))?;
}
if attributes.len() > properties.max_vertex_input_attributes as usize {
return Err(Box::new(ValidationError {
context: "attributes".into(),
problem: "the length exceeds the `max_vertex_input_attributes` limit".into(),
vuids: &[
"VUID-VkPipelineVertexInputStateCreateInfo-vertexAttributeDescriptionCount-00614",
"VUID-vkCmdSetVertexInputEXT-vertexAttributeDescriptionCount-04792",
],
..Default::default()
}));
}
for (&location, attribute_desc) in attributes {
if location >= properties.max_vertex_input_attributes {
return Err(Box::new(ValidationError {
context: format!("attributes[{}]", location).into(),
problem: format!(
"the location {} exceeds the `max_vertex_input_attributes` limit",
location
)
.into(),
vuids: &[
"VUID-VkVertexInputAttributeDescription-location-00620",
"VUID-VkVertexInputAttributeDescription2EXT-location-06228",
],
..Default::default()
}));
}
attribute_desc
.validate(device)
.map_err(|err| err.add_context(format!("attributes[{}]", location)))?;
let &VertexInputAttributeDescription {
binding,
format,
offset,
_ne: _,
} = attribute_desc;
let binding_desc = bindings.get(&binding).ok_or_else(|| {
Box::new(ValidationError {
problem: format!(
"`attributes[{}].binding` is not present in `bindings`",
binding
)
.into(),
vuids: &[
"VUID-VkPipelineVertexInputStateCreateInfo-binding-00615",
"VUID-vkCmdSetVertexInputEXT-binding-04793",
],
..Default::default()
})
})?;
if device.enabled_extensions().khr_portability_subset
&& !device
.enabled_features()
.vertex_attribute_access_beyond_stride
&& offset as DeviceSize + format.block_size() > binding_desc.stride as DeviceSize
{
return Err(Box::new(ValidationError {
problem: format!(
"this device is a portability subset device, and \
`attributes[{0}].offset + attributes[{0}].format.block_size()` \
is greater than `bindings[attributes[{0}]].stride`",
location,
)
.into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceFeature(
"vertex_attribute_access_beyond_stride",
)])]),
vuids: &[
"VUID-VkVertexInputAttributeDescription-vertexAttributeAccessBeyondStride-04457",
"VUID-VkVertexInputAttributeDescription2EXT-vertexAttributeAccessBeyondStride-04806",
],
..Default::default()
}));
}
}
let unassigned_locations = attributes
.iter()
.filter(|&(_, attribute_desc)| attribute_desc.format.locations() == 2)
.map(|(location, _)| location + 1);
for location in unassigned_locations {
if !attributes.get(&location).is_none() {
return Err(Box::new(ValidationError {
problem: format!(
"`attributes[{}].format` takes up two locations, but \
`attributes` also contains a description for location {}",
location - 1, location,
)
.into(),
vuids: &[
"VUID-VkPipelineVertexInputStateCreateInfo-pVertexAttributeDescriptions-00617",
"VUID-vkCmdSetVertexInputEXT-pVertexAttributeDescriptions-04795",
],
..Default::default()
}));
}
}
Ok(())
}
pub(crate) fn validate_required_vertex_inputs(
&self,
vertex_shader_inputs: &HashMap<u32, ShaderInterfaceLocationInfo>,
vuids: RequiredVertexInputsVUIDs,
) -> Result<(), Box<ValidationError>> {
for (&location, location_info) in vertex_shader_inputs {
let (is_previous, attribute_desc) = self
.attributes
.get(&location)
.map(|d| (false, d))
.or_else(|| {
location.checked_sub(1).and_then(|location| {
self.attributes
.get(&location)
.filter(|attribute_desc| attribute_desc.format.locations() == 2)
.map(|d| (true, d))
})
})
.ok_or_else(|| {
Box::new(ValidationError {
problem: format!(
"the vertex shader has an input variable with location {0}, but \
the vertex input attributes do not contain {0}",
location,
)
.into(),
vuids: vuids.not_present,
..Default::default()
})
})?;
let attribute_numeric_type = attribute_desc
.format
.numeric_format_color()
.unwrap()
.numeric_type();
if attribute_numeric_type != location_info.numeric_type {
return Err(Box::new(ValidationError {
problem: format!(
"the numeric type of the format of vertex input attribute {0} ({1:?}) \
does not equal the numeric type of the vertex shader input variable with \
location {0} ({2:?})",
location, attribute_numeric_type, location_info.numeric_type,
)
.into(),
vuids: vuids.numeric_type,
..Default::default()
}));
}
let attribute_components = attribute_desc.format.components();
match location_info.width {
ShaderInterfaceLocationWidth::Bits32 => {
if attribute_components[0] > 32 {
return Err(Box::new(ValidationError {
problem: format!(
"the vertex shader input variable location {0} requires a non-64-bit \
format, but the format of vertex input attribute {0} is 64-bit",
location,
)
.into(),
vuids: vuids.requires32,
..Default::default()
}));
}
}
ShaderInterfaceLocationWidth::Bits64 => {
if attribute_components[0] <= 32 {
return Err(Box::new(ValidationError {
problem: format!(
"the vertex shader input variable location {0} requires a 64-bit \
format, but the format of vertex input attribute {0} is not 64-bit",
location,
)
.into(),
vuids: vuids.requires64,
..Default::default()
}));
}
if location_info.components[0]
.intersects(ColorComponents::B | ColorComponents::A)
{
let second_half_attribute_component = if is_previous { 3 } else { 1 };
if attribute_components[second_half_attribute_component] != 64 {
return Err(Box::new(ValidationError {
problem: format!(
"the vertex shader input variable location {0} requires a format \
with at least {1} 64-bit components, but the format of \
vertex input attribute {0} contains only {2} components",
location,
second_half_attribute_component + 1,
attribute_components.into_iter().filter(|&c| c != 0).count(),
)
.into(),
vuids: vuids.requires_second_half,
..Default::default()
}));
}
}
}
}
}
Ok(())
}
pub(crate) fn to_vk<'a>(
&self,
fields1_vk: &'a VertexInputStateFields1Vk,
extensions_vk: &'a mut VertexInputStateExtensionsVk<'_>,
) -> ash::vk::PipelineVertexInputStateCreateInfo<'a> {
let VertexInputStateFields1Vk {
bindings_vk,
attributes_vk,
binding_divisors_vk: _,
} = fields1_vk;
let mut val_vk = ash::vk::PipelineVertexInputStateCreateInfo::default()
.flags(ash::vk::PipelineVertexInputStateCreateFlags::empty())
.vertex_binding_descriptions(bindings_vk)
.vertex_attribute_descriptions(attributes_vk);
let VertexInputStateExtensionsVk { divisor_vk } = extensions_vk;
if let Some(next) = divisor_vk {
val_vk = val_vk.push_next(next);
}
val_vk
}
pub(crate) fn to_vk_extensions<'a>(
&self,
fields1_vk: &'a VertexInputStateFields1Vk,
) -> VertexInputStateExtensionsVk<'a> {
let VertexInputStateFields1Vk {
bindings_vk: _,
attributes_vk: _,
binding_divisors_vk,
} = fields1_vk;
let divisor_vk = (!binding_divisors_vk.is_empty()).then(|| {
ash::vk::PipelineVertexInputDivisorStateCreateInfoEXT::default()
.vertex_binding_divisors(binding_divisors_vk)
});
VertexInputStateExtensionsVk { divisor_vk }
}
pub(crate) fn to_vk_fields1(&self) -> VertexInputStateFields1Vk {
let Self {
bindings,
attributes,
_ne: _,
} = self;
let mut bindings_vk = SmallVec::with_capacity(bindings.len());
let mut binding_divisors_vk = SmallVec::new();
for (&binding, binding_desc) in bindings {
bindings_vk.push(binding_desc.to_vk(binding));
if let Some(divisor_vk) = binding_desc.to_vk_divisor(binding) {
binding_divisors_vk.push(divisor_vk);
}
}
let attributes_vk = attributes
.iter()
.map(|(&location, attribute_desc)| attribute_desc.to_vk(location))
.collect();
VertexInputStateFields1Vk {
bindings_vk,
attributes_vk,
binding_divisors_vk,
}
}
}
pub(crate) struct VertexInputStateExtensionsVk<'a> {
pub(crate) divisor_vk: Option<ash::vk::PipelineVertexInputDivisorStateCreateInfoEXT<'a>>,
}
pub(crate) struct VertexInputStateFields1Vk {
pub(crate) bindings_vk: SmallVec<[ash::vk::VertexInputBindingDescription; 8]>,
pub(crate) attributes_vk: SmallVec<[ash::vk::VertexInputAttributeDescription; 8]>,
pub(crate) binding_divisors_vk: SmallVec<[ash::vk::VertexInputBindingDivisorDescriptionKHR; 8]>,
}
#[derive(Clone, Debug)]
pub struct VertexInputBindingDescription {
pub stride: u32,
pub input_rate: VertexInputRate,
pub _ne: crate::NonExhaustive,
}
impl Default for VertexInputBindingDescription {
#[inline]
fn default() -> Self {
Self {
stride: 0,
input_rate: VertexInputRate::Vertex,
_ne: crate::NonExhaustive(()),
}
}
}
impl VertexInputBindingDescription {
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let &Self {
stride,
input_rate,
_ne: _,
} = self;
let properties = device.physical_device().properties();
if stride > properties.max_vertex_input_binding_stride {
return Err(Box::new(ValidationError {
context: "stride".into(),
problem: "exceeds the `max_vertex_input_binding_stride` limit".into(),
vuids: &[
"VUID-VkVertexInputBindingDescription-stride-00619",
"VUID-VkVertexInputBindingDescription2EXT-stride-04797",
],
..Default::default()
}));
}
if device.enabled_extensions().khr_portability_subset
&& (stride == 0
|| stride
% properties
.min_vertex_input_binding_stride_alignment
.unwrap()
!= 0)
{
return Err(Box::new(ValidationError {
problem: "this device is a portability subset device, and \
`stride` is not a multiple of, and at least as large as, the \
`min_vertex_input_binding_stride_alignment` limit"
.into(),
vuids: &["VUID-VkVertexInputBindingDescription-stride-04456"],
..Default::default()
}));
}
match input_rate {
VertexInputRate::Instance { divisor } if divisor != 1 => {
if !device
.enabled_features()
.vertex_attribute_instance_rate_divisor
{
return Err(Box::new(ValidationError {
context: "input_rate".into(),
problem: "is `VertexInputRate::Instance`, and \
its `divisor` value is not 1".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceFeature(
"vertex_attribute_instance_rate_divisor",
)])]),
vuids: &[
"VUID-VkVertexInputBindingDivisorDescriptionEXT-vertexAttributeInstanceRateDivisor-02229",
"VUID-VkVertexInputBindingDescription2EXT-divisor-04799",
],
}));
}
if divisor == 0
&& !device
.enabled_features()
.vertex_attribute_instance_rate_zero_divisor
{
return Err(Box::new(ValidationError {
context: "input_rate".into(),
problem: "is `VertexInputRate::Instance`, and \
its `divisor` value is 0".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::DeviceFeature(
"vertex_attribute_instance_rate_zero_divisor",
)])]),
vuids: &[
"VUID-VkVertexInputBindingDivisorDescriptionEXT-vertexAttributeInstanceRateZeroDivisor-02228",
"VUID-VkVertexInputBindingDescription2EXT-divisor-04798",
],
}));
}
if divisor > properties.max_vertex_attrib_divisor.unwrap() {
return Err(Box::new(ValidationError {
context: "input_rate".into(),
problem: "is `VertexInputRate::Instance`, and \
its `divisor` value exceeds the `max_vertex_attrib_divisor` limit"
.into(),
vuids: &[
"VUID-VkVertexInputBindingDivisorDescriptionEXT-divisor-01870",
"VUID-VkVertexInputBindingDescription2EXT-divisor-06226",
],
..Default::default()
}));
}
}
_ => (),
}
Ok(())
}
pub(crate) fn to_vk2(
&self,
binding_vk: u32,
) -> ash::vk::VertexInputBindingDescription2EXT<'static> {
let &Self {
stride,
input_rate,
_ne: _,
} = self;
let (input_rate_vk, divisor_vk) = input_rate.to_vk();
ash::vk::VertexInputBindingDescription2EXT::default()
.binding(binding_vk)
.stride(stride)
.input_rate(input_rate_vk)
.divisor(divisor_vk)
}
pub(crate) fn to_vk(&self, binding_vk: u32) -> ash::vk::VertexInputBindingDescription {
let &Self {
stride,
input_rate,
_ne: _,
} = self;
let (input_rate_vk, _) = input_rate.to_vk();
ash::vk::VertexInputBindingDescription::default()
.binding(binding_vk)
.stride(stride)
.input_rate(input_rate_vk)
}
pub(crate) fn to_vk_divisor(
&self,
binding_vk: u32,
) -> Option<ash::vk::VertexInputBindingDivisorDescriptionKHR> {
match self.input_rate {
VertexInputRate::Instance { divisor } if divisor != 1 => Some(
ash::vk::VertexInputBindingDivisorDescriptionKHR::default()
.binding(binding_vk)
.divisor(divisor),
),
_ => None,
}
}
}
#[derive(Clone, Debug)]
pub struct VertexInputAttributeDescription {
pub binding: u32,
pub format: Format,
pub offset: u32,
pub _ne: crate::NonExhaustive,
}
impl Default for VertexInputAttributeDescription {
#[inline]
fn default() -> Self {
Self {
binding: 0,
format: Format::UNDEFINED,
offset: 0,
_ne: crate::NonExhaustive(()),
}
}
}
impl VertexInputAttributeDescription {
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let &Self {
binding,
format,
offset,
_ne: _,
} = self;
let properties = device.physical_device().properties();
format.validate_device(device).map_err(|err| {
err.add_context("format").set_vuids(&[
"VUID-VkVertexInputAttributeDescription-format-parameter",
"VUID-VkVertexInputAttributeDescription2EXT-format-parameter",
])
})?;
if binding > properties.max_vertex_input_bindings {
return Err(Box::new(ValidationError {
context: "binding".into(),
problem: "exceeds the `max_vertex_input_bindings` limit".into(),
vuids: &[
"VUID-VkVertexInputAttributeDescription-binding-00621",
"VUID-VkVertexInputAttributeDescription2EXT-binding-06229",
],
..Default::default()
}));
}
if offset > properties.max_vertex_input_attribute_offset {
return Err(Box::new(ValidationError {
context: "offset".into(),
problem: "exceeds the `max_vertex_input_attribute_offset` limit".into(),
vuids: &[
"VUID-VkVertexInputAttributeDescription-offset-00622",
"VUID-VkVertexInputAttributeDescription2EXT-offset-06230",
],
..Default::default()
}));
}
let format_properties =
unsafe { device.physical_device().format_properties_unchecked(format) };
let format_features = format_properties.buffer_features;
if !format_features.intersects(FormatFeatures::VERTEX_BUFFER) {
return Err(Box::new(ValidationError {
context: "format".into(),
problem: "the format features do not include `FormatFeatures::VERTEX_BUFFER`"
.into(),
vuids: &[
"VUID-VkVertexInputAttributeDescription-format-00623",
"VUID-VkVertexInputAttributeDescription2EXT-format-04805",
],
..Default::default()
}));
}
Ok(())
}
pub(crate) fn to_vk2(
&self,
location_vk: u32,
) -> ash::vk::VertexInputAttributeDescription2EXT<'static> {
let &Self {
binding,
format,
offset,
_ne: _,
} = self;
ash::vk::VertexInputAttributeDescription2EXT::default()
.location(location_vk)
.binding(binding)
.format(format.into())
.offset(offset)
}
pub(crate) fn to_vk(&self, location_vk: u32) -> ash::vk::VertexInputAttributeDescription {
let &Self {
binding,
format,
offset,
_ne: _,
} = self;
ash::vk::VertexInputAttributeDescription::default()
.location(location_vk)
.binding(binding)
.format(format.into())
.offset(offset)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum VertexInputRate {
Vertex,
Instance { divisor: u32 },
}
impl VertexInputRate {
#[allow(clippy::trivially_copy_pass_by_ref, clippy::wrong_self_convention)]
#[doc(hidden)]
pub fn to_vk(&self) -> (ash::vk::VertexInputRate, u32) {
match *self {
VertexInputRate::Vertex => (ash::vk::VertexInputRate::VERTEX, 1),
VertexInputRate::Instance { divisor } => (ash::vk::VertexInputRate::INSTANCE, divisor),
}
}
}
pub(crate) struct RequiredVertexInputsVUIDs {
pub(crate) not_present: &'static [&'static str],
pub(crate) numeric_type: &'static [&'static str],
pub(crate) requires32: &'static [&'static str],
pub(crate) requires64: &'static [&'static str],
pub(crate) requires_second_half: &'static [&'static str],
}