diff --git a/examples/src/bin/texture_array/main.rs b/examples/src/bin/texture_array/main.rs index 2917f9009..9f00b95b8 100644 --- a/examples/src/bin/texture_array/main.rs +++ b/examples/src/bin/texture_array/main.rs @@ -18,21 +18,21 @@ use winit::window::{Window, WindowBuilder}; use vulkano::buffer::{BufferUsage, CpuAccessibleBuffer, TypedBufferAccess}; use vulkano::command_buffer::{AutoCommandBufferBuilder, CommandBufferUsage, SubpassContents}; use vulkano::descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet}; -use vulkano::device::{Device, DeviceExtensions, Features}; use vulkano::device::physical::{PhysicalDevice, PhysicalDeviceType}; +use vulkano::device::{Device, DeviceExtensions, Features}; use vulkano::format::Format; -use vulkano::image::{ - ImageDimensions, ImageUsage, ImmutableImage, MipmapsCount, SwapchainImage, view::ImageView, -}; use vulkano::image::ImageAccess; +use vulkano::image::{ + view::ImageView, ImageDimensions, ImageUsage, ImmutableImage, MipmapsCount, SwapchainImage, +}; use vulkano::instance::Instance; -use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; use vulkano::pipeline::graphics::color_blend::ColorBlendState; use vulkano::pipeline::graphics::input_assembly::{InputAssemblyState, PrimitiveTopology}; use vulkano::pipeline::graphics::vertex_input::BuffersDefinition; use vulkano::pipeline::graphics::viewport::{Viewport, ViewportState}; +use vulkano::pipeline::{GraphicsPipeline, Pipeline, PipelineBindPoint}; use vulkano::render_pass::{Framebuffer, RenderPass, Subpass}; -use vulkano::sampler::{Filter, Sampler, SamplerAddressMode}; +use vulkano::sampler::{Sampler}; use vulkano::swapchain::{self, AcquireError, Swapchain, SwapchainCreationError}; use vulkano::sync::{self, FlushError, GpuFuture}; use vulkano::Version; @@ -86,7 +86,7 @@ fn main() { .union(&device_extensions), [(queue_family, 0.5)].iter().cloned(), ) - .unwrap(); + .unwrap(); let queue = queues.next().unwrap(); let (mut swapchain, images) = { @@ -131,10 +131,10 @@ fn main() { position: [0.5, 0.2], }, ] - .iter() - .cloned(), + .iter() + .cloned(), ) - .unwrap(); + .unwrap(); let vs = vs::load(device.clone()).unwrap(); let fs = fs::load(device.clone()).unwrap(); @@ -153,11 +153,16 @@ fn main() { depth_stencil: {} } ) - .unwrap(); + .unwrap(); let (texture, tex_future) = { - let image_array_data: Vec<_> = vec![include_bytes!("square.png").to_vec(), include_bytes!("star.png").to_vec(), include_bytes!("asterisk.png").to_vec()] - .into_iter().flat_map(|png_bytes| { + let image_array_data: Vec<_> = vec![ + include_bytes!("square.png").to_vec(), + include_bytes!("star.png").to_vec(), + include_bytes!("asterisk.png").to_vec(), + ] + .into_iter() + .flat_map(|png_bytes| { let cursor = Cursor::new(png_bytes); let decoder = png::Decoder::new(cursor); let mut reader = decoder.read_info().unwrap(); @@ -166,7 +171,8 @@ fn main() { image_data.resize((info.width * info.height * 4) as usize, 0); reader.next_frame(&mut image_data).unwrap(); image_data - }).collect(); + }) + .collect(); let dimensions = ImageDimensions::Dim2d { width: 128, height: 128, @@ -179,7 +185,7 @@ fn main() { Format::R8G8B8A8_SRGB, queue.clone(), ) - .unwrap(); + .unwrap(); (ImageView::new(image).unwrap(), future) }; @@ -206,7 +212,7 @@ fn main() { sampler.clone(), )], ) - .unwrap(); + .unwrap(); let mut viewport = Viewport { origin: [0.0, 0.0], @@ -269,7 +275,7 @@ fn main() { queue.family(), CommandBufferUsage::OneTimeSubmit, ) - .unwrap(); + .unwrap(); builder .begin_render_pass( framebuffers[image_num].clone(), diff --git a/vulkano-shaders/src/codegen.rs b/vulkano-shaders/src/codegen.rs index 40b0dda3c..8fdf119b0 100644 --- a/vulkano-shaders/src/codegen.rs +++ b/vulkano-shaders/src/codegen.rs @@ -231,7 +231,7 @@ where let minor = spirv.version().minor; let patch = spirv.version().patch; quote! { - Version { + ::vulkano::Version { major: #major, minor: #minor, patch: #patch, @@ -240,7 +240,7 @@ where }; let spirv_capabilities = reflect::spirv_capabilities(&spirv).map(|capability| { let name = format_ident!("{}", format!("{:?}", capability)); - quote! { &Capability::#name } + quote! { &::vulkano::shader::spirv::Capability::#name } }); let spirv_extensions = reflect::spirv_extensions(&spirv); let entry_points = reflect::entry_points(&spirv) @@ -267,21 +267,12 @@ where pub fn #load_name(device: ::std::sync::Arc<::vulkano::device::Device>) -> Result<::std::sync::Arc<::vulkano::shader::ShaderModule>, ::vulkano::shader::ShaderCreationError> { - use vulkano::shader::EntryPointInfo; - use vulkano::shader::GeometryShaderExecution; - use vulkano::shader::ShaderExecution; - use vulkano::shader::ShaderModule; - use vulkano::shader::ShaderStage; - use vulkano::shader::SpecializationConstantRequirements; - use vulkano::shader::spirv::Capability; - use vulkano::Version; - let _bytes = ( #( #include_bytes),* ); static WORDS: &[u32] = &[ #( #words ),* ]; unsafe { - Ok(ShaderModule::from_words_with_data( + Ok(::vulkano::shader::ShaderModule::from_words_with_data( device, WORDS, #spirv_version, diff --git a/vulkano-shaders/src/entry_point.rs b/vulkano-shaders/src/entry_point.rs index 1324a5928..d26533495 100644 --- a/vulkano-shaders/src/entry_point.rs +++ b/vulkano-shaders/src/entry_point.rs @@ -12,8 +12,8 @@ use proc_macro2::TokenStream; use vulkano::pipeline::layout::PipelineLayoutPcRange; use vulkano::shader::spirv::ExecutionModel; use vulkano::shader::{ - DescriptorIdentifier, DescriptorRequirements, GeometryShaderExecution, ShaderExecution, - ShaderInterfaceEntry, ShaderInterfaceEntryType, SpecializationConstantRequirements, + DescriptorIdentifier, DescriptorRequirements, ShaderExecution, ShaderInterfaceEntry, + ShaderInterfaceEntryType, SpecializationConstantRequirements, }; use vulkano::shader::{EntryPointInfo, ShaderInterface, ShaderStages}; @@ -24,7 +24,7 @@ pub(super) fn write_entry_point( ) -> TokenStream { let execution = write_shader_execution(&info.execution); let model = syn::parse_str::(&format!( - "vulkano::shader::spirv::ExecutionModel::{:?}", + "::vulkano::shader::spirv::ExecutionModel::{:?}", model )) .unwrap(); @@ -40,7 +40,7 @@ pub(super) fn write_entry_point( ( #name.to_owned(), #model, - EntryPointInfo { + ::vulkano::shader::EntryPointInfo { execution: #execution, descriptor_requirements: #descriptor_requirements.into_iter().collect(), push_constant_requirements: #push_constant_requirements, @@ -54,21 +54,23 @@ pub(super) fn write_entry_point( fn write_shader_execution(execution: &ShaderExecution) -> TokenStream { match execution { - ShaderExecution::Vertex => quote! { ShaderExecution::Vertex }, - ShaderExecution::TessellationControl => quote! { ShaderExecution::TessellationControl }, - ShaderExecution::TessellationEvaluation => { - quote! { ShaderExecution::TessellationEvaluation } + ShaderExecution::Vertex => quote! { ::vulkano::shader::ShaderExecution::Vertex }, + ShaderExecution::TessellationControl => { + quote! { ::vulkano::shader::ShaderExecution::TessellationControl } } - ShaderExecution::Geometry(GeometryShaderExecution { input }) => { + ShaderExecution::TessellationEvaluation => { + quote! { ::vulkano::shader::ShaderExecution::TessellationEvaluation } + } + ShaderExecution::Geometry(::vulkano::shader::GeometryShaderExecution { input }) => { let input = format_ident!("{}", format!("{:?}", input)); quote! { - ShaderExecution::Geometry { + ::vulkano::shader::ShaderExecution::Geometry { input: GeometryShaderInput::#input, } } } - ShaderExecution::Fragment => quote! { ShaderExecution::Fragment }, - ShaderExecution::Compute => quote! { ShaderExecution::Compute }, + ShaderExecution::Fragment => quote! { ::vulkano::shader::ShaderExecution::Fragment }, + ShaderExecution::Compute => quote! { ::vulkano::shader::ShaderExecution::Compute }, } } @@ -80,36 +82,46 @@ fn write_descriptor_requirements( let DescriptorRequirements { descriptor_types, descriptor_count, - format, + image_format, + image_multisampled, + image_scalar_type, image_view_type, - multisampled, - mutable, - sampler_no_unnormalized, + sampler_compare, + sampler_no_unnormalized_coordinates, sampler_with_images, stages, storage_image_atomic, + storage_read, + storage_write, } = reqs; let descriptor_types = descriptor_types.into_iter().map(|ty| { let ident = format_ident!("{}", format!("{:?}", ty)); - quote! { DescriptorType::#ident } + quote! { ::vulkano::descriptor_set::layout::DescriptorType::#ident } }); - let format = match format { - Some(format) => { - let ident = format_ident!("{}", format!("{:?}", format)); - quote! { Some(Format::#ident) } + let image_format = match image_format { + Some(image_format) => { + let ident = format_ident!("{}", format!("{:?}", image_format)); + quote! { Some(::vulkano::format::Format::#ident) } + } + None => quote! { None }, + }; + let image_scalar_type = match image_scalar_type { + Some(image_scalar_type) => { + let ident = format_ident!("{}", format!("{:?}", image_scalar_type)); + quote! { Some(::vulkano::shader::ShaderScalarType::#ident) } } None => quote! { None }, }; let image_view_type = match image_view_type { Some(image_view_type) => { let ident = format_ident!("{}", format!("{:?}", image_view_type)); - quote! { Some(ImageViewType::#ident) } + quote! { Some(::vulkano::image::view::ImageViewType::#ident) } } None => quote! { None }, }; - let mutable = mutable.iter(); - let sampler_no_unnormalized = sampler_no_unnormalized.iter(); + let sampler_compare = sampler_compare.iter(); + let sampler_no_unnormalized_coordinates = sampler_no_unnormalized_coordinates.iter(); let sampler_with_images = { sampler_with_images.iter().map(|(&index, identifiers)| { let identifiers = identifiers.iter().map( @@ -119,7 +131,7 @@ fn write_descriptor_requirements( index, }| { quote! { - vulkano::shader::DescriptorIdentifier { + ::vulkano::shader::DescriptorIdentifier { set: #set, binding: #binding, index: #index, @@ -152,7 +164,7 @@ fn write_descriptor_requirements( } = stages; quote! { - ShaderStages { + ::vulkano::shader::ShaderStages { vertex: #vertex, tessellation_control: #tessellation_control, tessellation_evaluation: #tessellation_evaluation, @@ -169,21 +181,26 @@ fn write_descriptor_requirements( } }; let storage_image_atomic = storage_image_atomic.iter(); + let storage_read = storage_read.iter(); + let storage_write = storage_write.iter(); quote! { ( (#set_num, #binding_num), - DescriptorRequirements { + ::vulkano::shader::DescriptorRequirements { descriptor_types: vec![#(#descriptor_types),*], descriptor_count: #descriptor_count, - format: #format, + image_format: #image_format, + image_multisampled: #image_multisampled, + image_scalar_type: #image_scalar_type, image_view_type: #image_view_type, - multisampled: #multisampled, - mutable: [#(#mutable),*].into_iter().collect(), - sampler_no_unnormalized: [#(#sampler_no_unnormalized),*].into_iter().collect(), + sampler_compare: [#(#sampler_compare),*].into_iter().collect(), + sampler_no_unnormalized_coordinates: [#(#sampler_no_unnormalized_coordinates),*].into_iter().collect(), sampler_with_images: [#(#sampler_with_images),*].into_iter().collect(), stages: #stages, storage_image_atomic: [#(#storage_image_atomic),*].into_iter().collect(), + storage_read: [#(#storage_read),*].into_iter().collect(), + storage_write: [#(#storage_write),*].into_iter().collect(), }, ), } @@ -222,7 +239,7 @@ fn write_push_constant_requirements( } = stages; quote! { - ShaderStages { + ::vulkano::shader::ShaderStages { vertex: #vertex, tessellation_control: #tessellation_control, tessellation_evaluation: #tessellation_evaluation, @@ -240,7 +257,7 @@ fn write_push_constant_requirements( }; quote! { - Some(PipelineLayoutPcRange { + Some(::vulkano::pipeline::layout::PipelineLayoutPcRange { offset: #offset, size: #size, stages: #stages, @@ -263,7 +280,7 @@ fn write_specialization_constant_requirements( quote! { ( #constant_id, - SpecializationConstantRequirements { + ::vulkano::shader::SpecializationConstantRequirements { size: #size, }, ), diff --git a/vulkano-shaders/src/lib.rs b/vulkano-shaders/src/lib.rs index a25bc1245..f61fdf8fd 100644 --- a/vulkano-shaders/src/lib.rs +++ b/vulkano-shaders/src/lib.rs @@ -895,34 +895,6 @@ pub fn shader(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let uses = &input.types_meta.uses; let result = quote! { - #[allow(unused_imports)] - use std::sync::Arc; - #[allow(unused_imports)] - use std::vec::IntoIter as VecIntoIter; - - #[allow(unused_imports)] - use vulkano::device::Device; - #[allow(unused_imports)] - use vulkano::descriptor_set::layout::DescriptorType; - #[allow(unused_imports)] - use vulkano::format::Format; - #[allow(unused_imports)] - use vulkano::image::view::ImageViewType; - #[allow(unused_imports)] - use vulkano::pipeline::layout::PipelineLayout; - #[allow(unused_imports)] - use vulkano::pipeline::layout::PipelineLayoutPcRange; - #[allow(unused_imports)] - use vulkano::shader::DescriptorRequirements; - #[allow(unused_imports)] - use vulkano::shader::ShaderStages; - #[allow(unused_imports)] - use vulkano::shader::SpecializationConstants as SpecConstsTrait; - #[allow(unused_imports)] - use vulkano::shader::SpecializationMapEntry; - #[allow(unused_imports)] - use vulkano::Version; - #( #shaders_code )* diff --git a/vulkano-shaders/src/structs.rs b/vulkano-shaders/src/structs.rs index 0bb3ebd68..b04c24dda 100644 --- a/vulkano-shaders/src/structs.rs +++ b/vulkano-shaders/src/structs.rs @@ -814,7 +814,7 @@ pub(super) fn write_specialization_constants<'a>( let constant_id = spec_const.constant_id; let rust_size = spec_const.rust_size; map_entries.push(quote! { - SpecializationMapEntry { + ::vulkano::shader::SpecializationMapEntry { constant_id: #constant_id, offset: #curr_offset, size: #rust_size, @@ -857,9 +857,9 @@ pub(super) fn write_specialization_constants<'a>( } } - unsafe impl SpecConstsTrait for #struct_name { - fn descriptors() -> &'static [SpecializationMapEntry] { - static DESCRIPTORS: [SpecializationMapEntry; #num_map_entries] = [ + unsafe impl ::vulkano::shader::SpecializationConstants for #struct_name { + fn descriptors() -> &'static [::vulkano::shader::SpecializationMapEntry] { + static DESCRIPTORS: [::vulkano::shader::SpecializationMapEntry; #num_map_entries] = [ #( #map_entries ),* ]; &DESCRIPTORS diff --git a/vulkano/src/command_buffer/synced/commands.rs b/vulkano/src/command_buffer/synced/commands.rs index 830e350d8..1ac5961c5 100644 --- a/vulkano/src/command_buffer/synced/commands.rs +++ b/vulkano/src/command_buffer/synced/commands.rs @@ -2732,7 +2732,7 @@ impl SyncCommandBufferBuilder { let access = (0..).map(|index| { let mut access = access; - let mutable = reqs.mutable.contains(&index); + let mutable = reqs.storage_write.contains(&index); access.access.shader_write = mutable; access.exclusive = mutable; access diff --git a/vulkano/src/command_buffer/validity/blit_image.rs b/vulkano/src/command_buffer/validity/blit_image.rs index e50d1c293..5492fc20f 100644 --- a/vulkano/src/command_buffer/validity/blit_image.rs +++ b/vulkano/src/command_buffer/validity/blit_image.rs @@ -102,14 +102,14 @@ where } } - let source_dimensions = match source.dimensions().mipmap_dimensions(source_mip_level) { + let source_dimensions = match source.dimensions().mip_level_dimensions(source_mip_level) { Some(d) => d, None => return Err(CheckBlitImageError::SourceCoordinatesOutOfRange), }; let destination_dimensions = match destination .dimensions() - .mipmap_dimensions(destination_mip_level) + .mip_level_dimensions(destination_mip_level) { Some(d) => d, None => return Err(CheckBlitImageError::DestinationCoordinatesOutOfRange), diff --git a/vulkano/src/command_buffer/validity/copy_image.rs b/vulkano/src/command_buffer/validity/copy_image.rs index 13b4ba159..82aacb26a 100644 --- a/vulkano/src/command_buffer/validity/copy_image.rs +++ b/vulkano/src/command_buffer/validity/copy_image.rs @@ -88,14 +88,14 @@ where } } - let source_dimensions = match source.dimensions().mipmap_dimensions(source_mip_level) { + let source_dimensions = match source.dimensions().mip_level_dimensions(source_mip_level) { Some(d) => d, None => return Err(CheckCopyImageError::SourceCoordinatesOutOfRange), }; let destination_dimensions = match destination .dimensions() - .mipmap_dimensions(destination_mip_level) + .mip_level_dimensions(destination_mip_level) { Some(d) => d, None => return Err(CheckCopyImageError::DestinationCoordinatesOutOfRange), diff --git a/vulkano/src/command_buffer/validity/copy_image_buffer.rs b/vulkano/src/command_buffer/validity/copy_image_buffer.rs index 01273cd8d..4da7e55f8 100644 --- a/vulkano/src/command_buffer/validity/copy_image_buffer.rs +++ b/vulkano/src/command_buffer/validity/copy_image_buffer.rs @@ -86,7 +86,7 @@ where return Err(CheckCopyBufferImageError::UnexpectedMultisampled); } - let image_dimensions = match image.dimensions().mipmap_dimensions(image_mipmap) { + let image_dimensions = match image.dimensions().mip_level_dimensions(image_mipmap) { Some(d) => d, None => return Err(CheckCopyBufferImageError::ImageCoordinatesOutOfRange), }; diff --git a/vulkano/src/command_buffer/validity/descriptor_sets.rs b/vulkano/src/command_buffer/validity/descriptor_sets.rs index ec77cff82..fd6327970 100644 --- a/vulkano/src/command_buffer/validity/descriptor_sets.rs +++ b/vulkano/src/command_buffer/validity/descriptor_sets.rs @@ -7,14 +7,20 @@ // notice may not be copied, modified, or distributed except // according to those terms. +use crate::buffer::BufferAccess; +use crate::buffer::BufferViewAbstract; use crate::command_buffer::synced::CommandBufferState; +use crate::descriptor_set::layout::DescriptorType; use crate::descriptor_set::DescriptorBindingResources; use crate::format::Format; use crate::image::view::ImageViewType; use crate::image::ImageViewAbstract; use crate::image::SampleCount; use crate::pipeline::Pipeline; +use crate::sampler::Sampler; +use crate::sampler::SamplerImageViewIncompatibleError; use crate::shader::DescriptorRequirements; +use crate::shader::ShaderScalarType; use std::error; use std::fmt; use std::sync::Arc; @@ -29,12 +35,14 @@ pub(in super::super) fn check_descriptor_sets_validity<'a, P: Pipeline>( return Ok(()); } + // VUID-vkCmdDispatch-None-02697 let bindings_pipeline_layout = match current_state.descriptor_sets_pipeline_layout(pipeline.bind_point()) { Some(x) => x, None => return Err(CheckDescriptorSetsValidityError::IncompatiblePipelineLayout), }; + // VUID-vkCmdDispatch-None-02697 if !pipeline.layout().is_compatible_with( bindings_pipeline_layout, pipeline.num_used_descriptor_sets(), @@ -43,17 +51,68 @@ pub(in super::super) fn check_descriptor_sets_validity<'a, P: Pipeline>( } for ((set_num, binding_num), reqs) in descriptor_requirements { - let check_image_view = |image_view: &Arc| { - if let Some(image_view_type) = reqs.image_view_type { - if image_view.ty() != image_view_type { - return Err(InvalidDescriptorResource::ImageViewTypeMismatch { - required: reqs.image_view_type.unwrap(), - obtained: image_view.ty(), - }); + let layout_binding = pipeline.layout().descriptor_set_layouts()[set_num as usize] + .desc() + .descriptor(binding_num) + .unwrap(); + + let check_buffer = |index: u32, buffer: &Arc| Ok(()); + + let check_buffer_view = |index: u32, buffer_view: &Arc| { + if layout_binding.ty == DescriptorType::StorageTexelBuffer { + // VUID-vkCmdDispatch-OpTypeImage-06423 + if reqs.image_format.is_none() + && reqs.storage_write.contains(&index) + && !buffer_view.format_features().storage_write_without_format + { + return Err(InvalidDescriptorResource::StorageWriteWithoutFormatNotSupported); + } + + // VUID-vkCmdDispatch-OpTypeImage-06424 + if reqs.image_format.is_none() + && reqs.storage_read.contains(&index) + && !buffer_view.format_features().storage_read_without_format + { + return Err(InvalidDescriptorResource::StorageReadWithoutFormatNotSupported); } } - if let Some(format) = reqs.format { + Ok(()) + }; + + let check_image_view_common = |index: u32, image_view: &Arc| { + // VUID-vkCmdDispatch-None-02691 + if reqs.storage_image_atomic.contains(&index) + && !image_view.format_features().storage_image_atomic + { + return Err(InvalidDescriptorResource::StorageImageAtomicNotSupported); + } + + if layout_binding.ty == DescriptorType::StorageImage { + // VUID-vkCmdDispatch-OpTypeImage-06423 + if reqs.image_format.is_none() + && reqs.storage_write.contains(&index) + && !image_view.format_features().storage_write_without_format + { + return Err(InvalidDescriptorResource::StorageWriteWithoutFormatNotSupported); + } + + // VUID-vkCmdDispatch-OpTypeImage-06424 + if reqs.image_format.is_none() + && reqs.storage_read.contains(&index) + && !image_view.format_features().storage_read_without_format + { + return Err(InvalidDescriptorResource::StorageReadWithoutFormatNotSupported); + } + } + + /* + Instruction/Sampler/Image View Validation + https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap16.html#textures-input-validation + */ + + // The SPIR-V Image Format is not compatible with the image view’s format. + if let Some(format) = reqs.image_format { if image_view.format() != format { return Err(InvalidDescriptorResource::ImageViewFormatMismatch { required: format, @@ -62,13 +121,145 @@ pub(in super::super) fn check_descriptor_sets_validity<'a, P: Pipeline>( } } - if reqs.multisampled != (image_view.image().samples() != SampleCount::Sample1) { - return Err(InvalidDescriptorResource::ImageMultisampledMismatch { - required: reqs.multisampled, + // Rules for viewType + if let Some(image_view_type) = reqs.image_view_type { + if image_view.ty() != image_view_type { + return Err(InvalidDescriptorResource::ImageViewTypeMismatch { + required: image_view_type, + obtained: image_view.ty(), + }); + } + } + + // - If the image was created with VkImageCreateInfo::samples equal to + // VK_SAMPLE_COUNT_1_BIT, the instruction must have MS = 0. + // - If the image was created with VkImageCreateInfo::samples not equal to + // VK_SAMPLE_COUNT_1_BIT, the instruction must have MS = 1. + if reqs.image_multisampled != (image_view.image().samples() != SampleCount::Sample1) { + return Err(InvalidDescriptorResource::ImageViewMultisampledMismatch { + required: reqs.image_multisampled, obtained: image_view.image().samples() != SampleCount::Sample1, }); } + // - If the Sampled Type of the OpTypeImage does not match the numeric format of the + // image, as shown in the SPIR-V Sampled Type column of the + // Interpretation of Numeric Format table. + // - If the signedness of any read or sample operation does not match the signedness of + // the image’s format. + if let Some(scalar_type) = reqs.image_scalar_type { + let aspects = image_view.aspects(); + let view_scalar_type = ShaderScalarType::from( + if aspects.color || aspects.plane0 || aspects.plane1 || aspects.plane2 { + image_view.format().type_color().unwrap() + } else if aspects.depth { + image_view.format().type_depth().unwrap() + } else if aspects.stencil { + image_view.format().type_stencil().unwrap() + } else { + // Per `ImageViewBuilder::aspects` and + // VUID-VkDescriptorImageInfo-imageView-01976 + unreachable!() + }, + ); + + if scalar_type != view_scalar_type { + return Err(InvalidDescriptorResource::ImageViewScalarTypeMismatch { + required: scalar_type, + obtained: view_scalar_type, + }); + } + } + + Ok(()) + }; + + let check_sampler_common = |index: u32, sampler: &Arc| { + // VUID-vkCmdDispatch-None-02703 + // VUID-vkCmdDispatch-None-02704 + if reqs.sampler_no_unnormalized_coordinates.contains(&index) + && sampler.unnormalized_coordinates() + { + return Err(InvalidDescriptorResource::SamplerUnnormalizedCoordinatesNotAllowed); + } + + /* + Instruction/Sampler/Image View Validation + https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap16.html#textures-input-validation + */ + + // - The SPIR-V instruction is one of the OpImage*Dref* instructions and the sampler + // compareEnable is VK_FALSE + // - The SPIR-V instruction is not one of the OpImage*Dref* instructions and the sampler + // compareEnable is VK_TRUE + if reqs.sampler_compare.contains(&index) != sampler.compare().is_some() { + return Err(InvalidDescriptorResource::SamplerCompareMismatch { + required: reqs.sampler_compare.contains(&index), + obtained: sampler.compare().is_some(), + }); + } + + Ok(()) + }; + + let check_image_view = |index: u32, image_view: &Arc| { + check_image_view_common(index, image_view)?; + + if let Some(sampler) = layout_binding.immutable_samplers.get(index as usize) { + check_sampler_common(index, sampler)?; + } + + Ok(()) + }; + + let check_image_view_sampler = + |index: u32, (image_view, sampler): &(Arc, Arc)| { + check_image_view_common(index, image_view)?; + check_sampler_common(index, sampler)?; + + Ok(()) + }; + + let check_sampler = |index: u32, sampler: &Arc| { + check_sampler_common(index, sampler)?; + + // Check sampler-image compatibility. Only done for separate samplers; combined image + // samplers are checked when updating the descriptor set. + if let Some(with_images) = reqs.sampler_with_images.get(&index) { + // If the image view isn't actually present in the resources, then just skip it. + // It will be caught later by check_resources. + let iter = with_images.iter().filter_map(|id| { + current_state + .descriptor_set(pipeline.bind_point(), id.set) + .and_then(|set| set.resources().binding(id.binding)) + .and_then(|res| match res { + DescriptorBindingResources::ImageView(elements) => elements + .get(id.index as usize) + .and_then(|opt| opt.as_ref().map(|opt| (id, opt))), + _ => None, + }) + }); + + for (id, image_view) in iter { + if let Err(error) = sampler.check_can_sample(image_view.as_ref()) { + return Err(InvalidDescriptorResource::SamplerImageViewIncompatible { + image_view_set_num: id.set, + image_view_binding_num: id.binding, + image_view_index: id.index, + error, + }); + } + } + } + + Ok(()) + }; + + let check_none = |index: u32, _: &()| { + if let Some(sampler) = layout_binding.immutable_samplers.get(index as usize) { + check_sampler(index, sampler)?; + } + Ok(()) }; @@ -80,25 +271,29 @@ pub(in super::super) fn check_descriptor_sets_validity<'a, P: Pipeline>( let binding_resources = set_resources.binding(binding_num).unwrap(); match binding_resources { - DescriptorBindingResources::None(_) => (), + DescriptorBindingResources::None(elements) => { + check_resources(set_num, binding_num, reqs, elements, check_none)?; + } DescriptorBindingResources::Buffer(elements) => { - check_resources(set_num, binding_num, reqs, elements, |_| Ok(()))?; + check_resources(set_num, binding_num, reqs, elements, check_buffer)?; } DescriptorBindingResources::BufferView(elements) => { - check_resources(set_num, binding_num, reqs, elements, |_| Ok(()))?; + check_resources(set_num, binding_num, reqs, elements, check_buffer_view)?; } DescriptorBindingResources::ImageView(elements) => { - check_resources(set_num, binding_num, reqs, elements, |i| { - check_image_view(i) - })?; + check_resources(set_num, binding_num, reqs, elements, check_image_view)?; } DescriptorBindingResources::ImageViewSampler(elements) => { - check_resources(set_num, binding_num, reqs, elements, |(i, s)| { - check_image_view(i) - })?; + check_resources( + set_num, + binding_num, + reqs, + elements, + check_image_view_sampler, + )?; } DescriptorBindingResources::Sampler(elements) => { - check_resources(set_num, binding_num, reqs, elements, |_| Ok(()))?; + check_resources(set_num, binding_num, reqs, elements, check_sampler)?; } } } @@ -162,12 +357,15 @@ fn check_resources( binding_num: u32, reqs: &DescriptorRequirements, elements: &[Option], - mut extra_check: impl FnMut(&T) -> Result<(), InvalidDescriptorResource>, + mut extra_check: impl FnMut(u32, &T) -> Result<(), InvalidDescriptorResource>, ) -> Result<(), CheckDescriptorSetsValidityError> { for (index, element) in elements[0..reqs.descriptor_count as usize] .iter() .enumerate() { + let index = index as u32; + + // VUID-vkCmdDispatch-None-02699 let element = match element { Some(x) => x, None => { @@ -175,19 +373,19 @@ fn check_resources( CheckDescriptorSetsValidityError::InvalidDescriptorResource { set_num, binding_num, - index: index as u32, + index, error: InvalidDescriptorResource::Missing, }, ) } }; - if let Err(error) = extra_check(element) { + if let Err(error) = extra_check(index, element) { return Err( CheckDescriptorSetsValidityError::InvalidDescriptorResource { set_num, binding_num, - index: index as u32, + index, error, }, ); @@ -203,35 +401,95 @@ pub enum InvalidDescriptorResource { required: Format, obtained: Format, }, - ImageMultisampledMismatch { + ImageViewMultisampledMismatch { required: bool, obtained: bool, }, + ImageViewScalarTypeMismatch { + required: ShaderScalarType, + obtained: ShaderScalarType, + }, ImageViewTypeMismatch { required: ImageViewType, obtained: ImageViewType, }, Missing, + SamplerCompareMismatch { + required: bool, + obtained: bool, + }, + SamplerImageViewIncompatible { + image_view_set_num: u32, + image_view_binding_num: u32, + image_view_index: u32, + error: SamplerImageViewIncompatibleError, + }, + SamplerUnnormalizedCoordinatesNotAllowed, + StorageImageAtomicNotSupported, + StorageReadWithoutFormatNotSupported, + StorageWriteWithoutFormatNotSupported, } -impl error::Error for InvalidDescriptorResource {} +impl error::Error for InvalidDescriptorResource { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match self { + Self::SamplerImageViewIncompatible { error, .. } => Some(error), + _ => None, + } + } +} impl fmt::Display for InvalidDescriptorResource { #[inline] fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { match self { - Self::Missing => { - write!(fmt, "no resource was bound") - } Self::ImageViewFormatMismatch { required, obtained } => { write!(fmt, "the bound image view did not have the required format; required {:?}, obtained {:?}", required, obtained) } - Self::ImageMultisampledMismatch { required, obtained } => { + Self::ImageViewMultisampledMismatch { required, obtained } => { write!(fmt, "the bound image did not have the required multisampling; required {}, obtained {}", required, obtained) } + Self::ImageViewScalarTypeMismatch { required, obtained } => { + write!(fmt, "the bound image view did not have a format and aspect with the required scalar type; required {:?}, obtained {:?}", required, obtained) + } Self::ImageViewTypeMismatch { required, obtained } => { write!(fmt, "the bound image view did not have the required type; required {:?}, obtained {:?}", required, obtained) } + Self::Missing => { + write!(fmt, "no resource was bound") + } + Self::SamplerImageViewIncompatible { + image_view_set_num, + image_view_binding_num, + image_view_index, + .. + } => { + write!( + fmt, + "the bound sampler samples an image view that is not compatible with it" + ) + } + Self::SamplerCompareMismatch { required, obtained } => { + write!( + fmt, + "the bound sampler did not have the required depth comparison state; required {}, obtained {}", required, obtained + ) + } + Self::SamplerUnnormalizedCoordinatesNotAllowed => { + write!( + fmt, + "the bound sampler is required to have unnormalized coordinates disabled" + ) + } + Self::StorageImageAtomicNotSupported => { + write!(fmt, "the bound image view did not support the `storage_image_atomic` format feature") + } + Self::StorageReadWithoutFormatNotSupported => { + write!(fmt, "the bound image view or buffer view did not support the `storage_read_without_format` format feature") + } + Self::StorageWriteWithoutFormatNotSupported => { + write!(fmt, "the bound image view or buffer view did not support the `storage_write_without_format` format feature") + } } } } diff --git a/vulkano/src/descriptor_set/layout/desc.rs b/vulkano/src/descriptor_set/layout/desc.rs index bd001be6a..0d453c7bb 100644 --- a/vulkano/src/descriptor_set/layout/desc.rs +++ b/vulkano/src/descriptor_set/layout/desc.rs @@ -311,14 +311,17 @@ impl DescriptorDesc { let DescriptorRequirements { descriptor_types, descriptor_count, - format, + image_format, + image_multisampled, + image_scalar_type, image_view_type, - multisampled, - mutable, - sampler_no_unnormalized, + sampler_compare, + sampler_no_unnormalized_coordinates, sampler_with_images, stages, storage_image_atomic, + storage_read, + storage_write, } = descriptor_requirements; if !descriptor_types.contains(&self.ty) { diff --git a/vulkano/src/descriptor_set/mod.rs b/vulkano/src/descriptor_set/mod.rs index ee2cecfe5..adced46af 100644 --- a/vulkano/src/descriptor_set/mod.rs +++ b/vulkano/src/descriptor_set/mod.rs @@ -499,7 +499,7 @@ where } } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug)] pub enum DescriptorSetCreationError { DescriptorSetUpdateError(DescriptorSetUpdateError), OomError(OomError), diff --git a/vulkano/src/descriptor_set/update.rs b/vulkano/src/descriptor_set/update.rs index 185045530..602c3fb26 100644 --- a/vulkano/src/descriptor_set/update.rs +++ b/vulkano/src/descriptor_set/update.rs @@ -11,8 +11,9 @@ use crate::buffer::{BufferAccess, BufferInner, BufferViewAbstract}; use crate::descriptor_set::layout::{DescriptorDesc, DescriptorType}; use crate::descriptor_set::DescriptorSetLayout; use crate::device::DeviceOwned; -use crate::image::ImageViewAbstract; -use crate::sampler::Sampler; +use crate::image::view::ImageViewType; +use crate::image::{ImageType, ImageViewAbstract}; +use crate::sampler::{Sampler, SamplerImageViewIncompatibleError}; use crate::DeviceSize; use crate::VulkanObject; use smallvec::SmallVec; @@ -561,7 +562,8 @@ pub(crate) fn check_descriptor_write<'a>( layout.device().internal_object(), ); - if !image_view.image().inner().image.usage().sampled { + // VUID-VkWriteDescriptorSet-descriptorType-00337 + if !image_view.usage().sampled { return Err(DescriptorSetUpdateError::MissingUsage { binding: write.binding(), index: descriptor_range_start + index as u32, @@ -569,10 +571,32 @@ pub(crate) fn check_descriptor_write<'a>( }); } - if !sampler.can_sample(image_view.as_ref()) { + // VUID-VkDescriptorImageInfo-imageView-00343 + if matches!( + image_view.ty(), + ImageViewType::Dim2d | ImageViewType::Dim2dArray + ) && image_view.image().inner().image.dimensions().image_type() + == ImageType::Dim3d + { + return Err(DescriptorSetUpdateError::ImageView2dFrom3d { + binding: write.binding(), + index: descriptor_range_start + index as u32, + }); + } + + // VUID-VkDescriptorImageInfo-imageView-01976 + if image_view.aspects().depth && image_view.aspects().stencil { + return Err(DescriptorSetUpdateError::ImageViewDepthAndStencil { + binding: write.binding(), + index: descriptor_range_start + index as u32, + }); + } + + if let Err(error) = sampler.check_can_sample(image_view.as_ref()) { return Err(DescriptorSetUpdateError::ImageViewIncompatibleSampler { binding: write.binding(), index: descriptor_range_start + index as u32, + error, }); } } @@ -584,13 +608,35 @@ pub(crate) fn check_descriptor_write<'a>( layout.device().internal_object(), ); - if !image_view.image().inner().image.usage().sampled { + // VUID-VkWriteDescriptorSet-descriptorType-00337 + if !image_view.usage().sampled { return Err(DescriptorSetUpdateError::MissingUsage { binding: write.binding(), index: descriptor_range_start + index as u32, usage: "sampled", }); } + + // VUID-VkDescriptorImageInfo-imageView-00343 + if matches!( + image_view.ty(), + ImageViewType::Dim2d | ImageViewType::Dim2dArray + ) && image_view.image().inner().image.dimensions().image_type() + == ImageType::Dim3d + { + return Err(DescriptorSetUpdateError::ImageView2dFrom3d { + binding: write.binding(), + index: descriptor_range_start + index as u32, + }); + } + + // VUID-VkDescriptorImageInfo-imageView-01976 + if image_view.aspects().depth && image_view.aspects().stencil { + return Err(DescriptorSetUpdateError::ImageViewDepthAndStencil { + binding: write.binding(), + index: descriptor_range_start + index as u32, + }); + } } } DescriptorType::StorageImage => { @@ -600,7 +646,8 @@ pub(crate) fn check_descriptor_write<'a>( layout.device().internal_object(), ); - if !image_view.image().inner().image.usage().storage { + // VUID-VkWriteDescriptorSet-descriptorType-00339 + if !image_view.usage().storage { return Err(DescriptorSetUpdateError::MissingUsage { binding: write.binding(), index: descriptor_range_start + index as u32, @@ -608,6 +655,28 @@ pub(crate) fn check_descriptor_write<'a>( }); } + // VUID-VkDescriptorImageInfo-imageView-00343 + if matches!( + image_view.ty(), + ImageViewType::Dim2d | ImageViewType::Dim2dArray + ) && image_view.image().inner().image.dimensions().image_type() + == ImageType::Dim3d + { + return Err(DescriptorSetUpdateError::ImageView2dFrom3d { + binding: write.binding(), + index: descriptor_range_start + index as u32, + }); + } + + // VUID-VkDescriptorImageInfo-imageView-01976 + if image_view.aspects().depth && image_view.aspects().stencil { + return Err(DescriptorSetUpdateError::ImageViewDepthAndStencil { + binding: write.binding(), + index: descriptor_range_start + index as u32, + }); + } + + // VUID-VkWriteDescriptorSet-descriptorType-00336 if !image_view.component_mapping().is_identity() { return Err(DescriptorSetUpdateError::ImageViewNotIdentitySwizzled { binding: write.binding(), @@ -623,7 +692,8 @@ pub(crate) fn check_descriptor_write<'a>( layout.device().internal_object(), ); - if !image_view.image().inner().image.usage().input_attachment { + // VUID-VkWriteDescriptorSet-descriptorType-00338 + if !image_view.usage().input_attachment { return Err(DescriptorSetUpdateError::MissingUsage { binding: write.binding(), index: descriptor_range_start + index as u32, @@ -631,6 +701,28 @@ pub(crate) fn check_descriptor_write<'a>( }); } + // VUID-VkDescriptorImageInfo-imageView-00343 + if matches!( + image_view.ty(), + ImageViewType::Dim2d | ImageViewType::Dim2dArray + ) && image_view.image().inner().image.dimensions().image_type() + == ImageType::Dim3d + { + return Err(DescriptorSetUpdateError::ImageView2dFrom3d { + binding: write.binding(), + index: descriptor_range_start + index as u32, + }); + } + + // VUID-VkDescriptorImageInfo-imageView-01976 + if image_view.aspects().depth && image_view.aspects().stencil { + return Err(DescriptorSetUpdateError::ImageViewDepthAndStencil { + binding: write.binding(), + index: descriptor_range_start + index as u32, + }); + } + + // VUID-VkWriteDescriptorSet-descriptorType-00336 if !image_view.component_mapping().is_identity() { return Err(DescriptorSetUpdateError::ImageViewNotIdentitySwizzled { binding: write.binding(), @@ -638,9 +730,7 @@ pub(crate) fn check_descriptor_write<'a>( }); } - let image_layers = image_view.array_layers(); - let num_layers = image_layers.end - image_layers.start; - + // VUID?? if image_view.ty().is_arrayed() { return Err(DescriptorSetUpdateError::ImageViewIsArrayed { binding: write.binding(), @@ -673,7 +763,8 @@ pub(crate) fn check_descriptor_write<'a>( layout.device().internal_object(), ); - if !image_view.image().inner().image.usage().sampled { + // VUID-VkWriteDescriptorSet-descriptorType-00337 + if !image_view.usage().sampled { return Err(DescriptorSetUpdateError::MissingUsage { binding: write.binding(), index: descriptor_range_start + index as u32, @@ -681,10 +772,32 @@ pub(crate) fn check_descriptor_write<'a>( }); } - if !sampler.can_sample(image_view.as_ref()) { + // VUID-VkDescriptorImageInfo-imageView-00343 + if matches!( + image_view.ty(), + ImageViewType::Dim2d | ImageViewType::Dim2dArray + ) && image_view.image().inner().image.dimensions().image_type() + == ImageType::Dim3d + { + return Err(DescriptorSetUpdateError::ImageView2dFrom3d { + binding: write.binding(), + index: descriptor_range_start + index as u32, + }); + } + + // VUID-VkDescriptorImageInfo-imageView-01976 + if image_view.aspects().depth && image_view.aspects().stencil { + return Err(DescriptorSetUpdateError::ImageViewDepthAndStencil { + binding: write.binding(), + index: descriptor_range_start + index as u32, + }); + } + + if let Err(error) = sampler.check_can_sample(image_view.as_ref()) { return Err(DescriptorSetUpdateError::ImageViewIncompatibleSampler { binding: write.binding(), index: descriptor_range_start + index as u32, + error, }); } } @@ -721,7 +834,7 @@ pub(crate) fn check_descriptor_write<'a>( Ok(layout_binding) } -#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug)] pub enum DescriptorSetUpdateError { /// Tried to write more elements than were available in a binding. ArrayIndexOutOfBounds { @@ -733,13 +846,23 @@ pub enum DescriptorSetUpdateError { written_count: u32, }, + /// Tried to write an image view with a 2D type and a 3D underlying image. + ImageView2dFrom3d { binding: u32, index: u32 }, + + /// Tried to write an image view that has both the `depth` and `stencil` aspects. + ImageViewDepthAndStencil { binding: u32, index: u32 }, + /// Tried to write an image view of an arrayed type to a descriptor type that does not support /// it. ImageViewIsArrayed { binding: u32, index: u32 }, /// Tried to write an image view that was not compatible with the sampler that was provided as /// part of the update or immutably in the layout. - ImageViewIncompatibleSampler { binding: u32, index: u32 }, + ImageViewIncompatibleSampler { + binding: u32, + index: u32, + error: SamplerImageViewIncompatibleError, + }, /// Tried to write an image view to a descriptor type that requires it to be identity swizzled, /// but it was not. @@ -763,7 +886,14 @@ pub enum DescriptorSetUpdateError { SamplerIsImmutable { binding: u32 }, } -impl std::error::Error for DescriptorSetUpdateError {} +impl std::error::Error for DescriptorSetUpdateError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::ImageViewIncompatibleSampler { error, .. } => Some(error), + _ => None, + } + } +} impl std::fmt::Display for DescriptorSetUpdateError { #[inline] @@ -778,12 +908,22 @@ impl std::fmt::Display for DescriptorSetUpdateError { "tried to write up to element {} to binding {}, but only {} descriptors are available", written_count, binding, available_count, ), + Self::ImageView2dFrom3d { binding, index } => write!( + fmt, + "tried to write an image view to binding {} index {} with a 2D type and a 3D underlying image", + binding, index, + ), + Self::ImageViewDepthAndStencil { binding, index } => write!( + fmt, + "tried to write an image view to binding {} index {} that has both the `depth` and `stencil` aspects", + binding, index, + ), Self::ImageViewIsArrayed { binding, index } => write!( fmt, "tried to write an image view of an arrayed type to binding {} index {}, but this binding has a descriptor type that does not support arrayed image views", binding, index, ), - Self::ImageViewIncompatibleSampler { binding, index } => write!( + Self::ImageViewIncompatibleSampler { binding, index, .. } => write!( fmt, "tried to write an image view to binding {} index {}, that was not compatible with the sampler that was provided as part of the update or immutably in the layout", binding, index, diff --git a/vulkano/src/device/physical.rs b/vulkano/src/device/physical.rs index dea3fbe6c..04804ca04 100644 --- a/vulkano/src/device/physical.rs +++ b/vulkano/src/device/physical.rs @@ -9,6 +9,7 @@ use crate::device::{DeviceExtensions, Features, FeaturesFfi, Properties, PropertiesFfi}; use crate::format::Format; +use crate::image::view::ImageViewType; use crate::image::{ImageCreateFlags, ImageTiling, ImageType, ImageUsage, SampleCounts}; use crate::instance::{Instance, InstanceCreationError}; use crate::memory::ExternalMemoryHandleType; @@ -494,7 +495,10 @@ impl<'a> PhysicalDevice<'a> { usage: ImageUsage, flags: ImageCreateFlags, external_memory_handle_type: Option, + image_view_type: Option, ) -> Result, OomError> { + /* Input */ + let mut format_info2 = ash::vk::PhysicalDeviceImageFormatInfo2::builder() .format(format.into()) .ty(ty.into()) @@ -526,7 +530,40 @@ impl<'a> PhysicalDevice<'a> { format_info2 = format_info2.push_next(next); } + let mut image_view_image_format_info = if let Some(image_view_type) = image_view_type { + if !self.supported_extensions().ext_filter_cubic { + // Can't query this, return unsupported + return Ok(None); + } + + if !image_view_type.is_compatible_with(ty) { + return Ok(None); + } + + Some( + ash::vk::PhysicalDeviceImageViewImageFormatInfoEXT::builder() + .image_view_type(image_view_type.into()), + ) + } else { + None + }; + + if let Some(next) = image_view_image_format_info.as_mut() { + format_info2 = format_info2.push_next(next); + } + + /* Output */ + let mut image_format_properties2 = ash::vk::ImageFormatProperties2::default(); + let mut filter_cubic_image_view_image_format_properties = + ash::vk::FilterCubicImageViewImageFormatPropertiesEXT::default(); + + if image_view_type.is_some() { + filter_cubic_image_view_image_format_properties.p_next = + image_format_properties2.p_next; + image_format_properties2.p_next = + &mut filter_cubic_image_view_image_format_properties as *mut _ as *mut _; + } let result = unsafe { let fns = self.instance.fns(); @@ -567,9 +604,14 @@ impl<'a> PhysicalDevice<'a> { }; match result { - Ok(_) => Ok(Some( - image_format_properties2.image_format_properties.into(), - )), + Ok(_) => Ok(Some(ImageFormatProperties { + filter_cubic: filter_cubic_image_view_image_format_properties.filter_cubic + != ash::vk::FALSE, + filter_cubic_minmax: filter_cubic_image_view_image_format_properties + .filter_cubic_minmax + != ash::vk::FALSE, + ..image_format_properties2.image_format_properties.into() + })), Err(Error::FormatNotSupported) => Ok(None), Err(err) => Err(err.into()), } @@ -1307,6 +1349,14 @@ pub struct ImageFormatProperties { /// The maximum total size of an image, in bytes. This is guaranteed to be at least /// 0x80000000. pub max_resource_size: DeviceSize, + /// When querying with an image view type, whether such image views support sampling with + /// a [`Cubic`](crate::sampler::Filter::Cubic) `mag_filter` or `min_filter`. + pub filter_cubic: bool, + /// When querying with an image view type, whether such image views support sampling with + /// a [`Cubic`](crate::sampler::Filter::Cubic) `mag_filter` or `min_filter`, and with a + /// [`Min`](crate::sampler::SamplerReductionMode::Min) or + /// [`Max`](crate::sampler::SamplerReductionMode::Max) `reduction_mode`. + pub filter_cubic_minmax: bool, } impl From for ImageFormatProperties { @@ -1321,6 +1371,8 @@ impl From for ImageFormatProperties { max_array_layers: props.max_array_layers, sample_counts: props.sample_counts.into(), max_resource_size: props.max_resource_size, + filter_cubic: false, + filter_cubic_minmax: false, } } } diff --git a/vulkano/src/format.rs b/vulkano/src/format.rs index fe658d1b4..6c6c534db 100644 --- a/vulkano/src/format.rs +++ b/vulkano/src/format.rs @@ -113,6 +113,58 @@ impl Format { physical_device.format_properties(*self) } + /// Returns whether the format can be used with a storage image, without specifying + /// the format in the shader, if the + /// [`shader_storage_image_read_without_format`](crate::device::Features::shader_storage_image_read_without_format) + /// and/or + /// [`shader_storage_image_write_without_format`](crate::device::Features::shader_storage_image_write_without_format) + /// features are enabled on the device. + #[inline] + pub fn shader_storage_image_without_format(&self) -> bool { + matches!( + *self, + Format::R8G8B8A8_UNORM + | Format::R8G8B8A8_SNORM + | Format::R8G8B8A8_UINT + | Format::R8G8B8A8_SINT + | Format::R32_UINT + | Format::R32_SINT + | Format::R32_SFLOAT + | Format::R32G32_UINT + | Format::R32G32_SINT + | Format::R32G32_SFLOAT + | Format::R32G32B32A32_UINT + | Format::R32G32B32A32_SINT + | Format::R32G32B32A32_SFLOAT + | Format::R16G16B16A16_UINT + | Format::R16G16B16A16_SINT + | Format::R16G16B16A16_SFLOAT + | Format::R16G16_SFLOAT + | Format::B10G11R11_UFLOAT_PACK32 + | Format::R16_SFLOAT + | Format::R16G16B16A16_UNORM + | Format::A2B10G10R10_UNORM_PACK32 + | Format::R16G16_UNORM + | Format::R8G8_UNORM + | Format::R16_UNORM + | Format::R8_UNORM + | Format::R16G16B16A16_SNORM + | Format::R16G16_SNORM + | Format::R8G8_SNORM + | Format::R16_SNORM + | Format::R8_SNORM + | Format::R16G16_SINT + | Format::R8G8_SINT + | Format::R16_SINT + | Format::R8_SINT + | Format::A2B10G10R10_UINT_PACK32 + | Format::R16G16_UINT + | Format::R8G8_UINT + | Format::R16_UINT + | Format::R8_UINT + ) + } + #[inline] pub fn decode_clear_value(&self, value: ClearValue) -> ClearValue { let aspects = self.aspects(); @@ -156,6 +208,7 @@ impl From for ash::vk::Format { } } +// https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap46.html#spirvenv-image-formats impl From for Option { fn from(val: ImageFormat) -> Self { match val { diff --git a/vulkano/src/image/aspect.rs b/vulkano/src/image/aspect.rs index 41d834eb3..6dfa8878a 100644 --- a/vulkano/src/image/aspect.rs +++ b/vulkano/src/image/aspect.rs @@ -66,6 +66,32 @@ impl ImageAspects { memory_plane2: false, } } + + pub const fn contains(&self, other: &Self) -> bool { + let Self { + color, + depth, + stencil, + metadata, + plane0, + plane1, + plane2, + memory_plane0, + memory_plane1, + memory_plane2, + } = *self; + + (color || !other.color) + && (depth || !other.depth) + && (stencil || !other.stencil) + && (metadata || !other.metadata) + && (plane0 || !other.plane0) + && (plane1 || !other.plane1) + && (plane2 || !other.plane2) + && (memory_plane0 || !other.memory_plane0) + && (memory_plane1 || !other.memory_plane1) + && (memory_plane2 || !other.memory_plane2) + } } impl BitOr for ImageAspects { diff --git a/vulkano/src/image/immutable.rs b/vulkano/src/image/immutable.rs index e5e795e7a..a8a4aa572 100644 --- a/vulkano/src/image/immutable.rs +++ b/vulkano/src/image/immutable.rs @@ -128,32 +128,18 @@ fn generate_mipmaps( for level in 1..image.mip_levels() { for layer in 0..image.dimensions().array_layers() { let [xs, ys, ds] = dimensions - .mipmap_dimensions(level - 1) + .mip_level_dimensions(level - 1) .unwrap() .width_height_depth(); let [xd, yd, dd] = dimensions - .mipmap_dimensions(level) + .mip_level_dimensions(level) .unwrap() .width_height_depth(); - - let src = SubImage::new( - image.clone(), - level - 1, - 1, - layer, - 1, - layout, - ); - - let dst = SubImage::new( - image.clone(), - level, - 1, - layer, - 1, - layout, - ); - + + let src = SubImage::new(image.clone(), level - 1, 1, layer, 1, layout); + + let dst = SubImage::new(image.clone(), level, 1, layer, 1, layout); + cbb.blit_image( src, //source [0, 0, 0], //source_top_left diff --git a/vulkano/src/image/mod.rs b/vulkano/src/image/mod.rs index f6ad3e3cb..c6415c1d1 100644 --- a/vulkano/src/image/mod.rs +++ b/vulkano/src/image/mod.rs @@ -415,9 +415,18 @@ impl ImageDimensions { self.width() * self.height() * self.depth() * self.array_layers() } - /// Returns the maximum number of mipmaps for these image dimensions. + #[inline] + pub fn image_type(&self) -> ImageType { + match *self { + ImageDimensions::Dim1d { .. } => ImageType::Dim1d, + ImageDimensions::Dim2d { .. } => ImageType::Dim2d, + ImageDimensions::Dim3d { .. } => ImageType::Dim3d, + } + } + + /// Returns the maximum number of mipmap levels for these image dimensions. /// - /// The returned value is always at least superior or equal to 1. + /// The returned value is always at least 1. /// /// # Example /// @@ -430,17 +439,28 @@ impl ImageDimensions { /// array_layers: 1, /// }; /// - /// assert_eq!(dims.max_mipmaps(), 6); + /// assert_eq!(dims.max_mip_levels(), 6); /// ``` /// - pub fn max_mipmaps(&self) -> u32 { - 32 - (self.width() | self.height() | self.depth()).leading_zeros() + #[inline] + pub fn max_mip_levels(&self) -> u32 { + // This calculates `log2(max(width, height, depth)) + 1` using fast integer operations. + let max = match *self { + ImageDimensions::Dim1d { width, .. } => width, + ImageDimensions::Dim2d { width, height, .. } => width | height, + ImageDimensions::Dim3d { + width, + height, + depth, + } => width | height | depth, + }; + 32 - max.leading_zeros() } /// Returns the dimensions of the `level`th mipmap level. If `level` is 0, then the dimensions /// are left unchanged. /// - /// Returns `None` if `level` is superior or equal to `max_mipmaps()`. + /// Returns `None` if `level` is superior or equal to `max_mip_levels()`. /// /// # Example /// @@ -453,23 +473,23 @@ impl ImageDimensions { /// array_layers: 1, /// }; /// - /// assert_eq!(dims.mipmap_dimensions(0), Some(dims)); - /// assert_eq!(dims.mipmap_dimensions(1), Some(ImageDimensions::Dim2d { + /// assert_eq!(dims.mip_level_dimensions(0), Some(dims)); + /// assert_eq!(dims.mip_level_dimensions(1), Some(ImageDimensions::Dim2d { /// width: 481, /// height: 128, /// array_layers: 1, /// })); - /// assert_eq!(dims.mipmap_dimensions(6), Some(ImageDimensions::Dim2d { + /// assert_eq!(dims.mip_level_dimensions(6), Some(ImageDimensions::Dim2d { /// width: 15, /// height: 4, /// array_layers: 1, /// })); - /// assert_eq!(dims.mipmap_dimensions(9), Some(ImageDimensions::Dim2d { + /// assert_eq!(dims.mip_level_dimensions(9), Some(ImageDimensions::Dim2d { /// width: 1, /// height: 1, /// array_layers: 1, /// })); - /// assert_eq!(dims.mipmap_dimensions(11), None); + /// assert_eq!(dims.mip_level_dimensions(11), None); /// ``` /// /// # Panic @@ -477,12 +497,12 @@ impl ImageDimensions { /// In debug mode, Panics if `width`, `height` or `depth` is equal to 0. In release, returns /// an unspecified value. /// - pub fn mipmap_dimensions(&self, level: u32) -> Option { + pub fn mip_level_dimensions(&self, level: u32) -> Option { if level == 0 { return Some(*self); } - if level >= self.max_mipmaps() { + if level >= self.max_mip_levels() { return None; } @@ -538,39 +558,39 @@ mod tests { use crate::image::MipmapsCount; #[test] - fn max_mipmaps() { + fn max_mip_levels() { let dims = ImageDimensions::Dim2d { width: 2, height: 1, array_layers: 1, }; - assert_eq!(dims.max_mipmaps(), 2); + assert_eq!(dims.max_mip_levels(), 2); let dims = ImageDimensions::Dim2d { width: 2, height: 3, array_layers: 1, }; - assert_eq!(dims.max_mipmaps(), 2); + assert_eq!(dims.max_mip_levels(), 2); let dims = ImageDimensions::Dim2d { width: 512, height: 512, array_layers: 1, }; - assert_eq!(dims.max_mipmaps(), 10); + assert_eq!(dims.max_mip_levels(), 10); } #[test] - fn mipmap_dimensions() { + fn mip_level_dimensions() { let dims = ImageDimensions::Dim2d { width: 283, height: 175, array_layers: 1, }; - assert_eq!(dims.mipmap_dimensions(0), Some(dims)); + assert_eq!(dims.mip_level_dimensions(0), Some(dims)); assert_eq!( - dims.mipmap_dimensions(1), + dims.mip_level_dimensions(1), Some(ImageDimensions::Dim2d { width: 141, height: 87, @@ -578,7 +598,7 @@ mod tests { }) ); assert_eq!( - dims.mipmap_dimensions(2), + dims.mip_level_dimensions(2), Some(ImageDimensions::Dim2d { width: 70, height: 43, @@ -586,7 +606,7 @@ mod tests { }) ); assert_eq!( - dims.mipmap_dimensions(3), + dims.mip_level_dimensions(3), Some(ImageDimensions::Dim2d { width: 35, height: 21, @@ -595,7 +615,7 @@ mod tests { ); assert_eq!( - dims.mipmap_dimensions(4), + dims.mip_level_dimensions(4), Some(ImageDimensions::Dim2d { width: 17, height: 10, @@ -603,7 +623,7 @@ mod tests { }) ); assert_eq!( - dims.mipmap_dimensions(5), + dims.mip_level_dimensions(5), Some(ImageDimensions::Dim2d { width: 8, height: 5, @@ -611,7 +631,7 @@ mod tests { }) ); assert_eq!( - dims.mipmap_dimensions(6), + dims.mip_level_dimensions(6), Some(ImageDimensions::Dim2d { width: 4, height: 2, @@ -619,7 +639,7 @@ mod tests { }) ); assert_eq!( - dims.mipmap_dimensions(7), + dims.mip_level_dimensions(7), Some(ImageDimensions::Dim2d { width: 2, height: 1, @@ -627,14 +647,14 @@ mod tests { }) ); assert_eq!( - dims.mipmap_dimensions(8), + dims.mip_level_dimensions(8), Some(ImageDimensions::Dim2d { width: 1, height: 1, array_layers: 1, }) ); - assert_eq!(dims.mipmap_dimensions(9), None); + assert_eq!(dims.mip_level_dimensions(9), None); } #[test] diff --git a/vulkano/src/image/sys.rs b/vulkano/src/image/sys.rs index b0ca97f58..88bf7c6c7 100644 --- a/vulkano/src/image/sys.rs +++ b/vulkano/src/image/sys.rs @@ -510,7 +510,7 @@ impl UnsafeImageBuilder { // Compute the number of mip levels let mip_levels = match mip_levels.into() { MipmapsCount::Specific(num) => num, - MipmapsCount::Log2 => dimensions.max_mipmaps(), + MipmapsCount::Log2 => dimensions.max_mip_levels(), MipmapsCount::One => 1, }; @@ -533,9 +533,10 @@ impl UnsafeImageBuilder { }; // Check mip levels - let max_mip_levels = dimensions.max_mipmaps(); + let max_mip_levels = dimensions.max_mip_levels(); debug_assert!(max_mip_levels >= 1); + // VUID-VkImageCreateInfo-mipLevels-00958 if mip_levels > max_mip_levels { return Err(ImageCreationError::MaxMipLevelsExceeded { mip_levels, @@ -543,7 +544,7 @@ impl UnsafeImageBuilder { }); } - // Check limits for multisampled images + // VUID-VkImageCreateInfo-samples-02257 if samples != SampleCount::Sample1 { if image_type != ImageType::Dim2d { return Err(ImageCreationError::MultisampleNot2d); @@ -564,18 +565,22 @@ impl UnsafeImageBuilder { // Check limits for YCbCr formats if let Some(chroma_sampling) = format.ycbcr_chroma_sampling() { + // VUID-VkImageCreateInfo-format-06410 if mip_levels != 1 { return Err(ImageCreationError::YcbcrFormatMultipleMipLevels); } + // VUID-VkImageCreateInfo-format-06411 if samples != SampleCount::Sample1 { return Err(ImageCreationError::YcbcrFormatMultisampling); } + // VUID-VkImageCreateInfo-format-06412 if image_type != ImageType::Dim2d { return Err(ImageCreationError::YcbcrFormatNot2d); } + // VUID-VkImageCreateInfo-format-06413 if array_layers > 1 && !device.enabled_features().ycbcr_image_arrays { return Err(ImageCreationError::FeatureNotEnabled { feature: "ycbcr_image_arrays", @@ -586,11 +591,14 @@ impl UnsafeImageBuilder { match chroma_sampling { ChromaSampling::Mode444 => (), ChromaSampling::Mode422 => { + // VUID-VkImageCreateInfo-format-04712 if !(extent[0] % 2 == 0) { return Err(ImageCreationError::YcbcrFormatInvalidDimensions); } } ChromaSampling::Mode420 => { + // VUID-VkImageCreateInfo-format-04712 + // VUID-VkImageCreateInfo-format-04713 if !(extent[0] % 2 == 0 && extent[1] % 2 == 0) { return Err(ImageCreationError::YcbcrFormatInvalidDimensions); } @@ -624,6 +632,8 @@ impl UnsafeImageBuilder { }); } + // VUID-VkImageCreateInfo-usage-00964 + // VUID-VkImageCreateInfo-usage-00965 if (usage.color_attachment || usage.depth_stencil_attachment || usage.input_attachment @@ -645,8 +655,7 @@ impl UnsafeImageBuilder { return Err(ImageCreationError::FormatUsageNotSupported { usage: "storage" }); } - // If the `shaderStorageImageMultisample` feature is not enabled and we have - // `usage_storage` set to true, then the number of samples must be 1. + // VUID-VkImageCreateInfo-usage-00968 if samples != SampleCount::Sample1 && !device.enabled_features().shader_storage_image_multisample { @@ -674,30 +683,31 @@ impl UnsafeImageBuilder { /* Check flags requirements */ if flags.cube_compatible { + // VUID-VkImageCreateInfo-flags-00949 if image_type != ImageType::Dim2d { return Err(ImageCreationError::CubeCompatibleNot2d); } + // VUID-VkImageCreateInfo-imageType-00954 if extent[0] != extent[1] { return Err(ImageCreationError::CubeCompatibleNotSquare); } + // VUID-VkImageCreateInfo-imageType-00954 if array_layers < 6 { return Err(ImageCreationError::CubeCompatibleNotEnoughArrayLayers); } - - if samples != SampleCount::Sample1 { - return Err(ImageCreationError::CubeCompatibleMultisampling); - } } if flags.array_2d_compatible { + // VUID-VkImageCreateInfo-flags-00950 if image_type != ImageType::Dim3d { return Err(ImageCreationError::Array2dCompatibleNot3d); } } if flags.block_texel_view_compatible { + // VUID-VkImageCreateInfo-flags-01572 if format.compression().is_none() { return Err(ImageCreationError::BlockTexelViewCompatibleNotCompressed); } @@ -716,6 +726,7 @@ impl UnsafeImageBuilder { debug_assert!(ids.len() >= 2); for &id in ids { + // VUID-VkImageCreateInfo-sharingMode-01420 if device.physical_device().queue_family_by_id(id).is_none() { return Err(ImageCreationError::SharingInvalidQueueFamilyId { id }); } @@ -737,6 +748,7 @@ impl UnsafeImageBuilder { }); } + // VUID-VkImageCreateInfo-pNext-01443 if initial_layout != ImageLayout::Undefined { return Err(ImageCreationError::ExternalMemoryInvalidInitialLayout); } @@ -940,6 +952,7 @@ impl UnsafeImageBuilder { usage, flags, handle_type, + None, )?; let ImageFormatProperties { @@ -948,11 +961,15 @@ impl UnsafeImageBuilder { max_array_layers, sample_counts, max_resource_size, + .. } = match image_format_properties { Some(x) => x, None => return Err(ImageCreationError::ImageFormatPropertiesNotSupported), }; + // VUID-VkImageCreateInfo-extent-02252 + // VUID-VkImageCreateInfo-extent-02253 + // VUID-VkImageCreateInfo-extent-02254 if extent[0] > max_extent[0] || extent[1] > max_extent[1] || extent[2] > max_extent[2] @@ -963,6 +980,7 @@ impl UnsafeImageBuilder { }); } + // VUID-VkImageCreateInfo-mipLevels-02255 if mip_levels > max_mip_levels { return Err(ImageCreationError::MaxMipLevelsExceeded { mip_levels, @@ -970,6 +988,7 @@ impl UnsafeImageBuilder { }); } + // VUID-VkImageCreateInfo-arrayLayers-02256 if array_layers > max_array_layers { return Err(ImageCreationError::MaxArrayLayersExceeded { array_layers, @@ -977,6 +996,7 @@ impl UnsafeImageBuilder { }); } + // VUID-VkImageCreateInfo-samples-02258 if !sample_counts.contains(samples) { return Err(ImageCreationError::SampleCountNotSupported { samples, @@ -1056,9 +1076,17 @@ impl UnsafeImageBuilder { #[inline] pub fn dimensions(mut self, dimensions: ImageDimensions) -> Self { let extent = dimensions.width_height_depth(); + + // VUID-VkImageCreateInfo-extent-00944 assert!(extent[0] != 0); + + // VUID-VkImageCreateInfo-extent-00945 assert!(extent[1] != 0); + + // VUID-VkImageCreateInfo-extent-00946 assert!(extent[2] != 0); + + // VUID-VkImageCreateInfo-arrayLayers-00948 assert!(dimensions.array_layers() != 0); self.dimensions = Some(dimensions); @@ -1088,7 +1116,9 @@ impl UnsafeImageBuilder { /// - Panics if `flags` contains `block_texel_view_compatible` but not `mutable_format`. #[inline] pub fn flags(mut self, flags: ImageCreateFlags) -> Self { + // VUID-VkImageCreateInfo-flags-01573 assert!(!(flags.block_texel_view_compatible && !flags.mutable_format)); + self.flags = flags; self } @@ -1113,10 +1143,12 @@ impl UnsafeImageBuilder { /// [`Preinitialized`](ImageLayout::Preinitialized). #[inline] pub fn initial_layout(mut self, layout: ImageLayout) -> Self { + // VUID-VkImageCreateInfo-initialLayout-00993 assert!(matches!( layout, ImageLayout::Undefined | ImageLayout::Preinitialized )); + self.initial_layout = layout; self } @@ -1134,7 +1166,10 @@ impl UnsafeImageBuilder { M: Into, { let mip_levels = mip_levels.into(); + + // VUID-VkImageCreateInfo-mipLevels-00947 assert!(!matches!(mip_levels, MipmapsCount::Specific(0))); + self.mip_levels = mip_levels; self } @@ -1164,9 +1199,12 @@ impl UnsafeImageBuilder { Sharing::Exclusive => Sharing::Exclusive, Sharing::Concurrent(ids) => { let mut ids: SmallVec<[u32; 4]> = ids.into_iter().collect(); + + // VUID-VkImageCreateInfo-sharingMode-00942 ids.sort_unstable(); ids.dedup(); assert!(ids.len() >= 2); + Sharing::Concurrent(ids) } }; @@ -1194,15 +1232,16 @@ impl UnsafeImageBuilder { /// other than these. #[inline] pub fn usage(mut self, usage: ImageUsage) -> Self { + // VUID-VkImageCreateInfo-usage-requiredbitmask assert!(usage != ImageUsage::none()); if usage.transient_attachment { - // At least one of these must also be set + // VUID-VkImageCreateInfo-usage-00966 assert!( usage.color_attachment || usage.depth_stencil_attachment || usage.input_attachment ); - // And no others must be set + // VUID-VkImageCreateInfo-usage-00963 assert!( ImageUsage { transient_attachment: false, diff --git a/vulkano/src/image/view.rs b/vulkano/src/image/view.rs index 3c0d0426a..ff15eb7c1 100644 --- a/vulkano/src/image/view.rs +++ b/vulkano/src/image/view.rs @@ -16,7 +16,9 @@ use crate::device::physical::FormatFeatures; use crate::device::{Device, DeviceOwned}; use crate::format::Format; -use crate::image::{ImageAccess, ImageDimensions, ImageTiling}; +use crate::image::{ + ImageAccess, ImageAspects, ImageDimensions, ImageTiling, ImageType, ImageUsage, SampleCount, +}; use crate::sampler::ComponentMapping; use crate::OomError; use crate::VulkanObject; @@ -38,10 +40,16 @@ where image: Arc, array_layers: Range, + aspects: ImageAspects, component_mapping: ComponentMapping, format: Format, format_features: FormatFeatures, + mip_levels: Range, ty: ImageViewType, + usage: ImageUsage, + + filter_cubic: bool, + filter_cubic_minmax: bool, } impl ImageView @@ -56,6 +64,26 @@ where /// Begins building an `ImageView`. pub fn start(image: Arc) -> ImageViewBuilder { + let array_layers = 0..image.dimensions().array_layers(); + let aspects = { + let aspects = image.format().aspects(); + if aspects.depth || aspects.stencil { + debug_assert!(!aspects.color); + ImageAspects { + depth: aspects.depth, + stencil: aspects.stencil, + ..Default::default() + } + } else { + debug_assert!(aspects.color); + ImageAspects { + color: true, + ..Default::default() + } + } + }; + let format = image.format(); + let mip_levels = 0..image.mip_levels(); let ty = match image.dimensions() { ImageDimensions::Dim1d { array_layers: 1, .. @@ -67,17 +95,16 @@ where ImageDimensions::Dim2d { .. } => ImageViewType::Dim2dArray, ImageDimensions::Dim3d { .. } => ImageViewType::Dim3d, }; - let mip_levels = 0..image.mip_levels(); - let array_layers = 0..image.dimensions().array_layers(); ImageViewBuilder { + image, + array_layers, + aspects, component_mapping: ComponentMapping::default(), - format: image.format(), + format, mip_levels, ty, - - image, } } @@ -158,13 +185,14 @@ where #[derive(Debug)] pub struct ImageViewBuilder { + image: Arc, + array_layers: Range, + aspects: ImageAspects, component_mapping: ComponentMapping, format: Format, mip_levels: Range, ty: ImageViewType, - - image: Arc, } impl ImageViewBuilder @@ -173,154 +201,301 @@ where { /// Builds the `ImageView`. pub fn build(self) -> Result>, ImageViewCreationError> { - let dimensions = self.image.dimensions(); - let image_inner = self.image.inner().image; - let image_flags = image_inner.flags(); - let image_format = image_inner.format(); - let image_usage = image_inner.usage(); + let Self { + array_layers, + aspects, + component_mapping, + format, + mip_levels, + ty, + image, + } = self; - // TODO: Let user choose - let aspects = image_format.aspects(); + let image_inner = image.inner().image; + let level_count = mip_levels.end - mip_levels.start; + let layer_count = array_layers.end - array_layers.start; - if self.mip_levels.end <= self.mip_levels.start - || self.mip_levels.end > image_inner.mip_levels() - { - return Err(ImageViewCreationError::MipLevelsOutOfRange); + // Get format features + let format_features = { + let format_features = if format != image_inner.format() { + let format_properties = image_inner + .device() + .physical_device() + .format_properties(self.format); + + match image_inner.tiling() { + ImageTiling::Optimal => format_properties.optimal_tiling_features, + ImageTiling::Linear => format_properties.linear_tiling_features, + } + } else { + *image_inner.format_features() + }; + + // Per https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap12.html#resources-image-view-format-features + if image_inner + .device() + .enabled_extensions() + .khr_format_feature_flags2 + { + format_features + } else { + let is_without_format = format.shader_storage_image_without_format(); + + FormatFeatures { + sampled_image_depth_comparison: format.type_color().is_none() + && format_features.sampled_image, + storage_read_without_format: is_without_format + && image_inner + .device() + .enabled_features() + .shader_storage_image_read_without_format, + storage_write_without_format: is_without_format + && image_inner + .device() + .enabled_features() + .shader_storage_image_write_without_format, + ..format_features + } + } + }; + + // No VUID apparently, but this seems like something we want to check? + if !image_inner.format().aspects().contains(&aspects) { + return Err(ImageViewCreationError::ImageAspectsNotCompatible { + aspects, + image_aspects: image_inner.format().aspects(), + }); } - if self.array_layers.end <= self.array_layers.start - || self.array_layers.end > dimensions.array_layers() - { - return Err(ImageViewCreationError::ArrayLayersOutOfRange); + // VUID-VkImageViewCreateInfo-None-02273 + if format_features == FormatFeatures::default() { + return Err(ImageViewCreationError::FormatNotSupported); } - if !(image_usage.sampled - || image_usage.storage - || image_usage.color_attachment - || image_usage.depth_stencil_attachment - || image_usage.input_attachment - || image_usage.transient_attachment) + // Get usage + // Can be different from image usage, see + // https://khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkImageViewCreateInfo.html#_description + let usage = *image_inner.usage(); + + // Check for compatibility with the image + let image_type = image.dimensions().image_type(); + + // VUID-VkImageViewCreateInfo-subResourceRange-01021 + if !ty.is_compatible_with(image_type) { + return Err(ImageViewCreationError::ImageTypeNotCompatible); + } + + // VUID-VkImageViewCreateInfo-image-01003 + if (ty == ImageViewType::Cube || ty == ImageViewType::CubeArray) + && !image_inner.flags().cube_compatible + { + return Err(ImageViewCreationError::ImageNotCubeCompatible); + } + + // VUID-VkImageViewCreateInfo-viewType-01004 + if ty == ImageViewType::CubeArray + && !image_inner.device().enabled_features().image_cube_array + { + return Err(ImageViewCreationError::FeatureNotEnabled { + feature: "image_cube_array", + reason: "the `CubeArray` view type was requested", + }); + } + + // VUID-VkImageViewCreateInfo-subresourceRange-01718 + if mip_levels.end > image_inner.mip_levels() { + return Err(ImageViewCreationError::MipLevelsOutOfRange { + range_end: mip_levels.end, + max: image_inner.mip_levels(), + }); + } + + if image_type == ImageType::Dim3d + && (ty == ImageViewType::Dim2d || ty == ImageViewType::Dim2dArray) + { + // VUID-VkImageViewCreateInfo-image-01005 + if !image_inner.flags().array_2d_compatible { + return Err(ImageViewCreationError::ImageNotArray2dCompatible); + } + + // VUID-VkImageViewCreateInfo-image-04970 + if level_count != 1 { + return Err(ImageViewCreationError::Array2dCompatibleMultipleMipLevels); + } + + // VUID-VkImageViewCreateInfo-image-02724 + // VUID-VkImageViewCreateInfo-subresourceRange-02725 + // We're using the depth dimension as array layers, but because of mip scaling, the + // depth, and therefore number of layers available, shrinks as the mip level gets + // higher. + let max = image_inner + .dimensions() + .mip_level_dimensions(mip_levels.start) + .unwrap() + .depth(); + if array_layers.end > max { + return Err(ImageViewCreationError::ArrayLayersOutOfRange { + range_end: array_layers.end, + max, + }); + } + } else { + // VUID-VkImageViewCreateInfo-image-01482 + // VUID-VkImageViewCreateInfo-subresourceRange-01483 + if array_layers.end > image_inner.dimensions().array_layers() { + return Err(ImageViewCreationError::ArrayLayersOutOfRange { + range_end: array_layers.end, + max: image_inner.dimensions().array_layers(), + }); + } + } + + // VUID-VkImageViewCreateInfo-image-04972 + if image_inner.samples() != SampleCount::Sample1 + && !(ty == ImageViewType::Dim2d || ty == ImageViewType::Dim2dArray) + { + return Err(ImageViewCreationError::MultisamplingNot2d); + } + + /* Check usage requirements */ + + // VUID-VkImageViewCreateInfo-image-04441 + if !(image_inner.usage().sampled + || image_inner.usage().storage + || image_inner.usage().color_attachment + || image_inner.usage().depth_stencil_attachment + || image_inner.usage().input_attachment + || image_inner.usage().transient_attachment) { return Err(ImageViewCreationError::InvalidImageUsage); } - // Check for compatibility with the image - match ( - self.ty, - self.image.dimensions(), - self.array_layers.end - self.array_layers.start, - self.mip_levels.end - self.mip_levels.start, - ) { - (ImageViewType::Dim1d, ImageDimensions::Dim1d { .. }, 1, _) => (), - (ImageViewType::Dim1dArray, ImageDimensions::Dim1d { .. }, _, _) => (), - (ImageViewType::Dim2d, ImageDimensions::Dim2d { .. }, 1, _) => (), - (ImageViewType::Dim2dArray, ImageDimensions::Dim2d { .. }, _, _) => (), - (ImageViewType::Cube, ImageDimensions::Dim2d { .. }, 6, _) - if image_flags.cube_compatible => - { - () - } - (ImageViewType::CubeArray, ImageDimensions::Dim2d { .. }, n, _) - if image_flags.cube_compatible && n % 6 == 0 => - { - () - } - (ImageViewType::Dim3d, ImageDimensions::Dim3d { .. }, 1, _) => (), - (ImageViewType::Dim2d, ImageDimensions::Dim3d { .. }, 1, 1) - if image_flags.array_2d_compatible => - { - () - } - (ImageViewType::Dim2dArray, ImageDimensions::Dim3d { .. }, _, 1) - if image_flags.array_2d_compatible => - { - () - } - _ => return Err(ImageViewCreationError::IncompatibleType), + // VUID-VkImageViewCreateInfo-usage-02274 + if usage.sampled && !format_features.sampled_image { + return Err(ImageViewCreationError::FormatUsageNotSupported { usage: "sampled" }); } - if image_format.ycbcr_chroma_sampling().is_some() { + // VUID-VkImageViewCreateInfo-usage-02275 + if usage.storage && !format_features.storage_image { + return Err(ImageViewCreationError::FormatUsageNotSupported { usage: "storage" }); + } + + // VUID-VkImageViewCreateInfo-usage-02276 + if usage.color_attachment && !format_features.color_attachment { + return Err(ImageViewCreationError::FormatUsageNotSupported { + usage: "color_attachment", + }); + } + + // VUID-VkImageViewCreateInfo-usage-02277 + if usage.depth_stencil_attachment && !format_features.depth_stencil_attachment { + return Err(ImageViewCreationError::FormatUsageNotSupported { + usage: "depth_stencil_attachment", + }); + } + + // VUID-VkImageViewCreateInfo-usage-02652 + if usage.input_attachment + && !(format_features.color_attachment || format_features.depth_stencil_attachment) + { + return Err(ImageViewCreationError::FormatUsageNotSupported { + usage: "input_attachment", + }); + } + + /* Check flags requirements */ + + if image_inner.flags().block_texel_view_compatible { + // VUID-VkImageViewCreateInfo-image-01583 + if !(format.compatibility() == image_inner.format().compatibility() + || format.block_size() == image_inner.format().block_size()) + { + return Err(ImageViewCreationError::FormatNotCompatible); + } + + // VUID-VkImageViewCreateInfo-image-01584 + if layer_count != 1 { + return Err(ImageViewCreationError::BlockTexelViewCompatibleMultipleArrayLayers); + } + + // VUID-VkImageViewCreateInfo-image-01584 + if level_count != 1 { + return Err(ImageViewCreationError::BlockTexelViewCompatibleMultipleMipLevels); + } + + // VUID-VkImageViewCreateInfo-image-04739 + if format.compression().is_none() && ty == ImageViewType::Dim3d { + return Err(ImageViewCreationError::BlockTexelViewCompatibleUncompressedIs3d); + } + } + // VUID-VkImageViewCreateInfo-image-01761 + else if image_inner.flags().mutable_format + && image_inner.format().planes().is_empty() + && format.compatibility() != image_inner.format().compatibility() + { + return Err(ImageViewCreationError::FormatNotCompatible); + } + + if image_inner.flags().mutable_format + && !image_inner.format().planes().is_empty() + && !aspects.color + { + let plane = if aspects.plane0 { + 0 + } else if aspects.plane1 { + 1 + } else if aspects.plane2 { + 2 + } else { + unreachable!() + }; + let plane_format = image_inner.format().planes()[plane]; + + // VUID-VkImageViewCreateInfo-image-01586 + if format.compatibility() != plane_format.compatibility() { + return Err(ImageViewCreationError::FormatNotCompatible); + } + } + // VUID-VkImageViewCreateInfo-image-01762 + else if format != image_inner.format() { + return Err(ImageViewCreationError::FormatNotCompatible); + } + + // VUID-VkImageViewCreateInfo-format-06415 + if image_inner.format().ycbcr_chroma_sampling().is_some() { + // VUID-VkImageViewCreateInfo-format-04714 + // VUID-VkImageViewCreateInfo-format-04715 + // VUID-VkImageViewCreateInfo-pNext-01970 unimplemented!() } - if image_flags.block_texel_view_compatible { - if self.format.compatibility() != image_format.compatibility() - || self.format.block_size() != image_format.block_size() - { - return Err(ImageViewCreationError::IncompatibleFormat); - } - - if self.array_layers.end - self.array_layers.start != 1 { - return Err(ImageViewCreationError::ArrayLayersOutOfRange); - } - - if self.mip_levels.end - self.mip_levels.start != 1 { - return Err(ImageViewCreationError::MipLevelsOutOfRange); - } - - if self.format.compression().is_none() && self.ty == ImageViewType::Dim3d { - return Err(ImageViewCreationError::IncompatibleType); - } - } else if image_flags.mutable_format { - if image_format.planes().is_empty() { - if self.format != image_format { - return Err(ImageViewCreationError::IncompatibleFormat); - } - } else { - // TODO: VUID-VkImageViewCreateInfo-image-01586 - // If image was created with the VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT flag, if the - // format of the image is a multi-planar format, and if subresourceRange.aspectMask - // is one of VK_IMAGE_ASPECT_PLANE_0_BIT, VK_IMAGE_ASPECT_PLANE_1_BIT, or - // VK_IMAGE_ASPECT_PLANE_2_BIT, then format must be compatible with the VkFormat for - // the plane of the image format indicated by subresourceRange.aspectMask, as - // defined in Compatible formats of planes of multi-planar formats - - // TODO: VUID-VkImageViewCreateInfo-image-01762 - // If image was not created with the VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT flag, or if - // the format of the image is a multi-planar format and if - // subresourceRange.aspectMask is VK_IMAGE_ASPECT_COLOR_BIT, format must be - // identical to the format used to create image - } - } else if self.format != image_format { - return Err(ImageViewCreationError::IncompatibleFormat); + // VUID-VkImageViewCreateInfo-imageViewType-04973 + if (ty == ImageViewType::Dim1d || ty == ImageViewType::Dim2d || ty == ImageViewType::Dim3d) + && layer_count != 1 + { + return Err(ImageViewCreationError::TypeNonArrayedMultipleArrayLayers); + } + // VUID-VkImageViewCreateInfo-viewType-02960 + else if ty == ImageViewType::Cube && layer_count != 6 { + return Err(ImageViewCreationError::TypeCubeNot6ArrayLayers); + } + // VUID-VkImageViewCreateInfo-viewType-02961 + else if ty == ImageViewType::CubeArray && layer_count % 6 != 0 { + return Err(ImageViewCreationError::TypeCubeArrayNotMultipleOf6ArrayLayers); } - - let format_features = if self.format != image_format { - if !(image_flags.mutable_format && image_format.planes().is_empty()) { - return Err(ImageViewCreationError::IncompatibleFormat); - } else if self.format.compatibility() != image_format.compatibility() { - if !image_flags.block_texel_view_compatible { - return Err(ImageViewCreationError::IncompatibleFormat); - } else if self.format.block_size() != image_format.block_size() { - return Err(ImageViewCreationError::IncompatibleFormat); - } - } - - let format_properties = image_inner - .device() - .physical_device() - .format_properties(self.format); - - match image_inner.tiling() { - ImageTiling::Optimal => format_properties.optimal_tiling_features, - ImageTiling::Linear => format_properties.linear_tiling_features, - } - } else { - *image_inner.format_features() - }; let create_info = ash::vk::ImageViewCreateInfo { flags: ash::vk::ImageViewCreateFlags::empty(), image: image_inner.internal_object(), - view_type: self.ty.into(), - format: image_format.into(), - components: self.component_mapping.into(), + view_type: ty.into(), + format: format.into(), + components: component_mapping.into(), subresource_range: ash::vk::ImageSubresourceRange { aspect_mask: aspects.into(), - base_mip_level: self.mip_levels.start, - level_count: self.mip_levels.end - self.mip_levels.start, - base_array_layer: self.array_layers.start, - layer_count: self.array_layers.end - self.array_layers.start, + base_mip_level: mip_levels.start, + level_count, + base_array_layer: array_layers.start, + layer_count, }, ..Default::default() }; @@ -337,32 +512,106 @@ where output.assume_init() }; + let (filter_cubic, filter_cubic_minmax) = if let Some(properties) = image_inner + .device() + .physical_device() + .image_format_properties( + image_inner.format(), + image_type, + image_inner.tiling(), + *image_inner.usage(), + image_inner.flags(), + None, + Some(ty), + )? { + (properties.filter_cubic, properties.filter_cubic_minmax) + } else { + (false, false) + }; + Ok(Arc::new(ImageView { handle, - image: self.image, + image, - array_layers: self.array_layers, - component_mapping: self.component_mapping, - format: self.format, + array_layers, + aspects, + component_mapping, + format, format_features, - ty: self.ty, + mip_levels, + ty, + usage, + + filter_cubic, + filter_cubic_minmax, })) } - /// Sets the image view type. + /// The range of array layers of the image that the view should cover. /// - /// The view type must be compatible with the dimensions of the image and the selected array - /// layers. + /// The default value is the full range of array layers present in the image. /// - /// The default value is determined from the image, based on its dimensions and number of - /// layers. + /// # Panics + /// + /// - Panics if `array_layers` is empty. #[inline] - pub fn ty(mut self, ty: ImageViewType) -> Self { - self.ty = ty; + pub fn array_layers(mut self, array_layers: Range) -> Self { + assert!(!array_layers.is_empty()); + self.array_layers = array_layers; self } - /// Sets the format of the image view. + /// The aspects of the image that the view should cover. + /// + /// The default value is `color` if the image is a color format, `depth` and/or `stencil` if + /// the image is a depth/stencil format. + /// + /// # Panics + /// + /// - Panics if aspects other than `color`, `depth`, `stencil`, `plane0`, `plane1` or `plane2` + /// are selected. + /// - Panics if more than one aspect is selected, unless `depth` and `stencil` are the only + /// aspects selected. + #[inline] + pub fn aspects(mut self, aspects: ImageAspects) -> Self { + let ImageAspects { + color, + depth, + stencil, + metadata, + plane0, + plane1, + plane2, + memory_plane0, + memory_plane1, + memory_plane2, + } = aspects; + + assert!(!(metadata || memory_plane0 || memory_plane1 || memory_plane2)); + assert!({ + let num_bits = color as u8 + + depth as u8 + + stencil as u8 + + plane0 as u8 + + plane1 as u8 + + plane2 as u8; + num_bits == 1 || depth && stencil && !(color || plane0 || plane1 || plane2) + }); + + self.aspects = aspects; + self + } + + /// How to map components of each pixel. + /// + /// The default value is [`ComponentMapping::identity()`]. + #[inline] + pub fn component_mapping(mut self, component_mapping: ComponentMapping) -> Self { + self.component_mapping = component_mapping; + self + } + + /// The format of the image view. /// /// If this is set to a format that is different from the image, the image must be created with /// the `mutable_format` flag. @@ -374,30 +623,30 @@ where self } - /// Sets how to map components of each pixel. - /// - /// The default value is [`ComponentMapping::identity()`]. - #[inline] - pub fn component_mapping(mut self, component_mapping: ComponentMapping) -> Self { - self.component_mapping = component_mapping; - self - } - - /// Sets the range of mipmap levels that the view should cover. + /// The range of mipmap levels of the image that the view should cover. /// /// The default value is the full range of mipmaps present in the image. + /// + /// # Panics + /// + /// - Panics if `mip_levels` is empty. #[inline] pub fn mip_levels(mut self, mip_levels: Range) -> Self { + assert!(!mip_levels.is_empty()); self.mip_levels = mip_levels; self } - /// Sets the range of array layers that the view should cover. + /// The image view type. /// - /// The default value is the full range of array layers present in the image. + /// The view type must be compatible with the dimensions of the image and the selected array + /// layers. + /// + /// The default value is determined from the image, based on its dimensions and number of + /// layers. #[inline] - pub fn array_layers(mut self, array_layers: Range) -> Self { - self.array_layers = array_layers; + pub fn ty(mut self, ty: ImageViewType) -> Self { + self.ty = ty; self } } @@ -408,19 +657,61 @@ pub enum ImageViewCreationError { /// Allocating memory failed. OomError(OomError), - /// The specified range of array layers was out of range for the image. - ArrayLayersOutOfRange, + FeatureNotEnabled { + feature: &'static str, + reason: &'static str, + }, + + /// A 2D image view was requested from a 3D image, but a range of multiple mip levels was + /// specified. + Array2dCompatibleMultipleMipLevels, + + /// The specified range of array layers was not a subset of those in the image. + ArrayLayersOutOfRange { range_end: u32, max: u32 }, + + /// The image has the `block_texel_view_compatible` flag, but a range of multiple array layers + /// was specified. + BlockTexelViewCompatibleMultipleArrayLayers, + + /// The image has the `block_texel_view_compatible` flag, but a range of multiple mip levels + /// was specified. + BlockTexelViewCompatibleMultipleMipLevels, + + /// The image has the `block_texel_view_compatible` flag, and an uncompressed format was + /// requested, and the image view type was `Dim3d`. + BlockTexelViewCompatibleUncompressedIs3d, + + /// The requested format was not compatible with the image. + FormatNotCompatible, + + /// The given format was not supported by the device. + FormatNotSupported, /// The format requires a sampler YCbCr conversion, but none was provided. FormatRequiresSamplerYcbcrConversion { format: Format }, - /// The specified range of mipmap levels was out of range for the image. - MipLevelsOutOfRange, + /// A requested usage flag was not supported by the given format. + FormatUsageNotSupported { usage: &'static str }, - /// The requested format was not compatible with the image. - IncompatibleFormat, + /// An aspect was selected that was not present in the image. + ImageAspectsNotCompatible { + aspects: ImageAspects, + image_aspects: ImageAspects, + }, - /// The requested [`ImageViewType`] was not compatible with the image, or with the specified ranges of array layers and mipmap levels. + /// A 2D image view was requested from a 3D image, but the image was not created with the + /// `array_2d_compatible` flag. + ImageNotArray2dCompatible, + + /// A cube image view type was requested, but the image was not created with the + /// `cube_compatible` flag. + ImageNotCubeCompatible, + + /// The given image view type was not compatible with the type of the image. + ImageTypeNotCompatible, + + /// The requested [`ImageViewType`] was not compatible with the image, or with the specified + /// ranges of array layers and mipmap levels. IncompatibleType, /// The image was not created with @@ -428,8 +719,27 @@ pub enum ImageViewCreationError { /// for image views. InvalidImageUsage, + /// The specified range of mip levels was not a subset of those in the image. + MipLevelsOutOfRange { range_end: u32, max: u32 }, + + /// The image has multisampling enabled, but the image view type was not `Dim2d` or + /// `Dim2dArray`. + MultisamplingNot2d, + /// Sampler YCbCr conversion was enabled, but `component_mapping` was not the identity mapping. SamplerYcbcrConversionComponentMappingNotIdentity { component_mapping: ComponentMapping }, + + /// The `CubeArray` image view type was specified, but the range of array layers did not have a + /// size that is a multiple 6. + TypeCubeArrayNotMultipleOf6ArrayLayers, + + /// The `Cube` image view type was specified, but the range of array layers did not have a size + /// of 6. + TypeCubeNot6ArrayLayers, + + /// A non-arrayed image view type was specified, but a range of multiple array layers was + /// specified. + TypeNonArrayedMultipleArrayLayers, } impl error::Error for ImageViewCreationError { @@ -445,22 +755,99 @@ impl error::Error for ImageViewCreationError { impl fmt::Display for ImageViewCreationError { #[inline] fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!( - fmt, - "{}", - match *self { - Self::OomError(err) => "allocating memory failed", - Self::ArrayLayersOutOfRange => "array layers are out of range", - Self::FormatRequiresSamplerYcbcrConversion { .. } => "the format requires a sampler YCbCr conversion, but none was provided", - Self::MipLevelsOutOfRange => "mipmap levels are out of range", - Self::IncompatibleFormat => "format is not compatible with image", - Self::IncompatibleType => - "image view type is not compatible with image, array layers or mipmap levels", - Self::InvalidImageUsage => - "the usage of the image is not compatible with image views", - Self::SamplerYcbcrConversionComponentMappingNotIdentity { .. } => "sampler YCbCr conversion was enabled, but `component_mapping` was not the identity mapping", + match *self { + Self::OomError(err) => write!( + fmt, + "allocating memory failed", + ), + Self::FeatureNotEnabled { feature, reason } => { + write!(fmt, "the feature {} must be enabled: {}", feature, reason) } - ) + Self::Array2dCompatibleMultipleMipLevels => write!( + fmt, + "a 2D image view was requested from a 3D image, but a range of multiple mip levels was specified", + ), + Self::ArrayLayersOutOfRange { .. } => write!( + fmt, + "the specified range of array layers was not a subset of those in the image", + ), + Self::BlockTexelViewCompatibleMultipleArrayLayers => write!( + fmt, + "the image has the `block_texel_view_compatible` flag, but a range of multiple array layers was specified", + ), + Self::BlockTexelViewCompatibleMultipleMipLevels => write!( + fmt, + "the image has the `block_texel_view_compatible` flag, but a range of multiple mip levels was specified", + ), + Self::BlockTexelViewCompatibleUncompressedIs3d => write!( + fmt, + "the image has the `block_texel_view_compatible` flag, and an uncompressed format was requested, and the image view type was `Dim3d`", + ), + Self::FormatNotCompatible => write!( + fmt, + "the requested format was not compatible with the image", + ), + Self::FormatNotSupported => write!( + fmt, + "the given format was not supported by the device" + ), + Self::FormatRequiresSamplerYcbcrConversion { .. } => write!( + fmt, + "the format requires a sampler YCbCr conversion, but none was provided", + ), + Self::FormatUsageNotSupported { usage } => write!( + fmt, + "a requested usage flag was not supported by the given format" + ), + Self::ImageAspectsNotCompatible { .. } => write!( + fmt, + "an aspect was selected that was not present in the image", + ), + Self::ImageNotArray2dCompatible => write!( + fmt, + "a 2D image view was requested from a 3D image, but the image was not created with the `array_2d_compatible` flag", + ), + Self::ImageNotCubeCompatible => write!( + fmt, + "a cube image view type was requested, but the image was not created with the `cube_compatible` flag", + ), + Self::ImageTypeNotCompatible => write!( + fmt, + "the given image view type was not compatible with the type of the image", + ), + Self::IncompatibleType => write!( + fmt, + "image view type is not compatible with image, array layers or mipmap levels", + ), + Self::InvalidImageUsage => write!( + fmt, + "the usage of the image is not compatible with image views", + ), + Self::MipLevelsOutOfRange { .. } => write!( + fmt, + "the specified range of mip levels was not a subset of those in the image", + ), + Self::MultisamplingNot2d => write!( + fmt, + "the image has multisampling enabled, but the image view type was not `Dim2d` or `Dim2dArray`", + ), + Self::SamplerYcbcrConversionComponentMappingNotIdentity { .. } => write!( + fmt, + "sampler YCbCr conversion was enabled, but `component_mapping` was not the identity mapping", + ), + Self::TypeCubeArrayNotMultipleOf6ArrayLayers => write!( + fmt, + "the `CubeArray` image view type was specified, but the range of array layers did not have a size that is a multiple 6" + ), + Self::TypeCubeNot6ArrayLayers => write!( + fmt, + "the `Cube` image view type was specified, but the range of array layers did not have a size of 6" + ), + Self::TypeNonArrayedMultipleArrayLayers => write!( + fmt, + "a non-arrayed image view type was specified, but a range of multiple array layers was specified" + ) + } } } @@ -496,6 +883,7 @@ pub enum ImageViewType { } impl ImageViewType { + /// Returns whether the type is arrayed. #[inline] pub fn is_arrayed(&self) -> bool { match self { @@ -503,6 +891,24 @@ impl ImageViewType { Self::Dim1dArray | Self::Dim2dArray | Self::CubeArray => true, } } + + /// Returns whether `self` is compatible with the given `image_type`. + #[inline] + pub fn is_compatible_with(&self, image_type: ImageType) -> bool { + matches!( + (*self, image_type,), + ( + ImageViewType::Dim1d | ImageViewType::Dim1dArray, + ImageType::Dim1d + ) | ( + ImageViewType::Dim2d | ImageViewType::Dim2dArray, + ImageType::Dim2d | ImageType::Dim3d + ) | ( + ImageViewType::Cube | ImageViewType::CubeArray, + ImageType::Dim2d + ) | (ImageViewType::Dim3d, ImageType::Dim3d) + ) + } } impl From for ash::vk::ImageViewType { @@ -521,17 +927,36 @@ pub unsafe trait ImageViewAbstract: /// Returns the range of array layers of the wrapped image that this view exposes. fn array_layers(&self) -> Range; + /// Returns the aspects of the wrapped image that this view exposes. + fn aspects(&self) -> &ImageAspects; + /// Returns the component mapping of this view. fn component_mapping(&self) -> ComponentMapping; + /// Returns whether the image view supports sampling with a + /// [`Cubic`](crate::sampler::Filter::Cubic) `mag_filter` or `min_filter`. + fn filter_cubic(&self) -> bool; + + /// Returns whether the image view supports sampling with a + /// [`Cubic`](crate::sampler::Filter::Cubic) `mag_filter` or `min_filter`, and with a + /// [`Min`](crate::sampler::SamplerReductionMode::Min) or + /// [`Max`](crate::sampler::SamplerReductionMode::Max) `reduction_mode`. + fn filter_cubic_minmax(&self) -> bool; + /// Returns the format of this view. This can be different from the parent's format. fn format(&self) -> Format; /// Returns the features supported by the image view's format. fn format_features(&self) -> &FormatFeatures; + /// Returns the range of mip levels of the wrapped image that this view exposes. + fn mip_levels(&self) -> Range; + /// Returns the [`ImageViewType`] of this image view. fn ty(&self) -> ImageViewType; + + /// Returns the usage of the image view. + fn usage(&self) -> &ImageUsage; } unsafe impl ImageViewAbstract for ImageView @@ -548,11 +973,26 @@ where self.array_layers.clone() } + #[inline] + fn aspects(&self) -> &ImageAspects { + &self.aspects + } + #[inline] fn component_mapping(&self) -> ComponentMapping { self.component_mapping } + #[inline] + fn filter_cubic(&self) -> bool { + self.filter_cubic + } + + #[inline] + fn filter_cubic_minmax(&self) -> bool { + self.filter_cubic_minmax + } + #[inline] fn format(&self) -> Format { self.format @@ -563,10 +1003,20 @@ where &self.format_features } + #[inline] + fn mip_levels(&self) -> Range { + self.mip_levels.clone() + } + #[inline] fn ty(&self) -> ImageViewType { self.ty } + + #[inline] + fn usage(&self) -> &ImageUsage { + &self.usage + } } impl PartialEq for dyn ImageViewAbstract { diff --git a/vulkano/src/pipeline/mod.rs b/vulkano/src/pipeline/mod.rs index 24cb95ff8..437b73cd3 100644 --- a/vulkano/src/pipeline/mod.rs +++ b/vulkano/src/pipeline/mod.rs @@ -20,6 +20,7 @@ pub use self::compute::ComputePipeline; pub use self::graphics::GraphicsPipeline; pub use self::layout::PipelineLayout; +use crate::device::DeviceOwned; use std::sync::Arc; pub mod cache; @@ -28,7 +29,7 @@ pub mod graphics; pub mod layout; /// A trait for operations shared between pipeline types. -pub trait Pipeline { +pub trait Pipeline: DeviceOwned { /// Returns the bind point of this pipeline. fn bind_point(&self) -> PipelineBindPoint; diff --git a/vulkano/src/render_pass/compat_atch.rs b/vulkano/src/render_pass/compat_atch.rs index 082a9dc07..7c68e6be4 100644 --- a/vulkano/src/render_pass/compat_atch.rs +++ b/vulkano/src/render_pass/compat_atch.rs @@ -64,7 +64,7 @@ where .any(|&(n, _)| n == attachment_num) { debug_assert!(aspects.color); // Was normally checked by the render pass. - if !image_view.image().inner().image.usage().color_attachment { + if !image_view.usage().color_attachment { return Err(IncompatibleRenderPassAttachmentError::MissingColorAttachmentUsage); } } @@ -73,13 +73,7 @@ where if ds == attachment_num { // Was normally checked by the render pass. debug_assert!(aspects.depth || aspects.stencil); - if !image_view - .image() - .inner() - .image - .usage() - .depth_stencil_attachment - { + if !image_view.usage().depth_stencil_attachment { return Err( IncompatibleRenderPassAttachmentError::MissingDepthStencilAttachmentUsage, ); @@ -92,7 +86,7 @@ where .iter() .any(|&(n, _)| n == attachment_num) { - if !image_view.image().inner().image.usage().input_attachment { + if !image_view.usage().input_attachment { return Err(IncompatibleRenderPassAttachmentError::MissingInputAttachmentUsage); } } diff --git a/vulkano/src/sampler.rs b/vulkano/src/sampler.rs index 2fa4b49af..9dd1f774b 100644 --- a/vulkano/src/sampler.rs +++ b/vulkano/src/sampler.rs @@ -47,8 +47,10 @@ use crate::check_errors; use crate::device::Device; use crate::device::DeviceOwned; +use crate::image::view::ImageViewType; use crate::image::ImageViewAbstract; use crate::pipeline::graphics::depth_stencil::CompareOp; +use crate::shader::ShaderScalarType; use crate::Error; use crate::OomError; use crate::VulkanObject; @@ -89,11 +91,14 @@ use std::sync::Arc; pub struct Sampler { handle: ash::vk::Sampler, device: Arc, - compare_mode: bool, - unnormalized: bool, - usable_with_float_formats: bool, - usable_with_int_formats: bool, - usable_with_swizzling: bool, + + border_color: Option, + compare: Option, + mag_filter: Filter, + min_filter: Filter, + mipmap_mode: SamplerMipmapMode, + reduction_mode: SamplerReductionMode, + unnormalized_coordinates: bool, } impl Sampler { @@ -147,44 +152,195 @@ impl Sampler { .build() } - /// Returns whether this sampler is allowed to sample `image_view`. - pub fn can_sample(&self, image_view: &I) -> bool + /// Checks whether this sampler is compatible with `image_view`. + pub fn check_can_sample( + &self, + image_view: &I, + ) -> Result<(), SamplerImageViewIncompatibleError> where I: ImageViewAbstract + ?Sized, { - // TODO: many more things need to be tested here + /* + Note: Most of these checks come from the Instruction/Sampler/Image View Validation + section, and are not strictly VUIDs. + https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap16.html#textures-input-validation + */ - true + if self.compare.is_some() { + // VUID-vkCmdDispatch-None-06479 + if !image_view.format_features().sampled_image_depth_comparison { + return Err(SamplerImageViewIncompatibleError::DepthComparisonNotSupported); + } + + // The SPIR-V instruction is one of the OpImage*Dref* instructions, the image + // view format is one of the depth/stencil formats, and the image view aspect + // is not VK_IMAGE_ASPECT_DEPTH_BIT. + if !image_view.aspects().depth { + return Err(SamplerImageViewIncompatibleError::DepthComparisonWrongAspect); + } + } else { + if !image_view.format_features().sampled_image_filter_linear { + // VUID-vkCmdDispatch-magFilter-04553 + if self.mag_filter == Filter::Linear || self.min_filter == Filter::Linear { + return Err(SamplerImageViewIncompatibleError::FilterLinearNotSupported); + } + + // VUID-vkCmdDispatch-mipmapMode-04770 + if self.mipmap_mode == SamplerMipmapMode::Linear { + return Err(SamplerImageViewIncompatibleError::MipmapModeLinearNotSupported); + } + } + } + + if self.mag_filter == Filter::Cubic || self.min_filter == Filter::Cubic { + // VUID-vkCmdDispatch-None-02692 + if !image_view.format_features().sampled_image_filter_cubic { + return Err(SamplerImageViewIncompatibleError::FilterCubicNotSupported); + } + + // VUID-vkCmdDispatch-filterCubic-02694 + if !image_view.filter_cubic() { + return Err(SamplerImageViewIncompatibleError::FilterCubicNotSupported); + } + + // VUID-vkCmdDispatch-filterCubicMinmax-02695 + if matches!( + self.reduction_mode, + SamplerReductionMode::Min | SamplerReductionMode::Max + ) && !image_view.filter_cubic_minmax() + { + return Err(SamplerImageViewIncompatibleError::FilterCubicMinmaxNotSupported); + } + } + + if let Some(border_color) = self.border_color { + let aspects = image_view.aspects(); + let view_scalar_type = ShaderScalarType::from( + if aspects.color || aspects.plane0 || aspects.plane1 || aspects.plane2 { + image_view.format().type_color().unwrap() + } else if aspects.depth { + image_view.format().type_depth().unwrap() + } else if aspects.stencil { + image_view.format().type_stencil().unwrap() + } else { + // Per `ImageViewBuilder::aspects` and + // VUID-VkDescriptorImageInfo-imageView-01976 + unreachable!() + }, + ); + + match border_color { + BorderColor::IntTransparentBlack + | BorderColor::IntOpaqueBlack + | BorderColor::IntOpaqueWhite => { + // The sampler borderColor is an integer type and the image view + // format is not one of the VkFormat integer types or a stencil + // component of a depth/stencil format. + if !matches!( + view_scalar_type, + ShaderScalarType::Sint | ShaderScalarType::Uint + ) { + return Err( + SamplerImageViewIncompatibleError::BorderColorFormatNotCompatible, + ); + } + } + BorderColor::FloatTransparentBlack + | BorderColor::FloatOpaqueBlack + | BorderColor::FloatOpaqueWhite => { + // The sampler borderColor is a float type and the image view + // format is not one of the VkFormat float types or a depth + // component of a depth/stencil format. + if !matches!(view_scalar_type, ShaderScalarType::Float) { + return Err( + SamplerImageViewIncompatibleError::BorderColorFormatNotCompatible, + ); + } + } + } + + // The sampler borderColor is one of the opaque black colors + // (VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK or VK_BORDER_COLOR_INT_OPAQUE_BLACK) + // and the image view VkComponentSwizzle for any of the VkComponentMapping + // components is not the identity swizzle, and + // VkPhysicalDeviceBorderColorSwizzleFeaturesEXT::borderColorSwizzleFromImage + // feature is not enabled, and + // VkSamplerBorderColorComponentMappingCreateInfoEXT is not specified. + if matches!( + border_color, + BorderColor::FloatOpaqueBlack | BorderColor::IntOpaqueBlack + ) && !image_view.component_mapping().is_identity() + { + return Err( + SamplerImageViewIncompatibleError::BorderColorOpaqueBlackNotIdentitySwizzled, + ); + } + } + + // The sampler unnormalizedCoordinates is VK_TRUE and any of the limitations of + // unnormalized coordinates are violated. + // https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap13.html#samplers-unnormalizedCoordinates + if self.unnormalized_coordinates { + // The viewType must be either VK_IMAGE_VIEW_TYPE_1D or + // VK_IMAGE_VIEW_TYPE_2D. + // VUID-vkCmdDispatch-None-02702 + if !matches!(image_view.ty(), ImageViewType::Dim1d | ImageViewType::Dim2d) { + return Err( + SamplerImageViewIncompatibleError::UnnormalizedCoordinatesViewTypeNotCompatible, + ); + } + + // The image view must have a single layer and a single mip level. + if image_view.mip_levels().end - image_view.mip_levels().start != 1 { + return Err( + SamplerImageViewIncompatibleError::UnnormalizedCoordinatesMultipleMipLevels, + ); + } + } + + Ok(()) } - /// Returns true if the sampler is a compare-mode sampler. + /// Returns the border color if one is used by this sampler. #[inline] - pub fn compare_mode(&self) -> bool { - self.compare_mode + pub fn border_color(&self) -> Option { + self.border_color } - /// Returns true if the sampler is unnormalized. + /// Returns the compare operation if the sampler is a compare-mode sampler. #[inline] - pub fn is_unnormalized(&self) -> bool { - self.unnormalized + pub fn compare(&self) -> Option { + self.compare } - /// Returns true if the sampler can be used with floating-point image views. + /// Returns the magnification filter. #[inline] - pub fn usable_with_float_formats(&self) -> bool { - self.usable_with_float_formats + pub fn mag_filter(&self) -> Filter { + self.mag_filter } - /// Returns true if the sampler can be used with integer image views. + /// Returns the minification filter. #[inline] - pub fn usable_with_int_formats(&self) -> bool { - self.usable_with_int_formats + pub fn min_filter(&self) -> Filter { + self.min_filter } - /// Returns true if the sampler can be used with image views that have non-identity swizzling. + /// Returns the mipmap mode. #[inline] - pub fn usable_with_swizzling(&self) -> bool { - self.usable_with_swizzling + pub fn mipmap_mode(&self) -> SamplerMipmapMode { + self.mipmap_mode + } + + /// Returns the reduction mode. + #[inline] + pub fn reduction_mode(&self) -> SamplerReductionMode { + self.reduction_mode + } + + /// Returns true if the sampler uses unnormalized coordinates. + #[inline] + pub fn unnormalized_coordinates(&self) -> bool { + self.unnormalized_coordinates } } @@ -254,15 +410,26 @@ pub struct SamplerBuilder { impl SamplerBuilder { /// Creates the `Sampler`. pub fn build(self) -> Result, SamplerCreationError> { - let device = self.device; + let Self { + device, + mag_filter, + min_filter, + mipmap_mode, + address_mode_u, + address_mode_v, + address_mode_w, + mip_lod_bias, + anisotropy, + compare, + lod, + border_color, + unnormalized_coordinates, + reduction_mode, + } = self; - if [ - self.address_mode_u, - self.address_mode_v, - self.address_mode_w, - ] - .into_iter() - .any(|mode| mode == SamplerAddressMode::MirrorClampToEdge) + if [address_mode_u, address_mode_v, address_mode_w] + .into_iter() + .any(|mode| mode == SamplerAddressMode::MirrorClampToEdge) { if !device.enabled_features().sampler_mirror_clamp_to_edge && !device.enabled_extensions().khr_sampler_mirror_clamp_to_edge @@ -287,15 +454,15 @@ impl SamplerBuilder { { let limit = device.physical_device().properties().max_sampler_lod_bias; - if self.mip_lod_bias.abs() > limit { + if mip_lod_bias.abs() > limit { return Err(SamplerCreationError::MaxSamplerLodBiasExceeded { - requested: self.mip_lod_bias, + requested: mip_lod_bias, maximum: limit, }); } } - let (anisotropy_enable, max_anisotropy) = if let Some(max_anisotropy) = self.anisotropy { + let (anisotropy_enable, max_anisotropy) = if let Some(max_anisotropy) = anisotropy { if !device.enabled_features().sampler_anisotropy { return Err(SamplerCreationError::FeatureNotEnabled { feature: "sampler_anisotropy", @@ -311,13 +478,13 @@ impl SamplerBuilder { }); } - if [self.mag_filter, self.min_filter] + if [mag_filter, min_filter] .into_iter() .any(|filter| filter == Filter::Cubic) { return Err(SamplerCreationError::AnisotropyInvalidFilter { - mag_filter: self.mag_filter, - min_filter: self.min_filter, + mag_filter: mag_filter, + min_filter: min_filter, }); } @@ -326,11 +493,9 @@ impl SamplerBuilder { (ash::vk::FALSE, 1.0) }; - let (compare_enable, compare_op) = if let Some(compare_op) = self.compare { - if self.reduction_mode != SamplerReductionMode::WeightedAverage { - return Err(SamplerCreationError::CompareInvalidReductionMode { - reduction_mode: self.reduction_mode, - }); + let (compare_enable, compare_op) = if let Some(compare_op) = compare { + if reduction_mode != SamplerReductionMode::WeightedAverage { + return Err(SamplerCreationError::CompareInvalidReductionMode { reduction_mode }); } (ash::vk::TRUE, compare_op) @@ -338,60 +503,47 @@ impl SamplerBuilder { (ash::vk::FALSE, CompareOp::Never) }; - let border_color_used = [ - self.address_mode_u, - self.address_mode_v, - self.address_mode_w, - ] - .into_iter() - .any(|mode| mode == SamplerAddressMode::ClampToBorder); - - if self.unnormalized_coordinates { - if self.min_filter != self.mag_filter { + if unnormalized_coordinates { + if min_filter != mag_filter { return Err( SamplerCreationError::UnnormalizedCoordinatesFiltersNotEqual { - mag_filter: self.mag_filter, - min_filter: self.min_filter, + mag_filter, + min_filter, }, ); } - if self.mipmap_mode != SamplerMipmapMode::Nearest { + if mipmap_mode != SamplerMipmapMode::Nearest { return Err( - SamplerCreationError::UnnormalizedCoordinatesInvalidMipmapMode { - mipmap_mode: self.mipmap_mode, - }, + SamplerCreationError::UnnormalizedCoordinatesInvalidMipmapMode { mipmap_mode }, ); } - if self.lod != (0.0..=0.0) { + if lod != (0.0..=0.0) { return Err(SamplerCreationError::UnnormalizedCoordinatesNonzeroLod { - lod: self.lod.clone(), + lod: lod.clone(), }); } - if [self.address_mode_u, self.address_mode_v] - .into_iter() - .any(|mode| { - !matches!( - mode, - SamplerAddressMode::ClampToEdge | SamplerAddressMode::ClampToBorder - ) - }) - { + if [address_mode_u, address_mode_v].into_iter().any(|mode| { + !matches!( + mode, + SamplerAddressMode::ClampToEdge | SamplerAddressMode::ClampToBorder + ) + }) { return Err( SamplerCreationError::UnnormalizedCoordinatesInvalidAddressMode { - address_mode_u: self.address_mode_u, - address_mode_v: self.address_mode_v, + address_mode_u, + address_mode_v, }, ); } - if self.anisotropy.is_some() { + if anisotropy.is_some() { return Err(SamplerCreationError::UnnormalizedCoordinatesAnisotropyEnabled); } - if self.compare.is_some() { + if compare.is_some() { return Err(SamplerCreationError::UnnormalizedCoordinatesCompareEnabled); } } @@ -401,11 +553,11 @@ impl SamplerBuilder { || device.enabled_extensions().ext_sampler_filter_minmax { Some(ash::vk::SamplerReductionModeCreateInfo { - reduction_mode: self.reduction_mode.into(), + reduction_mode: reduction_mode.into(), ..Default::default() }) } else { - if self.reduction_mode != SamplerReductionMode::WeightedAverage { + if reduction_mode != SamplerReductionMode::WeightedAverage { if device .physical_device() .supported_features() @@ -430,21 +582,21 @@ impl SamplerBuilder { let handle = unsafe { let mut create_info = ash::vk::SamplerCreateInfo { flags: ash::vk::SamplerCreateFlags::empty(), - mag_filter: self.mag_filter.into(), - min_filter: self.min_filter.into(), - mipmap_mode: self.mipmap_mode.into(), - address_mode_u: self.address_mode_u.into(), - address_mode_v: self.address_mode_v.into(), - address_mode_w: self.address_mode_w.into(), - mip_lod_bias: self.mip_lod_bias, + mag_filter: mag_filter.into(), + min_filter: min_filter.into(), + mipmap_mode: mipmap_mode.into(), + address_mode_u: address_mode_u.into(), + address_mode_v: address_mode_v.into(), + address_mode_w: address_mode_w.into(), + mip_lod_bias, anisotropy_enable, max_anisotropy, compare_enable, compare_op: compare_op.into(), - min_lod: *self.lod.start(), - max_lod: *self.lod.end(), - border_color: self.border_color.into(), - unnormalized_coordinates: self.unnormalized_coordinates as ash::vk::Bool32, + min_lod: *lod.start(), + max_lod: *lod.end(), + border_color: border_color.into(), + unnormalized_coordinates: unnormalized_coordinates as ash::vk::Bool32, ..Default::default() }; @@ -468,28 +620,17 @@ impl SamplerBuilder { Ok(Arc::new(Sampler { handle, device, - compare_mode: self.compare.is_some(), - unnormalized: self.unnormalized_coordinates, - usable_with_float_formats: !border_color_used - || matches!( - self.border_color, - BorderColor::FloatTransparentBlack - | BorderColor::FloatOpaqueBlack - | BorderColor::FloatOpaqueWhite - ), - usable_with_int_formats: (!border_color_used - || matches!( - self.border_color, - BorderColor::IntTransparentBlack - | BorderColor::IntOpaqueBlack - | BorderColor::IntOpaqueWhite - )) - && self.compare.is_none(), - usable_with_swizzling: !border_color_used - || !matches!( - self.border_color, - BorderColor::FloatOpaqueBlack | BorderColor::IntOpaqueBlack - ), + + border_color: [address_mode_u, address_mode_v, address_mode_w] + .into_iter() + .any(|mode| mode == SamplerAddressMode::ClampToBorder) + .then(|| border_color), + compare, + mag_filter, + min_filter, + mipmap_mode, + reduction_mode, + unnormalized_coordinates, })) } @@ -1165,6 +1306,64 @@ impl From for ash::vk::SamplerReductionMode { } } +#[derive(Clone, Copy, Debug)] +pub enum SamplerImageViewIncompatibleError { + /// The sampler has a border color with a numeric type different from the image view. + BorderColorFormatNotCompatible, + + /// The sampler has an opaque black border color, but the image view is not identity swizzled. + BorderColorOpaqueBlackNotIdentitySwizzled, + + /// The sampler has depth comparison enabled, but this is not supported by the image view. + DepthComparisonNotSupported, + + /// The sampler has depth comparison enabled, but the image view does not select the `depth` + /// aspect. + DepthComparisonWrongAspect, + + /// The sampler uses a linear filter, but this is not supported by the image view's format + /// features. + FilterLinearNotSupported, + + /// The sampler uses a cubic filter, but this is not supported by the image view's format + /// features. + FilterCubicNotSupported, + + /// The sampler uses a cubic filter with a `Min` or `Max` reduction mode, but this is not + /// supported by the image view's format features. + FilterCubicMinmaxNotSupported, + + /// The sampler uses a linear mipmap mode, but this is not supported by the image view's format + /// features. + MipmapModeLinearNotSupported, + + /// The sampler uses unnormalized coordinates, but the image view has multiple mip levels. + UnnormalizedCoordinatesMultipleMipLevels, + + /// The sampler uses unnormalized coordinates, but the image view has a type other than `Dim1d` + /// or `Dim2d`. + UnnormalizedCoordinatesViewTypeNotCompatible, +} + +impl error::Error for SamplerImageViewIncompatibleError {} + +impl fmt::Display for SamplerImageViewIncompatibleError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + Self::BorderColorFormatNotCompatible => write!(fmt, "the sampler has a border color with a numeric type different from the image view"), + Self::BorderColorOpaqueBlackNotIdentitySwizzled => write!(fmt, "the sampler has an opaque black border color, but the image view is not identity swizzled"), + Self::DepthComparisonNotSupported => write!(fmt, "the sampler has depth comparison enabled, but this is not supported by the image view"), + Self::DepthComparisonWrongAspect => write!(fmt, "the sampler has depth comparison enabled, but the image view does not select the `depth` aspect"), + Self::FilterLinearNotSupported => write!(fmt, "the sampler uses a linear filter, but this is not supported by the image view's format features"), + Self::FilterCubicNotSupported => write!(fmt, "the sampler uses a cubic filter, but this is not supported by the image view's format features"), + Self::FilterCubicMinmaxNotSupported => write!(fmt, "the sampler uses a cubic filter with a `Min` or `Max` reduction mode, but this is not supported by the image view's format features"), + Self::MipmapModeLinearNotSupported => write!(fmt, "the sampler uses a linear mipmap mode, but this is not supported by the image view's format features"), + Self::UnnormalizedCoordinatesMultipleMipLevels => write!(fmt, "the sampler uses unnormalized coordinates, but the image view has multiple mip levels"), + Self::UnnormalizedCoordinatesViewTypeNotCompatible => write!(fmt, "the sampler uses unnormalized coordinates, but the image view has a type other than `Dim1d` or `Dim2d`"), + } + } +} + #[cfg(test)] mod tests { use crate::{ @@ -1185,8 +1384,8 @@ mod tests { .lod(0.0..=2.0) .build() .unwrap(); - assert!(!s.compare_mode()); - assert!(!s.is_unnormalized()); + assert!(!s.compare().is_some()); + assert!(!s.unnormalized_coordinates()); } #[test] @@ -1201,8 +1400,8 @@ mod tests { .lod(0.0..=2.0) .build() .unwrap(); - assert!(s.compare_mode()); - assert!(!s.is_unnormalized()); + assert!(s.compare().is_some()); + assert!(!s.unnormalized_coordinates()); } #[test] @@ -1214,8 +1413,8 @@ mod tests { .unnormalized_coordinates(true) .build() .unwrap(); - assert!(!s.compare_mode()); - assert!(s.is_unnormalized()); + assert!(!s.compare().is_some()); + assert!(s.unnormalized_coordinates()); } #[test] diff --git a/vulkano/src/shader/mod.rs b/vulkano/src/shader/mod.rs index d9fc77051..eed2b3598 100644 --- a/vulkano/src/shader/mod.rs +++ b/vulkano/src/shader/mod.rs @@ -20,7 +20,7 @@ use crate::check_errors; use crate::descriptor_set::layout::DescriptorType; use crate::device::Device; -use crate::format::Format; +use crate::format::{Format, NumericType}; use crate::image::view::ImageViewType; use crate::pipeline::graphics::input_assembly::PrimitiveTopology; use crate::pipeline::layout::PipelineLayoutPcRange; @@ -540,22 +540,26 @@ pub struct DescriptorRequirements { /// The image format that is required for image views bound to this descriptor. If this is /// `None`, then any image format is allowed. - pub format: Option, - - /// The view type that is required for image views bound to this descriptor. This is `None` for - /// non-image descriptors. - pub image_view_type: Option, + pub image_format: Option, /// Whether image views bound to this descriptor must have multisampling enabled or disabled. - pub multisampled: bool, + pub image_multisampled: bool, - /// The descriptor indices that require mutable (exclusive) access to the bound resource. - pub mutable: FnvHashSet, + /// The base scalar type required for the format of image views bound to this descriptor. + /// This is `None` for non-image descriptors. + pub image_scalar_type: Option, + + /// The view type that is required for image views bound to this descriptor. + /// This is `None` for non-image descriptors. + pub image_view_type: Option, + + /// For sampler bindings, the descriptor indices that require a depth comparison sampler. + pub sampler_compare: FnvHashSet, /// For sampler bindings, the descriptor indices that perform sampling operations that are not /// permitted with unnormalized coordinates. This includes sampling with `ImplicitLod`, /// `Dref` or `Proj` SPIR-V instructions or with an LOD bias or offset. - pub sampler_no_unnormalized: FnvHashSet, + pub sampler_no_unnormalized_coordinates: FnvHashSet, /// For sampler bindings, the sampled image descriptors that are used in combination with each /// sampler descriptor index. @@ -566,6 +570,14 @@ pub struct DescriptorRequirements { /// For storage image bindings, the descriptor indices that atomic operations are used with. pub storage_image_atomic: FnvHashSet, + + /// For storage images and storage texel buffers, the descriptor indices that perform read + /// operations on the bound resource. + pub storage_read: FnvHashSet, + + /// For storage buffers, storage images and storage texel buffers, the descriptor indices that + /// perform write operations on the bound resource. + pub storage_write: FnvHashSet, } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -590,9 +602,15 @@ impl DescriptorRequirements { return Err(DescriptorRequirementsIncompatible::DescriptorType); } - if let (Some(first), Some(second)) = (self.format, other.format) { + if let (Some(first), Some(second)) = (self.image_format, other.image_format) { if first != second { - return Err(DescriptorRequirementsIncompatible::Format); + return Err(DescriptorRequirementsIncompatible::ImageFormat); + } + } + + if let (Some(first), Some(second)) = (self.image_scalar_type, other.image_scalar_type) { + if first != second { + return Err(DescriptorRequirementsIncompatible::ImageScalarType); } } @@ -602,8 +620,8 @@ impl DescriptorRequirements { } } - if self.multisampled != other.multisampled { - return Err(DescriptorRequirementsIncompatible::Multisampled); + if self.image_multisampled != other.image_multisampled { + return Err(DescriptorRequirementsIncompatible::ImageMultisampled); } let sampler_with_images = { @@ -619,14 +637,18 @@ impl DescriptorRequirements { Ok(Self { descriptor_types, descriptor_count: self.descriptor_count.max(other.descriptor_count), - format: self.format.or(other.format), + image_format: self.image_format.or(other.image_format), + image_multisampled: self.image_multisampled, + image_scalar_type: self.image_scalar_type.or(other.image_scalar_type), image_view_type: self.image_view_type.or(other.image_view_type), - multisampled: self.multisampled, - mutable: &self.mutable | &other.mutable, - sampler_no_unnormalized: &self.sampler_no_unnormalized | &other.sampler_no_unnormalized, + sampler_compare: &self.sampler_compare | &other.sampler_compare, + sampler_no_unnormalized_coordinates: &self.sampler_no_unnormalized_coordinates + | &other.sampler_no_unnormalized_coordinates, sampler_with_images, stages: self.stages | other.stages, storage_image_atomic: &self.storage_image_atomic | &other.storage_image_atomic, + storage_read: &self.storage_read | &other.storage_read, + storage_write: &self.storage_write | &other.storage_write, }) } } @@ -638,35 +660,36 @@ pub enum DescriptorRequirementsIncompatible { /// The allowed descriptor types of the descriptors do not overlap. DescriptorType, /// The descriptors require different formats. - Format, + ImageFormat, + /// The descriptors require different scalar types. + ImageScalarType, + /// The multisampling requirements of the descriptors differ. + ImageMultisampled, /// The descriptors require different image view types. ImageViewType, - /// The multisampling requirements of the descriptors differ. - Multisampled, } impl Error for DescriptorRequirementsIncompatible {} impl Display for DescriptorRequirementsIncompatible { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - DescriptorRequirementsIncompatible::DescriptorType => { - write!( - f, - "the allowed descriptor types of the two descriptors do not overlap" - ) + DescriptorRequirementsIncompatible::DescriptorType => write!( + fmt, + "the allowed descriptor types of the two descriptors do not overlap", + ), + DescriptorRequirementsIncompatible::ImageFormat => { + write!(fmt, "the descriptors require different formats",) } - DescriptorRequirementsIncompatible::Format => { - write!(f, "the descriptors require different formats") + DescriptorRequirementsIncompatible::ImageMultisampled => write!( + fmt, + "the multisampling requirements of the descriptors differ", + ), + DescriptorRequirementsIncompatible::ImageScalarType => { + write!(fmt, "the descriptors require different scalar types",) } DescriptorRequirementsIncompatible::ImageViewType => { - write!(f, "the descriptors require different image view types") - } - DescriptorRequirementsIncompatible::Multisampled => { - write!( - f, - "the multisampling requirements of the descriptors differ" - ) + write!(fmt, "the descriptors require different image view types",) } } } @@ -928,7 +951,7 @@ impl ShaderInterfaceEntryType { } } -/// The numeric base type of a shader interface variable. +/// The numeric base type of a shader variable. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum ShaderScalarType { Float, @@ -936,6 +959,23 @@ pub enum ShaderScalarType { Uint, } +// https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap43.html#formats-numericformat +impl From for ShaderScalarType { + fn from(val: NumericType) -> Self { + match val { + NumericType::SFLOAT => Self::Float, + NumericType::UFLOAT => Self::Float, + NumericType::SINT => Self::Sint, + NumericType::UINT => Self::Uint, + NumericType::SNORM => Self::Float, + NumericType::UNORM => Self::Float, + NumericType::SSCALED => Self::Float, + NumericType::USCALED => Self::Float, + NumericType::SRGB => Self::Float, + } + } +} + /// Error that can happen when the interface mismatches between two shader stages. #[derive(Clone, Debug, PartialEq, Eq)] pub enum ShaderInterfaceMismatchError { diff --git a/vulkano/src/shader/reflect.rs b/vulkano/src/shader/reflect.rs index ee5d64de3..1f4cae7a1 100644 --- a/vulkano/src/shader/reflect.rs +++ b/vulkano/src/shader/reflect.rs @@ -14,12 +14,11 @@ use crate::image::view::ImageViewType; use crate::shader::ShaderScalarType; use crate::DeviceSize; use crate::{ - format::Format, pipeline::layout::PipelineLayoutPcRange, shader::{ spirv::{ - Capability, Decoration, Dim, ExecutionMode, ExecutionModel, Id, ImageFormat, - Instruction, Spirv, StorageClass, + Capability, Decoration, Dim, ExecutionMode, ExecutionModel, Id, Instruction, Spirv, + StorageClass, }, DescriptorIdentifier, DescriptorRequirements, EntryPointInfo, GeometryShaderExecution, GeometryShaderInput, ShaderExecution, ShaderInterface, ShaderInterfaceEntry, @@ -391,8 +390,46 @@ fn inspect_entry_point( sampled_image, ref image_operands, .. + } => { + if let Some((variable, Some(index))) = instruction_chain( + result, + global, + spirv, + [inst_sampled_image, inst_load], + sampled_image, + ) { + variable + .reqs + .sampler_no_unnormalized_coordinates + .insert(index); + } } - | &Instruction::ImageSampleDrefImplicitLod { + + &Instruction::ImageSampleProjExplicitLod { + sampled_image, + ref image_operands, + .. + } + | &Instruction::ImageSparseSampleProjExplicitLod { + sampled_image, + ref image_operands, + .. + } => { + if let Some((variable, Some(index))) = instruction_chain( + result, + global, + spirv, + [inst_sampled_image, inst_load], + sampled_image, + ) { + variable + .reqs + .sampler_no_unnormalized_coordinates + .insert(index); + } + } + + &Instruction::ImageSampleDrefImplicitLod { sampled_image, ref image_operands, .. @@ -419,21 +456,15 @@ fn inspect_entry_point( [inst_sampled_image, inst_load], sampled_image, ) { - variable.reqs.sampler_no_unnormalized.insert(index); + variable + .reqs + .sampler_no_unnormalized_coordinates + .insert(index); + variable.reqs.sampler_compare.insert(index); } } - &Instruction::ImageSampleProjExplicitLod { - sampled_image, - ref image_operands, - .. - } - | &Instruction::ImageSparseSampleProjExplicitLod { - sampled_image, - ref image_operands, - .. - } - | &Instruction::ImageSampleDrefExplicitLod { + &Instruction::ImageSampleDrefExplicitLod { sampled_image, ref image_operands, .. @@ -460,7 +491,11 @@ fn inspect_entry_point( [inst_sampled_image, inst_load], sampled_image, ) { - variable.reqs.sampler_no_unnormalized.insert(index); + variable + .reqs + .sampler_no_unnormalized_coordinates + .insert(index); + variable.reqs.sampler_compare.insert(index); } } @@ -486,7 +521,10 @@ fn inspect_entry_point( || image_operands.const_offsets.is_some() || image_operands.offset.is_some() { - variable.reqs.sampler_no_unnormalized.insert(index); + variable + .reqs + .sampler_no_unnormalized_coordinates + .insert(index); } } } @@ -497,11 +535,19 @@ fn inspect_entry_point( instruction_chain(result, global, spirv, [], image); } + &Instruction::ImageRead { image, .. } => { + if let Some((variable, Some(index))) = + instruction_chain(result, global, spirv, [inst_load], image) + { + variable.reqs.storage_read.insert(index); + } + } + &Instruction::ImageWrite { image, .. } => { if let Some((variable, Some(index))) = instruction_chain(result, global, spirv, [inst_load], image) { - variable.reqs.mutable.insert(index); + variable.reqs.storage_write.insert(index); } } @@ -536,7 +582,7 @@ fn inspect_entry_point( if let Some((variable, Some(index))) = instruction_chain(result, global, spirv, [], pointer) { - variable.reqs.mutable.insert(index); + variable.reqs.storage_write.insert(index); } } @@ -635,6 +681,7 @@ fn descriptor_requirements_of(spirv: &Spirv, variable_id: Id) -> DescriptorVaria } &Instruction::TypeImage { + sampled_type, ref dim, arrayed, ms, @@ -642,25 +689,39 @@ fn descriptor_requirements_of(spirv: &Spirv, variable_id: Id) -> DescriptorVaria ref image_format, .. } => { - let multisampled = ms != 0; assert!(sampled != 0, "Vulkan requires that variables of type OpTypeImage have a Sampled operand of 1 or 2"); - let format: Option = image_format.clone().into(); + reqs.image_format = image_format.clone().into(); + reqs.image_multisampled = ms != 0; + reqs.image_scalar_type = Some(match spirv.id(sampled_type).instruction() { + &Instruction::TypeInt { + width, signedness, .. + } => { + assert!(width == 32); // TODO: 64-bit components + match signedness { + 0 => ShaderScalarType::Uint, + 1 => ShaderScalarType::Sint, + _ => unreachable!(), + } + } + &Instruction::TypeFloat { width, .. } => { + assert!(width == 32); // TODO: 64-bit components + ShaderScalarType::Float + } + _ => unreachable!(), + }); match dim { Dim::SubpassData => { assert!( - *image_format == ImageFormat::Unknown, + reqs.image_format.is_none(), "If Dim is SubpassData, Image Format must be Unknown" ); assert!(sampled == 2, "If Dim is SubpassData, Sampled must be 2"); assert!(arrayed == 0, "If Dim is SubpassData, Arrayed must be 0"); reqs.descriptor_types = vec![DescriptorType::InputAttachment]; - reqs.multisampled = multisampled; } Dim::Buffer => { - reqs.format = format; - if sampled == 1 { reqs.descriptor_types = vec![DescriptorType::UniformTexelBuffer]; } else { @@ -668,7 +729,7 @@ fn descriptor_requirements_of(spirv: &Spirv, variable_id: Id) -> DescriptorVaria } } _ => { - let image_view_type = Some(match (dim, arrayed) { + reqs.image_view_type = Some(match (dim, arrayed) { (Dim::Dim1D, 0) => ImageViewType::Dim1d, (Dim::Dim1D, 1) => ImageViewType::Dim1dArray, (Dim::Dim2D, 0) => ImageViewType::Dim2d, @@ -685,10 +746,6 @@ fn descriptor_requirements_of(spirv: &Spirv, variable_id: Id) -> DescriptorVaria _ => unreachable!(), }); - reqs.format = format; - reqs.multisampled = multisampled; - reqs.image_view_type = image_view_type; - if reqs.descriptor_types.is_empty() { if sampled == 1 { reqs.descriptor_types = vec![DescriptorType::SampledImage]; diff --git a/vulkano/src/swapchain/swapchain.rs b/vulkano/src/swapchain/swapchain.rs index 69c2643b5..0830a2aa0 100644 --- a/vulkano/src/swapchain/swapchain.rs +++ b/vulkano/src/swapchain/swapchain.rs @@ -660,6 +660,7 @@ impl SwapchainBuilder { usage, flags, None, + None, )? .is_none() { diff --git a/vulkano/src/sync/future/mod.rs b/vulkano/src/sync/future/mod.rs index af51f7332..7d23c41a2 100644 --- a/vulkano/src/sync/future/mod.rs +++ b/vulkano/src/sync/future/mod.rs @@ -291,33 +291,33 @@ pub unsafe trait GpuFuture: DeviceOwned { { Box::new(self) as _ } - + /// Turn the current future into a `Box`. /// /// This is a helper function that calls `Box::new(yourFuture) as Box`. fn boxed_send(self) -> Box - where - Self: Sized + Send + 'static, + where + Self: Sized + Send + 'static, { Box::new(self) as _ } - + /// Turn the current future into a `Box`. /// /// This is a helper function that calls `Box::new(yourFuture) as Box`. fn boxed_sync(self) -> Box - where - Self: Sized + Sync + 'static, + where + Self: Sized + Sync + 'static, { Box::new(self) as _ } - + /// Turn the current future into a `Box`. /// /// This is a helper function that calls `Box::new(yourFuture) as Box`. fn boxed_send_sync(self) -> Box - where - Self: Sized + Send + Sync + 'static, + where + Self: Sized + Send + Sync + 'static, { Box::new(self) as _ }