synvael/crates/renderer/src/device.rs

113 lines
3.9 KiB
Rust

// SPDX-License-Identifier: AGPL-3.0-only
//! Logic for selecting physical devices and creating logical devices.
use crate::error::RendererError;
use ash::{Device, Instance, khr, vk};
/// 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() {
#[expect(clippy::expect_used)]
let index = u32::try_from(index).expect("Queue family index exceeds u32 range");
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()
}