Add mesh caching for unchanging text labels

This commit is contained in:
vottozi 2026-02-12 13:23:16 +06:00
parent 4eda57b4e4
commit b56fafa0e0
3 changed files with 103 additions and 62 deletions

View File

@ -208,7 +208,9 @@ namespace ZL
// Можно делать масштаб по дальности: чем дальше — тем меньше.
// depth в NDC: ближе к -1 (near) и к 1 (far). Стабильнее считать по расстоянию:
float dist = (Environment::shipState.position - boxWorld).norm();
float scale = std::clamp(120.0f / (dist + 1.0f), 0.6f, 1.2f);
float scaleRaw = 120.0f / (dist + 1.0f);
float scale = std::round(scaleRaw * 10.0f) / 10.0f;
scale = std::clamp(scale, 0.6f, 1.2f);
textRenderer->drawText(boxLabels[i], uiX, uiY, scale, /*centered*/true);
}
@ -465,9 +467,6 @@ namespace ZL
boxAlive.resize(boxCoordsArr.size(), true);
ZL::CheckGlError();
textRenderer = std::make_unique<ZL::TextRenderer>();
textRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 32, CONST_ZIP_FILE);
ZL::CheckGlError();
boxLabels.clear();
boxLabels.reserve(boxCoordsArr.size());
for (size_t i = 0; i < boxCoordsArr.size(); ++i) {
@ -480,6 +479,14 @@ namespace ZL
}
renderer.InitOpenGL();
// TextRenderer создаём/инициализируем ПОСЛЕ инициализации OpenGL
textRenderer = std::make_unique<ZL::TextRenderer>();
if (!textRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 32, CONST_ZIP_FILE)) {
std::cerr << "Failed to init TextRenderer\n";
}
ZL::CheckGlError();
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@ -1316,6 +1323,14 @@ namespace ZL
if (event.type == SDL_QUIT) {
Environment::exitGameLoop = true;
}
#if SDL_VERSION_ATLEAST(2,0,5)
else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
// Обновляем размеры и сбрасываем кеш текстов, т.к. меши хранятся в пикселях
Environment::width = event.window.data1;
Environment::height = event.window.data2;
if (textRenderer) textRenderer->ClearCache();
}
#endif
#ifdef __ANDROID__
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) {
Environment::exitGameLoop = true;

View File

@ -62,6 +62,11 @@ bool TextRenderer::init(Renderer& renderer, const std::string& ttfPath, int pixe
return true;
}
void TextRenderer::ClearCache()
{
cache.clear();
}
bool TextRenderer::loadGlyphs(const std::string& ttfPath, int pixelSize, const std::string& zipfilename)
{
// 1. Загружаем сырые данные из ZIP
@ -296,27 +301,22 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
{
if (!r || text.empty() || !atlasTexture) return;
// 1. Считаем ширину для центрирования
float totalW = 0.0f;
if (centered) {
for (char ch : text) {
auto it = glyphs.find(ch);
if (it == glyphs.end()) continue;
totalW += (it->second.advance >> 6) * scale;
}
x -= totalW * 0.5f;
}
// формируем ключ кеша
std::string key = text + "|" + std::to_string(scale) + "|" + (centered ? "1" : "0");
auto itCache = cache.find(key);
// 2. Подготовка данных (аналог CreateRect2D, но для всей строки)
if (itCache == cache.end()) {
VertexDataStruct textData;
float penX = x;
float penY = y;
float penX = 0.0f;
float penY = 0.0f;
float totalW = 0.0f;
float maxH = 0.0f;
for (char ch : text) {
auto it = glyphs.find(ch);
if (it == glyphs.end()) continue;
const GlyphInfo& g = it->second;
auto git = glyphs.find(ch);
if (git == glyphs.end()) continue;
const GlyphInfo& g = git->second;
float xpos = penX + g.bearing.x() * scale;
float ypos = penY - (g.size.y() - g.bearing.y()) * scale;
@ -324,7 +324,6 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
float h = g.size.y() * scale;
// Добавляем 2 треугольника (6 вершин) для текущего символа
// Координаты Z ставим 0.0f, так как это 2D
textData.PositionData.push_back({ xpos, ypos + h, 0.0f });
textData.PositionData.push_back({ xpos, ypos, 0.0f });
textData.PositionData.push_back({ xpos + w, ypos, 0.0f });
@ -338,15 +337,6 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
float u1 = u0 + g.uvSize.x();
float v1 = v0 + g.uvSize.y();
//// UV-координаты (здесь есть нюанс с атласом, ниже поясню)
//textData.TexCoordData.push_back({ 0.0f, 0.0f });
//textData.TexCoordData.push_back({ 0.0f, 1.0f });
//textData.TexCoordData.push_back({ 1.0f, 1.0f });
//textData.TexCoordData.push_back({ 0.0f, 0.0f });
//textData.TexCoordData.push_back({ 1.0f, 1.0f });
//textData.TexCoordData.push_back({ 1.0f, 0.0f });
// Соответствие прежней системе UV: (0,0)=верх-лево, (0,1)=ниж-лево
textData.TexCoordData.push_back({ u0, v0 });
textData.TexCoordData.push_back({ u0, v1 });
textData.TexCoordData.push_back({ u1, v1 });
@ -355,13 +345,35 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
textData.TexCoordData.push_back({ u1, v0 });
penX += (g.advance >> 6) * scale;
totalW = penX;
maxH = max(maxH, h);
}
// Сохраняем в кеш
CachedText ct;
ct.width = totalW;
ct.height = maxH;
ct.mesh.AssignFrom(textData);
auto res = cache.emplace(key, std::move(ct));
itCache = res.first;
}
// Используем кешированный меш
CachedText& cached = itCache->second;
// Вычисляем смещение для проекции (оставляем Y как есть)
float tx = x;
if (centered) {
tx = x - cached.width * 0.5f;
}
float ty = y;
// 3. Обновляем VBO через наш стандартный механизм
// Примечание: для текста лучше использовать GL_DYNAMIC_DRAW,
// но RefreshVBO сейчас жестко зашит на GL_STATIC_DRAW.
// Для UI это обычно не критично, если строк не тысячи.
textMesh.AssignFrom(textData);
// textMesh.AssignFrom(textData);
// 4. Рендеринг
r->shaderManager.PushShader(shaderName);
@ -372,8 +384,9 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
Eigen::Matrix4f proj = Eigen::Matrix4f::Identity();
proj(0, 0) = 2.0f / W;
proj(1, 1) = 2.0f / H;
proj(0, 3) = -1.0f;
proj(1, 3) = -1.0f;
// Сдвигаем проекцию так, чтобы локальные координаты меша (pen-origin=0,0) оказались в (tx,ty)
proj(0, 3) = -1.0f + 2.0f * (tx) / W;
proj(1, 3) = -1.0f + 2.0f * (ty) / H;
r->RenderUniformMatrix4fv("uProjection", false, proj.data());
r->RenderUniform1i("uText", 0);
@ -402,7 +415,7 @@ void TextRenderer::drawText(const std::string& text, float x, float y, float sca
// glDrawArrays(GL_TRIANGLES, 0, 6);
//}
r->DrawVertexRenderStruct(textMesh);
r->DrawVertexRenderStruct(cached.mesh);
r->DisableVertexAttribArray("vPosition");
r->DisableVertexAttribArray("vTexCoord");

View File

@ -29,6 +29,9 @@ public:
bool init(Renderer& renderer, const std::string& ttfPath, int pixelSize, const std::string& zipfilename);
void drawText(const std::string& text, float x, float y, float scale, bool centered, std::array<float, 4> color = { 1.f,1.f,1.f,1.f });
// Clear cached meshes (call on window resize / DPI change)
void ClearCache();
private:
bool loadGlyphs(const std::string& ttfPath, int pixelSize, const std::string& zipfilename);
@ -48,6 +51,16 @@ private:
VertexRenderStruct textMesh;
std::string shaderName = "text2d";
// caching for static texts
struct CachedText {
VertexRenderStruct mesh;
float width = 0.f; // in pixels, total advance
float height = 0.f; // optional, not currently used
};
// key: text + "|" + scale + "|" + centered
std::unordered_map<std::string, CachedText> cache;
};
} // namespace ZL