From f0fe49c110f21e1093451aa482f5b2528787028e Mon Sep 17 00:00:00 2001 From: Glen Fraser Date: Tue, 28 Jul 2026 13:55:32 +0200 Subject: [PATCH 1/3] Stop using deprecated ImGui functions and enums - enable IMGUI_DISABLE_OBSOLETE_FUNCTIONS in imconfig.h and port the deprecated uses: tab/nav colour enums, the NavNoCaptureKeyboard flag, clipboard callbacks, Image() to ImageWithBg(), and DX12 legacy SRV descriptors. - make an inline version of Initialize() so IMGUI_CHECKVERSION() runs in the caller's translation unit, where it can catch an app/library config mismatch instead of only comparing the Cinder lib version to itself. - to resolve issue #2411. --- include/cinder/CinderImGui.h | 24 +++++++++- include/imgui/imconfig.h | 13 +++++- src/cinder/CinderImGui.cpp | 87 ++++++++++++++++++++++++++++++------ 3 files changed, 109 insertions(+), 15 deletions(-) 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/src/cinder/CinderImGui.cpp b/src/cinder/CinderImGui.cpp index 4129c027a2..966bf55c67 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 ); @@ -402,7 +411,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 +740,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 +791,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 +828,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 +868,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 +886,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 From af0fd32daeee5f946ab4faae64ccd504aafc62fe Mon Sep 17 00:00:00 2001 From: Glen Fraser Date: Tue, 28 Jul 2026 14:50:59 +0200 Subject: [PATCH 2/3] Fixed broken Release build for samples/ImGui VS2022 project - removed IgnoreSpecificDefaultLibraries (LIBCMT;LIBCPMT) from samples/ImGui, which broke its Release x64 link (/MT requests exactly the libs being ignored, so the CRT was excluded with thousands of unresolved symbols). - the same line appears in 67 sample projects; fixing the others seems out of scope here but is probably worth a separate pass. --- samples/ImGui/proj/vc2022/ImGui.vcxproj | 2 -- 1 file changed, 2 deletions(-) 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 From 7f49ffa4736632e8ad682931bddc6d7f696e4145 Mon Sep 17 00:00:00 2001 From: Glen Fraser Date: Mon, 10 Aug 2026 22:18:52 -0400 Subject: [PATCH 3/3] Fix default height of ImGui::ListBox() - the ListBoxHeader() -> BeginListBox() port passed 'height_in_items' as a pixel height, so the default of -1 became a negative size and collapsed the list to a few pixels. Restore the old semantics: -1 means min(count, 7) rows. --- src/cinder/CinderImGui.cpp | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/cinder/CinderImGui.cpp b/src/cinder/CinderImGui.cpp index 966bf55c67..b6ec555225 100644 --- a/src/cinder/CinderImGui.cpp +++ b/src/cinder/CinderImGui.cpp @@ -391,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 );