[ ocornut ](https://github.com/ocornut) / ** [imgui](https://github.com/ocornut/imgui) ** Public

  * ### Uh oh!

There was an error while loading. [Please reload this page](https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples).

  * [ Notifications ](https://github.com/login?return_to=%2Focornut%2Fimgui) You must be signed in to change notification settings
  * [ Fork 11.6k ](https://github.com/login?return_to=%2Focornut%2Fimgui)
  * [ Star  72.2k ](https://github.com/login?return_to=%2Focornut%2Fimgui)

# Image Loading and Displaying Examples

Jump to bottom

omar edited this page Sep 8, 2025 · [68 revisions](https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples/_history)

## Index

  * TL;DR;
  * Rendering your game scene into a texture
  * About filenames
  * About ImTextureId
  * Example for OpenGL users
  * Example for DirectX9 users
  * Example for DirectX11 users
  * Example for DirectX12 users
  * Example for SDL_GPU users
  * Example for SDL_Renderer users
  * Example for Vulkan users
  * Example for WebGPU users
  * About Texture Coordinates
  * Modifying Render State

## TL;DR;

Loading an image file into a GPU texture is outside of the scope of Dear ImGui and has more to do with your Graphics API. Because this is such a recurring issue for Dear ImGui users, we are providing a guide here.

This guide will have us load an image file from disk and display it in a Dear ImGui window.

We will load this image: (Right-click to save as MyImage01.jpg, 20,123 bytes)

This is generally done in two steps:

  * Load image from the disk into RAM. In this example, we'll decompress the image into RGBA a image. You may use helper libraries such as [stb_image.h](https://github.com/nothings/stb/blob/master/stb_image.h) to do this.
  * Load the raw decompressed RGBA image from RAM into a GPU texture. You'll want to use dedicated functions of your graphics API (e.g. OpenGL, DirectX11) to do this.

Once you have an image in GPU texture memory, you can use functions such as `ImGui::Image()` to request Dear ImGui to create a draw command that your Dear ImGui rendering back-end will turn into a draw call.

(Note: Large games and applications are likely to be using texture formats that are compressed on the GPU or more advanced techniques that are outside of the scope of this article. Generally, if you are reading this you shouldn't need to worry or care about that.)

##### Return to Index

## Rendering your game scene into a texture

This guide covers loading an image file from disk and turning it into a GPU texture. Another frequent use of textures is wanting to rendering your application/game scene into a texture and then display that texture inside a Dear ImGui window.

This is not covered by this guide, however you should first read this guide in order to get a better understanding of how texture works. Then you can look up resources about rendering to a texture, e.g.

  * DirectX9: [google search](https://www.google.com/search?q=dx9+render+to+texture), [ID3DXRenderToSurface interface](https://docs.microsoft.com/en-us/windows/win32/direct3d9/id3dxrendertosurface)
  * DirectX11: [google search](https://www.google.com/search?q=dx11+render+to+texture)
  * OpenGL: [google search](https://www.google.com/search?q=opengl+render+to+texture), e.g. [this blog post](https://www.codingwiththomas.com/blog/rendering-an-opengl-framebuffer-into-a-dear-imgui-window)

##### Return to Index

## About filenames

**Please note that many new C/C++ users have issues with their files _because the filename they provide is wrong_.**

Two things to watch for:

  * In C/C++ and most programming languages if you want to use a backslash `\` within a string literal, you need to write it double backslash `\\`. As it happens, Windows uses backslashes as a path separator, so be mindful.

    filename = "C:\MyFiles\MyImage01.jpg"         // This is INCORRECT!!
    filename = "C:\\MyFiles\\MyImage01.jpg"       // This is CORRECT

In some situations, you may also use `/` path separator under Windows.

  * Make sure your IDE/debugger settings starts your executable from the right working directory. In Visual Studio you can change your working directory in project `Properties > General > Debugging > Working Directory`. People assume that their execution will start from the root folder of the project, where by default it oftens start from the folder where object or executable files are stored.

    filename = "MyImage01.jpg";    // Relative filename depends on your Working Directory when running your program!
    filename = "../MyImage01.jpg"; // Load from parent folder

##### Return to Index

## About ImTextureId

From the FAQ: [Q: What are ImTextureID/ImTextureRef?](https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-what-are-imtextureidimtextureref)

Short explanation:

  * You may use functions such as `ImGui::Image()`, `ImGui::ImageButton()` or lower-level `ImDrawList::AddImage()` to emit draw calls that will use your own textures. Using `ImGui::GetBackgroundDrawList()` you may submit AddImage() calls that are not part of a specific ImGui window but displayed between your background contents and ImGui windows.
  * Actual textures are identified in a way that is up to the user/engine. Those identifiers are stored and passed as a `ImTextureID` value (which is no other than an opaque u64).
  * Loading image files from the disk and turning them into a texture is not within the scope of Dear ImGui (for a good reason). You can read documentations or tutorials on your graphics API to understand how to upload textures. Onward in this document you'll find examples.

Long explanation:

  * Dear ImGui's job is to create "meshes", defined in a renderer-agnostic format made of draw commands and vertices. At the end of the frame those meshes (ImDrawList) will be displayed by your rendering function. They are made up of textured polygons and the code to render them is generally fairly short (a few dozen lines). In the examples/ folder we provide functions for popular graphics API (OpenGL, DirectX, etc.).
  * Each rendering function decides on a data type to represent "textures". The concept of what is a "texture" is entirely tied to your underlying engine/graphics API. We carry the information to identify a "texture" in the ImTextureID type. ImTextureID default to ImU64, aka 8 bytes worth of data: just enough to store 1 pointer or 1 integer of your choice (note: before 1.91.3, ImTextureID defaulted to being a void*, which couldn't fit e.g. a 64-bit descriptor in 32-bits builds). Dear ImGui doesn't know or understand what you are storing in ImTextureID, it merely pass ImTextureID values until they reach your rendering function.
  * In the examples/ bindings, for each graphics API binding we decided on a type that is likely to be a good representation for specifying an image from the end-user perspective. This is what the _examples_ rendering functions are using:

    OpenGL:       ImTextureID = GLuint                      (see ImGui_ImplOpenGL3_RenderDrawData()      in imgui_impl_opengl3.cpp)
    DirectX9:     ImTextureID = LPDIRECT3DTEXTURE9          (see ImGui_ImplDX9_RenderDrawData()          in imgui_impl_dx9.cpp)
    DirectX11:    ImTextureID = ID3D11ShaderResourceView*   (see ImGui_ImplDX11_RenderDrawData()         in imgui_impl_dx11.cpp)
    DirectX12:    ImTextureID = D3D12_GPU_DESCRIPTOR_HANDLE (see ImGui_ImplDX12_RenderDrawData()         in imgui_impl_dx12.cpp)
    SDL_Renderer: ImTextureID = SDL_Texture*                (see ImGui_ImplSDLRenderer2_RenderDrawData() in imgui_impl_sdlrenderer2.cpp)
    Vulkan:       ImTextureID = VkDescriptorSet             (see ImGui_ImplVulkan_RenderDrawData()       in imgui_impl_vulkan.cpp)
    WebGPU:       ImTextureID = WGPUTextureView             (see ImGui_ImplWGPU_RenderDrawData()         in imgui_impl_wgpu.cpp)

For example, in the OpenGL example binding we store raw OpenGL texture identifier (GLuint) inside ImTextureID. Whereas in the DirectX11 example binding we store a pointer to ID3D11ShaderResourceView inside ImTextureID, which is a higher-level structure tying together both the texture and information about its format and how to read it.

  * If you have a custom engine built over e.g. OpenGL, instead of passing GLuint around you may decide to use a high-level data type to carry information about the texture as well as how to display it (shaders, etc.). The decision of what to use as ImTextureID can always be made better knowing how your codebase is designed. If your engine has high-level data types for "textures" and "material" then you may want to use them. If you are starting with OpenGL or DirectX or Vulkan and haven't built much of a rendering engine over them, keeping the default ImTextureID representation suggested by the example bindings is probably the best choice. (Advanced users may also decide to keep a low-level type in ImTextureID, and use ImDrawList callback and pass information to their renderer) User code may do:

    // Cast our texture type to ImTextureID
    MyTexture* texture = g_CoffeeTableTexture;
    ImGui::Image((ImTextureID)(intptr_t)texture, ImVec2(texture->Width, texture->Height));

The renderer function called after ImGui::Render() will receive that same value that the user code passed:

    // Cast ImTextureID stored in the draw command as our texture type
    MyTexture* texture = (MyTexture*)(intptr_t)pcmd->TextureId;
    MyEngineBindTexture2D(texture);

Once you understand this design you can begin understand that loading image files and turning them into displayable textures is not within the scope of Dear ImGui. This is by design and is actually a good thing, because it means your code has full control over your data types and how you display them. In reality, the concept of what constitute a "texture" is largely open-ended, and Dear ImGui doesn't want to narrow that concept. If you want to display an image file (e.g. PNG file) into the screen, please refer to tutorials above.

Finally, you may call `ImGui::ShowMetricsWindow()` to explore/visualize/understand how the ImDrawList are generated.

##### Return to Index

## Example for OpenGL users

We will here use [stb_image.h](https://github.com/nothings/stb/blob/master/stb_image.h) to load images from disk.

Add at the top of one of your source file:

    #define _CRT_SECURE_NO_WARNINGS
    #define STB_IMAGE_IMPLEMENTATION
    #include "stb_image.h"

    // Simple helper function to load an image into a OpenGL texture with common settings
    bool LoadTextureFromMemory(const void* data, size_t data_size, GLuint* out_texture, int* out_width, int* out_height)
        // Load from file
        int image_width = 0;
        int image_height = 0;
        unsigned char* image_data = stbi_load_from_memory((const unsigned char*)data, (int)data_size, &image_width, &image_height, NULL, 4);
        if (image_data == NULL)
            return false;

        // Create a OpenGL texture identifier
        GLuint image_texture;
        glGenTextures(1, &image_texture);
        glBindTexture(GL_TEXTURE_2D, image_texture);

        // Setup filtering parameters for display
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

        // Upload pixels into texture
        glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_width, image_height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image_data);
        stbi_image_free(image_data);

        *out_texture = image_texture;
        *out_width = image_width;
        *out_height = image_height;

        return true;

    // Open and read a file, then forward to LoadTextureFromMemory()
    bool LoadTextureFromFile(const char* file_name, GLuint* out_texture, int* out_width, int* out_height)
        FILE* f = fopen(file_name, "rb");
        if (f == NULL)
            return false;
        fseek(f, 0, SEEK_END);
        size_t file_size = (size_t)ftell(f);
        if (file_size == -1)
            return false;
        fseek(f, 0, SEEK_SET);
        void* file_data = IM_ALLOC(file_size);
        fread(file_data, 1, file_size, f);
        fclose(f);
        bool ret = LoadTextureFromMemory(file_data, file_size, out_texture, out_width, out_height);
        IM_FREE(file_data);
        return ret;

Load our texture after initializing OpenGL loader (for example after `glewInit()`):

    int my_image_width = 0;
    int my_image_height = 0;
    GLuint my_image_texture = 0;
    bool ret = LoadTextureFromFile("../../MyImage01.jpg", &my_image_texture, &my_image_width, &my_image_height);
    IM_ASSERT(ret);

In the snippet of code above, we added an assert `IM_ASSERT(ret)` to check if the image file was loaded correctly. You may also use your Debugger and confirm that `my_image_texture` is not zero and that `my_image_width` `my_image_height` are correct.

Now that we have an OpenGL texture and its dimensions, we can display it in our main loop:

    ImGui::Begin("OpenGL Texture Text");
    ImGui::Text("pointer = %x", my_image_texture);
    ImGui::Text("size = %d x %d", my_image_width, my_image_height);
    ImGui::Image((ImTextureID)(intptr_t)my_image_texture, ImVec2(my_image_width, my_image_height));
    ImGui::End();

##### Return to Index

## Example for DirectX9 users

Unlike the majority of modern graphics API, DirectX9 include helper functions to load image files from disk. We are going to use them here, instead of using `stb_image.h`.

Add at the top of one of your source file:

    #include <D3dx9tex.h>
    #pragma comment(lib, "D3dx9")

    // Simple helper function to load an image into a DX9 texture with common settings
    bool LoadTextureFromFile(const char* filename, PDIRECT3DTEXTURE9* out_texture, int* out_width, int* out_height)
        // Load texture from disk
        PDIRECT3DTEXTURE9 texture;
        HRESULT hr = D3DXCreateTextureFromFileA(g_pd3dDevice, filename, &texture);
        if (hr != S_OK)
            return false;

        // Retrieve description of the texture surface so we can access its size
        D3DSURFACE_DESC my_image_desc;
        texture->GetLevelDesc(0, &my_image_desc);
        *out_texture = texture;
        *out_width = (int)my_image_desc.Width;
        *out_height = (int)my_image_desc.Height;
        return true;

At initialization time, load our texture:

    int my_image_width = 0;
    int my_image_height = 0;
    PDIRECT3DTEXTURE9 my_texture = NULL;
    bool ret = LoadTextureFromFile("../../MyImage01.jpg", &my_texture, &my_image_width, &my_image_height);
    IM_ASSERT(ret);

Now that we have an DirectX9 texture and its dimensions, we can display it in our main loop:

    ImGui::Begin("DirectX9 Texture Test");
    ImGui::Text("pointer = %p", my_texture);
    ImGui::Text("size = %d x %d", my_image_width, my_image_height);
    ImGui::Image((ImTextureID)(intptr_t)my_texture, ImVec2(my_image_width, my_image_height));
    ImGui::End();

##### Return to Index

## Example for DirectX11 users

Add at the top of one of your source file:

    #define _CRT_SECURE_NO_WARNINGS
    #define STB_IMAGE_IMPLEMENTATION
    #include "stb_image.h"

    // Simple helper function to load an image into a DX11 texture with common settings
    bool LoadTextureFromMemory(const void* data, size_t data_size, ID3D11ShaderResourceView** out_srv, int* out_width, int* out_height)
        // Load from disk into a raw RGBA buffer
        int image_width = 0;
        int image_height = 0;
        unsigned char* image_data = stbi_load_from_memory((const unsigned char*)data, (int)data_size, &image_width, &image_height, NULL, 4);
        if (image_data == NULL)
            return false;

        // Create texture
        D3D11_TEXTURE2D_DESC desc;
        ZeroMemory(&desc, sizeof(desc));
        desc.Width = image_width;
        desc.Height = image_height;
        desc.MipLevels = 1;
        desc.ArraySize = 1;
        desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        desc.SampleDesc.Count = 1;
        desc.Usage = D3D11_USAGE_DEFAULT;
        desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
        desc.CPUAccessFlags = 0;

        ID3D11Texture2D *pTexture = NULL;
        D3D11_SUBRESOURCE_DATA subResource;
        subResource.pSysMem = image_data;
        subResource.SysMemPitch = desc.Width * 4;
        subResource.SysMemSlicePitch = 0;
        g_pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture);

        // Create texture view
        D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
        ZeroMemory(&srvDesc, sizeof(srvDesc));
        srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MipLevels = desc.MipLevels;
        srvDesc.Texture2D.MostDetailedMip = 0;
        g_pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, out_srv);
        pTexture->Release();

        *out_width = image_width;
        *out_height = image_height;
        stbi_image_free(image_data);

        return true;

    // Open and read a file, then forward to LoadTextureFromMemory()
    bool LoadTextureFromFile(const char* file_name, ID3D11ShaderResourceView** out_srv, int* out_width, int* out_height)
        FILE* f = fopen(file_name, "rb");
        if (f == NULL)
            return false;
        fseek(f, 0, SEEK_END);
        size_t file_size = (size_t)ftell(f);
        if (file_size == -1)
            return false;
        fseek(f, 0, SEEK_SET);
        void* file_data = IM_ALLOC(file_size);
        fread(file_data, 1, file_size, f);
        fclose(f);
        bool ret = LoadTextureFromMemory(file_data, file_size, out_srv, out_width, out_height);
        IM_FREE(file_data);
        return ret;

At initialization time, load our texture:

    int my_image_width = 0;
    int my_image_height = 0;
    ID3D11ShaderResourceView* my_texture = NULL;
    bool ret = LoadTextureFromFile("../../MyImage01.jpg", &my_texture, &my_image_width, &my_image_height);
    IM_ASSERT(ret);

Now that we have an DirectX11 texture and its dimensions, we can display it in our main loop:

    ImGui::Begin("DirectX11 Texture Test");
    ImGui::Text("pointer = %p", my_texture);
    ImGui::Text("size = %d x %d", my_image_width, my_image_height);
    ImGui::Image((ImTextureID)(intptr_t)my_texture, ImVec2(my_image_width, my_image_height));
    ImGui::End();

##### Return to Index

## Example for DirectX12 users

It should be noted that the DirectX12 API is substantially more complex than previous iterations, and assumes that you will be writing your own code to manage various things such as resource allocation. As such, it's a lot harder to provide a "one size fits all" example than with some APIs, and in all probability the code below will need modification to work with any existing engine code. It should however work if inserted into the Dear ImGui DirectX12 example project. Since 1.91.5, the DirectX12 backend was changed to accept a callback for allocating/free SRV descriptor, so this is the responsability of the app. The DirectX12 example creates a free list (see `int APP_SRV_HEAP_SIZE = 64;' near top of the file).

The actual implementation itself is as follows - firstly, at the top of one of your source files add:

    #define _CRT_SECURE_NO_WARNINGS
    #define STB_IMAGE_IMPLEMENTATION
    #include "stb_image.h"

    // Simple helper function to load an image into a DX12 texture with common settings
    // Returns true on success, with the SRV CPU handle having an SRV for the newly-created texture placed in it (srv_cpu_handle must be a handle in a valid descriptor heap)
    bool LoadTextureFromMemory(const void* data, size_t data_size, ID3D12Device* d3d_device, D3D12_CPU_DESCRIPTOR_HANDLE srv_cpu_handle, ID3D12Resource** out_tex_resource, int* out_width, int* out_height)
        // Load from disk into a raw RGBA buffer
        int image_width = 0;
        int image_height = 0;
        unsigned char* image_data = stbi_load_from_memory((const unsigned char*)data, (int)data_size, &image_width, &image_height, NULL, 4);
        if (image_data == NULL)
            return false;

        // Create texture resource
        D3D12_HEAP_PROPERTIES props;
        memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES));
        props.Type = D3D12_HEAP_TYPE_DEFAULT;
        props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
        props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;

        D3D12_RESOURCE_DESC desc;
        ZeroMemory(&desc, sizeof(desc));
        desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
        desc.Alignment = 0;
        desc.Width = image_width;
        desc.Height = image_height;
        desc.DepthOrArraySize = 1;
        desc.MipLevels = 1;
        desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        desc.SampleDesc.Count = 1;
        desc.SampleDesc.Quality = 0;
        desc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
        desc.Flags = D3D12_RESOURCE_FLAG_NONE;

        ID3D12Resource* pTexture = NULL;
        d3d_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc,
            D3D12_RESOURCE_STATE_COPY_DEST, NULL, IID_PPV_ARGS(&pTexture));

        // Create a temporary upload resource to move the data in
        UINT uploadPitch = (image_width * 4 + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u);
        UINT uploadSize = image_height * uploadPitch;
        desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
        desc.Alignment = 0;
        desc.Width = uploadSize;
        desc.Height = 1;
        desc.DepthOrArraySize = 1;
        desc.MipLevels = 1;
        desc.Format = DXGI_FORMAT_UNKNOWN;
        desc.SampleDesc.Count = 1;
        desc.SampleDesc.Quality = 0;
        desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
        desc.Flags = D3D12_RESOURCE_FLAG_NONE;

        props.Type = D3D12_HEAP_TYPE_UPLOAD;
        props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
        props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;

        ID3D12Resource* uploadBuffer = NULL;
        HRESULT hr = d3d_device->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc,
            D3D12_RESOURCE_STATE_GENERIC_READ, NULL, IID_PPV_ARGS(&uploadBuffer));
        IM_ASSERT(SUCCEEDED(hr));

        // Write pixels into the upload resource
        void* mapped = NULL;
        D3D12_RANGE range = { 0, uploadSize };
        hr = uploadBuffer->Map(0, &range, &mapped);
        IM_ASSERT(SUCCEEDED(hr));
        for (int y = 0; y < image_height; y++)
            memcpy((void*)((uintptr_t)mapped + y * uploadPitch), image_data + y * image_width * 4, image_width * 4);
        uploadBuffer->Unmap(0, &range);

        // Copy the upload resource content into the real resource
        D3D12_TEXTURE_COPY_LOCATION srcLocation = {};
        srcLocation.pResource = uploadBuffer;
        srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
        srcLocation.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        srcLocation.PlacedFootprint.Footprint.Width = image_width;
        srcLocation.PlacedFootprint.Footprint.Height = image_height;
        srcLocation.PlacedFootprint.Footprint.Depth = 1;
        srcLocation.PlacedFootprint.Footprint.RowPitch = uploadPitch;

        D3D12_TEXTURE_COPY_LOCATION dstLocation = {};
        dstLocation.pResource = pTexture;
        dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
        dstLocation.SubresourceIndex = 0;

        D3D12_RESOURCE_BARRIER barrier = {};
        barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
        barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
        barrier.Transition.pResource = pTexture;
        barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
        barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_COPY_DEST;
        barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;

        // Create a temporary command queue to do the copy with
        ID3D12Fence* fence = NULL;
        hr = d3d_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence));
        IM_ASSERT(SUCCEEDED(hr));

        HANDLE event = CreateEvent(0, 0, 0, 0);
        IM_ASSERT(event != NULL);

        D3D12_COMMAND_QUEUE_DESC queueDesc = {};
        queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
        queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
        queueDesc.NodeMask = 1;

        ID3D12CommandQueue* cmdQueue = NULL;
        hr = d3d_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&cmdQueue));
        IM_ASSERT(SUCCEEDED(hr));

        ID3D12CommandAllocator* cmdAlloc = NULL;
        hr = d3d_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc));
        IM_ASSERT(SUCCEEDED(hr));

        ID3D12GraphicsCommandList* cmdList = NULL;
        hr = d3d_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, NULL, IID_PPV_ARGS(&cmdList));
        IM_ASSERT(SUCCEEDED(hr));

        cmdList->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, NULL);
        cmdList->ResourceBarrier(1, &barrier);

        hr = cmdList->Close();
        IM_ASSERT(SUCCEEDED(hr));

        // Execute the copy
        cmdQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&cmdList);
        hr = cmdQueue->Signal(fence, 1);
        IM_ASSERT(SUCCEEDED(hr));

        // Wait for everything to complete
        fence->SetEventOnCompletion(1, event);
        WaitForSingleObject(event, INFINITE);

        // Tear down our temporary command queue and release the upload resource
        cmdList->Release();
        cmdAlloc->Release();
        cmdQueue->Release();
        CloseHandle(event);
        fence->Release();
        uploadBuffer->Release();

        // Create a shader resource view for the texture
        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc;
        ZeroMemory(&srvDesc, sizeof(srvDesc));
        srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MipLevels = desc.MipLevels;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        d3d_device->CreateShaderResourceView(pTexture, &srvDesc, srv_cpu_handle);

        // Return results
        *out_tex_resource = pTexture;
        *out_width = image_width;
        *out_height = image_height;
        stbi_image_free(image_data);

        return true;

    // Open and read a file, then forward to LoadTextureFromMemory()
    bool LoadTextureFromFile(const char* file_name, ID3D12Device* d3d_device, D3D12_CPU_DESCRIPTOR_HANDLE srv_cpu_handle, ID3D12Resource** out_tex_resource, int* out_width, int* out_height)
        FILE* f = fopen(file_name, "rb");
        if (f == NULL)
            return false;
        fseek(f, 0, SEEK_END);
        size_t file_size = (size_t)ftell(f);
        if (file_size == -1)
            return false;
        fseek(f, 0, SEEK_SET);
        void* file_data = IM_ALLOC(file_size);
        fread(file_data, 1, file_size, f);
        fclose(f);
        bool ret = LoadTextureFromMemory(file_data, file_size, d3d_device, srv_cpu_handle, out_tex_resource, out_width, out_height);
        IM_FREE(file_data);
        return ret;

    void DestroyTexture(ID3D12Resource** tex_resources)
        (*tex_resources)->Release();
        *tex_resources = NULL;

Then at initialization time, load our texture:

    // We need to pass a D3D12_CPU_DESCRIPTOR_HANDLE in ImTextureID, so make sure it will fit
    static_assert(sizeof(ImTextureID) >= sizeof(D3D12_CPU_DESCRIPTOR_HANDLE), "D3D12_CPU_DESCRIPTOR_HANDLE is too large to fit in an ImTextureID");

    // We presume here that we have our D3D device pointer in g_pd3dDevice

    int my_image_width = 0;
    int my_image_height = 0;
    ID3D12Resource* my_texture = NULL;

    // Get CPU/GPU handles for the shader resource view
    // Normally your engine will have some sort of allocator for these.
    // In our example we use a simple ExampleDescriptorHeapAllocator helper.
    D3D12_CPU_DESCRIPTOR_HANDLE my_texture_srv_cpu_handle;
    D3D12_GPU_DESCRIPTOR_HANDLE my_texture_srv_gpu_handle;
    g_pd3dSrvDescHeapAlloc.Alloc(&my_texture_srv_cpu_handle, &my_texture_srv_gpu_handle);

    // Load the texture from a file
    bool ret = LoadTextureFromFile("../../MyImage01.jpg", g_pd3dDevice, my_texture_srv_cpu_handle, &my_texture, &my_image_width, &my_image_height);
    IM_ASSERT(ret);

And finally now that we have an DirectX12 texture and its dimensions, we can display it in our main loop:

    ImGui::Begin("DirectX12 Texture Test");
    ImGui::Text("CPU handle = %p", my_texture_srv_cpu_handle.ptr);
    ImGui::Text("GPU handle = %p", my_texture_srv_gpu_handle.ptr);
    ImGui::Text("size = %d x %d", my_image_width, my_image_height);
    // Note that we pass the GPU SRV handle here, *not* the CPU handle. We're passing the internal pointer value, cast to an ImTextureID
    ImGui::Image((ImTextureID)my_texture_srv_gpu_handle.ptr, ImVec2((float)my_image_width, (float)my_image_height));
    ImGui::End();

##### Return to Index

## Example for SDL_GPU users

**IMPORTANT** The code below requires 1.92.2 (IMGUI_VERSION_NUM >= 19214) as the type of ImTextureID for SDL_GPU3 backend changed from 'SDL_GPUTextureSamplerBinding*' to 'SDL_GPUTexture*'.

We will here use [stb_image.h](https://github.com/nothings/stb/blob/master/stb_image.h) to load images from disk.

Add at the top of one of your source files:

    #define _CRT_SECURE_NO_WARNINGS
    #define STB_IMAGE_IMPLEMENTATION
    #include "stb_image.h"

    bool LoadTextureFromMemory(const void* data, size_t data_size, SDL_GPUDevice* device, SDL_GPUTexture** out_texture, int* out_width, int* out_height)
        // Load from disk into a raw RGBA buffer
        int image_width = 0;
        int image_height = 0;
        unsigned char* image_data = stbi_load_from_memory((const unsigned char*)data, (int)data_size, &image_width, &image_height, NULL, 4);
        if (image_data == NULL)
            return false;

        // Create texture
        SDL_GPUTextureCreateInfo texture_info = {};
        texture_info.type = SDL_GPU_TEXTURETYPE_2D;
        texture_info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
        texture_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
        texture_info.width = image_width;
        texture_info.height = image_height;
        texture_info.layer_count_or_depth = 1;
        texture_info.num_levels = 1;
        texture_info.sample_count = SDL_GPU_SAMPLECOUNT_1;

        SDL_GPUTexture* texture = SDL_CreateGPUTexture(device, &texture_info);

        // Create transfer buffer
        // FIXME: A real engine would likely keep one around, see what the SDL_GPU backend is doing.
        SDL_GPUTransferBufferCreateInfo transferbuffer_info = {};
        transferbuffer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
        transferbuffer_info.size = image_width * image_height * 4;
        SDL_GPUTransferBuffer* transferbuffer = SDL_CreateGPUTransferBuffer(device, &transferbuffer_info);
        IM_ASSERT(transferbuffer != NULL);

        // Copy to transfer buffer
        uint32_t upload_pitch = image_width * 4;
        void* texture_ptr = SDL_MapGPUTransferBuffer(device, transferbuffer, true);
        for (int y = 0; y < image_height; y++)
            memcpy((void*)((uintptr_t)texture_ptr + y * upload_pitch), image_data + y * upload_pitch, upload_pitch);
        SDL_UnmapGPUTransferBuffer(device, transferbuffer);

        SDL_GPUTextureTransferInfo transfer_info = {};
        transfer_info.offset = 0;
        transfer_info.transfer_buffer = transferbuffer;

        SDL_GPUTextureRegion texture_region = {};
        texture_region.texture = texture;
        texture_region.x = (Uint32)0;
        texture_region.y = (Uint32)0;
        texture_region.w = (Uint32)image_width;
        texture_region.h = (Uint32)image_height;
        texture_region.d = 1;

        // Upload
        SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(device);
        SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd);
        SDL_UploadToGPUTexture(copy_pass, &transfer_info, &texture_region, false);
        SDL_EndGPUCopyPass(copy_pass);
        SDL_SubmitGPUCommandBuffer(cmd);

        SDL_ReleaseGPUTransferBuffer(device, transferbuffer);

        *out_texture = texture;
        *out_width = image_width;
        *out_height = image_height;
        stbi_image_free(image_data);

        return true;

    // Open and read a file, then forward to LoadTextureFromMemory()
    bool LoadTextureFromFile(const char* file_name, SDL_GPUDevice* device, SDL_GPUTexture** out_texture, int* out_width, int* out_height)
        FILE* f = fopen(file_name, "rb");
        if (f == NULL)
            return false;
        fseek(f, 0, SEEK_END);
        size_t file_size = (size_t)ftell(f);
        if (file_size == -1)
            return false;
        fseek(f, 0, SEEK_SET);
        void* file_data = IM_ALLOC(file_size);
        fread(file_data, 1, file_size, f);
        fclose(f);
        bool ret = LoadTextureFromMemory(file_data, file_size, device, out_texture, out_width, out_height);
        IM_FREE(file_data);
        return ret;

    void DestroyTexture(SDL_GPUDevice* device, SDL_GPUTexture* texture)
        SDL_ReleaseGPUTexture(device, texture);

Load our texture after initialising SDL3 and SDL_GPU:

    SDL_GPUTexture* my_texture;
    int my_image_width, my_image_height;
    bool ret = LoadTextureFromFile("MyImage01.jpg", gpu_device, &my_texture, &my_image_width, &my_image_height);
    IM_ASSERT(ret);

Now that we have a SDL_GPUTexture* and its dimensions, we can display it in our main loop:

    ImGui::Begin("SDL_GPU Texture Test");
    ImGui::Text("pointer = %p", &my_texture);
    ImGui::Text("size = %d x %d", my_image_width, my_image_height);
    ImGui::Image((ImTextureID)(intptr_t)my_texture, ImVec2((float)my_image_width, (float)my_image_height));
    ImGui::End();

##### Return to Index

## Example for SDL_Renderer users

We will here use [stb_image.h](https://github.com/nothings/stb/blob/master/stb_image.h) to load images from disk.

SDL_Renderer2: Add at the top of one of your source files:

    #define _CRT_SECURE_NO_WARNINGS
    #define STB_IMAGE_IMPLEMENTATION
    #include "stb_image.h"

    bool LoadTextureFromMemory(const void* data, size_t data_size, SDL_Renderer* renderer, SDL_Texture** out_texture, int* out_width, int* out_height)
        int image_width = 0;
        int image_height = 0;
        int channels = 4;
        unsigned char* image_data = stbi_load_from_memory((const unsigned char*)data, (int)data_size, &image_width, &image_height, NULL, 4);
        if (image_data == nullptr)
            fprintf(stderr, "Failed to load image: %s\n", stbi_failure_reason());
            return false;

        SDL_Surface* surface = SDL_CreateRGBSurfaceFrom((void*)image_data, image_width, image_height, channels * 8, channels * image_width, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000);
        if (surface == nullptr)
            fprintf(stderr, "Failed to create SDL surface: %s\n", SDL_GetError());
            return false;

        SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
        if (texture == nullptr)
            fprintf(stderr, "Failed to create SDL texture: %s\n", SDL_GetError());

        *out_texture = texture;
        *out_width = image_width;
        *out_height = image_height;

        SDL_FreeSurface(surface);
        stbi_image_free(image_data);

        return true;

    // Open and read a file, then forward to LoadTextureFromMemory()
    bool LoadTextureFromFile(const char* file_name, SDL_Renderer* renderer, SDL_Texture** out_texture, int* out_width, int* out_height)
        FILE* f = fopen(file_name, "rb");
        if (f == NULL)
            return false;
        fseek(f, 0, SEEK_END);
        size_t file_size = (size_t)ftell(f);
        if (file_size == -1)
            return false;
        fseek(f, 0, SEEK_SET);
        void* file_data = IM_ALLOC(file_size);
        fread(file_data, 1, file_size, f);
        fclose(f);
        bool ret = LoadTextureFromMemory(file_data, file_size, renderer, out_texture, out_width, out_height);
        IM_FREE(file_data);
        return ret;

SDL_Renderer3: Add at the top of one of your source files:

    #define _CRT_SECURE_NO_WARNINGS
    #define STB_IMAGE_IMPLEMENTATION
    #include "stb_image.h"

    bool LoadTextureFromMemory(const void* data, size_t data_size, SDL_Renderer* renderer, SDL_Texture** out_texture, int* out_width, int* out_height)
        int image_width = 0;
        int image_height = 0;
        int channels = 4;
        unsigned char* image_data = stbi_load_from_memory((const unsigned char*)data, (int)data_size, &image_width, &image_height, NULL, 4);
        if (image_data == nullptr)
            fprintf(stderr, "Failed to load image: %s\n", stbi_failure_reason());
            return false;

        SDL_Surface* surface = SDL_CreateSurfaceFrom(image_width, image_height, SDL_PIXELFORMAT_RGBA32, (void*)image_data, channels * image_width);
        if (surface == nullptr)
            fprintf(stderr, "Failed to create SDL surface: %s\n", SDL_GetError());
            return false;

        SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
        if (texture == nullptr)
            fprintf(stderr, "Failed to create SDL texture: %s\n", SDL_GetError());

        *out_texture = texture;
        *out_width = image_width;
        *out_height = image_height;

        SDL_DestroySurface(surface);
        stbi_image_free(image_data);

        return true;

    // Open and read a file, then forward to LoadTextureFromMemory()
    bool LoadTextureFromFile(const char* file_name, SDL_Renderer* renderer, SDL_Texture** out_texture, int* out_width, int* out_height)
        FILE* f = fopen(file_name, "rb");
        if (f == NULL)
            return false;
        fseek(f, 0, SEEK_END);
        size_t file_size = (size_t)ftell(f);
        if (file_size == -1)
            return false;
        fseek(f, 0, SEEK_SET);
        void* file_data = IM_ALLOC(file_size);
        fread(file_data, 1, file_size, f);
        fclose(f);
        bool ret = LoadTextureFromMemory(file_data, file_size, renderer, out_texture, out_width, out_height);
        IM_FREE(file_data);
        return ret;

Load our texture after initialising SDL:

    SDL_Texture* my_texture;
    int my_image_width, my_image_height;
    bool ret = LoadTextureFromFile("MyImage01.jpg", renderer, &my_texture, &my_image_width, &my_image_height);
    IM_ASSERT(ret);

Now that we have a SDL_Texture and its dimensions, we can display it in our main loop:

    ImGui::Begin("SDL_Renderer Texture Test");
    ImGui::Text("pointer = %p", my_texture);
    ImGui::Text("size = %d x %d", my_image_width, my_image_height);
    ImGui::Image((ImTextureID)(intptr_t)my_texture, ImVec2((float)my_image_width, (float)my_image_height));
    ImGui::End();

##### Return to Index

## Example for Vulkan users

We will here use [stb_image.h](https://github.com/nothings/stb/blob/master/stb_image.h) to load images from disk.

Note that since 2023/07/29, our examples Vulkan application are creating minimum-sized descriptor pools. You'll need to increase pool sizes in examples's main.cpp to fit extra descriptors.

**THIS IS ONE WAY TO DO THIS AMONG MANY, and provided for informational purpose. Unfortunately due to the nature of Vulkan, it is not really possible to provide a function that will work in all setups and codebases.**

Add at the top of the Vulkan example main.cpp file after the `check_vk_result()` function:

    #define STB_IMAGE_IMPLEMENTATION
    #include "stb_image.h"

    // A struct to manage data related to one image in vulkan
    struct MyTextureData
        VkDescriptorSet DS;         // Descriptor set: this is what you'll pass to Image()
        int             Width;
        int             Height;
        int             Channels;

        // Need to keep track of these to properly cleanup
        VkImageView     ImageView;
        VkImage         Image;
        VkDeviceMemory  ImageMemory;
        VkSampler       Sampler;
        VkBuffer        UploadBuffer;
        VkDeviceMemory  UploadBufferMemory;

        MyTextureData() { memset(this, 0, sizeof(*this)); }
    };

    // Helper function to find Vulkan memory type bits. See ImGui_ImplVulkan_MemoryType() in imgui_impl_vulkan.cpp
    uint32_t findMemoryType(uint32_t type_filter, VkMemoryPropertyFlags properties)
        VkPhysicalDeviceMemoryProperties mem_properties;
        vkGetPhysicalDeviceMemoryProperties(g_PhysicalDevice, &mem_properties);

        for (uint32_t i = 0; i < mem_properties.memoryTypeCount; i++)
            if ((type_filter & (1 << i)) && (mem_properties.memoryTypes[i].propertyFlags & properties) == properties)
                return i;

        return 0xFFFFFFFF; // Unable to find memoryType

    // Helper function to load an image with common settings and return a MyTextureData with a VkDescriptorSet as a sort of Vulkan pointer
    bool LoadTextureFromFile(const char* filename, MyTextureData* tex_data)
        // Specifying 4 channels forces stb to load the image in RGBA which is an easy format for Vulkan
        tex_data->Channels = 4;
        unsigned char* image_data = stbi_load(filename, &tex_data->Width, &tex_data->Height, 0, tex_data->Channels);

        if (image_data == NULL)
            return false;

        // Calculate allocation size (in number of bytes)
        size_t image_size = tex_data->Width * tex_data->Height * tex_data->Channels;

        VkResult err;

        // Create the Vulkan image.
            VkImageCreateInfo info = {};
            info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
            info.imageType = VK_IMAGE_TYPE_2D;
            info.format = VK_FORMAT_R8G8B8A8_UNORM;
            info.extent.width = tex_data->Width;
            info.extent.height = tex_data->Height;
            info.extent.depth = 1;
            info.mipLevels = 1;
            info.arrayLayers = 1;
            info.samples = VK_SAMPLE_COUNT_1_BIT;
            info.tiling = VK_IMAGE_TILING_OPTIMAL;
            info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
            info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
            info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
            err = vkCreateImage(g_Device, &info, g_Allocator, &tex_data->Image);
            check_vk_result(err);
            VkMemoryRequirements req;
            vkGetImageMemoryRequirements(g_Device, tex_data->Image, &req);
            VkMemoryAllocateInfo alloc_info = {};
            alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
            alloc_info.allocationSize = req.size;
            alloc_info.memoryTypeIndex = findMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
            err = vkAllocateMemory(g_Device, &alloc_info, g_Allocator, &tex_data->ImageMemory);
            check_vk_result(err);
            err = vkBindImageMemory(g_Device, tex_data->Image, tex_data->ImageMemory, 0);
            check_vk_result(err);

        // Create the Image View
            VkImageViewCreateInfo info = {};
            info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
            info.image = tex_data->Image;
            info.viewType = VK_IMAGE_VIEW_TYPE_2D;
            info.format = VK_FORMAT_R8G8B8A8_UNORM;
            info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
            info.subresourceRange.levelCount = 1;
            info.subresourceRange.layerCount = 1;
            err = vkCreateImageView(g_Device, &info, g_Allocator, &tex_data->ImageView);
            check_vk_result(err);

        // Create Sampler
            VkSamplerCreateInfo sampler_info{};
            sampler_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
            sampler_info.magFilter = VK_FILTER_LINEAR;
            sampler_info.minFilter = VK_FILTER_LINEAR;
            sampler_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
            sampler_info.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; // outside image bounds just use border color
            sampler_info.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
            sampler_info.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
            sampler_info.minLod = -1000;
            sampler_info.maxLod = 1000;
            sampler_info.maxAnisotropy = 1.0f;
            err = vkCreateSampler(g_Device, &sampler_info, g_Allocator, &tex_data->Sampler);
            check_vk_result(err);

        // Create Descriptor Set using ImGUI's implementation
        tex_data->DS = ImGui_ImplVulkan_AddTexture(tex_data->Sampler, tex_data->ImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);

        // Create Upload Buffer
            VkBufferCreateInfo buffer_info = {};
            buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
            buffer_info.size = image_size;
            buffer_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
            buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
            err = vkCreateBuffer(g_Device, &buffer_info, g_Allocator, &tex_data->UploadBuffer);
            check_vk_result(err);
            VkMemoryRequirements req;
            vkGetBufferMemoryRequirements(g_Device, tex_data->UploadBuffer, &req);
            VkMemoryAllocateInfo alloc_info = {};
            alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
            alloc_info.allocationSize = req.size;
            alloc_info.memoryTypeIndex = findMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT);
            err = vkAllocateMemory(g_Device, &alloc_info, g_Allocator, &tex_data->UploadBufferMemory);
            check_vk_result(err);
            err = vkBindBufferMemory(g_Device, tex_data->UploadBuffer, tex_data->UploadBufferMemory, 0);
            check_vk_result(err);

        // Upload to Buffer:
            void* map = NULL;
            err = vkMapMemory(g_Device, tex_data->UploadBufferMemory, 0, image_size, 0, &map);
            check_vk_result(err);
            memcpy(map, image_data, image_size);
            VkMappedMemoryRange range[1] = {};
            range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
            range[0].memory = tex_data->UploadBufferMemory;
            range[0].size = image_size;
            err = vkFlushMappedMemoryRanges(g_Device, 1, range);
            check_vk_result(err);
            vkUnmapMemory(g_Device, tex_data->UploadBufferMemory);

        // Release image memory using stb
        stbi_image_free(image_data);

        // Create a command buffer that will perform following steps when hit in the command queue.
        // TODO: this works in the example, but may need input if this is an acceptable way to access the pool/create the command buffer.
        VkCommandPool command_pool = g_MainWindowData.Frames[g_MainWindowData.FrameIndex].CommandPool;
        VkCommandBuffer command_buffer;
            VkCommandBufferAllocateInfo alloc_info{};
            alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
            alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
            alloc_info.commandPool = command_pool;
            alloc_info.commandBufferCount = 1;

            err = vkAllocateCommandBuffers(g_Device, &alloc_info, &command_buffer);
            check_vk_result(err);

            VkCommandBufferBeginInfo begin_info = {};
            begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
            begin_info.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
            err = vkBeginCommandBuffer(command_buffer, &begin_info);
            check_vk_result(err);

        // Copy to Image
            VkImageMemoryBarrier copy_barrier[1] = {};
            copy_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
            copy_barrier[0].dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
            copy_barrier[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
            copy_barrier[0].newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
            copy_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
            copy_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
            copy_barrier[0].image = tex_data->Image;
            copy_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
            copy_barrier[0].subresourceRange.levelCount = 1;
            copy_barrier[0].subresourceRange.layerCount = 1;
            vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, copy_barrier);

            VkBufferImageCopy region = {};
            region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
            region.imageSubresource.layerCount = 1;
            region.imageExtent.width = tex_data->Width;
            region.imageExtent.height = tex_data->Height;
            region.imageExtent.depth = 1;
            vkCmdCopyBufferToImage(command_buffer, tex_data->UploadBuffer, tex_data->Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);

            VkImageMemoryBarrier use_barrier[1] = {};
            use_barrier[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
            use_barrier[0].srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
            use_barrier[0].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
            use_barrier[0].oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
            use_barrier[0].newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
            use_barrier[0].srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
            use_barrier[0].dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
            use_barrier[0].image = tex_data->Image;
            use_barrier[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
            use_barrier[0].subresourceRange.levelCount = 1;
            use_barrier[0].subresourceRange.layerCount = 1;
            vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, use_barrier);

        // End command buffer
            VkSubmitInfo end_info = {};
            end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
            end_info.commandBufferCount = 1;
            end_info.pCommandBuffers = &command_buffer;
            err = vkEndCommandBuffer(command_buffer);
            check_vk_result(err);
            err = vkQueueSubmit(g_Queue, 1, &end_info, VK_NULL_HANDLE);
            check_vk_result(err);
            err = vkDeviceWaitIdle(g_Device);
            check_vk_result(err);

        return true;

    // Helper function to cleanup an image loaded with LoadTextureFromFile
    void RemoveTexture(MyTextureData* tex_data)
        vkFreeMemory(g_Device, tex_data->UploadBufferMemory, nullptr);
        vkDestroyBuffer(g_Device, tex_data->UploadBuffer, nullptr);
        vkDestroySampler(g_Device, tex_data->Sampler, nullptr);
        vkDestroyImageView(g_Device, tex_data->ImageView, nullptr);
        vkDestroyImage(g_Device, tex_data->Image, nullptr);
        vkFreeMemory(g_Device, tex_data->ImageMemory, nullptr);
        ImGui_ImplVulkan_RemoveTexture(tex_data->DS);

Load our texture after initializing Vulkan loader (for example after `ImGui_ImplVulkan_Init()`):

    MyTextureData my_texture;
    bool ret = LoadTextureFromFile("../../MyImage01.jpg", &my_texture);
    IM_ASSERT(ret);

In the snippet of code above, we added an assert `IM_ASSERT(ret)` to check if the image file was loaded correctly. You may also use your Debugger and confirm that `my_image_texture` is not zero and that `my_image_texture.Width` `my_image_texture.Height` are correct.

Now that we have an Vulkan texture and its dimensions, we can display it in our main loop:

    ImGui::Begin("Vulkan Texture Test");
    ImGui::Text("pointer = %p", my_texture.DS);
    ImGui::Text("size = %d x %d", my_texture.Width, my_texture.Height);
    ImGui::Image((ImTextureID)my_texture.DS, ImVec2(my_texture.Width, my_texture.Height));
    ImGui::End();

In the cleanup stage remember to call the RemoveTexture function to avoid leaks, and angry Vulkan Validation Layers! Call it before `ImGui_ImplVulkan_Shutdown()`

    RemoveTexture(&my_texture);

##### Return to Index

## Example for WebGPU users

We will here use [stb_image.h](https://github.com/nothings/stb/blob/master/stb_image.h) to load images from disk.

Add at the top of one of your source file:

    #define _CRT_SECURE_NO_WARNINGS
    #define STB_IMAGE_IMPLEMENTATION
    #include "stb_image.h"

    // Simple helper function to load an image into a WebGPU texture with common settings
    bool LoadTextureFromMemory(const void* data, size_t data_size, WGPUDevice device, WGPUQueue queue, WGPUTexture* out_texture, WGPUTextureView* out_texture_view, int* out_width, int* out_height)
        // Load image
        int image_width = 0;
        int image_height = 0;
        // Ask stbi to output 4 channels since WebGPU doesn't have 3-channels texture format
        const int output_channels = 4;
        void* const image_data = stbi_load_from_memory((const unsigned char *)data, (int)data_size, &image_width, &image_height, NULL, output_channels);
        if (image_data == NULL)
            return false;

        // Create a WebGPU texture
        WGPUTextureDescriptor texture_desc;
        texture_desc.nextInChain = NULL;
        texture_desc.label = NULL;
        texture_desc.usage = WGPUTextureUsage_CopyDst | WGPUTextureUsage_TextureBinding;
        texture_desc.dimension = WGPUTextureDimension_2D;
        texture_desc.size.width = (uint32_t)image_width;
        texture_desc.size.height = (uint32_t)image_height;
        texture_desc.size.depthOrArrayLayers = 1;
        texture_desc.format = WGPUTextureFormat_RGBA8Unorm;
        texture_desc.mipLevelCount = 1;
        texture_desc.sampleCount = 1;
        texture_desc.viewFormatCount = 0;
        texture_desc.viewFormats = NULL;
        WGPUTexture image_texture = wgpuDeviceCreateTexture(device, &texture_desc);

        // Write loaded data to texture
        WGPUImageCopyTexture dest;
        dest.nextInChain = NULL;
        dest.texture = image_texture;
        dest.mipLevel = 0;
        dest.origin.x = 0;
        dest.origin.y = 0;
        dest.origin.z = 0;
        dest.aspect = WGPUTextureAspect_All;

        WGPUTextureDataLayout src;
        src.nextInChain = NULL;
        src.offset = 0;
        src.bytesPerRow = (uint32_t)(output_channels * image_width);
        src.rowsPerImage = (uint32_t)image_height;

        const size_t bytes = src.bytesPerRow * src.rowsPerImage;
        WGPUExtent3D write_size;
        write_size.width = (uint32_t)image_width;
        write_size.height = (uint32_t)image_height;
        write_size.depthOrArrayLayers = 1;
        wgpuQueueWriteTexture(queue, &dest, image_data, bytes, &src, &write_size);

        // Create a WebGPU texture view
        WGPUTextureViewDescriptor view_desc;
        view_desc.nextInChain = NULL;
        view_desc.label = NULL;
        view_desc.format = WGPUTextureFormat_RGBA8Unorm;
        view_desc.dimension = WGPUTextureViewDimension_2D;
        view_desc.baseMipLevel = 0;
        view_desc.mipLevelCount = 1;
        view_desc.baseArrayLayer = 0;
        view_desc.arrayLayerCount = 1;
        view_desc.aspect = WGPUTextureAspect_All;

        *out_texture = image_texture;
        *out_texture_view = wgpuTextureCreateView(image_texture, &view_desc);
        *out_width = image_width;
        *out_height = image_height;

        return true;

    // Open and read a file, then forward to LoadTextureFromMemory()
    bool LoadTextureFromFile(const char* file_name, WGPUDevice device, WGPUQueue queue, WGPUTexture* out_texture, WGPUTextureView* out_texture_view, int* out_width, int* out_height)
        FILE * f = fopen(file_name, "rb");
        if (f == NULL)
            return false;
        fseek(f, 0, SEEK_END);
        long file_size = ftell(f);
        if (file_size == -1)
            return false;
        fseek(f, 0, SEEK_SET);
        void * file_data = IM_ALLOC(file_size);
        fread(file_data, 1, (size_t)file_size, f);
        fclose(f);
        bool ret = LoadTextureFromMemory(file_data, (size_t)file_size, device, queue, out_texture, out_texture_view, out_width, out_height);
        IM_FREE(file_data);
        return ret;

After initializing a `WGPUDevice` and a `WGPUQueue`, load our texture:

    WGPUDevice device = /* ... */;
    WGPUQueue queue = /* ... */;

    WGPUTexture my_texture = NULL;
    WGPUTextureView my_texture_view = NULL;
    int my_image_width = 0;
    int my_image_height = 0;
    bool ret = LoadTextureFromFile("../../MyImage01.jpg", device, queue, &my_texture, &my_texture_view, &my_image_width, &my_image_height);
    IM_ASSERT(ret);

In the snippet of code above, we added an assert `IM_ASSERT(ret)` to check if the image file was loaded correctly. You may also use your Debugger and confirm that `my_image_texture` is not zero and that `my_image_width` `my_image_height` are correct.

Now that we have a WebGPU texture view and its dimensions, we can display it in our main loop:

    ImGui::Begin("WebGPU Texture Test");
    ImGui::Text("pointer = %p", my_texture_view);
    ImGui::Text("size = %d x %d", my_image_width, my_image_height);
    ImGui::Image((ImTextureID)(intptr_t)my_texture_view, ImVec2(my_image_width, my_image_height));
    ImGui::End();

Don't forget to clean the memory at the end of the program:

    wgpuTextureViewRelease(my_texture_view);
    wgpuTextureRelease(my_texture);

##### Return to Index

## About Texture Coordinates

See e.g. <http://wiki.polycount.com/wiki/Texture_Coordinates>

For the purpose of this section we use "Texture Coordinates" and "UV Coordinates" interchangeably.

The `ImGui::Image()` and `ImDrawList::AddImage()` functions allow you to pass "UV coordinates" corresponding to the upper-left and bottom-right portion of the texture you want to display. Using the default values, respectively `(0.0f, 0.0f)` and `(1.0f, 1.0f)` for those coordinates allow you to display the entire underlying texture. UV coordinates are traditionally normalized coordinates, meaning that for each axis, instead of counting a number of texels, we address a location in the texture using a number from 0.0f to 1.0f. So (0.0f, 0.0f) is generally addressing the upper-left section of the texture and (1.0f, 1.0f) is addressing the lower-right corner of the texture. Some graphics systems may use coordinates that are inverted on the Y axis.

    ImGui::Image((ImTextureID)(intptr_t)my_texture, ImVec2(256, 256), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f)); // Default coordinates
    ImGui::Text("Flip Y coordinates:");
    ImGui::Image((ImTextureID)(intptr_t)my_texture, ImVec2(256, 256), ImVec2(0.0f, 1.0f), ImVec2(1.0f, 0.0f)); // Flip Y coordinates

If you want to display part of a texture, say display a 100x200 rectangle stored from pixel (10,10) to pixel (110,210) out of a 256x256 texture, you will need to calculate the normalized coordinates of those pixels:

    // Normalized coordinates of pixel (10,10) in a 256x256 texture.
    ImVec2 uv0 = ImVec2(10.0f/256.0f, 10.0f/256.0f);

    // Normalized coordinates of pixel (110,210) in a 256x256 texture.
    ImVec2 uv1 = ImVec2((10.0f+100.0f)/256.0f, (10.0f+200.0f)/256.0f);

    ImGui::Text("uv0 = (%f, %f)", uv0.x, uv0.y);
    ImGui::Text("uv1 = (%f, %f)", uv1.x, uv1.y);

    // Display the 100x200 section starting at (10,10)
    ImGui::Image((ImTextureID)(intptr_t)my_image_texture, ImVec2(100.0f, 200.0f), uv0, uv1);

Same code written differently, with named variables:

    ImVec2 display_min = ImVec2(10.0f, 10.0f);
    ImVec2 display_size = ImVec2(100.0f, 200.0f);
    ImVec2 texture_size = ImVec2(256.0f, 256.0f);

    // Normalized coordinates of pixel (10,10) in a 256x256 texture.
    ImVec2 uv0 = ImVec2(display_min.x / texture_size.x, display_min.y / texture_size.y);

    // Normalized coordinates of pixel (110,210) in a 256x256 texture.
    ImVec2 uv1 = ImVec2((display_min.x + display_size.x) / texture_size.x, (display_min.y + display_size.y) / texture_size.y);

    ImGui::Text("uv0 = (%f, %f)", uv0.x, uv0.y);
    ImGui::Text("uv1 = (%f, %f)", uv1.x, uv1.y);

    // Display the 100x200 section starting at (10,10)
    ImGui::Image((ImTextureID)(intptr_t)my_image_texture, ImVec2(display_size.x, display_size.y), uv0, uv1);

You can look up "texture coordinates" from other resources such as your favorite search engine, or graphics tutorials. Tip: map your UV coordinates to widgets (using `SliderFloat2` or `DragFloat2`) so you can manipulate them in real-time and better understand the meaning of those values.

If you want to display the same image but scaled, keep the same UV coordinates but alter the Size:

    // Normal size
    ImGui::Image((ImTextureID)(intptr_t)my_image_texture, ImVec2(my_image_width, my_image_height), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f));

    // Half size, same contents
    ImGui::Image((ImTextureID)(intptr_t)my_image_texture, ImVec2(my_image_width*0.5f, my_image_height*0.5f), ImVec2(0.0f, 0.0f), ImVec2(1.0f, 1.0f));

## Modifying Render State

You can use draw callbacks to manipulate the render state, e.g. alter sampling. Since 1.91.3, backends are now exposing some of their state during the render loop, which should make it easier to leverage callbacks. Refer to the .h file of each backend to see if a `ImGui_ImplXXXX_RenderState` struct is exposed.
Access them with e.g. `ImGui_ImplDX11_RenderState* state = (ImGui_ImplDX11_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState;`.

    // For DX11 backend: Callback to modify current sampler
    void ImDrawCallback_ImplDX11_SetSampler(const ImDrawList* parent_list, const ImDrawCmd* cmd)
        ImGui_ImplDX11_RenderState* state = (ImGui_ImplDX11_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState;
        ID3D11SamplerState* sampler = cmd->UserCallbackData ? (ID3D11SamplerState*)cmd->UserCallbackData : state->SamplerDefault;
        state->DeviceContext->PSSetSamplers(0, 1, &sampler);

    // For DX11 backend: Create a Point sampler
        D3D11_SAMPLER_DESC desc;
        ZeroMemory(&desc, sizeof(desc));
        desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
        desc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
        desc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
        desc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
        desc.MipLODBias = 0.f;
        desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
        desc.MinLOD = 0.f;
        desc.MaxLOD = 0.f;
        g_pd3dDevice->CreateSamplerState(&desc, &g_SamplerPoint);

    // Usage
    ImGui::GetWindowDrawList()->AddCallback(ImDrawCallback_ImplDX11_SetSampler, g_SamplerPoint); // Set custom sampler
    ImGui::Image((ImTextureID)(intptr_t)my_texture, ImVec2((float)my_image_width * 4, (float)my_image_height * 4));
    ImGui::GetWindowDrawList()->AddCallback(ImDrawCallback_ImplDX11_SetSampler, NULL); // Restore sampler

This may be easier or harder to use for some backends. Don't hesitate to open issues to discuss your needs and so we can improve the feature.

    // For SDLGPU3 backend: Callback to modify current sampler
    void ImDrawCallback_ImplSDLGPU3_SetSampler(const ImDrawList* parent_list, const ImDrawCmd* cmd)
        ImGui_ImplSDLGPU3_RenderState* state = (ImGui_ImplSDLGPU3_RenderState*)ImGui::GetPlatformIO().Renderer_RenderState;
        SDL_GPUSampler* sampler = cmd->UserCallbackData ? (SDL_GPUSampler*)cmd->UserCallbackData : state->SamplerDefault;
        state->SamplerCurrent = sampler;

##### Return to Index

### Clone this wiki locally


[ ocornut ](https://github.com/ocornut) / ** [imgui](https://github.com/ocornut/imgui) ** Public

  * ### Uh oh!

There was an error while loading. [Please reload this page](https://github.com/ocornut/imgui/wiki/Developer-Tips).

  * [ Notifications ](https://github.com/login?return_to=%2Focornut%2Fimgui) You must be signed in to change notification settings
  * [ Fork 11.6k ](https://github.com/login?return_to=%2Focornut%2Fimgui)
  * [ Star  72.2k ](https://github.com/login?return_to=%2Focornut%2Fimgui)

# Developer Tips

Jump to bottom

omar edited this page Sep 8, 2023 · [17 revisions](https://github.com/ocornut/imgui/wiki/Developer-Tips/_history)

Tips for when working on Dear ImGui codebase.

Also see [Tips](https://github.com/ocornut/imgui/wiki/Tips), [Debug Tools](https://github.com/ocornut/imgui/wiki/Debug-Tools).

### Automation and regression testing system

Get yourself familiarized with running the [Dear ImGui Test Suite](https://github.com/ocornut/imgui_test_engine). You can use it to:

  * catch regression and contract changes.
  * understand when some code is exercised or which condition may be leading to a given state. e.g. "can this value ever be NULL?": add a break-point or assert and run the Test Suite, you are likely to find out!

Making a change and running the Test Suite is a good way to understand possible side-effects of that change.

When fixing bugs or adjusting some esoteric features, adding a new test case is generally useful.

### Metrics

The Metrics window exposes lots of information about the library state. See [Debug Tools](https://github.com/ocornut/imgui/wiki/Debug-Tools).

### Logging

You can use the `IMGUI_DEBUG_LOG_XXX` macros (declared in `imgui_internal.h`) to easily print text to console and in 'Debug Log' while including the current frame counter, which is very often useful in log.

### Using breakpoints

Using debugger breakpoints can be tedious in an interactive application dealing with lots of data. Even more so as the state of the application may be so reliant on mouse and keyboard inputs. One convenient trick is filter breakpoint based on custom conditions, e.g checking for the Alt key modifier to be pressed:

    if (ImGui::GetIO().KeyAlt)
        printf(""); // Set a debugger breakpoint here!

So you can setup your UI state for debugging (open windows, mouse position, active action etc.) and then only when you press ALT your breakpoint will trigger.

### Continuous integration

Branches pushed publicly will have [build actions](https://github.com/ocornut/imgui/actions) run on them.

### Using Natvis file for Visual Studio debugging

The `misc/natvis/imgui.natvis` file may be included in your project to provide support for Dear ImGui types in the debugger (e.g. expanding of `ImVector<>` arrays).

### Using static analysis with PVS-Studio:

I am using [PVS-Studio](https://www.viva64.com/en/pvs-studio/) (Viva64 had kindly provided a license to use with free software products) for static code analysis and it's been extremely helpful in reducing the amount of errors or confusing code.

_run_pvs_studio.bat_ :

    @echo off
    set PVS_DIR=C:\Program Files (x86)\PVS-Studio
    set WORK_DIR=C:\Work\imgui_dev\pvs_studio
    set PROJ_DIR=C:\Work\imgui\examples\example_win32_directx11
    set PROJ_NAME=example_win32_directx11
    "%PVS_DIR%\PVS-Studio_Cmd.exe" -r -t "%PROJ_DIR%\%PROJ_NAME%.vcxproj"
    mkdir "%WORK_DIR%\output"
    "%PVS_DIR%\PlogConverter.exe" -a GA:1,2;OP:1 -t Html,FullHtml,Txt,Totals "%PROJ_DIR%\%PROJ_NAME%.plog" -o "%WORK_DIR%\output"
    del "%PROJ_DIR%\%PROJ_NAME%.plog"
    echo ---- Totals:
    type "%WORK_DIR%\output\%PROJ_NAME%.plog_totals.txt"
    start "" "%WORK_DIR%\output\fullhtml\index.html"

### Clone this wiki locally


[ ocornut ](https://github.com/ocornut) / ** [imgui](https://github.com/ocornut/imgui) ** Public

  * ### Uh oh!

There was an error while loading. [Please reload this page](https://github.com/ocornut/imgui/wiki/Debug-Tools).

  * [ Notifications ](https://github.com/login?return_to=%2Focornut%2Fimgui) You must be signed in to change notification settings
  * [ Fork 11.6k ](https://github.com/login?return_to=%2Focornut%2Fimgui)
  * [ Star  72.2k ](https://github.com/login?return_to=%2Focornut%2Fimgui)

# Debug Tools

Jump to bottom

omar edited this page Dec 16, 2025 · [20 revisions](https://github.com/ocornut/imgui/wiki/Debug-Tools/_history)

## Index

  * Stack recovery / Error handling
  * Highlight ID Conflicts
  * Item Picker
  * Metrics/Debugger window
  * Debug Log
  * Debug Configuration Flags
  * Debug Break Buttons
  * ID Stack Tool
  * UTF-8 Encoding Viewer

## Error Handling & Stack Mismatch recovery

See [Error Handling](https://github.com/ocornut/imgui/wiki/Error-Handling) page.

## Highlight ID Conflicts

Highlight and show an error message when multiple items have conflicting identifiers. Set `io.ConfigDebugHighlightIdConflicts = true` or toggle via `Demo -> Tools -> Highlight ID Conflicts`.

The highlight and popup will only show when hovering item, due to technical/performance reason.

(with `#define IMGUI_DEBUG_HIGHLIGHT_ALL_ID_CONFLICTS` at compile-time you can enable highlighting items **before** hovering them, but this will make everything in Dear ImGui costlier/slower and should only be enabled temporarily. Active efforts will be made to prevent you from keeping this enabled.)

## Item Picker

<https://github.com/ocornut/imgui/issues/2673>

The Item Picker will allow you to pick an item with the mouse and have Dear ImGui break within the call-stack of that item. This is useful if you have large UI / codebase and you would to easily find out where some UI item is emitted. You can find it in _Demo >Tools>ItemPicker_* or _Metrics >Tools>Item Picker_ or expose it in your own UI by calling `ImGui::DebugStartItemPicker()`.See [#2673](https://github.com/ocornut/imgui/issues/2673) for more details.

**(*) The Item Picker shortcut from 'Demo->Tools->Item Picker' is disabled until your code set `io.ConfigDebugIsDebuggerPresent = true`. This is in order to avoid accidental use by non-debugger users. It is always available from Demo->Tools->Metrics regardless of that setting.**

## Metrics/Debugger window

Access the Metrics/Debugger window via `Demo->Tools->Metrics/Debugger` or by calling `ShowMetricsWindow()` from your code.

Many internal state and tools are exposed in the Metrics window. They will help you understand how Dear ImGui works, and can help you diagnose many problems.

_Some of the debug tools_

## Debug Log

Access the Debug Log window via `Demo->Tools->Debug Log` or `Metrics->Tools->Debug Log` or by calling `ShowDebugLogWindow()`. Also see [#5855](https://github.com/ocornut/imgui/issues/5855).

**Dear ImGui carefully sends categorized events which you can filter with the provided checkboxes. **You may also call IMGUI_DEBUG_LOG() to submit unfiltered events. USE WITH CAUTION, THE LAST THING YOU WANT IS TO ADD LOG SPAM IN YOUR CODEBASE.

It has options to enable logging of variety of events. Useful e.g.:

  * You have issue with focus or active id being taken away.
  * You have issue with popup closing.
  * You have issue with windows being undocked.
  * You want to visualize submitted input events.
  * You want to visualize clipper steps etc.
  * etc.

Inside the log, if you hover an ImGuiID identifier (formatted as `0xXXXXXXXX`) it will automatically attempt to visually locate the item if the item still exists:

If `io.ConfigDebugIsDebuggerPresent` is enabled, an additional tooltip will appear after some time:

## Debug Configuration Flags

Runtime flags available in `ImGuiIO` (and exposed in Demo->Configuration):

    // Option to enable various debug tools showing buttons that will call the IM_DEBUG_BREAK() macro.
    // - The Item Picker tool will be available regardless of this being enabled, in order to maximize its discoverability.
    // - Requires a debugger being attached, otherwise IM_DEBUG_BREAK() options will appear to crash your application.
    //   e.g. io.ConfigDebugIsDebuggerPresent = ::IsDebuggerPresent() on Win32, or refer to ImOsIsDebuggerPresent() imgui_test_engine/imgui_te_utils.cpp for a Unix compatible version).
    bool ConfigDebugIsDebuggerPresent;   // = false          // Enable various tools calling IM_DEBUG_BREAK().

    // Tools to detect code submitting items with conflicting/duplicate IDs
    // - Code should use PushID()/PopID() in loops, or append "##xx" to same-label identifiers.
    // - Empty label e.g. Button("") == same ID as parent widget/node. Use Button("##xx") instead!
    // - See FAQ https://github.com/ocornut/imgui/blob/master/docs/FAQ.md#q-about-the-id-stack-system
    bool ConfigDebugHighlightIdConflicts;// = true           // Highlight and show an error message when multiple items have conflicting identifiers.

    // Tools to test correct Begin/End and BeginChild/EndChild behaviors.
    // Presently Begin()/End() and BeginChild()/EndChild() needs to ALWAYS be called in tandem, regardless of return value of BeginXXX()
    // This is inconsistent with other BeginXXX functions and create confusion for many users.
    // We expect to update the API eventually. In the meanwhile we provide tools to facilitate checking user-code behavior.
    bool ConfigDebugBeginReturnValueOnce; // First-time calls to Begin()/BeginChild() will return false. NEEDS TO BE SET AT APPLICATION BOOT TIME if you don't want to miss windows.
    bool ConfigDebugBeginReturnValueLoop; // Some calls to Begin()/BeginChild() will return false. Will cycle through window depths then repeat. Suggested use: add "io.ConfigDebugBeginReturnValue = io.KeyShift" in your main loop then occasionally press SHIFT. Windows should be flickering while running.

    // Option to deactivate io.AddFocusEvent(false) handling. May facilitate interactions with a debugger when focus loss leads to clearing inputs data.
    // Backends may have other side-effects on focus loss, so this will reduce side-effects but not necessary remove all of them.
    bool ConfigDebugIgnoreFocusLoss;      // Ignore io.AddFocusEvent(false), consequently not calling io.ClearInputKeys() in input processing.

    // Options to audit .ini data
    bool ConfigDebugIniSettings;          // Save .ini data with extra comments (particularly helpful for Docking, but makes saving slower)

## Debug Break Buttons

When `io.ConfigDebugIsDebuggerPresent` is enabled, various `**DebugBreak**` buttons will appears in debug tools. Clicking them will attempt to break you in debugger in the desired location:

  * Request a debug break in a Begin() call.
  * Request a debug break in a ItemAdd() call via debug log and hovering 0xXXXXXX identifiers.
  * Request a debug break in a BeginTable() call.
  * Request a debug break in a SetShortcutRouting()/Shortcut() call. [Internal]

In the Debug Log you can also hover a 0xXXXXXXXX identifier and press the `Pause/Break` keyboard key to attempt to break in the ItemAdd() function for this item.

## ID Stack Tool

<https://github.com/ocornut/imgui/issues/4631>

    ImGui::ShowIdStackToolWindow();

## UTF-8 Encoding Viewer

See <https://github.com/ocornut/imgui/blob/master/docs/FONTS.md#about-utf-8-encoding>

    ImGui::SeparatorText("CORRECT");
    ImGui::DebugTextEncoding(u8"こんにちは");

    ImGui::SeparatorText("INCORRECT");
    ImGui::DebugTextEncoding("こんにちは");

Also see [Tips](https://github.com/ocornut/imgui/wiki/Tips) page.

### Clone this wiki locally


[ ocornut ](https://github.com/ocornut) / ** [imgui](https://github.com/ocornut/imgui) ** Public

  * ### Uh oh!

There was an error while loading. [Please reload this page](https://github.com/ocornut/imgui/wiki/Multi-Select).

  * [ Notifications ](https://github.com/login?return_to=%2Focornut%2Fimgui) You must be signed in to change notification settings
  * [ Fork 11.6k ](https://github.com/login?return_to=%2Focornut%2Fimgui)
  * [ Star  72.2k ](https://github.com/login?return_to=%2Focornut%2Fimgui)

# Multi Select

Jump to bottom

omar edited this page Mar 23, 2026 · [16 revisions](https://github.com/ocornut/imgui/wiki/Multi-Select/_history)

_The feature has been merged into master on July 18, 2024 (version 1.91.0)_

### Index

  * Overview
  * Features
  * Demo Code
  * Using ImGuiSelectionBasicStorage helper
  * Main API
  * About ImGuiSelectionUserData
  * Using ImGuiSelectionExternalStorage helper
  * Using multi-select with trees

### Overview

  * **This system implements standard multi-selection idioms** (CTRL+Mouse/Keyboard, SHIFT+Mouse/Keyboard, etc) and supports a clipper being used. Handling this manually and correctly is tricky, this is why we provide the functionality. If you don't need SHIFT+Mouse/Keyboard range-select + clipping, you could technically implement a simple form of multi-selection yourself, by reacting to click/presses on Selectable() items.
  * Selectable(), Checkbox() are supported but custom widgets may use it as well.
  * TreeNode() is technically supported but... using this correctly is more complicated: you need some sort of linear/random access to your tree, which is suited to advanced trees setups also implementing filters and clipper. We will work toward simplifying and demoing it.
  * **In the spirit of Dear ImGui design, your code owns actual selection data**. This is designed to allow all kinds of selection storage you may use in your application e.g. external selection (set/map/hash), intrusive selection (bool inside your objects) etc.
  * The work involved to deal with multi-selection differs whether you want to only submit visible items and clip others, or submit all items regardless of their visibility. Clipping items is more efficient and will allow you to deal with large lists (1k~100k items). See "Usage flow" section below for details. If you are not sure, always start without clipping! You can work your way to the optimized version afterwards.

### Features

  * Design allows all item data and selection data to be fully owned by user. Agnostic to storage type.
  * Support CTRL+Click
  * Support Shift+Click
  * Support mouse box-selection (with scrolling).
  * Compatible with keyboard navigation, incl CTRL+Arrow, SHIFT+Arrows but also naturally works with PageUp/PageDown, Home/End etc.
  * Compatible with ImGuiListClipper.
  * Compatible with drag and drop idioms.
  * `ImGuiSelectionBasicStorage` helper used by demos and for quick-start/convenience. Advanced users may bypass it.
  * `ImGuiSelectionExternalStorage` helper used to easily wire multi-select to existing randomly accessible storage.

### Demo Code

Always refer to demo code for usage. Demos are in `Demo->Widgets->Selection State & Multi-Select` and `Demo->Examples->Assets Browser`.

### Using ImGuiSelectionBasicStorage helper

💡 `ImGuiSelectionBasicStorage` is an optional helper to store multi-selection state + apply multi-selection requests.

  * Used by our demos and provided as a convenience to easily implement basic multi-selection.
  * USING THIS IS NOT MANDATORY. This is only a helper and not a required API.

Minimum pseudo-code example using this helper:

    static vector<MyItem> items;                  // Your items
    static ImGuiSelectionBasicStorage selection;  // Your selection
    selection.UserData = (void*)&items;           // Setup adapter so selection.ApplyRequests() function can convert indexes to identifiers.
    selection.AdapterIndexToStorageId = [](ImGuiSelectionBasicStorage* self, int idx) { return ((vector<MyItem>*)self->UserData))[idx].ID; };

    ImGuiMultiSelectIO* ms_io = ImGui::BeginMultiSelect(ImGuiMultiSelectFlags_None, selection.Size, items.Size);
    selection.ApplyRequests(ms_io);
    for (int idx = 0; idx < items.Size; idx++)
        bool item_is_selected = selection.Contains(items[idx].ID);
        ImGui::SetNextItemSelectionUserData(idx);
        ImGui::Selectable(label, item_is_selected);
    ms_io = ImGui::EndMultiSelect();
    selection.ApplyRequests(ms_io);

To store a multi-selection, in your real application you could:

  * A) Use this helper as a convenience. We use our simple key->value ImGuiStorage as a std::set replacement.
  * B) Use your own external storage: e.g. std::set, std::vector, interval trees, etc.
  * C) Use intrusively stored selection (e.g. 'bool IsSelected' inside objects). Not recommended because you can't have multiple views over same objects. Also some features requires to provide selection _size_ , which with this strategy requires additional work.

Our BeginMultiSelect() api/system doesn't make assumption about:

  * how you want to identify items in multi-selection API? (Indices or Custom Ids or Pointers? Indices are better: easy to iterate/interpolate)
  * how you want to store persistent selection data? (Indices or Custom Ids or Pointers? Custom Ids is better: as selection can persist)

In ImGuiSelectionBasicStorage we:

  * always use indices in the multi-selection API (passed to SetNextItemSelectionUserData(), retrieved in ImGuiMultiSelectIO)
  * use the AdapterIndexToStorageId() indirection layer to abstract how persistent selection data is derived from an index.
  * in some cases we use Index as custom identifier (default implementation returns Index cast as Identifier): only valid for a never changing item list.
  * in some cases we read an ID from some custom item data structure (better, and closer to what you would do in your codebase)

Many combinations are possible depending on how you prefer to store your items and how you prefer to store your selection. When your application settles on a choice, you may want to get rid of this indirection layer and do your own thing.

(In theory, for maximum abstraction, this class could contains AdapterIndexToUserData() and AdapterUserDataToIndex() functions as well, but because we mostly use indices in SetNextItemSelectionUserData(), we omit those indirection for clarity.)

### Main API

💡 This is the low-level API. When using the `ImGuiSelectionBasicStorage` you may not need to care about details of `ImGuiMultiSelectIO` and `ImGuiSelectionRequest`.

    // Flags for BeginMultiSelect()
    enum ImGuiMultiSelectFlags_
        ImGuiMultiSelectFlags_None                  = 0,
        ImGuiMultiSelectFlags_SingleSelect          = 1 << 0,   // Disable selecting more than one item. This is available to allow single-selection code to share same code/logic if desired. It essentially disables the main purpose of BeginMultiSelect() tho!
        ImGuiMultiSelectFlags_NoSelectAll           = 1 << 1,   // Disable CTRL+A shortcut to select all.
        ImGuiMultiSelectFlags_NoRangeSelect         = 1 << 2,   // Disable Shift+selection mouse/keyboard support (useful for unordered 2D selection). With BoxSelect is also ensure contiguous SetRange requests are not combined into one. This allows not handling interpolation in SetRange requests.
        ImGuiMultiSelectFlags_NoAutoSelect          = 1 << 3,   // Disable selecting items when navigating (useful for e.g. supporting range-select in a list of checkboxes)
        ImGuiMultiSelectFlags_NoAutoClear           = 1 << 4,   // Disable clearing selection when navigating or selecting another one (generally used with ImGuiMultiSelectFlags_NoAutoSelect. useful for e.g. supporting range-select in a list of checkboxes)
        ImGuiMultiSelectFlags_NoAutoClearOnReselect = 1 << 5,   // Disable clearing selection when clicking/selecting an already selected item
        ImGuiMultiSelectFlags_BoxSelect1d           = 1 << 6,   // Enable box-selection with same width and same x pos items (e.g. only full row Selectable()). Box-selection works better with little bit of spacing between items hit-box in order to be able to aim at empty space.
        ImGuiMultiSelectFlags_BoxSelect2d           = 1 << 7,   // Enable box-selection with varying width or varying x pos items support (e.g. different width labels, or 2D layout/grid). This is slower: alters clipping logic so that e.g. horizontal movements will update selection of normally clipped items.
        ImGuiMultiSelectFlags_BoxSelectNoScroll     = 1 << 8,   // Disable scrolling when box-selecting near edges of scope.
        ImGuiMultiSelectFlags_ClearOnEscape         = 1 << 9,   // Clear selection when pressing Escape while scope is focused.
        ImGuiMultiSelectFlags_ClearOnClickVoid      = 1 << 10,  // Clear selection when clicking on empty location within scope.
        ImGuiMultiSelectFlags_ScopeWindow           = 1 << 11,  // Scope for _BoxSelect and _ClearOnClickVoid is whole window (Default). Use if BeginMultiSelect() covers a whole window or used a single time in same window.
        ImGuiMultiSelectFlags_ScopeRect             = 1 << 12,  // Scope for _BoxSelect and _ClearOnClickVoid is rectangle encompassing BeginMultiSelect()/EndMultiSelect(). Use if BeginMultiSelect() is called multiple times in same window.
        ImGuiMultiSelectFlags_SelectOnAuto          = 1 << 13,  // Apply selection on mouse down when clicking on unselected item, on mouse up when clicking on selected item. (Default)
        ImGuiMultiSelectFlags_SelectOnClickAlways   = 1 << 14,  // Apply selection on mouse down when clicking on any items. Prevents Drag and Drop from being used on multiple-selection, but allows e.g. BoxSelect to always reselect even when clicking inside an existing selection. (Excel style behavior)
        ImGuiMultiSelectFlags_SelectOnClickRelease  = 1 << 15,  // Apply selection on mouse release when clicking an unselected item. Allow dragging an unselected item without altering selection.
        ImGuiMultiSelectFlags_NavWrapX              = 1 << 16,  // [Temporary] Enable navigation wrapping on X axis. Provided as a convenience because we don't have a design for the general Nav API for this yet. When the more general feature be public we may obsolete this flag in favor of new one.
        ImGuiMultiSelectFlags_NoSelectOnRightClick  = 1 << 17,  // Disable default right-click processing, which selects item on mouse down, and is designed for context-menus.
    };

    // Main API
    ImGuiMultiSelectIO*   BeginMultiSelect(ImGuiMultiSelectFlags flags, int selection_size = -1, int items_count = -1);
    ImGuiMultiSelectIO*   EndMultiSelect();
    void                  SetNextItemSelectionUserData(ImGuiSelectionUserData selection_user_data);

    // Main IO structure returned by BeginMultiSelect()/EndMultiSelect().
    // This mainly contains a list of selection requests.
    // - Use 'Demo->Tools->Debug Log->Selection' to see requests as they happen.
    // - Some fields are only useful if your list is dynamic and allows deletion (getting post-deletion focus/state right is shown in the demo)
    // - Below: who reads/writes each fields? 'r'=read, 'w'=write, 'ms'=multi-select code, 'app'=application/user code.
    struct ImGuiMultiSelectIO
        //------------------------------------------// BeginMultiSelect / EndMultiSelect
        ImVector<ImGuiSelectionRequest> Requests;   //  ms:w, app:r     /  ms:w  app:r   // Requests to apply to your selection data.
        ImGuiSelectionUserData      RangeSrcItem;   //  ms:w  app:r     /                // (If using clipper) Begin: Source item (generally the first selected item when multi-selecting, which is used as a reference point) must never be clipped!
        ImGuiSelectionUserData      NavIdItem;      //  ms:w, app:r     /                // (If using deletion) Last known SetNextItemSelectionUserData() value for NavId (if part of submitted items).
        bool                        NavIdSelected;  //  ms:w, app:r     /        app:r   // (If using deletion) Last known selection state for NavId (if part of submitted items).
        bool                        RangeSrcReset;  //        app:w     /  ms:r          // (If using deletion) Set before EndMultiSelect() to reset ResetSrcItem (e.g. if deleted selection).
        int                         ItemsCount;     //  ms:w, app:r     /        app:r   // 'int items_count' parameter to BeginMultiSelect() is copied here for convenience, allowing simpler calls to your ApplyRequests handler. Not used internally.
    };

    // Selection request item
    struct ImGuiSelectionRequest
        //------------------------------------------// BeginMultiSelect / EndMultiSelect
        ImGuiSelectionRequestType   Type;           //  ms:w, app:r     /  ms:w, app:r   // Request type. You'll most often receive 1 Clear + 1 SetRange with a single-item range.
        bool                        Selected;       //                  /  ms:w, app:r   // Parameter for SetAll/SetRange request (true = select, false = unselect)
        ImS8                        RangeDirection; //                  /  ms:w  app:r   // Parameter for SetRange request: +1 when RangeFirstItem comes before RangeLastItem, -1 otherwise. Useful if you want to preserve selection order on a backward Shift+Click.
        ImGuiSelectionUserData      RangeFirstItem; //                  /  ms:w, app:r   // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from top to bottom)
        ImGuiSelectionUserData      RangeLastItem;  //                  /  ms:w, app:r   // Parameter for SetRange request (this is generally == RangeSrcItem when shift selecting from bottom to top)
    };

    // Selection request type
    enum ImGuiSelectionRequestType
        ImGuiSelectionRequestType_None = 0,
        ImGuiSelectionRequestType_SetAll,           // Request app to clear selection (if Selected==false) or select all items (if Selected==true)
        ImGuiSelectionRequestType_SetRange,         // Request app to select/unselect [RangeFirstItem..RangeLastItem] items (inclusive) based on value of Selected. Only EndMultiSelect() request this, app code can read after BeginMultiSelect() and it will always be false.
    };

TL;DR;

  * Identify submitted items with `SetNextItemSelectionUserData()`, most likely using an index into your current data-set.
  * Store and maintain actual selection data using persistent object identifiers.
  * Usage Flow:
    * (1) Call `BeginMultiSelect()` and retrieve the `ImGuiMultiSelectIO*` result.
    * (2) [If using clipper] Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 6.
    * (3) [If using clipper] You need to make sure RangeSrcItem is always submitted.
      * Calculate its index and pass to `clipper.IncludeItemByIndex()`.
      * If storing indices in `ImGuiSelectionUserData`, a simple `clipper.IncludeItemByIndex(ms_io->RangeSrcItem)` call will work.
    * (4) Submit your items with `SetNextItemSelectionUserData()` \+ `Selectable()`/`TreeNode()` calls.
    * (5) Call `EndMultiSelect()` and retrieve the `ImGuiMultiSelectIO*` result.
    * (6) Honor request list (SetAll/SetRange requests) by updating your selection data. Same code as Step 2.
    * If you submit all items (no clipper), Step 2 and 3 are optional and will be handled by each item themselves. It is fine to always honor those steps.

### About ImGuiSelectionUserData

  * For each item is it submitted by your call to `SetNextItemSelectionUserData()`.
  * This can store an application-defined identifier (e.g. index or pointer).
  * In return we store them into RangeSrcItem/RangeFirstItem/RangeLastItem and other fields in `ImGuiMultiSelectIO`.
  * Most applications will store an object INDEX, hence the chosen name and type. Storing an integer index is the easiest thing to do, as SetRange requests will give you two end-points and you will need to iterate/interpolate between them to update your selection.
  * However it is perfectly possible to store a POINTER or another IDENTIFIER inside this value! Our system never assume that you identify items by indices, it never attempts to interpolate between two values.
  * As most users will want to store an index, for convenience and to reduce confusion we use ImS64 instead of void*, being syntactically easier to downcast. Feel free to reinterpret_cast and store a pointer inside.
  * If you need to wrap this API for another language/framework, feel free to expose this as 'int' if simpler.

### Using ImGuiSelectionExternalStorage helper

Optional helper to apply multi-selection requests to existing randomly accessible storage. Convenient if you want to quickly wire multi-select API on e.g. items storing their own selection state, or an array of bools.

### Using multi-select with trees

We do provide a demo, however, **THIS IS CURRENTLY NOT SIMPLE**.
Next version after 1.91, I aim to provide better idioms and examples for dealing with trees.

### Clone this wiki locally


[ ocornut ](https://github.com/ocornut) / ** [imgui](https://github.com/ocornut/imgui) ** Public

  * ### Uh oh!

There was an error while loading. [Please reload this page](https://github.com/ocornut/imgui/wiki/Getting-Started/_history).

  * [ Notifications ](https://github.com/login?return_to=%2Focornut%2Fimgui) You must be signed in to change notification settings
  * [ Fork 11.6k ](https://github.com/login?return_to=%2Focornut%2Fimgui)
  * [ Star  72.2k ](https://github.com/login?return_to=%2Focornut%2Fimgui)

# [History](https://github.com/ocornut/imgui/wiki/_history) / Getting Started

## Revisions

Compare revisions

  * imgui_manual -> imgui_explorer

[ ocornut ](https://github.com/ocornut) committed Mar 2, 2026

[cfeaa73](https://github.com/ocornut/imgui/wiki/Getting-Started/cfeaa73a1e448a7be2db6a17b965d003465aef39)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed Feb 18, 2026

[04ceedf](https://github.com/ocornut/imgui/wiki/Getting-Started/04ceedf1648dc767abee5e9bd96040f68c777886)

  * What is a Game Loop?

[ ocornut ](https://github.com/ocornut) committed Feb 18, 2026

[c001585](https://github.com/ocornut/imgui/wiki/Getting-Started/c0015859c47a7abd87edb8e6c155fbeb519a3428)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed Feb 18, 2026

[7877c34](https://github.com/ocornut/imgui/wiki/Getting-Started/7877c349b664ac2d1d56560fdd278725ede3fcb1)

  * Checking Out section

[ ocornut ](https://github.com/ocornut) committed Feb 18, 2026

[229ee53](https://github.com/ocornut/imgui/wiki/Getting-Started/229ee539c733f06fb93f8440606eef5e4b01a2bb)

  * Fixed GLFW+Metal path

[ ocornut ](https://github.com/ocornut) committed Feb 10, 2026

[36d083e](https://github.com/ocornut/imgui/wiki/Getting-Started/36d083eb9a96a2bcc3d675f28dd00ff8b17eb6e0)

  * Tweaks + add reference to C++20 imgui-module

[ ocornut ](https://github.com/ocornut) committed Nov 26, 2025

[3aa43bc](https://github.com/ocornut/imgui/wiki/Getting-Started/3aa43bcbac021d4b7a77098257caeb03a57dafb3)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed Oct 30, 2025

[f8c243d](https://github.com/ocornut/imgui/wiki/Getting-Started/f8c243d1e63bb142673403a3b1be29776a06630b)

  * Added note about feedback + event handling

[ ocornut ](https://github.com/ocornut) committed Oct 30, 2025

[7a7d1f1](https://github.com/ocornut/imgui/wiki/Getting-Started/7a7d1f1b5e62f7b72c853bb5a70d8adc41b1b2e9)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed Aug 22, 2025

[8ecd79c](https://github.com/ocornut/imgui/wiki/Getting-Started/8ecd79c6328edf4ef5e7bdcbe8539591a284b1bf)

  * Added SDL3+SDL_GPU3 example.

[ ocornut ](https://github.com/ocornut) committed Aug 20, 2025

[92e768a](https://github.com/ocornut/imgui/wiki/Getting-Started/92e768ae83ffc2ba9c9df00d1d476488c0551ab1)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed Aug 14, 2025

[3605277](https://github.com/ocornut/imgui/wiki/Getting-Started/3605277ebe73d7e82a420350289359b74a78e729)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed Jul 23, 2025

[4fb7782](https://github.com/ocornut/imgui/wiki/Getting-Started/4fb7782e7a736ba0b461a513c5a4c7a694ba072e)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed Jul 23, 2025

[5f19b30](https://github.com/ocornut/imgui/wiki/Getting-Started/5f19b30dc81e36834b92e002766ae2bda136f7b9)

  * Add SDL3

[ ocornut ](https://github.com/ocornut) committed Feb 4, 2025

[ff77ca4](https://github.com/ocornut/imgui/wiki/Getting-Started/ff77ca470903063dd0512482c746ee2c47d2c62a)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed Jan 10, 2025

[d6cbe4d](https://github.com/ocornut/imgui/wiki/Getting-Started/d6cbe4dc030617f246406269473f427cdab75a05)

  * Update DirectX12 example

[ ocornut ](https://github.com/ocornut) committed Nov 20, 2024

[cee2e70](https://github.com/ocornut/imgui/wiki/Getting-Started/cee2e708425b4afa0fc954dc4c0f712a083519cd)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed Nov 3, 2024

[ff6a3ca](https://github.com/ocornut/imgui/wiki/Getting-Started/ff6a3ca52e6d6399377b3e1a10cd90b0c24ba22e)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed Oct 29, 2024

[ef83b50](https://github.com/ocornut/imgui/wiki/Getting-Started/ef83b506fd03e225272a716573bdbf39275f2832)

  * Amend section 5 (#7953)

[ ocornut ](https://github.com/ocornut) committed Sep 4, 2024

[0db00a7](https://github.com/ocornut/imgui/wiki/Getting-Started/0db00a798d1d8f3aeca5a97f9291b0d7ba4d0bee)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed Aug 20, 2024

[7e1d554](https://github.com/ocornut/imgui/wiki/Getting-Started/7e1d5547801bd6d3b849449f3e0a3ff2d32d3ab8)

  * Updated Getting Started (markdown)

[ Exp1iots ](https://github.com/Exp1iots) committed Jul 3, 2024

[d98df47](https://github.com/ocornut/imgui/wiki/Getting-Started/d98df47af5772a9b327e45540a890ef28f4bcc29)

  * Updated Getting Started (markdown)

[ Exp1iots ](https://github.com/Exp1iots) committed Jul 3, 2024

[48ebf38](https://github.com/ocornut/imgui/wiki/Getting-Started/48ebf384996981df6d2258e8dde8939601336ba7)

  * Updated Getting Started (markdown)

[ Exp1iots ](https://github.com/Exp1iots) committed Jul 3, 2024

[d2e82b3](https://github.com/ocornut/imgui/wiki/Getting-Started/d2e82b304f854f99bd460a7a5943ca18372a626d)

  * Fix - "misc/debugger/" is "misc/debuggers/" in the source tree

[ bprb ](https://github.com/bprb) committed May 28, 2024

[833cad9](https://github.com/ocornut/imgui/wiki/Getting-Started/833cad99ee4ed1e29e339baf194044813d68e8ae)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed May 14, 2024

[a358434](https://github.com/ocornut/imgui/wiki/Getting-Started/a35843418df399a16c2861262b7ebea98fc2f2b5)

  * If you use this code verbatim in OpenGL it won't work properly - the context needs backing up & restoring. Signposting to help users.

[ Yann4 ](https://github.com/Yann4) committed Dec 20, 2023

[b9ddd1c](https://github.com/ocornut/imgui/wiki/Getting-Started/b9ddd1cd431a91165d4a7d5f1dbbe0181e41a6f1)

  * Corrected a small typo.

[ GitGeddes ](https://github.com/GitGeddes) committed Nov 7, 2023

[50cbe82](https://github.com/ocornut/imgui/wiki/Getting-Started/50cbe82f51eaec12f03fc653da56bb1c424f3a63)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed Sep 15, 2023

[63699b5](https://github.com/ocornut/imgui/wiki/Getting-Started/63699b5d5046a0f742df317e107ca89f3256da8d)

  * Updated Getting Started (markdown)

[ ocornut ](https://github.com/ocornut) committed Aug 23, 2023

[16760b0](https://github.com/ocornut/imgui/wiki/Getting-Started/16760b0764172f5beec1d7050839cc52105964f9)