diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 8df12926f802..af029b2715ea 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -251,6 +251,10 @@ static PFN_vkCmdBeginRenderingKHR ImGuiImplVulkanFuncs_vkCmdBeginRenderingKHR; static PFN_vkCmdEndRenderingKHR ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR; #endif +#ifdef IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET +static PFN_vkCmdPushDescriptorSetKHR ImGuiImplVulkanFuncs_vkCmdPushDescriptorSetKHR; +#endif + // Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplVulkan_RenderDrawData() // [Please zero-clear before use!] struct ImGui_ImplVulkan_FrameRenderBuffers @@ -293,7 +297,7 @@ struct ImGui_ImplVulkan_ViewportData bool SwapChainSuboptimal; // Flag when VK_SUBOPTIMAL_KHR was returned. ImGui_ImplVulkan_ViewportData() { WindowOwned = SwapChainNeedRebuild = SwapChainSuboptimal = false; memset((void*)&RenderBuffers, 0, sizeof(RenderBuffers)); } - ~ImGui_ImplVulkan_ViewportData() { } + ~ImGui_ImplVulkan_ViewportData() {} }; // Vulkan data @@ -565,9 +569,9 @@ static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline } // Draw callbacks -static void ImGui_ImplVulkan_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. -static void ImGui_ImplVulkan_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerLinearDS, 0, nullptr); } -static void ImGui_ImplVulkan_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerNearestDS, 0, nullptr); } +static void ImGui_ImplVulkan_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way. +static void ImGui_ImplVulkan_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerLinearDS, 0, nullptr); } +static void ImGui_ImplVulkan_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerNearestDS, 0, nullptr); } // Render function void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline) @@ -660,6 +664,9 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm // Render command lists // (Because we merged all buffers into a single one, we maintain our own offset into them) VkDescriptorSet last_image_view = VK_NULL_HANDLE; +#ifdef IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET + VkImageView last_push_image_view = VK_NULL_HANDLE; +#endif int global_vtx_offset = 0; int global_idx_offset = 0; for (const ImDrawList* draw_list : draw_data->CmdLists) @@ -674,6 +681,9 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm { ImGui_ImplVulkan_SetupRenderState(draw_data, pipeline, command_buffer, rb, fb_width, fb_height); last_image_view = VK_NULL_HANDLE; +#ifdef IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET + last_push_image_view = VK_NULL_HANDLE; +#endif } else pcmd->UserCallback(draw_list, pcmd); @@ -700,11 +710,41 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm scissor.extent.height = (uint32_t)(clip_max.y - clip_min.y); vkCmdSetScissor(command_buffer, 0, 1, &scissor); + // PushDescriptorSets for image view(font or user texture) +#ifdef IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET + if (bd->VulkanInitInfo.UsePushDescriptorSet) + { + VkImageView image_view = (VkImageView)pcmd->GetTexID(); + if (image_view != last_push_image_view) + { + // The user application is strictly responsible for transitioning the + // image layout to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL prior to rendering. + VkDescriptorImageInfo image_info = {}; + image_info.imageView = image_view; + image_info.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + + VkWriteDescriptorSet write_desc[1] = {}; + write_desc[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + write_desc[0].dstSet = 0; + write_desc[0].dstBinding = 0; + write_desc[0].descriptorCount = 1; + write_desc[0].descriptorType = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + write_desc[0].pImageInfo = &image_info; + + if (ImGuiImplVulkanFuncs_vkCmdPushDescriptorSetKHR) + ImGuiImplVulkanFuncs_vkCmdPushDescriptorSetKHR(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 0, 1, write_desc); + } + last_push_image_view = image_view; + } + else +#endif // Bind DescriptorSets for image view (font or user texture) and samplers - VkDescriptorSet image_view = (VkDescriptorSet)pcmd->GetTexID(); - if (image_view != last_image_view) - vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 0, 1, &image_view, 0, nullptr); - last_image_view = image_view; + { + VkDescriptorSet image_view = (VkDescriptorSet)pcmd->GetTexID(); + if (image_view != last_image_view) + vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 0, 1, &image_view, 0, nullptr); + last_image_view = image_view; + } // Draw vkCmdDrawIndexed(command_buffer, pcmd->ElemCount, 1, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset, 0); @@ -730,10 +770,20 @@ static void ImGui_ImplVulkan_DestroyTexture(ImTextureData* tex) { if (ImGui_ImplVulkan_Texture* backend_tex = (ImGui_ImplVulkan_Texture*)tex->BackendUserData) { - IM_ASSERT(backend_tex->DescriptorSet == (VkDescriptorSet)tex->TexID); ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); +#ifdef IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET + if (bd->VulkanInitInfo.UsePushDescriptorSet) + { + IM_ASSERT(backend_tex->ImageView == (VkImageView)tex->TexID); + } + else +#endif + { + IM_ASSERT(backend_tex->DescriptorSet == (VkDescriptorSet)tex->TexID); + ImGui_ImplVulkan_RemoveTexture(backend_tex->DescriptorSet); + } ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; - ImGui_ImplVulkan_RemoveTexture(backend_tex->DescriptorSet); + vkDestroyImageView(v->Device, backend_tex->ImageView, v->Allocator); vkDestroyImage(v->Device, backend_tex->Image, v->Allocator); vkFreeMemory(v->Device, backend_tex->Memory, v->Allocator); @@ -806,11 +856,20 @@ void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex) check_vk_result(err); } - // Create the Descriptor Set - backend_tex->DescriptorSet = ImGui_ImplVulkan_AddTexture(backend_tex->ImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); +#ifdef IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET + if (bd->VulkanInitInfo.UsePushDescriptorSet) + { + tex->SetTexID((ImTextureID)backend_tex->ImageView); + } + else +#endif + { + // Create the Descriptor Set + backend_tex->DescriptorSet = ImGui_ImplVulkan_AddTexture(backend_tex->ImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - // Store identifiers - tex->SetTexID((ImTextureID)backend_tex->DescriptorSet); + // Store identifiers + tex->SetTexID((ImTextureID)backend_tex->DescriptorSet); + } tex->BackendUserData = backend_tex; } @@ -1115,6 +1174,11 @@ static void ImGui_ImplVulkan_CreateDescriptorSetLayout(VkDescriptorSetLayout* p_ info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; info.bindingCount = 1; info.pBindings = binding; +#ifdef IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET + if (bd->VulkanInitInfo.UsePushDescriptorSet && descriptor_type == VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE) + info.flags = VK_DESCRIPTOR_SET_LAYOUT_CREATE_PUSH_DESCRIPTOR_BIT_KHR; +#endif + VkResult err = vkCreateDescriptorSetLayout(v->Device, &info, v->Allocator, p_layout); check_vk_result(err); } @@ -1159,7 +1223,12 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() if (v->DescriptorPoolSize != 0) { - IM_ASSERT(v->DescriptorPoolSize >= IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE); +#ifdef IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET + if (!bd->VulkanInitInfo.UsePushDescriptorSet) +#endif + { + IM_ASSERT(v->DescriptorPoolSize >= IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE); + } VkDescriptorPoolSize pool_sizes[] = { { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, v->DescriptorPoolSize }, @@ -1323,6 +1392,21 @@ static void ImGui_ImplVulkan_LoadDynamicRenderingFunctions(uint32_t api_version, } #endif +#ifdef IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET +static void ImGui_ImplVulkan_LoadPushDescriptorSetFunction(uint32_t api_version, PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data) +{ + IM_UNUSED(api_version); + + ImGuiImplVulkanFuncs_vkCmdPushDescriptorSetKHR = reinterpret_cast(loader_func("vkCmdPushDescriptorSet", user_data)); + + // - Fallback to KHR versions if core not available (this will work if KHR extension is available and enabled and also the device supports push descriptors) + if (ImGuiImplVulkanFuncs_vkCmdPushDescriptorSetKHR == nullptr) + { + ImGuiImplVulkanFuncs_vkCmdPushDescriptorSetKHR = reinterpret_cast(loader_func("vkCmdPushDescriptorSetKHR", user_data)); + } +} +#endif + // If unspecified by user, assume that ApiVersion == HeaderVersion // We don't care about other versions than 1.3 for our checks, so don't need to make this exhaustive (e.g. with all #ifdef VK_VERSION_1_X checks) static uint32_t ImGui_ImplVulkan_GetDefaultApiVersion() @@ -1352,7 +1436,11 @@ bool ImGui_ImplVulkan_LoadFunctions(uint32_t api_version, PFN_vkVoidFunction( #undef IMGUI_VULKAN_FUNC_LOAD #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING - ImGui_ImplVulkan_LoadDynamicRenderingFunctions(api_version, loader_func, user_data); + ImGui_ImplVulkan_LoadDynamicRenderingFunctions(api_version, loader_func, user_data); +#endif + +#ifdef IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET + ImGui_ImplVulkan_LoadPushDescriptorSetFunction(api_version, loader_func, user_data); #endif #else IM_UNUSED(loader_func); @@ -1383,6 +1471,18 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) #endif } + if (info->UsePushDescriptorSet) + { +#ifdef IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET +#ifndef IMGUI_IMPL_VULKAN_USE_LOADER + ImGui_ImplVulkan_LoadPushDescriptorSetFunction(info->ApiVersion, [](const char* function_name, void* user_data) { return vkGetDeviceProcAddr((VkDevice)user_data, function_name); }, (void*)info->Device); +#endif + IM_ASSERT(ImGuiImplVulkanFuncs_vkCmdPushDescriptorSetKHR != nullptr); +#else + IM_ASSERT(0 && "Can't use Push Descriptors when neither VK_VERSION_1_4 or VK_KHR_push_descriptor is defined."); +#endif + } + ImGuiIO& io = ImGui::GetIO(); IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); @@ -1487,6 +1587,13 @@ VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkImageView image_view, VkImageLayou { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + +#ifdef IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET + IM_ASSERT(v->UsePushDescriptorSet == false && "ImGui_ImplVulkan_AddTexture() is not needed and should not be called when UsePushDescriptorSet is enabled. Simply cast your VkImageView to ImTextureID directly."); + if (v->UsePushDescriptorSet) + return VK_NULL_HANDLE; +#endif + VkDescriptorPool pool = bd->DescriptorPool ? bd->DescriptorPool : v->DescriptorPool; // Create Descriptor Set: @@ -1530,10 +1637,18 @@ void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + +#ifdef IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET + IM_ASSERT(v->UsePushDescriptorSet == false && "ImGui_ImplVulkan_RemoveTexture() should not be called when UsePushDescriptorSet is enabled."); + if (v->UsePushDescriptorSet) + return; +#endif + VkDescriptorPool pool = bd->DescriptorPool ? bd->DescriptorPool : v->DescriptorPool; vkFreeDescriptorSets(v->Device, pool, 1, &descriptor_set); } + void ImGui_ImplVulkan_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkan_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator) { if (buffers->VertexBuffer) { vkDestroyBuffer(device, buffers->VertexBuffer, allocator); buffers->VertexBuffer = VK_NULL_HANDLE; } @@ -2065,7 +2180,7 @@ static void ImGui_ImplVulkan_CreateWindow(ImGuiViewport* viewport) ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); VkResult err = (VkResult)platform_io.Platform_CreateVkSurface(viewport, (ImU64)v->Instance, (const void*)v->Allocator, (ImU64*)&wd->Surface); check_vk_result(err); - + // Check if surface creation failed if (err != VK_SUCCESS || wd->Surface == VK_NULL_HANDLE) { diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index a3eda9717f2b..42bb602bb122 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -78,6 +78,10 @@ #define IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING #endif +#if defined(VK_VERSION_1_4) || defined(VK_KHR_push_descriptor) +#define IMGUI_IMPL_VULKAN_HAS_PUSH_DESCRIPTOR_SET +#endif + // Backend uses a small number of descriptors per font atlas + as many as additional calls done to ImGui_ImplVulkan_AddTexture(). #define IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE (8) // Minimum per atlas #define IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE (2) // Minimum for linear + nearest @@ -134,6 +138,12 @@ struct ImGui_ImplVulkan_InitInfo // Need to explicitly enable VK_KHR_dynamic_rendering extension to use this, even for Vulkan 1.3 + setup PipelineInfoMain.PipelineRenderingCreateInfo and PipelineInfoViewports.PipelineRenderingCreateInfo. bool UseDynamicRendering; + // (Optional) PushDescriptorSet + // Need to explicitly enable VK_KHR_push_descriptor extension to use this. + // When enabled, you must pass VkImageView as ImTextureID instead of VkDescriptorSet, + // eliminating the need for descriptor pool allocations for textures. + bool UsePushDescriptorSet; + // (Optional) Allocation, Debugging const VkAllocationCallbacks* Allocator; void (*CheckVkResultFn)(VkResult err); @@ -162,6 +172,7 @@ IMGUI_IMPL_API void ImGui_ImplVulkan_CreateMainPipeline(const ImGui_ IMGUI_IMPL_API void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex); // Register a texture (VkDescriptorSet for a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE == ImTextureID) +// (Note: If using InitInfo.UsePushDescriptorSet, this function is NOT needed. You can directly pass your VkImageView casted to ImTextureID.) IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkImageView image_view, VkImageLayout image_layout); IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set);