/* * 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 #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" #include "nvvk/buffers_vk.hpp" extern std::vector defaultSearchPaths; //-------------------------------------------------------------------------------------------------- // 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); GlobalUniforms hostUBO = {}; const auto& view = CameraManip.getMatrix(); const auto& proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f); // proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK). hostUBO.viewProj = proj * view; hostUBO.viewInverse = nvmath::invert(view); hostUBO.projInverse = nvmath::invert(proj); // UBO on the device, and what stages access it. VkBuffer deviceUBO = m_bGlobals.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_bGlobals.buffer, 0, sizeof(GlobalUniforms), &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()); // Camera matrices m_descSetLayoutBind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Obj descriptions m_descSetLayoutBind.addBinding(SceneBindings::eObjDescs, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // Textures m_descSetLayoutBind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTxt, VK_SHADER_STAGE_FRAGMENT_BIT | 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_bGlobals.buffer, 0, VK_WHOLE_SIZE}; writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif)); VkDescriptorBufferInfo dbiSceneDesc{m_bObjDesc.buffer, 0, VK_WHOLE_SIZE}; writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eObjDescs, &dbiSceneDesc)); // All texture samplers std::vector diit; for(auto& texture : m_textures) { diit.emplace_back(texture.descriptor); } writes.emplace_back(m_descSetLayoutBind.makeWriteArray(m_descSet, SceneBindings::eTextures, 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(PushConstantRaster)}; // 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); } 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 flag = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; VkBufferUsageFlags rayTracingFlags = // used also for building acceleration structures flag | VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT; model.vertexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_vertices, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | rayTracingFlags); model.indexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_indices, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | rayTracingFlags); model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | flag); // Creates all textures found and find the offset for this model auto txtOffset = static_cast(m_textures.size()); createTextureImages(cmdBuf, loader.m_textures); cmdBufGet.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); std::string objNb = std::to_string(m_objModel.size()); m_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb))); m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb))); m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb))); m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb))); // Keeping transformation matrix of the instance ObjInstance instance; instance.transform = transform; instance.objIndex = static_cast(m_objModel.size()); m_instances.push_back(instance); // Creating information for device access ObjDesc desc; desc.txtOffset = txtOffset; desc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); desc.indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); desc.materialAddress = nvvk::getBufferDeviceAddress(m_device, model.matColorBuffer.buffer); desc.materialIndexAddress = nvvk::getBufferDeviceAddress(m_device, model.matIndexBuffer.buffer); // Keeping the obj host model and device description m_objModel.emplace_back(model); m_objDesc.emplace_back(desc); } // Add a light-emitting colored lantern to the scene. May only be called before TLAS build. void HelloVulkan::addLantern(nvmath::vec3f pos, nvmath::vec3f color, float brightness, float radius) { assert(m_lanternCount == 0); // Indicates TLAS build has not happened yet. m_lanterns.push_back({pos, color, brightness, radius}); } //-------------------------------------------------------------------------------------------------- // Creating the uniform buffer holding the camera matrices // - Buffer is host visible // void HelloVulkan::createUniformBuffer() { m_bGlobals = m_alloc.createBuffer(sizeof(GlobalUniforms), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); m_debug.setObjectName(m_bGlobals.buffer, "Globals"); } //-------------------------------------------------------------------------------------------------- // Create a storage buffer containing the description of the scene elements // - Which geometry is used by which instance // - Transformation // - Offset for texture // void HelloVulkan::createObjDescriptionBuffer() { nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex); auto cmdBuf = cmdGen.createCommandBuffer(); m_bObjDesc = m_alloc.createBuffer(cmdBuf, m_objDesc, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT); cmdGen.submitAndWait(cmdBuf); m_alloc.finalizeAndReleaseStaging(); m_debug.setObjectName(m_bObjDesc.buffer, "ObjDescs"); } //-------------------------------------------------------------------------------------------------- // 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_bGlobals); m_alloc.destroy(m_bObjDesc); 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_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); m_alloc.destroy(m_rtSBTBuffer); vkDestroyDescriptorPool(m_device, m_lanternIndirectDescPool, nullptr); vkDestroyDescriptorSetLayout(m_device, m_lanternIndirectDescSetLayout, nullptr); vkDestroyPipeline(m_device, m_lanternIndirectCompPipeline, nullptr); vkDestroyPipelineLayout(m_device, m_lanternIndirectCompPipelineLayout, nullptr); m_alloc.destroy(m_lanternIndirectBuffer); m_alloc.destroy(m_lanternVertexBuffer); m_alloc.destroy(m_lanternIndexBuffer); 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(const HelloVulkan::ObjInstance& inst : m_instances) { auto& model = m_objModel[inst.objIndex]; m_pcRaster.objIndex = inst.objIndex; // Telling which object is drawn m_pcRaster.modelMatrix = inst.transform; vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(PushConstantRaster), &m_pcRaster); 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); } //-------------------------------------------------------------------------------------------------- // 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. VkDeviceAddress vertexAddress = nvvk::getBufferDeviceAddress(m_device, model.vertexBuffer.buffer); VkDeviceAddress indexAddress = nvvk::getBufferDeviceAddress(m_device, model.indexBuffer.buffer); 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_R32G32B32_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; } // Tesselate a sphere as a list of triangles; return its // vertices and indices as reference arguments. void HelloVulkan::fillLanternVerts(std::vector& vertices, std::vector& indices) { // Create a spherical lantern model by recursively tesselating an octahedron. struct VertexIndex { nvmath::vec3f vertex; uint32_t index; // Keep track of this vert's _eventual_ index in vertices. }; struct Triangle { VertexIndex vert0, vert1, vert2; }; VertexIndex posX{{m_lanternModelRadius, 0, 0}, 0}; VertexIndex negX{{-m_lanternModelRadius, 0, 0}, 1}; VertexIndex posY{{0, m_lanternModelRadius, 0}, 2}; VertexIndex negY{{0, -m_lanternModelRadius, 0}, 3}; VertexIndex posZ{{0, 0, m_lanternModelRadius}, 4}; VertexIndex negZ{{0, 0, -m_lanternModelRadius}, 5}; uint32_t vertexCount = 6; // Initial triangle list is octahedron. std::vector triangles{{posX, posY, posZ}, {posX, posY, negZ}, {posX, negY, posZ}, {posX, negY, negZ}, {negX, posY, posZ}, {negX, posY, negZ}, {negX, negY, posZ}, {negX, negY, negZ}}; // Recursion: every iteration, convert the current model to a new // model by breaking each triangle into 4 triangles. for(int recursions = 0; recursions < 3; ++recursions) { std::vector new_triangles; for(const Triangle& t : triangles) { // Split each of three edges in half, then fixup the // length of the midpoint to match m_lanternModelRadius. // Record the index the new vertices will eventually have in vertices. VertexIndex v01{m_lanternModelRadius * nvmath::normalize(t.vert0.vertex + t.vert1.vertex), vertexCount++}; VertexIndex v12{m_lanternModelRadius * nvmath::normalize(t.vert1.vertex + t.vert2.vertex), vertexCount++}; VertexIndex v02{m_lanternModelRadius * nvmath::normalize(t.vert0.vertex + t.vert2.vertex), vertexCount++}; // Old triangle becomes 4 new triangles. new_triangles.push_back({t.vert0, v01, v02}); new_triangles.push_back({t.vert1, v01, v12}); new_triangles.push_back({t.vert2, v02, v12}); new_triangles.push_back({v01, v02, v12}); } triangles = std::move(new_triangles); } vertices.resize(vertexCount); indices.clear(); indices.reserve(triangles.size() * 3); // Write out the vertices to the vertices vector, and // connect the tessellated triangles with indices in the indices vector. for(const Triangle& t : triangles) { vertices[t.vert0.index] = t.vert0.vertex; vertices[t.vert1.index] = t.vert1.vertex; vertices[t.vert2.index] = t.vert2.vertex; indices.push_back(t.vert0.index); indices.push_back(t.vert1.index); indices.push_back(t.vert2.index); } } // Create the BLAS storing triangles for the spherical lantern model. This fills in // m_lanternVertexBuffer: packed VkBuffer of vec3 // m_lanternIndexBuffer: packed VkBuffer of 32-bit indices // m_lanternBlasInput: BLAS for spherical lantern // // NOTE: A more elegant way to do this is to use a procedural hit group instead, // then, this BLAS can just be one AABB. I wanted to avoid introducing the new // concept of intersection shaders here. void HelloVulkan::createLanternModel() { std::vector vertices; std::vector indices; fillLanternVerts(vertices, indices); // Upload vertex and index data to buffers. auto usageFlags = VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; auto memFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; auto vertexBytes = vertices.size() * sizeof vertices[0]; m_lanternVertexBuffer = m_alloc.createBuffer(vertexBytes, usageFlags, memFlags); void* map = m_alloc.map(m_lanternVertexBuffer); memcpy(map, vertices.data(), vertexBytes); m_alloc.unmap(m_lanternVertexBuffer); auto indexBytes = indices.size() * sizeof indices[0]; m_lanternIndexBuffer = m_alloc.createBuffer(indexBytes, usageFlags, memFlags); map = m_alloc.map(m_lanternIndexBuffer); memcpy(map, indices.data(), indexBytes); m_alloc.unmap(m_lanternIndexBuffer); // Package vertex and index buffers as BlasInput. VkDeviceAddress vertexAddress = nvvk::getBufferDeviceAddress(m_device, m_lanternVertexBuffer.buffer); VkDeviceAddress indexAddress = nvvk::getBufferDeviceAddress(m_device, m_lanternIndexBuffer.buffer); auto maxPrimitiveCount = uint32_t(indices.size() / 3); // Describe buffer as packed array of float vec3. VkAccelerationStructureGeometryTrianglesDataKHR triangles{VK_STRUCTURE_TYPE_ACCELERATION_STRUCTURE_GEOMETRY_TRIANGLES_DATA_KHR}; triangles.vertexFormat = VK_FORMAT_R32G32B32_SFLOAT; // vec3 vertex position data. triangles.vertexData.deviceAddress = vertexAddress; triangles.vertexStride = sizeof(nvmath::vec3f); // 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 = uint32_t(vertices.size()); // 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 m_lanternBlasInput.asGeometry.emplace_back(asGeom); m_lanternBlasInput.asBuildOffsetInfo.emplace_back(offset); } //-------------------------------------------------------------------------------------------------- // // Build the array of BLAS in m_rtBuilder. There are `m_objModel.size() + 1`-many BLASes. // The first `m_objModel.size()` are used for OBJ model BLASes, and the last one // is used for the lanterns (model generated at runtime). void HelloVulkan::createBottomLevelAS() { // BLAS - Storing each primitive in a geometry std::vector allBlas; allBlas.reserve(m_objModel.size() + 1); // Add OBJ models. 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); } // Add lantern model. createLanternModel(); m_lanternBlasId = allBlas.size(); allBlas.emplace_back(m_lanternBlasInput); m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } // Build the TLAS in m_rtBuilder. Requires that the BLASes were already built and // that all ObjInstance and lanterns have been added. One instance with hitGroupId=0 // is created for every OBJ instance, and one instance with hitGroupId=1 for each lantern. // // gl_InstanceCustomIndexEXT will be the index of the obj id or lantern in m_instances or // m_lanterns respectively. //-------------------------------------------------------------------------------------------------- // // void HelloVulkan::createTopLevelAS() { assert(m_lanternCount == 0); m_lanternCount = m_lanterns.size(); std::vector tlas; tlas.reserve(m_instances.size() + m_lanternCount); // Add the OBJ instances. for(const HelloVulkan::ObjInstance& inst : m_instances) { VkAccelerationStructureInstanceKHR rayInst{}; rayInst.transform = nvvk::toTransformMatrixKHR(inst.transform); // Position of the instance rayInst.instanceCustomIndex = inst.objIndex; // gl_InstanceCustomIndexEXT rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(inst.objIndex); rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; rayInst.mask = 0xFF; // Only be hit if rayMask & instance.mask != 0 rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects tlas.emplace_back(rayInst); } // Add lantern instances. for(int i = 0; i < static_cast(m_lanterns.size()); ++i) { VkAccelerationStructureInstanceKHR lanternInstance; lanternInstance.transform = nvvk::toTransformMatrixKHR(nvmath::translation_mat4(m_lanterns[i].position)); lanternInstance.instanceCustomIndex = i; lanternInstance.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(uint32_t(m_lanternBlasId)); lanternInstance.instanceShaderBindingTableRecordOffset = 1; // Next hit group is for lanterns. lanternInstance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR; lanternInstance.mask = 0xFF; tlas.emplace_back(lanternInstance); } m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR); } //-------------------------------------------------------------------------------------------------- // This descriptor set holds the Acceleration structure, output image, and lanterns array buffer. // void HelloVulkan::createRtDescriptorSet() { // Top-level acceleration structure, usable by both the ray generation and the closest hit (to // shoot shadow rays) m_rtDescSetLayoutBind.addBinding(RtxBindings::eTlas, VK_DESCRIPTOR_TYPE_ACCELERATION_STRUCTURE_KHR, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); // TLAS m_rtDescSetLayoutBind.addBinding(RtxBindings::eOutImage, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR); // Output image // Lantern buffer m_rtDescSetLayoutBind.addBinding(eLanterns, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_BIT_KHR | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR); assert(m_lanternCount > 0); 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}; VkDescriptorBufferInfo lanternBufferInfo{m_lanternIndirectBuffer.buffer, 0, m_lanternCount * sizeof(LanternIndirectEntry)}; std::vector writes; writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eTlas, &descASInfo)); writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, RtxBindings::eOutImage, &imageInfo)); writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, eLanterns, &lanternBufferInfo)); 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, RtxBindings::eOutImage, &imageInfo); vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr); } //-------------------------------------------------------------------------------------------------- // Pipeline for the ray tracer: all shaders, raygen, chit, miss // // Shader list: // // 0 ====== Ray Generation Shaders ===================================================== // // Raygen shader: Ray generation shader. Casts primary rays from camera to scene. // // 1 ====== Miss Shaders =============================================================== // // Miss shader 0: Miss shader when casting primary rays. Fill in clear color. // // 2 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // // Miss shader 1: Miss shader when casting shadow rays towards main light. // Reports no shadow. // // 3 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // // Miss shader 2: Miss shader when casting shadow rays towards a lantern. // Reports no lantern hit (-1). // // 4 ====== Hit Groups for Primary Rays (sbtRecordOffset=0) ============================ // // chit shader 0: Closest hit shader for primary rays hitting OBJ instances // (hitGroupId=0). Casts shadow ray (to sky light or to lantern, // depending on pass number) and returns specular // and diffuse light to add to output image. // // 5 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // // chit shader 1: Closest hit shader for primary rays hitting lantern instances // (hitGroupId=1). Returns color value to replace the current // image pixel color with (lanterns are self-illuminating). // // 6 - - - - Hit Groups for Lantern Shadow Rays (sbtRecordOffset=2) - - - - - - - - - - - // // chit shader 2: Closest hit shader for OBJ instances hit when casting shadow // rays to a lantern. Returns -1 to report that the shadow ray // failed to reach the targetted lantern. // // 7 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // // chit shader 3: Closest hit shader for lantern instances hit when casting shadow // rays to a lantern. Returns the gl_CustomInstanceIndexEXT [lantern // number] of the lantern hit. // // 8 ===================================================================================== void HelloVulkan::createRtPipeline() { enum StageIndices { eRaygen, eMiss, eMissShd, eMissLantern, eClosestHit, eClosestHitLantern, eClosestHitLanternShdObj, eClosestHitLanternShd, eShaderGroupCount }; // All stages 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)); stage.stage = VK_SHADER_STAGE_RAYGEN_BIT_KHR; stages[eRaygen] = stage; // Miss shader 0 invoked when a primary ray doesn't hit geometry. Fills in clear color. stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rmiss.spv", true, defaultSearchPaths, true)); stage.stage = VK_SHADER_STAGE_MISS_BIT_KHR; stages[eMiss] = stage; // Miss shader 1 is invoked when a shadow ray (for the main scene light) // 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)); stage.stage = VK_SHADER_STAGE_MISS_BIT_KHR; stages[eMissShd] = stage; // Miss shader 2 is invoked when a shadow ray for lantern lighting misses the // lantern. It shouldn't be invoked, but I include it just in case. stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/lanternShadow.rmiss.spv", true, defaultSearchPaths, true)); stage.stage = VK_SHADER_STAGE_MISS_BIT_KHR; stages[eMissLantern] = stage; // OBJ Primary Ray Hit Group - Closest Hit stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytrace.rchit.spv", true, defaultSearchPaths, true)); stage.stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; stages[eClosestHit] = stage; // Lantern Primary Ray Hit Group stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/lantern.rchit.spv", true, defaultSearchPaths, true)); stage.stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; stages[eClosestHitLantern] = stage; // OBJ Lantern Shadow Ray Hit Group stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/lanternShadowObj.rchit.spv", true, defaultSearchPaths, true)); stage.stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; stages[eClosestHitLanternShdObj] = stage; // Lantern Lantern Shadow Ray Hit Group stage.module = nvvk::createShaderModule(m_device, nvh::loadFile("spv/lanternShadowLantern.rchit.spv", true, defaultSearchPaths, true)); stage.stage = VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR; stages[eClosestHitLanternShd] = 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 = eMissShd; m_rtShaderGroups.push_back(group); // Lantern Shadow Miss group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR; group.generalShader = eMissLantern; m_rtShaderGroups.push_back(group); // closest hit shader group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_TRIANGLES_HIT_GROUP_KHR; group.generalShader = VK_SHADER_UNUSED_KHR; group.closestHitShader = eClosestHit; m_rtShaderGroups.push_back(group); group.closestHitShader = eClosestHitLantern; m_rtShaderGroups.push_back(group); group.closestHitShader = eClosestHitLanternShdObj; m_rtShaderGroups.push_back(group); group.closestHitShader = eClosestHitLanternShd; m_rtShaderGroups.push_back(group); // 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(PushConstantRay)}; 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); // 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; vkCreateRayTracingPipelinesKHR(m_device, {}, {}, 1, &rayPipelineInfo, nullptr, &m_rtPipeline); for(auto& s : stages) vkDestroyShaderModule(m_device, s.module, nullptr); } //-------------------------------------------------------------------------------------------------- // The Shader Binding Table (SBT) // - getting all shader handles and write them in a SBT buffer // - Besides exception, this could be always done like this // See how the SBT buffer is used in run() // void HelloVulkan::createRtShaderBindingTable() { auto groupCount = static_cast(m_rtShaderGroups.size()); assert(groupCount == 8 && "Update Comment"); // 8 shaders: raygen, 3 miss, 4 chit uint32_t groupHandleSize = m_rtProperties.shaderGroupHandleSize; // Size of a program identifier // Compute the actual size needed per SBT entry (round-up to alignment needed). uint32_t groupSizeAligned = nvh::align_up(groupHandleSize, m_rtProperties.shaderGroupBaseAlignment); // Bytes needed for the SBT. uint32_t sbtSize = groupCount * groupSizeAligned; // Fetch all the shader handles used in the pipeline. This is opaque data, // so we store it in a vector of bytes. std::vector shaderHandleStorage(sbtSize); auto result = vkGetRayTracingShaderGroupHandlesKHR(m_device, m_rtPipeline, 0, groupCount, sbtSize, shaderHandleStorage.data()); assert(result == VK_SUCCESS); // Allocate a buffer for storing the SBT. Give it a debug name for NSight. m_rtSBTBuffer = m_alloc.createBuffer(sbtSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_SHADER_BINDING_TABLE_BIT_KHR, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); m_debug.setObjectName(m_rtSBTBuffer.buffer, std::string("SBT")); // Map the SBT buffer and write in the handles. void* mapped = m_alloc.map(m_rtSBTBuffer); auto* pData = reinterpret_cast(mapped); for(uint32_t g = 0; g < groupCount; g++) { memcpy(pData, shaderHandleStorage.data() + g * groupHandleSize, groupHandleSize); pData += groupSizeAligned; } m_alloc.unmap(m_rtSBTBuffer); m_alloc.finalizeAndReleaseStaging(); } //-------------------------------------------------------------------------------------------------- // The compute shader just needs read/write access to the buffer of LanternIndirectEntry. void HelloVulkan::createLanternIndirectDescriptorSet() { // Lantern buffer (binding = 0) m_lanternIndirectDescSetLayoutBind.addBinding(0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, VK_SHADER_STAGE_COMPUTE_BIT); m_lanternIndirectDescPool = m_lanternIndirectDescSetLayoutBind.createPool(m_device); m_lanternIndirectDescSetLayout = m_lanternIndirectDescSetLayoutBind.createLayout(m_device); VkDescriptorSetAllocateInfo allocateInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; allocateInfo.descriptorPool = m_lanternIndirectDescPool; allocateInfo.descriptorSetCount = 1; allocateInfo.pSetLayouts = &m_lanternIndirectDescSetLayout; vkAllocateDescriptorSets(m_device, &allocateInfo, &m_lanternIndirectDescSet); assert(m_lanternIndirectBuffer.buffer); VkDescriptorBufferInfo lanternBufferInfo{m_lanternIndirectBuffer.buffer, 0, m_lanternCount * sizeof(LanternIndirectEntry)}; std::vector writes; writes.emplace_back(m_lanternIndirectDescSetLayoutBind.makeWrite(m_lanternIndirectDescSet, 0, &lanternBufferInfo)); vkUpdateDescriptorSets(m_device, static_cast(writes.size()), writes.data(), 0, nullptr); } // Create compute pipeline used to fill m_lanternIndirectBuffer with parameters // for dispatching the correct number of ray traces. void HelloVulkan::createLanternIndirectCompPipeline() { // Compile compute shader and package as stage. VkShaderModule computeShader = nvvk::createShaderModule(m_device, nvh::loadFile("spv/lanternIndirect.comp.spv", true, defaultSearchPaths, true)); VkPipelineShaderStageCreateInfo stageInfo{VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO}; stageInfo.stage = VK_SHADER_STAGE_COMPUTE_BIT; stageInfo.module = computeShader; stageInfo.pName = "main"; // Set up push constant and pipeline layout. constexpr auto pushSize = static_cast(sizeof(m_lanternIndirectPushConstants)); VkPushConstantRange pushCRange = {VK_SHADER_STAGE_COMPUTE_BIT, 0, pushSize}; static_assert(pushSize <= 128, "Spec guarantees only 128 byte push constant"); VkPipelineLayoutCreateInfo layoutInfo{VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; layoutInfo.setLayoutCount = 1; layoutInfo.pSetLayouts = &m_lanternIndirectDescSetLayout; layoutInfo.pushConstantRangeCount = 1; layoutInfo.pPushConstantRanges = &pushCRange; vkCreatePipelineLayout(m_device, &layoutInfo, nullptr, &m_lanternIndirectCompPipelineLayout); // Create compute pipeline. VkComputePipelineCreateInfo pipelineInfo{VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; pipelineInfo.stage = stageInfo; pipelineInfo.layout = m_lanternIndirectCompPipelineLayout; vkCreateComputePipelines(m_device, {}, 1, &pipelineInfo, nullptr, &m_lanternIndirectCompPipeline); vkDestroyShaderModule(m_device, computeShader, nullptr); } // Allocate the buffer used to pass lantern info + ray trace indirect parameters to ray tracer. // Fill in the lantern info from m_lanterns (indirect info is filled per-frame on device // using a compute shader). Must be called only after TLAS build. // // The buffer is an array of LanternIndirectEntry, entry i is for m_lanterns[i]. void HelloVulkan::createLanternIndirectBuffer() { assert(m_lanternCount > 0); assert(m_lanternCount == m_lanterns.size()); // m_alloc behind the scenes uses cmdBuf to transfer data to the buffer. nvvk::CommandPool cmdBufGet(m_device, m_graphicsQueueIndex); VkCommandBuffer cmdBuf = cmdBufGet.createCommandBuffer(); using Usage = VkBufferUsageFlagBits; m_lanternIndirectBuffer = m_alloc.createBuffer(sizeof(LanternIndirectEntry) * m_lanternCount, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); std::vector entries(m_lanternCount); for(size_t i = 0; i < m_lanternCount; ++i) entries[i].lantern = m_lanterns[i]; vkCmdUpdateBuffer(cmdBuf, m_lanternIndirectBuffer.buffer, 0, entries.size() * sizeof entries[0], entries.data()); cmdBufGet.submitAndWait(cmdBuf); } //-------------------------------------------------------------------------------------------------- // Ray Tracing the scene // // The raytracing is split into multiple passes: // // First pass fills in the initial values for every pixel in the output image. // Illumination and shadow rays come from the main light. // // Subsequently, one lantern pass is run for each lantern in the scene. We run // a compute shader to calculate a bounding scissor rectangle for each lantern's light // effect. This is stored in m_lanternIndirectBuffer. Then an indirect trace rays command // is run for every lantern within its scissor rectangle. The lanterns' light // contribution is additively blended into the output image. void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const nvmath::vec4f& clearColor) { // Before tracing rays, we need to dispatch the compute shaders that // fill in the ray trace indirect parameters for each lantern pass. // First, barrier before, ensure writes aren't visible to previous frame. VkBufferMemoryBarrier bufferBarrier{VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER}; bufferBarrier.srcAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT; bufferBarrier.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT; bufferBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufferBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; bufferBarrier.buffer = m_lanternIndirectBuffer.buffer; bufferBarrier.offset = 0; bufferBarrier.size = m_lanternCount * sizeof m_lanterns[0]; vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, // VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, // VkDependencyFlags(0), // 0, nullptr, 1, &bufferBarrier, 0, nullptr); // Bind compute shader, update push constant and descriptors, dispatch compute. vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE, m_lanternIndirectCompPipeline); nvmath::mat4f view = getViewMatrix(); m_lanternIndirectPushConstants.viewRowX = view.row(0); m_lanternIndirectPushConstants.viewRowY = view.row(1); m_lanternIndirectPushConstants.viewRowZ = view.row(2); m_lanternIndirectPushConstants.proj = getProjMatrix(); m_lanternIndirectPushConstants.nearZ = nearZ; m_lanternIndirectPushConstants.screenX = m_size.width; m_lanternIndirectPushConstants.screenY = m_size.height; m_lanternIndirectPushConstants.lanternCount = int32_t(m_lanternCount); vkCmdPushConstants(cmdBuf, m_lanternIndirectCompPipelineLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(LanternIndirectPushConstants), &m_lanternIndirectPushConstants); vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_COMPUTE, m_lanternIndirectCompPipelineLayout, 0, 1, &m_lanternIndirectDescSet, 0, nullptr); vkCmdDispatch(cmdBuf, 1, 1, 1); // Ensure compute results are visible when doing indirect ray trace. bufferBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; bufferBarrier.dstAccessMask = VK_ACCESS_INDIRECT_COMMAND_READ_BIT; vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, // VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT, // VkDependencyFlags(0), // 0, nullptr, 1, &bufferBarrier, 0, nullptr); // Now move on to the actual ray tracing. m_debug.beginLabel(cmdBuf, "Ray trace"); // Initialize push constant values m_pcRay.clearColor = clearColor; m_pcRay.lightPosition = m_pcRaster.lightPosition; m_pcRay.lightIntensity = m_pcRaster.lightIntensity; m_pcRay.lightType = m_pcRaster.lightType; m_pcRay.lanternPassNumber = -1; // Global non-lantern pass m_pcRay.screenX = m_size.width; m_pcRay.screenY = m_size.height; m_pcRay.lanternDebug = m_lanternDebug; 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(PushConstantRay), &m_pcRay); // Size of a program identifier uint32_t groupSize = nvh::align_up(m_rtProperties.shaderGroupHandleSize, m_rtProperties.shaderGroupBaseAlignment); uint32_t groupStride = groupSize; VkDeviceAddress sbtAddress = nvvk::getBufferDeviceAddress(m_device, m_rtSBTBuffer.buffer); using Stride = VkStridedDeviceAddressRegionKHR; std::array strideAddresses{Stride{sbtAddress + 0u * groupSize, groupStride, groupSize * 1}, // raygen Stride{sbtAddress + 1u * groupSize, groupStride, groupSize * 3}, // miss Stride{sbtAddress + 4u * groupSize, groupStride, groupSize * 4}, // hit Stride{0u, 0u, 0u}}; // callable // First pass, illuminate scene with global light. vkCmdTraceRaysKHR(cmdBuf, &strideAddresses[0], &strideAddresses[1], &strideAddresses[2], &strideAddresses[3], m_size.width, m_size.height, 1); // Lantern passes, ensure previous pass completed, then add light contribution from each lantern. for(int i = 0; i < static_cast(m_lanternCount); ++i) { // Barrier to ensure previous pass finished. VkImage offscreenImage{m_offscreenColor.image}; VkImageSubresourceRange colorRange{VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS}; VkImageMemoryBarrier imageBarrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER}; imageBarrier.oldLayout = VK_IMAGE_LAYOUT_GENERAL; imageBarrier.newLayout = VK_IMAGE_LAYOUT_GENERAL; imageBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; imageBarrier.image = offscreenImage; imageBarrier.subresourceRange = colorRange; imageBarrier.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; imageBarrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; vkCmdPipelineBarrier(cmdBuf, VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, // VK_PIPELINE_STAGE_RAY_TRACING_SHADER_BIT_KHR, // VkDependencyFlags(0), // 0, nullptr, 0, nullptr, 1, &imageBarrier); // Set lantern pass number. m_pcRay.lanternPassNumber = i; 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(PushConstantRay), &m_pcRay); VkDeviceAddress indirectDeviceAddress = nvvk::getBufferDeviceAddress(m_device, m_lanternIndirectBuffer.buffer) + i * sizeof(LanternIndirectEntry); // Execute lantern pass. vkCmdTraceRaysIndirectKHR(cmdBuf, &strideAddresses[0], &strideAddresses[1], // &strideAddresses[2], &strideAddresses[3], // indirectDeviceAddress); } m_debug.endLabel(cmdBuf); }