Added support for ImGui context update and rendering:

- Added ImGui Module Manager that that implements module logic and manages other module resources.
- Added Texture Manager to manage texture resources and maps them to index that can be passed to ImGui context.
- Added Context Proxy that represents and manages a single ImGui context.
- Added Slate ImGui Widget to render ImGui output.
This commit is contained in:
Sebastian 2017-03-26 21:32:57 +01:00
parent e74e3475d9
commit 35f2d342a0
17 changed files with 949 additions and 3 deletions

View File

@ -39,6 +39,10 @@ public class ImGui : ModuleRules
PrivateDependencyModuleNames.AddRange(
new string[]
{
"CoreUObject",
"Engine",
"Slate",
"SlateCore"
// ... add private dependencies that you statically link with here ...
}
);

View File

@ -0,0 +1,96 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#include "ImGuiPrivatePCH.h"
#include "ImGuiContextProxy.h"
static constexpr float DEFAULT_CANVAS_WIDTH = 3840.f;
static constexpr float DEFAULT_CANVAS_HEIGHT = 2160.f;
FImGuiContextProxy::FImGuiContextProxy()
{
ImGuiIO& IO = ImGui::GetIO();
// Use pre-defined canvas size.
IO.DisplaySize.x = DEFAULT_CANVAS_WIDTH;
IO.DisplaySize.y = DEFAULT_CANVAS_HEIGHT;
// Load texture atlas.
unsigned char* Pixels;
IO.Fonts->GetTexDataAsRGBA32(&Pixels, nullptr, nullptr);
// Begin frame to complete context initialization (this is to avoid problems with other systems calling to ImGui
// during startup).
BeginFrame();
}
FImGuiContextProxy::~FImGuiContextProxy()
{
ImGui::Shutdown();
}
void FImGuiContextProxy::Tick(float DeltaSeconds)
{
if (bIsFrameStarted)
{
// Broadcast draw event to allow listeners to draw their controls to this context.
if (DrawEvent.IsBound())
{
DrawEvent.Broadcast();
}
// 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.
EndFrame();
}
// 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)
{
if (!bIsFrameStarted)
{
ImGuiIO& IO = ImGui::GetIO();
IO.DeltaTime = DeltaTime;
ImGui::NewFrame();
bIsFrameStarted = true;
}
}
void FImGuiContextProxy::EndFrame()
{
if (bIsFrameStarted)
{
// Prepare draw data (after this call we cannot draw to this context until we start a new frame).
ImGui::Render();
// Update our draw data, so we can use them later during Slate rendering while ImGui is in the middle of the
// next frame.
UpdateDrawData(ImGui::GetDrawData());
bIsFrameStarted = false;
}
}
void FImGuiContextProxy::UpdateDrawData(ImDrawData* DrawData)
{
if (DrawData && DrawData->CmdListsCount > 0)
{
DrawLists.SetNum(DrawData->CmdListsCount, false);
for (int Index = 0; Index < DrawData->CmdListsCount; Index++)
{
DrawLists[Index].TransferDrawData(*DrawData->CmdLists[Index]);
}
}
else
{
// If we are not rendering then this might be a good moment to empty the array.
DrawLists.Empty();
}
}

View File

@ -0,0 +1,47 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
#include "ImGuiDrawData.h"
#include <imgui.h>
// Represents a single ImGui context. All the context updates should be done through this proxy. During update it
// broadcasts draw events to allow listeners draw their controls. After update it stores produced draw data.
// TODO: Add dynamically created contexts, so we can have a better support for multi-PIE.
class FImGuiContextProxy
{
public:
FImGuiContextProxy();
~FImGuiContextProxy();
FImGuiContextProxy(const FImGuiContextProxy&) = delete;
FImGuiContextProxy& operator=(const FImGuiContextProxy&) = delete;
FImGuiContextProxy(FImGuiContextProxy&&) = delete;
FImGuiContextProxy& operator=(FImGuiContextProxy&&) = delete;
// Get draw data from the last frame.
const TArray<FImGuiDrawList>& GetDrawData() const { return DrawLists; }
// 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.
void Tick(float DeltaSeconds);
private:
void BeginFrame(float DeltaTime = 1.f / 60.f);
void EndFrame();
void UpdateDrawData(ImDrawData* DrawData);
TArray<FImGuiDrawList> DrawLists;
FSimpleMulticastDelegate DrawEvent;
bool bIsFrameStarted = false;
};

View File

@ -0,0 +1,39 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#include "ImGuiPrivatePCH.h"
#include "ImGuiDemo.h"
#include "ImGuiModuleManager.h"
// Demo copied from ImGui examples. See https://github.com/ocornut/imgui.
void FImGuiDemo::DrawControls()
{
// 1. Show a simple window
// Tip: if we don't call ImGui::Begin()/ImGui::End() the widgets appears in a window automatically called "Debug"
{
static float f = 0.0f;
ImGui::Text("Hello, world!");
ImGui::SliderFloat("float", &f, 0.0f, 1.0f);
ImGui::ColorEdit3("clear color", (float*)&ClearColor);
if (ImGui::Button("Test Window")) bDemoShowTestWindow = !bDemoShowTestWindow;
if (ImGui::Button("Another Window")) bDemoShowAnotherTestWindow = !bDemoShowAnotherTestWindow;
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
}
// 2. Show another simple window, this time using an explicit Begin/End pair
if (bDemoShowAnotherTestWindow)
{
ImGui::SetNextWindowSize(ImVec2(200, 100), ImGuiSetCond_FirstUseEver);
ImGui::Begin("Another Window", &bDemoShowAnotherTestWindow);
ImGui::Text("Hello");
ImGui::End();
}
// 3. Show the ImGui test window. Most of the sample code is in ImGui::ShowTestWindow()
if (bDemoShowTestWindow)
{
ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiSetCond_FirstUseEver);
ImGui::ShowTestWindow(&bDemoShowTestWindow);
}
}

View File

@ -0,0 +1,22 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
#include <imgui.h>
// Widget drawing ImGui demo.
class FImGuiDemo
{
public:
void DrawControls();
private:
ImVec4 ClearColor = ImColor{ 114, 144, 154 };
bool bShowDemo = false;
bool bDemoShowTestWindow = true;
bool bDemoShowAnotherTestWindow = false;
};

View File

@ -0,0 +1,59 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#include "ImGuiPrivatePCH.h"
#include "ImGuiDrawData.h"
void FImGuiDrawList::CopyVertexData(TArray<FSlateVertex>& OutVertexBuffer, const FVector2D VertexPositionOffset, const FSlateRotatedRect& VertexClippingRect) const
{
// Reset and reserve space in destination buffer.
OutVertexBuffer.SetNumUninitialized(ImGuiVertexBuffer.Size, false);
// Transform and copy vertex data.
for (int Idx = 0; Idx < ImGuiVertexBuffer.Size; Idx++)
{
const ImDrawVert& ImGuiVertex = ImGuiVertexBuffer[Idx];
FSlateVertex& SlateVertex = OutVertexBuffer[Idx];
// Final UV is calculated in shader as XY * ZW, so we need set all components.
SlateVertex.TexCoords[0] = ImGuiVertex.uv.x;
SlateVertex.TexCoords[1] = ImGuiVertex.uv.y;
SlateVertex.TexCoords[2] = SlateVertex.TexCoords[3] = 1.f;
// Copy ImGui position and add offset.
SlateVertex.Position[0] = ImGuiVertex.pos.x + VertexPositionOffset.X;
SlateVertex.Position[1] = ImGuiVertex.pos.y + VertexPositionOffset.Y;
// Set clipping rectangle.
SlateVertex.ClipRect = VertexClippingRect;
// Unpack ImU32 color.
SlateVertex.Color = ImGuiInterops::UnpackImU32Color(ImGuiVertex.col);
}
}
void FImGuiDrawList::CopyIndexData(TArray<SlateIndex>& OutIndexBuffer, const int32 StartIndex, const int32 NumElements) const
{
// Reset buffer.
OutIndexBuffer.SetNumUninitialized(NumElements, false);
// Copy elements (slow copy because of different sizes of ImDrawIdx and SlateIndex and because SlateIndex can
// have different size on different platforms).
for (int i = 0; i < NumElements; i++)
{
OutIndexBuffer[i] = ImGuiIndexBuffer[StartIndex + i];
}
}
void FImGuiDrawList::TransferDrawData(ImDrawList& Src)
{
// Move data from source to this list.
Src.CmdBuffer.swap(ImGuiCommandBuffer);
Src.IdxBuffer.swap(ImGuiIndexBuffer);
Src.VtxBuffer.swap(ImGuiVertexBuffer);
// ImGui seems to clear draw lists in every frame, but since source list can contain pointers to buffers that
// we just swapped, it is better to clear explicitly here.
Src.Clear();
}

View File

@ -0,0 +1,58 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
#include "ImGuiInteroperability.h"
#include <SlateCore.h>
#include <imgui.h>
// ImGui draw command data transformed for Slate.
struct FImGuiDrawCommand
{
uint32 NumElements;
FSlateRect ClippingRect;
TextureIndex TextureId;
};
// Wraps raw ImGui draw list data in utilities that transform them for Slate.
class FImGuiDrawList
{
public:
// Get the number of draw commands in this list.
FORCEINLINE int NumCommands() const { return ImGuiCommandBuffer.Size; }
// Get the draw command by number.
// @param CommandNb - Number of draw command
// @returns Draw command data
FORCEINLINE FImGuiDrawCommand GetCommand(int CommandNb) const
{
const ImDrawCmd& ImGuiCommand = ImGuiCommandBuffer[CommandNb];
return{ ImGuiCommand.ElemCount, ImGuiInterops::ToSlateRect(ImGuiCommand.ClipRect), ImGuiInterops::ToTextureIndex(ImGuiCommand.TextureId) };
}
// Transform and copy vertex data to target buffer (old data in the target buffer are replaced).
// @param OutVertexBuffer - Destination buffer
// @param VertexPositionOffset - Position offset added to every vertex to transform it to different space
// @param VertexClippingRect - Clipping rectangle for Slate vertices
void CopyVertexData(TArray<FSlateVertex>& OutVertexBuffer, const FVector2D VertexPositionOffset, const FSlateRotatedRect& VertexClippingRect) const;
// Transform and copy index data to target buffer (old data in the target buffer are replaced).
// Internal index buffer contains enough data to match the sum of NumElements from all draw commands.
// @param OutIndexBuffer - Destination buffer
// @param StartIndex - Start copying source data starting from this index
// @param NumElements - How many elements we want to copy
void CopyIndexData(TArray<SlateIndex>& OutIndexBuffer, const int32 StartIndex, const int32 NumElements) const;
// Transfers data from ImGui source list to this object. Leaves source cleared.
void TransferDrawData(ImDrawList& Src);
private:
ImVector<ImDrawCmd> ImGuiCommandBuffer;
ImVector<ImDrawIdx> ImGuiIndexBuffer;
ImVector<ImDrawVert> ImGuiVertexBuffer;
};

View File

@ -0,0 +1,42 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
#include "TextureManager.h"
#include <imgui.h>
// Utilities to help standardise operations between Unreal and ImGui.
namespace ImGuiInterops
{
//====================================================================================================
// Conversions
//====================================================================================================
// Convert from ImGui packed color to FColor.
FORCEINLINE FColor UnpackImU32Color(ImU32 Color)
{
// We use IM_COL32_R/G/B/A_SHIFT macros to support different ImGui configurations.
return FColor{ ((Color >> IM_COL32_R_SHIFT) & 0xFF), ((Color >> IM_COL32_G_SHIFT) & 0xFF),
((Color >> IM_COL32_B_SHIFT) & 0xFF), ((Color >> IM_COL32_A_SHIFT) & 0xFF) };
}
// Convert from ImVec4 rectangle to FSlateRect.
FORCEINLINE FSlateRect ToSlateRect(const ImVec4& ImGuiRect)
{
return FSlateRect{ ImGuiRect.x, ImGuiRect.y, ImGuiRect.z, ImGuiRect.w };
}
// Convert from ImGui Texture Id to Texture Index that we use for texture resources.
FORCEINLINE TextureIndex ToTextureIndex(ImTextureID Index)
{
return static_cast<TextureIndex>(reinterpret_cast<intptr_t>(Index));
}
// Convert from Texture Index to ImGui Texture Id that we pass to ImGui.
FORCEINLINE ImTextureID ToImTextureID(TextureIndex Index)
{
return reinterpret_cast<ImTextureID>(static_cast<intptr_t>(Index));
}
}

View File

@ -2,20 +2,31 @@
#include "ImGuiPrivatePCH.h"
#include "ImGuiModuleManager.h"
#include <IPluginManager.h>
#define LOCTEXT_NAMESPACE "FImGuiModule"
static FImGuiModuleManager* ModuleManager = nullptr;
void FImGuiModule::StartupModule()
{
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
checkf(!ModuleManager, TEXT("Instance of Module Manager already exists. Instance should be created only during module startup."));
// Create module manager that implements modules logic.
ModuleManager = new FImGuiModuleManager();
}
void FImGuiModule::ShutdownModule()
{
// This function may be called during shutdown to clean up your module. For modules that support dynamic reloading,
// we call this function before unloading the module.
checkf(ModuleManager, TEXT("Null Module Manager. Manager instance should be deleted during module shutdown."));
// Before we shutdown we need to delete manager that will do all necessary cleanup.
delete ModuleManager;
ModuleManager = nullptr;
}
#undef LOCTEXT_NAMESPACE

View File

@ -0,0 +1,172 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#include "ImGuiPrivatePCH.h"
#include "ImGuiModuleManager.h"
#include "ImGuiInteroperability.h"
#include <imgui.h>
FImGuiModuleManager::FImGuiModuleManager()
{
// Bind ImGui demo to proxy, so it can draw controls in its context.
ContextProxy.OnDraw().AddRaw(&ImGuiDemo, &FImGuiDemo::DrawControls);
// Typically we will use viewport created events to add widget to new game viewports.
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.
Initialize();
// We need to add widgets to active game viewports as they won't generate on-created events. This is especially
// important during hot-reloading.
if (bInitialized)
{
AddWidgetToAllViewports();
}
}
FImGuiModuleManager::~FImGuiModuleManager()
{
// We are no longer interested with adding widgets to viewports.
if (ViewportCreatedHandle.IsValid())
{
UGameViewportClient::OnViewportCreated().Remove(ViewportCreatedHandle);
ViewportCreatedHandle.Reset();
}
// Deactivate this manager.
Uninitialize();
}
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()
{
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.
TextureManager.CreatePlainTexture(FName{ "ImGuiModule_Null" }, 2, 2, FColor::White);
// Create a font atlas texture.
ImFontAtlas* Fonts = ImGui::GetIO().Fonts;
checkf(Fonts, TEXT("Invalid font atlas."));
unsigned char* Pixels;
int Width, Height, Bpp;
Fonts->GetTexDataAsRGBA32(&Pixels, &Width, &Height, &Bpp);
TextureIndex FontsTexureIndex = TextureManager.CreateTexture(FName{ "ImGuiModule_FontAtlas" }, Width, Height, Bpp, Pixels, false);
// Set font texture index in ImGui.
Fonts->TexID = ImGuiInterops::ToImTextureID(FontsTexureIndex);
}
void FImGuiModuleManager::RegisterTick()
{
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can register tick listener."));
// 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);
}
}
void FImGuiModuleManager::UnregisterTick()
{
if (TickDelegateHandle.IsValid())
{
if (FSlateApplication::IsInitialized())
{
FSlateApplication::Get().OnPostTick().Remove(TickDelegateHandle);
}
TickDelegateHandle.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 proxy to advance to next frame.
ContextProxy.Tick(DeltaSeconds);
}
}
void FImGuiModuleManager::OnViewportCreated()
{
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.
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."));
if (!ViewportWidget.IsValid())
{
SAssignNew(ViewportWidget, SImGuiWidget).ModuleManager(this);
checkf(ViewportWidget.IsValid(), TEXT("Failed to create SImGuiWidget."));
}
// High enough z-order guarantees that ImGui output is rendered on top of the game UI.
constexpr int32 IMGUI_WIDGET_Z_ORDER = 10000;
GameViewport->AddViewportWidgetContent(SNew(SWeakWidget).PossiblyNullContent(ViewportWidget), IMGUI_WIDGET_Z_ORDER);
}
void FImGuiModuleManager::AddWidgetToAllViewports()
{
checkf(FSlateApplication::IsInitialized(), TEXT("Slate should be initialized before we can add widget to game viewports."));
if (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;
}
}
}
}

View File

@ -0,0 +1,70 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
#include "ImGuiContextProxy.h"
#include "ImGuiDemo.h"
#include "SImGuiWidget.h"
#include "TextureManager.h"
// Central manager that implements module logic. It initializes and controls remaining module components.
class FImGuiModuleManager
{
// Allow module to control life-cycle of this class.
friend class FImGuiModule;
public:
// Get ImGui context proxy.
FImGuiContextProxy& GetContextProxy() { return ContextProxy; }
// Get texture resources manager.
FTextureManager& GetTextureManager() { return TextureManager; }
private:
FImGuiModuleManager();
~FImGuiModuleManager();
FImGuiModuleManager(const FImGuiModuleManager&) = delete;
FImGuiModuleManager& operator=(const FImGuiModuleManager&) = delete;
FImGuiModuleManager(FImGuiModuleManager&&) = delete;
FImGuiModuleManager& operator=(FImGuiModuleManager&&) = delete;
void Initialize();
void Uninitialize();
void LoadTextures();
void RegisterTick();
void UnregisterTick();
bool IsInUpdateThread();
void Tick(float DeltaSeconds);
void OnViewportCreated();
void AddWidgetToViewport(UGameViewportClient* GameViewport);
void AddWidgetToAllViewports();
// Proxy controlling ImGui context.
FImGuiContextProxy ContextProxy;
// ImWidget that draws ImGui demo.
FImGuiDemo ImGuiDemo;
// Manager for textures resources.
FTextureManager TextureManager;
// Slate widget that we attach to created game viewports (widget without per-viewport state can be attached to
// multiple viewports).
TSharedPtr<SImGuiWidget> ViewportWidget;
FDelegateHandle TickDelegateHandle;
FDelegateHandle ViewportCreatedHandle;
bool bInitialized = false;
};

View File

@ -5,6 +5,7 @@
// Engine
#include <Core.h>
#include <Engine.h>
// You should place include statements to your module's private header files here. You only need to
// add includes for headers that are used in most of your module's source files though.

View File

@ -0,0 +1,65 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#include "ImGuiPrivatePCH.h"
#include "SImGuiWidget.h"
#include "ImGuiModuleManager.h"
#include "TextureManager.h"
#include "Utilities/ScopeGuards.h"
BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION
void SImGuiWidget::Construct(const FArguments& InArgs)
{
checkf(InArgs._ModuleManager, TEXT("Null Module Manager argument"));
ModuleManager = InArgs._ModuleManager;
}
END_SLATE_FUNCTION_BUILD_OPTIMIZATION
int32 SImGuiWidget::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect,
FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& WidgetStyle, bool bParentEnabled) const
{
// 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) };
// Convert clipping rectangle to format required by Slate vertex.
const FSlateRotatedRect VertexClippingRect{ MyClippingRect };
for (const auto& DrawList : ModuleManager->GetContextProxy().GetDrawData())
{
DrawList.CopyVertexData(VertexBuffer, VertexPositionOffset, VertexClippingRect);
// Get access to the Slate scissor rectangle defined in Slate Core API, so we can customize elements drawing.
extern SLATECORE_API TOptional<FShortRect> GSlateScissorRect;
auto GSlateScissorRectSaver = ScopeGuards::MakeStateSaver(GSlateScissorRect);
int IndexBufferOffset = 0;
for (int CommandNb = 0; CommandNb < DrawList.NumCommands(); CommandNb++)
{
const auto& DrawCommand = DrawList.GetCommand(CommandNb);
DrawList.CopyIndexData(IndexBuffer, IndexBufferOffset, DrawCommand.NumElements);
// Advance offset by number of copied elements to position it for the next command.
IndexBufferOffset += DrawCommand.NumElements;
// Get texture resource handle for this draw command (null index will be also mapped to a valid texture).
const FSlateResourceHandle& Handle = ModuleManager->GetTextureManager().GetTextureHandle(DrawCommand.TextureId);
// Transform clipping rectangle to screen space and set in Slate, to apply it to elements that we draw.
GSlateScissorRect = FShortRect{ DrawCommand.ClippingRect.OffsetBy(MyClippingRect.GetTopLeft()).IntersectionWith(MyClippingRect) };
// Add elements to the list.
FSlateDrawElement::MakeCustomVerts(OutDrawElements, LayerId, Handle, VertexBuffer, IndexBuffer, nullptr, 0, 0);
}
}
return LayerId;
}
FVector2D SImGuiWidget::ComputeDesiredSize(float) const
{
return FVector2D{ 3840.f, 2160.f };
}

View File

@ -0,0 +1,31 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
#include <Widgets/SLeafWidget.h>
class FImGuiModuleManager;
// Slate widget for rendering ImGui output.
class SImGuiWidget : public SLeafWidget
{
public:
SLATE_BEGIN_ARGS(SImGuiWidget)
{}
SLATE_ARGUMENT(FImGuiModuleManager*, ModuleManager)
SLATE_END_ARGS()
void Construct(const FArguments& InArgs);
private:
virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyClippingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& WidgetStyle, bool bParentEnabled) const override;
virtual FVector2D ComputeDesiredSize(float) const override;
FImGuiModuleManager* ModuleManager = nullptr;
mutable TArray<FSlateVertex> VertexBuffer;
mutable TArray<SlateIndex> IndexBuffer;
};

View File

@ -0,0 +1,76 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#include "ImGuiPrivatePCH.h"
#include "TextureManager.h"
#include <algorithm>
TextureIndex FTextureManager::CreateTexture(const FName& Name, int32 Width, int32 Height, uint32 SrcBpp, uint8* SrcData, bool bDeleteSrcData)
{
checkf(FindTextureIndex(Name) == INDEX_NONE, TEXT("Trying to create texture using resource name '%s' that is already registered."), *Name.ToString());
// Create a texture.
UTexture2D* Texture = UTexture2D::CreateTransient(Width, Height);
// Create a new resource for that texture.
Texture->UpdateResource();
// Update texture data.
FUpdateTextureRegion2D* TextureRegion = new FUpdateTextureRegion2D(0, 0, 0, 0, Width, Height);
auto DataCleanup = [bDeleteSrcData](uint8* Data, const FUpdateTextureRegion2D* UpdateRegion)
{
if (bDeleteSrcData)
{
delete Data;
}
delete UpdateRegion;
};
Texture->UpdateTextureRegions(0, 1u, TextureRegion, SrcBpp * Width, SrcBpp, SrcData, DataCleanup);
// Create a new entry for the texture.
return TextureResources.Emplace(Name, Texture);
}
TextureIndex FTextureManager::CreatePlainTexture(const FName& Name, int32 Width, int32 Height, FColor Color)
{
// Create buffer with raw data.
const uint32 ColorPacked = Color.ToPackedARGB();
const uint32 Bpp = sizeof(ColorPacked);
const uint32 SizeInPixels = Width * Height;
const uint32 SizeInBytes = SizeInPixels * Bpp;
uint8* SrcData = new uint8[SizeInBytes];
std::fill(reinterpret_cast<uint32*>(SrcData), reinterpret_cast<uint32*>(SrcData) + SizeInPixels, ColorPacked);
// Create new texture from raw data (we created the buffer, so mark it for delete).
return CreateTexture(Name, Width, Height, Bpp, SrcData, true);
}
FTextureManager::FTextureEntry::FTextureEntry(const FName& InName, UTexture2D* InTexture)
: Name{ InName }
, Texture{ InTexture }
{
// Add texture to root to prevent garbage collection.
Texture->AddToRoot();
// Create brush and resource handle for input texture.
Brush.SetResourceObject(Texture);
ResourceHandle = FSlateApplication::Get().GetRenderer()->GetResourceHandle(Brush);
}
FTextureManager::FTextureEntry::~FTextureEntry()
{
// Release brush.
if (Brush.HasUObject() && FSlateApplication::IsInitialized())
{
FSlateApplication::Get().GetRenderer()->ReleaseDynamicResource(Brush);
}
// Remove texture from root to allow for garbage collection (it might be already invalid if this is application
// shutdown).
if (Texture && Texture->IsValidLowLevel())
{
Texture->RemoveFromRoot();
}
}

View File

@ -0,0 +1,95 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
#include <Core.h>
#include <Styling/SlateBrush.h>
#include <Textures/SlateShaderResource.h>
// Index type to be used as a texture handle.
using TextureIndex = int32;
// Manager for textures resources which can be referenced by a unique name or index.
// Name is primarily for lookup and index provides a direct access to resources.
class FTextureManager
{
public:
// Creates an empty manager.
FTextureManager() = default;
// Copying is disabled to protected resource ownership.
FTextureManager(const FTextureManager&) = delete;
FTextureManager& operator=(const FTextureManager&) = delete;
// Moving transfers ownership and leaves source empty.
FTextureManager(FTextureManager&&) = default;
FTextureManager& operator=(FTextureManager&&) = default;
// Find texture index by name.
// @param Name - The name of a texture to find
// @returns The index of a texture with given name or INDEX_NONE if there is no such texture
TextureIndex FindTextureIndex(const FName& Name) const
{
return TextureResources.IndexOfByPredicate([&](const auto& Entry) { return Entry.Name == Name; });
}
// Get the name of a texture at given index. Throws exception if index is out of range.
// @param Index - Index of a texture
// @returns The name of a texture at given index
FORCEINLINE FName GetTextureName(TextureIndex Index) const
{
return TextureResources[Index].Name;
}
// Get the Slate Resource Handle to a texture at given index. Throws exception if index is out of range.
// @param Index - Index of a texture
// @returns The Slate Resource Handle for a texture at given index
FORCEINLINE const FSlateResourceHandle& GetTextureHandle(TextureIndex Index) const
{
return TextureResources[Index].ResourceHandle;
}
// Create a texture from raw data. Throws exception if there is already a texture with that name.
// @param Name - The texture name
// @param Width - The texture width
// @param Height - The texture height
// @param SrcBpp - The size in bytes of one pixel
// @param SrcData - The source data
// @param bDeleteSrcData - If true, we should delete source data after creating a texture
// @returns The index of a texture that was created
TextureIndex CreateTexture(const FName& Name, int32 Width, int32 Height, uint32 SrcBpp, uint8* SrcData, bool bDeleteSrc = false);
// Create a plain texture. Throws exception if there is already a texture with that name.
// @param Name - The texture name
// @param Width - The texture width
// @param Height - The texture height
// @param Color - The texture color
// @returns The index of a texture that was created
TextureIndex CreatePlainTexture(const FName& Name, int32 Width, int32 Height, FColor Color);
private:
// Entry for texture resources. Only supports explicit construction.
struct FTextureEntry
{
FTextureEntry(const FName& InName, UTexture2D* InTexture);
~FTextureEntry();
// Copying is not supported.
FTextureEntry(const FTextureEntry&) = delete;
FTextureEntry& operator=(const FTextureEntry&) = delete;
// We rely on TArray and don't implement custom move semantics.
FTextureEntry(FTextureEntry&&) = delete;
FTextureEntry& operator=(FTextureEntry&&) = delete;
FName Name = NAME_None;
UTexture2D* Texture = nullptr;
FSlateBrush Brush;
FSlateResourceHandle ResourceHandle;
};
TArray<FTextureEntry> TextureResources;
};

View File

@ -0,0 +1,58 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
namespace ScopeGuards
{
// Saves snapshot of the object state and restores it during destruction.
template<typename T>
class TStateSaver
{
public:
// Constructor taking target object in state that we want to save.
TStateSaver(T& Target)
: Ptr(&Target)
, Value(Target)
{
}
// Move constructor allowing to transfer state out of scope.
TStateSaver(TStateSaver&& Other)
: Ptr(Other.Ptr)
, Value(MoveTemp(Other.Value))
{
// Release responsibility from the other object (std::exchange currently not supported by all platforms).
Other.Ptr = nullptr;
}
// Non-assignable to enforce acquisition on construction.
TStateSaver& operator=(TStateSaver&&) = delete;
// Non-copyable.
TStateSaver(const TStateSaver&) = delete;
TStateSaver& operator=(const TStateSaver&) = delete;
~TStateSaver()
{
if (Ptr)
{
*Ptr = Value;
}
}
private:
T* Ptr;
T Value;
};
// Create a state saver for target object. Unless saver is moved, state will be restored at the end of scope.
// @param Target - Target object in state that we want to save
// @returns State saver that unless moved, will restore target's state during scope exit
template<typename T>
TStateSaver<T> MakeStateSaver(T& Target)
{
return TStateSaver<T>{ Target };
}
}