From 9a7b5816fd4a4051ec68dd4a6c3310740ab4e7de Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Tue, 15 Aug 2023 22:53:10 -0300 Subject: [PATCH] Add support for video capabilities and formats --- examples/src/bin/video.rs | 56 +++++++ vulkano/src/device/physical.rs | 293 ++++++++++++++++++++++++++++++++- vulkano/src/image/usage.rs | 9 +- vulkano/src/video.rs | 259 ++++++++++++++++++++++++++--- 4 files changed, 590 insertions(+), 27 deletions(-) diff --git a/examples/src/bin/video.rs b/examples/src/bin/video.rs index c17f416e55..e93076bb83 100644 --- a/examples/src/bin/video.rs +++ b/examples/src/bin/video.rs @@ -2,7 +2,12 @@ use std::sync::Arc; use vulkano::{ device::{Device, DeviceCreateInfo, DeviceExtensions, QueueCreateInfo, QueueFlags}, + image::ImageUsage, instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, + video::{ + H264ProfileInfo, ProfileInfo, ProfileListInfo, VideoDecodeCapabilityFlags, + VideoDecodeH264PictureLayoutFlags, VideoFormatInfo, + }, VulkanLibrary, }; @@ -98,4 +103,55 @@ fn main() { "Video queue supports the following codecs: {:?}", video_properties.video_codec_operations ); + + // Video profiles are provided as input to video capability queries such as + // vkGetPhysicalDeviceVideoCapabilitiesKHR or + // vkGetPhysicalDeviceVideoFormatPropertiesKHR, as well as when creating + // resources to be used by video coding operations such as images, buffers, + // query pools, and video sessions. + // + // You must parse the bitstream to correctly construct the profile info. + // This is hardcoded for the bitstream in this example. + let profile_info = ProfileInfo { + video_codec_operation: vulkano::video::VideoCodecOperation::DecodeH264, + chroma_subsampling: vulkano::video::VideoChromaSubsampling::Type420, + luma_bit_depth: vulkano::video::VideoComponentBitDepth::Type8, + chroma_bit_depth: Some(vulkano::video::VideoComponentBitDepth::Type8), + codec_profile_info: vulkano::video::DecodeProfileInfo::H264(H264ProfileInfo { + std_profile_idc: 0, + picture_layout: VideoDecodeH264PictureLayoutFlags::PROGRESSIVE, + ..Default::default() + }), + ..Default::default() + }; + + let video_caps = physical_device + .video_capabilities(profile_info.clone()) + .unwrap(); + println!("Video capabilities: {:#?}", video_caps); + + let video_format_info = VideoFormatInfo { + image_usage: if !video_caps + .decode_capabilities + .flags + .intersects(VideoDecodeCapabilityFlags::DPB_AND_OUTPUT_COINCIDE) + { + ImageUsage::VIDEO_DECODE_DPB + } else { + ImageUsage::VIDEO_DECODE_DPB + | ImageUsage::VIDEO_DECODE_DST + | ImageUsage::TRANSFER_SRC + | ImageUsage::SAMPLED + }, + profile_list_info: ProfileListInfo { + profiles: vec![profile_info], + ..Default::default() + }, + }; + + let formats = physical_device + .video_format_properties(video_format_info) + .unwrap(); + + println!("video formats: {:#?}", formats); } diff --git a/vulkano/src/device/physical.rs b/vulkano/src/device/physical.rs index bec1a38678..f6bcc9ed35 100644 --- a/vulkano/src/device/physical.rs +++ b/vulkano/src/device/physical.rs @@ -14,8 +14,8 @@ use crate::{ device::{properties::Properties, DeviceExtensions, Features, FeaturesFfi, PropertiesFfi}, format::{DrmFormatModifierProperties, Format, FormatProperties}, image::{ - ImageAspects, ImageDrmFormatModifierInfo, ImageFormatInfo, ImageFormatProperties, - ImageUsage, SparseImageFormatInfo, SparseImageFormatProperties, + sampler::ComponentMapping, ImageAspects, ImageDrmFormatModifierInfo, ImageFormatInfo, + ImageFormatProperties, ImageUsage, SparseImageFormatInfo, SparseImageFormatProperties, }, instance::{Instance, InstanceOwned}, macros::{impl_id_counter, vulkan_bitflags, vulkan_enum}, @@ -29,9 +29,15 @@ use crate::{ semaphore::{ExternalSemaphoreInfo, ExternalSemaphoreProperties}, Sharing, }, + video::{ + ProfileInfo, VideoCapabilities, VideoCodecOperation, VideoDecodeCapabilities, + VideoDecodeCodecCapabilities, VideoDecodeH264Capabilities, VideoFormatInfo, + VideoFormatProperties, + }, DebugWrapper, ExtensionProperties, Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, Version, VulkanError, VulkanObject, }; +use ash::vk::VideoFormatPropertiesKHR; use bytemuck::cast_slice; use std::{ fmt::{Debug, Display, Error as FmtError, Formatter}, @@ -2654,6 +2660,289 @@ impl PhysicalDevice { visual_id, ) != 0 } + + pub fn video_format_properties( + &self, + video_format_info: VideoFormatInfo, + ) -> Result, Validated> { + self.validate_video_format_info(&video_format_info)?; + + unsafe { Ok(self.video_format_properties_unchecked(video_format_info)?) } + } + + fn validate_video_format_info( + &self, + video_format_info: &VideoFormatInfo, + ) -> Result<(), Box> { + if !self.supported_extensions.khr_video_queue || self.api_version() < Version::V1_3 { + return Err(Box::new(ValidationError { + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[ + Requires::DeviceExtension("khr_video_queue"), + // Requires::APIVersion(Version::V1_3), // ? + ])]), + ..Default::default() + })); + } else { + Ok(()) + } + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn video_format_properties_unchecked( + &self, + video_format_info: VideoFormatInfo, + ) -> Result, VulkanError> { + loop { + let mut num_formats = 0; + let mut h264_profile_info_vk = None; + let video_profile_info_vk = video_format_info + .profile_list_info + .profiles + .iter() + .map(|profile| { + let mut profile_info_vk = ash::vk::VideoProfileInfoKHR { + video_codec_operation: profile.video_codec_operation.into(), + chroma_subsampling: profile.chroma_subsampling.into(), + luma_bit_depth: profile.luma_bit_depth.into(), + chroma_bit_depth: if let Some(chroma_bit_depth) = profile.chroma_bit_depth { + chroma_bit_depth.into() + } else { + ash::vk::VideoComponentBitDepthFlagsKHR::INVALID + }, + ..Default::default() + }; + + match profile.video_codec_operation { + VideoCodecOperation::EncodeH264 => todo!(), + VideoCodecOperation::EncodeH265 => todo!(), + VideoCodecOperation::DecodeH264 => { + let crate::video::DecodeProfileInfo::H264(codec_profile_info) = + &profile.codec_profile_info + else { + panic!("Unexpected profile"); + }; + + let next = h264_profile_info_vk.insert( + ash::vk::VideoDecodeH264ProfileInfoKHR { + std_profile_idc: codec_profile_info.std_profile_idc, + picture_layout: codec_profile_info.picture_layout.into(), + ..Default::default() + }, + ); + + // VUID-VkVideoProfileInfoKHR-videoCodecOperation-07179 + profile_info_vk.p_next = next as *const _ as *const _; + } + VideoCodecOperation::DecodeH265 => todo!(), + } + + profile_info_vk + }) + .collect::>(); + + let profile_list_info_vk = ash::vk::VideoProfileListInfoKHR { + profile_count: video_profile_info_vk.len() as _, + p_profiles: video_profile_info_vk.as_ptr() as _, + ..Default::default() + }; + + let video_format_info_vk = ash::vk::PhysicalDeviceVideoFormatInfoKHR { + p_next: &profile_list_info_vk as *const _ as _, + image_usage: video_format_info.image_usage.into(), + ..Default::default() + }; + + let fns = self.instance().fns(); + + (fns.khr_video_queue + .get_physical_device_video_format_properties_khr)( + self.handle(), + &video_format_info_vk, + &mut num_formats, + std::ptr::null_mut(), + ) + .result() + .map_err(VulkanError::from)?; + + let mut video_format_properties_vk = + vec![VideoFormatPropertiesKHR::default(); num_formats as usize]; + + let result = (fns + .khr_video_queue + .get_physical_device_video_format_properties_khr)( + self.handle(), + &video_format_info_vk, + &mut num_formats, + video_format_properties_vk.as_mut_ptr(), + ); + + match result { + ash::vk::Result::SUCCESS => { + video_format_properties_vk.set_len(num_formats as usize); + return Ok(video_format_properties_vk + .into_iter() + .filter_map(|vk| { + Some(VideoFormatProperties { + format: vk.format.try_into().ok()?, + component_mapping: ComponentMapping { + r: vk.component_mapping.r.try_into().ok()?, + g: vk.component_mapping.g.try_into().ok()?, + b: vk.component_mapping.b.try_into().ok()?, + a: vk.component_mapping.a.try_into().ok()?, + }, + image_create_flags: vk.image_create_flags.try_into().ok()?, + image_type: vk.image_type.try_into().ok()?, + image_tiling: vk.image_tiling.try_into().ok()?, + image_usage_flags: vk.image_usage_flags.try_into().ok()?, + }) + }) + .collect()); + } + ash::vk::Result::INCOMPLETE => (), + err => return Err(VulkanError::from(err)), + } + } + } + + pub fn video_capabilities( + &self, + profile_info: ProfileInfo, + ) -> Result> { + self.validate_video_capabilities(&profile_info)?; + + unsafe { Ok(self.video_capabilities_unchecked(profile_info)?) } + } + + fn validate_video_capabilities( + &self, + profile_info: &ProfileInfo, + ) -> Result<(), Box> { + if !self.supported_extensions.khr_video_queue || self.api_version() < Version::V1_3 { + return Err(Box::new(ValidationError { + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[ + Requires::DeviceExtension("khr_video_queue"), + // Requires::APIVersion(Version::V1_3), // ? + ])]), + ..Default::default() + })); + } + + profile_info + .validate() + .map_err(|err| err.add_context("profile_info")) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn video_capabilities_unchecked( + &self, + profile_info: ProfileInfo, + ) -> Result { + let mut video_capabilities = ash::vk::VideoCapabilitiesKHR::default(); + let mut decode_capabilities = ash::vk::VideoDecodeCapabilitiesKHR::default(); + let mut h264_decode_capabilities = None; + let mut h264_profile_info = None; + + let ProfileInfo { + video_codec_operation, + chroma_subsampling, + luma_bit_depth, + chroma_bit_depth, + codec_profile_info, + _ne: _, + } = profile_info; + + let mut profile_info = ash::vk::VideoProfileInfoKHR { + video_codec_operation: video_codec_operation.into(), + chroma_subsampling: chroma_subsampling.into(), + luma_bit_depth: luma_bit_depth.into(), + chroma_bit_depth: if let Some(chroma_bit_depth) = chroma_bit_depth { + chroma_bit_depth.into() + } else { + ash::vk::VideoComponentBitDepthFlagsKHR::INVALID + }, + ..Default::default() + }; + + match video_codec_operation { + VideoCodecOperation::EncodeH264 => todo!(), + VideoCodecOperation::EncodeH265 => todo!(), + VideoCodecOperation::DecodeH264 => { + let h264_decode_capabilities = h264_decode_capabilities + .insert(ash::vk::VideoDecodeH264CapabilitiesKHR::default()); + + let codec_profile_info = match codec_profile_info { + crate::video::DecodeProfileInfo::H264(p) => p, + _ => panic!("invalid profile info for H264"), + }; + + decode_capabilities.p_next = h264_decode_capabilities as *mut _ as *mut _; + video_capabilities.p_next = &mut decode_capabilities as *mut _ as *mut _; + + let codec_profile_info = + h264_profile_info.insert(ash::vk::VideoDecodeH264ProfileInfoKHR { + std_profile_idc: codec_profile_info.std_profile_idc, + picture_layout: codec_profile_info.picture_layout.into(), + ..Default::default() + }); + + // VUID-VkVideoProfileInfoKHR-videoCodecOperation-07179 + profile_info.p_next = codec_profile_info as *const _ as *const _; + } + VideoCodecOperation::DecodeH265 => todo!(), + } + + let fns = self.instance().fns(); + (fns.khr_video_queue + .get_physical_device_video_capabilities_khr)( + self.handle(), + &mut profile_info, + &mut video_capabilities, + ) + .result() + .map_err(VulkanError::from)?; + + Ok(VideoCapabilities { + flags: video_capabilities.flags.into(), + min_bitstream_buffer_offset_alignment: video_capabilities + .min_bitstream_buffer_offset_alignment, + min_bitstream_buffer_size_alignment: video_capabilities + .min_bitstream_buffer_size_alignment, + picture_access_granularity: [ + video_capabilities.picture_access_granularity.width, + video_capabilities.picture_access_granularity.height, + ], + min_coded_extent: [ + video_capabilities.min_coded_extent.width, + video_capabilities.min_coded_extent.height, + ], + max_coded_extent: [ + video_capabilities.max_coded_extent.width, + video_capabilities.max_coded_extent.height, + ], + max_dpb_slots: video_capabilities.max_dpb_slots, + max_active_reference_pictures: video_capabilities.max_active_reference_pictures, + std_header_version: video_capabilities.std_header_version.into(), + decode_capabilities: VideoDecodeCapabilities { + flags: decode_capabilities.flags.into(), + codec_capabilities: match video_codec_operation { + VideoCodecOperation::DecodeH264 => { + let h264_decode_capabilities = h264_decode_capabilities.unwrap(); + VideoDecodeCodecCapabilities::H264(VideoDecodeH264Capabilities { + max_level_idc: h264_decode_capabilities.max_level_idc, + field_offset_granularity: [ + h264_decode_capabilities.field_offset_granularity.x, + h264_decode_capabilities.field_offset_granularity.y, + ], + _ne: crate::NonExhaustive(()), + }) + } + _ => unimplemented!(), + }, + _ne: crate::NonExhaustive(()), + }, + _ne: crate::NonExhaustive(()), + }) + } } impl Debug for PhysicalDevice { diff --git a/vulkano/src/image/usage.rs b/vulkano/src/image/usage.rs index e62ea0dced..86284c2fcd 100644 --- a/vulkano/src/image/usage.rs +++ b/vulkano/src/image/usage.rs @@ -49,26 +49,23 @@ vulkan_bitflags! { /// The image can be used as an input attachment in a render pass/framebuffer. INPUT_ATTACHMENT = INPUT_ATTACHMENT, - /* TODO: enable // TODO: document VIDEO_DECODE_DST = VIDEO_DECODE_DST_KHR RequiresOneOf([ RequiresAllOf([DeviceExtension(khr_video_decode_queue)]), - ]),*/ + ]), - /* TODO: enable // TODO: document VIDEO_DECODE_SRC = VIDEO_DECODE_SRC_KHR RequiresOneOf([ RequiresAllOf([DeviceExtension(khr_video_decode_queue)]), - ]),*/ + ]), - /* TODO: enable // TODO: document VIDEO_DECODE_DPB = VIDEO_DECODE_DPB_KHR RequiresOneOf([ RequiresAllOf([DeviceExtension(khr_video_decode_queue)]), - ]),*/ + ]), /* TODO: enable // TODO: document diff --git a/vulkano/src/video.rs b/vulkano/src/video.rs index 48a50177ac..a810908ff0 100644 --- a/vulkano/src/video.rs +++ b/vulkano/src/video.rs @@ -7,42 +7,263 @@ // notice may not be copied, modified, or distributed except // according to those terms. -use crate::macros::vulkan_bitflags; +use ash::vk::{ + native::{StdVideoH264LevelIdc, StdVideoH264ProfileIdc}, + DeviceSize, +}; -vulkan_bitflags! { +use crate::{ + format::Format, + image::{sampler::ComponentMapping, ImageCreateFlags, ImageTiling, ImageType, ImageUsage}, + macros::{vulkan_bitflags, vulkan_bitflags_enum}, + ExtensionProperties, ValidationError, +}; + +vulkan_bitflags_enum! { #[non_exhaustive] /// The type of video coding operation and video compression standard used /// by a video profile - VideoCodecOperations = VideoCodecOperationFlagsKHR(u32); + VideoCodecOperations, - /// Indicates no support for any video codec operations. - NONE = NONE - RequiresOneOf([ - RequiresAllOf([DeviceExtension(khr_video_decode_queue)]), - ]), + VideoCodecOperation, + + = VideoCodecOperationFlagsKHR(u32); /// Specifies support for H.264 video encode operations - ENCODE_H264 = ENCODE_H264_EXT + ENCODE_H264, EncodeH264 = ENCODE_H264_EXT RequiresOneOf([ - RequiresAllOf([DeviceExtension(khr_video_decode_queue)]), - ]), + RequiresAllOf([DeviceExtension(khr_video_encode_queue)]), + RequiresAllOf([DeviceExtension(ext_video_encode_h264)])] + ), /// Specifies support for H.265 video encode operations - ENCODE_H265 = ENCODE_H265_EXT + ENCODE_H265, EncodeH265 = ENCODE_H265_EXT RequiresOneOf([ - RequiresAllOf([DeviceExtension(khr_video_decode_queue)]), - ]), + RequiresAllOf([DeviceExtension(khr_video_encode_queue)]), + RequiresAllOf([DeviceExtension(ext_video_encode_h265)])] + ), /// Specifies support for H.264 video decode operations - DECODE_H264 = DECODE_H264 + DECODE_H264, DecodeH264 = DECODE_H264 RequiresOneOf([ RequiresAllOf([DeviceExtension(khr_video_decode_queue)]), - ]), + RequiresAllOf([DeviceExtension(khr_video_decode_h264)])] + ), /// Specifies support for H.265 video decode operations - DECODE_H265 = DECODE_H265 + DECODE_H265, DecodeH265 = DECODE_H265 RequiresOneOf([ - RequiresAllOf([DeviceExtension(khr_video_decode_queue)]), - ]), + RequiresAllOf([DeviceExtension(khr_video_encode_queue)]), + RequiresAllOf([DeviceExtension(khr_video_decode_h265)])] + ), +} + +vulkan_bitflags_enum! { + #[non_exhaustive] + + VideoChromaSubsamplings, + + VideoChromaSubsampling, + + = VideoChromaSubsamplingFlagsKHR(u32); + + /// Specifies that the format is monochrome. + MONOCHROME, Monochrome = MONOCHROME, + /// Specified that the format is 4:2:0 chroma subsampled, i.e. the two + /// chroma components are sampled horizontally and vertically at half the + /// sample rate of the luma component. + TYPE_420, Type420 = TYPE_420, + /// The format is 4:2:2 chroma subsampled, i.e. the two chroma components + /// are sampled horizontally at half the sample rate of luma component. + TYPE_422, Type422 = TYPE_422, + /// The format is 4:4:4 chroma sampled, i.e. all three components of the + /// Y′CBCR format are sampled at the same rate, thus there is no chroma + /// subsampling. + TYPE_444, Type444 = TYPE_444, +} + +vulkan_bitflags_enum! { + #[non_exhaustive] + + VideoComponentBitDepths, + + VideoComponentBitDepth, + + = VideoComponentBitDepthFlagsKHR(u32); + + /// Specifies a component bit depth of 8 bits. + TYPE_8, Type8 = TYPE_8, + /// Specifies a component bit depth of 10 bits. + TYPE_10, Type10 = TYPE_10, + /// Specifies a component bit depth of 12 bits. + TYPE_12, Type12 = TYPE_12, +} + +vulkan_bitflags! { + #[non_exhaustive] + + VideoCapabilityFlags = VideoCapabilityFlagsKHR(u32); + + PROTECTED_CONTENT = PROTECTED_CONTENT, + SEPARATE_REFERENCE_IMAGES = SEPARATE_REFERENCE_IMAGES, +} + +vulkan_bitflags! { + #[non_exhaustive] + + VideoDecodeCapabilityFlags = VideoDecodeCapabilityFlagsKHR(u32); + + DPB_AND_OUTPUT_COINCIDE = DPB_AND_OUTPUT_COINCIDE, + DPB_AND_OUTPUT_DISTINCT = DPB_AND_OUTPUT_DISTINCT, +} + +vulkan_bitflags! { + #[non_exhaustive] + + VideoDecodeH264PictureLayoutFlags = VideoDecodeH264PictureLayoutFlagsKHR(u32); + + PROGRESSIVE = PROGRESSIVE, + INTERLACED_INTERLEAVED_LINES_BIT_KHR = INTERLACED_INTERLEAVED_LINES, + INTERLACED_SEPARATE_PLANES_BIT_KHR = INTERLACED_SEPARATE_PLANES, +} + +#[derive(Clone, Debug)] +pub struct ProfileInfo { + pub video_codec_operation: VideoCodecOperation, + pub chroma_subsampling: VideoChromaSubsampling, + pub luma_bit_depth: VideoComponentBitDepth, + pub chroma_bit_depth: Option, + pub codec_profile_info: DecodeProfileInfo, + pub _ne: crate::NonExhaustive, +} + +impl ProfileInfo { + pub(crate) fn validate(&self) -> Result<(), Box> { + let &Self { + chroma_subsampling, + chroma_bit_depth, + _ne, + .. + } = self; + + if !matches!(chroma_subsampling, VideoChromaSubsampling::Monochrome) + && chroma_bit_depth.is_none() + { + return Err(Box::new(ValidationError { + context: "chroma_bit_depth".into(), + problem: "is `Invalid`".into(), + vuids: &["VUID-VkVideoProfileInfoKHR-chromaSubsampling-07015"], + ..Default::default() + })); + } + + Ok(()) + } +} + +impl Default for ProfileInfo { + fn default() -> Self { + Self { + video_codec_operation: VideoCodecOperation::EncodeH265, + chroma_subsampling: VideoChromaSubsampling::Monochrome, + luma_bit_depth: VideoComponentBitDepth::Type8, + chroma_bit_depth: Some(VideoComponentBitDepth::Type8), + codec_profile_info: DecodeProfileInfo::H264(H264ProfileInfo { + std_profile_idc: 66, + picture_layout: VideoDecodeH264PictureLayoutFlags::PROGRESSIVE, + _ne: crate::NonExhaustive(()), + }), + _ne: crate::NonExhaustive(()), + } + } +} + +#[derive(Clone, Debug)] +pub struct H264ProfileInfo { + pub std_profile_idc: StdVideoH264ProfileIdc, + pub picture_layout: VideoDecodeH264PictureLayoutFlags, + pub _ne: crate::NonExhaustive, +} + +impl Default for H264ProfileInfo { + fn default() -> Self { + Self { + std_profile_idc: Default::default(), + picture_layout: Default::default(), + _ne: crate::NonExhaustive(()), + } + } +} + +#[derive(Clone, Debug)] +pub enum DecodeProfileInfo { + H264(H264ProfileInfo), +} + +#[derive(Clone, Debug)] +pub struct VideoCapabilities { + pub flags: VideoCapabilityFlags, + pub min_bitstream_buffer_offset_alignment: DeviceSize, + pub min_bitstream_buffer_size_alignment: DeviceSize, + pub picture_access_granularity: [u32; 2], + pub min_coded_extent: [u32; 2], + pub max_coded_extent: [u32; 2], + pub max_dpb_slots: u32, + pub max_active_reference_pictures: u32, + pub std_header_version: ExtensionProperties, + pub decode_capabilities: VideoDecodeCapabilities, + pub _ne: crate::NonExhaustive, +} + +#[derive(Clone, Debug)] +pub struct VideoDecodeCapabilities { + pub flags: VideoDecodeCapabilityFlags, + pub codec_capabilities: VideoDecodeCodecCapabilities, + pub _ne: crate::NonExhaustive, +} + +#[derive(Clone, Debug)] +#[non_exhaustive] +pub enum VideoDecodeCodecCapabilities { + H264(VideoDecodeH264Capabilities), + /* todo */ +} + +#[derive(Clone, Debug)] +pub struct VideoDecodeH264Capabilities { + pub max_level_idc: StdVideoH264LevelIdc, + pub field_offset_granularity: [i32; 2], + pub _ne: crate::NonExhaustive, +} + +#[derive(Clone, Debug)] +pub struct ProfileListInfo { + pub profiles: Vec, + pub _ne: crate::NonExhaustive, +} + +impl Default for ProfileListInfo { + fn default() -> Self { + Self { + profiles: Default::default(), + _ne: crate::NonExhaustive(()), + } + } +} + +#[derive(Clone, Debug)] +pub struct VideoFormatInfo { + pub image_usage: ImageUsage, + pub profile_list_info: ProfileListInfo, +} + +#[derive(Clone, Debug)] +pub struct VideoFormatProperties { + pub format: Format, + pub component_mapping: ComponentMapping, + pub image_create_flags: ImageCreateFlags, + pub image_type: ImageType, + pub image_tiling: ImageTiling, + pub image_usage_flags: ImageUsage, }