diff --git a/Wobble.Tests/Screens/Tests/Imgui/TestImGuiMenu.cs b/Wobble.Tests/Screens/Tests/Imgui/TestImGuiMenu.cs index 21827ab4..b5b813b2 100644 --- a/Wobble.Tests/Screens/Tests/Imgui/TestImGuiMenu.cs +++ b/Wobble.Tests/Screens/Tests/Imgui/TestImGuiMenu.cs @@ -1,5 +1,5 @@ using System; -using ImGuiNET; +using Hexa.NET.ImGui; using Microsoft.Xna.Framework; using Wobble.Graphics.ImGUI; using Wobble.Graphics.UI.Buttons; @@ -45,7 +45,7 @@ protected override void RenderImguiLayout() { if (ImGui.BeginMenu("File")) { - ImGui.MenuItem("Open", null, ref Opened); + ImGui.MenuItem("Open", (string)null, ref Opened); ImGui.Separator(); ImGui.MenuItem("Save"); ImGui.MenuItem("Exit"); @@ -54,12 +54,12 @@ protected override void RenderImguiLayout() if (ImGui.BeginMenu("Effects")) { - ImGui.MenuItem("Rotation", null, ref Rotation); - ImGui.MenuItem("Lightshow", null, ref Lightshow); + ImGui.MenuItem("Rotation", (string)null, ref Rotation); + ImGui.MenuItem("Lightshow", (string)null, ref Lightshow); ImGui.EndMenu(); } - ImGui.EndMenuBar(); + ImGui.EndMainMenuBar(); Button.IsGloballyClickable = !ImGui.IsAnyItemHovered(); } } @@ -70,4 +70,4 @@ public override void Destroy() Button.IsGloballyClickable = true; } } -} \ No newline at end of file +} diff --git a/Wobble.Tests/Screens/Tests/Imgui/TestImGuiScreenView.cs b/Wobble.Tests/Screens/Tests/Imgui/TestImGuiScreenView.cs index dc71c425..d5ee3c9c 100644 --- a/Wobble.Tests/Screens/Tests/Imgui/TestImGuiScreenView.cs +++ b/Wobble.Tests/Screens/Tests/Imgui/TestImGuiScreenView.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using ImGuiNET; +using Hexa.NET.ImGui; using Microsoft.Xna.Framework; using Wobble.Assets; using Wobble.Graphics; diff --git a/Wobble/Graphics/ImGUI/DrawVertDeclaration.cs b/Wobble/Graphics/ImGUI/DrawVertDeclaration.cs index 148db544..8c49a76e 100644 --- a/Wobble/Graphics/ImGUI/DrawVertDeclaration.cs +++ b/Wobble/Graphics/ImGUI/DrawVertDeclaration.cs @@ -1,4 +1,4 @@ -using ImGuiNET; +using Hexa.NET.ImGui; using Microsoft.Xna.Framework.Graphics; namespace Wobble.Graphics.ImGUI diff --git a/Wobble/Graphics/ImGUI/ImGuiOptions.cs b/Wobble/Graphics/ImGUI/ImGuiOptions.cs index 9c798d0b..658140e6 100644 --- a/Wobble/Graphics/ImGUI/ImGuiOptions.cs +++ b/Wobble/Graphics/ImGUI/ImGuiOptions.cs @@ -1,5 +1,5 @@ using System.Collections.Generic; -using ImGuiNET; +using Hexa.NET.ImGui; namespace Wobble.Graphics.ImGUI { diff --git a/Wobble/Graphics/ImGUI/ImGuiRenderer.cs b/Wobble/Graphics/ImGUI/ImGuiRenderer.cs index 4ba640df..6178ccfc 100644 --- a/Wobble/Graphics/ImGUI/ImGuiRenderer.cs +++ b/Wobble/Graphics/ImGUI/ImGuiRenderer.cs @@ -1,9 +1,9 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Net.Mime; using System.Runtime.InteropServices; -using ImGuiNET; +using System.Threading; +using Hexa.NET.ImGui; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; @@ -11,7 +11,7 @@ namespace Wobble.Graphics.ImGUI { - public sealed class ImGuiRenderer : IDisposable + public sealed unsafe class ImGuiRenderer : IDisposable { /// /// ImGui renderers use separate contexts, so ImGui cannot arbitrate input between overlapping windows itself. @@ -31,7 +31,7 @@ public sealed class ImGuiRenderer : IDisposable /// /// - public IntPtr Context { get; } + public ImGuiContextPtr Context { get; } /// /// @@ -79,11 +79,14 @@ public sealed class ImGuiRenderer : IDisposable /// /// - private int TextureId { get; set; } + private static long NextTextureId; - /// - /// - private IntPtr? FontTextureId { get; set; } + private static readonly ImGuiErrorCallback RecoveredPluginErrorCallback = IgnoreRecoveredPluginError; + + private static unsafe void IgnoreRecoveredPluginError(ImGuiContext* context, void* userData, byte* message) + { + // Recovery is expected for legacy Lua plugins that scoped style pushes to End(). + } /// /// @@ -153,10 +156,9 @@ public ImGuiRenderer(bool destroyContext = true, ImGuiOptions options = null, fl DestroyContext = destroyContext; Options = options; Scale = scale; - FontAtlas = SharedFontAtlasCache.Retain(options, scale); - - Context = ImGui.CreateContext(FontAtlas.Atlas); + Context = ImGui.CreateContext(); ImGui.SetCurrentContext(Context); + FontAtlas = new SharedFontAtlas(options, scale, ImGui.GetIO().Fonts); LoadedTextures = new Dictionary(); @@ -189,56 +191,25 @@ public unsafe void RebuildFontAtlas() if (previousContext != Context) ImGui.SetCurrentContext(Context); - var io = ImGui.GetIO(); FontAtlas.AssignFontPointers(Options, out var defaultFontPtr); DefaultFontPtr = defaultFontPtr; - FontAtlas.EnsureTexture(GraphicsDevice); - - if (FontTextureId.HasValue && FontTextureId.Value != FontAtlas.TextureId) - UnbindTexture(FontTextureId.Value); - - FontTextureId = FontAtlas.TextureId; - LoadedTextures[FontTextureId.Value] = new TextureBinding(FontAtlas.Texture, false); + var io = ImGui.GetIO(); + io.FontDefault = defaultFontPtr; - // Let ImGui know where to find the texture - io.Fonts.SetTexID(FontTextureId.Value); + if (Options?.Fonts.Count > 0) + ImGui.GetStyle().FontSizeBase = Options.Fonts[0].Size * Scale; if (previousContext != Context) ImGui.SetCurrentContext(previousContext); } - private static IntPtr GetGlyphRanges(ImFontAtlasPtr fonts, ImGuiGlyphRanges ranges) - { - switch (ranges) - { - case ImGuiGlyphRanges.ChineseFull: - return fonts.GetGlyphRangesChineseFull(); - case ImGuiGlyphRanges.ChineseSimplifiedCommon: - return fonts.GetGlyphRangesChineseSimplifiedCommon(); - case ImGuiGlyphRanges.Japanese: - return fonts.GetGlyphRangesJapanese(); - case ImGuiGlyphRanges.Korean: - return fonts.GetGlyphRangesKorean(); - case ImGuiGlyphRanges.Cyrillic: - return fonts.GetGlyphRangesCyrillic(); - case ImGuiGlyphRanges.Greek: - return fonts.GetGlyphRangesGreek(); - case ImGuiGlyphRanges.Thai: - return fonts.GetGlyphRangesThai(); - case ImGuiGlyphRanges.Vietnamese: - return fonts.GetGlyphRangesVietnamese(); - default: - return fonts.GetGlyphRangesDefault(); - } - } - /// /// Creates a pointer to a texture, which can be passed through ImGui calls such as . /// That pointer is then used by ImGui to let us know what texture to draw /// public IntPtr BindTexture(Texture2D texture) { - var id = new IntPtr(TextureId++); + var id = new IntPtr(Interlocked.Increment(ref NextTextureId)); LoadedTextures.Add(id, new TextureBinding(texture, true)); @@ -308,9 +279,23 @@ private void OnWindowOnTextInput(object s, TextInputEventArgs a) /// /// Maps ImGui keys to XNA keys. We use this later on to tell ImGui what keys were pressed /// - private void SetupInput() + private unsafe void SetupInput() { var io = ImGui.GetIO(); + io.BackendFlags |= ImGuiBackendFlags.RendererHasTextures | ImGuiBackendFlags.RendererHasVtxOffset; + io.ConfigErrorRecovery = true; + io.ConfigErrorRecoveryEnableAssert = false; + // Plugins written against pre-1.92 ImGui commonly scoped style pushes to a whole + // window and relied on End() to recover the stack. Keep that safe recovery behavior, + // but don't flood the editor UI or logs with a diagnostic for every recovered item. + io.ConfigErrorRecoveryEnableDebugLog = false; + io.ConfigErrorRecoveryEnableTooltip = false; + var context = Context; + context.ErrorCallback = (void*)Marshal.GetFunctionPointerForDelegate(RecoveredPluginErrorCallback); + + var platformIo = ImGui.GetPlatformIO(); + platformIo.RendererTextureMaxWidth = GraphicsDevice.GraphicsProfile == GraphicsProfile.HiDef ? 16384 : 4096; + platformIo.RendererTextureMaxHeight = GraphicsDevice.GraphicsProfile == GraphicsProfile.HiDef ? 16384 : 4096; // MonoGame-specific ////////////////////// @@ -327,8 +312,8 @@ private void SetupInput() /////////////////////////////////////////// // ImGUI provides out-of-the-box clipboard only on Windows. For other platforms, we need to set up the function pointers. - io.SetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(SetClipboardTextFnDelegate); - io.GetClipboardTextFn = Marshal.GetFunctionPointerForDelegate(GetClipboardTextFnDelegate); + platformIo.PlatformSetClipboardTextFn = (void*)Marshal.GetFunctionPointerForDelegate(SetClipboardTextFnDelegate); + platformIo.PlatformGetClipboardTextFn = (void*)Marshal.GetFunctionPointerForDelegate(GetClipboardTextFnDelegate); } /* @@ -498,6 +483,8 @@ private void UpdateInputOwners(GameTime gameTime, MouseState mouse) /// private void RenderDrawData(ImDrawDataPtr drawData) { + UpdateDynamicTextures(drawData); + LastVertexCount = drawData.TotalVtxCount; LastIndexCount = drawData.TotalIdxCount; @@ -508,6 +495,7 @@ private void RenderDrawData(ImDrawDataPtr drawData) var lastBlendState = GraphicsDevice.BlendState; var lastRasterizerState = GraphicsDevice.RasterizerState; var lastDepthStencilState = GraphicsDevice.DepthStencilState; + var lastSamplerState = GraphicsDevice.SamplerStates[0]; // We are submitting vertex and index buffers without using SpriteBatch. // We need to end the batch so we are drawing on top of those that are not flushed @@ -516,6 +504,7 @@ private void RenderDrawData(ImDrawDataPtr drawData) GraphicsDevice.BlendState = BlendState.NonPremultiplied; GraphicsDevice.RasterizerState = RasterizerState; GraphicsDevice.DepthStencilState = DepthStencilState.None; + GraphicsDevice.SamplerStates[0] = SamplerState.LinearClamp; // Handle cases of screen coordinates != from framebuffer coordinates (e.g. retina displays) drawData.ScaleClipRects(ImGui.GetIO().DisplayFramebufferScale); @@ -539,6 +528,28 @@ private void RenderDrawData(ImDrawDataPtr drawData) GraphicsDevice.BlendState = lastBlendState; GraphicsDevice.RasterizerState = lastRasterizerState; GraphicsDevice.DepthStencilState = lastDepthStencilState; + GraphicsDevice.SamplerStates[0] = lastSamplerState; + } + + private unsafe void UpdateDynamicTextures(ImDrawDataPtr drawData) + { + for (var i = 0; i < drawData.Textures.Size; i++) + { + var textureData = drawData.Textures[i]; + + switch (textureData.Status) + { + case ImTextureStatus.WantCreate: + FontAtlas.CreateTexture(GraphicsDevice, textureData); + break; + case ImTextureStatus.WantUpdates: + FontAtlas.UpdateTexture(textureData); + break; + case ImTextureStatus.WantDestroy: + FontAtlas.DestroyTexture(textureData); + break; + } + } } /// @@ -640,9 +651,11 @@ private void RenderCommandLists(ImDrawDataPtr drawData) { var drawCmd = cmdList.CmdBuffer[cmdi]; - if (!LoadedTextures.ContainsKey(drawCmd.TextureId)) + var textureId = (IntPtr)drawCmd.GetTexID(); + + if (!TryGetTexture(textureId, out var texture)) throw new InvalidOperationException( - $"Could not find a texture with id '{drawCmd.TextureId}', please check your bindings" + $"Could not find a texture with id '{textureId}', please check your bindings" ); GraphicsDevice.ScissorRectangle = new Rectangle( @@ -652,7 +665,7 @@ private void RenderCommandLists(ImDrawDataPtr drawData) (int)(drawCmd.ClipRect.W - drawCmd.ClipRect.Y) ); - var effect = UpdateEffect(LoadedTextures[drawCmd.TextureId].Texture); + var effect = UpdateEffect(texture); var vertexOffset = vtxOffset + (int)drawCmd.VtxOffset; var indexOffset = idxOffset + (int)drawCmd.IdxOffset; @@ -682,6 +695,17 @@ private void RenderCommandLists(ImDrawDataPtr drawData) GraphicsDevice.ScissorRectangle = lastScissorRectangle; } + private bool TryGetTexture(IntPtr textureId, out Texture2D texture) + { + if (LoadedTextures.TryGetValue(textureId, out var binding)) + { + texture = binding.Texture; + return true; + } + + return FontAtlas.TryGetTexture(textureId, out texture); + } + #endregion Internals /// @@ -699,7 +723,10 @@ public void Dispose() KeyboardInputOwner = null; if (DestroyContext) + { + FontAtlas.Dispose(); ImGui.DestroyContext(Context); + } foreach (var texture in LoadedTextures.Values) { @@ -714,8 +741,6 @@ public void Dispose() IndexBuffer?.Dispose(); Game.Window.TextInput -= OnWindowOnTextInput; - if (DestroyContext) - SharedFontAtlasCache.Release(FontAtlas); } private sealed class TextureBinding @@ -733,25 +758,20 @@ public TextureBinding(Texture2D texture, bool disposeWithRenderer) private sealed class SharedFontAtlas { - public string Key { get; } - public ImFontAtlasPtr Atlas { get; } - public IntPtr TextureId { get; } = new IntPtr(-1); - - public Texture2D Texture { get; private set; } - - public int ReferenceCount { get; set; } - private ImFontPtr DefaultFontPtr { get; set; } private List FontPointers { get; } - public unsafe SharedFontAtlas(string key, ImGuiOptions options, float scale) + private Dictionary Textures { get; } + + public unsafe SharedFontAtlas(ImGuiOptions options, float scale, ImFontAtlasPtr atlas) { - Key = key; - Atlas = new ImFontAtlasPtr(ImGuiNative.ImFontAtlas_ImFontAtlas()); + Atlas = atlas; + Atlas.TexDesiredFormat = ImTextureFormat.Rgba32; FontPointers = new List(); + Textures = new Dictionary(); if (options == null || options.LoadDefaultFont) DefaultFontPtr = Atlas.AddFontDefault(); @@ -766,12 +786,11 @@ public unsafe SharedFontAtlas(string key, ImGuiOptions options, float scale) foreach (var fallback in font.Fallbacks) { - var config = new ImFontConfigPtr(ImGuiNative.ImFontConfig_ImFontConfig()); + var config = ImGui.ImFontConfig(); config.MergeMode = true; - config.FontNo = fallback.Index; + config.FontNo = (uint)fallback.Index; - Atlas.AddFontFromFileTTF(fallback.Path, font.Size * scale, config, - GetGlyphRanges(Atlas, fallback.GlyphRanges)); + Atlas.AddFontFromFileTTF(fallback.Path, font.Size * scale, config); config.Destroy(); } @@ -782,6 +801,9 @@ public void AssignFontPointers(ImGuiOptions options, out ImFontPtr defaultFontPt { defaultFontPtr = DefaultFontPtr; + if (defaultFontPtr.IsNull && FontPointers.Count > 0) + defaultFontPtr = FontPointers[0]; + if (options == null) return; @@ -789,85 +811,57 @@ public void AssignFontPointers(ImGuiOptions options, out ImFontPtr defaultFontPt options.Fonts[i].Context = FontPointers[i]; } - public unsafe void EnsureTexture(GraphicsDevice graphicsDevice) - { - if (Texture != null) - return; - - Atlas.GetTexDataAsRGBA32(out byte* pixelData, out var width, out var height, out var bytesPerPixel); + public bool TryGetTexture(IntPtr textureId, out Texture2D texture) => + Textures.TryGetValue(textureId, out texture); - var pixels = new byte[width * height * bytesPerPixel]; - Marshal.Copy(new IntPtr(pixelData), pixels, 0, pixels.Length); - - Texture = new Texture2D(graphicsDevice, width, height, false, SurfaceFormat.Color); - Texture.SetData(pixels); - - Atlas.SetTexID(TextureId); - Atlas.ClearTexData(); - } - - public unsafe void Dispose() + public unsafe void CreateTexture(GraphicsDevice graphicsDevice, ImTextureDataPtr textureData) { - Texture?.Dispose(); - Texture = null; - ImGuiNative.ImFontAtlas_destroy(Atlas.NativePtr); + if (textureData.Format != ImTextureFormat.Rgba32) + throw new NotSupportedException($"Unsupported ImGui texture format: {textureData.Format}"); + + var id = new IntPtr(Interlocked.Increment(ref NextTextureId)); + var texture = new Texture2D(graphicsDevice, textureData.Width, textureData.Height, false, SurfaceFormat.Color); + Textures.Add(id, texture); + UploadTexture(texture, textureData); + textureData.SetTexID(id); + textureData.SetStatus(ImTextureStatus.Ok); } - } - - private static class SharedFontAtlasCache - { - private static readonly Dictionary Cache = new Dictionary(); - public static SharedFontAtlas Retain(ImGuiOptions options, float scale) + public unsafe void UpdateTexture(ImTextureDataPtr textureData) { - var key = CreateKey(options, scale); + var id = (IntPtr)textureData.TexID; - if (!Cache.TryGetValue(key, out var atlas)) - { - atlas = new SharedFontAtlas(key, options, scale); - Cache.Add(key, atlas); - } + if (!Textures.TryGetValue(id, out var texture)) + throw new InvalidOperationException($"Could not update ImGui texture with id '{id}'"); - atlas.ReferenceCount++; - return atlas; + UploadTexture(texture, textureData); + textureData.SetStatus(ImTextureStatus.Ok); } - public static void Release(SharedFontAtlas atlas) + public void DestroyTexture(ImTextureDataPtr textureData) { - if (atlas == null) - return; + var id = (IntPtr)textureData.TexID; - atlas.ReferenceCount--; - - if (atlas.ReferenceCount > 0) - return; + if (Textures.Remove(id, out var texture)) + texture.Dispose(); - Cache.Remove(atlas.Key); - atlas.Dispose(); + textureData.SetTexID(IntPtr.Zero); + textureData.SetStatus(ImTextureStatus.Destroyed); } - private static string CreateKey(ImGuiOptions options, float scale) + private static unsafe void UploadTexture(Texture2D texture, ImTextureDataPtr textureData) { - var key = "scale=" + scale.ToString("R", CultureInfo.InvariantCulture); - - if (options == null) - return key + ";default"; - - key += ";loadDefault=" + options.LoadDefaultFont; - - foreach (var font in options.Fonts) - { - key += ";font=" + font.Path + "," + font.Size.ToString(CultureInfo.InvariantCulture); + var pixels = new byte[textureData.Width * textureData.Height * textureData.BytesPerPixel]; + Marshal.Copy((IntPtr)textureData.GetPixels(), pixels, 0, pixels.Length); + texture.SetData(pixels); + } - foreach (var fallback in font.Fallbacks) - { - key += ";fallback=" + fallback.Path + "," + - fallback.Index.ToString(CultureInfo.InvariantCulture) + "," + - fallback.GlyphRanges; - } - } + public void Dispose() + { + foreach (var texture in Textures.Values) + texture.Dispose(); - return key; + Textures.Clear(); } } } diff --git a/Wobble/Graphics/ImGUI/SpriteImGui.cs b/Wobble/Graphics/ImGUI/SpriteImGui.cs index 5b16376e..087b5f68 100644 --- a/Wobble/Graphics/ImGUI/SpriteImGui.cs +++ b/Wobble/Graphics/ImGUI/SpriteImGui.cs @@ -1,5 +1,5 @@ using System; -using ImGuiNET; +using Hexa.NET.ImGui; using Microsoft.Xna.Framework; namespace Wobble.Graphics.ImGUI diff --git a/Wobble/Graphics/UI/Debugging/DebugOverlay.cs b/Wobble/Graphics/UI/Debugging/DebugOverlay.cs index 312be451..d32dd791 100644 --- a/Wobble/Graphics/UI/Debugging/DebugOverlay.cs +++ b/Wobble/Graphics/UI/Debugging/DebugOverlay.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Generic; -using ImGuiNET; +using Hexa.NET.ImGui; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Input; using Wobble.Graphics.ImGUI; @@ -179,7 +179,7 @@ private void RenderTextObjectControls() highlightedTextObjectId = selectedTextObjectId; - if (ImGui.BeginChild("SpriteTextPlusObjectList", new Vector2(0, 180), ImGuiChildFlags.Border)) + if (ImGui.BeginChild("SpriteTextPlusObjectList", new Vector2(0, 180), ImGuiChildFlags.Borders)) { for (var i = 0; i < objects.Count; i++) { @@ -228,7 +228,7 @@ private void RenderDrawableControls() highlightedDrawableId = selectedDrawableId; - if (ImGui.BeginChild("DrawableTypeList", new Vector2(0, 260), ImGuiChildFlags.Border)) + if (ImGui.BeginChild("DrawableTypeList", new Vector2(0, 260), ImGuiChildFlags.Borders)) { for (var i = 0; i < groups.Count; i++) RenderDrawableTypeGroup(groups[i]); diff --git a/Wobble/Wobble.csproj b/Wobble/Wobble.csproj index 29a13dae..f4c3a017 100644 --- a/Wobble/Wobble.csproj +++ b/Wobble/Wobble.csproj @@ -10,7 +10,7 @@ - +