From be387657cb5ec3ad692a824e9107e3725bf25577 Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Tue, 8 Aug 2023 11:34:01 -0300 Subject: [PATCH 01/12] Add the video module This will house the video data structures. --- vulkano/src/lib.rs | 1 + vulkano/src/video.rs | 8 ++++++++ 2 files changed, 9 insertions(+) create mode 100644 vulkano/src/video.rs diff --git a/vulkano/src/lib.rs b/vulkano/src/lib.rs index 328392cca8..47cd76db3a 100644 --- a/vulkano/src/lib.rs +++ b/vulkano/src/lib.rs @@ -198,6 +198,7 @@ pub mod range_set; pub mod shader; pub mod swapchain; pub mod sync; +pub mod video; /// Represents memory size and offset values on a Vulkan device. /// Analogous to the Rust `usize` type on the host. diff --git a/vulkano/src/video.rs b/vulkano/src/video.rs new file mode 100644 index 0000000000..b63aaafa1b --- /dev/null +++ b/vulkano/src/video.rs @@ -0,0 +1,8 @@ +// Copyright (c) 2023 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. From ba997d308150f8bcc65ee90dc0d5d1a6b03de977 Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Tue, 8 Aug 2023 11:15:00 -0300 Subject: [PATCH 02/12] Add support to video queues This adds support to QueueFamilyVideoPropertiesKHR, and in particular lets us know which codecs are supported through the contained VideoCodecOperationFlagsKHR. --- vulkano/src/device/physical.rs | 28 +++++++++++++++++++++++++--- vulkano/src/device/queue.rs | 23 +++++++++++++++++++++++ vulkano/src/video.rs | 28 ++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 3 deletions(-) diff --git a/vulkano/src/device/physical.rs b/vulkano/src/device/physical.rs index 5aca32629c..801084bdb1 100644 --- a/vulkano/src/device/physical.rs +++ b/vulkano/src/device/physical.rs @@ -125,7 +125,8 @@ impl PhysicalDevice { properties = Self::get_properties2(handle, &instance, api_version, &supported_extensions); memory_properties = Self::get_memory_properties2(handle, &instance); - queue_family_properties = Self::get_queue_family_properties2(handle, &instance); + queue_family_properties = + Self::get_queue_family_properties2(handle, &instance, &supported_extensions); } else { supported_features = Self::get_features(handle, &instance); properties = @@ -333,8 +334,9 @@ impl PhysicalDevice { unsafe fn get_queue_family_properties2( handle: ash::vk::PhysicalDevice, instance: &Instance, + supported_extensions: &DeviceExtensions, ) -> Vec { - let mut num = 0; + let mut num: u32 = 0; let fns = instance.fns(); if instance.api_version() >= Version::V1_1 { @@ -352,8 +354,17 @@ impl PhysicalDevice { ); } + let mut queue_family_video_properties_vk = vec![None; num as usize]; let mut output = vec![ash::vk::QueueFamilyProperties2::default(); num as usize]; + if supported_extensions.khr_video_decode_queue { + for i in 0..output.len() { + let next = queue_family_video_properties_vk[i] + .insert(ash::vk::QueueFamilyVideoPropertiesKHR::default()); + output[i].p_next = next as *mut _ as *mut _; + } + } + if instance.api_version() >= Version::V1_1 { (fns.v1_1.get_physical_device_queue_family_properties2)( handle, @@ -371,7 +382,18 @@ impl PhysicalDevice { output .into_iter() - .map(|family| family.queue_family_properties.into()) + .enumerate() + .map(|(index, family)| { + let mut queue_family_properties: QueueFamilyProperties = + family.queue_family_properties.into(); + if let Some(queue_family_video_properties_vk) = + queue_family_video_properties_vk[index] + { + queue_family_properties.video_properties = + Some(queue_family_video_properties_vk.into()); + } + queue_family_properties + }) .collect() } diff --git a/vulkano/src/device/queue.rs b/vulkano/src/device/queue.rs index ddb6f7a5cc..16bbe743ba 100644 --- a/vulkano/src/device/queue.rs +++ b/vulkano/src/device/queue.rs @@ -26,6 +26,7 @@ use crate::{ future::{AccessCheckError, GpuFuture}, semaphore::SemaphoreState, }, + video::VideoCodecOperations, Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, Version, VulkanError, VulkanObject, }; @@ -1673,6 +1674,27 @@ pub struct QueueFamilyProperties { /// The minimum granularity supported for image transfers, in terms of `[width, height, depth]`. pub min_image_transfer_granularity: [u32; 3], + + /// The properties of the video queue family. + /// + /// This will be empty if the [`khr_video_decode_queue`] extension is not supported + /// by the physical device. + /// + /// [`khr_video_decode_queue`]: crate::device::DeviceExtensions::khr_video_decode_queue + pub video_properties: Option, +} + +#[derive(Clone, Debug)] +pub struct QueueFamilyVideoProperties { + pub video_codec_operations: VideoCodecOperations, +} + +impl From for QueueFamilyVideoProperties { + fn from(value: ash::vk::QueueFamilyVideoPropertiesKHR) -> Self { + Self { + video_codec_operations: value.video_codec_operations.into(), + } + } } impl From for QueueFamilyProperties { @@ -1688,6 +1710,7 @@ impl From for QueueFamilyProperties { val.min_image_transfer_granularity.height, val.min_image_transfer_granularity.depth, ], + video_properties: Default::default(), } } } diff --git a/vulkano/src/video.rs b/vulkano/src/video.rs index b63aaafa1b..1e2b2982a2 100644 --- a/vulkano/src/video.rs +++ b/vulkano/src/video.rs @@ -6,3 +6,31 @@ // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. + +use crate::macros::vulkan_bitflags; + +vulkan_bitflags_enum! { + #[non_exhaustive] + + /// The type of video coding operation and video compression standard used + /// by a video profile + VideoCodecOperations, + + VideoCodecOperation, + + = VideoCodecOperationFlagsKHR(u32); + + /// Specifies support for H.264 video decode operations + 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, DecodeH265 = DECODE_H265 + RequiresOneOf([ + RequiresAllOf([DeviceExtension(khr_video_decode_queue)]), + RequiresAllOf([DeviceExtension(khr_video_decode_h265)])] + ), +} From 230dc1b4e528b2fbac9a999a0d5496d718739832 Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Tue, 8 Aug 2023 11:19:26 -0300 Subject: [PATCH 03/12] Add video example This example illustrates the use of Vulkan Video in Vulkano. --- examples/src/bin/video.rs | 101 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 examples/src/bin/video.rs diff --git a/examples/src/bin/video.rs b/examples/src/bin/video.rs new file mode 100644 index 0000000000..c17f416e55 --- /dev/null +++ b/examples/src/bin/video.rs @@ -0,0 +1,101 @@ +use std::sync::Arc; + +use vulkano::{ + device::{Device, DeviceCreateInfo, DeviceExtensions, QueueCreateInfo, QueueFlags}, + instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, + VulkanLibrary, +}; + +fn main() { + let library = VulkanLibrary::new().unwrap(); + + let instance = Instance::new( + library, + InstanceCreateInfo { + // Enable enumerating devices that use non-conformant Vulkan implementations. + // (e.g. MoltenVK) + flags: InstanceCreateFlags::ENUMERATE_PORTABILITY, + ..Default::default() + }, + ) + .unwrap(); + + let device_extensions = DeviceExtensions { + khr_video_queue: true, + khr_video_decode_queue: true, + khr_video_decode_h264: true, + ..DeviceExtensions::empty() + }; + + let (physical_device, graphics_queue_family_index, video_queue_family_index) = instance + .enumerate_physical_devices() + .unwrap() + .filter(|p| p.supported_extensions().contains(&device_extensions)) + .filter_map(|p| { + let graphic_pos = p + .queue_family_properties() + .iter() + .position(|q| q.queue_flags.intersects(QueueFlags::GRAPHICS)); + let video_pos = p + .queue_family_properties() + .iter() + .position(|q| q.queue_flags.intersects(QueueFlags::VIDEO_DECODE)); + graphic_pos + .zip(video_pos) + .map(|(g, v)| (p, g as u32, v as u32)) + }) + .next() + .expect("no suitable physical device found"); + + // Some little debug infos. + println!( + "Using device: {} (type: {:?})", + physical_device.properties().device_name, + physical_device.properties().device_type, + ); + + let (device, mut queues) = Device::new( + Arc::clone(&physical_device), + DeviceCreateInfo { + enabled_extensions: device_extensions, + queue_create_infos: if video_queue_family_index == graphics_queue_family_index { + vec![QueueCreateInfo { + queue_family_index: graphics_queue_family_index, + ..Default::default() + }] + } else { + vec![ + QueueCreateInfo { + queue_family_index: graphics_queue_family_index, + ..Default::default() + }, + QueueCreateInfo { + queue_family_index: video_queue_family_index, + ..Default::default() + }, + ] + }, + + ..Default::default() + }, + ) + .unwrap(); + + let (graphics_queue, video_queue) = if graphics_queue_family_index == video_queue_family_index { + let queue = queues.next().unwrap(); + (Arc::clone(&queue), Arc::clone(&queue)) + } else { + (queues.next().unwrap(), queues.next().unwrap()) + }; + + let video_properties = physical_device.queue_family_properties() + [video_queue_family_index as usize] + .video_properties + .as_ref() + .unwrap(); + + println!( + "Video queue supports the following codecs: {:?}", + video_properties.video_codec_operations + ); +} From a493758ad385dae8b649c828d3e941255ab67bc6 Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Tue, 15 Aug 2023 22:53:10 -0300 Subject: [PATCH 04/12] Add support for video capabilities and formats --- examples/src/bin/video.rs | 59 +++++ vulkano/src/device/physical.rs | 223 ++++++++++++++++++- vulkano/src/image/usage.rs | 9 +- vulkano/src/video.rs | 385 ++++++++++++++++++++++++++++++++- 4 files changed, 667 insertions(+), 9 deletions(-) diff --git a/examples/src/bin/video.rs b/examples/src/bin/video.rs index c17f416e55..2e957b98e7 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::{ + CodecCapabilities, VideoDecodeCapabilityFlags, VideoDecodeH264PictureLayoutFlags, + VideoDecodeH264ProfileInfo, VideoFormatInfo, VideoProfileInfo, VideoProfileListInfo, + }, VulkanLibrary, }; @@ -98,4 +103,58 @@ 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 = VideoProfileInfo { + 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::VideoDecodeProfileInfo::H264( + VideoDecodeH264ProfileInfo { + 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 CodecCapabilities::VideoDecode(video_decode_caps) = video_caps.codec_capabilities; + + let video_format_info = VideoFormatInfo { + image_usage: if !video_decode_caps + .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: VideoProfileListInfo { + profiles: vec![profile_info.clone()], + ..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 801084bdb1..fb97443254 100644 --- a/vulkano/src/device/physical.rs +++ b/vulkano/src/device/physical.rs @@ -15,8 +15,8 @@ use crate::{ display::{Display, DisplayPlaneProperties, DisplayPlanePropertiesRaw, DisplayProperties}, format::{DrmFormatModifierProperties, Format, FormatProperties}, image::{ - ImageDrmFormatModifierInfo, ImageFormatInfo, ImageFormatProperties, ImageUsage, - SparseImageFormatInfo, SparseImageFormatProperties, + sampler::ComponentMapping, ImageDrmFormatModifierInfo, ImageFormatInfo, + ImageFormatProperties, ImageUsage, SparseImageFormatInfo, SparseImageFormatProperties, }, instance::{Instance, InstanceOwned}, macros::{impl_id_counter, vulkan_bitflags, vulkan_enum}, @@ -30,9 +30,15 @@ use crate::{ semaphore::{ExternalSemaphoreInfo, ExternalSemaphoreProperties}, Sharing, }, + video::{ + CodecCapabilities, VideoCapabilities, VideoCodecOperation, VideoDecodeCapabilities, + VideoDecodeCodecCapabilities, VideoDecodeH264Capabilities, VideoDecodeH264ProfileInfo, + VideoFormatInfo, VideoFormatProperties, VideoProfileInfo, + }, DebugWrapper, ExtensionProperties, Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, Version, VulkanError, VulkanObject, }; +use ash::vk::VideoFormatPropertiesKHR; use bytemuck::cast_slice; use parking_lot::RwLock; use std::{ @@ -3106,6 +3112,219 @@ 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 { + 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 video_profile_list_info_vk = None; + let mut video_profile_info_vk = vec![]; + let mut video_decode_profile_info_vk = None; + + let video_format_info_vk = video_format_info.to_vulkan( + &mut video_profile_list_info_vk, + &mut video_profile_info_vk, + &mut video_decode_profile_info_vk, + ); + + 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, + video_profile: VideoProfileInfo, + ) -> Result> { + self.validate_video_capabilities(&video_profile)?; + + unsafe { Ok(self.video_capabilities_unchecked(video_profile)?) } + } + + fn validate_video_capabilities( + &self, + video_profile: &VideoProfileInfo, + ) -> 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() + })); + } + + video_profile + .validate() + .map_err(|err| err.add_context("profile_info")) + } + + #[cfg_attr(not(feature = "document_unchecked"), doc(hidden))] + pub unsafe fn video_capabilities_unchecked( + &self, + video_profile: VideoProfileInfo, + ) -> Result { + let mut video_decode_capabilities_vk = None; + let mut video_decode_capabilities_next_vk = None; + let mut video_decode_profile_info_next_vk = None; + + let VideoProfileInfo { + video_codec_operation, + .. + } = video_profile; + + let mut video_capabilities_vk = video_profile.to_vulkan_video_capabilities( + &mut video_decode_capabilities_vk, + &mut video_decode_capabilities_next_vk, + ); + + let mut video_profile_info_vk = + video_profile.to_vulkan(&mut video_decode_profile_info_next_vk); + + let fns = self.instance().fns(); + (fns.khr_video_queue + .get_physical_device_video_capabilities_khr)( + self.handle(), + &mut video_profile_info_vk, + &mut video_capabilities_vk, + ) + .result() + .map_err(VulkanError::from)?; + + Ok(VideoCapabilities { + flags: video_capabilities_vk.flags.into(), + min_bitstream_buffer_offset_alignment: video_capabilities_vk + .min_bitstream_buffer_offset_alignment, + min_bitstream_buffer_size_alignment: video_capabilities_vk + .min_bitstream_buffer_size_alignment, + picture_access_granularity: [ + video_capabilities_vk.picture_access_granularity.width, + video_capabilities_vk.picture_access_granularity.height, + ], + min_coded_extent: [ + video_capabilities_vk.min_coded_extent.width, + video_capabilities_vk.min_coded_extent.height, + ], + max_coded_extent: [ + video_capabilities_vk.max_coded_extent.width, + video_capabilities_vk.max_coded_extent.height, + ], + max_dpb_slots: video_capabilities_vk.max_dpb_slots, + max_active_reference_pictures: video_capabilities_vk.max_active_reference_pictures, + std_header_version: video_capabilities_vk.std_header_version.into(), + codec_capabilities: match video_codec_operation { + VideoCodecOperation::DecodeH264 | VideoCodecOperation::DecodeH265 => { + let video_decode_capabilities_vk = video_decode_capabilities_vk.unwrap(); + + CodecCapabilities::VideoDecode(VideoDecodeCapabilities { + flags: video_decode_capabilities_vk.flags.into(), + codec_capabilities: match video_codec_operation { + VideoCodecOperation::DecodeH264 => { + let video_decode_h264_capabilities_vk = + match video_decode_capabilities_next_vk.unwrap() { + crate::video::VideoDecodeCapabilitiesNextVk::H264(v) => v, + _ => panic!("Unexpected video decode capabilities"), + }; + + VideoDecodeCodecCapabilities::H264(VideoDecodeH264Capabilities { + max_level_idc: video_decode_h264_capabilities_vk.max_level_idc, + field_offset_granularity: [ + video_decode_h264_capabilities_vk + .field_offset_granularity + .x, + video_decode_h264_capabilities_vk + .field_offset_granularity + .y, + ], + _ne: crate::NonExhaustive(()), + }) + } + VideoCodecOperation::DecodeH265 => 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 1e2b2982a2..47b7f8d871 100644 --- a/vulkano/src/video.rs +++ b/vulkano/src/video.rs @@ -7,7 +7,17 @@ // 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, +}; + +use crate::{ + format::Format, + image::{sampler::ComponentMapping, ImageCreateFlags, ImageTiling, ImageType, ImageUsage}, + macros::{vulkan_bitflags, vulkan_bitflags_enum}, + ExtensionProperties, ValidationError, +}; vulkan_bitflags_enum! { #[non_exhaustive] @@ -34,3 +44,376 @@ vulkan_bitflags_enum! { 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, +} + +pub enum VideoDecodeProfileInfoNextVk { + H264(ash::vk::VideoDecodeH264ProfileInfoKHR), + H265(ash::vk::VideoDecodeH265ProfileInfoKHR), +} + +pub enum VideoDecodeCapabilitiesNextVk { + H264(ash::vk::VideoDecodeH264CapabilitiesKHR), + H265(ash::vk::VideoDecodeH265CapabilitiesKHR), +} + +#[derive(Clone, Debug)] +pub struct VideoProfileInfo { + pub video_codec_operation: VideoCodecOperation, + pub chroma_subsampling: VideoChromaSubsampling, + pub luma_bit_depth: VideoComponentBitDepth, + pub chroma_bit_depth: Option, + pub codec_profile_info: VideoDecodeProfileInfo, + pub _ne: crate::NonExhaustive, +} + +impl VideoProfileInfo { + 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(()) + } + + /// Safety: `ash::vk::VideoProfileInfoKHR` is only valid so long as the + /// arguments to this function are not moved or dropped. + pub(crate) unsafe fn to_vulkan( + self, + video_decode_profile_info_next_vk: &mut Option, + ) -> ash::vk::VideoProfileInfoKHR { + let VideoProfileInfo { + video_codec_operation, + chroma_subsampling, + luma_bit_depth, + chroma_bit_depth, + codec_profile_info, + _ne: _, + } = self; + + let mut video_profile_info_vk = 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::DecodeH264 => { + let video_decode_h264_profile_info = match codec_profile_info { + VideoDecodeProfileInfo::H264(p) => p, + _ => panic!("Wrong codec profile info type for H264"), + }; + + let video_decode_h264_profile_info = video_decode_profile_info_next_vk.insert( + VideoDecodeProfileInfoNextVk::H264(ash::vk::VideoDecodeH264ProfileInfoKHR { + std_profile_idc: video_decode_h264_profile_info.std_profile_idc, + picture_layout: video_decode_h264_profile_info.picture_layout.into(), + ..Default::default() + }), + ); + + let video_decode_h264_profile_info = match video_decode_h264_profile_info { + VideoDecodeProfileInfoNextVk::H264(v) => v, + VideoDecodeProfileInfoNextVk::H265(_) => unreachable!(), + }; + + // VUID-VkVideoProfileInfoKHR-videoCodecOperation-07179 + video_profile_info_vk.p_next = + video_decode_h264_profile_info as *const _ as *const _; + } + VideoCodecOperation::DecodeH265 => todo!(), + } + + video_profile_info_vk + } + + pub(crate) unsafe fn to_vulkan_video_capabilities( + &self, + video_decode_capabilities_vk: &mut Option, + video_decode_capabilities_next_vk: &mut Option, + ) -> ash::vk::VideoCapabilitiesKHR { + let mut video_capabilities_vk = ash::vk::VideoCapabilitiesKHR::default(); + + let VideoProfileInfo { + video_codec_operation, + .. + } = self; + + let specifies_decode_operation = match video_codec_operation { + VideoCodecOperation::DecodeH264 | VideoCodecOperation::DecodeH265 => true, + }; + + if specifies_decode_operation { + let video_decode_capabilities_vk = + video_decode_capabilities_vk.insert(ash::vk::VideoDecodeCapabilitiesKHR::default()); + + video_capabilities_vk.p_next = video_decode_capabilities_vk as *mut _ as *mut _; + } + + match video_codec_operation { + VideoCodecOperation::DecodeH264 => { + let video_decode_h264_capabilities_vk = + video_decode_capabilities_next_vk.insert(VideoDecodeCapabilitiesNextVk::H264( + ash::vk::VideoDecodeH264CapabilitiesKHR::default(), + )); + + let video_decode_h264_capabilities_vk = match video_decode_h264_capabilities_vk { + VideoDecodeCapabilitiesNextVk::H264(v) => v, + _ => unreachable!(), + }; + + let video_decode_capabilities_vk = video_decode_capabilities_vk.as_mut().unwrap(); + + video_decode_capabilities_vk.p_next = + video_decode_h264_capabilities_vk as *mut _ as *mut _; + } + VideoCodecOperation::DecodeH265 => todo!(), + } + + video_capabilities_vk + } +} + +impl Default for VideoProfileInfo { + fn default() -> Self { + Self { + video_codec_operation: VideoCodecOperation::DecodeH264, + chroma_subsampling: VideoChromaSubsampling::Monochrome, + luma_bit_depth: VideoComponentBitDepth::Type8, + chroma_bit_depth: Some(VideoComponentBitDepth::Type8), + codec_profile_info: VideoDecodeProfileInfo::H264(VideoDecodeH264ProfileInfo { + std_profile_idc: 66, + picture_layout: VideoDecodeH264PictureLayoutFlags::PROGRESSIVE, + _ne: crate::NonExhaustive(()), + }), + _ne: crate::NonExhaustive(()), + } + } +} + +#[derive(Clone, Debug)] +pub struct VideoDecodeH264ProfileInfo { + pub std_profile_idc: StdVideoH264ProfileIdc, + pub picture_layout: VideoDecodeH264PictureLayoutFlags, + pub _ne: crate::NonExhaustive, +} + +impl Default for VideoDecodeH264ProfileInfo { + fn default() -> Self { + Self { + std_profile_idc: Default::default(), + picture_layout: Default::default(), + _ne: crate::NonExhaustive(()), + } + } +} + +#[derive(Clone, Debug)] +pub enum VideoDecodeProfileInfo { + H264(VideoDecodeH264ProfileInfo), + H265, /* TODO */ +} + +#[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 codec_capabilities: CodecCapabilities, + pub _ne: crate::NonExhaustive, +} + +#[derive(Clone, Debug)] +pub enum CodecCapabilities { + VideoDecode(VideoDecodeCapabilities), +} + +#[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 VideoProfileListInfo { + pub profiles: Vec, + pub _ne: crate::NonExhaustive, +} + +impl VideoProfileListInfo { + /// Safety: The type returned by this function is only valid so long as the + /// arguments to this function are not moved or dropped. + pub(crate) unsafe fn to_vulkan( + &self, + video_profile_info_vk: &mut Vec, + video_decode_profile_info_vk: &mut Option, + ) -> ash::vk::VideoProfileListInfoKHR { + *video_profile_info_vk = self + .profiles + .iter() + .cloned() + .map(|video_profile_info| video_profile_info.to_vulkan(video_decode_profile_info_vk)) + .collect::>(); + + ash::vk::VideoProfileListInfoKHR { + profile_count: video_profile_info_vk.len() as _, + p_profiles: video_profile_info_vk.as_ptr() as _, + ..Default::default() + } + } +} + +impl Default for VideoProfileListInfo { + fn default() -> Self { + Self { + profiles: Default::default(), + _ne: crate::NonExhaustive(()), + } + } +} + +#[derive(Clone, Debug)] +pub struct VideoFormatInfo { + pub image_usage: ImageUsage, + pub profile_list_info: VideoProfileListInfo, +} + +impl VideoFormatInfo { + /// Safety: The type returned by this function is only valid so long as the + /// arguments to this function are not moved or dropped. + pub(crate) unsafe fn to_vulkan( + &self, + video_profile_list_info_vk: &mut Option, + video_profile_info_vk: &mut Vec, + video_decode_profile_info_vk: &mut Option, + ) -> ash::vk::PhysicalDeviceVideoFormatInfoKHR { + let video_profile_list_info_vk = video_profile_list_info_vk.insert( + self.profile_list_info + .to_vulkan(video_profile_info_vk, video_decode_profile_info_vk), + ); + + ash::vk::PhysicalDeviceVideoFormatInfoKHR { + p_next: video_profile_list_info_vk as *const _ as _, + image_usage: self.image_usage.into(), + ..Default::default() + } + } +} + +#[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, +} From 7a36c67a64da843e823e120f9130478db24016f6 Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Mon, 2 Oct 2023 16:07:19 -0300 Subject: [PATCH 05/12] Add support for VkVideoSession --- examples/src/bin/video.rs | 22 ++- vulkano/src/device/mod.rs | 1 + vulkano/src/video.rs | 366 +++++++++++++++++++++++++++++++++++++- 3 files changed, 386 insertions(+), 3 deletions(-) diff --git a/examples/src/bin/video.rs b/examples/src/bin/video.rs index 2e957b98e7..4f85b7c378 100644 --- a/examples/src/bin/video.rs +++ b/examples/src/bin/video.rs @@ -3,10 +3,11 @@ use std::sync::Arc; use vulkano::{ device::{Device, DeviceCreateInfo, DeviceExtensions, QueueCreateInfo, QueueFlags}, image::ImageUsage, - instance::{Instance, InstanceCreateFlags, InstanceCreateInfo, InstanceExtensions}, + instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}, video::{ CodecCapabilities, VideoDecodeCapabilityFlags, VideoDecodeH264PictureLayoutFlags, VideoDecodeH264ProfileInfo, VideoFormatInfo, VideoProfileInfo, VideoProfileListInfo, + VideoSession, VideoSessionCreateInfo, }, VulkanLibrary, }; @@ -152,9 +153,26 @@ fn main() { }, }; - let formats = physical_device + let mut formats = physical_device .video_format_properties(video_format_info) .unwrap(); println!("video formats: {:#?}", formats); + + let format = formats.pop().unwrap(); + + let video_session_create_info = VideoSessionCreateInfo { + queue_family_index: video_queue_family_index, + video_profile: profile_info, + picture_format: format.format, + max_coded_extent: video_caps.max_coded_extent, + reference_picture_format: format.format, + max_dpb_slots: video_caps.max_dpb_slots, + max_active_reference_pictures: video_caps.max_active_reference_pictures, + std_header_version: video_caps.std_header_version, + ..Default::default() + }; + + let video_session = VideoSession::new(Arc::clone(&device), video_session_create_info).unwrap(); + println!("video session: {:#?}", video_session); } diff --git a/vulkano/src/device/mod.rs b/vulkano/src/device/mod.rs index 5bc225055f..707974bae7 100644 --- a/vulkano/src/device/mod.rs +++ b/vulkano/src/device/mod.rs @@ -119,6 +119,7 @@ use crate::{ instance::{Instance, InstanceOwned, InstanceOwnedDebugWrapper}, macros::{impl_id_counter, vulkan_bitflags}, memory::ExternalMemoryHandleType, + video::{VideoSession, VideoSessionCreateInfo}, Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, Version, VulkanError, VulkanObject, }; diff --git a/vulkano/src/video.rs b/vulkano/src/video.rs index 47b7f8d871..df0240cf90 100644 --- a/vulkano/src/video.rs +++ b/vulkano/src/video.rs @@ -7,16 +7,24 @@ // notice may not be copied, modified, or distributed except // according to those terms. +use std::{ + mem::MaybeUninit, + sync::{Arc, Mutex}, +}; + use ash::vk::{ native::{StdVideoH264LevelIdc, StdVideoH264ProfileIdc}, DeviceSize, }; use crate::{ + device::Device, format::Format, image::{sampler::ComponentMapping, ImageCreateFlags, ImageTiling, ImageType, ImageUsage}, + instance::InstanceOwnedDebugWrapper, macros::{vulkan_bitflags, vulkan_bitflags_enum}, - ExtensionProperties, ValidationError, + ExtensionProperties, Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, + Version, VulkanError, VulkanObject, }; vulkan_bitflags_enum! { @@ -417,3 +425,359 @@ pub struct VideoFormatProperties { pub image_tiling: ImageTiling, pub image_usage_flags: ImageUsage, } + +vulkan_bitflags_enum! { + #[non_exhaustive] + + VideoSessionCreateFlags, + + VideoSessionCreateFlag, + + = VideoSessionCreateFlagsKHR(u32); + + PROTECTED_CONTENT, ProtectedContent = PROTECTED_CONTENT, +} + +#[derive(Debug)] +pub struct VideoSession { + handle: ash::vk::VideoSessionKHR, + device: InstanceOwnedDebugWrapper>, + /// The `VideoSessionCreateInfo` that created `self`. + create_info: VideoSessionCreateInfo, +} + +impl VideoSession { + pub fn new( + device: Arc, + create_info: VideoSessionCreateInfo, + ) -> Result, Validated> { + Self::validate_new(&device, &create_info)?; + + unsafe { Ok(Self::new_unchecked(device, create_info)?) } + } + + fn validate_new( + device: &Device, + create_info: &VideoSessionCreateInfo, + ) -> Result<(), Box> { + if !device + .physical_device() + .supported_extensions() + .khr_video_queue + || device.physical_device().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() + })); + } + + create_info + .validate(device) + .map_err(|err| err.add_context("create_info"))?; + + Ok(()) + } + + unsafe fn new_unchecked( + device: Arc, + create_info: VideoSessionCreateInfo, + ) -> Result, VulkanError> { + let &VideoSessionCreateInfo { + queue_family_index, + flags, + ref video_profile, + picture_format, + max_coded_extent, + reference_picture_format, + max_dpb_slots, + max_active_reference_pictures, + std_header_version: + ExtensionProperties { + ref extension_name, + spec_version, + }, + .. + } = &create_info; + let mut video_decode_h264_profile_info_vk = None; + let video_profile_vk = video_profile + .clone() + .to_vulkan(&mut video_decode_h264_profile_info_vk); + + let extension_properties_vk = ash::vk::ExtensionProperties { + extension_name: { + let c_str = std::ffi::CString::new(extension_name.clone()).unwrap(); + let mut bytes = [0; 256]; + bytes[0..c_str.as_bytes_with_nul().len()] + .copy_from_slice(c_str.as_bytes_with_nul()); + bytes.map(|b| b as _) + }, + spec_version, + }; + + let create_info_vk = ash::vk::VideoSessionCreateInfoKHR { + queue_family_index, + flags: flags.into(), + p_video_profile: &video_profile_vk, + picture_format: picture_format.into(), + max_coded_extent: ash::vk::Extent2D { + width: max_coded_extent[0], + height: max_coded_extent[1], + }, + reference_picture_format: reference_picture_format.into(), + max_dpb_slots, + max_active_reference_pictures, + p_std_header_version: &extension_properties_vk, + ..Default::default() + }; + + let handle = unsafe { + let fns = device.fns(); + let mut output = MaybeUninit::uninit(); + (fns.khr_video_queue.create_video_session_khr)( + device.handle(), + &create_info_vk, + std::ptr::null(), + output.as_mut_ptr(), + ) + .result() + .map_err(VulkanError::from)?; + output.assume_init() + }; + + Ok(Self::from_handle(handle, create_info, device)) + } + + /// Creates a new `VideoSession` from a raw object handle. + /// + /// # Safety + /// + /// - `handle` must be a valid Vulkan object handle created from `device`. + /// - `create_info` must match the info used to create the object. + pub unsafe fn from_handle( + handle: ash::vk::VideoSessionKHR, + create_info: VideoSessionCreateInfo, + device: Arc, + ) -> Arc { + Arc::new(VideoSession { + handle, + device: InstanceOwnedDebugWrapper(device), + create_info, + }) + } +} + +impl Drop for VideoSession { + #[inline] + fn drop(&mut self) { + unsafe { + let fns = self.device.fns(); + (fns.khr_video_queue.destroy_video_session_khr)( + self.device.handle(), + self.handle, + std::ptr::null(), + ); + } + } +} + +#[derive(Clone, Debug)] +pub struct VideoSessionCreateInfo { + pub queue_family_index: u32, + pub flags: VideoSessionCreateFlags, + pub video_profile: VideoProfileInfo, + pub picture_format: Format, + pub max_coded_extent: [u32; 2], + pub reference_picture_format: Format, + pub max_dpb_slots: u32, + pub max_active_reference_pictures: u32, + pub std_header_version: ExtensionProperties, + pub _ne: crate::NonExhaustive, +} + +impl VideoSessionCreateInfo { + pub(crate) fn validate(&self, device: &Device) -> Result<(), Box> { + let &Self { + flags, + max_coded_extent, + reference_picture_format, + max_dpb_slots, + max_active_reference_pictures, + video_profile: + VideoProfileInfo { + video_codec_operation, + .. + }, + .. + } = self; + + flags.validate_device(device).map_err(|err| { + err.add_context("flags") + .set_vuids(&["VUID-VkSwapchainCreateInfoKHR-flags-parameter"]) + })?; + + let video_capabilities = device + .physical_device() + .video_capabilities(self.video_profile.clone()) + .map_err(|err| { + Box::new(ValidationError::from_error(err)) + .add_context("pVideoProfile") + .set_vuids(&["VUID-VkVideoSessionCreateInfoKHR-pVideoProfile-04845"]) + })?; + + if max_dpb_slots > video_capabilities.max_dpb_slots { + return Err(Box::new(ValidationError { + context: "max_dpb_slots".into(), + problem: + "is greater than the maximum number of DPB slots supported by the video profile" + .into(), + vuids: &["VUID-VkVideoSessionCreateInfoKHR-maxDPBSlots-04847"], + ..Default::default() + })); + } + + if max_active_reference_pictures > video_capabilities.max_active_reference_pictures { + return Err(Box::new(ValidationError { + context: "max_active_reference_pictures".into(), + problem: "is greater than the maximum number of active reference pictures supported by the video profile".into(), + vuids: &["VUID-VkVideoSessionCreateInfoKHR-maxActiveReferencePictures-04849"], + ..Default::default() + })); + } + + if (max_dpb_slots == 0 && max_active_reference_pictures != 0) + || (max_active_reference_pictures == 0 && max_dpb_slots != 0) + { + return Err(Box::new(ValidationError { + context: "max_dpb_slots".into(), + problem: "is 0 while max_active_reference_pictures is not 0, or vice versa".into(), + vuids: &["VUID-VkVideoSessionCreateInfoKHR-maxDpbSlots-04850"], + ..Default::default() + })); + } + + if max_coded_extent < video_capabilities.min_coded_extent + || max_coded_extent > video_capabilities.max_coded_extent + { + return Err(Box::new(ValidationError { + context: "max_coded_extent".into(), + problem: "is not within the range of supported coded extents".into(), + vuids: &["VUID-VkVideoSessionCreateInfoKHR-maxCodedExtent-04851"], + ..Default::default() + })); + } + + let is_decode_operation = match video_codec_operation { + VideoCodecOperation::DecodeH264 | VideoCodecOperation::DecodeH265 => true, + }; + + if is_decode_operation && max_active_reference_pictures > 0 { + let video_format_info = VideoFormatInfo { + image_usage: ImageUsage::VIDEO_DECODE_DPB, + profile_list_info: VideoProfileListInfo { + profiles: vec![self.video_profile.clone()], + ..Default::default() + }, + }; + + let formats = device + .physical_device() + .video_format_properties(video_format_info) + .map_err(|err| { + Box::new(ValidationError::from_error(err)).add_context("pVideoProfile") + })?; + + if !formats + .into_iter() + .any(|f| f.format == reference_picture_format) + { + return Err(Box::new(ValidationError { + context: "referencePictureFormat".into(), + problem: "must be one of the supported decode DPB formats".into(), + vuids: &["VUID-VkVideoSessionCreateInfoKHR-referencePictureFormat-04852"], + ..Default::default() + })); + } + } + + /* encode: */ + /* VUID-VkVideoSessionCreateInfoKHR-referencePictureFormat-06814 */ + + if is_decode_operation { + let video_format_info = VideoFormatInfo { + image_usage: ImageUsage::VIDEO_DECODE_DST, + profile_list_info: VideoProfileListInfo { + profiles: vec![self.video_profile.clone()], + ..Default::default() + }, + }; + + let formats = device + .physical_device() + .video_format_properties(video_format_info) + .map_err(|err| { + Box::new(ValidationError::from_error(err)).add_context("pVideoProfile") + })?; + + if !formats + .into_iter() + .any(|f| f.format == reference_picture_format) + { + return Err(Box::new(ValidationError { + context: "pictureFormat".into(), + problem: "must be one of the supported decode output formats".into(), + vuids: &["VUID-VkVideoSessionCreateInfoKHR-pictureFormat-04853"], + ..Default::default() + })); + } + } + + /* encode: */ + /* VUID-VkVideoSessionCreateInfoKHR-pictureFormat-04854 */ + + if self.std_header_version.extension_name + != video_capabilities.std_header_version.extension_name + { + return Err(Box::new(ValidationError { + context: "std_header_version.extensionName".into(), + problem: "does not match video_capabilities.std_header_version.extension_name" + .into(), + vuids: &["VUID-VkVideoSessionCreateInfoKHR-pStdHeaderVersion-07190"], + ..Default::default() + })); + } + + if self.std_header_version.spec_version > video_capabilities.std_header_version.spec_version + { + return Err(Box::new(ValidationError { + context: "std_header_version.specVersion".into(), + problem: "is greater than video_capabilities.std_header_version.spec_version" + .into(), + vuids: &["VUID-VkVideoSessionCreateInfoKHR-pStdHeaderVersion-07191"], + ..Default::default() + })); + } + + Ok(()) + } +} + +impl Default for VideoSessionCreateInfo { + fn default() -> Self { + Self { + queue_family_index: Default::default(), + flags: Default::default(), + video_profile: Default::default(), + picture_format: Default::default(), + max_coded_extent: Default::default(), + reference_picture_format: Default::default(), + max_dpb_slots: Default::default(), + max_active_reference_pictures: Default::default(), + std_header_version: ExtensionProperties::from(ash::vk::ExtensionProperties::default()), + _ne: crate::NonExhaustive(()), + } + } +} From c5f744a7acafa2aed72b4bafa8b365e6666f21ba Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Wed, 25 Oct 2023 13:51:43 -0300 Subject: [PATCH 06/12] Add support for VideoSessionParameters Add support for VideoSessionParameters. These are used to deliver stream metadata to the driver. --- examples/src/bin/video.rs | 15 +- vulkano/src/video.rs | 394 ++++++++++++++++++++++++++++++++++++++ vulkano/src/video/h264.rs | 164 ++++++++++++++++ 3 files changed, 572 insertions(+), 1 deletion(-) create mode 100644 vulkano/src/video/h264.rs diff --git a/examples/src/bin/video.rs b/examples/src/bin/video.rs index 4f85b7c378..482ca31c56 100644 --- a/examples/src/bin/video.rs +++ b/examples/src/bin/video.rs @@ -7,7 +7,8 @@ use vulkano::{ video::{ CodecCapabilities, VideoDecodeCapabilityFlags, VideoDecodeH264PictureLayoutFlags, VideoDecodeH264ProfileInfo, VideoFormatInfo, VideoProfileInfo, VideoProfileListInfo, - VideoSession, VideoSessionCreateInfo, + VideoSession, VideoSessionCreateInfo, VideoSessionParameters, + VideoSessionParametersCreateFlags, VideoSessionParametersCreateInfo, }, VulkanLibrary, }; @@ -175,4 +176,16 @@ fn main() { let video_session = VideoSession::new(Arc::clone(&device), video_session_create_info).unwrap(); println!("video session: {:#?}", video_session); + + let video_session_parameters_create_info = VideoSessionParametersCreateInfo::new( + VideoSessionParametersCreateFlags::empty(), None, Arc::clone(&video_session), vulkano::video::VideoSessionParametersCreateInfoNext::VideoDecodeH264SessionParametersCreateInfo { max_std_sps_count: 0, max_std_pps_count: 0, parameter_add_info: Some(vulkano::video::h264::VideoDecodeH264SessionParametersAddInfo { + std_sp_ss: vec![], + std_pp_ss: vec![], + }) } + ); + + let empty_session_parameters = + VideoSessionParameters::new(Arc::clone(&device), video_session_parameters_create_info) + .unwrap(); + println!("empty session parameters: {:#?}", empty_session_parameters); } diff --git a/vulkano/src/video.rs b/vulkano/src/video.rs index df0240cf90..4962cd8dec 100644 --- a/vulkano/src/video.rs +++ b/vulkano/src/video.rs @@ -7,6 +7,8 @@ // notice may not be copied, modified, or distributed except // according to those terms. +pub mod h264; + use std::{ mem::MaybeUninit, sync::{Arc, Mutex}, @@ -27,6 +29,8 @@ use crate::{ Version, VulkanError, VulkanObject, }; +pub use ash::vk::native::*; + vulkan_bitflags_enum! { #[non_exhaustive] @@ -781,3 +785,393 @@ impl Default for VideoSessionCreateInfo { } } } + +pub enum VideoSessionParametersCreateInfoNextVk { + VideoDecodeH264(ash::vk::VideoDecodeH264SessionParametersCreateInfoKHR), + VideoDecodeH265(ash::vk::VideoDecodeH265SessionParametersCreateInfoKHR), +} + +pub enum VideoSessionParametersAddInfoVk { + VideoDecodeH264(ash::vk::VideoDecodeH264SessionParametersAddInfoKHR), + VideoDecodeH265(ash::vk::VideoDecodeH265SessionParametersAddInfoKHR), +} + +#[derive(Debug)] +pub struct VideoSessionParameters { + handle: ash::vk::VideoSessionParametersKHR, + device: InstanceOwnedDebugWrapper>, + + pub create_info: VideoSessionParametersCreateInfo, + pub _ne: crate::NonExhaustive, +} + +impl VideoSessionParameters { + pub fn new( + device: Arc, + create_info: VideoSessionParametersCreateInfo, + ) -> Result, Validated> { + Self::validate_new(&device, &create_info)?; + + unsafe { Ok(Self::new_unchecked(device, create_info)?) } + } + + fn validate_new( + device: &Device, + create_info: &VideoSessionParametersCreateInfo, + ) -> Result<(), Box> { + if !device + .physical_device() + .supported_extensions() + .khr_video_queue + || device.physical_device().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() + })); + } + + create_info + .validate() + .map_err(|err| err.add_context("create_info"))?; + + Ok(()) + } + + unsafe fn new_unchecked( + device: Arc, + create_info: VideoSessionParametersCreateInfo, + ) -> Result, VulkanError> { + let mut video_decode_create_info_next = None; + let mut video_decode_parameter_add_info = None; + + let create_info_vk = create_info.clone().to_vulkan( + &mut video_decode_create_info_next, + &mut video_decode_parameter_add_info, + ); + + let handle = unsafe { + let fns = device.fns(); + let mut output = MaybeUninit::uninit(); + (fns.khr_video_queue.create_video_session_parameters_khr)( + device.handle(), + &create_info_vk, + std::ptr::null(), + output.as_mut_ptr(), + ) + .result() + .map_err(VulkanError::from)?; + output.assume_init() + }; + + Ok(Self::from_handle(handle, create_info, device)) + } + + /// Creates a new `VideoSessionParameters` from a raw object handle. + /// + /// # Safety + /// + /// - `handle` must be a valid Vulkan object handle created from `device`. + /// - `create_info` must match the info used to create the object. + pub unsafe fn from_handle( + handle: ash::vk::VideoSessionParametersKHR, + create_info: VideoSessionParametersCreateInfo, + device: Arc, + ) -> Arc { + Arc::new(VideoSessionParameters { + handle, + device: InstanceOwnedDebugWrapper(device), + create_info, + _ne: crate::NonExhaustive(()), + }) + } +} + +impl Drop for VideoSessionParameters { + fn drop(&mut self) { + unsafe { + let fns = self.device.fns(); + (fns.khr_video_queue.destroy_video_session_parameters_khr)( + self.device.handle(), + self.handle, + std::ptr::null(), + ); + } + } +} + +vulkan_bitflags! { + #[non_exhaustive] + + VideoSessionParametersCreateFlags = VideoSessionParametersCreateFlagsKHR(u32); +} + +#[derive(Clone, Debug)] +pub enum VideoSessionParametersCreateInfoNext { + VideoDecodeH264SessionParametersCreateInfo { + max_std_sps_count: u32, + max_std_pps_count: u32, + parameter_add_info: Option, + }, + VideoDecodeH265SessionParametersCreateInfo {/* TODO */}, +} + +#[derive(Clone, Debug)] +pub struct VideoSessionParametersCreateInfo { + pub flags: VideoSessionParametersCreateFlags, + pub video_session_parameters_template: Option>, + pub video_session: Arc, + pub next: VideoSessionParametersCreateInfoNext, + pub _ne: crate::NonExhaustive, +} + +impl VideoSessionParametersCreateInfo { + #[inline] + pub fn new( + flags: VideoSessionParametersCreateFlags, + video_session_parameters_template: Option>, + video_session: Arc, + next: VideoSessionParametersCreateInfoNext, + ) -> Self { + Self { + flags, + video_session_parameters_template, + video_session: Arc::clone(&video_session), + next, + _ne: crate::NonExhaustive(()), + } + } + + /// Compute "spsAddList" + fn sps_add_list(&self) -> usize { + let mut seq_parameter_ids = vec![]; + match &self.next { + VideoSessionParametersCreateInfoNext::VideoDecodeH264SessionParametersCreateInfo { + parameter_add_info, + .. + } => { + if let Some(parameter_add_info) = parameter_add_info { + seq_parameter_ids.extend( + parameter_add_info + .std_sp_ss + .iter() + .map(|sps| sps.seq_parameter_set_id), + ); + } + } + _ => todo!(), + } + + if let Some(video_session_parameters_template) = &self.video_session_parameters_template { + match &video_session_parameters_template.create_info.next { + VideoSessionParametersCreateInfoNext::VideoDecodeH264SessionParametersCreateInfo { parameter_add_info: Some(template_parameter_add_info), .. } => { + for sps in &template_parameter_add_info.std_sp_ss { + if !seq_parameter_ids.contains(&sps.seq_parameter_set_id) { + seq_parameter_ids.push(sps.seq_parameter_set_id); + } + } + } + _ => todo!(), + } + } + + seq_parameter_ids.len() + } + + fn pps_add_list(&self) -> usize { + let mut seq_parameter_ids = vec![]; + let mut pic_parameter_ids = vec![]; + match &self.next { + VideoSessionParametersCreateInfoNext::VideoDecodeH264SessionParametersCreateInfo { + parameter_add_info, + .. + } => { + if let Some(parameter_add_info) = parameter_add_info { + pic_parameter_ids.extend(parameter_add_info.std_pp_ss.iter().map(|pps| { + seq_parameter_ids.push(pps.seq_parameter_set_id); + pps.pic_parameter_set_id + })); + } + } + _ => todo!(), + } + + if let Some(video_session_parameters_template) = &self.video_session_parameters_template { + match &video_session_parameters_template.create_info.next { + VideoSessionParametersCreateInfoNext::VideoDecodeH264SessionParametersCreateInfo { parameter_add_info: Some(template_parameter_add_info), .. } => { + for pps in &template_parameter_add_info.std_pp_ss { + if !pic_parameter_ids.contains(&pps.pic_parameter_set_id) && !seq_parameter_ids.contains(&pps.seq_parameter_set_id) { + pic_parameter_ids.push(pps.pic_parameter_set_id); + seq_parameter_ids.push(pps.seq_parameter_set_id); + + } + } + } + _ => todo!(), + } + } + + pic_parameter_ids.len() + } + + pub(crate) fn validate(&self) -> Result<(), Box> { + let Self { + video_session_parameters_template, + video_session, + .. + } = self; + + if let Some(video_session_parameters_template) = video_session_parameters_template { + if video_session.handle + != video_session_parameters_template + .create_info + .video_session + .handle + { + return Err(Box::new(ValidationError { + context: "video_session_parameter_templte".into(), + problem: " if videoSessionParametersTemplate represents a valid handle, it must have been created against videoSession" + .into(), + vuids: &[" VUID-VkVideoSessionParametersCreateInfoKHR-videoSessionParametersTemplate-04855"], + ..Default::default() + })); + } + } + + match video_session + .create_info + .video_profile + .video_codec_operation + { + VideoCodecOperation::DecodeH264 => { + if let VideoSessionParametersCreateInfoNext::VideoDecodeH264SessionParametersCreateInfo { max_std_sps_count, max_std_pps_count, ..} = &self.next { + let sps_add_list = self.sps_add_list(); + if sps_add_list as u32 > *max_std_sps_count { + return Err(Box::new(ValidationError { + context: "spsAddList".into(), + problem: "must be less than or equal to the maxStdSPSCount".into(), + vuids: &["VUID-VkVideoSessionParametersCreateInfoKHR-videoSession-07203"], + ..Default::default() + })); + + } + + let pps_add_list = self.pps_add_list(); + if pps_add_list as u32 > *max_std_pps_count { + return Err(Box::new(ValidationError { + context: "ppsAddList".into(), + problem: "must be less than or equal to the maxStdPPSCount".into(), + vuids: &["VUID-VkVideoSessionParametersCreateInfoKHR-videoSession-07205"], + ..Default::default() + })); + } + } else { + return Err(Box::new(ValidationError { + context: "next".into(), + problem: "must be `VideoSessionParametersCreateInfoNext::VideoDecodeH264SessionParametersCreateInfo`".into(), + vuids: &["VUID-VkVideoSessionParametersCreateInfoKHR-videoSession-07203"], + ..Default::default() + })); + } + } + VideoCodecOperation::DecodeH265 => { + if !matches!(self.next, VideoSessionParametersCreateInfoNext::VideoDecodeH265SessionParametersCreateInfo{}) { + return Err(Box::new(ValidationError { + context: "next".into(), + problem: "must be `VideoSessionParametersCreateInfoNext::VideoDecodeH265SessionParametersCreateInfo`".into(), + vuids: &["VUID-VkVideoSessionParametersCreateInfoKHR-videoSession-07203"], + ..Default::default() + })); + } + } + } + + Ok(()) + } + + pub(crate) unsafe fn to_vulkan( + self, + video_decode_create_info_next: &mut Option, + video_session_parameter_add_info: &mut Option, + ) -> ash::vk::VideoSessionParametersCreateInfoKHR { + let mut video_session_parameters_create_info_vk = + ash::vk::VideoSessionParametersCreateInfoKHR { + flags: self.flags.into(), + p_next: std::ptr::null(), + video_session_parameters_template: self + .video_session_parameters_template + .map(|v| v.handle) + .unwrap_or(ash::vk::VideoSessionParametersKHR::null()), + video_session: self.video_session.handle, + ..Default::default() + }; + + match self + .video_session + .create_info + .video_profile + .video_codec_operation + { + VideoCodecOperation::DecodeH264 => { + let (max_sps_count, max_pps_count, parameter_add_info) = match self.next { + VideoSessionParametersCreateInfoNext::VideoDecodeH264SessionParametersCreateInfo { max_std_sps_count, max_std_pps_count, parameter_add_info: Some(parameter_add_info) } => (max_std_sps_count, max_std_pps_count, parameter_add_info), + _ => panic!(), + }; + + let mut video_decode_h264_session_parameters_create_info_vk = + ash::vk::VideoDecodeH264SessionParametersCreateInfoKHR { + max_std_sps_count: max_sps_count, + max_std_pps_count: max_pps_count, + p_next: std::ptr::null(), + ..Default::default() + }; + + let video_decode_h264_session_parameters_add_info_vk = + ash::vk::VideoDecodeH264SessionParametersAddInfoKHR { + std_sps_count: parameter_add_info.std_sp_ss.len() as _, + p_std_sp_ss: parameter_add_info.std_sp_ss.as_ptr() as _, + std_pps_count: parameter_add_info.std_pp_ss.len() as _, + p_std_pp_ss: parameter_add_info.std_pp_ss.as_ptr() as _, + ..Default::default() + }; + + let video_session_parameters_add_info_vk = video_session_parameter_add_info.insert( + VideoSessionParametersAddInfoVk::VideoDecodeH264( + video_decode_h264_session_parameters_add_info_vk, + ), + ); + + let video_session_parameters_add_info_vk = + match video_session_parameters_add_info_vk { + VideoSessionParametersAddInfoVk::VideoDecodeH264(v) => v, + _ => panic!(), + }; + + video_decode_h264_session_parameters_create_info_vk.p_parameters_add_info = + video_session_parameters_add_info_vk as *mut _ as _; + + let video_decode_h264_session_parameters_create_info_vk = + video_decode_create_info_next.insert( + VideoSessionParametersCreateInfoNextVk::VideoDecodeH264( + video_decode_h264_session_parameters_create_info_vk, + ), + ); + + let video_decode_h264_session_parameters_create_info_vk = + match video_decode_h264_session_parameters_create_info_vk { + VideoSessionParametersCreateInfoNextVk::VideoDecodeH264(v) => v, + _ => panic!(), + }; + + video_session_parameters_create_info_vk.p_next = + video_decode_h264_session_parameters_create_info_vk as *mut _ as _; + } + VideoCodecOperation::DecodeH265 => todo!(), + } + + video_session_parameters_create_info_vk + } +} diff --git a/vulkano/src/video/h264.rs b/vulkano/src/video/h264.rs new file mode 100644 index 0000000000..eb98e82a92 --- /dev/null +++ b/vulkano/src/video/h264.rs @@ -0,0 +1,164 @@ +// Copyright (c) 2023 The vulkano developers +// Licensed under the Apache License, Version 2.0 +// or the MIT +// license , +// at your option. All files in the project carrying such +// notice may not be copied, modified, or distributed except +// according to those terms. + +use ash::vk::native::StdVideoH264ChromaFormatIdc; +use ash::vk::native::StdVideoH264LevelIdc; +use ash::vk::native::StdVideoH264PocType; +use ash::vk::native::StdVideoH264PpsFlags; +use ash::vk::native::StdVideoH264ProfileIdc; +use ash::vk::native::StdVideoH264WeightedBipredIdc; + +#[derive(Clone, Debug)] +pub struct VideoH264SpsFlags(ash::vk::native::StdVideoH264SpsFlags); + +impl VideoH264SpsFlags { + pub fn new( + constraint_set0_flag: u32, + constraint_set1_flag: u32, + constraint_set2_flag: u32, + constraint_set3_flag: u32, + constraint_set4_flag: u32, + constraint_set5_flag: u32, + direct_8x8_inference_flag: u32, + mb_adaptive_frame_field_flag: u32, + frame_mbs_only_flag: u32, + delta_pic_order_always_zero_flag: u32, + separate_colour_plane_flag: u32, + gaps_in_frame_num_value_allowed_flag: u32, + qpprime_y_zero_transform_bypass_flag: u32, + frame_cropping_flag: u32, + seq_scaling_matrix_present_flag: u32, + vui_parameters_present_flag: u32, + ) -> Self { + let _bitfield_1 = ash::vk::native::StdVideoH264SpsFlags::new_bitfield_1( + constraint_set0_flag, + constraint_set1_flag, + constraint_set2_flag, + constraint_set3_flag, + constraint_set4_flag, + constraint_set5_flag, + direct_8x8_inference_flag, + mb_adaptive_frame_field_flag, + frame_mbs_only_flag, + delta_pic_order_always_zero_flag, + separate_colour_plane_flag, + gaps_in_frame_num_value_allowed_flag, + qpprime_y_zero_transform_bypass_flag, + frame_cropping_flag, + seq_scaling_matrix_present_flag, + vui_parameters_present_flag, + ); + + Self(ash::vk::native::StdVideoH264SpsFlags { + _bitfield_align_1: Default::default(), + _bitfield_1, + __bindgen_padding_0: Default::default(), + }) + } +} + +// Using u32 for now. This macro does not work if the path is not reexported +// into ash::vk +// ash::vk_bitflags_wrapped! { +// +// VideoH264ProfileIdc = StdVideoH264ProfileIdc(u32); +// +// VIDEO_H264_PROFILE_IDC_BASELINE = StdVideoH264ProfileIdc_STD_VIDEO_H264_PROFILE_IDC_BASELINE, +// VIDEO_H264_PROFILE_IDC_MAIN = StdVideoH264ProfileIdc_STD_VIDEO_H264_PROFILE_IDC_MAIN, +// VIDEO_H264_PROFILE_IDC_HIGH = StdVideoH264ProfileIdc_STD_VIDEO_H264_PROFILE_IDC_HIGH, +// VIDEO_H264_PROFILE_IDC_HIGH_444_PREDICTIVE = StdVideoH264ProfileIdc_STD_VIDEO_H264_PROFILE_IDC_HIGH_444_PREDICTIVE, +// VIDEO_H264_PROFILE_IDC_INVALID = StdVideoH264ProfileIdc_STD_VIDEO_H264_PROFILE_IDC_INVALID, +// } + +#[derive(Clone, Debug)] +pub struct VideoH264SequenceParameterSet { + pub flags: VideoH264SpsFlags, + pub profile_idc: StdVideoH264ProfileIdc, + pub level_idc: StdVideoH264LevelIdc, + pub chroma_format_idc: StdVideoH264ChromaFormatIdc, + pub seq_parameter_set_id: u8, + pub bit_depth_luma_minus8: u8, + pub bit_depth_chroma_minus8: u8, + pub log2_max_frame_num_minus4: u8, + pub pic_order_cnt_type: StdVideoH264PocType, + pub offset_for_non_ref_pic: i32, + pub offset_for_top_to_bottom_field: i32, + pub log2_max_pic_order_cnt_lsb_minus4: u8, + pub num_ref_frames_in_pic_order_cnt_cycle: u8, + pub max_num_ref_frames: u8, + pub pic_width_in_mbs_minus1: u32, + pub pic_height_in_map_units_minus1: u32, + pub frame_crop_left_offset: u32, + pub frame_crop_right_offset: u32, + pub frame_crop_top_offset: u32, + pub frame_crop_bottom_offset: u32, + pub offset_for_ref_frame: [i32; 255], +} + +#[derive(Clone, Debug)] +pub struct VideoH264PpsFlags(ash::vk::native::StdVideoH264PpsFlags); + +impl VideoH264PpsFlags { + pub fn new( + transform_8x8_mode_flag: u32, + redundant_pic_cnt_present_flag: u32, + constrained_intra_pred_flag: u32, + deblocking_filter_control_present_flag: u32, + weighted_pred_flag: u32, + bottom_field_pic_order_in_frame_present_flag: u32, + entropy_coding_mode_flag: u32, + pic_scaling_matrix_present_flag: u32, + ) -> Self { + let _bitfield_1 = ash::vk::native::StdVideoH264PpsFlags::new_bitfield_1( + transform_8x8_mode_flag, + redundant_pic_cnt_present_flag, + constrained_intra_pred_flag, + deblocking_filter_control_present_flag, + weighted_pred_flag, + bottom_field_pic_order_in_frame_present_flag, + entropy_coding_mode_flag, + pic_scaling_matrix_present_flag, + ); + + Self(ash::vk::native::StdVideoH264PpsFlags { + _bitfield_align_1: Default::default(), + _bitfield_1, + __bindgen_padding_0: Default::default(), + }) + } +} + +#[derive(Clone, Debug)] +pub struct VideoH264ScalingLists { + pub scaling_list_present_mask: u16, + pub use_default_scaling_matrix_mask: u16, + pub scaling_list_4x4: [[u8; 16usize]; 6usize], + pub scaling_list_8x8: [[u8; 64usize]; 6usize], +} + +#[derive(Clone, Debug)] +pub struct VideoH264PictureParameterSet { + pub flags: StdVideoH264PpsFlags, + pub seq_parameter_set_id: u8, + pub pic_parameter_set_id: u8, + pub num_ref_idx_l0_default_active_minus1: u8, + pub num_ref_idx_l1_default_active_minus1: u8, + pub weighted_bipred_idc: StdVideoH264WeightedBipredIdc, + pub pic_init_qp_minus26: i8, + pub pic_init_qs_minus26: i8, + pub chroma_qp_index_offset: i8, + pub second_chroma_qp_index_offset: i8, + pub scaling_lists: VideoH264ScalingLists, +} + +#[derive(Clone, Debug)] +pub struct VideoDecodeH264SessionParametersAddInfo { + pub std_sp_ss: Vec, + pub std_pp_ss: Vec, +} From d4312d051790f80cff0ab61058ef7fae2fa4b6ce Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Wed, 25 Oct 2023 16:36:58 -0300 Subject: [PATCH 07/12] Add h264 test data This test data will be used in the video example. It contains a single I frame encoded with GStreamer. --- examples/src/bin/video/64x64-I.h264 | Bin 0 -> 2128 bytes examples/src/bin/{video.rs => video/main.rs} | 7 +++++++ 2 files changed, 7 insertions(+) create mode 100644 examples/src/bin/video/64x64-I.h264 rename examples/src/bin/{video.rs => video/main.rs} (94%) diff --git a/examples/src/bin/video/64x64-I.h264 b/examples/src/bin/video/64x64-I.h264 new file mode 100644 index 0000000000000000000000000000000000000000..6718ba53b8a97df13f094f3652b474b52d092c0a GIT binary patch literal 2128 zcmYLKeNYtV8UO9w!W~Fq&kvx2@?sFQ^09XyBF)(aq>z}XG}eGIx!XGy?&5u{dxsn} z6BkjjNHUPN#%AnFA72(i)xMRA-LXrp=gYk})aMX{67Zrv2mI z_j#Ys-}`%>cMkxUA%NEIMq|-szSP(@XtjPv2dtoFY0JU964M$uKk_nd>1lIwuU={X z@Qn{HxBmK2`82zH*NvMAS8WZeVE(A4FeiyxwNtEP)lS8^wredJS}Vz+>i*5mEfqDa zVQUk4b}D`{G)2|EkkS=rE>WzmaEY#JQU>C2wZ5vVx3{-)SFlrwhU7?PRO_xXt5gQ! z;Sl*m)p#%(iPbZ|?34X&k!eboyP9<>zEIS^!|kjW>qRC<sFvdN03!~ztg`91$bY}z%y+wap@*hkcu$b3QaCyXw!rjD6XJQ^}dW;;!M zZn3hKMAa;GXO|Q1+O?#J#TC_E!*=<0dBSqy{~1h$ucs>(?5E@|R~@6~dIHq92$9XY zf}xONQWI3EUo;z3i7?4KX%x)Hnxo7q2ll4T&F-H0`8dktR2}Rb&A694@$R32iBV)0 zyhTqzh0t%Lzz)EYrGq`E6@qsF4Wr-}9+y%WXt^>3K6BmQ=58+@Fd%IQUjRs-*$j%b zz=qRtGvBa*tcLaAzUIZ(pB@62Bf!xMZt?4bI`#tg8HwsAbR0Yh`=u*Xg8?m>oPj10 z{3f7zIF&*t_%tuss)6+%gKs~cjdTgp)}p!Dz?g)l_o-$Lymgirj!1A=!TIevN7Z=^U&_Mt+sh6{74{Pd}$)v$A?y9MYEyl;djCx7SVuBlr~ zSh9KhiV^!G(OF4C<7W)T5U>Q?T}~a#zR>53$DXNZTezYle^r&^l{P`Z$|LRimTh~U z_3}E0bj#34QCj)PzOlDwJ2H#k(Wi#=Kiz&LZ(zlZ$2a|UW-58Z1`Y;<4dA%_9^~4E z+zT_S_oW{jYA&0+dXb04>P@Zm>S&@qhJZ$Z|CZV=2s5|jni)b3N7=)L>@j8TuaNG{BP;zeKDOYjv6I+PvF2@Ne~4{pKYN@3abAkFm<^`VX4$s!f= zADl6Y_ii7;en%`D78?VbpslyqfOL&siv;P$wYuH2tIU_tPZL61y@_Q%J_8P5 za2z4zyn%+$ml3lPgmUsdoS?$(u;hvK5G?>Zn?n;y1T{QaDCXSGT}p$njuN|&YR@cH zQ}BYf8u^1WJa~Fm->yNLuaOFgSTG-4b+zi4{6W?|3tuB*(qTzYS&Y2pOLG5mQT*i6 z@SapMP*o#XBGN2#{Fyfz{rs{FOgqJNE37rXn5eQREYJeLDB(g!;CQX{=G-Z7mNgsv(Nl+8KDwv$)*r54C(4%6pRNGm;SAg4 z)iq~E->&ce1*;|qHX`LsSklIea#uwjF6?PoeZIZrg%s??#D!4Rdq#SydE>d9=SDI; zcM~)L)bf>k#(GVTljCil_no~oQTDyZE%aT)00Rph zsDFKIi*!hRamZ%qXlQRm*~J3SM&t8;pL}WgyVQ*i31`l6rQb+}KF%vzzqEDqKPX*` A0ssI2 literal 0 HcmV?d00001 diff --git a/examples/src/bin/video.rs b/examples/src/bin/video/main.rs similarity index 94% rename from examples/src/bin/video.rs rename to examples/src/bin/video/main.rs index 482ca31c56..fea5b5efe7 100644 --- a/examples/src/bin/video.rs +++ b/examples/src/bin/video/main.rs @@ -188,4 +188,11 @@ fn main() { VideoSessionParameters::new(Arc::clone(&device), video_session_parameters_create_info) .unwrap(); println!("empty session parameters: {:#?}", empty_session_parameters); + + // A 64x64 progressive byte-stream encoded I-frame. + // Encoded with the following GStreamer pipeline: + // + // gst-launch-1.0 videotestsrc num-buffers=1 ! video/x-raw,format=I420,width=64,height=64 ! x264enc ! video/x-h264,profile=constrained-baseline,stream-format=byte-stream ! filesink location="64x64-I.h264" + let h264_stream = include_bytes!("64x64-I.h264"); + println!("loaded {} bytes of h264 data", h264_stream.len()); } From 02d51d142646237b4f1e0287dac74e79466ffa2e Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Thu, 26 Oct 2023 09:51:07 -0300 Subject: [PATCH 08/12] Add support for QueueFamilyQueryResultStatusProperties We need to know whether a queue supports result status queries in order to implement vulkan video support. --- vulkano/src/device/physical.rs | 9 +++++++++ vulkano/src/device/queue.rs | 3 +++ 2 files changed, 12 insertions(+) diff --git a/vulkano/src/device/physical.rs b/vulkano/src/device/physical.rs index fb97443254..3a3e12448e 100644 --- a/vulkano/src/device/physical.rs +++ b/vulkano/src/device/physical.rs @@ -361,6 +361,7 @@ impl PhysicalDevice { } let mut queue_family_video_properties_vk = vec![None; num as usize]; + let mut queue_family_query_result_status_props = vec![None; num as usize]; let mut output = vec![ash::vk::QueueFamilyProperties2::default(); num as usize]; if supported_extensions.khr_video_decode_queue { @@ -368,6 +369,10 @@ impl PhysicalDevice { let next = queue_family_video_properties_vk[i] .insert(ash::vk::QueueFamilyVideoPropertiesKHR::default()); output[i].p_next = next as *mut _ as *mut _; + let video_props = next; + let next = queue_family_query_result_status_props[i] + .insert(ash::vk::QueueFamilyQueryResultStatusPropertiesKHR::default()); + video_props.p_next = next as *mut _ as *mut _; } } @@ -398,6 +403,10 @@ impl PhysicalDevice { queue_family_properties.video_properties = Some(queue_family_video_properties_vk.into()); } + queue_family_properties.supports_result_status_query = + queue_family_query_result_status_props[index] + .map(|props| props.query_result_status_support != 0) + .unwrap_or(false); queue_family_properties }) .collect() diff --git a/vulkano/src/device/queue.rs b/vulkano/src/device/queue.rs index 16bbe743ba..46a5000409 100644 --- a/vulkano/src/device/queue.rs +++ b/vulkano/src/device/queue.rs @@ -1682,6 +1682,8 @@ pub struct QueueFamilyProperties { /// /// [`khr_video_decode_queue`]: crate::device::DeviceExtensions::khr_video_decode_queue pub video_properties: Option, + /// Whether this queue supports result status queries. + pub supports_result_status_query: bool, } #[derive(Clone, Debug)] @@ -1711,6 +1713,7 @@ impl From for QueueFamilyProperties { val.min_image_transfer_granularity.depth, ], video_properties: Default::default(), + supports_result_status_query: Default::default(), } } } From e82e62a63c9fa1554a72819b818b30a0f25b0f9c Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Wed, 25 Oct 2023 17:28:30 -0300 Subject: [PATCH 09/12] Add support for QueryType::ResultStatusOnly This type is introduced by khr_video_queue. We need it to check on the status of a video decode/encode operation. --- vulkano/src/command_buffer/commands/query.rs | 12 ++++++++ vulkano/src/query.rs | 32 ++++++++++++++++++-- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/vulkano/src/command_buffer/commands/query.rs b/vulkano/src/command_buffer/commands/query.rs index dbcee28302..beed357b66 100644 --- a/vulkano/src/command_buffer/commands/query.rs +++ b/vulkano/src/command_buffer/commands/query.rs @@ -520,6 +520,18 @@ where })); } } + QueryType::ResultStatusOnly => { + if !queue_family_properties.supports_result_status_query { + return Err(Box::new(ValidationError { + problem: "`query_pool.query_type()` is `QueryType::ResultStatusOnly`, but \ + the queue family of the command buffer does not support \ + result status queries" + .into(), + vuids: &["VUID-vkCmdBeginQuery-queryType-07126"], + ..Default::default() + })); + } + } QueryType::Timestamp | QueryType::AccelerationStructureCompactedSize | QueryType::AccelerationStructureSerializationSize diff --git a/vulkano/src/query.rs b/vulkano/src/query.rs index 9b7d8242fd..3902401474 100644 --- a/vulkano/src/query.rs +++ b/vulkano/src/query.rs @@ -251,6 +251,17 @@ impl QueryPool { })); } } + QueryType::ResultStatusOnly => { + if !flags.intersects(QueryResultFlags::WITH_STATUS) { + return Err(Box::new(ValidationError { + problem: "`self.query_type()` is `QueryType::ResultStatusOnly`, but \ + `flags` does not contain `QueryResultFlags::WITH_STATUS`" + .into(), + vuids: &["VUID-vkGetQueryPoolResults-queryType-04810"], + ..Default::default() + })); + } + } QueryType::Occlusion | QueryType::PipelineStatistics(_) | QueryType::AccelerationStructureCompactedSize @@ -388,7 +399,8 @@ impl QueryPoolCreateInfo { | QueryType::AccelerationStructureCompactedSize | QueryType::AccelerationStructureSerializationSize | QueryType::AccelerationStructureSerializationBottomLevelPointers - | QueryType::AccelerationStructureSize => (), + | QueryType::AccelerationStructureSize + | QueryType::ResultStatusOnly => (), }; if query_count == 0 { @@ -470,6 +482,8 @@ pub enum QueryType { /// /// [`write_acceleration_structures_properties`]: crate::command_buffer::AutoCommandBufferBuilder::write_acceleration_structures_properties AccelerationStructureSize = ash::vk::QueryType::ACCELERATION_STRUCTURE_SIZE_KHR.as_raw(), + + ResultStatusOnly = ash::vk::QueryType::RESULT_STATUS_ONLY_KHR.as_raw(), } impl QueryType { @@ -496,6 +510,7 @@ impl QueryType { | Self::AccelerationStructureSerializationBottomLevelPointers | Self::AccelerationStructureSize => 1, Self::PipelineStatistics(flags) => flags.count() as DeviceSize, + Self::ResultStatusOnly => 0, } } @@ -550,6 +565,17 @@ impl QueryType { })); } } + QueryType::ResultStatusOnly => { + if !device.enabled_extensions().khr_video_queue { + return Err(Box::new(ValidationError { + problem: "is `QueryType::ResultStatusOnly`".into(), + requires_one_of: RequiresOneOf(&[RequiresAllOf(&[ + Requires::DeviceExtension("khr_video_queue"), + ])]), + ..Default::default() + })); + } + } } Ok(()) @@ -575,6 +601,7 @@ impl From<&QueryType> for ash::vk::QueryType { QueryType::AccelerationStructureSize => { ash::vk::QueryType::ACCELERATION_STRUCTURE_SIZE_KHR } + QueryType::ResultStatusOnly => ash::vk::QueryType::RESULT_STATUS_ONLY_KHR, } } } @@ -708,12 +735,11 @@ vulkan_bitflags! { /// available. PARTIAL = PARTIAL, - /* TODO: enable // TODO: document WITH_STATUS = WITH_STATUS_KHR RequiresOneOf([ RequiresAllOf([DeviceExtension(khr_video_queue)]), - ]),*/ + ]), } #[cfg(test)] From 6ff92a7c5d98ea47b72cc4b5429f97b019d4c2b9 Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Wed, 25 Oct 2023 17:34:30 -0300 Subject: [PATCH 10/12] wip --- examples/src/bin/video/main.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/examples/src/bin/video/main.rs b/examples/src/bin/video/main.rs index fea5b5efe7..707beebf65 100644 --- a/examples/src/bin/video/main.rs +++ b/examples/src/bin/video/main.rs @@ -1,9 +1,11 @@ use std::sync::Arc; use vulkano::{ + command_buffer::allocator::{CommandBufferAllocator, StandardCommandBufferAllocator}, device::{Device, DeviceCreateInfo, DeviceExtensions, QueueCreateInfo, QueueFlags}, image::ImageUsage, instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}, + query::{QueryPool, QueryPoolCreateInfo, QueryType}, video::{ CodecCapabilities, VideoDecodeCapabilityFlags, VideoDecodeH264PictureLayoutFlags, VideoDecodeH264ProfileInfo, VideoFormatInfo, VideoProfileInfo, VideoProfileListInfo, @@ -195,4 +197,19 @@ fn main() { // gst-launch-1.0 videotestsrc num-buffers=1 ! video/x-raw,format=I420,width=64,height=64 ! x264enc ! video/x-h264,profile=constrained-baseline,stream-format=byte-stream ! filesink location="64x64-I.h264" let h264_stream = include_bytes!("64x64-I.h264"); println!("loaded {} bytes of h264 data", h264_stream.len()); + + let command_buffer_allocator = + StandardCommandBufferAllocator::new(device.clone(), Default::default()); + + let command_buffer = command_buffer_allocator + .allocate( + video_queue_family_index, + vulkano::command_buffer::CommandBufferLevel::Primary, + 1, + ) + .unwrap(); + + let mut query_pool_create_info = QueryPoolCreateInfo::query_type(QueryType::ResultStatusOnly); + query_pool_create_info.query_count = 1; + let query_pool = QueryPool::new(Arc::clone(&device), query_pool_create_info).unwrap(); } From 347e3161c531a060f64ea261c325ca06d89edb44 Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Fri, 27 Oct 2023 11:32:30 -0300 Subject: [PATCH 11/12] Add support for VideoSessionMemoryRequirements --- examples/src/bin/video/main.rs | 6 +++ vulkano/src/video.rs | 75 ++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+) diff --git a/examples/src/bin/video/main.rs b/examples/src/bin/video/main.rs index 707beebf65..4a09ed919a 100644 --- a/examples/src/bin/video/main.rs +++ b/examples/src/bin/video/main.rs @@ -179,6 +179,12 @@ fn main() { let video_session = VideoSession::new(Arc::clone(&device), video_session_create_info).unwrap(); println!("video session: {:#?}", video_session); + let video_session_mem_requirements = video_session.get_memory_requirements().unwrap(); + println!( + "video session memory requirements: {:?}", + video_session_mem_requirements + ); + let video_session_parameters_create_info = VideoSessionParametersCreateInfo::new( VideoSessionParametersCreateFlags::empty(), None, Arc::clone(&video_session), vulkano::video::VideoSessionParametersCreateInfoNext::VideoDecodeH264SessionParametersCreateInfo { max_std_sps_count: 0, max_std_pps_count: 0, parameter_add_info: Some(vulkano::video::h264::VideoDecodeH264SessionParametersAddInfo { std_sp_ss: vec![], diff --git a/vulkano/src/video.rs b/vulkano/src/video.rs index 4962cd8dec..0e28651023 100644 --- a/vulkano/src/video.rs +++ b/vulkano/src/video.rs @@ -25,6 +25,7 @@ use crate::{ image::{sampler::ComponentMapping, ImageCreateFlags, ImageTiling, ImageType, ImageUsage}, instance::InstanceOwnedDebugWrapper, macros::{vulkan_bitflags, vulkan_bitflags_enum}, + memory::{allocator::DeviceLayout, MemoryRequirements}, ExtensionProperties, Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, Version, VulkanError, VulkanObject, }; @@ -572,6 +573,56 @@ impl VideoSession { create_info, }) } + + pub fn get_memory_requirements( + self: &Arc, + ) -> Result, VulkanError> { + let fns = self.device.instance().fns(); + let device = self.device.handle(); + + loop { + let mut count = 0; + // Safety: just a FFI call. `device` and `handle` are valid and + // `self` was allocated from `device`. + unsafe { + (fns.khr_video_queue + .get_video_session_memory_requirements_khr)( + device, + self.handle, + &mut count, + std::ptr::null_mut(), + ) + .result() + .map_err(VulkanError::from)?; + } + + let mut output = Vec::with_capacity(count as usize); + // Safety: just a FFI call. `device` and `handle` are valid and + // `self` was allocated from `device`. `output` points to an + // appropriately sized array. + let result = unsafe { + (fns.khr_video_queue + .get_video_session_memory_requirements_khr)( + device, + self.handle, + &mut count, + output.as_mut_ptr(), + ) + }; + + match result { + ash::vk::Result::SUCCESS => { + // Safety: the driver will have written to `count` entries. + unsafe { + output.set_len(count as usize); + } + return Ok(output.into_iter().map(Into::into).collect()); + } + ash::vk::Result::INCOMPLETE => (), + err => return Err(VulkanError::from(err)), + } + } + } } impl Drop for VideoSession { @@ -1175,3 +1226,27 @@ impl VideoSessionParametersCreateInfo { video_session_parameters_create_info_vk } } + +#[derive(Clone, Copy, Debug)] +pub struct VideoSessionMemoryRequirements { + pub memory_bind_index: u32, + pub memory_requirements: MemoryRequirements, +} + +impl From for VideoSessionMemoryRequirements { + fn from(value: ash::vk::VideoSessionMemoryRequirementsKHR) -> Self { + Self { + memory_bind_index: value.memory_bind_index, + memory_requirements: MemoryRequirements { + layout: DeviceLayout::from_size_alignment( + value.memory_requirements.size, + value.memory_requirements.alignment, + ) + .unwrap(), + memory_type_bits: value.memory_requirements.memory_type_bits, + prefers_dedicated_allocation: false, + requires_dedicated_allocation: false, + }, + } + } +} From e91092bd38fec51f1f2509bc41ea0168042336d0 Mon Sep 17 00:00:00 2001 From: Daniel Almeida Date: Fri, 27 Oct 2023 17:09:50 -0300 Subject: [PATCH 12/12] Add support for VkBindVideoSessionMemory --- examples/src/bin/video/main.rs | 64 ++++++++++- vulkano/src/video.rs | 201 ++++++++++++++++++++++++++++++++- 2 files changed, 260 insertions(+), 5 deletions(-) diff --git a/examples/src/bin/video/main.rs b/examples/src/bin/video/main.rs index 4a09ed919a..c4ce6ed1f6 100644 --- a/examples/src/bin/video/main.rs +++ b/examples/src/bin/video/main.rs @@ -5,16 +5,36 @@ use vulkano::{ device::{Device, DeviceCreateInfo, DeviceExtensions, QueueCreateInfo, QueueFlags}, image::ImageUsage, instance::{Instance, InstanceCreateFlags, InstanceCreateInfo}, + memory::{DeviceMemory, MemoryAllocateFlags, MemoryAllocateInfo}, query::{QueryPool, QueryPoolCreateInfo, QueryType}, video::{ - CodecCapabilities, VideoDecodeCapabilityFlags, VideoDecodeH264PictureLayoutFlags, - VideoDecodeH264ProfileInfo, VideoFormatInfo, VideoProfileInfo, VideoProfileListInfo, - VideoSession, VideoSessionCreateInfo, VideoSessionParameters, - VideoSessionParametersCreateFlags, VideoSessionParametersCreateInfo, + BindVideoSessionMemoryInfo, CodecCapabilities, VideoDecodeCapabilityFlags, + VideoDecodeH264PictureLayoutFlags, VideoDecodeH264ProfileInfo, VideoFormatInfo, + VideoProfileInfo, VideoProfileListInfo, VideoSession, VideoSessionCreateInfo, + VideoSessionMemoryRequirements, VideoSessionParameters, VideoSessionParametersCreateFlags, + VideoSessionParametersCreateInfo, }, VulkanLibrary, }; +fn find_suitable_memory( + device: Arc, + memory_requirements: VideoSessionMemoryRequirements, +) -> Option { + let mem_props = device.physical_device().memory_properties(); + + for (i, mem_type) in mem_props.memory_types.iter().enumerate() { + /* The memory type must agree with the memory requirements */ + if memory_requirements.memory_requirements.memory_type_bits & (1 << i) == 0 { + continue; + } + + return Some(i as u32); + } + + None +} + fn main() { let library = VulkanLibrary::new().unwrap(); @@ -185,6 +205,42 @@ fn main() { video_session_mem_requirements ); + let mut mems: Vec<_> = video_session_mem_requirements + .iter() + .map(|mem_req| { + let mem_idx = find_suitable_memory(Arc::clone(&device), *mem_req) + .expect("no suitable memory found"); + DeviceMemory::allocate( + Arc::clone(&device), + MemoryAllocateInfo { + allocation_size: mem_req.memory_requirements.layout.size(), + memory_type_index: mem_idx, + dedicated_allocation: None, + export_handle_types: Default::default(), + flags: MemoryAllocateFlags::empty(), + ..Default::default() + }, + ) + .unwrap() + }) + .collect(); + + let bind_video_session_memory_infos = video_session_mem_requirements + .iter() + .map(|mem_req| { + BindVideoSessionMemoryInfo::new( + mem_req.memory_bind_index, + mems.pop().unwrap(), + 0, + mem_req.memory_requirements.layout.size(), + ) + }) + .collect(); + + video_session + .bind_video_session_memory(bind_video_session_memory_infos) + .unwrap(); + let video_session_parameters_create_info = VideoSessionParametersCreateInfo::new( VideoSessionParametersCreateFlags::empty(), None, Arc::clone(&video_session), vulkano::video::VideoSessionParametersCreateInfoNext::VideoDecodeH264SessionParametersCreateInfo { max_std_sps_count: 0, max_std_pps_count: 0, parameter_add_info: Some(vulkano::video::h264::VideoDecodeH264SessionParametersAddInfo { std_sp_ss: vec![], diff --git a/vulkano/src/video.rs b/vulkano/src/video.rs index 0e28651023..8a0ebf7e6a 100644 --- a/vulkano/src/video.rs +++ b/vulkano/src/video.rs @@ -25,7 +25,7 @@ use crate::{ image::{sampler::ComponentMapping, ImageCreateFlags, ImageTiling, ImageType, ImageUsage}, instance::InstanceOwnedDebugWrapper, macros::{vulkan_bitflags, vulkan_bitflags_enum}, - memory::{allocator::DeviceLayout, MemoryRequirements}, + memory::{allocator::DeviceLayout, DeviceMemory, MemoryRequirements}, ExtensionProperties, Requires, RequiresAllOf, RequiresOneOf, Validated, ValidationError, Version, VulkanError, VulkanObject, }; @@ -449,6 +449,7 @@ pub struct VideoSession { device: InstanceOwnedDebugWrapper>, /// The `VideoSessionCreateInfo` that created `self`. create_info: VideoSessionCreateInfo, + bound_memory_indices: std::collections::HashSet, } impl VideoSession { @@ -571,6 +572,7 @@ impl VideoSession { handle, device: InstanceOwnedDebugWrapper(device), create_info, + bound_memory_indices: Default::default(), }) } @@ -623,6 +625,134 @@ impl VideoSession { } } } + + fn validate_bind_video_session_memory( + self: &Arc, + bind_session_memory_infos: &Vec, + memory_requirements: Vec, + ) -> Result<(), Box> { + for info in bind_session_memory_infos { + if self.bound_memory_indices.contains(&info.memory_bind_index) { + return Err(Box::new(ValidationError { + context: "memory_bind_index".into(), + problem: "The memory binding of videoSession identified by the memoryBindIndex member of any element of pBindSessionMemoryInfos must not already be backed by a memory object" + .into(), + vuids: &["VUID-vkBindVideoSessionMemoryKHR-videoSession-07195"], + ..Default::default() + })); + } + + let corresponding_mem_req = match memory_requirements + .iter() + .find(|mem_req| mem_req.memory_bind_index == info.memory_bind_index) + { + None => { + return Err(Box::new(ValidationError { + context: "memory_bind_index".into(), + problem: "Each element of pBindSessionMemoryInfos must have a corresponding VkMemoryRequirements structure" + .into(), + vuids: &["VUID-vkBindVideoSessionMemoryKHR-pBindSessionMemoryInfos-07197"], + ..Default::default() + })); + } + Some(mem_req) => mem_req, + }; + + if info.memory.memory_type_index() + & corresponding_mem_req.memory_requirements.memory_type_bits + == 0 + { + return Err(Box::new(ValidationError { + context: "memory".into(), + problem: "The memory type of each element of pBindSessionMemoryInfos must be supported by the memoryRequirements.memoryTypeBits of the corresponding element of pMemoryRequirements" + .into(), + vuids: &["VUID-vkBindVideoSessionMemoryKHR-pBindSessionMemoryInfos-07198"], + ..Default::default() + })); + } + + if !vulkano::memory::is_aligned( + info.memory_offset, + corresponding_mem_req.memory_requirements.layout.alignment(), + ) { + return Err(Box::new(ValidationError { + context: "memory".into(), + problem: "The memory must be aligned according to the corresponding VkMemoryRequirements" + .into(), + vuids: &["VUID-vkBindVideoSessionMemoryKHR-pBindSessionMemoryInfos-07199"], + ..Default::default() + })); + } + + if info.memory_size != corresponding_mem_req.memory_requirements.layout.size() { + return Err(Box::new(ValidationError { + context: "memory".into(), + problem: "The memorySize member of that element of pBindSessionMemoryInfos must equal the size member of the corresponding VkMemoryRequirements structure" + .into(), + vuids: &["VUID-vkBindVideoSessionMemoryKHR-pBindSessionMemoryInfos-07200"], + ..Default::default() + })); + } + } + + let memory_bind_indices: Vec = bind_session_memory_infos + .iter() + .map(|b| b.memory_bind_index) + .collect(); + + let mut unique = std::collections::HashSet::new(); + let unique = memory_bind_indices.iter().all(|i| unique.insert(i)); + + if !unique { + return Err(Box::new(ValidationError { + context: "memory_bind_index".into(), + problem: "The memoryBindIndex member of each element of pBindSessionMemoryInfos must be unique within pBindSessionMemoryInfos" + .into(), + vuids: &["VUID-vkBindVideoSessionMemoryKHR-memoryBindIndex-07196"], + ..Default::default() + })); + } + + Ok(()) + } + + unsafe fn bind_video_session_memory_unchecked( + &self, + bind_video_session_memory_infos: Vec, + ) -> Result<(), Validated> { + let bind_video_session_memory_infos: Vec<_> = (0..bind_video_session_memory_infos.len()) + .map(|i| { + let info = &bind_video_session_memory_infos[i]; + info.to_vulkan() + }) + .collect(); + + let fns = self.device.instance().fns(); + (fns.khr_video_queue.bind_video_session_memory_khr)( + self.device.handle(), + self.handle, + bind_video_session_memory_infos.len() as _, + bind_video_session_memory_infos.as_ptr(), + ) + .result() + .map_err(VulkanError::from)?; + + Ok(()) + } + + pub fn bind_video_session_memory( + self: &Arc, + bind_video_session_memory_infos: Vec, + ) -> Result<(), Validated> { + for info in &bind_video_session_memory_infos { + info.validate() + .map_err(|err| err.add_context("bind_session_memory_info"))?; + } + + let reqs = self.get_memory_requirements()?; + self.validate_bind_video_session_memory(&bind_video_session_memory_infos, reqs)?; + unsafe { self.bind_video_session_memory_unchecked(bind_video_session_memory_infos) } + } } impl Drop for VideoSession { @@ -1250,3 +1380,72 @@ impl From for VideoSessionMemoryRequ } } } + +#[derive(Debug)] +pub struct BindVideoSessionMemoryInfo { + pub memory_bind_index: u32, + pub memory: DeviceMemory, + pub memory_offset: DeviceSize, + pub memory_size: DeviceSize, + ne: crate::NonExhaustive, +} + +impl BindVideoSessionMemoryInfo { + fn validate(&self) -> Result<(), Box> { + let &Self { + ref memory, + memory_offset, + memory_size, + .. + } = self; + + if memory_offset >= memory.allocation_size() { + return Err(Box::new(ValidationError { + context: "memory_offset".into(), + problem: "memoryOffset must be less than the size of memory".into(), + vuids: &["VUID-VkBindVideoSessionMemoryInfoKHR-memoryOffset-07201"], + ..Default::default() + })); + } + + if memory_size > (memory.allocation_size() - memory_offset) { + return Err(Box::new(ValidationError { + context: "memory_size".into(), + problem: + "memorySize must be less than or equal to the size of memory minus memoryOffset" + .into(), + vuids: &["VUID-VkBindVideoSessionMemoryInfoKHR-memorySize-07202"], + ..Default::default() + })); + } + + Ok(()) + } +} + +impl BindVideoSessionMemoryInfo { + pub fn new( + memory_bind_index: u32, + memory: DeviceMemory, + memory_offset: DeviceSize, + memory_size: DeviceSize, + ) -> Self { + Self { + memory_bind_index, + memory, + memory_offset, + memory_size, + ne: crate::NonExhaustive(()), + } + } + + pub(crate) fn to_vulkan(&self) -> ash::vk::BindVideoSessionMemoryInfoKHR { + ash::vk::BindVideoSessionMemoryInfoKHR { + memory_bind_index: self.memory_bind_index, + memory: self.memory.handle(), + memory_offset: self.memory_offset, + memory_size: self.memory_size, + ..Default::default() + } + } +}