Added support for registering in module user textures to them in ImGui:

- Added ImGuiTextureHandle that is implicitly convertible to ImTextureID and can be used directly with ImGui interface.
- Added to ImGui Module interface allowing to register, update and release textures or find handles to existing resources.
- Refactored Texture Manager and added functions allowing create, update or release resources for user textures.
- Loosened resource verification policy in Texture Manager to work smoothly with external requests and to handle gracefully situations when invalid texture id is passed to ImGui.
- Introduced ‘error texture’ that is used for invalid resources.
This commit is contained in:
Sebastian 2018-08-15 22:20:46 +01:00
parent 9c181463ce
commit dd06fcbbdf
7 changed files with 365 additions and 30 deletions

View File

@ -3,6 +3,9 @@
#include "ImGuiPrivatePCH.h"
#include "ImGuiModuleManager.h"
#include "ImGuiTextureHandle.h"
#include "TextureManager.h"
#include "Utilities/WorldContext.h"
#include "Utilities/WorldContextIndex.h"
@ -113,6 +116,26 @@ void FImGuiModule::RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle)
}
}
FImGuiTextureHandle FImGuiModule::FindTextureHandle(const FName& Name)
{
const TextureIndex Index = ImGuiModuleManager->GetTextureManager().FindTextureIndex(Name);
return (Index != INDEX_NONE) ? FImGuiTextureHandle{ Name, ImGuiInterops::ToImTextureID(Index) } : FImGuiTextureHandle{};
}
FImGuiTextureHandle FImGuiModule::RegisterTexture(const FName& Name, class UTexture2D* Texture, bool bMakeUnique)
{
const TextureIndex Index = ImGuiModuleManager->GetTextureManager().CreateTextureResources(Name, Texture, bMakeUnique);
return FImGuiTextureHandle{ Name, ImGuiInterops::ToImTextureID(Index) };
}
void FImGuiModule::ReleaseTexture(const FImGuiTextureHandle& Handle)
{
if (Handle.IsValid())
{
ImGuiModuleManager->GetTextureManager().ReleaseTextureResources(ImGuiInterops::ToTextureIndex(Handle.GetTextureId()));
}
}
void FImGuiModule::StartupModule()
{
// Create managers that implements module logic.
@ -198,6 +221,17 @@ void FImGuiModule::ToggleShowDemo()
SetShowDemo(!IsShowingDemo());
}
//----------------------------------------------------------------------------------------------------
// Partial implementations of other classes that needs access to ImGuiModuleManager
//----------------------------------------------------------------------------------------------------
bool FImGuiTextureHandle::HasValidEntry() const
{
const TextureIndex Index = ImGuiInterops::ToTextureIndex(TextureId);
return Index != INDEX_NONE && ImGuiModuleManager && ImGuiModuleManager->GetTextureManager().GetTextureName(Index) == Name;
}
#undef LOCTEXT_NAMESPACE
IMPLEMENT_MODULE(FImGuiModule, ImGui)

View File

@ -62,6 +62,8 @@ void FImGuiModuleManager::LoadTextures()
{
bTexturesLoaded = true;
TextureManager.InitializeErrorTexture(FColor::Magenta);
// 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);

View File

@ -0,0 +1,26 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#include "ImGuiPrivatePCH.h"
#include "ImGuiTextureHandle.h"
#include "ImGuiInteroperability.h"
FImGuiTextureHandle::FImGuiTextureHandle()
: FImGuiTextureHandle(NAME_None, ImGuiInterops::ToImTextureID(INDEX_NONE))
{
}
FImGuiTextureHandle::FImGuiTextureHandle(const FName& InName, ImTextureID InTextureId)
: Name(InName)
, TextureId(InTextureId)
{
const TextureIndex Index = ImGuiInterops::ToTextureIndex(TextureId);
checkf((Index == INDEX_NONE) == (Name == NAME_None),
TEXT("Mismatch between Name and TextureId parameters, with only one indicating a null handle.")
TEXT(" Name = '%s', TextureIndex(TextureId) = %d"), *Name.ToString(), Index);
}
// FImGuiTextureHandle::HasValidEntry() is implemented in ImGuiModule.cpp to get access to FImGuiModuleManager instance
// without referencing in this class.

View File

@ -7,10 +7,51 @@
#include <algorithm>
void FTextureManager::InitializeErrorTexture(const FColor& Color)
{
CreatePlainTextureInternal(NAME_ErrorTexture, 2, 2, Color);
}
TextureIndex FTextureManager::CreateTexture(const FName& Name, int32 Width, int32 Height, uint32 SrcBpp, uint8* SrcData, TFunction<void(uint8*)> SrcDataCleanup)
{
checkf(FindTextureIndex(Name) == INDEX_NONE, TEXT("Trying to create texture using resource name '%s' that is already registered."), *Name.ToString());
checkf(Name != NAME_None, TEXT("Trying to create a texture with a name 'NAME_None' is not allowed."));
checkf(FindTextureIndex(Name) == INDEX_NONE, TEXT("Trying to create texture using name '%s' that is already registered."), *Name.ToString());
return CreateTextureInternal(Name, Width, Height, SrcBpp, SrcData, SrcDataCleanup);
}
TextureIndex FTextureManager::CreatePlainTexture(const FName& Name, int32 Width, int32 Height, FColor Color)
{
checkf(Name != NAME_None, TEXT("Trying to create a texture with a name 'NAME_None' is not allowed."));
checkf(FindTextureIndex(Name) == INDEX_NONE, TEXT("Trying to create texture using name '%s' that is already registered."), *Name.ToString());
return CreatePlainTextureInternal(Name, Width, Height, Color);
}
TextureIndex FTextureManager::CreateTextureResources(const FName& Name, UTexture2D* Texture, bool bMakeUnique)
{
checkf(Name != NAME_None, TEXT("Trying to create texture resources with a name 'NAME_None' is not allowed."));
checkf(Texture, TEXT("Null Texture."));
if (bMakeUnique)
{
checkf(FindTextureIndex(Name) == INDEX_NONE, TEXT("Trying to create texture resources using name '%s' that is already registered.")
TEXT(" Consider using different name or set bMakeUnique parameter to false."), *Name.ToString());
}
// Create an entry for the texture.
return AddTextureEntry(Name, Texture, false, true);
}
void FTextureManager::ReleaseTextureResources(TextureIndex Index)
{
checkf(IsInRange(Index), TEXT("Invalid texture index %d. Texture resources array has %d entries total."), Index, TextureResources.Num());
TextureResources[Index] = {};
}
TextureIndex FTextureManager::CreateTextureInternal(const FName& Name, int32 Width, int32 Height, uint32 SrcBpp, uint8* SrcData, TFunction<void(uint8*)> SrcDataCleanup)
{
// Create a texture.
UTexture2D* Texture = UTexture2D::CreateTransient(Width, Height);
@ -26,11 +67,19 @@ TextureIndex FTextureManager::CreateTexture(const FName& Name, int32 Width, int3
};
Texture->UpdateTextureRegions(0, 1u, TextureRegion, SrcBpp * Width, SrcBpp, SrcData, DataCleanup);
// Create a new entry for the texture.
return TextureResources.Emplace(Name, Texture);
// Create an entry for the texture.
if (Name == NAME_ErrorTexture)
{
ErrorTexture = { Name, Texture, true };
return INDEX_ErrorTexture;
}
else
{
return AddTextureEntry(Name, Texture, true, false);
}
}
TextureIndex FTextureManager::CreatePlainTexture(const FName& Name, int32 Width, int32 Height, FColor Color)
TextureIndex FTextureManager::CreatePlainTextureInternal(const FName& Name, int32 Width, int32 Height, const FColor& Color)
{
// Create buffer with raw data.
const uint32 ColorPacked = Color.ToPackedARGB();
@ -42,15 +91,44 @@ TextureIndex FTextureManager::CreatePlainTexture(const FName& Name, int32 Width,
auto SrcDataCleanup = [](uint8* Data) { delete[] Data; };
// Create new texture from raw data.
return CreateTexture(Name, Width, Height, Bpp, SrcData, SrcDataCleanup);
return CreateTextureInternal(Name, Width, Height, Bpp, SrcData, SrcDataCleanup);
}
FTextureManager::FTextureEntry::FTextureEntry(const FName& InName, UTexture2D* InTexture)
: Name{ InName }
, Texture{ InTexture }
TextureIndex FTextureManager::AddTextureEntry(const FName& Name, UTexture2D* Texture, bool bAddToRoot, bool bUpdate)
{
// Add texture to root to prevent garbage collection.
Texture->AddToRoot();
// If we update try to find entry with that name.
TextureIndex Index = bUpdate ? FindTextureIndex(Name) : INDEX_NONE;
// If we didn't find, try to find and entry to reuse.
if (Index == INDEX_NONE)
{
Index = FindTextureIndex(NAME_None);
}
// Either update/reuse entry or add a new one.
if (Index != INDEX_NONE)
{
TextureResources[Index] = { Name, Texture, bAddToRoot };
return Index;
}
else
{
return TextureResources.Emplace(Name, Texture, bAddToRoot);
}
}
FTextureManager::FTextureEntry::FTextureEntry(const FName& InName, UTexture2D* InTexture, bool bAddToRoot)
: Name(InName)
{
checkf(InTexture, TEXT("Null texture."));
if (bAddToRoot)
{
// Get pointer only for textures that we added to root, so we can later release them.
Texture = InTexture;
// Add texture to the root to prevent garbage collection.
InTexture->AddToRoot();
}
// Create brush and resource handle for input texture.
Brush.SetResourceObject(InTexture);
@ -59,16 +137,50 @@ FTextureManager::FTextureEntry::FTextureEntry(const FName& InName, UTexture2D* I
FTextureManager::FTextureEntry::~FTextureEntry()
{
// Release brush.
if (Brush.HasUObject() && FSlateApplication::IsInitialized())
Reset(true);
}
FTextureManager::FTextureEntry& FTextureManager::FTextureEntry::operator=(FTextureEntry&& Other)
{
// Release old resources if allocated.
Reset(true);
// Move data and ownership to this instance.
Name = MoveTemp(Other.Name);
Texture = MoveTemp(Other.Texture);
Brush = MoveTemp(Other.Brush);
ResourceHandle = MoveTemp(Other.ResourceHandle);
// Reset the other entry (without releasing resources which are already moved to this instance) to remove tracks
// of ownership and mark it as empty/reusable.
Other.Reset(false);
return *this;
}
void FTextureManager::FTextureEntry::Reset(bool bReleaseResources)
{
if (bReleaseResources)
{
FSlateApplication::Get().GetRenderer()->ReleaseDynamicResource(Brush);
// Release brush.
if (Brush.HasUObject() && FSlateApplication::IsInitialized())
{
FSlateApplication::Get().GetRenderer()->ReleaseDynamicResource(Brush);
}
// Remove texture from root to allow for garbage collection (it might be invalid, if we never set it
// or this is an application shutdown).
if (Texture.IsValid())
{
Texture->RemoveFromRoot();
}
}
// Remove texture from root to allow for garbage collection (it might be already invalid if this is application
// shutdown).
if (Texture.IsValid())
{
Texture->RemoveFromRoot();
}
// We use empty name to mark unused entries.
Name = NAME_None;
// Clean fields to make sure that we don't reference released or moved resources.
Texture.Reset();
Brush = FSlateNoResource();
ResourceHandle = FSlateResourceHandle();
}

View File

@ -27,6 +27,13 @@ public:
FTextureManager(FTextureManager&&) = default;
FTextureManager& operator=(FTextureManager&&) = default;
// Initialize error texture that will be used for rendering textures without registered resources. Can be called
// multiple time, if color needs to be changed.
// Note: Because of any-time module loading and lazy resources initialization goals we can't simply call it from
// constructor.
// @param Color - The color of the error texture
void InitializeErrorTexture(const FColor& Color);
// 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
@ -35,20 +42,22 @@ public:
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.
// Get the name of a texture at given index. Returns NAME_None, 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
// @returns The name of a texture at given index or NAME_None if index is out of range.
FName GetTextureName(TextureIndex Index) const
{
return TextureResources[Index].Name;
return IsInRange(Index) ? TextureResources[Index].Name : NAME_None;
}
// Get the Slate Resource Handle to a texture at given index. Throws exception if index is out of range.
// Get the Slate Resource Handle to a texture at given index. If index is out of range or resources are not valid
// it returns a handle to the error texture.
// @param Index - Index of a texture
// @returns The Slate Resource Handle for a texture at given index
FORCEINLINE const FSlateResourceHandle& GetTextureHandle(TextureIndex Index) const
// @returns The Slate Resource Handle for a texture at given index or to error texture, if no valid resources were
// found at given index
const FSlateResourceHandle& GetTextureHandle(TextureIndex Index) const
{
return TextureResources[Index].ResourceHandle;
return IsValidTexture(Index) ? TextureResources[Index].ResourceHandle : ErrorTexture.ResourceHandle;
}
// Create a texture from raw data. Throws exception if there is already a texture with that name.
@ -69,27 +78,81 @@ public:
// @returns The index of a texture that was created
TextureIndex CreatePlainTexture(const FName& Name, int32 Width, int32 Height, FColor Color);
// Create Slate resources to an existing texture, managed externally. As part of an external interface it allows
// to loosen resource verification policy. By default (consistently with other create function) it protects from
// creating resources with name that is already registered. If bMakeUnique is false, then existing resources are
// updated/replaced. Throws exception, if name argument is NAME_None or texture is null.
// @param Name - The texture name
// @param Texture - The texture
// @param bMakeUnique - If true (default) and there is already a texture with given name, then exception is thrown,
// otherwise existing resources are updated.
// @returns The index to created/updated texture resources
TextureIndex CreateTextureResources(const FName& Name, UTexture2D* Texture, bool bMakeUnique = true);
// Release resources for given texture. Ignores invalid indices.
// @param Index - The index of a texture resources
void ReleaseTextureResources(TextureIndex Index);
private:
// See CreateTexture for general description.
// Internal implementations doesn't validate name or resource uniqueness. Instead it uses NAME_ErrorTexture
// (aka NAME_None) and INDEX_ErrorTexture (aka INDEX_NONE) to identify ErrorTexture.
TextureIndex CreateTextureInternal(const FName& Name, int32 Width, int32 Height, uint32 SrcBpp, uint8* SrcData, TFunction<void(uint8*)> SrcDataCleanup = [](uint8*) {});
// See CreatePlainTexture for general description.
// Internal implementations doesn't validate name or resource uniqueness. Instead it uses NAME_ErrorTexture
// (aka NAME_None) and INDEX_ErrorTexture (aka INDEX_NONE) to identify ErrorTexture.
TextureIndex CreatePlainTextureInternal(const FName& Name, int32 Width, int32 Height, const FColor& Color);
// Add or reuse texture entry.
// @param Name - The texture name
// @param Texture - The texture
// @param bAddToRoot - If true, we should add texture to root to prevent garbage collection (use for own textures)
// @returns The index of the entry that we created or reused
TextureIndex AddTextureEntry(const FName& Name, UTexture2D* Texture, bool bAddToRoot, bool bUpdate);
// Check whether index is in range allocated for TextureResources (it doesn't mean that resources are valid).
FORCEINLINE bool IsInRange(TextureIndex Index) const
{
return static_cast<uint32>(Index) < static_cast<uint32>(TextureResources.Num());
}
// Check whether index is in range and whether texture resources are valid (using NAME_None sentinel).
FORCEINLINE bool IsValidTexture(TextureIndex Index) const
{
return IsInRange(Index) && TextureResources[Index].Name != NAME_None;
}
// Entry for texture resources. Only supports explicit construction.
struct FTextureEntry
{
FTextureEntry(const FName& InName, UTexture2D* InTexture);
FTextureEntry() = default;
FTextureEntry(const FName& InName, UTexture2D* InTexture, bool bAddToRoot);
~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.
// We rely on TArray and don't implement custom move constructor...
FTextureEntry(FTextureEntry&&) = delete;
FTextureEntry& operator=(FTextureEntry&&) = delete;
// ... but we need move assignment to support reusing entries.
FTextureEntry& operator=(FTextureEntry&& Other);
FName Name = NAME_None;
TWeakObjectPtr<UTexture2D> Texture;
FSlateBrush Brush;
FSlateResourceHandle ResourceHandle;
private:
void Reset(bool bReleaseResources);
};
TArray<FTextureEntry> TextureResources;
FTextureEntry ErrorTexture;
static constexpr EName NAME_ErrorTexture = NAME_None;
static constexpr TextureIndex INDEX_ErrorTexture = INDEX_NONE;
};

View File

@ -3,6 +3,7 @@
#pragma once
#include "ImGuiDelegates.h"
#include "ImGuiTextureHandle.h"
#include <ModuleManager.h>
@ -69,6 +70,39 @@ public:
*/
virtual void RemoveImGuiDelegate(const FImGuiDelegateHandle& Handle);
/**
* If it exists, get a handle to the texture with given resource name.
*
* @param Name - Resource name of a texture to find
* @returns Handle to a registered texture or invalid handle if resources could not be found or were not valid
*/
virtual FImGuiTextureHandle FindTextureHandle(const FName& Name);
/**
* Register texture and create its Slate resources. If texture with that name already exists then it may be updated
* or if bMakeUnique is true, exception will be thrown. Throws exception, if name argument is NAME_None or texture
* is null.
*
* Note, that updating texture resources doesn't invalidate already existing handles and returned handle will have
* the same value.
*
* @param Name - Resource name for the texture that needs to be registered or updated
* @param Texture - Texture for which we want to create or update Slate resources
* @param bMakeUnique - If false then existing resources are updated/overwritten (default). If true, then stricter
* policy is applied and if resource with that name exists then exception is thrown.
* @returns Handle to the texture resources, which can be used to release allocated resources and as an argument to
* relevant ImGui functions
*/
virtual FImGuiTextureHandle RegisterTexture(const FName& Name, class UTexture2D* Texture, bool bMakeUnique = false);
/**
* Unregister texture and release its Slate resources. If handle is null or not valid, this function fails silently
* (for definition of 'valid' look @ FImGuiTextureHandle).
*
* @returns ImGui Texture Handle to texture that needs to be unregistered
*/
virtual void ReleaseTexture(const FImGuiTextureHandle& Handle);
/**
* Check whether Input Mode is enabled (tests ImGui.InputEnabled console variable).
*

View File

@ -0,0 +1,64 @@
// Distributed under the MIT License (MIT) (see accompanying LICENSE file)
#pragma once
#include <CoreMinimal.h>
#include <imgui.h>
/**
* Handle to texture resources registered in module instance. Returned after successful texture registration.
* Can be implicitly converted to ImTextureID making it possible to use it directly with ImGui interface.
* Once texture is not needed handle can be used to release resources.
*/
class IMGUI_API FImGuiTextureHandle
{
public:
/** Creates an empty (null and not valid) texture handle. */
FImGuiTextureHandle();
/**
* Checks whether this handle is null. Can be used as a quick test whether it points to any resources but it does
* not check whether those resources are valid (see @ IsValid).
*
* @returns True, if this handle is null (Name is NAME_None and TextureId is invalid) and false otherwise.
*/
bool IsNull() const { return Name == NAME_None; }
/**
* Checks whether this handle is not null and valid. Valid handle points to valid texture resources.
* It is slower but safer test, more useful when there is no guarantee that resources haven't been released.
*
* @returns True, if this handle is not null and valid, false otherwise.
*/
bool IsValid() const { return !IsNull() && HasValidEntry(); }
/** Get the name of the texture resources (NAME_None if handle is null). */
const FName& GetName() const { return Name; }
/** Get the ImGui texture id for this texture (invalid if handle is null). */
ImTextureID GetTextureId() const { return TextureId; }
/** Implicit conversion to ImTextureID. */
operator ImTextureID() const { return GetTextureId(); }
private:
/**
* Creates a texture handle with known name and texture id.
* @param InName - Name of the texture
* @param InTextureId - ImGui id of texture
*/
FImGuiTextureHandle(const FName& InName, ImTextureID InTextureId);
/** Checks if texture manager has entry that matches this name and texture id index. */
bool HasValidEntry() const;
FName Name;
ImTextureID TextureId;
// Give module class a private access, so it can create valid handles.
friend class FImGuiModule;
};