// Copyright (c) 2022 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::{view::BufferView, BufferUsage, Subbuffer},
    command_buffer::{
        allocator::CommandBufferAllocator,
        auto::{RenderPassState, RenderPassStateType},
        synced::{Command, Resource, SyncCommandBufferBuilder, SyncCommandBufferBuilderError},
        sys::UnsafeCommandBufferBuilder,
        AutoCommandBufferBuilder, DispatchIndirectCommand, DrawIndexedIndirectCommand,
        DrawIndirectCommand, ResourceInCommand, ResourceUseRef, SubpassContents,
    },
    descriptor_set::{layout::DescriptorType, DescriptorBindingResources},
    device::{DeviceOwned, QueueFlags},
    format::{Format, FormatFeatures},
    image::{
        view::ImageViewType, ImageAccess, ImageAspects, ImageSubresourceRange, ImageViewAbstract,
        SampleCount,
    },
    pipeline::{
        graphics::{
            input_assembly::{PrimitiveTopology, PrimitiveTopologyClass},
            render_pass::PipelineRenderPassType,
            vertex_input::VertexInputRate,
        },
        DynamicState, GraphicsPipeline, PartialStateMode, Pipeline, PipelineLayout,
    },
    sampler::{Sampler, SamplerImageViewIncompatibleError},
    shader::{DescriptorBindingRequirements, ShaderScalarType, ShaderStage},
    sync::{AccessFlags, PipelineMemoryAccess, PipelineStages},
    DeviceSize, RequiresOneOf, VulkanObject,
};
use std::{
    cmp::min,
    error::Error,
    fmt::{Display, Error as FmtError, Formatter},
    mem::size_of,
    ops::Range,
    sync::Arc,
};

/// # Commands to execute a bound pipeline.
///
/// Dispatch commands require a compute queue, draw commands require a graphics queue.
impl<L, A> AutoCommandBufferBuilder<L, A>
where
    A: CommandBufferAllocator,
{
    /// Perform a single compute operation using a compute pipeline.
    ///
    /// A compute pipeline must have been bound using
    /// [`bind_pipeline_compute`](Self::bind_pipeline_compute). Any resources used by the compute
    /// pipeline, such as descriptor sets, must have been set beforehand.
    pub fn dispatch(
        &mut self,
        group_counts: [u32; 3],
    ) -> Result<&mut Self, PipelineExecutionError> {
        self.validate_dispatch(group_counts)?;

        unsafe {
            self.inner.dispatch(group_counts)?;
        }

        Ok(self)
    }

    fn validate_dispatch(&self, group_counts: [u32; 3]) -> Result<(), PipelineExecutionError> {
        let queue_family_properties = self.queue_family_properties();

        // VUID-vkCmdDispatch-commandBuffer-cmdpool
        if !queue_family_properties
            .queue_flags
            .intersects(QueueFlags::COMPUTE)
        {
            return Err(PipelineExecutionError::NotSupportedByQueueFamily);
        }

        // VUID-vkCmdDispatch-renderpass
        if self.render_pass_state.is_some() {
            return Err(PipelineExecutionError::ForbiddenInsideRenderPass);
        }

        // VUID-vkCmdDispatch-None-02700
        let pipeline = match self.state().pipeline_compute() {
            Some(x) => x.as_ref(),
            None => return Err(PipelineExecutionError::PipelineNotBound),
        };

        self.validate_pipeline_descriptor_sets(pipeline)?;
        self.validate_pipeline_push_constants(pipeline.layout())?;

        let max = self
            .device()
            .physical_device()
            .properties()
            .max_compute_work_group_count;

        // VUID-vkCmdDispatch-groupCountX-00386
        // VUID-vkCmdDispatch-groupCountY-00387
        // VUID-vkCmdDispatch-groupCountZ-00388
        if group_counts[0] > max[0] || group_counts[1] > max[1] || group_counts[2] > max[2] {
            return Err(PipelineExecutionError::MaxComputeWorkGroupCountExceeded {
                requested: group_counts,
                max,
            });
        }

        Ok(())
    }

    /// Perform multiple compute operations using a compute pipeline. One dispatch is performed for
    /// each [`DispatchIndirectCommand`] struct in `indirect_buffer`.
    ///
    /// A compute pipeline must have been bound using
    /// [`bind_pipeline_compute`](Self::bind_pipeline_compute). Any resources used by the compute
    /// pipeline, such as descriptor sets, must have been set beforehand.
    pub fn dispatch_indirect(
        &mut self,
        indirect_buffer: Subbuffer<[DispatchIndirectCommand]>,
    ) -> Result<&mut Self, PipelineExecutionError> {
        self.validate_dispatch_indirect(indirect_buffer.as_bytes())?;

        unsafe {
            self.inner.dispatch_indirect(indirect_buffer)?;
        }

        Ok(self)
    }

    fn validate_dispatch_indirect(
        &self,
        indirect_buffer: &Subbuffer<[u8]>,
    ) -> Result<(), PipelineExecutionError> {
        let queue_family_properties = self.queue_family_properties();

        // VUID-vkCmdDispatchIndirect-commandBuffer-cmdpool
        if !queue_family_properties
            .queue_flags
            .intersects(QueueFlags::COMPUTE)
        {
            return Err(PipelineExecutionError::NotSupportedByQueueFamily);
        }

        // VUID-vkCmdDispatchIndirect-renderpass
        if self.render_pass_state.is_some() {
            return Err(PipelineExecutionError::ForbiddenInsideRenderPass);
        }

        // VUID-vkCmdDispatchIndirect-None-02700
        let pipeline = match self.state().pipeline_compute() {
            Some(x) => x.as_ref(),
            None => return Err(PipelineExecutionError::PipelineNotBound),
        };

        self.validate_pipeline_descriptor_sets(pipeline)?;
        self.validate_pipeline_push_constants(pipeline.layout())?;
        self.validate_indirect_buffer(indirect_buffer)?;

        Ok(())
    }

    /// Perform a single draw operation using a graphics pipeline.
    ///
    /// The parameters specify the first vertex and the number of vertices to draw, and the first
    /// instance and number of instances. For non-instanced drawing, specify `instance_count` as 1
    /// and `first_instance` as 0.
    ///
    /// A graphics pipeline must have been bound using
    /// [`bind_pipeline_graphics`](Self::bind_pipeline_graphics). Any resources used by the graphics
    /// pipeline, such as descriptor sets, vertex buffers and dynamic state, must have been set
    /// beforehand. If the bound graphics pipeline uses vertex buffers, then the provided vertex and
    /// instance ranges must be in range of the bound vertex buffers.
    pub fn draw(
        &mut self,
        vertex_count: u32,
        instance_count: u32,
        first_vertex: u32,
        first_instance: u32,
    ) -> Result<&mut Self, PipelineExecutionError> {
        self.validate_draw(vertex_count, instance_count, first_vertex, first_instance)?;

        unsafe {
            self.inner
                .draw(vertex_count, instance_count, first_vertex, first_instance)?;
        }

        if let RenderPassStateType::BeginRendering(state) =
            &mut self.render_pass_state.as_mut().unwrap().render_pass
        {
            state.pipeline_used = true;
        }

        Ok(self)
    }

    fn validate_draw(
        &self,
        vertex_count: u32,
        instance_count: u32,
        first_vertex: u32,
        first_instance: u32,
    ) -> Result<(), PipelineExecutionError> {
        // VUID-vkCmdDraw-renderpass
        let render_pass_state = self
            .render_pass_state
            .as_ref()
            .ok_or(PipelineExecutionError::ForbiddenOutsideRenderPass)?;

        // VUID-vkCmdDraw-None-02700
        let pipeline = match self.state().pipeline_graphics() {
            Some(x) => x.as_ref(),
            None => return Err(PipelineExecutionError::PipelineNotBound),
        };

        self.validate_pipeline_descriptor_sets(pipeline)?;
        self.validate_pipeline_push_constants(pipeline.layout())?;
        self.validate_pipeline_graphics_dynamic_state(pipeline)?;
        self.validate_pipeline_graphics_render_pass(pipeline, render_pass_state)?;
        self.validate_pipeline_graphics_vertex_buffers(
            pipeline,
            Some((first_vertex, vertex_count)),
            Some((first_instance, instance_count)),
        )?;

        Ok(())
    }

    /// Perform multiple draw operations using a graphics pipeline.
    ///
    /// One draw is performed for each [`DrawIndirectCommand`] struct in `indirect_buffer`.
    /// The maximum number of draw commands in the buffer is limited by the
    /// [`max_draw_indirect_count`](crate::device::Properties::max_draw_indirect_count) limit.
    /// This limit is 1 unless the
    /// [`multi_draw_indirect`](crate::device::Features::multi_draw_indirect) feature has been
    /// enabled.
    ///
    /// A graphics pipeline must have been bound using
    /// [`bind_pipeline_graphics`](Self::bind_pipeline_graphics). Any resources used by the graphics
    /// pipeline, such as descriptor sets, vertex buffers and dynamic state, must have been set
    /// beforehand. If the bound graphics pipeline uses vertex buffers, then the vertex and instance
    /// ranges of each `DrawIndirectCommand` in the indirect buffer must be in range of the bound
    /// vertex buffers.
    pub fn draw_indirect(
        &mut self,
        indirect_buffer: Subbuffer<[DrawIndirectCommand]>,
    ) -> Result<&mut Self, PipelineExecutionError> {
        let draw_count = indirect_buffer.len() as u32;
        let stride = size_of::<DrawIndirectCommand>() as u32;
        self.validate_draw_indirect(indirect_buffer.as_bytes(), draw_count, stride)?;

        unsafe {
            self.inner
                .draw_indirect(indirect_buffer, draw_count, stride)?;
        }

        if let RenderPassStateType::BeginRendering(state) =
            &mut self.render_pass_state.as_mut().unwrap().render_pass
        {
            state.pipeline_used = true;
        }

        Ok(self)
    }

    fn validate_draw_indirect(
        &self,
        indirect_buffer: &Subbuffer<[u8]>,
        draw_count: u32,
        _stride: u32,
    ) -> Result<(), PipelineExecutionError> {
        // VUID-vkCmdDrawIndirect-renderpass
        let render_pass_state = self
            .render_pass_state
            .as_ref()
            .ok_or(PipelineExecutionError::ForbiddenOutsideRenderPass)?;

        // VUID-vkCmdDrawIndirect-None-02700
        let pipeline = match self.state().pipeline_graphics() {
            Some(x) => x.as_ref(),
            None => return Err(PipelineExecutionError::PipelineNotBound),
        };

        self.validate_pipeline_descriptor_sets(pipeline)?;
        self.validate_pipeline_push_constants(pipeline.layout())?;
        self.validate_pipeline_graphics_dynamic_state(pipeline)?;
        self.validate_pipeline_graphics_render_pass(pipeline, render_pass_state)?;
        self.validate_pipeline_graphics_vertex_buffers(pipeline, None, None)?;

        self.validate_indirect_buffer(indirect_buffer)?;

        // VUID-vkCmdDrawIndirect-drawCount-02718
        if draw_count > 1 && !self.device().enabled_features().multi_draw_indirect {
            return Err(PipelineExecutionError::RequirementNotMet {
                required_for: "`draw_count` is greater than `1`",
                requires_one_of: RequiresOneOf {
                    features: &["multi_draw_indirect"],
                    ..Default::default()
                },
            });
        }

        let max = self
            .device()
            .physical_device()
            .properties()
            .max_draw_indirect_count;

        // VUID-vkCmdDrawIndirect-drawCount-02719
        if draw_count > max {
            return Err(PipelineExecutionError::MaxDrawIndirectCountExceeded {
                provided: draw_count,
                max,
            });
        }

        Ok(())
    }

    /// Perform a single draw operation using a graphics pipeline, using an index buffer.
    ///
    /// The parameters specify the first index and the number of indices in the index buffer that
    /// should be used, and the first instance and number of instances. For non-instanced drawing,
    /// specify `instance_count` as 1 and `first_instance` as 0. The `vertex_offset` is a constant
    /// value that should be added to each index in the index buffer to produce the final vertex
    /// number to be used.
    ///
    /// An index buffer must have been bound using
    /// [`bind_index_buffer`](Self::bind_index_buffer), and the provided index range must be in
    /// range of the bound index buffer.
    ///
    /// A graphics pipeline must have been bound using
    /// [`bind_pipeline_graphics`](Self::bind_pipeline_graphics). Any resources used by the graphics
    /// pipeline, such as descriptor sets, vertex buffers and dynamic state, must have been set
    /// beforehand. If the bound graphics pipeline uses vertex buffers, then the provided instance
    /// range must be in range of the bound vertex buffers. The vertex indices in the index buffer
    /// must be in range of the bound vertex buffers.
    pub fn draw_indexed(
        &mut self,
        index_count: u32,
        instance_count: u32,
        first_index: u32,
        vertex_offset: i32,
        first_instance: u32,
    ) -> Result<&mut Self, PipelineExecutionError> {
        self.validate_draw_indexed(
            index_count,
            instance_count,
            first_index,
            vertex_offset,
            first_instance,
        )?;

        unsafe {
            self.inner.draw_indexed(
                index_count,
                instance_count,
                first_index,
                vertex_offset,
                first_instance,
            )?;
        }

        if let RenderPassStateType::BeginRendering(state) =
            &mut self.render_pass_state.as_mut().unwrap().render_pass
        {
            state.pipeline_used = true;
        }

        Ok(self)
    }

    fn validate_draw_indexed(
        &self,
        index_count: u32,
        instance_count: u32,
        first_index: u32,
        _vertex_offset: i32,
        first_instance: u32,
    ) -> Result<(), PipelineExecutionError> {
        // TODO: how to handle an index out of range of the vertex buffers?

        // VUID-vkCmdDrawIndexed-renderpass
        let render_pass_state = self
            .render_pass_state
            .as_ref()
            .ok_or(PipelineExecutionError::ForbiddenOutsideRenderPass)?;

        // VUID-vkCmdDrawIndexed-None-02700
        let pipeline = match self.state().pipeline_graphics() {
            Some(x) => x.as_ref(),
            None => return Err(PipelineExecutionError::PipelineNotBound),
        };

        self.validate_pipeline_descriptor_sets(pipeline)?;
        self.validate_pipeline_push_constants(pipeline.layout())?;
        self.validate_pipeline_graphics_dynamic_state(pipeline)?;
        self.validate_pipeline_graphics_render_pass(pipeline, render_pass_state)?;
        self.validate_pipeline_graphics_vertex_buffers(
            pipeline,
            None,
            Some((first_instance, instance_count)),
        )?;

        self.validate_index_buffer(Some((first_index, index_count)))?;

        Ok(())
    }

    /// Perform multiple draw operations using a graphics pipeline, using an index buffer.
    ///
    /// One draw is performed for each [`DrawIndexedIndirectCommand`] struct in `indirect_buffer`.
    /// The maximum number of draw commands in the buffer is limited by the
    /// [`max_draw_indirect_count`](crate::device::Properties::max_draw_indirect_count) limit.
    /// This limit is 1 unless the
    /// [`multi_draw_indirect`](crate::device::Features::multi_draw_indirect) feature has been
    /// enabled.
    ///
    /// An index buffer must have been bound using
    /// [`bind_index_buffer`](Self::bind_index_buffer), and the index ranges of each
    /// `DrawIndexedIndirectCommand` in the indirect buffer must be in range of the bound index
    /// buffer.
    ///
    /// A graphics pipeline must have been bound using
    /// [`bind_pipeline_graphics`](Self::bind_pipeline_graphics). Any resources used by the graphics
    /// pipeline, such as descriptor sets, vertex buffers and dynamic state, must have been set
    /// beforehand. If the bound graphics pipeline uses vertex buffers, then the instance ranges of
    /// each `DrawIndexedIndirectCommand` in the indirect buffer must be in range of the bound
    /// vertex buffers.
    pub fn draw_indexed_indirect(
        &mut self,
        indirect_buffer: Subbuffer<[DrawIndexedIndirectCommand]>,
    ) -> Result<&mut Self, PipelineExecutionError> {
        let draw_count = indirect_buffer.len() as u32;
        let stride = size_of::<DrawIndexedIndirectCommand>() as u32;
        self.validate_draw_indexed_indirect(indirect_buffer.as_bytes(), draw_count, stride)?;

        unsafe {
            self.inner
                .draw_indexed_indirect(indirect_buffer, draw_count, stride)?;
        }

        if let RenderPassStateType::BeginRendering(state) =
            &mut self.render_pass_state.as_mut().unwrap().render_pass
        {
            state.pipeline_used = true;
        }

        Ok(self)
    }

    fn validate_draw_indexed_indirect(
        &self,
        indirect_buffer: &Subbuffer<[u8]>,
        draw_count: u32,
        _stride: u32,
    ) -> Result<(), PipelineExecutionError> {
        // VUID-vkCmdDrawIndexedIndirect-renderpass
        let render_pass_state = self
            .render_pass_state
            .as_ref()
            .ok_or(PipelineExecutionError::ForbiddenOutsideRenderPass)?;

        // VUID-vkCmdDrawIndexedIndirect-None-02700
        let pipeline = match self.state().pipeline_graphics() {
            Some(x) => x.as_ref(),
            None => return Err(PipelineExecutionError::PipelineNotBound),
        };

        self.validate_pipeline_descriptor_sets(pipeline)?;
        self.validate_pipeline_push_constants(pipeline.layout())?;
        self.validate_pipeline_graphics_dynamic_state(pipeline)?;
        self.validate_pipeline_graphics_render_pass(pipeline, render_pass_state)?;
        self.validate_pipeline_graphics_vertex_buffers(pipeline, None, None)?;

        self.validate_index_buffer(None)?;
        self.validate_indirect_buffer(indirect_buffer)?;

        // VUID-vkCmdDrawIndexedIndirect-drawCount-02718
        if draw_count > 1 && !self.device().enabled_features().multi_draw_indirect {
            return Err(PipelineExecutionError::RequirementNotMet {
                required_for: "`draw_count` is greater than `1`",
                requires_one_of: RequiresOneOf {
                    features: &["multi_draw_indirect"],
                    ..Default::default()
                },
            });
        }

        let max = self
            .device()
            .physical_device()
            .properties()
            .max_draw_indirect_count;

        // VUID-vkCmdDrawIndexedIndirect-drawCount-02719
        if draw_count > max {
            return Err(PipelineExecutionError::MaxDrawIndirectCountExceeded {
                provided: draw_count,
                max,
            });
        }

        Ok(())
    }

    fn validate_index_buffer(
        &self,
        indices: Option<(u32, u32)>,
    ) -> Result<(), PipelineExecutionError> {
        let current_state = self.state();

        // VUID?
        let (index_buffer, index_type) = match current_state.index_buffer() {
            Some(x) => x,
            None => return Err(PipelineExecutionError::IndexBufferNotBound),
        };

        if let Some((first_index, index_count)) = indices {
            let max_index_count = (index_buffer.size() / index_type.size()) as u32;

            // // VUID-vkCmdDrawIndexed-firstIndex-04932
            if first_index + index_count > max_index_count {
                return Err(PipelineExecutionError::IndexBufferRangeOutOfBounds {
                    highest_index: first_index + index_count,
                    max_index_count,
                });
            }
        }

        Ok(())
    }

    fn validate_indirect_buffer(
        &self,
        buffer: &Subbuffer<[u8]>,
    ) -> Result<(), PipelineExecutionError> {
        // VUID-vkCmdDispatchIndirect-commonparent
        assert_eq!(self.device(), buffer.device());

        // VUID-vkCmdDispatchIndirect-buffer-02709
        if !buffer
            .buffer()
            .usage()
            .intersects(BufferUsage::INDIRECT_BUFFER)
        {
            return Err(PipelineExecutionError::IndirectBufferMissingUsage);
        }

        // VUID-vkCmdDispatchIndirect-offset-02710
        // TODO:

        Ok(())
    }

    fn validate_pipeline_descriptor_sets<Pl: Pipeline>(
        &self,
        pipeline: &Pl,
    ) -> Result<(), PipelineExecutionError> {
        fn validate_resources<T>(
            set_num: u32,
            binding_num: u32,
            binding_reqs: &DescriptorBindingRequirements,
            elements: &[Option<T>],
            mut extra_check: impl FnMut(u32, &T) -> Result<(), DescriptorResourceInvalidError>,
        ) -> Result<(), PipelineExecutionError> {
            let elements_to_check = if let Some(descriptor_count) = binding_reqs.descriptor_count {
                // The shader has a fixed-sized array, so it will never access more than
                // the first `descriptor_count` elements.
                elements.get(..descriptor_count as usize).ok_or({
                    // There are less than `descriptor_count` elements in `elements`
                    PipelineExecutionError::DescriptorResourceInvalid {
                        set_num,
                        binding_num,
                        index: elements.len() as u32,
                        error: DescriptorResourceInvalidError::Missing,
                    }
                })?
            } else {
                // The shader has a runtime-sized array, so any element could potentially
                // be accessed. We must check them all.
                elements
            };

            for (index, element) in elements_to_check.iter().enumerate() {
                let index = index as u32;

                // VUID-vkCmdDispatch-None-02699
                let element = match element {
                    Some(x) => x,
                    None => {
                        return Err(PipelineExecutionError::DescriptorResourceInvalid {
                            set_num,
                            binding_num,
                            index,
                            error: DescriptorResourceInvalidError::Missing,
                        })
                    }
                };

                if let Err(error) = extra_check(index, element) {
                    return Err(PipelineExecutionError::DescriptorResourceInvalid {
                        set_num,
                        binding_num,
                        index,
                        error,
                    });
                }
            }

            Ok(())
        }

        if pipeline.num_used_descriptor_sets() == 0 {
            return Ok(());
        }

        let current_state = self.state();

        // VUID-vkCmdDispatch-None-02697
        let bindings_pipeline_layout =
            match current_state.descriptor_sets_pipeline_layout(pipeline.bind_point()) {
                Some(x) => x,
                None => return Err(PipelineExecutionError::PipelineLayoutNotCompatible),
            };

        // VUID-vkCmdDispatch-None-02697
        if !pipeline.layout().is_compatible_with(
            bindings_pipeline_layout,
            pipeline.num_used_descriptor_sets(),
        ) {
            return Err(PipelineExecutionError::PipelineLayoutNotCompatible);
        }

        for (&(set_num, binding_num), binding_reqs) in pipeline.descriptor_binding_requirements() {
            let layout_binding =
                &pipeline.layout().set_layouts()[set_num as usize].bindings()[&binding_num];

            let check_buffer =
                |_index: u32, (_buffer, _range): &(Subbuffer<[u8]>, Range<DeviceSize>)| Ok(());

            let check_buffer_view = |index: u32, buffer_view: &Arc<BufferView>| {
                for desc_reqs in (binding_reqs.descriptors.get(&Some(index)).into_iter())
                    .chain(binding_reqs.descriptors.get(&None))
                {
                    if layout_binding.descriptor_type == DescriptorType::StorageTexelBuffer {
                        // VUID-vkCmdDispatch-OpTypeImage-06423
                        if binding_reqs.image_format.is_none()
                            && !desc_reqs.memory_write.is_empty()
                            && !buffer_view
                                .format_features()
                                .intersects(FormatFeatures::STORAGE_WRITE_WITHOUT_FORMAT)
                        {
                            return Err(DescriptorResourceInvalidError::StorageWriteWithoutFormatNotSupported);
                        }

                        // VUID-vkCmdDispatch-OpTypeImage-06424
                        if binding_reqs.image_format.is_none()
                            && !desc_reqs.memory_read.is_empty()
                            && !buffer_view
                                .format_features()
                                .intersects(FormatFeatures::STORAGE_READ_WITHOUT_FORMAT)
                        {
                            return Err(DescriptorResourceInvalidError::StorageReadWithoutFormatNotSupported);
                        }
                    }
                }

                Ok(())
            };

            let check_image_view_common = |index: u32, image_view: &Arc<dyn ImageViewAbstract>| {
                for desc_reqs in (binding_reqs.descriptors.get(&Some(index)).into_iter())
                    .chain(binding_reqs.descriptors.get(&None))
                {
                    // VUID-vkCmdDispatch-None-02691
                    if desc_reqs.storage_image_atomic
                        && !image_view
                            .format_features()
                            .intersects(FormatFeatures::STORAGE_IMAGE_ATOMIC)
                    {
                        return Err(DescriptorResourceInvalidError::StorageImageAtomicNotSupported);
                    }

                    if layout_binding.descriptor_type == DescriptorType::StorageImage {
                        // VUID-vkCmdDispatch-OpTypeImage-06423
                        if binding_reqs.image_format.is_none()
                            && !desc_reqs.memory_write.is_empty()
                            && !image_view
                                .format_features()
                                .intersects(FormatFeatures::STORAGE_WRITE_WITHOUT_FORMAT)
                        {
                            return Err(
                            DescriptorResourceInvalidError::StorageWriteWithoutFormatNotSupported,
                        );
                        }

                        // VUID-vkCmdDispatch-OpTypeImage-06424
                        if binding_reqs.image_format.is_none()
                            && !desc_reqs.memory_read.is_empty()
                            && !image_view
                                .format_features()
                                .intersects(FormatFeatures::STORAGE_READ_WITHOUT_FORMAT)
                        {
                            return Err(
                            DescriptorResourceInvalidError::StorageReadWithoutFormatNotSupported,
                        );
                        }
                    }
                }

                /*
                   Instruction/Sampler/Image View Validation
                   https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap16.html#textures-input-validation
                */

                // The SPIR-V Image Format is not compatible with the image view’s format.
                if let Some(format) = binding_reqs.image_format {
                    if image_view.format() != Some(format) {
                        return Err(DescriptorResourceInvalidError::ImageViewFormatMismatch {
                            required: format,
                            provided: image_view.format(),
                        });
                    }
                }

                // Rules for viewType
                if let Some(image_view_type) = binding_reqs.image_view_type {
                    if image_view.view_type() != image_view_type {
                        return Err(DescriptorResourceInvalidError::ImageViewTypeMismatch {
                            required: image_view_type,
                            provided: image_view.view_type(),
                        });
                    }
                }

                // - If the image was created with VkImageCreateInfo::samples equal to
                //   VK_SAMPLE_COUNT_1_BIT, the instruction must have MS = 0.
                // - If the image was created with VkImageCreateInfo::samples not equal to
                //   VK_SAMPLE_COUNT_1_BIT, the instruction must have MS = 1.
                if binding_reqs.image_multisampled
                    != (image_view.image().samples() != SampleCount::Sample1)
                {
                    return Err(
                        DescriptorResourceInvalidError::ImageViewMultisampledMismatch {
                            required: binding_reqs.image_multisampled,
                            provided: image_view.image().samples() != SampleCount::Sample1,
                        },
                    );
                }

                // - If the Sampled Type of the OpTypeImage does not match the numeric format of the
                //   image, as shown in the SPIR-V Sampled Type column of the
                //   Interpretation of Numeric Format table.
                // - If the signedness of any read or sample operation does not match the signedness of
                //   the image’s format.
                if let Some(scalar_type) = binding_reqs.image_scalar_type {
                    let aspects = image_view.subresource_range().aspects;
                    let view_scalar_type = ShaderScalarType::from(
                        if aspects.intersects(
                            ImageAspects::COLOR
                                | ImageAspects::PLANE_0
                                | ImageAspects::PLANE_1
                                | ImageAspects::PLANE_2,
                        ) {
                            image_view.format().unwrap().type_color().unwrap()
                        } else if aspects.intersects(ImageAspects::DEPTH) {
                            image_view.format().unwrap().type_depth().unwrap()
                        } else if aspects.intersects(ImageAspects::STENCIL) {
                            image_view.format().unwrap().type_stencil().unwrap()
                        } else {
                            // Per `ImageViewBuilder::aspects` and
                            // VUID-VkDescriptorImageInfo-imageView-01976
                            unreachable!()
                        },
                    );

                    if scalar_type != view_scalar_type {
                        return Err(
                            DescriptorResourceInvalidError::ImageViewScalarTypeMismatch {
                                required: scalar_type,
                                provided: view_scalar_type,
                            },
                        );
                    }
                }

                Ok(())
            };

            let check_sampler_common = |index: u32, sampler: &Arc<Sampler>| {
                for desc_reqs in (binding_reqs.descriptors.get(&Some(index)).into_iter())
                    .chain(binding_reqs.descriptors.get(&None))
                {
                    // VUID-vkCmdDispatch-None-02703
                    // VUID-vkCmdDispatch-None-02704
                    if desc_reqs.sampler_no_unnormalized_coordinates
                        && sampler.unnormalized_coordinates()
                    {
                        return Err(
                        DescriptorResourceInvalidError::SamplerUnnormalizedCoordinatesNotAllowed,
                    );
                    }

                    // - OpImageFetch, OpImageSparseFetch, OpImage*Gather, and OpImageSparse*Gather must not
                    //   be used with a sampler that enables sampler Y′CBCR conversion.
                    // - The ConstOffset and Offset operands must not be used with a sampler that enables
                    //   sampler Y′CBCR conversion.
                    if desc_reqs.sampler_no_ycbcr_conversion
                        && sampler.sampler_ycbcr_conversion().is_some()
                    {
                        return Err(
                            DescriptorResourceInvalidError::SamplerYcbcrConversionNotAllowed,
                        );
                    }

                    /*
                        Instruction/Sampler/Image View Validation
                        https://registry.khronos.org/vulkan/specs/1.3-extensions/html/chap16.html#textures-input-validation
                    */

                    // - The SPIR-V instruction is one of the OpImage*Dref* instructions and the sampler
                    //   compareEnable is VK_FALSE
                    // - The SPIR-V instruction is not one of the OpImage*Dref* instructions and the sampler
                    //   compareEnable is VK_TRUE
                    if desc_reqs.sampler_compare != sampler.compare().is_some() {
                        return Err(DescriptorResourceInvalidError::SamplerCompareMismatch {
                            required: desc_reqs.sampler_compare,
                            provided: sampler.compare().is_some(),
                        });
                    }
                }

                Ok(())
            };

            let check_image_view = |index: u32, image_view: &Arc<dyn ImageViewAbstract>| {
                check_image_view_common(index, image_view)?;

                if let Some(sampler) = layout_binding.immutable_samplers.get(index as usize) {
                    check_sampler_common(index, sampler)?;
                }

                Ok(())
            };

            let check_image_view_sampler =
                |index: u32, (image_view, sampler): &(Arc<dyn ImageViewAbstract>, Arc<Sampler>)| {
                    check_image_view_common(index, image_view)?;
                    check_sampler_common(index, sampler)?;

                    Ok(())
                };

            let check_sampler = |index: u32, sampler: &Arc<Sampler>| {
                check_sampler_common(index, sampler)?;

                for desc_reqs in (binding_reqs.descriptors.get(&Some(index)).into_iter())
                    .chain(binding_reqs.descriptors.get(&None))
                {
                    // Check sampler-image compatibility. Only done for separate samplers;
                    // combined image samplers are checked when updating the descriptor set.

                    // If the image view isn't actually present in the resources, then just skip it.
                    // It will be caught later by check_resources.
                    let iter = desc_reqs.sampler_with_images.iter().filter_map(|id| {
                        current_state
                            .descriptor_set(pipeline.bind_point(), id.set)
                            .and_then(|set| set.resources().binding(id.binding))
                            .and_then(|res| match res {
                                DescriptorBindingResources::ImageView(elements) => elements
                                    .get(id.index as usize)
                                    .and_then(|opt| opt.as_ref().map(|opt| (id, opt))),
                                _ => None,
                            })
                    });

                    for (id, image_view) in iter {
                        if let Err(error) = sampler.check_can_sample(image_view.as_ref()) {
                            return Err(
                                DescriptorResourceInvalidError::SamplerImageViewIncompatible {
                                    image_view_set_num: id.set,
                                    image_view_binding_num: id.binding,
                                    image_view_index: id.index,
                                    error,
                                },
                            );
                        }
                    }
                }

                Ok(())
            };

            let check_none = |index: u32, _: &()| {
                if let Some(sampler) = layout_binding.immutable_samplers.get(index as usize) {
                    check_sampler(index, sampler)?;
                }

                Ok(())
            };

            let set_resources = match current_state.descriptor_set(pipeline.bind_point(), set_num) {
                Some(x) => x.resources(),
                None => return Err(PipelineExecutionError::DescriptorSetNotBound { set_num }),
            };

            let binding_resources = set_resources.binding(binding_num).unwrap();

            match binding_resources {
                DescriptorBindingResources::None(elements) => {
                    validate_resources(set_num, binding_num, binding_reqs, elements, check_none)?;
                }
                DescriptorBindingResources::Buffer(elements) => {
                    validate_resources(set_num, binding_num, binding_reqs, elements, check_buffer)?;
                }
                DescriptorBindingResources::BufferView(elements) => {
                    validate_resources(
                        set_num,
                        binding_num,
                        binding_reqs,
                        elements,
                        check_buffer_view,
                    )?;
                }
                DescriptorBindingResources::ImageView(elements) => {
                    validate_resources(
                        set_num,
                        binding_num,
                        binding_reqs,
                        elements,
                        check_image_view,
                    )?;
                }
                DescriptorBindingResources::ImageViewSampler(elements) => {
                    validate_resources(
                        set_num,
                        binding_num,
                        binding_reqs,
                        elements,
                        check_image_view_sampler,
                    )?;
                }
                DescriptorBindingResources::Sampler(elements) => {
                    validate_resources(
                        set_num,
                        binding_num,
                        binding_reqs,
                        elements,
                        check_sampler,
                    )?;
                }
            }
        }

        Ok(())
    }

    fn validate_pipeline_push_constants(
        &self,
        pipeline_layout: &PipelineLayout,
    ) -> Result<(), PipelineExecutionError> {
        if pipeline_layout.push_constant_ranges().is_empty()
            || self.device().enabled_features().maintenance4
        {
            return Ok(());
        }

        let current_state = self.state();

        // VUID-vkCmdDispatch-maintenance4-06425
        let constants_pipeline_layout = match current_state.push_constants_pipeline_layout() {
            Some(x) => x,
            None => return Err(PipelineExecutionError::PushConstantsMissing),
        };

        // VUID-vkCmdDispatch-maintenance4-06425
        if pipeline_layout.handle() != constants_pipeline_layout.handle()
            && pipeline_layout.push_constant_ranges()
                != constants_pipeline_layout.push_constant_ranges()
        {
            return Err(PipelineExecutionError::PushConstantsNotCompatible);
        }

        let set_bytes = current_state.push_constants();

        // VUID-vkCmdDispatch-maintenance4-06425
        if !pipeline_layout
            .push_constant_ranges()
            .iter()
            .all(|pc_range| set_bytes.contains(pc_range.offset..pc_range.offset + pc_range.size))
        {
            return Err(PipelineExecutionError::PushConstantsMissing);
        }

        Ok(())
    }

    fn validate_pipeline_graphics_dynamic_state(
        &self,
        pipeline: &GraphicsPipeline,
    ) -> Result<(), PipelineExecutionError> {
        let device = pipeline.device();
        let current_state = self.state();

        // VUID-vkCmdDraw-commandBuffer-02701
        for dynamic_state in pipeline
            .dynamic_states()
            .filter(|(_, d)| *d)
            .map(|(s, _)| s)
        {
            match dynamic_state {
                DynamicState::BlendConstants => {
                    // VUID?
                    if current_state.blend_constants().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::ColorWriteEnable => {
                    // VUID-vkCmdDraw-attachmentCount-06667
                    let enables = if let Some(enables) = current_state.color_write_enable() {
                        enables
                    } else {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    };

                    // VUID-vkCmdDraw-attachmentCount-06667
                    if enables.len() < pipeline.color_blend_state().unwrap().attachments.len() {
                        return Err(
                            PipelineExecutionError::DynamicColorWriteEnableNotEnoughValues {
                                color_write_enable_count: enables.len() as u32,
                                attachment_count: pipeline
                                    .color_blend_state()
                                    .unwrap()
                                    .attachments
                                    .len() as u32,
                            },
                        );
                    }
                }
                DynamicState::CullMode => {
                    // VUID?
                    if current_state.cull_mode().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::DepthBias => {
                    // VUID?
                    if current_state.depth_bias().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::DepthBiasEnable => {
                    // VUID-vkCmdDraw-None-04877
                    if current_state.depth_bias_enable().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::DepthBounds => {
                    // VUID?
                    if current_state.depth_bounds().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::DepthBoundsTestEnable => {
                    // VUID?
                    if current_state.depth_bounds_test_enable().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::DepthCompareOp => {
                    // VUID?
                    if current_state.depth_compare_op().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::DepthTestEnable => {
                    // VUID?
                    if current_state.depth_test_enable().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::DepthWriteEnable => {
                    // VUID?
                    if current_state.depth_write_enable().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }

                    // TODO: Check if the depth buffer is writable
                }
                DynamicState::DiscardRectangle => {
                    let discard_rectangle_count =
                        match pipeline.discard_rectangle_state().unwrap().rectangles {
                            PartialStateMode::Dynamic(count) => count,
                            _ => unreachable!(),
                        };

                    for num in 0..discard_rectangle_count {
                        // VUID?
                        if current_state.discard_rectangle(num).is_none() {
                            return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                        }
                    }
                }
                DynamicState::ExclusiveScissor => todo!(),
                DynamicState::FragmentShadingRate => todo!(),
                DynamicState::FrontFace => {
                    // VUID?
                    if current_state.front_face().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::LineStipple => {
                    // VUID?
                    if current_state.line_stipple().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::LineWidth => {
                    // VUID?
                    if current_state.line_width().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::LogicOp => {
                    // VUID-vkCmdDraw-logicOp-04878
                    if current_state.logic_op().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::PatchControlPoints => {
                    // VUID-vkCmdDraw-None-04875
                    if current_state.patch_control_points().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::PrimitiveRestartEnable => {
                    // VUID-vkCmdDraw-None-04879
                    let primitive_restart_enable =
                        if let Some(enable) = current_state.primitive_restart_enable() {
                            enable
                        } else {
                            return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                        };

                    if primitive_restart_enable {
                        let topology = match pipeline.input_assembly_state().topology {
                            PartialStateMode::Fixed(topology) => topology,
                            PartialStateMode::Dynamic(_) => {
                                if let Some(topology) = current_state.primitive_topology() {
                                    topology
                                } else {
                                    return Err(PipelineExecutionError::DynamicStateNotSet {
                                        dynamic_state: DynamicState::PrimitiveTopology,
                                    });
                                }
                            }
                        };

                        match topology {
                            PrimitiveTopology::PointList
                            | PrimitiveTopology::LineList
                            | PrimitiveTopology::TriangleList
                            | PrimitiveTopology::LineListWithAdjacency
                            | PrimitiveTopology::TriangleListWithAdjacency => {
                                // VUID?
                                if !device.enabled_features().primitive_topology_list_restart {
                                    return Err(PipelineExecutionError::RequirementNotMet {
                                        required_for: "The bound pipeline sets \
                                            `DynamicState::PrimitiveRestartEnable` and the \
                                            current primitive topology is \
                                            `PrimitiveTopology::*List`",
                                        requires_one_of: RequiresOneOf {
                                            features: &["primitive_topology_list_restart"],
                                            ..Default::default()
                                        },
                                    });
                                }
                            }
                            PrimitiveTopology::PatchList => {
                                // VUID?
                                if !device
                                    .enabled_features()
                                    .primitive_topology_patch_list_restart
                                {
                                    return Err(PipelineExecutionError::RequirementNotMet {
                                        required_for: "The bound pipeline sets \
                                            `DynamicState::PrimitiveRestartEnable` and the \
                                            current primitive topology is \
                                            `PrimitiveTopology::PatchList`",
                                        requires_one_of: RequiresOneOf {
                                            features: &["primitive_topology_patch_list_restart"],
                                            ..Default::default()
                                        },
                                    });
                                }
                            }
                            _ => (),
                        }
                    }
                }
                DynamicState::PrimitiveTopology => {
                    // VUID-vkCmdDraw-primitiveTopology-03420
                    let topology = if let Some(topology) = current_state.primitive_topology() {
                        topology
                    } else {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    };

                    if pipeline.shader(ShaderStage::TessellationControl).is_some() {
                        // VUID?
                        if !matches!(topology, PrimitiveTopology::PatchList) {
                            return Err(PipelineExecutionError::DynamicPrimitiveTopologyInvalid {
                                topology,
                            });
                        }
                    } else {
                        // VUID?
                        if matches!(topology, PrimitiveTopology::PatchList) {
                            return Err(PipelineExecutionError::DynamicPrimitiveTopologyInvalid {
                                topology,
                            });
                        }
                    }

                    let required_topology_class = match pipeline.input_assembly_state().topology {
                        PartialStateMode::Dynamic(topology_class) => topology_class,
                        _ => unreachable!(),
                    };

                    // VUID-vkCmdDraw-primitiveTopology-03420
                    if topology.class() != required_topology_class {
                        return Err(
                            PipelineExecutionError::DynamicPrimitiveTopologyClassMismatch {
                                provided_class: topology.class(),
                                required_class: required_topology_class,
                            },
                        );
                    }

                    // TODO: check that the topology matches the geometry shader
                }
                DynamicState::RasterizerDiscardEnable => {
                    // VUID-vkCmdDraw-None-04876
                    if current_state.rasterizer_discard_enable().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::RayTracingPipelineStackSize => unreachable!(
                    "RayTracingPipelineStackSize dynamic state should not occur on a graphics pipeline"
                ),
                DynamicState::SampleLocations => todo!(),
                DynamicState::Scissor => {
                    for num in 0..pipeline.viewport_state().unwrap().count().unwrap() {
                        // VUID?
                        if current_state.scissor(num).is_none() {
                            return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                        }
                    }
                }
                DynamicState::ScissorWithCount => {
                    // VUID-vkCmdDraw-scissorCount-03418
                    // VUID-vkCmdDraw-viewportCount-03419
                    let scissor_count = if let Some(scissors) = current_state.scissor_with_count() {
                        scissors.len() as u32
                    } else {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    };

                    // Check if the counts match, but only if the viewport count is fixed.
                    // If the viewport count is also dynamic, then the
                    // DynamicState::ViewportWithCount match arm will handle it.
                    if let Some(viewport_count) = pipeline.viewport_state().unwrap().count() {
                        // VUID-vkCmdDraw-scissorCount-03418
                        if viewport_count != scissor_count {
                            return Err(
                                PipelineExecutionError::DynamicViewportScissorCountMismatch {
                                    viewport_count,
                                    scissor_count,
                                },
                            );
                        }
                    }
                }
                DynamicState::StencilCompareMask => {
                    let state = current_state.stencil_compare_mask();

                    // VUID?
                    if state.front.is_none() || state.back.is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::StencilOp => {
                    let state = current_state.stencil_op();

                    // VUID?
                    if state.front.is_none() || state.back.is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::StencilReference => {
                    let state = current_state.stencil_reference();

                    // VUID?
                    if state.front.is_none() || state.back.is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::StencilTestEnable => {
                    // VUID?
                    if current_state.stencil_test_enable().is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }

                    // TODO: Check if the stencil buffer is writable
                }
                DynamicState::StencilWriteMask => {
                    let state = current_state.stencil_write_mask();

                    // VUID?
                    if state.front.is_none() || state.back.is_none() {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    }
                }
                DynamicState::VertexInput => todo!(),
                DynamicState::VertexInputBindingStride => todo!(),
                DynamicState::Viewport => {
                    for num in 0..pipeline.viewport_state().unwrap().count().unwrap() {
                        // VUID?
                        if current_state.viewport(num).is_none() {
                            return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                        }
                    }
                }
                DynamicState::ViewportCoarseSampleOrder => todo!(),
                DynamicState::ViewportShadingRatePalette => todo!(),
                DynamicState::ViewportWithCount => {
                    // VUID-vkCmdDraw-viewportCount-03417
                    let viewport_count = if let Some(viewports) = current_state.viewport_with_count() {
                        viewports.len() as u32
                    } else {
                        return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                    };

                    let scissor_count = if let Some(scissor_count) =
                        pipeline.viewport_state().unwrap().count()
                    {
                        // The scissor count is fixed.
                        scissor_count
                    } else {
                        // VUID-vkCmdDraw-viewportCount-03419
                        // The scissor count is also dynamic.
                        if let Some(scissors) = current_state.scissor_with_count() {
                            scissors.len() as u32
                        } else {
                            return Err(PipelineExecutionError::DynamicStateNotSet { dynamic_state });
                        }
                    };

                    // VUID-vkCmdDraw-viewportCount-03417
                    // VUID-vkCmdDraw-viewportCount-03419
                    if viewport_count != scissor_count {
                        return Err(
                            PipelineExecutionError::DynamicViewportScissorCountMismatch {
                                viewport_count,
                                scissor_count,
                            },
                        );
                    }

                    // TODO: VUID-vkCmdDrawIndexed-primitiveFragmentShadingRateWithMultipleViewports-04552
                    // If the primitiveFragmentShadingRateWithMultipleViewports limit is not supported,
                    // the bound graphics pipeline was created with the
                    // VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT_EXT dynamic state enabled, and any of the
                    // shader stages of the bound graphics pipeline write to the PrimitiveShadingRateKHR
                    // built-in, then vkCmdSetViewportWithCountEXT must have been called in the current
                    // command buffer prior to this drawing command, and the viewportCount parameter of
                    // vkCmdSetViewportWithCountEXT must be 1
                }
                DynamicState::ViewportWScaling => todo!(),
                DynamicState::TessellationDomainOrigin => todo!(),
                DynamicState::DepthClampEnable => todo!(),
                DynamicState::PolygonMode => todo!(),
                DynamicState::RasterizationSamples => todo!(),
                DynamicState::SampleMask => todo!(),
                DynamicState::AlphaToCoverageEnable => todo!(),
                DynamicState::AlphaToOneEnable => todo!(),
                DynamicState::LogicOpEnable => todo!(),
                DynamicState::ColorBlendEnable => todo!(),
                DynamicState::ColorBlendEquation => todo!(),
                DynamicState::ColorWriteMask => todo!(),
                DynamicState::RasterizationStream => todo!(),
                DynamicState::ConservativeRasterizationMode => todo!(),
                DynamicState::ExtraPrimitiveOverestimationSize => todo!(),
                DynamicState::DepthClipEnable => todo!(),
                DynamicState::SampleLocationsEnable => todo!(),
                DynamicState::ColorBlendAdvanced => todo!(),
                DynamicState::ProvokingVertexMode => todo!(),
                DynamicState::LineRasterizationMode => todo!(),
                DynamicState::LineStippleEnable => todo!(),
                DynamicState::DepthClipNegativeOneToOne => todo!(),
                DynamicState::ViewportWScalingEnable => todo!(),
                DynamicState::ViewportSwizzle => todo!(),
                DynamicState::CoverageToColorEnable => todo!(),
                DynamicState::CoverageToColorLocation => todo!(),
                DynamicState::CoverageModulationMode => todo!(),
                DynamicState::CoverageModulationTableEnable => todo!(),
                DynamicState::CoverageModulationTable => todo!(),
                DynamicState::ShadingRateImageEnable => todo!(),
                DynamicState::RepresentativeFragmentTestEnable => todo!(),
                DynamicState::CoverageReductionMode => todo!(),
            }
        }

        Ok(())
    }

    fn validate_pipeline_graphics_render_pass(
        &self,
        pipeline: &GraphicsPipeline,
        render_pass_state: &RenderPassState,
    ) -> Result<(), PipelineExecutionError> {
        // VUID?
        if render_pass_state.contents != SubpassContents::Inline {
            return Err(PipelineExecutionError::ForbiddenWithSubpassContents {
                subpass_contents: render_pass_state.contents,
            });
        }

        match (&render_pass_state.render_pass, pipeline.render_pass()) {
            (
                RenderPassStateType::BeginRenderPass(state),
                PipelineRenderPassType::BeginRenderPass(pipeline_subpass),
            ) => {
                // VUID-vkCmdDraw-renderPass-02684
                if !pipeline_subpass
                    .render_pass()
                    .is_compatible_with(state.subpass.render_pass())
                {
                    return Err(PipelineExecutionError::PipelineRenderPassNotCompatible);
                }

                // VUID-vkCmdDraw-subpass-02685
                if pipeline_subpass.index() != state.subpass.index() {
                    return Err(PipelineExecutionError::PipelineSubpassMismatch {
                        pipeline: pipeline_subpass.index(),
                        current: state.subpass.index(),
                    });
                }
            }
            (
                RenderPassStateType::BeginRendering(current_rendering_info),
                PipelineRenderPassType::BeginRendering(pipeline_rendering_info),
            ) => {
                // VUID-vkCmdDraw-viewMask-06178
                if pipeline_rendering_info.view_mask != render_pass_state.view_mask {
                    return Err(PipelineExecutionError::PipelineViewMaskMismatch {
                        pipeline_view_mask: pipeline_rendering_info.view_mask,
                        required_view_mask: render_pass_state.view_mask,
                    });
                }

                // VUID-vkCmdDraw-colorAttachmentCount-06179
                if pipeline_rendering_info.color_attachment_formats.len()
                    != current_rendering_info.color_attachment_formats.len()
                {
                    return Err(
                        PipelineExecutionError::PipelineColorAttachmentCountMismatch {
                            pipeline_count: pipeline_rendering_info.color_attachment_formats.len()
                                as u32,
                            required_count: current_rendering_info.color_attachment_formats.len()
                                as u32,
                        },
                    );
                }

                for (color_attachment_index, required_format, pipeline_format) in
                    current_rendering_info
                        .color_attachment_formats
                        .iter()
                        .zip(
                            pipeline_rendering_info
                                .color_attachment_formats
                                .iter()
                                .copied(),
                        )
                        .enumerate()
                        .filter_map(|(i, (r, p))| r.map(|r| (i as u32, r, p)))
                {
                    // VUID-vkCmdDraw-colorAttachmentCount-06180
                    if Some(required_format) != pipeline_format {
                        return Err(
                            PipelineExecutionError::PipelineColorAttachmentFormatMismatch {
                                color_attachment_index,
                                pipeline_format,
                                required_format,
                            },
                        );
                    }
                }

                if let Some((required_format, pipeline_format)) = current_rendering_info
                    .depth_attachment_format
                    .map(|r| (r, pipeline_rendering_info.depth_attachment_format))
                {
                    // VUID-vkCmdDraw-pDepthAttachment-06181
                    if Some(required_format) != pipeline_format {
                        return Err(
                            PipelineExecutionError::PipelineDepthAttachmentFormatMismatch {
                                pipeline_format,
                                required_format,
                            },
                        );
                    }
                }

                if let Some((required_format, pipeline_format)) = current_rendering_info
                    .stencil_attachment_format
                    .map(|r| (r, pipeline_rendering_info.stencil_attachment_format))
                {
                    // VUID-vkCmdDraw-pStencilAttachment-06182
                    if Some(required_format) != pipeline_format {
                        return Err(
                            PipelineExecutionError::PipelineStencilAttachmentFormatMismatch {
                                pipeline_format,
                                required_format,
                            },
                        );
                    }
                }

                // VUID-vkCmdDraw-imageView-06172
                // VUID-vkCmdDraw-imageView-06173
                // VUID-vkCmdDraw-imageView-06174
                // VUID-vkCmdDraw-imageView-06175
                // VUID-vkCmdDraw-imageView-06176
                // VUID-vkCmdDraw-imageView-06177
                // TODO:
            }
            _ => return Err(PipelineExecutionError::PipelineRenderPassTypeMismatch),
        }

        // VUID-vkCmdDraw-None-02686
        // TODO:

        Ok(())
    }

    fn validate_pipeline_graphics_vertex_buffers(
        &self,
        pipeline: &GraphicsPipeline,
        vertices: Option<(u32, u32)>,
        instances: Option<(u32, u32)>,
    ) -> Result<(), PipelineExecutionError> {
        let vertex_input = pipeline.vertex_input_state();
        let mut vertices_in_buffers: Option<u64> = None;
        let mut instances_in_buffers: Option<u64> = None;
        let current_state = self.state();

        for (&binding_num, binding_desc) in &vertex_input.bindings {
            // VUID-vkCmdDraw-None-04007
            let vertex_buffer = match current_state.vertex_buffer(binding_num) {
                Some(x) => x,
                None => return Err(PipelineExecutionError::VertexBufferNotBound { binding_num }),
            };

            let mut num_elements = vertex_buffer.size() / binding_desc.stride as u64;

            match binding_desc.input_rate {
                VertexInputRate::Vertex => {
                    vertices_in_buffers = Some(if let Some(x) = vertices_in_buffers {
                        min(x, num_elements)
                    } else {
                        num_elements
                    });
                }
                VertexInputRate::Instance { divisor } => {
                    if divisor == 0 {
                        // A divisor of 0 means the same instance data is used for all instances,
                        // so we can draw any number of instances from a single element.
                        // The buffer must contain at least one element though.
                        if num_elements != 0 {
                            num_elements = u64::MAX;
                        }
                    } else {
                        // If divisor is e.g. 2, we use only half the amount of data from the source
                        // buffer, so the number of instances that can be drawn is twice as large.
                        num_elements = num_elements.saturating_mul(divisor as u64);
                    }

                    instances_in_buffers = Some(if let Some(x) = instances_in_buffers {
                        min(x, num_elements)
                    } else {
                        num_elements
                    });
                }
            };
        }

        if let Some((first_vertex, vertex_count)) = vertices {
            let vertices_needed = first_vertex as u64 + vertex_count as u64;

            if let Some(vertices_in_buffers) = vertices_in_buffers {
                // VUID-vkCmdDraw-None-02721
                if vertices_needed > vertices_in_buffers {
                    return Err(PipelineExecutionError::VertexBufferVertexRangeOutOfBounds {
                        vertices_needed,
                        vertices_in_buffers,
                    });
                }
            }
        }

        if let Some((first_instance, instance_count)) = instances {
            let instances_needed = first_instance as u64 + instance_count as u64;

            if let Some(instances_in_buffers) = instances_in_buffers {
                // VUID-vkCmdDraw-None-02721
                if instances_needed > instances_in_buffers {
                    return Err(
                        PipelineExecutionError::VertexBufferInstanceRangeOutOfBounds {
                            instances_needed,
                            instances_in_buffers,
                        },
                    );
                }
            }

            let view_mask = match pipeline.render_pass() {
                PipelineRenderPassType::BeginRenderPass(subpass) => {
                    subpass.render_pass().views_used()
                }
                PipelineRenderPassType::BeginRendering(rendering_info) => rendering_info.view_mask,
            };

            if view_mask != 0 {
                let max = pipeline
                    .device()
                    .physical_device()
                    .properties()
                    .max_multiview_instance_index
                    .unwrap_or(0);

                let highest_instance = instances_needed.saturating_sub(1);

                // VUID-vkCmdDraw-maxMultiviewInstanceIndex-02688
                if highest_instance > max as u64 {
                    return Err(PipelineExecutionError::MaxMultiviewInstanceIndexExceeded {
                        highest_instance,
                        max,
                    });
                }
            }
        }

        Ok(())
    }
}

impl SyncCommandBufferBuilder {
    /// Calls `vkCmdDispatch` on the builder.
    #[inline]
    pub unsafe fn dispatch(
        &mut self,
        group_counts: [u32; 3],
    ) -> Result<(), SyncCommandBufferBuilderError> {
        struct Cmd {
            group_counts: [u32; 3],
        }

        impl Command for Cmd {
            fn name(&self) -> &'static str {
                "dispatch"
            }

            unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
                out.dispatch(self.group_counts);
            }
        }

        let command_index = self.commands.len();
        let command_name = "dispatch";
        let pipeline = self
            .current_state
            .pipeline_compute
            .as_ref()
            .unwrap()
            .as_ref();

        let mut resources = Vec::new();
        self.add_descriptor_sets(&mut resources, command_index, command_name, pipeline);

        for resource in &resources {
            self.check_resource_conflicts(resource)?;
        }

        self.commands.push(Box::new(Cmd { group_counts }));

        for resource in resources {
            self.add_resource(resource);
        }

        Ok(())
    }

    /// Calls `vkCmdDispatchIndirect` on the builder.
    #[inline]
    pub unsafe fn dispatch_indirect(
        &mut self,
        indirect_buffer: Subbuffer<[DispatchIndirectCommand]>,
    ) -> Result<(), SyncCommandBufferBuilderError> {
        struct Cmd {
            indirect_buffer: Subbuffer<[DispatchIndirectCommand]>,
        }

        impl Command for Cmd {
            fn name(&self) -> &'static str {
                "dispatch_indirect"
            }

            unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
                out.dispatch_indirect(&self.indirect_buffer);
            }
        }

        let command_index = self.commands.len();
        let command_name = "dispatch_indirect";
        let pipeline = self
            .current_state
            .pipeline_compute
            .as_ref()
            .unwrap()
            .as_ref();

        let mut resources = Vec::new();
        self.add_descriptor_sets(&mut resources, command_index, command_name, pipeline);
        self.add_indirect_buffer(
            &mut resources,
            command_index,
            command_name,
            indirect_buffer.as_bytes(),
        );

        for resource in &resources {
            self.check_resource_conflicts(resource)?;
        }

        self.commands.push(Box::new(Cmd { indirect_buffer }));

        for resource in resources {
            self.add_resource(resource);
        }

        Ok(())
    }

    /// Calls `vkCmdDraw` on the builder.
    #[inline]
    pub unsafe fn draw(
        &mut self,
        vertex_count: u32,
        instance_count: u32,
        first_vertex: u32,
        first_instance: u32,
    ) -> Result<(), SyncCommandBufferBuilderError> {
        struct Cmd {
            vertex_count: u32,
            instance_count: u32,
            first_vertex: u32,
            first_instance: u32,
        }

        impl Command for Cmd {
            fn name(&self) -> &'static str {
                "draw"
            }

            unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
                out.draw(
                    self.vertex_count,
                    self.instance_count,
                    self.first_vertex,
                    self.first_instance,
                );
            }
        }

        let command_index = self.commands.len();
        let command_name = "draw";
        let pipeline = self
            .current_state
            .pipeline_graphics
            .as_ref()
            .unwrap()
            .as_ref();

        let mut resources = Vec::new();
        self.add_descriptor_sets(&mut resources, command_index, command_name, pipeline);
        self.add_vertex_buffers(&mut resources, command_index, command_name, pipeline);

        for resource in &resources {
            self.check_resource_conflicts(resource)?;
        }

        self.commands.push(Box::new(Cmd {
            vertex_count,
            instance_count,
            first_vertex,
            first_instance,
        }));

        for resource in resources {
            self.add_resource(resource);
        }

        Ok(())
    }

    /// Calls `vkCmdDrawIndexed` on the builder.
    #[inline]
    pub unsafe fn draw_indexed(
        &mut self,
        index_count: u32,
        instance_count: u32,
        first_index: u32,
        vertex_offset: i32,
        first_instance: u32,
    ) -> Result<(), SyncCommandBufferBuilderError> {
        struct Cmd {
            index_count: u32,
            instance_count: u32,
            first_index: u32,
            vertex_offset: i32,
            first_instance: u32,
        }

        impl Command for Cmd {
            fn name(&self) -> &'static str {
                "draw_indexed"
            }

            unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
                out.draw_indexed(
                    self.index_count,
                    self.instance_count,
                    self.first_index,
                    self.vertex_offset,
                    self.first_instance,
                );
            }
        }

        let command_index = self.commands.len();
        let command_name = "draw_indexed";
        let pipeline = self
            .current_state
            .pipeline_graphics
            .as_ref()
            .unwrap()
            .as_ref();

        let mut resources = Vec::new();
        self.add_descriptor_sets(&mut resources, command_index, command_name, pipeline);
        self.add_vertex_buffers(&mut resources, command_index, command_name, pipeline);
        self.add_index_buffer(&mut resources, command_index, command_name);

        for resource in &resources {
            self.check_resource_conflicts(resource)?;
        }

        self.commands.push(Box::new(Cmd {
            index_count,
            instance_count,
            first_index,
            vertex_offset,
            first_instance,
        }));

        for resource in resources {
            self.add_resource(resource);
        }

        Ok(())
    }

    /// Calls `vkCmdDrawIndirect` on the builder.
    #[inline]
    pub unsafe fn draw_indirect(
        &mut self,
        indirect_buffer: Subbuffer<[DrawIndirectCommand]>,
        draw_count: u32,
        stride: u32,
    ) -> Result<(), SyncCommandBufferBuilderError> {
        struct Cmd {
            indirect_buffer: Subbuffer<[DrawIndirectCommand]>,
            draw_count: u32,
            stride: u32,
        }

        impl Command for Cmd {
            fn name(&self) -> &'static str {
                "draw_indirect"
            }

            unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
                out.draw_indirect(&self.indirect_buffer, self.draw_count, self.stride);
            }
        }

        let command_index = self.commands.len();
        let command_name = "draw_indirect";
        let pipeline = self
            .current_state
            .pipeline_graphics
            .as_ref()
            .unwrap()
            .as_ref();

        let mut resources = Vec::new();
        self.add_descriptor_sets(&mut resources, command_index, command_name, pipeline);
        self.add_vertex_buffers(&mut resources, command_index, command_name, pipeline);
        self.add_indirect_buffer(
            &mut resources,
            command_index,
            command_name,
            indirect_buffer.as_bytes(),
        );

        for resource in &resources {
            self.check_resource_conflicts(resource)?;
        }

        self.commands.push(Box::new(Cmd {
            indirect_buffer,
            draw_count,
            stride,
        }));

        for resource in resources {
            self.add_resource(resource);
        }

        Ok(())
    }

    /// Calls `vkCmdDrawIndexedIndirect` on the builder.
    #[inline]
    pub unsafe fn draw_indexed_indirect(
        &mut self,
        indirect_buffer: Subbuffer<[DrawIndexedIndirectCommand]>,
        draw_count: u32,
        stride: u32,
    ) -> Result<(), SyncCommandBufferBuilderError> {
        struct Cmd {
            indirect_buffer: Subbuffer<[DrawIndexedIndirectCommand]>,
            draw_count: u32,
            stride: u32,
        }

        impl Command for Cmd {
            fn name(&self) -> &'static str {
                "draw_indexed_indirect"
            }

            unsafe fn send(&self, out: &mut UnsafeCommandBufferBuilder) {
                out.draw_indexed_indirect(&self.indirect_buffer, self.draw_count, self.stride);
            }
        }

        let command_index = self.commands.len();
        let command_name = "draw_indexed_indirect";
        let pipeline = self
            .current_state
            .pipeline_graphics
            .as_ref()
            .unwrap()
            .as_ref();

        let mut resources = Vec::new();
        self.add_descriptor_sets(&mut resources, command_index, command_name, pipeline);
        self.add_vertex_buffers(&mut resources, command_index, command_name, pipeline);
        self.add_index_buffer(&mut resources, command_index, command_name);
        self.add_indirect_buffer(
            &mut resources,
            command_index,
            command_name,
            indirect_buffer.as_bytes(),
        );

        for resource in &resources {
            self.check_resource_conflicts(resource)?;
        }

        self.commands.push(Box::new(Cmd {
            indirect_buffer,
            draw_count,
            stride,
        }));

        for resource in resources {
            self.add_resource(resource);
        }

        Ok(())
    }

    fn add_descriptor_sets<Pl: Pipeline>(
        &self,
        resources: &mut Vec<(ResourceUseRef, Resource)>,
        command_index: usize,
        command_name: &'static str,
        pipeline: &Pl,
    ) {
        let descriptor_sets_state = match self
            .current_state
            .descriptor_sets
            .get(&pipeline.bind_point())
        {
            Some(x) => x,
            None => return,
        };

        for (&(set, binding), binding_reqs) in pipeline.descriptor_binding_requirements() {
            // TODO: Can things be refactored so that the pipeline layout isn't needed at all?
            let descriptor_type = descriptor_sets_state.pipeline_layout.set_layouts()[set as usize]
                .bindings()[&binding]
                .descriptor_type;

            let (access_read, access_write) = match descriptor_type {
                DescriptorType::Sampler => continue,
                DescriptorType::InputAttachment => {
                    // FIXME: This is tricky. Since we read from the input attachment
                    // and this input attachment is being written in an earlier pass,
                    // vulkano will think that it needs to put a pipeline barrier and will
                    // return a `Conflict` error. For now as a work-around we simply ignore
                    // input attachments.
                    continue;
                }
                DescriptorType::CombinedImageSampler
                | DescriptorType::SampledImage
                | DescriptorType::UniformTexelBuffer => (Some(AccessFlags::SHADER_READ), None),
                DescriptorType::StorageImage
                | DescriptorType::StorageTexelBuffer
                | DescriptorType::StorageBuffer
                | DescriptorType::StorageBufferDynamic => (
                    Some(AccessFlags::SHADER_READ),
                    Some(AccessFlags::SHADER_WRITE),
                ),
                DescriptorType::UniformBuffer | DescriptorType::UniformBufferDynamic => {
                    (Some(AccessFlags::UNIFORM_READ), None)
                }
            };

            let memory_iter = move |index: u32| {
                let mut stages_read = PipelineStages::empty();
                let mut stages_write = PipelineStages::empty();

                for desc_reqs in (binding_reqs.descriptors.get(&Some(index)).into_iter())
                    .chain(binding_reqs.descriptors.get(&None))
                {
                    stages_read |= desc_reqs.memory_read.into();
                    stages_write |= desc_reqs.memory_write.into();
                }

                let memory_read = (!stages_read.is_empty()).then(|| PipelineMemoryAccess {
                    stages: stages_read,
                    access: access_read.unwrap(),
                    exclusive: false,
                });
                let memory_write = (!stages_write.is_empty()).then(|| PipelineMemoryAccess {
                    stages: stages_write,
                    access: access_write.unwrap(),
                    exclusive: true,
                });

                [memory_read, memory_write].into_iter().flatten()
            };
            let buffer_resource =
                |(index, buffer, range): (u32, Subbuffer<[u8]>, Range<DeviceSize>)| {
                    memory_iter(index).map(move |memory| {
                        (
                            ResourceUseRef {
                                command_index,
                                command_name,
                                resource_in_command: ResourceInCommand::DescriptorSet {
                                    set,
                                    binding,
                                    index,
                                },
                                secondary_use_ref: None,
                            },
                            Resource::Buffer {
                                buffer: buffer.clone(),
                                range: range.clone(),
                                memory,
                            },
                        )
                    })
                };
            let image_resource = |(index, image, subresource_range): (
                u32,
                Arc<dyn ImageAccess>,
                ImageSubresourceRange,
            )| {
                let layout = image
                    .descriptor_layouts()
                    .expect("descriptor_layouts must return Some when used in an image view")
                    .layout_for(descriptor_type);

                memory_iter(index).map(move |memory| {
                    (
                        ResourceUseRef {
                            command_index,
                            command_name,
                            resource_in_command: ResourceInCommand::DescriptorSet {
                                set,
                                binding,
                                index,
                            },
                            secondary_use_ref: None,
                        },
                        Resource::Image {
                            image: image.clone(),
                            subresource_range: subresource_range.clone(),
                            memory,
                            start_layout: layout,
                            end_layout: layout,
                        },
                    )
                })
            };

            let descriptor_set_state = &descriptor_sets_state.descriptor_sets[&set];

            match descriptor_set_state.resources().binding(binding).unwrap() {
                DescriptorBindingResources::None(_) => continue,
                DescriptorBindingResources::Buffer(elements) => {
                    if matches!(
                        descriptor_type,
                        DescriptorType::UniformBufferDynamic | DescriptorType::StorageBufferDynamic
                    ) {
                        let dynamic_offsets = descriptor_set_state.dynamic_offsets();
                        resources.extend(
                            (elements.iter().enumerate())
                                .filter_map(|(index, element)| {
                                    element.as_ref().map(|(buffer, range)| {
                                        let dynamic_offset = dynamic_offsets[index] as DeviceSize;

                                        (
                                            index as u32,
                                            buffer.clone(),
                                            dynamic_offset + range.start
                                                ..dynamic_offset + range.end,
                                        )
                                    })
                                })
                                .flat_map(buffer_resource),
                        );
                    } else {
                        resources.extend(
                            (elements.iter().enumerate())
                                .filter_map(|(index, element)| {
                                    element.as_ref().map(|(buffer, range)| {
                                        (index as u32, buffer.clone(), range.clone())
                                    })
                                })
                                .flat_map(buffer_resource),
                        );
                    }
                }
                DescriptorBindingResources::BufferView(elements) => {
                    resources.extend(
                        (elements.iter().enumerate())
                            .filter_map(|(index, element)| {
                                element.as_ref().map(|buffer_view| {
                                    (
                                        index as u32,
                                        buffer_view.buffer().clone(),
                                        buffer_view.range(),
                                    )
                                })
                            })
                            .flat_map(buffer_resource),
                    );
                }
                DescriptorBindingResources::ImageView(elements) => {
                    resources.extend(
                        (elements.iter().enumerate())
                            .filter_map(|(index, element)| {
                                element.as_ref().map(|image_view| {
                                    (
                                        index as u32,
                                        image_view.image(),
                                        image_view.subresource_range().clone(),
                                    )
                                })
                            })
                            .flat_map(image_resource),
                    );
                }
                DescriptorBindingResources::ImageViewSampler(elements) => {
                    resources.extend(
                        (elements.iter().enumerate())
                            .filter_map(|(index, element)| {
                                element.as_ref().map(|(image_view, _)| {
                                    (
                                        index as u32,
                                        image_view.image(),
                                        image_view.subresource_range().clone(),
                                    )
                                })
                            })
                            .flat_map(image_resource),
                    );
                }
                DescriptorBindingResources::Sampler(_) => (),
            }
        }
    }

    fn add_vertex_buffers(
        &self,
        resources: &mut Vec<(ResourceUseRef, Resource)>,
        command_index: usize,
        command_name: &'static str,
        pipeline: &GraphicsPipeline,
    ) {
        resources.extend(
            pipeline
                .vertex_input_state()
                .bindings
                .iter()
                .map(|(&binding, _)| {
                    let vertex_buffer = &self.current_state.vertex_buffers[&binding];
                    (
                        ResourceUseRef {
                            command_index,
                            command_name,
                            resource_in_command: ResourceInCommand::VertexBuffer { binding },
                            secondary_use_ref: None,
                        },
                        Resource::Buffer {
                            buffer: vertex_buffer.clone(),
                            range: 0..vertex_buffer.size(), // TODO:
                            memory: PipelineMemoryAccess {
                                stages: PipelineStages::VERTEX_INPUT,
                                access: AccessFlags::VERTEX_ATTRIBUTE_READ,
                                exclusive: false,
                            },
                        },
                    )
                }),
        );
    }

    fn add_index_buffer(
        &self,
        resources: &mut Vec<(ResourceUseRef, Resource)>,
        command_index: usize,
        command_name: &'static str,
    ) {
        let index_buffer = &self.current_state.index_buffer.as_ref().unwrap().0;
        resources.push((
            ResourceUseRef {
                command_index,
                command_name,
                resource_in_command: ResourceInCommand::IndexBuffer,
                secondary_use_ref: None,
            },
            Resource::Buffer {
                buffer: index_buffer.clone(),
                range: 0..index_buffer.size(), // TODO:
                memory: PipelineMemoryAccess {
                    stages: PipelineStages::VERTEX_INPUT,
                    access: AccessFlags::INDEX_READ,
                    exclusive: false,
                },
            },
        ));
    }

    fn add_indirect_buffer(
        &self,
        resources: &mut Vec<(ResourceUseRef, Resource)>,
        command_index: usize,
        command_name: &'static str,
        indirect_buffer: &Subbuffer<[u8]>,
    ) {
        resources.push((
            ResourceUseRef {
                command_index,
                command_name,
                resource_in_command: ResourceInCommand::IndirectBuffer,
                secondary_use_ref: None,
            },
            Resource::Buffer {
                buffer: indirect_buffer.clone(),
                range: 0..indirect_buffer.size(), // TODO:
                memory: PipelineMemoryAccess {
                    stages: PipelineStages::DRAW_INDIRECT,
                    access: AccessFlags::INDIRECT_COMMAND_READ,
                    exclusive: false,
                },
            },
        ));
    }
}

impl UnsafeCommandBufferBuilder {
    /// Calls `vkCmdDispatch` on the builder.
    #[inline]
    pub unsafe fn dispatch(&mut self, group_counts: [u32; 3]) {
        debug_assert!({
            let max_group_counts = self
                .device
                .physical_device()
                .properties()
                .max_compute_work_group_count;
            group_counts[0] <= max_group_counts[0]
                && group_counts[1] <= max_group_counts[1]
                && group_counts[2] <= max_group_counts[2]
        });

        let fns = self.device.fns();
        (fns.v1_0.cmd_dispatch)(
            self.handle,
            group_counts[0],
            group_counts[1],
            group_counts[2],
        );
    }

    /// Calls `vkCmdDispatchIndirect` on the builder.
    #[inline]
    pub unsafe fn dispatch_indirect(&mut self, buffer: &Subbuffer<[DispatchIndirectCommand]>) {
        let fns = self.device.fns();

        debug_assert!(buffer.offset() < buffer.buffer().size());
        debug_assert!(buffer
            .buffer()
            .usage()
            .intersects(BufferUsage::INDIRECT_BUFFER));
        debug_assert_eq!(buffer.offset() % 4, 0);

        (fns.v1_0.cmd_dispatch_indirect)(self.handle, buffer.buffer().handle(), buffer.offset());
    }

    /// Calls `vkCmdDraw` on the builder.
    #[inline]
    pub unsafe fn draw(
        &mut self,
        vertex_count: u32,
        instance_count: u32,
        first_vertex: u32,
        first_instance: u32,
    ) {
        let fns = self.device.fns();
        (fns.v1_0.cmd_draw)(
            self.handle,
            vertex_count,
            instance_count,
            first_vertex,
            first_instance,
        );
    }

    /// Calls `vkCmdDrawIndexed` on the builder.
    #[inline]
    pub unsafe fn draw_indexed(
        &mut self,
        index_count: u32,
        instance_count: u32,
        first_index: u32,
        vertex_offset: i32,
        first_instance: u32,
    ) {
        let fns = self.device.fns();
        (fns.v1_0.cmd_draw_indexed)(
            self.handle,
            index_count,
            instance_count,
            first_index,
            vertex_offset,
            first_instance,
        );
    }

    /// Calls `vkCmdDrawIndirect` on the builder.
    #[inline]
    pub unsafe fn draw_indirect(
        &mut self,
        buffer: &Subbuffer<[DrawIndirectCommand]>,
        draw_count: u32,
        stride: u32,
    ) {
        let fns = self.device.fns();

        debug_assert!(
            draw_count == 0
                || ((stride % 4) == 0)
                    && stride as usize >= size_of::<ash::vk::DrawIndirectCommand>()
        );

        debug_assert!(buffer.offset() < buffer.buffer().size());
        debug_assert!(buffer
            .buffer()
            .usage()
            .intersects(BufferUsage::INDIRECT_BUFFER));

        (fns.v1_0.cmd_draw_indirect)(
            self.handle,
            buffer.buffer().handle(),
            buffer.offset(),
            draw_count,
            stride,
        );
    }

    /// Calls `vkCmdDrawIndexedIndirect` on the builder.
    #[inline]
    pub unsafe fn draw_indexed_indirect(
        &mut self,
        buffer: &Subbuffer<[DrawIndexedIndirectCommand]>,
        draw_count: u32,
        stride: u32,
    ) {
        let fns = self.device.fns();

        debug_assert!(buffer.offset() < buffer.buffer().size());
        debug_assert!(buffer
            .buffer()
            .usage()
            .intersects(BufferUsage::INDIRECT_BUFFER));

        (fns.v1_0.cmd_draw_indexed_indirect)(
            self.handle,
            buffer.buffer().handle(),
            buffer.offset(),
            draw_count,
            stride,
        );
    }
}

/// Error that can happen when recording a bound pipeline execution command.
#[derive(Debug, Clone)]
pub enum PipelineExecutionError {
    SyncCommandBufferBuilderError(SyncCommandBufferBuilderError),

    RequirementNotMet {
        required_for: &'static str,
        requires_one_of: RequiresOneOf,
    },

    /// The resource bound to a descriptor set binding at a particular index is not compatible
    /// with the requirements of the pipeline and shaders.
    DescriptorResourceInvalid {
        set_num: u32,
        binding_num: u32,
        index: u32,
        error: DescriptorResourceInvalidError,
    },

    /// The pipeline layout requires a descriptor set bound to a set number, but none was bound.
    DescriptorSetNotBound {
        set_num: u32,
    },

    /// The bound pipeline uses a dynamic color write enable setting, but the number of provided
    /// enable values is less than the number of attachments in the current render subpass.
    DynamicColorWriteEnableNotEnoughValues {
        color_write_enable_count: u32,
        attachment_count: u32,
    },

    /// The bound pipeline uses a dynamic primitive topology, but the provided topology is of a
    /// different topology class than what the pipeline requires.
    DynamicPrimitiveTopologyClassMismatch {
        provided_class: PrimitiveTopologyClass,
        required_class: PrimitiveTopologyClass,
    },

    /// The bound pipeline uses a dynamic primitive topology, but the provided topology is not
    /// compatible with the shader stages in the pipeline.
    DynamicPrimitiveTopologyInvalid {
        topology: PrimitiveTopology,
    },

    /// The pipeline requires a particular dynamic state, but this state was not set.
    DynamicStateNotSet {
        dynamic_state: DynamicState,
    },

    /// The bound pipeline uses a dynamic scissor and/or viewport count, but the scissor count
    /// does not match the viewport count.
    DynamicViewportScissorCountMismatch {
        viewport_count: u32,
        scissor_count: u32,
    },

    /// Operation forbidden inside a render pass.
    ForbiddenInsideRenderPass,

    /// Operation forbidden outside a render pass.
    ForbiddenOutsideRenderPass,

    /// Operation forbidden inside a render subpass with the specified contents.
    ForbiddenWithSubpassContents {
        subpass_contents: SubpassContents,
    },

    /// An indexed draw command was recorded, but no index buffer was bound.
    IndexBufferNotBound,

    /// The highest index to be drawn exceeds the available number of indices in the bound index
    /// buffer.
    IndexBufferRangeOutOfBounds {
        highest_index: u32,
        max_index_count: u32,
    },

    /// The `indirect_buffer` usage was not enabled on the indirect buffer.
    IndirectBufferMissingUsage,

    /// The `max_compute_work_group_count` limit has been exceeded.
    MaxComputeWorkGroupCountExceeded {
        requested: [u32; 3],
        max: [u32; 3],
    },

    /// The `max_draw_indirect_count` limit has been exceeded.
    MaxDrawIndirectCountExceeded {
        provided: u32,
        max: u32,
    },

    /// The `max_multiview_instance_index` limit has been exceeded.
    MaxMultiviewInstanceIndexExceeded {
        highest_instance: u64,
        max: u32,
    },

    /// The queue family doesn't allow this operation.
    NotSupportedByQueueFamily,

    /// The color attachment count in the bound pipeline does not match the count of the current
    /// render pass.
    PipelineColorAttachmentCountMismatch {
        pipeline_count: u32,
        required_count: u32,
    },

    /// The format of a color attachment in the bound pipeline does not match the format of the
    /// corresponding color attachment in the current render pass.
    PipelineColorAttachmentFormatMismatch {
        color_attachment_index: u32,
        pipeline_format: Option<Format>,
        required_format: Format,
    },

    /// The format of the depth attachment in the bound pipeline does not match the format of the
    /// depth attachment in the current render pass.
    PipelineDepthAttachmentFormatMismatch {
        pipeline_format: Option<Format>,
        required_format: Format,
    },

    /// The bound pipeline is not compatible with the layout used to bind the descriptor sets.
    PipelineLayoutNotCompatible,

    /// No pipeline was bound to the bind point used by the operation.
    PipelineNotBound,

    /// The bound graphics pipeline uses a render pass that is not compatible with the currently
    /// active render pass.
    PipelineRenderPassNotCompatible,

    /// The bound graphics pipeline uses a render pass of a different type than the currently
    /// active render pass.
    PipelineRenderPassTypeMismatch,

    /// The bound graphics pipeline uses a render subpass index that doesn't match the currently
    /// active subpass index.
    PipelineSubpassMismatch {
        pipeline: u32,
        current: u32,
    },

    /// The format of the stencil attachment in the bound pipeline does not match the format of the
    /// stencil attachment in the current render pass.
    PipelineStencilAttachmentFormatMismatch {
        pipeline_format: Option<Format>,
        required_format: Format,
    },

    /// The view mask of the bound pipeline does not match the view mask of the current render pass.
    PipelineViewMaskMismatch {
        pipeline_view_mask: u32,
        required_view_mask: u32,
    },

    /// The push constants are not compatible with the pipeline layout.
    PushConstantsNotCompatible,

    /// Not all push constants used by the pipeline have been set.
    PushConstantsMissing,

    /// The bound graphics pipeline requires a vertex buffer bound to a binding number, but none
    /// was bound.
    VertexBufferNotBound {
        binding_num: u32,
    },

    /// The number of instances to be drawn exceeds the available number of indices in the
    /// bound vertex buffers used by the pipeline.
    VertexBufferInstanceRangeOutOfBounds {
        instances_needed: u64,
        instances_in_buffers: u64,
    },

    /// The number of vertices to be drawn exceeds the lowest available number of vertices in the
    /// bound vertex buffers used by the pipeline.
    VertexBufferVertexRangeOutOfBounds {
        vertices_needed: u64,
        vertices_in_buffers: u64,
    },
}

impl Error for PipelineExecutionError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::SyncCommandBufferBuilderError(err) => Some(err),
            Self::DescriptorResourceInvalid { error, .. } => Some(error),
            _ => None,
        }
    }
}

impl Display for PipelineExecutionError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
        match self {
            Self::SyncCommandBufferBuilderError(_) => write!(f, "a SyncCommandBufferBuilderError"),
            Self::RequirementNotMet {
                required_for,
                requires_one_of,
            } => write!(
                f,
                "a requirement was not met for: {}; requires one of: {}",
                required_for, requires_one_of,
            ),
            Self::DescriptorResourceInvalid {
                set_num,
                binding_num,
                index,
                ..
            } => write!(
                f,
                "the resource bound to descriptor set {} binding {} at index {} is not compatible \
                with the requirements of the pipeline and shaders",
                set_num, binding_num, index,
            ),
            Self::DescriptorSetNotBound { set_num } => write!(
                f,
                "the pipeline layout requires a descriptor set bound to set number {}, but none \
                was bound",
                set_num,
            ),
            Self::DynamicColorWriteEnableNotEnoughValues {
                color_write_enable_count,
                attachment_count,
            } => write!(
                f,
                "the bound pipeline uses a dynamic color write enable setting, but the number of \
                provided enable values ({}) is less than the number of attachments in the current \
                render subpass ({})",
                color_write_enable_count, attachment_count,
            ),
            Self::DynamicPrimitiveTopologyClassMismatch {
                provided_class,
                required_class,
            } => write!(
                f,
                "The bound pipeline uses a dynamic primitive topology, but the provided topology \
                is of a different topology class ({:?}) than what the pipeline requires ({:?})",
                provided_class, required_class,
            ),
            Self::DynamicPrimitiveTopologyInvalid { topology } => write!(
                f,
                "the bound pipeline uses a dynamic primitive topology, but the provided topology \
                ({:?}) is not compatible with the shader stages in the pipeline",
                topology,
            ),
            Self::DynamicStateNotSet { dynamic_state } => write!(
                f,
                "the pipeline requires the dynamic state {:?}, but this state was not set",
                dynamic_state,
            ),
            Self::DynamicViewportScissorCountMismatch {
                viewport_count,
                scissor_count,
            } => write!(
                f,
                "the bound pipeline uses a dynamic scissor and/or viewport count, but the scissor \
                count ({}) does not match the viewport count ({})",
                scissor_count, viewport_count,
            ),
            Self::ForbiddenInsideRenderPass => {
                write!(f, "operation forbidden inside a render pass")
            }
            Self::ForbiddenOutsideRenderPass => {
                write!(f, "operation forbidden outside a render pass")
            }
            Self::ForbiddenWithSubpassContents { subpass_contents } => write!(
                f,
                "operation forbidden inside a render subpass with contents {:?}",
                subpass_contents,
            ),
            Self::IndexBufferNotBound => write!(
                f,
                "an indexed draw command was recorded, but no index buffer was bound",
            ),
            Self::IndexBufferRangeOutOfBounds {
                highest_index,
                max_index_count,
            } => write!(
                f,
                "the highest index to be drawn ({}) exceeds the available number of indices in the \
                bound index buffer ({})",
                highest_index, max_index_count,
            ),
            Self::IndirectBufferMissingUsage => write!(
                f,
                "the `indirect_buffer` usage was not enabled on the indirect buffer",
            ),
            Self::MaxComputeWorkGroupCountExceeded { .. } => write!(
                f,
                "the `max_compute_work_group_count` limit has been exceeded",
            ),
            Self::MaxDrawIndirectCountExceeded { .. } => {
                write!(f, "the `max_draw_indirect_count` limit has been exceeded")
            }
            Self::MaxMultiviewInstanceIndexExceeded { .. } => write!(
                f,
                "the `max_multiview_instance_index` limit has been exceeded",
            ),
            Self::NotSupportedByQueueFamily => {
                write!(f, "the queue family doesn't allow this operation")
            }
            Self::PipelineColorAttachmentCountMismatch {
                pipeline_count,
                required_count,
            } => write!(
                f,
                "the color attachment count in the bound pipeline ({}) does not match the count of \
                the current render pass ({})",
                pipeline_count, required_count,
            ),
            Self::PipelineColorAttachmentFormatMismatch {
                color_attachment_index,
                pipeline_format,
                required_format,
            } => write!(
                f,
                "the format of color attachment {} in the bound pipeline ({:?}) does not match the \
                format of the corresponding color attachment in the current render pass ({:?})",
                color_attachment_index, pipeline_format, required_format,
            ),
            Self::PipelineDepthAttachmentFormatMismatch {
                pipeline_format,
                required_format,
            } => write!(
                f,
                "the format of the depth attachment in the bound pipeline ({:?}) does not match \
                the format of the depth attachment in the current render pass ({:?})",
                pipeline_format, required_format,
            ),
            Self::PipelineLayoutNotCompatible => write!(
                f,
                "the bound pipeline is not compatible with the layout used to bind the descriptor \
                sets",
            ),
            Self::PipelineNotBound => write!(
                f,
                "no pipeline was bound to the bind point used by the operation",
            ),
            Self::PipelineRenderPassNotCompatible => write!(
                f,
                "the bound graphics pipeline uses a render pass that is not compatible with the \
                currently active render pass",
            ),
            Self::PipelineRenderPassTypeMismatch => write!(
                f,
                "the bound graphics pipeline uses a render pass of a different type than the \
                currently active render pass",
            ),
            Self::PipelineSubpassMismatch { pipeline, current } => write!(
                f,
                "the bound graphics pipeline uses a render subpass index ({}) that doesn't match \
                the currently active subpass index ({})",
                pipeline, current,
            ),
            Self::PipelineStencilAttachmentFormatMismatch {
                pipeline_format,
                required_format,
            } => write!(
                f,
                "the format of the stencil attachment in the bound pipeline ({:?}) does not match \
                the format of the stencil attachment in the current render pass ({:?})",
                pipeline_format, required_format,
            ),
            Self::PipelineViewMaskMismatch {
                pipeline_view_mask,
                required_view_mask,
            } => write!(
                f,
                "the view mask of the bound pipeline ({}) does not match the view mask of the \
                current render pass ({})",
                pipeline_view_mask, required_view_mask,
            ),
            Self::PushConstantsNotCompatible => write!(
                f,
                "the push constants are not compatible with the pipeline layout",
            ),
            Self::PushConstantsMissing => write!(
                f,
                "not all push constants used by the pipeline have been set",
            ),
            Self::VertexBufferNotBound { binding_num } => write!(
                f,
                "the bound graphics pipeline requires a vertex buffer bound to binding number {}, \
                but none was bound",
                binding_num,
            ),
            Self::VertexBufferInstanceRangeOutOfBounds {
                instances_needed,
                instances_in_buffers,
            } => write!(
                f,
                "the number of instances to be drawn ({}) exceeds the available number of \
                instances in the bound vertex buffers ({}) used by the pipeline",
                instances_needed, instances_in_buffers,
            ),
            Self::VertexBufferVertexRangeOutOfBounds {
                vertices_needed,
                vertices_in_buffers,
            } => write!(
                f,
                "the number of vertices to be drawn ({}) exceeds the available number of vertices \
                in the bound vertex buffers ({}) used by the pipeline",
                vertices_needed, vertices_in_buffers,
            ),
        }
    }
}

impl From<SyncCommandBufferBuilderError> for PipelineExecutionError {
    fn from(err: SyncCommandBufferBuilderError) -> Self {
        Self::SyncCommandBufferBuilderError(err)
    }
}

#[derive(Clone, Copy, Debug)]
pub enum DescriptorResourceInvalidError {
    ImageViewFormatMismatch {
        required: Format,
        provided: Option<Format>,
    },
    ImageViewMultisampledMismatch {
        required: bool,
        provided: bool,
    },
    ImageViewScalarTypeMismatch {
        required: ShaderScalarType,
        provided: ShaderScalarType,
    },
    ImageViewTypeMismatch {
        required: ImageViewType,
        provided: ImageViewType,
    },
    Missing,
    SamplerCompareMismatch {
        required: bool,
        provided: bool,
    },
    SamplerImageViewIncompatible {
        image_view_set_num: u32,
        image_view_binding_num: u32,
        image_view_index: u32,
        error: SamplerImageViewIncompatibleError,
    },
    SamplerUnnormalizedCoordinatesNotAllowed,
    SamplerYcbcrConversionNotAllowed,
    StorageImageAtomicNotSupported,
    StorageReadWithoutFormatNotSupported,
    StorageWriteWithoutFormatNotSupported,
}

impl Error for DescriptorResourceInvalidError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            Self::SamplerImageViewIncompatible { error, .. } => Some(error),
            _ => None,
        }
    }
}

impl Display for DescriptorResourceInvalidError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
        match self {
            Self::ImageViewFormatMismatch { provided, required } => write!(
                f,
                "the format of the bound image view ({:?}) does not match what the pipeline \
                requires ({:?})",
                provided, required,
            ),
            Self::ImageViewMultisampledMismatch { provided, required } => write!(
                f,
                "the multisampling of the bound image ({}) does not match what the pipeline \
                requires ({})",
                provided, required,
            ),
            Self::ImageViewScalarTypeMismatch { provided, required } => write!(
                f,
                "the scalar type of the format and aspect of the bound image view ({:?}) does not \
                match what the pipeline requires ({:?})",
                provided, required,
            ),
            Self::ImageViewTypeMismatch { provided, required } => write!(
                f,
                "the image view type of the bound image view ({:?}) does not match what the \
                pipeline requires ({:?})",
                provided, required,
            ),
            Self::Missing => write!(f, "no resource was bound"),
            Self::SamplerImageViewIncompatible { .. } => write!(
                f,
                "the bound sampler samples an image view that is not compatible with that sampler",
            ),
            Self::SamplerCompareMismatch { provided, required } => write!(
                f,
                "the depth comparison state of the bound sampler ({}) does not match what the \
                pipeline requires ({})",
                provided, required,
            ),
            Self::SamplerUnnormalizedCoordinatesNotAllowed => write!(
                f,
                "the bound sampler is required to have unnormalized coordinates disabled",
            ),
            Self::SamplerYcbcrConversionNotAllowed => write!(
                f,
                "the bound sampler is required to have no attached sampler YCbCr conversion",
            ),
            Self::StorageImageAtomicNotSupported => write!(
                f,
                "the bound image view does not support the `storage_image_atomic` format feature",
            ),
            Self::StorageReadWithoutFormatNotSupported => write!(
                f,
                "the bound image view or buffer view does not support the \
                `storage_read_without_format` format feature",
            ),
            Self::StorageWriteWithoutFormatNotSupported => write!(
                f,
                "the bound image view or buffer view does not support the \
                `storage_write_without_format` format feature",
            ),
        }
    }
}
