mirror of
https://github.com/kevinporetti/UnrealImGui.git
synced 2025-01-18 08:20:32 +00:00
Reduced lag between producing ImGui output and rendering it:
- ImGui Context Proxy Tick function has been split for Tick and Draw. Both functions may now be called separately from different places but they will be processed only once per frame. This opens possibility to define own mechanisms to trigger draw events and to independently update contexts. - Multi-context draw event delegate is passed to ImGui Context Proxy during construction rather than as an argument of Tick function. - To minimise lag between ending ImGui frame and rendering its output, ImGui Widget updates its own context just before painting. - Contexts without widget (editor, dedicated server) are updated by manager during post-tick event. - Instead of setting global CurrentContextIndex by ImGui Context Manager, information about context is passed to ImGui Demo as part of a closure. - Fixed bug in ImGui Demo where inactive contexts were triggering multi-PIE warning.
This commit is contained in:
parent
f18e1f0b68
commit
052ae0a201
@ -12,11 +12,6 @@
|
||||
#include <imgui.h>
|
||||
|
||||
|
||||
// Index of the currently updated context. Only valid during context manager tick.
|
||||
// TODO: Move to public interface (but probably as a current world/viewport etc.)
|
||||
int32 CurrentContextIndex = Utilities::INVALID_CONTEXT_INDEX;
|
||||
|
||||
|
||||
namespace
|
||||
{
|
||||
#if WITH_EDITOR
|
||||
@ -85,13 +80,10 @@ void FImGuiContextManager::Tick(float DeltaSeconds)
|
||||
|
||||
for (auto& Pair : Contexts)
|
||||
{
|
||||
auto ContextIndexSave = ScopeGuards::MakeStateSaver(CurrentContextIndex);
|
||||
CurrentContextIndex = Pair.Key;
|
||||
auto& ContextData = Pair.Value;
|
||||
if (ContextData.CanTick())
|
||||
{
|
||||
ContextData.ContextProxy.SetAsCurrent();
|
||||
ContextData.ContextProxy.Tick(DeltaSeconds, &DrawMultiContextEvent);
|
||||
ContextData.ContextProxy.Tick(DeltaSeconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -113,7 +105,7 @@ FImGuiContextManager::FContextData& FImGuiContextManager::GetEditorContextData()
|
||||
|
||||
if (UNLIKELY(!Data))
|
||||
{
|
||||
Data = &Contexts.Emplace(Utilities::EDITOR_CONTEXT_INDEX, FContextData{ GetEditorContextName(), ImGuiDemo });
|
||||
Data = &Contexts.Emplace(Utilities::EDITOR_CONTEXT_INDEX, FContextData{ GetEditorContextName(), Utilities::EDITOR_CONTEXT_INDEX, DrawMultiContextEvent, ImGuiDemo, -1 });
|
||||
}
|
||||
|
||||
return *Data;
|
||||
@ -127,7 +119,7 @@ FImGuiContextManager::FContextData& FImGuiContextManager::GetStandaloneWorldCont
|
||||
|
||||
if (UNLIKELY(!Data))
|
||||
{
|
||||
Data = &Contexts.Emplace(Utilities::STANDALONE_GAME_CONTEXT_INDEX, FContextData{ GetWorldContextName(), ImGuiDemo });
|
||||
Data = &Contexts.Emplace(Utilities::STANDALONE_GAME_CONTEXT_INDEX, FContextData{ GetWorldContextName(), Utilities::STANDALONE_GAME_CONTEXT_INDEX, DrawMultiContextEvent, ImGuiDemo });
|
||||
}
|
||||
|
||||
return *Data;
|
||||
@ -167,7 +159,7 @@ FImGuiContextManager::FContextData& FImGuiContextManager::GetWorldContextData(co
|
||||
#if WITH_EDITOR
|
||||
if (UNLIKELY(!Data))
|
||||
{
|
||||
Data = &Contexts.Emplace(Index, FContextData{ GetWorldContextName(World), ImGuiDemo, WorldContext->PIEInstance });
|
||||
Data = &Contexts.Emplace(Index, FContextData{ GetWorldContextName(World), Index, DrawMultiContextEvent, ImGuiDemo, WorldContext->PIEInstance });
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -177,7 +169,7 @@ FImGuiContextManager::FContextData& FImGuiContextManager::GetWorldContextData(co
|
||||
#else
|
||||
if (UNLIKELY(!Data))
|
||||
{
|
||||
Data = &Contexts.Emplace(Index, FContextData{ GetWorldContextName(World), ImGuiDemo });
|
||||
Data = &Contexts.Emplace(Index, FContextData{ GetWorldContextName(World), Index, DrawMultiContextEvent, ImGuiDemo });
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@ -56,11 +56,11 @@ private:
|
||||
|
||||
struct FContextData
|
||||
{
|
||||
FContextData(const FString& ContextName, FImGuiDemo& Demo, int32 InPIEInstance = -1)
|
||||
FContextData(const FString& ContextName, int32 ContextIndex, FSimpleMulticastDelegate& SharedDrawEvent, FImGuiDemo& Demo, int32 InPIEInstance = -1)
|
||||
: PIEInstance(InPIEInstance)
|
||||
, ContextProxy(ContextName)
|
||||
, ContextProxy(ContextName, &SharedDrawEvent)
|
||||
{
|
||||
ContextProxy.OnDraw().AddRaw(&Demo, &FImGuiDemo::DrawControls);
|
||||
ContextProxy.OnDraw().AddLambda([&Demo, ContextIndex]() { Demo.DrawControls(ContextIndex); });
|
||||
}
|
||||
|
||||
FORCEINLINE bool CanTick() const { return PIEInstance < 0 || GEngine->GetWorldContextFromPIEInstance(PIEInstance); }
|
||||
@ -73,10 +73,10 @@ private:
|
||||
|
||||
struct FContextData
|
||||
{
|
||||
FContextData(const FString& ContextName, FImGuiDemo& Demo)
|
||||
: ContextProxy(ContextName)
|
||||
FContextData(const FString& ContextName, int32 ContextIndex, FSimpleMulticastDelegate& SharedDrawEvent, FImGuiDemo& Demo)
|
||||
: ContextProxy(ContextName, &SharedDrawEvent)
|
||||
{
|
||||
ContextProxy.OnDraw().AddRaw(&Demo, &FImGuiDemo::DrawControls);
|
||||
ContextProxy.OnDraw().AddLambda([&Demo, ContextIndex]() { Demo.DrawControls(ContextIndex); });
|
||||
}
|
||||
|
||||
FORCEINLINE bool CanTick() const { return true; }
|
||||
|
@ -39,12 +39,13 @@ namespace
|
||||
}
|
||||
}
|
||||
|
||||
FImGuiContextProxy::FImGuiContextProxy(const FString& InName)
|
||||
FImGuiContextProxy::FImGuiContextProxy(const FString& InName, FSimpleMulticastDelegate* InSharedDrawEvent)
|
||||
: Name(InName)
|
||||
, SharedDrawEvent(InSharedDrawEvent)
|
||||
, IniFilename(TCHAR_TO_ANSI(*GetIniFile(InName)))
|
||||
{
|
||||
// Create context.
|
||||
Context = ImGui::CreateContext();
|
||||
Context = TUniquePtr<ImGuiContext>(ImGui::CreateContext());
|
||||
|
||||
// Set this context in ImGui for initialization (any allocations will be tracked in this context).
|
||||
SetAsCurrent();
|
||||
@ -80,35 +81,6 @@ FImGuiContextProxy::FImGuiContextProxy(const FString& InName)
|
||||
BeginFrame();
|
||||
}
|
||||
|
||||
FImGuiContextProxy::FImGuiContextProxy(FImGuiContextProxy&& Other)
|
||||
: Context(std::move(Other.Context))
|
||||
, bHasActiveItem(Other.bHasActiveItem)
|
||||
, DrawEvent(std::move(Other.DrawEvent))
|
||||
, InputState(std::move(Other.InputState))
|
||||
, DrawLists(std::move(Other.DrawLists))
|
||||
, Name(std::move(Other.Name))
|
||||
, IniFilename(std::move(Other.IniFilename))
|
||||
{
|
||||
Other.Context = nullptr;
|
||||
}
|
||||
|
||||
FImGuiContextProxy& FImGuiContextProxy::operator=(FImGuiContextProxy&& Other)
|
||||
{
|
||||
// Swapping context so it can be destroyed with the other object.
|
||||
using std::swap;
|
||||
swap(Context, Other.Context);
|
||||
|
||||
// Just moving remaining data that doesn't affect cleanup.
|
||||
bHasActiveItem = Other.bHasActiveItem;
|
||||
DrawEvent = std::move(Other.DrawEvent);
|
||||
InputState = std::move(Other.InputState);
|
||||
DrawLists = std::move(Other.DrawLists);
|
||||
Name = std::move(Other.Name);
|
||||
IniFilename = std::move(Other.IniFilename);
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
FImGuiContextProxy::~FImGuiContextProxy()
|
||||
{
|
||||
if (Context)
|
||||
@ -118,17 +90,24 @@ FImGuiContextProxy::~FImGuiContextProxy()
|
||||
|
||||
// Save context data and destroy.
|
||||
ImGuiImplementation::SaveCurrentContextIniSettings(IniFilename.c_str());
|
||||
ImGui::DestroyContext(Context);
|
||||
ImGui::DestroyContext(Context.Release());
|
||||
|
||||
// Set default context in ImGui to keep global context pointer valid.
|
||||
ImGui::SetCurrentContext(&ImGuiImplementation::GetDefaultContext());
|
||||
}
|
||||
}
|
||||
|
||||
void FImGuiContextProxy::Tick(float DeltaSeconds, FSimpleMulticastDelegate* SharedDrawEvent)
|
||||
void FImGuiContextProxy::Draw()
|
||||
{
|
||||
// Comparing to LastTickFrameNumber rather than GFrameNumber to make sure that this is driven by our own update cycle.
|
||||
if (LastDrawFrameNumber < LastTickFrameNumber)
|
||||
{
|
||||
LastDrawFrameNumber = LastTickFrameNumber;
|
||||
|
||||
if (bIsFrameStarted)
|
||||
{
|
||||
SetAsCurrent();
|
||||
|
||||
// Broadcast draw event to allow listeners to draw their controls to this context.
|
||||
if (DrawEvent.IsBound())
|
||||
{
|
||||
@ -138,6 +117,23 @@ void FImGuiContextProxy::Tick(float DeltaSeconds, FSimpleMulticastDelegate* Shar
|
||||
{
|
||||
SharedDrawEvent->Broadcast();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FImGuiContextProxy::Tick(float DeltaSeconds)
|
||||
{
|
||||
// Making sure that we tick only once per frame.
|
||||
if (LastTickFrameNumber < GFrameNumber)
|
||||
{
|
||||
LastTickFrameNumber = GFrameNumber;
|
||||
|
||||
SetAsCurrent();
|
||||
|
||||
if (bIsFrameStarted)
|
||||
{
|
||||
// Make sure that draw events are called before the end of the frame.
|
||||
Draw();
|
||||
|
||||
// Ending frame will produce render output that we capture and store for later use. This also puts context to
|
||||
// state in which it does not allow to draw controls, so we want to immediately start a new frame.
|
||||
@ -152,6 +148,7 @@ void FImGuiContextProxy::Tick(float DeltaSeconds, FSimpleMulticastDelegate* Shar
|
||||
// Begin a new frame and set the context back to a state in which it allows to draw controls.
|
||||
BeginFrame(DeltaSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
void FImGuiContextProxy::BeginFrame(float DeltaTime)
|
||||
{
|
||||
|
@ -19,14 +19,14 @@ class FImGuiContextProxy
|
||||
{
|
||||
public:
|
||||
|
||||
FImGuiContextProxy(const FString& Name);
|
||||
FImGuiContextProxy(const FString& Name, FSimpleMulticastDelegate* InSharedDrawEvent);
|
||||
~FImGuiContextProxy();
|
||||
|
||||
FImGuiContextProxy(const FImGuiContextProxy&) = delete;
|
||||
FImGuiContextProxy& operator=(const FImGuiContextProxy&) = delete;
|
||||
|
||||
FImGuiContextProxy(FImGuiContextProxy&& Other);
|
||||
FImGuiContextProxy& operator=(FImGuiContextProxy&& Other);
|
||||
FImGuiContextProxy(FImGuiContextProxy&& Other) = default;
|
||||
FImGuiContextProxy& operator=(FImGuiContextProxy&& Other) = default;
|
||||
|
||||
// Get the name of this context.
|
||||
const FString& GetName() const { return Name; }
|
||||
@ -44,10 +44,10 @@ public:
|
||||
void RemoveInputState(const FImGuiInputState* InputStateToRemove) { if (InputState == InputStateToRemove) InputState = nullptr; }
|
||||
|
||||
// Is this context the current ImGui context.
|
||||
bool IsCurrentContext() const { return ImGui::GetCurrentContext() == Context; }
|
||||
bool IsCurrentContext() const { return ImGui::GetCurrentContext() == Context.Get(); }
|
||||
|
||||
// Set this context as current ImGui context.
|
||||
void SetAsCurrent() { ImGui::SetCurrentContext(Context); }
|
||||
void SetAsCurrent() { ImGui::SetCurrentContext(Context.Get()); }
|
||||
|
||||
bool HasActiveItem() const { return bHasActiveItem; }
|
||||
|
||||
@ -56,9 +56,12 @@ public:
|
||||
// Delegate called right before ending the frame to allows listeners draw their controls.
|
||||
FSimpleMulticastDelegate& OnDraw() { return DrawEvent; }
|
||||
|
||||
// Tick to advance context to the next frame.
|
||||
// @param SharedDrawEvent - Shared draw event provided from outside to be called right after context own event
|
||||
void Tick(float DeltaSeconds, FSimpleMulticastDelegate* SharedDrawEvent = nullptr);
|
||||
// Call draw events to allow listeners draw their widgets. Only one call per frame is processed. If it is not
|
||||
// called manually before, then it will be called from the Tick function.
|
||||
void Draw();
|
||||
|
||||
// Tick to advance context to the next frame. Only one call per frame will be processed.
|
||||
void Tick(float DeltaSeconds);
|
||||
|
||||
private:
|
||||
|
||||
@ -67,13 +70,18 @@ private:
|
||||
|
||||
void UpdateDrawData(ImDrawData* DrawData);
|
||||
|
||||
ImGuiContext* Context = nullptr;
|
||||
TUniquePtr<ImGuiContext> Context;
|
||||
|
||||
bool bHasActiveItem = false;
|
||||
EMouseCursor::Type MouseCursor = EMouseCursor::None;
|
||||
bool bHasActiveItem = false;
|
||||
|
||||
bool bIsFrameStarted = false;
|
||||
FSimpleMulticastDelegate DrawEvent;
|
||||
FSimpleMulticastDelegate* SharedDrawEvent = nullptr;
|
||||
|
||||
uint32 LastTickFrameNumber = 0;
|
||||
uint32 LastDrawFrameNumber = 0;
|
||||
|
||||
const FImGuiInputState* InputState = nullptr;
|
||||
|
||||
TArray<FImGuiDrawList> DrawLists;
|
||||
|
@ -17,13 +17,11 @@ namespace CVars
|
||||
}
|
||||
|
||||
// Demo copied (with minor modifications) from ImGui examples. See https://github.com/ocornut/imgui.
|
||||
void FImGuiDemo::DrawControls()
|
||||
void FImGuiDemo::DrawControls(int32 ContextIndex)
|
||||
{
|
||||
if (CVars::ShowDemo.GetValueOnGameThread() > 0)
|
||||
{
|
||||
// TODO: This should be part of a public interface.
|
||||
extern int32 CurrentContextIndex;
|
||||
const int32 ContextBit = CurrentContextIndex < 0 ? 0 : 1 << CurrentContextIndex;
|
||||
const int32 ContextBit = ContextIndex < 0 ? 0 : 1 << ContextIndex;
|
||||
|
||||
// 1. Show a simple window
|
||||
// Tip: if we don't call ImGui::Begin()/ImGui::End() the widgets appears in a window automatically called "Debug"
|
||||
@ -53,13 +51,28 @@ void FImGuiDemo::DrawControls()
|
||||
// 3. Show the ImGui test window. Most of the sample code is in ImGui::ShowTestWindow()
|
||||
if (ShowDemoWindowMask & ContextBit)
|
||||
{
|
||||
// Display warning about running ImGui examples in multiple contexts.
|
||||
if (ShowDemoWindowMask != ContextBit)
|
||||
// If more than one demo window is opened display warning about running ImGui examples in multiple contexts.
|
||||
|
||||
// For everything, but the first windows in this frame we assume warning.
|
||||
bool bWarning = true;
|
||||
if (GFrameNumber > LastDemoWindowFrameNumber)
|
||||
{
|
||||
// If this is the first window in this frame, then we need to look at the last frame to see whether
|
||||
// there were more than one windows. Higher frame distance automatically means that there were not.
|
||||
bWarning = ((GFrameNumber - LastDemoWindowFrameNumber) == 1) && (DemoWindowCounter > 1);
|
||||
|
||||
LastDemoWindowFrameNumber = GFrameNumber;
|
||||
DemoWindowCounter = 0;
|
||||
}
|
||||
|
||||
DemoWindowCounter++;
|
||||
|
||||
if (bWarning)
|
||||
{
|
||||
ImGui::Spacing();
|
||||
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, { 1.f, 1.f, 0.5f, 1.f });
|
||||
ImGui::TextWrapped("Demo Window is opend in more than one context, some of the ImGui examples may not work correctly.");
|
||||
ImGui::TextWrapped("Demo Window is opened in more than one context, some of the ImGui examples may not work correctly.");
|
||||
ImGui::PopStyleColor();
|
||||
|
||||
if (ImGui::IsItemHovered())
|
||||
@ -70,6 +83,8 @@ void FImGuiDemo::DrawControls()
|
||||
"If you have a problem with an example try to run it in one context only.");
|
||||
}
|
||||
}
|
||||
|
||||
// Draw demo window.
|
||||
ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiSetCond_FirstUseEver);
|
||||
ImGui::ShowDemoWindow();
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ class FImGuiDemo
|
||||
{
|
||||
public:
|
||||
|
||||
void DrawControls();
|
||||
void DrawControls(int32 ContextIndex);
|
||||
|
||||
private:
|
||||
|
||||
@ -18,4 +18,7 @@ private:
|
||||
|
||||
int32 ShowDemoWindowMask = 0;
|
||||
int32 ShowAnotherWindowMask = 0;
|
||||
|
||||
int32 DemoWindowCounter = 0;
|
||||
uint32 LastDemoWindowFrameNumber = 0;
|
||||
};
|
||||
|
@ -433,6 +433,10 @@ int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeo
|
||||
{
|
||||
if (FImGuiContextProxy* ContextProxy = ModuleManager->GetContextManager().GetContextProxy(ContextIndex))
|
||||
{
|
||||
// Manually update ImGui context to minimise lag between creating and rendering ImGui output. This will also
|
||||
// keep frame tearing at minimum because it is executed at the very end of the frame.
|
||||
ContextProxy->Tick(FSlateApplication::Get().GetDeltaTime());
|
||||
|
||||
// Calculate offset that will transform vertex positions to screen space - rounded to avoid half pixel offsets.
|
||||
const FVector2D VertexPositionOffset{ FMath::RoundToFloat(MyClippingRect.Left), FMath::RoundToFloat(MyClippingRect.Top) };
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user