diff --git a/include/cinder/CinderImGui.h b/include/cinder/CinderImGui.h
index d755a68095..9832d2d76c 100644
--- a/include/cinder/CinderImGui.h
+++ b/include/cinder/CinderImGui.h
@@ -23,6 +23,16 @@
#pragma once
+// NOTE: this is not the place to configure ImGui from an application.
+// ImGui configuration macros (IMGUI_DISABLE_OBSOLETE_FUNCTIONS and friends) belong
+// in include/imgui/imconfig.h, which imgui.h includes in *every* translation unit.
+// CinderImGuiConfig.h below is only reached by code that goes through this header --
+// cinder's own imgui*.cpp never define IMGUI_USER_CONFIG and so never see it.
+// Defining such a macro in application code therefore changes the layout of
+// ImGuiIO/ImGuiContext for that application only, while cinder.lib keeps the
+// unmodified layout. That is an ODR violation which neither the compiler nor the
+// linker reports, and it silently corrupts ImGui state. Initialize() below calls
+// IMGUI_CHECKVERSION() from the caller's translation unit to catch it.
#if ! defined( IMGUI_USER_CONFIG )
#define IMGUI_USER_CONFIG "cinder/CinderImGuiConfig.h"
#endif
@@ -112,7 +122,19 @@ namespace ImGui {
//! For D3D12 apps: auto-render is not supported since apps manage their own command lists.
//! Use GetD3D12SrvHeap() to get the descriptor heap, then call NewFrame(), your GUI code,
//! Render(), and ImGui_ImplDX12_RenderDrawData() manually.
- CI_API bool Initialize( const Options& options = Options() );
+ CI_API bool InitializeImpl( const Options& options );
+
+ //! Deliberately inline: IMGUI_CHECKVERSION() compares the sizes of ImGuiIO,
+ //! ImGuiStyle, ImVec2/4 and ImDrawVert/Idx as seen by the *caller* against those
+ //! compiled into cinder. Calling it from inside cinder would only ever compare
+ //! cinder against itself, so it must be instantiated in the application's
+ //! translation unit to detect a configuration mismatch across the library
+ //! boundary (see the note at the top of this header).
+ inline bool Initialize( const Options& options = Options() )
+ {
+ IMGUI_CHECKVERSION();
+ return InitializeImpl( options );
+ }
//! Returns true if ImGui is using the D3D12 backend
CI_API bool IsUsingD3D12();
diff --git a/include/imgui/imconfig.h b/include/imgui/imconfig.h
index 4dab1b6042..959bafd112 100644
--- a/include/imgui/imconfig.h
+++ b/include/imgui/imconfig.h
@@ -27,8 +27,19 @@
//#define IMGUI_API __declspec(dllimport) // MSVC Windows: DLL import
//#define IMGUI_API __attribute__((visibility("default"))) // GCC/Clang: override visibility when set is hidden
+// NOTE: this file is the ONLY correct place for macros like this one. imgui.h
+// includes imconfig.h in every translation unit, so both Cinder's bundled
+// imgui*.cpp and user application code see the same definition. Defining such a
+// macro in application code instead (e.g. before including CinderImGui.h) changes
+// the layout of ImGuiIO/ImGuiContext for that application only, while cinder.lib
+// keeps the unmodified layout. That is an ODR violation which neither the
+// compiler nor the linker reports: in debug builds, inline ImGui helpers are
+// emitted as out-of-line COMDATs and the linker keeps one copy arbitrarily, so
+// ImGui may read struct members at the wrong offsets. ImGui::Initialize() is
+// inline and calls IMGUI_CHECKVERSION() from the caller's translation unit, so
+// any such mismatch is detected automatically at startup.
//---- Don't define obsolete functions/enums/behaviors. Consider enabling from time to time after updating to clean your code of obsolete function/names.
-//#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
+#define IMGUI_DISABLE_OBSOLETE_FUNCTIONS
//---- Disable all of Dear ImGui or don't implement standard windows/tools.
// It is very strongly recommended to NOT disable the demo windows and debug tool during development. They are extremely useful in day to day work. Please read comments in imgui_demo.cpp.
diff --git a/samples/ImGui/proj/vc2022/ImGui.vcxproj b/samples/ImGui/proj/vc2022/ImGui.vcxproj
index c73d4e8b04..3018db293c 100644
--- a/samples/ImGui/proj/vc2022/ImGui.vcxproj
+++ b/samples/ImGui/proj/vc2022/ImGui.vcxproj
@@ -76,7 +76,6 @@
false
- LIBCMT;LIBCPMT
@@ -107,7 +106,6 @@
false
- LIBCMT;LIBCPMT
diff --git a/src/cinder/CinderImGui.cpp b/src/cinder/CinderImGui.cpp
index 4129c027a2..b6ec555225 100644
--- a/src/cinder/CinderImGui.cpp
+++ b/src/cinder/CinderImGui.cpp
@@ -23,6 +23,7 @@ void shutdownD3d12();
#endif
#include
+#include
static bool sInitialized = false;
static bool sTriggerNewFrame = false;
@@ -34,6 +35,14 @@ static std::unordered_map sWind
static bool sUsingD3D12 = false;
static ID3D12DescriptorHeap* sImGuiSrvHeap = nullptr;
static const UINT kImGuiSrvHeapSize = 64;
+
+// Free list over sImGuiSrvHeap. Since 1.91.5 ImGui's D3D12 backend allocates SRV
+// descriptors through callbacks rather than taking a single descriptor up front,
+// and from 1.92 it allocates more than one (one per texture).
+static D3D12_CPU_DESCRIPTOR_HANDLE sSrvHeapCpuStart = {};
+static D3D12_GPU_DESCRIPTOR_HANDLE sSrvHeapGpuStart = {};
+static UINT sSrvHeapHandleIncrement = 0;
+static std::vector sSrvHeapFreeIndices;
#endif
namespace ImGui {
@@ -122,9 +131,9 @@ Options::Options()
mStyle.Colors[ImGuiCol_ResizeGripActive] = orangeActive;
mStyle.Colors[ImGuiCol_Tab] = grayInactive;
mStyle.Colors[ImGuiCol_TabHovered] = orangeDimmed;
- mStyle.Colors[ImGuiCol_TabActive] = orangeActive; // orangeBright;
- mStyle.Colors[ImGuiCol_TabUnfocused] = orangeDimmed;
- mStyle.Colors[ImGuiCol_TabUnfocusedActive] = orangeActive;
+ mStyle.Colors[ImGuiCol_TabSelected] = orangeActive; // orangeBright;
+ mStyle.Colors[ImGuiCol_TabDimmed] = orangeDimmed;
+ mStyle.Colors[ImGuiCol_TabDimmedSelected] = orangeActive;
// mStyle.Colors[ImGuiCol_DockingPreview] = grayInactive;
// mStyle.Colors[ImGuiCol_DockingEmptyBg] = ImVec4( 0.20f, 0.20f, 0.20f, 1.00f );
mStyle.Colors[ImGuiCol_PlotLines] = ImVec4( 0.93f, 0.96f, 0.95f, 0.80f );
@@ -133,7 +142,7 @@ Options::Options()
mStyle.Colors[ImGuiCol_PlotHistogramHovered] = grayInactive;
mStyle.Colors[ImGuiCol_TextSelectedBg] = grayInactive;
mStyle.Colors[ImGuiCol_DragDropTarget] = grayInactive;
- mStyle.Colors[ImGuiCol_NavHighlight] = grayInactive;
+ mStyle.Colors[ImGuiCol_NavCursor] = grayInactive;
mStyle.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4( 1.00f, 1.00f, 1.00f, 0.70f );
mStyle.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4( 0.80f, 0.80f, 0.80f, 0.20f );
mStyle.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4( 0.20f, 0.22f, 0.27f, 0.73f );
@@ -382,8 +391,16 @@ bool ListBox( const char* label, int* currIndex, const std::vector&
if( values.empty() )
return false;
+ // 'height_in_items' is a count of rows, not a pixel size: passing it straight to
+ // BeginListBox() makes the default of -1 a negative size, which CalcItemSize() reads as
+ // "content region minus one line" and collapses the list inside an auto-sized popup.
+ if( height_in_items < 0 )
+ height_in_items = ( (int)values.size() < 7 ) ? (int)values.size() : 7;
+ const ImVec2 size( 0.0f, (float)(int)( ImGui::GetTextLineHeightWithSpacing() * ( height_in_items + 0.25f )
+ + ImGui::GetStyle().FramePadding.y * 2.0f ) );
+
bool changed = false;
- if( ImGui::BeginListBox( label, ImVec2( 0, height_in_items * ImGui::GetTextLineHeightWithSpacing() ) ) ) {
+ if( ImGui::BeginListBox( label, size ) ) {
for( int i = 0; i < (int)values.size(); ++i ) {
ImGui::PushID( (void*)(intptr_t)i );
bool selected = ( *currIndex == i );
@@ -402,7 +419,23 @@ bool ListBox( const char* label, int* currIndex, const std::vector&
void Image( const ci::gl::Texture2dRef& texture, const ci::vec2& size, const ci::vec2& uv0, const ci::vec2& uv1, const ci::vec4& tint_col, const ci::vec4& border_col )
{
- Image( (ImTextureID)(intptr_t)texture->getId(), size, uv0, uv1, tint_col, border_col );
+ // Mirrors ImGui's own obsolete Image() shim (see imgui_widgets.cpp), which is
+ // compiled out by IMGUI_DISABLE_OBSOLETE_FUNCTIONS. Two things to note:
+ // - ImageWithBg() takes (bg_col, tint_col), the opposite order to the old
+ // Image()'s (tint_col, border_col), so they must not be forwarded
+ // positionally.
+ // - 'border_col' was removed in 1.91.9; the border is now drawn with the
+ // ImGuiCol_Border style colour at ImGuiStyleVar_ImageBorderSize thickness.
+ // Forcing a non-zero thickness whenever border_col has alpha preserves the
+ // legacy behaviour of drawing a border purely because a colour was given.
+ const float borderSize = ( border_col.w > 0.0f )
+ ? ( ( GetStyle().ImageBorderSize > 1.0f ) ? GetStyle().ImageBorderSize : 1.0f )
+ : 0.0f;
+ PushStyleVar( ImGuiStyleVar_ImageBorderSize, borderSize );
+ PushStyleColor( ImGuiCol_Border, border_col );
+ ImageWithBg( (ImTextureID)(intptr_t)texture->getId(), size, uv0, uv1, ImVec4( 0, 0, 0, 0 ), tint_col );
+ PopStyleColor();
+ PopStyleVar();
}
} // namespace ImGui
@@ -715,9 +748,11 @@ static bool ImGui_ImplCinder_Init( const ci::app::WindowRef& window, const ImGui
imGuiStyle = options.getStyle();
#ifndef CINDER_LINUX
- // clipboard callbacks
- io.SetClipboardTextFn = []( void* user_data, const char* text ) { ci::Clipboard::setString( std::string( text ) ); };
- io.GetClipboardTextFn = []( void* user_data ) {
+ // Clipboard callbacks. These live in ImGuiPlatformIO since 1.91.1; the old
+ // ImGuiIO members are compiled out by IMGUI_DISABLE_OBSOLETE_FUNCTIONS.
+ ImGuiPlatformIO& platformIo = ImGui::GetPlatformIO();
+ platformIo.Platform_SetClipboardTextFn = []( ImGuiContext* ctx, const char* text ) { ci::Clipboard::setString( std::string( text ) ); };
+ platformIo.Platform_GetClipboardTextFn = []( ImGuiContext* ctx ) {
std::string str = ci::Clipboard::getString();
static std::vector strCopy;
strCopy = std::vector( str.begin(), str.end() );
@@ -764,6 +799,26 @@ static void ImGui_ImplCinder_Shutdown()
}
#if defined( CINDER_MSW )
+// SRV descriptor allocator callbacks used by ImGui's D3D12 backend. They hand out
+// descriptors from sImGuiSrvHeap via a simple free list; the backend needs one per
+// texture (including its own font atlas).
+static void ImGui_ImplCinder_SrvDescriptorAlloc( ImGui_ImplDX12_InitInfo* /*info*/, D3D12_CPU_DESCRIPTOR_HANDLE* outCpuHandle, D3D12_GPU_DESCRIPTOR_HANDLE* outGpuHandle )
+{
+ CI_ASSERT_MSG( ! sSrvHeapFreeIndices.empty(), "ImGui SRV descriptor heap exhausted; increase kImGuiSrvHeapSize" );
+ const UINT index = sSrvHeapFreeIndices.back();
+ sSrvHeapFreeIndices.pop_back();
+ outCpuHandle->ptr = sSrvHeapCpuStart.ptr + index * sSrvHeapHandleIncrement;
+ outGpuHandle->ptr = sSrvHeapGpuStart.ptr + index * sSrvHeapHandleIncrement;
+}
+
+static void ImGui_ImplCinder_SrvDescriptorFree( ImGui_ImplDX12_InitInfo* /*info*/, D3D12_CPU_DESCRIPTOR_HANDLE cpuHandle, D3D12_GPU_DESCRIPTOR_HANDLE gpuHandle )
+{
+ const UINT cpuIndex = (UINT)( ( cpuHandle.ptr - sSrvHeapCpuStart.ptr ) / sSrvHeapHandleIncrement );
+ const UINT gpuIndex = (UINT)( ( gpuHandle.ptr - sSrvHeapGpuStart.ptr ) / sSrvHeapHandleIncrement );
+ CI_ASSERT( cpuIndex == gpuIndex && cpuIndex < kImGuiSrvHeapSize );
+ sSrvHeapFreeIndices.push_back( cpuIndex );
+}
+
static bool initializeD3d12( ci::app::RendererD3d12* renderer )
{
sUsingD3D12 = true;
@@ -781,15 +836,25 @@ static bool initializeD3d12( ci::app::RendererD3d12* renderer )
return false;
}
- // Initialize D3D12 backend using legacy single descriptor API
+ // Prime the descriptor free list. Indices are pushed in reverse so the first
+ // allocation hands out index 0.
+ sSrvHeapCpuStart = sImGuiSrvHeap->GetCPUDescriptorHandleForHeapStart();
+ sSrvHeapGpuStart = sImGuiSrvHeap->GetGPUDescriptorHandleForHeapStart();
+ sSrvHeapHandleIncrement = device->GetDescriptorHandleIncrementSize( D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV );
+ sSrvHeapFreeIndices.clear();
+ sSrvHeapFreeIndices.reserve( kImGuiSrvHeapSize );
+ for( UINT i = kImGuiSrvHeapSize; i > 0; --i )
+ sSrvHeapFreeIndices.push_back( i - 1 );
+
+ // Initialize D3D12 backend, providing the SRV descriptor allocator callbacks.
ImGui_ImplDX12_InitInfo initInfo = {};
initInfo.Device = device;
initInfo.CommandQueue = renderer->getCommandQueue();
initInfo.NumFramesInFlight = ci::app::RendererD3d12::MaxFrameCount;
initInfo.RTVFormat = renderer->getBackBufferFormat();
initInfo.SrvDescriptorHeap = sImGuiSrvHeap;
- initInfo.LegacySingleSrvCpuDescriptor = sImGuiSrvHeap->GetCPUDescriptorHandleForHeapStart();
- initInfo.LegacySingleSrvGpuDescriptor = sImGuiSrvHeap->GetGPUDescriptorHandleForHeapStart();
+ initInfo.SrvDescriptorAllocFn = ImGui_ImplCinder_SrvDescriptorAlloc;
+ initInfo.SrvDescriptorFreeFn = ImGui_ImplCinder_SrvDescriptorFree;
if( ! ImGui_ImplDX12_Init( &initInfo ) ) {
CI_LOG_E( "Failed to initialize ImGui D3D12 backend" );
@@ -811,10 +876,14 @@ static void shutdownD3d12()
sImGuiSrvHeap->Release();
sImGuiSrvHeap = nullptr;
}
+ sSrvHeapFreeIndices.clear();
+ sSrvHeapCpuStart = {};
+ sSrvHeapGpuStart = {};
+ sSrvHeapHandleIncrement = 0;
}
#endif
-bool ImGui::Initialize( const ImGui::Options& options )
+bool ImGui::InitializeImpl( const ImGui::Options& options )
{
if( sInitialized )
return false;
@@ -825,7 +894,7 @@ bool ImGui::Initialize( const ImGui::Options& options )
ImGuiIO& io = ImGui::GetIO();
if( options.isKeyboardEnabled() ) {
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
- io.ConfigFlags |= ImGuiConfigFlags_NavNoCaptureKeyboard; // Don't capture keyboard when navigation is active
+ io.ConfigNavCaptureKeyboard = false; // Don't capture keyboard when navigation is active
}
if( options.isGamepadEnabled() )
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls