refactor(renderer): modularize crate and enable vulkan 1.3 features
This commit is contained in:
parent
1805ae6aa6
commit
3997fa5fd9
243
Cargo.lock
generated
243
Cargo.lock
generated
|
|
@ -130,7 +130,7 @@ version = "0.5.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -144,6 +144,20 @@ name = "bytemuck"
|
|||
version = "1.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||
dependencies = [
|
||||
"bytemuck_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck_derive"
|
||||
version = "1.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
|
|
@ -437,6 +451,28 @@ dependencies = [
|
|||
"wasip2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "glam"
|
||||
version = "0.29.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8babf46d4c1c9d92deac9f7be466f76dfc4482b6452fc5024b5e8daf6ffeb3ee"
|
||||
|
||||
[[package]]
|
||||
name = "gpu-allocator"
|
||||
version = "0.28.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51255ea7cfaadb6c5f1528d43e92a82acb2b96c43365989a28b2d44ee38f8795"
|
||||
dependencies = [
|
||||
"ash",
|
||||
"log",
|
||||
"objc2 0.6.4",
|
||||
"objc2-foundation 0.3.2",
|
||||
"objc2-metal 0.3.2",
|
||||
"presser",
|
||||
"thiserror 2.0.18",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.17.0"
|
||||
|
|
@ -701,6 +737,15 @@ dependencies = [
|
|||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f"
|
||||
dependencies = [
|
||||
"objc2-encode",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-app-kit"
|
||||
version = "0.2.2"
|
||||
|
|
@ -710,10 +755,10 @@ dependencies = [
|
|||
"bitflags 2.11.1",
|
||||
"block2",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-data",
|
||||
"objc2-core-image",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-quartz-core",
|
||||
]
|
||||
|
||||
|
|
@ -725,9 +770,9 @@ checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009"
|
|||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -737,8 +782,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -749,8 +794,8 @@ checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef"
|
|||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -760,9 +805,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-metal",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-metal 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -772,9 +817,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-contacts",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -793,7 +838,17 @@ dependencies = [
|
|||
"block2",
|
||||
"dispatch",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-foundation"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"objc2 0.6.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -803,9 +858,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -816,8 +871,19 @@ checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6"
|
|||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-metal"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a0125f776a10d00af4152d74616409f0d4a2053a6f57fa5b7d6aa2854ac04794"
|
||||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"objc2 0.6.4",
|
||||
"objc2-foundation 0.3.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -828,9 +894,9 @@ checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a"
|
|||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2-metal",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-metal 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -839,8 +905,8 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -851,12 +917,12 @@ checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f"
|
|||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-cloud-kit",
|
||||
"objc2-core-data",
|
||||
"objc2-core-image",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-link-presentation",
|
||||
"objc2-quartz-core",
|
||||
"objc2-symbols",
|
||||
|
|
@ -871,8 +937,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe"
|
||||
dependencies = [
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
"objc2 0.5.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -883,9 +949,9 @@ checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3"
|
|||
dependencies = [
|
||||
"bitflags 2.11.1",
|
||||
"block2",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-core-location",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -971,6 +1037,12 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "presser"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8cf8e6a8aa66ce33f63993ffc4ea4271eb5b0530a9002db8455ea6050c77bfa"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-crate"
|
||||
version = "3.5.0"
|
||||
|
|
@ -1055,6 +1127,9 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"ash",
|
||||
"ash-window",
|
||||
"bytemuck",
|
||||
"glam",
|
||||
"gpu-allocator",
|
||||
"raw-window-handle",
|
||||
"thiserror 2.0.18",
|
||||
"tracing",
|
||||
|
|
@ -1673,12 +1748,107 @@ dependencies = [
|
|||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580"
|
||||
dependencies = [
|
||||
"windows-collections",
|
||||
"windows-core",
|
||||
"windows-future",
|
||||
"windows-numerics",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-collections"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-core"
|
||||
version = "0.62.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||
dependencies = [
|
||||
"windows-implement",
|
||||
"windows-interface",
|
||||
"windows-link",
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-future"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
"windows-threading",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-implement"
|
||||
version = "0.60.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-interface"
|
||||
version = "0.59.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||
|
||||
[[package]]
|
||||
name = "windows-numerics"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26"
|
||||
dependencies = [
|
||||
"windows-core",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
|
|
@ -1722,6 +1892,15 @@ dependencies = [
|
|||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-threading"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37"
|
||||
dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
|
|
@ -1793,9 +1972,9 @@ dependencies = [
|
|||
"libc",
|
||||
"memmap2",
|
||||
"ndk",
|
||||
"objc2",
|
||||
"objc2 0.5.2",
|
||||
"objc2-app-kit",
|
||||
"objc2-foundation",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-ui-kit",
|
||||
"orbclient",
|
||||
"percent-encoding",
|
||||
|
|
|
|||
|
|
@ -9,3 +9,6 @@ ash-window = "0.13.0"
|
|||
raw-window-handle = "0.6.2"
|
||||
thiserror = "2.0.18"
|
||||
tracing = "0.1.44"
|
||||
gpu-allocator = "0.28.0"
|
||||
bytemuck = { version = "1.21", features = ["derive"] }
|
||||
glam = "0.29"
|
||||
|
|
|
|||
98
crates/renderer/src/device.rs
Normal file
98
crates/renderer/src/device.rs
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
use ash::{Device, Instance, khr, vk};
|
||||
use crate::error::RendererError;
|
||||
|
||||
/// Picks a physical device (GPU) that supports the required features and extensions.
|
||||
pub fn pick_physical_device(
|
||||
instance: &Instance,
|
||||
surface_loader: &khr::surface::Instance,
|
||||
surface: vk::SurfaceKHR,
|
||||
) -> Result<vk::PhysicalDevice, RendererError> {
|
||||
let devices = unsafe { instance.enumerate_physical_devices()? };
|
||||
|
||||
for device in devices {
|
||||
if is_device_suitable(instance, device, surface_loader, surface) {
|
||||
let props = unsafe { instance.get_physical_device_properties(device) };
|
||||
let name = unsafe { std::ffi::CStr::from_ptr(props.device_name.as_ptr()).to_string_lossy() };
|
||||
tracing::info!("Selected GPU: \"{name}\"");
|
||||
return Ok(device);
|
||||
}
|
||||
}
|
||||
|
||||
Err(RendererError::NoSuitableGpu)
|
||||
}
|
||||
|
||||
/// Creates a logical device and retrieves the graphics queue.
|
||||
pub fn create_logical_device(
|
||||
instance: &Instance,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
queue_family_index: u32,
|
||||
) -> Result<(Device, vk::Queue), RendererError> {
|
||||
let priorities = [1.0];
|
||||
let queue_info = vk::DeviceQueueCreateInfo::default()
|
||||
.queue_family_index(queue_family_index)
|
||||
.queue_priorities(&priorities);
|
||||
|
||||
let device_extensions = [khr::swapchain::NAME.as_ptr()];
|
||||
|
||||
// Enable Vulkan 1.3 features
|
||||
let mut synchronization2_features = vk::PhysicalDeviceSynchronization2Features::default()
|
||||
.synchronization2(true);
|
||||
let mut dynamic_rendering_features = vk::PhysicalDeviceDynamicRenderingFeatures::default()
|
||||
.dynamic_rendering(true);
|
||||
|
||||
let create_info = vk::DeviceCreateInfo::default()
|
||||
.queue_create_infos(std::slice::from_ref(&queue_info))
|
||||
.enabled_extension_names(&device_extensions)
|
||||
.push_next(&mut synchronization2_features)
|
||||
.push_next(&mut dynamic_rendering_features);
|
||||
|
||||
let device = unsafe { instance.create_device(physical_device, &create_info, None)? };
|
||||
let graphics_queue = unsafe { device.get_device_queue(queue_family_index, 0) };
|
||||
|
||||
Ok((device, graphics_queue))
|
||||
}
|
||||
|
||||
/// Finds a queue family that supports both graphics commands and presentation.
|
||||
pub fn find_graphics_queue_family(
|
||||
instance: &Instance,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
surface_loader: &khr::surface::Instance,
|
||||
surface: vk::SurfaceKHR,
|
||||
) -> Result<u32, RendererError> {
|
||||
let props = unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
|
||||
|
||||
for (index, prop) in props.iter().enumerate() {
|
||||
let index = index as u32;
|
||||
let graphics = prop.queue_flags.contains(vk::QueueFlags::GRAPHICS);
|
||||
let present = unsafe {
|
||||
surface_loader.get_physical_device_surface_support(physical_device, index, surface)?
|
||||
};
|
||||
|
||||
if graphics && present {
|
||||
return Ok(index);
|
||||
}
|
||||
}
|
||||
|
||||
Err(RendererError::NoSuitableGpu)
|
||||
}
|
||||
|
||||
fn is_device_suitable(
|
||||
instance: &Instance,
|
||||
device: vk::PhysicalDevice,
|
||||
surface_loader: &khr::surface::Instance,
|
||||
surface: vk::SurfaceKHR,
|
||||
) -> bool {
|
||||
let extensions = unsafe { instance.enumerate_device_extension_properties(device).unwrap_or_default() };
|
||||
let has_swapchain = extensions.iter().any(|ext| {
|
||||
unsafe { std::ffi::CStr::from_ptr(ext.extension_name.as_ptr()) == khr::swapchain::NAME }
|
||||
});
|
||||
|
||||
let formats = unsafe {
|
||||
surface_loader.get_physical_device_surface_formats(device, surface).unwrap_or_default()
|
||||
};
|
||||
let present_modes = unsafe {
|
||||
surface_loader.get_physical_device_surface_present_modes(device, surface).unwrap_or_default()
|
||||
};
|
||||
|
||||
has_swapchain && !formats.is_empty() && !present_modes.is_empty()
|
||||
}
|
||||
80
crates/renderer/src/instance.rs
Normal file
80
crates/renderer/src/instance.rs
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
use std::ffi::{CStr, c_char};
|
||||
use ash::{Entry, Instance, ext, vk};
|
||||
use tracing::{debug, error, info, warn};
|
||||
use crate::error::RendererError;
|
||||
|
||||
/// Creates a Vulkan instance and optionally a debug messenger.
|
||||
pub fn create_instance(
|
||||
entry: &Entry,
|
||||
required_extensions: &[*const c_char],
|
||||
) -> Result<(Instance, Option<ext::debug_utils::Instance>, vk::DebugUtilsMessengerEXT), RendererError> {
|
||||
let mut extensions = required_extensions.to_vec();
|
||||
let mut layers = Vec::new();
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
extensions.push(ext::debug_utils::NAME.as_ptr());
|
||||
layers.push(c"VK_LAYER_KHRONOS_validation".as_ptr());
|
||||
}
|
||||
|
||||
let app_info = vk::ApplicationInfo::default()
|
||||
.api_version(vk::API_VERSION_1_3);
|
||||
|
||||
let create_info = vk::InstanceCreateInfo::default()
|
||||
.application_info(&app_info)
|
||||
.enabled_extension_names(&extensions)
|
||||
.enabled_layer_names(&layers);
|
||||
|
||||
let instance = unsafe { entry.create_instance(&create_info, None)? };
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let (debug_utils, debug_messenger) = {
|
||||
let debug_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
|
||||
.message_severity(
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::WARNING |
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR
|
||||
)
|
||||
.message_type(
|
||||
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL |
|
||||
vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION |
|
||||
vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE
|
||||
)
|
||||
.pfn_user_callback(Some(vulkan_debug_callback));
|
||||
|
||||
let utils = ext::debug_utils::Instance::new(entry, &instance);
|
||||
let messenger = unsafe { utils.create_debug_utils_messenger(&debug_info, None)? };
|
||||
|
||||
(Some(utils), messenger)
|
||||
};
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let (debug_utils, debug_messenger) = (None, vk::DebugUtilsMessengerEXT::null());
|
||||
|
||||
Ok((instance, debug_utils, debug_messenger))
|
||||
}
|
||||
|
||||
/// The callback function invoked by Vulkan's validation layers.
|
||||
unsafe extern "system" fn vulkan_debug_callback(
|
||||
message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
|
||||
_message_type: vk::DebugUtilsMessageTypeFlagsEXT,
|
||||
p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>,
|
||||
_user_data: *mut std::ffi::c_void,
|
||||
) -> vk::Bool32 {
|
||||
let callback_data = unsafe { *p_callback_data };
|
||||
|
||||
let message = if callback_data.p_message.is_null() {
|
||||
"".into()
|
||||
} else {
|
||||
unsafe { CStr::from_ptr(callback_data.p_message).to_string_lossy() }
|
||||
};
|
||||
|
||||
match message_severity {
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => debug!("{message}"),
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::INFO => info!("{message}"),
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => warn!("{message}"),
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => error!("{message}"),
|
||||
_ => info!("{message}"),
|
||||
}
|
||||
|
||||
vk::FALSE
|
||||
}
|
||||
|
|
@ -1,70 +1,23 @@
|
|||
pub mod error;
|
||||
mod instance;
|
||||
mod device;
|
||||
mod surface;
|
||||
mod swapchain;
|
||||
mod sync;
|
||||
mod renderer;
|
||||
|
||||
use std::ffi::{CStr, c_char};
|
||||
use ash::{Device, Entry, Instance, ext, khr, vk};
|
||||
use std::ffi::c_char;
|
||||
use ash::{Entry, vk};
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
||||
use tracing::{debug, error, info, warn};
|
||||
|
||||
pub use error::RendererError;
|
||||
|
||||
/// The core renderer structure holding the Vulkan instance and debug resources.
|
||||
pub struct Renderer {
|
||||
/// The entry point to the Vulkan library.
|
||||
_entry: Entry,
|
||||
/// The Vulkan instance handle.
|
||||
instance: Instance,
|
||||
/// The debug utils instance, if enabled (debug builds only).
|
||||
debug_utils: Option<ext::debug_utils::Instance>,
|
||||
/// The debug messenger handle, if enabled (debug builds only).
|
||||
debug_messenger: vk::DebugUtilsMessengerEXT,
|
||||
/// The selected physical device (GPU).
|
||||
physical_device: vk::PhysicalDevice,
|
||||
/// The logical device handle.
|
||||
device: Device,
|
||||
/// The handle to the graphics queue.
|
||||
graphics_queue: vk::Queue,
|
||||
/// The index of the graphics queue family.
|
||||
graphics_queue_index: u32,
|
||||
/// The surface loader instance.
|
||||
surface_loader: khr::surface::Instance,
|
||||
/// The window surface handle.
|
||||
surface: vk::SurfaceKHR,
|
||||
/// The swapchain loader instance.
|
||||
swapchain_loader: khr::swapchain::Device,
|
||||
/// The swapchain handle.
|
||||
swapchain: vk::SwapchainKHR,
|
||||
/// The images retrieved from the swapchain.
|
||||
swapchain_images: Vec<vk::Image>,
|
||||
/// The pixel format of the swapchain images.
|
||||
swapchain_format: vk::Format,
|
||||
/// The resolution of the swapchain images.
|
||||
swapchain_extent: vk::Extent2D,
|
||||
/// The views into the swapchain images.
|
||||
swapchain_image_views: Vec<vk::ImageView>,
|
||||
/// The pool used to allocate command buffers.
|
||||
command_pool: vk::CommandPool,
|
||||
/// The buffers used to record GPU commands (one per frame in flight).
|
||||
command_buffers: Vec<vk::CommandBuffer>,
|
||||
/// Semaphores signaled when an image is acquired (one per frame in flight).
|
||||
image_available_semaphores: Vec<vk::Semaphore>,
|
||||
/// Semaphores signaled when rendering is complete (one per frame in flight).
|
||||
render_finished_semaphores: Vec<vk::Semaphore>,
|
||||
/// Fences signaled when the GPU has finished a frame (one per frame in flight).
|
||||
in_flight_fences: Vec<vk::Fence>,
|
||||
/// Tracks which frame is using which swapchain image (one per swapchain image).
|
||||
images_in_flight: Vec<vk::Fence>,
|
||||
/// The index of the frame currently being processed (0..MAX_FRAMES_IN_FLIGHT).
|
||||
current_frame: usize,
|
||||
}
|
||||
pub use renderer::{Renderer, MAX_FRAMES_IN_FLIGHT};
|
||||
|
||||
impl Renderer {
|
||||
/// Initializes the Vulkan renderer.
|
||||
///
|
||||
/// This function loads the Vulkan library, creates an instance, selects a GPU,
|
||||
/// and initializes a logical device with a graphics queue.
|
||||
///
|
||||
/// `display_handle` and `window_handle` are used to create the OS-specific surface.
|
||||
/// `required_extensions` are raw C-strings (pointers) provided by the windowing system.
|
||||
pub fn new(
|
||||
display_handle: RawDisplayHandle,
|
||||
window_handle: RawWindowHandle,
|
||||
|
|
@ -74,117 +27,51 @@ impl Renderer {
|
|||
) -> Result<Self, RendererError> {
|
||||
let entry = unsafe { Entry::load() }?;
|
||||
|
||||
// Prepare extensions and layers
|
||||
let mut extensions: Vec<*const c_char> = required_extensions.to_vec();
|
||||
let mut layers: Vec<*const c_char> = Vec::new();
|
||||
// 1. Instance and Debug Messenger
|
||||
let (instance, debug_utils, debug_messenger) =
|
||||
instance::create_instance(&entry, required_extensions)?;
|
||||
|
||||
// Enable debug extensions and validation layers in debug builds
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
extensions.push(ext::debug_utils::NAME.as_ptr());
|
||||
layers.push(c"VK_LAYER_KHRONOS_validation".as_ptr());
|
||||
}
|
||||
// 2. Surface
|
||||
let (surface_loader, surface) =
|
||||
surface::create_surface(&entry, &instance, display_handle, window_handle)?;
|
||||
|
||||
let app_info = vk::ApplicationInfo::default()
|
||||
.api_version(vk::API_VERSION_1_3);
|
||||
// 3. Physical Device (GPU)
|
||||
let physical_device =
|
||||
device::pick_physical_device(&instance, &surface_loader, surface)?;
|
||||
|
||||
let create_info = vk::InstanceCreateInfo::default()
|
||||
.application_info(&app_info)
|
||||
.enabled_extension_names(&extensions)
|
||||
.enabled_layer_names(&layers);
|
||||
// 4. Graphics Queue Index
|
||||
let graphics_queue_index =
|
||||
device::find_graphics_queue_family(&instance, physical_device, &surface_loader, surface)?;
|
||||
|
||||
// Create the Vulkan instance
|
||||
let instance = unsafe { entry.create_instance(&create_info, None)? };
|
||||
// 5. Logical Device and Queue
|
||||
let (device, graphics_queue) =
|
||||
device::create_logical_device(&instance, physical_device, graphics_queue_index)?;
|
||||
|
||||
// Setup debug messenger for validation layer feedback
|
||||
#[cfg(debug_assertions)]
|
||||
let (debug_utils, debug_messenger) = {
|
||||
let debug_info = vk::DebugUtilsMessengerCreateInfoEXT::default()
|
||||
.message_severity(
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::WARNING |
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR
|
||||
)
|
||||
.message_type(
|
||||
vk::DebugUtilsMessageTypeFlagsEXT::GENERAL |
|
||||
vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION |
|
||||
vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE
|
||||
)
|
||||
.pfn_user_callback(Some(vulkan_debug_callback));
|
||||
|
||||
let utils = ext::debug_utils::Instance::new(&entry, &instance);
|
||||
let messenger = unsafe { utils.create_debug_utils_messenger(&debug_info, None)? };
|
||||
|
||||
(Some(utils), messenger)
|
||||
};
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let (debug_utils, debug_messenger) = (None, vk::DebugUtilsMessengerEXT::null());
|
||||
|
||||
// Create the OS surface
|
||||
let surface = unsafe {
|
||||
ash_window::create_surface(&entry, &instance, display_handle, window_handle, None)?
|
||||
};
|
||||
let surface_loader = khr::surface::Instance::new(&entry, &instance);
|
||||
|
||||
// Pick a physical device that supports the surface
|
||||
let physical_device = Self::pick_physical_device(&instance, &surface_loader, surface)?;
|
||||
|
||||
// Find the graphics queue family (which must also support presentation)
|
||||
let graphics_queue_index = Self::find_graphics_queue_family(&instance, physical_device, &surface_loader, surface)?;
|
||||
|
||||
// Create the logical device
|
||||
let (device, graphics_queue) = Self::create_logical_device(&instance, physical_device, graphics_queue_index)?;
|
||||
|
||||
// Create the swapchain
|
||||
// 6. Swapchain
|
||||
let (swapchain_loader, swapchain, swapchain_images, swapchain_format, swapchain_extent) =
|
||||
Self::create_swapchain(&instance, physical_device, &device, &surface_loader, surface, width, height)?;
|
||||
swapchain::create_swapchain(&instance, physical_device, &device, &surface_loader, surface, width, height)?;
|
||||
|
||||
// Create image views for the swapchain images
|
||||
let image_views = Self::create_image_views(&device, &swapchain_images, swapchain_format)?;
|
||||
// 7. Image Views
|
||||
let image_views =
|
||||
swapchain::create_image_views(&device, &swapchain_images, swapchain_format)?;
|
||||
|
||||
// Create the command pool
|
||||
// 8. Command Pool
|
||||
let pool_create_info = vk::CommandPoolCreateInfo::default()
|
||||
.queue_family_index(graphics_queue_index)
|
||||
// Allows reusing the command buffer every frame by resetting it
|
||||
.flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER);
|
||||
|
||||
let command_pool = unsafe { device.create_command_pool(&pool_create_info, None)? };
|
||||
|
||||
// Create synchronization primitives
|
||||
// We use MAX_FRAMES_IN_FLIGHT (2) to allow the CPU to stay one frame ahead of the GPU.
|
||||
const MAX_FRAMES_IN_FLIGHT: usize = 2;
|
||||
let mut image_available_semaphores = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT);
|
||||
let mut render_finished_semaphores = Vec::with_capacity(swapchain_images.len());
|
||||
let mut in_flight_fences = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT);
|
||||
let mut command_buffers = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT);
|
||||
|
||||
let semaphore_info = vk::SemaphoreCreateInfo::default();
|
||||
let fence_info = vk::FenceCreateInfo::default()
|
||||
.flags(vk::FenceCreateFlags::SIGNALED);
|
||||
|
||||
// 9. Command Buffers
|
||||
let alloc_info = vk::CommandBufferAllocateInfo::default()
|
||||
.command_pool(command_pool)
|
||||
.level(vk::CommandBufferLevel::PRIMARY)
|
||||
.command_buffer_count(MAX_FRAMES_IN_FLIGHT as u32);
|
||||
|
||||
let cbs = unsafe { device.allocate_command_buffers(&alloc_info)? };
|
||||
let command_buffers = unsafe { device.allocate_command_buffers(&alloc_info)? };
|
||||
|
||||
for i in 0..MAX_FRAMES_IN_FLIGHT {
|
||||
image_available_semaphores.push(unsafe { device.create_semaphore(&semaphore_info, None)? });
|
||||
in_flight_fences.push(unsafe { device.create_fence(&fence_info, None)? });
|
||||
command_buffers.push(cbs[i]);
|
||||
}
|
||||
|
||||
// We create one render-finished semaphore per swapchain image.
|
||||
// This satisfies strict drivers (like AMD RADV) that require a semaphore
|
||||
// to be tied to a specific image until it is re-acquired.
|
||||
for _ in 0..swapchain_images.len() {
|
||||
render_finished_semaphores.push(unsafe { device.create_semaphore(&semaphore_info, None)? });
|
||||
}
|
||||
|
||||
// Initially, no image is in use by a frame.
|
||||
// We use this to track which frame's fence is protecting which swapchain image.
|
||||
let images_in_flight = vec![vk::Fence::null(); swapchain_images.len()];
|
||||
// 10. Synchronization Primitives
|
||||
let sync = sync::create_sync_primitives(&device, MAX_FRAMES_IN_FLIGHT, swapchain_images.len())?;
|
||||
|
||||
Ok(Self {
|
||||
_entry: entry,
|
||||
|
|
@ -205,433 +92,8 @@ impl Renderer {
|
|||
swapchain_image_views: image_views,
|
||||
command_pool,
|
||||
command_buffers,
|
||||
image_available_semaphores,
|
||||
render_finished_semaphores,
|
||||
in_flight_fences,
|
||||
images_in_flight,
|
||||
sync,
|
||||
current_frame: 0,
|
||||
})
|
||||
}
|
||||
|
||||
/// Selects the best physical device (GPU) available on the system.
|
||||
///
|
||||
/// This function filters for devices supporting graphics operations and scores
|
||||
/// them based on their type, preferring discrete GPUs.
|
||||
fn pick_physical_device(
|
||||
instance: &Instance,
|
||||
surface_loader: &khr::surface::Instance,
|
||||
surface: vk::SurfaceKHR,
|
||||
) -> Result<vk::PhysicalDevice, RendererError> {
|
||||
let devices = unsafe { instance.enumerate_physical_devices()? };
|
||||
|
||||
let selected = devices.into_iter()
|
||||
.filter(|&device| {
|
||||
// Check if the device has a queue family that supports graphics AND presentation
|
||||
let props = unsafe { instance.get_physical_device_queue_family_properties(device) };
|
||||
props.iter().enumerate().any(|(i, p)| {
|
||||
let supports_graphics = p.queue_flags.contains(vk::QueueFlags::GRAPHICS);
|
||||
let supports_present = unsafe {
|
||||
surface_loader.get_physical_device_surface_support(device, i as u32, surface)
|
||||
.unwrap_or(false)
|
||||
};
|
||||
supports_graphics && supports_present
|
||||
})
|
||||
})
|
||||
.max_by_key(|&device| {
|
||||
// Score the device based on its type
|
||||
let props = unsafe { instance.get_physical_device_properties(device) };
|
||||
match props.device_type {
|
||||
vk::PhysicalDeviceType::DISCRETE_GPU => 1000,
|
||||
vk::PhysicalDeviceType::INTEGRATED_GPU => 100,
|
||||
_ => 1,
|
||||
}
|
||||
})
|
||||
.ok_or(RendererError::NoSuitableGpu)?;
|
||||
|
||||
let props = unsafe { instance.get_physical_device_properties(selected) };
|
||||
info!("Selected GPU: {:?}", unsafe { CStr::from_ptr(props.device_name.as_ptr()).to_string_lossy() });
|
||||
|
||||
Ok(selected)
|
||||
}
|
||||
|
||||
/// Finds the index of the first queue family that supports both graphics and presentation.
|
||||
fn find_graphics_queue_family(
|
||||
instance: &Instance,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
surface_loader: &khr::surface::Instance,
|
||||
surface: vk::SurfaceKHR,
|
||||
) -> Result<u32, RendererError> {
|
||||
let props = unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
|
||||
|
||||
props.iter().enumerate()
|
||||
.find(|&(i, p)| {
|
||||
let supports_graphics = p.queue_flags.contains(vk::QueueFlags::GRAPHICS);
|
||||
let supports_present = unsafe {
|
||||
surface_loader.get_physical_device_surface_support(physical_device, i as u32, surface)
|
||||
.unwrap_or(false)
|
||||
};
|
||||
supports_graphics && supports_present
|
||||
})
|
||||
.map(|(i, _)| i as u32)
|
||||
.ok_or(RendererError::NoSuitableGpu)
|
||||
}
|
||||
|
||||
/// Creates a logical device and retrieves the handle to its graphics queue.
|
||||
fn create_logical_device(
|
||||
instance: &Instance,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
queue_index: u32,
|
||||
) -> Result<(Device, vk::Queue), RendererError> {
|
||||
// Request a single queue from the graphics family
|
||||
let queue_priorities = [1.0];
|
||||
let queue_info = vk::DeviceQueueCreateInfo::default()
|
||||
.queue_family_index(queue_index)
|
||||
.queue_priorities(&queue_priorities);
|
||||
|
||||
// Enable modern Vulkan 1.3 features
|
||||
let mut features_13 = vk::PhysicalDeviceVulkan13Features::default()
|
||||
.dynamic_rendering(true) // Removes the need for RenderPasses/Framebuffers
|
||||
.synchronization2(true); // Cleaner memory barriers and synchronization
|
||||
|
||||
// We need the swapchain extension to present images to the window
|
||||
let device_extensions = [khr::swapchain::NAME.as_ptr()];
|
||||
|
||||
let create_info = vk::DeviceCreateInfo::default()
|
||||
.queue_create_infos(std::slice::from_ref(&queue_info))
|
||||
.enabled_extension_names(&device_extensions)
|
||||
.push_next(&mut features_13);
|
||||
|
||||
let device = unsafe { instance.create_device(physical_device, &create_info, None)? };
|
||||
let graphics_queue = unsafe { device.get_device_queue(queue_index, 0) };
|
||||
|
||||
Ok((device, graphics_queue))
|
||||
}
|
||||
|
||||
/// Creates a swapchain and retrieves its images.
|
||||
///
|
||||
/// This function negotiates with the surface to find a suitable color format,
|
||||
/// presentation mode, and image resolution.
|
||||
fn create_swapchain(
|
||||
instance: &Instance,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
device: &Device,
|
||||
surface_loader: &khr::surface::Instance,
|
||||
surface: vk::SurfaceKHR,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<
|
||||
(khr::swapchain::Device, vk::SwapchainKHR, Vec<vk::Image>, vk::Format, vk::Extent2D),
|
||||
RendererError,
|
||||
> {
|
||||
let surface_capabilities = unsafe { surface_loader.get_physical_device_surface_capabilities(physical_device, surface)? };
|
||||
|
||||
let surface_formats = unsafe { surface_loader.get_physical_device_surface_formats(physical_device, surface)? };
|
||||
|
||||
let surface_present_modes = unsafe { surface_loader.get_physical_device_surface_present_modes(physical_device, surface)?};
|
||||
|
||||
// 1. Pick the best color format (Prefer SRGB for accurate colors)
|
||||
let format = surface_formats.iter()
|
||||
.find(|f| f.format == vk::Format::B8G8R8A8_SRGB && f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR)
|
||||
.unwrap_or(&surface_formats[0]);
|
||||
|
||||
// 2. Pick the best presentation mode (Mailbox = Triple Buffering, FIFO = VSync)
|
||||
let present_mode = surface_present_modes.into_iter()
|
||||
.find(|&m| m == vk::PresentModeKHR::MAILBOX)
|
||||
.unwrap_or(vk::PresentModeKHR::FIFO);
|
||||
|
||||
// 3. Match the window resolution
|
||||
// If the surface says u32::MAX, it means we can pick any size within bounds.
|
||||
let extent = if surface_capabilities.current_extent.width != u32::MAX {
|
||||
surface_capabilities.current_extent
|
||||
} else {
|
||||
vk::Extent2D {
|
||||
width: width.clamp(surface_capabilities.min_image_extent.width, surface_capabilities.max_image_extent.width),
|
||||
height: height.clamp(surface_capabilities.min_image_extent.height, surface_capabilities.max_image_extent.height),
|
||||
}
|
||||
};
|
||||
|
||||
let mut image_count = surface_capabilities.min_image_count + 1;
|
||||
|
||||
if surface_capabilities.max_image_count > 0 && image_count > surface_capabilities.max_image_count {
|
||||
image_count = surface_capabilities.max_image_count;
|
||||
}
|
||||
|
||||
let swapchain_loader = khr::swapchain::Device::new(instance, device);
|
||||
|
||||
let create_info = vk::SwapchainCreateInfoKHR::default()
|
||||
.surface(surface)
|
||||
.min_image_count(image_count)
|
||||
.image_format(format.format)
|
||||
.image_color_space(format.color_space)
|
||||
.image_extent(extent)
|
||||
.image_array_layers(1)
|
||||
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
|
||||
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||
.pre_transform(surface_capabilities.current_transform)
|
||||
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
|
||||
.present_mode(present_mode)
|
||||
.clipped(true);
|
||||
|
||||
let swapchain = unsafe { swapchain_loader.create_swapchain(&create_info, None)? };
|
||||
|
||||
let images = unsafe { swapchain_loader.get_swapchain_images(swapchain)? };
|
||||
|
||||
Ok((swapchain_loader, swapchain, images, format.format, extent ))
|
||||
}
|
||||
|
||||
/// Creates image views for the swapchain images.
|
||||
///
|
||||
/// An image view describes how to access an image (e.g. as a 2D texture).
|
||||
fn create_image_views(
|
||||
device: &Device,
|
||||
images: &[vk::Image],
|
||||
format: vk::Format,
|
||||
) -> Result<Vec<vk::ImageView>, RendererError> {
|
||||
let mut views = Vec::with_capacity(images.len());
|
||||
|
||||
for &image in images {
|
||||
let create_info = vk::ImageViewCreateInfo::default()
|
||||
.image(image)
|
||||
.view_type(vk::ImageViewType::TYPE_2D)
|
||||
.format(format)
|
||||
.components(vk::ComponentMapping {
|
||||
r: vk::ComponentSwizzle::IDENTITY,
|
||||
g: vk::ComponentSwizzle::IDENTITY,
|
||||
b: vk::ComponentSwizzle::IDENTITY,
|
||||
a: vk::ComponentSwizzle::IDENTITY,
|
||||
})
|
||||
.subresource_range(vk::ImageSubresourceRange {
|
||||
aspect_mask: vk::ImageAspectFlags::COLOR,
|
||||
base_mip_level: 0,
|
||||
level_count: 1,
|
||||
base_array_layer: 0,
|
||||
layer_count: 1,
|
||||
});
|
||||
|
||||
let view = unsafe { device.create_image_view(&create_info, None)? };
|
||||
views.push(view);
|
||||
}
|
||||
|
||||
Ok(views)
|
||||
}
|
||||
|
||||
/// Renders a single frame.
|
||||
///
|
||||
/// This function handles synchronization, image acquisition, command recording
|
||||
/// for clearing the screen, and presentation.
|
||||
pub fn draw_frame(&mut self) -> Result<(), RendererError> {
|
||||
let in_flight_fence = self.in_flight_fences[self.current_frame];
|
||||
let image_available_semaphore = self.image_available_semaphores[self.current_frame];
|
||||
let cmd = self.command_buffers[self.current_frame];
|
||||
|
||||
// 1. Wait for the current frame's GPU work to finish
|
||||
unsafe {
|
||||
self.device.wait_for_fences(&[in_flight_fence], true, u64::MAX)?;
|
||||
self.device.reset_fences(&[in_flight_fence])?;
|
||||
}
|
||||
|
||||
// 2. Acquire an image from the swapchain
|
||||
let (image_index, _is_suboptimal) = unsafe {
|
||||
self.swapchain_loader.acquire_next_image(
|
||||
self.swapchain,
|
||||
u64::MAX,
|
||||
image_available_semaphore,
|
||||
vk::Fence::null(),
|
||||
)?
|
||||
};
|
||||
|
||||
// If the acquired image is still being used by a previous frame, wait for it
|
||||
let image_fence = self.images_in_flight[image_index as usize];
|
||||
if image_fence != vk::Fence::null() {
|
||||
unsafe { self.device.wait_for_fences(&[image_fence], true, u64::MAX)? };
|
||||
}
|
||||
// Mark the image as being in use by the current frame's fence
|
||||
self.images_in_flight[image_index as usize] = in_flight_fence;
|
||||
|
||||
// Use the semaphore tied to this specific swapchain image for rendering completion
|
||||
let render_finished_semaphore = self.render_finished_semaphores[image_index as usize];
|
||||
|
||||
// 3. Reset and begin recording the command buffer
|
||||
unsafe {
|
||||
self.device.reset_command_buffer(cmd, vk::CommandBufferResetFlags::empty())?;
|
||||
let begin_info = vk::CommandBufferBeginInfo::default()
|
||||
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
|
||||
self.device.begin_command_buffer(cmd, &begin_info)?;
|
||||
}
|
||||
|
||||
let image = self.swapchain_images[image_index as usize];
|
||||
let view = self.swapchain_image_views[image_index as usize];
|
||||
|
||||
// 4. Transition the swapchain image to a layout suitable for drawing
|
||||
let range = vk::ImageSubresourceRange {
|
||||
aspect_mask: vk::ImageAspectFlags::COLOR,
|
||||
base_mip_level: 0,
|
||||
level_count: 1,
|
||||
base_array_layer: 0,
|
||||
layer_count: 1,
|
||||
};
|
||||
|
||||
let barrier_to_draw = vk::ImageMemoryBarrier2::default()
|
||||
.image(image)
|
||||
.subresource_range(range)
|
||||
.src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT)
|
||||
.src_access_mask(vk::AccessFlags2::empty())
|
||||
.dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT)
|
||||
.dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE)
|
||||
.old_layout(vk::ImageLayout::UNDEFINED)
|
||||
.new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
|
||||
|
||||
let dependency_info = vk::DependencyInfo::default()
|
||||
.image_memory_barriers(std::slice::from_ref(&barrier_to_draw));
|
||||
|
||||
unsafe { self.device.cmd_pipeline_barrier2(cmd, &dependency_info) };
|
||||
|
||||
// 5. Begin Dynamic Rendering with a clear color
|
||||
let color_attachment = vk::RenderingAttachmentInfo::default()
|
||||
.image_view(view)
|
||||
.image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||
.load_op(vk::AttachmentLoadOp::CLEAR)
|
||||
.store_op(vk::AttachmentStoreOp::STORE)
|
||||
.clear_value(vk::ClearValue {
|
||||
color: vk::ClearColorValue {
|
||||
float32: [0.1, 0.2, 0.4, 1.0], // Project Catalyst Blue
|
||||
},
|
||||
});
|
||||
|
||||
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));
|
||||
|
||||
unsafe {
|
||||
self.device.cmd_begin_rendering(cmd, &rendering_info);
|
||||
// Future draw calls will go here
|
||||
self.device.cmd_end_rendering(cmd);
|
||||
}
|
||||
|
||||
// 6. Transition the image back to Present layout
|
||||
let barrier_to_present = vk::ImageMemoryBarrier2::default()
|
||||
.image(image)
|
||||
.subresource_range(range)
|
||||
.src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT)
|
||||
.src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE)
|
||||
.dst_stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE)
|
||||
.dst_access_mask(vk::AccessFlags2::empty())
|
||||
.old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||
.new_layout(vk::ImageLayout::PRESENT_SRC_KHR);
|
||||
|
||||
let dependency_info = vk::DependencyInfo::default()
|
||||
.image_memory_barriers(std::slice::from_ref(&barrier_to_present));
|
||||
|
||||
unsafe {
|
||||
self.device.cmd_pipeline_barrier2(cmd, &dependency_info);
|
||||
self.device.end_command_buffer(cmd)?;
|
||||
}
|
||||
|
||||
// 7. Submit the work to the GPU
|
||||
let submit_info = vk::SubmitInfo::default()
|
||||
.wait_semaphores(std::slice::from_ref(&image_available_semaphore))
|
||||
.wait_dst_stage_mask(&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT])
|
||||
.command_buffers(std::slice::from_ref(&cmd))
|
||||
.signal_semaphores(std::slice::from_ref(&render_finished_semaphore));
|
||||
|
||||
unsafe {
|
||||
self.device.queue_submit(self.graphics_queue, &[submit_info], in_flight_fence)?;
|
||||
}
|
||||
|
||||
// 8. Present the result to the screen
|
||||
let present_info = vk::PresentInfoKHR::default()
|
||||
.wait_semaphores(std::slice::from_ref(&render_finished_semaphore))
|
||||
.swapchains(std::slice::from_ref(&self.swapchain))
|
||||
.image_indices(std::slice::from_ref(&image_index));
|
||||
|
||||
unsafe {
|
||||
self.swapchain_loader.queue_present(self.graphics_queue, &present_info)?;
|
||||
}
|
||||
|
||||
// Advance the frame index for the next call
|
||||
self.current_frame = (self.current_frame + 1) % self.in_flight_fences.len();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures all Vulkan resources are destroyed in the correct order.
|
||||
impl Drop for Renderer {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// Ensure the GPU is finished before we start destroying things
|
||||
let _ = self.device.device_wait_idle();
|
||||
|
||||
self.device.destroy_command_pool(self.command_pool, None);
|
||||
|
||||
// Destroy synchronization primitives for all frames
|
||||
for &semaphore in &self.image_available_semaphores {
|
||||
self.device.destroy_semaphore(semaphore, None);
|
||||
}
|
||||
for &semaphore in &self.render_finished_semaphores {
|
||||
self.device.destroy_semaphore(semaphore, None);
|
||||
}
|
||||
for &fence in &self.in_flight_fences {
|
||||
self.device.destroy_fence(fence, None);
|
||||
}
|
||||
|
||||
// Destroy the swapchain
|
||||
self.swapchain_loader.destroy_swapchain(self.swapchain, None);
|
||||
|
||||
// Destroy image views
|
||||
for &view in &self.swapchain_image_views {
|
||||
self.device.destroy_image_view(view, None);
|
||||
}
|
||||
|
||||
// Destroy the logical device
|
||||
self.device.destroy_device(None);
|
||||
|
||||
// Destroy the surface
|
||||
self.surface_loader.destroy_surface(self.surface, None);
|
||||
|
||||
// Destroy the debug messenger if we created one
|
||||
if let Some(utils) = &self.debug_utils {
|
||||
utils.destroy_debug_utils_messenger(self.debug_messenger, None);
|
||||
}
|
||||
|
||||
// Destroy the instance
|
||||
self.instance.destroy_instance(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The callback function invoked by Vulkan's validation layers.
|
||||
///
|
||||
/// This function translates Vulkan debug messages into `tracing` events.
|
||||
unsafe extern "system" fn vulkan_debug_callback(
|
||||
message_severity: vk::DebugUtilsMessageSeverityFlagsEXT,
|
||||
_message_type: vk::DebugUtilsMessageTypeFlagsEXT,
|
||||
p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>,
|
||||
_user_data: *mut std::ffi::c_void,
|
||||
) -> vk::Bool32 {
|
||||
// Safety: p_callback_data is guaranteed to be valid by the Vulkan spec
|
||||
let callback_data = unsafe { *p_callback_data };
|
||||
|
||||
// Convert raw C string to Rust string for logging
|
||||
let message = if callback_data.p_message.is_null() {
|
||||
"".into()
|
||||
} else {
|
||||
unsafe { CStr::from_ptr(callback_data.p_message).to_string_lossy() }
|
||||
};
|
||||
|
||||
// Route severity to matching tracing macro
|
||||
match message_severity {
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => debug!("{message}"),
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::INFO => info!("{message}"),
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => warn!("{message}"),
|
||||
vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => error!("{message}"),
|
||||
_ => info!("{message}"),
|
||||
}
|
||||
|
||||
// TRUE aborts the Vulkan call that triggered the warning
|
||||
vk::FALSE
|
||||
}
|
||||
207
crates/renderer/src/renderer.rs
Normal file
207
crates/renderer/src/renderer.rs
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
use ash::{Device, Instance, khr, vk};
|
||||
use crate::error::RendererError;
|
||||
use crate::sync::SyncPrimitives;
|
||||
|
||||
pub const MAX_FRAMES_IN_FLIGHT: usize = 2;
|
||||
|
||||
/// The core renderer structure holding the Vulkan resources.
|
||||
pub struct Renderer {
|
||||
pub(crate) _entry: ash::Entry,
|
||||
pub(crate) instance: Instance,
|
||||
pub(crate) debug_utils: Option<ash::ext::debug_utils::Instance>,
|
||||
pub(crate) debug_messenger: vk::DebugUtilsMessengerEXT,
|
||||
pub(crate) physical_device: vk::PhysicalDevice,
|
||||
pub(crate) device: Device,
|
||||
pub(crate) graphics_queue: vk::Queue,
|
||||
pub(crate) graphics_queue_index: u32,
|
||||
pub(crate) surface_loader: khr::surface::Instance,
|
||||
pub(crate) surface: vk::SurfaceKHR,
|
||||
pub(crate) swapchain_loader: khr::swapchain::Device,
|
||||
pub(crate) swapchain: vk::SwapchainKHR,
|
||||
pub(crate) swapchain_images: Vec<vk::Image>,
|
||||
pub(crate) swapchain_format: vk::Format,
|
||||
pub(crate) swapchain_extent: vk::Extent2D,
|
||||
pub(crate) swapchain_image_views: Vec<vk::ImageView>,
|
||||
pub(crate) command_pool: vk::CommandPool,
|
||||
pub(crate) command_buffers: Vec<vk::CommandBuffer>,
|
||||
pub(crate) sync: SyncPrimitives,
|
||||
pub(crate) current_frame: usize,
|
||||
}
|
||||
|
||||
impl Renderer {
|
||||
/// Renders a single frame.
|
||||
pub fn draw_frame(&mut self) -> Result<(), RendererError> {
|
||||
let in_flight_fence = self.sync.in_flight[self.current_frame];
|
||||
let image_available_semaphore = self.sync.image_available[self.current_frame];
|
||||
let cmd = self.command_buffers[self.current_frame];
|
||||
|
||||
// 1. Wait for the current frame's GPU work to finish
|
||||
unsafe {
|
||||
self.device.wait_for_fences(&[in_flight_fence], true, u64::MAX)?;
|
||||
self.device.reset_fences(&[in_flight_fence])?;
|
||||
}
|
||||
|
||||
// 2. Acquire an image from the swapchain
|
||||
let (image_index, _is_suboptimal) = unsafe {
|
||||
self.swapchain_loader.acquire_next_image(
|
||||
self.swapchain,
|
||||
u64::MAX,
|
||||
image_available_semaphore,
|
||||
vk::Fence::null(),
|
||||
)?
|
||||
};
|
||||
|
||||
// If the acquired image is still being used by a previous frame, wait for it
|
||||
let image_fence = self.sync.images_in_flight[image_index as usize];
|
||||
if image_fence != vk::Fence::null() {
|
||||
unsafe { self.device.wait_for_fences(&[image_fence], true, u64::MAX)? };
|
||||
}
|
||||
// Mark the image as being in use by the current frame's fence
|
||||
self.sync.images_in_flight[image_index as usize] = in_flight_fence;
|
||||
|
||||
// Use the semaphore tied to this specific swapchain image for rendering completion
|
||||
let render_finished_semaphore = self.sync.render_finished[image_index as usize];
|
||||
|
||||
// 3. Reset and begin recording the command buffer
|
||||
unsafe {
|
||||
self.device.reset_command_buffer(cmd, vk::CommandBufferResetFlags::empty())?;
|
||||
let begin_info = vk::CommandBufferBeginInfo::default()
|
||||
.flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT);
|
||||
self.device.begin_command_buffer(cmd, &begin_info)?;
|
||||
}
|
||||
|
||||
let image = self.swapchain_images[image_index as usize];
|
||||
let view = self.swapchain_image_views[image_index as usize];
|
||||
|
||||
// 4. Transition the swapchain image to a layout suitable for drawing
|
||||
let range = vk::ImageSubresourceRange {
|
||||
aspect_mask: vk::ImageAspectFlags::COLOR,
|
||||
base_mip_level: 0,
|
||||
level_count: 1,
|
||||
base_array_layer: 0,
|
||||
layer_count: 1,
|
||||
};
|
||||
|
||||
let barrier_to_draw = vk::ImageMemoryBarrier2::default()
|
||||
.image(image)
|
||||
.subresource_range(range)
|
||||
.src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT)
|
||||
.src_access_mask(vk::AccessFlags2::empty())
|
||||
.dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT)
|
||||
.dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE)
|
||||
.old_layout(vk::ImageLayout::UNDEFINED)
|
||||
.new_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL);
|
||||
|
||||
let dependency_info = vk::DependencyInfo::default()
|
||||
.image_memory_barriers(std::slice::from_ref(&barrier_to_draw));
|
||||
|
||||
unsafe { self.device.cmd_pipeline_barrier2(cmd, &dependency_info) };
|
||||
|
||||
// 5. Begin Dynamic Rendering with a clear color
|
||||
let color_attachment = vk::RenderingAttachmentInfo::default()
|
||||
.image_view(view)
|
||||
.image_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||
.load_op(vk::AttachmentLoadOp::CLEAR)
|
||||
.store_op(vk::AttachmentStoreOp::STORE)
|
||||
.clear_value(vk::ClearValue {
|
||||
color: vk::ClearColorValue {
|
||||
float32: [0.1, 0.2, 0.4, 1.0], // Project Catalyst Blue
|
||||
},
|
||||
});
|
||||
|
||||
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));
|
||||
|
||||
unsafe {
|
||||
self.device.cmd_begin_rendering(cmd, &rendering_info);
|
||||
// Future draw calls will go here
|
||||
self.device.cmd_end_rendering(cmd);
|
||||
}
|
||||
|
||||
// 6. Transition the image back to Present layout
|
||||
let barrier_to_present = vk::ImageMemoryBarrier2::default()
|
||||
.image(image)
|
||||
.subresource_range(range)
|
||||
.src_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT)
|
||||
.src_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE)
|
||||
.dst_stage_mask(vk::PipelineStageFlags2::BOTTOM_OF_PIPE)
|
||||
.dst_access_mask(vk::AccessFlags2::empty())
|
||||
.old_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL)
|
||||
.new_layout(vk::ImageLayout::PRESENT_SRC_KHR);
|
||||
|
||||
let dependency_info = vk::DependencyInfo::default()
|
||||
.image_memory_barriers(std::slice::from_ref(&barrier_to_present));
|
||||
|
||||
unsafe {
|
||||
self.device.cmd_pipeline_barrier2(cmd, &dependency_info);
|
||||
self.device.end_command_buffer(cmd)?;
|
||||
}
|
||||
|
||||
// 7. Submit the work to the GPU
|
||||
let submit_info = vk::SubmitInfo::default()
|
||||
.wait_semaphores(std::slice::from_ref(&image_available_semaphore))
|
||||
.wait_dst_stage_mask(&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT])
|
||||
.command_buffers(std::slice::from_ref(&cmd))
|
||||
.signal_semaphores(std::slice::from_ref(&render_finished_semaphore));
|
||||
|
||||
unsafe {
|
||||
self.device.queue_submit(self.graphics_queue, &[submit_info], in_flight_fence)?;
|
||||
}
|
||||
|
||||
// 8. Present the result to the screen
|
||||
let present_info = vk::PresentInfoKHR::default()
|
||||
.wait_semaphores(std::slice::from_ref(&render_finished_semaphore))
|
||||
.swapchains(std::slice::from_ref(&self.swapchain))
|
||||
.image_indices(std::slice::from_ref(&image_index));
|
||||
|
||||
unsafe {
|
||||
self.swapchain_loader.queue_present(self.graphics_queue, &present_info)?;
|
||||
}
|
||||
|
||||
// Advance the frame index for the next call
|
||||
self.current_frame = (self.current_frame + 1) % MAX_FRAMES_IN_FLIGHT;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Renderer {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _ = self.device.device_wait_idle();
|
||||
|
||||
self.device.destroy_command_pool(self.command_pool, None);
|
||||
|
||||
// Use the safe cleanup function from sync module
|
||||
let sync = std::ptr::read(&self.sync);
|
||||
crate::sync::destroy_sync_primitives(&self.device, sync);
|
||||
|
||||
// Destroy the swapchain
|
||||
self.swapchain_loader.destroy_swapchain(self.swapchain, None);
|
||||
|
||||
// Destroy image views
|
||||
for &view in &self.swapchain_image_views {
|
||||
self.device.destroy_image_view(view, None);
|
||||
}
|
||||
|
||||
// Destroy the logical device
|
||||
self.device.destroy_device(None);
|
||||
|
||||
// Destroy the surface
|
||||
self.surface_loader.destroy_surface(self.surface, None);
|
||||
|
||||
// Destroy the debug messenger if we created one
|
||||
if let Some(utils) = &self.debug_utils {
|
||||
utils.destroy_debug_utils_messenger(self.debug_messenger, None);
|
||||
}
|
||||
|
||||
// Destroy the instance
|
||||
self.instance.destroy_instance(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
18
crates/renderer/src/surface.rs
Normal file
18
crates/renderer/src/surface.rs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
use ash::{Entry, Instance, khr, vk};
|
||||
use raw_window_handle::{RawDisplayHandle, RawWindowHandle};
|
||||
use crate::error::RendererError;
|
||||
|
||||
/// Creates a Vulkan surface for the given window.
|
||||
pub fn create_surface(
|
||||
entry: &Entry,
|
||||
instance: &Instance,
|
||||
display_handle: RawDisplayHandle,
|
||||
window_handle: RawWindowHandle,
|
||||
) -> Result<(khr::surface::Instance, vk::SurfaceKHR), RendererError> {
|
||||
let surface = unsafe {
|
||||
ash_window::create_surface(entry, instance, display_handle, window_handle, None)?
|
||||
};
|
||||
let surface_loader = khr::surface::Instance::new(entry, instance);
|
||||
|
||||
Ok((surface_loader, surface))
|
||||
}
|
||||
96
crates/renderer/src/swapchain.rs
Normal file
96
crates/renderer/src/swapchain.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
use ash::{Device, Instance, khr, vk};
|
||||
use crate::error::RendererError;
|
||||
|
||||
/// Creates a swapchain and retrieves its images.
|
||||
pub fn create_swapchain(
|
||||
instance: &Instance,
|
||||
physical_device: vk::PhysicalDevice,
|
||||
device: &Device,
|
||||
surface_loader: &khr::surface::Instance,
|
||||
surface: vk::SurfaceKHR,
|
||||
width: u32,
|
||||
height: u32,
|
||||
) -> Result<(khr::swapchain::Device, vk::SwapchainKHR, Vec<vk::Image>, vk::Format, vk::Extent2D), RendererError> {
|
||||
let surface_capabilities = unsafe {
|
||||
surface_loader.get_physical_device_surface_capabilities(physical_device, surface)?
|
||||
};
|
||||
let surface_formats = unsafe {
|
||||
surface_loader.get_physical_device_surface_formats(physical_device, surface)?
|
||||
};
|
||||
let surface_present_modes = unsafe {
|
||||
surface_loader.get_physical_device_surface_present_modes(physical_device, surface)?
|
||||
};
|
||||
|
||||
let format = surface_formats.iter()
|
||||
.find(|f| f.format == vk::Format::B8G8R8A8_SRGB && f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR)
|
||||
.unwrap_or(&surface_formats[0]);
|
||||
|
||||
let present_mode = surface_present_modes.iter()
|
||||
.copied()
|
||||
.find(|&m| m == vk::PresentModeKHR::MAILBOX)
|
||||
.unwrap_or(vk::PresentModeKHR::FIFO);
|
||||
|
||||
let extent = if surface_capabilities.current_extent.width != u32::MAX {
|
||||
surface_capabilities.current_extent
|
||||
} else {
|
||||
vk::Extent2D {
|
||||
width: width.clamp(surface_capabilities.min_image_extent.width, surface_capabilities.max_image_extent.width),
|
||||
height: height.clamp(surface_capabilities.min_image_extent.height, surface_capabilities.max_image_extent.height),
|
||||
}
|
||||
};
|
||||
|
||||
let image_count = if surface_capabilities.max_image_count > 0 && surface_capabilities.min_image_count + 1 > surface_capabilities.max_image_count {
|
||||
surface_capabilities.max_image_count
|
||||
} else {
|
||||
surface_capabilities.min_image_count + 1
|
||||
};
|
||||
|
||||
let swapchain_loader = khr::swapchain::Device::new(instance, device);
|
||||
|
||||
let create_info = vk::SwapchainCreateInfoKHR::default()
|
||||
.surface(surface)
|
||||
.min_image_count(image_count)
|
||||
.image_format(format.format)
|
||||
.image_color_space(format.color_space)
|
||||
.image_extent(extent)
|
||||
.image_array_layers(1)
|
||||
.image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT)
|
||||
.image_sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||
.pre_transform(surface_capabilities.current_transform)
|
||||
.composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE)
|
||||
.present_mode(present_mode)
|
||||
.clipped(true);
|
||||
|
||||
let swapchain = unsafe { swapchain_loader.create_swapchain(&create_info, None)? };
|
||||
let images = unsafe { swapchain_loader.get_swapchain_images(swapchain)? };
|
||||
|
||||
Ok((swapchain_loader, swapchain, images, format.format, extent))
|
||||
}
|
||||
|
||||
/// Creates image views for the swapchain images.
|
||||
pub fn create_image_views(
|
||||
device: &Device,
|
||||
images: &[vk::Image],
|
||||
format: vk::Format,
|
||||
) -> Result<Vec<vk::ImageView>, RendererError> {
|
||||
let mut views = Vec::with_capacity(images.len());
|
||||
|
||||
for &image in images {
|
||||
let create_info = vk::ImageViewCreateInfo::default()
|
||||
.image(image)
|
||||
.view_type(vk::ImageViewType::TYPE_2D)
|
||||
.format(format)
|
||||
.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.create_image_view(&create_info, None)? };
|
||||
views.push(view);
|
||||
}
|
||||
|
||||
Ok(views)
|
||||
}
|
||||
58
crates/renderer/src/sync.rs
Normal file
58
crates/renderer/src/sync.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
use ash::{Device, vk};
|
||||
use crate::error::RendererError;
|
||||
|
||||
/// Groups all synchronization primitives for the renderer.
|
||||
pub struct SyncPrimitives {
|
||||
pub image_available: Vec<vk::Semaphore>,
|
||||
pub render_finished: Vec<vk::Semaphore>,
|
||||
pub in_flight: Vec<vk::Fence>,
|
||||
pub images_in_flight: Vec<vk::Fence>,
|
||||
}
|
||||
|
||||
/// Creates all synchronization primitives for the given number of frames and images.
|
||||
pub fn create_sync_primitives(
|
||||
device: &Device,
|
||||
max_frames_in_flight: usize,
|
||||
image_count: usize,
|
||||
) -> Result<SyncPrimitives, RendererError> {
|
||||
let semaphore_info = vk::SemaphoreCreateInfo::default();
|
||||
let fence_info = vk::FenceCreateInfo::default()
|
||||
.flags(vk::FenceCreateFlags::SIGNALED);
|
||||
|
||||
let mut image_available = Vec::with_capacity(max_frames_in_flight);
|
||||
let mut render_finished = Vec::with_capacity(image_count);
|
||||
let mut in_flight = Vec::with_capacity(max_frames_in_flight);
|
||||
|
||||
for _ in 0..max_frames_in_flight {
|
||||
image_available.push(unsafe { device.create_semaphore(&semaphore_info, None)? });
|
||||
in_flight.push(unsafe { device.create_fence(&fence_info, None)? });
|
||||
}
|
||||
|
||||
for _ in 0..image_count {
|
||||
render_finished.push(unsafe { device.create_semaphore(&semaphore_info, None)? });
|
||||
}
|
||||
|
||||
let images_in_flight = vec![vk::Fence::null(); image_count];
|
||||
|
||||
Ok(SyncPrimitives {
|
||||
image_available,
|
||||
render_finished,
|
||||
in_flight,
|
||||
images_in_flight,
|
||||
})
|
||||
}
|
||||
|
||||
/// Destroys all synchronization primitives.
|
||||
pub unsafe fn destroy_sync_primitives(device: &Device, sync: SyncPrimitives) {
|
||||
unsafe {
|
||||
for semaphore in sync.image_available {
|
||||
device.destroy_semaphore(semaphore, None);
|
||||
}
|
||||
for semaphore in sync.render_finished {
|
||||
device.destroy_semaphore(semaphore, None);
|
||||
}
|
||||
for fence in sync.in_flight {
|
||||
device.destroy_fence(fence, None);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue