├── .clang-format ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md ├── assets ├── CornellBox-Original.mtl └── CornellBox-Original.obj ├── main.cpp └── shaders ├── closesthit.rchit ├── closesthit.rchit.spv ├── common.glsl ├── compile.bat ├── miss.rmiss ├── miss.rmiss.spv ├── raygen.rgen └── raygen.rgen.spv /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Chromium 2 | IndentWidth: 4 3 | TabWidth: 4 4 | ColumnLimit: 150 5 | UseTab: Never 6 | AccessModifierOffset: -4 7 | SortIncludes: false 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .vscode 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/glfw"] 2 | path = external/glfw 3 | url = https://github.com/glfw/glfw.git 4 | [submodule "external/tinyobjloader"] 5 | path = external/tinyobjloader 6 | url = https://github.com/tinyobjloader/tinyobjloader.git 7 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0.0) 2 | project(single-file-vulkan-pathtracing VERSION 0.1.0) 3 | set(CMAKE_CXX_STANDARD 20) 4 | 5 | set(GLFW_BUILD_DOCS OFF CACHE BOOL "" FORCE) 6 | set(GLFW_BUILD_TESTS OFF CACHE BOOL "" FORCE) 7 | set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE) 8 | add_subdirectory(external/glfw) 9 | 10 | file(GLOB SHADERS shaders/*) 11 | add_executable(single-file-vulkan-pathtracing main.cpp ${SHADERS}) 12 | 13 | source_group("Shader Files" FILES ${SHADERS}) 14 | 15 | target_link_libraries(${PROJECT_NAME} PUBLIC glfw) 16 | target_include_directories(${PROJECT_NAME} PUBLIC 17 | "$ENV{VULKAN_SDK}/Include" 18 | "${PROJECT_SOURCE_DIR}/external/tinyobjloader" 19 | ) 20 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Yuki Nishidate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Single-File Vulkan Pathtracing 2 | 3 | ![image](https://user-images.githubusercontent.com/30839669/167279645-c56a70ac-8941-4a2b-ba1c-05a5d03c3d27.png) 4 | 5 | # Environment 6 | 7 | - C++20 8 | - Vulkan SDK 1.3.250.1 or later 9 | - GPU / Driver that support Vulkan Ray Tracing 10 | - NVIDIA Vulkan beta driver Windows 531.83, Linux 525.47.22 or later 11 | 12 | ## Setup 13 | 14 | ### Manual 15 | 16 | See [Vulkan Tutorial / Development environment](https://vulkan-tutorial.com/Development_environment) 17 | 18 | ### CMake 19 | 20 | ``` 21 | git clone --recursive https://github.com/yknishidate/single-file-vulkan-pathtracing 22 | cd single-file-vulkan-pathtracing 23 | cmake . -B build 24 | ``` 25 | 26 | # References 27 | 28 | - [NVIDIA Vulkan Ray Tracing Tutorial](https://nvpro-samples.github.io/vk_raytracing_tutorial_KHR/) 29 | - [Vulkan-Hpp](https://github.com/KhronosGroup/Vulkan-Hpp) 30 | - [Vulkan Tutorial](https://vulkan-tutorial.com/) 31 | - [SaschaWillems/Vulkan](https://github.com/SaschaWillems/Vulkan) 32 | - [vk_raytrace](https://github.com/nvpro-samples/vk_raytrace) 33 | -------------------------------------------------------------------------------- /assets/CornellBox-Original.mtl: -------------------------------------------------------------------------------- 1 | # The original Cornell Box in OBJ format. 2 | # Note that the real box is not a perfect cube, so 3 | # the faces are imperfect in this data set. 4 | # 5 | # Created by Guedis Cardenas and Morgan McGuire at Williams College, 2011 6 | # Released into the Public Domain. 7 | # 8 | # http://graphics.cs.williams.edu/data 9 | # http://www.graphics.cornell.edu/online/box/data.html 10 | # 11 | 12 | newmtl leftWall 13 | Ns 10.0000 14 | Ni 1.5000 15 | illum 2 16 | Ka 0.63 0.065 0.05 # Red 17 | Kd 0.63 0.065 0.05 18 | Ks 0 0 0 19 | Ke 0 0 0 20 | 21 | 22 | newmtl rightWall 23 | Ns 10.0000 24 | Ni 1.5000 25 | illum 2 26 | Ka 0.14 0.45 0.091 # Green 27 | Kd 0.14 0.45 0.091 28 | Ks 0 0 0 29 | Ke 0 0 0 30 | 31 | 32 | newmtl floor 33 | Ns 10.0000 34 | Ni 1.0000 35 | illum 2 36 | Ka 0.725 0.71 0.68 # White 37 | Kd 0.725 0.71 0.68 38 | Ks 0 0 0 39 | Ke 0 0 0 40 | 41 | 42 | newmtl ceiling 43 | Ns 10.0000 44 | Ni 1.0000 45 | illum 2 46 | Ka 0.725 0.71 0.68 # White 47 | Kd 0.725 0.71 0.68 48 | Ks 0 0 0 49 | Ke 0 0 0 50 | 51 | 52 | newmtl backWall 53 | Ns 10.0000 54 | Ni 1.0000 55 | illum 2 56 | Ka 0.725 0.71 0.68 # White 57 | Kd 0.725 0.71 0.68 58 | Ks 0 0 0 59 | Ke 0 0 0 60 | 61 | 62 | newmtl shortBox 63 | Ns 10.0000 64 | Ni 1.0000 65 | illum 2 66 | Ka 0.725 0.71 0.68 # White 67 | Kd 0.725 0.71 0.68 68 | Ks 0 0 0 69 | Ke 0 0 0 70 | 71 | 72 | newmtl tallBox 73 | Ns 10.0000 74 | Ni 1.0000 75 | illum 2 76 | Ka 0.725 0.71 0.68 # White 77 | Kd 0.725 0.71 0.68 78 | Ks 0 0 0 79 | Ke 0 0 0 80 | 81 | newmtl light 82 | Ns 10.0000 83 | Ni 1.0000 84 | illum 2 85 | Ka 0.78 0.78 0.78 # White 86 | Kd 0.78 0.78 0.78 87 | Ks 0 0 0 88 | Ke 17 12 4 89 | -------------------------------------------------------------------------------- /assets/CornellBox-Original.obj: -------------------------------------------------------------------------------- 1 | # The original Cornell Box in OBJ format. 2 | # Note that the real box is not a perfect cube, so 3 | # the faces are imperfect in this data set. 4 | # 5 | # Created by Guedis Cardenas and Morgan McGuire at Williams College, 2011 6 | # Released into the Public Domain. 7 | # 8 | # http://graphics.cs.williams.edu/data 9 | # http://www.graphics.cornell.edu/online/box/data.html 10 | # 11 | 12 | mtllib CornellBox-Original.mtl 13 | 14 | ## Object floor 15 | v -1.01 0.00 0.99 16 | v 1.00 0.00 0.99 17 | v 1.00 0.00 -1.04 18 | v -0.99 0.00 -1.04 19 | 20 | g floor 21 | usemtl floor 22 | f -4 -3 -2 -1 23 | 24 | ## Object ceiling 25 | v -1.02 1.99 0.99 26 | v -1.02 1.99 -1.04 27 | v 1.00 1.99 -1.04 28 | v 1.00 1.99 0.99 29 | 30 | g ceiling 31 | usemtl ceiling 32 | f -4 -3 -2 -1 33 | 34 | ## Object backwall 35 | v -0.99 0.00 -1.04 36 | v 1.00 0.00 -1.04 37 | v 1.00 1.99 -1.04 38 | v -1.02 1.99 -1.04 39 | 40 | g backWall 41 | usemtl backWall 42 | f -4 -3 -2 -1 43 | 44 | ## Object rightwall 45 | v 1.00 0.00 -1.04 46 | v 1.00 0.00 0.99 47 | v 1.00 1.99 0.99 48 | v 1.00 1.99 -1.04 49 | 50 | g rightWall 51 | usemtl rightWall 52 | f -4 -3 -2 -1 53 | 54 | ## Object leftWall 55 | v -1.01 0.00 0.99 56 | v -0.99 0.00 -1.04 57 | v -1.02 1.99 -1.04 58 | v -1.02 1.99 0.99 59 | 60 | g leftWall 61 | usemtl leftWall 62 | f -4 -3 -2 -1 63 | 64 | ## Object shortBox 65 | usemtl shortBox 66 | 67 | # Top Face 68 | v 0.53 0.60 0.75 69 | v 0.70 0.60 0.17 70 | v 0.13 0.60 0.00 71 | v -0.05 0.60 0.57 72 | f -4 -3 -2 -1 73 | 74 | # Left Face 75 | v -0.05 0.00 0.57 76 | v -0.05 0.60 0.57 77 | v 0.13 0.60 0.00 78 | v 0.13 0.00 0.00 79 | f -4 -3 -2 -1 80 | 81 | # Front Face 82 | v 0.53 0.00 0.75 83 | v 0.53 0.60 0.75 84 | v -0.05 0.60 0.57 85 | v -0.05 0.00 0.57 86 | f -4 -3 -2 -1 87 | 88 | # Right Face 89 | v 0.70 0.00 0.17 90 | v 0.70 0.60 0.17 91 | v 0.53 0.60 0.75 92 | v 0.53 0.00 0.75 93 | f -4 -3 -2 -1 94 | 95 | # Back Face 96 | v 0.13 0.00 0.00 97 | v 0.13 0.60 0.00 98 | v 0.70 0.60 0.17 99 | v 0.70 0.00 0.17 100 | f -4 -3 -2 -1 101 | 102 | # Bottom Face 103 | v 0.53 0.00 0.75 104 | v 0.70 0.00 0.17 105 | v 0.13 0.00 0.00 106 | v -0.05 0.00 0.57 107 | f -12 -11 -10 -9 108 | 109 | g shortBox 110 | usemtl shortBox 111 | 112 | ## Object tallBox 113 | usemtl tallBox 114 | 115 | # Top Face 116 | v -0.53 1.20 0.09 117 | v 0.04 1.20 -0.09 118 | v -0.14 1.20 -0.67 119 | v -0.71 1.20 -0.49 120 | f -4 -3 -2 -1 121 | 122 | # Left Face 123 | v -0.53 0.00 0.09 124 | v -0.53 1.20 0.09 125 | v -0.71 1.20 -0.49 126 | v -0.71 0.00 -0.49 127 | f -4 -3 -2 -1 128 | 129 | # Back Face 130 | v -0.71 0.00 -0.49 131 | v -0.71 1.20 -0.49 132 | v -0.14 1.20 -0.67 133 | v -0.14 0.00 -0.67 134 | f -4 -3 -2 -1 135 | 136 | # Right Face 137 | v -0.14 0.00 -0.67 138 | v -0.14 1.20 -0.67 139 | v 0.04 1.20 -0.09 140 | v 0.04 0.00 -0.09 141 | f -4 -3 -2 -1 142 | 143 | # Front Face 144 | v 0.04 0.00 -0.09 145 | v 0.04 1.20 -0.09 146 | v -0.53 1.20 0.09 147 | v -0.53 0.00 0.09 148 | f -4 -3 -2 -1 149 | 150 | # Bottom Face 151 | v -0.53 0.00 0.09 152 | v 0.04 0.00 -0.09 153 | v -0.14 0.00 -0.67 154 | v -0.71 0.00 -0.49 155 | f -8 -7 -6 -5 156 | 157 | g tallBox 158 | usemtl tallBox 159 | 160 | ## Object light 161 | v -0.24 1.98 0.16 162 | v -0.24 1.98 -0.22 163 | v 0.23 1.98 -0.22 164 | v 0.23 1.98 0.16 165 | 166 | g light 167 | usemtl light 168 | f -4 -3 -2 -1 -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1 8 | #include 9 | #include 10 | 11 | #define TINYOBJLOADER_IMPLEMENTATION 12 | #include 13 | 14 | VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE 15 | 16 | constexpr int WIDTH = 1024; 17 | constexpr int HEIGHT = 1024; 18 | 19 | struct Vertex { 20 | float position[3]; 21 | }; 22 | 23 | struct Face { 24 | float diffuse[3]; 25 | float emission[3]; 26 | }; 27 | 28 | void loadFromFile(std::vector& vertices, std::vector& indices, std::vector& faces) { 29 | tinyobj::attrib_t attrib; 30 | std::vector shapes; 31 | std::vector materials; 32 | std::string warn, err; 33 | 34 | if (!tinyobj::LoadObj(&attrib, &shapes, &materials, &warn, &err, "../assets/CornellBox-Original.obj", "../assets")) { 35 | throw std::runtime_error(warn + err); 36 | } 37 | 38 | for (const auto& shape : shapes) { 39 | for (const auto& index : shape.mesh.indices) { 40 | Vertex vertex{}; 41 | vertex.position[0] = attrib.vertices[3 * index.vertex_index + 0]; 42 | vertex.position[1] = -attrib.vertices[3 * index.vertex_index + 1]; 43 | vertex.position[2] = attrib.vertices[3 * index.vertex_index + 2]; 44 | vertices.push_back(vertex); 45 | indices.push_back(static_cast(indices.size())); 46 | } 47 | for (const auto& matIndex : shape.mesh.material_ids) { 48 | Face face; 49 | face.diffuse[0] = materials[matIndex].diffuse[0]; 50 | face.diffuse[1] = materials[matIndex].diffuse[1]; 51 | face.diffuse[2] = materials[matIndex].diffuse[2]; 52 | face.emission[0] = materials[matIndex].emission[0]; 53 | face.emission[1] = materials[matIndex].emission[1]; 54 | face.emission[2] = materials[matIndex].emission[2]; 55 | faces.push_back(face); 56 | } 57 | } 58 | } 59 | 60 | std::vector readFile(const std::string& filename) { 61 | std::ifstream file(filename, std::ios::ate | std::ios::binary); 62 | if (!file.is_open()) { 63 | throw std::runtime_error("failed to open file!"); 64 | } 65 | 66 | size_t fileSize = file.tellg(); 67 | std::vector buffer(fileSize); 68 | file.seekg(0); 69 | file.read(buffer.data(), fileSize); 70 | file.close(); 71 | return buffer; 72 | } 73 | 74 | struct Context { 75 | Context() { 76 | // Create window 77 | glfwInit(); 78 | glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); 79 | glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); 80 | window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan Pathtracing", nullptr, nullptr); 81 | 82 | // Prepase extensions and layers 83 | uint32_t glfwExtensionCount = 0; 84 | const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); 85 | std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); 86 | extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME); 87 | 88 | std::vector layers{"VK_LAYER_KHRONOS_validation"}; 89 | 90 | auto vkGetInstanceProcAddr = dl.getProcAddress("vkGetInstanceProcAddr"); 91 | VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); 92 | 93 | // Create instance 94 | vk::ApplicationInfo appInfo; 95 | appInfo.setApiVersion(VK_API_VERSION_1_3); 96 | 97 | vk::InstanceCreateInfo instanceInfo; 98 | instanceInfo.setPApplicationInfo(&appInfo); 99 | instanceInfo.setPEnabledLayerNames(layers); 100 | instanceInfo.setPEnabledExtensionNames(extensions); 101 | instance = vk::createInstanceUnique(instanceInfo); 102 | VULKAN_HPP_DEFAULT_DISPATCHER.init(*instance); 103 | 104 | // Pick first gpu 105 | physicalDevice = instance->enumeratePhysicalDevices().front(); 106 | 107 | // Create debug messenger 108 | vk::DebugUtilsMessengerCreateInfoEXT messengerInfo; 109 | messengerInfo.setMessageSeverity(vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); 110 | messengerInfo.setMessageType(vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); 111 | messengerInfo.setPfnUserCallback(&debugUtilsMessengerCallback); 112 | messenger = instance->createDebugUtilsMessengerEXTUnique(messengerInfo); 113 | 114 | // Create surface 115 | VkSurfaceKHR _surface; 116 | VkResult res = glfwCreateWindowSurface(VkInstance(*instance), window, nullptr, &_surface); 117 | if (res != VK_SUCCESS) { 118 | throw std::runtime_error("failed to create window surface!"); 119 | } 120 | surface = vk::UniqueSurfaceKHR(vk::SurfaceKHR(_surface), {*instance}); 121 | 122 | // Find queue family 123 | std::vector queueFamilies = physicalDevice.getQueueFamilyProperties(); 124 | for (int i = 0; i < queueFamilies.size(); i++) { 125 | auto supportCompute = queueFamilies[i].queueFlags & vk::QueueFlagBits::eCompute; 126 | auto supportPresent = physicalDevice.getSurfaceSupportKHR(i, *surface); 127 | if (supportCompute && supportPresent) { 128 | queueFamilyIndex = i; 129 | } 130 | } 131 | 132 | // Create device 133 | const float queuePriority = 1.0f; 134 | vk::DeviceQueueCreateInfo queueCreateInfo; 135 | queueCreateInfo.setQueueFamilyIndex(queueFamilyIndex); 136 | queueCreateInfo.setQueuePriorities(queuePriority); 137 | 138 | const std::vector deviceExtensions{ 139 | VK_KHR_SWAPCHAIN_EXTENSION_NAME, 140 | VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME, 141 | VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME, 142 | VK_KHR_MAINTENANCE3_EXTENSION_NAME, 143 | VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME, 144 | VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME, 145 | VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, 146 | VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME, 147 | VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, 148 | }; 149 | 150 | if (!checkDeviceExtensionSupport(deviceExtensions)) { 151 | throw std::runtime_error("Some required extensions are not supported"); 152 | } 153 | 154 | vk::DeviceCreateInfo deviceInfo; 155 | deviceInfo.setQueueCreateInfos(queueCreateInfo); 156 | deviceInfo.setPEnabledExtensionNames(deviceExtensions); 157 | 158 | vk::PhysicalDeviceBufferDeviceAddressFeatures bufferDeviceAddressFeatures{true}; 159 | vk::PhysicalDeviceRayTracingPipelineFeaturesKHR rayTracingPipelineFeatures{true}; 160 | vk::PhysicalDeviceAccelerationStructureFeaturesKHR accelerationStructureFeatures{true}; 161 | vk::StructureChain createInfoChain{ 162 | deviceInfo, 163 | bufferDeviceAddressFeatures, 164 | rayTracingPipelineFeatures, 165 | accelerationStructureFeatures, 166 | }; 167 | 168 | device = physicalDevice.createDeviceUnique(createInfoChain.get()); 169 | VULKAN_HPP_DEFAULT_DISPATCHER.init(*device); 170 | 171 | queue = device->getQueue(queueFamilyIndex, 0); 172 | 173 | // Create command pool 174 | vk::CommandPoolCreateInfo commandPoolInfo; 175 | commandPoolInfo.setFlags(vk::CommandPoolCreateFlagBits::eResetCommandBuffer); 176 | commandPoolInfo.setQueueFamilyIndex(queueFamilyIndex); 177 | commandPool = device->createCommandPoolUnique(commandPoolInfo); 178 | 179 | // Create descriptor pool 180 | std::vector poolSizes{ 181 | {vk::DescriptorType::eAccelerationStructureKHR, 1}, 182 | {vk::DescriptorType::eStorageImage, 1}, 183 | {vk::DescriptorType::eStorageBuffer, 3}, 184 | }; 185 | 186 | vk::DescriptorPoolCreateInfo descPoolInfo; 187 | descPoolInfo.setPoolSizes(poolSizes); 188 | descPoolInfo.setMaxSets(1); 189 | descPoolInfo.setFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet); 190 | descPool = device->createDescriptorPoolUnique(descPoolInfo); 191 | } 192 | 193 | bool checkDeviceExtensionSupport(const std::vector& requiredExtensions) const { 194 | std::vector availableExtensions = physicalDevice.enumerateDeviceExtensionProperties(); 195 | std::vector requiredExtensionNames(requiredExtensions.begin(), requiredExtensions.end()); 196 | 197 | for (const auto& extension : availableExtensions) { 198 | requiredExtensionNames.erase(std::remove(requiredExtensionNames.begin(), requiredExtensionNames.end(), extension.extensionName), 199 | requiredExtensionNames.end()); 200 | } 201 | 202 | if (requiredExtensionNames.empty()) { 203 | std::cout << "All required extensions are supported by the device." << std::endl; 204 | return true; 205 | } else { 206 | std::cout << "The following required extensions are not supported by the device:" << std::endl; 207 | for (const auto& name : requiredExtensionNames) { 208 | std::cout << "\t" << name << std::endl; 209 | } 210 | return false; 211 | } 212 | } 213 | 214 | uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) const { 215 | vk::PhysicalDeviceMemoryProperties memProperties = physicalDevice.getMemoryProperties(); 216 | for (uint32_t i = 0; i != memProperties.memoryTypeCount; ++i) { 217 | if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) { 218 | return i; 219 | } 220 | } 221 | throw std::runtime_error("failed to find suitable memory type"); 222 | } 223 | 224 | void oneTimeSubmit(const std::function& func) const { 225 | vk::CommandBufferAllocateInfo commandBufferInfo; 226 | commandBufferInfo.setCommandPool(*commandPool); 227 | commandBufferInfo.setCommandBufferCount(1); 228 | 229 | vk::UniqueCommandBuffer commandBuffer = std::move(device->allocateCommandBuffersUnique(commandBufferInfo).front()); 230 | commandBuffer->begin({vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); 231 | func(*commandBuffer); 232 | commandBuffer->end(); 233 | 234 | vk::SubmitInfo submitInfo; 235 | submitInfo.setCommandBuffers(*commandBuffer); 236 | queue.submit(submitInfo); 237 | queue.waitIdle(); 238 | } 239 | 240 | vk::UniqueDescriptorSet allocateDescSet(vk::DescriptorSetLayout descSetLayout) { 241 | vk::DescriptorSetAllocateInfo descSetInfo; 242 | descSetInfo.setDescriptorPool(*descPool); 243 | descSetInfo.setSetLayouts(descSetLayout); 244 | return std::move(device->allocateDescriptorSetsUnique(descSetInfo).front()); 245 | } 246 | 247 | static VKAPI_ATTR VkBool32 VKAPI_CALL debugUtilsMessengerCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, 248 | VkDebugUtilsMessageTypeFlagsEXT messageTypes, 249 | VkDebugUtilsMessengerCallbackDataEXT const* pCallbackData, 250 | void* pUserData) { 251 | std::cerr << pCallbackData->pMessage << std::endl; 252 | return VK_FALSE; 253 | } 254 | 255 | GLFWwindow* window; 256 | vk::DynamicLoader dl; 257 | vk::UniqueInstance instance; 258 | vk::UniqueDebugUtilsMessengerEXT messenger; 259 | vk::UniqueSurfaceKHR surface; 260 | vk::UniqueDevice device; 261 | vk::PhysicalDevice physicalDevice; 262 | uint32_t queueFamilyIndex; 263 | vk::Queue queue; 264 | vk::UniqueCommandPool commandPool; 265 | vk::UniqueDescriptorPool descPool; 266 | }; 267 | 268 | struct Buffer { 269 | enum class Type { 270 | Scratch, 271 | AccelInput, 272 | AccelStorage, 273 | ShaderBindingTable, 274 | }; 275 | 276 | Buffer() = default; 277 | Buffer(const Context& context, Type type, vk::DeviceSize size, const void* data = nullptr) { 278 | vk::BufferUsageFlags usage; 279 | vk::MemoryPropertyFlags memoryProps; 280 | using Usage = vk::BufferUsageFlagBits; 281 | using Memory = vk::MemoryPropertyFlagBits; 282 | if (type == Type::AccelInput) { 283 | usage = Usage::eAccelerationStructureBuildInputReadOnlyKHR | Usage::eStorageBuffer | Usage::eShaderDeviceAddress; 284 | memoryProps = Memory::eHostVisible | Memory::eHostCoherent; 285 | } else if (type == Type::Scratch) { 286 | usage = Usage::eStorageBuffer | Usage::eShaderDeviceAddress; 287 | memoryProps = Memory::eDeviceLocal; 288 | } else if (type == Type::AccelStorage) { 289 | usage = Usage::eAccelerationStructureStorageKHR | Usage::eShaderDeviceAddress; 290 | memoryProps = Memory::eDeviceLocal; 291 | } else if (type == Type::ShaderBindingTable) { 292 | usage = Usage::eShaderBindingTableKHR | Usage::eShaderDeviceAddress; 293 | memoryProps = Memory::eHostVisible | Memory::eHostCoherent; 294 | } 295 | 296 | buffer = context.device->createBufferUnique({{}, size, usage}); 297 | 298 | // Allocate memory 299 | vk::MemoryRequirements requirements = context.device->getBufferMemoryRequirements(*buffer); 300 | uint32_t memoryTypeIndex = context.findMemoryType(requirements.memoryTypeBits, memoryProps); 301 | 302 | vk::MemoryAllocateFlagsInfo flagsInfo{vk::MemoryAllocateFlagBits::eDeviceAddress}; 303 | 304 | vk::MemoryAllocateInfo memoryInfo; 305 | memoryInfo.setAllocationSize(requirements.size); 306 | memoryInfo.setMemoryTypeIndex(memoryTypeIndex); 307 | memoryInfo.setPNext(&flagsInfo); 308 | memory = context.device->allocateMemoryUnique(memoryInfo); 309 | 310 | context.device->bindBufferMemory(*buffer, *memory, 0); 311 | 312 | // Get device address 313 | vk::BufferDeviceAddressInfoKHR bufferDeviceAI{*buffer}; 314 | deviceAddress = context.device->getBufferAddressKHR(&bufferDeviceAI); 315 | 316 | descBufferInfo.setBuffer(*buffer); 317 | descBufferInfo.setOffset(0); 318 | descBufferInfo.setRange(size); 319 | 320 | if (data) { 321 | void* mapped = context.device->mapMemory(*memory, 0, size); 322 | memcpy(mapped, data, size); 323 | context.device->unmapMemory(*memory); 324 | } 325 | } 326 | 327 | vk::UniqueBuffer buffer; 328 | vk::UniqueDeviceMemory memory; 329 | vk::DescriptorBufferInfo descBufferInfo; 330 | uint64_t deviceAddress = 0; 331 | }; 332 | 333 | struct Image { 334 | Image() = default; 335 | Image(const Context& context, vk::Extent2D extent, vk::Format format, vk::ImageUsageFlags usage) { 336 | // Create image 337 | vk::ImageCreateInfo imageInfo; 338 | imageInfo.setImageType(vk::ImageType::e2D); 339 | imageInfo.setExtent({extent.width, extent.height, 1}); 340 | imageInfo.setMipLevels(1); 341 | imageInfo.setArrayLayers(1); 342 | imageInfo.setFormat(format); 343 | imageInfo.setUsage(usage); 344 | image = context.device->createImageUnique(imageInfo); 345 | 346 | // Allocate memory 347 | vk::MemoryRequirements requirements = context.device->getImageMemoryRequirements(*image); 348 | uint32_t memoryTypeIndex = context.findMemoryType(requirements.memoryTypeBits, vk::MemoryPropertyFlagBits::eDeviceLocal); 349 | vk::MemoryAllocateInfo memoryInfo; 350 | memoryInfo.setAllocationSize(requirements.size); 351 | memoryInfo.setMemoryTypeIndex(memoryTypeIndex); 352 | memory = context.device->allocateMemoryUnique(memoryInfo); 353 | 354 | // Bind memory and image 355 | context.device->bindImageMemory(*image, *memory, 0); 356 | 357 | // Create image view 358 | vk::ImageViewCreateInfo imageViewInfo; 359 | imageViewInfo.setImage(*image); 360 | imageViewInfo.setViewType(vk::ImageViewType::e2D); 361 | imageViewInfo.setFormat(format); 362 | imageViewInfo.setSubresourceRange({vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}); 363 | view = context.device->createImageViewUnique(imageViewInfo); 364 | 365 | // Set image info 366 | descImageInfo.setImageView(*view); 367 | descImageInfo.setImageLayout(vk::ImageLayout::eGeneral); 368 | context.oneTimeSubmit([&](vk::CommandBuffer commandBuffer) { // 369 | setImageLayout(commandBuffer, *image, vk::ImageLayout::eUndefined, vk::ImageLayout::eGeneral); 370 | }); 371 | } 372 | 373 | static vk::AccessFlags toAccessFlags(vk::ImageLayout layout) { 374 | switch (layout) { 375 | case vk::ImageLayout::eTransferSrcOptimal: 376 | return vk::AccessFlagBits::eTransferRead; 377 | case vk::ImageLayout::eTransferDstOptimal: 378 | return vk::AccessFlagBits::eTransferWrite; 379 | default: 380 | return {}; 381 | } 382 | } 383 | 384 | static void setImageLayout(vk::CommandBuffer commandBuffer, vk::Image image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { 385 | vk::ImageMemoryBarrier barrier; 386 | barrier.setDstQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED); 387 | barrier.setSrcQueueFamilyIndex(VK_QUEUE_FAMILY_IGNORED); 388 | barrier.setImage(image); 389 | barrier.setOldLayout(oldLayout); 390 | barrier.setNewLayout(newLayout); 391 | barrier.setSubresourceRange({vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}); 392 | barrier.setSrcAccessMask(toAccessFlags(oldLayout)); 393 | barrier.setDstAccessMask(toAccessFlags(newLayout)); 394 | commandBuffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, // 395 | vk::PipelineStageFlagBits::eAllCommands, // 396 | {}, {}, {}, barrier); 397 | } 398 | 399 | static void copyImage(vk::CommandBuffer commandBuffer, vk::Image srcImage, vk::Image dstImage) { 400 | vk::ImageCopy copyRegion; 401 | copyRegion.setSrcSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1}); 402 | copyRegion.setDstSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1}); 403 | copyRegion.setExtent({WIDTH, HEIGHT, 1}); 404 | commandBuffer.copyImage(srcImage, vk::ImageLayout::eTransferSrcOptimal, dstImage, vk::ImageLayout::eTransferDstOptimal, copyRegion); 405 | } 406 | 407 | vk::UniqueImage image; 408 | vk::UniqueImageView view; 409 | vk::UniqueDeviceMemory memory; 410 | vk::DescriptorImageInfo descImageInfo; 411 | }; 412 | 413 | struct Accel { 414 | Accel() = default; 415 | Accel(const Context& context, vk::AccelerationStructureGeometryKHR geometry, uint32_t primitiveCount, vk::AccelerationStructureTypeKHR type) { 416 | vk::AccelerationStructureBuildGeometryInfoKHR buildGeometryInfo; 417 | buildGeometryInfo.setType(type); 418 | buildGeometryInfo.setFlags(vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace); 419 | buildGeometryInfo.setGeometries(geometry); 420 | 421 | // Create buffer 422 | vk::AccelerationStructureBuildSizesInfoKHR buildSizesInfo = context.device->getAccelerationStructureBuildSizesKHR( // 423 | vk::AccelerationStructureBuildTypeKHR::eDevice, buildGeometryInfo, primitiveCount); 424 | vk::DeviceSize size = buildSizesInfo.accelerationStructureSize; 425 | buffer = Buffer{context, Buffer::Type::AccelStorage, size}; 426 | 427 | // Create accel 428 | vk::AccelerationStructureCreateInfoKHR accelInfo; 429 | accelInfo.setBuffer(*buffer.buffer); 430 | accelInfo.setSize(size); 431 | accelInfo.setType(type); 432 | accel = context.device->createAccelerationStructureKHRUnique(accelInfo); 433 | 434 | // Build 435 | Buffer scratchBuffer{context, Buffer::Type::Scratch, buildSizesInfo.buildScratchSize}; 436 | buildGeometryInfo.setScratchData(scratchBuffer.deviceAddress); 437 | buildGeometryInfo.setDstAccelerationStructure(*accel); 438 | 439 | context.oneTimeSubmit([&](vk::CommandBuffer commandBuffer) { // 440 | vk::AccelerationStructureBuildRangeInfoKHR buildRangeInfo; 441 | buildRangeInfo.setPrimitiveCount(primitiveCount); 442 | buildRangeInfo.setFirstVertex(0); 443 | buildRangeInfo.setPrimitiveOffset(0); 444 | buildRangeInfo.setTransformOffset(0); 445 | commandBuffer.buildAccelerationStructuresKHR(buildGeometryInfo, &buildRangeInfo); 446 | }); 447 | 448 | descAccelInfo.setAccelerationStructures(*accel); 449 | } 450 | 451 | Buffer buffer; 452 | vk::UniqueAccelerationStructureKHR accel; 453 | vk::WriteDescriptorSetAccelerationStructureKHR descAccelInfo; 454 | }; 455 | 456 | int main() { 457 | Context context; 458 | 459 | vk::SwapchainCreateInfoKHR swapchainInfo; 460 | swapchainInfo.setSurface(*context.surface); 461 | swapchainInfo.setMinImageCount(3); 462 | swapchainInfo.setImageFormat(vk::Format::eB8G8R8A8Unorm); 463 | swapchainInfo.setImageColorSpace(vk::ColorSpaceKHR::eSrgbNonlinear); 464 | swapchainInfo.setImageExtent({WIDTH, HEIGHT}); 465 | swapchainInfo.setImageArrayLayers(1); 466 | swapchainInfo.setImageUsage(vk::ImageUsageFlagBits::eTransferDst); 467 | swapchainInfo.setPreTransform(vk::SurfaceTransformFlagBitsKHR::eIdentity); 468 | swapchainInfo.setPresentMode(vk::PresentModeKHR::eFifo); 469 | swapchainInfo.setClipped(true); 470 | swapchainInfo.setQueueFamilyIndices(context.queueFamilyIndex); 471 | vk::UniqueSwapchainKHR swapchain = context.device->createSwapchainKHRUnique(swapchainInfo); 472 | 473 | std::vector swapchainImages = context.device->getSwapchainImagesKHR(*swapchain); 474 | 475 | vk::CommandBufferAllocateInfo commandBufferInfo; 476 | commandBufferInfo.setCommandPool(*context.commandPool); 477 | commandBufferInfo.setCommandBufferCount(static_cast(swapchainImages.size())); 478 | std::vector commandBuffers = context.device->allocateCommandBuffersUnique(commandBufferInfo); 479 | 480 | Image outputImage{context, 481 | {WIDTH, HEIGHT}, 482 | vk::Format::eB8G8R8A8Unorm, 483 | vk::ImageUsageFlagBits::eStorage | vk::ImageUsageFlagBits::eTransferSrc | vk::ImageUsageFlagBits::eTransferDst}; 484 | 485 | // Load mesh 486 | std::vector vertices; 487 | std::vector indices; 488 | std::vector faces; 489 | loadFromFile(vertices, indices, faces); 490 | 491 | Buffer vertexBuffer{context, Buffer::Type::AccelInput, sizeof(Vertex) * vertices.size(), vertices.data()}; 492 | Buffer indexBuffer{context, Buffer::Type::AccelInput, sizeof(uint32_t) * indices.size(), indices.data()}; 493 | Buffer faceBuffer{context, Buffer::Type::AccelInput, sizeof(Face) * faces.size(), faces.data()}; 494 | 495 | // Create bottom level accel struct 496 | vk::AccelerationStructureGeometryTrianglesDataKHR triangleData; 497 | triangleData.setVertexFormat(vk::Format::eR32G32B32Sfloat); 498 | triangleData.setVertexData(vertexBuffer.deviceAddress); 499 | triangleData.setVertexStride(sizeof(Vertex)); 500 | triangleData.setMaxVertex(static_cast(vertices.size())); 501 | triangleData.setIndexType(vk::IndexType::eUint32); 502 | triangleData.setIndexData(indexBuffer.deviceAddress); 503 | 504 | vk::AccelerationStructureGeometryKHR triangleGeometry; 505 | triangleGeometry.setGeometryType(vk::GeometryTypeKHR::eTriangles); 506 | triangleGeometry.setGeometry({triangleData}); 507 | triangleGeometry.setFlags(vk::GeometryFlagBitsKHR::eOpaque); 508 | 509 | const auto primitiveCount = static_cast(indices.size() / 3); 510 | 511 | Accel bottomAccel{context, triangleGeometry, primitiveCount, vk::AccelerationStructureTypeKHR::eBottomLevel}; 512 | 513 | // Create top level accel struct 514 | vk::TransformMatrixKHR transformMatrix = std::array{ 515 | std::array{1.0f, 0.0f, 0.0f, 0.0f}, 516 | std::array{0.0f, 1.0f, 0.0f, 0.0f}, 517 | std::array{0.0f, 0.0f, 1.0f, 0.0f}, 518 | }; 519 | 520 | vk::AccelerationStructureInstanceKHR accelInstance; 521 | accelInstance.setTransform(transformMatrix); 522 | accelInstance.setMask(0xFF); 523 | accelInstance.setAccelerationStructureReference(bottomAccel.buffer.deviceAddress); 524 | accelInstance.setFlags(vk::GeometryInstanceFlagBitsKHR::eTriangleFacingCullDisable); 525 | 526 | Buffer instancesBuffer{context, Buffer::Type::AccelInput, sizeof(vk::AccelerationStructureInstanceKHR), &accelInstance}; 527 | 528 | vk::AccelerationStructureGeometryInstancesDataKHR instancesData; 529 | instancesData.setArrayOfPointers(false); 530 | instancesData.setData(instancesBuffer.deviceAddress); 531 | 532 | vk::AccelerationStructureGeometryKHR instanceGeometry; 533 | instanceGeometry.setGeometryType(vk::GeometryTypeKHR::eInstances); 534 | instanceGeometry.setGeometry({instancesData}); 535 | instanceGeometry.setFlags(vk::GeometryFlagBitsKHR::eOpaque); 536 | 537 | Accel topAccel{context, instanceGeometry, 1, vk::AccelerationStructureTypeKHR::eTopLevel}; 538 | 539 | // Load shaders 540 | const std::vector raygenCode = readFile("../shaders/raygen.rgen.spv"); 541 | const std::vector missCode = readFile("../shaders/miss.rmiss.spv"); 542 | const std::vector chitCode = readFile("../shaders/closesthit.rchit.spv"); 543 | 544 | std::vector shaderModules(3); 545 | shaderModules[0] = context.device->createShaderModuleUnique({{}, raygenCode.size(), reinterpret_cast(raygenCode.data())}); 546 | shaderModules[1] = context.device->createShaderModuleUnique({{}, missCode.size(), reinterpret_cast(missCode.data())}); 547 | shaderModules[2] = context.device->createShaderModuleUnique({{}, chitCode.size(), reinterpret_cast(chitCode.data())}); 548 | 549 | std::vector shaderStages(3); 550 | shaderStages[0] = {{}, vk::ShaderStageFlagBits::eRaygenKHR, *shaderModules[0], "main"}; 551 | shaderStages[1] = {{}, vk::ShaderStageFlagBits::eMissKHR, *shaderModules[1], "main"}; 552 | shaderStages[2] = {{}, vk::ShaderStageFlagBits::eClosestHitKHR, *shaderModules[2], "main"}; 553 | 554 | std::vector shaderGroups(3); 555 | shaderGroups[0] = {vk::RayTracingShaderGroupTypeKHR::eGeneral, 0, VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR}; 556 | shaderGroups[1] = {vk::RayTracingShaderGroupTypeKHR::eGeneral, 1, VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR}; 557 | shaderGroups[2] = {vk::RayTracingShaderGroupTypeKHR::eTrianglesHitGroup, VK_SHADER_UNUSED_KHR, 2, VK_SHADER_UNUSED_KHR, VK_SHADER_UNUSED_KHR}; 558 | 559 | // create ray tracing pipeline 560 | std::vector bindings{ 561 | {0, vk::DescriptorType::eAccelerationStructureKHR, 1, vk::ShaderStageFlagBits::eRaygenKHR}, // Binding = 0 : TLAS 562 | {1, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eRaygenKHR}, // Binding = 1 : Storage image 563 | {2, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eClosestHitKHR}, // Binding = 2 : Vertices 564 | {3, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eClosestHitKHR}, // Binding = 3 : Indices 565 | {4, vk::DescriptorType::eStorageBuffer, 1, vk::ShaderStageFlagBits::eClosestHitKHR}, // Binding = 4 : Faces 566 | }; 567 | 568 | // Create desc set layout 569 | vk::DescriptorSetLayoutCreateInfo descSetLayoutInfo; 570 | descSetLayoutInfo.setBindings(bindings); 571 | vk::UniqueDescriptorSetLayout descSetLayout = context.device->createDescriptorSetLayoutUnique(descSetLayoutInfo); 572 | 573 | // Create pipeline layout 574 | vk::PushConstantRange pushRange; 575 | pushRange.setOffset(0); 576 | pushRange.setSize(sizeof(int)); 577 | pushRange.setStageFlags(vk::ShaderStageFlagBits::eRaygenKHR); 578 | 579 | vk::PipelineLayoutCreateInfo pipelineLayoutInfo; 580 | pipelineLayoutInfo.setSetLayouts(*descSetLayout); 581 | pipelineLayoutInfo.setPushConstantRanges(pushRange); 582 | vk::UniquePipelineLayout pipelineLayout = context.device->createPipelineLayoutUnique(pipelineLayoutInfo); 583 | 584 | // Create pipeline 585 | vk::RayTracingPipelineCreateInfoKHR rtPipelineInfo; 586 | rtPipelineInfo.setStages(shaderStages); 587 | rtPipelineInfo.setGroups(shaderGroups); 588 | rtPipelineInfo.setMaxPipelineRayRecursionDepth(4); 589 | rtPipelineInfo.setLayout(*pipelineLayout); 590 | 591 | auto result = context.device->createRayTracingPipelineKHRUnique(nullptr, nullptr, rtPipelineInfo); 592 | if (result.result != vk::Result::eSuccess) { 593 | throw std::runtime_error("failed to create ray tracing pipeline."); 594 | } 595 | 596 | vk::UniquePipeline pipeline = std::move(result.value); 597 | 598 | // Get ray tracing properties 599 | auto properties = context.physicalDevice.getProperties2(); 600 | auto rtProperties = properties.get(); 601 | 602 | // Calculate shader binding table (SBT) size 603 | uint32_t handleSize = rtProperties.shaderGroupHandleSize; 604 | uint32_t handleSizeAligned = rtProperties.shaderGroupHandleAlignment; 605 | uint32_t groupCount = static_cast(shaderGroups.size()); 606 | uint32_t sbtSize = groupCount * handleSizeAligned; 607 | 608 | // Get shader group handles 609 | std::vector handleStorage(sbtSize); 610 | if (context.device->getRayTracingShaderGroupHandlesKHR(*pipeline, 0, groupCount, sbtSize, handleStorage.data()) != vk::Result::eSuccess) { 611 | throw std::runtime_error("failed to get ray tracing shader group handles."); 612 | } 613 | 614 | // Create SBT 615 | Buffer raygenSBT{context, Buffer::Type::ShaderBindingTable, handleSize, handleStorage.data() + 0 * handleSizeAligned}; 616 | Buffer missSBT{context, Buffer::Type::ShaderBindingTable, handleSize, handleStorage.data() + 1 * handleSizeAligned}; 617 | Buffer hitSBT{context, Buffer::Type::ShaderBindingTable, handleSize, handleStorage.data() + 2 * handleSizeAligned}; 618 | 619 | uint32_t stride = rtProperties.shaderGroupHandleAlignment; 620 | uint32_t size = rtProperties.shaderGroupHandleAlignment; 621 | 622 | vk::StridedDeviceAddressRegionKHR raygenRegion{raygenSBT.deviceAddress, stride, size}; 623 | vk::StridedDeviceAddressRegionKHR missRegion{missSBT.deviceAddress, stride, size}; 624 | vk::StridedDeviceAddressRegionKHR hitRegion{hitSBT.deviceAddress, stride, size}; 625 | 626 | // Create desc set 627 | vk::UniqueDescriptorSet descSet = context.allocateDescSet(*descSetLayout); 628 | std::vector writes(bindings.size()); 629 | for (int i = 0; i < bindings.size(); i++) { 630 | writes[i].setDstSet(*descSet); 631 | writes[i].setDescriptorType(bindings[i].descriptorType); 632 | writes[i].setDescriptorCount(bindings[i].descriptorCount); 633 | writes[i].setDstBinding(bindings[i].binding); 634 | } 635 | writes[0].setPNext(&topAccel.descAccelInfo); 636 | writes[1].setImageInfo(outputImage.descImageInfo); 637 | writes[2].setBufferInfo(vertexBuffer.descBufferInfo); 638 | writes[3].setBufferInfo(indexBuffer.descBufferInfo); 639 | writes[4].setBufferInfo(faceBuffer.descBufferInfo); 640 | context.device->updateDescriptorSets(writes, nullptr); 641 | 642 | // Main loop 643 | uint32_t imageIndex = 0; 644 | int frame = 0; 645 | vk::UniqueSemaphore imageAcquiredSemaphore = context.device->createSemaphoreUnique(vk::SemaphoreCreateInfo()); 646 | while (!glfwWindowShouldClose(context.window)) { 647 | glfwPollEvents(); 648 | 649 | // Acquire next image 650 | imageIndex = context.device->acquireNextImageKHR(*swapchain, UINT64_MAX, *imageAcquiredSemaphore).value; 651 | 652 | // Record commands 653 | vk::CommandBuffer commandBuffer = *commandBuffers[imageIndex]; 654 | commandBuffer.begin(vk::CommandBufferBeginInfo()); 655 | commandBuffer.bindPipeline(vk::PipelineBindPoint::eRayTracingKHR, *pipeline); 656 | commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eRayTracingKHR, *pipelineLayout, 0, *descSet, nullptr); 657 | commandBuffer.pushConstants(*pipelineLayout, vk::ShaderStageFlagBits::eRaygenKHR, 0, sizeof(int), &frame); 658 | commandBuffer.traceRaysKHR(raygenRegion, missRegion, hitRegion, {}, WIDTH, HEIGHT, 1); 659 | 660 | vk::Image srcImage = *outputImage.image; 661 | vk::Image dstImage = swapchainImages[imageIndex]; 662 | Image::setImageLayout(commandBuffer, srcImage, vk::ImageLayout::eGeneral, vk::ImageLayout::eTransferSrcOptimal); 663 | Image::setImageLayout(commandBuffer, dstImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); 664 | Image::copyImage(commandBuffer, srcImage, dstImage); 665 | Image::setImageLayout(commandBuffer, srcImage, vk::ImageLayout::eTransferSrcOptimal, vk::ImageLayout::eGeneral); 666 | Image::setImageLayout(commandBuffer, dstImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::ePresentSrcKHR); 667 | 668 | commandBuffer.end(); 669 | 670 | // Submit 671 | context.queue.submit(vk::SubmitInfo().setCommandBuffers(commandBuffer)); 672 | 673 | // Present image 674 | vk::PresentInfoKHR presentInfo; 675 | presentInfo.setSwapchains(*swapchain); 676 | presentInfo.setImageIndices(imageIndex); 677 | presentInfo.setWaitSemaphores(*imageAcquiredSemaphore); 678 | auto result = context.queue.presentKHR(presentInfo); 679 | if (result != vk::Result::eSuccess) { 680 | throw std::runtime_error("failed to present."); 681 | } 682 | context.queue.waitIdle(); 683 | frame++; 684 | } 685 | 686 | context.device->waitIdle(); 687 | glfwDestroyWindow(context.window); 688 | glfwTerminate(); 689 | } 690 | -------------------------------------------------------------------------------- /shaders/closesthit.rchit: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_EXT_ray_tracing : enable 3 | #extension GL_GOOGLE_include_directive : enable 4 | #include "common.glsl" 5 | 6 | layout(binding = 2, set = 0) buffer Vertices{float vertices[];}; 7 | layout(binding = 3, set = 0) buffer Indices{uint indices[];}; 8 | layout(binding = 4, set = 0) buffer Faces{float faces[];}; 9 | 10 | layout(location = 0) rayPayloadInEXT HitPayload payload; 11 | hitAttributeEXT vec3 attribs; 12 | 13 | struct Vertex 14 | { 15 | vec3 position; 16 | }; 17 | 18 | struct Face 19 | { 20 | vec3 diffuse; 21 | vec3 emission; 22 | }; 23 | 24 | Vertex unpackVertex(uint index) 25 | { 26 | uint stride = 3; 27 | uint offset = index * stride; 28 | Vertex v; 29 | v.position = vec3(vertices[offset + 0], vertices[offset + 1], vertices[offset + 2]); 30 | return v; 31 | } 32 | 33 | Face unpackFace(uint index) 34 | { 35 | uint stride = 6; 36 | uint offset = index * stride; 37 | Face f; 38 | f.diffuse = vec3(faces[offset + 0], faces[offset + 1], faces[offset + 2]); 39 | f.emission = vec3(faces[offset + 3], faces[offset + 4], faces[offset + 5]); 40 | return f; 41 | } 42 | 43 | vec3 calcNormal(Vertex v0, Vertex v1, Vertex v2) 44 | { 45 | vec3 e01 = v1.position - v0.position; 46 | vec3 e02 = v2.position - v0.position; 47 | return -normalize(cross(e01, e02)); 48 | } 49 | 50 | void main() 51 | { 52 | const Vertex v0 = unpackVertex(indices[3 * gl_PrimitiveID + 0]); 53 | const Vertex v1 = unpackVertex(indices[3 * gl_PrimitiveID + 1]); 54 | const Vertex v2 = unpackVertex(indices[3 * gl_PrimitiveID + 2]); 55 | 56 | const vec3 barycentricCoords = vec3(1.0f - attribs.x - attribs.y, attribs.x, attribs.y); 57 | const vec3 position = v0.position * barycentricCoords.x + v1.position * barycentricCoords.y + v2.position * barycentricCoords.z; 58 | const vec3 normal = calcNormal(v0, v1, v2); 59 | 60 | const Face face = unpackFace(gl_PrimitiveID); 61 | payload.brdf = face.diffuse / M_PI; 62 | payload.emission = face.emission * 2.0; 63 | payload.position = position; 64 | payload.normal = normal; 65 | } 66 | -------------------------------------------------------------------------------- /shaders/closesthit.rchit.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yknishidate/single-file-vulkan-pathtracing/b9bf1e0c3273070e0696194bf728643a74a5caf0/shaders/closesthit.rchit.spv -------------------------------------------------------------------------------- /shaders/common.glsl: -------------------------------------------------------------------------------- 1 | 2 | struct HitPayload 3 | { 4 | vec3 position; 5 | vec3 normal; 6 | vec3 emission; 7 | vec3 brdf; 8 | bool done; 9 | }; 10 | 11 | const highp float M_PI = 3.14159265358979323846; 12 | 13 | uint pcg(inout uint state) 14 | { 15 | uint prev = state * 747796405u + 2891336453u; 16 | uint word = ((prev >> ((prev >> 28u) + 4u)) ^ prev) * 277803737u; 17 | state = prev; 18 | return (word >> 22u) ^ word; 19 | } 20 | 21 | uvec2 pcg2d(uvec2 v) 22 | { 23 | v = v * 1664525u + 1013904223u; 24 | v.x += v.y * 1664525u; 25 | v.y += v.x * 1664525u; 26 | v = v ^ (v >> 16u); 27 | v.x += v.y * 1664525u; 28 | v.y += v.x * 1664525u; 29 | v = v ^ (v >> 16u); 30 | return v; 31 | } 32 | 33 | float rand(inout uint seed) 34 | { 35 | uint val = pcg(seed); 36 | return (float(val) * (1.0 / float(0xffffffffu))); 37 | } 38 | -------------------------------------------------------------------------------- /shaders/compile.bat: -------------------------------------------------------------------------------- 1 | %VULKAN_SDK%/Bin/glslc.exe raygen.rgen -o raygen.rgen.spv --target-env=vulkan1.3 2 | %VULKAN_SDK%/Bin/glslc.exe closesthit.rchit -o closesthit.rchit.spv --target-env=vulkan1.3 3 | %VULKAN_SDK%/Bin/glslc.exe miss.rmiss -o miss.rmiss.spv --target-env=vulkan1.3 -------------------------------------------------------------------------------- /shaders/miss.rmiss: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_EXT_ray_tracing : enable 3 | #extension GL_GOOGLE_include_directive : enable 4 | #include "common.glsl" 5 | 6 | layout(location = 0) rayPayloadInEXT HitPayload payload; 7 | 8 | void main() 9 | { 10 | payload.emission = vec3(0.7, 0.6, 0.5); 11 | payload.done = true; 12 | } 13 | -------------------------------------------------------------------------------- /shaders/miss.rmiss.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yknishidate/single-file-vulkan-pathtracing/b9bf1e0c3273070e0696194bf728643a74a5caf0/shaders/miss.rmiss.spv -------------------------------------------------------------------------------- /shaders/raygen.rgen: -------------------------------------------------------------------------------- 1 | #version 460 2 | #extension GL_EXT_ray_tracing : enable 3 | #extension GL_GOOGLE_include_directive : enable 4 | #include "common.glsl" 5 | 6 | layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; 7 | layout(binding = 1, set = 0, rgba8) uniform image2D outputImage; 8 | layout(push_constant) uniform PushConstants { 9 | int frame; 10 | }; 11 | 12 | layout(location = 0) rayPayloadEXT HitPayload payload; 13 | 14 | void createCoordinateSystem(in vec3 N, out vec3 T, out vec3 B) 15 | { 16 | if(abs(N.x) > abs(N.y)) 17 | T = vec3(N.z, 0, -N.x) / sqrt(N.x * N.x + N.z * N.z); 18 | else 19 | T = vec3(0, -N.z, N.y) / sqrt(N.y * N.y + N.z * N.z); 20 | B = cross(N, T); 21 | } 22 | 23 | vec3 sampleHemisphere(float rand1, float rand2) 24 | { 25 | vec3 dir; 26 | dir.x = cos(2 * M_PI * rand2) * sqrt(1 - rand1 * rand1); 27 | dir.y = sin(2 * M_PI * rand2) * sqrt(1 - rand1 * rand1); 28 | dir.z = rand1; 29 | return dir; 30 | } 31 | 32 | vec3 sampleDirection(float rand1, float rand2, vec3 normal) 33 | { 34 | vec3 tangent; 35 | vec3 bitangent; 36 | createCoordinateSystem(normal, tangent, bitangent); 37 | vec3 dir = sampleHemisphere(rand1, rand2); 38 | return dir.x * tangent + dir.y * bitangent + dir.z * normal; 39 | } 40 | 41 | void main() 42 | { 43 | int maxSamples = 32; 44 | vec3 color = vec3(0.0); 45 | for(uint sampleNum = 0; sampleNum < maxSamples; sampleNum++){ 46 | // Calc seed 47 | uvec2 s = pcg2d(ivec2(gl_LaunchIDEXT.xy) * (sampleNum + maxSamples * frame + 1)); 48 | uint seed = s.x + s.y; 49 | 50 | // Calc ray 51 | const vec2 screenPos = vec2(gl_LaunchIDEXT.xy) + vec2(rand(seed), rand(seed)); 52 | const vec2 inUV = screenPos / vec2(gl_LaunchSizeEXT.xy); 53 | vec2 d = inUV * 2.0 - 1.0; 54 | 55 | vec4 origin = vec4(0, -1, 5, 1); 56 | vec4 target = vec4(d.x, d.y - 1, 2, 1) ; 57 | vec4 direction = vec4(normalize(target.xyz - origin.xyz), 0) ; 58 | 59 | vec3 weight = vec3(1.0); 60 | payload.done = false; 61 | 62 | for(uint depth = 0; depth < 8; depth++){ 63 | traceRayEXT( 64 | topLevelAS, 65 | gl_RayFlagsOpaqueEXT, 66 | 0xff, // cullMask 67 | 0, // sbtRecordOffset 68 | 0, // sbtRecordStride 69 | 0, // missIndex 70 | origin.xyz, 71 | 0.001, 72 | direction.xyz, 73 | 10000.0, 74 | 0 // payloadLocation 75 | ); 76 | color += weight * payload.emission; 77 | origin.xyz = payload.position; 78 | direction.xyz = sampleDirection(rand(seed), rand(seed), payload.normal); 79 | float pdf = 1.0 / (2.0 * M_PI); 80 | weight *= payload.brdf * dot(direction.xyz, payload.normal) / pdf; 81 | if(payload.done){ 82 | break; 83 | } 84 | } 85 | } 86 | color /= maxSamples; 87 | 88 | vec4 oldColor = imageLoad(outputImage, ivec2(gl_LaunchIDEXT.xy)); 89 | vec4 newColor = vec4(color, 1.0); 90 | newColor = (newColor + (oldColor * frame)) / (frame + 1); 91 | imageStore(outputImage, ivec2(gl_LaunchIDEXT.xy), newColor); 92 | } 93 | -------------------------------------------------------------------------------- /shaders/raygen.rgen.spv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yknishidate/single-file-vulkan-pathtracing/b9bf1e0c3273070e0696194bf728643a74a5caf0/shaders/raygen.rgen.spv --------------------------------------------------------------------------------