bluenoise-raytracer/raytracer/nvpro_core/nvvkhl/application.cpp
2024-05-25 11:53:25 +02:00

1261 lines
49 KiB
C++

/*
* 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
*/
/**
/class nvvkhl::Application
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
- The Application is a singleton, and the main loop is inside the run() function.
- The Application is the owner of the elements, and it will call the onRender, onUIRender, onUIMenu
for each element that is connected to it.
- The Application is the owner of the Vulkan context, and it will create the surface and window.
- The Application is the owner of the ImGui context, and it will create the dockspace and the main menu.
- ::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 <implot.h>
#include "application.hpp"
#include "backends/imgui_impl_glfw.h"
#include "backends/imgui_impl_vulkan.h"
#include "imgui/imgui_camera_widget.h"
#include "imgui/imgui_helper.h"
#include "imgui/imgui_icon.h"
#include "nvpsystem.hpp"
#include "nvvk/context_vk.hpp"
#include "nvvk/error_vk.hpp"
#include "nvvk/images_vk.hpp"
#include "perproject_globals.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>
// To save images
#define STB_IMAGE_WRITE_IMPLEMENTATION
#define STB_IMAGE_WRITE_STATIC
#include "stb_image_write.h"
// Static
uint32_t nvvkhl::Application::m_currentFrameIndex{0};
std::vector<std::vector<std::function<void()>>> nvvkhl::Application::m_resourceFreeQueue;
// Forward declaration
void ImplVulkanH_CreateOrResizeWindow(VkInstance instance,
VkPhysicalDevice physical_device,
VkDevice device,
ImGui_ImplVulkanH_Window* wnd,
uint32_t queue_family,
const VkAllocationCallbacks* allocator,
int w,
int h,
uint32_t min_image_count);
// 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;
m_hasUndockableViewport = info.hasUndockableViewport;
// 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);
resetFreeQueue(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 = info.imguiConfigFlags;
io.ConfigWindowsMoveFromTitleBarOnly = true;
// 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);
// Add icon font
ImGuiH::addIconicFont();
// 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.RenderPass = wd->RenderPass;
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);
// Read camera setting
ImGuiH::SetCameraJsonFile(getProjectName());
// Implot
ImPlot::CreateContext();
}
//--------------------------------------------------------------------------------------------------
// To call on exit
//
void nvvkhl::Application::shutdown()
{
for(auto& e : m_elements)
{
e->onDetach();
}
// Cleanup
vkDeviceWaitIdle(m_context->m_device);
// Free all resources
resetFreeQueue(0);
if(ImPlot::GetCurrentContext() != nullptr)
{
ImPlot::DestroyContext();
}
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.
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);
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);
resetFreeQueue(wd->ImageCount);
m_mainWindowData->FrameIndex = 0;
m_swapChainRebuild = false;
}
}
// Start the Dear ImGui frame
ImGui_ImplVulkan_NewFrame();
ImGui_ImplGlfw_NewFrame();
ImGui::NewFrame();
// Docking
{
createDock();
// Adding menu
if(m_useMenubar)
{
if(ImGui::BeginMainMenuBar())
{
for(auto& e : m_elements)
{
e->onUIMenu();
}
ImGui::EndMainMenuBar();
}
}
// Setting up the viewport and getting information
ImGuiWindow* viewportWindow = ImGui::FindWindowByName("Viewport");
if(m_hasUndockableViewport || viewportWindow)
{
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);
bool viewport_valid = viewport_size.x > 0 && viewport_size.y > 0;
ImGui::End();
ImGui::PopStyleVar();
ImGui::PopStyleColor();
// Callback if the viewport changed sizes
if(viewport_size_changed && viewport_valid)
{
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();
}
}
// 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 (floating windows)
// See: ImGui_ImplVulkan_InitPlatformInterface()
if((io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) != 0)
{
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
}
if(m_screenShotRequested)
{
m_screenShotRequested = false;
vkDeviceWaitIdle(m_context->m_device);
saveScreenShot(m_screenShotFilename, m_screenShotQuality);
}
// 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;
if(m_hasUndockableViewport)
dockspace_flags |= ImGuiDockNodeFlags_NoDockingOverCentralNode;
// 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);
if(m_hasUndockableViewport)
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();
m_signalSemaphores.clear();
m_commandBuffers.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
VkDebugUtilsLabelEXT s{VK_STRUCTURE_TYPE_DEBUG_UTILS_LABEL_EXT, nullptr, "ImGui_ImplVulkan_RenderDrawData", {1.0f, 1.0f, 1.0f, 1.0f}};
vkCmdBeginDebugUtilsLabelEXT(fd->CommandBuffer, &s);
ImGui_ImplVulkan_RenderDrawData(draw_data, fd->CommandBuffer);
vkCmdEndDebugUtilsLabelEXT(fd->CommandBuffer);
}
// Submit command buffer
vkCmdEndRenderPass(fd->CommandBuffer);
{
VkCommandBufferSubmitInfo cmdBufInfo{VK_STRUCTURE_TYPE_COMMAND_BUFFER_SUBMIT_INFO};
cmdBufInfo.commandBuffer = fd->CommandBuffer;
m_commandBuffers.emplace_back(cmdBufInfo);
VkSemaphoreSubmitInfo signalSemaphore{VK_STRUCTURE_TYPE_SEMAPHORE_SUBMIT_INFO};
signalSemaphore.semaphore = render_complete_semaphore;
signalSemaphore.stageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
m_signalSemaphores.emplace_back(signalSemaphore);
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 = (uint32_t)m_commandBuffers.size();
submits.pCommandBufferInfos = m_commandBuffers.data();
submits.waitSemaphoreInfoCount = (uint32_t)m_waitSemaphores.size();
submits.pWaitSemaphoreInfos = m_waitSemaphores.data();
submits.signalSemaphoreInfoCount = (uint32_t)m_signalSemaphores.size();
submits.pSignalSemaphoreInfos = m_signalSemaphores.data();
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::addSignalSemaphore(const VkSemaphoreSubmitInfoKHR& signal)
{
m_signalSemaphores.push_back(signal);
}
void nvvkhl::Application::prependCommandBuffer(const VkCommandBufferSubmitInfoKHR& cmd)
{
m_commandBuffers.push_back(cmd);
}
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->FrameIndex = (wd->FrameIndex + 1) % wd->ImageCount; // This is for the next vkWaitForFences()
wd->SemaphoreIndex = (wd->SemaphoreIndex + 1) % wd->SemaphoreCount; // 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));
}
void nvvkhl::Application::resetFreeQueue(uint32_t size)
{
vkDeviceWaitIdle(m_context->m_device);
for(auto& queue : m_resourceFreeQueue)
{
// Free resources in queue
for(auto& func : queue)
{
func();
}
queue.clear();
}
m_resourceFreeQueue.clear();
m_resourceFreeQueue.resize(size);
}
// 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);
}
}
//////////////////////////////////////////////////////////////////////////
//
// Screenshot
//
//////////////////////////////////////////////////////////////////////////
// Convert a tiled image to RGBA8 linear
void imageToRgba8Linear(VkCommandBuffer cmd,
VkDevice device,
VkPhysicalDevice physicalDevice,
VkImage srcImage,
VkExtent2D size,
VkImage& dstImage,
VkDeviceMemory& dstImageMemory)
{
// Find the memory type index for the memory
auto getMemoryType = [&](uint32_t typeBits, const VkMemoryPropertyFlags& properties) {
VkPhysicalDeviceMemoryProperties prop;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &prop);
for(uint32_t i = 0; i < prop.memoryTypeCount; i++)
{
if(((typeBits & (1 << i)) > 0) && (prop.memoryTypes[i].propertyFlags & properties) == properties)
return i;
}
return ~0u; // Unable to find memoryType
};
// Create the linear tiled destination image to copy to and to read the memory from
VkImageCreateInfo imageCreateCI = {VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
imageCreateCI.imageType = VK_IMAGE_TYPE_2D;
imageCreateCI.format = VK_FORMAT_R8G8B8A8_UNORM;
imageCreateCI.extent.width = size.width;
imageCreateCI.extent.height = size.height;
imageCreateCI.extent.depth = 1;
imageCreateCI.arrayLayers = 1;
imageCreateCI.mipLevels = 1;
imageCreateCI.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageCreateCI.samples = VK_SAMPLE_COUNT_1_BIT;
imageCreateCI.tiling = VK_IMAGE_TILING_LINEAR;
imageCreateCI.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
NVVK_CHECK(vkCreateImage(device, &imageCreateCI, nullptr, &dstImage));
// Create memory for the image
// We want host visible and coherent memory to be able to map it and write to it directly
VkMemoryRequirements memRequirements;
vkGetImageMemoryRequirements(device, dstImage, &memRequirements);
VkMemoryAllocateInfo memAllocInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
memAllocInfo.allocationSize = memRequirements.size;
memAllocInfo.memoryTypeIndex =
getMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
NVVK_CHECK(vkAllocateMemory(device, &memAllocInfo, nullptr, &dstImageMemory));
NVVK_CHECK(vkBindImageMemory(device, dstImage, dstImageMemory, 0));
nvvk::cmdBarrierImageLayout(cmd, srcImage, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL);
nvvk::cmdBarrierImageLayout(cmd, dstImage, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL);
// Do the actual blit from the swapchain image to our host visible destination image
// The Blit allow to convert the image from VK_FORMAT_B8G8R8A8_UNORM to VK_FORMAT_R8G8B8A8_UNORM automatically
VkOffset3D blitSize = {int32_t(size.width), int32_t(size.height), 1};
VkImageBlit imageBlitRegion{};
imageBlitRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageBlitRegion.srcSubresource.layerCount = 1;
imageBlitRegion.srcOffsets[1] = blitSize;
imageBlitRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
imageBlitRegion.dstSubresource.layerCount = 1;
imageBlitRegion.dstOffsets[1] = blitSize;
vkCmdBlitImage(cmd, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1,
&imageBlitRegion, VK_FILTER_NEAREST);
nvvk::cmdBarrierImageLayout(cmd, srcImage, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR);
nvvk::cmdBarrierImageLayout(cmd, dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_GENERAL);
}
// Record that a screenshot is requested, and will be saved at the end of the frame (saveScreenShot)
void nvvkhl::Application::screenShot(const std::string& filename, int quality)
{
m_screenShotRequested = true;
m_screenShotFilename = filename;
m_screenShotQuality = quality;
}
// Save the current swapchain image to a file
void nvvkhl::Application::saveScreenShot(const std::string& filename, int quality)
{
ImGui_ImplVulkanH_Window* wd = m_mainWindowData.get();
VkExtent2D size = {uint32_t(wd->Width), uint32_t(wd->Height)};
VkDevice device = m_context->m_device;
VkImage srcImage = wd->Frames[wd->FrameIndex].Backbuffer;
VkImage dstImage;
VkDeviceMemory dstImageMemory;
VkCommandBuffer cmd = createTempCmdBuffer();
imageToRgba8Linear(cmd, device, m_context->m_physicalDevice, srcImage, size, dstImage, dstImageMemory);
submitAndWaitTempCmdBuffer(cmd);
// Get layout of the image (including offset and row pitch)
VkImageSubresource subResource{VK_IMAGE_ASPECT_COLOR_BIT, 0, 0};
VkSubresourceLayout subResourceLayout;
vkGetImageSubresourceLayout(device, dstImage, &subResource, &subResourceLayout);
// Map image memory so we can start copying from it
const char* data;
vkMapMemory(device, dstImageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&data);
data += subResourceLayout.offset;
// Copy the data and adjust for the row pitch
std::vector<uint8_t> pixels(size.width * size.height * 4);
for(uint32_t y = 0; y < size.height; y++)
{
memcpy(pixels.data() + y * size.width * 4, data, size.width * 4);
data += subResourceLayout.rowPitch;
}
std::filesystem::path path = filename;
std::string extension = path.extension().string();
// Check the extension and perform actions accordingly
if(extension == ".png")
{
stbi_write_png(filename.c_str(), size.width, size.height, 4, pixels.data(), size.width * 4);
}
else if(extension == ".jpg" || extension == ".jpeg")
{
stbi_write_jpg(filename.c_str(), size.width, size.height, 4, pixels.data(), quality);
}
else if(extension == ".bmp")
{
stbi_write_bmp(filename.c_str(), size.width, size.height, 4, pixels.data());
}
else
{
LOGW("Screenshot: unknown file extension, saving as PNG\n");
stbi_write_png(filename.c_str(), size.width, size.height, 4, data, size.width * 4);
}
LOGI("Screenshot saved to %s\n", filename.c_str());
// Clean up resources
vkUnmapMemory(device, dstImageMemory);
vkFreeMemory(device, dstImageMemory, nullptr);
vkDestroyImage(device, dstImage, nullptr);
}
//////////////////////////////////////////////////////////////////////////
//
// Vulkan Helper
// This section is a copy of the ImGui_ImplVulkanH_XXX functions
// with modifications to be used in the Application class
//
//////////////////////////////////////////////////////////////////////////
void ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_device,
VkDevice device,
ImGui_ImplVulkanH_Window* wd,
uint32_t queue_family,
const VkAllocationCallbacks* allocator)
{
IM_ASSERT(physical_device != VK_NULL_HANDLE && device != VK_NULL_HANDLE);
IM_UNUSED(physical_device);
// Create Command Buffers
for(uint32_t i = 0; i < wd->ImageCount; i++)
{
ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i];
{
VkCommandPoolCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
info.flags = 0; //VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
info.queueFamilyIndex = queue_family;
NVVK_CHECK(vkCreateCommandPool(device, &info, allocator, &fd->CommandPool));
}
{
VkCommandBufferAllocateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
info.commandPool = fd->CommandPool;
info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
info.commandBufferCount = 1;
NVVK_CHECK(vkAllocateCommandBuffers(device, &info, &fd->CommandBuffer));
}
{
VkFenceCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
NVVK_CHECK(vkCreateFence(device, &info, allocator, &fd->Fence));
}
}
for(uint32_t i = 0; i < wd->SemaphoreCount; i++)
{
ImGui_ImplVulkanH_FrameSemaphores* fsd = &wd->FrameSemaphores[i];
{
VkSemaphoreCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
NVVK_CHECK(vkCreateSemaphore(device, &info, allocator, &fsd->ImageAcquiredSemaphore));
NVVK_CHECK(vkCreateSemaphore(device, &info, allocator, &fsd->RenderCompleteSemaphore));
}
}
}
void ImplVulkanH_DestroyFrame(VkDevice device, ImGui_ImplVulkanH_Frame* fd, const VkAllocationCallbacks* allocator)
{
vkDestroyFence(device, fd->Fence, allocator);
vkFreeCommandBuffers(device, fd->CommandPool, 1, &fd->CommandBuffer);
vkDestroyCommandPool(device, fd->CommandPool, allocator);
fd->Fence = VK_NULL_HANDLE;
fd->CommandBuffer = VK_NULL_HANDLE;
fd->CommandPool = VK_NULL_HANDLE;
vkDestroyImageView(device, fd->BackbufferView, allocator);
vkDestroyFramebuffer(device, fd->Framebuffer, allocator);
}
void ImplVulkanH_DestroyFrameSemaphores(VkDevice device, ImGui_ImplVulkanH_FrameSemaphores* fsd, const VkAllocationCallbacks* allocator)
{
vkDestroySemaphore(device, fsd->ImageAcquiredSemaphore, allocator);
vkDestroySemaphore(device, fsd->RenderCompleteSemaphore, allocator);
fsd->ImageAcquiredSemaphore = fsd->RenderCompleteSemaphore = VK_NULL_HANDLE;
}
// Also destroy old swap chain and in-flight frames data, if any.
void ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device,
VkDevice device,
ImGui_ImplVulkanH_Window* wd,
const VkAllocationCallbacks* allocator,
int w,
int h,
uint32_t min_image_count)
{
VkSwapchainKHR old_swapchain = wd->Swapchain;
wd->Swapchain = VK_NULL_HANDLE;
NVVK_CHECK(vkDeviceWaitIdle(device));
// We don't use ImGui_ImplVulkanH_DestroyWindow() because we want to preserve the old swapchain to create the new one.
// Destroy old Framebuffer
for(uint32_t i = 0; i < wd->ImageCount; i++)
ImplVulkanH_DestroyFrame(device, &wd->Frames[i], allocator);
for(uint32_t i = 0; i < wd->SemaphoreCount; i++)
ImplVulkanH_DestroyFrameSemaphores(device, &wd->FrameSemaphores[i], allocator);
IM_FREE(wd->Frames);
IM_FREE(wd->FrameSemaphores);
wd->Frames = nullptr;
wd->FrameSemaphores = nullptr;
wd->ImageCount = 0;
if(wd->RenderPass)
vkDestroyRenderPass(device, wd->RenderPass, allocator);
if(wd->Pipeline)
vkDestroyPipeline(device, wd->Pipeline, allocator);
// If min image count was not specified, request different count of images dependent on selected present mode
if(min_image_count == 0)
min_image_count = ImGui_ImplVulkanH_GetMinImageCountFromPresentMode(wd->PresentMode);
// Create Swapchain
{
VkSwapchainCreateInfoKHR info = {};
info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
info.surface = wd->Surface;
info.minImageCount = min_image_count;
info.imageFormat = wd->SurfaceFormat.format;
info.imageColorSpace = wd->SurfaceFormat.colorSpace;
info.imageArrayLayers = 1;
info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; // Assume that graphics family == present family
info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
info.presentMode = wd->PresentMode;
info.clipped = VK_TRUE;
info.oldSwapchain = old_swapchain;
VkSurfaceCapabilitiesKHR cap;
NVVK_CHECK(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, wd->Surface, &cap));
if(info.minImageCount < cap.minImageCount)
info.minImageCount = cap.minImageCount;
else if(cap.maxImageCount != 0 && info.minImageCount > cap.maxImageCount)
info.minImageCount = cap.maxImageCount;
if(cap.currentExtent.width == 0xffffffff)
{
info.imageExtent.width = wd->Width = w;
info.imageExtent.height = wd->Height = h;
}
else
{
info.imageExtent.width = wd->Width = cap.currentExtent.width;
info.imageExtent.height = wd->Height = cap.currentExtent.height;
}
NVVK_CHECK(vkCreateSwapchainKHR(device, &info, allocator, &wd->Swapchain));
NVVK_CHECK(vkGetSwapchainImagesKHR(device, wd->Swapchain, &wd->ImageCount, nullptr));
VkImage backbuffers[16] = {};
IM_ASSERT(wd->ImageCount >= min_image_count);
IM_ASSERT(wd->ImageCount < IM_ARRAYSIZE(backbuffers));
NVVK_CHECK(vkGetSwapchainImagesKHR(device, wd->Swapchain, &wd->ImageCount, backbuffers));
IM_ASSERT(wd->Frames == nullptr && wd->FrameSemaphores == nullptr);
wd->SemaphoreCount = wd->ImageCount + 1;
wd->Frames = (ImGui_ImplVulkanH_Frame*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_Frame) * wd->ImageCount);
wd->FrameSemaphores =
(ImGui_ImplVulkanH_FrameSemaphores*)IM_ALLOC(sizeof(ImGui_ImplVulkanH_FrameSemaphores) * wd->SemaphoreCount);
memset(wd->Frames, 0, sizeof(wd->Frames[0]) * wd->ImageCount);
memset(wd->FrameSemaphores, 0, sizeof(wd->FrameSemaphores[0]) * wd->SemaphoreCount);
for(uint32_t i = 0; i < wd->ImageCount; i++)
wd->Frames[i].Backbuffer = backbuffers[i];
}
if(old_swapchain)
vkDestroySwapchainKHR(device, old_swapchain, allocator);
// Create the Render Pass
if(wd->UseDynamicRendering == false)
{
VkAttachmentDescription attachment = {};
attachment.format = wd->SurfaceFormat.format;
attachment.samples = VK_SAMPLE_COUNT_1_BIT;
attachment.loadOp = wd->ClearEnable ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
VkAttachmentReference color_attachment = {};
color_attachment.attachment = 0;
color_attachment.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
VkSubpassDescription subpass = {};
subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &color_attachment;
VkSubpassDependency dependency = {};
dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
dependency.dstSubpass = 0;
dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
dependency.srcAccessMask = 0;
dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
VkRenderPassCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
info.attachmentCount = 1;
info.pAttachments = &attachment;
info.subpassCount = 1;
info.pSubpasses = &subpass;
info.dependencyCount = 1;
info.pDependencies = &dependency;
NVVK_CHECK(vkCreateRenderPass(device, &info, allocator, &wd->RenderPass));
// We do not create a pipeline by default as this is also used by examples' main.cpp,
// but secondary viewport in multi-viewport mode may want to create one with:
//ImGui_ImplVulkan_CreatePipeline(device, allocator, VK_NULL_HANDLE, wd->RenderPass, VK_SAMPLE_COUNT_1_BIT, &wd->Pipeline, v->Subpass);
}
// Create The Image Views
{
VkImageViewCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
info.viewType = VK_IMAGE_VIEW_TYPE_2D;
info.format = wd->SurfaceFormat.format;
info.components.r = VK_COMPONENT_SWIZZLE_R;
info.components.g = VK_COMPONENT_SWIZZLE_G;
info.components.b = VK_COMPONENT_SWIZZLE_B;
info.components.a = VK_COMPONENT_SWIZZLE_A;
VkImageSubresourceRange image_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
info.subresourceRange = image_range;
for(uint32_t i = 0; i < wd->ImageCount; i++)
{
ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i];
info.image = fd->Backbuffer;
NVVK_CHECK(vkCreateImageView(device, &info, allocator, &fd->BackbufferView));
}
}
// Create Framebuffer
if(wd->UseDynamicRendering == false)
{
VkImageView attachment[1];
VkFramebufferCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
info.renderPass = wd->RenderPass;
info.attachmentCount = 1;
info.pAttachments = attachment;
info.width = wd->Width;
info.height = wd->Height;
info.layers = 1;
for(uint32_t i = 0; i < wd->ImageCount; i++)
{
ImGui_ImplVulkanH_Frame* fd = &wd->Frames[i];
attachment[0] = fd->BackbufferView;
NVVK_CHECK(vkCreateFramebuffer(device, &info, allocator, &fd->Framebuffer));
}
}
}
// Create or resize window
void ImplVulkanH_CreateOrResizeWindow(VkInstance instance,
VkPhysicalDevice physical_device,
VkDevice device,
ImGui_ImplVulkanH_Window* wd,
uint32_t queue_family,
const VkAllocationCallbacks* allocator,
int width,
int height,
uint32_t min_image_count)
{
ImplVulkanH_CreateWindowSwapChain(physical_device, device, wd, allocator, width, height, min_image_count);
ImplVulkanH_CreateWindowCommandBuffers(physical_device, device, wd, queue_family, allocator);
}