mod data; mod structs; use data::AppData; use structs::*; use ::anyhow::{anyhow, Result}; use ::lazy_static::lazy_static; use ::nalgebra_glm as glm; use ::std::collections::HashSet; use ::std::mem::size_of; use ::std::ptr::copy_nonoverlapping as memcpy; use ::std::time::Instant; use ::vulkanalia::loader::{LibloadingLoader, LIBRARY}; use ::vulkanalia::prelude::v1_0::*; use ::vulkanalia::vk::{KhrSurfaceExtension, KhrSwapchainExtension}; 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; lazy_static! { pub static ref VERTICES: Vec = vec![ Vertex::new(glm::vec2(-0.5, -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)), Vertex::new(glm::vec2(-0.5, 0.5), glm::vec3(1.0, 1.0, 1.0)), ]; } pub const INDICES: &[u16] = &[0, 1, 2, 2, 3, 0]; /// 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, start: Instant, } 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, device) = data.create(window, &entry)?; Ok(Self { _entry: entry, instance, data, device, frame: 0, resized: false, start: Instant::now(), }) } } /// Destroys our Vulkan app, in reverse order of creation /// /// # Safety /// Here be Dragons pub fn destroy(&mut self) { unsafe { self.data.destroy(&self.instance, &self.device); } } /// 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; self.update_uniform_buffer(image_index)?; 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.data .recreate_swapchain(window, &self.instance, &self.device)?; } else if let Err(e) = result { return Err(anyhow!(e)); } self.frame = (self.frame + 1) % MAX_FRAMES_IN_FLIGHT; } Ok(()) } unsafe fn update_uniform_buffer(&self, image_index: usize) -> Result<()> { let time = self.start.elapsed().as_secs_f32(); let model = glm::rotate( &glm::identity(), time * glm::radians(&glm::vec1(90.0))[0], &glm::vec3(0.0, 0.0, 1.0), ); let view = glm::look_at( &glm::vec3(2.0, 2.0, 2.0), &glm::vec3(0.0, 0.0, 0.0), &glm::vec3(0.0, 0.0, 1.0), ); let mut proj = glm::perspective( self.data.swapchain_extent.width as f32 / self.data.swapchain_extent.height as f32, glm::radians(&glm::vec1(45.0))[0], 0.1, 10.0, ); // Flip the image right-side-up proj[(1, 1)] *= -1.0; let ubo = UniformBufferObject { model, view, proj }; let memory = self.device.map_memory( self.data.uniform_buffers_memory[image_index], 0, size_of::() as u64, vk::MemoryMapFlags::empty(), )?; memcpy(&ubo, memory.cast(), 1); self.device .unmap_memory(self.data.uniform_buffers_memory[image_index]); Ok(()) } /// Recreates the swapchain /// /// # Safety /// Here be Dragons fn recreate_swapchain(&mut self, window: &Window) -> Result<()> { unsafe { self.data .recreate_swapchain(window, &self.instance, &self.device)?; } Ok(()) } } //================================================ // Functions //================================================ pub(crate) unsafe fn check_physical_device_extensions( instance: &Instance, physical_device: vk::PhysicalDevice, ) -> Result<()> { let extensions = instance .enumerate_device_extension_properties(physical_device, None)? .iter() .map(|e| e.extension_name) .collect::>(); if DEVICE_EXTENSIONS.iter().all(|e| extensions.contains(e)) { Ok(()) } else { Err(anyhow!(SuitabilityError( "Missing required device extensions." ))) } } pub(crate) fn get_swapchain_surface_format( formats: &[vk::SurfaceFormatKHR], ) -> vk::SurfaceFormatKHR { formats .iter() .cloned() .find(|f| { f.format == vk::Format::B8G8R8A8_SRGB && f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR }) .unwrap_or_else(|| formats[0]) } pub(crate) fn get_swapchain_present_mode( present_modes: &[vk::PresentModeKHR], ) -> vk::PresentModeKHR { present_modes .iter() .cloned() .find(|m| *m == vk::PresentModeKHR::MAILBOX) .unwrap_or(vk::PresentModeKHR::FIFO) } pub(crate) fn get_swapchain_extent( window: &Window, capabilities: vk::SurfaceCapabilitiesKHR, ) -> vk::Extent2D { if capabilities.current_extent.width != u32::MAX { capabilities.current_extent } else { let size = window.inner_size(); let clamp = |min: u32, max: u32, v: u32| min.max(max.min(v)); vk::Extent2D::builder() .width(clamp( capabilities.min_image_extent.width, capabilities.max_image_extent.width, size.width, )) .height(clamp( capabilities.min_image_extent.height, capabilities.max_image_extent.height, size.height, )) .build() } } pub(crate) unsafe fn create_shader_module( device: &Device, bytecode: &[u8], ) -> Result { let bytecode = Vec::::from(bytecode); let (prefix, code, suffix) = bytecode.align_to::(); if !prefix.is_empty() || !suffix.is_empty() { return Err(anyhow!("Shader bytecode is not properly aligned.")); } let info = vk::ShaderModuleCreateInfo::builder() .code_size(bytecode.len()) .code(code); Ok(device.create_shader_module(&info, None)?) } pub(crate) unsafe fn create_image_view( device: &Device, image: vk::Image, format: vk::Format, ) -> Result { let subresource_range = vk::ImageSubresourceRange::builder() .aspect_mask(vk::ImageAspectFlags::COLOR) .base_mip_level(0) .level_count(1) .base_array_layer(0) .layer_count(1); let info = vk::ImageViewCreateInfo::builder() .image(image) .view_type(vk::ImageViewType::_2D) .format(format) .subresource_range(subresource_range); Ok(device.create_image_view(&info, None)?) }