impl: 3D Rendering
This commit is contained in:
parent
dbf9544e80
commit
70176bb86a
18 changed files with 862 additions and 185 deletions
102
Cargo.lock
generated
102
Cargo.lock
generated
|
|
@ -549,6 +549,72 @@ version = "1.1.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96a6ac251f4a2aca6b3f91340350eab87ae57c3f127ffeb585e92bd336717991"
|
||||
|
||||
[[package]]
|
||||
name = "darling"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"darling_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_core"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e"
|
||||
dependencies = [
|
||||
"fnv",
|
||||
"ident_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"strsim",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "darling_macro"
|
||||
version = "0.20.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead"
|
||||
dependencies = [
|
||||
"darling_core",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "507dfb09ea8b7fa618fcf76e953f4f5e192547945816d5358edffe39f6f94947"
|
||||
dependencies = [
|
||||
"derive_builder_macro",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_core"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d5bcf7b024d6835cfb3d473887cd966994907effbe9227e8c8219824d06c4e8"
|
||||
dependencies = [
|
||||
"darling",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_builder_macro"
|
||||
version = "0.20.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab63b0e2bf4d5928aff72e83a7dace85d7bba5fe12dcc3c5a572d78caffd3f3c"
|
||||
dependencies = [
|
||||
"derive_builder_core",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dispatch"
|
||||
version = "0.2.0"
|
||||
|
|
@ -664,9 +730,11 @@ dependencies = [
|
|||
"egui",
|
||||
"egui-winit",
|
||||
"gfx_hal",
|
||||
"glam",
|
||||
"raw-window-handle",
|
||||
"renderer",
|
||||
"resource_manager",
|
||||
"shared",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"tracing-subscriber",
|
||||
|
|
@ -748,6 +816,12 @@ dependencies = [
|
|||
"miniz_oxide 0.8.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.5.0"
|
||||
|
|
@ -811,6 +885,7 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"ash",
|
||||
"ash-window",
|
||||
"gpu-allocator",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"winit",
|
||||
|
|
@ -990,6 +1065,12 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ident_case"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
|
||||
|
||||
[[package]]
|
||||
name = "idna"
|
||||
version = "1.0.3"
|
||||
|
|
@ -1199,6 +1280,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.4"
|
||||
|
|
@ -1758,6 +1848,7 @@ dependencies = [
|
|||
"gpu-allocator",
|
||||
"resource_manager",
|
||||
"shaderc",
|
||||
"shared",
|
||||
"thiserror 2.0.12",
|
||||
"tracing",
|
||||
"walkdir",
|
||||
|
|
@ -1910,6 +2001,17 @@ dependencies = [
|
|||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shared"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"bytemuck",
|
||||
"derive_builder",
|
||||
"glam",
|
||||
"memoffset",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ members = [
|
|||
"crates/engine",
|
||||
"crates/gfx_hal",
|
||||
"crates/renderer",
|
||||
"crates/resource_manager",
|
||||
"crates/resource_manager", "crates/shared",
|
||||
]
|
||||
|
||||
[workspace.dependencies]
|
||||
|
|
|
|||
|
|
@ -13,9 +13,12 @@ tracing-subscriber.workspace = true
|
|||
winit.workspace = true
|
||||
raw-window-handle.workspace = true
|
||||
thiserror.workspace = true
|
||||
glam.workspace = true
|
||||
|
||||
gfx_hal = { path = "../gfx_hal" }
|
||||
renderer = { path = "../renderer" }
|
||||
resource_manager = { path = "../resource_manager" }
|
||||
shared = { path = "../shared" }
|
||||
|
||||
clap = { version = "4.5.34", features = ["derive"] }
|
||||
egui-winit = "0.31.1"
|
||||
|
|
|
|||
|
|
@ -8,15 +8,17 @@ use std::{
|
|||
|
||||
use ash::vk;
|
||||
use clap::Parser;
|
||||
use egui::{Context, ViewportId};
|
||||
use egui::{Context, Slider, ViewportId};
|
||||
use egui_winit::State;
|
||||
use gfx_hal::{
|
||||
device::Device, error::GfxHalError, instance::Instance, instance::InstanceConfig,
|
||||
physical_device::PhysicalDevice, queue::Queue, surface::Surface,
|
||||
};
|
||||
use glam::Vec3;
|
||||
use raw_window_handle::HasDisplayHandle;
|
||||
use renderer::{Renderer, RendererError};
|
||||
use resource_manager::{ResourceManager, ResourceManagerError};
|
||||
use resource_manager::{Geometry, ResourceManager, ResourceManagerError};
|
||||
use shared::{CameraInfo, Vertex};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing_subscriber::{filter, layer::SubscriberExt, util::SubscriberInitExt, Layer};
|
||||
use winit::{
|
||||
|
|
@ -25,6 +27,7 @@ use winit::{
|
|||
event_loop::{ActiveEventLoop, EventLoop},
|
||||
window::Window,
|
||||
};
|
||||
|
||||
// --- Configuration ---
|
||||
const APP_NAME: &str = "BeginDisregard";
|
||||
const ENGINE_NAME: &str = "Engine";
|
||||
|
|
@ -69,18 +72,33 @@ struct Application {
|
|||
frame_count: u32,
|
||||
last_fps_update_time: Instant,
|
||||
last_frame_time: Instant,
|
||||
current_fps: f64,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct EditorUI {}
|
||||
struct EditorUI {
|
||||
camera_info: CameraInfo,
|
||||
}
|
||||
|
||||
impl EditorUI {
|
||||
fn title() -> String {
|
||||
"engine".to_string()
|
||||
}
|
||||
|
||||
fn build_ui(&mut self, ctx: &egui::Context) {
|
||||
egui::Window::new(Self::title()).show(ctx, |ui| ui.label(Self::title()));
|
||||
fn build_ui(&mut self, ctx: &egui::Context, current_fps: f64) {
|
||||
egui::Window::new(Self::title()).show(ctx, |ui| {
|
||||
ui.label(format!("FPS - {:.2}", current_fps));
|
||||
|
||||
ui.separator();
|
||||
|
||||
egui::Grid::new("main_grid")
|
||||
.spacing([40.0, 4.0])
|
||||
.striped(true)
|
||||
.show(ui, |ui| {
|
||||
ui.label("FOV");
|
||||
ui.add(Slider::new(&mut self.camera_info.camera_fov, 10.0..=120.0));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -241,14 +259,61 @@ impl Application {
|
|||
|
||||
// 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();
|
||||
|
||||
// --- 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();
|
||||
let vertices = vec![
|
||||
// Define 8 vertices for a cube with positions and colors
|
||||
// Make sure winding order is correct (e.g., counter-clockwise for front faces)
|
||||
Vertex {
|
||||
pos: Vec3::new(-0.5, -0.5, 0.5).into(),
|
||||
color: Vec3::new(1.0, 0.0, 0.0).into(),
|
||||
}, // Front bottom left 0
|
||||
Vertex {
|
||||
pos: Vec3::new(0.5, -0.5, 0.5).into(),
|
||||
color: Vec3::new(0.0, 1.0, 0.0).into(),
|
||||
}, // Front bottom right 1
|
||||
Vertex {
|
||||
pos: Vec3::new(0.5, 0.5, 0.5).into(),
|
||||
color: Vec3::new(0.0, 0.0, 1.0).into(),
|
||||
}, // Front top right 2
|
||||
Vertex {
|
||||
pos: Vec3::new(-0.5, 0.5, 0.5).into(),
|
||||
color: Vec3::new(1.0, 1.0, 0.0).into(),
|
||||
}, // Front top left 3
|
||||
// ... add back face vertices (4-7) ...
|
||||
Vertex {
|
||||
pos: Vec3::new(-0.5, -0.5, -0.5).into(),
|
||||
color: Vec3::new(1.0, 0.0, 1.0).into(),
|
||||
}, // Back bottom left 4
|
||||
Vertex {
|
||||
pos: Vec3::new(0.5, -0.5, -0.5).into(),
|
||||
color: Vec3::new(0.0, 1.0, 1.0).into(),
|
||||
}, // Back bottom right 5
|
||||
Vertex {
|
||||
pos: Vec3::new(0.5, 0.5, -0.5).into(),
|
||||
color: Vec3::new(0.5, 0.5, 0.5).into(),
|
||||
}, // Back top right 6
|
||||
Vertex {
|
||||
pos: Vec3::new(-0.5, 0.5, -0.5).into(),
|
||||
color: Vec3::new(1.0, 1.0, 1.0).into(),
|
||||
}, // Back top left 7
|
||||
];
|
||||
|
||||
let indices = vec![
|
||||
// Define 12 triangles (36 indices) for the cube faces
|
||||
// Front face
|
||||
0, 1, 2, 2, 3, 0, // Right face
|
||||
1, 5, 6, 6, 2, 1, // Back face
|
||||
5, 4, 7, 7, 6, 5, // Left face
|
||||
4, 0, 3, 3, 7, 4, // Top face
|
||||
3, 2, 6, 6, 7, 3, // Bottom face
|
||||
4, 5, 1, 1, 0, 4,
|
||||
];
|
||||
|
||||
let cube_geometry = Geometry::new(resource_manager.clone(), &vertices, &indices)?;
|
||||
|
||||
// --- 5. Renderer ---
|
||||
let initial_size = window.inner_size();
|
||||
|
|
@ -258,6 +323,7 @@ impl Application {
|
|||
graphics_queue.clone(),
|
||||
surface.clone(),
|
||||
resource_manager.clone(),
|
||||
vec![cube_geometry],
|
||||
initial_size.width,
|
||||
initial_size.height,
|
||||
)?;
|
||||
|
|
@ -288,6 +354,7 @@ impl Application {
|
|||
egui_ctx,
|
||||
egui_app,
|
||||
frame_count: 0,
|
||||
current_fps: 0.,
|
||||
last_fps_update_time: Instant::now(),
|
||||
last_frame_time: Instant::now(),
|
||||
})
|
||||
|
|
@ -332,6 +399,7 @@ impl Application {
|
|||
|
||||
if elapsed_sice_last_update >= Duration::from_secs(1) {
|
||||
let fps = self.frame_count as f64 / elapsed_sice_last_update.as_secs_f64();
|
||||
self.current_fps = fps;
|
||||
|
||||
let new_title = format!("{} - {} - {:.0} FPS", ENGINE_NAME, APP_NAME, fps);
|
||||
self.window.set_title(&new_title);
|
||||
|
|
@ -349,7 +417,7 @@ impl Application {
|
|||
pixels_per_point,
|
||||
..
|
||||
} = self.egui_ctx.run(raw_input, |ctx| {
|
||||
self.egui_app.build_ui(ctx);
|
||||
self.egui_app.build_ui(ctx, self.current_fps);
|
||||
});
|
||||
|
||||
self.renderer.update_textures(textures_delta).unwrap();
|
||||
|
|
@ -360,10 +428,11 @@ impl Application {
|
|||
let clipped_primitives = self.egui_ctx.tessellate(shapes, pixels_per_point);
|
||||
|
||||
// --- Render Frame ---
|
||||
match self
|
||||
.renderer
|
||||
.render_frame(pixels_per_point, &clipped_primitives)
|
||||
{
|
||||
match self.renderer.render_frame(
|
||||
pixels_per_point,
|
||||
&clipped_primitives,
|
||||
self.egui_app.camera_info,
|
||||
) {
|
||||
Ok(_) => {
|
||||
self.window.request_redraw();
|
||||
}
|
||||
|
|
@ -499,7 +568,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
.with_ansi(true)
|
||||
.with_file(false)
|
||||
.with_line_number(false)
|
||||
.with_filter(filter::LevelFilter::DEBUG);
|
||||
.with_filter(filter::LevelFilter::INFO);
|
||||
|
||||
let registry = tracing_subscriber::registry().with(fmt_layer);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,3 +9,4 @@ ash-window.workspace = true
|
|||
thiserror.workspace = true
|
||||
tracing.workspace = true
|
||||
winit.workspace = true
|
||||
gpu-allocator.workspace = true
|
||||
|
|
|
|||
|
|
@ -63,6 +63,10 @@ pub enum GfxHalError {
|
|||
/// Placeholder for other specific errors.
|
||||
#[error("An unexpected error occurred: {0}")]
|
||||
Other(String),
|
||||
|
||||
/// Size for Buffer is invalid.
|
||||
#[error("Buffer size is invalid.")]
|
||||
BufferSizeInvalid,
|
||||
}
|
||||
|
||||
pub type Result<T, E = GfxHalError> = std::result::Result<T, E>;
|
||||
|
|
|
|||
|
|
@ -6,3 +6,12 @@ pub mod queue;
|
|||
pub mod surface;
|
||||
pub mod swapchain;
|
||||
pub mod sync;
|
||||
|
||||
pub use device::*;
|
||||
pub use error::*;
|
||||
pub use instance::*;
|
||||
pub use physical_device::*;
|
||||
pub use queue::*;
|
||||
pub use surface::*;
|
||||
pub use swapchain::*;
|
||||
pub use sync::*;
|
||||
|
|
|
|||
|
|
@ -65,18 +65,6 @@ impl Queue {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use crate::{
|
|||
/// Wraps a `vk::Fence`, used for CPU-GPU synchronization.
|
||||
///
|
||||
/// Owns the `vk::Fence` handle.
|
||||
#[derive(Clone)]
|
||||
pub struct Fence {
|
||||
device: Arc<Device>,
|
||||
fence: vk::Fence,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ winit.workspace = true
|
|||
|
||||
gfx_hal = { path = "../gfx_hal" }
|
||||
resource_manager = { path = "../resource_manager" }
|
||||
shared = { path = "../shared" }
|
||||
|
||||
[build-dependencies]
|
||||
shaderc = "0.9.1"
|
||||
|
|
|
|||
|
|
@ -16,6 +16,8 @@ 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")?;
|
||||
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
|
||||
let compiler = Compiler::new().context("Failed to create shader compiler")?;
|
||||
let mut options = CompileOptions::new().context("Failed to create compile options")?;
|
||||
|
||||
|
|
@ -49,6 +51,7 @@ fn main() -> Result<()> {
|
|||
.filter(|e| e.file_type().is_file())
|
||||
// Only process files
|
||||
{
|
||||
println!("cargo:rerun-if-changed={:?}", entry.path());
|
||||
let in_path = entry.path();
|
||||
|
||||
// Determine shader kind from extension
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
use std::{
|
||||
ffi::CStr,
|
||||
ffi::{c_void, CStr},
|
||||
mem,
|
||||
sync::{Arc, Mutex},
|
||||
time::Instant,
|
||||
};
|
||||
|
||||
use ash::vk;
|
||||
|
|
@ -10,14 +12,15 @@ 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 resource_manager::{ImageHandle, ResourceManager, ResourceManagerError};
|
||||
use glam::{Mat4, Vec3};
|
||||
use gpu_allocator::{
|
||||
vulkan::{Allocation, AllocationCreateDesc, Allocator},
|
||||
MemoryLocation,
|
||||
};
|
||||
use resource_manager::{Geometry, ImageHandle, ResourceManager, ResourceManagerError};
|
||||
use shared::{CameraInfo, UniformBufferObject};
|
||||
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;
|
||||
|
||||
|
|
@ -55,6 +58,14 @@ pub enum RendererError {
|
|||
ImageInfoUnavailable,
|
||||
#[error("Failed to get allocator from resource manager")]
|
||||
AllocatorUnavailable, // Added based on egui requirement
|
||||
#[error("Allocator Error: {0}")]
|
||||
AllocatorError(#[from] gpu_allocator::AllocationError),
|
||||
}
|
||||
|
||||
impl<T> From<std::sync::PoisonError<T>> for RendererError {
|
||||
fn from(_: std::sync::PoisonError<T>) -> Self {
|
||||
Self::AllocatorUnavailable
|
||||
}
|
||||
}
|
||||
|
||||
struct FrameData {
|
||||
|
|
@ -64,6 +75,12 @@ struct FrameData {
|
|||
render_finished_semaphore: Semaphore,
|
||||
textures_to_free: Option<Vec<TextureId>>,
|
||||
in_flight_fence: Fence,
|
||||
|
||||
descriptor_set: vk::DescriptorSet,
|
||||
uniform_buffer_object: UniformBufferObject,
|
||||
uniform_buffer: vk::Buffer,
|
||||
uniform_buffer_allocation: Allocation,
|
||||
uniform_buffer_mapped_ptr: *mut c_void,
|
||||
}
|
||||
|
||||
struct SwapchainSupportDetails {
|
||||
|
|
@ -84,14 +101,19 @@ pub struct Renderer {
|
|||
swapchain_format: vk::SurfaceFormatKHR,
|
||||
swapchain_extent: vk::Extent2D,
|
||||
|
||||
scene: Vec<Geometry>,
|
||||
|
||||
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||
descriptor_pool: vk::DescriptorPool,
|
||||
|
||||
egui_renderer: EguiRenderer,
|
||||
|
||||
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,
|
||||
model_pipeline_layout: vk::PipelineLayout,
|
||||
model_pipeline: vk::Pipeline,
|
||||
|
||||
frames_data: Vec<FrameData>,
|
||||
current_frame: usize,
|
||||
|
|
@ -100,6 +122,8 @@ pub struct Renderer {
|
|||
window_resized: bool,
|
||||
current_width: u32,
|
||||
current_height: u32,
|
||||
|
||||
start_time: Instant,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
|
|
@ -109,6 +133,7 @@ impl Renderer {
|
|||
graphics_queue: Arc<Queue>,
|
||||
surface: Arc<Surface>,
|
||||
resource_manager: Arc<ResourceManager>,
|
||||
scene: Vec<Geometry>,
|
||||
initial_width: u32,
|
||||
initial_height: u32,
|
||||
) -> Result<Self, RendererError> {
|
||||
|
|
@ -128,10 +153,26 @@ impl Renderer {
|
|||
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 (descriptor_set_layout, descriptor_pool) =
|
||||
Self::create_descriptor_sets_resources(&device)?;
|
||||
|
||||
let frames_data = Self::create_frame_data(&device)?;
|
||||
let (model_pipeline_layout, model_pipeline) = Self::create_model_pipeline(
|
||||
&device,
|
||||
format.format,
|
||||
depth_format,
|
||||
descriptor_set_layout,
|
||||
)?;
|
||||
|
||||
let start_time = Instant::now();
|
||||
|
||||
let frames_data = Self::create_frame_data(
|
||||
&device,
|
||||
&resource_manager,
|
||||
descriptor_pool,
|
||||
descriptor_set_layout,
|
||||
swapchain.extent(),
|
||||
start_time,
|
||||
)?;
|
||||
|
||||
info!("Renderer initialized successfully.");
|
||||
|
||||
|
|
@ -144,6 +185,7 @@ impl Renderer {
|
|||
},
|
||||
Options {
|
||||
srgb_framebuffer: true,
|
||||
in_flight_frames: MAX_FRAMES_IN_FLIGHT,
|
||||
..Default::default()
|
||||
},
|
||||
)?;
|
||||
|
|
@ -159,16 +201,21 @@ impl Renderer {
|
|||
swapchain_image_views: image_views,
|
||||
swapchain_format: format,
|
||||
swapchain_extent: extent,
|
||||
descriptor_set_layout,
|
||||
descriptor_pool,
|
||||
depth_image_handle,
|
||||
depth_image_view,
|
||||
depth_format,
|
||||
triangle_pipeline_layout,
|
||||
triangle_pipeline,
|
||||
model_pipeline_layout,
|
||||
model_pipeline,
|
||||
frames_data,
|
||||
scene,
|
||||
current_frame: 0,
|
||||
window_resized: false,
|
||||
current_width: initial_width,
|
||||
current_height: initial_height,
|
||||
|
||||
start_time,
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -208,6 +255,7 @@ impl Renderer {
|
|||
&mut self,
|
||||
pixels_per_point: f32,
|
||||
clipped_primitives: &[ClippedPrimitive],
|
||||
camera_info: CameraInfo,
|
||||
) -> Result<(), RendererError> {
|
||||
// --- Handle Resize ---
|
||||
if self.window_resized {
|
||||
|
|
@ -229,6 +277,7 @@ impl Renderer {
|
|||
.swapchain
|
||||
.as_ref()
|
||||
.ok_or(RendererError::SwapchainAcquisitionFailed)?;
|
||||
|
||||
let (image_index, suboptimal) = unsafe {
|
||||
// Need unsafe block for acquire_next_image
|
||||
swapchain_ref.acquire_next_image(
|
||||
|
|
@ -250,7 +299,6 @@ impl Renderer {
|
|||
frame_data.in_flight_fence.reset()?;
|
||||
|
||||
if let Some(textures) = frame_data.textures_to_free.take() {
|
||||
tracing::debug!("Freeing EGUI Textures");
|
||||
self.egui_renderer.free_textures(&textures)?;
|
||||
}
|
||||
|
||||
|
|
@ -266,6 +314,15 @@ impl Renderer {
|
|||
let cmd_begin_info = vk::CommandBufferBeginInfo::default()
|
||||
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
|
||||
|
||||
// -- Update uniform buffer --
|
||||
self.update_uniform_buffer(camera_info)?;
|
||||
|
||||
let frame_data = &mut self.frames_data[self.current_frame];
|
||||
let swapchain_ref = self
|
||||
.swapchain
|
||||
.as_ref()
|
||||
.ok_or(RendererError::SwapchainAcquisitionFailed)?;
|
||||
|
||||
unsafe {
|
||||
// Need unsafe for Vulkan commands
|
||||
self.device
|
||||
|
|
@ -373,10 +430,21 @@ impl Renderer {
|
|||
self.device.raw().cmd_bind_pipeline(
|
||||
command_buffer,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
self.triangle_pipeline,
|
||||
self.model_pipeline,
|
||||
);
|
||||
// Draw 3 vertices, 1 instance, 0 first vertex, 0 first instance
|
||||
self.device.raw().cmd_draw(command_buffer, 3, 1, 0, 0);
|
||||
|
||||
self.device.raw().cmd_bind_descriptor_sets(
|
||||
command_buffer,
|
||||
vk::PipelineBindPoint::GRAPHICS,
|
||||
self.model_pipeline_layout,
|
||||
0,
|
||||
&[frame_data.descriptor_set],
|
||||
&[],
|
||||
);
|
||||
}
|
||||
|
||||
for g in &self.scene {
|
||||
g.draw(self.device.raw(), command_buffer)?;
|
||||
}
|
||||
|
||||
tracing::trace!("Rendering EGUI");
|
||||
|
|
@ -682,33 +750,12 @@ impl Renderer {
|
|||
}
|
||||
|
||||
// --- Helper: Create Triangle Pipeline ---
|
||||
fn create_triangle_pipeline(
|
||||
fn create_model_pipeline(
|
||||
device: &Arc<Device>,
|
||||
color_format: vk::Format,
|
||||
depth_format: vk::Format,
|
||||
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||
) -> 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
|
||||
|
|
@ -730,8 +777,13 @@ impl Renderer {
|
|||
|
||||
let shader_stages = [vert_stage_info, frag_stage_info];
|
||||
|
||||
let binding_description = shared::Vertex::get_binding_decription();
|
||||
let attribute_descriptions = shared::Vertex::get_attribute_descriptions();
|
||||
|
||||
// --- Fixed Function State ---
|
||||
let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default(); // No vertex buffers/attributes
|
||||
let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default()
|
||||
.vertex_binding_descriptions(std::slice::from_ref(&binding_description))
|
||||
.vertex_attribute_descriptions(&attribute_descriptions);
|
||||
|
||||
let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default()
|
||||
.topology(vk::PrimitiveTopology::TRIANGLE_LIST)
|
||||
|
|
@ -774,7 +826,8 @@ impl Renderer {
|
|||
vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&dynamic_states);
|
||||
|
||||
// --- Pipeline Layout ---
|
||||
let layout_info = vk::PipelineLayoutCreateInfo::default(); // No descriptors/push constants
|
||||
let layout_info = vk::PipelineLayoutCreateInfo::default()
|
||||
.set_layouts(std::slice::from_ref(&descriptor_set_layout)); // No descriptors/push constants
|
||||
let pipeline_layout = unsafe {
|
||||
device
|
||||
.raw()
|
||||
|
|
@ -872,7 +925,14 @@ impl Renderer {
|
|||
}
|
||||
|
||||
// --- Helper: Create Frame Sync Objects & Command Resources ---
|
||||
fn create_frame_data(device: &Arc<Device>) -> Result<Vec<FrameData>, RendererError> {
|
||||
fn create_frame_data(
|
||||
device: &Arc<Device>,
|
||||
resource_manager: &Arc<ResourceManager>,
|
||||
descriptor_pool: vk::DescriptorPool,
|
||||
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||
swapchain_extent: vk::Extent2D,
|
||||
instant: Instant,
|
||||
) -> 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())?;
|
||||
|
|
@ -905,6 +965,19 @@ impl Renderer {
|
|||
.map_err(RendererError::CommandBufferAllocation)?[0]
|
||||
};
|
||||
|
||||
tracing::info!("Allocated frame_data command_buffer: {:?}", command_buffer);
|
||||
|
||||
let descriptor_set =
|
||||
Self::create_descriptor_set(device, descriptor_set_layout, descriptor_pool)?;
|
||||
|
||||
let (uniform_buffer, uniform_buffer_allocation, uniform_buffer_mapped_ptr) =
|
||||
Self::create_uniform_buffer(device, resource_manager)?;
|
||||
|
||||
Self::update_descriptor_set(device.clone(), descriptor_set, uniform_buffer);
|
||||
|
||||
let uniform_buffer_object =
|
||||
calculate_ubo(CameraInfo::default(), swapchain_extent, instant);
|
||||
|
||||
frames_data.push(FrameData {
|
||||
textures_to_free: None,
|
||||
command_pool,
|
||||
|
|
@ -912,6 +985,11 @@ impl Renderer {
|
|||
image_available_semaphore,
|
||||
render_finished_semaphore,
|
||||
in_flight_fence,
|
||||
descriptor_set,
|
||||
uniform_buffer,
|
||||
uniform_buffer_allocation,
|
||||
uniform_buffer_mapped_ptr,
|
||||
uniform_buffer_object,
|
||||
});
|
||||
}
|
||||
Ok(frames_data)
|
||||
|
|
@ -936,22 +1014,20 @@ impl Renderer {
|
|||
}
|
||||
|
||||
fn choose_swapchain_format(available_formats: &[vk::SurfaceFormatKHR]) -> vk::SurfaceFormatKHR {
|
||||
available_formats
|
||||
*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()
|
||||
.unwrap_or(&available_formats[0])
|
||||
}
|
||||
|
||||
fn choose_swapchain_present_mode(available_modes: &[vk::PresentModeKHR]) -> vk::PresentModeKHR {
|
||||
available_modes
|
||||
*available_modes
|
||||
.iter()
|
||||
.find(|&&mode| mode == vk::PresentModeKHR::MAILBOX) // Prefer Mailbox (low latency)
|
||||
.unwrap_or(&vk::PresentModeKHR::FIFO) // Guaranteed fallback
|
||||
.clone()
|
||||
.unwrap_or(&vk::PresentModeKHR::FIFO)
|
||||
}
|
||||
|
||||
fn choose_swapchain_extent(
|
||||
|
|
@ -1004,6 +1080,166 @@ impl Renderer {
|
|||
vk::Result::ERROR_FORMAT_NOT_SUPPORTED,
|
||||
)) // Or custom error
|
||||
}
|
||||
|
||||
fn create_descriptor_sets_resources(
|
||||
device: &Arc<Device>,
|
||||
) -> Result<(vk::DescriptorSetLayout, vk::DescriptorPool), RendererError> {
|
||||
let ubo_layout_binding = vk::DescriptorSetLayoutBinding::default()
|
||||
.binding(0)
|
||||
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
|
||||
.descriptor_count(1)
|
||||
.stage_flags(vk::ShaderStageFlags::VERTEX);
|
||||
|
||||
let layout_info = vk::DescriptorSetLayoutCreateInfo::default()
|
||||
.bindings(std::slice::from_ref(&ubo_layout_binding));
|
||||
|
||||
let descriptor_set_layout = unsafe {
|
||||
device
|
||||
.raw()
|
||||
.create_descriptor_set_layout(&layout_info, None)?
|
||||
};
|
||||
|
||||
let pool_size = vk::DescriptorPoolSize {
|
||||
ty: vk::DescriptorType::UNIFORM_BUFFER,
|
||||
descriptor_count: MAX_FRAMES_IN_FLIGHT as u32,
|
||||
};
|
||||
|
||||
let pool_info = vk::DescriptorPoolCreateInfo::default()
|
||||
.pool_sizes(std::slice::from_ref(&pool_size))
|
||||
.max_sets(MAX_FRAMES_IN_FLIGHT as u32);
|
||||
|
||||
let descriptor_pool = unsafe { device.raw().create_descriptor_pool(&pool_info, None)? };
|
||||
|
||||
Ok((descriptor_set_layout, descriptor_pool))
|
||||
}
|
||||
|
||||
fn create_descriptor_set(
|
||||
device: &Arc<Device>,
|
||||
descriptor_set_layout: vk::DescriptorSetLayout,
|
||||
descriptor_pool: vk::DescriptorPool,
|
||||
) -> Result<vk::DescriptorSet, RendererError> {
|
||||
let layouts = vec![descriptor_set_layout; 1];
|
||||
let alloc_info = vk::DescriptorSetAllocateInfo::default()
|
||||
.descriptor_pool(descriptor_pool)
|
||||
.set_layouts(&layouts);
|
||||
|
||||
let descriptor_set = unsafe { device.raw().allocate_descriptor_sets(&alloc_info)? }[0];
|
||||
|
||||
Ok(descriptor_set)
|
||||
}
|
||||
|
||||
fn create_uniform_buffer(
|
||||
device: &Arc<Device>,
|
||||
resource_manager: &Arc<ResourceManager>,
|
||||
) -> Result<(vk::Buffer, Allocation, *mut std::ffi::c_void), RendererError> {
|
||||
let buffer_size = mem::size_of::<UniformBufferObject>() as vk::DeviceSize;
|
||||
|
||||
let buffer_info = vk::BufferCreateInfo::default()
|
||||
.size(buffer_size)
|
||||
.usage(vk::BufferUsageFlags::UNIFORM_BUFFER)
|
||||
.sharing_mode(vk::SharingMode::EXCLUSIVE);
|
||||
|
||||
let allocation = resource_manager
|
||||
.allocator()
|
||||
.lock()?
|
||||
.allocate(&AllocationCreateDesc {
|
||||
name: "Uniform Buffer",
|
||||
requirements: unsafe {
|
||||
{
|
||||
let temp_buffer = device.raw().create_buffer(&buffer_info, None)?;
|
||||
let req = device.raw().get_buffer_memory_requirements(temp_buffer);
|
||||
|
||||
device.raw().destroy_buffer(temp_buffer, None);
|
||||
req
|
||||
}
|
||||
},
|
||||
location: MemoryLocation::CpuToGpu,
|
||||
linear: true,
|
||||
allocation_scheme: gpu_allocator::vulkan::AllocationScheme::GpuAllocatorManaged,
|
||||
})?;
|
||||
|
||||
let buffer = unsafe { device.raw().create_buffer(&buffer_info, None)? };
|
||||
tracing::info!("Created uniform buffer {:?}", buffer);
|
||||
|
||||
unsafe {
|
||||
device
|
||||
.raw()
|
||||
.bind_buffer_memory(buffer, allocation.memory(), allocation.offset())?;
|
||||
}
|
||||
|
||||
let mapped_ptr = allocation
|
||||
.mapped_ptr()
|
||||
.ok_or_else(|| {
|
||||
error!("Failed to get mapped pointer for CPU->GPU uniform buffer");
|
||||
ResourceManagerError::Other("Failed to map uniform buffer".to_string())
|
||||
})?
|
||||
.as_ptr();
|
||||
|
||||
Ok((buffer, allocation, mapped_ptr))
|
||||
}
|
||||
|
||||
fn update_descriptor_set(
|
||||
device: Arc<Device>,
|
||||
descriptor_set: vk::DescriptorSet,
|
||||
buffer: vk::Buffer,
|
||||
) {
|
||||
let buffer_info = vk::DescriptorBufferInfo::default()
|
||||
.buffer(buffer)
|
||||
.offset(0)
|
||||
.range(mem::size_of::<UniformBufferObject>() as vk::DeviceSize);
|
||||
|
||||
let descriptor_write = vk::WriteDescriptorSet::default()
|
||||
.dst_set(descriptor_set)
|
||||
.dst_binding(0)
|
||||
.dst_array_element(0)
|
||||
.descriptor_type(vk::DescriptorType::UNIFORM_BUFFER)
|
||||
.buffer_info(std::slice::from_ref(&buffer_info));
|
||||
|
||||
unsafe {
|
||||
device
|
||||
.raw()
|
||||
.update_descriptor_sets(std::slice::from_ref(&descriptor_write), &[]);
|
||||
}
|
||||
}
|
||||
|
||||
fn update_uniform_buffer(&mut self, camera_info: CameraInfo) -> Result<(), RendererError> {
|
||||
let frame_data = &mut self.frames_data[self.current_frame];
|
||||
|
||||
let ubo = calculate_ubo(camera_info, self.swapchain_extent, self.start_time);
|
||||
|
||||
if frame_data.uniform_buffer_object != ubo {
|
||||
let ptr = frame_data.uniform_buffer_mapped_ptr;
|
||||
unsafe {
|
||||
let aligned_ptr = ptr as *mut UniformBufferObject;
|
||||
aligned_ptr.write(ubo);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_ubo(
|
||||
camera_info: CameraInfo,
|
||||
swapchain_extent: vk::Extent2D,
|
||||
start: Instant,
|
||||
) -> UniformBufferObject {
|
||||
let time = start.elapsed();
|
||||
|
||||
let model = Mat4::from_rotation_y(time.as_secs_f32());
|
||||
|
||||
let view = Mat4::look_at_rh(camera_info.camera_pos, camera_info.camera_target, Vec3::Y);
|
||||
|
||||
let mut proj = Mat4::perspective_rh(
|
||||
camera_info.camera_fov.to_radians(),
|
||||
swapchain_extent.width as f32 / swapchain_extent.height as f32,
|
||||
0.1,
|
||||
1000.0,
|
||||
);
|
||||
|
||||
proj.y_axis.y *= -1.0;
|
||||
|
||||
UniformBufferObject { model, view, proj }
|
||||
}
|
||||
|
||||
// --- Drop Implementation ---
|
||||
|
|
@ -1027,16 +1263,39 @@ impl Drop for Renderer {
|
|||
unsafe {
|
||||
self.device
|
||||
.raw()
|
||||
.destroy_pipeline(self.triangle_pipeline, None);
|
||||
.destroy_pipeline(self.model_pipeline, None);
|
||||
self.device
|
||||
.raw()
|
||||
.destroy_pipeline_layout(self.triangle_pipeline_layout, None);
|
||||
.destroy_pipeline_layout(self.model_pipeline_layout, None);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
self.device
|
||||
.raw()
|
||||
.destroy_descriptor_pool(self.descriptor_pool, None);
|
||||
self.device
|
||||
.raw()
|
||||
.destroy_descriptor_set_layout(self.descriptor_set_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_buffer(frame_data.uniform_buffer, None);
|
||||
|
||||
let mut allocator = self
|
||||
.allocator
|
||||
.lock()
|
||||
.expect("Allocator Mutex to not be poisoned.");
|
||||
allocator
|
||||
.free(frame_data.uniform_buffer_allocation)
|
||||
.expect("Allocator to be able to free an allocation");
|
||||
}
|
||||
|
||||
unsafe {
|
||||
self.device
|
||||
.raw()
|
||||
|
|
|
|||
174
crates/resource_manager/src/geo.rs
Normal file
174
crates/resource_manager/src/geo.rs
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use ash::vk::{self, IndexType};
|
||||
use gpu_allocator::MemoryLocation;
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use crate::{BufferHandle, ResourceManager, ResourceManagerError, Result};
|
||||
|
||||
// Helper to safely get a byte slice from structured data
|
||||
unsafe fn as_byte_slice<T: Sized>(data: &[T]) -> &[u8] {
|
||||
std::slice::from_raw_parts(data.as_ptr() as *const u8, std::mem::size_of_val(data))
|
||||
}
|
||||
|
||||
/// Represents geometry data (verticies and indicies) stored in GPU buffers managed by
|
||||
/// ResourceManager. Handles automatic cleanup via a `Drop` implementation.
|
||||
pub struct Geometry {
|
||||
resource_manager: Arc<ResourceManager>,
|
||||
pub vertex_buffer: BufferHandle,
|
||||
pub index_buffer: BufferHandle,
|
||||
pub index_count: u32,
|
||||
}
|
||||
|
||||
impl Geometry {
|
||||
/// Creates new GPU buffers for the given vetex and index data using `ResourceManager`.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `resource_manager` - An Arc reference to the ResourceManager.
|
||||
/// * `vertices` - A slice of vertex data.
|
||||
/// * `indices` - A slice of index data (u32)
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns a new `ResourceManagerError` if buffer creation or data upload fails.
|
||||
pub fn new<V: Sized + Copy>(
|
||||
resource_manager: Arc<ResourceManager>,
|
||||
vertices: &[V],
|
||||
indicies: &[u32],
|
||||
) -> Result<Self> {
|
||||
trace!(
|
||||
"Creating Geometry: {} vertices, {} indicies",
|
||||
vertices.len(),
|
||||
indicies.len()
|
||||
);
|
||||
|
||||
if vertices.is_empty() || indicies.is_empty() {
|
||||
return Err(ResourceManagerError::Other(
|
||||
"Cannot create Geometry with empty vertices or indicies.".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let vertex_buffer = resource_manager.create_buffer_init(
|
||||
vk::BufferUsageFlags::VERTEX_BUFFER,
|
||||
MemoryLocation::GpuOnly,
|
||||
unsafe { as_byte_slice(vertices) },
|
||||
)?;
|
||||
trace!("Vertex buffer created: handle={:?}", vertex_buffer);
|
||||
|
||||
let index_buffer = resource_manager.create_buffer_init(
|
||||
vk::BufferUsageFlags::INDEX_BUFFER,
|
||||
MemoryLocation::GpuOnly,
|
||||
unsafe { as_byte_slice(indicies) },
|
||||
)?;
|
||||
trace!("Index buffer created: handle={:?}", index_buffer);
|
||||
|
||||
let index_count = indicies.len() as u32;
|
||||
|
||||
debug!(
|
||||
"Geometry created successfully: VB={:?}, IB={:?}, Indices={}",
|
||||
vertex_buffer, index_buffer, index_count
|
||||
);
|
||||
|
||||
Ok(Self {
|
||||
resource_manager,
|
||||
vertex_buffer,
|
||||
index_buffer,
|
||||
index_count,
|
||||
// vertex_count,
|
||||
})
|
||||
}
|
||||
|
||||
/// Binds the vertex and index buffers for drawing.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `device` - Raw `ash::Device` handle.
|
||||
/// * `command_buffer` - The command buffer to record binding commands into.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `ResourceManagerError` if buffer info cannot be retrieved.
|
||||
pub fn bind(&self, device: &ash::Device, command_buffer: vk::CommandBuffer) -> Result<()> {
|
||||
trace!(
|
||||
"Binding geometry: VB={:?}, IB={:?}",
|
||||
self.vertex_buffer,
|
||||
self.index_buffer
|
||||
);
|
||||
// Get buffer info (locks resource manager map briefly)
|
||||
let vb_info = self.resource_manager.get_buffer_info(self.vertex_buffer)?;
|
||||
let ib_info = self.resource_manager.get_buffer_info(self.index_buffer)?;
|
||||
|
||||
let vk_vertex_buffers = [vb_info.buffer];
|
||||
let offsets = [0_u64]; // Use vk::DeviceSize (u64)
|
||||
|
||||
unsafe {
|
||||
device.cmd_bind_vertex_buffers(
|
||||
command_buffer,
|
||||
0, // binding = 0
|
||||
&vk_vertex_buffers,
|
||||
&offsets,
|
||||
);
|
||||
device.cmd_bind_index_buffer(
|
||||
command_buffer,
|
||||
ib_info.buffer,
|
||||
0, // offset = 0
|
||||
vk::IndexType::UINT32,
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Binds the geometry buffers and issues an indexed draw command.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `device` - Raw `ash::Device` handle.
|
||||
/// * `command_buffer` - The command buffer to record commands into.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns `ResourceManagerError` if binding fails.
|
||||
pub fn draw(&self, device: &ash::Device, command_buffer: vk::CommandBuffer) -> Result<()> {
|
||||
self.bind(device, command_buffer)?; // Bind first
|
||||
trace!("Drawing geometry: {} indices", self.index_count);
|
||||
unsafe {
|
||||
device.cmd_draw_indexed(
|
||||
command_buffer,
|
||||
self.index_count, // Use stored index count
|
||||
1, // instance_count
|
||||
0, // first_index
|
||||
0, // vertex_offset
|
||||
0, // first_instance
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Geometry {
|
||||
fn drop(&mut self) {
|
||||
debug!(
|
||||
"Dropping Geometry: VB={:?}, IB={:?}",
|
||||
self.vertex_buffer, self.index_buffer
|
||||
);
|
||||
// Request destruction from the resource manager.
|
||||
// Ignore errors during drop, but log them.
|
||||
if let Err(e) = self.resource_manager.destroy_buffer(self.vertex_buffer) {
|
||||
tracing::error!(
|
||||
"Failed to destroy vertex buffer {:?} during Geometry drop: {}",
|
||||
self.vertex_buffer,
|
||||
e
|
||||
);
|
||||
}
|
||||
|
||||
if let Err(e) = self.resource_manager.destroy_buffer(self.index_buffer) {
|
||||
tracing::error!(
|
||||
"Failed to destroy index buffer {:?} during Geometry drop: {}",
|
||||
self.index_buffer,
|
||||
e
|
||||
);
|
||||
}
|
||||
// The Arc<ResourceManager> reference count decreases automatically.
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
mod error;
|
||||
mod geo;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
|
|
@ -10,10 +11,12 @@ use std::{
|
|||
};
|
||||
|
||||
use ash::vk;
|
||||
use gfx_hal::{device::Device, instance::Instance, queue::Queue};
|
||||
use gfx_hal::{device::Device, instance::Instance, queue::Queue, Fence};
|
||||
use tracing::{debug, error, trace, warn};
|
||||
|
||||
pub use error::{ResourceManagerError, Result};
|
||||
pub use geo::Geometry;
|
||||
|
||||
use gpu_allocator::{
|
||||
vulkan::{Allocation, AllocationCreateDesc, Allocator, AllocatorCreateDesc},
|
||||
MemoryLocation,
|
||||
|
|
@ -125,7 +128,7 @@ impl Drop for InternalImageInfo {
|
|||
struct TransferSetup {
|
||||
command_pool: vk::CommandPool,
|
||||
queue: Arc<Queue>,
|
||||
fence: vk::Fence,
|
||||
fence: Fence,
|
||||
}
|
||||
|
||||
pub struct ResourceManager {
|
||||
|
|
@ -135,7 +138,7 @@ pub struct ResourceManager {
|
|||
buffers: Mutex<HashMap<u64, InternalBufferInfo>>,
|
||||
images: Mutex<HashMap<u64, InternalImageInfo>>,
|
||||
next_id: AtomicU64,
|
||||
transfer_setup: Mutex<Option<TransferSetup>>,
|
||||
transfer_setup: Arc<Mutex<TransferSetup>>,
|
||||
}
|
||||
|
||||
impl ResourceManager {
|
||||
|
|
@ -152,6 +155,28 @@ impl ResourceManager {
|
|||
})?;
|
||||
debug!("GPU Allocator created.");
|
||||
|
||||
let queue_family_index = device
|
||||
.transfer_queue_family_index()
|
||||
.or(device.compute_queue_family_index()) // Try compute as fallback
|
||||
.unwrap_or(device.graphics_queue_family_index()); // Graphics as last resort
|
||||
|
||||
let queue = device.get_queue(queue_family_index, 0)?;
|
||||
|
||||
// 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 { device.raw().create_command_pool(&pool_info, None)? };
|
||||
|
||||
// Create a fence for waiting
|
||||
let fence = Fence::new(device.clone(), false)?;
|
||||
|
||||
let new_setup = TransferSetup {
|
||||
command_pool,
|
||||
queue,
|
||||
fence,
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
_instance: instance,
|
||||
device,
|
||||
|
|
@ -159,7 +184,7 @@ impl ResourceManager {
|
|||
buffers: Mutex::new(HashMap::new()),
|
||||
images: Mutex::new(HashMap::new()),
|
||||
next_id: AtomicU64::new(1),
|
||||
transfer_setup: Mutex::new(None),
|
||||
transfer_setup: Arc::new(Mutex::new(new_setup)),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -168,114 +193,59 @@ impl ResourceManager {
|
|||
self.allocator.clone()
|
||||
}
|
||||
|
||||
/// 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)?;
|
||||
|
||||
// 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.
|
||||
/// Helper to allocate, begin, end, submit, and wait for a single command buffer
|
||||
/// using the provided TransferSetup.
|
||||
unsafe fn submit_commands_and_wait<F>(
|
||||
&self,
|
||||
transfer_setup: &TransferSetup,
|
||||
transfer_setup: &TransferSetup, // Use the cloned setup
|
||||
record_fn: F,
|
||||
) -> Result<()>
|
||||
where
|
||||
F: FnOnce(vk::CommandBuffer) -> Result<()>,
|
||||
F: FnOnce(vk::CommandBuffer) -> Result<()>, // Closure records commands
|
||||
{
|
||||
let device = self.device.raw();
|
||||
let device_raw = self.device.raw(); // Get raw ash::Device
|
||||
|
||||
// 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];
|
||||
let command_buffer = device_raw.allocate_command_buffers(&alloc_info)?[0];
|
||||
tracing::info!("Allocated command_buffer: {:?}", command_buffer);
|
||||
trace!("Allocated temporary command buffer for transfer.");
|
||||
|
||||
// Begin recording
|
||||
let begin_info = vk::CommandBufferBeginInfo::default()
|
||||
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
|
||||
device.begin_command_buffer(command_buffer, &begin_info)?;
|
||||
device_raw.begin_command_buffer(command_buffer, &begin_info)?;
|
||||
|
||||
// Record user commands
|
||||
// --- 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)?;
|
||||
// --- End Recording ---
|
||||
// Always end buffer, even if recording failed, to allow cleanup
|
||||
device_raw.end_command_buffer(command_buffer)?;
|
||||
|
||||
// Check user function result *after* ending buffer
|
||||
record_result?;
|
||||
trace!("Transfer commands recorded.");
|
||||
|
||||
let binding = [command_buffer];
|
||||
// Submit
|
||||
let submits = [vk::SubmitInfo::default().command_buffers(&binding)];
|
||||
// Use the transfer queue and fence
|
||||
// Submit to the transfer queue
|
||||
let submits =
|
||||
[vk::SubmitInfo::default().command_buffers(std::slice::from_ref(&command_buffer))];
|
||||
// Use the queue from the TransferSetup. Assuming Queue::submit handles locking.
|
||||
transfer_setup
|
||||
.queue
|
||||
.submit(self.device.raw(), &submits, None)?; // Submit without fence initially
|
||||
.submit(device_raw, &submits, Some(&transfer_setup.fence))?; // Submit WITH fence
|
||||
trace!("Transfer command buffer submitted.");
|
||||
|
||||
// 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()),
|
||||
}
|
||||
// Wait for completion using the fence
|
||||
transfer_setup.fence.wait(None)?;
|
||||
|
||||
// Free command buffer
|
||||
device.free_command_buffers(transfer_setup.command_pool, &[command_buffer]);
|
||||
// Free command buffer *after* successful wait
|
||||
device_raw.free_command_buffers(transfer_setup.command_pool, &[command_buffer]);
|
||||
trace!("Temporary command buffer freed.");
|
||||
|
||||
transfer_setup.fence.reset()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -387,7 +357,7 @@ impl ResourceManager {
|
|||
let dest_handle = self.create_buffer(size, final_usage, location)?;
|
||||
|
||||
// 4. Record and submit transfer command
|
||||
let transfer_setup = self.get_transfer_setup()?;
|
||||
let transfer_setup = self.transfer_setup.lock()?;
|
||||
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
|
||||
|
||||
|
|
@ -589,22 +559,18 @@ impl Drop for ResourceManager {
|
|||
debug!("Clearing {} image entries...", images_map.len());
|
||||
images_map.clear();
|
||||
|
||||
// Destroy transfer setup resources
|
||||
let mut setup_guard = self
|
||||
let setup = self
|
||||
.transfer_setup
|
||||
.lock()
|
||||
.expect("mutex to not be poisoned");
|
||||
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.");
|
||||
|
||||
debug!("Destroying TransferSetup resources...");
|
||||
unsafe {
|
||||
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.
|
||||
|
|
|
|||
11
crates/shared/Cargo.toml
Normal file
11
crates/shared/Cargo.toml
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "shared"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
glam.workspace = true
|
||||
ash.workspace = true
|
||||
bytemuck.workspace = true
|
||||
memoffset = "0.9.1"
|
||||
derive_builder = "0.20.2"
|
||||
61
crates/shared/src/lib.rs
Normal file
61
crates/shared/src/lib.rs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
use ash::vk;
|
||||
use glam::{Mat4, Vec3};
|
||||
|
||||
use core::f32;
|
||||
use std::mem::size_of;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy)]
|
||||
pub struct Vertex {
|
||||
pub pos: [f32; 3],
|
||||
pub color: [f32; 3],
|
||||
}
|
||||
|
||||
impl Vertex {
|
||||
pub fn get_binding_decription() -> vk::VertexInputBindingDescription {
|
||||
vk::VertexInputBindingDescription::default()
|
||||
.binding(0)
|
||||
.stride(size_of::<Self>() as u32)
|
||||
.input_rate(vk::VertexInputRate::VERTEX)
|
||||
}
|
||||
|
||||
pub fn get_attribute_descriptions() -> [vk::VertexInputAttributeDescription; 2] {
|
||||
[
|
||||
vk::VertexInputAttributeDescription::default()
|
||||
.location(0)
|
||||
.binding(0)
|
||||
.format(vk::Format::R32G32B32_SFLOAT)
|
||||
.offset(memoffset::offset_of!(Vertex, pos) as u32),
|
||||
vk::VertexInputAttributeDescription::default()
|
||||
.location(1)
|
||||
.binding(0)
|
||||
.format(vk::Format::R32G32B32_SFLOAT)
|
||||
.offset(memoffset::offset_of!(Vertex, color) as u32),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Debug, Copy, PartialEq)]
|
||||
pub struct UniformBufferObject {
|
||||
pub model: Mat4,
|
||||
pub view: Mat4,
|
||||
pub proj: Mat4,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Copy, PartialEq)]
|
||||
pub struct CameraInfo {
|
||||
pub camera_pos: Vec3,
|
||||
pub camera_target: Vec3,
|
||||
pub camera_fov: f32,
|
||||
}
|
||||
|
||||
impl Default for CameraInfo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
camera_pos: Vec3::new(10.0, 10.0, 10.0),
|
||||
camera_target: Vec3::new(0.0, 0.0, 0.0),
|
||||
camera_fov: 45.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,16 @@
|
|||
#version 450
|
||||
|
||||
// Input from vertex shader
|
||||
layout(location = 0) in vec3 fragColor;
|
||||
// layout(location = 1) in vec2 fragTexCoord; // If using textures
|
||||
|
||||
// Output color
|
||||
layout(location = 0) out vec4 outColor;
|
||||
|
||||
// layout(binding = 1) uniform sampler2D texSampler; // If using textures
|
||||
|
||||
void main() {
|
||||
outColor = vec4(1.0, 0.5, 0.0, 1.0); // Orange
|
||||
// Use interpolated color
|
||||
outColor = vec4(fragColor, 1.0);
|
||||
// outColor = texture(texSampler, fragTexCoord); // If using textures
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,26 @@
|
|||
#version 450
|
||||
vec2 positions[3] = vec2[](
|
||||
vec2(0.0, -0.5),
|
||||
vec2(0.5, 0.5),
|
||||
vec2(-0.5, 0.5)
|
||||
);
|
||||
|
||||
// Matches Vertex struct attribute descriptions
|
||||
layout(location = 0) in vec3 inPosition;
|
||||
layout(location = 1) in vec3 inColor;
|
||||
// layout(location = 2) in vec2 inTexCoord; // If you add texture coords
|
||||
|
||||
// Matches UniformBufferObject struct and descriptor set layout binding
|
||||
layout(binding = 0) uniform UniformBufferObject {
|
||||
mat4 model;
|
||||
mat4 view;
|
||||
mat4 proj;
|
||||
} ubo;
|
||||
|
||||
// Output to fragment shader
|
||||
layout(location = 0) out vec3 fragColor;
|
||||
// layout(location = 1) out vec2 fragTexCoord; // If you add texture coords
|
||||
|
||||
void main() {
|
||||
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
|
||||
// Transform position: Model -> World -> View -> Clip space
|
||||
gl_Position = ubo.proj * ubo.view * ubo.model * vec4(inPosition, 1.0);
|
||||
|
||||
// Pass color (and other attributes) through
|
||||
fragColor = inColor;
|
||||
// fragTexCoord = inTexCoord;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue