1400 lines
63 KiB
C++
1400 lines
63 KiB
C++
/*
|
|
* 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 <sstream>
|
|
|
|
|
|
#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<std::string> 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<float>(m_size.height);
|
|
CameraMatrices hostUBO = {};
|
|
hostUBO.view = getViewMatrix();
|
|
hostUBO.proj = getProjMatrix();
|
|
// 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<uint32_t>(m_textures.size());
|
|
auto nbObj = static_cast<uint32_t>(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<VkWriteDescriptorSet> 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<VkDescriptorBufferInfo> dbiMat;
|
|
std::vector<VkDescriptorBufferInfo> dbiMatIdx;
|
|
std::vector<VkDescriptorBufferInfo> dbiVert;
|
|
std::vector<VkDescriptorBufferInfo> 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<VkDescriptorImageInfo> 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<uint32_t>(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<std::string> 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<uint32_t>(offsetof(VertexObj, pos))},
|
|
{1, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(VertexObj, nrm))},
|
|
{2, 0, VK_FORMAT_R32G32B32_SFLOAT, static_cast<uint32_t>(offsetof(VertexObj, color))},
|
|
{3, 0, VK_FORMAT_R32G32_SFLOAT, static_cast<uint32_t>(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<uint32_t>(m_objModel.size());
|
|
instance.transform = transform;
|
|
instance.transformIT = nvmath::transpose(nvmath::invert(transform));
|
|
instance.txtOffset = static_cast<uint32_t>(m_textures.size());
|
|
|
|
ObjModel model;
|
|
model.nbIndices = static_cast<uint32_t>(loader.m_indices.size());
|
|
model.nbVertices = static_cast<uint32_t>(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);
|
|
}
|
|
|
|
// 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_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<std::string>& 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<uint8_t, 4> 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<stbi_uc, 4> color{255u, 0u, 255u, 255u};
|
|
|
|
stbi_uc* pixels = stbi_pixels;
|
|
// Handle failure
|
|
if(!stbi_pixels)
|
|
{
|
|
texWidth = texHeight = 1;
|
|
texChannels = 4;
|
|
pixels = reinterpret_cast<stbi_uc*>(color.data());
|
|
}
|
|
|
|
VkDeviceSize bufferSize = static_cast<uint64_t>(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_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(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<VkImageView> 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<float>(m_size.width) / static_cast<float>(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_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;
|
|
}
|
|
|
|
// Tesselate a sphere as a list of triangles; return its
|
|
// vertices and indices as reference arguments.
|
|
void HelloVulkan::fillLanternVerts(std::vector<nvmath::vec3f>& vertices, std::vector<uint32_t>& 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<Triangle> 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<Triangle> new_triangles;
|
|
for(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 tesselated triangles with indices in the indices vector.
|
|
for(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<nvmath::vec3f> vertices;
|
|
std::vector<uint32_t> 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<nvvk::RaytracingBuilderKHR::BlasInput> 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 instance or lantern in m_objInstance or
|
|
// m_lanterns respectively.
|
|
void HelloVulkan::createTopLevelAS()
|
|
{
|
|
assert(m_lanternCount == 0);
|
|
m_lanternCount = m_lanterns.size();
|
|
|
|
std::vector<nvvk::RaytracingBuilderKHR::Instance> tlas;
|
|
tlas.reserve(m_objInstance.size() + m_lanternCount);
|
|
|
|
// Add the OBJ instances.
|
|
for(uint32_t i = 0; i < static_cast<uint32_t>(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 OBJ
|
|
rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
|
|
tlas.emplace_back(rayInst);
|
|
}
|
|
|
|
// Add lantern instances.
|
|
for(int i = 0; i < static_cast<int>(m_lanterns.size()); ++i)
|
|
{
|
|
nvvk::RaytracingBuilderKHR::Instance lanternInstance;
|
|
lanternInstance.transform = nvmath::translation_mat4(m_lanterns[i].position);
|
|
lanternInstance.instanceCustomId = i;
|
|
lanternInstance.blasId = uint32_t(m_lanternBlasId);
|
|
lanternInstance.hitGroupId = 1; // Next hit group is for lanterns.
|
|
lanternInstance.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
|
|
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(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
|
|
// Lantern buffer (binding = 2)
|
|
m_rtDescSetLayoutBind.addBinding(2, 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<VkWriteDescriptorSet> writes;
|
|
writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 0, &descASInfo));
|
|
writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 1, &imageInfo));
|
|
writes.emplace_back(m_rtDescSetLayoutBind.makeWrite(m_rtDescSet, 2, &lanternBufferInfo));
|
|
vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(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);
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// 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<VkPipelineShaderStageCreateInfo, eShaderGroupCount> 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(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<VkDescriptorSetLayout> rtDescSetLayouts = {m_rtDescSetLayout, m_descSetLayout};
|
|
pipelineLayoutCreateInfo.setLayoutCount = static_cast<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint8_t> 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").c_str());
|
|
|
|
// Map the SBT buffer and write in the handles.
|
|
void* mapped = m_alloc.map(m_rtSBTBuffer);
|
|
auto* pData = reinterpret_cast<uint8_t*>(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<VkWriteDescriptorSet> writes;
|
|
writes.emplace_back(m_lanternIndirectDescSetLayoutBind.makeWrite(m_lanternIndirectDescSet, 0, &lanternBufferInfo));
|
|
vkUpdateDescriptorSets(m_device, static_cast<uint32_t>(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<uint32_t>(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<LanternIndirectEntry> 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::mat4 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_rtPushConstants.clearColor = clearColor;
|
|
m_rtPushConstants.lightPosition = m_pushConstant.lightPosition;
|
|
m_rtPushConstants.lightIntensity = m_pushConstant.lightIntensity;
|
|
m_rtPushConstants.lightType = m_pushConstant.lightType;
|
|
m_rtPushConstants.lanternPassNumber = -1; // Global non-lantern pass
|
|
m_rtPushConstants.screenX = m_size.width;
|
|
m_rtPushConstants.screenY = m_size.height;
|
|
m_rtPushConstants.lanternDebug = m_lanternDebug;
|
|
|
|
std::vector<VkDescriptorSet> 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);
|
|
|
|
|
|
// 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<Stride, 4> 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<int>(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_rtPushConstants.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(RtPushConstant), &m_rtPushConstants);
|
|
|
|
|
|
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);
|
|
}
|