use super::VulkanDebugLogger;
use crate::unmanaged_interop::vulkan_global_api::{fixed_name, proc_loader};
use crate::unmanaged_interop::{Utf8BufferArray, VulkanGlobalApi, VulkanInstanceApi};
use crate::{
IVulkanInstance, VkGetInstanceProcAddressDelegate, VulkanException, VulkanExternalObjectsFeature, VulkanHelpers,
VulkanInstanceCreationOptions, VulkanPlatformSpecificOptions, VulkanResultExtensions,
};
use ash::vk;
use std::collections::HashSet;
use std::ffi::CString;
use std::sync::Arc;
const DEFAULT_NAME: &str = "FerroUI";
pub(crate) struct VulkanInstance {
handle: vk::Instance,
get_proc_address: VkGetInstanceProcAddressDelegate,
fp: ash::InstanceFnV1_0,
enabled_extensions: Vec<String>,
}
impl VulkanInstance {
pub fn new(
handle: vk::Instance,
get_proc_address: VkGetInstanceProcAddressDelegate,
enabled_extensions: Vec<String>,
) -> Self {
let raw = vk::Handle::as_raw(handle) as isize;
let fp = ash::InstanceFnV1_0::load(proc_loader(|name| get_proc_address(raw, name)));
Self { handle, get_proc_address, fp, enabled_extensions }
}
pub fn create(
options: &VulkanInstanceCreationOptions,
platform_options: &VulkanPlatformSpecificOptions,
) -> Result<Arc<dyn IVulkanInstance>, VulkanException> {
let get_proc_address = options
.custom_get_proc_address_delegate
.clone()
.or_else(|| platform_options.get_proc_address_delegate.clone())
.ok_or_else(|| VulkanException::new("No VkGetInstanceProcAddr provided"))?;
let name = c_string(options.application_name.as_deref().unwrap_or(DEFAULT_NAME));
let engine_name = c_string(DEFAULT_NAME);
let gapi = VulkanGlobalApi::try_new(get_proc_address.clone())?;
let supported_extensions = Self::get_supported_extensions(&gapi)?;
let app_info = vk::ApplicationInfo::default()
.api_version(VulkanHelpers::make_version_of(options.vulkan_version))
.application_version(VulkanHelpers::make_version(1, 0, 0))
.engine_version(VulkanHelpers::make_version(1, 0, 0))
.application_name(&name)
.engine_name(&engine_name);
let enabled_extensions = instance_extensions(options, platform_options, &supported_extensions);
let mut enabled_layers = options.enabled_layers.clone();
if options.use_debug && Self::is_layer_available(&gapi, "VK_LAYER_KHRONOS_validation")? {
enabled_layers.push("VK_LAYER_KHRONOS_validation".to_string());
}
let enabled_extension_buffers = Utf8BufferArray::new(distinct_names(&enabled_extensions));
let enabled_layers_buffers = Utf8BufferArray::new(distinct_names(&enabled_layers));
let create_info = vk::InstanceCreateInfo::default()
.application_info(&app_info)
.enabled_layer_names(enabled_layers_buffers.as_slice())
.enabled_extension_names(enabled_extension_buffers.as_slice());
let p_instance = gapi.vk_create_instance(&create_info).throw_on_error("vkCreateInstance")?;
let instance: Arc<dyn IVulkanInstance> =
Arc::new(VulkanInstance::new(p_instance, get_proc_address, enabled_extensions));
let instance_api = VulkanInstanceApi::new(instance.clone());
if options.use_debug {
let debug_create_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
.message_severity(
vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE
| vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
| vk::DebugUtilsMessageSeverityFlagsEXT::INFO
| vk::DebugUtilsMessageSeverityFlagsEXT::ERROR,
)
.message_type(
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
| vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
| vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
)
.pfn_user_callback(VulkanDebugLogger::callback_ptr());
if instance.enabled_extensions().iter().any(|name| name == "VK_EXT_debug_utils") {
let _ = instance_api.create_debug_utils_messenger_ext(p_instance, &debug_create_info);
}
}
Ok(instance)
}
fn is_layer_available(api: &VulkanGlobalApi, layer_name: &str) -> Result<bool, VulkanException> {
let layer_properties =
api.enumerate_instance_layer_properties().throw_on_error("vkEnumerateInstanceLayerProperties")?;
Ok(layer_properties.iter().any(|properties| fixed_name(&properties.layer_name) == layer_name))
}
fn get_supported_extensions(api: &VulkanGlobalApi) -> Result<HashSet<String>, VulkanException> {
let properties = api
.vk_enumerate_instance_extension_properties()
.throw_on_error("vkEnumerateInstanceExtensionProperties")?;
Ok(properties.iter().map(|properties| fixed_name(&properties.extension_name)).collect())
}
}
impl IVulkanInstance for VulkanInstance {
fn handle(&self) -> isize {
vk::Handle::as_raw(self.handle) as isize
}
fn get_instance_proc_address(&self, instance: isize, name: &str) -> isize {
(self.get_proc_address)(instance, name)
}
fn get_device_proc_address(&self, device: isize, name: &str) -> isize {
let buf = c_string(name);
let address =
unsafe { (self.fp.get_device_proc_addr)(vk::Handle::from_raw(device as u64), buf.as_ptr()) };
address.map_or(0, |address| address as usize as isize)
}
fn enabled_extensions(&self) -> Vec<String> {
self.enabled_extensions.clone()
}
}
fn c_string(s: &str) -> CString {
let end = s.find('\0').unwrap_or(s.len());
CString::new(&s[..end]).expect("the string was cut at its first zero character")
}
fn distinct_names(names: &[String]) -> Vec<&str> {
let mut seen = HashSet::new();
names.iter().map(String::as_str).filter(|x| !x.trim().is_empty() && seen.insert(*x)).collect()
}
pub(crate) fn instance_extensions(
options: &VulkanInstanceCreationOptions,
platform_options: &VulkanPlatformSpecificOptions,
supported_extensions: &HashSet<String>,
) -> Vec<String> {
let mut enabled_extensions: Vec<String> = Vec::new();
let add = |enabled_extensions: &mut Vec<String>, name: &str| {
if !enabled_extensions.iter().any(|x| x == name) {
enabled_extensions.push(name.to_string());
}
};
for name in options.instance_extensions.iter().chain(platform_options.required_instance_extensions.iter()) {
add(&mut enabled_extensions, name);
}
if options.require_surface_extension {
add(&mut enabled_extensions, "VK_KHR_surface");
}
let add_extensions_if_supported = |enabled_extensions: &mut Vec<String>, names: &[&str]| {
if names.iter().all(|n| supported_extensions.contains(*n)) {
for n in names {
add(enabled_extensions, n);
}
}
};
if options.use_debug {
add_extensions_if_supported(&mut enabled_extensions, &["VK_EXT_debug_utils"]);
}
add_extensions_if_supported(&mut enabled_extensions, VulkanExternalObjectsFeature::REQUIRED_INSTANCE_EXTENSIONS);
enabled_extensions
}
#[cfg(test)]
mod tests {
use super::*;
fn supported(names: &[&str]) -> HashSet<String> {
names.iter().map(|name| name.to_string()).collect()
}
#[test]
fn the_surface_extension_is_required_by_default() {
let extensions = instance_extensions(&Default::default(), &Default::default(), &supported(&[]));
assert_eq!(extensions, ["VK_KHR_surface"]);
let options = VulkanInstanceCreationOptions { require_surface_extension: false, ..Default::default() };
assert!(instance_extensions(&options, &Default::default(), &supported(&[])).is_empty());
}
#[test]
fn the_extensions_of_the_options_and_of_the_platform_come_first_and_once() {
let options = VulkanInstanceCreationOptions {
instance_extensions: vec!["VK_A".into(), "VK_KHR_xlib_surface".into()],
..Default::default()
};
let platform = VulkanPlatformSpecificOptions {
required_instance_extensions: vec!["VK_KHR_xlib_surface".into(), "VK_KHR_surface".into()],
..Default::default()
};
let extensions = instance_extensions(&options, &platform, &supported(&[]));
assert_eq!(extensions, ["VK_A", "VK_KHR_xlib_surface", "VK_KHR_surface"]);
}
#[test]
fn the_debug_extension_is_added_when_asked_for_and_supported() {
let debug = VulkanInstanceCreationOptions { use_debug: true, ..Default::default() };
let with = supported(&["VK_EXT_debug_utils"]);
assert_eq!(instance_extensions(&debug, &Default::default(), &with), ["VK_KHR_surface", "VK_EXT_debug_utils"]);
assert_eq!(instance_extensions(&debug, &Default::default(), &supported(&[])), ["VK_KHR_surface"]);
assert_eq!(instance_extensions(&Default::default(), &Default::default(), &with), ["VK_KHR_surface"]);
}
#[test]
fn the_external_objects_extensions_are_added_only_together() {
let all = supported(&[
"VK_KHR_get_physical_device_properties2",
"VK_KHR_external_memory_capabilities",
"VK_KHR_external_semaphore_capabilities",
]);
assert_eq!(
instance_extensions(&Default::default(), &Default::default(), &all),
[
"VK_KHR_surface",
"VK_KHR_get_physical_device_properties2",
"VK_KHR_external_memory_capabilities",
"VK_KHR_external_semaphore_capabilities"
]
);
let two = supported(&["VK_KHR_get_physical_device_properties2", "VK_KHR_external_memory_capabilities"]);
assert_eq!(instance_extensions(&Default::default(), &Default::default(), &two), ["VK_KHR_surface"]);
}
#[test]
fn blank_and_repeated_names_are_not_given_to_vulkan() {
let names = vec!["VK_A".to_string(), " ".to_string(), String::new(), "VK_B".to_string(), "VK_A".to_string()];
assert_eq!(distinct_names(&names), ["VK_A", "VK_B"]);
}
#[test]
fn without_a_way_to_the_entry_points_no_instance_is_created() {
let error = VulkanInstance::create(&Default::default(), &Default::default()).err().unwrap();
assert_eq!(error.message(), "No VkGetInstanceProcAddr provided");
}
#[test]
fn the_names_given_to_vulkan_are_those_of_the_port() {
assert_eq!(DEFAULT_NAME, "FerroUI");
}
}