Repository: adrien-ben/vulkan-tutorial-rs Branch: master Commit: 3b3e61b9d987 Files: 16 Total size: 30.8 MB Directory structure: gitextract_7u63e6xb/ ├── .github/ │ └── workflows/ │ └── build.yml ├── .gitignore ├── Cargo.toml ├── README.md ├── assets/ │ ├── models/ │ │ └── chalet.obj │ └── shaders/ │ ├── shader.frag │ └── shader.vert ├── build.rs └── src/ ├── camera.rs ├── context.rs ├── debug.rs ├── fs.rs ├── main.rs ├── math.rs ├── swapchain.rs └── texture.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yml ================================================ name: Cross-platform build on: [push] jobs: # Build the project on linux, windows and macos build: name: Build on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: ["ubuntu-latest", "windows-latest", "macos-latest"] steps: - uses: actions/checkout@v4 - name: Build env: SKIP_SHADER_COMPILATION: true run: cargo build ================================================ FILE: .gitignore ================================================ /target **/*.rs.bk **/*.spv /.vscode Cargo.lock .idea ================================================ FILE: Cargo.toml ================================================ [package] name = "vulkan-tutorial-ash" version = "0.1.0" authors = ["Adrien Bennadji "] edition = "2024" build = "build.rs" rust-version = "1.85" [dependencies] log = "0.4" env_logger = "0.11" ash = "0.38" ash-window = "0.13" raw-window-handle = "0.6" cgmath = "0.18" image = "0.25" tobj = "4.0" ahash = "0.8" winit = "0.30" ================================================ FILE: README.md ================================================ # Vulkan tutorial ![][8] Vulkan [tutorials][0] written in Rust using [Ash][1]. The [extended][10] branch contains a few more chapters that I won't merge on that branch since I want it to stay close to the original tutorial. Please check it out :). If you wan't to run it on android see the [android][11] branch. ![The end result](screenshots/end.png) ## Introduction This repository will follow the structure of the original tutorial. Each commit will correspond to one page or on section of the page for long chapters. Sometimes an 'extra' commit will be added with some refactoring, commenting or feature. All chapters of the original tutorial are now covered. The code compiles on windows, linux and macos and runs on windows, and linux. It should also run on macos but I haven't been able to test yet. I'll update this statement when I (or someone else) can try. ## Requirements You need to have a [Vulkan SDK][3] installed and `glslangValidator` executable in your `PATH`. This should be the case when installing the Vulkan SDK. If for some reason you want to skip the shader compilation when buiding the project you can set the `SKIP_SHADER_COMPILATION` environment variable to `true`. Though you will need to provide the compiled shaders for the program to run. ## Commits This section contains the summary of the project commits. Follow :rabbit2: to go to the related tutorial page. ### 1.1.1: Base code [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Base_code) Application setup. We don't setup the window system now as it's done in the original tutorial. ### 1.1.2: Instance [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Instance) Create and destroy the Vulkan instance with required surface extensions. ### 1.1.3: Validation layers [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Validation_layers) Add `VK_LAYER_LUNARG_standard_validation` at instance creation and creates a debug report callback function after checking that it is available. Since we are using the `log` crate, we log the message with the proper log level. The callback is detroyed at application termination. ### 1.1.4: Physical devices and queue families [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Physical_devices_and_queue_families) Find a physical device with at least a queue family supporting graphics. ### 1.1.5: Logical device and queues [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Setup/Logical_device_and_queues) Create the logical device interfacing with the physical device. Then create the graphics queue from the device. ### 1.1.extra: Refactoring and comments - Update the readme with explanations on the structure of the repository. - Move validation layers related code to its own module. - Disabled validation layers on release build. ### 1.2.1: Window surface [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Presentation/Window_surface) Create the window, the window surface and the presentation queue. Update the physical device creation to get a device with presentation support. At that point, the code will only work on Windows. ### 1.2.2: Swapchain [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Presentation/Swap_chain) Checks for swapchain support and enable device extension for swapchain. Then query the swapchain details and choose the right settings. Then create the swapchain and retrieve the swapchain images. ### 1.2.3: Image views [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Presentation/Image_views) Create the image views to the swapchain images. ### 1.2.extra: Refactoring swapchain creation Add `SwapchainProperties` to hold the format, present mode and extent of our swapchain. Add a method to build the best properties to `SwapchainSupportDetails`. Move these two struct into the `swapchain` module. ### 1.3.2: Shader module [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Shader_modules) Create the vertex and fragment shaders GLSL source and add a `compile.bat` script to compile it into SPIR-V bytecode using `glslangValidator`. Load the compiled SPIR-V and create a `ShaderModule` from it. In this section I forgot to create the shader stage create info structures. It's ok they will be created in `1.3.5: Graphics pipeline`. ### 1.3.3: Fixed functions [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Fixed_functions) This one is huge so it will be split across several commits. - 1.3.3.1: Vertex input and input assembly Create the vertex input and input assembly info for the pipeline. - 1.3.3.2: Viewports and scissors Create the viewport and scissor info for the pipeline. - 1.3.3.3: Rasterizer Create the rasterizer info for the pipeline. - 1.3.3.4: Multisampling Create the multisampling info for the pipeline. - 1.3.3.5: Color blending Create color blend attachment and color blend info for the pipeline. - 1.3.3.6: Pipeline layout Create the pipeline layout info. ### 1.3.4: Render passes [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Render_passes) Create the render pass. ### 1.3.5: Graphics pipeline [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Graphics_pipeline_basics/Conclusion) Create the `PipelineShaderStageCreateInfo` that we forgot in `1.3.2: Shader module`. Create the grahics pipeline. ### 1.3.extra: Shader compilation refactoring Until now we compiled the shaders with a `compile.bat` script that we have to run manually before running the application. In this section, we will compite them when building the application using [Cargo][2]'s build scripts. The build script scan the content of the `shaders` directory and generates a compiled SPIR-V shader for each file it founds. The files are generated in a the same directory as the GLSL shaders and with the same name appended with `.spv`. ### 1.4.1: Framebuffers [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Drawing/Framebuffers) Create one framebuffer for each image of the swapchain. ### 1.4.2: Command buffers [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Drawing/Command_buffers) Create a command pool and allocate one command buffer per swapchain image. Then we register all the commands required to render. ### 1.4.3: Rendering and presentation [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Drawing/Rendering_and_presentation) This section is also split across multiple commits. - 1.4.3.1: Main loop Setup the main loop. - 1.4.3.2: Semaphores Create a semphore to signal that an image has been acquired and another one to signal that the rendering to the image is finished. - 1.4.3.3: Rendering the triangle! Acquire the next image from the swapchain, submit the command buffer and present the rendered image. ![The first triangle!](screenshots/triangle.png) - 1.4.3.4: Frames in flight Limit the number of frames that can be renderer simultaneously using fences. ### 1.4.extra: Refactoring - Add `QueueFamilyIndices` structure and return it at physical device creation to avoid having to recreate it multiple times. - Add `SyncObjects` containing the semaphores and fence for one frame. - Add `InFlightFrames` containing all `SyncObjects` and the current frame index. - Implement `Iterator` for `InFlightFrames` so we just need to call `next()` to get next frame sync objects. ### 1.5: Swapchain recreation [:rabbit2:](https://vulkan-tutorial.com/Drawing_a_triangle/Swap_chain_recreation) Handle swapchain recreation when resizing the window or when the swapchain is suboptimal or out of date. ### 2.1: Vertex input description [:rabbit2:](https://vulkan-tutorial.com/Vertex_buffers/Vertex_input_description) Remove hard coded vertices from the vertex shader source and create vertices on the cpu. Update the pipeline with the vertex binding and attributes description. ### 2.2: Vertex buffer creation [:rabbit2:](https://vulkan-tutorial.com/Vertex_buffers/Vertex_buffer_creation) Create and fill the vertex buffer and bind it before rendering. ### 2.3: Staging buffer [:rabbit2:](https://vulkan-tutorial.com/Vertex_buffers/Staging_buffer) Create a staging buffer for the vertex data and copy the vertex data from this buffer's memory to the memory of the device local buffer. The tutorial also suggests that we allocate command buffers used for memory copy from a command pool dedicated to short-lived command buffers, so we did that too. ### 2.4: Index buffer [:rabbit2:](https://vulkan-tutorial.com/Vertex_buffers/Index_buffer) Use index buffer to reuse vertice when drawing a rectangle. In the original tutorial the `create_index_buffer` is the same as `create_vertex_buffer` but with the vertex data replaced with the index data. To limit duplication we've added a method that creates and fill a buffer and fill it with the passed data. This method is called from `create_vertex_buffer` and `create_index_buffer`. ![Then a quad.](screenshots/quad.png) ### 3.1: Descriptor layout and buffer [:rabbit2:](https://vulkan-tutorial.com/Uniform_buffers/Descriptor_layout_and_buffer) Create a `UniformBufferObject` structure containing transformation matrices and create the descriptor layout and buffers used to make it accessible from the vertex shader. Also add a `math` module containing a `perspective` function that creates a prespective matrix that is working with Vulkan's NDC. ### 3.2: Descriptor pool and sets [:rabbit2:](https://vulkan-tutorial.com/Uniform_buffers/Descriptor_pool_and_sets) Create a descriptor pool and allocate a descriptor set for each descriptor buffer. ![With MVP matrices.](screenshots/ubo.png) ### 4.1: Images [:rabbit2:](https://vulkan-tutorial.com/Texture_mapping/Images) This section is split too. - 4.1.1: Loading an image Load an image from a file. - 4.1.2: Creating the image Create an host visible staging buffer for image data and create a device local image. At this point the image is empty, we will copy the buffer data in a later section. - 4.1.3: Copying buffer data into the image Copy the image data store in the host visible buffer to the device local image. ### 4.2: Image view and sampler [:rabbit2:](https://vulkan-tutorial.com/Texture_mapping/Image_view_and_sampler) Create the image view and sampler. Also enable the sampler anisotropy feature. ### 4.3: Combined image sampler [:rabbit2:](https://vulkan-tutorial.com/Texture_mapping/Combined_image_sampler) Update the descriptor set, add texture coordinates to `Vertex` and update the shaders to read texture coordinates and sample the texture. ![Textured.](screenshots/texture.png) ### 5: Depth buffering [:rabbit2:](https://vulkan-tutorial.com/Depth_buffering) Update `Vertex` to make the position 3d. Update the vertex shader to take the new dimension into account. Add a new quad to render. And setup depth buffer so the new quad is renderer correctly relatively to the other. Recreate the depth buffer resources when the swapchain is recreated. ![With a friend!](screenshots/depth.png) ### 5.extra: Refactoring Add `Texture` struct which will hold the resources required by mutable image, (image, memory, view and optionnally a sampler). Add `VkContext` that will hold the instance, debug callback, physical and logical devices, and surface. Overall refactoring of the code with some Rust specific code smell fixes. ### 6: Loading models [:rabbit2:](https://vulkan-tutorial.com/Loading_models) Load a 3D model from an wavefront obj file and render it. We skip the deduplication step because the crate we use to load obj files already does it. ### 6.extra: Orbital camera Since `3.1: Descriptor layout and buffer`, our rendered geometry has been spinning infinitely around its local z axis. In this chapter we change this behaviour and implement an orbital camera controlled with the mouse. You can scroll the mouse wheel to get closer or further away from the global origin. And you can left click and move the mouse to move around the global origin. ### 7: Generating mipmaps [:rabbit2:](https://vulkan-tutorial.com/Generating_Mipmaps) Generate mipmaps for the model texture and update the sampler to make use of them. ### 8: Multisampling [:rabbit2:](https://vulkan-tutorial.com/Multisampling) Add multisampling anti-aliasing. ![The end result](screenshots/end.png) ## Run it With validation layers: ```sh RUST_LOG=vulkan_tutorial_ash=debug cargo run ``` > The RUST_LOG level will affect the log level of the validation layers too. or without: ```sh cargo run --release ``` ## Links [Vulkan tutotial][0] [Ash][1] [Rust docs][4] [Cargo docs][2] [Vulkan SDK][3] [Vulkan specs][5] [The image statue][6] [The 3D model][7] ## Credits Thanks to Alexander Overvoorde for this amazing tutorials. [0]: https://vulkan-tutorial.com/Introduction [1]: https://github.com/MaikKlein/ash [2]: https://doc.rust-lang.org/cargo [3]: https://www.lunarg.com/vulkan-sdk [4]: https://doc.rust-lang.org/ [5]: https://www.khronos.org/registry/vulkan/specs/1.1/html/ [6]: https://pixabay.com/en/statue-sculpture-figure-1275469/ [7]: https://sketchfab.com/3d-models/chalet-hippolyte-chassande-baroz-e925320e1d5744d9ae661aeff61e7aef [8]: https://github.com/adrien-ben/vulkan-tutorial-rs/workflows/Cross-platform%20build/badge.svg [10]: https://github.com/adrien-ben/vulkan-tutorial-rs/tree/extended [11]: https://github.com/adrien-ben/vulkan-tutorial-rs/tree/android ================================================ FILE: assets/models/chalet.obj ================================================ [File too large to display: 30.7 MB] ================================================ FILE: assets/shaders/shader.frag ================================================ #version 450 #extension GL_ARB_separate_shader_objects : enable layout(location = 0) in vec3 fragColor; layout(location = 1) in vec2 fragCoords; layout(binding = 1) uniform sampler2D texSampler; layout(location = 0) out vec4 outColor; void main() { outColor = texture(texSampler, fragCoords)*vec4(fragColor, 1.0); } ================================================ FILE: assets/shaders/shader.vert ================================================ #version 450 #extension GL_ARB_separate_shader_objects : enable layout(location = 0) in vec3 vPosition; layout(location = 1) in vec3 vColor; layout(location = 2) in vec2 vCoords; layout(binding = 0) uniform UniformBufferObject { mat4 model; mat4 view; mat4 proj; } ubo; layout(location = 0) out vec3 fragColor; layout(location = 1) out vec2 fragCoords; void main() { gl_Position = ubo.proj * ubo.view * ubo.model * vec4(vPosition, 1.0); fragColor = vColor; fragCoords = vCoords; } ================================================ FILE: build.rs ================================================ use std::{ env::var, ffi::OsStr, fs, io::Result, path::{Path, PathBuf}, process::{Command, Output}, }; fn main() { if !should_skip_shader_compilation() { compile_shaders(); } } fn should_skip_shader_compilation() -> bool { var("SKIP_SHADER_COMPILATION") .map(|var| var.parse::().unwrap_or(false)) .unwrap_or(false) } fn compile_shaders() { println!("Compiling shaders"); let shader_dir_path = get_shader_source_dir_path(); fs::read_dir(shader_dir_path.clone()) .unwrap() .map(Result::unwrap) .filter(|dir| dir.file_type().unwrap().is_file()) .filter(|dir| dir.path().extension() != Some(OsStr::new("spv"))) .for_each(|dir| { let path = dir.path(); let name = path.file_name().unwrap().to_str().unwrap(); let output_name = format!("{}.spv", &name); println!("Found file {:?}.\nCompiling...", path.as_os_str()); let result = Command::new("glslangValidator") .current_dir(&shader_dir_path) .arg("-V") .arg(&path) .arg("-o") .arg(output_name) .output(); handle_program_result(result); }) } fn get_shader_source_dir_path() -> PathBuf { let path = get_root_path().join("assets").join("shaders"); println!("Shader source directory: {:?}", path.as_os_str()); path } fn get_root_path() -> &'static Path { Path::new(env!("CARGO_MANIFEST_DIR")) } fn handle_program_result(result: Result) { match result { Ok(output) => { if output.status.success() { println!("Shader compilation succedeed."); print!( "stdout: {}", String::from_utf8(output.stdout) .unwrap_or("Failed to print program stdout".to_string()) ); } else { eprintln!("Shader compilation failed. Status: {}", output.status); eprint!( "stdout: {}", String::from_utf8(output.stdout) .unwrap_or("Failed to print program stdout".to_string()) ); eprint!( "stderr: {}", String::from_utf8(output.stderr) .unwrap_or("Failed to print program stderr".to_string()) ); panic!("Shader compilation failed. Status: {}", output.status); } } Err(error) => { panic!("Failed to compile shader. Cause: {}", error); } } } ================================================ FILE: src/camera.rs ================================================ use crate::math::clamp; use cgmath::Point3; #[derive(Clone, Copy)] pub struct Camera { theta: f32, phi: f32, r: f32, } impl Camera { pub fn position(&self) -> Point3 { Point3::new( self.r * self.phi.sin() * self.theta.sin(), self.r * self.phi.cos(), self.r * self.phi.sin() * self.theta.cos(), ) } } impl Camera { pub fn rotate(&mut self, theta: f32, phi: f32) { self.theta += theta; let phi = self.phi + phi; self.phi = clamp(phi, 10.0_f32.to_radians(), 170.0_f32.to_radians()); } pub fn forward(&mut self, r: f32) { self.r -= r; } } impl Default for Camera { fn default() -> Self { Camera { theta: 0.0_f32.to_radians(), phi: 45.0_f32.to_radians(), r: 3.0, } } } ================================================ FILE: src/context.rs ================================================ use ash::{Device, Entry, Instance, ext::debug_utils, khr::surface, vk}; pub struct VkContext { _entry: Entry, instance: Instance, debug_report_callback: Option<(debug_utils::Instance, vk::DebugUtilsMessengerEXT)>, surface: surface::Instance, surface_khr: vk::SurfaceKHR, physical_device: vk::PhysicalDevice, device: Device, } impl VkContext { pub fn instance(&self) -> &Instance { &self.instance } pub fn surface(&self) -> &surface::Instance { &self.surface } pub fn surface_khr(&self) -> vk::SurfaceKHR { self.surface_khr } pub fn physical_device(&self) -> vk::PhysicalDevice { self.physical_device } pub fn device(&self) -> &Device { &self.device } } impl VkContext { pub fn get_mem_properties(&self) -> vk::PhysicalDeviceMemoryProperties { unsafe { self.instance .get_physical_device_memory_properties(self.physical_device) } } /// Find the first compatible format from `candidates`. pub fn find_supported_format( &self, candidates: &[vk::Format], tiling: vk::ImageTiling, features: vk::FormatFeatureFlags, ) -> Option { candidates.iter().cloned().find(|candidate| { let props = unsafe { self.instance .get_physical_device_format_properties(self.physical_device, *candidate) }; (tiling == vk::ImageTiling::LINEAR && props.linear_tiling_features.contains(features)) || (tiling == vk::ImageTiling::OPTIMAL && props.optimal_tiling_features.contains(features)) }) } /// Return the maximim sample count supported. pub fn get_max_usable_sample_count(&self) -> vk::SampleCountFlags { let props = unsafe { self.instance .get_physical_device_properties(self.physical_device) }; let color_sample_counts = props.limits.framebuffer_color_sample_counts; let depth_sample_counts = props.limits.framebuffer_depth_sample_counts; let sample_counts = color_sample_counts.min(depth_sample_counts); if sample_counts.contains(vk::SampleCountFlags::TYPE_64) { vk::SampleCountFlags::TYPE_64 } else if sample_counts.contains(vk::SampleCountFlags::TYPE_32) { vk::SampleCountFlags::TYPE_32 } else if sample_counts.contains(vk::SampleCountFlags::TYPE_16) { vk::SampleCountFlags::TYPE_16 } else if sample_counts.contains(vk::SampleCountFlags::TYPE_8) { vk::SampleCountFlags::TYPE_8 } else if sample_counts.contains(vk::SampleCountFlags::TYPE_4) { vk::SampleCountFlags::TYPE_4 } else if sample_counts.contains(vk::SampleCountFlags::TYPE_2) { vk::SampleCountFlags::TYPE_2 } else { vk::SampleCountFlags::TYPE_1 } } } impl VkContext { pub fn new( entry: Entry, instance: Instance, debug_report_callback: Option<(debug_utils::Instance, vk::DebugUtilsMessengerEXT)>, surface: surface::Instance, surface_khr: vk::SurfaceKHR, physical_device: vk::PhysicalDevice, device: Device, ) -> Self { VkContext { _entry: entry, instance, debug_report_callback, surface, surface_khr, physical_device, device, } } } impl Drop for VkContext { fn drop(&mut self) { unsafe { self.device.destroy_device(None); self.surface.destroy_surface(self.surface_khr, None); if let Some((utils, messenger)) = self.debug_report_callback.take() { utils.destroy_debug_utils_messenger(messenger, None); } self.instance.destroy_instance(None); } } } ================================================ FILE: src/debug.rs ================================================ use ash::{Entry, Instance, ext::debug_utils, vk}; use std::{ ffi::{CStr, CString}, os::raw::{c_char, c_void}, }; #[cfg(debug_assertions)] pub const ENABLE_VALIDATION_LAYERS: bool = true; #[cfg(not(debug_assertions))] pub const ENABLE_VALIDATION_LAYERS: bool = false; const REQUIRED_LAYERS: [&str; 1] = ["VK_LAYER_KHRONOS_validation"]; unsafe extern "system" fn vulkan_debug_callback( flag: vk::DebugUtilsMessageSeverityFlagsEXT, typ: vk::DebugUtilsMessageTypeFlagsEXT, p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT, _: *mut c_void, ) -> vk::Bool32 { unsafe { use vk::DebugUtilsMessageSeverityFlagsEXT as Flag; let message = CStr::from_ptr((*p_callback_data).p_message); match flag { Flag::VERBOSE => log::debug!("{:?} - {:?}", typ, message), Flag::INFO => log::info!("{:?} - {:?}", typ, message), Flag::WARNING => log::warn!("{:?} - {:?}", typ, message), _ => log::error!("{:?} - {:?}", typ, message), } vk::FALSE } } /// Get the pointers to the validation layers names. /// Also return the corresponding `CString` to avoid dangling pointers. pub fn get_layer_names_and_pointers() -> (Vec, Vec<*const c_char>) { let layer_names = REQUIRED_LAYERS .iter() .map(|name| CString::new(*name).unwrap()) .collect::>(); let layer_names_ptrs = layer_names .iter() .map(|name| name.as_ptr()) .collect::>(); (layer_names, layer_names_ptrs) } /// Check if the required validation set in `REQUIRED_LAYERS` /// are supported by the Vulkan instance. /// /// # Panics /// /// Panic if at least one on the layer is not supported. pub fn check_validation_layer_support(entry: &Entry) { let supported_layers = unsafe { entry.enumerate_instance_layer_properties().unwrap() }; for required in REQUIRED_LAYERS.iter() { let found = supported_layers.iter().any(|layer| { let name = unsafe { CStr::from_ptr(layer.layer_name.as_ptr()) }; let name = name.to_str().expect("Failed to get layer name pointer"); required == &name }); if !found { panic!("Validation layer not supported: {}", required); } } } /// Setup the debug message if validation layers are enabled. pub fn setup_debug_messenger( entry: &Entry, instance: &Instance, ) -> Option<(debug_utils::Instance, vk::DebugUtilsMessengerEXT)> { if !ENABLE_VALIDATION_LAYERS { return None; } let create_info = create_debug_create_info(); let debug_utils = debug_utils::Instance::new(entry, instance); let debug_utils_messenger = unsafe { debug_utils .create_debug_utils_messenger(&create_info, None) .unwrap() }; Some((debug_utils, debug_utils_messenger)) } pub fn create_debug_create_info() -> vk::DebugUtilsMessengerCreateInfoEXT<'static> { vk::DebugUtilsMessengerCreateInfoEXT::default() .flags(vk::DebugUtilsMessengerCreateFlagsEXT::empty()) .message_severity( vk::DebugUtilsMessageSeverityFlagsEXT::ERROR | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING | vk::DebugUtilsMessageSeverityFlagsEXT::INFO, ) .message_type( vk::DebugUtilsMessageTypeFlagsEXT::GENERAL | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, ) .pfn_user_callback(Some(vulkan_debug_callback)) } ================================================ FILE: src/fs.rs ================================================ use std::io::Cursor; use std::path::Path; pub fn load>(path: P) -> Cursor> { use std::fs::File; use std::io::Read; let mut buf = Vec::new(); let fullpath = Path::new("assets").join(path); let mut file = File::open(fullpath).unwrap(); file.read_to_end(&mut buf).unwrap(); Cursor::new(buf) } ================================================ FILE: src/main.rs ================================================ mod camera; mod context; mod debug; mod fs; mod math; mod swapchain; mod texture; use crate::{camera::*, context::*, debug::*, swapchain::*, texture::*}; use ash::{ Device, Entry, Instance, ext::debug_utils, khr::{surface, swapchain as khr_swapchain}, vk, }; use cgmath::{Deg, Matrix4, Point3, Vector3}; use raw_window_handle::{HasDisplayHandle, HasWindowHandle}; use std::{ ffi::{CStr, CString}, mem::{align_of, offset_of, size_of, size_of_val}, }; use winit::{ application::ApplicationHandler, dpi::PhysicalSize, event::{ElementState, MouseButton, MouseScrollDelta, StartCause, WindowEvent}, event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, window::{Window, WindowId}, }; const WIDTH: u32 = 800; const HEIGHT: u32 = 600; const MAX_FRAMES_IN_FLIGHT: u32 = 2; fn main() { env_logger::init(); let event_loop = EventLoop::new().unwrap(); event_loop.set_control_flow(ControlFlow::Poll); let mut app = App::default(); event_loop.run_app(&mut app).unwrap(); } #[derive(Default)] struct App { vulkan: Option, window: Option, } impl ApplicationHandler for App { fn resumed(&mut self, event_loop: &ActiveEventLoop) { let window = event_loop .create_window( Window::default_attributes() .with_title("Vulkan tutorial with Ash") .with_inner_size(PhysicalSize::new(WIDTH, HEIGHT)), ) .unwrap(); self.vulkan = Some(VulkanApp::new(&window)); self.window = Some(window); } fn new_events(&mut self, _: &ActiveEventLoop, _: StartCause) { // we need to check that the vulkan context has been created because `new_events` can be called before `resumed`. if let Some(app) = self.vulkan.as_mut() { app.wheel_delta = None; } } fn window_event(&mut self, event_loop: &ActiveEventLoop, _: WindowId, event: WindowEvent) { match event { WindowEvent::CloseRequested => { event_loop.exit(); } WindowEvent::Resized(new_dimensions) => { let vulkan_app = self.vulkan.as_mut().unwrap(); if new_dimensions.width != vulkan_app.swapchain_properties.extent.width || new_dimensions.height != vulkan_app.swapchain_properties.extent.height { vulkan_app.dirty_swapchain = true; } } WindowEvent::MouseInput { button, state, .. } => { self.vulkan.as_mut().unwrap().is_left_clicked = state == ElementState::Pressed && button == MouseButton::Left; } WindowEvent::CursorMoved { position, .. } => { let app = self.vulkan.as_mut().unwrap(); let position: (i32, i32) = position.into(); app.cursor_delta = Some([ app.cursor_position[0] - position.0, app.cursor_position[1] - position.1, ]); app.cursor_position = [position.0, position.1]; } WindowEvent::MouseWheel { delta: MouseScrollDelta::LineDelta(_, v_lines), .. } => { self.vulkan.as_mut().unwrap().wheel_delta = Some(v_lines); } _ => (), } } fn about_to_wait(&mut self, _: &ActiveEventLoop) { let app = self.vulkan.as_mut().unwrap(); let window = self.window.as_ref().unwrap(); if app.dirty_swapchain { let size = window.inner_size(); if size.width > 0 && size.height > 0 { app.resize_dimensions = [size.width, size.height]; app.recreate_swapchain(); } else { return; } } app.dirty_swapchain = app.draw_frame(); } fn exiting(&mut self, _: &ActiveEventLoop) { self.vulkan.as_ref().unwrap().wait_gpu_idle(); } } struct VulkanApp { resize_dimensions: [u32; 2], camera: Camera, is_left_clicked: bool, cursor_position: [i32; 2], cursor_delta: Option<[i32; 2]>, wheel_delta: Option, dirty_swapchain: bool, vk_context: VkContext, queue_families_indices: QueueFamiliesIndices, graphics_queue: vk::Queue, present_queue: vk::Queue, swapchain: khr_swapchain::Device, swapchain_khr: vk::SwapchainKHR, swapchain_properties: SwapchainProperties, images: Vec, swapchain_image_views: Vec, render_pass: vk::RenderPass, descriptor_set_layout: vk::DescriptorSetLayout, pipeline_layout: vk::PipelineLayout, pipeline: vk::Pipeline, swapchain_framebuffers: Vec, command_pool: vk::CommandPool, transient_command_pool: vk::CommandPool, msaa_samples: vk::SampleCountFlags, color_texture: Texture, depth_format: vk::Format, depth_texture: Texture, texture: Texture, model_index_count: usize, vertex_buffer: vk::Buffer, vertex_buffer_memory: vk::DeviceMemory, index_buffer: vk::Buffer, index_buffer_memory: vk::DeviceMemory, uniform_buffers: Vec, uniform_buffer_memories: Vec, descriptor_pool: vk::DescriptorPool, descriptor_sets: Vec, command_buffers: Vec, in_flight_frames: InFlightFrames, } impl VulkanApp { fn new(window: &Window) -> Self { log::debug!("Creating application."); let entry = unsafe { Entry::load().expect("Failed to create entry.") }; let instance = Self::create_instance(&entry, window); let surface = surface::Instance::new(&entry, &instance); let surface_khr = unsafe { ash_window::create_surface( &entry, &instance, window.display_handle().unwrap().as_raw(), window.window_handle().unwrap().as_raw(), None, ) .unwrap() }; let debug_report_callback = setup_debug_messenger(&entry, &instance); let (physical_device, queue_families_indices) = Self::pick_physical_device(&instance, &surface, surface_khr); let (device, graphics_queue, present_queue) = Self::create_logical_device_with_graphics_queue( &instance, physical_device, queue_families_indices, ); let vk_context = VkContext::new( entry, instance, debug_report_callback, surface, surface_khr, physical_device, device, ); let (swapchain, swapchain_khr, properties, images) = Self::create_swapchain_and_images(&vk_context, queue_families_indices, [WIDTH, HEIGHT]); let swapchain_image_views = Self::create_swapchain_image_views(vk_context.device(), &images, properties); let msaa_samples = vk_context.get_max_usable_sample_count(); let depth_format = Self::find_depth_format(&vk_context); let render_pass = Self::create_render_pass(vk_context.device(), properties, msaa_samples, depth_format); let descriptor_set_layout = Self::create_descriptor_set_layout(vk_context.device()); let (pipeline, layout) = Self::create_pipeline( vk_context.device(), properties, msaa_samples, render_pass, descriptor_set_layout, ); let command_pool = Self::create_command_pool( vk_context.device(), queue_families_indices, vk::CommandPoolCreateFlags::empty(), ); let transient_command_pool = Self::create_command_pool( vk_context.device(), queue_families_indices, vk::CommandPoolCreateFlags::TRANSIENT, ); let color_texture = Self::create_color_texture( &vk_context, command_pool, graphics_queue, properties, msaa_samples, ); let depth_texture = Self::create_depth_texture( &vk_context, command_pool, graphics_queue, depth_format, properties.extent, msaa_samples, ); let swapchain_framebuffers = Self::create_framebuffers( vk_context.device(), &swapchain_image_views, color_texture, depth_texture, render_pass, properties, ); let texture = Self::create_texture_image(&vk_context, command_pool, graphics_queue); let (vertices, indices) = Self::load_model(); let (vertex_buffer, vertex_buffer_memory) = Self::create_vertex_buffer( &vk_context, transient_command_pool, graphics_queue, &vertices, ); let (index_buffer, index_buffer_memory) = Self::create_index_buffer( &vk_context, transient_command_pool, graphics_queue, &indices, ); let (uniform_buffers, uniform_buffer_memories) = Self::create_uniform_buffers(&vk_context, images.len()); let descriptor_pool = Self::create_descriptor_pool(vk_context.device(), images.len() as _); let descriptor_sets = Self::create_descriptor_sets( vk_context.device(), descriptor_pool, descriptor_set_layout, &uniform_buffers, texture, ); let command_buffers = Self::create_and_register_command_buffers( vk_context.device(), command_pool, &swapchain_framebuffers, render_pass, properties, vertex_buffer, index_buffer, indices.len(), layout, &descriptor_sets, pipeline, ); let in_flight_frames = Self::create_sync_objects(vk_context.device()); Self { resize_dimensions: [WIDTH, HEIGHT], camera: Default::default(), is_left_clicked: false, cursor_position: [0, 0], cursor_delta: None, wheel_delta: None, dirty_swapchain: false, vk_context, queue_families_indices, graphics_queue, present_queue, swapchain, swapchain_khr, swapchain_properties: properties, images, swapchain_image_views, render_pass, descriptor_set_layout, pipeline_layout: layout, pipeline, swapchain_framebuffers, command_pool, transient_command_pool, msaa_samples, color_texture, depth_format, depth_texture, texture, model_index_count: indices.len(), vertex_buffer, vertex_buffer_memory, index_buffer, index_buffer_memory, uniform_buffers, uniform_buffer_memories, descriptor_pool, descriptor_sets, command_buffers, in_flight_frames, } } fn create_instance(entry: &Entry, window: &Window) -> Instance { let app_name = CString::new("Vulkan Application").unwrap(); let engine_name = CString::new("No Engine").unwrap(); let app_info = vk::ApplicationInfo::default() .application_name(app_name.as_c_str()) .application_version(vk::make_api_version(0, 0, 1, 0)) .engine_name(engine_name.as_c_str()) .engine_version(vk::make_api_version(0, 0, 1, 0)) .api_version(vk::make_api_version(0, 1, 0, 0)); let extension_names = ash_window::enumerate_required_extensions(window.display_handle().unwrap().as_raw()) .unwrap(); let mut extension_names = extension_names.to_vec(); if ENABLE_VALIDATION_LAYERS { extension_names.push(debug_utils::NAME.as_ptr()); } #[cfg(any(target_os = "macos", target_os = "ios"))] { extension_names.push(ash::khr::portability_enumeration::NAME.as_ptr()); // Enabling this extension is a requirement when using `VK_KHR_portability_subset` extension_names.push(ash::khr::get_physical_device_properties2::NAME.as_ptr()); } let (_layer_names, layer_names_ptrs) = get_layer_names_and_pointers(); let create_flags = if cfg!(any(target_os = "macos", target_os = "ios")) { vk::InstanceCreateFlags::ENUMERATE_PORTABILITY_KHR } else { vk::InstanceCreateFlags::default() }; let mut debug_create_info = create_debug_create_info(); let mut instance_create_info = vk::InstanceCreateInfo::default() .application_info(&app_info) .enabled_extension_names(&extension_names) .flags(create_flags); if ENABLE_VALIDATION_LAYERS { check_validation_layer_support(entry); instance_create_info = instance_create_info .enabled_layer_names(&layer_names_ptrs) .push_next(&mut debug_create_info); } unsafe { entry.create_instance(&instance_create_info, None).unwrap() } } /// Pick the first suitable physical device. /// /// # Requirements /// - At least one queue family with one queue supportting graphics. /// - At least one queue family with one queue supporting presentation to `surface_khr`. /// - Swapchain extension support. /// /// # Returns /// /// A tuple containing the physical device and the queue families indices. fn pick_physical_device( instance: &Instance, surface: &surface::Instance, surface_khr: vk::SurfaceKHR, ) -> (vk::PhysicalDevice, QueueFamiliesIndices) { let devices = unsafe { instance.enumerate_physical_devices().unwrap() }; let device = devices .into_iter() .find(|device| Self::is_device_suitable(instance, surface, surface_khr, *device)) .expect("No suitable physical device."); let props = unsafe { instance.get_physical_device_properties(device) }; log::debug!("Selected physical device: {:?}", unsafe { CStr::from_ptr(props.device_name.as_ptr()) }); let (graphics, present) = Self::find_queue_families(instance, surface, surface_khr, device); let queue_families_indices = QueueFamiliesIndices { graphics_index: graphics.unwrap(), present_index: present.unwrap(), }; (device, queue_families_indices) } fn is_device_suitable( instance: &Instance, surface: &surface::Instance, surface_khr: vk::SurfaceKHR, device: vk::PhysicalDevice, ) -> bool { let (graphics, present) = Self::find_queue_families(instance, surface, surface_khr, device); let extention_support = Self::check_device_extension_support(instance, device); let is_swapchain_adequate = { let details = SwapchainSupportDetails::new(device, surface, surface_khr); !details.formats.is_empty() && !details.present_modes.is_empty() }; let features = unsafe { instance.get_physical_device_features(device) }; graphics.is_some() && present.is_some() && extention_support && is_swapchain_adequate && features.sampler_anisotropy == vk::TRUE } fn check_device_extension_support(instance: &Instance, device: vk::PhysicalDevice) -> bool { let required_extentions = Self::get_required_device_extensions(); let extension_props = unsafe { instance .enumerate_device_extension_properties(device) .unwrap() }; for required in required_extentions.iter() { let found = extension_props.iter().any(|ext| { let name = unsafe { CStr::from_ptr(ext.extension_name.as_ptr()) }; required == &name }); if !found { return false; } } true } #[cfg(not(any(target_os = "macos", target_os = "ios")))] fn get_required_device_extensions() -> [&'static CStr; 1] { [khr_swapchain::NAME] } #[cfg(any(target_os = "macos", target_os = "ios"))] fn get_required_device_extensions() -> [&'static CStr; 2] { [khr_swapchain::NAME, ash::khr::portability_subset::NAME] } /// Find a queue family with at least one graphics queue and one with /// at least one presentation queue from `device`. /// /// #Returns /// /// Return a tuple (Option, Option). fn find_queue_families( instance: &Instance, surface: &surface::Instance, surface_khr: vk::SurfaceKHR, device: vk::PhysicalDevice, ) -> (Option, Option) { let mut graphics = None; let mut present = None; let props = unsafe { instance.get_physical_device_queue_family_properties(device) }; for (index, family) in props.iter().filter(|f| f.queue_count > 0).enumerate() { let index = index as u32; if family.queue_flags.contains(vk::QueueFlags::GRAPHICS) && graphics.is_none() { graphics = Some(index); } let present_support = unsafe { surface .get_physical_device_surface_support(device, index, surface_khr) .unwrap() }; if present_support && present.is_none() { present = Some(index); } if graphics.is_some() && present.is_some() { break; } } (graphics, present) } /// Create the logical device to interact with `device`, a graphics queue /// and a presentation queue. /// /// # Returns /// /// Return a tuple containing the logical device, the graphics queue and the presentation queue. fn create_logical_device_with_graphics_queue( instance: &Instance, device: vk::PhysicalDevice, queue_families_indices: QueueFamiliesIndices, ) -> (Device, vk::Queue, vk::Queue) { let graphics_family_index = queue_families_indices.graphics_index; let present_family_index = queue_families_indices.present_index; let queue_priorities = [1.0f32]; let queue_create_infos = { // Vulkan specs does not allow passing an array containing duplicated family indices. // And since the family for graphics and presentation could be the same we need to // deduplicate it. let mut indices = vec![graphics_family_index, present_family_index]; indices.dedup(); // Now we build an array of `DeviceQueueCreateInfo`. // One for each different family index. indices .iter() .map(|index| { vk::DeviceQueueCreateInfo::default() .queue_family_index(*index) .queue_priorities(&queue_priorities) }) .collect::>() }; let device_extensions = Self::get_required_device_extensions(); let device_extensions_ptrs = device_extensions .iter() .map(|ext| ext.as_ptr()) .collect::>(); let device_features = vk::PhysicalDeviceFeatures::default().sampler_anisotropy(true); let device_create_info = vk::DeviceCreateInfo::default() .queue_create_infos(&queue_create_infos) .enabled_extension_names(&device_extensions_ptrs) .enabled_features(&device_features); // Build device and queues let device = unsafe { instance .create_device(device, &device_create_info, None) .expect("Failed to create logical device.") }; let graphics_queue = unsafe { device.get_device_queue(graphics_family_index, 0) }; let present_queue = unsafe { device.get_device_queue(present_family_index, 0) }; (device, graphics_queue, present_queue) } /// Create the swapchain with optimal settings possible with /// `device`. /// /// # Returns /// /// A tuple containing the swapchain loader and the actual swapchain. fn create_swapchain_and_images( vk_context: &VkContext, queue_families_indices: QueueFamiliesIndices, dimensions: [u32; 2], ) -> ( khr_swapchain::Device, vk::SwapchainKHR, SwapchainProperties, Vec, ) { let details = SwapchainSupportDetails::new( vk_context.physical_device(), vk_context.surface(), vk_context.surface_khr(), ); let properties = details.get_ideal_swapchain_properties(dimensions); let format = properties.format; let present_mode = properties.present_mode; let extent = properties.extent; let image_count = { let max = details.capabilities.max_image_count; let mut preferred = details.capabilities.min_image_count + 1; if max > 0 && preferred > max { preferred = max; } preferred }; log::debug!( "Creating swapchain.\n\tFormat: {:?}\n\tColorSpace: {:?}\n\tPresentMode: {:?}\n\tExtent: {:?}\n\tImageCount: {:?}", format.format, format.color_space, present_mode, extent, image_count, ); let graphics = queue_families_indices.graphics_index; let present = queue_families_indices.present_index; let families_indices = [graphics, present]; let create_info = { let mut builder = vk::SwapchainCreateInfoKHR::default() .surface(vk_context.surface_khr()) .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); builder = if graphics != present { builder .image_sharing_mode(vk::SharingMode::CONCURRENT) .queue_family_indices(&families_indices) } else { builder.image_sharing_mode(vk::SharingMode::EXCLUSIVE) }; builder .pre_transform(details.capabilities.current_transform) .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) .present_mode(present_mode) .clipped(true) // .old_swapchain() We don't have an old swapchain but can't pass null }; let swapchain = khr_swapchain::Device::new(vk_context.instance(), vk_context.device()); let swapchain_khr = unsafe { swapchain.create_swapchain(&create_info, None).unwrap() }; let images = unsafe { swapchain.get_swapchain_images(swapchain_khr).unwrap() }; (swapchain, swapchain_khr, properties, images) } /// Create one image view for each image of the swapchain. fn create_swapchain_image_views( device: &Device, swapchain_images: &[vk::Image], swapchain_properties: SwapchainProperties, ) -> Vec { swapchain_images .iter() .map(|image| { Self::create_image_view( device, *image, 1, swapchain_properties.format.format, vk::ImageAspectFlags::COLOR, ) }) .collect::>() } fn create_image_view( device: &Device, image: vk::Image, mip_levels: u32, format: vk::Format, aspect_mask: vk::ImageAspectFlags, ) -> vk::ImageView { let create_info = vk::ImageViewCreateInfo::default() .image(image) .view_type(vk::ImageViewType::TYPE_2D) .format(format) .subresource_range(vk::ImageSubresourceRange { aspect_mask, base_mip_level: 0, level_count: mip_levels, base_array_layer: 0, layer_count: 1, }); unsafe { device.create_image_view(&create_info, None).unwrap() } } fn create_render_pass( device: &Device, swapchain_properties: SwapchainProperties, msaa_samples: vk::SampleCountFlags, depth_format: vk::Format, ) -> vk::RenderPass { let color_attachment_desc = vk::AttachmentDescription::default() .format(swapchain_properties.format.format) .samples(msaa_samples) .load_op(vk::AttachmentLoadOp::CLEAR) .store_op(vk::AttachmentStoreOp::STORE) .initial_layout(vk::ImageLayout::UNDEFINED) .final_layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); let depth_attachement_desc = vk::AttachmentDescription::default() .format(depth_format) .samples(msaa_samples) .load_op(vk::AttachmentLoadOp::CLEAR) .store_op(vk::AttachmentStoreOp::DONT_CARE) .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE) .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE) .initial_layout(vk::ImageLayout::UNDEFINED) .final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL); let resolve_attachment_desc = vk::AttachmentDescription::default() .format(swapchain_properties.format.format) .samples(vk::SampleCountFlags::TYPE_1) .load_op(vk::AttachmentLoadOp::DONT_CARE) .store_op(vk::AttachmentStoreOp::STORE) .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE) .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE) .initial_layout(vk::ImageLayout::UNDEFINED) .final_layout(vk::ImageLayout::PRESENT_SRC_KHR); let attachment_descs = [ color_attachment_desc, depth_attachement_desc, resolve_attachment_desc, ]; let color_attachment_ref = vk::AttachmentReference::default() .attachment(0) .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); let color_attachment_refs = [color_attachment_ref]; let depth_attachment_ref = vk::AttachmentReference::default() .attachment(1) .layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL); let resolve_attachment_ref = vk::AttachmentReference::default() .attachment(2) .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); let resolve_attachment_refs = [resolve_attachment_ref]; let subpass_desc = vk::SubpassDescription::default() .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) .color_attachments(&color_attachment_refs) .resolve_attachments(&resolve_attachment_refs) .depth_stencil_attachment(&depth_attachment_ref); let subpass_descs = [subpass_desc]; let subpass_dep = vk::SubpassDependency::default() .src_subpass(vk::SUBPASS_EXTERNAL) .dst_subpass(0) .src_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) .src_access_mask(vk::AccessFlags::empty()) .dst_stage_mask(vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT) .dst_access_mask( vk::AccessFlags::COLOR_ATTACHMENT_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE, ); let subpass_deps = [subpass_dep]; let render_pass_info = vk::RenderPassCreateInfo::default() .attachments(&attachment_descs) .subpasses(&subpass_descs) .dependencies(&subpass_deps); unsafe { device.create_render_pass(&render_pass_info, None).unwrap() } } fn create_descriptor_set_layout(device: &Device) -> vk::DescriptorSetLayout { let ubo_binding = UniformBufferObject::get_descriptor_set_layout_binding(); let sampler_binding = vk::DescriptorSetLayoutBinding::default() .binding(1) .descriptor_count(1) .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) .stage_flags(vk::ShaderStageFlags::FRAGMENT); let bindings = [ubo_binding, sampler_binding]; let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&bindings); unsafe { device .create_descriptor_set_layout(&layout_info, None) .unwrap() } } /// Create a descriptor pool to allocate the descriptor sets. fn create_descriptor_pool(device: &Device, size: u32) -> vk::DescriptorPool { let ubo_pool_size = vk::DescriptorPoolSize { ty: vk::DescriptorType::UNIFORM_BUFFER, descriptor_count: size, }; let sampler_pool_size = vk::DescriptorPoolSize { ty: vk::DescriptorType::COMBINED_IMAGE_SAMPLER, descriptor_count: size, }; let pool_sizes = [ubo_pool_size, sampler_pool_size]; let pool_info = vk::DescriptorPoolCreateInfo::default() .pool_sizes(&pool_sizes) .max_sets(size); unsafe { device.create_descriptor_pool(&pool_info, None).unwrap() } } /// Create one descriptor set for each uniform buffer. fn create_descriptor_sets( device: &Device, pool: vk::DescriptorPool, layout: vk::DescriptorSetLayout, uniform_buffers: &[vk::Buffer], texture: Texture, ) -> Vec { let layouts = (0..uniform_buffers.len()) .map(|_| layout) .collect::>(); let alloc_info = vk::DescriptorSetAllocateInfo::default() .descriptor_pool(pool) .set_layouts(&layouts); let descriptor_sets = unsafe { device.allocate_descriptor_sets(&alloc_info).unwrap() }; descriptor_sets .iter() .zip(uniform_buffers.iter()) .for_each(|(set, buffer)| { let buffer_info = vk::DescriptorBufferInfo::default() .buffer(*buffer) .offset(0) .range(size_of::() as vk::DeviceSize); let buffer_infos = [buffer_info]; let image_info = vk::DescriptorImageInfo::default() .image_layout(vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL) .image_view(texture.view) .sampler(texture.sampler.unwrap()); let image_infos = [image_info]; let ubo_descriptor_write = vk::WriteDescriptorSet::default() .dst_set(*set) .dst_binding(0) .dst_array_element(0) .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) .buffer_info(&buffer_infos); let sampler_descriptor_write = vk::WriteDescriptorSet::default() .dst_set(*set) .dst_binding(1) .dst_array_element(0) .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) .image_info(&image_infos); let descriptor_writes = [ubo_descriptor_write, sampler_descriptor_write]; unsafe { device.update_descriptor_sets(&descriptor_writes, &[]) } }); descriptor_sets } fn create_pipeline( device: &Device, swapchain_properties: SwapchainProperties, msaa_samples: vk::SampleCountFlags, render_pass: vk::RenderPass, descriptor_set_layout: vk::DescriptorSetLayout, ) -> (vk::Pipeline, vk::PipelineLayout) { let vertex_source = Self::read_shader_from_file("shaders/shader.vert.spv"); let fragment_source = Self::read_shader_from_file("shaders/shader.frag.spv"); let vertex_shader_module = Self::create_shader_module(device, &vertex_source); let fragment_shader_module = Self::create_shader_module(device, &fragment_source); let entry_point_name = CString::new("main").unwrap(); let vertex_shader_state_info = vk::PipelineShaderStageCreateInfo::default() .stage(vk::ShaderStageFlags::VERTEX) .module(vertex_shader_module) .name(&entry_point_name); let fragment_shader_state_info = vk::PipelineShaderStageCreateInfo::default() .stage(vk::ShaderStageFlags::FRAGMENT) .module(fragment_shader_module) .name(&entry_point_name); let shader_states_infos = [vertex_shader_state_info, fragment_shader_state_info]; let vertex_binding_descs = [Vertex::get_binding_description()]; let vertex_attribute_descs = Vertex::get_attribute_descriptions(); let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::default() .vertex_binding_descriptions(&vertex_binding_descs) .vertex_attribute_descriptions(&vertex_attribute_descs); let input_assembly_info = vk::PipelineInputAssemblyStateCreateInfo::default() .topology(vk::PrimitiveTopology::TRIANGLE_LIST) .primitive_restart_enable(false); let viewport = vk::Viewport { x: 0.0, y: 0.0, width: swapchain_properties.extent.width as _, height: swapchain_properties.extent.height as _, min_depth: 0.0, max_depth: 1.0, }; let viewports = [viewport]; let scissor = vk::Rect2D { offset: vk::Offset2D { x: 0, y: 0 }, extent: swapchain_properties.extent, }; let scissors = [scissor]; let viewport_info = vk::PipelineViewportStateCreateInfo::default() .viewports(&viewports) .scissors(&scissors); let rasterizer_info = vk::PipelineRasterizationStateCreateInfo::default() .depth_clamp_enable(false) .rasterizer_discard_enable(false) .polygon_mode(vk::PolygonMode::FILL) .line_width(1.0) .cull_mode(vk::CullModeFlags::BACK) .front_face(vk::FrontFace::COUNTER_CLOCKWISE) .depth_bias_enable(false) .depth_bias_constant_factor(0.0) .depth_bias_clamp(0.0) .depth_bias_slope_factor(0.0); let multisampling_info = vk::PipelineMultisampleStateCreateInfo::default() .sample_shading_enable(false) .rasterization_samples(msaa_samples) .min_sample_shading(1.0) // .sample_mask() // null .alpha_to_coverage_enable(false) .alpha_to_one_enable(false); let depth_stencil_info = vk::PipelineDepthStencilStateCreateInfo::default() .depth_test_enable(true) .depth_write_enable(true) .depth_compare_op(vk::CompareOp::LESS) .depth_bounds_test_enable(false) .min_depth_bounds(0.0) .max_depth_bounds(1.0) .stencil_test_enable(false) .front(Default::default()) .back(Default::default()); let color_blend_attachment = vk::PipelineColorBlendAttachmentState::default() .color_write_mask(vk::ColorComponentFlags::RGBA) .blend_enable(false) .src_color_blend_factor(vk::BlendFactor::ONE) .dst_color_blend_factor(vk::BlendFactor::ZERO) .color_blend_op(vk::BlendOp::ADD) .src_alpha_blend_factor(vk::BlendFactor::ONE) .dst_alpha_blend_factor(vk::BlendFactor::ZERO) .alpha_blend_op(vk::BlendOp::ADD); let color_blend_attachments = [color_blend_attachment]; let color_blending_info = vk::PipelineColorBlendStateCreateInfo::default() .logic_op_enable(false) .logic_op(vk::LogicOp::COPY) .attachments(&color_blend_attachments) .blend_constants([0.0, 0.0, 0.0, 0.0]); let layout = { let layouts = [descriptor_set_layout]; let layout_info = vk::PipelineLayoutCreateInfo::default().set_layouts(&layouts); // .push_constant_range; unsafe { device.create_pipeline_layout(&layout_info, None).unwrap() } }; let pipeline_info = vk::GraphicsPipelineCreateInfo::default() .stages(&shader_states_infos) .vertex_input_state(&vertex_input_info) .input_assembly_state(&input_assembly_info) .viewport_state(&viewport_info) .rasterization_state(&rasterizer_info) .multisample_state(&multisampling_info) .depth_stencil_state(&depth_stencil_info) .color_blend_state(&color_blending_info) // .dynamic_state() null since don't have any dynamic states .layout(layout) .render_pass(render_pass) .subpass(0); // .base_pipeline_handle() null since it is not derived from another // .base_pipeline_index(-1) same let pipeline_infos = [pipeline_info]; let pipeline = unsafe { device .create_graphics_pipelines(vk::PipelineCache::null(), &pipeline_infos, None) .unwrap()[0] }; unsafe { device.destroy_shader_module(vertex_shader_module, None); device.destroy_shader_module(fragment_shader_module, None); }; (pipeline, layout) } fn read_shader_from_file>(path: P) -> Vec { log::debug!("Loading shader file {}", path.as_ref().to_str().unwrap()); let mut cursor = fs::load(path); ash::util::read_spv(&mut cursor).unwrap() } fn create_shader_module(device: &Device, code: &[u32]) -> vk::ShaderModule { let create_info = vk::ShaderModuleCreateInfo::default().code(code); unsafe { device.create_shader_module(&create_info, None).unwrap() } } fn create_framebuffers( device: &Device, image_views: &[vk::ImageView], color_texture: Texture, depth_texture: Texture, render_pass: vk::RenderPass, swapchain_properties: SwapchainProperties, ) -> Vec { image_views .iter() .map(|view| [color_texture.view, depth_texture.view, *view]) .map(|attachments| { let framebuffer_info = vk::FramebufferCreateInfo::default() .render_pass(render_pass) .attachments(&attachments) .width(swapchain_properties.extent.width) .height(swapchain_properties.extent.height) .layers(1); unsafe { device.create_framebuffer(&framebuffer_info, None).unwrap() } }) .collect::>() } fn create_command_pool( device: &Device, queue_families_indices: QueueFamiliesIndices, create_flags: vk::CommandPoolCreateFlags, ) -> vk::CommandPool { let command_pool_info = vk::CommandPoolCreateInfo::default() .queue_family_index(queue_families_indices.graphics_index) .flags(create_flags); unsafe { device .create_command_pool(&command_pool_info, None) .unwrap() } } fn create_color_texture( vk_context: &VkContext, command_pool: vk::CommandPool, transition_queue: vk::Queue, swapchain_properties: SwapchainProperties, msaa_samples: vk::SampleCountFlags, ) -> Texture { let format = swapchain_properties.format.format; let (image, memory) = Self::create_image( vk_context, vk::MemoryPropertyFlags::DEVICE_LOCAL, swapchain_properties.extent, 1, msaa_samples, format, vk::ImageTiling::OPTIMAL, vk::ImageUsageFlags::TRANSIENT_ATTACHMENT | vk::ImageUsageFlags::COLOR_ATTACHMENT, ); Self::transition_image_layout( vk_context.device(), command_pool, transition_queue, image, 1, format, vk::ImageLayout::UNDEFINED, vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL, ); let view = Self::create_image_view( vk_context.device(), image, 1, format, vk::ImageAspectFlags::COLOR, ); Texture::new(image, memory, view, None) } /// Create the depth buffer texture (image, memory and view). /// /// This function also transitions the image to be ready to be used /// as a depth/stencil attachement. fn create_depth_texture( vk_context: &VkContext, command_pool: vk::CommandPool, transition_queue: vk::Queue, format: vk::Format, extent: vk::Extent2D, msaa_samples: vk::SampleCountFlags, ) -> Texture { let (image, mem) = Self::create_image( vk_context, vk::MemoryPropertyFlags::DEVICE_LOCAL, extent, 1, msaa_samples, format, vk::ImageTiling::OPTIMAL, vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT, ); let device = vk_context.device(); Self::transition_image_layout( device, command_pool, transition_queue, image, 1, format, vk::ImageLayout::UNDEFINED, vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, ); let view = Self::create_image_view(device, image, 1, format, vk::ImageAspectFlags::DEPTH); Texture::new(image, mem, view, None) } fn find_depth_format(vk_context: &VkContext) -> vk::Format { let candidates = vec![ vk::Format::D32_SFLOAT, vk::Format::D32_SFLOAT_S8_UINT, vk::Format::D24_UNORM_S8_UINT, ]; vk_context .find_supported_format( &candidates, vk::ImageTiling::OPTIMAL, vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT, ) .expect("Failed to find a supported depth format") } fn has_stencil_component(format: vk::Format) -> bool { format == vk::Format::D32_SFLOAT_S8_UINT || format == vk::Format::D24_UNORM_S8_UINT } fn create_texture_image( vk_context: &VkContext, command_pool: vk::CommandPool, copy_queue: vk::Queue, ) -> Texture { let cursor = fs::load("images/chalet.jpg"); let image = image::load(cursor, image::ImageFormat::Jpeg) .unwrap() .flipv(); let image_as_rgb = image.to_rgba8(); let width = image_as_rgb.width(); let height = image_as_rgb.height(); let max_mip_levels = ((width.min(height) as f32).log2().floor() + 1.0) as u32; let extent = vk::Extent2D { width, height }; let pixels = image_as_rgb.into_raw(); let image_size = (pixels.len() * size_of::()) as vk::DeviceSize; let device = vk_context.device(); let (buffer, memory, mem_size) = Self::create_buffer( vk_context, image_size, vk::BufferUsageFlags::TRANSFER_SRC, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, ); unsafe { let ptr = device .map_memory(memory, 0, image_size, vk::MemoryMapFlags::empty()) .unwrap(); let mut align = ash::util::Align::new(ptr, align_of::() as _, mem_size); align.copy_from_slice(&pixels); device.unmap_memory(memory); } let (image, image_memory) = Self::create_image( vk_context, vk::MemoryPropertyFlags::DEVICE_LOCAL, extent, max_mip_levels, vk::SampleCountFlags::TYPE_1, vk::Format::R8G8B8A8_UNORM, vk::ImageTiling::OPTIMAL, vk::ImageUsageFlags::TRANSFER_SRC | vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED, ); // Transition the image layout and copy the buffer into the image // and transition the layout again to be readable from fragment shader. { Self::transition_image_layout( device, command_pool, copy_queue, image, max_mip_levels, vk::Format::R8G8B8A8_UNORM, vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL, ); Self::copy_buffer_to_image(device, command_pool, copy_queue, buffer, image, extent); Self::generate_mipmaps( vk_context, command_pool, copy_queue, image, extent, vk::Format::R8G8B8A8_UNORM, max_mip_levels, ); } unsafe { device.destroy_buffer(buffer, None); device.free_memory(memory, None); } let image_view = Self::create_image_view( device, image, max_mip_levels, vk::Format::R8G8B8A8_UNORM, vk::ImageAspectFlags::COLOR, ); let sampler = { let sampler_info = vk::SamplerCreateInfo::default() .mag_filter(vk::Filter::LINEAR) .min_filter(vk::Filter::LINEAR) .address_mode_u(vk::SamplerAddressMode::REPEAT) .address_mode_v(vk::SamplerAddressMode::REPEAT) .address_mode_w(vk::SamplerAddressMode::REPEAT) .anisotropy_enable(true) .max_anisotropy(16.0) .border_color(vk::BorderColor::INT_OPAQUE_BLACK) .unnormalized_coordinates(false) .compare_enable(false) .compare_op(vk::CompareOp::ALWAYS) .mipmap_mode(vk::SamplerMipmapMode::LINEAR) .mip_lod_bias(0.0) .min_lod(0.0) .max_lod(max_mip_levels as _); unsafe { device.create_sampler(&sampler_info, None).unwrap() } }; Texture::new(image, image_memory, image_view, Some(sampler)) } fn create_image( vk_context: &VkContext, mem_properties: vk::MemoryPropertyFlags, extent: vk::Extent2D, mip_levels: u32, sample_count: vk::SampleCountFlags, format: vk::Format, tiling: vk::ImageTiling, usage: vk::ImageUsageFlags, ) -> (vk::Image, vk::DeviceMemory) { let image_info = vk::ImageCreateInfo::default() .image_type(vk::ImageType::TYPE_2D) .extent(vk::Extent3D { width: extent.width, height: extent.height, depth: 1, }) .mip_levels(mip_levels) .array_layers(1) .format(format) .tiling(tiling) .initial_layout(vk::ImageLayout::UNDEFINED) .usage(usage) .sharing_mode(vk::SharingMode::EXCLUSIVE) .samples(sample_count) .flags(vk::ImageCreateFlags::empty()); let device = vk_context.device(); let image = unsafe { device.create_image(&image_info, None).unwrap() }; let mem_requirements = unsafe { device.get_image_memory_requirements(image) }; let mem_type_index = Self::find_memory_type( mem_requirements, vk_context.get_mem_properties(), mem_properties, ); let alloc_info = vk::MemoryAllocateInfo::default() .allocation_size(mem_requirements.size) .memory_type_index(mem_type_index); let memory = unsafe { let mem = device.allocate_memory(&alloc_info, None).unwrap(); device.bind_image_memory(image, mem, 0).unwrap(); mem }; (image, memory) } fn transition_image_layout( device: &Device, command_pool: vk::CommandPool, transition_queue: vk::Queue, image: vk::Image, mip_levels: u32, format: vk::Format, old_layout: vk::ImageLayout, new_layout: vk::ImageLayout, ) { Self::execute_one_time_commands(device, command_pool, transition_queue, |buffer| { let (src_access_mask, dst_access_mask, src_stage, dst_stage) = match (old_layout, new_layout) { (vk::ImageLayout::UNDEFINED, vk::ImageLayout::TRANSFER_DST_OPTIMAL) => ( vk::AccessFlags::empty(), vk::AccessFlags::TRANSFER_WRITE, vk::PipelineStageFlags::TOP_OF_PIPE, vk::PipelineStageFlags::TRANSFER, ), ( vk::ImageLayout::TRANSFER_DST_OPTIMAL, vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, ) => ( vk::AccessFlags::TRANSFER_WRITE, vk::AccessFlags::SHADER_READ, vk::PipelineStageFlags::TRANSFER, vk::PipelineStageFlags::FRAGMENT_SHADER, ), ( vk::ImageLayout::UNDEFINED, vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL, ) => ( vk::AccessFlags::empty(), vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_READ | vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE, vk::PipelineStageFlags::TOP_OF_PIPE, vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS, ), (vk::ImageLayout::UNDEFINED, vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL) => ( vk::AccessFlags::empty(), vk::AccessFlags::COLOR_ATTACHMENT_READ | vk::AccessFlags::COLOR_ATTACHMENT_WRITE, vk::PipelineStageFlags::TOP_OF_PIPE, vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT, ), _ => panic!( "Unsupported layout transition({:?} => {:?}).", old_layout, new_layout ), }; let aspect_mask = if new_layout == vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL { let mut mask = vk::ImageAspectFlags::DEPTH; if Self::has_stencil_component(format) { mask |= vk::ImageAspectFlags::STENCIL; } mask } else { vk::ImageAspectFlags::COLOR }; let barrier = vk::ImageMemoryBarrier::default() .old_layout(old_layout) .new_layout(new_layout) .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) .image(image) .subresource_range(vk::ImageSubresourceRange { aspect_mask, base_mip_level: 0, level_count: mip_levels, base_array_layer: 0, layer_count: 1, }) .src_access_mask(src_access_mask) .dst_access_mask(dst_access_mask); let barriers = [barrier]; unsafe { device.cmd_pipeline_barrier( buffer, src_stage, dst_stage, vk::DependencyFlags::empty(), &[], &[], &barriers, ) }; }); } fn copy_buffer_to_image( device: &Device, command_pool: vk::CommandPool, transition_queue: vk::Queue, buffer: vk::Buffer, image: vk::Image, extent: vk::Extent2D, ) { Self::execute_one_time_commands(device, command_pool, transition_queue, |command_buffer| { let region = vk::BufferImageCopy::default() .buffer_offset(0) .buffer_row_length(0) .buffer_image_height(0) .image_subresource(vk::ImageSubresourceLayers { aspect_mask: vk::ImageAspectFlags::COLOR, mip_level: 0, base_array_layer: 0, layer_count: 1, }) .image_offset(vk::Offset3D { x: 0, y: 0, z: 0 }) .image_extent(vk::Extent3D { width: extent.width, height: extent.height, depth: 1, }); let regions = [region]; unsafe { device.cmd_copy_buffer_to_image( command_buffer, buffer, image, vk::ImageLayout::TRANSFER_DST_OPTIMAL, ®ions, ) } }) } fn generate_mipmaps( vk_context: &VkContext, command_pool: vk::CommandPool, transfer_queue: vk::Queue, image: vk::Image, extent: vk::Extent2D, format: vk::Format, mip_levels: u32, ) { let format_properties = unsafe { vk_context .instance() .get_physical_device_format_properties(vk_context.physical_device(), format) }; if !format_properties .optimal_tiling_features .contains(vk::FormatFeatureFlags::SAMPLED_IMAGE_FILTER_LINEAR) { panic!("Linear blitting is not supported for format {:?}.", format) } Self::execute_one_time_commands( vk_context.device(), command_pool, transfer_queue, |buffer| { let mut barrier = vk::ImageMemoryBarrier::default() .image(image) .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED) .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED) .subresource_range(vk::ImageSubresourceRange { aspect_mask: vk::ImageAspectFlags::COLOR, base_array_layer: 0, layer_count: 1, level_count: 1, ..Default::default() }); let mut mip_width = extent.width as i32; let mut mip_height = extent.height as i32; for level in 1..mip_levels { let next_mip_width = if mip_width > 1 { mip_width / 2 } else { mip_width }; let next_mip_height = if mip_height > 1 { mip_height / 2 } else { mip_height }; barrier.subresource_range.base_mip_level = level - 1; barrier.old_layout = vk::ImageLayout::TRANSFER_DST_OPTIMAL; barrier.new_layout = vk::ImageLayout::TRANSFER_SRC_OPTIMAL; barrier.src_access_mask = vk::AccessFlags::TRANSFER_WRITE; barrier.dst_access_mask = vk::AccessFlags::TRANSFER_READ; let barriers = [barrier]; unsafe { vk_context.device().cmd_pipeline_barrier( buffer, vk::PipelineStageFlags::TRANSFER, vk::PipelineStageFlags::TRANSFER, vk::DependencyFlags::empty(), &[], &[], &barriers, ) }; let blit = vk::ImageBlit::default() .src_offsets([ vk::Offset3D { x: 0, y: 0, z: 0 }, vk::Offset3D { x: mip_width, y: mip_height, z: 1, }, ]) .src_subresource(vk::ImageSubresourceLayers { aspect_mask: vk::ImageAspectFlags::COLOR, mip_level: level - 1, base_array_layer: 0, layer_count: 1, }) .dst_offsets([ vk::Offset3D { x: 0, y: 0, z: 0 }, vk::Offset3D { x: next_mip_width, y: next_mip_height, z: 1, }, ]) .dst_subresource(vk::ImageSubresourceLayers { aspect_mask: vk::ImageAspectFlags::COLOR, mip_level: level, base_array_layer: 0, layer_count: 1, }); let blits = [blit]; unsafe { vk_context.device().cmd_blit_image( buffer, image, vk::ImageLayout::TRANSFER_SRC_OPTIMAL, image, vk::ImageLayout::TRANSFER_DST_OPTIMAL, &blits, vk::Filter::LINEAR, ) }; barrier.old_layout = vk::ImageLayout::TRANSFER_SRC_OPTIMAL; barrier.new_layout = vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL; barrier.src_access_mask = vk::AccessFlags::TRANSFER_READ; barrier.dst_access_mask = vk::AccessFlags::SHADER_READ; let barriers = [barrier]; unsafe { vk_context.device().cmd_pipeline_barrier( buffer, vk::PipelineStageFlags::TRANSFER, vk::PipelineStageFlags::FRAGMENT_SHADER, vk::DependencyFlags::empty(), &[], &[], &barriers, ) }; mip_width = next_mip_width; mip_height = next_mip_height; } barrier.subresource_range.base_mip_level = mip_levels - 1; barrier.old_layout = vk::ImageLayout::TRANSFER_DST_OPTIMAL; barrier.new_layout = vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL; barrier.src_access_mask = vk::AccessFlags::TRANSFER_WRITE; barrier.dst_access_mask = vk::AccessFlags::SHADER_READ; let barriers = [barrier]; unsafe { vk_context.device().cmd_pipeline_barrier( buffer, vk::PipelineStageFlags::TRANSFER, vk::PipelineStageFlags::FRAGMENT_SHADER, vk::DependencyFlags::empty(), &[], &[], &barriers, ) }; }, ); } fn load_model() -> (Vec, Vec) { log::debug!("Loading model."); let mut cursor = fs::load("models/chalet.obj"); let (models, _) = tobj::load_obj_buf( &mut cursor, &tobj::LoadOptions { single_index: true, triangulate: true, ..Default::default() }, |_| Ok((vec![], ahash::AHashMap::new())), ) .unwrap(); let mesh = &models[0].mesh; let positions = mesh.positions.as_slice(); let coords = mesh.texcoords.as_slice(); let vertex_count = mesh.positions.len() / 3; let mut vertices = Vec::with_capacity(vertex_count); for i in 0..vertex_count { let x = positions[i * 3]; let y = positions[i * 3 + 1]; let z = positions[i * 3 + 2]; let u = coords[i * 2]; let v = coords[i * 2 + 1]; let vertex = Vertex { pos: [x, y, z], color: [1.0, 1.0, 1.0], coords: [u, v], }; vertices.push(vertex); } (vertices, mesh.indices.clone()) } fn create_vertex_buffer( vk_context: &VkContext, command_pool: vk::CommandPool, transfer_queue: vk::Queue, vertices: &[Vertex], ) -> (vk::Buffer, vk::DeviceMemory) { Self::create_device_local_buffer_with_data::( vk_context, command_pool, transfer_queue, vk::BufferUsageFlags::VERTEX_BUFFER, vertices, ) } fn create_index_buffer( vk_context: &VkContext, command_pool: vk::CommandPool, transfer_queue: vk::Queue, indices: &[u32], ) -> (vk::Buffer, vk::DeviceMemory) { Self::create_device_local_buffer_with_data::( vk_context, command_pool, transfer_queue, vk::BufferUsageFlags::INDEX_BUFFER, indices, ) } /// Create a buffer and it's gpu memory and fill it. /// /// This function internally creates an host visible staging buffer and /// a device local buffer. The data is first copied from the cpu to the /// staging buffer. Then we copy the data from the staging buffer to the /// final buffer using a one-time command buffer. fn create_device_local_buffer_with_data( vk_context: &VkContext, command_pool: vk::CommandPool, transfer_queue: vk::Queue, usage: vk::BufferUsageFlags, data: &[T], ) -> (vk::Buffer, vk::DeviceMemory) { let device = vk_context.device(); let size = size_of_val(data) as vk::DeviceSize; let (staging_buffer, staging_memory, staging_mem_size) = Self::create_buffer( vk_context, size, vk::BufferUsageFlags::TRANSFER_SRC, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, ); unsafe { let data_ptr = device .map_memory(staging_memory, 0, size, vk::MemoryMapFlags::empty()) .unwrap(); let mut align = ash::util::Align::new(data_ptr, align_of::() as _, staging_mem_size); align.copy_from_slice(data); device.unmap_memory(staging_memory); }; let (buffer, memory, _) = Self::create_buffer( vk_context, size, vk::BufferUsageFlags::TRANSFER_DST | usage, vk::MemoryPropertyFlags::DEVICE_LOCAL, ); Self::copy_buffer( device, command_pool, transfer_queue, staging_buffer, buffer, size, ); unsafe { device.destroy_buffer(staging_buffer, None); device.free_memory(staging_memory, None); }; (buffer, memory) } fn create_uniform_buffers( vk_context: &VkContext, count: usize, ) -> (Vec, Vec) { let size = size_of::() as vk::DeviceSize; let mut buffers = Vec::new(); let mut memories = Vec::new(); for _ in 0..count { let (buffer, memory, _) = Self::create_buffer( vk_context, size, vk::BufferUsageFlags::UNIFORM_BUFFER, vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, ); buffers.push(buffer); memories.push(memory); } (buffers, memories) } /// Create a buffer and allocate its memory. /// /// # Returns /// /// The buffer, its memory and the actual size in bytes of the /// allocated memory since in may differ from the requested size. fn create_buffer( vk_context: &VkContext, size: vk::DeviceSize, usage: vk::BufferUsageFlags, mem_properties: vk::MemoryPropertyFlags, ) -> (vk::Buffer, vk::DeviceMemory, vk::DeviceSize) { let device = vk_context.device(); let buffer = { let buffer_info = vk::BufferCreateInfo::default() .size(size) .usage(usage) .sharing_mode(vk::SharingMode::EXCLUSIVE); unsafe { device.create_buffer(&buffer_info, None).unwrap() } }; let mem_requirements = unsafe { device.get_buffer_memory_requirements(buffer) }; let memory = { let mem_type = Self::find_memory_type( mem_requirements, vk_context.get_mem_properties(), mem_properties, ); let alloc_info = vk::MemoryAllocateInfo::default() .allocation_size(mem_requirements.size) .memory_type_index(mem_type); unsafe { device.allocate_memory(&alloc_info, None).unwrap() } }; unsafe { device.bind_buffer_memory(buffer, memory, 0).unwrap() }; (buffer, memory, mem_requirements.size) } /// Copy the `size` first bytes of `src` into `dst`. /// /// It's done using a command buffer allocated from /// `command_pool`. The command buffer is cubmitted tp /// `transfer_queue`. fn copy_buffer( device: &Device, command_pool: vk::CommandPool, transfer_queue: vk::Queue, src: vk::Buffer, dst: vk::Buffer, size: vk::DeviceSize, ) { Self::execute_one_time_commands(device, command_pool, transfer_queue, |buffer| { let region = vk::BufferCopy { src_offset: 0, dst_offset: 0, size, }; let regions = [region]; unsafe { device.cmd_copy_buffer(buffer, src, dst, ®ions) }; }); } /// Create a one time use command buffer and pass it to `executor`. fn execute_one_time_commands( device: &Device, command_pool: vk::CommandPool, queue: vk::Queue, executor: F, ) { let command_buffer = { let alloc_info = vk::CommandBufferAllocateInfo::default() .level(vk::CommandBufferLevel::PRIMARY) .command_pool(command_pool) .command_buffer_count(1); unsafe { device.allocate_command_buffers(&alloc_info).unwrap()[0] } }; let command_buffers = [command_buffer]; // Begin recording { let begin_info = vk::CommandBufferBeginInfo::default() .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT); unsafe { device .begin_command_buffer(command_buffer, &begin_info) .unwrap() }; } // Execute user function executor(command_buffer); // End recording unsafe { device.end_command_buffer(command_buffer).unwrap() }; // Submit and wait { let submit_info = vk::SubmitInfo::default().command_buffers(&command_buffers); let submit_infos = [submit_info]; unsafe { device .queue_submit(queue, &submit_infos, vk::Fence::null()) .unwrap(); device.queue_wait_idle(queue).unwrap(); }; } // Free unsafe { device.free_command_buffers(command_pool, &command_buffers) }; } /// Find a memory type in `mem_properties` that is suitable /// for `requirements` and supports `required_properties`. /// /// # Returns /// /// The index of the memory type from `mem_properties`. fn find_memory_type( requirements: vk::MemoryRequirements, mem_properties: vk::PhysicalDeviceMemoryProperties, required_properties: vk::MemoryPropertyFlags, ) -> u32 { for i in 0..mem_properties.memory_type_count { if requirements.memory_type_bits & (1 << i) != 0 && mem_properties.memory_types[i as usize] .property_flags .contains(required_properties) { return i; } } panic!("Failed to find suitable memory type.") } fn create_and_register_command_buffers( device: &Device, pool: vk::CommandPool, framebuffers: &[vk::Framebuffer], render_pass: vk::RenderPass, swapchain_properties: SwapchainProperties, vertex_buffer: vk::Buffer, index_buffer: vk::Buffer, index_count: usize, pipeline_layout: vk::PipelineLayout, descriptor_sets: &[vk::DescriptorSet], graphics_pipeline: vk::Pipeline, ) -> Vec { let allocate_info = vk::CommandBufferAllocateInfo::default() .command_pool(pool) .level(vk::CommandBufferLevel::PRIMARY) .command_buffer_count(framebuffers.len() as _); let buffers = unsafe { device.allocate_command_buffers(&allocate_info).unwrap() }; buffers.iter().enumerate().for_each(|(i, buffer)| { let buffer = *buffer; let framebuffer = framebuffers[i]; // begin command buffer { let command_buffer_begin_info = vk::CommandBufferBeginInfo::default() .flags(vk::CommandBufferUsageFlags::SIMULTANEOUS_USE); // .inheritance_info() null since it's a primary command buffer unsafe { device .begin_command_buffer(buffer, &command_buffer_begin_info) .unwrap() }; } // begin render pass { let clear_values = [ vk::ClearValue { color: vk::ClearColorValue { float32: [0.0, 0.0, 0.0, 1.0], }, }, vk::ClearValue { depth_stencil: vk::ClearDepthStencilValue { depth: 1.0, stencil: 0, }, }, ]; let render_pass_begin_info = vk::RenderPassBeginInfo::default() .render_pass(render_pass) .framebuffer(framebuffer) .render_area(vk::Rect2D { offset: vk::Offset2D { x: 0, y: 0 }, extent: swapchain_properties.extent, }) .clear_values(&clear_values); unsafe { device.cmd_begin_render_pass( buffer, &render_pass_begin_info, vk::SubpassContents::INLINE, ) }; } // Bind pipeline unsafe { device.cmd_bind_pipeline(buffer, vk::PipelineBindPoint::GRAPHICS, graphics_pipeline) }; // Bind vertex buffer let vertex_buffers = [vertex_buffer]; let offsets = [0]; unsafe { device.cmd_bind_vertex_buffers(buffer, 0, &vertex_buffers, &offsets) }; // Bind index buffer unsafe { device.cmd_bind_index_buffer(buffer, index_buffer, 0, vk::IndexType::UINT32) }; // Bind descriptor set unsafe { let null = []; device.cmd_bind_descriptor_sets( buffer, vk::PipelineBindPoint::GRAPHICS, pipeline_layout, 0, &descriptor_sets[i..=i], &null, ) }; // Draw unsafe { device.cmd_draw_indexed(buffer, index_count as _, 1, 0, 0, 0) }; // End render pass unsafe { device.cmd_end_render_pass(buffer) }; // End command buffer unsafe { device.end_command_buffer(buffer).unwrap() }; }); buffers } fn create_sync_objects(device: &Device) -> InFlightFrames { let mut sync_objects_vec = Vec::new(); for _ in 0..MAX_FRAMES_IN_FLIGHT { let image_available_semaphore = { let semaphore_info = vk::SemaphoreCreateInfo::default(); unsafe { device.create_semaphore(&semaphore_info, None).unwrap() } }; let render_finished_semaphore = { let semaphore_info = vk::SemaphoreCreateInfo::default(); unsafe { device.create_semaphore(&semaphore_info, None).unwrap() } }; let in_flight_fence = { let fence_info = vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); unsafe { device.create_fence(&fence_info, None).unwrap() } }; let sync_objects = SyncObjects { image_available_semaphore, render_finished_semaphore, fence: in_flight_fence, }; sync_objects_vec.push(sync_objects) } InFlightFrames::new(sync_objects_vec) } pub fn wait_gpu_idle(&self) { unsafe { self.vk_context.device().device_wait_idle().unwrap() }; } pub fn draw_frame(&mut self) -> bool { log::trace!("Drawing frame."); let sync_objects = self.in_flight_frames.next().unwrap(); let image_available_semaphore = sync_objects.image_available_semaphore; let render_finished_semaphore = sync_objects.render_finished_semaphore; let in_flight_fence = sync_objects.fence; let wait_fences = [in_flight_fence]; unsafe { self.vk_context .device() .wait_for_fences(&wait_fences, true, u64::MAX) .unwrap() }; let result = unsafe { self.swapchain.acquire_next_image( self.swapchain_khr, u64::MAX, image_available_semaphore, vk::Fence::null(), ) }; let image_index = match result { Ok((image_index, _)) => image_index, Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { return true; } Err(error) => panic!("Error while acquiring next image. Cause: {}", error), }; unsafe { self.vk_context.device().reset_fences(&wait_fences).unwrap() }; self.update_uniform_buffers(image_index); let device = self.vk_context.device(); let wait_semaphores = [image_available_semaphore]; let signal_semaphores = [render_finished_semaphore]; // Submit command buffer { let wait_stages = [vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]; let command_buffers = [self.command_buffers[image_index as usize]]; let submit_info = vk::SubmitInfo::default() .wait_semaphores(&wait_semaphores) .wait_dst_stage_mask(&wait_stages) .command_buffers(&command_buffers) .signal_semaphores(&signal_semaphores); let submit_infos = [submit_info]; unsafe { device .queue_submit(self.graphics_queue, &submit_infos, in_flight_fence) .unwrap() }; } let swapchains = [self.swapchain_khr]; let images_indices = [image_index]; { let present_info = vk::PresentInfoKHR::default() .wait_semaphores(&signal_semaphores) .swapchains(&swapchains) .image_indices(&images_indices); // .results() null since we only have one swapchain let result = unsafe { self.swapchain .queue_present(self.present_queue, &present_info) }; match result { Ok(true) | Err(vk::Result::ERROR_OUT_OF_DATE_KHR) => { return true; } Err(error) => panic!("Failed to present queue. Cause: {}", error), _ => {} } } false } /// Recreates the swapchain. /// /// If the window has been resized, then the new size is used /// otherwise, the size of the current swapchain is used. /// /// If the window has been minimized, then the functions block until /// the window is maximized. This is because a width or height of 0 /// is not legal. pub fn recreate_swapchain(&mut self) { log::debug!("Recreating swapchain."); self.wait_gpu_idle(); self.cleanup_swapchain(); let device = self.vk_context.device(); let dimensions = self.resize_dimensions; let (swapchain, swapchain_khr, properties, images) = Self::create_swapchain_and_images( &self.vk_context, self.queue_families_indices, dimensions, ); let swapchain_image_views = Self::create_swapchain_image_views(device, &images, properties); let render_pass = Self::create_render_pass(device, properties, self.msaa_samples, self.depth_format); let (pipeline, layout) = Self::create_pipeline( device, properties, self.msaa_samples, render_pass, self.descriptor_set_layout, ); let color_texture = Self::create_color_texture( &self.vk_context, self.command_pool, self.graphics_queue, properties, self.msaa_samples, ); let depth_texture = Self::create_depth_texture( &self.vk_context, self.command_pool, self.graphics_queue, self.depth_format, properties.extent, self.msaa_samples, ); let swapchain_framebuffers = Self::create_framebuffers( device, &swapchain_image_views, color_texture, depth_texture, render_pass, properties, ); let command_buffers = Self::create_and_register_command_buffers( device, self.command_pool, &swapchain_framebuffers, render_pass, properties, self.vertex_buffer, self.index_buffer, self.model_index_count, layout, &self.descriptor_sets, pipeline, ); self.swapchain = swapchain; self.swapchain_khr = swapchain_khr; self.swapchain_properties = properties; self.images = images; self.swapchain_image_views = swapchain_image_views; self.render_pass = render_pass; self.pipeline = pipeline; self.pipeline_layout = layout; self.color_texture = color_texture; self.depth_texture = depth_texture; self.swapchain_framebuffers = swapchain_framebuffers; self.command_buffers = command_buffers; } /// Clean up the swapchain and all resources that depends on it. fn cleanup_swapchain(&mut self) { let device = self.vk_context.device(); unsafe { self.depth_texture.destroy(device); self.color_texture.destroy(device); self.swapchain_framebuffers .iter() .for_each(|f| device.destroy_framebuffer(*f, None)); device.free_command_buffers(self.command_pool, &self.command_buffers); device.destroy_pipeline(self.pipeline, None); device.destroy_pipeline_layout(self.pipeline_layout, None); device.destroy_render_pass(self.render_pass, None); self.swapchain_image_views .iter() .for_each(|v| device.destroy_image_view(*v, None)); self.swapchain.destroy_swapchain(self.swapchain_khr, None); } } fn update_uniform_buffers(&mut self, current_image: u32) { if self.is_left_clicked && self.cursor_delta.is_some() { let delta = self.cursor_delta.take().unwrap(); let x_ratio = delta[0] as f32 / self.swapchain_properties.extent.width as f32; let y_ratio = delta[1] as f32 / self.swapchain_properties.extent.height as f32; let theta = x_ratio * 180.0_f32.to_radians(); let phi = y_ratio * 90.0_f32.to_radians(); self.camera.rotate(theta, phi); } if let Some(wheel_delta) = self.wheel_delta { self.camera.forward(wheel_delta * 0.3); } let aspect = self.swapchain_properties.extent.width as f32 / self.swapchain_properties.extent.height as f32; let ubo = UniformBufferObject { model: Matrix4::from_angle_x(Deg(270.0)), view: Matrix4::look_at_rh( self.camera.position(), Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 0.0), ), proj: math::perspective(Deg(45.0), aspect, 0.1, 10.0), }; let ubos = [ubo]; let buffer_mem = self.uniform_buffer_memories[current_image as usize]; let size = size_of::() as vk::DeviceSize; unsafe { let device = self.vk_context.device(); let data_ptr = device .map_memory(buffer_mem, 0, size, vk::MemoryMapFlags::empty()) .unwrap(); let mut align = ash::util::Align::new(data_ptr, align_of::() as _, size); align.copy_from_slice(&ubos); device.unmap_memory(buffer_mem); } } } impl Drop for VulkanApp { fn drop(&mut self) { log::debug!("Dropping application."); self.cleanup_swapchain(); let device = self.vk_context.device(); self.in_flight_frames.destroy(device); unsafe { device.destroy_descriptor_pool(self.descriptor_pool, None); device.destroy_descriptor_set_layout(self.descriptor_set_layout, None); self.uniform_buffer_memories .iter() .for_each(|m| device.free_memory(*m, None)); self.uniform_buffers .iter() .for_each(|b| device.destroy_buffer(*b, None)); device.free_memory(self.index_buffer_memory, None); device.destroy_buffer(self.index_buffer, None); device.destroy_buffer(self.vertex_buffer, None); device.free_memory(self.vertex_buffer_memory, None); self.texture.destroy(device); device.destroy_command_pool(self.transient_command_pool, None); device.destroy_command_pool(self.command_pool, None); } } } #[derive(Clone, Copy)] struct QueueFamiliesIndices { graphics_index: u32, present_index: u32, } #[derive(Clone, Copy)] struct SyncObjects { image_available_semaphore: vk::Semaphore, render_finished_semaphore: vk::Semaphore, fence: vk::Fence, } impl SyncObjects { fn destroy(&self, device: &Device) { unsafe { device.destroy_semaphore(self.image_available_semaphore, None); device.destroy_semaphore(self.render_finished_semaphore, None); device.destroy_fence(self.fence, None); } } } struct InFlightFrames { sync_objects: Vec, current_frame: usize, } impl InFlightFrames { fn new(sync_objects: Vec) -> Self { Self { sync_objects, current_frame: 0, } } fn destroy(&self, device: &Device) { self.sync_objects.iter().for_each(|o| o.destroy(device)); } } impl Iterator for InFlightFrames { type Item = SyncObjects; fn next(&mut self) -> Option { let next = self.sync_objects[self.current_frame]; self.current_frame = (self.current_frame + 1) % self.sync_objects.len(); Some(next) } } #[derive(Clone, Copy)] #[allow(dead_code)] #[repr(C)] struct Vertex { pos: [f32; 3], color: [f32; 3], coords: [f32; 2], } impl Vertex { fn get_binding_description() -> vk::VertexInputBindingDescription { vk::VertexInputBindingDescription::default() .binding(0) .stride(size_of::() as _) .input_rate(vk::VertexInputRate::VERTEX) } fn get_attribute_descriptions() -> [vk::VertexInputAttributeDescription; 3] { let position_desc = vk::VertexInputAttributeDescription::default() .binding(0) .location(0) .format(vk::Format::R32G32B32_SFLOAT) .offset(offset_of!(Vertex, pos) as _); let color_desc = vk::VertexInputAttributeDescription::default() .binding(0) .location(1) .format(vk::Format::R32G32B32_SFLOAT) .offset(offset_of!(Vertex, color) as _); let coords_desc = vk::VertexInputAttributeDescription::default() .binding(0) .location(2) .format(vk::Format::R32G32_SFLOAT) .offset(offset_of!(Vertex, coords) as _); [position_desc, color_desc, coords_desc] } } #[derive(Clone, Copy)] #[allow(dead_code)] struct UniformBufferObject { model: Matrix4, view: Matrix4, proj: Matrix4, } impl UniformBufferObject { fn get_descriptor_set_layout_binding<'a>() -> vk::DescriptorSetLayoutBinding<'a> { vk::DescriptorSetLayoutBinding::default() .binding(0) .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) .descriptor_count(1) .stage_flags(vk::ShaderStageFlags::VERTEX) // .immutable_samplers() null since we're not creating a sampler descriptor } } ================================================ FILE: src/math.rs ================================================ use cgmath::prelude::*; use cgmath::{BaseFloat, Matrix4, Rad}; /// Perspective matrix that is suitable for Vulkan. /// /// It inverts the projected y-axis. And set the depth range to 0..1 /// instead of -1..1. Mind the vertex winding order though. pub fn perspective(fovy: F, aspect: S, near: S, far: S) -> Matrix4 where S: BaseFloat, F: Into>, { let two = S::one() + S::one(); let f = Rad::cot(fovy.into() / two); let c0r0 = f / aspect; let c0r1 = S::zero(); let c0r2 = S::zero(); let c0r3 = S::zero(); let c1r0 = S::zero(); let c1r1 = -f; let c1r2 = S::zero(); let c1r3 = S::zero(); let c2r0 = S::zero(); let c2r1 = S::zero(); let c2r2 = -far / (far - near); let c2r3 = -S::one(); let c3r0 = S::zero(); let c3r1 = S::zero(); let c3r2 = -(far * near) / (far - near); let c3r3 = S::zero(); #[rustfmt::skip] let res = Matrix4::new( c0r0, c0r1, c0r2, c0r3, c1r0, c1r1, c1r2, c1r3, c2r0, c2r1, c2r2, c2r3, c3r0, c3r1, c3r2, c3r3, ); res } /// Clamp `value` between `min` and `max`. pub fn clamp(value: T, min: T, max: T) -> T { let value = if value > max { max } else { value }; if value < min { min } else { value } } ================================================ FILE: src/swapchain.rs ================================================ use ash::khr::surface; use ash::vk; pub struct SwapchainSupportDetails { pub capabilities: vk::SurfaceCapabilitiesKHR, pub formats: Vec, pub present_modes: Vec, } impl SwapchainSupportDetails { pub fn new( device: vk::PhysicalDevice, surface: &surface::Instance, surface_khr: vk::SurfaceKHR, ) -> Self { let capabilities = unsafe { surface .get_physical_device_surface_capabilities(device, surface_khr) .unwrap() }; let formats = unsafe { surface .get_physical_device_surface_formats(device, surface_khr) .unwrap() }; let present_modes = unsafe { surface .get_physical_device_surface_present_modes(device, surface_khr) .unwrap() }; Self { capabilities, formats, present_modes, } } pub fn get_ideal_swapchain_properties( &self, preferred_dimensions: [u32; 2], ) -> SwapchainProperties { let format = Self::choose_swapchain_surface_format(&self.formats); let present_mode = Self::choose_swapchain_surface_present_mode(&self.present_modes); let extent = Self::choose_swapchain_extent(self.capabilities, preferred_dimensions); SwapchainProperties { format, present_mode, extent, } } /// Choose the swapchain surface format. /// /// Will choose B8G8R8A8_UNORM/SRGB_NONLINEAR if possible or /// the first available otherwise. fn choose_swapchain_surface_format( available_formats: &[vk::SurfaceFormatKHR], ) -> vk::SurfaceFormatKHR { if available_formats.len() == 1 && available_formats[0].format == vk::Format::UNDEFINED { return vk::SurfaceFormatKHR { format: vk::Format::B8G8R8A8_UNORM, color_space: vk::ColorSpaceKHR::SRGB_NONLINEAR, }; } *available_formats .iter() .find(|format| { format.format == vk::Format::B8G8R8A8_UNORM && format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR }) .unwrap_or(&available_formats[0]) } /// Choose the swapchain present mode. /// /// Will favor MAILBOX if present otherwise FIFO. /// If none is present it will fallback to IMMEDIATE. fn choose_swapchain_surface_present_mode( available_present_modes: &[vk::PresentModeKHR], ) -> vk::PresentModeKHR { if available_present_modes.contains(&vk::PresentModeKHR::MAILBOX) { vk::PresentModeKHR::MAILBOX } else if available_present_modes.contains(&vk::PresentModeKHR::FIFO) { vk::PresentModeKHR::FIFO } else { vk::PresentModeKHR::IMMEDIATE } } /// Choose the swapchain extent. /// /// If a current extent is defined it will be returned. /// Otherwise the surface extent clamped between the min /// and max image extent will be returned. fn choose_swapchain_extent( capabilities: vk::SurfaceCapabilitiesKHR, preferred_dimensions: [u32; 2], ) -> vk::Extent2D { if capabilities.current_extent.width != u32::MAX { return capabilities.current_extent; } let min = capabilities.min_image_extent; let max = capabilities.max_image_extent; let width = preferred_dimensions[0].min(max.width).max(min.width); let height = preferred_dimensions[1].min(max.height).max(min.height); vk::Extent2D { width, height } } } #[derive(Clone, Copy, Debug)] pub struct SwapchainProperties { pub format: vk::SurfaceFormatKHR, pub present_mode: vk::PresentModeKHR, pub extent: vk::Extent2D, } ================================================ FILE: src/texture.rs ================================================ use ash::{Device, vk}; #[derive(Clone, Copy)] pub struct Texture { pub image: vk::Image, pub memory: vk::DeviceMemory, pub view: vk::ImageView, pub sampler: Option, } impl Texture { pub fn new( image: vk::Image, memory: vk::DeviceMemory, view: vk::ImageView, sampler: Option, ) -> Self { Texture { image, memory, view, sampler, } } pub fn destroy(&mut self, device: &Device) { unsafe { if let Some(sampler) = self.sampler.take() { device.destroy_sampler(sampler, None); } device.destroy_image_view(self.view, None); device.destroy_image(self.image, None); device.free_memory(self.memory, None); } } }