You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
852 lines
33 KiB
852 lines
33 KiB
/*
|
|
* Copyright (c) 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 "nvvkhl/appbase_vk.hpp"
|
|
#include "nvp/perproject_globals.hpp"
|
|
#include "backends/imgui_impl_glfw.h"
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Creation order of all elements for the application
|
|
// First keep the Vulkan instance, device, ... in class members
|
|
// Then create the swapchain, a depth buffer, a default render pass and the
|
|
// framebuffers for the swapchain (all sharing the depth image)
|
|
// Initialize Imgui and setup callback functions for windows operations (mouse, key, ...)
|
|
void nvvkhl::AppBaseVk::create(const AppBaseVkCreateInfo& info)
|
|
{
|
|
m_useDynamicRendering = info.useDynamicRendering;
|
|
setup(info.instance, info.device, info.physicalDevice, info.queueIndices[0]);
|
|
createSwapchain(info.surface, info.size.width, info.size.height, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_UNDEFINED, info.useVsync);
|
|
createDepthBuffer();
|
|
createRenderPass();
|
|
createFrameBuffers();
|
|
initGUI();
|
|
setupGlfwCallbacks(info.window);
|
|
ImGui_ImplGlfw_InitForVulkan(info.window, true);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Setup the low level Vulkan for various operations
|
|
//
|
|
void nvvkhl::AppBaseVk::setup(const VkInstance& instance, const VkDevice& device, const VkPhysicalDevice& physicalDevice, uint32_t graphicsQueueIndex)
|
|
{
|
|
m_instance = instance;
|
|
m_device = device;
|
|
m_physicalDevice = physicalDevice;
|
|
m_graphicsQueueIndex = graphicsQueueIndex;
|
|
vkGetDeviceQueue(m_device, m_graphicsQueueIndex, 0, &m_queue);
|
|
|
|
VkCommandPoolCreateInfo poolCreateInfo{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
|
poolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
vkCreateCommandPool(m_device, &poolCreateInfo, nullptr, &m_cmdPool);
|
|
|
|
VkPipelineCacheCreateInfo pipelineCacheInfo{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO};
|
|
vkCreatePipelineCache(m_device, &pipelineCacheInfo, nullptr, &m_pipelineCache);
|
|
|
|
ImGuiH::SetCameraJsonFile(getProjectName());
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// To call on exit
|
|
//
|
|
void nvvkhl::AppBaseVk::destroy()
|
|
{
|
|
vkDeviceWaitIdle(m_device);
|
|
|
|
if(ImGui::GetCurrentContext() != nullptr)
|
|
{
|
|
// In case multiple ImGUI contexts are used in the same application, the VK side may not own ImGui resources
|
|
if(ImGui::GetIO().BackendRendererUserData)
|
|
ImGui_ImplVulkan_Shutdown();
|
|
|
|
ImGui::DestroyContext();
|
|
}
|
|
|
|
if(!m_useDynamicRendering)
|
|
vkDestroyRenderPass(m_device, m_renderPass, nullptr);
|
|
|
|
vkDestroyImageView(m_device, m_depthView, nullptr);
|
|
vkDestroyImage(m_device, m_depthImage, nullptr);
|
|
vkFreeMemory(m_device, m_depthMemory, nullptr);
|
|
vkDestroyPipelineCache(m_device, m_pipelineCache, nullptr);
|
|
|
|
for(uint32_t i = 0; i < m_swapChain.getImageCount(); i++)
|
|
{
|
|
vkDestroyFence(m_device, m_waitFences[i], nullptr);
|
|
|
|
if(!m_useDynamicRendering)
|
|
vkDestroyFramebuffer(m_device, m_framebuffers[i], nullptr);
|
|
|
|
vkFreeCommandBuffers(m_device, m_cmdPool, 1, &m_commandBuffers[i]);
|
|
}
|
|
m_swapChain.deinit();
|
|
vkDestroyDescriptorPool(m_device, m_imguiDescPool, nullptr);
|
|
vkDestroyCommandPool(m_device, m_cmdPool, nullptr);
|
|
|
|
if(m_surface)
|
|
vkDestroySurfaceKHR(m_instance, m_surface, nullptr);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Return the surface "screen" for the display
|
|
//
|
|
VkSurfaceKHR nvvkhl::AppBaseVk::getVkSurface(const VkInstance& instance, GLFWwindow* window)
|
|
{
|
|
assert(instance);
|
|
m_window = window;
|
|
|
|
VkSurfaceKHR surface{};
|
|
VkResult err = glfwCreateWindowSurface(instance, window, nullptr, &surface);
|
|
|
|
if(err != VK_SUCCESS)
|
|
assert(!"Failed to create a Window surface");
|
|
|
|
m_surface = surface;
|
|
|
|
return surface;
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Creating the surface for rendering
|
|
//
|
|
void nvvkhl::AppBaseVk::createSwapchain(const VkSurfaceKHR& surface,
|
|
uint32_t width,
|
|
uint32_t height,
|
|
VkFormat colorFormat /*= VK_FORMAT_B8G8R8A8_UNORM*/,
|
|
VkFormat depthFormat /*= VK_FORMAT_UNDEFINED*/,
|
|
bool vsync /*= false*/)
|
|
{
|
|
m_size = VkExtent2D{width, height};
|
|
m_colorFormat = colorFormat;
|
|
m_depthFormat = depthFormat;
|
|
m_vsync = vsync;
|
|
|
|
// Find the most suitable depth format
|
|
if(m_depthFormat == VK_FORMAT_UNDEFINED)
|
|
{
|
|
auto feature = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
|
|
for(const auto& f : {VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D16_UNORM_S8_UINT})
|
|
{
|
|
VkFormatProperties formatProp{VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2};
|
|
vkGetPhysicalDeviceFormatProperties(m_physicalDevice, f, &formatProp);
|
|
if((formatProp.optimalTilingFeatures & feature) == feature)
|
|
{
|
|
m_depthFormat = f;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_swapChain.init(m_device, m_physicalDevice, m_queue, m_graphicsQueueIndex, surface, static_cast<VkFormat>(colorFormat));
|
|
m_size = m_swapChain.update(m_size.width, m_size.height, vsync);
|
|
m_colorFormat = static_cast<VkFormat>(m_swapChain.getFormat());
|
|
|
|
// Create Synchronization Primitives
|
|
m_waitFences.resize(m_swapChain.getImageCount());
|
|
for(auto& fence : m_waitFences)
|
|
{
|
|
VkFenceCreateInfo fenceCreateInfo{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO};
|
|
fenceCreateInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
|
vkCreateFence(m_device, &fenceCreateInfo, nullptr, &fence);
|
|
}
|
|
|
|
// Command buffers store a reference to the frame buffer inside their render pass info
|
|
// so for static usage without having to rebuild them each frame, we use one per frame buffer
|
|
VkCommandBufferAllocateInfo allocateInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
|
allocateInfo.commandPool = m_cmdPool;
|
|
allocateInfo.commandBufferCount = m_swapChain.getImageCount();
|
|
allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
m_commandBuffers.resize(m_swapChain.getImageCount());
|
|
vkAllocateCommandBuffers(m_device, &allocateInfo, m_commandBuffers.data());
|
|
|
|
auto cmdBuffer = createTempCmdBuffer();
|
|
m_swapChain.cmdUpdateBarriers(cmdBuffer);
|
|
submitTempCmdBuffer(cmdBuffer);
|
|
|
|
#ifdef _DEBUG
|
|
for(size_t i = 0; i < m_commandBuffers.size(); i++)
|
|
{
|
|
std::string name = std::string("AppBase") + std::to_string(i);
|
|
|
|
VkDebugUtilsObjectNameInfoEXT nameInfo{VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT};
|
|
nameInfo.objectHandle = (uint64_t)m_commandBuffers[i];
|
|
nameInfo.objectType = VK_OBJECT_TYPE_COMMAND_BUFFER;
|
|
nameInfo.pObjectName = name.c_str();
|
|
vkSetDebugUtilsObjectNameEXT(m_device, &nameInfo);
|
|
}
|
|
#endif // _DEBUG
|
|
|
|
// Setup camera
|
|
CameraManip.setWindowSize(m_size.width, m_size.height);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Create all the framebuffers in which the image will be rendered
|
|
// - Swapchain need to be created before calling this
|
|
//
|
|
void nvvkhl::AppBaseVk::createFrameBuffers()
|
|
{
|
|
if(m_useDynamicRendering)
|
|
return;
|
|
|
|
// Recreate the frame buffers
|
|
for(auto framebuffer : m_framebuffers)
|
|
vkDestroyFramebuffer(m_device, framebuffer, nullptr);
|
|
|
|
// Array of attachment (color, depth)
|
|
std::array<VkImageView, 2> attachments{};
|
|
|
|
// Create frame buffers for every swap chain image
|
|
VkFramebufferCreateInfo framebufferCreateInfo{VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO};
|
|
framebufferCreateInfo.renderPass = m_renderPass;
|
|
framebufferCreateInfo.attachmentCount = 2;
|
|
framebufferCreateInfo.width = m_size.width;
|
|
framebufferCreateInfo.height = m_size.height;
|
|
framebufferCreateInfo.layers = 1;
|
|
framebufferCreateInfo.pAttachments = attachments.data();
|
|
|
|
// Create frame buffers for every swap chain image
|
|
m_framebuffers.resize(m_swapChain.getImageCount());
|
|
for(uint32_t i = 0; i < m_swapChain.getImageCount(); i++)
|
|
{
|
|
attachments[0] = m_swapChain.getImageView(i);
|
|
attachments[1] = m_depthView;
|
|
vkCreateFramebuffer(m_device, &framebufferCreateInfo, nullptr, &m_framebuffers[i]);
|
|
}
|
|
|
|
|
|
#ifdef _DEBUG
|
|
for(size_t i = 0; i < m_framebuffers.size(); i++)
|
|
{
|
|
std::string name = std::string("AppBase") + std::to_string(i);
|
|
VkDebugUtilsObjectNameInfoEXT nameInfo{VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT};
|
|
nameInfo.objectHandle = (uint64_t)m_framebuffers[i];
|
|
nameInfo.objectType = VK_OBJECT_TYPE_FRAMEBUFFER;
|
|
nameInfo.pObjectName = name.c_str();
|
|
vkSetDebugUtilsObjectNameEXT(m_device, &nameInfo);
|
|
}
|
|
#endif // _DEBUG
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Creating a default render pass, very simple one.
|
|
// Other examples will mostly override this one.
|
|
//
|
|
void nvvkhl::AppBaseVk::createRenderPass()
|
|
{
|
|
if(m_useDynamicRendering)
|
|
return;
|
|
|
|
if(m_renderPass)
|
|
vkDestroyRenderPass(m_device, m_renderPass, nullptr);
|
|
|
|
std::array<VkAttachmentDescription, 2> attachments{};
|
|
// Color attachment
|
|
attachments[0].format = m_colorFormat;
|
|
attachments[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
attachments[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
|
|
attachments[0].samples = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
// Depth attachment
|
|
attachments[1].format = m_depthFormat;
|
|
attachments[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
attachments[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
|
attachments[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
attachments[1].samples = VK_SAMPLE_COUNT_1_BIT;
|
|
|
|
// One color, one depth
|
|
const VkAttachmentReference colorReference{0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL};
|
|
const VkAttachmentReference depthReference{1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL};
|
|
|
|
std::array<VkSubpassDependency, 1> subpassDependencies{};
|
|
// Transition from final to initial (VK_SUBPASS_EXTERNAL refers to all commands executed outside of the actual renderpass)
|
|
subpassDependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
|
|
subpassDependencies[0].dstSubpass = 0;
|
|
subpassDependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
|
|
subpassDependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
subpassDependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
|
|
subpassDependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
|
|
subpassDependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
|
|
|
|
VkSubpassDescription subpassDescription{};
|
|
subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
|
|
subpassDescription.colorAttachmentCount = 1;
|
|
subpassDescription.pColorAttachments = &colorReference;
|
|
subpassDescription.pDepthStencilAttachment = &depthReference;
|
|
|
|
VkRenderPassCreateInfo renderPassInfo{VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO};
|
|
renderPassInfo.attachmentCount = static_cast<uint32_t>(attachments.size());
|
|
renderPassInfo.pAttachments = attachments.data();
|
|
renderPassInfo.subpassCount = 1;
|
|
renderPassInfo.pSubpasses = &subpassDescription;
|
|
renderPassInfo.dependencyCount = static_cast<uint32_t>(subpassDependencies.size());
|
|
renderPassInfo.pDependencies = subpassDependencies.data();
|
|
|
|
vkCreateRenderPass(m_device, &renderPassInfo, nullptr, &m_renderPass);
|
|
|
|
#ifdef _DEBUG
|
|
VkDebugUtilsObjectNameInfoEXT nameInfo{VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT};
|
|
nameInfo.objectHandle = (uint64_t)m_renderPass;
|
|
nameInfo.objectType = VK_OBJECT_TYPE_RENDER_PASS;
|
|
nameInfo.pObjectName = R"(AppBaseVk)";
|
|
vkSetDebugUtilsObjectNameEXT(m_device, &nameInfo);
|
|
#endif // _DEBUG
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Creating an image to be used as depth buffer
|
|
//
|
|
void nvvkhl::AppBaseVk::createDepthBuffer()
|
|
{
|
|
if(m_depthView)
|
|
vkDestroyImageView(m_device, m_depthView, nullptr);
|
|
|
|
if(m_depthImage)
|
|
vkDestroyImage(m_device, m_depthImage, nullptr);
|
|
|
|
if(m_depthMemory)
|
|
vkFreeMemory(m_device, m_depthMemory, nullptr);
|
|
|
|
// Depth information
|
|
const VkImageAspectFlags aspect = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
|
|
VkImageCreateInfo depthStencilCreateInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
|
|
depthStencilCreateInfo.imageType = VK_IMAGE_TYPE_2D;
|
|
depthStencilCreateInfo.extent = VkExtent3D{m_size.width, m_size.height, 1};
|
|
depthStencilCreateInfo.format = m_depthFormat;
|
|
depthStencilCreateInfo.mipLevels = 1;
|
|
depthStencilCreateInfo.arrayLayers = 1;
|
|
depthStencilCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
|
|
depthStencilCreateInfo.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
|
|
// Create the depth image
|
|
vkCreateImage(m_device, &depthStencilCreateInfo, nullptr, &m_depthImage);
|
|
|
|
#ifdef _DEBUG
|
|
std::string name = std::string("AppBaseDepth");
|
|
VkDebugUtilsObjectNameInfoEXT nameInfo{VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT};
|
|
nameInfo.objectHandle = (uint64_t)m_depthImage;
|
|
nameInfo.objectType = VK_OBJECT_TYPE_IMAGE;
|
|
nameInfo.pObjectName = R"(AppBase)";
|
|
vkSetDebugUtilsObjectNameEXT(m_device, &nameInfo);
|
|
#endif // _DEBUG
|
|
|
|
// Allocate the memory
|
|
VkMemoryRequirements memReqs;
|
|
vkGetImageMemoryRequirements(m_device, m_depthImage, &memReqs);
|
|
VkMemoryAllocateInfo memAllocInfo{VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
|
|
memAllocInfo.allocationSize = memReqs.size;
|
|
memAllocInfo.memoryTypeIndex = getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
|
|
vkAllocateMemory(m_device, &memAllocInfo, nullptr, &m_depthMemory);
|
|
|
|
// Bind image and memory
|
|
vkBindImageMemory(m_device, m_depthImage, m_depthMemory, 0);
|
|
|
|
auto cmdBuffer = createTempCmdBuffer();
|
|
|
|
// Put barrier on top, Put barrier inside setup command buffer
|
|
VkImageSubresourceRange subresourceRange{};
|
|
subresourceRange.aspectMask = aspect;
|
|
subresourceRange.levelCount = 1;
|
|
subresourceRange.layerCount = 1;
|
|
VkImageMemoryBarrier imageMemoryBarrier{VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER};
|
|
imageMemoryBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
|
|
imageMemoryBarrier.newLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
|
|
imageMemoryBarrier.image = m_depthImage;
|
|
imageMemoryBarrier.subresourceRange = subresourceRange;
|
|
imageMemoryBarrier.srcAccessMask = VkAccessFlags();
|
|
imageMemoryBarrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
|
|
const VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
|
|
const VkPipelineStageFlags destStageMask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
|
|
|
|
vkCmdPipelineBarrier(cmdBuffer, srcStageMask, destStageMask, VK_FALSE, 0, nullptr, 0, nullptr, 1, &imageMemoryBarrier);
|
|
submitTempCmdBuffer(cmdBuffer);
|
|
|
|
|
|
// Setting up the view
|
|
VkImageViewCreateInfo depthStencilView{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO};
|
|
depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
|
depthStencilView.format = m_depthFormat;
|
|
depthStencilView.subresourceRange = subresourceRange;
|
|
depthStencilView.image = m_depthImage;
|
|
vkCreateImageView(m_device, &depthStencilView, nullptr, &m_depthView);
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Convenient function to call before rendering.
|
|
// - Waits for a framebuffer to be available
|
|
// - Update camera matrix if in movement
|
|
void nvvkhl::AppBaseVk::prepareFrame()
|
|
{
|
|
// Resize protection - should be cached by the glFW callback
|
|
int w, h;
|
|
glfwGetFramebufferSize(m_window, &w, &h);
|
|
|
|
if(w != (int)m_size.width || h != (int)m_size.height)
|
|
onFramebufferSize(w, h);
|
|
|
|
// Acquire the next image from the swap chain
|
|
if(!m_swapChain.acquire())
|
|
assert(!"This shouldn't happen");
|
|
|
|
// Use a fence to wait until the command buffer has finished execution before using it again
|
|
uint32_t imageIndex = m_swapChain.getActiveImageIndex();
|
|
|
|
VkResult result{VK_SUCCESS};
|
|
do
|
|
{
|
|
result = vkWaitForFences(m_device, 1, &m_waitFences[imageIndex], VK_TRUE, 1'000'000);
|
|
} while(result == VK_TIMEOUT);
|
|
if(result != VK_SUCCESS)
|
|
{ // This allows Aftermath to do things and later assert below
|
|
#ifdef _WIN32
|
|
Sleep(1000);
|
|
#else
|
|
usleep(1000);
|
|
#endif
|
|
}
|
|
assert(result == VK_SUCCESS);
|
|
|
|
// start new frame with updated camera
|
|
updateCamera();
|
|
updateInputs();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Convenient function to call for submitting the rendering command
|
|
// Sending the command buffer of the current frame and add a fence to know when it will be free to use
|
|
//
|
|
void nvvkhl::AppBaseVk::submitFrame()
|
|
{
|
|
uint32_t imageIndex = m_swapChain.getActiveImageIndex();
|
|
vkResetFences(m_device, 1, &m_waitFences[imageIndex]);
|
|
|
|
// In case of using NVLINK
|
|
const uint32_t deviceMask = m_useNvlink ? 0b0000'0011 : 0b0000'0001;
|
|
const std::array<uint32_t, 2> deviceIndex = {0, 1};
|
|
|
|
VkDeviceGroupSubmitInfo deviceGroupSubmitInfo{VK_STRUCTURE_TYPE_DEVICE_GROUP_SUBMIT_INFO_KHR};
|
|
deviceGroupSubmitInfo.waitSemaphoreCount = 1;
|
|
deviceGroupSubmitInfo.commandBufferCount = 1;
|
|
deviceGroupSubmitInfo.pCommandBufferDeviceMasks = &deviceMask;
|
|
deviceGroupSubmitInfo.signalSemaphoreCount = m_useNvlink ? 2 : 1;
|
|
deviceGroupSubmitInfo.pSignalSemaphoreDeviceIndices = deviceIndex.data();
|
|
deviceGroupSubmitInfo.pWaitSemaphoreDeviceIndices = deviceIndex.data();
|
|
|
|
VkSemaphore semaphoreRead = m_swapChain.getActiveReadSemaphore();
|
|
VkSemaphore semaphoreWrite = m_swapChain.getActiveWrittenSemaphore();
|
|
|
|
// Pipeline stage at which the queue submission will wait (via pWaitSemaphores)
|
|
const VkPipelineStageFlags waitStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
// The submit info structure specifies a command buffer queue submission batch
|
|
VkSubmitInfo submitInfo{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
|
submitInfo.pWaitDstStageMask = &waitStageMask; // Pointer to the list of pipeline stages that the semaphore waits will occur at
|
|
submitInfo.pWaitSemaphores = &semaphoreRead; // Semaphore(s) to wait upon before the submitted command buffer starts executing
|
|
submitInfo.waitSemaphoreCount = 1; // One wait semaphore
|
|
submitInfo.pSignalSemaphores = &semaphoreWrite; // Semaphore(s) to be signaled when command buffers have completed
|
|
submitInfo.signalSemaphoreCount = 1; // One signal semaphore
|
|
submitInfo.pCommandBuffers = &m_commandBuffers[imageIndex]; // Command buffers(s) to execute in this batch (submission)
|
|
submitInfo.commandBufferCount = 1; // One command buffer
|
|
submitInfo.pNext = &deviceGroupSubmitInfo;
|
|
|
|
// Submit to the graphics queue passing a wait fence
|
|
vkQueueSubmit(m_queue, 1, &submitInfo, m_waitFences[imageIndex]);
|
|
|
|
// Presenting frame
|
|
m_swapChain.present(m_queue);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// When the pipeline is set for using dynamic, this becomes useful
|
|
//
|
|
void nvvkhl::AppBaseVk::setViewport(const VkCommandBuffer& cmdBuf)
|
|
{
|
|
VkViewport viewport{0.0f, 0.0f, static_cast<float>(m_size.width), static_cast<float>(m_size.height), 0.0f, 1.0f};
|
|
vkCmdSetViewport(cmdBuf, 0, 1, &viewport);
|
|
|
|
VkRect2D scissor{{0, 0}, {m_size.width, m_size.height}};
|
|
vkCmdSetScissor(cmdBuf, 0, 1, &scissor);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Window callback when the it is resized
|
|
// - Destroy allocated frames, then rebuild them with the new size
|
|
// - Call onResize() of the derived class
|
|
//
|
|
void nvvkhl::AppBaseVk::onFramebufferSize(int w, int h)
|
|
{
|
|
if(w == 0 || h == 0)
|
|
return;
|
|
|
|
// Update imgui
|
|
if(ImGui::GetCurrentContext() != nullptr)
|
|
{
|
|
auto& imgui_io = ImGui::GetIO();
|
|
imgui_io.DisplaySize = ImVec2(static_cast<float>(w), static_cast<float>(h));
|
|
}
|
|
|
|
// Wait to finish what is currently drawing
|
|
vkDeviceWaitIdle(m_device);
|
|
vkQueueWaitIdle(m_queue);
|
|
|
|
// Request new swapchain image size
|
|
m_size = m_swapChain.update(w, h, m_vsync);
|
|
auto cmdBuffer = createTempCmdBuffer();
|
|
m_swapChain.cmdUpdateBarriers(cmdBuffer); // Make them presentable
|
|
submitTempCmdBuffer(cmdBuffer);
|
|
|
|
if(m_size.width != w || m_size.height != h)
|
|
LOGW("Requested size (%d, %d) is different from created size (%u, %u) ", w, h, m_size.width, m_size.height);
|
|
|
|
CameraManip.setWindowSize(m_size.width, m_size.height);
|
|
// Invoking Sample callback
|
|
onResize(m_size.width, m_size.height); // <-- to implement on derived class
|
|
// Recreating other resources
|
|
createDepthBuffer();
|
|
createFrameBuffers();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Window callback when the mouse move
|
|
// - Handling ImGui and a default camera
|
|
//
|
|
void nvvkhl::AppBaseVk::onMouseMotion(int x, int y)
|
|
{
|
|
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureMouse)
|
|
return;
|
|
|
|
if(m_inputs.lmb || m_inputs.rmb || m_inputs.mmb)
|
|
CameraManip.mouseMove(x, y, m_inputs);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Window callback when a special key gets hit
|
|
// - Handling ImGui and a default camera
|
|
//
|
|
void nvvkhl::AppBaseVk::onKeyboard(int key, int /*scancode*/, int action, int mods)
|
|
{
|
|
const bool pressed = action != GLFW_RELEASE;
|
|
|
|
if(pressed && key == GLFW_KEY_F11)
|
|
m_show_gui = !m_show_gui;
|
|
else if(pressed && key == GLFW_KEY_ESCAPE)
|
|
glfwSetWindowShouldClose(m_window, 1);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Window callback when a key gets hit
|
|
//
|
|
void nvvkhl::AppBaseVk::onKeyboardChar(unsigned char key)
|
|
{
|
|
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureKeyboard)
|
|
return;
|
|
|
|
// Toggling vsync
|
|
if(key == 'v')
|
|
{
|
|
m_vsync = !m_vsync;
|
|
vkDeviceWaitIdle(m_device);
|
|
vkQueueWaitIdle(m_queue);
|
|
m_swapChain.update(m_size.width, m_size.height, m_vsync);
|
|
auto cmdBuffer = createTempCmdBuffer();
|
|
m_swapChain.cmdUpdateBarriers(cmdBuffer); // Make them presentable
|
|
submitTempCmdBuffer(cmdBuffer);
|
|
createFrameBuffers();
|
|
}
|
|
|
|
if(key == 'h' || key == '?')
|
|
m_showHelp = !m_showHelp;
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Window callback when the mouse button is pressed
|
|
// - Handling ImGui and a default camera
|
|
//
|
|
void nvvkhl::AppBaseVk::onMouseButton(int button, int action, int mods)
|
|
{
|
|
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureMouse)
|
|
return;
|
|
|
|
double x, y;
|
|
glfwGetCursorPos(m_window, &x, &y);
|
|
CameraManip.setMousePosition(static_cast<int>(x), static_cast<int>(y));
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Window callback when the mouse wheel is modified
|
|
// - Handling ImGui and a default camera
|
|
//
|
|
void nvvkhl::AppBaseVk::onMouseWheel(int delta)
|
|
{
|
|
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureMouse)
|
|
return;
|
|
|
|
if(delta != 0)
|
|
CameraManip.wheel(delta > 0 ? 1 : -1, m_inputs);
|
|
}
|
|
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Called every frame to translate currently pressed keys into camera movement
|
|
//
|
|
void nvvkhl::AppBaseVk::updateCamera()
|
|
{
|
|
// measure one frame at a time
|
|
float factor = ImGui::GetIO().DeltaTime * 1000 * m_sceneRadius;
|
|
|
|
m_inputs.lmb = ImGui::IsMouseDown(ImGuiMouseButton_Left);
|
|
m_inputs.rmb = ImGui::IsMouseDown(ImGuiMouseButton_Right);
|
|
m_inputs.mmb = ImGui::IsMouseDown(ImGuiMouseButton_Middle);
|
|
m_inputs.ctrl = ImGui::IsKeyDown(ImGuiKey_LeftCtrl) || ImGui::IsKeyDown(ImGuiKey_RightCtrl);
|
|
m_inputs.shift = ImGui::IsKeyDown(ImGuiKey_LeftShift) || ImGui::IsKeyDown(ImGuiKey_RightShift);
|
|
m_inputs.alt = ImGui::IsKeyDown(ImGuiKey_LeftAlt) || ImGui::IsKeyDown(ImGuiKey_RightAlt);
|
|
|
|
// Allow camera movement only when not editing
|
|
if(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().WantCaptureKeyboard)
|
|
return;
|
|
|
|
// For all pressed keys - apply the action
|
|
CameraManip.keyMotion(0, 0, nvh::CameraManipulator::NoAction);
|
|
|
|
if(!(ImGui::IsKeyDown(ImGuiKey_ModAlt) || ImGui::IsKeyDown(ImGuiKey_ModCtrl) || ImGui::IsKeyDown(ImGuiKey_ModShift)))
|
|
{
|
|
if(ImGui::IsKeyDown(ImGuiKey_W))
|
|
CameraManip.keyMotion(factor, 0, nvh::CameraManipulator::Dolly);
|
|
|
|
if(ImGui::IsKeyDown(ImGuiKey_S))
|
|
CameraManip.keyMotion(-factor, 0, nvh::CameraManipulator::Dolly);
|
|
|
|
if(ImGui::IsKeyDown(ImGuiKey_D) || ImGui::IsKeyDown(ImGuiKey_RightArrow))
|
|
CameraManip.keyMotion(factor, 0, nvh::CameraManipulator::Pan);
|
|
|
|
if(ImGui::IsKeyDown(ImGuiKey_A) || ImGui::IsKeyDown(ImGuiKey_LeftArrow))
|
|
CameraManip.keyMotion(-factor, 0, nvh::CameraManipulator::Pan);
|
|
|
|
if(ImGui::IsKeyDown(ImGuiKey_UpArrow))
|
|
CameraManip.keyMotion(0, factor, nvh::CameraManipulator::Pan);
|
|
|
|
if(ImGui::IsKeyDown(ImGuiKey_DownArrow))
|
|
CameraManip.keyMotion(0, -factor, nvh::CameraManipulator::Pan);
|
|
}
|
|
|
|
// This makes the camera to transition smoothly to the new position
|
|
CameraManip.updateAnim();
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Initialization of the GUI
|
|
// - Need to be call after the device creation
|
|
//
|
|
void nvvkhl::AppBaseVk::initGUI(uint32_t subpassID /*= 0*/)
|
|
{
|
|
//assert(m_renderPass && "Render Pass must be set");
|
|
|
|
// UI
|
|
ImGui::CreateContext();
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
io.IniFilename = nullptr; // Avoiding the INI file
|
|
io.LogFilename = nullptr;
|
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
|
|
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
|
|
//io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
|
|
|
|
ImGuiH::setStyle();
|
|
ImGuiH::setFonts();
|
|
|
|
std::vector<VkDescriptorPoolSize> poolSize{{VK_DESCRIPTOR_TYPE_SAMPLER, 1}, {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1}};
|
|
VkDescriptorPoolCreateInfo poolInfo{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
|
poolInfo.maxSets = 2;
|
|
poolInfo.poolSizeCount = 2;
|
|
poolInfo.pPoolSizes = poolSize.data();
|
|
vkCreateDescriptorPool(m_device, &poolInfo, nullptr, &m_imguiDescPool);
|
|
|
|
// Setup Platform/Renderer back ends
|
|
ImGui_ImplVulkan_InitInfo init_info = {};
|
|
init_info.Instance = m_instance;
|
|
init_info.PhysicalDevice = m_physicalDevice;
|
|
init_info.Device = m_device;
|
|
init_info.QueueFamily = m_graphicsQueueIndex;
|
|
init_info.Queue = m_queue;
|
|
init_info.PipelineCache = VK_NULL_HANDLE;
|
|
init_info.DescriptorPool = m_imguiDescPool;
|
|
init_info.Subpass = subpassID;
|
|
init_info.MinImageCount = 2;
|
|
init_info.ImageCount = static_cast<int>(m_swapChain.getImageCount());
|
|
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT; // <--- need argument?
|
|
init_info.CheckVkResultFn = nullptr;
|
|
init_info.Allocator = nullptr;
|
|
|
|
#ifdef VK_KHR_dynamic_rendering
|
|
VkPipelineRenderingCreateInfoKHR rfInfo{VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR};
|
|
if(m_useDynamicRendering)
|
|
{
|
|
rfInfo.colorAttachmentCount = 1;
|
|
rfInfo.pColorAttachmentFormats = &m_colorFormat;
|
|
rfInfo.depthAttachmentFormat = m_depthFormat;
|
|
rfInfo.stencilAttachmentFormat = m_depthFormat;
|
|
init_info.rinfo = &rfInfo;
|
|
}
|
|
#endif
|
|
|
|
ImGui_ImplVulkan_Init(&init_info, m_renderPass);
|
|
|
|
// Upload Fonts
|
|
VkCommandBuffer cmdbuf = createTempCmdBuffer();
|
|
ImGui_ImplVulkan_CreateFontsTexture(cmdbuf);
|
|
submitTempCmdBuffer(cmdbuf);
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Fit the camera to the Bounding box
|
|
//
|
|
void nvvkhl::AppBaseVk::fitCamera(const nvmath::vec3f& boxMin, const nvmath::vec3f& boxMax, bool instantFit /*= true*/)
|
|
{
|
|
CameraManip.fit(boxMin, boxMax, instantFit, false, m_size.width / static_cast<float>(m_size.height));
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// Return true if the window is minimized
|
|
//
|
|
bool nvvkhl::AppBaseVk::isMinimized(bool doSleeping /*= true*/)
|
|
{
|
|
int w, h;
|
|
glfwGetWindowSize(m_window, &w, &h);
|
|
bool minimized(w == 0 || h == 0);
|
|
if(minimized && doSleeping)
|
|
{
|
|
#ifdef _WIN32
|
|
Sleep(50);
|
|
#else
|
|
usleep(50);
|
|
#endif
|
|
}
|
|
return minimized;
|
|
}
|
|
|
|
void nvvkhl::AppBaseVk::setupGlfwCallbacks(GLFWwindow* window)
|
|
{
|
|
m_window = window;
|
|
glfwSetWindowUserPointer(window, this);
|
|
glfwSetKeyCallback(window, &key_cb);
|
|
glfwSetCharCallback(window, &char_cb);
|
|
glfwSetCursorPosCallback(window, &cursorpos_cb);
|
|
glfwSetMouseButtonCallback(window, &mousebutton_cb);
|
|
glfwSetScrollCallback(window, &scroll_cb);
|
|
glfwSetFramebufferSizeCallback(window, &framebuffersize_cb);
|
|
glfwSetDropCallback(window, &drop_cb);
|
|
}
|
|
|
|
void nvvkhl::AppBaseVk::framebuffersize_cb(GLFWwindow* window, int w, int h)
|
|
{
|
|
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
|
|
app->onFramebufferSize(w, h);
|
|
}
|
|
|
|
void nvvkhl::AppBaseVk::mousebutton_cb(GLFWwindow* window, int button, int action, int mods)
|
|
{
|
|
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
|
|
app->onMouseButton(button, action, mods);
|
|
}
|
|
|
|
void nvvkhl::AppBaseVk::cursorpos_cb(GLFWwindow* window, double x, double y)
|
|
{
|
|
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
|
|
app->onMouseMotion(static_cast<int>(x), static_cast<int>(y));
|
|
}
|
|
|
|
void nvvkhl::AppBaseVk::scroll_cb(GLFWwindow* window, double x, double y)
|
|
{
|
|
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
|
|
app->onMouseWheel(static_cast<int>(y));
|
|
}
|
|
|
|
void nvvkhl::AppBaseVk::key_cb(GLFWwindow* window, int key, int scancode, int action, int mods)
|
|
{
|
|
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
|
|
app->onKeyboard(key, scancode, action, mods);
|
|
}
|
|
|
|
void nvvkhl::AppBaseVk::char_cb(GLFWwindow* window, unsigned int key)
|
|
{
|
|
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
|
|
app->onKeyboardChar(key);
|
|
}
|
|
|
|
void nvvkhl::AppBaseVk::drop_cb(GLFWwindow* window, int count, const char** paths)
|
|
{
|
|
auto app = static_cast<AppBaseVk*>(glfwGetWindowUserPointer(window));
|
|
int i;
|
|
for(i = 0; i < count; i++)
|
|
app->onFileDrop(paths[i]);
|
|
}
|
|
|
|
uint32_t nvvkhl::AppBaseVk::getMemoryType(uint32_t typeBits, const VkMemoryPropertyFlags& properties) const
|
|
{
|
|
VkPhysicalDeviceMemoryProperties memoryProperties;
|
|
vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memoryProperties);
|
|
|
|
for(uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++)
|
|
{
|
|
if(((typeBits & (1 << i)) > 0) && (memoryProperties.memoryTypes[i].propertyFlags & properties) == properties)
|
|
return i;
|
|
}
|
|
LOGE("Unable to find memory type %u\n", static_cast<unsigned int>(properties));
|
|
assert(0);
|
|
return ~0u;
|
|
}
|
|
|
|
// Showing help
|
|
void nvvkhl::AppBaseVk::uiDisplayHelp()
|
|
{
|
|
if(m_showHelp)
|
|
{
|
|
ImGui::BeginChild("Help", ImVec2(370, 120), true);
|
|
ImGui::Text("%s", CameraManip.getHelp().c_str());
|
|
ImGui::EndChild();
|
|
}
|
|
}
|
|
|
|
VkCommandBuffer nvvkhl::AppBaseVk::createTempCmdBuffer()
|
|
{
|
|
// Create an image barrier to change the layout from undefined to DepthStencilAttachmentOptimal
|
|
VkCommandBufferAllocateInfo allocateInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
|
allocateInfo.commandBufferCount = 1;
|
|
allocateInfo.commandPool = m_cmdPool;
|
|
allocateInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
VkCommandBuffer cmdBuffer;
|
|
vkAllocateCommandBuffers(m_device, &allocateInfo, &cmdBuffer);
|
|
|
|
VkCommandBufferBeginInfo beginInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
|
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
vkBeginCommandBuffer(cmdBuffer, &beginInfo);
|
|
return cmdBuffer;
|
|
}
|
|
|
|
void nvvkhl::AppBaseVk::submitTempCmdBuffer(VkCommandBuffer cmdBuffer)
|
|
{
|
|
vkEndCommandBuffer(cmdBuffer);
|
|
|
|
VkSubmitInfo submitInfo{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
|
submitInfo.commandBufferCount = 1;
|
|
submitInfo.pCommandBuffers = &cmdBuffer;
|
|
vkQueueSubmit(m_queue, 1, &submitInfo, {});
|
|
vkQueueWaitIdle(m_queue);
|
|
vkFreeCommandBuffers(m_device, m_cmdPool, 1, &cmdBuffer);
|
|
}
|
|
|