use crate::core::{
self, CommandBuffer, CommandPool, Device, Fence, Instance, InstanceCreateInfo, Queue, Semaphore,
};
use crate::ex::errors::RuntimeError;
use ash::vk;
use std::sync::Arc;
pub struct RuntimeConfig {
pub app_name: String,
pub app_version: u32,
pub enable_validation: bool,
pub in_flight_frames: usize,
pub device_extensions: Vec<String>,
#[allow(clippy::type_complexity)]
pub physical_device_selector: Option<Box<dyn Fn(&core::PhysicalDeviceInfo) -> bool>>,
}
impl Default for RuntimeConfig {
fn default() -> Self {
Self {
app_name: "shdrlib application".to_string(),
app_version: 1,
enable_validation: cfg!(debug_assertions),
in_flight_frames: 2,
device_extensions: vec![],
physical_device_selector: None,
}
}
}
pub struct FrameContext<'a> {
pub device: Arc<Device>,
pub command_buffer: CommandBuffer,
pub frame_index: usize,
_runtime: &'a mut RuntimeManager,
}
impl<'a> FrameContext<'a> {
#[inline]
pub fn cmd(&self) -> &CommandBuffer {
&self.command_buffer
}
#[inline]
pub fn image_available_semaphore(&self) -> vk::Semaphore {
self._runtime.image_available_semaphore()
}
#[inline]
pub fn render_finished_semaphore(&self) -> vk::Semaphore {
self._runtime.render_finished_semaphore()
}
}
#[derive(Default)]
pub struct SubmitInfo {
pub wait_semaphores: Vec<vk::Semaphore>,
pub signal_semaphores: Vec<vk::Semaphore>,
pub wait_stages: Vec<vk::PipelineStageFlags>,
}
pub struct RuntimeManager {
command_buffers: Vec<CommandBuffer>,
command_pool: CommandPool,
render_finished: Vec<Semaphore>,
image_available: Vec<Semaphore>,
in_flight_fences: Vec<Fence>,
graphics_queue: Queue,
device: Arc<Device>,
physical_device: vk::PhysicalDevice,
#[allow(dead_code)]
instance: Instance,
current_frame: usize,
graphics_family_index: u32,
in_flight_frames: usize,
}
impl RuntimeManager {
pub fn new(config: RuntimeConfig) -> Result<Self, RuntimeError> {
let instance = Instance::new(InstanceCreateInfo {
app_name: config.app_name.clone(),
app_version: config.app_version,
enable_validation: config.enable_validation,
extensions: vec![], })?;
let physical_devices = instance.enumerate_physical_devices()?;
if physical_devices.is_empty() {
return Err(RuntimeError::NoSuitableDevice(0));
}
let physical_device = Self::select_physical_device(
&instance,
&physical_devices,
config.physical_device_selector.as_ref(),
)?;
let queue_families =
unsafe { instance.get_physical_device_queue_family_properties(physical_device) };
let graphics_family_index = queue_families
.iter()
.position(|qf| qf.queue_flags.contains(vk::QueueFlags::GRAPHICS))
.ok_or(RuntimeError::NoGraphicsQueue)? as u32;
let device = Device::new(
&instance,
physical_device,
core::DeviceCreateInfo {
extensions: config.device_extensions.clone(),
features: Default::default(),
queue_create_infos: vec![core::QueueCreateInfo {
queue_family_index: graphics_family_index,
queue_count: 1,
queue_priorities: vec![1.0],
}],
},
)?;
let device = Arc::new(device);
let graphics_queue = Queue::get(&device, graphics_family_index, 0);
let mut in_flight_fences = Vec::with_capacity(config.in_flight_frames);
let mut image_available = Vec::with_capacity(config.in_flight_frames);
let mut render_finished = Vec::with_capacity(config.in_flight_frames);
for _ in 0..config.in_flight_frames {
in_flight_fences.push(Fence::new(&device, true)?); image_available.push(Semaphore::new(&device)?);
render_finished.push(Semaphore::new(&device)?);
}
let command_pool = CommandPool::new(
&device,
graphics_family_index,
vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER,
)?;
let command_buffers = command_pool.allocate(
&device,
vk::CommandBufferLevel::PRIMARY,
config.in_flight_frames as u32,
)?;
Ok(Self {
instance,
physical_device,
device,
graphics_queue,
graphics_family_index,
in_flight_fences,
image_available,
render_finished,
command_pool,
command_buffers,
current_frame: 0,
in_flight_frames: config.in_flight_frames,
})
}
#[allow(clippy::type_complexity)]
fn select_physical_device(
instance: &Instance,
devices: &[vk::PhysicalDevice],
selector: Option<&Box<dyn Fn(&core::PhysicalDeviceInfo) -> bool>>,
) -> Result<vk::PhysicalDevice, RuntimeError> {
match selector {
Some(selector_fn) => {
for &device in devices {
let info = core::PhysicalDeviceInfo {
device,
properties: unsafe { instance.get_physical_device_properties(device) },
features: unsafe { instance.get_physical_device_features(device) },
memory_properties: unsafe {
instance.get_physical_device_memory_properties(device)
},
queue_families: unsafe {
instance.get_physical_device_queue_family_properties(device)
},
};
if selector_fn(&info) {
return Ok(device);
}
}
Err(RuntimeError::NoSuitableDevice(devices.len()))
}
None => {
let discrete = devices.iter().find(|&&device| {
let props = unsafe { instance.get_physical_device_properties(device) };
props.device_type == vk::PhysicalDeviceType::DISCRETE_GPU
});
match discrete {
Some(&device) => Ok(device),
None => devices
.first()
.copied()
.ok_or(RuntimeError::NoSuitableDevice(0)),
}
}
}
}
pub fn begin_frame(&mut self) -> Result<FrameContext<'_>, RuntimeError> {
let fence = &self.in_flight_fences[self.current_frame];
fence.wait(&self.device, u64::MAX)?;
fence.reset(&self.device)?;
let command_buffer = self.command_buffers[self.current_frame].handle();
Ok(FrameContext {
device: Arc::clone(&self.device),
command_buffer: CommandBuffer::from_handle(
command_buffer,
vk::CommandBufferLevel::PRIMARY,
),
frame_index: self.current_frame,
_runtime: self,
})
}
pub fn end_frame(&mut self, submit_info: &SubmitInfo) -> Result<(), RuntimeError> {
let command_buffer_handle = self.command_buffers[self.current_frame].handle();
let fence = &self.in_flight_fences[self.current_frame];
let mut wait_semaphores = vec![self.image_available[self.current_frame].handle()];
wait_semaphores.extend(&submit_info.wait_semaphores);
let mut wait_stages = if submit_info.wait_stages.is_empty() {
vec![vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT; wait_semaphores.len()]
} else {
submit_info.wait_stages.clone()
};
while wait_stages.len() < wait_semaphores.len() {
wait_stages.push(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT);
}
let mut signal_semaphores = vec![self.render_finished[self.current_frame].handle()];
signal_semaphores.extend(&submit_info.signal_semaphores);
self.graphics_queue.submit(
&self.device,
&[command_buffer_handle],
&wait_semaphores,
&wait_stages,
&signal_semaphores,
Some(fence.handle()),
)?;
self.current_frame = (self.current_frame + 1) % self.in_flight_frames;
Ok(())
}
#[inline]
pub fn device(&self) -> Arc<Device> {
Arc::clone(&self.device)
}
#[inline]
pub fn physical_device(&self) -> vk::PhysicalDevice {
self.physical_device
}
#[inline]
pub fn graphics_family(&self) -> u32 {
self.graphics_family_index
}
#[inline]
pub fn graphics_queue(&self) -> &Queue {
&self.graphics_queue
}
#[inline]
pub fn current_command_buffer(&self) -> vk::CommandBuffer {
self.command_buffers[self.current_frame].handle()
}
#[inline]
pub fn instance(&self) -> &Instance {
&self.instance
}
#[inline]
pub fn image_available_semaphore(&self) -> vk::Semaphore {
self.image_available[self.current_frame].handle()
}
#[inline]
pub fn render_finished_semaphore(&self) -> vk::Semaphore {
self.render_finished[self.current_frame].handle()
}
pub fn wait_idle(&self) -> Result<(), RuntimeError> {
self.device.wait_idle()?;
Ok(())
}
}
impl Drop for RuntimeManager {
fn drop(&mut self) {
let _ = self.device.wait_idle();
for fence in &mut self.in_flight_fences {
fence.destroy(&self.device);
}
for semaphore in &mut self.image_available {
semaphore.destroy(&self.device);
}
for semaphore in &mut self.render_finished {
semaphore.destroy(&self.device);
}
self.command_pool.destroy(&self.device);
}
}
#[cfg(test)]
mod tests {
use super::*;
fn test_config() -> RuntimeConfig {
RuntimeConfig {
enable_validation: false, ..Default::default()
}
}
#[test]
fn test_runtime_manager_creation_with_defaults() {
let runtime = RuntimeManager::new(test_config());
assert!(runtime.is_ok(), "RuntimeManager creation should succeed");
}
#[test]
fn test_runtime_manager_device_access() {
let runtime = RuntimeManager::new(test_config()).unwrap();
let device = runtime.device();
assert!(Arc::strong_count(&device) >= 1);
}
#[test]
fn test_runtime_manager_custom_config() {
let config = RuntimeConfig {
app_name: "Test App".to_string(),
app_version: 42,
enable_validation: false,
in_flight_frames: 3,
..Default::default()
};
let runtime = RuntimeManager::new(config);
assert!(runtime.is_ok());
}
#[test]
fn test_frame_begin_end_cycle() {
let mut runtime = RuntimeManager::new(test_config()).unwrap();
let frame = runtime.begin_frame();
assert!(frame.is_ok());
let frame = frame.unwrap();
assert_eq!(frame.frame_index, 0);
frame
.command_buffer
.begin(&frame.device, vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT)
.unwrap();
frame.command_buffer.end(&frame.device).unwrap();
let result = runtime.end_frame(&SubmitInfo::default());
assert!(result.is_ok());
assert_eq!(runtime.current_frame, 1);
}
}