mirror of
https://github.com/vulkano-rs/vulkano.git
synced 2025-02-16 09:02:25 +00:00
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:
parent
5f601b125e
commit
e3146aac0e
@ -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(),
|
||||
|
@ -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();
|
||||
|
@ -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 Y′CBCR conversion.
|
||||
// - The ConstOffset and Offset operands must not be used with a sampler that enables
|
||||
// sampler Y′CBCR 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")
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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 { .. } => {
|
||||
|
@ -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",
|
||||
|
@ -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]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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"
|
689
vulkano/src/sampler/ycbcr.rs
Normal file
689
vulkano/src/sampler/ycbcr.rs
Normal 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!(),
|
||||
}
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user