Visual Studio
[UE] Editable Text 최적화 사례 본문
아이디어
- 폰트 관련 데이터 객체들(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%정도 감소하였음.
(성능 변화를 정확하게 측정하는게 중요하지 않은 피쳐다 보니 따로 자료는 만들지 않았음)
'Game Engine' 카테고리의 다른 글
[UE] RelativeLocation과 LocalOffset은 어떤 차이가 있을까 (0) | 2024.10.17 |
---|---|
[UE] Animation, Sequencer 관련 구조체들 (0) | 2024.10.17 |
[UE] Unreal Engine 소스 기여 경험 (0) | 2024.07.14 |
[UE] SWindow 생성시 윈도우 크기를 임의로 설정하는 방법 (0) | 2024.06.18 |
[UE] TObjectKey, FObjectKey에 대하여 (0) | 2024.05.07 |