ImGui
comes with a handy image widget in their demo page. It works magically in my engine to display the built-in font textures, even though I didn't explicitly bind it during the runtime. The ImGui::Image()
function to draw pictures has a textureId
, which is also unclear to me.
Since I'm writing my own rendering backend, I took some time to investigate how the textureId
works. When we call ImGui::Image(textureId, ...)
, ImGui creates a draw command to draw a rectangle, and set the ImDrawCmd::TextureId
variable with the textureId
we pass in. During the execution time, ImDrawCmd::TextureId
is accessible when we prepare the GPU command buffer. That said, TextureId
serves simply as an index of the texture we want to bind. We have to implement the array of texture information structure that can be bond to pipeline when we record the command buffer.
A Fast Solution
In the issue the author of ImGui has discussed to wrap the texture into a VkDescriptorSet
handle, and pass it as textureId
. It works as the fact that typedef void* ImTextureID
has 64 bits on a 64-bit machine, while VkDescriptorSet
has also 64 bits. However, this doesn't hold on a 32-bit machines. That's reason why the Vulkan ImGui demo implementation has no implementation of ImGui::Image()
yet.
Not That Fast Solution
The fast solution inspired me that I can track descriptor sets with textureId
. In my solution, I use my DescriptorManager to create and track a list of descriptor sets used in ImGui, where the list is addressed by textureId
. I also keep a map from resource identifier to textureId
so that I can use bind the resource when I draw image with ImGui::Image()
.
1 | std::vector<VkDescriptorSet> m_vImGuiTextureDescriptorSets; |
Note that the first textureId
is reserved for font textures. ImDrawCmd::TextureId
is zero when there's no ImGui::Image()
called on the draw command. To honour this, I have to insert the font texture as the first ImGui texture when creating resources for ImGui.
1 | // Create font texture |
The other textures can be inserted with the same fashion. For example, I can display my environment map with ImGui like this.
1 | //... |
During the draw call, we can simply bind the corresponding descriptor set based on the TextureId
in draw command like this.
1 | vkCmdBindDescriptorSets(curCmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipelineLayout, 0, 1, &GetDescriptorManager()->GetImGuiTextureDescriptorSet((size_t)drawCmd.TextureId), 0, nullptr); |