mirror of
https://github.com/InstanceBrick/nightcap.git
synced 2026-10-17 19:00:48 +02:00
perf(input): reuse the TextBox overlay fonts across rasters
Every keystroke re-ran TTF_Init, a fontconfig sort, six TTF_OpenFont calls and TTF_Quit on the render thread. Hold the opened fonts on the overlay and reopen them only when the Roblox font or the point size changes, keeping the current set when a reopen fails. Measured over 100 rasters, 1.59 ms to 0.12 ms per keystroke.
This commit is contained in:
1 parent
7dd32335ca
commit
02108a9bb1
3 files changed
+92
-24
No files matched your search
@@ -11,6 +11,8 @@
|
||||
#include "mocktail/status.h"
|
||||
#include "runtime/roblox_text_display_state.h"
|
||||
|
||||
struct TTF_Font;
|
||||
|
||||
namespace mocktail {
|
||||
namespace runtime {
|
||||
|
||||
@@ -39,6 +41,8 @@ class RobloxTextSurfaceOverlay final {
|
||||
const RobloxTextDisplayUpdate& update);
|
||||
void ApplyUpdate(const RobloxTextDisplayUpdate& update);
|
||||
Status RasterizeLocked();
|
||||
Status OpenFontsLocked(std::int32_t font, float point_size);
|
||||
void CloseFontsLocked();
|
||||
void ClearFrameLocked();
|
||||
void RecordFailureLocked(Status status);
|
||||
|
||||
@@ -46,10 +50,14 @@ class RobloxTextSurfaceOverlay final {
|
||||
RobloxTextDisplayState state_;
|
||||
RobloxTextOverlayViewport viewport_;
|
||||
std::vector<std::uint8_t> rgba_;
|
||||
std::vector<TTF_Font*> fonts_;
|
||||
std::int32_t fonts_font_ = 0;
|
||||
float fonts_point_size_ = 0.0F;
|
||||
std::uint64_t state_revision_ = 0;
|
||||
std::uint64_t raster_revision_ = 0;
|
||||
Status failure_;
|
||||
bool initialized_ = false;
|
||||
bool ttf_initialized_ = false;
|
||||
bool failure_logged_ = false;
|
||||
bool ready_logged_ = false;
|
||||
};
|
||||
|
||||
@@ -301,6 +301,7 @@ Status RobloxTextSurfaceOverlay::Shutdown() {
|
||||
}
|
||||
state_.Reset();
|
||||
ClearFrameLocked();
|
||||
CloseFontsLocked();
|
||||
viewport_ = {};
|
||||
initialized_ = false;
|
||||
if (owned_active_overlay) {
|
||||
@@ -445,10 +446,6 @@ Status RobloxTextSurfaceOverlay::RasterizeLocked() {
|
||||
const bool wrapped_layout =
|
||||
presentation.multiline || presentation.text_wrapped;
|
||||
|
||||
if (!TTF_Init()) {
|
||||
SecureClear(&candidate);
|
||||
return RasterizationFailure("unable to initialize SDL3_ttf");
|
||||
}
|
||||
const RobloxTextFontSelection font_selection =
|
||||
ResolveRobloxTextFont(presentation.font);
|
||||
const float native_point_size =
|
||||
@@ -458,23 +455,12 @@ Status RobloxTextSurfaceOverlay::RasterizeLocked() {
|
||||
? std::clamp(native_point_size * font_selection.size_scale, 1.0F,
|
||||
512.0F)
|
||||
: std::clamp(static_cast<float>(height) * 0.38F, 12.0F, 28.0F);
|
||||
const std::vector<std::string> font_files = ResolveFontFiles(font_selection);
|
||||
std::vector<TTF_Font*> fonts;
|
||||
fonts.reserve(font_files.size());
|
||||
for (const std::string& file : font_files) {
|
||||
TTF_Font* font = TTF_OpenFont(file.c_str(), point_size);
|
||||
if (font != nullptr) {
|
||||
fonts.push_back(font);
|
||||
}
|
||||
}
|
||||
if (fonts.empty()) {
|
||||
TTF_Quit();
|
||||
if (Status status = OpenFontsLocked(presentation.font, point_size);
|
||||
!status.ok()) {
|
||||
SecureClear(&candidate);
|
||||
return RasterizationFailure("unable to open a Roblox or fallback font");
|
||||
}
|
||||
for (std::size_t index = 1; index < fonts.size(); ++index) {
|
||||
(void)TTF_AddFallbackFont(fonts.front(), fonts[index]);
|
||||
return status;
|
||||
}
|
||||
const std::vector<TTF_Font*>& fonts = fonts_;
|
||||
if (wrapped_layout) {
|
||||
TTF_HorizontalAlignment alignment = TTF_HORIZONTAL_ALIGN_LEFT;
|
||||
if (presentation.x_alignment == 1) {
|
||||
@@ -629,11 +615,6 @@ Status RobloxTextSurfaceOverlay::RasterizeLocked() {
|
||||
if (layout != nullptr) {
|
||||
TTF_DestroyText(layout);
|
||||
}
|
||||
TTF_ClearFallbackFonts(fonts.front());
|
||||
for (auto iterator = fonts.rbegin(); iterator != fonts.rend(); ++iterator) {
|
||||
TTF_CloseFont(*iterator);
|
||||
}
|
||||
TTF_Quit();
|
||||
|
||||
if (!status.ok()) {
|
||||
SecureClear(&candidate);
|
||||
@@ -651,6 +632,58 @@ Status RobloxTextSurfaceOverlay::RasterizeLocked() {
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
Status RobloxTextSurfaceOverlay::OpenFontsLocked(std::int32_t font,
|
||||
float point_size) {
|
||||
if (!fonts_.empty() && fonts_font_ == font &&
|
||||
fonts_point_size_ == point_size) {
|
||||
return Status::Ok();
|
||||
}
|
||||
if (!TTF_Init()) {
|
||||
return RasterizationFailure("unable to initialize SDL3_ttf");
|
||||
}
|
||||
const std::vector<std::string> files =
|
||||
ResolveFontFiles(ResolveRobloxTextFont(font));
|
||||
std::vector<TTF_Font*> opened;
|
||||
opened.reserve(files.size());
|
||||
for (const std::string& file : files) {
|
||||
TTF_Font* candidate = TTF_OpenFont(file.c_str(), point_size);
|
||||
if (candidate != nullptr) {
|
||||
opened.push_back(candidate);
|
||||
}
|
||||
}
|
||||
// A failed reopen leaves the fonts already rendering in place.
|
||||
if (opened.empty()) {
|
||||
TTF_Quit();
|
||||
return RasterizationFailure("unable to open a Roblox or fallback font");
|
||||
}
|
||||
for (std::size_t index = 1; index < opened.size(); ++index) {
|
||||
(void)TTF_AddFallbackFont(opened.front(), opened[index]);
|
||||
}
|
||||
CloseFontsLocked();
|
||||
fonts_ = std::move(opened);
|
||||
ttf_initialized_ = true;
|
||||
fonts_font_ = font;
|
||||
fonts_point_size_ = point_size;
|
||||
return Status::Ok();
|
||||
}
|
||||
|
||||
void RobloxTextSurfaceOverlay::CloseFontsLocked() {
|
||||
if (!fonts_.empty()) {
|
||||
TTF_ClearFallbackFonts(fonts_.front());
|
||||
for (auto iterator = fonts_.rbegin(); iterator != fonts_.rend();
|
||||
++iterator) {
|
||||
TTF_CloseFont(*iterator);
|
||||
}
|
||||
fonts_.clear();
|
||||
}
|
||||
fonts_font_ = 0;
|
||||
fonts_point_size_ = 0.0F;
|
||||
if (ttf_initialized_) {
|
||||
TTF_Quit();
|
||||
ttf_initialized_ = false;
|
||||
}
|
||||
}
|
||||
|
||||
void RobloxTextSurfaceOverlay::ClearFrameLocked() {
|
||||
SecureClear(&rgba_);
|
||||
raster_revision_ = 0;
|
||||
|
||||
@@ -594,6 +594,33 @@ TEST(RobloxTextDisplayStateTest, AdvancesCaretForTrailingSpace) {
|
||||
EXPECT_TRUE(overlay.Shutdown().ok());
|
||||
}
|
||||
|
||||
TEST(RobloxTextDisplayStateTest, ReopensFontsAfterPointSizeChange) {
|
||||
RobloxTextSurfaceOverlay overlay;
|
||||
ASSERT_TRUE(overlay.Initialize({800, 600}).ok());
|
||||
RobloxTextDisplaySink sink = overlay.sink();
|
||||
const std::string text = "size";
|
||||
const auto text_width = [&](float font_size, RobloxTextDisplayEvent event) {
|
||||
RobloxTextDisplayUpdate update = Show(1, text, 4);
|
||||
update.event = event;
|
||||
update.area_height = 64;
|
||||
update.font_size = font_size;
|
||||
sink.update(sink.context, update);
|
||||
MocktailTextOverlayFrameInfo frame;
|
||||
EXPECT_TRUE(overlay.QueryFrame(&frame));
|
||||
std::vector<std::uint8_t> rgba(frame.rgba_bytes);
|
||||
EXPECT_TRUE(overlay.CopyFrame(frame.revision, rgba.data(), rgba.size()));
|
||||
const auto bounds = FindAlphaBounds(frame, rgba);
|
||||
EXPECT_TRUE(bounds.valid());
|
||||
return bounds.valid() ? bounds.maximum_x - bounds.minimum_x : 0;
|
||||
};
|
||||
|
||||
const int small = text_width(14.0F, RobloxTextDisplayEvent::kShow);
|
||||
const int large = text_width(28.0F, RobloxTextDisplayEvent::kUpdate);
|
||||
|
||||
EXPECT_GT(large, small);
|
||||
EXPECT_TRUE(overlay.Shutdown().ok());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace runtime
|
||||
} // namespace mocktail
|
||||
Reference in new issue
Block a user