use {
super::{physical_device::PhysicalDevice, DriverError},
ash::{extensions::ext, vk, Entry},
log::{debug, error, info, logger, trace, warn, Level, Metadata},
std::{
env::var,
ffi::{c_void, CStr, CString},
fmt::{Debug, Formatter},
ops::Deref,
os::raw::c_char,
process::id,
thread::{current, panicking, park},
},
};
#[cfg(target_os = "macos")]
use std::env::set_var;
unsafe extern "system" fn vulkan_debug_callback(
_flags: vk::DebugReportFlagsEXT,
_obj_type: vk::DebugReportObjectTypeEXT,
_src_obj: u64,
_location: usize,
_msg_code: i32,
_layer_prefix: *const c_char,
message: *const c_char,
_user_data: *mut c_void,
) -> u32 {
let message = CStr::from_ptr(message).to_str().unwrap();
if message.starts_with("Validation Warning: [ UNASSIGNED-BestPractices-pipeline-stage-flags ]")
{
warn!("{}", message);
} else {
let prefix = "Validation Error: [ ";
let (vuid, message) = if message.starts_with(prefix) {
let (vuid, message) = message
.trim_start_matches(prefix)
.split_once(" ]")
.unwrap_or_default();
let message = message.split(" | ").nth(2).unwrap_or(message);
(Some(vuid.trim()), message)
} else {
(None, message)
};
if let Some(vuid) = vuid {
info!("{vuid}");
}
error!("🆘 {message}");
if !logger().enabled(&Metadata::builder().level(Level::Debug).build())
|| var("RUST_LOG")
.map(|rust_log| rust_log.is_empty())
.unwrap_or(true)
{
panic!("run with `RUST_LOG=trace` environment variable to display more information - see https://github.com/rust-lang/log#in-executables");
}
if current().name() != Some("main") {
warn!("executing on a child thread!")
}
debug!(
"🛑 PARKING THREAD `{}` -> attach debugger to pid {}!",
current().name().unwrap_or_default(),
id()
);
logger().flush();
park();
}
vk::FALSE
}
pub struct Instance {
_debug_callback: Option<vk::DebugReportCallbackEXT>,
#[allow(deprecated)] _debug_loader: Option<ext::DebugReport>,
debug_utils: Option<ext::DebugUtils>,
entry: Entry,
instance: ash::Instance,
}
impl Instance {
#[profiling::function]
pub fn create<'a>(
debug: bool,
required_extensions: impl Iterator<Item = &'a CStr>,
) -> Result<Self, DriverError> {
#[cfg(target_os = "macos")]
set_var("MVK_CONFIG_USE_METAL_ARGUMENT_BUFFERS", "1");
#[cfg(not(target_os = "macos"))]
let entry = unsafe {
Entry::load().map_err(|err| {
error!("Vulkan driver not found: {err}");
DriverError::Unsupported
})?
};
#[cfg(target_os = "macos")]
let entry = ash_molten::load();
let required_extensions = required_extensions.collect::<Vec<_>>();
let instance_extensions = required_extensions
.iter()
.map(|ext| ext.as_ptr())
.chain(unsafe { Self::extension_names(debug).into_iter() })
.collect::<Box<[_]>>();
let layer_names = Self::layer_names(debug);
let layer_names: Vec<*const i8> = layer_names
.iter()
.map(|raw_name| raw_name.as_ptr())
.collect();
let app_desc = vk::ApplicationInfo::builder().api_version(vk::API_VERSION_1_2);
let instance_desc = vk::InstanceCreateInfo::builder()
.application_info(&app_desc)
.enabled_layer_names(&layer_names)
.enabled_extension_names(&instance_extensions);
let instance = unsafe {
entry.create_instance(&instance_desc, None).map_err(|_| {
if debug {
warn!("debug may only be enabled with a valid Vulkan SDK installation");
}
error!("Vulkan driver does not support API v1.2");
for layer_name in Self::layer_names(debug) {
debug!("Layer: {:?}", layer_name);
}
for extension_name in required_extensions {
debug!("Extension: {:?}", extension_name);
}
DriverError::Unsupported
})?
};
trace!("created a Vulkan instance");
let (debug_loader, debug_callback, debug_utils) = if debug {
let debug_info = vk::DebugReportCallbackCreateInfoEXT {
flags: vk::DebugReportFlagsEXT::ERROR
| vk::DebugReportFlagsEXT::WARNING
| vk::DebugReportFlagsEXT::PERFORMANCE_WARNING,
pfn_callback: Some(vulkan_debug_callback),
..Default::default()
};
#[allow(deprecated)]
let debug_loader = ext::DebugReport::new(&entry, &instance);
let debug_callback = unsafe {
#[allow(deprecated)]
debug_loader
.create_debug_report_callback(&debug_info, None)
.unwrap()
};
let debug_utils = ext::DebugUtils::new(&entry, &instance);
(Some(debug_loader), Some(debug_callback), Some(debug_utils))
} else {
(None, None, None)
};
Ok(Self {
_debug_callback: debug_callback,
_debug_loader: debug_loader,
debug_utils,
entry,
instance,
})
}
#[profiling::function]
pub fn load(entry: Entry, instance: vk::Instance) -> Result<Self, DriverError> {
if instance == vk::Instance::null() {
return Err(DriverError::InvalidData);
}
let instance = unsafe { ash::Instance::load(entry.static_fn(), instance) };
Ok(Self {
_debug_callback: None,
_debug_loader: None,
debug_utils: None,
entry,
instance,
})
}
pub fn entry(this: &Self) -> &Entry {
&this.entry
}
unsafe fn extension_names(debug: bool) -> Vec<*const i8> {
let mut res = vec![];
if debug {
#[allow(deprecated)]
res.push(ext::DebugReport::name().as_ptr());
res.push(vk::ExtDebugUtilsFn::name().as_ptr());
}
res
}
pub fn is_debug(this: &Self) -> bool {
this.debug_utils.is_some()
}
fn layer_names(debug: bool) -> Vec<CString> {
let mut res = Vec::new();
if debug {
if let Ok(name) = CString::new("VK_LAYER_KHRONOS_validation") {
res.push(name);
}
}
res
}
#[profiling::function]
pub fn physical_devices(this: &Self) -> Result<Vec<PhysicalDevice>, DriverError> {
let physical_devices = unsafe { this.enumerate_physical_devices() };
Ok(physical_devices
.map_err(|err| {
error!("unable to enumerate physical devices: {err}");
DriverError::Unsupported
})?
.into_iter()
.enumerate()
.filter_map(|(idx, physical_device)| {
let res = PhysicalDevice::new(this, physical_device);
if let Err(err) = &res {
warn!("unable to create physical device at index {idx}: {err}");
}
res.ok().filter(|physical_device| {
let major = vk::api_version_major(physical_device.properties_v1_0.api_version);
let minor = vk::api_version_minor(physical_device.properties_v1_0.api_version);
let supports_vulkan_1_2 = major > 1 || (major == 1 && minor >= 2);
if !supports_vulkan_1_2 {
warn!(
"physical device `{}` does not support Vulkan v1.2",
physical_device.properties_v1_0.device_name
);
}
supports_vulkan_1_2
})
})
.collect())
}
}
impl Debug for Instance {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str("Instance")
}
}
impl Deref for Instance {
type Target = ash::Instance;
fn deref(&self) -> &Self::Target {
&self.instance
}
}
impl Drop for Instance {
#[profiling::function]
fn drop(&mut self) {
if panicking() {
return;
}
unsafe {
#[allow(deprecated)]
if let Some(debug_loader) = &self._debug_loader {
let debug_callback = self._debug_callback.unwrap();
debug_loader.destroy_debug_report_callback(debug_callback, None);
}
self.instance.destroy_instance(None);
}
}
}