pub mod ycbcr;
use self::ycbcr::SamplerYcbcrConversion;
use crate::{
device::{Device, DeviceOwned, DeviceOwnedDebugWrapper},
format::{FormatFeatures, NumericType},
image::{
view::{ImageView, ImageViewType},
ImageAspects,
},
instance::InstanceOwnedDebugWrapper,
macros::{impl_id_counter, vulkan_enum},
pipeline::graphics::depth_stencil::CompareOp,
Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, VulkanError, VulkanObject,
};
use std::{mem::MaybeUninit, num::NonZeroU64, ops::RangeInclusive, ptr, sync::Arc};
#[derive(Debug)]
pub struct Sampler {
handle: ash::vk::Sampler,
device: InstanceOwnedDebugWrapper<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<DeviceOwnedDebugWrapper<Arc<SamplerYcbcrConversion>>>,
unnormalized_coordinates: bool,
}
impl Sampler {
#[inline]
pub fn new(
device: Arc<Device>,
create_info: SamplerCreateInfo,
) -> Result<Arc<Sampler>, Validated<VulkanError>> {
Self::validate_new(&device, &create_info)?;
unsafe { Ok(Self::new_unchecked(device, create_info)?) }
}
fn validate_new(
device: &Device,
create_info: &SamplerCreateInfo,
) -> Result<(), Box<ValidationError>> {
create_info
.validate(device)
.map_err(|err| err.add_context("create_info"))?;
Ok(())
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
pub unsafe fn new_unchecked(
device: Arc<Device>,
create_info: SamplerCreateInfo,
) -> Result<Arc<Sampler>, VulkanError> {
let &SamplerCreateInfo {
mag_filter,
min_filter,
mipmap_mode,
address_mode,
mip_lod_bias,
anisotropy,
compare,
ref lod,
border_color,
unnormalized_coordinates,
reduction_mode,
ref sampler_ycbcr_conversion,
_ne: _,
} = &create_info;
let (anisotropy_enable, max_anisotropy) = if let Some(max_anisotropy) = anisotropy {
(ash::vk::TRUE, max_anisotropy)
} else {
(ash::vk::FALSE, 1.0)
};
let (compare_enable, compare_op) = if let Some(compare_op) = compare {
(ash::vk::TRUE, compare_op)
} else {
(ash::vk::FALSE, CompareOp::Never)
};
let mut create_info_vk = 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()
};
let mut sampler_reduction_mode_create_info_vk = None;
let mut sampler_ycbcr_conversion_info_vk = None;
if reduction_mode != SamplerReductionMode::WeightedAverage {
let next = sampler_reduction_mode_create_info_vk.insert(
ash::vk::SamplerReductionModeCreateInfo {
reduction_mode: reduction_mode.into(),
..Default::default()
},
);
next.p_next = create_info_vk.p_next;
create_info_vk.p_next = next as *const _ as *const _;
}
if let Some(sampler_ycbcr_conversion) = sampler_ycbcr_conversion {
let next =
sampler_ycbcr_conversion_info_vk.insert(ash::vk::SamplerYcbcrConversionInfo {
conversion: sampler_ycbcr_conversion.handle(),
..Default::default()
});
next.p_next = create_info_vk.p_next;
create_info_vk.p_next = next 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_vk,
ptr::null(),
output.as_mut_ptr(),
)
.result()
.map_err(VulkanError::from)?;
output.assume_init()
};
Ok(Self::from_handle(device, handle, create_info))
}
#[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: InstanceOwnedDebugWrapper(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: sampler_ycbcr_conversion.map(DeviceOwnedDebugWrapper),
unnormalized_coordinates,
})
}
pub(crate) fn check_can_sample(
&self,
image_view: &ImageView,
) -> Result<(), Box<ValidationError>> {
if self.compare.is_some() {
if !image_view
.format_features()
.intersects(FormatFeatures::SAMPLED_IMAGE_DEPTH_COMPARISON)
{
return Err(Box::new(ValidationError {
problem: "the sampler has depth comparison enabled, and \
the image view's format features do not include \
`FormatFeatures::SAMPLED_IMAGE_DEPTH_COMPARISON`"
.into(),
..Default::default()
}));
}
if !image_view
.subresource_range()
.aspects
.intersects(ImageAspects::DEPTH)
{
return Err(Box::new(ValidationError {
problem: "the sampler has depth comparison enabled, and \
the image view's aspects do not include `ImageAspects::DEPTH`"
.into(),
..Default::default()
}));
}
} else {
if !image_view
.format_features()
.intersects(FormatFeatures::SAMPLED_IMAGE_FILTER_LINEAR)
{
if self.mag_filter == Filter::Linear || self.min_filter == Filter::Linear {
return Err(Box::new(ValidationError {
problem: "the sampler's `mag_filter` or `min_filter` is `Filter::Linear`, \
and the image view's format features do not include \
`FormatFeatures::SAMPLED_IMAGE_FILTER_LINEAR`"
.into(),
..Default::default()
}));
}
if self.mipmap_mode == SamplerMipmapMode::Linear {
return Err(Box::new(ValidationError {
problem: "the sampler's `mipmap_mode` is `SamplerMipmapMpde::Linear`, and \
the image view's format features do not include \
`FormatFeatures::SAMPLED_IMAGE_FILTER_LINEAR`"
.into(),
..Default::default()
}));
}
}
}
if self.mag_filter == Filter::Cubic || self.min_filter == Filter::Cubic {
if !image_view
.format_features()
.intersects(FormatFeatures::SAMPLED_IMAGE_FILTER_CUBIC)
{
return Err(Box::new(ValidationError {
problem: "the sampler's `mag_filter` or `min_filter` is `Filter::Cubic`, and \
the image view's format features do not include \
`FormatFeatures::SAMPLED_IMAGE_FILTER_CUBIC`"
.into(),
..Default::default()
}));
}
if !image_view.filter_cubic() {
return Err(Box::new(ValidationError {
problem: "the sampler's `mag_filter` or `min_filter` is Filter::Cubic, and \
the image view does not support this, as returned by \
`PhysicalDevice::image_format_properties`"
.into(),
..Default::default()
}));
}
if matches!(
self.reduction_mode,
SamplerReductionMode::Min | SamplerReductionMode::Max
) && !image_view.filter_cubic_minmax()
{
return Err(Box::new(ValidationError {
problem: "the sampler's `mag_filter` or `min_filter` is `Filter::Cubic`, and \
the its `reduction_mode` is `SamplerReductionMode::Min` or \
`SamplerReductionMode::Max`, and \
the image view does not support this, as returned by \
`PhysicalDevice::image_format_properties`"
.into(),
..Default::default()
}));
}
}
if let Some(border_color) = self.border_color {
let aspects = image_view.subresource_range().aspects;
let view_numeric_type = NumericType::from(
if aspects.intersects(
ImageAspects::COLOR
| ImageAspects::PLANE_0
| ImageAspects::PLANE_1
| ImageAspects::PLANE_2,
) {
image_view.format().numeric_format_color().unwrap()
} else if aspects.intersects(ImageAspects::DEPTH) {
image_view.format().numeric_format_depth().unwrap()
} else if aspects.intersects(ImageAspects::STENCIL) {
image_view.format().numeric_format_stencil().unwrap()
} else {
unreachable!()
},
);
match border_color {
BorderColor::IntTransparentBlack
| BorderColor::IntOpaqueBlack
| BorderColor::IntOpaqueWhite => {
if !matches!(view_numeric_type, NumericType::Int | NumericType::Uint) {
return Err(Box::new(ValidationError {
problem: "the sampler has an integer border color, and \
the image view does not have an integer format"
.into(),
..Default::default()
}));
}
}
BorderColor::FloatTransparentBlack
| BorderColor::FloatOpaqueBlack
| BorderColor::FloatOpaqueWhite => {
if !matches!(view_numeric_type, NumericType::Float) {
return Err(Box::new(ValidationError {
problem: "the sampler has an floating-point border color, and \
the image view does not have a floating-point format"
.into(),
..Default::default()
}));
}
}
}
if matches!(
border_color,
BorderColor::FloatOpaqueBlack | BorderColor::IntOpaqueBlack
) && !image_view.component_mapping().is_identity()
{
return Err(Box::new(ValidationError {
problem: "the sampler has an opaque black border color, and \
the image view is not identity swizzled"
.into(),
..Default::default()
}));
}
}
if self.unnormalized_coordinates {
if !matches!(
image_view.view_type(),
ImageViewType::Dim1d | ImageViewType::Dim2d
) {
return Err(Box::new(ValidationError {
problem: "the sampler uses unnormalized coordinates, and \
the image view's type is not `ImageViewtype::Dim1d` or \
`ImageViewType::Dim2d`"
.into(),
..Default::default()
}));
}
if image_view.subresource_range().mip_levels.end
- image_view.subresource_range().mip_levels.start
!= 1
{
return Err(Box::new(ValidationError {
problem: "the sampler uses unnormalized coordinates, and \
the image view has more than one mip level"
.into(),
..Default::default()
}));
}
}
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_deref()
}
#[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);
#[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 {
#[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 {
#[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(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let &Self {
mag_filter,
min_filter,
mipmap_mode,
address_mode,
mip_lod_bias,
anisotropy,
compare,
ref lod,
border_color,
unnormalized_coordinates,
reduction_mode,
ref sampler_ycbcr_conversion,
_ne: _,
} = self;
let properties = device.physical_device().properties();
mag_filter.validate_device(device).map_err(|err| {
err.add_context("mag_filter")
.set_vuids(&["VUID-VkSamplerCreateInfo-magFilter-parameter"])
})?;
min_filter.validate_device(device).map_err(|err| {
err.add_context("min_filter")
.set_vuids(&["VUID-VkSamplerCreateInfo-minFilter-parameter"])
})?;
mipmap_mode.validate_device(device).map_err(|err| {
err.add_context("mipmap_mode")
.set_vuids(&["VUID-VkSamplerCreateInfo-mipmapMode-parameter"])
})?;
for (index, mode) in address_mode.into_iter().enumerate() {
mode.validate_device(device).map_err(|err| {
err.add_context(format!("address_mode[{}]", index))
.set_vuids(&[
"VUID-VkSamplerCreateInfo-addressModeU-parameter",
"VUID-VkSamplerCreateInfo-addressModeV-parameter",
"VUID-VkSamplerCreateInfo-addressModeW-parameter",
])
})?;
}
if address_mode.contains(&SamplerAddressMode::ClampToBorder) {
border_color.validate_device(device).map_err(|err| {
err.add_context("border_color")
.set_vuids(&["VUID-VkSamplerCreateInfo-addressModeU-01078"])
})?;
}
reduction_mode.validate_device(device).map_err(|err| {
err.add_context("reduction_mode")
.set_vuids(&["VUID-VkSamplerReductionModeCreateInfo-reductionMode-parameter"])
})?;
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(Box::new(ValidationError {
context: "address_mode".into(),
problem: "contains `SamplerAddressMode::MirrorClampToEdge`".into(),
requires_one_of: RequiresOneOf(&[
RequiresAllOf(&[Requires::Feature("sampler_mirror_clamp_to_edge")]),
RequiresAllOf(&[Requires::DeviceExtension(
"khr_sampler_mirror_clamp_to_edge",
)]),
]),
..Default::default()
}));
}
}
if lod.is_empty() {
return Err(Box::new(ValidationError {
context: "lod".into(),
problem: "is empty".into(),
vuids: &["VUID-VkSamplerCreateInfo-maxLod-01973"],
..Default::default()
}));
}
if mip_lod_bias.abs() > properties.max_sampler_lod_bias {
return Err(Box::new(ValidationError {
context: "lod".into(),
problem: "the absolute value is greater than the `max_sampler_lod_bias` limit"
.into(),
vuids: &["VUID-VkSamplerCreateInfo-mipLodBias-01069"],
..Default::default()
}));
}
if device.enabled_extensions().khr_portability_subset
&& !device.enabled_features().sampler_mip_lod_bias
&& mip_lod_bias != 0.0
{
return Err(Box::new(ValidationError {
problem: "this device is a portability subset device, and \
`mip_lod_bias` is not zero"
.into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"sampler_mip_lod_bias",
)])]),
vuids: &["VUID-VkSamplerCreateInfo-samplerMipLodBias-04467"],
..Default::default()
}));
}
if let Some(max_anisotropy) = anisotropy {
if !device.enabled_features().sampler_anisotropy {
return Err(Box::new(ValidationError {
context: "anisotropy".into(),
problem: "is `Some`".into(),
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"sampler_anisotropy",
)])]),
vuids: &["VUID-VkSamplerCreateInfo-anisotropyEnable-01070"],
}));
}
if max_anisotropy < 1.0 {
return Err(Box::new(ValidationError {
context: "anisotropy".into(),
problem: "is less than 1.0".into(),
vuids: &["VUID-VkSamplerCreateInfo-anisotropyEnable-01071"],
..Default::default()
}));
}
if max_anisotropy > properties.max_sampler_anisotropy {
return Err(Box::new(ValidationError {
context: "anisotropy".into(),
problem: "is greater than the `max_sampler_anisotropy` limit".into(),
vuids: &["VUID-VkSamplerCreateInfo-anisotropyEnable-01071"],
..Default::default()
}));
}
if [mag_filter, min_filter].contains(&Filter::Cubic) {
return Err(Box::new(ValidationError {
problem: "`anisotropy` is `Some`, but `mag_filter` or `min_filter` is \
`Filter::Cubic`"
.into(),
vuids: &["VUID-VkSamplerCreateInfo-magFilter-01081"],
..Default::default()
}));
}
}
if let Some(compare_op) = compare {
compare_op.validate_device(device).map_err(|err| {
err.add_context("compare")
.set_vuids(&["VUID-VkSamplerCreateInfo-compareEnable-01080"])
})?;
if reduction_mode != SamplerReductionMode::WeightedAverage {
return Err(Box::new(ValidationError {
problem: "`compare` is `Some`, but `reduction_mode` is not \
`SamplerReductionMode::WeightedAverage`"
.into(),
vuids: &["VUID-VkSamplerCreateInfo-compareEnable-01423"],
..Default::default()
}));
}
}
if unnormalized_coordinates {
if min_filter != mag_filter {
return Err(Box::new(ValidationError {
problem: "`unnormalized_coordinates` is `true`, but \
`min_filter` and `mag_filter` are not equal"
.into(),
vuids: &["VUID-VkSamplerCreateInfo-unnormalizedCoordinates-01072"],
..Default::default()
}));
}
if mipmap_mode != SamplerMipmapMode::Nearest {
return Err(Box::new(ValidationError {
problem: "`unnormalized_coordinates` is `true`, but \
`mipmap_mode` is not `SamplerMipmapMode::Nearest`"
.into(),
vuids: &["VUID-VkSamplerCreateInfo-unnormalizedCoordinates-01073"],
..Default::default()
}));
}
if *lod != (0.0..=0.0) {
return Err(Box::new(ValidationError {
problem: "`unnormalized_coordinates` is `true`, but \
`lod` is not `0.0..=1.0`"
.into(),
vuids: &["VUID-VkSamplerCreateInfo-unnormalizedCoordinates-01074"],
..Default::default()
}));
}
if address_mode[0..2].iter().any(|mode| {
!matches!(
mode,
SamplerAddressMode::ClampToEdge | SamplerAddressMode::ClampToBorder
)
}) {
return Err(Box::new(ValidationError {
problem: "`unnormalized_coordinates` is `true`, but \
`address_mode[0]` or `address_mode[1]` are not \
`SamplerAddressMode::ClampToEdge` or `SamplerAddressMode::ClampToBorder`"
.into(),
vuids: &["VUID-VkSamplerCreateInfo-unnormalizedCoordinates-01075"],
..Default::default()
}));
}
if anisotropy.is_some() {
return Err(Box::new(ValidationError {
problem: "`unnormalized_coordinates` is `true`, but `anisotropy` is `Some`"
.into(),
vuids: &["VUID-VkSamplerCreateInfo-unnormalizedCoordinates-01076"],
..Default::default()
}));
}
if compare.is_some() {
return Err(Box::new(ValidationError {
problem: "`unnormalized_coordinates` is `true`, but `compare` is `Some`".into(),
vuids: &["VUID-VkSamplerCreateInfo-unnormalizedCoordinates-0107"],
..Default::default()
}));
}
}
if reduction_mode != SamplerReductionMode::WeightedAverage {
if !(device.enabled_features().sampler_filter_minmax
|| device.enabled_extensions().ext_sampler_filter_minmax)
{
return Err(Box::new(ValidationError {
context: "reduction_mode".into(),
problem: "is not `SamplerReductionMode::WeightedAverage`".into(),
requires_one_of: RequiresOneOf(&[
RequiresAllOf(&[Requires::Feature("sampler_filter_minmax")]),
RequiresAllOf(&[Requires::DeviceExtension("ext_sampler_filter_minmax")]),
]),
..Default::default()
}));
}
}
if let Some(sampler_ycbcr_conversion) = sampler_ycbcr_conversion {
assert_eq!(device, sampler_ycbcr_conversion.device().as_ref());
let potential_format_features = unsafe {
device
.physical_device()
.format_properties_unchecked(sampler_ycbcr_conversion.format())
.potential_format_features()
};
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(Box::new(ValidationError {
problem: "`sampler_ycbcr_conversion` is `Some`, and \
the potential format features of `sampler_ycbcr_conversion.format()` \
do not include `FormatFeatures::\
SAMPLED_IMAGE_YCBCR_CONVERSION_SEPARATE_RECONSTRUCTION_FILTER`, but \
`mag_filter` and `min_filter` are not both equal to \
`sampler_ycbcr_conversion.chroma_filter()`"
.into(),
vuids: &["VUID-VkSamplerCreateInfo-minFilter-01645"],
..Default::default()
}));
}
if address_mode
.into_iter()
.any(|mode| !matches!(mode, SamplerAddressMode::ClampToEdge))
{
return Err(Box::new(ValidationError {
problem: "`sampler_ycbcr_conversion` is `Some`, but \
not all elements of `address_mode` are \
`SamplerAddressMode::ClampToEdge`"
.into(),
vuids: &["VUID-VkSamplerCreateInfo-addressModeU-01646"],
..Default::default()
}));
}
if anisotropy.is_some() {
return Err(Box::new(ValidationError {
problem: "`sampler_ycbcr_conversion` is `Some`, but \
`anisotropy` is `Some`"
.into(),
vuids: &["VUID-VkSamplerCreateInfo-addressModeU-01646"],
..Default::default()
}));
}
if unnormalized_coordinates {
return Err(Box::new(ValidationError {
problem: "`sampler_ycbcr_conversion` is `Some`, but \
`unnormalized_coordinates` is `true`"
.into(),
vuids: &["VUID-VkSamplerCreateInfo-addressModeU-01646"],
..Default::default()
}));
}
if reduction_mode != SamplerReductionMode::WeightedAverage {
return Err(Box::new(ValidationError {
problem: "`sampler_ycbcr_conversion` is `Some`, but \
`reduction_mode` is not `SamplerReductionMode::WeightedAverage`"
.into(),
vuids: &["VUID-VkSamplerCreateInfo-None-01647"],
..Default::default()
}));
}
}
Ok(())
}
}
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),
},
]
}
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let &Self { r, g, b, a } = self;
r.validate_device(device).map_err(|err| {
err.add_context("r")
.set_vuids(&["VUID-VkComponentMapping-r-parameter"])
})?;
g.validate_device(device).map_err(|err| {
err.add_context("g")
.set_vuids(&["VUID-VkComponentMapping-g-parameter"])
})?;
b.validate_device(device).map_err(|err| {
err.add_context("b")
.set_vuids(&["VUID-VkComponentMapping-b-parameter"])
})?;
a.validate_device(device).map_err(|err| {
err.add_context("a")
.set_vuids(&["VUID-VkComponentMapping-a-parameter"])
})?;
Ok(())
}
}
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]
ComponentSwizzle = ComponentSwizzle(i32);
Identity = IDENTITY,
Zero = ZERO,
One = ONE,
Red = R,
Green = G,
Blue = B,
Alpha = A,
}
impl Default for ComponentSwizzle {
#[inline]
fn default() -> ComponentSwizzle {
ComponentSwizzle::Identity
}
}
vulkan_enum! {
#[non_exhaustive]
Filter = Filter(i32);
Nearest = NEAREST,
Linear = LINEAR,
Cubic = CUBIC_EXT
RequiresOneOf([
RequiresAllOf([DeviceExtension(ext_filter_cubic)]),
RequiresAllOf([DeviceExtension(img_filter_cubic)]),
]),
}
vulkan_enum! {
#[non_exhaustive]
SamplerMipmapMode = SamplerMipmapMode(i32);
Nearest = NEAREST,
Linear = LINEAR,
}
vulkan_enum! {
#[non_exhaustive]
SamplerAddressMode = SamplerAddressMode(i32);
Repeat = REPEAT,
MirroredRepeat = MIRRORED_REPEAT,
ClampToEdge = CLAMP_TO_EDGE,
ClampToBorder = CLAMP_TO_BORDER,
MirrorClampToEdge = MIRROR_CLAMP_TO_EDGE
RequiresOneOf([
RequiresAllOf([APIVersion(V1_2)]),
RequiresAllOf([DeviceExtension(khr_sampler_mirror_clamp_to_edge)]),
]),
}
vulkan_enum! {
#[non_exhaustive]
BorderColor = BorderColor(i32);
FloatTransparentBlack = FLOAT_TRANSPARENT_BLACK,
IntTransparentBlack = INT_TRANSPARENT_BLACK,
FloatOpaqueBlack = FLOAT_OPAQUE_BLACK,
IntOpaqueBlack = INT_OPAQUE_BLACK,
FloatOpaqueWhite = FLOAT_OPAQUE_WHITE,
IntOpaqueWhite = INT_OPAQUE_WHITE,
}
vulkan_enum! {
#[non_exhaustive]
SamplerReductionMode = SamplerReductionMode(i32);
WeightedAverage = WEIGHTED_AVERAGE,
Min = MIN,
Max = MAX,
}
#[cfg(test)]
mod tests {
use crate::{
image::sampler::{
Filter, Sampler, SamplerAddressMode, SamplerCreateInfo, SamplerReductionMode,
},
pipeline::graphics::depth_stencil::CompareOp,
Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError,
};
#[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!();
if 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()
},
)
.is_ok()
{
panic!()
}
}
#[test]
fn max_anisotropy() {
let (device, _queue) = gfx_dev_and_queue!();
if 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()
},
)
.is_ok()
{
panic!()
}
}
#[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(Validated::ValidationError(_)) => (),
_ => 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(Validated::ValidationError(_)) => (),
_ => 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(Validated::ValidationError(_)) => (),
_ => 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(Validated::ValidationError(err))
if matches!(
*err,
ValidationError {
requires_one_of: RequiresOneOf([
RequiresAllOf([Requires::Feature("sampler_mirror_clamp_to_edge")]),
RequiresAllOf([Requires::DeviceExtension(
"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(Validated::ValidationError(err))
if matches!(
*err,
ValidationError {
requires_one_of: RequiresOneOf([
RequiresAllOf([Requires::Feature("sampler_filter_minmax")]),
RequiresAllOf([Requires::DeviceExtension("ext_sampler_filter_minmax")])
],),
..
}
) => {}
_ => panic!(),
}
}
}