use crate::{
check_errors,
device::{Device, DeviceOwned},
format::{ChromaSampling, Format, NumericType},
sampler::{ComponentMapping, ComponentSwizzle, Filter},
Error, OomError, Version, VulkanObject,
};
use std::{
error, fmt,
hash::{Hash, Hasher},
mem::MaybeUninit,
ptr,
sync::Arc,
};
#[derive(Debug)]
pub struct SamplerYcbcrConversion {
handle: ash::vk::SamplerYcbcrConversion,
device: Arc<Device>,
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 {
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::FeatureNotEnabled {
feature: "sampler_ycbcr_conversion",
reason: "tried to create a SamplerYcbcrConversion",
});
}
let format = match format {
Some(f) => f,
None => {
return Err(SamplerYcbcrConversionCreationError::FormatMissing);
}
};
if !format
.type_color()
.map_or(false, |ty| ty == NumericType::UNORM)
{
return Err(SamplerYcbcrConversionCreationError::FormatNotUnorm);
}
let potential_format_features = device
.physical_device()
.format_properties(format)
.potential_format_features();
if !(potential_format_features.midpoint_chroma_samples
|| potential_format_features.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 => {
if !potential_format_features.cosited_chroma_samples {
return Err(
SamplerYcbcrConversionCreationError::FormatChromaOffsetNotSupported,
);
}
}
ChromaLocation::Midpoint => {
if !potential_format_features.midpoint_chroma_samples {
return Err(
SamplerYcbcrConversionCreationError::FormatChromaOffsetNotSupported,
);
}
}
}
}
let g_ok = component_mapping.g_is_identity();
let a_ok = component_mapping.a_is_identity()
|| matches!(
component_mapping.a,
ComponentSwizzle::One | ComponentSwizzle::Zero
);
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]))
};
if ycbcr_model != SamplerYcbcrModelConversion::RgbIdentity
&& !components_bits[0..3]
.iter()
.all(|b| b.map_or(false, |b| b != 0))
{
return Err(SamplerYcbcrConversionCreationError::YcbcrModelInvalidComponentMapping);
}
if ycbcr_range == SamplerYcbcrRange::ItuNarrow {
for &bits in components_bits[0..3].iter().flatten() {
if bits < 8 {
return Err(SamplerYcbcrConversionCreationError::YcbcrRangeFormatNotEnoughBits);
}
}
}
if force_explicit_reconstruction
&& !potential_format_features
.sampled_image_ycbcr_conversion_chroma_reconstruction_explicit_forceable
{
return Err(
SamplerYcbcrConversionCreationError::FormatForceExplicitReconstructionNotSupported,
);
}
match chroma_filter {
Filter::Nearest => (),
Filter::Linear => {
if !potential_format_features.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();
check_errors(create_sampler_ycbcr_conversion(
device.internal_object(),
&create_info,
ptr::null(),
output.as_mut_ptr(),
))?;
output.assume_init()
};
Ok(Arc::new(SamplerYcbcrConversion {
handle,
device,
format: Some(format),
ycbcr_model,
ycbcr_range,
component_mapping,
chroma_offset,
chroma_filter,
force_explicit_reconstruction,
}))
}
#[inline]
pub fn chroma_filter(&self) -> Filter {
self.chroma_filter
}
#[inline]
pub fn chroma_offset(&self) -> [ChromaLocation; 2] {
self.chroma_offset
}
#[inline]
pub fn component_mapping(&self) -> ComponentMapping {
self.component_mapping
}
#[inline]
pub fn force_explicit_reconstruction(&self) -> bool {
self.force_explicit_reconstruction
}
#[inline]
pub fn format(&self) -> Option<Format> {
self.format
}
#[inline]
pub fn ycbcr_model(&self) -> SamplerYcbcrModelConversion {
self.ycbcr_model
}
#[inline]
pub fn ycbcr_range(&self) -> SamplerYcbcrRange {
self.ycbcr_range
}
#[inline]
pub fn is_identical(&self, other: &SamplerYcbcrConversion) -> bool {
self.handle == other.handle || {
let &Self {
handle: _,
device: _,
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.internal_object(),
self.handle,
ptr::null(),
);
}
}
}
unsafe impl VulkanObject for SamplerYcbcrConversion {
type Object = ash::vk::SamplerYcbcrConversion;
#[inline]
fn internal_object(&self) -> ash::vk::SamplerYcbcrConversion {
self.handle
}
}
unsafe impl DeviceOwned for SamplerYcbcrConversion {
#[inline]
fn device(&self) -> &Arc<Device> {
&self.device
}
}
impl PartialEq for SamplerYcbcrConversion {
#[inline]
fn eq(&self, other: &Self) -> bool {
self.handle == other.handle && self.device() == other.device()
}
}
impl Eq for SamplerYcbcrConversion {}
impl Hash for SamplerYcbcrConversion {
#[inline]
fn hash<H: Hasher>(&self, state: &mut H) {
self.handle.hash(state);
self.device().hash(state);
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum SamplerYcbcrConversionCreationError {
OomError(OomError),
FeatureNotEnabled {
feature: &'static str,
reason: &'static str,
},
CubicFilterNotSupported,
FormatMissing,
FormatNotUnorm,
FormatNotSupported,
FormatChromaOffsetNotSupported,
FormatInvalidComponentMapping,
FormatForceExplicitReconstructionNotSupported,
FormatLinearFilterNotSupported,
YcbcrModelInvalidComponentMapping,
YcbcrRangeFormatNotEnoughBits,
}
impl error::Error for SamplerYcbcrConversionCreationError {
#[inline]
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match *self {
SamplerYcbcrConversionCreationError::OomError(ref err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for SamplerYcbcrConversionCreationError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
match *self {
Self::OomError(_) => write!(fmt, "not enough memory available"),
Self::FeatureNotEnabled { feature, reason } => {
write!(fmt, "the feature {} must be enabled: {}", feature, reason)
}
Self::CubicFilterNotSupported => {
write!(fmt, "the `Cubic` filter was specified")
}
Self::FormatMissing => {
write!(fmt, "no format was specified when one was required")
}
Self::FormatNotUnorm => {
write!(fmt, "the format has a color type other than `UNORM`")
}
Self::FormatNotSupported => {
write!(fmt, "the format does not support sampler YCbCr conversion")
}
Self::FormatChromaOffsetNotSupported => {
write!(fmt, "the format does not support the chosen chroma offsets")
}
Self::FormatInvalidComponentMapping => {
write!(
fmt,
"the component mapping was not valid for use with the chosen format"
)
}
Self::FormatForceExplicitReconstructionNotSupported => {
write!(
fmt,
"the format does not support `force_explicit_reconstruction`"
)
}
Self::FormatLinearFilterNotSupported => {
write!(fmt, "the format does not support the `Linear` filter")
}
Self::YcbcrModelInvalidComponentMapping => {
write!(
fmt,
"the component mapping was not valid for use with the chosen YCbCr model"
)
}
Self::YcbcrRangeFormatNotEnoughBits => {
write!(fmt, "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 {
#[inline]
fn from(err: OomError) -> SamplerYcbcrConversionCreationError {
SamplerYcbcrConversionCreationError::OomError(err)
}
}
impl From<Error> for SamplerYcbcrConversionCreationError {
#[inline]
fn from(err: Error) -> SamplerYcbcrConversionCreationError {
match err {
err @ Error::OutOfHostMemory => {
SamplerYcbcrConversionCreationError::OomError(OomError::from(err))
}
err @ Error::OutOfDeviceMemory => {
SamplerYcbcrConversionCreationError::OomError(OomError::from(err))
}
_ => panic!("unexpected error: {:?}", err),
}
}
}
#[derive(Clone, Debug)]
pub struct SamplerYcbcrConversionCreateInfo {
pub format: Option<Format>,
pub ycbcr_model: SamplerYcbcrModelConversion,
pub ycbcr_range: SamplerYcbcrRange,
pub component_mapping: ComponentMapping,
pub chroma_offset: [ChromaLocation; 2],
pub chroma_filter: Filter,
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(()),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum SamplerYcbcrModelConversion {
RgbIdentity = ash::vk::SamplerYcbcrModelConversion::RGB_IDENTITY.as_raw(),
YcbcrIdentity = ash::vk::SamplerYcbcrModelConversion::YCBCR_IDENTITY.as_raw(),
Ycbcr709 = ash::vk::SamplerYcbcrModelConversion::YCBCR_709.as_raw(),
Ycbcr601 = ash::vk::SamplerYcbcrModelConversion::YCBCR_601.as_raw(),
Ycbcr2020 = ash::vk::SamplerYcbcrModelConversion::YCBCR_2020.as_raw(),
}
impl From<SamplerYcbcrModelConversion> for ash::vk::SamplerYcbcrModelConversion {
#[inline]
fn from(val: SamplerYcbcrModelConversion) -> Self {
Self::from_raw(val as i32)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum SamplerYcbcrRange {
ItuFull = ash::vk::SamplerYcbcrRange::ITU_FULL.as_raw(),
ItuNarrow = ash::vk::SamplerYcbcrRange::ITU_NARROW.as_raw(),
}
impl From<SamplerYcbcrRange> for ash::vk::SamplerYcbcrRange {
#[inline]
fn from(val: SamplerYcbcrRange) -> Self {
Self::from_raw(val as i32)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[repr(i32)]
pub enum ChromaLocation {
CositedEven = ash::vk::ChromaLocation::COSITED_EVEN.as_raw(),
Midpoint = ash::vk::ChromaLocation::MIDPOINT.as_raw(),
}
impl From<ChromaLocation> for ash::vk::ChromaLocation {
#[inline]
fn from(val: ChromaLocation) -> Self {
Self::from_raw(val as i32)
}
}
#[cfg(test)]
mod tests {
use super::{SamplerYcbcrConversion, SamplerYcbcrConversionCreationError};
#[test]
fn feature_not_enabled() {
let (device, queue) = gfx_dev_and_queue!();
let r = SamplerYcbcrConversion::new(device, Default::default());
match r {
Err(SamplerYcbcrConversionCreationError::FeatureNotEnabled {
feature: "sampler_ycbcr_conversion",
..
}) => (),
_ => panic!(),
}
}
}