Bad idea/design choice to use FText for widgets/UI?

I can’t get my head around the fact that UMG and widgets use FText as their only input for getting text rendered on the screen.

Imagine I want to display numeric values (FPS counter, score, etc.) or strings that need to be manipulated frequently. For this, we have to create a new FText object every single time (via FText::FromString). What if we’re updating scores, filling the screen with damage text pop-ups (as seen in MMOs), displaying numeric values for cool-downs, etc. We’d be much better off having mutable strings (such as FString) that can change its data without reallocating on the heap (as opposed to creating new and immutable FText objects every time).

Not only you’d have to pay the cost of manipulating the strings themselves (which can be efficient if you reserve or preallocate enough space), but you’d also have to pay the extra cost of converting this data into an FText that has to be allocated on the heap every single time, and it’s even more expensive. I’m baffled to learn that UE doesn’t provide any feature out of the box for this common use case.

Hi @Guirie921

Although it’s true that UMG uses FText and may seem inefficient, keep in mind that no matter what you’re trying to display, it’s always going to end being some kind of character array/structure (string/text/name/…). But it’s not true that you can only use FText. You can bind the value of a Text UMG to an integer, float or whatever. Manipulating the value will update the text output, so you can do all you mentioned without having to manually convert it to FText.

1 Like

Thank you for chiming in. A while ago I made a custom string class that is very efficient. It works with char* and TCHAR* (the latter being similar to FString, but faster). AFAIK, UMG only have a few methods where all of them accept FText objects. For instance:

void STextBlock::SetText(TAttribute<FText> InText)
	// Cache the IsBound.
	// When the attribute is not bound, we need to go through all the other bound property to check if it is bound.
	bIsAttributeBoundTextBound = InText.IsBound();
	BoundText.Assign(*this, MoveTemp(InText));


int32 STextBlock::OnPaint( const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled ) const
	// ...

	FSlateDrawElement::MakeText( // ...


FORCEINLINE static void MakeText(/* some input */ const FText& InText /* some input */)

No matter what you do, it seems to either create an FText for you or force you to input an FText in the end.

When you say that it can accept other input (such as integers), would you mind elaborating how exactly I could do that? Like, for instance, which method should I call exactly? Because if it can accept integers, it should be able to accept char or at least TCHAR types too.

I think I misread you. When you mentioned that you can bind the value of a text UMG object to an integer and that changing the integer updates the display, what happens under the hood is an FText object being created in order to show the updated value (if I’m not mistaken). So this actually doesn’t address the problem.

Just to put things worse, when you manipulate a string (with FString or your custom solution), then you have to convert it to a new FText object because the ‘MakeText’ method expects an FText reference, but then it converts it back to an FString internally:

FORCEINLINE static void MakeText(FSlateWindowElementList& ElementList, uint32 InLayer, const FPaintGeometry& PaintGeometry, const FText& InText, const FSlateFontInfo& InFontInfo, ESlateDrawEffect InDrawEffects = ESlateDrawEffect::None, const FLinearColor& InTint = FLinearColor::White)
	MakeText(ElementList, InLayer, PaintGeometry, InText.ToString(), InFontInfo, InDrawEffects, InTint);

What a waste, isn’t it?

I didn’t say it was efficient or that you could directly input an integer or any other variable/object type. What I said is that at the end of the day it’s always got to be something that holds chars.

Obviously that UE code doesn’t look pretty, but Slate is older than the sun.
Even though that’s not efficient or pretty, nowadays modern computers don’t really suffer from that. You can make a thousand conversions from int to FString to FText per frame and no one will notice.
Is that good? Yes and no, but it’s something we can live with.

If you need ultimate performance, you can always make your own renderer and UI renderer, but that’s much harder than learning to live with the fact that some Slate methods don’t seem to make sense :joy:

It wasn’t much of a complaint (though it was a bit :joy:), but I wanted to confirm whether or not there are some other UI methods I might have overlooked. So, can we safely assume that UE’s text-UI kind of sucks then? I don’t want to be harsh on UE, but I was expecting more than this for Unreal’s UI, maybe it’s just my fault for having naive expectations.

I agree with you, partially. String prep (manipulation, conversion, etc.) for text rendering can take up a significant portion (up to 30-40% in some cases) of the time needed to display text on the screen. Most of the time is always spent on the actual render, but still, it could be significant, especially if UE is wasting performance like that carelessly.

I also agree that this wouldn’t be a bottleneck in the vast majority of cases (so who cares), but it ultimately depends on your use case. If you’re building a game/application that heavily relies on text display, this is a big bummer. Even more so if you’re not shipping for PC and resources are more limited. It could potentially take away a big chunk of your MS budget, which could’ve been used for something more productive or interesting otherwise.

Anyhow, thanks for sharing your thoughts.