940 lines
40 KiB
C++
940 lines
40 KiB
C++
/*
|
|
* Copyright (c) 2014-2023, 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>
|
|
#include <iostream>
|
|
#include <utk/samplers/SamplerStep_Custom.hpp>
|
|
#include <utk/utils/PointsetIO.hpp>
|
|
|
|
|
|
#define TINYGLTF_IMPLEMENTATION
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
#define STB_IMAGE_WRITE_IMPLEMENTATION
|
|
|
|
|
|
#include "hello_vulkan.h"
|
|
#include "nvh/cameramanipulator.hpp"
|
|
#include "nvh/fileoperations.hpp"
|
|
#include "nvh/gltfscene.hpp"
|
|
#include "nvh/nvprint.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 "nvh/alignment.hpp"
|
|
#include "nvvk/buffers_vk.hpp"
|
|
|
|
extern std::vector<std::string> 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<float>(m_size.height);
|
|
GlobalUniforms hostUBO = {};
|
|
const auto& view = CameraManip.getMatrix();
|
|
glm::mat4 proj = glm::perspectiveRH_ZO(glm::radians(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 = glm::inverse(view);
|
|
hostUBO.projInverse = glm::inverse(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& bind = m_descSetLayoutBind;
|
|
// Camera matrices
|
|
bind.addBinding(SceneBindings::eGlobals, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1,
|
|
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_RAYGEN_BIT_KHR);
|
|
// Array of textures
|
|
auto nbTextures = static_cast<uint32_t>(m_textures.size());
|
|
bind.addBinding(SceneBindings::eTextures, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, nbTextures,
|
|
VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR);
|
|
// Scene buffers
|
|
bind.addBinding(SceneBindings::eSceneDesc, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1,
|
|
VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT | VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR
|
|
| VK_SHADER_STAGE_ANY_HIT_BIT_KHR);
|
|
|
|
bind.addBinding(SceneBindings::ePointset, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_RAYGEN_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_bGlobals.buffer, 0, VK_WHOLE_SIZE};
|
|
VkDescriptorBufferInfo sceneDesc{m_sceneDesc.buffer, 0, VK_WHOLE_SIZE};
|
|
VkDescriptorBufferInfo pointsetDesc{m_pointsetBuffer.buffer, 0, VK_WHOLE_SIZE};
|
|
|
|
writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eGlobals, &dbiUnif));
|
|
writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::eSceneDesc, &sceneDesc));
|
|
writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, SceneBindings::ePointset, &pointsetDesc));
|
|
|
|
// 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, SceneBindings::eTextures, 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(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<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.addBindingDescriptions({{0, sizeof(glm::vec3)}, {1, sizeof(glm::vec3)}, {2, sizeof(glm::vec2)}});
|
|
gpb.addAttributeDescriptions({
|
|
{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0}, // Position
|
|
{1, 1, VK_FORMAT_R32G32B32_SFLOAT, 0}, // Normal
|
|
{2, 2, VK_FORMAT_R32G32_SFLOAT, 0}, // Texcoord0
|
|
});
|
|
m_graphicsPipeline = gpb.createPipeline();
|
|
m_debug.setObjectName(m_graphicsPipeline, "Graphics");
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Loading the OBJ file and setting up all buffers
|
|
//
|
|
void HelloVulkan::loadScene(const std::string& filename)
|
|
{
|
|
using vkBU = VkBufferUsageFlagBits;
|
|
tinygltf::Model tmodel;
|
|
tinygltf::TinyGLTF tcontext;
|
|
std::string warn, error;
|
|
|
|
LOGI("Loading file: %s", filename.c_str());
|
|
if(!tcontext.LoadASCIIFromFile(&tmodel, &error, &warn, filename))
|
|
{
|
|
assert(!"Error while loading scene");
|
|
}
|
|
LOGW("%s", warn.c_str());
|
|
LOGE("%s", error.c_str());
|
|
|
|
|
|
m_gltfScene.importMaterials(tmodel);
|
|
m_gltfScene.importDrawableNodes(tmodel, nvh::GltfAttributes::Normal | nvh::GltfAttributes::Texcoord_0);
|
|
|
|
// Create the buffers on Device and copy vertices, indices and materials
|
|
nvvk::CommandPool cmdBufGet(m_device, m_graphicsQueueIndex);
|
|
VkCommandBuffer cmdBuf = cmdBufGet.createCommandBuffer();
|
|
|
|
m_vertexBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_positions,
|
|
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT
|
|
| VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR);
|
|
m_indexBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_indices,
|
|
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT
|
|
| VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR);
|
|
m_normalBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_normals,
|
|
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
|
|
| VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
|
m_uvBuffer = m_alloc.createBuffer(cmdBuf, m_gltfScene.m_texcoords0,
|
|
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT
|
|
| VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
|
|
|
// Copying all materials, only the elements we need
|
|
std::vector<GltfShadeMaterial> shadeMaterials;
|
|
for(const auto& m : m_gltfScene.m_materials)
|
|
{
|
|
shadeMaterials.emplace_back(GltfShadeMaterial{m.baseColorFactor, m.emissiveFactor, m.baseColorTexture});
|
|
}
|
|
m_materialBuffer = m_alloc.createBuffer(cmdBuf, shadeMaterials,
|
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
|
|
|
// The following is used to find the primitive mesh information in the CHIT
|
|
std::vector<PrimMeshInfo> primLookup;
|
|
for(auto& primMesh : m_gltfScene.m_primMeshes)
|
|
{
|
|
primLookup.push_back({primMesh.firstIndex, primMesh.vertexOffset, primMesh.materialIndex});
|
|
}
|
|
m_primInfo = m_alloc.createBuffer(cmdBuf, primLookup, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
|
|
|
|
|
SceneDesc sceneDesc;
|
|
sceneDesc.vertexAddress = nvvk::getBufferDeviceAddress(m_device, m_vertexBuffer.buffer);
|
|
sceneDesc.indexAddress = nvvk::getBufferDeviceAddress(m_device, m_indexBuffer.buffer);
|
|
sceneDesc.normalAddress = nvvk::getBufferDeviceAddress(m_device, m_normalBuffer.buffer);
|
|
sceneDesc.uvAddress = nvvk::getBufferDeviceAddress(m_device, m_uvBuffer.buffer);
|
|
sceneDesc.materialAddress = nvvk::getBufferDeviceAddress(m_device, m_materialBuffer.buffer);
|
|
sceneDesc.primInfoAddress = nvvk::getBufferDeviceAddress(m_device, m_primInfo.buffer);
|
|
m_sceneDesc = m_alloc.createBuffer(cmdBuf, sizeof(SceneDesc), &sceneDesc,
|
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
|
|
|
// Creates all textures found
|
|
createTextureImages(cmdBuf, tmodel);
|
|
//generate and submit pointset
|
|
createPointsetImage(cmdBuf);
|
|
cmdBufGet.submitAndWait(cmdBuf);
|
|
m_alloc.finalizeAndReleaseStaging();
|
|
|
|
|
|
NAME_VK(m_vertexBuffer.buffer);
|
|
NAME_VK(m_indexBuffer.buffer);
|
|
NAME_VK(m_normalBuffer.buffer);
|
|
NAME_VK(m_uvBuffer.buffer);
|
|
NAME_VK(m_materialBuffer.buffer);
|
|
NAME_VK(m_primInfo.buffer);
|
|
NAME_VK(m_sceneDesc.buffer);
|
|
NAME_VK(m_pointsetBuffer.buffer);
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// 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");
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Creating all textures and samplers
|
|
//
|
|
void HelloVulkan::createTextureImages(const VkCommandBuffer& cmdBuf, tinygltf::Model& gltfModel)
|
|
{
|
|
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;
|
|
|
|
auto addDefaultTexture = [this]() {
|
|
// Make dummy image(1,1), needed as we cannot have an empty array
|
|
nvvk::ScopeCommandBuffer cmdBuf(m_device, m_graphicsQueueIndex);
|
|
std::array<uint8_t, 4> white = {255, 255, 255, 255};
|
|
|
|
VkSamplerCreateInfo sampler{VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO};
|
|
m_textures.emplace_back(m_alloc.createTexture(cmdBuf, 4, white.data(), nvvk::makeImage2DCreateInfo(VkExtent2D{1, 1}), sampler));
|
|
m_debug.setObjectName(m_textures.back().image, "dummy");
|
|
};
|
|
|
|
if(gltfModel.images.empty())
|
|
{
|
|
addDefaultTexture();
|
|
return;
|
|
}
|
|
|
|
m_textures.reserve(gltfModel.images.size());
|
|
for(size_t i = 0; i < gltfModel.images.size(); i++)
|
|
{
|
|
auto& gltfimage = gltfModel.images[i];
|
|
void* buffer = &gltfimage.image[0];
|
|
VkDeviceSize bufferSize = gltfimage.image.size();
|
|
auto imgSize = VkExtent2D{(uint32_t)gltfimage.width, (uint32_t)gltfimage.height};
|
|
|
|
if(bufferSize == 0 || gltfimage.width == -1 || gltfimage.height == -1)
|
|
{
|
|
addDefaultTexture();
|
|
continue;
|
|
}
|
|
|
|
VkImageCreateInfo imageCreateInfo = nvvk::makeImage2DCreateInfo(imgSize, format, VK_IMAGE_USAGE_SAMPLED_BIT, true);
|
|
|
|
nvvk::Image image = m_alloc.createImage(cmdBuf, bufferSize, buffer, imageCreateInfo);
|
|
nvvk::cmdGenerateMipmaps(cmdBuf, image.image, format, imgSize, imageCreateInfo.mipLevels);
|
|
VkImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, imageCreateInfo);
|
|
m_textures.emplace_back(m_alloc.createTexture(image, ivInfo, samplerCreateInfo));
|
|
|
|
m_debug.setObjectName(m_textures[i].image, std::string("Txt" + std::to_string(i)));
|
|
}
|
|
}
|
|
|
|
void HelloVulkan::createPointsetImage(const VkCommandBuffer& cmdBuf)
|
|
{
|
|
std::vector<vec2> points(m_size.height * m_size.width, vec2(0.f));
|
|
|
|
|
|
//generate blue noise points
|
|
const int npoints = 256; //points.size() / NCHANNELS;
|
|
utk::CustomHeckSampler sampler(0.606f, 8.f, 0.5);
|
|
auto pointset = utk::Pointset<float>{};
|
|
std::cout << std::endl << "generating " << npoints << " blue noise points…" << std::endl;
|
|
sampler.generateSamples(pointset, npoints);
|
|
|
|
utk::write_text_pointset("biig_pointset.txt", pointset);
|
|
|
|
/*
|
|
//iterate pointset and fill texture
|
|
for(int i = 0; i < pointset.Npts(); ++i)
|
|
{
|
|
auto point = std::make_pair(pointset[i][0] * (m_size.width - 1), pointset[i][1] * ((m_size.height) - 1));
|
|
float_t wholex, wholey, fractionalx, fractionaly;
|
|
fractionalx = std::modf(point.first, &wholex);
|
|
fractionaly = std::modf(point.second, &wholey);
|
|
|
|
points[wholex + wholey * m_size.width] = vec2{fractionalx, fractionaly};
|
|
}
|
|
*/
|
|
|
|
for(int y = 0; y < m_size.height; ++y)
|
|
{
|
|
for(int x = 0; x < m_size.width; ++x)
|
|
{
|
|
points[x + y * m_size.width] = vec2{static_cast<float_t>(x) / static_cast<float_t>(m_size.width), static_cast<float_t>(y) / static_cast<float_t>(m_size.height)};
|
|
}
|
|
}
|
|
|
|
m_pointsetBuffer = m_alloc.createBuffer(cmdBuf, sizeof(glm::vec2) * points.size(), points.data(),
|
|
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// 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_vertexBuffer);
|
|
m_alloc.destroy(m_normalBuffer);
|
|
m_alloc.destroy(m_uvBuffer);
|
|
m_alloc.destroy(m_indexBuffer);
|
|
m_alloc.destroy(m_materialBuffer);
|
|
m_alloc.destroy(m_primInfo);
|
|
m_alloc.destroy(m_sceneDesc);
|
|
m_alloc.destroy(m_pointsetBuffer);
|
|
|
|
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();
|
|
m_sbtWrapper.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.deinit();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Drawing the scene in raster mode
|
|
//
|
|
void HelloVulkan::rasterize(const VkCommandBuffer& cmdBuf)
|
|
{
|
|
using vkPBP = VkPipelineBindPoint;
|
|
|
|
std::vector<VkDeviceSize> offsets = {0, 0, 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);
|
|
|
|
std::vector<VkBuffer> vertexBuffers = {m_vertexBuffer.buffer, m_normalBuffer.buffer, m_uvBuffer.buffer};
|
|
vkCmdBindVertexBuffers(cmdBuf, 0, static_cast<uint32_t>(vertexBuffers.size()), vertexBuffers.data(), offsets.data());
|
|
vkCmdBindIndexBuffer(cmdBuf, m_indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
|
|
|
|
uint32_t idxNode = 0;
|
|
for(auto& node : m_gltfScene.m_nodes)
|
|
{
|
|
auto& primitive = m_gltfScene.m_primMeshes[node.primMesh];
|
|
|
|
m_pcRaster.modelMatrix = node.worldMatrix;
|
|
m_pcRaster.objIndex = node.primMesh;
|
|
m_pcRaster.materialId = primitive.materialIndex;
|
|
vkCmdPushConstants(cmdBuf, m_pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 0,
|
|
sizeof(PushConstantRaster), &m_pcRaster);
|
|
vkCmdDrawIndexed(cmdBuf, primitive.indexCount, 1, primitive.firstIndex, primitive.vertexOffset, 0);
|
|
}
|
|
|
|
m_debug.endLabel(cmdBuf);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Handling resize of the window
|
|
//
|
|
void HelloVulkan::onResize(int /*w*/, int /*h*/)
|
|
{
|
|
createOffscreenRender();
|
|
updatePostDescriptorSet();
|
|
updateRtDescriptorSet();
|
|
resetFrame();
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// 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);
|
|
m_sbtWrapper.setup(m_device, m_graphicsQueueIndex, &m_alloc, m_rtProperties);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Converting a GLTF primitive in the Raytracing Geometry used for the BLAS
|
|
//
|
|
auto HelloVulkan::primitiveToVkGeometry(const nvh::GltfPrimMesh& prim)
|
|
{
|
|
// BLAS builder requires raw device addresses.
|
|
VkDeviceAddress vertexAddress = nvvk::getBufferDeviceAddress(m_device, m_vertexBuffer.buffer);
|
|
VkDeviceAddress indexAddress = nvvk::getBufferDeviceAddress(m_device, m_indexBuffer.buffer);
|
|
|
|
uint32_t maxPrimitiveCount = prim.indexCount / 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(glm::vec3);
|
|
// 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 = prim.vertexCount - 1;
|
|
|
|
// 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_NO_DUPLICATE_ANY_HIT_INVOCATION_BIT_KHR; // For AnyHit
|
|
asGeom.geometry.triangles = triangles;
|
|
|
|
VkAccelerationStructureBuildRangeInfoKHR offset;
|
|
offset.firstVertex = prim.vertexOffset;
|
|
offset.primitiveCount = maxPrimitiveCount;
|
|
offset.primitiveOffset = prim.firstIndex * sizeof(uint32_t);
|
|
offset.transformOffset = 0;
|
|
|
|
// Our blas is made from only one geometry, but could be made of many geometries
|
|
nvvk::RaytracingBuilderKHR::BlasInput input;
|
|
input.asGeometry.emplace_back(asGeom);
|
|
input.asBuildOffsetInfo.emplace_back(offset);
|
|
|
|
return input;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
//
|
|
//
|
|
void HelloVulkan::createBottomLevelAS()
|
|
{
|
|
// BLAS - Storing each primitive in a geometry
|
|
std::vector<nvvk::RaytracingBuilderKHR::BlasInput> allBlas;
|
|
allBlas.reserve(m_gltfScene.m_primMeshes.size());
|
|
for(auto& primMesh : m_gltfScene.m_primMeshes)
|
|
{
|
|
auto geo = primitiveToVkGeometry(primMesh);
|
|
allBlas.push_back({geo});
|
|
}
|
|
m_rtBuilder.buildBlas(allBlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
//
|
|
//
|
|
void HelloVulkan::createTopLevelAS()
|
|
{
|
|
std::vector<VkAccelerationStructureInstanceKHR> tlas;
|
|
tlas.reserve(m_gltfScene.m_nodes.size());
|
|
for(auto& node : m_gltfScene.m_nodes)
|
|
{
|
|
VkAccelerationStructureInstanceKHR rayInst{};
|
|
rayInst.transform = nvvk::toTransformMatrixKHR(node.worldMatrix);
|
|
rayInst.instanceCustomIndex = node.primMesh; // gl_InstanceCustomIndexEXT: to find which primitive
|
|
rayInst.accelerationStructureReference = m_rtBuilder.getBlasDeviceAddress(node.primMesh);
|
|
rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
|
|
rayInst.mask = 0xFF;
|
|
rayInst.instanceShaderBindingTableRecordOffset = 0; // We will use the same hit group for all objects
|
|
tlas.emplace_back(rayInst);
|
|
}
|
|
m_rtBuilder.buildTlas(tlas, VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// This descriptor set holds the Acceleration structure and the output image
|
|
//
|
|
void HelloVulkan::createRtDescriptorSet()
|
|
{
|
|
// Top-level acceleration structure, usable by both the ray generation and the closest hit (to shoot shadow rays)
|
|
m_rtDescSetLayoutBind.addBinding(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
|
|
m_rtDescSetLayoutBind.addBinding(RtxBindings::ePrimLookup, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1,
|
|
VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR | VK_SHADER_STAGE_ANY_HIT_BIT_KHR); // Primitive info
|
|
|
|
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 primitiveInfoDesc{m_primInfo.buffer, 0, VK_WHOLE_SIZE};
|
|
|
|
std::vector<VkWriteDescriptorSet> 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, RtxBindings::ePrimLookup, &primitiveInfoDesc));
|
|
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, RtxBindings::eOutImage, &imageInfo);
|
|
vkUpdateDescriptorSets(m_device, 1, &wds, 0, nullptr);
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Pipeline for the ray tracer: all shaders, raygen, chit, miss
|
|
//
|
|
void HelloVulkan::createRtPipeline()
|
|
{
|
|
enum StageIndices
|
|
{
|
|
eRaygen,
|
|
eMiss,
|
|
eMiss2,
|
|
eClosestHit,
|
|
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
|
|
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;
|
|
// The second miss shader is invoked when a shadow ray misses the geometry. It simply indicates that no occlusion has been found
|
|
stage.module =
|
|
nvvk::createShaderModule(m_device, nvh::loadFile("spv/raytraceShadow.rmiss.spv", true, defaultSearchPaths, true));
|
|
stage.stage = VK_SHADER_STAGE_MISS_BIT_KHR;
|
|
stages[eMiss2] = stage;
|
|
// 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;
|
|
|
|
|
|
// Shader groups
|
|
VkRayTracingShaderGroupCreateInfoKHR group{VK_STRUCTURE_TYPE_RAY_TRACING_SHADER_GROUP_CREATE_INFO_KHR};
|
|
group.anyHitShader = VK_SHADER_UNUSED_KHR;
|
|
group.closestHitShader = VK_SHADER_UNUSED_KHR;
|
|
group.generalShader = VK_SHADER_UNUSED_KHR;
|
|
group.intersectionShader = VK_SHADER_UNUSED_KHR;
|
|
|
|
// Raygen
|
|
group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
|
|
group.generalShader = eRaygen;
|
|
m_rtShaderGroups.push_back(group);
|
|
|
|
// Miss
|
|
group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
|
|
group.generalShader = eMiss;
|
|
m_rtShaderGroups.push_back(group);
|
|
|
|
// Shadow Miss
|
|
group.type = VK_RAY_TRACING_SHADER_GROUP_TYPE_GENERAL_KHR;
|
|
group.generalShader = eMiss2;
|
|
m_rtShaderGroups.push_back(group);
|
|
|
|
// 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);
|
|
|
|
|
|
// 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<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);
|
|
|
|
|
|
// Creating the SBT
|
|
m_sbtWrapper.create(m_rtPipeline, rayPipelineInfo);
|
|
|
|
|
|
for(auto& s : stages)
|
|
vkDestroyShaderModule(m_device, s.module, nullptr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Ray Tracing the scene
|
|
//
|
|
void HelloVulkan::raytrace(const VkCommandBuffer& cmdBuf, const glm::vec4& clearColor)
|
|
{
|
|
updateFrame();
|
|
|
|
m_debug.beginLabel(cmdBuf, "Ray trace");
|
|
// Initializing 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;
|
|
|
|
|
|
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(PushConstantRay), &m_pcRay);
|
|
|
|
|
|
auto& regions = m_sbtWrapper.getRegions();
|
|
vkCmdTraceRaysKHR(cmdBuf, ®ions[0], ®ions[1], ®ions[2], ®ions[3], m_size.width, m_size.height, 1);
|
|
|
|
|
|
m_debug.endLabel(cmdBuf);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// If the camera matrix has changed, resets the frame.
|
|
// otherwise, increments frame.
|
|
//
|
|
void HelloVulkan::updateFrame()
|
|
{
|
|
static glm::mat4 refCamMatrix;
|
|
static float refFov{CameraManip.getFov()};
|
|
|
|
const auto& m = CameraManip.getMatrix();
|
|
const auto fov = CameraManip.getFov();
|
|
|
|
if(refCamMatrix != m || refFov != fov)
|
|
{
|
|
resetFrame();
|
|
refCamMatrix = m;
|
|
refFov = fov;
|
|
}
|
|
m_pcRay.frame++;
|
|
}
|
|
|
|
void HelloVulkan::resetFrame()
|
|
{
|
|
m_pcRay.frame = -1;
|
|
}
|