mirror of
				https://github.com/kevinporetti/UnrealImGui.git
				synced 2025-11-03 23:33:16 +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,33 +50,18 @@ FImGuiModuleManager::~FImGuiModuleManager()
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Deactivate this manager.
 | 
						// Deactivate this manager.
 | 
				
			||||||
	Uninitialize();
 | 
						ReleaseTickInitializer();
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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();
 | 
						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."));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if (!bTexturesLoaded)
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							bTexturesLoaded = true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Create an empty texture at index 0. We will use it for ImGui outputs with null texture id.
 | 
							// Create an empty texture at index 0. We will use it for ImGui outputs with null texture id.
 | 
				
			||||||
		TextureManager.CreatePlainTexture(FName{ "ImGuiModule_Plain" }, 2, 2, FColor::White);
 | 
							TextureManager.CreatePlainTexture(FName{ "ImGuiModule_Plain" }, 2, 2, FColor::White);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -86,15 +76,13 @@ void FImGuiModuleManager::LoadTextures()
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
		// Set font texture index in ImGui.
 | 
							// Set font texture index in ImGui.
 | 
				
			||||||
		Fonts.TexID = ImGuiInterops::ToImTextureID(FontsTexureIndex);
 | 
							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