LCOV - code coverage report
Current view: top level - source/graphics - TextRenderer.cpp (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 1 181 0.6 %
Date: 2023-01-19 00:18:29 Functions: 2 18 11.1 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #include "precompiled.h"
      19             : 
      20             : #include "TextRenderer.h"
      21             : 
      22             : #include "graphics/Font.h"
      23             : #include "graphics/FontManager.h"
      24             : #include "graphics/ShaderProgram.h"
      25             : #include "graphics/TextureManager.h"
      26             : #include "maths/Matrix3D.h"
      27             : #include "ps/CStrIntern.h"
      28             : #include "ps/CStrInternStatic.h"
      29             : #include "renderer/Renderer.h"
      30             : 
      31             : #include <errno.h>
      32             : 
      33             : namespace
      34             : {
      35             : 
      36             : // We can't draw chars more than vertices, currently we use 4 vertices per char.
      37             : constexpr size_t MAX_CHAR_COUNT_PER_BATCH = 65536 / 4;
      38             : 
      39             : } // anonymous namespace
      40             : 
      41           0 : CTextRenderer::CTextRenderer()
      42             : {
      43           0 :     ResetTranslate();
      44           0 :     SetCurrentColor(CColor(1.0f, 1.0f, 1.0f, 1.0f));
      45           0 :     SetCurrentFont(str_sans_10);
      46           0 : }
      47             : 
      48           0 : void CTextRenderer::ResetTranslate(const CVector2D& translate)
      49             : {
      50           0 :     m_Translate = translate;
      51           0 :     m_Dirty = true;
      52           0 : }
      53             : 
      54           0 : void CTextRenderer::Translate(float x, float y)
      55             : {
      56           0 :     m_Translate += CVector2D{x, y};
      57           0 :     m_Dirty = true;
      58           0 : }
      59             : 
      60           0 : void CTextRenderer::SetClippingRect(const CRect& rect)
      61             : {
      62           0 :     m_Clipping = rect;
      63           0 : }
      64             : 
      65           0 : void CTextRenderer::SetCurrentColor(const CColor& color)
      66             : {
      67           0 :     if (m_Color != color)
      68             :     {
      69           0 :         m_Color = color;
      70           0 :         m_Dirty = true;
      71             :     }
      72           0 : }
      73             : 
      74           0 : void CTextRenderer::SetCurrentFont(CStrIntern font)
      75             : {
      76           0 :     if (font != m_FontName)
      77             :     {
      78           0 :         m_FontName = font;
      79           0 :         m_Font = g_Renderer.GetFontManager().LoadFont(font);
      80           0 :         m_Dirty = true;
      81             :     }
      82           0 : }
      83             : 
      84           0 : void CTextRenderer::PrintfAdvance(const wchar_t* fmt, ...)
      85             : {
      86           0 :     wchar_t buf[1024] = {0};
      87             : 
      88             :     va_list args;
      89           0 :     va_start(args, fmt);
      90           0 :     int ret = vswprintf(buf, ARRAY_SIZE(buf)-1, fmt, args);
      91           0 :     va_end(args);
      92             : 
      93           0 :     if (ret < 0)
      94           0 :         debug_printf("CTextRenderer::Printf vswprintf failed (buffer size exceeded?) - return value %d, errno %d\n", ret, errno);
      95             : 
      96           0 :     PutAdvance(buf);
      97           0 : }
      98             : 
      99             : 
     100           0 : void CTextRenderer::PrintfAt(float x, float y, const wchar_t* fmt, ...)
     101             : {
     102           0 :     wchar_t buf[1024] = {0};
     103             : 
     104             :     va_list args;
     105           0 :     va_start(args, fmt);
     106           0 :     int ret = vswprintf(buf, ARRAY_SIZE(buf)-1, fmt, args);
     107           0 :     va_end(args);
     108             : 
     109           0 :     if (ret < 0)
     110           0 :         debug_printf("CTextRenderer::PrintfAt vswprintf failed (buffer size exceeded?) - return value %d, errno %d\n", ret, errno);
     111             : 
     112           0 :     Put(x, y, buf);
     113           0 : }
     114             : 
     115           0 : void CTextRenderer::PutAdvance(const wchar_t* buf)
     116             : {
     117           0 :     Put(0.0f, 0.0f, buf);
     118             : 
     119             :     int w, h;
     120           0 :     m_Font->CalculateStringSize(buf, w, h);
     121           0 :     Translate((float)w, 0.0f);
     122           0 : }
     123             : 
     124           0 : void CTextRenderer::Put(float x, float y, const wchar_t* buf)
     125             : {
     126           0 :     if (buf[0] == 0)
     127           0 :         return; // empty string; don't bother storing
     128             : 
     129           0 :     PutString(x, y, new std::wstring(buf), true);
     130             : }
     131             : 
     132           0 : void CTextRenderer::Put(float x, float y, const char* buf)
     133             : {
     134           0 :     if (buf[0] == 0)
     135           0 :         return; // empty string; don't bother storing
     136             : 
     137           0 :     PutString(x, y, new std::wstring(wstring_from_utf8(buf)), true);
     138             : }
     139             : 
     140           0 : void CTextRenderer::Put(float x, float y, const std::wstring* buf)
     141             : {
     142           0 :     if (buf->empty())
     143           0 :         return; // empty string; don't bother storing
     144             : 
     145           0 :     PutString(x, y, buf, false);
     146             : }
     147             : 
     148           0 : void CTextRenderer::PutString(float x, float y, const std::wstring* buf, bool owned)
     149             : {
     150           0 :     if (!m_Font)
     151           0 :         return; // invalid font; can't render
     152             : 
     153           0 :     if (m_Clipping != CRect())
     154             :     {
     155             :         float x0, y0, x1, y1;
     156           0 :         m_Font->GetGlyphBounds(x0, y0, x1, y1);
     157           0 :         if (y + y1 < m_Clipping.top)
     158           0 :             return;
     159           0 :         if (y + y0 > m_Clipping.bottom)
     160           0 :             return;
     161             :     }
     162             : 
     163             :     // If any state has changed since the last batch, start a new batch
     164           0 :     if (m_Dirty)
     165             :     {
     166           0 :         SBatch batch;
     167           0 :         batch.chars = 0;
     168           0 :         batch.translate = m_Translate;
     169           0 :         batch.color = m_Color;
     170           0 :         batch.font = m_Font;
     171           0 :         m_Batches.push_back(batch);
     172           0 :         m_Dirty = false;
     173             :     }
     174             : 
     175             :     // Push a new run onto the latest batch
     176           0 :     SBatchRun run;
     177           0 :     run.x = x;
     178           0 :     run.y = y;
     179           0 :     m_Batches.back().runs.push_back(run);
     180           0 :     m_Batches.back().runs.back().text = buf;
     181           0 :     m_Batches.back().runs.back().owned = owned;
     182           0 :     m_Batches.back().chars += buf->size();
     183             : }
     184             : 
     185             : struct SBatchCompare
     186             : {
     187           0 :     bool operator()(const CTextRenderer::SBatch& a, const CTextRenderer::SBatch& b)
     188             :     {
     189           0 :         if (a.font != b.font)
     190           0 :             return a.font < b.font;
     191             :         // TODO: is it worth sorting by color/translate too?
     192           0 :         return false;
     193             :     }
     194             : };
     195             : 
     196           0 : void CTextRenderer::Render(
     197             :     Renderer::Backend::IDeviceCommandContext* deviceCommandContext,
     198             :     Renderer::Backend::IShaderProgram* shader,
     199             :     const CVector2D& transformScale, const CVector2D& translation)
     200             : {
     201           0 :     std::vector<u16> indices;
     202           0 :     std::vector<CVector2D> positions;
     203           0 :     std::vector<CVector2D> uvs;
     204             : 
     205             :     // Try to merge non-consecutive batches that share the same font/color/translate:
     206             :     // sort the batch list by font, then merge the runs of adjacent compatible batches
     207           0 :     m_Batches.sort(SBatchCompare());
     208           0 :     for (std::list<SBatch>::iterator it = m_Batches.begin(); it != m_Batches.end(); )
     209             :     {
     210           0 :         std::list<SBatch>::iterator next = std::next(it);
     211           0 :         if (next != m_Batches.end() && it->chars + next->chars <= MAX_CHAR_COUNT_PER_BATCH && it->font == next->font && it->color == next->color && it->translate == next->translate)
     212             :         {
     213           0 :             it->chars += next->chars;
     214           0 :             it->runs.splice(it->runs.end(), next->runs);
     215           0 :             m_Batches.erase(next);
     216             :         }
     217             :         else
     218           0 :             ++it;
     219             :     }
     220             : 
     221           0 :     const int32_t texBindingSlot = shader->GetBindingSlot(str_tex);
     222           0 :     const int32_t translationBindingSlot = shader->GetBindingSlot(str_translation);
     223           0 :     const int32_t colorAddBindingSlot = shader->GetBindingSlot(str_colorAdd);
     224           0 :     const int32_t colorMulBindingSlot = shader->GetBindingSlot(str_colorMul);
     225             : 
     226           0 :     bool translationChanged = false;
     227             : 
     228           0 :     CTexture* lastTexture = nullptr;
     229           0 :     for (std::list<SBatch>::iterator it = m_Batches.begin(); it != m_Batches.end(); ++it)
     230             :     {
     231           0 :         SBatch& batch = *it;
     232             : 
     233           0 :         const CFont::GlyphMap& glyphs = batch.font->GetGlyphs();
     234             : 
     235           0 :         if (lastTexture != batch.font->GetTexture().get())
     236             :         {
     237           0 :             lastTexture = batch.font->GetTexture().get();
     238           0 :             lastTexture->UploadBackendTextureIfNeeded(deviceCommandContext);
     239           0 :             deviceCommandContext->SetTexture(texBindingSlot, lastTexture->GetBackendTexture());
     240             :         }
     241             : 
     242           0 :         if (batch.translate.X != 0.0f || batch.translate.Y != 0.0f)
     243             :         {
     244             :             const CVector2D localTranslation =
     245           0 :                 translation + CVector2D(batch.translate.X * transformScale.X, batch.translate.Y * transformScale.Y);
     246           0 :             deviceCommandContext->SetUniform(translationBindingSlot, localTranslation.AsFloatArray());
     247           0 :             translationChanged = true;
     248             :         }
     249             : 
     250             :         // ALPHA-only textures will have .rgb sampled as 0, so we need to
     251             :         // replace it with white (but not affect RGBA textures)
     252           0 :         if (batch.font->HasRGB())
     253           0 :             deviceCommandContext->SetUniform(colorAddBindingSlot, 0.0f, 0.0f, 0.0f, 0.0f);
     254             :         else
     255           0 :             deviceCommandContext->SetUniform(colorAddBindingSlot, batch.color.r, batch.color.g, batch.color.b, 0.0f);
     256             : 
     257           0 :         deviceCommandContext->SetUniform(colorMulBindingSlot, batch.color.AsFloatArray());
     258             : 
     259           0 :         positions.resize(std::min(MAX_CHAR_COUNT_PER_BATCH, batch.chars) * 4);
     260           0 :         uvs.resize(std::min(MAX_CHAR_COUNT_PER_BATCH, batch.chars) * 4);
     261           0 :         indices.resize(std::min(MAX_CHAR_COUNT_PER_BATCH, batch.chars) * 6);
     262             : 
     263           0 :         size_t idx = 0;
     264             : 
     265           0 :         auto flush = [deviceCommandContext, &idx, &positions, &uvs, &indices]() -> void
     266           0 :         {
     267           0 :             if (idx == 0)
     268           0 :                 return;
     269             : 
     270           0 :             deviceCommandContext->SetVertexBufferData(
     271           0 :                 0, positions.data(), positions.size() * sizeof(positions[0]));
     272           0 :             deviceCommandContext->SetVertexBufferData(
     273           0 :                 1, uvs.data(), uvs.size() * sizeof(uvs[0]));
     274           0 :             deviceCommandContext->SetIndexBufferData(
     275           0 :                 indices.data(), indices.size() * sizeof(indices[0]));
     276             : 
     277           0 :             deviceCommandContext->DrawIndexed(0, idx * 6, 0);
     278           0 :             idx = 0;
     279           0 :         };
     280             : 
     281           0 :         for (std::list<SBatchRun>::iterator runit = batch.runs.begin(); runit != batch.runs.end(); ++runit)
     282             :         {
     283           0 :             SBatchRun& run = *runit;
     284           0 :             i16 x = run.x;
     285           0 :             i16 y = run.y;
     286           0 :             for (size_t i = 0; i < run.text->size(); ++i)
     287             :             {
     288           0 :                 const CFont::GlyphData* g = glyphs.get((*run.text)[i]);
     289             : 
     290           0 :                 if (!g)
     291           0 :                     g = glyphs.get(0xFFFD); // Use the missing glyph symbol
     292           0 :                 if (!g) // Missing the missing glyph symbol - give up
     293           0 :                     continue;
     294             : 
     295           0 :                 uvs[idx*4].X = g->u1;
     296           0 :                 uvs[idx*4].Y = g->v0;
     297           0 :                 positions[idx*4].X = g->x1 + x;
     298           0 :                 positions[idx*4].Y = g->y0 + y;
     299             : 
     300           0 :                 uvs[idx*4+1].X = g->u0;
     301           0 :                 uvs[idx*4+1].Y = g->v0;
     302           0 :                 positions[idx*4+1].X = g->x0 + x;
     303           0 :                 positions[idx*4+1].Y = g->y0 + y;
     304             : 
     305           0 :                 uvs[idx*4+2].X = g->u0;
     306           0 :                 uvs[idx*4+2].Y = g->v1;
     307           0 :                 positions[idx*4+2].X = g->x0 + x;
     308           0 :                 positions[idx*4+2].Y = g->y1 + y;
     309             : 
     310           0 :                 uvs[idx*4+3].X = g->u1;
     311           0 :                 uvs[idx*4+3].Y = g->v1;
     312           0 :                 positions[idx*4+3].X = g->x1 + x;
     313           0 :                 positions[idx*4+3].Y = g->y1 + y;
     314             : 
     315           0 :                 indices[idx*6+0] = static_cast<u16>(idx*4+0);
     316           0 :                 indices[idx*6+1] = static_cast<u16>(idx*4+1);
     317           0 :                 indices[idx*6+2] = static_cast<u16>(idx*4+2);
     318           0 :                 indices[idx*6+3] = static_cast<u16>(idx*4+2);
     319           0 :                 indices[idx*6+4] = static_cast<u16>(idx*4+3);
     320           0 :                 indices[idx*6+5] = static_cast<u16>(idx*4+0);
     321             : 
     322           0 :                 x += g->xadvance;
     323             : 
     324           0 :                 ++idx;
     325           0 :                 if (idx == MAX_CHAR_COUNT_PER_BATCH)
     326           0 :                     flush();
     327             :             }
     328             :         }
     329             : 
     330           0 :         flush();
     331             :     }
     332             : 
     333           0 :     m_Batches.clear();
     334             : 
     335           0 :     if (translationChanged)
     336           0 :         deviceCommandContext->SetUniform(translationBindingSlot, translation.AsFloatArray());
     337           3 : }

Generated by: LCOV version 1.13