UnrealImGui/Source/ImGui/Private/ImGuiModuleManager.cpp
Sebastian d9220ad536 Fixed bad deallocation in Texture Manager and interface to create textures from raw data:
- Fixed rather embarrassing overlook in deallocation of data array passed from to CreateTexture from other function.
- Changed CreateTexture to use cleanup function passed with data (optional and only needed if data need to be released). Keeping data allocation and deallocation code together should make bugs like this easier to spot and avoid. Additionally this allows for more generic usage.
2018-08-14 20:28:25 +01:00

206 lines
5.8 KiB
C++

// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#include "ImGuiPrivatePCH.h"
#include "ImGuiModuleManager.h"
#include "ImGuiInteroperability.h"
#include "Utilities/WorldContextIndex.h"
#include <ModuleManager.h>
#include <imgui.h>
FImGuiModuleManager::FImGuiModuleManager()
{
// Typically we will use viewport created events to add widget to new game viewports.
ViewportCreatedHandle = UGameViewportClient::OnViewportCreated().AddRaw(this, &FImGuiModuleManager::OnViewportCreated);
// Try to register tick delegate (it may fail if Slate application isn't yet ready).
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
// important during hot-reloading.
AddWidgetsToActiveViewports();
}
FImGuiModuleManager::~FImGuiModuleManager()
{
// We are no longer interested with adding widgets to viewports.
if (ViewportCreatedHandle.IsValid())
{
UGameViewportClient::OnViewportCreated().Remove(ViewportCreatedHandle);
ViewportCreatedHandle.Reset();
}
// Remove still active widgets (important during hot-reloading).
for (auto& Widget : Widgets)
{
if (Widget.IsValid())
{
Widget.Pin()->Detach();
}
}
// Deactivate this manager.
ReleaseTickInitializer();
UnregisterTick();
}
void FImGuiModuleManager::LoadTextures()
{
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.
TextureManager.CreatePlainTexture(FName{ "ImGuiModule_Plain" }, 2, 2, FColor::White);
// Create a font atlas texture.
ImFontAtlas& Fonts = ContextManager.GetFontAtlas();
unsigned char* Pixels;
int Width, Height, Bpp;
Fonts.GetTexDataAsRGBA32(&Pixels, &Width, &Height, &Bpp);
TextureIndex FontsTexureIndex = TextureManager.CreateTexture(FName{ "ImGuiModule_FontAtlas" }, Width, Height, Bpp, Pixels);
// Set font texture index in ImGui.
Fonts.TexID = ImGuiInterops::ToImTextureID(FontsTexureIndex);
}
}
void FImGuiModuleManager::RegisterTick()
{
// Slate Post-Tick is a good moment to end and advance ImGui frame as it minimises a tearing.
if (!TickDelegateHandle.IsValid() && FSlateApplication::IsInitialized())
{
TickDelegateHandle = FSlateApplication::Get().OnPostTick().AddRaw(this, &FImGuiModuleManager::Tick);
}
}
void FImGuiModuleManager::UnregisterTick()
{
if (TickDelegateHandle.IsValid())
{
if (FSlateApplication::IsInitialized())
{
FSlateApplication::Get().OnPostTick().Remove(TickDelegateHandle);
}
TickDelegateHandle.Reset();
}
}
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()
{
// We can get ticks from the Game thread and Slate loading thread. In both cases IsInGameThread() is true, so we
// need to make additional test to filter out loading thread.
return IsInGameThread() && !IsInSlateThread();
}
void FImGuiModuleManager::Tick(float DeltaSeconds)
{
if (IsInUpdateThread())
{
// Update context manager to advance all ImGui contexts to the next frame.
ContextManager.Tick(DeltaSeconds);
// Inform that we finished updating ImGui, so other subsystems can react.
PostImGuiUpdateEvent.Broadcast();
}
}
void FImGuiModuleManager::OnViewportCreated()
{
checkf(FSlateApplication::IsInitialized(), TEXT("We expect Slate to be initialized when game viewport is created."));
// Create widget to viewport responsible for this event.
AddWidgetToViewport(GEngine->GameViewport);
}
void FImGuiModuleManager::AddWidgetToViewport(UGameViewportClient* GameViewport)
{
checkf(GameViewport, TEXT("Null game viewport."));
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can add widget to game viewports."));
// Make sure that we have a context for this viewport's world and get its index.
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;
SAssignNew(SharedWidget, SImGuiWidget).ModuleManager(this).GameViewport(GameViewport).ContextIndex(ContextIndex);
// 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(); }))
{
*Slot = SharedWidget;
}
else
{
Widgets.Emplace(SharedWidget);
}
}
void FImGuiModuleManager::AddWidgetsToActiveViewports()
{
if (FSlateApplication::IsInitialized() && GEngine)
{
// Loop as long as we have a valid viewport or until we detect a cycle.
UGameViewportClient* GameViewport = GEngine->GameViewport;
while (GameViewport)
{
AddWidgetToViewport(GameViewport);
GameViewport = GEngine->GetNextPIEViewport(GameViewport);
if (GameViewport == GEngine->GameViewport)
{
break;
}
}
}
}