mirror of
https://github.com/kevinporetti/UnrealImGui.git
synced 2025-01-18 08:20:32 +00:00
Improved plugin to initialize correctly even when started during earlier loading phases:
- Split tick and textures initializations. - Supporting deferred tick initialization, if Slate application is not ready during module startup. - Textures are lazy-loaded before the first use. - ImGui Context Manager initializes font atlas to make sure that ImGui resources are always initialized.
This commit is contained in:
parent
ec5bc3e738
commit
f57a73b91b
@ -72,6 +72,10 @@ namespace
|
|||||||
|
|
||||||
FImGuiContextManager::FImGuiContextManager()
|
FImGuiContextManager::FImGuiContextManager()
|
||||||
{
|
{
|
||||||
|
unsigned char* Pixels;
|
||||||
|
int Width, Height, Bpp;
|
||||||
|
FontAtlas.GetTexDataAsRGBA32(&Pixels, &Width, &Height, &Bpp);
|
||||||
|
|
||||||
FWorldDelegates::OnWorldTickStart.AddRaw(this, &FImGuiContextManager::OnWorldTickStart);
|
FWorldDelegates::OnWorldTickStart.AddRaw(this, &FImGuiContextManager::OnWorldTickStart);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,8 @@
|
|||||||
#include "ImGuiInteroperability.h"
|
#include "ImGuiInteroperability.h"
|
||||||
#include "Utilities/WorldContextIndex.h"
|
#include "Utilities/WorldContextIndex.h"
|
||||||
|
|
||||||
|
#include <ModuleManager.h>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
|
|
||||||
@ -15,15 +17,18 @@ FImGuiModuleManager::FImGuiModuleManager()
|
|||||||
// Typically we will use viewport created events to add widget to new game viewports.
|
// Typically we will use viewport created events to add widget to new game viewports.
|
||||||
ViewportCreatedHandle = UGameViewportClient::OnViewportCreated().AddRaw(this, &FImGuiModuleManager::OnViewportCreated);
|
ViewportCreatedHandle = UGameViewportClient::OnViewportCreated().AddRaw(this, &FImGuiModuleManager::OnViewportCreated);
|
||||||
|
|
||||||
// Initialize resources and start ticking. Depending on loading phase, this may fail if Slate is not yet ready.
|
// Try to register tick delegate (it may fail if Slate application isn't yet ready).
|
||||||
Initialize();
|
RegisterTick();
|
||||||
|
|
||||||
|
// If we failed to register, create an initializer that will do it later.
|
||||||
|
if (!IsTickRegistered())
|
||||||
|
{
|
||||||
|
CreateTickInitializer();
|
||||||
|
}
|
||||||
|
|
||||||
// We need to add widgets to active game viewports as they won't generate on-created events. This is especially
|
// We need to add widgets to active game viewports as they won't generate on-created events. This is especially
|
||||||
// important during hot-reloading.
|
// important during hot-reloading.
|
||||||
if (bInitialized)
|
AddWidgetsToActiveViewports();
|
||||||
{
|
|
||||||
AddWidgetToAllViewports();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FImGuiModuleManager::~FImGuiModuleManager()
|
FImGuiModuleManager::~FImGuiModuleManager()
|
||||||
@ -45,56 +50,39 @@ FImGuiModuleManager::~FImGuiModuleManager()
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deactivate this manager.
|
// Deactivate this manager.
|
||||||
Uninitialize();
|
ReleaseTickInitializer();
|
||||||
}
|
UnregisterTick();
|
||||||
|
|
||||||
void FImGuiModuleManager::Initialize()
|
|
||||||
{
|
|
||||||
// We rely on Slate, so we can only continue if it is already initialized.
|
|
||||||
if (!bInitialized && FSlateApplication::IsInitialized())
|
|
||||||
{
|
|
||||||
bInitialized = true;
|
|
||||||
LoadTextures();
|
|
||||||
RegisterTick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void FImGuiModuleManager::Uninitialize()
|
|
||||||
{
|
|
||||||
if (bInitialized)
|
|
||||||
{
|
|
||||||
bInitialized = false;
|
|
||||||
UnregisterTick();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FImGuiModuleManager::LoadTextures()
|
void FImGuiModuleManager::LoadTextures()
|
||||||
{
|
{
|
||||||
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can create textures."));
|
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can create textures."));
|
||||||
|
|
||||||
// Create an empty texture at index 0. We will use it for ImGui outputs with null texture id.
|
if (!bTexturesLoaded)
|
||||||
TextureManager.CreatePlainTexture(FName{ "ImGuiModule_Plain" }, 2, 2, FColor::White);
|
{
|
||||||
|
bTexturesLoaded = true;
|
||||||
|
|
||||||
// Create a font atlas texture.
|
// Create an empty texture at index 0. We will use it for ImGui outputs with null texture id.
|
||||||
ImFontAtlas& Fonts = ContextManager.GetFontAtlas();
|
TextureManager.CreatePlainTexture(FName{ "ImGuiModule_Plain" }, 2, 2, FColor::White);
|
||||||
|
|
||||||
unsigned char* Pixels;
|
// Create a font atlas texture.
|
||||||
int Width, Height, Bpp;
|
ImFontAtlas& Fonts = ContextManager.GetFontAtlas();
|
||||||
Fonts.GetTexDataAsRGBA32(&Pixels, &Width, &Height, &Bpp);
|
|
||||||
|
|
||||||
TextureIndex FontsTexureIndex = TextureManager.CreateTexture(FName{ "ImGuiModule_FontAtlas" }, Width, Height, Bpp, Pixels, false);
|
unsigned char* Pixels;
|
||||||
|
int Width, Height, Bpp;
|
||||||
|
Fonts.GetTexDataAsRGBA32(&Pixels, &Width, &Height, &Bpp);
|
||||||
|
|
||||||
// Set font texture index in ImGui.
|
TextureIndex FontsTexureIndex = TextureManager.CreateTexture(FName{ "ImGuiModule_FontAtlas" }, Width, Height, Bpp, Pixels, false);
|
||||||
Fonts.TexID = ImGuiInterops::ToImTextureID(FontsTexureIndex);
|
|
||||||
|
// Set font texture index in ImGui.
|
||||||
|
Fonts.TexID = ImGuiInterops::ToImTextureID(FontsTexureIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FImGuiModuleManager::RegisterTick()
|
void FImGuiModuleManager::RegisterTick()
|
||||||
{
|
{
|
||||||
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can register tick listener."));
|
// Slate Post-Tick is a good moment to end and advance ImGui frame as it minimises a tearing.
|
||||||
|
if (!TickDelegateHandle.IsValid() && FSlateApplication::IsInitialized())
|
||||||
// We will tick on Slate Post-Tick events. They are quite convenient as they happen at the very end of the frame,
|
|
||||||
// what helps to minimise tearing.
|
|
||||||
if (!TickDelegateHandle.IsValid())
|
|
||||||
{
|
{
|
||||||
TickDelegateHandle = FSlateApplication::Get().OnPostTick().AddRaw(this, &FImGuiModuleManager::Tick);
|
TickDelegateHandle = FSlateApplication::Get().OnPostTick().AddRaw(this, &FImGuiModuleManager::Tick);
|
||||||
}
|
}
|
||||||
@ -112,6 +100,36 @@ void FImGuiModuleManager::UnregisterTick()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FImGuiModuleManager::CreateTickInitializer()
|
||||||
|
{
|
||||||
|
if (!TickInitializerHandle.IsValid())
|
||||||
|
{
|
||||||
|
// Try to register tick delegate until we finally succeed.
|
||||||
|
|
||||||
|
TickInitializerHandle = FModuleManager::Get().OnModulesChanged().AddLambda([this](FName Name, EModuleChangeReason Reason)
|
||||||
|
{
|
||||||
|
if (Reason == EModuleChangeReason::ModuleLoaded)
|
||||||
|
{
|
||||||
|
RegisterTick();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsTickRegistered())
|
||||||
|
{
|
||||||
|
ReleaseTickInitializer();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void FImGuiModuleManager::ReleaseTickInitializer()
|
||||||
|
{
|
||||||
|
if (TickInitializerHandle.IsValid())
|
||||||
|
{
|
||||||
|
FModuleManager::Get().OnModulesChanged().Remove(TickInitializerHandle);
|
||||||
|
TickInitializerHandle.Reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool FImGuiModuleManager::IsInUpdateThread()
|
bool FImGuiModuleManager::IsInUpdateThread()
|
||||||
{
|
{
|
||||||
// We can get ticks from the Game thread and Slate loading thread. In both cases IsInGameThread() is true, so we
|
// We can get ticks from the Game thread and Slate loading thread. In both cases IsInGameThread() is true, so we
|
||||||
@ -135,9 +153,6 @@ void FImGuiModuleManager::OnViewportCreated()
|
|||||||
{
|
{
|
||||||
checkf(FSlateApplication::IsInitialized(), TEXT("We expect Slate to be initialized when game viewport is created."));
|
checkf(FSlateApplication::IsInitialized(), TEXT("We expect Slate to be initialized when game viewport is created."));
|
||||||
|
|
||||||
// Make sure that all resources are initialized to handle configurations where this module is loaded before Slate.
|
|
||||||
Initialize();
|
|
||||||
|
|
||||||
// Create widget to viewport responsible for this event.
|
// Create widget to viewport responsible for this event.
|
||||||
AddWidgetToViewport(GEngine->GameViewport);
|
AddWidgetToViewport(GEngine->GameViewport);
|
||||||
}
|
}
|
||||||
@ -147,13 +162,19 @@ void FImGuiModuleManager::AddWidgetToViewport(UGameViewportClient* GameViewport)
|
|||||||
checkf(GameViewport, TEXT("Null game viewport."));
|
checkf(GameViewport, TEXT("Null game viewport."));
|
||||||
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can add widget to game viewports."));
|
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can add widget to game viewports."));
|
||||||
|
|
||||||
// This makes sure that context for this world is created.
|
// Make sure that we have a context for this viewport's world and get its index.
|
||||||
auto& Proxy = ContextManager.GetWorldContextProxy(*GameViewport->GetWorld());
|
int32 ContextIndex;
|
||||||
|
auto& Proxy = ContextManager.GetWorldContextProxy(*GameViewport->GetWorld(), ContextIndex);
|
||||||
|
|
||||||
|
// Make sure that textures are loaded before the first Slate widget is created.
|
||||||
|
LoadTextures();
|
||||||
|
|
||||||
|
// Create and initialize the widget.
|
||||||
TSharedPtr<SImGuiWidget> SharedWidget;
|
TSharedPtr<SImGuiWidget> SharedWidget;
|
||||||
SAssignNew(SharedWidget, SImGuiWidget).ModuleManager(this).GameViewport(GameViewport)
|
SAssignNew(SharedWidget, SImGuiWidget).ModuleManager(this).GameViewport(GameViewport).ContextIndex(ContextIndex);
|
||||||
.ContextIndex(Utilities::GetWorldContextIndex(GameViewport));
|
|
||||||
|
|
||||||
|
// We transfer widget ownerships to viewports but we keep weak references in case we need to manually detach active
|
||||||
|
// widgets during module shutdown (important during hot-reloading).
|
||||||
if (TWeakPtr<SImGuiWidget>* Slot = Widgets.FindByPredicate([](auto& Widget) { return !Widget.IsValid(); }))
|
if (TWeakPtr<SImGuiWidget>* Slot = Widgets.FindByPredicate([](auto& Widget) { return !Widget.IsValid(); }))
|
||||||
{
|
{
|
||||||
*Slot = SharedWidget;
|
*Slot = SharedWidget;
|
||||||
@ -164,11 +185,9 @@ void FImGuiModuleManager::AddWidgetToViewport(UGameViewportClient* GameViewport)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void FImGuiModuleManager::AddWidgetToAllViewports()
|
void FImGuiModuleManager::AddWidgetsToActiveViewports()
|
||||||
{
|
{
|
||||||
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can add widget to game viewports."));
|
if (FSlateApplication::IsInitialized() && GEngine)
|
||||||
|
|
||||||
if (GEngine)
|
|
||||||
{
|
{
|
||||||
// Loop as long as we have a valid viewport or until we detect a cycle.
|
// Loop as long as we have a valid viewport or until we detect a cycle.
|
||||||
UGameViewportClient* GameViewport = GEngine->GameViewport;
|
UGameViewportClient* GameViewport = GEngine->GameViewport;
|
||||||
|
@ -35,14 +35,15 @@ private:
|
|||||||
FImGuiModuleManager(FImGuiModuleManager&&) = delete;
|
FImGuiModuleManager(FImGuiModuleManager&&) = delete;
|
||||||
FImGuiModuleManager& operator=(FImGuiModuleManager&&) = delete;
|
FImGuiModuleManager& operator=(FImGuiModuleManager&&) = delete;
|
||||||
|
|
||||||
void Initialize();
|
|
||||||
void Uninitialize();
|
|
||||||
|
|
||||||
void LoadTextures();
|
void LoadTextures();
|
||||||
|
|
||||||
|
bool IsTickRegistered() { return TickDelegateHandle.IsValid(); }
|
||||||
void RegisterTick();
|
void RegisterTick();
|
||||||
void UnregisterTick();
|
void UnregisterTick();
|
||||||
|
|
||||||
|
void CreateTickInitializer();
|
||||||
|
void ReleaseTickInitializer();
|
||||||
|
|
||||||
bool IsInUpdateThread();
|
bool IsInUpdateThread();
|
||||||
|
|
||||||
void Tick(float DeltaSeconds);
|
void Tick(float DeltaSeconds);
|
||||||
@ -50,7 +51,7 @@ private:
|
|||||||
void OnViewportCreated();
|
void OnViewportCreated();
|
||||||
|
|
||||||
void AddWidgetToViewport(UGameViewportClient* GameViewport);
|
void AddWidgetToViewport(UGameViewportClient* GameViewport);
|
||||||
void AddWidgetToAllViewports();
|
void AddWidgetsToActiveViewports();
|
||||||
|
|
||||||
// Event that we call after ImGui is updated.
|
// Event that we call after ImGui is updated.
|
||||||
FSimpleMulticastDelegate PostImGuiUpdateEvent;
|
FSimpleMulticastDelegate PostImGuiUpdateEvent;
|
||||||
@ -64,8 +65,9 @@ private:
|
|||||||
// Slate widgets that we created.
|
// Slate widgets that we created.
|
||||||
TArray<TWeakPtr<SImGuiWidget>> Widgets;
|
TArray<TWeakPtr<SImGuiWidget>> Widgets;
|
||||||
|
|
||||||
|
FDelegateHandle TickInitializerHandle;
|
||||||
FDelegateHandle TickDelegateHandle;
|
FDelegateHandle TickDelegateHandle;
|
||||||
FDelegateHandle ViewportCreatedHandle;
|
FDelegateHandle ViewportCreatedHandle;
|
||||||
|
|
||||||
bool bInitialized = false;
|
bool bTexturesLoaded = false;
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user