// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

//! How to retrieve data from a sampled image within a shader.
//!
//! When you retrieve data from a sampled image, you have to pass the coordinates of the pixel you
//! want to retrieve. The implementation then performs various calculations, and these operations
//! are what the `Sampler` object describes.
//!
//! # Level of detail
//!
//! The level-of-detail (LOD) is a floating-point value that expresses a sense of how much texture
//! detail is visible to the viewer. It is used in texture filtering and mipmapping calculations.
//!
//! LOD is calculated through one or more steps to produce a final value. The base LOD is
//! determined by one of two ways:
//! - Implicitly, by letting Vulkan calculate it automatically, based on factors such as number of
//!   pixels, distance and viewing angle. This is done using an `ImplicitLod` SPIR-V sampling
//!   operation, which corresponds to the `texture*` functions not suffixed with `Lod` in GLSL.
//! - Explicitly, specified in the shader. This is done using an `ExplicitLod` SPIR-V sampling
//!   operation, which corresponds to the `texture*Lod` functions in GLSL.
//!
//! It is possible to provide a *bias* to the base LOD value, which is simply added to it.
//! An LOD bias can be provided both in the sampler object and as part of the sampling operation in
//! the shader, and are combined by addition to produce the final bias value, which is then added to
//! the base LOD.
//!
//! Once LOD bias has been applied, the resulting value may be *clamped* to a minimum and maximum
//! value to provide the final LOD. A maximum may be specified by the sampler, while a minimum
//! can be specified by the sampler or the shader sampling operation.
//!
//! # Texel filtering
//!
//! Texel filtering operations determine how the color value to be sampled from each mipmap is
//! calculated. The filtering mode can be set independently for different signs of the LOD value:
//! - Negative or zero: **magnification**. The rendered object is closer to the viewer, and each
//!   pixel in the texture corresponds to exactly one or more than one framebuffer pixel.
//! - Positive: **minification**. The rendered object is further from the viewer, and each pixel in
//!   the texture corresponds to less than one framebuffer pixel.

pub mod ycbcr;

use self::ycbcr::SamplerYcbcrConversion;
use crate::{
    device::{Device, DeviceOwned},
    format::FormatFeatures,
    image::{view::ImageViewType, ImageAspects, ImageViewAbstract},
    macros::{impl_id_counter, vulkan_enum},
    pipeline::graphics::depth_stencil::CompareOp,
    shader::ShaderScalarType,
    OomError, RequirementNotMet, RequiresOneOf, VulkanError, VulkanObject,
};
use std::{
    error::Error,
    fmt::{Display, Error as FmtError, Formatter},
    mem::MaybeUninit,
    num::NonZeroU64,
    ops::RangeInclusive,
    ptr,
    sync::Arc,
};

/// Describes how to retrieve data from a sampled image within a shader.
///
/// # Examples
///
/// A simple sampler for most usages:
///
/// ```
/// use vulkano::sampler::{Sampler, SamplerCreateInfo};
///
/// # let device: std::sync::Arc<vulkano::device::Device> = return;
/// let _sampler = Sampler::new(device.clone(), SamplerCreateInfo::simple_repeat_linear_no_mipmap());
/// ```
///
/// More detailed sampler creation:
///
/// ```
/// use vulkano::sampler::{Filter, Sampler, SamplerAddressMode, SamplerCreateInfo};
///
/// # let device: std::sync::Arc<vulkano::device::Device> = return;
/// let _sampler = Sampler::new(device.clone(), SamplerCreateInfo {
///     mag_filter: Filter::Linear,
///     min_filter: Filter::Linear,
///     address_mode: [SamplerAddressMode::Repeat; 3],
///     mip_lod_bias: 1.0,
///     lod: 0.0..=100.0,
///     ..Default::default()
/// })
/// .unwrap();
/// ```
#[derive(Debug)]
pub struct Sampler {
    handle: ash::vk::Sampler,
    device: Arc<Device>,
    id: NonZeroU64,

    address_mode: [SamplerAddressMode; 3],
    anisotropy: Option<f32>,
    border_color: Option<BorderColor>,
    compare: Option<CompareOp>,
    lod: RangeInclusive<f32>,
    mag_filter: Filter,
    min_filter: Filter,
    mip_lod_bias: f32,
    mipmap_mode: SamplerMipmapMode,
    reduction_mode: SamplerReductionMode,
    sampler_ycbcr_conversion: Option<Arc<SamplerYcbcrConversion>>,
    unnormalized_coordinates: bool,
}

impl Sampler {
    /// Creates a new `Sampler`.
    ///
    /// # Panics
    ///
    /// - Panics if `create_info.anisotropy` is `Some` and contains a value less than 1.0.
    /// - Panics if `create_info.lod` is empty.
    pub fn new(
        device: Arc<Device>,
        create_info: SamplerCreateInfo,
    ) -> Result<Arc<Sampler>, SamplerCreationError> {
        let SamplerCreateInfo {
            mag_filter,
            min_filter,
            mipmap_mode,
            address_mode,
            mip_lod_bias,
            anisotropy,
            compare,
            lod,
            border_color,
            unnormalized_coordinates,
            reduction_mode,
            sampler_ycbcr_conversion,
            _ne: _,
        } = create_info;

        for filter in [mag_filter, min_filter] {
            // VUID-VkSamplerCreateInfo-magFilter-parameter
            // VUID-VkSamplerCreateInfo-minFilter-parameter
            filter.validate_device(&device)?;
        }

        // VUID-VkSamplerCreateInfo-mipmapMode-parameter
        mipmap_mode.validate_device(&device)?;

        for mode in address_mode {
            // VUID-VkSamplerCreateInfo-addressModeU-parameter
            // VUID-VkSamplerCreateInfo-addressModeV-parameter
            // VUID-VkSamplerCreateInfo-addressModeW-parameter
            mode.validate_device(&device)?;

            if mode == SamplerAddressMode::ClampToBorder {
                // VUID-VkSamplerCreateInfo-addressModeU-01078
                border_color.validate_device(&device)?;
            }
        }

        if address_mode.contains(&SamplerAddressMode::MirrorClampToEdge) {
            if !device.enabled_features().sampler_mirror_clamp_to_edge
                && !device.enabled_extensions().khr_sampler_mirror_clamp_to_edge
            {
                return Err(SamplerCreationError::RequirementNotMet {
                    required_for: "`create_info.address_mode` contains \
                        `SamplerAddressMode::MirrorClampToEdge`",
                    requires_one_of: RequiresOneOf {
                        features: &["sampler_mirror_clamp_to_edge"],
                        device_extensions: &["khr_sampler_mirror_clamp_to_edge"],
                        ..Default::default()
                    },
                });
            }
        }

        {
            assert!(!lod.is_empty());
            let limit = device.physical_device().properties().max_sampler_lod_bias;
            if mip_lod_bias.abs() > limit {
                return Err(SamplerCreationError::MaxSamplerLodBiasExceeded {
                    requested: mip_lod_bias,
                    maximum: limit,
                });
            }
        }

        // VUID-VkSamplerCreateInfo-samplerMipLodBias-04467
        if device.enabled_extensions().khr_portability_subset
            && !device.enabled_features().sampler_mip_lod_bias
            && mip_lod_bias != 0.0
        {
            return Err(SamplerCreationError::RequirementNotMet {
                required_for: "this device is a portability subset device, and \
                    `create_info.mip_lod_bias` is not zero",
                requires_one_of: RequiresOneOf {
                    features: &["sampler_mip_lod_bias"],
                    ..Default::default()
                },
            });
        }

        let (anisotropy_enable, max_anisotropy) = if let Some(max_anisotropy) = anisotropy {
            assert!(max_anisotropy >= 1.0);

            if !device.enabled_features().sampler_anisotropy {
                return Err(SamplerCreationError::RequirementNotMet {
                    required_for: "`create_info.anisotropy` is `Some`",
                    requires_one_of: RequiresOneOf {
                        features: &["sampler_anisotropy"],
                        ..Default::default()
                    },
                });
            }

            let limit = device.physical_device().properties().max_sampler_anisotropy;
            if max_anisotropy > limit {
                return Err(SamplerCreationError::MaxSamplerAnisotropyExceeded {
                    requested: max_anisotropy,
                    maximum: limit,
                });
            }

            if [mag_filter, min_filter]
                .into_iter()
                .any(|filter| filter == Filter::Cubic)
            {
                return Err(SamplerCreationError::AnisotropyInvalidFilter {
                    mag_filter,
                    min_filter,
                });
            }

            (ash::vk::TRUE, max_anisotropy)
        } else {
            (ash::vk::FALSE, 1.0)
        };

        let (compare_enable, compare_op) = if let Some(compare_op) = compare {
            // VUID-VkSamplerCreateInfo-compareEnable-01080
            compare_op.validate_device(&device)?;

            if reduction_mode != SamplerReductionMode::WeightedAverage {
                return Err(SamplerCreationError::CompareInvalidReductionMode { reduction_mode });
            }

            (ash::vk::TRUE, compare_op)
        } else {
            (ash::vk::FALSE, CompareOp::Never)
        };

        if unnormalized_coordinates {
            if min_filter != mag_filter {
                return Err(
                    SamplerCreationError::UnnormalizedCoordinatesFiltersNotEqual {
                        mag_filter,
                        min_filter,
                    },
                );
            }

            if mipmap_mode != SamplerMipmapMode::Nearest {
                return Err(
                    SamplerCreationError::UnnormalizedCoordinatesInvalidMipmapMode { mipmap_mode },
                );
            }

            if lod != (0.0..=0.0) {
                return Err(SamplerCreationError::UnnormalizedCoordinatesNonzeroLod { lod });
            }

            if address_mode[0..2].iter().any(|mode| {
                !matches!(
                    mode,
                    SamplerAddressMode::ClampToEdge | SamplerAddressMode::ClampToBorder
                )
            }) {
                return Err(
                    SamplerCreationError::UnnormalizedCoordinatesInvalidAddressMode {
                        address_mode: [address_mode[0], address_mode[1]],
                    },
                );
            }

            if anisotropy.is_some() {
                return Err(SamplerCreationError::UnnormalizedCoordinatesAnisotropyEnabled);
            }

            if compare.is_some() {
                return Err(SamplerCreationError::UnnormalizedCoordinatesCompareEnabled);
            }
        }

        let mut sampler_reduction_mode_create_info =
            if reduction_mode != SamplerReductionMode::WeightedAverage {
                if !(device.enabled_features().sampler_filter_minmax
                    || device.enabled_extensions().ext_sampler_filter_minmax)
                {
                    return Err(SamplerCreationError::RequirementNotMet {
                        required_for: "`create_info.reduction_mode` is not \
                            `SamplerReductionMode::WeightedAverage`",
                        requires_one_of: RequiresOneOf {
                            features: &["sampler_filter_minmax"],
                            device_extensions: &["ext_sampler_filter_minmax"],
                            ..Default::default()
                        },
                    });
                }

                // VUID-VkSamplerReductionModeCreateInfo-reductionMode-parameter
                reduction_mode.validate_device(&device)?;

                Some(ash::vk::SamplerReductionModeCreateInfo {
                    reduction_mode: reduction_mode.into(),
                    ..Default::default()
                })
            } else {
                None
            };

        // Don't need to check features because you can't create a conversion object without the
        // feature anyway.
        let mut sampler_ycbcr_conversion_info = if let Some(sampler_ycbcr_conversion) =
            &sampler_ycbcr_conversion
        {
            assert_eq!(&device, sampler_ycbcr_conversion.device());

            // Use unchecked, because all validation has been done by the SamplerYcbcrConversion.
            let potential_format_features = unsafe {
                device
                    .physical_device()
                    .format_properties_unchecked(sampler_ycbcr_conversion.format().unwrap())
                    .potential_format_features()
            };

            // VUID-VkSamplerCreateInfo-minFilter-01645
            if !potential_format_features.intersects(
                FormatFeatures::SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER,
            ) && !(mag_filter == sampler_ycbcr_conversion.chroma_filter()
                && min_filter == sampler_ycbcr_conversion.chroma_filter())
            {
                return Err(
                    SamplerCreationError::SamplerYcbcrConversionChromaFilterMismatch {
                        chroma_filter: sampler_ycbcr_conversion.chroma_filter(),
                        mag_filter,
                        min_filter,
                    },
                );
            }

            // VUID-VkSamplerCreateInfo-addressModeU-01646
            if address_mode
                .into_iter()
                .any(|mode| !matches!(mode, SamplerAddressMode::ClampToEdge))
            {
                return Err(
                    SamplerCreationError::SamplerYcbcrConversionInvalidAddressMode { address_mode },
                );
            }

            // VUID-VkSamplerCreateInfo-addressModeU-01646
            if anisotropy.is_some() {
                return Err(SamplerCreationError::SamplerYcbcrConversionAnisotropyEnabled);
            }

            // VUID-VkSamplerCreateInfo-addressModeU-01646
            if unnormalized_coordinates {
                return Err(
                    SamplerCreationError::SamplerYcbcrConversionUnnormalizedCoordinatesEnabled,
                );
            }

            // VUID-VkSamplerCreateInfo-None-01647
            if reduction_mode != SamplerReductionMode::WeightedAverage {
                return Err(
                    SamplerCreationError::SamplerYcbcrConversionInvalidReductionMode {
                        reduction_mode,
                    },
                );
            }

            Some(ash::vk::SamplerYcbcrConversionInfo {
                conversion: sampler_ycbcr_conversion.handle(),
                ..Default::default()
            })
        } else {
            None
        };

        let mut create_info = ash::vk::SamplerCreateInfo {
            flags: ash::vk::SamplerCreateFlags::empty(),
            mag_filter: mag_filter.into(),
            min_filter: min_filter.into(),
            mipmap_mode: mipmap_mode.into(),
            address_mode_u: address_mode[0].into(),
            address_mode_v: address_mode[1].into(),
            address_mode_w: address_mode[2].into(),
            mip_lod_bias,
            anisotropy_enable,
            max_anisotropy,
            compare_enable,
            compare_op: compare_op.into(),
            min_lod: *lod.start(),
            max_lod: *lod.end(),
            border_color: border_color.into(),
            unnormalized_coordinates: unnormalized_coordinates as ash::vk::Bool32,
            ..Default::default()
        };

        if let Some(sampler_reduction_mode_create_info) =
            sampler_reduction_mode_create_info.as_mut()
        {
            sampler_reduction_mode_create_info.p_next = create_info.p_next;
            create_info.p_next = sampler_reduction_mode_create_info as *const _ as *const _;
        }

        if let Some(sampler_ycbcr_conversion_info) = sampler_ycbcr_conversion_info.as_mut() {
            sampler_ycbcr_conversion_info.p_next = create_info.p_next;
            create_info.p_next = sampler_ycbcr_conversion_info as *const _ as *const _;
        }

        let handle = unsafe {
            let fns = device.fns();
            let mut output = MaybeUninit::uninit();
            (fns.v1_0.create_sampler)(
                device.handle(),
                &create_info,
                ptr::null(),
                output.as_mut_ptr(),
            )
            .result()
            .map_err(VulkanError::from)?;
            output.assume_init()
        };

        Ok(Arc::new(Sampler {
            handle,
            device,
            id: Self::next_id(),
            address_mode,
            anisotropy,
            border_color: address_mode
                .into_iter()
                .any(|mode| mode == SamplerAddressMode::ClampToBorder)
                .then_some(border_color),
            compare,
            lod,
            mag_filter,
            min_filter,
            mip_lod_bias,
            mipmap_mode,
            reduction_mode,
            sampler_ycbcr_conversion,
            unnormalized_coordinates,
        }))
    }

    /// Creates a new `Sampler` from a raw object handle.
    ///
    /// # Safety
    ///
    /// - `handle` must be a valid Vulkan object handle created from `device`.
    /// - `create_info` must match the info used to create the object.
    #[inline]
    pub unsafe fn from_handle(
        device: Arc<Device>,
        handle: ash::vk::Sampler,
        create_info: SamplerCreateInfo,
    ) -> Arc<Sampler> {
        let SamplerCreateInfo {
            mag_filter,
            min_filter,
            mipmap_mode,
            address_mode,
            mip_lod_bias,
            anisotropy,
            compare,
            lod,
            border_color,
            unnormalized_coordinates,
            reduction_mode,
            sampler_ycbcr_conversion,
            _ne: _,
        } = create_info;

        Arc::new(Sampler {
            handle,
            device,
            id: Self::next_id(),
            address_mode,
            anisotropy,
            border_color: address_mode
                .into_iter()
                .any(|mode| mode == SamplerAddressMode::ClampToBorder)
                .then_some(border_color),
            compare,
            lod,
            mag_filter,
            min_filter,
            mip_lod_bias,
            mipmap_mode,
            reduction_mode,
            sampler_ycbcr_conversion,
            unnormalized_coordinates,
        })
    }

    /// Checks whether this sampler is compatible with `image_view`.
    pub fn check_can_sample(
        &self,
        image_view: &(impl ImageViewAbstract + ?Sized),
    ) -> Result<(), SamplerImageViewIncompatibleError> {
        /*
            Note: Most of these checks come from the Instruction/Sampler/Image View Validation
            section, and are not strictly VUIDs.
            https://registry.khronos.org/vulkan/specs/1.2-extensions/html/chap16.html#textures-input-validation
        */

        if self.compare.is_some() {
            // VUID-vkCmdDispatch-None-06479
            if !image_view
                .format_features()
                .intersects(FormatFeatures::SAMPLED_IMAGE_DEPTH_COMPARISON)
            {
                return Err(SamplerImageViewIncompatibleError::DepthComparisonNotSupported);
            }

            // The SPIR-V instruction is one of the OpImage*Dref* instructions, the image
            // view format is one of the depth/stencil formats, and the image view aspect
            // is not VK_IMAGE_ASPECT_DEPTH_BIT.
            if !image_view
                .subresource_range()
                .aspects
                .intersects(ImageAspects::DEPTH)
            {
                return Err(SamplerImageViewIncompatibleError::DepthComparisonWrongAspect);
            }
        } else {
            if !image_view
                .format_features()
                .intersects(FormatFeatures::SAMPLED_IMAGE_FILTER_LINEAR)
            {
                // VUID-vkCmdDispatch-magFilter-04553
                if self.mag_filter == Filter::Linear || self.min_filter == Filter::Linear {
                    return Err(SamplerImageViewIncompatibleError::FilterLinearNotSupported);
                }

                // VUID-vkCmdDispatch-mipmapMode-04770
                if self.mipmap_mode == SamplerMipmapMode::Linear {
                    return Err(SamplerImageViewIncompatibleError::MipmapModeLinearNotSupported);
                }
            }
        }

        if self.mag_filter == Filter::Cubic || self.min_filter == Filter::Cubic {
            // VUID-vkCmdDispatch-None-02692
            if !image_view
                .format_features()
                .intersects(FormatFeatures::SAMPLED_IMAGE_FILTER_CUBIC)
            {
                return Err(SamplerImageViewIncompatibleError::FilterCubicNotSupported);
            }

            // VUID-vkCmdDispatch-filterCubic-02694
            if !image_view.filter_cubic() {
                return Err(SamplerImageViewIncompatibleError::FilterCubicNotSupported);
            }

            // VUID-vkCmdDispatch-filterCubicMinmax-02695
            if matches!(
                self.reduction_mode,
                SamplerReductionMode::Min | SamplerReductionMode::Max
            ) && !image_view.filter_cubic_minmax()
            {
                return Err(SamplerImageViewIncompatibleError::FilterCubicMinmaxNotSupported);
            }
        }

        if let Some(border_color) = self.border_color {
            let aspects = image_view.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!()
                },
            );

            match border_color {
                BorderColor::IntTransparentBlack
                | BorderColor::IntOpaqueBlack
                | BorderColor::IntOpaqueWhite => {
                    // The sampler borderColor is an integer type and the image view
                    // format is not one of the VkFormat integer types or a stencil
                    // component of a depth/stencil format.
                    if !matches!(
                        view_scalar_type,
                        ShaderScalarType::Sint | ShaderScalarType::Uint
                    ) {
                        return Err(
                            SamplerImageViewIncompatibleError::BorderColorFormatNotCompatible,
                        );
                    }
                }
                BorderColor::FloatTransparentBlack
                | BorderColor::FloatOpaqueBlack
                | BorderColor::FloatOpaqueWhite => {
                    // The sampler borderColor is a float type and the image view
                    // format is not one of the VkFormat float types or a depth
                    // component of a depth/stencil format.
                    if !matches!(view_scalar_type, ShaderScalarType::Float) {
                        return Err(
                            SamplerImageViewIncompatibleError::BorderColorFormatNotCompatible,
                        );
                    }
                }
            }

            // The sampler borderColor is one of the opaque black colors
            // (VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK or VK_BORDER_COLOR_INT_OPAQUE_BLACK)
            // and the image view VkComponentSwizzle for any of the VkComponentMapping
            // components is not the identity swizzle, and
            // VkPhysicalDeviceBorderColorSwizzleFeaturesEXT::borderColorSwizzleFromImage
            // feature is not enabled, and
            // VkSamplerBorderColorComponentMappingCreateInfoEXT is not specified.
            if matches!(
                border_color,
                BorderColor::FloatOpaqueBlack | BorderColor::IntOpaqueBlack
            ) && !image_view.component_mapping().is_identity()
            {
                return Err(
                    SamplerImageViewIncompatibleError::BorderColorOpaqueBlackNotIdentitySwizzled,
                );
            }
        }

        // The sampler unnormalizedCoordinates is VK_TRUE and any of the limitations of
        // unnormalized coordinates are violated.
        // https://registry.khronos.org/vulkan/specs/1.2-extensions/html/chap13.html#samplers-unnormalizedCoordinates
        if self.unnormalized_coordinates {
            // The viewType must be either VK_IMAGE_VIEW_TYPE_1D or
            // VK_IMAGE_VIEW_TYPE_2D.
            // VUID-vkCmdDispatch-None-02702
            if !matches!(
                image_view.view_type(),
                ImageViewType::Dim1d | ImageViewType::Dim2d
            ) {
                return Err(
                    SamplerImageViewIncompatibleError::UnnormalizedCoordinatesViewTypeNotCompatible,
                );
            }

            // The image view must have a single layer and a single mip level.
            if image_view.subresource_range().mip_levels.end
                - image_view.subresource_range().mip_levels.start
                != 1
            {
                return Err(
                    SamplerImageViewIncompatibleError::UnnormalizedCoordinatesMultipleMipLevels,
                );
            }
        }

        Ok(())
    }

    /// Returns the address modes for the u, v and w coordinates.
    #[inline]
    pub fn address_mode(&self) -> [SamplerAddressMode; 3] {
        self.address_mode
    }

    /// Returns the anisotropy mode.
    #[inline]
    pub fn anisotropy(&self) -> Option<f32> {
        self.anisotropy
    }

    /// Returns the border color if one is used by this sampler.
    #[inline]
    pub fn border_color(&self) -> Option<BorderColor> {
        self.border_color
    }

    /// Returns the compare operation if the sampler is a compare-mode sampler.
    #[inline]
    pub fn compare(&self) -> Option<CompareOp> {
        self.compare
    }

    /// Returns the LOD range.
    #[inline]
    pub fn lod(&self) -> RangeInclusive<f32> {
        self.lod.clone()
    }

    /// Returns the magnification filter.
    #[inline]
    pub fn mag_filter(&self) -> Filter {
        self.mag_filter
    }

    /// Returns the minification filter.
    #[inline]
    pub fn min_filter(&self) -> Filter {
        self.min_filter
    }

    /// Returns the mip LOD bias.
    #[inline]
    pub fn mip_lod_bias(&self) -> f32 {
        self.mip_lod_bias
    }

    /// Returns the mipmap mode.
    #[inline]
    pub fn mipmap_mode(&self) -> SamplerMipmapMode {
        self.mipmap_mode
    }

    /// Returns the reduction mode.
    #[inline]
    pub fn reduction_mode(&self) -> SamplerReductionMode {
        self.reduction_mode
    }

    /// Returns a reference to the sampler YCbCr conversion of this sampler, if any.
    #[inline]
    pub fn sampler_ycbcr_conversion(&self) -> Option<&Arc<SamplerYcbcrConversion>> {
        self.sampler_ycbcr_conversion.as_ref()
    }

    /// Returns true if the sampler uses unnormalized coordinates.
    #[inline]
    pub fn unnormalized_coordinates(&self) -> bool {
        self.unnormalized_coordinates
    }
}

impl Drop for Sampler {
    #[inline]
    fn drop(&mut self) {
        unsafe {
            let fns = self.device.fns();
            (fns.v1_0.destroy_sampler)(self.device.handle(), self.handle, ptr::null());
        }
    }
}

unsafe impl VulkanObject for Sampler {
    type Handle = ash::vk::Sampler;

    #[inline]
    fn handle(&self) -> Self::Handle {
        self.handle
    }
}

unsafe impl DeviceOwned for Sampler {
    #[inline]
    fn device(&self) -> &Arc<Device> {
        &self.device
    }
}

impl_id_counter!(Sampler);

/// Error that can happen when creating an instance.
#[derive(Clone, Debug, PartialEq)]
pub enum SamplerCreationError {
    /// Not enough memory.
    OomError(OomError),

    /// Too many sampler objects have been created. You must destroy some before creating new ones.
    /// Note the specs guarantee that at least 4000 samplers can exist simultaneously.
    TooManyObjects,

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

    /// Anisotropy was enabled with an invalid filter.
    AnisotropyInvalidFilter {
        mag_filter: Filter,
        min_filter: Filter,
    },

    /// Depth comparison was enabled with an invalid reduction mode.
    CompareInvalidReductionMode {
        reduction_mode: SamplerReductionMode,
    },

    /// The requested anisotropy level exceeds the device's limits.
    MaxSamplerAnisotropyExceeded {
        /// The value that was requested.
        requested: f32,
        /// The maximum supported value.
        maximum: f32,
    },

    /// The requested mip lod bias exceeds the device's limits.
    MaxSamplerLodBiasExceeded {
        /// The value that was requested.
        requested: f32,
        /// The maximum supported value.
        maximum: f32,
    },

    /// Sampler YCbCr conversion was enabled together with anisotropy.
    SamplerYcbcrConversionAnisotropyEnabled,

    /// Sampler YCbCr conversion was enabled, and its format does not support
    /// `sampled_image_ycbcr_conversion_separate_reconstruction_filter`, but `mag_filter` or
    /// `min_filter` did not match the conversion's `chroma_filter`.
    SamplerYcbcrConversionChromaFilterMismatch {
        chroma_filter: Filter,
        mag_filter: Filter,
        min_filter: Filter,
    },

    /// Sampler YCbCr conversion was enabled, but the address mode for `u`, `v` or `w` was
    /// something other than `ClampToEdge`.
    SamplerYcbcrConversionInvalidAddressMode {
        address_mode: [SamplerAddressMode; 3],
    },

    /// Sampler YCbCr conversion was enabled, but the reduction mode was something other than
    /// `WeightedAverage`.
    SamplerYcbcrConversionInvalidReductionMode {
        reduction_mode: SamplerReductionMode,
    },

    /// Sampler YCbCr conversion was enabled together with unnormalized coordinates.
    SamplerYcbcrConversionUnnormalizedCoordinatesEnabled,

    /// Unnormalized coordinates were enabled together with anisotropy.
    UnnormalizedCoordinatesAnisotropyEnabled,

    /// Unnormalized coordinates were enabled together with depth comparison.
    UnnormalizedCoordinatesCompareEnabled,

    /// Unnormalized coordinates were enabled, but the min and mag filters were not equal.
    UnnormalizedCoordinatesFiltersNotEqual {
        mag_filter: Filter,
        min_filter: Filter,
    },

    /// Unnormalized coordinates were enabled, but the address mode for `u` or `v` was something
    /// other than `ClampToEdge` or `ClampToBorder`.
    UnnormalizedCoordinatesInvalidAddressMode {
        address_mode: [SamplerAddressMode; 2],
    },

    /// Unnormalized coordinates were enabled, but the mipmap mode was not `Nearest`.
    UnnormalizedCoordinatesInvalidMipmapMode { mipmap_mode: SamplerMipmapMode },

    /// Unnormalized coordinates were enabled, but the LOD range was not zero.
    UnnormalizedCoordinatesNonzeroLod { lod: RangeInclusive<f32> },
}

impl Error for SamplerCreationError {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        match self {
            SamplerCreationError::OomError(err) => Some(err),
            _ => None,
        }
    }
}

impl Display for SamplerCreationError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
        match self {
            Self::OomError(_) => write!(f, "not enough memory available"),
            Self::TooManyObjects => write!(f, "too many simultaneous sampler objects"),
            Self::RequirementNotMet {
                required_for,
                requires_one_of,
            } => write!(
                f,
                "a requirement was not met for: {}; requires one of: {}",
                required_for, requires_one_of,
            ),
            Self::AnisotropyInvalidFilter { .. } => {
                write!(f, "anisotropy was enabled with an invalid filter")
            }
            Self::CompareInvalidReductionMode { .. } => write!(
                f,
                "depth comparison was enabled with an invalid reduction mode",
            ),
            Self::MaxSamplerAnisotropyExceeded { .. } => {
                write!(f, "max_sampler_anisotropy limit exceeded")
            }
            Self::MaxSamplerLodBiasExceeded { .. } => write!(f, "mip lod bias limit exceeded"),
            Self::SamplerYcbcrConversionAnisotropyEnabled => write!(
                f,
                "sampler YCbCr conversion was enabled together with anisotropy",
            ),
            Self::SamplerYcbcrConversionChromaFilterMismatch { .. } => write!(
                f,
                "sampler YCbCr conversion was enabled, and its format does not support
                `sampled_image_ycbcr_conversion_separate_reconstruction_filter`, but `mag_filter`
                or `min_filter` did not match the conversion's `chroma_filter`",
            ),
            Self::SamplerYcbcrConversionInvalidAddressMode { .. } => write!(
                f,
                "sampler YCbCr conversion was enabled, but the address mode for u, v or w was
                something other than `ClampToEdge`",
            ),
            Self::SamplerYcbcrConversionInvalidReductionMode { .. } => write!(
                f,
                "sampler YCbCr conversion was enabled, but the reduction mode was something other \
                than `WeightedAverage`",
            ),
            Self::SamplerYcbcrConversionUnnormalizedCoordinatesEnabled => write!(
                f,
                "sampler YCbCr conversion was enabled together with unnormalized coordinates",
            ),
            Self::UnnormalizedCoordinatesAnisotropyEnabled => write!(
                f,
                "unnormalized coordinates were enabled together with anisotropy",
            ),
            Self::UnnormalizedCoordinatesCompareEnabled => write!(
                f,
                "unnormalized coordinates were enabled together with depth comparison",
            ),
            Self::UnnormalizedCoordinatesFiltersNotEqual { .. } => write!(
                f,
                "unnormalized coordinates were enabled, but the min and mag filters were not equal",
            ),
            Self::UnnormalizedCoordinatesInvalidAddressMode { .. } => write!(
                f,
                "unnormalized coordinates were enabled, but the address mode for u or v was \
                something other than `ClampToEdge` or `ClampToBorder`",
            ),
            Self::UnnormalizedCoordinatesInvalidMipmapMode { .. } => write!(
                f,
                "unnormalized coordinates were enabled, but the mipmap mode was not `Nearest`",
            ),
            Self::UnnormalizedCoordinatesNonzeroLod { .. } => write!(
                f,
                "unnormalized coordinates were enabled, but the LOD range was not zero",
            ),
        }
    }
}

impl From<OomError> for SamplerCreationError {
    fn from(err: OomError) -> Self {
        Self::OomError(err)
    }
}

impl From<VulkanError> for SamplerCreationError {
    fn from(err: VulkanError) -> Self {
        match err {
            err @ VulkanError::OutOfHostMemory => Self::OomError(OomError::from(err)),
            err @ VulkanError::OutOfDeviceMemory => Self::OomError(OomError::from(err)),
            VulkanError::TooManyObjects => Self::TooManyObjects,
            _ => panic!("unexpected error: {:?}", err),
        }
    }
}

impl From<RequirementNotMet> for SamplerCreationError {
    fn from(err: RequirementNotMet) -> Self {
        Self::RequirementNotMet {
            required_for: err.required_for,
            requires_one_of: err.requires_one_of,
        }
    }
}

/// Parameters to create a new `Sampler`.
#[derive(Clone, Debug)]
pub struct SamplerCreateInfo {
    /// How the sampled value of a single mipmap should be calculated,
    /// when magnification is applied (LOD <= 0.0).
    ///
    /// The default value is [`Nearest`](Filter::Nearest).
    pub mag_filter: Filter,

    /// How the sampled value of a single mipmap should be calculated,
    /// when minification is applied (LOD > 0.0).
    ///
    /// The default value is [`Nearest`](Filter::Nearest).
    pub min_filter: Filter,

    /// How the final sampled value should be calculated from the samples of individual
    /// mipmaps.
    ///
    /// The default value is [`Nearest`](SamplerMipmapMode::Nearest).
    pub mipmap_mode: SamplerMipmapMode,

    /// How out-of-range texture coordinates should be treated, for the `u`, `v` and `w` texture
    /// coordinate indices respectively.
    ///
    /// The default value is [`ClampToEdge`](SamplerAddressMode::ClampToEdge).
    pub address_mode: [SamplerAddressMode; 3],

    /// The bias value to be added to the base LOD before clamping.
    ///
    /// The absolute value of the provided value must not exceed the
    /// [`max_sampler_lod_bias`](crate::device::Properties::max_sampler_lod_bias) limit of the
    /// device.
    ///
    /// On [portability subset](crate::instance#portability-subset-devices-and-the-enumerate_portability-flag)
    /// devices, if `mip_lod_bias` is not `0.0`, the
    /// [`sampler_mip_lod_bias`](crate::device::Features::sampler_mip_lod_bias)
    /// feature must be enabled on the device.
    ///
    /// The default value is `0.0`.
    pub mip_lod_bias: f32,

    /// Whether anisotropic texel filtering is enabled (`Some`), and the maximum anisotropy value
    /// to use if it is enabled.
    ///
    /// Anisotropic filtering is a special filtering mode that takes into account the differences in
    /// scaling between the horizontal and vertical framebuffer axes.
    ///
    /// If set to `Some`, the [`sampler_anisotropy`](crate::device::Features::sampler_anisotropy)
    /// feature must be enabled on the device, the provided maximum value must not exceed the
    /// [`max_sampler_anisotropy`](crate::device::Properties::max_sampler_anisotropy) limit, and
    /// the [`Cubic`](Filter::Cubic) filter must not be used.
    ///
    /// The default value is `None`.
    pub anisotropy: Option<f32>,

    /// Whether depth comparison is enabled (`Some`), and the comparison operator to use if it is
    /// enabled.
    ///
    /// Depth comparison is an alternative mode for samplers that can be used in combination with
    /// image views specifying the depth aspect. Instead of returning a value that is sampled from
    /// the image directly, a comparison operation is applied between the sampled value and a
    /// reference value that is specified as part of the operation. The result is binary: 1.0 if the
    /// operation returns `true`, 0.0 if it returns `false`.
    ///
    /// If set to `Some`, the `reduction_mode` must be set to
    /// [`WeightedAverage`](SamplerReductionMode::WeightedAverage).
    ///
    /// On [portability subset](crate::instance#portability-subset-devices-and-the-enumerate_portability-flag)
    /// devices, if the sampler is going to be used as a mutable sampler (written to descriptor sets
    /// rather than being an immutable part of a descriptor set layout), the
    /// [`mutable_comparison_samplers`](crate::device::Features::mutable_comparison_samplers)
    /// feature must be enabled on the device.
    ///
    /// The default value is `None`.
    pub compare: Option<CompareOp>,

    /// The range that LOD values must be clamped to.
    ///
    /// If the end of the range is set to [`LOD_CLAMP_NONE`], it is unbounded.
    ///
    /// The default value is `0.0..=0.0`.
    pub lod: RangeInclusive<f32>,

    /// The border color to use if `address_mode` is set to
    /// [`ClampToBorder`](SamplerAddressMode::ClampToBorder).
    ///
    /// The default value is [`FloatTransparentBlack`](BorderColor::FloatTransparentBlack).
    pub border_color: BorderColor,

    /// Whether unnormalized texture coordinates are enabled.
    ///
    /// When a sampler is set to use unnormalized coordinates as input, the texture coordinates are
    /// not scaled by the size of the image, and therefore range up to the size of the image rather
    /// than 1.0. Enabling this comes with several restrictions:
    /// - `min_filter` and `mag_filter` must be equal.
    /// - `mipmap_mode` must be [`Nearest`](SamplerMipmapMode::Nearest).
    /// - The `lod` range must be `0.0..=0.0`.
    /// - `address_mode` for u and v must be either
    ///   [`ClampToEdge`](`SamplerAddressMode::ClampToEdge`) or
    ///   [`ClampToBorder`](`SamplerAddressMode::ClampToBorder`).
    /// - Anisotropy and depth comparison must be disabled.
    ///
    /// Some restrictions also apply to the image view being sampled:
    /// - The view type must be [`Dim1d`](crate::image::view::ImageViewType::Dim1d) or
    ///   [`Dim2d`](crate::image::view::ImageViewType::Dim2d). Arrayed types are not allowed.
    /// - It must have a single mipmap level.
    ///
    /// Finally, restrictions apply to the sampling operations that can be used in a shader:
    /// - Only explicit LOD operations are allowed, implicit LOD operations are not.
    /// - Sampling with projection is not allowed.
    /// - Sampling with an LOD bias is not allowed.
    /// - Sampling with an offset is not allowed.
    ///
    /// The default value is `false`.
    pub unnormalized_coordinates: bool,

    /// How the value sampled from a mipmap should be calculated from the selected
    /// pixels, for the `Linear` and `Cubic` filters.
    ///
    /// The default value is [`WeightedAverage`](SamplerReductionMode::WeightedAverage).
    pub reduction_mode: SamplerReductionMode,

    /// Adds a sampler YCbCr conversion to the sampler.
    ///
    /// If set to `Some`, several restrictions apply:
    /// - If the `format` of `conversion` does not support
    ///   `sampled_image_ycbcr_conversion_separate_reconstruction_filter`, then `mag_filter` and
    ///   `min_filter` must be equal to the `chroma_filter` of `conversion`.
    /// - `address_mode` for u, v and w must be [`ClampToEdge`](`SamplerAddressMode::ClampToEdge`).
    /// - Anisotropy and unnormalized coordinates must be disabled.
    /// - The `reduction_mode` must be [`WeightedAverage`](SamplerReductionMode::WeightedAverage).
    ///
    /// In addition, the sampler must only be used as an immutable sampler within a descriptor set
    /// layout, and only in a combined image sampler descriptor.
    ///
    /// The default value is `None`.
    pub sampler_ycbcr_conversion: Option<Arc<SamplerYcbcrConversion>>,

    pub _ne: crate::NonExhaustive,
}

impl Default for SamplerCreateInfo {
    #[inline]
    fn default() -> Self {
        Self {
            mag_filter: Filter::Nearest,
            min_filter: Filter::Nearest,
            mipmap_mode: SamplerMipmapMode::Nearest,
            address_mode: [SamplerAddressMode::ClampToEdge; 3],
            mip_lod_bias: 0.0,
            anisotropy: None,
            compare: None,
            lod: 0.0..=0.0,
            border_color: BorderColor::FloatTransparentBlack,
            unnormalized_coordinates: false,
            reduction_mode: SamplerReductionMode::WeightedAverage,
            sampler_ycbcr_conversion: None,
            _ne: crate::NonExhaustive(()),
        }
    }
}

impl SamplerCreateInfo {
    /// Shortcut for creating a sampler with linear sampling, linear mipmaps, and with the repeat
    /// mode for borders.
    #[inline]
    pub fn simple_repeat_linear() -> Self {
        Self {
            mag_filter: Filter::Linear,
            min_filter: Filter::Linear,
            mipmap_mode: SamplerMipmapMode::Linear,
            address_mode: [SamplerAddressMode::Repeat; 3],
            lod: 0.0..=LOD_CLAMP_NONE,
            ..Default::default()
        }
    }

    /// Shortcut for creating a sampler with linear sampling, that only uses the main level of
    /// images, and with the repeat mode for borders.
    #[inline]
    pub fn simple_repeat_linear_no_mipmap() -> Self {
        Self {
            mag_filter: Filter::Linear,
            min_filter: Filter::Linear,
            address_mode: [SamplerAddressMode::Repeat; 3],
            lod: 0.0..=1.0,
            ..Default::default()
        }
    }
}

/// A special value to indicate that the maximum LOD should not be clamped.
pub const LOD_CLAMP_NONE: f32 = ash::vk::LOD_CLAMP_NONE;

/// A mapping between components of a source format and components read by a shader.
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ComponentMapping {
    /// First component.
    pub r: ComponentSwizzle,
    /// Second component.
    pub g: ComponentSwizzle,
    /// Third component.
    pub b: ComponentSwizzle,
    /// Fourth component.
    pub a: ComponentSwizzle,
}

impl ComponentMapping {
    /// Creates a `ComponentMapping` with all components identity swizzled.
    #[inline]
    pub fn identity() -> Self {
        Self::default()
    }

    /// Returns `true` if all components are identity swizzled,
    /// meaning that all the members are `Identity` or the name of that member.
    ///
    /// Certain operations require views that are identity swizzled, and will return an error
    /// otherwise. For example, attaching a view to a framebuffer is only possible if the view is
    /// identity swizzled.
    #[inline]
    pub fn is_identity(&self) -> bool {
        self.r_is_identity() && self.g_is_identity() && self.b_is_identity() && self.a_is_identity()
    }

    /// Returns `true` if the red component mapping is identity swizzled.
    #[inline]
    pub fn r_is_identity(&self) -> bool {
        matches!(self.r, ComponentSwizzle::Identity | ComponentSwizzle::Red)
    }

    /// Returns `true` if the green component mapping is identity swizzled.
    #[inline]
    pub fn g_is_identity(&self) -> bool {
        matches!(self.g, ComponentSwizzle::Identity | ComponentSwizzle::Green)
    }

    /// Returns `true` if the blue component mapping is identity swizzled.
    #[inline]
    pub fn b_is_identity(&self) -> bool {
        matches!(self.b, ComponentSwizzle::Identity | ComponentSwizzle::Blue)
    }

    /// Returns `true` if the alpha component mapping is identity swizzled.
    #[inline]
    pub fn a_is_identity(&self) -> bool {
        matches!(self.a, ComponentSwizzle::Identity | ComponentSwizzle::Alpha)
    }

    /// Returns the component indices that each component reads from. The index is `None` if the
    /// component has a fixed value and is not read from anywhere (`Zero` or `One`).
    #[inline]
    pub fn component_map(&self) -> [Option<usize>; 4] {
        [
            match self.r {
                ComponentSwizzle::Identity => Some(0),
                ComponentSwizzle::Zero => None,
                ComponentSwizzle::One => None,
                ComponentSwizzle::Red => Some(0),
                ComponentSwizzle::Green => Some(1),
                ComponentSwizzle::Blue => Some(2),
                ComponentSwizzle::Alpha => Some(3),
            },
            match self.g {
                ComponentSwizzle::Identity => Some(1),
                ComponentSwizzle::Zero => None,
                ComponentSwizzle::One => None,
                ComponentSwizzle::Red => Some(0),
                ComponentSwizzle::Green => Some(1),
                ComponentSwizzle::Blue => Some(2),
                ComponentSwizzle::Alpha => Some(3),
            },
            match self.b {
                ComponentSwizzle::Identity => Some(2),
                ComponentSwizzle::Zero => None,
                ComponentSwizzle::One => None,
                ComponentSwizzle::Red => Some(0),
                ComponentSwizzle::Green => Some(1),
                ComponentSwizzle::Blue => Some(2),
                ComponentSwizzle::Alpha => Some(3),
            },
            match self.a {
                ComponentSwizzle::Identity => Some(3),
                ComponentSwizzle::Zero => None,
                ComponentSwizzle::One => None,
                ComponentSwizzle::Red => Some(0),
                ComponentSwizzle::Green => Some(1),
                ComponentSwizzle::Blue => Some(2),
                ComponentSwizzle::Alpha => Some(3),
            },
        ]
    }
}

impl From<ComponentMapping> for ash::vk::ComponentMapping {
    #[inline]
    fn from(value: ComponentMapping) -> Self {
        Self {
            r: value.r.into(),
            g: value.g.into(),
            b: value.b.into(),
            a: value.a.into(),
        }
    }
}

vulkan_enum! {
    #[non_exhaustive]

    /// Describes the value that an individual component must return when being accessed.
    ComponentSwizzle = ComponentSwizzle(i32);

    /// Returns the value that this component should normally have.
    ///
    /// This is the `Default` value.
    Identity = IDENTITY,

    /// Always return zero.
    Zero = ZERO,

    /// Always return one.
    One = ONE,

    /// Returns the value of the first component.
    Red = R,

    /// Returns the value of the second component.
    Green = G,

    /// Returns the value of the third component.
    Blue = B,

    /// Returns the value of the fourth component.
    Alpha = A,
}

impl Default for ComponentSwizzle {
    #[inline]
    fn default() -> ComponentSwizzle {
        ComponentSwizzle::Identity
    }
}

vulkan_enum! {
    #[non_exhaustive]

    /// Describes how the color of each pixel should be determined.
    Filter = Filter(i32);

    /// The pixel whose center is nearest to the requested coordinates is taken from the source
    /// and its value is returned as-is.
    Nearest = NEAREST,

    /// The 8/4/2 pixels (depending on view dimensionality) whose center surround the requested
    /// coordinates are taken, then their values are combined according to the chosen
    /// `reduction_mode`.
    Linear = LINEAR,

    /// The 64/16/4 pixels (depending on the view dimensionality) whose center surround the
    /// requested coordinates are taken, then their values are combined according to the chosen
    /// `reduction_mode`.
    ///
    /// The [`ext_filter_cubic`](crate::device::DeviceExtensions::ext_filter_cubic) extension must
    /// be enabled on the device, and anisotropy must be disabled. Sampled image views must have
    /// a type of [`Dim2d`](crate::image::view::ImageViewType::Dim2d).
    Cubic = CUBIC_EXT {
        device_extensions: [ext_filter_cubic, img_filter_cubic],
    },
}

vulkan_enum! {
    #[non_exhaustive]

    /// Describes which mipmap from the source to use.
    SamplerMipmapMode = SamplerMipmapMode(i32);

    /// Use the mipmap whose dimensions are the nearest to the dimensions of the destination.
    Nearest = NEAREST,

    /// Take the mipmap whose dimensions are no greater than that of the destination together
    /// with the next higher level mipmap, calculate the value for both, and interpolate them.
    Linear = LINEAR,
}

vulkan_enum! {
    #[non_exhaustive]

    /// How the sampler should behave when it needs to access a pixel that is out of range of the
    /// texture.
    SamplerAddressMode = SamplerAddressMode(i32);

    /// Repeat the texture. In other words, the pixel at coordinate `x + 1.0` is the same as the
    /// one at coordinate `x`.
    Repeat = REPEAT,

    /// Repeat the texture but mirror it at every repetition. In other words, the pixel at
    /// coordinate `x + 1.0` is the same as the one at coordinate `1.0 - x`.
    MirroredRepeat = MIRRORED_REPEAT,

    /// The coordinates are clamped to the valid range. Coordinates below 0.0 have the same value
    /// as coordinate 0.0. Coordinates over 1.0 have the same value as coordinate 1.0.
    ClampToEdge = CLAMP_TO_EDGE,

    /// Any pixel out of range is colored using the colour selected with the `border_color` on the
    /// `SamplerBuilder`.
    ///
    /// When this mode is chosen, the numeric type of the image view's format must match the border
    /// color. When using a floating-point border color, the sampler can only be used with
    /// floating-point or depth image views. When using an integer border color, the sampler can
    /// only be used with integer or stencil image views. In addition to this, you can't use an
    /// opaque black border color with an image view that uses component swizzling.
    ClampToBorder = CLAMP_TO_BORDER,

    /// Similar to `MirroredRepeat`, except that coordinates are clamped to the range
    /// `[-1.0, 1.0]`.
    ///
    /// The [`sampler_mirror_clamp_to_edge`](crate::device::Features::sampler_mirror_clamp_to_edge)
    /// feature or the
    /// [`khr_sampler_mirror_clamp_to_edge`](crate::device::DeviceExtensions::khr_sampler_mirror_clamp_to_edge)
    /// extension must be enabled on the device.
    MirrorClampToEdge = MIRROR_CLAMP_TO_EDGE {
        api_version: V1_2,
        device_extensions: [khr_sampler_mirror_clamp_to_edge],
    },
}

vulkan_enum! {
    #[non_exhaustive]

    /// The color to use for the border of an image.
    ///
    /// Only relevant if you use `ClampToBorder`.
    ///
    /// Using a border color restricts the sampler to either floating-point images or integer images.
    BorderColor = BorderColor(i32);

    /// The value `(0.0, 0.0, 0.0, 0.0)`. Can only be used with floating-point images.
    FloatTransparentBlack = FLOAT_TRANSPARENT_BLACK,

    /// The value `(0, 0, 0, 0)`. Can only be used with integer images.
    IntTransparentBlack = INT_TRANSPARENT_BLACK,

    /// The value `(0.0, 0.0, 0.0, 1.0)`. Can only be used with floating-point identity-swizzled
    /// images.
    FloatOpaqueBlack = FLOAT_OPAQUE_BLACK,

    /// The value `(0, 0, 0, 1)`. Can only be used with integer identity-swizzled images.
    IntOpaqueBlack = INT_OPAQUE_BLACK,

    /// The value `(1.0, 1.0, 1.0, 1.0)`. Can only be used with floating-point images.
    FloatOpaqueWhite = FLOAT_OPAQUE_WHITE,

    /// The value `(1, 1, 1, 1)`. Can only be used with integer images.
    IntOpaqueWhite = INT_OPAQUE_WHITE,

    /* TODO: enable
    // TODO: document
    FloatCustom = VK_BORDER_COLOR_FLOAT_CUSTOM_EXT {
        device_extensions: [ext_custom_border_color],
    },*/

    /* TODO: enable
    // TODO: document
    IntCustom = INT_CUSTOM_EXT {
        device_extensions: [ext_custom_border_color],
    },*/
}

vulkan_enum! {
    #[non_exhaustive]

    /// Describes how the value sampled from a mipmap should be calculated from the selected
    /// pixels, for the `Linear` and `Cubic` filters.
    SamplerReductionMode = SamplerReductionMode(i32);

    /// Calculates a weighted average of the selected pixels. For `Linear` filtering the pixels
    /// are evenly weighted, for `Cubic` filtering they use Catmull-Rom weights.
    WeightedAverage = WEIGHTED_AVERAGE,

    /// Calculates the minimum of the selected pixels.
    ///
    /// The [`sampler_filter_minmax`](crate::device::Features::sampler_filter_minmax)
    /// feature or the
    /// [`ext_sampler_filter_minmax`](crate::device::DeviceExtensions::ext_sampler_filter_minmax)
    /// extension must be enabled on the device.
    Min = MIN,

    /// Calculates the maximum of the selected pixels.
    ///
    /// The [`sampler_filter_minmax`](crate::device::Features::sampler_filter_minmax)
    /// feature or the
    /// [`ext_sampler_filter_minmax`](crate::device::DeviceExtensions::ext_sampler_filter_minmax)
    /// extension must be enabled on the device.
    Max = MAX,
}

#[derive(Clone, Copy, Debug)]
pub enum SamplerImageViewIncompatibleError {
    /// The sampler has a border color with a numeric type different from the image view.
    BorderColorFormatNotCompatible,

    /// The sampler has an opaque black border color, but the image view is not identity swizzled.
    BorderColorOpaqueBlackNotIdentitySwizzled,

    /// The sampler has depth comparison enabled, but this is not supported by the image view.
    DepthComparisonNotSupported,

    /// The sampler has depth comparison enabled, but the image view does not select the `depth`
    /// aspect.
    DepthComparisonWrongAspect,

    /// The sampler uses a linear filter, but this is not supported by the image view's format
    /// features.
    FilterLinearNotSupported,

    /// The sampler uses a cubic filter, but this is not supported by the image view's format
    /// features.
    FilterCubicNotSupported,

    /// The sampler uses a cubic filter with a `Min` or `Max` reduction mode, but this is not
    /// supported by the image view's format features.
    FilterCubicMinmaxNotSupported,

    /// The sampler uses a linear mipmap mode, but this is not supported by the image view's format
    /// features.
    MipmapModeLinearNotSupported,

    /// The sampler uses unnormalized coordinates, but the image view has multiple mip levels.
    UnnormalizedCoordinatesMultipleMipLevels,

    /// The sampler uses unnormalized coordinates, but the image view has a type other than `Dim1d`
    /// or `Dim2d`.
    UnnormalizedCoordinatesViewTypeNotCompatible,
}

impl Error for SamplerImageViewIncompatibleError {}

impl Display for SamplerImageViewIncompatibleError {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
        match self {
            Self::BorderColorFormatNotCompatible => write!(
                f,
                "the sampler has a border color with a numeric type different from the image view",
            ),
            Self::BorderColorOpaqueBlackNotIdentitySwizzled => write!(
                f,
                "the sampler has an opaque black border color, but the image view is not identity \
                swizzled",
            ),
            Self::DepthComparisonNotSupported => write!(
                f,
                "the sampler has depth comparison enabled, but this is not supported by the image \
                view",
            ),
            Self::DepthComparisonWrongAspect => write!(
                f,
                "the sampler has depth comparison enabled, but the image view does not select the \
                `depth` aspect",
            ),
            Self::FilterLinearNotSupported => write!(
                f,
                "the sampler uses a linear filter, but this is not supported by the image view's \
                format features",
            ),
            Self::FilterCubicNotSupported => write!(
                f,
                "the sampler uses a cubic filter, but this is not supported by the image view's \
                format features",
            ),
            Self::FilterCubicMinmaxNotSupported => write!(
                f,
                "the sampler uses a cubic filter with a `Min` or `Max` reduction mode, but this is \
                not supported by the image view's format features",
            ),
            Self::MipmapModeLinearNotSupported => write!(
                f,
                "the sampler uses a linear mipmap mode, but this is not supported by the image \
                view's format features",
            ),
            Self::UnnormalizedCoordinatesMultipleMipLevels => write!(
                f,
                "the sampler uses unnormalized coordinates, but the image view has multiple mip \
                levels",
            ),
            Self::UnnormalizedCoordinatesViewTypeNotCompatible => write!(
                f,
                "the sampler uses unnormalized coordinates, but the image view has a type other \
                than `Dim1d` or `Dim2d`",
            ),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::{
        pipeline::graphics::depth_stencil::CompareOp,
        sampler::{
            Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerCreationError,
            SamplerReductionMode,
        },
        RequiresOneOf,
    };

    #[test]
    fn create_regular() {
        let (device, _queue) = gfx_dev_and_queue!();

        let s = Sampler::new(
            device,
            SamplerCreateInfo {
                mag_filter: Filter::Linear,
                min_filter: Filter::Linear,
                address_mode: [SamplerAddressMode::Repeat; 3],
                mip_lod_bias: 1.0,
                lod: 0.0..=2.0,
                ..Default::default()
            },
        )
        .unwrap();
        assert!(s.compare().is_none());
        assert!(!s.unnormalized_coordinates());
    }

    #[test]
    fn create_compare() {
        let (device, _queue) = gfx_dev_and_queue!();

        let s = Sampler::new(
            device,
            SamplerCreateInfo {
                mag_filter: Filter::Linear,
                min_filter: Filter::Linear,
                address_mode: [SamplerAddressMode::Repeat; 3],
                mip_lod_bias: 1.0,
                compare: Some(CompareOp::Less),
                lod: 0.0..=2.0,
                ..Default::default()
            },
        )
        .unwrap();
        assert!(s.compare().is_some());
        assert!(!s.unnormalized_coordinates());
    }

    #[test]
    fn create_unnormalized() {
        let (device, _queue) = gfx_dev_and_queue!();

        let s = Sampler::new(
            device,
            SamplerCreateInfo {
                mag_filter: Filter::Linear,
                min_filter: Filter::Linear,
                unnormalized_coordinates: true,
                ..Default::default()
            },
        )
        .unwrap();
        assert!(s.compare().is_none());
        assert!(s.unnormalized_coordinates());
    }

    #[test]
    fn simple_repeat_linear() {
        let (device, _queue) = gfx_dev_and_queue!();
        let _ = Sampler::new(device, SamplerCreateInfo::simple_repeat_linear());
    }

    #[test]
    fn simple_repeat_linear_no_mipmap() {
        let (device, _queue) = gfx_dev_and_queue!();
        let _ = Sampler::new(device, SamplerCreateInfo::simple_repeat_linear_no_mipmap());
    }

    #[test]
    fn min_lod_inferior() {
        let (device, _queue) = gfx_dev_and_queue!();

        assert_should_panic!({
            let _ = Sampler::new(
                device,
                SamplerCreateInfo {
                    mag_filter: Filter::Linear,
                    min_filter: Filter::Linear,
                    address_mode: [SamplerAddressMode::Repeat; 3],
                    mip_lod_bias: 1.0,
                    lod: 5.0..=2.0,
                    ..Default::default()
                },
            );
        });
    }

    #[test]
    fn max_anisotropy() {
        let (device, _queue) = gfx_dev_and_queue!();

        assert_should_panic!({
            let _ = Sampler::new(
                device,
                SamplerCreateInfo {
                    mag_filter: Filter::Linear,
                    min_filter: Filter::Linear,
                    address_mode: [SamplerAddressMode::Repeat; 3],
                    mip_lod_bias: 1.0,
                    anisotropy: Some(0.5),
                    lod: 0.0..=2.0,
                    ..Default::default()
                },
            );
        });
    }

    #[test]
    fn anisotropy_feature() {
        let (device, _queue) = gfx_dev_and_queue!();

        let r = Sampler::new(
            device,
            SamplerCreateInfo {
                mag_filter: Filter::Linear,
                min_filter: Filter::Linear,
                address_mode: [SamplerAddressMode::Repeat; 3],
                mip_lod_bias: 1.0,
                anisotropy: Some(2.0),
                lod: 0.0..=2.0,
                ..Default::default()
            },
        );

        match r {
            Err(SamplerCreationError::RequirementNotMet {
                requires_one_of: RequiresOneOf { features, .. },
                ..
            }) if features.contains(&"sampler_anisotropy") => (),
            _ => panic!(),
        }
    }

    #[test]
    fn anisotropy_limit() {
        let (device, _queue) = gfx_dev_and_queue!(sampler_anisotropy);

        let r = Sampler::new(
            device,
            SamplerCreateInfo {
                mag_filter: Filter::Linear,
                min_filter: Filter::Linear,
                address_mode: [SamplerAddressMode::Repeat; 3],
                mip_lod_bias: 1.0,
                anisotropy: Some(100000000.0),
                lod: 0.0..=2.0,
                ..Default::default()
            },
        );

        match r {
            Err(SamplerCreationError::MaxSamplerAnisotropyExceeded { .. }) => (),
            _ => panic!(),
        }
    }

    #[test]
    fn mip_lod_bias_limit() {
        let (device, _queue) = gfx_dev_and_queue!();

        let r = Sampler::new(
            device,
            SamplerCreateInfo {
                mag_filter: Filter::Linear,
                min_filter: Filter::Linear,
                address_mode: [SamplerAddressMode::Repeat; 3],
                mip_lod_bias: 100000000.0,
                lod: 0.0..=2.0,
                ..Default::default()
            },
        );

        match r {
            Err(SamplerCreationError::MaxSamplerLodBiasExceeded { .. }) => (),
            _ => panic!(),
        }
    }

    #[test]
    fn sampler_mirror_clamp_to_edge_extension() {
        let (device, _queue) = gfx_dev_and_queue!();

        let r = Sampler::new(
            device,
            SamplerCreateInfo {
                mag_filter: Filter::Linear,
                min_filter: Filter::Linear,
                address_mode: [SamplerAddressMode::MirrorClampToEdge; 3],
                mip_lod_bias: 1.0,
                lod: 0.0..=2.0,
                ..Default::default()
            },
        );

        match r {
            Err(SamplerCreationError::RequirementNotMet {
                requires_one_of:
                    RequiresOneOf {
                        features,
                        device_extensions,
                        ..
                    },
                ..
            }) if features.contains(&"sampler_mirror_clamp_to_edge")
                && device_extensions.contains(&"khr_sampler_mirror_clamp_to_edge") => {}
            _ => panic!(),
        }
    }

    #[test]
    fn sampler_filter_minmax_extension() {
        let (device, _queue) = gfx_dev_and_queue!();

        let r = Sampler::new(
            device,
            SamplerCreateInfo {
                mag_filter: Filter::Linear,
                min_filter: Filter::Linear,
                reduction_mode: SamplerReductionMode::Min,
                ..Default::default()
            },
        );

        match r {
            Err(SamplerCreationError::RequirementNotMet {
                requires_one_of:
                    RequiresOneOf {
                        features,
                        device_extensions,
                        ..
                    },
                ..
            }) if features.contains(&"sampler_filter_minmax")
                && device_extensions.contains(&"ext_sampler_filter_minmax") => {}
            _ => panic!(),
        }
    }
}
