How to Use ImTextureID with Custom Texture Types in OpenGL: A Complete Guide
Dear ImGui's ImTextureID is an opaque void* value that the OpenGL backends interpret as a GLuint texture name, allowing you to pass raw texture handles, custom wrapper classes, or complex resource managers as long as the value round-trips through void* → intptr_t → GLuint without data loss.
Integrating custom texture abstractions with Dear ImGui requires understanding how ImTextureID bridges your application code with the rendering backend. In the ocornut/imgui repository, this opaque type provides flexibility for OpenGL applications to use raw texture names, C++ object wrappers, or smart pointers while maintaining compatibility with ImGui's immediate-mode drawing API. This guide explains the exact mechanism used by the OpenGL backends and provides working patterns for custom texture integration.
Understanding ImTextureID in Dear ImGui
ImTextureID is defined in imgui.h as a void* typedef, making it an opaque pointer-sized value that ImGui treats as a black box. The library itself never dereferences this pointer; instead, it passes the value directly to the rendering backend during draw-list execution. This design allows the same ImGui code to work across DirectX, Vulkan, Metal, and OpenGL, where the underlying texture representation differs significantly.
For OpenGL specifically, the backend implementation in backends/imgui_impl_opengl3.cpp (and the legacy backends/imgui_impl_opengl2.cpp) expects this opaque value to represent an OpenGL texture name (GLuint). The backend extracts the actual texture handle using a specific casting pattern that preserves integer values through pointer storage.
How the OpenGL Backend Interprets ImTextureID
The OpenGL backend converts the opaque ImTextureID back to a usable GLuint through a two-step cast designed to prevent compiler warnings while preserving data:
// Located in backends/imgui_impl_opengl3.cpp
GLuint texture_id = (GLuint)(intptr_t)user_texture_id;
This conversion sequence is critical: intptr_t ensures the integer texture name survives the round-trip through a pointer-sized storage. Therefore, any custom texture type you create must produce a value that can be cast to void* (as ImTextureID) and then back to GLuint through intptr_t without truncation or alteration.
Integration Patterns for Custom Texture Types
The flexibility of the opaque handle allows several architectural patterns. Choose the approach that best fits your resource management strategy while adhering to the casting contract.
Raw GLuint Texture Names
The simplest approach stores the OpenGL texture name directly. This works immediately with zero overhead and is the pattern used in the official examples like examples/example_glfw_opengl3/main.cpp.
GLuint my_gl_tex;
glGenTextures(1, &my_gl_tex);
glBindTexture(GL_TEXTURE_2D, my_gl_tex);
// Upload pixel data with glTexImage2D...
// Cast to ImTextureID for ImGui
ImTextureID tex_id = (ImTextureID)(intptr_t)my_gl_tex;
ImGui::Begin("Texture Demo");
ImGui::Image(tex_id, ImVec2(256.0f, 256.0f));
ImGui::End();
Custom C++ Structs and Classes
For applications using wrapper objects (e.g., a Texture2D class), you have two valid approaches:
Pass the GLuint handle directly (recommended):
Extract the raw texture name via a getter method and cast it to ImTextureID at the call site. This keeps ImGui calls clean and avoids pointer indirection.
Pass a pointer to the wrapper:
Store the address of your texture object as the ImTextureID, then dereference it when needed. This is useful when you need additional metadata (width, height, format) alongside the texture ID.
struct MyTexture
{
GLuint gl_id;
int width, height;
MyTexture(const char* filename) {
// Load image, glGenTextures, glTexImage2D...
glGenTextures(1, &gl_id);
// ... upload logic
}
GLuint GetGLHandle() const { return gl_id; }
};
MyTexture myTex("awesome.png");
// Approach A: Pass raw handle
ImGui::Image((ImTextureID)(intptr_t)myTex.GetGLHandle(),
ImVec2((float)myTex.width, (float)myTex.height));
// Approach B: Pass pointer to wrapper
MyTexture* texPtr = &myTex;
ImGui::ImageButton(static_cast<ImTextureID>(texPtr), ImVec2(128, 128));
Smart Pointer Integration
When using smart pointers like std::shared_ptr or std::unique_ptr, store the raw pointer value obtained via .get(). Do not pass the smart pointer object itself, as that would slicing-cast the control block.
std::shared_ptr<MyTexture> texPtr = std::make_shared<MyTexture>("logo.png");
ImTextureID imgui_id = static_cast<ImTextureID>(texPtr.get());
ImGui::Image(imgui_id, ImVec2(200.0f, 200.0f));
// Later retrieval (if you stored the pointer):
if (ImGui::ImageButton(imgui_id, ImVec2(100, 100)))
{
MyTexture* tex = static_cast<MyTexture*>(imgui_id);
GLuint actual_id = tex->GetGLHandle();
// Use actual_id for manual binding if needed
}
Practical Implementation Examples
The following patterns demonstrate complete, compile-ready workflows for different abstraction levels.
Example 1: Basic Raw GLuint Workflow
// Create and configure OpenGL texture
GLuint my_gl_tex;
glGenTextures(1, &my_gl_tex);
glBindTexture(GL_TEXTURE_2D, my_gl_tex);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// ... upload with glTexImage2D ...
// Convert to ImTextureID using intptr_t cast
ImTextureID tex_id = (ImTextureID)(intptr_t)my_gl_tex;
// Use in ImGui
ImGui::Begin("Raw Texture");
ImGui::Image(tex_id, ImVec2(256.0f, 256.0f));
ImGui::End();
Example 2: Wrapper Class with Metadata
class Texture2D
{
GLuint m_id;
public:
int Width, Height;
Texture2D(const char* path) : Width(0), Height(0) {
// Load file, get dimensions
glGenTextures(1, &m_id);
glBindTexture(GL_TEXTURE_2D, m_id);
// ... upload ...
}
GLuint GetID() const { return m_id; }
~Texture2D() { glDeleteTextures(1, &m_id); }
};
// Usage
Texture2D playerSprite("player.png");
ImTextureID player_img = (ImTextureID)(intptr_t)playerSprite.GetID();
ImGui::Image(player_img, ImVec2((float)playerSprite.Width,
(float)playerSprite.Height));
Example 3: Pointer-to-Wrapper Pattern
struct GameTexture {
GLuint gl_id;
std::string debugName;
float lastAccessTime;
};
GameTexture* hudTexture = new GameTexture(); // In practice, use your pool allocator
hudTexture->gl_id = loadTexture("hud.png"); // Your loading function
ImTextureID hud_id = static_cast<ImTextureID>(hudTexture);
ImGui::Begin("HUD");
if (ImGui::ImageButton("hud_btn", hud_id, ImVec2(64, 64)))
{
// Retrieve wrapper to access metadata
GameTexture* tex = static_cast<GameTexture*>(hud_id);
std::cout << "Clicked texture: " << tex->debugName << std::endl;
}
ImGui::End();
Example 4: Smart Pointer Safety
auto sharedTex = std::make_shared<MyTexture>("ui_icon.png");
ImTextureID tex_id = static_cast<ImTextureID>(sharedTex.get());
// Ensure sharedTex remains alive while ImGui renders
ImGui::Image(tex_id, ImVec2(32.0f, 32.0f));
// If you need to ensure the resource stays alive through the frame,
// keep the shared_ptr in a temporary variable or frame-bound storage
Key Source Files and References
Understanding the contract between ImGui and the OpenGL renderer requires examining these specific files in the ocornut/imgui repository:
| File | Relevance |
|---|---|
backends/imgui_impl_opengl3.cpp |
Contains the critical conversion (GLuint)(intptr_t)user_texture_id used to extract texture handles from ImTextureID. |
backends/imgui_impl_opengl2.cpp |
Implements identical extraction logic for legacy OpenGL 2.x contexts. |
imgui.h |
Defines ImTextureID as void* and declares ImGui::Image(), ImGui::ImageButton(), and other texture-consuming functions. |
examples/example_glfw_opengl3/main.cpp |
Demonstrates practical texture loading with GLFW/GLAD and proper ImTextureID casting. |
examples/example_sdl2_opengl3/main.cpp |
Alternative example showing SDL2 integration with OpenGL texture handling. |
Summary
- ImTextureID is an opaque
void*handle that ImGui passes through to the rendering backend without interpretation. - The OpenGL backends in
imgui_impl_opengl3.cppexpect the value to cast safely toGLuintvia(GLuint)(intptr_t). - You can pass raw GLuint texture names directly by casting through
intptr_t. - For custom classes, either expose a
GetGLHandle()method and pass the result, or pass the object pointer if you need runtime metadata retrieval. - When using smart pointers, store the raw pointer from
.get()as theImTextureID, ensuring the managed object outlives the ImGui render frame. - Never store complex objects directly in
ImTextureID; the value must fit in a pointer-sized integer and round-trip through theintptr_tconversion without data loss.
Frequently Asked Questions
What is the ImTextureID type in ImGui?
ImTextureID is defined in imgui.h as a void* typedef, serving as an opaque handle that ImGui treats as a black box. The library never dereferences it; instead, it passes the value to the active rendering backend (OpenGL, DirectX, Vulkan, etc.), which interprets the value according to its own graphics API requirements. In OpenGL backends, this value represents a GLuint texture name.
Can I pass a C++ class directly to ImTextureID?
You cannot pass a C++ class instance by value to ImTextureID, but you can pass a pointer to your class instance. Store the address of your texture wrapper object as the ImTextureID using static_cast<ImTextureID>(&myObject), then cast it back to your type when needed. Alternatively, extract the raw GLuint from your class and pass that directly through the required intptr_t cast.
How does the OpenGL backend convert ImTextureID to GLuint?
The OpenGL implementation in backends/imgui_impl_opengl3.cpp extracts the texture ID using the expression (GLuint)(intptr_t)user_texture_id. This two-stage cast first converts the void* to intptr_t (an integer type guaranteed to hold pointer values), then to GLuint. This ensures that integer texture names survive round-trip storage in a pointer-sized value without compiler warnings or data truncation.
Is ImTextureID compatible with other graphics APIs?
Yes. The opaque nature of ImTextureID makes it API-agnostic. While OpenGL uses GLuint texture names, the DirectX backends use ID3D11ShaderResourceView*, Vulkan implementations might use VkImageView handles, and Metal uses MTLTexture pointers. The same ImGui code calling ImGui::Image() works across all backends because each implementation interprets the void* according to its specific graphics API contract.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →