aboutsummaryrefslogtreecommitdiff
path: root/src/sampler/ycbcr.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/sampler/ycbcr.rs')
-rw-r--r--src/sampler/ycbcr.rs809
1 files changed, 809 insertions, 0 deletions
diff --git a/src/sampler/ycbcr.rs b/src/sampler/ycbcr.rs
new file mode 100644
index 0000000..9d9aaf7
--- /dev/null
+++ b/src/sampler/ycbcr.rs
@@ -0,0 +1,809 @@
+// 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;
+//! # let memory_allocator: vulkano::memory::allocator::StandardMemoryAllocator = return;
+//! # let descriptor_set_allocator: vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator = return;
+//! # let mut command_buffer_builder: vulkano::command_buffer::AutoCommandBufferBuilder<vulkano::command_buffer::PrimaryAutoCommandBuffer> = return;
+//! use vulkano::descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet};
+//! use vulkano::descriptor_set::layout::{DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType};
+//! use vulkano::format::Format;
+//! use vulkano::image::{ImmutableImage, ImageCreateFlags, ImageDimensions, ImageUsage, MipmapsCount};
+//! use vulkano::image::view::{ImageView, ImageViewCreateInfo};
+//! use vulkano::sampler::{Sampler, SamplerCreateInfo};
+//! use vulkano::sampler::ycbcr::{SamplerYcbcrConversion, SamplerYcbcrConversionCreateInfo, SamplerYcbcrModelConversion};
+//! use vulkano::shader::ShaderStage;
+//!
+//! let conversion = SamplerYcbcrConversion::new(device.clone(), SamplerYcbcrConversionCreateInfo {
+//! format: Some(Format::G8_B8_R8_3PLANE_420_UNORM),
+//! ycbcr_model: SamplerYcbcrModelConversion::YcbcrIdentity,
+//! ..Default::default()
+//! })
+//! .unwrap();
+//!
+//! let sampler = Sampler::new(device.clone(), SamplerCreateInfo {
+//! sampler_ycbcr_conversion: Some(conversion.clone()),
+//! ..Default::default()
+//! })
+//! .unwrap();
+//!
+//! let descriptor_set_layout = DescriptorSetLayout::new(
+//! device.clone(),
+//! DescriptorSetLayoutCreateInfo {
+//! bindings: [(
+//! 0,
+//! DescriptorSetLayoutBinding {
+//! stages: ShaderStage::Fragment.into(),
+//! immutable_samplers: vec![sampler],
+//! ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::CombinedImageSampler)
+//! },
+//! )]
+//! .into(),
+//! ..Default::default()
+//! },
+//! ).unwrap();
+//!
+//! let image = ImmutableImage::from_iter(
+//! &memory_allocator,
+//! image_data,
+//! ImageDimensions::Dim2d { width: 1920, height: 1080, array_layers: 1 },
+//! MipmapsCount::One,
+//! Format::G8_B8_R8_3PLANE_420_UNORM,
+//! &mut command_buffer_builder,
+//! ).unwrap();
+//!
+//! let create_info = ImageViewCreateInfo {
+//! sampler_ycbcr_conversion: Some(conversion.clone()),
+//! ..ImageViewCreateInfo::from_image(&image)
+//! };
+//! let image_view = ImageView::new(image, create_info).unwrap();
+//!
+//! let descriptor_set = PersistentDescriptorSet::new(
+//! &descriptor_set_allocator,
+//! descriptor_set_layout.clone(),
+//! [WriteDescriptorSet::image_view(0, image_view)],
+//! ).unwrap();
+//! ```
+
+use crate::{
+ device::{Device, DeviceOwned},
+ format::{ChromaSampling, Format, FormatFeatures, NumericType},
+ macros::{impl_id_counter, vulkan_enum},
+ sampler::{ComponentMapping, ComponentSwizzle, Filter},
+ OomError, RequirementNotMet, RequiresOneOf, Version, VulkanError, VulkanObject,
+};
+use std::{
+ error::Error,
+ fmt::{Display, Error as FmtError, Formatter},
+ mem::MaybeUninit,
+ num::NonZeroU64,
+ 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>,
+ id: NonZeroU64,
+
+ format: Option<Format>,
+ ycbcr_model: SamplerYcbcrModelConversion,
+ ycbcr_range: SamplerYcbcrRange,
+ component_mapping: ComponentMapping,
+ chroma_offset: [ChromaLocation; 2],
+ chroma_filter: Filter,
+ force_explicit_reconstruction: bool,
+}
+
+impl SamplerYcbcrConversion {
+ /// Creates a new `SamplerYcbcrConversion`.
+ ///
+ /// The [`sampler_ycbcr_conversion`](crate::device::Features::sampler_ycbcr_conversion)
+ /// feature must be enabled on the device.
+ pub fn new(
+ device: Arc<Device>,
+ create_info: SamplerYcbcrConversionCreateInfo,
+ ) -> Result<Arc<SamplerYcbcrConversion>, SamplerYcbcrConversionCreationError> {
+ let SamplerYcbcrConversionCreateInfo {
+ format,
+ ycbcr_model,
+ ycbcr_range,
+ component_mapping,
+ chroma_offset,
+ chroma_filter,
+ force_explicit_reconstruction,
+ _ne: _,
+ } = create_info;
+
+ if !device.enabled_features().sampler_ycbcr_conversion {
+ return Err(SamplerYcbcrConversionCreationError::RequirementNotMet {
+ required_for: "`SamplerYcbcrConversion::new`",
+ requires_one_of: RequiresOneOf {
+ features: &["sampler_ycbcr_conversion"],
+ ..Default::default()
+ },
+ });
+ }
+
+ let format = match format {
+ Some(f) => f,
+ None => {
+ return Err(SamplerYcbcrConversionCreationError::FormatMissing);
+ }
+ };
+
+ // VUID-VkSamplerYcbcrConversionCreateInfo-format-parameter
+ format.validate_device(&device)?;
+
+ // VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrModel-parameter
+ ycbcr_model.validate_device(&device)?;
+
+ // VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrRange-parameter
+ ycbcr_range.validate_device(&device)?;
+
+ // VUID-VkComponentMapping-r-parameter
+ component_mapping.r.validate_device(&device)?;
+
+ // VUID-VkComponentMapping-g-parameter
+ component_mapping.g.validate_device(&device)?;
+
+ // VUID-VkComponentMapping-b-parameter
+ component_mapping.b.validate_device(&device)?;
+
+ // VUID-VkComponentMapping-a-parameter
+ component_mapping.a.validate_device(&device)?;
+
+ for offset in chroma_offset {
+ // VUID-VkSamplerYcbcrConversionCreateInfo-xChromaOffset-parameter
+ // VUID-VkSamplerYcbcrConversionCreateInfo-yChromaOffset-parameter
+ offset.validate_device(&device)?;
+ }
+
+ // VUID-VkSamplerYcbcrConversionCreateInfo-chromaFilter-parameter
+ chroma_filter.validate_device(&device)?;
+
+ // VUID-VkSamplerYcbcrConversionCreateInfo-format-04061
+ if !format
+ .type_color()
+ .map_or(false, |ty| ty == NumericType::UNORM)
+ {
+ return Err(SamplerYcbcrConversionCreationError::FormatNotUnorm);
+ }
+
+ // Use unchecked, because all validation has been done above.
+ let potential_format_features = unsafe {
+ device
+ .physical_device()
+ .format_properties_unchecked(format)
+ .potential_format_features()
+ };
+
+ // VUID-VkSamplerYcbcrConversionCreateInfo-format-01650
+ if !potential_format_features.intersects(
+ FormatFeatures::MIDPOINT_CHROMA_SAMPLES | FormatFeatures::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
+ .intersects(FormatFeatures::COSITED_CHROMA_SAMPLES)
+ {
+ return Err(
+ SamplerYcbcrConversionCreationError::FormatChromaOffsetNotSupported,
+ );
+ }
+ }
+ ChromaLocation::Midpoint => {
+ // VUID-VkSamplerYcbcrConversionCreateInfo-xChromaOffset-01652
+ if !potential_format_features
+ .intersects(FormatFeatures::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.intersects(FormatFeatures::
+ 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
+ .intersects(FormatFeatures::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();
+ create_sampler_ycbcr_conversion(
+ device.handle(),
+ &create_info,
+ ptr::null(),
+ output.as_mut_ptr(),
+ )
+ .result()
+ .map_err(VulkanError::from)?;
+ output.assume_init()
+ };
+
+ Ok(Arc::new(SamplerYcbcrConversion {
+ handle,
+ device,
+ id: Self::next_id(),
+ format: Some(format),
+ ycbcr_model,
+ ycbcr_range,
+ component_mapping,
+ chroma_offset,
+ chroma_filter,
+ force_explicit_reconstruction,
+ }))
+ }
+
+ /// Creates a new `SamplerYcbcrConversion` from a raw object handle.
+ ///
+ /// # Safety
+ ///
+ /// - `handle` must be a valid Vulkan object handle created from `device`.
+ /// - `create_info` must match the info used to create the object.
+ /// - `create_info.format` must be `Some`.
+ #[inline]
+ pub unsafe fn from_handle(
+ device: Arc<Device>,
+ handle: ash::vk::SamplerYcbcrConversion,
+ create_info: SamplerYcbcrConversionCreateInfo,
+ ) -> Arc<SamplerYcbcrConversion> {
+ let SamplerYcbcrConversionCreateInfo {
+ format,
+ ycbcr_model,
+ ycbcr_range,
+ component_mapping,
+ chroma_offset,
+ chroma_filter,
+ force_explicit_reconstruction,
+ _ne: _,
+ } = create_info;
+
+ Arc::new(SamplerYcbcrConversion {
+ handle,
+ device,
+ id: Self::next_id(),
+ format,
+ ycbcr_model,
+ ycbcr_range,
+ component_mapping,
+ chroma_offset,
+ chroma_filter,
+ force_explicit_reconstruction,
+ })
+ }
+
+ /// Returns the chroma filter used by the conversion.
+ #[inline]
+ pub fn chroma_filter(&self) -> Filter {
+ self.chroma_filter
+ }
+
+ /// Returns the chroma offsets used by the conversion.
+ #[inline]
+ pub fn chroma_offset(&self) -> [ChromaLocation; 2] {
+ self.chroma_offset
+ }
+
+ /// Returns the component mapping of the conversion.
+ #[inline]
+ pub fn component_mapping(&self) -> ComponentMapping {
+ self.component_mapping
+ }
+
+ /// Returns whether the conversion has forced explicit reconstruction to be enabled.
+ #[inline]
+ pub fn force_explicit_reconstruction(&self) -> bool {
+ self.force_explicit_reconstruction
+ }
+
+ /// Returns the format that the conversion was created for.
+ #[inline]
+ pub fn format(&self) -> Option<Format> {
+ self.format
+ }
+
+ /// Returns the YCbCr model of the conversion.
+ #[inline]
+ pub fn ycbcr_model(&self) -> SamplerYcbcrModelConversion {
+ self.ycbcr_model
+ }
+
+ /// Returns the YCbCr range of the conversion.
+ #[inline]
+ pub fn ycbcr_range(&self) -> SamplerYcbcrRange {
+ self.ycbcr_range
+ }
+
+ /// Returns whether `self` is equal or identically defined to `other`.
+ #[inline]
+ pub fn is_identical(&self, other: &SamplerYcbcrConversion) -> bool {
+ self.handle == other.handle || {
+ let &Self {
+ handle: _,
+ device: _,
+ id: _,
+ format,
+ ycbcr_model,
+ ycbcr_range,
+ component_mapping,
+ chroma_offset,
+ chroma_filter,
+ force_explicit_reconstruction,
+ } = self;
+
+ format == other.format
+ && ycbcr_model == other.ycbcr_model
+ && ycbcr_range == other.ycbcr_range
+ && component_mapping == other.component_mapping
+ && chroma_offset == other.chroma_offset
+ && chroma_filter == other.chroma_filter
+ && force_explicit_reconstruction == other.force_explicit_reconstruction
+ }
+ }
+}
+
+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.handle(), self.handle, ptr::null());
+ }
+ }
+}
+
+unsafe impl VulkanObject for SamplerYcbcrConversion {
+ type Handle = ash::vk::SamplerYcbcrConversion;
+
+ #[inline]
+ fn handle(&self) -> Self::Handle {
+ self.handle
+ }
+}
+
+unsafe impl DeviceOwned for SamplerYcbcrConversion {
+ #[inline]
+ fn device(&self) -> &Arc<Device> {
+ &self.device
+ }
+}
+
+impl_id_counter!(SamplerYcbcrConversion);
+
+/// Error that can happen when creating a `SamplerYcbcrConversion`.
+#[derive(Clone, Debug, PartialEq, Eq)]
+pub enum SamplerYcbcrConversionCreationError {
+ /// Not enough memory.
+ OomError(OomError),
+
+ RequirementNotMet {
+ required_for: &'static str,
+ requires_one_of: RequiresOneOf,
+ },
+
+ /// 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 for SamplerYcbcrConversionCreationError {
+ fn source(&self) -> Option<&(dyn Error + 'static)> {
+ match self {
+ SamplerYcbcrConversionCreationError::OomError(err) => Some(err),
+ _ => None,
+ }
+ }
+}
+
+impl Display for SamplerYcbcrConversionCreationError {
+ fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
+ match self {
+ Self::OomError(_) => write!(f, "not enough memory available"),
+ Self::RequirementNotMet {
+ required_for,
+ requires_one_of,
+ } => write!(
+ f,
+ "a requirement was not met for: {}; requires one of: {}",
+ required_for, requires_one_of,
+ ),
+ Self::CubicFilterNotSupported => {
+ write!(f, "the `Cubic` filter was specified")
+ }
+ Self::FormatMissing => {
+ write!(f, "no format was specified when one was required")
+ }
+ Self::FormatNotUnorm => {
+ write!(f, "the format has a color type other than `UNORM`")
+ }
+ Self::FormatNotSupported => {
+ write!(f, "the format does not support sampler YCbCr conversion")
+ }
+ Self::FormatChromaOffsetNotSupported => {
+ write!(f, "the format does not support the chosen chroma offsets")
+ }
+ Self::FormatInvalidComponentMapping => write!(
+ f,
+ "the component mapping was not valid for use with the chosen format",
+ ),
+ Self::FormatForceExplicitReconstructionNotSupported => write!(
+ f,
+ "the format does not support `force_explicit_reconstruction`",
+ ),
+ Self::FormatLinearFilterNotSupported => {
+ write!(f, "the format does not support the `Linear` filter")
+ }
+ Self::YcbcrModelInvalidComponentMapping => write!(
+ f,
+ "the component mapping was not valid for use with the chosen YCbCr model",
+ ),
+ Self::YcbcrRangeFormatNotEnoughBits => write!(
+ f,
+ "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 {
+ fn from(err: OomError) -> SamplerYcbcrConversionCreationError {
+ SamplerYcbcrConversionCreationError::OomError(err)
+ }
+}
+
+impl From<VulkanError> for SamplerYcbcrConversionCreationError {
+ fn from(err: VulkanError) -> SamplerYcbcrConversionCreationError {
+ match err {
+ err @ VulkanError::OutOfHostMemory => {
+ SamplerYcbcrConversionCreationError::OomError(OomError::from(err))
+ }
+ err @ VulkanError::OutOfDeviceMemory => {
+ SamplerYcbcrConversionCreationError::OomError(OomError::from(err))
+ }
+ _ => panic!("unexpected error: {:?}", err),
+ }
+ }
+}
+
+impl From<RequirementNotMet> for SamplerYcbcrConversionCreationError {
+ fn from(err: RequirementNotMet) -> Self {
+ Self::RequirementNotMet {
+ required_for: err.required_for,
+ requires_one_of: err.requires_one_of,
+ }
+ }
+}
+
+/// Parameters to create a new `SamplerYcbcrConversion`.
+#[derive(Clone, Debug)]
+pub struct SamplerYcbcrConversionCreateInfo {
+ /// 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`.
+ pub format: Option<Format>,
+
+ /// 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).
+ pub ycbcr_model: SamplerYcbcrModelConversion,
+
+ /// 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).
+ pub ycbcr_range: SamplerYcbcrRange,
+
+ /// The mapping to apply to the components of the input format, before color model conversion
+ /// and range expansion.
+ ///
+ /// The default value is [`ComponentMapping::identity()`].
+ pub component_mapping: ComponentMapping,
+
+ /// 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.
+ pub chroma_offset: [ChromaLocation; 2],
+
+ /// 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).
+ pub chroma_filter: Filter,
+
+ /// Forces explicit reconstruction if the implementation does not use it by default. The format
+ /// must support it. See
+ /// [the spec](https://registry.khronos.org/vulkan/specs/1.2-extensions/html/chap16.html#textures-chroma-reconstruction)
+ /// for more information.
+ ///
+ /// The default value is `false`.
+ pub force_explicit_reconstruction: bool,
+
+ pub _ne: crate::NonExhaustive,
+}
+
+impl Default for SamplerYcbcrConversionCreateInfo {
+ #[inline]
+ fn default() -> Self {
+ Self {
+ 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,
+ _ne: crate::NonExhaustive(()),
+ }
+ }
+}
+
+vulkan_enum! {
+ #[non_exhaustive]
+
+ /// The conversion between the color model of the source image and the color model of the shader.
+ SamplerYcbcrModelConversion = SamplerYcbcrModelConversion(i32);
+
+ /// The input values are already in the shader's model, and are passed through unmodified.
+ RgbIdentity = RGB_IDENTITY,
+
+ /// The input values are only range expanded, no other modifications are done.
+ YcbcrIdentity = YCBCR_IDENTITY,
+
+ /// The input values are converted according to the
+ /// [ITU-R BT.709](https://en.wikipedia.org/wiki/Rec._709) standard.
+ Ycbcr709 = YCBCR_709,
+
+ /// The input values are converted according to the
+ /// [ITU-R BT.601](https://en.wikipedia.org/wiki/Rec._601) standard.
+ Ycbcr601 = YCBCR_601,
+
+ /// The input values are converted according to the
+ /// [ITU-R BT.2020](https://en.wikipedia.org/wiki/Rec._2020) standard.
+ Ycbcr2020 = YCBCR_2020,
+}
+
+vulkan_enum! {
+ #[non_exhaustive]
+
+ /// How the numeric range of the input data is converted.
+ SamplerYcbcrRange = SamplerYcbcrRange(i32);
+
+ /// The input values cover the full numeric range, and are interpreted according to the ITU
+ /// "full range" rules.
+ ItuFull = ITU_FULL,
+
+ /// 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 = ITU_NARROW,
+}
+
+vulkan_enum! {
+ #[non_exhaustive]
+
+ /// For formats with chroma subsampling, the location where the chroma components are sampled,
+ /// relative to the luma component.
+ ChromaLocation = ChromaLocation(i32);
+
+ /// The chroma components are sampled at the even luma coordinate.
+ CositedEven = COSITED_EVEN,
+
+ /// The chroma components are sampled at the midpoint between the even luma coordinate and
+ /// the next higher odd luma coordinate.
+ Midpoint = MIDPOINT,
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{SamplerYcbcrConversion, SamplerYcbcrConversionCreationError};
+ use crate::RequiresOneOf;
+
+ #[test]
+ fn feature_not_enabled() {
+ let (device, _queue) = gfx_dev_and_queue!();
+
+ let r = SamplerYcbcrConversion::new(device, Default::default());
+
+ match r {
+ Err(SamplerYcbcrConversionCreationError::RequirementNotMet {
+ requires_one_of: RequiresOneOf { features, .. },
+ ..
+ }) if features.contains(&"sampler_ycbcr_conversion") => (),
+ _ => panic!(),
+ }
+ }
+}