From c49c815ea51c75f67d203a9820ce3b4096c2264d Mon Sep 17 00:00:00 2001 From: AbigailBuccaneer Date: Fri, 27 Apr 2018 09:58:09 +0100 Subject: [PATCH 1/2] Set uniforms less often during text rendering We now call SetWindowCoordinates and SetInterfaceCoordinates once per string, rather than once or twice per character. --- src/graphics/engine/text.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/graphics/engine/text.cpp b/src/graphics/engine/text.cpp index 96bb3071..edf3c50d 100644 --- a/src/graphics/engine/text.cpp +++ b/src/graphics/engine/text.cpp @@ -691,6 +691,7 @@ void CText::DrawString(const std::string &text, std::vector::itera float size, Math::IntPoint pos, int width, int eol, Color color) { m_engine->SetState(ENG_RSTATE_TEXT); + m_engine->SetWindowCoordinates(); int start = pos.x; @@ -776,6 +777,7 @@ 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_engine->SetInterfaceCoordinates(); } void CText::StringToUTFCharList(const std::string &text, std::vector &chars) @@ -847,10 +849,12 @@ void CText::DrawString(const std::string &text, FontType font, 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_engine->SetInterfaceCoordinates(); } void CText::DrawHighlight(FontMetaChar hl, Math::IntPoint pos, Math::IntPoint size) @@ -902,9 +906,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); @@ -963,9 +965,7 @@ 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); pos.x += width; @@ -1008,9 +1008,7 @@ void CText::DrawCharAndAdjustPos(UTF8Char ch, FontType font, float size, Math::I }; 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); pos.x += tex.charSize.x * width; From 6f6cfb136a9359f0be6391a90ec21e6d621e5025 Mon Sep 17 00:00:00 2001 From: AbigailBuccaneer Date: Fri, 27 Apr 2018 11:16:45 +0100 Subject: [PATCH 2/2] Batch draw calls from CText to improve performance This significantly speeds up text rendering. On my computer, looking at the program editor with a full screen of text, this commit takes the framerate from under 30 to 60 (hitting vsync). Performance could be further improved in the gl33 renderer by using instancing or glPrimitiveRestartIndex instead of glMultiDrawArrays, but that would be a more invasive change. All of the interface rendering could use a unified quad batching system, instead of it being limited to CText, but that would require some refactoring in CText as it currently draws using a different coordinate space to the rest of the interface. Fixes #1104. --- src/graphics/engine/text.cpp | 104 +++++++++++++++++++++++++++++------ src/graphics/engine/text.h | 3 + 2 files changed, 90 insertions(+), 17 deletions(-) diff --git a/src/graphics/engine/text.cpp b/src/graphics/engine/text.cpp index edf3c50d..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,6 @@ 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; @@ -756,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); } @@ -777,6 +851,7 @@ 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(); } @@ -845,8 +920,6 @@ 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(); @@ -854,6 +927,7 @@ void CText::DrawString(const std::string &text, FontType font, { DrawCharAndAdjustPos(*it, font, size, pos, color); } + m_quadBatch->Flush(); m_engine->SetInterfaceCoordinates(); } @@ -878,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? @@ -927,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; @@ -965,13 +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_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, quad, 4, color); - 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,9 +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_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, quad, 4, color); - 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; };