blob: 9d9aaf7688dd076931d8367907bfadec33daf55f [file] [log] [blame]
// 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!(),
}
}
}