Merge branch 'master' into docking

# Conflicts:
#	backends/imgui_impl_dx12.cpp
This commit is contained in:
ocornut
2025-09-29 16:16:16 +02:00
5 changed files with 71 additions and 73 deletions

View File

@@ -23,6 +23,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-09-29: DirectX12: Rework synchronization logic. (#8961)
// 2025-09-29: DirectX12: Reuse a command list and allocator for texture uploads instead of recreating them each time.
// 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown. // 2025-09-18: Call platform_io.ClearRendererHandlers() on shutdown.
// 2025-06-19: Fixed build on MinGW. (#8702, #4594) // 2025-06-19: Fixed build on MinGW. (#8702, #4594)
// 2025-06-11: DirectX12: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas. // 2025-06-11: DirectX12: Added support for ImGuiBackendFlags_RendererHasTextures, for dynamic font atlas.
@@ -98,8 +100,14 @@ struct ImGui_ImplDX12_Data
DXGI_FORMAT RTVFormat; DXGI_FORMAT RTVFormat;
DXGI_FORMAT DSVFormat; DXGI_FORMAT DSVFormat;
ID3D12DescriptorHeap* pd3dSrvDescHeap; ID3D12DescriptorHeap* pd3dSrvDescHeap;
ID3D12Fence* Fence;
UINT64 FenceLastSignaledValue;
HANDLE FenceEvent;
UINT numFramesInFlight; UINT numFramesInFlight;
ID3D12CommandAllocator* pTexCmdAllocator;
ID3D12GraphicsCommandList* pTexCmdList;
ImGui_ImplDX12_RenderBuffers* pFrameResources; ImGui_ImplDX12_RenderBuffers* pFrameResources;
UINT frameIndex; UINT frameIndex;
@@ -529,29 +537,15 @@ void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex)
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? // FIXME-OPT: Could upload buffer be kept around, reused, and grown only when needed? Would that be worth it?
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));
// Create temporary command list and execute immediately bd->pTexCmdAllocator->Reset();
ID3D12Fence* fence = nullptr; bd->pTexCmdList->Reset(bd->pTexCmdAllocator, nullptr);
hr = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&fence)); ID3D12GraphicsCommandList* cmdList = bd->pTexCmdList;
IM_ASSERT(SUCCEEDED(hr));
HANDLE event = ::CreateEvent(0, 0, 0, 0);
IM_ASSERT(event != nullptr);
// FIXME-OPT: Create once and reuse?
ID3D12CommandAllocator* cmdAlloc = nullptr;
hr = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&cmdAlloc));
IM_ASSERT(SUCCEEDED(hr));
// FIXME-OPT: Can be use the one from user? (pass ID3D12GraphicsCommandList* to ImGui_ImplDX12_UpdateTextures)
ID3D12GraphicsCommandList* cmdList = nullptr;
hr = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, cmdAlloc, nullptr, IID_PPV_ARGS(&cmdList));
IM_ASSERT(SUCCEEDED(hr));
// Copy to upload buffer // Copy to upload buffer
void* mapped = nullptr; void* mapped = nullptr;
@@ -606,20 +600,16 @@ void ImGui_ImplDX12_UpdateTexture(ImTextureData* tex)
ID3D12CommandQueue* cmdQueue = bd->pCommandQueue; ID3D12CommandQueue* cmdQueue = bd->pCommandQueue;
cmdQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&cmdList); cmdQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&cmdList);
hr = cmdQueue->Signal(fence, 1); hr = cmdQueue->Signal(bd->Fence, ++bd->FenceLastSignaledValue);
IM_ASSERT(SUCCEEDED(hr)); IM_ASSERT(SUCCEEDED(hr));
// FIXME-OPT: Suboptimal? // FIXME-OPT: Suboptimal?
// - To remove this may need to create NumFramesInFlight x ImGui_ImplDX12_FrameContext in backend data (mimick docking version) // - 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? // - Store per-frame in flight: upload buffer?
// - Where do cmdList and cmdAlloc fit? // - Where do cmdList and cmdAlloc fit?
fence->SetEventOnCompletion(1, event); bd->Fence->SetEventOnCompletion(bd->FenceLastSignaledValue, bd->FenceEvent);
::WaitForSingleObject(event, INFINITE); ::WaitForSingleObject(bd->FenceEvent, INFINITE);
cmdList->Release();
cmdAlloc->Release();
::CloseHandle(event);
fence->Release();
uploadBuffer->Release(); uploadBuffer->Release();
tex->SetStatus(ImTextureStatus_OK); tex->SetStatus(ImTextureStatus_OK);
} }
@@ -857,6 +847,20 @@ bool ImGui_ImplDX12_CreateDeviceObjects()
if (result_pipeline_state != S_OK) if (result_pipeline_state != S_OK)
return false; return false;
// Create command allocator and command list for ImGui_ImplDX12_UpdateTexture()
HRESULT hr = bd->pd3dDevice->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&bd->pTexCmdAllocator));
IM_ASSERT(SUCCEEDED(hr));
hr = bd->pd3dDevice->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, bd->pTexCmdAllocator, nullptr, IID_PPV_ARGS(&bd->pTexCmdList));
IM_ASSERT(SUCCEEDED(hr));
hr = bd->pTexCmdList->Close();
IM_ASSERT(SUCCEEDED(hr));
// Create fence.
hr = bd->pd3dDevice->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&bd->Fence));
IM_ASSERT(hr == S_OK);
bd->FenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
IM_ASSERT(bd->FenceEvent != nullptr);
return true; return true;
} }
@@ -878,6 +882,11 @@ void ImGui_ImplDX12_InvalidateDeviceObjects()
bd->commandQueueOwned = false; bd->commandQueueOwned = false;
SafeRelease(bd->pRootSignature); SafeRelease(bd->pRootSignature);
SafeRelease(bd->pPipelineState); SafeRelease(bd->pPipelineState);
SafeRelease(bd->pTexCmdList);
SafeRelease(bd->pTexCmdAllocator);
SafeRelease(bd->Fence);
CloseHandle(bd->FenceEvent);
bd->FenceEvent = nullptr;
// Destroy all textures // Destroy all textures
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures) for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)

View File

@@ -116,6 +116,11 @@
#endif #endif
// GLFW // GLFW
#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
#define GLFW_HAS_X11_OR_WAYLAND 1
#else
#define GLFW_HAS_X11_OR_WAYLAND 0
#endif
#include <GLFW/glfw3.h> #include <GLFW/glfw3.h>
#ifdef _WIN32 #ifdef _WIN32
#undef APIENTRY #undef APIENTRY
@@ -128,8 +133,8 @@
#define GLFW_EXPOSE_NATIVE_COCOA #define GLFW_EXPOSE_NATIVE_COCOA
#endif #endif
#include <GLFW/glfw3native.h> #include <GLFW/glfw3native.h>
#elif !defined(__EMSCRIPTEN__) #elif GLFW_HAS_X11_OR_WAYLAND
#ifndef GLFW_EXPOSE_NATIVE_X11 // for glfwGetX11Window() on Freedesktop (Linux, BSD, etc.) #ifndef GLFW_EXPOSE_NATIVE_X11 // for glfwGetX11Display(), glfwGetX11Window() on Freedesktop (Linux, BSD, etc.)
#define GLFW_EXPOSE_NATIVE_X11 #define GLFW_EXPOSE_NATIVE_X11
#include <X11/Xatom.h> #include <X11/Xatom.h>
#endif #endif
@@ -184,11 +189,6 @@
#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() #define GLFW_HAS_GETPLATFORM (GLFW_VERSION_COMBINED >= 3400) // 3.4+ glfwGetPlatform()
#if defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__NetBSD__) || defined(__DragonFly__)
#define GLFW_HAS_X11_OR_WAYLAND 1
#else
#define GLFW_HAS_X11_OR_WAYLAND 0
#endif
// Map GLFWWindow* to ImGuiContext*. // 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 glfwSetWindowUserPointer()/glfwGetWindowUserPointer(), but this is a single and shared resource.

View File

@@ -66,6 +66,8 @@ Other Changes:
- Windows: added lower-right resize grip on child windows using both - Windows: added lower-right resize grip on child windows using both
ImGuiChildFlags_ResizeX and ImGuiChildFlags_ResizeY flags. (#8501) [@aleksijuvani] ImGuiChildFlags_ResizeX and ImGuiChildFlags_ResizeY flags. (#8501) [@aleksijuvani]
The grip is not visible before hovering to reduce clutter. The grip is not visible before hovering to reduce clutter.
- InputText: fixed single-line InputText() not applying fine character clipping
properly (regression in 1.92.3). (#8967) [@Cyphall]
- IO: added ImGuiPlatformIO::ClearPlatformHandlers(), ClearRendererHandlers() - IO: added ImGuiPlatformIO::ClearPlatformHandlers(), ClearRendererHandlers()
helpers to null all handlers. (#8945, #2769) helpers to null all handlers. (#8945, #2769)
- Misc: Debuggers: added type formatters for the LLDB debuggers (e.g. Xcode, - Misc: Debuggers: added type formatters for the LLDB debuggers (e.g. Xcode,
@@ -75,8 +77,13 @@ Other Changes:
- Backends: all backends call ImGuiPlatformIO::ClearPlatformHandlers() and - Backends: all backends call ImGuiPlatformIO::ClearPlatformHandlers() and
ClearRendererHandlers() on shutdown, so as not to leave function pointers ClearRendererHandlers() on shutdown, so as not to leave function pointers
which may be dangling when using backend in e.g. DLL. (#8945, #2769) which may be dangling when using backend in e.g. DLL. (#8945, #2769)
- Backends: DirectX12: reuse a command list and allocator for texture uploads instead
of recreating them each time. (#8963, #8465) [@RT2Code]
- Backends: DirectX12: Rework synchronization logic. (#8961) [@RT2Code]
- Backends: OpenGL3: fixed GL loader to work on Haiku OS which does not support - Backends: OpenGL3: fixed GL loader to work on Haiku OS which does not support
`RTLD_NOLOAD`. (#8952) [@Xottab-DUTY, @threedeyes] `RTLD_NOLOAD`. (#8952) [@Xottab-DUTY, @threedeyes]
- Backends: GLFW: fixed build on platform that are neither Windows, macOS or
known Unixes (Regression in 1.92.3). (#8969, #8920, #8921) [@oktonion]
- Backends: SDL2,SDL3: avoid using the SDL_GetGlobalMouseState() path when one of our - Backends: SDL2,SDL3: avoid using the SDL_GetGlobalMouseState() path when one of our
window is hovered, as the event data is reliable and enough in this case. window is hovered, as the event data is reliable and enough in this case.
- Fix mouse coordinates issue in fullscreen apps with macOS notch. (#7919, #7786) - Fix mouse coordinates issue in fullscreen apps with macOS notch. (#7919, #7786)
@@ -89,6 +96,7 @@ Other Changes:
- Examples: SDL2+DirectX11: Try WARP software driver if hardware driver is - Examples: SDL2+DirectX11: Try WARP software driver if hardware driver is
not available. (#5924, #5562) not available. (#5924, #5562)
- Examples: SDL3+DirectX11: Added SDL3+DirectX11 example. (#8956, #8957) [@tomaz82] - Examples: SDL3+DirectX11: Added SDL3+DirectX11 example. (#8956, #8957) [@tomaz82]
- Examples: Win32+DirectX12: Rework synchronization logic. (#8961) [@RT2Code]
- Examples: made examples's main.cpp consistent with returning 1 on error. - Examples: made examples's main.cpp consistent with returning 1 on error.
Docking+Viewports Branch: Docking+Viewports Branch:

View File

@@ -102,8 +102,8 @@ bool CreateDeviceD3D(HWND hWnd);
void CleanupDeviceD3D(); void CleanupDeviceD3D();
void CreateRenderTarget(); void CreateRenderTarget();
void CleanupRenderTarget(); void CleanupRenderTarget();
void WaitForLastSubmittedFrame(); void WaitForPendingOperations();
FrameContext* WaitForNextFrameResources(); FrameContext* WaitForNextFrameContext();
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
// Main code // Main code
@@ -269,7 +269,7 @@ int main(int, char**)
// Rendering // Rendering
ImGui::Render(); ImGui::Render();
FrameContext* frameCtx = WaitForNextFrameResources(); FrameContext* frameCtx = WaitForNextFrameContext();
UINT backBufferIdx = g_pSwapChain->GetCurrentBackBufferIndex(); UINT backBufferIdx = g_pSwapChain->GetCurrentBackBufferIndex();
frameCtx->CommandAllocator->Reset(); frameCtx->CommandAllocator->Reset();
@@ -295,6 +295,8 @@ int main(int, char**)
g_pd3dCommandList->Close(); g_pd3dCommandList->Close();
g_pd3dCommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&g_pd3dCommandList); g_pd3dCommandQueue->ExecuteCommandLists(1, (ID3D12CommandList* const*)&g_pd3dCommandList);
g_pd3dCommandQueue->Signal(g_fence, ++g_fenceLastSignaledValue);
frameCtx->FenceValue = g_fenceLastSignaledValue;
// Update and Render additional Platform Windows // Update and Render additional Platform Windows
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable) if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
@@ -307,14 +309,10 @@ int main(int, char**)
HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync HRESULT hr = g_pSwapChain->Present(1, 0); // Present with vsync
//HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync //HRESULT hr = g_pSwapChain->Present(0, 0); // Present without vsync
g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED); g_SwapChainOccluded = (hr == DXGI_STATUS_OCCLUDED);
g_frameIndex++;
UINT64 fenceValue = g_fenceLastSignaledValue + 1;
g_pd3dCommandQueue->Signal(g_fence, fenceValue);
g_fenceLastSignaledValue = fenceValue;
frameCtx->FenceValue = fenceValue;
} }
WaitForLastSubmittedFrame(); WaitForPendingOperations();
// Cleanup // Cleanup
ImGui_ImplDX12_Shutdown(); ImGui_ImplDX12_Shutdown();
@@ -484,49 +482,33 @@ void CreateRenderTarget()
void CleanupRenderTarget() void CleanupRenderTarget()
{ {
WaitForLastSubmittedFrame(); WaitForPendingOperations();
for (UINT i = 0; i < APP_NUM_BACK_BUFFERS; i++) for (UINT i = 0; i < APP_NUM_BACK_BUFFERS; i++)
if (g_mainRenderTargetResource[i]) { g_mainRenderTargetResource[i]->Release(); g_mainRenderTargetResource[i] = nullptr; } if (g_mainRenderTargetResource[i]) { g_mainRenderTargetResource[i]->Release(); g_mainRenderTargetResource[i] = nullptr; }
} }
void WaitForLastSubmittedFrame() void WaitForPendingOperations()
{ {
FrameContext* frameCtx = &g_frameContext[g_frameIndex % APP_NUM_FRAMES_IN_FLIGHT]; g_pd3dCommandQueue->Signal(g_fence, ++g_fenceLastSignaledValue);
UINT64 fenceValue = frameCtx->FenceValue; g_fence->SetEventOnCompletion(g_fenceLastSignaledValue, g_fenceEvent);
if (fenceValue == 0) ::WaitForSingleObject(g_fenceEvent, INFINITE);
return; // No fence was signaled
frameCtx->FenceValue = 0;
if (g_fence->GetCompletedValue() >= fenceValue)
return;
g_fence->SetEventOnCompletion(fenceValue, g_fenceEvent);
WaitForSingleObject(g_fenceEvent, INFINITE);
} }
FrameContext* WaitForNextFrameResources() FrameContext* WaitForNextFrameContext()
{ {
UINT nextFrameIndex = g_frameIndex + 1; FrameContext* frame_context = &g_frameContext[g_frameIndex % APP_NUM_FRAMES_IN_FLIGHT];
g_frameIndex = nextFrameIndex; if (g_fence->GetCompletedValue() < frame_context->FenceValue)
HANDLE waitableObjects[] = { g_hSwapChainWaitableObject, nullptr };
DWORD numWaitableObjects = 1;
FrameContext* frameCtx = &g_frameContext[nextFrameIndex % APP_NUM_FRAMES_IN_FLIGHT];
UINT64 fenceValue = frameCtx->FenceValue;
if (fenceValue != 0) // means no fence was signaled
{ {
frameCtx->FenceValue = 0; g_fence->SetEventOnCompletion(frame_context->FenceValue, g_fenceEvent);
g_fence->SetEventOnCompletion(fenceValue, g_fenceEvent); HANDLE waitableObjects[] = { g_hSwapChainWaitableObject, g_fenceEvent };
waitableObjects[1] = g_fenceEvent; ::WaitForMultipleObjects(2, waitableObjects, TRUE, INFINITE);
numWaitableObjects = 2;
} }
else
::WaitForSingleObject(g_hSwapChainWaitableObject, INFINITE);
WaitForMultipleObjects(numWaitableObjects, waitableObjects, TRUE, INFINITE); return frame_context;
return frameCtx;
} }
// Forward declare message handler from imgui_impl_win32.cpp // Forward declare message handler from imgui_impl_win32.cpp
@@ -547,7 +529,6 @@ LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
case WM_SIZE: case WM_SIZE:
if (g_pd3dDevice != nullptr && wParam != SIZE_MINIMIZED) if (g_pd3dDevice != nullptr && wParam != SIZE_MINIMIZED)
{ {
WaitForLastSubmittedFrame();
CleanupRenderTarget(); CleanupRenderTarget();
HRESULT result = g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT); HRESULT result = g_pSwapChain->ResizeBuffers(0, (UINT)LOWORD(lParam), (UINT)HIWORD(lParam), DXGI_FORMAT_UNKNOWN, DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT);
assert(SUCCEEDED(result) && "Failed to resize swapchain."); assert(SUCCEEDED(result) && "Failed to resize swapchain.");

View File

@@ -5531,7 +5531,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_
text_col, clip_rect.AsVec4(), text_col, clip_rect.AsVec4(),
line_index->get_line_begin(buf_display, line_visible_n0), line_index->get_line_begin(buf_display, line_visible_n0),
line_index->get_line_end(buf_display, line_visible_n1 - 1), line_index->get_line_end(buf_display, line_visible_n1 - 1),
wrap_width, ImDrawTextFlags_WrapKeepBlanks); wrap_width, ImDrawTextFlags_WrapKeepBlanks | ImDrawTextFlags_CpuFineClip);
// Render blinking cursor // Render blinking cursor
if (render_cursor) if (render_cursor)