diff options
Diffstat (limited to 'src/sampler/ycbcr.rs')
-rw-r--r-- | src/sampler/ycbcr.rs | 809 |
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!(), + } + } +} |