new architecture
This commit is contained in:
parent
9f7e72b784
commit
9cfd9d8b17
28 changed files with 2625 additions and 5351 deletions
223
crates/gfx_hal/src/device.rs
Normal file
223
crates/gfx_hal/src/device.rs
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
use ash::vk;
|
||||
use parking_lot::Mutex;
|
||||
use std::ffi::CStr;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use crate::error::{GfxHalError, Result};
|
||||
use crate::instance::Instance;
|
||||
use crate::physical_device::{PhysicalDevice, QueueFamilyIndices};
|
||||
use crate::queue::Queue;
|
||||
|
||||
/// Represents the logical Vulkan device, created from a `PhysicalDevice`.
|
||||
///
|
||||
/// Owns the `ash::Device` and provides access to device functions and queues.
|
||||
pub struct Device {
|
||||
instance: Arc<Instance>,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
device: ash::Device,
|
||||
queues: Mutex<HashMap<(u32, u32), Arc<Queue>>>,
|
||||
graphics_queue_family_index: u32,
|
||||
compute_queue_family_index: Option<u32>,
|
||||
transfer_queue_family_index: Option<u32>,
|
||||
}
|
||||
|
||||
impl Device {
|
||||
/// Creates a new logical device. Typically called via `PhysicalDevice::create_logical_device`.
|
||||
///
|
||||
/// # Saftey
|
||||
/// - `instance` and `physical_device_handle` must be valid.
|
||||
/// - `queue_family_indicies` must be valid indicies obtained from the `physical_device_handle`.
|
||||
/// - `required_extensions` must be supported by the `physical_device_handle`.
|
||||
/// - `enabled_features` and `mesh_features` must be supported by the `physical_device_handle`.
|
||||
pub(crate) unsafe fn new(
|
||||
instance: Arc<Instance>,
|
||||
physical_device_handle: vk::PhysicalDevice,
|
||||
queue_family_indicies: &QueueFamilyIndices,
|
||||
required_extensions: &[&CStr],
|
||||
enabled_features: &vk::PhysicalDeviceFeatures,
|
||||
mesh_features: Option<&vk::PhysicalDeviceMeshShaderFeaturesEXT>,
|
||||
) -> Result<Arc<Self>> {
|
||||
let mut queue_create_infos = Vec::new();
|
||||
let mut unique_queue_families = std::collections::HashSet::new();
|
||||
|
||||
let graphics_family = queue_family_indicies.graphics_family.ok_or_else(|| {
|
||||
GfxHalError::MissingQueueFamily("Graphics Queue Family Missing".to_string())
|
||||
})?;
|
||||
unique_queue_families.insert(graphics_family);
|
||||
|
||||
if let Some(compute_family) = queue_family_indicies.compute_family {
|
||||
unique_queue_families.insert(compute_family);
|
||||
}
|
||||
if let Some(transfer_family) = queue_family_indicies.transfer_family {
|
||||
unique_queue_families.insert(transfer_family);
|
||||
}
|
||||
|
||||
let queue_priorities = [1.0f32];
|
||||
for &family_index in &unique_queue_families {
|
||||
let queue_create_info = vk::DeviceQueueCreateInfo::default()
|
||||
.queue_family_index(family_index)
|
||||
.queue_priorities(&queue_priorities);
|
||||
queue_create_infos.push(queue_create_info);
|
||||
}
|
||||
|
||||
let extension_names_raw: Vec<*const i8> =
|
||||
required_extensions.iter().map(|s| s.as_ptr()).collect();
|
||||
|
||||
let mut features2 = vk::PhysicalDeviceFeatures2::default().features(*enabled_features);
|
||||
let mut mesh_features_copy;
|
||||
|
||||
if let Some(mesh_feats) = mesh_features {
|
||||
mesh_features_copy = *mesh_feats;
|
||||
features2 = features2.push_next(&mut mesh_features_copy);
|
||||
}
|
||||
|
||||
let device_create_info = vk::DeviceCreateInfo::default()
|
||||
.queue_create_infos(&queue_create_infos)
|
||||
.enabled_extension_names(&extension_names_raw)
|
||||
.push_next(&mut features2);
|
||||
|
||||
tracing::info!(
|
||||
"Creating logical device with extensions: {:?}",
|
||||
required_extensions
|
||||
);
|
||||
let device = instance.ash_instance().create_device(
|
||||
physical_device_handle,
|
||||
&device_create_info,
|
||||
None,
|
||||
)?;
|
||||
tracing::info!("logical device created successfully.");
|
||||
|
||||
let mut queues_map = HashMap::new();
|
||||
let arc_device_placeholder = Arc::new(Self {
|
||||
instance,
|
||||
physical_device: physical_device_handle,
|
||||
device,
|
||||
queues: Mutex::new(HashMap::new()),
|
||||
graphics_queue_family_index: graphics_family,
|
||||
compute_queue_family_index: queue_family_indicies.compute_family,
|
||||
transfer_queue_family_index: queue_family_indicies.transfer_family,
|
||||
});
|
||||
|
||||
for &family_index in &unique_queue_families {
|
||||
let queue_handler = arc_device_placeholder
|
||||
.device
|
||||
.get_device_queue(family_index, 0);
|
||||
let queue_wrapper = Arc::new(Queue::new(
|
||||
Arc::clone(&arc_device_placeholder),
|
||||
queue_handler,
|
||||
family_index,
|
||||
));
|
||||
queues_map.insert((family_index, 0), queue_wrapper);
|
||||
}
|
||||
|
||||
let device_handle = unsafe {
|
||||
arc_device_placeholder
|
||||
.instance
|
||||
.ash_instance()
|
||||
.create_device(physical_device_handle, &device_create_info, None)?
|
||||
};
|
||||
|
||||
let final_device = Arc::new(Self {
|
||||
instance: Arc::clone(&arc_device_placeholder.instance), // Clone from placeholder
|
||||
physical_device: physical_device_handle,
|
||||
device: device_handle, // Use the newly created handle
|
||||
queues: Mutex::new(queues_map), // Use the populated map
|
||||
graphics_queue_family_index: graphics_family,
|
||||
compute_queue_family_index: queue_family_indicies.compute_family,
|
||||
transfer_queue_family_index: queue_family_indicies.transfer_family,
|
||||
});
|
||||
|
||||
Ok(final_device)
|
||||
}
|
||||
|
||||
/// Provides raw access to the underlying `ash::Device`.
|
||||
/// Use with caution, prefer safe wrappers where possible.
|
||||
pub fn raw(&self) -> &ash::Device {
|
||||
&self.device
|
||||
}
|
||||
|
||||
/// Gets the handle of the physical device this logical device was created from.
|
||||
pub fn physical_device_handle(&self) -> vk::PhysicalDevice {
|
||||
self.physical_device
|
||||
}
|
||||
|
||||
/// Gets the primary graphics queue family index used by this device.
|
||||
pub fn graphics_queue_family_index(&self) -> u32 {
|
||||
self.graphics_queue_family_index
|
||||
}
|
||||
|
||||
/// Gets the compute queue family index, if a distinct one was found/used.
|
||||
pub fn compute_queue_family_index(&self) -> Option<u32> {
|
||||
self.compute_queue_family_index
|
||||
}
|
||||
|
||||
/// Gets the transfer queue family index, if a distinct one was found/used.
|
||||
pub fn transfer_queue_family_index(&self) -> Option<u32> {
|
||||
self.transfer_queue_family_index
|
||||
}
|
||||
|
||||
/// Gets a wrapped queue handle.
|
||||
/// Currently only supports queue index 0 for each family.
|
||||
pub fn get_queue(&self, family_index: u32, queue_index: u32) -> Option<Arc<Queue>> {
|
||||
if queue_index != 0 {
|
||||
tracing::warn!("get_queue currently only supports queue_index 0");
|
||||
return None;
|
||||
}
|
||||
self.queues
|
||||
.lock()
|
||||
.get(&(family_index, queue_index))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Gets the primary graphics queue (family index from `graphics_queue_family_index`, queue index 0).
|
||||
/// Panics if the graphics queue wasn't successfully created.
|
||||
pub fn get_graphics_queue(&self) -> Arc<Queue> {
|
||||
self.get_queue(self.graphics_queue_family_index, 0)
|
||||
.expect("Graphics queue should always exist")
|
||||
}
|
||||
|
||||
/// Waits until the logical device becomes idle.
|
||||
/// This is a heavy operation and should be used sparingly (e.g., before destruction).
|
||||
pub fn wait_idle(&self) -> Result<()> {
|
||||
tracing::debug!("Waiting for device idle...");
|
||||
unsafe { self.device.device_wait_idle()? };
|
||||
tracing::debug!("Device idle.");
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Device {
|
||||
fn drop(&mut self) {
|
||||
tracing::debug!("Destroying logical device...");
|
||||
if let Err(e) = self.wait_idle() {
|
||||
tracing::error!("Error waiting for device idle during drop: {}", e);
|
||||
}
|
||||
unsafe {
|
||||
self.device.destroy_device(None);
|
||||
}
|
||||
tracing::debug!("Logical device destroyed.");
|
||||
}
|
||||
}
|
||||
|
||||
impl PhysicalDevice {
|
||||
/// Creates the logical device (`Device`) from this physical device.
|
||||
///
|
||||
/// # Safety
|
||||
/// See `Device::new` safety comments.
|
||||
pub unsafe fn create_logical_device(
|
||||
&self,
|
||||
required_extensions: &[&CStr],
|
||||
queue_family_indices: &QueueFamilyIndices,
|
||||
enabled_features: &vk::PhysicalDeviceFeatures,
|
||||
mesh_features: Option<&vk::PhysicalDeviceMeshShaderFeaturesEXT>,
|
||||
) -> Result<Arc<Device>> {
|
||||
Device::new(
|
||||
Arc::clone(self.instance()),
|
||||
self.handle(),
|
||||
queue_family_indices,
|
||||
required_extensions,
|
||||
enabled_features,
|
||||
mesh_features,
|
||||
)
|
||||
}
|
||||
}
|
||||
64
crates/gfx_hal/src/error.rs
Normal file
64
crates/gfx_hal/src/error.rs
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
use ash::vk;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Top-level error type for the gfx_hal crate.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum GfxHalError {
|
||||
/// Error originating directly from a Vulkan API call.
|
||||
#[error("Vulkan API Error: {0}")]
|
||||
VulkanError(#[from] vk::Result),
|
||||
|
||||
/// Error loading the Vulkan library or resolving function pointers.
|
||||
#[error("Failed to load Vulkan library: {0}")]
|
||||
LoadError(String),
|
||||
|
||||
/// No suitable physical device (GPU) could be found that meets requirements.
|
||||
#[error("No suitable physical device found: {0}")]
|
||||
NoSuitableGpu(String),
|
||||
|
||||
/// A required Vulkan instance or device extension is not supported.
|
||||
#[error("Missing required Vulkan extension: {0:?}")]
|
||||
MissingExtension(String),
|
||||
|
||||
/// A required Vulkan feature is not supported by the physical device.
|
||||
#[error("Missing required Vulkan feature.")]
|
||||
MissingFeature,
|
||||
|
||||
/// Failed to find a suitable queue family (e.g., graphics, present).
|
||||
#[error("Could not find required queue family: {0}")]
|
||||
MissingQueueFamily(String),
|
||||
|
||||
/// Error related to window system integration surface creation.
|
||||
#[error("Failed to create Vulkan surface: {0}")]
|
||||
SurfaceCreationError(vk::Result),
|
||||
|
||||
/// The Vulkan surface became invalid (e.g., window resized, closed).
|
||||
#[error("Vulkan surface is no longer valid (maybe lost or out of date)")]
|
||||
SurfaceLost,
|
||||
|
||||
/// Error converting a C-style string.
|
||||
#[error("Invalid C string: {0}")]
|
||||
InvalidCString(#[from] std::ffi::NulError),
|
||||
|
||||
/// Error converting C string slice to Rust string slice.
|
||||
#[error("Invalid UTF-8 sequence in C string: {0}")]
|
||||
InvalidCStringUtf8(#[from] std::ffi::FromBytesWithNulError),
|
||||
|
||||
/// Generic I/O error.
|
||||
#[error("I/O error: {0}")]
|
||||
IoError(#[from] std::io::Error),
|
||||
|
||||
/// Error with winit windowing library.
|
||||
#[error("Winit Error: {0}")]
|
||||
WinitHandleError(#[from] winit::raw_window_handle::HandleError),
|
||||
|
||||
/// Ash loader error.
|
||||
#[error("Error loading the ash entry.")]
|
||||
AshEntryError(#[from] ash::LoadingError),
|
||||
|
||||
/// Placeholder for other specific errors.
|
||||
#[error("An unexpected error occurred: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
pub type Result<T, E = GfxHalError> = std::result::Result<T, E>;
|
||||
353
crates/gfx_hal/src/instance.rs
Normal file
353
crates/gfx_hal/src/instance.rs
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
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};
|
||||
|
||||
use crate::error::{GfxHalError, Result};
|
||||
|
||||
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_phyiscal_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");
|
||||
}
|
||||
}
|
||||
}
|
||||
8
crates/gfx_hal/src/lib.rs
Normal file
8
crates/gfx_hal/src/lib.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
pub mod device;
|
||||
pub mod error;
|
||||
pub mod instance;
|
||||
pub mod physical_device;
|
||||
pub mod queue;
|
||||
pub mod surface;
|
||||
pub mod swapchain;
|
||||
pub mod sync;
|
||||
164
crates/gfx_hal/src/physical_device.rs
Normal file
164
crates/gfx_hal/src/physical_device.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
use ash::vk;
|
||||
|
||||
use crate::{error::GfxHalError, instance::Instance};
|
||||
|
||||
use std::{ffi::CStr, sync::Arc};
|
||||
|
||||
/// Represents a physical Vulkan device (GPU).
|
||||
///
|
||||
/// This struct holds a handle to the Vulkan physical device and a
|
||||
/// reference back to the `Instance` it originated from. It does *not* own
|
||||
/// the `vk::PhysicalDevice` in the sense that it doesn't destroy it; physical
|
||||
/// devices are implicitly managed by the `vk::Instance`
|
||||
///
|
||||
/// It's cheap to clone as it only clones the `Arc<Instance>` and copies the handle
|
||||
#[derive(Clone)]
|
||||
pub struct PhysicalDevice {
|
||||
/// Shared reference to the Vulkan instance
|
||||
instance: Arc<Instance>,
|
||||
/// The raw Vulkan physical device handle.
|
||||
handle: vk::PhysicalDevice,
|
||||
}
|
||||
|
||||
/// Holds information about queue families found on a `PhysicalDevice`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct QueueFamilyIndices {
|
||||
/// Queue family index supporting graphics operations.
|
||||
pub graphics_family: Option<u32>,
|
||||
/// Queue family index supporting compute operations.
|
||||
pub compute_family: Option<u32>,
|
||||
/// Queue family index supporting transfer operations.
|
||||
pub transfer_family: Option<u32>,
|
||||
/// Queue family index supporting presentaiton to a given surface.
|
||||
/// This might be the same as the graphics family.
|
||||
pub present_family: Option<u32>,
|
||||
}
|
||||
|
||||
impl QueueFamilyIndices {
|
||||
/// Checks if all essential queue families (graphics, present if surface exists) were found.
|
||||
pub fn is_complete(&self, requires_present: bool) -> bool {
|
||||
self.graphics_family.is_some() && (!requires_present || self.present_family.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents the suitability of a physical device.
|
||||
#[derive(Debug)]
|
||||
pub enum Suitability<'a> {
|
||||
/// The device is suitable and meets requirements.
|
||||
Suitable {
|
||||
/// A score indicating preference (higher is better).
|
||||
score: u32,
|
||||
/// The indicies of the required queue families.
|
||||
indicies: QueueFamilyIndices,
|
||||
/// The properties of the device.
|
||||
properties: Box<vk::PhysicalDeviceProperties>,
|
||||
/// The supported base features of the device.
|
||||
features: Box<vk::PhysicalDeviceFeatures>,
|
||||
/// THe supported mesh shader features.
|
||||
mesh_shader_features: vk::PhysicalDeviceMeshShaderFeaturesEXT<'a>,
|
||||
},
|
||||
/// The device is not suitable.
|
||||
NotSuitable {
|
||||
/// The reason why the device is not suitable.
|
||||
reason: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl PhysicalDevice {
|
||||
/// Creates a new `PhysicalDevice` wrapper
|
||||
/// Typically called internally by `Instance::enumerate_physical_devices`
|
||||
pub(crate) fn new(instance: Arc<Instance>, handle: vk::PhysicalDevice) -> Self {
|
||||
Self { instance, handle }
|
||||
}
|
||||
|
||||
/// Gets the raw `vk::PhysicalDevice` handle.
|
||||
pub fn handle(&self) -> vk::PhysicalDevice {
|
||||
self.handle
|
||||
}
|
||||
|
||||
/// Gets a reference to the `Instance` this device belongs to.
|
||||
pub fn instance(&self) -> &Arc<Instance> {
|
||||
&self.instance
|
||||
}
|
||||
|
||||
/// Queries the basic properties of the physical device.
|
||||
///
|
||||
/// # Safety
|
||||
/// Assumes the `PhysicalDevice` handle is valid.
|
||||
pub unsafe fn get_properties(&self) -> vk::PhysicalDeviceProperties {
|
||||
self.instance
|
||||
.ash_instance()
|
||||
.get_physical_device_properties(self.handle)
|
||||
}
|
||||
|
||||
/// Queries the supported features, including mesh shaders.
|
||||
///
|
||||
/// # Safety
|
||||
/// Assumes the `PhysicalDevice` handle is valid.
|
||||
pub unsafe fn get_features(
|
||||
&self,
|
||||
) -> (
|
||||
vk::PhysicalDeviceFeatures,
|
||||
vk::PhysicalDeviceMeshShaderFeaturesEXT,
|
||||
) {
|
||||
let mut mesh_shader_features = vk::PhysicalDeviceMeshShaderFeaturesEXT::default();
|
||||
let mut features2 =
|
||||
vk::PhysicalDeviceFeatures2::default().push_next(&mut mesh_shader_features);
|
||||
|
||||
self.instance
|
||||
.ash_instance()
|
||||
.get_physical_device_features2(self.handle, &mut features2);
|
||||
|
||||
(features2.features, mesh_shader_features)
|
||||
}
|
||||
|
||||
/// Queries the properties of all queue families available on the device.
|
||||
///
|
||||
/// # Safety
|
||||
/// Assumes the `PhysicalDevice` handle is valid.
|
||||
pub unsafe fn get_queue_family_properties(&self) -> Vec<vk::QueueFamilyProperties> {
|
||||
self.instance
|
||||
.ash_instance()
|
||||
.get_physical_device_queue_family_properties(self.handle)
|
||||
}
|
||||
|
||||
/// Queries the device specific extensions supported by this physical device.
|
||||
///
|
||||
/// # Safety
|
||||
/// Assumes the `PhysicalDevice` handle is valid.
|
||||
pub unsafe fn get_supported_extensions(
|
||||
&self,
|
||||
) -> Result<Vec<vk::ExtensionProperties>, GfxHalError> {
|
||||
self.instance
|
||||
.ash_instance()
|
||||
.enumerate_device_extension_properties(self.handle)
|
||||
.map_err(GfxHalError::VulkanError)
|
||||
}
|
||||
|
||||
// /// Finds suitable queue family indicies based on required flags and optional surface.
|
||||
// ///
|
||||
// /// # Safety
|
||||
// /// Assumes the `PhysicalDevice` handle and `Surface` (if provided) are valid.
|
||||
// pub unsafe fn find_queue_families(
|
||||
// &self,
|
||||
// surface: Option<&Surface>,
|
||||
// ) -> Result<QueueFamilyIndices, GfxHalError> {
|
||||
// }
|
||||
|
||||
// /// Checks if the physical device meets the specified requirements and scores it.
|
||||
// ///
|
||||
// /// # Arguments
|
||||
// /// * `required_extensions` - A slice of C-style strings representing required device extensions (e.g., `ash::extensions::khr::Swapchain::name()`).
|
||||
// /// * `required_mesh_features` - The minimum mesh shader features required. Check `task_shader` and `mesh_shader` fields.
|
||||
// /// * `surface` - An optional surface to check for presentation support.
|
||||
// ///
|
||||
// /// # Safety
|
||||
// /// Assumes the `PhysicalDevice` handle and `Surface` (if provided) are valid.
|
||||
// pub unsafe fn check_suitability(
|
||||
// &self,
|
||||
// required_extensions: &[&CStr],
|
||||
// required_mesh_features: &vk::PhysicalDeviceMeshShaderFeaturesEXT,
|
||||
// surface: Option<&Surface>,
|
||||
// ) -> Result<Suitability, GfxHalError> {
|
||||
// }
|
||||
}
|
||||
102
crates/gfx_hal/src/queue.rs
Normal file
102
crates/gfx_hal/src/queue.rs
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use ash::vk;
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::device::Device;
|
||||
use crate::error::Result;
|
||||
use crate::sync::Fence;
|
||||
|
||||
/// Represents a Vulkan device queue.
|
||||
///
|
||||
/// Holds a reference to the `Device` and the raw `vk::Queue` handle.
|
||||
/// Provides methods for submitting command buffers.
|
||||
pub struct Queue {
|
||||
device: Arc<Device>,
|
||||
queue: vk::Queue,
|
||||
family_index: u32,
|
||||
// Each queue submission must be externally synchronized or locked internally.
|
||||
// Using a Mutex here provides a simple internal locking per queue.
|
||||
submit_lock: Mutex<()>,
|
||||
}
|
||||
|
||||
impl Queue {
|
||||
/// Creates a new Queue wrapper. Called internally by `Device`.
|
||||
pub(crate) fn new(device: Arc<Device>, queue: vk::Queue, family_index: u32) -> Self {
|
||||
Self {
|
||||
device,
|
||||
queue,
|
||||
family_index,
|
||||
submit_lock: Mutex::new(()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the raw `vk::Queue` handle.
|
||||
pub fn handle(&self) -> vk::Queue {
|
||||
self.queue
|
||||
}
|
||||
|
||||
/// Gets the queue family index this queue belongs to.
|
||||
pub fn family_index(&self) -> u32 {
|
||||
self.family_index
|
||||
}
|
||||
|
||||
/// Gets a reference to the logical device this queue belongs to.
|
||||
pub fn device(&self) -> &Arc<Device> {
|
||||
&self.device
|
||||
}
|
||||
|
||||
/// Submits command buffers to the queue.
|
||||
///
|
||||
/// This method acquires an internal lock for the duration of the submission call
|
||||
/// to prevent concurrent `vkQueueSubmit` calls on the same queue from this wrapper.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `submits` - A slice of `vk::SubmitInfo` describing the work to submit.
|
||||
/// * `signal_fence` - An optional `Fence` to signal when the submission completes.
|
||||
///
|
||||
/// # Safety
|
||||
/// - The command buffers and synchronization primitieves within `submits` must be valid.
|
||||
/// - The `signal_fence`, if provided, must be valid and unsignaled.
|
||||
pub unsafe fn submit(
|
||||
&self,
|
||||
submits: &[vk::SubmitInfo],
|
||||
signal_fence: Option<&Fence>,
|
||||
) -> Result<()> {
|
||||
let fence_handle = signal_fence.map_or(vk::Fence::null(), |f| f.handle());
|
||||
|
||||
let _lock = self.submit_lock.lock();
|
||||
|
||||
tracing::trace!(
|
||||
"Submitting {} batch(es) to queue family {}",
|
||||
submits.len(),
|
||||
self.family_index
|
||||
);
|
||||
self.device
|
||||
.raw()
|
||||
.queue_submit(self.queue, submits, fence_handle)?;
|
||||
tracing::trace!("Submission successful.");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Waits until this queue becomes idle.
|
||||
///
|
||||
/// This is a heavy operation and blocks the current thread.
|
||||
pub fn wait_idle(&self) -> Result<()> {
|
||||
tracing::debug!("Waiting for queue idle (family {})...", self.family_index);
|
||||
// Lock the mutex while waiting to prevent submissions during the wait?
|
||||
// Or allow submissions and let Vulkan handle it? Let Vulkan handle it.
|
||||
unsafe { self.device.raw().queue_wait_idle(self.queue)? };
|
||||
tracing::debug!("Queue idle (family {}).", self.family_index);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Note: vkQueuePresentKHR is intentionally omitted here.
|
||||
// Presentation is tightly coupled with Swapchain. It's safer to
|
||||
// have a method like `Swapchain::present(&self, queue: &Queue, ...)`
|
||||
// which internally calls `queue.device().raw().queue_present_khr(...)`
|
||||
// using the swapchain's loader.
|
||||
// If direct access is needed, it can be done with `queue.device().raw()`.
|
||||
}
|
||||
|
||||
// Queues don't own the vk::Queue handle (the Device does), so no Drop impl.
|
||||
126
crates/gfx_hal/src/surface.rs
Normal file
126
crates/gfx_hal/src/surface.rs
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
use crate::{
|
||||
error::{GfxHalError, Result},
|
||||
instance::Instance,
|
||||
};
|
||||
|
||||
use ash::{khr::surface::Instance as SurfaceLoader, vk};
|
||||
use std::sync::Arc;
|
||||
use winit::raw_window_handle::{HasDisplayHandle, HasWindowHandle};
|
||||
|
||||
/// Represents a Vulkan presentation surface, tied to a window.
|
||||
///
|
||||
/// Owns the `vk::SurfaceKHR` handle and the `ash` Surface loader extension.
|
||||
pub struct Surface {
|
||||
instance: Arc<Instance>,
|
||||
surface_loader: SurfaceLoader,
|
||||
surface: vk::SurfaceKHR,
|
||||
}
|
||||
|
||||
impl Surface {
|
||||
/// Creates a new Vulkan `Surface`
|
||||
///
|
||||
/// # Safety
|
||||
/// - The `window_handle_trait_obj` must provide valid window and display handles
|
||||
/// for the lifetime of the `Surface`.
|
||||
/// - The `Instance` must outlive the `Surface`
|
||||
pub unsafe fn new(
|
||||
instance: Arc<Instance>,
|
||||
window_handle_trait_obj: &(impl HasWindowHandle + HasDisplayHandle),
|
||||
) -> Result<Arc<Self>> {
|
||||
let surface_loader = SurfaceLoader::new(instance.entry(), instance.ash_instance());
|
||||
let surface = ash_window::create_surface(
|
||||
instance.entry(),
|
||||
instance.ash_instance(),
|
||||
window_handle_trait_obj.display_handle()?.into(),
|
||||
window_handle_trait_obj.window_handle()?.into(),
|
||||
None,
|
||||
)
|
||||
.map_err(GfxHalError::SurfaceCreationError)?;
|
||||
|
||||
tracing::info!("Vulkan surface created successfully.");
|
||||
|
||||
Ok(Arc::new(Self {
|
||||
instance,
|
||||
surface_loader,
|
||||
surface,
|
||||
}))
|
||||
}
|
||||
|
||||
/// Gets the raw `vk::SurfaceKHR` handle.
|
||||
pub fn handle(&self) -> vk::SurfaceKHR {
|
||||
self.surface
|
||||
}
|
||||
|
||||
/// Gets a reference to the `ash` Surface loader extension.
|
||||
pub fn surface_loader(&self) -> &SurfaceLoader {
|
||||
&self.surface_loader
|
||||
}
|
||||
|
||||
/// Gets a reference to the `Instance` this surface belongs to
|
||||
pub fn instance(&self) -> &Arc<Instance> {
|
||||
&self.instance
|
||||
}
|
||||
|
||||
/// Queries surface capabilites for a given physical device.
|
||||
///
|
||||
/// # Safety
|
||||
/// The `physical_device` handle must be valid and compatible with this surface.
|
||||
pub unsafe fn get_physical_device_surface_capabilities(
|
||||
&self,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
) -> Result<vk::SurfaceCapabilitiesKHR> {
|
||||
self.surface_loader
|
||||
.get_physical_device_surface_capabilities(physical_device, self.surface)
|
||||
.map_err(GfxHalError::VulkanError)
|
||||
}
|
||||
|
||||
/// Queries supported surface formats for a given physical device.
|
||||
///
|
||||
/// # Safety
|
||||
/// The `physical_device` handle must be valid and compatible with this surface.
|
||||
pub unsafe fn get_physical_device_surface_formats(
|
||||
&self,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
) -> Result<Vec<vk::SurfaceFormatKHR>> {
|
||||
self.surface_loader
|
||||
.get_physical_device_surface_formats(physical_device, self.surface)
|
||||
.map_err(GfxHalError::VulkanError)
|
||||
}
|
||||
|
||||
/// Queries supported present modes for a given physical device.
|
||||
///
|
||||
/// # Safety
|
||||
/// The `physical_device` handle must be valid and compatible with this surface.
|
||||
pub unsafe fn get_physical_device_surface_present_modes(
|
||||
&self,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
) -> Result<Vec<vk::PresentModeKHR>> {
|
||||
self.surface_loader
|
||||
.get_physical_device_surface_present_modes(physical_device, self.surface)
|
||||
.map_err(GfxHalError::VulkanError)
|
||||
}
|
||||
|
||||
/// Queries surface support for a given queue family index on a physical device.
|
||||
///
|
||||
/// # Safety
|
||||
/// The `physical_device` handle must be valid and compatible with this surface.
|
||||
pub unsafe fn get_physical_device_surface_support(
|
||||
&self,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
queue_family_index: u32,
|
||||
) -> Result<bool> {
|
||||
self.surface_loader
|
||||
.get_physical_device_surface_support(physical_device, queue_family_index, self.surface)
|
||||
.map_err(GfxHalError::VulkanError)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Surface {
|
||||
fn drop(&mut self) {
|
||||
tracing::debug!("Destroying Vulkan surface...");
|
||||
unsafe {
|
||||
self.surface_loader.destroy_surface(self.surface, None);
|
||||
}
|
||||
tracing::debug!("Vulkan surface destroyed.");
|
||||
}
|
||||
}
|
||||
321
crates/gfx_hal/src/swapchain.rs
Normal file
321
crates/gfx_hal/src/swapchain.rs
Normal file
|
|
@ -0,0 +1,321 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use ash::khr::swapchain::Device as SwapchainLoader;
|
||||
use ash::vk;
|
||||
|
||||
use crate::device::{self, Device};
|
||||
use crate::error::{GfxHalError, Result};
|
||||
use crate::surface::{self, Surface};
|
||||
use crate::sync::{Fence, Semaphore};
|
||||
|
||||
/// Configuration for creating or recreating a `Swapchain`.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct SwapchainConfig {
|
||||
/// Desired number of images in the swapchain (min/max clamped by capabilities).
|
||||
pub desired_image_count: u32,
|
||||
/// Preferred surface format (e.g., `vk::Format::B8G8R8A8_SRGB`).
|
||||
pub desired_format: vk::SurfaceFormatKHR,
|
||||
/// Preferred presentation mode (e.g., `vk::PresentModeKHR::MAILBOX`).
|
||||
pub desired_present_mode: vk::PresentModeKHR,
|
||||
/// Desired usage flags for swapchain images (e.g., `vk::ImageUsageFlags::COLOR_ATTACHMENT`).
|
||||
pub image_usage: vk::ImageUsageFlags,
|
||||
/// The dimensions of the surface.
|
||||
pub extent: vk::Extent2D,
|
||||
/// Transformation to apply (usually `vk::SurfaceTransformFlagsKHR::IDENTITY`).
|
||||
pub pre_transform: vk::SurfaceTransformFlagsKHR,
|
||||
/// Alpha compositing mode (usually `vk::CompositeAlphaFlagsKHR::OPAQUE`).
|
||||
pub composite_alpha: vk::CompositeAlphaFlagsKHR,
|
||||
}
|
||||
|
||||
/// Represents the Vulkan swapchain, managing presentation images.
|
||||
///
|
||||
/// Owns the `vk::SwapchainKHR`, the `ash` Swapchain loader, the swapchain images,
|
||||
/// and their corresponding image views.
|
||||
pub struct Swapchain {
|
||||
device: Arc<Device>,
|
||||
swapchain_loader: SwapchainLoader,
|
||||
swapchain: vk::SwapchainKHR,
|
||||
images: Vec<vk::Image>,
|
||||
image_views: Vec<vk::ImageView>,
|
||||
format: vk::SurfaceFormatKHR,
|
||||
extent: vk::Extent2D,
|
||||
image_count: u32,
|
||||
}
|
||||
|
||||
impl Swapchain {
|
||||
/// Creates a new `Swapchain` or recreates an exisiting one.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `device` - The logical device.
|
||||
/// * `surface` - The surface to present to.
|
||||
/// * `config` - Desired swapchain configuration.
|
||||
/// * `old_swapchain` - Optional handle to a previous swapchain for smoother recreation.
|
||||
///
|
||||
/// # Safety
|
||||
/// - `device` and `surface` must be valid and compatible.
|
||||
/// - If `old_swapchain` is provided, it must be a valid handle previously created
|
||||
/// with the same `surface`.
|
||||
/// - The caller must ensure that the `old_swapchain` (and its associated resources like
|
||||
/// image views) are no longer in use before calling this function and are properly
|
||||
/// destroyed *after* the new swapchain is successfully created.
|
||||
pub unsafe fn new(
|
||||
device: Arc<Device>,
|
||||
surface: Arc<Surface>,
|
||||
config: SwapchainConfig,
|
||||
old_swapchain: Option<vk::SwapchainKHR>,
|
||||
) -> Result<Self> {
|
||||
let physical_device = device.physical_device_handle();
|
||||
|
||||
let capabilities = surface.get_physical_device_surface_capabilities(physical_device)?;
|
||||
let formats = surface.get_physical_device_surface_formats(physical_device)?;
|
||||
let present_modes = surface.get_physical_device_surface_present_modes(physical_device)?;
|
||||
|
||||
if formats.is_empty() || present_modes.is_empty() {
|
||||
return Err(GfxHalError::NoSuitableGpu(
|
||||
"Swapchain creation failed: No formats or present modes available.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let surface_format = Self::choose_surface_format(&formats, config.desired_format);
|
||||
let present_mode = Self::choose_present_mode(&present_modes, config.desired_present_mode);
|
||||
let extent = Self::choose_extent(capabilities, config.extent);
|
||||
let image_count = Self::choose_image_count(capabilities, config.desired_image_count);
|
||||
|
||||
tracing::info!("Creating swapchain: Format={:?}, ColorSpace={:?}, PresentMode={:?}, Extent={:?}, ImageCount={}", surface_format.format, surface_format.color_space, present_mode, extent, image_count);
|
||||
|
||||
let mut create_info = vk::SwapchainCreateInfoKHR::default()
|
||||
.surface(surface.handle())
|
||||
.min_image_count(image_count)
|
||||
.image_format(surface_format.format)
|
||||
.image_color_space(surface_format.color_space)
|
||||
.image_extent(extent)
|
||||
.image_array_layers(1)
|
||||
.image_usage(config.image_usage)
|
||||
.pre_transform(config.pre_transform)
|
||||
.composite_alpha(config.composite_alpha)
|
||||
.present_mode(present_mode)
|
||||
.clipped(true);
|
||||
|
||||
let queue_family_indicies = [device.graphics_queue_family_index()];
|
||||
create_info = create_info
|
||||
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||
.queue_family_indices(&queue_family_indicies);
|
||||
|
||||
if let Some(old) = old_swapchain {
|
||||
create_info = create_info.old_swapchain(old);
|
||||
tracing::debug!("Passing old swapchain handle for recreation.");
|
||||
}
|
||||
|
||||
let swapchain_loader =
|
||||
SwapchainLoader::new(surface.instance().ash_instance(), device.raw());
|
||||
let swapchain = swapchain_loader.create_swapchain(&create_info, None)?;
|
||||
tracing::info!("Swapchain created successfully.");
|
||||
|
||||
let images = swapchain_loader.get_swapchain_images(swapchain)?;
|
||||
tracing::debug!("Retrieved {} swapchain images.", images.len());
|
||||
|
||||
let image_views = Self::create_image_views(device.raw(), &images, surface_format.format)?;
|
||||
tracing::debug!("Created {} swapchain image views.", image_views.len());
|
||||
|
||||
Ok(Self {
|
||||
device,
|
||||
swapchain_loader,
|
||||
swapchain,
|
||||
images,
|
||||
image_views,
|
||||
format: surface_format,
|
||||
extent,
|
||||
image_count,
|
||||
})
|
||||
}
|
||||
|
||||
/// Acquires the next available image from the swapchain.
|
||||
///
|
||||
/// Returns the index of the acquired image and a boolean indicating if the
|
||||
/// swapchain is suboptimal (needs recreation).
|
||||
///
|
||||
/// # Safety
|
||||
/// - `signal_semaphore` and `signal_fence`, if provided, must be valid handles
|
||||
/// that are not currently waited on by the GPU.
|
||||
/// - The caller must ensure proper synchronization before using the returned image index.
|
||||
pub unsafe fn acquire_next_image(
|
||||
&self,
|
||||
timeout_ns: u64,
|
||||
signal_semaphore: Option<&Semaphore>,
|
||||
signal_fence: Option<&Fence>,
|
||||
) -> Result<(u32, bool)> {
|
||||
let semaphore_handle = signal_semaphore.map_or(vk::Semaphore::null(), |s| s.handle());
|
||||
let fence_handle = signal_fence.map_or(vk::Fence::null(), |f| f.handle());
|
||||
|
||||
match self.swapchain_loader.acquire_next_image(
|
||||
self.swapchain,
|
||||
timeout_ns,
|
||||
semaphore_handle,
|
||||
fence_handle,
|
||||
) {
|
||||
Ok((image_index, suboptimal)) => Ok((image_index, suboptimal)),
|
||||
Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => Err(GfxHalError::SurfaceLost),
|
||||
Err(e) => Err(GfxHalError::VulkanError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the raw `vk::SwapchainKHR` handle.
|
||||
pub fn handle(&self) -> vk::SwapchainKHR {
|
||||
self.swapchain
|
||||
}
|
||||
|
||||
/// Gets a reference to the `ash` Swapchain loader extension
|
||||
pub fn loader(&self) -> &SwapchainLoader {
|
||||
&self.swapchain_loader
|
||||
}
|
||||
|
||||
/// Gets the chosen surface format of the swapchain.
|
||||
pub fn format(&self) -> vk::SurfaceFormatKHR {
|
||||
self.format
|
||||
}
|
||||
|
||||
/// Gets the extent (dimensions) of the swapchain image
|
||||
pub fn extent(&self) -> vk::Extent2D {
|
||||
self.extent
|
||||
}
|
||||
|
||||
/// Gets the actual number of images in the swapchain
|
||||
pub fn image_count(&self) -> u32 {
|
||||
self.image_count
|
||||
}
|
||||
|
||||
/// Gets a slice containing the raw `vk::Image` handles.
|
||||
pub fn images(&self) -> &[vk::Image] {
|
||||
&self.images
|
||||
}
|
||||
|
||||
/// Gets a slice containing the raw `vk::ImageView` handles.
|
||||
pub fn image_views(&self) -> &[vk::ImageView] {
|
||||
&self.image_views
|
||||
}
|
||||
|
||||
fn choose_surface_format(
|
||||
available_formats: &[vk::SurfaceFormatKHR],
|
||||
desired_format: vk::SurfaceFormatKHR,
|
||||
) -> vk::SurfaceFormatKHR {
|
||||
for format in available_formats {
|
||||
if format.format == desired_format.format
|
||||
&& format.color_space == desired_format.color_space
|
||||
{
|
||||
return *format;
|
||||
}
|
||||
}
|
||||
|
||||
tracing::warn!(
|
||||
"Desired swapchain format {:?}/{:?} not available. Falling back to {:?}/{:?}.",
|
||||
desired_format.format,
|
||||
desired_format.color_space,
|
||||
available_formats[0].format,
|
||||
available_formats[0].color_space
|
||||
);
|
||||
available_formats[0]
|
||||
}
|
||||
|
||||
fn choose_present_mode(
|
||||
available_modes: &[vk::PresentModeKHR],
|
||||
desired_mode: vk::PresentModeKHR,
|
||||
) -> vk::PresentModeKHR {
|
||||
if desired_mode == vk::PresentModeKHR::MAILBOX
|
||||
&& available_modes.contains(&vk::PresentModeKHR::MAILBOX)
|
||||
{
|
||||
return vk::PresentModeKHR::MAILBOX;
|
||||
}
|
||||
|
||||
if desired_mode == vk::PresentModeKHR::IMMEDIATE
|
||||
&& available_modes.contains(&vk::PresentModeKHR::IMMEDIATE)
|
||||
{
|
||||
return vk::PresentModeKHR::IMMEDIATE;
|
||||
}
|
||||
|
||||
vk::PresentModeKHR::FIFO
|
||||
}
|
||||
|
||||
fn choose_extent(
|
||||
capabilities: vk::SurfaceCapabilitiesKHR,
|
||||
desired_extent: vk::Extent2D,
|
||||
) -> vk::Extent2D {
|
||||
if capabilities.current_extent.width != u32::MAX {
|
||||
capabilities.current_extent
|
||||
} else {
|
||||
vk::Extent2D {
|
||||
width: desired_extent.width.clamp(
|
||||
capabilities.min_image_extent.width,
|
||||
capabilities.max_image_extent.width,
|
||||
),
|
||||
height: desired_extent.height.clamp(
|
||||
capabilities.min_image_extent.height,
|
||||
capabilities.max_image_extent.height,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn choose_image_count(capabilities: vk::SurfaceCapabilitiesKHR, desired_count: u32) -> u32 {
|
||||
let mut count = desired_count.max(capabilities.min_image_count);
|
||||
if capabilities.max_image_count > 0 {
|
||||
count = count.min(capabilities.max_image_count);
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
unsafe fn create_image_views(
|
||||
device: &ash::Device,
|
||||
images: &[vk::Image],
|
||||
format: vk::Format,
|
||||
) -> Result<Vec<vk::ImageView>> {
|
||||
images
|
||||
.iter()
|
||||
.map(|image| {
|
||||
let create_info = vk::ImageViewCreateInfo::default()
|
||||
.image(*image)
|
||||
.view_type(vk::ImageViewType::TYPE_2D)
|
||||
.format(format)
|
||||
.components(vk::ComponentMapping {
|
||||
r: vk::ComponentSwizzle::IDENTITY,
|
||||
g: vk::ComponentSwizzle::IDENTITY,
|
||||
b: vk::ComponentSwizzle::IDENTITY,
|
||||
a: vk::ComponentSwizzle::IDENTITY,
|
||||
})
|
||||
.subresource_range(vk::ImageSubresourceRange {
|
||||
aspect_mask: vk::ImageAspectFlags::COLOR,
|
||||
base_mip_level: 0,
|
||||
level_count: 1,
|
||||
base_array_layer: 0,
|
||||
layer_count: 1,
|
||||
});
|
||||
device.create_image_view(&create_info, None)
|
||||
})
|
||||
.collect::<std::result::Result<Vec<_>, _>>()
|
||||
.map_err(GfxHalError::VulkanError)
|
||||
}
|
||||
|
||||
/// Destroys the image views associated with this swapchain.
|
||||
/// Called internally by Drop and potentially during recreation.
|
||||
unsafe fn destory_image_views(&mut self) {
|
||||
tracing::debug!(
|
||||
"Destroying {} swapchain image views...",
|
||||
self.image_views.len()
|
||||
);
|
||||
for view in self.image_views.drain(..) {
|
||||
self.device.raw().destroy_image_view(view, None);
|
||||
}
|
||||
tracing::debug!("Swapchain image views destroyed.")
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Swapchain {
|
||||
fn drop(&mut self) {
|
||||
tracing::debug!("Destroying swapchain...");
|
||||
unsafe {
|
||||
self.destory_image_views();
|
||||
self.swapchain_loader
|
||||
.destroy_swapchain(self.swapchain, None);
|
||||
}
|
||||
tracing::debug!("Swapchain destroyed.")
|
||||
}
|
||||
}
|
||||
183
crates/gfx_hal/src/sync.rs
Normal file
183
crates/gfx_hal/src/sync.rs
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
use std::{sync::Arc, time::Duration};
|
||||
|
||||
use ash::vk;
|
||||
|
||||
use crate::{
|
||||
device::Device,
|
||||
error::{GfxHalError, Result},
|
||||
};
|
||||
|
||||
/// Wraps a `vk::Fence`, used for CPU-GPU synchronization.
|
||||
///
|
||||
/// Owns the `vk::Fence` handle.
|
||||
pub struct Fence {
|
||||
device: Arc<Device>,
|
||||
fence: vk::Fence,
|
||||
}
|
||||
|
||||
impl Fence {
|
||||
/// Creates a new `Fence`.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `device` - The logical device.
|
||||
/// * `signaled` - If true, the fence is created in the signaled state.
|
||||
pub fn new(device: Arc<Device>, signaled: bool) -> Result<Self> {
|
||||
let create_flags = if signaled {
|
||||
vk::FenceCreateFlags::SIGNALED
|
||||
} else {
|
||||
vk::FenceCreateFlags::empty()
|
||||
};
|
||||
let create_info = vk::FenceCreateInfo::default().flags(create_flags);
|
||||
let fence = unsafe { device.raw().create_fence(&create_info, None)? };
|
||||
tracing::trace!("Created Fence (signaled: {})", signaled);
|
||||
Ok(Self { device, fence })
|
||||
}
|
||||
|
||||
/// Waits for the fence to become signaled.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `timeout` - Maximum duration to wait. `None` waits indefinitely.
|
||||
pub fn wait(&self, timeout: Option<Duration>) -> Result<()> {
|
||||
let timeout_ns = timeout.map_or(u64::MAX, |d| d.as_nanos() as u64);
|
||||
tracing::trace!("Waiting for Fence with timeout: {:?}", timeout);
|
||||
let fences = [self.fence];
|
||||
match unsafe { self.device.raw().wait_for_fences(&fences, true, timeout_ns) } {
|
||||
Ok(_) => {
|
||||
tracing::trace!("Fence signaled.");
|
||||
Ok(())
|
||||
}
|
||||
Err(vk::Result::TIMEOUT) => {
|
||||
tracing::trace!("Fence wait timed out.");
|
||||
Err(GfxHalError::VulkanError(vk::Result::TIMEOUT)) // Return timeout error
|
||||
}
|
||||
Err(e) => Err(GfxHalError::VulkanError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resets the fence to the unsignaled state.
|
||||
/// Must only be called when the fence is not in use by pending GPU work.
|
||||
pub fn reset(&self) -> Result<()> {
|
||||
tracing::trace!("Resetting Fence.");
|
||||
let fences = [self.fence];
|
||||
unsafe { self.device.raw().reset_fences(&fences)? };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks the current status of the fence without waiting.
|
||||
/// Returns `Ok(true)` if signaled, `Ok(false)` if unsignaled.
|
||||
pub fn status(&self) -> Result<bool> {
|
||||
match unsafe { self.device.raw().get_fence_status(self.fence) } {
|
||||
Ok(signaled) => Ok(signaled),
|
||||
// NOT_READY means unsignaled, not an error in this context
|
||||
Err(vk::Result::NOT_READY) => Ok(false),
|
||||
Err(e) => Err(GfxHalError::VulkanError(e)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the raw `vk::Fence` handle.
|
||||
pub fn handle(&self) -> vk::Fence {
|
||||
self.fence
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Fence {
|
||||
fn drop(&mut self) {
|
||||
tracing::trace!("Destroying fence...");
|
||||
unsafe {
|
||||
self.device.raw().destroy_fence(self.fence, None);
|
||||
}
|
||||
tracing::trace!("Fence destroyed.")
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a `vk::Semaphore`, used for GPU-GPU synchronization (within or across queues).
|
||||
///
|
||||
/// Owns the `vk::Semaphore` handle.
|
||||
pub struct Semaphore {
|
||||
device: Arc<Device>,
|
||||
semaphore: vk::Semaphore,
|
||||
}
|
||||
|
||||
impl Semaphore {
|
||||
/// Creates a new `Semaphore`.
|
||||
pub fn new(device: Arc<Device>) -> Result<Self> {
|
||||
let create_info = vk::SemaphoreCreateInfo::default();
|
||||
let semaphore = unsafe { device.raw().create_semaphore(&create_info, None)? };
|
||||
tracing::trace!("Created Semaphore.");
|
||||
Ok(Self { device, semaphore })
|
||||
}
|
||||
|
||||
/// Gets the raw `vk::Semaphore` handle.
|
||||
pub fn handle(&self) -> vk::Semaphore {
|
||||
self.semaphore
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Semaphore {
|
||||
fn drop(&mut self) {
|
||||
tracing::trace!("Destroying Semaphore...");
|
||||
unsafe {
|
||||
self.device.raw().destroy_semaphore(self.semaphore, None);
|
||||
}
|
||||
tracing::trace!("Semaphore destroyed.");
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps a `vk::Event`, used for fine-grained GPU-GPU or GPU-Host synchronization.
|
||||
///
|
||||
/// Owns the `vk::Event` handle.
|
||||
pub struct Event {
|
||||
device: Arc<Device>,
|
||||
event: vk::Event,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
/// Creates a new `Event`.
|
||||
pub fn new(device: Arc<Device>) -> Result<Self> {
|
||||
let create_info = vk::EventCreateInfo::default();
|
||||
let event = unsafe { device.raw().create_event(&create_info, None)? };
|
||||
tracing::trace!("Created Event.");
|
||||
Ok(Self { device, event })
|
||||
}
|
||||
|
||||
/// Sets the event from the host (CPU).
|
||||
pub fn set(&self) -> Result<()> {
|
||||
tracing::trace!("Setting Event from host.");
|
||||
unsafe { self.device.raw().set_event(self.event)? };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resets the event from the host (CPU).
|
||||
pub fn reset(&self) -> Result<()> {
|
||||
tracing::trace!("Resetting Event from host.");
|
||||
unsafe { self.device.raw().reset_event(self.event)? };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Checks the status of the event from the host (CPU).
|
||||
/// Returns `Ok(true)` if set, `Ok(false)` if reset.
|
||||
pub fn status(&self) -> Result<bool> {
|
||||
let res = unsafe { self.device.raw().get_event_status(self.event) }?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
/// Gets the raw `vk::Event` handle.
|
||||
pub fn handle(&self) -> vk::Event {
|
||||
self.event
|
||||
}
|
||||
|
||||
// Note: Setting/resetting/waiting on events from the GPU involves
|
||||
// vkCmdSetEvent, vkCmdResetEvent, vkCmdWaitEvents within command buffers.
|
||||
// These are not wrapped here but would be used via device.raw() when
|
||||
// recording command buffers.
|
||||
}
|
||||
|
||||
impl Drop for Event {
|
||||
fn drop(&mut self) {
|
||||
tracing::trace!("Destroying Event...");
|
||||
unsafe {
|
||||
self.device.raw().destroy_event(self.event, None);
|
||||
}
|
||||
tracing::trace!("Event destroyed.");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue