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