diff options
Diffstat (limited to 'src/sampler.rs')
-rw-r--r-- | src/sampler.rs | 1051 |
1 files changed, 1051 insertions, 0 deletions
diff --git a/src/sampler.rs b/src/sampler.rs new file mode 100644 index 0000000..be7fc9b --- /dev/null +++ b/src/sampler.rs @@ -0,0 +1,1051 @@ +// 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 an image within a shader. +//! +//! When you retrieve data from an 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` struct describes. +//! +//! Sampling is a very complex topic but that hasn't changed much since the beginnings of 3D +//! rendering. Documentation here is missing, but any tutorial about OpenGL or DirectX can teach +//! you how it works. +//! +//! # Examples +//! +//! A simple sampler for most usages: +//! +//! ``` +//! use vulkano::sampler::Sampler; +//! +//! # let device: std::sync::Arc<vulkano::device::Device> = return; +//! let _sampler = Sampler::simple_repeat_linear_no_mipmap(device.clone()); +//! ``` +//! +//! More detailed sampler creation: +//! +//! ``` +//! use vulkano::sampler; +//! +//! # let device: std::sync::Arc<vulkano::device::Device> = return; +//! let _sampler = sampler::Sampler::new(device.clone(), sampler::Filter::Linear, +//! sampler::Filter::Linear, +//! sampler::MipmapMode::Nearest, +//! sampler::SamplerAddressMode::Repeat, +//! sampler::SamplerAddressMode::Repeat, +//! sampler::SamplerAddressMode::Repeat, 1.0, 1.0, +//! 0.0, 100.0).unwrap();; +//! ``` +//! +//! # About border colors +//! +//! One of the possible values of `SamplerAddressMode` and `UnnormalizedSamplerAddressMode` is +//! `ClampToBorder`. This value indicates that accessing an image outside of its range must return +//! the specified color. +//! +//! However this comes with restrictions. 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 components swizzling. +//! +//! > **Note**: The reason for this restriction about opaque black borders is that the value of the +//! > alpha is 1.0 while the value of the color components is 0.0. In the other border colors, the +//! > value of all the components is the same. +//! +//! Samplers that don't use `ClampToBorder` are not concerned by these restrictions. +//! +// FIXME: restrictions aren't checked yet + +use crate::check_errors; +use crate::device::Device; +use crate::device::DeviceOwned; +pub use crate::pipeline::depth_stencil::Compare; +use crate::Error; +use crate::OomError; +use crate::VulkanObject; +use std::error; +use std::fmt; +use std::mem::MaybeUninit; +use std::ptr; +use std::sync::Arc; + +/// Describes how to retrieve data from an image within a shader. +pub struct Sampler { + sampler: ash::vk::Sampler, + device: Arc<Device>, + compare_mode: bool, + unnormalized: bool, + usable_with_float_formats: bool, + usable_with_int_formats: bool, + usable_with_swizzling: bool, +} + +impl Sampler { + /// Shortcut for creating a sampler with linear sampling, linear mipmaps, and with the repeat + /// mode for borders. + /// + /// Useful for prototyping, but can also be used in real projects. + /// + /// # Panic + /// + /// - Panics if out of memory or the maximum number of samplers has exceeded. + /// + #[inline] + pub fn simple_repeat_linear(device: Arc<Device>) -> Arc<Sampler> { + Sampler::new( + device, + Filter::Linear, + Filter::Linear, + MipmapMode::Linear, + SamplerAddressMode::Repeat, + SamplerAddressMode::Repeat, + SamplerAddressMode::Repeat, + 0.0, + 1.0, + 0.0, + 1_000.0, + ) + .unwrap() + } + + /// Shortcut for creating a sampler with linear sampling, that only uses the main level of + /// images, and with the repeat mode for borders. + /// + /// Useful for prototyping, but can also be used in real projects. + /// + /// # Panic + /// + /// - Panics if out of memory or the maximum number of samplers has exceeded. + /// + #[inline] + pub fn simple_repeat_linear_no_mipmap(device: Arc<Device>) -> Arc<Sampler> { + Sampler::new( + device, + Filter::Linear, + Filter::Linear, + MipmapMode::Nearest, + SamplerAddressMode::Repeat, + SamplerAddressMode::Repeat, + SamplerAddressMode::Repeat, + 0.0, + 1.0, + 0.0, + 1.0, + ) + .unwrap() + } + + /// Creates a new `Sampler` with the given behavior. + /// + /// `mag_filter` and `min_filter` define how the implementation should sample from the image + /// when it is respectively larger and smaller than the original. + /// + /// `mipmap_mode` defines how the implementation should choose which mipmap to use. + /// + /// `address_u`, `address_v` and `address_w` define how the implementation should behave when + /// sampling outside of the texture coordinates range `[0.0, 1.0]`. + /// + /// `mip_lod_bias` is a value to add to . + /// + /// `max_anisotropy` must be greater than or equal to 1.0. If greater than 1.0, the + /// implementation will use anisotropic filtering. Using a value greater than 1.0 requires + /// the `sampler_anisotropy` feature to be enabled when creating the device. + /// + /// `min_lod` and `max_lod` are respectively the minimum and maximum mipmap level to use. + /// `max_lod` must always be greater than or equal to `min_lod`. + /// + /// # Panic + /// + /// - Panics if multiple `ClampToBorder` values are passed and the border color is different. + /// - Panics if `max_anisotropy < 1.0`. + /// - Panics if `min_lod > max_lod`. + /// + #[inline(always)] + pub fn new( + device: Arc<Device>, + mag_filter: Filter, + min_filter: Filter, + mipmap_mode: MipmapMode, + address_u: SamplerAddressMode, + address_v: SamplerAddressMode, + address_w: SamplerAddressMode, + mip_lod_bias: f32, + max_anisotropy: f32, + min_lod: f32, + max_lod: f32, + ) -> Result<Arc<Sampler>, SamplerCreationError> { + Sampler::new_impl( + device, + mag_filter, + min_filter, + mipmap_mode, + address_u, + address_v, + address_w, + mip_lod_bias, + max_anisotropy, + min_lod, + max_lod, + None, + ) + } + + /// Creates a new `Sampler` with the given behavior. + /// + /// Contrary to `new`, this creates a sampler that is used to compare depth values. + /// + /// A sampler like this can only operate on depth or depth-stencil textures. Instead of + /// returning the value of the texture, this sampler will return a value between 0.0 and 1.0 + /// indicating how much the reference value (passed by the shader) compares to the value in the + /// texture. + /// + /// Note that it doesn't make sense to create a compare-mode sampler with an integer border + /// color, as such a sampler would be unusable. + /// + /// # Panic + /// + /// Same panic reasons as `new`. + /// + #[inline(always)] + pub fn compare( + device: Arc<Device>, + mag_filter: Filter, + min_filter: Filter, + mipmap_mode: MipmapMode, + address_u: SamplerAddressMode, + address_v: SamplerAddressMode, + address_w: SamplerAddressMode, + mip_lod_bias: f32, + max_anisotropy: f32, + min_lod: f32, + max_lod: f32, + compare: Compare, + ) -> Result<Arc<Sampler>, SamplerCreationError> { + Sampler::new_impl( + device, + mag_filter, + min_filter, + mipmap_mode, + address_u, + address_v, + address_w, + mip_lod_bias, + max_anisotropy, + min_lod, + max_lod, + Some(compare), + ) + } + + fn new_impl( + device: Arc<Device>, + mag_filter: Filter, + min_filter: Filter, + mipmap_mode: MipmapMode, + address_u: SamplerAddressMode, + address_v: SamplerAddressMode, + address_w: SamplerAddressMode, + mip_lod_bias: f32, + max_anisotropy: f32, + min_lod: f32, + max_lod: f32, + compare: Option<Compare>, + ) -> Result<Arc<Sampler>, SamplerCreationError> { + assert!(max_anisotropy >= 1.0); + assert!(min_lod <= max_lod); + + // Check max anisotropy. + if max_anisotropy > 1.0 { + if !device.enabled_features().sampler_anisotropy { + return Err(SamplerCreationError::SamplerAnisotropyFeatureNotEnabled); + } + + let limit = device + .physical_device() + .properties() + .max_sampler_anisotropy; + if max_anisotropy > limit { + return Err(SamplerCreationError::AnisotropyLimitExceeded { + requested: max_anisotropy, + maximum: limit, + }); + } + } + + // Check mip_lod_bias value. + { + let limit = device + .physical_device() + .properties() + .max_sampler_lod_bias; + if mip_lod_bias > limit { + return Err(SamplerCreationError::MipLodBiasLimitExceeded { + requested: mip_lod_bias, + maximum: limit, + }); + } + } + + // Check MirrorClampToEdge extension support + if [address_u, address_v, address_w] + .iter() + .any(|&mode| mode == SamplerAddressMode::MirrorClampToEdge) + { + if !device.enabled_extensions().khr_sampler_mirror_clamp_to_edge { + return Err(SamplerCreationError::SamplerMirrorClampToEdgeExtensionNotEnabled); + } + } + + // Handling border color. + let border_color = address_u.border_color(); + let border_color = match (border_color, address_v.border_color()) { + (Some(b1), Some(b2)) => { + assert_eq!(b1, b2); + Some(b1) + } + (None, b) => b, + (b, None) => b, + }; + let border_color = match (border_color, address_w.border_color()) { + (Some(b1), Some(b2)) => { + assert_eq!(b1, b2); + Some(b1) + } + (None, b) => b, + (b, None) => b, + }; + + let fns = device.fns(); + let sampler = unsafe { + let infos = 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_u.into(), + address_mode_v: address_v.into(), + address_mode_w: address_w.into(), + mip_lod_bias: mip_lod_bias, + anisotropy_enable: if max_anisotropy > 1.0 { + ash::vk::TRUE + } else { + ash::vk::FALSE + }, + max_anisotropy: max_anisotropy, + compare_enable: if compare.is_some() { + ash::vk::TRUE + } else { + ash::vk::FALSE + }, + compare_op: compare + .map(|c| c.into()) + .unwrap_or(ash::vk::CompareOp::NEVER), + min_lod: min_lod, + max_lod: max_lod, + border_color: border_color + .map(|b| b.into()) + .unwrap_or(ash::vk::BorderColor::FLOAT_TRANSPARENT_BLACK), + unnormalized_coordinates: ash::vk::FALSE, + ..Default::default() + }; + + let mut output = MaybeUninit::uninit(); + check_errors(fns.v1_0.create_sampler( + device.internal_object(), + &infos, + ptr::null(), + output.as_mut_ptr(), + ))?; + output.assume_init() + }; + + Ok(Arc::new(Sampler { + sampler: sampler, + device: device.clone(), + compare_mode: compare.is_some(), + unnormalized: false, + usable_with_float_formats: match border_color { + Some(BorderColor::FloatTransparentBlack) => true, + Some(BorderColor::FloatOpaqueBlack) => true, + Some(BorderColor::FloatOpaqueWhite) => true, + Some(_) => false, + None => true, + }, + usable_with_int_formats: compare.is_none() + && match border_color { + Some(BorderColor::IntTransparentBlack) => true, + Some(BorderColor::IntOpaqueBlack) => true, + Some(BorderColor::IntOpaqueWhite) => true, + Some(_) => false, + None => true, + }, + usable_with_swizzling: match border_color { + Some(BorderColor::FloatOpaqueBlack) => false, + Some(BorderColor::IntOpaqueBlack) => false, + _ => true, + }, + })) + } + + /// Creates a sampler with unnormalized coordinates. This means that texture coordinates won't + /// range between `0.0` and `1.0` but use plain pixel offsets. + /// + /// Using an unnormalized sampler adds a few restrictions: + /// + /// - It can only be used with non-array 1D or 2D images. + /// - It can only be used with images with a single mipmap. + /// - Projection and offsets can't be used by shaders. Only the first mipmap can be accessed. + /// + /// # Panic + /// + /// - Panics if multiple `ClampToBorder` values are passed and the border color is different. + /// + pub fn unnormalized( + device: Arc<Device>, + filter: Filter, + address_u: UnnormalizedSamplerAddressMode, + address_v: UnnormalizedSamplerAddressMode, + ) -> Result<Arc<Sampler>, SamplerCreationError> { + let fns = device.fns(); + + let border_color = address_u.border_color(); + let border_color = match (border_color, address_v.border_color()) { + (Some(b1), Some(b2)) => { + assert_eq!(b1, b2); + Some(b1) + } + (None, b) => b, + (b, None) => b, + }; + + let sampler = unsafe { + let infos = ash::vk::SamplerCreateInfo { + flags: ash::vk::SamplerCreateFlags::empty(), + mag_filter: filter.into(), + min_filter: filter.into(), + mipmap_mode: ash::vk::SamplerMipmapMode::NEAREST, + address_mode_u: address_u.into(), + address_mode_v: address_v.into(), + address_mode_w: ash::vk::SamplerAddressMode::CLAMP_TO_EDGE, // unused by the impl + mip_lod_bias: 0.0, + anisotropy_enable: ash::vk::FALSE, + max_anisotropy: 1.0, + compare_enable: ash::vk::FALSE, + compare_op: ash::vk::CompareOp::NEVER, + min_lod: 0.0, + max_lod: 0.0, + border_color: border_color + .map(|b| b.into()) + .unwrap_or(ash::vk::BorderColor::FLOAT_TRANSPARENT_BLACK), + unnormalized_coordinates: ash::vk::TRUE, + ..Default::default() + }; + + let mut output = MaybeUninit::uninit(); + check_errors(fns.v1_0.create_sampler( + device.internal_object(), + &infos, + ptr::null(), + output.as_mut_ptr(), + ))?; + output.assume_init() + }; + + Ok(Arc::new(Sampler { + sampler: sampler, + device: device.clone(), + compare_mode: false, + unnormalized: true, + usable_with_float_formats: match border_color { + Some(BorderColor::FloatTransparentBlack) => true, + Some(BorderColor::FloatOpaqueBlack) => true, + Some(BorderColor::FloatOpaqueWhite) => true, + Some(_) => false, + None => true, + }, + usable_with_int_formats: match border_color { + Some(BorderColor::IntTransparentBlack) => true, + Some(BorderColor::IntOpaqueBlack) => true, + Some(BorderColor::IntOpaqueWhite) => true, + Some(_) => false, + None => true, + }, + usable_with_swizzling: match border_color { + Some(BorderColor::FloatOpaqueBlack) => false, + Some(BorderColor::IntOpaqueBlack) => false, + _ => true, + }, + })) + } + + /// Returns true if the sampler is a compare-mode sampler. + #[inline] + pub fn compare_mode(&self) -> bool { + self.compare_mode + } + + /// Returns true if the sampler is unnormalized. + #[inline] + pub fn is_unnormalized(&self) -> bool { + self.unnormalized + } + + /// Returns true if the sampler can be used with floating-point image views. See the + /// documentation of the `sampler` module for more info. + #[inline] + pub fn usable_with_float_formats(&self) -> bool { + self.usable_with_float_formats + } + + /// Returns true if the sampler can be used with integer image views. See the documentation of + /// the `sampler` module for more info. + #[inline] + pub fn usable_with_int_formats(&self) -> bool { + self.usable_with_int_formats + } + + /// Returns true if the sampler can be used with image views that have non-identity swizzling. + /// See the documentation of the `sampler` module for more info. + #[inline] + pub fn usable_with_swizzling(&self) -> bool { + self.usable_with_swizzling + } +} + +unsafe impl DeviceOwned for Sampler { + #[inline] + fn device(&self) -> &Arc<Device> { + &self.device + } +} + +unsafe impl VulkanObject for Sampler { + type Object = ash::vk::Sampler; + + #[inline] + fn internal_object(&self) -> ash::vk::Sampler { + self.sampler + } +} + +impl fmt::Debug for Sampler { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!(fmt, "<Vulkan sampler {:?}>", self.sampler) + } +} + +impl Drop for Sampler { + #[inline] + fn drop(&mut self) { + unsafe { + let fns = self.device.fns(); + fns.v1_0 + .destroy_sampler(self.device.internal_object(), self.sampler, ptr::null()); + } + } +} + +/// Describes how the color of each pixel should be determined. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[repr(i32)] +pub enum Filter { + /// The four pixels whose center surround the requested coordinates are taken, then their + /// values are interpolated. + Linear = ash::vk::Filter::LINEAR.as_raw(), + + /// The pixel whose center is nearest to the requested coordinates is taken from the source + /// and its value is returned as-is. + Nearest = ash::vk::Filter::NEAREST.as_raw(), +} + +impl From<Filter> for ash::vk::Filter { + #[inline] + fn from(val: Filter) -> Self { + Self::from_raw(val as i32) + } +} + +/// Describes which mipmap from the source to use. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[repr(i32)] +pub enum MipmapMode { + /// Use the mipmap whose dimensions are the nearest to the dimensions of the destination. + Nearest = ash::vk::SamplerMipmapMode::NEAREST.as_raw(), + + /// 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 = ash::vk::SamplerMipmapMode::LINEAR.as_raw(), +} + +impl From<MipmapMode> for ash::vk::SamplerMipmapMode { + #[inline] + fn from(val: MipmapMode) -> Self { + Self::from_raw(val as i32) + } +} + +/// How the sampler should behave when it needs to access a pixel that is out of range of the +/// texture. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum SamplerAddressMode { + /// Repeat the texture. In other words, the pixel at coordinate `x + 1.0` is the same as the + /// one at coordinate `x`. + 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, + + /// 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, + + /// Any pixel out of range is considered to be part of the "border" of the image, which has a + /// specific color of your choice. + /// + /// Note that if you use `ClampToBorder` multiple times, they must all have the same border + /// color. + ClampToBorder(BorderColor), + + /// Similar to `MirroredRepeat`, except that coordinates are clamped to the range + /// `[-1.0, 1.0]`. + MirrorClampToEdge, +} + +impl SamplerAddressMode { + #[inline] + fn border_color(self) -> Option<BorderColor> { + match self { + SamplerAddressMode::ClampToBorder(c) => Some(c), + _ => None, + } + } +} + +impl From<SamplerAddressMode> for ash::vk::SamplerAddressMode { + #[inline] + fn from(val: SamplerAddressMode) -> Self { + match val { + SamplerAddressMode::Repeat => ash::vk::SamplerAddressMode::REPEAT, + SamplerAddressMode::MirroredRepeat => ash::vk::SamplerAddressMode::MIRRORED_REPEAT, + SamplerAddressMode::ClampToEdge => ash::vk::SamplerAddressMode::CLAMP_TO_EDGE, + SamplerAddressMode::ClampToBorder(_) => ash::vk::SamplerAddressMode::CLAMP_TO_BORDER, + SamplerAddressMode::MirrorClampToEdge => { + ash::vk::SamplerAddressMode::MIRROR_CLAMP_TO_EDGE + } + } + } +} + +/// How the sampler should behave when it needs to access a pixel that is out of range of the +/// texture. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[repr(u32)] +pub enum UnnormalizedSamplerAddressMode { + /// The coordinates are clamped to the valid range. Coordinates below 0 have the same value + /// as coordinate 0. Coordinates over *size of texture* have the same value as coordinate + /// *size of texture*. + ClampToEdge, + + /// Any pixel out of range is considered to be part of the "border" of the image, which has a + /// specific color of your choice. + /// + /// Note that if you use `ClampToBorder` multiple times, they must all have the same border + /// color. + ClampToBorder(BorderColor), +} + +impl UnnormalizedSamplerAddressMode { + #[inline] + fn border_color(self) -> Option<BorderColor> { + match self { + UnnormalizedSamplerAddressMode::ClampToEdge => None, + UnnormalizedSamplerAddressMode::ClampToBorder(c) => Some(c), + } + } +} + +impl From<UnnormalizedSamplerAddressMode> for ash::vk::SamplerAddressMode { + #[inline] + fn from(val: UnnormalizedSamplerAddressMode) -> Self { + match val { + UnnormalizedSamplerAddressMode::ClampToEdge => { + ash::vk::SamplerAddressMode::CLAMP_TO_EDGE + } + UnnormalizedSamplerAddressMode::ClampToBorder(_) => { + ash::vk::SamplerAddressMode::CLAMP_TO_BORDER + } + } + } +} + +/// 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. +/// See the documentation of the `sampler` module for more info. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +#[repr(i32)] +pub enum BorderColor { + /// The value `(0.0, 0.0, 0.0, 0.0)`. Can only be used with floating-point images. + FloatTransparentBlack = ash::vk::BorderColor::FLOAT_TRANSPARENT_BLACK.as_raw(), + + /// The value `(0, 0, 0, 0)`. Can only be used with integer images. + IntTransparentBlack = ash::vk::BorderColor::INT_TRANSPARENT_BLACK.as_raw(), + + /// The value `(0.0, 0.0, 0.0, 1.0)`. Can only be used with floating-point identity-swizzled + /// images. + FloatOpaqueBlack = ash::vk::BorderColor::FLOAT_OPAQUE_BLACK.as_raw(), + + /// The value `(0, 0, 0, 1)`. Can only be used with integer identity-swizzled images. + IntOpaqueBlack = ash::vk::BorderColor::INT_OPAQUE_BLACK.as_raw(), + + /// The value `(1.0, 1.0, 1.0, 1.0)`. Can only be used with floating-point images. + FloatOpaqueWhite = ash::vk::BorderColor::FLOAT_OPAQUE_WHITE.as_raw(), + + /// The value `(1, 1, 1, 1)`. Can only be used with integer images. + IntOpaqueWhite = ash::vk::BorderColor::INT_OPAQUE_WHITE.as_raw(), +} + +impl From<BorderColor> for ash::vk::BorderColor { + #[inline] + fn from(val: BorderColor) -> Self { + Self::from_raw(val as i32) + } +} + +/// 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, + + /// Using an anisotropy greater than 1.0 requires enabling the `sampler_anisotropy` feature + /// when creating the device. + SamplerAnisotropyFeatureNotEnabled, + + /// The requested anisotropy level exceeds the device's limits. + AnisotropyLimitExceeded { + /// The value that was requested. + requested: f32, + /// The maximum supported value. + maximum: f32, + }, + + /// The requested mip lod bias exceeds the device's limits. + MipLodBiasLimitExceeded { + /// The value that was requested. + requested: f32, + /// The maximum supported value. + maximum: f32, + }, + + /// Using `MirrorClampToEdge` requires enabling the `VK_KHR_sampler_mirror_clamp_to_edge` + /// extension when creating the device. + SamplerMirrorClampToEdgeExtensionNotEnabled, +} + +impl error::Error for SamplerCreationError { + #[inline] + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + match *self { + SamplerCreationError::OomError(ref err) => Some(err), + _ => None, + } + } +} + +impl fmt::Display for SamplerCreationError { + #[inline] + fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> { + write!( + fmt, + "{}", + match *self { + SamplerCreationError::OomError(_) => "not enough memory available", + SamplerCreationError::TooManyObjects => "too many simultaneous sampler objects", + SamplerCreationError::SamplerAnisotropyFeatureNotEnabled => { + "the `sampler_anisotropy` feature is not enabled" + } + SamplerCreationError::AnisotropyLimitExceeded { .. } => "anisotropy limit exceeded", + SamplerCreationError::MipLodBiasLimitExceeded { .. } => + "mip lod bias limit exceeded", + SamplerCreationError::SamplerMirrorClampToEdgeExtensionNotEnabled => { + "the device extension `VK_KHR_sampler_mirror_clamp_to_edge` is not enabled" + } + } + ) + } +} + +impl From<OomError> for SamplerCreationError { + #[inline] + fn from(err: OomError) -> SamplerCreationError { + SamplerCreationError::OomError(err) + } +} + +impl From<Error> for SamplerCreationError { + #[inline] + fn from(err: Error) -> SamplerCreationError { + match err { + err @ Error::OutOfHostMemory => SamplerCreationError::OomError(OomError::from(err)), + err @ Error::OutOfDeviceMemory => SamplerCreationError::OomError(OomError::from(err)), + Error::TooManyObjects => SamplerCreationError::TooManyObjects, + _ => panic!("unexpected error: {:?}", err), + } + } +} + +#[cfg(test)] +mod tests { + use crate::sampler; + + #[test] + fn create_regular() { + let (device, queue) = gfx_dev_and_queue!(); + + let s = sampler::Sampler::new( + device, + sampler::Filter::Linear, + sampler::Filter::Linear, + sampler::MipmapMode::Nearest, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + 1.0, + 1.0, + 0.0, + 2.0, + ) + .unwrap(); + assert!(!s.compare_mode()); + assert!(!s.is_unnormalized()); + } + + #[test] + fn create_compare() { + let (device, queue) = gfx_dev_and_queue!(); + + let s = sampler::Sampler::compare( + device, + sampler::Filter::Linear, + sampler::Filter::Linear, + sampler::MipmapMode::Nearest, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + 1.0, + 1.0, + 0.0, + 2.0, + sampler::Compare::Less, + ) + .unwrap(); + + assert!(s.compare_mode()); + assert!(!s.is_unnormalized()); + } + + #[test] + fn create_unnormalized() { + let (device, queue) = gfx_dev_and_queue!(); + + let s = sampler::Sampler::unnormalized( + device, + sampler::Filter::Linear, + sampler::UnnormalizedSamplerAddressMode::ClampToEdge, + sampler::UnnormalizedSamplerAddressMode::ClampToEdge, + ) + .unwrap(); + + assert!(!s.compare_mode()); + assert!(s.is_unnormalized()); + } + + #[test] + fn simple_repeat_linear() { + let (device, queue) = gfx_dev_and_queue!(); + let _ = sampler::Sampler::simple_repeat_linear(device); + } + + #[test] + fn simple_repeat_linear_no_mipmap() { + let (device, queue) = gfx_dev_and_queue!(); + let _ = sampler::Sampler::simple_repeat_linear_no_mipmap(device); + } + + #[test] + fn min_lod_inferior() { + let (device, queue) = gfx_dev_and_queue!(); + + assert_should_panic!({ + let _ = sampler::Sampler::new( + device, + sampler::Filter::Linear, + sampler::Filter::Linear, + sampler::MipmapMode::Nearest, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + 1.0, + 1.0, + 5.0, + 2.0, + ); + }); + } + + #[test] + fn max_anisotropy() { + let (device, queue) = gfx_dev_and_queue!(); + + assert_should_panic!({ + let _ = sampler::Sampler::new( + device, + sampler::Filter::Linear, + sampler::Filter::Linear, + sampler::MipmapMode::Nearest, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + 1.0, + 0.5, + 0.0, + 2.0, + ); + }); + } + + #[test] + fn different_borders() { + let (device, queue) = gfx_dev_and_queue!(); + + let b1 = sampler::BorderColor::IntTransparentBlack; + let b2 = sampler::BorderColor::FloatOpaqueWhite; + + assert_should_panic!({ + let _ = sampler::Sampler::new( + device, + sampler::Filter::Linear, + sampler::Filter::Linear, + sampler::MipmapMode::Nearest, + sampler::SamplerAddressMode::ClampToBorder(b1), + sampler::SamplerAddressMode::ClampToBorder(b2), + sampler::SamplerAddressMode::Repeat, + 1.0, + 1.0, + 5.0, + 2.0, + ); + }); + } + + #[test] + fn anisotropy_feature() { + let (device, queue) = gfx_dev_and_queue!(); + + let r = sampler::Sampler::new( + device, + sampler::Filter::Linear, + sampler::Filter::Linear, + sampler::MipmapMode::Nearest, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + 1.0, + 2.0, + 0.0, + 2.0, + ); + + match r { + Err(sampler::SamplerCreationError::SamplerAnisotropyFeatureNotEnabled) => (), + _ => panic!(), + } + } + + #[test] + fn anisotropy_limit() { + let (device, queue) = gfx_dev_and_queue!(sampler_anisotropy); + + let r = sampler::Sampler::new( + device, + sampler::Filter::Linear, + sampler::Filter::Linear, + sampler::MipmapMode::Nearest, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + 1.0, + 100000000.0, + 0.0, + 2.0, + ); + + match r { + Err(sampler::SamplerCreationError::AnisotropyLimitExceeded { .. }) => (), + _ => panic!(), + } + } + + #[test] + fn mip_lod_bias_limit() { + let (device, queue) = gfx_dev_and_queue!(); + + let r = sampler::Sampler::new( + device, + sampler::Filter::Linear, + sampler::Filter::Linear, + sampler::MipmapMode::Nearest, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + sampler::SamplerAddressMode::Repeat, + 100000000.0, + 1.0, + 0.0, + 2.0, + ); + + match r { + Err(sampler::SamplerCreationError::MipLodBiasLimitExceeded { .. }) => (), + _ => panic!(), + } + } + + #[test] + fn sampler_mirror_clamp_to_edge_extension() { + let (device, queue) = gfx_dev_and_queue!(); + + let r = sampler::Sampler::new( + device, + sampler::Filter::Linear, + sampler::Filter::Linear, + sampler::MipmapMode::Nearest, + sampler::SamplerAddressMode::MirrorClampToEdge, + sampler::SamplerAddressMode::MirrorClampToEdge, + sampler::SamplerAddressMode::MirrorClampToEdge, + 1.0, + 1.0, + 0.0, + 2.0, + ); + + match r { + Err(sampler::SamplerCreationError::SamplerMirrorClampToEdgeExtensionNotEnabled) => (), + _ => panic!(), + } + } +} |