// Copyright (c) 2016 The vulkano developers // Licensed under the Apache License, Version 2.0 // or the MIT // license , // 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 = 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 = 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, id: NonZeroU64, address_mode: [SamplerAddressMode; 3], anisotropy: Option, border_color: Option, compare: Option, lod: RangeInclusive, mag_filter: Filter, min_filter: Filter, mip_lod_bias: f32, mipmap_mode: SamplerMipmapMode, reduction_mode: SamplerReductionMode, sampler_ycbcr_conversion: Option>, 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, create_info: SamplerCreateInfo, ) -> Result, 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, handle: ash::vk::Sampler, create_info: SamplerCreateInfo, ) -> Arc { 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 { self.anisotropy } /// Returns the border color if one is used by this sampler. #[inline] pub fn border_color(&self) -> Option { self.border_color } /// Returns the compare operation if the sampler is a compare-mode sampler. #[inline] pub fn compare(&self) -> Option { self.compare } /// Returns the LOD range. #[inline] pub fn lod(&self) -> RangeInclusive { 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> { 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 { &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 }, } 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 for SamplerCreationError { fn from(err: OomError) -> Self { Self::OomError(err) } } impl From 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 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, /// 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, /// 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, /// 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>, 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; 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 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!(), } } }