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, input_assembly::PrimitiveTopology,
rasterization::{CullMode, DepthBiasState, FrontFace, LineStipple}, rasterization::{CullMode, DepthBiasState, FrontFace, LineStipple},
subpass::PipelineRenderingCreateInfo, subpass::PipelineRenderingCreateInfo,
vertex_input::VertexInputState,
viewport::{Scissor, Viewport}, viewport::{Scissor, Viewport},
}, },
ComputePipeline, DynamicState, GraphicsPipeline, PipelineBindPoint, PipelineLayout, 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_reference: StencilStateDynamic,
pub(in crate::command_buffer) stencil_test_enable: Option<bool>, pub(in crate::command_buffer) stencil_test_enable: Option<bool>,
pub(in crate::command_buffer) stencil_write_mask: StencilStateDynamic, 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: HashMap<u32, Viewport>,
pub(in crate::command_buffer) viewport_with_count: Option<SmallVec<[Viewport; 2]>>, 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::StencilReference => self.stencil_reference = Default::default(),
DynamicState::StencilTestEnable => self.stencil_test_enable = None, DynamicState::StencilTestEnable => self.stencil_test_enable = None,
DynamicState::StencilWriteMask => self.stencil_write_mask = Default::default(), DynamicState::StencilWriteMask => self.stencil_write_mask = Default::default(),
// DynamicState::VertexInput => todo!(), DynamicState::VertexInput => self.vertex_input = None,
// DynamicState::VertexInputBindingStride => todo!(), // DynamicState::VertexInputBindingStride => todo!(),
DynamicState::Viewport => self.viewport.clear(), DynamicState::Viewport => self.viewport.clear(),
// DynamicState::ViewportCoarseSampleOrder => todo!(), // DynamicState::ViewportCoarseSampleOrder => todo!(),

View File

@ -13,10 +13,10 @@ use crate::{
graphics::vertex_input::VertexBuffersCollection, ComputePipeline, GraphicsPipeline, graphics::vertex_input::VertexBuffersCollection, ComputePipeline, GraphicsPipeline,
PipelineBindPoint, PipelineLayout, PipelineBindPoint, PipelineLayout,
}, },
DeviceSize, Requires, RequiresAllOf, RequiresOneOf, ValidationError, VulkanObject, DeviceSize, Requires, RequiresAllOf, RequiresOneOf, ValidationError, Version, VulkanObject,
}; };
use smallvec::SmallVec; 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. /// # Commands to bind or push state for pipeline execution commands.
/// ///
@ -954,19 +954,58 @@ impl UnsafeCommandBufferBuilder {
return self; return self;
} }
let (buffers_vk, offsets_vk): (SmallVec<[_; 2]>, SmallVec<[_; 2]>) = vertex_buffers let device = self.device();
.iter()
.map(|buffer| (buffer.buffer().handle(), buffer.offset()))
.unzip();
let fns = self.device().fns(); if device.api_version() >= Version::V1_3
(fns.v1_0.cmd_bind_vertex_buffers)( || device.enabled_extensions().ext_extended_dynamic_state
self.handle(), || device.enabled_extensions().ext_shader_object
first_binding, {
buffers_vk.len() as u32, let mut buffers_vk: SmallVec<[_; 2]> = SmallVec::with_capacity(vertex_buffers.len());
buffers_vk.as_ptr(), let mut offsets_vk: SmallVec<[_; 2]> = SmallVec::with_capacity(vertex_buffers.len());
offsets_vk.as_ptr(), 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 self
} }

View File

@ -7,6 +7,10 @@ use crate::{
depth_stencil::{CompareOp, StencilFaces, StencilOp, StencilOps}, depth_stencil::{CompareOp, StencilFaces, StencilOp, StencilOps},
input_assembly::PrimitiveTopology, input_assembly::PrimitiveTopology,
rasterization::{CullMode, DepthBiasState, FrontFace, LineStipple}, rasterization::{CullMode, DepthBiasState, FrontFace, LineStipple},
vertex_input::{
VertexInputAttributeDescription, VertexInputBindingDescription, VertexInputRate,
VertexInputState,
},
viewport::{Scissor, Viewport}, viewport::{Scissor, Viewport},
}, },
DynamicState, DynamicState,
@ -1069,6 +1073,46 @@ impl<L> AutoCommandBufferBuilder<L> {
self 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. /// Sets the dynamic viewports for future draw calls.
pub fn set_viewport( pub fn set_viewport(
&mut self, &mut self,
@ -2837,6 +2881,121 @@ impl UnsafeCommandBufferBuilder {
self 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] #[inline]
pub unsafe fn set_viewport( pub unsafe fn set_viewport(
&mut self, &mut self,

View File

@ -16,8 +16,9 @@ use crate::{
image::{sampler::Sampler, view::ImageView, ImageAspects, ImageLayout, SampleCount}, image::{sampler::Sampler, view::ImageView, ImageAspects, ImageLayout, SampleCount},
pipeline::{ pipeline::{
graphics::{ graphics::{
input_assembly::PrimitiveTopology, subpass::PipelineSubpassType, input_assembly::PrimitiveTopology,
vertex_input::VertexInputRate, subpass::PipelineSubpassType,
vertex_input::{self, RequiredVertexInputsVUIDs, VertexInputRate},
}, },
DynamicState, GraphicsPipeline, Pipeline, PipelineLayout, 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]; let vertex_buffer = &self.builder_state.vertex_buffers[&binding_num];
// Per spec: // 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]; let vertex_buffer = &self.builder_state.vertex_buffers[&binding_num];
// Per spec: // 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::VertexInputBindingStride => todo!(),
DynamicState::Viewport => { DynamicState::Viewport => {
let viewport_state = pipeline.viewport_state().unwrap(); let viewport_state = pipeline.viewport_state().unwrap();
@ -2437,9 +2488,13 @@ impl<L> AutoCommandBufferBuilder<L> {
vuid_type: VUIDType, vuid_type: VUIDType,
pipeline: &GraphicsPipeline, pipeline: &GraphicsPipeline,
) -> Result<(), Box<ValidationError>> { ) -> 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) { if !self.builder_state.vertex_buffers.contains_key(&binding_num) {
return Err(Box::new(ValidationError { return Err(Box::new(ValidationError {
problem: format!( problem: format!(
@ -2663,20 +2718,24 @@ impl<L> AutoCommandBufferBuilder<L> {
used_resources: &mut Vec<(ResourceUseRef2, Resource)>, used_resources: &mut Vec<(ResourceUseRef2, Resource)>,
pipeline: &GraphicsPipeline, pipeline: &GraphicsPipeline,
) { ) {
used_resources.extend(pipeline.vertex_input_state().bindings.iter().map( let vertex_input_state = pipeline
|(&binding, _)| { .dynamic_state()
let vertex_buffer = &self.builder_state.vertex_buffers[&binding]; .contains(&DynamicState::VertexInput)
( .then(|| self.builder_state.vertex_input.as_ref().unwrap())
ResourceInCommand::VertexBuffer { binding }.into(), .unwrap_or_else(|| pipeline.vertex_input_state().unwrap());
Resource::Buffer {
buffer: vertex_buffer.clone(), used_resources.extend(vertex_input_state.bindings.iter().map(|(&binding, _)| {
range: 0..vertex_buffer.size(), // TODO: let vertex_buffer = &self.builder_state.vertex_buffers[&binding];
memory_access: (
PipelineStageAccessFlags::VertexAttributeInput_VertexAttributeRead, 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)>) { fn add_index_buffer_resources(&self, used_resources: &mut Vec<(ResourceUseRef2, Resource)>) {

View File

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

View File

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

View File

@ -99,10 +99,15 @@ pub use self::{
}; };
use crate::{ use crate::{
device::Device, device::Device,
format::{Format, FormatFeatures}, format::{Format, FormatFeatures, NumericType},
shader::{
reflect::get_constant,
spirv::{Decoration, Id, Instruction, Spirv, StorageClass},
},
DeviceSize, Requires, RequiresAllOf, RequiresOneOf, ValidationError, DeviceSize, Requires, RequiresAllOf, RequiresOneOf, ValidationError,
}; };
use ahash::HashMap; use ahash::HashMap;
use std::collections::hash_map::Entry;
mod buffers; mod buffers;
mod collection; mod collection;
@ -185,15 +190,33 @@ impl VertexInputState {
problem: "the length exceeds the `max_vertex_input_bindings` limit".into(), problem: "the length exceeds the `max_vertex_input_bindings` limit".into(),
vuids: &[ vuids: &[
"VUID-VkPipelineVertexInputStateCreateInfo-vertexBindingDescriptionCount-00613", "VUID-VkPipelineVertexInputStateCreateInfo-vertexBindingDescriptionCount-00613",
"VUID-vkCmdSetVertexInputEXT-vertexBindingDescriptionCount-04791",
], ],
..Default::default() ..Default::default()
})); }));
} }
// VUID-VkPipelineVertexInputStateCreateInfo-pVertexBindingDescriptions-00616 // VUID-VkPipelineVertexInputStateCreateInfo-pVertexBindingDescriptions-00616
// VUID-vkCmdSetVertexInputEXT-pVertexBindingDescriptions-04794
// Ensured by HashMap. // Ensured by HashMap.
for (&binding, binding_desc) in bindings { 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 binding_desc
.validate(device) .validate(device)
.map_err(|err| err.add_context(format!("bindings[{}]", binding)))?; .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(), problem: "the length exceeds the `max_vertex_input_attributes` limit".into(),
vuids: &[ vuids: &[
"VUID-VkPipelineVertexInputStateCreateInfo-vertexAttributeDescriptionCount-00614", "VUID-VkPipelineVertexInputStateCreateInfo-vertexAttributeDescriptionCount-00614",
"VUID-vkCmdSetVertexInputEXT-vertexAttributeDescriptionCount-04792",
], ],
..Default::default() ..Default::default()
})); }));
} }
for (&location, attribute_desc) in attributes { 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 attribute_desc
.validate(device) .validate(device)
.map_err(|err| err.add_context(format!("attributes[{}]", location)))?; .map_err(|err| err.add_context(format!("attributes[{}]", location)))?;
@ -219,21 +259,9 @@ impl VertexInputState {
binding, binding,
format, format,
offset, offset,
_ne: _,
} = attribute_desc; } = 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(|| { let binding_desc = bindings.get(&binding).ok_or_else(|| {
Box::new(ValidationError { Box::new(ValidationError {
problem: format!( problem: format!(
@ -241,7 +269,10 @@ impl VertexInputState {
binding binding
) )
.into(), .into(),
vuids: &["VUID-VkPipelineVertexInputStateCreateInfo-binding-00615"], vuids: &[
"VUID-VkPipelineVertexInputStateCreateInfo-binding-00615",
"VUID-vkCmdSetVertexInputEXT-binding-04793",
],
..Default::default() ..Default::default()
}) })
})?; })?;
@ -263,7 +294,10 @@ impl VertexInputState {
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"vertex_attribute_access_beyond_stride", "vertex_attribute_access_beyond_stride",
)])]), )])]),
vuids: &["VUID-VkVertexInputAttributeDescription-vertexAttributeAccessBeyondStride-04457"], vuids: &[
"VUID-VkVertexInputAttributeDescription-vertexAttributeAccessBeyondStride-04457",
"VUID-VkVertexInputAttributeDescription2EXT-vertexAttributeAccessBeyondStride-04806",
],
..Default::default() ..Default::default()
})); }));
} }
@ -285,7 +319,10 @@ impl VertexInputState {
location - 1, location, location - 1, location,
) )
.into(), .into(),
vuids: &["VUID-VkPipelineVertexInputStateCreateInfo-pVertexAttributeDescriptions-00617"], vuids: &[
"VUID-VkPipelineVertexInputStateCreateInfo-pVertexAttributeDescriptions-00617",
"VUID-vkCmdSetVertexInputEXT-pVertexAttributeDescriptions-04795",
],
..Default::default() ..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 /// 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 /// next element. This can be simply the size of the data in each element, but larger strides
/// are possible. /// are possible.
///
/// The default value is `0`, which must be overridden.
pub stride: u32, pub stride: u32,
/// How often the vertex input should advance to the next element. /// How often the vertex input should advance to the next element.
///
/// The default value is [`VertexInputRate::Vertex`].
pub input_rate: VertexInputRate, 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 { impl VertexInputBindingDescription {
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> { 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(); let properties = device.physical_device().properties();
@ -328,7 +386,10 @@ impl VertexInputBindingDescription {
return Err(Box::new(ValidationError { return Err(Box::new(ValidationError {
context: "stride".into(), context: "stride".into(),
problem: "exceeds the `max_vertex_input_binding_stride` limit".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() ..Default::default()
})); }));
} }
@ -364,7 +425,10 @@ impl VertexInputBindingDescription {
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature( requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"vertex_attribute_instance_rate_divisor", "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( requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"vertex_attribute_instance_rate_zero_divisor", "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 \ problem: "is `VertexInputRate::Instance`, and \
its `divisor` value exceeds the `max_vertex_attrib_divisor` limit" its `divisor` value exceeds the `max_vertex_attrib_divisor` limit"
.into(), .into(),
vuids: &["VUID-VkVertexInputBindingDivisorDescriptionEXT-divisor-01870"], vuids: &[
"VUID-VkVertexInputBindingDivisorDescriptionEXT-divisor-01870",
"VUID-VkVertexInputBindingDescription2EXT-divisor-06226",
],
..Default::default() ..Default::default()
})); }));
} }
@ -406,9 +476,13 @@ impl VertexInputBindingDescription {
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub struct VertexInputAttributeDescription { pub struct VertexInputAttributeDescription {
/// The vertex buffer binding number that this attribute should take its data from. /// The vertex buffer binding number that this attribute should take its data from.
///
/// The default value is `0`.
pub binding: u32, pub binding: u32,
/// The size and type of the vertex data. /// The size and type of the vertex data.
///
/// The default value is [`Format::UNDEFINED`], which must be overridden.
pub format: Format, pub format: Format,
/// Number of bytes between the start of a vertex buffer element and the location of attribute. /// 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 /// `binding`, the
/// [`vertex_attribute_access_beyond_stride`](crate::device::Features::vertex_attribute_access_beyond_stride) /// [`vertex_attribute_access_beyond_stride`](crate::device::Features::vertex_attribute_access_beyond_stride)
/// feature must be enabled on the device. /// feature must be enabled on the device.
///
/// The default value is `0`.
pub offset: u32, 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 { impl VertexInputAttributeDescription {
@ -427,20 +517,26 @@ impl VertexInputAttributeDescription {
binding, binding,
format, format,
offset, offset,
_ne: _,
} = self; } = self;
let properties = device.physical_device().properties(); let properties = device.physical_device().properties();
format.validate_device(device).map_err(|err| { format.validate_device(device).map_err(|err| {
err.add_context("format") err.add_context("format").set_vuids(&[
.set_vuids(&["VUID-VkVertexInputAttributeDescription-format-parameter"]) "VUID-VkVertexInputAttributeDescription-format-parameter",
"VUID-VkVertexInputAttributeDescription2EXT-format-parameter",
])
})?; })?;
if binding > properties.max_vertex_input_bindings { if binding > properties.max_vertex_input_bindings {
return Err(Box::new(ValidationError { return Err(Box::new(ValidationError {
context: "binding".into(), context: "binding".into(),
problem: "exceeds the `max_vertex_input_bindings` limit".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() ..Default::default()
})); }));
} }
@ -449,7 +545,10 @@ impl VertexInputAttributeDescription {
return Err(Box::new(ValidationError { return Err(Box::new(ValidationError {
context: "offset".into(), context: "offset".into(),
problem: "exceeds the `max_vertex_input_attribute_offset` limit".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() ..Default::default()
})); }));
} }
@ -466,7 +565,10 @@ impl VertexInputAttributeDescription {
context: "format".into(), context: "format".into(),
problem: "the format features do not include `FormatFeatures::VERTEX_BUFFER`" problem: "the format features do not include `FormatFeatures::VERTEX_BUFFER`"
.into(), .into(),
vuids: &["VUID-VkVertexInputAttributeDescription-format-00623"], vuids: &[
"VUID-VkVertexInputAttributeDescription-format-00623",
"VUID-VkVertexInputAttributeDescription2EXT-format-04805",
],
..Default::default() ..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)]), RequiresAllOf([DeviceExtension(ext_line_rasterization)]),
]), ]),
/* TODO: enable /// The `Option` variant of
// TODO: document /// [`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 VertexInput = VERTEX_INPUT_EXT
RequiresOneOf([ RequiresOneOf([
RequiresAllOf([DeviceExtension(ext_vertex_input_dynamic_state)]), RequiresAllOf([DeviceExtension(ext_vertex_input_dynamic_state)]),
]), */ ]),
/// The value of /// The value of
/// [`TessellationState::patch_control_points`](crate::pipeline::graphics::tessellation::TessellationState::patch_control_points). /// [`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)] #[cfg(test)]
mod tests { mod tests {
use super::{HashMap, PushConstantRange, ShaderStages, Version}; use super::{HashMap, PushConstantRange, ShaderStages, Version};