From b56fafa0e0fdfbb5dea34e16edbd60502417168d Mon Sep 17 00:00:00 2001 From: vottozi Date: Thu, 12 Feb 2026 13:23:16 +0600 Subject: [PATCH] Add mesh caching for unchanging text labels --- src/Game.cpp | 23 +++++-- src/render/TextRenderer.cpp | 129 ++++++++++++++++++++---------------- src/render/TextRenderer.h | 13 ++++ 3 files changed, 103 insertions(+), 62 deletions(-) diff --git a/src/Game.cpp b/src/Game.cpp index 6b39c63..32b95d7 100644 --- a/src/Game.cpp +++ b/src/Game.cpp @@ -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(); - 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(); + 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; diff --git a/src/render/TextRenderer.cpp b/src/render/TextRenderer.cpp index 7cd52c4..4493818 100644 --- a/src/render/TextRenderer.cpp +++ b/src/render/TextRenderer.cpp @@ -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,72 +301,79 @@ 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) { + // формируем ключ кеша + std::string key = text + "|" + std::to_string(scale) + "|" + (centered ? "1" : "0"); + auto itCache = cache.find(key); + + if (itCache == cache.end()) { + VertexDataStruct textData; + 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; - totalW += (it->second.advance >> 6) * scale; + 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; + float w = g.size.x() * scale; + float h = g.size.y() * scale; + + // Добавляем 2 треугольника (6 вершин) для текущего символа + 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 }); + textData.PositionData.push_back({ xpos, ypos + h, 0.0f }); + textData.PositionData.push_back({ xpos + w, ypos, 0.0f }); + textData.PositionData.push_back({ xpos + w, ypos + h, 0.0f }); + + // TexCoords — на основе UV позиции и размера в атласе (uv указывает на верх-лево) + float u0 = g.uv.x(); + float v0 = g.uv.y(); + float u1 = u0 + g.uvSize.x(); + float v1 = v0 + g.uvSize.y(); + + textData.TexCoordData.push_back({ u0, v0 }); + textData.TexCoordData.push_back({ u0, v1 }); + textData.TexCoordData.push_back({ u1, v1 }); + textData.TexCoordData.push_back({ u0, v0 }); + textData.TexCoordData.push_back({ u1, v1 }); + textData.TexCoordData.push_back({ u1, v0 }); + + penX += (g.advance >> 6) * scale; + totalW = penX; + maxH = max(maxH, h); } - x -= totalW * 0.5f; + + // Сохраняем в кеш + CachedText ct; + ct.width = totalW; + ct.height = maxH; + ct.mesh.AssignFrom(textData); + + auto res = cache.emplace(key, std::move(ct)); + itCache = res.first; } - // 2. Подготовка данных (аналог CreateRect2D, но для всей строки) - VertexDataStruct textData; - float penX = x; - float penY = y; + // Используем кешированный меш + CachedText& cached = itCache->second; - for (char ch : text) { - auto it = glyphs.find(ch); - if (it == glyphs.end()) continue; - - const GlyphInfo& g = it->second; - - float xpos = penX + g.bearing.x() * scale; - float ypos = penY - (g.size.y() - g.bearing.y()) * scale; - float w = g.size.x() * scale; - 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 }); - textData.PositionData.push_back({ xpos, ypos + h, 0.0f }); - textData.PositionData.push_back({ xpos + w, ypos, 0.0f }); - textData.PositionData.push_back({ xpos + w, ypos + h, 0.0f }); - - // TexCoords — на основе UV позиции и размера в атласе (uv указывает на верх-лево) - float u0 = g.uv.x(); - float v0 = g.uv.y(); - 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 }); - textData.TexCoordData.push_back({ u0, v0 }); - textData.TexCoordData.push_back({ u1, v1 }); - textData.TexCoordData.push_back({ u1, v0 }); - - penX += (g.advance >> 6) * scale; + // Вычисляем смещение для проекции (оставляем 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"); diff --git a/src/render/TextRenderer.h b/src/render/TextRenderer.h index e55e80e..d7a138a 100644 --- a/src/render/TextRenderer.h +++ b/src/render/TextRenderer.h @@ -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 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 cache; }; } // namespace ZL \ No newline at end of file