Closed GreenComfyTea closed 4 months ago
OnLoad
won't work because all DirectX/ImGui related stuff is only initialized during the transition to the title screen. But yeah I'll have a look, I'm not completely sure how exactly fonts work in ImGui either.
It also might take a bit before I get to it because I'm pretty busy at the moment and working on this by myself lol.
What you could try is during the first render call, load the font but with a config and enable MergeMode
and specify glyph ranges. (See here)
Also keep this in mind: https://github.com/ocornut/imgui/blob/master/docs/FONTS.md#3-missing-glyph-ranges
Yeah, no problem. Whatever time you need and remember to take breaks too!
What you could try is during the first render call, load the font but with a config and enable MergeMode and specify glyph ranges. (See here)
This doesn't work either. Same result, all ImGui windows disappear.
private ImFontPtr Font { get; set; }
private bool IsFontInitialized { get; set; } = false;
private unsafe CustomizationWindow InitFont()
{
IsFontInitialized = true;
var fonts = ImGui.GetIO().Fonts;
var fontConfig = new ImFontConfig();
fontConfig.MergeMode = 1;
var fontConfigPtr = new ImFontConfigPtr(&fontConfig);
// fonts.AddFontDefault(fontConfigPtr); // Uncommenting makes no difference
Font = fonts.AddFontFromFileTTF(Path.Combine(Constants.PLUGIN_DATA_PATH, "NotoSansKR-Bold.otf"), 26f, fontConfigPtr, fonts.GetGlyphRangesKorean());
fonts.Build();
// ImGui.SetCurrentFont(Font); // Uncommenting makes no difference
var loaded = Font.IsLoaded(); // True
Log.Info(loaded.ToString());
}
public void OnImGuiFreeRender()
{
if(!IsFontInitialized) InitFont();
ImGui.PushFont(Font);
...
ImGui.PopFont();
}
I tried doing it inside SPL directly, in Core
-> Renderer
-> ImGuiRender()
before ImGui.NewFrame()
and I am getting same results as inside plugin's OnImguiFreeRender()
.
With the code below, in both cases, the windows don't disappear, thou default font is not actually updated, korean symbols don't work. Calling Build()
does make them disappear.
public static unsafe void InitFont()
{
IsFontInitialized = true;
var io = ImGui.GetIO();
var fonts = io.Fonts;
ImFontConfig* config = ImGuiNative.ImFontConfig_ImFontConfig();
config->MergeMode = 1;
Font = fonts.AddFontFromFileTTF(Path.Combine(Constants.PLUGIN_DATA_PATH, "NotoSansKR-Bold.otf"), 26f, config, fonts.GetGlyphRangesKorean());
// fonts.Build();
}
There was a person on ImGui.NET discord who appears to have the same issue. So it's most likely ImGui.NET bug.
Mine:
Have you tried loading the font inside D3DModule::imgui_load_fonts
? If it really is a bug with ImGui.NET then you could also try creating a native component and load the font from there, at least until I manage to find a proper fix for it.
I haven't, I will take a look, thanks!
Loading the font inside D3DModule::imgui_load_fonts
worked, as I would expect. I will fiddle with a native component now.
void D3DModule::imgui_load_fonts() {
const auto& io = *igGetIO();
ImFontAtlas_Clear(io.Fonts);
const auto& chunk_module = NativePluginFramework::get_module<ChunkModule>();
const auto& default_chunk = chunk_module->request_chunk("Default");
const auto& roboto = default_chunk->get_file("/Resources/Roboto-Medium.ttf");
const auto& noto_sans_jp = default_chunk->get_file("/Resources/NotoSansJP-Regular.ttf");
const auto& fa6 = default_chunk->get_file("/Resources/fa-solid-900.ttf");
ImFontConfig* font_cfg = ImFontConfig_ImFontConfig();
font_cfg->FontDataOwnedByAtlas = false;
font_cfg->MergeMode = false;
ImFontAtlas_AddFontFromMemoryTTF(io.Fonts, roboto->Contents.data(), (i32)roboto->size(), 16.0f, font_cfg, nullptr);
font_cfg->MergeMode = true;
ImFontAtlas_AddFontFromMemoryTTF(io.Fonts, noto_sans_jp->Contents.data(), (i32)noto_sans_jp->size(), 18.0f, font_cfg, s_japanese_glyph_ranges);
ImFontAtlas_AddFontFromMemoryTTF(io.Fonts, fa6->Contents.data(), (i32)fa6->size(), 16.0f, font_cfg, icons_ranges);
ImFontAtlas_AddFontFromFileTTF(io.Fonts, "D:/Programs/Steam/steamapps/common/Monster Hunter World/nativePC/plugins/CSharp/BetterMatchmaking/data/NotoSansKR-Bold.otf", 26.0f, font_cfg, ImFontAtlas_GetGlyphRangesKorean(io.Fonts));
ImFontAtlas_Build(io.Fonts);
ImFontConfig_destroy(font_cfg);
}
Actually, this issue seems to be related: https://github.com/ocornut/imgui/issues/2311. Have to recreate font atlas texture (call ImGui_ImplDX11_CreateFontsTexture()
/ImGui_ImplDX12_CreateFontsTexture()
?).
Okey, so. I don't know how to link ImGui to a Native Component, so I tried to modify existing SPL code. I assume I need to rebuild the texture and ImGui_ImplDX12_CreateFontsTexture()
does everything I need. It calls ImFontAtlas_GetTexDataAsRGBA32
which internally calls ImFontAtlas_Build
, creates font atlas texture and sets the id for it: ImFontAtlas_SetTexID
.
In Managed
-> Core
-> Rendering
-> Renderer
I did this:
private static ImFontPtr Font { get; set; }
private static bool IsFontInitialized { get; set; } = false;
private static unsafe void InitFont()
{
IsFontInitialized = true;
var io = ImGui.GetIO();
var fonts = io.Fonts;
ImFontConfig* config = ImGuiNative.ImFontConfig_ImFontConfig();
config->MergeMode = 0;
config->FontDataOwnedByAtlas = 0;
// Maybe I will need this, idk
// fonts.Clear();
fonts.AddFontDefault();
config->MergeMode = 1;
Font = fonts.AddFontFromFileTTF(@"D:\Programs\Steam\steamapps\common\Monster Hunter World\nativePC\plugins\CSharp\BetterMatchmaking\data\NotoSansKR-Bold.otf", 26f, config, fonts.GetGlyphRangesKorean());
// Only doing DX12 for now for simplicity
ImGuiExtensions.RecreateFontTextureDX12();
}
[UnmanagedCallersOnly]
internal static unsafe nint ImGuiRender()
{
if(Input.IsPressed(_menuKey))
_showMenu = !_showMenu;
if(Input.IsPressed(_demoKey))
_showDemo = !_showDemo;
if(!IsFontInitialized) InitFont();
...
ImGui.NewFrame();
...
}
In Managed
-> Core
-> Rendering
-> ImGuiExtensions
I added this:
public static void RecreateFontTextureDX12() => InternalCalls.RecreateFontTextureDX12();
In Native
-> Header Files
-> mhw-cs-plugin-loader
-> Modules
-> ImGuiModule.h
I added this:
private:
static void recreate_font_texture_dx12();
In Native
-> Source Files
-> mhw-cs-plugin-loader
-> Modules
-> ImGuiModule.cpp
I added this:
#include "imgui_impl_dx12.h"
void ImGuiModule::initialize(CoreClr* coreclr) {
coreclr->add_internal_call("RecreateFontTextureDX12", &ImGuiModule::recreate_font_texture_dx12);
...
}
void ImGuiModule::recreate_font_texture_dx12() {
// Line that produces the error:
ImGui_ImplDX12_CreateFontsTexture();
}
This gives me this error:
>ImGuiModule.obj : error LNK2001: unresolved external symbol "void __cdecl ImGui_ImplDX12_CreateFontsTexture(void)" (?ImGui_ImplDX12_CreateFontsTexture@@YAXXZ)
1>E:\GitHub\SharpPluginLoader\x64\Release\mhw-cs-plugin-loader.dll : fatal error LNK1120: 1 unresolved externals
Sorry, I am not very familiar with C++. I am extra allergic to declaration/implementation split and dependency hells. xd
ImGui_ImplDX12_CreateFontsTexture
is declared static
meaning you cannot access it from outside imgui_impl_dx12.cpp
. Only if you remove the static
can you do that.
Realistically however you should not be calling that yourself. You should call ImGui_ImplDX12_InvalidateDeviceObjects
instead, which will lead to the font atlas being rebuilt on the next frame. Be sure to do this before the NewFrame
call.
Alternatively, this PR seems to be a more generic solution to this problem. I could potentially merge this into my fork of imgui.
If merging the PR is feasible, go for it. All I want is to allow translators to define their own font in the localization file. Ideally, I want to load fonts on request at runtime, my plugins already support auto-updating localizations at runtime. But I will be fine if it was just at initialization on startup.
Right now I am crashing and I am not even sure where.
In Managed
-> Core
-> Rendering
-> Renderer
:
private static ImFontPtr Font { get; set; }
private static bool IsFontInitialized { get; set; } = false;
private static unsafe void InitFont()
{
IsFontInitialized = true;
var io = ImGui.GetIO();
var fonts = io.Fonts;
ImFontConfig* config = ImGuiNative.ImFontConfig_ImFontConfig();
config->MergeMode = 0;
config->FontDataOwnedByAtlas = 0;
// Maybe I will need this, idk
// fonts.Clear();
fonts.AddFontDefault();
config->MergeMode = 1;
Font = fonts.AddFontFromFileTTF(@"D:\Programs\Steam\steamapps\common\Monster Hunter World\nativePC\plugins\CSharp\BetterMatchmaking\data\NotoSansKR-Bold.otf", 26f, config, fonts.GetGlyphRangesKorean());
fonts.Build();
// Only doing DX12 for now for simplicity
Log.Info("Pre-InvalidateDeviceObjectsDX12");
ImGuiExtensions.InvalidateDeviceObjectsDX12();
Log.Info("Post-InvalidateDeviceObjectsDX12");
}
[UnmanagedCallersOnly]
internal static unsafe nint ImGuiRender()
{
if(Input.IsPressed(_menuKey))
_showMenu = !_showMenu;
if(Input.IsPressed(_demoKey))
_showDemo = !_showDemo;
if(!IsFontInitialized) InitFont();
...
Log.Info("Pre-NewFrame");
ImGui.NewFrame();
Log.Info("Post-NewFrame");
...
Log.Info("Pre-EndFrame");
ImGui.EndFrame();
Log.Info("Post-EndFrame");
...
Log.Info("Pre-Render");
ImGui.Render();
Log.Info("Post-Render");
...
}
In Managed
-> Core
-> Rendering
-> ImGuiExtensions
:
public static void InvalidateDeviceObjectsDX12() => InternalCalls.InvalidateDeviceObjectsDX12();
In Managed
-> Core
-> InternalCalls
:
public static delegate* unmanaged<void> InvalidateDeviceObjectsDX12Ptr;
public static void InvalidateDeviceObjectsDX12() => InvalidateDeviceObjectsDX12Ptr();
In Native
-> Source Files
-> mhw-cs-plugin-loader
-> Modules
-> ImGuiModule.cpp
:
#include "imgui_impl_dx12.h"
#include "Log.h"
void ImGuiModule::initialize(CoreClr* coreclr) {
coreclr->add_internal_call("InvalidateDeviceObjectsDX12", &ImGuiModule::invalidate_device_objects_dx12);
...
}
void ImGuiModule::invalidate_device_objects_dx12() {
dlog::info("ImGui_ImplDX12_InvalidateDeviceObjects");
ImGui_ImplDX12_InvalidateDeviceObjects();
}
Ok I got it working by calling ImGui_ImplDX12_CreateDeviceObjects()
instead. Much heavier function, but at least it works.
WICKED.
Merging the PR or using this is up to you. InitFont()
call right before NewFrame()
can be replaced with a new event, something like bool OnPreImGuiRender()
that returns bool
that indicates that plugins are requesting font atlas rebuild. And inside OnPreImGuiRender()
plugins can initialize their fonts with just ImGui.GetIO().Fonts.AddFontFrom...()
.
All modified files: ImGuiModule.h: https://pastebin.com/E7Tg3L0K ImGuiModule.cpp: https://pastebin.com/yLhxJ9uP InternalCalls.cs: https://pastebin.com/ffRVfa8A ImGuiExtensions.cs: https://pastebin.com/RhDA2pXf Renderer.cs: https://pastebin.com/XmT7EEgF
I ended up choosing a different approach entirely, which is implemented in cfb9e11dfe2fcd99c9bec63139438678997eece8.
The new system lets you submit your own fonts inside OnLoad
using Renderer.RegisterFont
.
public static unsafe void RegisterFont(string name, string path, float size, nint glyphRanges = 0,
bool merge = false, int oversampleV = 0, int oversampleH = 0)
Example:
// Inside OnLoad
Renderer.RegisterFont("My Font", @"C:\Windows\Fonts\times.ttf", 16f);
// Inside OnImGuiRender
ImGui.PushFont(Renderer.GetFont("My Font"));
ImGui.Text("Sample Text");
ImGui.PopFont();
Using a more unique name than "My Font" is recommended because it is using a dictionary behind the scenes, so try to avoid name collisions with other plugins.
For specific glyph ranges do not use the GetGlyphRanges...
functions provided by ImFontAtlas
, use Renderer.GetGlyphRanges
instead. It provides all the default glyph ranges provided by ImGui as well. If you want to use custom glyph ranges, you can use GlyphRangeFactory.CreateGlyphRanges
to allocate persistent arrays that can be passed directly to RegisterFont
.
Works well so far, thank you!
GlyphRangeFactory
is internal
so it's not accessible.
Whoops, let me fix that
Edit: f8f4132fa2f85179cad726c873de6af8eb0603ce
I am having trouble merging fonts. 7?
is supposed to be 7⭐
. ⭐
= U+2B50
. Am I missing something?
public void OnLoad()
{
GlyphRange[] emojiRange = [(0x2122, 0x2B55), (0x0, 0x0)];
var emojiGlyphRangeAddress = GlyphRangeFactory.CreateGlyphRanges(emojiRange);
Renderer.RegisterFont("Default@@BetterMatchmaking", $"{Constants.PLUGIN_FONTS_PATH}NotoSans-Bold.ttf", 17f, 0, false, 2, 2);
Renderer.RegisterFont("Default@@BetterMatchmaking", $"{Constants.PLUGIN_FONTS_PATH}NotoEmoji-Bold.ttf", 17f, emojiGlyphRangeAddress, true, 2, 2);
GlyphRangeFactory.DestroyGlyphRanges(emojiGlyphRangeAddress);
}
Individually, both fonts work correctly? (Idk what the hell are those circles thou, I don't even load ASCII range?).
Just NotoSans
:
public void OnLoad()
{
Renderer.RegisterFont("Default@@BetterMatchmaking", $"{Constants.PLUGIN_FONTS_PATH}NotoSans-Bold.ttf", 17f, 0, false, 2, 2);
}
Just NotoEmoji
:
public void OnLoad()
{
GlyphRange[] emojiRange = [(0x2122, 0x2B55), (0x0, 0x0)];
var emojiGlyphRangeAddress = GlyphRangeFactory.CreateGlyphRanges(emojiRange);
Renderer.RegisterFont("Default@@BetterMatchmaking", $"{Constants.PLUGIN_FONTS_PATH}NotoEmoji-Bold.ttf", 17f, emojiGlyphRangeAddress, false, 2, 2);
GlyphRangeFactory.DestroyGlyphRanges(emojiGlyphRangeAddress);
}
Not sure if this is what's causing it and it might not be communicated properly, but you shouldn't free the glyph ranges until after the font was loaded (i.e. the first time OnImGui(Free)Render
was called).
In fact, I'm not sure if you should destroy them at all. If anything, do it inside OnUnload
. Should make that a bit clearer. And yes, merging seems to always be weird.
Another thing, you don't need to manually add the (0, 0) entries for your glyph ranges, those are added automatically.
Oopsies, you are right. I somehow thought that font is built on each RegisterFont, which is extremely silly of me.
Probably, I got confused because I saw the method description saying to destroy the ranges, as well as saying to provide null terminator.
Creates a new set of glyph ranges from the provided ranges, with a null terminator at the end.
I read it as if a null terminator should belong to the provided ranges.
It works now. Null terminator was the issue.
I will close this issue if there are no more problems.
Ok, sorry for bumping. Encountered an issue. Didn't test properly in the beginning.
The font initialization is called again when going back to main menu from a session, and if any plugin loads a font, it crashes.
To reproduce:
OnLoad
;This should now be fixed as of 734e9f84f3c9a598db8ab3736fdc617c751d6994, haven't gotten around to fixing this until now.
Describe The Problem
So for correct localization support in my plugins, I need to load arbitrary fonts. Working with fonts in ImGui is pain by itself, so no wonder I am having issues.
ImGui recommends to load fonts during initialization. I tried doing so in
IPlugin.OnLoad()
but that throws an exception on game launch.Ok, then I tried to do that during runtime inside
IPlugin.OnImGuiFreeRender()
(once). With exact same code as above it goes without crashes but all ImGui windows stop being rendered at all.loaded
printsTrue
thou. If I removeImGui.SetCurrentFont(font)
, the result is the same. I callImGui.PushFont(font)
andImGui.PopFont()
insideIPlugin.OnImGuiFreeRender()
. If I don't do that, the result is the same.Is there something that I am missing?
PC Specs
Show/Hide
- `Motherboard`**: Asus ROG Strix Z390-H Gaming** - `CPU`**: Intel Core i9-9900k** - `RAM`**: 32 GB 3200MHz** - `GPU`**: Nvidia GeForce RTX 3090 Ti** - `System and Game SSD`**: Samsung SSD 970 EVO Plus NVMe M.2 1TB** - `Plugin Repo SSD`**: Western Digital Blue SATA M.2 2280 2TB**Environment
Show/Hide
- `OS`: **Window 10 Enterprise Version 22H2 (OS Build 19045.3996)** - `Monster Hunter: World`: **v15.21.00** - `SharpPluginLoader`: **v0.0.4.1** - `Nvidia Drivers`**: v551.76**Game Display Settings
Show/Hide
- `DirectX 12 API`**: On** - `Screen Mode`**: Borderless Window** - `Resolution Settings`**: 2880x1620** (Supersampled down to 1920x1080) - `Aspect Ratio`**: Wide (16:9)** - `Nvidia DLSS`**: Off** - `FidelityFX CAS + Upscaling`**: Off** - `Frame Rate`**: No Limit** - `V-Sync`**: On**Game Advanced Graphics Settings
Show/Hide
- `Image Quality`**: High** - `Texture Quality`**: High Resolution Texture Pack** - `Ambient Occlusion`**: High** - `Volume Rendering Quality`**: Highest** - `Shadow Quality`**: High** - `Capsule AO`**: On** - `Contact Shadows`**: On** - `Anti-Aliasing`**: Off** - `LOD Bias`**: High** - `Max LOD Level`**: No Limit** - `Foliage Sway`**: On** - `Subsurface Scattering`**: On** - `Screen Space Reflection`**: On** - `Anisotropic Filtering`**: Highest** - `Water Reflection`**: On** - `Snow Quality`**: High** - `SH Diffuse Quality`**: High** - `Dynamic Range`**: 64-bit** - `Motion Blur`**: On** - `DOF (Depth of Field)`**: On** - `Vignette Effects`**: Normal** - `Z-Prepass`**: On**Mods and External Tools:
Show/Hide
- [RTSS Overlay v7.3.4](https://www.guru3d.com/page/rivatuner-rtss-homepage/) - [Reshade 6.0.0](https://reshade.me/) with SMAA.fx - [Stracker's Loader v3.0.1](https://www.nexusmods.com/monsterhunterworld/mods/1982) - [Performance Booster and Plugin Extender v1.3](https://www.nexusmods.com/monsterhunterworld/mods/3473) - [HunterPie v2.10.0.134](https://www.nexusmods.com/monsterhunterrise/mods/181) - [HelloWorld (an overlay that shows lots and lots of stuff) v10.4](https://www.nexusmods.com/monsterhunterworld/mods/142) - [Bow Spread Pattern Fix v1.0](https://www.nexusmods.com/monsterhunterworld/mods/6914) - [Tic Rate Fix v1.0](https://www.nexusmods.com/monsterhunterworld/mods/3474) - [Guiding Lands Gathering Indicator v1.12.11.01](https://www.nexusmods.com/monsterhunterworld/mods/1986) - [Gems Sorted by Name (Alphabetized Gems) v1.6.1](https://www.nexusmods.com/monsterhunterworld/mods/2364) - [Reshade Hook v1.0](https://www.nexusmods.com/monsterhunterworld/mods/7015) - [Endemic Quality (Iceborne Edition) v1.4](https://www.nexusmods.com/monsterhunterworld/mods/2137) - [Remove censor bad word banned word filter v1.0](https://www.nexusmods.com/monsterhunterworld/mods/6956) - [Monster Weakness Icon Indicator for Iceborne (Hi-Res) vFinal](https://www.nexusmods.com/monsterhunterworld/mods/1938) - [Cuter Handler Face Model (Post-Iceborne) aka Make Handler Cuter Again v3.0](https://www.nexusmods.com/monsterhunterworld/mods/1914)Additional Context
Tea Overlay Repo Better Matchmaking Repo