# How to Use ImTextureID with Custom Texture Types in OpenGL: A Complete Guide

> Learn how to use ImTextureID with custom texture types in OpenGL. This guide helps you pass custom texture handles and resource managers by leveraging Dear ImGui's void* value.

- Repository: [omar/imgui](https://github.com/ocornut/imgui)
- Tags: tutorial
- Published: 2026-07-16

---

**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`](https://github.com/ocornut/imgui/blob/main/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`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_opengl3.cpp) (and the legacy [`backends/imgui_impl_opengl2.cpp`](https://github.com/ocornut/imgui/blob/main/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:

```cpp
// 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`](https://github.com/ocornut/imgui/blob/main/examples/example_glfw_opengl3/main.cpp).

```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.

```cpp
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.

```cpp
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

```cpp
// 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

```cpp
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

```cpp
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

```cpp
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`](https://github.com/ocornut/imgui/blob/main/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`](https://github.com/ocornut/imgui/blob/main/backends/imgui_impl_opengl2.cpp) | Implements identical extraction logic for legacy OpenGL 2.x contexts. |
| [`imgui.h`](https://github.com/ocornut/imgui/blob/main/imgui.h) | Defines `ImTextureID` as `void*` and declares `ImGui::Image()`, `ImGui::ImageButton()`, and other texture-consuming functions. |
| [`examples/example_glfw_opengl3/main.cpp`](https://github.com/ocornut/imgui/blob/main/examples/example_glfw_opengl3/main.cpp) | Demonstrates practical texture loading with GLFW/GLAD and proper `ImTextureID` casting. |
| [`examples/example_sdl2_opengl3/main.cpp`](https://github.com/ocornut/imgui/blob/main/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.cpp`](https://github.com/ocornut/imgui/blob/main/imgui_impl_opengl3.cpp) expect the value to cast safely to `GLuint` via `(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 the `ImTextureID`, 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 the `intptr_t` conversion without data loss.

## Frequently Asked Questions

### What is the ImTextureID type in ImGui?

**ImTextureID** is defined in [`imgui.h`](https://github.com/ocornut/imgui/blob/main/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`](https://github.com/ocornut/imgui/blob/main/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.