From cf3091b4099b9ca365360088c8409c93a71a2361 Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Wed, 14 Feb 2024 13:05:45 +0900 Subject: [PATCH 1/2] If docked, force default font on window decoration ImGui docking functions are called outside our drawing context (from ImGui::NewFrame), which includes most of dock-related drawing calls. However, ImGui::RenderWindowDecoration is called from ImGui::Begin, which may be under the effect of other pushed font. As IG::RWD references to the ImDrawList irrelevant to the global shared state, it was trying to draw a rectangle referring to a pixel that is not guaranteed to be a white pixel. This commit fixes that by forcing the use of the default font for IG::RWD when the window is docked. --- .../ImGuiClipboardFunctionProvider.cs | 1 - .../Internals/ImGuiDockNodeUpdateForceFont.cs | 78 +++++++++++++++++++ 2 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/ImGuiDockNodeUpdateForceFont.cs diff --git a/Dalamud/Interface/Internal/ImGuiClipboardFunctionProvider.cs b/Dalamud/Interface/Internal/ImGuiClipboardFunctionProvider.cs index 1746fb1c4..bbf665405 100644 --- a/Dalamud/Interface/Internal/ImGuiClipboardFunctionProvider.cs +++ b/Dalamud/Interface/Internal/ImGuiClipboardFunctionProvider.cs @@ -52,7 +52,6 @@ internal sealed unsafe class ImGuiClipboardFunctionProvider : IServiceType, IDis private ImGuiClipboardFunctionProvider(InterfaceManager.InterfaceManagerWithScene imws) { // Effectively waiting for ImGui to become available. - _ = imws; Debug.Assert(ImGuiHelpers.IsImGuiInitialized, "IMWS initialized but IsImGuiInitialized is false?"); var io = ImGui.GetIO(); diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/ImGuiDockNodeUpdateForceFont.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/ImGuiDockNodeUpdateForceFont.cs new file mode 100644 index 000000000..a2a30429a --- /dev/null +++ b/Dalamud/Interface/ManagedFontAtlas/Internals/ImGuiDockNodeUpdateForceFont.cs @@ -0,0 +1,78 @@ +using System.Diagnostics; +using System.Linq; + +using Dalamud.Hooking; +using Dalamud.Interface.Internal; +using Dalamud.Interface.Utility; + +namespace Dalamud.Interface.ManagedFontAtlas.Internals; + +/// +/// Forces ImGui::RenderWindowDecorations to use the default font. +/// Fixes dock node draw using shared data across different draw lists. +/// TODO: figure out how to synchronize ImDrawList::_Data and ImDrawList::Push/PopTextureID across different instances. +/// It might be better to just special-case that particular function, +/// as no other code touches ImDrawList that is irrelevant to the global shared state, +/// with the exception of Dock... functions which are called from ImGui::NewFrame, +/// which are guaranteed to use the global default font. +/// +[ServiceManager.EarlyLoadedService] +internal class ImGuiRenderWindowDecorationsForceFont : IServiceType, IDisposable +{ + private const int CImGuiRenderWindowDecorationsOffset = 0x461B0; + private const int CImGuiWindowDockIsActiveOffset = 0x401; + + private readonly Hook hook; + + [ServiceManager.ServiceConstructor] + private ImGuiRenderWindowDecorationsForceFont(InterfaceManager.InterfaceManagerWithScene imws) + { + // Effectively waiting for ImGui to become available. + Debug.Assert(ImGuiHelpers.IsImGuiInitialized, "IMWS initialized but IsImGuiInitialized is false?"); + + var cimgui = Process.GetCurrentProcess().Modules.Cast() + .First(x => x.ModuleName == "cimgui.dll") + .BaseAddress; + this.hook = Hook.FromAddress( + cimgui + CImGuiRenderWindowDecorationsOffset, + this.ImGuiRenderWindowDecorationsDetour); + this.hook.Enable(); + } + + private delegate void ImGuiRenderWindowDecorationsDelegate( + nint window, + nint titleBarRectPtr, + byte titleBarIsHighlight, + byte handleBordersAndResizeGrips, + int resizeGripCount, + nint resizeGripColPtr, + float resizeGripDrawSize); + + /// + public void Dispose() => this.hook.Dispose(); + + private unsafe void ImGuiRenderWindowDecorationsDetour( + nint window, + nint titleBarRectPtr, + byte titleBarIsHighlight, + byte handleBordersAndResizeGrips, + int resizeGripCount, + nint resizeGripColPtr, + float resizeGripDrawSize) + { + using ( + ((byte*)window)![CImGuiWindowDockIsActiveOffset] != 0 + ? Service.Get().DefaultFontHandle?.Push() + : null) + { + this.hook.Original( + window, + titleBarRectPtr, + titleBarIsHighlight, + handleBordersAndResizeGrips, + resizeGripCount, + resizeGripColPtr, + resizeGripDrawSize); + } + } +} From 2de9c8ed5b88e4f4ba0ecbc05c1bfc46cc10495b Mon Sep 17 00:00:00 2001 From: Soreepeong Date: Wed, 14 Feb 2024 21:59:20 +0900 Subject: [PATCH 2/2] Fix insufficient ImDrawList implementation --- Dalamud/Interface/Internal/DalamudIme.cs | 4 +- .../Internal/ImGuiDrawListFixProvider.cs | 124 ++++++++++++++++++ .../ManagedAsserts/ImGuiContextOffsets.cs | 2 + .../Internals/ImGuiDockNodeUpdateForceFont.cs | 78 ----------- 4 files changed, 128 insertions(+), 80 deletions(-) create mode 100644 Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs delete mode 100644 Dalamud/Interface/ManagedFontAtlas/Internals/ImGuiDockNodeUpdateForceFont.cs diff --git a/Dalamud/Interface/Internal/DalamudIme.cs b/Dalamud/Interface/Internal/DalamudIme.cs index 28a9075bd..1ee248b17 100644 --- a/Dalamud/Interface/Internal/DalamudIme.cs +++ b/Dalamud/Interface/Internal/DalamudIme.cs @@ -11,6 +11,7 @@ using System.Text.Unicode; using Dalamud.Game.Text; using Dalamud.Hooking.WndProcHook; using Dalamud.Interface.GameFonts; +using Dalamud.Interface.Internal.ManagedAsserts; using Dalamud.Interface.ManagedFontAtlas.Internals; using Dalamud.Interface.Utility; @@ -28,7 +29,6 @@ namespace Dalamud.Interface.Internal; [ServiceManager.BlockingEarlyLoadedService] internal sealed unsafe class DalamudIme : IDisposable, IServiceType { - private const int ImGuiContextTextStateOffset = 0x4588; private const int CImGuiStbTextCreateUndoOffset = 0xB57A0; private const int CImGuiStbTextUndoOffset = 0xB59C0; @@ -178,7 +178,7 @@ internal sealed unsafe class DalamudIme : IDisposable, IServiceType internal char InputModeIcon { get; private set; } private static ImGuiInputTextState* TextState => - (ImGuiInputTextState*)(ImGui.GetCurrentContext() + ImGuiContextTextStateOffset); + (ImGuiInputTextState*)(ImGui.GetCurrentContext() + ImGuiContextOffsets.TextStateOffset); /// public void Dispose() diff --git a/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs b/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs new file mode 100644 index 000000000..cdf7ab23e --- /dev/null +++ b/Dalamud/Interface/Internal/ImGuiDrawListFixProvider.cs @@ -0,0 +1,124 @@ +using System.Diagnostics; +using System.Linq; +using System.Numerics; + +using Dalamud.Hooking; + +using ImGuiNET; + +namespace Dalamud.Interface.Internal; + +/// +/// Fixes ImDrawList not correctly dealing with the current texture for that draw list not in tune with the global +/// state. Currently, ImDrawList::AddPolyLine and ImDrawList::AddRectFilled are affected. +/// +/// * The implementation for AddRectFilled is entirely replaced with the hook below. +/// * The implementation for AddPolyLine is wrapped with Push/PopTextureID. +/// +/// TODO: +/// * imgui_draw.cpp:1433 ImDrawList::AddRectFilled +/// The if block needs a PushTextureID(_Data->TexIdCommon)/PopTextureID() block, +/// if _Data->TexIdCommon != _CmdHeader.TextureId. +/// * imgui_draw.cpp:729 ImDrawList::AddPolyLine +/// The if block always needs to call PushTextureID if the abovementioned condition is not met. +/// Change push_texture_id to only have one condition. +/// +[ServiceManager.EarlyLoadedService] +internal sealed unsafe class ImGuiDrawListFixProvider : IServiceType, IDisposable +{ + private const int CImGuiImDrawListAddPolyLineOffset = 0x589B0; + private const int CImGuiImDrawListAddRectFilled = 0x59FD0; + private const int CImGuiImDrawListSharedDataTexIdCommonOffset = 0; + + private readonly Hook hookImDrawListAddPolyline; + private readonly Hook hookImDrawListAddRectFilled; + + [ServiceManager.ServiceConstructor] + private ImGuiDrawListFixProvider(InterfaceManager.InterfaceManagerWithScene imws) + { + // Force cimgui.dll to be loaded. + _ = ImGui.GetCurrentContext(); + var cimgui = Process.GetCurrentProcess().Modules.Cast() + .First(x => x.ModuleName == "cimgui.dll") + .BaseAddress; + + this.hookImDrawListAddPolyline = Hook.FromAddress( + cimgui + CImGuiImDrawListAddPolyLineOffset, + this.ImDrawListAddPolylineDetour); + this.hookImDrawListAddRectFilled = Hook.FromAddress( + cimgui + CImGuiImDrawListAddRectFilled, + this.ImDrawListAddRectFilledDetour); + this.hookImDrawListAddPolyline.Enable(); + this.hookImDrawListAddRectFilled.Enable(); + } + + private delegate void ImDrawListAddPolyLine( + ImDrawListPtr drawListPtr, + ref Vector2 points, + int pointsCount, + uint color, + ImDrawFlags flags, + float thickness); + + private delegate void ImDrawListAddRectFilled( + ImDrawListPtr drawListPtr, + ref Vector2 min, + ref Vector2 max, + uint col, + float rounding, + ImDrawFlags flags); + + /// + public void Dispose() + { + this.hookImDrawListAddPolyline.Dispose(); + this.hookImDrawListAddRectFilled.Dispose(); + } + + private void ImDrawListAddRectFilledDetour( + ImDrawListPtr drawListPtr, + ref Vector2 min, + ref Vector2 max, + uint col, + float rounding, + ImDrawFlags flags) + { + if (rounding < 0 || (flags & ImDrawFlags.RoundCornersMask) == ImDrawFlags.RoundCornersMask) + { + var texIdCommon = *(nint*)(drawListPtr._Data + CImGuiImDrawListSharedDataTexIdCommonOffset); + var pushTextureId = texIdCommon != drawListPtr._CmdHeader.TextureId; + if (pushTextureId) + drawListPtr.PushTextureID(texIdCommon); + + drawListPtr.PrimReserve(6, 4); + drawListPtr.PrimRect(min, max, col); + + if (pushTextureId) + drawListPtr.PopTextureID(); + } + else + { + drawListPtr.PathRect(min, max, rounding, flags); + drawListPtr.PathFillConvex(col); + } + } + + private void ImDrawListAddPolylineDetour( + ImDrawListPtr drawListPtr, + ref Vector2 points, + int pointsCount, + uint color, + ImDrawFlags flags, + float thickness) + { + var texIdCommon = *(nint*)(drawListPtr._Data + CImGuiImDrawListSharedDataTexIdCommonOffset); + var pushTextureId = texIdCommon != drawListPtr._CmdHeader.TextureId; + if (pushTextureId) + drawListPtr.PushTextureID(texIdCommon); + + this.hookImDrawListAddPolyline.Original(drawListPtr, ref points, pointsCount, color, flags, thickness); + + if (pushTextureId) + drawListPtr.PopTextureID(); + } +} diff --git a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs index fd203192f..89e23ab78 100644 --- a/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs +++ b/Dalamud/Interface/Internal/ManagedAsserts/ImGuiContextOffsets.cs @@ -18,4 +18,6 @@ internal static class ImGuiContextOffsets public const int FontStackOffset = 0x7A4; public const int BeginPopupStackOffset = 0x7B8; + + public const int TextStateOffset = 0x4588; } diff --git a/Dalamud/Interface/ManagedFontAtlas/Internals/ImGuiDockNodeUpdateForceFont.cs b/Dalamud/Interface/ManagedFontAtlas/Internals/ImGuiDockNodeUpdateForceFont.cs deleted file mode 100644 index a2a30429a..000000000 --- a/Dalamud/Interface/ManagedFontAtlas/Internals/ImGuiDockNodeUpdateForceFont.cs +++ /dev/null @@ -1,78 +0,0 @@ -using System.Diagnostics; -using System.Linq; - -using Dalamud.Hooking; -using Dalamud.Interface.Internal; -using Dalamud.Interface.Utility; - -namespace Dalamud.Interface.ManagedFontAtlas.Internals; - -/// -/// Forces ImGui::RenderWindowDecorations to use the default font. -/// Fixes dock node draw using shared data across different draw lists. -/// TODO: figure out how to synchronize ImDrawList::_Data and ImDrawList::Push/PopTextureID across different instances. -/// It might be better to just special-case that particular function, -/// as no other code touches ImDrawList that is irrelevant to the global shared state, -/// with the exception of Dock... functions which are called from ImGui::NewFrame, -/// which are guaranteed to use the global default font. -/// -[ServiceManager.EarlyLoadedService] -internal class ImGuiRenderWindowDecorationsForceFont : IServiceType, IDisposable -{ - private const int CImGuiRenderWindowDecorationsOffset = 0x461B0; - private const int CImGuiWindowDockIsActiveOffset = 0x401; - - private readonly Hook hook; - - [ServiceManager.ServiceConstructor] - private ImGuiRenderWindowDecorationsForceFont(InterfaceManager.InterfaceManagerWithScene imws) - { - // Effectively waiting for ImGui to become available. - Debug.Assert(ImGuiHelpers.IsImGuiInitialized, "IMWS initialized but IsImGuiInitialized is false?"); - - var cimgui = Process.GetCurrentProcess().Modules.Cast() - .First(x => x.ModuleName == "cimgui.dll") - .BaseAddress; - this.hook = Hook.FromAddress( - cimgui + CImGuiRenderWindowDecorationsOffset, - this.ImGuiRenderWindowDecorationsDetour); - this.hook.Enable(); - } - - private delegate void ImGuiRenderWindowDecorationsDelegate( - nint window, - nint titleBarRectPtr, - byte titleBarIsHighlight, - byte handleBordersAndResizeGrips, - int resizeGripCount, - nint resizeGripColPtr, - float resizeGripDrawSize); - - /// - public void Dispose() => this.hook.Dispose(); - - private unsafe void ImGuiRenderWindowDecorationsDetour( - nint window, - nint titleBarRectPtr, - byte titleBarIsHighlight, - byte handleBordersAndResizeGrips, - int resizeGripCount, - nint resizeGripColPtr, - float resizeGripDrawSize) - { - using ( - ((byte*)window)![CImGuiWindowDockIsActiveOffset] != 0 - ? Service.Get().DefaultFontHandle?.Push() - : null) - { - this.hook.Original( - window, - titleBarRectPtr, - titleBarIsHighlight, - handleBordersAndResizeGrips, - resizeGripCount, - resizeGripColPtr, - resizeGripDrawSize); - } - } -}