910 lines
38 KiB
C++
910 lines
38 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>
|
|
#include <vulkan/vulkan.hpp>
|
|
|
|
extern std::vector<std::string> defaultSearchPaths;
|
|
|
|
#define STB_IMAGE_IMPLEMENTATION
|
|
#include "obj_loader.h"
|
|
#include "stb_image.h"
|
|
|
|
#include "hello_vulkan.h"
|
|
#include "nvh/cameramanipulator.hpp"
|
|
#include "nvvk/descriptorsets_vk.hpp"
|
|
#include "nvvk/images_vk.hpp"
|
|
#include "nvvk/pipeline_vk.hpp"
|
|
|
|
#include "nvh/fileoperations.hpp"
|
|
#include "nvvk/commands_vk.hpp"
|
|
#include "nvvk/renderpasses_vk.hpp"
|
|
#include "nvvk/shaders_vk.hpp"
|
|
|
|
|
|
// 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 vk::Instance& instance,
|
|
const vk::Device& device,
|
|
const vk::PhysicalDevice& physicalDevice,
|
|
uint32_t queueFamily)
|
|
{
|
|
AppBase::setup(instance, device, physicalDevice, queueFamily);
|
|
m_alloc.init(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 vk::CommandBuffer& cmdBuf)
|
|
{
|
|
// Prepare new UBO contents on host.
|
|
const float aspectRatio = m_size.width / static_cast<float>(m_size.height);
|
|
CameraMatrices hostUBO = {};
|
|
hostUBO.view = CameraManip.getMatrix();
|
|
hostUBO.proj = nvmath::perspectiveVK(CameraManip.getFov(), aspectRatio, 0.1f, 1000.0f);
|
|
// hostUBO.proj[1][1] *= -1; // Inverting Y for Vulkan (not needed with perspectiveVK).
|
|
hostUBO.viewInverse = nvmath::invert(hostUBO.view);
|
|
// #VKRay
|
|
hostUBO.projInverse = nvmath::invert(hostUBO.proj);
|
|
|
|
// UBO on the device, and what stages access it.
|
|
vk::Buffer deviceUBO = m_cameraMat.buffer;
|
|
auto uboUsageStages =
|
|
vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eRayTracingShaderKHR;
|
|
|
|
// Ensure that the modified UBO is not visible to previous frames.
|
|
vk::BufferMemoryBarrier beforeBarrier;
|
|
beforeBarrier.setSrcAccessMask(vk::AccessFlagBits::eShaderRead);
|
|
beforeBarrier.setDstAccessMask(vk::AccessFlagBits::eTransferWrite);
|
|
beforeBarrier.setBuffer(deviceUBO);
|
|
beforeBarrier.setOffset(0);
|
|
beforeBarrier.setSize(sizeof hostUBO);
|
|
cmdBuf.pipelineBarrier(uboUsageStages, vk::PipelineStageFlagBits::eTransfer,
|
|
vk::DependencyFlagBits::eDeviceGroup, {}, {beforeBarrier}, {});
|
|
|
|
// Schedule the host-to-device upload. (hostUBO is copied into the cmd
|
|
// buffer so it is okay to deallocate when the function returns).
|
|
cmdBuf.updateBuffer<CameraMatrices>(m_cameraMat.buffer, 0, hostUBO);
|
|
|
|
// Making sure the updated UBO will be visible.
|
|
vk::BufferMemoryBarrier afterBarrier;
|
|
afterBarrier.setSrcAccessMask(vk::AccessFlagBits::eTransferWrite);
|
|
afterBarrier.setDstAccessMask(vk::AccessFlagBits::eShaderRead);
|
|
afterBarrier.setBuffer(deviceUBO);
|
|
afterBarrier.setOffset(0);
|
|
afterBarrier.setSize(sizeof hostUBO);
|
|
cmdBuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, uboUsageStages,
|
|
vk::DependencyFlagBits::eDeviceGroup, {}, {afterBarrier}, {});
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Describing the layout pushed when rendering
|
|
//
|
|
void HelloVulkan::createDescriptorSetLayout()
|
|
{
|
|
using vkDS = vk::DescriptorSetLayoutBinding;
|
|
using vkDT = vk::DescriptorType;
|
|
using vkSS = vk::ShaderStageFlagBits;
|
|
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(vkDS(0, vkDT::eUniformBuffer, 1, vkSS::eVertex));
|
|
// Materials (binding = 1)
|
|
m_descSetLayoutBind.addBinding(vkDS(1, vkDT::eStorageBuffer, nbObj, vkSS::eFragment));
|
|
// Scene description (binding = 2)
|
|
m_descSetLayoutBind.addBinding(vkDS(2, vkDT::eStorageBuffer, 1, vkSS::eVertex | vkSS::eFragment));
|
|
// Textures (binding = 3)
|
|
m_descSetLayoutBind.addBinding(vkDS(3, vkDT::eCombinedImageSampler, nbTxt, vkSS::eFragment));
|
|
// Materials (binding = 4)
|
|
m_descSetLayoutBind.addBinding(vkDS(4, vkDT::eStorageBuffer, nbObj, vkSS::eFragment));
|
|
|
|
|
|
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<vk::WriteDescriptorSet> writes;
|
|
|
|
// Camera matrices and scene description
|
|
vk::DescriptorBufferInfo dbiUnif{m_cameraMat.buffer, 0, VK_WHOLE_SIZE};
|
|
writes.emplace_back(m_descSetLayoutBind.makeWrite(m_descSet, 0, &dbiUnif));
|
|
vk::DescriptorBufferInfo 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<vk::DescriptorBufferInfo> dbiMat;
|
|
std::vector<vk::DescriptorBufferInfo> dbiMatIdx;
|
|
std::vector<vk::DescriptorBufferInfo> dbiVert;
|
|
std::vector<vk::DescriptorBufferInfo> dbiIdx;
|
|
for(auto& obj : m_objModel)
|
|
{
|
|
dbiMat.emplace_back(obj.matColorBuffer.buffer, 0, VK_WHOLE_SIZE);
|
|
dbiMatIdx.emplace_back(obj.matIndexBuffer.buffer, 0, VK_WHOLE_SIZE);
|
|
dbiVert.emplace_back(obj.vertexBuffer.buffer, 0, VK_WHOLE_SIZE);
|
|
dbiIdx.emplace_back(obj.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()));
|
|
|
|
// All texture samplers
|
|
std::vector<vk::DescriptorImageInfo> 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
|
|
m_device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Creating the pipeline layout
|
|
//
|
|
void HelloVulkan::createGraphicsPipeline()
|
|
{
|
|
using vkSS = vk::ShaderStageFlagBits;
|
|
|
|
vk::PushConstantRange pushConstantRanges = {vkSS::eVertex | vkSS::eFragment, 0,
|
|
sizeof(ObjPushConstant)};
|
|
|
|
// Creating the Pipeline Layout
|
|
vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo;
|
|
vk::DescriptorSetLayout descSetLayout(m_descSetLayout);
|
|
pipelineLayoutCreateInfo.setSetLayoutCount(1);
|
|
pipelineLayoutCreateInfo.setPSetLayouts(&descSetLayout);
|
|
pipelineLayoutCreateInfo.setPushConstantRangeCount(1);
|
|
pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstantRanges);
|
|
m_pipelineLayout = m_device.createPipelineLayout(pipelineLayoutCreateInfo);
|
|
|
|
// 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), vkSS::eVertex);
|
|
gpb.addShader(nvh::loadFile("spv/frag_shader.frag.spv", true, paths, true), vkSS::eFragment);
|
|
gpb.addBindingDescription({0, sizeof(VertexObj)});
|
|
gpb.addAttributeDescriptions({
|
|
{0, 0, vk::Format::eR32G32B32Sfloat, static_cast<uint32_t>(offsetof(VertexObj, pos))},
|
|
{1, 0, vk::Format::eR32G32B32Sfloat, static_cast<uint32_t>(offsetof(VertexObj, nrm))},
|
|
{2, 0, vk::Format::eR32G32B32Sfloat, static_cast<uint32_t>(offsetof(VertexObj, color))},
|
|
{3, 0, vk::Format::eR32G32Sfloat, static_cast<uint32_t>(offsetof(VertexObj, texCoord))},
|
|
});
|
|
|
|
vk::PipelineColorBlendAttachmentState res;
|
|
res.colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG
|
|
| vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA;
|
|
gpb.addBlendAttachmentState(res);
|
|
|
|
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)
|
|
{
|
|
using vkBU = vk::BufferUsageFlagBits;
|
|
|
|
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);
|
|
vk::CommandBuffer cmdBuf = cmdBufGet.createCommandBuffer();
|
|
model.vertexBuffer =
|
|
m_alloc.createBuffer(cmdBuf, loader.m_vertices,
|
|
vkBU::eVertexBuffer | vkBU::eStorageBuffer | vkBU::eShaderDeviceAddress
|
|
| vkBU::eAccelerationStructureBuildInputReadOnlyKHR);
|
|
model.indexBuffer =
|
|
m_alloc.createBuffer(cmdBuf, loader.m_indices,
|
|
vkBU::eIndexBuffer | vkBU::eStorageBuffer | vkBU::eShaderDeviceAddress
|
|
| vkBU::eAccelerationStructureBuildInputReadOnlyKHR);
|
|
model.matColorBuffer = m_alloc.createBuffer(cmdBuf, loader.m_materials, vkBU::eStorageBuffer);
|
|
model.matIndexBuffer = m_alloc.createBuffer(cmdBuf, loader.m_matIndx, vkBU::eStorageBuffer);
|
|
// Creates all textures found
|
|
createTextureImages(cmdBuf, loader.m_textures);
|
|
cmdBufGet.submitAndWait(cmdBuf);
|
|
m_alloc.finalizeAndReleaseStaging();
|
|
|
|
std::string objNb = std::to_string(instance.objIndex);
|
|
m_debug.setObjectName(model.vertexBuffer.buffer, (std::string("vertex_" + objNb).c_str()));
|
|
m_debug.setObjectName(model.indexBuffer.buffer, (std::string("index_" + objNb).c_str()));
|
|
m_debug.setObjectName(model.matColorBuffer.buffer, (std::string("mat_" + objNb).c_str()));
|
|
m_debug.setObjectName(model.matIndexBuffer.buffer, (std::string("matIdx_" + objNb).c_str()));
|
|
|
|
m_objModel.emplace_back(model);
|
|
m_objInstance.emplace_back(instance);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Creating the uniform buffer holding the camera matrices
|
|
// - Buffer is host visible
|
|
//
|
|
void HelloVulkan::createUniformBuffer()
|
|
{
|
|
using vkBU = vk::BufferUsageFlagBits;
|
|
using vkMP = vk::MemoryPropertyFlagBits;
|
|
|
|
m_cameraMat = m_alloc.createBuffer(sizeof(CameraMatrices),
|
|
vkBU::eUniformBuffer | vkBU::eTransferDst, vkMP::eDeviceLocal);
|
|
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()
|
|
{
|
|
using vkBU = vk::BufferUsageFlagBits;
|
|
nvvk::CommandPool cmdGen(m_device, m_graphicsQueueIndex);
|
|
|
|
auto cmdBuf = cmdGen.createCommandBuffer();
|
|
m_sceneDesc = m_alloc.createBuffer(cmdBuf, m_objInstance, vkBU::eStorageBuffer);
|
|
cmdGen.submitAndWait(cmdBuf);
|
|
m_alloc.finalizeAndReleaseStaging();
|
|
m_debug.setObjectName(m_sceneDesc.buffer, "sceneDesc");
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Creating all textures and samplers
|
|
//
|
|
void HelloVulkan::createTextureImages(const vk::CommandBuffer& cmdBuf,
|
|
const std::vector<std::string>& textures)
|
|
{
|
|
using vkIU = vk::ImageUsageFlagBits;
|
|
|
|
vk::SamplerCreateInfo samplerCreateInfo{
|
|
{}, vk::Filter::eLinear, vk::Filter::eLinear, vk::SamplerMipmapMode::eLinear};
|
|
samplerCreateInfo.setMaxLod(FLT_MAX);
|
|
vk::Format format = vk::Format::eR8G8B8A8Srgb;
|
|
|
|
// 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};
|
|
vk::DeviceSize bufferSize = sizeof(color);
|
|
auto imgSize = vk::Extent2D(1, 1);
|
|
auto imageCreateInfo = nvvk::makeImage2DCreateInfo(imgSize, format);
|
|
|
|
// Creating the VKImage
|
|
nvvk::Image image = m_alloc.createImage(cmdBuf, bufferSize, color.data(), imageCreateInfo);
|
|
vk::ImageViewCreateInfo 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::ImageLayout::eUndefined,
|
|
vk::ImageLayout::eShaderReadOnlyOptimal);
|
|
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());
|
|
}
|
|
|
|
vk::DeviceSize bufferSize = static_cast<uint64_t>(texWidth) * texHeight * sizeof(uint8_t) * 4;
|
|
auto imgSize = vk::Extent2D(texWidth, texHeight);
|
|
auto imageCreateInfo = nvvk::makeImage2DCreateInfo(imgSize, format, vkIU::eSampled, true);
|
|
|
|
{
|
|
nvvk::Image image = m_alloc.createImage(cmdBuf, bufferSize, pixels, imageCreateInfo);
|
|
nvvk::cmdGenerateMipmaps(cmdBuf, image.image, format, imgSize, imageCreateInfo.mipLevels);
|
|
vk::ImageViewCreateInfo 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()
|
|
{
|
|
m_device.destroy(m_graphicsPipeline);
|
|
m_device.destroy(m_pipelineLayout);
|
|
m_device.destroy(m_descPool);
|
|
m_device.destroy(m_descSetLayout);
|
|
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_device.destroy(m_postPipeline);
|
|
m_device.destroy(m_postPipelineLayout);
|
|
m_device.destroy(m_postDescPool);
|
|
m_device.destroy(m_postDescSetLayout);
|
|
m_alloc.destroy(m_offscreenColor);
|
|
m_alloc.destroy(m_gBuffer);
|
|
m_alloc.destroy(m_aoBuffer);
|
|
m_alloc.destroy(m_offscreenDepth);
|
|
m_device.destroy(m_offscreenRenderPass);
|
|
m_device.destroy(m_offscreenFramebuffer);
|
|
|
|
// Compute
|
|
m_device.destroy(m_compDescPool);
|
|
m_device.destroy(m_compDescSetLayout);
|
|
m_device.destroy(m_compPipeline);
|
|
m_device.destroy(m_compPipelineLayout);
|
|
|
|
// #VKRay
|
|
m_rtBuilder.destroy();
|
|
m_alloc.deinit();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Drawing the scene in raster mode
|
|
//
|
|
void HelloVulkan::rasterize(const vk::CommandBuffer& cmdBuf)
|
|
{
|
|
using vkPBP = vk::PipelineBindPoint;
|
|
using vkSS = vk::ShaderStageFlagBits;
|
|
vk::DeviceSize offset{0};
|
|
|
|
m_debug.beginLabel(cmdBuf, "Rasterize");
|
|
|
|
// Dynamic Viewport
|
|
cmdBuf.setViewport(0, {vk::Viewport(0, 0, (float)m_size.width, (float)m_size.height, 0, 1)});
|
|
cmdBuf.setScissor(0, {{{0, 0}, {m_size.width, m_size.height}}});
|
|
|
|
// Drawing all triangles
|
|
cmdBuf.bindPipeline(vkPBP::eGraphics, m_graphicsPipeline);
|
|
cmdBuf.bindDescriptorSets(vkPBP::eGraphics, m_pipelineLayout, 0, {m_descSet}, {});
|
|
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
|
|
cmdBuf.pushConstants<ObjPushConstant>(m_pipelineLayout, vkSS::eVertex | vkSS::eFragment, 0,
|
|
m_pushConstant);
|
|
|
|
cmdBuf.bindVertexBuffers(0, {model.vertexBuffer.buffer}, {offset});
|
|
cmdBuf.bindIndexBuffer(model.indexBuffer.buffer, 0, vk::IndexType::eUint32);
|
|
cmdBuf.drawIndexed(model.nbIndices, 1, 0, 0, 0);
|
|
}
|
|
m_debug.endLabel(cmdBuf);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Handling resize of the window
|
|
//
|
|
void HelloVulkan::onResize(int /*w*/, int /*h*/)
|
|
{
|
|
createOffscreenRender();
|
|
updatePostDescriptorSet();
|
|
updateCompDescriptors();
|
|
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_gBuffer);
|
|
m_alloc.destroy(m_aoBuffer);
|
|
m_alloc.destroy(m_offscreenDepth);
|
|
|
|
// Creating the color image
|
|
{
|
|
auto colorCreateInfo = nvvk::makeImage2DCreateInfo(m_size, m_offscreenColorFormat,
|
|
vk::ImageUsageFlagBits::eColorAttachment
|
|
| vk::ImageUsageFlagBits::eSampled
|
|
| vk::ImageUsageFlagBits::eStorage);
|
|
|
|
|
|
nvvk::Image image = m_alloc.createImage(colorCreateInfo);
|
|
vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, colorCreateInfo);
|
|
m_offscreenColor = m_alloc.createTexture(image, ivInfo, vk::SamplerCreateInfo());
|
|
m_offscreenColor.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
|
m_debug.setObjectName(m_offscreenColor.image, "offscreen");
|
|
}
|
|
|
|
// The G-Buffer (rgba32f) - position(xyz) / normal(w-compressed)
|
|
{
|
|
auto colorCreateInfo = nvvk::makeImage2DCreateInfo(m_size, vk::Format::eR32G32B32A32Sfloat,
|
|
vk::ImageUsageFlagBits::eColorAttachment
|
|
| vk::ImageUsageFlagBits::eSampled
|
|
| vk::ImageUsageFlagBits::eStorage);
|
|
|
|
|
|
nvvk::Image image = m_alloc.createImage(colorCreateInfo);
|
|
vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, colorCreateInfo);
|
|
m_gBuffer = m_alloc.createTexture(image, ivInfo, vk::SamplerCreateInfo());
|
|
m_gBuffer.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
|
m_debug.setObjectName(m_gBuffer.image, "G-Buffer");
|
|
}
|
|
|
|
// The ambient occlusion result (r32)
|
|
{
|
|
auto colorCreateInfo = nvvk::makeImage2DCreateInfo(m_size, vk::Format::eR32Sfloat,
|
|
vk::ImageUsageFlagBits::eColorAttachment
|
|
| vk::ImageUsageFlagBits::eSampled
|
|
| vk::ImageUsageFlagBits::eStorage);
|
|
|
|
|
|
nvvk::Image image = m_alloc.createImage(colorCreateInfo);
|
|
vk::ImageViewCreateInfo ivInfo = nvvk::makeImageViewCreateInfo(image.image, colorCreateInfo);
|
|
m_aoBuffer = m_alloc.createTexture(image, ivInfo, vk::SamplerCreateInfo());
|
|
m_aoBuffer.descriptor.imageLayout = VK_IMAGE_LAYOUT_GENERAL;
|
|
m_debug.setObjectName(m_aoBuffer.image, "aoBuffer");
|
|
}
|
|
|
|
|
|
// Creating the depth buffer
|
|
auto depthCreateInfo =
|
|
nvvk::makeImage2DCreateInfo(m_size, m_offscreenDepthFormat,
|
|
vk::ImageUsageFlagBits::eDepthStencilAttachment);
|
|
{
|
|
nvvk::Image image = m_alloc.createImage(depthCreateInfo);
|
|
|
|
vk::ImageViewCreateInfo depthStencilView;
|
|
depthStencilView.setViewType(vk::ImageViewType::e2D);
|
|
depthStencilView.setFormat(m_offscreenDepthFormat);
|
|
depthStencilView.setSubresourceRange({vk::ImageAspectFlagBits::eDepth, 0, 1, 0, 1});
|
|
depthStencilView.setImage(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::ImageLayout::eUndefined,
|
|
vk::ImageLayout::eGeneral);
|
|
nvvk::cmdBarrierImageLayout(cmdBuf, m_gBuffer.image, vk::ImageLayout::eUndefined,
|
|
vk::ImageLayout::eGeneral);
|
|
nvvk::cmdBarrierImageLayout(cmdBuf, m_aoBuffer.image, vk::ImageLayout::eUndefined,
|
|
vk::ImageLayout::eGeneral);
|
|
nvvk::cmdBarrierImageLayout(cmdBuf, m_offscreenDepth.image, vk::ImageLayout::eUndefined,
|
|
vk::ImageLayout::eDepthStencilAttachmentOptimal,
|
|
vk::ImageAspectFlagBits::eDepth);
|
|
|
|
genCmdBuf.submitAndWait(cmdBuf);
|
|
}
|
|
|
|
// Creating a renderpass for the offscreen
|
|
if(!m_offscreenRenderPass)
|
|
{
|
|
m_offscreenRenderPass =
|
|
nvvk::createRenderPass(m_device,
|
|
{m_offscreenColorFormat, m_offscreenColorFormat}, // RGBA + G-Buffer
|
|
m_offscreenDepthFormat, 1, true, true, vk::ImageLayout::eGeneral,
|
|
vk::ImageLayout::eGeneral);
|
|
}
|
|
|
|
// Creating the frame buffer for offscreen
|
|
std::vector<vk::ImageView> attachments = {m_offscreenColor.descriptor.imageView,
|
|
m_gBuffer.descriptor.imageView,
|
|
m_offscreenDepth.descriptor.imageView};
|
|
|
|
m_device.destroy(m_offscreenFramebuffer);
|
|
vk::FramebufferCreateInfo info;
|
|
info.setRenderPass(m_offscreenRenderPass);
|
|
info.setAttachmentCount(static_cast<int>(attachments.size()));
|
|
info.setPAttachments(attachments.data());
|
|
info.setWidth(m_size.width);
|
|
info.setHeight(m_size.height);
|
|
info.setLayers(1);
|
|
m_offscreenFramebuffer = m_device.createFramebuffer(info);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// 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
|
|
vk::PushConstantRange pushConstantRanges = {vk::ShaderStageFlagBits::eFragment, 0, sizeof(float)};
|
|
|
|
// Creating the pipeline layout
|
|
vk::PipelineLayoutCreateInfo pipelineLayoutCreateInfo;
|
|
pipelineLayoutCreateInfo.setSetLayoutCount(1);
|
|
pipelineLayoutCreateInfo.setPSetLayouts(&m_postDescSetLayout);
|
|
pipelineLayoutCreateInfo.setPushConstantRangeCount(1);
|
|
pipelineLayoutCreateInfo.setPPushConstantRanges(&pushConstantRanges);
|
|
m_postPipelineLayout = m_device.createPipelineLayout(pipelineLayoutCreateInfo);
|
|
|
|
// 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::ShaderStageFlagBits::eVertex);
|
|
pipelineGenerator.addShader(nvh::loadFile("spv/post.frag.spv", true, defaultSearchPaths, true),
|
|
vk::ShaderStageFlagBits::eFragment);
|
|
pipelineGenerator.rasterizationState.setCullMode(vk::CullModeFlagBits::eNone);
|
|
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()
|
|
{
|
|
using vkDS = vk::DescriptorSetLayoutBinding;
|
|
using vkDT = vk::DescriptorType;
|
|
using vkSS = vk::ShaderStageFlagBits;
|
|
|
|
m_postDescSetLayoutBind.addBinding(vkDS(0, vkDT::eCombinedImageSampler, 1, vkSS::eFragment));
|
|
m_postDescSetLayoutBind.addBinding(vkDS(1, vkDT::eCombinedImageSampler, 1, vkSS::eFragment));
|
|
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()
|
|
{
|
|
std::vector<vk::WriteDescriptorSet> writes;
|
|
writes.emplace_back(
|
|
m_postDescSetLayoutBind.makeWrite(m_postDescSet, 0, &m_offscreenColor.descriptor));
|
|
writes.emplace_back(m_postDescSetLayoutBind.makeWrite(m_postDescSet, 1, &m_aoBuffer.descriptor));
|
|
m_device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Draw a full screen quad with the attached image
|
|
//
|
|
void HelloVulkan::drawPost(vk::CommandBuffer cmdBuf)
|
|
{
|
|
m_debug.beginLabel(cmdBuf, "Post");
|
|
|
|
cmdBuf.setViewport(0, {vk::Viewport(0, 0, (float)m_size.width, (float)m_size.height, 0, 1)});
|
|
cmdBuf.setScissor(0, {{{0, 0}, {m_size.width, m_size.height}}});
|
|
|
|
auto aspectRatio = static_cast<float>(m_size.width) / static_cast<float>(m_size.height);
|
|
cmdBuf.pushConstants<float>(m_postPipelineLayout, vk::ShaderStageFlagBits::eFragment, 0,
|
|
aspectRatio);
|
|
cmdBuf.bindPipeline(vk::PipelineBindPoint::eGraphics, m_postPipeline);
|
|
cmdBuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_postPipelineLayout, 0,
|
|
m_postDescSet, {});
|
|
cmdBuf.draw(3, 1, 0, 0);
|
|
|
|
m_debug.endLabel(cmdBuf);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Raytracing, creation of BLAS and TLAS
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Initialize Vulkan ray tracing
|
|
// #VKRay
|
|
void HelloVulkan::initRayTracing()
|
|
{
|
|
// Requesting ray tracing properties
|
|
auto properties =
|
|
m_physicalDevice.getProperties2<vk::PhysicalDeviceProperties2,
|
|
vk::PhysicalDeviceRayTracingPipelinePropertiesKHR>();
|
|
m_rtProperties = properties.get<vk::PhysicalDeviceRayTracingPipelinePropertiesKHR>();
|
|
m_rtBuilder.setup(m_device, &m_alloc, m_graphicsQueueIndex);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Converting a OBJ primitive to the ray tracing geometry used for the BLAS
|
|
//
|
|
auto HelloVulkan::objectToVkGeometryKHR(const ObjModel& model)
|
|
{
|
|
// Building part
|
|
vk::DeviceAddress vertexAddress = m_device.getBufferAddress({model.vertexBuffer.buffer});
|
|
vk::DeviceAddress indexAddress = m_device.getBufferAddress({model.indexBuffer.buffer});
|
|
|
|
vk::AccelerationStructureGeometryTrianglesDataKHR triangles;
|
|
triangles.setVertexFormat(vk::Format::eR32G32B32Sfloat);
|
|
triangles.setVertexData(vertexAddress);
|
|
triangles.setVertexStride(sizeof(VertexObj));
|
|
triangles.setIndexType(vk::IndexType::eUint32);
|
|
triangles.setIndexData(indexAddress);
|
|
triangles.setTransformData({});
|
|
triangles.setMaxVertex(model.nbVertices);
|
|
|
|
// Setting up the build info of the acceleration
|
|
vk::AccelerationStructureGeometryKHR asGeom;
|
|
asGeom.setGeometryType(vk::GeometryTypeKHR::eTriangles);
|
|
asGeom.setFlags(vk::GeometryFlagBitsKHR::eOpaque);
|
|
asGeom.geometry.setTriangles(triangles);
|
|
|
|
// The primitive itself
|
|
vk::AccelerationStructureBuildRangeInfoKHR offset;
|
|
offset.setFirstVertex(0);
|
|
offset.setPrimitiveCount(model.nbIndices / 3); // Nb triangles
|
|
offset.setPrimitiveOffset(0);
|
|
offset.setTransformOffset(0);
|
|
|
|
// Our blas is 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_objModel.size());
|
|
for(const auto& obj : m_objModel)
|
|
{
|
|
auto blas = objectToVkGeometryKHR(obj);
|
|
|
|
// We could add more geometry in each BLAS, but we add only one for now
|
|
allBlas.emplace_back(blas);
|
|
}
|
|
m_rtBuilder.buildBlas(allBlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace);
|
|
}
|
|
|
|
void HelloVulkan::createTopLevelAS()
|
|
{
|
|
std::vector<nvvk::RaytracingBuilderKHR::Instance> tlas;
|
|
tlas.reserve(m_objInstance.size());
|
|
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 objects
|
|
rayInst.flags = VK_GEOMETRY_INSTANCE_TRIANGLE_FACING_CULL_DISABLE_BIT_KHR;
|
|
tlas.emplace_back(rayInst);
|
|
}
|
|
m_rtBuilder.buildTlas(tlas, vk::BuildAccelerationStructureFlagBitsKHR::ePreferFastTrace);
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Compute shader from ANIMATION tutorial
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Compute shader descriptor
|
|
//
|
|
void HelloVulkan::createCompDescriptors()
|
|
{
|
|
m_compDescSetLayoutBind.addBinding(vk::DescriptorSetLayoutBinding( // [in] G-Buffer
|
|
0, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eCompute));
|
|
m_compDescSetLayoutBind.addBinding(vk::DescriptorSetLayoutBinding( // [out] AO
|
|
1, vk::DescriptorType::eStorageImage, 1, vk::ShaderStageFlagBits::eCompute));
|
|
m_compDescSetLayoutBind.addBinding(vk::DescriptorSetLayoutBinding( // [in] TLAS
|
|
2, vk::DescriptorType::eAccelerationStructureKHR, 1, vk::ShaderStageFlagBits::eCompute));
|
|
|
|
m_compDescSetLayout = m_compDescSetLayoutBind.createLayout(m_device);
|
|
m_compDescPool = m_compDescSetLayoutBind.createPool(m_device, 1);
|
|
m_compDescSet = nvvk::allocateDescriptorSet(m_device, m_compDescPool, m_compDescSetLayout);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Setting up the values to the descriptors
|
|
//
|
|
void HelloVulkan::updateCompDescriptors()
|
|
{
|
|
std::vector<vk::WriteDescriptorSet> writes;
|
|
writes.emplace_back(m_compDescSetLayoutBind.makeWrite(m_compDescSet, 0, &m_gBuffer.descriptor));
|
|
writes.emplace_back(m_compDescSetLayoutBind.makeWrite(m_compDescSet, 1, &m_aoBuffer.descriptor));
|
|
|
|
vk::AccelerationStructureKHR tlas = m_rtBuilder.getAccelerationStructure();
|
|
vk::WriteDescriptorSetAccelerationStructureKHR descASInfo{1, &tlas};
|
|
writes.emplace_back(m_compDescSetLayoutBind.makeWrite(m_compDescSet, 2, &descASInfo));
|
|
|
|
m_device.updateDescriptorSets(static_cast<uint32_t>(writes.size()), writes.data(), 0, nullptr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Creating the pipeline: shader ...
|
|
//
|
|
void HelloVulkan::createCompPipelines()
|
|
{
|
|
// pushing time
|
|
vk::PushConstantRange push_constants = {vk::ShaderStageFlagBits::eCompute, 0, sizeof(AoControl)};
|
|
vk::PipelineLayoutCreateInfo layout_info{{}, 1, &m_compDescSetLayout, 1, &push_constants};
|
|
m_compPipelineLayout = m_device.createPipelineLayout(layout_info);
|
|
vk::ComputePipelineCreateInfo computePipelineCreateInfo{{}, {}, m_compPipelineLayout};
|
|
|
|
computePipelineCreateInfo.stage =
|
|
nvvk::createShaderStageInfo(m_device,
|
|
nvh::loadFile("spv/ao.comp.spv", true, defaultSearchPaths, true),
|
|
VK_SHADER_STAGE_COMPUTE_BIT);
|
|
m_compPipeline = m_device.createComputePipeline({}, computePipelineCreateInfo).value;
|
|
m_device.destroy(computePipelineCreateInfo.stage.module);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Running compute shader
|
|
//
|
|
#define GROUP_SIZE 16 // Same group size as in compute shader
|
|
void HelloVulkan::runCompute(vk::CommandBuffer cmdBuf, AoControl& aoControl)
|
|
{
|
|
updateFrame();
|
|
|
|
// Stop by default after 100'000 samples
|
|
if(m_frame * aoControl.rtao_samples > aoControl.max_samples)
|
|
return;
|
|
|
|
m_debug.beginLabel(cmdBuf, "Compute");
|
|
|
|
// Adding a barrier to be sure the fragment has finished writing to the G-Buffer
|
|
// before the compute shader is using the buffer
|
|
vk::ImageSubresourceRange range{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1};
|
|
vk::ImageMemoryBarrier imgMemBarrier;
|
|
imgMemBarrier.setSrcAccessMask(vk::AccessFlagBits::eShaderWrite);
|
|
imgMemBarrier.setDstAccessMask(vk::AccessFlagBits::eShaderRead);
|
|
imgMemBarrier.setImage(m_gBuffer.image);
|
|
imgMemBarrier.setOldLayout(vk::ImageLayout::eGeneral);
|
|
imgMemBarrier.setNewLayout(vk::ImageLayout::eGeneral);
|
|
imgMemBarrier.setSubresourceRange(range);
|
|
cmdBuf.pipelineBarrier(vk::PipelineStageFlagBits::eFragmentShader,
|
|
vk::PipelineStageFlagBits::eComputeShader,
|
|
vk::DependencyFlagBits::eDeviceGroup, {}, {}, {imgMemBarrier});
|
|
|
|
|
|
// Preparing for the compute shader
|
|
cmdBuf.bindPipeline(vk::PipelineBindPoint::eCompute, m_compPipeline);
|
|
cmdBuf.bindDescriptorSets(vk::PipelineBindPoint::eCompute, m_compPipelineLayout, 0,
|
|
{m_compDescSet}, {});
|
|
|
|
// Sending the push constant information
|
|
aoControl.frame = m_frame;
|
|
cmdBuf.pushConstants(m_compPipelineLayout, vk::ShaderStageFlagBits::eCompute, 0,
|
|
sizeof(AoControl), &aoControl);
|
|
|
|
// Dispatching the shader
|
|
cmdBuf.dispatch((m_size.width + (GROUP_SIZE - 1)) / GROUP_SIZE,
|
|
(m_size.height + (GROUP_SIZE - 1)) / GROUP_SIZE, 1);
|
|
|
|
|
|
// Adding a barrier to be sure the compute shader has finished
|
|
// writing to the AO buffer before the post shader is using it
|
|
imgMemBarrier.setImage(m_aoBuffer.image);
|
|
imgMemBarrier.setOldLayout(vk::ImageLayout::eGeneral);
|
|
imgMemBarrier.setNewLayout(vk::ImageLayout::eGeneral);
|
|
imgMemBarrier.setSubresourceRange(range);
|
|
cmdBuf.pipelineBarrier(vk::PipelineStageFlagBits::eComputeShader,
|
|
vk::PipelineStageFlagBits::eFragmentShader,
|
|
vk::DependencyFlagBits::eDeviceGroup, {}, {}, {imgMemBarrier});
|
|
|
|
m_debug.endLabel(cmdBuf);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Reset from JITTER CAM tutorial
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// If the camera matrix has changed, resets the frame otherwise, increments frame.
|
|
//
|
|
void HelloVulkan::updateFrame()
|
|
{
|
|
static nvmath::mat4f refCamMatrix;
|
|
static float fov = 0;
|
|
|
|
auto& m = CameraManip.getMatrix();
|
|
auto f = CameraManip.getFov();
|
|
if(memcmp(&refCamMatrix.a00, &m.a00, sizeof(nvmath::mat4f)) != 0 || f != fov)
|
|
{
|
|
resetFrame();
|
|
refCamMatrix = m;
|
|
fov = f;
|
|
}
|
|
|
|
m_frame++;
|
|
}
|
|
|
|
void HelloVulkan::resetFrame()
|
|
{
|
|
m_frame = -1;
|
|
}
|