#![allow(unsafe_code)]
#![allow(
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
clippy::cast_possible_wrap,
reason = "Vulkan FFI: every count/size here is driver-reported and small (queue families, \
DPB slots, memory requirement counts) — casts mirror vulkanalia's own generated \
builder code and mediaway-encoder-vulkan::session's identical allow."
)]
use thiserror::Error;
use vulkanalia::vk;
use vulkanalia::vk::{
DeviceV1_0, HasBuilder, InstanceV1_0, KhrVideoQueueExtensionDeviceCommands,
KhrVideoQueueExtensionInstanceCommands,
};
use crate::vulkan::dpb::DpbError;
use crate::vulkan::h264_params::H264ParamError;
pub enum DecodeProfile {
H264(vk::VideoDecodeH264ProfileInfoKHR),
Hevc(vk::VideoDecodeH265ProfileInfoKHR),
}
impl DecodeProfile {
#[must_use]
pub fn new_h264() -> Self {
Self::H264(
vk::VideoDecodeH264ProfileInfoKHR::builder()
.std_profile_idc(vulkanalia::vk::video::STD_VIDEO_H264_PROFILE_IDC_BASELINE)
.picture_layout(vk::VideoDecodeH264PictureLayoutFlagsKHR::PROGRESSIVE)
.build(),
)
}
#[must_use]
pub fn new_hevc() -> Self {
Self::Hevc(
vk::VideoDecodeH265ProfileInfoKHR::builder()
.std_profile_idc(vulkanalia::vk::video::STD_VIDEO_H265_PROFILE_IDC_MAIN)
.build(),
)
}
pub fn info(&mut self) -> vk::VideoProfileInfoKHR {
let base = vk::VideoProfileInfoKHR::builder()
.chroma_subsampling(vk::VideoChromaSubsamplingFlagsKHR::_420)
.luma_bit_depth(vk::VideoComponentBitDepthFlagsKHR::_8)
.chroma_bit_depth(vk::VideoComponentBitDepthFlagsKHR::_8);
match self {
Self::H264(h264) => base
.video_codec_operation(vk::VideoCodecOperationFlagsKHR::DECODE_H264)
.push_next(h264)
.build(),
Self::Hevc(hevc) => base
.video_codec_operation(vk::VideoCodecOperationFlagsKHR::DECODE_H265)
.push_next(hevc)
.build(),
}
}
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum VulkanDecodeError {
#[error("failed to load the Vulkan loader: {0}")]
Loader(Box<dyn vulkanalia::loader::LoaderError>),
#[error("vkCreateInstance failed: {0:?}")]
CreateInstance(vk::ErrorCode),
#[error("vkEnumeratePhysicalDevices failed: {0:?}")]
EnumeratePhysicalDevices(vk::ErrorCode),
#[error(
"no physical device advertises a VK_VIDEO_CODEC_OPERATION_DECODE_H264_BIT_KHR queue family"
)]
NoDecodeCapableDevice,
#[error("{call} failed: {result:?}")]
VkCall {
call: &'static str,
result: vk::ErrorCode,
},
#[error("driver reported no VK_KHR_video_decode_queue image format for usage {usage:?}")]
NoVideoFormat {
usage: vk::ImageUsageFlags,
},
#[error("driver requires separate DPB/output images (DPB_AND_OUTPUT_COINCIDE not advertised)")]
SeparateReferenceImagesRequired,
#[error(
"requested {width}x{height} outside driver-reported coded-extent bounds \
{min_width}x{min_height}..={max_width}x{max_height} (granularity \
{granularity_width}x{granularity_height})"
)]
UnsupportedResolution {
width: u32,
height: u32,
min_width: u32,
min_height: u32,
max_width: u32,
max_height: u32,
granularity_width: u32,
granularity_height: u32,
},
#[error(
"no memory type matches requirements (type_bits={type_bits:#x}, required={required:?})"
)]
NoMemoryType {
type_bits: u32,
required: vk::MemoryPropertyFlags,
},
#[error(transparent)]
Bitstream(#[from] H264ParamError),
#[error(transparent)]
HevcBitstream(#[from] crate::vulkan::hevc_params::HevcParamError),
#[error(transparent)]
Dpb(#[from] DpbError),
#[error("no active SPS/PPS for this packet (id {id})")]
MissingParameterSet {
id: u32,
},
}
pub(crate) struct InstanceGuard {
pub(crate) instance: vulkanalia::Instance,
}
impl Drop for InstanceGuard {
fn drop(&mut self) {
unsafe { self.instance.destroy_instance(None) };
}
}
pub(crate) struct DeviceGuard {
pub(crate) device: vulkanalia::Device,
}
impl Drop for DeviceGuard {
fn drop(&mut self) {
unsafe { self.device.destroy_device(None) };
}
}
pub(crate) struct DecodeDevice<'a> {
pub(crate) device: &'a vulkanalia::Device,
pub(crate) queue: vk::Queue,
pub(crate) queue_family_index: u32,
}
pub(crate) fn create_instance() -> Result<(vulkanalia::Entry, InstanceGuard), VulkanDecodeError> {
let loader = unsafe { vulkanalia::loader::LibloadingLoader::new(vulkanalia::loader::LIBRARY) }
.map_err(|error| VulkanDecodeError::Loader(error.into()))?;
let entry = unsafe { vulkanalia::Entry::new(loader) }.map_err(VulkanDecodeError::Loader)?;
let app_info = vk::ApplicationInfo::builder()
.application_name(b"mediaway-decoder-vulkan-session\0")
.api_version(vk::make_version(1, 3, 0));
let create_info = vk::InstanceCreateInfo::builder().application_info(&app_info);
let instance = unsafe { entry.create_instance(&create_info, None) }
.map_err(VulkanDecodeError::CreateInstance)?;
Ok((entry, InstanceGuard { instance }))
}
fn find_decode_device(
instance: &vulkanalia::Instance,
op: vk::VideoCodecOperationFlagsKHR,
) -> Result<(vk::PhysicalDevice, u32), VulkanDecodeError> {
let physical_devices = unsafe { instance.enumerate_physical_devices() }
.map_err(VulkanDecodeError::EnumeratePhysicalDevices)?;
for physical_device in physical_devices {
let family_count =
unsafe { instance.get_physical_device_queue_family_properties(physical_device) }.len();
let mut video_props: Vec<vk::QueueFamilyVideoPropertiesKHR> = (0..family_count)
.map(|_| vk::QueueFamilyVideoPropertiesKHR::default())
.collect();
let mut families2: Vec<vk::QueueFamilyProperties2> = video_props
.iter_mut()
.map(|entry| {
vk::QueueFamilyProperties2::builder()
.push_next(entry)
.build()
})
.collect();
unsafe {
let mut written = u32::try_from(family_count).unwrap_or(u32::MAX);
(instance
.commands()
.get_physical_device_queue_family_properties2)(
physical_device,
&raw mut written,
families2.as_mut_ptr(),
);
}
let found = video_props
.iter()
.position(|p| p.video_codec_operations.contains(op));
if let Some(index) = found {
let queue_family_index = u32::try_from(index).unwrap_or(u32::MAX);
return Ok((physical_device, queue_family_index));
}
}
Err(VulkanDecodeError::NoDecodeCapableDevice)
}
pub(crate) fn find_h264_decode_device(
instance: &vulkanalia::Instance,
) -> Result<(vk::PhysicalDevice, u32), VulkanDecodeError> {
find_decode_device(instance, vk::VideoCodecOperationFlagsKHR::DECODE_H264)
}
pub(crate) fn find_hevc_decode_device(
instance: &vulkanalia::Instance,
) -> Result<(vk::PhysicalDevice, u32), VulkanDecodeError> {
find_decode_device(instance, vk::VideoCodecOperationFlagsKHR::DECODE_H265)
}
pub(crate) struct Capabilities {
pub(crate) min_coded_extent: vk::Extent2D,
pub(crate) max_coded_extent: vk::Extent2D,
pub(crate) picture_access_granularity: vk::Extent2D,
pub(crate) max_dpb_slots: u32,
pub(crate) max_active_reference_pictures: u32,
pub(crate) std_header_version: vk::ExtensionProperties,
pub(crate) min_bitstream_buffer_size_alignment: vk::DeviceSize,
}
impl Capabilities {
pub(crate) const fn validate_requested_extent(
&self,
width: u32,
height: u32,
) -> Result<(), VulkanDecodeError> {
let in_range = width >= self.min_coded_extent.width
&& width <= self.max_coded_extent.width
&& height >= self.min_coded_extent.height
&& height <= self.max_coded_extent.height;
let aligned = self.picture_access_granularity.width != 0
&& self.picture_access_granularity.height != 0
&& width % self.picture_access_granularity.width == 0
&& height % self.picture_access_granularity.height == 0;
if in_range && aligned {
return Ok(());
}
Err(VulkanDecodeError::UnsupportedResolution {
width,
height,
min_width: self.min_coded_extent.width,
min_height: self.min_coded_extent.height,
max_width: self.max_coded_extent.width,
max_height: self.max_coded_extent.height,
granularity_width: self.picture_access_granularity.width,
granularity_height: self.picture_access_granularity.height,
})
}
}
pub(crate) fn query_capabilities(
instance: &vulkanalia::Instance,
physical_device: vk::PhysicalDevice,
profile: &mut DecodeProfile,
) -> Result<Capabilities, VulkanDecodeError> {
let is_hevc = matches!(profile, DecodeProfile::Hevc(_));
let profile_info = profile.info();
let mut h264_caps = vk::VideoDecodeH264CapabilitiesKHR::default();
let mut hevc_caps = vk::VideoDecodeH265CapabilitiesKHR::default();
let mut decode_caps = vk::VideoDecodeCapabilitiesKHR::default();
let mut caps_builder = vk::VideoCapabilitiesKHR::builder().push_next(&mut decode_caps);
caps_builder = if is_hevc {
caps_builder.push_next(&mut hevc_caps)
} else {
caps_builder.push_next(&mut h264_caps)
};
let mut caps = caps_builder.build();
let result = unsafe {
instance.get_physical_device_video_capabilities_khr(
physical_device,
&profile_info,
&mut caps,
)
};
result.map_err(|result| VulkanDecodeError::VkCall {
call: "vkGetPhysicalDeviceVideoCapabilitiesKHR",
result,
})?;
if !decode_caps
.flags
.contains(vk::VideoDecodeCapabilityFlagsKHR::DPB_AND_OUTPUT_COINCIDE)
{
return Err(VulkanDecodeError::SeparateReferenceImagesRequired);
}
Ok(Capabilities {
min_coded_extent: caps.min_coded_extent,
max_coded_extent: caps.max_coded_extent,
picture_access_granularity: caps.picture_access_granularity,
max_dpb_slots: caps.max_dpb_slots,
max_active_reference_pictures: caps.max_active_reference_pictures,
std_header_version: caps.std_header_version,
min_bitstream_buffer_size_alignment: caps.min_bitstream_buffer_size_alignment,
})
}
pub(crate) fn query_video_format(
instance: &vulkanalia::Instance,
physical_device: vk::PhysicalDevice,
profile: &mut DecodeProfile,
usage: vk::ImageUsageFlags,
) -> Result<vk::Format, VulkanDecodeError> {
let profile_info = profile.info();
let mut profile_list = vk::VideoProfileListInfoKHR::builder()
.profiles(std::slice::from_ref(&profile_info))
.build();
let format_info = vk::PhysicalDeviceVideoFormatInfoKHR::builder()
.image_usage(usage)
.push_next(&mut profile_list)
.build();
let formats = unsafe {
instance.get_physical_device_video_format_properties_khr(physical_device, &format_info)
}
.map_err(|result| VulkanDecodeError::VkCall {
call: "vkGetPhysicalDeviceVideoFormatPropertiesKHR",
result,
})?;
formats
.first()
.map(|f| f.format)
.ok_or(VulkanDecodeError::NoVideoFormat { usage })
}
pub(crate) fn create_logical_device(
instance: &vulkanalia::Instance,
physical_device: vk::PhysicalDevice,
queue_family_index: u32,
) -> Result<DeviceGuard, VulkanDecodeError> {
let queue_priorities = [1.0f32];
let queue_create_infos = [vk::DeviceQueueCreateInfo::builder()
.queue_family_index(queue_family_index)
.queue_priorities(&queue_priorities)
.build()];
let extension_names: [*const std::ffi::c_char; 4] = [
vk::KHR_VIDEO_QUEUE_EXTENSION.name.as_ptr(),
vk::KHR_VIDEO_DECODE_QUEUE_EXTENSION.name.as_ptr(),
vk::KHR_VIDEO_DECODE_H264_EXTENSION.name.as_ptr(),
vk::KHR_VIDEO_DECODE_H265_EXTENSION.name.as_ptr(),
];
let create_info = vk::DeviceCreateInfo::builder()
.queue_create_infos(&queue_create_infos)
.enabled_extension_names(&extension_names);
let device = unsafe { instance.create_device(physical_device, &create_info, None) }.map_err(
|result| VulkanDecodeError::VkCall {
call: "vkCreateDevice",
result,
},
)?;
Ok(DeviceGuard { device })
}
pub(crate) fn find_memory_type(
memory_properties: &vk::PhysicalDeviceMemoryProperties,
type_bits: u32,
required: vk::MemoryPropertyFlags,
) -> Result<u32, VulkanDecodeError> {
for i in 0..memory_properties.memory_type_count {
let bit_set = (type_bits >> i) & 1 == 1;
let props_match = memory_properties.memory_types[i as usize]
.property_flags
.contains(required);
if bit_set && props_match {
return Ok(i);
}
}
Err(VulkanDecodeError::NoMemoryType {
type_bits,
required,
})
}
pub(crate) fn create_video_session(
decode_device: &DecodeDevice<'_>,
memory_properties: &vk::PhysicalDeviceMemoryProperties,
profile: &mut DecodeProfile,
capabilities: &Capabilities,
coded_extent: vk::Extent2D,
picture_format: vk::Format,
max_dpb_slots: u32,
max_active_reference_pictures: u32,
) -> Result<(vk::VideoSessionKHR, Vec<vk::DeviceMemory>), VulkanDecodeError> {
let device = decode_device.device;
let profile_info = profile.info();
let create_info = vk::VideoSessionCreateInfoKHR::builder()
.queue_family_index(decode_device.queue_family_index)
.video_profile(&profile_info)
.picture_format(picture_format)
.max_coded_extent(coded_extent)
.reference_picture_format(picture_format)
.max_dpb_slots(max_dpb_slots)
.max_active_reference_pictures(max_active_reference_pictures)
.std_header_version(&capabilities.std_header_version);
let session =
unsafe { device.create_video_session_khr(&create_info, None) }.map_err(|result| {
VulkanDecodeError::VkCall {
call: "vkCreateVideoSessionKHR",
result,
}
})?;
let reqs =
unsafe { device.get_video_session_memory_requirements_khr(session) }.map_err(|result| {
VulkanDecodeError::VkCall {
call: "vkGetVideoSessionMemoryRequirementsKHR",
result,
}
})?;
let mut memories = Vec::with_capacity(reqs.len());
for req in &reqs {
let type_index = find_memory_type(
memory_properties,
req.memory_requirements.memory_type_bits,
vk::MemoryPropertyFlags::empty(),
)?;
let alloc_info = vk::MemoryAllocateInfo::builder()
.allocation_size(req.memory_requirements.size)
.memory_type_index(type_index);
let memory = unsafe { device.allocate_memory(&alloc_info, None) }.map_err(|result| {
VulkanDecodeError::VkCall {
call: "vkAllocateMemory (video session)",
result,
}
})?;
memories.push(memory);
}
let binds: Vec<vk::BindVideoSessionMemoryInfoKHR> = reqs
.iter()
.zip(memories.iter())
.map(|(req, &memory)| {
vk::BindVideoSessionMemoryInfoKHR::builder()
.memory_bind_index(req.memory_bind_index)
.memory(memory)
.memory_offset(0)
.memory_size(req.memory_requirements.size)
.build()
})
.collect();
unsafe { device.bind_video_session_memory_khr(session, &binds) }.map_err(|result| {
VulkanDecodeError::VkCall {
call: "vkBindVideoSessionMemoryKHR",
result,
}
})?;
Ok((session, memories))
}
pub(crate) fn create_session_parameters_h264(
decode_device: &DecodeDevice<'_>,
session: vk::VideoSessionKHR,
sps: &vulkanalia::vk::video::StdVideoH264SequenceParameterSet,
pps: &vulkanalia::vk::video::StdVideoH264PictureParameterSet,
) -> Result<vk::VideoSessionParametersKHR, VulkanDecodeError> {
let device = decode_device.device;
let add_info = vk::VideoDecodeH264SessionParametersAddInfoKHR::builder()
.std_sp_ss(std::slice::from_ref(sps))
.std_pp_ss(std::slice::from_ref(pps));
let mut h264_create_info = vk::VideoDecodeH264SessionParametersCreateInfoKHR::builder()
.max_std_sps_count(1)
.max_std_pps_count(1)
.parameters_add_info(&add_info)
.build();
let create_info = vk::VideoSessionParametersCreateInfoKHR::builder()
.video_session(session)
.push_next(&mut h264_create_info);
unsafe { device.create_video_session_parameters_khr(&create_info, None) }.map_err(|result| {
VulkanDecodeError::VkCall {
call: "vkCreateVideoSessionParametersKHR",
result,
}
})
}
pub(crate) fn create_session_parameters_hevc(
decode_device: &DecodeDevice<'_>,
session: vk::VideoSessionKHR,
vps: &vulkanalia::vk::video::StdVideoH265VideoParameterSet,
sps: &vulkanalia::vk::video::StdVideoH265SequenceParameterSet,
pps: &vulkanalia::vk::video::StdVideoH265PictureParameterSet,
) -> Result<vk::VideoSessionParametersKHR, VulkanDecodeError> {
let device = decode_device.device;
let add_info = vk::VideoDecodeH265SessionParametersAddInfoKHR::builder()
.std_vp_ss(std::slice::from_ref(vps))
.std_sp_ss(std::slice::from_ref(sps))
.std_pp_ss(std::slice::from_ref(pps));
let mut hevc_create_info = vk::VideoDecodeH265SessionParametersCreateInfoKHR::builder()
.max_std_vps_count(1)
.max_std_sps_count(1)
.max_std_pps_count(1)
.parameters_add_info(&add_info)
.build();
let create_info = vk::VideoSessionParametersCreateInfoKHR::builder()
.video_session(session)
.push_next(&mut hevc_create_info);
unsafe { device.create_video_session_parameters_khr(&create_info, None) }.map_err(|result| {
VulkanDecodeError::VkCall {
call: "vkCreateVideoSessionParametersKHR",
result,
}
})
}