├── .envrc ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── README.md ├── assets ├── fonts │ ├── Roboto 400.ttf │ └── mplus-1p-regular.ttf ├── models │ ├── CornellBox │ │ ├── cornellBox.bin │ │ ├── cornellBox.gltf │ │ └── credits.txt │ ├── CornellBoxLucy │ │ ├── cornellBoxLucy.bin │ │ ├── cornellBoxLucy.gltf │ │ └── credits │ ├── license_extractor.py │ ├── licenses │ ├── reflections.glb │ └── shadows.glb └── skyboxs │ └── Yokohama │ ├── negx.jpg │ ├── negy.jpg │ ├── negz.jpg │ ├── posx.jpg │ ├── posy.jpg │ ├── posz.jpg │ └── readme.txt ├── crates ├── examples │ └── gltf_viewer │ │ ├── Cargo.toml │ │ ├── build.rs │ │ ├── shaders │ │ ├── AnimationCompute.comp │ │ ├── RayTracing.rahit │ │ ├── RayTracing.rchit │ │ ├── RayTracing.rgen │ │ ├── RayTracing.rmiss │ │ ├── RayTracing.shadow.rahit │ │ ├── RayTracing.shadow.rmiss │ │ └── lib │ │ │ ├── Camera.glsl │ │ │ ├── Heatmap.glsl │ │ │ ├── Material.glsl │ │ │ ├── PBR.glsl │ │ │ ├── PunctualLight.glsl │ │ │ ├── Random.glsl │ │ │ ├── RayTracingCommons.glsl │ │ │ ├── Raytracing.shadow.rahit │ │ │ ├── Tonemapping.glsl │ │ │ └── UniformBufferObject.glsl │ │ └── src │ │ ├── args.rs │ │ ├── compute_unit.rs │ │ ├── desc_sets.rs │ │ ├── gui_state.rs │ │ ├── loader.rs │ │ ├── main.rs │ │ ├── pipeline_res.rs │ │ └── ubo.rs └── libs │ ├── app │ ├── Cargo.toml │ └── src │ │ ├── camera.rs │ │ ├── lib.rs │ │ └── types.rs │ ├── asset_loader │ ├── Cargo.toml │ └── src │ │ ├── aabb.rs │ │ ├── acceleration_structures.rs │ │ ├── animation.rs │ │ ├── cubumap.rs │ │ ├── error.rs │ │ ├── geometry.rs │ │ ├── globals.rs │ │ ├── image.rs │ │ ├── lib.rs │ │ ├── light.rs │ │ ├── material.rs │ │ ├── morph.rs │ │ ├── scene_graph.rs │ │ ├── skinning.rs │ │ └── texture.rs │ ├── gui │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ ├── resource_manager │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ └── vulkan │ ├── Cargo.toml │ └── src │ ├── buffer.rs │ ├── command.rs │ ├── context.rs │ ├── descriptor.rs │ ├── device.rs │ ├── image.rs │ ├── instance.rs │ ├── lib.rs │ ├── physical_device.rs │ ├── pipeline │ ├── compute.rs │ ├── graphics.rs │ ├── layout.rs │ ├── mod.rs │ └── shader.rs │ ├── query.rs │ ├── queue.rs │ ├── ray_tracing │ ├── acceleration_structure.rs │ ├── mod.rs │ ├── pipeline.rs │ └── shader_binding_table.rs │ ├── sampler.rs │ ├── surface.rs │ ├── swapchain.rs │ ├── sync.rs │ └── utils │ ├── mod.rs │ └── platforms.rs ├── flake.lock ├── flake.nix ├── images └── lucy.png └── spv └── .gitkeep /.envrc: -------------------------------------------------------------------------------- 1 | source_url "https://github.com/zhaofengli/nix-direnv/raw/2121c62181130c2295183421afda26d2ea7549ef/direnvrc" "sha256-zcBGwswqqcm6VY7j+TMzUtqwZF3romx7fIxqkiu3XxI=" 2 | 3 | #use_flake_if_supported 4 | use flake . 5 | eval "$shellHook" 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | .idea 3 | *.spv 4 | .direnv 5 | assets 6 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = [ 3 | "crates/examples/gltf_viewer", 4 | "crates/libs/app", 5 | "crates/libs/asset_loader", 6 | "crates/libs/resource_manager", 7 | "crates/libs/gui", 8 | "crates/libs/vulkan", 9 | ] 10 | 11 | [workspace.package] 12 | authors = ["Kosumi "] 13 | version = "0.2.0" 14 | edition = "2021" 15 | 16 | [workspace.dependencies] 17 | winit = {version="0.27.5"} 18 | nalgebra = "0.32.2" 19 | ash = {version = "0.37.2", features = ["linked", "debug"]} 20 | memoffset = "0.8.0" 21 | gltf = {version = "1.1.0", features = [ 22 | "names", 23 | "extras", 24 | "image_jpeg_rayon", 25 | 26 | "KHR_materials_ior", 27 | "KHR_materials_pbrSpecularGlossiness", 28 | "KHR_materials_transmission", 29 | "KHR_materials_variants", 30 | "KHR_materials_volume", 31 | "KHR_materials_specular", 32 | "KHR_texture_transform", 33 | "KHR_materials_unlit", 34 | "KHR_lights_punctual", 35 | ]} 36 | #egui = "0.21.0" 37 | #egui-winit = "0.21.1" 38 | #egui-winit-ash-integration = "0.3.0" 39 | log = "0.4.17" 40 | anyhow = "1.0.0" 41 | pretty_env_logger = "0.4.0" 42 | gpu-allocator = { version = "0.22", default-features = false, features = ["vulkan"] } 43 | 44 | imgui = "0.11.0" 45 | imgui-winit-support = "0.11.0" 46 | imgui-rs-vulkan-renderer = { version = "1.6", features = ["gpu-allocator", "dynamic-rendering"] } 47 | 48 | thiserror = "1.0" 49 | 50 | glam = { version = "0.24.0", features = ["serde"] } 51 | 52 | glsl-to-spirv = "0.1.7" 53 | 54 | rand = "0.8.5" 55 | shellexpand = "3.0.0" 56 | strum = "0.24.1" 57 | strum_macros = "0.24.3" 58 | mikktspace = "0.3.0" 59 | rayon = "1.7" 60 | cfg-if = "1.0.0" 61 | clap = {version = "3.2.23", features = ["derive"]} 62 | 63 | [workspace.dependencies.image] 64 | version = "0.24.6" 65 | default-features = false 66 | features = ["png", "jpeg"] 67 | 68 | [profile.dev.package.gltf] 69 | opt-level = 3 70 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2023, KaminariOS 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | 1. Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright notice, 12 | this list of conditions and the following disclaimer in the documentation 13 | and/or other materials provided with the distribution. 14 | 15 | 3. Neither the name of the copyright holder nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 23 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 25 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 26 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 27 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | # `Rustracer` 4 | 5 | ![Lines of code](https://tokei.rs/b1/github/KaminariOS/rustracer) 6 | ![GitHub repo size](https://img.shields.io/github/repo-size/KaminariOS/rustracer) 7 | [![dependency status](https://deps.rs/repo/github/KaminariOS/rustracer/status.svg)](https://deps.rs/repo/github/KaminariOS/rustracer) 8 | ![GitHub](https://img.shields.io/github/license/KaminariOS/rustracer) 9 | 10 |
11 | 12 | A PBR [glTF 2.0](https://www.khronos.org/gltf) renderer based on Vulkan ray-tracing, written in Rust. 13 | 14 | ## Naming 15 | This project and I are not affiliated with the [Rust Foundation](https://foundation.rust-lang.org). I name it `rus-tracer` only because I love [Rust](https://rust.facepunch.com) and ray tracing. 16 | 17 | ## Credits 18 | 19 | This project is based on Adrien Ben's [vulkan-examples-rs](https://github.com/adrien-ben/vulkan-examples-rs). 20 | 21 | Sample accumulation implementation, heatmap and Lucy obj model are from project [Ray Tracing In Vulkan](https://github.com/GPSnoopy/RayTracingInVulkan). 22 | 23 | I stole the PBR shaders from the [referencePT](https://github.com/boksajak/referencePT) project and made some changes. 24 | 25 | ## Demos 26 | [rustracer-0.2.0-Demo videos](https://www.youtube.com/watch?v=HYZCgwP7rmA&list=LLeDjbBFLvS94sHm_j6i3CHw). 27 | 28 | [rustracer-0.1.0-Demo videos](https://www.youtube.com/playlist?list=PLD1H28onwV_mFsPySwOtlBn9h5ybzepir). 29 | 30 | 31 | ![Lucy in Cornell](images/lucy.png) 32 | 33 | ## Features 34 | * [x] Loading arbitrary glTF 2.0 models 35 | * [x] Full node hierarchy 36 | * [x] Mesh 37 | * [x] Geometry normal generation 38 | * [x] Two sets of texture coords 39 | * [x] Mikktspace tangent generation 40 | * [x] Normal mapping 41 | * [ ] Camera 42 | * [x] Alpha blending and testing 43 | * [x] Full PBR material support 44 | * [x] Metallic-Roughness workflow 45 | * [x] Specular-Glossiness workflow 46 | * [x] Animations 47 | * [x] Articulated (translate, rotate, scale) 48 | * [x] Skinning(using compute shader) 49 | * [ ] Morph targets 50 | * [x] Extensions 51 | * [x] "KHR_materials_ior", 52 | * [x] "KHR_materials_pbrSpecularGlossiness", 53 | * [x] "KHR_materials_transmission", 54 | * [ ] importance sampling and BTDF 55 | * [x] "KHR_materials_variants", 56 | * [ ] GUI support 57 | * [x] "KHR_materials_volume", 58 | * [ ] "KHR_materials_specular", 59 | * [ ] "KHR_texture_transform", 60 | * [x] "KHR_materials_unlit", 61 | * [x] "KHR_lights_punctual", 62 | * [x] Optimizations 63 | * [x] Rayon-accelerated texture loading 64 | * [x] Async model loading 65 | * [ ] Async acceleration structure building 66 | 67 | * [ ] Realtime ray tracing 68 | * [ ] Rasterization mode 69 | * [ ] G-buffer 70 | * [ ] Hybrid mode 71 | * [ ] SVGF denoiser 72 | * [ ] Path regularization 73 | * [ ] Better multi-light sampling like ReSTIR 74 | * [ ] Blue noise and Halton sequence 75 | 76 | * [x] Extras 77 | * [x] Open file by drag-and-drop 78 | * [x] Skybox 79 | * [ ] Skydome(hdr) 80 | * [ ] Loading multiple glTF scene dynamically 81 | * [ ] Rigid-body simulation 82 | 83 | ## Building 84 | ### Prerequisites 85 | - Linux and a graphics card that supports KHR ray tracing 86 | - Currently, I hard-coded all test model paths in an enum(in [`gui_state.rs`](crates/examples/gltf_viewer/src/gui_state.rs)) and load models in the search paths(see [`resource manager`](crates/libs/resource_manager)). 87 | - You can open arbitrary glTF file by `cargo run -- -f ` or drag-and-drop 88 | - Windows not supported. Need some minor cfg tweaks to work on Windows. Open to pull requests. 89 | 90 | ### Build command 91 | Thanks to the superior Rust package manager `Cargo`, building and running can be as brainless as a one-liner: `cargo run`. 92 | 93 | However, some external C libraries like Vulkan SDK may be missing on your system(those libraries are necessary for basically any Vulkan or non-trivial graphics programming project, regardless of whatever programming language used). 94 | 95 | 96 | - To install those libraries automatically, 97 | - Install [Nix](https://nixos.org/download.html) package manager(Linux only) and [direnv](https://direnv.net). 98 | - `cd` into the project directory and `direnv allow`. 99 | - To install external libraries manually 100 | - `cargo run` to find out what is missing and install it 101 | - Look into the list in [flake.nix](flake.nix). 102 | 103 | ## Assets 104 | Pointers to glTF models: 105 | - [My collection](./assets/models/licenses) 106 | - [glTF sample models](https://github.com/KhronosGroup/glTF-Sample-Models). 107 | - [A ton of glTF models](https://sketchfab.com/search?features=downloadable&type=models) 108 | - [Open Research Content Archive](https://developer.nvidia.com/orca) can be converted to glTF with Blender. 109 | - [Poly heaven](https://polyhaven.com) 110 | 111 | ## References 112 | - [boksajak/referencePT](https://github.com/boksajak/referencePT) 113 | - [NVIDIA Vulkan Ray Tracing Tutorial](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/) 114 | - [adrien-ben/gltf-viewer-rs](https://github.com/adrien-ben/gltf-viewer-rs) 115 | - [adrien-ben/vulkan-examples-rs](https://github.com/adrien-ben/vulkan-examples-rs) 116 | - [GPSnoopy/RayTracingInVulkan](https://github.com/GPSnoopy/RayTracingInVulkan) 117 | - [Ray Tracing Gems II](https://www.realtimerendering.com/raytracinggems/rtg2/index.html) 118 | -------------------------------------------------------------------------------- /assets/fonts/Roboto 400.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/assets/fonts/Roboto 400.ttf -------------------------------------------------------------------------------- /assets/fonts/mplus-1p-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/assets/fonts/mplus-1p-regular.ttf -------------------------------------------------------------------------------- /assets/models/CornellBox/cornellBox.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/assets/models/CornellBox/cornellBox.bin -------------------------------------------------------------------------------- /assets/models/CornellBox/credits.txt: -------------------------------------------------------------------------------- 1 | https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/blob/master/media/scenes/cornellBox.gltf -------------------------------------------------------------------------------- /assets/models/CornellBoxLucy/cornellBoxLucy.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/assets/models/CornellBoxLucy/cornellBoxLucy.bin -------------------------------------------------------------------------------- /assets/models/CornellBoxLucy/credits: -------------------------------------------------------------------------------- 1 | https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/blob/master/media/scenes/cornellBox.gltf 2 | -------------------------------------------------------------------------------- /assets/models/license_extractor.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | def get_license(filename): 5 | return filename == "license.txt" 6 | 7 | record = [] 8 | for root, dirs, files in os.walk(".", topdown=False): 9 | for name in filter(get_license, files): 10 | full_path = os.path.join(root, name) 11 | path = Path(full_path) 12 | parent_dir = path.parent.name 13 | record.append('Name: ' + str(parent_dir) + '\n') 14 | with open(full_path) as input_file: 15 | head = [next(input_file) for _ in range(5)] 16 | record += head 17 | print(''.join(record)) 18 | # for name in dirs: 19 | # print(os.path.join(root, name)) 20 | -------------------------------------------------------------------------------- /assets/models/reflections.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/assets/models/reflections.glb -------------------------------------------------------------------------------- /assets/models/shadows.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/assets/models/shadows.glb -------------------------------------------------------------------------------- /assets/skyboxs/Yokohama/negx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/assets/skyboxs/Yokohama/negx.jpg -------------------------------------------------------------------------------- /assets/skyboxs/Yokohama/negy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/assets/skyboxs/Yokohama/negy.jpg -------------------------------------------------------------------------------- /assets/skyboxs/Yokohama/negz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/assets/skyboxs/Yokohama/negz.jpg -------------------------------------------------------------------------------- /assets/skyboxs/Yokohama/posx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/assets/skyboxs/Yokohama/posx.jpg -------------------------------------------------------------------------------- /assets/skyboxs/Yokohama/posy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/assets/skyboxs/Yokohama/posy.jpg -------------------------------------------------------------------------------- /assets/skyboxs/Yokohama/posz.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/assets/skyboxs/Yokohama/posz.jpg -------------------------------------------------------------------------------- /assets/skyboxs/Yokohama/readme.txt: -------------------------------------------------------------------------------- 1 | Author 2 | ====== 3 | 4 | This is the work of Emil Persson, aka Humus. 5 | http://www.humus.name 6 | 7 | 8 | 9 | License 10 | ======= 11 | 12 | This work is licensed under a Creative Commons Attribution 3.0 Unported License. 13 | http://creativecommons.org/licenses/by/3.0/ 14 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gltf-renderer" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors.workspace = true 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | app = { path = "../../libs/app" } 11 | gui = { path = "../../libs/gui" } 12 | asset_loader = { path = "../../libs/asset_loader", features = ["ash"]} 13 | rand.workspace = true 14 | strum.workspace = true 15 | strum_macros.workspace = true 16 | log.workspace = true 17 | clap.workspace = true 18 | 19 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/build.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::process::{Command, Output}; 3 | 4 | fn main() -> Result<()> { 5 | // Tell the build script to only run again if we change our source shaders 6 | println!("cargo:rerun-if-changed=shaders"); 7 | 8 | // Create destination path if necessary 9 | for entry in std::fs::read_dir("shaders")? { 10 | let entry = entry?; 11 | 12 | if entry.file_type()?.is_file() { 13 | let in_path = entry.path(); 14 | let path_str = in_path.to_str().unwrap(); 15 | let stat = Command::new("glslc") 16 | .args([path_str, "--target-env=vulkan1.2", "-o"]) 17 | .arg(&format!( 18 | "../../../spv/{}.spv", 19 | entry.file_name().into_string().unwrap() 20 | )) 21 | .output(); 22 | 23 | handle_program_result(stat); 24 | // if !stat.success() { 25 | // panic!("Failed to compile shader {:?}.", path_str); 26 | // } 27 | 28 | // Support only vertex and fragment shaders currently 29 | // let shader_type = in_path.extension().and_then(|ext| { 30 | // match ext.to_string_lossy().as_ref() { 31 | // "vert" => Some(ShaderType::Vertex), 32 | // "frag" => Some(ShaderType::Fragment), 33 | // _ => None, 34 | // } 35 | // }); 36 | // 37 | // if let Some(shader_type) = shader_type { 38 | // use std::io::Read; 39 | // 40 | // let source = std::fs::read_to_string(&in_path)?; 41 | // let mut compiled_file = glsl_to_spirv::compile(&source, shader_type)?; 42 | // 43 | // let mut compiled_bytes = Vec::new(); 44 | // compiled_file.read_to_end(&mut compiled_bytes)?; 45 | // 46 | // let out_path = format!( 47 | // "assets/gen/shaders/{}.spv", 48 | // in_path.file_name().unwrap().to_string_lossy() 49 | // ); 50 | // 51 | // std::fs::write(&out_path, &compiled_bytes)?; 52 | // } 53 | } 54 | } 55 | 56 | Ok(()) 57 | } 58 | 59 | use io::Result; 60 | fn handle_program_result(result: Result) { 61 | match result { 62 | Ok(output) => { 63 | if output.status.success() { 64 | println!("Shader compilation succedeed."); 65 | print!( 66 | "stdout: {}", 67 | String::from_utf8(output.stdout) 68 | .unwrap_or_else(|_| "Failed to print program stdout".to_string()) 69 | ); 70 | } else { 71 | eprintln!("Shader compilation failed. Status: {}", output.status); 72 | eprint!( 73 | "stdout: {}", 74 | String::from_utf8(output.stdout) 75 | .unwrap_or_else(|_| "Failed to print program stdout".to_string()) 76 | ); 77 | eprint!( 78 | "stderr: {}", 79 | String::from_utf8(output.stderr) 80 | .unwrap_or_else(|_| "Failed to print program stderr".to_string()) 81 | ); 82 | panic!("Shader compilation failed. Status: {}", output.status); 83 | } 84 | } 85 | Err(error) => { 86 | panic!("Failed to compile shader. Cause: {error}"); 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/AnimationCompute.comp: -------------------------------------------------------------------------------- 1 | #version 460 2 | #include "lib/RayTracingCommons.glsl" 3 | 4 | #define MAX_BONES_PER_SKIN 256 5 | struct Skin { 6 | mat4 bones[MAX_BONES_PER_SKIN]; 7 | }; 8 | layout (local_size_x = 256, local_size_y = 1,local_size_z = 1) in; 9 | 10 | layout(binding = VERTEX_BIND, set = 0) readonly buffer Vertices_in { Vertex vin[]; }; 11 | layout(binding = ANIMATION_BIND, set = 0) writeonly buffer Vertices_out { Vertex vout[]; }; 12 | layout(binding = SKIN_BIND, set = 0) readonly buffer Skins { Skin skins[]; }; 13 | 14 | void main() { 15 | uint gID = gl_GlobalInvocationID.x; 16 | if (gID >= vin.length()) { 17 | return; 18 | } 19 | Vertex v = vin[gID]; 20 | int skin_index = v.skin_index; 21 | if (skin_index >= 0) { 22 | Skin skin = skins[skin_index]; 23 | uvec4 joints = v.joints; 24 | vec4 weights = v.weights; 25 | mat4 transform = 26 | weights.x * skin.bones[joints.x] + 27 | weights.y * skin.bones[joints.y] + 28 | weights.z * skin.bones[joints.z] + 29 | weights.w * skin.bones[joints.w]; 30 | 31 | vec4 pos = transform * vec4(v.pos, 1.); 32 | v.pos = pos.xyz; 33 | v.normal = normalize((transform * vec4(v.normal, 0.)).xyz); 34 | float w = v.tangent.w; 35 | v.tangent = normalize((transform * vec4(v.tangent.xyz, 0.))); 36 | v.tangent.w = w; 37 | } 38 | vout[gID] = v; 39 | } 40 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/RayTracing.rahit: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_EXT_nonuniform_qualifier : require 3 | #extension GL_GOOGLE_include_directive : require 4 | #extension GL_EXT_ray_tracing : require 5 | // https://developer.nvidia.com/blog/best-practices-for-using-nvidia-rtx-ray-tracing-updated/ 6 | // Any-hit shader 7 | // 8 | //Prefer unified and simplified any-hit shaders. 9 | // An any-hit shader is potentially executed a lot during ray traversal, 10 | // and it interrupts the hardware intersection search. 11 | // The cost of any-hit shaders can have a significant effect on overall performance. 12 | // I recommend having a unified and simplified any-hit shader in a ray-tracing pass. 13 | // Also, the full register capacity of the GPU is not available for any-hit shaders, as part of it is consumed by the driver for storing the ray state. 14 | // 15 | //Optimize access to material data. 16 | // In any-hit shaders, optimal access to material data is often crucial. 17 | // A series of dependent memory accesses is a common pattern. 18 | // Load vertex indices, vertex data, and sample textures. 19 | // When possible, removing indirections from that path is beneficial. 20 | // 21 | //When blending, remember the undefined order of hits. 22 | // Hits along ray are discovered and the corresponding any-hit shader invocations happen in undefined order. 23 | // This means that the blending technique must be order-independent. 24 | // It also means that to exclude hits beyond the closest opaque hit, ray distance must be limited properly. 25 | // Additionally, you may need to flag the blended geometries with NO_DUPLICATE_ANYHIT_INVOCATION to ensure the correct results. For more information, see Chapter 9 in Ray Tracing Gems. 26 | 27 | #include "lib/Random.glsl" 28 | #include "lib/RayTracingCommons.glsl" 29 | #include "lib/Material.glsl" 30 | 31 | layout(binding = VERTEX_BIND, set = 0) readonly buffer Vertices { Vertex v[]; } vertices; 32 | layout(binding = INDEX_BIND, set = 0) readonly buffer Indices { uint i[]; } indices; 33 | layout(binding = MAT_BIND, set = 0) readonly buffer Materials { MaterialRaw m[]; } materials; 34 | layout(binding = TEXTURE_BIND) uniform sampler2D[] textures; 35 | layout(binding = GEO_BIND, set = 0) readonly buffer PrimInfos { PrimInfo p[]; } primInfos; 36 | hitAttributeEXT vec2 HitAttributes; 37 | rayPayloadInEXT RayPayload Ray; 38 | 39 | const uint OPAQUE = 1; 40 | const uint MASK = 2; 41 | const uint BLEND = 3; 42 | 43 | // Performs an opacity test in any hit shader for potential hit. Returns true if hit point is transparent and can be ignored 44 | bool testOpacityAnyHit() { 45 | // Load material at hit point 46 | 47 | const PrimInfo primInfo = primInfos.p[gl_InstanceCustomIndexEXT]; 48 | const MaterialRaw mat = materials.m[primInfo.material_id]; 49 | 50 | if (mat.alpha_mode == OPAQUE) { 51 | return false; 52 | } 53 | 54 | const uint vertexOffset = primInfo.v_offset; 55 | const uint indexOffset = primInfo.i_offset + (3 * gl_PrimitiveID); 56 | 57 | const uint i0 = vertexOffset + indices.i[indexOffset]; 58 | const uint i1 = vertexOffset + indices.i[indexOffset + 1]; 59 | const uint i2 = vertexOffset + indices.i[indexOffset + 2]; 60 | 61 | const Vertex v0 = vertices.v[i0]; 62 | const Vertex v1 = vertices.v[i1]; 63 | const Vertex v2 = vertices.v[i2]; 64 | 65 | 66 | 67 | // Compute the ray hit point properties. 68 | Vertex mix_vertex; 69 | getMixVertexAndGeoNormal(v0, v1, v2, vec2(HitAttributes), mix_vertex); 70 | vec4 uv0And1= mix_vertex.uv0And1; 71 | 72 | // Interpolate Color 73 | const vec4 baseColor = mat.baseColor; 74 | vec4 color4 = mix_vertex.color * baseColor; 75 | TextureInfo baseColorTexture = mat.baseColorTexture; 76 | if (baseColorTexture.index >= 0) { 77 | color4 *= texture(textures[baseColorTexture.index], 78 | getUV(uv0And1, baseColorTexture.coord) 79 | ); 80 | } 81 | float opacity = color4.a; 82 | if (mat.workflow == SPECULAR_GLOSS_WORKFLOW) { 83 | SpecularGlossiness sg = mat.sg; 84 | vec4 diffuse_factor = sg.diffuse_factor; 85 | 86 | TextureInfo diffuse_texture = sg.diffuse_texture; 87 | if (diffuse_texture.index >= 0) { 88 | diffuse_factor *= texture(textures[diffuse_texture.index], getUV(uv0And1, diffuse_texture.coord)); 89 | } 90 | color4 = mix_vertex.color * diffuse_factor; 91 | 92 | opacity = color4.a; 93 | } 94 | 95 | // Decide whether this hit is opaque or not according to chosen alpha testing mode 96 | if (mat.alpha_mode == MASK) { 97 | // When alphaMode is set to MASK the alphaCutoff property specifies the cutoff threshold. If the alpha value is greater than or equal to the alphaCutoff value then it is rendered as fully opaque, otherwise, it is rendered as fully transparent. alphaCutoff value is ignored for other modes. 98 | return (opacity < mat.alpha_cutoff); 99 | } else { 100 | // Alpha blending mode 101 | float u = rand(Ray.rngState); // If you want alpha blending, there should be a random u. Semi-transparent things are, however, better rendered using refracted rays with real IoR 102 | return (opacity <= u); 103 | } 104 | } 105 | 106 | void main() { 107 | if (testOpacityAnyHit()) { 108 | ignoreIntersectionEXT; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/RayTracing.rgen: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_ARB_gpu_shader_int64 : require 3 | #extension GL_ARB_shader_clock : require 4 | #extension GL_GOOGLE_include_directive : require 5 | #extension GL_EXT_ray_tracing : require 6 | 7 | #include "lib/Heatmap.glsl" 8 | #include "lib/Random.glsl" 9 | #include "lib/RayTracingCommons.glsl" 10 | #include "lib/Camera.glsl" 11 | #include "lib/UniformBufferObject.glsl" 12 | #include "lib/Material.glsl" 13 | #include "lib/Tonemapping.glsl" 14 | 15 | #define MIN_BOUNCES 3 16 | 17 | layout(binding = AS_BIND, set = 0) uniform accelerationStructureEXT Scene; 18 | layout(binding = ACC_BIND, set = 1, rgba32f) uniform image2D AccumulationImage; 19 | layout(binding = STORAGE_BIND, set = 1, rgba8) uniform image2D OutputImage; 20 | layout(binding = UNIFORM_BIND, set = 0) readonly uniform UniformBufferObjectStruct { UniformBufferObject ubo; }; 21 | 22 | layout(location = 0) rayPayloadEXT RayPayload Ray; 23 | 24 | 25 | void main() 26 | { 27 | const uint64_t clock = ubo.mapping == HEAT ? clockARB() : 0; 28 | 29 | // Initialise separate random seeds for the pixel and the rays. 30 | // - pixel: we want the same random seed for each pixel to get a homogeneous anti-aliasing. 31 | // - ray: we want a noisy random seed, different for each pixel. 32 | uint pixelRandomSeed = ubo.frame_count; 33 | Ray.RandomSeed = InitRandomSeed(InitRandomSeed(gl_LaunchIDEXT.x, gl_LaunchIDEXT.y), uint(clockARB())); 34 | RngStateType rngState = initRNG(uvec2(gl_LaunchIDEXT), uvec2(gl_LaunchSizeEXT.xy), uint(pixelRandomSeed)); 35 | // switch (ubo.debug) { 36 | // case 0: 37 | // Ray.rngState = initRNG(uvec2(gl_LaunchIDEXT), uvec2(gl_LaunchSizeEXT.xy), uint(clockARB())); 38 | // break; 39 | // case 1: 40 | Ray.rngState = initRNG(uvec2(gl_LaunchIDEXT), uvec2(gl_LaunchSizeEXT.xy), uint(clockARB())); 41 | // break; 42 | // case 2: 43 | // Ray.rngState = initRNG(uvec2(gl_LaunchIDEXT), uvec2(gl_LaunchSizeEXT.xy), uint(ubo.TotalNumberOfSamples)); 44 | // } 45 | 46 | vec3 radiance = vec3(0); 47 | // Ray.instance_id = 0xFFFFFFFF; 48 | // Accumulate all the rays for this pixels. 49 | for (uint s = 0; s < ubo.NumberOfSamples; ++s) 50 | { 51 | //if (ubo.NumberOfSamples != ubo.TotalNumberOfSamples) break; 52 | vec2 pixel; 53 | vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5); 54 | if (ubo.antialiasing) { 55 | vec2 offset = vec2(rand(rngState), rand(rngState)) - 0.5; 56 | pixel = pixelCenter + offset; 57 | } else { 58 | pixel = pixelCenter; 59 | } 60 | const vec2 uv = (pixel / gl_LaunchSizeEXT.xy) * 2.0 - 1.0; 61 | 62 | vec2 offset = ubo.Aperture / 2 * RandomInUnitDisk(Ray.RandomSeed); 63 | vec4 origin = ubo.ModelViewInverse * vec4(offset, 0, 1); 64 | vec4 target = ubo.ProjectionInverse * (vec4(uv.x, uv.y, 1, 1)); 65 | vec4 direction = ubo.ModelViewInverse * vec4(normalize(target.xyz * ubo.FocusDistance - vec3(offset, 0)), 0); 66 | float tFar = tMax; 67 | if (ubo.orthographic_fov_dis > 0.) 68 | { 69 | vec2 new_uv = (1. + ubo.orthographic_fov_dis) * uv; 70 | origin = vec4(new_uv.x , -new_uv.y, 0, 1); 71 | origin = ubo.ModelViewInverse * origin; 72 | direction = vec4(0, 0, -1, 0); 73 | direction = ubo.ModelViewInverse * direction; 74 | tFar = 10 * tMax; 75 | } 76 | 77 | 78 | vec3 throughput = vec3(1); 79 | Ray.t = 0; 80 | Ray.volume_dis = -1.; 81 | // Ray scatters are handled in this loop. There are no recursive traceRayEXT() calls in other shaders. 82 | uint rayFlags = gl_RayFlagsNoneEXT; 83 | if (ubo.fully_opaque) { 84 | rayFlags |= gl_RayFlagsOpaqueEXT; 85 | } 86 | for (uint b = 0; b < ubo.NumberOfBounces; b++) 87 | { 88 | 89 | traceRayEXT( 90 | Scene, rayFlags, 0xff, 91 | 0 /*sbtRecordOffset*/, 0 /*sbtRecordStride*/, 0 /*missIndex*/, 92 | origin.xyz, tMin, direction.xyz, tFar, 0 /*payload*/); 93 | 94 | const vec3 hitColor = Ray.hitValue; 95 | const float t = Ray.t; 96 | const bool isScattered = Ray.needScatter; 97 | 98 | radiance += throughput * Ray.emittance; 99 | Ray.emittance = vec3(0.); 100 | 101 | // Should sample light here 102 | 103 | // No need to do more work if it is the last bounce 104 | if (b + 1 == ubo.NumberOfBounces) { 105 | break; 106 | } 107 | 108 | // Russian roulette 109 | if (b > MIN_BOUNCES) { 110 | float rrProbability = clamp(luminance(throughput), 0.01, 0.95); 111 | float prop = rand(rngState); 112 | if (rrProbability < prop) { 113 | break; 114 | } 115 | else { 116 | throughput /= rrProbability; 117 | } 118 | } 119 | 120 | throughput *= hitColor; 121 | // Trace missed, or end of trace. 122 | 123 | if (!isScattered || t < 0) { 124 | break; 125 | } 126 | // Trace hit. 127 | origin = vec4(Ray.hitPoint, 1.); 128 | direction = vec4(Ray.scatterDirection, 0); 129 | } 130 | } 131 | 132 | const bool accumulate = ubo.NumberOfSamples != ubo.TotalNumberOfSamples; 133 | const vec3 accumulatedColor = (accumulate ? imageLoad(AccumulationImage, ivec2(gl_LaunchIDEXT.xy)) : vec4(0)).rgb + radiance; 134 | 135 | radiance = accumulatedColor / ubo.TotalNumberOfSamples; 136 | 137 | uint tone_mapping_mode = ubo.tone_mapping_mode; 138 | vec3 color; 139 | if (tone_mapping_mode == TONE_MAP_MODE_DEFAULT) { 140 | color = defaultToneMap(radiance); 141 | } else if (tone_mapping_mode == TONE_MAP_MODE_UNCHARTED) { 142 | color = toneMapUncharted(radiance); 143 | } else if (tone_mapping_mode == TONE_MAP_MODE_HEJL_RICHARD) { 144 | color = toneMapHejlRichard(radiance); 145 | } else if (tone_mapping_mode == TONE_MAP_MODE_ACES) { 146 | color = toneMapACES(radiance); 147 | } else { 148 | color = LINEARtoSRGB(radiance); 149 | } 150 | 151 | switch (ubo.mapping) 152 | { 153 | case HEAT: 154 | const uint64_t deltaTime = clockARB() - clock; 155 | const float heatmapScale = 1000000.0f * ubo.HeatmapScale * ubo.HeatmapScale; 156 | const float deltaTimeScaled = clamp(float(deltaTime) / heatmapScale, 0.0f, 1.0f); 157 | color = heatmap(deltaTimeScaled); 158 | break; 159 | case DISTANCE: 160 | color = vec3(min((ubo.HeatmapScale - max(Ray.t, tMin)) / ubo.HeatmapScale, 1)); 161 | break; 162 | case RENDER: 163 | break; 164 | } 165 | imageStore(AccumulationImage, ivec2(gl_LaunchIDEXT.xy), vec4(accumulatedColor, 0)); 166 | imageStore(OutputImage, ivec2(gl_LaunchIDEXT.xy), vec4(color, 1.)); 167 | } 168 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/RayTracing.rmiss: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_GOOGLE_include_directive : require 3 | #extension GL_EXT_ray_tracing : require 4 | #include "lib/RayTracingCommons.glsl" 5 | #include "lib/UniformBufferObject.glsl" 6 | 7 | layout(binding = UNIFORM_BIND) readonly uniform UniformBufferObjectStruct { UniformBufferObject Camera; }; 8 | layout(binding = DLIGHT_BIND) readonly buffer Lights { Light[] lights; }; 9 | 10 | layout(binding = SKYBOX_BIND) uniform samplerCube skybox; 11 | 12 | layout(location = 0) rayPayloadInEXT RayPayload Ray; 13 | 14 | void main() 15 | { 16 | vec3 light_acc = vec3(0.); 17 | vec3 ray_direction = normalize(gl_WorldRayDirectionEXT.xyz); 18 | if (Ray.t != 0) { 19 | for(int i = 0; i < lights.length(); i++) { 20 | Light li = lights[i]; 21 | float cos = dot(normalize(li.transform.xyz), ray_direction); 22 | if (cos < 0.) { 23 | light_acc += -cos * li.color.xyz * li.intensity; 24 | } 25 | } 26 | } 27 | if (Camera.HasSky) 28 | { 29 | // Sky color 30 | // const float t = 0.5 * (normalize(gl_WorldRayDirectionEXT).y + 1); 31 | // const vec3 skyColor = mix(vec3(1.0), vec3(0.5, 0.7, 1.0), t); 32 | const vec3 skyColor = texture(skybox, ray_direction).rgb; 33 | light_acc += skyColor + light_acc; 34 | } else 35 | { 36 | light_acc += vec3(0.01); 37 | } 38 | Ray.hitValue = vec3(0.); 39 | Ray.needScatter = false; 40 | Ray.emittance = vec3(light_acc); 41 | if (lights.length() == 0) { 42 | Ray.emittance = vec3(0.); 43 | } 44 | Ray.t = -1.; 45 | } 46 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/RayTracing.shadow.rahit: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_EXT_nonuniform_qualifier : require 3 | #extension GL_GOOGLE_include_directive : require 4 | #extension GL_EXT_ray_tracing : require 5 | // https://developer.nvidia.com/blog/best-practices-for-using-nvidia-rtx-ray-tracing-updated/ 6 | // Any-hit shader 7 | // 8 | //Prefer unified and simplified any-hit shaders. 9 | // An any-hit shader is potentially executed a lot during ray traversal, 10 | // and it interrupts the hardware intersection search. 11 | // The cost of any-hit shaders can have a significant effect on overall performance. 12 | // I recommend having a unified and simplified any-hit shader in a ray-tracing pass. 13 | // Also, the full register capacity of the GPU is not available for any-hit shaders, as part of it is consumed by the driver for storing the ray state. 14 | // 15 | //Optimize access to material data. 16 | // In any-hit shaders, optimal access to material data is often crucial. 17 | // A series of dependent memory accesses is a common pattern. 18 | // Load vertex indices, vertex data, and sample textures. 19 | // When possible, removing indirections from that path is beneficial. 20 | // 21 | //When blending, remember the undefined order of hits. 22 | // Hits along ray are discovered and the corresponding any-hit shader invocations happen in undefined order. 23 | // This means that the blending technique must be order-independent. 24 | // It also means that to exclude hits beyond the closest opaque hit, ray distance must be limited properly. 25 | // Additionally, you may need to flag the blended geometries with NO_DUPLICATE_ANYHIT_INVOCATION to ensure the correct results. For more information, see Chapter 9 in Ray Tracing Gems. 26 | 27 | #include "lib/Random.glsl" 28 | #include "lib/RayTracingCommons.glsl" 29 | #include "lib/Material.glsl" 30 | 31 | layout(binding = VERTEX_BIND, set = 0) readonly buffer Vertices { Vertex v[]; } vertices; 32 | layout(binding = INDEX_BIND, set = 0) readonly buffer Indices { uint i[]; } indices; 33 | layout(binding = MAT_BIND, set = 0) readonly buffer Materials { MaterialRaw m[]; } materials; 34 | layout(binding = TEXTURE_BIND) uniform sampler2D[] textures; 35 | layout(binding = GEO_BIND, set = 0) readonly buffer PrimInfos { PrimInfo p[]; } primInfos; 36 | hitAttributeEXT vec2 HitAttributes; 37 | rayPayloadInEXT ShadowRay Ray; 38 | 39 | const uint OPAQUE = 1; 40 | const uint MASK = 2; 41 | const uint BLEND = 3; 42 | 43 | // Performs an opacity test in any hit shader for potential hit. Returns true if hit point is transparent and can be ignored 44 | bool testOpacityAnyHit() { 45 | // Load material at hit point 46 | 47 | const PrimInfo primInfo = primInfos.p[gl_InstanceCustomIndexEXT]; 48 | const MaterialRaw mat = materials.m[primInfo.material_id]; 49 | 50 | if (mat.alpha_mode == OPAQUE) { 51 | return false; 52 | } 53 | 54 | const uint vertexOffset = primInfo.v_offset; 55 | const uint indexOffset = primInfo.i_offset + (3 * gl_PrimitiveID); 56 | 57 | const uint i0 = vertexOffset + indices.i[indexOffset]; 58 | const uint i1 = vertexOffset + indices.i[indexOffset + 1]; 59 | const uint i2 = vertexOffset + indices.i[indexOffset + 2]; 60 | 61 | const Vertex v0 = vertices.v[i0]; 62 | const Vertex v1 = vertices.v[i1]; 63 | const Vertex v2 = vertices.v[i2]; 64 | 65 | 66 | 67 | // Compute the ray hit point properties. 68 | Vertex mix_vertex; 69 | getMixVertexAndGeoNormal(v0, v1, v2, vec2(HitAttributes), mix_vertex); 70 | vec4 uv0And1= mix_vertex.uv0And1; 71 | 72 | // Interpolate Color 73 | const vec4 baseColor = mat.baseColor; 74 | vec4 color4 = mix_vertex.color * baseColor; 75 | TextureInfo baseColorTexture = mat.baseColorTexture; 76 | if (baseColorTexture.index >= 0) { 77 | color4 *= texture(textures[baseColorTexture.index], 78 | getUV(uv0And1, baseColorTexture.coord) 79 | ); 80 | } 81 | float opacity = color4.a; 82 | if (mat.workflow == SPECULAR_GLOSS_WORKFLOW) { 83 | SpecularGlossiness sg = mat.sg; 84 | vec4 diffuse_factor = sg.diffuse_factor; 85 | 86 | TextureInfo diffuse_texture = sg.diffuse_texture; 87 | if (diffuse_texture.index >= 0) { 88 | diffuse_factor *= texture(textures[diffuse_texture.index], getUV(uv0And1, diffuse_texture.coord)); 89 | } 90 | color4 = mix_vertex.color * diffuse_factor; 91 | 92 | opacity = color4.a; 93 | } 94 | 95 | // Decide whether this hit is opaque or not according to chosen alpha testing mode 96 | if (mat.alpha_mode == MASK) { 97 | // When alphaMode is set to MASK the alphaCutoff property specifies the cutoff threshold. If the alpha value is greater than or equal to the alphaCutoff value then it is rendered as fully opaque, otherwise, it is rendered as fully transparent. alphaCutoff value is ignored for other modes. 98 | return (opacity < mat.alpha_cutoff); 99 | } else { 100 | // Alpha blending mode 101 | float u = rand(Ray.rngState); // If you want alpha blending, there should be a random u. Semi-transparent things are, however, better rendered using refracted rays with real IoR 102 | return (opacity <= u); 103 | } 104 | } 105 | 106 | void main() { 107 | if (testOpacityAnyHit()) { 108 | ignoreIntersectionEXT; 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/RayTracing.shadow.rmiss: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_GOOGLE_include_directive : require 3 | #extension GL_EXT_ray_tracing : require 4 | #include "lib/RayTracingCommons.glsl" 5 | 6 | 7 | 8 | layout(location = 0) rayPayloadInEXT ShadowRay Ray; 9 | 10 | void main() 11 | { 12 | Ray.shadow = false; 13 | } 14 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/lib/Camera.glsl: -------------------------------------------------------------------------------- 1 | // Ray tracing Gems 2, Chapter 3 2 | const float tMin = 0.001; 3 | const float tMax = 10000.0; 4 | struct RayInfo { 5 | vec3 pos; 6 | float min; 7 | vec3 dir; 8 | float max; 9 | }; -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/lib/Heatmap.glsl: -------------------------------------------------------------------------------- 1 | 2 | // Profiling DXR Shaders with Timer Instrumentation 3 | // https://developer.nvidia.com/blog/profiling-dxr-shaders-with-timer-instrumentation/ 4 | vec3 heatmap(float t) 5 | { 6 | const vec3 c[10] = { 7 | vec3(0.0f / 255.0f, 2.0f / 255.0f, 91.0f / 255.0f), 8 | vec3(0.0f / 255.0f, 108.0f / 255.0f, 251.0f / 255.0f), 9 | vec3(0.0f / 255.0f, 221.0f / 255.0f, 221.0f / 255.0f), 10 | vec3(51.0f / 255.0f, 221.0f / 255.0f, 0.0f / 255.0f), 11 | vec3(255.0f / 255.0f, 252.0f / 255.0f, 0.0f / 255.0f), 12 | vec3(255.0f / 255.0f, 180.0f / 255.0f, 0.0f / 255.0f), 13 | vec3(255.0f / 255.0f, 104.0f / 255.0f, 0.0f / 255.0f), 14 | vec3(226.0f / 255.0f, 22.0f / 255.0f, 0.0f / 255.0f), 15 | vec3(191.0f / 255.0f, 0.0f / 255.0f, 83.0f / 255.0f), 16 | vec3(145.0f / 255.0f, 0.0f / 255.0f, 65.0f / 255.0f) 17 | }; 18 | 19 | const float s = t * 10.0f; 20 | 21 | const int cur = int(s) <= 9 ? int(s) : 9; 22 | const int prv = cur >= 1 ? cur - 1 : 0; 23 | const int nxt = cur < 9 ? cur + 1 : 9; 24 | 25 | const float blur = 0.8f; 26 | 27 | const float wc = smoothstep(float(cur) - blur, float(cur) + blur, s) * (1.0f - smoothstep(float(cur + 1) - blur, float(cur + 1) + blur, s)); 28 | const float wp = 1.0f - smoothstep(float(cur) - blur, float(cur) + blur, s); 29 | const float wn = smoothstep(float(cur + 1) - blur, float(cur + 1) + blur, s); 30 | 31 | const vec3 r = wc * c[cur] + wp * c[prv] + wn * c[nxt]; 32 | return vec3(clamp(r.x, 0.0f, 1.0f), clamp(r.y, 0.0f, 1.0f), clamp(r.z, 0.0f, 1.0f)); 33 | } -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/lib/Material.glsl: -------------------------------------------------------------------------------- 1 | #include "PBR.glsl" 2 | struct TextureInfo { 3 | int index; 4 | int coord; 5 | }; 6 | 7 | struct TransmissionInfo { 8 | TextureInfo transmission_texture; 9 | float transmission_factor; 10 | bool exist; 11 | }; 12 | 13 | struct MetallicRoughnessInfo { 14 | float metallic_factor; 15 | float roughness_factor; 16 | TextureInfo metallic_roughness_texture; 17 | }; 18 | 19 | struct VolumeInfo { 20 | vec3 attenuation_color; 21 | float thickness_factor; 22 | TextureInfo thickness_texture; 23 | float attenuation_distance; 24 | bool exists; 25 | }; 26 | 27 | struct SpecularInfo { 28 | TextureInfo specular_texture; 29 | TextureInfo specular_color_texture; 30 | vec4 specular_color_factor; 31 | float specular_factor; 32 | bool exist; 33 | vec2 _padding; 34 | }; 35 | 36 | 37 | struct SpecularGlossiness { 38 | vec4 diffuse_factor; 39 | vec4 specular_glossiness_factor; 40 | TextureInfo diffuse_texture; 41 | TextureInfo specular_glossiness_texture; 42 | }; 43 | 44 | const uint METALLIC_WORKFLOW = 0; 45 | const uint SPECULAR_GLOSS_WORKFLOW = 1; 46 | 47 | // https://developer.nvidia.com/blog/best-practices-for-using-nvidia-rtx-ray-tracing-updated/ 48 | // Use a separate hit shader for each material model(for example: metal?). Reducing code and data divergence within hit shaders is helpful, especially with incoherent rays. In particular, avoid übershaders that manually switch between material models. Implementing each required material model in a separate hit shader gives the system the best possibilities to manage divergent hit shading. 49 | // 50 | //When the material model allows a unified shader without much divergence, you can consider using a common hit shader for geometries with various materials. 51 | struct MaterialRaw { 52 | uint alpha_mode; 53 | float alpha_cutoff; 54 | bool double_sided; 55 | uint workflow; 56 | vec2 _padding; 57 | 58 | TextureInfo baseColorTexture; 59 | vec4 baseColor; 60 | 61 | MetallicRoughnessInfo metallicRoughnessInfo; 62 | TextureInfo normal_texture; 63 | TextureInfo emissive_texture; 64 | vec4 emissive_factor; 65 | 66 | TextureInfo occlusion_texture; 67 | float ior; 68 | bool unlit; 69 | TransmissionInfo transmission_info; 70 | VolumeInfo volume_info; 71 | SpecularInfo specular_info; 72 | SpecularGlossiness sg; 73 | }; 74 | 75 | const float c_MinRoughness = 0.04; 76 | // Gets metallic factor from specular glossiness workflow inputs 77 | // https://github.com/SaschaWillems/Vulkan-glTF-PBR/blob/master/data/shaders/pbr_khr.frag 78 | float convertMetallic(vec3 diffuse, vec3 specular, float maxSpecular) { 79 | float perceivedDiffuse = sqrt(0.299 * diffuse.r * diffuse.r + 0.587 * diffuse.g * diffuse.g + 0.114 * diffuse.b * diffuse.b); 80 | float perceivedSpecular = sqrt(0.299 * specular.r * specular.r + 0.587 * specular.g * specular.g + 0.114 * specular.b * specular.b); 81 | if (perceivedSpecular < c_MinRoughness) { 82 | return 0.0; 83 | } 84 | float a = c_MinRoughness; 85 | float b = perceivedDiffuse * (1.0 - maxSpecular) / (1.0 - c_MinRoughness) + perceivedSpecular - 2.0 * c_MinRoughness; 86 | float c = c_MinRoughness - perceivedSpecular; 87 | float D = max(b * b - 4.0 * a * c, 0.0); 88 | return clamp((-b + sqrt(D)) / (2.0 * a), 0.0, 1.0); 89 | } 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/lib/PunctualLight.glsl: -------------------------------------------------------------------------------- 1 | #ifndef VIRTUAL_LIGHT 2 | #define VIRTUAL_LIGHT 3 | 4 | const uint DIRECT_LIGHT = 0; 5 | const uint POINT_LIGHT = 1; 6 | const uint SPOT_LIGHT = 2; 7 | 8 | struct Light { 9 | vec4 color; 10 | vec4 transform; 11 | uint kind; 12 | float range; 13 | float intensity; 14 | float _padding; 15 | }; 16 | 17 | vec3 getLightIntensityAtPoint(Light light, float distance) { 18 | vec3 color = light.intensity * light.color.rgb; 19 | if (light.kind == POINT_LIGHT) { 20 | // Cem Yuksel's improved attenuation avoiding singularity at distance=0 21 | // Source: http://www.cemyuksel.com/research/pointlightattenuation/ 22 | const float radius = 0.5f; //< We hardcode radius at 0.5, but this should be a light parameter 23 | const float radiusSquared = radius * radius; 24 | const float distanceSquared = distance * distance; 25 | const float attenuation = 2.0f / (distanceSquared + radiusSquared + distance * sqrt(distanceSquared + radiusSquared)); 26 | return color * attenuation; 27 | } else { 28 | return color; 29 | } 30 | } 31 | #endif -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/lib/Random.glsl: -------------------------------------------------------------------------------- 1 | #ifndef RANDOM 2 | #define RANDOM 3 | #extension GL_EXT_control_flow_attributes : require 4 | 5 | #define RngStateType uvec4 6 | #define RIS_CANDIDATES_LIGHTS 3 7 | 8 | // Generates a seed for a random number generator from 2 inputs plus a backoff 9 | // https://github.com/nvpro-samples/optix_prime_baking/blob/332a886f1ac46c0b3eea9e89a59593470c755a0e/random.h 10 | // https://github.com/nvpro-samples/vk_raytracing_tutorial_KHR/tree/master/ray_tracing_jitter_cam 11 | // https://en.wikipedia.org/wiki/Tiny_Encryption_Algorithm 12 | uint InitRandomSeed(uint val0, uint val1) 13 | { 14 | uint v0 = val0, v1 = val1, s0 = 0; 15 | 16 | [[unroll]] 17 | for (uint n = 0; n < 16; n++) 18 | { 19 | s0 += 0x9e3779b9; 20 | v0 += ((v1 << 4) + 0xa341316c) ^ (v1 + s0) ^ ((v1 >> 5) + 0xc8013ea4); 21 | v1 += ((v0 << 4) + 0xad90777d) ^ (v0 + s0) ^ ((v0 >> 5) + 0x7e95761e); 22 | } 23 | 24 | return v0; 25 | } 26 | 27 | uint RandomInt(inout uint seed) 28 | { 29 | // LCG values from Numerical Recipes 30 | return (seed = 1664525 * seed + 1013904223); 31 | } 32 | 33 | float RandomFloat(inout uint seed) 34 | { 35 | //// Float version using bitmask from Numerical Recipes 36 | //const uint one = 0x3f800000; 37 | //const uint msk = 0x007fffff; 38 | //return uintBitsToFloat(one | (msk & (RandomInt(seed) >> 9))) - 1; 39 | 40 | // Faster version from NVIDIA examples; quality good enough for our use case. 41 | return (float(RandomInt(seed) & 0x00FFFFFFu) / float(0x01000000)); 42 | } 43 | 44 | vec2 RandomInUnitDisk(inout uint seed) 45 | { 46 | for (;;) 47 | { 48 | const vec2 p = 2 * vec2(RandomFloat(seed), RandomFloat(seed)) - 1; 49 | if (dot(p, p) < 1) 50 | { 51 | return p; 52 | } 53 | } 54 | } 55 | 56 | vec3 RandomInUnitSphere(inout uint seed) 57 | { 58 | for (;;) 59 | { 60 | const vec3 p = 2 * vec3(RandomFloat(seed), RandomFloat(seed), RandomFloat(seed)) - 1; 61 | if (dot(p, p) < 1) 62 | { 63 | return p; 64 | } 65 | } 66 | } 67 | 68 | 69 | // Converts unsigned integer into float int range <0; 1) by using 23 most significant bits for mantissa 70 | float uintToFloat(uint x) { 71 | return uintBitsToFloat(0x3f800000u | (x >> 9)) - 1.0f; 72 | } 73 | 74 | // Initialize RNG for given pixel, and frame number (PCG version) 75 | RngStateType initRNG(uvec2 pixelCoords, uvec2 resolution, uint frameNumber) { 76 | return RngStateType(pixelCoords.xy, frameNumber, 0); //< Seed for PCG uses a sequential sample number in 4th channel, which increments on every RNG call and starts from 0 77 | } 78 | 79 | // PCG random numbers generator 80 | // Source: "Hash Functions for GPU Rendering" by Jarzynski & Olano 81 | uvec4 pcg4d(uvec4 v) 82 | { 83 | v = v * 1664525u + 1013904223u; 84 | 85 | v.x += v.y * v.w; 86 | v.y += v.z * v.x; 87 | v.z += v.x * v.y; 88 | v.w += v.y * v.z; 89 | 90 | v = v ^ (v >> 16u); 91 | 92 | v.x += v.y * v.w; 93 | v.y += v.z * v.x; 94 | v.z += v.x * v.y; 95 | v.w += v.y * v.z; 96 | 97 | return v; 98 | } 99 | 100 | 101 | // Return random float in <0; 1) range (PCG version) 102 | float rand(inout RngStateType rngState) { 103 | rngState.w++; //< Increment sample index 104 | return uintToFloat(pcg4d(rngState).x); 105 | } 106 | 107 | // Jenkins's "one at a time" hash function 108 | uint jenkinsHash(uint x) { 109 | x += x << 10; 110 | x ^= x >> 6; 111 | x += x << 3; 112 | x ^= x >> 11; 113 | x += x << 15; 114 | return x; 115 | } 116 | 117 | 118 | // Maps integers to colors using the hash function (generates pseudo-random colors) 119 | vec3 hashAndColor(uint i) { 120 | uint hash = jenkinsHash(i); 121 | float r = ((hash >> 0) & 0xFFu) / 255.0f; 122 | float g = ((hash >> 8) & 0xFFu) / 255.0f; 123 | float b = ((hash >> 16) & 0xFFu) / 255.0f; 124 | return vec3(r, g, b); 125 | } 126 | #endif 127 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/lib/RayTracingCommons.glsl: -------------------------------------------------------------------------------- 1 | #include "Random.glsl" 2 | #include "PunctualLight.glsl" 3 | #define STANDARD_RAY_INDEX 0 4 | #define SHADOW_RAY_INDEX 1 5 | #define SHADOW_RAY_IN_RIS 0 6 | 7 | // ALLOW_UPDATE 8 | // Consider writing a safe default value to unused payload fields. 9 | // When some shader doesn’t use all fields in the payload, which are required by other shaders, it can be beneficial to still write a safe default value to the unused fields. 10 | // This allows the compiler to discard the unused input value and use the payload register for other purposes before writing to it. 11 | struct RayPayload 12 | { 13 | vec3 hitValue; 14 | vec3 hitPoint; 15 | float t; 16 | vec3 scatterDirection; 17 | bool needScatter; 18 | uint RandomSeed; 19 | vec3 emittance; 20 | RngStateType rngState; 21 | float volume_dis; 22 | // uint instance_id; 23 | // vec2 bary; 24 | }; 25 | 26 | struct ShadowRay { 27 | bool shadow; 28 | RngStateType rngState; 29 | }; 30 | 31 | 32 | vec2 Mix(vec2 a, vec2 b, vec2 c, vec3 barycentrics) 33 | { 34 | return a * barycentrics.x + b * barycentrics.y + c * barycentrics.z; 35 | } 36 | 37 | vec3 Mix(vec3 a, vec3 b, vec3 c, vec3 barycentrics) 38 | { 39 | return a * barycentrics.x + b * barycentrics.y + c * barycentrics.z; 40 | } 41 | 42 | vec4 Mix(vec4 a, vec4 b, vec4 c, vec3 barycentrics) 43 | { 44 | return a * barycentrics.x + b * barycentrics.y + c * barycentrics.z; 45 | } 46 | 47 | struct PrimInfo { 48 | uint v_offset; 49 | uint i_offset; 50 | uint material_id; 51 | uint _padding; 52 | }; 53 | 54 | struct Vertex { 55 | vec3 pos; 56 | vec3 normal; 57 | vec4 tangent; 58 | vec4 color; 59 | vec4 weights; 60 | uvec4 joints; 61 | vec4 uv0And1; 62 | int skin_index; 63 | }; 64 | 65 | vec2 getUV(vec4 uv0And1, uint index) { 66 | if (index == 0) { 67 | return uv0And1.xy; 68 | } else if (index == 1) { 69 | return uv0And1.zw; 70 | } 71 | return vec2(0.); 72 | } 73 | 74 | 75 | vec3 calculate_geo_normal(const vec3 p0, const vec3 p1, const vec3 p2) { 76 | vec3 v1 = p2 - p0; 77 | vec3 edge21 = p2 - p1; 78 | vec3 v0 = p1 - p0; 79 | return cross(v0, v1); 80 | } 81 | 82 | vec3 getMixVertexAndGeoNormal(const Vertex v0, 83 | const Vertex v1, 84 | const Vertex v2, 85 | const vec2 attrs, 86 | out Vertex mix_v) { 87 | const vec3 barycentricCoords = vec3(1.0 - attrs.x - attrs.y, attrs.x, attrs.y); 88 | 89 | mix_v.uv0And1 = Mix(v0.uv0And1, v1.uv0And1, v2.uv0And1, barycentricCoords); 90 | mix_v.pos = Mix(v0.pos, v1.pos, v2.pos, barycentricCoords); 91 | mix_v.color = Mix(v0.color, v1.color, v2.color, barycentricCoords); 92 | mix_v.normal = normalize(Mix(normalize(v0.normal), normalize(v1.normal), 93 | normalize(v2.normal), barycentricCoords)); 94 | mix_v.tangent = normalize(Mix(v0.tangent, v1.tangent, v2.tangent, barycentricCoords)); 95 | mix_v.skin_index = v0.skin_index; 96 | return calculate_geo_normal(v0.pos, v1.pos, v2.pos); 97 | } 98 | 99 | vec3 bary_to_color(vec2 bary) { 100 | return vec3(1 - bary[0] - bary[1], bary); 101 | } 102 | 103 | vec3 offset_ray(const vec3 p, const vec3 n) { 104 | const float origin = 1. / 32.; 105 | const float float_scale = 1. / 65536.; 106 | const float int_scale = 256.; 107 | 108 | ivec3 of_i = ivec3(n * int_scale) ; 109 | vec3 p_i = vec3( 110 | intBitsToFloat(floatBitsToInt(p.x) + ((p.x < 0) ? -of_i.x : of_i.x)), 111 | intBitsToFloat(floatBitsToInt(p.y) + ((p.y < 0) ? -of_i.y : of_i.y)), 112 | intBitsToFloat(floatBitsToInt(p.z) + ((p.z < 0) ? -of_i.z : of_i.z)) 113 | ); 114 | return vec3( 115 | abs(p.x) < origin? p.x + float_scale * n.x: p_i.x, 116 | abs(p.y) < origin? p.y + float_scale * n.y: p_i.y, 117 | abs(p.z) < origin? p.z + float_scale * n.z: p_i.z 118 | ); 119 | } 120 | 121 | 122 | 123 | const uint AS_BIND = 0; 124 | const uint STORAGE_BIND = 1; 125 | const uint UNIFORM_BIND = 2; 126 | const uint VERTEX_BIND = 3; 127 | const uint INDEX_BIND = 4; 128 | const uint GEO_BIND = 5; 129 | const uint TEXTURE_BIND = 6; 130 | const uint ACC_BIND = 8; 131 | const uint MAT_BIND = 9; 132 | const uint DLIGHT_BIND = 10; 133 | const uint PLIGHT_BIND = 11; 134 | const uint SKYBOX_BIND = 12; 135 | const uint ANIMATION_BIND = 13; 136 | const uint SKIN_BIND = 14; 137 | 138 | 139 | const uint RENDER = 0; 140 | const uint HEAT = 1; 141 | const uint INSTANCE = 2; 142 | const uint TRIANGLE = 3; 143 | const uint DISTANCE = 4; 144 | const uint ALBEDO = 5; 145 | const uint METALLIC = 6; 146 | const uint ROUGHNESS = 7; 147 | const uint NORMAL = 8; 148 | const uint TANGENT = 9; 149 | const uint TRANSMISSION = 10; 150 | const uint GEO_ID = 11; 151 | 152 | 153 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/lib/Raytracing.shadow.rahit: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_EXT_nonuniform_qualifier : require 3 | #extension GL_GOOGLE_include_directive : require 4 | #extension GL_EXT_ray_tracing : require 5 | 6 | #include "lib/Random.glsl" 7 | #include "lib/RayTracingCommons.glsl" 8 | #include "lib/Material.glsl" 9 | 10 | layout(binding = VERTEX_BIND, set = 0) readonly buffer Vertices { Vertex v[]; } vertices; 11 | layout(binding = INDEX_BIND, set = 0) readonly buffer Indices { uint i[]; } indices; 12 | layout(binding = MAT_BIND, set = 0) readonly buffer Materials { MaterialRaw m[]; } materials; 13 | layout(binding = TEXTURE_BIND) uniform sampler2D[] textures; 14 | layout(binding = GEO_BIND, set = 0) readonly buffer PrimInfos { PrimInfo p[]; } primInfos; 15 | hitAttributeEXT vec2 HitAttributes; 16 | rayPayloadInEXT RayPayload Ray; 17 | 18 | const uint OPAQUE = 1; 19 | const uint MASK = 2; 20 | const uint BLEND = 3; 21 | 22 | // Performs an opacity test in any hit shader for potential hit. Returns true if hit point is transparent and can be ignored 23 | bool testOpacityAnyHit() { 24 | 25 | // Load material at hit point 26 | 27 | const PrimInfo primInfo = primInfos.p[gl_InstanceCustomIndexEXT]; 28 | const MaterialRaw mat = materials.m[primInfo.material_id]; 29 | 30 | const uint vertexOffset = primInfo.v_offset; 31 | const uint indexOffset = primInfo.i_offset + (3 * gl_PrimitiveID); 32 | 33 | const uint i0 = vertexOffset + indices.i[indexOffset]; 34 | const uint i1 = vertexOffset + indices.i[indexOffset + 1]; 35 | const uint i2 = vertexOffset + indices.i[indexOffset + 2]; 36 | 37 | const Vertex v0 = vertices.v[i0]; 38 | const Vertex v1 = vertices.v[i1]; 39 | const Vertex v2 = vertices.v[i2]; 40 | 41 | 42 | 43 | // Compute the ray hit point properties. 44 | const vec3 barycentricCoords = vec3(1.0 - HitAttributes.x - HitAttributes.y, HitAttributes.x, HitAttributes.y); 45 | const vec2 uvs = Mix(v0.uv0, v1.uv0, v2.uv0, barycentricCoords); 46 | 47 | 48 | const vec4 vertexColor = Mix(v0.color, v1.color, v2.color, barycentricCoords); 49 | // Also load the opacity texture if available 50 | const vec4 baseColor = mat.baseColor * vertexColor; 51 | vec4 color = baseColor; 52 | if (mat.baseColorTexture.index >= 0) { 53 | color = color * texture(textures[mat.baseColorTexture.index], uvs); 54 | } 55 | float opacity = color.a; 56 | 57 | // Decide whether this hit is opaque or not according to chosen alpha testing mode 58 | if (mat.alpha_mode == MASK) { 59 | return (opacity < mat.alpha_cutoff); 60 | } else { 61 | // Alpha blending mode 62 | uint seed = Ray.RandomSeed; 63 | float u = RandomFloat(seed); // If you want alpha blending, there should be a random u. Semi-transparent things are, however, better rendered using refracted rays with real IoR 64 | Ray.RandomSeed = seed; 65 | return (opacity < u); 66 | } 67 | } 68 | 69 | void main() { 70 | if (testOpacityAnyHit()) { 71 | ignoreIntersectionEXT; 72 | } else { 73 | terminateRayEXT; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/lib/Tonemapping.glsl: -------------------------------------------------------------------------------- 1 | const uint TONE_MAP_MODE_DEFAULT = 0; 2 | const uint TONE_MAP_MODE_UNCHARTED = 1; 3 | const uint TONE_MAP_MODE_HEJL_RICHARD = 2; 4 | const uint TONE_MAP_MODE_ACES = 3; 5 | 6 | const float GAMMA = 2.2; 7 | const float INV_GAMMA = 1.0 / GAMMA; 8 | 9 | vec3 LINEARtoSRGB(vec3 color) { 10 | return pow(color, vec3(INV_GAMMA)); 11 | } 12 | 13 | // Uncharted 2 tone map 14 | // see: http://filmicworlds.com/blog/filmic-tonemapping-operators/ 15 | vec3 toneMapUncharted2Impl(vec3 color) { 16 | const float A = 0.15; 17 | const float B = 0.50; 18 | const float C = 0.10; 19 | const float D = 0.20; 20 | const float E = 0.02; 21 | const float F = 0.30; 22 | return ((color*(A*color+C*B)+D*E)/(color*(A*color+B)+D*F))-E/F; 23 | } 24 | 25 | vec3 toneMapUncharted(vec3 color) { 26 | const float W = 11.2; 27 | color = toneMapUncharted2Impl(color * 2.0); 28 | vec3 whiteScale = 1.0 / toneMapUncharted2Impl(vec3(W)); 29 | return LINEARtoSRGB(color * whiteScale); 30 | } 31 | 32 | // Hejl Richard tone map 33 | // see: http://filmicworlds.com/blog/filmic-tonemapping-operators/ 34 | vec3 toneMapHejlRichard(vec3 color) { 35 | color = max(vec3(0.0), color - vec3(0.004)); 36 | return (color*(6.2*color+.5))/(color*(6.2*color+1.7)+0.06); 37 | } 38 | 39 | // ACES tone map 40 | // see: https://knarkowicz.wordpress.com/2016/01/06/aces-filmic-tone-mapping-curve/ 41 | vec3 toneMapACES(vec3 color) { 42 | const float A = 2.51; 43 | const float B = 0.03; 44 | const float C = 2.43; 45 | const float D = 0.59; 46 | const float E = 0.14; 47 | return LINEARtoSRGB(clamp((color * (A * color + B)) / (color * (C * color + D) + E), 0.0, 1.0)); 48 | } 49 | 50 | vec3 defaultToneMap(vec3 color) { 51 | color = color/(color + 1.0); 52 | return LINEARtoSRGB(color); 53 | } 54 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/shaders/lib/UniformBufferObject.glsl: -------------------------------------------------------------------------------- 1 | #include "PunctualLight.glsl" 2 | 3 | struct UniformBufferObject 4 | { 5 | mat4 ModelView; 6 | mat4 Projection; 7 | mat4 ModelViewInverse; 8 | mat4 ProjectionInverse; 9 | 10 | float Aperture; 11 | float FocusDistance; 12 | float fovAngle; 13 | float orthographic_fov_dis; 14 | float HeatmapScale; 15 | uint TotalNumberOfSamples; 16 | 17 | uint NumberOfSamples; 18 | uint NumberOfBounces; 19 | uint RandomSeed; 20 | bool HasSky; 21 | 22 | bool antialiasing; 23 | uint mapping; 24 | uint frame_count; 25 | uint debug; 26 | 27 | bool fully_opaque; 28 | float exposure; 29 | uint tone_mapping_mode; 30 | }; 31 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/src/args.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | 3 | /// Simple program to greet a person 4 | #[derive(Parser, Debug)] 5 | #[clap(author, version, about, long_about = None)] 6 | pub struct Args { 7 | /// Path of the glTF file 8 | #[clap(short, long, value_parser, default_value = "")] 9 | pub file: String, 10 | } 11 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/src/compute_unit.rs: -------------------------------------------------------------------------------- 1 | use crate::desc_sets::DescriptorRes; 2 | use crate::{ANIMATION_BIND, SKIN_BIND, VERTEX_BIND}; 3 | use app::anyhow::Result; 4 | use app::load_spv; 5 | use app::vulkan::ash::vk; 6 | use app::vulkan::{ 7 | ComputePipeline, ComputePipelineCreateInfo, Context, DescriptorSetLayout, PipelineLayout, 8 | WriteDescriptorSet, WriteDescriptorSetKind, 9 | }; 10 | use asset_loader::globals::Buffers; 11 | 12 | const DISPATCH_SIZE: u32 = 256; 13 | pub struct ComputeUnit { 14 | pipeline: ComputePipelineRes, 15 | descriptor_res: DescriptorRes, 16 | // skins_buffer: Buffer, 17 | // animation_buffer: Buffer, 18 | } 19 | 20 | impl ComputeUnit { 21 | pub fn new(context: &Context, buffers: &Buffers) -> Result { 22 | let pipeline = create_compute_pipeline(context)?; 23 | let descriptor_res = create_descriptor_sets(context, &pipeline, buffers)?; 24 | Ok(Self { 25 | pipeline, 26 | descriptor_res, 27 | }) 28 | } 29 | 30 | pub fn dispatch(&self, context: &Context, _buffers: &Buffers, vertex_count: u32) -> Result<()> { 31 | let cmd_buffer = context 32 | .command_pool 33 | .allocate_command_buffer(vk::CommandBufferLevel::PRIMARY)?; 34 | cmd_buffer.begin(Some(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT))?; 35 | let static_set = &self.descriptor_res.static_set; 36 | cmd_buffer.bind_compute_pipeline(&self.pipeline.pipeline); 37 | cmd_buffer.bind_descriptor_sets( 38 | vk::PipelineBindPoint::COMPUTE, 39 | &self.pipeline.pipeline_layout, 40 | 0, 41 | &[static_set], 42 | ); 43 | cmd_buffer.dispatch((vertex_count / DISPATCH_SIZE) + 1, 1, 1); 44 | unsafe { 45 | context.device.inner.cmd_pipeline_barrier2( 46 | cmd_buffer.inner, 47 | &vk::DependencyInfo::builder() 48 | .memory_barriers(&[vk::MemoryBarrier2::builder() 49 | .src_access_mask(vk::AccessFlags2::MEMORY_WRITE) 50 | .dst_access_mask(vk::AccessFlags2::ACCELERATION_STRUCTURE_READ_KHR) 51 | .src_stage_mask(vk::PipelineStageFlags2::COMPUTE_SHADER) 52 | .dst_stage_mask(vk::PipelineStageFlags2::ACCELERATION_STRUCTURE_BUILD_KHR) 53 | .build()]) 54 | .build(), 55 | ); 56 | } 57 | // End recording 58 | cmd_buffer.end()?; 59 | // Submit and wait 60 | let fence = context.create_fence(None)?; 61 | // let fence = Fence::null(&context.device); 62 | context 63 | .graphics_queue 64 | .submit(&cmd_buffer, None, None, &fence)?; 65 | fence.wait(None)?; 66 | context.command_pool.free_command_buffer(&cmd_buffer)?; 67 | Ok(()) 68 | } 69 | } 70 | 71 | pub fn create_compute_pipeline(context: &Context) -> Result { 72 | let shader = load_spv("AnimationCompute.comp.spv"); 73 | let info = ComputePipelineCreateInfo { 74 | shader_source: &shader, 75 | }; 76 | let stage_flag = vk::ShaderStageFlags::COMPUTE; 77 | let layout_bindings = [ 78 | vk::DescriptorSetLayoutBinding::builder() 79 | .binding(VERTEX_BIND) 80 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 81 | .descriptor_count(1) 82 | .stage_flags(stage_flag) 83 | .build(), 84 | vk::DescriptorSetLayoutBinding::builder() 85 | .binding(ANIMATION_BIND) 86 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 87 | .descriptor_count(1) 88 | .stage_flags(stage_flag) 89 | .build(), 90 | vk::DescriptorSetLayoutBinding::builder() 91 | .binding(SKIN_BIND) 92 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 93 | .descriptor_count(1) 94 | .stage_flags(stage_flag) 95 | .build(), 96 | ]; 97 | let dsl = context.create_descriptor_set_layout(&layout_bindings)?; 98 | let pipeline_layout = context.create_pipeline_layout(&[&dsl])?; 99 | let pipeline = ComputePipeline::new(context.device.clone(), &pipeline_layout, info)?; 100 | Ok(ComputePipelineRes { 101 | pipeline, 102 | pipeline_layout, 103 | dsl, 104 | }) 105 | } 106 | 107 | pub struct ComputePipelineRes { 108 | pub(crate) pipeline: ComputePipeline, 109 | pub(crate) pipeline_layout: PipelineLayout, 110 | pub(crate) dsl: DescriptorSetLayout, 111 | } 112 | 113 | pub fn create_descriptor_sets( 114 | context: &Context, 115 | pipeline_res: &ComputePipelineRes, 116 | buffers: &Buffers, 117 | ) -> Result { 118 | let pool_sizes = [vk::DescriptorPoolSize::builder() 119 | .ty(vk::DescriptorType::STORAGE_BUFFER) 120 | .descriptor_count(3) 121 | .build()]; 122 | 123 | let pool = context.create_descriptor_pool(1, &pool_sizes)?; 124 | 125 | let static_set = pool.allocate_set(&pipeline_res.dsl)?; 126 | let (skins, ani) = buffers.animation_buffers.as_ref().unwrap(); 127 | static_set.update(&[ 128 | WriteDescriptorSet { 129 | binding: VERTEX_BIND, 130 | kind: WriteDescriptorSetKind::StorageBuffer { 131 | buffer: &buffers.vertex_buffer, 132 | }, 133 | }, 134 | WriteDescriptorSet { 135 | binding: SKIN_BIND, 136 | kind: WriteDescriptorSetKind::StorageBuffer { buffer: skins }, 137 | }, 138 | WriteDescriptorSet { 139 | binding: ANIMATION_BIND, 140 | kind: WriteDescriptorSetKind::StorageBuffer { buffer: ani }, 141 | }, 142 | ]); 143 | Ok(DescriptorRes { 144 | _pool: pool, 145 | dynamic_sets: vec![], 146 | static_set, 147 | }) 148 | } 149 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/src/desc_sets.rs: -------------------------------------------------------------------------------- 1 | use crate::pipeline_res::PipelineRes; 2 | use crate::{ 3 | ACC_BIND, AS_BIND, DLIGHT_BIND, GEO_BIND, INDEX_BIND, MAT_BIND, PLIGHT_BIND, STORAGE_BIND, 4 | TEXTURE_BIND, UNIFORM_BIND, VERTEX_BIND, 5 | }; 6 | use app::anyhow::Result; 7 | use app::vulkan::ash::vk; 8 | use app::vulkan::{ 9 | Buffer, Context, DescriptorPool, DescriptorSet, WriteDescriptorSet, WriteDescriptorSetKind, 10 | }; 11 | use app::ImageAndView; 12 | use asset_loader::acceleration_structures::TopAS; 13 | use asset_loader::globals::{Buffers, VkGlobal}; 14 | 15 | pub struct DescriptorRes { 16 | pub(crate) _pool: DescriptorPool, 17 | pub(crate) static_set: DescriptorSet, 18 | pub(crate) dynamic_sets: Vec, 19 | } 20 | 21 | pub fn create_descriptor_sets( 22 | context: &Context, 23 | pipeline_res: &PipelineRes, 24 | model: &VkGlobal, 25 | top_as: &TopAS, 26 | storage_imgs: &[ImageAndView], 27 | acc_images: &[ImageAndView], 28 | ubo_buffer: &Buffer, 29 | buffers: &Buffers, 30 | ) -> Result { 31 | let set_count = storage_imgs.len() as u32; 32 | let acc_count = acc_images.len() as u32; 33 | // assert_eq!(set_count, acc_count); 34 | 35 | let pool_sizes = [ 36 | vk::DescriptorPoolSize::builder() 37 | .ty(vk::DescriptorType::ACCELERATION_STRUCTURE_KHR) 38 | .descriptor_count(1) 39 | .build(), 40 | vk::DescriptorPoolSize::builder() 41 | .ty(vk::DescriptorType::STORAGE_IMAGE) 42 | .descriptor_count(set_count) 43 | .build(), 44 | vk::DescriptorPoolSize::builder() 45 | .ty(vk::DescriptorType::STORAGE_IMAGE) 46 | .descriptor_count(acc_count) 47 | .build(), 48 | vk::DescriptorPoolSize::builder() 49 | .ty(vk::DescriptorType::UNIFORM_BUFFER) 50 | .descriptor_count(1) 51 | .build(), 52 | vk::DescriptorPoolSize::builder() 53 | .ty(vk::DescriptorType::STORAGE_BUFFER) 54 | .descriptor_count(6) 55 | .build(), 56 | vk::DescriptorPoolSize::builder() 57 | .ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) 58 | .descriptor_count(model.textures.len() as _) 59 | .build(), 60 | ]; 61 | 62 | // println!("Image array size:{}", model.images.len()); 63 | let pool = context.create_descriptor_pool(set_count + 1, &pool_sizes)?; 64 | 65 | let static_set = pool.allocate_set(&pipeline_res.static_dsl)?; 66 | let dynamic_sets = pool.allocate_sets(&pipeline_res.dynamic_dsl, set_count)?; 67 | 68 | static_set.update(&[ 69 | WriteDescriptorSet { 70 | binding: VERTEX_BIND, 71 | kind: WriteDescriptorSetKind::StorageBuffer { 72 | buffer: if let Some((_, ani)) = &buffers.animation_buffers { 73 | ani 74 | } else { 75 | &buffers.vertex_buffer 76 | }, 77 | }, 78 | }, 79 | WriteDescriptorSet { 80 | binding: INDEX_BIND, 81 | kind: WriteDescriptorSetKind::StorageBuffer { 82 | buffer: &buffers.index_buffer, 83 | }, 84 | }, 85 | ]); 86 | static_set.update(&[ 87 | WriteDescriptorSet { 88 | binding: GEO_BIND, 89 | kind: WriteDescriptorSetKind::StorageBuffer { 90 | buffer: &buffers.geo_buffer, 91 | }, 92 | }, 93 | WriteDescriptorSet { 94 | binding: MAT_BIND, 95 | kind: WriteDescriptorSetKind::StorageBuffer { 96 | buffer: &buffers.material_buffer, 97 | }, 98 | }, 99 | ]); 100 | static_set.update(&[ 101 | WriteDescriptorSet { 102 | binding: AS_BIND, 103 | kind: WriteDescriptorSetKind::AccelerationStructure { 104 | acceleration_structure: &top_as.inner, 105 | }, 106 | }, 107 | WriteDescriptorSet { 108 | binding: UNIFORM_BIND, 109 | kind: WriteDescriptorSetKind::UniformBuffer { buffer: ubo_buffer }, 110 | }, 111 | WriteDescriptorSet { 112 | binding: DLIGHT_BIND, 113 | kind: WriteDescriptorSetKind::StorageBuffer { 114 | buffer: &buffers.dlights_buffer, 115 | }, 116 | }, 117 | WriteDescriptorSet { 118 | binding: PLIGHT_BIND, 119 | kind: WriteDescriptorSetKind::StorageBuffer { 120 | buffer: &buffers.plights_buffer, 121 | }, 122 | }, 123 | ]); 124 | 125 | let mut writes = vec![]; 126 | for [_texture_index, image_index, sampler_index] in model.textures.iter() { 127 | let view = &model.views[*image_index]; 128 | let sampler = &model.samplers[*sampler_index]; 129 | writes.push(WriteDescriptorSet { 130 | binding: TEXTURE_BIND, 131 | kind: WriteDescriptorSetKind::CombinedImageSampler { 132 | view, 133 | sampler, 134 | layout: vk::ImageLayout::SHADER_READ_ONLY_OPTIMAL, 135 | }, 136 | }); 137 | } 138 | static_set.update_texture_array(&writes); 139 | 140 | dynamic_sets.iter().enumerate().for_each(|(index, set)| { 141 | set.update(&[ 142 | WriteDescriptorSet { 143 | binding: STORAGE_BIND, 144 | kind: WriteDescriptorSetKind::StorageImage { 145 | layout: vk::ImageLayout::GENERAL, 146 | view: &storage_imgs[index].view, 147 | }, 148 | }, 149 | WriteDescriptorSet { 150 | binding: ACC_BIND, 151 | kind: WriteDescriptorSetKind::StorageImage { 152 | layout: vk::ImageLayout::GENERAL, 153 | view: &acc_images[0].view, 154 | }, 155 | }, 156 | ]); 157 | }); 158 | 159 | Ok(DescriptorRes { 160 | _pool: pool, 161 | dynamic_sets, 162 | static_set, 163 | }) 164 | } 165 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/src/loader.rs: -------------------------------------------------------------------------------- 1 | //! Non-blocking model loader 2 | //! 3 | //! The loader starts a worker thread that will wait for load messages. 4 | //! Once a message is received the thread will load the model and send the 5 | //! loaded model through another channel. 6 | //! 7 | //! When dropping the loader, a stop message is sent to the thread so it can 8 | //! stop listening for load events. Then we wait for the thread to terminate. 9 | //! 10 | //! Users have to call `load` to load a new model and `get_model` to retrieve 11 | //! the loaded model. 12 | 13 | use log::{error, info}; 14 | 15 | use std::sync::mpsc; 16 | use std::sync::mpsc::{Receiver, Sender}; 17 | 18 | use std::thread; 19 | use std::thread::JoinHandle; 20 | 21 | use asset_loader::{load_file, Doc}; 22 | 23 | enum Message { 24 | Load(String), 25 | Stop, 26 | } 27 | 28 | pub struct Loader { 29 | message_sender: Sender, 30 | model_receiver: Receiver, 31 | thread_handle: Option>, 32 | } 33 | 34 | impl Loader { 35 | pub fn new() -> Self { 36 | let (message_sender, message_receiver) = mpsc::channel(); 37 | let (model_sender, model_receiver) = mpsc::channel(); 38 | let thread_handle = Some(thread::spawn(move || { 39 | info!("Starting loader"); 40 | loop { 41 | let message = message_receiver.recv().expect("Failed to receive a path"); 42 | match message { 43 | Message::Load(path) => { 44 | info!("Start loading {}", path); 45 | let pre_loaded_model = load_file(&path); 46 | 47 | match pre_loaded_model { 48 | Ok(pre_loaded_model) => { 49 | info!("Finish loading {}", path); 50 | model_sender.send(pre_loaded_model).unwrap(); 51 | } 52 | Err(error) => { 53 | error!("Failed to load {}. Cause: {:?}", path, error); 54 | } 55 | } 56 | } 57 | Message::Stop => break, 58 | } 59 | } 60 | info!("Stopping loader"); 61 | })); 62 | 63 | Self { 64 | message_sender, 65 | model_receiver, 66 | thread_handle, 67 | } 68 | } 69 | 70 | /// Start loading a new model in the background. 71 | /// 72 | /// Call `get_model` to retrieve the loaded model. 73 | pub fn load(&self, path: String) { 74 | self.message_sender 75 | .send(Message::Load(path)) 76 | .expect("Failed to send load message to loader"); 77 | } 78 | 79 | /// Get the last loaded model. 80 | /// 81 | /// If no model is ready, then `None` is returned. 82 | pub fn get_model(&self) -> Option { 83 | match self.model_receiver.try_recv() { 84 | Ok(pre_loaded_model) => Some(pre_loaded_model), 85 | _ => None, 86 | } 87 | } 88 | } 89 | 90 | // fn pre_load_model>( 91 | // path: P, 92 | // ) -> Result, Box> { 93 | // let device = context.device(); 94 | // 95 | // // Create command buffer 96 | // 97 | // } 98 | 99 | impl Drop for Loader { 100 | fn drop(&mut self) { 101 | self.message_sender 102 | .send(Message::Stop) 103 | .expect("Failed to send stop message to loader thread"); 104 | if let Some(handle) = self.thread_handle.take() { 105 | handle 106 | .join() 107 | .expect("Failed to wait for loader thread termination"); 108 | } 109 | info!("Loader dropped"); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/src/pipeline_res.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | ACC_BIND, AS_BIND, DLIGHT_BIND, GEO_BIND, INDEX_BIND, MAT_BIND, PLIGHT_BIND, SKYBOX_BIND, 3 | STORAGE_BIND, TEXTURE_BIND, UNIFORM_BIND, VERTEX_BIND, 4 | }; 5 | use app::anyhow::Result; 6 | use app::load_spv; 7 | use app::vulkan::ash::vk; 8 | use app::vulkan::{ 9 | Context, DescriptorSetLayout, PipelineLayout, RayTracingPipeline, RayTracingPipelineCreateInfo, 10 | RayTracingShaderCreateInfo, RayTracingShaderGroup, 11 | }; 12 | use asset_loader::globals::VkGlobal; 13 | 14 | pub struct PipelineRes { 15 | pub(crate) pipeline: RayTracingPipeline, 16 | pub(crate) pipeline_layout: PipelineLayout, 17 | pub(crate) static_dsl: DescriptorSetLayout, 18 | pub(crate) dynamic_dsl: DescriptorSetLayout, 19 | } 20 | 21 | pub fn create_pipeline( 22 | context: &Context, 23 | model: &VkGlobal, 24 | fully_opaque: bool, 25 | ) -> Result { 26 | // descriptor and pipeline layouts 27 | let primary_hit_group_flags = 28 | vk::ShaderStageFlags::ANY_HIT_KHR | vk::ShaderStageFlags::CLOSEST_HIT_KHR; 29 | let static_layout_bindings = [ 30 | vk::DescriptorSetLayoutBinding::builder() 31 | .binding(AS_BIND) 32 | .descriptor_type(vk::DescriptorType::ACCELERATION_STRUCTURE_KHR) 33 | .descriptor_count(1) 34 | .stage_flags(vk::ShaderStageFlags::RAYGEN_KHR | vk::ShaderStageFlags::CLOSEST_HIT_KHR) 35 | .build(), 36 | vk::DescriptorSetLayoutBinding::builder() 37 | .binding(UNIFORM_BIND) 38 | .descriptor_type(vk::DescriptorType::UNIFORM_BUFFER) 39 | .descriptor_count(1) 40 | .stage_flags( 41 | vk::ShaderStageFlags::RAYGEN_KHR 42 | | vk::ShaderStageFlags::CLOSEST_HIT_KHR 43 | | vk::ShaderStageFlags::MISS_KHR, 44 | ) 45 | .build(), 46 | // Vertex buffer 47 | vk::DescriptorSetLayoutBinding::builder() 48 | .binding(VERTEX_BIND) 49 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 50 | .descriptor_count(1) 51 | .stage_flags(primary_hit_group_flags) 52 | .build(), 53 | //Index buffer 54 | vk::DescriptorSetLayoutBinding::builder() 55 | .binding(INDEX_BIND) 56 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 57 | .descriptor_count(1) 58 | .stage_flags(primary_hit_group_flags) 59 | .build(), 60 | // Geometry info buffer 61 | vk::DescriptorSetLayoutBinding::builder() 62 | .binding(GEO_BIND) 63 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 64 | .descriptor_count(1) 65 | .stage_flags(primary_hit_group_flags) 66 | .build(), 67 | // Textures 68 | vk::DescriptorSetLayoutBinding::builder() 69 | .binding(TEXTURE_BIND) 70 | .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) 71 | .descriptor_count(model.textures.len() as _) 72 | .stage_flags(primary_hit_group_flags) 73 | .build(), 74 | vk::DescriptorSetLayoutBinding::builder() 75 | .binding(MAT_BIND) 76 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 77 | .descriptor_count(1) 78 | .stage_flags(primary_hit_group_flags) 79 | .build(), 80 | vk::DescriptorSetLayoutBinding::builder() 81 | .binding(DLIGHT_BIND) 82 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 83 | .descriptor_count(1) 84 | .stage_flags(vk::ShaderStageFlags::MISS_KHR) 85 | .build(), 86 | vk::DescriptorSetLayoutBinding::builder() 87 | .binding(PLIGHT_BIND) 88 | .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) 89 | .descriptor_count(1) 90 | .stage_flags(vk::ShaderStageFlags::CLOSEST_HIT_KHR) 91 | .build(), 92 | vk::DescriptorSetLayoutBinding::builder() 93 | .binding(SKYBOX_BIND) 94 | .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) 95 | .descriptor_count(1) 96 | .stage_flags(vk::ShaderStageFlags::MISS_KHR) 97 | .build(), 98 | ]; 99 | 100 | let dynamic_layout_bindings = [ 101 | vk::DescriptorSetLayoutBinding::builder() 102 | .binding(STORAGE_BIND) 103 | .descriptor_type(vk::DescriptorType::STORAGE_IMAGE) 104 | .descriptor_count(1) 105 | .stage_flags(vk::ShaderStageFlags::RAYGEN_KHR) 106 | .build(), 107 | vk::DescriptorSetLayoutBinding::builder() 108 | .binding(ACC_BIND) 109 | .descriptor_type(vk::DescriptorType::STORAGE_IMAGE) 110 | .descriptor_count(1) 111 | .stage_flags(vk::ShaderStageFlags::RAYGEN_KHR) 112 | .build(), 113 | ]; 114 | 115 | let static_dsl = context.create_descriptor_set_layout(&static_layout_bindings)?; 116 | let dynamic_dsl = context.create_descriptor_set_layout(&dynamic_layout_bindings)?; 117 | let dsls = [&static_dsl, &dynamic_dsl]; 118 | 119 | let pipeline_layout = context.create_pipeline_layout(&dsls)?; 120 | 121 | // Shaders 122 | let ray_gen = load_spv("RayTracing.rgen.spv"); 123 | let ray_miss = load_spv("RayTracing.rmiss.spv"); 124 | let ray_chit = load_spv("RayTracing.rchit.spv"); 125 | let ray_rahit = load_spv("RayTracing.rahit.spv"); 126 | let shadow_rahit = load_spv("RayTracing.shadow.rahit.spv"); 127 | let shadow_miss = load_spv("RayTracing.shadow.rmiss.spv"); 128 | let mut shaders_create_info = vec![ 129 | RayTracingShaderCreateInfo { 130 | source: &ray_gen, 131 | stage: vk::ShaderStageFlags::RAYGEN_KHR, 132 | group: RayTracingShaderGroup::RayGen, 133 | }, 134 | RayTracingShaderCreateInfo { 135 | source: &ray_miss, 136 | stage: vk::ShaderStageFlags::MISS_KHR, 137 | group: RayTracingShaderGroup::Miss, 138 | }, 139 | RayTracingShaderCreateInfo { 140 | source: &shadow_miss, 141 | stage: vk::ShaderStageFlags::MISS_KHR, 142 | group: RayTracingShaderGroup::Miss, 143 | }, 144 | // RayTracingShaderCreateInfo { 145 | // source: &include_bytes!("../spv/shadow.rmiss.spv")[..], 146 | // stage: vk::ShaderStageFlags::MISS_KHR, 147 | // group: RayTracingShaderGroup::Miss, 148 | // }, 149 | RayTracingShaderCreateInfo { 150 | source: &ray_chit, 151 | stage: vk::ShaderStageFlags::CLOSEST_HIT_KHR, 152 | group: RayTracingShaderGroup::ClosestHit, 153 | }, 154 | ]; 155 | if !fully_opaque { 156 | shaders_create_info.push(RayTracingShaderCreateInfo { 157 | source: &ray_rahit, 158 | stage: vk::ShaderStageFlags::ANY_HIT_KHR, 159 | group: RayTracingShaderGroup::AnyHit, 160 | }); 161 | shaders_create_info.push(RayTracingShaderCreateInfo { 162 | source: &shadow_rahit, 163 | stage: vk::ShaderStageFlags::ANY_HIT_KHR, 164 | group: RayTracingShaderGroup::ShadowAnyHit, 165 | }); 166 | } 167 | 168 | let pipeline_create_info = RayTracingPipelineCreateInfo { 169 | shaders: shaders_create_info.as_slice(), 170 | max_ray_recursion_depth: 2, 171 | }; 172 | 173 | let pipeline = context.create_ray_tracing_pipeline(&pipeline_layout, pipeline_create_info)?; 174 | 175 | Ok(PipelineRes { 176 | pipeline, 177 | pipeline_layout, 178 | static_dsl, 179 | dynamic_dsl, 180 | }) 181 | } 182 | // 183 | -------------------------------------------------------------------------------- /crates/examples/gltf_viewer/src/ubo.rs: -------------------------------------------------------------------------------- 1 | use app::types::*; 2 | 3 | #[derive(Debug, Clone, Copy)] 4 | #[repr(C)] 5 | pub struct UniformBufferObject { 6 | pub(crate) model_view: Mat4, 7 | pub(crate) projection: Mat4, 8 | pub(crate) model_view_inverse: Mat4, 9 | pub(crate) projection_inverse: Mat4, 10 | 11 | pub(crate) aperture: f32, 12 | pub(crate) focus_distance: f32, 13 | pub fov_angle: f32, 14 | pub orthographic_fov_dis: f32, 15 | pub(crate) heatmap_scale: f32, 16 | pub(crate) total_number_of_samples: u32, 17 | 18 | pub(crate) number_of_samples: u32, 19 | pub(crate) number_of_bounces: u32, 20 | pub(crate) random_seed: u32, 21 | pub(crate) has_sky: u32, 22 | 23 | pub antialiasing: u32, 24 | pub(crate) mapping: u32, 25 | pub frame_count: u32, 26 | pub debug: u32, 27 | 28 | pub fully_opaque: u32, 29 | pub exposure: f32, 30 | pub tone_mapping_mode: u32, 31 | } 32 | -------------------------------------------------------------------------------- /crates/libs/app/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "app" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors.workspace = true 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | log.workspace = true 11 | pretty_env_logger.workspace = true 12 | anyhow.workspace = true 13 | winit.workspace = true 14 | nalgebra.workspace = true 15 | gpu-allocator.workspace = true 16 | resource_manager = {path = "../resource_manager"} 17 | 18 | vulkan = { path = "../vulkan" } 19 | gui = { path = "../gui" } 20 | -------------------------------------------------------------------------------- /crates/libs/app/src/camera.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use crate::types::*; 4 | use winit::event::{DeviceEvent, ElementState, Event, KeyboardInput, MouseButton, WindowEvent}; 5 | 6 | const MOVE_SPEED: f32 = 12.0; 7 | const ANGLE_PER_POINT: f32 = 0.001745; 8 | 9 | const FORWARD_SCANCODE: u32 = 17; 10 | const BACKWARD_SCANCODE: u32 = 31; 11 | const RIGHT_SCANCODE: u32 = 32; 12 | const LEFT_SCANCODE: u32 = 30; 13 | const UP_SCANCODE: u32 = 57; 14 | const DOWN_SCANCODE: u32 = 29; 15 | 16 | const UP: Vec3 = Vec3::new(0., 1., 0.); 17 | 18 | #[derive(Debug, Clone, Copy, PartialEq)] 19 | pub struct Camera { 20 | pub position: Point, 21 | pub direction: Vec3, 22 | pub fov: f32, 23 | pub aspect_ratio: f32, 24 | pub z_near: f32, 25 | pub z_far: f32, 26 | } 27 | 28 | impl Camera { 29 | pub fn new( 30 | position: Point, 31 | direction: Vec3, 32 | fov: f32, 33 | aspect_ratio: f32, 34 | z_near: f32, 35 | z_far: f32, 36 | ) -> Self { 37 | Self { 38 | position, 39 | direction: direction.normalize(), 40 | fov, 41 | aspect_ratio, 42 | z_near, 43 | z_far, 44 | } 45 | } 46 | 47 | pub fn update(self, controls: &Controls, delta_time: Duration) -> Self { 48 | let delta_time = delta_time.as_secs_f32(); 49 | let side = UniVec3::new_normalize(self.direction.cross(&UP)); 50 | 51 | // Update direction 52 | let new_direction = if controls.look_around { 53 | let side_rot = 54 | Quat::from_axis_angle(&side, -controls.cursor_delta[1] * ANGLE_PER_POINT); 55 | let y_rot = 56 | Quat::from_axis_angle(&Vec3::y_axis(), -controls.cursor_delta[0] * ANGLE_PER_POINT); 57 | let rot = side_rot * y_rot; 58 | 59 | (rot * self.direction).normalize() 60 | } else { 61 | self.direction 62 | }; 63 | // Update position 64 | let mut direction = Vec3::zeros(); 65 | let side = side.into_inner(); 66 | if controls.go_forward { 67 | direction += new_direction; 68 | } 69 | if controls.go_backward { 70 | direction -= new_direction; 71 | } 72 | if controls.strafe_right { 73 | direction += side; 74 | } 75 | if controls.strafe_left { 76 | direction -= side; 77 | } 78 | if controls.go_up { 79 | direction += UP; 80 | } 81 | if controls.go_down { 82 | direction -= UP; 83 | } 84 | 85 | let direction = if direction.norm_squared() == 0.0 { 86 | direction 87 | } else { 88 | direction.normalize() 89 | }; 90 | 91 | Self { 92 | position: self.position + direction * MOVE_SPEED * delta_time, 93 | direction: new_direction, 94 | ..self 95 | } 96 | } 97 | 98 | pub fn view_matrix(&self) -> Mat4 { 99 | Mat4::look_at_rh( 100 | &self.position, 101 | &(self.position + self.direction), 102 | &Vec3::y(), 103 | ) 104 | } 105 | 106 | pub fn projection_matrix(&self) -> Mat4 { 107 | OPENGL_TO_VULKAN_RT 108 | * Mat4::new_perspective( 109 | self.aspect_ratio, 110 | self.fov.to_radians(), 111 | self.z_near, 112 | self.z_far, 113 | ) 114 | } 115 | } 116 | const OPENGL_TO_VULKAN_RT: Mat4 = Mat4::new( 117 | 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0, 118 | ); 119 | 120 | #[derive(Debug, Clone, Copy)] 121 | pub struct Controls { 122 | pub go_forward: bool, 123 | pub go_backward: bool, 124 | pub strafe_right: bool, 125 | pub strafe_left: bool, 126 | pub go_up: bool, 127 | pub go_down: bool, 128 | pub look_around: bool, 129 | pub cursor_delta: [f32; 2], 130 | } 131 | 132 | impl Default for Controls { 133 | fn default() -> Self { 134 | Self { 135 | go_forward: false, 136 | go_backward: false, 137 | strafe_right: false, 138 | strafe_left: false, 139 | go_up: false, 140 | go_down: false, 141 | look_around: false, 142 | cursor_delta: [0.0; 2], 143 | } 144 | } 145 | } 146 | 147 | impl Controls { 148 | pub fn reset(self) -> Self { 149 | Self { 150 | cursor_delta: [0.0; 2], 151 | ..self 152 | } 153 | } 154 | 155 | pub fn handle_event(self, event: &Event<()>) -> Self { 156 | let mut new_state = self; 157 | 158 | match event { 159 | Event::WindowEvent { event, .. } => { 160 | match event { 161 | WindowEvent::KeyboardInput { 162 | input: 163 | KeyboardInput { 164 | scancode, state, .. 165 | }, 166 | .. 167 | } => { 168 | if *scancode == FORWARD_SCANCODE { 169 | new_state.go_forward = *state == ElementState::Pressed; 170 | } 171 | if *scancode == BACKWARD_SCANCODE { 172 | new_state.go_backward = *state == ElementState::Pressed; 173 | } 174 | if *scancode == RIGHT_SCANCODE { 175 | new_state.strafe_right = *state == ElementState::Pressed; 176 | } 177 | if *scancode == LEFT_SCANCODE { 178 | new_state.strafe_left = *state == ElementState::Pressed; 179 | } 180 | if *scancode == UP_SCANCODE { 181 | new_state.go_up = *state == ElementState::Pressed; 182 | } 183 | if *scancode == DOWN_SCANCODE { 184 | new_state.go_down = *state == ElementState::Pressed; 185 | } 186 | } 187 | WindowEvent::MouseInput { state, button, .. } => { 188 | if *button == MouseButton::Right { 189 | new_state.look_around = *state == ElementState::Pressed; 190 | } 191 | } 192 | _ => {} 193 | }; 194 | } 195 | Event::DeviceEvent { 196 | event: DeviceEvent::MouseMotion { delta: (x, y) }, 197 | .. 198 | } => { 199 | let x = *x as f32; 200 | let y = *y as f32; 201 | new_state.cursor_delta = [self.cursor_delta[0] + x, self.cursor_delta[1] + y]; 202 | } 203 | _ => (), 204 | } 205 | 206 | new_state 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /crates/libs/app/src/types.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::{Matrix4, Point3, UnitQuaternion, UnitVector3, Vector3}; 2 | 3 | pub type Point = Point3; 4 | pub type Vec3 = Vector3; 5 | pub type UniVec3 = UnitVector3; 6 | // pub type Vec4 = Vector4; 7 | // pub type Mat3 = Matrix3; 8 | pub type Mat4 = Matrix4; 9 | pub type Quat = UnitQuaternion; 10 | -------------------------------------------------------------------------------- /crates/libs/asset_loader/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "asset_loader" 3 | version = "0.2.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | log.workspace = true 10 | thiserror.workspace = true 11 | gltf.workspace = true 12 | glam.workspace = true 13 | resource_manager = {path = "../resource_manager"} 14 | vulkan = {path = "../vulkan", optional = true} 15 | anyhow.workspace = true 16 | mikktspace.workspace = true 17 | image.workspace = true 18 | strum.workspace = true 19 | strum_macros.workspace = true 20 | rayon = {workspace = true, optional = true} 21 | cfg-if.workspace = true 22 | rand.workspace = true 23 | 24 | [features] 25 | default = ["rayon", "ash"] 26 | ash = ["dep:vulkan"] 27 | rayon = ["dep:rayon"] -------------------------------------------------------------------------------- /crates/libs/asset_loader/src/aabb.rs: -------------------------------------------------------------------------------- 1 | use glam::{Mat4, Vec3, Vec4}; 2 | use gltf::mesh::Bounds; 3 | use std::ops::Mul; 4 | 5 | /// Axis aligned bounding box. 6 | #[derive(Copy, Clone, Debug)] 7 | pub struct Aabb { 8 | min: Vec3, 9 | max: Vec3, 10 | } 11 | 12 | impl Aabb { 13 | /// Create a new AABB. 14 | pub fn new(min: Vec3, max: Vec3) -> Self { 15 | Aabb { min, max } 16 | } 17 | } 18 | 19 | impl Aabb { 20 | /// Compute the union of several AABBs. 21 | pub fn union(aabbs: &[Aabb]) -> Option { 22 | if aabbs.is_empty() { 23 | None 24 | } else if aabbs.len() == 1 { 25 | Some(aabbs[0]) 26 | } else { 27 | // let partial_cmp = |a, b| a.partial_cmp(b); 28 | let by_key = |a: &f32, b: &f32| a.partial_cmp(b).unwrap(); 29 | let min_x = aabbs.iter().map(|aabb| aabb.min.x).min_by(by_key).unwrap(); 30 | let min_y = aabbs.iter().map(|aabb| aabb.min.y).min_by(by_key).unwrap(); 31 | let min_z = aabbs.iter().map(|aabb| aabb.min.z).min_by(by_key).unwrap(); 32 | let min = Vec3::new(min_x, min_y, min_z); 33 | 34 | let max_x = aabbs.iter().map(|aabb| aabb.max.x).max_by(by_key).unwrap(); 35 | let max_y = aabbs.iter().map(|aabb| aabb.max.y).max_by(by_key).unwrap(); 36 | let max_z = aabbs.iter().map(|aabb| aabb.max.z).max_by(by_key).unwrap(); 37 | let max = Vec3::new(max_x, max_y, max_z); 38 | 39 | Some(Aabb::new(min, max)) 40 | } 41 | } 42 | 43 | /// Get the size of the larger side of the AABB. 44 | pub fn get_larger_side_size(&self) -> f32 { 45 | let size = self.max - self.min; 46 | let x = size.x.abs(); 47 | let y = size.y.abs(); 48 | let z = size.z.abs(); 49 | 50 | if x > y && x > z { 51 | x 52 | } else if y > z { 53 | y 54 | } else { 55 | z 56 | } 57 | } 58 | 59 | /// Get the center of the AABB. 60 | pub fn get_center(&self) -> Vec3 { 61 | let two = 2.; 62 | self.min + (self.max - self.min) / two 63 | } 64 | 65 | pub fn get_transform(&self) -> Mat4 { 66 | let translation = Mat4::from_translation(-self.get_center()); 67 | let scale = Mat4::from_scale(Vec3::splat(10. / self.get_larger_side_size())); 68 | scale * translation 69 | } 70 | } 71 | 72 | /// Transform the AABB by multiplying it with a Matrix4. 73 | impl Mul for Aabb { 74 | type Output = Aabb; 75 | 76 | fn mul(self, rhs: Mat4) -> Self::Output { 77 | let min = self.min; 78 | let min = rhs * Vec4::new(min.x, min.y, min.z, 1.); 79 | 80 | let max = self.max; 81 | let max = rhs * Vec4::new(max.x, max.y, max.z, 1.); 82 | 83 | Aabb::new(min.truncate(), max.truncate()) 84 | } 85 | } 86 | 87 | /// Scale the AABB by multiplying it by a BaseFloat 88 | impl Mul for Aabb { 89 | type Output = Aabb; 90 | 91 | fn mul(self, rhs: f32) -> Self::Output { 92 | Aabb::new(self.min * rhs, self.max * rhs) 93 | } 94 | } 95 | 96 | pub fn get_aabb(bounds: &Bounds<[f32; 3]>) -> Aabb { 97 | let min = bounds.min; 98 | let min = Vec3::from_array(min); 99 | 100 | let max = bounds.max; 101 | let max = Vec3::from_array(max); 102 | 103 | Aabb::new(min, max) 104 | } 105 | -------------------------------------------------------------------------------- /crates/libs/asset_loader/src/animation.rs: -------------------------------------------------------------------------------- 1 | use crate::geometry::GeoBuilder; 2 | use crate::{get_name, Name, NodeID}; 3 | use glam::{Quat, Vec3}; 4 | use gltf::animation::util::ReadOutputs; 5 | use gltf::animation::{Channel, Interpolation, Sampler}; 6 | use log::warn; 7 | 8 | type Float3 = [f32; 3]; 9 | 10 | pub struct Animation { 11 | pub index: usize, 12 | name: Name, 13 | pub channels: Vec, 14 | } 15 | 16 | pub struct AnimationChannel { 17 | pub target: NodeID, 18 | property: Property, 19 | input: Vec, 20 | interpolation: Interpolation, 21 | } 22 | 23 | enum Property { 24 | Translation(Vec), 25 | Rotation(Vec<[f32; 4]>), 26 | Scale(Vec), 27 | Morph(Vec>), 28 | } 29 | 30 | impl Property { 31 | fn len(&self) -> usize { 32 | match self { 33 | Self::Translation(a) | Self::Scale(a) => a.len(), 34 | Self::Rotation(a) => a.len(), 35 | Self::Morph(a) => a.len(), 36 | } 37 | } 38 | } 39 | 40 | #[derive(Clone, Copy)] 41 | pub enum PropertyOutput { 42 | Translation(Float3), 43 | Rotation([f32; 4]), 44 | Scale(Float3), 45 | Morph, 46 | } 47 | 48 | impl AnimationChannel { 49 | fn new(channel: Channel<'_>, builder: &GeoBuilder) -> Self { 50 | let reader = channel.reader(|buffer| Some(&builder.buffers[buffer.index()])); 51 | let target = channel.target(); 52 | let target_node = target.node().index(); 53 | // let property = target.property().into(); 54 | let input: Vec<_> = reader.read_inputs().unwrap().collect(); 55 | let input_len = input.len(); 56 | let output = reader.read_outputs().unwrap(); 57 | let property = match output { 58 | ReadOutputs::Translations(t) => Property::Translation(t.collect()), 59 | ReadOutputs::Rotations(r) => Property::Rotation(r.into_f32().collect()), 60 | ReadOutputs::Scales(s) => Property::Scale(s.collect()), 61 | ReadOutputs::MorphTargetWeights(m) => { 62 | let weights: Vec<_> = m.into_f32().collect(); 63 | let chuck_size = weights.len() / input_len; 64 | Property::Morph(weights.chunks(chuck_size).map(|x| x.to_vec()).collect()) 65 | } 66 | }; 67 | let sampler = channel.sampler(); 68 | assert_eq!(input_len, property.len()); 69 | Self { 70 | target: target_node, 71 | property, 72 | input, 73 | interpolation: sampler.interpolation(), 74 | } 75 | } 76 | 77 | pub fn get_transform(&self, t: f32) -> PropertyOutput { 78 | let min = self.input[0]; 79 | let len = self.input.len(); 80 | let max = self.input[len - 1]; 81 | let interval = max - min; 82 | let t = if t > min { 83 | (t - min) % interval + min 84 | } else { 85 | t 86 | }; 87 | let mut s = 0; 88 | let mut e = 0; 89 | for (i, &d) in self.input[..len - 1].iter().enumerate() { 90 | if t >= d && t <= self.input[i + 1] { 91 | s = i; 92 | e = s + 1; 93 | } 94 | } 95 | 96 | let s3 = s * 3; 97 | let prev_time = self.input[s]; 98 | let next_time = self.input[e]; 99 | let factor = (t - prev_time) / (next_time - prev_time); 100 | let interpolation = self.interpolation; 101 | match &self.property { 102 | Property::Translation(t) => { 103 | let res = match interpolation { 104 | Interpolation::Linear => interpolate_lerp3(t[s], t[e], factor), 105 | Interpolation::Step => t[s], 106 | Interpolation::CubicSpline => cubic_spline( 107 | [t[s3], t[s3 + 1], t[s3 + 2]], 108 | prev_time, 109 | [t[s3 + 3], t[s3 + 4], t[s3 + 5]], 110 | next_time, 111 | factor, 112 | ), 113 | }; 114 | PropertyOutput::Translation(res) 115 | } 116 | Property::Rotation(r) => { 117 | let l = Quat::from_array(r[s]); 118 | let right = Quat::from_array(r[e]); 119 | let res = match interpolation { 120 | Interpolation::Linear | Interpolation::CubicSpline => { 121 | l.slerp(right, factor).to_array() 122 | } 123 | Interpolation::Step => r[s], 124 | }; 125 | PropertyOutput::Rotation(res) 126 | } 127 | Property::Scale(t) => { 128 | let res = match interpolation { 129 | Interpolation::Linear => interpolate_lerp3(t[s], t[e], factor), 130 | Interpolation::Step => t[s], 131 | Interpolation::CubicSpline => cubic_spline( 132 | [t[s3], t[s3 + 1], t[s3 + 2]], 133 | prev_time, 134 | [t[s3 + 3], t[s3 + 4], t[s3 + 5]], 135 | next_time, 136 | factor, 137 | ), 138 | }; 139 | PropertyOutput::Scale(res) 140 | } 141 | Property::Morph(_) => { 142 | warn!("Morph unimplemented. Ignore."); 143 | PropertyOutput::Scale([1.; 3]) 144 | } 145 | } 146 | } 147 | } 148 | 149 | fn interpolate_lerp3(s: Float3, e: Float3, factor: f32) -> Float3 { 150 | let start = Vec3::from_array(s); 151 | let end = Vec3::from_array(e); 152 | start.lerp(end, factor).to_array() 153 | } 154 | 155 | // Stole from Ben 156 | fn cubic_spline( 157 | source: [Float3; 3], 158 | source_time: f32, 159 | target: [Float3; 3], 160 | target_time: f32, 161 | amount: f32, 162 | ) -> Float3 { 163 | let source = source.map(Vec3::from_array); 164 | let target = target.map(Vec3::from_array); 165 | let t = amount; 166 | let p0 = source[1]; 167 | let m0 = (target_time - source_time) * source[2]; 168 | let p1 = target[1]; 169 | let m1 = (target_time - source_time) * target[0]; 170 | 171 | let res = (2.0 * t * t * t - 3.0 * t * t + 1.0) * p0 172 | + (t * t * t - 2.0 * t * t + t) * m0 173 | + (-2.0 * t * t * t + 3.0 * t * t) * p1 174 | + (t * t * t - t * t) * m1; 175 | res.to_array() 176 | } 177 | 178 | // https://web.mit.edu/2.998/www/QuaternionReport1.pdf 179 | // Todo: implement squad 180 | // fn cubic_spline4( 181 | // source: [Float4; 3], 182 | // source_time: f32, 183 | // target: [Float4; 3], 184 | // target_time: f32, 185 | // amount: f32, 186 | // ) -> Float4 { 187 | // let source = source.map(|i| Quat::from_array(i)); 188 | // let target = target.map(|i| Quat::from_array(i)); 189 | // let t = amount; 190 | // let p0 = source[1]; 191 | // let m0 = (target_time - source_time) * source[2]; 192 | // let p1 = target[1]; 193 | // let m1 = (target_time - source_time) * target[0]; 194 | // 195 | // let res = (2.0 * t * t * t - 3.0 * t * t + 1.0) * p0 196 | // + (t * t * t - 2.0 * t * t + t) * m0 197 | // + (-2.0 * t * t * t + 3.0 * t * t) * p1 198 | // + (t * t * t - t * t) * m1; 199 | // res.to_array() 200 | // } 201 | 202 | struct AnimationSampler {} 203 | 204 | impl From> for AnimationSampler { 205 | fn from(sampler: Sampler<'_>) -> Self { 206 | sampler.input(); 207 | sampler.output(); 208 | Self {} 209 | } 210 | } 211 | 212 | impl Animation { 213 | pub fn new(animation: gltf::Animation<'_>, builder: &GeoBuilder) -> Self { 214 | let index = animation.index(); 215 | let channels = animation 216 | .channels() 217 | .map(|c| AnimationChannel::new(c, builder)) 218 | .collect(); 219 | // let sampler= animation.samplers(); 220 | Self { 221 | index, 222 | name: get_name!(animation), 223 | channels, 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /crates/libs/asset_loader/src/cubumap.rs: -------------------------------------------------------------------------------- 1 | use crate::image::{Image, TexGamma}; 2 | use std::fs::DirEntry; 3 | use std::path::Path; 4 | use std::str::FromStr; 5 | use std::time::Instant; 6 | use strum_macros::{EnumCount, EnumString}; 7 | 8 | use crate::texture::Sampler; 9 | use anyhow::Result; 10 | use cfg_if::cfg_if; 11 | use log::info; 12 | 13 | pub struct SkyBox { 14 | pub images: Vec, 15 | pub sampler: Sampler, 16 | pub collector: Vec, 17 | } 18 | #[derive(Debug, PartialEq, EnumString, EnumCount)] 19 | #[allow(non_camel_case_types)] 20 | enum Face { 21 | posx = 0, 22 | negx, 23 | posy, 24 | negy, 25 | posz, 26 | negz, 27 | } 28 | 29 | impl Face { 30 | fn get_index(name: &str) -> usize { 31 | let name = name.split('.').next().unwrap(); 32 | let index = Self::from_str(name); 33 | let i = if let Ok(index) = index { 34 | index 35 | } else { 36 | match name { 37 | "back" => Self::negz, 38 | "front" => Self::posz, 39 | "top" => Self::posy, 40 | "bottom" => Self::negy, 41 | "left" => Self::negx, 42 | "right" => Self::posx, 43 | _ => { 44 | unimplemented!() 45 | } 46 | } 47 | }; 48 | i as _ 49 | } 50 | } 51 | 52 | #[cfg(not(feature = "rayon"))] 53 | fn load_skybox(dir_entry: Vec) -> Result<(Vec, Vec)> { 54 | let images = dir_entry 55 | .into_iter() 56 | .map(|d| Image::load_image(d.path())) 57 | .collect::>>()?; 58 | 59 | let collector = images 60 | .iter() 61 | .map(|i| &i.pixels) 62 | .flatten() 63 | .map(|&p| p) 64 | .collect(); 65 | Ok((images, collector)) 66 | } 67 | 68 | #[cfg(feature = "rayon")] 69 | fn load_skybox_par(dir_entry: Vec) -> Result<(Vec, Vec)> { 70 | use rayon::prelude::*; 71 | let images = dir_entry 72 | .into_par_iter() 73 | .map(|d| Image::load_image(d.path())) 74 | .collect::>>()?; 75 | 76 | let collector = images 77 | .par_iter() 78 | .map(|i| &i.pixels) 79 | .flatten() 80 | .map(|&p| p) 81 | .collect(); 82 | Ok((images, collector)) 83 | } 84 | 85 | impl SkyBox { 86 | pub fn new>(path: P) -> Result { 87 | let now = Instant::now(); 88 | let mut dir_entry = resource_manager::load_cubemap(path).unwrap(); 89 | dir_entry.sort_by_key(|d| Face::get_index(d.file_name().to_str().unwrap())); 90 | // let (images, collector) = 91 | cfg_if! { 92 | if #[cfg(feature = "rayon")] { 93 | let (images, collector) = load_skybox_par(dir_entry)?; 94 | } else { 95 | let (images, collector) = load_skybox(dir_entry)?; 96 | } 97 | } 98 | 99 | info!("Finish Skybox processing: {}s", now.elapsed().as_secs()); 100 | Ok(Self { 101 | images, 102 | sampler: Default::default(), 103 | collector, 104 | }) 105 | } 106 | 107 | pub fn get_total_size(&self) -> usize { 108 | self.images.iter().map(|i| i.pixels.len()).sum() 109 | } 110 | 111 | pub fn get_extents(&self) -> [u32; 2] { 112 | [self.images[0].width, self.images[1].height] 113 | } 114 | 115 | pub fn get_gamma(&self) -> TexGamma { 116 | self.images[0].gamma 117 | } 118 | } 119 | 120 | #[test] 121 | fn test() { 122 | let s = SkyBox::new("LancellottiChapel").unwrap(); 123 | let _k = SkyBox::new("/home/kosumi/Rusty/rustracer/assets/skyboxs/LancellottiChapel").unwrap(); 124 | let images = &s.images; 125 | assert_eq!(images.len(), 6) 126 | } 127 | -------------------------------------------------------------------------------- /crates/libs/asset_loader/src/error.rs: -------------------------------------------------------------------------------- 1 | use thiserror::Error as ThisError; 2 | 3 | pub type Result = std::result::Result; 4 | 5 | #[derive(Debug, ThisError)] 6 | pub enum Error { 7 | #[error("Failed to load gltf file: {0}")] 8 | Load(String), 9 | #[error("Unsupported gltf feature: {0}")] 10 | Support(String), 11 | } 12 | -------------------------------------------------------------------------------- /crates/libs/asset_loader/src/image.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashSet; 2 | 3 | use std::path::Path; 4 | 5 | use crate::error::*; 6 | use crate::{check_indices, Name}; 7 | use cfg_if::cfg_if; 8 | use gltf::image::{Format, Source}; 9 | use gltf::Document; 10 | use image::io::Reader as ImageReader; 11 | use image::GenericImageView; 12 | use log::info; 13 | 14 | #[derive(Debug, Clone)] 15 | pub struct Image { 16 | pub pixels: Vec, 17 | pub width: u32, 18 | pub height: u32, 19 | pub source: Name, 20 | pub index: usize, 21 | pub gamma: TexGamma, 22 | format: Format, 23 | } 24 | 25 | #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] 26 | pub enum TexGamma { 27 | Linear, 28 | Srgb, 29 | } 30 | 31 | impl Default for Image { 32 | fn default() -> Self { 33 | Self { 34 | pixels: vec![1; 4], 35 | width: 1, 36 | height: 1, 37 | source: None, 38 | index: 0, 39 | gamma: TexGamma::Srgb, 40 | format: Format::R8G8B8A8, 41 | } 42 | } 43 | } 44 | 45 | impl Image { 46 | pub fn update_info(&mut self, info: gltf::Image, linear: &HashSet) { 47 | let original_index = info.index(); 48 | self.index = original_index + 1; 49 | if linear.contains(&original_index) { 50 | self.gamma = TexGamma::Linear; 51 | } 52 | self.source = match info.source() { 53 | Source::View { .. } => None, 54 | Source::Uri { uri, .. } => Some(uri.to_string()), 55 | }; 56 | 57 | info!("Image:{:?} format: {:?}", self.source, self.format); 58 | } 59 | 60 | pub fn load_image>(p: P) -> anyhow::Result { 61 | let source = p.as_ref().to_str().map(|i| i.to_string()); 62 | let img = ImageReader::open(p)?.decode()?; 63 | 64 | let width = img.width(); 65 | let height = img.height(); 66 | let iter = img.pixels().flat_map(|(_x, _y, c)| c.0); 67 | let pixels = 68 | // if let Some(collecter) = collector { 69 | // collecter.extend(iter); 70 | // Vec::with_capacity(0) 71 | // } else { 72 | iter.collect(); 73 | // }; 74 | Ok(Self { 75 | pixels, 76 | width, 77 | height, 78 | source, 79 | index: 0, 80 | gamma: TexGamma::Srgb, 81 | format: Format::R8G8B8A8, 82 | }) 83 | } 84 | } 85 | 86 | impl TryFrom<&gltf::image::Data> for Image { 87 | type Error = Error; 88 | 89 | fn try_from(image: &gltf::image::Data) -> Result { 90 | let width = image.width; 91 | let height = image.height; 92 | let pixel_count = width * height; 93 | use gltf::image::Format::*; 94 | let pixels = match image.format { 95 | R8G8B8A8 => image.pixels.clone(), 96 | _ => PixelIter::new(image, pixel_count as _)? 97 | .flatten() 98 | .collect::>(), 99 | }; 100 | 101 | Ok(Self { 102 | pixels, 103 | width, 104 | height, 105 | format: image.format, 106 | ..Default::default() 107 | }) 108 | } 109 | } 110 | 111 | #[derive(Clone, Debug)] 112 | pub(crate) struct PixelIter<'a> { 113 | pixels: &'a [u8], 114 | format: Format, 115 | pixel_size: usize, 116 | position: usize, 117 | pixel_count: usize, 118 | } 119 | 120 | impl<'a> PixelIter<'a> { 121 | pub(crate) fn new(image: &'a gltf::image::Data, pixel_count: usize) -> Result { 122 | use Format::*; 123 | let pixels = &image.pixels; 124 | let format = image.format; 125 | let pixel_size = match format { 126 | R8 => 1, 127 | R8G8 => 2, 128 | R8G8B8 => 3, 129 | // R8G8B8A8 => 4, 130 | _ => return Err(Error::Support("16 bytes images".to_string())), 131 | }; 132 | 133 | Ok(Self { 134 | pixels, 135 | format, 136 | pixel_size, 137 | position: 0, 138 | pixel_count, 139 | }) 140 | } 141 | } 142 | 143 | impl<'a> Iterator for PixelIter<'a> { 144 | type Item = [u8; 4]; 145 | 146 | fn next(&mut self) -> Option { 147 | if self.position == self.pixel_count { 148 | return None; 149 | } 150 | let pixels = self.pixels; 151 | let index = self.position; 152 | use Format::*; 153 | let pixel = match self.format { 154 | R8 => [pixels[index], pixels[index], pixels[index], u8::MAX], 155 | // actually luma8 with alpha 156 | R8G8 => [ 157 | pixels[index * 2], 158 | pixels[index * 2], 159 | pixels[index * 2], 160 | pixels[index * 2 + 1], 161 | ], 162 | R8G8B8 => [ 163 | pixels[index * 3], 164 | pixels[index * 3 + 1], 165 | pixels[index * 3 + 2], 166 | u8::MAX, 167 | ], 168 | // R8G8B8A8 => [ 169 | // pixels[index * 4], 170 | // pixels[index * 4 + 1], 171 | // pixels[index * 4 + 2], 172 | // pixels[index * 4 + 3], 173 | // ], 174 | _ => unreachable!("Self::new already checks"), 175 | }; 176 | 177 | self.position += 1; 178 | Some(pixel) 179 | } 180 | } 181 | 182 | #[cfg(feature = "rayon")] 183 | pub fn process_images_par( 184 | gltf_images: &[gltf::image::Data], 185 | doc: &Document, 186 | linear: &HashSet, 187 | ) -> Vec { 188 | use rayon::prelude::*; 189 | let image_infos = doc.images().collect::>(); 190 | info!("Rayon enabled. Processing {} images", image_infos.len()); 191 | let images: Vec<_> = rayon::iter::once(Image::default()) 192 | .chain( 193 | gltf_images 194 | .par_iter() 195 | .map(Image::try_from) 196 | .map(Result::unwrap) 197 | .zip(image_infos) 198 | .map(|(mut img, info)| { 199 | img.update_info(info, linear); 200 | img 201 | }), 202 | ) 203 | .collect(); 204 | check_indices!(images); 205 | images 206 | } 207 | 208 | pub fn process_images_unified( 209 | gltf_images: &[gltf::image::Data], 210 | doc: &Document, 211 | linear: &HashSet, 212 | ) -> Vec { 213 | cfg_if! { 214 | if #[cfg(feature = "rayon")] { 215 | process_images_par(gltf_images, doc, linear) 216 | } else { 217 | process_images(gltf_images, doc, linear) 218 | } 219 | } 220 | } 221 | 222 | #[cfg(not(feature = "rayon"))] 223 | pub fn process_images( 224 | gltf_images: &[gltf::image::Data], 225 | doc: &Document, 226 | linear: &HashSet, 227 | ) -> Vec { 228 | let image_infos = doc.images().collect::>(); 229 | info!("Rayon disabled. Processing {} images", image_infos.len()); 230 | let images: Vec<_> = once(Image::default()) 231 | .chain( 232 | gltf_images 233 | .iter() 234 | .map(Image::try_from) 235 | .map(Result::unwrap) 236 | .zip(image_infos) 237 | .map(|(mut img, info)| { 238 | img.update_info(info, &linear); 239 | img 240 | }), 241 | ) 242 | .collect(); 243 | check_indices!(images); 244 | images 245 | } 246 | -------------------------------------------------------------------------------- /crates/libs/asset_loader/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod aabb; 2 | #[cfg(feature = "ash")] 3 | pub mod acceleration_structures; 4 | mod animation; 5 | mod cubumap; 6 | mod error; 7 | mod geometry; 8 | mod image; 9 | pub mod light; 10 | mod material; 11 | mod morph; 12 | mod scene_graph; 13 | mod skinning; 14 | mod texture; 15 | 16 | #[cfg(feature = "ash")] 17 | pub mod globals; 18 | 19 | pub use crate::scene_graph::load_file; 20 | pub use crate::scene_graph::Doc; 21 | use gltf::Document; 22 | 23 | type Name = Option; 24 | type Index = u32; 25 | 26 | type SceneID = usize; 27 | type NodeID = usize; 28 | type MeshID = usize; 29 | 30 | // fn to_owned_string(r: &str) -> String { 31 | // r.to_string() 32 | // } 33 | 34 | fn a3toa4(a3: &[T], w: T) -> [T; 4] { 35 | [a3[0], a3[1], a3[2], w] 36 | } 37 | 38 | fn check_extensions(doc: &Document) { 39 | const SUPPORTED: [&str; 0] = [ 40 | // "KHR_materials_ior", 41 | // "KHR_materials_pbrSpecularGlossiness", 42 | // "KHR_materials_transmission", 43 | // "KHR_materials_variants", 44 | // "KHR_materials_volume", 45 | // "KHR_materials_specular", 46 | // "KHR_texture_transform", 47 | // "KHR_materials_unlit" 48 | ]; 49 | doc.extensions_used() 50 | .filter(|ext| SUPPORTED.iter().all(|s| s != ext)) 51 | .for_each(|ext| log::error!("Extension {} is used but not supported", ext)); 52 | } 53 | 54 | #[macro_export] 55 | macro_rules! check_indices { 56 | ($expr:expr) => { 57 | assert!($expr.iter().enumerate().all(|(i, m)| i == m.index)); 58 | }; 59 | } 60 | 61 | #[macro_export] 62 | macro_rules! get_index { 63 | ($expr:expr) => { 64 | $expr.map(|m| m.index()) 65 | }; 66 | } 67 | 68 | #[macro_export] 69 | macro_rules! get_index_array { 70 | ($expr:expr) => { 71 | get_index!($expr).collect::>() 72 | }; 73 | } 74 | 75 | #[macro_export] 76 | macro_rules! get_name { 77 | ($expr:expr) => { 78 | $expr.name().map(|n| n.to_string()) 79 | }; 80 | } 81 | -------------------------------------------------------------------------------- /crates/libs/asset_loader/src/light.rs: -------------------------------------------------------------------------------- 1 | use crate::{a3toa4, get_name, Name}; 2 | use glam::{Mat4, Vec4, Vec4Swizzles}; 3 | use gltf::khr_lights_punctual::Kind; 4 | use log::{error, info}; 5 | 6 | pub struct Light { 7 | pub index: usize, 8 | color: [f32; 3], 9 | name: Name, 10 | kind: LightType, 11 | range: f32, 12 | intensity: f32, 13 | } 14 | 15 | impl Light { 16 | pub fn to_raw(&self, transform: Mat4) -> LightRaw { 17 | let kind = self.kind; 18 | LightRaw { 19 | color: Vec4::from_array(a3toa4(&self.color, 0.)), 20 | transform: kind.get_transform(transform), 21 | kind: kind as _, 22 | range: self.range, 23 | intensity: self.intensity, 24 | _padding: 0, 25 | } 26 | } 27 | } 28 | 29 | #[derive(Copy, Clone, PartialEq)] 30 | enum LightType { 31 | Directional = 0, 32 | Point = 1, 33 | Spot = 2, 34 | } 35 | 36 | impl LightType { 37 | fn get_transform(&self, transform: Mat4) -> Vec4 { 38 | transform 39 | * match self { 40 | LightType::Directional => Vec4::from_array([0., 0., -1.0, 0.]), 41 | LightType::Point | LightType::Spot => Vec4::from_array([0., 0., 0., 1.]), 42 | } 43 | } 44 | } 45 | 46 | impl From for LightType { 47 | fn from(value: Kind) -> Self { 48 | match value { 49 | Kind::Directional => Self::Directional, 50 | Kind::Point => Self::Point, 51 | Kind::Spot { 52 | inner_cone_angle: _, 53 | outer_cone_angle: _, 54 | } => { 55 | error!("Unimplemented; treat spot light as point light"); 56 | Self::Point 57 | } 58 | } 59 | } 60 | } 61 | 62 | #[repr(C)] 63 | #[derive(Copy, Clone, Debug, PartialEq)] 64 | pub struct LightRaw { 65 | pub color: Vec4, 66 | pub(crate) transform: Vec4, 67 | pub kind: u32, 68 | pub range: f32, 69 | pub intensity: f32, 70 | pub _padding: u32, 71 | } 72 | 73 | impl LightRaw { 74 | pub fn is_dir(&self) -> bool { 75 | self.kind == LightType::Directional as u32 76 | } 77 | 78 | pub fn update_angles(&mut self, [theta, phi]: [f32; 2]) { 79 | self.transform[0] = -theta.sin() * phi.sin(); 80 | self.transform[1] = -theta.cos(); 81 | self.transform[2] = -theta.sin() * phi.cos(); 82 | } 83 | 84 | pub fn random_light(distance: f32) -> Self { 85 | use rand::Rng; 86 | let mut rng = rand::thread_rng(); 87 | let mut get_random = || { 88 | Vec4::from_array([ 89 | rng.gen::(), 90 | rng.gen::(), 91 | rng.gen::(), 92 | rng.gen::(), 93 | ]) 94 | }; 95 | Self { 96 | color: Vec4::ONE, 97 | transform: (get_random() - 0.5) * 2. * distance, 98 | kind: LightType::Point as _, 99 | range: f32::INFINITY, 100 | intensity: 0.0, 101 | _padding: 0, 102 | } 103 | } 104 | 105 | pub fn update_distance(&mut self, dis: f32) { 106 | let new_vec = self.transform.xyz().normalize() * dis; 107 | self.transform = Vec4::from_array(a3toa4(&new_vec.to_array(), 1.)); 108 | } 109 | 110 | pub fn update_color(&mut self, color: [f32; 4]) { 111 | self.color = Vec4::from_array(color); 112 | } 113 | } 114 | 115 | impl Default for LightRaw { 116 | fn default() -> Self { 117 | Self { 118 | color: Vec4::from_array([1.; 4]), 119 | transform: Vec4::ONE, 120 | kind: LightType::Directional as _, 121 | range: f32::INFINITY, 122 | intensity: 0., 123 | _padding: 0, 124 | } 125 | } 126 | } 127 | 128 | impl<'a> From> for Light { 129 | fn from(light: gltf::khr_lights_punctual::Light) -> Self { 130 | Self { 131 | index: light.index(), 132 | color: light.color(), 133 | name: get_name!(light), 134 | kind: light.kind().into(), 135 | range: light.range().unwrap_or(f32::MAX), 136 | intensity: light.intensity(), 137 | } 138 | } 139 | } 140 | 141 | pub fn report_lights(lights: &[Light]) { 142 | let dirs = lights 143 | .iter() 144 | .filter(|l| l.kind == LightType::Directional) 145 | .count(); 146 | let points = lights.iter().filter(|l| l.kind == LightType::Point).count(); 147 | info!("Directional lights: {}; point lights: {}", dirs, points); 148 | } 149 | -------------------------------------------------------------------------------- /crates/libs/asset_loader/src/morph.rs: -------------------------------------------------------------------------------- 1 | struct MorphTarget {} 2 | -------------------------------------------------------------------------------- /crates/libs/asset_loader/src/skinning.rs: -------------------------------------------------------------------------------- 1 | use glam::Mat4; 2 | use gltf::buffer; 3 | use log::warn; 4 | use std::default::Default; 5 | 6 | use crate::scene_graph::Node; 7 | use crate::{get_index, get_index_array, get_name, Name, NodeID}; 8 | 9 | pub const MAX_JOINTS: usize = 256; 10 | pub type SkinRaw = [JointRaw; MAX_JOINTS]; 11 | 12 | pub struct Skin { 13 | pub index: usize, 14 | name: Name, 15 | joints: Vec, 16 | } 17 | 18 | impl Skin { 19 | pub fn new(skin: gltf::Skin, data: &[buffer::Data]) -> Self { 20 | // let reader = skin.inverse_bind_matrices(); 21 | let joints: Vec<_> = get_index_array!(skin.joints()); 22 | skin.skeleton(); 23 | let ibms: Vec<_> = skin 24 | .reader(|b| Some(&data[b.index()])) 25 | .read_inverse_bind_matrices() 26 | .unwrap() 27 | .map(|m| Mat4::from_cols_array_2d(&m)) 28 | .collect(); 29 | assert_eq!(ibms.len(), joints.len()); 30 | let joints = joints.into_iter().zip(ibms).map(Joint::from).collect(); 31 | // let reader = skin.reader(); 32 | Self { 33 | index: skin.index(), 34 | name: get_name!(skin), 35 | joints, 36 | } 37 | } 38 | 39 | pub fn get_skin_matrices(&self, nodes: &[Node]) -> SkinRaw { 40 | let len = self.joints.len(); 41 | if len > MAX_JOINTS { 42 | warn!("Too many joints: {}; current max: {}", len, MAX_JOINTS); 43 | } 44 | let mut res: SkinRaw = [Default::default(); MAX_JOINTS]; 45 | let len = len.min(MAX_JOINTS); 46 | self.joints[0..len].iter().enumerate().for_each(|(i, j)| { 47 | res[i] = (j.compute_skinning_matrix(nodes)).into(); 48 | }); 49 | res 50 | } 51 | } 52 | 53 | #[repr(C)] 54 | #[derive(Default, Copy, Clone)] 55 | pub struct JointRaw { 56 | matrix: [f32; 16], 57 | } 58 | 59 | impl From for JointRaw { 60 | fn from(value: Mat4) -> Self { 61 | Self { 62 | matrix: value.to_cols_array(), 63 | } 64 | } 65 | } 66 | 67 | pub struct Joint { 68 | node: NodeID, 69 | ibm: Mat4, 70 | } 71 | 72 | impl From<(usize, Mat4)> for Joint { 73 | fn from((node, ibm): (usize, Mat4)) -> Self { 74 | Self { node, ibm } 75 | } 76 | } 77 | 78 | impl Joint { 79 | pub fn compute_skinning_matrix(&self, nodes: &[Node]) -> Mat4 { 80 | nodes[self.node].get_world_transform() * self.ibm 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /crates/libs/asset_loader/src/texture.rs: -------------------------------------------------------------------------------- 1 | use crate::{get_name, Name}; 2 | 3 | #[derive(Debug, Clone)] 4 | pub struct Texture { 5 | pub image_index: usize, 6 | pub index: usize, 7 | pub sampler_index: usize, 8 | pub name: Name, 9 | } 10 | 11 | impl Default for Texture { 12 | fn default() -> Self { 13 | Self { 14 | image_index: 0, 15 | index: 0, 16 | sampler_index: 0, 17 | name: None, 18 | } 19 | } 20 | } 21 | 22 | #[derive(Debug, Clone, Copy)] 23 | pub struct Sampler { 24 | pub mag_filter: MagFilter, 25 | pub min_filter: MinFilter, 26 | pub wrap_s: WrapMode, 27 | pub wrap_t: WrapMode, 28 | pub(crate) index: usize, 29 | } 30 | 31 | impl Default for Sampler { 32 | fn default() -> Self { 33 | Sampler { 34 | mag_filter: MagFilter::Linear, 35 | min_filter: MinFilter::LinearMipmapLinear, 36 | wrap_s: WrapMode::Repeat, 37 | wrap_t: WrapMode::Repeat, 38 | index: 0, 39 | } 40 | } 41 | } 42 | 43 | #[derive(Debug, Clone, Copy)] 44 | pub enum MagFilter { 45 | Nearest, 46 | Linear, 47 | } 48 | 49 | #[derive(Debug, Clone, Copy)] 50 | pub enum MinFilter { 51 | Nearest, 52 | Linear, 53 | NearestMipmapNearest, 54 | LinearMipmapNearest, 55 | NearestMipmapLinear, 56 | LinearMipmapLinear, 57 | } 58 | 59 | #[derive(Debug, Clone, Copy)] 60 | pub enum WrapMode { 61 | ClampToEdge, 62 | MirroredRepeat, 63 | Repeat, 64 | } 65 | 66 | impl<'a> From> for Texture { 67 | fn from(texture: gltf::Texture) -> Self { 68 | // println!("Tex: {} Image:{} sampler: {}", texture.index(), texture.source().index(), 69 | // texture.sampler().index().map_or(0, |i| i + 1) 70 | // ); 71 | Self { 72 | image_index: texture.source().index() + 1, 73 | index: texture.index() + 1, 74 | sampler_index: texture.sampler().index().map_or(0, |i| i + 1), 75 | name: get_name!(texture), 76 | } 77 | } 78 | } 79 | 80 | impl<'a> From> for Sampler { 81 | fn from(sampler: gltf::texture::Sampler<'a>) -> Self { 82 | Self { 83 | mag_filter: sampler 84 | .mag_filter() 85 | .unwrap_or(gltf::texture::MagFilter::Linear) 86 | .into(), 87 | min_filter: sampler 88 | .min_filter() 89 | .unwrap_or(gltf::texture::MinFilter::Linear) 90 | .into(), 91 | wrap_s: sampler.wrap_s().into(), 92 | wrap_t: sampler.wrap_t().into(), 93 | index: sampler.index().map_or(0, |i| i + 1), 94 | } 95 | } 96 | } 97 | 98 | impl From for MagFilter { 99 | fn from(wrapping_mode: gltf::texture::MagFilter) -> Self { 100 | match wrapping_mode { 101 | gltf::texture::MagFilter::Linear => Self::Linear, 102 | gltf::texture::MagFilter::Nearest => Self::Nearest, 103 | } 104 | } 105 | } 106 | 107 | impl From for MinFilter { 108 | fn from(wrapping_mode: gltf::texture::MinFilter) -> Self { 109 | match wrapping_mode { 110 | gltf::texture::MinFilter::Linear => Self::Linear, 111 | gltf::texture::MinFilter::Nearest => Self::Nearest, 112 | gltf::texture::MinFilter::LinearMipmapLinear => Self::LinearMipmapLinear, 113 | gltf::texture::MinFilter::LinearMipmapNearest => Self::LinearMipmapNearest, 114 | gltf::texture::MinFilter::NearestMipmapLinear => Self::NearestMipmapLinear, 115 | gltf::texture::MinFilter::NearestMipmapNearest => Self::NearestMipmapNearest, 116 | } 117 | } 118 | } 119 | 120 | impl From for WrapMode { 121 | fn from(wrapping_mode: gltf::texture::WrappingMode) -> Self { 122 | match wrapping_mode { 123 | gltf::texture::WrappingMode::ClampToEdge => Self::ClampToEdge, 124 | gltf::texture::WrappingMode::MirroredRepeat => Self::MirroredRepeat, 125 | gltf::texture::WrappingMode::Repeat => Self::Repeat, 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /crates/libs/gui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "gui" 3 | version.workspace = true 4 | edition.workspace = true 5 | authors.workspace = true 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | log.workspace = true 11 | anyhow.workspace = true 12 | winit.workspace = true 13 | #egui.workspace = true 14 | #egui-winit.workspace = true 15 | #egui-winit-ash-integration.workspace = true 16 | 17 | imgui.workspace = true 18 | imgui-winit-support.workspace = true 19 | imgui-rs-vulkan-renderer.workspace = true 20 | vulkan = { path = "../vulkan" } 21 | -------------------------------------------------------------------------------- /crates/libs/gui/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub extern crate imgui; 2 | pub extern crate imgui_rs_vulkan_renderer; 3 | pub extern crate imgui_winit_support; 4 | 5 | use std::time::Duration; 6 | 7 | use anyhow::Result; 8 | use imgui::{Context, DrawData, FontConfig, FontSource}; 9 | use imgui_rs_vulkan_renderer::{DynamicRendering, Options, Renderer}; 10 | use imgui_winit_support::{HiDpiMode, WinitPlatform}; 11 | use vulkan::{ash::vk, CommandBuffer, CommandPool, Context as VkContext}; 12 | use winit::{event::Event, window::Window}; 13 | 14 | pub struct GuiContext { 15 | pub imgui: Context, 16 | pub platform: WinitPlatform, 17 | pub renderer: Renderer, 18 | } 19 | 20 | impl GuiContext { 21 | pub fn new( 22 | context: &VkContext, 23 | command_pool: &CommandPool, 24 | format: vk::Format, 25 | window: &Window, 26 | in_flight_frames: usize, 27 | ) -> Result { 28 | let mut imgui = Context::create(); 29 | imgui.set_ini_filename(None); 30 | 31 | let mut platform = WinitPlatform::init(&mut imgui); 32 | 33 | let hidpi_factor = platform.hidpi_factor(); 34 | let font_size = (13.0 * hidpi_factor) as f32; 35 | imgui.fonts().add_font(&[ 36 | FontSource::TtfData { 37 | data: include_bytes!("../../../../assets/fonts/Roboto 400.ttf"), 38 | size_pixels: font_size, 39 | config: Some(FontConfig { 40 | rasterizer_multiply: 1.75, 41 | ..FontConfig::default() 42 | }), 43 | }, 44 | FontSource::DefaultFontData { 45 | config: Some(FontConfig { 46 | size_pixels: font_size, 47 | ..FontConfig::default() 48 | }), 49 | }, 50 | ]); 51 | imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; 52 | platform.attach_window(imgui.io_mut(), window, HiDpiMode::Rounded); 53 | 54 | let gui_renderer = Renderer::with_gpu_allocator( 55 | context.allocator.clone(), 56 | context.device.inner.clone(), 57 | context.graphics_queue.inner, 58 | command_pool.inner, 59 | DynamicRendering { 60 | color_attachment_format: format, 61 | depth_attachment_format: None, 62 | }, 63 | &mut imgui, 64 | Some(Options { 65 | in_flight_frames, 66 | ..Default::default() 67 | }), 68 | )?; 69 | 70 | Ok(Self { 71 | imgui, 72 | platform, 73 | renderer: gui_renderer, 74 | }) 75 | } 76 | 77 | pub fn handle_event(&mut self, window: &Window, event: &Event) { 78 | self.platform 79 | .handle_event(self.imgui.io_mut(), window, event); 80 | } 81 | 82 | pub fn update_delta_time(&mut self, delta: Duration) { 83 | self.imgui.io_mut().update_delta_time(delta); 84 | } 85 | 86 | pub fn cmd_draw(&mut self, buffer: &CommandBuffer, draw_data: &DrawData) -> Result<()> { 87 | self.renderer.cmd_draw(buffer.inner, draw_data)?; 88 | Ok(()) 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /crates/libs/resource_manager/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "resource_manager" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | shellexpand.workspace = true 10 | anyhow.workspace = true 11 | -------------------------------------------------------------------------------- /crates/libs/resource_manager/src/lib.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::fs::DirEntry; 3 | use std::path::PathBuf; 4 | use std::{fs, path::Path}; 5 | 6 | const SPV_SEARCH_PATHS: [&str; 2] = ["", "./spv"]; 7 | 8 | const MODEL_SEARCH_PATHS: [&str; 5] = [ 9 | "", 10 | "./assets/models", 11 | "/home/kosumi/Rusty/glTF-Sample-Models/2.0", 12 | "../../../assets/models", 13 | "/home/kosumi/Rusty/LGL-Tracer-Renderer.github.io/models", 14 | ]; 15 | 16 | const SKYBOX_SEARCH_PATHS: [&str; 3] = ["", "./assets/skyboxs", "../../../assets/skyboxs"]; 17 | 18 | pub fn load_spv>(path: P) -> Vec { 19 | let mut res = None; 20 | 21 | for pre in SPV_SEARCH_PATHS { 22 | let search = Path::new(pre).join(&path); 23 | if let Ok(bytes) = fs::read(&search) { 24 | res = Some(bytes); 25 | break; 26 | } 27 | } 28 | res.unwrap_or_else(|| { 29 | panic!( 30 | "Couldn't find spv file {}, current path: {}", 31 | path.as_ref().display(), 32 | Path::new(".").canonicalize().unwrap().display() 33 | ) 34 | }) 35 | } 36 | 37 | fn find_gltf(search: &PathBuf) -> Option { 38 | for entry in fs::read_dir(search).ok()?.filter_map(|e| e.ok()) { 39 | if entry 40 | .file_name() 41 | .to_str() 42 | .filter(|name| name.ends_with(".gltf") || name.ends_with(".glb")) 43 | .is_some() 44 | { 45 | return Some(entry.path()); 46 | } 47 | } 48 | None 49 | } 50 | 51 | pub fn load_model>(path: P) -> Result { 52 | let mut res = None; 53 | let tails = ["", "glTF"]; 54 | for pre in MODEL_SEARCH_PATHS { 55 | let search = Path::new(pre).join(&path); 56 | for tail in tails { 57 | let mut path = search.clone(); 58 | if !tail.is_empty() { 59 | path = path.join(tail); 60 | } 61 | if path.exists() && path.is_file() { 62 | res = Some(path); 63 | } else if let Some(p) = find_gltf(&path) { 64 | res = Some(p); 65 | } 66 | if res.is_some() { 67 | break; 68 | } 69 | } 70 | } 71 | Ok(res.unwrap_or_default()) 72 | // res.expect(&*format!( 73 | // "Couldn't find model file {}, current path: {}", 74 | // path_ref.display(), 75 | // Path::new(".").canonicalize().unwrap().display() 76 | // ) 77 | } 78 | 79 | pub fn load_cubemap>(path: P) -> Result> { 80 | let test_fun = |p: &Path| p.exists() && p.is_dir(); 81 | let mut abs_path = PathBuf::new(); 82 | for pre in SKYBOX_SEARCH_PATHS { 83 | let search = Path::new(pre).join(&path); 84 | if test_fun(&search) { 85 | abs_path = search; 86 | break; 87 | } 88 | } 89 | let res: Vec<_> = fs::read_dir(abs_path)? 90 | .filter_map(|f| f.ok()) 91 | .filter(|dir| { 92 | let filename = dir.file_name(); 93 | filename 94 | .to_str() 95 | .filter(|s| s.ends_with(".png") || s.ends_with(".jpg")) 96 | .is_some() 97 | }) 98 | .collect(); 99 | assert_eq!(res.len(), 6); 100 | Ok(res) 101 | } 102 | 103 | // pub fn select_gltf>(path: P) -> Option { 104 | // use native_dialog::FileDialog; 105 | // FileDialog::new() 106 | // .add_filter("gltf", &["gltf", "glb"]) 107 | // .set_location(&path) 108 | // .show_open_single_file().unwrap() 109 | // } 110 | // 111 | #[test] 112 | fn test_load_spv() { 113 | load_spv("./src/main.rs"); 114 | } 115 | -------------------------------------------------------------------------------- /crates/libs/vulkan/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "vulkan" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | log.workspace = true 10 | anyhow.workspace = true 11 | ash.workspace = true 12 | gpu-allocator.workspace = true 13 | winit.workspace = true 14 | vk-sync = "0.1.6" 15 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/buffer.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | mem::{align_of, size_of_val}, 3 | sync::{Arc, Mutex}, 4 | }; 5 | 6 | use anyhow::Result; 7 | use ash::vk; 8 | use gpu_allocator::vulkan::AllocationScheme; 9 | use gpu_allocator::{ 10 | vulkan::{Allocation, AllocationCreateDesc, Allocator}, 11 | MemoryLocation, 12 | }; 13 | 14 | use crate::{device::Device, Context}; 15 | 16 | pub struct Buffer { 17 | device: Arc, 18 | allocator: Arc>, 19 | pub(crate) inner: vk::Buffer, 20 | allocation: Option, 21 | pub size: vk::DeviceSize, 22 | } 23 | 24 | impl Buffer { 25 | pub fn as_raw(&self) -> u64 { 26 | use vk::Handle; 27 | self.inner.as_raw() 28 | } 29 | pub(crate) fn new( 30 | device: Arc, 31 | allocator: Arc>, 32 | usage: vk::BufferUsageFlags, 33 | memory_location: MemoryLocation, 34 | size: vk::DeviceSize, 35 | ) -> Result { 36 | let create_info = vk::BufferCreateInfo::builder().size(size).usage(usage); 37 | let inner = unsafe { device.inner.create_buffer(&create_info, None)? }; 38 | let requirements = unsafe { device.inner.get_buffer_memory_requirements(inner) }; 39 | let allocation = allocator.lock().unwrap().allocate(&AllocationCreateDesc { 40 | name: "buffer", 41 | requirements, 42 | location: memory_location, 43 | linear: true, 44 | allocation_scheme: AllocationScheme::GpuAllocatorManaged, 45 | })?; 46 | 47 | unsafe { 48 | device 49 | .inner 50 | .bind_buffer_memory(inner, allocation.memory(), allocation.offset())? 51 | }; 52 | 53 | Ok(Self { 54 | device, 55 | allocator, 56 | inner, 57 | allocation: Some(allocation), 58 | size, 59 | }) 60 | } 61 | 62 | pub fn copy_data_to_buffer(&self, data: &[T]) -> Result<()> { 63 | unsafe { 64 | let data_ptr = self 65 | .allocation 66 | .as_ref() 67 | .unwrap() 68 | .mapped_ptr() 69 | .unwrap() 70 | .as_ptr(); 71 | let mut align = 72 | ash::util::Align::new(data_ptr, align_of::() as _, size_of_val(data) as _); 73 | align.copy_from_slice(data); 74 | }; 75 | 76 | Ok(()) 77 | } 78 | 79 | pub fn get_device_address(&self) -> u64 { 80 | let addr_info = vk::BufferDeviceAddressInfo::builder().buffer(self.inner); 81 | unsafe { self.device.inner.get_buffer_device_address(&addr_info) } 82 | } 83 | } 84 | 85 | impl Context { 86 | pub fn create_buffer( 87 | &self, 88 | usage: vk::BufferUsageFlags, 89 | memory_location: MemoryLocation, 90 | size: vk::DeviceSize, 91 | ) -> Result { 92 | Buffer::new( 93 | self.device.clone(), 94 | self.allocator.clone(), 95 | usage, 96 | memory_location, 97 | size, 98 | ) 99 | } 100 | } 101 | 102 | impl Drop for Buffer { 103 | fn drop(&mut self) { 104 | unsafe { self.device.inner.destroy_buffer(self.inner, None) }; 105 | self.allocator 106 | .lock() 107 | .unwrap() 108 | .free(self.allocation.take().unwrap()) 109 | .unwrap(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/device.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CString, sync::Arc}; 2 | 3 | use anyhow::Result; 4 | use ash::{vk, Device as AshDevice}; 5 | 6 | use crate::{ 7 | instance::Instance, 8 | physical_device::PhysicalDevice, 9 | queue::{Queue, QueueFamily}, 10 | }; 11 | 12 | pub struct Device { 13 | pub inner: AshDevice, 14 | } 15 | 16 | impl Device { 17 | pub(crate) fn new( 18 | instance: &Instance, 19 | physical_device: &PhysicalDevice, 20 | queue_families: &[QueueFamily], 21 | required_extensions: &[&str], 22 | device_features: &DeviceFeatures, 23 | ) -> Result { 24 | let queue_priorities = [1.0f32]; 25 | 26 | let queue_create_infos = { 27 | let mut indices = queue_families.iter().map(|f| f.index).collect::>(); 28 | indices.dedup(); 29 | 30 | indices 31 | .iter() 32 | .map(|index| { 33 | vk::DeviceQueueCreateInfo::builder() 34 | .queue_family_index(*index) 35 | .queue_priorities(&queue_priorities) 36 | .build() 37 | }) 38 | .collect::>() 39 | }; 40 | 41 | let device_extensions_ptrs = required_extensions 42 | .iter() 43 | .map(|e| CString::new(*e)) 44 | .collect::, _>>()?; 45 | let device_extensions_ptrs = device_extensions_ptrs 46 | .iter() 47 | .map(|e| e.as_ptr()) 48 | .collect::>(); 49 | 50 | let mut ray_tracing_feature = vk::PhysicalDeviceRayTracingPipelineFeaturesKHR::builder() 51 | .ray_tracing_pipeline(device_features.ray_tracing_pipeline); 52 | let mut acceleration_struct_feature = 53 | vk::PhysicalDeviceAccelerationStructureFeaturesKHR::builder() 54 | .acceleration_structure(device_features.acceleration_structure); 55 | let mut vulkan_12_features = vk::PhysicalDeviceVulkan12Features::builder() 56 | .runtime_descriptor_array(device_features.runtime_descriptor_array) 57 | .buffer_device_address(device_features.buffer_device_address); 58 | let mut vulkan_13_features = vk::PhysicalDeviceVulkan13Features::builder() 59 | .dynamic_rendering(device_features.dynamic_rendering) 60 | .synchronization2(device_features.synchronization2); 61 | let mut shader_device_clock_features = vk::PhysicalDeviceShaderClockFeaturesKHR::builder() 62 | .shader_subgroup_clock(true) 63 | .shader_device_clock(true); 64 | 65 | let pf = vk::PhysicalDeviceFeatures { 66 | shader_int64: vk::TRUE, 67 | ..Default::default() 68 | }; 69 | 70 | let mut features = vk::PhysicalDeviceFeatures2::builder() 71 | .features(pf) 72 | .push_next(&mut acceleration_struct_feature) 73 | .push_next(&mut ray_tracing_feature) 74 | .push_next(&mut vulkan_12_features) 75 | .push_next(&mut vulkan_13_features) 76 | .push_next(&mut shader_device_clock_features); 77 | 78 | let device_create_info = vk::DeviceCreateInfo::builder() 79 | .queue_create_infos(&queue_create_infos) 80 | .enabled_extension_names(&device_extensions_ptrs) 81 | .push_next(&mut features); 82 | 83 | let inner = unsafe { 84 | instance 85 | .inner 86 | .create_device(physical_device.inner, &device_create_info, None)? 87 | }; 88 | 89 | Ok(Self { inner }) 90 | } 91 | 92 | pub fn get_queue(self: &Arc, queue_family: QueueFamily, queue_index: u32) -> Queue { 93 | let inner = unsafe { self.inner.get_device_queue(queue_family.index, queue_index) }; 94 | Queue::new(self.clone(), inner) 95 | } 96 | } 97 | 98 | impl Drop for Device { 99 | fn drop(&mut self) { 100 | unsafe { 101 | self.inner.destroy_device(None); 102 | } 103 | } 104 | } 105 | 106 | #[derive(Debug, Clone, Copy, Default)] 107 | pub struct DeviceFeatures { 108 | pub ray_tracing_pipeline: bool, 109 | pub acceleration_structure: bool, 110 | pub runtime_descriptor_array: bool, 111 | pub buffer_device_address: bool, 112 | pub dynamic_rendering: bool, 113 | pub synchronization2: bool, 114 | } 115 | 116 | impl DeviceFeatures { 117 | pub fn is_compatible_with(&self, requirements: &Self) -> bool { 118 | (!requirements.ray_tracing_pipeline || self.ray_tracing_pipeline) 119 | && (!requirements.acceleration_structure || self.acceleration_structure) 120 | && (!requirements.runtime_descriptor_array || self.runtime_descriptor_array) 121 | && (!requirements.buffer_device_address || self.buffer_device_address) 122 | && (!requirements.dynamic_rendering || self.dynamic_rendering) 123 | && (!requirements.synchronization2 || self.synchronization2) 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/image.rs: -------------------------------------------------------------------------------- 1 | use std::sync::{Arc, Mutex}; 2 | 3 | use anyhow::Result; 4 | use ash::vk; 5 | use gpu_allocator::vulkan::AllocationScheme; 6 | use gpu_allocator::{ 7 | vulkan::{Allocation, AllocationCreateDesc, Allocator}, 8 | MemoryLocation, 9 | }; 10 | 11 | use crate::{device::Device, Context}; 12 | 13 | pub struct Image { 14 | device: Arc, 15 | allocator: Arc>, 16 | pub(crate) inner: vk::Image, 17 | allocation: Option, 18 | pub format: vk::Format, 19 | pub extent: vk::Extent3D, 20 | is_swapchain: bool, // if set, image should not be destroyed 21 | } 22 | 23 | pub struct ImageView { 24 | device: Arc, 25 | pub(crate) inner: vk::ImageView, 26 | } 27 | 28 | impl Image { 29 | pub(crate) fn new_2d( 30 | device: Arc, 31 | allocator: Arc>, 32 | usage: vk::ImageUsageFlags, 33 | memory_location: MemoryLocation, 34 | format: vk::Format, 35 | width: u32, 36 | height: u32, 37 | ) -> Result { 38 | let extent = vk::Extent3D { 39 | width, 40 | height, 41 | depth: 1, 42 | }; 43 | 44 | let image_info = vk::ImageCreateInfo::builder() 45 | .image_type(vk::ImageType::TYPE_2D) 46 | .format(format) 47 | .extent(extent) 48 | .mip_levels(1) 49 | .array_layers(1) 50 | .samples(vk::SampleCountFlags::TYPE_1) 51 | .tiling(vk::ImageTiling::OPTIMAL) 52 | .usage(usage) 53 | .initial_layout(vk::ImageLayout::UNDEFINED); 54 | 55 | let inner = unsafe { device.inner.create_image(&image_info, None)? }; 56 | let requirements = unsafe { device.inner.get_image_memory_requirements(inner) }; 57 | 58 | let allocation = allocator.lock().unwrap().allocate(&AllocationCreateDesc { 59 | name: "image", 60 | requirements, 61 | location: memory_location, 62 | linear: true, 63 | allocation_scheme: AllocationScheme::GpuAllocatorManaged, 64 | })?; 65 | 66 | unsafe { 67 | device 68 | .inner 69 | .bind_image_memory(inner, allocation.memory(), allocation.offset())? 70 | }; 71 | 72 | Ok(Self { 73 | device, 74 | allocator, 75 | inner, 76 | allocation: Some(allocation), 77 | format, 78 | extent, 79 | is_swapchain: false, 80 | }) 81 | } 82 | 83 | pub(crate) fn new_cubemap( 84 | device: Arc, 85 | allocator: Arc>, 86 | usage: vk::ImageUsageFlags, 87 | memory_location: MemoryLocation, 88 | format: vk::Format, 89 | width: u32, 90 | height: u32, 91 | ) -> Result { 92 | let extent = vk::Extent3D { 93 | width, 94 | height, 95 | depth: 1, 96 | }; 97 | 98 | let image_info = vk::ImageCreateInfo::builder() 99 | .image_type(vk::ImageType::TYPE_2D) 100 | .format(format) 101 | .extent(extent) 102 | .mip_levels(1) 103 | .array_layers(6) 104 | .samples(vk::SampleCountFlags::TYPE_1) 105 | .tiling(vk::ImageTiling::OPTIMAL) 106 | .usage(usage) 107 | .initial_layout(vk::ImageLayout::UNDEFINED) 108 | .flags(vk::ImageCreateFlags::CUBE_COMPATIBLE); 109 | 110 | let inner = unsafe { device.inner.create_image(&image_info, None)? }; 111 | let requirements = unsafe { device.inner.get_image_memory_requirements(inner) }; 112 | 113 | let allocation = allocator.lock().unwrap().allocate(&AllocationCreateDesc { 114 | name: "skybox_image", 115 | requirements, 116 | location: memory_location, 117 | linear: true, 118 | allocation_scheme: AllocationScheme::GpuAllocatorManaged, 119 | })?; 120 | 121 | unsafe { 122 | device 123 | .inner 124 | .bind_image_memory(inner, allocation.memory(), allocation.offset())? 125 | }; 126 | 127 | Ok(Self { 128 | device, 129 | allocator, 130 | inner, 131 | allocation: Some(allocation), 132 | format, 133 | extent, 134 | is_swapchain: false, 135 | }) 136 | } 137 | 138 | pub(crate) fn from_swapchain_image( 139 | device: Arc, 140 | allocator: Arc>, 141 | swapchain_image: vk::Image, 142 | format: vk::Format, 143 | extent: vk::Extent2D, 144 | ) -> Self { 145 | let extent = vk::Extent3D { 146 | width: extent.width, 147 | height: extent.height, 148 | depth: 1, 149 | }; 150 | 151 | Self { 152 | device, 153 | allocator, 154 | inner: swapchain_image, 155 | allocation: None, 156 | format, 157 | extent, 158 | is_swapchain: true, 159 | } 160 | } 161 | 162 | pub fn create_image_view(&self) -> Result { 163 | let view_info = vk::ImageViewCreateInfo::builder() 164 | .image(self.inner) 165 | .view_type(vk::ImageViewType::TYPE_2D) 166 | .format(self.format) 167 | .subresource_range(vk::ImageSubresourceRange { 168 | aspect_mask: vk::ImageAspectFlags::COLOR, 169 | base_mip_level: 0, 170 | level_count: 1, 171 | base_array_layer: 0, 172 | layer_count: 1, 173 | }); 174 | 175 | let inner = unsafe { self.device.inner.create_image_view(&view_info, None)? }; 176 | 177 | Ok(ImageView { 178 | device: self.device.clone(), 179 | inner, 180 | }) 181 | } 182 | pub fn create_cubemap_view(&self) -> Result { 183 | let view_info = vk::ImageViewCreateInfo::builder() 184 | .image(self.inner) 185 | .view_type(vk::ImageViewType::CUBE) 186 | .format(self.format) 187 | .subresource_range(vk::ImageSubresourceRange { 188 | aspect_mask: vk::ImageAspectFlags::COLOR, 189 | base_mip_level: 0, 190 | level_count: 1, 191 | base_array_layer: 0, 192 | layer_count: 6, 193 | }); 194 | 195 | let inner = unsafe { self.device.inner.create_image_view(&view_info, None)? }; 196 | 197 | Ok(ImageView { 198 | device: self.device.clone(), 199 | inner, 200 | }) 201 | } 202 | } 203 | 204 | impl Context { 205 | pub fn create_image( 206 | &self, 207 | usage: vk::ImageUsageFlags, 208 | memory_location: MemoryLocation, 209 | format: vk::Format, 210 | width: u32, 211 | height: u32, 212 | ) -> Result { 213 | Image::new_2d( 214 | self.device.clone(), 215 | self.allocator.clone(), 216 | usage, 217 | memory_location, 218 | format, 219 | width, 220 | height, 221 | ) 222 | } 223 | 224 | pub fn create_cubemap_image( 225 | &self, 226 | usage: vk::ImageUsageFlags, 227 | memory_location: MemoryLocation, 228 | format: vk::Format, 229 | width: u32, 230 | height: u32, 231 | ) -> Result { 232 | Image::new_cubemap( 233 | self.device.clone(), 234 | self.allocator.clone(), 235 | usage, 236 | memory_location, 237 | format, 238 | width, 239 | height, 240 | ) 241 | } 242 | } 243 | 244 | impl Drop for Image { 245 | fn drop(&mut self) { 246 | if self.is_swapchain { 247 | return; 248 | } 249 | 250 | unsafe { self.device.inner.destroy_image(self.inner, None) }; 251 | self.allocator 252 | .lock() 253 | .unwrap() 254 | .free(self.allocation.take().unwrap()) 255 | .unwrap(); 256 | } 257 | } 258 | 259 | impl Drop for ImageView { 260 | fn drop(&mut self) { 261 | unsafe { self.device.inner.destroy_image_view(self.inner, None) }; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/instance.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{c_void, CStr, CString}; 2 | 3 | use anyhow::Result; 4 | use ash::{extensions::ext::DebugUtils, vk, Entry, Instance as AshInstance}; 5 | 6 | use crate::{physical_device::PhysicalDevice, surface::Surface, Version}; 7 | 8 | use crate::utils::platforms::required_extension_names; 9 | 10 | #[cfg(debug_assertions)] 11 | const ENABLE_VALIDATION_LAYERS: bool = true; 12 | #[cfg(not(debug_assertions))] 13 | const ENABLE_VALIDATION_LAYERS: bool = false; 14 | const VALIDATION: &[&str] = &["VK_LAYER_KHRONOS_validation"]; 15 | 16 | pub struct Instance { 17 | pub(crate) inner: AshInstance, 18 | debug_utils: DebugUtils, 19 | debug_utils_messenger: vk::DebugUtilsMessengerEXT, 20 | physical_devices: Vec, 21 | } 22 | 23 | impl Instance { 24 | pub(crate) fn new(entry: &Entry, api_version: Version, app_name: &str) -> Result { 25 | // Vulkan instance 26 | let app_name = CString::new(app_name)?; 27 | 28 | let app_info = vk::ApplicationInfo::builder() 29 | .application_name(app_name.as_c_str()) 30 | .api_version(api_version.make_api_version()); 31 | 32 | let mut extension_names = required_extension_names(); 33 | extension_names.push(DebugUtils::name().as_ptr()); 34 | 35 | let enabled_layer_names = VALIDATION 36 | .iter() 37 | .map(|layer_name| CString::new(*layer_name).unwrap()) 38 | .collect::>(); 39 | let enabled_layer_names = enabled_layer_names 40 | .iter() 41 | .map(|layer_name| layer_name.as_ptr()) 42 | .collect::>(); 43 | let instance_create_info = vk::InstanceCreateInfo::builder() 44 | .application_info(&app_info) 45 | .enabled_extension_names(&extension_names); 46 | 47 | let instance_create_info = if ENABLE_VALIDATION_LAYERS { 48 | instance_create_info.enabled_layer_names(&enabled_layer_names) 49 | } else { 50 | instance_create_info 51 | }; 52 | 53 | let inner = unsafe { entry.create_instance(&instance_create_info, None)? }; 54 | 55 | // Vulkan debug report 56 | let create_info = vk::DebugUtilsMessengerCreateInfoEXT::builder() 57 | .flags(vk::DebugUtilsMessengerCreateFlagsEXT::empty()) 58 | .message_severity( 59 | vk::DebugUtilsMessageSeverityFlagsEXT::INFO 60 | | vk::DebugUtilsMessageSeverityFlagsEXT::WARNING 61 | | vk::DebugUtilsMessageSeverityFlagsEXT::ERROR, 62 | ) 63 | .message_type( 64 | vk::DebugUtilsMessageTypeFlagsEXT::GENERAL 65 | | vk::DebugUtilsMessageTypeFlagsEXT::VALIDATION 66 | | vk::DebugUtilsMessageTypeFlagsEXT::PERFORMANCE, 67 | ) 68 | .pfn_user_callback(Some(vulkan_debug_callback)); 69 | let debug_utils = DebugUtils::new(entry, &inner); 70 | let debug_utils_messenger = 71 | unsafe { debug_utils.create_debug_utils_messenger(&create_info, None)? }; 72 | 73 | Ok(Self { 74 | inner, 75 | debug_utils, 76 | debug_utils_messenger, 77 | physical_devices: vec![], 78 | }) 79 | } 80 | 81 | pub(crate) fn enumerate_physical_devices( 82 | &mut self, 83 | surface: &Surface, 84 | ) -> Result<&[PhysicalDevice]> { 85 | if self.physical_devices.is_empty() { 86 | let physical_devices = unsafe { self.inner.enumerate_physical_devices()? }; 87 | 88 | let mut physical_devices = physical_devices 89 | .into_iter() 90 | .map(|pd| PhysicalDevice::new(&self.inner, surface, pd)) 91 | .collect::>>()?; 92 | 93 | physical_devices.sort_by_key(|pd| match pd.device_type { 94 | vk::PhysicalDeviceType::DISCRETE_GPU => 0, 95 | vk::PhysicalDeviceType::INTEGRATED_GPU => 1, 96 | _ => 2, 97 | }); 98 | 99 | self.physical_devices = physical_devices; 100 | } 101 | 102 | Ok(&self.physical_devices) 103 | } 104 | } 105 | 106 | unsafe extern "system" fn vulkan_debug_callback( 107 | flag: vk::DebugUtilsMessageSeverityFlagsEXT, 108 | typ: vk::DebugUtilsMessageTypeFlagsEXT, 109 | p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT, 110 | _: *mut c_void, 111 | ) -> vk::Bool32 { 112 | use vk::DebugUtilsMessageSeverityFlagsEXT as Flag; 113 | 114 | let message = CStr::from_ptr((*p_callback_data).p_message); 115 | match flag { 116 | Flag::VERBOSE => log::debug!("{:?} - {:?}", typ, message), 117 | Flag::INFO => log::info!("{:?} - {:?}", typ, message), 118 | Flag::WARNING => log::warn!("{:?} - {:?}", typ, message), 119 | _ => log::error!("{:?} - {:?}", typ, message), 120 | } 121 | vk::FALSE 122 | } 123 | 124 | impl Drop for Instance { 125 | fn drop(&mut self) { 126 | unsafe { 127 | self.debug_utils 128 | .destroy_debug_utils_messenger(self.debug_utils_messenger, None); 129 | self.inner.destroy_instance(None); 130 | } 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use ash; 2 | pub use gpu_allocator; 3 | 4 | mod buffer; 5 | mod command; 6 | mod context; 7 | mod descriptor; 8 | mod device; 9 | mod image; 10 | mod instance; 11 | mod physical_device; 12 | mod pipeline; 13 | mod query; 14 | mod queue; 15 | mod ray_tracing; 16 | mod sampler; 17 | mod surface; 18 | mod swapchain; 19 | mod sync; 20 | 21 | pub mod utils; 22 | 23 | pub use buffer::*; 24 | pub use command::*; 25 | pub use context::*; 26 | pub use descriptor::*; 27 | pub use device::*; 28 | pub use image::*; 29 | pub use pipeline::*; 30 | pub use query::*; 31 | pub use queue::*; 32 | pub use ray_tracing::*; 33 | pub use sampler::*; 34 | pub use swapchain::*; 35 | pub use sync::*; 36 | 37 | pub const VERSION_1_0: Version = Version::from_major_minor(1, 0); 38 | pub const VERSION_1_1: Version = Version::from_major_minor(1, 1); 39 | pub const VERSION_1_2: Version = Version::from_major_minor(1, 2); 40 | pub const VERSION_1_3: Version = Version::from_major_minor(1, 3); 41 | 42 | #[derive(Debug, Clone, Copy)] 43 | pub struct Version { 44 | pub variant: u32, 45 | pub major: u32, 46 | pub minor: u32, 47 | pub patch: u32, 48 | } 49 | 50 | impl Version { 51 | pub const fn new(variant: u32, major: u32, minor: u32, patch: u32) -> Self { 52 | Self { 53 | variant, 54 | major, 55 | minor, 56 | patch, 57 | } 58 | } 59 | 60 | pub const fn from_major(major: u32) -> Self { 61 | Self { 62 | major, 63 | ..Self::default() 64 | } 65 | } 66 | 67 | pub const fn from_major_minor(major: u32, minor: u32) -> Self { 68 | Self { 69 | major, 70 | minor, 71 | ..Self::default() 72 | } 73 | } 74 | 75 | const fn default() -> Self { 76 | Self { 77 | variant: 0, 78 | major: 0, 79 | minor: 0, 80 | patch: 0, 81 | } 82 | } 83 | 84 | pub(crate) fn make_api_version(&self) -> u32 { 85 | ash::vk::make_api_version(self.variant, self.major, self.minor, self.patch) 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/physical_device.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CStr; 2 | 3 | use anyhow::Result; 4 | use ash::{vk, Instance}; 5 | 6 | use crate::{device::DeviceFeatures, queue::QueueFamily, surface::Surface}; 7 | 8 | #[derive(Debug, Clone)] 9 | pub struct PhysicalDevice { 10 | pub(crate) inner: vk::PhysicalDevice, 11 | pub(crate) name: String, 12 | pub(crate) device_type: vk::PhysicalDeviceType, 13 | pub(crate) limits: vk::PhysicalDeviceLimits, 14 | pub(crate) queue_families: Vec, 15 | pub(crate) supported_extensions: Vec, 16 | pub(crate) supported_surface_formats: Vec, 17 | pub(crate) supported_present_modes: Vec, 18 | pub(crate) supported_device_features: DeviceFeatures, 19 | } 20 | 21 | impl PhysicalDevice { 22 | pub(crate) fn new( 23 | instance: &Instance, 24 | surface: &Surface, 25 | inner: vk::PhysicalDevice, 26 | ) -> Result { 27 | let props = unsafe { instance.get_physical_device_properties(inner) }; 28 | 29 | let name = unsafe { 30 | CStr::from_ptr(props.device_name.as_ptr()) 31 | .to_str() 32 | .unwrap() 33 | .to_owned() 34 | }; 35 | 36 | let device_type = props.device_type; 37 | let limits = props.limits; 38 | 39 | let queue_family_properties = 40 | unsafe { instance.get_physical_device_queue_family_properties(inner) }; 41 | let queue_families = queue_family_properties 42 | .into_iter() 43 | .enumerate() 44 | .map(|(index, p)| { 45 | let present_support = unsafe { 46 | surface.inner.get_physical_device_surface_support( 47 | inner, 48 | index as _, 49 | surface.surface_khr, 50 | )? 51 | }; 52 | 53 | Ok(QueueFamily::new(index as _, p, present_support)) 54 | }) 55 | .collect::>()?; 56 | 57 | let extension_properties = 58 | unsafe { instance.enumerate_device_extension_properties(inner)? }; 59 | let supported_extensions = extension_properties 60 | .into_iter() 61 | .map(|p| { 62 | let name = unsafe { CStr::from_ptr(p.extension_name.as_ptr()) }; 63 | name.to_str().unwrap().to_owned() 64 | }) 65 | .collect(); 66 | 67 | let supported_surface_formats = unsafe { 68 | surface 69 | .inner 70 | .get_physical_device_surface_formats(inner, surface.surface_khr)? 71 | }; 72 | 73 | let supported_present_modes = unsafe { 74 | surface 75 | .inner 76 | .get_physical_device_surface_present_modes(inner, surface.surface_khr)? 77 | }; 78 | 79 | let mut ray_tracing_feature = vk::PhysicalDeviceRayTracingPipelineFeaturesKHR::default(); 80 | let mut acceleration_struct_feature = 81 | vk::PhysicalDeviceAccelerationStructureFeaturesKHR::default(); 82 | let mut features12 = vk::PhysicalDeviceVulkan12Features::builder() 83 | .runtime_descriptor_array(true) 84 | .buffer_device_address(true); 85 | let mut features13 = vk::PhysicalDeviceVulkan13Features::default(); 86 | let mut features = vk::PhysicalDeviceFeatures2::builder() 87 | .push_next(&mut ray_tracing_feature) 88 | .push_next(&mut acceleration_struct_feature) 89 | .push_next(&mut features12) 90 | .push_next(&mut features13); 91 | unsafe { instance.get_physical_device_features2(inner, &mut features) }; 92 | 93 | let supported_device_features = DeviceFeatures { 94 | ray_tracing_pipeline: ray_tracing_feature.ray_tracing_pipeline == vk::TRUE, 95 | acceleration_structure: acceleration_struct_feature.acceleration_structure == vk::TRUE, 96 | runtime_descriptor_array: features12.runtime_descriptor_array == vk::TRUE, 97 | buffer_device_address: features12.buffer_device_address == vk::TRUE, 98 | dynamic_rendering: features13.dynamic_rendering == vk::TRUE, 99 | synchronization2: features13.synchronization2 == vk::TRUE, 100 | }; 101 | 102 | Ok(Self { 103 | inner, 104 | name, 105 | device_type, 106 | limits, 107 | queue_families, 108 | supported_extensions, 109 | supported_surface_formats, 110 | supported_present_modes, 111 | supported_device_features, 112 | }) 113 | } 114 | 115 | pub fn supports_extensions(&self, extensions: &[&str]) -> bool { 116 | let supported_extensions = self 117 | .supported_extensions 118 | .iter() 119 | .map(String::as_str) 120 | .collect::>(); 121 | extensions.iter().all(|e| supported_extensions.contains(e)) 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/pipeline/compute.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CString, sync::Arc}; 2 | 3 | use anyhow::Result; 4 | use ash::vk; 5 | 6 | use crate::{device::Device, Context, PipelineLayout, ShaderModule}; 7 | 8 | pub struct ComputePipeline { 9 | device: Arc, 10 | pub(crate) inner: vk::Pipeline, 11 | } 12 | 13 | #[derive(Debug, Clone, Copy)] 14 | pub struct ComputePipelineCreateInfo<'a> { 15 | pub shader_source: &'a [u8], 16 | } 17 | 18 | impl ComputePipeline { 19 | pub fn new( 20 | device: Arc, 21 | layout: &PipelineLayout, 22 | create_info: ComputePipelineCreateInfo, 23 | ) -> Result { 24 | let entry_point_name = CString::new("main").unwrap(); 25 | let shader_module = ShaderModule::from_bytes(device.clone(), create_info.shader_source)?; 26 | let shader_stage_info = vk::PipelineShaderStageCreateInfo::builder() 27 | .stage(vk::ShaderStageFlags::COMPUTE) 28 | .module(shader_module.inner) 29 | .name(&entry_point_name) 30 | .build(); 31 | 32 | let pipeline_info = vk::ComputePipelineCreateInfo::builder() 33 | .stage(shader_stage_info) 34 | .layout(layout.inner); 35 | 36 | let inner = unsafe { 37 | device 38 | .inner 39 | .create_compute_pipelines( 40 | vk::PipelineCache::null(), 41 | std::slice::from_ref(&pipeline_info), 42 | None, 43 | ) 44 | .map_err(|e| e.1)?[0] 45 | }; 46 | 47 | Ok(Self { device, inner }) 48 | } 49 | } 50 | 51 | impl Context { 52 | pub fn create_compute_pipeline( 53 | &self, 54 | layout: &PipelineLayout, 55 | create_info: ComputePipelineCreateInfo, 56 | ) -> Result { 57 | ComputePipeline::new(self.device.clone(), layout, create_info) 58 | } 59 | } 60 | 61 | impl Drop for ComputePipeline { 62 | fn drop(&mut self) { 63 | unsafe { self.device.inner.destroy_pipeline(self.inner, None) }; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/pipeline/graphics.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CString, sync::Arc}; 2 | 3 | use anyhow::Result; 4 | use ash::vk; 5 | 6 | use crate::{device::Device, Context, PipelineLayout, ShaderModule}; 7 | 8 | pub struct GraphicsPipeline { 9 | device: Arc, 10 | pub(crate) inner: vk::Pipeline, 11 | } 12 | 13 | #[derive(Debug, Clone, Copy)] 14 | pub struct GraphicsPipelineCreateInfo<'a> { 15 | pub shaders: &'a [GraphicsShaderCreateInfo<'a>], 16 | pub primitive_topology: vk::PrimitiveTopology, 17 | pub extent: Option, 18 | pub color_attachment_format: vk::Format, 19 | pub color_attachment_blend: Option, 20 | pub dynamic_states: Option<&'a [vk::DynamicState]>, 21 | } 22 | 23 | pub trait Vertex { 24 | fn bindings() -> Vec; 25 | fn attributes() -> Vec; 26 | } 27 | 28 | #[derive(Debug, Clone, Copy)] 29 | pub struct GraphicsShaderCreateInfo<'a> { 30 | pub source: &'a [u8], 31 | pub stage: vk::ShaderStageFlags, 32 | } 33 | 34 | impl GraphicsPipeline { 35 | pub(crate) fn new( 36 | device: Arc, 37 | layout: &PipelineLayout, 38 | create_info: GraphicsPipelineCreateInfo, 39 | ) -> Result { 40 | // shaders 41 | let mut shader_modules = vec![]; 42 | let mut shader_stages_infos = vec![]; 43 | 44 | let entry_point_name = CString::new("main").unwrap(); 45 | 46 | for shader in create_info.shaders.iter() { 47 | let module = ShaderModule::from_bytes(device.clone(), shader.source)?; 48 | 49 | let stage = vk::PipelineShaderStageCreateInfo::builder() 50 | .stage(shader.stage) 51 | .module(module.inner) 52 | .name(&entry_point_name) 53 | .build(); 54 | 55 | shader_modules.push(module); 56 | shader_stages_infos.push(stage); 57 | } 58 | 59 | // vertex 60 | let vertex_bindings = V::bindings(); 61 | let vertex_attributes = V::attributes(); 62 | let vertex_input_info = vk::PipelineVertexInputStateCreateInfo::builder() 63 | .vertex_binding_descriptions(&vertex_bindings) 64 | .vertex_attribute_descriptions(&vertex_attributes); 65 | 66 | let input_assembly_info = vk::PipelineInputAssemblyStateCreateInfo::builder() 67 | .topology(create_info.primitive_topology) 68 | .primitive_restart_enable(false); 69 | 70 | // viewport/scissors 71 | let viewports = create_info 72 | .extent 73 | .map(|e| { 74 | vec![vk::Viewport { 75 | x: 0.0, 76 | y: 0.0, 77 | width: e.width as _, 78 | height: e.height as _, 79 | min_depth: 0.0, 80 | max_depth: 1.0, 81 | }] 82 | }) 83 | .unwrap_or_default(); 84 | let scissors = create_info 85 | .extent 86 | .map(|e| { 87 | vec![vk::Rect2D { 88 | offset: vk::Offset2D { x: 0, y: 0 }, 89 | extent: e, 90 | }] 91 | }) 92 | .unwrap_or_default(); 93 | 94 | let viewport_info = vk::PipelineViewportStateCreateInfo::builder() 95 | .viewports(&viewports) 96 | .viewport_count(1) 97 | .scissors(&scissors) 98 | .scissor_count(1); 99 | 100 | // raster 101 | let rasterizer_info = vk::PipelineRasterizationStateCreateInfo::builder() 102 | .depth_clamp_enable(false) 103 | .rasterizer_discard_enable(false) 104 | .polygon_mode(vk::PolygonMode::FILL) 105 | .line_width(1.0) 106 | .cull_mode(vk::CullModeFlags::BACK) 107 | .front_face(vk::FrontFace::COUNTER_CLOCKWISE) 108 | .depth_bias_enable(false) 109 | .depth_bias_constant_factor(0.0) 110 | .depth_bias_clamp(0.0) 111 | .depth_bias_slope_factor(0.0); 112 | 113 | // msaa 114 | let multisampling_info = vk::PipelineMultisampleStateCreateInfo::builder() 115 | .sample_shading_enable(false) 116 | .rasterization_samples(vk::SampleCountFlags::TYPE_1) 117 | .min_sample_shading(1.0) 118 | .alpha_to_coverage_enable(false) 119 | .alpha_to_one_enable(false); 120 | 121 | // blending 122 | let color_blend_attachment = 123 | create_info 124 | .color_attachment_blend 125 | .unwrap_or(vk::PipelineColorBlendAttachmentState { 126 | color_write_mask: vk::ColorComponentFlags::RGBA, 127 | ..Default::default() 128 | }); 129 | let color_blend_attachments = [color_blend_attachment]; 130 | let color_blending_info = vk::PipelineColorBlendStateCreateInfo::builder() 131 | .logic_op_enable(false) 132 | .logic_op(vk::LogicOp::COPY) 133 | .attachments(&color_blend_attachments) 134 | .blend_constants([0.0, 0.0, 0.0, 0.0]); 135 | 136 | // dynamic states 137 | let dynamic_state_info = vk::PipelineDynamicStateCreateInfo::builder() 138 | .dynamic_states(create_info.dynamic_states.unwrap_or(&[])); 139 | 140 | // dynamic rendering 141 | let color_attachment_formats = [create_info.color_attachment_format]; 142 | let mut rendering_info = vk::PipelineRenderingCreateInfo::builder() 143 | .color_attachment_formats(&color_attachment_formats); 144 | 145 | let pipeline_info = vk::GraphicsPipelineCreateInfo::builder() 146 | .stages(&shader_stages_infos) 147 | .vertex_input_state(&vertex_input_info) 148 | .input_assembly_state(&input_assembly_info) 149 | .viewport_state(&viewport_info) 150 | .rasterization_state(&rasterizer_info) 151 | .multisample_state(&multisampling_info) 152 | .color_blend_state(&color_blending_info) 153 | .dynamic_state(&dynamic_state_info) 154 | .layout(layout.inner) 155 | .push_next(&mut rendering_info); 156 | 157 | let inner = unsafe { 158 | device 159 | .inner 160 | .create_graphics_pipelines( 161 | vk::PipelineCache::null(), 162 | std::slice::from_ref(&pipeline_info), 163 | None, 164 | ) 165 | .map_err(|e| e.1)?[0] 166 | }; 167 | 168 | Ok(Self { device, inner }) 169 | } 170 | } 171 | 172 | impl Context { 173 | pub fn create_graphics_pipeline( 174 | &self, 175 | layout: &PipelineLayout, 176 | create_info: GraphicsPipelineCreateInfo, 177 | ) -> Result { 178 | GraphicsPipeline::new::(self.device.clone(), layout, create_info) 179 | } 180 | } 181 | 182 | impl Drop for GraphicsPipeline { 183 | fn drop(&mut self) { 184 | unsafe { self.device.inner.destroy_pipeline(self.inner, None) }; 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/pipeline/layout.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::Result; 4 | use ash::vk; 5 | 6 | use crate::{device::Device, Context, DescriptorSetLayout}; 7 | 8 | pub struct PipelineLayout { 9 | device: Arc, 10 | pub(crate) inner: vk::PipelineLayout, 11 | } 12 | 13 | impl PipelineLayout { 14 | pub(crate) fn new( 15 | device: Arc, 16 | descriptor_set_layouts: &[&DescriptorSetLayout], 17 | ) -> Result { 18 | let layouts = descriptor_set_layouts 19 | .iter() 20 | .map(|l| l.inner) 21 | .collect::>(); 22 | 23 | let pipe_layout_info = vk::PipelineLayoutCreateInfo::builder().set_layouts(&layouts); 24 | let inner = unsafe { 25 | device 26 | .inner 27 | .create_pipeline_layout(&pipe_layout_info, None)? 28 | }; 29 | 30 | Ok(Self { device, inner }) 31 | } 32 | } 33 | 34 | impl Context { 35 | pub fn create_pipeline_layout( 36 | &self, 37 | descriptor_set_layouts: &[&DescriptorSetLayout], 38 | ) -> Result { 39 | PipelineLayout::new(self.device.clone(), descriptor_set_layouts) 40 | } 41 | } 42 | 43 | impl Drop for PipelineLayout { 44 | fn drop(&mut self) { 45 | unsafe { self.device.inner.destroy_pipeline_layout(self.inner, None) }; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/pipeline/mod.rs: -------------------------------------------------------------------------------- 1 | mod compute; 2 | mod graphics; 3 | mod layout; 4 | mod shader; 5 | 6 | pub use compute::*; 7 | pub use graphics::*; 8 | pub use layout::*; 9 | pub use shader::*; 10 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/pipeline/shader.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::Result; 4 | use ash::vk; 5 | 6 | use crate::{device::Device, utils::read_shader_from_bytes, Context}; 7 | 8 | pub struct ShaderModule { 9 | device: Arc, 10 | pub(crate) inner: vk::ShaderModule, 11 | } 12 | 13 | impl ShaderModule { 14 | pub(crate) fn from_bytes(device: Arc, source: &[u8]) -> Result { 15 | let source = read_shader_from_bytes(source)?; 16 | 17 | let create_info = vk::ShaderModuleCreateInfo::builder().code(&source); 18 | let inner = unsafe { device.inner.create_shader_module(&create_info, None)? }; 19 | 20 | Ok(Self { device, inner }) 21 | } 22 | } 23 | 24 | impl Context { 25 | pub fn create_shader_module(&self, source: &[u8]) -> Result { 26 | ShaderModule::from_bytes(self.device.clone(), source) 27 | } 28 | } 29 | 30 | impl Drop for ShaderModule { 31 | fn drop(&mut self) { 32 | unsafe { 33 | self.device.inner.destroy_shader_module(self.inner, None); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/query.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::Result; 4 | use ash::vk; 5 | 6 | use crate::{Context, Device}; 7 | 8 | pub struct TimestampQueryPool { 9 | device: Arc, 10 | pub(crate) inner: vk::QueryPool, 11 | timestamp_period: f64, 12 | } 13 | 14 | impl TimestampQueryPool { 15 | pub(crate) fn new(device: Arc, timestamp_period: f64) -> Result { 16 | let create_info = vk::QueryPoolCreateInfo::builder() 17 | .query_type(vk::QueryType::TIMESTAMP) 18 | .query_count(C as _); 19 | 20 | let inner = unsafe { device.inner.create_query_pool(&create_info, None)? }; 21 | 22 | Ok(Self { 23 | device, 24 | inner, 25 | timestamp_period, 26 | }) 27 | } 28 | } 29 | 30 | impl Context { 31 | pub fn create_timestamp_query_pool(&self) -> Result> { 32 | TimestampQueryPool::new( 33 | self.device.clone(), 34 | self.physical_device.limits.timestamp_period as _, 35 | ) 36 | } 37 | } 38 | 39 | impl Drop for TimestampQueryPool { 40 | fn drop(&mut self) { 41 | unsafe { 42 | self.device.inner.destroy_query_pool(self.inner, None); 43 | } 44 | } 45 | } 46 | 47 | impl TimestampQueryPool { 48 | pub fn reset_all(&self) { 49 | unsafe { 50 | self.device.inner.reset_query_pool(self.inner, 0, C as _); 51 | } 52 | } 53 | 54 | pub fn wait_for_all_results(&self) -> Result<[u64; C]> { 55 | let mut data = [0u64; C]; 56 | 57 | unsafe { 58 | self.device.inner.get_query_pool_results( 59 | self.inner, 60 | 0, 61 | C as _, 62 | &mut data, 63 | vk::QueryResultFlags::WAIT | vk::QueryResultFlags::TYPE_64, 64 | )?; 65 | } 66 | 67 | let mut result = [0u64; C]; 68 | for (index, timestamp) in data.iter().enumerate() { 69 | result[index] = (*timestamp as f64 * self.timestamp_period) as u64; 70 | } 71 | 72 | Ok(result) 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/queue.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::Result; 4 | use ash::vk; 5 | 6 | use crate::{device::Device, CommandBuffer, Fence, Semaphore}; 7 | 8 | #[derive(Debug, Clone, Copy)] 9 | pub struct QueueFamily { 10 | pub index: u32, 11 | pub(crate) inner: vk::QueueFamilyProperties, 12 | supports_present: bool, 13 | } 14 | 15 | impl QueueFamily { 16 | pub(crate) fn new( 17 | index: u32, 18 | inner: vk::QueueFamilyProperties, 19 | supports_present: bool, 20 | ) -> Self { 21 | Self { 22 | index, 23 | inner, 24 | supports_present, 25 | } 26 | } 27 | 28 | pub fn supports_compute(&self) -> bool { 29 | self.inner.queue_flags.contains(vk::QueueFlags::COMPUTE) 30 | } 31 | 32 | pub fn supports_graphics(&self) -> bool { 33 | self.inner.queue_flags.contains(vk::QueueFlags::GRAPHICS) 34 | } 35 | 36 | pub fn supports_present(&self) -> bool { 37 | self.supports_present 38 | } 39 | 40 | pub fn has_queues(&self) -> bool { 41 | self.inner.queue_count > 0 42 | } 43 | 44 | pub fn supports_timestamp_queries(&self) -> bool { 45 | self.inner.timestamp_valid_bits > 0 46 | } 47 | } 48 | 49 | pub struct Queue { 50 | device: Arc, 51 | pub inner: vk::Queue, 52 | } 53 | 54 | impl Queue { 55 | pub(crate) fn new(device: Arc, inner: vk::Queue) -> Self { 56 | Self { device, inner } 57 | } 58 | 59 | pub fn submit( 60 | &self, 61 | command_buffer: &CommandBuffer, 62 | wait_semaphore: Option, 63 | signal_semaphore: Option, 64 | fence: &Fence, 65 | ) -> Result<()> { 66 | let wait_semaphore_submit_info = wait_semaphore.map(|s| { 67 | vk::SemaphoreSubmitInfo::builder() 68 | .semaphore(s.semaphore.inner) 69 | .stage_mask(s.stage_mask) 70 | }); 71 | 72 | let signal_semaphore_submit_info = signal_semaphore.map(|s| { 73 | vk::SemaphoreSubmitInfo::builder() 74 | .semaphore(s.semaphore.inner) 75 | .stage_mask(s.stage_mask) 76 | }); 77 | 78 | let cmd_buffer_submit_info = 79 | vk::CommandBufferSubmitInfo::builder().command_buffer(command_buffer.inner); 80 | 81 | let submit_info = vk::SubmitInfo2::builder() 82 | .command_buffer_infos(std::slice::from_ref(&cmd_buffer_submit_info)); 83 | 84 | let submit_info = match wait_semaphore_submit_info.as_ref() { 85 | Some(info) => submit_info.wait_semaphore_infos(std::slice::from_ref(info)), 86 | None => submit_info, 87 | }; 88 | 89 | let submit_info = match signal_semaphore_submit_info.as_ref() { 90 | Some(info) => submit_info.signal_semaphore_infos(std::slice::from_ref(info)), 91 | None => submit_info, 92 | }; 93 | 94 | unsafe { 95 | self.device.inner.queue_submit2( 96 | self.inner, 97 | std::slice::from_ref(&submit_info), 98 | fence.inner, 99 | )? 100 | }; 101 | 102 | Ok(()) 103 | } 104 | } 105 | 106 | pub struct SemaphoreSubmitInfo<'a> { 107 | pub semaphore: &'a Semaphore, 108 | pub stage_mask: vk::PipelineStageFlags2, 109 | } 110 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/ray_tracing/mod.rs: -------------------------------------------------------------------------------- 1 | mod acceleration_structure; 2 | mod pipeline; 3 | mod shader_binding_table; 4 | 5 | pub use acceleration_structure::*; 6 | pub use pipeline::*; 7 | pub use shader_binding_table::*; 8 | 9 | use ash::{ 10 | extensions::khr::{ 11 | AccelerationStructure as AshAccelerationStructure, 12 | RayTracingPipeline as AshRayTracingPipeline, 13 | }, 14 | vk, 15 | }; 16 | 17 | use crate::{device::Device, instance::Instance, physical_device::PhysicalDevice}; 18 | 19 | pub struct RayTracingContext { 20 | pub pipeline_properties: vk::PhysicalDeviceRayTracingPipelinePropertiesKHR, 21 | pub pipeline_fn: AshRayTracingPipeline, 22 | pub acceleration_structure_properties: vk::PhysicalDeviceAccelerationStructurePropertiesKHR, 23 | pub acceleration_structure_fn: AshAccelerationStructure, 24 | } 25 | 26 | impl RayTracingContext { 27 | pub(crate) fn new(instance: &Instance, pdevice: &PhysicalDevice, device: &Device) -> Self { 28 | let pipeline_properties = 29 | unsafe { AshRayTracingPipeline::get_properties(&instance.inner, pdevice.inner) }; 30 | let pipeline_fn = AshRayTracingPipeline::new(&instance.inner, &device.inner); 31 | 32 | let acceleration_structure_properties = 33 | unsafe { AshAccelerationStructure::get_properties(&instance.inner, pdevice.inner) }; 34 | let acceleration_structure_fn = 35 | AshAccelerationStructure::new(&instance.inner, &device.inner); 36 | 37 | Self { 38 | pipeline_properties, 39 | pipeline_fn, 40 | acceleration_structure_properties, 41 | acceleration_structure_fn, 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/ray_tracing/pipeline.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::CString, sync::Arc}; 2 | 3 | use anyhow::Result; 4 | use ash::vk; 5 | 6 | use crate::{device::Device, Context}; 7 | 8 | use crate::{PipelineLayout, RayTracingContext, ShaderModule}; 9 | 10 | #[derive(Debug, Clone, Copy)] 11 | pub struct RayTracingPipelineCreateInfo<'a> { 12 | pub shaders: &'a [RayTracingShaderCreateInfo<'a>], 13 | pub max_ray_recursion_depth: u32, 14 | } 15 | 16 | #[derive(Debug, Clone, Copy)] 17 | pub struct RayTracingShaderCreateInfo<'a> { 18 | pub source: &'a [u8], 19 | pub stage: vk::ShaderStageFlags, 20 | pub group: RayTracingShaderGroup, 21 | } 22 | 23 | #[derive(Debug, Clone, Copy, PartialEq)] 24 | pub enum RayTracingShaderGroup { 25 | RayGen, 26 | Miss, 27 | ClosestHit, 28 | AnyHit, 29 | ShadowAnyHit, 30 | ShadowMiss, 31 | } 32 | 33 | pub struct RayTracingPipeline { 34 | device: Arc, 35 | pub(crate) inner: vk::Pipeline, 36 | pub(crate) shader_group_info: RayTracingShaderGroupInfo, 37 | } 38 | 39 | #[derive(Debug, Clone, Copy, Default)] 40 | pub struct RayTracingShaderGroupInfo { 41 | pub group_count: u32, 42 | pub raygen_shader_count: u32, 43 | pub miss_shader_count: u32, 44 | pub hit_shader_count: u32, 45 | } 46 | 47 | impl RayTracingPipeline { 48 | pub(crate) fn new( 49 | device: Arc, 50 | ray_tracing: &RayTracingContext, 51 | layout: &PipelineLayout, 52 | create_info: RayTracingPipelineCreateInfo, 53 | ) -> Result { 54 | let mut modules = vec![]; 55 | let mut stages = vec![]; 56 | let mut groups = vec![]; 57 | 58 | let entry_point_name = CString::new("main").unwrap(); 59 | let mut anyhit = None; 60 | let mut shadow_anyhit = None; 61 | create_info 62 | .shaders 63 | .iter() 64 | .enumerate() 65 | .for_each(|(index, shader)| match shader.group { 66 | RayTracingShaderGroup::AnyHit => { 67 | assert!(anyhit.is_none()); 68 | anyhit = Some(index); 69 | } 70 | RayTracingShaderGroup::ShadowAnyHit => { 71 | assert!(shadow_anyhit.is_none()); 72 | shadow_anyhit = Some(index); 73 | } 74 | _ => {} 75 | }); 76 | let mut shader_group_info = RayTracingShaderGroupInfo { 77 | group_count: create_info.shaders.len() as u32 - if anyhit.is_some() { 1 } else { 0 }, 78 | ..Default::default() 79 | }; 80 | for (shader_index, shader) in create_info.shaders.iter().enumerate() { 81 | let module = ShaderModule::from_bytes(device.clone(), shader.source)?; 82 | 83 | let stage = vk::PipelineShaderStageCreateInfo::builder() 84 | .stage(shader.stage) 85 | .module(module.inner) 86 | .name(&entry_point_name) 87 | .build(); 88 | 89 | match shader.group { 90 | RayTracingShaderGroup::RayGen => shader_group_info.raygen_shader_count += 1, 91 | RayTracingShaderGroup::Miss | RayTracingShaderGroup::ShadowMiss => { 92 | shader_group_info.miss_shader_count += 1 93 | } 94 | RayTracingShaderGroup::ClosestHit | RayTracingShaderGroup::ShadowAnyHit => { 95 | shader_group_info.hit_shader_count += 1 96 | } 97 | _ => {} 98 | }; 99 | if shader.group != RayTracingShaderGroup::AnyHit { 100 | let mut group = vk::RayTracingShaderGroupCreateInfoKHR::builder() 101 | .ty(vk::RayTracingShaderGroupTypeKHR::GENERAL) 102 | .general_shader(vk::SHADER_UNUSED_KHR) 103 | .closest_hit_shader(vk::SHADER_UNUSED_KHR) 104 | .any_hit_shader(vk::SHADER_UNUSED_KHR) 105 | .intersection_shader(vk::SHADER_UNUSED_KHR); 106 | group = match shader.group { 107 | RayTracingShaderGroup::RayGen 108 | | RayTracingShaderGroup::Miss 109 | | RayTracingShaderGroup::ShadowMiss => group.general_shader(shader_index as _), 110 | RayTracingShaderGroup::ClosestHit => group 111 | .ty(vk::RayTracingShaderGroupTypeKHR::TRIANGLES_HIT_GROUP) 112 | .closest_hit_shader(shader_index as _) 113 | .any_hit_shader(if let Some(anyhit_index) = anyhit { 114 | anyhit_index as _ 115 | } else { 116 | vk::SHADER_UNUSED_KHR 117 | }), 118 | 119 | RayTracingShaderGroup::ShadowAnyHit => group 120 | .ty(vk::RayTracingShaderGroupTypeKHR::TRIANGLES_HIT_GROUP) 121 | .any_hit_shader(shader_index as _), 122 | RayTracingShaderGroup::AnyHit => { 123 | unreachable!() 124 | } // _ => {} 125 | }; 126 | groups.push(group.build()); 127 | } 128 | 129 | modules.push(module); 130 | stages.push(stage); 131 | } 132 | 133 | let pipe_info = vk::RayTracingPipelineCreateInfoKHR::builder() 134 | .layout(layout.inner) 135 | .stages(&stages) 136 | .groups(&groups) 137 | // https://developer.nvidia.com/blog/best-practices-for-using-nvidia-rtx-ray-tracing-updated/ 138 | // Use SKIP_PROCEDURAL_PRIMITIVES, SKIP_AABBS, and SKIP_TRIANGLES whenever possible. These pipeline state flags allow simple but potentially effective optimizations in state compilation. 139 | // If the rayTraversalPrimitiveCulling feature is not enabled, flags must not include VK_PIPELINE_CREATE_RAY_TRACING_SKIP_AABBS_BIT_KHR. 140 | // .flags(vk::PipelineCreateFlags::RAY_TRACING_SKIP_AABBS_KHR) 141 | .max_pipeline_ray_recursion_depth(2); 142 | 143 | let inner = unsafe { 144 | ray_tracing.pipeline_fn.create_ray_tracing_pipelines( 145 | vk::DeferredOperationKHR::null(), 146 | vk::PipelineCache::null(), 147 | std::slice::from_ref(&pipe_info), 148 | None, 149 | )?[0] 150 | }; 151 | 152 | Ok(Self { 153 | device, 154 | inner, 155 | shader_group_info, 156 | }) 157 | } 158 | } 159 | 160 | impl Context { 161 | pub fn create_ray_tracing_pipeline( 162 | &self, 163 | layout: &PipelineLayout, 164 | create_info: RayTracingPipelineCreateInfo, 165 | ) -> Result { 166 | let ray_tracing = self.ray_tracing.as_ref().expect( 167 | "Cannot call Context::create_ray_tracing_pipeline when ray tracing is not enabled", 168 | ); 169 | 170 | RayTracingPipeline::new(self.device.clone(), ray_tracing, layout, create_info) 171 | } 172 | } 173 | 174 | impl Drop for RayTracingPipeline { 175 | fn drop(&mut self) { 176 | unsafe { self.device.inner.destroy_pipeline(self.inner, None) }; 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/ray_tracing/shader_binding_table.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use ash::vk; 3 | use gpu_allocator::MemoryLocation; 4 | 5 | use crate::{utils::compute_aligned_size, Buffer, Context, RayTracingContext, RayTracingPipeline}; 6 | 7 | pub struct ShaderBindingTable { 8 | _buffer: Buffer, 9 | pub(crate) raygen_region: vk::StridedDeviceAddressRegionKHR, 10 | pub(crate) miss_region: vk::StridedDeviceAddressRegionKHR, 11 | pub(crate) hit_region: vk::StridedDeviceAddressRegionKHR, 12 | } 13 | 14 | impl ShaderBindingTable { 15 | pub(crate) fn new( 16 | context: &Context, 17 | ray_tracing: &RayTracingContext, 18 | pipeline: &RayTracingPipeline, 19 | ) -> Result { 20 | let desc = pipeline.shader_group_info; 21 | 22 | // Handle size & aligment 23 | let handle_size = ray_tracing.pipeline_properties.shader_group_handle_size; 24 | let handle_alignment = ray_tracing 25 | .pipeline_properties 26 | .shader_group_handle_alignment; 27 | let aligned_handle_size = compute_aligned_size(handle_size, handle_alignment); 28 | let handle_pad = aligned_handle_size - handle_size; 29 | 30 | let group_alignment = ray_tracing.pipeline_properties.shader_group_base_alignment; 31 | 32 | // Get Handles 33 | let data_size = desc.group_count * handle_size; 34 | let handles = unsafe { 35 | ray_tracing 36 | .pipeline_fn 37 | .get_ray_tracing_shader_group_handles( 38 | pipeline.inner, 39 | 0, 40 | desc.group_count, 41 | data_size as _, 42 | )? 43 | }; 44 | 45 | // Region sizes 46 | let raygen_region_size = compute_aligned_size( 47 | desc.raygen_shader_count * aligned_handle_size, 48 | group_alignment, 49 | ); 50 | 51 | let miss_region_size = compute_aligned_size( 52 | desc.miss_shader_count * aligned_handle_size, 53 | group_alignment, 54 | ); 55 | let hit_region_size = 56 | compute_aligned_size(desc.hit_shader_count * aligned_handle_size, group_alignment); 57 | 58 | // Create sbt data 59 | let buffer_size = raygen_region_size + miss_region_size + hit_region_size; 60 | let mut stb_data = Vec::::with_capacity(buffer_size as _); 61 | let groups_shader_count = [ 62 | desc.raygen_shader_count, 63 | desc.miss_shader_count, 64 | desc.hit_shader_count, 65 | ]; 66 | 67 | let mut offset = 0; 68 | // for each groups 69 | for group_shader_count in groups_shader_count { 70 | let group_size = group_shader_count * aligned_handle_size; 71 | let aligned_group_size = compute_aligned_size(group_size, group_alignment); 72 | let group_pad = aligned_group_size - group_size; 73 | 74 | // for each handle 75 | for _ in 0..group_shader_count { 76 | //copy handle 77 | for _ in 0..handle_size as usize { 78 | stb_data.push(handles[offset]); 79 | offset += 1; 80 | } 81 | 82 | // pad handle to alignment 83 | stb_data.extend(vec![0; handle_pad as usize]); 84 | } 85 | 86 | // pad group to alignment 87 | stb_data.extend(vec![0; group_pad as usize]); 88 | } 89 | 90 | // Create buffer 91 | let buffer_usage = vk::BufferUsageFlags::SHADER_BINDING_TABLE_KHR 92 | | vk::BufferUsageFlags::SHADER_DEVICE_ADDRESS; 93 | let memory_location = MemoryLocation::CpuToGpu; 94 | 95 | let buffer = context.create_buffer(buffer_usage, memory_location, buffer_size as _)?; 96 | 97 | buffer.copy_data_to_buffer(&stb_data)?; 98 | 99 | let address = buffer.get_device_address(); 100 | 101 | // see https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/Images/sbt_0.png 102 | let raygen_region = vk::StridedDeviceAddressRegionKHR::builder() 103 | .device_address(address) 104 | .size(raygen_region_size as _) 105 | .stride(raygen_region_size as _) 106 | .build(); 107 | 108 | let miss_region = vk::StridedDeviceAddressRegionKHR::builder() 109 | .device_address(address + raygen_region.size) 110 | .size(miss_region_size as _) 111 | .stride(aligned_handle_size as _) 112 | .build(); 113 | 114 | let hit_region = vk::StridedDeviceAddressRegionKHR::builder() 115 | .device_address(address + raygen_region.size + miss_region.size) 116 | .size(hit_region_size as _) 117 | .stride(aligned_handle_size as _) 118 | .build(); 119 | 120 | Ok(Self { 121 | _buffer: buffer, 122 | raygen_region, 123 | miss_region, 124 | hit_region, 125 | }) 126 | } 127 | } 128 | 129 | impl Context { 130 | pub fn create_shader_binding_table( 131 | &self, 132 | pipeline: &RayTracingPipeline, 133 | ) -> Result { 134 | let ray_tracing = self.ray_tracing.as_ref().expect( 135 | "Cannot call Context::create_shader_binding_table when ray tracing is not enabled", 136 | ); 137 | 138 | ShaderBindingTable::new(self, ray_tracing, pipeline) 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/sampler.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use anyhow::Result; 4 | use ash::vk; 5 | 6 | use crate::{device::Device, Context}; 7 | 8 | pub struct Sampler { 9 | device: Arc, 10 | pub(crate) inner: vk::Sampler, 11 | } 12 | 13 | impl Sampler { 14 | pub(crate) fn new(device: Arc, create_info: &vk::SamplerCreateInfo) -> Result { 15 | let inner = unsafe { device.inner.create_sampler(create_info, None)? }; 16 | 17 | Ok(Self { device, inner }) 18 | } 19 | } 20 | 21 | impl Context { 22 | pub fn create_sampler(&self, create_info: &vk::SamplerCreateInfo) -> Result { 23 | Sampler::new(self.device.clone(), create_info) 24 | } 25 | } 26 | 27 | impl Drop for Sampler { 28 | fn drop(&mut self) { 29 | unsafe { 30 | self.device.inner.destroy_sampler(self.inner, None); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/surface.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::platforms; 2 | use anyhow::Result; 3 | use ash::{extensions::khr::Surface as AshSurface, vk, Entry}; 4 | 5 | use crate::instance::Instance; 6 | 7 | pub struct Surface { 8 | pub(crate) inner: AshSurface, 9 | pub surface_khr: vk::SurfaceKHR, 10 | } 11 | 12 | impl Surface { 13 | pub(crate) fn new( 14 | entry: &Entry, 15 | instance: &Instance, 16 | window: &winit::window::Window, 17 | ) -> Result { 18 | let inner = AshSurface::new(entry, &instance.inner); 19 | let surface_khr = unsafe { 20 | platforms::create_surface(entry, &instance.inner, window) 21 | .expect("Failed to create surface.") 22 | }; 23 | 24 | Ok(Self { inner, surface_khr }) 25 | } 26 | } 27 | 28 | impl Drop for Surface { 29 | fn drop(&mut self) { 30 | unsafe { 31 | self.inner.destroy_surface(self.surface_khr, None); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/sync.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use ash::vk; 3 | use std::sync::Arc; 4 | 5 | use crate::{device::Device, Context}; 6 | 7 | pub struct Semaphore { 8 | device: Arc, 9 | pub(crate) inner: vk::Semaphore, 10 | } 11 | 12 | impl Semaphore { 13 | pub(crate) fn new(device: Arc) -> Result { 14 | let semaphore_info = vk::SemaphoreCreateInfo::builder(); 15 | let inner = unsafe { device.inner.create_semaphore(&semaphore_info, None)? }; 16 | 17 | Ok(Self { device, inner }) 18 | } 19 | } 20 | 21 | impl Context { 22 | pub fn create_semaphore(&self) -> Result { 23 | Semaphore::new(self.device.clone()) 24 | } 25 | } 26 | 27 | impl Drop for Semaphore { 28 | fn drop(&mut self) { 29 | unsafe { 30 | self.device.inner.destroy_semaphore(self.inner, None); 31 | } 32 | } 33 | } 34 | 35 | pub struct Fence { 36 | device: Arc, 37 | pub(crate) inner: vk::Fence, 38 | } 39 | 40 | impl Fence { 41 | pub(crate) fn new(device: Arc, flags: Option) -> Result { 42 | let flags = flags.unwrap_or_else(vk::FenceCreateFlags::empty); 43 | 44 | let fence_info = vk::FenceCreateInfo::builder().flags(flags); 45 | let inner = unsafe { device.inner.create_fence(&fence_info, None)? }; 46 | 47 | Ok(Self { device, inner }) 48 | } 49 | 50 | pub fn wait(&self, timeout: Option) -> Result<()> { 51 | let timeout = timeout.unwrap_or(u64::MAX); 52 | 53 | unsafe { 54 | self.device 55 | .inner 56 | .wait_for_fences(&[self.inner], true, timeout)? 57 | }; 58 | 59 | Ok(()) 60 | } 61 | 62 | pub fn reset(&self) -> Result<()> { 63 | unsafe { self.device.inner.reset_fences(&[self.inner])? }; 64 | 65 | Ok(()) 66 | } 67 | 68 | pub fn null(device: &Arc) -> Self { 69 | Self { 70 | device: device.clone(), 71 | inner: vk::Fence::null(), 72 | } 73 | } 74 | } 75 | 76 | impl Context { 77 | pub fn create_fence(&self, flags: Option) -> Result { 78 | Fence::new(self.device.clone(), flags) 79 | } 80 | } 81 | 82 | impl Drop for Fence { 83 | fn drop(&mut self) { 84 | unsafe { 85 | self.device.inner.destroy_fence(self.inner, None); 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use std::mem::size_of_val; 2 | 3 | use anyhow::Result; 4 | use ash::vk; 5 | pub mod platforms; 6 | 7 | use gpu_allocator::MemoryLocation; 8 | 9 | use crate::{Buffer, CommandBuffer, Context}; 10 | 11 | pub fn compute_aligned_size(size: u32, alignment: u32) -> u32 { 12 | (size + (alignment - 1)) & !(alignment - 1) 13 | } 14 | 15 | pub fn read_shader_from_bytes(bytes: &[u8]) -> Result> { 16 | let mut cursor = std::io::Cursor::new(bytes); 17 | Ok(ash::util::read_spv(&mut cursor)?) 18 | } 19 | 20 | pub fn create_gpu_only_buffer_from_data( 21 | context: &Context, 22 | usage: vk::BufferUsageFlags, 23 | data: &[T], 24 | ) -> Result { 25 | let size = size_of_val(data) as _; 26 | let staging_buffer = context.create_buffer( 27 | vk::BufferUsageFlags::TRANSFER_SRC, 28 | MemoryLocation::CpuToGpu, 29 | size, 30 | )?; 31 | staging_buffer.copy_data_to_buffer(data)?; 32 | 33 | let buffer = context.create_buffer( 34 | usage | vk::BufferUsageFlags::TRANSFER_DST, 35 | MemoryLocation::GpuOnly, 36 | size, 37 | )?; 38 | 39 | context.execute_one_time_commands(|cmd_buffer| { 40 | cmd_buffer.copy_buffer(&staging_buffer, &buffer); 41 | })?; 42 | 43 | Ok(buffer) 44 | } 45 | 46 | pub fn create_gpu_only_buffer_from_data_batch( 47 | context: &Context, 48 | usage: vk::BufferUsageFlags, 49 | data: &[T], 50 | cmd_buffer: &CommandBuffer, 51 | ) -> Result<(Buffer, Buffer)> { 52 | let size = size_of_val(data) as _; 53 | let staging_buffer = context.create_buffer( 54 | vk::BufferUsageFlags::TRANSFER_SRC, 55 | MemoryLocation::CpuToGpu, 56 | size, 57 | )?; 58 | staging_buffer.copy_data_to_buffer(data)?; 59 | 60 | let buffer = context.create_buffer( 61 | usage | vk::BufferUsageFlags::TRANSFER_DST, 62 | MemoryLocation::GpuOnly, 63 | size, 64 | )?; 65 | 66 | cmd_buffer.copy_buffer(&staging_buffer, &buffer); 67 | 68 | Ok((buffer, staging_buffer)) 69 | } 70 | -------------------------------------------------------------------------------- /crates/libs/vulkan/src/utils/platforms.rs: -------------------------------------------------------------------------------- 1 | use ash::vk::{self}; 2 | use ash::{Entry, Instance}; 3 | 4 | #[cfg(target_os = "windows")] 5 | use ash::extensions::khr::Win32Surface; 6 | #[cfg(all(unix, not(target_os = "android"), not(target_os = "macos")))] 7 | use ash::extensions::khr::XlibSurface; 8 | #[cfg(target_os = "macos")] 9 | use ash::extensions::mvk::MacOSSurface; 10 | 11 | use ash::extensions::ext::DebugUtils; 12 | use ash::extensions::khr::Surface; 13 | 14 | #[cfg(target_os = "macos")] 15 | use cocoa::appkit::{NSView, NSWindow}; 16 | #[cfg(target_os = "macos")] 17 | use cocoa::base::id as cocoa_id; 18 | #[cfg(target_os = "macos")] 19 | use metal::CoreAnimationLayer; 20 | #[cfg(target_os = "macos")] 21 | use objc::runtime::YES; 22 | 23 | // required extension ------------------------------------------------------ 24 | #[cfg(target_os = "macos")] 25 | pub fn required_extension_names() -> Vec<*const i8> { 26 | vec![ 27 | Surface::name().as_ptr(), 28 | MacOSSurface::name().as_ptr(), 29 | DebugUtils::name().as_ptr(), 30 | ] 31 | } 32 | 33 | #[cfg(all(windows))] 34 | pub fn required_extension_names() -> Vec<*const i8> { 35 | vec![ 36 | Surface::name().as_ptr(), 37 | Win32Surface::name().as_ptr(), 38 | DebugUtils::name().as_ptr(), 39 | ] 40 | } 41 | 42 | #[cfg(all(unix, not(target_os = "android"), not(target_os = "macos")))] 43 | pub fn required_extension_names() -> Vec<*const i8> { 44 | vec![ 45 | Surface::name().as_ptr(), 46 | XlibSurface::name().as_ptr(), 47 | DebugUtils::name().as_ptr(), 48 | ] 49 | } 50 | // ------------------------------------------------------------------------ 51 | 52 | // create surface --------------------------------------------------------- 53 | #[cfg(all(unix, not(target_os = "android"), not(target_os = "macos")))] 54 | pub unsafe fn create_surface( 55 | entry: &Entry, 56 | instance: &Instance, 57 | window: &winit::window::Window, 58 | ) -> Result { 59 | use std::ptr; 60 | use winit::platform::unix::WindowExtUnix; 61 | 62 | let x11_display = window.xlib_display().unwrap(); 63 | let x11_window = window.xlib_window().unwrap(); 64 | let x11_create_info = vk::XlibSurfaceCreateInfoKHR { 65 | s_type: vk::StructureType::XLIB_SURFACE_CREATE_INFO_KHR, 66 | p_next: ptr::null(), 67 | flags: Default::default(), 68 | window: x11_window as vk::Window, 69 | dpy: x11_display as *mut vk::Display, 70 | }; 71 | let xlib_surface_loader = XlibSurface::new(entry, instance); 72 | xlib_surface_loader.create_xlib_surface(&x11_create_info, None) 73 | } 74 | 75 | #[cfg(target_os = "macos")] 76 | pub unsafe fn create_surface( 77 | entry: &E, 78 | instance: &I, 79 | window: &winit::window::Window, 80 | ) -> Result { 81 | use std::mem; 82 | use std::os::raw::c_void; 83 | use std::ptr; 84 | use winit::platform::macos::WindowExtMacOS; 85 | 86 | let wnd: cocoa_id = mem::transmute(window.ns_window()); 87 | 88 | let layer = CoreAnimationLayer::new(); 89 | 90 | layer.set_edge_antialiasing_mask(0); 91 | layer.set_presents_with_transaction(false); 92 | layer.remove_all_animations(); 93 | 94 | let view = wnd.contentView(); 95 | 96 | layer.set_contents_scale(view.backingScaleFactor()); 97 | view.setLayer(mem::transmute(layer.as_ref())); 98 | view.setWantsLayer(YES); 99 | 100 | let create_info = vk::MacOSSurfaceCreateInfoMVK { 101 | s_type: vk::StructureType::MACOS_SURFACE_CREATE_INFO_M, 102 | p_next: ptr::null(), 103 | flags: Default::default(), 104 | p_view: window.ns_view() as *const c_void, 105 | }; 106 | 107 | let macos_surface_loader = MacOSSurface::new(entry, instance); 108 | macos_surface_loader.create_mac_os_surface_mvk(&create_info, None) 109 | } 110 | 111 | #[cfg(target_os = "windows")] 112 | pub unsafe fn create_surface( 113 | entry: &E, 114 | instance: &I, 115 | window: &winit::window::Window, 116 | ) -> Result { 117 | use std::os::raw::c_void; 118 | use std::ptr; 119 | use winapi::shared::windef::HWND; 120 | use winapi::um::libloaderapi::GetModuleHandleW; 121 | use winit::platform::windows::WindowExtWindows; 122 | 123 | let hwnd = window.hwnd() as HWND; 124 | let hinstance = GetModuleHandleW(ptr::null()) as *const c_void; 125 | let win32_create_info = vk::Win32SurfaceCreateInfoKHR { 126 | s_type: vk::StructureType::WIN32_SURFACE_CREATE_INFO_KHR, 127 | p_next: ptr::null(), 128 | flags: Default::default(), 129 | hinstance, 130 | hwnd: hwnd as *const c_void, 131 | }; 132 | let win32_surface_loader = Win32Surface::new(entry, instance); 133 | win32_surface_loader.create_win32_surface(&win32_create_info, None) 134 | } 135 | // ------------------------------------------------------------------------ 136 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1681202837, 9 | "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "cfacdce06f30d2b68473a46042957675eebb3401", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "flake-utils_2": { 22 | "inputs": { 23 | "systems": "systems_2" 24 | }, 25 | "locked": { 26 | "lastModified": 1681202837, 27 | "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=", 28 | "owner": "numtide", 29 | "repo": "flake-utils", 30 | "rev": "cfacdce06f30d2b68473a46042957675eebb3401", 31 | "type": "github" 32 | }, 33 | "original": { 34 | "owner": "numtide", 35 | "repo": "flake-utils", 36 | "type": "github" 37 | } 38 | }, 39 | "nixpkgs": { 40 | "locked": { 41 | "lastModified": 1683286087, 42 | "narHash": "sha256-xseOd7W7xwF5GOF2RW8qhjmVGrKoBz+caBlreaNzoeI=", 43 | "owner": "nixos", 44 | "repo": "nixpkgs", 45 | "rev": "3e313808bd2e0a0669430787fb22e43b2f4bf8bf", 46 | "type": "github" 47 | }, 48 | "original": { 49 | "owner": "nixos", 50 | "ref": "nixos-unstable", 51 | "repo": "nixpkgs", 52 | "type": "github" 53 | } 54 | }, 55 | "nixpkgs_2": { 56 | "locked": { 57 | "lastModified": 1681358109, 58 | "narHash": "sha256-eKyxW4OohHQx9Urxi7TQlFBTDWII+F+x2hklDOQPB50=", 59 | "owner": "NixOS", 60 | "repo": "nixpkgs", 61 | "rev": "96ba1c52e54e74c3197f4d43026b3f3d92e83ff9", 62 | "type": "github" 63 | }, 64 | "original": { 65 | "owner": "NixOS", 66 | "ref": "nixpkgs-unstable", 67 | "repo": "nixpkgs", 68 | "type": "github" 69 | } 70 | }, 71 | "root": { 72 | "inputs": { 73 | "flake-utils": "flake-utils", 74 | "nixpkgs": "nixpkgs", 75 | "rust-overlay": "rust-overlay" 76 | } 77 | }, 78 | "rust-overlay": { 79 | "inputs": { 80 | "flake-utils": "flake-utils_2", 81 | "nixpkgs": "nixpkgs_2" 82 | }, 83 | "locked": { 84 | "lastModified": 1683253065, 85 | "narHash": "sha256-92vzWdV7VNGNQJeF+gtlcw18nj3ERU+i/fnSVzI7Lsw=", 86 | "owner": "oxalica", 87 | "repo": "rust-overlay", 88 | "rev": "59e9ddf0c5232136f71c458eea1303a418f7a117", 89 | "type": "github" 90 | }, 91 | "original": { 92 | "owner": "oxalica", 93 | "repo": "rust-overlay", 94 | "type": "github" 95 | } 96 | }, 97 | "systems": { 98 | "locked": { 99 | "lastModified": 1681028828, 100 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 101 | "owner": "nix-systems", 102 | "repo": "default", 103 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 104 | "type": "github" 105 | }, 106 | "original": { 107 | "owner": "nix-systems", 108 | "repo": "default", 109 | "type": "github" 110 | } 111 | }, 112 | "systems_2": { 113 | "locked": { 114 | "lastModified": 1681028828, 115 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 116 | "owner": "nix-systems", 117 | "repo": "default", 118 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 119 | "type": "github" 120 | }, 121 | "original": { 122 | "owner": "nix-systems", 123 | "repo": "default", 124 | "type": "github" 125 | } 126 | } 127 | }, 128 | "root": "root", 129 | "version": 7 130 | } 131 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Realtime Vulkan ray tracing"; 3 | 4 | inputs = { 5 | nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; 6 | rust-overlay.url = "github:oxalica/rust-overlay"; 7 | flake-utils.url = "github:numtide/flake-utils"; 8 | }; 9 | 10 | outputs = { self, nixpkgs, flake-utils, rust-overlay,... }: let 11 | lib = { 12 | inherit (flake-utils.lib) defaultSystems eachSystem; 13 | }; 14 | supportedSystems = [ "x86_64-linux" ]; 15 | in lib.eachSystem supportedSystems (system: let 16 | nightlyVersion = "2023-01-15"; 17 | 18 | #pkgs = mars-std.legacyPackages.${system}; 19 | pkgs = import nixpkgs { 20 | inherit system; 21 | overlays = [ 22 | (import rust-overlay) 23 | #(import ./pkgs) 24 | ]; 25 | }; 26 | pinnedRust = pkgs.rust-bin.nightly.${nightlyVersion}.default.override { 27 | extensions = ["rustc-dev" "rust-src" "rust-analyzer-preview" ]; 28 | targets = [ "x86_64-unknown-linux-gnu" ]; 29 | }; 30 | rustPlatform = pkgs.makeRustPlatform { 31 | rustc = pinnedRust; 32 | cargo = pinnedRust; 33 | }; 34 | cargoExpand = pkgs.cargo-expand.override { inherit rustPlatform; }; 35 | cargoFlame = pkgs.cargo-flamegraph.override { inherit rustPlatform; }; 36 | #cargoMSRV = pkgs.cargo-msrv.override { inherit rustPlatform; }; 37 | cargoBloat = pkgs.cargo-bloat.override { inherit rustPlatform; }; 38 | cargoLicense = pkgs.cargo-license.override { inherit rustPlatform; }; 39 | cargo-supply-chain = pkgs.cargo-supply-chain.override { inherit rustPlatform; }; 40 | #cargoREADME = pkgs.cargo-readme.override { inherit rustPlatform; }; 41 | #cargoFeature = pkgs.cargo-feature.override { inherit rustPlatform; }; 42 | #cargoGeiger = pkgs.cargo-geiger.override { inherit rustPlatform; }; 43 | in { 44 | 45 | devShell = pkgs.mkShell rec { 46 | hardeningDisable = [ 47 | "fortify" 48 | ]; 49 | nativeBuildInputs = [ 50 | pinnedRust 51 | cargoExpand 52 | cargoFlame 53 | cargoBloat 54 | cargoLicense 55 | cargo-supply-chain 56 | #cargoREADME 57 | #cargoFeature 58 | #cargoGeiger 59 | #cargoMSRV 60 | ]; 61 | buildInputs = with pkgs; [ 62 | # alsaLib 63 | # binaryen 64 | fontconfig 65 | # freetype 66 | # libxkbcommon 67 | pkg-config 68 | # spirv-tools 69 | #udev 70 | 71 | vulkan-loader 72 | vulkan-tools 73 | 74 | xorg.libX11 75 | xorg.libXcursor 76 | xorg.libXi 77 | xorg.libXrandr 78 | shaderc 79 | #renderdoc 80 | # gcc-unwrapped.lib 81 | ]; 82 | 83 | VULKAN_SDK = "${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d"; 84 | VK_LAYER_PATH="${pkgs.vulkan-validation-layers}/share/vulkan/explicit_layer.d"; 85 | 86 | shellHook = '' 87 | export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:${ pkgs.lib.makeLibraryPath buildInputs}" 88 | ''; 89 | }; 90 | 91 | }); 92 | } 93 | -------------------------------------------------------------------------------- /images/lucy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/images/lucy.png -------------------------------------------------------------------------------- /spv/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaminariOS/rustracer/0a6f950bd1506ca2ccf927e53e1cc5b458aa3bb5/spv/.gitkeep --------------------------------------------------------------------------------