/* * 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 #include #include #include #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 #endif // Embedded font #include "Roboto-Regular.h" // GLFW #ifdef _WIN32 #define GLFW_EXPOSE_NATIVE_WIN32 #endif #include #include // 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>> 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(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(); 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(static_cast(mode->width) * 0.8F); info.height = static_cast(static_cast(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((mode->width - info.width) * 0.5), static_cast((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(); 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 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(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(viewport_size.x) != m_viewportSize.width || static_cast(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(viewport_size.x); m_viewportSize.height = static_cast(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& layer) { m_elements.emplace_back(layer); layer->onAttach(this); } void nvvkhl::Application::submitResourceFree(std::function&& 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 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(present_modes.size())); } void nvvkhl::Application::createDescriptorPool() { std::vector 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(pool_sizes.size()); pool_info.poolSizeCount = static_cast(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(m_viewportSize.width), static_cast(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 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); }