diff --git a/examples/imgui_impl_vulkan.cpp b/examples/imgui_impl_vulkan.cpp index 39ec861b..a36a3e38 100644 --- a/examples/imgui_impl_vulkan.cpp +++ b/examples/imgui_impl_vulkan.cpp @@ -65,6 +65,17 @@ struct ImGui_ImplVulkanH_WindowRenderBuffers ImGui_ImplVulkanH_FrameRenderBuffers* FrameRenderBuffers; }; +// For multi-viewport support +struct ImGuiViewportDataVulkan +{ + bool WindowOwned; + ImGui_ImplVulkanH_Window Window; // Used by secondary viewports only + ImGui_ImplVulkanH_WindowRenderBuffers RenderBuffers; // Used by all viewports + + ImGuiViewportDataVulkan() { WindowOwned = false; memset(&RenderBuffers, 0, sizeof(RenderBuffers)); } + ~ImGuiViewportDataVulkan() { } +}; + // Vulkan data static ImGui_ImplVulkan_InitInfo g_VulkanInitInfo = {}; static VkRenderPass g_RenderPass = VK_NULL_HANDLE; @@ -83,9 +94,6 @@ static VkImageView g_FontView = VK_NULL_HANDLE; static VkDeviceMemory g_UploadBufferMemory = VK_NULL_HANDLE; static VkBuffer g_UploadBuffer = VK_NULL_HANDLE; -// Render buffers -static ImGui_ImplVulkanH_WindowRenderBuffers g_MainWindowRenderBuffers; - // Forward Declarations bool ImGui_ImplVulkan_CreateDeviceObjects(); void ImGui_ImplVulkan_DestroyDeviceObjects(); @@ -93,6 +101,7 @@ void ImGui_ImplVulkanH_DestroyFrame(VkDevice device, ImGui_ImplVulkanH_Frame* fd void ImGui_ImplVulkanH_DestroyFrameSemaphores(VkDevice device, ImGui_ImplVulkanH_FrameSemaphores* fsd, const VkAllocationCallbacks* allocator); void ImGui_ImplVulkanH_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkanH_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator); void ImGui_ImplVulkanH_DestroyWindowRenderBuffers(VkDevice device, ImGui_ImplVulkanH_WindowRenderBuffers* buffers, const VkAllocationCallbacks* allocator); +void ImGui_ImplVulkanH_DestroyAllViewportsRenderBuffers(VkDevice device, const VkAllocationCallbacks* allocator); void ImGui_ImplVulkanH_CreateWindowSwapChain(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator, int w, int h, uint32_t min_image_count); void ImGui_ImplVulkanH_CreateWindowCommandBuffers(VkPhysicalDevice physical_device, VkDevice device, ImGui_ImplVulkanH_Window* wd, uint32_t queue_family, const VkAllocationCallbacks* allocator); @@ -276,8 +285,10 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; - // Allocate array to store enough vertex/index buffers - ImGui_ImplVulkanH_WindowRenderBuffers* wrb = &g_MainWindowRenderBuffers; + // Allocate array to store enough vertex/index buffers. Each unique viewport gets its own storage. + ImGuiViewportDataVulkan* viewport_renderer_data = (ImGuiViewportDataVulkan*)draw_data->OwnerViewport->RendererUserData; + IM_ASSERT(viewport_renderer_data != NULL); + ImGui_ImplVulkanH_WindowRenderBuffers* wrb = &viewport_renderer_data->RenderBuffers; if (wrb->FrameRenderBuffers == NULL) { wrb->Index = 0; @@ -779,7 +790,7 @@ void ImGui_ImplVulkan_DestroyFontUploadObjects() void ImGui_ImplVulkan_DestroyDeviceObjects() { ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; - ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &g_MainWindowRenderBuffers, v->Allocator); + ImGui_ImplVulkanH_DestroyAllViewportsRenderBuffers(v->Device, v->Allocator); ImGui_ImplVulkan_DestroyFontUploadObjects(); if (g_FontView) { vkDestroyImageView(v->Device, g_FontView, v->Allocator); g_FontView = VK_NULL_HANDLE; } @@ -811,6 +822,10 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info, VkRenderPass rend g_RenderPass = render_pass; ImGui_ImplVulkan_CreateDeviceObjects(); + // Our render function expect RendererUserData to be storing the window render buffer we need (for the main viewport we won't use ->Window) + ImGuiViewport* main_viewport = ImGui::GetMainViewport(); + main_viewport->RendererUserData = IM_NEW(ImGuiViewportDataVulkan)(); + if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) ImGui_ImplVulkan_InitPlatformInterface(); @@ -833,10 +848,12 @@ void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count) if (g_VulkanInitInfo.MinImageCount == min_image_count) return; + IM_ASSERT(0); // FIXME-VIEWPORT: Unsupported. Need to recreate all swap chains! ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; VkResult err = vkDeviceWaitIdle(v->Device); check_vk_result(err); - ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &g_MainWindowRenderBuffers, v->Allocator); + + ImGui_ImplVulkanH_DestroyAllViewportsRenderBuffers(v->Device, v->Allocator); g_VulkanInitInfo.MinImageCount = min_image_count; } @@ -1211,57 +1228,56 @@ void ImGui_ImplVulkanH_DestroyWindowRenderBuffers(VkDevice device, ImGui_ImplVul buffers->Count = 0; } +void ImGui_ImplVulkanH_DestroyAllViewportsRenderBuffers(VkDevice device, const VkAllocationCallbacks* allocator) +{ + ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); + for (int n = 0; n < platform_io.Viewports.Size; n++) + if (ImGuiViewportDataVulkan* data = (ImGuiViewportDataVulkan*)platform_io.Viewports[n]->RendererUserData) + ImGui_ImplVulkanH_DestroyWindowRenderBuffers(device, &data->RenderBuffers, allocator); +} + //-------------------------------------------------------------------------------------------------------- // MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT // This is an _advanced_ and _optional_ feature, allowing the back-end to create and handle multiple viewports simultaneously. // If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first.. //-------------------------------------------------------------------------------------------------------- -// FIXME-PLATFORM: Vulkan support unfinished -//-------------------------------------------------------------------------------------------------------- - -struct ImGuiViewportDataVulkan -{ - ImGui_ImplVulkanH_WindowData WindowData; - - ImGuiViewportDataVulkan() { } - ~ImGuiViewportDataVulkan() { } -}; static void ImGui_ImplVulkan_CreateWindow(ImGuiViewport* viewport) { ImGuiViewportDataVulkan* data = IM_NEW(ImGuiViewportDataVulkan)(); viewport->RendererUserData = data; - ImGui_ImplVulkanH_WindowData* wd = &data->WindowData; + ImGui_ImplVulkanH_Window* wd = &data->Window; + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; // Create surface ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); - VkResult err = (VkResult)platform_io.Platform_CreateVkSurface(viewport, (ImU64)g_Instance, (const void*)g_Allocator, (ImU64*)&wd->Surface); + VkResult err = (VkResult)platform_io.Platform_CreateVkSurface(viewport, (ImU64)v->Instance, (const void*)v->Allocator, (ImU64*)&wd->Surface); check_vk_result(err); // Check for WSI support VkBool32 res; - vkGetPhysicalDeviceSurfaceSupportKHR(g_PhysicalDevice, g_QueueFamily, wd->Surface, &res); + vkGetPhysicalDeviceSurfaceSupportKHR(v->PhysicalDevice, v->QueueFamily, wd->Surface, &res); if (res != VK_TRUE) { - fprintf(stderr, "Error no WSI support on physical device 0\n"); - exit(-1); + IM_ASSERT(0); // Error: no WSI support on physical device + return; } // Select Surface Format const VkFormat requestSurfaceImageFormat[] = { VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_B8G8R8_UNORM, VK_FORMAT_R8G8B8_UNORM }; const VkColorSpaceKHR requestSurfaceColorSpace = VK_COLORSPACE_SRGB_NONLINEAR_KHR; - wd->SurfaceFormat = ImGui_ImplVulkanH_SelectSurfaceFormat(g_PhysicalDevice, wd->Surface, requestSurfaceImageFormat, (size_t)IM_ARRAYSIZE(requestSurfaceImageFormat), requestSurfaceColorSpace); + wd->SurfaceFormat = ImGui_ImplVulkanH_SelectSurfaceFormat(v->PhysicalDevice, wd->Surface, requestSurfaceImageFormat, (size_t)IM_ARRAYSIZE(requestSurfaceImageFormat), requestSurfaceColorSpace); // Select Present Mode // FIXME-VULKAN: Even thought mailbox seems to get us maximum framerate with a single window, it halves framerate with a second window etc. (w/ Nvidia and SDK 1.82.1) VkPresentModeKHR present_modes[] = { VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_FIFO_KHR }; - wd->PresentMode = ImGui_ImplVulkanH_SelectPresentMode(g_PhysicalDevice, wd->Surface, &present_modes[0], IM_ARRAYSIZE(present_modes)); + wd->PresentMode = ImGui_ImplVulkanH_SelectPresentMode(v->PhysicalDevice, wd->Surface, &present_modes[0], IM_ARRAYSIZE(present_modes)); //printf("[vulkan] Secondary window selected PresentMode = %d\n", wd->PresentMode); // Create SwapChain, RenderPass, Framebuffer, etc. wd->ClearEnable = (viewport->Flags & ImGuiViewportFlags_NoRendererClear) ? false : true; - ImGui_ImplVulkanH_CreateWindowDataCommandBuffers(g_PhysicalDevice, g_Device, g_QueueFamily, wd, g_Allocator); - ImGui_ImplVulkanH_CreateWindowDataSwapChainAndFramebuffer(g_PhysicalDevice, g_Device, wd, g_Allocator, (int)viewport->Size.x, (int)viewport->Size.y); + ImGui_ImplVulkanH_CreateWindow(v->Instance, v->PhysicalDevice, v->Device, wd, v->QueueFamily, v->Allocator, (int)viewport->Size.x, (int)viewport->Size.y, v->MinImageCount); + data->WindowOwned = true; } static void ImGui_ImplVulkan_DestroyWindow(ImGuiViewport* viewport) @@ -1269,7 +1285,10 @@ static void ImGui_ImplVulkan_DestroyWindow(ImGuiViewport* viewport) // The main viewport (owned by the application) will always have RendererUserData == NULL since we didn't create the data for it. if (ImGuiViewportDataVulkan* data = (ImGuiViewportDataVulkan*)viewport->RendererUserData) { - ImGui_ImplVulkanH_DestroyWindowData(g_Instance, g_Device, &data->WindowData, g_Allocator); + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + if (data->WindowOwned) + ImGui_ImplVulkanH_DestroyWindow(v->Instance, v->Device, &data->Window, v->Allocator); + ImGui_ImplVulkanH_DestroyWindowRenderBuffers(v->Device, &data->RenderBuffers, v->Allocator); IM_DELETE(data); } viewport->RendererUserData = NULL; @@ -1280,31 +1299,35 @@ static void ImGui_ImplVulkan_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) ImGuiViewportDataVulkan* data = (ImGuiViewportDataVulkan*)viewport->RendererUserData; if (data == NULL) // This is NULL for the main viewport (which is left to the user/app to handle) return; - data->WindowData.ClearEnable = (viewport->Flags & ImGuiViewportFlags_NoRendererClear) ? false : true; - ImGui_ImplVulkanH_CreateWindowDataSwapChainAndFramebuffer(g_PhysicalDevice, g_Device, &data->WindowData, g_Allocator, (int)size.x, (int)size.y); + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; + data->Window.ClearEnable = (viewport->Flags & ImGuiViewportFlags_NoRendererClear) ? false : true; + ImGui_ImplVulkanH_CreateWindow(v->Instance, v->PhysicalDevice, v->Device, &data->Window, v->QueueFamily, v->Allocator, (int)size.x, (int)size.y, v->MinImageCount); } static void ImGui_ImplVulkan_RenderWindow(ImGuiViewport* viewport, void*) { ImGuiViewportDataVulkan* data = (ImGuiViewportDataVulkan*)viewport->RendererUserData; - ImGui_ImplVulkanH_WindowData* wd = &data->WindowData; + ImGui_ImplVulkanH_Window* wd = &data->Window; + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; VkResult err; + ImGui_ImplVulkanH_Frame* fd = &wd->Frames[wd->FrameIndex]; + ImGui_ImplVulkanH_FrameSemaphores* fsd = &wd->FrameSemaphores[wd->SemaphoreIndex]; { - ImGui_ImplVulkanH_FrameData* fd = &wd->Frames[wd->FrameIndex]; for (;;) { - err = vkWaitForFences(g_Device, 1, &fd->Fence, VK_TRUE, 100); + err = vkWaitForFences(v->Device, 1, &fd->Fence, VK_TRUE, 100); if (err == VK_SUCCESS) break; if (err == VK_TIMEOUT) continue; check_vk_result(err); } { - err = vkAcquireNextImageKHR(g_Device, wd->Swapchain, UINT64_MAX, fd->ImageAcquiredSemaphore, VK_NULL_HANDLE, &fd->BackbufferIndex); + err = vkAcquireNextImageKHR(v->Device, wd->Swapchain, UINT64_MAX, fsd->ImageAcquiredSemaphore, VK_NULL_HANDLE, &wd->FrameIndex); check_vk_result(err); + fd = &wd->Frames[wd->FrameIndex]; } { - err = vkResetCommandPool(g_Device, fd->CommandPool, 0); + err = vkResetCommandPool(v->Device, fd->CommandPool, 0); check_vk_result(err); VkCommandBufferBeginInfo info = {}; info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; @@ -1319,7 +1342,7 @@ static void ImGui_ImplVulkan_RenderWindow(ImGuiViewport* viewport, void*) VkRenderPassBeginInfo info = {}; info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; info.renderPass = wd->RenderPass; - info.framebuffer = wd->Framebuffer[fd->BackbufferIndex]; + info.framebuffer = fd->Framebuffer; info.renderArea.extent.width = wd->Width; info.renderArea.extent.height = wd->Height; info.clearValueCount = (viewport->Flags & ImGuiViewportFlags_NoRendererClear) ? 0 : 1; @@ -1328,28 +1351,27 @@ static void ImGui_ImplVulkan_RenderWindow(ImGuiViewport* viewport, void*) } } - ImGui_ImplVulkan_RenderDrawData(viewport->DrawData, wd->Frames[wd->FrameIndex].CommandBuffer); + ImGui_ImplVulkan_RenderDrawData(viewport->DrawData, fd->CommandBuffer); { - ImGui_ImplVulkanH_FrameData* fd = &wd->Frames[wd->FrameIndex]; vkCmdEndRenderPass(fd->CommandBuffer); { VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; VkSubmitInfo info = {}; info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; info.waitSemaphoreCount = 1; - info.pWaitSemaphores = &fd->ImageAcquiredSemaphore; + info.pWaitSemaphores = &fsd->ImageAcquiredSemaphore; info.pWaitDstStageMask = &wait_stage; info.commandBufferCount = 1; info.pCommandBuffers = &fd->CommandBuffer; info.signalSemaphoreCount = 1; - info.pSignalSemaphores = &fd->RenderCompleteSemaphore; + info.pSignalSemaphores = &fsd->RenderCompleteSemaphore; err = vkEndCommandBuffer(fd->CommandBuffer); check_vk_result(err); - err = vkResetFences(g_Device, 1, &fd->Fence); + err = vkResetFences(v->Device, 1, &fd->Fence); check_vk_result(err); - err = vkQueueSubmit(g_Queue, 1, &info, fd->Fence); + err = vkQueueSubmit(v->Queue, 1, &info, fd->Fence); check_vk_result(err); } } @@ -1358,22 +1380,25 @@ static void ImGui_ImplVulkan_RenderWindow(ImGuiViewport* viewport, void*) static void ImGui_ImplVulkan_SwapBuffers(ImGuiViewport* viewport, void*) { ImGuiViewportDataVulkan* data = (ImGuiViewportDataVulkan*)viewport->RendererUserData; - ImGui_ImplVulkanH_WindowData* wd = &data->WindowData; + ImGui_ImplVulkanH_Window* wd = &data->Window; + ImGui_ImplVulkan_InitInfo* v = &g_VulkanInitInfo; VkResult err; - uint32_t PresentIndex = wd->FrameIndex; + uint32_t present_index = wd->FrameIndex; - ImGui_ImplVulkanH_FrameData* fd = &wd->Frames[PresentIndex]; + ImGui_ImplVulkanH_FrameSemaphores* fsd = &wd->FrameSemaphores[wd->SemaphoreIndex]; VkPresentInfoKHR info = {}; info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; info.waitSemaphoreCount = 1; - info.pWaitSemaphores = &fd->RenderCompleteSemaphore; + info.pWaitSemaphores = &fsd->RenderCompleteSemaphore; info.swapchainCount = 1; info.pSwapchains = &wd->Swapchain; - info.pImageIndices = &fd->BackbufferIndex; - err = vkQueuePresentKHR(g_Queue, &info); + info.pImageIndices = &present_index; + err = vkQueuePresentKHR(v->Queue, &info); check_vk_result(err); - wd->FrameIndex = (wd->FrameIndex + 1) % IMGUI_VK_QUEUED_FRAMES; + + wd->FrameIndex = (wd->FrameIndex + 1) % wd->ImageCount; // This is for the next vkWaitForFences() + wd->SemaphoreIndex = (wd->SemaphoreIndex + 1) % wd->ImageCount; // Now we can use the next set of semaphores } void ImGui_ImplVulkan_InitPlatformInterface() diff --git a/imgui.cpp b/imgui.cpp index 65ea1196..63631846 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -4040,6 +4040,7 @@ static void SetupViewportDrawData(ImGuiViewportP* viewport, ImVectorDisplayPos = viewport->Pos; draw_data->DisplaySize = viewport->Size; draw_data->FramebufferScale = ImGui::GetIO().DisplayFramebufferScale; // FIXME-VIEWPORT: This may vary on a per-monitor/viewport basis? + draw_data->OwnerViewport = viewport; for (int n = 0; n < draw_lists->Size; n++) { draw_data->TotalVtxCount += draw_lists->Data[n]->VtxBuffer.Size; diff --git a/imgui.h b/imgui.h index 4613625f..1b4a78f3 100644 --- a/imgui.h +++ b/imgui.h @@ -2019,11 +2019,12 @@ struct ImDrawData ImVec2 DisplayPos; // Upper-left position of the viewport to render (== upper-left of the orthogonal projection matrix to use) ImVec2 DisplaySize; // Size of the viewport to render (== io.DisplaySize for the main viewport) (DisplayPos + DisplaySize == lower-right of the orthogonal projection matrix to use) ImVec2 FramebufferScale; // Amount of pixels for each unit of DisplaySize. Based on io.DisplayFramebufferScale. Generally (1,1) on normal display, (2,2) on OSX with Retina display. + ImGuiViewport* OwnerViewport; // Viewport carrying the ImDrawData instance, might be of use to the renderer (generally not). // Functions ImDrawData() { Valid = false; Clear(); } ~ImDrawData() { Clear(); } - void Clear() { Valid = false; CmdLists = NULL; CmdListsCount = TotalVtxCount = TotalIdxCount = 0; DisplayPos = DisplaySize = FramebufferScale = ImVec2(0.f, 0.f); } // The ImDrawList are owned by ImGuiContext! + void Clear() { Valid = false; CmdLists = NULL; CmdListsCount = TotalVtxCount = TotalIdxCount = 0; DisplayPos = DisplaySize = FramebufferScale = ImVec2(0.f, 0.f); OwnerViewport = NULL; } // The ImDrawList are owned by ImGuiContext! IMGUI_API void DeIndexAllBuffers(); // Helper to convert all buffers from indexed to non-indexed, in case you cannot render indexed. Note: this is slow and most likely a waste of resources. Always prefer indexed rendering! IMGUI_API void ScaleClipRects(const ImVec2& fb_scale); // Helper to scale the ClipRect field of each ImDrawCmd. Use if your final output buffer is at a different scale than ImGui expects, or if there is a difference between your window resolution and framebuffer resolution. };