use crate::{
device::{Device, DeviceOwned},
format::{ChromaSampling, Format, FormatFeatures, NumericFormat},
image::sampler::{ComponentMapping, ComponentSwizzle, Filter},
instance::InstanceOwnedDebugWrapper,
macros::{impl_id_counter, vulkan_enum},
Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, Version, VulkanError,
VulkanObject,
};
use std::{mem::MaybeUninit, num::NonZeroU64, ptr, sync::Arc};
#[derive(Debug)]
pub struct SamplerYcbcrConversion {
handle: ash::vk::SamplerYcbcrConversion,
device: InstanceOwnedDebugWrapper<Arc<Device>>,
id: NonZeroU64,
format: Format,
ycbcr_model: SamplerYcbcrModelConversion,
ycbcr_range: SamplerYcbcrRange,
component_mapping: ComponentMapping,
chroma_offset: [ChromaLocation; 2],
chroma_filter: Filter,
force_explicit_reconstruction: bool,
}
impl SamplerYcbcrConversion {
#[inline]
pub fn new(
device: Arc<Device>,
create_info: SamplerYcbcrConversionCreateInfo,
) -> Result<Arc<SamplerYcbcrConversion>, Validated<VulkanError>> {
Self::validate_new(&device, &create_info)?;
unsafe { Ok(Self::new_unchecked(device, create_info)?) }
}
fn validate_new(
device: &Device,
create_info: &SamplerYcbcrConversionCreateInfo,
) -> Result<(), Box<ValidationError>> {
if !device.enabled_features().sampler_ycbcr_conversion {
return Err(Box::new(ValidationError {
requires_one_of: RequiresOneOf(&[RequiresAllOf(&[Requires::Feature(
"sampler_ycbcr_conversion",
)])]),
vuids: &["VUID-vkCreateSamplerYcbcrConversion-None-01648"],
..Default::default()
}));
}
create_info
.validate(device)
.map_err(|err| err.add_context("create_info"))?;
Ok(())
}
#[cfg_attr(not(feature = "document_unchecked"), doc(hidden))]
pub unsafe fn new_unchecked(
device: Arc<Device>,
create_info: SamplerYcbcrConversionCreateInfo,
) -> Result<Arc<SamplerYcbcrConversion>, VulkanError> {
let &SamplerYcbcrConversionCreateInfo {
format,
ycbcr_model,
ycbcr_range,
component_mapping,
chroma_offset,
chroma_filter,
force_explicit_reconstruction,
_ne: _,
} = &create_info;
let create_info_vk = 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_vk,
ptr::null(),
output.as_mut_ptr(),
)
.result()
.map_err(VulkanError::from)?;
output.assume_init()
};
Ok(Self::from_handle(device, handle, create_info))
}
#[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: InstanceOwnedDebugWrapper(device),
id: Self::next_id(),
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) -> 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: _,
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);
#[derive(Clone, Debug)]
pub struct SamplerYcbcrConversionCreateInfo {
pub format: 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: Format::UNDEFINED,
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(()),
}
}
}
impl SamplerYcbcrConversionCreateInfo {
pub(crate) fn validate(&self, device: &Device) -> Result<(), Box<ValidationError>> {
let &Self {
format,
ycbcr_model,
ycbcr_range,
component_mapping,
chroma_offset,
chroma_filter,
force_explicit_reconstruction,
_ne: _,
} = self;
format.validate_device(device).map_err(|err| {
err.add_context("format")
.set_vuids(&["VUID-VkSamplerYcbcrConversionCreateInfo-format-parameter"])
})?;
ycbcr_model.validate_device(device).map_err(|err| {
err.add_context("ycbcr_model")
.set_vuids(&["VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrModel-parameter"])
})?;
ycbcr_range.validate_device(device).map_err(|err| {
err.add_context("ycbcr_range")
.set_vuids(&["VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrRange-parameter"])
})?;
component_mapping
.validate(device)
.map_err(|err| err.add_context("component_mapping"))?;
for (index, offset) in chroma_offset.into_iter().enumerate() {
offset.validate_device(device).map_err(|err| {
err.add_context(format!("chroma_offset[{}]", index))
.set_vuids(&[
"VUID-VkSamplerYcbcrConversionCreateInfo-xChromaOffset-parameter",
"VUID-VkSamplerYcbcrConversionCreateInfo-yChromaOffset-parameter",
])
})?;
}
chroma_filter.validate_device(device).map_err(|err| {
err.add_context("chroma_filter")
.set_vuids(&["VUID-VkSamplerYcbcrConversionCreateInfo-chromaFilter-parameter"])
})?;
if !format
.numeric_format_color()
.map_or(false, |ty| ty == NumericFormat::UNORM)
{
return Err(Box::new(ValidationError {
context: "format".into(),
problem: "the numeric type is not `UNORM`".into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-format-04061"],
..Default::default()
}));
}
let potential_format_features = unsafe {
device
.physical_device()
.format_properties_unchecked(format)
.potential_format_features()
};
if !potential_format_features.intersects(
FormatFeatures::MIDPOINT_CHROMA_SAMPLES | FormatFeatures::COSITED_CHROMA_SAMPLES,
) {
return Err(Box::new(ValidationError {
context: "format".into(),
problem: "the potential format features do not contain \
`FormatFeatures::MIDPOINT_CHROMA_SAMPLES` or \
`FormatFeatures::COSITED_CHROMA_SAMPLES`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-format-01650"],
..Default::default()
}));
}
if let Some(chroma_sampling @ (ChromaSampling::Mode422 | ChromaSampling::Mode420)) =
format.ycbcr_chroma_sampling()
{
match chroma_sampling {
ChromaSampling::Mode420 => {
if chroma_offset.contains(&ChromaLocation::CositedEven)
&& !potential_format_features
.intersects(FormatFeatures::COSITED_CHROMA_SAMPLES)
{
return Err(Box::new(ValidationError {
problem: "`format` has both horizontal and vertical chroma \
subsampling, and \
its potential format features do not \
contain `FormatFeatures::COSITED_CHROMA_SAMPLES`, but \
`chroma_offset[0]` or `chroma_offset[1]` are \
`ChromaLocation::CositedEven`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-xChromaOffset-01651"],
..Default::default()
}));
}
if chroma_offset.contains(&ChromaLocation::Midpoint)
&& !potential_format_features
.intersects(FormatFeatures::MIDPOINT_CHROMA_SAMPLES)
{
return Err(Box::new(ValidationError {
problem: "`format` has both horizontal and vertical chroma \
subsampling, and \
its potential format features do not \
contain `FormatFeatures::MIDPOINT_CHROMA_SAMPLES`, but \
`chroma_offset[0]` or `chroma_offset[1]` are \
`ChromaLocation::Midpoint`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-xChromaOffset-01652"],
..Default::default()
}));
}
}
ChromaSampling::Mode422 => {
if chroma_offset[0] == ChromaLocation::CositedEven
&& !potential_format_features
.intersects(FormatFeatures::COSITED_CHROMA_SAMPLES)
{
return Err(Box::new(ValidationError {
problem: "`format` has horizontal chroma subsampling, and \
its potential format features do not \
contain `FormatFeatures::COSITED_CHROMA_SAMPLES`, but \
`chroma_offset[0]` is \
`ChromaLocation::CositedEven`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-xChromaOffset-01651"],
..Default::default()
}));
}
if chroma_offset[0] == ChromaLocation::Midpoint
&& !potential_format_features
.intersects(FormatFeatures::MIDPOINT_CHROMA_SAMPLES)
{
return Err(Box::new(ValidationError {
problem: "`format` has horizontal chroma subsampling, and \
its potential format features do not \
contain `FormatFeatures::MIDPOINT_CHROMA_SAMPLES`, but \
`chroma_offset[0]` is \
`ChromaLocation::Midpoint`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-xChromaOffset-01652"],
..Default::default()
}));
}
}
_ => unreachable!(),
}
if !component_mapping.g_is_identity() {
return Err(Box::new(ValidationError {
problem: "`format` has chroma subsampling, but \
`component_mapping.g` is not identity swizzled"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-components-02581"],
..Default::default()
}));
}
if !(component_mapping.a_is_identity()
|| matches!(
component_mapping.a,
ComponentSwizzle::One | ComponentSwizzle::Zero
))
{
return Err(Box::new(ValidationError {
context: "component_mapping.a".into(),
problem: "`format` has chroma subsampling, but \
`component_mapping.a` is not identity swizzled, or \
`ComponentSwizzle::One` or `ComponentSwizzle::Zero`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-components-02582"],
..Default::default()
}));
}
if !(component_mapping.r_is_identity()
|| matches!(component_mapping.r, ComponentSwizzle::Blue))
{
return Err(Box::new(ValidationError {
problem: "`format` has chroma subsampling, but \
`component_mapping.r` is not identity swizzled, or \
`ComponentSwizzle::Blue`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-components-02583"],
..Default::default()
}));
}
if !(component_mapping.b_is_identity()
|| matches!(component_mapping.b, ComponentSwizzle::Red))
{
return Err(Box::new(ValidationError {
problem: "`format` has chroma subsampling, but \
`component_mapping.b` is not identity swizzled, or \
`ComponentSwizzle::Red`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-components-02584"],
..Default::default()
}));
}
match (
component_mapping.r_is_identity(),
component_mapping.b_is_identity(),
) {
(true, false) => {
return Err(Box::new(ValidationError {
problem: "`format` has chroma subsampling, and \
`component_mapping.r` is identity swizzled, but \
`component_mapping.b` is not identity swizzled"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-components-02585"],
..Default::default()
}));
}
(false, true) => {
return Err(Box::new(ValidationError {
problem: "`format` has chroma subsampling, and \
`component_mapping.b` is identity swizzled, but \
`component_mapping.r` is not identity swizzled"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-components-02585"],
..Default::default()
}));
}
_ => (),
}
}
let components_bits = {
let bits = format.components();
component_mapping
.component_map()
.map(move |i| i.map(|i| bits[i]))
};
if ycbcr_model != SamplerYcbcrModelConversion::RgbIdentity {
if components_bits[0].map_or(true, |bits| bits == 0) {
return Err(Box::new(ValidationError {
problem: "`ycbcr_model` is not `SamplerYcbcrModelConversion::RgbIdentity`, \
and `component_mapping.r` does not map to a component that exists in \
`format`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-components-02585"],
..Default::default()
}));
}
if components_bits[1].map_or(true, |bits| bits == 0) {
return Err(Box::new(ValidationError {
problem: "`ycbcr_model` is not `SamplerYcbcrModelConversion::RgbIdentity`, \
and `component_mapping.g` does not map to a component that exists in \
`format`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-components-02585"],
..Default::default()
}));
}
if components_bits[2].map_or(true, |bits| bits == 0) {
return Err(Box::new(ValidationError {
problem: "`ycbcr_model` is not `SamplerYcbcrModelConversion::RgbIdentity`, \
and `component_mapping.b` does not map to a component that exists in \
`format`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-components-02585"],
..Default::default()
}));
}
if components_bits[3].map_or(true, |bits| bits == 0) {
return Err(Box::new(ValidationError {
problem: "`ycbcr_model` is not `SamplerYcbcrModelConversion::RgbIdentity`, \
and `component_mapping.a` does not map to a component that exists in \
`format`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-components-02585"],
..Default::default()
}));
}
}
if ycbcr_range == SamplerYcbcrRange::ItuNarrow {
if components_bits[0].map_or(false, |bits| bits < 8) {
return Err(Box::new(ValidationError {
problem: "`ycbcr_range` is `SamplerYcbcrRange::ItuNarrow`, and \
`component_mapping.r` maps to a component in `format` with less than \
8 bits"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrRange-02748"],
..Default::default()
}));
}
if components_bits[1].map_or(false, |bits| bits < 8) {
return Err(Box::new(ValidationError {
problem: "`ycbcr_range` is `SamplerYcbcrRange::ItuNarrow`, and \
`component_mapping.g` maps to a component in `format` with less than \
8 bits"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrRange-02748"],
..Default::default()
}));
}
if components_bits[2].map_or(false, |bits| bits < 8) {
return Err(Box::new(ValidationError {
problem: "`ycbcr_range` is `SamplerYcbcrRange::ItuNarrow`, and \
`component_mapping.b` maps to a component in `format` with less than \
8 bits"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrRange-02748"],
..Default::default()
}));
}
if components_bits[3].map_or(false, |bits| bits < 8) {
return Err(Box::new(ValidationError {
problem: "`ycbcr_range` is `SamplerYcbcrRange::ItuNarrow`, and \
`component_mapping.a` maps to a component in `format` with less than \
8 bits"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-ycbcrRange-02748"],
..Default::default()
}));
}
}
if force_explicit_reconstruction
&& !potential_format_features.intersects(FormatFeatures::
SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_FORCEABLE)
{
return Err(Box::new(ValidationError {
problem: "`force_explicit_reconstruction` is `true`, but the \
potential format features of `format` do not include `FormatFeatures::\
SAMPLED_IMAGE_YCBCR_CONVERSION_CHROMA_RECONSTRUCTION_EXPLICIT_FORCEABLE`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-forceExplicitReconstruction-01656"],
..Default::default()
}));
}
match chroma_filter {
Filter::Nearest => (),
Filter::Linear => {
if !potential_format_features
.intersects(FormatFeatures::SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER)
{
return Err(Box::new(ValidationError {
problem: "`chroma_filter` is `Filter::Linear`, but the \
potential format features of `format` do not include `FormatFeatures::\
SAMPLED_IMAGE_YCBCR_CONVERSION_LINEAR_FILTER`"
.into(),
vuids: &["VUID-VkSamplerYcbcrConversionCreateInfo-chromaFilter-01657"],
..Default::default()
}));
}
}
Filter::Cubic => {
return Err(Box::new(ValidationError {
context: "chroma_filter".into(),
problem: "is `Filter::Cubic`".into(),
..Default::default()
}));
}
}
Ok(())
}
}
vulkan_enum! {
#[non_exhaustive]
SamplerYcbcrModelConversion = SamplerYcbcrModelConversion(i32);
RgbIdentity = RGB_IDENTITY,
YcbcrIdentity = YCBCR_IDENTITY,
Ycbcr709 = YCBCR_709,
Ycbcr601 = YCBCR_601,
Ycbcr2020 = YCBCR_2020,
}
vulkan_enum! {
#[non_exhaustive]
SamplerYcbcrRange = SamplerYcbcrRange(i32);
ItuFull = ITU_FULL,
ItuNarrow = ITU_NARROW,
}
vulkan_enum! {
#[non_exhaustive]
ChromaLocation = ChromaLocation(i32);
CositedEven = COSITED_EVEN,
Midpoint = MIDPOINT,
}
#[cfg(test)]
mod tests {
use super::SamplerYcbcrConversion;
use crate::{Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError};
#[test]
fn feature_not_enabled() {
let (device, _queue) = gfx_dev_and_queue!();
let r = SamplerYcbcrConversion::new(device, Default::default());
match r {
Err(Validated::ValidationError(err))
if matches!(
*err,
ValidationError {
requires_one_of: RequiresOneOf([RequiresAllOf([Requires::Feature(
"sampler_ycbcr_conversion"
)])]),
..
}
) => {}
_ => panic!(),
}
}
}