1 // Copyright (c) 2022 The vulkano developers
2 // Licensed under the Apache License, Version 2.0
3 // <LICENSE-APACHE or
4 // https://www.apache.org/licenses/LICENSE-2.0> or the MIT
5 // license <LICENSE-MIT or https://opensource.org/licenses/MIT>,
6 // at your option. All files in the project carrying such
7 // notice may not be copied, modified, or distributed except
8 // according to those terms.
9 
10 //! Conversion from sampled YCbCr image data to RGB shader data.
11 //!
12 //! A sampler YCbCr conversion is an object that assists a sampler when converting from YCbCr
13 //! formats and/or YCbCr texel input data. It is used to read frames of video data within a shader,
14 //! possibly to apply it as texture on a rendered primitive. Sampler YCbCr conversion can only be
15 //! used with certain formats, and conversely, some formats require the use of a sampler YCbCr
16 //! conversion to be sampled at all.
17 //!
18 //! A sampler YCbCr conversion can only be used with a combined image sampler descriptor in a
19 //! descriptor set. The conversion must be attached on both the image view and sampler in the
20 //! descriptor, and the sampler must be included in the descriptor set layout as an immutable
21 //! sampler.
22 //!
23 //! # Examples
24 //!
25 //! ```
26 //! # let device: std::sync::Arc<vulkano::device::Device> = return;
27 //! # let image_data: Vec<u8> = return;
28 //! # let queue: std::sync::Arc<vulkano::device::Queue> = return;
29 //! # let memory_allocator: vulkano::memory::allocator::StandardMemoryAllocator = return;
30 //! # let descriptor_set_allocator: vulkano::descriptor_set::allocator::StandardDescriptorSetAllocator = return;
31 //! # let mut command_buffer_builder: vulkano::command_buffer::AutoCommandBufferBuilder<vulkano::command_buffer::PrimaryAutoCommandBuffer> = return;
32 //! use vulkano::descriptor_set::{PersistentDescriptorSet, WriteDescriptorSet};
33 //! use vulkano::descriptor_set::layout::{DescriptorSetLayout, DescriptorSetLayoutBinding, DescriptorSetLayoutCreateInfo, DescriptorType};
34 //! use vulkano::format::Format;
35 //! use vulkano::image::{ImmutableImage, ImageCreateFlags, ImageDimensions, ImageUsage, MipmapsCount};
36 //! use vulkano::image::view::{ImageView, ImageViewCreateInfo};
37 //! use vulkano::sampler::{Sampler, SamplerCreateInfo};
38 //! use vulkano::sampler::ycbcr::{SamplerYcbcrConversion, SamplerYcbcrConversionCreateInfo, SamplerYcbcrModelConversion};
39 //! use vulkano::shader::ShaderStage;
40 //!
41 //! let conversion = SamplerYcbcrConversion::new(device.clone(), SamplerYcbcrConversionCreateInfo {
42 //!     format: Some(Format::G8_B8_R8_3PLANE_420_UNORM),
43 //!     ycbcr_model: SamplerYcbcrModelConversion::YcbcrIdentity,
44 //!     ..Default::default()
45 //! })
46 //! .unwrap();
47 //!
48 //! let sampler = Sampler::new(device.clone(), SamplerCreateInfo {
49 //!     sampler_ycbcr_conversion: Some(conversion.clone()),
50 //!     ..Default::default()
51 //! })
52 //! .unwrap();
53 //!
54 //! let descriptor_set_layout = DescriptorSetLayout::new(
55 //!     device.clone(),
56 //!         DescriptorSetLayoutCreateInfo {
57 //!         bindings: [(
58 //!             0,
59 //!             DescriptorSetLayoutBinding {
60 //!                 stages: ShaderStage::Fragment.into(),
61 //!                 immutable_samplers: vec![sampler],
62 //!                 ..DescriptorSetLayoutBinding::descriptor_type(DescriptorType::CombinedImageSampler)
63 //!             },
64 //!         )]
65 //!         .into(),
66 //!         ..Default::default()
67 //!     },
68 //! ).unwrap();
69 //!
70 //! let image = ImmutableImage::from_iter(
71 //!     &memory_allocator,
72 //!     image_data,
73 //!     ImageDimensions::Dim2d { width: 1920, height: 1080, array_layers: 1 },
74 //!     MipmapsCount::One,
75 //!     Format::G8_B8_R8_3PLANE_420_UNORM,
76 //!     &mut command_buffer_builder,
77 //! ).unwrap();
78 //!
79 //! let create_info = ImageViewCreateInfo {
80 //!     sampler_ycbcr_conversion: Some(conversion.clone()),
81 //!     ..ImageViewCreateInfo::from_image(&image)
82 //! };
83 //! let image_view = ImageView::new(image, create_info).unwrap();
84 //!
85 //! let descriptor_set = PersistentDescriptorSet::new(
86 //!     &descriptor_set_allocator,
87 //!     descriptor_set_layout.clone(),
88 //!     [WriteDescriptorSet::image_view(0, image_view)],
89 //! ).unwrap();
90 //! ```
91 
92 use crate::{
93     device::{Device, DeviceOwned},
94     format::{ChromaSampling, Format, FormatFeatures, NumericType},
95     macros::{impl_id_counter, vulkan_enum},
96     sampler::{ComponentMapping, ComponentSwizzle, Filter},
97     OomError, RequirementNotMet, RequiresOneOf, Version, VulkanError, VulkanObject,
98 };
99 use std::{
100     error::Error,
101     fmt::{Display, Error as FmtError, Formatter},
102     mem::MaybeUninit,
103     num::NonZeroU64,
104     ptr,
105     sync::Arc,
106 };
107 
108 /// Describes how sampled image data should converted from a YCbCr representation to an RGB one.
109 #[derive(Debug)]
110 pub struct SamplerYcbcrConversion {
111     handle: ash::vk::SamplerYcbcrConversion,
112     device: Arc<Device>,
113     id: NonZeroU64,
114 
115     format: Option<Format>,
116     ycbcr_model: SamplerYcbcrModelConversion,
117     ycbcr_range: SamplerYcbcrRange,
118     component_mapping: ComponentMapping,
119     chroma_offset: [ChromaLocation; 2],
120     chroma_filter: Filter,
121     force_explicit_reconstruction: bool,
122 }
123 
124 impl SamplerYcbcrConversion {
125     /// Creates a new `SamplerYcbcrConversion`.
126     ///
127     /// The [`sampler_ycbcr_conversion`](crate::device::Features::sampler_ycbcr_conversion)
128     /// feature must be enabled on the device.
new( device: Arc<Device>, create_info: SamplerYcbcrConversionCreateInfo, ) -> Result<Arc<SamplerYcbcrConversion>, SamplerYcbcrConversionCreationError>129     pub fn new(
130         device: Arc<Device>,
131         create_info: SamplerYcbcrConversionCreateInfo,
132     ) -> Result<Arc<SamplerYcbcrConversion>, SamplerYcbcrConversionCreationError> {
133         let SamplerYcbcrConversionCreateInfo {
134             format,
135             ycbcr_model,
136             ycbcr_range,
137             component_mapping,
138             chroma_offset,
139             chroma_filter,
140             force_explicit_reconstruction,
141             _ne: _,
142         } = create_info;
143 
144         if !device.enabled_features().sampler_ycbcr_conversion {
145             return Err(SamplerYcbcrConversionCreationError::RequirementNotMet {
146                 required_for: "`SamplerYcbcrConversion::new`",
147                 requires_one_of: RequiresOneOf {
148                     features: &["sampler_ycbcr_conversion"],
149                     ..Default::default()
150                 },
151             });
152         }
153 
154         let format = match format {
155             Some(f) => f,
156             None => {
157                 return Err(SamplerYcbcrConversionCreationError::FormatMissing);
158             }
159         };
160 
161         // VUID-VkSamplerYcbcrConversionCreateInfo-format-parameter
162         format.validate_device(&device)?;
163 
164         // VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrModel-parameter
165         ycbcr_model.validate_device(&device)?;
166 
167         // VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrRange-parameter
168         ycbcr_range.validate_device(&device)?;
169 
170         // VUID-VkComponentMapping-r-parameter
171         component_mapping.r.validate_device(&device)?;
172 
173         // VUID-VkComponentMapping-g-parameter
174         component_mapping.g.validate_device(&device)?;
175 
176         // VUID-VkComponentMapping-b-parameter
177         component_mapping.b.validate_device(&device)?;
178 
179         // VUID-VkComponentMapping-a-parameter
180         component_mapping.a.validate_device(&device)?;
181 
182         for offset in chroma_offset {
183             // VUID-VkSamplerYcbcrConversionCreateInfo-xChromaOffset-parameter
184             // VUID-VkSamplerYcbcrConversionCreateInfo-yChromaOffset-parameter
185             offset.validate_device(&device)?;
186         }
187 
188         // VUID-VkSamplerYcbcrConversionCreateInfo-chromaFilter-parameter
189         chroma_filter.validate_device(&device)?;
190 
191         // VUID-VkSamplerYcbcrConversionCreateInfo-format-04061
192         if !format
193             .type_color()
194             .map_or(false, |ty| ty == NumericType::UNORM)
195         {
196             return Err(SamplerYcbcrConversionCreationError::FormatNotUnorm);
197         }
198 
199         // Use unchecked, because all validation has been done above.
200         let potential_format_features = unsafe {
201             device
202                 .physical_device()
203                 .format_properties_unchecked(format)
204                 .potential_format_features()
205         };
206 
207         // VUID-VkSamplerYcbcrConversionCreateInfo-format-01650
208         if !potential_format_features.intersects(
209             FormatFeatures::MIDPOINT_CHROMA_SAMPLES | FormatFeatures::COSITED_CHROMA_SAMPLES,
210         ) {
211             return Err(SamplerYcbcrConversionCreationError::FormatNotSupported);
212         }
213 
214         if let Some(chroma_sampling @ (ChromaSampling::Mode422 | ChromaSampling::Mode420)) =
215             format.ycbcr_chroma_sampling()
216         {
217             let chroma_offsets_to_check = match chroma_sampling {
218                 ChromaSampling::Mode420 => &chroma_offset[0..2],
219                 ChromaSampling::Mode422 => &chroma_offset[0..1],
220                 _ => unreachable!(),
221             };
222 
223             for offset in chroma_offsets_to_check {
224                 match offset {
225                     ChromaLocation::CositedEven => {
226                         // VUID-VkSamplerYcbcrConversionCreateInfo-xChromaOffset-01651
227                         if !potential_format_features
228                             .intersects(FormatFeatures::COSITED_CHROMA_SAMPLES)
229                         {
230                             return Err(
231                                 SamplerYcbcrConversionCreationError::FormatChromaOffsetNotSupported,
232                             );
233                         }
234                     }
235                     ChromaLocation::Midpoint => {
236                         // VUID-VkSamplerYcbcrConversionCreateInfo-xChromaOffset-01652
237                         if !potential_format_features
238                             .intersects(FormatFeatures::MIDPOINT_CHROMA_SAMPLES)
239                         {
240                             return Err(
241                                 SamplerYcbcrConversionCreationError::FormatChromaOffsetNotSupported,
242                             );
243                         }
244                     }
245                 }
246             }
247 
248             // VUID-VkSamplerYcbcrConversionCreateInfo-components-02581
249             let g_ok = component_mapping.g_is_identity();
250 
251             // VUID-VkSamplerYcbcrConversionCreateInfo-components-02582
252             let a_ok = component_mapping.a_is_identity()
253                 || matches!(
254                     component_mapping.a,
255                     ComponentSwizzle::One | ComponentSwizzle::Zero
256                 );
257 
258             // VUID-VkSamplerYcbcrConversionCreateInfo-components-02583
259             // VUID-VkSamplerYcbcrConversionCreateInfo-components-02584
260             // VUID-VkSamplerYcbcrConversionCreateInfo-components-02585
261             let rb_ok1 = component_mapping.r_is_identity() && component_mapping.b_is_identity();
262             let rb_ok2 = matches!(component_mapping.r, ComponentSwizzle::Blue)
263                 && matches!(component_mapping.b, ComponentSwizzle::Red);
264 
265             if !(g_ok && a_ok && (rb_ok1 || rb_ok2)) {
266                 return Err(SamplerYcbcrConversionCreationError::FormatInvalidComponentMapping);
267             }
268         }
269 
270         let components_bits = {
271             let bits = format.components();
272             component_mapping
273                 .component_map()
274                 .map(move |i| i.map(|i| bits[i]))
275         };
276 
277         // VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrModel-01655
278         if ycbcr_model != SamplerYcbcrModelConversion::RgbIdentity
279             && !components_bits[0..3]
280                 .iter()
281                 .all(|b| b.map_or(false, |b| b != 0))
282         {
283             return Err(SamplerYcbcrConversionCreationError::YcbcrModelInvalidComponentMapping);
284         }
285 
286         // VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrRange-02748
287         if ycbcr_range == SamplerYcbcrRange::ItuNarrow {
288             // TODO: Spec doesn't say how many bits `Zero` and `One` are considered to have, so
289             // just skip them for now.
290             for &bits in components_bits[0..3].iter().flatten() {
291                 if bits < 8 {
292                     return Err(SamplerYcbcrConversionCreationError::YcbcrRangeFormatNotEnoughBits);
293                 }
294             }
295         }
296 
297         // VUID-VkSamplerYcbcrConversionCreateInfo-forceExplicitReconstruction-01656
298         if force_explicit_reconstruction
299             && !potential_format_features.intersects(FormatFeatures::
300                 SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_FORCEABLE)
301         {
302             return Err(
303                 SamplerYcbcrConversionCreationError::FormatForceExplicitReconstructionNotSupported,
304             );
305         }
306 
307         match chroma_filter {
308             Filter::Nearest => (),
309             Filter::Linear => {
310                 // VUID-VkSamplerYcbcrConversionCreateInfo-chromaFilter-01657
311                 if !potential_format_features
312                     .intersects(FormatFeatures::SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER)
313                 {
314                     return Err(
315                         SamplerYcbcrConversionCreationError::FormatLinearFilterNotSupported,
316                     );
317                 }
318             }
319             Filter::Cubic => {
320                 return Err(SamplerYcbcrConversionCreationError::CubicFilterNotSupported);
321             }
322         }
323 
324         let create_info = ash::vk::SamplerYcbcrConversionCreateInfo {
325             format: format.into(),
326             ycbcr_model: ycbcr_model.into(),
327             ycbcr_range: ycbcr_range.into(),
328             components: component_mapping.into(),
329             x_chroma_offset: chroma_offset[0].into(),
330             y_chroma_offset: chroma_offset[1].into(),
331             chroma_filter: chroma_filter.into(),
332             force_explicit_reconstruction: force_explicit_reconstruction as ash::vk::Bool32,
333             ..Default::default()
334         };
335 
336         let handle = unsafe {
337             let fns = device.fns();
338             let create_sampler_ycbcr_conversion = if device.api_version() >= Version::V1_1 {
339                 fns.v1_1.create_sampler_ycbcr_conversion
340             } else {
341                 fns.khr_sampler_ycbcr_conversion
342                     .create_sampler_ycbcr_conversion_khr
343             };
344 
345             let mut output = MaybeUninit::uninit();
346             create_sampler_ycbcr_conversion(
347                 device.handle(),
348                 &create_info,
349                 ptr::null(),
350                 output.as_mut_ptr(),
351             )
352             .result()
353             .map_err(VulkanError::from)?;
354             output.assume_init()
355         };
356 
357         Ok(Arc::new(SamplerYcbcrConversion {
358             handle,
359             device,
360             id: Self::next_id(),
361             format: Some(format),
362             ycbcr_model,
363             ycbcr_range,
364             component_mapping,
365             chroma_offset,
366             chroma_filter,
367             force_explicit_reconstruction,
368         }))
369     }
370 
371     /// Creates a new `SamplerYcbcrConversion` from a raw object handle.
372     ///
373     /// # Safety
374     ///
375     /// - `handle` must be a valid Vulkan object handle created from `device`.
376     /// - `create_info` must match the info used to create the object.
377     /// - `create_info.format` must be `Some`.
378     #[inline]
from_handle( device: Arc<Device>, handle: ash::vk::SamplerYcbcrConversion, create_info: SamplerYcbcrConversionCreateInfo, ) -> Arc<SamplerYcbcrConversion>379     pub unsafe fn from_handle(
380         device: Arc<Device>,
381         handle: ash::vk::SamplerYcbcrConversion,
382         create_info: SamplerYcbcrConversionCreateInfo,
383     ) -> Arc<SamplerYcbcrConversion> {
384         let SamplerYcbcrConversionCreateInfo {
385             format,
386             ycbcr_model,
387             ycbcr_range,
388             component_mapping,
389             chroma_offset,
390             chroma_filter,
391             force_explicit_reconstruction,
392             _ne: _,
393         } = create_info;
394 
395         Arc::new(SamplerYcbcrConversion {
396             handle,
397             device,
398             id: Self::next_id(),
399             format,
400             ycbcr_model,
401             ycbcr_range,
402             component_mapping,
403             chroma_offset,
404             chroma_filter,
405             force_explicit_reconstruction,
406         })
407     }
408 
409     /// Returns the chroma filter used by the conversion.
410     #[inline]
chroma_filter(&self) -> Filter411     pub fn chroma_filter(&self) -> Filter {
412         self.chroma_filter
413     }
414 
415     /// Returns the chroma offsets used by the conversion.
416     #[inline]
chroma_offset(&self) -> [ChromaLocation; 2]417     pub fn chroma_offset(&self) -> [ChromaLocation; 2] {
418         self.chroma_offset
419     }
420 
421     /// Returns the component mapping of the conversion.
422     #[inline]
component_mapping(&self) -> ComponentMapping423     pub fn component_mapping(&self) -> ComponentMapping {
424         self.component_mapping
425     }
426 
427     /// Returns whether the conversion has forced explicit reconstruction to be enabled.
428     #[inline]
force_explicit_reconstruction(&self) -> bool429     pub fn force_explicit_reconstruction(&self) -> bool {
430         self.force_explicit_reconstruction
431     }
432 
433     /// Returns the format that the conversion was created for.
434     #[inline]
format(&self) -> Option<Format>435     pub fn format(&self) -> Option<Format> {
436         self.format
437     }
438 
439     /// Returns the YCbCr model of the conversion.
440     #[inline]
ycbcr_model(&self) -> SamplerYcbcrModelConversion441     pub fn ycbcr_model(&self) -> SamplerYcbcrModelConversion {
442         self.ycbcr_model
443     }
444 
445     /// Returns the YCbCr range of the conversion.
446     #[inline]
ycbcr_range(&self) -> SamplerYcbcrRange447     pub fn ycbcr_range(&self) -> SamplerYcbcrRange {
448         self.ycbcr_range
449     }
450 
451     /// Returns whether `self` is equal or identically defined to `other`.
452     #[inline]
is_identical(&self, other: &SamplerYcbcrConversion) -> bool453     pub fn is_identical(&self, other: &SamplerYcbcrConversion) -> bool {
454         self.handle == other.handle || {
455             let &Self {
456                 handle: _,
457                 device: _,
458                 id: _,
459                 format,
460                 ycbcr_model,
461                 ycbcr_range,
462                 component_mapping,
463                 chroma_offset,
464                 chroma_filter,
465                 force_explicit_reconstruction,
466             } = self;
467 
468             format == other.format
469                 && ycbcr_model == other.ycbcr_model
470                 && ycbcr_range == other.ycbcr_range
471                 && component_mapping == other.component_mapping
472                 && chroma_offset == other.chroma_offset
473                 && chroma_filter == other.chroma_filter
474                 && force_explicit_reconstruction == other.force_explicit_reconstruction
475         }
476     }
477 }
478 
479 impl Drop for SamplerYcbcrConversion {
480     #[inline]
drop(&mut self)481     fn drop(&mut self) {
482         unsafe {
483             let fns = self.device.fns();
484             let destroy_sampler_ycbcr_conversion = if self.device.api_version() >= Version::V1_1 {
485                 fns.v1_1.destroy_sampler_ycbcr_conversion
486             } else {
487                 fns.khr_sampler_ycbcr_conversion
488                     .destroy_sampler_ycbcr_conversion_khr
489             };
490 
491             destroy_sampler_ycbcr_conversion(self.device.handle(), self.handle, ptr::null());
492         }
493     }
494 }
495 
496 unsafe impl VulkanObject for SamplerYcbcrConversion {
497     type Handle = ash::vk::SamplerYcbcrConversion;
498 
499     #[inline]
handle(&self) -> Self::Handle500     fn handle(&self) -> Self::Handle {
501         self.handle
502     }
503 }
504 
505 unsafe impl DeviceOwned for SamplerYcbcrConversion {
506     #[inline]
device(&self) -> &Arc<Device>507     fn device(&self) -> &Arc<Device> {
508         &self.device
509     }
510 }
511 
512 impl_id_counter!(SamplerYcbcrConversion);
513 
514 /// Error that can happen when creating a `SamplerYcbcrConversion`.
515 #[derive(Clone, Debug, PartialEq, Eq)]
516 pub enum SamplerYcbcrConversionCreationError {
517     /// Not enough memory.
518     OomError(OomError),
519 
520     RequirementNotMet {
521         required_for: &'static str,
522         requires_one_of: RequiresOneOf,
523     },
524 
525     /// The `Cubic` filter was specified.
526     CubicFilterNotSupported,
527 
528     /// No format was specified when one was required.
529     FormatMissing,
530 
531     /// The format has a color type other than `UNORM`.
532     FormatNotUnorm,
533 
534     /// The format does not support sampler YCbCr conversion.
535     FormatNotSupported,
536 
537     /// The format does not support the chosen chroma offsets.
538     FormatChromaOffsetNotSupported,
539 
540     /// The component mapping was not valid for use with the chosen format.
541     FormatInvalidComponentMapping,
542 
543     /// The format does not support `force_explicit_reconstruction`.
544     FormatForceExplicitReconstructionNotSupported,
545 
546     /// The format does not support the `Linear` filter.
547     FormatLinearFilterNotSupported,
548 
549     /// The component mapping was not valid for use with the chosen YCbCr model.
550     YcbcrModelInvalidComponentMapping,
551 
552     /// For the chosen `ycbcr_range`, the R, G or B components being read from the `format` do not
553     /// have the minimum number of required bits.
554     YcbcrRangeFormatNotEnoughBits,
555 }
556 
557 impl Error for SamplerYcbcrConversionCreationError {
source(&self) -> Option<&(dyn Error + 'static)>558     fn source(&self) -> Option<&(dyn Error + 'static)> {
559         match self {
560             SamplerYcbcrConversionCreationError::OomError(err) => Some(err),
561             _ => None,
562         }
563     }
564 }
565 
566 impl Display for SamplerYcbcrConversionCreationError {
fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError>567     fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), FmtError> {
568         match self {
569             Self::OomError(_) => write!(f, "not enough memory available"),
570             Self::RequirementNotMet {
571                 required_for,
572                 requires_one_of,
573             } => write!(
574                 f,
575                 "a requirement was not met for: {}; requires one of: {}",
576                 required_for, requires_one_of,
577             ),
578             Self::CubicFilterNotSupported => {
579                 write!(f, "the `Cubic` filter was specified")
580             }
581             Self::FormatMissing => {
582                 write!(f, "no format was specified when one was required")
583             }
584             Self::FormatNotUnorm => {
585                 write!(f, "the format has a color type other than `UNORM`")
586             }
587             Self::FormatNotSupported => {
588                 write!(f, "the format does not support sampler YCbCr conversion")
589             }
590             Self::FormatChromaOffsetNotSupported => {
591                 write!(f, "the format does not support the chosen chroma offsets")
592             }
593             Self::FormatInvalidComponentMapping => write!(
594                 f,
595                 "the component mapping was not valid for use with the chosen format",
596             ),
597             Self::FormatForceExplicitReconstructionNotSupported => write!(
598                 f,
599                 "the format does not support `force_explicit_reconstruction`",
600             ),
601             Self::FormatLinearFilterNotSupported => {
602                 write!(f, "the format does not support the `Linear` filter")
603             }
604             Self::YcbcrModelInvalidComponentMapping => write!(
605                 f,
606                 "the component mapping was not valid for use with the chosen YCbCr model",
607             ),
608             Self::YcbcrRangeFormatNotEnoughBits => write!(
609                 f,
610                 "for the chosen `ycbcr_range`, the R, G or B components being read from the \
611                 `format` do not have the minimum number of required bits",
612             ),
613         }
614     }
615 }
616 
617 impl From<OomError> for SamplerYcbcrConversionCreationError {
from(err: OomError) -> SamplerYcbcrConversionCreationError618     fn from(err: OomError) -> SamplerYcbcrConversionCreationError {
619         SamplerYcbcrConversionCreationError::OomError(err)
620     }
621 }
622 
623 impl From<VulkanError> for SamplerYcbcrConversionCreationError {
from(err: VulkanError) -> SamplerYcbcrConversionCreationError624     fn from(err: VulkanError) -> SamplerYcbcrConversionCreationError {
625         match err {
626             err @ VulkanError::OutOfHostMemory => {
627                 SamplerYcbcrConversionCreationError::OomError(OomError::from(err))
628             }
629             err @ VulkanError::OutOfDeviceMemory => {
630                 SamplerYcbcrConversionCreationError::OomError(OomError::from(err))
631             }
632             _ => panic!("unexpected error: {:?}", err),
633         }
634     }
635 }
636 
637 impl From<RequirementNotMet> for SamplerYcbcrConversionCreationError {
from(err: RequirementNotMet) -> Self638     fn from(err: RequirementNotMet) -> Self {
639         Self::RequirementNotMet {
640             required_for: err.required_for,
641             requires_one_of: err.requires_one_of,
642         }
643     }
644 }
645 
646 /// Parameters to create a new `SamplerYcbcrConversion`.
647 #[derive(Clone, Debug)]
648 pub struct SamplerYcbcrConversionCreateInfo {
649     /// The image view format that this conversion will read data from. The conversion cannot be
650     /// used with image views of any other format.
651     ///
652     /// The format must support YCbCr conversions, meaning that its `FormatFeatures` must support
653     /// at least one of `cosited_chroma_samples` or `midpoint_chroma_samples`.
654     ///
655     /// If this is set to a format that has chroma subsampling (contains `422` or `420` in the name)
656     /// then `component_mapping` is restricted as follows:
657     /// - `g` must be identity swizzled.
658     /// - `a` must be identity swizzled or `Zero` or `One`.
659     /// - `r` and `b` must be identity swizzled or mapped to each other.
660     ///
661     /// Compatibility notice: currently, this value must be `Some`, but future additions may allow
662     /// `None` as a valid value as well.
663     ///
664     /// The default value is `None`.
665     pub format: Option<Format>,
666 
667     /// The conversion between the input color model and the output RGB color model.
668     ///
669     /// If this is not set to `RgbIdentity`, then the `r`, `g` and `b` components of
670     /// `component_mapping` must not be `Zero` or `One`, and the component being read must exist in
671     /// `format` (must be represented as a nonzero number of bits).
672     ///
673     /// The default value is [`RgbIdentity`](SamplerYcbcrModelConversion::RgbIdentity).
674     pub ycbcr_model: SamplerYcbcrModelConversion,
675 
676     /// If `ycbcr_model` is not `RgbIdentity`, specifies the range expansion of the input values
677     /// that should be used.
678     ///
679     /// If this is set to `ItuNarrow`, then the `r`, `g` and `b` components of `component_mapping`
680     /// must each map to a component of `format` that is represented with at least 8 bits.
681     ///
682     /// The default value is [`ItuFull`](SamplerYcbcrRange::ItuFull).
683     pub ycbcr_range: SamplerYcbcrRange,
684 
685     /// The mapping to apply to the components of the input format, before color model conversion
686     /// and range expansion.
687     ///
688     /// The default value is [`ComponentMapping::identity()`].
689     pub component_mapping: ComponentMapping,
690 
691     /// For formats with chroma subsampling and a `Linear` filter, specifies the sampled location
692     /// for the subsampled components, in the x and y direction.
693     ///
694     /// The value is ignored if the filter is `Nearest` or the corresponding axis is not chroma
695     /// subsampled. If the value is not ignored, the format must support the chosen mode.
696     ///
697     /// The default value is [`CositedEven`](ChromaLocation::CositedEven) for both axes.
698     pub chroma_offset: [ChromaLocation; 2],
699 
700     /// For formats with chroma subsampling, specifies the filter used for reconstructing the chroma
701     /// components to full resolution.
702     ///
703     /// The `Cubic` filter is not supported. If `Linear` is used, the format must support it.
704     ///
705     /// The default value is [`Nearest`](Filter::Nearest).
706     pub chroma_filter: Filter,
707 
708     /// Forces explicit reconstruction if the implementation does not use it by default. The format
709     /// must support it. See
710     /// [the spec](https://registry.khronos.org/vulkan/specs/1.2-extensions/html/chap16.html#textures-chroma-reconstruction)
711     /// for more information.
712     ///
713     /// The default value is `false`.
714     pub force_explicit_reconstruction: bool,
715 
716     pub _ne: crate::NonExhaustive,
717 }
718 
719 impl Default for SamplerYcbcrConversionCreateInfo {
720     #[inline]
default() -> Self721     fn default() -> Self {
722         Self {
723             format: None,
724             ycbcr_model: SamplerYcbcrModelConversion::RgbIdentity,
725             ycbcr_range: SamplerYcbcrRange::ItuFull,
726             component_mapping: ComponentMapping::identity(),
727             chroma_offset: [ChromaLocation::CositedEven; 2],
728             chroma_filter: Filter::Nearest,
729             force_explicit_reconstruction: false,
730             _ne: crate::NonExhaustive(()),
731         }
732     }
733 }
734 
735 vulkan_enum! {
736     #[non_exhaustive]
737 
738     /// The conversion between the color model of the source image and the color model of the shader.
739     SamplerYcbcrModelConversion = SamplerYcbcrModelConversion(i32);
740 
741     /// The input values are already in the shader's model, and are passed through unmodified.
742     RgbIdentity = RGB_IDENTITY,
743 
744     /// The input values are only range expanded, no other modifications are done.
745     YcbcrIdentity = YCBCR_IDENTITY,
746 
747     /// The input values are converted according to the
748     /// [ITU-R BT.709](https://en.wikipedia.org/wiki/Rec._709) standard.
749     Ycbcr709 = YCBCR_709,
750 
751     /// The input values are converted according to the
752     /// [ITU-R BT.601](https://en.wikipedia.org/wiki/Rec._601) standard.
753     Ycbcr601 = YCBCR_601,
754 
755     /// The input values are converted according to the
756     /// [ITU-R BT.2020](https://en.wikipedia.org/wiki/Rec._2020) standard.
757     Ycbcr2020 = YCBCR_2020,
758 }
759 
760 vulkan_enum! {
761     #[non_exhaustive]
762 
763     /// How the numeric range of the input data is converted.
764     SamplerYcbcrRange = SamplerYcbcrRange(i32);
765 
766     /// The input values cover the full numeric range, and are interpreted according to the ITU
767     /// "full range" rules.
768     ItuFull = ITU_FULL,
769 
770     /// The input values cover only a subset of the numeric range, with the remainder reserved as
771     /// headroom/footroom. The values are interpreted according to the ITU "narrow range" rules.
772     ItuNarrow = ITU_NARROW,
773 }
774 
775 vulkan_enum! {
776     #[non_exhaustive]
777 
778     /// For formats with chroma subsampling, the location where the chroma components are sampled,
779     /// relative to the luma component.
780     ChromaLocation = ChromaLocation(i32);
781 
782     /// The chroma components are sampled at the even luma coordinate.
783     CositedEven = COSITED_EVEN,
784 
785     /// The chroma components are sampled at the midpoint between the even luma coordinate and
786     /// the next higher odd luma coordinate.
787     Midpoint = MIDPOINT,
788 }
789 
790 #[cfg(test)]
791 mod tests {
792     use super::{SamplerYcbcrConversion, SamplerYcbcrConversionCreationError};
793     use crate::RequiresOneOf;
794 
795     #[test]
feature_not_enabled()796     fn feature_not_enabled() {
797         let (device, _queue) = gfx_dev_and_queue!();
798 
799         let r = SamplerYcbcrConversion::new(device, Default::default());
800 
801         match r {
802             Err(SamplerYcbcrConversionCreationError::RequirementNotMet {
803                 requires_one_of: RequiresOneOf { features, .. },
804                 ..
805             }) if features.contains(&"sampler_ycbcr_conversion") => (),
806             _ => panic!(),
807         }
808     }
809 }
810