diff options
Diffstat (limited to 'src/command_buffer/validity')
-rw-r--r-- | src/command_buffer/validity/blit_image.rs | 302 | ||||
-rw-r--r-- | src/command_buffer/validity/clear_color_image.rs | 81 | ||||
-rw-r--r-- | src/command_buffer/validity/copy_buffer.rs | 103 | ||||
-rw-r--r-- | src/command_buffer/validity/copy_image.rs | 243 | ||||
-rw-r--r-- | src/command_buffer/validity/copy_image_buffer.rs | 266 | ||||
-rw-r--r-- | src/command_buffer/validity/debug_marker.rs | 32 | ||||
-rw-r--r-- | src/command_buffer/validity/descriptor_sets.rs | 109 | ||||
-rw-r--r-- | src/command_buffer/validity/dispatch.rs | 88 | ||||
-rw-r--r-- | src/command_buffer/validity/dynamic_state.rs | 215 | ||||
-rw-r--r-- | src/command_buffer/validity/fill_buffer.rs | 105 | ||||
-rw-r--r-- | src/command_buffer/validity/index_buffer.rs | 148 | ||||
-rw-r--r-- | src/command_buffer/validity/indirect_buffer.rs | 72 | ||||
-rw-r--r-- | src/command_buffer/validity/mod.rs | 50 | ||||
-rw-r--r-- | src/command_buffer/validity/push_constants.rs | 52 | ||||
-rw-r--r-- | src/command_buffer/validity/query.rs | 391 | ||||
-rw-r--r-- | src/command_buffer/validity/update_buffer.rs | 181 | ||||
-rw-r--r-- | src/command_buffer/validity/vertex_buffers.rs | 118 |
17 files changed, 2556 insertions, 0 deletions
diff --git a/src/command_buffer/validity/blit_image.rs b/src/command_buffer/validity/blit_image.rs new file mode 100644 index 0000000..aae58f6 --- /dev/null +++ b/src/command_buffer/validity/blit_image.rs @@ -0,0 +1,302 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use crate::device::Device; +use crate::format::FormatTy; +use crate::image::ImageAccess; +use crate::image::ImageDimensions; +use crate::image::SampleCount; +use crate::sampler::Filter; +use crate::VulkanObject; +use std::error; +use std::fmt; + +/// Checks whether a blit image command is valid. +/// +/// Note that this doesn't check whether `layer_count` is equal to 0. TODO: change that? +/// +/// # Panic +/// +/// - Panics if the source or the destination was not created with `device`. +/// +pub fn check_blit_image<S, D>( + device: &Device, + source: &S, + source_top_left: [i32; 3], + source_bottom_right: [i32; 3], + source_base_array_layer: u32, + source_mip_level: u32, + destination: &D, + destination_top_left: [i32; 3], + destination_bottom_right: [i32; 3], + destination_base_array_layer: u32, + destination_mip_level: u32, + layer_count: u32, + filter: Filter, +) -> Result<(), CheckBlitImageError> +where + S: ?Sized + ImageAccess, + D: ?Sized + ImageAccess, +{ + let source_inner = source.inner(); + let destination_inner = destination.inner(); + + assert_eq!( + source_inner.image.device().internal_object(), + device.internal_object() + ); + assert_eq!( + destination_inner.image.device().internal_object(), + device.internal_object() + ); + + if !source_inner.image.usage().transfer_source { + return Err(CheckBlitImageError::MissingTransferSourceUsage); + } + + if !destination_inner.image.usage().transfer_destination { + return Err(CheckBlitImageError::MissingTransferDestinationUsage); + } + + if !source_inner.image.format_features().blit_src { + return Err(CheckBlitImageError::SourceFormatNotSupported); + } + + if !destination_inner.image.format_features().blit_dst { + return Err(CheckBlitImageError::DestinationFormatNotSupported); + } + + if source.samples() != SampleCount::Sample1 || destination.samples() != SampleCount::Sample1 { + return Err(CheckBlitImageError::UnexpectedMultisampled); + } + + let source_format_ty = source.format().ty(); + let destination_format_ty = destination.format().ty(); + + if matches!( + source_format_ty, + FormatTy::Depth | FormatTy::Stencil | FormatTy::DepthStencil + ) { + if source.format() != destination.format() { + return Err(CheckBlitImageError::DepthStencilFormatMismatch); + } + + if filter != Filter::Nearest { + return Err(CheckBlitImageError::DepthStencilNearestMandatory); + } + } + + let types_should_be_same = source_format_ty == FormatTy::Uint + || destination_format_ty == FormatTy::Uint + || source_format_ty == FormatTy::Sint + || destination_format_ty == FormatTy::Sint; + if types_should_be_same && (source_format_ty != destination_format_ty) { + return Err(CheckBlitImageError::IncompatibleFormatsTypes { + source_format_ty: source.format().ty(), + destination_format_ty: destination.format().ty(), + }); + } + + let source_dimensions = match source.dimensions().mipmap_dimensions(source_mip_level) { + Some(d) => d, + None => return Err(CheckBlitImageError::SourceCoordinatesOutOfRange), + }; + + let destination_dimensions = match destination + .dimensions() + .mipmap_dimensions(destination_mip_level) + { + Some(d) => d, + None => return Err(CheckBlitImageError::DestinationCoordinatesOutOfRange), + }; + + if source_base_array_layer + layer_count > source_dimensions.array_layers() { + return Err(CheckBlitImageError::SourceCoordinatesOutOfRange); + } + + if destination_base_array_layer + layer_count > destination_dimensions.array_layers() { + return Err(CheckBlitImageError::DestinationCoordinatesOutOfRange); + } + + if source_top_left[0] < 0 || source_top_left[0] > source_dimensions.width() as i32 { + return Err(CheckBlitImageError::SourceCoordinatesOutOfRange); + } + + if source_top_left[1] < 0 || source_top_left[1] > source_dimensions.height() as i32 { + return Err(CheckBlitImageError::SourceCoordinatesOutOfRange); + } + + if source_top_left[2] < 0 || source_top_left[2] > source_dimensions.depth() as i32 { + return Err(CheckBlitImageError::SourceCoordinatesOutOfRange); + } + + if source_bottom_right[0] < 0 || source_bottom_right[0] > source_dimensions.width() as i32 { + return Err(CheckBlitImageError::SourceCoordinatesOutOfRange); + } + + if source_bottom_right[1] < 0 || source_bottom_right[1] > source_dimensions.height() as i32 { + return Err(CheckBlitImageError::SourceCoordinatesOutOfRange); + } + + if source_bottom_right[2] < 0 || source_bottom_right[2] > source_dimensions.depth() as i32 { + return Err(CheckBlitImageError::SourceCoordinatesOutOfRange); + } + + if destination_top_left[0] < 0 + || destination_top_left[0] > destination_dimensions.width() as i32 + { + return Err(CheckBlitImageError::DestinationCoordinatesOutOfRange); + } + + if destination_top_left[1] < 0 + || destination_top_left[1] > destination_dimensions.height() as i32 + { + return Err(CheckBlitImageError::DestinationCoordinatesOutOfRange); + } + + if destination_top_left[2] < 0 + || destination_top_left[2] > destination_dimensions.depth() as i32 + { + return Err(CheckBlitImageError::DestinationCoordinatesOutOfRange); + } + + if destination_bottom_right[0] < 0 + || destination_bottom_right[0] > destination_dimensions.width() as i32 + { + return Err(CheckBlitImageError::DestinationCoordinatesOutOfRange); + } + + if destination_bottom_right[1] < 0 + || destination_bottom_right[1] > destination_dimensions.height() as i32 + { + return Err(CheckBlitImageError::DestinationCoordinatesOutOfRange); + } + + if destination_bottom_right[2] < 0 + || destination_bottom_right[2] > destination_dimensions.depth() as i32 + { + return Err(CheckBlitImageError::DestinationCoordinatesOutOfRange); + } + + match source_dimensions { + ImageDimensions::Dim1d { .. } => { + if source_top_left[1] != 0 || source_bottom_right[1] != 1 { + return Err(CheckBlitImageError::IncompatibleRangeForImageType); + } + if source_top_left[2] != 0 || source_bottom_right[2] != 1 { + return Err(CheckBlitImageError::IncompatibleRangeForImageType); + } + } + ImageDimensions::Dim2d { .. } => { + if source_top_left[2] != 0 || source_bottom_right[2] != 1 { + return Err(CheckBlitImageError::IncompatibleRangeForImageType); + } + } + ImageDimensions::Dim3d { .. } => {} + } + + match destination_dimensions { + ImageDimensions::Dim1d { .. } => { + if destination_top_left[1] != 0 || destination_bottom_right[1] != 1 { + return Err(CheckBlitImageError::IncompatibleRangeForImageType); + } + if destination_top_left[2] != 0 || destination_bottom_right[2] != 1 { + return Err(CheckBlitImageError::IncompatibleRangeForImageType); + } + } + ImageDimensions::Dim2d { .. } => { + if destination_top_left[2] != 0 || destination_bottom_right[2] != 1 { + return Err(CheckBlitImageError::IncompatibleRangeForImageType); + } + } + ImageDimensions::Dim3d { .. } => {} + } + + Ok(()) +} + +/// Error that can happen from `check_clear_color_image`. +#[derive(Debug, Copy, Clone)] +pub enum CheckBlitImageError { + /// The source is missing the transfer source usage. + MissingTransferSourceUsage, + /// The destination is missing the transfer destination usage. + MissingTransferDestinationUsage, + /// The format of the source image doesn't support blit operations. + SourceFormatNotSupported, + /// The format of the destination image doesn't support blit operations. + DestinationFormatNotSupported, + /// You must use the nearest filter when blitting depth/stencil images. + DepthStencilNearestMandatory, + /// The format of the source and destination must be equal when blitting depth/stencil images. + DepthStencilFormatMismatch, + /// The types of the source format and the destination format aren't compatible. + IncompatibleFormatsTypes { + source_format_ty: FormatTy, + destination_format_ty: FormatTy, + }, + /// Blitting between multisampled images is forbidden. + UnexpectedMultisampled, + /// The offsets, array layers and/or mipmap levels are out of range in the source image. + SourceCoordinatesOutOfRange, + /// The offsets, array layers and/or mipmap levels are out of range in the destination image. + DestinationCoordinatesOutOfRange, + /// The top-left and/or bottom-right coordinates are incompatible with the image type. + IncompatibleRangeForImageType, +} + +impl error::Error for CheckBlitImageError {} + +impl fmt::Display for CheckBlitImageError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckBlitImageError::MissingTransferSourceUsage => { + "the source is missing the transfer source usage" + } + CheckBlitImageError::MissingTransferDestinationUsage => { + "the destination is missing the transfer destination usage" + } + CheckBlitImageError::SourceFormatNotSupported => { + "the format of the source image doesn't support blit operations" + } + CheckBlitImageError::DestinationFormatNotSupported => { + "the format of the destination image doesn't support blit operations" + } + CheckBlitImageError::DepthStencilNearestMandatory => { + "you must use the nearest filter when blitting depth/stencil images" + } + CheckBlitImageError::DepthStencilFormatMismatch => { + "the format of the source and destination must be equal when blitting \ + depth/stencil images" + } + CheckBlitImageError::IncompatibleFormatsTypes { .. } => { + "the types of the source format and the destination format aren't compatible" + } + CheckBlitImageError::UnexpectedMultisampled => { + "blitting between multisampled images is forbidden" + } + CheckBlitImageError::SourceCoordinatesOutOfRange => { + "the offsets, array layers and/or mipmap levels are out of range in the source \ + image" + } + CheckBlitImageError::DestinationCoordinatesOutOfRange => { + "the offsets, array layers and/or mipmap levels are out of range in the \ + destination image" + } + CheckBlitImageError::IncompatibleRangeForImageType => { + "the top-left and/or bottom-right coordinates are incompatible with the image type" + } + } + ) + } +} diff --git a/src/command_buffer/validity/clear_color_image.rs b/src/command_buffer/validity/clear_color_image.rs new file mode 100644 index 0000000..6ecb68f --- /dev/null +++ b/src/command_buffer/validity/clear_color_image.rs @@ -0,0 +1,81 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::error; +use std::fmt; + +use crate::device::Device; +use crate::image::ImageAccess; +use crate::VulkanObject; + +/// Checks whether a clear color image command is valid. +/// +/// # Panic +/// +/// - Panics if the destination was not created with `device`. +/// +pub fn check_clear_color_image<I>( + device: &Device, + image: &I, + first_layer: u32, + num_layers: u32, + first_mipmap: u32, + num_mipmaps: u32, +) -> Result<(), CheckClearColorImageError> +where + I: ?Sized + ImageAccess, +{ + assert_eq!( + image.inner().image.device().internal_object(), + device.internal_object() + ); + + if !image.inner().image.usage().transfer_destination { + return Err(CheckClearColorImageError::MissingTransferUsage); + } + + if first_layer + num_layers > image.dimensions().array_layers() { + return Err(CheckClearColorImageError::OutOfRange); + } + + if first_mipmap + num_mipmaps > image.mipmap_levels() { + return Err(CheckClearColorImageError::OutOfRange); + } + + Ok(()) +} + +/// Error that can happen from `check_clear_color_image`. +#[derive(Debug, Copy, Clone)] +pub enum CheckClearColorImageError { + /// The image is missing the transfer destination usage. + MissingTransferUsage, + /// The array layers and mipmap levels are out of range. + OutOfRange, +} + +impl error::Error for CheckClearColorImageError {} + +impl fmt::Display for CheckClearColorImageError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckClearColorImageError::MissingTransferUsage => { + "the image is missing the transfer destination usage" + } + CheckClearColorImageError::OutOfRange => { + "the array layers and mipmap levels are out of range" + } + } + ) + } +} diff --git a/src/command_buffer/validity/copy_buffer.rs b/src/command_buffer/validity/copy_buffer.rs new file mode 100644 index 0000000..3691860 --- /dev/null +++ b/src/command_buffer/validity/copy_buffer.rs @@ -0,0 +1,103 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use crate::buffer::TypedBufferAccess; +use crate::device::Device; +use crate::device::DeviceOwned; +use crate::DeviceSize; +use crate::VulkanObject; +use std::cmp; +use std::error; +use std::fmt; + +/// Checks whether a copy buffer command is valid. +/// +/// # Panic +/// +/// - Panics if the source and destination were not created with `device`. +/// +pub fn check_copy_buffer<S, D, T>( + device: &Device, + source: &S, + destination: &D, +) -> Result<CheckCopyBuffer, CheckCopyBufferError> +where + S: ?Sized + TypedBufferAccess<Content = T>, + D: ?Sized + TypedBufferAccess<Content = T>, + T: ?Sized, +{ + assert_eq!( + source.inner().buffer.device().internal_object(), + device.internal_object() + ); + assert_eq!( + destination.inner().buffer.device().internal_object(), + device.internal_object() + ); + + if !source.inner().buffer.usage().transfer_source { + return Err(CheckCopyBufferError::SourceMissingTransferUsage); + } + + if !destination.inner().buffer.usage().transfer_destination { + return Err(CheckCopyBufferError::DestinationMissingTransferUsage); + } + + let copy_size = cmp::min(source.size(), destination.size()); + + if source.conflict_key() == destination.conflict_key() { + return Err(CheckCopyBufferError::OverlappingRanges); + } else { + debug_assert!(destination.conflict_key() != source.conflict_key()); + } + + Ok(CheckCopyBuffer { copy_size }) +} + +/// Information returned if `check_copy_buffer` succeeds. +pub struct CheckCopyBuffer { + /// Size of the transfer in bytes. + /// + /// If the size of the source and destination are not equal, then the value is equal to the + /// smallest of the two. + pub copy_size: DeviceSize, +} + +/// Error that can happen from `check_copy_buffer`. +#[derive(Debug, Copy, Clone)] +pub enum CheckCopyBufferError { + /// The source buffer is missing the transfer source usage. + SourceMissingTransferUsage, + /// The destination buffer is missing the transfer destination usage. + DestinationMissingTransferUsage, + /// The source and destination are overlapping. + OverlappingRanges, +} + +impl error::Error for CheckCopyBufferError {} + +impl fmt::Display for CheckCopyBufferError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckCopyBufferError::SourceMissingTransferUsage => { + "the source buffer is missing the transfer source usage" + } + CheckCopyBufferError::DestinationMissingTransferUsage => { + "the destination buffer is missing the transfer destination usage" + } + CheckCopyBufferError::OverlappingRanges => + "the source and destination are overlapping", + } + ) + } +} diff --git a/src/command_buffer/validity/copy_image.rs b/src/command_buffer/validity/copy_image.rs new file mode 100644 index 0000000..b55e73d --- /dev/null +++ b/src/command_buffer/validity/copy_image.rs @@ -0,0 +1,243 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use crate::device::Device; +use crate::format::FormatTy; +use crate::image::ImageAccess; +use crate::image::ImageDimensions; +use crate::VulkanObject; +use std::error; +use std::fmt; + +/// Checks whether a copy image command is valid. +/// +/// Note that this doesn't check whether `layer_count` is equal to 0. TODO: change that? +/// +/// # Panic +/// +/// - Panics if the source or the destination was not created with `device`. +/// +pub fn check_copy_image<S, D>( + device: &Device, + source: &S, + source_offset: [i32; 3], + source_base_array_layer: u32, + source_mip_level: u32, + destination: &D, + destination_offset: [i32; 3], + destination_base_array_layer: u32, + destination_mip_level: u32, + extent: [u32; 3], + layer_count: u32, +) -> Result<(), CheckCopyImageError> +where + S: ?Sized + ImageAccess, + D: ?Sized + ImageAccess, +{ + let source_inner = source.inner(); + let destination_inner = destination.inner(); + + assert_eq!( + source_inner.image.device().internal_object(), + device.internal_object() + ); + assert_eq!( + destination_inner.image.device().internal_object(), + device.internal_object() + ); + + if !source_inner.image.usage().transfer_source { + return Err(CheckCopyImageError::MissingTransferSourceUsage); + } + + if !destination_inner.image.usage().transfer_destination { + return Err(CheckCopyImageError::MissingTransferDestinationUsage); + } + + if source.samples() != destination.samples() { + return Err(CheckCopyImageError::SampleCountMismatch); + } + + let source_format_ty = source.format().ty(); + let destination_format_ty = destination.format().ty(); + + if matches!( + source_format_ty, + FormatTy::Depth | FormatTy::Stencil | FormatTy::DepthStencil + ) { + if source.format() != destination.format() { + return Err(CheckCopyImageError::DepthStencilFormatMismatch); + } + } + + // TODO: The correct check here is that the uncompressed element size of the source is + // equal to the compressed element size of the destination. However, format doesn't + // currently expose this information, so to be safe, we simply disallow compressed formats. + if source.format().ty() == FormatTy::Compressed + || destination.format().ty() == FormatTy::Compressed + || (source.format().size() != destination.format().size()) + { + return Err(CheckCopyImageError::SizeIncompatibleFormatsTypes { + source_format_ty: source.format().ty(), + destination_format_ty: destination.format().ty(), + }); + } + + let source_dimensions = match source.dimensions().mipmap_dimensions(source_mip_level) { + Some(d) => d, + None => return Err(CheckCopyImageError::SourceCoordinatesOutOfRange), + }; + + let destination_dimensions = match destination + .dimensions() + .mipmap_dimensions(destination_mip_level) + { + Some(d) => d, + None => return Err(CheckCopyImageError::DestinationCoordinatesOutOfRange), + }; + + if source_base_array_layer + layer_count > source_dimensions.array_layers() { + return Err(CheckCopyImageError::SourceCoordinatesOutOfRange); + } + + if destination_base_array_layer + layer_count > destination_dimensions.array_layers() { + return Err(CheckCopyImageError::DestinationCoordinatesOutOfRange); + } + + if source_offset[0] < 0 || source_offset[0] as u32 + extent[0] > source_dimensions.width() { + return Err(CheckCopyImageError::SourceCoordinatesOutOfRange); + } + + if source_offset[1] < 0 || source_offset[1] as u32 + extent[1] > source_dimensions.height() { + return Err(CheckCopyImageError::SourceCoordinatesOutOfRange); + } + + if source_offset[2] < 0 || source_offset[2] as u32 + extent[2] > source_dimensions.depth() { + return Err(CheckCopyImageError::SourceCoordinatesOutOfRange); + } + + if destination_offset[0] < 0 + || destination_offset[0] as u32 + extent[0] > destination_dimensions.width() + { + return Err(CheckCopyImageError::DestinationCoordinatesOutOfRange); + } + + if destination_offset[1] < 0 + || destination_offset[1] as u32 + extent[1] > destination_dimensions.height() + { + return Err(CheckCopyImageError::DestinationCoordinatesOutOfRange); + } + + if destination_offset[2] < 0 + || destination_offset[2] as u32 + extent[2] > destination_dimensions.depth() + { + return Err(CheckCopyImageError::DestinationCoordinatesOutOfRange); + } + + match source_dimensions { + ImageDimensions::Dim1d { .. } => { + if source_offset[1] != 0 || extent[1] != 1 { + return Err(CheckCopyImageError::IncompatibleRangeForImageType); + } + if source_offset[2] != 0 || extent[2] != 1 { + return Err(CheckCopyImageError::IncompatibleRangeForImageType); + } + } + ImageDimensions::Dim2d { .. } => { + if source_offset[2] != 0 || extent[2] != 1 { + return Err(CheckCopyImageError::IncompatibleRangeForImageType); + } + } + ImageDimensions::Dim3d { .. } => {} + } + + match destination_dimensions { + ImageDimensions::Dim1d { .. } => { + if destination_offset[1] != 0 || extent[1] != 1 { + return Err(CheckCopyImageError::IncompatibleRangeForImageType); + } + if destination_offset[2] != 0 || extent[2] != 1 { + return Err(CheckCopyImageError::IncompatibleRangeForImageType); + } + } + ImageDimensions::Dim2d { .. } => { + if destination_offset[2] != 0 || extent[2] != 1 { + return Err(CheckCopyImageError::IncompatibleRangeForImageType); + } + } + ImageDimensions::Dim3d { .. } => {} + } + + Ok(()) +} + +/// Error that can happen from `check_copy_image`. +#[derive(Debug, Copy, Clone)] +pub enum CheckCopyImageError { + /// The source is missing the transfer source usage. + MissingTransferSourceUsage, + /// The destination is missing the transfer destination usage. + MissingTransferDestinationUsage, + /// The number of samples in the source and destination do not match. + SampleCountMismatch, + /// The format of the source and destination must be equal when copying depth/stencil images. + DepthStencilFormatMismatch, + /// The types of the source format and the destination format aren't size-compatible. + SizeIncompatibleFormatsTypes { + source_format_ty: FormatTy, + destination_format_ty: FormatTy, + }, + /// The offsets, array layers and/or mipmap levels are out of range in the source image. + SourceCoordinatesOutOfRange, + /// The offsets, array layers and/or mipmap levels are out of range in the destination image. + DestinationCoordinatesOutOfRange, + /// The offsets or extent are incompatible with the image type. + IncompatibleRangeForImageType, +} + +impl error::Error for CheckCopyImageError {} + +impl fmt::Display for CheckCopyImageError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckCopyImageError::MissingTransferSourceUsage => { + "the source is missing the transfer source usage" + } + CheckCopyImageError::MissingTransferDestinationUsage => { + "the destination is missing the transfer destination usage" + } + CheckCopyImageError::SampleCountMismatch => { + "the number of samples in the source and destination do not match" + } + CheckCopyImageError::DepthStencilFormatMismatch => { + "the format of the source and destination must be equal when copying \ + depth/stencil images" + } + CheckCopyImageError::SizeIncompatibleFormatsTypes { .. } => { + "the types of the source format and the destination format aren't size-compatible" + } + CheckCopyImageError::SourceCoordinatesOutOfRange => { + "the offsets, array layers and/or mipmap levels are out of range in the source \ + image" + } + CheckCopyImageError::DestinationCoordinatesOutOfRange => { + "the offsets, array layers and/or mipmap levels are out of range in the \ + destination image" + } + CheckCopyImageError::IncompatibleRangeForImageType => { + "the offsets or extent are incompatible with the image type" + } + } + ) + } +} diff --git a/src/command_buffer/validity/copy_image_buffer.rs b/src/command_buffer/validity/copy_image_buffer.rs new file mode 100644 index 0000000..77224b1 --- /dev/null +++ b/src/command_buffer/validity/copy_image_buffer.rs @@ -0,0 +1,266 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use crate::buffer::TypedBufferAccess; +use crate::device::Device; +use crate::device::DeviceOwned; +use crate::format::Format; +use crate::format::IncompatiblePixelsType; +use crate::format::Pixel; +use crate::image::ImageAccess; +use crate::image::SampleCount; +use crate::DeviceSize; +use crate::VulkanObject; +use std::error; +use std::fmt; + +/// Type of operation to check. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum CheckCopyBufferImageTy { + BufferToImage, + ImageToBuffer, +} + +/// Checks whether a copy buffer-image command is valid. Can check both buffer-to-image copies and +/// image-to-buffer copies. +/// +/// # Panic +/// +/// - Panics if the buffer and image were not created with `device`. +/// +pub fn check_copy_buffer_image<B, I, Px>( + device: &Device, + buffer: &B, + image: &I, + ty: CheckCopyBufferImageTy, + image_offset: [u32; 3], + image_size: [u32; 3], + image_first_layer: u32, + image_num_layers: u32, + image_mipmap: u32, +) -> Result<(), CheckCopyBufferImageError> +where + I: ?Sized + ImageAccess, + B: ?Sized + TypedBufferAccess<Content = [Px]>, + Px: Pixel, // TODO: use a trait on the image itself instead +{ + let buffer_inner = buffer.inner(); + let image_inner = image.inner(); + + assert_eq!( + buffer_inner.buffer.device().internal_object(), + device.internal_object() + ); + assert_eq!( + image_inner.image.device().internal_object(), + device.internal_object() + ); + + match ty { + CheckCopyBufferImageTy::BufferToImage => { + if !buffer_inner.buffer.usage().transfer_source { + return Err(CheckCopyBufferImageError::SourceMissingTransferUsage); + } + if !image_inner.image.usage().transfer_destination { + return Err(CheckCopyBufferImageError::DestinationMissingTransferUsage); + } + } + CheckCopyBufferImageTy::ImageToBuffer => { + if !image_inner.image.usage().transfer_source { + return Err(CheckCopyBufferImageError::SourceMissingTransferUsage); + } + if !buffer_inner.buffer.usage().transfer_destination { + return Err(CheckCopyBufferImageError::DestinationMissingTransferUsage); + } + } + } + + if image.samples() != SampleCount::Sample1 { + return Err(CheckCopyBufferImageError::UnexpectedMultisampled); + } + + let image_dimensions = match image.dimensions().mipmap_dimensions(image_mipmap) { + Some(d) => d, + None => return Err(CheckCopyBufferImageError::ImageCoordinatesOutOfRange), + }; + + if image_first_layer + image_num_layers > image_dimensions.array_layers() { + return Err(CheckCopyBufferImageError::ImageCoordinatesOutOfRange); + } + + if image_offset[0] + image_size[0] > image_dimensions.width() { + return Err(CheckCopyBufferImageError::ImageCoordinatesOutOfRange); + } + + if image_offset[1] + image_size[1] > image_dimensions.height() { + return Err(CheckCopyBufferImageError::ImageCoordinatesOutOfRange); + } + + if image_offset[2] + image_size[2] > image_dimensions.depth() { + return Err(CheckCopyBufferImageError::ImageCoordinatesOutOfRange); + } + + Px::ensure_accepts(image.format())?; + + { + let required_len = + required_len_for_format::<Px>(image.format(), image_size, image_num_layers); + if required_len > buffer.len() { + return Err(CheckCopyBufferImageError::BufferTooSmall { + required_len, + actual_len: buffer.len(), + }); + } + } + + // TODO: check memory overlap? + + Ok(()) +} + +/// Computes the minimum required len in elements for buffer with image data in specified +/// format of specified size. +fn required_len_for_format<Px>( + format: Format, + image_size: [u32; 3], + image_num_layers: u32, +) -> DeviceSize +where + Px: Pixel, +{ + let (block_width, block_height) = format.block_dimensions(); + let num_blocks = (image_size[0] + block_width - 1) / block_width + * ((image_size[1] + block_height - 1) / block_height) + * image_size[2] + * image_num_layers; + let required_len = num_blocks as DeviceSize * Px::rate(format) as DeviceSize; + + return required_len; +} + +#[cfg(test)] +mod tests { + use crate::command_buffer::validity::copy_image_buffer::required_len_for_format; + use crate::format::Format; + + #[test] + fn test_required_len_for_format() { + // issue #1292 + assert_eq!( + required_len_for_format::<u8>(Format::BC1_RGBUnormBlock, [2048, 2048, 1], 1), + 2097152 + ); + // other test cases + assert_eq!( + required_len_for_format::<u8>(Format::R8G8B8A8Unorm, [2048, 2048, 1], 1), + 16777216 + ); + assert_eq!( + required_len_for_format::<u8>(Format::R4G4UnormPack8, [512, 512, 1], 1), + 262144 + ); + assert_eq!( + required_len_for_format::<u8>(Format::R8G8B8Uscaled, [512, 512, 1], 1), + 786432 + ); + assert_eq!( + required_len_for_format::<u8>(Format::R32G32Uint, [512, 512, 1], 1), + 2097152 + ); + assert_eq!( + required_len_for_format::<u32>(Format::R32G32Uint, [512, 512, 1], 1), + 524288 + ); + assert_eq!( + required_len_for_format::<[u32; 2]>(Format::R32G32Uint, [512, 512, 1], 1), + 262144 + ); + assert_eq!( + required_len_for_format::<u8>(Format::ASTC_8x8UnormBlock, [512, 512, 1], 1), + 65536 + ); + assert_eq!( + required_len_for_format::<u8>(Format::ASTC_12x12SrgbBlock, [512, 512, 1], 1), + 29584 + ); + } +} + +/// Error that can happen from `check_copy_buffer_image`. +#[derive(Debug, Copy, Clone)] +pub enum CheckCopyBufferImageError { + /// The source buffer or image is missing the transfer source usage. + SourceMissingTransferUsage, + /// The destination buffer or image is missing the transfer destination usage. + DestinationMissingTransferUsage, + /// The source and destination are overlapping. + OverlappingRanges, + /// The image must not be multisampled. + UnexpectedMultisampled, + /// The image coordinates are out of range. + ImageCoordinatesOutOfRange, + /// The type of pixels in the buffer isn't compatible with the image format. + WrongPixelType(IncompatiblePixelsType), + /// The buffer is too small for the copy operation. + BufferTooSmall { + /// Required number of elements in the buffer. + required_len: DeviceSize, + /// Actual number of elements in the buffer. + actual_len: DeviceSize, + }, +} + +impl error::Error for CheckCopyBufferImageError { + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + CheckCopyBufferImageError::WrongPixelType(ref err) => Some(err), + _ => None, + } + } +} + +impl fmt::Display for CheckCopyBufferImageError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckCopyBufferImageError::SourceMissingTransferUsage => { + "the source buffer is missing the transfer source usage" + } + CheckCopyBufferImageError::DestinationMissingTransferUsage => { + "the destination buffer is missing the transfer destination usage" + } + CheckCopyBufferImageError::OverlappingRanges => { + "the source and destination are overlapping" + } + CheckCopyBufferImageError::UnexpectedMultisampled => { + "the image must not be multisampled" + } + CheckCopyBufferImageError::ImageCoordinatesOutOfRange => { + "the image coordinates are out of range" + } + CheckCopyBufferImageError::WrongPixelType(_) => { + "the type of pixels in the buffer isn't compatible with the image format" + } + CheckCopyBufferImageError::BufferTooSmall { .. } => { + "the buffer is too small for the copy operation" + } + } + ) + } +} + +impl From<IncompatiblePixelsType> for CheckCopyBufferImageError { + #[inline] + fn from(err: IncompatiblePixelsType) -> CheckCopyBufferImageError { + CheckCopyBufferImageError::WrongPixelType(err) + } +} diff --git a/src/command_buffer/validity/debug_marker.rs b/src/command_buffer/validity/debug_marker.rs new file mode 100644 index 0000000..9a80e7f --- /dev/null +++ b/src/command_buffer/validity/debug_marker.rs @@ -0,0 +1,32 @@ +use std::{error, fmt}; + +/// Checks whether the specified color is valid as debug marker color. +/// +/// The color parameter must contain RGBA values in order, in the range 0.0 to 1.0. +pub fn check_debug_marker_color(color: [f32; 4]) -> Result<(), CheckColorError> { + // The values contain RGBA values in order, in the range 0.0 to 1.0. + if color.iter().any(|x| !(0f32..=1f32).contains(x)) { + return Err(CheckColorError); + } + + Ok(()) +} + +/// Error that can happen from `check_debug_marker_color`. +#[derive(Debug, Copy, Clone)] +pub struct CheckColorError; + +impl error::Error for CheckColorError {} + +impl fmt::Display for CheckColorError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckColorError => "color parameter does contains values out of 0.0 to 1.0 range", + } + ) + } +} diff --git a/src/command_buffer/validity/descriptor_sets.rs b/src/command_buffer/validity/descriptor_sets.rs new file mode 100644 index 0000000..227101b --- /dev/null +++ b/src/command_buffer/validity/descriptor_sets.rs @@ -0,0 +1,109 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::error; +use std::fmt; + +use crate::descriptor_set::layout::DescriptorDescSupersetError; +use crate::descriptor_set::DescriptorSetWithOffsets; +use crate::pipeline::layout::PipelineLayout; + +/// Checks whether descriptor sets are compatible with the pipeline. +pub fn check_descriptor_sets_validity( + pipeline_layout: &PipelineLayout, + descriptor_sets: &[DescriptorSetWithOffsets], +) -> Result<(), CheckDescriptorSetsValidityError> { + // What's important is not that the pipeline layout and the descriptor sets *match*. Instead + // what's important is that the descriptor sets are a superset of the pipeline layout. It's not + // a problem if the descriptor sets provide more elements than expected. + + for (set_num, set) in pipeline_layout.descriptor_set_layouts().iter().enumerate() { + for (binding_num, pipeline_desc) in + (0..set.num_bindings()).filter_map(|i| set.descriptor(i).map(|d| (i, d))) + { + let set_desc = descriptor_sets + .get(set_num) + .and_then(|so| so.as_ref().0.layout().descriptor(binding_num)); + + let set_desc = match set_desc { + Some(s) => s, + None => { + return Err(CheckDescriptorSetsValidityError::MissingDescriptor { + set_num: set_num, + binding_num: binding_num, + }) + } + }; + + if let Err(err) = set_desc.ensure_superset_of(&pipeline_desc) { + return Err(CheckDescriptorSetsValidityError::IncompatibleDescriptor { + error: err, + set_num: set_num, + binding_num: binding_num, + }); + } + } + } + + Ok(()) +} + +/// Error that can happen when checking descriptor sets validity. +#[derive(Debug, Clone)] +pub enum CheckDescriptorSetsValidityError { + /// A descriptor is missing in the descriptor sets that were provided. + MissingDescriptor { + /// The index of the set of the descriptor. + set_num: usize, + /// The binding number of the descriptor. + binding_num: usize, + }, + + /// A descriptor in the provided sets is not compatible with what is expected. + IncompatibleDescriptor { + /// The reason why the two descriptors aren't compatible. + error: DescriptorDescSupersetError, + /// The index of the set of the descriptor. + set_num: usize, + /// The binding number of the descriptor. + binding_num: usize, + }, +} + +impl error::Error for CheckDescriptorSetsValidityError { + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + CheckDescriptorSetsValidityError::IncompatibleDescriptor { ref error, .. } => { + Some(error) + } + _ => None, + } + } +} + +impl fmt::Display for CheckDescriptorSetsValidityError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckDescriptorSetsValidityError::MissingDescriptor { .. } => { + "a descriptor is missing in the descriptor sets that were provided" + } + CheckDescriptorSetsValidityError::IncompatibleDescriptor { .. } => { + "a descriptor in the provided sets is not compatible with what is expected" + } + } + ) + } +} + +// TODO: tests diff --git a/src/command_buffer/validity/dispatch.rs b/src/command_buffer/validity/dispatch.rs new file mode 100644 index 0000000..297c1f6 --- /dev/null +++ b/src/command_buffer/validity/dispatch.rs @@ -0,0 +1,88 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::error; +use std::fmt; + +use crate::device::Device; + +/// Checks whether the dispatch dimensions are supported by the device. +pub fn check_dispatch(device: &Device, dimensions: [u32; 3]) -> Result<(), CheckDispatchError> { + let max = device + .physical_device() + .properties() + .max_compute_work_group_count; + + if dimensions[0] > max[0] || dimensions[1] > max[1] || dimensions[2] > max[2] { + return Err(CheckDispatchError::UnsupportedDimensions { + requested: dimensions, + max_supported: max, + }); + } + + Ok(()) +} + +/// Error that can happen when checking dispatch command validity. +#[derive(Debug, Copy, Clone)] +pub enum CheckDispatchError { + /// The dimensions are too large for the device's limits. + UnsupportedDimensions { + /// The requested dimensions. + requested: [u32; 3], + /// The actual supported dimensions. + max_supported: [u32; 3], + }, +} + +impl error::Error for CheckDispatchError {} + +impl fmt::Display for CheckDispatchError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckDispatchError::UnsupportedDimensions { .. } => { + "the dimensions are too large for the device's limits" + } + } + ) + } +} + +#[cfg(test)] +mod tests { + use crate::command_buffer::validity; + + #[test] + fn max_checked() { + let (device, _) = gfx_dev_and_queue!(); + + let attempted = [u32::MAX, u32::MAX, u32::MAX]; + + // Just in case the device is some kind of software implementation. + if device + .physical_device() + .properties() + .max_compute_work_group_count + == attempted + { + return; + } + + match validity::check_dispatch(&device, attempted) { + Err(validity::CheckDispatchError::UnsupportedDimensions { requested, .. }) => { + assert_eq!(requested, attempted); + } + _ => panic!(), + } + } +} diff --git a/src/command_buffer/validity/dynamic_state.rs b/src/command_buffer/validity/dynamic_state.rs new file mode 100644 index 0000000..f9a65ad --- /dev/null +++ b/src/command_buffer/validity/dynamic_state.rs @@ -0,0 +1,215 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::error; +use std::fmt; + +use crate::command_buffer::DynamicState; +use crate::pipeline::GraphicsPipelineAbstract; + +/// Checks whether states that are about to be set are correct. +pub fn check_dynamic_state_validity<Pl>( + pipeline: &Pl, + state: &DynamicState, +) -> Result<(), CheckDynamicStateValidityError> +where + Pl: GraphicsPipelineAbstract, +{ + let device = pipeline.device(); + + if pipeline.has_dynamic_line_width() { + if let Some(value) = state.line_width { + if value != 1.0 && !pipeline.device().enabled_features().wide_lines { + return Err(CheckDynamicStateValidityError::LineWidthMissingExtension); + } + } else { + return Err(CheckDynamicStateValidityError::LineWidthMissing); + } + } else { + if state.line_width.is_some() { + return Err(CheckDynamicStateValidityError::LineWidthNotDynamic); + } + } + + if pipeline.has_dynamic_viewports() { + if let Some(ref viewports) = state.viewports { + if viewports.len() != pipeline.num_viewports() as usize { + return Err(CheckDynamicStateValidityError::ViewportsCountMismatch { + expected: pipeline.num_viewports() as usize, + obtained: viewports.len(), + }); + } + } else { + return Err(CheckDynamicStateValidityError::ViewportsMissing); + } + } else { + if state.viewports.is_some() { + return Err(CheckDynamicStateValidityError::ViewportsNotDynamic); + } + } + + if pipeline.has_dynamic_scissors() { + if let Some(ref scissors) = state.scissors { + if scissors.len() != pipeline.num_viewports() as usize { + return Err(CheckDynamicStateValidityError::ScissorsCountMismatch { + expected: pipeline.num_viewports() as usize, + obtained: scissors.len(), + }); + } + } else { + return Err(CheckDynamicStateValidityError::ScissorsMissing); + } + } else { + if state.scissors.is_some() { + return Err(CheckDynamicStateValidityError::ScissorsNotDynamic); + } + } + + if pipeline.has_dynamic_stencil_compare_mask() { + if let None = state.compare_mask { + return Err(CheckDynamicStateValidityError::CompareMaskMissing); + } + } else { + if state.compare_mask.is_some() { + return Err(CheckDynamicStateValidityError::CompareMaskNotDynamic); + } + } + + if pipeline.has_dynamic_stencil_write_mask() { + if let None = state.write_mask { + return Err(CheckDynamicStateValidityError::WriteMaskMissing); + } + } else { + if state.write_mask.is_some() { + return Err(CheckDynamicStateValidityError::WriteMaskNotDynamic); + } + } + + if pipeline.has_dynamic_stencil_reference() { + if let None = state.reference { + return Err(CheckDynamicStateValidityError::ReferenceMissing); + } + } else { + if state.reference.is_some() { + return Err(CheckDynamicStateValidityError::ReferenceNotDynamic); + } + } + + Ok(()) +} + +/// Error that can happen when validating dynamic states. +#[derive(Debug, Copy, Clone)] +pub enum CheckDynamicStateValidityError { + /// Passed a dynamic line width, while the pipeline doesn't have line width set as dynamic. + LineWidthNotDynamic, + /// The pipeline has a dynamic line width, but no line width value was passed. + LineWidthMissing, + /// The `wide_lines` extension must be enabled in order to use line width values different + /// from 1.0. + LineWidthMissingExtension, + /// Passed dynamic viewports, while the pipeline doesn't have viewports set as dynamic. + ViewportsNotDynamic, + /// The pipeline has dynamic viewports, but no viewports were passed. + ViewportsMissing, + /// The number of dynamic viewports doesn't match the expected number of viewports. + ViewportsCountMismatch { + /// Expected number of viewports. + expected: usize, + /// Number of viewports that were passed. + obtained: usize, + }, + /// Passed dynamic scissors, while the pipeline doesn't have scissors set as dynamic. + ScissorsNotDynamic, + /// The pipeline has dynamic scissors, but no scissors were passed. + ScissorsMissing, + /// The number of dynamic scissors doesn't match the expected number of scissors. + ScissorsCountMismatch { + /// Expected number of scissors. + expected: usize, + /// Number of scissors that were passed. + obtained: usize, + }, + /// Passed dynamic compare mask, while the pipeline doesn't have the compare mask set as dynamic. + CompareMaskNotDynamic, + /// The pipeline has dynamic compare mask, but no compare mask was passed. + CompareMaskMissing, + /// Passed dynamic write mask, while the pipeline doesn't have the write mask set as dynamic. + WriteMaskNotDynamic, + /// The pipeline has dynamic write mask, but no write mask was passed. + WriteMaskMissing, + /// Passed dynamic reference, while the pipeline doesn't have the reference set as dynamic. + ReferenceNotDynamic, + /// The pipeline has dynamic reference, but no reference was passed. + ReferenceMissing, +} + +impl error::Error for CheckDynamicStateValidityError {} + +impl fmt::Display for CheckDynamicStateValidityError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckDynamicStateValidityError::LineWidthNotDynamic => { + "passed a dynamic line width, while the pipeline doesn't have line width set as \ + dynamic" + } + CheckDynamicStateValidityError::LineWidthMissing => { + "the pipeline has a dynamic line width, but no line width value was passed" + } + CheckDynamicStateValidityError::LineWidthMissingExtension => { + "the `wide_lines` extension must be enabled in order to use line width values \ + different from 1.0" + } + CheckDynamicStateValidityError::ViewportsNotDynamic => { + "passed dynamic viewports, while the pipeline doesn't have viewports set as \ + dynamic" + } + CheckDynamicStateValidityError::ViewportsMissing => { + "the pipeline has dynamic viewports, but no viewports were passed" + } + CheckDynamicStateValidityError::ViewportsCountMismatch { .. } => { + "the number of dynamic viewports doesn't match the expected number of viewports" + } + CheckDynamicStateValidityError::ScissorsNotDynamic => { + "passed dynamic scissors, while the pipeline doesn't have scissors set as dynamic" + } + CheckDynamicStateValidityError::ScissorsMissing => { + "the pipeline has dynamic scissors, but no scissors were passed" + } + CheckDynamicStateValidityError::ScissorsCountMismatch { .. } => { + "the number of dynamic scissors doesn't match the expected number of scissors" + } + CheckDynamicStateValidityError::CompareMaskNotDynamic => { + "passed dynamic compare mask, while the pipeline doesn't have compare mask set as dynamic" + } + CheckDynamicStateValidityError::CompareMaskMissing => { + "the pipeline has dynamic compare mask, but no compare mask was passed" + } + CheckDynamicStateValidityError::WriteMaskNotDynamic => { + "passed dynamic write mask, while the pipeline doesn't have write mask set as dynamic" + } + CheckDynamicStateValidityError::WriteMaskMissing => { + "the pipeline has dynamic write mask, but no write mask was passed" + } + CheckDynamicStateValidityError::ReferenceNotDynamic => { + "passed dynamic Reference, while the pipeline doesn't have reference set as dynamic" + } + CheckDynamicStateValidityError::ReferenceMissing => { + "the pipeline has dynamic reference, but no reference was passed" + } + } + ) + } +} + +// TODO: tests diff --git a/src/command_buffer/validity/fill_buffer.rs b/src/command_buffer/validity/fill_buffer.rs new file mode 100644 index 0000000..a02f47d --- /dev/null +++ b/src/command_buffer/validity/fill_buffer.rs @@ -0,0 +1,105 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::error; +use std::fmt; + +use crate::buffer::BufferAccess; +use crate::device::Device; +use crate::device::DeviceOwned; +use crate::VulkanObject; + +/// Checks whether a fill buffer command is valid. +/// +/// # Panic +/// +/// - Panics if the buffer not created with `device`. +/// +pub fn check_fill_buffer<B>(device: &Device, buffer: &B) -> Result<(), CheckFillBufferError> +where + B: ?Sized + BufferAccess, +{ + assert_eq!( + buffer.inner().buffer.device().internal_object(), + device.internal_object() + ); + + if !buffer.inner().buffer.usage().transfer_destination { + return Err(CheckFillBufferError::BufferMissingUsage); + } + + if buffer.inner().offset % 4 != 0 { + return Err(CheckFillBufferError::WrongAlignment); + } + + Ok(()) +} + +/// Error that can happen when attempting to add a `fill_buffer` command. +#[derive(Debug, Copy, Clone)] +pub enum CheckFillBufferError { + /// The "transfer destination" usage must be enabled on the buffer. + BufferMissingUsage, + /// The data or size must be 4-bytes aligned. + WrongAlignment, +} + +impl error::Error for CheckFillBufferError {} + +impl fmt::Display for CheckFillBufferError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckFillBufferError::BufferMissingUsage => { + "the transfer destination usage must be enabled on the buffer" + } + CheckFillBufferError::WrongAlignment => + "the offset or size are not aligned to 4 bytes", + } + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::buffer::BufferUsage; + use crate::buffer::CpuAccessibleBuffer; + + #[test] + fn missing_usage() { + let (device, queue) = gfx_dev_and_queue!(); + let buffer = CpuAccessibleBuffer::from_data( + device.clone(), + BufferUsage::vertex_buffer(), + false, + 0u32, + ) + .unwrap(); + + match check_fill_buffer(&device, &buffer) { + Err(CheckFillBufferError::BufferMissingUsage) => (), + _ => panic!(), + } + } + + #[test] + fn wrong_device() { + let (dev1, queue) = gfx_dev_and_queue!(); + let (dev2, _) = gfx_dev_and_queue!(); + let buffer = CpuAccessibleBuffer::from_data(dev1, BufferUsage::all(), false, 0u32).unwrap(); + + assert_should_panic!({ + let _ = check_fill_buffer(&dev2, &buffer); + }); + } +} diff --git a/src/command_buffer/validity/index_buffer.rs b/src/command_buffer/validity/index_buffer.rs new file mode 100644 index 0000000..52f2bdc --- /dev/null +++ b/src/command_buffer/validity/index_buffer.rs @@ -0,0 +1,148 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::error; +use std::fmt; + +use crate::buffer::BufferAccess; +use crate::buffer::TypedBufferAccess; +use crate::device::Device; +use crate::device::DeviceOwned; +use crate::pipeline::input_assembly::Index; +use crate::VulkanObject; + +/// Checks whether an index buffer can be bound. +/// +/// # Panic +/// +/// - Panics if the buffer was not created with `device`. +/// +pub fn check_index_buffer<B, I>( + device: &Device, + buffer: &B, +) -> Result<CheckIndexBuffer, CheckIndexBufferError> +where + B: ?Sized + BufferAccess + TypedBufferAccess<Content = [I]>, + I: Index, +{ + assert_eq!( + buffer.inner().buffer.device().internal_object(), + device.internal_object() + ); + + if !buffer.inner().buffer.usage().index_buffer { + return Err(CheckIndexBufferError::BufferMissingUsage); + } + + // TODO: The sum of offset and the address of the range of VkDeviceMemory object that is + // backing buffer, must be a multiple of the type indicated by indexType + + // TODO: full_draw_index_uint32 feature + + Ok(CheckIndexBuffer { + num_indices: buffer.len() as u32, + }) +} + +/// Information returned if `check_index_buffer` succeeds. +pub struct CheckIndexBuffer { + /// Number of indices in the index buffer. + pub num_indices: u32, +} + +/// Error that can happen when checking whether binding an index buffer is valid. +#[derive(Debug, Copy, Clone)] +pub enum CheckIndexBufferError { + /// The "index buffer" usage must be enabled on the index buffer. + BufferMissingUsage, + /// The data or size must be 4-bytes aligned. + WrongAlignment, + /// The type of the indices is not supported by the device. + UnsupportIndexType, +} + +impl error::Error for CheckIndexBufferError {} + +impl fmt::Display for CheckIndexBufferError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckIndexBufferError::BufferMissingUsage => { + "the index buffer usage must be enabled on the index buffer" + } + CheckIndexBufferError::WrongAlignment => { + "the sum of offset and the address of the range of VkDeviceMemory object that is \ + backing buffer, must be a multiple of the type indicated by indexType" + } + CheckIndexBufferError::UnsupportIndexType => { + "the type of the indices is not supported by the device" + } + } + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::buffer::BufferUsage; + use crate::buffer::CpuAccessibleBuffer; + + #[test] + fn num_indices() { + let (device, queue) = gfx_dev_and_queue!(); + let buffer = CpuAccessibleBuffer::from_iter( + device.clone(), + BufferUsage::index_buffer(), + false, + 0..500u32, + ) + .unwrap(); + + match check_index_buffer(&device, &buffer) { + Ok(CheckIndexBuffer { num_indices }) => { + assert_eq!(num_indices, 500); + } + _ => panic!(), + } + } + + #[test] + fn missing_usage() { + let (device, queue) = gfx_dev_and_queue!(); + let buffer = CpuAccessibleBuffer::from_iter( + device.clone(), + BufferUsage::vertex_buffer(), + false, + 0..500u32, + ) + .unwrap(); + + match check_index_buffer(&device, &buffer) { + Err(CheckIndexBufferError::BufferMissingUsage) => (), + _ => panic!(), + } + } + + #[test] + fn wrong_device() { + let (dev1, queue) = gfx_dev_and_queue!(); + let (dev2, _) = gfx_dev_and_queue!(); + + let buffer = + CpuAccessibleBuffer::from_iter(dev1, BufferUsage::all(), false, 0..500u32).unwrap(); + + assert_should_panic!({ + let _ = check_index_buffer(&dev2, &buffer); + }); + } +} diff --git a/src/command_buffer/validity/indirect_buffer.rs b/src/command_buffer/validity/indirect_buffer.rs new file mode 100644 index 0000000..3acd4b2 --- /dev/null +++ b/src/command_buffer/validity/indirect_buffer.rs @@ -0,0 +1,72 @@ +// Copyright (c) 2021 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use crate::buffer::BufferAccess; +use crate::device::Device; +use crate::device::DeviceOwned; +use crate::VulkanObject; +use std::error; +use std::fmt; + +/// Checks whether an indirect buffer can be bound. +pub fn check_indirect_buffer<Inb>( + device: &Device, + buffer: &Inb, +) -> Result<(), CheckIndirectBufferError> +where + Inb: BufferAccess + Send + Sync + 'static, +{ + assert_eq!( + buffer.inner().buffer.device().internal_object(), + device.internal_object() + ); + + if !buffer.inner().buffer.usage().indirect_buffer { + return Err(CheckIndirectBufferError::BufferMissingUsage); + } + + Ok(()) +} + +/// Error that can happen when checking whether binding an indirect buffer is valid. +#[derive(Debug, Copy, Clone)] +pub enum CheckIndirectBufferError { + /// The "indirect buffer" usage must be enabled on the indirect buffer. + BufferMissingUsage, + /// The maximum number of indirect draws has been exceeded. + MaxDrawIndirectCountLimitExceeded { + /// The limit that must be fulfilled. + limit: u32, + /// What was requested. + requested: u32, + }, +} + +impl error::Error for CheckIndirectBufferError {} + +impl fmt::Display for CheckIndirectBufferError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckIndirectBufferError::BufferMissingUsage => { + "the indirect buffer usage must be enabled on the indirect buffer" + } + CheckIndirectBufferError::MaxDrawIndirectCountLimitExceeded { + limit, + requested, + } => { + "the maximum number of indirect draws has been exceeded" + } + } + ) + } +} diff --git a/src/command_buffer/validity/mod.rs b/src/command_buffer/validity/mod.rs new file mode 100644 index 0000000..d4c7a9e --- /dev/null +++ b/src/command_buffer/validity/mod.rs @@ -0,0 +1,50 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +//! Functions that check the validity of commands. + +pub use self::blit_image::{check_blit_image, CheckBlitImageError}; +pub use self::clear_color_image::{check_clear_color_image, CheckClearColorImageError}; +pub use self::copy_buffer::{check_copy_buffer, CheckCopyBuffer, CheckCopyBufferError}; +pub use self::copy_image::{check_copy_image, CheckCopyImageError}; +pub use self::copy_image_buffer::{ + check_copy_buffer_image, CheckCopyBufferImageError, CheckCopyBufferImageTy, +}; +pub use self::debug_marker::{check_debug_marker_color, CheckColorError}; +pub use self::descriptor_sets::{check_descriptor_sets_validity, CheckDescriptorSetsValidityError}; +pub use self::dispatch::{check_dispatch, CheckDispatchError}; +pub use self::dynamic_state::{check_dynamic_state_validity, CheckDynamicStateValidityError}; +pub use self::fill_buffer::{check_fill_buffer, CheckFillBufferError}; +pub use self::index_buffer::{check_index_buffer, CheckIndexBuffer, CheckIndexBufferError}; +pub use self::indirect_buffer::{check_indirect_buffer, CheckIndirectBufferError}; +pub use self::push_constants::{check_push_constants_validity, CheckPushConstantsValidityError}; +pub use self::query::{ + check_begin_query, check_copy_query_pool_results, check_end_query, check_reset_query_pool, + check_write_timestamp, CheckBeginQueryError, CheckCopyQueryPoolResultsError, + CheckEndQueryError, CheckResetQueryPoolError, CheckWriteTimestampError, +}; +pub use self::update_buffer::{check_update_buffer, CheckUpdateBufferError}; +pub use self::vertex_buffers::{check_vertex_buffers, CheckVertexBuffer, CheckVertexBufferError}; + +mod blit_image; +mod clear_color_image; +mod copy_buffer; +mod copy_image; +mod copy_image_buffer; +mod debug_marker; +mod descriptor_sets; +mod dispatch; +mod dynamic_state; +mod fill_buffer; +mod index_buffer; +mod indirect_buffer; +mod push_constants; +mod query; +mod update_buffer; +mod vertex_buffers; diff --git a/src/command_buffer/validity/push_constants.rs b/src/command_buffer/validity/push_constants.rs new file mode 100644 index 0000000..65b749e --- /dev/null +++ b/src/command_buffer/validity/push_constants.rs @@ -0,0 +1,52 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use crate::pipeline::layout::PipelineLayout; +use std::error; +use std::fmt; + +/// Checks whether push constants are compatible with the pipeline. +pub fn check_push_constants_validity<Pc>( + pipeline_layout: &PipelineLayout, + push_constants: &Pc, +) -> Result<(), CheckPushConstantsValidityError> +where + Pc: ?Sized, +{ + // TODO + if !true { + return Err(CheckPushConstantsValidityError::IncompatiblePushConstants); + } + + Ok(()) +} + +/// Error that can happen when checking push constants validity. +#[derive(Debug, Copy, Clone)] +pub enum CheckPushConstantsValidityError { + /// The push constants are incompatible with the pipeline layout. + IncompatiblePushConstants, +} + +impl error::Error for CheckPushConstantsValidityError {} + +impl fmt::Display for CheckPushConstantsValidityError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckPushConstantsValidityError::IncompatiblePushConstants => { + "the push constants are incompatible with the pipeline layout" + } + } + ) + } +} diff --git a/src/command_buffer/validity/query.rs b/src/command_buffer/validity/query.rs new file mode 100644 index 0000000..52781f4 --- /dev/null +++ b/src/command_buffer/validity/query.rs @@ -0,0 +1,391 @@ +// Copyright (c) 2016 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use crate::buffer::TypedBufferAccess; +use crate::device::physical::QueueFamily; +use crate::device::Device; +use crate::device::DeviceOwned; +use crate::query::GetResultsError; +use crate::query::QueryControlFlags; +use crate::query::QueryPool; +use crate::query::QueryResultElement; +use crate::query::QueryResultFlags; +use crate::query::QueryType; +use crate::sync::PipelineStage; +use crate::DeviceSize; +use crate::VulkanObject; +use std::error; +use std::fmt; +use std::ops::Range; + +/// Checks whether a `begin_query` command is valid. +/// +/// # Panic +/// +/// - Panics if the query pool was not created with `device`. +pub fn check_begin_query( + device: &Device, + query_pool: &QueryPool, + query: u32, + flags: QueryControlFlags, +) -> Result<(), CheckBeginQueryError> { + assert_eq!( + device.internal_object(), + query_pool.device().internal_object(), + ); + query_pool + .query(query) + .ok_or(CheckBeginQueryError::OutOfRange)?; + + match query_pool.ty() { + QueryType::Occlusion => { + if flags.precise && !device.enabled_features().occlusion_query_precise { + return Err(CheckBeginQueryError::OcclusionQueryPreciseFeatureNotEnabled); + } + } + QueryType::PipelineStatistics(_) => { + if flags.precise { + return Err(CheckBeginQueryError::InvalidFlags); + } + } + QueryType::Timestamp => return Err(CheckBeginQueryError::NotPermitted), + } + + Ok(()) +} + +/// Error that can happen from `check_begin_query`. +#[derive(Debug, Copy, Clone)] +pub enum CheckBeginQueryError { + /// The provided flags are not allowed for this type of query. + InvalidFlags, + /// This operation is not permitted on this query type. + NotPermitted, + /// `QueryControlFlags::precise` was requested, but the `occlusion_query_precise` feature was not enabled. + OcclusionQueryPreciseFeatureNotEnabled, + /// The provided query index is not valid for this pool. + OutOfRange, +} + +impl error::Error for CheckBeginQueryError {} + +impl fmt::Display for CheckBeginQueryError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + Self::InvalidFlags => { + "the provided flags are not allowed for this type of query" + } + Self::NotPermitted => { + "this operation is not permitted on this query type" + } + Self::OcclusionQueryPreciseFeatureNotEnabled => { + "QueryControlFlags::precise was requested, but the occlusion_query_precise feature was not enabled" + } + Self::OutOfRange => { + "the provided query index is not valid for this pool" + } + } + ) + } +} + +/// Checks whether a `end_query` command is valid. +/// +/// # Panic +/// +/// - Panics if the query pool was not created with `device`. +pub fn check_end_query( + device: &Device, + query_pool: &QueryPool, + query: u32, +) -> Result<(), CheckEndQueryError> { + assert_eq!( + device.internal_object(), + query_pool.device().internal_object(), + ); + + query_pool + .query(query) + .ok_or(CheckEndQueryError::OutOfRange)?; + + Ok(()) +} + +/// Error that can happen from `check_end_query`. +#[derive(Debug, Copy, Clone)] +pub enum CheckEndQueryError { + /// The provided query index is not valid for this pool. + OutOfRange, +} + +impl error::Error for CheckEndQueryError {} + +impl fmt::Display for CheckEndQueryError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + Self::OutOfRange => { + "the provided query index is not valid for this pool" + } + } + ) + } +} + +/// Checks whether a `write_timestamp` command is valid. +/// +/// # Panic +/// +/// - Panics if the query pool was not created with `device`. +pub fn check_write_timestamp( + device: &Device, + queue_family: QueueFamily, + query_pool: &QueryPool, + query: u32, + stage: PipelineStage, +) -> Result<(), CheckWriteTimestampError> { + assert_eq!( + device.internal_object(), + query_pool.device().internal_object(), + ); + + if !matches!(query_pool.ty(), QueryType::Timestamp) { + return Err(CheckWriteTimestampError::NotPermitted); + } + + query_pool + .query(query) + .ok_or(CheckWriteTimestampError::OutOfRange)?; + + if queue_family.timestamp_valid_bits().is_none() { + return Err(CheckWriteTimestampError::NoTimestampValidBits); + } + + if !queue_family.supports_stage(stage) { + return Err(CheckWriteTimestampError::StageNotSupported); + } + + match stage { + PipelineStage::GeometryShader => { + if !device.enabled_features().geometry_shader { + return Err(CheckWriteTimestampError::GeometryShaderFeatureNotEnabled); + } + } + PipelineStage::TessellationControlShader | PipelineStage::TessellationEvaluationShader => { + if !device.enabled_features().tessellation_shader { + return Err(CheckWriteTimestampError::TessellationShaderFeatureNotEnabled); + } + } + _ => (), + } + + Ok(()) +} + +/// Error that can happen from `check_write_timestamp`. +#[derive(Debug, Copy, Clone)] +pub enum CheckWriteTimestampError { + /// The geometry shader stage was requested, but the `geometry_shader` feature was not enabled. + GeometryShaderFeatureNotEnabled, + /// The queue family's `timestamp_valid_bits` value is `None`. + NoTimestampValidBits, + /// This operation is not permitted on this query type. + NotPermitted, + /// The provided query index is not valid for this pool. + OutOfRange, + /// The provided stage is not supported by the queue family. + StageNotSupported, + /// A tessellation shader stage was requested, but the `tessellation_shader` feature was not enabled. + TessellationShaderFeatureNotEnabled, +} + +impl error::Error for CheckWriteTimestampError {} + +impl fmt::Display for CheckWriteTimestampError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + Self::GeometryShaderFeatureNotEnabled => { + "the geometry shader stage was requested, but the geometry_shader feature was not enabled" + } + Self::NoTimestampValidBits => { + "the queue family's timestamp_valid_bits value is None" + } + Self::NotPermitted => { + "this operation is not permitted on this query type" + } + Self::OutOfRange => { + "the provided query index is not valid for this pool" + } + Self::StageNotSupported => { + "the provided stage is not supported by the queue family" + } + Self::TessellationShaderFeatureNotEnabled => { + "a tessellation shader stage was requested, but the tessellation_shader feature was not enabled" + } + } + ) + } +} + +/// Checks whether a `copy_query_pool_results` command is valid. +/// +/// # Panic +/// +/// - Panics if the query pool or buffer was not created with `device`. +pub fn check_copy_query_pool_results<D, T>( + device: &Device, + query_pool: &QueryPool, + queries: Range<u32>, + destination: &D, + flags: QueryResultFlags, +) -> Result<DeviceSize, CheckCopyQueryPoolResultsError> +where + D: ?Sized + TypedBufferAccess<Content = [T]>, + T: QueryResultElement, +{ + let buffer_inner = destination.inner(); + assert_eq!( + device.internal_object(), + buffer_inner.buffer.device().internal_object(), + ); + assert_eq!( + device.internal_object(), + query_pool.device().internal_object(), + ); + + if !buffer_inner.buffer.usage().transfer_destination { + return Err(CheckCopyQueryPoolResultsError::DestinationMissingTransferUsage); + } + + let queries_range = query_pool + .queries_range(queries) + .ok_or(CheckCopyQueryPoolResultsError::OutOfRange)?; + + Ok(queries_range.check_query_pool_results::<T>( + buffer_inner.offset, + destination.len(), + flags, + )?) +} + +/// Error that can happen from `check_copy_query_pool_results`. +#[derive(Debug, Copy, Clone)] +pub enum CheckCopyQueryPoolResultsError { + /// The buffer is too small for the copy operation. + BufferTooSmall { + /// Required number of elements in the buffer. + required_len: DeviceSize, + /// Actual number of elements in the buffer. + actual_len: DeviceSize, + }, + /// The destination buffer is missing the transfer destination usage. + DestinationMissingTransferUsage, + /// The provided flags are not allowed for this type of query. + InvalidFlags, + /// The provided queries range is not valid for this pool. + OutOfRange, +} + +impl From<GetResultsError> for CheckCopyQueryPoolResultsError { + #[inline] + fn from(value: GetResultsError) -> Self { + match value { + GetResultsError::BufferTooSmall { + required_len, + actual_len, + } => CheckCopyQueryPoolResultsError::BufferTooSmall { + required_len, + actual_len, + }, + GetResultsError::InvalidFlags => CheckCopyQueryPoolResultsError::InvalidFlags, + GetResultsError::DeviceLost | GetResultsError::OomError(_) => unreachable!(), + } + } +} + +impl error::Error for CheckCopyQueryPoolResultsError {} + +impl fmt::Display for CheckCopyQueryPoolResultsError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + Self::BufferTooSmall { .. } => { + "the buffer is too small for the copy operation" + } + Self::DestinationMissingTransferUsage => { + "the destination buffer is missing the transfer destination usage" + } + Self::InvalidFlags => { + "the provided flags are not allowed for this type of query" + } + Self::OutOfRange => { + "the provided queries range is not valid for this pool" + } + } + ) + } +} + +/// Checks whether a `reset_query_pool` command is valid. +/// +/// # Panic +/// +/// - Panics if the query pool was not created with `device`. +pub fn check_reset_query_pool( + device: &Device, + query_pool: &QueryPool, + queries: Range<u32>, +) -> Result<(), CheckResetQueryPoolError> { + assert_eq!( + device.internal_object(), + query_pool.device().internal_object(), + ); + query_pool + .queries_range(queries) + .ok_or(CheckResetQueryPoolError::OutOfRange)?; + Ok(()) +} + +/// Error that can happen from `check_reset_query_pool`. +#[derive(Debug, Copy, Clone)] +pub enum CheckResetQueryPoolError { + /// The provided queries range is not valid for this pool. + OutOfRange, +} + +impl error::Error for CheckResetQueryPoolError {} + +impl fmt::Display for CheckResetQueryPoolError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + Self::OutOfRange => { + "the provided queries range is not valid for this pool" + } + } + ) + } +} diff --git a/src/command_buffer/validity/update_buffer.rs b/src/command_buffer/validity/update_buffer.rs new file mode 100644 index 0000000..39f60be --- /dev/null +++ b/src/command_buffer/validity/update_buffer.rs @@ -0,0 +1,181 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use crate::buffer::TypedBufferAccess; +use crate::device::Device; +use crate::device::DeviceOwned; +use crate::DeviceSize; +use crate::VulkanObject; +use std::cmp; +use std::error; +use std::fmt; +use std::mem; + +/// Checks whether an update buffer command is valid. +/// +/// # Panic +/// +/// - Panics if the buffer not created with `device`. +/// +pub fn check_update_buffer<B, D>( + device: &Device, + buffer: &B, + data: &D, +) -> Result<(), CheckUpdateBufferError> +where + B: ?Sized + TypedBufferAccess<Content = D>, + D: ?Sized, +{ + assert_eq!( + buffer.inner().buffer.device().internal_object(), + device.internal_object() + ); + + if !buffer.inner().buffer.usage().transfer_destination { + return Err(CheckUpdateBufferError::BufferMissingUsage); + } + + if buffer.inner().offset % 4 != 0 { + return Err(CheckUpdateBufferError::WrongAlignment); + } + + let size = cmp::min(buffer.size(), mem::size_of_val(data) as DeviceSize); + + if size % 4 != 0 { + return Err(CheckUpdateBufferError::WrongAlignment); + } + + if size > 65536 { + return Err(CheckUpdateBufferError::DataTooLarge); + } + + Ok(()) +} + +/// Error that can happen when attempting to add an `update_buffer` command. +#[derive(Debug, Copy, Clone)] +pub enum CheckUpdateBufferError { + /// The "transfer destination" usage must be enabled on the buffer. + BufferMissingUsage, + /// The data or size must be 4-bytes aligned. + WrongAlignment, + /// The data must not be larger than 64k bytes. + DataTooLarge, +} + +impl error::Error for CheckUpdateBufferError {} + +impl fmt::Display for CheckUpdateBufferError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckUpdateBufferError::BufferMissingUsage => { + "the transfer destination usage must be enabled on the buffer" + } + CheckUpdateBufferError::WrongAlignment => { + "the offset or size are not aligned to 4 bytes" + } + CheckUpdateBufferError::DataTooLarge => "data is too large", + } + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::buffer::BufferAccess; + use crate::buffer::BufferUsage; + use crate::buffer::CpuAccessibleBuffer; + + #[test] + fn missing_usage() { + let (device, queue) = gfx_dev_and_queue!(); + let buffer = CpuAccessibleBuffer::from_data( + device.clone(), + BufferUsage::vertex_buffer(), + false, + 0u32, + ) + .unwrap(); + + match check_update_buffer(&device, &buffer, &0) { + Err(CheckUpdateBufferError::BufferMissingUsage) => (), + _ => panic!(), + } + } + + #[test] + fn data_too_large() { + let (device, queue) = gfx_dev_and_queue!(); + let buffer = CpuAccessibleBuffer::from_iter( + device.clone(), + BufferUsage::transfer_destination(), + false, + 0..65536, + ) + .unwrap(); + let data = (0..65536).collect::<Vec<u32>>(); + + match check_update_buffer(&device, &buffer, &data[..]) { + Err(CheckUpdateBufferError::DataTooLarge) => (), + _ => panic!(), + } + } + + #[test] + fn data_just_large_enough() { + let (device, queue) = gfx_dev_and_queue!(); + let buffer = CpuAccessibleBuffer::from_iter( + device.clone(), + BufferUsage::transfer_destination(), + false, + (0..100000).map(|_| 0), + ) + .unwrap(); + let data = (0..65536).map(|_| 0).collect::<Vec<u8>>(); + + match check_update_buffer(&device, &buffer, &data[..]) { + Ok(_) => (), + _ => panic!(), + } + } + + #[test] + fn wrong_alignment() { + let (device, queue) = gfx_dev_and_queue!(); + let buffer = CpuAccessibleBuffer::from_iter( + device.clone(), + BufferUsage::transfer_destination(), + false, + 0..100, + ) + .unwrap(); + let data = (0..30).collect::<Vec<u8>>(); + + match check_update_buffer(&device, &buffer.slice(1..50).unwrap(), &data[..]) { + Err(CheckUpdateBufferError::WrongAlignment) => (), + _ => panic!(), + } + } + + #[test] + fn wrong_device() { + let (dev1, queue) = gfx_dev_and_queue!(); + let (dev2, _) = gfx_dev_and_queue!(); + let buffer = CpuAccessibleBuffer::from_data(dev1, BufferUsage::all(), false, 0u32).unwrap(); + + assert_should_panic!({ + let _ = check_update_buffer(&dev2, &buffer, &0); + }); + } +} diff --git a/src/command_buffer/validity/vertex_buffers.rs b/src/command_buffer/validity/vertex_buffers.rs new file mode 100644 index 0000000..9031498 --- /dev/null +++ b/src/command_buffer/validity/vertex_buffers.rs @@ -0,0 +1,118 @@ +// Copyright (c) 2017 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// <LICENSE-APACHE or +// https://www.apache.org/licenses/LICENSE-2.0> or the MIT +// license <LICENSE-MIT or https://opensource.org/licenses/MIT>, +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use std::error; +use std::fmt; + +use crate::buffer::BufferAccess; +use crate::device::DeviceOwned; +use crate::pipeline::vertex::VertexSource; +use crate::pipeline::GraphicsPipelineAbstract; +use crate::VulkanObject; + +/// Checks whether vertex buffers can be bound. +/// +/// # Panic +/// +/// - Panics if one of the vertex buffers was not created with the same device as `pipeline`. +/// +pub fn check_vertex_buffers<GP, V>( + pipeline: &GP, + vertex_buffers: V, +) -> Result<CheckVertexBuffer, CheckVertexBufferError> +where + GP: GraphicsPipelineAbstract + DeviceOwned + VertexSource<V>, +{ + let (vertex_buffers, vertex_count, instance_count) = pipeline.decode(vertex_buffers); + + for (num, buf) in vertex_buffers.iter().enumerate() { + assert_eq!( + buf.inner().buffer.device().internal_object(), + pipeline.device().internal_object() + ); + + if !buf.inner().buffer.usage().vertex_buffer { + return Err(CheckVertexBufferError::BufferMissingUsage { num_buffer: num }); + } + } + + if let Some(multiview) = pipeline.subpass().render_pass().desc().multiview() { + let max_instance_index = pipeline + .device() + .physical_device() + .properties() + .max_multiview_instance_index + .unwrap_or(0) as usize; + + // vulkano currently always uses `0` as the first instance which means the highest + // used index will just be `instance_count - 1` + if instance_count > max_instance_index + 1 { + return Err(CheckVertexBufferError::TooManyInstances { + instance_count, + max_instance_count: max_instance_index + 1, + }); + } + } + + Ok(CheckVertexBuffer { + vertex_buffers, + vertex_count: vertex_count as u32, + instance_count: instance_count as u32, + }) +} + +/// Information returned if `check_vertex_buffer` succeeds. +pub struct CheckVertexBuffer { + /// The list of vertex buffers. + pub vertex_buffers: Vec<Box<dyn BufferAccess + Send + Sync>>, + /// Number of vertices available in the intersection of the buffers. + pub vertex_count: u32, + /// Number of instances available in the intersection of the buffers. + pub instance_count: u32, +} + +/// Error that can happen when checking whether the vertex buffers are valid. +#[derive(Debug, Copy, Clone)] +pub enum CheckVertexBufferError { + /// The "vertex buffer" usage must be enabled on the buffer. + BufferMissingUsage { + /// Index of the buffer that is missing usage. + num_buffer: usize, + }, + + /// The vertex buffer has too many instances. + /// When the `multiview` feature is used the maximum amount of instances may be reduced + /// because the implementation may use instancing internally to implement `multiview`. + TooManyInstances { + /// The used amount of instances. + instance_count: usize, + /// The allowed amount of instances. + max_instance_count: usize, + }, +} + +impl error::Error for CheckVertexBufferError {} + +impl fmt::Display for CheckVertexBufferError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + CheckVertexBufferError::BufferMissingUsage { .. } => { + "the vertex buffer usage is missing on a vertex buffer" + } + CheckVertexBufferError::TooManyInstances { .. } => { + "the vertex buffer has too many instances" + } + } + ) + } +} |