update Dear ImGui to 1.92.0, part 1

This commit is contained in:
tildearrow 2025-08-11 20:40:50 -05:00
parent e757ccec55
commit 11ecbebcdc
52 changed files with 7788 additions and 3871 deletions

View file

@ -2,7 +2,8 @@
// (Info: Allegro 5 is a cross-platform general purpose library for handling windows, inputs, graphics, etc.) // (Info: Allegro 5 is a cross-platform general purpose library for handling windows, inputs, graphics, etc.)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'ALLEGRO_BITMAP*' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'ALLEGRO_BITMAP*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy ALLEGRO_KEY_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy ALLEGRO_KEY_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Clipboard support (from Allegro 5.1.12). // [X] Platform: Clipboard support (from Allegro 5.1.12).
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
@ -21,6 +22,7 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLGPU3_CreateFontsTexture() and ImGui_ImplSDLGPU3_DestroyFontsTexture().
// 2025-02-18: Added ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress mouse cursor support. // 2025-02-18: Added ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress mouse cursor support.
// 2025-01-06: Avoid calling al_set_mouse_cursor() repeatedly since it appears to leak on on X11 (#8256). // 2025-01-06: Avoid calling al_set_mouse_cursor() repeatedly since it appears to leak on on X11 (#8256).
// 2024-08-22: moved some OS/backend related function pointers from ImGuiIO to ImGuiPlatformIO: // 2024-08-22: moved some OS/backend related function pointers from ImGuiIO to ImGuiPlatformIO:
@ -138,6 +140,13 @@ void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data)
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return; return;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplAllegro5_UpdateTexture(tex);
// Backup Allegro state that will be modified // Backup Allegro state that will be modified
ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
ALLEGRO_TRANSFORM last_transform = *al_get_current_transform(); ALLEGRO_TRANSFORM last_transform = *al_get_current_transform();
@ -233,43 +242,7 @@ void ImGui_ImplAllegro5_RenderDrawData(ImDrawData* draw_data)
bool ImGui_ImplAllegro5_CreateDeviceObjects() bool ImGui_ImplAllegro5_CreateDeviceObjects()
{ {
// Build texture atlas
ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
ImGuiIO& io = ImGui::GetIO();
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
// Create texture
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
int flags = al_get_new_bitmap_flags();
int fmt = al_get_new_bitmap_format();
al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP | ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR);
al_set_new_bitmap_format(ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE);
ALLEGRO_BITMAP* img = al_create_bitmap(width, height);
al_set_new_bitmap_flags(flags);
al_set_new_bitmap_format(fmt);
if (!img)
return false;
ALLEGRO_LOCKED_REGION* locked_img = al_lock_bitmap(img, al_get_bitmap_format(img), ALLEGRO_LOCK_WRITEONLY);
if (!locked_img)
{
al_destroy_bitmap(img);
return false;
}
memcpy(locked_img->data, pixels, sizeof(int) * width * height);
al_unlock_bitmap(img);
// Convert software texture to hardware texture.
ALLEGRO_BITMAP* cloned_img = al_clone_bitmap(img);
al_destroy_bitmap(img);
if (!cloned_img)
return false;
// Store our identifier
io.Fonts->SetTexID((ImTextureID)(intptr_t)cloned_img);
bd->Texture = cloned_img;
// Create an invisible mouse cursor // Create an invisible mouse cursor
// Because al_hide_mouse_cursor() seems to mess up with the actual inputs.. // Because al_hide_mouse_cursor() seems to mess up with the actual inputs..
@ -280,16 +253,81 @@ bool ImGui_ImplAllegro5_CreateDeviceObjects()
return true; return true;
} }
void ImGui_ImplAllegro5_UpdateTexture(ImTextureData* tex)
{
if (tex->Status == ImTextureStatus_WantCreate)
{
// Create and upload new texture to graphics system
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
// Create texture
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
const int new_bitmap_flags = al_get_new_bitmap_flags();
int new_bitmap_format = al_get_new_bitmap_format();
al_set_new_bitmap_flags(ALLEGRO_MEMORY_BITMAP | ALLEGRO_MIN_LINEAR | ALLEGRO_MAG_LINEAR);
al_set_new_bitmap_format(ALLEGRO_PIXEL_FORMAT_ABGR_8888_LE);
ALLEGRO_BITMAP* cpu_bitmap = al_create_bitmap(tex->Width, tex->Height);
al_set_new_bitmap_flags(new_bitmap_flags);
al_set_new_bitmap_format(new_bitmap_format);
IM_ASSERT(cpu_bitmap != nullptr && "Backend failed to create texture!");
// Upload pixels
ALLEGRO_LOCKED_REGION* locked_region = al_lock_bitmap(cpu_bitmap, al_get_bitmap_format(cpu_bitmap), ALLEGRO_LOCK_WRITEONLY);
IM_ASSERT(locked_region != nullptr && "Backend failed to create texture!");
memcpy(locked_region->data, tex->GetPixels(), tex->GetSizeInBytes());
al_unlock_bitmap(cpu_bitmap);
// Convert software texture to hardware texture.
ALLEGRO_BITMAP* gpu_bitmap = al_clone_bitmap(cpu_bitmap);
al_destroy_bitmap(cpu_bitmap);
IM_ASSERT(gpu_bitmap != nullptr && "Backend failed to create texture!");
// Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)gpu_bitmap);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantUpdates)
{
// Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
ImTextureRect r_bb = tex->UpdateRect; // Bounding box encompassing all individual updates
ALLEGRO_BITMAP* gpu_bitmap = (ALLEGRO_BITMAP*)(intptr_t)tex->TexID;
ALLEGRO_LOCKED_REGION* locked_region = al_lock_bitmap_region(gpu_bitmap, r_bb.x, r_bb.y, r_bb.w, r_bb.h, al_get_bitmap_format(gpu_bitmap), ALLEGRO_LOCK_WRITEONLY);
IM_ASSERT(locked_region && "Backend failed to update texture!");
for (ImTextureRect& r : tex->Updates)
for (int y = 0; y < r.h; y++)
memcpy((unsigned char*)locked_region->data + locked_region->pitch * (r.y - r_bb.y + y) + (r.x - r_bb.x) * tex->BytesPerPixel, // dst
tex->GetPixelsAt(r.x, r.y + y), r.w * tex->BytesPerPixel); // src, block pitch
al_unlock_bitmap(gpu_bitmap);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{
ALLEGRO_BITMAP* backend_tex = (ALLEGRO_BITMAP*)(intptr_t)tex->TexID;
if (backend_tex)
al_destroy_bitmap(backend_tex);
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
}
}
void ImGui_ImplAllegro5_InvalidateDeviceObjects() void ImGui_ImplAllegro5_InvalidateDeviceObjects()
{ {
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
if (bd->Texture)
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
{ {
io.Fonts->SetTexID(0); tex->SetStatus(ImTextureStatus_WantDestroy);
al_destroy_bitmap(bd->Texture); ImGui_ImplAllegro5_UpdateTexture(tex);
bd->Texture = nullptr;
} }
// Destroy mouse cursor
if (bd->MouseCursorInvisible) if (bd->MouseCursorInvisible)
{ {
al_destroy_mouse_cursor(bd->MouseCursorInvisible); al_destroy_mouse_cursor(bd->MouseCursorInvisible);
@ -440,6 +478,7 @@ bool ImGui_ImplAllegro5_Init(ALLEGRO_DISPLAY* display)
io.BackendPlatformUserData = (void*)bd; io.BackendPlatformUserData = (void*)bd;
io.BackendPlatformName = io.BackendRendererName = "imgui_impl_allegro5"; io.BackendPlatformName = io.BackendRendererName = "imgui_impl_allegro5";
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
bd->Display = display; bd->Display = display;
bd->LastCursor = ALLEGRO_SYSTEM_MOUSE_CURSOR_NONE; bd->LastCursor = ALLEGRO_SYSTEM_MOUSE_CURSOR_NONE;
@ -479,7 +518,7 @@ void ImGui_ImplAllegro5_Shutdown()
io.BackendPlatformName = io.BackendRendererName = nullptr; io.BackendPlatformName = io.BackendRendererName = nullptr;
io.BackendPlatformUserData = nullptr; io.BackendPlatformUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_HasMouseCursors; io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_RendererHasTextures);
IM_DELETE(bd); IM_DELETE(bd);
} }
@ -609,12 +648,11 @@ void ImGui_ImplAllegro5_NewFrame()
ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData(); ImGui_ImplAllegro5_Data* bd = ImGui_ImplAllegro5_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplAllegro5_Init()?"); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplAllegro5_Init()?");
if (!bd->Texture) if (!bd->MouseCursorInvisible)
ImGui_ImplAllegro5_CreateDeviceObjects(); ImGui_ImplAllegro5_CreateDeviceObjects();
ImGuiIO& io = ImGui::GetIO();
// Setup display size (every frame to accommodate for window resizing) // Setup display size (every frame to accommodate for window resizing)
ImGuiIO& io = ImGui::GetIO();
int w, h; int w, h;
w = al_get_display_width(bd->Display); w = al_get_display_width(bd->Display);
h = al_get_display_height(bd->Display); h = al_get_display_height(bd->Display);

View file

@ -2,7 +2,8 @@
// (Info: Allegro 5 is a cross-platform general purpose library for handling windows, inputs, graphics, etc.) // (Info: Allegro 5 is a cross-platform general purpose library for handling windows, inputs, graphics, etc.)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'ALLEGRO_BITMAP*' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'ALLEGRO_BITMAP*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy ALLEGRO_KEY_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy ALLEGRO_KEY_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Clipboard support (from Allegro 5.1.12). // [X] Platform: Clipboard support (from Allegro 5.1.12).
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
@ -37,4 +38,7 @@ IMGUI_IMPL_API bool ImGui_ImplAllegro5_ProcessEvent(ALLEGRO_EVENT* event);
IMGUI_IMPL_API bool ImGui_ImplAllegro5_CreateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplAllegro5_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplAllegro5_InvalidateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplAllegro5_InvalidateDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplAllegro5_UpdateTexture(ImTextureData* tex);
#endif // #ifndef IMGUI_DISABLE #endif // #ifndef IMGUI_DISABLE

View file

@ -6,7 +6,7 @@
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen. // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen.
// Missing features or Issues: // Missing features or Issues:
// [ ] Platform: Clipboard support. // [ ] Platform: Clipboard support.
// [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [ ] Platform: Gamepad support.
// [ ] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android. // [ ] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
// [ ] Platform: Multi-viewport support (multiple windows). Not meaningful on Android. // [ ] Platform: Multi-viewport support (multiple windows). Not meaningful on Android.
// Important: // Important:

View file

@ -6,7 +6,7 @@
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen. // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen.
// Missing features or Issues: // Missing features or Issues:
// [ ] Platform: Clipboard support. // [ ] Platform: Clipboard support.
// [ ] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [ ] Platform: Gamepad support.
// [ ] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android. // [ ] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. FIXME: Check if this is even possible with Android.
// [ ] Platform: Multi-viewport support (multiple windows). Not meaningful on Android. // [ ] Platform: Multi-viewport support (multiple windows). Not meaningful on Android.
// Important: // Important:

View file

@ -2,8 +2,9 @@
// This needs to be used along with a Platform Backend (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
@ -17,6 +18,8 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-06-11: DirectX10: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
// 2025-05-07: DirectX10: Honor draw_data->FramebufferScale to allow for custom backends and experiment using it (consistently with other renderer backends, even though in normal condition it is not set under Windows).
// 2025-01-06: DirectX10: Expose selected render state in ImGui_ImplDX10_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks. // 2025-01-06: DirectX10: Expose selected render state in ImGui_ImplDX10_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.
// 2024-10-07: DirectX10: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-10-07: DirectX10: Changed default texture sampler to Clamp instead of Repeat/Wrap.
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11. // 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
@ -49,7 +52,19 @@
#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below. #pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
#endif #endif
// Clang/GCC warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#endif
// DirectX10 data // DirectX10 data
struct ImGui_ImplDX10_Texture
{
ID3D10Texture2D* pTexture;
ID3D10ShaderResourceView* pTextureView;
};
struct ImGui_ImplDX10_Data struct ImGui_ImplDX10_Data
{ {
ID3D10Device* pd3dDevice; ID3D10Device* pd3dDevice;
@ -61,7 +76,6 @@ struct ImGui_ImplDX10_Data
ID3D10Buffer* pVertexConstantBuffer; ID3D10Buffer* pVertexConstantBuffer;
ID3D10PixelShader* pPixelShader; ID3D10PixelShader* pPixelShader;
ID3D10SamplerState* pFontSampler; ID3D10SamplerState* pFontSampler;
ID3D10ShaderResourceView* pFontTextureView;
ID3D10RasterizerState* pRasterizerState; ID3D10RasterizerState* pRasterizerState;
ID3D10BlendState* pBlendState; ID3D10BlendState* pBlendState;
ID3D10DepthStencilState* pDepthStencilState; ID3D10DepthStencilState* pDepthStencilState;
@ -94,8 +108,8 @@ static void ImGui_ImplDX10_SetupRenderState(ImDrawData* draw_data, ID3D10Device*
// Setup viewport // Setup viewport
D3D10_VIEWPORT vp = {}; D3D10_VIEWPORT vp = {};
vp.Width = (UINT)draw_data->DisplaySize.x; vp.Width = (UINT)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
vp.Height = (UINT)draw_data->DisplaySize.y; vp.Height = (UINT)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
vp.MinDepth = 0.0f; vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f; vp.MaxDepth = 1.0f;
vp.TopLeftX = vp.TopLeftY = 0; vp.TopLeftX = vp.TopLeftY = 0;
@ -152,6 +166,13 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data)
ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
ID3D10Device* device = bd->pd3dDevice; ID3D10Device* device = bd->pd3dDevice;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplDX10_UpdateTexture(tex);
// Create and grow vertex/index buffers if needed // Create and grow vertex/index buffers if needed
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount) if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
{ {
@ -252,6 +273,7 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data)
int global_vtx_offset = 0; int global_vtx_offset = 0;
int global_idx_offset = 0; int global_idx_offset = 0;
ImVec2 clip_off = draw_data->DisplayPos; ImVec2 clip_off = draw_data->DisplayPos;
ImVec2 clip_scale = draw_data->FramebufferScale;
for (int n = 0; n < draw_data->CmdListsCount; n++) for (int n = 0; n < draw_data->CmdListsCount; n++)
{ {
const ImDrawList* draw_list = draw_data->CmdLists[n]; const ImDrawList* draw_list = draw_data->CmdLists[n];
@ -270,8 +292,8 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data)
else else
{ {
// Project scissor/clipping rectangles into framebuffer space // Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y); ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y); ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue; continue;
@ -308,21 +330,39 @@ void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data)
device->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release(); device->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();
} }
static void ImGui_ImplDX10_CreateFontsTexture() static void ImGui_ImplDX10_DestroyTexture(ImTextureData* tex)
{ {
// Build texture atlas ImGui_ImplDX10_Texture* backend_tex = (ImGui_ImplDX10_Texture*)tex->BackendUserData;
ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); if (backend_tex == nullptr)
ImGuiIO& io = ImGui::GetIO(); return;
unsigned char* pixels; IM_ASSERT(backend_tex->pTextureView == (ID3D10ShaderResourceView*)(intptr_t)tex->TexID);
int width, height; backend_tex->pTexture->Release();
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); backend_tex->pTextureView->Release();
IM_DELETE(backend_tex);
// Upload texture to graphics system // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
tex->BackendUserData = nullptr;
}
void ImGui_ImplDX10_UpdateTexture(ImTextureData* tex)
{ {
ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
if (tex->Status == ImTextureStatus_WantCreate)
{
// Create and upload new texture to graphics system
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
unsigned int* pixels = (unsigned int*)tex->GetPixels();
ImGui_ImplDX10_Texture* backend_tex = IM_NEW(ImGui_ImplDX10_Texture)();
// Create texture
D3D10_TEXTURE2D_DESC desc; D3D10_TEXTURE2D_DESC desc;
ZeroMemory(&desc, sizeof(desc)); ZeroMemory(&desc, sizeof(desc));
desc.Width = width; desc.Width = (UINT)tex->Width;
desc.Height = height; desc.Height = (UINT)tex->Height;
desc.MipLevels = 1; desc.MipLevels = 1;
desc.ArraySize = 1; desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
@ -331,13 +371,12 @@ static void ImGui_ImplDX10_CreateFontsTexture()
desc.BindFlags = D3D10_BIND_SHADER_RESOURCE; desc.BindFlags = D3D10_BIND_SHADER_RESOURCE;
desc.CPUAccessFlags = 0; desc.CPUAccessFlags = 0;
ID3D10Texture2D* pTexture = nullptr;
D3D10_SUBRESOURCE_DATA subResource; D3D10_SUBRESOURCE_DATA subResource;
subResource.pSysMem = pixels; subResource.pSysMem = pixels;
subResource.SysMemPitch = desc.Width * 4; subResource.SysMemPitch = desc.Width * 4;
subResource.SysMemSlicePitch = 0; subResource.SysMemSlicePitch = 0;
bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &backend_tex->pTexture);
IM_ASSERT(pTexture != nullptr); IM_ASSERT(backend_tex->pTexture != nullptr && "Backend failed to create texture!");
// Create texture view // Create texture view
D3D10_SHADER_RESOURCE_VIEW_DESC srv_desc; D3D10_SHADER_RESOURCE_VIEW_DESC srv_desc;
@ -346,23 +385,29 @@ static void ImGui_ImplDX10_CreateFontsTexture()
srv_desc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D; srv_desc.ViewDimension = D3D10_SRV_DIMENSION_TEXTURE2D;
srv_desc.Texture2D.MipLevels = desc.MipLevels; srv_desc.Texture2D.MipLevels = desc.MipLevels;
srv_desc.Texture2D.MostDetailedMip = 0; srv_desc.Texture2D.MostDetailedMip = 0;
bd->pd3dDevice->CreateShaderResourceView(pTexture, &srv_desc, &bd->pFontTextureView); bd->pd3dDevice->CreateShaderResourceView(backend_tex->pTexture, &srv_desc, &backend_tex->pTextureView);
pTexture->Release(); IM_ASSERT(backend_tex->pTextureView != nullptr && "Backend failed to create texture!");
}
// Store our identifier // Store identifiers
io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView); tex->SetTexID((ImTextureID)(intptr_t)backend_tex->pTextureView);
tex->SetStatus(ImTextureStatus_OK);
tex->BackendUserData = backend_tex;
} }
else if (tex->Status == ImTextureStatus_WantUpdates)
static void ImGui_ImplDX10_DestroyFontsTexture()
{ {
ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); // Update selected blocks. We only ever write to textures regions which have never been used before!
if (bd->pFontTextureView) // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
ImGui_ImplDX10_Texture* backend_tex = (ImGui_ImplDX10_Texture*)tex->BackendUserData;
IM_ASSERT(backend_tex->pTextureView == (ID3D10ShaderResourceView*)(intptr_t)tex->TexID);
for (ImTextureRect& r : tex->Updates)
{ {
bd->pFontTextureView->Release(); D3D10_BOX box = { (UINT)r.x, (UINT)r.y, (UINT)0, (UINT)(r.x + r.w), (UINT)(r.y + r.h), (UINT)1 };
bd->pFontTextureView = nullptr; bd->pd3dDevice->UpdateSubresource(backend_tex->pTexture, 0, &box, tex->GetPixelsAt(r.x, r.y), (UINT)tex->GetPitch(), 0);
ImGui::GetIO().Fonts->SetTexID(0); // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well.
} }
tex->SetStatus(ImTextureStatus_OK);
}
if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
ImGui_ImplDX10_DestroyTexture(tex);
} }
bool ImGui_ImplDX10_CreateDeviceObjects() bool ImGui_ImplDX10_CreateDeviceObjects()
@ -370,7 +415,6 @@ bool ImGui_ImplDX10_CreateDeviceObjects()
ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData(); ImGui_ImplDX10_Data* bd = ImGui_ImplDX10_GetBackendData();
if (!bd->pd3dDevice) if (!bd->pd3dDevice)
return false; return false;
if (bd->pFontSampler)
ImGui_ImplDX10_InvalidateDeviceObjects(); ImGui_ImplDX10_InvalidateDeviceObjects();
// By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A) // By using D3DCompile() from <d3dcompiler.h> / d3dcompiler.lib, we introduce a dependency to a given version of d3dcompiler_XX.dll (see D3DCOMPILER_DLL_A)
@ -530,8 +574,6 @@ bool ImGui_ImplDX10_CreateDeviceObjects()
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler); bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler);
} }
ImGui_ImplDX10_CreateFontsTexture();
return true; return true;
} }
@ -541,8 +583,10 @@ void ImGui_ImplDX10_InvalidateDeviceObjects()
if (!bd->pd3dDevice) if (!bd->pd3dDevice)
return; return;
ImGui_ImplDX10_DestroyFontsTexture(); // Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
ImGui_ImplDX10_DestroyTexture(tex);
if (bd->pFontSampler) { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; } if (bd->pFontSampler) { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; }
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; } if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; } if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
@ -566,8 +610,12 @@ bool ImGui_ImplDX10_Init(ID3D10Device* device)
io.BackendRendererUserData = (void*)bd; io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx10"; io.BackendRendererName = "imgui_impl_dx10";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = D3D10_REQ_TEXTURE2D_U_OR_V_DIMENSION;
// Get factory from device // Get factory from device
IDXGIDevice* pDXGIDevice = nullptr; IDXGIDevice* pDXGIDevice = nullptr;
IDXGIAdapter* pDXGIAdapter = nullptr; IDXGIAdapter* pDXGIAdapter = nullptr;
@ -600,7 +648,7 @@ void ImGui_ImplDX10_Shutdown()
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); } if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasViewports); io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
IM_DELETE(bd); IM_DELETE(bd);
} }
@ -610,7 +658,8 @@ void ImGui_ImplDX10_NewFrame()
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX10_Init()?"); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX10_Init()?");
if (!bd->pVertexShader) if (!bd->pVertexShader)
ImGui_ImplDX10_CreateDeviceObjects(); if (!ImGui_ImplDX10_CreateDeviceObjects())
IM_ASSERT(0 && "ImGui_ImplDX10_CreateDeviceObjects() failed!");
} }
//-------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------

View file

@ -2,8 +2,9 @@
// This needs to be used along with a Platform Backend (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'ID3D10ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
@ -32,6 +33,9 @@ IMGUI_IMPL_API void ImGui_ImplDX10_RenderDrawData(ImDrawData* draw_data);
IMGUI_IMPL_API bool ImGui_ImplDX10_CreateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplDX10_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplDX10_InvalidateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX10_InvalidateDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplDX10_UpdateTexture(ImTextureData* tex);
// [BETA] Selected render state data shared with callbacks. // [BETA] Selected render state data shared with callbacks.
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplDX10_RenderDrawData() call. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplDX10_RenderDrawData() call.
// (Please open an issue if you feel you need access to more data) // (Please open an issue if you feel you need access to more data)

View file

@ -2,8 +2,9 @@
// This needs to be used along with a Platform Backend (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
@ -18,6 +19,8 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-06-11: DirectX11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
// 2025-05-07: DirectX11: Honor draw_data->FramebufferScale to allow for custom backends and experiment using it (consistently with other renderer backends, even though in normal condition it is not set under Windows).
// 2025-02-24: [Docking] Added undocumented ImGui_ImplDX11_SetSwapChainDescs() to configure swap chain creation for secondary viewports. // 2025-02-24: [Docking] Added undocumented ImGui_ImplDX11_SetSwapChainDescs() to configure swap chain creation for secondary viewports.
// 2025-01-06: DirectX11: Expose VertexConstantBuffer in ImGui_ImplDX11_RenderState. Reset projection matrix in ImDrawCallback_ResetRenderState handler. // 2025-01-06: DirectX11: Expose VertexConstantBuffer in ImGui_ImplDX11_RenderState. Reset projection matrix in ImDrawCallback_ResetRenderState handler.
// 2024-10-07: DirectX11: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-10-07: DirectX11: Changed default texture sampler to Clamp instead of Repeat/Wrap.
@ -52,7 +55,19 @@
typedef HRESULT (__stdcall *D3DCompile_t)(LPCVOID, SIZE_T, LPCSTR, D3D_SHADER_MACRO*, ID3DInclude*, LPCSTR, LPCSTR, UINT, UINT, ID3DBlob**, ID3DBlob*); typedef HRESULT (__stdcall *D3DCompile_t)(LPCVOID, SIZE_T, LPCSTR, D3D_SHADER_MACRO*, ID3DInclude*, LPCSTR, LPCSTR, UINT, UINT, ID3DBlob**, ID3DBlob*);
// Clang/GCC warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#endif
// DirectX11 data // DirectX11 data
struct ImGui_ImplDX11_Texture
{
ID3D11Texture2D* pTexture;
ID3D11ShaderResourceView* pTextureView;
};
struct ImGui_ImplDX11_Data struct ImGui_ImplDX11_Data
{ {
ID3D11Device* pd3dDevice; ID3D11Device* pd3dDevice;
@ -65,8 +80,6 @@ struct ImGui_ImplDX11_Data
ID3D11Buffer* pVertexConstantBuffer; ID3D11Buffer* pVertexConstantBuffer;
ID3D11PixelShader* pPixelShader; ID3D11PixelShader* pPixelShader;
ID3D11SamplerState* pFontSampler; ID3D11SamplerState* pFontSampler;
ID3D11SamplerState* pTexSampler;
ID3D11ShaderResourceView* pFontTextureView;
ID3D11RasterizerState* pRasterizerState; ID3D11RasterizerState* pRasterizerState;
ID3D11BlendState* pBlendState; ID3D11BlendState* pBlendState;
ID3D11DepthStencilState* pDepthStencilState; ID3D11DepthStencilState* pDepthStencilState;
@ -102,8 +115,8 @@ static void ImGui_ImplDX11_SetupRenderState(ImDrawData* draw_data, ID3D11DeviceC
// Setup viewport // Setup viewport
D3D11_VIEWPORT vp = {}; D3D11_VIEWPORT vp = {};
vp.Width = draw_data->DisplaySize.x; vp.Width = draw_data->DisplaySize.x * draw_data->FramebufferScale.x;
vp.Height = draw_data->DisplaySize.y; vp.Height = draw_data->DisplaySize.y * draw_data->FramebufferScale.y;
vp.MinDepth = 0.0f; vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f; vp.MaxDepth = 1.0f;
vp.TopLeftX = vp.TopLeftY = 0; vp.TopLeftX = vp.TopLeftY = 0;
@ -165,6 +178,13 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
ID3D11DeviceContext* device = bd->pd3dDeviceContext; ID3D11DeviceContext* device = bd->pd3dDeviceContext;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplDX11_UpdateTexture(tex);
// Create and grow vertex/index buffers if needed // Create and grow vertex/index buffers if needed
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount) if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
{ {
@ -273,6 +293,7 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
int global_idx_offset = 0; int global_idx_offset = 0;
int global_vtx_offset = 0; int global_vtx_offset = 0;
ImVec2 clip_off = draw_data->DisplayPos; ImVec2 clip_off = draw_data->DisplayPos;
ImVec2 clip_scale = draw_data->FramebufferScale;
for (int n = 0; n < draw_data->CmdListsCount; n++) for (int n = 0; n < draw_data->CmdListsCount; n++)
{ {
const ImDrawList* draw_list = draw_data->CmdLists[n]; const ImDrawList* draw_list = draw_data->CmdLists[n];
@ -291,8 +312,8 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
else else
{ {
// Project scissor/clipping rectangles into framebuffer space // Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y); ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y); ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue; continue;
@ -339,21 +360,39 @@ void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data)
device->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release(); device->IASetInputLayout(old.InputLayout); if (old.InputLayout) old.InputLayout->Release();
} }
static void ImGui_ImplDX11_CreateFontsTexture() static void ImGui_ImplDX11_DestroyTexture(ImTextureData* tex)
{ {
// Build texture atlas ImGui_ImplDX11_Texture* backend_tex = (ImGui_ImplDX11_Texture*)tex->BackendUserData;
ImGuiIO& io = ImGui::GetIO(); if (backend_tex == nullptr)
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); return;
unsigned char* pixels; IM_ASSERT(backend_tex->pTextureView == (ID3D11ShaderResourceView*)(intptr_t)tex->TexID);
int width, height; backend_tex->pTextureView->Release();
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); backend_tex->pTexture->Release();
IM_DELETE(backend_tex);
// Upload texture to graphics system // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
tex->BackendUserData = nullptr;
}
void ImGui_ImplDX11_UpdateTexture(ImTextureData* tex)
{ {
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
if (tex->Status == ImTextureStatus_WantCreate)
{
// Create and upload new texture to graphics system
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
unsigned int* pixels = (unsigned int*)tex->GetPixels();
ImGui_ImplDX11_Texture* backend_tex = IM_NEW(ImGui_ImplDX11_Texture)();
// Create texture
D3D11_TEXTURE2D_DESC desc; D3D11_TEXTURE2D_DESC desc;
ZeroMemory(&desc, sizeof(desc)); ZeroMemory(&desc, sizeof(desc));
desc.Width = width; desc.Width = (UINT)tex->Width;
desc.Height = height; desc.Height = (UINT)tex->Height;
desc.MipLevels = 1; desc.MipLevels = 1;
desc.ArraySize = 1; desc.ArraySize = 1;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
@ -361,15 +400,13 @@ static void ImGui_ImplDX11_CreateFontsTexture()
desc.Usage = D3D11_USAGE_DEFAULT; desc.Usage = D3D11_USAGE_DEFAULT;
desc.BindFlags = D3D11_BIND_SHADER_RESOURCE; desc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
desc.CPUAccessFlags = 0; desc.CPUAccessFlags = 0;
ID3D11Texture2D* pTexture = nullptr;
D3D11_SUBRESOURCE_DATA subResource; D3D11_SUBRESOURCE_DATA subResource;
subResource.pSysMem = pixels; subResource.pSysMem = pixels;
subResource.SysMemPitch = desc.Width * 4; subResource.SysMemPitch = desc.Width * 4;
subResource.SysMemSlicePitch = 0; subResource.SysMemSlicePitch = 0;
bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &pTexture); bd->pd3dDevice->CreateTexture2D(&desc, &subResource, &backend_tex->pTexture);
IM_ASSERT(backend_tex->pTexture != nullptr && "Backend failed to create texture!");
if (pTexture != nullptr) {
// Create texture view // Create texture view
D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc; D3D11_SHADER_RESOURCE_VIEW_DESC srvDesc;
ZeroMemory(&srvDesc, sizeof(srvDesc)); ZeroMemory(&srvDesc, sizeof(srvDesc));
@ -377,43 +414,29 @@ static void ImGui_ImplDX11_CreateFontsTexture()
srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D; srvDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MipLevels = desc.MipLevels; srvDesc.Texture2D.MipLevels = desc.MipLevels;
srvDesc.Texture2D.MostDetailedMip = 0; srvDesc.Texture2D.MostDetailedMip = 0;
bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, &bd->pFontTextureView); bd->pd3dDevice->CreateShaderResourceView(backend_tex->pTexture, &srvDesc, &backend_tex->pTextureView);
pTexture->Release(); IM_ASSERT(backend_tex->pTextureView != nullptr && "Backend failed to create texture!");
} else {
bd->pFontTextureView=NULL;
bd->pFontSampler=NULL;
return;
}
}
// Store our identifier // Store identifiers
io.Fonts->SetTexID((ImTextureID)bd->pFontTextureView); tex->SetTexID((ImTextureID)(intptr_t)backend_tex->pTextureView);
tex->SetStatus(ImTextureStatus_OK);
tex->BackendUserData = backend_tex;
} }
else if (tex->Status == ImTextureStatus_WantUpdates)
static void ImGui_ImplDX11_DestroyFontsTexture()
{ {
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); // Update selected blocks. We only ever write to textures regions which have never been used before!
if (bd->pFontTextureView) // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
ImGui_ImplDX11_Texture* backend_tex = (ImGui_ImplDX11_Texture*)tex->BackendUserData;
IM_ASSERT(backend_tex->pTextureView == (ID3D11ShaderResourceView*)(intptr_t)tex->TexID);
for (ImTextureRect& r : tex->Updates)
{ {
bd->pFontTextureView->Release(); D3D11_BOX box = { (UINT)r.x, (UINT)r.y, (UINT)0, (UINT)(r.x + r.w), (UINT)(r.y + r .h), (UINT)1 };
bd->pFontTextureView = nullptr; bd->pd3dDeviceContext->UpdateSubresource(backend_tex->pTexture, 0, &box, tex->GetPixelsAt(r.x, r.y), (UINT)tex->GetPitch(), 0);
ImGui::GetIO().Fonts->SetTexID(0); // We copied data->pFontTextureView to io.Fonts->TexID so let's clear that as well.
} }
tex->SetStatus(ImTextureStatus_OK);
// Create other sampler
{
D3D11_SAMPLER_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
desc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
desc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
desc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
desc.MipLODBias = 0.f;
desc.ComparisonFunc = D3D11_COMPARISON_ALWAYS;
desc.MinLOD = 0.f;
desc.MaxLOD = 0.f;
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pTexSampler);
} }
if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
ImGui_ImplDX11_DestroyTexture(tex);
} }
bool ImGui_ImplDX11_CreateDeviceObjects() bool ImGui_ImplDX11_CreateDeviceObjects()
@ -421,7 +444,6 @@ bool ImGui_ImplDX11_CreateDeviceObjects()
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
if (!bd->pd3dDevice) if (!bd->pd3dDevice)
return false; return false;
if (bd->pFontSampler)
ImGui_ImplDX11_InvalidateDeviceObjects(); ImGui_ImplDX11_InvalidateDeviceObjects();
// See https://github.com/ocornut/imgui/pull/638 for sources and details. // See https://github.com/ocornut/imgui/pull/638 for sources and details.
@ -592,8 +614,6 @@ bool ImGui_ImplDX11_CreateDeviceObjects()
bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler); bd->pd3dDevice->CreateSamplerState(&desc, &bd->pFontSampler);
} }
ImGui_ImplDX11_CreateFontsTexture();
return true; return true;
} }
@ -603,7 +623,10 @@ void ImGui_ImplDX11_InvalidateDeviceObjects()
if (!bd->pd3dDevice) if (!bd->pd3dDevice)
return; return;
ImGui_ImplDX11_DestroyFontsTexture(); // Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
ImGui_ImplDX11_DestroyTexture(tex);
if (bd->pFontSampler) { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; } if (bd->pFontSampler) { bd->pFontSampler->Release(); bd->pFontSampler = nullptr; }
// tildearrow: why do we not need this anymore? // tildearrow: why do we not need this anymore?
@ -632,8 +655,12 @@ bool ImGui_ImplDX11_Init(ID3D11Device* device, ID3D11DeviceContext* device_co
io.BackendRendererUserData = (void*)bd; io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx11"; io.BackendRendererName = "imgui_impl_dx11";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = D3D11_REQ_TEXTURE2D_U_OR_V_DIMENSION;
// Get factory from device // Get factory from device
IDXGIDevice* pDXGIDevice = nullptr; IDXGIDevice* pDXGIDevice = nullptr;
IDXGIAdapter* pDXGIAdapter = nullptr; IDXGIAdapter* pDXGIAdapter = nullptr;
@ -670,7 +697,7 @@ void ImGui_ImplDX11_Shutdown()
if (bd->pd3dDeviceContext) { bd->pd3dDeviceContext->Release(); } if (bd->pd3dDeviceContext) { bd->pd3dDeviceContext->Release(); }
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasViewports); io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
IM_DELETE(bd); IM_DELETE(bd);
} }
@ -679,10 +706,9 @@ bool ImGui_ImplDX11_NewFrame()
ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData(); ImGui_ImplDX11_Data* bd = ImGui_ImplDX11_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX11_Init()?"); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX11_Init()?");
if (!bd->pFontSampler) if (!bd->pVertexShader)
ImGui_ImplDX11_CreateDeviceObjects(); if (!ImGui_ImplDX11_CreateDeviceObjects())
IM_ASSERT(0 && "ImGui_ImplDX11_CreateDeviceObjects() failed!");
return bd->pFontSampler!=NULL;
} }
//-------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------

View file

@ -2,8 +2,9 @@
// This needs to be used along with a Platform Backend (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'ID3D11ShaderResourceView*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
@ -34,6 +35,9 @@ IMGUI_IMPL_API void ImGui_ImplDX11_RenderDrawData(ImDrawData* draw_data);
IMGUI_IMPL_API bool ImGui_ImplDX11_CreateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplDX11_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplDX11_InvalidateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX11_InvalidateDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplDX11_UpdateTexture(ImTextureData* tex);
// [BETA] Selected render state data shared with callbacks. // [BETA] Selected render state data shared with callbacks.
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplDX11_RenderDrawData() call. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplDX11_RenderDrawData() call.
// (Please open an issue if you feel you need access to more data) // (Please open an issue if you feel you need access to more data)

View file

@ -2,8 +2,9 @@
// This needs to be used along with a Platform Backend (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// FIXME: The transition from removing a viewport and moving the window in an existing hosted viewport tends to flicker. // FIXME: The transition from removing a viewport and moving the window in an existing hosted viewport tends to flicker.
@ -22,6 +23,9 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-06-19: Fixed build on MinGW. (#8702, #4594)
// 2025-06-11: DirectX12: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
// 2025-05-07: DirectX12: Honor draw_data->FramebufferScale to allow for custom backends and experiment using it (consistently with other renderer backends, even though in normal condition it is not set under Windows).
// 2025-02-24: DirectX12: Fixed an issue where ImGui_ImplDX12_Init() signature change from 2024-11-15 combined with change from 2025-01-15 made legacy ImGui_ImplDX12_Init() crash. (#8429) // 2025-02-24: DirectX12: Fixed an issue where ImGui_ImplDX12_Init() signature change from 2024-11-15 combined with change from 2025-01-15 made legacy ImGui_ImplDX12_Init() crash. (#8429)
// 2025-01-15: DirectX12: Texture upload use the command queue provided in ImGui_ImplDX12_InitInfo instead of creating its own. // 2025-01-15: DirectX12: Texture upload use the command queue provided in ImGui_ImplDX12_InitInfo instead of creating its own.
// 2024-12-09: DirectX12: Let user specifies the DepthStencilView format by setting ImGui_ImplDX12_InitInfo::DSVFormat. // 2024-12-09: DirectX12: Let user specifies the DepthStencilView format by setting ImGui_ImplDX12_InitInfo::DSVFormat.
@ -61,6 +65,15 @@
#pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below. #pragma comment(lib, "d3dcompiler") // Automatically link with d3dcompiler.lib as we are using D3DCompile() below.
#endif #endif
// Clang/GCC warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#endif
// MinGW workaround, see #4594
typedef decltype(D3D12SerializeRootSignature) *_PFN_D3D12_SERIALIZE_ROOT_SIGNATURE;
// DirectX12 data // DirectX12 data
struct ImGui_ImplDX12_RenderBuffers; struct ImGui_ImplDX12_RenderBuffers;
@ -214,8 +227,8 @@ static void ImGui_ImplDX12_SetupRenderState(ImDrawData* draw_data, ID3D12Graphic
// Setup viewport // Setup viewport
D3D12_VIEWPORT vp = {}; D3D12_VIEWPORT vp = {};
vp.Width = draw_data->DisplaySize.x; vp.Width = draw_data->DisplaySize.x * draw_data->FramebufferScale.x;
vp.Height = draw_data->DisplaySize.y; vp.Height = draw_data->DisplaySize.y * draw_data->FramebufferScale.y;
vp.MinDepth = 0.0f; vp.MinDepth = 0.0f;
vp.MaxDepth = 1.0f; vp.MaxDepth = 1.0f;
vp.TopLeftX = vp.TopLeftY = 0.0f; vp.TopLeftX = vp.TopLeftY = 0.0f;
@ -259,6 +272,13 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return; return;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplDX12_UpdateTexture(tex);
// FIXME: We are assuming that this only gets called once per frame! // FIXME: We are assuming that this only gets called once per frame!
ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)draw_data->OwnerViewport->RendererUserData; ImGui_ImplDX12_ViewportData* vd = (ImGui_ImplDX12_ViewportData*)draw_data->OwnerViewport->RendererUserData;
@ -351,6 +371,7 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL
int global_vtx_offset = 0; int global_vtx_offset = 0;
int global_idx_offset = 0; int global_idx_offset = 0;
ImVec2 clip_off = draw_data->DisplayPos; ImVec2 clip_off = draw_data->DisplayPos;
ImVec2 clip_scale = draw_data->FramebufferScale;
for (int n = 0; n < draw_data->CmdListsCount; n++) for (int n = 0; n < draw_data->CmdListsCount; n++)
{ {
const ImDrawList* draw_list = draw_data->CmdLists[n]; const ImDrawList* draw_list = draw_data->CmdLists[n];
@ -369,8 +390,8 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL
else else
{ {
// Project scissor/clipping rectangles into framebuffer space // Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y); ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x, (pcmd->ClipRect.y - clip_off.y) * clip_scale.y);
ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y); ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x, (pcmd->ClipRect.w - clip_off.y) * clip_scale.y);
if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y) if (clip_max.x <= clip_min.x || clip_max.y <= clip_min.y)
continue; continue;
@ -391,18 +412,39 @@ void ImGui_ImplDX12_RenderDrawData(ImDrawData* draw_data, ID3D12GraphicsCommandL
platform_io.Renderer_RenderState = nullptr; platform_io.Renderer_RenderState = nullptr;
} }
static void ImGui_ImplDX12_CreateFontsTexture() static void ImGui_ImplDX12_DestroyTexture(ImTextureData* tex)
{ {
// Build texture atlas ImGui_ImplDX12_Texture* backend_tex = (ImGui_ImplDX12_Texture*)tex->BackendUserData;
ImGuiIO& io = ImGui::GetIO(); if (backend_tex == nullptr)
return;
IM_ASSERT(backend_tex->hFontSrvGpuDescHandle.ptr == (UINT64)tex->TexID);
ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
unsigned char* pixels; bd->InitInfo.SrvDescriptorFreeFn(&bd->InitInfo, backend_tex->hFontSrvCpuDescHandle, backend_tex->hFontSrvGpuDescHandle);
int width, height; SafeRelease(backend_tex->pTextureResource);
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); backend_tex->hFontSrvCpuDescHandle.ptr = 0;
backend_tex->hFontSrvGpuDescHandle.ptr = 0;
IM_DELETE(backend_tex);
// Upload texture to graphics system // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
ImGui_ImplDX12_Texture* font_tex = &bd->FontTexture; tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
tex->BackendUserData = nullptr;
}
void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex)
{ {
ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
bool need_barrier_before_copy = true; // Do we need a resource barrier before we copy new data in?
if (tex->Status == ImTextureStatus_WantCreate)
{
// Create and upload new texture to graphics system
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
ImGui_ImplDX12_Texture* backend_tex = IM_NEW(ImGui_ImplDX12_Texture)();
bd->InitInfo.SrvDescriptorAllocFn(&bd->InitInfo, &backend_tex->hFontSrvCpuDescHandle, &backend_tex->hFontSrvGpuDescHandle); // Allocate a desctriptor handle
D3D12_HEAP_PROPERTIES props = {}; D3D12_HEAP_PROPERTIES props = {};
props.Type = D3D12_HEAP_TYPE_DEFAULT; props.Type = D3D12_HEAP_TYPE_DEFAULT;
props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
@ -412,8 +454,8 @@ static void ImGui_ImplDX12_CreateFontsTexture()
ZeroMemory(&desc, sizeof(desc)); ZeroMemory(&desc, sizeof(desc));
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D; desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
desc.Alignment = 0; desc.Alignment = 0;
desc.Width = width; desc.Width = tex->Width;
desc.Height = height; desc.Height = tex->Height;
desc.DepthOrArraySize = 1; desc.DepthOrArraySize = 1;
desc.MipLevels = 1; desc.MipLevels = 1;
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
@ -426,8 +468,47 @@ static void ImGui_ImplDX12_CreateFontsTexture()
bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc,
D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&pTexture)); D3D12_RESOURCE_STATE_COPY_DEST, nullptr, IID_PPV_ARGS(&pTexture));
UINT upload_pitch = (width * 4 + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u); // Create SRV
UINT upload_size = height * upload_pitch; 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;
bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, backend_tex->hFontSrvCpuDescHandle);
SafeRelease(backend_tex->pTextureResource);
backend_tex->pTextureResource = pTexture;
// Store identifiers
tex->SetTexID((ImTextureID)backend_tex->hFontSrvGpuDescHandle.ptr);
tex->BackendUserData = backend_tex;
need_barrier_before_copy = false; // Because this is a newly-created texture it will be in D3D12_RESOURCE_STATE_COMMON and thus we don't need a barrier
// We don't set tex->Status to ImTextureStatus_OK to let the code fallthrough below.
}
if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates)
{
ImGui_ImplDX12_Texture* backend_tex = (ImGui_ImplDX12_Texture*)tex->BackendUserData;
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
// We could use the smaller rect on _WantCreate but using the full rect allows us to clear the texture.
// FIXME-OPT: Uploading single box even when using ImTextureStatus_WantUpdates. Could use tex->Updates[]
// - Copy all blocks contiguously in upload buffer.
// - Barrier before copy, submit all CopyTextureRegion(), barrier after copy.
const int upload_x = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.x;
const int upload_y = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.y;
const int upload_w = (tex->Status == ImTextureStatus_WantCreate) ? tex->Width : tex->UpdateRect.w;
const int upload_h = (tex->Status == ImTextureStatus_WantCreate) ? tex->Height : tex->UpdateRect.h;
// Update full texture or selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->UpdateRect but you can use tex->Updates[] to upload individual regions.
UINT upload_pitch_src = upload_w * tex->BytesPerPixel;
UINT upload_pitch_dst = (upload_pitch_src + D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u) & ~(D3D12_TEXTURE_DATA_PITCH_ALIGNMENT - 1u);
UINT upload_size = upload_pitch_dst * upload_h;
D3D12_RESOURCE_DESC desc;
ZeroMemory(&desc, sizeof(desc));
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER; desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
desc.Alignment = 0; desc.Alignment = 0;
desc.Width = upload_size; desc.Width = upload_size;
@ -440,47 +521,19 @@ static void ImGui_ImplDX12_CreateFontsTexture()
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR; desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
desc.Flags = D3D12_RESOURCE_FLAG_NONE; desc.Flags = D3D12_RESOURCE_FLAG_NONE;
D3D12_HEAP_PROPERTIES props;
memset(&props, 0, sizeof(D3D12_HEAP_PROPERTIES));
props.Type = D3D12_HEAP_TYPE_UPLOAD; props.Type = D3D12_HEAP_TYPE_UPLOAD;
props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN; props.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN; props.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
// FIXME-OPT: Can upload buffer be reused?
ID3D12Resource* uploadBuffer = nullptr; ID3D12Resource* uploadBuffer = nullptr;
HRESULT hr = bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc, HRESULT hr = bd->pd3dDevice->CreateCommittedResource(&props, D3D12_HEAP_FLAG_NONE, &desc,
D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&uploadBuffer)); D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&uploadBuffer));
IM_ASSERT(SUCCEEDED(hr)); IM_ASSERT(SUCCEEDED(hr));
void* mapped = nullptr; // Create temporary command list and execute immediately
D3D12_RANGE range = { 0, upload_size };
hr = uploadBuffer->Map(0, &range, &mapped);
IM_ASSERT(SUCCEEDED(hr));
for (int y = 0; y < height; y++)
memcpy((void*) ((uintptr_t) mapped + y * upload_pitch), pixels + y * width * 4, width * 4);
uploadBuffer->Unmap(0, &range);
D3D12_TEXTURE_COPY_LOCATION srcLocation = {};
D3D12_TEXTURE_COPY_LOCATION dstLocation = {};
{
srcLocation.pResource = uploadBuffer;
srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
srcLocation.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
srcLocation.PlacedFootprint.Footprint.Width = width;
srcLocation.PlacedFootprint.Footprint.Height = height;
srcLocation.PlacedFootprint.Footprint.Depth = 1;
srcLocation.PlacedFootprint.Footprint.RowPitch = upload_pitch;
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;
ID3D12Fence* fence = nullptr; ID3D12Fence* fence = nullptr;
hr = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)); hr = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence));
IM_ASSERT(SUCCEEDED(hr)); IM_ASSERT(SUCCEEDED(hr));
@ -488,16 +541,63 @@ static void ImGui_ImplDX12_CreateFontsTexture()
HANDLE event = ::CreateEvent(0, 0, 0, 0); HANDLE event = ::CreateEvent(0, 0, 0, 0);
IM_ASSERT(event != nullptr); IM_ASSERT(event != nullptr);
// FIXME-OPT: Create once and reuse?
ID3D12CommandAllocator* cmdAlloc = nullptr; ID3D12CommandAllocator* cmdAlloc = nullptr;
hr = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc)); hr = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc));
IM_ASSERT(SUCCEEDED(hr)); IM_ASSERT(SUCCEEDED(hr));
// FIXME-OPT: Can be use the one from user? (pass ID3D12GraphicsCommandList* to ImGui_ImplDX12_UpdateTextures)
ID3D12GraphicsCommandList* cmdList = nullptr; ID3D12GraphicsCommandList* cmdList = nullptr;
hr = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, nullptr, IID_PPV_ARGS(&cmdList)); hr = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, nullptr, IID_PPV_ARGS(&cmdList));
IM_ASSERT(SUCCEEDED(hr)); IM_ASSERT(SUCCEEDED(hr));
cmdList->CopyTextureRegion(&dstLocation, 0, 0, 0, &srcLocation, nullptr); // Copy to upload buffer
void* mapped = nullptr;
D3D12_RANGE range = { 0, upload_size };
hr = uploadBuffer->Map(0, &range, &mapped);
IM_ASSERT(SUCCEEDED(hr));
for (int y = 0; y < upload_h; y++)
memcpy((void*)((uintptr_t)mapped + y * upload_pitch_dst), tex->GetPixelsAt(upload_x, upload_y + y), upload_pitch_src);
uploadBuffer->Unmap(0, &range);
if (need_barrier_before_copy)
{
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = backend_tex->pTextureResource;
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
barrier.Transition.StateBefore = D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE;
barrier.Transition.StateAfter = D3D12_RESOURCE_STATE_COPY_DEST;
cmdList->ResourceBarrier(1, &barrier); cmdList->ResourceBarrier(1, &barrier);
}
D3D12_TEXTURE_COPY_LOCATION srcLocation = {};
D3D12_TEXTURE_COPY_LOCATION dstLocation = {};
{
srcLocation.pResource = uploadBuffer;
srcLocation.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
srcLocation.PlacedFootprint.Footprint.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
srcLocation.PlacedFootprint.Footprint.Width = upload_w;
srcLocation.PlacedFootprint.Footprint.Height = upload_h;
srcLocation.PlacedFootprint.Footprint.Depth = 1;
srcLocation.PlacedFootprint.Footprint.RowPitch = upload_pitch_dst;
dstLocation.pResource = backend_tex->pTextureResource;
dstLocation.Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX;
dstLocation.SubresourceIndex = 0;
}
cmdList->CopyTextureRegion(&dstLocation, upload_x, upload_y, 0, &srcLocation, nullptr);
{
D3D12_RESOURCE_BARRIER barrier = {};
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
barrier.Transition.pResource = backend_tex->pTextureResource;
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;
cmdList->ResourceBarrier(1, &barrier);
}
hr = cmdList->Close(); hr = cmdList->Close();
IM_ASSERT(SUCCEEDED(hr)); IM_ASSERT(SUCCEEDED(hr));
@ -507,6 +607,10 @@ static void ImGui_ImplDX12_CreateFontsTexture()
hr = cmdQueue->Signal(fence, 1); hr = cmdQueue->Signal(fence, 1);
IM_ASSERT(SUCCEEDED(hr)); IM_ASSERT(SUCCEEDED(hr));
// FIXME-OPT: Suboptimal?
// - To remove this may need to create NumFramesInFlight x ImGui_ImplDX12_FrameContext in backend data (mimick docking version)
// - Store per-frame in flight: upload buffer?
// - Where do cmdList and cmdAlloc fit?
fence->SetEventOnCompletion(1, event); fence->SetEventOnCompletion(1, event);
::WaitForSingleObject(event, INFINITE); ::WaitForSingleObject(event, INFINITE);
@ -515,22 +619,11 @@ static void ImGui_ImplDX12_CreateFontsTexture()
::CloseHandle(event); ::CloseHandle(event);
fence->Release(); fence->Release();
uploadBuffer->Release(); uploadBuffer->Release();
tex->SetStatus(ImTextureStatus_OK);
// Create texture view
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;
bd->pd3dDevice->CreateShaderResourceView(pTexture, &srvDesc, font_tex->hFontSrvCpuDescHandle);
SafeRelease(font_tex->pTextureResource);
font_tex->pTextureResource = pTexture;
} }
// Store our identifier if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames >= (int)bd->numFramesInFlight)
io.Fonts->SetTexID((ImTextureID)font_tex->hFontSrvGpuDescHandle.ptr); ImGui_ImplDX12_DestroyTexture(tex);
} }
bool ImGui_ImplDX12_CreateDeviceObjects() bool ImGui_ImplDX12_CreateDeviceObjects()
@ -574,7 +667,7 @@ bool ImGui_ImplDX12_CreateDeviceObjects()
staticSampler.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS; staticSampler.ComparisonFunc = D3D12_COMPARISON_FUNC_ALWAYS;
staticSampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK; staticSampler.BorderColor = D3D12_STATIC_BORDER_COLOR_TRANSPARENT_BLACK;
staticSampler.MinLOD = 0.f; staticSampler.MinLOD = 0.f;
staticSampler.MaxLOD = 0.f; staticSampler.MaxLOD = D3D12_FLOAT32_MAX;
staticSampler.ShaderRegister = 0; staticSampler.ShaderRegister = 0;
staticSampler.RegisterSpace = 0; staticSampler.RegisterSpace = 0;
staticSampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL; staticSampler.ShaderVisibility = D3D12_SHADER_VISIBILITY_PIXEL;
@ -612,7 +705,7 @@ bool ImGui_ImplDX12_CreateDeviceObjects()
return false; return false;
} }
PFN_D3D12_SERIALIZE_ROOT_SIGNATURE D3D12SerializeRootSignatureFn = (PFN_D3D12_SERIALIZE_ROOT_SIGNATURE)::GetProcAddress(d3d12_dll, "D3D12SerializeRootSignature"); _PFN_D3D12_SERIALIZE_ROOT_SIGNATURE D3D12SerializeRootSignatureFn = (_PFN_D3D12_SERIALIZE_ROOT_SIGNATURE)(void*)::GetProcAddress(d3d12_dll, "D3D12SerializeRootSignature");
if (D3D12SerializeRootSignatureFn == nullptr) if (D3D12SerializeRootSignatureFn == nullptr)
return false; return false;
@ -762,8 +855,6 @@ bool ImGui_ImplDX12_CreateDeviceObjects()
if (result_pipeline_state != S_OK) if (result_pipeline_state != S_OK)
return false; return false;
ImGui_ImplDX12_CreateFontsTexture();
return true; return true;
} }
@ -786,14 +877,34 @@ void ImGui_ImplDX12_InvalidateDeviceObjects()
SafeRelease(bd->pRootSignature); SafeRelease(bd->pRootSignature);
SafeRelease(bd->pPipelineState); SafeRelease(bd->pPipelineState);
// Free SRV descriptor used by texture // Destroy all textures
ImGuiIO& io = ImGui::GetIO(); for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
ImGui_ImplDX12_Texture* font_tex = &bd->FontTexture; if (tex->RefCount == 1)
bd->InitInfo.SrvDescriptorFreeFn(&bd->InitInfo, font_tex->hFontSrvCpuDescHandle, font_tex->hFontSrvGpuDescHandle); ImGui_ImplDX12_DestroyTexture(tex);
SafeRelease(font_tex->pTextureResource);
io.Fonts->SetTexID(0); // We copied bd->hFontSrvGpuDescHandle to io.Fonts->TexID so let's clear that as well.
} }
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
static void ImGui_ImplDX12_InitLegacySingleDescriptorMode(ImGui_ImplDX12_InitInfo* init_info)
{
// Wrap legacy behavior of passing space for a single descriptor
IM_ASSERT(init_info->LegacySingleSrvCpuDescriptor.ptr != 0 && init_info->LegacySingleSrvGpuDescriptor.ptr != 0);
init_info->SrvDescriptorAllocFn = [](ImGui_ImplDX12_InitInfo*, D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_handle)
{
ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
IM_ASSERT(bd->LegacySingleDescriptorUsed == false && "Only 1 simultaneous texture allowed with legacy ImGui_ImplDX12_Init() signature!");
*out_cpu_handle = bd->InitInfo.LegacySingleSrvCpuDescriptor;
*out_gpu_handle = bd->InitInfo.LegacySingleSrvGpuDescriptor;
bd->LegacySingleDescriptorUsed = true;
};
init_info->SrvDescriptorFreeFn = [](ImGui_ImplDX12_InitInfo*, D3D12_CPU_DESCRIPTOR_HANDLE, D3D12_GPU_DESCRIPTOR_HANDLE)
{
ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
IM_ASSERT(bd->LegacySingleDescriptorUsed == true);
bd->LegacySingleDescriptorUsed = false;
};
}
#endif
bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info) bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info)
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
@ -816,7 +927,9 @@ bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info)
io.BackendRendererUserData = (void*)bd; io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx12"; io.BackendRendererName = "imgui_impl_dx12";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
ImGui_ImplDX12_InitPlatformInterface(); ImGui_ImplDX12_InitPlatformInterface();
@ -827,29 +940,9 @@ bool ImGui_ImplDX12_Init(ImGui_ImplDX12_InitInfo* init_info)
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
if (init_info->SrvDescriptorAllocFn == nullptr) if (init_info->SrvDescriptorAllocFn == nullptr)
{ ImGui_ImplDX12_InitLegacySingleDescriptorMode(init_info);
// Wrap legacy behavior of passing space for a single descriptor
IM_ASSERT(init_info->LegacySingleSrvCpuDescriptor.ptr != 0 && init_info->LegacySingleSrvGpuDescriptor.ptr != 0);
init_info->SrvDescriptorAllocFn = [](ImGui_ImplDX12_InitInfo*, D3D12_CPU_DESCRIPTOR_HANDLE* out_cpu_handle, D3D12_GPU_DESCRIPTOR_HANDLE* out_gpu_handle)
{
ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
IM_ASSERT(bd->LegacySingleDescriptorUsed == false && "Only 1 simultaneous texture allowed with legacy ImGui_ImplDX12_Init() signature!");
*out_cpu_handle = bd->InitInfo.LegacySingleSrvCpuDescriptor;
*out_gpu_handle = bd->InitInfo.LegacySingleSrvGpuDescriptor;
bd->LegacySingleDescriptorUsed = true;
};
init_info->SrvDescriptorFreeFn = [](ImGui_ImplDX12_InitInfo*, D3D12_CPU_DESCRIPTOR_HANDLE, D3D12_GPU_DESCRIPTOR_HANDLE)
{
ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
IM_ASSERT(bd->LegacySingleDescriptorUsed == true);
bd->LegacySingleDescriptorUsed = false;
};
}
#endif #endif
// Allocate 1 SRV descriptor for the font texture
IM_ASSERT(init_info->SrvDescriptorAllocFn != nullptr && init_info->SrvDescriptorFreeFn != nullptr); IM_ASSERT(init_info->SrvDescriptorAllocFn != nullptr && init_info->SrvDescriptorFreeFn != nullptr);
init_info->SrvDescriptorAllocFn(&bd->InitInfo, &bd->FontTexture.hFontSrvCpuDescHandle, &bd->FontTexture.hFontSrvGpuDescHandle);
return true; return true;
} }
@ -877,6 +970,9 @@ bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames_in_flight, DXGI_FO
bool ret = ImGui_ImplDX12_Init(&init_info); bool ret = ImGui_ImplDX12_Init(&init_info);
ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData(); ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
bd->commandQueueOwned = true; bd->commandQueueOwned = true;
ImGuiIO& io = ImGui::GetIO();
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasTextures; // Using legacy ImGui_ImplDX12_Init() call with 1 SRV descriptor we cannot support multiple textures.
return ret; return ret;
} }
#endif #endif
@ -904,7 +1000,7 @@ void ImGui_ImplDX12_Shutdown()
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasViewports); io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
IM_DELETE(bd); IM_DELETE(bd);
} }
@ -914,7 +1010,8 @@ void ImGui_ImplDX12_NewFrame()
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX12_Init()?"); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX12_Init()?");
if (!bd->pPipelineState) if (!bd->pPipelineState)
ImGui_ImplDX12_CreateDeviceObjects(); if (!ImGui_ImplDX12_CreateDeviceObjects())
IM_ASSERT(0 && "ImGui_ImplDX12_CreateDeviceObjects() failed!");
} }
//-------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------
@ -1118,7 +1215,7 @@ static void ImGui_ImplDX12_RenderWindow(ImGuiViewport* viewport, void*)
cmd_list->ResourceBarrier(1, &barrier); cmd_list->ResourceBarrier(1, &barrier);
cmd_list->OMSetRenderTargets(1, &vd->FrameCtx[back_buffer_idx].RenderTargetCpuDescriptors, FALSE, nullptr); cmd_list->OMSetRenderTargets(1, &vd->FrameCtx[back_buffer_idx].RenderTargetCpuDescriptors, FALSE, nullptr);
if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear)) if (!(viewport->Flags & ImGuiViewportFlags_NoRendererClear))
cmd_list->ClearRenderTargetView(vd->FrameCtx[back_buffer_idx].RenderTargetCpuDescriptors, (float*)&clear_color, 0, nullptr); cmd_list->ClearRenderTargetView(vd->FrameCtx[back_buffer_idx].RenderTargetCpuDescriptors, (const float*)&clear_color, 0, nullptr);
cmd_list->SetDescriptorHeaps(1, &bd->pd3dSrvDescHeap); cmd_list->SetDescriptorHeaps(1, &bd->pd3dSrvDescHeap);
ImGui_ImplDX12_RenderDrawData(viewport->DrawData, cmd_list); ImGui_ImplDX12_RenderDrawData(viewport->DrawData, cmd_list);

View file

@ -2,8 +2,9 @@
// This needs to be used along with a Platform Backend (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'D3D12_GPU_DESCRIPTOR_HANDLE' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
@ -64,6 +65,9 @@ IMGUI_IMPL_API bool ImGui_ImplDX12_Init(ID3D12Device* device, int num_frames
IMGUI_IMPL_API bool ImGui_ImplDX12_CreateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplDX12_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplDX12_InvalidateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX12_InvalidateDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex);
// [BETA] Selected render state data shared with callbacks. // [BETA] Selected render state data shared with callbacks.
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplDX12_RenderDrawData() call. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplDX12_RenderDrawData() call.
// (Please open an issue if you feel you need access to more data) // (Please open an issue if you feel you need access to more data)

View file

@ -2,8 +2,9 @@
// This needs to be used along with a Platform Backend (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9. // [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
@ -18,6 +19,7 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-06-11: DirectX9: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
// 2024-10-07: DirectX9: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-10-07: DirectX9: Changed default texture sampler to Clamp instead of Repeat/Wrap.
// 2024-02-12: DirectX9: Using RGBA format when supported by the driver to avoid CPU side conversion. (#6575) // 2024-02-12: DirectX9: Using RGBA format when supported by the driver to avoid CPU side conversion. (#6575)
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11. // 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
@ -46,13 +48,18 @@
// DirectX // DirectX
#include <d3d9.h> #include <d3d9.h>
// Clang/GCC warnings with -Weverything
#if defined(__clang__)
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast // yes, they are more terse.
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#endif
// DirectX data // DirectX data
struct ImGui_ImplDX9_Data struct ImGui_ImplDX9_Data
{ {
LPDIRECT3DDEVICE9 pd3dDevice; LPDIRECT3DDEVICE9 pd3dDevice;
LPDIRECT3DVERTEXBUFFER9 pVB; LPDIRECT3DVERTEXBUFFER9 pVB;
LPDIRECT3DINDEXBUFFER9 pIB; LPDIRECT3DINDEXBUFFER9 pIB;
LPDIRECT3DTEXTURE9 FontTexture;
int VertexBufferSize; int VertexBufferSize;
int IndexBufferSize; int IndexBufferSize;
bool HasRgbaSupport; bool HasRgbaSupport;
@ -171,6 +178,13 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
LPDIRECT3DDEVICE9 device = bd->pd3dDevice; LPDIRECT3DDEVICE9 device = bd->pd3dDevice;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplDX9_UpdateTexture(tex);
// Create and grow buffers if needed // Create and grow buffers if needed
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount) if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
{ {
@ -349,8 +363,12 @@ bool ImGui_ImplDX9_Init(IDirect3DDevice9* device)
io.BackendRendererUserData = (void*)bd; io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_dx9"; io.BackendRendererName = "imgui_impl_dx9";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = 4096;
bd->pd3dDevice = device; bd->pd3dDevice = device;
bd->pd3dDevice->AddRef(); bd->pd3dDevice->AddRef();
bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8); bd->HasRgbaSupport = ImGui_ImplDX9_CheckFormatSupport(bd->pd3dDevice, D3DFMT_A8B8G8R8);
@ -371,12 +389,12 @@ void ImGui_ImplDX9_Shutdown()
if (bd->pd3dDevice) { bd->pd3dDevice->Release(); } if (bd->pd3dDevice) { bd->pd3dDevice->Release(); }
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasViewports); io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
IM_DELETE(bd); IM_DELETE(bd);
} }
// Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices) // Convert RGBA32 to BGRA32 (because RGBA32 is not well supported by DX9 devices)
static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h) static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, const ImU32* src, int src_pitch, ImU32* dst, int dst_pitch, int w, int h)
{ {
#ifndef IMGUI_USE_BGRA_PACKED_COLOR #ifndef IMGUI_USE_BGRA_PACKED_COLOR
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
@ -387,8 +405,8 @@ static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, ImU32* src, int
#endif #endif
for (int y = 0; y < h; y++) for (int y = 0; y < h; y++)
{ {
ImU32* src_p = (ImU32*)((unsigned char*)src + src_pitch * y); const ImU32* src_p = (const ImU32*)(const void*)((const unsigned char*)src + src_pitch * y);
ImU32* dst_p = (ImU32*)((unsigned char*)dst + dst_pitch * y); ImU32* dst_p = (ImU32*)(void*)((unsigned char*)dst + dst_pitch * y);
if (convert_rgba_to_bgra) if (convert_rgba_to_bgra)
for (int x = w; x > 0; x--, src_p++, dst_p++) // Convert copy for (int x = w; x > 0; x--, src_p++, dst_p++) // Convert copy
*dst_p = IMGUI_COL_TO_DX9_ARGB(*src_p); *dst_p = IMGUI_COL_TO_DX9_ARGB(*src_p);
@ -397,28 +415,61 @@ static void ImGui_ImplDX9_CopyTextureRegion(bool tex_use_colors, ImU32* src, int
} }
} }
static bool ImGui_ImplDX9_CreateFontsTexture() void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex)
{ {
// Build texture atlas
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
unsigned char* pixels;
int width, height, bytes_per_pixel;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel);
// Upload texture to graphics system if (tex->Status == ImTextureStatus_WantCreate)
bd->FontTexture = nullptr; {
if (bd->pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, bd->HasRgbaSupport ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &bd->FontTexture, nullptr) < 0) // Create and upload new texture to graphics system
return false; //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
D3DLOCKED_RECT tex_locked_rect; IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
if (bd->FontTexture->LockRect(0, &tex_locked_rect, nullptr, 0) != D3D_OK) IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
return false; LPDIRECT3DTEXTURE9 dx_tex = nullptr;
ImGui_ImplDX9_CopyTextureRegion(io.Fonts->TexPixelsUseColors, (ImU32*)pixels, width * bytes_per_pixel, (ImU32*)tex_locked_rect.pBits, (int)tex_locked_rect.Pitch, width, height); HRESULT hr = bd->pd3dDevice->CreateTexture(tex->Width, tex->Height, 1, D3DUSAGE_DYNAMIC, bd->HasRgbaSupport ? D3DFMT_A8B8G8R8 : D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &dx_tex, nullptr);
bd->FontTexture->UnlockRect(0); if (hr < 0)
{
IM_ASSERT(hr >= 0 && "Backend failed to create texture!");
return;
}
// Store our identifier D3DLOCKED_RECT locked_rect;
io.Fonts->SetTexID((ImTextureID)bd->FontTexture); if (dx_tex->LockRect(0, &locked_rect, nullptr, 0) == D3D_OK)
return true; {
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixels(), tex->Width * 4, (ImU32*)locked_rect.pBits, (ImU32)locked_rect.Pitch, tex->Width, tex->Height);
dx_tex->UnlockRect(0);
}
// Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)dx_tex);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantUpdates)
{
// Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)(intptr_t)tex->TexID;
RECT update_rect = { (LONG)tex->UpdateRect.x, (LONG)tex->UpdateRect.y, (LONG)(tex->UpdateRect.x + tex->UpdateRect.w), (LONG)(tex->UpdateRect.y + tex->UpdateRect.h) };
D3DLOCKED_RECT locked_rect;
if (backend_tex->LockRect(0, &locked_rect, &update_rect, 0) == D3D_OK)
for (ImTextureRect& r : tex->Updates)
ImGui_ImplDX9_CopyTextureRegion(tex->UseColors, (ImU32*)tex->GetPixelsAt(r.x, r.y), tex->Width * 4,
(ImU32*)locked_rect.pBits + (r.x - update_rect.left) + (r.y - update_rect.top) * (locked_rect.Pitch / 4), (int)locked_rect.Pitch, r.w, r.h);
backend_tex->UnlockRect(0);
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{
LPDIRECT3DTEXTURE9 backend_tex = (LPDIRECT3DTEXTURE9)tex->TexID;
if (backend_tex == nullptr)
return;
IM_ASSERT(tex->TexID == (ImTextureID)(intptr_t)backend_tex);
backend_tex->Release();
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
}
} }
bool ImGui_ImplDX9_CreateDeviceObjects() bool ImGui_ImplDX9_CreateDeviceObjects()
@ -426,8 +477,6 @@ bool ImGui_ImplDX9_CreateDeviceObjects()
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd || !bd->pd3dDevice) if (!bd || !bd->pd3dDevice)
return false; return false;
if (!ImGui_ImplDX9_CreateFontsTexture())
return false;
ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows(); ImGui_ImplDX9_CreateDeviceObjectsForPlatformWindows();
return true; return true;
} }
@ -437,9 +486,16 @@ void ImGui_ImplDX9_InvalidateDeviceObjects()
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd || !bd->pd3dDevice) if (!bd || !bd->pd3dDevice)
return; return;
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
{
tex->SetStatus(ImTextureStatus_WantDestroy);
ImGui_ImplDX9_UpdateTexture(tex);
}
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; } if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; } if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
if (bd->FontTexture) { bd->FontTexture->Release(); bd->FontTexture = nullptr; ImGui::GetIO().Fonts->SetTexID(0); } // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well.
ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows(); ImGui_ImplDX9_InvalidateDeviceObjectsForPlatformWindows();
} }
@ -447,11 +503,7 @@ bool ImGui_ImplDX9_NewFrame()
{ {
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX9_Init()?"); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX9_Init()?");
IM_UNUSED(bd);
if (!bd->FontTexture)
return ImGui_ImplDX9_CreateDeviceObjects();
return true;
} }
//-------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------

View file

@ -2,8 +2,9 @@
// This needs to be used along with a Platform Backend (e.g. Win32) // This needs to be used along with a Platform Backend (e.g. Win32)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9. // [X] Renderer: IMGUI_USE_BGRA_PACKED_COLOR support, as this is the optimal color encoding for DirectX9.
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
@ -31,4 +32,7 @@ IMGUI_IMPL_API void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data);
IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplDX9_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplDX9_InvalidateDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplDX9_UpdateTexture(ImTextureData* tex);
#endif // #ifndef IMGUI_DISABLE #endif // #ifndef IMGUI_DISABLE

View file

@ -10,6 +10,7 @@
// [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Resizing cursors requires GLFW 3.4+! Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Resizing cursors requires GLFW 3.4+! Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// [X] Multiple Dear ImGui contexts support.
// Missing features or Issues: // Missing features or Issues:
// [ ] Touch events are only correctly identified as Touch on Windows. This create issues with some interactions. GLFW doesn't provide a way to identify touch inputs from mouse inputs, we cannot call io.AddMouseSourceEvent() to identify the source. We provide a Windows-specific workaround. // [ ] Touch events are only correctly identified as Touch on Windows. This create issues with some interactions. GLFW doesn't provide a way to identify touch inputs from mouse inputs, we cannot call io.AddMouseSourceEvent() to identify the source. We provide a Windows-specific workaround.
// [ ] Missing ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress cursors. // [ ] Missing ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress cursors.
@ -31,6 +32,10 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-06-18: Added support for multiple Dear ImGui contexts. (#8676, #8239, #8069)
// 2025-06-11: Added ImGui_ImplGlfw_GetContentScaleForWindow(GLFWwindow* window) and ImGui_ImplGlfw_GetContentScaleForMonitor(GLFWmonitor* monitor) helper to facilitate making DPI-aware apps.
// 2025-05-15: [Docking] Add Platform_GetWindowFramebufferScale() handler, to allow varying Retina display density on multiple monitors.
// 2025-04-26: [Docking] Disable multi-viewports under Wayland. (#8587)
// 2025-03-10: Map GLFW_KEY_WORLD_1 and GLFW_KEY_WORLD_2 into ImGuiKey_Oem102. // 2025-03-10: Map GLFW_KEY_WORLD_1 and GLFW_KEY_WORLD_2 into ImGuiKey_Oem102.
// 2025-03-03: Fixed clipboard handler assertion when using GLFW <= 3.2.1 compiled with asserts enabled. // 2025-03-03: Fixed clipboard handler assertion when using GLFW <= 3.2.1 compiled with asserts enabled.
// 2025-02-21: [Docking] Update monitors and work areas information every frame, as the later may change regardless of monitor changes. (#8415) // 2025-02-21: [Docking] Update monitors and work areas information every frame, as the later may change regardless of monitor changes. (#8415)
@ -102,6 +107,8 @@
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast #pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#elif defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe
#endif #endif
// GLFW // GLFW
@ -123,6 +130,7 @@
#ifndef _WIN32 #ifndef _WIN32
#include <unistd.h> // for usleep() #include <unistd.h> // for usleep()
#endif #endif
#include <stdio.h> // for snprintf()
#ifdef __EMSCRIPTEN__ #ifdef __EMSCRIPTEN__
#include <emscripten.h> #include <emscripten.h>
@ -162,6 +170,17 @@
#define GLFW_HAS_GAMEPAD_API (GLFW_VERSION_COMBINED >= 3300) // 3.3+ glfwGetGamepadState() new api #define GLFW_HAS_GAMEPAD_API (GLFW_VERSION_COMBINED >= 3300) // 3.3+ glfwGetGamepadState() new api
#define GLFW_HAS_GETKEYNAME (GLFW_VERSION_COMBINED >= 3200) // 3.2+ glfwGetKeyName() #define GLFW_HAS_GETKEYNAME (GLFW_VERSION_COMBINED >= 3200) // 3.2+ glfwGetKeyName()
#define GLFW_HAS_GETERROR (GLFW_VERSION_COMBINED >= 3300) // 3.3+ glfwGetError() #define GLFW_HAS_GETERROR (GLFW_VERSION_COMBINED >= 3300) // 3.3+ glfwGetError()
#define GLFW_HAS_GETPLATFORM (GLFW_VERSION_COMBINED >= 3400) // 3.4+ glfwGetPlatform()
// Map GLFWWindow* to ImGuiContext*.
// - Would be simpler if we could use glfwSetWindowUserPointer()/glfwGetWindowUserPointer(), but this is a single and shared resource.
// - Would be simpler if we could use e.g. std::map<> as well. But we don't.
// - This is not particularly optimized as we expect size to be small and queries to be rare.
struct ImGui_ImplGlfw_WindowToContext { GLFWwindow* Window; ImGuiContext* Context; };
static ImVector<ImGui_ImplGlfw_WindowToContext> g_ContextMap;
static void ImGui_ImplGlfw_ContextMap_Add(GLFWwindow* window, ImGuiContext* ctx) { g_ContextMap.push_back(ImGui_ImplGlfw_WindowToContext{ window, ctx }); }
static void ImGui_ImplGlfw_ContextMap_Remove(GLFWwindow* window) { for (ImGui_ImplGlfw_WindowToContext& entry : g_ContextMap) if (entry.Window == window) { g_ContextMap.erase_unsorted(&entry); return; } }
static ImGuiContext* ImGui_ImplGlfw_ContextMap_Get(GLFWwindow* window) { for (ImGui_ImplGlfw_WindowToContext& entry : g_ContextMap) if (entry.Window == window) return entry.Context; return nullptr; }
// GLFW data // GLFW data
enum GlfwClientApi enum GlfwClientApi
@ -173,6 +192,7 @@ enum GlfwClientApi
struct ImGui_ImplGlfw_Data struct ImGui_ImplGlfw_Data
{ {
ImGuiContext* Context;
GLFWwindow* Window; GLFWwindow* Window;
GlfwClientApi ClientApi; GlfwClientApi ClientApi;
double Time; double Time;
@ -184,6 +204,7 @@ struct ImGui_ImplGlfw_Data
GLFWwindow* KeyOwnerWindows[GLFW_KEY_LAST]; GLFWwindow* KeyOwnerWindows[GLFW_KEY_LAST];
bool InstalledCallbacks; bool InstalledCallbacks;
bool CallbacksChainForAllWindows; bool CallbacksChainForAllWindows;
char BackendPlatformName[32];
#ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3 #ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3
const char* CanvasSelector; const char* CanvasSelector;
#endif #endif
@ -211,10 +232,18 @@ struct ImGui_ImplGlfw_Data
// (passing install_callbacks=false in ImGui_ImplGlfw_InitXXX functions), set the current dear imgui context and then call our callbacks. // (passing install_callbacks=false in ImGui_ImplGlfw_InitXXX functions), set the current dear imgui context and then call our callbacks.
// - Otherwise we may need to store a GLFWWindow* -> ImGuiContext* map and handle this in the backend, adding a little bit of extra complexity to it. // - Otherwise we may need to store a GLFWWindow* -> ImGuiContext* map and handle this in the backend, adding a little bit of extra complexity to it.
// FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context. // FIXME: some shared resources (mouse cursor shape, gamepad) are mishandled when using multi-context.
namespace ImGui { extern ImGuiIO& GetIO(ImGuiContext*); }
static ImGui_ImplGlfw_Data* ImGui_ImplGlfw_GetBackendData() static ImGui_ImplGlfw_Data* ImGui_ImplGlfw_GetBackendData()
{ {
// Get data for current context
return ImGui::GetCurrentContext() ? (ImGui_ImplGlfw_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr; return ImGui::GetCurrentContext() ? (ImGui_ImplGlfw_Data*)ImGui::GetIO().BackendPlatformUserData : nullptr;
} }
static ImGui_ImplGlfw_Data* ImGui_ImplGlfw_GetBackendData(GLFWwindow* window)
{
// Get data for a given GLFW window, regardless of current context (since GLFW events are sent together)
ImGuiContext* ctx = ImGui_ImplGlfw_ContextMap_Get(window);
return (ImGui_ImplGlfw_Data*)ImGui::GetIO(ctx).BackendPlatformUserData;
}
// Forward Declarations // Forward Declarations
static void ImGui_ImplGlfw_UpdateMonitors(); static void ImGui_ImplGlfw_UpdateMonitors();
@ -355,42 +384,39 @@ ImGuiKey ImGui_ImplGlfw_KeyToImGuiKey(int keycode, int scancode)
// X11 does not include current pressed/released modifier key in 'mods' flags submitted by GLFW // X11 does not include current pressed/released modifier key in 'mods' flags submitted by GLFW
// See https://github.com/ocornut/imgui/issues/6034 and https://github.com/glfw/glfw/issues/1630 // See https://github.com/ocornut/imgui/issues/6034 and https://github.com/glfw/glfw/issues/1630
static void ImGui_ImplGlfw_UpdateKeyModifiers(GLFWwindow* window) static void ImGui_ImplGlfw_UpdateKeyModifiers(ImGuiIO& io, GLFWwindow* window)
{ {
ImGuiIO& io = ImGui::GetIO();
io.AddKeyEvent(ImGuiMod_Ctrl, (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS)); io.AddKeyEvent(ImGuiMod_Ctrl, (glfwGetKey(window, GLFW_KEY_LEFT_CONTROL) == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_CONTROL) == GLFW_PRESS));
io.AddKeyEvent(ImGuiMod_Shift, (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS)); io.AddKeyEvent(ImGuiMod_Shift, (glfwGetKey(window, GLFW_KEY_LEFT_SHIFT) == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_SHIFT) == GLFW_PRESS));
io.AddKeyEvent(ImGuiMod_Alt, (glfwGetKey(window, GLFW_KEY_LEFT_ALT) == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS)); io.AddKeyEvent(ImGuiMod_Alt, (glfwGetKey(window, GLFW_KEY_LEFT_ALT) == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_ALT) == GLFW_PRESS));
io.AddKeyEvent(ImGuiMod_Super, (glfwGetKey(window, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS)); io.AddKeyEvent(ImGuiMod_Super, (glfwGetKey(window, GLFW_KEY_LEFT_SUPER) == GLFW_PRESS) || (glfwGetKey(window, GLFW_KEY_RIGHT_SUPER) == GLFW_PRESS));
} }
static bool ImGui_ImplGlfw_ShouldChainCallback(GLFWwindow* window) static bool ImGui_ImplGlfw_ShouldChainCallback(ImGui_ImplGlfw_Data* bd, GLFWwindow* window)
{ {
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
return bd->CallbacksChainForAllWindows ? true : (window == bd->Window); return bd->CallbacksChainForAllWindows ? true : (window == bd->Window);
} }
void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods) void ImGui_ImplGlfw_MouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
{ {
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(window);
if (bd->PrevUserCallbackMousebutton != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window)) if (bd->PrevUserCallbackMousebutton != nullptr && ImGui_ImplGlfw_ShouldChainCallback(bd, window))
bd->PrevUserCallbackMousebutton(window, button, action, mods); bd->PrevUserCallbackMousebutton(window, button, action, mods);
// Workaround for Linux: ignore mouse up events which are following an focus loss following a viewport creation // Workaround for Linux: ignore mouse up events which are following an focus loss following a viewport creation
if (bd->MouseIgnoreButtonUp && action == GLFW_RELEASE) if (bd->MouseIgnoreButtonUp && action == GLFW_RELEASE)
return; return;
ImGui_ImplGlfw_UpdateKeyModifiers(window); ImGuiIO& io = ImGui::GetIO(bd->Context);
ImGui_ImplGlfw_UpdateKeyModifiers(io, window);
ImGuiIO& io = ImGui::GetIO();
if (button >= 0 && button < ImGuiMouseButton_COUNT) if (button >= 0 && button < ImGuiMouseButton_COUNT)
io.AddMouseButtonEvent(button, action == GLFW_PRESS); io.AddMouseButtonEvent(button, action == GLFW_PRESS);
} }
void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset) void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
{ {
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(window);
if (bd->PrevUserCallbackScroll != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window)) if (bd->PrevUserCallbackScroll != nullptr && ImGui_ImplGlfw_ShouldChainCallback(bd, window))
bd->PrevUserCallbackScroll(window, xoffset, yoffset); bd->PrevUserCallbackScroll(window, xoffset, yoffset);
#ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3 #ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3
@ -398,7 +424,7 @@ void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double xoffset, double yo
return; return;
#endif #endif
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO(bd->Context);
io.AddMouseWheelEvent((float)xoffset, (float)yoffset); io.AddMouseWheelEvent((float)xoffset, (float)yoffset);
} }
@ -438,21 +464,21 @@ static int ImGui_ImplGlfw_TranslateUntranslatedKey(int key, int scancode)
void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int keycode, int scancode, int action, int mods) void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int keycode, int scancode, int action, int mods)
{ {
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(window);
if (bd->PrevUserCallbackKey != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window)) if (bd->PrevUserCallbackKey != nullptr && ImGui_ImplGlfw_ShouldChainCallback(bd, window))
bd->PrevUserCallbackKey(window, keycode, scancode, action, mods); bd->PrevUserCallbackKey(window, keycode, scancode, action, mods);
if (action != GLFW_PRESS && action != GLFW_RELEASE) if (action != GLFW_PRESS && action != GLFW_RELEASE)
return; return;
ImGui_ImplGlfw_UpdateKeyModifiers(window); ImGuiIO& io = ImGui::GetIO(bd->Context);
ImGui_ImplGlfw_UpdateKeyModifiers(io, window);
if (keycode >= 0 && keycode < IM_ARRAYSIZE(bd->KeyOwnerWindows)) if (keycode >= 0 && keycode < IM_ARRAYSIZE(bd->KeyOwnerWindows))
bd->KeyOwnerWindows[keycode] = (action == GLFW_PRESS) ? window : nullptr; bd->KeyOwnerWindows[keycode] = (action == GLFW_PRESS) ? window : nullptr;
keycode = ImGui_ImplGlfw_TranslateUntranslatedKey(keycode, scancode); keycode = ImGui_ImplGlfw_TranslateUntranslatedKey(keycode, scancode);
ImGuiIO& io = ImGui::GetIO();
ImGuiKey imgui_key = ImGui_ImplGlfw_KeyToImGuiKey(keycode, scancode); ImGuiKey imgui_key = ImGui_ImplGlfw_KeyToImGuiKey(keycode, scancode);
io.AddKeyEvent(imgui_key, (action == GLFW_PRESS)); io.AddKeyEvent(imgui_key, (action == GLFW_PRESS));
io.SetKeyEventNativeData(imgui_key, keycode, scancode); // To support legacy indexing (<1.87 user code) io.SetKeyEventNativeData(imgui_key, keycode, scancode); // To support legacy indexing (<1.87 user code)
@ -460,25 +486,25 @@ void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int keycode, int scancode, i
void ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused) void ImGui_ImplGlfw_WindowFocusCallback(GLFWwindow* window, int focused)
{ {
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(window);
if (bd->PrevUserCallbackWindowFocus != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window)) if (bd->PrevUserCallbackWindowFocus != nullptr && ImGui_ImplGlfw_ShouldChainCallback(bd, window))
bd->PrevUserCallbackWindowFocus(window, focused); bd->PrevUserCallbackWindowFocus(window, focused);
// Workaround for Linux: when losing focus with MouseIgnoreButtonUpWaitForFocusLoss set, we will temporarily ignore subsequent Mouse Up events // Workaround for Linux: when losing focus with MouseIgnoreButtonUpWaitForFocusLoss set, we will temporarily ignore subsequent Mouse Up events
bd->MouseIgnoreButtonUp = (bd->MouseIgnoreButtonUpWaitForFocusLoss && focused == 0); bd->MouseIgnoreButtonUp = (bd->MouseIgnoreButtonUpWaitForFocusLoss && focused == 0);
bd->MouseIgnoreButtonUpWaitForFocusLoss = false; bd->MouseIgnoreButtonUpWaitForFocusLoss = false;
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO(bd->Context);
io.AddFocusEvent(focused != 0); io.AddFocusEvent(focused != 0);
} }
void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y) void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y)
{ {
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(window);
if (bd->PrevUserCallbackCursorPos != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window)) if (bd->PrevUserCallbackCursorPos != nullptr && ImGui_ImplGlfw_ShouldChainCallback(bd, window))
bd->PrevUserCallbackCursorPos(window, x, y); bd->PrevUserCallbackCursorPos(window, x, y);
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO(bd->Context);
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{ {
int window_x, window_y; int window_x, window_y;
@ -494,11 +520,11 @@ void ImGui_ImplGlfw_CursorPosCallback(GLFWwindow* window, double x, double y)
// so we back it up and restore on Leave/Enter (see https://github.com/ocornut/imgui/issues/4984) // so we back it up and restore on Leave/Enter (see https://github.com/ocornut/imgui/issues/4984)
void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered) void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered)
{ {
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(window);
if (bd->PrevUserCallbackCursorEnter != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window)) if (bd->PrevUserCallbackCursorEnter != nullptr && ImGui_ImplGlfw_ShouldChainCallback(bd, window))
bd->PrevUserCallbackCursorEnter(window, entered); bd->PrevUserCallbackCursorEnter(window, entered);
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO(bd->Context);
if (entered) if (entered)
{ {
bd->MouseWindow = window; bd->MouseWindow = window;
@ -514,11 +540,11 @@ void ImGui_ImplGlfw_CursorEnterCallback(GLFWwindow* window, int entered)
void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c) void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c)
{ {
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(window);
if (bd->PrevUserCallbackChar != nullptr && ImGui_ImplGlfw_ShouldChainCallback(window)) if (bd->PrevUserCallbackChar != nullptr && ImGui_ImplGlfw_ShouldChainCallback(bd, window))
bd->PrevUserCallbackChar(window, c); bd->PrevUserCallbackChar(window, c);
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO(bd->Context);
io.AddInputCharacter(c); io.AddInputCharacter(c);
} }
@ -528,17 +554,18 @@ void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor*, int)
} }
#ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3 #ifdef EMSCRIPTEN_USE_EMBEDDED_GLFW3
static EM_BOOL ImGui_ImplEmscripten_WheelCallback(int, const EmscriptenWheelEvent* ev, void*) static EM_BOOL ImGui_ImplEmscripten_WheelCallback(int, const EmscriptenWheelEvent* ev, void* user_data)
{ {
// Mimic Emscripten_HandleWheel() in SDL. // Mimic Emscripten_HandleWheel() in SDL.
// Corresponding equivalent in GLFW JS emulation layer has incorrect quantizing preventing small values. See #6096 // Corresponding equivalent in GLFW JS emulation layer has incorrect quantizing preventing small values. See #6096
ImGui_ImplGlfw_Data* bd = (ImGui_ImplGlfw_Data*)user_data;
float multiplier = 0.0f; float multiplier = 0.0f;
if (ev->deltaMode == DOM_DELTA_PIXEL) { multiplier = 1.0f / 100.0f; } // 100 pixels make up a step. if (ev->deltaMode == DOM_DELTA_PIXEL) { multiplier = 1.0f / 100.0f; } // 100 pixels make up a step.
else if (ev->deltaMode == DOM_DELTA_LINE) { multiplier = 1.0f / 3.0f; } // 3 lines make up a step. else if (ev->deltaMode == DOM_DELTA_LINE) { multiplier = 1.0f / 3.0f; } // 3 lines make up a step.
else if (ev->deltaMode == DOM_DELTA_PAGE) { multiplier = 80.0f; } // A page makes up 80 steps. else if (ev->deltaMode == DOM_DELTA_PAGE) { multiplier = 80.0f; } // A page makes up 80 steps.
float wheel_x = ev->deltaX * -multiplier; float wheel_x = ev->deltaX * -multiplier;
float wheel_y = ev->deltaY * -multiplier; float wheel_y = ev->deltaY * -multiplier;
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO(bd->Context);
io.AddMouseWheelEvent(wheel_x, wheel_y); io.AddMouseWheelEvent(wheel_x, wheel_y);
//IMGUI_DEBUG_LOG("[Emsc] mode %d dx: %.2f, dy: %.2f, dz: %.2f --> feed %.2f %.2f\n", (int)ev->deltaMode, ev->deltaX, ev->deltaY, ev->deltaZ, wheel_x, wheel_y); //IMGUI_DEBUG_LOG("[Emsc] mode %d dx: %.2f, dy: %.2f, dz: %.2f --> feed %.2f %.2f\n", (int)ev->deltaMode, ev->deltaX, ev->deltaY, ev->deltaZ, wheel_x, wheel_y);
return EM_TRUE; return EM_TRUE;
@ -551,7 +578,7 @@ static LRESULT CALLBACK ImGui_ImplGlfw_WndProc(HWND hWnd, UINT msg, WPARAM wPara
void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window) void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window)
{ {
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(window);
IM_ASSERT(bd->InstalledCallbacks == false && "Callbacks already installed!"); IM_ASSERT(bd->InstalledCallbacks == false && "Callbacks already installed!");
IM_ASSERT(bd->Window == window); IM_ASSERT(bd->Window == window);
@ -568,7 +595,7 @@ void ImGui_ImplGlfw_InstallCallbacks(GLFWwindow* window)
void ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window) void ImGui_ImplGlfw_RestoreCallbacks(GLFWwindow* window)
{ {
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(window);
IM_ASSERT(bd->InstalledCallbacks == true && "Callbacks not installed!"); IM_ASSERT(bd->InstalledCallbacks == true && "Callbacks not installed!");
IM_ASSERT(bd->Window == window); IM_ASSERT(bd->Window == window);
@ -618,19 +645,30 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw
// Setup backend capabilities flags // Setup backend capabilities flags
ImGui_ImplGlfw_Data* bd = IM_NEW(ImGui_ImplGlfw_Data)(); ImGui_ImplGlfw_Data* bd = IM_NEW(ImGui_ImplGlfw_Data)();
snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_glfw (%d)", GLFW_VERSION_COMBINED);
io.BackendPlatformUserData = (void*)bd; io.BackendPlatformUserData = (void*)bd;
io.BackendPlatformName = "imgui_impl_glfw"; io.BackendPlatformName = bd->BackendPlatformName;
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
bool has_viewports = false;
#ifndef __EMSCRIPTEN__ #ifndef __EMSCRIPTEN__
has_viewports = true;
#if GLFW_HAS_GETPLATFORM
if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND)
has_viewports = false;
#endif
if (has_viewports)
io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional) io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional)
#endif #endif
#if GLFW_HAS_MOUSE_PASSTHROUGH || GLFW_HAS_WINDOW_HOVERED #if GLFW_HAS_MOUSE_PASSTHROUGH || GLFW_HAS_WINDOW_HOVERED
io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport; // We can call io.AddMouseViewportEvent() with correct data (optional) io.BackendFlags |= ImGuiBackendFlags_HasMouseHoveredViewport; // We can call io.AddMouseViewportEvent() with correct data (optional)
#endif #endif
bd->Context = ImGui::GetCurrentContext();
bd->Window = window; bd->Window = window;
bd->Time = 0.0; bd->Time = 0.0;
ImGui_ImplGlfw_ContextMap_Add(window, bd->Context);
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
#if GLFW_VERSION_COMBINED < 3300 #if GLFW_VERSION_COMBINED < 3300
@ -690,11 +728,14 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw
#else #else
IM_UNUSED(main_viewport); IM_UNUSED(main_viewport);
#endif #endif
if (has_viewports)
ImGui_ImplGlfw_InitMultiViewportSupport(); ImGui_ImplGlfw_InitMultiViewportSupport();
// Windows: register a WndProc hook so we can intercept some messages. // Windows: register a WndProc hook so we can intercept some messages.
#ifdef _WIN32 #ifdef _WIN32
bd->PrevWndProc = (WNDPROC)::GetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC); HWND hwnd = (HWND)main_viewport->PlatformHandleRaw;
::SetPropA(hwnd, "IMGUI_BACKEND_DATA", bd);
bd->PrevWndProc = (WNDPROC)::GetWindowLongPtrW(hwnd, GWLP_WNDPROC);
IM_ASSERT(bd->PrevWndProc != nullptr); IM_ASSERT(bd->PrevWndProc != nullptr);
::SetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC, (LONG_PTR)ImGui_ImplGlfw_WndProc); ::SetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC, (LONG_PTR)ImGui_ImplGlfw_WndProc);
#endif #endif
@ -705,7 +746,7 @@ static bool ImGui_ImplGlfw_Init(GLFWwindow* window, bool install_callbacks, Glfw
#if EMSCRIPTEN_USE_PORT_CONTRIB_GLFW3 >= 34020240817 #if EMSCRIPTEN_USE_PORT_CONTRIB_GLFW3 >= 34020240817
if (emscripten::glfw3::IsRuntimePlatformApple()) if (emscripten::glfw3::IsRuntimePlatformApple())
{ {
ImGui::GetIO().ConfigMacOSXBehaviors = true; io.ConfigMacOSXBehaviors = true;
// Due to how the browser (poorly) handles the Meta Key, this line essentially disables repeats when used. // Due to how the browser (poorly) handles the Meta Key, this line essentially disables repeats when used.
// This means that Meta + V only registers a single key-press, even if the keys are held. // This means that Meta + V only registers a single key-press, even if the keys are held.
@ -756,6 +797,7 @@ void ImGui_ImplGlfw_Shutdown()
// Windows: restore our WndProc hook // Windows: restore our WndProc hook
#ifdef _WIN32 #ifdef _WIN32
ImGuiViewport* main_viewport = ImGui::GetMainViewport(); ImGuiViewport* main_viewport = ImGui::GetMainViewport();
::SetPropA((HWND)main_viewport->PlatformHandleRaw, "IMGUI_BACKEND_DATA", nullptr);
::SetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC, (LONG_PTR)bd->PrevWndProc); ::SetWindowLongPtrW((HWND)main_viewport->PlatformHandleRaw, GWLP_WNDPROC, (LONG_PTR)bd->PrevWndProc);
bd->PrevWndProc = nullptr; bd->PrevWndProc = nullptr;
#endif #endif
@ -763,6 +805,7 @@ void ImGui_ImplGlfw_Shutdown()
io.BackendPlatformName = nullptr; io.BackendPlatformName = nullptr;
io.BackendPlatformUserData = nullptr; io.BackendPlatformUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad | ImGuiBackendFlags_PlatformHasViewports | ImGuiBackendFlags_HasMouseHoveredViewport); io.BackendFlags &= ~(ImGuiBackendFlags_HasMouseCursors | ImGuiBackendFlags_HasSetMousePos | ImGuiBackendFlags_HasGamepad | ImGuiBackendFlags_PlatformHasViewports | ImGuiBackendFlags_HasMouseHoveredViewport);
ImGui_ImplGlfw_ContextMap_Remove(bd->Window);
IM_DELETE(bd); IM_DELETE(bd);
} }
@ -869,7 +912,7 @@ static inline float Saturate(float v) { return v < 0.0f ? 0.0f : v > 1.0f ? 1.0
static void ImGui_ImplGlfw_UpdateGamepads() static void ImGui_ImplGlfw_UpdateGamepads()
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs. if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs, but see #8075
return; return;
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
@ -946,33 +989,62 @@ static void ImGui_ImplGlfw_UpdateMonitors()
monitor.WorkSize = ImVec2((float)w, (float)h); monitor.WorkSize = ImVec2((float)w, (float)h);
} }
#endif #endif
#if GLFW_HAS_PER_MONITOR_DPI float scale = ImGui_ImplGlfw_GetContentScaleForMonitor(glfw_monitors[n]);
// Warning: the validity of monitor DPI information on Windows depends on the application DPI awareness settings, which generally needs to be set in the manifest or at runtime. if (scale == 0.0f)
float x_scale, y_scale;
glfwGetMonitorContentScale(glfw_monitors[n], &x_scale, &y_scale);
if (x_scale == 0.0f)
continue; // Some accessibility applications are declaring virtual monitors with a DPI of 0, see #7902. continue; // Some accessibility applications are declaring virtual monitors with a DPI of 0, see #7902.
monitor.DpiScale = x_scale; monitor.DpiScale = scale;
#endif
monitor.PlatformHandle = (void*)glfw_monitors[n]; // [...] GLFW doc states: "guaranteed to be valid only until the monitor configuration changes" monitor.PlatformHandle = (void*)glfw_monitors[n]; // [...] GLFW doc states: "guaranteed to be valid only until the monitor configuration changes"
platform_io.Monitors.push_back(monitor); platform_io.Monitors.push_back(monitor);
} }
} }
// - On Windows the process needs to be marked DPI-aware!! SDL2 doesn't do it by default. You can call ::SetProcessDPIAware() or call ImGui_ImplWin32_EnableDpiAwareness() from Win32 backend.
// - Apple platforms use FramebufferScale so we always return 1.0f.
// - Some accessibility applications are declaring virtual monitors with a DPI of 0.0f, see #7902. We preserve this value for caller to handle.
float ImGui_ImplGlfw_GetContentScaleForWindow(GLFWwindow* window)
{
#if GLFW_HAS_PER_MONITOR_DPI && !defined(__APPLE__)
float x_scale, y_scale;
glfwGetWindowContentScale(window, &x_scale, &y_scale);
return x_scale;
#else
IM_UNUSED(window);
return 1.0f;
#endif
}
float ImGui_ImplGlfw_GetContentScaleForMonitor(GLFWmonitor* monitor)
{
#if GLFW_HAS_PER_MONITOR_DPI && !defined(__APPLE__)
float x_scale, y_scale;
glfwGetMonitorContentScale(monitor, &x_scale, &y_scale);
return x_scale;
#else
IM_UNUSED(monitor);
return 1.0f;
#endif
}
static void ImGui_ImplGlfw_GetWindowSizeAndFramebufferScale(GLFWwindow* window, ImVec2* out_size, ImVec2* out_framebuffer_scale)
{
int w, h;
int display_w, display_h;
glfwGetWindowSize(window, &w, &h);
glfwGetFramebufferSize(window, &display_w, &display_h);
if (out_size != nullptr)
*out_size = ImVec2((float)w, (float)h);
if (out_framebuffer_scale != nullptr)
*out_framebuffer_scale = (w > 0 && h > 0) ? ImVec2((float)display_w / (float)w, (float)display_h / (float)h) : ImVec2(1.0f, 1.0f);
}
void ImGui_ImplGlfw_NewFrame() void ImGui_ImplGlfw_NewFrame()
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplGlfw_InitForXXX()?"); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplGlfw_InitForXXX()?");
// Setup display size (every frame to accommodate for window resizing) // Setup main viewport size (every frame to accommodate for window resizing)
int w, h; ImGui_ImplGlfw_GetWindowSizeAndFramebufferScale(bd->Window, &io.DisplaySize, &io.DisplayFramebufferScale);
int display_w, display_h;
glfwGetWindowSize(bd->Window, &w, &h);
glfwGetFramebufferSize(bd->Window, &display_w, &display_h);
io.DisplaySize = ImVec2((float)w, (float)h);
if (w > 0 && h > 0)
io.DisplayFramebufferScale = ImVec2((float)display_w / (float)w, (float)display_h / (float)h);
ImGui_ImplGlfw_UpdateMonitors(); ImGui_ImplGlfw_UpdateMonitors();
// Setup time step // Setup time step
@ -1038,7 +1110,7 @@ void ImGui_ImplGlfw_InstallEmscriptenCallbacks(GLFWwindow*, const char* canvas_s
// Register Emscripten Wheel callback to workaround issue in Emscripten GLFW Emulation (#6096) // Register Emscripten Wheel callback to workaround issue in Emscripten GLFW Emulation (#6096)
// We intentionally do not check 'if (install_callbacks)' here, as some users may set it to false and call GLFW callback themselves. // We intentionally do not check 'if (install_callbacks)' here, as some users may set it to false and call GLFW callback themselves.
// FIXME: May break chaining in case user registered their own Emscripten callback? // FIXME: May break chaining in case user registered their own Emscripten callback?
emscripten_set_wheel_callback(bd->CanvasSelector, nullptr, false, ImGui_ImplEmscripten_WheelCallback); emscripten_set_wheel_callback(bd->CanvasSelector, bd, false, ImGui_ImplEmscripten_WheelCallback);
} }
#elif defined(EMSCRIPTEN_USE_PORT_CONTRIB_GLFW3) #elif defined(EMSCRIPTEN_USE_PORT_CONTRIB_GLFW3)
// When using --use-port=contrib.glfw3 for the GLFW implementation, you can override the behavior of this call // When using --use-port=contrib.glfw3 for the GLFW implementation, you can override the behavior of this call
@ -1142,9 +1214,11 @@ static void ImGui_ImplGlfw_CreateWindow(ImGuiViewport* viewport)
GLFWwindow* share_window = (bd->ClientApi == GlfwClientApi_OpenGL) ? bd->Window : nullptr; GLFWwindow* share_window = (bd->ClientApi == GlfwClientApi_OpenGL) ? bd->Window : nullptr;
vd->Window = glfwCreateWindow((int)viewport->Size.x, (int)viewport->Size.y, "No Title Yet", nullptr, share_window); vd->Window = glfwCreateWindow((int)viewport->Size.x, (int)viewport->Size.y, "No Title Yet", nullptr, share_window);
vd->WindowOwned = true; vd->WindowOwned = true;
ImGui_ImplGlfw_ContextMap_Add(vd->Window, bd->Context);
viewport->PlatformHandle = (void*)vd->Window; viewport->PlatformHandle = (void*)vd->Window;
#ifdef _WIN32 #ifdef _WIN32
viewport->PlatformHandleRaw = glfwGetWin32Window(vd->Window); viewport->PlatformHandleRaw = glfwGetWin32Window(vd->Window);
::SetPropA((HWND)viewport->PlatformHandleRaw, "IMGUI_BACKEND_DATA", bd);
#elif defined(__APPLE__) #elif defined(__APPLE__)
viewport->PlatformHandleRaw = (void*)glfwGetCocoaWindow(vd->Window); viewport->PlatformHandleRaw = (void*)glfwGetCocoaWindow(vd->Window);
#endif #endif
@ -1186,6 +1260,7 @@ static void ImGui_ImplGlfw_DestroyWindow(ImGuiViewport* viewport)
if (bd->KeyOwnerWindows[i] == vd->Window) if (bd->KeyOwnerWindows[i] == vd->Window)
ImGui_ImplGlfw_KeyCallback(vd->Window, i, 0, GLFW_RELEASE, 0); // Later params are only used for main viewport, on which this function is never called. ImGui_ImplGlfw_KeyCallback(vd->Window, i, 0, GLFW_RELEASE, 0); // Later params are only used for main viewport, on which this function is never called.
ImGui_ImplGlfw_ContextMap_Remove(vd->Window);
glfwDestroyWindow(vd->Window); glfwDestroyWindow(vd->Window);
} }
vd->Window = nullptr; vd->Window = nullptr;
@ -1209,12 +1284,10 @@ static void ImGui_ImplGlfw_ShowWindow(ImGuiViewport* viewport)
::SetWindowLong(hwnd, GWL_EXSTYLE, ex_style); ::SetWindowLong(hwnd, GWL_EXSTYLE, ex_style);
} }
// GLFW hack: install hook for WM_NCHITTEST message handler // GLFW hack: install WndProc for mouse source event and WM_NCHITTEST message handler.
#if !GLFW_HAS_MOUSE_PASSTHROUGH && GLFW_HAS_WINDOW_HOVERED && defined(_WIN32)
::SetPropA(hwnd, "IMGUI_VIEWPORT", viewport); ::SetPropA(hwnd, "IMGUI_VIEWPORT", viewport);
vd->PrevWndProc = (WNDPROC)::GetWindowLongPtrW(hwnd, GWLP_WNDPROC); vd->PrevWndProc = (WNDPROC)::GetWindowLongPtrW(hwnd, GWLP_WNDPROC);
::SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)ImGui_ImplGlfw_WndProc); ::SetWindowLongPtrW(hwnd, GWLP_WNDPROC, (LONG_PTR)ImGui_ImplGlfw_WndProc);
#endif
#if !GLFW_HAS_FOCUS_ON_SHOW #if !GLFW_HAS_FOCUS_ON_SHOW
// GLFW hack: GLFW 3.2 has a bug where glfwShowWindow() also activates/focus the window. // GLFW hack: GLFW 3.2 has a bug where glfwShowWindow() also activates/focus the window.
@ -1258,7 +1331,7 @@ static ImVec2 ImGui_ImplGlfw_GetWindowSize(ImGuiViewport* viewport)
static void ImGui_ImplGlfw_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) static void ImGui_ImplGlfw_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
{ {
ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData;
#if __APPLE__ && !GLFW_HAS_OSX_WINDOW_POS_FIX #if defined(__APPLE__) && !GLFW_HAS_OSX_WINDOW_POS_FIX
// Native OS windows are positioned from the bottom-left corner on macOS, whereas on other platforms they are // Native OS windows are positioned from the bottom-left corner on macOS, whereas on other platforms they are
// positioned from the upper-left corner. GLFW makes an effort to convert macOS style coordinates, however it // positioned from the upper-left corner. GLFW makes an effort to convert macOS style coordinates, however it
// doesn't handle it when changing size. We are manually moving the window in order for changes of size to be based // doesn't handle it when changing size. We are manually moving the window in order for changes of size to be based
@ -1272,6 +1345,14 @@ static void ImGui_ImplGlfw_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
glfwSetWindowSize(vd->Window, (int)size.x, (int)size.y); glfwSetWindowSize(vd->Window, (int)size.x, (int)size.y);
} }
static ImVec2 ImGui_ImplGlfw_GetWindowFramebufferScale(ImGuiViewport* viewport)
{
ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData;
ImVec2 framebuffer_scale;
ImGui_ImplGlfw_GetWindowSizeAndFramebufferScale(vd->Window, nullptr, &framebuffer_scale);
return framebuffer_scale;
}
static void ImGui_ImplGlfw_SetWindowTitle(ImGuiViewport* viewport, const char* title) static void ImGui_ImplGlfw_SetWindowTitle(ImGuiViewport* viewport, const char* title)
{ {
ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData; ImGui_ImplGlfw_ViewportData* vd = (ImGui_ImplGlfw_ViewportData*)viewport->PlatformUserData;
@ -1370,6 +1451,7 @@ static void ImGui_ImplGlfw_InitMultiViewportSupport()
platform_io.Platform_GetWindowPos = ImGui_ImplGlfw_GetWindowPos; platform_io.Platform_GetWindowPos = ImGui_ImplGlfw_GetWindowPos;
platform_io.Platform_SetWindowSize = ImGui_ImplGlfw_SetWindowSize; platform_io.Platform_SetWindowSize = ImGui_ImplGlfw_SetWindowSize;
platform_io.Platform_GetWindowSize = ImGui_ImplGlfw_GetWindowSize; platform_io.Platform_GetWindowSize = ImGui_ImplGlfw_GetWindowSize;
platform_io.Platform_GetWindowFramebufferScale = ImGui_ImplGlfw_GetWindowFramebufferScale;
platform_io.Platform_SetWindowFocus = ImGui_ImplGlfw_SetWindowFocus; platform_io.Platform_SetWindowFocus = ImGui_ImplGlfw_SetWindowFocus;
platform_io.Platform_GetWindowFocus = ImGui_ImplGlfw_GetWindowFocus; platform_io.Platform_GetWindowFocus = ImGui_ImplGlfw_GetWindowFocus;
platform_io.Platform_GetWindowMinimized = ImGui_ImplGlfw_GetWindowMinimized; platform_io.Platform_GetWindowMinimized = ImGui_ImplGlfw_GetWindowMinimized;
@ -1413,7 +1495,9 @@ static ImGuiMouseSource GetMouseSourceFromMessageExtraInfo()
} }
static LRESULT CALLBACK ImGui_ImplGlfw_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) static LRESULT CALLBACK ImGui_ImplGlfw_WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{ {
ImGui_ImplGlfw_Data* bd = ImGui_ImplGlfw_GetBackendData(); ImGui_ImplGlfw_Data* bd = (ImGui_ImplGlfw_Data*)::GetPropA(hWnd, "IMGUI_BACKEND_DATA");
ImGuiIO& io = ImGui::GetIO(bd->Context);
WNDPROC prev_wndproc = bd->PrevWndProc; WNDPROC prev_wndproc = bd->PrevWndProc;
ImGuiViewport* viewport = (ImGuiViewport*)::GetPropA(hWnd, "IMGUI_VIEWPORT"); ImGuiViewport* viewport = (ImGuiViewport*)::GetPropA(hWnd, "IMGUI_VIEWPORT");
if (viewport != NULL) if (viewport != NULL)
@ -1429,7 +1513,7 @@ static LRESULT CALLBACK ImGui_ImplGlfw_WndProc(HWND hWnd, UINT msg, WPARAM wPara
case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: case WM_RBUTTONUP: case WM_RBUTTONDOWN: case WM_RBUTTONDBLCLK: case WM_RBUTTONUP:
case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: case WM_MBUTTONUP: case WM_MBUTTONDOWN: case WM_MBUTTONDBLCLK: case WM_MBUTTONUP:
case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK: case WM_XBUTTONUP: case WM_XBUTTONDOWN: case WM_XBUTTONDBLCLK: case WM_XBUTTONUP:
ImGui::GetIO().AddMouseSourceEvent(GetMouseSourceFromMessageExtraInfo()); io.AddMouseSourceEvent(GetMouseSourceFromMessageExtraInfo());
break; break;
// We have submitted https://github.com/glfw/glfw/pull/1568 to allow GLFW to support "transparent inputs". // We have submitted https://github.com/glfw/glfw/pull/1568 to allow GLFW to support "transparent inputs".
@ -1446,6 +1530,7 @@ static LRESULT CALLBACK ImGui_ImplGlfw_WndProc(HWND hWnd, UINT msg, WPARAM wPara
break; break;
} }
#endif #endif
default: break;
} }
return ::CallWindowProcW(prev_wndproc, hWnd, msg, wParam, lParam); return ::CallWindowProcW(prev_wndproc, hWnd, msg, wParam, lParam);
} }

View file

@ -10,6 +10,7 @@
// [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Gamepad support. Enable with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Resizing cursors requires GLFW 3.4+! Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Resizing cursors requires GLFW 3.4+! Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// [X] Multiple Dear ImGui contexts support.
// Missing features or Issues: // Missing features or Issues:
// [ ] Touch events are only correctly identified as Touch on Windows. This create issues with some interactions. GLFW doesn't provide a way to identify touch inputs from mouse inputs, we cannot call io.AddMouseSourceEvent() to identify the source. We provide a Windows-specific workaround. // [ ] Touch events are only correctly identified as Touch on Windows. This create issues with some interactions. GLFW doesn't provide a way to identify touch inputs from mouse inputs, we cannot call io.AddMouseSourceEvent() to identify the source. We provide a Windows-specific workaround.
// [ ] Missing ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress cursors. // [ ] Missing ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress cursors.
@ -65,5 +66,8 @@ IMGUI_IMPL_API void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor* monitor, int
// GLFW helpers // GLFW helpers
IMGUI_IMPL_API void ImGui_ImplGlfw_Sleep(int milliseconds); IMGUI_IMPL_API void ImGui_ImplGlfw_Sleep(int milliseconds);
IMGUI_IMPL_API float ImGui_ImplGlfw_GetContentScaleForWindow(GLFWwindow* window);
IMGUI_IMPL_API float ImGui_ImplGlfw_GetContentScaleForMonitor(GLFWmonitor* monitor);
#endif // #ifndef IMGUI_DISABLE #endif // #ifndef IMGUI_DISABLE

View file

@ -2,8 +2,9 @@
// This needs to be used along with a Platform Backend (e.g. OSX) // This needs to be used along with a Platform Backend (e.g. OSX)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'MTLTexture' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
@ -36,11 +37,12 @@ IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData,
id<MTLRenderCommandEncoder> commandEncoder); id<MTLRenderCommandEncoder> commandEncoder);
// Called by Init/NewFrame/Shutdown // Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplMetal_CreateFontsTexture(id<MTLDevice> device);
IMGUI_IMPL_API void ImGui_ImplMetal_DestroyFontsTexture();
IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device); IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device);
IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex);
#endif #endif
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -63,11 +65,12 @@ IMGUI_IMPL_API void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data,
MTL::RenderCommandEncoder* commandEncoder); MTL::RenderCommandEncoder* commandEncoder);
// Called by Init/NewFrame/Shutdown // Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplMetal_CreateFontsTexture(MTL::Device* device);
IMGUI_IMPL_API void ImGui_ImplMetal_DestroyFontsTexture();
IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device); IMGUI_IMPL_API bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device);
IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplMetal_DestroyDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex);
#endif #endif
#endif #endif

View file

@ -2,8 +2,9 @@
// This needs to be used along with a Platform Backend (e.g. OSX) // This needs to be used along with a Platform Backend (e.g. OSX)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'MTLTexture' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
@ -17,6 +18,7 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-XX-XX: Metal: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-XX-XX: Metal: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplMetal_CreateFontsTexture() and ImGui_ImplMetal_DestroyFontsTexture().
// 2025-02-03: Metal: Crash fix. (#8367) // 2025-02-03: Metal: Crash fix. (#8367)
// 2025-01-08: Metal: Fixed memory leaks when using metal-cpp (#8276, #8166) or when using multiple contexts (#7419). // 2025-01-08: Metal: Fixed memory leaks when using metal-cpp (#8276, #8166) or when using multiple contexts (#7419).
// 2022-08-23: Metal: Update deprecated property 'sampleCount'->'rasterSampleCount'. // 2022-08-23: Metal: Update deprecated property 'sampleCount'->'rasterSampleCount'.
@ -67,6 +69,11 @@ static void ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows();
- (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor; - (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor;
@end @end
@interface MetalTexture : NSObject
@property (nonatomic, strong) id<MTLTexture> metalTexture;
- (instancetype)initWithTexture:(id<MTLTexture>)metalTexture;
@end
// A singleton that stores long-lived objects that are needed by the Metal // A singleton that stores long-lived objects that are needed by the Metal
// renderer backend. Stores the render pipeline state cache and the default // renderer backend. Stores the render pipeline state cache and the default
// font texture, and manages the reusable buffer cache. // font texture, and manages the reusable buffer cache.
@ -75,7 +82,6 @@ static void ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows();
@property (nonatomic, strong) id<MTLDepthStencilState> depthStencilState; @property (nonatomic, strong) id<MTLDepthStencilState> depthStencilState;
@property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; // framebuffer descriptor for current frame; transient @property (nonatomic, strong) FramebufferDescriptor* framebufferDescriptor; // framebuffer descriptor for current frame; transient
@property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors @property (nonatomic, strong) NSMutableDictionary* renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors
@property (nonatomic, strong, nullable) id<MTLTexture> fontTexture;
@property (nonatomic, strong) NSMutableArray<MetalBuffer*>* bufferCache; @property (nonatomic, strong) NSMutableArray<MetalBuffer*>* bufferCache;
@property (nonatomic, assign) double lastBufferCachePurge; @property (nonatomic, assign) double lastBufferCachePurge;
- (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device; - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length device:(id<MTLDevice>)device;
@ -118,11 +124,6 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data,
} }
bool ImGui_ImplMetal_CreateFontsTexture(MTL::Device* device)
{
return ImGui_ImplMetal_CreateFontsTexture((__bridge id<MTLDevice>)(device));
}
bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device) bool ImGui_ImplMetal_CreateDeviceObjects(MTL::Device* device)
{ {
return ImGui_ImplMetal_CreateDeviceObjects((__bridge id<MTLDevice>)(device)); return ImGui_ImplMetal_CreateDeviceObjects((__bridge id<MTLDevice>)(device));
@ -142,6 +143,7 @@ bool ImGui_ImplMetal_Init(id<MTLDevice> device)
io.BackendRendererUserData = (void*)bd; io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_metal"; io.BackendRendererName = "imgui_impl_metal";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
bd->SharedMetalContext = [[MetalContext alloc] init]; bd->SharedMetalContext = [[MetalContext alloc] init];
@ -164,7 +166,7 @@ void ImGui_ImplMetal_Shutdown()
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasViewports); io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
} }
bool ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor) bool ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor)
@ -186,7 +188,7 @@ bool ImGui_ImplMetal_NewFrame(MTLRenderPassDescriptor* renderPassDescriptor)
return true; return true;
} }
static void ImGui_ImplMetal_SetupRenderState(ImDrawData* drawData, id<MTLCommandBuffer> commandBuffer, static void ImGui_ImplMetal_SetupRenderState(ImDrawData* draw_data, id<MTLCommandBuffer> commandBuffer,
id<MTLRenderCommandEncoder> commandEncoder, id<MTLRenderPipelineState> renderPipelineState, id<MTLRenderCommandEncoder> commandEncoder, id<MTLRenderPipelineState> renderPipelineState,
MetalBuffer* vertexBuffer, size_t vertexBufferOffset) MetalBuffer* vertexBuffer, size_t vertexBufferOffset)
{ {
@ -202,17 +204,17 @@ static void ImGui_ImplMetal_SetupRenderState(ImDrawData* drawData, id<MTLCommand
{ {
.originX = 0.0, .originX = 0.0,
.originY = 0.0, .originY = 0.0,
.width = (double)(drawData->DisplaySize.x * drawData->FramebufferScale.x), .width = (double)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x),
.height = (double)(drawData->DisplaySize.y * drawData->FramebufferScale.y), .height = (double)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y),
.znear = 0.0, .znear = 0.0,
.zfar = 1.0 .zfar = 1.0
}; };
[commandEncoder setViewport:viewport]; [commandEncoder setViewport:viewport];
float L = drawData->DisplayPos.x; float L = draw_data->DisplayPos.x;
float R = drawData->DisplayPos.x + drawData->DisplaySize.x; float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
float T = drawData->DisplayPos.y; float T = draw_data->DisplayPos.y;
float B = drawData->DisplayPos.y + drawData->DisplaySize.y; float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
float N = (float)viewport.znear; float N = (float)viewport.znear;
float F = (float)viewport.zfar; float F = (float)viewport.zfar;
const float ortho_projection[4][4] = const float ortho_projection[4][4] =
@ -231,17 +233,24 @@ static void ImGui_ImplMetal_SetupRenderState(ImDrawData* drawData, id<MTLCommand
} }
// Metal Render function. // Metal Render function.
void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id<MTLCommandBuffer> commandBuffer, id<MTLRenderCommandEncoder> commandEncoder) void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id<MTLCommandBuffer> commandBuffer, id<MTLRenderCommandEncoder> commandEncoder)
{ {
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
MetalContext* ctx = bd->SharedMetalContext; MetalContext* ctx = bd->SharedMetalContext;
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
int fb_width = (int)(drawData->DisplaySize.x * drawData->FramebufferScale.x); int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
int fb_height = (int)(drawData->DisplaySize.y * drawData->FramebufferScale.y); int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
if (fb_width <= 0 || fb_height <= 0 || drawData->CmdListsCount == 0) if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdListsCount == 0)
return; return;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplMetal_UpdateTexture(tex);
// Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame // Try to retrieve a render pipeline state that is compatible with the framebuffer config for this frame
// The hit rate for this cache should be very near 100%. // The hit rate for this cache should be very near 100%.
id<MTLRenderPipelineState> renderPipelineState = ctx.renderPipelineStateCache[ctx.framebufferDescriptor]; id<MTLRenderPipelineState> renderPipelineState = ctx.renderPipelineStateCache[ctx.framebufferDescriptor];
@ -255,23 +264,23 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id<MTLCommandBuffer> c
ctx.renderPipelineStateCache[ctx.framebufferDescriptor] = renderPipelineState; ctx.renderPipelineStateCache[ctx.framebufferDescriptor] = renderPipelineState;
} }
size_t vertexBufferLength = (size_t)drawData->TotalVtxCount * sizeof(ImDrawVert); size_t vertexBufferLength = (size_t)draw_data->TotalVtxCount * sizeof(ImDrawVert);
size_t indexBufferLength = (size_t)drawData->TotalIdxCount * sizeof(ImDrawIdx); size_t indexBufferLength = (size_t)draw_data->TotalIdxCount * sizeof(ImDrawIdx);
MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device]; MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device];
MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device]; MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device];
ImGui_ImplMetal_SetupRenderState(drawData, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0); ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0);
// Will project scissor/clipping rectangles into framebuffer space // Will project scissor/clipping rectangles into framebuffer space
ImVec2 clip_off = drawData->DisplayPos; // (0,0) unless using multi-viewports ImVec2 clip_off = draw_data->DisplayPos; // (0,0) unless using multi-viewports
ImVec2 clip_scale = drawData->FramebufferScale; // (1,1) unless using retina display which are often (2,2) ImVec2 clip_scale = draw_data->FramebufferScale; // (1,1) unless using retina display which are often (2,2)
// Render command lists // Render command lists
size_t vertexBufferOffset = 0; size_t vertexBufferOffset = 0;
size_t indexBufferOffset = 0; size_t indexBufferOffset = 0;
for (int n = 0; n < drawData->CmdListsCount; n++) for (int n = 0; n < draw_data->CmdListsCount; n++)
{ {
const ImDrawList* draw_list = drawData->CmdLists[n]; const ImDrawList* draw_list = draw_data->CmdLists[n];
memcpy((char*)vertexBuffer.buffer.contents + vertexBufferOffset, draw_list->VtxBuffer.Data, (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert)); memcpy((char*)vertexBuffer.buffer.contents + vertexBufferOffset, draw_list->VtxBuffer.Data, (size_t)draw_list->VtxBuffer.Size * sizeof(ImDrawVert));
memcpy((char*)indexBuffer.buffer.contents + indexBufferOffset, draw_list->IdxBuffer.Data, (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx)); memcpy((char*)indexBuffer.buffer.contents + indexBufferOffset, draw_list->IdxBuffer.Data, (size_t)draw_list->IdxBuffer.Size * sizeof(ImDrawIdx));
@ -284,7 +293,7 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id<MTLCommandBuffer> c
// User callback, registered via ImDrawList::AddCallback() // User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.) // (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplMetal_SetupRenderState(drawData, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset); ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, vertexBufferOffset);
else else
pcmd->UserCallback(draw_list, pcmd); pcmd->UserCallback(draw_list, pcmd);
} }
@ -344,21 +353,37 @@ void ImGui_ImplMetal_RenderDrawData(ImDrawData* drawData, id<MTLCommandBuffer> c
}]; }];
} }
bool ImGui_ImplMetal_CreateFontsTexture(id<MTLDevice> device) static void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex)
{
MetalTexture* backend_tex = (__bridge_transfer MetalTexture*)(tex->BackendUserData);
if (backend_tex == nullptr)
return;
IM_ASSERT(backend_tex.metalTexture == (__bridge id<MTLTexture>)(void*)(intptr_t)tex->TexID);
backend_tex.metalTexture = nil;
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
tex->BackendUserData = nullptr;
}
void ImGui_ImplMetal_UpdateTexture(ImTextureData* tex)
{ {
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
ImGuiIO& io = ImGui::GetIO(); if (tex->Status == ImTextureStatus_WantCreate)
{
// Create and upload new texture to graphics system
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
// We are retrieving and uploading the font atlas as a 4-channels RGBA texture here. // We are retrieving and uploading the font atlas as a 4-channels RGBA texture here.
// In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth. // In theory we could call GetTexDataAsAlpha8() and upload a 1-channel texture to save on memory access bandwidth.
// However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures. // However, using a shader designed for 1-channel texture would make it less obvious to use the ImTextureID facility to render users own textures.
// You can make that change in your implementation. // You can make that change in your implementation.
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm MTLTextureDescriptor* textureDescriptor = [MTLTextureDescriptor texture2DDescriptorWithPixelFormat:MTLPixelFormatRGBA8Unorm
width:(NSUInteger)width width:(NSUInteger)tex->Width
height:(NSUInteger)height height:(NSUInteger)tex->Height
mipmapped:NO]; mipmapped:NO];
textureDescriptor.usage = MTLTextureUsageShaderRead; textureDescriptor.usage = MTLTextureUsageShaderRead;
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST #if TARGET_OS_OSX || TARGET_OS_MACCATALYST
@ -366,20 +391,33 @@ bool ImGui_ImplMetal_CreateFontsTexture(id<MTLDevice> device)
#else #else
textureDescriptor.storageMode = MTLStorageModeShared; textureDescriptor.storageMode = MTLStorageModeShared;
#endif #endif
id <MTLTexture> texture = [device newTextureWithDescriptor:textureDescriptor]; id <MTLTexture> texture = [bd->SharedMetalContext.device newTextureWithDescriptor:textureDescriptor];
[texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)width, (NSUInteger)height) mipmapLevel:0 withBytes:pixels bytesPerRow:(NSUInteger)width * 4]; [texture replaceRegion:MTLRegionMake2D(0, 0, (NSUInteger)tex->Width, (NSUInteger)tex->Height) mipmapLevel:0 withBytes:tex->Pixels bytesPerRow:(NSUInteger)tex->Width * 4];
bd->SharedMetalContext.fontTexture = texture; MetalTexture* backend_tex = [[MetalTexture alloc] initWithTexture:texture];
io.Fonts->SetTexID((ImTextureID)(intptr_t)(__bridge void*)bd->SharedMetalContext.fontTexture); // ImTextureID == ImU64
return (bd->SharedMetalContext.fontTexture != nil); // Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)texture);
tex->SetStatus(ImTextureStatus_OK);
tex->BackendUserData = (__bridge_retained void*)(backend_tex);
} }
else if (tex->Status == ImTextureStatus_WantUpdates)
void ImGui_ImplMetal_DestroyFontsTexture()
{ {
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); // Update selected blocks. We only ever write to textures regions which have never been used before!
ImGuiIO& io = ImGui::GetIO(); // This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
bd->SharedMetalContext.fontTexture = nil; MetalTexture* backend_tex = (__bridge MetalTexture*)(tex->BackendUserData);
io.Fonts->SetTexID(0); for (ImTextureRect& r : tex->Updates)
{
[backend_tex.metalTexture replaceRegion:MTLRegionMake2D((NSUInteger)r.x, (NSUInteger)r.y, (NSUInteger)r.w, (NSUInteger)r.h)
mipmapLevel:0
withBytes:tex->GetPixelsAt(r.x, r.y)
bytesPerRow:(NSUInteger)tex->Width * 4];
}
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
{
ImGui_ImplMetal_DestroyTexture(tex);
}
} }
bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device) bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device)
@ -393,13 +431,19 @@ bool ImGui_ImplMetal_CreateDeviceObjects(id<MTLDevice> device)
#ifdef IMGUI_IMPL_METAL_CPP #ifdef IMGUI_IMPL_METAL_CPP
[depthStencilDescriptor release]; [depthStencilDescriptor release];
#endif #endif
return ImGui_ImplMetal_CreateFontsTexture(device);
return true;
} }
void ImGui_ImplMetal_DestroyDeviceObjects() void ImGui_ImplMetal_DestroyDeviceObjects()
{ {
ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData();
ImGui_ImplMetal_DestroyFontsTexture();
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
ImGui_ImplMetal_DestroyTexture(tex);
ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows(); ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows();
[bd->SharedMetalContext.renderPipelineStateCache removeAllObjects]; [bd->SharedMetalContext.renderPipelineStateCache removeAllObjects];
} }
@ -491,13 +535,12 @@ static void ImGui_ImplMetal_RenderWindow(ImGuiViewport* viewport, void*)
} }
data->FirstFrame = false; data->FirstFrame = false;
viewport->DpiScale = (float)window.backingScaleFactor; float fb_scale = (float)window.backingScaleFactor;
if (data->MetalLayer.contentsScale != viewport->DpiScale) if (data->MetalLayer.contentsScale != fb_scale)
{ {
data->MetalLayer.contentsScale = viewport->DpiScale; data->MetalLayer.contentsScale = fb_scale;
data->MetalLayer.drawableSize = MakeScaledSize(window.frame.size, viewport->DpiScale); data->MetalLayer.drawableSize = MakeScaledSize(window.frame.size, fb_scale);
} }
viewport->DrawData->FramebufferScale = ImVec2(viewport->DpiScale, viewport->DpiScale);
#endif #endif
id <CAMetalDrawable> drawable = [data->MetalLayer nextDrawable]; id <CAMetalDrawable> drawable = [data->MetalLayer nextDrawable];
@ -611,6 +654,18 @@ static void ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows()
@end @end
#pragma mark - MetalTexture implementation
@implementation MetalTexture
- (instancetype)initWithTexture:(id<MTLTexture>)metalTexture
{
if ((self = [super init]))
self.metalTexture = metalTexture;
return self;
}
@end
#pragma mark - MetalContext implementation #pragma mark - MetalContext implementation
@implementation MetalContext @implementation MetalContext

View file

@ -2,7 +2,8 @@
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// Missing features or Issues: // Missing features or Issues:
// [ ] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [ ] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
@ -26,6 +27,7 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL2_CreateFontsTexture() and ImGui_ImplOpenGL2_DestroyFontsTexture().
// 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap.
// 2024-06-28: OpenGL: ImGui_ImplOpenGL2_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL2_DestroyFontsTexture(). (#7748) // 2024-06-28: OpenGL: ImGui_ImplOpenGL2_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL2_DestroyFontsTexture(). (#7748)
// 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11. // 2022-10-11: Using 'nullptr' instead of 'NULL' as per our switch to C++11.
@ -83,8 +85,6 @@
// OpenGL data // OpenGL data
struct ImGui_ImplOpenGL2_Data struct ImGui_ImplOpenGL2_Data
{ {
GLuint FontTexture;
ImGui_ImplOpenGL2_Data() { memset((void*)this, 0, sizeof(*this)); } ImGui_ImplOpenGL2_Data() { memset((void*)this, 0, sizeof(*this)); }
}; };
@ -110,6 +110,7 @@ bool ImGui_ImplOpenGL2_Init()
ImGui_ImplOpenGL2_Data* bd = IM_NEW(ImGui_ImplOpenGL2_Data)(); ImGui_ImplOpenGL2_Data* bd = IM_NEW(ImGui_ImplOpenGL2_Data)();
io.BackendRendererUserData = (void*)bd; io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_opengl2"; io.BackendRendererName = "imgui_impl_opengl2";
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
ImGui_ImplOpenGL2_InitMultiViewportSupport(); ImGui_ImplOpenGL2_InitMultiViewportSupport();
@ -127,7 +128,7 @@ void ImGui_ImplOpenGL2_Shutdown()
ImGui_ImplOpenGL2_DestroyDeviceObjects(); ImGui_ImplOpenGL2_DestroyDeviceObjects();
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasViewports; io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
IM_DELETE(bd); IM_DELETE(bd);
} }
@ -135,13 +136,7 @@ bool ImGui_ImplOpenGL2_NewFrame()
{ {
ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplOpenGL2_Init()?"); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplOpenGL2_Init()?");
IM_UNUSED(bd);
if (!bd->FontTexture)
ImGui_ImplOpenGL2_CreateDeviceObjects();
if (!bd->FontTexture)
ImGui_ImplOpenGL2_CreateFontsTexture();
return true;
} }
static void ImGui_ImplOpenGL2_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height) static void ImGui_ImplOpenGL2_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height)
@ -199,6 +194,13 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data)
if (fb_width == 0 || fb_height == 0) if (fb_width == 0 || fb_height == 0)
return; return;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplOpenGL2_UpdateTexture(tex);
// Backup GL state // Backup GL state
GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture);
GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode); GLint last_polygon_mode[2]; glGetIntegerv(GL_POLYGON_MODE, last_polygon_mode);
@ -272,57 +274,77 @@ void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data)
glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, last_tex_env_mode); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, last_tex_env_mode);
} }
bool ImGui_ImplOpenGL2_CreateFontsTexture() void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex)
{ {
// Build texture atlas if (tex->Status == ImTextureStatus_WantCreate)
ImGuiIO& io = ImGui::GetIO(); {
ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); // Create and upload new texture to graphics system
unsigned char* pixels; //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
int width, height; IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr);
io.Fonts->GetTexDataAsAlpha8(&pixels, &width, &height); // Load as Alpha8 8-bit, because we don't use shaders anyway. IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
const void* pixels = tex->GetPixels();
GLuint gl_texture_id = 0;
// Upload texture to graphics system // Upload texture to graphics system
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
GLint last_texture; GLint last_texture;
glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
glGenTextures(1, &bd->FontTexture); GL_CALL(glGenTextures(1, &gl_texture_id));
glBindTexture(GL_TEXTURE_2D, bd->FontTexture); GL_CALL(glBindTexture(GL_TEXTURE_2D, gl_texture_id));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP));
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP));
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA8, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels); GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->Width, tex->Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
// Store our identifier // Store identifiers
io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); tex->SetTexID((ImTextureID)(intptr_t)gl_texture_id);
tex->SetStatus(ImTextureStatus_OK);
// Restore state // Restore state
glBindTexture(GL_TEXTURE_2D, last_texture); GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture));
return true;
} }
else if (tex->Status == ImTextureStatus_WantUpdates)
{
// Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
GLint last_texture;
GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
void ImGui_ImplOpenGL2_DestroyFontsTexture() GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID;
GL_CALL(glBindTexture(GL_TEXTURE_2D, gl_tex_id));
GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->Width));
for (ImTextureRect& r : tex->Updates)
GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixelsAt(r.x, r.y)));
GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture)); // Restore state
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{ {
ImGuiIO& io = ImGui::GetIO(); GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID;
ImGui_ImplOpenGL2_Data* bd = ImGui_ImplOpenGL2_GetBackendData(); glDeleteTextures(1, &gl_tex_id);
if (bd->FontTexture)
{ // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
glDeleteTextures(1, &bd->FontTexture); tex->SetTexID(ImTextureID_Invalid);
io.Fonts->SetTexID(0); tex->SetStatus(ImTextureStatus_Destroyed);
bd->FontTexture = 0;
} }
} }
bool ImGui_ImplOpenGL2_CreateDeviceObjects() bool ImGui_ImplOpenGL2_CreateDeviceObjects()
{ {
return ImGui_ImplOpenGL2_CreateFontsTexture(); return true;
} }
void ImGui_ImplOpenGL2_DestroyDeviceObjects() void ImGui_ImplOpenGL2_DestroyDeviceObjects()
{ {
ImGui_ImplOpenGL2_DestroyFontsTexture(); for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
{
tex->SetStatus(ImTextureStatus_WantDestroy);
ImGui_ImplOpenGL2_UpdateTexture(tex);
}
} }

View file

@ -2,7 +2,8 @@
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// Missing features or Issues: // Missing features or Issues:
// [ ] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [ ] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
@ -34,9 +35,10 @@ IMGUI_IMPL_API bool ImGui_ImplOpenGL2_NewFrame();
IMGUI_IMPL_API void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data); IMGUI_IMPL_API void ImGui_ImplOpenGL2_RenderDrawData(ImDrawData* draw_data);
// Called by Init/NewFrame/Shutdown // Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplOpenGL2_CreateFontsTexture();
IMGUI_IMPL_API void ImGui_ImplOpenGL2_DestroyFontsTexture();
IMGUI_IMPL_API bool ImGui_ImplOpenGL2_CreateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplOpenGL2_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplOpenGL2_DestroyDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplOpenGL2_DestroyDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplOpenGL2_UpdateTexture(ImTextureData* tex);
#endif // #ifndef IMGUI_DISABLE #endif // #ifndef IMGUI_DISABLE

View file

@ -4,8 +4,9 @@
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!] // [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!]
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// About WebGL/ES: // About WebGL/ES:
@ -24,6 +25,8 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-06-11: OpenGL: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplOpenGL3_CreateFontsTexture() and ImGui_ImplOpenGL3_DestroyFontsTexture().
// 2025-06-04: OpenGL: Made GLES 3.20 contexts not access GL_CONTEXT_PROFILE_MASK nor GL_PRIMITIVE_RESTART. (#8664)
// 2025-02-18: OpenGL: Lazily reinitialize embedded GL loader for when calling backend from e.g. other DLL boundaries. (#8406) // 2025-02-18: OpenGL: Lazily reinitialize embedded GL loader for when calling backend from e.g. other DLL boundaries. (#8406)
// 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap. // 2024-10-07: OpenGL: Changed default texture sampler to Clamp instead of Repeat/Wrap.
// 2024-06-28: OpenGL: ImGui_ImplOpenGL3_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL3_DestroyFontsTexture(). (#7748) // 2024-06-28: OpenGL: ImGui_ImplOpenGL3_NewFrame() recreates font texture if it has been destroyed by ImGui_ImplOpenGL3_DestroyFontsTexture(). (#7748)
@ -235,7 +238,7 @@ struct ImGui_ImplOpenGL3_Data
bool GlProfileIsES3; bool GlProfileIsES3;
bool GlProfileIsCompat; bool GlProfileIsCompat;
GLint GlProfileMask; GLint GlProfileMask;
GLuint FontTexture; GLint MaxTextureSize;
GLuint ShaderHandle; GLuint ShaderHandle;
GLint AttribLocationTex; // Uniforms location GLint AttribLocationTex; // Uniforms location
GLint AttribLocationProjMtx; GLint AttribLocationProjMtx;
@ -248,6 +251,7 @@ struct ImGui_ImplOpenGL3_Data
bool HasPolygonMode; bool HasPolygonMode;
bool HasClipOrigin; bool HasClipOrigin;
bool UseBufferSubData; bool UseBufferSubData;
ImVector<char> TempBuffer;
ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); } ImGui_ImplOpenGL3_Data() { memset((void*)this, 0, sizeof(*this)); }
}; };
@ -338,11 +342,7 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
if (major == 0 && minor == 0) if (major == 0 && minor == 0)
sscanf(gl_version_str, "%d.%d", &major, &minor); // Query GL_VERSION in desktop GL 2.x, the string will start with "<major>.<minor>" sscanf(gl_version_str, "%d.%d", &major, &minor); // Query GL_VERSION in desktop GL 2.x, the string will start with "<major>.<minor>"
bd->GlVersion = (GLuint)(major * 100 + minor * 10); bd->GlVersion = (GLuint)(major * 100 + minor * 10);
#if defined(GL_CONTEXT_PROFILE_MASK) glGetIntegerv(GL_MAX_TEXTURE_SIZE, &bd->MaxTextureSize);
if (bd->GlVersion >= 320)
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask);
bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0;
#endif
#if defined(IMGUI_IMPL_OPENGL_ES3) #if defined(IMGUI_IMPL_OPENGL_ES3)
bd->GlProfileIsES3 = true; bd->GlProfileIsES3 = true;
@ -351,6 +351,12 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
bd->GlProfileIsES3 = true; bd->GlProfileIsES3 = true;
#endif #endif
#if defined(GL_CONTEXT_PROFILE_MASK)
if (!bd->GlProfileIsES3 && bd->GlVersion >= 320)
glGetIntegerv(GL_CONTEXT_PROFILE_MASK, &bd->GlProfileMask);
bd->GlProfileIsCompat = (bd->GlProfileMask & GL_CONTEXT_COMPATIBILITY_PROFILE_BIT) != 0;
#endif
bd->UseBufferSubData = false; bd->UseBufferSubData = false;
/* /*
// Query vendor to enable glBufferSubData kludge // Query vendor to enable glBufferSubData kludge
@ -370,8 +376,12 @@ bool ImGui_ImplOpenGL3_Init(const char* glsl_version)
if (bd->GlVersion >= 320) if (bd->GlVersion >= 320)
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
#endif #endif
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = (int)bd->MaxTextureSize;
// Store GLSL version string so we can refer to it later in case we recreate shaders. // Store GLSL version string so we can refer to it later in case we recreate shaders.
// Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure. // Note: GLSL version is NOT the same as GL version. Leave this to nullptr if unsure.
if (glsl_version == nullptr) if (glsl_version == nullptr)
@ -426,7 +436,7 @@ void ImGui_ImplOpenGL3_Shutdown()
ImGui_ImplOpenGL3_DestroyDeviceObjects(); ImGui_ImplOpenGL3_DestroyDeviceObjects();
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasViewports); io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
IM_DELETE(bd); IM_DELETE(bd);
} }
@ -439,12 +449,8 @@ bool ImGui_ImplOpenGL3_NewFrame()
ImGui_ImplOpenGL3_InitLoader(); // Lazily init loader if not already done for e.g. DLL boundaries. ImGui_ImplOpenGL3_InitLoader(); // Lazily init loader if not already done for e.g. DLL boundaries.
if (!bd->ShaderHandle) if (!bd->ShaderHandle)
ret=ImGui_ImplOpenGL3_CreateDeviceObjects(); if (!ImGui_ImplOpenGL3_CreateDeviceObjects())
IM_ASSERT(0 && "ImGui_ImplOpenGL3_CreateDeviceObjects() failed!");
if (!bd->FontTexture)
if (!ImGui_ImplOpenGL3_CreateFontsTexture()) ret=false;
return ret;
} }
static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object) static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_width, int fb_height, GLuint vertex_array_object)
@ -460,7 +466,7 @@ static void ImGui_ImplOpenGL3_SetupRenderState(ImDrawData* draw_data, int fb_wid
glDisable(GL_STENCIL_TEST); glDisable(GL_STENCIL_TEST);
glEnable(GL_SCISSOR_TEST); glEnable(GL_SCISSOR_TEST);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
if (bd->GlVersion >= 310) if (!bd->GlProfileIsES3 && bd->GlVersion >= 310)
glDisable(GL_PRIMITIVE_RESTART); glDisable(GL_PRIMITIVE_RESTART);
#endif #endif
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE
@ -536,6 +542,13 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplOpenGL3_UpdateTexture(tex);
// Backup GL state // Backup GL state
GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture); GLenum last_active_texture; glGetIntegerv(GL_ACTIVE_TEXTURE, (GLint*)&last_active_texture);
glActiveTexture(GL_TEXTURE0); glActiveTexture(GL_TEXTURE0);
@ -572,7 +585,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
GLboolean last_enable_stencil_test = glIsEnabled(GL_STENCIL_TEST); GLboolean last_enable_stencil_test = glIsEnabled(GL_STENCIL_TEST);
GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST); GLboolean last_enable_scissor_test = glIsEnabled(GL_SCISSOR_TEST);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
GLboolean last_enable_primitive_restart = (bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE; GLboolean last_enable_primitive_restart = (!bd->GlProfileIsES3 && bd->GlVersion >= 310) ? glIsEnabled(GL_PRIMITIVE_RESTART) : GL_FALSE;
#endif #endif
// Setup desired GL state // Setup desired GL state
@ -691,7 +704,7 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
if (last_enable_stencil_test) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST); if (last_enable_stencil_test) glEnable(GL_STENCIL_TEST); else glDisable(GL_STENCIL_TEST);
if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST); if (last_enable_scissor_test) glEnable(GL_SCISSOR_TEST); else glDisable(GL_SCISSOR_TEST);
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_PRIMITIVE_RESTART
if (bd->GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); } if (!bd->GlProfileIsES3 && bd->GlVersion >= 310) { if (last_enable_primitive_restart) glEnable(GL_PRIMITIVE_RESTART); else glDisable(GL_PRIMITIVE_RESTART); }
#endif #endif
#ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE #ifdef IMGUI_IMPL_OPENGL_MAY_HAVE_POLYGON_MODE
@ -704,25 +717,33 @@ void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data)
(void)bd; // Not all compilation paths use this (void)bd; // Not all compilation paths use this
} }
bool ImGui_ImplOpenGL3_CreateFontsTexture() static void ImGui_ImplOpenGL3_DestroyTexture(ImTextureData* tex)
{ {
ImGuiIO& io = ImGui::GetIO(); GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID;
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); glDeleteTextures(1, &gl_tex_id);
// Build texture atlas // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
unsigned char* pixels; tex->SetTexID(ImTextureID_Invalid);
int width, height; tex->SetStatus(ImTextureStatus_Destroyed);
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. }
void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex)
{
if (tex->Status == ImTextureStatus_WantCreate)
{
// Create and upload new texture to graphics system
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
const void* pixels = tex->GetPixels();
GLuint gl_texture_id = 0;
// Upload texture to graphics system // Upload texture to graphics system
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
GLint last_texture; GLint last_texture;
GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture)); GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
// clear errors GL_CALL(glGenTextures(1, &gl_texture_id));
glGetError(); GL_CALL(glBindTexture(GL_TEXTURE_2D, gl_texture_id));
GL_CALL_FALSE(glGenTextures(1, &bd->FontTexture));
GL_CALL_FALSE(glBindTexture(GL_TEXTURE_2D, bd->FontTexture));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)); GL_CALL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
@ -730,30 +751,48 @@ bool ImGui_ImplOpenGL3_CreateFontsTexture()
#ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES #ifdef GL_UNPACK_ROW_LENGTH // Not on WebGL/ES
GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0)); GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
#endif #endif
GL_CALL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, tex->Width, tex->Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels));
glGetError(); // Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)gl_texture_id);
GL_CALL_FALSE(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels)); tex->SetStatus(ImTextureStatus_OK);
// Store identifier
io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture);
// Restore state // Restore state
GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture)); GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture));
return true;
} }
else if (tex->Status == ImTextureStatus_WantUpdates)
void ImGui_ImplOpenGL3_DestroyFontsTexture()
{ {
ImGuiIO& io = ImGui::GetIO(); // Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
GLint last_texture;
GL_CALL(glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
GLuint gl_tex_id = (GLuint)(intptr_t)tex->TexID;
GL_CALL(glBindTexture(GL_TEXTURE_2D, gl_tex_id));
#if 0// GL_UNPACK_ROW_LENGTH // Not on WebGL/ES
GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, tex->Width));
for (ImTextureRect& r : tex->Updates)
GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, tex->GetPixelsAt(r.x, r.y)));
GL_CALL(glPixelStorei(GL_UNPACK_ROW_LENGTH, 0));
#else
// GL ES doesn't have GL_UNPACK_ROW_LENGTH, so we need to (A) copy to a contiguous buffer or (B) upload line by line.
ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData(); ImGui_ImplOpenGL3_Data* bd = ImGui_ImplOpenGL3_GetBackendData();
if (bd->FontTexture) for (ImTextureRect& r : tex->Updates)
{ {
glDeleteTextures(1, &bd->FontTexture); const int src_pitch = r.w * tex->BytesPerPixel;
io.Fonts->SetTexID(0); bd->TempBuffer.resize(r.h * src_pitch);
bd->FontTexture = 0; char* out_p = bd->TempBuffer.Data;
for (int y = 0; y < r.h; y++, out_p += src_pitch)
memcpy(out_p, tex->GetPixelsAt(r.x, r.y + y), src_pitch);
IM_ASSERT(out_p == bd->TempBuffer.end());
GL_CALL(glTexSubImage2D(GL_TEXTURE_2D, 0, r.x, r.y, r.w, r.h, GL_RGBA, GL_UNSIGNED_BYTE, bd->TempBuffer.Data));
} }
#endif
tex->SetStatus(ImTextureStatus_OK);
GL_CALL(glBindTexture(GL_TEXTURE_2D, last_texture)); // Restore state
}
else if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
ImGui_ImplOpenGL3_DestroyTexture(tex);
} }
// If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file. // If you get an error please report on github. You may try different GL context version or GLSL version. See GL<>GLSL version table at the top of this file.
@ -945,21 +984,24 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects()
GL_CALL(vert_handle = glCreateShader(GL_VERTEX_SHADER)); GL_CALL(vert_handle = glCreateShader(GL_VERTEX_SHADER));
glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr); glShaderSource(vert_handle, 2, vertex_shader_with_version, nullptr);
glCompileShader(vert_handle); glCompileShader(vert_handle);
CheckShader(vert_handle, "vertex shader"); if (!CheckShader(vert_handle, "vertex shader"))
return false;
const GLchar* fragment_shader_with_version[2] = { bd->GlslVersionString, fragment_shader }; const GLchar* fragment_shader_with_version[2] = { bd->GlslVersionString, fragment_shader };
GLuint frag_handle; GLuint frag_handle;
GL_CALL(frag_handle = glCreateShader(GL_FRAGMENT_SHADER)); GL_CALL(frag_handle = glCreateShader(GL_FRAGMENT_SHADER));
glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr); glShaderSource(frag_handle, 2, fragment_shader_with_version, nullptr);
glCompileShader(frag_handle); glCompileShader(frag_handle);
CheckShader(frag_handle, "fragment shader"); if (!CheckShader(frag_handle, "fragment shader"))
return false;
// Link // Link
bd->ShaderHandle = glCreateProgram(); bd->ShaderHandle = glCreateProgram();
glAttachShader(bd->ShaderHandle, vert_handle); glAttachShader(bd->ShaderHandle, vert_handle);
glAttachShader(bd->ShaderHandle, frag_handle); glAttachShader(bd->ShaderHandle, frag_handle);
glLinkProgram(bd->ShaderHandle); glLinkProgram(bd->ShaderHandle);
CheckProgram(bd->ShaderHandle, "shader program"); if (!CheckProgram(bd->ShaderHandle, "shader program"))
return false;
glDetachShader(bd->ShaderHandle, vert_handle); glDetachShader(bd->ShaderHandle, vert_handle);
glDetachShader(bd->ShaderHandle, frag_handle); glDetachShader(bd->ShaderHandle, frag_handle);
@ -976,10 +1018,6 @@ bool ImGui_ImplOpenGL3_CreateDeviceObjects()
glGenBuffers(1, &bd->VboHandle); glGenBuffers(1, &bd->VboHandle);
glGenBuffers(1, &bd->ElementsHandle); glGenBuffers(1, &bd->ElementsHandle);
bool whatReturn=true;
if (!ImGui_ImplOpenGL3_CreateFontsTexture()) whatReturn=false;
// Restore modified GL state // Restore modified GL state
glBindTexture(GL_TEXTURE_2D, last_texture); glBindTexture(GL_TEXTURE_2D, last_texture);
glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer); glBindBuffer(GL_ARRAY_BUFFER, last_array_buffer);
@ -999,7 +1037,11 @@ void ImGui_ImplOpenGL3_DestroyDeviceObjects()
if (bd->VboHandle) { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; } if (bd->VboHandle) { glDeleteBuffers(1, &bd->VboHandle); bd->VboHandle = 0; }
if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; } if (bd->ElementsHandle) { glDeleteBuffers(1, &bd->ElementsHandle); bd->ElementsHandle = 0; }
if (bd->ShaderHandle) { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; } if (bd->ShaderHandle) { glDeleteProgram(bd->ShaderHandle); bd->ShaderHandle = 0; }
ImGui_ImplOpenGL3_DestroyFontsTexture();
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
ImGui_ImplOpenGL3_DestroyTexture(tex);
} }
//-------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------

View file

@ -4,8 +4,9 @@
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture identifier as void*/ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'GLuint' OpenGL texture as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!] // [x] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset) [Desktop OpenGL only!]
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// About WebGL/ES: // About WebGL/ES:
@ -37,11 +38,12 @@ IMGUI_IMPL_API bool ImGui_ImplOpenGL3_NewFrame();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data); IMGUI_IMPL_API void ImGui_ImplOpenGL3_RenderDrawData(ImDrawData* draw_data);
// (Optional) Called by Init/NewFrame/Shutdown // (Optional) Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateFontsTexture();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyFontsTexture();
IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplOpenGL3_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplOpenGL3_DestroyDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplOpenGL3_UpdateTexture(ImTextureData* tex);
// Configuration flags to add in your imconfig file: // Configuration flags to add in your imconfig file:
//#define IMGUI_IMPL_OPENGL_ES2 // Enable ES 2 (Auto-detected on Emscripten) //#define IMGUI_IMPL_OPENGL_ES2 // Enable ES 2 (Auto-detected on Emscripten)
//#define IMGUI_IMPL_OPENGL_ES3 // Enable ES 3 (Auto-detected on iOS/Android) //#define IMGUI_IMPL_OPENGL_ES3 // Enable ES 3 (Auto-detected on iOS/Android)

View file

@ -167,6 +167,7 @@ typedef khronos_uint8_t GLubyte;
#define GL_SCISSOR_TEST 0x0C11 #define GL_SCISSOR_TEST 0x0C11
#define GL_UNPACK_ROW_LENGTH 0x0CF2 #define GL_UNPACK_ROW_LENGTH 0x0CF2
#define GL_PACK_ALIGNMENT 0x0D05 #define GL_PACK_ALIGNMENT 0x0D05
#define GL_MAX_TEXTURE_SIZE 0x0D33
#define GL_TEXTURE_2D 0x0DE1 #define GL_TEXTURE_2D 0x0DE1
#define GL_UNSIGNED_BYTE 0x1401 #define GL_UNSIGNED_BYTE 0x1401
#define GL_UNSIGNED_SHORT 0x1403 #define GL_UNSIGNED_SHORT 0x1403
@ -224,11 +225,13 @@ typedef khronos_float_t GLclampf;
typedef double GLclampd; typedef double GLclampd;
#define GL_TEXTURE_BINDING_2D 0x8069 #define GL_TEXTURE_BINDING_2D 0x8069
typedef void (APIENTRYP PFNGLDRAWELEMENTSPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices); typedef void (APIENTRYP PFNGLDRAWELEMENTSPROC) (GLenum mode, GLsizei count, GLenum type, const void *indices);
typedef void (APIENTRYP PFNGLTEXSUBIMAGE2DPROC) (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels);
typedef void (APIENTRYP PFNGLBINDTEXTUREPROC) (GLenum target, GLuint texture); typedef void (APIENTRYP PFNGLBINDTEXTUREPROC) (GLenum target, GLuint texture);
typedef void (APIENTRYP PFNGLDELETETEXTURESPROC) (GLsizei n, const GLuint *textures); typedef void (APIENTRYP PFNGLDELETETEXTURESPROC) (GLsizei n, const GLuint *textures);
typedef void (APIENTRYP PFNGLGENTEXTURESPROC) (GLsizei n, GLuint *textures); typedef void (APIENTRYP PFNGLGENTEXTURESPROC) (GLsizei n, GLuint *textures);
#ifdef GL_GLEXT_PROTOTYPES #ifdef GL_GLEXT_PROTOTYPES
GLAPI void APIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices); GLAPI void APIENTRY glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices);
GLAPI void APIENTRY glTexSubImage2D (GLenum target, GLint level, GLint xoffset, GLint yoffset, GLsizei width, GLsizei height, GLenum format, GLenum type, const void *pixels);
GLAPI void APIENTRY glBindTexture (GLenum target, GLuint texture); GLAPI void APIENTRY glBindTexture (GLenum target, GLuint texture);
GLAPI void APIENTRY glDeleteTextures (GLsizei n, const GLuint *textures); GLAPI void APIENTRY glDeleteTextures (GLsizei n, const GLuint *textures);
GLAPI void APIENTRY glGenTextures (GLsizei n, GLuint *textures); GLAPI void APIENTRY glGenTextures (GLsizei n, GLuint *textures);
@ -478,7 +481,7 @@ GL3W_API GL3WglProc imgl3wGetProcAddress(const char *proc);
/* gl3w internal state */ /* gl3w internal state */
union ImGL3WProcs { union ImGL3WProcs {
GL3WglProc ptr[59]; GL3WglProc ptr[60];
struct { struct {
PFNGLACTIVETEXTUREPROC ActiveTexture; PFNGLACTIVETEXTUREPROC ActiveTexture;
PFNGLATTACHSHADERPROC AttachShader; PFNGLATTACHSHADERPROC AttachShader;
@ -534,6 +537,7 @@ union ImGL3WProcs {
PFNGLSHADERSOURCEPROC ShaderSource; PFNGLSHADERSOURCEPROC ShaderSource;
PFNGLTEXIMAGE2DPROC TexImage2D; PFNGLTEXIMAGE2DPROC TexImage2D;
PFNGLTEXPARAMETERIPROC TexParameteri; PFNGLTEXPARAMETERIPROC TexParameteri;
PFNGLTEXSUBIMAGE2DPROC TexSubImage2D;
PFNGLUNIFORM1IPROC Uniform1i; PFNGLUNIFORM1IPROC Uniform1i;
PFNGLUNIFORMMATRIX4FVPROC UniformMatrix4fv; PFNGLUNIFORMMATRIX4FVPROC UniformMatrix4fv;
PFNGLUSEPROGRAMPROC UseProgram; PFNGLUSEPROGRAMPROC UseProgram;
@ -599,6 +603,7 @@ GL3W_API extern union ImGL3WProcs imgl3wProcs;
#define glShaderSource imgl3wProcs.gl.ShaderSource #define glShaderSource imgl3wProcs.gl.ShaderSource
#define glTexImage2D imgl3wProcs.gl.TexImage2D #define glTexImage2D imgl3wProcs.gl.TexImage2D
#define glTexParameteri imgl3wProcs.gl.TexParameteri #define glTexParameteri imgl3wProcs.gl.TexParameteri
#define glTexSubImage2D imgl3wProcs.gl.TexSubImage2D
#define glUniform1i imgl3wProcs.gl.Uniform1i #define glUniform1i imgl3wProcs.gl.Uniform1i
#define glUniformMatrix4fv imgl3wProcs.gl.UniformMatrix4fv #define glUniformMatrix4fv imgl3wProcs.gl.UniformMatrix4fv
#define glUseProgram imgl3wProcs.gl.UseProgram #define glUseProgram imgl3wProcs.gl.UseProgram
@ -894,6 +899,7 @@ static const char *proc_names[] = {
"glShaderSource", "glShaderSource",
"glTexImage2D", "glTexImage2D",
"glTexParameteri", "glTexParameteri",
"glTexSubImage2D",
"glUniform1i", "glUniform1i",
"glUniformMatrix4fv", "glUniformMatrix4fv",
"glUseProgram", "glUseProgram",

View file

@ -7,7 +7,7 @@
// [X] Platform: Clipboard support is part of core Dear ImGui (no specific code in this backend). // [X] Platform: Clipboard support is part of core Dear ImGui (no specific code in this backend).
// [X] Platform: Mouse support. Can discriminate Mouse/Pen. // [X] Platform: Mouse support. Can discriminate Mouse/Pen.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy kVK_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy kVK_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Gamepad support.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: IME support. // [X] Platform: IME support.
// [x] Platform: Multi-viewport / platform windows. // [x] Platform: Multi-viewport / platform windows.

View file

@ -7,7 +7,7 @@
// [X] Platform: Clipboard support is part of core Dear ImGui (no specific code in this backend). // [X] Platform: Clipboard support is part of core Dear ImGui (no specific code in this backend).
// [X] Platform: Mouse support. Can discriminate Mouse/Pen. // [X] Platform: Mouse support. Can discriminate Mouse/Pen.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy kVK_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy kVK_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Gamepad support.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: IME support. // [X] Platform: IME support.
// [x] Platform: Multi-viewport / platform windows. // [x] Platform: Multi-viewport / platform windows.
@ -35,6 +35,9 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-XX-XX: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-XX-XX: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-06-12: ImGui_ImplOSX_HandleEvent() only process event for window containing our view. (#8644)
// 2025-05-15: [Docking] Add Platform_GetWindowFramebufferScale() handler, to allow varying Retina display density on multiple monitors.
// 2025-03-21: Fill gamepad inputs and set ImGuiBackendFlags_HasGamepad regardless of ImGuiConfigFlags_NavEnableGamepad being set.
// 2025-01-20: Removed notification observer when shutting down. (#8331) // 2025-01-20: Removed notification observer when shutting down. (#8331)
// 2024-08-22: moved some OS/backend related function pointers from ImGuiIO to ImGuiPlatformIO: // 2024-08-22: moved some OS/backend related function pointers from ImGuiIO to ImGuiPlatformIO:
// - io.GetClipboardTextFn -> platform_io.Platform_GetClipboardTextFn // - io.GetClipboardTextFn -> platform_io.Platform_GetClipboardTextFn
@ -509,7 +512,7 @@ bool ImGui_ImplOSX_Init(NSView* view)
[view addSubview:bd->KeyEventResponder]; [view addSubview:bd->KeyEventResponder];
ImGui_ImplOSX_AddTrackingArea(view); ImGui_ImplOSX_AddTrackingArea(view);
platform_io.Platform_SetImeDataFn = [](ImGuiContext*, ImGuiViewport* viewport, ImGuiPlatformImeData* data) -> void platform_io.Platform_SetImeDataFn = [](ImGuiContext*, ImGuiViewport*, ImGuiPlatformImeData* data) -> void
{ {
ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData(); ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
if (data->WantVisible) if (data->WantVisible)
@ -569,7 +572,7 @@ static void ImGui_ImplOSX_UpdateMouseCursor()
else else
{ {
NSCursor* desired = bd->MouseCursors[imgui_cursor] ?: bd->MouseCursors[ImGuiMouseCursor_Arrow]; NSCursor* desired = bd->MouseCursors[imgui_cursor] ?: bd->MouseCursors[ImGuiMouseCursor_Arrow];
// -[NSCursor set] generates measureable overhead if called unconditionally. // -[NSCursor set] generates measurable overhead if called unconditionally.
if (desired != NSCursor.currentCursor) if (desired != NSCursor.currentCursor)
{ {
[desired set]; [desired set];
@ -585,8 +588,6 @@ static void ImGui_ImplOSX_UpdateMouseCursor()
static void ImGui_ImplOSX_UpdateGamepads() static void ImGui_ImplOSX_UpdateGamepads()
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
return;
#if APPLE_HAS_CONTROLLER #if APPLE_HAS_CONTROLLER
GCController* controller = GCController.current; GCController* controller = GCController.current;
@ -657,9 +658,9 @@ void ImGui_ImplOSX_NewFrame(NSView* view)
// Setup display size // Setup display size
if (view) if (view)
{ {
const float dpi = (float)[view.window backingScaleFactor]; const float fb_scale = (float)[view.window backingScaleFactor];
io.DisplaySize = ImVec2((float)view.bounds.size.width, (float)view.bounds.size.height); io.DisplaySize = ImVec2((float)view.bounds.size.width, (float)view.bounds.size.height);
io.DisplayFramebufferScale = ImVec2(dpi, dpi); io.DisplayFramebufferScale = ImVec2(fb_scale, fb_scale);
} }
// Setup time step // Setup time step
@ -697,8 +698,16 @@ static ImGuiMouseSource GetMouseSource(NSEvent* event)
static bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view) static bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view)
{ {
ImGuiIO& io = ImGui::GetIO(); // Only process events from the window containing ImGui view
void* event_handle = (__bridge void*)(event.window);
void* view_handle = (__bridge void*)(view.window);
if (event_handle == nullptr || view_handle == nullptr)
return false;
ImGuiViewport* viewport = ImGui::FindViewportByPlatformHandle(view_handle);
if (viewport == nullptr || viewport->PlatformHandleRaw != event_handle)
return false;
ImGuiIO& io = ImGui::GetIO();
if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown || event.type == NSEventTypeOtherMouseDown) if (event.type == NSEventTypeLeftMouseDown || event.type == NSEventTypeRightMouseDown || event.type == NSEventTypeOtherMouseDown)
{ {
int button = (int)[event buttonNumber]; int button = (int)[event buttonNumber];
@ -831,8 +840,6 @@ static bool ImGui_ImplOSX_HandleEvent(NSEvent* event, NSView* view)
default: default:
return io.WantCaptureKeyboard; return io.WantCaptureKeyboard;
} }
NSEventModifierFlags modifier_flags = [event modifierFlags];
io.AddKeyEvent(key, (modifier_flags & mask) != 0); io.AddKeyEvent(key, (modifier_flags & mask) != 0);
io.SetKeyEventNativeData(key, key_code, -1); // To support legacy indexing (<1.87 user code) io.SetKeyEventNativeData(key, key_code, -1); // To support legacy indexing (<1.87 user code)
} }
@ -873,13 +880,13 @@ static void ImGui_ImplOSX_AddTrackingArea(NSView* _Nonnull view)
// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first.. // If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..
//-------------------------------------------------------------------------------------------------------- //--------------------------------------------------------------------------------------------------------
struct ImGuiViewportDataOSX struct ImGui_ImplOSX_ViewportData
{ {
NSWindow* Window; NSWindow* Window;
bool WindowOwned; bool WindowOwned;
ImGuiViewportDataOSX() { WindowOwned = false; } ImGui_ImplOSX_ViewportData() { WindowOwned = false; }
~ImGuiViewportDataOSX() { IM_ASSERT(Window == nil); } ~ImGui_ImplOSX_ViewportData() { IM_ASSERT(Window == nil); }
}; };
@interface ImGui_ImplOSX_Window: NSWindow @interface ImGui_ImplOSX_Window: NSWindow
@ -904,8 +911,8 @@ static void ConvertNSRect(NSRect* r)
static void ImGui_ImplOSX_CreateWindow(ImGuiViewport* viewport) static void ImGui_ImplOSX_CreateWindow(ImGuiViewport* viewport)
{ {
ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData(); ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
ImGuiViewportDataOSX* data = IM_NEW(ImGuiViewportDataOSX)(); ImGui_ImplOSX_ViewportData* vd = IM_NEW(ImGui_ImplOSX_ViewportData)();
viewport->PlatformUserData = data; viewport->PlatformUserData = vd;
NSScreen* screen = bd->Window.screen; NSScreen* screen = bd->Window.screen;
NSRect rect = NSMakeRect(viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y); NSRect rect = NSMakeRect(viewport->Pos.x, viewport->Pos.y, viewport->Size.x, viewport->Size.y);
@ -934,8 +941,8 @@ static void ImGui_ImplOSX_CreateWindow(ImGuiViewport* viewport)
window.contentView = view; window.contentView = view;
data->Window = window; vd->Window = window;
data->WindowOwned = true; vd->WindowOwned = true;
viewport->PlatformRequestResize = false; viewport->PlatformRequestResize = false;
viewport->PlatformHandle = viewport->PlatformHandleRaw = (__bridge_retained void*)window; viewport->PlatformHandle = viewport->PlatformHandleRaw = (__bridge_retained void*)window;
} }
@ -945,40 +952,40 @@ static void ImGui_ImplOSX_DestroyWindow(ImGuiViewport* viewport)
NSWindow* window = (__bridge_transfer NSWindow*)viewport->PlatformHandleRaw; NSWindow* window = (__bridge_transfer NSWindow*)viewport->PlatformHandleRaw;
window = nil; window = nil;
if (ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData) if (ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)viewport->PlatformUserData)
{ {
NSWindow* window = data->Window; NSWindow* window = vd->Window;
if (window != nil && data->WindowOwned) if (window != nil && vd->WindowOwned)
{ {
window.contentView = nil; window.contentView = nil;
window.contentViewController = nil; window.contentViewController = nil;
[window orderOut:nil]; [window orderOut:nil];
} }
data->Window = nil; vd->Window = nil;
IM_DELETE(data); IM_DELETE(vd);
} }
viewport->PlatformUserData = viewport->PlatformHandle = viewport->PlatformHandleRaw = nullptr; viewport->PlatformUserData = viewport->PlatformHandle = viewport->PlatformHandleRaw = nullptr;
} }
static void ImGui_ImplOSX_ShowWindow(ImGuiViewport* viewport) static void ImGui_ImplOSX_ShowWindow(ImGuiViewport* viewport)
{ {
ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)viewport->PlatformUserData;
IM_ASSERT(data->Window != 0); IM_ASSERT(vd->Window != 0);
if (viewport->Flags & ImGuiViewportFlags_NoFocusOnAppearing) if (viewport->Flags & ImGuiViewportFlags_NoFocusOnAppearing)
[data->Window orderFront:nil]; [vd->Window orderFront:nil];
else else
[data->Window makeKeyAndOrderFront:nil]; [vd->Window makeKeyAndOrderFront:nil];
[data->Window setIsVisible:YES]; [vd->Window setIsVisible:YES];
} }
static ImVec2 ImGui_ImplOSX_GetWindowPos(ImGuiViewport* viewport) static ImVec2 ImGui_ImplOSX_GetWindowPos(ImGuiViewport* viewport)
{ {
ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)viewport->PlatformUserData;
IM_ASSERT(data->Window != 0); IM_ASSERT(vd->Window != 0);
NSWindow* window = data->Window; NSWindow* window = vd->Window;
NSRect frame = window.frame; NSRect frame = window.frame;
NSRect contentRect = window.contentLayoutRect; NSRect contentRect = window.contentLayoutRect;
if (window.styleMask & NSWindowStyleMaskFullSizeContentView) // No title bar windows should be considered. if (window.styleMask & NSWindowStyleMaskFullSizeContentView) // No title bar windows should be considered.
@ -990,10 +997,10 @@ static ImVec2 ImGui_ImplOSX_GetWindowPos(ImGuiViewport* viewport)
static void ImGui_ImplOSX_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos) static void ImGui_ImplOSX_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos)
{ {
ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)viewport->PlatformUserData;
IM_ASSERT(data->Window != 0); IM_ASSERT(vd->Window != 0);
NSWindow* window = data->Window; NSWindow* window = vd->Window;
NSSize size = window.frame.size; NSSize size = window.frame.size;
NSRect r = NSMakeRect(pos.x, pos.y, size.width, size.height); NSRect r = NSMakeRect(pos.x, pos.y, size.width, size.height);
@ -1003,20 +1010,20 @@ static void ImGui_ImplOSX_SetWindowPos(ImGuiViewport* viewport, ImVec2 pos)
static ImVec2 ImGui_ImplOSX_GetWindowSize(ImGuiViewport* viewport) static ImVec2 ImGui_ImplOSX_GetWindowSize(ImGuiViewport* viewport)
{ {
ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)viewport->PlatformUserData;
IM_ASSERT(data->Window != 0); IM_ASSERT(vd->Window != 0);
NSWindow* window = data->Window; NSWindow* window = vd->Window;
NSSize size = window.contentLayoutRect.size; NSSize size = window.contentLayoutRect.size;
return ImVec2(size.width, size.height); return ImVec2(size.width, size.height);
} }
static void ImGui_ImplOSX_SetWindowSize(ImGuiViewport* viewport, ImVec2 size) static void ImGui_ImplOSX_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
{ {
ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)viewport->PlatformUserData;
IM_ASSERT(data->Window != 0); IM_ASSERT(vd->Window != 0);
NSWindow* window = data->Window; NSWindow* window = vd->Window;
NSRect rect = window.frame; NSRect rect = window.frame;
rect.origin.y -= (size.y - rect.size.height); rect.origin.y -= (size.y - rect.size.height);
rect.size.width = size.x; rect.size.width = size.x;
@ -1024,53 +1031,61 @@ static void ImGui_ImplOSX_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
[window setFrame:rect display:YES]; [window setFrame:rect display:YES];
} }
static ImVec2 ImGui_ImplOSX_GetWindowFramebufferScale(ImGuiViewport* viewport)
{
ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)viewport->PlatformUserData;
NSWindow* window = vd->Window;
const float fb_scale = (float)[window backingScaleFactor];
return ImVec2(fb_scale, fb_scale);
}
static void ImGui_ImplOSX_SetWindowFocus(ImGuiViewport* viewport) static void ImGui_ImplOSX_SetWindowFocus(ImGuiViewport* viewport)
{ {
ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData(); ImGui_ImplOSX_Data* bd = ImGui_ImplOSX_GetBackendData();
ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)viewport->PlatformUserData;
IM_ASSERT(data->Window != 0); IM_ASSERT(vd->Window != 0);
[data->Window makeKeyAndOrderFront:bd->Window]; [vd->Window makeKeyAndOrderFront:bd->Window];
} }
static bool ImGui_ImplOSX_GetWindowFocus(ImGuiViewport* viewport) static bool ImGui_ImplOSX_GetWindowFocus(ImGuiViewport* viewport)
{ {
ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)viewport->PlatformUserData;
IM_ASSERT(data->Window != 0); IM_ASSERT(vd->Window != 0);
return data->Window.isKeyWindow; return vd->Window.isKeyWindow;
} }
static bool ImGui_ImplOSX_GetWindowMinimized(ImGuiViewport* viewport) static bool ImGui_ImplOSX_GetWindowMinimized(ImGuiViewport* viewport)
{ {
ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)viewport->PlatformUserData;
IM_ASSERT(data->Window != 0); IM_ASSERT(vd->Window != 0);
return data->Window.isMiniaturized; return vd->Window.isMiniaturized;
} }
static void ImGui_ImplOSX_SetWindowTitle(ImGuiViewport* viewport, const char* title) static void ImGui_ImplOSX_SetWindowTitle(ImGuiViewport* viewport, const char* title)
{ {
ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)viewport->PlatformUserData;
IM_ASSERT(data->Window != 0); IM_ASSERT(vd->Window != 0);
data->Window.title = [NSString stringWithUTF8String:title]; vd->Window.title = [NSString stringWithUTF8String:title];
} }
static void ImGui_ImplOSX_SetWindowAlpha(ImGuiViewport* viewport, float alpha) static void ImGui_ImplOSX_SetWindowAlpha(ImGuiViewport* viewport, float alpha)
{ {
ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)viewport->PlatformUserData;
IM_ASSERT(data->Window != 0); IM_ASSERT(vd->Window != 0);
IM_ASSERT(alpha >= 0.0f && alpha <= 1.0f); IM_ASSERT(alpha >= 0.0f && alpha <= 1.0f);
data->Window.alphaValue = alpha; vd->Window.alphaValue = alpha;
} }
static float ImGui_ImplOSX_GetWindowDpiScale(ImGuiViewport* viewport) static float ImGui_ImplOSX_GetWindowDpiScale(ImGuiViewport* viewport)
{ {
ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)viewport->PlatformUserData; ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)viewport->PlatformUserData;
IM_ASSERT(data->Window != 0); IM_ASSERT(vd->Window != 0);
return data->Window.backingScaleFactor; return vd->Window.backingScaleFactor;
} }
static void ImGui_ImplOSX_UpdateMonitors() static void ImGui_ImplOSX_UpdateMonitors()
@ -1113,6 +1128,7 @@ static void ImGui_ImplOSX_InitMultiViewportSupport()
platform_io.Platform_GetWindowPos = ImGui_ImplOSX_GetWindowPos; platform_io.Platform_GetWindowPos = ImGui_ImplOSX_GetWindowPos;
platform_io.Platform_SetWindowSize = ImGui_ImplOSX_SetWindowSize; platform_io.Platform_SetWindowSize = ImGui_ImplOSX_SetWindowSize;
platform_io.Platform_GetWindowSize = ImGui_ImplOSX_GetWindowSize; platform_io.Platform_GetWindowSize = ImGui_ImplOSX_GetWindowSize;
platform_io.Platform_GetWindowFramebufferScale = ImGui_ImplOSX_GetWindowFramebufferScale;
platform_io.Platform_SetWindowFocus = ImGui_ImplOSX_SetWindowFocus; platform_io.Platform_SetWindowFocus = ImGui_ImplOSX_SetWindowFocus;
platform_io.Platform_GetWindowFocus = ImGui_ImplOSX_GetWindowFocus; platform_io.Platform_GetWindowFocus = ImGui_ImplOSX_GetWindowFocus;
platform_io.Platform_GetWindowMinimized = ImGui_ImplOSX_GetWindowMinimized; platform_io.Platform_GetWindowMinimized = ImGui_ImplOSX_GetWindowMinimized;
@ -1122,10 +1138,10 @@ static void ImGui_ImplOSX_InitMultiViewportSupport()
// Register main window handle (which is owned by the main application, not by us) // Register main window handle (which is owned by the main application, not by us)
ImGuiViewport* main_viewport = ImGui::GetMainViewport(); ImGuiViewport* main_viewport = ImGui::GetMainViewport();
ImGuiViewportDataOSX* data = IM_NEW(ImGuiViewportDataOSX)(); ImGui_ImplOSX_ViewportData* vd = IM_NEW(ImGui_ImplOSX_ViewportData)();
data->Window = bd->Window; vd->Window = bd->Window;
data->WindowOwned = false; vd->WindowOwned = false;
main_viewport->PlatformUserData = data; main_viewport->PlatformUserData = vd;
main_viewport->PlatformHandle = (__bridge void*)bd->Window; main_viewport->PlatformHandle = (__bridge void*)bd->Window;
[NSNotificationCenter.defaultCenter addObserver:bd->Observer [NSNotificationCenter.defaultCenter addObserver:bd->Observer
@ -1149,8 +1165,8 @@ static void ImGui_ImplOSX_ShutdownMultiViewportSupport()
} }
ImGuiViewport* main_viewport = ImGui::GetMainViewport(); ImGuiViewport* main_viewport = ImGui::GetMainViewport();
ImGuiViewportDataOSX* data = (ImGuiViewportDataOSX*)main_viewport->PlatformUserData; ImGui_ImplOSX_ViewportData* vd = (ImGui_ImplOSX_ViewportData*)main_viewport->PlatformUserData;
IM_DELETE(data); IM_DELETE(vd);
main_viewport->PlatformUserData = nullptr; main_viewport->PlatformUserData = nullptr;
ImGui::DestroyPlatformWindows(); ImGui::DestroyPlatformWindows();
} }

View file

@ -7,7 +7,7 @@
// [X] Platform: Clipboard support. // [X] Platform: Clipboard support.
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen. // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Gamepad support.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!. // [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!.
// [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
@ -26,6 +26,11 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-06-11: Added ImGui_ImplSDL2_GetContentScaleForWindow(SDL_Window* window) and ImGui_ImplSDL2_GetContentScaleForDisplay(int display_index) helper to facilitate making DPI-aware apps.
// 2025-05-15: [Docking] Add Platform_GetWindowFramebufferScale() handler, to allow varying Retina display density on multiple monitors.
// 2025-04-09: [Docking] Revert update monitors and work areas information every frame. Only do it on Windows. (#8415, #8558)
// 2025-04-09: Don't attempt to call SDL_CaptureMouse() on drivers where we don't call SDL_GetGlobalMouseState(). (#8561)
// 2025-03-21: Fill gamepad inputs and set ImGuiBackendFlags_HasGamepad regardless of ImGuiConfigFlags_NavEnableGamepad being set.
// 2025-03-10: When dealing with OEM keys, use scancodes instead of translated keycodes to choose ImGuiKey values. (#7136, #7201, #7206, #7306, #7670, #7672, #8468) // 2025-03-10: When dealing with OEM keys, use scancodes instead of translated keycodes to choose ImGuiKey values. (#7136, #7201, #7206, #7306, #7670, #7672, #8468)
// 2025-02-26: Only start SDL_CaptureMouse() when mouse is being dragged, to mitigate issues with e.g.Linux debuggers not claiming capture back. (#6410, #3650) // 2025-02-26: Only start SDL_CaptureMouse() when mouse is being dragged, to mitigate issues with e.g.Linux debuggers not claiming capture back. (#6410, #3650)
// 2025-02-25: [Docking] Revert to use SDL_GetDisplayBounds() for WorkPos/WorkRect if SDL_GetDisplayUsableBounds() failed. // 2025-02-25: [Docking] Revert to use SDL_GetDisplayBounds() for WorkPos/WorkRect if SDL_GetDisplayUsableBounds() failed.
@ -110,13 +115,15 @@
// Clang warnings with -Weverything // Clang warnings with -Weverything
#if defined(__clang__) #if defined(__clang__)
#pragma clang diagnostic push #pragma clang diagnostic push
//#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
#endif #endif
// SDL // SDL
// (the multi-viewports feature requires SDL features supported from SDL 2.0.4+. SDL 2.0.5+ is highly recommended) // (the multi-viewports feature requires SDL features supported from SDL 2.0.4+. SDL 2.0.5+ is highly recommended)
#include <SDL.h> #include <SDL.h>
#include <SDL_syswm.h> #include <SDL_syswm.h>
#include <stdio.h> // for snprintf()
#ifdef __APPLE__ #ifdef __APPLE__
#include <TargetConditionals.h> #include <TargetConditionals.h>
extern "C" { extern "C" {
@ -154,7 +161,9 @@ struct ImGui_ImplSDL2_Data
SDL_Renderer* Renderer; SDL_Renderer* Renderer;
Uint64 Time; Uint64 Time;
char* ClipboardTextData; char* ClipboardTextData;
char BackendPlatformName[48];
bool UseVulkan; bool UseVulkan;
bool WantUpdateMonitors;
// Mouse handling // Mouse handling
Uint32 MouseWindowID; Uint32 MouseWindowID;
@ -163,6 +172,7 @@ struct ImGui_ImplSDL2_Data
SDL_Cursor* MouseLastCursor; SDL_Cursor* MouseLastCursor;
int MouseLastLeaveFrame; int MouseLastLeaveFrame;
bool MouseCanUseGlobalState; bool MouseCanUseGlobalState;
bool MouseCanUseCapture;
bool MouseCanReportHoveredViewport; // This is hard to use/unreliable on SDL so we'll set ImGuiBackendFlags_HasMouseHoveredViewport dynamically based on state. bool MouseCanReportHoveredViewport; // This is hard to use/unreliable on SDL so we'll set ImGuiBackendFlags_HasMouseHoveredViewport dynamically based on state.
// Gamepad handling // Gamepad handling
@ -459,16 +469,26 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
case SDL_KEYDOWN: case SDL_KEYDOWN:
case SDL_KEYUP: case SDL_KEYUP:
{ {
if (ImGui_ImplSDL2_GetViewportForWindowID(event->key.windowID) == nullptr) ImGuiViewport* viewport = ImGui_ImplSDL2_GetViewportForWindowID(event->key.windowID);
if (viewport == nullptr)
return false; return false;
//IMGUI_DEBUG_LOG("SDL_KEY%s : key=0x%08X ('%s'), scancode=%d ('%s'), mod=%X, windowID=%d, viewport=%08X\n",
// (event->type == SDL_KEYDOWN) ? "DOWN" : "UP ", event->key.keysym.sym, SDL_GetKeyName(event->key.keysym.sym), event->key.keysym.scancode, SDL_GetScancodeName(event->key.keysym.scancode), event->key.keysym.mod, event->key.windowID, viewport ? viewport->ID : 0);
ImGui_ImplSDL2_UpdateKeyModifiers((SDL_Keymod)event->key.keysym.mod); ImGui_ImplSDL2_UpdateKeyModifiers((SDL_Keymod)event->key.keysym.mod);
//IMGUI_DEBUG_LOG("SDL_KEY_%s : key=%d ('%s'), scancode=%d ('%s'), mod=%X\n",
// (event->type == SDL_KEYDOWN) ? "DOWN" : "UP ", event->key.keysym.sym, SDL_GetKeyName(event->key.keysym.sym), event->key.keysym.scancode, SDL_GetScancodeName(event->key.keysym.scancode), event->key.keysym.mod);
ImGuiKey key = ImGui_ImplSDL2_KeyEventToImGuiKey(event->key.keysym.sym, event->key.keysym.scancode); ImGuiKey key = ImGui_ImplSDL2_KeyEventToImGuiKey(event->key.keysym.sym, event->key.keysym.scancode);
io.AddKeyEvent(key, (event->type == SDL_KEYDOWN)); io.AddKeyEvent(key, (event->type == SDL_KEYDOWN));
io.SetKeyEventNativeData(key, event->key.keysym.sym, event->key.keysym.scancode, event->key.keysym.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions. io.SetKeyEventNativeData(key, (int)event->key.keysym.sym, (int)event->key.keysym.scancode, (int)event->key.keysym.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions.
return true; return true;
} }
#if SDL_HAS_DISPLAY_EVENT
case SDL_DISPLAYEVENT:
{
// 2.0.26 has SDL_DISPLAYEVENT_CONNECTED/SDL_DISPLAYEVENT_DISCONNECTED/SDL_DISPLAYEVENT_ORIENTATION,
// so change of DPI/Scaling are not reflected in this event. (SDL3 has it)
bd->WantUpdateMonitors = true;
return true;
}
#endif
case SDL_WINDOWEVENT: case SDL_WINDOWEVENT:
{ {
ImGuiViewport* viewport = ImGui_ImplSDL2_GetViewportForWindowID(event->window.windowID); ImGuiViewport* viewport = ImGui_ImplSDL2_GetViewportForWindowID(event->window.windowID);
@ -488,15 +508,17 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
} }
if (window_event == SDL_WINDOWEVENT_LEAVE) if (window_event == SDL_WINDOWEVENT_LEAVE)
bd->MouseLastLeaveFrame = ImGui::GetFrameCount() + 1; bd->MouseLastLeaveFrame = ImGui::GetFrameCount() + 1;
//if (window_event == SDL_WINDOWEVENT_FOCUS_GAINED) { IMGUI_DEBUG_LOG("SDL_WINDOWEVENT_FOCUS_GAINED: windowId %d, viewport: %08X\n", event->window.windowID, viewport ? viewport->ID : 0); }
//if (window_event == SDL_WINDOWEVENT_FOCUS_LOST) { IMGUI_DEBUG_LOG("SDL_WINDOWEVENT_FOCUS_LOST: windowId %d, viewport: %08X\n", event->window.windowID, viewport ? viewport->ID : 0); }
if (window_event == SDL_WINDOWEVENT_FOCUS_GAINED) if (window_event == SDL_WINDOWEVENT_FOCUS_GAINED)
io.AddFocusEvent(true); io.AddFocusEvent(true);
else if (window_event == SDL_WINDOWEVENT_FOCUS_LOST) if (window_event == SDL_WINDOWEVENT_FOCUS_LOST)
io.AddFocusEvent(false); io.AddFocusEvent(false);
else if (window_event == SDL_WINDOWEVENT_CLOSE) if (window_event == SDL_WINDOWEVENT_CLOSE)
viewport->PlatformRequestClose = true; viewport->PlatformRequestClose = true;
else if (window_event == SDL_WINDOWEVENT_MOVED) if (window_event == SDL_WINDOWEVENT_MOVED)
viewport->PlatformRequestMove = true; viewport->PlatformRequestMove = true;
else if (window_event == SDL_WINDOWEVENT_RESIZED) if (window_event == SDL_WINDOWEVENT_RESIZED)
viewport->PlatformRequestResize = true; viewport->PlatformRequestResize = true;
return true; return true;
} }
@ -506,6 +528,8 @@ bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event)
bd->WantUpdateGamepadsList = true; bd->WantUpdateGamepadsList = true;
return true; return true;
} }
default:
break;
} }
return false; return false;
} }
@ -519,26 +543,23 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer, void
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
//SDL_SetHint(SDL_HINT_EVENT_LOGGING, "2");
// Check and store if we are on a SDL backend that supports global mouse position // Obtain compiled and runtime versions
// ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list) SDL_version ver_compiled;
bool mouse_can_use_global_state = false; SDL_version ver_runtime;
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE SDL_VERSION(&ver_compiled);
const char* sdl_backend = SDL_GetCurrentVideoDriver(); SDL_GetVersion(&ver_runtime);
const char* global_mouse_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" };
for (int n = 0; n < IM_ARRAYSIZE(global_mouse_whitelist); n++)
if (strncmp(sdl_backend, global_mouse_whitelist[n], strlen(global_mouse_whitelist[n])) == 0)
mouse_can_use_global_state = true;
#endif
// Setup backend capabilities flags // Setup backend capabilities flags
ImGui_ImplSDL2_Data* bd = IM_NEW(ImGui_ImplSDL2_Data)(); ImGui_ImplSDL2_Data* bd = IM_NEW(ImGui_ImplSDL2_Data)();
snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_sdl2 (%u.%u.%u, %u.%u.%u)",
ver_compiled.major, ver_compiled.minor, ver_compiled.patch, ver_runtime.major, ver_runtime.minor, ver_runtime.patch);
io.BackendPlatformUserData = (void*)bd; io.BackendPlatformUserData = (void*)bd;
io.BackendPlatformName = "imgui_impl_sdl2"; io.BackendPlatformName = bd->BackendPlatformName;
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
if (mouse_can_use_global_state) // (ImGuiBackendFlags_PlatformHasViewports may be set just below)
io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional)
bd->Window = window; bd->Window = window;
bd->WindowID = SDL_GetWindowID(window); bd->WindowID = SDL_GetWindowID(window);
@ -546,13 +567,26 @@ static bool ImGui_ImplSDL2_Init(SDL_Window* window, SDL_Renderer* renderer, void
// SDL on Linux/OSX doesn't report events for unfocused windows (see https://github.com/ocornut/imgui/issues/4960) // SDL on Linux/OSX doesn't report events for unfocused windows (see https://github.com/ocornut/imgui/issues/4960)
// We will use 'MouseCanReportHoveredViewport' to set 'ImGuiBackendFlags_HasMouseHoveredViewport' dynamically each frame. // We will use 'MouseCanReportHoveredViewport' to set 'ImGuiBackendFlags_HasMouseHoveredViewport' dynamically each frame.
bd->MouseCanUseGlobalState = mouse_can_use_global_state;
#ifndef __APPLE__ #ifndef __APPLE__
bd->MouseCanReportHoveredViewport = bd->MouseCanUseGlobalState; bd->MouseCanReportHoveredViewport = bd->MouseCanUseGlobalState;
#else #else
bd->MouseCanReportHoveredViewport = false; bd->MouseCanReportHoveredViewport = false;
#endif #endif
// Check and store if we are on a SDL backend that supports SDL_GetGlobalMouseState() and SDL_CaptureMouse()
// ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list)
bd->MouseCanUseGlobalState = false;
bd->MouseCanUseCapture = false;
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
const char* sdl_backend = SDL_GetCurrentVideoDriver();
const char* capture_and_global_state_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" };
for (const char* item : capture_and_global_state_whitelist)
if (strncmp(sdl_backend, item, strlen(item)) == 0)
bd->MouseCanUseGlobalState = bd->MouseCanUseCapture = true;
#endif
if (bd->MouseCanUseGlobalState)
io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional)
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Platform_SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText; platform_io.Platform_SetClipboardTextFn = ImGui_ImplSDL2_SetClipboardText;
platform_io.Platform_GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText; platform_io.Platform_GetClipboardTextFn = ImGui_ImplSDL2_GetClipboardText;
@ -700,12 +734,15 @@ static void ImGui_ImplSDL2_UpdateMouseData()
// We forward mouse input when hovered or captured (via SDL_MOUSEMOTION) or when focused (below) // We forward mouse input when hovered or captured (via SDL_MOUSEMOTION) or when focused (below)
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE #if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
// - SDL_CaptureMouse() let the OS know e.g. that our drags can extend outside of parent boundaries (we want updated position) and shouldn't trigger other operations outside. // - SDL_CaptureMouse() let the OS know e.g. that our drags can extend outside of parent boundaries (we want updated position) and shouldn't trigger other operations outside.
// - Debuggers under Linux tends to leave captured mouse on break, which may be very inconvenient, so to migitate the issue we wait until mouse has moved to begin capture. // - Debuggers under Linux tends to leave captured mouse on break, which may be very inconvenient, so to mitigate the issue we wait until mouse has moved to begin capture.
if (bd->MouseCanUseCapture)
{
bool want_capture = false; bool want_capture = false;
for (int button_n = 0; button_n < ImGuiMouseButton_COUNT && !want_capture; button_n++) for (int button_n = 0; button_n < ImGuiMouseButton_COUNT && !want_capture; button_n++)
if (ImGui::IsMouseDragging(button_n, 1.0f)) if (ImGui::IsMouseDragging(button_n, 1.0f))
want_capture = true; want_capture = true;
SDL_CaptureMouse(want_capture ? SDL_TRUE : SDL_FALSE); SDL_CaptureMouse(want_capture ? SDL_TRUE : SDL_FALSE);
}
SDL_Window* focused_window = SDL_GetKeyboardFocus(); SDL_Window* focused_window = SDL_GetKeyboardFocus();
const bool is_app_focused = (focused_window && (bd->Window == focused_window || ImGui_ImplSDL2_GetViewportForWindowID(SDL_GetWindowID(focused_window)) != NULL)); const bool is_app_focused = (focused_window && (bd->Window == focused_window || ImGui_ImplSDL2_GetViewportForWindowID(SDL_GetWindowID(focused_window)) != NULL));
@ -790,6 +827,27 @@ static void ImGui_ImplSDL2_UpdateMouseCursor()
} }
} }
// - On Windows the process needs to be marked DPI-aware!! SDL2 doesn't do it by default. You can call ::SetProcessDPIAware() or call ImGui_ImplWin32_EnableDpiAwareness() from Win32 backend.
// - Apple platforms use FramebufferScale so we always return 1.0f.
// - Some accessibility applications are declaring virtual monitors with a DPI of 0.0f, see #7902. We preserve this value for caller to handle.
float ImGui_ImplSDL2_GetContentScaleForWindow(SDL_Window* window)
{
return ImGui_ImplSDL2_GetContentScaleForDisplay(SDL_GetWindowDisplayIndex(window));
}
float ImGui_ImplSDL2_GetContentScaleForDisplay(int display_index)
{
#if SDL_HAS_PER_MONITOR_DPI
#ifndef __APPLE__
float dpi = 0.0f;
if (SDL_GetDisplayDPI(display_index, &dpi, nullptr, nullptr) == 0)
return dpi / 96.0f;
#endif
#endif
IM_UNUSED(display_index);
return 1.0f;
}
static void ImGui_ImplSDL2_CloseGamepads() static void ImGui_ImplSDL2_CloseGamepads()
{ {
ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
@ -859,9 +917,6 @@ static void ImGui_ImplSDL2_UpdateGamepads()
bd->WantUpdateGamepadsList = false; bd->WantUpdateGamepadsList = false;
} }
// FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)
return;
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
if (bd->Gamepads.Size == 0) if (bd->Gamepads.Size == 0)
return; return;
@ -898,8 +953,10 @@ static void ImGui_ImplSDL2_UpdateGamepads()
// FIXME: Note that doesn't update with DPI/Scaling change only as SDL2 doesn't have an event for it (SDL3 has). // FIXME: Note that doesn't update with DPI/Scaling change only as SDL2 doesn't have an event for it (SDL3 has).
static void ImGui_ImplSDL2_UpdateMonitors() static void ImGui_ImplSDL2_UpdateMonitors()
{ {
ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Monitors.resize(0); platform_io.Monitors.resize(0);
bd->WantUpdateMonitors = false;
int display_count = SDL_GetNumVideoDisplays(); int display_count = SDL_GetNumVideoDisplays();
for (int n = 0; n < display_count; n++) for (int n = 0; n < display_count; n++)
{ {
@ -919,35 +976,52 @@ static void ImGui_ImplSDL2_UpdateMonitors()
#if SDL_HAS_PER_MONITOR_DPI #if SDL_HAS_PER_MONITOR_DPI
monitor.DpiScale = 1.0f; monitor.DpiScale = 1.0f;
#endif #endif
// tildearrow: this is the new code. not sure how well it works.
/*
float dpi_scale = ImGui_ImplSDL2_GetContentScaleForDisplay(n);
if (dpi_scale <= 0.0f)
continue; // Some accessibility applications are declaring virtual monitors with a DPI of 0, see #7902.
monitor.DpiScale = dpi_scale;
*/
monitor.PlatformHandle = (void*)(intptr_t)n; monitor.PlatformHandle = (void*)(intptr_t)n;
platform_io.Monitors.push_back(monitor); platform_io.Monitors.push_back(monitor);
} }
} }
static void ImGui_ImplSDL2_GetWindowSizeAndFramebufferScale(SDL_Window* window, SDL_Renderer* renderer, ImVec2* out_size, ImVec2* out_framebuffer_scale)
{
int w, h;
int display_w, display_h;
SDL_GetWindowSize(window, &w, &h);
if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED)
w = h = 0;
if (renderer != nullptr) {
if (SDL_GetRendererOutputSize(renderer, &display_w, &display_h)!=0) {
display_w=0;
display_h=0;
}
}
#if SDL_HAS_VULKAN
else if (SDL_GetWindowFlags(window) & SDL_WINDOW_VULKAN)
SDL_Vulkan_GetDrawableSize(window, &display_w, &display_h);
#endif
else
SDL_GL_GetDrawableSize(window, &display_w, &display_h);
if (out_size != nullptr)
*out_size = ImVec2((float)w, (float)h);
// tildearrow: TODO: good idea?
if (out_framebuffer_scale != nullptr)
*out_framebuffer_scale = (w > 0 && h > 0) ? ImVec2((float)display_w / w, (float)display_h / h) : ImVec2(1.0f, 1.0f);
}
void ImGui_ImplSDL2_NewFrame() void ImGui_ImplSDL2_NewFrame()
{ {
ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData(); ImGui_ImplSDL2_Data* bd = ImGui_ImplSDL2_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDL2_Init()?"); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDL2_Init()?");
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
// Setup display size (every frame to accommodate for window resizing) // Setup main viewport size (every frame to accommodate for window resizing)
int w, h; ImGui_ImplSDL2_GetWindowSizeAndFramebufferScale(bd->Window, bd->Renderer, &io.DisplaySize, &io.DisplayFramebufferScale);
int display_w, display_h;
SDL_GetWindowSize(bd->Window, &w, &h);
if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED)
w = h = 0;
if (bd->Renderer != nullptr) {
if (SDL_GetRendererOutputSize(bd->Renderer, &display_w, &display_h)!=0) {
display_w=0;
display_h=0;
}
}
#if SDL_HAS_VULKAN
else if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_VULKAN)
SDL_Vulkan_GetDrawableSize(bd->Window, &display_w, &display_h);
#endif
else
SDL_GL_GetDrawableSize(bd->Window, &display_w, &display_h);
if (w > 0 && h > 0) { if (w > 0 && h > 0) {
io.DisplaySize = ImVec2((float)w, (float)h); io.DisplaySize = ImVec2((float)w, (float)h);
@ -956,6 +1030,10 @@ void ImGui_ImplSDL2_NewFrame()
// TODO: is this before, or after? // TODO: is this before, or after?
// Update monitors // Update monitors
#ifdef WIN32
bd->WantUpdateMonitors = true; // Keep polling under Windows to handle changes of work area when resizing task-bar (#8415)
#endif
if (bd->WantUpdateMonitors)
ImGui_ImplSDL2_UpdateMonitors(); ImGui_ImplSDL2_UpdateMonitors();
// On Apple and Wayland, The window size is reported in Low DPI, even when running in high DPI mode // On Apple and Wayland, The window size is reported in Low DPI, even when running in high DPI mode
@ -1090,7 +1168,7 @@ static void ImGui_ImplSDL2_DestroyWindow(ImGuiViewport* viewport)
static void ImGui_ImplSDL2_ShowWindow(ImGuiViewport* viewport) static void ImGui_ImplSDL2_ShowWindow(ImGuiViewport* viewport)
{ {
ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
#if defined(_WIN32) && !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP || WINAPI_FAMILY == WINAPI_FAMILY_GAMES)) #if defined(_WIN32) && !(defined(WINAPI_FAMILY) && ((defined(WINAPI_FAMILY_APP) && WINAPI_FAMILY == WINAPI_FAMILY_APP) || (defined(WINAPI_FAMILY_GAMES) && WINAPI_FAMILY == WINAPI_FAMILY_GAMES)))
HWND hwnd = (HWND)viewport->PlatformHandleRaw; HWND hwnd = (HWND)viewport->PlatformHandleRaw;
// SDL hack: Hide icon from task bar // SDL hack: Hide icon from task bar
@ -1145,6 +1223,15 @@ static void ImGui_ImplSDL2_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
SDL_SetWindowSize(vd->Window, (int)size.x, (int)size.y); SDL_SetWindowSize(vd->Window, (int)size.x, (int)size.y);
} }
static ImVec2 ImGui_ImplSDL2_GetWindowFramebufferScale(ImGuiViewport* viewport)
{
// FIXME: SDL_Renderer does not support multi-viewport.
ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
ImVec2 framebuffer_scale;
ImGui_ImplSDL2_GetWindowSizeAndFramebufferScale(vd->Window, nullptr, nullptr, &framebuffer_scale);
return framebuffer_scale;
}
static void ImGui_ImplSDL2_SetWindowTitle(ImGuiViewport* viewport, const char* title) static void ImGui_ImplSDL2_SetWindowTitle(ImGuiViewport* viewport, const char* title)
{ {
ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData; ImGui_ImplSDL2_ViewportData* vd = (ImGui_ImplSDL2_ViewportData*)viewport->PlatformUserData;
@ -1218,6 +1305,7 @@ static void ImGui_ImplSDL2_InitMultiViewportSupport(SDL_Window* window, void* sd
platform_io.Platform_GetWindowPos = ImGui_ImplSDL2_GetWindowPos; platform_io.Platform_GetWindowPos = ImGui_ImplSDL2_GetWindowPos;
platform_io.Platform_SetWindowSize = ImGui_ImplSDL2_SetWindowSize; platform_io.Platform_SetWindowSize = ImGui_ImplSDL2_SetWindowSize;
platform_io.Platform_GetWindowSize = ImGui_ImplSDL2_GetWindowSize; platform_io.Platform_GetWindowSize = ImGui_ImplSDL2_GetWindowSize;
platform_io.Platform_GetWindowFramebufferScale = ImGui_ImplSDL2_GetWindowFramebufferScale;
platform_io.Platform_SetWindowFocus = ImGui_ImplSDL2_SetWindowFocus; platform_io.Platform_SetWindowFocus = ImGui_ImplSDL2_SetWindowFocus;
platform_io.Platform_GetWindowFocus = ImGui_ImplSDL2_GetWindowFocus; platform_io.Platform_GetWindowFocus = ImGui_ImplSDL2_GetWindowFocus;
platform_io.Platform_GetWindowMinimized = ImGui_ImplSDL2_GetWindowMinimized; platform_io.Platform_GetWindowMinimized = ImGui_ImplSDL2_GetWindowMinimized;

View file

@ -6,7 +6,7 @@
// [X] Platform: Clipboard support. // [X] Platform: Clipboard support.
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen. // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Gamepad support.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!. // [X] Platform: Basic IME support. App needs to call 'SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");' before SDL_CreateWindow()!.
// [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
@ -42,6 +42,10 @@ IMGUI_IMPL_API void ImGui_ImplSDL2_Shutdown();
IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame(); IMGUI_IMPL_API void ImGui_ImplSDL2_NewFrame();
IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event); IMGUI_IMPL_API bool ImGui_ImplSDL2_ProcessEvent(const SDL_Event* event);
// DPI-related helpers (optional)
IMGUI_IMPL_API float ImGui_ImplSDL2_GetContentScaleForWindow(SDL_Window* window);
IMGUI_IMPL_API float ImGui_ImplSDL2_GetContentScaleForDisplay(int display_index);
// Gamepad selection automatically starts in AutoFirst mode, picking first available SDL_Gamepad. You may override this. // Gamepad selection automatically starts in AutoFirst mode, picking first available SDL_Gamepad. You may override this.
// When using manual mode, caller is responsible for opening/closing gamepad. // When using manual mode, caller is responsible for opening/closing gamepad.
enum ImGui_ImplSDL2_GamepadMode { ImGui_ImplSDL2_GamepadMode_AutoFirst, ImGui_ImplSDL2_GamepadMode_AutoAll, ImGui_ImplSDL2_GamepadMode_Manual }; enum ImGui_ImplSDL2_GamepadMode { ImGui_ImplSDL2_GamepadMode_AutoFirst, ImGui_ImplSDL2_GamepadMode_AutoAll, ImGui_ImplSDL2_GamepadMode_Manual };

View file

@ -6,7 +6,7 @@
// [X] Platform: Clipboard support. // [X] Platform: Clipboard support.
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen. // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Gamepad support.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [x] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable' -> the OS animation effect when window gets created/destroyed is problematic. SDL2 backend doesn't have issue. // [x] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable' -> the OS animation effect when window gets created/destroyed is problematic. SDL2 backend doesn't have issue.
// Missing features or Issues: // Missing features or Issues:
@ -24,6 +24,13 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-05-15: [Docking] Add Platform_GetWindowFramebufferScale() handler, to allow varying Retina display density on multiple monitors.
// 2025-05-06: [Docking] macOS: fixed secondary viewports not appearing on other monitors before of parenting.
// 2025-04-09: [Docking] Revert update monitors and work areas information every frame. Only do it on Windows. (#8415, #8558)
// 2025-04-22: IME: honor ImGuiPlatformImeData->WantTextInput as an alternative way to call SDL_StartTextInput(), without IME being necessarily visible.
// 2025-04-09: Don't attempt to call SDL_CaptureMouse() on drivers where we don't call SDL_GetGlobalMouseState(). (#8561)
// 2025-03-30: Update for SDL3 api changes: Revert SDL_GetClipboardText() memory ownership change. (#8530, #7801)
// 2025-03-21: Fill gamepad inputs and set ImGuiBackendFlags_HasGamepad regardless of ImGuiConfigFlags_NavEnableGamepad being set.
// 2025-03-10: When dealing with OEM keys, use scancodes instead of translated keycodes to choose ImGuiKey values. (#7136, #7201, #7206, #7306, #7670, #7672, #8468) // 2025-03-10: When dealing with OEM keys, use scancodes instead of translated keycodes to choose ImGuiKey values. (#7136, #7201, #7206, #7306, #7670, #7672, #8468)
// 2025-02-26: Only start SDL_CaptureMouse() when mouse is being dragged, to mitigate issues with e.g.Linux debuggers not claiming capture back. (#6410, #3650) // 2025-02-26: Only start SDL_CaptureMouse() when mouse is being dragged, to mitigate issues with e.g.Linux debuggers not claiming capture back. (#6410, #3650)
// 2025-02-25: [Docking] Revert to use SDL_GetDisplayBounds() for WorkPos/WorkRect if SDL_GetDisplayUsableBounds() failed. // 2025-02-25: [Docking] Revert to use SDL_GetDisplayBounds() for WorkPos/WorkRect if SDL_GetDisplayUsableBounds() failed.
@ -67,11 +74,13 @@
// Clang warnings with -Weverything // Clang warnings with -Weverything
#if defined(__clang__) #if defined(__clang__)
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wold-style-cast" // warning: use of old-style cast
#pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision #pragma clang diagnostic ignored "-Wimplicit-int-float-conversion" // warning: implicit conversion from 'xxx' to 'float' may lose precision
#endif #endif
// SDL // SDL
#include <SDL3/SDL.h> #include <SDL3/SDL.h>
#include <stdio.h> // for snprintf()
#if defined(__APPLE__) #if defined(__APPLE__)
#include <TargetConditionals.h> #include <TargetConditionals.h>
#endif #endif
@ -104,7 +113,9 @@ struct ImGui_ImplSDL3_Data
SDL_Renderer* Renderer; SDL_Renderer* Renderer;
Uint64 Time; Uint64 Time;
char* ClipboardTextData; char* ClipboardTextData;
char BackendPlatformName[48];
bool UseVulkan; bool UseVulkan;
bool WantUpdateMonitors;
// IME handling // IME handling
SDL_Window* ImeWindow; SDL_Window* ImeWindow;
@ -116,6 +127,7 @@ struct ImGui_ImplSDL3_Data
SDL_Cursor* MouseLastCursor; SDL_Cursor* MouseLastCursor;
int MousePendingLeaveFrame; int MousePendingLeaveFrame;
bool MouseCanUseGlobalState; bool MouseCanUseGlobalState;
bool MouseCanUseCapture;
bool MouseCanReportHoveredViewport; // This is hard to use/unreliable on SDL so we'll set ImGuiBackendFlags_HasMouseHoveredViewport dynamically based on state. bool MouseCanReportHoveredViewport; // This is hard to use/unreliable on SDL so we'll set ImGuiBackendFlags_HasMouseHoveredViewport dynamically based on state.
// Gamepad handling // Gamepad handling
@ -146,8 +158,7 @@ static const char* ImGui_ImplSDL3_GetClipboardText(ImGuiContext*)
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData(); ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
if (bd->ClipboardTextData) if (bd->ClipboardTextData)
SDL_free(bd->ClipboardTextData); SDL_free(bd->ClipboardTextData);
const char* sdl_clipboard_text = SDL_GetClipboardText(); bd->ClipboardTextData = SDL_GetClipboardText();
bd->ClipboardTextData = sdl_clipboard_text ? SDL_strdup(sdl_clipboard_text) : nullptr;
return bd->ClipboardTextData; return bd->ClipboardTextData;
} }
@ -161,7 +172,7 @@ static void ImGui_ImplSDL3_PlatformSetImeData(ImGuiContext*, ImGuiViewport* view
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData(); ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
SDL_WindowID window_id = (SDL_WindowID)(intptr_t)viewport->PlatformHandle; SDL_WindowID window_id = (SDL_WindowID)(intptr_t)viewport->PlatformHandle;
SDL_Window* window = SDL_GetWindowFromID(window_id); SDL_Window* window = SDL_GetWindowFromID(window_id);
if ((data->WantVisible == false || bd->ImeWindow != window) && bd->ImeWindow != nullptr) if ((!(data->WantVisible || data->WantTextInput) || bd->ImeWindow != window) && bd->ImeWindow != nullptr)
{ {
SDL_StopTextInput(bd->ImeWindow); SDL_StopTextInput(bd->ImeWindow);
bd->ImeWindow = nullptr; bd->ImeWindow = nullptr;
@ -174,9 +185,10 @@ static void ImGui_ImplSDL3_PlatformSetImeData(ImGuiContext*, ImGuiViewport* view
r.w = 1; r.w = 1;
r.h = (int)data->InputLineHeight; r.h = (int)data->InputLineHeight;
SDL_SetTextInputArea(window, &r, 0); SDL_SetTextInputArea(window, &r, 0);
SDL_StartTextInput(window);
bd->ImeWindow = window; bd->ImeWindow = window;
} }
if (data->WantVisible || data->WantTextInput)
SDL_StartTextInput(window);
} }
// Not static to allow third-party code to use that if they want to (but undocumented) // Not static to allow third-party code to use that if they want to (but undocumented)
@ -413,14 +425,24 @@ bool ImGui_ImplSDL3_ProcessEvent(const SDL_Event* event)
case SDL_EVENT_KEY_DOWN: case SDL_EVENT_KEY_DOWN:
case SDL_EVENT_KEY_UP: case SDL_EVENT_KEY_UP:
{ {
if (ImGui_ImplSDL3_GetViewportForWindowID(event->key.windowID) == nullptr) ImGuiViewport* viewport = ImGui_ImplSDL3_GetViewportForWindowID(event->key.windowID);
if (viewport == nullptr)
return false; return false;
//IMGUI_DEBUG_LOG("SDL_EVENT_KEY_%s : key=0x%08X ('%s'), scancode=%d ('%s'), mod=%X, windowID=%d, viewport=%08X\n",
// (event->type == SDL_EVENT_KEY_DOWN) ? "DOWN" : "UP ", event->key.key, SDL_GetKeyName(event->key.key), event->key.scancode, SDL_GetScancodeName(event->key.scancode), event->key.mod, event->key.windowID, viewport ? viewport->ID : 0);
ImGui_ImplSDL3_UpdateKeyModifiers((SDL_Keymod)event->key.mod); ImGui_ImplSDL3_UpdateKeyModifiers((SDL_Keymod)event->key.mod);
//IMGUI_DEBUG_LOG("SDL_EVENT_KEY_%s : key=%d ('%s'), scancode=%d ('%s'), mod=%X\n",
// (event->type == SDL_EVENT_KEY_DOWN) ? "DOWN" : "UP ", event->key.key, SDL_GetKeyName(event->key.key), event->key.scancode, SDL_GetScancodeName(event->key.scancode), event->key.mod);
ImGuiKey key = ImGui_ImplSDL3_KeyEventToImGuiKey(event->key.key, event->key.scancode); ImGuiKey key = ImGui_ImplSDL3_KeyEventToImGuiKey(event->key.key, event->key.scancode);
io.AddKeyEvent(key, (event->type == SDL_EVENT_KEY_DOWN)); io.AddKeyEvent(key, (event->type == SDL_EVENT_KEY_DOWN));
io.SetKeyEventNativeData(key, event->key.key, event->key.scancode, event->key.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions. io.SetKeyEventNativeData(key, (int)event->key.key, (int)event->key.scancode, (int)event->key.scancode); // To support legacy indexing (<1.87 user code). Legacy backend uses SDLK_*** as indices to IsKeyXXX() functions.
return true;
}
case SDL_EVENT_DISPLAY_ORIENTATION:
case SDL_EVENT_DISPLAY_ADDED:
case SDL_EVENT_DISPLAY_REMOVED:
case SDL_EVENT_DISPLAY_MOVED:
case SDL_EVENT_DISPLAY_CONTENT_SCALE_CHANGED:
{
bd->WantUpdateMonitors = true;
return true; return true;
} }
case SDL_EVENT_WINDOW_MOUSE_ENTER: case SDL_EVENT_WINDOW_MOUSE_ENTER:
@ -445,8 +467,10 @@ bool ImGui_ImplSDL3_ProcessEvent(const SDL_Event* event)
case SDL_EVENT_WINDOW_FOCUS_GAINED: case SDL_EVENT_WINDOW_FOCUS_GAINED:
case SDL_EVENT_WINDOW_FOCUS_LOST: case SDL_EVENT_WINDOW_FOCUS_LOST:
{ {
if (ImGui_ImplSDL3_GetViewportForWindowID(event->window.windowID) == nullptr) ImGuiViewport* viewport = ImGui_ImplSDL3_GetViewportForWindowID(event->window.windowID);
if (viewport == nullptr)
return false; return false;
//IMGUI_DEBUG_LOG("%s: windowId %d, viewport: %08X\n", (event->type == SDL_EVENT_WINDOW_FOCUS_GAINED) ? "SDL_EVENT_WINDOW_FOCUS_GAINED" : "SDL_WINDOWEVENT_FOCUS_LOST", event->window.windowID, viewport ? viewport->ID : 0);
io.AddFocusEvent(event->type == SDL_EVENT_WINDOW_FOCUS_GAINED); io.AddFocusEvent(event->type == SDL_EVENT_WINDOW_FOCUS_GAINED);
return true; return true;
} }
@ -471,6 +495,8 @@ bool ImGui_ImplSDL3_ProcessEvent(const SDL_Event* event)
bd->WantUpdateGamepadsList = true; bd->WantUpdateGamepadsList = true;
return true; return true;
} }
default:
break;
} }
return false; return false;
} }
@ -481,7 +507,7 @@ static void ImGui_ImplSDL3_SetupPlatformHandles(ImGuiViewport* viewport, SDL_Win
viewport->PlatformHandleRaw = nullptr; viewport->PlatformHandleRaw = nullptr;
#if defined(_WIN32) && !defined(__WINRT__) #if defined(_WIN32) && !defined(__WINRT__)
viewport->PlatformHandleRaw = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr); viewport->PlatformHandleRaw = (HWND)SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_WIN32_HWND_POINTER, nullptr);
#elif defined(__APPLE__) && defined(SDL_VIDEO_DRIVER_COCOA) #elif defined(__APPLE__)
viewport->PlatformHandleRaw = SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr); viewport->PlatformHandleRaw = SDL_GetPointerProperty(SDL_GetWindowProperties(window), SDL_PROP_WINDOW_COCOA_WINDOW_POINTER, nullptr);
#endif #endif
} }
@ -492,26 +518,19 @@ static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer, void
IMGUI_CHECKVERSION(); IMGUI_CHECKVERSION();
IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!"); IM_ASSERT(io.BackendPlatformUserData == nullptr && "Already initialized a platform backend!");
IM_UNUSED(sdl_gl_context); // Unused in this branch IM_UNUSED(sdl_gl_context); // Unused in this branch
//SDL_SetHint(SDL_HINT_EVENT_LOGGING, "2");
// Check and store if we are on a SDL backend that supports global mouse position const int ver_linked = SDL_GetVersion();
// ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list)
bool mouse_can_use_global_state = false;
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
const char* sdl_backend = SDL_GetCurrentVideoDriver();
const char* global_mouse_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" };
for (int n = 0; n < IM_ARRAYSIZE(global_mouse_whitelist); n++)
if (strncmp(sdl_backend, global_mouse_whitelist[n], strlen(global_mouse_whitelist[n])) == 0)
mouse_can_use_global_state = true;
#endif
// Setup backend capabilities flags // Setup backend capabilities flags
ImGui_ImplSDL3_Data* bd = IM_NEW(ImGui_ImplSDL3_Data)(); ImGui_ImplSDL3_Data* bd = IM_NEW(ImGui_ImplSDL3_Data)();
snprintf(bd->BackendPlatformName, sizeof(bd->BackendPlatformName), "imgui_impl_sdl3 (%d.%d.%d; %d.%d.%d)",
SDL_MAJOR_VERSION, SDL_MINOR_VERSION, SDL_MICRO_VERSION, SDL_VERSIONNUM_MAJOR(ver_linked), SDL_VERSIONNUM_MINOR(ver_linked), SDL_VERSIONNUM_MICRO(ver_linked));
io.BackendPlatformUserData = (void*)bd; io.BackendPlatformUserData = (void*)bd;
io.BackendPlatformName = "imgui_impl_sdl3"; io.BackendPlatformName = bd->BackendPlatformName;
io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional) io.BackendFlags |= ImGuiBackendFlags_HasMouseCursors; // We can honor GetMouseCursor() values (optional)
io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used) io.BackendFlags |= ImGuiBackendFlags_HasSetMousePos; // We can honor io.WantSetMousePos requests (optional, rarely used)
if (mouse_can_use_global_state) // (ImGuiBackendFlags_PlatformHasViewports may be set just below)
io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional)
bd->Window = window; bd->Window = window;
bd->WindowID = SDL_GetWindowID(window); bd->WindowID = SDL_GetWindowID(window);
@ -519,13 +538,26 @@ static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer, void
// SDL on Linux/OSX doesn't report events for unfocused windows (see https://github.com/ocornut/imgui/issues/4960) // SDL on Linux/OSX doesn't report events for unfocused windows (see https://github.com/ocornut/imgui/issues/4960)
// We will use 'MouseCanReportHoveredViewport' to set 'ImGuiBackendFlags_HasMouseHoveredViewport' dynamically each frame. // We will use 'MouseCanReportHoveredViewport' to set 'ImGuiBackendFlags_HasMouseHoveredViewport' dynamically each frame.
bd->MouseCanUseGlobalState = mouse_can_use_global_state;
#ifndef __APPLE__ #ifndef __APPLE__
bd->MouseCanReportHoveredViewport = bd->MouseCanUseGlobalState; bd->MouseCanReportHoveredViewport = bd->MouseCanUseGlobalState;
#else #else
bd->MouseCanReportHoveredViewport = false; bd->MouseCanReportHoveredViewport = false;
#endif #endif
// Check and store if we are on a SDL backend that supports SDL_GetGlobalMouseState() and SDL_CaptureMouse()
// ("wayland" and "rpi" don't support it, but we chose to use a white-list instead of a black-list)
bd->MouseCanUseGlobalState = false;
bd->MouseCanUseCapture = false;
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
const char* sdl_backend = SDL_GetCurrentVideoDriver();
const char* capture_and_global_state_whitelist[] = { "windows", "cocoa", "x11", "DIVE", "VMAN" };
for (const char* item : capture_and_global_state_whitelist)
if (strncmp(sdl_backend, item, strlen(item)) == 0)
bd->MouseCanUseGlobalState = bd->MouseCanUseCapture = true;
#endif
if (bd->MouseCanUseGlobalState)
io.BackendFlags |= ImGuiBackendFlags_PlatformHasViewports; // We can create multi-viewports on the Platform side (optional)
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Platform_SetClipboardTextFn = ImGui_ImplSDL3_SetClipboardText; platform_io.Platform_SetClipboardTextFn = ImGui_ImplSDL3_SetClipboardText;
platform_io.Platform_GetClipboardTextFn = ImGui_ImplSDL3_GetClipboardText; platform_io.Platform_GetClipboardTextFn = ImGui_ImplSDL3_GetClipboardText;
@ -561,7 +593,7 @@ static bool ImGui_ImplSDL3_Init(SDL_Window* window, SDL_Renderer* renderer, void
// Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered. // Without this, when clicking to gain focus, our widgets wouldn't activate even though they showed as hovered.
// (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application. // (This is unfortunately a global SDL setting, so enabling it might have a side-effect on your application.
// It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click: // It is unlikely to make a difference, but if your app absolutely needs to ignore the initial on-focus click:
// you can ignore SDL_EVENT_MOUSE_BUTTON_DOWN events coming right after a SDL_WINDOWEVENT_FOCUS_GAINED) // you can ignore SDL_EVENT_MOUSE_BUTTON_DOWN events coming right after a SDL_EVENT_WINDOW_FOCUS_GAINED)
SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1"); SDL_SetHint(SDL_HINT_MOUSE_FOCUS_CLICKTHROUGH, "1");
// From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows (see #5710) // From 2.0.22: Disable auto-capture, this is preventing drag and drop across multiple windows (see #5710)
@ -652,12 +684,15 @@ static void ImGui_ImplSDL3_UpdateMouseData()
// We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused (below) // We forward mouse input when hovered or captured (via SDL_EVENT_MOUSE_MOTION) or when focused (below)
#if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE #if SDL_HAS_CAPTURE_AND_GLOBAL_MOUSE
// - SDL_CaptureMouse() let the OS know e.g. that our drags can extend outside of parent boundaries (we want updated position) and shouldn't trigger other operations outside. // - SDL_CaptureMouse() let the OS know e.g. that our drags can extend outside of parent boundaries (we want updated position) and shouldn't trigger other operations outside.
// - Debuggers under Linux tends to leave captured mouse on break, which may be very inconvenient, so to migitate the issue we wait until mouse has moved to begin capture. // - Debuggers under Linux tends to leave captured mouse on break, which may be very inconvenient, so to mitigate the issue we wait until mouse has moved to begin capture.
if (bd->MouseCanUseCapture)
{
bool want_capture = false; bool want_capture = false;
for (int button_n = 0; button_n < ImGuiMouseButton_COUNT && !want_capture; button_n++) for (int button_n = 0; button_n < ImGuiMouseButton_COUNT && !want_capture; button_n++)
if (ImGui::IsMouseDragging(button_n, 1.0f)) if (ImGui::IsMouseDragging(button_n, 1.0f))
want_capture = true; want_capture = true;
SDL_CaptureMouse(want_capture); SDL_CaptureMouse(want_capture);
}
SDL_Window* focused_window = SDL_GetKeyboardFocus(); SDL_Window* focused_window = SDL_GetKeyboardFocus();
const bool is_app_focused = (focused_window && (bd->Window == focused_window || ImGui_ImplSDL3_GetViewportForWindowID(SDL_GetWindowID(focused_window)) != NULL)); const bool is_app_focused = (focused_window && (bd->Window == focused_window || ImGui_ImplSDL3_GetViewportForWindowID(SDL_GetWindowID(focused_window)) != NULL));
@ -809,9 +844,6 @@ static void ImGui_ImplSDL3_UpdateGamepads()
SDL_free(sdl_gamepads); SDL_free(sdl_gamepads);
} }
// FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0)
return;
io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad; io.BackendFlags &= ~ImGuiBackendFlags_HasGamepad;
if (bd->Gamepads.Size == 0) if (bd->Gamepads.Size == 0)
return; return;
@ -847,8 +879,10 @@ static void ImGui_ImplSDL3_UpdateGamepads()
static void ImGui_ImplSDL3_UpdateMonitors() static void ImGui_ImplSDL3_UpdateMonitors()
{ {
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(); ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Monitors.resize(0); platform_io.Monitors.resize(0);
bd->WantUpdateMonitors = false;
int display_count; int display_count;
SDL_DisplayID* displays = SDL_GetDisplays(&display_count); SDL_DisplayID* displays = SDL_GetDisplays(&display_count);
@ -866,9 +900,7 @@ static void ImGui_ImplSDL3_UpdateMonitors()
monitor.WorkPos = ImVec2((float)r.x, (float)r.y); monitor.WorkPos = ImVec2((float)r.x, (float)r.y);
monitor.WorkSize = ImVec2((float)r.w, (float)r.h); monitor.WorkSize = ImVec2((float)r.w, (float)r.h);
} }
// FIXME-VIEWPORT: On MacOS SDL reports actual monitor DPI scale, ignoring OS configuration. We may want to set monitor.DpiScale = SDL_GetDisplayContentScale(display_id); // See https://wiki.libsdl.org/SDL3/README-highdpi for details.
// DpiScale to cocoa_window.backingScaleFactor here.
monitor.DpiScale = SDL_GetDisplayContentScale(display_id);
monitor.PlatformHandle = (void*)(intptr_t)n; monitor.PlatformHandle = (void*)(intptr_t)n;
if (monitor.DpiScale <= 0.0f) if (monitor.DpiScale <= 0.0f)
continue; // Some accessibility applications are declaring virtual monitors with a DPI of 0, see #7902. continue; // Some accessibility applications are declaring virtual monitors with a DPI of 0, see #7902.
@ -877,27 +909,37 @@ static void ImGui_ImplSDL3_UpdateMonitors()
SDL_free(displays); SDL_free(displays);
} }
static void ImGui_ImplSDL3_GetWindowSizeAndFramebufferScale(SDL_Window* window, ImVec2* out_size, ImVec2* out_framebuffer_scale)
{
int w, h;
int display_w, display_h;
SDL_GetWindowSize(window, &w, &h);
if (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED)
w = h = 0;
SDL_GetWindowSizeInPixels(window, &display_w, &display_h);
if (out_size != nullptr)
*out_size = ImVec2((float)w, (float)h);
if (out_framebuffer_scale != nullptr)
*out_framebuffer_scale = (w > 0 && h > 0) ? ImVec2((float)display_w / w, (float)display_h / h) : ImVec2(1.0f, 1.0f);
}
void ImGui_ImplSDL3_NewFrame() void ImGui_ImplSDL3_NewFrame()
{ {
ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData(); ImGui_ImplSDL3_Data* bd = ImGui_ImplSDL3_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?"); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDL3_Init()?");
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
// Setup display size (every frame to accommodate for window resizing) // Setup main viewport size (every frame to accommodate for window resizing)
int w, h; ImGui_ImplSDL3_GetWindowSizeAndFramebufferScale(bd->Window, &io.DisplaySize, &io.DisplayFramebufferScale);
int display_w, display_h;
SDL_GetWindowSize(bd->Window, &w, &h);
if (SDL_GetWindowFlags(bd->Window) & SDL_WINDOW_MINIMIZED)
w = h = 0;
SDL_GetWindowSizeInPixels(bd->Window, &display_w, &display_h);
io.DisplaySize = ImVec2((float)w, (float)h);
if (w > 0 && h > 0)
io.DisplayFramebufferScale = ImVec2((float)display_w / w, (float)display_h / h);
// Update monitors // Update monitors
#ifdef WIN32
bd->WantUpdateMonitors = true; // Keep polling under Windows to handle changes of work area when resizing task-bar (#8415)
#endif
if (bd->WantUpdateMonitors)
ImGui_ImplSDL3_UpdateMonitors(); ImGui_ImplSDL3_UpdateMonitors();
// Setup time step (we don't use SDL_GetTicks() because it is using millisecond resolution) // Setup time step (we could also use SDL_GetTicksNS() available since SDL3)
// (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644) // (Accept SDL_GetPerformanceCounter() not returning a monotonically increasing value. Happens in VMs and Emscripten, see #6189, #6114, #3644)
static Uint64 frequency = SDL_GetPerformanceFrequency(); static Uint64 frequency = SDL_GetPerformanceFrequency();
Uint64 current_time = SDL_GetPerformanceCounter(); Uint64 current_time = SDL_GetPerformanceCounter();
@ -987,7 +1029,9 @@ static void ImGui_ImplSDL3_CreateWindow(ImGuiViewport* viewport)
sdl_flags |= (viewport->Flags & ImGuiViewportFlags_NoTaskBarIcon) ? SDL_WINDOW_UTILITY : 0; sdl_flags |= (viewport->Flags & ImGuiViewportFlags_NoTaskBarIcon) ? SDL_WINDOW_UTILITY : 0;
sdl_flags |= (viewport->Flags & ImGuiViewportFlags_TopMost) ? SDL_WINDOW_ALWAYS_ON_TOP : 0; sdl_flags |= (viewport->Flags & ImGuiViewportFlags_TopMost) ? SDL_WINDOW_ALWAYS_ON_TOP : 0;
vd->Window = SDL_CreateWindow("No Title Yet", (int)viewport->Size.x, (int)viewport->Size.y, sdl_flags); vd->Window = SDL_CreateWindow("No Title Yet", (int)viewport->Size.x, (int)viewport->Size.y, sdl_flags);
#ifndef __APPLE__ // On Mac, SDL3 Parenting appears to prevent viewport from appearing in another monitor
SDL_SetWindowParent(vd->Window, vd->ParentWindow); SDL_SetWindowParent(vd->Window, vd->ParentWindow);
#endif
SDL_SetWindowPosition(vd->Window, (int)viewport->Pos.x, (int)viewport->Pos.y); SDL_SetWindowPosition(vd->Window, (int)viewport->Pos.x, (int)viewport->Pos.y);
vd->WindowOwned = true; vd->WindowOwned = true;
if (use_opengl) if (use_opengl)
@ -1019,7 +1063,7 @@ static void ImGui_ImplSDL3_DestroyWindow(ImGuiViewport* viewport)
static void ImGui_ImplSDL3_ShowWindow(ImGuiViewport* viewport) static void ImGui_ImplSDL3_ShowWindow(ImGuiViewport* viewport)
{ {
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData; ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
#if defined(_WIN32) && !(defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP || WINAPI_FAMILY == WINAPI_FAMILY_GAMES)) #if defined(_WIN32) && !(defined(WINAPI_FAMILY) && ((defined(WINAPI_FAMILY_APP) && WINAPI_FAMILY == WINAPI_FAMILY_APP) || (defined(WINAPI_FAMILY_GAMES) && WINAPI_FAMILY == WINAPI_FAMILY_GAMES)))
HWND hwnd = (HWND)viewport->PlatformHandleRaw; HWND hwnd = (HWND)viewport->PlatformHandleRaw;
// SDL hack: Show icon in task bar (#7989) // SDL hack: Show icon in task bar (#7989)
@ -1034,14 +1078,20 @@ static void ImGui_ImplSDL3_ShowWindow(ImGuiViewport* viewport)
} }
#endif #endif
#ifdef __APPLE__
SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, "1"); // Otherwise new window appear under
#else
SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, (viewport->Flags & ImGuiViewportFlags_NoFocusOnAppearing) ? "0" : "1"); SDL_SetHint(SDL_HINT_WINDOW_ACTIVATE_WHEN_SHOWN, (viewport->Flags & ImGuiViewportFlags_NoFocusOnAppearing) ? "0" : "1");
#endif
SDL_ShowWindow(vd->Window); SDL_ShowWindow(vd->Window);
} }
static void ImGui_ImplSDL3_UpdateWindow(ImGuiViewport* viewport) static void ImGui_ImplSDL3_UpdateWindow(ImGuiViewport* viewport)
{ {
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData; ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
IM_UNUSED(vd);
#ifndef __APPLE__ // On Mac, SDL3 Parenting appears to prevent viewport from appearing in another monitor
// Update SDL3 parent if it changed _after_ creation. // Update SDL3 parent if it changed _after_ creation.
// This is for advanced apps that are manipulating ParentViewportID manually. // This is for advanced apps that are manipulating ParentViewportID manually.
SDL_Window* new_parent = ImGui_ImplSDL3_GetSDLWindowFromViewportID(viewport->ParentViewportId); SDL_Window* new_parent = ImGui_ImplSDL3_GetSDLWindowFromViewportID(viewport->ParentViewportId);
@ -1050,6 +1100,7 @@ static void ImGui_ImplSDL3_UpdateWindow(ImGuiViewport* viewport)
vd->ParentWindow = new_parent; vd->ParentWindow = new_parent;
SDL_SetWindowParent(vd->Window, vd->ParentWindow); SDL_SetWindowParent(vd->Window, vd->ParentWindow);
} }
#endif
} }
static ImVec2 ImGui_ImplSDL3_GetWindowPos(ImGuiViewport* viewport) static ImVec2 ImGui_ImplSDL3_GetWindowPos(ImGuiViewport* viewport)
@ -1080,6 +1131,14 @@ static void ImGui_ImplSDL3_SetWindowSize(ImGuiViewport* viewport, ImVec2 size)
SDL_SetWindowSize(vd->Window, (int)size.x, (int)size.y); SDL_SetWindowSize(vd->Window, (int)size.x, (int)size.y);
} }
static ImVec2 ImGui_ImplSDL3_GetWindowFramebufferScale(ImGuiViewport* viewport)
{
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
ImVec2 framebuffer_scale;
ImGui_ImplSDL3_GetWindowSizeAndFramebufferScale(vd->Window, nullptr, &framebuffer_scale);
return framebuffer_scale;
}
static void ImGui_ImplSDL3_SetWindowTitle(ImGuiViewport* viewport, const char* title) static void ImGui_ImplSDL3_SetWindowTitle(ImGuiViewport* viewport, const char* title)
{ {
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData; ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
@ -1134,7 +1193,7 @@ static int ImGui_ImplSDL3_CreateVkSurface(ImGuiViewport* viewport, ImU64 vk_inst
{ {
ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData; ImGui_ImplSDL3_ViewportData* vd = (ImGui_ImplSDL3_ViewportData*)viewport->PlatformUserData;
(void)vk_allocator; (void)vk_allocator;
bool ret = SDL_Vulkan_CreateSurface(vd->Window, (VkInstance)vk_instance, (VkAllocationCallbacks*)vk_allocator, (VkSurfaceKHR*)out_vk_surface); bool ret = SDL_Vulkan_CreateSurface(vd->Window, (VkInstance)vk_instance, (const VkAllocationCallbacks*)vk_allocator, (VkSurfaceKHR*)out_vk_surface);
return ret ? 0 : 1; // ret ? VK_SUCCESS : VK_NOT_READY return ret ? 0 : 1; // ret ? VK_SUCCESS : VK_NOT_READY
} }
@ -1150,6 +1209,7 @@ static void ImGui_ImplSDL3_InitMultiViewportSupport(SDL_Window* window, void* sd
platform_io.Platform_GetWindowPos = ImGui_ImplSDL3_GetWindowPos; platform_io.Platform_GetWindowPos = ImGui_ImplSDL3_GetWindowPos;
platform_io.Platform_SetWindowSize = ImGui_ImplSDL3_SetWindowSize; platform_io.Platform_SetWindowSize = ImGui_ImplSDL3_SetWindowSize;
platform_io.Platform_GetWindowSize = ImGui_ImplSDL3_GetWindowSize; platform_io.Platform_GetWindowSize = ImGui_ImplSDL3_GetWindowSize;
platform_io.Platform_GetWindowFramebufferScale = ImGui_ImplSDL3_GetWindowFramebufferScale;
platform_io.Platform_SetWindowFocus = ImGui_ImplSDL3_SetWindowFocus; platform_io.Platform_SetWindowFocus = ImGui_ImplSDL3_SetWindowFocus;
platform_io.Platform_GetWindowFocus = ImGui_ImplSDL3_GetWindowFocus; platform_io.Platform_GetWindowFocus = ImGui_ImplSDL3_GetWindowFocus;
platform_io.Platform_GetWindowMinimized = ImGui_ImplSDL3_GetWindowMinimized; platform_io.Platform_GetWindowMinimized = ImGui_ImplSDL3_GetWindowMinimized;

View file

@ -6,7 +6,7 @@
// [X] Platform: Clipboard support. // [X] Platform: Clipboard support.
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen. // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy SDL_SCANCODE_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Gamepad support.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [x] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable' -> the OS animation effect when window gets created/destroyed is problematic. SDL2 backend doesn't have issue. // [x] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable' -> the OS animation effect when window gets created/destroyed is problematic. SDL2 backend doesn't have issue.
// Missing features or Issues: // Missing features or Issues:

View file

@ -3,9 +3,9 @@
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use simply cast a reference to your SDL_GPUTextureSamplerBinding to ImTextureID. // [X] Renderer: User texture binding. Use simply cast a reference to your SDL_GPUTextureSamplerBinding to ImTextureID.
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// Missing features: // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [ ] Renderer: Multi-viewport support (multiple windows). // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// The aim of imgui_impl_sdlgpu3.h/.cpp is to be usable in your engine without any modification. // The aim of imgui_impl_sdlgpu3.h/.cpp is to be usable in your engine without any modification.
// IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/
@ -19,10 +19,16 @@
// - Introduction, links and more at the top of imgui.cpp // - Introduction, links and more at the top of imgui.cpp
// Important note to the reader who wish to integrate imgui_impl_sdlgpu3.cpp/.h in their own engine/app. // Important note to the reader who wish to integrate imgui_impl_sdlgpu3.cpp/.h in their own engine/app.
// - Unlike other backends, the user must call the function Imgui_ImplSDLGPU3_PrepareDrawData() BEFORE issuing a SDL_GPURenderPass containing ImGui_ImplSDLGPU3_RenderDrawData. // - Unlike other backends, the user must call the function ImGui_ImplSDLGPU3_PrepareDrawData() BEFORE issuing a SDL_GPURenderPass containing ImGui_ImplSDLGPU3_RenderDrawData.
// Calling the function is MANDATORY, otherwise the ImGui will not upload neither the vertex nor the index buffer for the GPU. See imgui_impl_sdlgpu3.cpp for more info. // Calling the function is MANDATORY, otherwise the ImGui will not upload neither the vertex nor the index buffer for the GPU. See imgui_impl_sdlgpu3.cpp for more info.
// CHANGELOG // CHANGELOG
// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-06-25: Mapping transfer buffer for texture update use cycle=true. Fixes artifacts e.g. on Metal backend.
// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLGPU3_CreateFontsTexture() and ImGui_ImplSDLGPU3_DestroyFontsTexture().
// 2025-04-28: Added support for special ImDrawCallback_ResetRenderState callback to reset render state.
// 2025-03-30: Made ImGui_ImplSDLGPU3_PrepareDrawData() reuse GPU Transfer Buffers which were unusually slow to recreate every frame. Much faster now.
// 2025-03-21: Fixed typo in function name Imgui_ImplSDLGPU3_PrepareDrawData() -> ImGui_ImplSDLGPU3_PrepareDrawData().
// 2025-01-16: Renamed ImGui_ImplSDLGPU3_InitInfo::GpuDevice to Device. // 2025-01-16: Renamed ImGui_ImplSDLGPU3_InitInfo::GpuDevice to Device.
// 2025-01-09: SDL_GPU: Added the SDL_GPU3 backend. // 2025-01-09: SDL_GPU: Added the SDL_GPU3 backend.
@ -32,13 +38,20 @@
#include "imgui_impl_sdlgpu3_shaders.h" #include "imgui_impl_sdlgpu3_shaders.h"
// SDL_GPU Data // SDL_GPU Data
struct ImGui_ImplSDLGPU3_Texture
{
SDL_GPUTexture* Texture = nullptr;
SDL_GPUTextureSamplerBinding TextureSamplerBinding = { nullptr, nullptr };
};
// Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplSDLGPU3_RenderDrawData() // Reusable buffers used for rendering 1 current in-flight frame, for ImGui_ImplSDLGPU3_RenderDrawData()
struct ImGui_ImplSDLGPU3_FrameData struct ImGui_ImplSDLGPU3_FrameData
{ {
SDL_GPUBuffer* VertexBuffer = nullptr; SDL_GPUBuffer* VertexBuffer = nullptr;
SDL_GPUBuffer* IndexBuffer = nullptr; SDL_GPUTransferBuffer* VertexTransferBuffer = nullptr;
uint32_t VertexBufferSize = 0; uint32_t VertexBufferSize = 0;
SDL_GPUBuffer* IndexBuffer = nullptr;
SDL_GPUTransferBuffer* IndexTransferBuffer = nullptr;
uint32_t IndexBufferSize = 0; uint32_t IndexBufferSize = 0;
}; };
@ -50,11 +63,9 @@ struct ImGui_ImplSDLGPU3_Data
SDL_GPUShader* VertexShader = nullptr; SDL_GPUShader* VertexShader = nullptr;
SDL_GPUShader* FragmentShader = nullptr; SDL_GPUShader* FragmentShader = nullptr;
SDL_GPUGraphicsPipeline* Pipeline = nullptr; SDL_GPUGraphicsPipeline* Pipeline = nullptr;
SDL_GPUSampler* TexSampler = nullptr;
// Font data SDL_GPUTransferBuffer* TexTransferBuffer = nullptr;
SDL_GPUSampler* FontSampler = nullptr; uint32_t TexTransferBufferSize = 0;
SDL_GPUTexture* FontTexture = nullptr;
SDL_GPUTextureSamplerBinding FontBinding = { nullptr, nullptr };
// Frame data for main window // Frame data for main window
ImGui_ImplSDLGPU3_FrameData MainWindowFrameData; ImGui_ImplSDLGPU3_FrameData MainWindowFrameData;
@ -115,14 +126,15 @@ static void ImGui_ImplSDLGPU3_SetupRenderState(ImDrawData* draw_data, SDL_GPUGra
SDL_PushGPUVertexUniformData(command_buffer, 0, &ubo, sizeof(UBO)); SDL_PushGPUVertexUniformData(command_buffer, 0, &ubo, sizeof(UBO));
} }
static void CreateOrResizeBuffer(SDL_GPUBuffer** buffer, uint32_t* old_size, uint32_t new_size, SDL_GPUBufferUsageFlags usage) static void CreateOrResizeBuffers(SDL_GPUBuffer** buffer, SDL_GPUTransferBuffer** transferbuffer, uint32_t* old_size, uint32_t new_size, SDL_GPUBufferUsageFlags usage)
{ {
ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo;
// Even though this is fairly rarely called. // FIXME-OPT: Not optimal, but this is fairly rarely called.
SDL_WaitForGPUIdle(v->Device); SDL_WaitForGPUIdle(v->Device);
SDL_ReleaseGPUBuffer(v->Device, *buffer); SDL_ReleaseGPUBuffer(v->Device, *buffer);
SDL_ReleaseGPUTransferBuffer(v->Device, *transferbuffer);
SDL_GPUBufferCreateInfo buffer_info = {}; SDL_GPUBufferCreateInfo buffer_info = {};
buffer_info.usage = usage; buffer_info.usage = usage;
@ -131,12 +143,18 @@ static void CreateOrResizeBuffer(SDL_GPUBuffer** buffer, uint32_t* old_size, uin
*buffer = SDL_CreateGPUBuffer(v->Device, &buffer_info); *buffer = SDL_CreateGPUBuffer(v->Device, &buffer_info);
*old_size = new_size; *old_size = new_size;
IM_ASSERT(*buffer != nullptr && "Failed to create GPU Buffer, call SDL_GetError() for more information"); IM_ASSERT(*buffer != nullptr && "Failed to create GPU Buffer, call SDL_GetError() for more information");
SDL_GPUTransferBufferCreateInfo transferbuffer_info = {};
transferbuffer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
transferbuffer_info.size = new_size;
*transferbuffer = SDL_CreateGPUTransferBuffer(v->Device, &transferbuffer_info);
IM_ASSERT(*transferbuffer != nullptr && "Failed to create GPU Transfer Buffer, call SDL_GetError() for more information");
} }
// SDL_GPU doesn't allow copy passes to occur while a render or compute pass is bound! // SDL_GPU doesn't allow copy passes to occur while a render or compute pass is bound!
// The only way to allow a user to supply their own RenderPass (to render to a texture instead of the window for example), // The only way to allow a user to supply their own RenderPass (to render to a texture instead of the window for example),
// is to split the upload part of ImGui_ImplSDLGPU3_RenderDrawData() to another function that needs to be called by the user before rendering. // is to split the upload part of ImGui_ImplSDLGPU3_RenderDrawData() to another function that needs to be called by the user before rendering.
void Imgui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer) void ImGui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer)
{ {
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x); int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
@ -144,6 +162,13 @@ void Imgui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuff
if (fb_width <= 0 || fb_height <= 0 || draw_data->TotalVtxCount <= 0) if (fb_width <= 0 || fb_height <= 0 || draw_data->TotalVtxCount <= 0)
return; return;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplSDLGPU3_UpdateTexture(tex);
ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo;
ImGui_ImplSDLGPU3_FrameData* fd = &bd->MainWindowFrameData; ImGui_ImplSDLGPU3_FrameData* fd = &bd->MainWindowFrameData;
@ -151,25 +176,12 @@ void Imgui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuff
uint32_t vertex_size = draw_data->TotalVtxCount * sizeof(ImDrawVert); uint32_t vertex_size = draw_data->TotalVtxCount * sizeof(ImDrawVert);
uint32_t index_size = draw_data->TotalIdxCount * sizeof(ImDrawIdx); uint32_t index_size = draw_data->TotalIdxCount * sizeof(ImDrawIdx);
if (fd->VertexBuffer == nullptr || fd->VertexBufferSize < vertex_size) if (fd->VertexBuffer == nullptr || fd->VertexBufferSize < vertex_size)
CreateOrResizeBuffer(&fd->VertexBuffer, &fd->VertexBufferSize, vertex_size, SDL_GPU_BUFFERUSAGE_VERTEX); CreateOrResizeBuffers(&fd->VertexBuffer, &fd->VertexTransferBuffer, &fd->VertexBufferSize, vertex_size, SDL_GPU_BUFFERUSAGE_VERTEX);
if (fd->IndexBuffer == nullptr || fd->IndexBufferSize < index_size) if (fd->IndexBuffer == nullptr || fd->IndexBufferSize < index_size)
CreateOrResizeBuffer(&fd->IndexBuffer, &fd->IndexBufferSize, index_size, SDL_GPU_BUFFERUSAGE_INDEX); CreateOrResizeBuffers(&fd->IndexBuffer, &fd->IndexTransferBuffer, &fd->IndexBufferSize, index_size, SDL_GPU_BUFFERUSAGE_INDEX);
// FIXME: It feels like more code could be shared there. ImDrawVert* vtx_dst = (ImDrawVert*)SDL_MapGPUTransferBuffer(v->Device, fd->VertexTransferBuffer, true);
SDL_GPUTransferBufferCreateInfo vertex_transferbuffer_info = {}; ImDrawIdx* idx_dst = (ImDrawIdx*)SDL_MapGPUTransferBuffer(v->Device, fd->IndexTransferBuffer, true);
vertex_transferbuffer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
vertex_transferbuffer_info.size = vertex_size;
SDL_GPUTransferBufferCreateInfo index_transferbuffer_info = {};
index_transferbuffer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
index_transferbuffer_info.size = index_size;
SDL_GPUTransferBuffer* vertex_transferbuffer = SDL_CreateGPUTransferBuffer(v->Device, &vertex_transferbuffer_info);
IM_ASSERT(vertex_transferbuffer != nullptr && "Failed to create the vertex transfer buffer, call SDL_GetError() for more information");
SDL_GPUTransferBuffer* index_transferbuffer = SDL_CreateGPUTransferBuffer(v->Device, &index_transferbuffer_info);
IM_ASSERT(index_transferbuffer != nullptr && "Failed to create the index transfer buffer, call SDL_GetError() for more information");
ImDrawVert* vtx_dst = (ImDrawVert*)SDL_MapGPUTransferBuffer(v->Device, vertex_transferbuffer, true);
ImDrawIdx* idx_dst = (ImDrawIdx*)SDL_MapGPUTransferBuffer(v->Device, index_transferbuffer, true);
for (int n = 0; n < draw_data->CmdListsCount; n++) for (int n = 0; n < draw_data->CmdListsCount; n++)
{ {
const ImDrawList* draw_list = draw_data->CmdLists[n]; const ImDrawList* draw_list = draw_data->CmdLists[n];
@ -178,15 +190,15 @@ void Imgui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuff
vtx_dst += draw_list->VtxBuffer.Size; vtx_dst += draw_list->VtxBuffer.Size;
idx_dst += draw_list->IdxBuffer.Size; idx_dst += draw_list->IdxBuffer.Size;
} }
SDL_UnmapGPUTransferBuffer(v->Device, vertex_transferbuffer); SDL_UnmapGPUTransferBuffer(v->Device, fd->VertexTransferBuffer);
SDL_UnmapGPUTransferBuffer(v->Device, index_transferbuffer); SDL_UnmapGPUTransferBuffer(v->Device, fd->IndexTransferBuffer);
SDL_GPUTransferBufferLocation vertex_buffer_location = {}; SDL_GPUTransferBufferLocation vertex_buffer_location = {};
vertex_buffer_location.offset = 0; vertex_buffer_location.offset = 0;
vertex_buffer_location.transfer_buffer = vertex_transferbuffer; vertex_buffer_location.transfer_buffer = fd->VertexTransferBuffer;
SDL_GPUTransferBufferLocation index_buffer_location = {}; SDL_GPUTransferBufferLocation index_buffer_location = {};
index_buffer_location.offset = 0; index_buffer_location.offset = 0;
index_buffer_location.transfer_buffer = index_transferbuffer; index_buffer_location.transfer_buffer = fd->IndexTransferBuffer;
SDL_GPUBufferRegion vertex_buffer_region = {}; SDL_GPUBufferRegion vertex_buffer_region = {};
vertex_buffer_region.buffer = fd->VertexBuffer; vertex_buffer_region.buffer = fd->VertexBuffer;
@ -202,8 +214,6 @@ void Imgui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuff
SDL_UploadToGPUBuffer(copy_pass, &vertex_buffer_location, &vertex_buffer_region, true); SDL_UploadToGPUBuffer(copy_pass, &vertex_buffer_location, &vertex_buffer_region, true);
SDL_UploadToGPUBuffer(copy_pass, &index_buffer_location, &index_buffer_region, true); SDL_UploadToGPUBuffer(copy_pass, &index_buffer_location, &index_buffer_region, true);
SDL_EndGPUCopyPass(copy_pass); SDL_EndGPUCopyPass(copy_pass);
SDL_ReleaseGPUTransferBuffer(v->Device, index_transferbuffer);
SDL_ReleaseGPUTransferBuffer(v->Device, vertex_transferbuffer);
} }
void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer, SDL_GPURenderPass* render_pass, SDL_GPUGraphicsPipeline* pipeline) void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer, SDL_GPURenderPass* render_pass, SDL_GPUGraphicsPipeline* pipeline)
@ -238,6 +248,11 @@ void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffe
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i]; const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
if (pcmd->UserCallback != nullptr) if (pcmd->UserCallback != nullptr)
{ {
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplSDLGPU3_SetupRenderState(draw_data, pipeline, command_buffer, render_pass, fd, fb_width, fb_height);
else
pcmd->UserCallback(draw_list, pcmd); pcmd->UserCallback(draw_list, pcmd);
} }
else else
@ -281,94 +296,120 @@ void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffe
SDL_SetGPUScissor(render_pass, &scissor_rect); SDL_SetGPUScissor(render_pass, &scissor_rect);
} }
void ImGui_ImplSDLGPU3_CreateFontsTexture() static void ImGui_ImplSDLGPU3_DestroyTexture(ImTextureData* tex)
{
ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
ImGui_ImplSDLGPU3_Texture* backend_tex = (ImGui_ImplSDLGPU3_Texture*)tex->BackendUserData;
if (backend_tex == nullptr)
return;
SDL_GPUTextureSamplerBinding* binding = (SDL_GPUTextureSamplerBinding*)(intptr_t)tex->BackendUserData;
IM_ASSERT(backend_tex->Texture == binding->texture);
SDL_ReleaseGPUTexture(bd->InitInfo.Device, backend_tex->Texture);
IM_DELETE(backend_tex);
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
tex->BackendUserData = nullptr;
}
void ImGui_ImplSDLGPU3_UpdateTexture(ImTextureData* tex)
{ {
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo;
// Destroy existing texture (if any) if (tex->Status == ImTextureStatus_WantCreate)
if (bd->FontTexture)
{ {
SDL_WaitForGPUIdle(v->Device); // Create and upload new texture to graphics system
ImGui_ImplSDLGPU3_DestroyFontsTexture(); //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
} IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
ImGui_ImplSDLGPU3_Texture* backend_tex = IM_NEW(ImGui_ImplSDLGPU3_Texture)();
unsigned char* pixels; // Create texture
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
uint32_t upload_size = width * height * 4 * sizeof(char);
// Create the Image:
{
SDL_GPUTextureCreateInfo texture_info = {}; SDL_GPUTextureCreateInfo texture_info = {};
texture_info.type = SDL_GPU_TEXTURETYPE_2D; texture_info.type = SDL_GPU_TEXTURETYPE_2D;
texture_info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM; texture_info.format = SDL_GPU_TEXTUREFORMAT_R8G8B8A8_UNORM;
texture_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER; texture_info.usage = SDL_GPU_TEXTUREUSAGE_SAMPLER;
texture_info.width = width; texture_info.width = tex->Width;
texture_info.height = height; texture_info.height = tex->Height;
texture_info.layer_count_or_depth = 1; texture_info.layer_count_or_depth = 1;
texture_info.num_levels = 1; texture_info.num_levels = 1;
texture_info.sample_count = SDL_GPU_SAMPLECOUNT_1; texture_info.sample_count = SDL_GPU_SAMPLECOUNT_1;
bd->FontTexture = SDL_CreateGPUTexture(v->Device, &texture_info); backend_tex->Texture = SDL_CreateGPUTexture(v->Device, &texture_info);
IM_ASSERT(bd->FontTexture && "Failed to create font texture, call SDL_GetError() for more info"); backend_tex->TextureSamplerBinding.texture = backend_tex->Texture;
backend_tex->TextureSamplerBinding.sampler = bd->TexSampler;
IM_ASSERT(backend_tex->Texture && "Failed to create font texture, call SDL_GetError() for more info");
// Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)&backend_tex->TextureSamplerBinding);
tex->BackendUserData = backend_tex;
} }
// Assign the texture to the TextureSamplerBinding if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates)
bd->FontBinding.texture = bd->FontTexture;
// Create all the upload structures and upload:
{ {
ImGui_ImplSDLGPU3_Texture* backend_tex = (ImGui_ImplSDLGPU3_Texture*)tex->BackendUserData;
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
// Update full texture or selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->UpdateRect but you can use tex->Updates[] to upload individual regions.
// We could use the smaller rect on _WantCreate but using the full rect allows us to clear the texture.
const int upload_x = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.x;
const int upload_y = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.y;
const int upload_w = (tex->Status == ImTextureStatus_WantCreate) ? tex->Width : tex->UpdateRect.w;
const int upload_h = (tex->Status == ImTextureStatus_WantCreate) ? tex->Height : tex->UpdateRect.h;
uint32_t upload_pitch = upload_w * tex->BytesPerPixel;
uint32_t upload_size = upload_w * upload_h * tex->BytesPerPixel;
// Create transfer buffer
if (bd->TexTransferBufferSize < upload_size)
{
SDL_ReleaseGPUTransferBuffer(v->Device, bd->TexTransferBuffer);
SDL_GPUTransferBufferCreateInfo transferbuffer_info = {}; SDL_GPUTransferBufferCreateInfo transferbuffer_info = {};
transferbuffer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD; transferbuffer_info.usage = SDL_GPU_TRANSFERBUFFERUSAGE_UPLOAD;
transferbuffer_info.size = upload_size; transferbuffer_info.size = upload_size + 1024;
bd->TexTransferBufferSize = upload_size + 1024;
bd->TexTransferBuffer = SDL_CreateGPUTransferBuffer(v->Device, &transferbuffer_info);
IM_ASSERT(bd->TexTransferBuffer != nullptr && "Failed to create font transfer buffer, call SDL_GetError() for more information");
}
SDL_GPUTransferBuffer* transferbuffer = SDL_CreateGPUTransferBuffer(v->Device, &transferbuffer_info); // Copy to transfer buffer
IM_ASSERT(transferbuffer != nullptr && "Failed to create font transfer buffer, call SDL_GetError() for more information"); {
void* texture_ptr = SDL_MapGPUTransferBuffer(v->Device, bd->TexTransferBuffer, true);
void* texture_ptr = SDL_MapGPUTransferBuffer(v->Device, transferbuffer, false); for (int y = 0; y < upload_h; y++)
memcpy(texture_ptr, pixels, upload_size); memcpy((void*)((uintptr_t)texture_ptr + y * upload_pitch), tex->GetPixelsAt(upload_x, upload_y + y), upload_pitch);
SDL_UnmapGPUTransferBuffer(v->Device, transferbuffer); SDL_UnmapGPUTransferBuffer(v->Device, bd->TexTransferBuffer);
}
SDL_GPUTextureTransferInfo transfer_info = {}; SDL_GPUTextureTransferInfo transfer_info = {};
transfer_info.offset = 0; transfer_info.offset = 0;
transfer_info.transfer_buffer = transferbuffer; transfer_info.transfer_buffer = bd->TexTransferBuffer;
SDL_GPUTextureRegion texture_region = {}; SDL_GPUTextureRegion texture_region = {};
texture_region.texture = bd->FontTexture; texture_region.texture = backend_tex->Texture;
texture_region.w = width; texture_region.x = (Uint32)upload_x;
texture_region.h = height; texture_region.y = (Uint32)upload_y;
texture_region.w = (Uint32)upload_w;
texture_region.h = (Uint32)upload_h;
texture_region.d = 1; texture_region.d = 1;
// Upload
{
SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(v->Device); SDL_GPUCommandBuffer* cmd = SDL_AcquireGPUCommandBuffer(v->Device);
SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd); SDL_GPUCopyPass* copy_pass = SDL_BeginGPUCopyPass(cmd);
SDL_UploadToGPUTexture(copy_pass, &transfer_info, &texture_region, false); SDL_UploadToGPUTexture(copy_pass, &transfer_info, &texture_region, false);
SDL_EndGPUCopyPass(copy_pass); SDL_EndGPUCopyPass(copy_pass);
SDL_SubmitGPUCommandBuffer(cmd); SDL_SubmitGPUCommandBuffer(cmd);
SDL_ReleaseGPUTransferBuffer(v->Device, transferbuffer);
} }
// Store our identifier tex->SetStatus(ImTextureStatus_OK);
io.Fonts->SetTexID((ImTextureID)&bd->FontBinding); }
if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
ImGui_ImplSDLGPU3_DestroyTexture(tex);
} }
// You probably never need to call this, as it is called by ImGui_ImplSDLGPU3_CreateFontsTexture() and ImGui_ImplSDLGPU3_Shutdown(). static void ImGui_ImplSDLGPU3_CreateShaders()
void ImGui_ImplSDLGPU3_DestroyFontsTexture()
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo;
if (bd->FontTexture)
{
SDL_ReleaseGPUTexture(v->Device, bd->FontTexture);
bd->FontBinding.texture = nullptr;
bd->FontTexture = nullptr;
}
io.Fonts->SetTexID(0);
}
static void Imgui_ImplSDLGPU3_CreateShaders()
{ {
// Create the shader modules // Create the shader modules
ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
@ -433,7 +474,7 @@ static void ImGui_ImplSDLGPU3_CreateGraphicsPipeline()
{ {
ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo;
Imgui_ImplSDLGPU3_CreateShaders(); ImGui_ImplSDLGPU3_CreateShaders();
SDL_GPUVertexBufferDescription vertex_buffer_desc[1]; SDL_GPUVertexBufferDescription vertex_buffer_desc[1];
vertex_buffer_desc[0].slot = 0; vertex_buffer_desc[0].slot = 0;
@ -517,7 +558,9 @@ void ImGui_ImplSDLGPU3_CreateDeviceObjects()
ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo;
if (!bd->FontSampler) ImGui_ImplSDLGPU3_DestroyDeviceObjects();
if (bd->TexSampler == nullptr)
{ {
// Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling. // Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling.
SDL_GPUSamplerCreateInfo sampler_info = {}; SDL_GPUSamplerCreateInfo sampler_info = {};
@ -534,13 +577,11 @@ void ImGui_ImplSDLGPU3_CreateDeviceObjects()
sampler_info.max_anisotropy = 1.0f; sampler_info.max_anisotropy = 1.0f;
sampler_info.enable_compare = false; sampler_info.enable_compare = false;
bd->FontSampler = SDL_CreateGPUSampler(v->Device, &sampler_info); bd->TexSampler = SDL_CreateGPUSampler(v->Device, &sampler_info);
bd->FontBinding.sampler = bd->FontSampler; IM_ASSERT(bd->TexSampler != nullptr && "Failed to create font sampler, call SDL_GetError() for more information");
IM_ASSERT(bd->FontSampler != nullptr && "Failed to create font sampler, call SDL_GetError() for more information");
} }
ImGui_ImplSDLGPU3_CreateGraphicsPipeline(); ImGui_ImplSDLGPU3_CreateGraphicsPipeline();
ImGui_ImplSDLGPU3_CreateFontsTexture();
} }
void ImGui_ImplSDLGPU3_DestroyFrameData() void ImGui_ImplSDLGPU3_DestroyFrameData()
@ -548,12 +589,14 @@ void ImGui_ImplSDLGPU3_DestroyFrameData()
ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo;
SDL_ReleaseGPUBuffer(v->Device, bd->MainWindowFrameData.VertexBuffer); ImGui_ImplSDLGPU3_FrameData* fd = &bd->MainWindowFrameData;
SDL_ReleaseGPUBuffer(v->Device, bd->MainWindowFrameData.IndexBuffer); SDL_ReleaseGPUBuffer(v->Device, fd->VertexBuffer);
bd->MainWindowFrameData.VertexBuffer = nullptr; SDL_ReleaseGPUBuffer(v->Device, fd->IndexBuffer);
bd->MainWindowFrameData.IndexBuffer = nullptr; SDL_ReleaseGPUTransferBuffer(v->Device, fd->VertexTransferBuffer);
bd->MainWindowFrameData.VertexBufferSize = 0; SDL_ReleaseGPUTransferBuffer(v->Device, fd->IndexTransferBuffer);
bd->MainWindowFrameData.IndexBufferSize = 0; fd->VertexBuffer = fd->IndexBuffer = nullptr;
fd->VertexTransferBuffer = fd->IndexTransferBuffer = nullptr;
fd->VertexBufferSize = fd->IndexBufferSize = 0;
} }
void ImGui_ImplSDLGPU3_DestroyDeviceObjects() void ImGui_ImplSDLGPU3_DestroyDeviceObjects()
@ -562,14 +605,21 @@ void ImGui_ImplSDLGPU3_DestroyDeviceObjects()
ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo; ImGui_ImplSDLGPU3_InitInfo* v = &bd->InitInfo;
ImGui_ImplSDLGPU3_DestroyFrameData(); ImGui_ImplSDLGPU3_DestroyFrameData();
ImGui_ImplSDLGPU3_DestroyFontsTexture();
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
ImGui_ImplSDLGPU3_DestroyTexture(tex);
if (bd->TexTransferBuffer) { SDL_ReleaseGPUTransferBuffer(v->Device, bd->TexTransferBuffer); bd->TexTransferBuffer = nullptr; }
if (bd->VertexShader) { SDL_ReleaseGPUShader(v->Device, bd->VertexShader); bd->VertexShader = nullptr; } if (bd->VertexShader) { SDL_ReleaseGPUShader(v->Device, bd->VertexShader); bd->VertexShader = nullptr; }
if (bd->FragmentShader) { SDL_ReleaseGPUShader(v->Device, bd->FragmentShader); bd->FragmentShader = nullptr; } if (bd->FragmentShader) { SDL_ReleaseGPUShader(v->Device, bd->FragmentShader); bd->FragmentShader = nullptr; }
if (bd->FontSampler) { SDL_ReleaseGPUSampler(v->Device, bd->FontSampler); bd->FontSampler = nullptr;} if (bd->TexSampler) { SDL_ReleaseGPUSampler(v->Device, bd->TexSampler); bd->TexSampler = nullptr; }
if (bd->Pipeline) { SDL_ReleaseGPUGraphicsPipeline(v->Device, bd->Pipeline); bd->Pipeline = nullptr; } if (bd->Pipeline) { SDL_ReleaseGPUGraphicsPipeline(v->Device, bd->Pipeline); bd->Pipeline = nullptr; }
} }
static void ImGui_ImplSDLGPU3_InitMultiViewportSupport();
static void ImGui_ImplSDLGPU3_ShutdownMultiViewportSupport();
bool ImGui_ImplSDLGPU3_Init(ImGui_ImplSDLGPU3_InitInfo* info) bool ImGui_ImplSDLGPU3_Init(ImGui_ImplSDLGPU3_InitInfo* info)
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
@ -581,13 +631,15 @@ bool ImGui_ImplSDLGPU3_Init(ImGui_ImplSDLGPU3_InitInfo* info)
io.BackendRendererUserData = (void*)bd; io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_sdlgpu3"; io.BackendRendererName = "imgui_impl_sdlgpu3";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
IM_ASSERT(info->Device != nullptr); IM_ASSERT(info->Device != nullptr);
IM_ASSERT(info->ColorTargetFormat != SDL_GPU_TEXTUREFORMAT_INVALID); IM_ASSERT(info->ColorTargetFormat != SDL_GPU_TEXTUREFORMAT_INVALID);
bd->InitInfo = *info; bd->InitInfo = *info;
ImGui_ImplSDLGPU3_CreateDeviceObjects(); ImGui_ImplSDLGPU3_InitMultiViewportSupport();
return true; return true;
} }
@ -598,10 +650,11 @@ void ImGui_ImplSDLGPU3_Shutdown()
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?"); IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
ImGui_ImplSDLGPU3_ShutdownMultiViewportSupport();
ImGui_ImplSDLGPU3_DestroyDeviceObjects(); ImGui_ImplSDLGPU3_DestroyDeviceObjects();
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
IM_DELETE(bd); IM_DELETE(bd);
} }
@ -610,8 +663,79 @@ void ImGui_ImplSDLGPU3_NewFrame()
ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData(); ImGui_ImplSDLGPU3_Data* bd = ImGui_ImplSDLGPU3_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLGPU3_Init()?"); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLGPU3_Init()?");
if (!bd->FontTexture) if (!bd->TexSampler)
ImGui_ImplSDLGPU3_CreateFontsTexture(); ImGui_ImplSDLGPU3_CreateDeviceObjects();
} }
//--------------------------------------------------------------------------------------------------------
// MULTI-VIEWPORT / PLATFORM INTERFACE SUPPORT
// This is an _advanced_ and _optional_ feature, allowing the backend to create and handle multiple viewports simultaneously.
// If you are new to dear imgui or creating a new binding for dear imgui, it is recommended that you completely ignore this section first..
//--------------------------------------------------------------------------------------------------------
static void ImGui_ImplSDLGPU3_CreateWindow(ImGuiViewport* viewport)
{
ImGui_ImplSDLGPU3_Data* data = ImGui_ImplSDLGPU3_GetBackendData();
SDL_Window* window = SDL_GetWindowFromID((SDL_WindowID)(intptr_t)viewport->PlatformHandle);
SDL_ClaimWindowForGPUDevice(data->InitInfo.Device, window);
viewport->RendererUserData = (void*)1;
}
static void ImGui_ImplSDLGPU3_RenderWindow(ImGuiViewport* viewport, void*)
{
ImGui_ImplSDLGPU3_Data* data = ImGui_ImplSDLGPU3_GetBackendData();
SDL_Window* window = SDL_GetWindowFromID((SDL_WindowID)(intptr_t)viewport->PlatformHandle);
ImDrawData* draw_data = viewport->DrawData;
SDL_GPUCommandBuffer* command_buffer = SDL_AcquireGPUCommandBuffer(data->InitInfo.Device);
SDL_GPUTexture* swapchain_texture;
SDL_AcquireGPUSwapchainTexture(command_buffer, window, &swapchain_texture, nullptr, nullptr);
if (swapchain_texture != nullptr)
{
ImGui_ImplSDLGPU3_PrepareDrawData(draw_data, command_buffer); // FIXME-OPT: Not optimal, may this be done earlier?
SDL_GPUColorTargetInfo target_info = {};
target_info.texture = swapchain_texture;
target_info.clear_color = SDL_FColor{ 0.0f,0.0f,0.0f,1.0f };
target_info.load_op = SDL_GPU_LOADOP_CLEAR;
target_info.store_op = SDL_GPU_STOREOP_STORE;
target_info.mip_level = 0;
target_info.layer_or_depth_plane = 0;
target_info.cycle = false;
SDL_GPURenderPass* render_pass = SDL_BeginGPURenderPass(command_buffer, &target_info, 1, nullptr);
ImGui_ImplSDLGPU3_RenderDrawData(draw_data, command_buffer, render_pass);
SDL_EndGPURenderPass(render_pass);
}
SDL_SubmitGPUCommandBuffer(command_buffer);
}
static void ImGui_ImplSDLGPU3_DestroyWindow(ImGuiViewport* viewport)
{
ImGui_ImplSDLGPU3_Data* data = ImGui_ImplSDLGPU3_GetBackendData();
if (viewport->RendererUserData)
{
SDL_Window* window = SDL_GetWindowFromID((SDL_WindowID)(intptr_t)viewport->PlatformHandle);
SDL_ReleaseWindowFromGPUDevice(data->InitInfo.Device, window);
}
viewport->RendererUserData = nullptr;
}
static void ImGui_ImplSDLGPU3_InitMultiViewportSupport()
{
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
platform_io.Renderer_RenderWindow = ImGui_ImplSDLGPU3_RenderWindow;
platform_io.Renderer_CreateWindow = ImGui_ImplSDLGPU3_CreateWindow;
platform_io.Renderer_DestroyWindow = ImGui_ImplSDLGPU3_DestroyWindow;
}
static void ImGui_ImplSDLGPU3_ShutdownMultiViewportSupport()
{
ImGui::DestroyPlatformWindows();
}
//-----------------------------------------------------------------------------
#endif // #ifndef IMGUI_DISABLE #endif // #ifndef IMGUI_DISABLE

View file

@ -3,9 +3,9 @@
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use simply cast a reference to your SDL_GPUTextureSamplerBinding to ImTextureID. // [X] Renderer: User texture binding. Use simply cast a reference to your SDL_GPUTextureSamplerBinding to ImTextureID.
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices. // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// Missing features: // [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [ ] Renderer: Multi-viewport support (multiple windows). // [X] Renderer: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
// The aim of imgui_impl_sdlgpu3.h/.cpp is to be usable in your engine without any modification. // The aim of imgui_impl_sdlgpu3.h/.cpp is to be usable in your engine without any modification.
// IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/ // IF YOU FEEL YOU NEED TO MAKE ANY CHANGE TO THIS CODE, please share them and your feedback at https://github.com/ocornut/imgui/
@ -19,7 +19,7 @@
// - Introduction, links and more at the top of imgui.cpp // - Introduction, links and more at the top of imgui.cpp
// Important note to the reader who wish to integrate imgui_impl_sdlgpu3.cpp/.h in their own engine/app. // Important note to the reader who wish to integrate imgui_impl_sdlgpu3.cpp/.h in their own engine/app.
// - Unline other backends, the user must call the function Imgui_ImplSDLGPU_PrepareDrawData BEFORE issuing a SDL_GPURenderPass containing ImGui_ImplSDLGPU_RenderDrawData. // - Unlike other backends, the user must call the function ImGui_ImplSDLGPU_PrepareDrawData BEFORE issuing a SDL_GPURenderPass containing ImGui_ImplSDLGPU_RenderDrawData.
// Calling the function is MANDATORY, otherwise the ImGui will not upload neither the vertex nor the index buffer for the GPU. See imgui_impl_sdlgpu3.cpp for more info. // Calling the function is MANDATORY, otherwise the ImGui will not upload neither the vertex nor the index buffer for the GPU. See imgui_impl_sdlgpu3.cpp for more info.
#pragma once #pragma once
@ -40,12 +40,14 @@ struct ImGui_ImplSDLGPU3_InitInfo
IMGUI_IMPL_API bool ImGui_ImplSDLGPU3_Init(ImGui_ImplSDLGPU3_InitInfo* info); IMGUI_IMPL_API bool ImGui_ImplSDLGPU3_Init(ImGui_ImplSDLGPU3_InitInfo* info);
IMGUI_IMPL_API void ImGui_ImplSDLGPU3_Shutdown(); IMGUI_IMPL_API void ImGui_ImplSDLGPU3_Shutdown();
IMGUI_IMPL_API void ImGui_ImplSDLGPU3_NewFrame(); IMGUI_IMPL_API void ImGui_ImplSDLGPU3_NewFrame();
IMGUI_IMPL_API void Imgui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer); IMGUI_IMPL_API void ImGui_ImplSDLGPU3_PrepareDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer);
IMGUI_IMPL_API void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer, SDL_GPURenderPass* render_pass, SDL_GPUGraphicsPipeline* pipeline = nullptr); IMGUI_IMPL_API void ImGui_ImplSDLGPU3_RenderDrawData(ImDrawData* draw_data, SDL_GPUCommandBuffer* command_buffer, SDL_GPURenderPass* render_pass, SDL_GPUGraphicsPipeline* pipeline = nullptr);
// Use if you want to reset your rendering device without losing Dear ImGui state.
IMGUI_IMPL_API void ImGui_ImplSDLGPU3_CreateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplSDLGPU3_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplSDLGPU3_DestroyDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplSDLGPU3_DestroyDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplSDLGPU3_CreateFontsTexture();
IMGUI_IMPL_API void ImGui_ImplSDLGPU3_DestroyFontsTexture(); // (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplSDLGPU3_UpdateTexture(ImTextureData* tex);
#endif // #ifndef IMGUI_DISABLE #endif // #ifndef IMGUI_DISABLE

View file

@ -10,8 +10,9 @@
// and it might be difficult to step out of those boundaries. // and it might be difficult to step out of those boundaries.
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// Missing features: // Missing features:
// [ ] Renderer: Multi-viewport support (multiple windows). // [ ] Renderer: Multi-viewport support (multiple windows).
@ -25,6 +26,7 @@
// - Introduction, links and more at the top of imgui.cpp // - Introduction, links and more at the top of imgui.cpp
// CHANGELOG // CHANGELOG
// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLRenderer2_CreateFontsTexture() and ImGui_ImplSDLRenderer2_DestroyFontsTexture().
// 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color. // 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color.
// 2024-10-09: Expose selected render state in ImGui_ImplSDLRenderer2_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks. // 2024-10-09: Expose selected render state in ImGui_ImplSDLRenderer2_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.
// 2024-05-14: *BREAKING CHANGE* ImGui_ImplSDLRenderer3_RenderDrawData() requires SDL_Renderer* passed as parameter. // 2024-05-14: *BREAKING CHANGE* ImGui_ImplSDLRenderer3_RenderDrawData() requires SDL_Renderer* passed as parameter.
@ -44,6 +46,8 @@
#if defined(__clang__) #if defined(__clang__)
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#elif defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe
#endif #endif
// SDL // SDL
@ -56,7 +60,7 @@
struct ImGui_ImplSDLRenderer2_Data struct ImGui_ImplSDLRenderer2_Data
{ {
SDL_Renderer* Renderer; // Main viewport's renderer SDL_Renderer* Renderer; // Main viewport's renderer
SDL_Texture* FontTexture;
ImGui_ImplSDLRenderer2_Data() { memset((void*)this, 0, sizeof(*this)); } ImGui_ImplSDLRenderer2_Data() { memset((void*)this, 0, sizeof(*this)); }
}; };
@ -80,6 +84,7 @@ bool ImGui_ImplSDLRenderer2_Init(SDL_Renderer* renderer)
io.BackendRendererUserData = (void*)bd; io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_sdlrenderer2"; io.BackendRendererName = "imgui_impl_sdlrenderer2";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
bd->Renderer = renderer; bd->Renderer = renderer;
@ -96,7 +101,7 @@ void ImGui_ImplSDLRenderer2_Shutdown()
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures);
IM_DELETE(bd); IM_DELETE(bd);
} }
@ -112,10 +117,7 @@ bool ImGui_ImplSDLRenderer2_NewFrame()
{ {
ImGui_ImplSDLRenderer2_Data* bd = ImGui_ImplSDLRenderer2_GetBackendData(); ImGui_ImplSDLRenderer2_Data* bd = ImGui_ImplSDLRenderer2_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLRenderer2_Init()?"); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLRenderer2_Init()?");
IM_UNUSED(bd);
if (!bd->FontTexture)
return ImGui_ImplSDLRenderer2_CreateDeviceObjects();
return true;
} }
void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer) void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer)
@ -136,6 +138,13 @@ void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer*
if (fb_width == 0 || fb_height == 0) if (fb_width == 0 || fb_height == 0)
return; return;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplSDLRenderer2_UpdateTexture(tex);
// Backup SDL_Renderer state that will be modified to restore it afterwards // Backup SDL_Renderer state that will be modified to restore it afterwards
struct BackupSDLRendererState struct BackupSDLRendererState
{ {
@ -221,55 +230,67 @@ void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer*
SDL_RenderSetClipRect(renderer, old.ClipEnabled ? &old.ClipRect : nullptr); SDL_RenderSetClipRect(renderer, old.ClipEnabled ? &old.ClipRect : nullptr);
} }
// Called by Init/NewFrame/Shutdown void ImGui_ImplSDLRenderer2_UpdateTexture(ImTextureData* tex)
bool ImGui_ImplSDLRenderer2_CreateFontsTexture()
{ {
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplSDLRenderer2_Data* bd = ImGui_ImplSDLRenderer2_GetBackendData(); ImGui_ImplSDLRenderer2_Data* bd = ImGui_ImplSDLRenderer2_GetBackendData();
// Build texture atlas if (tex->Status == ImTextureStatus_WantCreate)
unsigned char* pixels; {
int width, height; // Create and upload new texture to graphics system
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
// Upload texture to graphics system // Create texture
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
bd->FontTexture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, width, height); SDL_Texture* sdl_texture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, tex->Width, tex->Height);
if (bd->FontTexture == nullptr) IM_ASSERT(sdl_texture != nullptr && "Backend failed to create texture!");
{ SDL_UpdateTexture(sdl_texture, nullptr, tex->GetPixels(), tex->GetPitch());
SDL_Log("error creating texture"); SDL_SetTextureBlendMode(sdl_texture, SDL_BLENDMODE_BLEND);
return false; SDL_SetTextureScaleMode(sdl_texture, SDL_ScaleModeLinear);
// Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)sdl_texture);
tex->SetStatus(ImTextureStatus_OK);
} }
SDL_UpdateTexture(bd->FontTexture, nullptr, pixels, 4 * width); else if (tex->Status == ImTextureStatus_WantUpdates)
SDL_SetTextureBlendMode(bd->FontTexture, SDL_BLENDMODE_BLEND); {
SDL_SetTextureScaleMode(bd->FontTexture, SDL_ScaleModeLinear); // Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
// Store our identifier SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID;
io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); for (ImTextureRect& r : tex->Updates)
{
return true; SDL_Rect sdl_r = { r.x, r.y, r.w, r.h };
SDL_UpdateTexture(sdl_texture, &sdl_r, tex->GetPixelsAt(r.x, r.y), tex->GetPitch());
} }
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{
SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID;
if (sdl_texture == nullptr)
return;
SDL_DestroyTexture(sdl_texture);
void ImGui_ImplSDLRenderer2_DestroyFontsTexture() // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
{ tex->SetTexID(ImTextureID_Invalid);
ImGuiIO& io = ImGui::GetIO(); tex->SetStatus(ImTextureStatus_Destroyed);
ImGui_ImplSDLRenderer2_Data* bd = ImGui_ImplSDLRenderer2_GetBackendData();
if (bd->FontTexture)
{
io.Fonts->SetTexID(0);
SDL_DestroyTexture(bd->FontTexture);
bd->FontTexture = nullptr;
} }
} }
bool ImGui_ImplSDLRenderer2_CreateDeviceObjects() void ImGui_ImplSDLRenderer2_CreateDeviceObjects()
{ {
return ImGui_ImplSDLRenderer2_CreateFontsTexture();
} }
void ImGui_ImplSDLRenderer2_DestroyDeviceObjects() void ImGui_ImplSDLRenderer2_DestroyDeviceObjects()
{ {
ImGui_ImplSDLRenderer2_DestroyFontsTexture(); // Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
{
tex->SetStatus(ImTextureStatus_WantDestroy);
ImGui_ImplSDLRenderer2_UpdateTexture(tex);
}
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

View file

@ -10,8 +10,9 @@
// and it might be difficult to step out of those boundaries. // and it might be difficult to step out of those boundaries.
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// Missing features: // Missing features:
// [ ] Renderer: Multi-viewport support (multiple windows). // [ ] Renderer: Multi-viewport support (multiple windows).
@ -37,11 +38,12 @@ IMGUI_IMPL_API bool ImGui_ImplSDLRenderer2_NewFrame();
IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer); IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer);
// Called by Init/NewFrame/Shutdown // Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplSDLRenderer2_CreateFontsTexture(); IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_DestroyFontsTexture();
IMGUI_IMPL_API bool ImGui_ImplSDLRenderer2_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_DestroyDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_DestroyDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplSDLRenderer2_UpdateTexture(ImTextureData* tex);
// [BETA] Selected render state data shared with callbacks. // [BETA] Selected render state data shared with callbacks.
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplSDLRenderer2_RenderDrawData() call. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplSDLRenderer2_RenderDrawData() call.
// (Please open an issue if you feel you need access to more data) // (Please open an issue if you feel you need access to more data)

View file

@ -10,8 +10,9 @@
// and it might be difficult to step out of those boundaries. // and it might be difficult to step out of those boundaries.
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// Missing features: // Missing features:
// [ ] Renderer: Multi-viewport support (multiple windows). // [ ] Renderer: Multi-viewport support (multiple windows).
@ -25,6 +26,7 @@
// - Introduction, links and more at the top of imgui.cpp // - Introduction, links and more at the top of imgui.cpp
// CHANGELOG // CHANGELOG
// 2025-06-11: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplSDLRenderer3_CreateFontsTexture() and ImGui_ImplSDLRenderer3_DestroyFontsTexture().
// 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color. // 2025-01-18: Use endian-dependent RGBA32 texture format, to match SDL_Color.
// 2024-10-09: Expose selected render state in ImGui_ImplSDLRenderer3_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks. // 2024-10-09: Expose selected render state in ImGui_ImplSDLRenderer3_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.
// 2024-07-01: Update for SDL3 api changes: SDL_RenderGeometryRaw() uint32 version was removed (SDL#9009). // 2024-07-01: Update for SDL3 api changes: SDL_RenderGeometryRaw() uint32 version was removed (SDL#9009).
@ -41,6 +43,8 @@
#if defined(__clang__) #if defined(__clang__)
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness #pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
#elif defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wfloat-equal" // warning: comparing floating-point with '==' or '!=' is unsafe
#endif #endif
// SDL // SDL
@ -53,7 +57,6 @@
struct ImGui_ImplSDLRenderer3_Data struct ImGui_ImplSDLRenderer3_Data
{ {
SDL_Renderer* Renderer; // Main viewport's renderer SDL_Renderer* Renderer; // Main viewport's renderer
SDL_Texture* FontTexture;
ImVector<SDL_FColor> ColorBuffer; ImVector<SDL_FColor> ColorBuffer;
ImGui_ImplSDLRenderer3_Data() { memset((void*)this, 0, sizeof(*this)); } ImGui_ImplSDLRenderer3_Data() { memset((void*)this, 0, sizeof(*this)); }
@ -79,6 +82,7 @@ bool ImGui_ImplSDLRenderer3_Init(SDL_Renderer* renderer)
io.BackendRendererUserData = (void*)bd; io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_sdlrenderer3"; io.BackendRendererName = "imgui_impl_sdlrenderer3";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
bd->Renderer = renderer; bd->Renderer = renderer;
@ -95,7 +99,7 @@ void ImGui_ImplSDLRenderer3_Shutdown()
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures);
IM_DELETE(bd); IM_DELETE(bd);
} }
@ -111,9 +115,7 @@ void ImGui_ImplSDLRenderer3_NewFrame()
{ {
ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLRenderer3_Init()?"); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplSDLRenderer3_Init()?");
IM_UNUSED(bd);
if (!bd->FontTexture)
ImGui_ImplSDLRenderer3_CreateDeviceObjects();
} }
// https://github.com/libsdl-org/SDL/issues/9009 // https://github.com/libsdl-org/SDL/issues/9009
@ -154,6 +156,13 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer*
if (fb_width == 0 || fb_height == 0) if (fb_width == 0 || fb_height == 0)
return; return;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplSDLRenderer3_UpdateTexture(tex);
// Backup SDL_Renderer state that will be modified to restore it afterwards // Backup SDL_Renderer state that will be modified to restore it afterwards
struct BackupSDLRendererState struct BackupSDLRendererState
{ {
@ -237,55 +246,67 @@ void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer*
SDL_SetRenderClipRect(renderer, old.ClipEnabled ? &old.ClipRect : nullptr); SDL_SetRenderClipRect(renderer, old.ClipEnabled ? &old.ClipRect : nullptr);
} }
// Called by Init/NewFrame/Shutdown void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex)
bool ImGui_ImplSDLRenderer3_CreateFontsTexture()
{ {
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData(); ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
// Build texture atlas if (tex->Status == ImTextureStatus_WantCreate)
unsigned char* pixels; {
int width, height; // Create and upload new texture to graphics system
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height); // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory. //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == 0 && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
// Upload texture to graphics system // Create texture
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling) // (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
bd->FontTexture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, width, height); SDL_Texture* sdl_texture = SDL_CreateTexture(bd->Renderer, SDL_PIXELFORMAT_RGBA32, SDL_TEXTUREACCESS_STATIC, tex->Width, tex->Height);
if (bd->FontTexture == nullptr) IM_ASSERT(sdl_texture != nullptr && "Backend failed to create texture!");
{ SDL_UpdateTexture(sdl_texture, nullptr, tex->GetPixels(), tex->GetPitch());
SDL_Log("error creating texture"); SDL_SetTextureBlendMode(sdl_texture, SDL_BLENDMODE_BLEND);
return false; SDL_SetTextureScaleMode(sdl_texture, SDL_SCALEMODE_LINEAR);
// Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)sdl_texture);
tex->SetStatus(ImTextureStatus_OK);
} }
SDL_UpdateTexture(bd->FontTexture, nullptr, pixels, 4 * width); else if (tex->Status == ImTextureStatus_WantUpdates)
SDL_SetTextureBlendMode(bd->FontTexture, SDL_BLENDMODE_BLEND); {
SDL_SetTextureScaleMode(bd->FontTexture, SDL_SCALEMODE_LINEAR); // Update selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->Updates[] but you can use tex->UpdateRect to upload a single region.
// Store our identifier SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID;
io.Fonts->SetTexID((ImTextureID)(intptr_t)bd->FontTexture); for (ImTextureRect& r : tex->Updates)
{
return true; SDL_Rect sdl_r = { r.x, r.y, r.w, r.h };
SDL_UpdateTexture(sdl_texture, &sdl_r, tex->GetPixelsAt(r.x, r.y), tex->GetPitch());
} }
tex->SetStatus(ImTextureStatus_OK);
}
else if (tex->Status == ImTextureStatus_WantDestroy)
{
SDL_Texture* sdl_texture = (SDL_Texture*)(intptr_t)tex->TexID;
if (sdl_texture == nullptr)
return;
SDL_DestroyTexture(sdl_texture);
void ImGui_ImplSDLRenderer3_DestroyFontsTexture() // Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
{ tex->SetTexID(ImTextureID_Invalid);
ImGuiIO& io = ImGui::GetIO(); tex->SetStatus(ImTextureStatus_Destroyed);
ImGui_ImplSDLRenderer3_Data* bd = ImGui_ImplSDLRenderer3_GetBackendData();
if (bd->FontTexture)
{
io.Fonts->SetTexID(0);
SDL_DestroyTexture(bd->FontTexture);
bd->FontTexture = nullptr;
} }
} }
bool ImGui_ImplSDLRenderer3_CreateDeviceObjects() void ImGui_ImplSDLRenderer3_CreateDeviceObjects()
{ {
return ImGui_ImplSDLRenderer3_CreateFontsTexture();
} }
void ImGui_ImplSDLRenderer3_DestroyDeviceObjects() void ImGui_ImplSDLRenderer3_DestroyDeviceObjects()
{ {
ImGui_ImplSDLRenderer3_DestroyFontsTexture(); // Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
{
tex->SetStatus(ImTextureStatus_WantDestroy);
ImGui_ImplSDLRenderer3_UpdateTexture(tex);
}
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

View file

@ -10,8 +10,9 @@
// and it might be difficult to step out of those boundaries. // and it might be difficult to step out of those boundaries.
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'SDL_Texture*' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// Missing features: // Missing features:
// [ ] Renderer: Multi-viewport support (multiple windows). // [ ] Renderer: Multi-viewport support (multiple windows).
@ -37,11 +38,12 @@ IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_NewFrame();
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer); IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_RenderDrawData(ImDrawData* draw_data, SDL_Renderer* renderer);
// Called by Init/NewFrame/Shutdown // Called by Init/NewFrame/Shutdown
IMGUI_IMPL_API bool ImGui_ImplSDLRenderer3_CreateFontsTexture(); IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_DestroyFontsTexture();
IMGUI_IMPL_API bool ImGui_ImplSDLRenderer3_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_DestroyDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_DestroyDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplSDLRenderer3_UpdateTexture(ImTextureData* tex);
// [BETA] Selected render state data shared with callbacks. // [BETA] Selected render state data shared with callbacks.
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplSDLRenderer3_RenderDrawData() call. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplSDLRenderer3_RenderDrawData() call.
// (Please open an issue if you feel you need access to more data) // (Please open an issue if you feel you need access to more data)

View file

@ -2,8 +2,9 @@
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
// Implemented features: // Implemented features:
// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as ImTextureID. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID! See https://github.com/ocornut/imgui/pull/914 for discussions. // [!] Renderer: User texture binding. Use 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions.
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// [x] Renderer: Multi-viewport / platform windows. With issues (flickering when creating a new viewport). // [x] Renderer: Multi-viewport / platform windows. With issues (flickering when creating a new viewport).
@ -28,6 +29,10 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-06-11: Vulkan: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. Removed ImGui_ImplVulkan_CreateFontsTexture() and ImGui_ImplVulkan_DestroyFontsTexture().
// 2025-05-07: Vulkan: Fixed validation errors during window detach in multi-viewport mode. (#8600, #8176)
// 2025-05-07: Vulkan: Load dynamic rendering functions using vkGetDeviceProcAddr() + try both non-KHR and KHR versions. (#8600, #8326, #8365)
// 2025-04-07: Vulkan: Deep-copy ImGui_ImplVulkan_InitInfo::PipelineRenderingCreateInfo's pColorAttachmentFormats buffer when set, in order to reduce common user-error of specifying a pointer to data that gets out of scope. (#8282)
// 2025-02-14: *BREAKING CHANGE*: Added uint32_t api_version to ImGui_ImplVulkan_LoadFunctions(). // 2025-02-14: *BREAKING CHANGE*: Added uint32_t api_version to ImGui_ImplVulkan_LoadFunctions().
// 2025-02-13: Vulkan: Added ApiVersion field in ImGui_ImplVulkan_InitInfo. Default to header version if unspecified. Dynamic rendering path loads "vkCmdBeginRendering/vkCmdEndRendering" (without -KHR suffix) on API 1.3. (#8326) // 2025-02-13: Vulkan: Added ApiVersion field in ImGui_ImplVulkan_InitInfo. Default to header version if unspecified. Dynamic rendering path loads "vkCmdBeginRendering/vkCmdEndRendering" (without -KHR suffix) on API 1.3. (#8326)
// 2025-01-09: Vulkan: Added IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE to clarify how many image sampler descriptors are expected to be available in descriptor pool. (#6642) // 2025-01-09: Vulkan: Added IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE to clarify how many image sampler descriptors are expected to be available in descriptor pool. (#6642)
@ -180,6 +185,7 @@ static bool g_FunctionsLoaded = true;
IMGUI_VULKAN_FUNC_MAP_MACRO(vkFreeDescriptorSets) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkFreeDescriptorSets) \
IMGUI_VULKAN_FUNC_MAP_MACRO(vkFreeMemory) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkFreeMemory) \
IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetBufferMemoryRequirements) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetBufferMemoryRequirements) \
IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetDeviceQueue) \
IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetImageMemoryRequirements) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetImageMemoryRequirements) \
IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetPhysicalDeviceProperties) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetPhysicalDeviceProperties) \
IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetPhysicalDeviceMemoryProperties) \ IMGUI_VULKAN_FUNC_MAP_MACRO(vkGetPhysicalDeviceMemoryProperties) \
@ -251,7 +257,7 @@ struct ImGui_ImplVulkan_ViewportData
bool SwapChainNeedRebuild; // Flag when viewport swapchain resized in the middle of processing a frame bool SwapChainNeedRebuild; // Flag when viewport swapchain resized in the middle of processing a frame
bool SwapChainSuboptimal; // Flag when VK_SUBOPTIMAL_KHR was returned. bool SwapChainSuboptimal; // Flag when VK_SUBOPTIMAL_KHR was returned.
ImGui_ImplVulkan_ViewportData() { WindowOwned = SwapChainNeedRebuild = SwapChainSuboptimal = false; memset(&RenderBuffers, 0, sizeof(RenderBuffers)); } ImGui_ImplVulkan_ViewportData() { WindowOwned = SwapChainNeedRebuild = SwapChainSuboptimal = false; memset((void*)&RenderBuffers, 0, sizeof(RenderBuffers)); }
~ImGui_ImplVulkan_ViewportData() { } ~ImGui_ImplVulkan_ViewportData() { }
}; };
@ -270,7 +276,6 @@ struct ImGui_ImplVulkan_Data
VkDescriptorPool DescriptorPool; VkDescriptorPool DescriptorPool;
// Texture management // Texture management
ImGui_ImplVulkan_Texture FontTexture;
VkSampler TexSampler; VkSampler TexSampler;
VkCommandPool TexCommandPool; VkCommandPool TexCommandPool;
VkCommandBuffer TexCommandBuffer; VkCommandBuffer TexCommandBuffer;
@ -525,6 +530,13 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm
if (fb_width <= 0 || fb_height <= 0) if (fb_width <= 0 || fb_height <= 0)
return; return;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplVulkan_UpdateTexture(tex);
ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData();
ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo;
if (pipeline == VK_NULL_HANDLE) if (pipeline == VK_NULL_HANDLE)
@ -663,64 +675,50 @@ void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer comm
vkCmdSetScissor(command_buffer, 0, 1, &scissor); vkCmdSetScissor(command_buffer, 0, 1, &scissor);
} }
bool ImGui_ImplVulkan_CreateFontsTexture() static void ImGui_ImplVulkan_DestroyTexture(ImTextureData* tex)
{ {
ImGuiIO& io = ImGui::GetIO(); ImGui_ImplVulkan_Texture* backend_tex = (ImGui_ImplVulkan_Texture*)tex->BackendUserData;
if (backend_tex == nullptr)
return;
IM_ASSERT(backend_tex->DescriptorSet == (VkDescriptorSet)tex->TexID);
ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData();
ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo;
ImGui_ImplVulkan_RemoveTexture(backend_tex->DescriptorSet);
vkDestroyImageView(v->Device, backend_tex->ImageView, v->Allocator);
vkDestroyImage(v->Device, backend_tex->Image, v->Allocator);
vkFreeMemory(v->Device, backend_tex->Memory, v->Allocator);
IM_DELETE(backend_tex);
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
tex->BackendUserData = nullptr;
}
void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex)
{
if (tex->Status == ImTextureStatus_OK)
return;
ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData();
ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo;
VkResult err; VkResult err;
// Destroy existing texture (if any) if (tex->Status == ImTextureStatus_WantCreate)
if (bd->FontTexture.DescriptorSet)
{ {
vkQueueWaitIdle(v->Queue); // Create and upload new texture to graphics system
ImGui_ImplVulkan_DestroyFontsTexture(); //IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
} IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
// Create command pool/buffer ImGui_ImplVulkan_Texture* backend_tex = IM_NEW(ImGui_ImplVulkan_Texture)();
if (bd->TexCommandPool == VK_NULL_HANDLE)
{
VkCommandPoolCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
info.flags = 0;
info.queueFamilyIndex = v->QueueFamily;
vkCreateCommandPool(v->Device, &info, v->Allocator, &bd->TexCommandPool);
}
if (bd->TexCommandBuffer == VK_NULL_HANDLE)
{
VkCommandBufferAllocateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
info.commandPool = bd->TexCommandPool;
info.commandBufferCount = 1;
err = vkAllocateCommandBuffers(v->Device, &info, &bd->TexCommandBuffer);
check_vk_result(err);
}
// Start command buffer
{
err = vkResetCommandPool(v->Device, bd->TexCommandPool, 0);
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(bd->TexCommandBuffer, &begin_info);
check_vk_result(err);
}
unsigned char* pixels;
int width, height;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height);
size_t upload_size = width * height * 4 * sizeof(char);
// Create the Image: // Create the Image:
ImGui_ImplVulkan_Texture* backend_tex = &bd->FontTexture;
{ {
VkImageCreateInfo info = {}; VkImageCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
info.imageType = VK_IMAGE_TYPE_2D; info.imageType = VK_IMAGE_TYPE_2D;
info.format = VK_FORMAT_R8G8B8A8_UNORM; info.format = VK_FORMAT_R8G8B8A8_UNORM;
info.extent.width = width; info.extent.width = tex->Width;
info.extent.height = height; info.extent.height = tex->Height;
info.extent.depth = 1; info.extent.depth = 1;
info.mipLevels = 1; info.mipLevels = 1;
info.arrayLayers = 1; info.arrayLayers = 1;
@ -757,12 +755,32 @@ bool ImGui_ImplVulkan_CreateFontsTexture()
check_vk_result(err); check_vk_result(err);
} }
// Create the Descriptor Set: // Create the Descriptor Set
backend_tex->DescriptorSet = ImGui_ImplVulkan_AddTexture(bd->TexSampler, backend_tex->ImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); backend_tex->DescriptorSet = ImGui_ImplVulkan_AddTexture(bd->TexSampler, backend_tex->ImageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
// Store identifiers
tex->SetTexID((ImTextureID)backend_tex->DescriptorSet);
tex->BackendUserData = backend_tex;
}
if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates)
{
ImGui_ImplVulkan_Texture* backend_tex = (ImGui_ImplVulkan_Texture*)tex->BackendUserData;
// Update full texture or selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->UpdateRect but you can use tex->Updates[] to upload individual regions.
// We could use the smaller rect on _WantCreate but using the full rect allows us to clear the texture.
const int upload_x = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.x;
const int upload_y = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.y;
const int upload_w = (tex->Status == ImTextureStatus_WantCreate) ? tex->Width : tex->UpdateRect.w;
const int upload_h = (tex->Status == ImTextureStatus_WantCreate) ? tex->Height : tex->UpdateRect.h;
// Create the Upload Buffer: // Create the Upload Buffer:
VkDeviceMemory upload_buffer_memory; VkDeviceMemory upload_buffer_memory;
VkBuffer upload_buffer; VkBuffer upload_buffer;
VkDeviceSize upload_pitch = upload_w * tex->BytesPerPixel;
VkDeviceSize upload_size = upload_h * upload_pitch;
{ {
VkBufferCreateInfo buffer_info = {}; VkBufferCreateInfo buffer_info = {};
buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
@ -789,7 +807,8 @@ bool ImGui_ImplVulkan_CreateFontsTexture()
char* map = nullptr; char* map = nullptr;
err = vkMapMemory(v->Device, upload_buffer_memory, 0, upload_size, 0, (void**)(&map)); err = vkMapMemory(v->Device, upload_buffer_memory, 0, upload_size, 0, (void**)(&map));
check_vk_result(err); check_vk_result(err);
memcpy(map, pixels, upload_size); for (int y = 0; y < upload_h; y++)
memcpy(map + upload_pitch * y, tex->GetPixelsAt(upload_x, upload_y + y), (size_t)upload_pitch);
VkMappedMemoryRange range[1] = {}; VkMappedMemoryRange range[1] = {};
range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE; range[0].sType = VK_STRUCTURE_TYPE_MAPPED_MEMORY_RANGE;
range[0].memory = upload_buffer_memory; range[0].memory = upload_buffer_memory;
@ -799,6 +818,17 @@ bool ImGui_ImplVulkan_CreateFontsTexture()
vkUnmapMemory(v->Device, upload_buffer_memory); vkUnmapMemory(v->Device, upload_buffer_memory);
} }
// Start command buffer
{
err = vkResetCommandPool(v->Device, bd->TexCommandPool, 0);
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(bd->TexCommandBuffer, &begin_info);
check_vk_result(err);
}
// Copy to Image: // Copy to Image:
{ {
VkImageMemoryBarrier copy_barrier[1] = {}; VkImageMemoryBarrier copy_barrier[1] = {};
@ -817,9 +847,11 @@ bool ImGui_ImplVulkan_CreateFontsTexture()
VkBufferImageCopy region = {}; VkBufferImageCopy region = {};
region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
region.imageSubresource.layerCount = 1; region.imageSubresource.layerCount = 1;
region.imageExtent.width = width; region.imageExtent.width = upload_w;
region.imageExtent.height = height; region.imageExtent.height = upload_h;
region.imageExtent.depth = 1; region.imageExtent.depth = 1;
region.imageOffset.x = upload_x;
region.imageOffset.y = upload_y;
vkCmdCopyBufferToImage(bd->TexCommandBuffer, upload_buffer, backend_tex->Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region); vkCmdCopyBufferToImage(bd->TexCommandBuffer, upload_buffer, backend_tex->Image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
VkImageMemoryBarrier use_barrier[1] = {}; VkImageMemoryBarrier use_barrier[1] = {};
@ -837,10 +869,8 @@ bool ImGui_ImplVulkan_CreateFontsTexture()
vkCmdPipelineBarrier(bd->TexCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, use_barrier); vkCmdPipelineBarrier(bd->TexCommandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, use_barrier);
} }
// Store our identifier
io.Fonts->SetTexID((ImTextureID)backend_tex->DescriptorSet);
// End command buffer // End command buffer
{
VkSubmitInfo end_info = {}; VkSubmitInfo end_info = {};
end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; end_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
end_info.commandBufferCount = 1; end_info.commandBufferCount = 1;
@ -849,34 +879,18 @@ bool ImGui_ImplVulkan_CreateFontsTexture()
check_vk_result(err); check_vk_result(err);
err = vkQueueSubmit(v->Queue, 1, &end_info, VK_NULL_HANDLE); err = vkQueueSubmit(v->Queue, 1, &end_info, VK_NULL_HANDLE);
check_vk_result(err); check_vk_result(err);
}
err = vkQueueWaitIdle(v->Queue); err = vkQueueWaitIdle(v->Queue); // FIXME-OPT: Suboptimal!
check_vk_result(err); check_vk_result(err);
vkDestroyBuffer(v->Device, upload_buffer, v->Allocator); vkDestroyBuffer(v->Device, upload_buffer, v->Allocator);
vkFreeMemory(v->Device, upload_buffer_memory, v->Allocator); vkFreeMemory(v->Device, upload_buffer_memory, v->Allocator);
return true; tex->SetStatus(ImTextureStatus_OK);
} }
// You probably never need to call this, as it is called by ImGui_ImplVulkan_CreateFontsTexture() and ImGui_ImplVulkan_Shutdown(). if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames >= (int)bd->VulkanInitInfo.ImageCount)
void ImGui_ImplVulkan_DestroyFontsTexture() ImGui_ImplVulkan_DestroyTexture(tex);
{
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData();
ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo;
ImGui_ImplVulkan_Texture* backend_tex = &bd->FontTexture;
if (backend_tex->DescriptorSet)
{
ImGui_ImplVulkan_RemoveTexture(backend_tex->DescriptorSet);
backend_tex->DescriptorSet = VK_NULL_HANDLE;
io.Fonts->SetTexID(0);
}
if (backend_tex->ImageView) { vkDestroyImageView(v->Device, backend_tex->ImageView, v->Allocator); backend_tex->ImageView = VK_NULL_HANDLE; }
if (backend_tex->Image) { vkDestroyImage(v->Device, backend_tex->Image, v->Allocator); backend_tex->Image = VK_NULL_HANDLE; }
if (backend_tex->Memory) { vkFreeMemory(v->Device, backend_tex->Memory, v->Allocator); backend_tex->Memory = VK_NULL_HANDLE; }
} }
static void ImGui_ImplVulkan_CreateShaderModules(VkDevice device, const VkAllocationCallbacks* allocator) static void ImGui_ImplVulkan_CreateShaderModules(VkDevice device, const VkAllocationCallbacks* allocator)
@ -1058,7 +1072,7 @@ bool ImGui_ImplVulkan_CreateDeviceObjects()
if (v->DescriptorPoolSize != 0) if (v->DescriptorPoolSize != 0)
{ {
IM_ASSERT(v->DescriptorPoolSize > IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE); IM_ASSERT(v->DescriptorPoolSize >= IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE);
VkDescriptorPoolSize pool_size = { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, v->DescriptorPoolSize }; VkDescriptorPoolSize pool_size = { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, v->DescriptorPoolSize };
VkDescriptorPoolCreateInfo pool_info = {}; VkDescriptorPoolCreateInfo pool_info = {};
pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
@ -1091,6 +1105,26 @@ bool ImGui_ImplVulkan_CreateDeviceObjects()
ImGui_ImplVulkan_CreatePipeline(v->Device, v->Allocator, v->PipelineCache, v->RenderPass, v->MSAASamples, &bd->Pipeline, v->Subpass); ImGui_ImplVulkan_CreatePipeline(v->Device, v->Allocator, v->PipelineCache, v->RenderPass, v->MSAASamples, &bd->Pipeline, v->Subpass);
// Create command pool/buffer for texture upload
if (!bd->TexCommandPool)
{
VkCommandPoolCreateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
info.flags = 0;
info.queueFamilyIndex = v->QueueFamily;
err = vkCreateCommandPool(v->Device, &info, v->Allocator, &bd->TexCommandPool);
check_vk_result(err);
}
if (!bd->TexCommandBuffer)
{
VkCommandBufferAllocateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
info.commandPool = bd->TexCommandPool;
info.commandBufferCount = 1;
err = vkAllocateCommandBuffers(v->Device, &info, &bd->TexCommandBuffer);
check_vk_result(err);
}
return true; return true;
} }
@ -1099,7 +1133,11 @@ void ImGui_ImplVulkan_DestroyDeviceObjects()
ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData();
ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo; ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo;
ImGui_ImplVulkanH_DestroyAllViewportsRenderBuffers(v->Device, v->Allocator); ImGui_ImplVulkanH_DestroyAllViewportsRenderBuffers(v->Device, v->Allocator);
ImGui_ImplVulkan_DestroyFontsTexture();
// Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
ImGui_ImplVulkan_DestroyTexture(tex);
if (bd->TexCommandBuffer) { vkFreeCommandBuffers(v->Device, bd->TexCommandPool, 1, &bd->TexCommandBuffer); bd->TexCommandBuffer = VK_NULL_HANDLE; } if (bd->TexCommandBuffer) { vkFreeCommandBuffers(v->Device, bd->TexCommandPool, 1, &bd->TexCommandBuffer); bd->TexCommandBuffer = VK_NULL_HANDLE; }
if (bd->TexCommandPool) { vkDestroyCommandPool(v->Device, bd->TexCommandPool, v->Allocator); bd->TexCommandPool = VK_NULL_HANDLE; } if (bd->TexCommandPool) { vkDestroyCommandPool(v->Device, bd->TexCommandPool, v->Allocator); bd->TexCommandPool = VK_NULL_HANDLE; }
@ -1116,9 +1154,19 @@ void ImGui_ImplVulkan_DestroyDeviceObjects()
#ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING
static void ImGui_ImplVulkan_LoadDynamicRenderingFunctions(uint32_t api_version, PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data) static void ImGui_ImplVulkan_LoadDynamicRenderingFunctions(uint32_t api_version, PFN_vkVoidFunction(*loader_func)(const char* function_name, void* user_data), void* user_data)
{ {
// Manually load those two (see #5446, #8326, #8365) IM_UNUSED(api_version);
ImGuiImplVulkanFuncs_vkCmdBeginRenderingKHR = reinterpret_cast<PFN_vkCmdBeginRenderingKHR>(loader_func(api_version < VK_API_VERSION_1_3 ? "vkCmdBeginRenderingKHR" : "vkCmdBeginRendering", user_data));
ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR = reinterpret_cast<PFN_vkCmdEndRenderingKHR>(loader_func(api_version < VK_API_VERSION_1_3 ? "vkCmdEndRenderingKHR" : "vkCmdEndRendering", user_data)); // Manually load those two (see #5446, #8326, #8365, #8600)
// - Try loading core (non-KHR) versions first (this will work for Vulkan 1.3+ and the device supports dynamic rendering)
ImGuiImplVulkanFuncs_vkCmdBeginRenderingKHR = reinterpret_cast<PFN_vkCmdBeginRenderingKHR>(loader_func("vkCmdBeginRendering", user_data));
ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR = reinterpret_cast<PFN_vkCmdEndRenderingKHR>(loader_func("vkCmdEndRendering", user_data));
// - Fallback to KHR versions if core not available (this will work if KHR extension is available and enabled and also the device supports dynamic rendering)
if (ImGuiImplVulkanFuncs_vkCmdBeginRenderingKHR == nullptr || ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR == nullptr)
{
ImGuiImplVulkanFuncs_vkCmdBeginRenderingKHR = reinterpret_cast<PFN_vkCmdBeginRenderingKHR>(loader_func("vkCmdBeginRenderingKHR", user_data));
ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR = reinterpret_cast<PFN_vkCmdEndRenderingKHR>(loader_func("vkCmdEndRenderingKHR", user_data));
}
} }
#endif #endif
@ -1173,7 +1221,7 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info)
{ {
#ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING #ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING
#ifndef IMGUI_IMPL_VULKAN_USE_LOADER #ifndef IMGUI_IMPL_VULKAN_USE_LOADER
ImGui_ImplVulkan_LoadDynamicRenderingFunctions(info->ApiVersion, [](const char* function_name, void* user_data) { return vkGetInstanceProcAddr((VkInstance)user_data, function_name); }, (void*)info->Instance); ImGui_ImplVulkan_LoadDynamicRenderingFunctions(info->ApiVersion, [](const char* function_name, void* user_data) { return vkGetDeviceProcAddr((VkDevice)user_data, function_name); }, (void*)info->Device);
#endif #endif
IM_ASSERT(ImGuiImplVulkanFuncs_vkCmdBeginRenderingKHR != nullptr); IM_ASSERT(ImGuiImplVulkanFuncs_vkCmdBeginRenderingKHR != nullptr);
IM_ASSERT(ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR != nullptr); IM_ASSERT(ImGuiImplVulkanFuncs_vkCmdEndRenderingKHR != nullptr);
@ -1191,6 +1239,7 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info)
io.BackendRendererUserData = (void*)bd; io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_vulkan"; io.BackendRendererName = "imgui_impl_vulkan";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional) io.BackendFlags |= ImGuiBackendFlags_RendererHasViewports; // We can create multi-viewports on the Renderer side (optional)
IM_ASSERT(info->Instance != VK_NULL_HANDLE); IM_ASSERT(info->Instance != VK_NULL_HANDLE);
@ -1207,8 +1256,19 @@ bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo* info)
IM_ASSERT(info->RenderPass != VK_NULL_HANDLE); IM_ASSERT(info->RenderPass != VK_NULL_HANDLE);
bd->VulkanInitInfo = *info; bd->VulkanInitInfo = *info;
#ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING
ImGui_ImplVulkan_InitInfo* v = &bd->VulkanInitInfo;
if (v->PipelineRenderingCreateInfo.pColorAttachmentFormats != NULL)
{
// Deep copy buffer to reduce error-rate for end user (#8282)
VkFormat* formats_copy = (VkFormat*)IM_ALLOC(sizeof(VkFormat) * v->PipelineRenderingCreateInfo.colorAttachmentCount);
memcpy(formats_copy, v->PipelineRenderingCreateInfo.pColorAttachmentFormats, sizeof(VkFormat) * v->PipelineRenderingCreateInfo.colorAttachmentCount);
v->PipelineRenderingCreateInfo.pColorAttachmentFormats = formats_copy;
}
#endif
ImGui_ImplVulkan_CreateDeviceObjects(); if (!ImGui_ImplVulkan_CreateDeviceObjects())
IM_ASSERT(0 && "ImGui_ImplVulkan_CreateDeviceObjects() failed!"); // <- Can't be hit yet.
// Our render function expect RendererUserData to be storing the window render buffer we need (for the main viewport we won't use ->Window) // Our render function expect RendererUserData to be storing the window render buffer we need (for the main viewport we won't use ->Window)
ImGuiViewport* main_viewport = ImGui::GetMainViewport(); ImGuiViewport* main_viewport = ImGui::GetMainViewport();
@ -1227,6 +1287,9 @@ void ImGui_ImplVulkan_Shutdown()
// First destroy objects in all viewports // First destroy objects in all viewports
ImGui_ImplVulkan_DestroyDeviceObjects(); ImGui_ImplVulkan_DestroyDeviceObjects();
#ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING
IM_FREE((void*)const_cast<VkFormat*>(bd->VulkanInitInfo.PipelineRenderingCreateInfo.pColorAttachmentFormats));
#endif
// Manually delete main viewport render data in-case we haven't initialized for viewports // Manually delete main viewport render data in-case we haven't initialized for viewports
ImGuiViewport* main_viewport = ImGui::GetMainViewport(); ImGuiViewport* main_viewport = ImGui::GetMainViewport();
@ -1239,7 +1302,7 @@ void ImGui_ImplVulkan_Shutdown()
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasViewports); io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasViewports);
IM_DELETE(bd); IM_DELETE(bd);
} }
@ -1247,9 +1310,7 @@ void ImGui_ImplVulkan_NewFrame()
{ {
ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData(); ImGui_ImplVulkan_Data* bd = ImGui_ImplVulkan_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplVulkan_Init()?"); IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplVulkan_Init()?");
IM_UNUSED(bd);
if (!bd->FontTexture.DescriptorSet)
ImGui_ImplVulkan_CreateFontsTexture();
} }
void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count) void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count)
@ -1694,6 +1755,82 @@ void ImGui_ImplVulkanH_CreateOrResizeWindow(VkInstance instance, VkPhysicalDevic
ImGui_ImplVulkanH_CreateWindowSwapChain(physical_device, device, wd, allocator, width, height, min_image_count); ImGui_ImplVulkanH_CreateWindowSwapChain(physical_device, device, wd, allocator, width, height, min_image_count);
//ImGui_ImplVulkan_CreatePipeline(device, allocator, VK_NULL_HANDLE, wd->RenderPass, VK_SAMPLE_COUNT_1_BIT, &wd->Pipeline, g_VulkanInitInfo.Subpass); //ImGui_ImplVulkan_CreatePipeline(device, allocator, VK_NULL_HANDLE, wd->RenderPass, VK_SAMPLE_COUNT_1_BIT, &wd->Pipeline, g_VulkanInitInfo.Subpass);
ImGui_ImplVulkanH_CreateWindowCommandBuffers(physical_device, device, wd, queue_family, allocator); ImGui_ImplVulkanH_CreateWindowCommandBuffers(physical_device, device, wd, queue_family, allocator);
// FIXME: to submit the command buffer, we need a queue. In the examples folder, the ImGui_ImplVulkanH_CreateOrResizeWindow function is called
// before the ImGui_ImplVulkan_Init function, so we don't have access to the queue yet. Here we have the queue_family that we can use to grab
// a queue from the device and submit the command buffer. It would be better to have access to the queue as suggested in the FIXME below.
VkCommandPool command_pool;
VkCommandPoolCreateInfo pool_info = {};
pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
pool_info.queueFamilyIndex = queue_family;
pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
VkResult err = vkCreateCommandPool(device, &pool_info, allocator, &command_pool);
check_vk_result(err);
VkFenceCreateInfo fence_info = {};
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
VkFence fence;
err = vkCreateFence(device, &fence_info, allocator, &fence);
check_vk_result(err);
VkCommandBufferAllocateInfo alloc_info = {};
alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
alloc_info.commandPool = command_pool;
alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
alloc_info.commandBufferCount = 1;
VkCommandBuffer command_buffer;
err = vkAllocateCommandBuffers(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);
// Transition the images to the correct layout for rendering
for (uint32_t i = 0; i < wd->ImageCount; i++)
{
VkImageMemoryBarrier barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.image = wd->Frames[i].Backbuffer;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
}
err = vkEndCommandBuffer(command_buffer);
check_vk_result(err);
VkSubmitInfo submit_info = {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &command_buffer;
VkQueue queue;
vkGetDeviceQueue(device, queue_family, 0, &queue);
err = vkQueueSubmit(queue, 1, &submit_info, fence);
check_vk_result(err);
err = vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
check_vk_result(err);
err = vkResetFences(device, 1, &fence);
check_vk_result(err);
err = vkResetCommandPool(device, command_pool, 0);
check_vk_result(err);
// Destroy command buffer and fence and command pool
vkFreeCommandBuffers(device, command_pool, 1, &command_buffer);
vkDestroyCommandPool(device, command_pool, allocator);
vkDestroyFence(device, fence, allocator);
command_pool = VK_NULL_HANDLE;
command_buffer = VK_NULL_HANDLE;
fence = VK_NULL_HANDLE;
queue = VK_NULL_HANDLE;
} }
void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator) void ImGui_ImplVulkanH_DestroyWindow(VkInstance instance, VkDevice device, ImGui_ImplVulkanH_Window* wd, const VkAllocationCallbacks* allocator)
@ -1883,13 +2020,13 @@ static void ImGui_ImplVulkan_RenderWindow(ImGuiViewport* viewport, void*)
VkImageMemoryBarrier barrier = {}; VkImageMemoryBarrier barrier = {};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; barrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.image = fd->Backbuffer; barrier.image = fd->Backbuffer;
barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier.subresourceRange.levelCount = 1; barrier.subresourceRange.levelCount = 1;
barrier.subresourceRange.layerCount = 1; barrier.subresourceRange.layerCount = 1;
vkCmdPipelineBarrier(fd->CommandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); vkCmdPipelineBarrier(fd->CommandBuffer, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_NONE, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier);
VkRenderingAttachmentInfo attachmentInfo = {}; VkRenderingAttachmentInfo attachmentInfo = {};
attachmentInfo.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR; attachmentInfo.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO_KHR;

View file

@ -2,8 +2,9 @@
// This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..) // This needs to be used along with a Platform Backend (e.g. GLFW, SDL, Win32, custom..)
// Implemented features: // Implemented features:
// [!] Renderer: User texture binding. Use 'VkDescriptorSet' as ImTextureID. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID! See https://github.com/ocornut/imgui/pull/914 for discussions. // [!] Renderer: User texture binding. Use 'VkDescriptorSet' as texture identifier. Call ImGui_ImplVulkan_AddTexture() to register one. Read the FAQ about ImTextureID/ImTextureRef + https://github.com/ocornut/imgui/pull/914 for discussions.
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// [x] Renderer: Multi-viewport / platform windows. With issues (flickering when creating a new viewport). // [x] Renderer: Multi-viewport / platform windows. With issues (flickering when creating a new viewport).
@ -62,9 +63,8 @@
#define IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING #define IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING
#endif #endif
// Current version of the backend use 1 descriptor for the font atlas + as many as additional calls done to ImGui_ImplVulkan_AddTexture(). // Backend uses a small number of descriptors per font atlas + as many as additional calls done to ImGui_ImplVulkan_AddTexture().
// It is expected that as early as Q1 2025 the backend will use a few more descriptors. Use this value + number of desired calls to ImGui_ImplVulkan_AddTexture(). #define IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE (8) // Minimum per atlas
#define IMGUI_IMPL_VULKAN_MINIMUM_IMAGE_SAMPLER_POOL_SIZE (1) // Minimum per atlas
// Initialization data, for ImGui_ImplVulkan_Init() // Initialization data, for ImGui_ImplVulkan_Init()
// [Please zero-clear before use!] // [Please zero-clear before use!]
@ -113,10 +113,11 @@ IMGUI_IMPL_API bool ImGui_ImplVulkan_Init(ImGui_ImplVulkan_InitInfo*
IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown(); IMGUI_IMPL_API void ImGui_ImplVulkan_Shutdown();
IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame(); IMGUI_IMPL_API void ImGui_ImplVulkan_NewFrame();
IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline = VK_NULL_HANDLE); IMGUI_IMPL_API void ImGui_ImplVulkan_RenderDrawData(ImDrawData* draw_data, VkCommandBuffer command_buffer, VkPipeline pipeline = VK_NULL_HANDLE);
IMGUI_IMPL_API bool ImGui_ImplVulkan_CreateFontsTexture();
IMGUI_IMPL_API void ImGui_ImplVulkan_DestroyFontsTexture();
IMGUI_IMPL_API void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count); // To override MinImageCount after initialization (e.g. if swap chain is recreated) IMGUI_IMPL_API void ImGui_ImplVulkan_SetMinImageCount(uint32_t min_image_count); // To override MinImageCount after initialization (e.g. if swap chain is recreated)
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplVulkan_UpdateTexture(ImTextureData* tex);
// Register a texture (VkDescriptorSet == ImTextureID) // Register a texture (VkDescriptorSet == ImTextureID)
// FIXME: This is experimental in the sense that we are unsure how to best design/tackle this problem // FIXME: This is experimental in the sense that we are unsure how to best design/tackle this problem
// Please post to https://github.com/ocornut/imgui/pull/914 if you have suggestions. // Please post to https://github.com/ocornut/imgui/pull/914 if you have suggestions.

View file

@ -3,11 +3,12 @@
// (Please note that WebGPU is currently experimental, will not run on non-beta browsers, and may break.) // (Please note that WebGPU is currently experimental, will not run on non-beta browsers, and may break.)
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// Missing features: // [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures).
// [ ] Renderer: Multi-viewport support (multiple windows). Not meaningful on the web. // Missing features or Issues:
// [ ] Renderer: Multi-viewport support (multiple windows), useful for desktop.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
@ -19,6 +20,7 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-06-12: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. (#8465)
// 2025-02-26: Recreate image bind groups during render. (#8426, #8046, #7765, #8027) + Update for latest webgpu-native changes. // 2025-02-26: Recreate image bind groups during render. (#8426, #8046, #7765, #8027) + Update for latest webgpu-native changes.
// 2024-10-14: Update Dawn support for change of string usages. (#8082, #8083) // 2024-10-14: Update Dawn support for change of string usages. (#8082, #8083)
// 2024-10-07: Expose selected render state in ImGui_ImplWGPU_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks. // 2024-10-07: Expose selected render state in ImGui_ImplWGPU_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.
@ -73,11 +75,15 @@ extern ImGuiID ImHashData(const void* data_p, size_t data_size, ImU32 seed);
#define MEMALIGN(_SIZE,_ALIGN) (((_SIZE) + ((_ALIGN) - 1)) & ~((_ALIGN) - 1)) // Memory align (copied from IM_ALIGN() macro). #define MEMALIGN(_SIZE,_ALIGN) (((_SIZE) + ((_ALIGN) - 1)) & ~((_ALIGN) - 1)) // Memory align (copied from IM_ALIGN() macro).
// WebGPU data // WebGPU data
struct ImGui_ImplWGPU_Texture
{
WGPUTexture Texture = nullptr;
WGPUTextureView TextureView = nullptr;
};
struct RenderResources struct RenderResources
{ {
WGPUTexture FontTexture = nullptr; // Font texture WGPUSampler Sampler = nullptr; // Sampler for textures
WGPUTextureView FontTextureView = nullptr; // Texture view for font texture
WGPUSampler Sampler = nullptr; // Sampler for the font texture
WGPUBuffer Uniforms = nullptr; // Shader uniforms WGPUBuffer Uniforms = nullptr; // Shader uniforms
WGPUBindGroup CommonBindGroup = nullptr; // Resources bind-group to bind the common resources to pipeline WGPUBindGroup CommonBindGroup = nullptr; // Resources bind-group to bind the common resources to pipeline
ImGuiStorage ImageBindGroups; // Resources bind-group to bind the font/image resources to pipeline (this is a key->value map) ImGuiStorage ImageBindGroups; // Resources bind-group to bind the font/image resources to pipeline (this is a key->value map)
@ -234,23 +240,8 @@ static void SafeRelease(WGPUShaderModule& res)
wgpuShaderModuleRelease(res); wgpuShaderModuleRelease(res);
res = nullptr; res = nullptr;
} }
static void SafeRelease(WGPUTextureView& res)
{
if (res)
wgpuTextureViewRelease(res);
res = nullptr;
}
static void SafeRelease(WGPUTexture& res)
{
if (res)
wgpuTextureRelease(res);
res = nullptr;
}
static void SafeRelease(RenderResources& res) static void SafeRelease(RenderResources& res)
{ {
SafeRelease(res.FontTexture);
SafeRelease(res.FontTextureView);
SafeRelease(res.Sampler); SafeRelease(res.Sampler);
SafeRelease(res.Uniforms); SafeRelease(res.Uniforms);
SafeRelease(res.CommonBindGroup); SafeRelease(res.CommonBindGroup);
@ -381,6 +372,13 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder
if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdListsCount == 0) if (fb_width <= 0 || fb_height <= 0 || draw_data->CmdListsCount == 0)
return; return;
// Catch up with texture updates. Most of the times, the list will have 1 element with an OK status, aka nothing to do.
// (This almost always points to ImGui::GetPlatformIO().Textures[] but is part of ImDrawData to allow overriding or disabling texture updates).
if (draw_data->Textures != nullptr)
for (ImTextureData* tex : *draw_data->Textures)
if (tex->Status != ImTextureStatus_OK)
ImGui_ImplWGPU_UpdateTexture(tex);
// FIXME: Assuming that this only gets called once per frame! // FIXME: Assuming that this only gets called once per frame!
// If not, we can't just re-allocate the IB or VB, we'll have to do a proper allocator. // If not, we can't just re-allocate the IB or VB, we'll have to do a proper allocator.
ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData(); ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
@ -536,33 +534,52 @@ void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURenderPassEncoder
platform_io.Renderer_RenderState = nullptr; platform_io.Renderer_RenderState = nullptr;
} }
static void ImGui_ImplWGPU_CreateFontsTexture() static void ImGui_ImplWGPU_DestroyTexture(ImTextureData* tex)
{ {
// Build texture atlas ImGui_ImplWGPU_Texture* backend_tex = (ImGui_ImplWGPU_Texture*)tex->BackendUserData;
ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData(); if (backend_tex == nullptr)
ImGuiIO& io = ImGui::GetIO(); return;
unsigned char* pixels;
int width, height, size_pp;
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &size_pp);
// Upload texture to graphics system IM_ASSERT(backend_tex->TextureView == (WGPUTextureView)(intptr_t)tex->TexID);
wgpuTextureViewRelease(backend_tex->TextureView);
wgpuTextureRelease(backend_tex->Texture);
IM_DELETE(backend_tex);
// Clear identifiers and mark as destroyed (in order to allow e.g. calling InvalidateDeviceObjects while running)
tex->SetTexID(ImTextureID_Invalid);
tex->SetStatus(ImTextureStatus_Destroyed);
tex->BackendUserData = nullptr;
}
void ImGui_ImplWGPU_UpdateTexture(ImTextureData* tex)
{ {
ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
if (tex->Status == ImTextureStatus_WantCreate)
{
// Create and upload new texture to graphics system
//IMGUI_DEBUG_LOG("UpdateTexture #%03d: WantCreate %dx%d\n", tex->UniqueID, tex->Width, tex->Height);
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
ImGui_ImplWGPU_Texture* backend_tex = IM_NEW(ImGui_ImplWGPU_Texture)();
// Create texture
WGPUTextureDescriptor tex_desc = {}; WGPUTextureDescriptor tex_desc = {};
#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) #if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU)
tex_desc.label = { "Dear ImGui Font Texture", WGPU_STRLEN }; tex_desc.label = { "Dear ImGui Texture", WGPU_STRLEN };
#else #else
tex_desc.label = "Dear ImGui Font Texture"; tex_desc.label = "Dear ImGui Texture";
#endif #endif
tex_desc.dimension = WGPUTextureDimension_2D; tex_desc.dimension = WGPUTextureDimension_2D;
tex_desc.size.width = width; tex_desc.size.width = tex->Width;
tex_desc.size.height = height; tex_desc.size.height = tex->Height;
tex_desc.size.depthOrArrayLayers = 1; tex_desc.size.depthOrArrayLayers = 1;
tex_desc.sampleCount = 1; tex_desc.sampleCount = 1;
tex_desc.format = WGPUTextureFormat_RGBA8Unorm; tex_desc.format = WGPUTextureFormat_RGBA8Unorm;
tex_desc.mipLevelCount = 1; tex_desc.mipLevelCount = 1;
tex_desc.usage = WGPUTextureUsage_CopyDst | WGPUTextureUsage_TextureBinding; tex_desc.usage = WGPUTextureUsage_CopyDst | WGPUTextureUsage_TextureBinding;
bd->renderResources.FontTexture = wgpuDeviceCreateTexture(bd->wgpuDevice, &tex_desc); backend_tex->Texture = wgpuDeviceCreateTexture(bd->wgpuDevice, &tex_desc);
// Create texture view
WGPUTextureViewDescriptor tex_view_desc = {}; WGPUTextureViewDescriptor tex_view_desc = {};
tex_view_desc.format = WGPUTextureFormat_RGBA8Unorm; tex_view_desc.format = WGPUTextureFormat_RGBA8Unorm;
tex_view_desc.dimension = WGPUTextureViewDimension_2D; tex_view_desc.dimension = WGPUTextureViewDimension_2D;
@ -571,19 +588,35 @@ static void ImGui_ImplWGPU_CreateFontsTexture()
tex_view_desc.baseArrayLayer = 0; tex_view_desc.baseArrayLayer = 0;
tex_view_desc.arrayLayerCount = 1; tex_view_desc.arrayLayerCount = 1;
tex_view_desc.aspect = WGPUTextureAspect_All; tex_view_desc.aspect = WGPUTextureAspect_All;
bd->renderResources.FontTextureView = wgpuTextureCreateView(bd->renderResources.FontTexture, &tex_view_desc); backend_tex->TextureView = wgpuTextureCreateView(backend_tex->Texture, &tex_view_desc);
// Store identifiers
tex->SetTexID((ImTextureID)(intptr_t)backend_tex->TextureView);
tex->BackendUserData = backend_tex;
// We don't set tex->Status to ImTextureStatus_OK to let the code fallthrough below.
} }
// Upload texture data if (tex->Status == ImTextureStatus_WantCreate || tex->Status == ImTextureStatus_WantUpdates)
{ {
ImGui_ImplWGPU_Texture* backend_tex = (ImGui_ImplWGPU_Texture*)tex->BackendUserData;
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
// We could use the smaller rect on _WantCreate but using the full rect allows us to clear the texture.
const int upload_x = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.x;
const int upload_y = (tex->Status == ImTextureStatus_WantCreate) ? 0 : tex->UpdateRect.y;
const int upload_w = (tex->Status == ImTextureStatus_WantCreate) ? tex->Width : tex->UpdateRect.w;
const int upload_h = (tex->Status == ImTextureStatus_WantCreate) ? tex->Height : tex->UpdateRect.h;
// Update full texture or selected blocks. We only ever write to textures regions which have never been used before!
// This backend choose to use tex->UpdateRect but you can use tex->Updates[] to upload individual regions.
#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) #if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU)
WGPUTexelCopyTextureInfo dst_view = {}; WGPUTexelCopyTextureInfo dst_view = {};
#else #else
WGPUImageCopyTexture dst_view = {}; WGPUImageCopyTexture dst_view = {};
#endif #endif
dst_view.texture = bd->renderResources.FontTexture; dst_view.texture = backend_tex->Texture;
dst_view.mipLevel = 0; dst_view.mipLevel = 0;
dst_view.origin = { 0, 0, 0 }; dst_view.origin = { (uint32_t)upload_x, (uint32_t)upload_y, 0 };
dst_view.aspect = WGPUTextureAspect_All; dst_view.aspect = WGPUTextureAspect_All;
#if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU) #if defined(IMGUI_IMPL_WEBGPU_BACKEND_DAWN) || defined(IMGUI_IMPL_WEBGPU_BACKEND_WGPU)
WGPUTexelCopyBufferLayout layout = {}; WGPUTexelCopyBufferLayout layout = {};
@ -591,29 +624,14 @@ static void ImGui_ImplWGPU_CreateFontsTexture()
WGPUTextureDataLayout layout = {}; WGPUTextureDataLayout layout = {};
#endif #endif
layout.offset = 0; layout.offset = 0;
layout.bytesPerRow = width * size_pp; layout.bytesPerRow = tex->Width * tex->BytesPerPixel;
layout.rowsPerImage = height; layout.rowsPerImage = upload_h;
WGPUExtent3D size = { (uint32_t)width, (uint32_t)height, 1 }; WGPUExtent3D write_size = { (uint32_t)upload_w, (uint32_t)upload_h, 1 };
wgpuQueueWriteTexture(bd->defaultQueue, &dst_view, pixels, (uint32_t)(width * size_pp * height), &layout, &size); wgpuQueueWriteTexture(bd->defaultQueue, &dst_view, tex->GetPixelsAt(upload_x, upload_y), (uint32_t)(tex->Width * upload_h * tex->BytesPerPixel), &layout, &write_size);
tex->SetStatus(ImTextureStatus_OK);
} }
if (tex->Status == ImTextureStatus_WantDestroy && tex->UnusedFrames > 0)
// Create the associated sampler ImGui_ImplWGPU_DestroyTexture(tex);
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
{
WGPUSamplerDescriptor sampler_desc = {};
sampler_desc.minFilter = WGPUFilterMode_Linear;
sampler_desc.magFilter = WGPUFilterMode_Linear;
sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Linear;
sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge;
sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge;
sampler_desc.addressModeW = WGPUAddressMode_ClampToEdge;
sampler_desc.maxAnisotropy = 1;
bd->renderResources.Sampler = wgpuDeviceCreateSampler(bd->wgpuDevice, &sampler_desc);
}
// Store our identifier
static_assert(sizeof(ImTextureID) >= sizeof(bd->renderResources.FontTexture), "Can't pack descriptor handle into TexID, 32-bit not supported yet.");
io.Fonts->SetTexID((ImTextureID)bd->renderResources.FontTextureView);
} }
static void ImGui_ImplWGPU_CreateUniformBuffer() static void ImGui_ImplWGPU_CreateUniformBuffer()
@ -757,16 +775,26 @@ bool ImGui_ImplWGPU_CreateDeviceObjects()
bd->pipelineState = wgpuDeviceCreateRenderPipeline(bd->wgpuDevice, &graphics_pipeline_desc); bd->pipelineState = wgpuDeviceCreateRenderPipeline(bd->wgpuDevice, &graphics_pipeline_desc);
ImGui_ImplWGPU_CreateFontsTexture();
ImGui_ImplWGPU_CreateUniformBuffer(); ImGui_ImplWGPU_CreateUniformBuffer();
// Create sampler
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
WGPUSamplerDescriptor sampler_desc = {};
sampler_desc.minFilter = WGPUFilterMode_Linear;
sampler_desc.magFilter = WGPUFilterMode_Linear;
sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Linear;
sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge;
sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge;
sampler_desc.addressModeW = WGPUAddressMode_ClampToEdge;
sampler_desc.maxAnisotropy = 1;
bd->renderResources.Sampler = wgpuDeviceCreateSampler(bd->wgpuDevice, &sampler_desc);
// Create resource bind group // Create resource bind group
WGPUBindGroupEntry common_bg_entries[] = WGPUBindGroupEntry common_bg_entries[] =
{ {
{ nullptr, 0, bd->renderResources.Uniforms, 0, MEMALIGN(sizeof(Uniforms), 16), 0, 0 }, { nullptr, 0, bd->renderResources.Uniforms, 0, MEMALIGN(sizeof(Uniforms), 16), 0, 0 },
{ nullptr, 1, 0, 0, 0, bd->renderResources.Sampler, 0 }, { nullptr, 1, 0, 0, 0, bd->renderResources.Sampler, 0 },
}; };
WGPUBindGroupDescriptor common_bg_descriptor = {}; WGPUBindGroupDescriptor common_bg_descriptor = {};
common_bg_descriptor.layout = bg_layouts[0]; common_bg_descriptor.layout = bg_layouts[0];
common_bg_descriptor.entryCount = sizeof(common_bg_entries) / sizeof(WGPUBindGroupEntry); common_bg_descriptor.entryCount = sizeof(common_bg_entries) / sizeof(WGPUBindGroupEntry);
@ -791,8 +819,10 @@ void ImGui_ImplWGPU_InvalidateDeviceObjects()
SafeRelease(bd->pipelineState); SafeRelease(bd->pipelineState);
SafeRelease(bd->renderResources); SafeRelease(bd->renderResources);
ImGuiIO& io = ImGui::GetIO(); // Destroy all textures
io.Fonts->SetTexID(0); // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well. for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
if (tex->RefCount == 1)
ImGui_ImplWGPU_DestroyTexture(tex);
for (unsigned int i = 0; i < bd->numFramesInFlight; i++) for (unsigned int i = 0; i < bd->numFramesInFlight; i++)
SafeRelease(bd->pFrameResources[i]); SafeRelease(bd->pFrameResources[i]);
@ -817,6 +847,7 @@ bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info)
io.BackendRendererName = "imgui_impl_webgpu"; io.BackendRendererName = "imgui_impl_webgpu";
#endif #endif
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures; // We can honor ImGuiPlatformIO::Textures[] requests during render.
bd->initInfo = *init_info; bd->initInfo = *init_info;
bd->wgpuDevice = init_info->Device; bd->wgpuDevice = init_info->Device;
@ -826,8 +857,6 @@ bool ImGui_ImplWGPU_Init(ImGui_ImplWGPU_InitInfo* init_info)
bd->numFramesInFlight = init_info->NumFramesInFlight; bd->numFramesInFlight = init_info->NumFramesInFlight;
bd->frameIndex = UINT_MAX; bd->frameIndex = UINT_MAX;
bd->renderResources.FontTexture = nullptr;
bd->renderResources.FontTextureView = nullptr;
bd->renderResources.Sampler = nullptr; bd->renderResources.Sampler = nullptr;
bd->renderResources.Uniforms = nullptr; bd->renderResources.Uniforms = nullptr;
bd->renderResources.CommonBindGroup = nullptr; bd->renderResources.CommonBindGroup = nullptr;
@ -866,7 +895,7 @@ void ImGui_ImplWGPU_Shutdown()
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasVtxOffset; io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasVtxOffset | ImGuiBackendFlags_RendererHasTextures);
IM_DELETE(bd); IM_DELETE(bd);
} }
@ -874,7 +903,8 @@ void ImGui_ImplWGPU_NewFrame()
{ {
ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData(); ImGui_ImplWGPU_Data* bd = ImGui_ImplWGPU_GetBackendData();
if (!bd->pipelineState) if (!bd->pipelineState)
ImGui_ImplWGPU_CreateDeviceObjects(); if (!ImGui_ImplWGPU_CreateDeviceObjects())
IM_ASSERT(0 && "ImGui_ImplWGPU_CreateDeviceObjects() failed!");
} }
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------

View file

@ -10,11 +10,12 @@
//#define IMGUI_IMPL_WEBGPU_BACKEND_WGPU //#define IMGUI_IMPL_WEBGPU_BACKEND_WGPU
// Implemented features: // Implemented features:
// [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID! // [X] Renderer: User texture binding. Use 'WGPUTextureView' as ImTextureID. Read the FAQ about ImTextureID/ImTextureRef!
// [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset). // [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'. // [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
// Missing features: // [X] Renderer: Texture updates support for dynamic font system (ImGuiBackendFlags_RendererHasTextures).
// [ ] Renderer: Multi-viewport support (multiple windows). Not meaningful on the web. // Missing features or Issues:
// [ ] Renderer: Multi-viewport support (multiple windows), useful for desktop.
// You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this. // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need. // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
@ -57,6 +58,9 @@ IMGUI_IMPL_API void ImGui_ImplWGPU_RenderDrawData(ImDrawData* draw_data, WGPURen
IMGUI_IMPL_API bool ImGui_ImplWGPU_CreateDeviceObjects(); IMGUI_IMPL_API bool ImGui_ImplWGPU_CreateDeviceObjects();
IMGUI_IMPL_API void ImGui_ImplWGPU_InvalidateDeviceObjects(); IMGUI_IMPL_API void ImGui_ImplWGPU_InvalidateDeviceObjects();
// (Advanced) Use e.g. if you need to precisely control the timing of texture updates (e.g. for staged rendering), by setting ImDrawData::Textures = NULL to handle this manually.
IMGUI_IMPL_API void ImGui_ImplWGPU_UpdateTexture(ImTextureData* tex);
// [BETA] Selected render state data shared with callbacks. // [BETA] Selected render state data shared with callbacks.
// This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplWGPU_RenderDrawData() call. // This is temporarily stored in GetPlatformIO().Renderer_RenderState during the ImGui_ImplWGPU_RenderDrawData() call.
// (Please open an issue if you feel you need access to more data) // (Please open an issue if you feel you need access to more data)

View file

@ -5,7 +5,7 @@
// [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui) // [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui)
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen. // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Gamepad support.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.
@ -23,6 +23,9 @@
// CHANGELOG // CHANGELOG
// (minor and older changes stripped away, please see git history for details) // (minor and older changes stripped away, please see git history for details)
// 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface. // 2025-XX-XX: Platform: Added support for multiple windows via the ImGuiPlatformIO interface.
// 2025-06-02: [Docking] WM_DPICHANGED also apply io.ConfigDpiScaleViewports for main viewport instead of letting it be done by application code.
// 2025-04-30: Inputs: Fixed an issue where externally losing mouse capture (due to e.g. focus loss) would fail to claim it again the next subsequent click. (#8594)
// 2025-03-26: [Docking] Viewports: fixed an issue when closing a window from the OS close button (with io.ConfigViewportsNoDecoration = false) while user code was discarding the 'bool* p_open = false' output from Begin(). Because we allowed the Win32 window to close early, Windows destroyed it and our imgui window became not visible even though user code was still submitting it.
// 2025-03-10: When dealing with OEM keys, use scancodes instead of translated keycodes to choose ImGuiKey values. (#7136, #7201, #7206, #7306, #7670, #7672, #8468) // 2025-03-10: When dealing with OEM keys, use scancodes instead of translated keycodes to choose ImGuiKey values. (#7136, #7201, #7206, #7306, #7670, #7672, #8468)
// 2025-02-21: [Docking] WM_SETTINGCHANGE's SPI_SETWORKAREA message also triggers a refresh of monitor list. (#8415) // 2025-02-21: [Docking] WM_SETTINGCHANGE's SPI_SETWORKAREA message also triggers a refresh of monitor list. (#8415)
// 2025-02-18: Added ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress mouse cursor support. // 2025-02-18: Added ImGuiMouseCursor_Wait and ImGuiMouseCursor_Progress mouse cursor support.
@ -396,8 +399,6 @@ static void ImGui_ImplWin32_UpdateGamepads(ImGuiIO& io)
{ {
#ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD #ifndef IMGUI_IMPL_WIN32_DISABLE_GAMEPAD
ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io); ImGui_ImplWin32_Data* bd = ImGui_ImplWin32_GetBackendData(io);
//if ((io.ConfigFlags & ImGuiConfigFlags_NavEnableGamepad) == 0) // FIXME: Technically feeding gamepad shouldn't depend on this now that they are regular inputs.
// return;
// Calling XInputGetState() every frame on disconnected gamepads is unfortunately too slow. // Calling XInputGetState() every frame on disconnected gamepads is unfortunately too slow.
// Instead we refresh gamepad availability by calling XInputGetCapabilities() _only_ after receiving WM_DEVICECHANGE. // Instead we refresh gamepad availability by calling XInputGetCapabilities() _only_ after receiving WM_DEVICECHANGE.
@ -667,6 +668,7 @@ ImGuiKey ImGui_ImplWin32_KeyEventToImGuiKey(WPARAM wParam, LPARAM lParam)
case 51: return ImGuiKey_Comma; case 51: return ImGuiKey_Comma;
case 52: return ImGuiKey_Period; case 52: return ImGuiKey_Period;
case 53: return ImGuiKey_Slash; case 53: return ImGuiKey_Slash;
default: break;
} }
return ImGuiKey_None; return ImGuiKey_None;
@ -705,6 +707,10 @@ static ImGuiMouseSource ImGui_ImplWin32_GetMouseSourceFromMessageExtraInfo()
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Use ImGui::GetCurrentContext() extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); // Use ImGui::GetCurrentContext()
extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ImGuiIO& io); // Doesn't use ImGui::GetCurrentContext() extern IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam, ImGuiIO& io); // Doesn't use ImGui::GetCurrentContext()
#ifndef WM_DPICHANGED
#define WM_DPICHANGED 0x02E0 // From Windows SDK 8.1+ headers
#endif
IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandler(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{ {
// Most backends don't have silent checks like this one, but we need it because WndProc are called early in CreateWindow(). // Most backends don't have silent checks like this one, but we need it because WndProc are called early in CreateWindow().
@ -782,7 +788,10 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPA
if (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONDBLCLK) { button = 1; } if (msg == WM_RBUTTONDOWN || msg == WM_RBUTTONDBLCLK) { button = 1; }
if (msg == WM_MBUTTONDOWN || msg == WM_MBUTTONDBLCLK) { button = 2; } if (msg == WM_MBUTTONDOWN || msg == WM_MBUTTONDBLCLK) { button = 2; }
if (msg == WM_XBUTTONDOWN || msg == WM_XBUTTONDBLCLK) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; } if (msg == WM_XBUTTONDOWN || msg == WM_XBUTTONDBLCLK) { button = (GET_XBUTTON_WPARAM(wParam) == XBUTTON1) ? 3 : 4; }
if (bd->MouseButtonsDown == 0 && ::GetCapture() == nullptr) HWND hwnd_with_capture = ::GetCapture();
if (bd->MouseButtonsDown != 0 && hwnd_with_capture != hwnd) // Did we externally lost capture?
bd->MouseButtonsDown = 0;
if (bd->MouseButtonsDown == 0 && hwnd_with_capture == nullptr)
::SetCapture(hwnd); // Allow us to read mouse coordinates when dragging mouse outside of our window bounds. ::SetCapture(hwnd); // Allow us to read mouse coordinates when dragging mouse outside of our window bounds.
bd->MouseButtonsDown |= 1 << button; bd->MouseButtonsDown |= 1 << button;
io.AddMouseSourceEvent(mouse_source); io.AddMouseSourceEvent(mouse_source);
@ -896,6 +905,13 @@ IMGUI_IMPL_API LRESULT ImGui_ImplWin32_WndProcHandlerEx(HWND hwnd, UINT msg, WPA
if (wParam == SPI_SETWORKAREA) if (wParam == SPI_SETWORKAREA)
bd->WantUpdateMonitors = true; bd->WantUpdateMonitors = true;
return 0; return 0;
case WM_DPICHANGED:
{
const RECT* suggested_rect = (RECT*)lParam;
if (io.ConfigDpiScaleViewports)
::SetWindowPos(hwnd, nullptr, suggested_rect->left, suggested_rect->top, suggested_rect->right - suggested_rect->left, suggested_rect->bottom - suggested_rect->top, SWP_NOZORDER | SWP_NOACTIVATE);
return 0;
}
} }
return 0; return 0;
} }
@ -1357,14 +1373,14 @@ static LRESULT CALLBACK ImGui_ImplWin32_WndProcHandler_PlatformWindow(HWND hWnd,
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(ctx); ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO(ctx);
LRESULT result = 0; LRESULT result = 0;
if (ImGui_ImplWin32_WndProcHandlerEx(hWnd, msg, wParam, lParam, io)) if (ImGui_ImplWin32_WndProcHandlerEx(hWnd, msg, wParam, lParam, io))
result = true; result = 1;
else if (ImGuiViewport* viewport = ImGui_ImplWin32_FindViewportByPlatformHandle(platform_io, hWnd)) else if (ImGuiViewport* viewport = ImGui_ImplWin32_FindViewportByPlatformHandle(platform_io, hWnd))
{ {
switch (msg) switch (msg)
{ {
case WM_CLOSE: case WM_CLOSE:
viewport->PlatformRequestClose = true; viewport->PlatformRequestClose = true;
break; return 0; // 0 = Operating system will ignore the message and not destroy the window. We close ourselves.
case WM_MOVE: case WM_MOVE:
viewport->PlatformRequestMove = true; viewport->PlatformRequestMove = true;
break; break;

View file

@ -5,7 +5,7 @@
// [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui) // [X] Platform: Clipboard support (for Win32 this is actually part of core dear imgui)
// [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen. // [X] Platform: Mouse support. Can discriminate Mouse/TouchScreen/Pen.
// [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values are obsolete since 1.87 and not supported since 1.91.5] // [X] Platform: Keyboard support. Since 1.87 we are using the io.AddKeyEvent() function. Pass ImGuiKey values to all key functions e.g. ImGui::IsKeyPressed(ImGuiKey_Space). [Legacy VK_* values are obsolete since 1.87 and not supported since 1.91.5]
// [X] Platform: Gamepad support. Enabled with 'io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad'. // [X] Platform: Gamepad support.
// [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'. // [X] Platform: Mouse cursor shape and visibility (ImGuiBackendFlags_HasMouseCursors). Disable with 'io.ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange'.
// [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'. // [X] Platform: Multi-viewport support (multiple windows). Enable with 'io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable'.

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,4 +1,4 @@
// dear imgui, v1.91.9b // dear imgui, v1.92.0
// (internal structures/api) // (internal structures/api)
// You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility. // You may use this file to debug, understand or extend Dear ImGui features but we don't provide any guarantee of forward compatibility.
@ -37,6 +37,7 @@ Index of this file:
// [SECTION] Tab bar, Tab item support // [SECTION] Tab bar, Tab item support
// [SECTION] Table support // [SECTION] Table support
// [SECTION] ImGui internal API // [SECTION] ImGui internal API
// [SECTION] ImFontLoader
// [SECTION] ImFontAtlas internal API // [SECTION] ImFontAtlas internal API
// [SECTION] Test Engine specific hooks (imgui_test_engine) // [SECTION] Test Engine specific hooks (imgui_test_engine)
@ -132,7 +133,7 @@ Index of this file:
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Utilities // Utilities
// (other types which are not forwarded declared are: ImBitArray<>, ImSpan<>, ImSpanAllocator<>, ImPool<>, ImChunkStream<>) // (other types which are not forwarded declared are: ImBitArray<>, ImSpan<>, ImSpanAllocator<>, ImStableVector<>, ImPool<>, ImChunkStream<>)
struct ImBitVector; // Store 1-bit per value struct ImBitVector; // Store 1-bit per value
struct ImRect; // An axis-aligned rectangle (2 points) struct ImRect; // An axis-aligned rectangle (2 points)
struct ImGuiTextIndex; // Maintain a line index for a text buffer. struct ImGuiTextIndex; // Maintain a line index for a text buffer.
@ -140,6 +141,9 @@ struct ImGuiTextIndex; // Maintain a line index for a text buffer.
// ImDrawList/ImFontAtlas // ImDrawList/ImFontAtlas
struct ImDrawDataBuilder; // Helper to build a ImDrawData instance struct ImDrawDataBuilder; // Helper to build a ImDrawData instance
struct ImDrawListSharedData; // Data shared between all ImDrawList instances struct ImDrawListSharedData; // Data shared between all ImDrawList instances
struct ImFontAtlasBuilder; // Internal storage for incrementally packing and building a ImFontAtlas
struct ImFontAtlasPostProcessData; // Data available to potential texture post-processing functions
struct ImFontAtlasRectEntry; // Packed rectangle lookup entry
// ImGui // ImGui
struct ImGuiBoxSelectState; // Box-selection state (currently used by multi-selection, could potentially be used by others) struct ImGuiBoxSelectState; // Box-selection state (currently used by multi-selection, could potentially be used by others)
@ -212,6 +216,10 @@ typedef int ImGuiTooltipFlags; // -> enum ImGuiTooltipFlags_ // F
typedef int ImGuiTypingSelectFlags; // -> enum ImGuiTypingSelectFlags_ // Flags: for GetTypingSelectRequest() typedef int ImGuiTypingSelectFlags; // -> enum ImGuiTypingSelectFlags_ // Flags: for GetTypingSelectRequest()
typedef int ImGuiWindowRefreshFlags; // -> enum ImGuiWindowRefreshFlags_ // Flags: for SetNextWindowRefreshPolicy() typedef int ImGuiWindowRefreshFlags; // -> enum ImGuiWindowRefreshFlags_ // Flags: for SetNextWindowRefreshPolicy()
// Table column indexing
typedef ImS16 ImGuiTableColumnIdx;
typedef ImU16 ImGuiTableDrawChannelIdx;
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] Context pointer // [SECTION] Context pointer
// See implementation of this variable in imgui.cpp for comments and details. // See implementation of this variable in imgui.cpp for comments and details.
@ -247,7 +255,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer
#define IMGUI_DEBUG_LOG_SELECTION(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_SELECTION(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventSelection) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)
#define IMGUI_DEBUG_LOG_CLIPPER(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventClipper) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_CLIPPER(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventClipper) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)
#define IMGUI_DEBUG_LOG_IO(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_IO(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventIO) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)
#define IMGUI_DEBUG_LOG_FONT(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_FONT(...) do { ImGuiContext* g2 = GImGui; if (g2 && g2->DebugLogFlags & ImGuiDebugLogFlags_EventFont) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) // Called from ImFontAtlas function which may operate without a context.
#define IMGUI_DEBUG_LOG_INPUTROUTING(...) do{if (g.DebugLogFlags & ImGuiDebugLogFlags_EventInputRouting)IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_INPUTROUTING(...) do{if (g.DebugLogFlags & ImGuiDebugLogFlags_EventInputRouting)IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)
#define IMGUI_DEBUG_LOG_DOCKING(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventDocking) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_DOCKING(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventDocking) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)
#define IMGUI_DEBUG_LOG_VIEWPORT(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventViewport) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0) #define IMGUI_DEBUG_LOG_VIEWPORT(...) do { if (g.DebugLogFlags & ImGuiDebugLogFlags_EventViewport) IMGUI_DEBUG_LOG(__VA_ARGS__); } while (0)
@ -359,6 +367,7 @@ extern IMGUI_API ImGuiContext* GImGui; // Current implicit context pointer
// - Helper: ImBitArray // - Helper: ImBitArray
// - Helper: ImBitVector // - Helper: ImBitVector
// - Helper: ImSpan<>, ImSpanAllocator<> // - Helper: ImSpan<>, ImSpanAllocator<>
// - Helper: ImStableVector<>
// - Helper: ImPool<> // - Helper: ImPool<>
// - Helper: ImChunkStream<> // - Helper: ImChunkStream<>
// - Helper: ImGuiTextIndex // - Helper: ImGuiTextIndex
@ -390,6 +399,7 @@ IMGUI_API int ImStricmp(const char* str1, const char* str2);
IMGUI_API int ImStrnicmp(const char* str1, const char* str2, size_t count); // Case insensitive compare to a certain count. IMGUI_API int ImStrnicmp(const char* str1, const char* str2, size_t count); // Case insensitive compare to a certain count.
IMGUI_API void ImStrncpy(char* dst, const char* src, size_t count); // Copy to a certain count and always zero terminate (strncpy doesn't). IMGUI_API void ImStrncpy(char* dst, const char* src, size_t count); // Copy to a certain count and always zero terminate (strncpy doesn't).
IMGUI_API char* ImStrdup(const char* str); // Duplicate a string. IMGUI_API char* ImStrdup(const char* str); // Duplicate a string.
IMGUI_API void* ImMemdup(const void* src, size_t size); // Duplicate a chunk of memory.
IMGUI_API char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* str); // Copy in provided buffer, recreate buffer if needed. IMGUI_API char* ImStrdupcpy(char* dst, size_t* p_dst_size, const char* str); // Copy in provided buffer, recreate buffer if needed.
IMGUI_API const char* ImStrchrRange(const char* str_begin, const char* str_end, char c); // Find first occurrence of 'c' in string range. IMGUI_API const char* ImStrchrRange(const char* str_begin, const char* str_end, char c); // Find first occurrence of 'c' in string range.
IMGUI_API const char* ImStreolRange(const char* str, const char* str_end); // End end-of-line IMGUI_API const char* ImStreolRange(const char* str, const char* str_end); // End end-of-line
@ -503,6 +513,8 @@ static inline float ImTrunc(float f)
static inline ImVec2 ImTrunc(const ImVec2& v) { return ImVec2((float)(int)(v.x), (float)(int)(v.y)); } static inline ImVec2 ImTrunc(const ImVec2& v) { return ImVec2((float)(int)(v.x), (float)(int)(v.y)); }
static inline float ImFloor(float f) { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf() static inline float ImFloor(float f) { return (float)((f >= 0 || (float)(int)f == f) ? (int)f : (int)f - 1); } // Decent replacement for floorf()
static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); } static inline ImVec2 ImFloor(const ImVec2& v) { return ImVec2(ImFloor(v.x), ImFloor(v.y)); }
static inline float ImTrunc64(float f) { return (float)(ImS64)(f); }
static inline float ImRound64(float f) { return (float)(ImS64)(f + 0.5f); }
static inline int ImModPositive(int a, int b) { return (a + b) % b; } static inline int ImModPositive(int a, int b) { return (a + b) % b; }
static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; } static inline float ImDot(const ImVec2& a, const ImVec2& b) { return a.x * b.x + a.y * b.y; }
static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); } static inline ImVec2 ImRotate(const ImVec2& v, float cos_a, float sin_a) { return ImVec2(v.x * cos_a - v.y * sin_a, v.x * sin_a + v.y * cos_a); }
@ -535,6 +547,14 @@ struct ImVec1
constexpr ImVec1(float _x) : x(_x) { } constexpr ImVec1(float _x) : x(_x) { }
}; };
// Helper: ImVec2i (2D vector, integer)
struct ImVec2i
{
int x, y;
constexpr ImVec2i() : x(0), y(0) {}
constexpr ImVec2i(int _x, int _y) : x(_x), y(_y) {}
};
// Helper: ImVec2ih (2D vector, half-size integer, for long-term packed storage) // Helper: ImVec2ih (2D vector, half-size integer, for long-term packed storage)
struct ImVec2ih struct ImVec2ih
{ {
@ -686,6 +706,39 @@ struct ImSpanAllocator
inline void GetSpan(int n, ImSpan<T>* span) { span->set((T*)GetSpanPtrBegin(n), (T*)GetSpanPtrEnd(n)); } inline void GetSpan(int n, ImSpan<T>* span) { span->set((T*)GetSpanPtrBegin(n), (T*)GetSpanPtrEnd(n)); }
}; };
// Helper: ImStableVector<>
// Allocating chunks of BLOCK_SIZE items. Objects pointers are never invalidated when growing, only by clear().
// Important: does not destruct anything!
// Implemented only the minimum set of functions we need for it.
template<typename T, int BLOCK_SIZE>
struct ImStableVector
{
int Size = 0;
int Capacity = 0;
ImVector<T*> Blocks;
// Functions
inline ~ImStableVector() { for (T* block : Blocks) IM_FREE(block); }
inline void clear() { Size = Capacity = 0; Blocks.clear_delete(); }
inline void resize(int new_size) { if (new_size > Capacity) reserve(new_size); Size = new_size; }
inline void reserve(int new_cap)
{
new_cap = IM_MEMALIGN(new_cap, BLOCK_SIZE);
int old_count = Capacity / BLOCK_SIZE;
int new_count = new_cap / BLOCK_SIZE;
if (new_count <= old_count)
return;
Blocks.resize(new_count);
for (int n = old_count; n < new_count; n++)
Blocks[n] = (T*)IM_ALLOC(sizeof(T) * BLOCK_SIZE);
Capacity = new_cap;
}
inline T& operator[](int i) { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; }
inline const T& operator[](int i) const { IM_ASSERT(i >= 0 && i < Size); return Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; }
inline T* push_back(const T& v) { int i = Size; IM_ASSERT(i >= 0); if (Size == Capacity) reserve(Capacity + BLOCK_SIZE); void* ptr = &Blocks[i / BLOCK_SIZE][i % BLOCK_SIZE]; memcpy(ptr, &v, sizeof(v)); Size++; return (T*)ptr; }
};
// Helper: ImPool<> // Helper: ImPool<>
// Basic keyed storage for contiguous instances, slow/amortized insertion, O(1) indexable, O(Log N) queries by ID over a dense/hot buffer, // Basic keyed storage for contiguous instances, slow/amortized insertion, O(1) indexable, O(Log N) queries by ID over a dense/hot buffer,
// Honor constructor/destructor. Add/remove invalidate all pointers. Indexes have the same lifetime as the associated object. // Honor constructor/destructor. Add/remove invalidate all pointers. Indexes have the same lifetime as the associated object.
@ -794,17 +847,20 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag
// You may want to create your own instance of you try to ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. // You may want to create your own instance of you try to ImDrawList completely without ImGui. In that case, watch out for future changes to this structure.
struct IMGUI_API ImDrawListSharedData struct IMGUI_API ImDrawListSharedData
{ {
ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel)
const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines)
ImFont* Font; // Current/default font (optional, for simplified AddText overload) ImFontAtlas* FontAtlas; // Current font atlas
float FontSize; // Current/default font size (optional, for simplified AddText overload) ImFont* Font; // Current font (used for simplified AddText overload)
float FontScale; // Current/default font scale (== FontSize / Font->FontSize) float FontSize; // Current font size (used for for simplified AddText overload)
float FontScale; // Current font scale (== FontSize / Font->FontSize)
float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo()
float CircleSegmentMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc float CircleSegmentMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc
float InitialFringeScale; // Initial scale to apply to AA fringe float InitialFringeScale; // Initial scale to apply to AA fringe
ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards)
ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen()
ImVector<ImVec2> TempBuffer; // Temporary write buffer ImVector<ImVec2> TempBuffer; // Temporary write buffer
ImVector<ImDrawList*> DrawLists; // All draw lists associated to this ImDrawListSharedData
ImGuiContext* Context; // [OPTIONAL] Link to Dear ImGui context. 99% of ImDrawList/ImFontAtlas can function without an ImGui context, but this facilitate handling one legacy edge case.
// Lookup tables // Lookup tables
ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle. ImVec2 ArcFastVtx[IM_DRAWLIST_ARCFAST_TABLE_SIZE]; // Sample points on the quarter of the circle.
@ -812,6 +868,7 @@ struct IMGUI_API ImDrawListSharedData
ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead) ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius before we calculate it dynamically (to avoid calculation overhead)
ImDrawListSharedData(); ImDrawListSharedData();
~ImDrawListSharedData();
void SetCircleTessellationMaxError(float max_error); void SetCircleTessellationMaxError(float max_error);
}; };
@ -823,6 +880,13 @@ struct ImDrawDataBuilder
ImDrawDataBuilder() { memset(this, 0, sizeof(*this)); } ImDrawDataBuilder() { memset(this, 0, sizeof(*this)); }
}; };
struct ImFontStackData
{
ImFont* Font;
float FontSizeBeforeScaling; // ~~ style.FontSizeBase
float FontSizeAfterScaling; // ~~ g.FontSize
};
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] Style support // [SECTION] Style support
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -894,6 +958,7 @@ enum ImGuiItemFlagsPrivate_
ImGuiItemFlags_AllowOverlap = 1 << 14, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame. ImGuiItemFlags_AllowOverlap = 1 << 14, // false // Allow being overlapped by another widget. Not-hovered to Hovered transition deferred by a frame.
ImGuiItemFlags_NoNavDisableMouseHover = 1 << 15, // false // Nav keyboard/gamepad mode doesn't disable hover highlight (behave as if NavHighlightItemUnderNav==false). ImGuiItemFlags_NoNavDisableMouseHover = 1 << 15, // false // Nav keyboard/gamepad mode doesn't disable hover highlight (behave as if NavHighlightItemUnderNav==false).
ImGuiItemFlags_NoMarkEdited = 1 << 16, // false // Skip calling MarkItemEdited() ImGuiItemFlags_NoMarkEdited = 1 << 16, // false // Skip calling MarkItemEdited()
ImGuiItemFlags_NoFocus = 1 << 17, // false // [EXPERIMENTAL: Not very well specced] Clicking doesn't take focus. Automatically sets ImGuiButtonFlags_NoFocus + ImGuiButtonFlags_NoNavFocus in ButtonBehavior().
// Controlled by widget code // Controlled by widget code
ImGuiItemFlags_Inputable = 1 << 20, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature. ImGuiItemFlags_Inputable = 1 << 20, // false // [WIP] Auto-activate input mode when tab focused. Currently only used and supported by a few items before it becomes a generic feature.
@ -972,7 +1037,9 @@ enum ImGuiButtonFlagsPrivate_
ImGuiButtonFlags_NoHoveredOnFocus = 1 << 19, // don't report as hovered when nav focus is on this item ImGuiButtonFlags_NoHoveredOnFocus = 1 << 19, // don't report as hovered when nav focus is on this item
ImGuiButtonFlags_NoSetKeyOwner = 1 << 20, // don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) ImGuiButtonFlags_NoSetKeyOwner = 1 << 20, // don't set key/input owner on the initial click (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!)
ImGuiButtonFlags_NoTestKeyOwner = 1 << 21, // don't test key/input owner when polling the key (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!) ImGuiButtonFlags_NoTestKeyOwner = 1 << 21, // don't test key/input owner when polling the key (note: mouse buttons are keys! often, the key in question will be ImGuiKey_MouseLeft!)
ImGuiButtonFlags_NoHashTextHide = 1 << 22, // tildearrow: Don't hide text after hash ImGuiButtonFlags_NoFocus = 1 << 22, // [EXPERIMENTAL: Not very well specced]. Don't focus parent window when clicking.
ImGuiButtonFlags_NoHashTextHide = 1 << 23, // tildearrow: Don't hide text after hash
ImGuiButtonFlags_PressedOnMask_ = ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_PressedOnDragDropHold, ImGuiButtonFlags_PressedOnMask_ = ImGuiButtonFlags_PressedOnClick | ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnClickReleaseAnywhere | ImGuiButtonFlags_PressedOnRelease | ImGuiButtonFlags_PressedOnDoubleClick | ImGuiButtonFlags_PressedOnDragDropHold,
ImGuiButtonFlags_PressedOnDefault_ = ImGuiButtonFlags_PressedOnClickRelease, ImGuiButtonFlags_PressedOnDefault_ = ImGuiButtonFlags_PressedOnClickRelease,
}; };
@ -1007,9 +1074,11 @@ enum ImGuiSelectableFlagsPrivate_
// Extend ImGuiTreeNodeFlags_ // Extend ImGuiTreeNodeFlags_
enum ImGuiTreeNodeFlagsPrivate_ enum ImGuiTreeNodeFlagsPrivate_
{ {
ImGuiTreeNodeFlags_NoNavFocus = 1 << 27,// Don't claim nav focus when interacting with this item (#8551)
ImGuiTreeNodeFlags_ClipLabelForTrailingButton = 1 << 28,// FIXME-WIP: Hard-coded for CollapsingHeader() ImGuiTreeNodeFlags_ClipLabelForTrailingButton = 1 << 28,// FIXME-WIP: Hard-coded for CollapsingHeader()
ImGuiTreeNodeFlags_UpsideDownArrow = 1 << 29,// FIXME-WIP: Turn Down arrow into an Up arrow, for reversed trees (#6517) ImGuiTreeNodeFlags_UpsideDownArrow = 1 << 29,// FIXME-WIP: Turn Down arrow into an Up arrow, for reversed trees (#6517)
ImGuiTreeNodeFlags_OpenOnMask_ = ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_OpenOnArrow, ImGuiTreeNodeFlags_OpenOnMask_ = ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_OpenOnArrow,
ImGuiTreeNodeFlags_DrawLinesMask_ = ImGuiTreeNodeFlags_DrawLinesNone | ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes,
}; };
enum ImGuiSeparatorFlags_ enum ImGuiSeparatorFlags_
@ -1309,7 +1378,7 @@ struct ImGuiLastItemData
}; };
// Store data emitted by TreeNode() for usage by TreePop() // Store data emitted by TreeNode() for usage by TreePop()
// - To implement ImGuiTreeNodeFlags_NavLeftJumpsBackHere: store the minimum amount of data // - To implement ImGuiTreeNodeFlags_NavLeftJumpsToParent: store the minimum amount of data
// which we can't infer in TreePop(), to perform the equivalent of NavApplyItemToResult(). // which we can't infer in TreePop(), to perform the equivalent of NavApplyItemToResult().
// Only stored when the node is a potential candidate for landing on a Left arrow jump. // Only stored when the node is a potential candidate for landing on a Left arrow jump.
struct ImGuiTreeNodeStackData struct ImGuiTreeNodeStackData
@ -1318,6 +1387,9 @@ struct ImGuiTreeNodeStackData
ImGuiTreeNodeFlags TreeFlags; ImGuiTreeNodeFlags TreeFlags;
ImGuiItemFlags ItemFlags; // Used for nav landing ImGuiItemFlags ItemFlags; // Used for nav landing
ImRect NavRect; // Used for nav landing ImRect NavRect; // Used for nav landing
float DrawLinesX1;
float DrawLinesToNodesY2;
ImGuiTableColumnIdx DrawLinesTableColumn;
}; };
// sizeof() = 20 // sizeof() = 20
@ -2206,11 +2278,13 @@ struct ImGuiMetricsConfig
bool ShowDrawCmdMesh = true; bool ShowDrawCmdMesh = true;
bool ShowDrawCmdBoundingBoxes = true; bool ShowDrawCmdBoundingBoxes = true;
bool ShowTextEncodingViewer = false; bool ShowTextEncodingViewer = false;
bool ShowTextureUsedRect = false;
bool ShowDockingNodes = false; bool ShowDockingNodes = false;
int ShowWindowsRectsType = -1; int ShowWindowsRectsType = -1;
int ShowTablesRectsType = -1; int ShowTablesRectsType = -1;
int HighlightMonitorIdx = -1; int HighlightMonitorIdx = -1;
ImGuiID HighlightViewportID = 0; ImGuiID HighlightViewportID = 0;
bool ShowFontPreview = true;
}; };
struct ImGuiStackLevelInfo struct ImGuiStackLevelInfo
@ -2263,16 +2337,18 @@ struct ImGuiContextHook
struct ImGuiContext struct ImGuiContext
{ {
bool Initialized; bool Initialized;
bool FontAtlasOwnedByContext; // IO.Fonts-> is owned by the ImGuiContext and will be destructed along with it.
ImGuiIO IO; ImGuiIO IO;
ImGuiPlatformIO PlatformIO; ImGuiPlatformIO PlatformIO;
ImGuiStyle Style; ImGuiStyle Style;
ImGuiConfigFlags ConfigFlagsCurrFrame; // = g.IO.ConfigFlags at the time of NewFrame() ImGuiConfigFlags ConfigFlagsCurrFrame; // = g.IO.ConfigFlags at the time of NewFrame()
ImGuiConfigFlags ConfigFlagsLastFrame; ImGuiConfigFlags ConfigFlagsLastFrame;
ImFont* Font; // (Shortcut) == FontStack.empty() ? IO.Font : FontStack.back() ImVector<ImFontAtlas*> FontAtlases; // List of font atlases used by the context (generally only contains g.IO.Fonts aka the main font atlas)
float FontSize; // (Shortcut) == FontBaseSize * g.CurrentWindow->FontWindowScale == window->FontSize(). Text height for current window. ImFont* Font; // Currently bound font. (== FontStack.back().Font)
float FontBaseSize; // (Shortcut) == IO.FontGlobalScale * Font->Scale * Font->FontSize. Base text height. ImFontBaked* FontBaked; // Currently bound font at currently bound size. (== Font->GetFontBaked(FontSize))
float FontScale; // == FontSize / Font->FontSize float FontSize; // Currently bound font size == line height (== FontSizeBase + externals scales applied in the UpdateCurrentFontSize() function).
float FontSizeBase; // Font size before scaling == style.FontSizeBase == value passed to PushFont() when specified.
float FontBakedScale; // == FontBaked->Size / FontSize. Scale factor over baked size. Rarely used nowadays, very often == 1.0f.
float FontRasterizerDensity; // Current font density. Used by all calls to GetFontBaked().
float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale float CurrentDpiScale; // Current window/viewport DpiScale == CurrentViewport->DpiScale
ImDrawListSharedData DrawListSharedData; ImDrawListSharedData DrawListSharedData;
double Time; double Time;
@ -2375,7 +2451,7 @@ struct ImGuiContext
ImGuiCol DebugFlashStyleColorIdx; // (Keep close to ColorStack to share cache line) ImGuiCol DebugFlashStyleColorIdx; // (Keep close to ColorStack to share cache line)
ImVector<ImGuiColorMod> ColorStack; // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin() ImVector<ImGuiColorMod> ColorStack; // Stack for PushStyleColor()/PopStyleColor() - inherited by Begin()
ImVector<ImGuiStyleMod> StyleVarStack; // Stack for PushStyleVar()/PopStyleVar() - inherited by Begin() ImVector<ImGuiStyleMod> StyleVarStack; // Stack for PushStyleVar()/PopStyleVar() - inherited by Begin()
ImVector<ImFont*> FontStack; // Stack for PushFont()/PopFont() - inherited by Begin() ImVector<ImFontStackData> FontStack; // Stack for PushFont()/PopFont() - inherited by Begin()
ImVector<ImGuiFocusScopeData> FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - inherited by BeginChild(), pushed into by Begin() ImVector<ImGuiFocusScopeData> FocusScopeStack; // Stack for PushFocusScope()/PopFocusScope() - inherited by BeginChild(), pushed into by Begin()
ImVector<ImGuiItemFlags> ItemFlagsStack; // Stack for PushItemFlag()/PopItemFlag() - inherited by Begin() ImVector<ImGuiItemFlags> ItemFlagsStack; // Stack for PushItemFlag()/PopItemFlag() - inherited by Begin()
ImVector<ImGuiGroupData> GroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin() ImVector<ImGuiGroupData> GroupStack; // Stack for BeginGroup()/EndGroup() - not inherited by Begin()
@ -2452,6 +2528,7 @@ struct ImGuiContext
bool NavJustMovedToHasSelectionData; // Copy of move result's ItemFlags & ImGuiItemFlags_HasSelectionUserData). Maybe we should just store ImGuiNavItemData. bool NavJustMovedToHasSelectionData; // Copy of move result's ItemFlags & ImGuiItemFlags_HasSelectionUserData). Maybe we should just store ImGuiNavItemData.
// Navigation: Windowing (CTRL+TAB for list, or Menu button + keys or directional pads to move/resize) // Navigation: Windowing (CTRL+TAB for list, or Menu button + keys or directional pads to move/resize)
bool ConfigNavWindowingWithGamepad; // = true. Enable CTRL+TAB by holding ImGuiKey_GamepadFaceLeft (== ImGuiKey_NavGamepadMenu). When false, the button may still be used to toggle Menu layer.
ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiKey_Tab on OS X). For reconfiguration (see #4828) ImGuiKeyChord ConfigNavWindowingKeyNext; // = ImGuiMod_Ctrl | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiKey_Tab on OS X). For reconfiguration (see #4828)
ImGuiKeyChord ConfigNavWindowingKeyPrev; // = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab on OS X) ImGuiKeyChord ConfigNavWindowingKeyPrev; // = ImGuiMod_Ctrl | ImGuiMod_Shift | ImGuiKey_Tab (or ImGuiMod_Super | ImGuiMod_Shift | ImGuiKey_Tab on OS X)
ImGuiWindow* NavWindowingTarget; // Target window when doing CTRL+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most! ImGuiWindow* NavWindowingTarget; // Target window when doing CTRL+Tab (or Pad Menu + FocusPrev/Next), this window is temporarily displayed top-most!
@ -2459,6 +2536,7 @@ struct ImGuiContext
ImGuiWindow* NavWindowingListWindow; // Internal window actually listing the CTRL+Tab contents ImGuiWindow* NavWindowingListWindow; // Internal window actually listing the CTRL+Tab contents
float NavWindowingTimer; float NavWindowingTimer;
float NavWindowingHighlightAlpha; float NavWindowingHighlightAlpha;
ImGuiInputSource NavWindowingInputSource;
bool NavWindowingToggleLayer; bool NavWindowingToggleLayer;
ImGuiKey NavWindowingToggleKey; ImGuiKey NavWindowingToggleKey;
ImVec2 NavWindowingAccumDeltaPos; ImVec2 NavWindowingAccumDeltaPos;
@ -2534,7 +2612,8 @@ struct ImGuiContext
// Widget state // Widget state
ImGuiInputTextState InputTextState; ImGuiInputTextState InputTextState;
ImGuiInputTextDeactivatedState InputTextDeactivatedState; ImGuiInputTextDeactivatedState InputTextDeactivatedState;
ImFont InputTextPasswordFont; ImFontBaked InputTextPasswordFontBackupBaked;
ImFontFlags InputTextPasswordFontBackupFlags;
ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc. ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc.
ImGuiDataTypeStorage DataTypeZeroValue; // 0 for all data types ImGuiDataTypeStorage DataTypeZeroValue; // 0 for all data types
int BeginMenuDepth; int BeginMenuDepth;
@ -2566,12 +2645,12 @@ struct ImGuiContext
ImGuiTypingSelectState TypingSelectState; // State for GetTypingSelectRequest() ImGuiTypingSelectState TypingSelectState; // State for GetTypingSelectRequest()
// Platform support // Platform support
ImGuiPlatformImeData PlatformImeData; // Data updated by current frame ImGuiPlatformImeData PlatformImeData; // Data updated by current frame. Will be applied at end of the frame. For some backends, this is required to have WantVisible=true in order to receive text message.
ImGuiPlatformImeData PlatformImeDataPrev; // Previous frame data. When changed we call the platform_io.Platform_SetImeDataFn() handler. ImGuiPlatformImeData PlatformImeDataPrev; // Previous frame data. When changed we call the platform_io.Platform_SetImeDataFn() handler.
ImGuiID PlatformImeViewport;
// Extensions // Extensions
// FIXME: We could provide an API to register one slot in an array held in ImGuiContext? // FIXME: We could provide an API to register one slot in an array held in ImGuiContext?
ImVector<ImTextureData*> UserTextures; // List of textures created/managed by user or third-party extension. Automatically appended into platform_io.Textures[].
ImGuiDockContext DockContext; ImGuiDockContext DockContext;
void (*DockNodeWindowMenuHandler)(ImGuiContext* ctx, ImGuiDockNode* node, ImGuiTabBar* tab_bar); void (*DockNodeWindowMenuHandler)(ImGuiContext* ctx, ImGuiDockNode* node, ImGuiTabBar* tab_bar);
@ -2641,7 +2720,7 @@ struct ImGuiContext
float FramerateSecPerFrameAccum; float FramerateSecPerFrameAccum;
int WantCaptureMouseNextFrame; // Explicit capture override via SetNextFrameWantCaptureMouse()/SetNextFrameWantCaptureKeyboard(). Default to -1. int WantCaptureMouseNextFrame; // Explicit capture override via SetNextFrameWantCaptureMouse()/SetNextFrameWantCaptureKeyboard(). Default to -1.
int WantCaptureKeyboardNextFrame; // " int WantCaptureKeyboardNextFrame; // "
int WantTextInputNextFrame; int WantTextInputNextFrame; // Copied in EndFrame() from g.PlatformImeData.WanttextInput. Needs to be set for some backends (SDL3) to emit character inputs.
ImVector<char> TempBuffer; // Temporary text buffer ImVector<char> TempBuffer; // Temporary text buffer
char TempKeychordName[64]; char TempKeychordName[64];
@ -2688,6 +2767,7 @@ struct IMGUI_API ImGuiWindowTempData
ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items measurement ImGuiMenuColumns MenuColumns; // Simplified columns storage for menu items measurement
int TreeDepth; // Current tree depth. int TreeDepth; // Current tree depth.
ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary. ImU32 TreeHasStackDataDepthMask; // Store whether given depth has ImGuiTreeNodeStackData data. Could be turned into a ImU64 if necessary.
ImU32 TreeRecordsClippedNodesY2Mask; // Store whether we should keep recording Y2. Cleared when passing clip max. Equivalent TreeHasStackDataDepthMask value should always be set.
ImVector<ImGuiWindow*> ChildWindows; ImVector<ImGuiWindow*> ChildWindows;
ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state) ImGuiStorage* StateStorage; // Current persistent per-window storage (store e.g. tree node open/close state)
ImGuiOldColumns* CurrentColumns; // Current columns set ImGuiOldColumns* CurrentColumns; // Current columns set
@ -2812,7 +2892,6 @@ struct IMGUI_API ImGuiWindow
ImVector<ImGuiOldColumns> ColumnsStorage; ImVector<ImGuiOldColumns> ColumnsStorage;
float FontWindowScale; // User scale multiplier per-window, via SetWindowFontScale() float FontWindowScale; // User scale multiplier per-window, via SetWindowFontScale()
float FontWindowScaleParents; float FontWindowScaleParents;
float FontDpiScale;
float FontRefSize; // This is a copy of window->CalcFontSize() at the time of Begin(), trying to phase out CalcFontSize() especially as it may be called on non-current window. float FontRefSize; // This is a copy of window->CalcFontSize() at the time of Begin(), trying to phase out CalcFontSize() especially as it may be called on non-current window.
int SettingsOffset; // Offset into SettingsWindows[] (offsets are always valid as we only grow the array from the back) int SettingsOffset; // Offset into SettingsWindows[] (offsets are always valid as we only grow the array from the back)
@ -2860,9 +2939,11 @@ public:
// We don't use g.FontSize because the window may be != g.CurrentWindow. // We don't use g.FontSize because the window may be != g.CurrentWindow.
ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); } ImRect Rect() const { return ImRect(Pos.x, Pos.y, Pos.x + Size.x, Pos.y + Size.y); }
float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontBaseSize * FontWindowScale * FontDpiScale * FontWindowScaleParents; }
ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight)); } ImRect TitleBarRect() const { return ImRect(Pos, ImVec2(Pos.x + SizeFull.x, Pos.y + TitleBarHeight)); }
ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight; return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight); } ImRect MenuBarRect() const { float y1 = Pos.y + TitleBarHeight; return ImRect(Pos.x, y1, Pos.x + SizeFull.x, y1 + MenuBarHeight); }
// [Obsolete] ImGuiWindow::CalcFontSize() was removed in 1.92.x because error-prone/misleading. You can use window->FontRefSize for a copy of g.FontSize at the time of the last Begin() call for this window.
//float CalcFontSize() const { ImGuiContext& g = *Ctx; return g.FontSizeBase * FontWindowScale * FontDpiScale * FontWindowScaleParents;
}; };
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -2953,11 +3034,7 @@ struct IMGUI_API ImGuiTabBar
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
#define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color. #define IM_COL32_DISABLE IM_COL32(0,0,0,1) // Special sentinel code which cannot be used as a regular color.
#define IMGUI_TABLE_MAX_COLUMNS 512 // May be further lifted #define IMGUI_TABLE_MAX_COLUMNS 512 // Arbitrary "safety" maximum, may be lifted in the future if needed. Must fit in ImGuiTableColumnIdx/ImGuiTableDrawChannelIdx.
// Our current column maximum is 64 but we may raise that in the future.
typedef ImS16 ImGuiTableColumnIdx;
typedef ImU16 ImGuiTableDrawChannelIdx;
// [Internal] sizeof() ~ 112 // [Internal] sizeof() ~ 112
// We use the terminology "Enabled" to refer to a column that is not Hidden by user/api. // We use the terminology "Enabled" to refer to a column that is not Hidden by user/api.
@ -3290,9 +3367,18 @@ namespace ImGui
IMGUI_API void SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags); IMGUI_API void SetNextWindowRefreshPolicy(ImGuiWindowRefreshFlags flags);
// Fonts, drawing // Fonts, drawing
IMGUI_API void SetCurrentFont(ImFont* font); IMGUI_API void RegisterUserTexture(ImTextureData* tex); // Register external texture
inline ImFont* GetDefaultFont() { ImGuiContext& g = *GImGui; return g.IO.FontDefault ? g.IO.FontDefault : g.IO.Fonts->Fonts[0]; } IMGUI_API void UnregisterUserTexture(ImTextureData* tex);
IMGUI_API void RegisterFontAtlas(ImFontAtlas* atlas);
IMGUI_API void UnregisterFontAtlas(ImFontAtlas* atlas);
IMGUI_API void SetCurrentFont(ImFont* font, float font_size_before_scaling, float font_size_after_scaling);
IMGUI_API void UpdateCurrentFontSize(float restore_font_size_after_scaling);
IMGUI_API void SetFontRasterizerDensity(float rasterizer_density);
inline float GetFontRasterizerDensity() { return GImGui->FontRasterizerDensity; }
inline float GetRoundedFontSize(float size) { return IM_ROUND(size); }
IMGUI_API ImFont* GetDefaultFont();
IMGUI_API void PushPasswordFont(); IMGUI_API void PushPasswordFont();
IMGUI_API void PopPasswordFont();
inline ImDrawList* GetForegroundDrawList(ImGuiWindow* window) { return GetForegroundDrawList(window->Viewport); } inline ImDrawList* GetForegroundDrawList(ImGuiWindow* window) { return GetForegroundDrawList(window->Viewport); }
IMGUI_API void AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector<ImDrawList*>* out_list, ImDrawList* draw_list); IMGUI_API void AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector<ImDrawList*>* out_list, ImDrawList* draw_list);
@ -3302,7 +3388,7 @@ namespace ImGui
// NewFrame // NewFrame
IMGUI_API void UpdateInputEvents(bool trickle_fast_inputs); IMGUI_API void UpdateInputEvents(bool trickle_fast_inputs);
IMGUI_API void UpdateHoveredWindowAndCaptureFlags(); IMGUI_API void UpdateHoveredWindowAndCaptureFlags(const ImVec2& mouse_pos);
IMGUI_API void FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_viewport, ImGuiWindow** out_hovered_window, ImGuiWindow** out_hovered_window_under_moving_window); IMGUI_API void FindHoveredWindowEx(const ImVec2& pos, bool find_first_and_in_any_viewport, ImGuiWindow** out_hovered_window, ImGuiWindow** out_hovered_window_under_moving_window);
IMGUI_API void StartMouseMovingWindow(ImGuiWindow* window); IMGUI_API void StartMouseMovingWindow(ImGuiWindow* window);
IMGUI_API void StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* node, bool undock); IMGUI_API void StartMouseMovingWindowOrNode(ImGuiWindow* window, ImGuiDockNode* node, bool undock);
@ -3434,7 +3520,7 @@ namespace ImGui
IMGUI_API void NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags); IMGUI_API void NavMoveRequestSubmit(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags);
IMGUI_API void NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags); IMGUI_API void NavMoveRequestForward(ImGuiDir move_dir, ImGuiDir clip_dir, ImGuiNavMoveFlags move_flags, ImGuiScrollFlags scroll_flags);
IMGUI_API void NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result); IMGUI_API void NavMoveRequestResolveWithLastItem(ImGuiNavItemData* result);
IMGUI_API void NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, ImGuiTreeNodeStackData* tree_node_data); IMGUI_API void NavMoveRequestResolveWithPastTreeNode(ImGuiNavItemData* result, const ImGuiTreeNodeStackData* tree_node_data);
IMGUI_API void NavMoveRequestCancel(); IMGUI_API void NavMoveRequestCancel();
IMGUI_API void NavMoveRequestApplyResult(); IMGUI_API void NavMoveRequestApplyResult();
IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags); IMGUI_API void NavMoveRequestTryWrapping(ImGuiWindow* window, ImGuiNavMoveFlags move_flags);
@ -3652,6 +3738,8 @@ namespace ImGui
IMGUI_API float TableGetHeaderAngledMaxLabelWidth(); IMGUI_API float TableGetHeaderAngledMaxLabelWidth();
IMGUI_API void TablePushBackgroundChannel(); IMGUI_API void TablePushBackgroundChannel();
IMGUI_API void TablePopBackgroundChannel(); IMGUI_API void TablePopBackgroundChannel();
IMGUI_API void TablePushColumnChannel(int column_n);
IMGUI_API void TablePopColumnChannel();
IMGUI_API void TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count); IMGUI_API void TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label_width, const ImGuiTableHeaderData* data, int data_count);
// Tables: Internals // Tables: Internals
@ -3732,7 +3820,7 @@ namespace ImGui
IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL); IMGUI_API void RenderTextClipped(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL);
IMGUI_API void RenderTextClippedNoHashHide(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL); IMGUI_API void RenderTextClippedNoHashHide(const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL);
IMGUI_API void RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL, bool hide_after_hash = true); IMGUI_API void RenderTextClippedEx(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, const char* text, const char* text_end, const ImVec2* text_size_if_known, const ImVec2& align = ImVec2(0, 0), const ImRect* clip_rect = NULL, bool hide_after_hash = true);
IMGUI_API void RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float clip_max_x, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known); IMGUI_API void RenderTextEllipsis(ImDrawList* draw_list, const ImVec2& pos_min, const ImVec2& pos_max, float ellipsis_max_x, const char* text, const char* text_end, const ImVec2* text_size_if_known);
IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders = true, float rounding = 0.0f); IMGUI_API void RenderFrame(ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool borders = true, float rounding = 0.0f);
IMGUI_API void RenderFrameDrawList(ImDrawList* dl, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); // MODIFIED - draw list version of RenderFrame IMGUI_API void RenderFrameDrawList(ImDrawList* dl, ImVec2 p_min, ImVec2 p_max, ImU32 fill_col, bool border = true, float rounding = 0.0f); // MODIFIED - draw list version of RenderFrame
IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f); IMGUI_API void RenderFrameBorder(ImVec2 p_min, ImVec2 p_max, float rounding = 0.0f);
@ -3754,12 +3842,16 @@ namespace ImGui
IMGUI_API void RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding); IMGUI_API void RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding);
IMGUI_API ImDrawFlags CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRect& r_outer, float threshold); IMGUI_API ImDrawFlags CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRect& r_outer, float threshold);
// Widgets // Widgets: Text
IMGUI_API void TextEx(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0); IMGUI_API void TextEx(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0);
IMGUI_API void TextExNoHashHide(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0); IMGUI_API void TextExNoHashHide(const char* text, const char* text_end = NULL, ImGuiTextFlags flags = 0);
IMGUI_API void TextAligned(float align_x, float size_x, const char* fmt, ...); // FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024)
IMGUI_API void TextAlignedV(float align_x, float size_x, const char* fmt, va_list args);
// Widgets
IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0); IMGUI_API bool ButtonEx(const char* label, const ImVec2& size_arg = ImVec2(0, 0), ImGuiButtonFlags flags = 0);
IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags = 0); IMGUI_API bool ArrowButtonEx(const char* str_id, ImGuiDir dir, ImVec2 size_arg, ImGuiButtonFlags flags = 0);
IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0); IMGUI_API bool ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags = 0);
IMGUI_API void SeparatorEx(ImGuiSeparatorFlags flags, float thickness = 1.0f); IMGUI_API void SeparatorEx(ImGuiSeparatorFlags flags, float thickness = 1.0f);
IMGUI_API void SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_width); IMGUI_API void SeparatorTextEx(ImGuiID id, const char* label, const char* label_end, float extra_width);
IMGUI_API bool CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value); IMGUI_API bool CheckboxFlags(const char* label, ImS64* flags, ImS64 flags_value);
@ -3783,6 +3875,8 @@ namespace ImGui
// Widgets: Tree Nodes // Widgets: Tree Nodes
IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL); IMGUI_API bool TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* label, const char* label_end = NULL);
IMGUI_API void TreeNodeDrawLineToChildNode(const ImVec2& target_pos);
IMGUI_API void TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data);
IMGUI_API void TreePushOverrideID(ImGuiID id); IMGUI_API void TreePushOverrideID(ImGuiID id);
IMGUI_API bool TreeNodeGetOpen(ImGuiID storage_id); IMGUI_API bool TreeNodeGetOpen(ImGuiID storage_id);
IMGUI_API void TreeNodeSetOpen(ImGuiID storage_id, bool open); IMGUI_API void TreeNodeSetOpen(ImGuiID storage_id, bool open);
@ -3864,7 +3958,9 @@ namespace ImGui
IMGUI_API void DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label); IMGUI_API void DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, const ImDrawList* draw_list, const char* label);
IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb); IMGUI_API void DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, const ImDrawList* draw_list, const ImDrawCmd* draw_cmd, bool show_mesh, bool show_aabb);
IMGUI_API void DebugNodeFont(ImFont* font); IMGUI_API void DebugNodeFont(ImFont* font);
IMGUI_API void DebugNodeFontGlyphesForSrcMask(ImFont* font, ImFontBaked* baked, int src_mask);
IMGUI_API void DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph); IMGUI_API void DebugNodeFontGlyph(ImFont* font, const ImFontGlyph* glyph);
IMGUI_API void DebugNodeTexture(ImTextureData* tex, int int_id, const ImFontAtlasRect* highlight_rect = NULL); // ID used to facilitate persisting the "current" texture.
IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label); IMGUI_API void DebugNodeStorage(ImGuiStorage* storage, const char* label);
IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label); IMGUI_API void DebugNodeTabBar(ImGuiTabBar* tab_bar, const char* label);
IMGUI_API void DebugNodeTable(ImGuiTable* table); IMGUI_API void DebugNodeTable(ImGuiTable* table);
@ -3898,31 +3994,188 @@ namespace ImGui
} // namespace ImGui } // namespace ImGui
//-----------------------------------------------------------------------------
// [SECTION] ImFontLoader
//-----------------------------------------------------------------------------
// Hooks and storage for a given font backend.
// This structure is likely to evolve as we add support for incremental atlas updates.
// Conceptually this could be public, but API is still going to be evolve.
struct ImFontLoader
{
const char* Name;
bool (*LoaderInit)(ImFontAtlas* atlas);
void (*LoaderShutdown)(ImFontAtlas* atlas);
bool (*FontSrcInit)(ImFontAtlas* atlas, ImFontConfig* src);
void (*FontSrcDestroy)(ImFontAtlas* atlas, ImFontConfig* src);
bool (*FontSrcContainsGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImWchar codepoint);
bool (*FontBakedInit)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src);
void (*FontBakedDestroy)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src);
bool (*FontBakedLoadGlyph)(ImFontAtlas* atlas, ImFontConfig* src, ImFontBaked* baked, void* loader_data_for_baked_src, ImWchar codepoint, ImFontGlyph* out_glyph);
// Size of backend data, Per Baked * Per Source. Buffers are managed by core to avoid excessive allocations.
// FIXME: At this point the two other types of buffers may be managed by core to be consistent?
size_t FontBakedSrcLoaderDataSize;
ImFontLoader() { memset(this, 0, sizeof(*this)); }
};
#ifdef IMGUI_ENABLE_STB_TRUETYPE
IMGUI_API const ImFontLoader* ImFontAtlasGetFontLoaderForStbTruetype();
#endif
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
typedef ImFontLoader ImFontBuilderIO; // [renamed/changed in 1.92] The types are not actually compatible but we provide this as a compile-time error report helper.
#endif
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] ImFontAtlas internal API // [SECTION] ImFontAtlas internal API
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// This structure is likely to evolve as we add support for incremental atlas updates. // Helpers: ImTextureRef ==/!= operators provided as convenience
// Conceptually this could be in ImGuiPlatformIO, but we are far from ready to make this public. // (note that _TexID and _TexData are never set simultaneously)
struct ImFontBuilderIO inline bool operator==(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID == rhs._TexID && lhs._TexData == rhs._TexData; }
inline bool operator!=(const ImTextureRef& lhs, const ImTextureRef& rhs) { return lhs._TexID != rhs._TexID || lhs._TexData != rhs._TexData; }
// Refer to ImFontAtlasPackGetRect() to better understand how this works.
#define ImFontAtlasRectId_IndexMask_ (0x000FFFFF) // 20-bits: index to access builder->RectsIndex[].
#define ImFontAtlasRectId_GenerationMask_ (0x3FF00000) // 10-bits: entry generation, so each ID is unique and get can safely detected old identifiers.
#define ImFontAtlasRectId_GenerationShift_ (20)
inline int ImFontAtlasRectId_GetIndex(ImFontAtlasRectId id) { return id & ImFontAtlasRectId_IndexMask_; }
inline int ImFontAtlasRectId_GetGeneration(ImFontAtlasRectId id) { return (id & ImFontAtlasRectId_GenerationMask_) >> ImFontAtlasRectId_GenerationShift_; }
inline ImFontAtlasRectId ImFontAtlasRectId_Make(int index_idx, int gen_idx) { IM_ASSERT(index_idx < ImFontAtlasRectId_IndexMask_ && gen_idx < (ImFontAtlasRectId_GenerationMask_ >> ImFontAtlasRectId_GenerationShift_)); return (ImFontAtlasRectId)(index_idx | (gen_idx << ImFontAtlasRectId_GenerationShift_)); }
// Packed rectangle lookup entry (we need an indirection to allow removing/reordering rectangles)
// User are returned ImFontAtlasRectId values which are meant to be persistent.
// We handle this with an indirection. While Rects[] may be in theory shuffled, compacted etc., RectsIndex[] cannot it is keyed by ImFontAtlasRectId.
// RectsIndex[] is used both as an index into Rects[] and an index into itself. This is basically a free-list. See ImFontAtlasBuildAllocRectIndexEntry() code.
// Having this also makes it easier to e.g. sort rectangles during repack.
struct ImFontAtlasRectEntry
{ {
bool (*FontBuilder_Build)(ImFontAtlas* atlas); int TargetIndex : 20; // When Used: ImFontAtlasRectId -> into Rects[]. When unused: index to next unused RectsIndex[] slot to consume free-list.
int Generation : 10; // Increased each time the entry is reused for a new rectangle.
unsigned int IsUsed : 1;
}; };
// Helper for font builder // Data available to potential texture post-processing functions
#ifdef IMGUI_ENABLE_STB_TRUETYPE struct ImFontAtlasPostProcessData
IMGUI_API const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype(); {
ImFontAtlas* FontAtlas;
ImFont* Font;
ImFontConfig* FontSrc;
ImFontBaked* FontBaked;
ImFontGlyph* Glyph;
// Pixel data
void* Pixels;
ImTextureFormat Format;
int Pitch;
int Width;
int Height;
};
// We avoid dragging imstb_rectpack.h into public header (partly because binding generators are having issues with it)
#ifdef IMGUI_STB_NAMESPACE
namespace IMGUI_STB_NAMESPACE { struct stbrp_node; }
typedef IMGUI_STB_NAMESPACE::stbrp_node stbrp_node_im;
#else
struct stbrp_node;
typedef stbrp_node stbrp_node_im;
#endif #endif
IMGUI_API void ImFontAtlasUpdateSourcesPointers(ImFontAtlas* atlas); struct stbrp_context_opaque { char data[80]; };
// Internal storage for incrementally packing and building a ImFontAtlas
struct ImFontAtlasBuilder
{
stbrp_context_opaque PackContext; // Actually 'stbrp_context' but we don't want to define this in the header file.
ImVector<stbrp_node_im> PackNodes;
ImVector<ImTextureRect> Rects;
ImVector<ImFontAtlasRectEntry> RectsIndex; // ImFontAtlasRectId -> index into Rects[]
ImVector<unsigned char> TempBuffer; // Misc scratch buffer
int RectsIndexFreeListStart;// First unused entry
int RectsPackedCount; // Number of packed rectangles.
int RectsPackedSurface; // Number of packed pixels. Used when compacting to heuristically find the ideal texture size.
int RectsDiscardedCount;
int RectsDiscardedSurface;
int FrameCount; // Current frame count
ImVec2i MaxRectSize; // Largest rectangle to pack (de-facto used as a "minimum texture size")
ImVec2i MaxRectBounds; // Bottom-right most used pixels
bool LockDisableResize; // Disable resizing texture
bool PreloadedAllGlyphsRanges; // Set when missing ImGuiBackendFlags_RendererHasTextures features forces atlas to preload everything.
// Cache of all ImFontBaked
ImStableVector<ImFontBaked,32> BakedPool;
ImGuiStorage BakedMap; // BakedId --> ImFontBaked*
int BakedDiscardedCount;
// Custom rectangle identifiers
ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure.
ImFontAtlasRectId PackIdLinesTexData;
ImFontAtlasBuilder() { memset(this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; }
};
IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src, float ascent, float descent); IMGUI_API void ImFontAtlasBuildDestroy(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque); IMGUI_API void ImFontAtlasBuildMain(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildSetupFontLoader(ImFontAtlas* atlas, const ImFontLoader* font_loader);
IMGUI_API void ImFontAtlasBuildRender8bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned char in_marker_pixel_value); IMGUI_API void ImFontAtlasBuildUpdatePointers(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasBuildRender32bppRectFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char, unsigned int in_marker_pixel_value); IMGUI_API void ImFontAtlasBuildRenderBitmapFromString(ImFontAtlas* atlas, int x, int y, int w, int h, const char* in_str, char in_marker_char);
IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); IMGUI_API void ImFontAtlasBuildClear(ImFontAtlas* atlas); // Clear output and custom rects
IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride);
IMGUI_API void ImFontAtlasBuildGetOversampleFactors(const ImFontConfig* src, int* out_oversample_h, int* out_oversample_v); IMGUI_API ImTextureData* ImFontAtlasTextureAdd(ImFontAtlas* atlas, int w, int h);
IMGUI_API void ImFontAtlasTextureMakeSpace(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasTextureRepack(ImFontAtlas* atlas, int w, int h);
IMGUI_API void ImFontAtlasTextureGrow(ImFontAtlas* atlas, int old_w = -1, int old_h = -1);
IMGUI_API void ImFontAtlasTextureCompact(ImFontAtlas* atlas);
IMGUI_API ImVec2i ImFontAtlasTextureGetSizeEstimate(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasBuildSetupFontSpecialGlyphs(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src);
IMGUI_API void ImFontAtlasBuildLegacyPreloadAllGlyphRanges(ImFontAtlas* atlas); // Legacy
IMGUI_API void ImFontAtlasBuildGetOversampleFactors(ImFontConfig* src, ImFontBaked* baked, int* out_oversample_h, int* out_oversample_v);
IMGUI_API void ImFontAtlasBuildDiscardBakes(ImFontAtlas* atlas, int unused_frames);
IMGUI_API bool ImFontAtlasFontSourceInit(ImFontAtlas* atlas, ImFontConfig* src);
IMGUI_API void ImFontAtlasFontSourceAddToFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* src);
IMGUI_API void ImFontAtlasFontDestroySourceData(ImFontAtlas* atlas, ImFontConfig* src);
IMGUI_API bool ImFontAtlasFontInitOutput(ImFontAtlas* atlas, ImFont* font); // Using FontDestroyOutput/FontInitOutput sequence useful notably if font loader params have changed
IMGUI_API void ImFontAtlasFontDestroyOutput(ImFontAtlas* atlas, ImFont* font);
IMGUI_API void ImFontAtlasFontDiscardBakes(ImFontAtlas* atlas, ImFont* font, int unused_frames);
IMGUI_API ImGuiID ImFontAtlasBakedGetId(ImGuiID font_id, float baked_size, float rasterizer_density);
IMGUI_API ImFontBaked* ImFontAtlasBakedGetOrAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density);
IMGUI_API ImFontBaked* ImFontAtlasBakedGetClosestMatch(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density);
IMGUI_API ImFontBaked* ImFontAtlasBakedAdd(ImFontAtlas* atlas, ImFont* font, float font_size, float font_rasterizer_density, ImGuiID baked_id);
IMGUI_API void ImFontAtlasBakedDiscard(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked);
IMGUI_API ImFontGlyph* ImFontAtlasBakedAddFontGlyph(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, const ImFontGlyph* in_glyph);
IMGUI_API void ImFontAtlasBakedDiscardFontGlyph(ImFontAtlas* atlas, ImFont* font, ImFontBaked* baked, ImFontGlyph* glyph);
IMGUI_API void ImFontAtlasBakedSetFontGlyphBitmap(ImFontAtlas* atlas, ImFontBaked* baked, ImFontConfig* src, ImFontGlyph* glyph, ImTextureRect* r, const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch);
IMGUI_API void ImFontAtlasPackInit(ImFontAtlas* atlas);
IMGUI_API ImFontAtlasRectId ImFontAtlasPackAddRect(ImFontAtlas* atlas, int w, int h, ImFontAtlasRectEntry* overwrite_entry = NULL);
IMGUI_API ImTextureRect* ImFontAtlasPackGetRect(ImFontAtlas* atlas, ImFontAtlasRectId id);
IMGUI_API ImTextureRect* ImFontAtlasPackGetRectSafe(ImFontAtlas* atlas, ImFontAtlasRectId id);
IMGUI_API void ImFontAtlasPackDiscardRect(ImFontAtlas* atlas, ImFontAtlasRectId id);
IMGUI_API void ImFontAtlasUpdateNewFrame(ImFontAtlas* atlas, int frame_count, bool renderer_has_textures);
IMGUI_API void ImFontAtlasAddDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data);
IMGUI_API void ImFontAtlasRemoveDrawListSharedData(ImFontAtlas* atlas, ImDrawListSharedData* data);
IMGUI_API void ImFontAtlasUpdateDrawListsTextures(ImFontAtlas* atlas, ImTextureRef old_tex, ImTextureRef new_tex);
IMGUI_API void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasTextureBlockConvert(const unsigned char* src_pixels, ImTextureFormat src_fmt, int src_pitch, unsigned char* dst_pixels, ImTextureFormat dst_fmt, int dst_pitch, int w, int h);
IMGUI_API void ImFontAtlasTextureBlockPostProcess(ImFontAtlasPostProcessData* data);
IMGUI_API void ImFontAtlasTextureBlockPostProcessMultiply(ImFontAtlasPostProcessData* data, float multiply_factor);
IMGUI_API void ImFontAtlasTextureBlockFill(ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h, ImU32 col);
IMGUI_API void ImFontAtlasTextureBlockCopy(ImTextureData* src_tex, int src_x, int src_y, ImTextureData* dst_tex, int dst_x, int dst_y, int w, int h);
IMGUI_API void ImFontAtlasTextureBlockQueueUpload(ImFontAtlas* atlas, ImTextureData* tex, int x, int y, int w, int h);
IMGUI_API int ImTextureDataGetFormatBytesPerPixel(ImTextureFormat format);
IMGUI_API const char* ImTextureDataGetStatusName(ImTextureStatus status);
IMGUI_API const char* ImTextureDataGetFormatName(ImTextureFormat format);
#ifndef IMGUI_DISABLE_DEBUG_TOOLS
IMGUI_API void ImFontAtlasDebugLogTextureRequests(ImFontAtlas* atlas);
#endif
IMGUI_API bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]); IMGUI_API bool ImFontAtlasGetMouseCursorTexData(ImFontAtlas* atlas, ImGuiMouseCursor cursor_type, ImVec2* out_offset, ImVec2* out_size, ImVec2 out_uv_border[2], ImVec2 out_uv_fill[2]);

View file

@ -1,4 +1,4 @@
// dear imgui, v1.91b // dear imgui, v1.92.0
// (tables and columns code) // (tables and columns code)
/* /*
@ -454,6 +454,7 @@ bool ImGui::BeginTableEx(const char* name, ImGuiID id, int columns_count, ImG
// But at this point we do NOT have a correct value for .Max.y (unless a height has been explicitly passed in). It will only be updated in EndTable(). // But at this point we do NOT have a correct value for .Max.y (unless a height has been explicitly passed in). It will only be updated in EndTable().
table->WorkRect = table->OuterRect = table->InnerRect = outer_rect; table->WorkRect = table->OuterRect = table->InnerRect = outer_rect;
table->HasScrollbarYPrev = table->HasScrollbarYCurr = false; table->HasScrollbarYPrev = table->HasScrollbarYCurr = false;
table->InnerWindow->DC.TreeDepth++; // This is designed to always linking ImGuiTreeNodeFlags_DrawLines linking accross a table
} }
// Push a standardized ID for both child-using and not-child-using tables // Push a standardized ID for both child-using and not-child-using tables
@ -1513,6 +1514,7 @@ void ImGui::EndTable()
} }
else else
{ {
table->InnerWindow->DC.TreeDepth--;
ItemSize(table->OuterRect.GetSize()); ItemSize(table->OuterRect.GetSize());
ItemAdd(table->OuterRect, 0); ItemAdd(table->OuterRect, 0);
} }
@ -1954,7 +1956,10 @@ void ImGui::TableEndRow(ImGuiTable* table)
IM_ASSERT(table->IsInsideRow); IM_ASSERT(table->IsInsideRow);
if (table->CurrentColumn != -1) if (table->CurrentColumn != -1)
{
TableEndCell(table); TableEndCell(table);
table->CurrentColumn = -1;
}
// Logging // Logging
if (g.LogEnabled) if (g.LogEnabled)
@ -2194,6 +2199,7 @@ void ImGui::TableBeginCell(ImGuiTable* table, int column_n)
g.LastItemData.StatusFlags = 0; g.LastItemData.StatusFlags = 0;
} }
// Also see TablePushColumnChannel()
if (table->Flags & ImGuiTableFlags_NoClip) if (table->Flags & ImGuiTableFlags_NoClip)
{ {
// FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed. // FIXME: if we end up drawing all borders/bg in EndTable, could remove this and just assert that channel hasn't changed.
@ -2467,10 +2473,38 @@ void ImGui::TablePopBackgroundChannel()
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow; ImGuiWindow* window = g.CurrentWindow;
ImGuiTable* table = g.CurrentTable; ImGuiTable* table = g.CurrentTable;
ImGuiTableColumn* column = &table->Columns[table->CurrentColumn];
// Optimization: avoid PopClipRect() + SetCurrentChannel() // Optimization: avoid PopClipRect() + SetCurrentChannel()
SetWindowClipRectBeforeSetChannel(window, table->HostBackupInnerClipRect); SetWindowClipRectBeforeSetChannel(window, table->HostBackupInnerClipRect);
table->DrawSplitter->SetCurrentChannel(window->DrawList, table->Columns[table->CurrentColumn].DrawChannelCurrent);
}
// Also see TableBeginCell()
void ImGui::TablePushColumnChannel(int column_n)
{
ImGuiContext& g = *GImGui;
ImGuiTable* table = g.CurrentTable;
// Optimization: avoid SetCurrentChannel() + PushClipRect()
if (table->Flags & ImGuiTableFlags_NoClip)
return;
ImGuiWindow* window = g.CurrentWindow;
const ImGuiTableColumn* column = &table->Columns[column_n];
SetWindowClipRectBeforeSetChannel(window, column->ClipRect);
table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent);
}
void ImGui::TablePopColumnChannel()
{
ImGuiContext& g = *GImGui;
ImGuiTable* table = g.CurrentTable;
// Optimization: avoid PopClipRect() + SetCurrentChannel()
if ((table->Flags & ImGuiTableFlags_NoClip) || (table->CurrentColumn == -1)) // Calling TreePop() after TableNextRow() is supported.
return;
ImGuiWindow* window = g.CurrentWindow;
const ImGuiTableColumn* column = &table->Columns[table->CurrentColumn];
SetWindowClipRectBeforeSetChannel(window, column->ClipRect);
table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent); table->DrawSplitter->SetCurrentChannel(window->DrawList, column->DrawChannelCurrent);
} }
@ -3247,7 +3281,7 @@ void ImGui::TableHeader(const char* label)
// Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will // Render clipped label. Clipping here ensure that in the majority of situations, all our header cells will
// be merged into a single draw call. // be merged into a single draw call.
//window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE); //window->DrawList->AddCircleFilled(ImVec2(ellipsis_max, label_pos.y), 40, IM_COL32_WHITE);
RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, label_pos.y + label_height + g.Style.FramePadding.y), ellipsis_max, ellipsis_max, label, label_end, &label_size); RenderTextEllipsis(window->DrawList, label_pos, ImVec2(ellipsis_max, bb.Max.y), ellipsis_max, label, label_end, &label_size);
const bool text_clipped = label_size.x > (ellipsis_max - label_pos.x); const bool text_clipped = label_size.x > (ellipsis_max - label_pos.x);
if (text_clipped && hovered && g.ActiveId == 0) if (text_clipped && hovered && g.ActiveId == 0)
@ -3344,7 +3378,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label
ButtonBehavior(row_r, row_id, NULL, NULL); ButtonBehavior(row_r, row_id, NULL, NULL);
KeepAliveID(row_id); KeepAliveID(row_id);
const float ascent_scaled = g.Font->Ascent * g.FontScale; // FIXME: Standardize those scaling factors better const float ascent_scaled = g.FontBaked->Ascent * g.FontBakedScale; // FIXME: Standardize those scaling factors better
const float line_off_for_ascent_x = (ImMax((g.FontSize - ascent_scaled) * 0.5f, 0.0f) / -sin_a) * (flip_label ? -1.0f : 1.0f); const float line_off_for_ascent_x = (ImMax((g.FontSize - ascent_scaled) * 0.5f, 0.0f) / -sin_a) * (flip_label ? -1.0f : 1.0f);
const ImVec2 padding = g.Style.CellPadding; // We will always use swapped component const ImVec2 padding = g.Style.CellPadding; // We will always use swapped component
const ImVec2 align = g.Style.TableAngledHeadersTextAlign; const ImVec2 align = g.Style.TableAngledHeadersTextAlign;
@ -3399,7 +3433,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label
ImRect clip_r(window->ClipRect.Min, window->ClipRect.Min + ImVec2(clip_width, clip_height)); ImRect clip_r(window->ClipRect.Min, window->ClipRect.Min + ImVec2(clip_width, clip_height));
int vtx_idx_begin = draw_list->_VtxCurrentIdx; int vtx_idx_begin = draw_list->_VtxCurrentIdx;
PushStyleColor(ImGuiCol_Text, request->TextColor); PushStyleColor(ImGuiCol_Text, request->TextColor);
RenderTextEllipsis(draw_list, clip_r.Min, clip_r.Max, clip_r.Max.x, clip_r.Max.x, label_name, label_name_eol, &label_size); RenderTextEllipsis(draw_list, clip_r.Min, clip_r.Max, clip_r.Max.x, label_name, label_name_eol, &label_size);
PopStyleColor(); PopStyleColor();
int vtx_idx_end = draw_list->_VtxCurrentIdx; int vtx_idx_end = draw_list->_VtxCurrentIdx;

View file

@ -1,4 +1,4 @@
// dear imgui, v1.91b // dear imgui, v1.92.0
// (widgets code) // (widgets code)
/* /*
@ -468,6 +468,46 @@ void ImGui::TextWrappedV(const char* fmt, va_list args)
PopTextWrapPos(); PopTextWrapPos();
} }
void ImGui::TextAligned(float align_x, float size_x, const char* fmt, ...)
{
va_list args;
va_start(args, fmt);
TextAlignedV(align_x, size_x, fmt, args);
va_end(args);
}
// align_x: 0.0f = left, 0.5f = center, 1.0f = right.
// size_x : 0.0f = shortcut for GetContentRegionAvail().x
// FIXME-WIP: Works but API is likely to be reworked. This is designed for 1 item on the line. (#7024)
void ImGui::TextAlignedV(float align_x, float size_x, const char* fmt, va_list args)
{
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
return;
const char* text, *text_end;
ImFormatStringToTempBufferV(&text, &text_end, fmt, args);
const ImVec2 text_size = CalcTextSize(text, text_end);
size_x = CalcItemSize(ImVec2(size_x, 0.0f), 0.0f, text_size.y).x;
ImVec2 pos(window->DC.CursorPos.x, window->DC.CursorPos.y + window->DC.CurrLineTextBaseOffset);
ImVec2 pos_max(pos.x + size_x, window->ClipRect.Max.y);
ImVec2 size(ImMin(size_x, text_size.x), text_size.y);
window->DC.CursorMaxPos.x = ImMax(window->DC.CursorMaxPos.x, pos.x + text_size.x);
window->DC.IdealMaxPos.x = ImMax(window->DC.IdealMaxPos.x, pos.x + text_size.x);
if (align_x > 0.0f && text_size.x < size_x)
pos.x += ImTrunc((size_x - text_size.x) * align_x);
RenderTextEllipsis(window->DrawList, pos, pos_max, pos_max.x, text, text_end, &text_size);
const ImVec2 backup_max_pos = window->DC.CursorMaxPos;
ItemSize(size);
ItemAdd(ImRect(pos, pos + size), 0);
window->DC.CursorMaxPos.x = backup_max_pos.x; // Cancel out extending content size because right-aligned text would otherwise mess it up.
if (size_x < text_size.x && IsItemHovered(ImGuiHoveredFlags_NoNavOverride | ImGuiHoveredFlags_AllowWhenDisabled | ImGuiHoveredFlags_ForTooltip))
SetTooltip("%.*s", (int)(text_end - text), text);
}
void ImGui::LabelText(const char* label, const char* fmt, ...) void ImGui::LabelText(const char* label, const char* fmt, ...)
{ {
va_list args; va_list args;
@ -676,7 +716,7 @@ void ImGui::ScrollText(ImGuiID id, const char* text, const ImVec2& pos, ImVec2 s
// And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);' // And better standardize how widgets use 'GetColor32((held && hovered) ? ... : hovered ? ...)' vs 'GetColor32(held ? ... : hovered ? ...);'
// For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading. // For mouse feedback we typically prefer the 'held && hovered' test, but for nav feedback not always. Outputting hovered=true on Activation may be misleading.
// - Since v1.91.2 (Sept 2024) we included io.ConfigDebugHighlightIdConflicts feature. // - Since v1.91.2 (Sept 2024) we included io.ConfigDebugHighlightIdConflicts feature.
// One idiom which was previously valid which will now emit a warning is when using multiple overlayed ButtonBehavior() // One idiom which was previously valid which will now emit a warning is when using multiple overlaid ButtonBehavior()
// with same ID and different MouseButton (see #8030). You can fix it by: // with same ID and different MouseButton (see #8030). You can fix it by:
// (1) switching to use a single ButtonBehavior() with multiple _MouseButton flags. // (1) switching to use a single ButtonBehavior() with multiple _MouseButton flags.
// or (2) surrounding those calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag() // or (2) surrounding those calls with PushItemFlag(ImGuiItemFlags_AllowDuplicateId, true); ... PopItemFlag()
@ -690,6 +730,8 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags); ImGuiItemFlags item_flags = (g.LastItemData.ID == id ? g.LastItemData.ItemFlags : g.CurrentItemFlags);
if (flags & ImGuiButtonFlags_AllowOverlap) if (flags & ImGuiButtonFlags_AllowOverlap)
item_flags |= ImGuiItemFlags_AllowOverlap; item_flags |= ImGuiItemFlags_AllowOverlap;
if (item_flags & ImGuiItemFlags_NoFocus)
flags |= ImGuiButtonFlags_NoFocus | ImGuiButtonFlags_NoNavFocus;
// Default only reacts to left mouse button // Default only reacts to left mouse button
if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0) if ((flags & ImGuiButtonFlags_MouseButtonMask_) == 0)
@ -765,7 +807,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
SetFocusID(id, window); SetFocusID(id, window);
FocusWindow(window); FocusWindow(window);
} }
else else if (!(flags & ImGuiButtonFlags_NoFocus))
{ {
FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child
} }
@ -783,7 +825,7 @@ bool ImGui::ButtonBehavior(const ImRect& bb, ImGuiID id, bool* out_hovered, bool
SetFocusID(id, window); SetFocusID(id, window);
FocusWindow(window); FocusWindow(window);
} }
else else if (!(flags & ImGuiButtonFlags_NoFocus))
{ {
FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child FocusWindow(window, ImGuiFocusRequestFlags_RestoreFocusedChild); // Still need to focus and bring to front, but try to avoid losing NavId when navigating a child
} }
@ -1054,11 +1096,12 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos)
if (hovered) if (hovered)
window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col);
RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact);
ImU32 cross_col = GetColorU32(ImGuiCol_Text); const ImU32 cross_col = GetColorU32(ImGuiCol_Text);
ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); const ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f);
float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; const float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f;
window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, 1.0f); const float cross_thickness = 1.0f; // FIXME-DPI
window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, 1.0f); window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, cross_thickness);
window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, -cross_extent), cross_center + ImVec2(-cross_extent, +cross_extent), cross_col, cross_thickness);
return pressed; return pressed;
} }
@ -1255,9 +1298,9 @@ bool ImGui::ScrollbarEx(const ImRect& bb_frame, ImGuiID id, ImGuiAxis axis, ImS6
return held; return held;
} }
// - Read about ImTextureID here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples // - Read about ImTextureID/ImTextureRef here: https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples
// - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above. // - 'uv0' and 'uv1' are texture coordinates. Read about them from the same link above.
void ImGui::ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) void ImGui::ImageWithBg(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
ImGuiWindow* window = GetCurrentWindow(); ImGuiWindow* window = GetCurrentWindow();
@ -1275,28 +1318,28 @@ void ImGui::ImageWithBg(ImTextureID user_texture_id, const ImVec2& image_size, c
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, ImDrawFlags_None, g.Style.ImageBorderSize); window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_Border), 0.0f, ImDrawFlags_None, g.Style.ImageBorderSize);
if (bg_col.w > 0.0f) if (bg_col.w > 0.0f)
window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
} }
void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1) void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1)
{ {
ImageWithBg(user_texture_id, image_size, uv0, uv1); ImageWithBg(tex_ref, image_size, uv0, uv1);
} }
// 1.91.9 (February 2025) removed 'tint_col' and 'border_col' parameters, made border size not depend on color value. (#8131, #8238) // 1.91.9 (February 2025) removed 'tint_col' and 'border_col' parameters, made border size not depend on color value. (#8131, #8238)
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
void ImGui::Image(ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col) void ImGui::Image(ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& tint_col, const ImVec4& border_col)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
PushStyleVar(ImGuiStyleVar_ImageBorderSize, (border_col.w > 0.0f) ? ImMax(1.0f, g.Style.ImageBorderSize) : 0.0f); // Preserve legacy behavior where border is always visible when border_col's Alpha is >0.0f PushStyleVar(ImGuiStyleVar_ImageBorderSize, (border_col.w > 0.0f) ? ImMax(1.0f, g.Style.ImageBorderSize) : 0.0f); // Preserve legacy behavior where border is always visible when border_col's Alpha is >0.0f
PushStyleColor(ImGuiCol_Border, border_col); PushStyleColor(ImGuiCol_Border, border_col);
ImageWithBg(user_texture_id, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col); ImageWithBg(tex_ref, image_size, uv0, uv1, ImVec4(0, 0, 0, 0), tint_col);
PopStyleColor(); PopStyleColor();
PopStyleVar(); PopStyleVar();
} }
#endif #endif
bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags) bool ImGui::ImageButtonEx(ImGuiID id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col, ImGuiButtonFlags flags)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
ImGuiWindow* window = GetCurrentWindow(); ImGuiWindow* window = GetCurrentWindow();
@ -1318,21 +1361,21 @@ bool ImGui::ImageButtonEx(ImGuiID id, ImTextureID user_texture_id, const ImVec2&
RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding)); RenderFrame(bb.Min, bb.Max, col, true, ImClamp((float)ImMin(padding.x, padding.y), 0.0f, g.Style.FrameRounding));
if (bg_col.w > 0.0f) if (bg_col.w > 0.0f)
window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col)); window->DrawList->AddRectFilled(bb.Min + padding, bb.Max - padding, GetColorU32(bg_col));
window->DrawList->AddImage(user_texture_id, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col)); window->DrawList->AddImage(tex_ref, bb.Min + padding, bb.Max - padding, uv0, uv1, GetColorU32(tint_col));
return pressed; return pressed;
} }
// - ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button. // - ImageButton() adds style.FramePadding*2.0f to provided size. This is in order to facilitate fitting an image in a button.
// - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. (#8165) // FIXME: Maybe that's not the best design? // - ImageButton() draws a background based on regular Button() color + optionally an inner background if specified. (#8165) // FIXME: Maybe that's not the best design?
bool ImGui::ImageButton(const char* str_id, ImTextureID user_texture_id, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col) bool ImGui::ImageButton(const char* str_id, ImTextureRef tex_ref, const ImVec2& image_size, const ImVec2& uv0, const ImVec2& uv1, const ImVec4& bg_col, const ImVec4& tint_col)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow; ImGuiWindow* window = g.CurrentWindow;
if (window->SkipItems) if (window->SkipItems)
return false; return false;
return ImageButtonEx(window->GetID(str_id), user_texture_id, image_size, uv0, uv1, bg_col, tint_col); return ImageButtonEx(window->GetID(str_id), tex_ref, image_size, uv0, uv1, bg_col, tint_col);
} }
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
@ -1671,8 +1714,8 @@ bool ImGui::TextLink(const char* label)
ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z); ColorConvertHSVtoRGB(h, s, v, line_colf.x, line_colf.y, line_colf.z);
} }
float line_y = bb.Max.y + ImFloor(g.Font->Descent * g.FontScale * 0.20f); float line_y = bb.Max.y + ImFloor(g.FontBaked->Descent * g.FontBakedScale * 0.20f);
window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode. window->DrawList->AddLine(ImVec2(bb.Min.x, line_y), ImVec2(bb.Max.x, line_y), GetColorU32(line_colf)); // FIXME-TEXT: Underline mode // FIXME-DPI
PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf)); PushStyleColor(ImGuiCol_Text, GetColorU32(text_colf));
RenderText(bb.Min, label, label_end); RenderText(bb.Min, label, label_end);
@ -1682,13 +1725,13 @@ bool ImGui::TextLink(const char* label)
return pressed; return pressed;
} }
void ImGui::TextLinkOpenURL(const char* label, const char* url) bool ImGui::TextLinkOpenURL(const char* label, const char* url)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
if (url == NULL) if (url == NULL)
url = label; url = label;
if (TextLink(label)) bool pressed = TextLink(label);
if (g.PlatformIO.Platform_OpenInShellFn != NULL) if (pressed && g.PlatformIO.Platform_OpenInShellFn != NULL)
g.PlatformIO.Platform_OpenInShellFn(&g, url); g.PlatformIO.Platform_OpenInShellFn(&g, url);
SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when we same as label SetItemTooltip(LocalizeGetMsg(ImGuiLocKey_OpenLink_s), url); // It is more reassuring for user to _always_ display URL when we same as label
if (BeginPopupContextItem()) if (BeginPopupContextItem())
@ -1697,6 +1740,7 @@ void ImGui::TextLinkOpenURL(const char* label, const char* url)
SetClipboardText(url); SetClipboardText(url);
EndPopup(); EndPopup();
} }
return pressed;
} }
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
@ -1883,7 +1927,7 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end
window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness); window->DrawList->AddLine(ImVec2(sep2_x1, seps_y), ImVec2(sep2_x2, seps_y), separator_col, separator_thickness);
if (g.LogEnabled) if (g.LogEnabled)
LogSetNextTextDecoration("---", NULL); LogSetNextTextDecoration("---", NULL);
RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, bb.Max.x, label, label_end, &label_size); RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, label, label_end, &label_size);
} }
else else
{ {
@ -4103,7 +4147,7 @@ bool ImGui::InputTextWithHint(const char* label, const char* hint, char* buf, si
return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data); return InputTextEx(label, hint, buf, (int)buf_size, ImVec2(0, 0), flags, callback, user_data);
} }
// This is only used in the path where the multiline widget is inactivate. // This is only used in the path where the multiline widget is inactive.
static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end) static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char** out_text_end)
{ {
int line_count = 0; int line_count = 0;
@ -4127,9 +4171,10 @@ static int InputTextCalcTextLenAndLineCount(const char* text_begin, const char**
static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining, ImVec2* out_offset, bool stop_on_new_line) static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, const char* text_end, const char** remaining, ImVec2* out_offset, bool stop_on_new_line)
{ {
ImGuiContext& g = *ctx; ImGuiContext& g = *ctx;
ImFont* font = g.Font; //ImFont* font = g.Font;
ImFontBaked* baked = g.FontBaked;
const float line_height = g.FontSize; const float line_height = g.FontSize;
const float scale = line_height / font->FontSize; const float scale = line_height / baked->Size;
ImVec2 text_size = ImVec2(0, 0); ImVec2 text_size = ImVec2(0, 0);
float line_width = 0.0f; float line_width = 0.0f;
@ -4155,8 +4200,7 @@ static ImVec2 InputTextCalcTextSize(ImGuiContext* ctx, const char* text_begin, c
if (c == '\r') if (c == '\r')
continue; continue;
const float char_width = ((int)c < font->IndexAdvanceX.Size ? font->IndexAdvanceX.Data[c] : font->FallbackAdvanceX) * scale; line_width += baked->GetCharAdvance((ImWchar)c) * scale;
line_width += char_width;
} }
if (text_size.x < line_width) if (text_size.x < line_width)
@ -4183,7 +4227,7 @@ namespace ImStb
{ {
static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->TextLen; } static int STB_TEXTEDIT_STRINGLEN(const ImGuiInputTextState* obj) { return obj->TextLen; }
static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->TextLen); return obj->TextSrc[idx]; } static char STB_TEXTEDIT_GETCHAR(const ImGuiInputTextState* obj, int idx) { IM_ASSERT(idx <= obj->TextLen); return obj->TextSrc[idx]; }
static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.Font->GetCharAdvance((ImWchar)c) * g.FontScale; } static float STB_TEXTEDIT_GETWIDTH(ImGuiInputTextState* obj, int line_start_idx, int char_idx) { unsigned int c; ImTextCharFromUtf8(&c, obj->TextSrc + line_start_idx + char_idx, obj->TextSrc + obj->TextLen); if ((ImWchar)c == '\n') return IMSTB_TEXTEDIT_GETWIDTH_NEWLINE; ImGuiContext& g = *obj->Ctx; return g.FontBaked->GetCharAdvance((ImWchar)c) * g.FontBakedScale; }
static char STB_TEXTEDIT_NEWLINE = '\n'; static char STB_TEXTEDIT_NEWLINE = '\n';
static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx) static void STB_TEXTEDIT_LAYOUTROW(StbTexteditRow* r, ImGuiInputTextState* obj, int line_start_idx)
{ {
@ -4459,23 +4503,24 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons
if (new_text == new_text_end) if (new_text == new_text_end)
return; return;
ImGuiContext& g = *Ctx;
ImGuiInputTextState* obj = &g.InputTextState;
IM_ASSERT(obj->ID != 0 && g.ActiveId == obj->ID);
// Grow internal buffer if needed // Grow internal buffer if needed
const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0; const bool is_resizable = (Flags & ImGuiInputTextFlags_CallbackResize) != 0;
const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)ImStrlen(new_text); const int new_text_len = new_text_end ? (int)(new_text_end - new_text) : (int)ImStrlen(new_text);
if (new_text_len + BufTextLen >= BufSize) if (new_text_len + BufTextLen + 1 > obj->TextA.Size && (Flags & ImGuiInputTextFlags_ReadOnly) == 0)
{ {
if (!is_resizable) if (!is_resizable)
return; return;
ImGuiContext& g = *Ctx; IM_ASSERT(Buf == obj->TextA.Data);
ImGuiInputTextState* edit_state = &g.InputTextState;
IM_ASSERT(edit_state->ID != 0 && g.ActiveId == edit_state->ID);
IM_ASSERT(Buf == edit_state->TextA.Data);
int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1; int new_buf_size = BufTextLen + ImClamp(new_text_len * 4, 32, ImMax(256, new_text_len)) + 1;
edit_state->TextA.resize(new_buf_size + 1); obj->TextA.resize(new_buf_size + 1);
edit_state->TextSrc = edit_state->TextA.Data; obj->TextSrc = obj->TextA.Data;
Buf = edit_state->TextA.Data; Buf = obj->TextA.Data;
BufSize = edit_state->BufCapacity = new_buf_size; BufSize = obj->BufCapacity = new_buf_size;
} }
if (BufTextLen != pos) if (BufTextLen != pos)
@ -4493,18 +4538,29 @@ void ImGuiInputTextCallbackData::InsertChars(int pos, const char* new_text, cons
void ImGui::PushPasswordFont() void ImGui::PushPasswordFont()
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
ImFont* in_font = g.Font; ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked;
ImFont* out_font = &g.InputTextPasswordFont; IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0);
ImFontGlyph* glyph = in_font->FindGlyph('*'); ImFontGlyph* glyph = g.FontBaked->FindGlyph('*');
out_font->FontSize = in_font->FontSize; g.InputTextPasswordFontBackupFlags = g.Font->Flags;
out_font->Scale = in_font->Scale; backup->FallbackGlyphIndex = g.FontBaked->FallbackGlyphIndex;
out_font->Ascent = in_font->Ascent; backup->FallbackAdvanceX = g.FontBaked->FallbackAdvanceX;
out_font->Descent = in_font->Descent; backup->IndexLookup.swap(g.FontBaked->IndexLookup);
out_font->ContainerAtlas = in_font->ContainerAtlas; backup->IndexAdvanceX.swap(g.FontBaked->IndexAdvanceX);
out_font->FallbackGlyph = glyph; g.Font->Flags |= ImFontFlags_NoLoadGlyphs;
out_font->FallbackAdvanceX = glyph->AdvanceX; g.FontBaked->FallbackGlyphIndex = g.FontBaked->Glyphs.index_from_ptr(glyph);
IM_ASSERT(out_font->Glyphs.Size == 0 && out_font->IndexAdvanceX.Size == 0 && out_font->IndexLookup.Size == 0); g.FontBaked->FallbackAdvanceX = glyph->AdvanceX;
PushFont(out_font); }
void ImGui::PopPasswordFont()
{
ImGuiContext& g = *GImGui;
ImFontBaked* backup = &g.InputTextPasswordFontBackupBaked;
g.Font->Flags = g.InputTextPasswordFontBackupFlags;
g.FontBaked->FallbackGlyphIndex = backup->FallbackGlyphIndex;
g.FontBaked->FallbackAdvanceX = backup->FallbackAdvanceX;
g.FontBaked->IndexLookup.swap(backup->IndexLookup);
g.FontBaked->IndexAdvanceX.swap(backup->IndexAdvanceX);
IM_ASSERT(backup->IndexAdvanceX.Size == 0 && backup->IndexLookup.Size == 0);
} }
// Return false to discard a character. // Return false to discard a character.
@ -4868,7 +4924,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (g.ActiveId == id) if (g.ActiveId == id)
{ {
// Declare some inputs, the other are registered and polled via Shortcut() routing system. // Declare some inputs, the other are registered and polled via Shortcut() routing system.
// FIXME: The reason we don't use Shortcut() is we would need a routing flag to specify multiple mods, or to all mods combinaison into individual shortcuts. // FIXME: The reason we don't use Shortcut() is we would need a routing flag to specify multiple mods, or to all mods combination into individual shortcuts.
const ImGuiKey always_owned_keys[] = { ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_Enter, ImGuiKey_KeypadEnter, ImGuiKey_Delete, ImGuiKey_Backspace, ImGuiKey_Home, ImGuiKey_End }; const ImGuiKey always_owned_keys[] = { ImGuiKey_LeftArrow, ImGuiKey_RightArrow, ImGuiKey_Enter, ImGuiKey_KeypadEnter, ImGuiKey_Delete, ImGuiKey_Backspace, ImGuiKey_Home, ImGuiKey_End };
for (ImGuiKey key : always_owned_keys) for (ImGuiKey key : always_owned_keys)
SetKeyOwner(key, id); SetKeyOwner(key, id);
@ -5381,8 +5437,6 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
// Otherwise request text input ahead for next frame. // Otherwise request text input ahead for next frame.
if (g.ActiveId == id && clear_active_id) if (g.ActiveId == id && clear_active_id)
ClearActiveID(); ClearActiveID();
else if (g.ActiveId == id)
g.WantTextInputNextFrame = 1;
// Render frame // Render frame
if (!is_multiline) if (!is_multiline)
@ -5408,7 +5462,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
if (new_is_displaying_hint != is_displaying_hint) if (new_is_displaying_hint != is_displaying_hint)
{ {
if (is_password && !is_displaying_hint) if (is_password && !is_displaying_hint)
PopFont(); PopPasswordFont();
is_displaying_hint = new_is_displaying_hint; is_displaying_hint = new_is_displaying_hint;
if (is_password && !is_displaying_hint) if (is_password && !is_displaying_hint)
PushPasswordFont(); PushPasswordFont();
@ -5533,7 +5587,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
else else
{ {
ImVec2 rect_size = InputTextCalcTextSize(&g, p, text_selected_end, &p, NULL, true); ImVec2 rect_size = InputTextCalcTextSize(&g, p, text_selected_end, &p, NULL, true);
if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.Font->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines if (rect_size.x <= 0.0f) rect_size.x = IM_TRUNC(g.FontBaked->GetCharAdvance((ImWchar)' ') * 0.50f); // So we can see selected empty lines
ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn)); ImRect rect(rect_pos + ImVec2(0.0f, bg_offy_up - g.FontSize), rect_pos + ImVec2(rect_size.x, bg_offy_dn));
rect.ClipWith(clip_rect); rect.ClipWith(clip_rect);
if (rect.Overlaps(clip_rect)) if (rect.Overlaps(clip_rect))
@ -5560,15 +5614,19 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll);
ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f);
if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect))
draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_Text)); draw_window->DrawList->AddLine(cursor_screen_rect.Min, cursor_screen_rect.GetBL(), GetColorU32(ImGuiCol_InputTextCursor), 1.0f); // FIXME-DPI: Cursor thickness (#7031)
// Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.)
if (!is_readonly) // This is required for some backends (SDL3) to start emitting character/text inputs.
// As per #6341, make sure we don't set that on the deactivating frame.
if (!is_readonly && g.ActiveId == id)
{ {
g.PlatformImeData.WantVisible = true; ImGuiPlatformImeData* ime_data = &g.PlatformImeData; // (this is a public struct, passed to io.Platform_SetImeDataFn() handler)
g.PlatformImeData.InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize); ime_data->WantVisible = true;
g.PlatformImeData.InputLineHeight = g.FontSize; ime_data->WantTextInput = true;
g.PlatformImeViewport = window->Viewport->ID; ime_data->InputPos = ImVec2(cursor_screen_pos.x - 1.0f, cursor_screen_pos.y - g.FontSize);
ime_data->InputLineHeight = g.FontSize;
ime_data->ViewportId = window->Viewport->ID;
} }
} }
} }
@ -5595,7 +5653,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
} }
if (is_password && !is_displaying_hint) if (is_password && !is_displaying_hint)
PopFont(); PopPasswordFont();
if (is_multiline) if (is_multiline)
{ {
@ -5647,7 +5705,7 @@ void ImGui::DebugNodeInputTextState(ImGuiInputTextState* state)
Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId); Text("ID: 0x%08X, ActiveID: 0x%08X", state->ID, g.ActiveId);
DebugLocateItemOnHover(state->ID); DebugLocateItemOnHover(state->ID);
Text("CurLenA: %d, Cursor: %d, Selection: %d..%d", state->TextLen, stb_state->cursor, stb_state->select_start, stb_state->select_end); Text("CurLenA: %d, Cursor: %d, Selection: %d..%d", state->TextLen, stb_state->cursor, stb_state->select_start, stb_state->select_end);
Text("BufCapacityA: %d", state->BufCapacity); Text("BufCapacity: %d", state->BufCapacity);
Text("(Internal Buffer: TextA Size: %d, Capacity: %d)", state->TextA.Size, state->TextA.Capacity); Text("(Internal Buffer: TextA Size: %d, Capacity: %d)", state->TextA.Size, state->TextA.Capacity);
Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x); Text("has_preferred_x: %d (%.2f)", stb_state->has_preferred_x, stb_state->preferred_x);
Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point); Text("undo_point: %d, redo_point: %d, undo_char_point: %d, redo_char_point: %d", undo_state->undo_point, undo_state->redo_point, undo_state->undo_char_point, undo_state->redo_char_point);
@ -6429,7 +6487,7 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl
if (g.Style.FrameBorderSize > 0.0f) if (g.Style.FrameBorderSize > 0.0f)
RenderFrameBorder(bb.Min, bb.Max, rounding); RenderFrameBorder(bb.Min, bb.Max, rounding);
else else
window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color buttons are often in need of some sort of border window->DrawList->AddRect(bb.Min, bb.Max, GetColorU32(ImGuiCol_FrameBg), rounding); // Color buttons are often in need of some sort of border // FIXME-DPI
} }
// Drag and Drop Source // Drag and Drop Source
@ -6611,6 +6669,7 @@ void ImGui::ColorPickerOptionsPopup(const float* ref_col, ImGuiColorEditFlags fl
// - TreeNodeV() // - TreeNodeV()
// - TreeNodeEx() // - TreeNodeEx()
// - TreeNodeExV() // - TreeNodeExV()
// - TreeNodeStoreStackData() [Internal]
// - TreeNodeBehavior() [Internal] // - TreeNodeBehavior() [Internal]
// - TreePush() // - TreePush()
// - TreePop() // - TreePop()
@ -6769,18 +6828,26 @@ bool ImGui::TreeNodeUpdateNextOpen(ImGuiID storage_id, ImGuiTreeNodeFlags flags)
// Store ImGuiTreeNodeStackData for just submitted node. // Store ImGuiTreeNodeStackData for just submitted node.
// Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase. // Currently only supports 32 level deep and we are fine with (1 << Depth) overflowing into a zero, easy to increase.
static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags) static void TreeNodeStoreStackData(ImGuiTreeNodeFlags flags, float x1)
{ {
ImGuiContext& g = *GImGui; ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow; ImGuiWindow* window = g.CurrentWindow;
g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1); g.TreeNodeStack.resize(g.TreeNodeStack.Size + 1);
ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.back(); ImGuiTreeNodeStackData* tree_node_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1];
tree_node_data->ID = g.LastItemData.ID; tree_node_data->ID = g.LastItemData.ID;
tree_node_data->TreeFlags = flags; tree_node_data->TreeFlags = flags;
tree_node_data->ItemFlags = g.LastItemData.ItemFlags; tree_node_data->ItemFlags = g.LastItemData.ItemFlags;
tree_node_data->NavRect = g.LastItemData.NavRect; tree_node_data->NavRect = g.LastItemData.NavRect;
// Initially I tried to latch value for GetColorU32(ImGuiCol_TreeLines) but it's not a good trade-off for very large trees.
const bool draw_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) != 0;
tree_node_data->DrawLinesX1 = draw_lines ? (x1 + g.FontSize * 0.5f + g.Style.FramePadding.x) : +FLT_MAX;
tree_node_data->DrawLinesTableColumn = (draw_lines && g.CurrentTable) ? (ImGuiTableColumnIdx)g.CurrentTable->CurrentColumn : -1;
tree_node_data->DrawLinesToNodesY2 = -FLT_MAX;
window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth); window->DC.TreeHasStackDataDepthMask |= (1 << window->DC.TreeDepth);
if (flags & ImGuiTreeNodeFlags_DrawLinesToNodes)
window->DC.TreeRecordsClippedNodesY2Mask |= (1 << window->DC.TreeDepth);
} }
// When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop. // When using public API, currently 'id == storage_id' is always true, but we separate the values to facilitate advanced user code doing storage queries outside of UI loop.
@ -6850,14 +6917,18 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect; g.LastItemData.StatusFlags |= ImGuiItemStatusFlags_HasDisplayRect;
g.LastItemData.DisplayRect = frame_bb; g.LastItemData.DisplayRect = frame_bb;
// If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsBackHere enabled: // If a NavLeft request is happening and ImGuiTreeNodeFlags_NavLeftJumpsToParent enabled:
// Store data for the current depth to allow returning to this node from any child item. // Store data for the current depth to allow returning to this node from any child item.
// For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop(). // For this purpose we essentially compare if g.NavIdIsAlive went from 0 to 1 between TreeNode() and TreePop().
// It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsBackHere by default or move it to ImGuiStyle. // It will become tempting to enable ImGuiTreeNodeFlags_NavLeftJumpsToParent by default or move it to ImGuiStyle.
bool store_tree_node_stack_data = false; bool store_tree_node_stack_data = false;
if ((flags & ImGuiTreeNodeFlags_DrawLinesMask_) == 0)
flags |= g.Style.TreeLinesFlags;
const bool draw_tree_lines = (flags & (ImGuiTreeNodeFlags_DrawLinesFull | ImGuiTreeNodeFlags_DrawLinesToNodes)) && (frame_bb.Min.y < window->ClipRect.Max.y) && (g.Style.TreeLinesSize > 0.0f);
if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) if (!(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
{ {
if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere) && is_open && !g.NavIdIsAlive) store_tree_node_stack_data = draw_tree_lines;
if ((flags & ImGuiTreeNodeFlags_NavLeftJumpsToParent) && !g.NavIdIsAlive)
if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) if (g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
store_tree_node_stack_data = true; store_tree_node_stack_data = true;
} }
@ -6865,8 +6936,15 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0; const bool is_leaf = (flags & ImGuiTreeNodeFlags_Leaf) != 0;
if (!is_visible) if (!is_visible)
{ {
if (store_tree_node_stack_data && is_open) if ((flags & ImGuiTreeNodeFlags_DrawLinesToNodes) && (window->DC.TreeRecordsClippedNodesY2Mask & (1 << (window->DC.TreeDepth - 1))))
TreeNodeStoreStackData(flags); // Call before TreePushOverrideID() {
ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1];
parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, window->DC.CursorPos.y); // Don't need to aim to mid Y position as we are clipped anyway.
if (frame_bb.Min.y >= window->ClipRect.Max.y)
window->DC.TreeRecordsClippedNodesY2Mask &= ~(1 << (window->DC.TreeDepth - 1)); // Done
}
if (is_open && store_tree_node_stack_data)
TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID()
if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
TreePushOverrideID(id); TreePushOverrideID(id);
IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0)); IMGUI_TEST_ENGINE_ITEM_INFO(g.LastItemData.ID, label, g.LastItemData.StatusFlags | (is_leaf ? 0 : ImGuiItemStatusFlags_Openable) | (is_open ? ImGuiItemStatusFlags_Opened : 0));
@ -6912,6 +6990,8 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick; button_flags |= ImGuiButtonFlags_PressedOnClickRelease | ImGuiButtonFlags_PressedOnDoubleClick;
else else
button_flags |= ImGuiButtonFlags_PressedOnClickRelease; button_flags |= ImGuiButtonFlags_PressedOnClickRelease;
if (flags & ImGuiTreeNodeFlags_NoNavFocus)
button_flags |= ImGuiButtonFlags_NoNavFocus;
bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0; bool selected = (flags & ImGuiTreeNodeFlags_Selected) != 0;
const bool was_selected = selected; const bool was_selected = selected;
@ -6998,6 +7078,8 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header); const ImU32 bg_col = GetColorU32((held && hovered) ? ImGuiCol_HeaderActive : hovered ? ImGuiCol_HeaderHovered : ImGuiCol_Header);
RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding); RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, true, style.FrameRounding);
RenderNavCursor(frame_bb, id, nav_render_cursor_flags); RenderNavCursor(frame_bb, id, nav_render_cursor_flags);
if (span_all_columns && !span_all_columns_label)
TablePopBackgroundChannel();
if (flags & ImGuiTreeNodeFlags_Bullet) if (flags & ImGuiTreeNodeFlags_Bullet)
RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col); RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.60f, text_pos.y + g.FontSize * 0.5f), text_col);
else if (!is_leaf) else if (!is_leaf)
@ -7018,6 +7100,8 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false); RenderFrame(frame_bb.Min, frame_bb.Max, bg_col, false);
} }
RenderNavCursor(frame_bb, id, nav_render_cursor_flags); RenderNavCursor(frame_bb, id, nav_render_cursor_flags);
if (span_all_columns && !span_all_columns_label)
TablePopBackgroundChannel();
if (flags & ImGuiTreeNodeFlags_Bullet) if (flags & ImGuiTreeNodeFlags_Bullet)
RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col); RenderBullet(window->DrawList, ImVec2(text_pos.x - text_offset_x * 0.5f, text_pos.y + g.FontSize * 0.5f), text_col);
else if (!is_leaf) else if (!is_leaf)
@ -7026,8 +7110,8 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
LogSetNextTextDecoration(">", NULL); LogSetNextTextDecoration(">", NULL);
} }
if (span_all_columns && !span_all_columns_label) if (draw_tree_lines)
TablePopBackgroundChannel(); TreeNodeDrawLineToChildNode(ImVec2(text_pos.x - text_offset_x + padding.x, text_pos.y + g.FontSize * 0.5f));
// Label // Label
if (display_frame) if (display_frame)
@ -7039,8 +7123,8 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
TablePopBackgroundChannel(); TablePopBackgroundChannel();
} }
if (store_tree_node_stack_data && is_open) if (is_open && store_tree_node_stack_data)
TreeNodeStoreStackData(flags); // Call before TreePushOverrideID() TreeNodeStoreStackData(flags, text_pos.x - text_offset_x); // Call before TreePushOverrideID()
if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen)) if (is_open && !(flags & ImGuiTreeNodeFlags_NoTreePushOnOpen))
TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice TreePushOverrideID(id); // Could use TreePush(label) but this avoid computing twice
@ -7048,6 +7132,64 @@ bool ImGui::TreeNodeBehavior(ImGuiID id, ImGuiTreeNodeFlags flags, const char* l
return is_open; return is_open;
} }
// Draw horizontal line from our parent node
// This is only called for visible child nodes so we are not too fussy anymore about performances
void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
if (window->DC.TreeDepth == 0 || (window->DC.TreeHasStackDataDepthMask & (1 << (window->DC.TreeDepth - 1))) == 0)
return;
ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1];
float x1 = ImTrunc(parent_data->DrawLinesX1);
float x2 = ImTrunc(target_pos.x - g.Style.ItemInnerSpacing.x);
float y = ImTrunc(target_pos.y);
float rounding = (g.Style.TreeLinesRounding > 0.0f) ? ImMin(x2 - x1, g.Style.TreeLinesRounding) : 0.0f;
parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, y - rounding);
if (x1 >= x2)
return;
if (rounding > 0.0f)
{
x1 += 0.5f + rounding;
window->DrawList->PathArcToFast(ImVec2(x1, y - rounding), rounding, 6, 3);
if (x1 < x2)
window->DrawList->PathLineTo(ImVec2(x2, y));
window->DrawList->PathStroke(GetColorU32(ImGuiCol_TreeLines), ImDrawFlags_None, g.Style.TreeLinesSize);
}
else
{
window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize);
}
}
// Draw vertical line of the hierarchy
void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = g.CurrentWindow;
float y1 = ImMax(data->NavRect.Max.y, window->ClipRect.Min.y);
float y2 = data->DrawLinesToNodesY2;
if (data->TreeFlags & ImGuiTreeNodeFlags_DrawLinesFull)
{
float y2_full = window->DC.CursorPos.y;
if (g.CurrentTable)
y2_full = ImMax(g.CurrentTable->RowPosY2, y2_full);
y2_full = ImTrunc(y2_full - g.Style.ItemSpacing.y - g.FontSize * 0.5f);
if (y2 + (g.Style.ItemSpacing.y + g.Style.TreeLinesRounding) < y2_full) // FIXME: threshold to use ToNodes Y2 instead of Full Y2 when close by ItemSpacing.y
y2 = y2_full;
}
y2 = ImMin(y2, window->ClipRect.Max.y);
if (y2 <= y1)
return;
float x = ImTrunc(data->DrawLinesX1);
if (data->DrawLinesTableColumn != -1)
TablePushColumnChannel(data->DrawLinesTableColumn);
window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize);
if (data->DrawLinesTableColumn != -1)
TablePopColumnChannel();
}
void ImGui::TreePush(const char* str_id) void ImGui::TreePush(const char* str_id)
{ {
ImGuiWindow* window = GetCurrentWindow(); ImGuiWindow* window = GetCurrentWindow();
@ -7082,18 +7224,23 @@ void ImGui::TreePop()
window->DC.TreeDepth--; window->DC.TreeDepth--;
ImU32 tree_depth_mask = (1 << window->DC.TreeDepth); ImU32 tree_depth_mask = (1 << window->DC.TreeDepth);
if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask) // Only set during request if (window->DC.TreeHasStackDataDepthMask & tree_depth_mask)
{ {
ImGuiTreeNodeStackData* data = &g.TreeNodeStack.back(); const ImGuiTreeNodeStackData* data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1];
IM_ASSERT(data->ID == window->IDStack.back()); IM_ASSERT(data->ID == window->IDStack.back());
if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsBackHere)
{ // Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsToParent is enabled)
// Handle Left arrow to move to parent tree node (when ImGuiTreeNodeFlags_NavLeftJumpsBackHere is enabled) if (data->TreeFlags & ImGuiTreeNodeFlags_NavLeftJumpsToParent)
if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet()) if (g.NavIdIsAlive && g.NavMoveDir == ImGuiDir_Left && g.NavWindow == window && NavMoveRequestButNoResultYet())
NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data); NavMoveRequestResolveWithPastTreeNode(&g.NavMoveResultLocal, data);
}
// Draw hierarchy lines
if (data->DrawLinesX1 != +FLT_MAX && window->DC.CursorPos.y >= window->ClipRect.Min.y)
TreeNodeDrawLineToTreePop(data);
g.TreeNodeStack.pop_back(); g.TreeNodeStack.pop_back();
window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask; window->DC.TreeHasStackDataDepthMask &= ~tree_depth_mask;
window->DC.TreeRecordsClippedNodesY2Mask &= ~tree_depth_mask;
} }
IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much. IM_ASSERT(window->IDStack.Size > 1); // There should always be 1 element in the IDStack (pushed during window creation). If this triggers you called TreePop/PopID too much.
@ -7726,7 +7873,7 @@ void ImGui::EndBoxSelect(const ImRect& scope_rect, ImGuiMultiSelectFlags ms_flag
ImRect box_select_r = bs->BoxSelectRectCurr; ImRect box_select_r = bs->BoxSelectRectCurr;
box_select_r.ClipWith(scope_rect); box_select_r.ClipWith(scope_rect);
window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling window->DrawList->AddRectFilled(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_SeparatorHovered, 0.30f)); // FIXME-MULTISELECT: Styling
window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT: Styling window->DrawList->AddRect(box_select_r.Min, box_select_r.Max, GetColorU32(ImGuiCol_NavCursor)); // FIXME-MULTISELECT FIXME-DPI: Styling
// Scroll // Scroll
const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0; const bool enable_scroll = (ms_flags & ImGuiMultiSelectFlags_ScopeWindow) && (ms_flags & ImGuiMultiSelectFlags_BoxSelectNoScroll) == 0;
@ -7930,7 +8077,7 @@ ImGuiMultiSelectIO* ImGui::EndMultiSelect()
if (ms->IsFocused) if (ms->IsFocused)
{ {
// We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here. // We currently don't allow user code to modify RangeSrcItem by writing to BeginIO's version, but that would be an easy change here.
if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at begining of the scope (see tests for easy failure) if (ms->IO.RangeSrcReset || (ms->RangeSrcPassedBy == false && ms->IO.RangeSrcItem != ImGuiSelectionUserData_Invalid)) // Can't read storage->RangeSrcItem here -> we want the state at beginning of the scope (see tests for easy failure)
{ {
IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n"); // Will set be to NavId. IMGUI_DEBUG_LOG_SELECTION("[selection] EndMultiSelect: Reset RangeSrcItem.\n"); // Will set be to NavId.
storage->RangeSrcItem = ImGuiSelectionUserData_Invalid; storage->RangeSrcItem = ImGuiSelectionUserData_Invalid;
@ -10739,13 +10886,12 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb,
#endif #endif
// Render text label (with clipping + alpha gradient) + unsaved marker // Render text label (with clipping + alpha gradient) + unsaved marker
ImRect text_pixel_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y); ImRect text_ellipsis_clip_bb(bb.Min.x + frame_padding.x, bb.Min.y + frame_padding.y, bb.Max.x - frame_padding.x, bb.Max.y);
ImRect text_ellipsis_clip_bb = text_pixel_clip_bb;
// Return clipped state ignoring the close button // Return clipped state ignoring the close button
if (out_text_clipped) if (out_text_clipped)
{ {
*out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_pixel_clip_bb.Max.x; *out_text_clipped = (text_ellipsis_clip_bb.Min.x + label_size.x) > text_ellipsis_clip_bb.Max.x;
//draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255)); //draw_list->AddCircle(text_ellipsis_clip_bb.Min, 3.0f, *out_text_clipped ? IM_COL32(255, 0, 0, 255) : IM_COL32(0, 255, 0, 255));
} }
@ -10791,15 +10937,22 @@ void ImGui::TabItemLabelAndCloseButton(ImDrawList* draw_list, const ImRect& bb,
// This is all rather complicated // This is all rather complicated
// (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position) // (the main idea is that because the close button only appears on hover, we don't want it to alter the ellipsis position)
// FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist.. // FIXME: if FramePadding is noticeably large, ellipsis_max_x will be wrong here (e.g. #3497), maybe for consistency that parameter of RenderTextEllipsis() shouldn't exist..
float ellipsis_max_x = close_button_visible ? text_pixel_clip_bb.Max.x : bb.Max.x - 1.0f; float ellipsis_max_x = text_ellipsis_clip_bb.Max.x;
if (close_button_visible || unsaved_marker_visible) if (close_button_visible || unsaved_marker_visible)
{ {
text_pixel_clip_bb.Max.x -= close_button_visible ? (button_sz) : (button_sz * 0.80f); const bool visible_without_hover = unsaved_marker_visible || (is_contents_visible ? g.Style.TabCloseButtonMinWidthSelected : g.Style.TabCloseButtonMinWidthUnselected) < 0.0f;
text_ellipsis_clip_bb.Max.x -= unsaved_marker_visible ? (button_sz * 0.80f) : 0.0f; if (visible_without_hover)
ellipsis_max_x = text_pixel_clip_bb.Max.x; {
text_ellipsis_clip_bb.Max.x -= button_sz * 0.90f;
ellipsis_max_x -= button_sz * 0.90f;
}
else
{
text_ellipsis_clip_bb.Max.x -= button_sz * 1.00f;
}
} }
LogSetNextTextDecoration("/", "\\"); LogSetNextTextDecoration("/", "\\");
RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, text_pixel_clip_bb.Max.x, ellipsis_max_x, label, NULL, &label_size); RenderTextEllipsis(draw_list, text_ellipsis_clip_bb.Min, text_ellipsis_clip_bb.Max, ellipsis_max_x, label, NULL, &label_size);
#if 0 #if 0
if (!is_contents_visible) if (!is_contents_visible)

View file

@ -141,6 +141,7 @@
// with previous char) // with previous char)
// STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character // STB_TEXTEDIT_KEYTOTEXT(k) maps a keyboard input to an insertable character
// (return type is int, -1 means not valid to insert) // (return type is int, -1 means not valid to insert)
// (not supported if you want to use UTF-8, see below)
// STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based // STB_TEXTEDIT_GETCHAR(obj,i) returns the i'th character of obj, 0-based
// STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize // STB_TEXTEDIT_NEWLINE the character returned by _GETCHAR() we recognize
// as manually wordwrapping for end-of-line positioning // as manually wordwrapping for end-of-line positioning
@ -178,6 +179,13 @@
// STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text // STB_TEXTEDIT_K_TEXTSTART2 secondary keyboard input to move cursor to start of text
// STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text // STB_TEXTEDIT_K_TEXTEND2 secondary keyboard input to move cursor to end of text
// //
// To support UTF-8:
//
// STB_TEXTEDIT_GETPREVCHARINDEX returns index of previous character
// STB_TEXTEDIT_GETNEXTCHARINDEX returns index of next character
// Do NOT define STB_TEXTEDIT_KEYTOTEXT.
// Instead, call stb_textedit_text() directly for text contents.
//
// Keyboard input must be encoded as a single integer value; e.g. a character code // Keyboard input must be encoded as a single integer value; e.g. a character code
// and some bitflags that represent shift states. to simplify the interface, SHIFT must // and some bitflags that represent shift states. to simplify the interface, SHIFT must
// be a bitflag, so we can test the shifted state of cursor movements to allow selection, // be a bitflag, so we can test the shifted state of cursor movements to allow selection,
@ -250,8 +258,10 @@
// if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are // if the STB_TEXTEDIT_KEYTOTEXT function is defined, selected keys are
// transformed into text and stb_textedit_text() is automatically called. // transformed into text and stb_textedit_text() is automatically called.
// //
// text: [DEAR IMGUI] added 2024-09 // text: (added 2025)
// call this to text inputs sent to the textfield. // call this to directly send text input the textfield, which is required
// for UTF-8 support, because stb_textedit_key() + STB_TEXTEDIT_KEYTOTEXT()
// cannot infer text length.
// //
// //
// When rendering, you can read the cursor position and selection state from // When rendering, you can read the cursor position and selection state from
@ -400,6 +410,16 @@ typedef struct
#define IMSTB_TEXTEDIT_memmove memmove #define IMSTB_TEXTEDIT_memmove memmove
#endif #endif
// [DEAR IMGUI]
// Functions must be implemented for UTF8 support
// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
// There is not necessarily a '[DEAR IMGUI]' at the usage sites.
#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(OBJ, IDX) ((IDX) - 1)
#endif
#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(OBJ, IDX) ((IDX) + 1)
#endif
///////////////////////////////////////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////
// //
@ -648,17 +668,6 @@ static void stb_textedit_move_to_last(IMSTB_TEXTEDIT_STRING *str, STB_TexteditSt
} }
} }
// [DEAR IMGUI]
// Functions must be implemented for UTF8 support
// Code in this file that uses those functions is modified for [DEAR IMGUI] and deviates from the original stb_textedit.
// There is not necessarily a '[DEAR IMGUI]' at the usage sites.
#ifndef IMSTB_TEXTEDIT_GETPREVCHARINDEX
#define IMSTB_TEXTEDIT_GETPREVCHARINDEX(obj, idx) (idx - 1)
#endif
#ifndef IMSTB_TEXTEDIT_GETNEXTCHARINDEX
#define IMSTB_TEXTEDIT_GETNEXTCHARINDEX(obj, idx) (idx + 1)
#endif
#ifdef STB_TEXTEDIT_IS_SPACE #ifdef STB_TEXTEDIT_IS_SPACE
static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx ) static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )
{ {
@ -668,9 +677,9 @@ static int is_word_boundary( IMSTB_TEXTEDIT_STRING *str, int idx )
#ifndef STB_TEXTEDIT_MOVEWORDLEFT #ifndef STB_TEXTEDIT_MOVEWORDLEFT
static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c ) static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c )
{ {
--c; // always move at least one character c = IMSTB_TEXTEDIT_GETPREVCHARINDEX( str, c ); // always move at least one character
while (c >= 0 && !is_word_boundary(str, c)) while (c >= 0 && !is_word_boundary(str, c))
--c; c = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, c);
if( c < 0 ) if( c < 0 )
c = 0; c = 0;
@ -684,9 +693,9 @@ static int stb_textedit_move_to_word_previous( IMSTB_TEXTEDIT_STRING *str, int c
static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c ) static int stb_textedit_move_to_word_next( IMSTB_TEXTEDIT_STRING *str, int c )
{ {
const int len = STB_TEXTEDIT_STRINGLEN(str); const int len = STB_TEXTEDIT_STRINGLEN(str);
++c; // always move at least one character c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c); // always move at least one character
while( c < len && !is_word_boundary( str, c ) ) while( c < len && !is_word_boundary( str, c ) )
++c; c = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, c);
if( c > len ) if( c > len )
c = len; c = len;
@ -742,6 +751,7 @@ static int stb_textedit_paste_internal(IMSTB_TEXTEDIT_STRING *str, STB_TexteditS
#define STB_TEXTEDIT_KEYTYPE int #define STB_TEXTEDIT_KEYTYPE int
#endif #endif
// API key: process text input
// [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility. // [DEAR IMGUI] Added stb_textedit_text(), extracted out and called by stb_textedit_key() for backward compatibility.
static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len) static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* state, const IMSTB_TEXTEDIT_CHARTYPE* text, int text_len)
{ {
@ -756,8 +766,7 @@ static void stb_textedit_text(IMSTB_TEXTEDIT_STRING* str, STB_TexteditState* sta
state->cursor += text_len; state->cursor += text_len;
state->has_preferred_x = 0; state->has_preferred_x = 0;
} }
} } else {
else {
stb_textedit_delete_selection(str, state); // implicitly clamps stb_textedit_delete_selection(str, state); // implicitly clamps
if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) { if (STB_TEXTEDIT_INSERTCHARS(str, state->cursor, text, text_len)) {
stb_text_makeundo_insert(state, state->cursor, text_len); stb_text_makeundo_insert(state, state->cursor, text_len);
@ -774,6 +783,7 @@ retry:
switch (key) { switch (key) {
default: { default: {
#ifdef STB_TEXTEDIT_KEYTOTEXT #ifdef STB_TEXTEDIT_KEYTOTEXT
// This is not suitable for UTF-8 support.
int c = STB_TEXTEDIT_KEYTOTEXT(key); int c = STB_TEXTEDIT_KEYTOTEXT(key);
if (c > 0) { if (c > 0) {
// TODO: PLEASE CHECK FOR BREAKAGE. // TODO: PLEASE CHECK FOR BREAKAGE.
@ -922,8 +932,9 @@ retry:
state->cursor = start; state->cursor = start;
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
x = row.x0; x = row.x0;
for (i=0; i < row.num_chars; ++i) { for (i=0; i < row.num_chars; ) {
float dx = STB_TEXTEDIT_GETWIDTH(str, start, i); float dx = STB_TEXTEDIT_GETWIDTH(str, start, i);
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
break; break;
@ -931,7 +942,8 @@ retry:
x += dx; x += dx;
if (x > goal_x) if (x > goal_x)
break; break;
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); i += next - state->cursor;
state->cursor = next;
} }
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
@ -984,8 +996,9 @@ retry:
state->cursor = find.prev_first; state->cursor = find.prev_first;
STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor); STB_TEXTEDIT_LAYOUTROW(&row, str, state->cursor);
x = row.x0; x = row.x0;
for (i=0; i < row.num_chars; ++i) { for (i=0; i < row.num_chars; ) {
float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i); float dx = STB_TEXTEDIT_GETWIDTH(str, find.prev_first, i);
int next = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
#ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE #ifdef IMSTB_TEXTEDIT_GETWIDTH_NEWLINE
if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE) if (dx == IMSTB_TEXTEDIT_GETWIDTH_NEWLINE)
break; break;
@ -993,7 +1006,8 @@ retry:
x += dx; x += dx;
if (x > goal_x) if (x > goal_x)
break; break;
state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor); i += next - state->cursor;
state->cursor = next;
} }
stb_textedit_clamp(str, state); stb_textedit_clamp(str, state);
@ -1006,8 +1020,13 @@ retry:
// go to previous line // go to previous line
// (we need to scan previous line the hard way. maybe we could expose this as a new API function?) // (we need to scan previous line the hard way. maybe we could expose this as a new API function?)
prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0; prev_scan = find.prev_first > 0 ? find.prev_first - 1 : 0;
while (prev_scan > 0 && STB_TEXTEDIT_GETCHAR(str, prev_scan - 1) != STB_TEXTEDIT_NEWLINE) while (prev_scan > 0)
--prev_scan; {
int prev = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, prev_scan);
if (STB_TEXTEDIT_GETCHAR(str, prev) == STB_TEXTEDIT_NEWLINE)
break;
prev_scan = prev;
}
find.first_char = find.prev_first; find.first_char = find.prev_first;
find.prev_first = prev_scan; find.prev_first = prev_scan;
} }
@ -1086,7 +1105,7 @@ retry:
if (state->single_line) if (state->single_line)
state->cursor = 0; state->cursor = 0;
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
--state->cursor; state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
@ -1100,7 +1119,7 @@ retry:
if (state->single_line) if (state->single_line)
state->cursor = n; state->cursor = n;
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
++state->cursor; state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
} }
@ -1114,7 +1133,7 @@ retry:
if (state->single_line) if (state->single_line)
state->cursor = 0; state->cursor = 0;
else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE) else while (state->cursor > 0 && STB_TEXTEDIT_GETCHAR(str, state->cursor-1) != STB_TEXTEDIT_NEWLINE)
--state->cursor; state->cursor = IMSTB_TEXTEDIT_GETPREVCHARINDEX(str, state->cursor);
state->select_end = state->cursor; state->select_end = state->cursor;
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;
@ -1129,7 +1148,7 @@ retry:
if (state->single_line) if (state->single_line)
state->cursor = n; state->cursor = n;
else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE) else while (state->cursor < n && STB_TEXTEDIT_GETCHAR(str, state->cursor) != STB_TEXTEDIT_NEWLINE)
++state->cursor; state->cursor = IMSTB_TEXTEDIT_GETNEXTCHARINDEX(str, state->cursor);
state->select_end = state->cursor; state->select_end = state->cursor;
state->has_preferred_x = 0; state->has_preferred_x = 0;
break; break;

View file

@ -4516,8 +4516,8 @@ static int stbtt__compute_crossings_x(float x, float y, int nverts, stbtt_vertex
q2[0] = (float)x2; q2[0] = (float)x2;
q2[1] = (float)y2; q2[1] = (float)y2;
if (equal(q0,q1) || equal(q1,q2)) { if (equal(q0,q1) || equal(q1,q2)) {
x0 = (int)verts[i-1].x; x0 = (int)verts[i-1].x; //-V1048
y0 = (int)verts[i-1].y; y0 = (int)verts[i-1].y; //-V1048
x1 = (int)verts[i ].x; x1 = (int)verts[i ].x;
y1 = (int)verts[i ].y; y1 = (int)verts[i ].y;
if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) { if (y > STBTT_min(y0,y1) && y < STBTT_max(y0,y1) && x > STBTT_min(x0,x1)) {

File diff suppressed because it is too large Load diff

View file

@ -6,7 +6,9 @@
#ifndef IMGUI_DISABLE #ifndef IMGUI_DISABLE
// Usage: // Usage:
// - Add '#define IMGUI_ENABLE_FREETYPE' in your imconfig to enable support for imgui_freetype in imgui. // - Add '#define IMGUI_ENABLE_FREETYPE' in your imconfig to automatically enable support
// for imgui_freetype in imgui. It is equivalent to selecting the default loader with:
// io.Fonts.FontLoader = ImGuiFreeType::GetFontLoader()
// Optional support for OpenType SVG fonts: // Optional support for OpenType SVG fonts:
// - Add '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG' to use plutosvg (not provided). See #7927. // - Add '#define IMGUI_ENABLE_FREETYPE_PLUTOSVG' to use plutosvg (not provided). See #7927.
@ -14,44 +16,67 @@
// Forward declarations // Forward declarations
struct ImFontAtlas; struct ImFontAtlas;
struct ImFontBuilderIO; struct ImFontLoader;
// Hinting greatly impacts visuals (and glyph sizes). // Hinting greatly impacts visuals (and glyph sizes).
// - By default, hinting is enabled and the font's native hinter is preferred over the auto-hinter. // - By default, hinting is enabled and the font's native hinter is preferred over the auto-hinter.
// - When disabled, FreeType generates blurrier glyphs, more or less matches the stb_truetype.h // - When disabled, FreeType generates blurrier glyphs, more or less matches the stb_truetype.h
// - The Default hinting mode usually looks good, but may distort glyphs in an unusual way. // - The Default hinting mode usually looks good, but may distort glyphs in an unusual way.
// - The Light hinting mode generates fuzzier glyphs but better matches Microsoft's rasterizer. // - The Light hinting mode generates fuzzier glyphs but better matches Microsoft's rasterizer.
// You can set those flags globaly in ImFontAtlas::FontBuilderFlags // You can set those flags globally in ImFontAtlas::FontLoaderFlags
// You can set those flags on a per font basis in ImFontConfig::FontBuilderFlags // You can set those flags on a per font basis in ImFontConfig::FontLoaderFlags
enum ImGuiFreeTypeBuilderFlags typedef unsigned int ImGuiFreeTypeLoaderFlags;
enum ImGuiFreeTypeLoaderFlags_
{ {
ImGuiFreeTypeBuilderFlags_NoHinting = 1 << 0, // Disable hinting. This generally generates 'blurrier' bitmap glyphs when the glyph are rendered in any of the anti-aliased modes. ImGuiFreeTypeLoaderFlags_NoHinting = 1 << 0, // Disable hinting. This generally generates 'blurrier' bitmap glyphs when the glyph are rendered in any of the anti-aliased modes.
ImGuiFreeTypeBuilderFlags_NoAutoHint = 1 << 1, // Disable auto-hinter. ImGuiFreeTypeLoaderFlags_NoAutoHint = 1 << 1, // Disable auto-hinter.
ImGuiFreeTypeBuilderFlags_ForceAutoHint = 1 << 2, // Indicates that the auto-hinter is preferred over the font's native hinter. ImGuiFreeTypeLoaderFlags_ForceAutoHint = 1 << 2, // Indicates that the auto-hinter is preferred over the font's native hinter.
ImGuiFreeTypeBuilderFlags_LightHinting = 1 << 3, // A lighter hinting algorithm for gray-level modes. Many generated glyphs are fuzzier but better resemble their original shape. This is achieved by snapping glyphs to the pixel grid only vertically (Y-axis), as is done by Microsoft's ClearType and Adobe's proprietary font renderer. This preserves inter-glyph spacing in horizontal text. ImGuiFreeTypeLoaderFlags_LightHinting = 1 << 3, // A lighter hinting algorithm for gray-level modes. Many generated glyphs are fuzzier but better resemble their original shape. This is achieved by snapping glyphs to the pixel grid only vertically (Y-axis), as is done by Microsoft's ClearType and Adobe's proprietary font renderer. This preserves inter-glyph spacing in horizontal text.
ImGuiFreeTypeBuilderFlags_MonoHinting = 1 << 4, // Strong hinting algorithm that should only be used for monochrome output. ImGuiFreeTypeLoaderFlags_MonoHinting = 1 << 4, // Strong hinting algorithm that should only be used for monochrome output.
ImGuiFreeTypeBuilderFlags_Bold = 1 << 5, // Styling: Should we artificially embolden the font? ImGuiFreeTypeLoaderFlags_Bold = 1 << 5, // Styling: Should we artificially embolden the font?
ImGuiFreeTypeBuilderFlags_Oblique = 1 << 6, // Styling: Should we slant the font, emulating italic style? ImGuiFreeTypeLoaderFlags_Oblique = 1 << 6, // Styling: Should we slant the font, emulating italic style?
ImGuiFreeTypeBuilderFlags_Monochrome = 1 << 7, // Disable anti-aliasing. Combine this with MonoHinting for best results! ImGuiFreeTypeLoaderFlags_Monochrome = 1 << 7, // Disable anti-aliasing. Combine this with MonoHinting for best results!
ImGuiFreeTypeBuilderFlags_LoadColor = 1 << 8, // Enable FreeType color-layered glyphs ImGuiFreeTypeLoaderFlags_LoadColor = 1 << 8, // Enable FreeType color-layered glyphs
ImGuiFreeTypeBuilderFlags_Bitmap = 1 << 9 // Enable FreeType bitmap glyphs ImGuiFreeTypeLoaderFlags_Bitmap = 1 << 9, // Enable FreeType bitmap glyphs
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
ImGuiFreeTypeBuilderFlags_NoHinting = ImGuiFreeTypeLoaderFlags_NoHinting,
ImGuiFreeTypeBuilderFlags_NoAutoHint = ImGuiFreeTypeLoaderFlags_NoAutoHint,
ImGuiFreeTypeBuilderFlags_ForceAutoHint = ImGuiFreeTypeLoaderFlags_ForceAutoHint,
ImGuiFreeTypeBuilderFlags_LightHinting = ImGuiFreeTypeLoaderFlags_LightHinting,
ImGuiFreeTypeBuilderFlags_MonoHinting = ImGuiFreeTypeLoaderFlags_MonoHinting,
ImGuiFreeTypeBuilderFlags_Bold = ImGuiFreeTypeLoaderFlags_Bold,
ImGuiFreeTypeBuilderFlags_Oblique = ImGuiFreeTypeLoaderFlags_Oblique,
ImGuiFreeTypeBuilderFlags_Monochrome = ImGuiFreeTypeLoaderFlags_Monochrome,
ImGuiFreeTypeBuilderFlags_LoadColor = ImGuiFreeTypeLoaderFlags_LoadColor,
ImGuiFreeTypeBuilderFlags_Bitmap = ImGuiFreeTypeLoaderFlags_Bitmap,
#endif
}; };
// Obsolete names (will be removed)
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
typedef ImGuiFreeTypeLoaderFlags_ ImGuiFreeTypeBuilderFlags_;
#endif
namespace ImGuiFreeType namespace ImGuiFreeType
{ {
// This is automatically assigned when using '#define IMGUI_ENABLE_FREETYPE'. // This is automatically assigned when using '#define IMGUI_ENABLE_FREETYPE'.
// If you need to dynamically select between multiple builders: // If you need to dynamically select between multiple builders:
// - you can manually assign this builder with 'atlas->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType()' // - you can manually assign this builder with 'atlas->FontLoader = ImGuiFreeType::GetFontLoader()'
// - prefer deep-copying this into your own ImFontBuilderIO instance if you use hot-reloading that messes up static data. // - prefer deep-copying this into your own ImFontLoader instance if you use hot-reloading that messes up static data.
IMGUI_API const ImFontBuilderIO* GetBuilderForFreeType(); IMGUI_API const ImFontLoader* GetFontLoader();
// Override allocators. By default ImGuiFreeType will use IM_ALLOC()/IM_FREE() // Override allocators. By default ImGuiFreeType will use IM_ALLOC()/IM_FREE()
// However, as FreeType does lots of allocations we provide a way for the user to redirect it to a separate memory heap if desired. // However, as FreeType does lots of allocations we provide a way for the user to redirect it to a separate memory heap if desired.
IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data = nullptr); IMGUI_API void SetAllocatorFunctions(void* (*alloc_func)(size_t sz, void* user_data), void (*free_func)(void* ptr, void* user_data), void* user_data = nullptr);
// Obsolete names (will be removed soon) // Display UI to edit ImFontAtlas::FontLoaderFlags (shared) or ImFontConfig::FontLoaderFlags (single source)
IMGUI_API bool DebugEditFontLoaderFlags(ImGuiFreeTypeLoaderFlags* p_font_loader_flags);
// Obsolete names (will be removed)
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//static inline bool BuildFontAtlas(ImFontAtlas* atlas, unsigned int flags = 0) { atlas->FontBuilderIO = GetBuilderForFreeType(); atlas->FontBuilderFlags = flags; return atlas->Build(); } // Prefer using '#define IMGUI_ENABLE_FREETYPE' //IMGUI_API const ImFontBuilderIO* GetBuilderForFreeType(); // Renamed/changed in 1.92. Change 'io.Fonts->FontBuilderIO = ImGuiFreeType::GetBuilderForFreeType()' to 'io.Fonts.FontLoader = ImGuiFreeType::GetFontLoader()' if you need runtime selection.
//static inline bool BuildFontAtlas(ImFontAtlas* atlas, unsigned int flags = 0) { atlas->FontBuilderIO = GetBuilderForFreeType(); atlas->FontLoaderFlags = flags; return atlas->Build(); } // Prefer using '#define IMGUI_ENABLE_FREETYPE'
#endif #endif
} }

View file

@ -647,6 +647,7 @@ static void paint_imgui(uint32_t *pixels, ImDrawData *drawData, int fb_width, in
bool ImGui_ImplSW_Init(SDL_Window* win) { bool ImGui_ImplSW_Init(SDL_Window* win) {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
ImGuiIO& platform_io = ImGui::GetPlatformIO();
IM_ASSERT(io.BackendRendererUserData == nullptr); IM_ASSERT(io.BackendRendererUserData == nullptr);
if (SDL_HasWindowSurface(win)==SDL_FALSE) { if (SDL_HasWindowSurface(win)==SDL_FALSE) {
@ -657,6 +658,9 @@ bool ImGui_ImplSW_Init(SDL_Window* win) {
bd->Window = win; bd->Window = win;
io.BackendRendererUserData = (void*)bd; io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_sw"; io.BackendRendererName = "imgui_sw";
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures;
platform_io.Renderer_TextureMaxWidth = platform_io.Renderer_TextureMaxHeight = (int)4096;
return true; return true;
} }
@ -669,6 +673,7 @@ void ImGui_ImplSW_Shutdown() {
ImGui_ImplSW_DestroyDeviceObjects(); ImGui_ImplSW_DestroyDeviceObjects();
io.BackendRendererName = nullptr; io.BackendRendererName = nullptr;
io.BackendRendererUserData = nullptr; io.BackendRendererUserData = nullptr;
io.BackendFlags &= ~ImGuiBackendFlags_RendererHasTextures;
IM_DELETE(bd); IM_DELETE(bd);
} }
@ -676,8 +681,6 @@ bool ImGui_ImplSW_NewFrame() {
ImGui_ImplSW_Data* bd = ImGui_ImplSW_GetBackendData(); ImGui_ImplSW_Data* bd = ImGui_ImplSW_GetBackendData();
IM_ASSERT(bd != nullptr); IM_ASSERT(bd != nullptr);
if (!bd->FontTexture) ImGui_ImplSW_CreateDeviceObjects();
return true; return true;
} }
@ -728,7 +731,7 @@ void ImGui_ImplSW_DestroyFontsTexture() {
} }
bool ImGui_ImplSW_CreateDeviceObjects() { bool ImGui_ImplSW_CreateDeviceObjects() {
return ImGui_ImplSW_CreateFontsTexture(); return true;
} }
void ImGui_ImplSW_DestroyDeviceObjects() { void ImGui_ImplSW_DestroyDeviceObjects() {

View file

@ -9,7 +9,7 @@
// The goal was to get something fast and decently accurate in not too many lines of code. // The goal was to get something fast and decently accurate in not too many lines of code.
// LIMITATIONS: // LIMITATIONS:
// * It is not pixel-perfect, but it is good enough for must use cases. // * It is not pixel-perfect, but it is good enough for must use cases.
// * It does not support painting with any other texture than the default font texture. // * It does not support texture scaling for the sake of performance.
#pragma once #pragma once
#include "imgui.h" // IMGUI_IMPL_API #include "imgui.h" // IMGUI_IMPL_API
#ifndef IMGUI_DISABLE #ifndef IMGUI_DISABLE