From db18bc22d3f7d21915901a5c3399a7250ecb17d3 Mon Sep 17 00:00:00 2001 From: Morfah Date: Wed, 4 Jun 2025 18:51:56 +0200 Subject: [PATCH 1/7] * Made ImGuiRenderer Disposable. * Enable docking support. * FNA fixes. * Minor code cleanups. --- .../ImGuiRenderer.cs | 105 +++++++++++++----- 1 file changed, 76 insertions(+), 29 deletions(-) diff --git a/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs b/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs index 04cd24b9..04656048 100644 --- a/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs +++ b/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs @@ -10,15 +10,15 @@ namespace ImGuiNET.SampleProgram.XNA /// /// ImGui renderer for use with XNA-likes (FNA & MonoGame) /// - public class ImGuiRenderer + public class ImGuiRenderer : IDisposable { - private Game _game; + private readonly Game _game; // Graphics - private GraphicsDevice _graphicsDevice; + private readonly GraphicsDevice _graphicsDevice; private BasicEffect _effect; - private RasterizerState _rasterizerState; + private readonly RasterizerState _rasterizerState; private byte[] _vertexData; private VertexBuffer _vertexBuffer; @@ -29,7 +29,7 @@ public class ImGuiRenderer private int _indexBufferSize; // Textures - private Dictionary _loadedTextures; + private readonly Dictionary _loadedTextures; private int _textureId; private IntPtr? _fontTextureId; @@ -38,7 +38,9 @@ public class ImGuiRenderer private int _scrollWheelValue; private int _horizontalScrollWheelValue; private readonly float WHEEL_DELTA = 120; - private Keys[] _allKeys = Enum.GetValues(); + private readonly Keys[] _allKeys = Enum.GetValues(); + + private bool _disposed; public ImGuiRenderer(Game game) { @@ -63,6 +65,12 @@ public ImGuiRenderer(Game game) SetupInput(); } + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + #region ImGuiRenderer /// @@ -81,6 +89,8 @@ public virtual unsafe void RebuildFontAtlas() // Create and register the texture as an XNA texture var tex2d = new Texture2D(_graphicsDevice, width, height, false, SurfaceFormat.Color); tex2d.SetData(pixels); + tex2d.Name = "ImGui font atlas"; + tex2d.Tag = "ImGui"; // Should a texture already have been build previously, unbind it first so it can be deallocated if (_fontTextureId.HasValue) UnbindTexture(_fontTextureId.Value); @@ -120,6 +130,9 @@ public virtual void BeforeLayout(GameTime gameTime) { ImGui.GetIO().DeltaTime = (float)gameTime.ElapsedGameTime.TotalSeconds; + // Enable docking + ImGui.GetIO().ConfigFlags |= ImGuiConfigFlags.DockingEnable; + UpdateInput(); ImGui.NewFrame(); @@ -144,24 +157,12 @@ public virtual void AfterLayout() /// protected virtual void SetupInput() { - var io = ImGui.GetIO(); - // MonoGame-specific ////////////////////// - _game.Window.TextInput += (s, a) => - { - if (a.Character == '\t') return; - io.AddInputCharacter(a.Character); - }; - + _game.Window.TextInput += OnTextInput; /////////////////////////////////////////// // FNA-specific /////////////////////////// - //TextInputEXT.TextInput += c => - //{ - // if (c == '\t') return; - - // ImGui.GetIO().AddInputCharacter(c); - //}; + // TextInputEXT.TextInput += OnTextInput; /////////////////////////////////////////// } @@ -170,7 +171,7 @@ protected virtual void SetupInput() /// protected virtual Effect UpdateEffect(Texture2D texture) { - _effect = _effect ?? new BasicEffect(_graphicsDevice); + _effect ??= new BasicEffect(_graphicsDevice); var io = ImGui.GetIO(); @@ -180,6 +181,8 @@ protected virtual Effect UpdateEffect(Texture2D texture) _effect.TextureEnabled = true; _effect.Texture = texture; _effect.VertexColorEnabled = true; + _effect.Name = $"{texture.Name}_effect"; + _effect.Tag = "ImGui"; return _effect; } @@ -190,7 +193,7 @@ protected virtual Effect UpdateEffect(Texture2D texture) protected virtual void UpdateInput() { if (!_game.IsActive) return; - + var io = ImGui.GetIO(); var mouse = Mouse.GetState(); @@ -202,6 +205,7 @@ protected virtual void UpdateInput() io.AddMouseButtonEvent(3, mouse.XButton1 == ButtonState.Pressed); io.AddMouseButtonEvent(4, mouse.XButton2 == ButtonState.Pressed); + // FNA-specific information. FNA does not have horizontal scroll wheel support. So you need to set 0f for horizontal scroll wheel value. io.AddMouseWheelEvent( (mouse.HorizontalScrollWheelValue - _horizontalScrollWheelValue) / WHEEL_DELTA, (mouse.ScrollWheelValue - _scrollWheelValue) / WHEEL_DELTA); @@ -220,11 +224,11 @@ protected virtual void UpdateInput() io.DisplayFramebufferScale = new System.Numerics.Vector2(1f, 1f); } - private bool TryMapKeys(Keys key, out ImGuiKey imguikey) + private static bool TryMapKeys(Keys key, out ImGuiKey imguikey) { - //Special case not handed in the switch... - //If the actual key we put in is "None", return none and true. - //otherwise, return none and false. + // Special case not handed in the switch... + // If the actual key we put in is "None", return none and true. + // otherwise, return none and false. if (key == Keys.None) { imguikey = ImGuiKey.None; @@ -390,12 +394,12 @@ private unsafe void RenderCommandLists(ImDrawDataPtr drawData) { ImDrawCmdPtr drawCmd = cmdList.CmdBuffer[cmdi]; - if (drawCmd.ElemCount == 0) + if (drawCmd.ElemCount == 0) { continue; } - if (!_loadedTextures.ContainsKey(drawCmd.TextureId)) + if (!_loadedTextures.TryGetValue(drawCmd.TextureId, out Texture2D value)) { throw new InvalidOperationException($"Could not find a texture with id '{drawCmd.TextureId}', please check your bindings"); } @@ -407,7 +411,7 @@ private unsafe void RenderCommandLists(ImDrawDataPtr drawData) (int)(drawCmd.ClipRect.W - drawCmd.ClipRect.Y) ); - var effect = UpdateEffect(_loadedTextures[drawCmd.TextureId]); + var effect = UpdateEffect(value); foreach (var pass in effect.CurrentTechnique.Passes) { @@ -431,6 +435,49 @@ private unsafe void RenderCommandLists(ImDrawDataPtr drawData) } } + // MonoGame-specific ////////////////////// + private void OnTextInput(object s, TextInputEventArgs a) + { + if (a.Character == '\t') return; + ImGui.GetIO().AddInputCharacter(a.Character); + } + /////////////////////////////////////////// + + // FNA-specific /////////////////////////// + // private void OnTextInput(char c) + // { + // if (c == '\t') return; + // ImGui.GetIO().AddInputCharacter(c); + // } + ///////////////////////////////////////// + + protected virtual void Dispose(bool disposing) + { + if (_disposed) return; + + if (disposing) + { + _vertexBuffer?.Dispose(); + _indexBuffer?.Dispose(); + _effect?.Dispose(); + + foreach (var texture in _loadedTextures) + { + texture.Value?.Dispose(); + } + + // MonoGame-specific ////////////////////// + _game.Window.TextInput -= OnTextInput; + /////////////////////////////////////////// + + // FNA-specific /////////////////////////// + // TextInputEXT.TextInput -= OnTextInput; + ///////////////////////////////////////// + } + + _disposed = true; + } + #endregion Internals } } From bf3dec5d537031bc14a6c02a3ee023079cd8db34 Mon Sep 17 00:00:00 2001 From: Morfah Date: Wed, 4 Jun 2025 18:52:01 +0200 Subject: [PATCH 2/7] * Disable multisampling for the SampleGame, it causes discoloration because some MonoGame bug. --- src/ImGui.NET.SampleProgram.XNA/SampleGame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs b/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs index 3eb4ba2e..155a1295 100644 --- a/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs +++ b/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs @@ -22,7 +22,7 @@ public SampleGame() _graphics = new GraphicsDeviceManager(this); _graphics.PreferredBackBufferWidth = 1024; _graphics.PreferredBackBufferHeight = 768; - _graphics.PreferMultiSampling = true; + _graphics.PreferMultiSampling = false; IsMouseVisible = true; } From 22f6a80a7c438169791fc4a80c14eef238211bfa Mon Sep 17 00:00:00 2001 From: Morfah Date: Wed, 4 Jun 2025 19:04:57 +0200 Subject: [PATCH 3/7] Also use the Dispose in the sample game. --- src/ImGui.NET.SampleProgram.XNA/SampleGame.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs b/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs index 155a1295..325f7edc 100644 --- a/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs +++ b/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs @@ -52,6 +52,13 @@ protected override void LoadContent() base.LoadContent(); } + protected override void UnloadContent() + { + _imGuiRenderer.Dispose(); + Content.Unload(); + base.UnloadContent(); + } + protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(new Color(clear_color.X, clear_color.Y, clear_color.Z)); From c0fe4db547bcb96dfe77348afeddcf427b16655b Mon Sep 17 00:00:00 2001 From: Morfah Date: Wed, 4 Jun 2025 22:09:07 +0200 Subject: [PATCH 4/7] Use consistent amount of slashes in the comments. --- src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs b/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs index 04656048..4ea6408c 100644 --- a/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs +++ b/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs @@ -449,7 +449,7 @@ private void OnTextInput(object s, TextInputEventArgs a) // if (c == '\t') return; // ImGui.GetIO().AddInputCharacter(c); // } - ///////////////////////////////////////// + /////////////////////////////////////////// protected virtual void Dispose(bool disposing) { @@ -472,7 +472,7 @@ protected virtual void Dispose(bool disposing) // FNA-specific /////////////////////////// // TextInputEXT.TextInput -= OnTextInput; - ///////////////////////////////////////// + /////////////////////////////////////////// } _disposed = true; From 78071028e655993c8dea1aed144cc1960b9bd490 Mon Sep 17 00:00:00 2001 From: Morfah Date: Thu, 5 Jun 2025 11:20:44 +0200 Subject: [PATCH 5/7] Renamed disposed field for clarity. --- src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs b/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs index 4ea6408c..a1d37a84 100644 --- a/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs +++ b/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs @@ -40,7 +40,7 @@ public class ImGuiRenderer : IDisposable private readonly float WHEEL_DELTA = 120; private readonly Keys[] _allKeys = Enum.GetValues(); - private bool _disposed; + private bool _isDisposed; public ImGuiRenderer(Game game) { @@ -453,7 +453,7 @@ private void OnTextInput(object s, TextInputEventArgs a) protected virtual void Dispose(bool disposing) { - if (_disposed) return; + if (_isDisposed) return; if (disposing) { @@ -475,7 +475,7 @@ protected virtual void Dispose(bool disposing) /////////////////////////////////////////// } - _disposed = true; + _isDisposed = true; } #endregion Internals From 5f61a3a467293fe21726de838ebd7eba5dac6f7c Mon Sep 17 00:00:00 2001 From: Morfah Date: Sun, 10 Aug 2025 10:57:55 +0200 Subject: [PATCH 6/7] * Update to MonoGame 3.8.4 * Only set effect name if it's empty, so we don't string interpolate unnecessarily. --- .../ImGui.NET.SampleProgram.XNA.csproj | 2 +- src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs | 15 +++++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/ImGui.NET.SampleProgram.XNA/ImGui.NET.SampleProgram.XNA.csproj b/src/ImGui.NET.SampleProgram.XNA/ImGui.NET.SampleProgram.XNA.csproj index 9510bea9..2b76761a 100644 --- a/src/ImGui.NET.SampleProgram.XNA/ImGui.NET.SampleProgram.XNA.csproj +++ b/src/ImGui.NET.SampleProgram.XNA/ImGui.NET.SampleProgram.XNA.csproj @@ -20,7 +20,7 @@ - + diff --git a/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs b/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs index a1d37a84..e96057ae 100644 --- a/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs +++ b/src/ImGui.NET.SampleProgram.XNA/ImGuiRenderer.cs @@ -36,8 +36,8 @@ public class ImGuiRenderer : IDisposable // Input private int _scrollWheelValue; - private int _horizontalScrollWheelValue; - private readonly float WHEEL_DELTA = 120; + private int _horizontalScrollWheelValue; // FNA does not support horizontal scroll wheel. This can be commented out when using FNA. + private const float WHEEL_DELTA = 120; private readonly Keys[] _allKeys = Enum.GetValues(); private bool _isDisposed; @@ -171,18 +171,21 @@ protected virtual void SetupInput() /// protected virtual Effect UpdateEffect(Texture2D texture) { - _effect ??= new BasicEffect(_graphicsDevice); - var io = ImGui.GetIO(); + _effect ??= new BasicEffect(_graphicsDevice); _effect.World = Matrix.Identity; _effect.View = Matrix.Identity; _effect.Projection = Matrix.CreateOrthographicOffCenter(0f, io.DisplaySize.X, io.DisplaySize.Y, 0f, -1f, 1f); _effect.TextureEnabled = true; _effect.Texture = texture; _effect.VertexColorEnabled = true; - _effect.Name = $"{texture.Name}_effect"; - _effect.Tag = "ImGui"; + + if (string.IsNullOrEmpty(_effect.Name)) + { + _effect.Name = $"{texture.Name}_effect"; + _effect.Tag = "ImGui"; + } return _effect; } From 3e7b585301f49d1484225d3e42c99478e97c2912 Mon Sep 17 00:00:00 2001 From: Morfah Date: Thu, 9 Oct 2025 22:51:22 +0200 Subject: [PATCH 7/7] Only draw ImGui if we have a valid delta time. I've seen a delta time of 0f in rare cases. --- src/ImGui.NET.SampleProgram.XNA/SampleGame.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs b/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs index 325f7edc..3cc35020 100644 --- a/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs +++ b/src/ImGui.NET.SampleProgram.XNA/SampleGame.cs @@ -63,14 +63,20 @@ protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(new Color(clear_color.X, clear_color.Y, clear_color.Z)); - // Call BeforeLayout first to set things up - _imGuiRenderer.BeforeLayout(gameTime); + // Only draw the UI when we have a valid delta time. Otherwise ImGui will give an assertion failure. + float dt = (float)gameTime.ElapsedGameTime.TotalSeconds; + if (dt > 0f) + { + ImGui.GetIO().Framerate = 1f / dt; + // Call BeforeLayout first to set things up + _imGuiRenderer.BeforeLayout(gameTime); - // Draw our UI - ImGuiLayout(); + // Draw our UI + ImGuiLayout(); - // Call AfterLayout now to finish up and draw all the things - _imGuiRenderer.AfterLayout(); + // Call AfterLayout now to finish up and draw all the things + _imGuiRenderer.AfterLayout(); + } base.Draw(gameTime); }