Skip to content

Commit 19aa049

Browse files
committed
Vulkan: Use non-zero heap indices as ImTextureID in descriptor-heap mode
1 parent 8462f29 commit 19aa049

3 files changed

Lines changed: 29 additions & 61 deletions

File tree

backends/imgui_impl_vulkan.cpp

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
33

44
// Implemented features:
5-
// [!] 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.
5+
// [!] 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.
66
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
77
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
88
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
@@ -27,7 +27,7 @@
2727

2828
// CHANGELOG
2929
// (minor and older changes stripped away, please see git history for details)
30-
// 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).
30+
// 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)
3131
// 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378)
3232
// 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.
3333
// - When registering custom textures: changed ImGui_ImplVulkan_AddTexture() signature to remove Sampler.
@@ -245,17 +245,6 @@ static PFN_vkCmdEndRenderingKHR ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR;
245245
#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP
246246
// VK_EXT_descriptor_heap (not exported by most Vulkan loaders; resolve like dynamic rendering)
247247
static PFN_vkCmdPushDataEXT ImGuiImplVulkanFuncs_vkCmdPushDataEXT = nullptr;
248-
249-
// Convert GPU descriptor handle (device address) back to a heap index for PushData.
250-
static uint32_t ImGui_ImplVulkan_DescriptorHeapIndexFromTexID(const ImGui_ImplVulkan_DescriptorHeapInfo* info, ImTextureID tex_id)
251-
{
252-
IM_ASSERT(tex_id != ImTextureID_Invalid);
253-
IM_ASSERT(info->ResourceHeapAddress != 0 && info->ImageDescriptorSize != 0);
254-
const VkDeviceAddress addr = (VkDeviceAddress)(ImU64)tex_id;
255-
IM_ASSERT(addr >= info->ResourceHeapAddress);
256-
IM_ASSERT(((addr - info->ResourceHeapAddress) % info->ImageDescriptorSize) == 0);
257-
return (uint32_t)((addr - info->ResourceHeapAddress) / info->ImageDescriptorSize);
258-
}
259248
#endif
260249

261250
// Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplVulkan_RenderDrawData()
@@ -285,7 +274,7 @@ struct ImGui_ImplVulkan_Texture
285274
VkImage Image;
286275
VkImageView ImageView;
287276
VkDescriptorSet DescriptorSet;
288-
uint64_t DescriptorHeapGpuHandle; // Device address into resource heap (== ImTextureID in heap mode)
277+
uint32_t DescriptorHeapIndex;
289278

290279
ImGui_ImplVulkan_Texture() { memset((void*)this, 0, sizeof(*this)); }
291280
};
@@ -837,8 +826,9 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm
837826
#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP
838827
if (v->DescriptorHeapInfo)
839828
{
840-
// ImTextureID is a GPU descriptor device address.
841-
uint32_t heap_index = ImGui_ImplVulkan_DescriptorHeapIndexFromTexID(v->DescriptorHeapInfo, image_id);
829+
// ImTextureID is the heap index. Index 0 is ImTextureID_Invalid / reserved.
830+
uint32_t heap_index = (uint32_t)(ImU64)image_id;
831+
IM_ASSERT(heap_index != 0 && "ImTextureID 0 is invalid in descriptor-heap mode (RegisterImage must not return 0)");
842832
VkPushDataInfoEXT push{};
843833
push.sType = VK_STRUCTURE_TYPE_PUSH_DATA_INFO_EXT;
844834
push.data.address = &heap_index;
@@ -880,7 +870,7 @@ static void ImGui_ImplVulkan_DestroyTexture(ImTextureData* tex)
880870
{
881871
IM_ASSERT(backend_tex->DescriptorSet == (VkDescriptorSet)tex->TexID
882872
#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP
883-
|| backend_tex->DescriptorHeapGpuHandle == (uint64_t)(ImU64)tex->TexID
873+
|| backend_tex->DescriptorHeapIndex == (uint32_t)(ImU64)tex->TexID
884874
#endif
885875
);
886876
ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData();
@@ -889,7 +879,7 @@ static void ImGui_ImplVulkan_DestroyTexture(ImTextureData* tex)
889879
ImGui_ImplVulkan_RemoveTexture(backend_tex->DescriptorSet);
890880
#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP
891881
else if (v->DescriptorHeapInfo)
892-
v->DescriptorHeapInfo->UnRegisterImage(v->DescriptorHeapInfo->UserContext, backend_tex->DescriptorHeapGpuHandle);
882+
v->DescriptorHeapInfo->UnRegisterImage(v->DescriptorHeapInfo->UserContext, backend_tex->DescriptorHeapIndex);
893883
#endif
894884
if (backend_tex->ImageView != VK_NULL_HANDLE)
895885
vkDestroyImageView(v->Device, backend_tex->ImageView, v->Allocator);
@@ -961,10 +951,10 @@ void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex)
961951
#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP
962952
if (v->DescriptorHeapInfo)
963953
{
964-
backend_tex->DescriptorHeapGpuHandle =
954+
backend_tex->DescriptorHeapIndex =
965955
v->DescriptorHeapInfo->RegisterImage(v->DescriptorHeapInfo->UserContext, &info);
966-
IM_ASSERT(backend_tex->DescriptorHeapGpuHandle != 0 && "RegisterImage must return a non-zero device address");
967-
tex->SetTexID((ImTextureID)backend_tex->DescriptorHeapGpuHandle);
956+
IM_ASSERT(backend_tex->DescriptorHeapIndex != 0 && "RegisterImage must not return 0 (reserved as ImTextureID_Invalid)");
957+
tex->SetTexID((ImTextureID)(ImU64)backend_tex->DescriptorHeapIndex);
968958
}
969959
else
970960
#endif
@@ -1616,8 +1606,6 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info)
16161606
ImGuiImplVulkanFuncs_vkCmdPushDataEXT = reinterpret_cast<PFN_vkCmdPushDataEXT>(vkGetDeviceProcAddr(info->Device, "vkCmdPushDataEXT"));
16171607
#endif
16181608
IM_ASSERT(ImGuiImplVulkanFuncs_vkCmdPushDataEXT != nullptr && "vkCmdPushDataEXT not available (enable VK_EXT_descriptor_heap)");
1619-
IM_ASSERT(sizeof(ImTextureID) >= sizeof(VkDeviceAddress) && "Descriptor heap mode needs 64-bit ImTextureID");
1620-
IM_ASSERT(info->DescriptorHeapInfo->ResourceHeapAddress != 0 && info->DescriptorHeapInfo->ImageDescriptorSize != 0);
16211609
IM_ASSERT(info->DescriptorHeapInfo->RegisterImage != nullptr && info->DescriptorHeapInfo->UnRegisterImage != nullptr);
16221610
IM_ASSERT(info->DescriptorHeapInfo->RegisterSampler != nullptr && info->DescriptorHeapInfo->UnRegisterSampler != nullptr);
16231611
}

backends/imgui_impl_vulkan.h

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
33

44
// Implemented features:
5-
// [!] 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.
5+
// [!] 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.
66
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
77
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
88
// [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
9797
};
9898

9999
#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP
100-
// App owns heap slots. ImTextureID is the descriptor's device address in the resource heap (like D3D12_GPU_DESCRIPTOR_HANDLE.ptr).
101-
// Backend converts to a heap index for vkCmdPushDataEXT via (tex_id - ResourceHeapAddress) / ImageDescriptorSize.
102-
// Requires a 64-bit ImTextureID (Dear ImGui default since 1.91.4). Do not redefine ImTextureID to a 32-bit type.
100+
// App owns heap slots via Register*/UnRegister* (like DX12 SrvDescriptorAllocFn/FreeFn).
101+
// ImTextureID is the heap index returned by RegisterImage. Index 0 is reserved (ImTextureID_Invalid == 0).
103102
struct ImGui_ImplVulkan_DescriptorHeapInfo
104103
{
105-
VkDeviceAddress ResourceHeapAddress; // Bound resource heap base (vkGetBufferDeviceAddress of heap buffer)
106-
VkDeviceSize ImageDescriptorSize; // imageDescriptorSize from VkPhysicalDeviceDescriptorHeapPropertiesEXT
107-
108-
// RegisterImage returns GPU descriptor handle (device address / VkDeviceAddress). UnRegisterImage takes that same handle.
109-
// Returned value is stored as ImTextureID; needs sizeof(ImTextureID) >= 8.
110-
uint64_t (*RegisterImage)(void*, const VkImageViewCreateInfo*);
111-
void (*UnRegisterImage)(void*, uint64_t);
112-
// Samplers are not ImTextureIDs; indices are fine (including 0).
113-
uint32_t (*RegisterSampler)(void*, const VkSamplerCreateInfo*);
104+
uint32_t (*RegisterImage)(void*, const VkImageViewCreateInfo*); // Return non-zero heap index used as ImTextureID
105+
void (*UnRegisterImage)(void*, uint32_t);
106+
uint32_t (*RegisterSampler)(void*, const VkSamplerCreateInfo*); // Indices may be 0 (not used as ImTextureID)
114107
void (*UnRegisterSampler)(void*, uint32_t);
115108
void* UserContext;
116109
};
@@ -127,9 +120,8 @@ struct ImGui_ImplVulkan_DescriptorHeapInfo
127120
// - About descriptor heap (requires VK_EXT_descriptor_heap in your Vulkan headers → IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP):
128121
// - When using descriptor heaps, set DescriptorHeapInfo and leave DescriptorPool / DescriptorPoolSize unset.
129122
// - The application owns heap slots via Register*/UnRegister* callbacks.
130-
// - ImTextureID is the GPU descriptor device address returned by RegisterImage.
131-
// Requires a 64-bit ImTextureID.
132-
// - 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).
123+
// - ImTextureID is the heap index returned by RegisterImage. Index 0 is reserved (conflicts with ImTextureID_Invalid).
124+
// - DescriptorHeapInfo pointer must remain valid for the backend lifetime.
133125
// - ImGui_ImplVulkan_AddTexture()/RemoveTexture() are pool-path only.
134126
// - Device extensions / features the application must enable:
135127
// - VK_EXT_descriptor_heap (feature: descriptorHeap).
@@ -175,7 +167,7 @@ struct ImGui_ImplVulkan_InitInfo
175167

176168
#ifdef IMGUI_IMPL_VULKAN_HAS_DESCRIPTOR_HEAP
177169
// (Optional) If set, use VK_EXT_descriptor_heap (app-owned Register*/UnRegister* callbacks; see comment above).
178-
// ImTextureID = RegisterImage(...) device address. ImGui_ImplVulkan_AddTexture() is unavailable in this mode.
170+
// ImTextureID = RegisterImage(...) heap index (non-zero). ImGui_ImplVulkan_AddTexture() is unavailable in this mode.
179171
// App must enable the device extensions/features listed under "About descriptor heap".
180172
const ImGui_ImplVulkan_DescriptorHeapInfo *DescriptorHeapInfo;
181173
#endif
@@ -198,7 +190,7 @@ IMGUI_IMPL_API void ImGui_ImplVulkan_UpdateTexture(ImTextureData* te
198190

199191
// Register a texture (VkDescriptorSet for a VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE == ImTextureID)
200192
// - Pool path only. Not available when InitInfo::DescriptorHeapInfo is set (asserts).
201-
// - Heap mode: ImTextureID = DescriptorHeapInfo::RegisterImage(...) device address; free with UnRegisterImage.
193+
// - Heap mode: ImTextureID = DescriptorHeapInfo::RegisterImage(...) heap index; free with UnRegisterImage.
202194
IMGUI_IMPL_API VkDescriptorSet ImGui_ImplVulkan_AddTexture(VkImageView image_view, VkImageLayout image_layout);
203195
IMGUI_IMPL_API void ImGui_ImplVulkan_RemoveTexture(VkDescriptorSet descriptor_set);
204196

examples/example_glfw_vulkan/main.cpp

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -146,8 +146,9 @@ struct ExampleDescriptorHeapAllocator
146146
BindInfo.reservedRangeOffset = descriptors_bytes;
147147
BindInfo.reservedRangeSize = reserved_range;
148148

149-
FreeIndices.reserve(capacity);
150-
for (int n = capacity - 1; n >= 0; n--)
149+
FreeIndices.reserve(capacity > 1 ? capacity - 1 : 0);
150+
// Slot 0 reserved: ImTextureID_Invalid is 0, so RegisterImage indices used as ImTextureID must be non-zero.
151+
for (int n = capacity - 1; n >= 1; n--)
151152
FreeIndices.push_back(n);
152153
}
153154
void Destroy(VkDevice device, const VkAllocationCallbacks* allocator)
@@ -171,16 +172,6 @@ struct ExampleDescriptorHeapAllocator
171172
{
172173
FreeIndices.push_back(idx);
173174
}
174-
VkDeviceAddress GpuHandleFromIndex(int idx) const
175-
{
176-
return BindInfo.heapRange.address + (VkDeviceAddress)idx * (VkDeviceAddress)Stride;
177-
}
178-
int IndexFromGpuHandle(VkDeviceAddress gpu_handle) const
179-
{
180-
IM_ASSERT(gpu_handle >= BindInfo.heapRange.address);
181-
IM_ASSERT(((gpu_handle - BindInfo.heapRange.address) % Stride) == 0);
182-
return (int)((gpu_handle - BindInfo.heapRange.address) / Stride);
183-
}
184175
VkHostAddressRangeEXT DescriptorAddress(int idx) const
185176
{
186177
return VkHostAddressRangeEXT{ (char*)Mapped + (VkDeviceSize)idx * Stride, Stride };
@@ -456,12 +447,11 @@ static void SetupVulkan(ImVector<const char*> instance_extensions, bool require_
456447
g_SamplerHeapAlloc.Create(g_Device, g_PhysicalDevice, descriptor_heap_properties.samplerDescriptorSize, descriptor_heap_properties.samplerHeapAlignment,
457448
descriptor_heap_properties.minSamplerHeapReservedRange, APP_SAMPLER_HEAP_SIZE, g_Allocator);
458449

459-
// Allocating descriptors is up to the application, so we provide callbacks. ImTextureID = GPU descriptor device address.
450+
// Allocating descriptors is up to the application (like DX12 SrvDescriptorAllocFn/FreeFn).
451+
// ImTextureID = heap index from RegisterImage; index 0 is reserved (ImTextureID_Invalid).
460452
g_DescriptorHeapInfo = {};
461-
g_DescriptorHeapInfo.ResourceHeapAddress = g_ResourceHeapAlloc.BindInfo.heapRange.address;
462-
g_DescriptorHeapInfo.ImageDescriptorSize = g_ResourceHeapAlloc.Stride;
463453
g_DescriptorHeapInfo.UserContext = nullptr;
464-
g_DescriptorHeapInfo.RegisterImage = [](void*, const VkImageViewCreateInfo* ci) -> uint64_t {
454+
g_DescriptorHeapInfo.RegisterImage = [](void*, const VkImageViewCreateInfo* ci) -> uint32_t {
465455
int idx = g_ResourceHeapAlloc.Alloc();
466456
VkImageDescriptorInfoEXT image = {};
467457
image.sType = VK_STRUCTURE_TYPE_IMAGE_DESCRIPTOR_INFO_EXT;
@@ -473,11 +463,9 @@ static void SetupVulkan(ImVector<const char*> instance_extensions, bool require_
473463
resource.data.pImage = &image;
474464
VkHostAddressRangeEXT addr = g_ResourceHeapAlloc.DescriptorAddress(idx);
475465
check_vk_result(g_DescHeapFns.vkWriteResourceDescriptorsEXT(g_Device, 1, &resource, &addr));
476-
return (uint64_t)g_ResourceHeapAlloc.GpuHandleFromIndex(idx);
477-
};
478-
g_DescriptorHeapInfo.UnRegisterImage = [](void*, uint64_t gpu_handle) {
479-
g_ResourceHeapAlloc.Free(g_ResourceHeapAlloc.IndexFromGpuHandle((VkDeviceAddress)gpu_handle));
466+
return (uint32_t)idx;
480467
};
468+
g_DescriptorHeapInfo.UnRegisterImage = [](void*, uint32_t idx) { g_ResourceHeapAlloc.Free((int)idx); };
481469
g_DescriptorHeapInfo.RegisterSampler = [](void*, const VkSamplerCreateInfo* ci) -> uint32_t {
482470
int idx = g_SamplerHeapAlloc.Alloc();
483471
VkHostAddressRangeEXT addr = g_SamplerHeapAlloc.DescriptorAddress(idx);

0 commit comments

Comments
 (0)