Merge pull request #19531 from hrydgard/fix-text-perf-problem

Improve performance of UI text rendering
This commit is contained in:
Henrik Rydgård 2024-10-16 15:13:59 +02:00 committed by GitHub
commit beed90ba8b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 178 additions and 229 deletions

View File

@ -521,7 +521,6 @@ void DrawBuffer::MeasureTextRect(FontID font_id, std::string_view text, const Bo
return;
}
std::string toMeasure = std::string(text);
int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT);
if (wrap) {
const AtlasFont *font = fontAtlas_->getFont(font_id);
@ -532,10 +531,12 @@ void DrawBuffer::MeasureTextRect(FontID font_id, std::string_view text, const Bo
*h = 0.0f;
return;
}
std::string toMeasure = std::string(text);
AtlasWordWrapper wrapper(*font, fontscalex, toMeasure, bounds.w, wrap);
toMeasure = wrapper.Wrapped();
} else {
MeasureText(font_id, text, w, h);
}
MeasureText(font_id, toMeasure, w, h);
}
void DrawBuffer::DrawTextShadow(FontID font, std::string_view text, float x, float y, Color color, int flags) {

View File

@ -18,8 +18,6 @@ TextDrawer::TextDrawer(Draw::DrawContext *draw) : draw_(draw) {
// These probably shouldn't be state.
dpiScale_ = CalculateDPIScale();
}
TextDrawer::~TextDrawer() {
}
float TextDrawerWordWrapper::MeasureWidth(std::string_view str) {
float w, h;
@ -123,13 +121,49 @@ void TextDrawer::DrawString(DrawBuffer &target, std::string_view str, float x, f
target.Flush(true);
}
void TextDrawer::MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align) {
std::string toMeasure = std::string(str);
int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT);
if (wrap) {
WrapString(toMeasure, toMeasure.c_str(), bounds.w, wrap);
void TextDrawer::MeasureString(std::string_view str, float *w, float *h) {
if (str.empty()) {
*w = 0.0;
*h = 0.0;
return;
}
CacheKey key{ std::string(str), fontHash_ };
TextMeasureEntry *entry;
auto iter = sizeCache_.find(key);
if (iter != sizeCache_.end()) {
entry = iter->second.get();
} else {
entry = new TextMeasureEntry();
float extW, extH;
MeasureStringInternal(str, &extW, &extH);
entry->width = extW;
entry->height = extH;
// Hm, use the old calculation?
// int h = i == lines.size() - 1 ? entry->height : metrics.tmHeight + metrics.tmExternalLeading;
sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry);
}
entry->lastUsedFrame = frameCount_;
*w = entry->width * fontScaleX_ * dpiScale_;
*h = entry->height * fontScaleY_ * dpiScale_;
}
void TextDrawer::MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align) {
int wrap = align & (FLAG_WRAP_TEXT | FLAG_ELLIPSIZE_TEXT);
float plainW, plainH;
MeasureString(str, &plainW, &plainH);
if (wrap && plainW > bounds.w) {
std::string toMeasure = std::string(str);
WrapString(toMeasure, toMeasure.c_str(), bounds.w, wrap);
MeasureString(toMeasure, w, h);
} else {
*w = plainW;
*h = plainH;
}
MeasureString(toMeasure, w, h);
}
void TextDrawer::DrawStringRect(DrawBuffer &target, std::string_view str, const Bounds &bounds, uint32_t color, int align) {
@ -186,7 +220,7 @@ void TextDrawer::OncePerFrame() {
}
// Drop old strings. Use a prime number to reduce clashing with other rhythms
if (frameCount_ % 23 == 0) {
if (frameCount_ % 63 == 0) {
for (auto iter = cache_.begin(); iter != cache_.end();) {
if (frameCount_ - iter->second->lastUsedFrame > 100) {
if (iter->second->texture)

View File

@ -42,14 +42,14 @@ struct TextMeasureEntry {
class TextDrawer {
public:
virtual ~TextDrawer();
virtual ~TextDrawer() = default;
virtual bool IsReady() const { return true; }
virtual uint32_t SetFont(const char *fontName, int size, int flags) = 0;
virtual void SetFont(uint32_t fontHandle) = 0; // Shortcut once you've set the font once.
void SetFontScale(float xscale, float yscale);
virtual void MeasureString(std::string_view str, float *w, float *h) = 0;
virtual void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT);
void MeasureString(std::string_view str, float *w, float *h);
void MeasureStringRect(std::string_view str, const Bounds &bounds, float *w, float *h, int align = ALIGN_TOPLEFT);
void DrawString(DrawBuffer &target, std::string_view str, float x, float y, uint32_t color, int align = ALIGN_TOPLEFT);
void DrawStringRect(DrawBuffer &target, std::string_view str, const Bounds &bounds, uint32_t color, int align);
@ -69,6 +69,9 @@ public:
protected:
TextDrawer(Draw::DrawContext *draw);
virtual void MeasureStringInternal(std::string_view str, float *w, float *h) = 0;
void ClearCache();
virtual bool SupportsColorEmoji() const = 0;

View File

@ -75,41 +75,23 @@ void TextDrawerAndroid::SetFont(uint32_t fontHandle) {
}
}
void TextDrawerAndroid::MeasureString(std::string_view str, float *w, float *h) {
if (str.empty()) {
*w = 0.0;
*h = 0.0;
return;
}
CacheKey key{ std::string(str), fontHash_ };
TextMeasureEntry *entry;
auto iter = sizeCache_.find(key);
if (iter != sizeCache_.end()) {
entry = iter->second.get();
void TextDrawerAndroid::MeasureStringInternal(std::string_view str, float *w, float *h) {
float scaledSize = 14;
auto iter = fontMap_.find(fontHash_);
if (iter != fontMap_.end()) {
scaledSize = iter->second.size;
} else {
float scaledSize = 14;
auto iter = fontMap_.find(fontHash_);
if (iter != fontMap_.end()) {
scaledSize = iter->second.size;
} else {
ERROR_LOG(Log::G3D, "Missing font");
}
std::string text(str);
auto env = getEnv();
// Unfortunate that we can't create a jstr from a std::string_view directly.
jstring jstr = env->NewStringUTF(text.c_str());
uint32_t size = env->CallStaticIntMethod(cls_textRenderer, method_measureText, jstr, scaledSize);
env->DeleteLocalRef(jstr);
entry = new TextMeasureEntry();
entry->width = (size >> 16);
entry->height = (size & 0xFFFF);
sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry);
ERROR_LOG(Log::G3D, "Missing font");
}
entry->lastUsedFrame = frameCount_;
*w = entry->width * fontScaleX_ * dpiScale_;
*h = entry->height * fontScaleY_ * dpiScale_;
std::string text(str);
auto env = getEnv();
// Unfortunate that we can't create a jstr from a std::string_view directly.
jstring jstr = env->NewStringUTF(text.c_str());
uint32_t size = env->CallStaticIntMethod(cls_textRenderer, method_measureText, jstr, scaledSize);
env->DeleteLocalRef(jstr);
*w = (size >> 16);
*h = (size & 0xFFFF);
}
bool TextDrawerAndroid::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) {

View File

@ -21,10 +21,10 @@ public:
bool IsReady() const override;
uint32_t SetFont(const char *fontName, int size, int flags) override;
void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once.
void MeasureString(std::string_view str, float *w, float *h) override;
bool DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override;
protected:
void MeasureStringInternal(std::string_view str, float *w, float *h) override;
bool SupportsColorEmoji() const override { return true; }
void ClearFonts() override;

View File

@ -18,10 +18,10 @@ public:
uint32_t SetFont(const char *fontName, int size, int flags) override;
void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once.
void MeasureString(std::string_view str, float *w, float *h) override;
bool DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override;
protected:
void MeasureStringInternal(std::string_view str, float *w, float *h) override;
bool SupportsColorEmoji() const override { return true; }
void ClearFonts() override;

View File

@ -117,50 +117,35 @@ void TextDrawerCocoa::ClearFonts() {
fontMap_.clear();
}
void TextDrawerCocoa::MeasureString(std::string_view str, float *w, float *h) {
CacheKey key{ std::string(str), fontHash_ };
TextMeasureEntry *entry;
auto iter = sizeCache_.find(key);
if (iter != sizeCache_.end()) {
entry = iter->second.get();
} else {
// INFO_LOG(Log::System, "Measuring %.*s", (int)str.length(), str.data());
auto iter = fontMap_.find(fontHash_);
NSDictionary *attributes = nil;
if (iter != fontMap_.end()) {
attributes = iter->second->attributes;
}
std::vector<std::string_view> lines;
SplitString(str, '\n', lines);
int extW = 0, extH = 0;
for (auto &line : lines) {
NSString *string = [[NSString alloc] initWithBytes:line.data() length:line.size() encoding: NSUTF8StringEncoding];
NSAttributedString* as = [[NSAttributedString alloc] initWithString:string attributes:attributes];
CTLineRef ctline = CTLineCreateWithAttributedString((CFAttributedStringRef)as);
CGFloat ascent, descent, leading;
double fWidth = CTLineGetTypographicBounds(ctline, &ascent, &descent, &leading);
size_t width = (size_t)ceilf(fWidth);
size_t height = (size_t)ceilf(ascent + descent);
if (width > extW)
extW = width;
extH += height;
}
entry = new TextMeasureEntry();
entry->width = extW;
entry->height = extH;
sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry);
void TextDrawerCocoa::MeasureStringInternal(std::string_view str, float *w, float *h) {
// INFO_LOG(Log::System, "Measuring %.*s", (int)str.length(), str.data());
auto iter = fontMap_.find(fontHash_);
NSDictionary *attributes = nil;
if (iter != fontMap_.end()) {
attributes = iter->second->attributes;
}
entry->lastUsedFrame = frameCount_;
*w = entry->width * fontScaleX_ * dpiScale_;
*h = entry->height * fontScaleY_ * dpiScale_;
std::vector<std::string_view> lines;
SplitString(str, '\n', lines);
int extW = 0, extH = 0;
for (auto &line : lines) {
NSString *string = [[NSString alloc] initWithBytes:line.data() length:line.size() encoding: NSUTF8StringEncoding];
NSAttributedString* as = [[NSAttributedString alloc] initWithString:string attributes:attributes];
CTLineRef ctline = CTLineCreateWithAttributedString((CFAttributedStringRef)as);
CGFloat ascent, descent, leading;
double fWidth = CTLineGetTypographicBounds(ctline, &ascent, &descent, &leading);
size_t width = (size_t)ceilf(fWidth);
size_t height = (size_t)ceilf(ascent + descent);
if (width > extW)
extW = width;
extH += height;
}
*w = extW;
*h = extH;
}
bool TextDrawerCocoa::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) {

View File

@ -51,27 +51,13 @@ void TextDrawerQt::SetFont(uint32_t fontHandle) {
}
}
void TextDrawerQt::MeasureString(std::string_view str, float *w, float *h) {
CacheKey key{ std::string(str), fontHash_ };
void TextDrawerQt::MeasureStringInternal(std::string_view str, float *w, float *h) {
QFont* font = fontMap_.find(fontHash_)->second;
QFontMetrics fm(*font);
QSize size = fm.size(0, QString::fromUtf8(str.data(), str.length()));
TextMeasureEntry *entry;
auto iter = sizeCache_.find(key);
if (iter != sizeCache_.end()) {
entry = iter->second.get();
} else {
QFont* font = fontMap_.find(fontHash_)->second;
QFontMetrics fm(*font);
QSize size = fm.size(0, QString::fromUtf8(str.data(), str.length()));
entry = new TextMeasureEntry();
entry->width = size.width();
entry->height = size.height();
sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry);
}
entry->lastUsedFrame = frameCount_;
*w = entry->width * fontScaleX_ * dpiScale_;
*h = entry->height * fontScaleY_ * dpiScale_;
*w = size.width();
*h = size.height();
}
bool TextDrawerQt::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) {

View File

@ -16,10 +16,10 @@ public:
uint32_t SetFont(const char *fontName, int size, int flags) override;
void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once.
void MeasureString(std::string_view str, float *w, float *h) override;
bool DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override;
protected:
void MeasureStringInternal(std::string_view str, float *w, float *h) override;
bool SupportsColorEmoji() const override { return false; }
void ClearFonts() override;

View File

@ -186,15 +186,13 @@ void TextDrawerSDL::PrepareFallbackFonts(std::string_view locale) {
#endif
}
uint32_t TextDrawerSDL::CheckMissingGlyph(const std::string& text) {
uint32_t TextDrawerSDL::CheckMissingGlyph(std::string_view text) {
TTF_Font *font = fontMap_.find(fontHash_)->second;
UTF8 utf8Decoded(text.c_str());
UTF8 utf8Decoded(text);
uint32_t missingGlyph = 0;
for (int i = 0; i < text.length(); ) {
while (!utf8Decoded.end()) {
uint32_t glyph = utf8Decoded.next();
i = utf8Decoded.byteIndex();
if (!TTF_GlyphIsProvided32(font, glyph)) {
missingGlyph = glyph;
break;
@ -279,39 +277,28 @@ void TextDrawerSDL::SetFont(uint32_t fontHandle) {
}
}
void TextDrawerSDL::MeasureString(std::string_view str, float *w, float *h) {
CacheKey key{ std::string(str), fontHash_ };
void TextDrawerSDL::MeasureStringInternal(std::string_view str, float *w, float *h) {
TTF_Font *font = fontMap_.find(fontHash_)->second;
int ptSize = TTF_FontHeight(font) / 1.35;
TextMeasureEntry *entry;
auto iter = sizeCache_.find(key);
if (iter != sizeCache_.end()) {
entry = iter->second.get();
} else {
TTF_Font *font = fontMap_.find(fontHash_)->second;
int ptSize = TTF_FontHeight(font) / 1.35;
uint32_t missingGlyph = CheckMissingGlyph(key.text);
uint32_t missingGlyph = CheckMissingGlyph(str);
if (missingGlyph) {
int fallbackFont = FindFallbackFonts(missingGlyph, ptSize);
if (fallbackFont >= 0) {
font = fallbackFonts_[fallbackFont];
}
if (missingGlyph) {
int fallbackFont = FindFallbackFonts(missingGlyph, ptSize);
if (fallbackFont >= 0) {
font = fallbackFonts_[fallbackFont];
}
int width = 0;
int height = 0;
TTF_SizeUTF8(font, key.text.c_str(), &width, &height);
entry = new TextMeasureEntry();
entry->width = width;
entry->height = height;
sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry);
}
entry->lastUsedFrame = frameCount_;
*w = entry->width * fontScaleX_ * dpiScale_;
*h = entry->height * fontScaleY_ * dpiScale_;
int width = 0;
int height = 0;
// Unfortunately we need to zero-terminate here.
std::string text(str);
TTF_SizeUTF8(font, text.c_str(), &width, &height);
*w = width;
*h = height;
}
bool TextDrawerSDL::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) {

View File

@ -19,16 +19,17 @@ public:
uint32_t SetFont(const char *fontName, int size, int flags) override;
void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once.
void MeasureString(std::string_view str, float *w, float *h) override;
bool DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override;
protected:
void MeasureStringInternal(std::string_view str, float *w, float *h) override;
bool SupportsColorEmoji() const override { return false; }
void ClearFonts() override;
private:
void PrepareFallbackFonts(std::string_view locale);
uint32_t CheckMissingGlyph(const std::string& text);
uint32_t CheckMissingGlyph(std::string_view text);
int FindFallbackFonts(uint32_t missingGlyph, int ptSize);
std::map<uint32_t, _TTF_Font *> fontMap_;

View File

@ -204,48 +204,34 @@ void TextDrawerUWP::SetFont(uint32_t fontHandle) {
}
}
void TextDrawerUWP::MeasureString(std::string_view str, float *w, float *h) {
CacheKey key{ std::string(str), fontHash_ };
TextMeasureEntry *entry;
auto iter = sizeCache_.find(key);
if (iter != sizeCache_.end()) {
entry = iter->second.get();
} else {
IDWriteTextFormat* format = nullptr;
auto iter = fontMap_.find(fontHash_);
if (iter != fontMap_.end()) {
format = iter->second->textFmt;
}
if (!format) return;
std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(std::string(str), "\n", "\r\n"));
format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
IDWriteTextLayout* layout;
m_dwriteFactory->CreateTextLayout(
(LPWSTR)wstr.c_str(),
(int)wstr.size(),
format,
MAX_TEXT_WIDTH,
MAX_TEXT_HEIGHT,
&layout
);
DWRITE_TEXT_METRICS metrics;
layout->GetMetrics(&metrics);
layout->Release();
entry = new TextMeasureEntry();
entry->width = (int)(metrics.width + 1.0f);
entry->height = (int)(metrics.height + 1.0f);
sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry);
void TextDrawerUWP::MeasureStringInternal(std::string_view str, float *w, float *h) {
IDWriteTextFormat* format = nullptr;
auto iter = fontMap_.find(fontHash_);
if (iter != fontMap_.end()) {
format = iter->second->textFmt;
}
if (!format) return;
entry->lastUsedFrame = frameCount_;
*w = entry->width * fontScaleX_ * dpiScale_;
*h = entry->height * fontScaleY_ * dpiScale_;
std::wstring wstr = ConvertUTF8ToWString(ReplaceAll(std::string(str), "\n", "\r\n"));
format->SetTextAlignment(DWRITE_TEXT_ALIGNMENT_LEADING);
IDWriteTextLayout* layout;
m_dwriteFactory->CreateTextLayout(
(LPWSTR)wstr.c_str(),
(int)wstr.size(),
format,
MAX_TEXT_WIDTH,
MAX_TEXT_HEIGHT,
&layout
);
DWRITE_TEXT_METRICS metrics;
layout->GetMetrics(&metrics);
layout->Release();
*w = (int)(metrics.width + 1.0f);
*h = (int)(metrics.height + 1.0f);
}
bool TextDrawerUWP::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) {

View File

@ -21,10 +21,10 @@ public:
uint32_t SetFont(const char *fontName, int size, int flags) override;
void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once.
void MeasureString(std::string_view str, float *w, float *h) override;
bool DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override;
protected:
void MeasureStringInternal(std::string_view str, float *w, float *h) override;
bool SupportsColorEmoji() const override { return true; }
void ClearFonts() override;

View File

@ -120,46 +120,30 @@ void TextDrawerWin32::SetFont(uint32_t fontHandle) {
}
}
void TextDrawerWin32::MeasureString(std::string_view str, float *w, float *h) {
CacheKey key{ std::string(str), fontHash_ };
TextMeasureEntry *entry;
auto iter = sizeCache_.find(key);
if (iter != sizeCache_.end()) {
entry = iter->second.get();
} else {
auto iter = fontMap_.find(fontHash_);
if (iter != fontMap_.end()) {
SelectObject(ctx_->hDC, iter->second->hFont);
}
std::string toMeasure(str);
std::vector<std::string_view> lines;
SplitString(toMeasure, '\n', lines);
int extW = 0, extH = 0;
for (auto &line : lines) {
SIZE size;
std::wstring wstr = ConvertUTF8ToWString(line);
GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size);
if (size.cx > extW)
extW = size.cx;
extH += size.cy;
}
entry = new TextMeasureEntry();
entry->width = extW;
entry->height = extH;
// Hm, use the old calculation?
// int h = i == lines.size() - 1 ? entry->height : metrics.tmHeight + metrics.tmExternalLeading;
sizeCache_[key] = std::unique_ptr<TextMeasureEntry>(entry);
void TextDrawerWin32::MeasureStringInternal(std::string_view str, float *w, float *h) {
auto iter = fontMap_.find(fontHash_);
if (iter != fontMap_.end()) {
SelectObject(ctx_->hDC, iter->second->hFont);
}
entry->lastUsedFrame = frameCount_;
*w = entry->width * fontScaleX_ * dpiScale_;
*h = entry->height * fontScaleY_ * dpiScale_;
std::string toMeasure(str);
std::vector<std::string_view> lines;
SplitString(toMeasure, '\n', lines);
int extW = 0, extH = 0;
for (auto &line : lines) {
SIZE size;
std::wstring wstr = ConvertUTF8ToWString(line);
GetTextExtentPoint32(ctx_->hDC, wstr.c_str(), (int)wstr.size(), &size);
if (size.cx > extW)
extW = size.cx;
extH += size.cy;
}
*w = extW;
*h = extH;
}
bool TextDrawerWin32::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) {

View File

@ -21,10 +21,10 @@ public:
uint32_t SetFont(const char *fontName, int size, int flags) override;
void SetFont(uint32_t fontHandle) override; // Shortcut once you've set the font once.
void MeasureString(std::string_view str, float *w, float *h) override;
bool DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextStringEntry &entry, Draw::DataFormat texFormat, std::string_view str, int align, bool fullColor) override;
protected:
void MeasureStringInternal(std::string_view str, float *w, float *h) override;
bool SupportsColorEmoji() const override { return false; }
void ClearFonts() override;

View File

@ -571,7 +571,7 @@ public:
TaskPriority Priority() const override {
return TaskPriority::NORMAL;
}
virtual void Run() {
virtual void Run() override {
mixer_->LoadSamplesOnThread();
}
private:

@ -1 +1 @@
Subproject commit 563230b1c249774b4852c944dc7cdcb952c9e8e8
Subproject commit 32917bdddf4982e62047862c6633e7671aaaf2cb