new architecture
This commit is contained in:
parent
9f7e72b784
commit
9cfd9d8b17
28 changed files with 2625 additions and 5351 deletions
18
crates/engine/Cargo.toml
Normal file
18
crates/engine/Cargo.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "engine"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ash.workspace = true
|
||||
winit.workspace = true
|
||||
raw-window-handle.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
glam.workspace = true
|
||||
bytemuck.workspace = true
|
||||
thiserror.workspace = true
|
||||
gpu-allocator.workspace = true
|
||||
|
||||
gfx_hal = { path = "../gfx_hal" }
|
||||
resource_manager = { path = "../resource_manager" }
|
||||
5
crates/engine/src/main.rs
Normal file
5
crates/engine/src/main.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
mod scene_data;
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
67
crates/engine/src/scene_data.rs
Normal file
67
crates/engine/src/scene_data.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
use core::f32;
|
||||
|
||||
use bytemuck::{Pod, Zeroable};
|
||||
use glam::{Mat4, Vec3};
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
pub struct Vertex {
|
||||
pub pos: [f32; 3],
|
||||
pub normal: [f32; 3],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
|
||||
pub struct UniformBufferObject {
|
||||
pub model: Mat4,
|
||||
pub view: Mat4,
|
||||
pub proj: Mat4,
|
||||
pub light_dir: Vec3,
|
||||
pub _padding: f32,
|
||||
pub light_color: Vec3,
|
||||
pub _padding2: f32,
|
||||
}
|
||||
|
||||
pub fn create_sphere(radius: f32, sectors: u32, stacks: u32) -> (Vec<Vertex>, Vec<u32>) {
|
||||
let mut vertices = Vec::new();
|
||||
let mut indices = Vec::new();
|
||||
|
||||
let sector_step = 2.0 * f32::consts::PI / sectors as f32;
|
||||
let stack_step = f32::consts::PI / stacks as f32;
|
||||
|
||||
for i in 0..stacks {
|
||||
let stack_angle = std::f32::consts::PI / 2.0 - (i as f32 * stack_step);
|
||||
let xy = radius * stack_angle.cos();
|
||||
let z = radius * stack_angle.sin();
|
||||
|
||||
for j in 0..=sectors {
|
||||
let sector_angle = j as f32 * sector_step;
|
||||
let x = xy * sector_angle.cos();
|
||||
let y = xy * sector_angle.sin();
|
||||
|
||||
let pos = [x, y, z];
|
||||
let normal = Vec3::from(pos).normalize().to_array();
|
||||
vertices.push(Vertex { pos, normal });
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0..stacks {
|
||||
let k1 = i * (sectors + 1);
|
||||
let k2 = k1 + sectors + 1;
|
||||
|
||||
for j in 0..sectors {
|
||||
if i != 0 {
|
||||
indices.push(k1 + j);
|
||||
indices.push(k2 + j);
|
||||
indices.push(k1 + j + 1);
|
||||
}
|
||||
if i != (stacks - 1) {
|
||||
indices.push(k1 + j + 1);
|
||||
indices.push(k2 + j);
|
||||
indices.push(k2 + j + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(vertices, indices)
|
||||
}
|
||||
12
crates/gfx_hal/Cargo.toml
Normal file
12
crates/gfx_hal/Cargo.toml
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "gfx_hal"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ash.workspace = true
|
||||
ash-window.workspace = true
|
||||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
winit.workspace = true
|
||||
parking_lot.workspace = true
|
||||
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.");
|
||||
}
|
||||
}
|
||||
13
crates/resource_manager/Cargo.toml
Normal file
13
crates/resource_manager/Cargo.toml
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "resource_manager"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ash.workspace = true
|
||||
gpu-allocator.workspace = true
|
||||
thiserror.workspace = true
|
||||
parking_lot.workspace = true
|
||||
tracing.workspace = true
|
||||
|
||||
gfx_hal = { path = "../gfx_hal" }
|
||||
48
crates/resource_manager/src/error.rs
Normal file
48
crates/resource_manager/src/error.rs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
use ash::vk;
|
||||
use thiserror::Error;
|
||||
|
||||
/// Error type for the resource_manager crate.
|
||||
#[derive(Error, Debug)]
|
||||
pub enum ResourceManagerError {
|
||||
#[error("Vulkan API error: {0}")]
|
||||
VulkanError(#[from] vk::Result),
|
||||
|
||||
#[error("GPU allocation error: {0}")]
|
||||
AllocationError(#[from] gpu_allocator::AllocationError),
|
||||
|
||||
#[error("Resource handle {0} not found")]
|
||||
HandleNotFound(u64),
|
||||
|
||||
#[error("Failed to map buffer memory")]
|
||||
MappingFailed,
|
||||
|
||||
#[error("Buffer is not CPU visible or mapped")]
|
||||
NotMapped,
|
||||
|
||||
#[error("Failed to find suitable memory type")]
|
||||
NoSuitableMemoryType,
|
||||
|
||||
#[error("Failed to find a queue supporting transfer operations")]
|
||||
NoTransferQueue,
|
||||
|
||||
#[error("Staging transfer failed: {0}")]
|
||||
TransferFailed(String),
|
||||
|
||||
#[error("Resource lock poisoned: {0}")]
|
||||
LockPoisoned(String),
|
||||
|
||||
#[error("Error occurred in GfxHal: {0}")]
|
||||
GfxHalError(#[from] gfx_hal::error::GfxHalError),
|
||||
|
||||
#[error("An unexpected error occurred: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
// Implement conversion from Lock Poison errors
|
||||
impl<T> From<std::sync::PoisonError<T>> for ResourceManagerError {
|
||||
fn from(e: std::sync::PoisonError<T>) -> Self {
|
||||
ResourceManagerError::LockPoisoned(e.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T, E = ResourceManagerError> = std::result::Result<T, E>;
|
||||
609
crates/resource_manager/src/lib.rs
Normal file
609
crates/resource_manager/src/lib.rs
Normal file
|
|
@ -0,0 +1,609 @@
|
|||
mod error;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
hash::Hash,
|
||||
sync::{
|
||||
atomic::{AtomicU64, Ordering},
|
||||
Arc,
|
||||
},
|
||||
};
|
||||
|
||||
use ash::vk;
|
||||
use gfx_hal::{device::Device, instance::Instance, queue::Queue};
|
||||
use tracing::{debug, error, trace, warn};
|
||||
|
||||
pub use error::{ResourceManagerError, Result};
|
||||
use gpu_allocator::{
|
||||
vulkan::{Allocation, AllocationCreateDesc, Allocator, AllocatorCreateDesc},
|
||||
MemoryLocation,
|
||||
};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct BufferHandle(u64);
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
||||
pub struct ImageHandle(u64);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BufferInfo {
|
||||
pub handle: BufferHandle,
|
||||
pub buffer: vk::Buffer,
|
||||
pub size: vk::DeviceSize,
|
||||
pub usage: vk::BufferUsageFlags,
|
||||
pub mapped_ptr: Option<*mut u8>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ImageInfo {
|
||||
pub handle: ImageHandle,
|
||||
/// Non-owning handle.
|
||||
pub image: vk::Image,
|
||||
/// Non-owning handle.
|
||||
pub view: vk::ImageView,
|
||||
pub format: vk::Format,
|
||||
pub extent: vk::Extent3D,
|
||||
pub usage: vk::ImageUsageFlags,
|
||||
pub layout: vk::ImageLayout,
|
||||
}
|
||||
|
||||
struct InternalBufferInfo {
|
||||
device: Arc<Device>, // Keep device alive for Drop
|
||||
allocator: Arc<Mutex<Allocator>>, // Needed for Drop
|
||||
buffer: vk::Buffer,
|
||||
allocation: Option<Allocation>, // Option because it's taken in Drop
|
||||
size: vk::DeviceSize,
|
||||
usage: vk::BufferUsageFlags,
|
||||
mapped_ptr: Option<*mut u8>,
|
||||
handle: BufferHandle,
|
||||
}
|
||||
|
||||
impl Drop for InternalBufferInfo {
|
||||
fn drop(&mut self) {
|
||||
trace!("Dropping InternalBufferInfo for handle: {:?}", self.handle);
|
||||
if let Some(allocation) = self.allocation.take() {
|
||||
let mut allc = self.allocator.lock();
|
||||
if let Err(e) = allc.free(allocation) {
|
||||
error!(
|
||||
"Failed to free allocation for buffer handle {:?}, {}",
|
||||
self.handle, e
|
||||
);
|
||||
} else {
|
||||
trace!("Freed alloation for buffer handle: {:?}", self.handle);
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
self.device.raw().destroy_buffer(self.buffer, None);
|
||||
}
|
||||
trace!("Destroyed vk::Buffer for handle {:?}", self.handle);
|
||||
}
|
||||
}
|
||||
|
||||
struct InternalImageInfo {
|
||||
device: Arc<Device>, // Keep device alive for Drop
|
||||
allocator: Arc<Mutex<Allocator>>, // Needed for Drop
|
||||
image: vk::Image,
|
||||
view: vk::ImageView,
|
||||
allocation: Option<Allocation>, // Option because it's taken in Drop
|
||||
format: vk::Format,
|
||||
extent: vk::Extent3D,
|
||||
usage: vk::ImageUsageFlags,
|
||||
layout: vk::ImageLayout,
|
||||
handle: ImageHandle,
|
||||
}
|
||||
|
||||
impl Drop for InternalImageInfo {
|
||||
fn drop(&mut self) {
|
||||
trace!("Dropping InternalImageInfo for handle {:?}", self.handle);
|
||||
// Destroy view first
|
||||
unsafe {
|
||||
self.device.raw().destroy_image_view(self.view, None);
|
||||
}
|
||||
// Then free memory
|
||||
if let Some(allocation) = self.allocation.take() {
|
||||
let mut allocator = self.allocator.lock();
|
||||
if let Err(e) = allocator.free(allocation) {
|
||||
error!(
|
||||
"Failed to free allocation for image handle {:?}: {}",
|
||||
self.handle, e
|
||||
);
|
||||
} else {
|
||||
trace!("Freed allocation for image handle {:?}", self.handle);
|
||||
}
|
||||
}
|
||||
// Then destroy image
|
||||
unsafe {
|
||||
self.device.raw().destroy_image(self.image, None);
|
||||
}
|
||||
trace!(
|
||||
"Destroyed vk::Image/vk::ImageView for handle {:?}",
|
||||
self.handle
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
struct TransferSetup {
|
||||
command_pool: vk::CommandPool,
|
||||
queue: Arc<Queue>,
|
||||
fence: vk::Fence,
|
||||
}
|
||||
|
||||
pub struct ResourceManager {
|
||||
_instance: Arc<Instance>,
|
||||
device: Arc<Device>,
|
||||
allocator: Arc<Mutex<Allocator>>,
|
||||
buffers: Mutex<HashMap<u64, InternalBufferInfo>>,
|
||||
images: Mutex<HashMap<u64, InternalImageInfo>>,
|
||||
next_id: AtomicU64,
|
||||
transfer_setup: Mutex<Option<TransferSetup>>,
|
||||
}
|
||||
|
||||
impl ResourceManager {
|
||||
/// Creates a new ResourceManager.
|
||||
pub fn new(instance: Arc<Instance>, device: Arc<Device>) -> Result<Self> {
|
||||
debug!("Initializing ResourceManager...");
|
||||
let allocator = Allocator::new(&AllocatorCreateDesc {
|
||||
instance: instance.ash_instance().clone(),
|
||||
device: device.raw().clone(),
|
||||
physical_device: device.physical_device_handle(),
|
||||
debug_settings: Default::default(),
|
||||
buffer_device_address: true,
|
||||
allocation_sizes: Default::default(),
|
||||
})?;
|
||||
debug!("GPU Allocator created.");
|
||||
|
||||
Ok(Self {
|
||||
_instance: instance,
|
||||
device,
|
||||
allocator: Arc::new(Mutex::new(allocator)),
|
||||
buffers: Mutex::new(HashMap::new()),
|
||||
images: Mutex::new(HashMap::new()),
|
||||
next_id: AtomicU64::new(1),
|
||||
transfer_setup: Mutex::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
/// Gets or initializes the TransferSetup resources.
|
||||
fn get_transfer_setup(&self) -> Result<TransferSetup> {
|
||||
let mut setup_guard = self.transfer_setup.lock();
|
||||
|
||||
if let Some(setup) = setup_guard.as_ref() {
|
||||
// Simple check: Reset fence before reusing
|
||||
unsafe { self.device.raw().reset_fences(&[setup.fence])? };
|
||||
return Ok(TransferSetup {
|
||||
// Return a copy/clone
|
||||
command_pool: setup.command_pool,
|
||||
queue: setup.queue.clone(),
|
||||
fence: setup.fence,
|
||||
});
|
||||
}
|
||||
|
||||
debug!("Initializing TransferSetup...");
|
||||
// Find a queue that supports transfer (prefer dedicated, fallback to graphics)
|
||||
let queue_family_index = self
|
||||
.device
|
||||
.transfer_queue_family_index()
|
||||
.or(self.device.compute_queue_family_index()) // Try compute as fallback
|
||||
.unwrap_or(self.device.graphics_queue_family_index()); // Graphics as last resort
|
||||
|
||||
let queue = self
|
||||
.device
|
||||
.get_queue(queue_family_index, 0)
|
||||
.ok_or(ResourceManagerError::NoTransferQueue)?;
|
||||
|
||||
// Create command pool for transfer commands
|
||||
let pool_info = vk::CommandPoolCreateInfo::default()
|
||||
.flags(vk::CommandPoolCreateFlags::TRANSIENT) // Hint that buffers are short-lived
|
||||
.queue_family_index(queue_family_index);
|
||||
let command_pool = unsafe { self.device.raw().create_command_pool(&pool_info, None)? };
|
||||
|
||||
// Create a fence for waiting
|
||||
let fence_info = vk::FenceCreateInfo::default();
|
||||
let fence = unsafe { self.device.raw().create_fence(&fence_info, None)? };
|
||||
|
||||
let new_setup = TransferSetup {
|
||||
command_pool,
|
||||
queue,
|
||||
fence,
|
||||
};
|
||||
*setup_guard = Some(new_setup); // Store it
|
||||
debug!("TransferSetup initialized.");
|
||||
|
||||
// Return a new copy for use
|
||||
Ok(TransferSetup {
|
||||
command_pool: setup_guard.as_ref().unwrap().command_pool,
|
||||
queue: setup_guard.as_ref().unwrap().queue.clone(),
|
||||
fence: setup_guard.as_ref().unwrap().fence,
|
||||
})
|
||||
}
|
||||
|
||||
/// Helper to allocate, begin, end, submit, and wait for a single command buffer.
|
||||
unsafe fn submit_commands_and_wait<F>(
|
||||
&self,
|
||||
transfer_setup: &TransferSetup,
|
||||
record_fn: F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: FnOnce(vk::CommandBuffer) -> Result<()>,
|
||||
{
|
||||
let device = self.device.raw();
|
||||
|
||||
// Allocate command buffer
|
||||
let alloc_info = vk::CommandBufferAllocateInfo::default()
|
||||
.command_pool(transfer_setup.command_pool)
|
||||
.level(vk::CommandBufferLevel::PRIMARY)
|
||||
.command_buffer_count(1);
|
||||
let command_buffer = device.allocate_command_buffers(&alloc_info)?[0];
|
||||
|
||||
// Begin recording
|
||||
let begin_info = vk::CommandBufferBeginInfo::default()
|
||||
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
|
||||
device.begin_command_buffer(command_buffer, &begin_info)?;
|
||||
|
||||
// Record user commands
|
||||
let record_result = record_fn(command_buffer);
|
||||
|
||||
// End recording (even if user function failed, to allow cleanup)
|
||||
device.end_command_buffer(command_buffer)?;
|
||||
|
||||
// Check user function result *after* ending buffer
|
||||
record_result?;
|
||||
|
||||
let binding = [command_buffer];
|
||||
// Submit
|
||||
let submits = [vk::SubmitInfo::default().command_buffers(&binding)];
|
||||
// Use the transfer queue and fence
|
||||
transfer_setup.queue.submit(&submits, None)?; // Submit without fence initially
|
||||
|
||||
// Wait for completion using a separate wait call
|
||||
// This avoids holding the queue's internal submit lock during the wait.
|
||||
let fences = [transfer_setup.fence];
|
||||
match device.wait_for_fences(&fences, true, u64::MAX) {
|
||||
Ok(_) => {}
|
||||
Err(vk::Result::TIMEOUT) => {
|
||||
// Should not happen with u64::MAX
|
||||
warn!("Transfer fence wait timed out unexpectedly.");
|
||||
return Err(ResourceManagerError::TransferFailed(
|
||||
"Fence wait timeout".to_string(),
|
||||
));
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
|
||||
// Free command buffer
|
||||
device.free_command_buffers(transfer_setup.command_pool, &[command_buffer]);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Creates a Vulkan buffer and allocates/binds memory for it.
|
||||
pub fn create_buffer(
|
||||
&self,
|
||||
size: vk::DeviceSize,
|
||||
usage: vk::BufferUsageFlags,
|
||||
location: MemoryLocation,
|
||||
) -> Result<BufferHandle> {
|
||||
trace!(
|
||||
"Creating buffer: size={}, usage={:?}, location={:?}",
|
||||
size,
|
||||
usage,
|
||||
location
|
||||
);
|
||||
let buffer_info = vk::BufferCreateInfo::default()
|
||||
.size(size)
|
||||
.usage(usage)
|
||||
.sharing_mode(vk::SharingMode::EXCLUSIVE); // Assuming exclusive access
|
||||
|
||||
let buffer = unsafe { self.device.raw().create_buffer(&buffer_info, None)? };
|
||||
|
||||
let requirements = unsafe { self.device.raw().get_buffer_memory_requirements(buffer) };
|
||||
|
||||
let allocation = self.allocator.lock().allocate(&AllocationCreateDesc {
|
||||
name: &format!("buffer_usage_{:?}_loc_{:?}", usage, location),
|
||||
requirements,
|
||||
location,
|
||||
linear: true, // Buffers are linear
|
||||
allocation_scheme: gpu_allocator::vulkan::AllocationScheme::GpuAllocatorManaged,
|
||||
})?;
|
||||
|
||||
unsafe {
|
||||
self.device.raw().bind_buffer_memory(
|
||||
buffer,
|
||||
allocation.memory(),
|
||||
allocation.offset(),
|
||||
)?;
|
||||
}
|
||||
trace!("Buffer memory bound.");
|
||||
|
||||
let mapped_ptr = allocation.mapped_ptr().map(|p| p.as_ptr() as *mut u8);
|
||||
if mapped_ptr.is_some() {
|
||||
trace!("Buffer memory is mapped.");
|
||||
}
|
||||
|
||||
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
|
||||
let handle = BufferHandle(id);
|
||||
|
||||
let internal_info = InternalBufferInfo {
|
||||
device: self.device.clone(),
|
||||
allocator: self.allocator.clone(),
|
||||
buffer,
|
||||
allocation: Some(allocation),
|
||||
size,
|
||||
usage,
|
||||
mapped_ptr,
|
||||
handle,
|
||||
};
|
||||
|
||||
self.buffers.lock().insert(id, internal_info);
|
||||
debug!("Buffer created successfully: handle={:?}", handle);
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
/// Creates a buffer, allocates memory, and uploads initial data using a staging buffer.
|
||||
pub fn create_buffer_init(
|
||||
&self,
|
||||
usage: vk::BufferUsageFlags, // Usage for the *final* buffer
|
||||
location: MemoryLocation, // Memory location for the *final* buffer
|
||||
data: &[u8],
|
||||
) -> Result<BufferHandle> {
|
||||
let size = data.len() as vk::DeviceSize;
|
||||
if size == 0 {
|
||||
return Err(ResourceManagerError::Other(
|
||||
"Cannot create buffer with empty data".to_string(),
|
||||
));
|
||||
}
|
||||
debug!(
|
||||
"Creating buffer with init data: size={}, usage={:?}, location={:?}",
|
||||
size, usage, location
|
||||
);
|
||||
|
||||
// 1. Create Staging Buffer
|
||||
let staging_usage = vk::BufferUsageFlags::TRANSFER_SRC;
|
||||
let staging_location = MemoryLocation::CpuToGpu; // Mapped memory for upload
|
||||
let staging_handle = self.create_buffer(size, staging_usage, staging_location)?;
|
||||
|
||||
// 2. Map & Copy data to staging buffer
|
||||
{
|
||||
// Scope for buffer info and mapping pointer
|
||||
let staging_info = self.get_buffer_info(staging_handle)?;
|
||||
let mapping = staging_info
|
||||
.mapped_ptr
|
||||
.ok_or(ResourceManagerError::MappingFailed)?;
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(data.as_ptr(), mapping, data.len());
|
||||
}
|
||||
// If memory is not HOST_COHERENT, need to flush here:
|
||||
// let mem_range = vk::MappedMemoryRange::builder().memory(...).offset(...).size(size);
|
||||
// unsafe { self.device.raw().flush_mapped_memory_ranges(&[mem_range])? };
|
||||
trace!("Data copied to staging buffer.");
|
||||
} // staging_info goes out of scope
|
||||
|
||||
// 3. Create Destination Buffer
|
||||
let final_usage = usage | vk::BufferUsageFlags::TRANSFER_DST; // Add transfer dest usage
|
||||
let dest_handle = self.create_buffer(size, final_usage, location)?;
|
||||
|
||||
// 4. Record and submit transfer command
|
||||
let transfer_setup = self.get_transfer_setup()?;
|
||||
let dest_info = self.get_buffer_info(dest_handle)?; // Get info for vk::Buffer handle
|
||||
let staging_info_for_copy = self.get_buffer_info(staging_handle)?; // Get info again
|
||||
|
||||
trace!("Submitting buffer copy command...");
|
||||
unsafe {
|
||||
self.submit_commands_and_wait(&transfer_setup, |cmd| {
|
||||
let region = vk::BufferCopy::default()
|
||||
.src_offset(0)
|
||||
.dst_offset(0)
|
||||
.size(size);
|
||||
self.device.raw().cmd_copy_buffer(
|
||||
cmd,
|
||||
staging_info_for_copy.buffer, // Use raw handle from info struct
|
||||
dest_info.buffer, // Use raw handle from info struct
|
||||
&[region],
|
||||
);
|
||||
Ok(()) // Return Ok inside the closure
|
||||
})?;
|
||||
}
|
||||
trace!("Buffer copy command finished.");
|
||||
|
||||
// 5. Cleanup staging buffer
|
||||
self.destroy_buffer(staging_handle)?; // This frees memory and destroys buffer
|
||||
debug!("Staging buffer destroyed.");
|
||||
|
||||
Ok(dest_handle)
|
||||
}
|
||||
|
||||
/// Creates a Vulkan image and allocates/binds memory for it.
|
||||
/// Also creates a default `ImageView`.
|
||||
/// Does not handle data uploads or layout transitions.
|
||||
pub fn create_image(
|
||||
&self,
|
||||
create_info: &vk::ImageCreateInfo, // User provides image details
|
||||
location: MemoryLocation,
|
||||
) -> Result<ImageHandle> {
|
||||
trace!(
|
||||
"Creating image: format={:?}, extent={:?}, usage={:?}, location={:?}",
|
||||
create_info.format,
|
||||
create_info.extent,
|
||||
create_info.usage,
|
||||
location
|
||||
);
|
||||
|
||||
let image = unsafe { self.device.raw().create_image(create_info, None)? };
|
||||
|
||||
let requirements = unsafe { self.device.raw().get_image_memory_requirements(image) };
|
||||
|
||||
let allocation = self.allocator.lock().allocate(&AllocationCreateDesc {
|
||||
name: &format!(
|
||||
"image_fmt_{:?}_usage_{:?}",
|
||||
create_info.format, create_info.usage
|
||||
),
|
||||
requirements,
|
||||
location,
|
||||
linear: create_info.tiling == vk::ImageTiling::LINEAR,
|
||||
allocation_scheme: gpu_allocator::vulkan::AllocationScheme::GpuAllocatorManaged,
|
||||
})?;
|
||||
|
||||
unsafe {
|
||||
self.device
|
||||
.raw()
|
||||
.bind_image_memory(image, allocation.memory(), allocation.offset())?;
|
||||
}
|
||||
trace!("Image memory bound.");
|
||||
|
||||
// Create a default image view
|
||||
// TODO: Make view creation more flexible (allow different subresource ranges, types)
|
||||
let view_info = vk::ImageViewCreateInfo::default()
|
||||
.image(image)
|
||||
.view_type(vk::ImageViewType::TYPE_2D) // Assuming 2D, adjust based on create_info
|
||||
.format(create_info.format)
|
||||
.subresource_range(vk::ImageSubresourceRange {
|
||||
aspect_mask: vk::ImageAspectFlags::COLOR, // Assuming color, adjust based on usage
|
||||
base_mip_level: 0,
|
||||
level_count: create_info.mip_levels,
|
||||
base_array_layer: 0,
|
||||
layer_count: create_info.array_layers,
|
||||
});
|
||||
let view = unsafe { self.device.raw().create_image_view(&view_info, None)? };
|
||||
trace!("Default image view created.");
|
||||
|
||||
let id = self.next_id.fetch_add(1, Ordering::Relaxed);
|
||||
let handle = ImageHandle(id);
|
||||
|
||||
let internal_info = InternalImageInfo {
|
||||
device: self.device.clone(),
|
||||
allocator: self.allocator.clone(),
|
||||
image,
|
||||
view,
|
||||
allocation: Some(allocation),
|
||||
format: create_info.format,
|
||||
extent: create_info.extent,
|
||||
usage: create_info.usage,
|
||||
layout: create_info.initial_layout, // Store initial layout
|
||||
handle,
|
||||
};
|
||||
|
||||
self.images.lock().insert(id, internal_info);
|
||||
debug!("Image created successfully: handle={:?}", handle);
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
// TODO: Implement create_image_init (similar to create_buffer_init but uses vkCmdCopyBufferToImage and layout transitions)
|
||||
|
||||
/// Destroys a buffer and frees its memory.
|
||||
pub fn destroy_buffer(&self, handle: BufferHandle) -> Result<()> {
|
||||
debug!("Requesting destroy for buffer handle {:?}", handle);
|
||||
let mut buffers_map = self.buffers.lock();
|
||||
// Remove the entry. The Drop impl of InternalBufferInfo handles the cleanup.
|
||||
if buffers_map.remove(&handle.0).is_some() {
|
||||
debug!("Buffer handle {:?} removed for destruction.", handle);
|
||||
Ok(())
|
||||
} else {
|
||||
warn!(
|
||||
"Attempted to destroy non-existent buffer handle {:?}",
|
||||
handle
|
||||
);
|
||||
Err(ResourceManagerError::HandleNotFound(handle.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Destroys an image, its view, and frees its memory.
|
||||
pub fn destroy_image(&self, handle: ImageHandle) -> Result<()> {
|
||||
debug!("Requesting destroy for image handle {:?}", handle);
|
||||
let mut images_map = self.images.lock();
|
||||
// Remove the entry. The Drop impl of InternalImageInfo handles the cleanup.
|
||||
if images_map.remove(&handle.0).is_some() {
|
||||
debug!("Image handle {:?} removed for destruction.", handle);
|
||||
Ok(())
|
||||
} else {
|
||||
warn!(
|
||||
"Attempted to destroy non-existent image handle {:?}",
|
||||
handle
|
||||
);
|
||||
Err(ResourceManagerError::HandleNotFound(handle.0))
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets non-owning information about a buffer.
|
||||
pub fn get_buffer_info(&self, handle: BufferHandle) -> Result<BufferInfo> {
|
||||
let buffers_map = self.buffers.lock();
|
||||
buffers_map
|
||||
.get(&handle.0)
|
||||
.map(|internal| BufferInfo {
|
||||
handle: internal.handle,
|
||||
buffer: internal.buffer,
|
||||
size: internal.size,
|
||||
usage: internal.usage,
|
||||
mapped_ptr: internal.mapped_ptr,
|
||||
})
|
||||
.ok_or(ResourceManagerError::HandleNotFound(handle.0))
|
||||
}
|
||||
|
||||
/// Gets non-owning information about an image.
|
||||
pub fn get_image_info(&self, handle: ImageHandle) -> Result<ImageInfo> {
|
||||
let images_map = self.images.lock();
|
||||
images_map
|
||||
.get(&handle.0)
|
||||
.map(|internal| ImageInfo {
|
||||
handle: internal.handle,
|
||||
image: internal.image,
|
||||
view: internal.view,
|
||||
format: internal.format,
|
||||
extent: internal.extent,
|
||||
usage: internal.usage,
|
||||
layout: internal.layout, // Note: Layout tracking is basic here
|
||||
})
|
||||
.ok_or(ResourceManagerError::HandleNotFound(handle.0))
|
||||
}
|
||||
|
||||
/// Explicitly waits for the device to be idle. Useful before shutdown.
|
||||
pub fn wait_device_idle(&self) -> Result<(), ResourceManagerError> {
|
||||
self.device
|
||||
.wait_idle()
|
||||
.map_err(|e| ResourceManagerError::Other(format!("Device wait idle failed: {}", e)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ResourceManager {
|
||||
fn drop(&mut self) {
|
||||
debug!("Destroying ResourceManager...");
|
||||
// Ensure all GPU operations are finished before freeing memory/destroying resources
|
||||
if let Err(e) = self.device.wait_idle() {
|
||||
error!(
|
||||
"Failed to wait for device idle during ResourceManager drop: {}",
|
||||
e
|
||||
);
|
||||
// Proceeding with cleanup, but resources might still be in use!
|
||||
}
|
||||
|
||||
// Clear resource maps. This triggers the Drop impl for each Internal*Info,
|
||||
// which frees allocations and destroys Vulkan objects.
|
||||
let mut buffers_map = self.buffers.lock();
|
||||
debug!("Clearing {} buffer entries...", buffers_map.len());
|
||||
buffers_map.clear();
|
||||
let mut images_map = self.images.lock();
|
||||
debug!("Clearing {} image entries...", images_map.len());
|
||||
images_map.clear();
|
||||
|
||||
// Destroy transfer setup resources
|
||||
let mut setup_guard = self.transfer_setup.lock();
|
||||
if let Some(setup) = setup_guard.take() {
|
||||
// take() removes it from the Option
|
||||
debug!("Destroying TransferSetup resources...");
|
||||
unsafe {
|
||||
self.device.raw().destroy_fence(setup.fence, None);
|
||||
self.device
|
||||
.raw()
|
||||
.destroy_command_pool(setup.command_pool, None);
|
||||
}
|
||||
debug!("TransferSetup resources destroyed.");
|
||||
}
|
||||
|
||||
// The Allocator is wrapped in an Arc<Mutex<>>, so its Drop will be handled
|
||||
// when the last Arc reference (including those held by Internal*Info) is dropped.
|
||||
// gpu-allocator's Allocator Drop implementation should be empty, as memory
|
||||
// is freed via allocator.free().
|
||||
|
||||
debug!("ResourceManager destroyed.");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
[package]
|
||||
name = "shaders-shared"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
spirv-std.workspace = true
|
||||
bytemuck.workspace = true
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
#![cfg_attr(target_arch = "spirv", no_std)]
|
||||
|
||||
use spirv_std::glam::{Mat4, Vec3, Vec4};
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[repr(C, align(16))]
|
||||
pub struct Material {
|
||||
pub base_color: Vec4,
|
||||
pub metallic_factor: f32,
|
||||
pub roughness_factor: f32,
|
||||
pub _padding: [f32; 2],
|
||||
}
|
||||
|
||||
#[repr(C, align(16))]
|
||||
#[derive(Clone)]
|
||||
pub struct UniformBufferObject {
|
||||
pub model: Mat4,
|
||||
pub view: Mat4,
|
||||
pub proj: Mat4,
|
||||
pub camera_pos: Vec3,
|
||||
pub material: Material,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct PushConstants {
|
||||
pub texture_size: Vec4,
|
||||
}
|
||||
|
||||
unsafe impl bytemuck::Pod for PushConstants {}
|
||||
unsafe impl bytemuck::Zeroable for PushConstants {}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
[package]
|
||||
name = "vk-rs"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ash.workspace = true
|
||||
color-eyre.workspace = true
|
||||
winit.workspace = true
|
||||
raw-window-handle.workspace = true
|
||||
cfg-if = "1.0.0"
|
||||
cgmath = "0.18.0"
|
||||
spirv-std.workspace = true
|
||||
gpu-allocator.workspace = true
|
||||
egui-ash = { version = "0.4.0", features = ["gpu-allocator"] }
|
||||
tobj = "4.0.2"
|
||||
egui = "0.25.0"
|
||||
ash-window = "0.12.0"
|
||||
shaders-shared = { path = "../shaders-shared" }
|
||||
puffin = { git = "https://github.com/EmbarkStudios/puffin", rev = "5ac4e54164ee89bd68c29f288c2b5613afc2c929" }
|
||||
puffin_egui = { git = "https://github.com/EmbarkStudios/puffin", rev = "5ac4e54164ee89bd68c29f288c2b5613afc2c929" }
|
||||
gpu-profiler = { git = "https://github.com/zackartz/gpu-profiler", features = [
|
||||
"use-ash",
|
||||
] }
|
||||
gltf = { version = "1.4.1", features = ["import"] }
|
||||
image = "0.25.5"
|
||||
rayon = "1.10.0"
|
||||
bytemuck.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
shaderc = "0.8"
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
use shaderc::{Compiler, ShaderKind};
|
||||
use std::{
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
// Tell Cargo to rerun if shaders directory changes
|
||||
println!("cargo:rerun-if-changed=../../shaders");
|
||||
|
||||
let shader_dir = Path::new("../../shaders");
|
||||
let cache_dir = Path::new("../../shader-cache");
|
||||
|
||||
// Create shader cache directory if it doesn't exist
|
||||
fs::create_dir_all(cache_dir)?;
|
||||
|
||||
let compiler = Compiler::new().expect("Failed to create shader compiler");
|
||||
|
||||
// Compile all .vert and .frag files
|
||||
for entry in fs::read_dir(shader_dir)? {
|
||||
let entry = entry?;
|
||||
let path = entry.path();
|
||||
|
||||
if let Some(extension) = path.extension() {
|
||||
let kind = match extension.to_str() {
|
||||
Some("vert") => ShaderKind::Vertex,
|
||||
Some("frag") => ShaderKind::Fragment,
|
||||
_ => continue,
|
||||
};
|
||||
|
||||
let source = fs::read_to_string(&path)?;
|
||||
let file_name = path.file_name().unwrap().to_str().unwrap();
|
||||
|
||||
// Create output path
|
||||
let spirv_path = cache_dir.join(format!("{}.spv", file_name));
|
||||
|
||||
// Check if we need to recompile
|
||||
if should_compile(&path, &spirv_path) {
|
||||
println!("Compiling shader: {}", file_name);
|
||||
|
||||
let compiled =
|
||||
compiler.compile_into_spirv(&source, kind, file_name, "main", None)?;
|
||||
|
||||
let mut file = File::create(&spirv_path)?;
|
||||
file.write_all(compiled.as_binary_u8())?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn should_compile(source_path: &Path, output_path: &PathBuf) -> bool {
|
||||
// If output doesn't exist, we need to compile
|
||||
if !output_path.exists() {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get modification times
|
||||
let source_modified = fs::metadata(source_path)
|
||||
.and_then(|m| m.modified())
|
||||
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
|
||||
|
||||
let output_modified = fs::metadata(output_path)
|
||||
.and_then(|m| m.modified())
|
||||
.unwrap_or(std::time::SystemTime::UNIX_EPOCH);
|
||||
|
||||
// Compile if source is newer than output
|
||||
source_modified > output_modified
|
||||
}
|
||||
|
|
@ -1,722 +0,0 @@
|
|||
use std::{
|
||||
collections::HashSet,
|
||||
f32::consts::PI,
|
||||
ffi::CString,
|
||||
mem::ManuallyDrop,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use ash::{
|
||||
extensions::{
|
||||
ext::DebugUtils,
|
||||
khr::{Surface, Swapchain},
|
||||
},
|
||||
vk::{self, KhrAccelerationStructureFn, KhrDeferredHostOperationsFn, KhrRayTracingPipelineFn},
|
||||
Device, Entry, Instance,
|
||||
};
|
||||
use egui_ash::{
|
||||
raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle},
|
||||
winit, App, AppCreator, AshRenderState, CreationContext, HandleRedraw, RunOption, Theme,
|
||||
};
|
||||
use gpu_allocator::vulkan::{Allocator, AllocatorCreateDesc};
|
||||
use renderer::Renderer;
|
||||
use spirv_std::glam::Vec3;
|
||||
|
||||
mod renderer;
|
||||
mod texture_cache;
|
||||
|
||||
struct Game {
|
||||
entry: Entry,
|
||||
instance: Instance,
|
||||
device: Device,
|
||||
debug_utils_loader: DebugUtils,
|
||||
debug_messenger: vk::DebugUtilsMessengerEXT,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
surface_loader: Surface,
|
||||
swapchain_loader: Swapchain,
|
||||
surface: vk::SurfaceKHR,
|
||||
queue: vk::Queue,
|
||||
command_pool: vk::CommandPool,
|
||||
allocator: ManuallyDrop<Arc<Mutex<Allocator>>>,
|
||||
|
||||
pub renderer: Renderer,
|
||||
|
||||
theme: Theme,
|
||||
rotate_y: f32,
|
||||
|
||||
camera_position: Vec3,
|
||||
camera_yaw: f32,
|
||||
camera_pitch: f32,
|
||||
camera_fov: f32,
|
||||
right_mouse_pressed: bool,
|
||||
last_mouse_pos: Option<(f32, f32)>,
|
||||
mouse_sensitivity: f32,
|
||||
|
||||
last_fps_update: std::time::Instant,
|
||||
frame_count_since_last_update: i32,
|
||||
current_fps: f32,
|
||||
|
||||
bg_color: [f32; 3],
|
||||
model_color: [f32; 3],
|
||||
|
||||
show_profiler: bool,
|
||||
}
|
||||
|
||||
impl App for Game {
|
||||
fn ui(&mut self, ctx: &egui::Context) {
|
||||
puffin::GlobalProfiler::lock().new_frame();
|
||||
let now = std::time::Instant::now();
|
||||
self.frame_count_since_last_update += 1;
|
||||
|
||||
if now.duration_since(self.last_fps_update).as_secs_f32() >= 0.1 {
|
||||
self.current_fps = self.frame_count_since_last_update as f32
|
||||
/ now.duration_since(self.last_fps_update).as_secs_f32();
|
||||
self.frame_count_since_last_update = 0;
|
||||
self.last_fps_update = now;
|
||||
}
|
||||
|
||||
if self.show_profiler {
|
||||
puffin_egui::profiler_window(ctx);
|
||||
}
|
||||
|
||||
egui::SidePanel::left("my_side_panel").show(ctx, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Theme");
|
||||
let id = ui.make_persistent_id("theme_combo_box_side");
|
||||
egui::ComboBox::from_id_source(id)
|
||||
.selected_text(format!("{:?}", self.theme))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut self.theme, Theme::Dark, "Dark");
|
||||
ui.selectable_value(&mut self.theme, Theme::Light, "Light");
|
||||
});
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.label("Rotate");
|
||||
ui.add(egui::widgets::Slider::new(
|
||||
&mut self.rotate_y,
|
||||
-180.0..=180.0,
|
||||
));
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.label("Camera Position");
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("X:");
|
||||
ui.add(egui::DragValue::new(&mut self.camera_position.x).speed(0.1));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Y:");
|
||||
ui.add(egui::DragValue::new(&mut self.camera_position.y).speed(0.1));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Z:");
|
||||
ui.add(egui::DragValue::new(&mut self.camera_position.z).speed(0.1));
|
||||
});
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.label("FOV");
|
||||
ui.add(egui::widgets::Slider::new(
|
||||
&mut self.camera_fov,
|
||||
10.0..=150.0,
|
||||
));
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.label("Mouse Sensitivity");
|
||||
ui.add(egui::widgets::Slider::new(
|
||||
&mut self.mouse_sensitivity,
|
||||
0.05..=4.0,
|
||||
));
|
||||
ui.separator();
|
||||
|
||||
ui.color_edit_button_rgb(&mut self.bg_color);
|
||||
ui.color_edit_button_rgb(&mut self.model_color);
|
||||
|
||||
ui.separator();
|
||||
|
||||
if ui.button("Show Profiler").clicked() {
|
||||
self.show_profiler = !self.show_profiler;
|
||||
}
|
||||
ui.label(format!("FPS: {:.1}", self.current_fps));
|
||||
});
|
||||
|
||||
if !ctx.wants_keyboard_input() {
|
||||
let movement_speed = 0.1;
|
||||
|
||||
let forward = Vec3::new(
|
||||
self.camera_yaw.sin() * self.camera_pitch.cos(),
|
||||
self.camera_pitch.sin(),
|
||||
self.camera_yaw.cos() * self.camera_pitch.cos(),
|
||||
)
|
||||
.normalize();
|
||||
|
||||
// Calculate right direction
|
||||
let right = Vec3::new(
|
||||
(self.camera_yaw + std::f32::consts::FRAC_PI_2).sin(),
|
||||
0.0,
|
||||
(self.camera_yaw + std::f32::consts::FRAC_PI_2).cos(),
|
||||
)
|
||||
.normalize();
|
||||
|
||||
ctx.input(|i| {
|
||||
if i.key_down(egui::Key::W) {
|
||||
self.camera_position += forward * movement_speed;
|
||||
}
|
||||
if i.key_down(egui::Key::S) {
|
||||
self.camera_position -= forward * movement_speed;
|
||||
}
|
||||
if i.key_down(egui::Key::A) {
|
||||
self.camera_position -= right * movement_speed;
|
||||
}
|
||||
if i.key_down(egui::Key::D) {
|
||||
self.camera_position += right * movement_speed;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Handle mouse input for camera rotation
|
||||
let is_right_mouse_down = ctx.input(|i| i.pointer.secondary_down());
|
||||
let is_middle_mouse_down = ctx.input(|i| i.pointer.middle_down());
|
||||
let hover_pos = ctx.input(|i| i.pointer.hover_pos());
|
||||
|
||||
// Set cursor visibility based on right mouse button
|
||||
// if is_right_mouse_down != self.right_mouse_pressed {
|
||||
// if is_right_mouse_down {
|
||||
// ctx.send_viewport_cmd(egui::ViewportCommand::CursorVisible(false));
|
||||
// ctx.send_viewport_cmd(egui::ViewportCommand::CursorGrab(
|
||||
// egui::CursorGrab::Confined,
|
||||
// ));
|
||||
// } else {
|
||||
// ctx.send_viewport_cmd(egui::ViewportCommand::CursorVisible(true));
|
||||
// ctx.send_viewport_cmd(egui::ViewportCommand::CursorGrab(egui::CursorGrab::None));
|
||||
// }
|
||||
// }
|
||||
|
||||
self.right_mouse_pressed = is_right_mouse_down;
|
||||
|
||||
match (self.right_mouse_pressed, is_middle_mouse_down) {
|
||||
(true, false) => {
|
||||
if let Some(pos) = hover_pos {
|
||||
if let Some((last_x, last_y)) = self.last_mouse_pos {
|
||||
let delta_x = pos.x - last_x;
|
||||
let delta_y = pos.y - last_y;
|
||||
|
||||
// Update camera rotation
|
||||
let rotation_speed = self.mouse_sensitivity / 100.0;
|
||||
self.camera_yaw += delta_x * rotation_speed;
|
||||
self.camera_pitch = (self.camera_pitch - delta_y * rotation_speed)
|
||||
.clamp(-89.0_f32.to_radians(), 89.0_f32.to_radians());
|
||||
}
|
||||
self.last_mouse_pos = Some((pos.x, pos.y));
|
||||
}
|
||||
}
|
||||
(false, true) => {
|
||||
if let Some(pos) = hover_pos {
|
||||
if let Some((last_x, last_y)) = self.last_mouse_pos {
|
||||
let delta_x = pos.x - last_x;
|
||||
let delta_y = pos.y - last_y;
|
||||
|
||||
let move_speed = 0.005;
|
||||
|
||||
self.camera_position.x -= delta_x * move_speed;
|
||||
self.camera_position.y += delta_y * move_speed;
|
||||
}
|
||||
|
||||
self.last_mouse_pos = Some((pos.x, pos.y))
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.last_mouse_pos = None;
|
||||
}
|
||||
}
|
||||
|
||||
match self.theme {
|
||||
Theme::Dark => ctx.set_visuals(egui::style::Visuals::dark()),
|
||||
Theme::Light => ctx.set_visuals(egui::style::Visuals::light()),
|
||||
}
|
||||
}
|
||||
|
||||
fn request_redraw(&mut self, _viewport_id: egui::ViewportId) -> HandleRedraw {
|
||||
puffin::profile_function!();
|
||||
HandleRedraw::Handle(Box::new({
|
||||
let renderer = self.renderer.clone();
|
||||
let rotate_y = self.rotate_y;
|
||||
let camera_position = self.camera_position;
|
||||
let camera_yaw = self.camera_yaw;
|
||||
let camera_pitch = self.camera_pitch;
|
||||
let camera_fov = self.camera_fov;
|
||||
|
||||
let bg_color = self.bg_color;
|
||||
let model_color = self.model_color;
|
||||
move |size, egui_cmd| {
|
||||
let mut renderer = renderer.inner.lock().unwrap();
|
||||
renderer.update_camera(camera_position, camera_yaw, camera_pitch, camera_fov);
|
||||
renderer.update_colors(bg_color.into(), model_color.into());
|
||||
renderer.render(size.width, size.height, egui_cmd, rotate_y)
|
||||
}
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Game {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
self.device.device_wait_idle().unwrap();
|
||||
self.renderer.destroy();
|
||||
self.device.destroy_command_pool(self.command_pool, None);
|
||||
self.surface_loader.destroy_surface(self.surface, None);
|
||||
ManuallyDrop::drop(&mut self.allocator);
|
||||
self.device.destroy_device(None);
|
||||
if self.debug_messenger != vk::DebugUtilsMessengerEXT::null() {
|
||||
self.debug_utils_loader
|
||||
.destroy_debug_utils_messenger(self.debug_messenger, None);
|
||||
}
|
||||
self.instance.destroy_instance(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MyAppCreator;
|
||||
impl MyAppCreator {
|
||||
#[cfg(debug_assertions)]
|
||||
const ENABLE_VALIDATION_LAYERS: bool = true;
|
||||
#[cfg(not(debug_assertions))]
|
||||
const ENABLE_VALIDATION_LAYERS: bool = false;
|
||||
const VALIDATION: [&'static str; 1] = ["VK_LAYER_KHRONOS_validation"];
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
unsafe extern "system" fn vulkan_debug_utils_callback(
|
||||
message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
|
||||
message_types: vk::DebugUtilsMessageTypeFlagsEXT,
|
||||
p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT,
|
||||
_p_user_data: *mut std::ffi::c_void,
|
||||
) -> vk::Bool32 {
|
||||
let types = match message_types {
|
||||
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL => "[GENERAL]",
|
||||
vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE => "[PERFORMANCE]",
|
||||
vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION => "[VALIDATION]",
|
||||
_ => panic!("[UNKNOWN]"),
|
||||
};
|
||||
|
||||
let message = std::ffi::CStr::from_ptr((*p_callback_data).p_message);
|
||||
match message_severity {
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => tracing::trace!("{types}{message:?}"),
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => tracing::warn!("{types}{message:?}"),
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => tracing::error!("{types}{message:?}"),
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::INFO => tracing::info!("{types}{message:?}"),
|
||||
_ => tracing::debug!("{types}{message:?}"),
|
||||
};
|
||||
|
||||
vk::FALSE
|
||||
}
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
unsafe extern "system" fn vulkan_debug_utils_callback(
|
||||
_message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
|
||||
_message_types: vk::DebugUtilsMessageTypeFlagsEXT,
|
||||
_p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT,
|
||||
_p_user_data: *mut std::ffi::c_void,
|
||||
) -> vk::Bool32 {
|
||||
vk::FALSE
|
||||
}
|
||||
|
||||
fn create_entry() -> Entry {
|
||||
unsafe { Entry::load().unwrap() }
|
||||
}
|
||||
|
||||
fn create_instance(
|
||||
required_instance_extensions: &[CString],
|
||||
entry: &Entry,
|
||||
) -> (Instance, DebugUtils, vk::DebugUtilsMessengerEXT) {
|
||||
let mut debug_utils_messenger_create_info = vk::DebugUtilsMessengerCreateInfoEXT::builder()
|
||||
.flags(vk::DebugUtilsMessengerCreateFlagsEXT::empty())
|
||||
.message_severity(
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::WARNING
|
||||
// | vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE
|
||||
// | vk::DebugUtilsMessageSeverityFlagsEXT::INFO
|
||||
| vk::DebugUtilsMessageSeverityFlagsEXT::ERROR,
|
||||
)
|
||||
.message_type(
|
||||
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL
|
||||
| vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE
|
||||
| vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION,
|
||||
)
|
||||
.pfn_user_callback(Some(Self::vulkan_debug_utils_callback))
|
||||
.build();
|
||||
|
||||
let app_name = std::ffi::CString::new("egui-winit-ash example simple").unwrap();
|
||||
let app_info = vk::ApplicationInfo::builder()
|
||||
.application_name(&app_name)
|
||||
.application_version(vk::make_api_version(1, 0, 0, 0))
|
||||
.api_version(vk::API_VERSION_1_2);
|
||||
let mut extension_names = vec![DebugUtils::name().as_ptr()];
|
||||
for ext in required_instance_extensions {
|
||||
let name = ext.as_ptr();
|
||||
extension_names.push(name);
|
||||
}
|
||||
let raw_layer_names = Self::VALIDATION
|
||||
.iter()
|
||||
.map(|l| std::ffi::CString::new(*l).unwrap())
|
||||
.collect::<Vec<_>>();
|
||||
let layer_names = raw_layer_names
|
||||
.iter()
|
||||
.map(|l| l.as_ptr())
|
||||
.collect::<Vec<*const i8>>();
|
||||
let instance_create_info = vk::InstanceCreateInfo::builder()
|
||||
.push_next(&mut debug_utils_messenger_create_info)
|
||||
.application_info(&app_info)
|
||||
.enabled_extension_names(&extension_names);
|
||||
let instance_create_info = if Self::ENABLE_VALIDATION_LAYERS {
|
||||
instance_create_info.enabled_layer_names(&layer_names)
|
||||
} else {
|
||||
instance_create_info
|
||||
};
|
||||
let instance = unsafe {
|
||||
entry
|
||||
.create_instance(&instance_create_info, None)
|
||||
.expect("Failed to create instance")
|
||||
};
|
||||
|
||||
// setup debug utils
|
||||
let debug_utils_loader = DebugUtils::new(entry, &instance);
|
||||
let debug_messenger = if Self::ENABLE_VALIDATION_LAYERS {
|
||||
unsafe {
|
||||
debug_utils_loader
|
||||
.create_debug_utils_messenger(&debug_utils_messenger_create_info, None)
|
||||
.expect("Failed to create debug utils messenger")
|
||||
}
|
||||
} else {
|
||||
vk::DebugUtilsMessengerEXT::null()
|
||||
};
|
||||
|
||||
(instance, debug_utils_loader, debug_messenger)
|
||||
}
|
||||
|
||||
fn create_surface_loader(entry: &Entry, instance: &Instance) -> Surface {
|
||||
Surface::new(entry, instance)
|
||||
}
|
||||
|
||||
fn create_swapchain_loader(instance: &Instance, device: &Device) -> Swapchain {
|
||||
Swapchain::new(instance, device)
|
||||
}
|
||||
|
||||
fn create_surface(
|
||||
entry: &Entry,
|
||||
instance: &Instance,
|
||||
window: &winit::window::Window,
|
||||
) -> vk::SurfaceKHR {
|
||||
unsafe {
|
||||
ash_window::create_surface(
|
||||
entry,
|
||||
instance,
|
||||
window.raw_display_handle(),
|
||||
window.raw_window_handle(),
|
||||
None,
|
||||
)
|
||||
.expect("Failed to create surface")
|
||||
}
|
||||
}
|
||||
|
||||
fn create_physical_device(
|
||||
instance: &Instance,
|
||||
surface_loader: &Surface,
|
||||
surface: vk::SurfaceKHR,
|
||||
required_device_extensions: &[CString],
|
||||
) -> (
|
||||
vk::PhysicalDevice,
|
||||
vk::PhysicalDeviceProperties,
|
||||
vk::PhysicalDeviceMemoryProperties,
|
||||
u32,
|
||||
) {
|
||||
let mut queue_family_index: Option<usize> = None;
|
||||
|
||||
let (physical_device, phsyical_device_properties, physical_device_memory_properties) = {
|
||||
let physical_devices = unsafe {
|
||||
instance
|
||||
.enumerate_physical_devices()
|
||||
.expect("Failed to enumerate physical devices")
|
||||
};
|
||||
let physical_device = physical_devices.into_iter().find(|physical_device| {
|
||||
let queue_families = unsafe {
|
||||
instance.get_physical_device_queue_family_properties(*physical_device)
|
||||
};
|
||||
for (i, queue_family) in queue_families.iter().enumerate() {
|
||||
let mut graphics_queue = false;
|
||||
let mut present_queue = false;
|
||||
if queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS) {
|
||||
graphics_queue = true;
|
||||
}
|
||||
let present_support = unsafe {
|
||||
surface_loader
|
||||
.get_physical_device_surface_support(
|
||||
*physical_device,
|
||||
i as u32,
|
||||
surface,
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
if present_support {
|
||||
present_queue = true;
|
||||
}
|
||||
if graphics_queue && present_queue {
|
||||
queue_family_index = Some(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
let is_queue_family_supported = queue_family_index.is_some();
|
||||
|
||||
// check device extensions
|
||||
let device_extensions = unsafe {
|
||||
instance
|
||||
.enumerate_device_extension_properties(*physical_device)
|
||||
.unwrap()
|
||||
};
|
||||
let mut device_extensions_name = vec![];
|
||||
for device_extension in device_extensions {
|
||||
let name = unsafe {
|
||||
std::ffi::CStr::from_ptr(device_extension.extension_name.as_ptr())
|
||||
.to_owned()
|
||||
};
|
||||
device_extensions_name.push(name);
|
||||
}
|
||||
let mut required_extensions = HashSet::new();
|
||||
for extension in required_device_extensions.iter() {
|
||||
required_extensions.insert(extension.to_owned());
|
||||
}
|
||||
for extension_name in device_extensions_name {
|
||||
required_extensions.remove(&extension_name);
|
||||
}
|
||||
let is_device_extension_supported = required_extensions.is_empty();
|
||||
|
||||
// check swapchain support
|
||||
let surface_formats = unsafe {
|
||||
surface_loader
|
||||
.get_physical_device_surface_formats(*physical_device, surface)
|
||||
.unwrap()
|
||||
};
|
||||
let surface_present_modes = unsafe {
|
||||
surface_loader
|
||||
.get_physical_device_surface_present_modes(*physical_device, surface)
|
||||
.unwrap()
|
||||
};
|
||||
let is_swapchain_supported =
|
||||
!surface_formats.is_empty() && !surface_present_modes.is_empty();
|
||||
|
||||
is_queue_family_supported && is_swapchain_supported && is_device_extension_supported
|
||||
});
|
||||
let physical_device = physical_device.expect("Failed to get physical device");
|
||||
let physical_device_memory_properties =
|
||||
unsafe { instance.get_physical_device_memory_properties(physical_device) };
|
||||
|
||||
let physical_device_properties =
|
||||
unsafe { instance.get_physical_device_properties(physical_device) };
|
||||
|
||||
(
|
||||
physical_device,
|
||||
physical_device_properties,
|
||||
physical_device_memory_properties,
|
||||
)
|
||||
};
|
||||
|
||||
(
|
||||
physical_device,
|
||||
phsyical_device_properties,
|
||||
physical_device_memory_properties,
|
||||
queue_family_index.unwrap() as u32,
|
||||
)
|
||||
}
|
||||
|
||||
fn create_device(
|
||||
instance: &Instance,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
queue_family_index: u32,
|
||||
required_device_extensions: &[CString],
|
||||
) -> (Device, vk::Queue) {
|
||||
let queue_priorities = [1.0_f32];
|
||||
let mut queue_create_infos = vec![];
|
||||
let queue_create_info = vk::DeviceQueueCreateInfo::builder()
|
||||
.queue_family_index(queue_family_index)
|
||||
.queue_priorities(&queue_priorities)
|
||||
.build();
|
||||
queue_create_infos.push(queue_create_info);
|
||||
|
||||
let physical_device_features = vk::PhysicalDeviceFeatures::builder()
|
||||
.sampler_anisotropy(true)
|
||||
.build();
|
||||
|
||||
let enable_extension_names = required_device_extensions
|
||||
.iter()
|
||||
.map(|s| s.as_ptr())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// device create info
|
||||
let device_create_info = vk::DeviceCreateInfo::builder()
|
||||
.queue_create_infos(&queue_create_infos)
|
||||
.enabled_features(&physical_device_features)
|
||||
.enabled_extension_names(&enable_extension_names);
|
||||
|
||||
// create device
|
||||
let device = unsafe {
|
||||
instance
|
||||
.create_device(physical_device, &device_create_info, None)
|
||||
.expect("Failed to create device")
|
||||
};
|
||||
|
||||
// get device queue
|
||||
let queue = unsafe { device.get_device_queue(queue_family_index, 0) };
|
||||
|
||||
(device, queue)
|
||||
}
|
||||
|
||||
fn create_command_pool(device: &Device, queue_family_index: u32) -> vk::CommandPool {
|
||||
let command_pool_create_info = vk::CommandPoolCreateInfo::builder()
|
||||
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER)
|
||||
.queue_family_index(queue_family_index);
|
||||
unsafe {
|
||||
device
|
||||
.create_command_pool(&command_pool_create_info, None)
|
||||
.expect("Failed to create command pool")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AppCreator<Arc<Mutex<Allocator>>> for MyAppCreator {
|
||||
type App = Game;
|
||||
|
||||
fn create(&self, cc: CreationContext) -> (Self::App, AshRenderState<Arc<Mutex<Allocator>>>) {
|
||||
// create vk objects
|
||||
let entry = Self::create_entry();
|
||||
let (instance, debug_utils_loader, debug_messenger) =
|
||||
Self::create_instance(&cc.required_instance_extensions, &entry);
|
||||
let surface_loader = Self::create_surface_loader(&entry, &instance);
|
||||
let surface = Self::create_surface(&entry, &instance, cc.main_window);
|
||||
let mut req_ext = vec![
|
||||
KhrDeferredHostOperationsFn::name().to_owned(),
|
||||
KhrRayTracingPipelineFn::name().to_owned(),
|
||||
KhrAccelerationStructureFn::name().to_owned(),
|
||||
];
|
||||
for ext in &cc.required_device_extensions {
|
||||
req_ext.push(ext.to_owned());
|
||||
}
|
||||
let (
|
||||
physical_device,
|
||||
_physical_device_properties,
|
||||
_physical_device_memory_properties,
|
||||
queue_family_index,
|
||||
) = Self::create_physical_device(&instance, &surface_loader, surface, &req_ext);
|
||||
let (device, queue) = Self::create_device(
|
||||
&instance,
|
||||
physical_device,
|
||||
queue_family_index,
|
||||
&cc.required_device_extensions,
|
||||
);
|
||||
let swapchain_loader = Self::create_swapchain_loader(&instance, &device);
|
||||
let command_pool = Self::create_command_pool(&device, queue_family_index);
|
||||
|
||||
// create allocator
|
||||
let allocator = {
|
||||
Allocator::new(&AllocatorCreateDesc {
|
||||
instance: instance.clone(),
|
||||
device: device.clone(),
|
||||
physical_device,
|
||||
debug_settings: Default::default(),
|
||||
buffer_device_address: false,
|
||||
allocation_sizes: Default::default(),
|
||||
})
|
||||
.expect("Failed to create allocator")
|
||||
};
|
||||
let allocator = Arc::new(Mutex::new(allocator));
|
||||
|
||||
// setup context
|
||||
cc.context.set_visuals(egui::style::Visuals::dark());
|
||||
|
||||
let app = Game {
|
||||
entry,
|
||||
instance,
|
||||
device: device.clone(),
|
||||
debug_utils_loader,
|
||||
debug_messenger,
|
||||
physical_device,
|
||||
surface_loader: surface_loader.clone(),
|
||||
swapchain_loader: swapchain_loader.clone(),
|
||||
surface,
|
||||
queue,
|
||||
command_pool,
|
||||
allocator: ManuallyDrop::new(allocator.clone()),
|
||||
|
||||
show_profiler: false,
|
||||
|
||||
renderer: Renderer::new(
|
||||
physical_device,
|
||||
device,
|
||||
surface_loader,
|
||||
swapchain_loader,
|
||||
allocator.clone(),
|
||||
surface,
|
||||
queue_family_index,
|
||||
queue,
|
||||
command_pool,
|
||||
1000,
|
||||
800,
|
||||
),
|
||||
|
||||
theme: if cc.context.style().visuals.dark_mode {
|
||||
Theme::Dark
|
||||
} else {
|
||||
Theme::Light
|
||||
},
|
||||
rotate_y: 0.0,
|
||||
|
||||
mouse_sensitivity: 0.4,
|
||||
|
||||
camera_position: Vec3::new(0.0, 0.0, -5.0),
|
||||
camera_pitch: 0.,
|
||||
camera_yaw: 0.,
|
||||
camera_fov: 90.,
|
||||
bg_color: Vec3::splat(0.1).into(),
|
||||
model_color: Vec3::splat(0.8).into(),
|
||||
last_mouse_pos: None,
|
||||
right_mouse_pressed: false,
|
||||
last_fps_update: std::time::Instant::now(),
|
||||
frame_count_since_last_update: 0,
|
||||
current_fps: 0.0,
|
||||
};
|
||||
let ash_render_state = AshRenderState {
|
||||
entry: app.entry.clone(),
|
||||
instance: app.instance.clone(),
|
||||
physical_device: app.physical_device,
|
||||
device: app.device.clone(),
|
||||
surface_loader: app.surface_loader.clone(),
|
||||
swapchain_loader: app.swapchain_loader.clone(),
|
||||
queue: app.queue,
|
||||
queue_family_index,
|
||||
command_pool: app.command_pool,
|
||||
allocator: allocator.clone(),
|
||||
};
|
||||
|
||||
(app, ash_render_state)
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> std::process::ExitCode {
|
||||
tracing_subscriber::fmt().pretty().init();
|
||||
puffin::set_scopes_on(true);
|
||||
|
||||
egui_ash::run(
|
||||
"vulkan",
|
||||
MyAppCreator,
|
||||
RunOption {
|
||||
viewport_builder: Some(
|
||||
egui::ViewportBuilder::default()
|
||||
.with_title("vulkan")
|
||||
.with_inner_size(egui::vec2(1000.0, 800.0)),
|
||||
),
|
||||
follow_system_theme: false,
|
||||
default_theme: Theme::Dark,
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -1,46 +0,0 @@
|
|||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{Arc, Mutex},
|
||||
};
|
||||
|
||||
use ash::Device;
|
||||
use gpu_allocator::vulkan::Allocator;
|
||||
|
||||
use super::renderer::Texture;
|
||||
|
||||
pub struct TextureCache {
|
||||
cache: HashMap<String, Arc<Texture>>,
|
||||
}
|
||||
|
||||
impl TextureCache {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
cache: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_or_load_texture(
|
||||
&mut self,
|
||||
key: String,
|
||||
load_fn: impl FnOnce() -> Option<Texture>,
|
||||
) -> Option<Arc<Texture>> {
|
||||
if let Some(texture) = self.cache.get(&key) {
|
||||
Some(Arc::clone(texture))
|
||||
} else {
|
||||
load_fn().map(|texture| {
|
||||
let texture = Arc::new(texture);
|
||||
self.cache.insert(key, Arc::clone(&texture));
|
||||
texture
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cleanup(&mut self, device: &Device, allocator: &mut Allocator) {
|
||||
for (_, texture) in self.cache.drain() {
|
||||
if let Ok(texture) = Arc::try_unwrap(texture) {
|
||||
let mut texture = texture;
|
||||
texture.destroy(device, allocator);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue