blob: 80f26491a287eed9d2e7551585103f14df408d3e [file] [log] [blame] [edit]
// 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!(),
}
}
}