Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Vulkan: Use GPU device address for ImTextureID in descriptor heap mode
  • Loading branch information
janagor authored and ocornut committed Aug 6, 2026
commit c33ea3ccb68e4313e36765fce67860dfd029681b
41 changes: 28 additions & 13 deletions backends/imgui_impl_vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)); }
};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1602,6 +1613,10 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info)
ImGuiImplVulkanFuncs_vkCmdPushDataEXT = reinterpret_cast<PFN_vkCmdPushDataEXT>(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

Expand Down Expand Up @@ -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;

Expand Down
30 changes: 20 additions & 10 deletions backends/imgui_impl_vulkan.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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)
Expand Down Expand Up @@ -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
};
Expand All @@ -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);

Expand Down
26 changes: 21 additions & 5 deletions examples/example_glfw_vulkan/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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 };
Expand Down Expand Up @@ -417,8 +427,12 @@ static void SetupVulkan(ImVector<const char*> 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;
Expand All @@ -430,9 +444,11 @@ static void SetupVulkan(ImVector<const char*> instance_extensions, bool require_
resource.data.pImage = &image;
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);
Expand Down