357 lines
13 KiB
Rust
357 lines
13 KiB
Rust
use std::{
|
|
collections::HashSet,
|
|
ffi::{c_char, c_void, CStr, CString},
|
|
sync::Arc,
|
|
};
|
|
|
|
use ash::{ext::debug_utils, vk};
|
|
use winit::raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle};
|
|
|
|
use crate::{
|
|
error::{GfxHalError, Result},
|
|
physical_device::PhysicalDevice,
|
|
surface::Surface,
|
|
};
|
|
|
|
unsafe extern "system" fn vulkan_debug_callback(
|
|
message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
|
|
message_type: vk::DebugUtilsMessageTypeFlagsEXT,
|
|
p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT,
|
|
_p_user_data: *mut c_void,
|
|
) -> vk::Bool32 {
|
|
let callback_data = *p_callback_data;
|
|
let message_id_number: i32 = callback_data.message_id_number;
|
|
let message_id_name = if callback_data.p_message_id_name.is_null() {
|
|
std::borrow::Cow::from("")
|
|
} else {
|
|
CStr::from_ptr(callback_data.p_message_id_name).to_string_lossy()
|
|
};
|
|
let message = if callback_data.p_message.is_null() {
|
|
std::borrow::Cow::from("")
|
|
} else {
|
|
CStr::from_ptr(callback_data.p_message).to_string_lossy()
|
|
};
|
|
|
|
let severity = match message_severity {
|
|
vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => "[VERBOSE]",
|
|
vk::DebugUtilsMessageSeverityFlagsEXT::INFO => "[INFO]",
|
|
vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => "[WARNING]",
|
|
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => "[ERROR]",
|
|
_ => "[UNKNOWN SEVERITY]",
|
|
};
|
|
let ty = match message_type {
|
|
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL => "[GENERAL]",
|
|
vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION => "[VALIDATION]",
|
|
vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE => "[PERFORMANCE]",
|
|
_ => "[UNKNOWN TYPE]",
|
|
};
|
|
|
|
// Use the tracing crate for output
|
|
match message_severity {
|
|
vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => {
|
|
tracing::debug!(
|
|
"{} {} ({}:{}) {}",
|
|
severity,
|
|
ty,
|
|
message_id_name,
|
|
message_id_number,
|
|
message
|
|
);
|
|
}
|
|
vk::DebugUtilsMessageSeverityFlagsEXT::INFO => {
|
|
tracing::info!(
|
|
"{} {} ({}:{}) {}",
|
|
severity,
|
|
ty,
|
|
message_id_name,
|
|
message_id_number,
|
|
message
|
|
);
|
|
}
|
|
vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => {
|
|
tracing::warn!(
|
|
"{} {} ({}:{}) {}",
|
|
severity,
|
|
ty,
|
|
message_id_name,
|
|
message_id_number,
|
|
message
|
|
);
|
|
}
|
|
// Treat ERROR and higher as errors
|
|
_ => {
|
|
tracing::error!(
|
|
"{} {} ({}:{}) {}",
|
|
severity,
|
|
ty,
|
|
message_id_name,
|
|
message_id_number,
|
|
message
|
|
);
|
|
}
|
|
}
|
|
|
|
vk::FALSE // Standard return value
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub struct InstanceConfig {
|
|
pub application_name: String,
|
|
pub engine_name: String,
|
|
pub application_version: u32,
|
|
pub engine_version: u32,
|
|
/// Enable Vulkan validation layers
|
|
pub enable_validation: bool,
|
|
/// Additional required instance extensions beyond surface/debug.
|
|
pub required_extensions: Vec<&'static CStr>,
|
|
}
|
|
|
|
impl Default for InstanceConfig {
|
|
fn default() -> Self {
|
|
InstanceConfig {
|
|
application_name: "Defualt App".to_string(),
|
|
engine_name: "Default Engine".to_string(),
|
|
application_version: vk::make_api_version(0, 1, 0, 0),
|
|
engine_version: vk::make_api_version(0, 1, 0, 0),
|
|
enable_validation: cfg!(debug_assertions),
|
|
required_extensions: Vec::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Represents the Vulkan API Instance
|
|
///
|
|
/// Owns the `ash::Entry`, `ash::Instance` and potentially the debug messenger.
|
|
/// This is the starting point for interacting with Vulkan
|
|
pub struct Instance {
|
|
entry: ash::Entry,
|
|
instance: ash::Instance,
|
|
debug_utils: Option<ash::ext::debug_utils::Instance>,
|
|
debug_messenger: Option<vk::DebugUtilsMessengerEXT>,
|
|
}
|
|
|
|
impl Instance {
|
|
/// Creates a new Vulkan `Instance`
|
|
///
|
|
/// # Arguments
|
|
/// * `config` - Configuration settings for the instance
|
|
/// * `display_handle` - Raw display handle for the windowing system
|
|
/// * `external_extensions` - A slice of `CString` representing additional required instance extensions,
|
|
/// typically provided by integration libraries like `egui-ash`
|
|
pub fn new(
|
|
config: &InstanceConfig,
|
|
display_handle: &dyn HasDisplayHandle,
|
|
external_extentions: &[CString],
|
|
) -> Result<Arc<Self>> {
|
|
let entry = unsafe { ash::Entry::load()? };
|
|
|
|
let app_name = CString::new(config.application_name.clone())?;
|
|
let engine_name = CString::new(config.engine_name.clone())?;
|
|
let app_info = vk::ApplicationInfo::default()
|
|
.application_name(&app_name)
|
|
.application_version(config.application_version)
|
|
.engine_name(&engine_name)
|
|
.engine_version(config.engine_version)
|
|
.api_version(vk::API_VERSION_1_3);
|
|
|
|
let validation_layers = [c"VK_LAYER_KHRONOS_validation"];
|
|
let enabled_layer_names_raw: Vec<*const c_char> = if config.enable_validation
|
|
&& Self::check_validation_layer_support(&entry, &validation_layers)?
|
|
{
|
|
tracing::info!("Validation layers enabled.");
|
|
validation_layers.iter().map(|name| name.as_ptr()).collect()
|
|
} else {
|
|
if config.enable_validation {
|
|
tracing::warn!("Validation layers requested but not supported. Disabling.");
|
|
}
|
|
Vec::new()
|
|
};
|
|
|
|
let display_handle: DisplayHandle = display_handle.display_handle()?;
|
|
|
|
let surface_extensions_ptrs =
|
|
ash_window::enumerate_required_extensions(display_handle.into())?;
|
|
|
|
let mut required_cstrs_for_check: Vec<&CStr> = Vec::new();
|
|
|
|
let surface_extensions_cstrs: Vec<&CStr> = surface_extensions_ptrs
|
|
.iter()
|
|
.map(|&ptr| unsafe { CStr::from_ptr(ptr) })
|
|
.collect();
|
|
required_cstrs_for_check.extend(&surface_extensions_cstrs);
|
|
|
|
required_cstrs_for_check.extend(external_extentions.iter().map(|cs| cs.as_c_str()));
|
|
|
|
if config.enable_validation {
|
|
required_cstrs_for_check.push(ash::ext::debug_utils::NAME);
|
|
}
|
|
|
|
required_cstrs_for_check.sort_unstable();
|
|
required_cstrs_for_check.dedup();
|
|
|
|
Self::check_instance_extension_support(&entry, &required_cstrs_for_check)?;
|
|
tracing::info!(
|
|
"Required instance extensions supported: {:?}",
|
|
required_cstrs_for_check
|
|
);
|
|
|
|
let mut enabled_extension_names_raw: Vec<*const c_char> = Vec::new();
|
|
enabled_extension_names_raw.extend(surface_extensions_ptrs);
|
|
enabled_extension_names_raw.extend(external_extentions.iter().map(|cs| cs.as_ptr()));
|
|
if config.enable_validation {
|
|
enabled_extension_names_raw.push(ash::ext::debug_utils::NAME.as_ptr());
|
|
}
|
|
|
|
enabled_extension_names_raw.sort_unstable();
|
|
enabled_extension_names_raw.dedup();
|
|
|
|
let mut debug_create_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
|
|
.message_severity(
|
|
vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
|
|
| vk::DebugUtilsMessageSeverityFlagsEXT::ERROR,
|
|
)
|
|
.message_type(
|
|
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
|
|
| vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION
|
|
| vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE,
|
|
)
|
|
.pfn_user_callback(Some(vulkan_debug_callback));
|
|
|
|
let mut instance_create_info = vk::InstanceCreateInfo::default()
|
|
.application_info(&app_info)
|
|
.enabled_layer_names(&enabled_layer_names_raw)
|
|
.enabled_extension_names(&enabled_extension_names_raw);
|
|
|
|
if config.enable_validation {
|
|
instance_create_info = instance_create_info.push_next(&mut debug_create_info);
|
|
}
|
|
|
|
let instance = unsafe { entry.create_instance(&instance_create_info, None)? };
|
|
tracing::info!("Vulkan instance created succesfully.");
|
|
|
|
let (debug_utils, debug_messenger) = if config.enable_validation {
|
|
let utils = ash::ext::debug_utils::Instance::new(&entry, &instance);
|
|
let messenger =
|
|
unsafe { utils.create_debug_utils_messenger(&debug_create_info, None)? };
|
|
tracing::debug!("Debug messenger created.");
|
|
(Some(utils), Some(messenger))
|
|
} else {
|
|
(None, None)
|
|
};
|
|
|
|
Ok(Arc::new(Self {
|
|
entry,
|
|
instance,
|
|
debug_utils,
|
|
debug_messenger,
|
|
}))
|
|
}
|
|
|
|
/// Provides access to the loaded Vulkan entry points.
|
|
pub fn entry(&self) -> &ash::Entry {
|
|
&self.entry
|
|
}
|
|
|
|
/// Provides access to the raw `ash::Instance`.
|
|
pub fn ash_instance(&self) -> &ash::Instance {
|
|
&self.instance
|
|
}
|
|
|
|
/// Provides access to the DebugUtils extension loader, if enabled.
|
|
pub fn debug_utils(&self) -> Option<&debug_utils::Instance> {
|
|
self.debug_utils.as_ref()
|
|
}
|
|
|
|
/// Checks if the requested validation layers are available.
|
|
fn check_validation_layer_support(
|
|
entry: &ash::Entry,
|
|
required_layers: &[&CStr],
|
|
) -> Result<bool> {
|
|
let available_layers = unsafe { entry.enumerate_instance_layer_properties()? };
|
|
let available_names: HashSet<&CStr> = available_layers
|
|
.iter()
|
|
.map(|layer| unsafe { CStr::from_ptr(layer.layer_name.as_ptr()) })
|
|
.collect();
|
|
|
|
for layer in required_layers {
|
|
if !available_names.contains(layer) {
|
|
tracing::warn!("Required validation layer {:?} not found.", layer);
|
|
return Ok(false);
|
|
}
|
|
}
|
|
|
|
Ok(true)
|
|
}
|
|
|
|
/// Checks if the requested instance extensions are available.
|
|
/// Takes a deduplicated list of required extension names (&CStr).
|
|
fn check_instance_extension_support(
|
|
entry: &ash::Entry,
|
|
required_extensions: &[&CStr],
|
|
) -> Result<()> {
|
|
let available_extensions = unsafe { entry.enumerate_instance_extension_properties(None)? };
|
|
let available_names: HashSet<&CStr> = available_extensions
|
|
.iter()
|
|
.map(|ext| unsafe { CStr::from_ptr(ext.extension_name.as_ptr()) })
|
|
.collect();
|
|
tracing::debug!("Available instance extensions: {:?}", available_names);
|
|
|
|
for ext in required_extensions {
|
|
if !available_names.contains(ext) {
|
|
tracing::error!("Missing required instance extension: {:?}", ext);
|
|
return Err(GfxHalError::MissingExtension(
|
|
ext.to_string_lossy().into_owned(),
|
|
));
|
|
}
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
/// Enumerates all physical devices available to this instance.
|
|
///
|
|
/// # Safety
|
|
/// The `Instance` must be kept alive while the returned `PhysicalDevices`s are in use.
|
|
/// This is ensured by returning `PhysicalDevice`s holding an `Arc<Instance>`.
|
|
pub unsafe fn enumerate_physical_devices(self: &Arc<Self>) -> Result<Vec<PhysicalDevice>> {
|
|
let physical_device_handles = self.instance.enumerate_physical_devices()?;
|
|
|
|
if physical_device_handles.is_empty() {
|
|
return Err(GfxHalError::NoSuitableGpu(
|
|
"No Vulkan-compatibile GPUs found.".to_string(),
|
|
));
|
|
}
|
|
|
|
let devices = physical_device_handles
|
|
.into_iter()
|
|
.map(|handle| PhysicalDevice::new(Arc::clone(self), handle))
|
|
.collect();
|
|
|
|
Ok(devices)
|
|
}
|
|
|
|
/// Creates a vulkan surface for the given window
|
|
///
|
|
/// # Safety
|
|
/// The `window_handle_trait_obj` must point to a valid window/display managed by caller.
|
|
/// The `Instance` must be kept alive longer than the returned `Surface`
|
|
pub unsafe fn create_surface(
|
|
self: &Arc<Self>,
|
|
window_handle_trait_obj: &(impl HasWindowHandle + HasDisplayHandle),
|
|
) -> Result<Arc<Surface>> {
|
|
Surface::new(Arc::clone(self), window_handle_trait_obj)
|
|
}
|
|
}
|
|
|
|
impl Drop for Instance {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
if let (Some(utils), Some(messenger)) = (&self.debug_utils, self.debug_messenger) {
|
|
tracing::debug!("Destroying debug messenger...");
|
|
utils.destroy_debug_utils_messenger(messenger, None);
|
|
}
|
|
tracing::debug!("Destroying Vulkan instance...");
|
|
self.instance.destroy_instance(None);
|
|
tracing::debug!("Vulkan instance destroyed");
|
|
}
|
|
}
|
|
}
|