Add tri
This commit is contained in:
parent
9cfd9d8b17
commit
8a1c5237d5
19 changed files with 1952 additions and 175 deletions
|
|
@ -4,15 +4,14 @@ version = "0.1.0"
|
|||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
egui.workspace = true
|
||||
ash.workspace = true
|
||||
winit.workspace = true
|
||||
raw-window-handle.workspace = true
|
||||
tracing.workspace = true
|
||||
tracing-subscriber.workspace = true
|
||||
glam.workspace = true
|
||||
bytemuck.workspace = true
|
||||
winit.workspace = true
|
||||
raw-window-handle.workspace = true
|
||||
thiserror.workspace = true
|
||||
gpu-allocator.workspace = true
|
||||
|
||||
gfx_hal = { path = "../gfx_hal" }
|
||||
renderer = { path = "../renderer" }
|
||||
resource_manager = { path = "../resource_manager" }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,462 @@
|
|||
mod scene_data;
|
||||
use std::{
|
||||
error::Error,
|
||||
ffi::{CStr, CString},
|
||||
fs::OpenOptions,
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
use ash::vk;
|
||||
use gfx_hal::{
|
||||
device::Device, error::GfxHalError, instance::Instance, instance::InstanceConfig,
|
||||
physical_device::PhysicalDevice, queue::Queue, surface::Surface,
|
||||
};
|
||||
use raw_window_handle::HasDisplayHandle;
|
||||
use renderer::{Renderer, RendererError};
|
||||
use resource_manager::{ResourceManager, ResourceManagerError};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt, Layer};
|
||||
use winit::{
|
||||
event::{Event, WindowEvent},
|
||||
event_loop::{ActiveEventLoop, EventLoop},
|
||||
window::{Window, WindowAttributes},
|
||||
};
|
||||
|
||||
// --- Configuration ---
|
||||
const WINDOW_TITLE: &str = "Rust Vulkan Egui Engine";
|
||||
const INITIAL_WIDTH: u32 = 1280;
|
||||
const INITIAL_HEIGHT: u32 = 720;
|
||||
const APP_NAME: &str = "My App";
|
||||
const ENGINE_NAME: &str = "My Engine";
|
||||
|
||||
// --- Error Handling ---
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum AppError {
|
||||
#[error("Window Creation Error: {0}")]
|
||||
WindowCreation(#[from] winit::error::OsError),
|
||||
#[error("Graphics HAL Error: {0}")]
|
||||
GfxHal(#[from] GfxHalError),
|
||||
#[error("Resource Manager Error: {0}")]
|
||||
ResourceManager(#[from] ResourceManagerError),
|
||||
#[error("Renderer Error: {0}")]
|
||||
Renderer(#[from] RendererError),
|
||||
#[error("Suitable physical device not found")]
|
||||
NoSuitableDevice,
|
||||
#[error("Required queue family not found")]
|
||||
NoSuitableQueueFamily,
|
||||
#[error("Failed to create CString: {0}")]
|
||||
NulError(#[from] std::ffi::NulError),
|
||||
#[error("Missing required Vulkan extension: {0}")]
|
||||
MissingExtension(String),
|
||||
}
|
||||
|
||||
// --- Main Application Structure ---
|
||||
struct Application {
|
||||
// Core Vulkan Objects (managed by gfx_hal)
|
||||
_instance: Arc<Instance>, // Keep instance alive
|
||||
_physical_device: PhysicalDevice, // Keep info, though Device holds handle
|
||||
device: Arc<Device>,
|
||||
graphics_queue: Arc<Queue>,
|
||||
surface: Arc<Surface>,
|
||||
|
||||
// Resource Management
|
||||
resource_manager: Arc<ResourceManager>,
|
||||
|
||||
// Renderer
|
||||
renderer: Renderer,
|
||||
|
||||
// Windowing
|
||||
window: Arc<Window>, // Use Arc for potential multi-threading later
|
||||
|
||||
// State
|
||||
last_frame_time: Instant,
|
||||
ui_show_demo: bool,
|
||||
}
|
||||
|
||||
impl Application {
|
||||
fn new(window: Arc<Window>) -> Result<Self, AppError> {
|
||||
info!("Initializing Application...");
|
||||
|
||||
// --- 1. gfx_hal Setup ---
|
||||
let instance_extensions = [
|
||||
// Add extensions required by the platform (e.g., from winit)
|
||||
// ash::extensions::ext::DebugUtils::name(), // If using validation
|
||||
ash::khr::surface::NAME,
|
||||
// Platform specific (example for Xlib/Wayland)
|
||||
#[cfg(target_os = "linux")]
|
||||
ash::khr::xlib_surface::NAME,
|
||||
#[cfg(target_os = "linux")]
|
||||
ash::khr::wayland_surface::NAME,
|
||||
// Add other platform extensions as needed (Win32, Metal, etc.)
|
||||
];
|
||||
let instance_extensions_c: Vec<CString> = instance_extensions
|
||||
.iter()
|
||||
.map(|&s| CString::new(s.to_bytes()).unwrap())
|
||||
.collect();
|
||||
|
||||
let instance_config = InstanceConfig {
|
||||
application_name: APP_NAME.to_string(),
|
||||
engine_name: ENGINE_NAME.to_string(),
|
||||
enable_validation: cfg!(debug_assertions), // Enable validation in debug
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let instance = Instance::new(
|
||||
&instance_config,
|
||||
&window.display_handle().unwrap(),
|
||||
&instance_extensions_c, // Pass external extensions
|
||||
)?;
|
||||
info!("Vulkan Instance created.");
|
||||
|
||||
let surface = unsafe {
|
||||
// Need unsafe for create_surface
|
||||
instance.create_surface(window.as_ref())? // Pass window ref
|
||||
};
|
||||
info!("Vulkan Surface created.");
|
||||
|
||||
// --- 2. Physical Device Selection ---
|
||||
let required_device_extensions =
|
||||
[ash::khr::swapchain::NAME, ash::khr::dynamic_rendering::NAME];
|
||||
let required_device_extensions_cstr: Vec<&CStr> = required_device_extensions
|
||||
.iter()
|
||||
.map(|s| CStr::from_bytes_with_nul(s.to_bytes_with_nul()).unwrap())
|
||||
.collect();
|
||||
|
||||
// Define required features (Dynamic Rendering is crucial)
|
||||
let required_dynamic_rendering_features =
|
||||
vk::PhysicalDeviceDynamicRenderingFeaturesKHR::default().dynamic_rendering(true);
|
||||
// Chain other required features if necessary (e.g., mesh shader)
|
||||
// let mut required_mesh_shader_features = vk::PhysicalDeviceMeshShaderFeaturesEXT::builder()...
|
||||
// required_dynamic_rendering_features = required_dynamic_rendering_features.push_next(&mut required_mesh_shader_features);
|
||||
|
||||
let physical_devices = unsafe { instance.enumerate_physical_devices()? };
|
||||
let (physical_device, queue_family_indices) = physical_devices
|
||||
.into_iter()
|
||||
.find_map(|pd| {
|
||||
match find_suitable_device_and_queues(
|
||||
&pd,
|
||||
&surface,
|
||||
&required_device_extensions_cstr,
|
||||
&required_dynamic_rendering_features,
|
||||
) {
|
||||
Ok(indices) => Some((pd, indices)),
|
||||
Err(e) => {
|
||||
warn!(
|
||||
"Skipping physical device {:?}: {}",
|
||||
unsafe {
|
||||
instance
|
||||
.ash_instance()
|
||||
.get_physical_device_properties(pd.handle())
|
||||
.device_name_as_c_str()
|
||||
},
|
||||
e
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
})
|
||||
.ok_or(AppError::NoSuitableDevice)?;
|
||||
|
||||
let pd_props = unsafe {
|
||||
instance
|
||||
.ash_instance()
|
||||
.get_physical_device_properties(physical_device.handle())
|
||||
};
|
||||
info!(
|
||||
"Selected Physical Device: {}",
|
||||
pd_props.device_name_as_c_str().unwrap().to_string_lossy()
|
||||
);
|
||||
debug!("Using Queue Families: {:?}", queue_family_indices);
|
||||
|
||||
// --- 3. Logical Device and Queues ---
|
||||
// Enable required features
|
||||
let enabled_features = vk::PhysicalDeviceFeatures::default(); // Add base features if needed
|
||||
|
||||
let enabled_buffer_device_address =
|
||||
vk::PhysicalDeviceBufferDeviceAddressFeatures::default().buffer_device_address(true);
|
||||
|
||||
let enabled_dynamic_rendering = required_dynamic_rendering_features; // Copy the builder state
|
||||
|
||||
let device = unsafe {
|
||||
// Need unsafe for create_logical_device
|
||||
physical_device.create_logical_device(
|
||||
&required_device_extensions_cstr,
|
||||
&queue_family_indices,
|
||||
&enabled_features,
|
||||
None,
|
||||
&enabled_dynamic_rendering, // Pass features to enable
|
||||
&enabled_buffer_device_address,
|
||||
)?
|
||||
};
|
||||
let device_handle_at_creation = device.raw().handle();
|
||||
info!(
|
||||
"App: Created Device handle: {:?}",
|
||||
device_handle_at_creation
|
||||
);
|
||||
|
||||
// Get specific queues (assuming graphics and present are the same for simplicity)
|
||||
let graphics_queue = device.get_graphics_queue();
|
||||
let queue_associated_device_handle = graphics_queue.device().raw().handle();
|
||||
info!(
|
||||
"App: Queue is associated with Device handle: {:?}",
|
||||
queue_associated_device_handle
|
||||
);
|
||||
assert_eq!(
|
||||
device_handle_at_creation, queue_associated_device_handle,
|
||||
"Device handle mismatch immediately after queue creation!"
|
||||
);
|
||||
|
||||
// --- 4. Resource Manager ---
|
||||
let resource_manager = Arc::new(ResourceManager::new(instance.clone(), device.clone())?);
|
||||
info!("Resource Manager initialized.");
|
||||
|
||||
let renderer_device_handle_to_pass = device.raw().handle();
|
||||
let renderer_queue_device_handle_to_pass = graphics_queue.device().raw().handle();
|
||||
info!(
|
||||
"App: Passing Device handle to Renderer: {:?}",
|
||||
renderer_device_handle_to_pass
|
||||
);
|
||||
info!(
|
||||
"App: Passing Queue associated with Device handle: {:?}",
|
||||
renderer_queue_device_handle_to_pass
|
||||
);
|
||||
|
||||
// --- 5. Renderer ---
|
||||
let initial_size = window.inner_size();
|
||||
let renderer = Renderer::new(
|
||||
instance.clone(), // Pass instance for allocator creation
|
||||
device.clone(),
|
||||
graphics_queue.clone(),
|
||||
surface.clone(),
|
||||
resource_manager.clone(),
|
||||
initial_size.width,
|
||||
initial_size.height,
|
||||
)?;
|
||||
info!("Renderer initialized.");
|
||||
|
||||
Ok(Self {
|
||||
_instance: instance,
|
||||
_physical_device: physical_device,
|
||||
device,
|
||||
graphics_queue,
|
||||
surface,
|
||||
resource_manager,
|
||||
renderer,
|
||||
window,
|
||||
last_frame_time: Instant::now(),
|
||||
ui_show_demo: true,
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_event(&mut self, event: &Event<()>, active_event_loop: &ActiveEventLoop) {
|
||||
match event {
|
||||
Event::WindowEvent { event, window_id } if *window_id == self.window.id() => {
|
||||
match event {
|
||||
WindowEvent::CloseRequested => {
|
||||
info!("Close requested. Exiting...");
|
||||
active_event_loop.exit();
|
||||
}
|
||||
WindowEvent::Resized(physical_size) => {
|
||||
info!(
|
||||
"Window resized to: {}x{}",
|
||||
physical_size.width, physical_size.height
|
||||
);
|
||||
// Important: Resize renderer *before* the next frame
|
||||
self.renderer
|
||||
.resize(physical_size.width, physical_size.height);
|
||||
// Egui also needs the new screen descriptor info, though
|
||||
// egui_winit_state might handle this internally via on_window_event.
|
||||
// Explicitly setting it might be safer depending on version.
|
||||
// self.egui_winit_state.set_max_size_points(...) // If needed
|
||||
}
|
||||
WindowEvent::ScaleFactorChanged { scale_factor, .. } => {
|
||||
info!("Scale factor changed: {}", scale_factor);
|
||||
// May also need to resize renderer if size depends on scale factor
|
||||
let new_inner_size = self.window.inner_size();
|
||||
self.renderer
|
||||
.resize(new_inner_size.width, new_inner_size.height);
|
||||
}
|
||||
// Handle other inputs if not consumed by egui
|
||||
WindowEvent::KeyboardInput { .. }
|
||||
| WindowEvent::CursorMoved { .. }
|
||||
| WindowEvent::MouseInput { .. } => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
// Event::MainEventsCleared => { // Use AboutToWait for newer winit
|
||||
// // Application update code.
|
||||
// self.window.request_redraw();
|
||||
// }
|
||||
Event::AboutToWait => {
|
||||
// Application update code and redraw request.
|
||||
// This is the main place to prepare and trigger rendering.
|
||||
|
||||
let now = Instant::now();
|
||||
let _delta_time = now.duration_since(self.last_frame_time);
|
||||
self.last_frame_time = now;
|
||||
|
||||
// --- Render Frame ---
|
||||
match self.renderer.render_frame() {
|
||||
Ok(_) => {}
|
||||
Err(RendererError::SwapchainSuboptimal) => {
|
||||
// Swapchain is suboptimal, recreate it next frame by triggering resize
|
||||
warn!("Swapchain suboptimal, forcing resize.");
|
||||
let size = self.window.inner_size();
|
||||
self.renderer.resize(size.width, size.height);
|
||||
}
|
||||
Err(e) => {
|
||||
error!("Failed to render frame: {}", e);
|
||||
// Decide how to handle persistent errors (e.g., exit)
|
||||
active_event_loop.exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
Event::LoopExiting => {
|
||||
info!("Event loop exiting. Cleaning up...");
|
||||
// Wait for GPU to finish before dropping resources
|
||||
if let Err(e) = self.device.wait_idle() {
|
||||
error!("Error waiting for device idle on exit: {}", e);
|
||||
}
|
||||
info!("GPU idle. Cleanup complete.");
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helper Functions ---
|
||||
|
||||
/// Finds queue family indices for graphics and presentation.
|
||||
fn find_suitable_device_and_queues(
|
||||
physical_device: &PhysicalDevice,
|
||||
surface: &Surface,
|
||||
required_extensions: &[&CStr],
|
||||
required_dynamic_rendering_features: &vk::PhysicalDeviceDynamicRenderingFeaturesKHR,
|
||||
) -> Result<gfx_hal::physical_device::QueueFamilyIndices, Box<dyn Error>> {
|
||||
// 1. Check Extension Support
|
||||
let supported_extensions = unsafe {
|
||||
physical_device
|
||||
.instance()
|
||||
.ash_instance()
|
||||
.enumerate_device_extension_properties(physical_device.handle())?
|
||||
};
|
||||
let supported_extension_names: std::collections::HashSet<&CStr> = supported_extensions
|
||||
.iter()
|
||||
.map(|ext| unsafe { CStr::from_ptr(ext.extension_name.as_ptr()) })
|
||||
.collect();
|
||||
|
||||
for &required in required_extensions {
|
||||
if !supported_extension_names.contains(required) {
|
||||
return Err(
|
||||
format!("Missing required extension: {}", required.to_string_lossy()).into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Check Feature Support (Dynamic Rendering)
|
||||
let mut dynamic_rendering_features = vk::PhysicalDeviceDynamicRenderingFeaturesKHR::default();
|
||||
let mut features2 =
|
||||
vk::PhysicalDeviceFeatures2::default().push_next(&mut dynamic_rendering_features);
|
||||
|
||||
unsafe {
|
||||
physical_device
|
||||
.instance()
|
||||
.ash_instance()
|
||||
.get_physical_device_features2(physical_device.handle(), &mut features2);
|
||||
}
|
||||
|
||||
if dynamic_rendering_features.dynamic_rendering == vk::FALSE {
|
||||
return Err("Dynamic Rendering feature not supported".into());
|
||||
}
|
||||
// Add checks for other required features here...
|
||||
|
||||
// 3. Check Queue Family Support
|
||||
let queue_family_properties = unsafe {
|
||||
physical_device
|
||||
.instance()
|
||||
.ash_instance()
|
||||
.get_physical_device_queue_family_properties(physical_device.handle())
|
||||
};
|
||||
|
||||
let mut graphics_family = None;
|
||||
let mut present_family = None;
|
||||
|
||||
for (i, queue_family) in queue_family_properties.iter().enumerate() {
|
||||
let index = i as u32;
|
||||
|
||||
// Check for graphics support
|
||||
if queue_family.queue_flags.contains(vk::QueueFlags::GRAPHICS) {
|
||||
graphics_family = Some(index);
|
||||
}
|
||||
|
||||
// Check for presentation support
|
||||
let present_support = unsafe {
|
||||
surface
|
||||
.surface_loader()
|
||||
.get_physical_device_surface_support(
|
||||
physical_device.handle(),
|
||||
index,
|
||||
surface.handle(),
|
||||
)?
|
||||
};
|
||||
if present_support {
|
||||
present_family = Some(index);
|
||||
}
|
||||
|
||||
if graphics_family.is_some() && present_family.is_some() {
|
||||
break; // Found suitable families
|
||||
}
|
||||
}
|
||||
|
||||
match (graphics_family, present_family) {
|
||||
(Some(graphics), Some(present)) => Ok(gfx_hal::physical_device::QueueFamilyIndices {
|
||||
graphics_family: Some(graphics),
|
||||
present_family: Some(present), // Could be the same as graphics
|
||||
compute_family: None, // Not needed for this example
|
||||
transfer_family: None, // Not needed for this example
|
||||
}),
|
||||
_ => Err("Could not find suitable queue families".into()),
|
||||
}
|
||||
}
|
||||
|
||||
// --- Entry Point ---
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
let fmt_layer = tracing_subscriber::fmt::layer()
|
||||
.with_ansi(true)
|
||||
.with_file(false)
|
||||
.with_line_number(false)
|
||||
.without_time();
|
||||
|
||||
let log_file = OpenOptions::new()
|
||||
.append(true)
|
||||
.create(true)
|
||||
.open("log-debug.log")?;
|
||||
|
||||
let json_layer = tracing_subscriber::fmt::layer()
|
||||
.json()
|
||||
.with_writer(log_file)
|
||||
.with_filter(filter::LevelFilter::TRACE);
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(fmt_layer)
|
||||
.with(json_layer)
|
||||
.init();
|
||||
|
||||
// --- Winit Setup ---
|
||||
let event_loop = EventLoop::new()?;
|
||||
let window = Arc::new(event_loop.create_window(WindowAttributes::default())?);
|
||||
|
||||
info!("Window created.");
|
||||
|
||||
// --- Application Setup ---
|
||||
let mut app = Application::new(window.clone())?;
|
||||
|
||||
// --- Event Loop ---
|
||||
info!("Starting event loop...");
|
||||
event_loop.run(move |event, elwt| {
|
||||
// elwt is EventLoopWindowTarget, not needed directly here often
|
||||
app.handle_event(&event, elwt);
|
||||
})?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
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)
|
||||
}
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
use ash::vk;
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashSet;
|
||||
use std::ffi::CStr;
|
||||
use std::sync::Weak;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use crate::error::{GfxHalError, Result};
|
||||
|
|
@ -23,12 +25,13 @@ pub struct Device {
|
|||
|
||||
impl Device {
|
||||
/// Creates a new logical device. Typically called via `PhysicalDevice::create_logical_device`.
|
||||
/// Uses a two-stage initialization to avoid Arc::new_cyclic issues.
|
||||
///
|
||||
/// # Saftey
|
||||
/// # Safety
|
||||
/// - `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`.
|
||||
/// - All feature structs passed must be supported by the `physical_device_handle`.
|
||||
pub(crate) unsafe fn new(
|
||||
instance: Arc<Instance>,
|
||||
physical_device_handle: vk::PhysicalDevice,
|
||||
|
|
@ -36,22 +39,26 @@ impl Device {
|
|||
required_extensions: &[&CStr],
|
||||
enabled_features: &vk::PhysicalDeviceFeatures,
|
||||
mesh_features: Option<&vk::PhysicalDeviceMeshShaderFeaturesEXT>,
|
||||
dynamic_rendering_features: &vk::PhysicalDeviceDynamicRenderingFeatures,
|
||||
buffer_device_address_features: &vk::PhysicalDeviceBufferDeviceAddressFeatures,
|
||||
// Add other feature structs here as needed...
|
||||
) -> Result<Arc<Self>> {
|
||||
// --- 1. Prepare Queue Create Infos (Same as before) ---
|
||||
let mut queue_create_infos = Vec::new();
|
||||
let mut unique_queue_families = std::collections::HashSet::new();
|
||||
|
||||
let mut unique_queue_families = 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);
|
||||
}
|
||||
|
||||
if let Some(present_family) = queue_family_indicies.present_family {
|
||||
unique_queue_families.insert(present_family);
|
||||
}
|
||||
let queue_priorities = [1.0f32];
|
||||
for &family_index in &unique_queue_families {
|
||||
let queue_create_info = vk::DeviceQueueCreateInfo::default()
|
||||
|
|
@ -60,74 +67,94 @@ impl Device {
|
|||
queue_create_infos.push(queue_create_info);
|
||||
}
|
||||
|
||||
// --- 2. Prepare Feature Chain (Same as before) ---
|
||||
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 mut dyn_rendering_feats_copy = *dynamic_rendering_features;
|
||||
if dyn_rendering_feats_copy.dynamic_rendering != vk::TRUE {
|
||||
return Err(GfxHalError::MissingFeature("Dynamic Rendering".to_string()));
|
||||
}
|
||||
features2 = features2.push_next(&mut dyn_rendering_feats_copy);
|
||||
let mut bda_features_copy = *buffer_device_address_features;
|
||||
if bda_features_copy.buffer_device_address != vk::TRUE {
|
||||
return Err(GfxHalError::MissingFeature(
|
||||
"Buffer Device Address".to_string(),
|
||||
));
|
||||
}
|
||||
features2 = features2.push_next(&mut bda_features_copy);
|
||||
// Chain other features here...
|
||||
|
||||
// --- 3. Create the SINGLE ash::Device (Same as before) ---
|
||||
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(
|
||||
let ash_device = instance.ash_instance().create_device(
|
||||
physical_device_handle,
|
||||
&device_create_info,
|
||||
None,
|
||||
)?;
|
||||
tracing::info!("logical device created successfully.");
|
||||
tracing::info!(
|
||||
"Logical device created successfully (ash::Device handle: {:?}).",
|
||||
ash_device.handle()
|
||||
);
|
||||
|
||||
let mut queues_map = HashMap::new();
|
||||
let arc_device_placeholder = Arc::new(Self {
|
||||
instance,
|
||||
// --- 4. Create the Device struct in an Arc (Stage 1) ---
|
||||
// Initialize the queues map as empty for now.
|
||||
let device_arc = Arc::new(Device {
|
||||
instance: instance.clone(),
|
||||
physical_device: physical_device_handle,
|
||||
device,
|
||||
queues: Mutex::new(HashMap::new()),
|
||||
device: ash_device, // Move the created ash::Device here
|
||||
queues: Mutex::new(HashMap::new()), // Start with empty 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,
|
||||
});
|
||||
tracing::debug!(
|
||||
"Device Arc created (Stage 1) with ash::Device handle: {:?}",
|
||||
device_arc.raw().handle()
|
||||
);
|
||||
|
||||
// --- 5. Create Queues and Populate Map (Stage 2) ---
|
||||
// Now that we have the final Arc<Device>, we can create the Queues.
|
||||
let mut queues_to_insert = HashMap::new();
|
||||
for &family_index in &unique_queue_families {
|
||||
let queue_handler = arc_device_placeholder
|
||||
.device
|
||||
.get_device_queue(family_index, 0);
|
||||
// Get the Vulkan queue handle using the device stored in the Arc
|
||||
// Assuming queue index 0 for simplicity
|
||||
let vk_queue_handle = device_arc.device.get_device_queue(family_index, 0);
|
||||
|
||||
// Create the Queue wrapper, passing a clone of the device_arc
|
||||
let queue_wrapper = Arc::new(Queue::new(
|
||||
Arc::clone(&arc_device_placeholder),
|
||||
queue_handler,
|
||||
device_arc.clone(), // Pass the Arc<Device>
|
||||
vk_queue_handle,
|
||||
family_index,
|
||||
));
|
||||
queues_map.insert((family_index, 0), queue_wrapper);
|
||||
queues_to_insert.insert((family_index, 0), queue_wrapper);
|
||||
tracing::trace!("Created queue wrapper for family {}", family_index);
|
||||
}
|
||||
|
||||
let device_handle = unsafe {
|
||||
arc_device_placeholder
|
||||
.instance
|
||||
.ash_instance()
|
||||
.create_device(physical_device_handle, &device_create_info, None)?
|
||||
};
|
||||
// Lock the mutex and insert the created queues into the map within the Arc<Device>
|
||||
{
|
||||
// Scope for the mutex guard
|
||||
let mut queues_map_guard = device_arc.queues.lock();
|
||||
*queues_map_guard = queues_to_insert; // Replace the empty map with the populated one
|
||||
tracing::debug!(
|
||||
"Device Arc populated with {} queues (Stage 2).",
|
||||
queues_map_guard.len()
|
||||
);
|
||||
} // Mutex guard is dropped here
|
||||
|
||||
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)
|
||||
Ok(device_arc) // Return the fully initialized Arc<Device>
|
||||
}
|
||||
|
||||
/// Provides raw access to the underlying `ash::Device`.
|
||||
|
|
@ -210,6 +237,8 @@ impl PhysicalDevice {
|
|||
queue_family_indices: &QueueFamilyIndices,
|
||||
enabled_features: &vk::PhysicalDeviceFeatures,
|
||||
mesh_features: Option<&vk::PhysicalDeviceMeshShaderFeaturesEXT>,
|
||||
dynamic_rendering_features: &vk::PhysicalDeviceDynamicRenderingFeatures,
|
||||
buffer_device_address_features: &vk::PhysicalDeviceBufferDeviceAddressFeatures,
|
||||
) -> Result<Arc<Device>> {
|
||||
Device::new(
|
||||
Arc::clone(self.instance()),
|
||||
|
|
@ -218,6 +247,8 @@ impl PhysicalDevice {
|
|||
required_extensions,
|
||||
enabled_features,
|
||||
mesh_features,
|
||||
dynamic_rendering_features,
|
||||
buffer_device_address_features,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,8 @@ pub enum GfxHalError {
|
|||
MissingExtension(String),
|
||||
|
||||
/// A required Vulkan feature is not supported by the physical device.
|
||||
#[error("Missing required Vulkan feature.")]
|
||||
MissingFeature,
|
||||
#[error("Missing required Vulkan feature: {0}")]
|
||||
MissingFeature(String),
|
||||
|
||||
/// Failed to find a suitable queue family (e.g., graphics, present).
|
||||
#[error("Could not find required queue family: {0}")]
|
||||
|
|
|
|||
|
|
@ -5,9 +5,13 @@ use std::{
|
|||
};
|
||||
|
||||
use ash::{ext::debug_utils, vk};
|
||||
use winit::raw_window_handle::{DisplayHandle, HasDisplayHandle};
|
||||
use winit::raw_window_handle::{DisplayHandle, HasDisplayHandle, HasWindowHandle};
|
||||
|
||||
use crate::error::{GfxHalError, Result};
|
||||
use crate::{
|
||||
error::{GfxHalError, Result},
|
||||
physical_device::PhysicalDevice,
|
||||
surface::Surface,
|
||||
};
|
||||
|
||||
unsafe extern "system" fn vulkan_debug_callback(
|
||||
message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
|
||||
|
|
@ -303,39 +307,39 @@ impl Instance {
|
|||
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)
|
||||
// }
|
||||
/// Enumerates all physical devices available to this instance.
|
||||
///
|
||||
/// # Safety
|
||||
/// The `Instance` must be kept alive while the returned `PhysicalDevices`s are in use.
|
||||
/// This is ensured by returning `PhysicalDevice`s holding an `Arc<Instance>`.
|
||||
pub unsafe fn enumerate_physical_devices(self: &Arc<Self>) -> Result<Vec<PhysicalDevice>> {
|
||||
let physical_device_handles = self.instance.enumerate_physical_devices()?;
|
||||
|
||||
// /// 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)
|
||||
// }
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use ash::vk;
|
|||
|
||||
use crate::{error::GfxHalError, instance::Instance};
|
||||
|
||||
use std::{ffi::CStr, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Represents a physical Vulkan device (GPU).
|
||||
///
|
||||
|
|
@ -21,7 +21,7 @@ pub struct PhysicalDevice {
|
|||
}
|
||||
|
||||
/// Holds information about queue families found on a `PhysicalDevice`.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct QueueFamilyIndices {
|
||||
/// Queue family index supporting graphics operations.
|
||||
pub graphics_family: Option<u32>,
|
||||
|
|
@ -100,16 +100,23 @@ impl PhysicalDevice {
|
|||
) -> (
|
||||
vk::PhysicalDeviceFeatures,
|
||||
vk::PhysicalDeviceMeshShaderFeaturesEXT,
|
||||
vk::PhysicalDeviceDynamicRenderingFeatures,
|
||||
) {
|
||||
let mut mesh_shader_features = vk::PhysicalDeviceMeshShaderFeaturesEXT::default();
|
||||
let mut features2 =
|
||||
vk::PhysicalDeviceFeatures2::default().push_next(&mut mesh_shader_features);
|
||||
let mut dynamic_rendering_features = vk::PhysicalDeviceDynamicRenderingFeatures::default();
|
||||
let mut features2 = vk::PhysicalDeviceFeatures2::default()
|
||||
.push_next(&mut mesh_shader_features)
|
||||
.push_next(&mut dynamic_rendering_features);
|
||||
|
||||
self.instance
|
||||
.ash_instance()
|
||||
.get_physical_device_features2(self.handle, &mut features2);
|
||||
|
||||
(features2.features, mesh_shader_features)
|
||||
(
|
||||
features2.features,
|
||||
mesh_shader_features,
|
||||
dynamic_rendering_features,
|
||||
)
|
||||
}
|
||||
|
||||
/// Queries the properties of all queue families available on the device.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use ash::vk;
|
||||
use ash::{vk, Device as AshDevice};
|
||||
use parking_lot::Mutex;
|
||||
|
||||
use crate::device::Device;
|
||||
|
|
@ -46,25 +46,41 @@ impl Queue {
|
|||
&self.device
|
||||
}
|
||||
|
||||
/// Submits command buffers to the queue.
|
||||
/// Submits command buffers to the queue using the provided device handle.
|
||||
///
|
||||
/// 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
|
||||
/// * `submit_device_raw` - The `ash::Device` handle corresponding to the device that owns the resources in `submits` and the `signal_fence`.
|
||||
/// * `submits` - A slice of `vk::SubmitInfo` describing the work to submit.
|
||||
/// * `signal_fence` - An optional `Fence` to signal when the submission completes.
|
||||
/// * `signal_fence` - An optional `Fence` to signal when the submission completes. The fence must have been created with the same logical device as `submit_device_raw`.
|
||||
///
|
||||
/// # Safety
|
||||
/// - The command buffers and synchronization primitieves within `submits` must be valid.
|
||||
/// - The `signal_fence`, if provided, must be valid and unsignaled.
|
||||
/// - `submit_device_raw` must be the correct, valid `ash::Device` handle associated with the resources being submitted.
|
||||
/// - The command buffers and synchronization primitives within `submits` must be valid and owned by the same logical device as `submit_device_raw`.
|
||||
/// - The `signal_fence`, if provided, must be valid, unsignaled, and owned by the same logical device as `submit_device_raw`.
|
||||
pub unsafe fn submit(
|
||||
&self,
|
||||
submit_device_raw: &AshDevice, // <<< Accept the ash::Device to use
|
||||
submits: &[vk::SubmitInfo],
|
||||
signal_fence: Option<&Fence>,
|
||||
) -> Result<()> {
|
||||
debug_assert!(
|
||||
self.device.raw().handle() == submit_device_raw.handle(),
|
||||
"Queue::submit called with an ash::Device from a different logical VkDevice than the queue belongs to!"
|
||||
);
|
||||
// Optional: Check fence device consistency
|
||||
if let Some(fence) = signal_fence {
|
||||
debug_assert!(
|
||||
fence.device().raw().handle() == submit_device_raw.handle(),
|
||||
"Fence passed to Queue::submit belongs to a different logical device than submit_device_raw!"
|
||||
);
|
||||
}
|
||||
|
||||
let fence_handle = signal_fence.map_or(vk::Fence::null(), |f| f.handle());
|
||||
|
||||
// Keep the lock for thread-safety on the VkQueue object itself
|
||||
let _lock = self.submit_lock.lock();
|
||||
|
||||
tracing::trace!(
|
||||
|
|
@ -72,9 +88,10 @@ impl Queue {
|
|||
submits.len(),
|
||||
self.family_index
|
||||
);
|
||||
self.device
|
||||
.raw()
|
||||
.queue_submit(self.queue, submits, fence_handle)?;
|
||||
|
||||
// Use the EXPLICITLY PASSED submit_device_raw for the Vulkan call
|
||||
submit_device_raw.queue_submit(self.queue, submits, fence_handle)?;
|
||||
|
||||
tracing::trace!("Submission successful.");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,9 @@ use std::sync::Arc;
|
|||
use ash::khr::swapchain::Device as SwapchainLoader;
|
||||
use ash::vk;
|
||||
|
||||
use crate::device::{self, Device};
|
||||
use crate::device::Device;
|
||||
use crate::error::{GfxHalError, Result};
|
||||
use crate::surface::{self, Surface};
|
||||
use crate::surface::Surface;
|
||||
use crate::sync::{Fence, Semaphore};
|
||||
|
||||
/// Configuration for creating or recreating a `Swapchain`.
|
||||
|
|
|
|||
|
|
@ -33,6 +33,11 @@ impl Fence {
|
|||
Ok(Self { device, fence })
|
||||
}
|
||||
|
||||
/// Returns the device used by the fence.
|
||||
pub fn device(&self) -> &Arc<Device> {
|
||||
&self.device
|
||||
}
|
||||
|
||||
/// Waits for the fence to become signaled.
|
||||
///
|
||||
/// # Arguments
|
||||
|
|
|
|||
24
crates/renderer/Cargo.toml
Normal file
24
crates/renderer/Cargo.toml
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
[package]
|
||||
name = "renderer"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
ash.workspace = true
|
||||
tracing.workspace = true
|
||||
thiserror.workspace = true
|
||||
glam.workspace = true
|
||||
bytemuck.workspace = true
|
||||
gpu-allocator.workspace = true
|
||||
egui.workspace = true
|
||||
egui-ash-renderer.workspace = true
|
||||
winit.workspace = true
|
||||
parking_lot.workspace = true
|
||||
|
||||
gfx_hal = { path = "../gfx_hal" }
|
||||
resource_manager = { path = "../resource_manager" }
|
||||
|
||||
[build-dependencies]
|
||||
shaderc = "0.9.1"
|
||||
walkdir = "2"
|
||||
anyhow = "1.0"
|
||||
152
crates/renderer/build.rs
Normal file
152
crates/renderer/build.rs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
use anyhow::{Context, Result};
|
||||
use shaderc::{CompileOptions, Compiler, ShaderKind};
|
||||
use std::{
|
||||
env,
|
||||
fs::{self, File},
|
||||
io::Write,
|
||||
path::PathBuf,
|
||||
};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
// Configuration
|
||||
const SHADER_SOURCE_DIR: &str = "../../shaders"; // Directory containing GLSL shaders
|
||||
// Output directory will be determined by Cargo (OUT_DIR)
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR")?).join("shaders"); // Put shaders in a subdirectory for clarity
|
||||
fs::create_dir_all(&out_dir).context("Failed to create shader output directory")?;
|
||||
|
||||
let compiler = Compiler::new().context("Failed to create shader compiler")?;
|
||||
let mut options = CompileOptions::new().context("Failed to create compile options")?;
|
||||
|
||||
// --- Optional: Add compile options ---
|
||||
// Example: Optimize for performance in release builds
|
||||
if env::var("PROFILE")? == "release" {
|
||||
options.set_optimization_level(shaderc::OptimizationLevel::Performance);
|
||||
eprintln!("Build.rs: Compiling shaders with Performance optimization.");
|
||||
} else {
|
||||
options.set_optimization_level(shaderc::OptimizationLevel::Zero); // Faster compile for debug
|
||||
options.set_generate_debug_info(); // Include debug info for debug builds
|
||||
eprintln!("Build.rs: Compiling shaders with Zero optimization and Debug info.");
|
||||
}
|
||||
// Add other options like defines if needed:
|
||||
// options.add_macro_definition("MY_DEFINE", Some("1"));
|
||||
options.set_target_env(
|
||||
shaderc::TargetEnv::Vulkan,
|
||||
shaderc::EnvVersion::Vulkan1_3 as u32,
|
||||
); // Specify Vulkan version if needed
|
||||
|
||||
eprintln!(
|
||||
"Build.rs: Compiling shaders from '{}' to '{}'",
|
||||
SHADER_SOURCE_DIR,
|
||||
out_dir.display()
|
||||
);
|
||||
|
||||
// --- Find and Compile Shaders ---
|
||||
for entry in WalkDir::new(SHADER_SOURCE_DIR)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok()) // Ignore directory reading errors
|
||||
.filter(|e| e.file_type().is_file())
|
||||
// Only process files
|
||||
{
|
||||
let in_path = entry.path();
|
||||
|
||||
// Determine shader kind from extension
|
||||
let extension = match in_path.extension().and_then(|s| s.to_str()) {
|
||||
Some(ext) => ext,
|
||||
None => {
|
||||
eprintln!(
|
||||
"cargo:warning=Skipping file with no extension: {}",
|
||||
in_path.display()
|
||||
);
|
||||
continue; // Skip files without extensions
|
||||
}
|
||||
};
|
||||
let shader_kind = match extension {
|
||||
"vert" => ShaderKind::Vertex,
|
||||
"frag" => ShaderKind::Fragment,
|
||||
"comp" => ShaderKind::Compute,
|
||||
"geom" => ShaderKind::Geometry,
|
||||
"tesc" => ShaderKind::TessControl,
|
||||
"tese" => ShaderKind::TessEvaluation,
|
||||
// Add other shader kinds if needed (ray tracing, mesh, etc.)
|
||||
_ => {
|
||||
eprintln!(
|
||||
"cargo:warning=Skipping file with unknown shader extension ({}): {}",
|
||||
extension,
|
||||
in_path.display()
|
||||
);
|
||||
continue; // Skip unknown shader types
|
||||
}
|
||||
};
|
||||
|
||||
let source_text = fs::read_to_string(in_path)
|
||||
.with_context(|| format!("Failed to read shader source: {}", in_path.display()))?;
|
||||
let input_file_name = in_path.to_string_lossy(); // For error messages
|
||||
|
||||
// Compile the shader
|
||||
let compiled_spirv = compiler
|
||||
.compile_into_spirv(
|
||||
&source_text,
|
||||
shader_kind,
|
||||
&input_file_name, // Source file name for errors
|
||||
"main", // Entry point function name
|
||||
Some(&options), // Pass compile options
|
||||
)
|
||||
.with_context(|| format!("Failed to compile shader: {}", input_file_name))?;
|
||||
|
||||
let spirv_bytes = compiled_spirv.as_binary_u8();
|
||||
let byte_count = spirv_bytes.len();
|
||||
eprintln!(
|
||||
"Build.rs: SPIR-V for {} has {} bytes.",
|
||||
input_file_name, byte_count
|
||||
);
|
||||
|
||||
// Check if it's a multiple of 4 right here
|
||||
if byte_count % 4 != 0 {
|
||||
eprintln!(
|
||||
"cargo:warning=Byte count for {} ({}) is NOT a multiple of 4!",
|
||||
input_file_name, byte_count
|
||||
);
|
||||
// Optionally bail out here:
|
||||
// bail!("Generated SPIR-V for {} has invalid byte count {}", input_file_name, byte_count);
|
||||
}
|
||||
|
||||
// Check for warnings
|
||||
if compiled_spirv.get_num_warnings() > 0 {
|
||||
eprintln!(
|
||||
"cargo:warning=Shader compilation warnings for {}:\n{}",
|
||||
input_file_name,
|
||||
compiled_spirv.get_warning_messages()
|
||||
);
|
||||
}
|
||||
|
||||
// Determine output path
|
||||
let out_filename = format!(
|
||||
"{}.spv",
|
||||
in_path
|
||||
.file_stem() // Get filename without extension
|
||||
.unwrap_or_default() // Handle potential weird filenames
|
||||
.to_string_lossy()
|
||||
);
|
||||
let out_path = out_dir.join(out_filename);
|
||||
|
||||
// Determine output path...
|
||||
// ...
|
||||
// Write the compiled SPIR-V binary
|
||||
let mut outfile = File::create(&out_path)
|
||||
.with_context(|| format!("Failed to create output file: {}", out_path.display()))?;
|
||||
outfile
|
||||
.write_all(spirv_bytes) // Use the stored bytes
|
||||
.with_context(|| format!("Failed to write SPIR-V to: {}", out_path.display()))?;
|
||||
|
||||
eprintln!(
|
||||
"Build.rs: Compiled {} -> {}",
|
||||
in_path.display(),
|
||||
out_path.display()
|
||||
);
|
||||
}
|
||||
|
||||
eprintln!("Build.rs: Shader compilation finished.");
|
||||
Ok(())
|
||||
}
|
||||
927
crates/renderer/src/lib.rs
Normal file
927
crates/renderer/src/lib.rs
Normal file
|
|
@ -0,0 +1,927 @@
|
|||
use std::{ffi::CStr, sync::Arc};
|
||||
|
||||
use ash::vk;
|
||||
use gfx_hal::{
|
||||
device::Device, error::GfxHalError, queue::Queue, surface::Surface, swapchain::Swapchain,
|
||||
swapchain::SwapchainConfig, sync::Fence, sync::Semaphore,
|
||||
};
|
||||
use gpu_allocator::{vulkan::Allocator, MemoryLocation};
|
||||
use parking_lot::Mutex;
|
||||
use resource_manager::{ImageHandle, ResourceManager, ResourceManagerError};
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, error, info, warn};
|
||||
// Assuming winit is used by the app
|
||||
|
||||
// Re-export ash for convenience if needed elsewhere
|
||||
pub use ash;
|
||||
|
||||
const MAX_FRAMES_IN_FLIGHT: usize = 2;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum RendererError {
|
||||
#[error("Graphics HAL Error: {0}")]
|
||||
GfxHal(#[from] GfxHalError),
|
||||
#[error("Resource Manager Error: {0}")]
|
||||
ResourceManager(#[from] ResourceManagerError),
|
||||
#[error("Egui Ash Renderer Error: {0}")]
|
||||
EguiRenderer(#[from] egui_ash_renderer::RendererError),
|
||||
#[error("Vulkan Error: {0}")]
|
||||
Vulkan(#[from] vk::Result),
|
||||
#[error("Failed to create shader module: {0}")]
|
||||
ShaderCreation(vk::Result),
|
||||
#[error("Failed to create pipeline layout: {0}")]
|
||||
PipelineLayoutCreation(vk::Result),
|
||||
#[error("Failed to create graphics pipeline: {0}")]
|
||||
PipelineCreation(vk::Result),
|
||||
#[error("Failed to create command pool: {0}")]
|
||||
CommandPoolCreation(vk::Result),
|
||||
#[error("Failed to allocate command buffers: {0}")]
|
||||
CommandBufferAllocation(vk::Result),
|
||||
#[error("Failed to begin command buffer: {0}")]
|
||||
CommandBufferBegin(vk::Result),
|
||||
#[error("Failed to end command buffer: {0}")]
|
||||
CommandBufferEnd(vk::Result),
|
||||
#[error("Swapchain acquisition failed")]
|
||||
SwapchainAcquisitionFailed,
|
||||
#[error("Swapchain is suboptimal")]
|
||||
SwapchainSuboptimal,
|
||||
#[error("Window reference is missing")] // If using raw-window-handle directly
|
||||
MissingWindow,
|
||||
#[error("Failed to get image info from resource manager")]
|
||||
ImageInfoUnavailable,
|
||||
#[error("Failed to get allocator from resource manager")]
|
||||
AllocatorUnavailable, // Added based on egui requirement
|
||||
}
|
||||
|
||||
struct FrameData {
|
||||
command_pool: vk::CommandPool,
|
||||
command_buffer: vk::CommandBuffer,
|
||||
image_available_semaphore: Semaphore,
|
||||
render_finished_semaphore: Semaphore,
|
||||
in_flight_fence: Fence,
|
||||
}
|
||||
|
||||
struct SwapchainSupportDetails {
|
||||
capabilities: vk::SurfaceCapabilitiesKHR,
|
||||
formats: Vec<vk::SurfaceFormatKHR>,
|
||||
present_modes: Vec<vk::PresentModeKHR>,
|
||||
}
|
||||
|
||||
pub struct Renderer {
|
||||
device: Arc<Device>,
|
||||
graphics_queue: Arc<Queue>,
|
||||
resource_manager: Arc<ResourceManager>,
|
||||
allocator: Arc<Mutex<Allocator>>, // Need direct access for egui
|
||||
|
||||
surface: Arc<Surface>, // Keep surface for recreation
|
||||
swapchain: Option<Swapchain>, // Option<> because it's recreated
|
||||
swapchain_image_views: Vec<vk::ImageView>,
|
||||
swapchain_format: vk::SurfaceFormatKHR,
|
||||
swapchain_extent: vk::Extent2D,
|
||||
|
||||
depth_image_handle: ImageHandle,
|
||||
depth_image_view: vk::ImageView, // Store the view directly
|
||||
depth_format: vk::Format,
|
||||
|
||||
triangle_pipeline_layout: vk::PipelineLayout,
|
||||
triangle_pipeline: vk::Pipeline,
|
||||
|
||||
frames_data: Vec<FrameData>,
|
||||
current_frame: usize,
|
||||
|
||||
// Window state tracking (needed for recreation)
|
||||
window_resized: bool,
|
||||
current_width: u32,
|
||||
current_height: u32,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
pub fn new(
|
||||
instance: Arc<gfx_hal::instance::Instance>, // Needed for allocator
|
||||
device: Arc<Device>,
|
||||
graphics_queue: Arc<Queue>,
|
||||
surface: Arc<Surface>,
|
||||
resource_manager: Arc<ResourceManager>,
|
||||
initial_width: u32,
|
||||
initial_height: u32,
|
||||
) -> Result<Self, RendererError> {
|
||||
info!("Initializing Renderer...");
|
||||
|
||||
let allocator = resource_manager.allocator();
|
||||
|
||||
let (swapchain, format, extent, image_views) = Self::create_swapchain_and_views(
|
||||
&device,
|
||||
&surface,
|
||||
initial_width,
|
||||
initial_height,
|
||||
None, // No old swapchain initially
|
||||
)?;
|
||||
|
||||
let depth_format = Self::find_depth_format(&instance, &device)?;
|
||||
let (depth_image_handle, depth_image_view) =
|
||||
Self::create_depth_resources(&device, &resource_manager, extent, depth_format)?;
|
||||
|
||||
let (triangle_pipeline_layout, triangle_pipeline) =
|
||||
Self::create_triangle_pipeline(&device, format.format, depth_format)?;
|
||||
|
||||
let frames_data = Self::create_frame_data(&device)?;
|
||||
|
||||
info!("Renderer initialized successfully.");
|
||||
|
||||
Ok(Self {
|
||||
device,
|
||||
graphics_queue,
|
||||
resource_manager,
|
||||
allocator, // Store the allocator Arc
|
||||
surface,
|
||||
swapchain: Some(swapchain),
|
||||
swapchain_image_views: image_views,
|
||||
swapchain_format: format,
|
||||
swapchain_extent: extent,
|
||||
depth_image_handle,
|
||||
depth_image_view,
|
||||
depth_format,
|
||||
triangle_pipeline_layout,
|
||||
triangle_pipeline,
|
||||
frames_data,
|
||||
current_frame: 0,
|
||||
window_resized: false,
|
||||
current_width: initial_width,
|
||||
current_height: initial_height,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn resize(&mut self, width: u32, height: u32) {
|
||||
if width > 0 && height > 0 {
|
||||
self.window_resized = true;
|
||||
self.current_width = width;
|
||||
self.current_height = height;
|
||||
debug!("Window resize requested to {}x{}", width, height);
|
||||
} else {
|
||||
debug!("Ignoring resize to 0 dimensions");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_frame(&mut self) -> Result<(), RendererError> {
|
||||
// --- Handle Resize ---
|
||||
if self.window_resized {
|
||||
self.window_resized = false;
|
||||
debug!("Executing resize...");
|
||||
self.recreate_swapchain()?;
|
||||
// Skip rendering this frame as swapchain is new
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// --- Wait for Previous Frame ---
|
||||
let frame_index = self.current_frame;
|
||||
let frame_data = &self.frames_data[frame_index];
|
||||
|
||||
frame_data.in_flight_fence.wait(None)?; // Wait indefinitely
|
||||
|
||||
// --- Acquire Swapchain Image ---
|
||||
let (image_index, suboptimal) = unsafe {
|
||||
// Need unsafe block for acquire_next_image
|
||||
self.swapchain
|
||||
.as_ref()
|
||||
.ok_or(RendererError::SwapchainAcquisitionFailed)? // Should exist
|
||||
.acquire_next_image(
|
||||
u64::MAX, // Timeout
|
||||
Some(&frame_data.image_available_semaphore),
|
||||
None, // Don't need a fence here
|
||||
)?
|
||||
};
|
||||
|
||||
if suboptimal {
|
||||
warn!("Swapchain is suboptimal, scheduling recreation.");
|
||||
self.window_resized = true; // Trigger recreation next frame
|
||||
// Reset fence *before* returning, otherwise we deadlock next frame
|
||||
frame_data.in_flight_fence.reset()?;
|
||||
return Ok(()); // Skip rendering
|
||||
}
|
||||
|
||||
// --- Reset Fence (only after successful acquisition) ---
|
||||
frame_data.in_flight_fence.reset()?;
|
||||
|
||||
// --- Record Command Buffer ---
|
||||
unsafe {
|
||||
// Need unsafe for Vulkan commands
|
||||
self.device
|
||||
.raw()
|
||||
.reset_command_pool(frame_data.command_pool, vk::CommandPoolResetFlags::empty())?;
|
||||
}
|
||||
|
||||
let command_buffer = frame_data.command_buffer;
|
||||
let cmd_begin_info = vk::CommandBufferBeginInfo::default()
|
||||
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
|
||||
|
||||
unsafe {
|
||||
// Need unsafe for Vulkan commands
|
||||
self.device
|
||||
.raw()
|
||||
.begin_command_buffer(command_buffer, &cmd_begin_info)?;
|
||||
}
|
||||
|
||||
// --- Dynamic Rendering Setup ---
|
||||
let color_attachment = vk::RenderingAttachmentInfo::default()
|
||||
.image_view(self.swapchain_image_views[image_index as usize])
|
||||
.image_layout(vk::ImageLayout::ATTACHMENT_OPTIMAL)
|
||||
.load_op(vk::AttachmentLoadOp::CLEAR)
|
||||
.store_op(vk::AttachmentStoreOp::STORE)
|
||||
.clear_value(vk::ClearValue {
|
||||
color: vk::ClearColorValue {
|
||||
float32: [0.1, 0.1, 0.1, 1.0],
|
||||
},
|
||||
});
|
||||
|
||||
let depth_attachment = vk::RenderingAttachmentInfo::default()
|
||||
.image_view(self.depth_image_view)
|
||||
.image_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL)
|
||||
.load_op(vk::AttachmentLoadOp::CLEAR)
|
||||
.store_op(vk::AttachmentStoreOp::DONT_CARE) // Or STORE if needed
|
||||
.clear_value(vk::ClearValue {
|
||||
depth_stencil: vk::ClearDepthStencilValue {
|
||||
depth: 1.0,
|
||||
stencil: 0,
|
||||
},
|
||||
});
|
||||
|
||||
let rendering_info = vk::RenderingInfo::default()
|
||||
.render_area(vk::Rect2D {
|
||||
offset: vk::Offset2D { x: 0, y: 0 },
|
||||
extent: self.swapchain_extent,
|
||||
})
|
||||
.layer_count(1)
|
||||
.color_attachments(std::slice::from_ref(&color_attachment))
|
||||
.depth_attachment(&depth_attachment);
|
||||
|
||||
// --- Begin Dynamic Rendering ---
|
||||
unsafe {
|
||||
// Need unsafe for Vulkan commands
|
||||
self.device
|
||||
.raw()
|
||||
.cmd_begin_rendering(command_buffer, &rendering_info);
|
||||
}
|
||||
|
||||
// --- Set Viewport & Scissor ---
|
||||
let viewport = vk::Viewport {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
width: self.swapchain_extent.width as f32,
|
||||
height: self.swapchain_extent.height as f32,
|
||||
min_depth: 0.0,
|
||||
max_depth: 1.0,
|
||||
};
|
||||
let scissor = vk::Rect2D {
|
||||
offset: vk::Offset2D { x: 0, y: 0 },
|
||||
extent: self.swapchain_extent,
|
||||
};
|
||||
unsafe {
|
||||
// Need unsafe for Vulkan commands
|
||||
self.device
|
||||
.raw()
|
||||
.cmd_set_viewport(command_buffer, 0, &[viewport]);
|
||||
self.device
|
||||
.raw()
|
||||
.cmd_set_scissor(command_buffer, 0, &[scissor]);
|
||||
}
|
||||
|
||||
// --- Draw Triangle ---
|
||||
unsafe {
|
||||
// Need unsafe for Vulkan commands
|
||||
self.device.raw().cmd_bind_pipeline(
|
||||
command_buffer,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
self.triangle_pipeline,
|
||||
);
|
||||
// Draw 3 vertices, 1 instance, 0 first vertex, 0 first instance
|
||||
self.device.raw().cmd_draw(command_buffer, 3, 1, 0, 0);
|
||||
}
|
||||
|
||||
// --- End Dynamic Rendering ---
|
||||
unsafe {
|
||||
// Need unsafe for Vulkan commands
|
||||
self.device.raw().cmd_end_rendering(command_buffer);
|
||||
}
|
||||
|
||||
// --- End Command Buffer ---
|
||||
unsafe {
|
||||
// Need unsafe for Vulkan commands
|
||||
self.device.raw().end_command_buffer(command_buffer)?;
|
||||
}
|
||||
|
||||
// --- Submit Command Buffer ---
|
||||
let wait_semaphores = [frame_data.image_available_semaphore.handle()];
|
||||
let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT];
|
||||
let signal_semaphores = [frame_data.render_finished_semaphore.handle()];
|
||||
let command_buffers = [command_buffer];
|
||||
|
||||
let submit_info = vk::SubmitInfo::default()
|
||||
.wait_semaphores(&wait_semaphores)
|
||||
.wait_dst_stage_mask(&wait_stages)
|
||||
.command_buffers(&command_buffers)
|
||||
.signal_semaphores(&signal_semaphores);
|
||||
|
||||
// assert_eq!(
|
||||
// self.graphics_queue.device().raw().handle(), // Device from Queue
|
||||
// self.device.raw().handle(), // Device stored in Renderer
|
||||
// "Device handle mismatch between Renderer and Graphics Queue!"
|
||||
// );
|
||||
|
||||
unsafe {
|
||||
// Need unsafe for queue submit
|
||||
self.graphics_queue.submit(
|
||||
self.device.raw(),
|
||||
&[submit_info],
|
||||
Some(&frame_data.in_flight_fence),
|
||||
)?;
|
||||
}
|
||||
|
||||
// --- Present ---
|
||||
let swapchains = [self.swapchain.as_ref().unwrap().handle()]; // Safe unwrap after acquire
|
||||
let image_indices = [image_index];
|
||||
|
||||
let present_info = vk::PresentInfoKHR::default()
|
||||
.wait_semaphores(&signal_semaphores)
|
||||
.swapchains(&swapchains)
|
||||
.image_indices(&image_indices);
|
||||
|
||||
let suboptimal_present = unsafe {
|
||||
// Need unsafe for queue_present
|
||||
self.swapchain
|
||||
.as_ref()
|
||||
.unwrap() // Safe unwrap
|
||||
.loader()
|
||||
.queue_present(self.graphics_queue.handle(), &present_info)
|
||||
.map_err(|e| {
|
||||
// Handle VK_ERROR_OUT_OF_DATE_KHR specifically
|
||||
if e == vk::Result::ERROR_OUT_OF_DATE_KHR {
|
||||
RendererError::SwapchainSuboptimal
|
||||
} else {
|
||||
RendererError::Vulkan(e)
|
||||
}
|
||||
})? // Returns true if suboptimal
|
||||
};
|
||||
|
||||
if suboptimal_present {
|
||||
warn!("Swapchain is suboptimal after present, scheduling recreation.");
|
||||
self.window_resized = true; // Trigger recreation next frame
|
||||
}
|
||||
|
||||
// --- Advance Frame Counter ---
|
||||
self.current_frame = (self.current_frame + 1) % MAX_FRAMES_IN_FLIGHT;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Helper: Swapchain Recreation ---
|
||||
fn recreate_swapchain(&mut self) -> Result<(), RendererError> {
|
||||
info!("Recreating swapchain...");
|
||||
self.device.wait_idle()?; // Wait until device is idle
|
||||
|
||||
// 1. Cleanup old resources
|
||||
self.cleanup_swapchain_resources(); // Destroys views, depth, old swapchain
|
||||
|
||||
// 2. Create new resources
|
||||
let (new_swapchain, new_format, new_extent, new_image_views) =
|
||||
Self::create_swapchain_and_views(
|
||||
&self.device,
|
||||
&self.surface,
|
||||
self.current_width,
|
||||
self.current_height,
|
||||
self.swapchain.as_ref().map(|s| s.handle()), // Pass old handle
|
||||
)?;
|
||||
|
||||
let (new_depth_handle, new_depth_view) = Self::create_depth_resources(
|
||||
&self.device,
|
||||
&self.resource_manager,
|
||||
new_extent,
|
||||
self.depth_format, // Keep the same depth format
|
||||
)?;
|
||||
|
||||
// 3. Update Renderer state
|
||||
self.swapchain = Some(new_swapchain);
|
||||
self.swapchain_format = new_format;
|
||||
self.swapchain_extent = new_extent;
|
||||
self.swapchain_image_views = new_image_views;
|
||||
self.depth_image_handle = new_depth_handle;
|
||||
self.depth_image_view = new_depth_view;
|
||||
|
||||
// 4. Update Egui Renderer (if necessary, depends on its implementation)
|
||||
// It might need the new extent or recreate internal resources.
|
||||
// Assuming it handles extent changes via update_screen_descriptor called earlier.
|
||||
|
||||
info!(
|
||||
"Swapchain recreated successfully ({}x{}).",
|
||||
new_extent.width, new_extent.height
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// --- Helper: Cleanup Swapchain Dependent Resources ---
|
||||
fn cleanup_swapchain_resources(&mut self) {
|
||||
debug!("Cleaning up swapchain resources...");
|
||||
// Destroy depth buffer view
|
||||
unsafe {
|
||||
self.device
|
||||
.raw()
|
||||
.destroy_image_view(self.depth_image_view, None);
|
||||
}
|
||||
// Destroy depth buffer image via resource manager
|
||||
if let Err(e) = self.resource_manager.destroy_image(self.depth_image_handle) {
|
||||
error!("Failed to destroy depth image: {}", e);
|
||||
// Continue cleanup even if this fails
|
||||
}
|
||||
// Drop the old swapchain object (RAII in gfx_hal::Swapchain handles vkDestroySwapchainKHR)
|
||||
self.swapchain = None;
|
||||
debug!("Swapchain resources cleaned up.");
|
||||
}
|
||||
|
||||
// --- Helper: Create Swapchain ---
|
||||
fn create_swapchain_and_views(
|
||||
device: &Arc<Device>,
|
||||
surface: &Arc<Surface>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
old_swapchain: Option<vk::SwapchainKHR>,
|
||||
) -> Result<
|
||||
(
|
||||
Swapchain,
|
||||
vk::SurfaceFormatKHR,
|
||||
vk::Extent2D,
|
||||
Vec<vk::ImageView>,
|
||||
),
|
||||
RendererError,
|
||||
> {
|
||||
let details = Self::query_swapchain_support(device.physical_device_handle(), surface)?;
|
||||
|
||||
let surface_format = Self::choose_swapchain_format(&details.formats);
|
||||
let present_mode = Self::choose_swapchain_present_mode(&details.present_modes);
|
||||
let extent = Self::choose_swapchain_extent(&details.capabilities, width, height);
|
||||
|
||||
let mut image_count = details.capabilities.min_image_count + 1;
|
||||
if details.capabilities.max_image_count > 0
|
||||
&& image_count > details.capabilities.max_image_count
|
||||
{
|
||||
image_count = details.capabilities.max_image_count;
|
||||
}
|
||||
|
||||
let config = SwapchainConfig {
|
||||
desired_format: surface_format,
|
||||
desired_present_mode: present_mode,
|
||||
desired_image_count: image_count,
|
||||
extent,
|
||||
image_usage: vk::ImageUsageFlags::COLOR_ATTACHMENT,
|
||||
pre_transform: details.capabilities.current_transform,
|
||||
composite_alpha: vk::CompositeAlphaFlagsKHR::OPAQUE,
|
||||
};
|
||||
|
||||
let swapchain =
|
||||
unsafe { Swapchain::new(device.clone(), surface.clone(), config, old_swapchain)? };
|
||||
|
||||
// Create Image Views
|
||||
let image_views = swapchain
|
||||
.image_views() // Assuming Swapchain::new creates and stores these
|
||||
.to_vec(); // Clone the slice into a Vec
|
||||
|
||||
// If Swapchain::new doesn't create views, we need to do it here:
|
||||
/*
|
||||
let images = swapchain.images()?; // Assuming this method exists
|
||||
let mut image_views = Vec::with_capacity(images.len());
|
||||
for &image in images.iter() {
|
||||
let create_info = vk::ImageViewCreateInfo::default()
|
||||
.image(image)
|
||||
.view_type(vk::ImageViewType::TYPE_2D)
|
||||
.format(surface_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,
|
||||
});
|
||||
let view = unsafe { device.raw().create_image_view(&create_info, None)? };
|
||||
image_views.push(view);
|
||||
}
|
||||
*/
|
||||
|
||||
Ok((swapchain, surface_format, extent, image_views))
|
||||
}
|
||||
|
||||
// --- Helper: Create Depth Resources ---
|
||||
fn create_depth_resources(
|
||||
device: &Arc<Device>,
|
||||
resource_manager: &Arc<ResourceManager>,
|
||||
extent: vk::Extent2D,
|
||||
depth_format: vk::Format,
|
||||
) -> Result<(ImageHandle, vk::ImageView), RendererError> {
|
||||
let image_create_info = vk::ImageCreateInfo::default()
|
||||
.image_type(vk::ImageType::TYPE_2D)
|
||||
.extent(vk::Extent3D {
|
||||
width: extent.width,
|
||||
height: extent.height,
|
||||
depth: 1,
|
||||
})
|
||||
.mip_levels(1)
|
||||
.array_layers(1)
|
||||
.format(depth_format)
|
||||
.tiling(vk::ImageTiling::OPTIMAL)
|
||||
.initial_layout(vk::ImageLayout::UNDEFINED)
|
||||
.usage(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT)
|
||||
.samples(vk::SampleCountFlags::TYPE_1)
|
||||
.sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||
|
||||
let handle = resource_manager.create_image(&image_create_info, MemoryLocation::GpuOnly)?;
|
||||
|
||||
// Get the vk::Image handle to create the view
|
||||
let image_info = resource_manager.get_image_info(handle)?;
|
||||
|
||||
let view_create_info = vk::ImageViewCreateInfo::default()
|
||||
.image(image_info.image)
|
||||
.view_type(vk::ImageViewType::TYPE_2D)
|
||||
.format(depth_format)
|
||||
.subresource_range(vk::ImageSubresourceRange {
|
||||
aspect_mask: vk::ImageAspectFlags::DEPTH,
|
||||
base_mip_level: 0,
|
||||
level_count: 1,
|
||||
base_array_layer: 0,
|
||||
layer_count: 1,
|
||||
});
|
||||
|
||||
let view = unsafe { device.raw().create_image_view(&view_create_info, None)? };
|
||||
|
||||
Ok((handle, view))
|
||||
}
|
||||
|
||||
// --- Helper: Create Triangle Pipeline ---
|
||||
fn create_triangle_pipeline(
|
||||
device: &Arc<Device>,
|
||||
color_format: vk::Format,
|
||||
depth_format: vk::Format,
|
||||
) -> Result<(vk::PipelineLayout, vk::Pipeline), RendererError> {
|
||||
// --- Shaders (Hardcoded example) ---
|
||||
// Vertex Shader (GLSL) - outputs clip space position based on vertex index
|
||||
/*
|
||||
#version 450
|
||||
vec2 positions[3] = vec2[](
|
||||
vec2(0.0, -0.5),
|
||||
vec2(0.5, 0.5),
|
||||
vec2(-0.5, 0.5)
|
||||
);
|
||||
void main() {
|
||||
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||
}
|
||||
*/
|
||||
// Fragment Shader (GLSL) - outputs solid orange
|
||||
/*
|
||||
#version 450
|
||||
layout(location = 0) out vec4 outColor;
|
||||
void main() {
|
||||
outColor = vec4(1.0, 0.5, 0.0, 1.0); // Orange
|
||||
}
|
||||
*/
|
||||
|
||||
// Load compiled SPIR-V (replace with actual loading)
|
||||
let vert_shader_code = include_bytes!(concat!(env!("OUT_DIR"), "/shaders/vert.glsl.spv")); // Placeholder path
|
||||
let frag_shader_code = include_bytes!(concat!(env!("OUT_DIR"), "/shaders/frag.glsl.spv")); // Placeholder path
|
||||
|
||||
let vert_module = Self::create_shader_module(device, vert_shader_code)?;
|
||||
let frag_module = Self::create_shader_module(device, frag_shader_code)?;
|
||||
|
||||
let main_function_name = CStr::from_bytes_with_nul(b"main\0").unwrap();
|
||||
|
||||
let vert_stage_info = vk::PipelineShaderStageCreateInfo::default()
|
||||
.stage(vk::ShaderStageFlags::VERTEX)
|
||||
.module(vert_module)
|
||||
.name(main_function_name);
|
||||
|
||||
let frag_stage_info = vk::PipelineShaderStageCreateInfo::default()
|
||||
.stage(vk::ShaderStageFlags::FRAGMENT)
|
||||
.module(frag_module)
|
||||
.name(main_function_name);
|
||||
|
||||
let shader_stages = [vert_stage_info, frag_stage_info];
|
||||
|
||||
// --- Fixed Function State ---
|
||||
let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default(); // No vertex buffers/attributes
|
||||
|
||||
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
|
||||
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
|
||||
.primitive_restart_enable(false);
|
||||
|
||||
let viewport_state = vk::PipelineViewportStateCreateInfo::default()
|
||||
.viewport_count(1) // Dynamic viewport
|
||||
.scissor_count(1); // Dynamic scissor
|
||||
|
||||
let rasterizer = vk::PipelineRasterizationStateCreateInfo::default()
|
||||
.depth_clamp_enable(false)
|
||||
.rasterizer_discard_enable(false)
|
||||
.polygon_mode(vk::PolygonMode::FILL)
|
||||
.line_width(1.0)
|
||||
.cull_mode(vk::CullModeFlags::NONE) // Draw front face
|
||||
.front_face(vk::FrontFace::CLOCKWISE) // Doesn't matter for hardcoded triangle
|
||||
.depth_bias_enable(false);
|
||||
|
||||
let multisampling = vk::PipelineMultisampleStateCreateInfo::default()
|
||||
.sample_shading_enable(false)
|
||||
.rasterization_samples(vk::SampleCountFlags::TYPE_1);
|
||||
|
||||
let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default()
|
||||
.depth_test_enable(true)
|
||||
.depth_write_enable(true)
|
||||
.depth_compare_op(vk::CompareOp::LESS)
|
||||
.depth_bounds_test_enable(false)
|
||||
.stencil_test_enable(false);
|
||||
|
||||
let color_blend_attachment = vk::PipelineColorBlendAttachmentState::default()
|
||||
.color_write_mask(vk::ColorComponentFlags::RGBA)
|
||||
.blend_enable(false); // No blending for opaque triangle
|
||||
|
||||
let color_blending = vk::PipelineColorBlendStateCreateInfo::default()
|
||||
.logic_op_enable(false)
|
||||
.attachments(std::slice::from_ref(&color_blend_attachment));
|
||||
|
||||
let dynamic_states = [vk::DynamicState::VIEWPORT, vk::DynamicState::SCISSOR];
|
||||
let dynamic_state =
|
||||
vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
|
||||
|
||||
// --- Pipeline Layout ---
|
||||
let layout_info = vk::PipelineLayoutCreateInfo::default(); // No descriptors/push constants
|
||||
let pipeline_layout = unsafe {
|
||||
device
|
||||
.raw()
|
||||
.create_pipeline_layout(&layout_info, None)
|
||||
.map_err(RendererError::PipelineLayoutCreation)?
|
||||
};
|
||||
|
||||
// --- Dynamic Rendering Info ---
|
||||
let mut pipeline_rendering_info = vk::PipelineRenderingCreateInfo::default()
|
||||
.color_attachment_formats(std::slice::from_ref(&color_format))
|
||||
.depth_attachment_format(depth_format);
|
||||
|
||||
// --- Graphics Pipeline ---
|
||||
let pipeline_info = vk::GraphicsPipelineCreateInfo::default()
|
||||
.stages(&shader_stages)
|
||||
.vertex_input_state(&vertex_input_info)
|
||||
.input_assembly_state(&input_assembly)
|
||||
.viewport_state(&viewport_state)
|
||||
.rasterization_state(&rasterizer)
|
||||
.multisample_state(&multisampling)
|
||||
.depth_stencil_state(&depth_stencil)
|
||||
.color_blend_state(&color_blending)
|
||||
.dynamic_state(&dynamic_state)
|
||||
.layout(pipeline_layout)
|
||||
// No render pass needed with dynamic rendering!
|
||||
.push_next(&mut pipeline_rendering_info); // Chain dynamic rendering info
|
||||
|
||||
let pipeline = unsafe {
|
||||
device
|
||||
.raw()
|
||||
.create_graphics_pipelines(vk::PipelineCache::null(), &[pipeline_info], None)
|
||||
.map_err(|(_, e)| RendererError::PipelineCreation(e))?[0] // Get the first pipeline from the result Vec
|
||||
};
|
||||
|
||||
// --- Cleanup Shader Modules ---
|
||||
unsafe {
|
||||
device.raw().destroy_shader_module(vert_module, None);
|
||||
device.raw().destroy_shader_module(frag_module, None);
|
||||
}
|
||||
|
||||
Ok((pipeline_layout, pipeline))
|
||||
}
|
||||
|
||||
fn create_shader_module(
|
||||
device: &Arc<Device>,
|
||||
code: &[u8],
|
||||
) -> Result<vk::ShaderModule, RendererError> {
|
||||
// 1. Check if byte count is a multiple of 4 (Vulkan requirement)
|
||||
let byte_count = code.len();
|
||||
if byte_count == 0 {
|
||||
// Handle empty shader code case if necessary, maybe return error
|
||||
return Err(RendererError::ShaderCreation(
|
||||
vk::Result::ERROR_INITIALIZATION_FAILED,
|
||||
)); // Or a custom error
|
||||
}
|
||||
if byte_count % 4 != 0 {
|
||||
// This indicates an invalid SPIR-V file was loaded.
|
||||
// Panicking here is reasonable during development, or return a specific error.
|
||||
error!(
|
||||
"Shader code size ({}) is not a multiple of 4 bytes! Check the .spv file generation.",
|
||||
byte_count
|
||||
);
|
||||
// You could return an error instead of panicking:
|
||||
return Err(RendererError::ShaderCreation(
|
||||
vk::Result::ERROR_INITIALIZATION_FAILED,
|
||||
)); // Or a custom error like InvalidShaderData
|
||||
// panic!(
|
||||
// "Shader code size ({}) is not a multiple of 4 bytes!",
|
||||
// byte_count
|
||||
// );
|
||||
}
|
||||
|
||||
// --- Alternative: Copying to Vec<u32> (Safest, but allocates/copies) ---
|
||||
let code_u32: Vec<u32> = code
|
||||
.chunks_exact(4)
|
||||
.map(|chunk| {
|
||||
u32::from_ne_bytes(chunk.try_into().expect("Chunk size is guaranteed to be 4"))
|
||||
}) // Use from_le_bytes if SPIR-V endianness matters (it's LE)
|
||||
.collect();
|
||||
let code_slice_ref = &code_u32; // Use this slice below
|
||||
// --------------------------------------------------------------------
|
||||
|
||||
// 3. Create the shader module
|
||||
let create_info = vk::ShaderModuleCreateInfo::default().code(&code_slice_ref); // Pass the &[u32] slice
|
||||
|
||||
unsafe {
|
||||
device
|
||||
.raw()
|
||||
.create_shader_module(&create_info, None)
|
||||
.map_err(|e| {
|
||||
error!("Failed to create shader module: {:?}", e); // Add logging
|
||||
RendererError::ShaderCreation(e)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helper: Create Frame Sync Objects & Command Resources ---
|
||||
fn create_frame_data(device: &Arc<Device>) -> Result<Vec<FrameData>, RendererError> {
|
||||
let mut frames_data = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT);
|
||||
for _ in 0..MAX_FRAMES_IN_FLIGHT {
|
||||
let image_available_semaphore = Semaphore::new(device.clone())?;
|
||||
let render_finished_semaphore = Semaphore::new(device.clone())?;
|
||||
let in_flight_fence = Fence::new(device.clone(), true)?; // Create signaled
|
||||
|
||||
// Create Command Pool
|
||||
let pool_info = vk::CommandPoolCreateInfo::default()
|
||||
.flags(
|
||||
vk::CommandPoolCreateFlags::TRANSIENT
|
||||
| vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER,
|
||||
) // Allow resetting individual buffers
|
||||
.queue_family_index(device.graphics_queue_family_index());
|
||||
let command_pool = unsafe {
|
||||
device
|
||||
.raw()
|
||||
.create_command_pool(&pool_info, None)
|
||||
.map_err(RendererError::CommandPoolCreation)?
|
||||
};
|
||||
|
||||
// Allocate Command Buffer
|
||||
let alloc_info = vk::CommandBufferAllocateInfo::default()
|
||||
.command_pool(command_pool)
|
||||
.level(vk::CommandBufferLevel::PRIMARY)
|
||||
.command_buffer_count(1);
|
||||
let command_buffer = unsafe {
|
||||
device
|
||||
.raw()
|
||||
.allocate_command_buffers(&alloc_info)
|
||||
.map_err(RendererError::CommandBufferAllocation)?[0]
|
||||
};
|
||||
|
||||
frames_data.push(FrameData {
|
||||
command_pool,
|
||||
command_buffer, // Stays allocated, just reset/rerecorded
|
||||
image_available_semaphore,
|
||||
render_finished_semaphore,
|
||||
in_flight_fence,
|
||||
});
|
||||
}
|
||||
Ok(frames_data)
|
||||
}
|
||||
|
||||
// --- Swapchain Support Helpers --- (Simplified versions)
|
||||
fn query_swapchain_support(
|
||||
physical_device: vk::PhysicalDevice,
|
||||
surface: &Arc<Surface>,
|
||||
) -> Result<SwapchainSupportDetails, GfxHalError> {
|
||||
unsafe {
|
||||
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)?;
|
||||
Ok(SwapchainSupportDetails {
|
||||
capabilities,
|
||||
formats,
|
||||
present_modes,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn choose_swapchain_format(available_formats: &[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR {
|
||||
available_formats
|
||||
.iter()
|
||||
.find(|format| {
|
||||
format.format == vk::Format::B8G8R8A8_SRGB // Prefer SRGB
|
||||
&& format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR
|
||||
})
|
||||
.unwrap_or(&available_formats[0]) // Fallback to first available
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn choose_swapchain_present_mode(available_modes: &[vk::PresentModeKHR]) -> vk::PresentModeKHR {
|
||||
available_modes
|
||||
.iter()
|
||||
.find(|&&mode| mode == vk::PresentModeKHR::MAILBOX) // Prefer Mailbox (low latency)
|
||||
.unwrap_or(&vk::PresentModeKHR::FIFO) // Guaranteed fallback
|
||||
.clone()
|
||||
}
|
||||
|
||||
fn choose_swapchain_extent(
|
||||
capabilities: &vk::SurfaceCapabilitiesKHR,
|
||||
window_width: u32,
|
||||
window_height: u32,
|
||||
) -> vk::Extent2D {
|
||||
if capabilities.current_extent.width != u32::MAX {
|
||||
// Window manager dictates extent
|
||||
capabilities.current_extent
|
||||
} else {
|
||||
// We can choose extent within bounds
|
||||
vk::Extent2D {
|
||||
width: window_width.clamp(
|
||||
capabilities.min_image_extent.width,
|
||||
capabilities.max_image_extent.width,
|
||||
),
|
||||
height: window_height.clamp(
|
||||
capabilities.min_image_extent.height,
|
||||
capabilities.max_image_extent.height,
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helper: Find Depth Format ---
|
||||
fn find_depth_format(
|
||||
instance: &Arc<gfx_hal::instance::Instance>,
|
||||
device: &Arc<Device>,
|
||||
) -> Result<vk::Format, RendererError> {
|
||||
let candidates = [
|
||||
vk::Format::D32_SFLOAT,
|
||||
vk::Format::D32_SFLOAT_S8_UINT,
|
||||
vk::Format::D24_UNORM_S8_UINT,
|
||||
];
|
||||
for &format in candidates.iter() {
|
||||
let props = unsafe {
|
||||
instance
|
||||
.ash_instance()
|
||||
.get_physical_device_format_properties(device.physical_device_handle(), format)
|
||||
};
|
||||
if props
|
||||
.optimal_tiling_features
|
||||
.contains(vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT)
|
||||
{
|
||||
return Ok(format);
|
||||
}
|
||||
}
|
||||
Err(RendererError::Vulkan(
|
||||
vk::Result::ERROR_FORMAT_NOT_SUPPORTED,
|
||||
)) // Or custom error
|
||||
}
|
||||
}
|
||||
|
||||
// --- Drop Implementation ---
|
||||
impl Drop for Renderer {
|
||||
fn drop(&mut self) {
|
||||
info!("Dropping Renderer...");
|
||||
// Ensure GPU is idle before destroying anything
|
||||
if let Err(e) = self.device.wait_idle() {
|
||||
error!("Error waiting for device idle during drop: {}", e);
|
||||
// Continue cleanup regardless
|
||||
}
|
||||
|
||||
// Cleanup swapchain resources (views, depth buffer, swapchain object)
|
||||
self.cleanup_swapchain_resources();
|
||||
|
||||
// Drop egui renderer explicitly before allocator/device go away
|
||||
// Assuming egui_renderer has a drop impl that cleans its Vulkan resources
|
||||
// std::mem::drop(self.egui_renderer); // Not needed if it implements Drop
|
||||
|
||||
// Destroy pipelines
|
||||
unsafe {
|
||||
self.device
|
||||
.raw()
|
||||
.destroy_pipeline(self.triangle_pipeline, None);
|
||||
self.device
|
||||
.raw()
|
||||
.destroy_pipeline_layout(self.triangle_pipeline_layout, None);
|
||||
}
|
||||
|
||||
// Destroy frame data (fences, semaphores, command pools)
|
||||
// Fences/Semaphores are handled by gfx_hal::Drop
|
||||
// Command buffers are freed with the pool
|
||||
for frame_data in self.frames_data.drain(..) {
|
||||
unsafe {
|
||||
self.device
|
||||
.raw()
|
||||
.destroy_command_pool(frame_data.command_pool, None);
|
||||
}
|
||||
}
|
||||
|
||||
// Arcs (device, queue, resource_manager, surface, allocator) will drop automatically.
|
||||
// ResourceManager's Drop impl should handle allocator destruction if needed.
|
||||
info!("Renderer dropped.");
|
||||
}
|
||||
}
|
||||
|
|
@ -164,6 +164,11 @@ impl ResourceManager {
|
|||
})
|
||||
}
|
||||
|
||||
/// Gets a shared reference to the Allocator
|
||||
pub fn allocator(&self) -> Arc<Mutex<Allocator>> {
|
||||
self.allocator.clone()
|
||||
}
|
||||
|
||||
/// Gets or initializes the TransferSetup resources.
|
||||
fn get_transfer_setup(&self) -> Result<TransferSetup> {
|
||||
let mut setup_guard = self.transfer_setup.lock();
|
||||
|
|
@ -254,7 +259,9 @@ impl ResourceManager {
|
|||
// 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
|
||||
transfer_setup
|
||||
.queue
|
||||
.submit(self.device.raw(), &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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue