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.
1008-fix
AbigailBuccaneer 2018-04-27 11:16:45 +01:00
parent c49c815ea5
commit 6f6cfb136a
2 changed files with 90 additions and 17 deletions

View File

@ -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<int>(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<int>(m_quads.size()), m_color);
m_engine.AddStatisticTriangle(static_cast<int>(m_quads.size() * 2));
m_quads.clear();
}
private:
CEngine& m_engine;
struct Quad { Vertex vertices[4]; };
std::vector<Quad> m_quads;
std::vector<int> m_firsts;
std::vector<int> 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<CQuadBatch>(*engine);
}
CText::~CText()
@ -690,7 +763,6 @@ void CText::DrawString(const std::string &text, std::vector<FontMetaChar>::itera
std::vector<FontMetaChar>::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<FontMetaChar>::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<FontMetaChar>::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<UTF8Char> 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<unsigned char>(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;
}

View File

@ -344,6 +344,9 @@ protected:
FontType m_lastFontType;
int m_lastFontSize;
CachedFont* m_lastCachedFont;
class CQuadBatch;
std::unique_ptr<CQuadBatch> m_quadBatch;
};