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). Стабильнее считать по расстоянию: // depth в NDC: ближе к -1 (near) и к 1 (far). Стабильнее считать по расстоянию:
float dist = (Environment::shipState.position - boxWorld).norm(); 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); textRenderer->drawText(boxLabels[i], uiX, uiY, scale, /*centered*/true);
} }
@ -465,9 +467,6 @@ namespace ZL
boxAlive.resize(boxCoordsArr.size(), true); boxAlive.resize(boxCoordsArr.size(), true);
ZL::CheckGlError(); ZL::CheckGlError();
textRenderer = std::make_unique<ZL::TextRenderer>();
textRenderer->init(renderer, "resources/fonts/DroidSans.ttf", 32, CONST_ZIP_FILE);
ZL::CheckGlError();
boxLabels.clear(); boxLabels.clear();
boxLabels.reserve(boxCoordsArr.size()); boxLabels.reserve(boxCoordsArr.size());
for (size_t i = 0; i < boxCoordsArr.size(); ++i) { for (size_t i = 0; i < boxCoordsArr.size(); ++i) {
@ -480,6 +479,14 @@ namespace ZL
} }
renderer.InitOpenGL(); 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); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@ -1316,6 +1323,14 @@ namespace ZL
if (event.type == SDL_QUIT) { if (event.type == SDL_QUIT) {
Environment::exitGameLoop = true; 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__ #ifdef __ANDROID__
if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) { if (event.type == SDL_KEYDOWN && event.key.keysym.sym == SDLK_AC_BACK) {
Environment::exitGameLoop = true; Environment::exitGameLoop = true;

View File

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

View File

@ -29,6 +29,9 @@ public:
bool init(Renderer& renderer, const std::string& ttfPath, int pixelSize, const std::string& zipfilename); 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 }); 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: private:
bool loadGlyphs(const std::string& ttfPath, int pixelSize, const std::string& zipfilename); bool loadGlyphs(const std::string& ttfPath, int pixelSize, const std::string& zipfilename);
@ -48,6 +51,16 @@ private:
VertexRenderStruct textMesh; VertexRenderStruct textMesh;
std::string shaderName = "text2d"; 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 } // namespace ZL