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.
783 lines
27 KiB
783 lines
27 KiB
/*
|
|
* Copyright (c) 2022, 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-2022 NVIDIA CORPORATION
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
|
|
/*
|
|
The Application is basically a small modification of the ImGui example for Vulkan.
|
|
Because we support multiple viewports, duplicating the code would be not necessary
|
|
and the code is very well explained.
|
|
|
|
Worth notice
|
|
|
|
- ::init() : will create the GLFW window, call nvvk::context for the creation of the
|
|
Vulkan context, initialize ImGui , create the surface and window (::setupVulkanWindow)
|
|
|
|
- ::shutdown() : the oposite of init
|
|
|
|
- ::run() : while running, render the frame and present the frame. Check for resize, minimize window
|
|
and other changes. In the loop, it will call some functions for each 'element' that is connected.
|
|
onUIRender, onUIMenu, onRender. See IApplication for details.
|
|
*/
|
|
|
|
|
|
#include <iostream>
|
|
#include <filesystem>
|
|
#include <imgui_internal.h>
|
|
|
|
#include "application.hpp"
|
|
|
|
#include "backends/imgui_impl_glfw.h"
|
|
#include "imgui/backends/imgui_impl_vulkan.h"
|
|
#include "imgui/imgui_camera_widget.h"
|
|
#include "imgui/imgui_helper.h"
|
|
#include "nvp/nvpsystem.hpp"
|
|
#include "nvp/perproject_globals.hpp"
|
|
#include "nvvk/context_vk.hpp"
|
|
#include "nvvk/error_vk.hpp"
|
|
|
|
|
|
#ifdef LINUX
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
// Embedded font
|
|
#include "Roboto-Regular.h"
|
|
|
|
// GLFW
|
|
#ifdef _WIN32
|
|
#define GLFW_EXPOSE_NATIVE_WIN32
|
|
#endif
|
|
#include <GLFW/glfw3.h>
|
|
#include <GLFW/glfw3native.h>
|
|
|
|
// Static
|
|
uint32_t nvvkhl::Application::m_currentFrameIndex{0};
|
|
std::vector<std::vector<std::function<void()>>> nvvkhl::Application::m_resourceFreeQueue;
|
|
|
|
// GLFW Callback functions
|
|
static void onErrorCallback(int error, const char* description)
|
|
{
|
|
fprintf(stderr, "GLFW Error %d: %s\n", error, description);
|
|
}
|
|
|
|
static void checkVkResult(VkResult err)
|
|
{
|
|
if(err == 0)
|
|
{
|
|
return;
|
|
}
|
|
fprintf(stderr, "[vulkan] Error: VkResult = %d\n", err);
|
|
if(err < 0)
|
|
{
|
|
abort();
|
|
}
|
|
}
|
|
|
|
void dropCb(GLFWwindow* window, int count, const char** paths)
|
|
{
|
|
auto* app = static_cast<nvvkhl::Application*>(glfwGetWindowUserPointer(window));
|
|
for(int i = 0; i < count; i++)
|
|
{
|
|
app->onFileDrop(paths[i]);
|
|
}
|
|
}
|
|
|
|
|
|
nvvkhl::Application::Application(ApplicationCreateInfo& info)
|
|
{
|
|
init(info);
|
|
}
|
|
|
|
|
|
nvvkhl::Application::~Application()
|
|
{
|
|
shutdown();
|
|
}
|
|
|
|
void nvvkhl::Application::init(ApplicationCreateInfo& info)
|
|
{
|
|
auto path_log = std::filesystem::path(NVPSystem::exePath())
|
|
/ std::filesystem::path(std::string("log_") + getProjectName() + std::string(".txt"));
|
|
auto path_ini = std::filesystem::path(NVPSystem::exePath()) / std::filesystem::path(getProjectName() + std::string(".ini"));
|
|
|
|
m_clearColor = info.clearColor;
|
|
m_dockSetup = info.dockSetup;
|
|
|
|
// setup some basic things for the sample, logging file for example
|
|
nvprintSetLogFileName(path_log.string().c_str());
|
|
|
|
m_mainWindowData = std::make_unique<ImGui_ImplVulkanH_Window>();
|
|
|
|
m_useMenubar = info.useMenu;
|
|
m_useDockMenubar = info.useDockMenu;
|
|
m_vsyncWanted = info.vSync;
|
|
|
|
// Setup GLFW window
|
|
glfwSetErrorCallback(onErrorCallback);
|
|
if(glfwInit() == 0)
|
|
{
|
|
std::cerr << "Could not initialize GLFW!\n";
|
|
return;
|
|
}
|
|
|
|
const GLFWvidmode* mode = glfwGetVideoMode(glfwGetPrimaryMonitor());
|
|
|
|
// If not defined, set the window to 80% of the screen size
|
|
if(info.width <= 0 || info.height <= 0)
|
|
{
|
|
info.width = static_cast<int>(static_cast<float>(mode->width) * 0.8F);
|
|
info.height = static_cast<int>(static_cast<float>(mode->height) * 0.8F);
|
|
}
|
|
|
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
|
m_windowHandle = glfwCreateWindow(info.width, info.height, info.name.c_str(), nullptr, nullptr);
|
|
|
|
glfwSetWindowPos(m_windowHandle, static_cast<int>((mode->width - info.width) * 0.5),
|
|
static_cast<int>((mode->height - info.height) * 0.5));
|
|
|
|
// Set the Drop callback
|
|
glfwSetWindowUserPointer(m_windowHandle, this);
|
|
glfwSetDropCallback(m_windowHandle, &dropCb);
|
|
|
|
// Setup Vulkan
|
|
if(glfwVulkanSupported() == 0)
|
|
{
|
|
std::cerr << "GLFW: Vulkan not supported!\n";
|
|
return;
|
|
}
|
|
|
|
// Adding required extensions
|
|
nvvk::ContextCreateInfo& vk_setup = info.vkSetup;
|
|
uint32_t extensions_count = 0;
|
|
const char** extensions = glfwGetRequiredInstanceExtensions(&extensions_count);
|
|
for(uint32_t i = 0; i < extensions_count; i++)
|
|
{
|
|
vk_setup.instanceExtensions.emplace_back(extensions[i]);
|
|
}
|
|
vk_setup.deviceExtensions.emplace_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
|
vk_setup.instanceExtensions.emplace_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
|
|
|
// Vulkan context creation
|
|
m_context = std::make_shared<nvvk::Context>();
|
|
for(auto& dbg_msg : info.ignoreDbgMessages)
|
|
{
|
|
m_context->ignoreDebugMessage(dbg_msg); // Turn-off messages
|
|
}
|
|
m_context->init(vk_setup);
|
|
|
|
|
|
createDescriptorPool();
|
|
|
|
VkCommandPoolCreateInfo pool_create_info{VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO};
|
|
pool_create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
|
vkCreateCommandPool(m_context->m_device, &pool_create_info, m_allocator, &m_cmdPool);
|
|
|
|
VkPipelineCacheCreateInfo pipeline_cache_info{VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO};
|
|
vkCreatePipelineCache(m_context->m_device, &pipeline_cache_info, m_allocator, &m_pipelineCache);
|
|
|
|
// Create Window Surface
|
|
VkSurfaceKHR surface = nullptr;
|
|
NVVK_CHECK(glfwCreateWindowSurface(m_context->m_instance, m_windowHandle, m_allocator, &surface));
|
|
|
|
// Create framebuffers
|
|
int w{0};
|
|
int h{0};
|
|
glfwGetFramebufferSize(m_windowHandle, &w, &h);
|
|
ImGui_ImplVulkanH_Window* wd = m_mainWindowData.get();
|
|
setupVulkanWindow(surface, w, h);
|
|
|
|
m_resourceFreeQueue.resize(wd->ImageCount);
|
|
|
|
// Setup Dear ImGui context
|
|
IMGUI_CHECKVERSION();
|
|
ImGui::CreateContext();
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
m_iniFilename = path_ini.string();
|
|
io.IniFilename = m_iniFilename.c_str();
|
|
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
|
|
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
|
|
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
|
|
|
|
// Setup Dear ImGui style
|
|
ImGuiH::setStyle();
|
|
|
|
// Load default font
|
|
const float high_dpi_scale = ImGuiH::getDPIScale();
|
|
ImFontConfig font_config;
|
|
font_config.FontDataOwnedByAtlas = false;
|
|
io.FontDefault = io.Fonts->AddFontFromMemoryTTF((void*)&g_Roboto_Regular[0], sizeof(g_Roboto_Regular),
|
|
14.0F * high_dpi_scale, &font_config);
|
|
|
|
// Setup Platform/Renderer backends
|
|
ImGui_ImplGlfw_InitForVulkan(m_windowHandle, true);
|
|
ImGui_ImplVulkan_InitInfo init_info = {};
|
|
init_info.Instance = m_context->m_instance;
|
|
init_info.PhysicalDevice = m_context->m_physicalDevice;
|
|
init_info.Device = m_context->m_device;
|
|
init_info.QueueFamily = m_context->m_queueGCT.familyIndex;
|
|
init_info.Queue = m_context->m_queueGCT.queue;
|
|
init_info.PipelineCache = m_pipelineCache;
|
|
init_info.DescriptorPool = m_descriptorPool;
|
|
init_info.Subpass = 0;
|
|
init_info.MinImageCount = m_minImageCount;
|
|
init_info.ImageCount = wd->ImageCount;
|
|
init_info.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
|
|
init_info.Allocator = m_allocator;
|
|
init_info.CheckVkResultFn = checkVkResult;
|
|
ImGui_ImplVulkan_Init(&init_info, wd->RenderPass);
|
|
|
|
|
|
// Upload Fonts
|
|
VkCommandBuffer cmd = createTempCmdBuffer();
|
|
ImGui_ImplVulkan_CreateFontsTexture(cmd);
|
|
submitAndWaitTempCmdBuffer(cmd);
|
|
ImGui_ImplVulkan_DestroyFontUploadObjects();
|
|
|
|
|
|
// Read camera setting
|
|
ImGuiH::SetCameraJsonFile(getProjectName());
|
|
}
|
|
|
|
//--------------------------------------------------------------------------------------------------
|
|
// To call on exit
|
|
//
|
|
void nvvkhl::Application::shutdown()
|
|
{
|
|
for(auto& e : m_elements)
|
|
{
|
|
e->onDetach();
|
|
}
|
|
|
|
// Cleanup
|
|
vkDeviceWaitIdle(m_context->m_device);
|
|
|
|
// Free all resources
|
|
for(auto& free_queue : m_resourceFreeQueue)
|
|
{
|
|
for(auto& func : free_queue)
|
|
{
|
|
func();
|
|
}
|
|
free_queue.clear();
|
|
}
|
|
|
|
if(ImGui::GetCurrentContext() != nullptr)
|
|
{
|
|
ImGui_ImplVulkan_Shutdown();
|
|
ImGui_ImplGlfw_Shutdown();
|
|
ImGui::DestroyContext();
|
|
}
|
|
|
|
// Destroying all attached application elements
|
|
m_elements.clear();
|
|
|
|
// Cleanup Vulkan Window
|
|
ImGui_ImplVulkanH_DestroyWindow(m_context->m_instance, m_context->m_device, m_mainWindowData.get(), m_allocator);
|
|
|
|
// Vulkan cleanup
|
|
vkDestroyPipelineCache(m_context->m_device, m_pipelineCache, m_allocator);
|
|
vkDestroyCommandPool(m_context->m_device, m_cmdPool, m_allocator);
|
|
vkDestroyDescriptorPool(m_context->m_device, m_descriptorPool, m_allocator);
|
|
|
|
m_context->deinit();
|
|
|
|
// Glfw cleanup
|
|
glfwDestroyWindow(m_windowHandle);
|
|
glfwTerminate();
|
|
}
|
|
|
|
|
|
void nvvkhl::Application::setupVulkanWindow(VkSurfaceKHR surface, int width, int height)
|
|
{
|
|
ImGui_ImplVulkanH_Window* wd = m_mainWindowData.get();
|
|
wd->Surface = surface;
|
|
|
|
m_windowSize.width = width;
|
|
m_windowSize.height = height;
|
|
|
|
// Check for WSI support
|
|
VkBool32 res = VK_FALSE;
|
|
NVVK_CHECK(vkGetPhysicalDeviceSurfaceSupportKHR(m_context->m_physicalDevice, m_context->m_queueGCT.familyIndex, wd->Surface, &res));
|
|
if(res != VK_TRUE)
|
|
{
|
|
fprintf(stderr, "Error no WSI support on physical device\n");
|
|
exit(-1);
|
|
}
|
|
|
|
// Select Surface Format
|
|
const std::array<VkFormat, 4> request_surface_image_format = {VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM,
|
|
VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM};
|
|
const VkColorSpaceKHR request_surface_color_space = VK_COLORSPACE_SRGB_NONLINEAR_KHR;
|
|
wd->SurfaceFormat =
|
|
ImGui_ImplVulkanH_SelectSurfaceFormat(m_context->m_physicalDevice, wd->Surface, request_surface_image_format.data(),
|
|
static_cast<int>(request_surface_image_format.size()), request_surface_color_space);
|
|
|
|
// Select Present Mode
|
|
setPresentMode(m_context->m_physicalDevice, wd);
|
|
|
|
// Create SwapChain, RenderPass, Framebuffer, etc.
|
|
ImGui_ImplVulkanH_CreateOrResizeWindow(m_context->m_instance, m_context->m_physicalDevice, m_context->m_device, wd,
|
|
m_context->m_queueGCT.familyIndex, m_allocator, width, height, m_minImageCount);
|
|
}
|
|
|
|
|
|
void nvvkhl::Application::run()
|
|
{
|
|
m_running = true;
|
|
|
|
ImGui_ImplVulkanH_Window* wd = m_mainWindowData.get();
|
|
ImVec4 clear_color = ImVec4(0.0F, 0.0F, 0.0F, 1.00F);
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
|
|
// Main loop
|
|
while((glfwWindowShouldClose(m_windowHandle) == 0) && m_running)
|
|
{
|
|
// Poll and handle events (inputs, window resize, etc.)
|
|
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
|
|
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application.
|
|
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application.
|
|
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
|
|
glfwPollEvents();
|
|
|
|
// Resize swap chain?
|
|
if(m_swapChainRebuild || m_vsyncSet != m_vsyncWanted)
|
|
{
|
|
int width{0};
|
|
int height{0};
|
|
glfwGetFramebufferSize(m_windowHandle, &width, &height);
|
|
if(width > 0 && height > 0)
|
|
{
|
|
setPresentMode(m_context->m_physicalDevice, wd);
|
|
|
|
ImGui_ImplVulkan_SetMinImageCount(m_minImageCount);
|
|
ImGui_ImplVulkanH_CreateOrResizeWindow(m_context->m_instance, m_context->m_physicalDevice, m_context->m_device,
|
|
m_mainWindowData.get(), m_context->m_queueGCT.familyIndex, m_allocator,
|
|
width, height, m_minImageCount);
|
|
m_resourceFreeQueue.resize(wd->ImageCount);
|
|
m_mainWindowData->FrameIndex = 0;
|
|
m_swapChainRebuild = false;
|
|
}
|
|
}
|
|
|
|
// Start the Dear ImGui frame
|
|
ImGui_ImplVulkan_NewFrame();
|
|
ImGui_ImplGlfw_NewFrame();
|
|
ImGui::NewFrame();
|
|
|
|
// Docking
|
|
{
|
|
createDock();
|
|
|
|
ImGui::Begin("DockSpace NVVKHL");
|
|
|
|
// Adding menu
|
|
if(m_useMenubar)
|
|
{
|
|
if(ImGui::BeginMainMenuBar())
|
|
{
|
|
for(auto& e : m_elements)
|
|
{
|
|
e->onUIMenu();
|
|
}
|
|
|
|
ImGui::EndMainMenuBar();
|
|
}
|
|
}
|
|
|
|
// Setting up the viewport and getting information
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
|
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, m_clearColor);
|
|
ImGui::Begin("Viewport");
|
|
ImVec2 viewport_size = ImGui::GetContentRegionAvail();
|
|
bool viewport_size_changed = (static_cast<uint32_t>(viewport_size.x) != m_viewportSize.width
|
|
|| static_cast<uint32_t>(viewport_size.y) != m_viewportSize.height);
|
|
ImGui::End();
|
|
ImGui::PopStyleVar();
|
|
ImGui::PopStyleColor();
|
|
|
|
if(viewport_size_changed)
|
|
{
|
|
m_viewportSize.width = static_cast<uint32_t>(viewport_size.x);
|
|
m_viewportSize.height = static_cast<uint32_t>(viewport_size.y);
|
|
for(auto& e : m_elements)
|
|
{
|
|
e->onResize(m_viewportSize.width, m_viewportSize.height);
|
|
}
|
|
}
|
|
|
|
// Call the implementation of the UI rendering
|
|
for(auto& e : m_elements)
|
|
{
|
|
e->onUIRender();
|
|
}
|
|
|
|
ImGui::End();
|
|
}
|
|
|
|
// Rendering
|
|
ImGui::Render();
|
|
ImDrawData* main_draw_data = ImGui::GetDrawData();
|
|
const bool main_is_minimized = (main_draw_data->DisplaySize.x <= 0.0F || main_draw_data->DisplaySize.y <= 0.0F);
|
|
wd->ClearValue.color.float32[0] = clear_color.x * clear_color.w;
|
|
wd->ClearValue.color.float32[1] = clear_color.y * clear_color.w;
|
|
wd->ClearValue.color.float32[2] = clear_color.z * clear_color.w;
|
|
wd->ClearValue.color.float32[3] = clear_color.w;
|
|
if(!main_is_minimized)
|
|
{
|
|
frameRender();
|
|
}
|
|
|
|
// Update and Render additional Platform Windows
|
|
if((io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) != 0)
|
|
{
|
|
ImGui::UpdatePlatformWindows();
|
|
ImGui::RenderPlatformWindowsDefault();
|
|
}
|
|
|
|
// Present Main Platform Window
|
|
if(!main_is_minimized)
|
|
{
|
|
framePresent();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void nvvkhl::Application::createDock() const
|
|
{
|
|
static ImGuiDockNodeFlags dockspace_flags = ImGuiDockNodeFlags_None;
|
|
|
|
// Keeping the unique ID of the dock space
|
|
ImGuiID dockspace_id = ImGui::GetID("DockSpace");
|
|
|
|
// We are using the ImGuiWindowFlags_NoDocking flag to make the parent window not dockable into,
|
|
// because it would be confusing to have two docking targets within each others.
|
|
ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDocking;
|
|
if(m_useDockMenubar)
|
|
{
|
|
window_flags |= ImGuiWindowFlags_MenuBar;
|
|
}
|
|
|
|
const ImGuiViewport* viewport = ImGui::GetMainViewport();
|
|
ImGui::SetNextWindowPos(viewport->WorkPos);
|
|
ImGui::SetNextWindowSize(viewport->WorkSize);
|
|
ImGui::SetNextWindowViewport(viewport->ID);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0F);
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0F);
|
|
window_flags |= ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove;
|
|
window_flags |= ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoNavFocus;
|
|
|
|
// When using ImGuiDockNodeFlags_PassthruCentralNode, DockSpace() will render our background
|
|
// and handle the pass-thru hole, so we ask Begin() to not render a background.
|
|
if((dockspace_flags & ImGuiDockNodeFlags_PassthruCentralNode) != 0)
|
|
{
|
|
window_flags |= ImGuiWindowFlags_NoBackground;
|
|
}
|
|
|
|
// Important: note that we proceed even if Begin() returns false (aka window is collapsed).
|
|
// This is because we want to keep our DockSpace() active. If a DockSpace() is inactive,
|
|
// all active windows docked into it will lose their parent and become undocked.
|
|
// We cannot preserve the docking relationship between an active window and an inactive docking, otherwise
|
|
// any change of dockspace/settings would lead to windows being stuck in limbo and never being visible.
|
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0F, 0.0F));
|
|
ImGui::Begin("DockSpace NVVKHL", nullptr, window_flags);
|
|
ImGui::PopStyleVar();
|
|
|
|
ImGui::PopStyleVar(2);
|
|
|
|
dockspace_flags = ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingInCentralNode;
|
|
// Building the splitting of the dock space is done only once
|
|
if(ImGui::DockBuilderGetNode(dockspace_id) == nullptr)
|
|
{
|
|
ImGuiID dock_main_id = dockspace_id;
|
|
ImGui::DockBuilderRemoveNode(dockspace_id);
|
|
ImGui::DockBuilderAddNode(dockspace_id, dockspace_flags | ImGuiDockNodeFlags_DockSpace);
|
|
ImGui::DockBuilderSetNodeSize(dockspace_id, viewport->Size);
|
|
ImGui::DockBuilderDockWindow("Viewport", dockspace_id); // Center
|
|
|
|
// Disable tab bar for custom toolbar
|
|
ImGuiDockNode* node = ImGui::DockBuilderGetNode(dock_main_id);
|
|
node->LocalFlags |= ImGuiDockNodeFlags_NoTabBar;
|
|
|
|
if(m_dockSetup)
|
|
{
|
|
// This override allow to create the layout of windows by default.
|
|
// All windows in the application must be named here, otherwise they will appear un-docked
|
|
m_dockSetup(dockspace_id);
|
|
}
|
|
else
|
|
{ // Default with a window named "Settings" on the right
|
|
ImGuiID id_right = ImGui::DockBuilderSplitNode(dock_main_id, ImGuiDir_Right, 0.2F, nullptr, &dock_main_id);
|
|
ImGui::DockBuilderDockWindow("Settings", id_right);
|
|
}
|
|
|
|
ImGui::DockBuilderFinish(dock_main_id);
|
|
}
|
|
|
|
// Submit the DockSpace
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
if((io.ConfigFlags & ImGuiConfigFlags_DockingEnable) != 0)
|
|
{
|
|
ImGui::DockSpace(dockspace_id, ImVec2(0.0F, 0.0F), dockspace_flags);
|
|
}
|
|
ImGui::End();
|
|
}
|
|
|
|
void nvvkhl::Application::close()
|
|
{
|
|
m_running = false;
|
|
}
|
|
|
|
void nvvkhl::Application::addElement(const std::shared_ptr<IAppElement>& layer)
|
|
{
|
|
m_elements.emplace_back(layer);
|
|
layer->onAttach(this);
|
|
}
|
|
|
|
void nvvkhl::Application::submitResourceFree(std::function<void()>&& func)
|
|
{
|
|
if(m_currentFrameIndex < m_resourceFreeQueue.size())
|
|
{
|
|
m_resourceFreeQueue[m_currentFrameIndex].emplace_back(func);
|
|
}
|
|
else
|
|
{
|
|
func();
|
|
}
|
|
}
|
|
|
|
void nvvkhl::Application::frameRender()
|
|
{
|
|
ImGui_ImplVulkanH_Window* wd = m_mainWindowData.get();
|
|
ImDrawData* draw_data = ImGui::GetDrawData();
|
|
m_waitSemaphores.clear();
|
|
|
|
VkSemaphore image_acquired_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].ImageAcquiredSemaphore;
|
|
VkSemaphore render_complete_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore;
|
|
VkResult err = vkAcquireNextImageKHR(m_context->m_device, wd->Swapchain, UINT64_MAX, image_acquired_semaphore,
|
|
VK_NULL_HANDLE, &wd->FrameIndex);
|
|
if(err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR)
|
|
{
|
|
m_swapChainRebuild = true;
|
|
return;
|
|
}
|
|
NVVK_CHECK(err);
|
|
|
|
m_currentFrameIndex = (m_currentFrameIndex + 1) % wd->ImageCount;
|
|
|
|
ImGui_ImplVulkanH_Frame* fd = &wd->Frames[wd->FrameIndex];
|
|
{
|
|
NVVK_CHECK(vkWaitForFences(m_context->m_device, 1, &fd->Fence, VK_TRUE, UINT64_MAX)); // wait indefinitely instead of periodically checking
|
|
NVVK_CHECK(vkResetFences(m_context->m_device, 1, &fd->Fence));
|
|
}
|
|
{
|
|
// Free resources in queue
|
|
for(auto& func : m_resourceFreeQueue[m_currentFrameIndex])
|
|
{
|
|
func();
|
|
}
|
|
m_resourceFreeQueue[m_currentFrameIndex].clear();
|
|
}
|
|
{
|
|
NVVK_CHECK(vkResetCommandPool(m_context->m_device, fd->CommandPool, 0));
|
|
VkCommandBufferBeginInfo info = {};
|
|
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
|
info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
NVVK_CHECK(vkBeginCommandBuffer(fd->CommandBuffer, &info));
|
|
}
|
|
|
|
for(auto& e : m_elements)
|
|
{
|
|
e->onRender(fd->CommandBuffer);
|
|
}
|
|
|
|
{
|
|
VkRenderPassBeginInfo info{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO};
|
|
info.renderPass = wd->RenderPass;
|
|
info.framebuffer = fd->Framebuffer;
|
|
info.renderArea.extent.width = wd->Width;
|
|
info.renderArea.extent.height = wd->Height;
|
|
info.clearValueCount = 1;
|
|
info.pClearValues = &wd->ClearValue;
|
|
vkCmdBeginRenderPass(fd->CommandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE);
|
|
}
|
|
|
|
// Record dear imgui primitives into command buffer
|
|
ImGui_ImplVulkan_RenderDrawData(draw_data, fd->CommandBuffer);
|
|
|
|
// Submit command buffer
|
|
vkCmdEndRenderPass(fd->CommandBuffer);
|
|
{
|
|
VkCommandBufferSubmitInfo cmdBufInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO};
|
|
cmdBufInfo.commandBuffer = fd->CommandBuffer;
|
|
|
|
VkSemaphoreSubmitInfo signalSemaphore{VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO};
|
|
signalSemaphore.semaphore = render_complete_semaphore;
|
|
signalSemaphore.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
|
|
VkSemaphoreSubmitInfo waitSemaphore{VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO};
|
|
waitSemaphore.semaphore = image_acquired_semaphore;
|
|
waitSemaphore.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
|
|
m_waitSemaphores.emplace_back(waitSemaphore);
|
|
|
|
VkSubmitInfo2 submits{VK_STRUCTURE_TYPE_SUBMIT_INFO_2_KHR};
|
|
submits.commandBufferInfoCount = 1;
|
|
submits.pCommandBufferInfos = &cmdBufInfo;
|
|
submits.waitSemaphoreInfoCount = (uint32_t)m_waitSemaphores.size();
|
|
submits.pWaitSemaphoreInfos = m_waitSemaphores.data();
|
|
submits.signalSemaphoreInfoCount = 1;
|
|
submits.pSignalSemaphoreInfos = &signalSemaphore;
|
|
|
|
NVVK_CHECK(vkEndCommandBuffer(fd->CommandBuffer));
|
|
NVVK_CHECK(vkQueueSubmit2(m_context->m_queueGCT.queue, 1, &submits, fd->Fence));
|
|
}
|
|
}
|
|
|
|
void nvvkhl::Application::addWaitSemaphore(const VkSemaphoreSubmitInfoKHR& wait)
|
|
{
|
|
m_waitSemaphores.push_back(wait);
|
|
}
|
|
|
|
|
|
void nvvkhl::Application::framePresent()
|
|
{
|
|
ImGui_ImplVulkanH_Window* wd = m_mainWindowData.get();
|
|
|
|
if(m_swapChainRebuild)
|
|
{
|
|
return;
|
|
}
|
|
|
|
VkSemaphore render_complete_semaphore = wd->FrameSemaphores[wd->SemaphoreIndex].RenderCompleteSemaphore;
|
|
VkPresentInfoKHR info{VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
|
|
info.waitSemaphoreCount = 1;
|
|
info.pWaitSemaphores = &render_complete_semaphore;
|
|
info.swapchainCount = 1;
|
|
info.pSwapchains = &wd->Swapchain;
|
|
info.pImageIndices = &wd->FrameIndex;
|
|
VkResult err = vkQueuePresentKHR(m_context->m_queueGCT.queue, &info);
|
|
if(err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR)
|
|
{
|
|
m_swapChainRebuild = true;
|
|
return;
|
|
}
|
|
checkVkResult(err);
|
|
wd->SemaphoreIndex = (wd->SemaphoreIndex + 1) % wd->ImageCount; // Now we can use the next set of semaphores
|
|
}
|
|
|
|
|
|
bool nvvkhl::Application::isVsync() const
|
|
{
|
|
return m_vsyncSet;
|
|
}
|
|
|
|
void nvvkhl::Application::setVsync(bool v)
|
|
{
|
|
m_vsyncWanted = v;
|
|
}
|
|
|
|
|
|
void nvvkhl::Application::setPresentMode(VkPhysicalDevice physicalDevice, ImGui_ImplVulkanH_Window* wd)
|
|
{
|
|
m_vsyncSet = m_vsyncWanted;
|
|
|
|
std::vector<VkPresentModeKHR> present_modes;
|
|
if(m_vsyncSet)
|
|
{
|
|
present_modes = {VK_PRESENT_MODE_FIFO_KHR};
|
|
}
|
|
else
|
|
{
|
|
present_modes = {VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR};
|
|
}
|
|
|
|
wd->PresentMode = ImGui_ImplVulkanH_SelectPresentMode(physicalDevice, wd->Surface, present_modes.data(),
|
|
static_cast<int>(present_modes.size()));
|
|
}
|
|
|
|
void nvvkhl::Application::createDescriptorPool()
|
|
{
|
|
std::vector<VkDescriptorPoolSize> pool_sizes = {{VK_DESCRIPTOR_TYPE_SAMPLER, 1000},
|
|
{VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000},
|
|
{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000},
|
|
{VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000},
|
|
{VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000},
|
|
{VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000},
|
|
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000},
|
|
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000},
|
|
{VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000},
|
|
{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000},
|
|
{VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000}};
|
|
|
|
VkDescriptorPoolCreateInfo pool_info{VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
|
|
pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT;
|
|
pool_info.maxSets = 1000 * static_cast<uint32_t>(pool_sizes.size());
|
|
pool_info.poolSizeCount = static_cast<uint32_t>(pool_sizes.size());
|
|
pool_info.pPoolSizes = pool_sizes.data();
|
|
NVVK_CHECK(vkCreateDescriptorPool(m_context->m_device, &pool_info, nullptr, &m_descriptorPool));
|
|
}
|
|
|
|
// Set the viewport using with the size of the "Viewport" window
|
|
void nvvkhl::Application::setViewport(const VkCommandBuffer& cmd)
|
|
{
|
|
VkViewport viewport{0.0F, 0.0F, static_cast<float>(m_viewportSize.width), static_cast<float>(m_viewportSize.height),
|
|
0.0F, 1.0F};
|
|
vkCmdSetViewport(cmd, 0, 1, &viewport);
|
|
|
|
VkRect2D scissor{{0, 0}, {m_viewportSize.width, m_viewportSize.height}};
|
|
vkCmdSetScissor(cmd, 0, 1, &scissor);
|
|
}
|
|
|
|
// Create a temporary command buffer
|
|
VkCommandBuffer nvvkhl::Application::createTempCmdBuffer()
|
|
{
|
|
VkCommandBufferAllocateInfo allocate_info{VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
|
|
allocate_info.commandBufferCount = 1;
|
|
allocate_info.commandPool = m_cmdPool;
|
|
allocate_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
VkCommandBuffer cmd_buffer = nullptr;
|
|
vkAllocateCommandBuffers(m_context->m_device, &allocate_info, &cmd_buffer);
|
|
|
|
VkCommandBufferBeginInfo begin_info{VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
|
|
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
|
|
vkBeginCommandBuffer(cmd_buffer, &begin_info);
|
|
return cmd_buffer;
|
|
}
|
|
|
|
// Submit the temporary command buffer
|
|
void nvvkhl::Application::submitAndWaitTempCmdBuffer(VkCommandBuffer cmd)
|
|
{
|
|
vkEndCommandBuffer(cmd);
|
|
|
|
VkSubmitInfo submit_info{VK_STRUCTURE_TYPE_SUBMIT_INFO};
|
|
submit_info.commandBufferCount = 1;
|
|
submit_info.pCommandBuffers = &cmd;
|
|
vkQueueSubmit(m_context->m_queueGCT.queue, 1, &submit_info, {});
|
|
vkQueueWaitIdle(m_context->m_queueGCT.queue);
|
|
vkFreeCommandBuffers(m_context->m_device, m_cmdPool, 1, &cmd);
|
|
}
|
|
|
|
void nvvkhl::Application::onFileDrop(const char* filename)
|
|
{
|
|
for(auto& e : m_elements)
|
|
{
|
|
e->onFileDrop(filename);
|
|
}
|
|
}
|
|
|