Merge pull request #1148 from AbigailBuccaneer/text-batching
Improve text rendering performance1008-fix
commit
44f2684c70
|
@ -94,6 +94,77 @@ const Math::IntPoint REFERENCE_SIZE(800, 600);
|
||||||
const Math::IntPoint FONT_TEXTURE_SIZE(256, 256);
|
const Math::IntPoint FONT_TEXTURE_SIZE(256, 256);
|
||||||
} // anonymous namespace
|
} // 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)
|
CText::CText(CEngine* engine)
|
||||||
{
|
{
|
||||||
|
@ -106,6 +177,8 @@ CText::CText(CEngine* engine)
|
||||||
m_lastFontType = FONT_COLOBOT;
|
m_lastFontType = FONT_COLOBOT;
|
||||||
m_lastFontSize = 0;
|
m_lastFontSize = 0;
|
||||||
m_lastCachedFont = nullptr;
|
m_lastCachedFont = nullptr;
|
||||||
|
|
||||||
|
m_quadBatch = MakeUnique<CQuadBatch>(*engine);
|
||||||
}
|
}
|
||||||
|
|
||||||
CText::~CText()
|
CText::~CText()
|
||||||
|
@ -690,7 +763,7 @@ void CText::DrawString(const std::string &text, std::vector<FontMetaChar>::itera
|
||||||
std::vector<FontMetaChar>::iterator end,
|
std::vector<FontMetaChar>::iterator end,
|
||||||
float size, Math::IntPoint pos, int width, int eol, Color color)
|
float size, Math::IntPoint pos, int width, int eol, Color color)
|
||||||
{
|
{
|
||||||
m_engine->SetState(ENG_RSTATE_TEXT);
|
m_engine->SetWindowCoordinates();
|
||||||
|
|
||||||
int start = pos.x;
|
int start = pos.x;
|
||||||
|
|
||||||
|
@ -755,6 +828,8 @@ void CText::DrawString(const std::string &text, std::vector<FontMetaChar>::itera
|
||||||
Math::IntPoint charSize;
|
Math::IntPoint charSize;
|
||||||
charSize.x = GetCharWidthInt(ch, font, size, offset);
|
charSize.x = GetCharWidthInt(ch, font, size, offset);
|
||||||
charSize.y = GetHeightInt(font, size);
|
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);
|
DrawHighlight(format[fmtIndex], pos, charSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -776,6 +851,8 @@ void CText::DrawString(const std::string &text, std::vector<FontMetaChar>::itera
|
||||||
color = Color(1.0f, 0.0f, 0.0f);
|
color = Color(1.0f, 0.0f, 0.0f);
|
||||||
DrawCharAndAdjustPos(ch, font, size, pos, color);
|
DrawCharAndAdjustPos(ch, font, size, pos, color);
|
||||||
}
|
}
|
||||||
|
m_quadBatch->Flush();
|
||||||
|
m_engine->SetInterfaceCoordinates();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CText::StringToUTFCharList(const std::string &text, std::vector<UTF8Char> &chars)
|
void CText::StringToUTFCharList(const std::string &text, std::vector<UTF8Char> &chars)
|
||||||
|
@ -843,14 +920,15 @@ void CText::DrawString(const std::string &text, FontType font,
|
||||||
{
|
{
|
||||||
assert(font != FONT_BUTTON);
|
assert(font != FONT_BUTTON);
|
||||||
|
|
||||||
m_engine->SetState(ENG_RSTATE_TEXT);
|
|
||||||
|
|
||||||
std::vector<UTF8Char> chars;
|
std::vector<UTF8Char> chars;
|
||||||
StringToUTFCharList(text, chars);
|
StringToUTFCharList(text, chars);
|
||||||
|
m_engine->SetWindowCoordinates();
|
||||||
for (auto it = chars.begin(); it != chars.end(); ++it)
|
for (auto it = chars.begin(); it != chars.end(); ++it)
|
||||||
{
|
{
|
||||||
DrawCharAndAdjustPos(*it, font, size, pos, color);
|
DrawCharAndAdjustPos(*it, font, size, pos, color);
|
||||||
}
|
}
|
||||||
|
m_quadBatch->Flush();
|
||||||
|
m_engine->SetInterfaceCoordinates();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CText::DrawHighlight(FontMetaChar hl, Math::IntPoint pos, Math::IntPoint size)
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_quadBatch->Flush();
|
||||||
|
|
||||||
Math::IntPoint vsize = m_engine->GetWindowSize();
|
Math::IntPoint vsize = m_engine->GetWindowSize();
|
||||||
float h = 0.0f;
|
float h = 0.0f;
|
||||||
if (vsize.y <= 768.0f) // 1024x768 or less?
|
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])
|
VertexCol(Math::Vector(p2.x, p1.y, 0.0f), grad[1])
|
||||||
};
|
};
|
||||||
|
|
||||||
m_engine->SetWindowCoordinates();
|
|
||||||
m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, quad, 4);
|
m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, quad, 4);
|
||||||
m_engine->SetInterfaceCoordinates();
|
|
||||||
m_engine->AddStatisticTriangle(2);
|
m_engine->AddStatisticTriangle(2);
|
||||||
|
|
||||||
m_device->SetTextureEnabled(0, true);
|
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
|
// For whatever reason ch.c1 is a SIGNED char, we need to fix that
|
||||||
unsigned char icon = static_cast<unsigned char>(ch.c1);
|
unsigned char icon = static_cast<unsigned char>(ch.c1);
|
||||||
|
|
||||||
|
unsigned int texID;
|
||||||
|
|
||||||
if ( icon >= 128 )
|
if ( icon >= 128 )
|
||||||
{
|
{
|
||||||
icon -= 128;
|
icon -= 128;
|
||||||
m_engine->SetTexture("textures/interface/button3.png");
|
texID = m_engine->LoadTexture("textures/interface/button3.png").id;
|
||||||
m_engine->SetState(ENG_RSTATE_TTEXTURE_WHITE);
|
|
||||||
}
|
}
|
||||||
else if ( icon >= 64 )
|
else if ( icon >= 64 )
|
||||||
{
|
{
|
||||||
icon -= 64;
|
icon -= 64;
|
||||||
m_engine->SetTexture("textures/interface/button2.png");
|
texID = m_engine->LoadTexture("textures/interface/button2.png").id;
|
||||||
m_engine->SetState(ENG_RSTATE_TTEXTURE_WHITE);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
m_engine->SetTexture("textures/interface/button1.png");
|
texID = m_engine->LoadTexture("textures/interface/button1.png").id;
|
||||||
m_engine->SetState(ENG_RSTATE_TTEXTURE_WHITE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Math::Point uv1, uv2;
|
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))
|
Vertex(Math::Vector(p2.x, p1.y, 0.0f), n, Math::Point(uv2.x, uv1.y))
|
||||||
};
|
};
|
||||||
|
|
||||||
m_engine->SetWindowCoordinates();
|
m_quadBatch->Add(quad, texID, ENG_RSTATE_TTEXTURE_WHITE, color);
|
||||||
m_device->DrawPrimitive(PRIMITIVE_TRIANGLE_STRIP, quad, 4, color);
|
|
||||||
m_engine->SetInterfaceCoordinates();
|
|
||||||
m_engine->AddStatisticTriangle(2);
|
|
||||||
|
|
||||||
pos.x += width;
|
pos.x += width;
|
||||||
|
|
||||||
// Don't forget to restore the state!
|
|
||||||
m_engine->SetState(ENG_RSTATE_TEXT);
|
|
||||||
}
|
}
|
||||||
else
|
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))
|
Vertex(Math::Vector(p2.x, p1.y, 0.0f), n, Math::Point(texCoord2.x, texCoord1.y))
|
||||||
};
|
};
|
||||||
|
|
||||||
m_device->SetTexture(0, tex.id);
|
m_quadBatch->Add(quad, tex.id, ENG_RSTATE_TEXT, color);
|
||||||
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;
|
pos.x += tex.charSize.x * width;
|
||||||
}
|
}
|
||||||
|
|
|
@ -344,6 +344,9 @@ protected:
|
||||||
FontType m_lastFontType;
|
FontType m_lastFontType;
|
||||||
int m_lastFontSize;
|
int m_lastFontSize;
|
||||||
CachedFont* m_lastCachedFont;
|
CachedFont* m_lastCachedFont;
|
||||||
|
|
||||||
|
class CQuadBatch;
|
||||||
|
std::unique_ptr<CQuadBatch> m_quadBatch;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue