pub mod ycbcr;
use self::ycbcr::SamplerYcbcrConversion;
use crate::{
check_errors,
device::{Device, DeviceOwned},
image::{view::ImageViewType, ImageViewAbstract},
pipeline::graphics::depth_stencil::CompareOp,
shader::ShaderScalarType,
Error, OomError, VulkanObject,
};
use std::{
error, fmt,
hash::{Hash, Hasher},
mem::MaybeUninit,
ops::RangeInclusive,
ptr,
sync::Arc,
};
#[derive(Debug)]
pub struct Sampler {
handle: ash::vk::Sampler,
device: Arc<Device>,
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 {
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;
if address_mode
.into_iter()
.any(|mode| mode == SamplerAddressMode::MirrorClampToEdge)
{
if !device.enabled_features().sampler_mirror_clamp_to_edge
&& !device.enabled_extensions().khr_sampler_mirror_clamp_to_edge
{
if device
.physical_device()
.supported_features()
.sampler_mirror_clamp_to_edge
{
return Err(SamplerCreationError::FeatureNotEnabled {
feature: "sampler_mirror_clamp_to_edge",
reason: "one or more address modes were MirrorClampToEdge",
});
} else {
return Err(SamplerCreationError::ExtensionNotEnabled {
extension: "khr_sampler_mirror_clamp_to_edge",
reason: "one or more address modes were MirrorClampToEdge",
});
}
}
}
{
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,
});
}
}
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::FeatureNotEnabled {
feature: "sampler_anisotropy",
reason: "anisotropy was set to `Some`",
});
}
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: mag_filter,
min_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 {
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: lod.clone(),
});
}
if address_mode[0..2].into_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)
{
if device
.physical_device()
.supported_features()
.sampler_filter_minmax
{
return Err(SamplerCreationError::FeatureNotEnabled {
feature: "sampler_filter_minmax",
reason: "reduction_mode was not WeightedAverage",
});
} else {
return Err(SamplerCreationError::ExtensionNotEnabled {
extension: "ext_sampler_filter_minmax",
reason: "reduction_mode was not WeightedAverage",
});
}
}
Some(ash::vk::SamplerReductionModeCreateInfo {
reduction_mode: reduction_mode.into(),
..Default::default()
})
} else {
None
};
let mut sampler_ycbcr_conversion_info = if let Some(sampler_ycbcr_conversion) =
&sampler_ycbcr_conversion
{
assert_eq!(&device, sampler_ycbcr_conversion.device());
let potential_format_features = device
.physical_device()
.format_properties(sampler_ycbcr_conversion.format().unwrap())
.potential_format_features();
if !potential_format_features
.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,
},
);
}
if address_mode
.into_iter()
.any(|mode| !matches!(mode, SamplerAddressMode::ClampToEdge))
{
return Err(
SamplerCreationError::SamplerYcbcrConversionInvalidAddressMode { address_mode },
);
}
if anisotropy.is_some() {
return Err(SamplerCreationError::SamplerYcbcrConversionAnisotropyEnabled);
}
if unnormalized_coordinates {
return Err(
SamplerCreationError::SamplerYcbcrConversionUnnormalizedCoordinatesEnabled,
);
}
if reduction_mode != SamplerReductionMode::WeightedAverage {
return Err(
SamplerCreationError::SamplerYcbcrConversionInvalidReductionMode {
reduction_mode,
},
);
}
Some(ash::vk::SamplerYcbcrConversionInfo {
conversion: sampler_ycbcr_conversion.internal_object(),
..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: 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();
check_errors(fns.v1_0.create_sampler(
device.internal_object(),
&create_info,
ptr::null(),
output.as_mut_ptr(),
))?;
output.assume_init()
};
Ok(Arc::new(Sampler {
handle,
device,
address_mode,
anisotropy,
border_color: address_mode
.into_iter()
.any(|mode| mode == SamplerAddressMode::ClampToBorder)
.then(|| border_color),
compare,
lod,
mag_filter,
min_filter,
mip_lod_bias,
mipmap_mode,
reduction_mode,
sampler_ycbcr_conversion,
unnormalized_coordinates,
}))
}
pub fn check_can_sample<I>(
&self,
image_view: &I,
) -> Result<(), SamplerImageViewIncompatibleError>
where
I: ImageViewAbstract + ?Sized,
{
if self.compare.is_some() {
if !image_view.format_features().sampled_image_depth_comparison {
return Err(SamplerImageViewIncompatibleError::DepthComparisonNotSupported);
}
if !image_view.aspects().depth {
return Err(SamplerImageViewIncompatibleError::DepthComparisonWrongAspect);
}
} else {
if !image_view.format_features().sampled_image_filter_linear {
if self.mag_filter == Filter::Linear || self.min_filter == Filter::Linear {
return Err(SamplerImageViewIncompatibleError::FilterLinearNotSupported);
}
if self.mipmap_mode == SamplerMipmapMode::Linear {
return Err(SamplerImageViewIncompatibleError::MipmapModeLinearNotSupported);
}
}
}
if self.mag_filter == Filter::Cubic || self.min_filter == Filter::Cubic {
if !image_view.format_features().sampled_image_filter_cubic {
return Err(SamplerImageViewIncompatibleError::FilterCubicNotSupported);
}
if !image_view.filter_cubic() {
return Err(SamplerImageViewIncompatibleError::FilterCubicNotSupported);
}
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.aspects();
let view_scalar_type = ShaderScalarType::from(
if aspects.color || aspects.plane0 || aspects.plane1 || aspects.plane2 {
image_view.format().unwrap().type_color().unwrap()
} else if aspects.depth {
image_view.format().unwrap().type_depth().unwrap()
} else if aspects.stencil {
image_view.format().unwrap().type_stencil().unwrap()
} else {
unreachable!()
},
);
match border_color {
BorderColor::IntTransparentBlack
| BorderColor::IntOpaqueBlack
| BorderColor::IntOpaqueWhite => {
if !matches!(
view_scalar_type,
ShaderScalarType::Sint | ShaderScalarType::Uint
) {
return Err(
SamplerImageViewIncompatibleError::BorderColorFormatNotCompatible,
);
}
}
BorderColor::FloatTransparentBlack
| BorderColor::FloatOpaqueBlack
| BorderColor::FloatOpaqueWhite => {
if !matches!(view_scalar_type, ShaderScalarType::Float) {
return Err(
SamplerImageViewIncompatibleError::BorderColorFormatNotCompatible,
);
}
}
}
if matches!(
border_color,
BorderColor::FloatOpaqueBlack | BorderColor::IntOpaqueBlack
) && !image_view.component_mapping().is_identity()
{
return Err(
SamplerImageViewIncompatibleError::BorderColorOpaqueBlackNotIdentitySwizzled,
);
}
}
if self.unnormalized_coordinates {
if !matches!(
image_view.view_type(),
ImageViewType::Dim1d | ImageViewType::Dim2d
) {
return Err(
SamplerImageViewIncompatibleError::UnnormalizedCoordinatesViewTypeNotCompatible,
);
}
if image_view.mip_levels().end - image_view.mip_levels().start != 1 {
return Err(
SamplerImageViewIncompatibleError::UnnormalizedCoordinatesMultipleMipLevels,
);
}
}
Ok(())
}
#[inline]
pub fn address_mode(&self) -> [SamplerAddressMode; 3] {
self.address_mode
}
#[inline]
pub fn anisotropy(&self) -> Option<f32> {
self.anisotropy
}
#[inline]
pub fn border_color(&self) -> Option<BorderColor> {
self.border_color
}
#[inline]
pub fn compare(&self) -> Option<CompareOp> {
self.compare
}
#[inline]
pub fn lod(&self) -> RangeInclusive<f32> {
self.lod.clone()
}
#[inline]
pub fn mag_filter(&self) -> Filter {
self.mag_filter
}
#[inline]
pub fn min_filter(&self) -> Filter {
self.min_filter
}
#[inline]
pub fn mip_lod_bias(&self) -> f32 {
self.mip_lod_bias
}
#[inline]
pub fn mipmap_mode(&self) -> SamplerMipmapMode {
self.mipmap_mode
}
#[inline]
pub fn reduction_mode(&self) -> SamplerReductionMode {
self.reduction_mode
}
#[inline]
pub fn sampler_ycbcr_conversion(&self) -> Option<&Arc<SamplerYcbcrConversion>> {
self.sampler_ycbcr_conversion.as_ref()
}
#[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.internal_object(), self.handle, ptr::null());
}
}
}
unsafe impl VulkanObject for Sampler {
type Object = ash::vk::Sampler;
#[inline]
fn internal_object(&self) -> ash::vk::Sampler {
self.handle
}
}
unsafe impl DeviceOwned for Sampler {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
impl PartialEq for Sampler {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.handle == other.handle && self.device() == other.device()
}
}
impl Eq for Sampler {}
impl Hash for Sampler {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.handle.hash(state);
self.device().hash(state);
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum SamplerCreationError {
OomError(OomError),
TooManyObjects,
ExtensionNotEnabled {
extension: &'static str,
reason: &'static str,
},
FeatureNotEnabled {
feature: &'static str,
reason: &'static str,
},
AnisotropyInvalidFilter {
mag_filter: Filter,
min_filter: Filter,
},
CompareInvalidReductionMode {
reduction_mode: SamplerReductionMode,
},
MaxSamplerAnisotropyExceeded {
requested: f32,
maximum: f32,
},
MaxSamplerLodBiasExceeded {
requested: f32,
maximum: f32,
},
SamplerYcbcrConversionAnisotropyEnabled,
SamplerYcbcrConversionChromaFilterMismatch {
chroma_filter: Filter,
mag_filter: Filter,
min_filter: Filter,
},
SamplerYcbcrConversionInvalidAddressMode {
address_mode: [SamplerAddressMode; 3],
},
SamplerYcbcrConversionInvalidReductionMode {
reduction_mode: SamplerReductionMode,
},
SamplerYcbcrConversionUnnormalizedCoordinatesEnabled,
UnnormalizedCoordinatesAnisotropyEnabled,
UnnormalizedCoordinatesCompareEnabled,
UnnormalizedCoordinatesFiltersNotEqual {
mag_filter: Filter,
min_filter: Filter,
},
UnnormalizedCoordinatesInvalidAddressMode {
address_mode: [SamplerAddressMode; 2],
},
UnnormalizedCoordinatesInvalidMipmapMode { mipmap_mode: SamplerMipmapMode },
UnnormalizedCoordinatesNonzeroLod { lod: RangeInclusive<f32> },
}
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> {
match *self {
Self::OomError(_) => write!(fmt, "not enough memory available"),
Self::TooManyObjects => write!(fmt, "too many simultaneous sampler objects",),
Self::ExtensionNotEnabled { extension, reason } => write!(
fmt,
"the extension {} must be enabled: {}",
extension, reason
),
Self::FeatureNotEnabled { feature, reason } => {
write!(fmt, "the feature {} must be enabled: {}", feature, reason)
}
Self::AnisotropyInvalidFilter { .. } => write!(fmt, "anisotropy was enabled with an invalid filter"),
Self::CompareInvalidReductionMode { .. } => write!(fmt, "depth comparison was enabled with an invalid reduction mode"),
Self::MaxSamplerAnisotropyExceeded { .. } => {
write!(fmt, "max_sampler_anisotropy limit exceeded")
}
Self::MaxSamplerLodBiasExceeded { .. } => write!(fmt, "mip lod bias limit exceeded"),
Self::SamplerYcbcrConversionAnisotropyEnabled => write!(
fmt,
"sampler YCbCr conversion was enabled together with anisotropy"
),
Self::SamplerYcbcrConversionChromaFilterMismatch { .. } => write!(fmt, "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!(fmt, "sampler YCbCr conversion was enabled, but the address mode for u, v or w was something other than `ClampToEdge`"),
Self::SamplerYcbcrConversionInvalidReductionMode { .. } => write!(fmt, "sampler YCbCr conversion was enabled, but the reduction mode was something other than `WeightedAverage`"),
Self::SamplerYcbcrConversionUnnormalizedCoordinatesEnabled => write!(
fmt,
"sampler YCbCr conversion was enabled together with unnormalized coordinates"
),
Self::UnnormalizedCoordinatesAnisotropyEnabled => write!(
fmt,
"unnormalized coordinates were enabled together with anisotropy"
),
Self::UnnormalizedCoordinatesCompareEnabled => write!(
fmt,
"unnormalized coordinates were enabled together with depth comparison"
),
Self::UnnormalizedCoordinatesFiltersNotEqual { .. } => write!(
fmt,
"unnormalized coordinates were enabled, but the min and mag filters were not equal"
),
Self::UnnormalizedCoordinatesInvalidAddressMode { .. } => write!(
fmt,
"unnormalized coordinates were enabled, but the address mode for u or v was something other than `ClampToEdge` or `ClampToBorder`"
),
Self::UnnormalizedCoordinatesInvalidMipmapMode { .. } => write!(
fmt,
"unnormalized coordinates were enabled, but the mipmap mode was not `Nearest`"
),
Self::UnnormalizedCoordinatesNonzeroLod { .. } => write!(
fmt,
"unnormalized coordinates were enabled, but the LOD range was not zero"
),
}
}
}
impl From<OomError> for SamplerCreationError {
#[inline]
fn from(err: OomError) -> Self {
Self::OomError(err)
}
}
impl From<Error> for SamplerCreationError {
#[inline]
fn from(err: Error) -> Self {
match err {
err @ Error::OutOfHostMemory => Self::OomError(OomError::from(err)),
err @ Error::OutOfDeviceMemory => Self::OomError(OomError::from(err)),
Error::TooManyObjects => Self::TooManyObjects,
_ => panic!("unexpected error: {:?}", err),
}
}
}
#[derive(Clone, Debug)]
pub struct SamplerCreateInfo {
pub mag_filter: Filter,
pub min_filter: Filter,
pub mipmap_mode: SamplerMipmapMode,
pub address_mode: [SamplerAddressMode; 3],
pub mip_lod_bias: f32,
pub anisotropy: Option<f32>,
pub compare: Option<CompareOp>,
pub lod: RangeInclusive<f32>,
pub border_color: BorderColor,
pub unnormalized_coordinates: bool,
pub reduction_mode: SamplerReductionMode,
pub sampler_ycbcr_conversion: Option<Arc<SamplerYcbcrConversion>>,
pub _ne: crate::NonExhaustive,
}
impl Default for SamplerCreateInfo {
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 {
#[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()
}
}
#[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()
}
}
}
pub const LOD_CLAMP_NONE: f32 = ash::vk::LOD_CLAMP_NONE;
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub struct ComponentMapping {
pub r: ComponentSwizzle,
pub g: ComponentSwizzle,
pub b: ComponentSwizzle,
pub a: ComponentSwizzle,
}
impl ComponentMapping {
#[inline]
pub fn identity() -> Self {
Self::default()
}
#[inline]
pub fn is_identity(&self) -> bool {
self.r_is_identity() && self.g_is_identity() && self.b_is_identity() && self.a_is_identity()
}
#[inline]
pub fn r_is_identity(&self) -> bool {
matches!(self.r, ComponentSwizzle::Identity | ComponentSwizzle::Red)
}
#[inline]
pub fn g_is_identity(&self) -> bool {
matches!(self.g, ComponentSwizzle::Identity | ComponentSwizzle::Green)
}
#[inline]
pub fn b_is_identity(&self) -> bool {
matches!(self.b, ComponentSwizzle::Identity | ComponentSwizzle::Blue)
}
#[inline]
pub fn a_is_identity(&self) -> bool {
matches!(self.a, ComponentSwizzle::Identity | ComponentSwizzle::Alpha)
}
#[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(),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
#[repr(i32)]
pub enum ComponentSwizzle {
Identity = ash::vk::ComponentSwizzle::IDENTITY.as_raw(),
Zero = ash::vk::ComponentSwizzle::ZERO.as_raw(),
One = ash::vk::ComponentSwizzle::ONE.as_raw(),
Red = ash::vk::ComponentSwizzle::R.as_raw(),
Green = ash::vk::ComponentSwizzle::G.as_raw(),
Blue = ash::vk::ComponentSwizzle::B.as_raw(),
Alpha = ash::vk::ComponentSwizzle::A.as_raw(),
}
impl From<ComponentSwizzle> for ash::vk::ComponentSwizzle {
#[inline]
fn from(val: ComponentSwizzle) -> Self {
Self::from_raw(val as i32)
}
}
impl Default for ComponentSwizzle {
#[inline]
fn default() -> ComponentSwizzle {
ComponentSwizzle::Identity
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum Filter {
Nearest = ash::vk::Filter::NEAREST.as_raw(),
Linear = ash::vk::Filter::LINEAR.as_raw(),
Cubic = ash::vk::Filter::CUBIC_EXT.as_raw(),
}
impl From<Filter> for ash::vk::Filter {
#[inline]
fn from(val: Filter) -> Self {
Self::from_raw(val as i32)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum SamplerMipmapMode {
Nearest = ash::vk::SamplerMipmapMode::NEAREST.as_raw(),
Linear = ash::vk::SamplerMipmapMode::LINEAR.as_raw(),
}
impl From<SamplerMipmapMode> for ash::vk::SamplerMipmapMode {
#[inline]
fn from(val: SamplerMipmapMode) -> Self {
Self::from_raw(val as i32)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum SamplerAddressMode {
Repeat = ash::vk::SamplerAddressMode::REPEAT.as_raw(),
MirroredRepeat = ash::vk::SamplerAddressMode::MIRRORED_REPEAT.as_raw(),
ClampToEdge = ash::vk::SamplerAddressMode::CLAMP_TO_EDGE.as_raw(),
ClampToBorder = ash::vk::SamplerAddressMode::CLAMP_TO_BORDER.as_raw(),
MirrorClampToEdge = ash::vk::SamplerAddressMode::MIRROR_CLAMP_TO_EDGE.as_raw(),
}
impl From<SamplerAddressMode> for ash::vk::SamplerAddressMode {
#[inline]
fn from(val: SamplerAddressMode) -> Self {
Self::from_raw(val as i32)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum BorderColor {
FloatTransparentBlack = ash::vk::BorderColor::FLOAT_TRANSPARENT_BLACK.as_raw(),
IntTransparentBlack = ash::vk::BorderColor::INT_TRANSPARENT_BLACK.as_raw(),
FloatOpaqueBlack = ash::vk::BorderColor::FLOAT_OPAQUE_BLACK.as_raw(),
IntOpaqueBlack = ash::vk::BorderColor::INT_OPAQUE_BLACK.as_raw(),
FloatOpaqueWhite = ash::vk::BorderColor::FLOAT_OPAQUE_WHITE.as_raw(),
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)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum SamplerReductionMode {
WeightedAverage = ash::vk::SamplerReductionMode::WEIGHTED_AVERAGE.as_raw(),
Min = ash::vk::SamplerReductionMode::MIN.as_raw(),
Max = ash::vk::SamplerReductionMode::MAX.as_raw(),
}
impl From<SamplerReductionMode> for ash::vk::SamplerReductionMode {
#[inline]
fn from(val: SamplerReductionMode) -> Self {
Self::from_raw(val as i32)
}
}
#[derive(Clone, Copy, Debug)]
pub enum SamplerImageViewIncompatibleError {
BorderColorFormatNotCompatible,
BorderColorOpaqueBlackNotIdentitySwizzled,
DepthComparisonNotSupported,
DepthComparisonWrongAspect,
FilterLinearNotSupported,
FilterCubicNotSupported,
FilterCubicMinmaxNotSupported,
MipmapModeLinearNotSupported,
UnnormalizedCoordinatesMultipleMipLevels,
UnnormalizedCoordinatesViewTypeNotCompatible,
}
impl error::Error for SamplerImageViewIncompatibleError {}
impl fmt::Display for SamplerImageViewIncompatibleError {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self {
Self::BorderColorFormatNotCompatible => write!(fmt, "the sampler has a border color with a numeric type different from the image view"),
Self::BorderColorOpaqueBlackNotIdentitySwizzled => write!(fmt, "the sampler has an opaque black border color, but the image view is not identity swizzled"),
Self::DepthComparisonNotSupported => write!(fmt, "the sampler has depth comparison enabled, but this is not supported by the image view"),
Self::DepthComparisonWrongAspect => write!(fmt, "the sampler has depth comparison enabled, but the image view does not select the `depth` aspect"),
Self::FilterLinearNotSupported => write!(fmt, "the sampler uses a linear filter, but this is not supported by the image view's format features"),
Self::FilterCubicNotSupported => write!(fmt, "the sampler uses a cubic filter, but this is not supported by the image view's format features"),
Self::FilterCubicMinmaxNotSupported => write!(fmt, "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!(fmt, "the sampler uses a linear mipmap mode, but this is not supported by the image view's format features"),
Self::UnnormalizedCoordinatesMultipleMipLevels => write!(fmt, "the sampler uses unnormalized coordinates, but the image view has multiple mip levels"),
Self::UnnormalizedCoordinatesViewTypeNotCompatible => write!(fmt, "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,
},
};
#[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_some());
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_some());
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::FeatureNotEnabled {
feature: "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::FeatureNotEnabled {
feature: "sampler_mirror_clamp_to_edge",
..
}
| SamplerCreationError::ExtensionNotEnabled {
extension: "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::FeatureNotEnabled {
feature: "sampler_filter_minmax",
..
}
| SamplerCreationError::ExtensionNotEnabled {
extension: "ext_sampler_filter_minmax",
..
},
) => (),
_ => panic!(),
}
}
}