#![allow(unsafe_code)]
use std::ffi::CStr;
use thiserror::Error;
use vulkanalia::vk;
use vulkanalia::vk::{HasBuilder, InstanceV1_0};
#[derive(Debug, Clone)]
pub struct VulkanDecodeCapability {
pub device_name: String,
pub device_type: vk::PhysicalDeviceType,
pub h264_decode_queue_family: Option<u32>,
pub h265_decode_queue_family: Option<u32>,
pub av1_decode_queue_family: Option<u32>,
}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum VulkanDecodeProbeError {
#[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),
}
struct InstanceGuard {
instance: vulkanalia::Instance,
}
impl Drop for InstanceGuard {
fn drop(&mut self) {
unsafe { self.instance.destroy_instance(None) };
}
}
pub fn probe_video_decode_queue_families()
-> Result<Vec<VulkanDecodeCapability>, VulkanDecodeProbeError> {
let loader = unsafe { vulkanalia::loader::LibloadingLoader::new(vulkanalia::loader::LIBRARY) }
.map_err(|error| VulkanDecodeProbeError::Loader(error.into()))?;
let entry =
unsafe { vulkanalia::Entry::new(loader) }.map_err(VulkanDecodeProbeError::Loader)?;
let app_info = vk::ApplicationInfo::builder()
.application_name(b"mediaway-decoder-vulkan-probe\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(VulkanDecodeProbeError::CreateInstance)?;
let guard = InstanceGuard { instance };
let physical_devices = unsafe { guard.instance.enumerate_physical_devices() }
.map_err(VulkanDecodeProbeError::EnumeratePhysicalDevices)?;
let mut results = Vec::with_capacity(physical_devices.len());
for physical_device in physical_devices {
results.push(probe_one_device(&guard.instance, physical_device));
}
Ok(results)
}
fn probe_one_device(
instance: &vulkanalia::Instance,
physical_device: vk::PhysicalDevice,
) -> VulkanDecodeCapability {
let props = unsafe { instance.get_physical_device_properties(physical_device) };
let device_name = device_name_to_string(&props.device_name[..]);
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 h264_decode_queue_family =
find_family_with_codec(&video_props, vk::VideoCodecOperationFlagsKHR::DECODE_H264);
let h265_decode_queue_family =
find_family_with_codec(&video_props, vk::VideoCodecOperationFlagsKHR::DECODE_H265);
let av1_decode_queue_family =
find_family_with_codec(&video_props, vk::VideoCodecOperationFlagsKHR::DECODE_AV1);
VulkanDecodeCapability {
device_name,
device_type: props.device_type,
h264_decode_queue_family,
h265_decode_queue_family,
av1_decode_queue_family,
}
}
fn find_family_with_codec(
video_props: &[vk::QueueFamilyVideoPropertiesKHR],
codec: vk::VideoCodecOperationFlagsKHR,
) -> Option<u32> {
let index = video_props
.iter()
.position(|entry| entry.video_codec_operations.contains(codec))?;
Some(u32::try_from(index).unwrap_or(u32::MAX))
}
fn device_name_to_string(name: &[std::ffi::c_char]) -> String {
let cstr = unsafe { CStr::from_ptr(name.as_ptr()) };
cstr.to_string_lossy().into_owned()
}
#[cfg(test)]
#[path = "probe_tests.rs"]
mod tests;