Sampler ycbcr conversion (#1807)

* Better validation of image view creation, descriptor set updates, draw-time resources and others

* Add sampler YCbCr conversion

* Add shader validation

* Minor fixes

* Add YCbCr example

* Typo
This commit is contained in:
Rua 2022-01-30 02:52:57 +01:00 committed by GitHub
parent 5f601b125e
commit e3146aac0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1245 additions and 59 deletions

View File

@ -88,6 +88,7 @@ fn write_descriptor_requirements(
image_view_type,
sampler_compare,
sampler_no_unnormalized_coordinates,
sampler_no_ycbcr_conversion,
sampler_with_images,
stages,
storage_image_atomic,
@ -122,6 +123,7 @@ fn write_descriptor_requirements(
};
let sampler_compare = sampler_compare.iter();
let sampler_no_unnormalized_coordinates = sampler_no_unnormalized_coordinates.iter();
let sampler_no_ycbcr_conversion = sampler_no_ycbcr_conversion.iter();
let sampler_with_images = {
sampler_with_images.iter().map(|(&index, identifiers)| {
let identifiers = identifiers.iter().map(
@ -196,6 +198,7 @@ fn write_descriptor_requirements(
image_view_type: #image_view_type,
sampler_compare: [#(#sampler_compare),*].into_iter().collect(),
sampler_no_unnormalized_coordinates: [#(#sampler_no_unnormalized_coordinates),*].into_iter().collect(),
sampler_no_ycbcr_conversion: [#(#sampler_no_ycbcr_conversion),*].into_iter().collect(),
sampler_with_images: [#(#sampler_with_images),*].into_iter().collect(),
stages: #stages,
storage_image_atomic: [#(#storage_image_atomic),*].into_iter().collect(),

View File

@ -2471,10 +2471,6 @@ impl UnsafeCommandBufferBuilderPipelineBarrier {
(ash::vk::QUEUE_FAMILY_IGNORED, ash::vk::QUEUE_FAMILY_IGNORED)
};
if image.format().ycbcr_chroma_sampling().is_some() {
unimplemented!();
}
// TODO: Let user choose
let aspects = image.format().aspects();
let image = image.inner();

View File

@ -183,6 +183,16 @@ pub(in super::super) fn check_descriptor_sets_validity<'a, P: Pipeline>(
return Err(InvalidDescriptorResource::SamplerUnnormalizedCoordinatesNotAllowed);
}
// - OpImageFetch, OpImageSparseFetch, OpImage*Gather, and OpImageSparse*Gather must not
// be used with a sampler that enables sampler YCBCR conversion.
// - The ConstOffset and Offset operands must not be used with a sampler that enables
// sampler YCBCR conversion.
if reqs.sampler_no_ycbcr_conversion.contains(&index)
&& sampler.sampler_ycbcr_conversion().is_some()
{
return Err(InvalidDescriptorResource::SamplerYcbcrConversionNotAllowed);
}
/*
Instruction/Sampler/Image View Validation
https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap16.html#textures-input-validation
@ -425,6 +435,7 @@ pub enum InvalidDescriptorResource {
error: SamplerImageViewIncompatibleError,
},
SamplerUnnormalizedCoordinatesNotAllowed,
SamplerYcbcrConversionNotAllowed,
StorageImageAtomicNotSupported,
StorageReadWithoutFormatNotSupported,
StorageWriteWithoutFormatNotSupported,
@ -481,6 +492,12 @@ impl fmt::Display for InvalidDescriptorResource {
"the bound sampler is required to have unnormalized coordinates disabled"
)
}
Self::SamplerYcbcrConversionNotAllowed => {
write!(
fmt,
"the bound sampler is required to have no attached sampler YCbCr conversion"
)
}
Self::StorageImageAtomicNotSupported => {
write!(fmt, "the bound image view did not support the `storage_image_atomic` format feature")
}

View File

@ -296,7 +296,9 @@ pub struct DescriptorDesc {
/// do not need to be provided when creating a descriptor set.
///
/// The list must be either empty, or contain exactly `descriptor_count` samplers. It must be
/// empty if `ty` is something other than `Sampler` or `CombinedImageSampler`.
/// empty if `ty` is something other than `Sampler` or `CombinedImageSampler`. If any of the
/// samplers has an attached sampler YCbCr conversion, then only `CombinedImageSampler` is
/// allowed.
pub immutable_samplers: Vec<Arc<Sampler>>,
}
@ -317,6 +319,7 @@ impl DescriptorDesc {
image_view_type,
sampler_compare,
sampler_no_unnormalized_coordinates,
sampler_no_ycbcr_conversion,
sampler_with_images,
stages,
storage_image_atomic,

View File

@ -116,11 +116,21 @@ impl DescriptorSetLayout {
let mut binding_flags = ash::vk::DescriptorBindingFlags::empty();
let p_immutable_samplers = if !binding_desc.immutable_samplers.is_empty() {
if !matches!(
ty,
DescriptorType::Sampler | DescriptorType::CombinedImageSampler
) {
return Err(DescriptorSetLayoutError::ImmutableSamplersWrongDescriptorType);
if binding_desc
.immutable_samplers
.iter()
.any(|sampler| sampler.sampler_ycbcr_conversion().is_some())
{
if !matches!(ty, DescriptorType::CombinedImageSampler) {
return Err(DescriptorSetLayoutError::ImmutableSamplersWrongDescriptorType);
}
} else {
if !matches!(
ty,
DescriptorType::Sampler | DescriptorType::CombinedImageSampler
) {
return Err(DescriptorSetLayoutError::ImmutableSamplersWrongDescriptorType);
}
}
if binding_desc.descriptor_count != binding_desc.immutable_samplers.len() as u32 {
@ -359,7 +369,8 @@ pub enum DescriptorSetLayoutError {
},
/// Immutable samplers were included on a descriptor type other than `Sampler` or
/// `CombinedImageSampler`.
/// `CombinedImageSampler`, or, if any of the samplers had a sampler YCbCr conversion, were
/// included on a descriptor type other than `CombinedImageSampler`.
ImmutableSamplersWrongDescriptorType,
/// The maximum number of push descriptors has been exceeded.
@ -417,7 +428,7 @@ impl std::fmt::Display for DescriptorSetLayoutError {
Self::ImmutableSamplersWrongDescriptorType => {
write!(
fmt,
"immutable samplers were included on a descriptor type other than Sampler or CombinedImageSampler"
"immutable samplers were included on a descriptor type other than Sampler or CombinedImageSampler, or, if any of the samplers had a sampler YCbCr conversion, were included on a descriptor type other than CombinedImageSampler"
)
}
Self::MaxPushDescriptorsExceeded { .. } => {

View File

@ -637,6 +637,16 @@ pub(crate) fn check_descriptor_write<'a>(
index: descriptor_range_start + index as u32,
});
}
// VUID-VkWriteDescriptorSet-descriptorType-01946
if image_view.sampler_ycbcr_conversion().is_some() {
return Err(
DescriptorSetUpdateError::ImageViewHasSamplerYcbcrConversion {
binding: write.binding(),
index: descriptor_range_start + index as u32,
},
);
}
}
}
DescriptorType::StorageImage => {
@ -683,6 +693,16 @@ pub(crate) fn check_descriptor_write<'a>(
index: descriptor_range_start + index as u32,
});
}
// VUID??
if image_view.sampler_ycbcr_conversion().is_some() {
return Err(
DescriptorSetUpdateError::ImageViewHasSamplerYcbcrConversion {
binding: write.binding(),
index: descriptor_range_start + index as u32,
},
);
}
}
}
DescriptorType::InputAttachment => {
@ -730,6 +750,16 @@ pub(crate) fn check_descriptor_write<'a>(
});
}
// VUID??
if image_view.sampler_ycbcr_conversion().is_some() {
return Err(
DescriptorSetUpdateError::ImageViewHasSamplerYcbcrConversion {
binding: write.binding(),
index: descriptor_range_start + index as u32,
},
);
}
// VUID??
if image_view.ty().is_arrayed() {
return Err(DescriptorSetUpdateError::ImageViewIsArrayed {
@ -793,6 +823,22 @@ pub(crate) fn check_descriptor_write<'a>(
});
}
if image_view.sampler_ycbcr_conversion().is_some() {
return Err(
DescriptorSetUpdateError::ImageViewHasSamplerYcbcrConversion {
binding: write.binding(),
index: descriptor_range_start + index as u32,
},
);
}
if sampler.sampler_ycbcr_conversion().is_some() {
return Err(DescriptorSetUpdateError::SamplerHasSamplerYcbcrConversion {
binding: write.binding(),
index: descriptor_range_start + index as u32,
});
}
if let Err(error) = sampler.check_can_sample(image_view.as_ref()) {
return Err(DescriptorSetUpdateError::ImageViewIncompatibleSampler {
binding: write.binding(),
@ -816,11 +862,18 @@ pub(crate) fn check_descriptor_write<'a>(
});
}
for sampler in elements {
for (index, sampler) in elements.iter().enumerate() {
assert_eq!(
sampler.device().internal_object(),
layout.device().internal_object(),
);
if sampler.sampler_ycbcr_conversion().is_some() {
return Err(DescriptorSetUpdateError::SamplerHasSamplerYcbcrConversion {
binding: write.binding(),
index: descriptor_range_start + index as u32,
});
}
}
}
_ => {
@ -852,6 +905,10 @@ pub enum DescriptorSetUpdateError {
/// Tried to write an image view that has both the `depth` and `stencil` aspects.
ImageViewDepthAndStencil { binding: u32, index: u32 },
/// Tried to write an image view with an attached sampler YCbCr conversion to a binding that
/// does not support it.
ImageViewHasSamplerYcbcrConversion { binding: u32, index: u32 },
/// Tried to write an image view of an arrayed type to a descriptor type that does not support
/// it.
ImageViewIsArrayed { binding: u32, index: u32 },
@ -882,6 +939,9 @@ pub enum DescriptorSetUpdateError {
usage: &'static str,
},
/// Tried to write a sampler that has an attached sampler YCbCr conversion.
SamplerHasSamplerYcbcrConversion { binding: u32, index: u32 },
/// Tried to write a sampler to a binding with immutable samplers.
SamplerIsImmutable { binding: u32 },
}
@ -918,6 +978,11 @@ impl std::fmt::Display for DescriptorSetUpdateError {
"tried to write an image view to binding {} index {} that has both the `depth` and `stencil` aspects",
binding, index,
),
Self::ImageViewHasSamplerYcbcrConversion { binding, index } => write!(
fmt,
"tried to write an image view to binding {} index {} with an attached sampler YCbCr conversion to binding that does not support it",
binding, index,
),
Self::ImageViewIsArrayed { binding, index } => write!(
fmt,
"tried to write an image view of an arrayed type to binding {} index {}, but this binding has a descriptor type that does not support arrayed image views",
@ -952,6 +1017,11 @@ impl std::fmt::Display for DescriptorSetUpdateError {
"tried to write a resource to binding {} index {} that did not have the required usage {} enabled",
binding, index, usage,
),
Self::SamplerHasSamplerYcbcrConversion { binding, index } => write!(
fmt,
"tried to write a sampler to binding {} index {} that has an attached sampler YCbCr conversion",
binding, index,
),
Self::SamplerIsImmutable { binding } => write!(
fmt,
"tried to write a sampler to binding {}, which already contains immutable samplers in the descriptor set layout",

View File

@ -22,6 +22,7 @@ use std::ffi::CStr;
use std::fmt;
use std::hash::Hash;
use std::mem::MaybeUninit;
use std::ops::BitOr;
use std::ptr;
use std::sync::Arc;
@ -1241,6 +1242,70 @@ pub struct FormatFeatures {
pub acceleration_structure_vertex_buffer: bool,
}
impl BitOr for &FormatFeatures {
type Output = FormatFeatures;
fn bitor(self, rhs: Self) -> Self::Output {
Self::Output {
sampled_image: self.sampled_image || rhs.sampled_image,
storage_image: self.storage_image || rhs.storage_image,
storage_image_atomic: self.storage_image_atomic || rhs.storage_image_atomic,
storage_read_without_format: self.storage_read_without_format
|| rhs.storage_read_without_format,
storage_write_without_format: self.storage_write_without_format
|| rhs.storage_write_without_format,
color_attachment: self.color_attachment || rhs.color_attachment,
color_attachment_blend: self.color_attachment_blend || rhs.color_attachment_blend,
depth_stencil_attachment: self.depth_stencil_attachment || rhs.depth_stencil_attachment,
fragment_density_map: self.fragment_density_map || rhs.fragment_density_map,
fragment_shading_rate_attachment: self.fragment_shading_rate_attachment
|| rhs.fragment_shading_rate_attachment,
transfer_src: self.transfer_src || rhs.transfer_src,
transfer_dst: self.transfer_dst || rhs.transfer_dst,
blit_src: self.blit_src || rhs.blit_src,
blit_dst: self.blit_dst || rhs.blit_dst,
sampled_image_filter_linear: self.sampled_image_filter_linear
|| rhs.sampled_image_filter_linear,
sampled_image_filter_cubic: self.sampled_image_filter_cubic
|| rhs.sampled_image_filter_cubic,
sampled_image_filter_minmax: self.sampled_image_filter_minmax
|| rhs.sampled_image_filter_minmax,
midpoint_chroma_samples: self.midpoint_chroma_samples || rhs.midpoint_chroma_samples,
cosited_chroma_samples: self.cosited_chroma_samples || rhs.cosited_chroma_samples,
sampled_image_ycbcr_conversion_linear_filter: self
.sampled_image_ycbcr_conversion_linear_filter
|| rhs.sampled_image_ycbcr_conversion_linear_filter,
sampled_image_ycbcr_conversion_separate_reconstruction_filter: self
.sampled_image_ycbcr_conversion_separate_reconstruction_filter
|| rhs.sampled_image_ycbcr_conversion_separate_reconstruction_filter,
sampled_image_ycbcr_conversion_chroma_reconstruction_explicit: self
.sampled_image_ycbcr_conversion_chroma_reconstruction_explicit
|| rhs.sampled_image_ycbcr_conversion_chroma_reconstruction_explicit,
sampled_image_ycbcr_conversion_chroma_reconstruction_explicit_forceable: self
.sampled_image_ycbcr_conversion_chroma_reconstruction_explicit_forceable
|| rhs.sampled_image_ycbcr_conversion_chroma_reconstruction_explicit_forceable,
sampled_image_depth_comparison: self.sampled_image_depth_comparison
|| rhs.sampled_image_depth_comparison,
video_decode_output: self.video_decode_output || rhs.video_decode_output,
video_decode_dpb: self.video_decode_dpb || rhs.video_decode_dpb,
video_encode_input: self.video_encode_input || rhs.video_encode_input,
video_encode_dpb: self.video_encode_dpb || rhs.video_encode_dpb,
disjoint: self.disjoint || rhs.disjoint,
uniform_texel_buffer: self.uniform_texel_buffer || rhs.uniform_texel_buffer,
storage_texel_buffer: self.storage_texel_buffer || rhs.storage_texel_buffer,
storage_texel_buffer_atomic: self.storage_texel_buffer_atomic
|| rhs.storage_texel_buffer_atomic,
vertex_buffer: self.vertex_buffer || rhs.vertex_buffer,
acceleration_structure_vertex_buffer: self.acceleration_structure_vertex_buffer
|| rhs.acceleration_structure_vertex_buffer,
}
}
}
impl From<ash::vk::FormatFeatureFlags> for FormatFeatures {
#[inline]
#[rustfmt::skip]

View File

@ -16,8 +16,8 @@
//!
//! Not all formats are supported by every device. Those that devices do support may only be
//! supported for certain use cases. It is an error to use a format where it is not supported, but
//! you can query a device beforehand for its support by calling the `properties` method on a format
//! value. You can use this to select a usable format from one or more suitable alternatives.
//! you can query a device beforehand for its support by calling `format_properties` on the physical
//! device. You can use this to select a usable format from one or more suitable alternatives.
//! Some formats are required to be always supported for a particular usage. These are listed in the
//! [tables in the Vulkan specification](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap43.html#features-required-format-support).
//!
@ -60,14 +60,13 @@
//! applications. In Vulkan, the formats used to encode YCbCr data use the green channel to
//! represent the luma component, while the blue and red components hold the chroma.
//!
//! To use most YCbCr formats in an [image view](crate::image::view), a feature known as
//! *sampler YCbCr conversion* is needed. It must be enabled on both the image view and any
//! combined image samplers in shaders that the image view is attached to. This feature handles
//! the correct conversion between YCbCr input data and RGB data inside the shader. To query whether
//! a format requires the conversion, you can call `requires_sampler_ycbcr_conversion` on a format.
//! As a rule, any format with `444`, `422`, `420`, `3PACK` or `4PACK` in the name requires it.
//! To use most YCbCr formats in an [image view](crate::image::view), a
//! [sampler YCbCr conversion](crate::sampler::ycbcr) object must be created, and attached to both
//! the image view and the sampler. To query whether a format requires the conversion, you can call
//! `ycbcr_chroma_sampling` on a format. As a rule, any format with `444`, `422`, `420`,
//! `3PACK` or `4PACK` in the name requires it.
//!
//! Almost all YCbCr formats make use of **chroma subsampling**. This is a technique whereby the two
//! Many YCbCr formats make use of **chroma subsampling**. This is a technique whereby the two
//! chroma components are encoded using a lower resolution than the luma component. The human eye is
//! less sensitive to color detail than to detail in brightness, so this allows more detail to be
//! encoded in less data. Chroma subsampling is indicated with one of three numbered suffixes in a

View File

@ -15,10 +15,11 @@
use crate::device::physical::FormatFeatures;
use crate::device::{Device, DeviceOwned};
use crate::format::Format;
use crate::format::{ChromaSampling, Format};
use crate::image::{
ImageAccess, ImageAspects, ImageDimensions, ImageTiling, ImageType, ImageUsage, SampleCount,
};
use crate::sampler::ycbcr::SamplerYcbcrConversion;
use crate::sampler::ComponentMapping;
use crate::OomError;
use crate::VulkanObject;
@ -45,6 +46,7 @@ where
format: Format,
format_features: FormatFeatures,
mip_levels: Range<u32>,
sampler_ycbcr_conversion: Option<Arc<SamplerYcbcrConversion>>,
ty: ImageViewType,
usage: ImageUsage,
@ -104,6 +106,7 @@ where
component_mapping: ComponentMapping::default(),
format,
mip_levels,
sampler_ycbcr_conversion: None,
ty,
}
}
@ -192,6 +195,7 @@ pub struct ImageViewBuilder<I> {
component_mapping: ComponentMapping,
format: Format,
mip_levels: Range<u32>,
sampler_ycbcr_conversion: Option<Arc<SamplerYcbcrConversion>>,
ty: ImageViewType,
}
@ -208,6 +212,7 @@ where
format,
mip_levels,
ty,
sampler_ycbcr_conversion,
image,
} = self;
@ -461,14 +466,6 @@ where
return Err(ImageViewCreationError::FormatNotCompatible);
}
// VUID-VkImageViewCreateInfo-format-06415
if image_inner.format().ycbcr_chroma_sampling().is_some() {
// VUID-VkImageViewCreateInfo-format-04714
// VUID-VkImageViewCreateInfo-format-04715
// VUID-VkImageViewCreateInfo-pNext-01970
unimplemented!()
}
// VUID-VkImageViewCreateInfo-imageViewType-04973
if (ty == ImageViewType::Dim1d || ty == ImageViewType::Dim2d || ty == ImageViewType::Dim3d)
&& layer_count != 1
@ -484,7 +481,59 @@ where
return Err(ImageViewCreationError::TypeCubeArrayNotMultipleOf6ArrayLayers);
}
let create_info = ash::vk::ImageViewCreateInfo {
// VUID-VkImageViewCreateInfo-format-04714
// VUID-VkImageViewCreateInfo-format-04715
match format.ycbcr_chroma_sampling() {
Some(ChromaSampling::Mode422) => {
if image_inner.dimensions().width() % 2 != 0 {
return Err(
ImageViewCreationError::FormatChromaSubsamplingInvalidImageDimensions,
);
}
}
Some(ChromaSampling::Mode420) => {
if image_inner.dimensions().width() % 2 != 0
|| image_inner.dimensions().height() % 2 != 0
{
return Err(
ImageViewCreationError::FormatChromaSubsamplingInvalidImageDimensions,
);
}
}
_ => (),
}
// 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(conversion) = &sampler_ycbcr_conversion
{
assert_eq!(image_inner.device(), conversion.device());
// VUID-VkImageViewCreateInfo-pNext-01970
if !component_mapping.is_identity() {
return Err(
ImageViewCreationError::SamplerYcbcrConversionComponentMappingNotIdentity {
component_mapping,
},
);
}
Some(ash::vk::SamplerYcbcrConversionInfo {
conversion: conversion.internal_object(),
..Default::default()
})
} else {
// VUID-VkImageViewCreateInfo-format-06415
if format.ycbcr_chroma_sampling().is_some() {
return Err(
ImageViewCreationError::FormatRequiresSamplerYcbcrConversion { format },
);
}
None
};
let mut create_info = ash::vk::ImageViewCreateInfo {
flags: ash::vk::ImageViewCreateFlags::empty(),
image: image_inner.internal_object(),
view_type: ty.into(),
@ -500,6 +549,11 @@ where
..Default::default()
};
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 = image_inner.device().fns();
let mut output = MaybeUninit::uninit();
@ -539,6 +593,7 @@ where
format,
format_features,
mip_levels,
sampler_ycbcr_conversion,
ty,
usage,
@ -637,6 +692,25 @@ where
self
}
/// The sampler YCbCr conversion to be used with the image view.
///
/// If set to `Some`, several restrictions apply:
/// - The `component_mapping` must be the identity swizzle for all components.
/// - If the image view is to be used in a shader, it must be in a combined image sampler
/// descriptor, a separate sampled image descriptor is not allowed.
/// - The corresponding sampler must have the same sampler YCbCr object or an identically
/// created one, and must be used as an immutable sampler within a descriptor set layout.
///
/// The default value is `None`.
#[inline]
pub fn sampler_ycbcr_conversion(
mut self,
conversion: Option<Arc<SamplerYcbcrConversion>>,
) -> Self {
self.sampler_ycbcr_conversion = conversion;
self
}
/// The image view type.
///
/// The view type must be compatible with the dimensions of the image and the selected array
@ -681,6 +755,10 @@ pub enum ImageViewCreationError {
/// requested, and the image view type was `Dim3d`.
BlockTexelViewCompatibleUncompressedIs3d,
/// The requested format has chroma subsampling, but the width and/or height of the image was
/// not a multiple of 2.
FormatChromaSubsamplingInvalidImageDimensions,
/// The requested format was not compatible with the image.
FormatNotCompatible,
@ -783,6 +861,10 @@ impl fmt::Display for ImageViewCreationError {
fmt,
"the image has the `block_texel_view_compatible` flag, and an uncompressed format was requested, and the image view type was `Dim3d`",
),
Self::FormatChromaSubsamplingInvalidImageDimensions => write!(
fmt,
"the requested format has chroma subsampling, but the width and/or height of the image was not a multiple of 2",
),
Self::FormatNotCompatible => write!(
fmt,
"the requested format was not compatible with the image",
@ -952,6 +1034,9 @@ pub unsafe trait ImageViewAbstract:
/// Returns the range of mip levels of the wrapped image that this view exposes.
fn mip_levels(&self) -> Range<u32>;
/// Returns the sampler YCbCr conversion that this image view was created with, if any.
fn sampler_ycbcr_conversion(&self) -> Option<&Arc<SamplerYcbcrConversion>>;
/// Returns the [`ImageViewType`] of this image view.
fn ty(&self) -> ImageViewType;
@ -1008,6 +1093,11 @@ where
self.mip_levels.clone()
}
#[inline]
fn sampler_ycbcr_conversion(&self) -> Option<&Arc<SamplerYcbcrConversion>> {
self.sampler_ycbcr_conversion.as_ref()
}
#[inline]
fn ty(&self) -> ImageViewType {
self.ty

View File

@ -44,12 +44,15 @@
//! - 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 crate::check_errors;
use crate::device::Device;
use crate::device::DeviceOwned;
use crate::image::view::ImageViewType;
use crate::image::ImageViewAbstract;
use crate::pipeline::graphics::depth_stencil::CompareOp;
use crate::sampler::ycbcr::SamplerYcbcrConversion;
use crate::shader::ShaderScalarType;
use crate::Error;
use crate::OomError;
@ -98,6 +101,7 @@ pub struct Sampler {
min_filter: Filter,
mipmap_mode: SamplerMipmapMode,
reduction_mode: SamplerReductionMode,
sampler_ycbcr_conversion: Option<Arc<SamplerYcbcrConversion>>,
unnormalized_coordinates: bool,
}
@ -120,6 +124,7 @@ impl Sampler {
border_color: BorderColor::FloatTransparentBlack,
unnormalized_coordinates: false,
reduction_mode: SamplerReductionMode::WeightedAverage,
sampler_ycbcr_conversion: None,
}
}
@ -337,6 +342,12 @@ impl Sampler {
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 {
@ -405,6 +416,7 @@ pub struct SamplerBuilder {
border_color: BorderColor,
unnormalized_coordinates: bool,
reduction_mode: SamplerReductionMode,
sampler_ycbcr_conversion: Option<Arc<SamplerYcbcrConversion>>,
}
impl SamplerBuilder {
@ -425,6 +437,7 @@ impl SamplerBuilder {
border_color,
unnormalized_coordinates,
reduction_mode,
sampler_ycbcr_conversion,
} = self;
if [address_mode_u, address_mode_v, address_mode_w]
@ -578,35 +591,114 @@ impl SamplerBuilder {
None
};
let fns = device.fns();
let handle = unsafe {
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_u.into(),
address_mode_v: address_mode_v.into(),
address_mode_w: address_mode_w.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()
// 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());
let format_properties = device
.physical_device()
.format_properties(sampler_ycbcr_conversion.format().unwrap());
let potential_format_features = &format_properties.linear_tiling_features
| &format_properties.optimal_tiling_features;
// VUID-VkSamplerCreateInfo-minFilter-01645
if !potential_format_features
.sampled_image_ycbcr_conversion_separate_reconstruction_filter
&& !(self.mag_filter == sampler_ycbcr_conversion.chroma_filter()
&& self.min_filter == sampler_ycbcr_conversion.chroma_filter())
{
return Err(
SamplerCreationError::SamplerYcbcrConversionChromaFilterMismatch {
chroma_filter: sampler_ycbcr_conversion.chroma_filter(),
mag_filter: self.mag_filter,
min_filter: self.min_filter,
},
);
}
// VUID-VkSamplerCreateInfo-addressModeU-01646
if [
self.address_mode_u,
self.address_mode_v,
self.address_mode_w,
]
.into_iter()
.any(|mode| !matches!(mode, SamplerAddressMode::ClampToEdge))
{
return Err(
SamplerCreationError::SamplerYcbcrConversionInvalidAddressMode {
address_mode_u: self.address_mode_u,
address_mode_v: self.address_mode_v,
address_mode_w: self.address_mode_w,
},
);
}
// VUID-VkSamplerCreateInfo-addressModeU-01646
if self.anisotropy.is_some() {
return Err(SamplerCreationError::SamplerYcbcrConversionAnisotropyEnabled);
}
// VUID-VkSamplerCreateInfo-addressModeU-01646
if self.unnormalized_coordinates {
return Err(
SamplerCreationError::SamplerYcbcrConversionUnnormalizedCoordinatesEnabled,
);
}
// VUID-VkSamplerCreateInfo-None-01647
if self.reduction_mode != SamplerReductionMode::WeightedAverage {
return Err(
SamplerCreationError::SamplerYcbcrConversionInvalidReductionMode {
reduction_mode: self.reduction_mode,
},
);
}
Some(ash::vk::SamplerYcbcrConversionInfo {
conversion: sampler_ycbcr_conversion.internal_object(),
..Default::default()
})
} else {
None
};
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 _;
}
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_u.into(),
address_mode_v: address_mode_v.into(),
address_mode_w: address_mode_w.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(),
@ -630,6 +722,7 @@ impl SamplerBuilder {
min_filter,
mipmap_mode,
reduction_mode,
sampler_ycbcr_conversion,
unnormalized_coordinates,
}))
}
@ -846,6 +939,29 @@ impl SamplerBuilder {
self.reduction_mode = mode;
self
}
/// 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`.
#[inline]
pub fn sampler_ycbcr_conversion(
mut self,
conversion: Option<Arc<SamplerYcbcrConversion>>,
) -> Self {
self.sampler_ycbcr_conversion = conversion;
self
}
}
/// Error that can happen when creating an instance.
@ -894,6 +1010,35 @@ pub enum SamplerCreationError {
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_u: SamplerAddressMode,
address_mode_v: SamplerAddressMode,
address_mode_w: SamplerAddressMode,
},
/// 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,
@ -950,6 +1095,17 @@ impl fmt::Display for SamplerCreationError {
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"

View File

@ -0,0 +1,689 @@
// Copyright (c) 2022 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.
//! Conversion from sampled YCbCr image data to RGB shader data.
//!
//! A sampler YCbCr conversion is an object that assists a sampler when converting from YCbCr
//! formats and/or YCbCr texel input data. It is used to read frames of video data within a shader,
//! possibly to apply it as texture on a rendered primitive. Sampler YCbCr conversion can only be
//! used with certain formats, and conversely, some formats require the use of a sampler YCbCr
//! conversion to be sampled at all.
//!
//! A sampler YCbCr conversion can only be used with a combined image sampler descriptor in a
//! descriptor set. The conversion must be attached on both the image view and sampler in the
//! descriptor, and the sampler must be included in the descriptor set layout as an immutable
//! sampler.
//!
//! # Examples
//!
//! ```
//! # let device: std::sync::Arc<vulkano::device::Device> = return;
//! # let image_data: Vec<u8> = return;
//! # let queue: std::sync::Arc<vulkano::device::Queue> = return;
//! use vulkano::descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet};
//! use vulkano::descriptor_set::layout::{DescriptorDesc, DescriptorSetLayout, DescriptorSetDesc, DescriptorType};
//! use vulkano::format::Format;
//! use vulkano::image::{ImmutableImage, ImageCreateFlags, ImageDimensions, ImageUsage, MipmapsCount};
//! use vulkano::image::view::ImageView;
//! use vulkano::sampler::Sampler;
//! use vulkano::sampler::ycbcr::{SamplerYcbcrConversion, SamplerYcbcrModelConversion};
//! use vulkano::shader::ShaderStage;
//!
//! let conversion = SamplerYcbcrConversion::start()
//! .format(Some(Format::G8_B8_R8_3PLANE_420_UNORM))
//! .ycbcr_model(SamplerYcbcrModelConversion::YcbcrIdentity)
//! .build(device.clone()).unwrap();
//!
//! let sampler = Sampler::start(device.clone())
//! .sampler_ycbcr_conversion(Some(conversion.clone()))
//! .build().unwrap();
//!
//! let descriptor_set_layout = DescriptorSetLayout::new(
//! device.clone(),
//! DescriptorSetDesc::new([Some(DescriptorDesc {
//! ty: DescriptorType::CombinedImageSampler,
//! descriptor_count: 1,
//! variable_count: false,
//! stages: ShaderStage::Fragment.into(),
//! immutable_samplers: vec![sampler],
//! })]),
//! ).unwrap();
//!
//! let (image, future) = ImmutableImage::from_iter(
//! image_data,
//! ImageDimensions::Dim2d { width: 1920, height: 1080, array_layers: 1 },
//! MipmapsCount::One,
//! Format::G8_B8_R8_3PLANE_420_UNORM,
//! queue.clone(),
//! ).unwrap();
//!
//! let image_view = ImageView::start(image)
//! .sampler_ycbcr_conversion(Some(conversion.clone()))
//! .build().unwrap();
//!
//! let descriptor_set = PersistentDescriptorSet::new(
//! descriptor_set_layout.clone(),
//! [WriteDescriptorSet::image_view(0, image_view)],
//! ).unwrap();
//! ```
use crate::{
check_errors,
device::{Device, DeviceOwned},
format::{ChromaSampling, Format, NumericType},
sampler::{ComponentMapping, ComponentSwizzle, Filter},
Error, OomError, Version, VulkanObject,
};
use std::{error, fmt, mem::MaybeUninit, ptr, sync::Arc};
/// Describes how sampled image data should converted from a YCbCr representation to an RGB one.
#[derive(Debug)]
pub struct SamplerYcbcrConversion {
handle: ash::vk::SamplerYcbcrConversion,
device: Arc<Device>,
create_info: SamplerYcbcrConversionBuilder,
}
impl SamplerYcbcrConversion {
/// Starts constructing a new `SamplerYcbcrConversion`.
#[inline]
pub fn start() -> SamplerYcbcrConversionBuilder {
SamplerYcbcrConversionBuilder {
format: None,
ycbcr_model: SamplerYcbcrModelConversion::RgbIdentity,
ycbcr_range: SamplerYcbcrRange::ItuFull,
component_mapping: ComponentMapping::identity(),
chroma_offset: [ChromaLocation::CositedEven; 2],
chroma_filter: Filter::Nearest,
force_explicit_reconstruction: false,
}
}
/// Returns the chroma filter used by this conversion.
#[inline]
pub fn chroma_filter(&self) -> Filter {
self.create_info.chroma_filter
}
/// Returns the format that this conversion was created for.
#[inline]
pub fn format(&self) -> Option<Format> {
self.create_info.format
}
/// Returns whether `self` is equal or identically defined to `other`.
#[inline]
pub fn is_identical(&self, other: &SamplerYcbcrConversion) -> bool {
self.handle == other.handle || self.create_info == other.create_info
}
}
unsafe impl DeviceOwned for SamplerYcbcrConversion {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
unsafe impl VulkanObject for SamplerYcbcrConversion {
type Object = ash::vk::SamplerYcbcrConversion;
#[inline]
fn internal_object(&self) -> ash::vk::SamplerYcbcrConversion {
self.handle
}
}
impl PartialEq for SamplerYcbcrConversion {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.handle == other.handle
}
}
impl Eq for SamplerYcbcrConversion {}
impl Drop for SamplerYcbcrConversion {
#[inline]
fn drop(&mut self) {
unsafe {
let fns = self.device.fns();
let destroy_sampler_ycbcr_conversion = if self.device.api_version() >= Version::V1_1 {
fns.v1_1.destroy_sampler_ycbcr_conversion
} else {
fns.khr_sampler_ycbcr_conversion
.destroy_sampler_ycbcr_conversion_khr
};
destroy_sampler_ycbcr_conversion(
self.device.internal_object(),
self.handle,
ptr::null(),
);
}
}
}
/// Used to construct a new `SamplerYcbcrConversion`.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SamplerYcbcrConversionBuilder {
format: Option<Format>,
ycbcr_model: SamplerYcbcrModelConversion,
ycbcr_range: SamplerYcbcrRange,
component_mapping: ComponentMapping,
chroma_offset: [ChromaLocation; 2],
chroma_filter: Filter,
force_explicit_reconstruction: bool,
}
impl SamplerYcbcrConversionBuilder {
/// Builds the `SamplerYcbcrConversion`.
///
/// The [`sampler_ycbcr_conversion`](crate::device::Features::sampler_ycbcr_conversion)
/// feature must be enabled on the device.
pub fn build(
self,
device: Arc<Device>,
) -> Result<Arc<SamplerYcbcrConversion>, SamplerYcbcrConversionCreationError> {
let Self {
format,
ycbcr_model,
ycbcr_range,
component_mapping,
chroma_offset,
chroma_filter,
force_explicit_reconstruction,
} = self;
if !device.enabled_features().sampler_ycbcr_conversion {
return Err(SamplerYcbcrConversionCreationError::FeatureNotEnabled {
feature: "sampler_ycbcr_conversion",
reason: "tried to create a SamplerYcbcrConversion",
});
}
let format = match format {
Some(f) => f,
None => {
return Err(SamplerYcbcrConversionCreationError::FormatMissing);
}
};
// VUID-VkSamplerYcbcrConversionCreateInfo-format-04061
if !format
.type_color()
.map_or(false, |ty| ty == NumericType::UNORM)
{
return Err(SamplerYcbcrConversionCreationError::FormatNotUnorm);
}
let format_properties = device.physical_device().format_properties(format);
let potential_format_features =
&format_properties.linear_tiling_features | &format_properties.optimal_tiling_features;
// VUID-VkSamplerYcbcrConversionCreateInfo-format-01650
if !(potential_format_features.midpoint_chroma_samples
|| potential_format_features.cosited_chroma_samples)
{
return Err(SamplerYcbcrConversionCreationError::FormatNotSupported);
}
if let Some(chroma_sampling @ (ChromaSampling::Mode422 | ChromaSampling::Mode420)) =
format.ycbcr_chroma_sampling()
{
let chroma_offsets_to_check = match chroma_sampling {
ChromaSampling::Mode420 => &chroma_offset[0..2],
ChromaSampling::Mode422 => &chroma_offset[0..1],
_ => unreachable!(),
};
for offset in chroma_offsets_to_check {
match offset {
ChromaLocation::CositedEven => {
// VUID-VkSamplerYcbcrConversionCreateInfo-xChromaOffset-01651
if !potential_format_features.cosited_chroma_samples {
return Err(
SamplerYcbcrConversionCreationError::FormatChromaOffsetNotSupported,
);
}
}
ChromaLocation::Midpoint => {
// VUID-VkSamplerYcbcrConversionCreateInfo-xChromaOffset-01652
if !potential_format_features.midpoint_chroma_samples {
return Err(
SamplerYcbcrConversionCreationError::FormatChromaOffsetNotSupported,
);
}
}
}
}
// VUID-VkSamplerYcbcrConversionCreateInfo-components-02581
let g_ok = component_mapping.g_is_identity();
// VUID-VkSamplerYcbcrConversionCreateInfo-components-02582
let a_ok = component_mapping.a_is_identity()
|| matches!(
component_mapping.a,
ComponentSwizzle::One | ComponentSwizzle::Zero
);
// VUID-VkSamplerYcbcrConversionCreateInfo-components-02583
// VUID-VkSamplerYcbcrConversionCreateInfo-components-02584
// VUID-VkSamplerYcbcrConversionCreateInfo-components-02585
let rb_ok1 = component_mapping.r_is_identity() && component_mapping.b_is_identity();
let rb_ok2 = matches!(component_mapping.r, ComponentSwizzle::Blue)
&& matches!(component_mapping.b, ComponentSwizzle::Red);
if !(g_ok && a_ok && (rb_ok1 || rb_ok2)) {
return Err(SamplerYcbcrConversionCreationError::FormatInvalidComponentMapping);
}
}
let components_bits = {
let bits = format.components();
component_mapping
.component_map()
.map(move |i| i.map(|i| bits[i]))
};
// VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrModel-01655
if ycbcr_model != SamplerYcbcrModelConversion::RgbIdentity
&& !components_bits[0..3]
.iter()
.all(|b| b.map_or(false, |b| b != 0))
{
return Err(SamplerYcbcrConversionCreationError::YcbcrModelInvalidComponentMapping);
}
// VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrRange-02748
if ycbcr_range == SamplerYcbcrRange::ItuNarrow {
// TODO: Spec doesn't say how many bits `Zero` and `One` are considered to have, so
// just skip them for now.
for &bits in components_bits[0..3].iter().flatten() {
if bits < 8 {
return Err(SamplerYcbcrConversionCreationError::YcbcrRangeFormatNotEnoughBits);
}
}
}
// VUID-VkSamplerYcbcrConversionCreateInfo-forceExplicitReconstruction-01656
if force_explicit_reconstruction
&& !potential_format_features
.sampled_image_ycbcr_conversion_chroma_reconstruction_explicit_forceable
{
return Err(
SamplerYcbcrConversionCreationError::FormatForceExplicitReconstructionNotSupported,
);
}
match chroma_filter {
Filter::Nearest => (),
Filter::Linear => {
// VUID-VkSamplerYcbcrConversionCreateInfo-chromaFilter-01657
if !potential_format_features.sampled_image_ycbcr_conversion_linear_filter {
return Err(
SamplerYcbcrConversionCreationError::FormatLinearFilterNotSupported,
);
}
}
Filter::Cubic => {
return Err(SamplerYcbcrConversionCreationError::CubicFilterNotSupported);
}
}
let create_info = ash::vk::SamplerYcbcrConversionCreateInfo {
format: format.into(),
ycbcr_model: ycbcr_model.into(),
ycbcr_range: ycbcr_range.into(),
components: component_mapping.into(),
x_chroma_offset: chroma_offset[0].into(),
y_chroma_offset: chroma_offset[1].into(),
chroma_filter: chroma_filter.into(),
force_explicit_reconstruction: force_explicit_reconstruction as ash::vk::Bool32,
..Default::default()
};
let handle = unsafe {
let fns = device.fns();
let create_sampler_ycbcr_conversion = if device.api_version() >= Version::V1_1 {
fns.v1_1.create_sampler_ycbcr_conversion
} else {
fns.khr_sampler_ycbcr_conversion
.create_sampler_ycbcr_conversion_khr
};
let mut output = MaybeUninit::uninit();
check_errors(create_sampler_ycbcr_conversion(
device.internal_object(),
&create_info,
ptr::null(),
output.as_mut_ptr(),
))?;
output.assume_init()
};
Ok(Arc::new(SamplerYcbcrConversion {
handle,
device,
create_info: self,
}))
}
/// The image view format that this conversion will read data from. The conversion cannot be
/// used with image views of any other format.
///
/// The format must support YCbCr conversions, meaning that its `FormatFeatures` must support
/// at least one of `cosited_chroma_samples` or `midpoint_chroma_samples`.
///
/// If this is set to a format that has chroma subsampling (contains `422` or `420` in the name)
/// then `component_mapping` is restricted as follows:
/// - `g` must be identity swizzled.
/// - `a` must be identity swizzled or `Zero` or `One`.
/// - `r` and `b` must be identity swizzled or mapped to each other.
///
/// Compatibility notice: currently, this value must be `Some`, but future additions may allow
/// `None` as a valid value as well.
///
/// The default value is `None`.
#[inline]
pub fn format(mut self, format: Option<Format>) -> Self {
self.format = format;
self
}
/// The conversion between the input color model and the output RGB color model.
///
/// If this is not set to `RgbIdentity`, then the `r`, `g` and `b` components of
/// `component_mapping` must not be `Zero` or `One`, and the component being read must exist in
/// `format` (must be represented as a nonzero number of bits).
///
/// The default value is [`RgbIdentity`](SamplerYcbcrModelConversion::RgbIdentity).
#[inline]
pub fn ycbcr_model(mut self, model: SamplerYcbcrModelConversion) -> Self {
self.ycbcr_model = model;
self
}
/// If `ycbcr_model` is not `RgbIdentity`, specifies the range expansion of the input values
/// that should be used.
///
/// If this is set to `ItuNarrow`, then the `r`, `g` and `b` components of `component_mapping`
/// must each map to a component of `format` that is represented with at least 8 bits.
///
/// The default value is [`ItuFull`](SamplerYcbcrRange::ItuFull).
#[inline]
pub fn ycbcr_range(mut self, range: SamplerYcbcrRange) -> Self {
self.ycbcr_range = range;
self
}
/// The mapping to apply to the components of the input format, before color model conversion
/// and range expansion.
///
/// The default value is [`ComponentMapping::identity()`].
#[inline]
pub fn component_mapping(mut self, component_mapping: ComponentMapping) -> Self {
self.component_mapping = component_mapping;
self
}
/// For formats with chroma subsampling and a `Linear` filter, specifies the sampled location
/// for the subsampled components, in the x and y direction.
///
/// The value is ignored if the filter is `Nearest` or the corresponding axis is not chroma
/// subsampled. If the value is not ignored, the format must support the chosen mode.
///
/// The default value is [`CositedEven`](ChromaLocation::CositedEven) for both axes.
#[inline]
pub fn chroma_offset(mut self, offsets: [ChromaLocation; 2]) -> Self {
self.chroma_offset = offsets;
self
}
/// For formats with chroma subsampling, specifies the filter used for reconstructing the chroma
/// components to full resolution.
///
/// The `Cubic` filter is not supported. If `Linear` is used, the format must support it.
///
/// The default value is [`Nearest`](Filter::Nearest).
#[inline]
pub fn chroma_filter(mut self, filter: Filter) -> Self {
self.chroma_filter = filter;
self
}
/// Forces explicit reconstruction if the implementation does not use it by default. The format
/// must support it. See
/// [the spec](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/html/chap16.html#textures-chroma-reconstruction)
/// for more information.
///
/// The default value is `false`.
#[inline]
pub fn force_explicit_reconstruction(mut self, enable: bool) -> Self {
self.force_explicit_reconstruction = enable;
self
}
}
/// Error that can happen when creating a `SamplerYcbcrConversion`.
#[derive(Clone, Debug, PartialEq)]
pub enum SamplerYcbcrConversionCreationError {
/// Not enough memory.
OomError(OomError),
FeatureNotEnabled {
feature: &'static str,
reason: &'static str,
},
/// The `Cubic` filter was specified.
CubicFilterNotSupported,
/// No format was specified when one was required.
FormatMissing,
/// The format has a color type other than `UNORM`.
FormatNotUnorm,
/// The format does not support sampler YCbCr conversion.
FormatNotSupported,
/// The format does not support the chosen chroma offsets.
FormatChromaOffsetNotSupported,
/// The component mapping was not valid for use with the chosen format.
FormatInvalidComponentMapping,
/// The format does not support `force_explicit_reconstruction`.
FormatForceExplicitReconstructionNotSupported,
/// The format does not support the `Linear` filter.
FormatLinearFilterNotSupported,
/// The component mapping was not valid for use with the chosen YCbCr model.
YcbcrModelInvalidComponentMapping,
/// For the chosen `ycbcr_range`, the R, G or B components being read from the `format` do not
/// have the minimum number of required bits.
YcbcrRangeFormatNotEnoughBits,
}
impl error::Error for SamplerYcbcrConversionCreationError {
#[inline]
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
SamplerYcbcrConversionCreationError::OomError(ref err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for SamplerYcbcrConversionCreationError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Self::OomError(_) => write!(fmt, "not enough memory available"),
Self::FeatureNotEnabled { feature, reason } => {
write!(fmt, "the feature {} must be enabled: {}", feature, reason)
}
Self::CubicFilterNotSupported => {
write!(fmt, "the `Cubic` filter was specified")
}
Self::FormatMissing => {
write!(fmt, "no format was specified when one was required")
}
Self::FormatNotUnorm => {
write!(fmt, "the format has a color type other than `UNORM`")
}
Self::FormatNotSupported => {
write!(fmt, "the format does not support sampler YCbCr conversion")
}
Self::FormatChromaOffsetNotSupported => {
write!(fmt, "the format does not support the chosen chroma offsets")
}
Self::FormatInvalidComponentMapping => {
write!(
fmt,
"the component mapping was not valid for use with the chosen format"
)
}
Self::FormatForceExplicitReconstructionNotSupported => {
write!(
fmt,
"the format does not support `force_explicit_reconstruction`"
)
}
Self::FormatLinearFilterNotSupported => {
write!(fmt, "the format does not support the `Linear` filter")
}
Self::YcbcrModelInvalidComponentMapping => {
write!(
fmt,
"the component mapping was not valid for use with the chosen YCbCr model"
)
}
Self::YcbcrRangeFormatNotEnoughBits => {
write!(fmt, "for the chosen `ycbcr_range`, the R, G or B components being read from the `format` do not have the minimum number of required bits")
}
}
}
}
impl From<OomError> for SamplerYcbcrConversionCreationError {
#[inline]
fn from(err: OomError) -> SamplerYcbcrConversionCreationError {
SamplerYcbcrConversionCreationError::OomError(err)
}
}
impl From<Error> for SamplerYcbcrConversionCreationError {
#[inline]
fn from(err: Error) -> SamplerYcbcrConversionCreationError {
match err {
err @ Error::OutOfHostMemory => {
SamplerYcbcrConversionCreationError::OomError(OomError::from(err))
}
err @ Error::OutOfDeviceMemory => {
SamplerYcbcrConversionCreationError::OomError(OomError::from(err))
}
_ => panic!("unexpected error: {:?}", err),
}
}
}
/// The conversion between the color model of the source image and the color model of the shader.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum SamplerYcbcrModelConversion {
/// The input values are already in the shader's model, and are passed through unmodified.
RgbIdentity = ash::vk::SamplerYcbcrModelConversion::RGB_IDENTITY.as_raw(),
/// The input values are only range expanded, no other modifications are done.
YcbcrIdentity = ash::vk::SamplerYcbcrModelConversion::YCBCR_IDENTITY.as_raw(),
/// The input values are converted according to the
/// [ITU-R BT.709](https://en.wikipedia.org/wiki/Rec._709) standard.
Ycbcr709 = ash::vk::SamplerYcbcrModelConversion::YCBCR_709.as_raw(),
/// The input values are converted according to the
/// [ITU-R BT.601](https://en.wikipedia.org/wiki/Rec._601) standard.
Ycbcr601 = ash::vk::SamplerYcbcrModelConversion::YCBCR_601.as_raw(),
/// The input values are converted according to the
/// [ITU-R BT.2020](https://en.wikipedia.org/wiki/Rec._2020) standard.
Ycbcr2020 = ash::vk::SamplerYcbcrModelConversion::YCBCR_2020.as_raw(),
}
impl From<SamplerYcbcrModelConversion> for ash::vk::SamplerYcbcrModelConversion {
#[inline]
fn from(val: SamplerYcbcrModelConversion) -> Self {
Self::from_raw(val as i32)
}
}
/// How the numeric range of the input data is converted.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum SamplerYcbcrRange {
/// The input values cover the full numeric range, and are interpreted according to the ITU
/// "full range" rules.
ItuFull = ash::vk::SamplerYcbcrRange::ITU_FULL.as_raw(),
/// The input values cover only a subset of the numeric range, with the remainder reserved as
/// headroom/footroom. The values are interpreted according to the ITU "narrow range" rules.
ItuNarrow = ash::vk::SamplerYcbcrRange::ITU_NARROW.as_raw(),
}
impl From<SamplerYcbcrRange> for ash::vk::SamplerYcbcrRange {
#[inline]
fn from(val: SamplerYcbcrRange) -> Self {
Self::from_raw(val as i32)
}
}
/// For formats with chroma subsampling, the location where the chroma components are sampled,
/// relative to the luma component.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum ChromaLocation {
/// The chroma components are sampled at the even luma coordinate.
CositedEven = ash::vk::ChromaLocation::COSITED_EVEN.as_raw(),
/// The chroma components are sampled at the midpoint between the even luma coordinate and
/// the next higher odd luma coordinate.
Midpoint = ash::vk::ChromaLocation::MIDPOINT.as_raw(),
}
impl From<ChromaLocation> for ash::vk::ChromaLocation {
#[inline]
fn from(val: ChromaLocation) -> Self {
Self::from_raw(val as i32)
}
}
#[cfg(test)]
mod tests {
use super::{SamplerYcbcrConversion, SamplerYcbcrConversionCreationError};
#[test]
fn feature_not_enabled() {
let (device, queue) = gfx_dev_and_queue!();
let r = SamplerYcbcrConversion::start().build(device);
match r {
Err(SamplerYcbcrConversionCreationError::FeatureNotEnabled {
feature: "sampler_ycbcr_conversion",
..
}) => (),
_ => panic!(),
}
}
}

View File

@ -561,6 +561,11 @@ pub struct DescriptorRequirements {
/// `Dref` or `Proj` SPIR-V instructions or with an LOD bias or offset.
pub sampler_no_unnormalized_coordinates: FnvHashSet<u32>,
/// For sampler bindings, the descriptor indices that perform sampling operations that are not
/// permitted with a sampler YCbCr conversion. This includes sampling with `Gather` SPIR-V
/// instructions or with an offset.
pub sampler_no_ycbcr_conversion: FnvHashSet<u32>,
/// For sampler bindings, the sampled image descriptors that are used in combination with each
/// sampler descriptor index.
pub sampler_with_images: FnvHashMap<u32, FnvHashSet<DescriptorIdentifier>>,
@ -644,6 +649,8 @@ impl DescriptorRequirements {
sampler_compare: &self.sampler_compare | &other.sampler_compare,
sampler_no_unnormalized_coordinates: &self.sampler_no_unnormalized_coordinates
| &other.sampler_no_unnormalized_coordinates,
sampler_no_ycbcr_conversion: &self.sampler_no_ycbcr_conversion
| &other.sampler_no_ycbcr_conversion,
sampler_with_images,
stages: self.stages | other.stages,
storage_image_atomic: &self.storage_image_atomic | &other.storage_image_atomic,

View File

@ -371,6 +371,55 @@ fn inspect_entry_point(
&Instruction::FunctionEnd => return,
&Instruction::ImageGather {
sampled_image,
ref image_operands,
..
}
| &Instruction::ImageSparseGather {
sampled_image,
ref image_operands,
..
} => {
if let Some((variable, Some(index))) = instruction_chain(
result,
global,
spirv,
[inst_sampled_image, inst_load],
sampled_image,
) {
variable.reqs.sampler_no_ycbcr_conversion.insert(index);
if image_operands.as_ref().map_or(false, |image_operands| {
image_operands.bias.is_some()
|| image_operands.const_offset.is_some()
|| image_operands.offset.is_some()
}) {
variable
.reqs
.sampler_no_unnormalized_coordinates
.insert(index);
}
}
}
&Instruction::ImageDrefGather { sampled_image, .. }
| &Instruction::ImageSparseDrefGather { sampled_image, .. } => {
if let Some((variable, Some(index))) = instruction_chain(
result,
global,
spirv,
[inst_sampled_image, inst_load],
sampled_image,
) {
variable
.reqs
.sampler_no_unnormalized_coordinates
.insert(index);
variable.reqs.sampler_no_ycbcr_conversion.insert(index);
}
}
&Instruction::ImageSampleImplicitLod {
sampled_image,
ref image_operands,
@ -402,6 +451,13 @@ fn inspect_entry_point(
.reqs
.sampler_no_unnormalized_coordinates
.insert(index);
if image_operands.as_ref().map_or(false, |image_operands| {
image_operands.const_offset.is_some()
|| image_operands.offset.is_some()
}) {
variable.reqs.sampler_no_ycbcr_conversion.insert(index);
}
}
}
@ -426,6 +482,12 @@ fn inspect_entry_point(
.reqs
.sampler_no_unnormalized_coordinates
.insert(index);
if image_operands.const_offset.is_some()
|| image_operands.offset.is_some()
{
variable.reqs.sampler_no_ycbcr_conversion.insert(index);
}
}
}
@ -461,6 +523,13 @@ fn inspect_entry_point(
.sampler_no_unnormalized_coordinates
.insert(index);
variable.reqs.sampler_compare.insert(index);
if image_operands.as_ref().map_or(false, |image_operands| {
image_operands.const_offset.is_some()
|| image_operands.offset.is_some()
}) {
variable.reqs.sampler_no_ycbcr_conversion.insert(index);
}
}
}
@ -496,6 +565,12 @@ fn inspect_entry_point(
.sampler_no_unnormalized_coordinates
.insert(index);
variable.reqs.sampler_compare.insert(index);
if image_operands.const_offset.is_some()
|| image_operands.offset.is_some()
{
variable.reqs.sampler_no_ycbcr_conversion.insert(index);
}
}
}
@ -518,7 +593,6 @@ fn inspect_entry_point(
) {
if image_operands.bias.is_some()
|| image_operands.const_offset.is_some()
|| image_operands.const_offsets.is_some()
|| image_operands.offset.is_some()
{
variable
@ -526,6 +600,12 @@ fn inspect_entry_point(
.sampler_no_unnormalized_coordinates
.insert(index);
}
if image_operands.const_offset.is_some()
|| image_operands.offset.is_some()
{
variable.reqs.sampler_no_ycbcr_conversion.insert(index);
}
}
}