Notice
Recent Posts
Recent Comments
Link
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
Tags
more
Archives
Today
Total
관리 메뉴

Visual Studio

[UE] Editable Text 최적화 사례 본문

Game Engine

[UE] Editable Text 최적화 사례

emacser 2024. 7. 17. 18:58

아이디어

- 폰트 관련 데이터 객체들(FCodepointFontCacheData, FFontData, FFreeTypeFaceGlyphData)을 가져올 때 유효한지 검사하는 로직이 오버헤드가 큼.

    - 정확히는 아래 두 함수 내부의 검사 로직이 오버헤드가 크다.

    - FSlateFontRenderer::GetFontDataForCodepoint()

    - FCompositeFontCache::GetFontFaceForCodepoint()

- 유효함이 이미 검증된 폰트 데이터는 한번 더 캐싱함. (편의를 위해 2차 캐시라고 부르겠음)

- 이후 같은 글자에 대해서 폰트 데이터가 필요할 경우 2차 캐시가 있다면 검사 과정을 스킵하고 가져옴.

 

구현

영향을 미치는 범위를 줄이기 위해 Ascii code 범위의 글자에만 적용하였다.

(Fallback font 등의 요인으로 예상치 못한 부작용이 발생하는 것을 막기 위함)

 

SlateTextShaper.h

...
#if SLATE_CACHE_FONT_FOR_CODEPOINT
// 헤더 추가
#include "Fonts/SlateFontRenderer.h"
#endif // SLATE_CACHE_FONT_FOR_CODEPOINT

...

class FSlateTextShaper
{

...

#if SLATE_CACHE_FONT_FOR_CODEPOINT
// 클래스 선언 끝부분에 추가
private:
    struct FCodepointFontCacheIdentifier
	{
		FName TypefaceFontName;
		TSharedPtr<const FCompositeFont> CompositeFont = nullptr;

		bool operator==(const FCodepointFontCacheIdentifier& Other) const
		{
			return TypefaceFontName == Other.TypefaceFontName &&
				CompositeFont == Other.CompositeFont;
		}
	};

	struct FCodepointFontCacheData
	{
		const FFontData* FontDataCache[128];
		FFreeTypeFaceGlyphData FontFaceCache[128];
	};

	struct FCodepointFontCacheEntry
	{
		FCodepointFontCacheIdentifier Identifier;
		FCodepointFontCacheData Data;
	};

	mutable TArray<TSharedPtr<FCodepointFontCacheEntry>> CodepointFontCache;
#endif // SLATE_CACHE_FONT_FOR_CODEPOINT
}

 

SlateTextShaper.cpp

void FSlateTextShaper::PerformKerningOnlyTextShaping(const TCHAR* InText, const int32 InTextStart, const int32 InTextLen, const FSlateFontInfo& InFontInfo, const float InFontScale, TArray<FShapedGlyphEntry>& OutGlyphsToRender) const
{

		...
        
#if SLATE_CACHE_FONT_FOR_CODEPOINT
		FCodepointFontCacheIdentifier CodepointFontCacheIdentifier;
		CodepointFontCacheIdentifier.TypefaceFontName = InFontInfo.TypefaceFontName;
		CodepointFontCacheIdentifier.CompositeFont = InFontInfo.CompositeFont;
#endif // SLATE_CACHE_FONT_FOR_CODEPOINT

		const int32 TextEndIndex = InTextStart + InTextLen;
		for (; RunningTextIndex < TextEndIndex; ++RunningTextIndex)
		{
			const TCHAR CurrentChar = InText[RunningTextIndex];
			const bool bShouldRenderAsWhitespace = RenderCodepointAsWhitespace(CurrentChar);

			// First try with the actual character
			float SubFontScalingFactor = 1.0f;

#if SLATE_CACHE_FONT_FOR_CODEPOINT
			FCodepointFontCacheData* CurrentCodepointFontCacheData = nullptr;
			for (const TSharedPtr<FCodepointFontCacheEntry>& Entry : CodepointFontCache)
			{
				if (Entry->Identifier == CodepointFontCacheIdentifier)
				{
					CurrentCodepointFontCacheData = &Entry->Data;
				}
			}

			if (CurrentCodepointFontCacheData == nullptr)
			{
				TSharedPtr<FCodepointFontCacheEntry> NewEntry = MakeShareable(new FCodepointFontCacheEntry);
				NewEntry->Identifier = CodepointFontCacheIdentifier;

				FMemory::Memset(NewEntry->Data.FontDataCache, 0, sizeof(NewEntry->Data.FontDataCache));
				FMemory::Memset(NewEntry->Data.FontFaceCache, 0, sizeof(NewEntry->Data.FontFaceCache));
				for (int32 Index = 0; Index < 128; ++Index)
				{
					NewEntry->Data.FontFaceCache[Index].GlyphIndex = INDEX_NONE;
				}

				CurrentCodepointFontCacheData = &NewEntry->Data;
				CodepointFontCache.Add(NewEntry);
			}

			const FFontData* FontDataPtr = nullptr;
			if (CurrentChar < 128)
			{
				FontDataPtr = CurrentCodepointFontCacheData->FontDataCache[CurrentChar];
				if (FontDataPtr == nullptr)
				{
					FontDataPtr = &CompositeFontCache->GetFontDataForCodepoint(InFontInfo, CurrentChar, SubFontScalingFactor);
					CurrentCodepointFontCacheData->FontDataCache[CurrentChar] = FontDataPtr;
				}
			}
			else
			{
				FontDataPtr = &CompositeFontCache->GetFontDataForCodepoint(InFontInfo, CurrentChar, SubFontScalingFactor);
			}

			FFreeTypeFaceGlyphData FaceGlyphData;
			if (CurrentChar < 128)
			{
				FaceGlyphData = CurrentCodepointFontCacheData->FontFaceCache[CurrentChar];
				if (FaceGlyphData.GlyphIndex == INDEX_NONE)
				{
					FaceGlyphData = FontRenderer->GetFontFaceForCodepoint(*FontDataPtr, CurrentChar, bShouldRenderAsWhitespace ? EFontFallback::FF_NoFallback : InFontInfo.FontFallback);
					CurrentCodepointFontCacheData->FontFaceCache[CurrentChar] = FaceGlyphData;
				}
			}
			else
			{
				FaceGlyphData = FontRenderer->GetFontFaceForCodepoint(*FontDataPtr, CurrentChar, bShouldRenderAsWhitespace ? EFontFallback::FF_NoFallback : InFontInfo.FontFallback);
			}
#else
			const FFontData* FontDataPtr = &CompositeFontCache->GetFontDataForCodepoint(InFontInfo, CurrentChar, SubFontScalingFactor);
			FFreeTypeFaceGlyphData FaceGlyphData = FontRenderer->GetFontFaceForCodepoint(*FontDataPtr, CurrentChar, bShouldRenderAsWhitespace ? EFontFallback::FF_NoFallback : InFontInfo.FontFallback);
#endif // SLATE_CACHE_FONT_FOR_CODEPOINT

			...
	}
    
    ...
    
}

성능 변화

PerformKerningOnlyTextShaping 함수의 실행시간이 평균 50%정도 감소하였음.

(성능 변화를 정확하게 측정하는게 중요하지 않은 피쳐다 보니 따로 자료는 만들지 않았음)