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