use super::VulkanDevice;
use crate::unmanaged_interop::vulkan_global_api::fixed_name;
use crate::unmanaged_interop::{Utf8BufferArray, VulkanInstanceApi};
use crate::{
IVulkanDevice, IVulkanInstance, VulkanDeviceCreationOptions, VulkanException, VulkanExternalObjectsFeature,
VulkanPlatformSpecificOptions, VulkanResultExtensions,
};
use ash::vk;
use std::sync::Arc;
const VK_KHR_SWAPCHAIN: &str = "VK_KHR_swapchain";
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct DeviceInfo {
pub physical_device: vk::PhysicalDevice,
pub queue_family_index: u32,
pub type_: vk::PhysicalDeviceType,
pub extensions: Vec<String>,
pub queue_count: u32,
}
impl VulkanDevice {
pub fn create(
instance: Arc<dyn IVulkanInstance>,
options: &VulkanDeviceCreationOptions,
platform_options: &VulkanPlatformSpecificOptions,
) -> Result<Arc<dyn IVulkanDevice>, VulkanException> {
let api = VulkanInstanceApi::new(instance.clone());
let vk_instance: vk::Instance = vk::Handle::from_raw(instance.handle() as u64);
let devices = api.enumerate_physical_devices(vk_instance).throw_on_error("vkEnumeratePhysicalDevices")?;
if devices.is_empty() {
return Err(VulkanException::new("No devices found"));
}
let surface_for_probe_ptr =
platform_options.device_check_surface_factory.as_ref().map(|factory| factory(&instance)).transpose()?;
let surface_for_probe: Option<vk::SurfaceKHR> =
surface_for_probe_ptr.filter(|surface| *surface != 0).map(vk::Handle::from_raw);
let failure = std::cell::RefCell::new(None);
let compatible_device = select_device(
devices.iter().map(|device| match Self::check_device(&api, *device, options, surface_for_probe) {
Ok(info) => info,
Err(error) => {
failure.borrow_mut().get_or_insert(error);
None
}
}),
options.prefer_discrete_gpu,
|| failure.borrow().is_some(),
);
if let Some(failure) = failure.into_inner() {
return Err(failure);
}
let dev = compatible_device.ok_or_else(|| VulkanException::new("No compatible devices found"))?;
let queue_priorities = vec![1f32; dev.queue_count as usize];
let queue_create_infos = [vk::DeviceQueueCreateInfo::default()
.queue_family_index(dev.queue_family_index)
.queue_priorities(&queue_priorities)];
let enabled_extensions = device_extensions(
&options.device_extensions,
VulkanExternalObjectsFeature::required_device_extensions(),
&dev.extensions,
options.require_swapchain_extension,
);
let p_enabled_extensions = Utf8BufferArray::new(&enabled_extensions);
let create_info = vk::DeviceCreateInfo::default()
.queue_create_infos(&queue_create_infos)
.enabled_extension_names(p_enabled_extensions.as_slice());
let created_device = api.create_device(dev.physical_device, &create_info).throw_on_error("vkCreateDevice")?;
let created_queue = api.get_device_queue(created_device, dev.queue_family_index, 0);
Ok(VulkanDevice::new(
api,
created_device,
dev.physical_device,
created_queue,
dev.queue_family_index,
enabled_extensions,
))
}
fn get_device_extensions(instance: &VulkanInstanceApi, physical_device: vk::PhysicalDevice) -> Vec<String> {
instance
.enumerate_device_extension_properties(physical_device)
.unwrap_or_default()
.iter()
.map(|properties| fixed_name(&properties.extension_name))
.collect()
}
fn check_device(
instance: &VulkanInstanceApi,
physical_device: vk::PhysicalDevice,
options: &VulkanDeviceCreationOptions,
surface: Option<vk::SurfaceKHR>,
) -> Result<Option<DeviceInfo>, VulkanException> {
let properties = instance.get_physical_device_properties(physical_device);
let supported_extensions = Self::get_device_extensions(instance, physical_device);
if options.require_swapchain_extension && !supported_extensions.iter().any(|name| name == VK_KHR_SWAPCHAIN) {
return Ok(None);
}
let family_properties = instance.get_physical_device_queue_family_properties(physical_device);
let family = select_queue_family(&family_properties, options.require_compute_bit, |c| match surface {
Some(surface) => instance
.get_physical_device_surface_support_khr(physical_device, c, surface)
.throw_on_error("vkGetPhysicalDeviceSurfaceSupportKHR"),
None => Ok(true),
})?;
Ok(family.map(|c| DeviceInfo {
physical_device,
extensions: supported_extensions,
type_: properties.device_type,
queue_family_index: c,
queue_count: family_properties[c as usize].queue_count,
}))
}
}
pub(crate) fn select_queue_family(
family_properties: &[vk::QueueFamilyProperties],
require_compute_bit: bool,
mut supports_surface: impl FnMut(u32) -> Result<bool, VulkanException>,
) -> Result<Option<u32>, VulkanException> {
let mut required_flags = vk::QueueFlags::GRAPHICS;
if require_compute_bit {
required_flags |= vk::QueueFlags::COMPUTE;
}
for (c, family) in family_properties.iter().enumerate() {
if (family.queue_flags & required_flags) != required_flags {
continue;
}
if !supports_surface(c as u32)? {
continue;
}
return Ok(Some(c as u32));
}
Ok(None)
}
pub(crate) fn select_device(
devices: impl Iterator<Item = Option<DeviceInfo>>,
prefer_discrete_gpu: bool,
stop: impl Fn() -> bool,
) -> Option<DeviceInfo> {
let mut compatible_device: Option<DeviceInfo> = None;
let mut discrete_device: Option<DeviceInfo> = None;
for info in devices {
if stop() {
break;
}
if let Some(info) = info {
if info.type_ == vk::PhysicalDeviceType::DISCRETE_GPU && discrete_device.is_none() {
discrete_device = Some(info.clone());
}
if compatible_device.is_none() {
compatible_device = Some(info);
}
}
if compatible_device.is_some() && (discrete_device.is_some() || !prefer_discrete_gpu) {
break;
}
}
if prefer_discrete_gpu && discrete_device.is_some() {
compatible_device = discrete_device;
}
compatible_device
}
pub(crate) fn device_extensions(
options_extensions: &[String],
external_objects_extensions: &[&str],
supported_extensions: &[String],
require_swapchain_extension: bool,
) -> Vec<String> {
let mut enabled_extensions: Vec<String> = Vec::new();
let asked = options_extensions.iter().map(String::as_str).chain(external_objects_extensions.iter().copied());
for name in asked {
if supported_extensions.iter().any(|supported| supported == name)
&& !enabled_extensions.iter().any(|enabled| enabled == name)
{
enabled_extensions.push(name.to_string());
}
}
if require_swapchain_extension && !enabled_extensions.iter().any(|enabled| enabled == VK_KHR_SWAPCHAIN) {
enabled_extensions.push(VK_KHR_SWAPCHAIN.to_string());
}
enabled_extensions
}
#[cfg(test)]
mod tests {
use super::*;
fn family(flags: vk::QueueFlags, count: u32) -> vk::QueueFamilyProperties {
vk::QueueFamilyProperties { queue_flags: flags, queue_count: count, ..Default::default() }
}
fn info(id: u64, type_: vk::PhysicalDeviceType) -> Option<DeviceInfo> {
Some(DeviceInfo {
physical_device: vk::Handle::from_raw(id),
queue_family_index: 0,
type_,
extensions: Vec::new(),
queue_count: 1,
})
}
fn id(info: Option<DeviceInfo>) -> Option<u64> {
info.map(|info| vk::Handle::as_raw(info.physical_device))
}
#[test]
fn the_first_family_with_graphics_is_taken() {
let families = [
family(vk::QueueFlags::TRANSFER, 1),
family(vk::QueueFlags::GRAPHICS | vk::QueueFlags::TRANSFER, 4),
family(vk::QueueFlags::GRAPHICS | vk::QueueFlags::COMPUTE, 2),
];
assert_eq!(select_queue_family(&families, false, |_| Ok(true)).unwrap(), Some(1));
assert_eq!(select_queue_family(&families, true, |_| Ok(true)).unwrap(), Some(2));
assert_eq!(select_queue_family(&families[..1], false, |_| Ok(true)).unwrap(), None);
}
#[test]
fn a_family_that_cannot_present_is_passed_over() {
let families = [family(vk::QueueFlags::GRAPHICS, 1), family(vk::QueueFlags::GRAPHICS, 1)];
let mut asked = Vec::new();
let chosen = select_queue_family(&families, false, |c| {
asked.push(c);
Ok(c == 1)
})
.unwrap();
assert_eq!(chosen, Some(1));
assert_eq!(asked, [0, 1]);
assert_eq!(select_queue_family(&families, false, |_| Ok(false)).unwrap(), None);
}
#[test]
fn a_failure_of_the_surface_query_is_the_failure_of_the_check() {
let families = [family(vk::QueueFlags::GRAPHICS, 1)];
let error =
select_queue_family(&families, false, |_| Err(VulkanException::new("lost"))).unwrap_err();
assert_eq!(error.message(), "lost");
}
#[test]
fn the_first_compatible_device_is_taken_and_the_rest_is_not_checked() {
let mut checked = 0;
let devices = [None, info(2, vk::PhysicalDeviceType::CPU), info(3, vk::PhysicalDeviceType::DISCRETE_GPU)];
let chosen = select_device(devices.into_iter().inspect(|_| checked += 1), false, || false);
assert_eq!(id(chosen), Some(2));
assert_eq!(checked, 2);
}
#[test]
fn a_discrete_device_is_preferred_when_asked_for() {
let devices = || {
[
info(1, vk::PhysicalDeviceType::INTEGRATED_GPU),
info(2, vk::PhysicalDeviceType::DISCRETE_GPU),
info(3, vk::PhysicalDeviceType::DISCRETE_GPU),
]
.into_iter()
};
assert_eq!(id(select_device(devices(), true, || false)), Some(2));
assert_eq!(id(select_device(devices(), false, || false)), Some(1));
let devices = [info(1, vk::PhysicalDeviceType::INTEGRATED_GPU), info(2, vk::PhysicalDeviceType::CPU)];
assert_eq!(id(select_device(devices.into_iter(), true, || false)), Some(1));
}
#[test]
fn no_compatible_device_is_none() {
assert_eq!(select_device([None, None].into_iter(), true, || false), None);
assert_eq!(select_device(std::iter::empty(), false, || false), None);
}
#[test]
fn asked_extensions_are_enabled_when_the_device_has_them() {
let supported: Vec<String> =
["VK_KHR_swapchain", "VK_KHR_external_memory", "VK_B", "VK_KHR_dedicated_allocation"]
.iter()
.map(|s| s.to_string())
.collect();
let options = vec!["VK_A".to_string(), "VK_B".to_string(), "VK_B".to_string()];
let external = ["VK_KHR_external_memory", "VK_KHR_external_semaphore", "VK_KHR_dedicated_allocation"];
assert_eq!(
device_extensions(&options, &external, &supported, true),
["VK_B", "VK_KHR_external_memory", "VK_KHR_dedicated_allocation", "VK_KHR_swapchain"]
);
assert_eq!(
device_extensions(&options, &external, &supported, false),
["VK_B", "VK_KHR_external_memory", "VK_KHR_dedicated_allocation"]
);
}
#[test]
fn the_swap_chain_extension_is_enabled_once() {
let supported = vec!["VK_KHR_swapchain".to_string()];
let options = vec!["VK_KHR_swapchain".to_string()];
assert_eq!(device_extensions(&options, &[], &supported, true), ["VK_KHR_swapchain"]);
assert_eq!(device_extensions(&[], &[], &[], true), ["VK_KHR_swapchain"]);
}
}