diff --git a/CMakeLists.txt b/CMakeLists.txt index e1c0494..1605665 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ add_subdirectory(ray_tracing_reflections) add_subdirectory(ray_tracing_ao) add_subdirectory(ray_tracing_indirect_scissor) add_subdirectory(ray_tracing_specialization) - +add_subdirectory(ray_tracing_advanced_compilation) #-------------------------------------------------------------------------------------------------- diff --git a/README.md b/README.md index 25a20f2..c4c1239 100644 --- a/README.md +++ b/README.md @@ -44,4 +44,5 @@ Tutorial | Details ![img](ray_tracing__advance/images/ray_tracing__advance.png) | [Advance](ray_tracing__advance)
An example combining most of the above samples in a single application. ![img](docs/Images/indirect_scissor/intro.png) | [Trace Rays Indirect](ray_tracing_indirect_scissor)
Teaches the use of `vkCmdTraceRaysIndirectKHR`, which sources width/height/depth from a buffer. As a use case, we add lanterns to the scene and use a compute shader to calculate scissor rectangles for each of them. ![img](ray_tracing_ao/images/ray_tracing_ao.png) | [AO Raytracing](ray_tracing_ao)
This extension to the tutorial is showing how G-Buffers from the fragment shader, can be used in a compute shader to cast ambient occlusion rays using ray queries ([GLSL_EXT_ray_query](https://github.com/KhronosGroup/GLSL/blob/master/extensions/ext/GLSL_EXT_ray_query.txt)). -![img](ray_tracing_specialization/images/specialization.png) | [Specialization Constants](ray_tracing_specialization)
Showing how to use specialization constant and using interactively different specialization. \ No newline at end of file +![img](ray_tracing_specialization/images/specialization.png) | [Specialization Constants](ray_tracing_specialization)
Showing how to use specialization constant and using interactively different specialization. +![img](ray_tracing_advanced_compilation/images/high_level_advanced_compilation.png) | [Advanced Compilation](ray_tracing_advanced_compilation)
Shows how to create reusable pipeline libraries and compile pipelines on multiple threads. \ No newline at end of file diff --git a/ray_tracing_advanced_compilation/CMakeLists.txt b/ray_tracing_advanced_compilation/CMakeLists.txt new file mode 100644 index 0000000..8587db8 --- /dev/null +++ b/ray_tracing_advanced_compilation/CMakeLists.txt @@ -0,0 +1,78 @@ +#***************************************************************************** +# Copyright 2020 NVIDIA Corporation. All rights reserved. +#***************************************************************************** + +cmake_minimum_required(VERSION 3.9.6 FATAL_ERROR) + +#-------------------------------------------------------------------------------------------------- +# Project setting +get_filename_component(PROJNAME ${CMAKE_CURRENT_SOURCE_DIR} NAME) +set(PROJNAME vk_${PROJNAME}_KHR) +project(${PROJNAME} LANGUAGES C CXX) +message(STATUS "-------------------------------") +message(STATUS "Processing Project ${PROJNAME}:") + + +#-------------------------------------------------------------------------------------------------- +# C++ target and defines +set(CMAKE_CXX_STANDARD 17) +add_executable(${PROJNAME}) +_add_project_definitions(${PROJNAME}) + + +#-------------------------------------------------------------------------------------------------- +# Source files for this project +# +file(GLOB SOURCE_FILES *.cpp *.hpp *.inl *.h *.c) +file(GLOB EXTRA_COMMON ${TUTO_KHR_DIR}/common/*.*) +list(APPEND COMMON_SOURCE_FILES ${EXTRA_COMMON}) +include_directories(${TUTO_KHR_DIR}/common) + + +#-------------------------------------------------------------------------------------------------- +# GLSL to SPIR-V custom build +compile_glsl_directory( + SRC "${CMAKE_CURRENT_SOURCE_DIR}/shaders" + DST "${CMAKE_CURRENT_SOURCE_DIR}/spv" + VULKAN_TARGET "vulkan1.2" + ) + + +#-------------------------------------------------------------------------------------------------- +# Sources +target_sources(${PROJNAME} PUBLIC ${SOURCE_FILES} ${HEADER_FILES}) +target_sources(${PROJNAME} PUBLIC ${COMMON_SOURCE_FILES}) +target_sources(${PROJNAME} PUBLIC ${PACKAGE_SOURCE_FILES}) +target_sources(${PROJNAME} PUBLIC ${GLSL_SOURCES} ${GLSL_HEADERS}) + + +#-------------------------------------------------------------------------------------------------- +# Sub-folders in Visual Studio +# +source_group("Common" FILES ${COMMON_SOURCE_FILES} ${PACKAGE_SOURCE_FILES}) +source_group("Sources" FILES ${SOURCE_FILES}) +source_group("Headers" FILES ${HEADER_FILES}) +source_group("Shader_Files" FILES ${GLSL_SOURCES} ${GLSL_HEADERS}) + + +#-------------------------------------------------------------------------------------------------- +# Linkage +# +target_link_libraries(${PROJNAME} ${PLATFORM_LIBRARIES} nvpro_core) + +foreach(DEBUGLIB ${LIBRARIES_DEBUG}) + target_link_libraries(${PROJNAME} debug ${DEBUGLIB}) +endforeach(DEBUGLIB) + +foreach(RELEASELIB ${LIBRARIES_OPTIMIZED}) + target_link_libraries(${PROJNAME} optimized ${RELEASELIB}) +endforeach(RELEASELIB) + +#-------------------------------------------------------------------------------------------------- +# copies binaries that need to be put next to the exe files (ZLib, etc.) +# +_finalize_target( ${PROJNAME} ) + + +install(FILES ${SPV_OUTPUT} CONFIGURATIONS Release DESTINATION "bin_${ARCH}/${PROJNAME}/spv") +install(FILES ${SPV_OUTPUT} CONFIGURATIONS Debug DESTINATION "bin_${ARCH}_debug/${PROJNAME}/spv") diff --git a/ray_tracing_advanced_compilation/README.md b/ray_tracing_advanced_compilation/README.md new file mode 100644 index 0000000..3ae5b1d --- /dev/null +++ b/ray_tracing_advanced_compilation/README.md @@ -0,0 +1,248 @@ +# Advanced Compilation + +![](images/advanced_compilation.png) + +The simplest way of defining ray tracing pipelines is by using monolithic `VkRayTracingPipelineCreateInfoKHR` structures specifying the pipeline stages and shader groups. Even if pipelines share some stages, they are compiled separately. Furthermore, by default `vkCreateRayTracingPipelinesKHR` blocks the current thread until completion: +![](images/high_level_regular_compilation.png) + + However, when using multiple pipelines, some of them may have some shader stages in common. Large pipelines may also take a long time to compile on a single thread. + +This sample introduces the [VK_KHR_pipeline_library](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_pipeline_library.html) extension to create shader libraries that can be separately compiled and reused in ray tracing pipelines. The compilation of the final pipeline is carried out on multiple threads using the [VK_KHR_deferred_host_operations](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_deferred_host_operations.html) extension. + +The code below is based on the [`ray_tracing_specialization`](../ray_tracing_specialization) sample, which introduces compile-time variations of a hit shader. + + +## Pipeline Library + + +Using monolithic pipeline definitions all stages are compiled for each pipeline, regardless of potential reuse. The shader groups are then referenced in the Shader Binding Table using their indices: +![](images/regular_pipeline.png) + +Pipeline libraries are `VkPipeline` objects that cannot be bound directly. Instead, they can be compiled once and linked into as many pipelines as necessary. The Shader Binding Table of the resulting pipeline references the shader groups of the library as if they had been appended to the groups and stages in the main `VkRayTracingPipelineCreateInfo` +![](images/library.png) + + We start by adding a new member in the `HelloVulkan` class: +~~~~ C + // Ray tracing shader library + VkPipeline m_rtShaderLibrary; +~~~~ + +In `HelloVulkan::createRtPipeline()` the `StageIndices` enumeration describes the indices of the stages defined in the pipeline creation structure. The hit groups will be moved to our library, hence we remove them from the enumeration: +~~~~ C + enum StageIndices + { + eRaygen, + eMiss, + eMiss2, + eShaderGroupCount + }; +~~~~ + +The shader modules will be referenced partly in the main pipeline, and partly in the pipeline library. To ensure proper deletion of the modules after use, we will store their handles in +~~~~ C + // Store the created modules for later cleanup + std::vector modules; +~~~~ +Then, after each call to `nvvk::createShaderModule` we store the resulting module: +~~~~ C + modules.push_back(stage.module); +~~~~ + +The specialization constants sample creates one shader module per specialization. Instead, we load that module once and reuse it for each specialization. Those specializations are then stored in the stages of the pipeline library: +~~~~ C + // Hit Group - Closest Hit + // Create many variation of the closest hit + stage.module = nvvk::createShaderModule( + m_device, nvh::loadFile("spv/raytrace.rchit.spv", true, defaultSearchPaths, true)); + + modules.push_back(stage.module); + // Store the hit groups for compilation in a separate pipeline library object + std::vector libraryStages{}; + for(uint32_t s = 0; s < (uint32_t)specializations.size(); s++) + { + stage.stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; + stage.pSpecializationInfo = specializations[s].getSpecialization(); + libraryStages.push_back(stage); + } +~~~~ + +Similarly, the hit groups will be stored in the library by replacing the storage of the hit groups in `m_rtShaderGroups` by: +~~~~ C + // Shader groups for the pipeline library containing the closest hit shaders + std::vector libraryShaderGroups; + + VkRayTracingShaderGroupCreateInfoKHR libraryGroup{VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR}; + libraryGroup.anyHitShader = VK_SHADER_UNUSED_KHR; + libraryGroup.closestHitShader = VK_SHADER_UNUSED_KHR; + libraryGroup.generalShader = VK_SHADER_UNUSED_KHR; + libraryGroup.intersectionShader = VK_SHADER_UNUSED_KHR; + libraryGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR; + + // Hit Group - Closest Hit + AnyHit + // Creating many Hit groups, one for each specialization + for(uint32_t s = 0; s < (uint32_t)specializations.size(); s++) + { + // The indices of the stages are local to the pipeline library + libraryGroup.closestHitShader = s; // Using variation of the closest hit + libraryShaderGroups.push_back(libraryGroup); + } +~~~~ +It is important to note that the stage indices are local to the pipeline library, regardless of where they will be used in the final pipeline. Those indices will be later offset depending on the contents of the pipeline. + +Once the groups and stages are defined we can create the pipeline library. After the creation of the ray tracing pipeline layout, we define the base of the library creation information: +~~~~ C + // Creation of the pipeline library object + VkRayTracingPipelineCreateInfoKHR pipelineLibraryInfo{VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR}; + // Flag the object as a pipeline library, which is a specific object that cannot be used directly. + pipelineLibraryInfo.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR; + // Use the same layout as the target pipeline + pipelineLibraryInfo.layout = m_rtPipelineLayout; + // As for the interface the maximum recursion depth must also be consistent across the pipeline + pipelineLibraryInfo.maxPipelineRayRecursionDepth = 2; +~~~~ +Pipeline libraries are technically independent from the pipeline they will be linked into. However, linking can only be achieved by enforcing strong consistency constraints, such as having the same pipeline layout and maximum recursion depth. If the recursion depth differs the compilation of the final pipeline will fail. + +In addition, the pipeline libraries need to have the same pipeline interface. This interface defines the maximum amount of data passed across stages: +~~~~ C + // Pipeline libraries need to define an interface, defined by the maximum hit attribute size (typically 2 for + // the built-in triangle intersector) and the maximum payload size (3 floating-point values in this sample). + // Pipeline libraries can be linked into a final pipeline only if their interface matches + VkRayTracingPipelineInterfaceCreateInfoKHR pipelineInterface{VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_INTERFACE_CREATE_INFO_KHR}; + pipelineInterface.maxPipelineRayHitAttributeSize = sizeof(nvmath::vec2f); + pipelineInterface.maxPipelineRayPayloadSize = sizeof(nvmath::vec3f); + pipelineLibraryInfo.pLibraryInterface = &pipelineInterface; +~~~~ + +Finally we provide the stage and shader groups information to the library creation information, and create the pipeline library in the same way as any other pipeline: +~~~~ C + // Shader groups and stages for the library + pipelineLibraryInfo.groupCount = static_cast(libraryShaderGroups.size()); + pipelineLibraryInfo.pGroups = libraryShaderGroups.data(); + pipelineLibraryInfo.stageCount = static_cast(libraryStages.size()); + pipelineLibraryInfo.pStages = libraryStages.data(); + + // Creation of the pipeline library + vkCreateRayTracingPipelinesKHR(m_device, {}, {}, 1, &pipelineLibraryInfo, nullptr, &m_rtShaderLibrary); +~~~~ +The pipeline library is now created, but the application cannot run yet: we still need to indicate that the final pipeline will link with our library. Before calling `vkCreateRayTracingPipelinesKHR` for the final pipeline, we insert the reference to the library: +~~~~ C + // The library will be linked into the final pipeline by specifying its handle and shared interface + VkPipelineLibraryCreateInfoKHR inputLibrary{VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR}; + inputLibrary.libraryCount = 1; + inputLibrary.pLibraries = &m_rtShaderLibrary; + rayPipelineInfo.pLibraryInfo = &inputLibrary; + rayPipelineInfo.pLibraryInterface = &pipelineInterface; +~~~~ + +The pipeline is now built from the specified shader groups and stages as well as the library containing the hit groups. The groups and stages are linked together using indices, and indices are local to each library. To avoid collisions the pipeline creation will consider the stages of the libraries as if they had been appended to the stage list of the original `VkRayTracingPipelineCreateInfoKHR`, in the order in which the libraries are defined in `pLibraries`. + +Therefore, the Shader Binding Table needs to be updated accordingly, by making the wrapper aware of the contents of the library: +~~~~ C + // The Shader Binding Table is built accounting for the entire pipeline, including the + // stages contained in the library. Passing the library information allows the wrapper + // to shift the shader group indices accordingly + m_sbtWrapper.create(m_rtPipeline, rayPipelineInfo, {pipelineLibraryInfo}); +~~~~ + +At the end of the function we destroy the shader modules using our vector of modules instead of iterating over the stages of the main pipeline: +~~~~ C + // Destroy all the created modules, for both libraries and main pipeline + for(auto& m : modules) + vkDestroyShaderModule(m_device, m, nullptr); +~~~~ + +The pipeline library has the same lifetime as the pipeline that uses it. The final step of this Section is the destruction of the library in the `HelloVulkan::destroy()` method: +~~~~ C + // Pipeline libraries have the same lifetime as the pipelines that uses them + vkDestroyPipeline(m_device, m_rtShaderLibrary, nullptr); +~~~~ + +As an exercise, it is possible to create another library containing the other shader stages, and link those libraries together into the pipeline. + +## Parallel Compilation Using Deferred Host Operations + +By default pipelines compilations are blocking the calling thread until completion: + ![Pipeline compilation on a single thread](images/single_threaded_compilation.png) + +Ray tracing pipelines are often complex, and can benefit from multithreaded compilation. This can drastically reduce the compilation times of complex pipelines. The deferred host operations extension allows the work involved in `vkCreateRayTracingPipelinesKHR` to be split into multiple threads provided by the application: + ![Deferred Host Operations use app-provided threads to parallelize the compilation](images/deferred_host_operations.png) + +We start by including the support of C++ threading using `std::async` at the beginning of the source file: +~~~~ C +#include +~~~~ + +In this sample we will distribute the compilation of the final ray tracing pipeline using a `VkDeferredOperation`, created just before calling `vkCreateRayTracingPipelinesKHR`: +~~~~ C + // Deferred operations allow the driver to parallelize the pipeline compilation on several threads + // Create a deferred operation + VkDeferredOperationKHR deferredOperation; + VkResult result = vkCreateDeferredOperationKHR(m_device, nullptr, &deferredOperation); + assert(result == VK_SUCCESS); +~~~~ + +Then we modify the pipeline creation to indicate we defer the operation: +~~~~ C + // The pipeline creation is called with the deferred operation. Instead of blocking until + // the compilation is done, the call returns immediately + vkCreateRayTracingPipelinesKHR(m_device, deferredOperation, {}, 1, &rayPipelineInfo, nullptr, &m_rtPipeline); +~~~~ +Instead of immediately launching the compilation and blocking execution until completion, this call will return immediately with value `VK_OPERATION_DEFERRED_KHR` if deferred operations are supported by the system. + +Threading control is left to the application. Therefore, our application will allocate a number of threads for compilation: +~~~~ C + // The compilation will be split into a maximum of 8 threads, or the maximum supported by the + // driver for that operation + uint32_t maxThreads{8}; + uint32_t threadCount = std::min(vkGetDeferredOperationMaxConcurrencyKHR(m_device, deferredOperation), maxThreads); +~~~~ + +We then launch those threads using `std::async`: +~~~~ C + std::vector> joins; + for(uint32_t i = 0; i < threadCount; i++) + { + VkDevice device{m_device}; + joins.emplace_back(std::async(std::launch::async, [device, deferredOperation]() { + // Wait until the thread has finished its work + VkResult result = vkDeferredOperationJoinKHR(device, deferredOperation); + // A return value of SUCCESS means the pipeline compilation is done. + // THREAD_DONE indicates that thread has no work to do for this task + // (e.g. the operation could not be split into that many threads) + // THREAD_IDLE indicates the thread has finished its task, but the overall pipeline + // compilation is not finished. + // In the last two cases, more work could be performed by the thread, such as waiting + // for another deferred operation + assert(result == VK_SUCCESS || result == VK_THREAD_DONE_KHR || result == VK_THREAD_IDLE_KHR); + })); + } +~~~~ +Each thread executes a blocking function taking care of a subset of the compilation. When a thread has finished its task, the pipeline compilation may be complete (`VK_SUCCESS`) or there may be no more work for this thread. In this case one could consider executing more work using those threads, such as compiling another pipeline. + +Since there is only one pipeline to compile, we wait for all threads to finish and check whether the pipeline compilation succeeded: +~~~~ C + // Wait for all threads to finish + for(auto& f : joins) + { + f.get(); + } + // Once the deferred operation is complete, check for compilation success + result = vkGetDeferredOperationResultKHR(m_device, deferredOperation); + assert(result == VK_SUCCESS); +~~~~ + +Once the compilation is finished we can destroy the deferred operation: +~~~~ C + // Destroy the deferred operation + vkDestroyDeferredOperationKHR(m_device, deferredOperation, nullptr); +~~~~ + +Congratulations! The ray tracing pipeline is now built using explicit stages and a pipeline library, and the final compilation is executed on multiple threads. As an exercise, the pipeline library described at the beginning of this tutorial can also be compiled in parallel. +This approach can be extended to compile multiple pipelines sharing some components using multiple threads: +![](images/high_level_advanced_compilation.png) + +## References + + * [VK_KHR_pipeline_library](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_pipeline_library.html) + * [VK_KHR_deferred_host_operations](https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VK_KHR_deferred_host_operations.html) + diff --git a/ray_tracing_advanced_compilation/hello_vulkan.cpp b/ray_tracing_advanced_compilation/hello_vulkan.cpp new file mode 100644 index 0000000..2e6f12b --- /dev/null +++ b/ray_tracing_advanced_compilation/hello_vulkan.cpp @@ -0,0 +1,1051 @@ +/* + * Copyright (c) 2014-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +#include +#include + + +#define STB_IMAGE_IMPLEMENTATION +#include "obj_loader.h" +#include "stb_image.h" + +#include "hello_vulkan.h" +#include "nvh/alignment.hpp" +#include "nvh/cameramanipulator.hpp" +#include "nvh/fileoperations.hpp" +#include "nvvk/commands_vk.hpp" +#include "nvvk/descriptorsets_vk.hpp" +#include "nvvk/images_vk.hpp" +#include "nvvk/pipeline_vk.hpp" +#include "nvvk/renderpasses_vk.hpp" +#include "nvvk/shaders_vk.hpp" +// Support for C++ multithreading +#include + +extern std::vector defaultSearchPaths; + + +// Holding the camera matrices +struct CameraMatrices +{ + nvmath::mat4f view; + nvmath::mat4f proj; + nvmath::mat4f viewInverse; + // #VKRay + nvmath::mat4f projInverse; +}; + + +//-------------------------------------------------------------------------------------------------- +// Keep the handle on the device +// Initialize the tool to do all our allocations: buffers, images +// +void HelloVulkan::setup(const VkInstance& instance, const VkDevice& device, const VkPhysicalDevice& physicalDevice, uint32_t queueFamily) +{ + AppBaseVk::setup(instance, device, physicalDevice, queueFamily); + m_alloc.init(instance, device, physicalDevice); + m_debug.setup(m_device); + m_offscreenDepthFormat = nvvk::findDepthFormat(physicalDevice); +} + +//-------------------------------------------------------------------------------------------------- +// Called at each frame to update the camera matrix +// +void HelloVulkan::updateUniformBuffer(const VkCommandBuffer& cmdBuf) +{ + // Prepare new UBO contents on host. + const float aspectRatio = m_size.width / static_cast(m_size.height); + CameraMatrices hostUBO = {}; + hostUBO.view = CameraManip.getMatrix(); + hostUBO.proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); + // hostUBO.proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). + hostUBO.viewInverse = nvmath::invert(hostUBO.view); + // #VKRay + hostUBO.projInverse = nvmath::invert(hostUBO.proj); + + // UBO on the device, and what stages access it. + VkBuffer deviceUBO = m_cameraMat.buffer; + auto uboUsageStages = VK_PIPELINE_STAGE_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR; + + // Ensure that the modified UBO is not visible to previous frames. + VkBufferMemoryBarrier beforeBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; + beforeBarrier.srcAccessMask = VK_ACCESS_SHADER_READ_BIT; + beforeBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + beforeBarrier.buffer = deviceUBO; + beforeBarrier.offset = 0; + beforeBarrier.size = sizeof(hostUBO); + vkCmdPipelineBarrier(cmdBuf, uboUsageStages, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_DEPENDENCY_DEVICE_GROUP_BIT, 0, + nullptr, 1, &beforeBarrier, 0, nullptr); + + + // Schedule the host-to-device upload. (hostUBO is copied into the cmd + // buffer so it is okay to deallocate when the function returns). + vkCmdUpdateBuffer(cmdBuf, m_cameraMat.buffer, 0, sizeof(CameraMatrices), &hostUBO); + + // Making sure the updated UBO will be visible. + VkBufferMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; + afterBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + afterBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + afterBarrier.buffer = deviceUBO; + afterBarrier.offset = 0; + afterBarrier.size = sizeof(hostUBO); + vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_TRANSFER_BIT, uboUsageStages, VK_DEPENDENCY_DEVICE_GROUP_BIT, 0, + nullptr, 1, &afterBarrier, 0, nullptr); +} + +//-------------------------------------------------------------------------------------------------- +// Describing the layout pushed when rendering +// +void HelloVulkan::createDescriptorSetLayout() +{ + auto nbTxt = static_cast(m_textures.size()); + auto nbObj = static_cast(m_objModel.size()); + + // Camera matrices (binding = 0) + m_descSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); + // Materials (binding = 1) + m_descSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, nbObj, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + // Scene description (binding = 2) + m_descSetLayoutBind.addBinding(2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, + VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + // Textures (binding = 3) + m_descSetLayoutBind.addBinding(3, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, + VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + // Materials (binding = 4) + m_descSetLayoutBind.addBinding(4, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, nbObj, + VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + // Storing vertices (binding = 5) + m_descSetLayoutBind.addBinding(5, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, nbObj, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + // Storing indices (binding = 6) + m_descSetLayoutBind.addBinding(6, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, nbObj, VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); + + + m_descSetLayout = m_descSetLayoutBind.createLayout(m_device); + m_descPool = m_descSetLayoutBind.createPool(m_device, 1); + m_descSet = nvvk::allocateDescriptorSet(m_device, m_descPool, m_descSetLayout); +} + +//-------------------------------------------------------------------------------------------------- +// Setting up the buffers in the descriptor set +// +void HelloVulkan::updateDescriptorSet() +{ + std::vector writes; + + // Camera matrices and scene description + VkDescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif)); + VkDescriptorBufferInfo dbiSceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE}; + writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 2, &dbiSceneDesc)); + + // All material buffers, 1 buffer per OBJ + std::vector dbiMat; + std::vector dbiMatIdx; + std::vector dbiVert; + std::vector dbiIdx; + for(auto& m : m_objModel) + { + dbiMat.push_back({m.matColorBuffer.buffer, 0, VK_WHOLE_SIZE}); + dbiMatIdx.push_back({m.matIndexBuffer.buffer, 0, VK_WHOLE_SIZE}); + dbiVert.push_back({m.vertexBuffer.buffer, 0, VK_WHOLE_SIZE}); + dbiIdx.push_back({m.indexBuffer.buffer, 0, VK_WHOLE_SIZE}); + } + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 1, dbiMat.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 4, dbiMatIdx.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 5, dbiVert.data())); + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 6, dbiIdx.data())); + + // All texture samplers + std::vector diit; + for(auto& texture : m_textures) + { + diit.emplace_back(texture.descriptor); + } + writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, 3, diit.data())); + + // Writing the information + vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); +} + + +//-------------------------------------------------------------------------------------------------- +// Creating the pipeline layout +// +void HelloVulkan::createGraphicsPipeline() +{ + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(ObjPushConstant)}; + + // Creating the Pipeline Layout + VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + createInfo.setLayoutCount = 1; + createInfo.pSetLayouts = &m_descSetLayout; + createInfo.pushConstantRangeCount = 1; + createInfo.pPushConstantRanges = &pushConstantRanges; + vkCreatePipelineLayout(m_device, &createInfo, nullptr, &m_pipelineLayout); + + + // Creating the Pipeline + std::vector paths = defaultSearchPaths; + nvvk::GraphicsPipelineGeneratorCombined gpb(m_device, m_pipelineLayout, m_offscreenRenderPass); + gpb.depthStencilState.depthTestEnable = true; + gpb.addShader(nvh::loadFile("spv/vert_shader.vert.spv", true, paths, true), VK_SHADER_STAGE_VERTEX_BIT); + gpb.addShader(nvh::loadFile("spv/frag_shader.frag.spv", true, paths, true), VK_SHADER_STAGE_FRAGMENT_BIT); + gpb.addBindingDescription({0, sizeof(VertexObj)}); + gpb.addAttributeDescriptions({ + {0, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast(offsetof(VertexObj, pos))}, + {1, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast(offsetof(VertexObj, nrm))}, + {2, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast(offsetof(VertexObj, color))}, + {3, 0, VK_FORMAT_R32G32_SFLOAT, static_cast(offsetof(VertexObj, texCoord))}, + }); + + m_graphicsPipeline = gpb.createPipeline(); + m_debug.setObjectName(m_graphicsPipeline, "Graphics"); +} + +//-------------------------------------------------------------------------------------------------- +// Loading the OBJ file and setting up all buffers +// +void HelloVulkan::loadModel(const std::string& filename, nvmath::mat4f transform) +{ + LOGI("Loading File: %s \n", filename.c_str()); + ObjLoader loader; + loader.loadModel(filename); + + // Converting from Srgb to linear + for(auto& m : loader.m_materials) + { + m.ambient = nvmath::pow(m.ambient, 2.2f); + m.diffuse = nvmath::pow(m.diffuse, 2.2f); + m.specular = nvmath::pow(m.specular, 2.2f); + } + + ObjInstance instance; + instance.objIndex = static_cast(m_objModel.size()); + instance.transform = transform; + instance.transformIT = nvmath::transpose(nvmath::invert(transform)); + instance.txtOffset = static_cast(m_textures.size()); + + ObjModel model; + model.nbIndices = static_cast(loader.m_indices.size()); + model.nbVertices = static_cast(loader.m_vertices.size()); + + // Create the buffers on Device and copy vertices, indices and materials + nvvk::CommandPool cmdBufGet(m_device, m_graphicsQueueIndex); + VkCommandBuffer cmdBuf = cmdBufGet.createCommandBuffer(); + VkBufferUsageFlags rtUsage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT + | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR; + model.vertexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_vertices, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | rtUsage); + model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rtUsage); + model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + // Creates all textures found + createTextureImages(cmdBuf, loader.m_textures); + cmdBufGet.submitAndWait(cmdBuf); + m_alloc.finalizeAndReleaseStaging(); + + std::string objNb = std::to_string(instance.objIndex); + m_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb).c_str())); + m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb).c_str())); + m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb).c_str())); + m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb).c_str())); + + m_objModel.emplace_back(model); + m_objInstance.emplace_back(instance); +} + + +//-------------------------------------------------------------------------------------------------- +// Creating the uniform buffer holding the camera matrices +// - Buffer is host visible +// +void HelloVulkan::createUniformBuffer() +{ + m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + m_debug.setObjectName(m_cameraMat.buffer, "cameraMat"); +} + +//-------------------------------------------------------------------------------------------------- +// Create a storage buffer containing the description of the scene elements +// - Which geometry is used by which instance +// - Transformation +// - Offset for texture +// +void HelloVulkan::createSceneDescriptionBuffer() +{ + nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); + + auto cmdBuf = cmdGen.createCommandBuffer(); + m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); + cmdGen.submitAndWait(cmdBuf); + m_alloc.finalizeAndReleaseStaging(); + m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc"); +} + +//-------------------------------------------------------------------------------------------------- +// Creating all textures and samplers +// +void HelloVulkan::createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures) +{ + VkSamplerCreateInfo samplerCreateInfo{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + samplerCreateInfo.minFilter = VK_FILTER_LINEAR; + samplerCreateInfo.magFilter = VK_FILTER_LINEAR; + samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; + samplerCreateInfo.maxLod = FLT_MAX; + + VkFormat format = VK_FORMAT_R8G8B8A8_SRGB; + + // If no textures are present, create a dummy one to accommodate the pipeline layout + if(textures.empty() && m_textures.empty()) + { + nvvk::Texture texture; + + std::array color{255u, 255u, 255u, 255u}; + VkDeviceSize bufferSize = sizeof(color); + auto imgSize = VkExtent2D{1, 1}; + auto imageCreateInfo = nvvk::makeImage2DCreateInfo(imgSize, format); + + // Creating the dummy texture + nvvk::Image image = m_alloc.createImage(cmdBuf, bufferSize, color.data(), imageCreateInfo); + VkImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, imageCreateInfo); + texture = m_alloc.createTexture(image, ivInfo, samplerCreateInfo); + + // The image format must be in VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL + nvvk::cmdBarrierImageLayout(cmdBuf, texture.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + m_textures.push_back(texture); + } + else + { + // Uploading all images + for(const auto& texture : textures) + { + std::stringstream o; + int texWidth, texHeight, texChannels; + o << "media/textures/" << texture; + std::string txtFile = nvh::findFile(o.str(), defaultSearchPaths, true); + + stbi_uc* stbi_pixels = stbi_load(txtFile.c_str(), &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); + + std::array color{255u, 0u, 255u, 255u}; + + stbi_uc* pixels = stbi_pixels; + // Handle failure + if(!stbi_pixels) + { + texWidth = texHeight = 1; + texChannels = 4; + pixels = reinterpret_cast(color.data()); + } + + VkDeviceSize bufferSize = static_cast(texWidth) * texHeight * sizeof(uint8_t) * 4; + auto imgSize = VkExtent2D{(uint32_t)texWidth, (uint32_t)texHeight}; + auto imageCreateInfo = nvvk::makeImage2DCreateInfo(imgSize, format, VK_IMAGE_USAGE_SAMPLED_BIT, true); + + { + nvvk::Image image = m_alloc.createImage(cmdBuf, bufferSize, pixels, imageCreateInfo); + nvvk::cmdGenerateMipmaps(cmdBuf, image.image, format, imgSize, imageCreateInfo.mipLevels); + VkImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, imageCreateInfo); + nvvk::Texture texture = m_alloc.createTexture(image, ivInfo, samplerCreateInfo); + + m_textures.push_back(texture); + } + + stbi_image_free(stbi_pixels); + } + } +} + +//-------------------------------------------------------------------------------------------------- +// Destroying all allocations +// +void HelloVulkan::destroyResources() +{ + vkDestroyPipeline(m_device, m_graphicsPipeline, nullptr); + vkDestroyPipelineLayout(m_device, m_pipelineLayout, nullptr); + vkDestroyDescriptorPool(m_device, m_descPool, nullptr); + vkDestroyDescriptorSetLayout(m_device, m_descSetLayout, nullptr); + + m_alloc.destroy(m_cameraMat); + m_alloc.destroy(m_sceneDesc); + + for(auto& m : m_objModel) + { + m_alloc.destroy(m.vertexBuffer); + m_alloc.destroy(m.indexBuffer); + m_alloc.destroy(m.matColorBuffer); + m_alloc.destroy(m.matIndexBuffer); + } + + for(auto& t : m_textures) + { + m_alloc.destroy(t); + } + + //#Post + m_alloc.destroy(m_offscreenColor); + m_alloc.destroy(m_offscreenDepth); + vkDestroyPipeline(m_device, m_postPipeline, nullptr); + vkDestroyPipelineLayout(m_device, m_postPipelineLayout, nullptr); + vkDestroyDescriptorPool(m_device, m_postDescPool, nullptr); + vkDestroyDescriptorSetLayout(m_device, m_postDescSetLayout, nullptr); + vkDestroyRenderPass(m_device, m_offscreenRenderPass, nullptr); + vkDestroyFramebuffer(m_device, m_offscreenFramebuffer, nullptr); + + + // #VKRay + m_sbtWrapper.destroy(); + m_rtBuilder.destroy(); + vkDestroyPipeline(m_device, m_rtPipeline, nullptr); + vkDestroyPipelineLayout(m_device, m_rtPipelineLayout, nullptr); + vkDestroyDescriptorPool(m_device, m_rtDescPool, nullptr); + vkDestroyDescriptorSetLayout(m_device, m_rtDescSetLayout, nullptr); + // Pipeline libraries have the same lifetime as the pipelines that uses them + vkDestroyPipeline(m_device, m_rtShaderLibrary, nullptr); + + m_alloc.deinit(); +} + +//-------------------------------------------------------------------------------------------------- +// Drawing the scene in raster mode +// +void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf) +{ + VkDeviceSize offset{0}; + + m_debug.beginLabel(cmdBuf, "Rasterize"); + + // Dynamic Viewport + setViewport(cmdBuf); + + // Drawing all triangles + vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_graphicsPipeline); + vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &m_descSet, 0, nullptr); + + + for(int i = 0; i < m_objInstance.size(); ++i) + { + auto& inst = m_objInstance[i]; + auto& model = m_objModel[inst.objIndex]; + m_pushConstant.instanceId = i; // Telling which instance is drawn + + vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, + sizeof(ObjPushConstant), &m_pushConstant); + vkCmdBindVertexBuffers(cmdBuf, 0, 1, &model.vertexBuffer.buffer, &offset); + vkCmdBindIndexBuffer(cmdBuf, model.indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32); + vkCmdDrawIndexed(cmdBuf, model.nbIndices, 1, 0, 0, 0); + } + m_debug.endLabel(cmdBuf); +} + +//-------------------------------------------------------------------------------------------------- +// Handling resize of the window +// +void HelloVulkan::onResize(int /*w*/, int /*h*/) +{ + createOffscreenRender(); + updatePostDescriptorSet(); + updateRtDescriptorSet(); +} + + +////////////////////////////////////////////////////////////////////////// +// Post-processing +////////////////////////////////////////////////////////////////////////// + + +//-------------------------------------------------------------------------------------------------- +// Creating an offscreen frame buffer and the associated render pass +// +void HelloVulkan::createOffscreenRender() +{ + m_alloc.destroy(m_offscreenColor); + m_alloc.destroy(m_offscreenDepth); + + // Creating the color image + { + auto colorCreateInfo = nvvk::makeImage2DCreateInfo(m_size, m_offscreenColorFormat, + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT + | VK_IMAGE_USAGE_STORAGE_BIT); + + + nvvk::Image image = m_alloc.createImage(colorCreateInfo); + VkImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, colorCreateInfo); + VkSamplerCreateInfo sampler{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO}; + m_offscreenColor = m_alloc.createTexture(image, ivInfo, sampler); + m_offscreenColor.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL; + } + + // Creating the depth buffer + auto depthCreateInfo = nvvk::makeImage2DCreateInfo(m_size, m_offscreenDepthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT); + { + nvvk::Image image = m_alloc.createImage(depthCreateInfo); + + + VkImageViewCreateInfo depthStencilView{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; + depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D; + depthStencilView.format = m_offscreenDepthFormat; + depthStencilView.subresourceRange = {VK_IMAGE_ASPECT_DEPTH_BIT, 0, 1, 0, 1}; + depthStencilView.image = image.image; + + m_offscreenDepth = m_alloc.createTexture(image, depthStencilView); + } + + // Setting the image layout for both color and depth + { + nvvk::CommandPool genCmdBuf(m_device, m_graphicsQueueIndex); + auto cmdBuf = genCmdBuf.createCommandBuffer(); + nvvk::cmdBarrierImageLayout(cmdBuf, m_offscreenColor.image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL); + nvvk::cmdBarrierImageLayout(cmdBuf, m_offscreenDepth.image, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_IMAGE_ASPECT_DEPTH_BIT); + + genCmdBuf.submitAndWait(cmdBuf); + } + + // Creating a renderpass for the offscreen + if(!m_offscreenRenderPass) + { + m_offscreenRenderPass = nvvk::createRenderPass(m_device, {m_offscreenColorFormat}, m_offscreenDepthFormat, 1, true, + true, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL); + } + + + // Creating the frame buffer for offscreen + std::vector attachments = {m_offscreenColor.descriptor.imageView, m_offscreenDepth.descriptor.imageView}; + + vkDestroyFramebuffer(m_device, m_offscreenFramebuffer, nullptr); + VkFramebufferCreateInfo info{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO}; + info.renderPass = m_offscreenRenderPass; + info.attachmentCount = 2; + info.pAttachments = attachments.data(); + info.width = m_size.width; + info.height = m_size.height; + info.layers = 1; + vkCreateFramebuffer(m_device, &info, nullptr, &m_offscreenFramebuffer); +} + +//-------------------------------------------------------------------------------------------------- +// The pipeline is how things are rendered, which shaders, type of primitives, depth test and more +// +void HelloVulkan::createPostPipeline() +{ + // Push constants in the fragment shader + VkPushConstantRange pushConstantRanges = {VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(float)}; + + // Creating the pipeline layout + VkPipelineLayoutCreateInfo createInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + createInfo.setLayoutCount = 1; + createInfo.pSetLayouts = &m_postDescSetLayout; + createInfo.pushConstantRangeCount = 1; + createInfo.pPushConstantRanges = &pushConstantRanges; + vkCreatePipelineLayout(m_device, &createInfo, nullptr, &m_postPipelineLayout); + + + // Pipeline: completely generic, no vertices + nvvk::GraphicsPipelineGeneratorCombined pipelineGenerator(m_device, m_postPipelineLayout, m_renderPass); + pipelineGenerator.addShader(nvh::loadFile("spv/passthrough.vert.spv", true, defaultSearchPaths, true), VK_SHADER_STAGE_VERTEX_BIT); + pipelineGenerator.addShader(nvh::loadFile("spv/post.frag.spv", true, defaultSearchPaths, true), VK_SHADER_STAGE_FRAGMENT_BIT); + pipelineGenerator.rasterizationState.cullMode = VK_CULL_MODE_NONE; + m_postPipeline = pipelineGenerator.createPipeline(); + m_debug.setObjectName(m_postPipeline, "post"); +} + +//-------------------------------------------------------------------------------------------------- +// The descriptor layout is the description of the data that is passed to the vertex or the +// fragment program. +// +void HelloVulkan::createPostDescriptor() +{ + m_postDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT); + m_postDescSetLayout = m_postDescSetLayoutBind.createLayout(m_device); + m_postDescPool = m_postDescSetLayoutBind.createPool(m_device); + m_postDescSet = nvvk::allocateDescriptorSet(m_device, m_postDescPool, m_postDescSetLayout); +} + + +//-------------------------------------------------------------------------------------------------- +// Update the output +// +void HelloVulkan::updatePostDescriptorSet() +{ + VkWriteDescriptorSet writeDescriptorSets = m_postDescSetLayoutBind.makeWrite(m_postDescSet, 0, &m_offscreenColor.descriptor); + vkUpdateDescriptorSets(m_device, 1, &writeDescriptorSets, 0, nullptr); +} + +//-------------------------------------------------------------------------------------------------- +// Draw a full screen quad with the attached image +// +void HelloVulkan::drawPost(VkCommandBuffer cmdBuf) +{ + m_debug.beginLabel(cmdBuf, "Post"); + + setViewport(cmdBuf); + + auto aspectRatio = static_cast(m_size.width) / static_cast(m_size.height); + vkCmdPushConstants(cmdBuf, m_postPipelineLayout, VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(float), &aspectRatio); + vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_postPipeline); + vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_postPipelineLayout, 0, 1, &m_postDescSet, 0, nullptr); + vkCmdDraw(cmdBuf, 3, 1, 0, 0); + + m_debug.endLabel(cmdBuf); +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// + +//-------------------------------------------------------------------------------------------------- +// Initialize Vulkan ray tracing +// #VKRay +void HelloVulkan::initRayTracing() +{ + // Requesting ray tracing properties + VkPhysicalDeviceProperties2 prop2{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2}; + prop2.pNext = &m_rtProperties; + vkGetPhysicalDeviceProperties2(m_physicalDevice, &prop2); + + m_rtBuilder.setup(m_device, &m_alloc, m_graphicsQueueIndex); + m_sbtWrapper.setup(m_device, m_graphicsQueueIndex, &m_alloc, m_rtProperties); +} + +//-------------------------------------------------------------------------------------------------- +// Convert an OBJ model into the ray tracing geometry used to build the BLAS +// +auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model) +{ + // BLAS builder requires raw device addresses. + VkBufferDeviceAddressInfo info{VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO}; + info.buffer = model.vertexBuffer.buffer; + VkDeviceAddress vertexAddress = vkGetBufferDeviceAddress(m_device, &info); + info.buffer = model.indexBuffer.buffer; + VkDeviceAddress indexAddress = vkGetBufferDeviceAddress(m_device, &info); + + uint32_t maxPrimitiveCount = model.nbIndices / 3; + + // Describe buffer as array of VertexObj. + VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; + triangles.vertexFormat = VK_FORMAT_R32G32B32A32_SFLOAT; // vec3 vertex position data. + triangles.vertexData.deviceAddress = vertexAddress; + triangles.vertexStride = sizeof(VertexObj); + // Describe index data (32-bit unsigned int) + triangles.indexType = VK_INDEX_TYPE_UINT32; + triangles.indexData.deviceAddress = indexAddress; + // Indicate identity transform by setting transformData to null device pointer. + //triangles.transformData = {}; + triangles.maxVertex = model.nbVertices; + + // Identify the above data as containing opaque triangles. + VkAccelerationStructureGeometryKHR asGeom{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_KHR}; + asGeom.geometryType = VK_GEOMETRY_TYPE_TRIANGLES_KHR; + asGeom.flags = VK_GEOMETRY_OPAQUE_BIT_KHR; + asGeom.geometry.triangles = triangles; + + // The entire array will be used to build the BLAS. + VkAccelerationStructureBuildRangeInfoKHR offset; + offset.firstVertex = 0; + offset.primitiveCount = maxPrimitiveCount; + offset.primitiveOffset = 0; + offset.transformOffset = 0; + + // Our blas is made from only one geometry, but could be made of many geometries + nvvk::RaytracingBuilderKHR::BlasInput input; + input.asGeometry.emplace_back(asGeom); + input.asBuildOffsetInfo.emplace_back(offset); + + return input; +} + +//-------------------------------------------------------------------------------------------------- +// +// +void HelloVulkan::createBottomLevelAS() +{ + // BLAS - Storing each primitive in a geometry + std::vector allBlas; + allBlas.reserve(m_objModel.size()); + for(const auto& obj : m_objModel) + { + auto blas = objectToVkGeometryKHR(obj); + + // We could add more geometry in each BLAS, but we add only one for now + allBlas.emplace_back(blas); + } + m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); +} + +void HelloVulkan::createTopLevelAS() +{ + std::vector tlas; + tlas.reserve(m_objInstance.size()); + for(uint32_t i = 0; i < static_cast(m_objInstance.size()); i++) + { + nvvk::RaytracingBuilderKHR::Instance rayInst; + rayInst.transform = m_objInstance[i].transform; // Position of the instance + rayInst.instanceCustomId = i; // gl_InstanceCustomIndexEXT + rayInst.blasId = m_objInstance[i].objIndex; + rayInst.hitGroupId = 0; // We will use the same hit group for all objects + rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; + tlas.emplace_back(rayInst); + } + m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); +} + +//-------------------------------------------------------------------------------------------------- +// This descriptor set holds the Acceleration structure and the output image +// +void HelloVulkan::createRtDescriptorSet() +{ + // Top-level acceleration structure, usable by both the ray generation and the closest hit (to + // shoot shadow rays) + m_rtDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, + VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS + m_rtDescSetLayoutBind.addBinding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, + VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image + + m_rtDescPool = m_rtDescSetLayoutBind.createPool(m_device); + m_rtDescSetLayout = m_rtDescSetLayoutBind.createLayout(m_device); + + VkDescriptorSetAllocateInfo allocateInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + allocateInfo.descriptorPool = m_rtDescPool; + allocateInfo.descriptorSetCount = 1; + allocateInfo.pSetLayouts = &m_rtDescSetLayout; + vkAllocateDescriptorSets(m_device, &allocateInfo, &m_rtDescSet); + + + VkAccelerationStructureKHR tlas = m_rtBuilder.getAccelerationStructure(); + VkWriteDescriptorSetAccelerationStructureKHR descASInfo{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET_ACCELERATION_STRUCTURE_KHR}; + descASInfo.accelerationStructureCount = 1; + descASInfo.pAccelerationStructures = &tlas; + VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; + + std::vector writes; + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 0, &descASInfo)); + writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo)); + vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); +} + + +//-------------------------------------------------------------------------------------------------- +// Writes the output image to the descriptor set +// - Required when changing resolution +// +void HelloVulkan::updateRtDescriptorSet() +{ + // (1) Output buffer + VkDescriptorImageInfo imageInfo{{}, m_offscreenColor.descriptor.imageView, VK_IMAGE_LAYOUT_GENERAL}; + VkWriteDescriptorSet wds = m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo); + vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); +} + + +////////////////////////////////////////////////////////////////////////// +// Helper to generate specialization info +// +class Specialization +{ +public: + void add(uint32_t constantID, int32_t value) + { + spec_values.push_back(value); + VkSpecializationMapEntry entry; + entry.constantID = constantID; + entry.size = sizeof(int32_t); + entry.offset = static_cast(spec_entries.size() * sizeof(int32_t)); + spec_entries.emplace_back(entry); + } + + void add(const std::vector>& const_values) + { + for(const auto& v : const_values) + add(v.first, v.second); + } + + VkSpecializationInfo* getSpecialization() + { + spec_info.dataSize = static_cast(spec_values.size() * sizeof(int32_t)); + spec_info.pData = spec_values.data(); + spec_info.mapEntryCount = static_cast(spec_entries.size()); + spec_info.pMapEntries = spec_entries.data(); + return &spec_info; + } + +private: + std::vector spec_values; + std::vector spec_entries; + VkSpecializationInfo spec_info; +}; + + +//-------------------------------------------------------------------------------------------------- +// Pipeline for the ray tracer: all shaders, raygen, chit, miss +// +void HelloVulkan::createRtPipeline() +{ + enum StageIndices + { + eRaygen, + eMiss, + eMiss2, + eShaderGroupCount + }; + + // Specialization - set 8 permutations of the 3 constant + std::vector specializations(8); + for(int i = 0; i < 8; i++) + { + int a = ((i >> 2) % 2) == 1; + int b = ((i >> 1) % 2) == 1; + int c = ((i >> 0) % 2) == 1; + specializations[i].add({{0, a}, {1, b}, {2, c}}); + } + + + // All stages + // Store the created modules for later cleanup + std::vector modules; + std::array stages{}; + VkPipelineShaderStageCreateInfo stage{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; + stage.pName = "main"; // All the same entry point + // Raygen + stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rgen.spv", true, defaultSearchPaths, true)); + modules.push_back(stage.module); + stage.stage = VK_SHADER_STAGE_RAYGEN_BIT_KHR; + stages[eRaygen] = stage; + // Miss + stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rmiss.spv", true, defaultSearchPaths, true)); + modules.push_back(stage.module); + stage.stage = VK_SHADER_STAGE_MISS_BIT_KHR; + stages[eMiss] = stage; + // The second miss shader is invoked when a shadow ray misses the geometry. It simply indicates that no occlusion has been found + stage.module = + nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytraceShadow.rmiss.spv", true, defaultSearchPaths, true)); + modules.push_back(stage.module); + stage.stage = VK_SHADER_STAGE_MISS_BIT_KHR; + stages[eMiss2] = stage; + + // Hit Group - Closest Hit + // Create many variation of the closest hit + stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rchit.spv", true, defaultSearchPaths, true)); + + modules.push_back(stage.module); + // Store the hit groups for compilation in a separate pipeline library object + std::vector libraryStages{}; + for(uint32_t s = 0; s < (uint32_t)specializations.size(); s++) + { + stage.stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; + stage.pSpecializationInfo = specializations[s].getSpecialization(); + libraryStages.push_back(stage); + } + + // Shader groups + VkRayTracingShaderGroupCreateInfoKHR group{VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR}; + group.anyHitShader = VK_SHADER_UNUSED_KHR; + group.closestHitShader = VK_SHADER_UNUSED_KHR; + group.generalShader = VK_SHADER_UNUSED_KHR; + group.intersectionShader = VK_SHADER_UNUSED_KHR; + + // Raygen + group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; + group.generalShader = eRaygen; + m_rtShaderGroups.push_back(group); + + // Miss + group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; + group.generalShader = eMiss; + m_rtShaderGroups.push_back(group); + + // Shadow Miss + group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; + group.generalShader = eMiss2; + m_rtShaderGroups.push_back(group); + + // Shader groups for the pipeline library containing the closest hit shaders + std::vector libraryShaderGroups; + + VkRayTracingShaderGroupCreateInfoKHR libraryGroup{VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR}; + libraryGroup.anyHitShader = VK_SHADER_UNUSED_KHR; + libraryGroup.closestHitShader = VK_SHADER_UNUSED_KHR; + libraryGroup.generalShader = VK_SHADER_UNUSED_KHR; + libraryGroup.intersectionShader = VK_SHADER_UNUSED_KHR; + libraryGroup.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR; + + // Hit Group - Closest Hit + AnyHit + // Creating many Hit groups, one for each specialization + + for(uint32_t s = 0; s < (uint32_t)specializations.size(); s++) + { + // The indices of the stages are local to the pipeline library + libraryGroup.closestHitShader = s; // Using variation of the closest hit + libraryShaderGroups.push_back(libraryGroup); + } + + // Push constant: we want to be able to update constants used by the shaders + VkPushConstantRange pushConstant{VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, + 0, sizeof(RtPushConstant)}; + + + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + pipelineLayoutCreateInfo.pushConstantRangeCount = 1; + pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstant; + + // Descriptor sets: one specific to ray tracing, and one shared with the rasterization pipeline + std::vector rtDescSetLayouts = {m_rtDescSetLayout, m_descSetLayout}; + pipelineLayoutCreateInfo.setLayoutCount = static_cast(rtDescSetLayouts.size()); + pipelineLayoutCreateInfo.pSetLayouts = rtDescSetLayouts.data(); + + vkCreatePipelineLayout(m_device, &pipelineLayoutCreateInfo, nullptr, &m_rtPipelineLayout); + + // Creation of the pipeline library object + VkRayTracingPipelineCreateInfoKHR pipelineLibraryInfo{VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR}; + // Flag the object as a pipeline library, which is a specific object that cannot be used directly. + pipelineLibraryInfo.flags = VK_PIPELINE_CREATE_LIBRARY_BIT_KHR; + // Use the same layout as the target pipeline + pipelineLibraryInfo.layout = m_rtPipelineLayout; + // As for the interface the maximum recursion depth must also be consistent across the pipeline + pipelineLibraryInfo.maxPipelineRayRecursionDepth = 2; + + // Pipeline libraries need to define an interface, defined by the maximum hit attribute size (typically 2 for + // the built-in triangle intersector) and the maximum payload size (3 floating-point values in this sample). + // Pipeline libraries can be linked into a final pipeline only if their interface matches + VkRayTracingPipelineInterfaceCreateInfoKHR pipelineInterface{VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_INTERFACE_CREATE_INFO_KHR}; + pipelineInterface.maxPipelineRayHitAttributeSize = sizeof(nvmath::vec2f); + pipelineInterface.maxPipelineRayPayloadSize = sizeof(nvmath::vec3f); + pipelineLibraryInfo.pLibraryInterface = &pipelineInterface; + + // Shader groups and stages for the library + pipelineLibraryInfo.groupCount = static_cast(libraryShaderGroups.size()); + pipelineLibraryInfo.pGroups = libraryShaderGroups.data(); + pipelineLibraryInfo.stageCount = static_cast(libraryStages.size()); + pipelineLibraryInfo.pStages = libraryStages.data(); + + // Creation of the pipeline library + vkCreateRayTracingPipelinesKHR(m_device, {}, {}, 1, &pipelineLibraryInfo, nullptr, &m_rtShaderLibrary); + + + // Assemble the shader stages and recursion depth info into the ray tracing pipeline + VkRayTracingPipelineCreateInfoKHR rayPipelineInfo{VK_STRUCTURE_TYPE_RAY_TRACING_PIPELINE_CREATE_INFO_KHR}; + rayPipelineInfo.stageCount = static_cast(stages.size()); // Stages are shaders + rayPipelineInfo.pStages = stages.data(); + + // In this case, m_rtShaderGroups.size() == 4: we have one raygen group, + // two miss shader groups, and one hit group. + rayPipelineInfo.groupCount = static_cast(m_rtShaderGroups.size()); + rayPipelineInfo.pGroups = m_rtShaderGroups.data(); + + // The ray tracing process can shoot rays from the camera, and a shadow ray can be shot from the + // hit points of the camera rays, hence a recursion level of 2. This number should be kept as low + // as possible for performance reasons. Even recursive ray tracing should be flattened into a loop + // in the ray generation to avoid deep recursion. + rayPipelineInfo.maxPipelineRayRecursionDepth = 2; // Ray depth + rayPipelineInfo.layout = m_rtPipelineLayout; + + // The library will be linked into the final pipeline by specifying its handle and shared interface + VkPipelineLibraryCreateInfoKHR inputLibrary{VK_STRUCTURE_TYPE_PIPELINE_LIBRARY_CREATE_INFO_KHR}; + inputLibrary.libraryCount = 1; + inputLibrary.pLibraries = &m_rtShaderLibrary; + rayPipelineInfo.pLibraryInfo = &inputLibrary; + rayPipelineInfo.pLibraryInterface = &pipelineInterface; + + // Deferred operations allow the driver to parallelize the pipeline compilation on several threads + // Create a deferred operation + VkDeferredOperationKHR hOp; + VkResult result = vkCreateDeferredOperationKHR(m_device, nullptr, &hOp); + assert(result == VK_SUCCESS); + + // The pipeline creation is called with the deferred operation. Instead of blocking until + // the compilation is done, the call returns immediately + vkCreateRayTracingPipelinesKHR(m_device, hOp, {}, 1, &rayPipelineInfo, nullptr, &m_rtPipeline); + + // The compilation will be split into a maximum of 8 threads, or the maximum supported by the + // driver for that operation + uint32_t maxThreads{8}; + uint32_t threadCount = std::min(vkGetDeferredOperationMaxConcurrencyKHR(m_device, hOp), maxThreads); + + + std::vector> joins; + for(uint32_t i = 0; i < threadCount; i++) + { + VkDevice device{m_device}; + joins.emplace_back(std::async(std::launch::async, [device, hOp]() { + // Wait until the thread has finished its work + VkResult result = vkDeferredOperationJoinKHR(device, hOp); + // A return value of SUCCESS means the pipeline compilation is done. + // THREAD_DONE indicates that thread has no work to do for this task + // (e.g. the operation could not be split into that many threads) + // THREAD_IDLE indicates the thread has finished its task, but the overall pipeline + // compilation is not finished. + // In the last two cases, more work could be performed by the thread, such as waiting + // for another deferred operation + assert(result == VK_SUCCESS || result == VK_THREAD_DONE_KHR || result == VK_THREAD_IDLE_KHR); + })); + } + // Wait for all threads to finish + for(auto& f : joins) + { + f.get(); + } + // Once the deferred operation is complete, check for compilation success + result = vkGetDeferredOperationResultKHR(m_device, hOp); + assert(result == VK_SUCCESS); + // Destroy the deferred operation + vkDestroyDeferredOperationKHR(m_device, hOp, nullptr); + + // The Shader Binding Table is built accounting for the entire pipeline, including the + // stages contained in the library. Passing the library information allows the wrapper + // to shift the shader group indices accordingly + m_sbtWrapper.create(m_rtPipeline, rayPipelineInfo, {pipelineLibraryInfo}); + + // Spec only guarantees 1 level of "recursion". Check for that sad possibility here. + if(m_rtProperties.maxRayRecursionDepth <= 1) + { + throw std::runtime_error("Device fails to support ray recursion (m_rtProperties.maxRayRecursionDepth <= 1)"); + } + + // Destroy all the created modules, for both libraries and main pipeline + for(auto& m : modules) + vkDestroyShaderModule(m_device, m, nullptr); +} + +//-------------------------------------------------------------------------------------------------- +// Ray Tracing the scene +// +void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& clearColor) +{ + m_debug.beginLabel(cmdBuf, "Ray trace"); + // Initializing push constant values + m_rtPushConstants.clearColor = clearColor; + m_rtPushConstants.lightPosition = m_pushConstant.lightPosition; + m_rtPushConstants.lightIntensity = m_pushConstant.lightIntensity; + m_rtPushConstants.lightType = m_pushConstant.lightType; + m_rtPushConstants.specialization = m_pushConstant.specialization; + + std::vector descSets{m_rtDescSet, m_descSet}; + vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipeline); + vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_RAY_TRACING_KHR, m_rtPipelineLayout, 0, + (uint32_t)descSets.size(), descSets.data(), 0, nullptr); + vkCmdPushConstants(cmdBuf, m_rtPipelineLayout, + VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_MISS_BIT_KHR, + 0, sizeof(RtPushConstant), &m_rtPushConstants); + + auto& regions = m_sbtWrapper.getRegions(); + vkCmdTraceRaysKHR(cmdBuf, ®ions[0], ®ions[1], ®ions[2], ®ions[3], m_size.width, m_size.height, 1); + + m_debug.endLabel(cmdBuf); +} diff --git a/ray_tracing_advanced_compilation/hello_vulkan.h b/ray_tracing_advanced_compilation/hello_vulkan.h new file mode 100644 index 0000000..6016ca9 --- /dev/null +++ b/ray_tracing_advanced_compilation/hello_vulkan.h @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2014-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "nvvk/appbase_vk.hpp" +#include "nvvk/debug_util_vk.hpp" +#include "nvvk/descriptorsets_vk.hpp" +#include "nvvk/memallocator_dma_vk.hpp" +#include "nvvk/resourceallocator_vk.hpp" + +// #VKRay +#include "nvvk/raytraceKHR_vk.hpp" +#include "nvvk/sbtwrapper_vk.hpp" + +//-------------------------------------------------------------------------------------------------- +// Simple rasterizer of OBJ objects +// - Each OBJ loaded are stored in an `ObjModel` and referenced by a `ObjInstance` +// - It is possible to have many `ObjInstance` referencing the same `ObjModel` +// - Rendering is done in an offscreen framebuffer +// - The image of the framebuffer is displayed in post-process in a full-screen quad +// +class HelloVulkan : public nvvk::AppBaseVk +{ +public: + void setup(const VkInstance& instance, const VkDevice& device, const VkPhysicalDevice& physicalDevice, uint32_t queueFamily) override; + void createDescriptorSetLayout(); + void createGraphicsPipeline(); + void loadModel(const std::string& filename, nvmath::mat4f transform = nvmath::mat4f(1)); + void updateDescriptorSet(); + void createUniformBuffer(); + void createSceneDescriptionBuffer(); + void createTextureImages(const VkCommandBuffer& cmdBuf, const std::vector& textures); + void updateUniformBuffer(const VkCommandBuffer& cmdBuf); + void onResize(int /*w*/, int /*h*/) override; + void destroyResources(); + void rasterize(const VkCommandBuffer& cmdBuff); + + // The OBJ model + struct ObjModel + { + uint32_t nbIndices{0}; + uint32_t nbVertices{0}; + nvvk::Buffer vertexBuffer; // Device buffer of all 'Vertex' + nvvk::Buffer indexBuffer; // Device buffer of the indices forming triangles + nvvk::Buffer matColorBuffer; // Device buffer of array of 'Wavefront material' + nvvk::Buffer matIndexBuffer; // Device buffer of array of 'Wavefront material' + }; + + // Instance of the OBJ + struct ObjInstance + { + uint32_t objIndex{0}; // Reference to the `m_objModel` + uint32_t txtOffset{0}; // Offset in `m_textures` + nvmath::mat4f transform{1}; // Position of the instance + nvmath::mat4f transformIT{1}; // Inverse transpose + }; + + // Information pushed at each draw call + struct ObjPushConstant + { + nvmath::vec3f lightPosition{10.f, 15.f, 8.f}; + int instanceId{0}; // To retrieve the transformation matrix + float lightIntensity{100.f}; + int lightType{0}; // 0: point, 1: infinite + int specialization{7}; // all in use + }; + ObjPushConstant m_pushConstant; + + // Array of objects and instances in the scene + std::vector m_objModel; + std::vector m_objInstance; + + // Graphic pipeline + VkPipelineLayout m_pipelineLayout; + VkPipeline m_graphicsPipeline; + nvvk::DescriptorSetBindings m_descSetLayoutBind; + VkDescriptorPool m_descPool; + VkDescriptorSetLayout m_descSetLayout; + VkDescriptorSet m_descSet; + + nvvk::Buffer m_cameraMat; // Device-Host of the camera matrices + nvvk::Buffer m_sceneDesc; // Device buffer of the OBJ instances + std::vector m_textures; // vector of all textures of the scene + + + nvvk::ResourceAllocatorDma m_alloc; // Allocator for buffer, images, acceleration structures + nvvk::DebugUtil m_debug; // Utility to name objects + + + // #Post + void createOffscreenRender(); + void createPostPipeline(); + void createPostDescriptor(); + void updatePostDescriptorSet(); + void drawPost(VkCommandBuffer cmdBuf); + + nvvk::DescriptorSetBindings m_postDescSetLayoutBind; + VkDescriptorPool m_postDescPool{VK_NULL_HANDLE}; + VkDescriptorSetLayout m_postDescSetLayout{VK_NULL_HANDLE}; + VkDescriptorSet m_postDescSet{VK_NULL_HANDLE}; + VkPipeline m_postPipeline{VK_NULL_HANDLE}; + VkPipelineLayout m_postPipelineLayout{VK_NULL_HANDLE}; + VkRenderPass m_offscreenRenderPass{VK_NULL_HANDLE}; + VkFramebuffer m_offscreenFramebuffer{VK_NULL_HANDLE}; + nvvk::Texture m_offscreenColor; + nvvk::Texture m_offscreenDepth; + VkFormat m_offscreenColorFormat{VK_FORMAT_R32G32B32A32_SFLOAT}; + VkFormat m_offscreenDepthFormat{VK_FORMAT_X8_D24_UNORM_PACK32}; + + // #VKRay + void initRayTracing(); + auto objectToVkGeometryKHR(const ObjModel& model); + void createBottomLevelAS(); + void createTopLevelAS(); + void createRtDescriptorSet(); + void updateRtDescriptorSet(); + void createRtPipeline(); + void raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& clearColor); + + + VkPhysicalDeviceRayTracingPipelinePropertiesKHR m_rtProperties{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_PROPERTIES_KHR}; + nvvk::RaytracingBuilderKHR m_rtBuilder; + nvvk::DescriptorSetBindings m_rtDescSetLayoutBind; + VkDescriptorPool m_rtDescPool; + VkDescriptorSetLayout m_rtDescSetLayout; + VkDescriptorSet m_rtDescSet; + std::vector m_rtShaderGroups; + VkPipelineLayout m_rtPipelineLayout; + VkPipeline m_rtPipeline; + nvvk::SBTWrapper m_sbtWrapper; + // Ray tracing shader library + VkPipeline m_rtShaderLibrary; + + struct RtPushConstant + { + nvmath::vec4f clearColor; + nvmath::vec3f lightPosition; + float lightIntensity{100.0f}; + int lightType{0}; + int specialization{7}; + } m_rtPushConstants; +}; diff --git a/ray_tracing_advanced_compilation/images/advanced_compilation.png b/ray_tracing_advanced_compilation/images/advanced_compilation.png new file mode 100644 index 0000000..8110cbc Binary files /dev/null and b/ray_tracing_advanced_compilation/images/advanced_compilation.png differ diff --git a/ray_tracing_advanced_compilation/images/deferred_host_operations.png b/ray_tracing_advanced_compilation/images/deferred_host_operations.png new file mode 100644 index 0000000..74b60e4 Binary files /dev/null and b/ray_tracing_advanced_compilation/images/deferred_host_operations.png differ diff --git a/ray_tracing_advanced_compilation/images/high_level_advanced_compilation.png b/ray_tracing_advanced_compilation/images/high_level_advanced_compilation.png new file mode 100644 index 0000000..982afb4 Binary files /dev/null and b/ray_tracing_advanced_compilation/images/high_level_advanced_compilation.png differ diff --git a/ray_tracing_advanced_compilation/images/high_level_regular_compilation.png b/ray_tracing_advanced_compilation/images/high_level_regular_compilation.png new file mode 100644 index 0000000..bcf3a3f Binary files /dev/null and b/ray_tracing_advanced_compilation/images/high_level_regular_compilation.png differ diff --git a/ray_tracing_advanced_compilation/images/library.png b/ray_tracing_advanced_compilation/images/library.png new file mode 100644 index 0000000..bc0e4ef Binary files /dev/null and b/ray_tracing_advanced_compilation/images/library.png differ diff --git a/ray_tracing_advanced_compilation/images/regular_pipeline.png b/ray_tracing_advanced_compilation/images/regular_pipeline.png new file mode 100644 index 0000000..cb8b126 Binary files /dev/null and b/ray_tracing_advanced_compilation/images/regular_pipeline.png differ diff --git a/ray_tracing_advanced_compilation/images/single_threaded_compilation.png b/ray_tracing_advanced_compilation/images/single_threaded_compilation.png new file mode 100644 index 0000000..b01fc65 Binary files /dev/null and b/ray_tracing_advanced_compilation/images/single_threaded_compilation.png differ diff --git a/ray_tracing_advanced_compilation/main.cpp b/ray_tracing_advanced_compilation/main.cpp new file mode 100644 index 0000000..d0e1e8a --- /dev/null +++ b/ray_tracing_advanced_compilation/main.cpp @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2014-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2014-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + + +// ImGui - standalone example application for Glfw + Vulkan, using programmable +// pipeline If you are new to ImGui, see examples/README.txt and documentation +// at the top of imgui.cpp. + +#include + +#include "backends/imgui_impl_glfw.h" +#include "imgui.h" + +#include "hello_vulkan.h" +#include "imgui/imgui_camera_widget.h" +#include "nvh/cameramanipulator.hpp" +#include "nvh/fileoperations.hpp" +#include "nvpsystem.hpp" +#include "nvvk/commands_vk.hpp" +#include "nvvk/context_vk.hpp" + + +////////////////////////////////////////////////////////////////////////// +#define UNUSED(x) (void)(x) +////////////////////////////////////////////////////////////////////////// + +// Default search path for shaders +std::vector defaultSearchPaths; + + +// GLFW Callback functions +static void onErrorCallback(int error, const char* description) +{ + fprintf(stderr, "GLFW Error %d: %s\n", error, description); +} + +// Extra UI +void renderUI(HelloVulkan& helloVk) +{ + ImGuiH::CameraWidget(); + if(ImGui::CollapsingHeader("Light")) + { + ImGui::RadioButton("Point", &helloVk.m_pushConstant.lightType, 0); + ImGui::SameLine(); + ImGui::RadioButton("Infinite", &helloVk.m_pushConstant.lightType, 1); + + ImGui::SliderFloat3("Position", &helloVk.m_pushConstant.lightPosition.x, -20.f, 20.f); + ImGui::SliderFloat("Intensity", &helloVk.m_pushConstant.lightIntensity, 0.f, 150.f); + } + + // Specialization + ImGui::SliderInt("Specialization", &helloVk.m_pushConstant.specialization, 0, 7); + int s = helloVk.m_pushConstant.specialization; + int a = ((s >> 2) % 2) == 1; + int b = ((s >> 1) % 2) == 1; + int c = ((s >> 0) % 2) == 1; + ImGui::Checkbox("Use Diffuse", (bool*)&a); + ImGui::Checkbox("Use Specular", (bool*)&b); + ImGui::Checkbox("Trace shadow", (bool*)&c); + helloVk.m_pushConstant.specialization = (a << 2) + (b << 1) + c; +} + +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////// +static int const SAMPLE_WIDTH = 1280; +static int const SAMPLE_HEIGHT = 720; + + +//-------------------------------------------------------------------------------------------------- +// Application Entry +// +int main(int argc, char** argv) +{ + UNUSED(argc); + + // Setup GLFW window + glfwSetErrorCallback(onErrorCallback); + if(!glfwInit()) + { + return 1; + } + glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); + GLFWwindow* window = glfwCreateWindow(SAMPLE_WIDTH, SAMPLE_HEIGHT, PROJECT_NAME, nullptr, nullptr); + + // Setup camera + CameraManip.setWindowSize(SAMPLE_WIDTH, SAMPLE_HEIGHT); + CameraManip.setLookat(nvmath::vec3f(5, 4, -4), nvmath::vec3f(0, 1, 0), nvmath::vec3f(0, 1, 0)); + + // Setup Vulkan + if(!glfwVulkanSupported()) + { + printf("GLFW: Vulkan Not Supported\n"); + return 1; + } + + // setup some basic things for the sample, logging file for example + NVPSystem system(PROJECT_NAME); + + // Search path for shaders and other media + defaultSearchPaths = { + NVPSystem::exePath() + PROJECT_RELDIRECTORY, + NVPSystem::exePath() + PROJECT_RELDIRECTORY "..", + std::string(PROJECT_NAME), + }; + + // Requesting Vulkan extensions and layers + nvvk::ContextCreateInfo contextInfo; + contextInfo.setVersion(1, 2); + contextInfo.addInstanceLayer("VK_LAYER_LUNARG_monitor", true); + contextInfo.addInstanceExtension(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, true); + contextInfo.addInstanceExtension(VK_KHR_SURFACE_EXTENSION_NAME); +#ifdef _WIN32 + contextInfo.addInstanceExtension(VK_KHR_WIN32_SURFACE_EXTENSION_NAME); +#else + contextInfo.addInstanceExtension(VK_KHR_XLIB_SURFACE_EXTENSION_NAME); + contextInfo.addInstanceExtension(VK_KHR_XCB_SURFACE_EXTENSION_NAME); +#endif + contextInfo.addInstanceExtension(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_SWAPCHAIN_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_DEDICATED_ALLOCATION_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_GET_MEMORY_REQUIREMENTS_2_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_EXT_DESCRIPTOR_INDEXING_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME); + + // #VKRay: Activate the ray tracing extension + VkPhysicalDeviceAccelerationStructureFeaturesKHR accelFeature{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_ACCELERATION_STRUCTURE_FEATURES_KHR}; + contextInfo.addDeviceExtension(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME, false, &accelFeature); + VkPhysicalDeviceRayTracingPipelineFeaturesKHR rtPipelineFeature{VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_RAY_TRACING_PIPELINE_FEATURES_KHR}; + contextInfo.addDeviceExtension(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME, false, &rtPipelineFeature); + contextInfo.addDeviceExtension(VK_KHR_MAINTENANCE3_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_PIPELINE_LIBRARY_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME); + contextInfo.addDeviceExtension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); + + // Creating Vulkan base application + nvvk::Context vkctx{}; + vkctx.initInstance(contextInfo); + // Find all compatible devices + auto compatibleDevices = vkctx.getCompatibleDevices(contextInfo); + assert(!compatibleDevices.empty()); + // Use a compatible device + vkctx.initDevice(compatibleDevices[0], contextInfo); + + // Create example + HelloVulkan helloVk; + + // Window need to be opened to get the surface on which to draw + const VkSurfaceKHR surface = helloVk.getVkSurface(vkctx.m_instance, window); + vkctx.setGCTQueueWithPresent(surface); + + helloVk.setup(vkctx.m_instance, vkctx.m_device, vkctx.m_physicalDevice, vkctx.m_queueGCT.familyIndex); + helloVk.createSwapchain(surface, SAMPLE_WIDTH, SAMPLE_HEIGHT); + helloVk.createDepthBuffer(); + helloVk.createRenderPass(); + helloVk.createFrameBuffers(); + + // Setup Imgui + helloVk.initGUI(0); // Using sub-pass 0 + + // Creation of the example + helloVk.loadModel(nvh::findFile("media/scenes/Medieval_building.obj", defaultSearchPaths, true)); + helloVk.loadModel(nvh::findFile("media/scenes/plane.obj", defaultSearchPaths, true)); + + + helloVk.createOffscreenRender(); + helloVk.createDescriptorSetLayout(); + helloVk.createGraphicsPipeline(); + helloVk.createUniformBuffer(); + helloVk.createSceneDescriptionBuffer(); + helloVk.updateDescriptorSet(); + + // #VKRay + helloVk.initRayTracing(); + helloVk.createBottomLevelAS(); + helloVk.createTopLevelAS(); + helloVk.createRtDescriptorSet(); + helloVk.createRtPipeline(); + + helloVk.createPostDescriptor(); + helloVk.createPostPipeline(); + helloVk.updatePostDescriptorSet(); + + + nvmath::vec4f clearColor = nvmath::vec4f(1, 1, 1, 1.00f); + bool useRaytracer = true; + + + helloVk.setupGlfwCallbacks(window); + ImGui_ImplGlfw_InitForVulkan(window, true); + + // Main loop + while(!glfwWindowShouldClose(window)) + { + glfwPollEvents(); + if(helloVk.isMinimized()) + continue; + + // Start the Dear ImGui frame + ImGui_ImplGlfw_NewFrame(); + ImGui::NewFrame(); + + + // Show UI window. + if(helloVk.showGui()) + { + ImGuiH::Panel::Begin(); + ImGui::ColorEdit3("Clear color", reinterpret_cast(&clearColor)); + ImGui::Checkbox("Ray Tracer mode", &useRaytracer); // Switch between raster and ray tracing + + renderUI(helloVk); + ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate); + ImGuiH::Control::Info("", "", "(F10) Toggle Pane", ImGuiH::Control::Flags::Disabled); + ImGuiH::Panel::End(); + } + + // Start rendering the scene + helloVk.prepareFrame(); + + // Start command buffer of this frame + auto curFrame = helloVk.getCurFrame(); + const VkCommandBuffer& cmdBuf = helloVk.getCommandBuffers()[curFrame]; + + VkCommandBufferBeginInfo beginInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; + vkBeginCommandBuffer(cmdBuf, &beginInfo); + + // Updating camera buffer + helloVk.updateUniformBuffer(cmdBuf); + + // Clearing screen + std::array clearValues{}; + clearValues[0].color = {{clearColor[0], clearColor[1], clearColor[2], clearColor[3]}}; + clearValues[1].depthStencil = {1.0f, 0}; + + // Offscreen render pass + { + VkRenderPassBeginInfo offscreenRenderPassBeginInfo{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + offscreenRenderPassBeginInfo.clearValueCount = 2; + offscreenRenderPassBeginInfo.pClearValues = clearValues.data(); + offscreenRenderPassBeginInfo.renderPass = helloVk.m_offscreenRenderPass; + offscreenRenderPassBeginInfo.framebuffer = helloVk.m_offscreenFramebuffer; + offscreenRenderPassBeginInfo.renderArea = {{0, 0}, helloVk.getSize()}; + + // Rendering Scene + if(useRaytracer) + { + helloVk.raytrace(cmdBuf, clearColor); + } + else + { + vkCmdBeginRenderPass(cmdBuf, &offscreenRenderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + helloVk.rasterize(cmdBuf); + vkCmdEndRenderPass(cmdBuf); + } + } + + // 2nd rendering pass: tone mapper, UI + { + VkRenderPassBeginInfo postRenderPassBeginInfo{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; + postRenderPassBeginInfo.clearValueCount = 2; + postRenderPassBeginInfo.pClearValues = clearValues.data(); + postRenderPassBeginInfo.renderPass = helloVk.getRenderPass(); + postRenderPassBeginInfo.framebuffer = helloVk.getFramebuffers()[curFrame]; + postRenderPassBeginInfo.renderArea = {{0, 0}, helloVk.getSize()}; + + // Rendering tonemapper + vkCmdBeginRenderPass(cmdBuf, &postRenderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); + helloVk.drawPost(cmdBuf); + // Rendering UI + ImGui::Render(); + ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), cmdBuf); + vkCmdEndRenderPass(cmdBuf); + } + + // Submit for display + vkEndCommandBuffer(cmdBuf); + helloVk.submitFrame(); + } + + // Cleanup + vkDeviceWaitIdle(helloVk.getDevice()); + + helloVk.destroyResources(); + helloVk.destroy(); + vkctx.deinit(); + + glfwDestroyWindow(window); + glfwTerminate(); + + return 0; +} diff --git a/ray_tracing_advanced_compilation/shaders/frag_shader.frag b/ray_tracing_advanced_compilation/shaders/frag_shader.frag new file mode 100644 index 0000000..425c866 --- /dev/null +++ b/ray_tracing_advanced_compilation/shaders/frag_shader.frag @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_EXT_nonuniform_qualifier : enable +#extension GL_GOOGLE_include_directive : enable +#extension GL_EXT_scalar_block_layout : enable + +#include "wavefront.glsl" + + +layout(push_constant) uniform shaderInformation +{ + vec3 lightPosition; + uint instanceId; + float lightIntensity; + int lightType; +} +pushC; + +// clang-format off +// Incoming +//layout(location = 0) flat in int matIndex; +layout(location = 1) in vec2 fragTexCoord; +layout(location = 2) in vec3 fragNormal; +layout(location = 3) in vec3 viewDir; +layout(location = 4) in vec3 worldPos; +// Outgoing +layout(location = 0) out vec4 outColor; +// Buffers +layout(binding = 1, scalar) buffer MatColorBufferObject { WaveFrontMaterial m[]; } materials[]; +layout(binding = 2, scalar) buffer ScnDesc { sceneDesc i[]; } scnDesc; +layout(binding = 3) uniform sampler2D[] textureSamplers; +layout(binding = 4, scalar) buffer MatIndex { int i[]; } matIdx[]; + +// clang-format on + + +void main() +{ + // Object of this instance + int objId = scnDesc.i[pushC.instanceId].objId; + + // Material of the object + int matIndex = matIdx[nonuniformEXT(objId)].i[gl_PrimitiveID]; + WaveFrontMaterial mat = materials[nonuniformEXT(objId)].m[matIndex]; + + vec3 N = normalize(fragNormal); + + // Vector toward light + vec3 L; + float lightIntensity = pushC.lightIntensity; + if(pushC.lightType == 0) + { + vec3 lDir = pushC.lightPosition - worldPos; + float d = length(lDir); + lightIntensity = pushC.lightIntensity / (d * d); + L = normalize(lDir); + } + else + { + L = normalize(pushC.lightPosition - vec3(0)); + } + + + // Diffuse + vec3 diffuse = computeDiffuse(mat, L, N); + if(mat.textureId >= 0) + { + int txtOffset = scnDesc.i[pushC.instanceId].txtOffset; + uint txtId = txtOffset + mat.textureId; + vec3 diffuseTxt = texture(textureSamplers[nonuniformEXT(txtId)], fragTexCoord).xyz; + diffuse *= diffuseTxt; + } + + // Specular + vec3 specular = computeSpecular(mat, viewDir, L, N); + + // Result + outColor = vec4(lightIntensity * (diffuse + specular), 1); +} diff --git a/ray_tracing_advanced_compilation/shaders/passthrough.vert b/ray_tracing_advanced_compilation/shaders/passthrough.vert new file mode 100644 index 0000000..ed293b8 --- /dev/null +++ b/ray_tracing_advanced_compilation/shaders/passthrough.vert @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#version 450 +layout (location = 0) out vec2 outUV; + + +out gl_PerVertex +{ + vec4 gl_Position; +}; + + +void main() +{ + outUV = vec2((gl_VertexIndex << 1) & 2, gl_VertexIndex & 2); + gl_Position = vec4(outUV * 2.0f - 1.0f, 1.0f, 1.0f); +} diff --git a/ray_tracing_advanced_compilation/shaders/post.frag b/ray_tracing_advanced_compilation/shaders/post.frag new file mode 100644 index 0000000..847481c --- /dev/null +++ b/ray_tracing_advanced_compilation/shaders/post.frag @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#version 450 +layout(location = 0) in vec2 outUV; +layout(location = 0) out vec4 fragColor; + +layout(set = 0, binding = 0) uniform sampler2D noisyTxt; + +layout(push_constant) uniform shaderInformation +{ + float aspectRatio; +} +pushc; + +void main() +{ + vec2 uv = outUV; + float gamma = 1. / 2.2; + fragColor = pow(texture(noisyTxt, uv).rgba, vec4(gamma)); +} diff --git a/ray_tracing_advanced_compilation/shaders/raycommon.glsl b/ray_tracing_advanced_compilation/shaders/raycommon.glsl new file mode 100644 index 0000000..b896c84 --- /dev/null +++ b/ray_tracing_advanced_compilation/shaders/raycommon.glsl @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +struct hitPayload +{ + vec3 hitValue; +}; diff --git a/ray_tracing_advanced_compilation/shaders/raytrace.rchit b/ray_tracing_advanced_compilation/shaders/raytrace.rchit new file mode 100644 index 0000000..e4ad9d0 --- /dev/null +++ b/ray_tracing_advanced_compilation/shaders/raytrace.rchit @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#version 460 +#extension GL_EXT_ray_tracing : require +#extension GL_EXT_nonuniform_qualifier : enable +#extension GL_EXT_scalar_block_layout : enable +#extension GL_GOOGLE_include_directive : enable +#include "raycommon.glsl" +#include "wavefront.glsl" + +hitAttributeEXT vec2 attribs; + +// clang-format off +layout(location = 0) rayPayloadInEXT hitPayload prd; +layout(location = 1) rayPayloadEXT bool isShadowed; + +layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; +layout(binding = 1, set = 1, scalar) buffer MatColorBufferObject { WaveFrontMaterial m[]; } materials[]; +layout(binding = 2, set = 1, scalar) buffer ScnDesc { sceneDesc i[]; } scnDesc; +layout(binding = 3, set = 1) uniform sampler2D textureSamplers[]; +layout(binding = 4, set = 1) buffer MatIndexColorBuffer { int i[]; } matIndex[]; +layout(binding = 5, set = 1, scalar) buffer Vertices { Vertex v[]; } vertices[]; +layout(binding = 6, set = 1) buffer Indices { uint i[]; } indices[]; + + +layout(constant_id = 0) const int USE_DIFFUSE = 1; +layout(constant_id = 1) const int USE_SPECULAR = 1; +layout(constant_id = 2) const int TRACE_SHADOW = 1; + +// clang-format on + +layout(push_constant) uniform Constants +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; + int specialization; +} +pushC; + + +void main() +{ + // Object of this instance + uint objId = scnDesc.i[gl_InstanceCustomIndexEXT].objId; + + // Indices of the triangle + ivec3 ind = ivec3(indices[nonuniformEXT(objId)].i[3 * gl_PrimitiveID + 0], // + indices[nonuniformEXT(objId)].i[3 * gl_PrimitiveID + 1], // + indices[nonuniformEXT(objId)].i[3 * gl_PrimitiveID + 2]); // + // Vertex of the triangle + Vertex v0 = vertices[nonuniformEXT(objId)].v[ind.x]; + Vertex v1 = vertices[nonuniformEXT(objId)].v[ind.y]; + Vertex v2 = vertices[nonuniformEXT(objId)].v[ind.z]; + + const vec3 barycentrics = vec3(1.0 - attribs.x - attribs.y, attribs.x, attribs.y); + + // Computing the normal at hit position + vec3 normal = v0.nrm * barycentrics.x + v1.nrm * barycentrics.y + v2.nrm * barycentrics.z; + // Transforming the normal to world space + normal = normalize(vec3(scnDesc.i[gl_InstanceCustomIndexEXT].transfoIT * vec4(normal, 0.0))); + + + // Computing the coordinates of the hit position + vec3 worldPos = v0.pos * barycentrics.x + v1.pos * barycentrics.y + v2.pos * barycentrics.z; + // Transforming the position to world space + worldPos = vec3(scnDesc.i[gl_InstanceCustomIndexEXT].transfo * vec4(worldPos, 1.0)); + + // Vector toward the light + vec3 L; + float lightIntensity = pushC.lightIntensity; + float lightDistance = 100000.0; + // Point light + if(pushC.lightType == 0) + { + vec3 lDir = pushC.lightPosition - worldPos; + lightDistance = length(lDir); + lightIntensity = pushC.lightIntensity / (lightDistance * lightDistance); + L = normalize(lDir); + } + else // Directional light + { + L = normalize(pushC.lightPosition - vec3(0)); + } + + // Material of the object + int matIdx = matIndex[nonuniformEXT(objId)].i[gl_PrimitiveID]; + WaveFrontMaterial mat = materials[nonuniformEXT(objId)].m[matIdx]; + + + // Diffuse + vec3 diffuse = vec3(0); + if(USE_DIFFUSE == 1) + { + diffuse = computeDiffuse(mat, L, normal); + if(mat.textureId >= 0) + { + uint txtId = mat.textureId + scnDesc.i[gl_InstanceCustomIndexEXT].txtOffset; + vec2 texCoord = v0.texCoord * barycentrics.x + v1.texCoord * barycentrics.y + + v2.texCoord * barycentrics.z; + diffuse *= texture(textureSamplers[nonuniformEXT(txtId)], texCoord).xyz; + } + } + + vec3 specular = vec3(0); + float attenuation = 1; + + // Tracing shadow ray only if the light is visible from the surface + if(dot(normal, L) > 0) + { + if(TRACE_SHADOW == 1) + { + float tMin = 0.001; + float tMax = lightDistance; + vec3 origin = gl_WorldRayOriginEXT + gl_WorldRayDirectionEXT * gl_HitTEXT; + vec3 rayDir = L; + uint flags = gl_RayFlagsTerminateOnFirstHitEXT | gl_RayFlagsOpaqueEXT + | gl_RayFlagsSkipClosestHitShaderEXT; + isShadowed = true; + traceRayEXT(topLevelAS, // acceleration structure + flags, // rayFlags + 0xFF, // cullMask + 0, // sbtRecordOffset + 0, // sbtRecordStride + 1, // missIndex + origin, // ray origin + tMin, // ray min range + rayDir, // ray direction + tMax, // ray max range + 1 // payload (location = 1) + ); + } + else + isShadowed = false; + + if(isShadowed) + { + attenuation = 0.3; + } + else + { + // Specular + if(USE_SPECULAR == 1) + { + specular = computeSpecular(mat, gl_WorldRayDirectionEXT, L, normal); + } + } + } + + prd.hitValue = vec3(lightIntensity * attenuation * (diffuse + specular)); +} diff --git a/ray_tracing_advanced_compilation/shaders/raytrace.rgen b/ray_tracing_advanced_compilation/shaders/raytrace.rgen new file mode 100644 index 0000000..655e6d0 --- /dev/null +++ b/ray_tracing_advanced_compilation/shaders/raytrace.rgen @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#version 460 +#extension GL_EXT_ray_tracing : require +#extension GL_GOOGLE_include_directive : enable +#include "raycommon.glsl" + +layout(binding = 0, set = 0) uniform accelerationStructureEXT topLevelAS; +layout(binding = 1, set = 0, rgba32f) uniform image2D image; + +layout(location = 0) rayPayloadEXT hitPayload prd; + +layout(binding = 0, set = 1) uniform CameraProperties +{ + mat4 view; + mat4 proj; + mat4 viewInverse; + mat4 projInverse; +} +cam; + +layout(push_constant) uniform Constants +{ + vec4 clearColor; + vec3 lightPosition; + float lightIntensity; + int lightType; + int specialization; +} +pushC; + +void main() +{ + const vec2 pixelCenter = vec2(gl_LaunchIDEXT.xy) + vec2(0.5); + const vec2 inUV = pixelCenter / vec2(gl_LaunchSizeEXT.xy); + vec2 d = inUV * 2.0 - 1.0; + + vec4 origin = cam.viewInverse * vec4(0, 0, 0, 1); + vec4 target = cam.projInverse * vec4(d.x, d.y, 1, 1); + vec4 direction = cam.viewInverse * vec4(normalize(target.xyz), 0); + + uint rayFlags = gl_RayFlagsOpaqueEXT; + float tMin = 0.001; + float tMax = 10000.0; + + traceRayEXT(topLevelAS, // acceleration structure + rayFlags, // rayFlags + 0xFF, // cullMask + pushC.specialization, // sbtRecordOffset + 0, // sbtRecordStride + 0, // missIndex + origin.xyz, // ray origin + tMin, // ray min range + direction.xyz, // ray direction + tMax, // ray max range + 0 // payload (location = 0) + ); + + imageStore(image, ivec2(gl_LaunchIDEXT.xy), vec4(prd.hitValue, 1.0)); +} diff --git a/ray_tracing_advanced_compilation/shaders/raytrace.rmiss b/ray_tracing_advanced_compilation/shaders/raytrace.rmiss new file mode 100644 index 0000000..c960eb6 --- /dev/null +++ b/ray_tracing_advanced_compilation/shaders/raytrace.rmiss @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#version 460 +#extension GL_EXT_ray_tracing : require +#extension GL_GOOGLE_include_directive : enable +#include "raycommon.glsl" + +layout(location = 0) rayPayloadInEXT hitPayload prd; + +layout(push_constant) uniform Constants +{ + vec4 clearColor; +}; + +void main() +{ + prd.hitValue = clearColor.xyz * 0.8; +} diff --git a/ray_tracing_advanced_compilation/shaders/raytraceShadow.rmiss b/ray_tracing_advanced_compilation/shaders/raytraceShadow.rmiss new file mode 100644 index 0000000..04dd9fc --- /dev/null +++ b/ray_tracing_advanced_compilation/shaders/raytraceShadow.rmiss @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#version 460 +#extension GL_EXT_ray_tracing : require + +layout(location = 1) rayPayloadInEXT bool isShadowed; + +void main() +{ + isShadowed = false; +} diff --git a/ray_tracing_advanced_compilation/shaders/vert_shader.vert b/ray_tracing_advanced_compilation/shaders/vert_shader.vert new file mode 100644 index 0000000..a1c55ba --- /dev/null +++ b/ray_tracing_advanced_compilation/shaders/vert_shader.vert @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +#version 450 +#extension GL_ARB_separate_shader_objects : enable +#extension GL_EXT_scalar_block_layout : enable +#extension GL_GOOGLE_include_directive : enable + +#include "wavefront.glsl" + +// clang-format off +layout(binding = 2, set = 0, scalar) buffer ScnDesc { sceneDesc i[]; } scnDesc; +// clang-format on + +layout(binding = 0) uniform UniformBufferObject +{ + mat4 view; + mat4 proj; + mat4 viewI; +} +ubo; + +layout(push_constant) uniform shaderInformation +{ + vec3 lightPosition; + uint instanceId; + float lightIntensity; + int lightType; +} +pushC; + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec3 inNormal; +layout(location = 2) in vec3 inColor; +layout(location = 3) in vec2 inTexCoord; + + +//layout(location = 0) flat out int matIndex; +layout(location = 1) out vec2 fragTexCoord; +layout(location = 2) out vec3 fragNormal; +layout(location = 3) out vec3 viewDir; +layout(location = 4) out vec3 worldPos; + +out gl_PerVertex +{ + vec4 gl_Position; +}; + + +void main() +{ + mat4 objMatrix = scnDesc.i[pushC.instanceId].transfo; + mat4 objMatrixIT = scnDesc.i[pushC.instanceId].transfoIT; + + vec3 origin = vec3(ubo.viewI * vec4(0, 0, 0, 1)); + + worldPos = vec3(objMatrix * vec4(inPosition, 1.0)); + viewDir = vec3(worldPos - origin); + fragTexCoord = inTexCoord; + fragNormal = vec3(objMatrixIT * vec4(inNormal, 0.0)); + // matIndex = inMatID; + + gl_Position = ubo.proj * ubo.view * vec4(worldPos, 1.0); +} diff --git a/ray_tracing_advanced_compilation/shaders/wavefront.glsl b/ray_tracing_advanced_compilation/shaders/wavefront.glsl new file mode 100644 index 0000000..b4a58e4 --- /dev/null +++ b/ray_tracing_advanced_compilation/shaders/wavefront.glsl @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2019-2021, NVIDIA CORPORATION. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-FileCopyrightText: Copyright (c) 2019-2021 NVIDIA CORPORATION + * SPDX-License-Identifier: Apache-2.0 + */ + +struct Vertex +{ + vec3 pos; + vec3 nrm; + vec3 color; + vec2 texCoord; +}; + +struct WaveFrontMaterial +{ + vec3 ambient; + vec3 diffuse; + vec3 specular; + vec3 transmittance; + vec3 emission; + float shininess; + float ior; // index of refraction + float dissolve; // 1 == opaque; 0 == fully transparent + int illum; // illumination model (see http://www.fileformat.info/format/material/) + int textureId; +}; + +struct sceneDesc +{ + int objId; + int txtOffset; + mat4 transfo; + mat4 transfoIT; +}; + + +vec3 computeDiffuse(WaveFrontMaterial mat, vec3 lightDir, vec3 normal) +{ + // Lambertian + float dotNL = max(dot(normal, lightDir), 0.0); + vec3 c = mat.diffuse * dotNL; + if(mat.illum >= 1) + c += mat.ambient; + return c; +} + +vec3 computeSpecular(WaveFrontMaterial mat, vec3 viewDir, vec3 lightDir, vec3 normal) +{ + if(mat.illum < 2) + return vec3(0); + + // Compute specular only if not in shadow + const float kPi = 3.14159265; + const float kShininess = max(mat.shininess, 4.0); + + // Specular + const float kEnergyConservation = (2.0 + kShininess) / (2.0 * kPi); + vec3 V = normalize(-viewDir); + vec3 R = reflect(-lightDir, normal); + float specular = kEnergyConservation * pow(max(dot(V, R), 0.0), kShininess); + + return vec3(mat.specular * specular); +}