new architecture
This commit is contained in:
parent
9f7e72b784
commit
9cfd9d8b17
28 changed files with 2625 additions and 5351 deletions
2345
Cargo.lock
generated
2345
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
18
Cargo.toml
18
Cargo.toml
|
|
@ -1,22 +1,24 @@
|
||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
|
|
||||||
members = ["crates/shaders-shared", "crates/vk-rs"]
|
members = ["crates/engine", "crates/gfx_hal", "crates/resource_manager"]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
ash = { version = "0.37.3" }
|
ash = { version = "0.38" }
|
||||||
|
ash-window = "0.13.0"
|
||||||
color-eyre = "0.6.3"
|
color-eyre = "0.6.3"
|
||||||
winit = { version = "0.30.7", features = ["rwh_06"] }
|
winit = { version = "0.30.7", features = ["rwh_06"] }
|
||||||
raw-window-handle = "0.6"
|
raw-window-handle = "0.6"
|
||||||
gpu-allocator = { version = "0.25.0", features = ["vulkan"] }
|
gpu-allocator = { version = "0.27.0", features = ["vulkan"] }
|
||||||
glam = { version = "0.22", default-features = false, features = ["libm"] }
|
glam = { version = "0.22", default-features = false, features = [
|
||||||
bytemuck = "1.21.0"
|
"libm",
|
||||||
|
"bytemuck",
|
||||||
|
] }
|
||||||
|
bytemuck = { version = "1.21.0", features = ["derive"] }
|
||||||
tracing = "0.1"
|
tracing = "0.1"
|
||||||
tracing-subscriber = "0.3"
|
tracing-subscriber = "0.3"
|
||||||
parking_lot = "0.12.3"
|
parking_lot = "0.12.3"
|
||||||
|
thiserror = "2.0.12"
|
||||||
spirv-builder = { git = "https://github.com/Rust-GPU/rust-gpu.git" }
|
|
||||||
spirv-std = { git = "https://github.com/Rust-GPU/rust-gpu.git" }
|
|
||||||
|
|
||||||
|
|
||||||
# # Enable incremental by default in release mode.
|
# # Enable incremental by default in release mode.
|
||||||
|
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
13
flake.nix
13
flake.nix
|
|
@ -18,17 +18,7 @@
|
||||||
nixpkgs,
|
nixpkgs,
|
||||||
flake-parts,
|
flake-parts,
|
||||||
...
|
...
|
||||||
}: let
|
}:
|
||||||
# For details on these options, See
|
|
||||||
# https://github.com/oxalica/rust-overlay?tab=readme-ov-file#cheat-sheet-common-usage-of-rust-bin
|
|
||||||
#
|
|
||||||
# Channel of the Rust toolchain (stable or beta).
|
|
||||||
rustChannel = "nightly";
|
|
||||||
# Version (latest or specific date/semantic version)
|
|
||||||
rustVersion = "latest";
|
|
||||||
# Profile (default or minimal)
|
|
||||||
rustProfile = "default";
|
|
||||||
in
|
|
||||||
flake-parts.lib.mkFlake {inherit inputs;} {
|
flake-parts.lib.mkFlake {inherit inputs;} {
|
||||||
systems = import inputs.systems;
|
systems = import inputs.systems;
|
||||||
|
|
||||||
|
|
@ -77,6 +67,7 @@
|
||||||
# WINIT_UNIX_BACKEND=wayland
|
# WINIT_UNIX_BACKEND=wayland
|
||||||
wayland
|
wayland
|
||||||
spirv-tools
|
spirv-tools
|
||||||
|
spirv-cross
|
||||||
vulkan-loader
|
vulkan-loader
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,134 +0,0 @@
|
||||||
#version 450
|
|
||||||
|
|
||||||
layout(location = 0) in vec3 frag_world_position;
|
|
||||||
layout(location = 1) in vec3 frag_world_normal;
|
|
||||||
layout(location = 2) in vec2 frag_tex_coord;
|
|
||||||
|
|
||||||
struct Material {
|
|
||||||
vec4 base_color;
|
|
||||||
float metallic_factor;
|
|
||||||
float roughness_factor;
|
|
||||||
vec2 _padding;
|
|
||||||
};
|
|
||||||
|
|
||||||
layout(set = 0, binding = 0) uniform UniformBufferObject {
|
|
||||||
mat4 model;
|
|
||||||
mat4 view;
|
|
||||||
mat4 proj;
|
|
||||||
vec3 camera_pos;
|
|
||||||
float _padding;
|
|
||||||
Material material;
|
|
||||||
} ubo;
|
|
||||||
|
|
||||||
layout(set = 0, binding = 1) uniform sampler2D albedo_map;
|
|
||||||
layout(set = 0, binding = 2) uniform sampler2D metallic_roughness_map;
|
|
||||||
layout(set = 0, binding = 3) uniform sampler2D normal_map;
|
|
||||||
|
|
||||||
layout(location = 0) out vec4 out_color;
|
|
||||||
|
|
||||||
const float PI = 3.14159265359;
|
|
||||||
|
|
||||||
// PBR functions
|
|
||||||
vec3 fresnelSchlick(float cosTheta, vec3 F0) {
|
|
||||||
return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
float DistributionGGX(vec3 N, vec3 H, float roughness) {
|
|
||||||
float a = roughness*roughness;
|
|
||||||
float a2 = a*a;
|
|
||||||
float NdotH = max(dot(N, H), 0.0);
|
|
||||||
float NdotH2 = NdotH*NdotH;
|
|
||||||
|
|
||||||
float nom = a2;
|
|
||||||
float denom = (NdotH2 * (a2 - 1.0) + 1.0);
|
|
||||||
denom = PI * denom * denom;
|
|
||||||
|
|
||||||
return nom / max(denom, 0.0000001);
|
|
||||||
}
|
|
||||||
|
|
||||||
float GeometrySchlickGGX(float NdotV, float roughness) {
|
|
||||||
float r = (roughness + 1.0);
|
|
||||||
float k = (r*r) / 8.0;
|
|
||||||
|
|
||||||
float nom = NdotV;
|
|
||||||
float denom = NdotV * (1.0 - k) + k;
|
|
||||||
|
|
||||||
return nom / denom;
|
|
||||||
}
|
|
||||||
|
|
||||||
float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness) {
|
|
||||||
float NdotV = max(dot(N, V), 0.0);
|
|
||||||
float NdotL = max(dot(N, L), 0.0);
|
|
||||||
float ggx2 = GeometrySchlickGGX(NdotV, roughness);
|
|
||||||
float ggx1 = GeometrySchlickGGX(NdotL, roughness);
|
|
||||||
|
|
||||||
return ggx1 * ggx2;
|
|
||||||
}
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// Sample textures
|
|
||||||
vec4 albedo = texture(albedo_map, frag_tex_coord);
|
|
||||||
vec2 metallic_roughness = texture(metallic_roughness_map, frag_tex_coord).bg;
|
|
||||||
vec3 normal = normalize(2.0 * texture(normal_map, frag_tex_coord).rgb - 1.0);
|
|
||||||
|
|
||||||
float metallic = metallic_roughness.x * ubo.material.metallic_factor;
|
|
||||||
float roughness = metallic_roughness.y * ubo.material.roughness_factor;
|
|
||||||
|
|
||||||
vec3 N = normalize(normal);
|
|
||||||
vec3 V = normalize(ubo.camera_pos - frag_world_position);
|
|
||||||
|
|
||||||
// Calculate reflectance at normal incidence
|
|
||||||
vec3 F0 = vec3(0.04);
|
|
||||||
F0 = mix(F0, albedo.rgb, metallic);
|
|
||||||
|
|
||||||
// Light parameters
|
|
||||||
vec3 light_positions[4] = vec3[](
|
|
||||||
vec3(5.0, 5.0, 5.0),
|
|
||||||
vec3(-5.0, 5.0, 5.0),
|
|
||||||
vec3(5.0, -5.0, 5.0),
|
|
||||||
vec3(-5.0, -5.0, 5.0)
|
|
||||||
);
|
|
||||||
vec3 light_colors[4] = vec3[](
|
|
||||||
vec3(23.47, 21.31, 20.79),
|
|
||||||
vec3(23.47, 21.31, 20.79),
|
|
||||||
vec3(23.47, 21.31, 20.79),
|
|
||||||
vec3(23.47, 21.31, 20.79)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Reflectance equation
|
|
||||||
vec3 Lo = vec3(0.0);
|
|
||||||
for(int i = 0; i < 4; ++i) {
|
|
||||||
vec3 L = normalize(light_positions[i] - frag_world_position);
|
|
||||||
vec3 H = normalize(V + L);
|
|
||||||
float distance = length(light_positions[i] - frag_world_position);
|
|
||||||
float attenuation = 1.0 / (distance * distance);
|
|
||||||
vec3 radiance = light_colors[i] * attenuation;
|
|
||||||
|
|
||||||
// Cook-Torrance BRDF
|
|
||||||
float NDF = DistributionGGX(N, H, roughness);
|
|
||||||
float G = GeometrySmith(N, V, L, roughness);
|
|
||||||
vec3 F = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0);
|
|
||||||
|
|
||||||
vec3 numerator = NDF * G * F;
|
|
||||||
float denominator = 4.0 * max(dot(N, V), 0.0) * max(dot(N, L), 0.0);
|
|
||||||
vec3 specular = numerator / max(denominator, 0.001);
|
|
||||||
|
|
||||||
vec3 kS = F;
|
|
||||||
vec3 kD = vec3(1.0) - kS;
|
|
||||||
kD *= 1.0 - metallic;
|
|
||||||
|
|
||||||
float NdotL = max(dot(N, L), 0.0);
|
|
||||||
|
|
||||||
Lo += (kD * albedo.rgb / PI + specular) * radiance * NdotL;
|
|
||||||
}
|
|
||||||
|
|
||||||
vec3 ambient = vec3(0.03) * albedo.rgb;
|
|
||||||
vec3 color = ambient + Lo;
|
|
||||||
|
|
||||||
// HDR tonemapping
|
|
||||||
color = color / (color + vec3(1.0));
|
|
||||||
// gamma correction
|
|
||||||
color = pow(color, vec3(1.0/2.2));
|
|
||||||
|
|
||||||
out_color = vec4(color, albedo.a);
|
|
||||||
}
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
#version 450
|
|
||||||
|
|
||||||
// Vertex inputs
|
|
||||||
layout(location = 0) in vec3 in_pos;
|
|
||||||
layout(location = 1) in vec3 in_normal;
|
|
||||||
layout(location = 2) in vec2 in_tex_coord;
|
|
||||||
|
|
||||||
// Uniform buffer
|
|
||||||
layout(set = 0, binding = 0) uniform UniformBufferObject {
|
|
||||||
mat4 model;
|
|
||||||
mat4 view;
|
|
||||||
mat4 proj;
|
|
||||||
} ubo;
|
|
||||||
|
|
||||||
// Vertex outputs
|
|
||||||
layout(location = 0) out vec3 out_world_position;
|
|
||||||
layout(location = 1) out vec3 out_world_normal;
|
|
||||||
layout(location = 2) out vec2 out_tex_coord;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
// Transform position to world space
|
|
||||||
vec4 pos = ubo.model * vec4(in_pos, 1.0);
|
|
||||||
out_world_position = (pos / pos.w).xyz;
|
|
||||||
|
|
||||||
// Transform normal to world space
|
|
||||||
mat3 normal_matrix = transpose(inverse(mat3(ubo.model)));
|
|
||||||
out_world_normal = normal_matrix * in_normal;
|
|
||||||
|
|
||||||
// Calculate clip space position
|
|
||||||
gl_Position = ubo.proj * ubo.view * pos;
|
|
||||||
out_tex_coord = in_tex_coord;
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue