mod data; mod functions; use data::AppData; use crate::VALIDATION_ENABLED; use ::anyhow::{anyhow, Result}; use ::lazy_static::lazy_static; use ::log::*; use ::nalgebra_glm as glm; use ::std::mem::size_of; use ::thiserror::Error; use ::vulkanalia::loader::{LibloadingLoader, LIBRARY}; use ::vulkanalia::prelude::v1_0::*; use ::vulkanalia::vk::{ExtDebugUtilsExtension, KhrSurfaceExtension, KhrSwapchainExtension}; use ::vulkanalia::window as vk_window; use ::vulkanalia::Version; use ::winit::window::Window; /// The name of the validation layers. pub const VALIDATION_LAYER: vk::ExtensionName = vk::ExtensionName::from_bytes(b"VK_LAYER_KHRONOS_validation"); /// The Vulkan SDK version that started requiring the portability subset extension for macOS. pub const PORTABILITY_MACOS_VERSION: Version = Version::new(1, 3, 216); /// The required device extensions pub const DEVICE_EXTENSIONS: &[vk::ExtensionName] = &[vk::KHR_SWAPCHAIN_EXTENSION.name]; /// The maximum number of frames that can be processed concurrently. pub const MAX_FRAMES_IN_FLIGHT: usize = 2; /// Our Vulkan app. #[derive(Clone, Debug)] pub struct App { /// This value needs to stick around, but we don't use it directly _entry: Entry, instance: Instance, data: AppData, pub device: Device, frame: usize, pub resized: bool, } impl App { /// Creates our Vulkan app. /// /// # Safety /// Here be Dragons pub fn create(window: &Window) -> Result { unsafe { let loader = LibloadingLoader::new(LIBRARY)?; let entry = Entry::new(loader).map_err(|b| anyhow!("{}", b))?; let mut data = AppData::default(); let instance = data.create_instance(window, &entry)?; data.surface = vk_window::create_surface(&instance, window)?; data.pick_physical_device(&instance)?; let device = data.create_logical_device(&instance)?; data.create_swapchain(window, &instance, &device)?; data.create_swapchain_image_views(&device)?; data.create_render_pass(&instance, &device)?; data.create_pipeline(&device)?; data.create_framebuffers(&device)?; data.create_command_pool(&instance, &device)?; data.create_vertex_buffer(&instance, &device)?; data.create_command_buffers(&device)?; data.create_sync_objects(&device)?; Ok(Self { _entry: entry, instance, data, device, frame: 0, resized: false, }) } } /// Destroys our Vulkan app, in reverse order of creation /// /// # Safety /// Here be Dragons pub unsafe fn destroy(&mut self) { self.destroy_swapchain(); self.device.destroy_buffer(self.data.vertex_buffer, None); self.device .free_memory(self.data.vertex_buffer_memory, None); self.data .in_flight_fences .iter() .for_each(|f| self.device.destroy_fence(*f, None)); self.data .render_finished_semaphores .iter() .for_each(|s| self.device.destroy_semaphore(*s, None)); self.data .image_available_semaphores .iter() .for_each(|s| self.device.destroy_semaphore(*s, None)); self.device .destroy_command_pool(self.data.command_pool, None); self.device.destroy_device(None); self.instance.destroy_surface_khr(self.data.surface, None); if VALIDATION_ENABLED { self.instance .destroy_debug_utils_messenger_ext(self.data.messenger, None); } self.instance.destroy_instance(None); } /// Cleanup the swapchain and related objects /// /// # Safety /// Here be Dragons unsafe fn destroy_swapchain(&mut self) { self.data .framebuffers .iter() .for_each(|f| self.device.destroy_framebuffer(*f, None)); self.device .free_command_buffers(self.data.command_pool, &self.data.command_buffers); self.device.destroy_pipeline(self.data.pipeline, None); self.device .destroy_pipeline_layout(self.data.pipeline_layout, None); self.device.destroy_render_pass(self.data.render_pass, None); self.data .swapchain_image_views .iter() .for_each(|v| self.device.destroy_image_view(*v, None)); self.device.destroy_swapchain_khr(self.data.swapchain, None); } /// Renders a frame for our Vulkan app. /// /// # Safety /// Here be Dragons pub fn render(&mut self, window: &Window) -> Result<()> { unsafe { let in_flight_fence = self.data.in_flight_fences[self.frame]; self.device .wait_for_fences(&[in_flight_fence], true, u64::max_value())?; let result = self.device.acquire_next_image_khr( self.data.swapchain, u64::max_value(), self.data.image_available_semaphores[self.frame], vk::Fence::null(), ); let image_index = match result { Ok((image_index, _)) => image_index as usize, Err(vk::ErrorCode::OUT_OF_DATE_KHR) => return self.recreate_swapchain(window), Err(e) => return Err(anyhow!(e)), }; let image_in_flight = self.data.images_in_flight[image_index]; if !image_in_flight.is_null() { self.device .wait_for_fences(&[image_in_flight], true, u64::max_value())?; } self.data.images_in_flight[image_index] = in_flight_fence; let wait_semaphores = &[self.data.image_available_semaphores[self.frame]]; let wait_stages = &[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]; let command_buffers = &[self.data.command_buffers[image_index]]; let signal_semaphores = &[self.data.render_finished_semaphores[self.frame]]; let submit_info = vk::SubmitInfo::builder() .wait_semaphores(wait_semaphores) .wait_dst_stage_mask(wait_stages) .command_buffers(command_buffers) .signal_semaphores(signal_semaphores); self.device.reset_fences(&[in_flight_fence])?; self.device .queue_submit(self.data.graphics_queue, &[submit_info], in_flight_fence)?; let swapchains = &[self.data.swapchain]; let image_indices = &[image_index as u32]; let present_info = vk::PresentInfoKHR::builder() .wait_semaphores(signal_semaphores) .swapchains(swapchains) .image_indices(image_indices); let result = self .device .queue_present_khr(self.data.present_queue, &present_info); let changed = result == Ok(vk::SuccessCode::SUBOPTIMAL_KHR) || result == Err(vk::ErrorCode::OUT_OF_DATE_KHR); if self.resized || changed { self.recreate_swapchain(window)?; } else if let Err(e) = result { return Err(anyhow!(e)); } self.frame = (self.frame + 1) % MAX_FRAMES_IN_FLIGHT; } Ok(()) } /// Recreates the swapchain /// /// # Safety /// Here be Dragons fn recreate_swapchain(&mut self, window: &Window) -> Result<()> { unsafe { self.device.device_wait_idle()?; self.destroy_swapchain(); self.data .create_swapchain(window, &self.instance, &self.device)?; self.data.create_swapchain_image_views(&self.device)?; self.data.create_render_pass(&self.instance, &self.device)?; self.data.create_pipeline(&self.device)?; self.data.create_framebuffers(&self.device)?; self.data.create_command_buffers(&self.device)?; self.data .images_in_flight .resize(self.data.swapchain_images.len(), vk::Fence::null()); } Ok(()) } } #[derive(Debug, Error)] #[error("Missing {0}.")] pub struct SuitabilityError(pub &'static str); #[derive(Copy, Clone, Debug)] pub(crate) struct QueueFamilyIndices { graphics: u32, present: u32, } impl QueueFamilyIndices { unsafe fn get( instance: &Instance, data: &AppData, physical_device: vk::PhysicalDevice, ) -> Result { let properties = instance.get_physical_device_queue_family_properties(physical_device); let graphics = properties .iter() .position(|p| p.queue_flags.contains(vk::QueueFlags::GRAPHICS)) .map(|i| i as u32); let mut present = None; for (index, _properties) in properties.iter().enumerate() { if instance.get_physical_device_surface_support_khr( physical_device, index as u32, data.surface, )? { present = Some(index as u32); break; } } if let (Some(graphics), Some(present)) = (graphics, present) { Ok(Self { graphics, present }) } else { Err(anyhow!(SuitabilityError( "Missing required queue families." ))) } } } #[derive(Clone, Debug)] pub(crate) struct SwapchainSupport { capabilities: vk::SurfaceCapabilitiesKHR, formats: Vec, present_modes: Vec, } impl SwapchainSupport { unsafe fn get( instance: &Instance, data: &AppData, physical_device: vk::PhysicalDevice, ) -> Result { Ok(Self { capabilities: instance .get_physical_device_surface_capabilities_khr(physical_device, data.surface)?, formats: instance .get_physical_device_surface_formats_khr(physical_device, data.surface)?, present_modes: instance .get_physical_device_surface_present_modes_khr(physical_device, data.surface)?, }) } } #[repr(C)] #[derive(Copy, Clone, Debug)] pub(crate) struct Vertex { pos: glm::Vec2, color: glm::Vec3, } impl Vertex { fn new(pos: glm::Vec2, color: glm::Vec3) -> Self { Self { pos, color } } pub fn binding_description() -> vk::VertexInputBindingDescription { vk::VertexInputBindingDescription::builder() .binding(0) .stride(size_of::() as u32) .input_rate(vk::VertexInputRate::VERTEX) .build() } pub fn attribute_descriptions() -> [vk::VertexInputAttributeDescription; 2] { let pos = vk::VertexInputAttributeDescription::builder() .binding(0) .location(0) .format(vk::Format::R32G32_SFLOAT) .offset(0) .build(); let color = vk::VertexInputAttributeDescription::builder() .binding(0) .location(1) .format(vk::Format::R32G32B32_SFLOAT) .offset(size_of::() as u32) .build(); [pos, color] } } lazy_static! { pub(crate) static ref VERTICES: Vec = vec![ Vertex::new(glm::vec2(0.0, -0.5), glm::vec3(1.0, 0.0, 0.0)), Vertex::new(glm::vec2(0.5, 0.5), glm::vec3(0.0, 1.0, 0.0)), Vertex::new(glm::vec2(-0.5, 0.5), glm::vec3(0.0, 0.0, 1.0)), ]; }