aboutsummaryrefslogtreecommitdiff
path: root/src/command_buffer/validity
diff options
context:
space:
mode:
Diffstat (limited to 'src/command_buffer/validity')
-rw-r--r--src/command_buffer/validity/blit_image.rs302
-rw-r--r--src/command_buffer/validity/clear_color_image.rs81
-rw-r--r--src/command_buffer/validity/copy_buffer.rs103
-rw-r--r--src/command_buffer/validity/copy_image.rs243
-rw-r--r--src/command_buffer/validity/copy_image_buffer.rs266
-rw-r--r--src/command_buffer/validity/debug_marker.rs32
-rw-r--r--src/command_buffer/validity/descriptor_sets.rs109
-rw-r--r--src/command_buffer/validity/dispatch.rs88
-rw-r--r--src/command_buffer/validity/dynamic_state.rs215
-rw-r--r--src/command_buffer/validity/fill_buffer.rs105
-rw-r--r--src/command_buffer/validity/index_buffer.rs148
-rw-r--r--src/command_buffer/validity/indirect_buffer.rs72
-rw-r--r--src/command_buffer/validity/mod.rs50
-rw-r--r--src/command_buffer/validity/push_constants.rs52
-rw-r--r--src/command_buffer/validity/query.rs391
-rw-r--r--src/command_buffer/validity/update_buffer.rs181
-rw-r--r--src/command_buffer/validity/vertex_buffers.rs118
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"
+ }
+ }
+ )
+ }
+}