Improve validation for vertex input, add dynamic state (#2417)

This commit is contained in:
Rua 2023-12-01 18:10:13 +01:00 committed by GitHub
parent a17d8f5cb5
commit 80e4afe073
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 951 additions and 152 deletions

View File

@ -24,6 +24,7 @@ use crate::{
input_assembly::PrimitiveTopology,
rasterization::{CullMode, DepthBiasState, FrontFace, LineStipple},
subpass::PipelineRenderingCreateInfo,
vertex_input::VertexInputState,
viewport::{Scissor, Viewport},
},
ComputePipeline, DynamicState, GraphicsPipeline, PipelineBindPoint, PipelineLayout,
@ -1318,6 +1319,7 @@ pub(in crate::command_buffer) struct CommandBufferBuilderState {
pub(in crate::command_buffer) stencil_reference: StencilStateDynamic,
pub(in crate::command_buffer) stencil_test_enable: Option<bool>,
pub(in crate::command_buffer) stencil_write_mask: StencilStateDynamic,
pub(in crate::command_buffer) vertex_input: Option<VertexInputState>,
pub(in crate::command_buffer) viewport: HashMap<u32, Viewport>,
pub(in crate::command_buffer) viewport_with_count: Option<SmallVec<[Viewport; 2]>>,
@ -1369,7 +1371,7 @@ impl CommandBufferBuilderState {
DynamicState::StencilReference => self.stencil_reference = Default::default(),
DynamicState::StencilTestEnable => self.stencil_test_enable = None,
DynamicState::StencilWriteMask => self.stencil_write_mask = Default::default(),
// DynamicState::VertexInput => todo!(),
DynamicState::VertexInput => self.vertex_input = None,
// DynamicState::VertexInputBindingStride => todo!(),
DynamicState::Viewport => self.viewport.clear(),
// DynamicState::ViewportCoarseSampleOrder => todo!(),

View File

@ -13,10 +13,10 @@ use crate::{
graphics::vertex_input::VertexBuffersCollection, ComputePipeline, GraphicsPipeline,
PipelineBindPoint, PipelineLayout,
},
DeviceSize, Requires, RequiresAllOf, RequiresOneOf, ValidationError, VulkanObject,
DeviceSize, Requires, RequiresAllOf, RequiresOneOf, ValidationError, Version, VulkanObject,
};
use smallvec::SmallVec;
use std::{cmp::min, ffi::c_void, mem::size_of, sync::Arc};
use std::{cmp::min, ffi::c_void, mem::size_of, ptr, sync::Arc};
/// # Commands to bind or push state for pipeline execution commands.
///
@ -954,19 +954,58 @@ impl UnsafeCommandBufferBuilder {
return self;
}
let (buffers_vk, offsets_vk): (SmallVec<[_; 2]>, SmallVec<[_; 2]>) = vertex_buffers
.iter()
.map(|buffer| (buffer.buffer().handle(), buffer.offset()))
.unzip();
let device = self.device();
let fns = self.device().fns();
(fns.v1_0.cmd_bind_vertex_buffers)(
self.handle(),
first_binding,
buffers_vk.len() as u32,
buffers_vk.as_ptr(),
offsets_vk.as_ptr(),
);
if device.api_version() >= Version::V1_3
|| device.enabled_extensions().ext_extended_dynamic_state
|| device.enabled_extensions().ext_shader_object
{
let mut buffers_vk: SmallVec<[_; 2]> = SmallVec::with_capacity(vertex_buffers.len());
let mut offsets_vk: SmallVec<[_; 2]> = SmallVec::with_capacity(vertex_buffers.len());
let mut sizes_vk: SmallVec<[_; 2]> = SmallVec::with_capacity(vertex_buffers.len());
for buffer in vertex_buffers {
buffers_vk.push(buffer.buffer().handle());
offsets_vk.push(buffer.offset());
sizes_vk.push(buffer.size());
}
let fns = self.device().fns();
let cmd_bind_vertex_buffers2 = if device.api_version() >= Version::V1_3 {
fns.v1_3.cmd_bind_vertex_buffers2
} else if device.enabled_extensions().ext_extended_dynamic_state {
fns.ext_extended_dynamic_state.cmd_bind_vertex_buffers2_ext
} else {
fns.ext_shader_object.cmd_bind_vertex_buffers2_ext
};
cmd_bind_vertex_buffers2(
self.handle(),
first_binding,
buffers_vk.len() as u32,
buffers_vk.as_ptr(),
offsets_vk.as_ptr(),
sizes_vk.as_ptr(),
ptr::null(),
)
} else {
let mut buffers_vk: SmallVec<[_; 2]> = SmallVec::with_capacity(vertex_buffers.len());
let mut offsets_vk: SmallVec<[_; 2]> = SmallVec::with_capacity(vertex_buffers.len());
for buffer in vertex_buffers {
buffers_vk.push(buffer.buffer().handle());
offsets_vk.push(buffer.offset());
}
let fns = self.device().fns();
(fns.v1_0.cmd_bind_vertex_buffers)(
self.handle(),
first_binding,
buffers_vk.len() as u32,
buffers_vk.as_ptr(),
offsets_vk.as_ptr(),
);
}
self
}

View File

@ -7,6 +7,10 @@ use crate::{
depth_stencil::{CompareOp, StencilFaces, StencilOp, StencilOps},
input_assembly::PrimitiveTopology,
rasterization::{CullMode, DepthBiasState, FrontFace, LineStipple},
vertex_input::{
VertexInputAttributeDescription, VertexInputBindingDescription, VertexInputRate,
VertexInputState,
},
viewport::{Scissor, Viewport},
},
DynamicState,
@ -1069,6 +1073,46 @@ impl<L> AutoCommandBufferBuilder<L> {
self
}
/// Sets the dynamic vertex input for future draw calls.
#[inline]
pub fn set_vertex_input(
&mut self,
vertex_input_state: VertexInputState,
) -> Result<&mut Self, Box<ValidationError>> {
self.validate_set_vertex_input(&vertex_input_state)?;
unsafe { Ok(self.set_vertex_input_unchecked(vertex_input_state)) }
}
fn validate_set_vertex_input(
&self,
vertex_input_state: &VertexInputState,
) -> Result<(), Box<ValidationError>> {
self.inner.validate_set_vertex_input(vertex_input_state)?;
self.validate_graphics_pipeline_fixed_state(DynamicState::VertexInput)?;
Ok(())
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
pub unsafe fn set_vertex_input_unchecked(
&mut self,
vertex_input_state: VertexInputState,
) -> &mut Self {
self.builder_state.vertex_input = Some(vertex_input_state.clone());
self.add_command(
"set_vertex_input",
Default::default(),
move |out: &mut UnsafeCommandBufferBuilder| {
out.set_vertex_input_unchecked(&vertex_input_state);
},
);
self
}
/// Sets the dynamic viewports for future draw calls.
pub fn set_viewport(
&mut self,
@ -2837,6 +2881,121 @@ impl UnsafeCommandBufferBuilder {
self
}
#[inline]
pub unsafe fn set_vertex_input(
&mut self,
vertex_input_state: &VertexInputState,
) -> Result<&mut Self, Box<ValidationError>> {
self.validate_set_vertex_input(vertex_input_state)?;
Ok(self.set_vertex_input_unchecked(vertex_input_state))
}
fn validate_set_vertex_input(
&self,
vertex_input_state: &VertexInputState,
) -> Result<(), Box<ValidationError>> {
if !(self.device().enabled_features().vertex_input_dynamic_state
|| self.device().enabled_features().shader_object)
{
return Err(Box::new(ValidationError {
requires_one_of: RequiresOneOf(&[
RequiresAllOf(&[Requires::Feature("vertex_input_dynamic_state")]),
RequiresAllOf(&[Requires::Feature("shader_object")]),
]),
vuids: &["VUID-vkCmdSetVertexInputEXT-None-08546"],
..Default::default()
}));
}
if !self
.queue_family_properties()
.queue_flags
.intersects(QueueFlags::GRAPHICS)
{
return Err(Box::new(ValidationError {
problem: "the queue family of the command buffer does not support \
graphics operations"
.into(),
vuids: &["VUID-vkCmdSetVertexInputEXT-commandBuffer-cmdpool"],
..Default::default()
}));
}
vertex_input_state
.validate(self.device())
.map_err(|err| err.add_context("vertex_input_state"))?;
Ok(())
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
pub unsafe fn set_vertex_input_unchecked(
&mut self,
vertex_input_state: &VertexInputState,
) -> &mut Self {
let mut vertex_binding_descriptions_vk: SmallVec<[_; 8]> = SmallVec::new();
let mut vertex_attribute_descriptions_vk: SmallVec<[_; 8]> = SmallVec::new();
let VertexInputState {
bindings,
attributes,
_ne: _,
} = vertex_input_state;
vertex_binding_descriptions_vk.extend(bindings.iter().map(|(&binding, binding_desc)| {
let &VertexInputBindingDescription {
stride,
input_rate,
_ne: _,
} = binding_desc;
let divisor = match input_rate {
// VUID-VkVertexInputBindingDescription2EXT-divisor-06227
VertexInputRate::Vertex => 1,
VertexInputRate::Instance { divisor } => divisor,
};
ash::vk::VertexInputBindingDescription2EXT {
binding,
stride,
input_rate: input_rate.into(),
divisor,
..Default::default()
}
}));
vertex_attribute_descriptions_vk.extend(attributes.iter().map(
|(&location, attribute_desc)| {
let &VertexInputAttributeDescription {
binding,
format,
offset,
_ne: _,
} = attribute_desc;
ash::vk::VertexInputAttributeDescription2EXT {
location,
binding,
format: format.into(),
offset,
..Default::default()
}
},
));
let fns = self.device().fns();
(fns.ext_vertex_input_dynamic_state.cmd_set_vertex_input_ext)(
self.handle(),
vertex_binding_descriptions_vk.len() as u32,
vertex_binding_descriptions_vk.as_ptr(),
vertex_attribute_descriptions_vk.len() as u32,
vertex_attribute_descriptions_vk.as_ptr(),
);
self
}
#[inline]
pub unsafe fn set_viewport(
&mut self,

View File

@ -16,8 +16,9 @@ use crate::{
image::{sampler::Sampler, view::ImageView, ImageAspects, ImageLayout, SampleCount},
pipeline::{
graphics::{
input_assembly::PrimitiveTopology, subpass::PipelineSubpassType,
vertex_input::VertexInputRate,
input_assembly::PrimitiveTopology,
subpass::PipelineSubpassType,
vertex_input::{self, RequiredVertexInputsVUIDs, VertexInputRate},
},
DynamicState, GraphicsPipeline, Pipeline, PipelineLayout,
},
@ -270,7 +271,13 @@ impl<L> AutoCommandBufferBuilder<L> {
}
}
for (&binding_num, binding_desc) in &pipeline.vertex_input_state().bindings {
let vertex_input_state = pipeline
.dynamic_state()
.contains(&DynamicState::VertexInput)
.then(|| self.builder_state.vertex_input.as_ref().unwrap())
.unwrap_or_else(|| pipeline.vertex_input_state().unwrap());
for (&binding_num, binding_desc) in &vertex_input_state.bindings {
let vertex_buffer = &self.builder_state.vertex_buffers[&binding_num];
// Per spec:
@ -604,7 +611,13 @@ impl<L> AutoCommandBufferBuilder<L> {
}
}
for (&binding_num, binding_desc) in &pipeline.vertex_input_state().bindings {
let vertex_input_state = pipeline
.dynamic_state()
.contains(&DynamicState::VertexInput)
.then(|| self.builder_state.vertex_input.as_ref().unwrap())
.unwrap_or_else(|| pipeline.vertex_input_state().unwrap());
for (&binding_num, binding_desc) in &vertex_input_state.bindings {
let vertex_buffer = &self.builder_state.vertex_buffers[&binding_num];
// Per spec:
@ -2148,7 +2161,45 @@ impl<L> AutoCommandBufferBuilder<L> {
}));
}
}
// DynamicState::VertexInput => todo!(),
DynamicState::VertexInput => {
if let Some(vertex_input_state) = &self.builder_state.vertex_input {
vertex_input::validate_required_vertex_inputs(
&vertex_input_state.attributes,
pipeline.required_vertex_inputs().unwrap(),
RequiredVertexInputsVUIDs {
not_present: vuids!(vuid_type, "Input-07939"),
numeric_type: vuids!(vuid_type, "Input-08734"),
requires32: vuids!(vuid_type, "format-08936"),
requires64: vuids!(vuid_type, "format-08937"),
requires_second_half: vuids!(vuid_type, "None-09203"),
},
)
.map_err(|mut err| {
err.problem = format!(
"the currently bound graphics pipeline requires the \
`DynamicState::VertexInput` dynamic state, but \
the dynamic vertex input does not meet the requirements of the \
vertex shader in the pipeline: {}",
err.problem,
)
.into();
err
})?;
} else {
return Err(Box::new(ValidationError {
problem: format!(
"the currently bound graphics pipeline requires the \
`DynamicState::{:?}` dynamic state, but \
this state was either not set, or it was overwritten by a \
more recent `bind_pipeline_graphics` command",
dynamic_state
)
.into(),
vuids: vuids!(vuid_type, "None-04914"),
..Default::default()
}));
}
}
// DynamicState::VertexInputBindingStride => todo!(),
DynamicState::Viewport => {
let viewport_state = pipeline.viewport_state().unwrap();
@ -2437,9 +2488,13 @@ impl<L> AutoCommandBufferBuilder<L> {
vuid_type: VUIDType,
pipeline: &GraphicsPipeline,
) -> Result<(), Box<ValidationError>> {
let vertex_input = pipeline.vertex_input_state();
let vertex_input_state = pipeline
.dynamic_state()
.contains(&DynamicState::VertexInput)
.then(|| self.builder_state.vertex_input.as_ref().unwrap())
.unwrap_or_else(|| pipeline.vertex_input_state().unwrap());
for &binding_num in vertex_input.bindings.keys() {
for &binding_num in vertex_input_state.bindings.keys() {
if !self.builder_state.vertex_buffers.contains_key(&binding_num) {
return Err(Box::new(ValidationError {
problem: format!(
@ -2663,20 +2718,24 @@ impl<L> AutoCommandBufferBuilder<L> {
used_resources: &mut Vec<(ResourceUseRef2, Resource)>,
pipeline: &GraphicsPipeline,
) {
used_resources.extend(pipeline.vertex_input_state().bindings.iter().map(
|(&binding, _)| {
let vertex_buffer = &self.builder_state.vertex_buffers[&binding];
(
ResourceInCommand::VertexBuffer { binding }.into(),
Resource::Buffer {
buffer: vertex_buffer.clone(),
range: 0..vertex_buffer.size(), // TODO:
memory_access:
PipelineStageAccessFlags::VertexAttributeInput_VertexAttributeRead,
},
)
},
));
let vertex_input_state = pipeline
.dynamic_state()
.contains(&DynamicState::VertexInput)
.then(|| self.builder_state.vertex_input.as_ref().unwrap())
.unwrap_or_else(|| pipeline.vertex_input_state().unwrap());
used_resources.extend(vertex_input_state.bindings.iter().map(|(&binding, _)| {
let vertex_buffer = &self.builder_state.vertex_buffers[&binding];
(
ResourceInCommand::VertexBuffer { binding }.into(),
Resource::Buffer {
buffer: vertex_buffer.clone(),
range: 0..vertex_buffer.size(), // TODO:
memory_access:
PipelineStageAccessFlags::VertexAttributeInput_VertexAttributeRead,
},
)
}));
}
fn add_index_buffer_resources(&self, used_resources: &mut Vec<(ResourceUseRef2, Resource)>) {

View File

@ -56,7 +56,7 @@ use self::{
rasterization::RasterizationState,
subpass::PipelineSubpassType,
tessellation::TessellationState,
vertex_input::VertexInputState,
vertex_input::{RequiredVertexInputsVUIDs, VertexInputLocationRequirements, VertexInputState},
viewport::ViewportState,
};
use super::{
@ -75,7 +75,9 @@ use crate::{
rasterization::{CullMode, DepthBiasState},
subpass::PipelineRenderingCreateInfo,
tessellation::TessellationDomainOrigin,
vertex_input::VertexInputRate,
vertex_input::{
VertexInputAttributeDescription, VertexInputBindingDescription, VertexInputRate,
},
},
shader::{
spirv::{ExecutionMode, ExecutionModel, Instruction},
@ -116,11 +118,7 @@ pub struct GraphicsPipeline {
flags: PipelineCreateFlags,
// TODO: replace () with an object that describes the shaders in some way.
shaders: HashMap<ShaderStage, ()>,
descriptor_binding_requirements: HashMap<(u32, u32), DescriptorBindingRequirements>,
num_used_descriptor_sets: u32,
fragment_tests_stages: Option<FragmentTestsStages>,
vertex_input_state: VertexInputState,
vertex_input_state: Option<VertexInputState>,
input_assembly_state: InputAssemblyState,
tessellation_state: Option<TessellationState>,
viewport_state: Option<ViewportState>,
@ -134,7 +132,12 @@ pub struct GraphicsPipeline {
discard_rectangle_state: Option<DiscardRectangleState>,
descriptor_binding_requirements: HashMap<(u32, u32), DescriptorBindingRequirements>,
num_used_descriptor_sets: u32,
fixed_state: HashSet<DynamicState>,
fragment_tests_stages: Option<FragmentTestsStages>,
// Note: this is only `Some` if `vertex_input_state` is `None`.
required_vertex_inputs: Option<HashMap<u32, VertexInputLocationRequirements>>,
}
impl GraphicsPipeline {
@ -305,19 +308,36 @@ impl GraphicsPipeline {
} = vertex_input_state;
vertex_binding_descriptions_vk.extend(bindings.iter().map(
|(&binding, binding_desc)| ash::vk::VertexInputBindingDescription {
binding,
stride: binding_desc.stride,
input_rate: binding_desc.input_rate.into(),
|(&binding, binding_desc)| {
let &VertexInputBindingDescription {
stride,
input_rate,
_ne: _,
} = binding_desc;
ash::vk::VertexInputBindingDescription {
binding,
stride,
input_rate: input_rate.into(),
}
},
));
vertex_attribute_descriptions_vk.extend(attributes.iter().map(
|(&location, attribute_desc)| ash::vk::VertexInputAttributeDescription {
location,
binding: attribute_desc.binding,
format: attribute_desc.format.into(),
offset: attribute_desc.offset,
|(&location, attribute_desc)| {
let &VertexInputAttributeDescription {
binding,
format,
offset,
_ne: _,
} = attribute_desc;
ash::vk::VertexInputAttributeDescription {
location,
binding,
format: format.into(),
offset,
}
},
));
@ -902,6 +922,7 @@ impl GraphicsPipeline {
DescriptorBindingRequirements,
> = HashMap::default();
let mut fragment_tests_stages = None;
let mut required_vertex_inputs = None;
for stage in &stages {
let &PipelineShaderStageCreateInfo {
@ -915,22 +936,33 @@ impl GraphicsPipeline {
let spirv = entry_point.module().spirv();
let entry_point_function = spirv.function(entry_point.id());
if matches!(entry_point_info.execution_model, ExecutionModel::Fragment) {
fragment_tests_stages = Some(FragmentTestsStages::Late);
match entry_point_info.execution_model {
ExecutionModel::Vertex => {
if vertex_input_state.is_none() {
required_vertex_inputs = Some(vertex_input::required_vertex_inputs(
entry_point.module().spirv(),
entry_point.id(),
));
}
}
ExecutionModel::Fragment => {
fragment_tests_stages = Some(FragmentTestsStages::Late);
for instruction in entry_point_function.execution_modes() {
if let Instruction::ExecutionMode { mode, .. } = *instruction {
match mode {
ExecutionMode::EarlyFragmentTests => {
fragment_tests_stages = Some(FragmentTestsStages::Early);
for instruction in entry_point_function.execution_modes() {
if let Instruction::ExecutionMode { mode, .. } = *instruction {
match mode {
ExecutionMode::EarlyFragmentTests => {
fragment_tests_stages = Some(FragmentTestsStages::Early);
}
ExecutionMode::EarlyAndLateFragmentTestsAMD => {
fragment_tests_stages = Some(FragmentTestsStages::EarlyAndLate);
}
_ => (),
}
ExecutionMode::EarlyAndLateFragmentTestsAMD => {
fragment_tests_stages = Some(FragmentTestsStages::EarlyAndLate);
}
_ => (),
}
}
}
_ => (),
}
for (&loc, reqs) in &entry_point_info.descriptor_binding_requirements {
@ -1022,11 +1054,7 @@ impl GraphicsPipeline {
flags,
shaders,
descriptor_binding_requirements,
num_used_descriptor_sets,
fragment_tests_stages,
vertex_input_state: vertex_input_state.unwrap(), // Can be None if there's a mesh shader, but we don't support that yet
vertex_input_state,
input_assembly_state: input_assembly_state.unwrap(), // Can be None if there's a mesh shader, but we don't support that yet
tessellation_state,
viewport_state,
@ -1040,7 +1068,11 @@ impl GraphicsPipeline {
discard_rectangle_state,
descriptor_binding_requirements,
num_used_descriptor_sets,
fixed_state,
fragment_tests_stages,
required_vertex_inputs,
})
}
@ -1070,8 +1102,8 @@ impl GraphicsPipeline {
/// Returns the vertex input state used to create this pipeline.
#[inline]
pub fn vertex_input_state(&self) -> &VertexInputState {
&self.vertex_input_state
pub fn vertex_input_state(&self) -> Option<&VertexInputState> {
self.vertex_input_state.as_ref()
}
/// Returns the input assembly state used to create this pipeline.
@ -1145,6 +1177,14 @@ impl GraphicsPipeline {
pub(crate) fn fixed_state(&self) -> &HashSet<DynamicState> {
&self.fixed_state
}
/// Returns the required vertex inputs.
#[inline]
pub(crate) fn required_vertex_inputs(
&self,
) -> Option<&HashMap<u32, VertexInputLocationRequirements>> {
self.required_vertex_inputs.as_ref()
}
}
impl Pipeline for GraphicsPipeline {
@ -1643,11 +1683,14 @@ impl GraphicsPipelineCreateInfo {
_ => (),
}
match (vertex_input_state.is_some(), need_vertex_input_state) {
match (
vertex_input_state.is_some(),
need_vertex_input_state && !dynamic_state.contains(&DynamicState::VertexInput),
) {
(true, false) => {
return Err(Box::new(ValidationError {
problem: "the pipeline is not being created with \
vertex input state, but \
problem: "the pipeline is not being created with vertex input state, or \
`dynamic_state` includes `DynamicState::VertexInput`, but \
`vertex_input_state` is `Some`"
.into(),
..Default::default()
@ -1655,8 +1698,8 @@ impl GraphicsPipelineCreateInfo {
}
(false, true) => {
return Err(Box::new(ValidationError {
problem: "the pipeline is being created with \
vertex input state, but \
problem: "the pipeline is being created with vertex input state, and \
`dynamic_state` does not include `DynamicState::VertexInput`, but \
`vertex_input_state` is `None`"
.into(),
vuids: &["VUID-VkGraphicsPipelineCreateInfo-pStages-02097"],
@ -2357,54 +2400,34 @@ impl GraphicsPipelineCreateInfo {
*/
if let (Some(vertex_stage), Some(vertex_input_state)) = (vertex_stage, vertex_input_state) {
for element in vertex_stage.entry_point.info().input_interface.elements() {
assert!(!element.ty.is_64bit); // TODO: implement
let location_range =
element.location..element.location + element.ty.num_locations();
let required_vertex_inputs = vertex_input::required_vertex_inputs(
vertex_stage.entry_point.module().spirv(),
vertex_stage.entry_point.id(),
);
for location in location_range {
let attribute_desc = match vertex_input_state.attributes.get(&location) {
Some(attribute_desc) => attribute_desc,
None => {
return Err(Box::new(ValidationError {
problem: format!(
"the vertex shader has an input variable with location {0}, but \
`vertex_input_state.attributes` does not contain {0}",
location,
)
.into(),
vuids: &["VUID-VkGraphicsPipelineCreateInfo-Input-07905"],
..Default::default()
}));
}
};
// TODO: Check component assignments too. Multiple variables can occupy the
// same location but in different components.
let shader_type = element.ty.base_type;
let attribute_type = attribute_desc
.format
.numeric_format_color()
.unwrap()
.numeric_type();
// VUID?
if shader_type != attribute_type {
return Err(Box::new(ValidationError {
problem: format!(
"`vertex_input_state.attributes[{}].format` has a different \
numeric type than the vertex shader input variable with \
location {0}",
location,
)
.into(),
vuids: &["VUID-VkGraphicsPipelineCreateInfo-Input-07905"],
..Default::default()
}));
}
}
}
vertex_input::validate_required_vertex_inputs(
&vertex_input_state.attributes,
&required_vertex_inputs,
RequiredVertexInputsVUIDs {
not_present: &["VUID-VkGraphicsPipelineCreateInfo-Input-07904"],
numeric_type: &["VUID-VkGraphicsPipelineCreateInfo-Input-08733"],
requires32: &["VUID-VkGraphicsPipelineCreateInfo-pVertexInputState-08929"],
requires64: &["VUID-VkGraphicsPipelineCreateInfo-pVertexInputState-08930"],
requires_second_half: &[
"VUID-VkGraphicsPipelineCreateInfo-pVertexInputState-09198",
],
},
)
.map_err(|mut err| {
err.problem = format!(
"{}: {}",
"`vertex_input_state` does not meet the requirements \
of the vertex shader in `stages`",
err.problem,
)
.into();
err
})?;
}
if let (Some(_), Some(_)) = (tessellation_control_stage, tessellation_evaluation_stage) {

View File

@ -27,6 +27,7 @@ unsafe impl VertexDefinition for &[VertexBufferDescription] {
VertexInputBindingDescription {
stride: buffer.stride,
input_rate: buffer.input_rate,
..Default::default()
},
)
});
@ -91,6 +92,7 @@ unsafe impl VertexDefinition for &[VertexBufferDescription] {
binding,
format: infos.format,
offset: offset as u32,
..Default::default()
},
));
offset += block_size;

View File

@ -99,10 +99,15 @@ pub use self::{
};
use crate::{
device::Device,
format::{Format, FormatFeatures},
format::{Format, FormatFeatures, NumericType},
shader::{
reflect::get_constant,
spirv::{Decoration, Id, Instruction, Spirv, StorageClass},
},
DeviceSize, Requires, RequiresAllOf, RequiresOneOf, ValidationError,
};
use ahash::HashMap;
use std::collections::hash_map::Entry;
mod buffers;
mod collection;
@ -185,15 +190,33 @@ impl VertexInputState {
problem: "the length exceeds the `max_vertex_input_bindings` limit".into(),
vuids: &[
"VUID-VkPipelineVertexInputStateCreateInfo-vertexBindingDescriptionCount-00613",
"VUID-vkCmdSetVertexInputEXT-vertexBindingDescriptionCount-04791",
],
..Default::default()
}));
}
// VUID-VkPipelineVertexInputStateCreateInfo-pVertexBindingDescriptions-00616
// VUID-vkCmdSetVertexInputEXT-pVertexBindingDescriptions-04794
// Ensured by HashMap.
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)))?;
@ -205,12 +228,29 @@ impl VertexInputState {
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)))?;
@ -219,21 +259,9 @@ impl VertexInputState {
binding,
format,
offset,
_ne: _,
} = attribute_desc;
if location > properties.max_vertex_input_attributes {
return Err(Box::new(ValidationError {
context: "attributes".into(),
problem: format!(
"the location {} exceeds the `max_vertex_input_attributes` limit",
location
)
.into(),
vuids: &["VUID-VkVertexInputAttributeDescription-location-00620"],
..Default::default()
}));
}
let binding_desc = bindings.get(&binding).ok_or_else(|| {
Box::new(ValidationError {
problem: format!(
@ -241,7 +269,10 @@ impl VertexInputState {
binding
)
.into(),
vuids: &["VUID-VkPipelineVertexInputStateCreateInfo-binding-00615"],
vuids: &[
"VUID-VkPipelineVertexInputStateCreateInfo-binding-00615",
"VUID-vkCmdSetVertexInputEXT-binding-04793",
],
..Default::default()
})
})?;
@ -263,7 +294,10 @@ impl VertexInputState {
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"vertex_attribute_access_beyond_stride",
)])]),
vuids: &["VUID-VkVertexInputAttributeDescription-vertexAttributeAccessBeyondStride-04457"],
vuids: &[
"VUID-VkVertexInputAttributeDescription-vertexAttributeAccessBeyondStride-04457",
"VUID-VkVertexInputAttributeDescription2EXT-vertexAttributeAccessBeyondStride-04806",
],
..Default::default()
}));
}
@ -285,7 +319,10 @@ impl VertexInputState {
location - 1, location,
)
.into(),
vuids: &["VUID-VkPipelineVertexInputStateCreateInfo-pVertexAttributeDescriptions-00617"],
vuids: &[
"VUID-VkPipelineVertexInputStateCreateInfo-pVertexAttributeDescriptions-00617",
"VUID-vkCmdSetVertexInputEXT-pVertexAttributeDescriptions-04795",
],
..Default::default()
}));
}
@ -312,15 +349,36 @@ pub struct VertexInputBindingDescription {
/// The number of bytes from the start of one element in the vertex buffer to the start of the
/// next element. This can be simply the size of the data in each element, but larger strides
/// are possible.
///
/// The default value is `0`, which must be overridden.
pub stride: u32,
/// How often the vertex input should advance to the next element.
///
/// The default value is [`VertexInputRate::Vertex`].
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 } = self;
let &Self {
stride,
input_rate,
_ne: _,
} = self;
let properties = device.physical_device().properties();
@ -328,7 +386,10 @@ impl VertexInputBindingDescription {
return Err(Box::new(ValidationError {
context: "stride".into(),
problem: "exceeds the `max_vertex_input_binding_stride` limit".into(),
vuids: &["VUID-VkVertexInputBindingDescription-stride-00619"],
vuids: &[
"VUID-VkVertexInputBindingDescription-stride-00619",
"VUID-VkVertexInputBindingDescription2EXT-stride-04797",
],
..Default::default()
}));
}
@ -364,7 +425,10 @@ impl VertexInputBindingDescription {
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"vertex_attribute_instance_rate_divisor",
)])]),
vuids: &["VUID-VkVertexInputBindingDivisorDescriptionEXT-vertexAttributeInstanceRateDivisor-02229"],
vuids: &[
"VUID-VkVertexInputBindingDivisorDescriptionEXT-vertexAttributeInstanceRateDivisor-02229",
"VUID-VkVertexInputBindingDescription2EXT-divisor-04799",
],
}));
}
@ -380,7 +444,10 @@ impl VertexInputBindingDescription {
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"vertex_attribute_instance_rate_zero_divisor",
)])]),
vuids: &["VUID-VkVertexInputBindingDivisorDescriptionEXT-vertexAttributeInstanceRateZeroDivisor-02228"],
vuids: &[
"VUID-VkVertexInputBindingDivisorDescriptionEXT-vertexAttributeInstanceRateZeroDivisor-02228",
"VUID-VkVertexInputBindingDescription2EXT-divisor-04798",
],
}));
}
@ -390,7 +457,10 @@ impl VertexInputBindingDescription {
problem: "is `VertexInputRate::Instance`, and \
its `divisor` value exceeds the `max_vertex_attrib_divisor` limit"
.into(),
vuids: &["VUID-VkVertexInputBindingDivisorDescriptionEXT-divisor-01870"],
vuids: &[
"VUID-VkVertexInputBindingDivisorDescriptionEXT-divisor-01870",
"VUID-VkVertexInputBindingDescription2EXT-divisor-06226",
],
..Default::default()
}));
}
@ -406,9 +476,13 @@ impl VertexInputBindingDescription {
#[derive(Clone, Copy, Debug)]
pub struct VertexInputAttributeDescription {
/// The vertex buffer binding number that this attribute should take its data from.
///
/// The default value is `0`.
pub binding: u32,
/// The size and type of the vertex data.
///
/// The default value is [`Format::UNDEFINED`], which must be overridden.
pub format: Format,
/// Number of bytes between the start of a vertex buffer element and the location of attribute.
@ -418,7 +492,23 @@ pub struct VertexInputAttributeDescription {
/// `binding`, the
/// [`vertex_attribute_access_beyond_stride`](crate::device::Features::vertex_attribute_access_beyond_stride)
/// feature must be enabled on the device.
///
/// The default value is `0`.
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 {
@ -427,20 +517,26 @@ impl VertexInputAttributeDescription {
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"])
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"],
vuids: &[
"VUID-VkVertexInputAttributeDescription-binding-00621",
"VUID-VkVertexInputAttributeDescription2EXT-binding-06229",
],
..Default::default()
}));
}
@ -449,7 +545,10 @@ impl VertexInputAttributeDescription {
return Err(Box::new(ValidationError {
context: "offset".into(),
problem: "exceeds the `max_vertex_input_attribute_offset` limit".into(),
vuids: &["VUID-VkVertexInputAttributeDescription-offset-00622"],
vuids: &[
"VUID-VkVertexInputAttributeDescription-offset-00622",
"VUID-VkVertexInputAttributeDescription2EXT-offset-06230",
],
..Default::default()
}));
}
@ -466,7 +565,10 @@ impl VertexInputAttributeDescription {
context: "format".into(),
problem: "the format features do not include `FormatFeatures::VERTEX_BUFFER`"
.into(),
vuids: &["VUID-VkVertexInputAttributeDescription-format-00623"],
vuids: &[
"VUID-VkVertexInputAttributeDescription-format-00623",
"VUID-VkVertexInputAttributeDescription2EXT-format-04805",
],
..Default::default()
}));
}
@ -504,3 +606,402 @@ impl From<VertexInputRate> for ash::vk::VertexInputRate {
}
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct VertexInputLocationRequirements {
pub(crate) numeric_type: NumericType,
pub(crate) width: VertexInputLocationWidth,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum VertexInputLocationWidth {
/// The shader requires a 32-bit or smaller value at this location.
Requires32,
/// The shader requires a 64-bit value at this location.
/// The boolean indicates whether the shader requires a format that fills the second half
/// of the location.
Requires64 { requires_second_half: bool },
}
pub(crate) fn required_vertex_inputs(
spirv: &Spirv,
entry_point_id: Id,
) -> HashMap<u32, VertexInputLocationRequirements> {
let interface = match spirv.function(entry_point_id).entry_point() {
Some(Instruction::EntryPoint { interface, .. }) => interface,
_ => unreachable!(),
};
let mut required_vertex_inputs = HashMap::default();
for &variable_id in interface {
let variable_id_info = spirv.id(variable_id);
let pointer_type_id = match *variable_id_info.instruction() {
Instruction::Variable {
result_type_id,
storage_class: StorageClass::Input,
..
} => result_type_id,
_ => continue,
};
let pointer_type_id_info = spirv.id(pointer_type_id);
let type_id = match *pointer_type_id_info.instruction() {
Instruction::TypePointer { ty, .. } => ty,
_ => unreachable!(),
};
let mut variable_location = None;
let mut variable_component = 0;
for instruction in variable_id_info.decorations() {
if let Instruction::Decorate { ref decoration, .. } = *instruction {
match *decoration {
Decoration::Location { location } => variable_location = Some(location),
Decoration::Component { component } => variable_component = component,
_ => (),
}
}
}
if let Some(variable_location) = variable_location {
add_type_location(
&mut required_vertex_inputs,
spirv,
variable_location,
variable_component,
type_id,
);
} else {
let block_type_id_info = spirv.id(type_id);
let member_types = match block_type_id_info.instruction() {
Instruction::TypeStruct { member_types, .. } => member_types,
_ => continue,
};
for (&type_id, member_info) in member_types.iter().zip(block_type_id_info.members()) {
let mut member_location = None;
let mut member_component = 0;
for instruction in member_info.decorations() {
if let Instruction::MemberDecorate { ref decoration, .. } = *instruction {
match *decoration {
Decoration::Location { location } => member_location = Some(location),
Decoration::Component { component } => member_component = component,
_ => (),
}
}
}
if let Some(member_location) = member_location {
add_type_location(
&mut required_vertex_inputs,
spirv,
member_location,
member_component,
type_id,
);
}
}
}
}
required_vertex_inputs
}
fn add_type_location(
required_vertex_inputs: &mut HashMap<u32, VertexInputLocationRequirements>,
spirv: &Spirv,
mut location: u32,
mut component: u32,
type_id: Id,
) -> (u32, u32) {
debug_assert!(component < 4);
let mut add_scalar = |numeric_type: NumericType, width: u32| -> (u32, u32) {
if width > 32 {
debug_assert!(component & 1 == 0);
let half_index = component as usize / 2;
match required_vertex_inputs.entry(location) {
Entry::Occupied(mut entry) => {
let requirements = entry.get_mut();
debug_assert_eq!(requirements.numeric_type, numeric_type);
match &mut requirements.width {
VertexInputLocationWidth::Requires32 => unreachable!(),
VertexInputLocationWidth::Requires64 {
requires_second_half,
} => {
if component == 2 {
debug_assert!(!*requires_second_half);
*requires_second_half = true;
}
}
}
}
Entry::Vacant(entry) => {
let mut required_halves = [false; 2];
required_halves[half_index] = true;
entry.insert(VertexInputLocationRequirements {
numeric_type,
width: VertexInputLocationWidth::Requires64 {
requires_second_half: component == 2,
},
});
}
}
(1, 2)
} else {
match required_vertex_inputs.entry(location) {
Entry::Occupied(entry) => {
let requirements = *entry.get();
debug_assert_eq!(requirements.numeric_type, numeric_type);
debug_assert_eq!(requirements.width, VertexInputLocationWidth::Requires32);
}
Entry::Vacant(entry) => {
entry.insert(VertexInputLocationRequirements {
numeric_type,
width: VertexInputLocationWidth::Requires32,
});
}
}
(1, 1)
}
};
match *spirv.id(type_id).instruction() {
Instruction::TypeInt {
width, signedness, ..
} => {
let numeric_type = if signedness == 1 {
NumericType::Int
} else {
NumericType::Uint
};
add_scalar(numeric_type, width)
}
Instruction::TypeFloat { width, .. } => add_scalar(NumericType::Float, width),
Instruction::TypeVector {
component_type,
component_count,
..
} => {
let mut total_locations_added = 1;
for _ in 0..component_count {
// Overflow into next location
if component == 4 {
component = 0;
location += 1;
total_locations_added += 1;
} else {
debug_assert!(component < 4);
}
let (_, components_added) = add_type_location(
required_vertex_inputs,
spirv,
location,
component,
component_type,
);
component += components_added;
}
(total_locations_added, 0)
}
Instruction::TypeMatrix {
column_type,
column_count,
..
} => {
let mut total_locations_added = 0;
for _ in 0..column_count {
let (locations_added, _) = add_type_location(
required_vertex_inputs,
spirv,
location,
component,
column_type,
);
location += locations_added;
total_locations_added += locations_added;
}
(total_locations_added, 0)
}
Instruction::TypeArray {
element_type,
length,
..
} => {
let length = get_constant(spirv, length).unwrap();
let mut total_locations_added = 0;
for _ in 0..length {
let (locations_added, _) = add_type_location(
required_vertex_inputs,
spirv,
location,
component,
element_type,
);
location += locations_added;
total_locations_added += locations_added;
}
(total_locations_added, 0)
}
Instruction::TypeStruct {
ref member_types, ..
} => {
let mut total_locations_added = 0;
for &member_type in member_types {
let (locations_added, _) = add_type_location(
required_vertex_inputs,
spirv,
location,
component,
member_type,
);
location += locations_added;
total_locations_added += locations_added;
}
(total_locations_added, 0)
}
_ => unimplemented!(),
}
}
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],
}
pub(crate) fn validate_required_vertex_inputs(
attribute_descs: &HashMap<u32, VertexInputAttributeDescription>,
required_vertex_inputs: &HashMap<u32, VertexInputLocationRequirements>,
vuids: RequiredVertexInputsVUIDs,
) -> Result<(), Box<ValidationError>> {
for (&location, location_info) in required_vertex_inputs {
let (is_previous, attribute_desc) = (attribute_descs.get(&location).map(|d| (false, d)))
.or_else(|| {
// If the previous location has at least three 64-bit components,
// then it extends into the current location, so try that instead.
location.checked_sub(1).and_then(|location| {
attribute_descs
.get(&location)
.filter(|attribute_desc| {
attribute_desc
.format
.components()
.starts_with(&[64, 64, 64])
})
.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();
// 64-bit in the shader must match with 64-bit in the attribute.
match location_info.width {
VertexInputLocationWidth::Requires32 => {
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()
}));
}
}
VertexInputLocationWidth::Requires64 {
requires_second_half,
} => {
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()
}));
}
// For 64-bit values, there are no default values for missing components.
// If the shader uses the 64-bit value in the second half of the location, then
// the attribute must provide it.
if requires_second_half {
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(())
}

View File

@ -1251,12 +1251,15 @@ vulkan_enum! {
RequiresAllOf([DeviceExtension(ext_line_rasterization)]),
]),
/* TODO: enable
// TODO: document
/// The `Option` variant of
/// [`GraphicsPipelineCreateInfo::vertex_input_state`](crate::pipeline::graphics::GraphicsPipelineCreateInfo::vertex_input_state).
///
/// Set with
/// [`set_vertex_input`](crate::command_buffer::AutoCommandBufferBuilder::set_vertex_input).
VertexInput = VERTEX_INPUT_EXT
RequiresOneOf([
RequiresAllOf([DeviceExtension(ext_vertex_input_dynamic_state)]),
]), */
]),
/// The value of
/// [`TessellationState::patch_control_points`](crate::pipeline::graphics::tessellation::TessellationState::patch_control_points).

View File

@ -1444,6 +1444,17 @@ fn is_builtin(spirv: &Spirv, id: Id) -> bool {
}
}
pub(crate) fn get_constant(spirv: &Spirv, id: Id) -> Option<u64> {
match spirv.id(id).instruction() {
Instruction::Constant { value, .. } => match value.len() {
1 => Some(value[0] as u64),
2 => Some(value[0] as u64 | (value[1] as u64) << 32),
_ => panic!("constant {} is larger than 64 bits", id),
},
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::{HashMap, PushConstantRange, ShaderStages, Version};