From 458f8a245d0e0ba86ad5f9d8400f908a223f5a40 Mon Sep 17 00:00:00 2001 From: Albert <-> Date: Mon, 8 Jun 2026 22:50:36 -0400 Subject: [PATCH 01/11] Backends: Vulkan: Add VK_EXT_descriptor_heap support (#9374) Fix: applied coding style Fix: Fixed Vulkan pNext chain when using both Dynamic Rendering and Descriptor Heaps. --- backends/imgui_impl_vulkan.cpp | 295 +++++++++++++++++++++---- backends/imgui_impl_vulkan.h | 13 ++ backends/vulkan/generate_spv.sh | 1 + backends/vulkan/glsl_shader_heap.frag | 18 ++ examples/example_win32_vulkan/main.cpp | 196 +++++++++++++++- 5 files changed, 482 insertions(+), 41 deletions(-) create mode 100644 backends/vulkan/glsl_shader_heap.frag diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 57ebdb064..e86f23337 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -171,6 +171,7 @@ static bool g_FunctionsLoaded = true; IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdDrawIndexed) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdPipelineBarrier) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdPushConstants) \ + IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdPushDataEXT) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdSetScissor) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdSetViewport) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateBuffer) \ @@ -268,6 +269,7 @@ struct ImGui_ImplVulkan_Texture VkImage Image; VkImageView ImageView; VkDescriptorSet DescriptorSet; + uint32_t DescriptorHeapIndex; ImGui_ImplVulkan_Texture() { memset((void*)this, 0, sizeof(*this)); } }; @@ -296,6 +298,8 @@ struct ImGui_ImplVulkan_Data VkDescriptorSet SamplerNearestDS; VkCommandPool TexCommandPool; VkCommandBuffer TexCommandBuffer; + uint32_t DescriptorHeapSamplerLinear; + uint32_t DescriptorHeapSamplerNearest; // Render buffers for main window ImGui_ImplVulkan_WindowRenderBuffers MainWindowRenderBuffers; @@ -422,6 +426,76 @@ static uint32_t __glsl_shader_frag_spv[] = 0x00010038 }; +// backends/vulkan/glsl_shader_heap.frag, compiled with: +// # glslangValidator -V --target-env spirv1.2 -x -o glsl_shader.frag.u32 glsl_shader.frag +/* +#version 460 core +#extension GL_EXT_descriptor_heap : require +#extension GL_EXT_nonuniform_qualifier : require + +layout(location = 0) out vec4 fColor; +layout(descriptor_heap) uniform texture2D _Textures[]; +layout(descriptor_heap) uniform sampler _Samplers[]; +layout(location = 0) in struct { vec4 Color; vec2 UV; } In; +layout(push_constant) uniform uPushConstant { vec2 uScale; vec2 uTranslate; uvec2 uID; } pc; +void main() +{ + fColor = In.Color * texture(sampler2D(_Textures[pc.uID.x], _Samplers[pc.uID.y]), In.UV.st); +} +*/ +static uint32_t __glsl_shader_heap_frag_spv[] = +{ + 0x07230203,0x00010200,0x0008000b,0x00000036,0x00000000,0x00020011,0x00000001,0x00020011, + 0x00001179,0x00020011,0x00001408,0x0007000a,0x5f565053,0x5f545845,0x63736564,0x74706972, + 0x685f726f,0x00706165,0x0008000a,0x5f565053,0x5f52484b,0x79746e75,0x5f646570,0x6e696f70, + 0x73726574,0x00000000,0x0006000b,0x00000001,0x4c534c47,0x6474732e,0x3035342e,0x00000000, + 0x0003000e,0x00000000,0x00000001,0x0007000f,0x00000004,0x00000004,0x6e69616d,0x00000000, + 0x00000009,0x0000000d,0x00030010,0x00000004,0x00000007,0x00030003,0x00000002,0x000001cc, + 0x00070004,0x455f4c47,0x645f5458,0x72637365,0x6f747069,0x65685f72,0x00007061,0x00080004, + 0x455f4c47,0x6e5f5458,0x6e756e6f,0x726f6669,0x75715f6d,0x66696c61,0x00726569,0x00040005, + 0x00000004,0x6e69616d,0x00000000,0x00040005,0x00000009,0x6c6f4366,0x0000726f,0x00030005, + 0x0000000b,0x00000000,0x00050006,0x0000000b,0x00000000,0x6f6c6f43,0x00000072,0x00040006, + 0x0000000b,0x00000001,0x00005655,0x00030005,0x0000000d,0x00006e49,0x00060005,0x00000014, + 0x6f736572,0x65637275,0x6165685f,0x00000070,0x00060005,0x00000017,0x73755075,0x6e6f4368, + 0x6e617473,0x00000074,0x00050006,0x00000017,0x00000000,0x61635375,0x0000656c,0x00060006, + 0x00000017,0x00000001,0x61725475,0x616c736e,0x00006574,0x00040006,0x00000017,0x00000002, + 0x00444975,0x00030005,0x00000019,0x00006370,0x00060005,0x00000025,0x706d6173,0x5f72656c, + 0x70616568,0x00000000,0x00040047,0x00000009,0x0000001e,0x00000000,0x00040047,0x0000000d, + 0x0000001e,0x00000000,0x00040047,0x00000014,0x0000000b,0x00001403,0x00030047,0x00000017, + 0x00000002,0x00050048,0x00000017,0x00000000,0x00000023,0x00000000,0x00050048,0x00000017, + 0x00000001,0x00000023,0x00000008,0x00050048,0x00000017,0x00000002,0x00000023,0x00000010, + 0x0004014c,0x00000023,0x00001404,0x00000022,0x00040047,0x00000025,0x0000000b,0x00001402, + 0x0004014c,0x0000002c,0x00001404,0x0000002b,0x00020013,0x00000002,0x00030021,0x00000003, + 0x00000002,0x00030016,0x00000006,0x00000020,0x00040017,0x00000007,0x00000006,0x00000004, + 0x00040020,0x00000008,0x00000003,0x00000007,0x0004003b,0x00000008,0x00000009,0x00000003, + 0x00040017,0x0000000a,0x00000006,0x00000002,0x0004001e,0x0000000b,0x00000007,0x0000000a, + 0x00040020,0x0000000c,0x00000001,0x0000000b,0x0004003b,0x0000000c,0x0000000d,0x00000001, + 0x00040015,0x0000000e,0x00000020,0x00000001,0x0004002b,0x0000000e,0x0000000f,0x00000000, + 0x00040020,0x00000010,0x00000001,0x00000007,0x00031141,0x00000013,0x00000000,0x00041142, + 0x00000013,0x00000014,0x00000000,0x00040015,0x00000015,0x00000020,0x00000000,0x00040017, + 0x00000016,0x00000015,0x00000002,0x0005001e,0x00000017,0x0000000a,0x0000000a,0x00000016, + 0x00040020,0x00000018,0x00000009,0x00000017,0x0004003b,0x00000018,0x00000019,0x00000009, + 0x0004002b,0x0000000e,0x0000001a,0x00000002,0x0004002b,0x00000015,0x0000001b,0x00000000, + 0x00040020,0x0000001c,0x00000009,0x00000015,0x00090019,0x0000001f,0x00000006,0x00000001, + 0x00000000,0x00000000,0x00000000,0x00000001,0x00000000,0x00031141,0x00000020,0x00000002, + 0x00041409,0x0000000e,0x00000022,0x0000001f,0x0003001d,0x00000023,0x0000001f,0x00041142, + 0x00000013,0x00000025,0x00000000,0x0004002b,0x00000015,0x00000026,0x00000001,0x0002001a, + 0x00000029,0x00041409,0x0000000e,0x0000002b,0x00000029,0x0003001d,0x0000002c,0x00000029, + 0x0003001b,0x0000002e,0x0000001f,0x0004002b,0x0000000e,0x00000030,0x00000001,0x00040020, + 0x00000031,0x00000001,0x0000000a,0x00050036,0x00000002,0x00000004,0x00000000,0x00000003, + 0x000200f8,0x00000005,0x00050041,0x00000010,0x00000011,0x0000000d,0x0000000f,0x0004003d, + 0x00000007,0x00000012,0x00000011,0x00060041,0x0000001c,0x0000001d,0x00000019,0x0000001a, + 0x0000001b,0x0004003d,0x00000015,0x0000001e,0x0000001d,0x00061143,0x00000013,0x00000021, + 0x00000023,0x00000014,0x0000001e,0x0004003d,0x0000001f,0x00000024,0x00000021,0x00060041, + 0x0000001c,0x00000027,0x00000019,0x0000001a,0x00000026,0x0004003d,0x00000015,0x00000028, + 0x00000027,0x00061143,0x00000013,0x0000002a,0x0000002c,0x00000025,0x00000028,0x0004003d, + 0x00000029,0x0000002d,0x0000002a,0x00050056,0x0000002e,0x0000002f,0x00000024,0x0000002d, + 0x00050041,0x00000031,0x00000032,0x0000000d,0x00000030,0x0004003d,0x0000000a,0x00000033, + 0x00000032,0x00050057,0x00000007,0x00000034,0x0000002f,0x00000033,0x00050085,0x00000007, + 0x00000035,0x00000012,0x00000034,0x0003003e,0x00000009,0x00000035,0x000100fd,0x00010038 +}; + + //----------------------------------------------------------------------------- // FUNCTIONS //----------------------------------------------------------------------------- @@ -522,23 +596,74 @@ static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline viewport.maxDepth = 1.0f; vkCmdSetViewport(command_buffer, 0, 1, &viewport); - // Setup scale and translation: - // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. - float constants[4]; - constants[0] = 2.0f / draw_data->DisplaySize.x; // Scale - constants[1] = 2.0f / draw_data->DisplaySize.y; - constants[2] = -1.0f - draw_data->DisplayPos.x * constants[0]; // Translate - constants[3] = -1.0f - draw_data->DisplayPos.y * constants[1]; - vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float) * 4, constants); + struct push_data + { + float constants[4]; + // First ID is texture, second is sampler. + uint32_t ids[2]; + } pd; + pd.constants[0] = 2.0f / draw_data->DisplaySize.x; // Scale + pd.constants[1] = 2.0f / draw_data->DisplaySize.y; + pd.constants[2] = -1.0f - draw_data->DisplayPos.x * pd.constants[0]; // Translate + pd.constants[3] = -1.0f - draw_data->DisplayPos.y * pd.constants[1]; + if (bd->VulkanInitInfo.DescriptorHeapInfo) + { + pd.ids[0] = 0; + pd.ids[1] = bd->DescriptorHeapSamplerLinear; - // Setup sampler - vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerLinearDS, 0, nullptr); + VkPushDataInfoEXT push{}; + push.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + push.data.address = &pd; + push.data.size = sizeof(pd); + vkCmdPushDataEXT(bd->RenderState->CommandBuffer, &push); + } + else + { + // Setup scale and translation: + // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. + vkCmdPushConstants(command_buffer, bd->PipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(float) * 4, pd.constants); + + // Setup sampler + vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerLinearDS, 0, nullptr); + } } // 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->RenderState->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->RenderState->PipelineLayout, 1, 1, &bd->SamplerNearestDS, 0, nullptr); } +static void ImGui_ImplVulkan_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + if (bd->VulkanInitInfo.DescriptorHeapInfo) + { + VkPushDataInfoEXT push{}; + push.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + push.data.address = &bd->DescriptorHeapSamplerLinear; + push.data.size = 4; + push.offset = 20; + vkCmdPushDataEXT(bd->RenderState->CommandBuffer, &push); + } + else + { + 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(); + if (bd->VulkanInitInfo.DescriptorHeapInfo) + { + VkPushDataInfoEXT push{}; + push.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + push.data.address = &bd->DescriptorHeapSamplerNearest; + push.data.size = 4; + push.offset = 20; + vkCmdPushDataEXT(bd->RenderState->CommandBuffer, &push); + } + else + { + vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerNearestDS, 0, nullptr); + } +} // If you want to use your own sampler, you can create your own callback, access ImGui_ImplVulkan_RenderState for command-buffer and pipeline layout, e.g. /* @@ -679,9 +804,25 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm vkCmdSetScissor(command_buffer, 0, 1, &scissor); // Bind DescriptorSets for image view (font or user texture) and samplers - VkDescriptorSet image_view = (VkDescriptorSet)pcmd->GetTexID(); + ImTextureID image_id = pcmd->GetTexID(); + VkDescriptorSet image_view = (VkDescriptorSet)image_id; if (image_view != last_image_view) - vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 0, 1, &image_view, 0, nullptr); + { + if (v->DescriptorHeapInfo) + { + VkPushDataInfoEXT push{}; + push.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; + image_id &= 0x7FFFFFFF; + push.data.address = &image_id; + push.data.size = 4; + push.offset = 16; + vkCmdPushDataEXT(command_buffer, &push); + } + else + { + vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 0, 1, &image_view, 0, nullptr); + } + } last_image_view = image_view; // Draw @@ -708,11 +849,16 @@ 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); + IM_ASSERT(backend_tex->DescriptorSet == (VkDescriptorSet)tex->TexID || + backend_tex->DescriptorHeapIndex == (tex->TexID & 0x7FFFFFFF)); ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; - ImGui_ImplVulkan_RemoveTexture(backend_tex->DescriptorSet); - vkDestroyImageView(v->Device, backend_tex->ImageView, v->Allocator); + if (backend_tex->DescriptorSet != VK_NULL_HANDLE) + ImGui_ImplVulkan_RemoveTexture(backend_tex->DescriptorSet); + else + ImGui_ImplVulkan_RemoveHeapTexture(backend_tex->DescriptorHeapIndex); + if (backend_tex->ImageView != VK_NULL_HANDLE) + vkDestroyImageView(v->Device, backend_tex->ImageView, v->Allocator); vkDestroyImage(v->Device, backend_tex->Image, v->Allocator); vkFreeMemory(v->Device, backend_tex->Memory, v->Allocator); IM_DELETE(backend_tex); @@ -770,25 +916,33 @@ void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex) check_vk_result(err); } - // Create the Image View: + VkImageViewCreateInfo info = {}; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.image = backend_tex->Image; + info.viewType = VK_IMAGE_VIEW_TYPE_2D; + info.format = VK_FORMAT_R8G8B8A8_UNORM; + info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + info.subresourceRange.levelCount = 1; + info.subresourceRange.layerCount = 1; + if (v->DescriptorHeapInfo) { - VkImageViewCreateInfo info = {}; - info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - info.image = backend_tex->Image; - info.viewType = VK_IMAGE_VIEW_TYPE_2D; - info.format = VK_FORMAT_R8G8B8A8_UNORM; - info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - info.subresourceRange.levelCount = 1; - info.subresourceRange.layerCount = 1; + backend_tex->DescriptorHeapIndex = + v->DescriptorHeapInfo->RegisterImage(v->DescriptorHeapInfo->UserContext, &info); + // Store identifiers + tex->SetTexID((ImTextureID)(backend_tex->DescriptorHeapIndex | 0x80000000)); + } + else + { + // Create the Image View: err = vkCreateImageView(v->Device, &info, v->Allocator, &backend_tex->ImageView); check_vk_result(err); + + // 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); } - // 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); tex->BackendUserData = backend_tex; } @@ -952,6 +1106,11 @@ static void ImGui_ImplVulkan_CreateShaderModules(VkDevice device, const VkAlloca default_frag_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; default_frag_info.codeSize = sizeof(__glsl_shader_frag_spv); default_frag_info.pCode = (uint32_t*)__glsl_shader_frag_spv; + if (v->DescriptorHeapInfo) + { + default_frag_info.codeSize = sizeof(__glsl_shader_heap_frag_spv); + default_frag_info.pCode = (uint32_t*)__glsl_shader_heap_frag_spv; + } VkShaderModuleCreateInfo* p_frag_info = (v->CustomShaderFragCreateInfo.sType == VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO) ? &v->CustomShaderFragCreateInfo : &default_frag_info; VkResult err = vkCreateShaderModule(device, p_frag_info, allocator, &bd->ShaderModuleFrag); check_vk_result(err); @@ -1065,15 +1224,27 @@ static VkPipeline ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAlloc create_info.renderPass = info->RenderPass; create_info.subpass = info->Subpass; + VkPipelineCreateFlags2CreateInfo ci_heap_flags{}; + ci_heap_flags.sType = VK_STRUCTURE_TYPE_PIPELINE_CREATE_FLAGS_2_CREATE_INFO; + ci_heap_flags.flags = VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT; + #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING + VkPipelineRenderingCreateInfo pipeline_rendering_create_info = {}; if (bd->VulkanInitInfo.UseDynamicRendering) { IM_ASSERT(info->PipelineRenderingCreateInfo.sType == VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR && "PipelineRenderingCreateInfo::sType must be VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR"); IM_ASSERT(info->PipelineRenderingCreateInfo.pNext == nullptr && "PipelineRenderingCreateInfo::pNext must be nullptr"); - create_info.pNext = &info->PipelineRenderingCreateInfo; + pipeline_rendering_create_info = info->PipelineRenderingCreateInfo; + if (bd->VulkanInitInfo.DescriptorHeapInfo) + pipeline_rendering_create_info.pNext = &ci_heap_flags; + create_info.pNext = &pipeline_rendering_create_info; create_info.renderPass = VK_NULL_HANDLE; // Just make sure it's actually nullptr. } #endif + if (bd->VulkanInitInfo.DescriptorHeapInfo) + { + create_info.pNext = &ci_heap_flags; + } VkPipeline pipeline; VkResult err = vkCreateGraphicsPipelines(device, pipelineCache, 1, &create_info, allocator, &pipeline); check_vk_result(err); @@ -1169,17 +1340,31 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() info.maxAnisotropy = 1.0f; err = vkCreateSampler(v->Device, &info, v->Allocator, &bd->SamplerLinear); check_vk_result(err); - bd->SamplerLinearDS = ImGui_ImplVulkan_CreateSamplerDS(bd->SamplerLinear); + if (v->DescriptorHeapInfo) + { + bd->DescriptorHeapSamplerLinear = + v->DescriptorHeapInfo->RegisterSampler(v->DescriptorHeapInfo->UserContext, &info); + } else + { + bd->SamplerLinearDS = ImGui_ImplVulkan_CreateSamplerDS(bd->SamplerLinear); + } info.magFilter = VK_FILTER_NEAREST; info.minFilter = VK_FILTER_NEAREST; info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; err = vkCreateSampler(v->Device, &info, v->Allocator, &bd->SamplerNearest); check_vk_result(err); - bd->SamplerNearestDS = ImGui_ImplVulkan_CreateSamplerDS(bd->SamplerNearest); + if (v->DescriptorHeapInfo) + { + bd->DescriptorHeapSamplerNearest = + v->DescriptorHeapInfo->RegisterSampler(v->DescriptorHeapInfo->UserContext, &info); + } else + { + bd->SamplerNearestDS = ImGui_ImplVulkan_CreateSamplerDS(bd->SamplerNearest); + } } - if (!bd->PipelineLayout) + if (!bd->PipelineLayout && !v->DescriptorHeapInfo) { // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix VkPushConstantRange push_constants[1] = {}; @@ -1278,6 +1463,11 @@ void ImGui_ImplVulkan_DestroyDeviceObjects() if (bd->PipelineLayout) { vkDestroyPipelineLayout(v->Device, bd->PipelineLayout, v->Allocator); bd->PipelineLayout = VK_NULL_HANDLE; } if (bd->Pipeline) { vkDestroyPipeline(v->Device, bd->Pipeline, v->Allocator); bd->Pipeline = VK_NULL_HANDLE; } if (bd->DescriptorPool) { vkDestroyDescriptorPool(v->Device, bd->DescriptorPool, v->Allocator); bd->DescriptorPool = VK_NULL_HANDLE; } + if (v->DescriptorHeapInfo) + { + v->DescriptorHeapInfo->UnRegisterSampler(v->DescriptorHeapInfo->UserContext, bd->DescriptorHeapSamplerLinear); + v->DescriptorHeapInfo->UnRegisterSampler(v->DescriptorHeapInfo->UserContext, bd->DescriptorHeapSamplerNearest); + } } #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING @@ -1382,7 +1572,9 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) IM_ASSERT(info->Queue != VK_NULL_HANDLE); IM_ASSERT(info->MinImageCount >= 2); IM_ASSERT(info->ImageCount >= info->MinImageCount); - if (info->DescriptorPool != VK_NULL_HANDLE) // Either DescriptorPool or DescriptorPoolSize must be set, not both! + if (info->DescriptorHeapInfo != nullptr) // If using descriptor heap then DescriptorPool and DescriptorPoolSize must be unset. + IM_ASSERT(info->DescriptorPool == VK_NULL_HANDLE && info->DescriptorPoolSize == 0); + else if (info->DescriptorPool != VK_NULL_HANDLE) // Either DescriptorPool or DescriptorPoolSize must be set, not both! IM_ASSERT(info->DescriptorPoolSize == 0); else IM_ASSERT(info->DescriptorPoolSize > 0); @@ -1491,12 +1683,35 @@ void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set) vkFreeDescriptorSets(v->Device, pool, 1, &descriptor_set); } +void ImGui_ImplVulkan_RemoveHeapTexture(uint32_t id) +{ + ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); + ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; + v->DescriptorHeapInfo->UnRegisterImage(v->DescriptorHeapInfo->UserContext, id); +} + 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; } - if (buffers->VertexBufferMemory) { vkFreeMemory(device, buffers->VertexBufferMemory, allocator); buffers->VertexBufferMemory = VK_NULL_HANDLE; } - if (buffers->IndexBuffer) { vkDestroyBuffer(device, buffers->IndexBuffer, allocator); buffers->IndexBuffer = VK_NULL_HANDLE; } - if (buffers->IndexBufferMemory) { vkFreeMemory(device, buffers->IndexBufferMemory, allocator); buffers->IndexBufferMemory = VK_NULL_HANDLE; } + if (buffers->VertexBuffer) + { + vkDestroyBuffer(device, buffers->VertexBuffer, allocator); + buffers->VertexBuffer = VK_NULL_HANDLE; + } + if (buffers->VertexBufferMemory) + { + vkFreeMemory(device, buffers->VertexBufferMemory, allocator); + buffers->VertexBufferMemory = VK_NULL_HANDLE; + } + if (buffers->IndexBuffer) + { + vkDestroyBuffer(device, buffers->IndexBuffer, allocator); + buffers->IndexBuffer = VK_NULL_HANDLE; + } + if (buffers->IndexBufferMemory) + { + vkFreeMemory(device, buffers->IndexBufferMemory, allocator); + buffers->IndexBufferMemory = VK_NULL_HANDLE; + } buffers->VertexBufferSize = 0; buffers->IndexBufferSize = 0; } diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index cf6a3be15..b92231346 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -93,6 +93,15 @@ struct ImGui_ImplVulkan_PipelineInfo #endif }; +struct ImGui_ImplVulkan_DescriptorHeapInfo +{ + uint32_t (*RegisterSampler)(void *, const VkSamplerCreateInfo *); + void (*UnRegisterSampler)(void *, uint32_t); + uint32_t (*RegisterImage)(void *, const VkImageViewCreateInfo *); + void (*UnRegisterImage)(void *, uint32_t); + void *UserContext; +}; + // Initialization data, for ImGui_ImplVulkan_Init() // [Please zero-clear before use!] // - About descriptor pool: @@ -136,6 +145,9 @@ struct ImGui_ImplVulkan_InitInfo // - Shader inputs/outputs need to match ours. Code/data pointed to by the structure needs to survive for whole during of backend usage. VkShaderModuleCreateInfo CustomShaderVertCreateInfo; VkShaderModuleCreateInfo CustomShaderFragCreateInfo; + + // (Optional) If set, use VK_EXT_descriptor_heap. + const ImGui_ImplVulkan_DescriptorHeapInfo *DescriptorHeapInfo; }; // Follow "Getting Started" link and check examples/ folder to learn about using backends! @@ -156,6 +168,7 @@ IMGUI_IMPL_API void ImGui_ImplVulkan_UpdateTexture(ImTextureData* te // Register a texture (VkDescriptorSet for a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE == ImTextureID) IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkImageView image_view, VkImageLayout image_layout); IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set); +IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveHeapTexture(uint32_t id); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout); // Ignore VkSampler diff --git a/backends/vulkan/generate_spv.sh b/backends/vulkan/generate_spv.sh index 948ef773d..d237e7473 100755 --- a/backends/vulkan/generate_spv.sh +++ b/backends/vulkan/generate_spv.sh @@ -4,3 +4,4 @@ ## -o: output file glslangValidator -V -x -o glsl_shader.frag.u32 glsl_shader.frag glslangValidator -V -x -o glsl_shader.vert.u32 glsl_shader.vert +glslangValidator -V --target-env spirv1.2 -x -o glsl_shader_heap.vert.u32 glsl_shader_heap.vert diff --git a/backends/vulkan/glsl_shader_heap.frag b/backends/vulkan/glsl_shader_heap.frag new file mode 100644 index 000000000..20973c0ea --- /dev/null +++ b/backends/vulkan/glsl_shader_heap.frag @@ -0,0 +1,18 @@ +#version 460 core +#extension GL_EXT_descriptor_heap : require +#extension GL_EXT_nonuniform_qualifier : require +layout(location = 0) out vec4 fColor; + +layout(descriptor_heap) uniform texture2D _Textures[]; +layout(descriptor_heap) uniform sampler _Samplers[]; + +layout(location = 0) in struct { + vec4 Color; + vec2 UV; +} In; + +layout(push_constant) uniform uPushConstant { vec2 uScale; vec2 uTranslate; uvec2 uID; } pc; +void main() +{ + fColor = In.Color * texture(sampler2D(_Textures[pc.uID.x], _Samplers[pc.uID.y]), In.UV.st); +} diff --git a/examples/example_win32_vulkan/main.cpp b/examples/example_win32_vulkan/main.cpp index cbdbdb830..e7c2662c3 100644 --- a/examples/example_win32_vulkan/main.cpp +++ b/examples/example_win32_vulkan/main.cpp @@ -47,6 +47,38 @@ static VkDescriptorPool g_DescriptorPool = VK_NULL_HANDLE; static ImGui_ImplVulkanH_Window g_MainWindowData; static uint32_t g_MinImageCount = 2; static bool g_SwapChainRebuild = false; +static bool g_UsingDescriptorHeap = false; + +struct HeapInfo +{ + VkBuffer Buffer; + VkDeviceMemory Memory; + void* Mapped; + VkBindHeapInfoEXT BindInfo; + uint64_t FreeList; + VkDeviceSize Stride; + + uint32_t Allocate() + { + IM_ASSERT(FreeList != 0); + for(uint32_t i = 0; i < 8 * sizeof(FreeList); i++) + { + if(FreeList & (1ull << i)) + { + FreeList ^= (1ull << i); + return i; + } + } + return 0; + } + void Free(uint32_t idx) + { + FreeList |= 1ull << idx; + } +}; +static HeapInfo g_ResourceHeap; +static HeapInfo g_SamplerHeap; +static ImGui_ImplVulkan_DescriptorHeapInfo g_DescriptorHeapInfo; static void check_vk_result(VkResult err) { @@ -74,6 +106,52 @@ static bool IsExtensionAvailable(const ImVector& properti return false; } +// Same as IM_MEMALIGN(). 'alignment' must be a power of two. +static inline VkDeviceSize AlignBufferSize(VkDeviceSize size, VkDeviceSize alignment) +{ + return (size + alignment - 1) & ~(alignment - 1); +} + +static void SetupHeap( + HeapInfo& heap, VkDeviceSize size, VkDeviceSize alignment, VkDeviceSize stride, uint32_t mem_index) +{ + heap.Stride = stride; + + VkDeviceSize buffer_size_aligned = AlignBufferSize(size, alignment); + VkBufferCreateInfo buffer_info = {}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.size = buffer_size_aligned; + buffer_info.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VkResult err = vkCreateBuffer(g_Device, &buffer_info, g_Allocator, &heap.Buffer); + check_vk_result(err); + + VkMemoryRequirements mem_req; + vkGetBufferMemoryRequirements(g_Device, heap.Buffer, &mem_req); + + VkMemoryAllocateFlagsInfo alloc_flags = {}; + alloc_flags.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO; + alloc_flags.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT; + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.pNext = &alloc_flags; + alloc_info.allocationSize = buffer_size_aligned; + alloc_info.memoryTypeIndex = mem_index; + err = vkAllocateMemory(g_Device, &alloc_info, g_Allocator, &heap.Memory); + check_vk_result(err); + err = vkMapMemory(g_Device, heap.Memory, 0, buffer_size_aligned, 0, &heap.Mapped); + check_vk_result(err); + err = vkBindBufferMemory(g_Device, heap.Buffer, heap.Memory, 0); + check_vk_result(err); + + const VkBufferDeviceAddressInfo addr = { + VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, heap.Buffer + }; + heap.BindInfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + heap.BindInfo.heapRange.address = vkGetBufferDeviceAddressKHR(g_Device, &addr); + heap.BindInfo.heapRange.size = size; +} + static void SetupVulkan(ImVector instance_extensions) { VkResult err; @@ -116,6 +194,9 @@ static void SetupVulkan(ImVector instance_extensions) // Create Vulkan Instance create_info.enabledExtensionCount = (uint32_t)instance_extensions.Size; create_info.ppEnabledExtensionNames = instance_extensions.Data; + VkApplicationInfo app_info{}; + app_info.apiVersion = VK_API_VERSION_1_1; + create_info.pApplicationInfo = &app_info; err = vkCreateInstance(&create_info, g_Allocator, &g_Instance); check_vk_result(err); #ifdef IMGUI_IMPL_VULKAN_USE_VOLK @@ -159,6 +240,20 @@ static void SetupVulkan(ImVector instance_extensions) if (IsExtensionAvailable(properties, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) device_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); #endif + if (IsExtensionAvailable(properties, VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME)) + { + device_extensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_MAINTENANCE_2_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_MULTIVIEW_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_SHADER_UNTYPED_POINTERS_EXTENSION_NAME); + device_extensions.push_back(VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME); + + g_UsingDescriptorHeap = true; + } const float queue_priority[] = { 1.0f }; VkDeviceQueueCreateInfo queue_info[1] = {}; @@ -167,6 +262,15 @@ static void SetupVulkan(ImVector instance_extensions) queue_info[0].queueCount = 1; queue_info[0].pQueuePriorities = queue_priority; VkDeviceCreateInfo create_info = {}; + VkPhysicalDeviceBufferDeviceAddressFeatures buffer_device_addr{ + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES, nullptr, VK_TRUE}; + VkPhysicalDeviceDescriptorHeapFeaturesEXT desc_heap_feat{ + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT, &buffer_device_addr, VK_TRUE}; + VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untyped_pointers{ + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR, &desc_heap_feat, VK_TRUE}; + if (g_UsingDescriptorHeap) + create_info.pNext = &untyped_pointers; + create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; create_info.queueCreateInfoCount = sizeof(queue_info) / sizeof(queue_info[0]); create_info.pQueueCreateInfos = queue_info; @@ -177,8 +281,80 @@ static void SetupVulkan(ImVector instance_extensions) vkGetDeviceQueue(g_Device, g_QueueFamily, 0, &g_Queue); } + // Create descriptor heaps + if (g_UsingDescriptorHeap) + { + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptor_heap_properties{ + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT}; + VkPhysicalDeviceProperties2 properties2{ + VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &descriptor_heap_properties}; + vkGetPhysicalDeviceProperties2(g_PhysicalDevice, &properties2); + + size_t resource_size = + descriptor_heap_properties.imageDescriptorSize * IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE + + descriptor_heap_properties.minResourceHeapReservedRange; + size_t sampler_size = + descriptor_heap_properties.samplerDescriptorSize * IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE + + descriptor_heap_properties.minSamplerHeapReservedRange; + + VkPhysicalDeviceMemoryProperties prop; + vkGetPhysicalDeviceMemoryProperties(g_PhysicalDevice, &prop); + uint32_t mem_index = 0; + for (uint32_t i = 0; i < prop.memoryTypeCount; i++) + { + if ((prop.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) + { + mem_index = i; + break; + } + } + + SetupHeap(g_ResourceHeap, resource_size, descriptor_heap_properties.resourceHeapAlignment, descriptor_heap_properties.imageDescriptorSize, mem_index); + SetupHeap(g_SamplerHeap, sampler_size, descriptor_heap_properties.samplerHeapAlignment, descriptor_heap_properties.samplerDescriptorSize, mem_index); + + g_ResourceHeap.BindInfo.reservedRangeOffset = + descriptor_heap_properties.imageDescriptorSize * IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE; + g_ResourceHeap.BindInfo.reservedRangeSize = descriptor_heap_properties.minResourceHeapReservedRange; + g_SamplerHeap.BindInfo.reservedRangeOffset = + descriptor_heap_properties.samplerDescriptorSize * IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE; + g_SamplerHeap.BindInfo.reservedRangeSize = descriptor_heap_properties.minSamplerHeapReservedRange; + + g_ResourceHeap.FreeList = (1ull << IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE) - 1; + g_SamplerHeap.FreeList = (1ull << IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE) - 1; + + g_DescriptorHeapInfo.RegisterImage = [](void*, const VkImageViewCreateInfo* ci) -> uint32_t { + uint32_t idx = g_ResourceHeap.Allocate(); + + VkImageDescriptorInfoEXT image{}; + image.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT; + image.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + image.pView = ci; + VkResourceDescriptorInfoEXT r{}; + r.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + r.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + r.data.pImage = ℑ + VkHostAddressRangeEXT addr{ + (char*)g_ResourceHeap.Mapped + idx * g_ResourceHeap.Stride, g_ResourceHeap.Stride}; + vkWriteResourceDescriptorsEXT(g_Device, 1, &r, &addr); + + return idx; + }; + g_DescriptorHeapInfo.UnRegisterImage = [](void*, uint32_t idx) { g_ResourceHeap.Free(idx); }; + g_DescriptorHeapInfo.RegisterSampler = [](void*, const VkSamplerCreateInfo* ci) -> uint32_t { + uint32_t idx = g_SamplerHeap.Allocate(); + + VkHostAddressRangeEXT addr{(char*)g_SamplerHeap.Mapped + idx * g_SamplerHeap.Stride, g_SamplerHeap.Stride}; + vkWriteSamplerDescriptorsEXT(g_Device, 1, ci, &addr); + + return idx; + }; + g_DescriptorHeapInfo.UnRegisterSampler = [](void*, uint32_t idx) { g_SamplerHeap.Free(idx); }; + } + // Create Descriptor Pool // If you wish to load e.g. additional textures you may need to alter pools sizes and maxSets. + if (!g_UsingDescriptorHeap) { VkDescriptorPoolSize pool_sizes[] = { @@ -233,7 +409,17 @@ static void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface static void CleanupVulkan() { - vkDestroyDescriptorPool(g_Device, g_DescriptorPool, g_Allocator); + if (g_UsingDescriptorHeap) + { + vkDestroyBuffer(g_Device, g_ResourceHeap.Buffer, g_Allocator); + vkDestroyBuffer(g_Device, g_SamplerHeap.Buffer, g_Allocator); + vkFreeMemory(g_Device, g_ResourceHeap.Memory, g_Allocator); + vkFreeMemory(g_Device, g_SamplerHeap.Memory, g_Allocator); + } + else + { + vkDestroyDescriptorPool(g_Device, g_DescriptorPool, g_Allocator); + } #ifdef APP_USE_VULKAN_DEBUG_REPORT // Remove the debug report callback @@ -292,6 +478,12 @@ static void FrameRender(ImGui_ImplVulkanH_Window* wd, ImDrawData* draw_data) vkCmdBeginRenderPass(fd->CommandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE); } + if (g_UsingDescriptorHeap) + { + vkCmdBindResourceHeapEXT(fd->CommandBuffer, &g_ResourceHeap.BindInfo); + vkCmdBindSamplerHeapEXT(fd->CommandBuffer, &g_SamplerHeap.BindInfo); + } + // Record dear imgui primitives into command buffer ImGui_ImplVulkan_RenderDrawData(draw_data, fd->CommandBuffer); @@ -411,6 +603,8 @@ int main(int, char**) init_info.PipelineInfoMain.Subpass = 0; init_info.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT; init_info.CheckVkResultFn = check_vk_result; + if (g_UsingDescriptorHeap) + init_info.DescriptorHeapInfo = &g_DescriptorHeapInfo; ImGui_ImplVulkan_Init(&init_info); // Load Fonts From a988e6f17317ce25247d3be7da4d64d9d378b7df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Aleksander=20G=C3=B3rski?= Date: Fri, 24 Jul 2026 00:28:43 +0900 Subject: [PATCH 02/11] Vulkan: Add VK_EXT_descriptor_heap example to example_glfw_vulkan, gate heap API with IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP, and load vkCmdPushDataEXT via vkGetDeviceProcAddr. Fix: Fix pNext chain overwrite in pipeline creation --- backends/imgui_impl_vulkan.cpp | 71 +++++- backends/imgui_impl_vulkan.h | 13 +- examples/example_glfw_vulkan/main.cpp | 305 ++++++++++++++++++++++++- examples/example_win32_vulkan/main.cpp | 196 +--------------- 4 files changed, 374 insertions(+), 211 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index e86f23337..f1a516792 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -171,7 +171,6 @@ static bool g_FunctionsLoaded = true; IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdDrawIndexed) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdPipelineBarrier) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdPushConstants) \ - IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdPushDataEXT) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdSetScissor) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkCmdSetViewport) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkCreateBuffer) \ @@ -242,6 +241,11 @@ static PFN_vkCmdBeginRenderingKHR ImGuiImplVulkanFuncs_vkCmdBeginRenderingKHR; static PFN_vkCmdEndRenderingKHR ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR; #endif +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP +// VK_EXT_descriptor_heap (not exported by most Vulkan loaders; resolve like dynamic rendering) +static PFN_vkCmdPushDataEXT ImGuiImplVulkanFuncs_vkCmdPushDataEXT = nullptr; +#endif + // Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplVulkan_RenderDrawData() // [Please zero-clear before use!] struct ImGui_ImplVulkan_FrameRenderBuffers @@ -428,6 +432,7 @@ static uint32_t __glsl_shader_frag_spv[] = // backends/vulkan/glsl_shader_heap.frag, compiled with: // # glslangValidator -V --target-env spirv1.2 -x -o glsl_shader.frag.u32 glsl_shader.frag +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP /* #version 460 core #extension GL_EXT_descriptor_heap : require @@ -494,6 +499,7 @@ static uint32_t __glsl_shader_heap_frag_spv[] = 0x00000032,0x00050057,0x00000007,0x00000034,0x0000002f,0x00000033,0x00050085,0x00000007, 0x00000035,0x00000012,0x00000034,0x0003003e,0x00000009,0x00000035,0x000100fd,0x00010038 }; +#endif // IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP //----------------------------------------------------------------------------- @@ -606,6 +612,7 @@ static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline pd.constants[1] = 2.0f / draw_data->DisplaySize.y; pd.constants[2] = -1.0f - draw_data->DisplayPos.x * pd.constants[0]; // Translate pd.constants[3] = -1.0f - draw_data->DisplayPos.y * pd.constants[1]; +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (bd->VulkanInitInfo.DescriptorHeapInfo) { pd.ids[0] = 0; @@ -615,9 +622,10 @@ static void ImGui_ImplVulkan_SetupRenderState(ImDrawData* draw_data, VkPipeline push.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; push.data.address = &pd; push.data.size = sizeof(pd); - vkCmdPushDataEXT(bd->RenderState->CommandBuffer, &push); + ImGuiImplVulkanFuncs_vkCmdPushDataEXT(bd->RenderState->CommandBuffer, &push); } else +#endif { // Setup scale and translation: // Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. @@ -633,6 +641,7 @@ static void ImGui_ImplVulkan_DrawCallback_ResetRenderState(const ImDrawList*, co static void ImGui_ImplVulkan_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (bd->VulkanInitInfo.DescriptorHeapInfo) { VkPushDataInfoEXT push{}; @@ -640,9 +649,10 @@ static void ImGui_ImplVulkan_DrawCallback_SetSamplerLinear(const ImDrawList*, co push.data.address = &bd->DescriptorHeapSamplerLinear; push.data.size = 4; push.offset = 20; - vkCmdPushDataEXT(bd->RenderState->CommandBuffer, &push); + ImGuiImplVulkanFuncs_vkCmdPushDataEXT(bd->RenderState->CommandBuffer, &push); } else +#endif { vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerLinearDS, 0, nullptr); } @@ -650,6 +660,7 @@ static void ImGui_ImplVulkan_DrawCallback_SetSamplerLinear(const ImDrawList*, co static void ImGui_ImplVulkan_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (bd->VulkanInitInfo.DescriptorHeapInfo) { VkPushDataInfoEXT push{}; @@ -657,9 +668,10 @@ static void ImGui_ImplVulkan_DrawCallback_SetSamplerNearest(const ImDrawList*, c push.data.address = &bd->DescriptorHeapSamplerNearest; push.data.size = 4; push.offset = 20; - vkCmdPushDataEXT(bd->RenderState->CommandBuffer, &push); + ImGuiImplVulkanFuncs_vkCmdPushDataEXT(bd->RenderState->CommandBuffer, &push); } else +#endif { vkCmdBindDescriptorSets(bd->RenderState->CommandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 1, 1, &bd->SamplerNearestDS, 0, nullptr); } @@ -808,6 +820,7 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm VkDescriptorSet image_view = (VkDescriptorSet)image_id; if (image_view != last_image_view) { +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (v->DescriptorHeapInfo) { VkPushDataInfoEXT push{}; @@ -816,9 +829,10 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm push.data.address = &image_id; push.data.size = 4; push.offset = 16; - vkCmdPushDataEXT(command_buffer, &push); + ImGuiImplVulkanFuncs_vkCmdPushDataEXT(command_buffer, &push); } else +#endif { vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, bd->PipelineLayout, 0, 1, &image_view, 0, nullptr); } @@ -855,8 +869,10 @@ static void ImGui_ImplVulkan_DestroyTexture(ImTextureData* tex) ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; if (backend_tex->DescriptorSet != VK_NULL_HANDLE) ImGui_ImplVulkan_RemoveTexture(backend_tex->DescriptorSet); +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP else ImGui_ImplVulkan_RemoveHeapTexture(backend_tex->DescriptorHeapIndex); +#endif if (backend_tex->ImageView != VK_NULL_HANDLE) vkDestroyImageView(v->Device, backend_tex->ImageView, v->Allocator); vkDestroyImage(v->Device, backend_tex->Image, v->Allocator); @@ -924,6 +940,7 @@ void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex) info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; info.subresourceRange.levelCount = 1; info.subresourceRange.layerCount = 1; +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (v->DescriptorHeapInfo) { backend_tex->DescriptorHeapIndex = @@ -932,6 +949,7 @@ void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex) tex->SetTexID((ImTextureID)(backend_tex->DescriptorHeapIndex | 0x80000000)); } else +#endif { // Create the Image View: err = vkCreateImageView(v->Device, &info, v->Allocator, &backend_tex->ImageView); @@ -1106,11 +1124,13 @@ static void ImGui_ImplVulkan_CreateShaderModules(VkDevice device, const VkAlloca default_frag_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; default_frag_info.codeSize = sizeof(__glsl_shader_frag_spv); default_frag_info.pCode = (uint32_t*)__glsl_shader_frag_spv; +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (v->DescriptorHeapInfo) { default_frag_info.codeSize = sizeof(__glsl_shader_heap_frag_spv); default_frag_info.pCode = (uint32_t*)__glsl_shader_heap_frag_spv; } +#endif VkShaderModuleCreateInfo* p_frag_info = (v->CustomShaderFragCreateInfo.sType == VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO) ? &v->CustomShaderFragCreateInfo : &default_frag_info; VkResult err = vkCreateShaderModule(device, p_frag_info, allocator, &bd->ShaderModuleFrag); check_vk_result(err); @@ -1224,9 +1244,11 @@ static VkPipeline ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAlloc create_info.renderPass = info->RenderPass; create_info.subpass = info->Subpass; +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP VkPipelineCreateFlags2CreateInfo ci_heap_flags{}; ci_heap_flags.sType = VK_STRUCTURE_TYPE_PIPELINE_CREATE_FLAGS_2_CREATE_INFO; ci_heap_flags.flags = VK_PIPELINE_CREATE_2_DESCRIPTOR_HEAP_BIT_EXT; +#endif #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING VkPipelineRenderingCreateInfo pipeline_rendering_create_info = {}; @@ -1235,16 +1257,23 @@ static VkPipeline ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAlloc IM_ASSERT(info->PipelineRenderingCreateInfo.sType == VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR && "PipelineRenderingCreateInfo::sType must be VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR"); IM_ASSERT(info->PipelineRenderingCreateInfo.pNext == nullptr && "PipelineRenderingCreateInfo::pNext must be nullptr"); pipeline_rendering_create_info = info->PipelineRenderingCreateInfo; +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (bd->VulkanInitInfo.DescriptorHeapInfo) pipeline_rendering_create_info.pNext = &ci_heap_flags; +#endif create_info.pNext = &pipeline_rendering_create_info; create_info.renderPass = VK_NULL_HANDLE; // Just make sure it's actually nullptr. } #endif +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (bd->VulkanInitInfo.DescriptorHeapInfo) { - create_info.pNext = &ci_heap_flags; + if (create_info.pNext == nullptr) + { + create_info.pNext = &ci_heap_flags; + } } +#endif VkPipeline pipeline; VkResult err = vkCreateGraphicsPipelines(device, pipelineCache, 1, &create_info, allocator, &pipeline); check_vk_result(err); @@ -1340,11 +1369,13 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() info.maxAnisotropy = 1.0f; err = vkCreateSampler(v->Device, &info, v->Allocator, &bd->SamplerLinear); check_vk_result(err); +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (v->DescriptorHeapInfo) { bd->DescriptorHeapSamplerLinear = v->DescriptorHeapInfo->RegisterSampler(v->DescriptorHeapInfo->UserContext, &info); } else +#endif { bd->SamplerLinearDS = ImGui_ImplVulkan_CreateSamplerDS(bd->SamplerLinear); } @@ -1354,17 +1385,23 @@ bool ImGui_ImplVulkan_CreateDeviceObjects() info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; err = vkCreateSampler(v->Device, &info, v->Allocator, &bd->SamplerNearest); check_vk_result(err); +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (v->DescriptorHeapInfo) { bd->DescriptorHeapSamplerNearest = v->DescriptorHeapInfo->RegisterSampler(v->DescriptorHeapInfo->UserContext, &info); } else +#endif { bd->SamplerNearestDS = ImGui_ImplVulkan_CreateSamplerDS(bd->SamplerNearest); } } +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (!bd->PipelineLayout && !v->DescriptorHeapInfo) +#else + if (!bd->PipelineLayout) +#endif { // Constants: we are using 'vec2 offset' and 'vec2 scale' instead of a full 3d projection matrix VkPushConstantRange push_constants[1] = {}; @@ -1463,11 +1500,13 @@ void ImGui_ImplVulkan_DestroyDeviceObjects() if (bd->PipelineLayout) { vkDestroyPipelineLayout(v->Device, bd->PipelineLayout, v->Allocator); bd->PipelineLayout = VK_NULL_HANDLE; } if (bd->Pipeline) { vkDestroyPipeline(v->Device, bd->Pipeline, v->Allocator); bd->Pipeline = VK_NULL_HANDLE; } if (bd->DescriptorPool) { vkDestroyDescriptorPool(v->Device, bd->DescriptorPool, v->Allocator); bd->DescriptorPool = VK_NULL_HANDLE; } +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (v->DescriptorHeapInfo) { v->DescriptorHeapInfo->UnRegisterSampler(v->DescriptorHeapInfo->UserContext, bd->DescriptorHeapSamplerLinear); v->DescriptorHeapInfo->UnRegisterSampler(v->DescriptorHeapInfo->UserContext, bd->DescriptorHeapSamplerNearest); } +#endif } #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING @@ -1520,6 +1559,9 @@ bool ImGui_ImplVulkan_LoadFunctions(uint32_t api_version, PFN_vkVoidFunction( #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING ImGui_ImplVulkan_LoadDynamicRenderingFunctions(api_version, loader_func, user_data); #endif +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP + ImGuiImplVulkanFuncs_vkCmdPushDataEXT = reinterpret_cast(loader_func("vkCmdPushDataEXT", user_data)); +#endif #else IM_UNUSED(loader_func); IM_UNUSED(user_data); @@ -1549,6 +1591,16 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) #endif } +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP + if (info->DescriptorHeapInfo != nullptr) + { +#ifndef IMGUI_IMPL_VULKAN_USE_LOADER + ImGuiImplVulkanFuncs_vkCmdPushDataEXT = reinterpret_cast(vkGetDeviceProcAddr(info->Device, "vkCmdPushDataEXT")); +#endif + IM_ASSERT(ImGuiImplVulkanFuncs_vkCmdPushDataEXT != nullptr && "vkCmdPushDataEXT not available (enable VK_EXT_descriptor_heap)"); + } +#endif + ImGuiIO& io = ImGui::GetIO(); IMGUI_CHECKVERSION(); IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!"); @@ -1572,9 +1624,12 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) IM_ASSERT(info->Queue != VK_NULL_HANDLE); IM_ASSERT(info->MinImageCount >= 2); IM_ASSERT(info->ImageCount >= info->MinImageCount); +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (info->DescriptorHeapInfo != nullptr) // If using descriptor heap then DescriptorPool and DescriptorPoolSize must be unset. IM_ASSERT(info->DescriptorPool == VK_NULL_HANDLE && info->DescriptorPoolSize == 0); - else if (info->DescriptorPool != VK_NULL_HANDLE) // Either DescriptorPool or DescriptorPoolSize must be set, not both! + else +#endif + if (info->DescriptorPool != VK_NULL_HANDLE) // Either DescriptorPool or DescriptorPoolSize must be set, not both! IM_ASSERT(info->DescriptorPoolSize == 0); else IM_ASSERT(info->DescriptorPoolSize > 0); @@ -1683,12 +1738,14 @@ void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set) vkFreeDescriptorSets(v->Device, pool, 1, &descriptor_set); } +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP void ImGui_ImplVulkan_RemoveHeapTexture(uint32_t id) { ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; v->DescriptorHeapInfo->UnRegisterImage(v->DescriptorHeapInfo->UserContext, id); } +#endif void ImGui_ImplVulkan_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkan_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator) { diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index b92231346..4094a057b 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -76,6 +76,9 @@ #if defined(VK_VERSION_1_3) || defined(VK_KHR_dynamic_rendering) #define IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING #endif +#if defined(VK_EXT_descriptor_heap) +#define IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP +#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 @@ -93,6 +96,7 @@ struct ImGui_ImplVulkan_PipelineInfo #endif }; +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP struct ImGui_ImplVulkan_DescriptorHeapInfo { uint32_t (*RegisterSampler)(void *, const VkSamplerCreateInfo *); @@ -101,6 +105,7 @@ struct ImGui_ImplVulkan_DescriptorHeapInfo void (*UnRegisterImage)(void *, uint32_t); void *UserContext; }; +#endif // Initialization data, for ImGui_ImplVulkan_Init() // [Please zero-clear before use!] @@ -108,8 +113,10 @@ struct ImGui_ImplVulkan_DescriptorHeapInfo // - A VkDescriptorPool should be created with VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, // and must contain a pool size large enough to hold a small number of VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER descriptors. // - As an convenience, by setting DescriptorPoolSize > 0 the backend will create one for you. -// - About dynamic rendering: + // - About dynamic rendering: // - When using dynamic rendering, set UseDynamicRendering=true + fill PipelineInfoMain.PipelineRenderingCreateInfo structure. +// - About descriptor heap (requires VK_EXT_descriptor_heap in your Vulkan headers → IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP): +// - When using descriptor heaps, set DescriptorHeapInfo and leave DescriptorPool / DescriptorPoolSize unset. struct ImGui_ImplVulkan_InitInfo { uint32_t ApiVersion; // Fill with API version of Instance, e.g. VK_API_VERSION_1_3 or your value of VkApplicationInfo::apiVersion. May be lower than header version (VK_HEADER_VERSION_COMPLETE) @@ -146,8 +153,10 @@ struct ImGui_ImplVulkan_InitInfo VkShaderModuleCreateInfo CustomShaderVertCreateInfo; VkShaderModuleCreateInfo CustomShaderFragCreateInfo; +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP // (Optional) If set, use VK_EXT_descriptor_heap. const ImGui_ImplVulkan_DescriptorHeapInfo *DescriptorHeapInfo; +#endif }; // Follow "Getting Started" link and check examples/ folder to learn about using backends! @@ -168,7 +177,9 @@ IMGUI_IMPL_API void ImGui_ImplVulkan_UpdateTexture(ImTextureData* te // Register a texture (VkDescriptorSet for a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE == ImTextureID) IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkImageView image_view, VkImageLayout image_layout); IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set); +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveHeapTexture(uint32_t id); +#endif #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout); // Ignore VkSampler diff --git a/examples/example_glfw_vulkan/main.cpp b/examples/example_glfw_vulkan/main.cpp index f2139cef7..c1128a17b 100644 --- a/examples/example_glfw_vulkan/main.cpp +++ b/examples/example_glfw_vulkan/main.cpp @@ -41,6 +41,141 @@ static VkDebugReportCallbackEXT g_DebugReport = VK_NULL_HANDLE; #endif +// Config for example app +static const int APP_RESOURCE_HEAP_SIZE = 64; +static const int APP_SAMPLER_HEAP_SIZE = 64; + +static void check_vk_result(VkResult err); + +// Explicitly loaded VK_EXT_descriptor_heap entry points. +// Stored as members so names can match the Vulkan entry points without colliding with prototypes. +struct DescriptorHeapFunctions +{ + PFN_vkWriteResourceDescriptorsEXT vkWriteResourceDescriptorsEXT = nullptr; + PFN_vkCmdBindResourceHeapEXT vkCmdBindResourceHeapEXT = nullptr; + PFN_vkCmdBindSamplerHeapEXT vkCmdBindSamplerHeapEXT = nullptr; + PFN_vkWriteSamplerDescriptorsEXT vkWriteSamplerDescriptorsEXT = nullptr; + PFN_vkGetBufferDeviceAddress vkGetBufferDeviceAddress = nullptr; + + void Load(VkDevice device) + { + vkWriteResourceDescriptorsEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkWriteResourceDescriptorsEXT")); + vkCmdBindResourceHeapEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBindResourceHeapEXT")); + vkCmdBindSamplerHeapEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkCmdBindSamplerHeapEXT")); + vkWriteSamplerDescriptorsEXT = reinterpret_cast(vkGetDeviceProcAddr(device, "vkWriteSamplerDescriptorsEXT")); + vkGetBufferDeviceAddress = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetBufferDeviceAddress")); + if (vkGetBufferDeviceAddress == nullptr) + vkGetBufferDeviceAddress = reinterpret_cast(vkGetDeviceProcAddr(device, "vkGetBufferDeviceAddressKHR")); + IM_ASSERT(vkWriteResourceDescriptorsEXT && vkCmdBindResourceHeapEXT && vkCmdBindSamplerHeapEXT && vkWriteSamplerDescriptorsEXT && vkGetBufferDeviceAddress); + } +}; +static DescriptorHeapFunctions g_DescHeapFns; + +// Simple free-list allocator modeled after example_win32_directx12 +struct ExampleDescriptorHeapAllocator +{ + VkBuffer Buffer = VK_NULL_HANDLE; + VkDeviceMemory Memory = VK_NULL_HANDLE; + void* Mapped = nullptr; + VkBindHeapInfoEXT BindInfo = {}; + VkDeviceSize Stride = 0; + int Capacity = 0; + ImVector FreeIndices; + + void Create(VkDevice device, VkPhysicalDevice physical_device, VkDeviceSize descriptor_size, VkDeviceSize alignment, VkDeviceSize reserved_range, int capacity, const VkAllocationCallbacks* allocator) + { + IM_ASSERT(Buffer == VK_NULL_HANDLE && FreeIndices.empty()); + Capacity = capacity; + Stride = descriptor_size; + + VkDeviceSize descriptors_bytes = descriptor_size * (VkDeviceSize)capacity; + VkDeviceSize total_size = descriptors_bytes + reserved_range; + VkDeviceSize buffer_size_aligned = (total_size + alignment - 1) & ~(alignment - 1); + + VkBufferCreateInfo buffer_info = {}; + buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; + buffer_info.size = buffer_size_aligned; + buffer_info.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; + buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VkResult err = vkCreateBuffer(device, &buffer_info, allocator, &Buffer); + check_vk_result(err); + + VkMemoryRequirements mem_req; + vkGetBufferMemoryRequirements(device, Buffer, &mem_req); + + VkPhysicalDeviceMemoryProperties mem_props; + vkGetPhysicalDeviceMemoryProperties(physical_device, &mem_props); + const VkMemoryPropertyFlags required_flags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; + uint32_t mem_index = (uint32_t)-1; + for (uint32_t i = 0; i < mem_props.memoryTypeCount; i++) + { + if ((mem_req.memoryTypeBits & (1u << i)) == 0) + continue; + if ((mem_props.memoryTypes[i].propertyFlags & required_flags) == required_flags) + { + mem_index = i; + break; + } + } + IM_ASSERT(mem_index != (uint32_t)-1 && "No HOST_VISIBLE|HOST_COHERENT memory type compatible with descriptor heap buffer"); + + VkMemoryAllocateFlagsInfo alloc_flags = {}; + alloc_flags.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO; + alloc_flags.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT; + VkMemoryAllocateInfo alloc_info = {}; + alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + alloc_info.pNext = &alloc_flags; + alloc_info.allocationSize = mem_req.size > buffer_size_aligned ? mem_req.size : buffer_size_aligned; + alloc_info.memoryTypeIndex = mem_index; + err = vkAllocateMemory(device, &alloc_info, allocator, &Memory); + check_vk_result(err); + err = vkMapMemory(device, Memory, 0, alloc_info.allocationSize, 0, &Mapped); + check_vk_result(err); + err = vkBindBufferMemory(device, Buffer, Memory, 0); + check_vk_result(err); + + VkBufferDeviceAddressInfo addr_info = {}; + addr_info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO; + addr_info.buffer = Buffer; + + BindInfo = {}; + BindInfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; + BindInfo.heapRange.address = g_DescHeapFns.vkGetBufferDeviceAddress(device, &addr_info); + BindInfo.heapRange.size = total_size; + BindInfo.reservedRangeOffset = descriptors_bytes; + BindInfo.reservedRangeSize = reserved_range; + + FreeIndices.reserve(capacity); + for (int n = capacity; n > 0; n--) + FreeIndices.push_back(n - 1); + } + void Destroy(VkDevice device, const VkAllocationCallbacks* allocator) + { + if (Buffer) { vkDestroyBuffer(device, Buffer, allocator); Buffer = VK_NULL_HANDLE; } + if (Memory) { vkFreeMemory(device, Memory, allocator); Memory = VK_NULL_HANDLE; } + Mapped = nullptr; + FreeIndices.clear(); + Capacity = 0; + Stride = 0; + BindInfo = {}; + } + int Alloc() + { + IM_ASSERT(FreeIndices.Size > 0); + int idx = FreeIndices.back(); + FreeIndices.pop_back(); + return idx; + } + void Free(int idx) + { + FreeIndices.push_back(idx); + } + VkHostAddressRangeEXT DescriptorAddress(int idx) const + { + return VkHostAddressRangeEXT{ (char*)Mapped + (VkDeviceSize)idx * Stride, Stride }; + } +}; + // Data static VkAllocationCallbacks* g_Allocator = nullptr; static VkInstance g_Instance = VK_NULL_HANDLE; @@ -54,6 +189,10 @@ static VkDescriptorPool g_DescriptorPool = VK_NULL_HANDLE; static ImGui_ImplVulkanH_Window g_MainWindowData; static uint32_t g_MinImageCount = 2; static bool g_SwapChainRebuild = false; +static bool g_UsingDescriptorHeap = false; +static ExampleDescriptorHeapAllocator g_ResourceHeapAlloc; +static ExampleDescriptorHeapAllocator g_SamplerHeapAlloc; +static ImGui_ImplVulkan_DescriptorHeapInfo g_DescriptorHeapInfo; static void glfw_error_callback(int error, const char* description) { @@ -85,7 +224,17 @@ static bool IsExtensionAvailable(const ImVector& properti return false; } -static void SetupVulkan(ImVector instance_extensions) +static bool PhysicalDeviceHasExtension(VkPhysicalDevice physical_device, const char* extension) +{ + uint32_t properties_count = 0; + vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &properties_count, nullptr); + ImVector properties; + properties.resize(properties_count); + vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &properties_count, properties.Data); + return IsExtensionAvailable(properties, extension); +} + +static void SetupVulkan(ImVector instance_extensions, bool require_descriptor_heap) { VkResult err; #ifdef IMGUI_IMPL_VULKAN_USE_VOLK @@ -127,6 +276,10 @@ static void SetupVulkan(ImVector instance_extensions) // Create Vulkan Instance create_info.enabledExtensionCount = (uint32_t)instance_extensions.Size; create_info.ppEnabledExtensionNames = instance_extensions.Data; + VkApplicationInfo app_info = {}; + app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; + app_info.apiVersion = VK_API_VERSION_1_1; + create_info.pApplicationInfo = &app_info; err = vkCreateInstance(&create_info, g_Allocator, &g_Instance); check_vk_result(err); #ifdef IMGUI_IMPL_VULKAN_USE_VOLK @@ -147,10 +300,41 @@ static void SetupVulkan(ImVector instance_extensions) #endif } - // Select Physical Device (GPU) - g_PhysicalDevice = ImGui_ImplVulkanH_SelectPhysicalDevice(g_Instance); + if (require_descriptor_heap) + { + uint32_t device_count = 0; + vkEnumeratePhysicalDevices(g_Instance, &device_count, nullptr); + ImVector physical_devices; + physical_devices.resize(device_count); + vkEnumeratePhysicalDevices(g_Instance, &device_count, physical_devices.Data); + + VkPhysicalDevice device_with_heap = VK_NULL_HANDLE; + for (uint32_t i = 0; i < device_count; i++) + { + VkPhysicalDeviceProperties props; + vkGetPhysicalDeviceProperties(physical_devices[i], &props); + const bool has_heap = PhysicalDeviceHasExtension(physical_devices[i], VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME); + + if (has_heap && device_with_heap == VK_NULL_HANDLE) + device_with_heap = physical_devices[i]; + } + + if (device_with_heap == VK_NULL_HANDLE) + { + fprintf(stderr, "Error: --require-descriptor-heap requested but no device with VK_EXT_descriptor_heap was found.\n"); + exit(1); + } + g_PhysicalDevice = device_with_heap; + } + else + { + g_PhysicalDevice = ImGui_ImplVulkanH_SelectPhysicalDevice(g_Instance); + } IM_ASSERT(g_PhysicalDevice != VK_NULL_HANDLE); + VkPhysicalDeviceProperties chosen_props; + vkGetPhysicalDeviceProperties(g_PhysicalDevice, &chosen_props); + // Select graphics queue family g_QueueFamily = ImGui_ImplVulkanH_SelectQueueFamilyIndex(g_PhysicalDevice); IM_ASSERT(g_QueueFamily != (uint32_t)-1); @@ -170,6 +354,19 @@ static void SetupVulkan(ImVector instance_extensions) if (IsExtensionAvailable(properties, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) device_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); #endif + if (IsExtensionAvailable(properties, VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME)) + { + device_extensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_MAINTENANCE_2_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_MULTIVIEW_EXTENSION_NAME); + device_extensions.push_back(VK_KHR_SHADER_UNTYPED_POINTERS_EXTENSION_NAME); + device_extensions.push_back(VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME); + g_UsingDescriptorHeap = true; + } const float queue_priority[] = { 1.0f }; VkDeviceQueueCreateInfo queue_info[1] = {}; @@ -183,14 +380,71 @@ static void SetupVulkan(ImVector instance_extensions) create_info.pQueueCreateInfos = queue_info; create_info.enabledExtensionCount = (uint32_t)device_extensions.Size; create_info.ppEnabledExtensionNames = device_extensions.Data; + + VkPhysicalDeviceBufferDeviceAddressFeatures buffer_device_addr = {}; + buffer_device_addr.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES; + buffer_device_addr.bufferDeviceAddress = VK_TRUE; + VkPhysicalDeviceDescriptorHeapFeaturesEXT desc_heap_feat = {}; + desc_heap_feat.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT; + desc_heap_feat.pNext = &buffer_device_addr; + desc_heap_feat.descriptorHeap = VK_TRUE; + VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untyped_pointers = {}; + untyped_pointers.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR; + untyped_pointers.pNext = &desc_heap_feat; + untyped_pointers.shaderUntypedPointers = VK_TRUE; + if (g_UsingDescriptorHeap) + create_info.pNext = &untyped_pointers; + err = vkCreateDevice(g_PhysicalDevice, &create_info, g_Allocator, &g_Device); check_vk_result(err); vkGetDeviceQueue(g_Device, g_QueueFamily, 0, &g_Queue); } - // Create Descriptor Pool - // If you wish to load e.g. additional textures you may need to alter pools sizes and maxSets. + if (g_UsingDescriptorHeap) { + g_DescHeapFns.Load(g_Device); + + VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptor_heap_properties = {}; + descriptor_heap_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT; + VkPhysicalDeviceProperties2 properties2 = {}; + properties2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; + properties2.pNext = &descriptor_heap_properties; + vkGetPhysicalDeviceProperties2(g_PhysicalDevice, &properties2); + + g_ResourceHeapAlloc.Create(g_Device, g_PhysicalDevice, descriptor_heap_properties.imageDescriptorSize, descriptor_heap_properties.resourceHeapAlignment, + descriptor_heap_properties.minResourceHeapReservedRange, APP_RESOURCE_HEAP_SIZE, g_Allocator); + g_SamplerHeapAlloc.Create(g_Device, g_PhysicalDevice, descriptor_heap_properties.samplerDescriptorSize, descriptor_heap_properties.samplerHeapAlignment, + descriptor_heap_properties.minSamplerHeapReservedRange, APP_SAMPLER_HEAP_SIZE, g_Allocator); + + // Allocating descriptors is up to the application, so we provide callbacks (same idea as DX12 SrvDescriptorAllocFn/FreeFn). + g_DescriptorHeapInfo.UserContext = nullptr; + g_DescriptorHeapInfo.RegisterImage = [](void*, const VkImageViewCreateInfo* ci) -> uint32_t { + int idx = g_ResourceHeapAlloc.Alloc(); + VkImageDescriptorInfoEXT image = {}; + image.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT; + image.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + image.pView = ci; + VkResourceDescriptorInfoEXT resource = {}; + resource.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; + resource.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; + resource.data.pImage = ℑ + VkHostAddressRangeEXT addr = g_ResourceHeapAlloc.DescriptorAddress(idx); + check_vk_result(g_DescHeapFns.vkWriteResourceDescriptorsEXT(g_Device, 1, &resource, &addr)); + return (uint32_t)idx; + }; + g_DescriptorHeapInfo.UnRegisterImage = [](void*, uint32_t idx) { g_ResourceHeapAlloc.Free((int)idx); }; + g_DescriptorHeapInfo.RegisterSampler = [](void*, const VkSamplerCreateInfo* ci) -> uint32_t { + int idx = g_SamplerHeapAlloc.Alloc(); + VkHostAddressRangeEXT addr = g_SamplerHeapAlloc.DescriptorAddress(idx); + check_vk_result(g_DescHeapFns.vkWriteSamplerDescriptorsEXT(g_Device, 1, ci, &addr)); + return (uint32_t)idx; + }; + g_DescriptorHeapInfo.UnRegisterSampler = [](void*, uint32_t idx) { g_SamplerHeapAlloc.Free((int)idx); }; + } + else + { + // Create Descriptor Pool + // If you wish to load e.g. additional textures you may need to alter pools sizes and maxSets. VkDescriptorPoolSize pool_sizes[] = { { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE }, @@ -244,7 +498,15 @@ static void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface static void CleanupVulkan() { - vkDestroyDescriptorPool(g_Device, g_DescriptorPool, g_Allocator); + if (g_UsingDescriptorHeap) + { + g_ResourceHeapAlloc.Destroy(g_Device, g_Allocator); + g_SamplerHeapAlloc.Destroy(g_Device, g_Allocator); + } + else + { + vkDestroyDescriptorPool(g_Device, g_DescriptorPool, g_Allocator); + } #ifdef APP_USE_VULKAN_DEBUG_REPORT // Remove the debug report callback @@ -303,6 +565,12 @@ static void FrameRender(ImGui_ImplVulkanH_Window* wd, ImDrawData* draw_data) vkCmdBeginRenderPass(fd->CommandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE); } + if (g_UsingDescriptorHeap) + { + g_DescHeapFns.vkCmdBindResourceHeapEXT(fd->CommandBuffer, &g_ResourceHeapAlloc.BindInfo); + g_DescHeapFns.vkCmdBindSamplerHeapEXT(fd->CommandBuffer, &g_SamplerHeapAlloc.BindInfo); + } + // Record dear imgui primitives into command buffer ImGui_ImplVulkan_RenderDrawData(draw_data, fd->CommandBuffer); @@ -350,8 +618,27 @@ static void FramePresent(ImGui_ImplVulkanH_Window* wd) } // Main code -int main(int, char**) +int main(int argc, char** argv) { + bool require_descriptor_heap = false; + for (int i = 1; i < argc; i++) + { + if (strcmp(argv[i], "--require-descriptor-heap") == 0) + require_descriptor_heap = true; + else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) + { + printf("Usage: %s [--require-descriptor-heap]\n", argv[0]); + printf(" --require-descriptor-heap Require a device with VK_EXT_descriptor_heap (exit on failure).\n"); + printf(" (default) Use ImGui_ImplVulkanH_SelectPhysicalDevice; enable heap if available.\n"); + return 0; + } + else + { + fprintf(stderr, "Unknown argument: %s (try --help)\n", argv[i]); + return 1; + } + } + glfwSetErrorCallback(glfw_error_callback); if (!glfwInit()) return 1; @@ -371,7 +658,7 @@ int main(int, char**) const char** glfw_extensions = glfwGetRequiredInstanceExtensions(&extensions_count); for (uint32_t i = 0; i < extensions_count; i++) extensions.push_back(glfw_extensions[i]); - SetupVulkan(extensions); + SetupVulkan(extensions, require_descriptor_heap); // Create Window Surface VkSurfaceKHR surface; @@ -418,6 +705,8 @@ int main(int, char**) init_info.PipelineInfoMain.Subpass = 0; init_info.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT; init_info.CheckVkResultFn = check_vk_result; + if (g_UsingDescriptorHeap) + init_info.DescriptorHeapInfo = &g_DescriptorHeapInfo; ImGui_ImplVulkan_Init(&init_info); // Load Fonts diff --git a/examples/example_win32_vulkan/main.cpp b/examples/example_win32_vulkan/main.cpp index e7c2662c3..cbdbdb830 100644 --- a/examples/example_win32_vulkan/main.cpp +++ b/examples/example_win32_vulkan/main.cpp @@ -47,38 +47,6 @@ static VkDescriptorPool g_DescriptorPool = VK_NULL_HANDLE; static ImGui_ImplVulkanH_Window g_MainWindowData; static uint32_t g_MinImageCount = 2; static bool g_SwapChainRebuild = false; -static bool g_UsingDescriptorHeap = false; - -struct HeapInfo -{ - VkBuffer Buffer; - VkDeviceMemory Memory; - void* Mapped; - VkBindHeapInfoEXT BindInfo; - uint64_t FreeList; - VkDeviceSize Stride; - - uint32_t Allocate() - { - IM_ASSERT(FreeList != 0); - for(uint32_t i = 0; i < 8 * sizeof(FreeList); i++) - { - if(FreeList & (1ull << i)) - { - FreeList ^= (1ull << i); - return i; - } - } - return 0; - } - void Free(uint32_t idx) - { - FreeList |= 1ull << idx; - } -}; -static HeapInfo g_ResourceHeap; -static HeapInfo g_SamplerHeap; -static ImGui_ImplVulkan_DescriptorHeapInfo g_DescriptorHeapInfo; static void check_vk_result(VkResult err) { @@ -106,52 +74,6 @@ static bool IsExtensionAvailable(const ImVector& properti return false; } -// Same as IM_MEMALIGN(). 'alignment' must be a power of two. -static inline VkDeviceSize AlignBufferSize(VkDeviceSize size, VkDeviceSize alignment) -{ - return (size + alignment - 1) & ~(alignment - 1); -} - -static void SetupHeap( - HeapInfo& heap, VkDeviceSize size, VkDeviceSize alignment, VkDeviceSize stride, uint32_t mem_index) -{ - heap.Stride = stride; - - VkDeviceSize buffer_size_aligned = AlignBufferSize(size, alignment); - VkBufferCreateInfo buffer_info = {}; - buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - buffer_info.size = buffer_size_aligned; - buffer_info.usage = VK_BUFFER_USAGE_DESCRIPTOR_HEAP_BIT_EXT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; - buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - VkResult err = vkCreateBuffer(g_Device, &buffer_info, g_Allocator, &heap.Buffer); - check_vk_result(err); - - VkMemoryRequirements mem_req; - vkGetBufferMemoryRequirements(g_Device, heap.Buffer, &mem_req); - - VkMemoryAllocateFlagsInfo alloc_flags = {}; - alloc_flags.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO; - alloc_flags.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT; - VkMemoryAllocateInfo alloc_info = {}; - alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - alloc_info.pNext = &alloc_flags; - alloc_info.allocationSize = buffer_size_aligned; - alloc_info.memoryTypeIndex = mem_index; - err = vkAllocateMemory(g_Device, &alloc_info, g_Allocator, &heap.Memory); - check_vk_result(err); - err = vkMapMemory(g_Device, heap.Memory, 0, buffer_size_aligned, 0, &heap.Mapped); - check_vk_result(err); - err = vkBindBufferMemory(g_Device, heap.Buffer, heap.Memory, 0); - check_vk_result(err); - - const VkBufferDeviceAddressInfo addr = { - VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO, nullptr, heap.Buffer - }; - heap.BindInfo.sType = VK_STRUCTURE_TYPE_BIND_HEAP_INFO_EXT; - heap.BindInfo.heapRange.address = vkGetBufferDeviceAddressKHR(g_Device, &addr); - heap.BindInfo.heapRange.size = size; -} - static void SetupVulkan(ImVector instance_extensions) { VkResult err; @@ -194,9 +116,6 @@ static void SetupVulkan(ImVector instance_extensions) // Create Vulkan Instance create_info.enabledExtensionCount = (uint32_t)instance_extensions.Size; create_info.ppEnabledExtensionNames = instance_extensions.Data; - VkApplicationInfo app_info{}; - app_info.apiVersion = VK_API_VERSION_1_1; - create_info.pApplicationInfo = &app_info; err = vkCreateInstance(&create_info, g_Allocator, &g_Instance); check_vk_result(err); #ifdef IMGUI_IMPL_VULKAN_USE_VOLK @@ -240,20 +159,6 @@ static void SetupVulkan(ImVector instance_extensions) if (IsExtensionAvailable(properties, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) device_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); #endif - if (IsExtensionAvailable(properties, VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME)) - { - device_extensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); - device_extensions.push_back(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); - device_extensions.push_back(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); - device_extensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); - device_extensions.push_back(VK_KHR_MAINTENANCE_2_EXTENSION_NAME); - device_extensions.push_back(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); - device_extensions.push_back(VK_KHR_MULTIVIEW_EXTENSION_NAME); - device_extensions.push_back(VK_KHR_SHADER_UNTYPED_POINTERS_EXTENSION_NAME); - device_extensions.push_back(VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME); - - g_UsingDescriptorHeap = true; - } const float queue_priority[] = { 1.0f }; VkDeviceQueueCreateInfo queue_info[1] = {}; @@ -262,15 +167,6 @@ static void SetupVulkan(ImVector instance_extensions) queue_info[0].queueCount = 1; queue_info[0].pQueuePriorities = queue_priority; VkDeviceCreateInfo create_info = {}; - VkPhysicalDeviceBufferDeviceAddressFeatures buffer_device_addr{ - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES, nullptr, VK_TRUE}; - VkPhysicalDeviceDescriptorHeapFeaturesEXT desc_heap_feat{ - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_FEATURES_EXT, &buffer_device_addr, VK_TRUE}; - VkPhysicalDeviceShaderUntypedPointersFeaturesKHR untyped_pointers{ - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_UNTYPED_POINTERS_FEATURES_KHR, &desc_heap_feat, VK_TRUE}; - if (g_UsingDescriptorHeap) - create_info.pNext = &untyped_pointers; - create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; create_info.queueCreateInfoCount = sizeof(queue_info) / sizeof(queue_info[0]); create_info.pQueueCreateInfos = queue_info; @@ -281,80 +177,8 @@ static void SetupVulkan(ImVector instance_extensions) vkGetDeviceQueue(g_Device, g_QueueFamily, 0, &g_Queue); } - // Create descriptor heaps - if (g_UsingDescriptorHeap) - { - VkPhysicalDeviceDescriptorHeapPropertiesEXT descriptor_heap_properties{ - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DESCRIPTOR_HEAP_PROPERTIES_EXT}; - VkPhysicalDeviceProperties2 properties2{ - VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2, &descriptor_heap_properties}; - vkGetPhysicalDeviceProperties2(g_PhysicalDevice, &properties2); - - size_t resource_size = - descriptor_heap_properties.imageDescriptorSize * IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE + - descriptor_heap_properties.minResourceHeapReservedRange; - size_t sampler_size = - descriptor_heap_properties.samplerDescriptorSize * IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE + - descriptor_heap_properties.minSamplerHeapReservedRange; - - VkPhysicalDeviceMemoryProperties prop; - vkGetPhysicalDeviceMemoryProperties(g_PhysicalDevice, &prop); - uint32_t mem_index = 0; - for (uint32_t i = 0; i < prop.memoryTypeCount; i++) - { - if ((prop.memoryTypes[i].propertyFlags & VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) == - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT) - { - mem_index = i; - break; - } - } - - SetupHeap(g_ResourceHeap, resource_size, descriptor_heap_properties.resourceHeapAlignment, descriptor_heap_properties.imageDescriptorSize, mem_index); - SetupHeap(g_SamplerHeap, sampler_size, descriptor_heap_properties.samplerHeapAlignment, descriptor_heap_properties.samplerDescriptorSize, mem_index); - - g_ResourceHeap.BindInfo.reservedRangeOffset = - descriptor_heap_properties.imageDescriptorSize * IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE; - g_ResourceHeap.BindInfo.reservedRangeSize = descriptor_heap_properties.minResourceHeapReservedRange; - g_SamplerHeap.BindInfo.reservedRangeOffset = - descriptor_heap_properties.samplerDescriptorSize * IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE; - g_SamplerHeap.BindInfo.reservedRangeSize = descriptor_heap_properties.minSamplerHeapReservedRange; - - g_ResourceHeap.FreeList = (1ull << IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE) - 1; - g_SamplerHeap.FreeList = (1ull << IMGUI_IMPL_VULKAN_MINIMUM_SAMPLER_POOL_SIZE) - 1; - - g_DescriptorHeapInfo.RegisterImage = [](void*, const VkImageViewCreateInfo* ci) -> uint32_t { - uint32_t idx = g_ResourceHeap.Allocate(); - - VkImageDescriptorInfoEXT image{}; - image.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT; - image.layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - image.pView = ci; - VkResourceDescriptorInfoEXT r{}; - r.sType = VK_STRUCTURE_TYPE_RESOURCE_DESCRIPTOR_INFO_EXT; - r.type = VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE; - r.data.pImage = ℑ - VkHostAddressRangeEXT addr{ - (char*)g_ResourceHeap.Mapped + idx * g_ResourceHeap.Stride, g_ResourceHeap.Stride}; - vkWriteResourceDescriptorsEXT(g_Device, 1, &r, &addr); - - return idx; - }; - g_DescriptorHeapInfo.UnRegisterImage = [](void*, uint32_t idx) { g_ResourceHeap.Free(idx); }; - g_DescriptorHeapInfo.RegisterSampler = [](void*, const VkSamplerCreateInfo* ci) -> uint32_t { - uint32_t idx = g_SamplerHeap.Allocate(); - - VkHostAddressRangeEXT addr{(char*)g_SamplerHeap.Mapped + idx * g_SamplerHeap.Stride, g_SamplerHeap.Stride}; - vkWriteSamplerDescriptorsEXT(g_Device, 1, ci, &addr); - - return idx; - }; - g_DescriptorHeapInfo.UnRegisterSampler = [](void*, uint32_t idx) { g_SamplerHeap.Free(idx); }; - } - // Create Descriptor Pool // If you wish to load e.g. additional textures you may need to alter pools sizes and maxSets. - if (!g_UsingDescriptorHeap) { VkDescriptorPoolSize pool_sizes[] = { @@ -409,17 +233,7 @@ static void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface static void CleanupVulkan() { - if (g_UsingDescriptorHeap) - { - vkDestroyBuffer(g_Device, g_ResourceHeap.Buffer, g_Allocator); - vkDestroyBuffer(g_Device, g_SamplerHeap.Buffer, g_Allocator); - vkFreeMemory(g_Device, g_ResourceHeap.Memory, g_Allocator); - vkFreeMemory(g_Device, g_SamplerHeap.Memory, g_Allocator); - } - else - { - vkDestroyDescriptorPool(g_Device, g_DescriptorPool, g_Allocator); - } + vkDestroyDescriptorPool(g_Device, g_DescriptorPool, g_Allocator); #ifdef APP_USE_VULKAN_DEBUG_REPORT // Remove the debug report callback @@ -478,12 +292,6 @@ static void FrameRender(ImGui_ImplVulkanH_Window* wd, ImDrawData* draw_data) vkCmdBeginRenderPass(fd->CommandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE); } - if (g_UsingDescriptorHeap) - { - vkCmdBindResourceHeapEXT(fd->CommandBuffer, &g_ResourceHeap.BindInfo); - vkCmdBindSamplerHeapEXT(fd->CommandBuffer, &g_SamplerHeap.BindInfo); - } - // Record dear imgui primitives into command buffer ImGui_ImplVulkan_RenderDrawData(draw_data, fd->CommandBuffer); @@ -603,8 +411,6 @@ int main(int, char**) init_info.PipelineInfoMain.Subpass = 0; init_info.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT; init_info.CheckVkResultFn = check_vk_result; - if (g_UsingDescriptorHeap) - init_info.DescriptorHeapInfo = &g_DescriptorHeapInfo; ImGui_ImplVulkan_Init(&init_info); // Load Fonts From d9af97f9a7f0eb8c7a1900e4950540a4bc5d476c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Aleksander=20G=C3=B3rski?= Date: Wed, 5 Aug 2026 22:55:19 +0900 Subject: [PATCH 03/11] Vulkan: Fix descriptor-heap TexID tagging and endian-safe PushData; disallow AddTexture when DescriptorHeapInfo is set --- backends/imgui_impl_vulkan.cpp | 19 +++++++++++++------ backends/imgui_impl_vulkan.h | 5 ++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index f1a516792..aad4185e1 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -823,11 +823,15 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (v->DescriptorHeapInfo) { + // Heap-mode TexIDs are tagged (index | 0x80000000). Use ImU64 for bit ops (ImTextureID may be redefined; default is ImU64). + // Push a uint32_t index (not &image_id) so size=4 is endian-safe. + const ImU64 tex_id_u64 = (ImU64)image_id; + IM_ASSERT((tex_id_u64 & 0x80000000ull) != 0 && "ImTextureID is not a descriptor-heap index (do not use ImGui_ImplVulkan_AddTexture when DescriptorHeapInfo is set; use RegisterImage and tag with 0x80000000)"); + uint32_t heap_index = (uint32_t)(tex_id_u64 & 0x7FFFFFFFull); VkPushDataInfoEXT push{}; push.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; - image_id &= 0x7FFFFFFF; - push.data.address = &image_id; - push.data.size = 4; + push.data.address = &heap_index; + push.data.size = sizeof(heap_index); push.offset = 16; ImGuiImplVulkanFuncs_vkCmdPushDataEXT(command_buffer, &push); } @@ -864,7 +868,7 @@ 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 || - backend_tex->DescriptorHeapIndex == (tex->TexID & 0x7FFFFFFF)); + backend_tex->DescriptorHeapIndex == (uint32_t)((ImU64)tex->TexID & 0x7FFFFFFFull)); ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; if (backend_tex->DescriptorSet != VK_NULL_HANDLE) @@ -945,8 +949,8 @@ void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex) { backend_tex->DescriptorHeapIndex = v->DescriptorHeapInfo->RegisterImage(v->DescriptorHeapInfo->UserContext, &info); - // Store identifiers - tex->SetTexID((ImTextureID)(backend_tex->DescriptorHeapIndex | 0x80000000)); + // Store identifiers (ImU64 cast so tagging works if ImTextureID is customized) + tex->SetTexID((ImTextureID)(ImU64)(backend_tex->DescriptorHeapIndex | 0x80000000u)); } else #endif @@ -1691,6 +1695,9 @@ 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_DESCRIPTOR_HEAP + IM_ASSERT(v->DescriptorHeapInfo == nullptr && "ImGui_ImplVulkan_AddTexture() is unavailable when using DescriptorHeapInfo; register via DescriptorHeapInfo::RegisterImage and set ImTextureID to (index | 0x80000000)"); +#endif VkDescriptorPool pool = bd->DescriptorPool ? bd->DescriptorPool : v->DescriptorPool; // Create Descriptor Set: diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 4094a057b..8d8f8e0c5 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -113,7 +113,7 @@ struct ImGui_ImplVulkan_DescriptorHeapInfo // - A VkDescriptorPool should be created with VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT, // and must contain a pool size large enough to hold a small number of VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER descriptors. // - As an convenience, by setting DescriptorPoolSize > 0 the backend will create one for you. - // - About dynamic rendering: +// - About dynamic rendering: // - When using dynamic rendering, set UseDynamicRendering=true + fill PipelineInfoMain.PipelineRenderingCreateInfo structure. // - About descriptor heap (requires VK_EXT_descriptor_heap in your Vulkan headers → IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP): // - When using descriptor heaps, set DescriptorHeapInfo and leave DescriptorPool / DescriptorPoolSize unset. @@ -155,6 +155,7 @@ struct ImGui_ImplVulkan_InitInfo #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP // (Optional) If set, use VK_EXT_descriptor_heap. + // ImTextureID must be (RegisterImage() index | 0x80000000). ImGui_ImplVulkan_AddTexture() is unavailable in this mode. const ImGui_ImplVulkan_DescriptorHeapInfo *DescriptorHeapInfo; #endif }; @@ -175,6 +176,8 @@ 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) +// - Not available when InitInfo::DescriptorHeapInfo is set (asserts). In heap mode register via DescriptorHeapInfo::RegisterImage +// and use ImTextureID = (heap_index | 0x80000000). IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkImageView image_view, VkImageLayout image_layout); IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set); #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP From 8fb2aea07b49d994a596adea28674d9ae14673d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Aleksander=20G=C3=B3rski?= Date: Wed, 5 Aug 2026 23:37:58 +0900 Subject: [PATCH 04/11] Vulkan: Drop public RemoveHeapTexture; heap mode uses DescriptorHeapInfo UnRegisterImage like DX12 SrvDescriptorFreeFn Fix: Rename target glsl_shader_heap file in validation command to match the existing one --- backends/imgui_impl_vulkan.cpp | 17 ++++------------- backends/imgui_impl_vulkan.h | 12 ++++++------ backends/vulkan/generate_spv.sh | 2 +- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index aad4185e1..21f190929 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -826,7 +826,7 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm // Heap-mode TexIDs are tagged (index | 0x80000000). Use ImU64 for bit ops (ImTextureID may be redefined; default is ImU64). // Push a uint32_t index (not &image_id) so size=4 is endian-safe. const ImU64 tex_id_u64 = (ImU64)image_id; - IM_ASSERT((tex_id_u64 & 0x80000000ull) != 0 && "ImTextureID is not a descriptor-heap index (do not use ImGui_ImplVulkan_AddTexture when DescriptorHeapInfo is set; use RegisterImage and tag with 0x80000000)"); + IM_ASSERT((tex_id_u64 & 0x80000000ull) != 0 && "ImTextureID is not a descriptor-heap index (do not use ImGui_ImplVulkan_AddTexture when DescriptorHeapInfo is set; use DescriptorHeapInfo::RegisterImage and tag with 0x80000000)"); uint32_t heap_index = (uint32_t)(tex_id_u64 & 0x7FFFFFFFull); VkPushDataInfoEXT push{}; push.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; @@ -874,8 +874,8 @@ static void ImGui_ImplVulkan_DestroyTexture(ImTextureData* tex) if (backend_tex->DescriptorSet != VK_NULL_HANDLE) ImGui_ImplVulkan_RemoveTexture(backend_tex->DescriptorSet); #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP - else - ImGui_ImplVulkan_RemoveHeapTexture(backend_tex->DescriptorHeapIndex); + else if (v->DescriptorHeapInfo) + v->DescriptorHeapInfo->UnRegisterImage(v->DescriptorHeapInfo->UserContext, backend_tex->DescriptorHeapIndex); #endif if (backend_tex->ImageView != VK_NULL_HANDLE) vkDestroyImageView(v->Device, backend_tex->ImageView, v->Allocator); @@ -1696,7 +1696,7 @@ 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_DESCRIPTOR_HEAP - IM_ASSERT(v->DescriptorHeapInfo == nullptr && "ImGui_ImplVulkan_AddTexture() is unavailable when using DescriptorHeapInfo; register via DescriptorHeapInfo::RegisterImage and set ImTextureID to (index | 0x80000000)"); + IM_ASSERT(v->DescriptorHeapInfo == nullptr && "ImGui_ImplVulkan_AddTexture() is unavailable when using DescriptorHeapInfo; use DescriptorHeapInfo::RegisterImage and set ImTextureID to (index | 0x80000000)"); #endif VkDescriptorPool pool = bd->DescriptorPool ? bd->DescriptorPool : v->DescriptorPool; @@ -1745,15 +1745,6 @@ void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set) vkFreeDescriptorSets(v->Device, pool, 1, &descriptor_set); } -#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP -void ImGui_ImplVulkan_RemoveHeapTexture(uint32_t id) -{ - ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); - ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; - v->DescriptorHeapInfo->UnRegisterImage(v->DescriptorHeapInfo->UserContext, id); -} -#endif - void ImGui_ImplVulkan_DestroyFrameRenderBuffers(VkDevice device, ImGui_ImplVulkan_FrameRenderBuffers* buffers, const VkAllocationCallbacks* allocator) { if (buffers->VertexBuffer) diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 8d8f8e0c5..331b3fd45 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -117,6 +117,9 @@ struct ImGui_ImplVulkan_DescriptorHeapInfo // - When using dynamic rendering, set UseDynamicRendering=true + fill PipelineInfoMain.PipelineRenderingCreateInfo structure. // - About descriptor heap (requires VK_EXT_descriptor_heap in your Vulkan headers → IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP): // - When using descriptor heaps, set DescriptorHeapInfo and leave DescriptorPool / DescriptorPoolSize unset. +// - Like DX12's SrvDescriptorAllocFn/FreeFn: the application owns heap allocation via Register*/UnRegister* callbacks. +// - ImGui_ImplVulkan_AddTexture()/RemoveTexture() are unavailable in this mode (pool path only). +// - User textures: call RegisterImage yourself and use ImTextureID = (heap_index | 0x80000000); free with UnRegisterImage. struct ImGui_ImplVulkan_InitInfo { uint32_t ApiVersion; // Fill with API version of Instance, e.g. VK_API_VERSION_1_3 or your value of VkApplicationInfo::apiVersion. May be lower than header version (VK_HEADER_VERSION_COMPLETE) @@ -154,7 +157,7 @@ struct ImGui_ImplVulkan_InitInfo VkShaderModuleCreateInfo CustomShaderFragCreateInfo; #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP - // (Optional) If set, use VK_EXT_descriptor_heap. + // (Optional) If set, use VK_EXT_descriptor_heap (app-owned Register*/UnRegister* callbacks; see comment above). // ImTextureID must be (RegisterImage() index | 0x80000000). ImGui_ImplVulkan_AddTexture() is unavailable in this mode. const ImGui_ImplVulkan_DescriptorHeapInfo *DescriptorHeapInfo; #endif @@ -176,13 +179,10 @@ 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) -// - Not available when InitInfo::DescriptorHeapInfo is set (asserts). In heap mode register via DescriptorHeapInfo::RegisterImage -// and use ImTextureID = (heap_index | 0x80000000). +// - Pool path only. Not available when InitInfo::DescriptorHeapInfo is set (asserts). +// - Heap mode: use DescriptorHeapInfo::RegisterImage / UnRegisterImage and ImTextureID = (heap_index | 0x80000000). IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkImageView image_view, VkImageLayout image_layout); IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set); -#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP -IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveHeapTexture(uint32_t id); -#endif #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkSampler sampler, VkImageView image_view, VkImageLayout image_layout); // Ignore VkSampler diff --git a/backends/vulkan/generate_spv.sh b/backends/vulkan/generate_spv.sh index d237e7473..492e0da8b 100755 --- a/backends/vulkan/generate_spv.sh +++ b/backends/vulkan/generate_spv.sh @@ -4,4 +4,4 @@ ## -o: output file glslangValidator -V -x -o glsl_shader.frag.u32 glsl_shader.frag glslangValidator -V -x -o glsl_shader.vert.u32 glsl_shader.vert -glslangValidator -V --target-env spirv1.2 -x -o glsl_shader_heap.vert.u32 glsl_shader_heap.vert +glslangValidator -V --target-env spirv1.2 -x -o glsl_shader_heap.frag.u32 glsl_shader_heap.frag From c33ea3ccb68e4313e36765fce67860dfd029681b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Aleksander=20G=C3=B3rski?= Date: Thu, 6 Aug 2026 02:15:30 +0900 Subject: [PATCH 05/11] Vulkan: Use GPU device address for ImTextureID in descriptor heap mode --- backends/imgui_impl_vulkan.cpp | 41 ++++++++++++++++++--------- backends/imgui_impl_vulkan.h | 30 +++++++++++++------- examples/example_glfw_vulkan/main.cpp | 26 +++++++++++++---- 3 files changed, 69 insertions(+), 28 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 21f190929..b13b13dc3 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -244,6 +244,17 @@ static PFN_vkCmdEndRenderingKHR ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR; #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP // VK_EXT_descriptor_heap (not exported by most Vulkan loaders; resolve like dynamic rendering) static PFN_vkCmdPushDataEXT ImGuiImplVulkanFuncs_vkCmdPushDataEXT = nullptr; + +// Convert GPU descriptor handle (device address) back to a heap index for PushData. +static uint32_t ImGui_ImplVulkan_DescriptorHeapIndexFromTexID(const ImGui_ImplVulkan_DescriptorHeapInfo* info, ImTextureID tex_id) +{ + IM_ASSERT(tex_id != ImTextureID_Invalid); + IM_ASSERT(info->ResourceHeapAddress != 0 && info->ImageDescriptorSize != 0); + const VkDeviceAddress addr = (VkDeviceAddress)(ImU64)tex_id; + IM_ASSERT(addr >= info->ResourceHeapAddress); + IM_ASSERT(((addr - info->ResourceHeapAddress) % info->ImageDescriptorSize) == 0); + return (uint32_t)((addr - info->ResourceHeapAddress) / info->ImageDescriptorSize); +} #endif // Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplVulkan_RenderDrawData() @@ -273,7 +284,7 @@ struct ImGui_ImplVulkan_Texture VkImage Image; VkImageView ImageView; VkDescriptorSet DescriptorSet; - uint32_t DescriptorHeapIndex; + uint64_t DescriptorHeapGpuHandle; // Device address into resource heap (== ImTextureID in heap mode) ImGui_ImplVulkan_Texture() { memset((void*)this, 0, sizeof(*this)); } }; @@ -823,11 +834,8 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (v->DescriptorHeapInfo) { - // Heap-mode TexIDs are tagged (index | 0x80000000). Use ImU64 for bit ops (ImTextureID may be redefined; default is ImU64). - // Push a uint32_t index (not &image_id) so size=4 is endian-safe. - const ImU64 tex_id_u64 = (ImU64)image_id; - IM_ASSERT((tex_id_u64 & 0x80000000ull) != 0 && "ImTextureID is not a descriptor-heap index (do not use ImGui_ImplVulkan_AddTexture when DescriptorHeapInfo is set; use DescriptorHeapInfo::RegisterImage and tag with 0x80000000)"); - uint32_t heap_index = (uint32_t)(tex_id_u64 & 0x7FFFFFFFull); + // ImTextureID is a GPU descriptor device address. + uint32_t heap_index = ImGui_ImplVulkan_DescriptorHeapIndexFromTexID(v->DescriptorHeapInfo, image_id); VkPushDataInfoEXT push{}; push.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; push.data.address = &heap_index; @@ -867,15 +875,18 @@ 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 || - backend_tex->DescriptorHeapIndex == (uint32_t)((ImU64)tex->TexID & 0x7FFFFFFFull)); + IM_ASSERT(backend_tex->DescriptorSet == (VkDescriptorSet)tex->TexID +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP + || backend_tex->DescriptorHeapGpuHandle == (uint64_t)(ImU64)tex->TexID +#endif + ); ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; if (backend_tex->DescriptorSet != VK_NULL_HANDLE) ImGui_ImplVulkan_RemoveTexture(backend_tex->DescriptorSet); #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP else if (v->DescriptorHeapInfo) - v->DescriptorHeapInfo->UnRegisterImage(v->DescriptorHeapInfo->UserContext, backend_tex->DescriptorHeapIndex); + v->DescriptorHeapInfo->UnRegisterImage(v->DescriptorHeapInfo->UserContext, backend_tex->DescriptorHeapGpuHandle); #endif if (backend_tex->ImageView != VK_NULL_HANDLE) vkDestroyImageView(v->Device, backend_tex->ImageView, v->Allocator); @@ -947,10 +958,10 @@ void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex) #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (v->DescriptorHeapInfo) { - backend_tex->DescriptorHeapIndex = + backend_tex->DescriptorHeapGpuHandle = v->DescriptorHeapInfo->RegisterImage(v->DescriptorHeapInfo->UserContext, &info); - // Store identifiers (ImU64 cast so tagging works if ImTextureID is customized) - tex->SetTexID((ImTextureID)(ImU64)(backend_tex->DescriptorHeapIndex | 0x80000000u)); + IM_ASSERT(backend_tex->DescriptorHeapGpuHandle != 0 && "RegisterImage must return a non-zero device address"); + tex->SetTexID((ImTextureID)backend_tex->DescriptorHeapGpuHandle); } else #endif @@ -1602,6 +1613,10 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) ImGuiImplVulkanFuncs_vkCmdPushDataEXT = reinterpret_cast(vkGetDeviceProcAddr(info->Device, "vkCmdPushDataEXT")); #endif IM_ASSERT(ImGuiImplVulkanFuncs_vkCmdPushDataEXT != nullptr && "vkCmdPushDataEXT not available (enable VK_EXT_descriptor_heap)"); + IM_ASSERT(sizeof(ImTextureID) >= sizeof(VkDeviceAddress) && "Descriptor heap mode needs 64-bit ImTextureID"); + IM_ASSERT(info->DescriptorHeapInfo->ResourceHeapAddress != 0 && info->DescriptorHeapInfo->ImageDescriptorSize != 0); + IM_ASSERT(info->DescriptorHeapInfo->RegisterImage != nullptr && info->DescriptorHeapInfo->UnRegisterImage != nullptr); + IM_ASSERT(info->DescriptorHeapInfo->RegisterSampler != nullptr && info->DescriptorHeapInfo->UnRegisterSampler != nullptr); } #endif @@ -1696,7 +1711,7 @@ 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_DESCRIPTOR_HEAP - IM_ASSERT(v->DescriptorHeapInfo == nullptr && "ImGui_ImplVulkan_AddTexture() is unavailable when using DescriptorHeapInfo; use DescriptorHeapInfo::RegisterImage and set ImTextureID to (index | 0x80000000)"); + IM_ASSERT(v->DescriptorHeapInfo == nullptr && "ImGui_ImplVulkan_AddTexture() is unavailable when using DescriptorHeapInfo; use DescriptorHeapInfo::RegisterImage"); #endif VkDescriptorPool pool = bd->DescriptorPool ? bd->DescriptorPool : v->DescriptorPool; diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 331b3fd45..91ca2d7c1 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -97,13 +97,22 @@ struct ImGui_ImplVulkan_PipelineInfo }; #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP +// App owns heap slots. ImTextureID is the descriptor's device address in the resource heap (like D3D12_GPU_DESCRIPTOR_HANDLE.ptr). +// Backend converts to a heap index for vkCmdPushDataEXT via (tex_id - ResourceHeapAddress) / ImageDescriptorSize. +// Requires a 64-bit ImTextureID (Dear ImGui default since 1.91.4). Do not redefine ImTextureID to a 32-bit type. struct ImGui_ImplVulkan_DescriptorHeapInfo { - uint32_t (*RegisterSampler)(void *, const VkSamplerCreateInfo *); - void (*UnRegisterSampler)(void *, uint32_t); - uint32_t (*RegisterImage)(void *, const VkImageViewCreateInfo *); - void (*UnRegisterImage)(void *, uint32_t); - void *UserContext; + VkDeviceAddress ResourceHeapAddress; // Bound resource heap base (vkGetBufferDeviceAddress of heap buffer) + VkDeviceSize ImageDescriptorSize; // imageDescriptorSize from VkPhysicalDeviceDescriptorHeapPropertiesEXT + + // RegisterImage returns GPU descriptor handle (device address / VkDeviceAddress). UnRegisterImage takes that same handle. + // Returned value is stored as ImTextureID; needs sizeof(ImTextureID) >= 8. + uint64_t (*RegisterImage)(void*, const VkImageViewCreateInfo*); + void (*UnRegisterImage)(void*, uint64_t); + // Samplers are not ImTextureIDs; indices are fine (including 0). + uint32_t (*RegisterSampler)(void*, const VkSamplerCreateInfo*); + void (*UnRegisterSampler)(void*, uint32_t); + void* UserContext; }; #endif @@ -117,9 +126,10 @@ struct ImGui_ImplVulkan_DescriptorHeapInfo // - When using dynamic rendering, set UseDynamicRendering=true + fill PipelineInfoMain.PipelineRenderingCreateInfo structure. // - About descriptor heap (requires VK_EXT_descriptor_heap in your Vulkan headers → IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP): // - When using descriptor heaps, set DescriptorHeapInfo and leave DescriptorPool / DescriptorPoolSize unset. -// - Like DX12's SrvDescriptorAllocFn/FreeFn: the application owns heap allocation via Register*/UnRegister* callbacks. -// - ImGui_ImplVulkan_AddTexture()/RemoveTexture() are unavailable in this mode (pool path only). -// - User textures: call RegisterImage yourself and use ImTextureID = (heap_index | 0x80000000); free with UnRegisterImage. +// - The application owns heap slots via Register*/UnRegister* callbacks. +// - ImTextureID is the GPU descriptor device address returned by RegisterImage. +// Requires a 64-bit ImTextureID. +// - ImGui_ImplVulkan_AddTexture()/RemoveTexture() are pool-path only. struct ImGui_ImplVulkan_InitInfo { uint32_t ApiVersion; // Fill with API version of Instance, e.g. VK_API_VERSION_1_3 or your value of VkApplicationInfo::apiVersion. May be lower than header version (VK_HEADER_VERSION_COMPLETE) @@ -158,7 +168,7 @@ struct ImGui_ImplVulkan_InitInfo #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP // (Optional) If set, use VK_EXT_descriptor_heap (app-owned Register*/UnRegister* callbacks; see comment above). - // ImTextureID must be (RegisterImage() index | 0x80000000). ImGui_ImplVulkan_AddTexture() is unavailable in this mode. + // ImTextureID = RegisterImage(...) device address. ImGui_ImplVulkan_AddTexture() is unavailable in this mode. const ImGui_ImplVulkan_DescriptorHeapInfo *DescriptorHeapInfo; #endif }; @@ -180,7 +190,7 @@ IMGUI_IMPL_API void ImGui_ImplVulkan_UpdateTexture(ImTextureData* te // Register a texture (VkDescriptorSet for a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE == ImTextureID) // - Pool path only. Not available when InitInfo::DescriptorHeapInfo is set (asserts). -// - Heap mode: use DescriptorHeapInfo::RegisterImage / UnRegisterImage and ImTextureID = (heap_index | 0x80000000). +// - Heap mode: ImTextureID = DescriptorHeapInfo::RegisterImage(...) device address; free with UnRegisterImage. IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkImageView image_view, VkImageLayout image_layout); IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set); diff --git a/examples/example_glfw_vulkan/main.cpp b/examples/example_glfw_vulkan/main.cpp index c1128a17b..e4a6c3ca3 100644 --- a/examples/example_glfw_vulkan/main.cpp +++ b/examples/example_glfw_vulkan/main.cpp @@ -146,8 +146,8 @@ struct ExampleDescriptorHeapAllocator BindInfo.reservedRangeSize = reserved_range; FreeIndices.reserve(capacity); - for (int n = capacity; n > 0; n--) - FreeIndices.push_back(n - 1); + for (int n = capacity - 1; n >= 0; n--) + FreeIndices.push_back(n); } void Destroy(VkDevice device, const VkAllocationCallbacks* allocator) { @@ -170,6 +170,16 @@ struct ExampleDescriptorHeapAllocator { FreeIndices.push_back(idx); } + VkDeviceAddress GpuHandleFromIndex(int idx) const + { + return BindInfo.heapRange.address + (VkDeviceAddress)idx * (VkDeviceAddress)Stride; + } + int IndexFromGpuHandle(VkDeviceAddress gpu_handle) const + { + IM_ASSERT(gpu_handle >= BindInfo.heapRange.address); + IM_ASSERT(((gpu_handle - BindInfo.heapRange.address) % Stride) == 0); + return (int)((gpu_handle - BindInfo.heapRange.address) / Stride); + } VkHostAddressRangeEXT DescriptorAddress(int idx) const { return VkHostAddressRangeEXT{ (char*)Mapped + (VkDeviceSize)idx * Stride, Stride }; @@ -417,8 +427,12 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ descriptor_heap_properties.minSamplerHeapReservedRange, APP_SAMPLER_HEAP_SIZE, g_Allocator); // Allocating descriptors is up to the application, so we provide callbacks (same idea as DX12 SrvDescriptorAllocFn/FreeFn). + // ImTextureID = GPU descriptor device address (like D3D12_GPU_DESCRIPTOR_HANDLE.ptr); slot 0 is usable. + g_DescriptorHeapInfo = {}; + g_DescriptorHeapInfo.ResourceHeapAddress = g_ResourceHeapAlloc.BindInfo.heapRange.address; + g_DescriptorHeapInfo.ImageDescriptorSize = g_ResourceHeapAlloc.Stride; g_DescriptorHeapInfo.UserContext = nullptr; - g_DescriptorHeapInfo.RegisterImage = [](void*, const VkImageViewCreateInfo* ci) -> uint32_t { + g_DescriptorHeapInfo.RegisterImage = [](void*, const VkImageViewCreateInfo* ci) -> uint64_t { int idx = g_ResourceHeapAlloc.Alloc(); VkImageDescriptorInfoEXT image = {}; image.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT; @@ -430,9 +444,11 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ resource.data.pImage = ℑ VkHostAddressRangeEXT addr = g_ResourceHeapAlloc.DescriptorAddress(idx); check_vk_result(g_DescHeapFns.vkWriteResourceDescriptorsEXT(g_Device, 1, &resource, &addr)); - return (uint32_t)idx; + return (uint64_t)g_ResourceHeapAlloc.GpuHandleFromIndex(idx); + }; + g_DescriptorHeapInfo.UnRegisterImage = [](void*, uint64_t gpu_handle) { + g_ResourceHeapAlloc.Free(g_ResourceHeapAlloc.IndexFromGpuHandle((VkDeviceAddress)gpu_handle)); }; - g_DescriptorHeapInfo.UnRegisterImage = [](void*, uint32_t idx) { g_ResourceHeapAlloc.Free((int)idx); }; g_DescriptorHeapInfo.RegisterSampler = [](void*, const VkSamplerCreateInfo* ci) -> uint32_t { int idx = g_SamplerHeapAlloc.Alloc(); VkHostAddressRangeEXT addr = g_SamplerHeapAlloc.DescriptorAddress(idx); From 5c666e59d0d367998069154db5770b690f7d7b67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Aleksander=20G=C3=B3rski?= Date: Thu, 6 Aug 2026 02:57:36 +0900 Subject: [PATCH 06/11] Vulkan: Require and document descriptor-heap device extensions. --- backends/imgui_impl_vulkan.h | 7 +++++ examples/example_glfw_vulkan/main.cpp | 40 +++++++++++++++++++-------- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 91ca2d7c1..adc832db7 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -130,6 +130,12 @@ struct ImGui_ImplVulkan_DescriptorHeapInfo // - ImTextureID is the GPU descriptor device address returned by RegisterImage. // Requires a 64-bit ImTextureID. // - ImGui_ImplVulkan_AddTexture()/RemoveTexture() are pool-path only. +// - Device extensions / features the application must enable: +// - VK_EXT_descriptor_heap (feature: descriptorHeap). +// - VK_KHR_maintenance5 (formal dependency of descriptor_heap; used for VkPipelineCreateFlags2 / DESCRIPTOR_HEAP pipeline flag). +// - VK_KHR_buffer_device_address, or Vulkan 1.2+ where BDA is core (formal dependency; enable the bufferDeviceAddress feature either way). +// - VK_KHR_shader_untyped_pointers (feature: shaderUntypedPointers): required by the default heap shaders (layout(descriptor_heap)). +// Not a formal dependency of VK_EXT_descriptor_heap; mapping-based custom shaders could omit it. struct ImGui_ImplVulkan_InitInfo { uint32_t ApiVersion; // Fill with API version of Instance, e.g. VK_API_VERSION_1_3 or your value of VkApplicationInfo::apiVersion. May be lower than header version (VK_HEADER_VERSION_COMPLETE) @@ -169,6 +175,7 @@ struct ImGui_ImplVulkan_InitInfo #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP // (Optional) If set, use VK_EXT_descriptor_heap (app-owned Register*/UnRegister* callbacks; see comment above). // ImTextureID = RegisterImage(...) device address. ImGui_ImplVulkan_AddTexture() is unavailable in this mode. + // App must enable the device extensions/features listed under "About descriptor heap". const ImGui_ImplVulkan_DescriptorHeapInfo *DescriptorHeapInfo; #endif }; diff --git a/examples/example_glfw_vulkan/main.cpp b/examples/example_glfw_vulkan/main.cpp index e4a6c3ca3..8f0c94744 100644 --- a/examples/example_glfw_vulkan/main.cpp +++ b/examples/example_glfw_vulkan/main.cpp @@ -244,6 +244,18 @@ static bool PhysicalDeviceHasExtension(VkPhysicalDevice physical_device, const c return IsExtensionAvailable(properties, extension); } +// Extensions required by this example's VK_EXT_descriptor_heap path (Vulkan 1.1 instance → BDA is an extension). +// Formal deps of VK_EXT_descriptor_heap: maintenance5 + (buffer_device_address | Vulkan 1.2). +// shader_untyped_pointers is required by ImGui's default layout(descriptor_heap) shaders, not by the Vulkan extension itself. +// See "About descriptor heap" in imgui_impl_vulkan.h. +static bool PhysicalDeviceSupportsImGuiDescriptorHeap(VkPhysicalDevice physical_device) +{ + return PhysicalDeviceHasExtension(physical_device, VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME) + && PhysicalDeviceHasExtension(physical_device, VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) + && PhysicalDeviceHasExtension(physical_device, VK_KHR_SHADER_UNTYPED_POINTERS_EXTENSION_NAME) + && PhysicalDeviceHasExtension(physical_device, VK_KHR_MAINTENANCE_5_EXTENSION_NAME); +} + static void SetupVulkan(ImVector instance_extensions, bool require_descriptor_heap) { VkResult err; @@ -321,17 +333,21 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ VkPhysicalDevice device_with_heap = VK_NULL_HANDLE; for (uint32_t i = 0; i < device_count; i++) { - VkPhysicalDeviceProperties props; - vkGetPhysicalDeviceProperties(physical_devices[i], &props); - const bool has_heap = PhysicalDeviceHasExtension(physical_devices[i], VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME); - - if (has_heap && device_with_heap == VK_NULL_HANDLE) + if (PhysicalDeviceSupportsImGuiDescriptorHeap(physical_devices[i])) + { device_with_heap = physical_devices[i]; + break; + } } if (device_with_heap == VK_NULL_HANDLE) { - fprintf(stderr, "Error: --require-descriptor-heap requested but no device with VK_EXT_descriptor_heap was found.\n"); + fprintf(stderr, "Error: --require-descriptor-heap requested but no device with VK_EXT_descriptor_heap and its required dependencies was found.\n"); + fprintf(stderr, " Required: %s, %s, %s, %s\n", + VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME, + VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME, + VK_KHR_SHADER_UNTYPED_POINTERS_EXTENSION_NAME, + VK_KHR_MAINTENANCE_5_EXTENSION_NAME); exit(1); } g_PhysicalDevice = device_with_heap; @@ -364,19 +380,19 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ if (IsExtensionAvailable(properties, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) device_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); #endif - if (IsExtensionAvailable(properties, VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME)) + if (PhysicalDeviceSupportsImGuiDescriptorHeap(g_PhysicalDevice)) { device_extensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); - device_extensions.push_back(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME); - device_extensions.push_back(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME); - device_extensions.push_back(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME); - device_extensions.push_back(VK_KHR_MAINTENANCE_2_EXTENSION_NAME); device_extensions.push_back(VK_KHR_MAINTENANCE_5_EXTENSION_NAME); - device_extensions.push_back(VK_KHR_MULTIVIEW_EXTENSION_NAME); device_extensions.push_back(VK_KHR_SHADER_UNTYPED_POINTERS_EXTENSION_NAME); device_extensions.push_back(VK_EXT_DESCRIPTOR_HEAP_EXTENSION_NAME); g_UsingDescriptorHeap = true; } + else if (require_descriptor_heap) + { + fprintf(stderr, "Error: --require-descriptor-heap but selected device is missing a required dependency.\n"); + exit(1); + } const float queue_priority[] = { 1.0f }; VkDeviceQueueCreateInfo queue_info[1] = {}; From 313f7f21fe2f10e416d11f7b4be8779e9de9d899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Aleksander=20G=C3=B3rski?= Date: Thu, 6 Aug 2026 05:35:03 +0900 Subject: [PATCH 07/11] Vulkan: Update feature info by adding note about heap mode --- backends/imgui_impl_vulkan.cpp | 2 +- backends/imgui_impl_vulkan.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index b13b13dc3..604ca40e7 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -2,7 +2,7 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [!] Renderer: User texture binding. Use a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. +// [!] Renderer: User texture binding. Pool mode: use a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE 'VkDescriptorSet' as texture identifier (ImGui_ImplVulkan_AddTexture()). Descriptor-heap mode (VK_EXT_descriptor_heap): use a GPU descriptor device address (like DX12's D3D12_GPU_DESCRIPTOR_HANDLE). Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index adc832db7..2d2605527 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -2,7 +2,7 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [!] Renderer: User texture binding. Use a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. +// [!] Renderer: User texture binding. Pool mode: use a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE 'VkDescriptorSet' as texture identifier (ImGui_ImplVulkan_AddTexture()). Descriptor-heap mode (VK_EXT_descriptor_heap): use a GPU descriptor device address (like DX12's D3D12_GPU_DESCRIPTOR_HANDLE). Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. From 663d1344ce063afb4b9ffdcb98701afe4e0c97db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Aleksander=20G=C3=B3rski?= Date: Thu, 6 Aug 2026 05:38:13 +0900 Subject: [PATCH 08/11] Vulkan: Add assertion preventing combining ImGui_ImplVulkan_RemoveTexture and descriptor heaps --- backends/imgui_impl_vulkan.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 604ca40e7..64ae8a7bd 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -1756,6 +1756,9 @@ 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_DESCRIPTOR_HEAP + IM_ASSERT(v->DescriptorHeapInfo == nullptr && "ImGui_ImplVulkan_RemoveTexture() is unavailable when using DescriptorHeapInfo; use DescriptorHeapInfo::UnRegisterImage"); +#endif VkDescriptorPool pool = bd->DescriptorPool ? bd->DescriptorPool : v->DescriptorPool; vkFreeDescriptorSets(v->Device, pool, 1, &descriptor_set); } From e9c79e349a9aadb474939adbd7bcd834d1fd5b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Aleksander=20G=C3=B3rski?= Date: Thu, 6 Aug 2026 05:46:30 +0900 Subject: [PATCH 09/11] Examples: Gate glfw+vulkan descriptor-heap code on IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP --- examples/example_glfw_vulkan/main.cpp | 36 +++++++++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/examples/example_glfw_vulkan/main.cpp b/examples/example_glfw_vulkan/main.cpp index 8f0c94744..7be54541f 100644 --- a/examples/example_glfw_vulkan/main.cpp +++ b/examples/example_glfw_vulkan/main.cpp @@ -41,12 +41,13 @@ static VkDebugReportCallbackEXT g_DebugReport = VK_NULL_HANDLE; #endif +static void check_vk_result(VkResult err); + +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP // Config for example app static const int APP_RESOURCE_HEAP_SIZE = 64; static const int APP_SAMPLER_HEAP_SIZE = 64; -static void check_vk_result(VkResult err); - // Explicitly loaded VK_EXT_descriptor_heap entry points. // Stored as members so names can match the Vulkan entry points without colliding with prototypes. struct DescriptorHeapFunctions @@ -185,6 +186,7 @@ struct ExampleDescriptorHeapAllocator return VkHostAddressRangeEXT{ (char*)Mapped + (VkDeviceSize)idx * Stride, Stride }; } }; +#endif // IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP // Data static VkAllocationCallbacks* g_Allocator = nullptr; @@ -199,10 +201,12 @@ static VkDescriptorPool g_DescriptorPool = VK_NULL_HANDLE; static ImGui_ImplVulkanH_Window g_MainWindowData; static uint32_t g_MinImageCount = 2; static bool g_SwapChainRebuild = false; +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP static bool g_UsingDescriptorHeap = false; static ExampleDescriptorHeapAllocator g_ResourceHeapAlloc; static ExampleDescriptorHeapAllocator g_SamplerHeapAlloc; static ImGui_ImplVulkan_DescriptorHeapInfo g_DescriptorHeapInfo; +#endif static void glfw_error_callback(int error, const char* description) { @@ -234,6 +238,7 @@ static bool IsExtensionAvailable(const ImVector& properti return false; } +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP static bool PhysicalDeviceHasExtension(VkPhysicalDevice physical_device, const char* extension) { uint32_t properties_count = 0; @@ -255,6 +260,7 @@ static bool PhysicalDeviceSupportsImGuiDescriptorHeap(VkPhysicalDevice physical_ && PhysicalDeviceHasExtension(physical_device, VK_KHR_SHADER_UNTYPED_POINTERS_EXTENSION_NAME) && PhysicalDeviceHasExtension(physical_device, VK_KHR_MAINTENANCE_5_EXTENSION_NAME); } +#endif // IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP static void SetupVulkan(ImVector instance_extensions, bool require_descriptor_heap) { @@ -322,6 +328,7 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ #endif } +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (require_descriptor_heap) { uint32_t device_count = 0; @@ -353,7 +360,9 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ g_PhysicalDevice = device_with_heap; } else +#endif { + (void)require_descriptor_heap; g_PhysicalDevice = ImGui_ImplVulkanH_SelectPhysicalDevice(g_Instance); } IM_ASSERT(g_PhysicalDevice != VK_NULL_HANDLE); @@ -380,6 +389,7 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ if (IsExtensionAvailable(properties, VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME)) device_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME); #endif +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (PhysicalDeviceSupportsImGuiDescriptorHeap(g_PhysicalDevice)) { device_extensions.push_back(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME); @@ -393,6 +403,7 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ fprintf(stderr, "Error: --require-descriptor-heap but selected device is missing a required dependency.\n"); exit(1); } +#endif const float queue_priority[] = { 1.0f }; VkDeviceQueueCreateInfo queue_info[1] = {}; @@ -407,6 +418,7 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ create_info.enabledExtensionCount = (uint32_t)device_extensions.Size; create_info.ppEnabledExtensionNames = device_extensions.Data; +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP VkPhysicalDeviceBufferDeviceAddressFeatures buffer_device_addr = {}; buffer_device_addr.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_BUFFER_DEVICE_ADDRESS_FEATURES; buffer_device_addr.bufferDeviceAddress = VK_TRUE; @@ -420,12 +432,14 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ untyped_pointers.shaderUntypedPointers = VK_TRUE; if (g_UsingDescriptorHeap) create_info.pNext = &untyped_pointers; +#endif err = vkCreateDevice(g_PhysicalDevice, &create_info, g_Allocator, &g_Device); check_vk_result(err); vkGetDeviceQueue(g_Device, g_QueueFamily, 0, &g_Queue); } +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (g_UsingDescriptorHeap) { g_DescHeapFns.Load(g_Device); @@ -474,6 +488,7 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ g_DescriptorHeapInfo.UnRegisterSampler = [](void*, uint32_t idx) { g_SamplerHeapAlloc.Free((int)idx); }; } else +#endif { // Create Descriptor Pool // If you wish to load e.g. additional textures you may need to alter pools sizes and maxSets. @@ -530,12 +545,14 @@ static void SetupVulkanWindow(ImGui_ImplVulkanH_Window* wd, VkSurfaceKHR surface static void CleanupVulkan() { +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (g_UsingDescriptorHeap) { g_ResourceHeapAlloc.Destroy(g_Device, g_Allocator); g_SamplerHeapAlloc.Destroy(g_Device, g_Allocator); } else +#endif { vkDestroyDescriptorPool(g_Device, g_DescriptorPool, g_Allocator); } @@ -597,11 +614,13 @@ static void FrameRender(ImGui_ImplVulkanH_Window* wd, ImDrawData* draw_data) vkCmdBeginRenderPass(fd->CommandBuffer, &info, VK_SUBPASS_CONTENTS_INLINE); } +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (g_UsingDescriptorHeap) { g_DescHeapFns.vkCmdBindResourceHeapEXT(fd->CommandBuffer, &g_ResourceHeapAlloc.BindInfo); g_DescHeapFns.vkCmdBindSamplerHeapEXT(fd->CommandBuffer, &g_SamplerHeapAlloc.BindInfo); } +#endif // Record dear imgui primitives into command buffer ImGui_ImplVulkan_RenderDrawData(draw_data, fd->CommandBuffer); @@ -656,12 +675,23 @@ int main(int argc, char** argv) for (int i = 1; i < argc; i++) { if (strcmp(argv[i], "--require-descriptor-heap") == 0) + { +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP require_descriptor_heap = true; +#else + fprintf(stderr, "Error: --require-descriptor-heap requested but Vulkan headers lack VK_EXT_descriptor_heap (IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP undefined).\n"); + return 1; +#endif + } else if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0) { printf("Usage: %s [--require-descriptor-heap]\n", argv[0]); +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP printf(" --require-descriptor-heap Require a device with VK_EXT_descriptor_heap (exit on failure).\n"); printf(" (default) Use ImGui_ImplVulkanH_SelectPhysicalDevice; enable heap if available.\n"); +#else + printf(" --require-descriptor-heap Unavailable (Vulkan headers lack VK_EXT_descriptor_heap).\n"); +#endif return 0; } else @@ -737,8 +767,10 @@ int main(int argc, char** argv) init_info.PipelineInfoMain.Subpass = 0; init_info.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT; init_info.CheckVkResultFn = check_vk_result; +#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (g_UsingDescriptorHeap) init_info.DescriptorHeapInfo = &g_DescriptorHeapInfo; +#endif ImGui_ImplVulkan_Init(&init_info); // Load Fonts From 438160954e23ceac14128ee43b9f7e2dccb1c88e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Aleksander=20G=C3=B3rski?= Date: Thu, 6 Aug 2026 05:50:12 +0900 Subject: [PATCH 10/11] Vulkan: Add note regarding lifetime of descriptor heap resources Chore: Add changelog note scratch regarding adding support for VK_EXT_descriptor_heap Examples: Remove and shorten the old comment info --- backends/imgui_impl_vulkan.cpp | 1 + backends/imgui_impl_vulkan.h | 1 + examples/example_glfw_vulkan/main.cpp | 3 +-- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 64ae8a7bd..98a79f1bf 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -27,6 +27,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-08-05: Vulkan: Added optional VK_EXT_descriptor_heap support via ImGui_ImplVulkan_InitInfo::DescriptorHeapInfo (app-owned Register*/UnRegister* callbacks). ImTextureID is a GPU descriptor device address (like DX12). Requires IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP (headers with VK_EXT_descriptor_heap). // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2026-04-22: *BREAKING CHANGE* redesigned to use separate ImageView + Sampler instead of Combined Image Sampler. This change allows us to facilitate changing samplers, in line with other backends. // - When registering custom textures: changed ImGui_ImplVulkan_AddTexture() signature to remove Sampler. diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 2d2605527..87006e11a 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -129,6 +129,7 @@ struct ImGui_ImplVulkan_DescriptorHeapInfo // - The application owns heap slots via Register*/UnRegister* callbacks. // - ImTextureID is the GPU descriptor device address returned by RegisterImage. // Requires a 64-bit ImTextureID. +// - DescriptorHeapInfo pointer and ResourceHeapAddress/ImageDescriptorSize must remain valid for the backend lifetime; if you recreate the heap, update those fields and re-register textures (old ImTextureIDs become invalid). // - ImGui_ImplVulkan_AddTexture()/RemoveTexture() are pool-path only. // - Device extensions / features the application must enable: // - VK_EXT_descriptor_heap (feature: descriptorHeap). diff --git a/examples/example_glfw_vulkan/main.cpp b/examples/example_glfw_vulkan/main.cpp index 7be54541f..a5a2bdc34 100644 --- a/examples/example_glfw_vulkan/main.cpp +++ b/examples/example_glfw_vulkan/main.cpp @@ -456,8 +456,7 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ g_SamplerHeapAlloc.Create(g_Device, g_PhysicalDevice, descriptor_heap_properties.samplerDescriptorSize, descriptor_heap_properties.samplerHeapAlignment, descriptor_heap_properties.minSamplerHeapReservedRange, APP_SAMPLER_HEAP_SIZE, g_Allocator); - // Allocating descriptors is up to the application, so we provide callbacks (same idea as DX12 SrvDescriptorAllocFn/FreeFn). - // ImTextureID = GPU descriptor device address (like D3D12_GPU_DESCRIPTOR_HANDLE.ptr); slot 0 is usable. + // Allocating descriptors is up to the application, so we provide callbacks. ImTextureID = GPU descriptor device address. g_DescriptorHeapInfo = {}; g_DescriptorHeapInfo.ResourceHeapAddress = g_ResourceHeapAlloc.BindInfo.heapRange.address; g_DescriptorHeapInfo.ImageDescriptorSize = g_ResourceHeapAlloc.Stride; From 89601b2a64704d664a9a8a52774f735f0ae6538f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Aleksander=20G=C3=B3rski?= Date: Thu, 6 Aug 2026 20:00:45 +0900 Subject: [PATCH 11/11] Vulkan: Use non-zero heap indices as ImTextureID in descriptor-heap mode --- backends/imgui_impl_vulkan.cpp | 34 +++++++++------------------ backends/imgui_impl_vulkan.h | 28 ++++++++-------------- examples/example_glfw_vulkan/main.cpp | 28 +++++++--------------- 3 files changed, 29 insertions(+), 61 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 98a79f1bf..7d0b1b496 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -2,7 +2,7 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [!] Renderer: User texture binding. Pool mode: use a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE 'VkDescriptorSet' as texture identifier (ImGui_ImplVulkan_AddTexture()). Descriptor-heap mode (VK_EXT_descriptor_heap): use a GPU descriptor device address (like DX12's D3D12_GPU_DESCRIPTOR_HANDLE). Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. +// [!] Renderer: User texture binding. Pool mode: use a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE 'VkDescriptorSet' as texture identifier (ImGui_ImplVulkan_AddTexture()). Descriptor-heap mode (VK_EXT_descriptor_heap): use a heap index (ImTextureID); index 0 is reserved (ImTextureID_Invalid). Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. @@ -27,7 +27,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) -// 2026-08-05: Vulkan: Added optional VK_EXT_descriptor_heap support via ImGui_ImplVulkan_InitInfo::DescriptorHeapInfo (app-owned Register*/UnRegister* callbacks). ImTextureID is a GPU descriptor device address (like DX12). Requires IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP (headers with VK_EXT_descriptor_heap). +// 2026-08-05: Vulkan: Added optional VK_EXT_descriptor_heap support via ImGui_ImplVulkan_InitInfo::DescriptorHeapInfo (app-owned Register*/UnRegister* callbacks). ImTextureID is a non-zero heap index. Requires IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP (headers with VK_EXT_descriptor_heap). (#9374) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) // 2026-04-22: *BREAKING CHANGE* redesigned to use separate ImageView + Sampler instead of Combined Image Sampler. This change allows us to facilitate changing samplers, in line with other backends. // - When registering custom textures: changed ImGui_ImplVulkan_AddTexture() signature to remove Sampler. @@ -245,17 +245,6 @@ static PFN_vkCmdEndRenderingKHR ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR; #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP // VK_EXT_descriptor_heap (not exported by most Vulkan loaders; resolve like dynamic rendering) static PFN_vkCmdPushDataEXT ImGuiImplVulkanFuncs_vkCmdPushDataEXT = nullptr; - -// Convert GPU descriptor handle (device address) back to a heap index for PushData. -static uint32_t ImGui_ImplVulkan_DescriptorHeapIndexFromTexID(const ImGui_ImplVulkan_DescriptorHeapInfo* info, ImTextureID tex_id) -{ - IM_ASSERT(tex_id != ImTextureID_Invalid); - IM_ASSERT(info->ResourceHeapAddress != 0 && info->ImageDescriptorSize != 0); - const VkDeviceAddress addr = (VkDeviceAddress)(ImU64)tex_id; - IM_ASSERT(addr >= info->ResourceHeapAddress); - IM_ASSERT(((addr - info->ResourceHeapAddress) % info->ImageDescriptorSize) == 0); - return (uint32_t)((addr - info->ResourceHeapAddress) / info->ImageDescriptorSize); -} #endif // Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplVulkan_RenderDrawData() @@ -285,7 +274,7 @@ struct ImGui_ImplVulkan_Texture VkImage Image; VkImageView ImageView; VkDescriptorSet DescriptorSet; - uint64_t DescriptorHeapGpuHandle; // Device address into resource heap (== ImTextureID in heap mode) + uint32_t DescriptorHeapIndex; ImGui_ImplVulkan_Texture() { memset((void*)this, 0, sizeof(*this)); } }; @@ -835,8 +824,9 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (v->DescriptorHeapInfo) { - // ImTextureID is a GPU descriptor device address. - uint32_t heap_index = ImGui_ImplVulkan_DescriptorHeapIndexFromTexID(v->DescriptorHeapInfo, image_id); + // ImTextureID is the heap index. Index 0 is ImTextureID_Invalid / reserved. + uint32_t heap_index = (uint32_t)(ImU64)image_id; + IM_ASSERT(heap_index != 0 && "ImTextureID 0 is invalid in descriptor-heap mode (RegisterImage must not return 0)"); VkPushDataInfoEXT push{}; push.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT; push.data.address = &heap_index; @@ -878,7 +868,7 @@ static void ImGui_ImplVulkan_DestroyTexture(ImTextureData* tex) { IM_ASSERT(backend_tex->DescriptorSet == (VkDescriptorSet)tex->TexID #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP - || backend_tex->DescriptorHeapGpuHandle == (uint64_t)(ImU64)tex->TexID + || backend_tex->DescriptorHeapIndex == (uint32_t)(ImU64)tex->TexID #endif ); ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); @@ -887,7 +877,7 @@ static void ImGui_ImplVulkan_DestroyTexture(ImTextureData* tex) ImGui_ImplVulkan_RemoveTexture(backend_tex->DescriptorSet); #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP else if (v->DescriptorHeapInfo) - v->DescriptorHeapInfo->UnRegisterImage(v->DescriptorHeapInfo->UserContext, backend_tex->DescriptorHeapGpuHandle); + v->DescriptorHeapInfo->UnRegisterImage(v->DescriptorHeapInfo->UserContext, backend_tex->DescriptorHeapIndex); #endif if (backend_tex->ImageView != VK_NULL_HANDLE) vkDestroyImageView(v->Device, backend_tex->ImageView, v->Allocator); @@ -959,10 +949,10 @@ void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex) #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP if (v->DescriptorHeapInfo) { - backend_tex->DescriptorHeapGpuHandle = + backend_tex->DescriptorHeapIndex = v->DescriptorHeapInfo->RegisterImage(v->DescriptorHeapInfo->UserContext, &info); - IM_ASSERT(backend_tex->DescriptorHeapGpuHandle != 0 && "RegisterImage must return a non-zero device address"); - tex->SetTexID((ImTextureID)backend_tex->DescriptorHeapGpuHandle); + IM_ASSERT(backend_tex->DescriptorHeapIndex != 0 && "RegisterImage must not return 0 (reserved as ImTextureID_Invalid)"); + tex->SetTexID((ImTextureID)(ImU64)backend_tex->DescriptorHeapIndex); } else #endif @@ -1614,8 +1604,6 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info) ImGuiImplVulkanFuncs_vkCmdPushDataEXT = reinterpret_cast(vkGetDeviceProcAddr(info->Device, "vkCmdPushDataEXT")); #endif IM_ASSERT(ImGuiImplVulkanFuncs_vkCmdPushDataEXT != nullptr && "vkCmdPushDataEXT not available (enable VK_EXT_descriptor_heap)"); - IM_ASSERT(sizeof(ImTextureID) >= sizeof(VkDeviceAddress) && "Descriptor heap mode needs 64-bit ImTextureID"); - IM_ASSERT(info->DescriptorHeapInfo->ResourceHeapAddress != 0 && info->DescriptorHeapInfo->ImageDescriptorSize != 0); IM_ASSERT(info->DescriptorHeapInfo->RegisterImage != nullptr && info->DescriptorHeapInfo->UnRegisterImage != nullptr); IM_ASSERT(info->DescriptorHeapInfo->RegisterSampler != nullptr && info->DescriptorHeapInfo->UnRegisterSampler != nullptr); } diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 87006e11a..a210e25b8 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -2,7 +2,7 @@ // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // Implemented features: -// [!] Renderer: User texture binding. Pool mode: use a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE 'VkDescriptorSet' as texture identifier (ImGui_ImplVulkan_AddTexture()). Descriptor-heap mode (VK_EXT_descriptor_heap): use a GPU descriptor device address (like DX12's D3D12_GPU_DESCRIPTOR_HANDLE). Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. +// [!] Renderer: User texture binding. Pool mode: use a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE 'VkDescriptorSet' as texture identifier (ImGui_ImplVulkan_AddTexture()). Descriptor-heap mode (VK_EXT_descriptor_heap): use a heap index (ImTextureID); index 0 is reserved (ImTextureID_Invalid). Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures). // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. @@ -97,20 +97,13 @@ struct ImGui_ImplVulkan_PipelineInfo }; #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP -// App owns heap slots. ImTextureID is the descriptor's device address in the resource heap (like D3D12_GPU_DESCRIPTOR_HANDLE.ptr). -// Backend converts to a heap index for vkCmdPushDataEXT via (tex_id - ResourceHeapAddress) / ImageDescriptorSize. -// Requires a 64-bit ImTextureID (Dear ImGui default since 1.91.4). Do not redefine ImTextureID to a 32-bit type. +// App owns heap slots via Register*/UnRegister* (like DX12 SrvDescriptorAllocFn/FreeFn). +// ImTextureID is the heap index returned by RegisterImage. Index 0 is reserved (ImTextureID_Invalid == 0). struct ImGui_ImplVulkan_DescriptorHeapInfo { - VkDeviceAddress ResourceHeapAddress; // Bound resource heap base (vkGetBufferDeviceAddress of heap buffer) - VkDeviceSize ImageDescriptorSize; // imageDescriptorSize from VkPhysicalDeviceDescriptorHeapPropertiesEXT - - // RegisterImage returns GPU descriptor handle (device address / VkDeviceAddress). UnRegisterImage takes that same handle. - // Returned value is stored as ImTextureID; needs sizeof(ImTextureID) >= 8. - uint64_t (*RegisterImage)(void*, const VkImageViewCreateInfo*); - void (*UnRegisterImage)(void*, uint64_t); - // Samplers are not ImTextureIDs; indices are fine (including 0). - uint32_t (*RegisterSampler)(void*, const VkSamplerCreateInfo*); + uint32_t (*RegisterImage)(void*, const VkImageViewCreateInfo*); // Return non-zero heap index used as ImTextureID + void (*UnRegisterImage)(void*, uint32_t); + uint32_t (*RegisterSampler)(void*, const VkSamplerCreateInfo*); // Indices may be 0 (not used as ImTextureID) void (*UnRegisterSampler)(void*, uint32_t); void* UserContext; }; @@ -127,9 +120,8 @@ struct ImGui_ImplVulkan_DescriptorHeapInfo // - About descriptor heap (requires VK_EXT_descriptor_heap in your Vulkan headers → IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP): // - When using descriptor heaps, set DescriptorHeapInfo and leave DescriptorPool / DescriptorPoolSize unset. // - The application owns heap slots via Register*/UnRegister* callbacks. -// - ImTextureID is the GPU descriptor device address returned by RegisterImage. -// Requires a 64-bit ImTextureID. -// - DescriptorHeapInfo pointer and ResourceHeapAddress/ImageDescriptorSize must remain valid for the backend lifetime; if you recreate the heap, update those fields and re-register textures (old ImTextureIDs become invalid). +// - ImTextureID is the heap index returned by RegisterImage. Index 0 is reserved (conflicts with ImTextureID_Invalid). +// - DescriptorHeapInfo pointer must remain valid for the backend lifetime. // - ImGui_ImplVulkan_AddTexture()/RemoveTexture() are pool-path only. // - Device extensions / features the application must enable: // - VK_EXT_descriptor_heap (feature: descriptorHeap). @@ -175,7 +167,7 @@ struct ImGui_ImplVulkan_InitInfo #ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP // (Optional) If set, use VK_EXT_descriptor_heap (app-owned Register*/UnRegister* callbacks; see comment above). - // ImTextureID = RegisterImage(...) device address. ImGui_ImplVulkan_AddTexture() is unavailable in this mode. + // ImTextureID = RegisterImage(...) heap index (non-zero). ImGui_ImplVulkan_AddTexture() is unavailable in this mode. // App must enable the device extensions/features listed under "About descriptor heap". const ImGui_ImplVulkan_DescriptorHeapInfo *DescriptorHeapInfo; #endif @@ -198,7 +190,7 @@ IMGUI_IMPL_API void ImGui_ImplVulkan_UpdateTexture(ImTextureData* te // Register a texture (VkDescriptorSet for a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE == ImTextureID) // - Pool path only. Not available when InitInfo::DescriptorHeapInfo is set (asserts). -// - Heap mode: ImTextureID = DescriptorHeapInfo::RegisterImage(...) device address; free with UnRegisterImage. +// - Heap mode: ImTextureID = DescriptorHeapInfo::RegisterImage(...) heap index; free with UnRegisterImage. IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkImageView image_view, VkImageLayout image_layout); IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set); diff --git a/examples/example_glfw_vulkan/main.cpp b/examples/example_glfw_vulkan/main.cpp index a5a2bdc34..6fd6da82e 100644 --- a/examples/example_glfw_vulkan/main.cpp +++ b/examples/example_glfw_vulkan/main.cpp @@ -146,8 +146,9 @@ struct ExampleDescriptorHeapAllocator BindInfo.reservedRangeOffset = descriptors_bytes; BindInfo.reservedRangeSize = reserved_range; - FreeIndices.reserve(capacity); - for (int n = capacity - 1; n >= 0; n--) + FreeIndices.reserve(capacity > 1 ? capacity - 1 : 0); + // Slot 0 reserved: ImTextureID_Invalid is 0, so RegisterImage indices used as ImTextureID must be non-zero. + for (int n = capacity - 1; n >= 1; n--) FreeIndices.push_back(n); } void Destroy(VkDevice device, const VkAllocationCallbacks* allocator) @@ -171,16 +172,6 @@ struct ExampleDescriptorHeapAllocator { FreeIndices.push_back(idx); } - VkDeviceAddress GpuHandleFromIndex(int idx) const - { - return BindInfo.heapRange.address + (VkDeviceAddress)idx * (VkDeviceAddress)Stride; - } - int IndexFromGpuHandle(VkDeviceAddress gpu_handle) const - { - IM_ASSERT(gpu_handle >= BindInfo.heapRange.address); - IM_ASSERT(((gpu_handle - BindInfo.heapRange.address) % Stride) == 0); - return (int)((gpu_handle - BindInfo.heapRange.address) / Stride); - } VkHostAddressRangeEXT DescriptorAddress(int idx) const { return VkHostAddressRangeEXT{ (char*)Mapped + (VkDeviceSize)idx * Stride, Stride }; @@ -456,12 +447,11 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ g_SamplerHeapAlloc.Create(g_Device, g_PhysicalDevice, descriptor_heap_properties.samplerDescriptorSize, descriptor_heap_properties.samplerHeapAlignment, descriptor_heap_properties.minSamplerHeapReservedRange, APP_SAMPLER_HEAP_SIZE, g_Allocator); - // Allocating descriptors is up to the application, so we provide callbacks. ImTextureID = GPU descriptor device address. + // Allocating descriptors is up to the application (like DX12 SrvDescriptorAllocFn/FreeFn). + // ImTextureID = heap index from RegisterImage; index 0 is reserved (ImTextureID_Invalid). g_DescriptorHeapInfo = {}; - g_DescriptorHeapInfo.ResourceHeapAddress = g_ResourceHeapAlloc.BindInfo.heapRange.address; - g_DescriptorHeapInfo.ImageDescriptorSize = g_ResourceHeapAlloc.Stride; g_DescriptorHeapInfo.UserContext = nullptr; - g_DescriptorHeapInfo.RegisterImage = [](void*, const VkImageViewCreateInfo* ci) -> uint64_t { + g_DescriptorHeapInfo.RegisterImage = [](void*, const VkImageViewCreateInfo* ci) -> uint32_t { int idx = g_ResourceHeapAlloc.Alloc(); VkImageDescriptorInfoEXT image = {}; image.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT; @@ -473,11 +463,9 @@ static void SetupVulkan(ImVector instance_extensions, bool require_ resource.data.pImage = ℑ VkHostAddressRangeEXT addr = g_ResourceHeapAlloc.DescriptorAddress(idx); check_vk_result(g_DescHeapFns.vkWriteResourceDescriptorsEXT(g_Device, 1, &resource, &addr)); - return (uint64_t)g_ResourceHeapAlloc.GpuHandleFromIndex(idx); - }; - g_DescriptorHeapInfo.UnRegisterImage = [](void*, uint64_t gpu_handle) { - g_ResourceHeapAlloc.Free(g_ResourceHeapAlloc.IndexFromGpuHandle((VkDeviceAddress)gpu_handle)); + return (uint32_t)idx; }; + g_DescriptorHeapInfo.UnRegisterImage = [](void*, uint32_t idx) { g_ResourceHeapAlloc.Free((int)idx); }; g_DescriptorHeapInfo.RegisterSampler = [](void*, const VkSamplerCreateInfo* ci) -> uint32_t { int idx = g_SamplerHeapAlloc.Alloc(); VkHostAddressRangeEXT addr = g_SamplerHeapAlloc.DescriptorAddress(idx);