├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md ├── TODO.md ├── examples ├── 00_base_code.rs ├── 01_init_instance.rs ├── 02_basic_loop.rs ├── 03_loading_vertices.rs ├── 04_loading_indexed_vertices.rs ├── 05_uniform_buffers.rs ├── 06_textures.rs ├── 07_depth_buffer_test.rs ├── 08_model_loading.rs ├── 09_mip_levels.rs └── 10_msaa.rs ├── rustfmt.toml ├── sarekt_screenshot.png ├── sarekt_screenshot2.png ├── setup.sh ├── shaders ├── no_buffer_triangle.frag ├── no_buffer_triangle.vert ├── sarekt_forward.frag └── sarekt_forward.vert └── src ├── error.rs ├── image_data.rs ├── lib.rs └── renderer ├── buffers_and_images.rs ├── config.rs ├── drawable_object.rs ├── mod.rs ├── shaders.rs ├── vertex_bindings.rs └── vulkan ├── images.rs ├── mod.rs ├── queues.rs ├── vulkan_buffer_image_functions.rs ├── vulkan_renderer ├── base_pipeline_bundle.rs ├── debug_utils_ext.rs ├── draw_synchronization.rs ├── mod.rs ├── pipelines.rs ├── render_attachments.rs ├── render_targets.rs ├── surface.rs ├── swap_chain.rs └── vulkan_core.rs ├── vulkan_shader_functions.rs └── vulkan_vertex_bindings.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | **/*.rs.bk 3 | .idea 4 | Cargo.lock 5 | *.png 6 | *.jpg 7 | *.obj 8 | *.glb 9 | *.gltf 10 | *.zip 11 | models 12 | textures 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sarekt" 3 | version = "0.0.4" 4 | authors = ["Brandon Pollack "] 5 | description = "A rendering engine based on Vulkan, but capable of being expanded to other graphics API backends such as Metal or D3D12" 6 | license = "Apache-2.0" 7 | repository = "https://github.com/brandonpollack23/sarekt" 8 | readme = "README.md" 9 | keywords = ["vulkan", "graphics", "ash", "rendering"] 10 | documentation = "https://docs.rs/sarekt" 11 | edition = "2018" 12 | exclude = ["models", "textures"] 13 | 14 | [[example]] 15 | name = "00_base_code" 16 | path = "examples/00_base_code.rs" 17 | 18 | [[example]] 19 | name = "01_init_instance" 20 | path = "examples/01_init_instance.rs" 21 | 22 | [[example]] 23 | name = "02_basic_loop" 24 | path = "examples/02_basic_loop.rs" 25 | 26 | [[example]] 27 | name = "03_loading_vertices" 28 | path = "examples/03_loading_vertices.rs" 29 | 30 | [[example]] 31 | name = "04_loading_indexed_vertices" 32 | path = "examples/04_loading_indexed_vertices.rs" 33 | 34 | [[example]] 35 | name = "05_uniform_buffers" 36 | path = "examples/05_uniform_buffers.rs" 37 | 38 | [[example]] 39 | name = "06_textures" 40 | path = "examples/06_textures.rs" 41 | 42 | [[example]] 43 | name = "07_depth_buffer_test" 44 | path = "examples/07_depth_buffer_test.rs" 45 | 46 | [[example]] 47 | name = "08_model_loading" 48 | path = "examples/08_model_loading.rs" 49 | 50 | [[example]] 51 | name = "09_mip_levels" 52 | path = "examples/09_mip_levels.rs" 53 | 54 | [[example]] 55 | name = "10_msaa" 56 | path = "examples/10_msaa.rs" 57 | 58 | [dependencies] 59 | ash = "0.30.0" 60 | ash-window = "0.3.0" 61 | derive_builder = "0.9.0" 62 | image = "0.23.2" 63 | lazy_static = "1.4.0" 64 | log = "0.4.8" 65 | memoffset = "0.5.3" 66 | raw-window-handle = "0.3.3" 67 | safe-transmute = "0.11.0-rc.2" 68 | slotmap = "0.4.0" 69 | static_assertions = "1.1.0" 70 | ultraviolet = "0.4.6" 71 | vk-mem = "0.2.2" 72 | vk-shader-macros = "0.2.2" 73 | 74 | [dev-dependencies] 75 | gltf = "0.15.2" 76 | simple_logger = "1.5.0" 77 | wavefront_obj = "7.0.0" 78 | winit = "0.21.0" 79 | itertools = "0.9.0" 80 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sarekt 2 | 3 | [![Crates.io](https://img.shields.io/crates/v/sarekt.svg)](https://crates.io/crates/sarekt) 4 | [![](https://tokei.rs/b1/github/brandonpollack23/sarekt)](https://github.com/brandonpollack23/sarekt) 5 | 6 | ![screenshot](./sarekt_screenshot.png) 7 | 8 | ## Overview 9 | 10 | This is my third attempt to make something in Vulkan. I think I've accumulated enough knowledge and 11 | wherewithal to make something worthwhile. 12 | 13 | This is a renderer that has Vulkan first as it's backend that I want to keep relatively low level. 14 | I think it'll expose a lot at first, and slowly begin wrapping this again in another wrapper 15 | I'll call "Shran", another Star Trek character. 16 | 17 | Shran wrapper. Get it? 18 | 19 | ## Setup 20 | 21 | As a library just include the crate and install the vulkan SDK (need it for shader compilation via glslc and validation layers). 22 | * Make sure the VulkanSDK/VERSION/Bin folder is in your path if this wasn't handled by a package manager (if you downloaded from LunarG's site!). 23 | * Also remember to set up libshaderc path as described [in shaderc-sys](https://github.com/google/shaderc-rs), which involves setting the path for SHADERC_LIB_DIR. 24 | 25 | Examples require one extra step: 26 | I used to use LFS, but its expensive once people started cloning and using my max bandwidth. If i weren't sharing this I'd use Azure Devops or something, but it's on github, so I hosted the assets in gcs. 27 | 28 | I would really appreciate that you didn't use git LFS to pull down the files. I'm not going to renew the upped bandwidth, so please use the setup if you want the asset files :). 29 | 30 | So now you need to run setup.sh which just curls the zip and unzips it. If you're on windows use git bash or mingw to run it or something (like I did). 31 | 32 | ## Usage 33 | 34 | This readme is minimal. Cargo doc is your friend. This is far from done. 35 | 36 | The most up to date documentation/usage you'll get is by checkout out the 37 | examples (later is better). 38 | 39 | Sarekt can load arbitrary models, textures, and uniforms and display them. 40 | 41 | Textures can be any image format supported by the image crate and will be 42 | converted 43 | 44 | Only one pipeline and render pass type. 45 | 46 | ## Hero Dependencies 47 | See the dependencies of this project. Seriously the Rust community is just 48 | fantastic. 49 | * [ash](https://crates.io/crates/ash) Rediculously good vulkan bindings with 50 | builders and rock solid velocity. 51 | * [vk-mem](https://crates.io/crates/vk-mem) Solid rust wrapper around an 52 | amazing allocation library so I dont have to make GPU malloc all by myself 53 | and can get to writing Vulkan code! I fixed a bug and somehow become a 54 | contributor to this one 55 | * [vk-shader-macros](https://crates.io/crates/vk-shader-macros) Shader macros that compile GLSL to SPIR-V and include the bytes for me. Juicy. 56 | * [log](https://crates.io/crates/log) Abstract at compile time logging. 57 | * [winit](https://crates.io/crates/winit) Platform agnostic window and vulkan context creation. 58 | * [slotmap](https://crates.io/crates/slotmap) A great generational index 59 | store, useful for handles 60 | * [wavefront_obj](https://crates.io/crates/wavefront_obj) Model loader for obj 61 | * [gltf](https://crates.io/crates/gltf) Model loader for GlTf 62 | * [image](https://crates.io/crates/image) Loading image data. 63 | 64 | There are more but these ones I rely on the most, please check them all out. 65 | 66 | ## Name 67 | "Sarek" is the Vulcan father of the character "Spok" of Star Trek. The added t also makes it a portmanteu with rect(angle). 68 | 69 | I know and I'm sorry. 70 | 71 | ## Examples 72 | All the examples that are straightforward with no params have no section here, just run'em with `cargo run --example NAME` 73 | 74 | Those that have arguments just pass them like so:
75 | `cargo run --example 06_textures -- colors` 76 | 77 | ### Example 6, Textures 78 | This is where things finally start to get fun,there's a param to enable color mixing with the texture. 79 | 80 | Starting with this example, the application coordinate space is right handed y 81 | up for simplicity, the ultraviolet library perspective functions correct it for the appropriate backend. 82 | 83 | arguments: 84 | * *colors* - turns on the color mixing from raw colors, a simple multiplicative color mix in the default forward shader 85 | 86 | ### Example 7 Depth buffer 87 | 88 | Here we can see the depth buffer working in action. Camera moves so you an 89 | see the 3d effect. 90 | 91 | Flags: 92 | * *fps* -- display fps 93 | 94 | ### Example 8 Model loading. 95 | 96 | Load a real 3d model (PreBaked lighting only) 97 | 98 | Flags: 99 | * *glb* -- load glb version of the model. 100 | * *fps* -- show fps 101 | 102 | ### Example 9 mip mapping 103 | 104 | Enable mip mapping. 105 | 106 | ### Example 10 MSAA 107 | 108 | Enable MSAA. Does 2 by default 109 | 110 | Flags: 111 | * *2x* -- 2x MSAA 112 | * *4x* -- 4x MSAA 113 | * *8x* -- 8x MSAA 114 | * *noaa* -- turn off antialiasing. 115 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## write examples to load meshes etc. use assimp? 2 | 3 | ## More Generic Program 4 | Things should be generic! 5 | * Width 6 | * Height 7 | * Fullscreen 8 | * VSync or no, triple buffering or no 9 | * Specify validation logging level 10 | * Specify validation layers/debug mode 11 | * Features/required extensions 12 | 13 | ## Get memory usage call 14 | 15 | ## Make deps build in "release" mode to increase speed see [this](https://rust-gamedev.github.io/posts/newsletter-006/#rust-1-41-profile-overrides-are-stable-now) 16 | 17 | -------------------------------------------------------------------------------- /examples/00_base_code.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | 3 | struct SarektApp; 4 | impl SarektApp { 5 | fn new() -> Result> { 6 | println!("Creating App"); 7 | Ok(Self) 8 | } 9 | 10 | fn run(&mut self) { 11 | println!("Running App"); 12 | } 13 | } 14 | 15 | fn main() -> Result<(), Box> { 16 | let mut app = SarektApp::new()?; 17 | app.run(); 18 | Ok(()) 19 | } 20 | -------------------------------------------------------------------------------- /examples/01_init_instance.rs: -------------------------------------------------------------------------------- 1 | use log::{info, Level}; 2 | use sarekt::{ 3 | self, 4 | renderer::{config::Config, VulkanRenderer}, 5 | }; 6 | use std::{error::Error, sync::Arc}; 7 | use winit::{event_loop::EventLoop, window::WindowBuilder}; 8 | 9 | const WIDTH: u32 = 800; 10 | const HEIGHT: u32 = 800; 11 | 12 | fn main() -> Result<(), Box> { 13 | simple_logger::init_with_level(Level::Info)?; 14 | info!("Creating App"); 15 | 16 | let event_loop = EventLoop::new(); 17 | let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap()); 18 | let config = Config::builder() 19 | .requested_width(WIDTH) 20 | .requested_height(HEIGHT) 21 | .build() 22 | .unwrap(); 23 | let _renderer = VulkanRenderer::new(window.clone(), config).unwrap(); 24 | Ok(()) 25 | } 26 | -------------------------------------------------------------------------------- /examples/02_basic_loop.rs: -------------------------------------------------------------------------------- 1 | use log::{info, warn, Level}; 2 | use sarekt::{ 3 | self, 4 | error::SarektError, 5 | renderer::{config::Config, Renderer, VulkanRenderer}, 6 | }; 7 | use std::{error::Error, sync::Arc}; 8 | use winit::{ 9 | dpi::{LogicalSize, PhysicalSize}, 10 | event::{ElementState, Event, VirtualKeyCode, WindowEvent}, 11 | event_loop::{ControlFlow, EventLoop}, 12 | window::{Window, WindowBuilder, WindowId}, 13 | }; 14 | 15 | const WIDTH: u32 = 800; 16 | const HEIGHT: u32 = 600; 17 | 18 | struct SarektApp { 19 | renderer: VulkanRenderer, 20 | event_loop: EventLoop<()>, 21 | window: Arc, 22 | } 23 | impl SarektApp { 24 | fn new() -> Result { 25 | info!("Creating App"); 26 | 27 | let event_loop = EventLoop::new(); 28 | let window = Arc::new( 29 | WindowBuilder::new() 30 | .with_inner_size(LogicalSize::new(WIDTH, HEIGHT)) 31 | .build(&event_loop) 32 | .unwrap(), 33 | ); 34 | let config = Config::builder() 35 | .requested_width(WIDTH) 36 | .requested_height(HEIGHT) 37 | .build() 38 | .unwrap(); 39 | let renderer = VulkanRenderer::new(window.clone(), config).unwrap(); 40 | 41 | Ok(Self { 42 | renderer, 43 | event_loop, 44 | window, 45 | }) 46 | } 47 | 48 | /// Takes full control of the executing thread and runs the event loop for it. 49 | fn main_loop(self) { 50 | info!("Running main loop..."); 51 | let window = self.window; 52 | let mut renderer = self.renderer; 53 | self.event_loop.run(move |event, _, control_flow| { 54 | // By default continuously run this event loop, even if the OS hasn't 55 | // distributed an event, that way we will draw as fast as possible. 56 | *control_flow = ControlFlow::Poll; 57 | 58 | match event { 59 | Event::MainEventsCleared => { 60 | // All the main events to process are done we can do "work" now (game 61 | // engine state update etc.) 62 | 63 | // Nothing to do though...yet. 64 | 65 | // At the end of "work" request redraw. 66 | window.request_redraw(); 67 | } 68 | Event::RedrawRequested(_) => { 69 | // Redraw requested, this is called after MainEventsCleared. 70 | renderer.frame().unwrap_or_else(|err| { 71 | match err { 72 | SarektError::SwapchainOutOfDate => { 73 | // Handle window resize etc. 74 | warn!("Tried to render without processing window resize event!"); 75 | let PhysicalSize { width, height } = window.inner_size(); 76 | renderer.recreate_swapchain(width, height).unwrap(); 77 | } 78 | e => panic!(e), 79 | } 80 | }); 81 | } 82 | Event::WindowEvent { window_id, event } => { 83 | Self::main_loop_window_event(&event, &window_id, control_flow, &mut renderer); 84 | } 85 | _ => (), 86 | } 87 | }); 88 | } 89 | 90 | fn main_loop_window_event( 91 | event: &WindowEvent, _id: &WindowId, control_flow: &mut winit::event_loop::ControlFlow, 92 | renderer: &mut VulkanRenderer, 93 | ) { 94 | match event { 95 | WindowEvent::CloseRequested => { 96 | // When the window system requests a close, signal to winit that we'd like to 97 | // close the window. 98 | info!("Exiting due to close request event from window system..."); 99 | *control_flow = ControlFlow::Exit 100 | } 101 | WindowEvent::KeyboardInput { input, .. } => { 102 | // When the keyboard input is a press on the escape key, exit and print the 103 | // line. 104 | if let (Some(VirtualKeyCode::Escape), ElementState::Pressed) = 105 | (input.virtual_keycode, input.state) 106 | { 107 | info!("Exiting due to escape press..."); 108 | *control_flow = ControlFlow::Exit 109 | } 110 | } 111 | WindowEvent::Resized(size) => { 112 | // If the size is 0, minimization or something like that happened so I 113 | // toggle drawing. 114 | info!("Window resized, recreating renderer swapchain..."); 115 | let enabled = !(size.height == 0 && size.width == 0); 116 | renderer.set_rendering_enabled(enabled); 117 | renderer 118 | .recreate_swapchain(size.width, size.height) 119 | .unwrap(); 120 | } 121 | _ => (), 122 | } 123 | } 124 | } 125 | 126 | fn main() -> Result<(), Box> { 127 | simple_logger::init_with_level(Level::Info)?; 128 | let app = SarektApp::new().expect("Could not create instance!"); 129 | app.main_loop(); 130 | Ok(()) 131 | } 132 | -------------------------------------------------------------------------------- /examples/03_loading_vertices.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use log::{info, warn, Level}; 3 | use sarekt::{ 4 | self, 5 | error::{SarektError, SarektResult}, 6 | renderer::{ 7 | buffers_and_images::BufferType, 8 | config::Config, 9 | drawable_object::DrawableObject, 10 | vertex_bindings::{DefaultForwardShaderLayout, DefaultForwardShaderVertex}, 11 | Drawer, Renderer, VulkanRenderer, 12 | }, 13 | }; 14 | use std::{error::Error, sync::Arc}; 15 | use ultraviolet as uv; 16 | use winit::{ 17 | dpi::{LogicalSize, PhysicalSize}, 18 | event::{ElementState, Event, VirtualKeyCode, WindowEvent}, 19 | event_loop::{ControlFlow, EventLoop}, 20 | platform::desktop::EventLoopExtDesktop, 21 | window::{WindowBuilder, WindowId}, 22 | }; 23 | 24 | const WIDTH: u32 = 800; 25 | const HEIGHT: u32 = 600; 26 | 27 | lazy_static! { 28 | static ref TRIANGLE_VERTICES: Vec = vec![ 29 | DefaultForwardShaderVertex::without_uv(&[0.0f32, -0.5f32, 0.0f32], &[1.0f32, 0.0f32, 0.0f32]), // Top, Red 30 | DefaultForwardShaderVertex::without_uv(&[0.5f32, 0.5f32, 0.0f32], &[0.0f32, 1.0f32, 0.0f32]), // Right, Green 31 | DefaultForwardShaderVertex::without_uv(&[-0.5f32, 0.5f32, 0.0f32], &[0.0f32, 0.0f32, 1.0f32]), // Left, Blue 32 | ]; 33 | } 34 | 35 | fn main() -> Result<(), Box> { 36 | simple_logger::init_with_level(Level::Info)?; 37 | main_loop()?; 38 | Ok(()) 39 | } 40 | 41 | /// Takes full control of the executing thread and runs the event loop for it. 42 | fn main_loop() -> SarektResult<()> { 43 | info!("Running main loop..."); 44 | 45 | // Build Window. 46 | let mut event_loop = EventLoop::new(); 47 | let window = Arc::new( 48 | WindowBuilder::new() 49 | .with_inner_size(LogicalSize::new(WIDTH, HEIGHT)) 50 | .build(&event_loop) 51 | .unwrap(), 52 | ); 53 | 54 | // Build Renderer. 55 | let config = Config::builder() 56 | .requested_width(WIDTH) 57 | .requested_height(HEIGHT) 58 | .build() 59 | .unwrap(); 60 | let mut renderer = VulkanRenderer::new(window.clone(), config).unwrap(); 61 | 62 | // Create Resources. 63 | let triangle_buffer = renderer.load_buffer(BufferType::Vertex, &TRIANGLE_VERTICES)?; 64 | let uniform_buffer = renderer.load_uniform_buffer(DefaultForwardShaderLayout::new( 65 | uv::Mat4::identity(), 66 | true, 67 | false, 68 | ))?; 69 | let triangle = DrawableObject::builder(&renderer) 70 | .vertex_buffer(&triangle_buffer) 71 | .uniform_buffer(&uniform_buffer) 72 | .build()?; 73 | 74 | // Run the loop. 75 | event_loop.run_return(move |event, _, control_flow| { 76 | // By default continuously run this event loop, even if the OS hasn't 77 | // distributed an event, that way we will draw as fast as possible. 78 | *control_flow = ControlFlow::Poll; 79 | 80 | match event { 81 | Event::MainEventsCleared => { 82 | // All the main events to process are done we can do "work" now (game 83 | // engine state update etc.) 84 | 85 | renderer.draw(&triangle).expect("Unable to draw triangle!"); 86 | 87 | // At the end of work request redraw. 88 | window.request_redraw(); 89 | } 90 | 91 | Event::RedrawRequested(_) => { 92 | // Redraw requested, this is called after MainEventsCleared. 93 | renderer.frame().unwrap_or_else(|err| { 94 | match err { 95 | SarektError::SwapchainOutOfDate => { 96 | // Handle window resize etc. 97 | warn!("Tried to render without processing window resize event!"); 98 | 99 | let PhysicalSize { width, height } = window.inner_size(); 100 | renderer 101 | .recreate_swapchain(width, height) 102 | .expect("Error recreating swapchain"); 103 | } 104 | e => panic!(e), 105 | } 106 | }); 107 | } 108 | 109 | Event::WindowEvent { window_id, event } => { 110 | main_loop_window_event(&event, &window_id, control_flow, &mut renderer) 111 | .expect("Error processing window event."); 112 | } 113 | 114 | Event::LoopDestroyed => { 115 | // Explicitly call exit so resources are cleaned up. 116 | std::process::exit(0); 117 | } 118 | _ => (), 119 | } 120 | }); 121 | 122 | Ok(()) 123 | } 124 | 125 | /// Handles all winit window specific events. 126 | fn main_loop_window_event( 127 | event: &WindowEvent, _id: &WindowId, control_flow: &mut winit::event_loop::ControlFlow, 128 | renderer: &mut VulkanRenderer, 129 | ) -> SarektResult<()> { 130 | match event { 131 | WindowEvent::CloseRequested => { 132 | // When the window system requests a close, signal to winit that we'd like to 133 | // close the window. 134 | info!("Exiting due to close request event from window system..."); 135 | *control_flow = ControlFlow::Exit; 136 | } 137 | 138 | WindowEvent::KeyboardInput { input, .. } => { 139 | // When the keyboard input is a press on the escape key, exit and print the 140 | // line. 141 | if let (Some(VirtualKeyCode::Escape), ElementState::Pressed) = 142 | (input.virtual_keycode, input.state) 143 | { 144 | info!("Exiting due to escape press..."); 145 | *control_flow = ControlFlow::Exit 146 | } 147 | } 148 | 149 | WindowEvent::Resized(size) => { 150 | // If the size is 0, minimization or something like that happened so I 151 | // toggle drawing. 152 | info!("Window resized, recreating renderer swapchain..."); 153 | let enabled = !(size.height == 0 && size.width == 0); 154 | renderer.set_rendering_enabled(enabled); 155 | return renderer.recreate_swapchain(size.width, size.height); 156 | } 157 | 158 | _ => (), 159 | } 160 | 161 | Ok(()) 162 | } 163 | -------------------------------------------------------------------------------- /examples/04_loading_indexed_vertices.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use log::{info, warn, Level}; 3 | use sarekt::{ 4 | self, 5 | error::{SarektError, SarektResult}, 6 | renderer::{ 7 | buffers_and_images::{BufferType, IndexBufferElemSize}, 8 | config::Config, 9 | drawable_object::DrawableObject, 10 | vertex_bindings::{DefaultForwardShaderLayout, DefaultForwardShaderVertex}, 11 | Drawer, Renderer, VulkanRenderer, 12 | }, 13 | }; 14 | use std::{error::Error, sync::Arc}; 15 | use ultraviolet as uv; 16 | use winit::{ 17 | dpi::{LogicalSize, PhysicalSize}, 18 | event::{ElementState, Event, VirtualKeyCode, WindowEvent}, 19 | event_loop::{ControlFlow, EventLoop}, 20 | platform::desktop::EventLoopExtDesktop, 21 | window::{WindowBuilder, WindowId}, 22 | }; 23 | 24 | const WIDTH: u32 = 800; 25 | const HEIGHT: u32 = 600; 26 | 27 | lazy_static! { 28 | static ref RECT_VERTICES: Vec = vec![ 29 | DefaultForwardShaderVertex::without_uv(&[-0.5f32, -0.5f32, 0.0f32], &[1.0f32, 0.0f32, 0.0f32]), // Top Left, Red 30 | DefaultForwardShaderVertex::without_uv(&[0.5f32, -0.5f32, 0.0f32], &[0.0f32, 1.0f32, 0.0f32]), // Top Right, Green 31 | DefaultForwardShaderVertex::without_uv(&[0.5f32, 0.5f32, 0.0f32], &[0.0f32, 0.0f32, 1.0f32]), // Bottom Right, Blue 32 | DefaultForwardShaderVertex::without_uv(&[-0.5f32, 0.5f32, 0.0f32], &[1.0f32, 1.0f32, 1.0f32]), // Bottom Left, White 33 | ]; 34 | } 35 | const RECT_INDICES: [u16; 6] = [0u16, 2u16, 1u16, 2u16, 0u16, 3u16]; // two triangles, upper right and lower left 36 | 37 | fn main() -> Result<(), Box> { 38 | simple_logger::init_with_level(Level::Info)?; 39 | main_loop()?; 40 | Ok(()) 41 | } 42 | 43 | /// Takes full control of the executing thread and runs the event loop for it. 44 | fn main_loop() -> SarektResult<()> { 45 | info!("Running main loop..."); 46 | 47 | // Build Window. 48 | let mut event_loop = EventLoop::new(); 49 | let window = Arc::new( 50 | WindowBuilder::new() 51 | .with_inner_size(LogicalSize::new(WIDTH, HEIGHT)) 52 | .build(&event_loop) 53 | .unwrap(), 54 | ); 55 | 56 | // Build Renderer. 57 | let config = Config::builder() 58 | .requested_width(WIDTH) 59 | .requested_height(HEIGHT) 60 | .build() 61 | .unwrap(); 62 | let mut renderer = VulkanRenderer::new(window.clone(), config).unwrap(); 63 | 64 | // Create Resources. 65 | let rect_vertex_buffer = renderer.load_buffer(BufferType::Vertex, &RECT_VERTICES)?; 66 | let rect_uniform_buffer = renderer.load_uniform_buffer(DefaultForwardShaderLayout::new( 67 | uv::Mat4::identity(), 68 | true, 69 | false, 70 | ))?; 71 | 72 | let rect_index_buffer = renderer.load_buffer( 73 | BufferType::Index(IndexBufferElemSize::UInt16), 74 | &RECT_INDICES, 75 | )?; 76 | let rect: DrawableObject = DrawableObject::builder(&renderer) 77 | .vertex_buffer(&rect_vertex_buffer) 78 | .index_buffer(&rect_index_buffer) 79 | .uniform_buffer(&rect_uniform_buffer) 80 | .build()?; 81 | 82 | // Run the loop. 83 | event_loop.run_return(move |event, _, control_flow| { 84 | // By default continuously run this event loop, even if the OS hasn't 85 | // distributed an event, that way we will draw as fast as possible. 86 | *control_flow = ControlFlow::Poll; 87 | 88 | match event { 89 | Event::MainEventsCleared => { 90 | // All the main events to process are done we can do "work" now (game 91 | // engine state update etc.) 92 | 93 | renderer.draw(&rect).expect("Unable to draw triangle!"); 94 | 95 | // At the end of work request redraw. 96 | window.request_redraw(); 97 | } 98 | 99 | Event::RedrawRequested(_) => { 100 | // Redraw requested, this is called after MainEventsCleared. 101 | renderer.frame().unwrap_or_else(|err| { 102 | match err { 103 | SarektError::SwapchainOutOfDate | SarektError::SuboptimalSwapchain => { 104 | // Handle window resize etc. 105 | warn!("Tried to render without processing window resize event!"); 106 | 107 | let PhysicalSize { width, height } = window.inner_size(); 108 | renderer 109 | .recreate_swapchain(width, height) 110 | .expect("Error recreating swapchain"); 111 | } 112 | e => panic!("Frame had an unrecoverable error! {}", e), 113 | } 114 | }); 115 | } 116 | 117 | Event::WindowEvent { window_id, event } => { 118 | main_loop_window_event(&event, &window_id, control_flow, &mut renderer) 119 | .expect("Error processing window event."); 120 | } 121 | 122 | Event::LoopDestroyed => { 123 | // Explicitly call exit so resources are cleaned up. 124 | std::process::exit(0); 125 | } 126 | _ => (), 127 | } 128 | }); 129 | 130 | Ok(()) 131 | } 132 | 133 | /// Handles all winit window specific events. 134 | fn main_loop_window_event( 135 | event: &WindowEvent, _id: &WindowId, control_flow: &mut winit::event_loop::ControlFlow, 136 | renderer: &mut VulkanRenderer, 137 | ) -> SarektResult<()> { 138 | match event { 139 | WindowEvent::CloseRequested => { 140 | // When the window system requests a close, signal to winit that we'd like to 141 | // close the window. 142 | info!("Exiting due to close request event from window system..."); 143 | *control_flow = ControlFlow::Exit; 144 | } 145 | 146 | WindowEvent::KeyboardInput { input, .. } => { 147 | // When the keyboard input is a press on the escape key, exit and print the 148 | // line. 149 | if let (Some(VirtualKeyCode::Escape), ElementState::Pressed) = 150 | (input.virtual_keycode, input.state) 151 | { 152 | info!("Exiting due to escape press..."); 153 | *control_flow = ControlFlow::Exit 154 | } 155 | } 156 | 157 | WindowEvent::Resized(size) => { 158 | // If the size is 0, minimization or something like that happened so I 159 | // toggle drawing. 160 | info!("Window resized, recreating renderer swapchain..."); 161 | let enabled = !(size.height == 0 && size.width == 0); 162 | renderer.set_rendering_enabled(enabled); 163 | return renderer.recreate_swapchain(size.width, size.height); 164 | } 165 | 166 | _ => (), 167 | } 168 | 169 | Ok(()) 170 | } 171 | -------------------------------------------------------------------------------- /examples/05_uniform_buffers.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use log::{info, warn, Level}; 3 | use sarekt::{ 4 | self, 5 | error::{SarektError, SarektResult}, 6 | renderer::{ 7 | buffers_and_images::{BufferType, IndexBufferElemSize}, 8 | config::Config, 9 | drawable_object::DrawableObject, 10 | vertex_bindings::{DefaultForwardShaderLayout, DefaultForwardShaderVertex}, 11 | Drawer, Renderer, VulkanRenderer, 12 | }, 13 | }; 14 | use std::{error::Error, f32, sync::Arc, time::Instant}; 15 | use ultraviolet as uv; 16 | use winit::{ 17 | dpi::{LogicalSize, PhysicalSize}, 18 | event::{ElementState, Event, VirtualKeyCode, WindowEvent}, 19 | event_loop::{ControlFlow, EventLoop}, 20 | platform::desktop::EventLoopExtDesktop, 21 | window::{WindowBuilder, WindowId}, 22 | }; 23 | 24 | const WIDTH: u32 = 800; 25 | const HEIGHT: u32 = 600; 26 | 27 | lazy_static! { 28 | static ref RECT_VERTICES: Vec = vec![ 29 | DefaultForwardShaderVertex::without_uv(&[-0.5f32, -0.5f32, 0.0f32], &[1.0f32, 0.0f32, 0.0f32]), // Top Left, Red 30 | DefaultForwardShaderVertex::without_uv(&[0.5f32, -0.5f32, 0.0f32], &[0.0f32, 1.0f32, 0.0f32]), // Top Right, Green 31 | DefaultForwardShaderVertex::without_uv(&[0.5f32, 0.5f32, 0.0f32], &[0.0f32, 0.0f32, 1.0f32]), // Bottom Right, Blue 32 | DefaultForwardShaderVertex::without_uv(&[-0.5f32, 0.5f32, 0.0f32], &[1.0f32, 1.0f32, 1.0f32]), // Bottom Left, White 33 | ]; 34 | } 35 | const RECT_INDICES: [u16; 6] = [0u16, 2u16, 1u16, 2u16, 0u16, 3u16]; // two triangles, upper right and lower left 36 | 37 | fn main() -> Result<(), Box> { 38 | simple_logger::init_with_level(Level::Info)?; 39 | main_loop()?; 40 | Ok(()) 41 | } 42 | 43 | /// Takes full control of the executing thread and runs the event loop for it. 44 | fn main_loop() -> SarektResult<()> { 45 | info!("Running main loop..."); 46 | 47 | let mut ar = WIDTH as f32 / HEIGHT as f32; 48 | 49 | // Build Window. 50 | let mut event_loop = EventLoop::new(); 51 | let window = Arc::new( 52 | WindowBuilder::new() 53 | .with_inner_size(LogicalSize::new(WIDTH, HEIGHT)) 54 | .build(&event_loop) 55 | .unwrap(), 56 | ); 57 | 58 | // Build Renderer. 59 | let config = Config::builder() 60 | .requested_width(WIDTH) 61 | .requested_height(HEIGHT) 62 | .build() 63 | .unwrap(); 64 | let mut renderer = VulkanRenderer::new(window.clone(), config).unwrap(); 65 | 66 | // Create Resources. 67 | let rect_vertex_buffer = renderer.load_buffer(BufferType::Vertex, &RECT_VERTICES)?; 68 | let rect_index_buffer = renderer.load_buffer( 69 | BufferType::Index(IndexBufferElemSize::UInt16), 70 | &RECT_INDICES, 71 | )?; 72 | let rect_uniform = DefaultForwardShaderLayout::default(); 73 | let rect_uniform_buffer = renderer.load_uniform_buffer(rect_uniform)?; 74 | let rect: DrawableObject = DrawableObject::builder(&renderer) 75 | .vertex_buffer(&rect_vertex_buffer) 76 | .index_buffer(&rect_index_buffer) 77 | .uniform_buffer(&rect_uniform_buffer) 78 | .build()?; 79 | 80 | let start_time = Instant::now(); 81 | 82 | // Run the loop. 83 | event_loop.run_return(move |event, _, control_flow| { 84 | // By default continuously run this event loop, even if the OS hasn't 85 | // distributed an event, that way we will draw as fast as possible. 86 | *control_flow = ControlFlow::Poll; 87 | 88 | match event { 89 | Event::MainEventsCleared => { 90 | // All the main events to process are done we can do "work" now (game 91 | // engine state update etc.) 92 | 93 | update_uniforms(&renderer, &rect, start_time, ar).unwrap(); 94 | renderer.draw(&rect).expect("Unable to draw triangle!"); 95 | 96 | // At the end of work request redraw. 97 | window.request_redraw(); 98 | } 99 | 100 | Event::RedrawRequested(_) => { 101 | // Redraw requested, this is called after MainEventsCleared. 102 | renderer.frame().unwrap_or_else(|err| { 103 | match err { 104 | SarektError::SwapchainOutOfDate | SarektError::SuboptimalSwapchain => { 105 | // Handle window resize etc. 106 | warn!("Tried to render without processing window resize event!"); 107 | 108 | let PhysicalSize { width, height } = window.inner_size(); 109 | renderer 110 | .recreate_swapchain(width, height) 111 | .expect("Error recreating swapchain"); 112 | } 113 | e => panic!("Frame had an unrecoverable error! {}", e), 114 | } 115 | }); 116 | } 117 | 118 | Event::WindowEvent { window_id, event } => { 119 | main_loop_window_event(&event, &window_id, control_flow, &mut renderer, &mut ar) 120 | .expect("Error processing window event."); 121 | } 122 | 123 | Event::LoopDestroyed => { 124 | // Explicitly call exit so resources are cleaned up. 125 | std::process::exit(0); 126 | } 127 | _ => (), 128 | } 129 | }); 130 | 131 | Ok(()) 132 | } 133 | 134 | fn update_uniforms( 135 | renderer: &VulkanRenderer, rect: &DrawableObject, 136 | start_time: Instant, ar: f32, 137 | ) -> SarektResult<()> { 138 | let now = Instant::now(); 139 | 140 | let time_since_start_secs = ((now - start_time).as_millis() as f32) / 1000f32; 141 | 142 | // Pi radians per second around the z axis. 143 | let rotation = (std::f32::consts::PI * time_since_start_secs) % (2f32 * std::f32::consts::PI); 144 | let model_matrix = uv::Mat4::from_rotation_z(rotation); // No scaling or translation. 145 | let view_matrix = uv::Mat4::look_at( 146 | /* eye= */ uv::Vec3::new(1.0f32, 1.0f32, 1.0f32), 147 | /* origin= */ uv::Vec3::zero(), 148 | /* up= */ uv::Vec3::unit_z(), 149 | ); 150 | // TODO BACKENDS this proj should be conditional on backend. 151 | let perspective_matrix = 152 | uv::projection::rh_ydown::perspective_vk(std::f32::consts::PI / 2f32, ar, 0.1f32, 10f32); 153 | 154 | let uniform = 155 | DefaultForwardShaderLayout::new(perspective_matrix * view_matrix * model_matrix, true, false); 156 | rect.set_uniform(renderer, &uniform) 157 | } 158 | 159 | /// Handles all winit window specific events. 160 | fn main_loop_window_event( 161 | event: &WindowEvent, _id: &WindowId, control_flow: &mut winit::event_loop::ControlFlow, 162 | renderer: &mut VulkanRenderer, ar: &mut f32, 163 | ) -> SarektResult<()> { 164 | match event { 165 | WindowEvent::CloseRequested => { 166 | // When the window system requests a close, signal to winit that we'd like to 167 | // close the window. 168 | info!("Exiting due to close request event from window system..."); 169 | *control_flow = ControlFlow::Exit; 170 | } 171 | 172 | WindowEvent::KeyboardInput { input, .. } => { 173 | // When the keyboard input is a press on the escape key, exit and print the 174 | // line. 175 | if let (Some(VirtualKeyCode::Escape), ElementState::Pressed) = 176 | (input.virtual_keycode, input.state) 177 | { 178 | info!("Exiting due to escape press..."); 179 | *control_flow = ControlFlow::Exit 180 | } 181 | } 182 | 183 | WindowEvent::Resized(size) => { 184 | // If the size is 0, minimization or something like that happened so I 185 | // toggle drawing. 186 | info!("Window resized, recreating renderer swapchain..."); 187 | let enabled = !(size.height == 0 && size.width == 0); 188 | if enabled { 189 | *ar = size.width as f32 / size.height as f32; 190 | } 191 | renderer.set_rendering_enabled(enabled); 192 | return renderer.recreate_swapchain(size.width, size.height); 193 | } 194 | 195 | _ => (), 196 | } 197 | 198 | Ok(()) 199 | } 200 | -------------------------------------------------------------------------------- /examples/06_textures.rs: -------------------------------------------------------------------------------- 1 | use image::GenericImageView; 2 | use lazy_static::lazy_static; 3 | use log::{info, warn, Level}; 4 | use sarekt::{ 5 | self, 6 | error::{SarektError, SarektResult}, 7 | renderer::{ 8 | buffers_and_images::{ 9 | BufferType, IndexBufferElemSize, MagnificationMinificationFilter, TextureAddressMode, 10 | }, 11 | config::Config, 12 | drawable_object::DrawableObject, 13 | vertex_bindings::{DefaultForwardShaderLayout, DefaultForwardShaderVertex}, 14 | Drawer, Renderer, VulkanRenderer, 15 | }, 16 | }; 17 | use std::{error::Error, f32, sync::Arc, time::Instant}; 18 | use ultraviolet as uv; 19 | use winit::{ 20 | dpi::{LogicalSize, PhysicalSize}, 21 | event::{ElementState, Event, VirtualKeyCode, WindowEvent}, 22 | event_loop::{ControlFlow, EventLoop}, 23 | platform::desktop::EventLoopExtDesktop, 24 | window::{WindowBuilder, WindowId}, 25 | }; 26 | 27 | const WIDTH: u32 = 800; 28 | const HEIGHT: u32 = 600; 29 | 30 | lazy_static! { 31 | static ref RECT_VERTICES: Vec = vec![ 32 | // Texture is bound to the four corners. 33 | DefaultForwardShaderVertex::new(&[-0.5f32, 0.0f32, -0.5f32], &[1.0f32, 0.0f32, 0.0f32], &[1.0f32, 0.0f32]), // Top Left, Red, 34 | DefaultForwardShaderVertex::new(&[0.5f32, 0.0f32, -0.5f32], &[0.0f32, 1.0f32, 0.0f32], &[0.0f32, 0.0f32]), // Top Right, Green 35 | DefaultForwardShaderVertex::new(&[0.5f32, 0.0f32, 0.5f32], &[0.0f32, 0.0f32, 1.0f32], &[0.0f32, 1.0f32]), // Bottom Right, Blue 36 | DefaultForwardShaderVertex::new(&[-0.5f32, 0.0f32, 0.5f32], &[1.0f32, 1.0f32, 1.0f32], &[1.0f32, 1.0f32]), // Bottom Left, White 37 | ]; 38 | } 39 | const RECT_INDICES: [u16; 6] = [0u16, 2u16, 1u16, 2u16, 0u16, 3u16]; // two triangles, upper right and lower left 40 | 41 | fn main() -> Result<(), Box> { 42 | simple_logger::init_with_level(Level::Info)?; 43 | main_loop()?; 44 | Ok(()) 45 | } 46 | 47 | /// Takes full control of the executing thread and runs the event loop for it. 48 | fn main_loop() -> Result<(), Box> { 49 | info!("Running main loop..."); 50 | 51 | let mut ar = WIDTH as f32 / HEIGHT as f32; 52 | 53 | // Build Window. 54 | let mut event_loop = EventLoop::new(); 55 | let window = Arc::new( 56 | WindowBuilder::new() 57 | .with_inner_size(LogicalSize::new(WIDTH, HEIGHT)) 58 | .build(&event_loop) 59 | .unwrap(), 60 | ); 61 | 62 | // Build Renderer. 63 | let config = Config::builder() 64 | .requested_width(WIDTH) 65 | .requested_height(HEIGHT) 66 | .build() 67 | .unwrap(); 68 | let mut renderer = VulkanRenderer::new(window.clone(), config).unwrap(); 69 | 70 | // Create Vertex Resources. 71 | let rect_vertex_buffer = renderer.load_buffer(BufferType::Vertex, &RECT_VERTICES)?; 72 | let rect_index_buffer = renderer.load_buffer( 73 | BufferType::Index(IndexBufferElemSize::UInt16), 74 | &RECT_INDICES, 75 | )?; 76 | 77 | // Create MVP uniform. 78 | let rect_uniform = DefaultForwardShaderLayout::default(); 79 | let rect_uniform_buffer = renderer.load_uniform_buffer(rect_uniform)?; 80 | 81 | // Load textures and create image. 82 | let spoderman = image::open("textures/spoderman.gif")?; 83 | info!("spoderman dimensions: {:?}", spoderman.dimensions()); 84 | let image = renderer.load_image_with_staging_initialization( 85 | spoderman, 86 | MagnificationMinificationFilter::Linear, 87 | MagnificationMinificationFilter::Linear, 88 | TextureAddressMode::Repeat, 89 | TextureAddressMode::Repeat, 90 | TextureAddressMode::Repeat, 91 | /* mip_levels */ 1, 92 | )?; 93 | 94 | let args: Vec = std::env::args().collect(); 95 | let enable_colors = args.contains(&"colors".to_owned()); 96 | info!("Colors enabled: {}", enable_colors); 97 | 98 | let rect = DrawableObject::builder(&renderer) 99 | .vertex_buffer(&rect_vertex_buffer) 100 | .index_buffer(&rect_index_buffer) 101 | .uniform_buffer(&rect_uniform_buffer) 102 | .texture_image(&image) 103 | .build()?; 104 | 105 | let start_time = Instant::now(); 106 | 107 | let mut camera_height = 0.25f32; 108 | 109 | // Run the loop. 110 | event_loop.run_return(move |event, _, control_flow| { 111 | // By default continuously run this event loop, even if the OS hasn't 112 | // distributed an event, that way we will draw as fast as possible. 113 | *control_flow = ControlFlow::Poll; 114 | 115 | match event { 116 | Event::MainEventsCleared => { 117 | // All the main events to process are done we can do "work" now (game 118 | // engine state update etc.) 119 | let now = Instant::now(); 120 | let time_since_start_secs = ((now - start_time).as_millis() as f32) / 1000f32; 121 | 122 | // Rise to max height then gently go back down. 123 | let camera_rate = 0.5f32; 124 | let max_camera_height = 1.5f32; 125 | camera_height = (camera_rate * time_since_start_secs) % (2.0f32 * max_camera_height); 126 | if camera_height >= max_camera_height { 127 | camera_height = (2.0f32 * max_camera_height) - camera_height; 128 | } 129 | 130 | update_uniforms( 131 | &renderer, 132 | &rect, 133 | camera_height, 134 | enable_colors, 135 | time_since_start_secs, 136 | ar, 137 | ) 138 | .unwrap(); 139 | renderer.draw(&rect).expect("Unable to draw triangle!"); 140 | 141 | // At the end of work request redraw. 142 | window.request_redraw(); 143 | } 144 | 145 | Event::RedrawRequested(_) => { 146 | // Redraw requested, this is called after MainEventsCleared. 147 | renderer.frame().unwrap_or_else(|err| { 148 | match err { 149 | SarektError::SwapchainOutOfDate | SarektError::SuboptimalSwapchain => { 150 | // Handle window resize etc. 151 | warn!("Tried to render without processing window resize event!"); 152 | 153 | let PhysicalSize { width, height } = window.inner_size(); 154 | renderer 155 | .recreate_swapchain(width, height) 156 | .expect("Error recreating swapchain"); 157 | } 158 | e => panic!("Frame had an unrecoverable error! {}", e), 159 | } 160 | }); 161 | } 162 | 163 | Event::WindowEvent { window_id, event } => { 164 | main_loop_window_event(&event, &window_id, control_flow, &mut renderer, &mut ar) 165 | .expect("Error processing window event."); 166 | } 167 | 168 | Event::LoopDestroyed => { 169 | // Explicitly call exit so resources are cleaned up. 170 | std::process::exit(0); 171 | } 172 | _ => (), 173 | } 174 | }); 175 | 176 | Ok(()) 177 | } 178 | 179 | fn update_uniforms( 180 | renderer: &VulkanRenderer, rect: &DrawableObject, 181 | camera_height: f32, enable_colors: bool, time_since_start_secs: f32, ar: f32, 182 | ) -> SarektResult<()> { 183 | let spoderman_position = uv::Vec3::new(0.0f32, 0.0f32, 1.0f32); 184 | // Pi radians per second around the z axis. 185 | let spoderman_rotation = 186 | (std::f32::consts::PI * time_since_start_secs) % (2f32 * std::f32::consts::PI); 187 | 188 | let model_matrix = 189 | uv::Mat4::from_translation(spoderman_position) * uv::Mat4::from_rotation_y(spoderman_rotation); 190 | let view_matrix = uv::Mat4::look_at( 191 | /* eye= */ uv::Vec3::new(0.0f32, camera_height, 0.0f32), 192 | /* at= */ spoderman_position, 193 | /* up= */ uv::Vec3::unit_y(), 194 | ); 195 | // TODO BACKENDS this proj should be conditional on backend. 196 | let perspective_matrix = 197 | uv::projection::rh_yup::perspective_vk(std::f32::consts::PI / 2f32, ar, 0.1f32, 10f32); 198 | 199 | let uniform = DefaultForwardShaderLayout::new( 200 | perspective_matrix * view_matrix * model_matrix, 201 | enable_colors, 202 | /* enable_texture_mixing= */ true, 203 | ); 204 | rect.set_uniform(renderer, &uniform) 205 | } 206 | 207 | /// Handles all winit window specific events. 208 | fn main_loop_window_event( 209 | event: &WindowEvent, _id: &WindowId, control_flow: &mut winit::event_loop::ControlFlow, 210 | renderer: &mut VulkanRenderer, ar: &mut f32, 211 | ) -> SarektResult<()> { 212 | match event { 213 | WindowEvent::CloseRequested => { 214 | // When the window system requests a close, signal to winit that we'd like to 215 | // close the window. 216 | info!("Exiting due to close request event from window system..."); 217 | *control_flow = ControlFlow::Exit; 218 | } 219 | 220 | WindowEvent::KeyboardInput { input, .. } => { 221 | // When the keyboard input is a press on the escape key, exit and print the 222 | // line. 223 | if let (Some(VirtualKeyCode::Escape), ElementState::Pressed) = 224 | (input.virtual_keycode, input.state) 225 | { 226 | info!("Exiting due to escape press..."); 227 | *control_flow = ControlFlow::Exit 228 | } 229 | } 230 | 231 | WindowEvent::Resized(size) => { 232 | // If the size is 0, minimization or something like that happened so I 233 | // toggle drawing. 234 | info!("Window resized, recreating renderer swapchain..."); 235 | let enabled = !(size.height == 0 && size.width == 0); 236 | if enabled { 237 | *ar = size.width as f32 / size.height as f32; 238 | } 239 | renderer.set_rendering_enabled(enabled); 240 | return renderer.recreate_swapchain(size.width, size.height); 241 | } 242 | 243 | _ => (), 244 | } 245 | 246 | Ok(()) 247 | } 248 | -------------------------------------------------------------------------------- /examples/07_depth_buffer_test.rs: -------------------------------------------------------------------------------- 1 | use image::GenericImageView; 2 | use lazy_static::lazy_static; 3 | use log::{info, warn, Level}; 4 | use sarekt::{ 5 | self, 6 | error::{SarektError, SarektResult}, 7 | renderer::{ 8 | buffers_and_images::{ 9 | BufferType, IndexBufferElemSize, MagnificationMinificationFilter, TextureAddressMode, 10 | }, 11 | config::Config, 12 | drawable_object::DrawableObject, 13 | vertex_bindings::{DefaultForwardShaderLayout, DefaultForwardShaderVertex}, 14 | Drawer, Renderer, VulkanRenderer, 15 | }, 16 | }; 17 | use std::{error::Error, f32, sync::Arc, time::Instant}; 18 | use ultraviolet as uv; 19 | use winit::{ 20 | dpi::{LogicalSize, PhysicalSize}, 21 | event::{ElementState, Event, VirtualKeyCode, WindowEvent}, 22 | event_loop::{ControlFlow, EventLoop}, 23 | platform::desktop::EventLoopExtDesktop, 24 | window::{WindowBuilder, WindowId}, 25 | }; 26 | 27 | const WIDTH: u32 = 800; 28 | const HEIGHT: u32 = 600; 29 | 30 | lazy_static! { 31 | static ref RECT_VERTICES: Vec = vec![ 32 | // Texture is bound to the four corners. 33 | DefaultForwardShaderVertex::new(&[-0.5f32, 0.0f32, -0.5f32], &[1.0f32, 0.0f32, 0.0f32], &[1.0f32, 0.0f32]), // Top Left, Red, 34 | DefaultForwardShaderVertex::new(&[0.5f32, 0.0f32, -0.5f32], &[0.0f32, 1.0f32, 0.0f32], &[0.0f32, 0.0f32]), // Top Right, Green 35 | DefaultForwardShaderVertex::new(&[0.5f32, 0.0f32, 0.5f32], &[0.0f32, 0.0f32, 1.0f32], &[0.0f32, 1.0f32]), // Bottom Right, Blue 36 | DefaultForwardShaderVertex::new(&[-0.5f32, 0.0f32, 0.5f32], &[1.0f32, 1.0f32, 1.0f32], &[1.0f32, 1.0f32]), // Bottom Left, White 37 | ]; 38 | } 39 | const RECT_INDICES: [u16; 6] = [0u16, 2u16, 1u16, 2u16, 0u16, 3u16]; // two triangles, upper right and lower left 40 | 41 | fn main() -> Result<(), Box> { 42 | simple_logger::init_with_level(Level::Info)?; 43 | main_loop()?; 44 | Ok(()) 45 | } 46 | 47 | /// Takes full control of the executing thread and runs the event loop for it. 48 | fn main_loop() -> Result<(), Box> { 49 | info!("Running main loop..."); 50 | 51 | let mut ar = WIDTH as f32 / HEIGHT as f32; 52 | 53 | // Build Window. 54 | let mut event_loop = EventLoop::new(); 55 | let window = Arc::new( 56 | WindowBuilder::new() 57 | .with_inner_size(LogicalSize::new(WIDTH, HEIGHT)) 58 | .build(&event_loop) 59 | .unwrap(), 60 | ); 61 | 62 | // Build Renderer. 63 | let config = Config::builder() 64 | .requested_width(WIDTH) 65 | .requested_height(HEIGHT) 66 | .build() 67 | .unwrap(); 68 | let mut renderer = VulkanRenderer::new(window.clone(), config).unwrap(); 69 | 70 | // Create Vertex Resources. 71 | let rect_vertex_buffer = renderer.load_buffer(BufferType::Vertex, &RECT_VERTICES)?; 72 | let rect_index_buffer = renderer.load_buffer( 73 | BufferType::Index(IndexBufferElemSize::UInt16), 74 | &RECT_INDICES, 75 | )?; 76 | 77 | // Create MVP uniform. 78 | let rect_uniform = DefaultForwardShaderLayout::default(); 79 | let rect_uniform_buffer = renderer.load_uniform_buffer(rect_uniform)?; 80 | 81 | // Load textures and create image. 82 | let spoderman = image::open("textures/spoderman.gif")?; 83 | info!("spoderman dimensions: {:?}", spoderman.dimensions()); 84 | let image = renderer.load_image_with_staging_initialization( 85 | spoderman, 86 | MagnificationMinificationFilter::Linear, 87 | MagnificationMinificationFilter::Linear, 88 | TextureAddressMode::Repeat, 89 | TextureAddressMode::Repeat, 90 | TextureAddressMode::Repeat, 91 | /* mip_levels */ 1, 92 | )?; 93 | 94 | let rect = DrawableObject::builder(&renderer) 95 | .vertex_buffer(&rect_vertex_buffer) 96 | .index_buffer(&rect_index_buffer) 97 | .uniform_buffer(&rect_uniform_buffer) 98 | .texture_image(&image) 99 | .build()?; 100 | 101 | let rect2_uniform_buffer = renderer.load_uniform_buffer(rect_uniform)?; 102 | let rect2 = DrawableObject::builder(&renderer) 103 | .vertex_buffer(&rect_vertex_buffer) 104 | .index_buffer(&rect_index_buffer) 105 | .uniform_buffer(&rect2_uniform_buffer) 106 | .texture_image(&image) 107 | .build()?; 108 | 109 | let rect3_uniform_buffer = renderer.load_uniform_buffer(rect_uniform)?; 110 | let rect3 = DrawableObject::builder(&renderer) 111 | .vertex_buffer(&rect_vertex_buffer) 112 | .index_buffer(&rect_index_buffer) 113 | .uniform_buffer(&rect3_uniform_buffer) 114 | .texture_image(&image) 115 | .build()?; 116 | 117 | let args: Vec = std::env::args().collect(); 118 | let enable_colors = args.contains(&"colors".to_owned()); 119 | let show_fps = args.contains(&"fps".to_owned()); 120 | info!("Colors enabled: {}", enable_colors); 121 | info!("Show FPS: {}", show_fps); 122 | 123 | let start_time = Instant::now(); 124 | let mut last_frame_time = start_time; 125 | let mut frame_number = 0; 126 | let mut fps_average = 0f32; 127 | 128 | let mut camera_height = 0.5f32; 129 | 130 | // Run the loop. 131 | event_loop.run_return(move |event, _, control_flow| { 132 | // By default continuously run this event loop, even if the OS hasn't 133 | // distributed an event, that way we will draw as fast as possible. 134 | *control_flow = ControlFlow::Poll; 135 | 136 | match event { 137 | Event::MainEventsCleared => { 138 | // All the main events to process are done we can do "work" now (game 139 | // engine state update etc.) 140 | let now = Instant::now(); 141 | let time_since_start_secs = ((now - start_time).as_millis() as f32) / 1000f32; 142 | 143 | if show_fps { 144 | let time_since_last_frame_secs = ((now - last_frame_time).as_nanos() as f32) / 1e9f32; 145 | let fps = 1f32 / time_since_last_frame_secs; 146 | if frame_number == 0 { 147 | fps_average = 0f32; 148 | } else { 149 | fps_average = 150 | ((frame_number as f32 * fps_average) + fps) / (frame_number as f32 + 1f32); 151 | } 152 | frame_number += 1; 153 | 154 | info!("Frame Period: {}", time_since_last_frame_secs); 155 | info!("FPS: {}", fps); 156 | info!("FPS averaged: {}", fps_average); 157 | last_frame_time = now; 158 | } 159 | 160 | // Rise to max height then gently go back down. 161 | let camera_rate = 0.5f32; 162 | let min_camera_height = 0.5f32; 163 | let camera_range = 1.5f32; 164 | camera_height = 165 | (camera_rate * time_since_start_secs) % (2.0f32 * camera_range) + min_camera_height; 166 | if camera_height >= (camera_range + min_camera_height) { 167 | camera_height = (2.0f32 * (camera_range + min_camera_height)) - camera_height; 168 | } 169 | 170 | let rotation = 171 | (std::f32::consts::PI * time_since_start_secs) % (2f32 * std::f32::consts::PI); 172 | update_uniforms( 173 | &renderer, 174 | &rect, 175 | uv::Vec3::new(0.0f32, 0.0f32, 1.0f32), 176 | rotation, 177 | camera_height, 178 | enable_colors, 179 | ar, 180 | ) 181 | .unwrap(); 182 | update_uniforms( 183 | &renderer, 184 | &rect2, 185 | uv::Vec3::new(0.5f32, 0.5f32, 1.0f32), 186 | -rotation, 187 | camera_height, 188 | enable_colors, 189 | ar, 190 | ) 191 | .unwrap(); 192 | update_uniforms( 193 | &renderer, 194 | &rect3, 195 | uv::Vec3::new(-0.5f32, 0.25f32, 1.0f32), 196 | -rotation, 197 | camera_height, 198 | enable_colors, 199 | ar, 200 | ) 201 | .unwrap(); 202 | 203 | for _ in 0..100 { 204 | renderer.draw(&rect3).unwrap(); 205 | } 206 | 207 | renderer.draw(&rect2).unwrap(); 208 | renderer.draw(&rect).unwrap(); 209 | 210 | // At the end of work request redraw. 211 | window.request_redraw(); 212 | } 213 | 214 | Event::RedrawRequested(_) => { 215 | // Redraw requested, this is called after MainEventsCleared. 216 | renderer.frame().unwrap_or_else(|err| { 217 | match err { 218 | SarektError::SwapchainOutOfDate | SarektError::SuboptimalSwapchain => { 219 | // Handle window resize etc. 220 | warn!("Tried to render without processing window resize event!"); 221 | 222 | let PhysicalSize { width, height } = window.inner_size(); 223 | renderer 224 | .recreate_swapchain(width, height) 225 | .expect("Error recreating swapchain"); 226 | } 227 | e => panic!("Frame had an unrecoverable error! {}", e), 228 | } 229 | }); 230 | } 231 | 232 | Event::WindowEvent { window_id, event } => { 233 | main_loop_window_event(&event, &window_id, control_flow, &mut renderer, &mut ar) 234 | .expect("Error processing window event."); 235 | } 236 | 237 | Event::LoopDestroyed => { 238 | // Explicitly call exit so resources are cleaned up. 239 | std::process::exit(0); 240 | } 241 | _ => (), 242 | } 243 | }); 244 | 245 | Ok(()) 246 | } 247 | 248 | fn update_uniforms( 249 | renderer: &VulkanRenderer, rect: &DrawableObject, 250 | position: uv::Vec3, rotation: f32, camera_height: f32, enable_colors: bool, ar: f32, 251 | ) -> SarektResult<()> { 252 | // Pi radians per second around the z axis. 253 | let model_matrix = uv::Mat4::from_translation(position) * uv::Mat4::from_rotation_y(rotation); 254 | let view_matrix = uv::Mat4::look_at( 255 | /* eye= */ uv::Vec3::new(0.0f32, camera_height, 0.0f32), 256 | /* at= */ uv::Vec3::new(0f32, 0f32, 1f32), 257 | /* up= */ uv::Vec3::unit_y(), 258 | ); 259 | // TODO BACKENDS this proj should be conditional on backend. 260 | let perspective_matrix = 261 | uv::projection::rh_yup::perspective_vk(std::f32::consts::PI / 2f32, ar, 0.1f32, 10f32); 262 | 263 | let uniform = DefaultForwardShaderLayout::new( 264 | perspective_matrix * view_matrix * model_matrix, 265 | enable_colors, 266 | /* enable_texture_mixing= */ true, 267 | ); 268 | rect.set_uniform(renderer, &uniform) 269 | } 270 | 271 | /// Handles all winit window specific events. 272 | fn main_loop_window_event( 273 | event: &WindowEvent, _id: &WindowId, control_flow: &mut winit::event_loop::ControlFlow, 274 | renderer: &mut VulkanRenderer, ar: &mut f32, 275 | ) -> SarektResult<()> { 276 | match event { 277 | WindowEvent::CloseRequested => { 278 | // When the window system requests a close, signal to winit that we'd like to 279 | // close the window. 280 | info!("Exiting due to close request event from window system..."); 281 | *control_flow = ControlFlow::Exit; 282 | } 283 | 284 | WindowEvent::KeyboardInput { input, .. } => { 285 | // When the keyboard input is a press on the escape key, exit and print the 286 | // line. 287 | if let (Some(VirtualKeyCode::Escape), ElementState::Pressed) = 288 | (input.virtual_keycode, input.state) 289 | { 290 | info!("Exiting due to escape press..."); 291 | *control_flow = ControlFlow::Exit 292 | } 293 | } 294 | 295 | WindowEvent::Resized(size) => { 296 | // If the size is 0, minimization or something like that happened so I 297 | // toggle drawing. 298 | info!("Window resized, recreating renderer swapchain..."); 299 | let enabled = !(size.height == 0 && size.width == 0); 300 | if enabled { 301 | *ar = size.width as f32 / size.height as f32; 302 | } 303 | renderer.set_rendering_enabled(enabled); 304 | return renderer.recreate_swapchain(size.width, size.height); 305 | } 306 | 307 | _ => (), 308 | } 309 | 310 | Ok(()) 311 | } 312 | -------------------------------------------------------------------------------- /examples/08_model_loading.rs: -------------------------------------------------------------------------------- 1 | use itertools::izip; 2 | use log::{info, warn, Level}; 3 | use sarekt::{ 4 | self, 5 | error::{SarektError, SarektResult}, 6 | renderer::{ 7 | buffers_and_images::{ 8 | BufferType, IndexBufferElemSize, MagnificationMinificationFilter, TextureAddressMode, 9 | }, 10 | config::Config, 11 | drawable_object::DrawableObject, 12 | vertex_bindings::{DefaultForwardShaderLayout, DefaultForwardShaderVertex}, 13 | Drawer, Renderer, VulkanRenderer, 14 | }, 15 | }; 16 | use std::{collections::HashMap, f32, fs::File, io::Read, sync::Arc, time::Instant}; 17 | use ultraviolet as uv; 18 | use wavefront_obj as obj; 19 | use winit::{ 20 | dpi::{LogicalSize, PhysicalSize}, 21 | event::{ElementState, Event, VirtualKeyCode, WindowEvent}, 22 | event_loop::{ControlFlow, EventLoop}, 23 | platform::desktop::EventLoopExtDesktop, 24 | window::{WindowBuilder, WindowId}, 25 | }; 26 | 27 | const WIDTH: u32 = 800; 28 | const HEIGHT: u32 = 600; 29 | 30 | const GLB_MODEL_FILE_NAME: &str = "models/chalet.glb"; 31 | const OBJ_MODEL_FILE_NAME: &str = "models/chalet.obj"; 32 | const MODEL_TEXTURE_FILE_NAME: &str = "textures/chalet.jpg"; 33 | 34 | fn main() { 35 | simple_logger::init_with_level(Level::Info).unwrap(); 36 | main_loop(); 37 | } 38 | 39 | /// Takes full control of the executing thread and runs the event loop for it. 40 | fn main_loop() { 41 | let args: Vec = std::env::args().collect(); 42 | let show_fps = args.contains(&"fps".to_owned()); 43 | let use_glb = args.contains(&"glb".to_owned()); 44 | if args.len() > 1 && !show_fps && !use_glb { 45 | panic!("Illegal arguments provided: {:#?}", args); 46 | } 47 | info!("Show FPS: {}", show_fps); 48 | info!("Use GLTF Model Type: {}", use_glb); 49 | 50 | info!("Running main loop..."); 51 | 52 | let mut ar = WIDTH as f32 / HEIGHT as f32; 53 | 54 | // Build Window. 55 | let mut event_loop = EventLoop::new(); 56 | let window = Arc::new( 57 | WindowBuilder::new() 58 | .with_inner_size(LogicalSize::new(WIDTH, HEIGHT)) 59 | .build(&event_loop) 60 | .unwrap(), 61 | ); 62 | 63 | // Build Renderer. 64 | let config = Config::builder() 65 | .requested_width(WIDTH) 66 | .requested_height(HEIGHT) 67 | .build() 68 | .unwrap(); 69 | let mut renderer = VulkanRenderer::new(window.clone(), config).unwrap(); 70 | 71 | // Create Vertex Resources. 72 | let (model_vertices, model_indices) = if use_glb { 73 | load_glb_model(GLB_MODEL_FILE_NAME) 74 | } else { 75 | load_obj_models(OBJ_MODEL_FILE_NAME) 76 | }; 77 | info!("Model file loaded"); 78 | let model_index_buffer = model_indices.map(|mi| { 79 | renderer 80 | .load_buffer(BufferType::Index(IndexBufferElemSize::UInt32), &mi) 81 | .unwrap() 82 | }); 83 | let model_buffer = renderer 84 | .load_buffer(BufferType::Vertex, &model_vertices) 85 | .unwrap(); 86 | 87 | // Create MVP uniform. 88 | let uniform_handle = renderer 89 | .load_uniform_buffer(DefaultForwardShaderLayout::default()) 90 | .unwrap(); 91 | 92 | // Load textures and create image. 93 | let model_texture_file = image::open(MODEL_TEXTURE_FILE_NAME).unwrap(); 94 | let model_texture = renderer 95 | .load_image_with_staging_initialization( 96 | model_texture_file, 97 | MagnificationMinificationFilter::Linear, 98 | MagnificationMinificationFilter::Linear, 99 | TextureAddressMode::ClampToEdge, 100 | TextureAddressMode::ClampToEdge, 101 | TextureAddressMode::ClampToEdge, 102 | /* mip_levels */ 1, 103 | ) 104 | .unwrap(); 105 | 106 | let mut drawable_object_builder = DrawableObject::builder(&renderer) 107 | .uniform_buffer(&uniform_handle) 108 | .vertex_buffer(&model_buffer) 109 | .texture_image(&model_texture); 110 | if model_index_buffer.is_some() { 111 | drawable_object_builder = 112 | drawable_object_builder.index_buffer(model_index_buffer.as_ref().unwrap()); 113 | } 114 | let drawable_object = drawable_object_builder.build().unwrap(); 115 | 116 | let start_time = Instant::now(); 117 | let mut last_frame_time = start_time; 118 | let mut frame_number = 0; 119 | let mut fps_average = 0f32; 120 | 121 | let mut camera_height = -0.5f32; 122 | 123 | // Run the loop. 124 | event_loop.run_return(move |event, _, control_flow| { 125 | // By default continuously run this event loop, even if the OS hasn't 126 | // distributed an event, that way we will draw as fast as possible. 127 | *control_flow = ControlFlow::Poll; 128 | 129 | match event { 130 | Event::MainEventsCleared => { 131 | // All the main events to process are done we can do "work" now (game 132 | // engine state update etc.) 133 | let now = Instant::now(); 134 | let time_since_start_secs = ((now - start_time).as_millis() as f32) / 1000f32; 135 | 136 | if show_fps { 137 | let time_since_last_frame_secs = ((now - last_frame_time).as_nanos() as f32) / 1e9f32; 138 | let fps = 1f32 / time_since_last_frame_secs; 139 | if frame_number == 0 { 140 | fps_average = 0f32; 141 | } else { 142 | fps_average = 143 | ((frame_number as f32 * fps_average) + fps) / (frame_number as f32 + 1f32); 144 | } 145 | frame_number += 1; 146 | 147 | info!("Frame Period: {}", time_since_last_frame_secs); 148 | info!("FPS: {}", fps); 149 | info!("FPS averaged: {}", fps_average); 150 | last_frame_time = now; 151 | } 152 | 153 | // Rise to max height then gently go back down. 154 | let camera_rate = 0.25f32; 155 | let min_camera_height = -0.5f32; 156 | let camera_range = 2f32; 157 | camera_height = 158 | (camera_rate * time_since_start_secs) % (2.0f32 * camera_range) + min_camera_height; 159 | if camera_height >= (camera_range + min_camera_height) { 160 | camera_height = (2.0f32 * (camera_range + min_camera_height)) - camera_height; 161 | } 162 | 163 | let rotation = (std::f32::consts::PI + std::f32::consts::PI * time_since_start_secs / 8f32) 164 | % (2f32 * std::f32::consts::PI); 165 | update_uniforms( 166 | &renderer, 167 | &drawable_object, 168 | uv::Vec3::new(0f32, -1f32, -1.5f32), 169 | rotation, 170 | camera_height, 171 | false, 172 | ar, 173 | ) 174 | .unwrap(); 175 | 176 | renderer.draw(&drawable_object).unwrap(); 177 | 178 | // At the end of work request redraw. 179 | window.request_redraw(); 180 | } 181 | 182 | Event::RedrawRequested(_) => { 183 | // Redraw requested, this is called after MainEventsCleared. 184 | renderer.frame().unwrap_or_else(|err| { 185 | match err { 186 | SarektError::SwapchainOutOfDate | SarektError::SuboptimalSwapchain => { 187 | // Handle window resize etc. 188 | warn!("Tried to render without processing window resize event!"); 189 | 190 | let PhysicalSize { width, height } = window.inner_size(); 191 | renderer 192 | .recreate_swapchain(width, height) 193 | .expect("Error recreating swapchain"); 194 | } 195 | e => panic!("Frame had an unrecoverable error! {}", e), 196 | } 197 | }); 198 | } 199 | 200 | Event::WindowEvent { window_id, event } => { 201 | main_loop_window_event(&event, &window_id, control_flow, &mut renderer, &mut ar) 202 | .expect("Error processing window event."); 203 | } 204 | 205 | Event::LoopDestroyed => { 206 | // Explicitly call exit so resources are cleaned up. 207 | std::process::exit(0); 208 | } 209 | _ => (), 210 | } 211 | }); 212 | } 213 | 214 | /// Handles all winit window specific events. 215 | fn main_loop_window_event( 216 | event: &WindowEvent, _id: &WindowId, control_flow: &mut winit::event_loop::ControlFlow, 217 | renderer: &mut VulkanRenderer, ar: &mut f32, 218 | ) -> SarektResult<()> { 219 | match event { 220 | WindowEvent::CloseRequested => { 221 | // When the window system requests a close, signal to winit that we'd like to 222 | // close the window. 223 | info!("Exiting due to close request event from window system..."); 224 | *control_flow = ControlFlow::Exit; 225 | } 226 | 227 | WindowEvent::KeyboardInput { input, .. } => { 228 | // When the keyboard input is a press on the escape key, exit and print the 229 | // line. 230 | if let (Some(VirtualKeyCode::Escape), ElementState::Pressed) = 231 | (input.virtual_keycode, input.state) 232 | { 233 | info!("Exiting due to escape press..."); 234 | *control_flow = ControlFlow::Exit 235 | } 236 | } 237 | 238 | WindowEvent::Resized(size) => { 239 | // If the size is 0, minimization or something like that happened so I 240 | // toggle drawing. 241 | info!("Window resized, recreating renderer swapchain..."); 242 | let enabled = !(size.height == 0 && size.width == 0); 243 | if enabled { 244 | *ar = size.width as f32 / size.height as f32; 245 | } 246 | renderer.set_rendering_enabled(enabled); 247 | return renderer.recreate_swapchain(size.width, size.height); 248 | } 249 | _ => (), 250 | } 251 | 252 | Ok(()) 253 | } 254 | 255 | fn update_uniforms( 256 | renderer: &VulkanRenderer, object: &DrawableObject, 257 | position: uv::Vec3, rotation: f32, camera_height: f32, enable_colors: bool, ar: f32, 258 | ) -> SarektResult<()> { 259 | // Pi radians per second around the y axis. 260 | let total_rotation = 261 | uv::Mat4::from_rotation_y(rotation) * uv::Mat4::from_rotation_x(-std::f32::consts::PI / 2f32); 262 | let model_matrix = uv::Mat4::from_translation(position) * total_rotation; 263 | 264 | let view_matrix = uv::Mat4::look_at( 265 | /* eye= */ uv::Vec3::new(0.0f32, camera_height, 0.0f32), 266 | /* at= */ position, 267 | /* up= */ uv::Vec3::unit_y(), 268 | ); 269 | // TODO BACKENDS this proj should be conditional on backend. 270 | let perspective_matrix = 271 | uv::projection::rh_yup::perspective_vk(std::f32::consts::PI / 2f32, ar, 0.1f32, 10f32); 272 | 273 | let uniform = DefaultForwardShaderLayout::new( 274 | perspective_matrix * view_matrix * model_matrix, 275 | enable_colors, 276 | /* enable_texture_mixing= */ true, 277 | ); 278 | object.set_uniform(renderer, &uniform) 279 | } 280 | 281 | /// For now only use the first object in the obj file. 282 | /// Returns (vertices, vertex_indicies, texture_coordinate indices) 283 | fn load_obj_models(obj_file_path: &str) -> (Vec, Option>) { 284 | let mut model_file = File::open(obj_file_path).unwrap(); 285 | let mut model_file_text = String::new(); 286 | model_file.read_to_string(&mut model_file_text).unwrap(); 287 | 288 | let obj_set = obj::obj::parse(&model_file_text).unwrap(); 289 | if obj_set.objects.len() != 1 { 290 | panic!( 291 | "The model you attempted to load has more than one object in it, implying it is a scene, if \ 292 | you wish to use it as a single model, modify the application code to ignore that or join \ 293 | your meshes into a single model" 294 | ); 295 | } 296 | 297 | info!("Loaded model {}", OBJ_MODEL_FILE_NAME); 298 | let mut vertices: Vec = Vec::new(); 299 | let mut indices: Vec = Vec::new(); 300 | 301 | // Map of inserted (obj_vertex_index, obj_texture_index) to index in the 302 | // vertices array im building. 303 | let mut inserted_indices: HashMap<(usize, usize), usize> = HashMap::new(); 304 | let model_vertices = &obj_set.objects[0].vertices; 305 | for geo in obj_set.objects[0].geometry.iter() { 306 | // For every set of geometry (regardless of material for now). 307 | for shape in geo.shapes.iter() { 308 | // For every face/shape in the set of geometry. 309 | match shape.primitive { 310 | obj::obj::Primitive::Triangle(x, y, z) => { 311 | for &vert in [x, y, z].iter() { 312 | // We're only building a buffer of indices and vertices which contain position 313 | // and tex coord. 314 | let index_key = (vert.0, vert.1.unwrap()); 315 | if let Some(&vtx_index) = inserted_indices.get(&index_key) { 316 | // Already loaded this (vertex index, texture index) combo, just add it to the 317 | // index buffer. 318 | indices.push(vtx_index as _); 319 | continue; 320 | } 321 | 322 | // This is a new unique vertex (where a vertex is both a position and it's 323 | // texture coordinate) so add it to the vertex buffer and the index buffer. 324 | let current_vertex = model_vertices[vert.0]; 325 | let vertex_as_float = [ 326 | current_vertex.x as f32, 327 | current_vertex.y as f32, 328 | current_vertex.z as f32, 329 | ]; 330 | let texture_vertices = &obj_set.objects[0].tex_vertices; 331 | let tex_vertex = texture_vertices[vert.1.unwrap()]; 332 | // TODO BACKENDS only flip on coordinate systems that should. 333 | let texture_vertex_as_float = [tex_vertex.u as f32, 1f32 - tex_vertex.v as f32]; 334 | 335 | // Ignoring normals, there is no shading in this example. 336 | 337 | // Keep track of which keys were inserted and add this vertex to the index 338 | // buffer. 339 | inserted_indices.insert(index_key, vertices.len()); 340 | indices.push(vertices.len() as _); 341 | 342 | // Add to the vertex buffer. 343 | vertices.push(DefaultForwardShaderVertex::new_with_texture( 344 | &vertex_as_float, 345 | &texture_vertex_as_float, 346 | )); 347 | } 348 | } 349 | _ => warn!("Unsupported primitive!"), 350 | } 351 | } 352 | } 353 | 354 | info!( 355 | "Vertices/indices in model: {}, {}", 356 | vertices.len(), 357 | indices.len() 358 | ); 359 | (vertices, Some(indices)) 360 | } 361 | 362 | /// Returns (vertices, vertex_indicies, texture_coordinate indices) 363 | fn load_glb_model(gltf_file_path: &str) -> (Vec, Option>) { 364 | let (document, buffers, _) = gltf::import(gltf_file_path).unwrap(); 365 | 366 | if document.scenes().len() != 1 || document.scenes().next().unwrap().nodes().len() != 1 { 367 | panic!( 368 | "The model you attempted to load has more than one scene or node in it, if you wish to use \ 369 | it as a single model, modify the application code to ignore that or join your meshes into \ 370 | a single model" 371 | ); 372 | } 373 | 374 | let mesh = document.meshes().nth(0).unwrap(); 375 | 376 | info!("Loaded model {}", gltf_file_path); 377 | let mut vertices: Vec = Vec::new(); 378 | let mut indices: Option> = None; 379 | 380 | for primitive in mesh.primitives() { 381 | let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()])); 382 | let positions = reader.read_positions().unwrap(); 383 | let tex_coords = reader.read_tex_coords(0).unwrap().into_f32(); 384 | for (position, tex_coord) in izip!(positions, tex_coords) { 385 | vertices.push(DefaultForwardShaderVertex::new_with_texture( 386 | &position, &tex_coord, 387 | )); 388 | } 389 | 390 | reader 391 | .read_indices() 392 | .map(|it| indices.get_or_insert(Vec::new()).extend(&mut it.into_u32())); 393 | } 394 | 395 | info!( 396 | "Vertices/indices in model: {}, {:?}", 397 | vertices.len(), 398 | indices.as_ref().map(|i| i.len()) 399 | ); 400 | (vertices, indices) 401 | } 402 | -------------------------------------------------------------------------------- /examples/09_mip_levels.rs: -------------------------------------------------------------------------------- 1 | use itertools::izip; 2 | use log::{info, warn, Level}; 3 | use sarekt::{ 4 | self, 5 | error::{SarektError, SarektResult}, 6 | image_data::ImageData, 7 | renderer::{ 8 | buffers_and_images::{ 9 | BufferType, IndexBufferElemSize, MagnificationMinificationFilter, TextureAddressMode, 10 | }, 11 | config::Config, 12 | drawable_object::DrawableObject, 13 | vertex_bindings::{DefaultForwardShaderLayout, DefaultForwardShaderVertex}, 14 | Drawer, Renderer, VulkanRenderer, 15 | }, 16 | }; 17 | use std::{collections::HashMap, f32, fs::File, io::Read, sync::Arc, time::Instant}; 18 | use ultraviolet as uv; 19 | use wavefront_obj as obj; 20 | use winit::{ 21 | dpi::{LogicalSize, PhysicalSize}, 22 | event::{ElementState, Event, VirtualKeyCode, WindowEvent}, 23 | event_loop::{ControlFlow, EventLoop}, 24 | platform::desktop::EventLoopExtDesktop, 25 | window::{WindowBuilder, WindowId}, 26 | }; 27 | 28 | const WIDTH: u32 = 1600; 29 | const HEIGHT: u32 = 1200; 30 | 31 | const GLB_MODEL_FILE_NAME: &str = "models/chalet.glb"; 32 | const OBJ_MODEL_FILE_NAME: &str = "models/viking_room.obj"; 33 | const MODEL_TEXTURE_FILE_NAME_GLB: &str = "textures/chalet.jpg"; 34 | const MODEL_TEXTURE_FILE_NAME_OBJ: &str = "textures/viking_room.png"; 35 | 36 | fn main() { 37 | simple_logger::init_with_level(Level::Info).unwrap(); 38 | main_loop(); 39 | } 40 | 41 | /// Takes full control of the executing thread and runs the event loop for it. 42 | fn main_loop() { 43 | let args: Vec = std::env::args().collect(); 44 | let show_fps = args.contains(&"fps".to_owned()); 45 | let use_glb = args.contains(&"glb".to_owned()); 46 | if args.len() > 1 && !show_fps && !use_glb { 47 | panic!("Illegal arguments provided: {:#?}", args); 48 | } 49 | info!("Show FPS: {}", show_fps); 50 | info!("Use GLTF Model Type: {}", use_glb); 51 | 52 | info!("Running main loop..."); 53 | 54 | let mut ar = WIDTH as f32 / HEIGHT as f32; 55 | 56 | // Build Window. 57 | let mut event_loop = EventLoop::new(); 58 | let window = Arc::new( 59 | WindowBuilder::new() 60 | .with_inner_size(LogicalSize::new(WIDTH, HEIGHT)) 61 | .build(&event_loop) 62 | .unwrap(), 63 | ); 64 | 65 | // Build Renderer. 66 | let config = Config::builder() 67 | .requested_width(WIDTH) 68 | .requested_height(HEIGHT) 69 | .build() 70 | .unwrap(); 71 | let mut renderer = VulkanRenderer::new(window.clone(), config).unwrap(); 72 | 73 | // Create Vertex Resources. 74 | let (model_vertices, model_indices) = if use_glb { 75 | load_glb_model(GLB_MODEL_FILE_NAME) 76 | } else { 77 | load_obj_models(OBJ_MODEL_FILE_NAME) 78 | }; 79 | info!("Model file loaded"); 80 | let model_index_buffer = model_indices.map(|mi| { 81 | renderer 82 | .load_buffer(BufferType::Index(IndexBufferElemSize::UInt32), &mi) 83 | .unwrap() 84 | }); 85 | let model_buffer = renderer 86 | .load_buffer(BufferType::Vertex, &model_vertices) 87 | .unwrap(); 88 | 89 | // Create MVP uniform. 90 | let uniform_handle = renderer 91 | .load_uniform_buffer(DefaultForwardShaderLayout::default()) 92 | .unwrap(); 93 | 94 | // Load textures and create image. 95 | let model_texture_file = if use_glb { 96 | image::open(MODEL_TEXTURE_FILE_NAME_GLB).unwrap() 97 | } else { 98 | image::open(MODEL_TEXTURE_FILE_NAME_OBJ).unwrap() 99 | }; 100 | let mip_levels = get_mip_levels(model_texture_file.dimensions()); 101 | let model_texture = renderer 102 | .load_image_with_staging_initialization( 103 | model_texture_file, 104 | MagnificationMinificationFilter::Linear, 105 | MagnificationMinificationFilter::Linear, 106 | TextureAddressMode::ClampToEdge, 107 | TextureAddressMode::ClampToEdge, 108 | TextureAddressMode::ClampToEdge, 109 | mip_levels, 110 | ) 111 | .unwrap(); 112 | 113 | let mut drawable_object_builder = DrawableObject::builder(&renderer) 114 | .uniform_buffer(&uniform_handle) 115 | .vertex_buffer(&model_buffer) 116 | .texture_image(&model_texture); 117 | if model_index_buffer.is_some() { 118 | drawable_object_builder = 119 | drawable_object_builder.index_buffer(model_index_buffer.as_ref().unwrap()); 120 | } 121 | let drawable_object = drawable_object_builder.build().unwrap(); 122 | 123 | let start_time = Instant::now(); 124 | let mut last_frame_time = start_time; 125 | let mut frame_number = 0; 126 | let mut fps_average = 0f32; 127 | 128 | let mut camera_height = -0.5f32; 129 | 130 | // Run the loop. 131 | event_loop.run_return(move |event, _, control_flow| { 132 | // By default continuously run this event loop, even if the OS hasn't 133 | // distributed an event, that way we will draw as fast as possible. 134 | *control_flow = ControlFlow::Poll; 135 | 136 | match event { 137 | Event::MainEventsCleared => { 138 | // All the main events to process are done we can do "work" now (game 139 | // engine state update etc.) 140 | let now = Instant::now(); 141 | let time_since_start_secs = ((now - start_time).as_millis() as f32) / 1000f32; 142 | 143 | if show_fps { 144 | let time_since_last_frame_secs = ((now - last_frame_time).as_nanos() as f32) / 1e9f32; 145 | let fps = 1f32 / time_since_last_frame_secs; 146 | if frame_number == 0 { 147 | fps_average = 0f32; 148 | } else { 149 | fps_average = 150 | ((frame_number as f32 * fps_average) + fps) / (frame_number as f32 + 1f32); 151 | } 152 | frame_number += 1; 153 | 154 | info!("Frame Period: {}", time_since_last_frame_secs); 155 | info!("FPS: {}", fps); 156 | info!("FPS averaged: {}", fps_average); 157 | last_frame_time = now; 158 | } 159 | 160 | // Rise to max height then gently go back down. 161 | let camera_rate = 0.25f32; 162 | let min_camera_height = -0.5f32; 163 | let camera_range = 2f32; 164 | camera_height = 165 | (camera_rate * time_since_start_secs) % (2.0f32 * camera_range) + min_camera_height; 166 | if camera_height >= (camera_range + min_camera_height) { 167 | camera_height = (2.0f32 * (camera_range + min_camera_height)) - camera_height; 168 | } 169 | 170 | let rotation = (std::f32::consts::PI + std::f32::consts::PI * time_since_start_secs / 8f32) 171 | % (2f32 * std::f32::consts::PI); 172 | update_uniforms( 173 | &renderer, 174 | &drawable_object, 175 | uv::Vec3::new(0f32, -1f32, -1.5f32), 176 | rotation, 177 | camera_height, 178 | false, 179 | ar, 180 | ) 181 | .unwrap(); 182 | 183 | renderer.draw(&drawable_object).unwrap(); 184 | 185 | // At the end of work request redraw. 186 | window.request_redraw(); 187 | } 188 | 189 | Event::RedrawRequested(_) => { 190 | // Redraw requested, this is called after MainEventsCleared. 191 | renderer.frame().unwrap_or_else(|err| { 192 | match err { 193 | SarektError::SwapchainOutOfDate | SarektError::SuboptimalSwapchain => { 194 | // Handle window resize etc. 195 | warn!("Tried to render without processing window resize event!"); 196 | 197 | let PhysicalSize { width, height } = window.inner_size(); 198 | renderer 199 | .recreate_swapchain(width, height) 200 | .expect("Error recreating swapchain"); 201 | } 202 | e => panic!("Frame had an unrecoverable error! {}", e), 203 | } 204 | }); 205 | } 206 | 207 | Event::WindowEvent { window_id, event } => { 208 | main_loop_window_event(&event, &window_id, control_flow, &mut renderer, &mut ar) 209 | .expect("Error processing window event."); 210 | } 211 | 212 | Event::LoopDestroyed => { 213 | // Explicitly call exit so resources are cleaned up. 214 | std::process::exit(0); 215 | } 216 | _ => (), 217 | } 218 | }); 219 | } 220 | 221 | /// Handles all winit window specific events. 222 | fn main_loop_window_event( 223 | event: &WindowEvent, _id: &WindowId, control_flow: &mut winit::event_loop::ControlFlow, 224 | renderer: &mut VulkanRenderer, ar: &mut f32, 225 | ) -> SarektResult<()> { 226 | match event { 227 | WindowEvent::CloseRequested => { 228 | // When the window system requests a close, signal to winit that we'd like to 229 | // close the window. 230 | info!("Exiting due to close request event from window system..."); 231 | *control_flow = ControlFlow::Exit; 232 | } 233 | 234 | WindowEvent::KeyboardInput { input, .. } => { 235 | // When the keyboard input is a press on the escape key, exit and print the 236 | // line. 237 | if let (Some(VirtualKeyCode::Escape), ElementState::Pressed) = 238 | (input.virtual_keycode, input.state) 239 | { 240 | info!("Exiting due to escape press..."); 241 | *control_flow = ControlFlow::Exit 242 | } 243 | } 244 | 245 | WindowEvent::Resized(size) => { 246 | // If the size is 0, minimization or something like that happened so I 247 | // toggle drawing. 248 | info!("Window resized, recreating renderer swapchain..."); 249 | let enabled = !(size.height == 0 && size.width == 0); 250 | if enabled { 251 | *ar = size.width as f32 / size.height as f32; 252 | } 253 | renderer.set_rendering_enabled(enabled); 254 | return renderer.recreate_swapchain(size.width, size.height); 255 | } 256 | _ => (), 257 | } 258 | 259 | Ok(()) 260 | } 261 | 262 | fn update_uniforms( 263 | renderer: &VulkanRenderer, object: &DrawableObject, 264 | position: uv::Vec3, rotation: f32, camera_height: f32, enable_colors: bool, ar: f32, 265 | ) -> SarektResult<()> { 266 | // Pi radians per second around the y axis. 267 | let total_rotation = 268 | uv::Mat4::from_rotation_y(rotation) * uv::Mat4::from_rotation_x(-std::f32::consts::PI / 2f32); 269 | let model_matrix = uv::Mat4::from_translation(position) * total_rotation; 270 | 271 | let view_matrix = uv::Mat4::look_at( 272 | /* eye= */ uv::Vec3::new(0.0f32, camera_height, 0.0f32), 273 | /* at= */ position, 274 | /* up= */ uv::Vec3::unit_y(), 275 | ); 276 | // TODO BACKENDS this proj should be conditional on backend. 277 | let perspective_matrix = 278 | uv::projection::rh_yup::perspective_vk(std::f32::consts::PI / 2f32, ar, 0.1f32, 10f32); 279 | 280 | let uniform = DefaultForwardShaderLayout::new( 281 | perspective_matrix * view_matrix * model_matrix, 282 | enable_colors, 283 | /* enable_texture_mixing= */ true, 284 | ); 285 | object.set_uniform(renderer, &uniform) 286 | } 287 | 288 | /// For now only use the first object in the obj file. 289 | /// Returns (vertices, vertex_indicies, texture_coordinate indices) 290 | fn load_obj_models(obj_file_path: &str) -> (Vec, Option>) { 291 | let mut model_file = File::open(obj_file_path).unwrap(); 292 | let mut model_file_text = String::new(); 293 | model_file.read_to_string(&mut model_file_text).unwrap(); 294 | 295 | let obj_set = obj::obj::parse(&model_file_text).unwrap(); 296 | if obj_set.objects.len() != 1 { 297 | panic!( 298 | "The model you attempted to load has more than one object in it, implying it is a scene, if \ 299 | you wish to use it as a single model, modify the application code to ignore that or join \ 300 | your meshes into a single model" 301 | ); 302 | } 303 | 304 | info!("Loaded model {}", OBJ_MODEL_FILE_NAME); 305 | let mut vertices: Vec = Vec::new(); 306 | let mut indices: Vec = Vec::new(); 307 | 308 | // Map of inserted (obj_vertex_index, obj_texture_index) to index in the 309 | // vertices array im building. 310 | let mut inserted_indices: HashMap<(usize, usize), usize> = HashMap::new(); 311 | let model_vertices = &obj_set.objects[0].vertices; 312 | for geo in obj_set.objects[0].geometry.iter() { 313 | // For every set of geometry (regardless of material for now). 314 | for shape in geo.shapes.iter() { 315 | // For every face/shape in the set of geometry. 316 | match shape.primitive { 317 | obj::obj::Primitive::Triangle(x, y, z) => { 318 | for &vert in [x, y, z].iter() { 319 | // We're only building a buffer of indices and vertices which contain position 320 | // and tex coord. 321 | let index_key = (vert.0, vert.1.unwrap()); 322 | if let Some(&vtx_index) = inserted_indices.get(&index_key) { 323 | // Already loaded this (vertex index, texture index) combo, just add it to the 324 | // index buffer. 325 | indices.push(vtx_index as _); 326 | continue; 327 | } 328 | 329 | // This is a new unique vertex (where a vertex is both a position and it's 330 | // texture coordinate) so add it to the vertex buffer and the index buffer. 331 | let current_vertex = model_vertices[vert.0]; 332 | let vertex_as_float = [ 333 | current_vertex.x as f32, 334 | current_vertex.y as f32, 335 | current_vertex.z as f32, 336 | ]; 337 | let texture_vertices = &obj_set.objects[0].tex_vertices; 338 | let tex_vertex = texture_vertices[vert.1.unwrap()]; 339 | // TODO BACKENDS only flip on coordinate systems that should. 340 | let texture_vertex_as_float = [tex_vertex.u as f32, 1f32 - tex_vertex.v as f32]; 341 | 342 | // Ignoring normals, there is no shading in this example. 343 | 344 | // Keep track of which keys were inserted and add this vertex to the index 345 | // buffer. 346 | inserted_indices.insert(index_key, vertices.len()); 347 | indices.push(vertices.len() as _); 348 | 349 | // Add to the vertex buffer. 350 | vertices.push(DefaultForwardShaderVertex::new_with_texture( 351 | &vertex_as_float, 352 | &texture_vertex_as_float, 353 | )); 354 | } 355 | } 356 | _ => warn!("Unsupported primitive!"), 357 | } 358 | } 359 | } 360 | 361 | info!( 362 | "Vertices/indices in model: {}, {}", 363 | vertices.len(), 364 | indices.len() 365 | ); 366 | (vertices, Some(indices)) 367 | } 368 | 369 | /// Returns (vertices, vertex_indicies, texture_coordinate indices) 370 | fn load_glb_model(gltf_file_path: &str) -> (Vec, Option>) { 371 | let (document, buffers, _) = gltf::import(gltf_file_path).unwrap(); 372 | 373 | if document.scenes().len() != 1 || document.scenes().next().unwrap().nodes().len() != 1 { 374 | panic!( 375 | "The model you attempted to load has more than one scene or node in it, if you wish to use \ 376 | it as a single model, modify the application code to ignore that or join your meshes into \ 377 | a single model" 378 | ); 379 | } 380 | 381 | let mesh = document.meshes().nth(0).unwrap(); 382 | 383 | info!("Loaded model {}", gltf_file_path); 384 | let mut vertices: Vec = Vec::new(); 385 | let mut indices: Option> = None; 386 | 387 | for primitive in mesh.primitives() { 388 | let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()])); 389 | let positions = reader.read_positions().unwrap(); 390 | let tex_coords = reader.read_tex_coords(0).unwrap().into_f32(); 391 | for (position, tex_coord) in izip!(positions, tex_coords) { 392 | vertices.push(DefaultForwardShaderVertex::new_with_texture( 393 | &position, &tex_coord, 394 | )); 395 | } 396 | 397 | reader 398 | .read_indices() 399 | .map(|it| indices.get_or_insert(Vec::new()).extend(&mut it.into_u32())); 400 | } 401 | 402 | info!( 403 | "Vertices/indices in model: {}, {:?}", 404 | vertices.len(), 405 | indices.as_ref().map(|i| i.len()) 406 | ); 407 | (vertices, indices) 408 | } 409 | 410 | fn get_mip_levels(dimensions: (u32, u32)) -> u32 { 411 | let w = dimensions.0; 412 | let h = dimensions.1; 413 | (w.max(h) as f32).log2().floor() as u32 + 1 414 | } 415 | -------------------------------------------------------------------------------- /examples/10_msaa.rs: -------------------------------------------------------------------------------- 1 | use itertools::izip; 2 | use log::{info, warn, Level}; 3 | use sarekt::{ 4 | self, 5 | error::{SarektError, SarektResult}, 6 | image_data::ImageData, 7 | renderer::{ 8 | buffers_and_images::{ 9 | BufferType, IndexBufferElemSize, MagnificationMinificationFilter, TextureAddressMode, 10 | }, 11 | config::{Config, MsaaConfig}, 12 | drawable_object::DrawableObject, 13 | vertex_bindings::{DefaultForwardShaderLayout, DefaultForwardShaderVertex}, 14 | Drawer, Renderer, VulkanRenderer, 15 | }, 16 | }; 17 | use std::{ 18 | collections::HashMap, convert::TryInto, f32, fs::File, io::Read, sync::Arc, time::Instant, 19 | }; 20 | use ultraviolet as uv; 21 | use wavefront_obj as obj; 22 | use winit::{ 23 | dpi::{LogicalSize, PhysicalSize}, 24 | event::{ElementState, Event, VirtualKeyCode, WindowEvent}, 25 | event_loop::{ControlFlow, EventLoop}, 26 | platform::desktop::EventLoopExtDesktop, 27 | window::{WindowBuilder, WindowId}, 28 | }; 29 | 30 | const WIDTH: u32 = 1600; 31 | const HEIGHT: u32 = 1200; 32 | 33 | const GLB_MODEL_FILE_NAME: &str = "models/chalet.glb"; 34 | const OBJ_MODEL_FILE_NAME: &str = "models/viking_room.obj"; 35 | const MODEL_TEXTURE_FILE_NAME_GLB: &str = "textures/chalet.jpg"; 36 | const MODEL_TEXTURE_FILE_NAME_OBJ: &str = "textures/viking_room.png"; 37 | 38 | fn main() { 39 | simple_logger::init_with_level(Level::Info).unwrap(); 40 | main_loop(); 41 | } 42 | 43 | /// Takes full control of the executing thread and runs the event loop for it. 44 | fn main_loop() { 45 | let args: Vec = std::env::args().collect(); 46 | let show_fps = args.contains(&"fps".to_owned()); 47 | let use_glb = args.contains(&"glb".to_owned()); 48 | let msaa_level = if args.contains(&"4x".to_owned()) { 49 | 4u8 50 | } else if args.contains(&"8x".to_owned()) { 51 | 8u8 52 | } else if args.contains(&"noaa".to_owned()) { 53 | 1u8 54 | } else { 55 | 2u8 56 | }; 57 | 58 | info!("MSAA {}x", msaa_level); 59 | info!("Show FPS: {}", show_fps); 60 | info!("Use GLTF Model Type: {}", use_glb); 61 | 62 | info!("Running main loop..."); 63 | 64 | let mut ar = WIDTH as f32 / HEIGHT as f32; 65 | 66 | // Build Window. 67 | let mut event_loop = EventLoop::new(); 68 | let window = Arc::new( 69 | WindowBuilder::new() 70 | .with_inner_size(LogicalSize::new(WIDTH, HEIGHT)) 71 | .build(&event_loop) 72 | .unwrap(), 73 | ); 74 | 75 | // Build Renderer. 76 | let config = Config::builder() 77 | .requested_width(WIDTH) 78 | .requested_height(HEIGHT) 79 | .msaa_config(MsaaConfig::new( 80 | msaa_level.try_into().unwrap(), 81 | Some(0.2f32), 82 | )) 83 | .build() 84 | .unwrap(); 85 | let mut renderer = VulkanRenderer::new(window.clone(), config).unwrap(); 86 | 87 | // Create Vertex Resources. 88 | let (model_vertices, model_indices) = if use_glb { 89 | load_glb_model(GLB_MODEL_FILE_NAME) 90 | } else { 91 | load_obj_models(OBJ_MODEL_FILE_NAME) 92 | }; 93 | info!("Model file loaded"); 94 | let model_index_buffer = model_indices.map(|mi| { 95 | renderer 96 | .load_buffer(BufferType::Index(IndexBufferElemSize::UInt32), &mi) 97 | .unwrap() 98 | }); 99 | let model_buffer = renderer 100 | .load_buffer(BufferType::Vertex, &model_vertices) 101 | .unwrap(); 102 | 103 | // Create MVP uniform. 104 | let uniform_handle = renderer 105 | .load_uniform_buffer(DefaultForwardShaderLayout::default()) 106 | .unwrap(); 107 | 108 | // Load textures and create image. 109 | let model_texture_file = if use_glb { 110 | image::open(MODEL_TEXTURE_FILE_NAME_GLB).unwrap() 111 | } else { 112 | image::open(MODEL_TEXTURE_FILE_NAME_OBJ).unwrap() 113 | }; 114 | let mip_levels = get_mip_levels(model_texture_file.dimensions()); 115 | let model_texture = renderer 116 | .load_image_with_staging_initialization( 117 | model_texture_file, 118 | MagnificationMinificationFilter::Linear, 119 | MagnificationMinificationFilter::Linear, 120 | TextureAddressMode::ClampToEdge, 121 | TextureAddressMode::ClampToEdge, 122 | TextureAddressMode::ClampToEdge, 123 | mip_levels, 124 | ) 125 | .unwrap(); 126 | 127 | let mut drawable_object_builder = DrawableObject::builder(&renderer) 128 | .uniform_buffer(&uniform_handle) 129 | .vertex_buffer(&model_buffer) 130 | .texture_image(&model_texture); 131 | if model_index_buffer.is_some() { 132 | drawable_object_builder = 133 | drawable_object_builder.index_buffer(model_index_buffer.as_ref().unwrap()); 134 | } 135 | let drawable_object = drawable_object_builder.build().unwrap(); 136 | 137 | let start_time = Instant::now(); 138 | let mut last_frame_time = start_time; 139 | let mut frame_number = 0; 140 | let mut fps_average = 0f32; 141 | 142 | let mut camera_height = -0.5f32; 143 | 144 | // Run the loop. 145 | event_loop.run_return(move |event, _, control_flow| { 146 | // By default continuously run this event loop, even if the OS hasn't 147 | // distributed an event, that way we will draw as fast as possible. 148 | *control_flow = ControlFlow::Poll; 149 | 150 | match event { 151 | Event::MainEventsCleared => { 152 | // All the main events to process are done we can do "work" now (game 153 | // engine state update etc.) 154 | let now = Instant::now(); 155 | let time_since_start_secs = ((now - start_time).as_millis() as f32) / 1000f32; 156 | 157 | if show_fps { 158 | let time_since_last_frame_secs = ((now - last_frame_time).as_nanos() as f32) / 1e9f32; 159 | let fps = 1f32 / time_since_last_frame_secs; 160 | if frame_number == 0 { 161 | fps_average = 0f32; 162 | } else { 163 | fps_average = 164 | ((frame_number as f32 * fps_average) + fps) / (frame_number as f32 + 1f32); 165 | } 166 | frame_number += 1; 167 | 168 | info!("Frame Period: {}", time_since_last_frame_secs); 169 | info!("FPS: {}", fps); 170 | info!("FPS averaged: {}", fps_average); 171 | last_frame_time = now; 172 | } 173 | 174 | // Rise to max height then gently go back down. 175 | let camera_rate = 0.25f32; 176 | let min_camera_height = -0.5f32; 177 | let camera_range = 2f32; 178 | camera_height = 179 | (camera_rate * time_since_start_secs) % (2.0f32 * camera_range) + min_camera_height; 180 | if camera_height >= (camera_range + min_camera_height) { 181 | camera_height = (2.0f32 * (camera_range + min_camera_height)) - camera_height; 182 | } 183 | 184 | let rotation = (std::f32::consts::PI + std::f32::consts::PI * time_since_start_secs / 8f32) 185 | % (2f32 * std::f32::consts::PI); 186 | update_uniforms( 187 | &renderer, 188 | &drawable_object, 189 | uv::Vec3::new(0f32, -1f32, -1.5f32), 190 | rotation, 191 | camera_height, 192 | false, 193 | ar, 194 | ) 195 | .unwrap(); 196 | 197 | renderer.draw(&drawable_object).unwrap(); 198 | 199 | // At the end of work request redraw. 200 | window.request_redraw(); 201 | } 202 | 203 | Event::RedrawRequested(_) => { 204 | // Redraw requested, this is called after MainEventsCleared. 205 | renderer.frame().unwrap_or_else(|err| { 206 | match err { 207 | SarektError::SwapchainOutOfDate | SarektError::SuboptimalSwapchain => { 208 | // Handle window resize etc. 209 | warn!("Tried to render without processing window resize event!"); 210 | 211 | let PhysicalSize { width, height } = window.inner_size(); 212 | renderer 213 | .recreate_swapchain(width, height) 214 | .expect("Error recreating swapchain"); 215 | } 216 | e => panic!("Frame had an unrecoverable error! {}", e), 217 | } 218 | }); 219 | } 220 | 221 | Event::WindowEvent { window_id, event } => { 222 | main_loop_window_event(&event, &window_id, control_flow, &mut renderer, &mut ar) 223 | .expect("Error processing window event."); 224 | } 225 | 226 | Event::LoopDestroyed => { 227 | // Explicitly call exit so resources are cleaned up. 228 | std::process::exit(0); 229 | } 230 | _ => (), 231 | } 232 | }); 233 | } 234 | 235 | /// Handles all winit window specific events. 236 | fn main_loop_window_event( 237 | event: &WindowEvent, _id: &WindowId, control_flow: &mut winit::event_loop::ControlFlow, 238 | renderer: &mut VulkanRenderer, ar: &mut f32, 239 | ) -> SarektResult<()> { 240 | match event { 241 | WindowEvent::CloseRequested => { 242 | // When the window system requests a close, signal to winit that we'd like to 243 | // close the window. 244 | info!("Exiting due to close request event from window system..."); 245 | *control_flow = ControlFlow::Exit; 246 | } 247 | 248 | WindowEvent::KeyboardInput { input, .. } => { 249 | // When the keyboard input is a press on the escape key, exit and print the 250 | // line. 251 | if let (Some(VirtualKeyCode::Escape), ElementState::Pressed) = 252 | (input.virtual_keycode, input.state) 253 | { 254 | info!("Exiting due to escape press..."); 255 | *control_flow = ControlFlow::Exit 256 | } 257 | } 258 | 259 | WindowEvent::Resized(size) => { 260 | // If the size is 0, minimization or something like that happened so I 261 | // toggle drawing. 262 | info!("Window resized, recreating renderer swapchain..."); 263 | let enabled = !(size.height == 0 && size.width == 0); 264 | if enabled { 265 | *ar = size.width as f32 / size.height as f32; 266 | } 267 | renderer.set_rendering_enabled(enabled); 268 | return renderer.recreate_swapchain(size.width, size.height); 269 | } 270 | _ => (), 271 | } 272 | 273 | Ok(()) 274 | } 275 | 276 | fn update_uniforms( 277 | renderer: &VulkanRenderer, object: &DrawableObject, 278 | position: uv::Vec3, rotation: f32, camera_height: f32, enable_colors: bool, ar: f32, 279 | ) -> SarektResult<()> { 280 | // Pi radians per second around the y axis. 281 | let total_rotation = 282 | uv::Mat4::from_rotation_y(rotation) * uv::Mat4::from_rotation_x(-std::f32::consts::PI / 2f32); 283 | let model_matrix = uv::Mat4::from_translation(position) * total_rotation; 284 | 285 | let view_matrix = uv::Mat4::look_at( 286 | /* eye= */ uv::Vec3::new(0.0f32, camera_height, 0.0f32), 287 | /* at= */ position, 288 | /* up= */ uv::Vec3::unit_y(), 289 | ); 290 | // TODO BACKENDS this proj should be conditional on backend. 291 | let perspective_matrix = 292 | uv::projection::rh_yup::perspective_vk(std::f32::consts::PI / 2f32, ar, 0.1f32, 10f32); 293 | 294 | let uniform = DefaultForwardShaderLayout::new( 295 | perspective_matrix * view_matrix * model_matrix, 296 | enable_colors, 297 | /* enable_texture_mixing= */ true, 298 | ); 299 | object.set_uniform(renderer, &uniform) 300 | } 301 | 302 | /// For now only use the first object in the obj file. 303 | /// Returns (vertices, vertex_indicies, texture_coordinate indices) 304 | fn load_obj_models(obj_file_path: &str) -> (Vec, Option>) { 305 | let mut model_file = File::open(obj_file_path).unwrap(); 306 | let mut model_file_text = String::new(); 307 | model_file.read_to_string(&mut model_file_text).unwrap(); 308 | 309 | let obj_set = obj::obj::parse(&model_file_text).unwrap(); 310 | if obj_set.objects.len() != 1 { 311 | panic!( 312 | "The model you attempted to load has more than one object in it, implying it is a scene, if \ 313 | you wish to use it as a single model, modify the application code to ignore that or join \ 314 | your meshes into a single model" 315 | ); 316 | } 317 | 318 | info!("Loaded model {}", OBJ_MODEL_FILE_NAME); 319 | let mut vertices: Vec = Vec::new(); 320 | let mut indices: Vec = Vec::new(); 321 | 322 | // Map of inserted (obj_vertex_index, obj_texture_index) to index in the 323 | // vertices array im building. 324 | let mut inserted_indices: HashMap<(usize, usize), usize> = HashMap::new(); 325 | let model_vertices = &obj_set.objects[0].vertices; 326 | for geo in obj_set.objects[0].geometry.iter() { 327 | // For every set of geometry (regardless of material for now). 328 | for shape in geo.shapes.iter() { 329 | // For every face/shape in the set of geometry. 330 | match shape.primitive { 331 | obj::obj::Primitive::Triangle(x, y, z) => { 332 | for &vert in [x, y, z].iter() { 333 | // We're only building a buffer of indices and vertices which contain position 334 | // and tex coord. 335 | let index_key = (vert.0, vert.1.unwrap()); 336 | if let Some(&vtx_index) = inserted_indices.get(&index_key) { 337 | // Already loaded this (vertex index, texture index) combo, just add it to the 338 | // index buffer. 339 | indices.push(vtx_index as _); 340 | continue; 341 | } 342 | 343 | // This is a new unique vertex (where a vertex is both a position and it's 344 | // texture coordinate) so add it to the vertex buffer and the index buffer. 345 | let current_vertex = model_vertices[vert.0]; 346 | let vertex_as_float = [ 347 | current_vertex.x as f32, 348 | current_vertex.y as f32, 349 | current_vertex.z as f32, 350 | ]; 351 | let texture_vertices = &obj_set.objects[0].tex_vertices; 352 | let tex_vertex = texture_vertices[vert.1.unwrap()]; 353 | // TODO BACKENDS only flip on coordinate systems that should. 354 | let texture_vertex_as_float = [tex_vertex.u as f32, 1f32 - tex_vertex.v as f32]; 355 | 356 | // Ignoring normals, there is no shading in this example. 357 | 358 | // Keep track of which keys were inserted and add this vertex to the index 359 | // buffer. 360 | inserted_indices.insert(index_key, vertices.len()); 361 | indices.push(vertices.len() as _); 362 | 363 | // Add to the vertex buffer. 364 | vertices.push(DefaultForwardShaderVertex::new_with_texture( 365 | &vertex_as_float, 366 | &texture_vertex_as_float, 367 | )); 368 | } 369 | } 370 | _ => warn!("Unsupported primitive!"), 371 | } 372 | } 373 | } 374 | 375 | info!( 376 | "Vertices/indices in model: {}, {}", 377 | vertices.len(), 378 | indices.len() 379 | ); 380 | (vertices, Some(indices)) 381 | } 382 | 383 | /// Returns (vertices, vertex_indicies, texture_coordinate indices) 384 | fn load_glb_model(gltf_file_path: &str) -> (Vec, Option>) { 385 | let (document, buffers, _) = gltf::import(gltf_file_path).unwrap(); 386 | 387 | if document.scenes().len() != 1 || document.scenes().next().unwrap().nodes().len() != 1 { 388 | panic!( 389 | "The model you attempted to load has more than one scene or node in it, if you wish to use \ 390 | it as a single model, modify the application code to ignore that or join your meshes into \ 391 | a single model" 392 | ); 393 | } 394 | 395 | let mesh = document.meshes().nth(0).unwrap(); 396 | 397 | info!("Loaded model {}", gltf_file_path); 398 | let mut vertices: Vec = Vec::new(); 399 | let mut indices: Option> = None; 400 | 401 | for primitive in mesh.primitives() { 402 | let reader = primitive.reader(|buffer| Some(&buffers[buffer.index()])); 403 | let positions = reader.read_positions().unwrap(); 404 | let tex_coords = reader.read_tex_coords(0).unwrap().into_f32(); 405 | for (position, tex_coord) in izip!(positions, tex_coords) { 406 | vertices.push(DefaultForwardShaderVertex::new_with_texture( 407 | &position, &tex_coord, 408 | )); 409 | } 410 | 411 | reader 412 | .read_indices() 413 | .map(|it| indices.get_or_insert(Vec::new()).extend(&mut it.into_u32())); 414 | } 415 | 416 | info!( 417 | "Vertices/indices in model: {}, {:?}", 418 | vertices.len(), 419 | indices.as_ref().map(|i| i.len()) 420 | ); 421 | (vertices, indices) 422 | } 423 | 424 | fn get_mip_levels(dimensions: (u32, u32)) -> u32 { 425 | let w = dimensions.0; 426 | let h = dimensions.1; 427 | (w.max(h) as f32).log2().floor() as u32 + 1 428 | } 429 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | merge_imports = true 2 | tab_spaces = 2 3 | fn_args_layout = "Compressed" 4 | format_strings = true 5 | reorder_impl_items = true 6 | use_try_shorthand = true 7 | wrap_comments = true 8 | 9 | -------------------------------------------------------------------------------- /sarekt_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonpollack23/sarekt/c757cb300250edf76d8acaefbe1fe9c802132d2b/sarekt_screenshot.png -------------------------------------------------------------------------------- /sarekt_screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/brandonpollack23/sarekt/c757cb300250edf76d8acaefbe1fe9c802132d2b/sarekt_screenshot2.png -------------------------------------------------------------------------------- /setup.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | curl -LO https://storage.googleapis.com/sarekt/sarekt_assets.zip 4 | unzip sarekt_assets.zip 5 | rm sarekt_assets.zip 6 | -------------------------------------------------------------------------------- /shaders/no_buffer_triangle.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(location = 0)in vec3 fragColor; 5 | layout(location = 0)out vec4 outColor; 6 | 7 | void main() { 8 | outColor = vec4(fragColor, 1.0); 9 | } -------------------------------------------------------------------------------- /shaders/no_buffer_triangle.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(location = 0)out vec3 fragColor; 5 | 6 | vec2 positions[3] = vec2[]( 7 | vec2(0.0, - 0.5), // Center 8 | vec2(0.5, 0.5), // Bottom Right 9 | vec2(-0.5, 0.5)// Bottom Left 10 | ); 11 | 12 | vec3 colors[3] = vec3[]( 13 | vec3(1.0, 0.0, 0.0), 14 | vec3(0.0, 1.0, 0.0), 15 | vec3(0.0, 0.0, 1.0) 16 | ); 17 | 18 | void main() { 19 | gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); 20 | fragColor = colors[gl_VertexIndex]; 21 | } -------------------------------------------------------------------------------- /shaders/sarekt_forward.frag: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 0) uniform DefaultForwardShaderUniform { 5 | mat4 mvp; 6 | int enableColorMixing; 7 | int enableTextureMixing; 8 | } ubo; 9 | 10 | layout(binding = 1) uniform sampler2D texSampler; 11 | 12 | layout(location = 0) in vec3 fragColor; 13 | layout(location = 1) in vec2 fragTexCoord; 14 | 15 | layout(location = 0) out vec4 outColor; 16 | 17 | void main() { 18 | vec3 colorFromFragColor = fragColor; 19 | 20 | vec4 colorFromTexture; 21 | if (ubo.enableTextureMixing == 1) { 22 | colorFromTexture = texture(texSampler, fragTexCoord); 23 | } else { 24 | colorFromTexture = vec4(1.0); 25 | } 26 | 27 | // Alpha is from the texture alone. 28 | outColor = vec4(colorFromFragColor * colorFromTexture.rgb, colorFromTexture.a); 29 | } -------------------------------------------------------------------------------- /shaders/sarekt_forward.vert: -------------------------------------------------------------------------------- 1 | #version 450 2 | #extension GL_ARB_separate_shader_objects : enable 3 | 4 | layout(binding = 0) uniform DefaultForwardShaderUniform { 5 | mat4 mvp; 6 | int enableColorMixing; 7 | int enableTextureMixing; 8 | } ubo; 9 | 10 | layout(location = 0) in vec3 inPosition; 11 | layout(location = 1) in vec3 inColor; 12 | layout(location = 2) in vec2 inTexCoord; 13 | 14 | layout(location = 0) out vec3 fragColor; 15 | layout(location = 1) out vec2 fragTexCoord; 16 | 17 | void main() { 18 | gl_Position = ubo.mvp * vec4(inPosition, 1.0); 19 | 20 | if (ubo.enableColorMixing != 0) { 21 | fragColor = inColor; 22 | } else { 23 | fragColor = vec3(1.0); 24 | } 25 | 26 | fragTexCoord = inTexCoord; 27 | } -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::error::SarektError::CStrError; 2 | 3 | use ash::vk; 4 | use std::{error::Error, ffi::NulError, fmt}; 5 | 6 | pub type SarektResult = Result; 7 | 8 | #[derive(Debug)] 9 | pub enum SarektError { 10 | Unknown, 11 | CouldNotSelectPhysicalDevice(&'static str), 12 | SuboptimalSwapchain, 13 | SwapchainOutOfDate, 14 | CStrError(NulError), 15 | VulkanError(vk::Result), 16 | InstanceError(ash::InstanceError), 17 | UnknownShader, 18 | IncompatibleShaderCode, 19 | IncorrectLoaderFunction, 20 | IncorrectBufferType, 21 | IncorrectResourceType, 22 | UnsupportedImageFormat, 23 | UnknownResource, 24 | NoSuitableMemoryHeap, 25 | NoSuitableDepthBufferFormat, 26 | VulkanMemoryAllocatorError(vk_mem::error::Error), 27 | IllegalMipmapCount, 28 | FormatDoesNotSupportMipmapping(String), 29 | UnsupportedMsaa(&'static str), 30 | } 31 | 32 | impl From for SarektError { 33 | fn from(e: vk::Result) -> Self { 34 | match e { 35 | vk::Result::SUBOPTIMAL_KHR => SarektError::SuboptimalSwapchain, 36 | vk::Result::ERROR_OUT_OF_DATE_KHR => SarektError::SwapchainOutOfDate, 37 | e => SarektError::VulkanError(e), 38 | } 39 | } 40 | } 41 | impl From for SarektError { 42 | fn from(e: ash::InstanceError) -> Self { 43 | SarektError::InstanceError(e) 44 | } 45 | } 46 | impl From for SarektError { 47 | fn from(e: vk_mem::Error) -> Self { 48 | SarektError::VulkanMemoryAllocatorError(e) 49 | } 50 | } 51 | impl From for SarektError { 52 | fn from(e: NulError) -> SarektError { 53 | CStrError(e) 54 | } 55 | } 56 | 57 | impl fmt::Display for SarektError { 58 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 59 | match self { 60 | SarektError::Unknown => write!(f, "Unknown Error"), 61 | SarektError::SwapchainOutOfDate => write!( 62 | f, 63 | "Swapchain is out of date, try using recreate_swapchain method" 64 | ), 65 | SarektError::SuboptimalSwapchain => write!( 66 | f, 67 | "Swapchain suboptimal, try using recreate_swapchain method" 68 | ), 69 | SarektError::VulkanError(r) => write!(f, "Vulkan Error: {}", r), 70 | SarektError::InstanceError(e) => write!(f, "The vulkan wrapper ash produced an error: {}", e), 71 | SarektError::UnknownShader => write!(f, "Tried to act on unknown shader"), 72 | SarektError::UnknownResource => { 73 | write!(f, "Tried to act on unknown resource (image or buffer)") 74 | } 75 | SarektError::IncorrectLoaderFunction => write!( 76 | f, 77 | "Attempted to load a special buffer type with the generic load_buffer function. Did you \ 78 | mean to use load_uniform buffer?" 79 | ), 80 | SarektError::IncorrectBufferType => write!( 81 | f, 82 | "Tried to load a buffer type that didn't match with function call. Perhaps you've \ 83 | tricked Sarekt into storing a Vertex buffer where it should have been a Uniform buffer?" 84 | ), 85 | SarektError::IncorrectResourceType => write!( 86 | f, 87 | "Resource type did not match function call, did you try to get a buffer with an image \ 88 | function or vice versa?" 89 | ), 90 | SarektError::UnsupportedImageFormat => { 91 | write!(f, "Image format of ImageData is not supported") 92 | } 93 | SarektError::NoSuitableMemoryHeap => write!( 94 | f, 95 | "Could not find memory heap that was suitable for the device allocation." 96 | ), 97 | SarektError::NoSuitableDepthBufferFormat => { 98 | write!(f, "Could not select a format for the depth buffer") 99 | } 100 | SarektError::VulkanMemoryAllocatorError(e) => { 101 | write!(f, "Vulkan memory allocator error: {}", e) 102 | } 103 | SarektError::IncompatibleShaderCode => { 104 | write!(f, "Tried to load an incompatible shader type into backend") 105 | } 106 | SarektError::CouldNotSelectPhysicalDevice(s) => { 107 | write!(f, "Sarekt could not find a suitable physical device: {}", s) 108 | } 109 | SarektError::CStrError(e) => write!(f, "{}", e), 110 | SarektError::IllegalMipmapCount => write!( 111 | f, 112 | "Illegal mipmap count, specify a (resonable) number higher than 0)" 113 | ), 114 | SarektError::FormatDoesNotSupportMipmapping(s) => { 115 | write!(f, "Format not supported for mipmapping: {}", s) 116 | } 117 | SarektError::UnsupportedMsaa(s) => write!(f, "Unsupported MSAA: {}", s), 118 | } 119 | } 120 | } 121 | 122 | impl Error for SarektError {} 123 | -------------------------------------------------------------------------------- /src/image_data.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::{SarektError, SarektResult}, 3 | image_data::ImageDataFormat::*, 4 | }; 5 | use safe_transmute::to_bytes::transmute_to_bytes_vec; 6 | 7 | /// The trait used for loading images into Sarekt. An implementation is 8 | /// provided for the rust [image](https://crates.io/crates/image) crate. Feel free to create one in your own project for other crates (by wrapping in a newtype to avoid the orphan problem). 9 | pub trait ImageData { 10 | /// Returns byte color array of pixels. 11 | fn into_bytes(self) -> Vec; 12 | 13 | /// Converts to rgba8, a format that must be supported by at least the Vulkan 14 | /// backend 15 | fn into_rgba8(self) -> Self; 16 | 17 | /// Returns (width, height) of the image. 18 | fn dimensions(&self) -> (u32, u32); 19 | 20 | /// Underlying image format. 21 | fn format(&self) -> SarektResult; 22 | } 23 | 24 | #[derive(Copy, Clone, Debug)] 25 | pub enum ImageDataFormat { 26 | R8G8B8Srgb, 27 | B8G8R8Srgb, 28 | B8G8R8A8Srgb, 29 | R8G8B8A8Srgb, 30 | 31 | R8G8B8Unorm, 32 | B8G8R8Unorm, 33 | B8G8R8A8Unorm, 34 | R8G8B8A8Unorm, 35 | RGB16Unorm, 36 | RGBA16Unorm, 37 | // Depth Buffer Formats 38 | D32Float, 39 | D32FloatS8, 40 | D24NormS8, 41 | } 42 | 43 | impl ImageData for image::DynamicImage { 44 | fn into_bytes(self) -> Vec { 45 | match self { 46 | image::DynamicImage::ImageBgr8(img) => img.into_raw(), 47 | image::DynamicImage::ImageLuma8(img) => img.into_raw(), 48 | image::DynamicImage::ImageLumaA8(img) => img.into_raw(), 49 | image::DynamicImage::ImageRgb8(img) => img.into_raw(), 50 | image::DynamicImage::ImageRgba8(img) => img.into_raw(), 51 | image::DynamicImage::ImageBgra8(img) => img.into_raw(), 52 | image::DynamicImage::ImageLuma16(img) => transmute_to_bytes_vec(img.into_raw()).unwrap(), 53 | image::DynamicImage::ImageLumaA16(img) => transmute_to_bytes_vec(img.into_raw()).unwrap(), 54 | image::DynamicImage::ImageRgb16(img) => transmute_to_bytes_vec(img.into_raw()).unwrap(), 55 | image::DynamicImage::ImageRgba16(img) => transmute_to_bytes_vec(img.into_raw()).unwrap(), 56 | } 57 | } 58 | 59 | fn into_rgba8(self) -> Self { 60 | image::DynamicImage::ImageRgba8(self.into_rgba()) 61 | } 62 | 63 | fn dimensions(&self) -> (u32, u32) { 64 | match self { 65 | image::DynamicImage::ImageBgr8(img) => img.dimensions(), 66 | image::DynamicImage::ImageLuma8(img) => img.dimensions(), 67 | image::DynamicImage::ImageLumaA8(img) => img.dimensions(), 68 | image::DynamicImage::ImageRgb8(img) => img.dimensions(), 69 | image::DynamicImage::ImageRgba8(img) => img.dimensions(), 70 | image::DynamicImage::ImageBgra8(img) => img.dimensions(), 71 | image::DynamicImage::ImageLuma16(img) => img.dimensions(), 72 | image::DynamicImage::ImageLumaA16(img) => img.dimensions(), 73 | image::DynamicImage::ImageRgb16(img) => img.dimensions(), 74 | image::DynamicImage::ImageRgba16(img) => img.dimensions(), 75 | } 76 | } 77 | 78 | fn format(&self) -> SarektResult { 79 | match self { 80 | image::DynamicImage::ImageBgr8(_) => Ok(B8G8R8A8Srgb), 81 | image::DynamicImage::ImageLuma8(_) => Err(SarektError::UnsupportedImageFormat), 82 | image::DynamicImage::ImageLumaA8(_) => Err(SarektError::UnsupportedImageFormat), 83 | image::DynamicImage::ImageRgb8(_) => Ok(R8G8B8Srgb), 84 | image::DynamicImage::ImageRgba8(_) => Ok(R8G8B8A8Srgb), 85 | image::DynamicImage::ImageBgra8(_) => Ok(B8G8R8A8Srgb), 86 | image::DynamicImage::ImageLuma16(_) => Err(SarektError::UnsupportedImageFormat), 87 | image::DynamicImage::ImageLumaA16(_) => Err(SarektError::UnsupportedImageFormat), 88 | image::DynamicImage::ImageRgb16(_) => Ok(RGB16Unorm), 89 | image::DynamicImage::ImageRgba16(_) => Ok(RGBA16Unorm), 90 | } 91 | } 92 | } 93 | 94 | /// Image data that represents a single color. 95 | pub struct Monocolor { 96 | inner: [u8; 4], 97 | } 98 | impl Monocolor { 99 | pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { 100 | Monocolor { 101 | inner: [r, g, b, a], 102 | } 103 | } 104 | 105 | pub fn clear() -> Self { 106 | Self::new(1, 1, 1, 0) 107 | } 108 | } 109 | impl ImageData for Monocolor { 110 | fn into_bytes(self) -> Vec { 111 | self.inner.to_vec() 112 | } 113 | 114 | fn into_rgba8(self) -> Self { 115 | self 116 | } 117 | 118 | fn dimensions(&self) -> (u32, u32) { 119 | (1, 1) 120 | } 121 | 122 | fn format(&self) -> SarektResult { 123 | Ok(R8G8B8A8Srgb) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! A renderer toy project! I hope to hide the fact that it only supports 2 | //! Vulkan from you in the hopes it one day supports more. It barely even 3 | //! supports that... 4 | //! 5 | //! See renderer crate for how to use. 6 | #[macro_use] 7 | extern crate memoffset; 8 | #[macro_use] 9 | extern crate derive_builder; 10 | 11 | pub mod error; 12 | pub mod image_data; 13 | pub mod renderer; 14 | -------------------------------------------------------------------------------- /src/renderer/buffers_and_images.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::{SarektError, SarektResult}, 3 | image_data::{ImageData, ImageDataFormat}, 4 | renderer::config::NumSamples, 5 | }; 6 | use log::warn; 7 | use slotmap::{DefaultKey, SlotMap}; 8 | use std::{ 9 | fmt::Debug, 10 | sync::{Arc, RwLock, Weak}, 11 | }; 12 | 13 | /// A type that can be used to retrieve a buffer from the renderer and 14 | /// BufferStore that will destroy the shader when it goes out of scope. 15 | /// 16 | /// As always, In order to pass this around with multiple ownership, wrap it in 17 | /// an Arc. 18 | pub struct BufferImageHandle 19 | where 20 | BL: BufferAndImageLoader, 21 | BL::BackendHandle: BackendHandleTrait + Copy + Debug, 22 | { 23 | inner_key: DefaultKey, 24 | resource_type: ResourceType, 25 | buffer_store: Weak>>, 26 | } 27 | impl Drop for BufferImageHandle 28 | where 29 | BL: BufferAndImageLoader, 30 | BL::BackendHandle: BackendHandleTrait + Copy + Debug, 31 | { 32 | fn drop(&mut self) { 33 | let buffer_store = self.buffer_store.upgrade(); 34 | if matches!(buffer_store, None) { 35 | return; 36 | } 37 | 38 | let mut buffer_store_guard = buffer_store 39 | .as_ref() 40 | .unwrap() 41 | .write() 42 | .expect("Could not unlock BufferStore due to previous panic"); 43 | 44 | let result = match self.resource_type { 45 | ResourceType::Buffer(_) => buffer_store_guard.destroy_buffer(self.inner_key), 46 | ResourceType::Image => buffer_store_guard.destroy_image(self.inner_key), 47 | }; 48 | 49 | match result { 50 | // Already deleted, likely shutting down. Nothing to do. 51 | Err(SarektError::UnknownResource) => {} 52 | Err(e) => warn!( 53 | "resource not destroyed, maybe it was already? Error: {:?}", 54 | e 55 | ), 56 | Ok(()) => {} 57 | } 58 | } 59 | } 60 | 61 | /// Which kind of buffer or image is this. The Renderer and DrawableObject wil 62 | /// use this information to utilize it correctly. 63 | #[derive(Copy, Clone, Debug)] 64 | pub enum ResourceType { 65 | Image, 66 | Buffer(BufferType), 67 | } 68 | 69 | /// Which type of buffer the resource is. 70 | #[derive(Copy, Clone, Debug)] 71 | pub enum BufferType { 72 | Vertex, 73 | Uniform, 74 | Index(IndexBufferElemSize), 75 | } 76 | 77 | /// Different backends support different index buffer sizes, select which one 78 | /// when you load it. 79 | #[derive(Copy, Clone, Debug)] 80 | pub enum IndexBufferElemSize { 81 | UInt16, 82 | UInt32, 83 | } 84 | 85 | /// The handle that represents a buffer or image in the backend. 86 | /// 87 | /// Unsafe because: 88 | /// This must specifically be the handle used to delete your 89 | /// image or buffer in the backend/GPU in 90 | /// [ShaderLoader](trait.ShaderLoader.html). 91 | pub unsafe trait BackendHandleTrait: Copy {} 92 | 93 | /// A special handle for uniform buffers. On some backends there are special 94 | /// cases needed to be handled more so than other (vertex and index) buffers. 95 | /// BufferLoader is the backing loader for the buffer and BufElem is the type 96 | /// that the buffer contains. 97 | /// 98 | /// For example, on Vulkan more than one frame can be in flight so this needs to 99 | /// actually create uniform buffers for each framebuffer. 100 | #[derive(Clone, Debug)] 101 | pub struct UniformBufferHandle { 102 | pub uniform_buffer_backend_handle: BL::UniformBufferHandle, 103 | _marker: std::marker::PhantomData, 104 | } 105 | impl UniformBufferHandle { 106 | pub fn new(uniform_buffer_backend_handle: BL::UniformBufferHandle) -> Self { 107 | Self { 108 | uniform_buffer_backend_handle, 109 | _marker: std::marker::PhantomData, 110 | } 111 | } 112 | } 113 | 114 | /// A trait used by each implementation in order to load buffers and images in 115 | /// their own way. 116 | /// 117 | /// Unsafe because: 118 | /// * The lifetimes of the functions to create them (which are 119 | /// usually dynamically loaded) must outlive the Loader itself. 120 | /// 121 | /// * BackendHandle must be an implementer of 122 | /// [ShaderBackendHandle](trait.ShaderBackendHandleTrait.html) 123 | /// 124 | /// * It is the responsibility of the implementor to drop anything loaded using 125 | /// delete_buffer_or_image cleanly on all elements, if the ShaderHandle 126 | /// dropping doesn't handle it. 127 | /// 128 | /// * Cleanup must be called if the application controls object lifetimes. 129 | pub unsafe trait BufferAndImageLoader { 130 | type BackendHandle; 131 | type UniformBufferDataHandle: Debug; 132 | type UniformBufferHandle; 133 | 134 | /// TODO(issue#5) PERFORMANCE some platforms might not actually ever benefit 135 | /// from staging. Detect this and elide the staging. 136 | 137 | /// # Safety 138 | /// Must call before exiting. 139 | unsafe fn cleanup(&self) -> SarektResult<()>; 140 | 141 | /// Loads a buffer using a staging buffer and then transfers it into GPU only 142 | /// memory for efficiency. 143 | fn load_buffer_with_staging( 144 | &self, buffer_type: BufferType, buffer: &[BufElem], 145 | ) -> SarektResult; 146 | 147 | /// Loads a buffer without staging. Frequently updated buffers will just be 148 | /// slowed down by waiting for transfer, such as uniform buffers. 149 | fn load_buffer_without_staging( 150 | &self, buffer_type: BufferType, buffer: &[BufElem], 151 | ) -> SarektResult; 152 | 153 | // TODO(issue#31) Allow cpu accessible images and updating all images. 154 | 155 | /// Same as `load_buffer_with_staging` but loads an r8g8b8a8 32 bit format 156 | /// image instead. 157 | fn load_image_with_staging_initialization( 158 | &self, pixels: impl ImageData, magnification_filter: MagnificationMinificationFilter, 159 | minification_filter: MagnificationMinificationFilter, address_x: TextureAddressMode, 160 | address_y: TextureAddressMode, address_z: TextureAddressMode, mip_levels: u32, 161 | ) -> SarektResult; 162 | 163 | /// Loads an image, much like `load_image_with_staging_initialization`, but 164 | /// does not give it any initial value, only a size and format. This is 165 | /// useful for initializing internally used attachments, depth buffers, etc. 166 | fn create_uninitialized_image( 167 | &self, dimensions: (u32, u32), format: ImageDataFormat, num_msaa_samples: NumSamples, 168 | ) -> SarektResult; 169 | 170 | /// Deletes that resource, baby! 171 | fn delete_buffer_or_image(&self, handle: Self::BackendHandle) -> SarektResult<()>; 172 | } 173 | 174 | /// A storage for all buffers to be loaded or destroyed from. Returns a handle 175 | /// that can be used to retrieve the associated buffer, which includes it's type 176 | /// and it's handle to whichever backend you're using. 177 | pub struct BufferImageStore 178 | where 179 | BL: BufferAndImageLoader, 180 | BL::BackendHandle: BackendHandleTrait + Copy + Debug, 181 | { 182 | loaded_buffers_and_images: SlotMap>, 183 | buffer_image_loader: BL, 184 | } 185 | impl BufferImageStore 186 | where 187 | BL: BufferAndImageLoader, 188 | BL::BackendHandle: BackendHandleTrait + Copy + Debug, 189 | { 190 | pub fn new(buffer_loader: BL) -> Self { 191 | Self { 192 | loaded_buffers_and_images: SlotMap::new(), 193 | buffer_image_loader: buffer_loader, 194 | } 195 | } 196 | 197 | /// Must be called by the backend when cleaning up all resources, if they are 198 | /// managed by the application (as in Vulkan/D3D12). 199 | /// 200 | /// # Safety 201 | /// Unsafe because afterwards the object becomes invalid and should not be 202 | /// used again. 203 | pub unsafe fn cleanup(&mut self) -> SarektResult<()> { 204 | self.buffer_image_loader.cleanup()?; 205 | self.destroy_all_images_and_buffers(); 206 | Ok(()) 207 | } 208 | 209 | /// Load a buffer and allocate memory into the backend/GPU and return a 210 | /// handle. 211 | pub(crate) fn load_buffer_with_staging( 212 | this: &Arc>, buffer_type: BufferType, buffer: &[BufElem], 213 | ) -> SarektResult<(BufferImageHandle, BufferOrImage)> { 214 | let mut buffer_store = this 215 | .write() 216 | .expect("Could not unlock BufferStore due to previous panic"); 217 | 218 | let buffer_backend_handle = buffer_store 219 | .buffer_image_loader 220 | .load_buffer_with_staging(buffer_type, buffer)?; 221 | let buffer_or_image = 222 | BufferOrImage::new(buffer_backend_handle, ResourceType::Buffer(buffer_type)); 223 | 224 | let inner_key = buffer_store 225 | .loaded_buffers_and_images 226 | .insert(buffer_or_image); 227 | 228 | Ok(( 229 | BufferImageHandle { 230 | inner_key, 231 | resource_type: ResourceType::Buffer(buffer_type), 232 | buffer_store: Arc::downgrade(this), 233 | }, 234 | buffer_or_image, 235 | )) 236 | } 237 | 238 | /// Same as above but keeps the buffer in cpu accessible memory. Useful for 239 | /// things updated frequently like MVP uniforms. 240 | pub(crate) fn load_buffer_without_staging( 241 | this: &Arc>, buffer_type: BufferType, buffer: &[BufElem], 242 | ) -> SarektResult<(BufferImageHandle, BufferOrImage)> { 243 | let mut buffer_store = this 244 | .write() 245 | .expect("Could not unlock BufferStore due to previous panic"); 246 | 247 | let buffer_backend_handle = buffer_store 248 | .buffer_image_loader 249 | .load_buffer_without_staging(buffer_type, buffer)?; 250 | let buffer_or_image = 251 | BufferOrImage::new(buffer_backend_handle, ResourceType::Buffer(buffer_type)); 252 | 253 | let inner_key = buffer_store 254 | .loaded_buffers_and_images 255 | .insert(buffer_or_image); 256 | 257 | Ok(( 258 | BufferImageHandle { 259 | inner_key, 260 | resource_type: ResourceType::Buffer(buffer_type), 261 | buffer_store: Arc::downgrade(this), 262 | }, 263 | buffer_or_image, 264 | )) 265 | } 266 | 267 | /// Destroy a buffer and free the memory associated with it from the 268 | /// backend/GPU. 269 | fn destroy_buffer(&mut self, inner_key: DefaultKey) -> SarektResult<()> { 270 | let buffer = self.loaded_buffers_and_images.remove(inner_key); 271 | if buffer.is_none() { 272 | return Err(SarektError::UnknownResource); 273 | } 274 | 275 | self 276 | .buffer_image_loader 277 | .delete_buffer_or_image(buffer.unwrap().handle) 278 | } 279 | 280 | /// Same as `load_buffer_with_staging` but loads an r8b8g8a8 image instead. 281 | pub(crate) fn load_image_with_staging_initialization( 282 | this: &Arc>, pixels: impl ImageData, 283 | magnification_filter: MagnificationMinificationFilter, 284 | minification_filter: MagnificationMinificationFilter, address_x: TextureAddressMode, 285 | address_y: TextureAddressMode, address_z: TextureAddressMode, mip_levels: u32, 286 | ) -> SarektResult<(BufferImageHandle, BufferOrImage)> { 287 | let mut buffer_store = this 288 | .write() 289 | .expect("Could not unlock BufferStore due to previous panic"); 290 | 291 | let buffer_backend_handle = buffer_store 292 | .buffer_image_loader 293 | .load_image_with_staging_initialization( 294 | pixels, 295 | magnification_filter, 296 | minification_filter, 297 | address_x, 298 | address_y, 299 | address_z, 300 | mip_levels, 301 | )?; 302 | let buffer_or_image = BufferOrImage::new(buffer_backend_handle, ResourceType::Image); 303 | 304 | let inner_key = buffer_store 305 | .loaded_buffers_and_images 306 | .insert(buffer_or_image); 307 | 308 | Ok(( 309 | BufferImageHandle { 310 | inner_key, 311 | resource_type: ResourceType::Image, 312 | buffer_store: Arc::downgrade(this), 313 | }, 314 | buffer_or_image, 315 | )) 316 | } 317 | 318 | /// Returns the handle to buffer or image and the backend buffer or image and 319 | /// memory. 320 | pub(crate) fn create_uninitialized_image( 321 | this: &Arc>, dimensions: (u32, u32), format: ImageDataFormat, 322 | ) -> SarektResult<(BufferImageHandle, BufferOrImage)> { 323 | Self::create_uninitialized_image_msaa(this, dimensions, format, NumSamples::One) 324 | } 325 | 326 | /// Same as above but allows for MSAA to be used, useful when creating 327 | /// internal render targets. 328 | pub(crate) fn create_uninitialized_image_msaa( 329 | this: &Arc>, dimensions: (u32, u32), format: ImageDataFormat, 330 | num_msaa_samples: NumSamples, 331 | ) -> SarektResult<(BufferImageHandle, BufferOrImage)> { 332 | let mut buffer_store = this 333 | .write() 334 | .expect("Could not unlock BufferStore due to previous panic"); 335 | 336 | let buffer_backend_handle = buffer_store 337 | .buffer_image_loader 338 | .create_uninitialized_image(dimensions, format, num_msaa_samples)?; 339 | let buffer_or_image = BufferOrImage::new(buffer_backend_handle, ResourceType::Image); 340 | 341 | let inner_key = buffer_store 342 | .loaded_buffers_and_images 343 | .insert(buffer_or_image); 344 | 345 | Ok(( 346 | BufferImageHandle { 347 | inner_key, 348 | resource_type: ResourceType::Image, 349 | buffer_store: Arc::downgrade(this), 350 | }, 351 | buffer_or_image, 352 | )) 353 | } 354 | 355 | /// Same as `destroy_buffer` but for images. 356 | fn destroy_image(&mut self, inner_key: DefaultKey) -> SarektResult<()> { 357 | let image = self.loaded_buffers_and_images.remove(inner_key); 358 | if image.is_none() { 359 | return Err(SarektError::UnknownResource); 360 | } 361 | 362 | self 363 | .buffer_image_loader 364 | .delete_buffer_or_image(image.unwrap().handle) 365 | } 366 | 367 | /// Retrieves the buffer associated with the handle to be bound etc. 368 | pub(crate) fn get_buffer( 369 | &self, handle: &BufferImageHandle, 370 | ) -> SarektResult<&BufferOrImage> { 371 | if !matches!(handle.resource_type, ResourceType::Buffer(_)) { 372 | return Err(SarektError::IncorrectResourceType); 373 | } 374 | 375 | let buffer = self.loaded_buffers_and_images.get(handle.inner_key); 376 | if let Some(buffer) = buffer { 377 | return Ok(buffer); 378 | } 379 | Err(SarektError::UnknownResource) 380 | } 381 | 382 | /// Same as `get_buffer` but for images. 383 | pub(crate) fn get_image( 384 | &self, handle: &BufferImageHandle, 385 | ) -> SarektResult<&BufferOrImage> { 386 | if !matches!(handle.resource_type, ResourceType::Image) { 387 | return Err(SarektError::IncorrectResourceType); 388 | } 389 | 390 | let image = self.loaded_buffers_and_images.get(handle.inner_key); 391 | if let Some(image) = image { 392 | return Ok(image); 393 | } 394 | Err(SarektError::UnknownResource) 395 | } 396 | 397 | /// Does what it says on the tin, but for all the buffers. See 398 | /// destroy_buffers. 399 | pub(crate) fn destroy_all_images_and_buffers(&mut self) { 400 | for resource in self.loaded_buffers_and_images.iter() { 401 | if let Err(err) = self 402 | .buffer_image_loader 403 | .delete_buffer_or_image(resource.1.handle) 404 | { 405 | warn!( 406 | "Buffer/image not destroyed, maybe it was already? Error: {:?}", 407 | err 408 | ); 409 | } 410 | } 411 | 412 | self.loaded_buffers_and_images.clear(); 413 | } 414 | } 415 | 416 | /// The Buffer in terms of its backend handle and the type of buffer. 417 | #[derive(Copy, Clone, Debug)] 418 | pub struct BufferOrImage { 419 | pub handle: BackendHandle, 420 | pub resource_type: ResourceType, 421 | } 422 | impl BufferOrImage { 423 | fn new(buffer_handle: BackendHandle, buffer_type: ResourceType) -> Self { 424 | Self { 425 | handle: buffer_handle, 426 | resource_type: buffer_type, 427 | } 428 | } 429 | } 430 | 431 | /// What filtering strategy to use on uv texture filtering. 432 | pub enum MagnificationMinificationFilter { 433 | /// Linear interpolation 434 | Linear, 435 | /// Nearest pixel 436 | Nearest, 437 | } 438 | 439 | /// What to do when u/v are greater than extent. 440 | pub enum TextureAddressMode { 441 | Repeat, 442 | MirroredRepeat, 443 | ClampToEdge, 444 | MirroredClampToEdge, 445 | } 446 | -------------------------------------------------------------------------------- /src/renderer/config.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{SarektError, SarektResult}; 2 | use std::convert::TryFrom; 3 | 4 | /// Sarekt configuration. Sane defaults provided (no AA, etc). 5 | #[derive(Builder, Copy, Clone, Debug)] 6 | #[builder(default)] 7 | pub struct Config { 8 | pub requested_width: u32, 9 | pub requested_height: u32, 10 | pub application_details: ApplicationDetails<'static>, 11 | pub engine_details: EngineDetails<'static>, 12 | pub present_mode: PresentMode, 13 | pub msaa_config: MsaaConfig, 14 | } 15 | impl Config { 16 | pub fn builder() -> ConfigBuilder { 17 | ConfigBuilder::default() 18 | } 19 | } 20 | impl<'a> Default for Config { 21 | fn default() -> Self { 22 | Self { 23 | requested_width: 800, 24 | requested_height: 600, 25 | application_details: ApplicationDetails::default(), 26 | engine_details: EngineDetails::default(), 27 | present_mode: PresentMode::default(), 28 | msaa_config: MsaaConfig::default(), 29 | } 30 | } 31 | } 32 | 33 | // ================================================================================ 34 | // Version struct 35 | // ================================================================================ 36 | /// A simple version with major, minor and patch fields for specifying 37 | /// information about your application. 38 | #[derive(Copy, Clone, Debug)] 39 | pub struct Version { 40 | major: u32, 41 | minor: u32, 42 | patch: u32, 43 | } 44 | impl Version { 45 | pub fn new(major: u32, minor: u32, patch: u32) -> Self { 46 | Self { 47 | major, 48 | minor, 49 | patch, 50 | } 51 | } 52 | } 53 | impl Default for Version { 54 | fn default() -> Self { 55 | Self { 56 | major: 0, 57 | minor: 1, 58 | patch: 0, 59 | } 60 | } 61 | } 62 | 63 | // ================================================================================ 64 | // ApplicationDetails Struct 65 | // ================================================================================ 66 | /// Application Details and version for your application. 67 | #[derive(Copy, Clone, Debug)] 68 | pub struct ApplicationDetails<'a> { 69 | pub name: &'a str, 70 | pub version: Version, 71 | } 72 | impl<'a> ApplicationDetails<'a> { 73 | pub fn new(name: &'a str, version: Version) -> Self { 74 | Self { name, version } 75 | } 76 | 77 | /// Get Major Minor Patch in a single u32. 78 | pub fn get_u32_version(self) -> u32 { 79 | ash::vk::make_version(self.version.major, self.version.minor, self.version.patch) 80 | } 81 | } 82 | impl<'a> Default for ApplicationDetails<'a> { 83 | fn default() -> Self { 84 | Self { 85 | name: "Nameless Application", 86 | version: Version::new(0, 1, 0), 87 | } 88 | } 89 | } 90 | 91 | // ================================================================================ 92 | // EngineDetails Struct 93 | // ================================================================================ 94 | /// Application Details and version for your engine. 95 | #[derive(Copy, Clone, Debug)] 96 | pub struct EngineDetails<'a> { 97 | pub name: &'a str, 98 | pub version: Version, 99 | } 100 | impl<'a> EngineDetails<'a> { 101 | pub fn new(name: &'a str, version: Version) -> Self { 102 | Self { name, version } 103 | } 104 | 105 | /// Get Major Minor Patch in a single u32. 106 | pub fn get_u32_version(self) -> u32 { 107 | ash::vk::make_version(self.version.major, self.version.minor, self.version.patch) 108 | } 109 | } 110 | impl<'a> Default for EngineDetails<'a> { 111 | fn default() -> Self { 112 | Self { 113 | name: "Nameless Engine", 114 | version: Version::new(0, 1, 0), 115 | } 116 | } 117 | } 118 | 119 | /// Determines Present mode, default is Mailbox if possible to allow for 120 | /// framerate equal to screen refresh while continuing to draw. 121 | #[derive(Copy, Clone, Debug)] 122 | pub enum PresentMode { 123 | Immediate, 124 | Mailbox, 125 | Fifo, 126 | } 127 | impl Default for PresentMode { 128 | fn default() -> PresentMode { 129 | PresentMode::Mailbox 130 | } 131 | } 132 | 133 | /// Configuration for MSAA. 134 | /// TODO(issue#32) SSAA. 135 | /// TODO(issue#33) other AA styles (TXAA?). 136 | #[derive(Copy, Clone, Debug, Default)] 137 | pub struct MsaaConfig { 138 | pub samples: NumSamples, 139 | pub min_sample_shading: Option, 140 | } 141 | impl MsaaConfig { 142 | pub fn new(samples: NumSamples, min_sample_shading: Option) -> MsaaConfig { 143 | MsaaConfig { 144 | samples, 145 | min_sample_shading, 146 | } 147 | } 148 | } 149 | 150 | #[derive(Copy, Clone, Debug)] 151 | pub enum NumSamples { 152 | One, 153 | Two, 154 | Four, 155 | Eight, 156 | } 157 | impl Default for NumSamples { 158 | fn default() -> NumSamples { 159 | NumSamples::One 160 | } 161 | } 162 | impl TryFrom for NumSamples { 163 | type Error = SarektError; 164 | 165 | fn try_from(n: u8) -> SarektResult { 166 | match n { 167 | 1 => Ok(NumSamples::One), 168 | 2 => Ok(NumSamples::Two), 169 | 4 => Ok(NumSamples::Four), 170 | 8 => Ok(NumSamples::Eight), 171 | _ => Err(SarektError::UnsupportedMsaa( 172 | "Not a power of two less than or equal to 8", 173 | )), 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /src/renderer/drawable_object.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::{ 4 | error::SarektResult, 5 | renderer::{ 6 | buffers_and_images::{ 7 | BackendHandleTrait, BufferAndImageLoader, BufferImageHandle, UniformBufferHandle, 8 | }, 9 | vertex_bindings::DefaultForwardShaderLayout, 10 | Renderer, VulkanRenderer, 11 | }, 12 | }; 13 | 14 | /// The object that is passed to Drawer's draw method. Contains all the 15 | /// necessary information to perform a draw command. 16 | /// 17 | /// vertex_buffer is the list of vertices for the mesh to draw, index_buffer is 18 | /// optional and contains the order of indices to make the mesh in the vertex 19 | /// buffer, and uniform_buffer contains the uniform data for the associated 20 | /// shaders/pipeline. 21 | /// 22 | /// This struct is constructed using references and the lifetime specifications 23 | /// will not allow this class to outlive them. 24 | pub struct DrawableObject< 25 | 'a, 26 | 'b, 27 | 'c, 28 | 'd, 29 | R: Renderer = VulkanRenderer, 30 | DescriptorLayoutStruct: Sized + Copy = DefaultForwardShaderLayout, 31 | > where 32 | R::BL: BufferAndImageLoader, 33 | ::BackendHandle: BackendHandleTrait + Copy + Debug, 34 | { 35 | pub(crate) vertex_buffer: ::BackendHandle, 36 | pub(crate) index_buffer: Option<::BackendHandle>, 37 | pub(crate) uniform_buffer: ::UniformBufferDataHandle, 38 | pub(crate) texture_image: Option<::BackendHandle>, 39 | 40 | _vertex_marker: std::marker::PhantomData<&'a BufferImageHandle>, 41 | _index_marker: std::marker::PhantomData<&'b BufferImageHandle>, 42 | _uniform_marker: std::marker::PhantomData<&'c BufferImageHandle>, 43 | _texture_image_marker: std::marker::PhantomData<&'d BufferImageHandle>, 44 | 45 | _uniform_type: std::marker::PhantomData, 46 | } 47 | impl<'a, 'b, 'c, 'd, R: Renderer, DescriptorLayoutStruct: Sized + Copy> 48 | DrawableObject<'a, 'b, 'c, 'd, R, DescriptorLayoutStruct> 49 | where 50 | R::BL: BufferAndImageLoader, 51 | ::BackendHandle: BackendHandleTrait + Copy + Debug, 52 | { 53 | pub fn builder<'r>( 54 | renderer: &'r R, 55 | ) -> DrawableObjectBuilder<'r, 'a, 'b, 'c, 'd, R, DescriptorLayoutStruct> { 56 | DrawableObjectBuilder { 57 | renderer: Some(renderer), 58 | vertex_buffer: None, 59 | index_buffer: None, 60 | uniform_buffer: None, 61 | texture_image: None, 62 | } 63 | } 64 | 65 | pub fn new( 66 | renderer: &R, vertex_buffer: &'a BufferImageHandle, 67 | index_buffer: Option<&'b BufferImageHandle>, 68 | uniform_buffer_handle: &'c UniformBufferHandle, 69 | texture_image: Option<&'d BufferImageHandle>, 70 | ) -> SarektResult { 71 | let vertex_buffer = renderer.get_buffer(vertex_buffer)?; 72 | let index_buffer = index_buffer 73 | .map(|ibh| renderer.get_buffer(ibh)) 74 | .transpose()?; 75 | let uniform_buffer = renderer.get_uniform_buffer(uniform_buffer_handle)?; 76 | let texture_image = texture_image 77 | .map(|tih| renderer.get_image(tih)) 78 | .transpose()?; 79 | 80 | Ok(Self { 81 | vertex_buffer, 82 | index_buffer, 83 | uniform_buffer, 84 | texture_image, 85 | 86 | _vertex_marker: std::marker::PhantomData, 87 | _index_marker: std::marker::PhantomData, 88 | _uniform_marker: std::marker::PhantomData, 89 | _texture_image_marker: std::marker::PhantomData, 90 | 91 | _uniform_type: std::marker::PhantomData, 92 | }) 93 | } 94 | 95 | // TODO(issue#6) BUFFERS BACKLOG for UniformBufferHandle/DataHandle can specify 96 | // push_constant type and switch on that in update uniform. 97 | // TODO(issue#7) PERFORMANCE allow setting at offsets/fields in uniform so you 98 | // don't have to copy over the whole thing. 99 | /// Set the value of a uniform in the renderer. 100 | pub fn set_uniform(&self, renderer: &R, data: &DescriptorLayoutStruct) -> SarektResult<()> { 101 | renderer.set_uniform(&self.uniform_buffer, data) 102 | } 103 | } 104 | 105 | #[derive(Copy, Clone)] 106 | pub struct DrawableObjectBuilder< 107 | 'r, 108 | 'a, 109 | 'b, 110 | 'c, 111 | 'd, 112 | R: Renderer, 113 | DescriptorLayoutStruct: Sized + Copy, 114 | > where 115 | R::BL: BufferAndImageLoader, 116 | ::BackendHandle: BackendHandleTrait + Copy + Debug, 117 | { 118 | pub renderer: Option<&'r R>, 119 | pub vertex_buffer: Option<&'a BufferImageHandle>, 120 | pub index_buffer: Option<&'b BufferImageHandle>, 121 | pub uniform_buffer: Option<&'c UniformBufferHandle>, 122 | pub texture_image: Option<&'d BufferImageHandle>, 123 | } 124 | impl<'r, 'a, 'b, 'c, 'd, R: Renderer, DescriptorLayoutStruct: Sized + Copy> 125 | DrawableObjectBuilder<'r, 'a, 'b, 'c, 'd, R, DescriptorLayoutStruct> 126 | where 127 | R::BL: BufferAndImageLoader, 128 | ::BackendHandle: BackendHandleTrait + Copy + Debug, 129 | { 130 | pub fn build(self) -> SarektResult> { 131 | DrawableObject::new( 132 | self.renderer.unwrap(), 133 | self.vertex_buffer.unwrap(), 134 | self.index_buffer, 135 | self.uniform_buffer.unwrap(), 136 | self.texture_image, 137 | ) 138 | } 139 | 140 | pub fn vertex_buffer( 141 | mut self, vertex_buffer: &'a BufferImageHandle, 142 | ) -> DrawableObjectBuilder<'r, 'a, 'b, 'c, 'd, R, DescriptorLayoutStruct> { 143 | self.vertex_buffer = Some(vertex_buffer); 144 | self 145 | } 146 | 147 | pub fn index_buffer(mut self, index_buffer: &'b BufferImageHandle) -> Self { 148 | self.index_buffer = Some(index_buffer); 149 | self 150 | } 151 | 152 | pub fn uniform_buffer( 153 | mut self, uniform_buffer: &'c UniformBufferHandle, 154 | ) -> Self { 155 | self.uniform_buffer = Some(uniform_buffer); 156 | self 157 | } 158 | 159 | pub fn texture_image(mut self, texture_image: &'d BufferImageHandle) -> Self { 160 | self.texture_image = Some(texture_image); 161 | self 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/renderer/mod.rs: -------------------------------------------------------------------------------- 1 | //! This is the main renderer module of Sarekt. 2 | //! 3 | //! As I learn vulkan and rendering better, so too will this documentation 4 | //! improve. For now in order to use Sarekt follow these steps: 5 | //! 1) Create your system window (I reccomened using 6 | //! [winit](https://www.crates.io/crates/winit)) 7 | //! 8 | //! 2) Create the renderer with either `new` or `new_detailed`, passing in your 9 | //! system window. 10 | //! ```no_run 11 | //! use sarekt::renderer::VulkanRenderer; 12 | //! use sarekt::renderer::Renderer; 13 | //! use sarekt::renderer::config::{Config, ApplicationDetails, Version, EngineDetails}; 14 | //! # use std::sync::Arc; 15 | //! # use winit::window::WindowBuilder; 16 | //! # use winit::event_loop::EventLoop; 17 | //! const WIDTH: u32 = 800; 18 | //! const HEIGHT: u32 = 600; 19 | //! let event_loop = EventLoop::new(); 20 | //! let config = Config::builder() 21 | //! .requested_width(WIDTH) 22 | //! .requested_height(HEIGHT) 23 | //! .application_details(ApplicationDetails::new( 24 | //! "Testing App", 25 | //! Version::new(0, 1, 0), 26 | //! )) 27 | //! .engine_details(EngineDetails::new("Test Engine", Version::new(0, 1, 0))) 28 | //! .build() 29 | //! .unwrap(); 30 | //! let window = Arc::new(WindowBuilder::new().build(&event_loop).unwrap()); 31 | //! let renderer = VulkanRenderer::new(window.clone(), config).unwrap(); 32 | //! ``` 33 | //! 34 | //! You may also wish to write something abstracted from which renderer backend 35 | //! you choose, so you can put the renderer in a `Box` 36 | //! 37 | //! 3) That's all you can do. 38 | //! 39 | //! Please take a look at examples for how to use Sarekt, they're the best 40 | //! resource. They are generally built off one another so going in order might 41 | //! be helpful. 42 | //! 43 | //! In terms of loading models, I want to keep that application specific so that 44 | //! nothing is limited by deciscions of the renderer, but example 08 should help 45 | //! get you started. It is also conceivable to implement your own vertex type 46 | //! and shader that does support model loading functions within its members. 47 | //! 48 | //! I hope to support some stuff like: 49 | //! - [x] Actually Rendering something 50 | //! - [x] Actually rendering something of your choosing 51 | //! - [x] Mipmapping, AA. 52 | //! - [ ] Multiple pipeline creation. 53 | //! - [ ] Dynamic lighting using a Phong shader. 54 | //! - [ ] Dynamic lighting using PBR. 55 | //! - [ ] Advanced lighting and shadows. 56 | //! - [ ] Multiple uniform buffers/descriptors for drawable objects. 57 | //! - [ ] Multiple uniform buffers for drawable objects. 58 | //! - [ ] Multithreading. 59 | //! - [ ] Loading spirv shaders and generating internal type information needed 60 | //! for their layout. 61 | //! - [ ] Support Other backends 62 | //! - [ ] Moar. 63 | pub mod buffers_and_images; 64 | pub mod config; 65 | pub mod drawable_object; 66 | pub mod shaders; 67 | pub mod vertex_bindings; 68 | 69 | mod vulkan; 70 | 71 | pub use crate::{ 72 | error::SarektResult, 73 | renderer::shaders::{ShaderBackendHandleTrait, ShaderCode, ShaderLoader}, 74 | }; 75 | pub use shaders::{ShaderHandle, ShaderType}; 76 | pub use vulkan::{ 77 | vulkan_buffer_image_functions::VulkanBufferImageFunctions, vulkan_renderer::VulkanRenderer, 78 | }; 79 | 80 | use crate::{ 81 | image_data::ImageData, 82 | renderer::{ 83 | buffers_and_images::{ 84 | BackendHandleTrait, BufferAndImageLoader, BufferImageHandle, BufferType, 85 | MagnificationMinificationFilter, TextureAddressMode, UniformBufferHandle, 86 | }, 87 | drawable_object::DrawableObject, 88 | vertex_bindings::DescriptorLayoutInfo, 89 | }, 90 | }; 91 | use std::fmt::Debug; 92 | 93 | // ================================================================================ 94 | // Compile Time Constants and Configurations 95 | // ================================================================================ 96 | #[cfg(debug_assertions)] 97 | const IS_DEBUG_MODE: bool = true; 98 | #[cfg(not(debug_assertions))] 99 | const IS_DEBUG_MODE: bool = false; 100 | const ENABLE_VALIDATION_LAYERS: bool = IS_DEBUG_MODE; 101 | 102 | // Wanna know more about what number is good here? [readme](https://software.intel.com/en-us/articles/practical-approach-to-vulkan-part-1) 103 | const MAX_FRAMES_IN_FLIGHT: usize = 2; 104 | 105 | // ================================================================================ 106 | // Renderer Trait 107 | // ================================================================================ 108 | /// This is the trait interface that every backend supports. Used to create 109 | /// [drawers](trait.Drawer.html) for use in other threads (to build up command 110 | /// buffers in parallel), finalize the frame, etc. 111 | /// 112 | /// SL is the [Shader Loader](trait.ShaderLoader.html) for the backing renderer. 113 | /// BL is the [Buffer Loader](trait.BufferLoader.html) for the backing renderer. 114 | pub trait Renderer { 115 | type BL; 116 | type SL; 117 | 118 | // TODO(issue#1) MULTITHREADING should load/get/update functions be part of 119 | // drawer so anyone can do it (within their own pools/queues) 120 | 121 | /// Enables or disables rendering. 122 | fn set_rendering_enabled(&mut self, enabled: bool); 123 | 124 | /// Mark this frame as complete and render it to the target of the renderer 125 | /// when ready. 126 | fn frame(&self) -> SarektResult<()>; 127 | 128 | // TODO(issue#2) PIPELINES create a new pipeline type out of shaders, render 129 | // pass, etc. 130 | 131 | // TODO(issue#3) SHADER get_shader with handle 132 | // TODO(issue#4) SHADER when loading a shader, use spirv-reflect to make sure 133 | // you don't exceed max bound descriptors to use it. 134 | /// Loads a shader and returns a RAII handle to be used for retrieval or 135 | /// pipeline creation. 136 | fn load_shader( 137 | &mut self, spirv: &ShaderCode, shader_type: ShaderType, 138 | ) -> SarektResult> 139 | where 140 | Self::SL: ShaderLoader, 141 | ::SBH: ShaderBackendHandleTrait + Copy + Debug; 142 | 143 | /// Loads a buffer and returns a RAII handle to be used for retrieval. 144 | fn load_buffer( 145 | &mut self, buffer_type: BufferType, buffer: &[BufElem], 146 | ) -> SarektResult> 147 | where 148 | Self::BL: BufferAndImageLoader, 149 | ::BackendHandle: BackendHandleTrait + Copy + Debug; 150 | 151 | /// Gets a buffer given th handle generated when it was loaded (see 152 | /// load_buffer). 153 | fn get_buffer( 154 | &self, handle: &BufferImageHandle, 155 | ) -> SarektResult<::BackendHandle> 156 | where 157 | Self::BL: BufferAndImageLoader, 158 | ::BackendHandle: BackendHandleTrait + Copy + Debug; 159 | 160 | /// Loads a uniform buffer. 161 | fn load_uniform_buffer( 162 | &mut self, buffer: UniformBufElem, 163 | ) -> SarektResult> 164 | where 165 | Self::BL: BufferAndImageLoader, 166 | ::BackendHandle: BackendHandleTrait + Copy + Debug; 167 | 168 | /// Returns a uniform buffer given the handle returned in load_uniform_buffer. 169 | fn get_uniform_buffer( 170 | &self, handle: &UniformBufferHandle, 171 | ) -> SarektResult<::UniformBufferDataHandle> 172 | where 173 | Self::BL: BufferAndImageLoader; 174 | 175 | /// Updates the uniform buffer's contained value. 176 | fn set_uniform( 177 | &self, handle_data: &::UniformBufferDataHandle, 178 | data: &BufElem, 179 | ) -> SarektResult<()> 180 | where 181 | Self::BL: BufferAndImageLoader; 182 | 183 | /// Loads a 32 bit r8b8g8a8 image (texture) into the renderer using a staging 184 | /// buffer. [ImageData](trait.ImageData.html) must be implemented for the 185 | /// type, see its documentation for details. 186 | /// 187 | /// Texture address modes are intents for the backend when uvs are greater 188 | /// than image extent. 189 | /// 190 | /// MagnificationMinificationFilters are intents when 191 | /// texture filtering are necessary (see Vulkan or D3D docs). 192 | /// 193 | /// Mip levels are the number of mipmap levels to generate (see Vulkan/D3D 194 | /// docs). 195 | fn load_image_with_staging_initialization( 196 | &mut self, pixels: impl ImageData, magnification_filter: MagnificationMinificationFilter, 197 | minification_filter: MagnificationMinificationFilter, address_x: TextureAddressMode, 198 | address_y: TextureAddressMode, address_z: TextureAddressMode, mip_levels: u32, 199 | ) -> SarektResult> 200 | where 201 | Self::BL: BufferAndImageLoader, 202 | ::BackendHandle: BackendHandleTrait + Copy + Debug; 203 | 204 | /// Retrieves an image using the handle returned by the `load_image_*` family 205 | /// of functions. 206 | fn get_image( 207 | &self, handle: &BufferImageHandle, 208 | ) -> SarektResult<::BackendHandle> 209 | where 210 | Self::BL: BufferAndImageLoader, 211 | ::BackendHandle: BackendHandleTrait + Copy + Debug; 212 | 213 | /// Handle swapchain out of date, such as window changes. 214 | fn recreate_swapchain(&mut self, width: u32, height: u32) -> SarektResult<()>; 215 | 216 | /// Return the number of frames drawn. 217 | fn get_frame_count(&self) -> u64; 218 | } 219 | 220 | /// Trait that each renderer as well as its secondary drawers (if supported) 221 | /// implement for multi-threading purposes. 222 | pub trait Drawer { 223 | type R; 224 | 225 | fn draw( 226 | &self, object: &DrawableObject, 227 | ) -> SarektResult<()> 228 | where 229 | UniformBufElem: Sized + Copy + DescriptorLayoutInfo, 230 | Self::R: Renderer, 231 | ::BL: BufferAndImageLoader, 232 | <::BL as BufferAndImageLoader>::BackendHandle: 233 | BackendHandleTrait + Copy + Debug; 234 | 235 | // TODO(issue#2) PIPELINE use method select render pass (predefined set?) log 236 | // when pipeline not compatible and dont draw? End previous render pass and 237 | // keep track of last render pass to end it as well. 238 | } 239 | -------------------------------------------------------------------------------- /src/renderer/shaders.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{SarektError, SarektResult}; 2 | 3 | use log::warn; 4 | use slotmap::{DefaultKey, SlotMap}; 5 | use std::{ 6 | fmt::Debug, 7 | sync::{Arc, RwLock}, 8 | }; 9 | 10 | /// A type that can be used to retrieve a shader from the renderer and 11 | /// ShaderStore that will destroy the shader when it goes out of scope. 12 | /// 13 | /// As always, In order to pass this around with multiple ownership, wrap it in 14 | /// an Arc. 15 | #[derive(Clone)] 16 | pub struct ShaderHandle 17 | where 18 | SL: ShaderLoader, 19 | SL::SBH: ShaderBackendHandleTrait + Copy + Debug, 20 | { 21 | inner_key: DefaultKey, 22 | shader_store: Arc>>, 23 | } 24 | impl Drop for ShaderHandle 25 | where 26 | SL: ShaderLoader, 27 | SL::SBH: ShaderBackendHandleTrait + Copy + Debug, 28 | { 29 | fn drop(&mut self) { 30 | let mut shader_store_guard = self 31 | .shader_store 32 | .write() 33 | .expect("Could not unlock ShaderStore due to previous panic"); 34 | match shader_store_guard.destroy_shader(self.inner_key) { 35 | // Already deleted, likely shutting down. Nothing to do. 36 | Err(SarektError::UnknownShader) => {} 37 | Err(e) => warn!("shader not destroyed, maybe it was already? Error: {:?}", e), 38 | Ok(()) => {} 39 | } 40 | } 41 | } 42 | 43 | /// The backing type of the shader, for vulkan this is spirv, gl just uses glsl, 44 | /// D3D hlsl, etc. 45 | pub enum ShaderCode<'a> { 46 | Spirv(&'a [u32]), 47 | Glsl(&'a str), // TODO(issue#22) support GLSL 48 | } 49 | 50 | /// The type of shader (vertex, fragment, etc). 51 | #[derive(Copy, Clone, Debug)] 52 | pub enum ShaderType { 53 | Vertex, 54 | Fragment, 55 | Geometry, 56 | Tesselation, 57 | Compute, 58 | } 59 | 60 | /// A marker to note that the type used is a Shader backend handle (eg 61 | /// vkShaderModule for Vulkan). 62 | /// 63 | /// Unsafe because: 64 | /// This must specifically be the handle used to delete your 65 | /// shader in the driver in [ShaderLoader](trait.ShaderLoader.html). 66 | pub unsafe trait ShaderBackendHandleTrait: Copy {} 67 | 68 | /// A trait used by each implementation in order to load shaders in their own 69 | /// way. 70 | /// 71 | /// Unsafe because: 72 | /// * The lifetimes of the functions to create them (which are 73 | /// usually dynamically loaded) must outlive the Loader itself. 74 | /// 75 | /// * SBH must be an implementer of 76 | /// [ShaderBackendHandle](trait.ShaderBackendHandle.html) 77 | /// 78 | /// * It is the responsibility of the implementor to drop anything loaded using 79 | /// delete_shader cleanly on all elements, if the ShaderHandle dropping 80 | /// doesn't handle it. 81 | pub unsafe trait ShaderLoader { 82 | type SBH; 83 | /// Loads the shader using underlying mechanism. 84 | fn load_shader(&self, code: &ShaderCode) -> SarektResult; 85 | /// Deletes the shader using underlying mechanism. 86 | fn delete_shader(&self, shader: Self::SBH) -> SarektResult<()>; 87 | } 88 | 89 | /// A storage for all shaders to be loaded or destroyed from. Returns a handle 90 | /// that can be used to retrieve the associated shader, which includes it's type 91 | /// and it's handle to whichever backend you're using. 92 | pub struct ShaderStore 93 | where 94 | SL: ShaderLoader, 95 | SL::SBH: ShaderBackendHandleTrait + Copy + Debug, 96 | { 97 | loaded_shaders: SlotMap>, 98 | shader_loader: SL, 99 | } 100 | 101 | impl ShaderStore 102 | where 103 | SL: ShaderLoader, 104 | SL::SBH: ShaderBackendHandleTrait + Copy + Debug, 105 | { 106 | /// Create with a group of methods to load/destroy shaders. 107 | pub(crate) fn new(shader_loader: SL) -> Self { 108 | Self { 109 | loaded_shaders: SlotMap::new(), 110 | shader_loader, 111 | } 112 | } 113 | 114 | /// Load a shader into the driver and return a handle. 115 | pub(crate) fn load_shader( 116 | this: &Arc>, code: &ShaderCode, shader_type: ShaderType, 117 | ) -> SarektResult> { 118 | let mut shader_store = this 119 | .write() 120 | .expect("Could not unlock ShaderStore due to previous panic"); 121 | 122 | let shader_backend_handle = shader_store.shader_loader.load_shader(code)?; 123 | let inner_key = shader_store 124 | .loaded_shaders 125 | .insert(Shader::new(shader_backend_handle, shader_type)); 126 | 127 | Ok(ShaderHandle { 128 | inner_key, 129 | shader_store: this.clone(), 130 | }) 131 | } 132 | 133 | /// Using the handle, destroy the shader from the backend. 134 | fn destroy_shader(&mut self, inner_key: DefaultKey) -> SarektResult<()> { 135 | let shader = self.loaded_shaders.remove(inner_key); 136 | if shader.is_none() { 137 | return Err(SarektError::UnknownShader); 138 | } 139 | self 140 | .shader_loader 141 | .delete_shader(shader.unwrap().shader_handle)?; 142 | Ok(()) 143 | } 144 | 145 | /// Destroys all the shaders. Unsafe because any outstanding handles will not 146 | /// result in errors when they drop, so they must be forgotten. 147 | pub(crate) unsafe fn destroy_all_shaders(&mut self) { 148 | for shader in self.loaded_shaders.iter() { 149 | if let Err(err) = self.shader_loader.delete_shader(shader.1.shader_handle) { 150 | warn!( 151 | "Shader not destroyed, maybe it was already? Error: {:?}", 152 | err 153 | ); 154 | } 155 | } 156 | 157 | self.loaded_shaders.clear(); 158 | } 159 | 160 | /// Retrieve a loaded shader to be used in pipeline construction, etc. 161 | pub(crate) fn get_shader(&self, handle: &ShaderHandle) -> SarektResult<&Shader> { 162 | let shader = self.loaded_shaders.get(handle.inner_key); 163 | if let Some(shader) = shader { 164 | return Ok(shader); 165 | } 166 | Err(SarektError::UnknownShader) 167 | } 168 | } 169 | 170 | /// The shader in it's backend type along with the type of shader itself (vertex 171 | /// etc). 172 | #[derive(Copy, Clone, Debug)] 173 | pub(crate) struct Shader { 174 | pub shader_handle: SBH, 175 | pub shader_type: ShaderType, 176 | } 177 | 178 | impl Shader 179 | where 180 | SBH: ShaderBackendHandleTrait + Copy, 181 | { 182 | fn new(shader_module: SBH, shader_type: ShaderType) -> Self { 183 | Self { 184 | shader_handle: shader_module, 185 | shader_type, 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/renderer/vertex_bindings.rs: -------------------------------------------------------------------------------- 1 | use crate::error::SarektResult; 2 | use ultraviolet as uv; 3 | 4 | /// A trait that provides a static function that generates backend specific 5 | /// vertex bindings. This is mainly provided out of convenience and would need 6 | /// to be custom defined for each backend otherwise. It is possible to seperate 7 | /// them and provide a generic implementation for get_binding_descriptions if 8 | /// you're only using one binding. 9 | /// 10 | /// BVB is backend specific vertex bindings object. 11 | /// BVA is backend specific vertex attributes object. 12 | /// 13 | /// Unsafe because one must only bring one in scope and understand how to define 14 | /// vertex bindings for the specific backend for Sarekt, which requires 15 | /// understanding how the layouts and bindings are laid out in the shaders, or 16 | /// creating ones own shaders and understanding layouts and bindings in them for 17 | /// their backend. 18 | pub unsafe trait VertexBindings { 19 | type BVA; 20 | type BVB; 21 | 22 | /// Return binding descriptions for the implemented type in the specific 23 | /// backend format. Bindings are bound during commands or command buffers 24 | /// and attach texture/image buffers to a binding location in the shader. 25 | fn get_binding_description() -> Self::BVB; 26 | 27 | /// Same as get_binding_description but for vertex attribute descriptions. 28 | fn get_attribute_descriptions() -> Vec; 29 | } 30 | 31 | /// Input vertices to the sarekt_forward shader set. 32 | #[repr(C)] 33 | #[derive(Copy, Clone, Debug)] 34 | pub struct DefaultForwardShaderVertex { 35 | pub position: uv::Vec3, 36 | pub color: uv::Vec3, 37 | pub texture_coordinates: uv::Vec2, 38 | } 39 | impl DefaultForwardShaderVertex { 40 | /// For use when there is no intended texture use. 41 | pub fn without_uv(pos: &[f32; 3], color: &[f32; 3]) -> Self { 42 | Self::new(pos, color, &[0.0f32, 0.0f32]) 43 | } 44 | 45 | pub fn new_with_texture(pos: &[f32; 3], texture_coordinates: &[f32; 2]) -> Self { 46 | Self { 47 | position: uv::Vec3::from(pos), 48 | color: uv::Vec3::new(1f32, 1f32, 1f32), 49 | texture_coordinates: uv::Vec2::from(texture_coordinates), 50 | } 51 | } 52 | 53 | pub fn new(pos: &[f32; 3], color: &[f32; 3], texture_coordinates: &[f32; 2]) -> Self { 54 | Self { 55 | position: uv::Vec3::from(pos), 56 | color: uv::Vec3::from(color), 57 | texture_coordinates: uv::Vec2::from(texture_coordinates), 58 | } 59 | } 60 | } 61 | 62 | /// Returns the descriptor layouts for the specific backend. These contain 63 | /// information such as which bindings to attach each part of uniform to in the 64 | /// shader, which stages they are used, etc. 65 | pub unsafe trait DescriptorLayoutInfo { 66 | type BackendDescriptorSetLayoutBindings; 67 | 68 | /// Gets the information for the shaders/pipeline in the backend for how to 69 | /// bind descriptors to them. 70 | fn get_descriptor_set_layout_bindings() -> Self::BackendDescriptorSetLayoutBindings; 71 | 72 | /// Gets the information needed in order to allocate/bind descriptors in the 73 | /// backend for uniforms. 74 | fn get_bind_uniform_info() -> SarektResult; 75 | 76 | /// Gets the information needed to allocate/bind descroptors in teh backend 77 | /// for textures. 78 | fn get_bind_texture_info() -> SarektResult; 79 | } 80 | #[derive(Clone, Debug)] 81 | /// Contains information needed by various backends to configure their 82 | /// descriptors for uniforms. 83 | pub struct BindUniformInfo { 84 | pub offset: u64, 85 | pub range: u64, 86 | pub bindings: Vec, 87 | } 88 | 89 | /// Information needed by backend to bind textures. 90 | pub struct BindTextureInfo { 91 | pub bindings: Vec, 92 | } 93 | 94 | /// Input uniforms to the sarekt_forward shader set. 95 | /// 96 | /// Note that ***alignment matters*** Please see the specification for your 97 | /// specific backend. 98 | /// 99 | /// The initial backend is Vulkan, which is why booleans are u32, all scalars 100 | /// need to be aligned to 4 bytes. 101 | #[repr(C)] 102 | #[derive(Copy, Clone, Debug)] 103 | pub struct DefaultForwardShaderLayout { 104 | /// The model view projection matrix to apply to the containing 105 | /// DrawableObject. 106 | pub mvp: uv::Mat4, 107 | pub enable_color_mixing: u32, 108 | pub enable_texture_mixing: u32, 109 | } 110 | impl DefaultForwardShaderLayout { 111 | pub fn new(mvp: uv::Mat4, enable_color_mixing: bool, enable_texture_mixing: bool) -> Self { 112 | Self { 113 | mvp, 114 | enable_color_mixing: u32::from(enable_color_mixing), 115 | enable_texture_mixing: u32::from(enable_texture_mixing), 116 | } 117 | } 118 | } 119 | impl Default for DefaultForwardShaderLayout { 120 | fn default() -> Self { 121 | DefaultForwardShaderLayout { 122 | mvp: uv::Mat4::identity(), 123 | enable_color_mixing: 0u32, 124 | enable_texture_mixing: 1u32, 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/renderer/vulkan/images.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | 3 | #[derive(Copy, Clone, Debug)] 4 | pub struct ImageAndView { 5 | pub image: vk::Image, 6 | pub view: vk::ImageView, 7 | } 8 | impl ImageAndView { 9 | /// Creates an image and imageview pairing, with a Drop implementation. 10 | /// Unsafe because you must clean up the vk::Image and vk::ImageView still. 11 | pub unsafe fn new(image: vk::Image, view: vk::ImageView) -> Self { 12 | Self { image, view } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/renderer/vulkan/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::{SarektError, SarektResult}, 3 | image_data::ImageDataFormat, 4 | renderer::{ 5 | config::NumSamples, vulkan::vulkan_shader_functions::VulkanShaderFunctions, ShaderHandle, 6 | }, 7 | }; 8 | use ash::vk; 9 | use std::convert::TryFrom; 10 | 11 | pub mod images; 12 | pub mod queues; 13 | pub mod vulkan_buffer_image_functions; 14 | pub mod vulkan_renderer; 15 | pub mod vulkan_shader_functions; 16 | pub mod vulkan_vertex_bindings; 17 | 18 | pub type VulkanShaderHandle = ShaderHandle; 19 | 20 | impl From for vk::SampleCountFlags { 21 | fn from(num_samples: NumSamples) -> vk::SampleCountFlags { 22 | match num_samples { 23 | NumSamples::One => vk::SampleCountFlags::TYPE_1, 24 | NumSamples::Two => vk::SampleCountFlags::TYPE_2, 25 | NumSamples::Four => vk::SampleCountFlags::TYPE_4, 26 | NumSamples::Eight => vk::SampleCountFlags::TYPE_8, 27 | } 28 | } 29 | } 30 | 31 | impl From for vk::Format { 32 | fn from(image_data_format: ImageDataFormat) -> vk::Format { 33 | match image_data_format { 34 | ImageDataFormat::R8G8B8Srgb => vk::Format::R8G8B8_SRGB, 35 | ImageDataFormat::B8G8R8Srgb => vk::Format::B8G8R8_SRGB, 36 | ImageDataFormat::B8G8R8A8Srgb => vk::Format::B8G8R8A8_SRGB, 37 | ImageDataFormat::R8G8B8A8Srgb => vk::Format::R8G8B8A8_SRGB, 38 | 39 | ImageDataFormat::R8G8B8Unorm => vk::Format::R8G8B8_UNORM, 40 | ImageDataFormat::B8G8R8Unorm => vk::Format::B8G8R8_UNORM, 41 | ImageDataFormat::B8G8R8A8Unorm => vk::Format::B8G8R8A8_UNORM, 42 | ImageDataFormat::R8G8B8A8Unorm => vk::Format::R8G8B8A8_UNORM, 43 | 44 | ImageDataFormat::RGB16Unorm => vk::Format::R5G6B5_UNORM_PACK16, 45 | ImageDataFormat::RGBA16Unorm => vk::Format::R5G5B5A1_UNORM_PACK16, 46 | 47 | ImageDataFormat::D32Float => vk::Format::D32_SFLOAT, 48 | ImageDataFormat::D32FloatS8 => vk::Format::D32_SFLOAT_S8_UINT, 49 | ImageDataFormat::D24NormS8 => vk::Format::D24_UNORM_S8_UINT, 50 | } 51 | } 52 | } 53 | 54 | impl TryFrom for ImageDataFormat { 55 | type Error = SarektError; 56 | 57 | fn try_from(format: vk::Format) -> SarektResult { 58 | match format { 59 | vk::Format::R8G8B8_SRGB => Ok(ImageDataFormat::R8G8B8Srgb), 60 | vk::Format::B8G8R8_SRGB => Ok(ImageDataFormat::B8G8R8Srgb), 61 | vk::Format::B8G8R8A8_SRGB => Ok(ImageDataFormat::B8G8R8A8Srgb), 62 | vk::Format::R8G8B8A8_SRGB => Ok(ImageDataFormat::R8G8B8A8Srgb), 63 | 64 | vk::Format::R8G8B8_UNORM => Ok(ImageDataFormat::R8G8B8Unorm), 65 | vk::Format::B8G8R8_UNORM => Ok(ImageDataFormat::B8G8R8Unorm), 66 | vk::Format::B8G8R8A8_UNORM => Ok(ImageDataFormat::B8G8R8A8Unorm), 67 | vk::Format::R8G8B8A8_UNORM => Ok(ImageDataFormat::R8G8B8A8Unorm), 68 | 69 | vk::Format::R5G6B5_UNORM_PACK16 => Ok(ImageDataFormat::RGB16Unorm), 70 | vk::Format::R5G5B5A1_UNORM_PACK16 => Ok(ImageDataFormat::RGBA16Unorm), 71 | 72 | vk::Format::D32_SFLOAT => Ok(ImageDataFormat::D32Float), 73 | vk::Format::D32_SFLOAT_S8_UINT => Ok(ImageDataFormat::D32FloatS8), 74 | vk::Format::D24_UNORM_S8_UINT => Ok(ImageDataFormat::D24NormS8), 75 | 76 | _ => Err(SarektError::UnsupportedImageFormat), 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/renderer/vulkan/queues.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | 3 | #[derive(Debug, Default, Clone)] 4 | pub struct QueueFamilyIndices { 5 | pub graphics_queue_family: Option, 6 | pub presentation_queue_family: Option, 7 | pub transfer_queue_family: Option, 8 | } 9 | impl QueueFamilyIndices { 10 | // TODO(issue#9) OFFSCREEN is_complete_for_offscreen also that doesn't need 11 | // presentation. 12 | pub fn is_complete(&self) -> bool { 13 | self.graphics_queue_family.is_some() 14 | && self.presentation_queue_family.is_some() 15 | && self.transfer_queue_family.is_some() 16 | } 17 | 18 | /// Returns all the queue indices as an array for easily handing over to 19 | /// Vulkan. Returns None if not complete 20 | pub fn as_vec(&self) -> Option> { 21 | // TODO(issue#9) OFFSCREEN is_complete_for_offscreen also that doesn't need 22 | // presentation. 23 | if !self.is_complete() { 24 | return None; 25 | } 26 | 27 | Some(vec![ 28 | self.graphics_queue_family.unwrap(), 29 | // TODO(issue#9) OFFSCREEN no presentation if it is none since that's allowed. 30 | self.presentation_queue_family.unwrap(), 31 | self.transfer_queue_family.unwrap(), 32 | ]) 33 | } 34 | } 35 | 36 | pub struct Queues { 37 | pub graphics_queue: vk::Queue, 38 | pub presentation_queue: vk::Queue, 39 | pub transfer_queue: vk::Queue, 40 | } 41 | impl Queues { 42 | pub fn new( 43 | graphics_queue: vk::Queue, presentation_queue: vk::Queue, transfer_queue: vk::Queue, 44 | ) -> Self { 45 | Queues { 46 | graphics_queue, 47 | presentation_queue, 48 | transfer_queue, 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/renderer/vulkan/vulkan_renderer/base_pipeline_bundle.rs: -------------------------------------------------------------------------------- 1 | use crate::renderer::{ 2 | vulkan::{ 3 | vulkan_renderer::render_attachments::{DepthAttachment, ResolveAttachment}, 4 | vulkan_shader_functions::VulkanShaderFunctions, 5 | VulkanShaderHandle, 6 | }, 7 | ShaderHandle, 8 | }; 9 | use ash::vk; 10 | 11 | /// Just a pipeline bundle to help return when creating the base pipeline. 12 | pub struct BasePipelineBundle { 13 | pub pipeline: vk::Pipeline, 14 | pub pipeline_layout: vk::PipelineLayout, 15 | pub pipeline_create_info: vk::GraphicsPipelineCreateInfo, 16 | pub resolve_attachment: Option, 17 | pub depth_resources: Option, 18 | pub descriptor_set_layouts: Option>, 19 | pub vertex_shader_handle: Option, 20 | pub fragment_shader_handle: Option, 21 | } 22 | impl BasePipelineBundle { 23 | pub fn new( 24 | pipeline: vk::Pipeline, pipeline_layout: vk::PipelineLayout, 25 | pipeline_create_info: vk::GraphicsPipelineCreateInfo, 26 | descriptor_set_layouts: Vec, 27 | resolve_attachment: Option, depth_resources: DepthAttachment, 28 | vertex_shader_handle: ShaderHandle, 29 | fragment_shader_handle: ShaderHandle, 30 | ) -> Self { 31 | Self { 32 | pipeline, 33 | pipeline_layout, 34 | pipeline_create_info, 35 | resolve_attachment, 36 | depth_resources: Some(depth_resources), 37 | descriptor_set_layouts: Some(descriptor_set_layouts), 38 | vertex_shader_handle: Some(vertex_shader_handle), 39 | fragment_shader_handle: Some(fragment_shader_handle), 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/renderer/vulkan/vulkan_renderer/debug_utils_ext.rs: -------------------------------------------------------------------------------- 1 | use ash::{extensions::ext::DebugUtils, vk, Entry, Instance}; 2 | use log::{debug, error, warn}; 3 | use static_assertions::assert_impl_all; 4 | use std::{ 5 | ffi::CStr, 6 | os::raw::c_void, 7 | pin::Pin, 8 | sync::{ 9 | atomic::{AtomicUsize, Ordering}, 10 | Arc, 11 | }, 12 | }; 13 | 14 | /// The debug callbacks for vulkan that are enabled when in debug mode. Called 15 | /// by validation layers (mostly). Keeps track of errors etc for unit tests and logs all errors with [the log crate](https://www.crates.io/crate/log). 16 | #[repr(C)] 17 | pub struct DebugUtilsAndMessenger { 18 | pub debug_utils: DebugUtils, 19 | pub messenger: vk::DebugUtilsMessengerEXT, 20 | pub debug_user_data: Pin>, 21 | } 22 | impl DebugUtilsAndMessenger { 23 | /// Creates a new Debug Extension for vulkan with the associated user data for 24 | /// the debug callback, if provided. 25 | /// 26 | /// This user data must be Sync, which is garunteed by Arc. 27 | pub fn new( 28 | entry: &Entry, instance: &Instance, severity_flags: vk::DebugUtilsMessageSeverityFlagsEXT, 29 | type_flags: vk::DebugUtilsMessageTypeFlagsEXT, 30 | debug_user_data: Option>>, 31 | ) -> Self { 32 | let debug_user_data = if let Some(debug_user_data) = debug_user_data { 33 | debug_user_data 34 | } else { 35 | Arc::pin(DebugUserData::new()) 36 | }; 37 | 38 | let debug_user_data_ptr = 39 | unsafe { Arc::into_raw(Pin::into_inner_unchecked(debug_user_data.clone())) as *mut c_void }; 40 | 41 | let debug_utils = DebugUtils::new(entry, instance); 42 | let messenger_ci = vk::DebugUtilsMessengerCreateInfoEXT::builder() 43 | .message_severity(severity_flags) 44 | .message_type(type_flags) 45 | .pfn_user_callback(Some(Self::debug_callback)) 46 | .user_data(debug_user_data_ptr) 47 | .build(); 48 | let messenger = unsafe { 49 | debug_utils 50 | .create_debug_utils_messenger(&messenger_ci, None) 51 | .expect("Could not create debug utils messenger") 52 | }; 53 | 54 | DebugUtilsAndMessenger { 55 | debug_utils, 56 | messenger, 57 | debug_user_data, 58 | } 59 | } 60 | 61 | /// It is invariant in the vulkan renderer setup that p_user_data is of type 62 | /// DebugUserData, it is set up in new. 63 | pub unsafe extern "system" fn debug_callback( 64 | message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, 65 | _message_types: vk::DebugUtilsMessageTypeFlagsEXT, 66 | p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT, p_user_data: *mut c_void, 67 | ) -> u32 { 68 | // Transmute the user data to its appropriate type, but not a box (we don't want 69 | // to drop it), if it exists. 70 | let user_data: Option<&mut DebugUserData> = if p_user_data.is_null() { 71 | Some(&mut *(p_user_data as *mut DebugUserData)) 72 | } else { 73 | None 74 | }; 75 | 76 | // Update user data if necessary. 77 | if let Some(user_data) = user_data { 78 | match message_severity { 79 | vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => { 80 | user_data.error_count.fetch_add(1, Ordering::SeqCst); 81 | } 82 | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => { 83 | user_data.warning_count.fetch_add(1, Ordering::SeqCst); 84 | } 85 | vk::DebugUtilsMessageSeverityFlagsEXT::INFO => { 86 | user_data.info_count.fetch_add(1, Ordering::SeqCst); 87 | } 88 | _ => {} 89 | } 90 | } 91 | 92 | // Log messages. 93 | let message = CStr::from_ptr((*p_callback_data).p_message as *const i8) 94 | .to_str() 95 | .unwrap(); 96 | match message_severity { 97 | vk::DebugUtilsMessageSeverityFlagsEXT::ERROR => { 98 | error!("Validation Error! {}", message); 99 | } 100 | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING => { 101 | warn!("Validation Warning! {}", message); 102 | } 103 | vk::DebugUtilsMessageSeverityFlagsEXT::INFO => { 104 | debug!("Validation Info {}", message); 105 | } 106 | vk::DebugUtilsMessageSeverityFlagsEXT::VERBOSE => { 107 | debug!("Validation Verbose {}", message); 108 | } 109 | _ => {} 110 | } 111 | 112 | vk::FALSE // Returning false indicates no error in callback. 113 | } 114 | } 115 | 116 | assert_impl_all!(DebugUserData: Sync); 117 | #[repr(C)] 118 | pub struct DebugUserData { 119 | info_count: AtomicUsize, 120 | warning_count: AtomicUsize, 121 | error_count: AtomicUsize, 122 | } 123 | impl DebugUserData { 124 | pub fn new() -> Self { 125 | Self::default() 126 | } 127 | 128 | /// Returns the number of errors, warning, and info messages created by the 129 | /// debug layers. 130 | #[allow(dead_code)] 131 | pub fn get_error_counts(&self) -> DebugUserDataCopy { 132 | DebugUserDataCopy { 133 | info_count: self.info_count.load(Ordering::SeqCst), 134 | warning_count: self.warning_count.load(Ordering::SeqCst), 135 | error_count: self.error_count.load(Ordering::SeqCst), 136 | } 137 | } 138 | } 139 | impl Default for DebugUserData { 140 | fn default() -> Self { 141 | Self { 142 | info_count: AtomicUsize::new(0), 143 | warning_count: AtomicUsize::new(0), 144 | error_count: AtomicUsize::new(0), 145 | } 146 | } 147 | } 148 | 149 | #[derive(Debug)] 150 | pub struct DebugUserDataCopy { 151 | pub info_count: usize, 152 | pub warning_count: usize, 153 | pub error_count: usize, 154 | } 155 | -------------------------------------------------------------------------------- /src/renderer/vulkan/vulkan_renderer/draw_synchronization.rs: -------------------------------------------------------------------------------- 1 | use crate::{error::SarektResult, renderer::MAX_FRAMES_IN_FLIGHT}; 2 | use ash::{version::DeviceV1_0, vk, Device}; 3 | use log::info; 4 | use std::{cell::Cell, sync::Arc}; 5 | 6 | /// Draw synchronization primitives for frames in flight and synchronizing 7 | /// between acquiring images, presenting them. 8 | /// Also contains some helper methods. 9 | pub struct DrawSynchronization { 10 | logical_device: Arc, 11 | acquire_fence: vk::Fence, 12 | image_available_semaphores: Vec, 13 | render_finished_semaphores: Vec, 14 | frame_fences: Vec, 15 | 16 | // Unowned tracking references to in_flight_fences. This is to track which in flight fences 17 | // correspond to which images that are in flight. 18 | image_to_frame_fence: Vec>, 19 | } 20 | impl DrawSynchronization { 21 | pub fn new(logical_device: Arc, num_render_targets: usize) -> SarektResult { 22 | let semaphore_ci = vk::SemaphoreCreateInfo::default(); 23 | let fence_ci = vk::FenceCreateInfo::builder() 24 | .flags(vk::FenceCreateFlags::SIGNALED) 25 | .build(); 26 | let mut image_available_semaphores = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT); 27 | let mut render_finished_semaphores = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT); 28 | let mut in_flight_fences = Vec::with_capacity(MAX_FRAMES_IN_FLIGHT); 29 | for _ in 0..MAX_FRAMES_IN_FLIGHT { 30 | unsafe { 31 | image_available_semaphores.push(logical_device.create_semaphore(&semaphore_ci, None)?); 32 | render_finished_semaphores.push(logical_device.create_semaphore(&semaphore_ci, None)?); 33 | in_flight_fences.push(logical_device.create_fence(&fence_ci, None)?); 34 | } 35 | } 36 | 37 | let acquire_fence = unsafe { logical_device.create_fence(&fence_ci, None)? }; 38 | 39 | Ok(Self { 40 | logical_device, 41 | acquire_fence, 42 | image_available_semaphores, 43 | render_finished_semaphores, 44 | frame_fences: in_flight_fences, 45 | image_to_frame_fence: vec![Cell::new(vk::Fence::null()); num_render_targets], 46 | }) 47 | } 48 | 49 | /// Returns fence used for image acquisition for swapchain. 50 | pub fn get_acquire_fence(&self) -> vk::Fence { 51 | self.acquire_fence 52 | } 53 | 54 | /// Waits for the image and its associated objects to be ready to be written 55 | /// to. 56 | pub fn wait_for_acquire_fence(&self) -> SarektResult<()> { 57 | unsafe { 58 | Ok( 59 | self 60 | .logical_device 61 | .wait_for_fences(&[self.acquire_fence], true, u64::max_value())?, 62 | ) 63 | } 64 | } 65 | 66 | pub fn reset_acquire_fence(&self) -> SarektResult<()> { 67 | unsafe { Ok(self.logical_device.reset_fences(&[self.acquire_fence])?) } 68 | } 69 | 70 | /// Returns fence associated with swapchain image, with bounds checking. 71 | pub fn get_image_fence(&self, image_index: usize) -> vk::Fence { 72 | if image_index >= self.image_to_frame_fence.len() { 73 | panic!("Invalid input! image_index {}", image_index); 74 | } 75 | self.image_to_frame_fence[image_index].get() 76 | } 77 | 78 | /// Returns semaphore associated with swapchain image availability, with 79 | /// bounds checking. 80 | pub fn get_image_available_sem(&self, current_frame_num: usize) -> vk::Semaphore { 81 | if current_frame_num >= MAX_FRAMES_IN_FLIGHT { 82 | panic!("Invalid input! current_frame_num {}", current_frame_num); 83 | } 84 | self.image_available_semaphores[current_frame_num] 85 | } 86 | 87 | /// Returns semaphore associated with swapchain image render output to COLOR 88 | /// attachment, with bounds checking. 89 | pub fn get_render_finished_semaphore(&self, current_frame_num: usize) -> vk::Semaphore { 90 | if current_frame_num >= MAX_FRAMES_IN_FLIGHT { 91 | panic!("Invalid input! current_frame_num {}", current_frame_num); 92 | } 93 | self.render_finished_semaphores[current_frame_num] 94 | } 95 | 96 | /// Ensures that the image is not currently in flight, so the command buffers 97 | /// for it are safe to write to (they are in the ready state). 98 | /// 99 | /// Returns the frame fence to submit the next queue with. 100 | pub fn ensure_image_resources_ready( 101 | &self, image_index: usize, current_frame_num: usize, 102 | ) -> SarektResult { 103 | if current_frame_num >= MAX_FRAMES_IN_FLIGHT || image_index >= self.image_to_frame_fence.len() { 104 | panic!( 105 | "Invalid input! image_index: {} current_frame_num: {}", 106 | image_index, current_frame_num 107 | ); 108 | } 109 | 110 | unsafe { 111 | // Wait for swapchain image resources to be ready. 112 | let image_fence = self.image_to_frame_fence[image_index as usize].get(); 113 | if image_fence != vk::Fence::null() { 114 | self 115 | .logical_device 116 | .wait_for_fences(&[image_fence], true, u64::max_value())?; 117 | } 118 | 119 | // Wait for the frame in flight to be ready (there are a max number of frames in 120 | // flight). 121 | let frame_fence = self.frame_fences[current_frame_num]; 122 | if frame_fence != image_fence { 123 | // Wait for swap chain image to be ready. 124 | self 125 | .logical_device 126 | .wait_for_fences(&[frame_fence], true, u64::max_value())?; 127 | } 128 | 129 | self.logical_device.reset_fences(&[frame_fence])?; 130 | 131 | Ok(frame_fence) 132 | } 133 | } 134 | 135 | /// Mark the image as in use by the given frame. 136 | pub fn set_image_to_in_flight_frame(&self, image_index: usize, current_frame_num: usize) { 137 | if current_frame_num >= MAX_FRAMES_IN_FLIGHT || image_index >= self.image_to_frame_fence.len() { 138 | panic!( 139 | "Invalid input! image_index: {} current_frame_num: {}", 140 | image_index, current_frame_num 141 | ); 142 | } 143 | self.image_to_frame_fence[image_index as usize].set(self.frame_fences[current_frame_num]); 144 | } 145 | 146 | /// Waits for all the in flight frames, ie device idle. 147 | pub fn wait_for_all_frames(&self) -> SarektResult<()> { 148 | unsafe { 149 | Ok( 150 | self 151 | .logical_device 152 | .wait_for_fences(&self.frame_fences, true, u64::max_value())?, 153 | ) 154 | } 155 | } 156 | 157 | /// Makes new semaphores for draw synchronization. Useful for swapchain 158 | /// recreation. 159 | /// 160 | /// Unsafe because they must not be in use. 161 | pub unsafe fn recreate_semaphores(&mut self) -> SarektResult<()> { 162 | let semaphore_ci = vk::SemaphoreCreateInfo::default(); 163 | for i in 0..MAX_FRAMES_IN_FLIGHT { 164 | let to_destroy = self.image_available_semaphores[i]; 165 | self.image_available_semaphores[i] = 166 | self.logical_device.create_semaphore(&semaphore_ci, None)?; 167 | self.logical_device.destroy_semaphore(to_destroy, None); 168 | 169 | let to_destroy = self.render_finished_semaphores[i]; 170 | self.render_finished_semaphores[i] = 171 | self.logical_device.create_semaphore(&semaphore_ci, None)?; 172 | self.logical_device.destroy_semaphore(to_destroy, None); 173 | } 174 | 175 | Ok(()) 176 | } 177 | 178 | pub unsafe fn destroy_all(&self) { 179 | info!("Destroying all synchronization primitives..."); 180 | for &sem in self.image_available_semaphores.iter() { 181 | self.logical_device.destroy_semaphore(sem, None); 182 | } 183 | for &sem in self.render_finished_semaphores.iter() { 184 | self.logical_device.destroy_semaphore(sem, None); 185 | } 186 | for &fence in self.frame_fences.iter() { 187 | self.logical_device.destroy_fence(fence, None); 188 | } 189 | 190 | // TODO(issue#9) OFFSCREEN this fence won't be the same. 191 | self 192 | .logical_device 193 | .wait_for_fences(&[self.acquire_fence], true, u64::max_value()) 194 | .expect("Failed to wait for fence during destruction"); 195 | self.logical_device.destroy_fence(self.acquire_fence, None); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/renderer/vulkan/vulkan_renderer/render_attachments.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::{SarektError, SarektResult}, 3 | image_data::ImageDataFormat, 4 | renderer::{ 5 | buffers_and_images::{BufferImageHandle, BufferImageStore}, 6 | config::NumSamples, 7 | vulkan::vulkan_buffer_image_functions::ImageAndMemory, 8 | VulkanBufferImageFunctions, 9 | }, 10 | }; 11 | use ash::{version::InstanceV1_0, vk, Instance}; 12 | use std::{ 13 | convert::TryInto, 14 | sync::{Arc, RwLock}, 15 | }; 16 | 17 | /// All resources relating to the Depth buffer (z buffer). 18 | /// This includes the image handle, the image that references, and the format. 19 | pub struct DepthAttachment { 20 | pub depth_buffer_image_handle: BufferImageHandle, 21 | pub image_and_memory: ImageAndMemory, 22 | pub format: vk::Format, 23 | } 24 | impl DepthAttachment { 25 | pub fn new( 26 | instance: &Instance, physical_device: vk::PhysicalDevice, 27 | buffer_image_store: &Arc>>, 28 | extent: (u32, u32), num_msaa_samples: NumSamples, 29 | ) -> SarektResult { 30 | let format = Self::find_depth_format(instance, physical_device)?; 31 | let (depth_buffer_image_handle, buffer_or_image) = 32 | BufferImageStore::create_uninitialized_image_msaa( 33 | buffer_image_store, 34 | extent, 35 | format.try_into()?, 36 | num_msaa_samples, 37 | )?; 38 | 39 | let image_and_memory = buffer_or_image.handle.image().unwrap(); 40 | 41 | Ok(DepthAttachment { 42 | depth_buffer_image_handle, 43 | image_and_memory, 44 | format, 45 | }) 46 | } 47 | 48 | fn find_supported_format( 49 | instance: &Instance, physical_device: vk::PhysicalDevice, format_candidates: &[vk::Format], 50 | tiling: vk::ImageTiling, features: vk::FormatFeatureFlags, 51 | ) -> SarektResult { 52 | for &format in format_candidates.iter() { 53 | let props = 54 | unsafe { instance.get_physical_device_format_properties(physical_device, format) }; 55 | 56 | if tiling == vk::ImageTiling::LINEAR && (props.linear_tiling_features & features) == features 57 | || (tiling == vk::ImageTiling::OPTIMAL 58 | && (props.optimal_tiling_features & features) == features) 59 | { 60 | // linear tiling requested and supported by this format. 61 | return Ok(format); 62 | } 63 | } 64 | 65 | Err(SarektError::NoSuitableDepthBufferFormat) 66 | } 67 | 68 | fn find_depth_format( 69 | instance: &Instance, physical_device: vk::PhysicalDevice, 70 | ) -> SarektResult { 71 | let format_candidates = [ 72 | vk::Format::D32_SFLOAT, 73 | vk::Format::D32_SFLOAT_S8_UINT, 74 | vk::Format::D24_UNORM_S8_UINT, 75 | ]; 76 | let tiling = vk::ImageTiling::OPTIMAL; 77 | let features = vk::FormatFeatureFlags::DEPTH_STENCIL_ATTACHMENT; 78 | 79 | Self::find_supported_format( 80 | instance, 81 | physical_device, 82 | &format_candidates, 83 | tiling, 84 | features, 85 | ) 86 | } 87 | 88 | fn has_stencil_component(format: vk::Format) -> bool { 89 | format == vk::Format::D32_SFLOAT_S8_UINT || format == vk::Format::D24_UNORM_S8_UINT 90 | } 91 | } 92 | 93 | /// Used for resolving MSAA, see https://www.khronos.org/registry/vulkan/specs/1.2-khr-extensions/html/chap7.html#VkSubpassDescription 94 | pub struct ResolveAttachment { 95 | pub resolve_image_handle: BufferImageHandle, 96 | pub resolve_image: ImageAndMemory, 97 | pub format: vk::Format, 98 | } 99 | impl ResolveAttachment { 100 | pub fn new( 101 | buffer_image_store: &Arc>>, 102 | dimensions: (u32, u32), format: ImageDataFormat, num_msaa_samples: NumSamples, 103 | ) -> SarektResult { 104 | let (resolve_image_handle, resolve_image) = BufferImageStore::create_uninitialized_image_msaa( 105 | buffer_image_store, 106 | dimensions, 107 | format, 108 | num_msaa_samples, 109 | )?; 110 | 111 | Ok(ResolveAttachment { 112 | resolve_image_handle, 113 | resolve_image: resolve_image.handle.image()?, 114 | format: format 115 | .try_into() 116 | .expect("Format not supported by sarekt for msaa color buffer"), 117 | }) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/renderer/vulkan/vulkan_renderer/render_targets.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::SarektResult, 3 | renderer::{ 4 | config::PresentMode, 5 | vulkan::{ 6 | images::ImageAndView, 7 | queues::QueueFamilyIndices, 8 | vulkan_renderer::{ 9 | surface::SurfaceAndExtension, 10 | swap_chain::SwapchainAndExtension, 11 | vulkan_core::{VulkanCoreStructures, VulkanDeviceStructures}, 12 | }, 13 | }, 14 | }, 15 | }; 16 | use ash::{version::DeviceV1_0, vk, Device}; 17 | use log::{info, warn}; 18 | use std::sync::Arc; 19 | 20 | /// Render target related structures, such as the swapchain extension, the 21 | /// extent, and the images themselves. 22 | pub struct RenderTargetBundle { 23 | pub swapchain_and_extension: SwapchainAndExtension, // TODO(issue#9) OFFSCREEN option 24 | pub render_targets: Vec, // aka SwapChainImages if presenting. 25 | pub extent: vk::Extent2D, 26 | } 27 | impl RenderTargetBundle { 28 | pub fn new( 29 | vulkan_core: &VulkanCoreStructures, device_bundle: &VulkanDeviceStructures, 30 | requested_width: u32, requested_height: u32, requested_present_mode: PresentMode, 31 | ) -> SarektResult { 32 | let swapchain_extension = ash::extensions::khr::Swapchain::new( 33 | vulkan_core.instance.as_ref(), 34 | device_bundle.logical_device.as_ref(), 35 | ); 36 | let (swapchain, format, extent) = Self::create_swapchain( 37 | &vulkan_core.surface_and_extension, 38 | &swapchain_extension, 39 | device_bundle.physical_device, 40 | &device_bundle.queue_families, 41 | requested_width, 42 | requested_height, 43 | requested_present_mode, 44 | None, 45 | )?; 46 | let swapchain_and_extension = 47 | SwapchainAndExtension::new(swapchain, format, swapchain_extension); 48 | 49 | // TODO(issue#9) OFFSCREEN if not swapchain create images that im rendering to. 50 | let render_target_images = unsafe { 51 | swapchain_and_extension 52 | .swapchain_functions 53 | .get_swapchain_images(swapchain_and_extension.swapchain)? 54 | }; 55 | let render_targets = Self::create_render_target_image_views( 56 | &device_bundle.logical_device, 57 | render_target_images, 58 | swapchain_and_extension.format, 59 | )?; 60 | 61 | Ok(RenderTargetBundle { 62 | swapchain_and_extension, 63 | render_targets, 64 | extent, 65 | }) 66 | } 67 | 68 | /// Gets the next image in the swapchain to draw to and associates the given 69 | /// semaphore and fence with it. 70 | pub fn acquire_next_image( 71 | &self, timeout: u64, image_available_semaphore: vk::Semaphore, image_available_fence: vk::Fence, 72 | ) -> SarektResult<(u32, bool)> { 73 | // TODO(issue#9) OFFSCREEN handle drawing without swapchain. 74 | unsafe { 75 | Ok( 76 | self 77 | .swapchain_and_extension 78 | .swapchain_functions 79 | .acquire_next_image( 80 | self.swapchain_and_extension.swapchain, 81 | timeout, 82 | image_available_semaphore, 83 | image_available_fence, 84 | )?, 85 | ) 86 | } 87 | } 88 | 89 | /// Presents to the swapchain waiting on the device semaphore. 90 | pub fn queue_present( 91 | &self, image_index: usize, presentation_queue: vk::Queue, wait_semaphores: &[vk::Semaphore], 92 | ) -> SarektResult<()> { 93 | let swapchains = [self.swapchain_and_extension.swapchain]; 94 | let image_indices = [image_index as u32]; 95 | let present_info = vk::PresentInfoKHR::builder() 96 | .wait_semaphores(wait_semaphores) 97 | .swapchains(&swapchains) 98 | .image_indices(&image_indices) 99 | .build(); 100 | unsafe { 101 | self 102 | .swapchain_and_extension 103 | .swapchain_functions 104 | .queue_present(presentation_queue, &present_info)?; 105 | } 106 | 107 | Ok(()) 108 | } 109 | 110 | pub fn get_render_target_format(&self) -> vk::Format { 111 | self.swapchain_and_extension.format 112 | } 113 | 114 | /// Checks if the width and height given differ from the render target extent. 115 | pub fn extent_is_equal_to(&self, width: u32, height: u32) -> bool { 116 | self.extent.width == width && self.extent.height == height 117 | } 118 | 119 | /// Recreates teh swapchain using the new parameters and returns the old 120 | /// swapchain and images/views. 121 | /// 122 | /// Unsafe because of FFI use and the returned swapchain must be cleaned up. 123 | pub unsafe fn recreate_swapchain( 124 | &mut self, vulkan_core: &VulkanCoreStructures, device_bundle: &VulkanDeviceStructures, 125 | requested_width: u32, requested_height: u32, requested_present_mode: PresentMode, 126 | ) -> SarektResult<(vk::SwapchainKHR, Vec)> { 127 | let old_swapchain = self.swapchain_and_extension.swapchain; 128 | 129 | let (new_swapchain, new_format, new_extent) = RenderTargetBundle::create_swapchain( 130 | &vulkan_core.surface_and_extension, 131 | &self.swapchain_and_extension.swapchain_functions, 132 | device_bundle.physical_device, 133 | &device_bundle.queue_families, 134 | requested_width, 135 | requested_height, 136 | requested_present_mode, 137 | Some(old_swapchain), 138 | )?; 139 | 140 | self.swapchain_and_extension.swapchain = new_swapchain; 141 | self.swapchain_and_extension.format = new_format; 142 | self.extent = new_extent; 143 | 144 | // TODO(issue#9) OFFSCREEN if not swapchain create images that im rendering to. 145 | let render_target_images = self 146 | .swapchain_and_extension 147 | .swapchain_functions 148 | .get_swapchain_images(new_swapchain)?; 149 | 150 | let mut render_targets = Self::create_render_target_image_views( 151 | &device_bundle.logical_device, 152 | render_target_images, 153 | new_format, 154 | )?; 155 | std::mem::swap(&mut self.render_targets, &mut render_targets); 156 | 157 | Ok((old_swapchain, render_targets)) 158 | } 159 | 160 | /// Useful during swapchain recreation, but the specific render targets and 161 | /// swapchain to delete are specified, since the current ones are always 162 | /// contained in the struct. 163 | pub unsafe fn cleanup_render_targets( 164 | &self, device_bundle: &VulkanDeviceStructures, render_targets: &[ImageAndView], 165 | swapchain: vk::SwapchainKHR, 166 | ) { 167 | info!("Destrying render target views..."); 168 | for view in render_targets.iter() { 169 | device_bundle 170 | .logical_device 171 | .destroy_image_view(view.view, None); 172 | } 173 | // TODO(issue#9) OFFSCREEN if images and not swapchain destroy images. 174 | 175 | // TODO(issue#9) OFFSCREEN if there is one, if not destroy images (as above todo 176 | // states). 177 | info!("Destrying swapchain..."); 178 | let swapchain_functions = &self.swapchain_and_extension.swapchain_functions; 179 | swapchain_functions.destroy_swapchain(swapchain, None); 180 | } 181 | 182 | // ================================================================================ 183 | // Presentation and Swapchain Helper Methods 184 | // ================================================================================ 185 | /// Based on the capabilities of the surface, the physical device, and the 186 | /// configuration of sarekt, creates a swapchain with the appropriate 187 | /// configuration (format, color space, present mode, and extent). 188 | fn create_swapchain( 189 | surface_and_extension: &SurfaceAndExtension, 190 | swapchain_extension: &ash::extensions::khr::Swapchain, physical_device: vk::PhysicalDevice, 191 | queue_family_indices: &QueueFamilyIndices, requested_width: u32, requested_height: u32, 192 | requested_present_mode: PresentMode, old_swapchain: Option, 193 | ) -> SarektResult<(vk::SwapchainKHR, vk::Format, vk::Extent2D)> { 194 | let swapchain_support = 195 | VulkanDeviceStructures::query_swap_chain_support(surface_and_extension, physical_device)?; 196 | 197 | let format = Self::choose_swap_surface_format(&swapchain_support.formats); 198 | let present_mode = 199 | Self::choose_presentation_mode(&swapchain_support.present_modes, requested_present_mode); 200 | let extent = Self::choose_swap_extent( 201 | &swapchain_support.capabilities, 202 | requested_width, 203 | requested_height, 204 | ); 205 | 206 | // Select minimum number of images to render to. For triple buffering this 207 | // would be 3, etc. But don't exceed the max. Implementation may create more 208 | // than this depending on present mode. 209 | // [vulkan tutorial](https://vulkan-tutorial.com/Drawing_a_triangle/Presentation/Swap_chain) 210 | // recommends setting this to min + 1 because if we select minimum we may wait 211 | // on internal driver operations. 212 | let max_image_count = swapchain_support.capabilities.max_image_count; 213 | let max_image_count = if max_image_count == 0 { 214 | u32::max_value() 215 | } else { 216 | max_image_count 217 | }; 218 | let min_image_count = (swapchain_support.capabilities.min_image_count + 1).min(max_image_count); 219 | 220 | let sharing_mode = if queue_family_indices.graphics_queue_family.unwrap() 221 | != queue_family_indices.presentation_queue_family.unwrap() 222 | { 223 | // Concurrent sharing mode because the images will need to be accessed by more 224 | // than one queue family. 225 | vk::SharingMode::CONCURRENT 226 | } else { 227 | // Exclusive (probly) has best performance, not sharing the image with other 228 | // queue families. 229 | vk::SharingMode::EXCLUSIVE 230 | }; 231 | 232 | let swapchain_ci = vk::SwapchainCreateInfoKHR::builder() 233 | .surface(surface_and_extension.surface) 234 | .min_image_count(min_image_count) 235 | .image_format(format.format) 236 | .image_color_space(format.color_space) 237 | .image_extent(extent) 238 | .image_array_layers(1) // Number of views (multiview/stereo surface for 3D applications with glasses or maybe VR). 239 | .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) // We'll just be rendering colors to this. We could render to another image and transfer here after post processing but we're not. 240 | .image_sharing_mode(sharing_mode) 241 | .queue_family_indices(&queue_family_indices.as_vec().unwrap()) 242 | .pre_transform(swapchain_support.capabilities.current_transform) // Match the transform of the swapchain, I'm not trying to redner upside down! 243 | .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) // No alpha blending within the window system for now. 244 | .present_mode(present_mode) 245 | .clipped(true) // Go ahead and discard rendering ops we dont need (window half off screen). 246 | .old_swapchain(old_swapchain.unwrap_or_else(vk::SwapchainKHR::null)) // Pass old swapchain for recreation. 247 | .build(); 248 | 249 | let swapchain = unsafe { swapchain_extension.create_swapchain(&swapchain_ci, None)? }; 250 | Ok((swapchain, format.format, extent)) 251 | } 252 | 253 | /// If drawing to a surface, chooses the best format from the ones available 254 | /// for the surface. Tries to use B8g8r8a8Srgb format with SRGB_NONLINEAR 255 | /// colorspace. 256 | /// 257 | /// If that isn't available, for now we just use the 0th SurfaceFormatKHR. 258 | fn choose_swap_surface_format( 259 | available_formats: &[vk::SurfaceFormatKHR], 260 | ) -> vk::SurfaceFormatKHR { 261 | *available_formats 262 | .iter() 263 | .find(|format| { 264 | format.format == vk::Format::B8G8R8A8_UNORM 265 | && format.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR 266 | }) 267 | .unwrap_or(&available_formats[0]) 268 | } 269 | 270 | /// Selects Mailbox if available, but if not tries to fallback to FIFO. See the [spec](https://renderdoc.org/vkspec_chunked/chap32.html#VkPresentModeKHR) for details on modes. 271 | /// 272 | /// TODO(issue#18) CONFIG support immediate mode if possible and allow the 273 | /// user to have tearing if they wish. 274 | fn choose_presentation_mode( 275 | available_presentation_modes: &[vk::PresentModeKHR], requested_present_mode: PresentMode, 276 | ) -> vk::PresentModeKHR { 277 | let present_mode = *available_presentation_modes 278 | .iter() 279 | .find(|&pm| match (requested_present_mode, pm) { 280 | (PresentMode::Mailbox, &vk::PresentModeKHR::MAILBOX) => true, 281 | (PresentMode::Immediate, &vk::PresentModeKHR::IMMEDIATE) => true, 282 | (PresentMode::Fifo, &vk::PresentModeKHR::FIFO) => true, 283 | _ => false, 284 | }) 285 | .unwrap_or(&vk::PresentModeKHR::FIFO); 286 | 287 | info!("Selecting present mode: {:?}", present_mode); 288 | present_mode 289 | } 290 | 291 | /// Selects the resolution of the swap chain images. 292 | /// This is almost always equal to the resolution of the Surface we're drawing 293 | /// too, but we need to double check since some window managers allow us to 294 | /// differ. 295 | fn choose_swap_extent( 296 | capabilities: &vk::SurfaceCapabilitiesKHR, requested_width: u32, requested_height: u32, 297 | ) -> vk::Extent2D { 298 | if capabilities.current_extent.width != u32::max_value() { 299 | return capabilities.current_extent; 300 | } 301 | // The window system indicates that we can specify our own extent if this is 302 | // true 303 | let clipped_requested_width = requested_width.min(capabilities.max_image_extent.width); 304 | let width = capabilities 305 | .min_image_extent 306 | .width 307 | .max(clipped_requested_width); 308 | let clipped_requested_height = requested_height.min(capabilities.max_image_extent.height); 309 | let height = capabilities 310 | .min_image_extent 311 | .height 312 | .max(clipped_requested_height); 313 | 314 | if width != requested_width || height != requested_height { 315 | warn!( 316 | "Could not create a swapchain with the requested height and width, rendering to a \ 317 | resolution of {}x{} instead", 318 | width, height 319 | ); 320 | } 321 | 322 | vk::Extent2D::builder().width(width).height(height).build() 323 | } 324 | 325 | /// Given the render target images and format, create an image view suitable 326 | /// for rendering on. (one level, no mipmapping, color bit access). 327 | fn create_render_target_image_views( 328 | logical_device: &Arc, targets: Vec, format: vk::Format, 329 | ) -> SarektResult> { 330 | let mut views = Vec::with_capacity(targets.len()); 331 | for &image in targets.iter() { 332 | // Not swizzling rgba around. 333 | let component_mapping = vk::ComponentMapping::default(); 334 | let image_subresource_range = vk::ImageSubresourceRange::builder() 335 | .aspect_mask(vk::ImageAspectFlags::COLOR) // We're writing color to this view 336 | .base_mip_level(0) // access to all mipmap levels 337 | .level_count(1) // Only one level, no mipmapping 338 | .base_array_layer(0) // access to all layers 339 | .layer_count(1) // Only one layer. (not sterescopic) 340 | .build(); 341 | 342 | let ci = vk::ImageViewCreateInfo::builder() 343 | .image(image) 344 | .view_type(vk::ImageViewType::TYPE_2D) 345 | .format(format) 346 | .components(component_mapping) 347 | .subresource_range(image_subresource_range); 348 | 349 | let view = unsafe { logical_device.create_image_view(&ci, None)? }; 350 | unsafe { views.push(ImageAndView::new(image, view)) }; 351 | } 352 | Ok(views) 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /src/renderer/vulkan/vulkan_renderer/surface.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | 3 | /// Wrapper for the surface and its associated methods. 4 | pub struct SurfaceAndExtension { 5 | pub surface: vk::SurfaceKHR, 6 | pub surface_functions: ash::extensions::khr::Surface, 7 | } 8 | impl SurfaceAndExtension { 9 | pub fn new(surface: vk::SurfaceKHR, surface_functions: ash::extensions::khr::Surface) -> Self { 10 | SurfaceAndExtension { 11 | surface, 12 | surface_functions, 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/renderer/vulkan/vulkan_renderer/swap_chain.rs: -------------------------------------------------------------------------------- 1 | use ash::vk; 2 | 3 | /// Wrapper for the swapchain, its format, and various methods. 4 | pub struct SwapchainAndExtension { 5 | pub swapchain: vk::SwapchainKHR, 6 | pub format: vk::Format, 7 | pub swapchain_functions: ash::extensions::khr::Swapchain, 8 | } 9 | impl SwapchainAndExtension { 10 | pub fn new( 11 | swapchain: vk::SwapchainKHR, format: vk::Format, 12 | swapchain_functions: ash::extensions::khr::Swapchain, 13 | ) -> Self { 14 | SwapchainAndExtension { 15 | swapchain, 16 | format, 17 | swapchain_functions, 18 | } 19 | } 20 | } 21 | 22 | pub struct SwapchainSupportDetails { 23 | pub capabilities: vk::SurfaceCapabilitiesKHR, 24 | pub formats: Vec, 25 | pub present_modes: Vec, 26 | } 27 | impl SwapchainSupportDetails { 28 | pub fn new( 29 | capabilities: vk::SurfaceCapabilitiesKHR, formats: Vec, 30 | present_modes: Vec, 31 | ) -> Self { 32 | Self { 33 | capabilities, 34 | formats, 35 | present_modes, 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/renderer/vulkan/vulkan_shader_functions.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::{SarektError, SarektResult}, 3 | renderer::shaders::{ShaderBackendHandleTrait, ShaderCode, ShaderLoader}, 4 | }; 5 | use ash::{version::DeviceV1_0, vk, Device}; 6 | use log::info; 7 | use std::sync::Arc; 8 | 9 | /// Vulkan implementation of [ShaderLoader](trait.ShaderLoader.html). 10 | #[derive(Clone)] 11 | pub struct VulkanShaderFunctions { 12 | logical_device: Arc, 13 | } 14 | impl VulkanShaderFunctions { 15 | pub fn new(logical_device: Arc) -> Self { 16 | Self { logical_device } 17 | } 18 | } 19 | unsafe impl ShaderLoader for VulkanShaderFunctions { 20 | type SBH = vk::ShaderModule; 21 | 22 | fn load_shader(&self, code: &ShaderCode) -> SarektResult { 23 | if let ShaderCode::Spirv(spirv) = code { 24 | let ci = vk::ShaderModuleCreateInfo::builder().code(spirv).build(); 25 | unsafe { 26 | return Ok(self.logical_device.create_shader_module(&ci, None)?); 27 | } 28 | } 29 | 30 | Err(SarektError::IncompatibleShaderCode) 31 | } 32 | 33 | fn delete_shader(&self, shader: vk::ShaderModule) -> SarektResult<()> { 34 | info!("Deleting shader {:?}...", shader); 35 | unsafe { self.logical_device.destroy_shader_module(shader, None) }; 36 | Ok(()) 37 | } 38 | } 39 | 40 | /// Allow vk::ShaderModule to be a backend handle for the 41 | /// [ShaderStore](struct.ShaderStore.html). 42 | unsafe impl ShaderBackendHandleTrait for vk::ShaderModule {} 43 | -------------------------------------------------------------------------------- /src/renderer/vulkan/vulkan_vertex_bindings.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | error::SarektResult, 3 | renderer::vertex_bindings::{ 4 | BindTextureInfo, BindUniformInfo, DefaultForwardShaderLayout, DefaultForwardShaderVertex, 5 | DescriptorLayoutInfo, VertexBindings, 6 | }, 7 | }; 8 | use ash::vk; 9 | 10 | // TODO(issue#21) SHADERS use reflection to generate these at compile time 11 | // (generically?). 12 | 13 | unsafe impl VertexBindings for DefaultForwardShaderVertex { 14 | type BVA = vk::VertexInputAttributeDescription; 15 | type BVB = vk::VertexInputBindingDescription; 16 | 17 | fn get_binding_description() -> Self::BVB { 18 | vk::VertexInputBindingDescription::builder() 19 | .binding(0) 20 | .stride(std::mem::size_of::() as u32) 21 | .input_rate(vk::VertexInputRate::VERTEX) 22 | .build() 23 | } 24 | 25 | fn get_attribute_descriptions() -> Vec { 26 | // Position 27 | let position_attr = vk::VertexInputAttributeDescription::builder() 28 | .binding(0) // Which binding in the shader. 29 | .location(0) // The layout location in the shader. 30 | .format(vk::Format::R32G32B32_SFLOAT) // RGB is unintuitive but the point is its three floats. 31 | .offset(offset_of!(DefaultForwardShaderVertex, position) as u32) 32 | .build(); 33 | let color_attr = vk::VertexInputAttributeDescription::builder() 34 | .binding(0) // Which binding in the shader. 35 | .location(1) // The layout location in the shader. 36 | .format(vk::Format::R32G32B32_SFLOAT) // RGB is unintuitive but the point is its two floats. 37 | .offset(offset_of!(DefaultForwardShaderVertex, color) as u32) 38 | .build(); 39 | let texture_attr = vk::VertexInputAttributeDescription::builder() 40 | .binding(0) 41 | .location(2) 42 | .format(vk::Format::R32G32_SFLOAT) 43 | .offset(offset_of!(DefaultForwardShaderVertex, texture_coordinates) as u32) 44 | .build(); 45 | 46 | vec![position_attr, color_attr, texture_attr] 47 | } 48 | } 49 | 50 | // TODO(issue#21) SHADERS use reflection to generate descriptor set layouts. 51 | 52 | unsafe impl DescriptorLayoutInfo for DefaultForwardShaderLayout { 53 | type BackendDescriptorSetLayoutBindings = [vk::DescriptorSetLayoutBinding; 2]; 54 | 55 | fn get_descriptor_set_layout_bindings() -> Self::BackendDescriptorSetLayoutBindings { 56 | // Create the bindings for each part of the uniform. 57 | [ 58 | vk::DescriptorSetLayoutBinding::builder() 59 | .binding(0) 60 | .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) 61 | .descriptor_count(1) // If this uniform contained an array (like of lights, or transforms for each bone for animation) this is how many. 62 | .stage_flags(vk::ShaderStageFlags::VERTEX | vk::ShaderStageFlags::FRAGMENT) // used in the vertex and fragment shader. 63 | // .immutable_samplers() no samplers since there's no textures. 64 | .build(), 65 | vk::DescriptorSetLayoutBinding::builder() 66 | .binding(1) 67 | .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) 68 | .descriptor_count(1) 69 | .stage_flags(vk::ShaderStageFlags::FRAGMENT) 70 | .build(), 71 | ] 72 | } 73 | 74 | fn get_bind_uniform_info() -> SarektResult { 75 | Ok(BindUniformInfo { 76 | bindings: vec![0], 77 | offset: 0u64, 78 | range: std::mem::size_of::() as u64, 79 | }) 80 | } 81 | 82 | fn get_bind_texture_info() -> SarektResult { 83 | Ok(BindTextureInfo { bindings: vec![1] }) 84 | } 85 | } 86 | --------------------------------------------------------------------------------