diff --git a/src/graphics/engine/text.cpp b/src/graphics/engine/text.cpp index 96bb3071..0ecd5820 100644 --- a/src/graphics/engine/text.cpp +++ b/src/graphics/engine/text.cpp @@ -94,6 +94,77 @@ const Math::IntPoint REFERENCE_SIZE(800, 600); const Math::IntPoint FONT_TEXTURE_SIZE(256, 256); } // anonymous namespace +/// The QuadBatch is responsible for collecting as many quad (aka rectangle) draws as possible and +/// sending them to the CDevice in one big batch. This avoids making one CDevice::DrawPrimitive call +/// for every CText::DrawCharAndAdjustPos call, which makes text rendering much faster. +/// Currently we only collect textured quads (ie. ones using Vertex), not untextured quads (which +/// use VertexCol). Untextured quads are only drawn via DrawHighlight, which happens much less often +/// than drawing textured quads. +class CText::CQuadBatch +{ +public: + explicit CQuadBatch(CEngine& engine) + : m_engine(engine) + { + m_quads.reserve(1024); + } + + /// Add a quad to be rendered. + /// This may trigger a call to Flush() if necessary. + void Add(Vertex vertices[4], unsigned int texID, EngineRenderState renderState, Color color) + { + if (texID != m_texID || renderState != m_renderState || color != m_color) + { + Flush(); + m_texID = texID; + m_renderState = renderState; + m_color = color; + } + m_quads.emplace_back(Quad{{vertices[0], vertices[1], vertices[2], vertices[3]}}); + } + + /// Draw all pending quads immediately. + void Flush() + { + if (m_quads.empty()) return; + + m_engine.SetState(m_renderState); + m_engine.GetDevice()->SetTexture(0, m_texID); + + assert(m_firsts.size() == m_counts.size()); + if (m_firsts.size() < m_quads.size()) + { + // m_firsts needs to look like { 0, 4, 8, 12, ... } + // m_counts needs to look like { 4, 4, 4, 4, ... } + // and both need to be the same length as m_quads + m_counts.resize(m_quads.size(), 4); + std::size_t begin = m_firsts.size(); + m_firsts.resize(m_quads.size()); + for (std::size_t i = begin; i < m_firsts.size(); ++i) + { + m_firsts[i] = static_cast(4 * i); + } + } + + const Vertex* vertices = m_quads.front().vertices; + m_engine.GetDevice()->DrawPrimitives(PRIMITIVE_TRIANGLE_STRIP, vertices, m_firsts.data(), + m_counts.data(), static_cast(m_quads.size()), m_color); + m_engine.AddStatisticTriangle(static_cast(m_quads.size() * 2)); + m_quads.clear(); + } +private: + CEngine& m_engine; + + struct Quad { Vertex vertices[4]; }; + std::vector m_quads; + std::vector m_firsts; + std::vector m_counts; + + Color m_color; + unsigned int m_texID{}; + EngineRenderState m_renderState{}; +}; + CText::CText(CEngine* engine) { @@ -106,6 +177,8 @@ CText::CText(CEngine* engine) m_lastFontType = FONT_COLOBOT; m_lastFontSize = 0; m_lastCachedFont = nullptr; + + m_quadBatch = MakeUnique(*engine); } CText::~CText() @@ -690,7 +763,7 @@ void CText::DrawString(const std::string &text, std::vector::itera std::vector::iterator end, float size, Math::IntPoint pos, int width, int eol, Color color) { - m_engine->SetState(ENG_RSTATE_TEXT); + m_engine->SetWindowCoordinates(); int start = pos.x; @@ -755,6 +828,8 @@ void CText::DrawString(const std::string &text, std::vector::itera Math::IntPoint charSize; charSize.x = GetCharWidthInt(ch, font, size, offset); charSize.y = GetHeightInt(font, size); + // NB. for quad batching to improve highlight drawing performance, this code would have + // to be rearranged to draw all highlights before any characters are drawn. DrawHighlight(format[fmtIndex], pos, charSize); } @@ -776,6 +851,8 @@ void CText::DrawString(const std::string &text, std::vector::itera color = Color(1.0f, 0.0f, 0.0f); DrawCharAndAdjustPos(ch, font, size, pos, color); } + m_quadBatch->Flush(); + m_engine->SetInterfaceCoordinates(); } void CText::StringToUTFCharList(const std::string &text, std::vector &chars) @@ -843,14 +920,15 @@ void CText::DrawString(const std::string &text, FontType font, { assert(font != FONT_BUTTON); - m_engine->SetState(ENG_RSTATE_TEXT); - std::vector chars; StringToUTFCharList(text, chars); + m_engine->SetWindowCoordinates(); for (auto it = chars.begin(); it != chars.end(); ++it) { DrawCharAndAdjustPos(*it, font, size, pos, color); } + m_quadBatch->Flush(); + m_engine->SetInterfaceCoordinates(); } void CText::DrawHighlight(FontMetaChar hl, Math::IntPoint pos, Math::IntPoint size) @@ -874,6 +952,8 @@ void CText::DrawHighlight(FontMetaChar hl, Math::IntPoint pos, Math::IntPoint si return; } + m_quadBatch->Flush(); + Math::IntPoint vsize = m_engine->GetWindowSize(); float h = 0.0f; if (vsize.y <= 768.0f) // 1024x768 or less? @@ -902,9 +982,7 @@ void CText::DrawHighlight(FontMetaChar hl, Math::IntPoint pos, Math::IntPoint si VertexCol(Math::Vector(p2.x, p1.y, 0.0f), grad[1]) }; - m_engine->SetWindowCoordinates(); m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, quad, 4); - m_engine->SetInterfaceCoordinates(); m_engine->AddStatisticTriangle(2); m_device->SetTextureEnabled(0, true); @@ -925,22 +1003,22 @@ void CText::DrawCharAndAdjustPos(UTF8Char ch, FontType font, float size, Math::I // For whatever reason ch.c1 is a SIGNED char, we need to fix that unsigned char icon = static_cast(ch.c1); + + unsigned int texID; + if ( icon >= 128 ) { icon -= 128; - m_engine->SetTexture("textures/interface/button3.png"); - m_engine->SetState(ENG_RSTATE_TTEXTURE_WHITE); + texID = m_engine->LoadTexture("textures/interface/button3.png").id; } else if ( icon >= 64 ) { icon -= 64; - m_engine->SetTexture("textures/interface/button2.png"); - m_engine->SetState(ENG_RSTATE_TTEXTURE_WHITE); + texID = m_engine->LoadTexture("textures/interface/button2.png").id; } else { - m_engine->SetTexture("textures/interface/button1.png"); - m_engine->SetState(ENG_RSTATE_TTEXTURE_WHITE); + texID = m_engine->LoadTexture("textures/interface/button1.png").id; } Math::Point uv1, uv2; @@ -963,15 +1041,9 @@ void CText::DrawCharAndAdjustPos(UTF8Char ch, FontType font, float size, Math::I Vertex(Math::Vector(p2.x, p1.y, 0.0f), n, Math::Point(uv2.x, uv1.y)) }; - m_engine->SetWindowCoordinates(); - m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, quad, 4, color); - m_engine->SetInterfaceCoordinates(); - m_engine->AddStatisticTriangle(2); + m_quadBatch->Add(quad, texID, ENG_RSTATE_TTEXTURE_WHITE, color); pos.x += width; - - // Don't forget to restore the state! - m_engine->SetState(ENG_RSTATE_TEXT); } else { @@ -1007,11 +1079,7 @@ void CText::DrawCharAndAdjustPos(UTF8Char ch, FontType font, float size, Math::I Vertex(Math::Vector(p2.x, p1.y, 0.0f), n, Math::Point(texCoord2.x, texCoord1.y)) }; - m_device->SetTexture(0, tex.id); - m_engine->SetWindowCoordinates(); - m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, quad, 4, color); - m_engine->SetInterfaceCoordinates(); - m_engine->AddStatisticTriangle(2); + m_quadBatch->Add(quad, tex.id, ENG_RSTATE_TEXT, color); pos.x += tex.charSize.x * width; } diff --git a/src/graphics/engine/text.h b/src/graphics/engine/text.h index 47538700..40fb90fa 100644 --- a/src/graphics/engine/text.h +++ b/src/graphics/engine/text.h @@ -344,6 +344,9 @@ protected: FontType m_lastFontType; int m_lastFontSize; CachedFont* m_lastCachedFont; + + class CQuadBatch; + std::unique_ptr m_quadBatch; };