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] 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);