Optimise text rendering by batching
When CTextRenderer is given multiple strings with no differences other than position, render them in a single glDrawElements call to reduce driver overhead. Also avoid some unnecessary copies of std::wstrings. This helps performance a bit with large GUI text boxes. This was SVN commit r14018.
This commit is contained in:
parent
b6c7c0d799
commit
751558d894
@ -44,6 +44,7 @@ void CTextRenderer::ResetTransform()
|
||||
CMatrix3D proj;
|
||||
proj.SetOrtho(0.f, (float)g_xres, 0.f, (float)g_yres, -1.f, 1000.f);
|
||||
m_Transform = proj * m_Transform;
|
||||
m_Dirty = true;
|
||||
}
|
||||
|
||||
CMatrix3D CTextRenderer::GetTransform()
|
||||
@ -54,6 +55,7 @@ CMatrix3D CTextRenderer::GetTransform()
|
||||
void CTextRenderer::SetTransform(const CMatrix3D& transform)
|
||||
{
|
||||
m_Transform = transform;
|
||||
m_Dirty = true;
|
||||
}
|
||||
|
||||
void CTextRenderer::Translate(float x, float y, float z)
|
||||
@ -61,21 +63,31 @@ void CTextRenderer::Translate(float x, float y, float z)
|
||||
CMatrix3D m;
|
||||
m.SetTranslation(x, y, z);
|
||||
m_Transform = m_Transform * m;
|
||||
m_Dirty = true;
|
||||
}
|
||||
|
||||
void CTextRenderer::Color(const CColor& color)
|
||||
{
|
||||
m_Color = color;
|
||||
if (m_Color != color)
|
||||
{
|
||||
m_Color = color;
|
||||
m_Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CTextRenderer::Color(float r, float g, float b, float a)
|
||||
{
|
||||
m_Color = CColor(r, g, b, a);
|
||||
Color(CColor(r, g, b, a));
|
||||
}
|
||||
|
||||
void CTextRenderer::Font(const CStrW& font)
|
||||
{
|
||||
m_Font = g_Renderer.GetFontManager().LoadFont(font);
|
||||
if (font != m_FontName)
|
||||
{
|
||||
m_FontName = font;
|
||||
m_Font = g_Renderer.GetFontManager().LoadFont(font);
|
||||
m_Dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
void CTextRenderer::PrintfAdvance(const wchar_t* fmt, ...)
|
||||
@ -123,20 +135,45 @@ void CTextRenderer::Put(float x, float y, const wchar_t* buf)
|
||||
if (buf[0] == 0)
|
||||
return; // empty string; don't bother storing
|
||||
|
||||
PutString(x, y, new std::wstring(buf), true);
|
||||
}
|
||||
|
||||
void CTextRenderer::Put(float x, float y, const std::wstring* buf)
|
||||
{
|
||||
if (buf->empty())
|
||||
return; // empty string; don't bother storing
|
||||
|
||||
PutString(x, y, buf, false);
|
||||
}
|
||||
|
||||
void CTextRenderer::PutString(float x, float y, const std::wstring* buf, bool owned)
|
||||
{
|
||||
if (!m_Font)
|
||||
return; // invalid font; can't render
|
||||
|
||||
CMatrix3D translate;
|
||||
translate.SetTranslation(x, y, 0.0f);
|
||||
// If any state has changed since the last batch, start a new batch
|
||||
if (m_Dirty)
|
||||
{
|
||||
SBatch batch;
|
||||
batch.chars = 0;
|
||||
batch.transform = m_Transform;
|
||||
batch.color = m_Color;
|
||||
batch.font = m_Font;
|
||||
m_Batches.push_back(batch);
|
||||
m_Dirty = false;
|
||||
}
|
||||
|
||||
SBatch batch;
|
||||
batch.transform = m_Transform * translate;
|
||||
batch.color = m_Color;
|
||||
batch.font = m_Font;
|
||||
batch.text = buf;
|
||||
m_Batches.push_back(batch);
|
||||
// Push a new run onto the latest batch
|
||||
SBatchRun run;
|
||||
run.x = x;
|
||||
run.y = y;
|
||||
m_Batches.back().runs.push_back(run);
|
||||
m_Batches.back().runs.back().text = buf;
|
||||
m_Batches.back().runs.back().owned = owned;
|
||||
m_Batches.back().chars += buf->size();
|
||||
}
|
||||
|
||||
|
||||
struct t2f_v2i
|
||||
{
|
||||
t2f_v2i() : u(0), v(0), x(0), y(0) { }
|
||||
@ -144,17 +181,44 @@ struct t2f_v2i
|
||||
i16 x, y;
|
||||
};
|
||||
|
||||
struct SBatchCompare
|
||||
{
|
||||
bool operator()(const CTextRenderer::SBatch& a, const CTextRenderer::SBatch& b)
|
||||
{
|
||||
if (a.font < b.font)
|
||||
return true;
|
||||
if (b.font < a.font)
|
||||
return false;
|
||||
// TODO: is it worth sorting by color/transform too?
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
void CTextRenderer::Render()
|
||||
{
|
||||
std::vector<u16> indexes;
|
||||
std::vector<t2f_v2i> vertexes;
|
||||
|
||||
for (size_t i = 0; i < m_Batches.size(); ++i)
|
||||
// Try to merge non-consecutive batches that share the same font/color/transform:
|
||||
// sort the batch list by font, then merge the runs of adjacent compatible batches
|
||||
m_Batches.sort(SBatchCompare());
|
||||
for (std::list<SBatch>::iterator it = m_Batches.begin(); it != m_Batches.end(); )
|
||||
{
|
||||
SBatch& batch = m_Batches[i];
|
||||
std::list<SBatch>::iterator next = it;
|
||||
++next;
|
||||
if (next != m_Batches.end() && it->font == next->font && it->color == next->color && it->transform == next->transform)
|
||||
{
|
||||
it->chars += next->chars;
|
||||
it->runs.splice(it->runs.end(), next->runs);
|
||||
m_Batches.erase(next);
|
||||
}
|
||||
else
|
||||
++it;
|
||||
}
|
||||
|
||||
if (batch.text.empty()) // avoid zero-length arrays
|
||||
continue;
|
||||
for (std::list<SBatch>::iterator it = m_Batches.begin(); it != m_Batches.end(); ++it)
|
||||
{
|
||||
SBatch& batch = *it;
|
||||
|
||||
const CFont::GlyphMap& glyphs = batch.font->GetGlyphs();
|
||||
|
||||
@ -171,50 +235,56 @@ void CTextRenderer::Render()
|
||||
|
||||
m_Shader->Uniform(str_colorMul, batch.color);
|
||||
|
||||
vertexes.clear();
|
||||
vertexes.resize(batch.text.size()*4);
|
||||
vertexes.resize(batch.chars*4);
|
||||
indexes.resize(batch.chars*6);
|
||||
|
||||
indexes.clear();
|
||||
indexes.resize(batch.text.size()*6);
|
||||
size_t idx = 0;
|
||||
|
||||
i16 x = 0;
|
||||
for (size_t i = 0; i < batch.text.size(); ++i)
|
||||
for (std::list<SBatchRun>::iterator runit = batch.runs.begin(); runit != batch.runs.end(); ++runit)
|
||||
{
|
||||
const CFont::GlyphData* g = glyphs.get(batch.text[i]);
|
||||
SBatchRun& run = *runit;
|
||||
i16 x = run.x;
|
||||
i16 y = run.y;
|
||||
for (size_t i = 0; i < run.text->size(); ++i)
|
||||
{
|
||||
const CFont::GlyphData* g = glyphs.get((*run.text)[i]);
|
||||
|
||||
if (!g)
|
||||
g = glyphs.get(0xFFFD); // Use the missing glyph symbol
|
||||
if (!g) // Missing the missing glyph symbol - give up
|
||||
continue;
|
||||
if (!g)
|
||||
g = glyphs.get(0xFFFD); // Use the missing glyph symbol
|
||||
if (!g) // Missing the missing glyph symbol - give up
|
||||
continue;
|
||||
|
||||
vertexes[i*4].u = g->u1;
|
||||
vertexes[i*4].v = g->v0;
|
||||
vertexes[i*4].x = g->x1 + x;
|
||||
vertexes[i*4].y = g->y0;
|
||||
vertexes[idx*4].u = g->u1;
|
||||
vertexes[idx*4].v = g->v0;
|
||||
vertexes[idx*4].x = g->x1 + x;
|
||||
vertexes[idx*4].y = g->y0 + y;
|
||||
|
||||
vertexes[i*4+1].u = g->u0;
|
||||
vertexes[i*4+1].v = g->v0;
|
||||
vertexes[i*4+1].x = g->x0 + x;
|
||||
vertexes[i*4+1].y = g->y0;
|
||||
vertexes[idx*4+1].u = g->u0;
|
||||
vertexes[idx*4+1].v = g->v0;
|
||||
vertexes[idx*4+1].x = g->x0 + x;
|
||||
vertexes[idx*4+1].y = g->y0 + y;
|
||||
|
||||
vertexes[i*4+2].u = g->u0;
|
||||
vertexes[i*4+2].v = g->v1;
|
||||
vertexes[i*4+2].x = g->x0 + x;
|
||||
vertexes[i*4+2].y = g->y1;
|
||||
vertexes[idx*4+2].u = g->u0;
|
||||
vertexes[idx*4+2].v = g->v1;
|
||||
vertexes[idx*4+2].x = g->x0 + x;
|
||||
vertexes[idx*4+2].y = g->y1 + y;
|
||||
|
||||
vertexes[i*4+3].u = g->u1;
|
||||
vertexes[i*4+3].v = g->v1;
|
||||
vertexes[i*4+3].x = g->x1 + x;
|
||||
vertexes[i*4+3].y = g->y1;
|
||||
vertexes[idx*4+3].u = g->u1;
|
||||
vertexes[idx*4+3].v = g->v1;
|
||||
vertexes[idx*4+3].x = g->x1 + x;
|
||||
vertexes[idx*4+3].y = g->y1 + y;
|
||||
|
||||
indexes[i*6+0] = i*4+0;
|
||||
indexes[i*6+1] = i*4+1;
|
||||
indexes[i*6+2] = i*4+2;
|
||||
indexes[i*6+3] = i*4+2;
|
||||
indexes[i*6+4] = i*4+3;
|
||||
indexes[i*6+5] = i*4+0;
|
||||
indexes[idx*6+0] = idx*4+0;
|
||||
indexes[idx*6+1] = idx*4+1;
|
||||
indexes[idx*6+2] = idx*4+2;
|
||||
indexes[idx*6+3] = idx*4+2;
|
||||
indexes[idx*6+4] = idx*4+3;
|
||||
indexes[idx*6+5] = idx*4+0;
|
||||
|
||||
x += g->xadvance;
|
||||
x += g->xadvance;
|
||||
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
|
||||
m_Shader->VertexPointer(2, GL_SHORT, sizeof(t2f_v2i), &vertexes[0].x);
|
||||
|
@ -75,33 +75,88 @@ public:
|
||||
void PutAdvance(const wchar_t* buf);
|
||||
|
||||
/**
|
||||
* Print text at (x,y) under the current transform,
|
||||
* and advance the transform by the width of the text.
|
||||
* Print text at (x,y) under the current transform.
|
||||
* Does not alter the current transform.
|
||||
*/
|
||||
void Put(float x, float y, const wchar_t* buf);
|
||||
|
||||
/**
|
||||
* Print text at (x,y) under the current transform.
|
||||
* Does not alter the current transform.
|
||||
* @p buf must remain valid until Render() is called.
|
||||
* (This should be used to minimise memory copies when possible.)
|
||||
*/
|
||||
void Put(float x, float y, const std::wstring* buf);
|
||||
|
||||
/**
|
||||
* Render all of the previously printed text calls.
|
||||
*/
|
||||
void Render();
|
||||
|
||||
private:
|
||||
friend struct SBatchCompare;
|
||||
|
||||
/**
|
||||
* A string (optionally owned by this object, or else pointing to an
|
||||
* externally-owned string) with a position.
|
||||
*/
|
||||
struct SBatchRun
|
||||
{
|
||||
private:
|
||||
SBatchRun& operator=(const SBatchRun&);
|
||||
public:
|
||||
SBatchRun()
|
||||
: text(NULL), owned(false)
|
||||
{
|
||||
}
|
||||
|
||||
SBatchRun(const SBatchRun& str)
|
||||
: x(str.x), y(str.y), owned(str.owned)
|
||||
{
|
||||
if (owned)
|
||||
text = new std::wstring(*str.text);
|
||||
else
|
||||
text = str.text;
|
||||
}
|
||||
|
||||
~SBatchRun()
|
||||
{
|
||||
if (owned)
|
||||
delete text;
|
||||
}
|
||||
|
||||
float x, y;
|
||||
|
||||
const std::wstring* text;
|
||||
bool owned;
|
||||
};
|
||||
|
||||
/**
|
||||
* A list of SBatchRuns, with a single font/color/transform,
|
||||
* to be rendered in a single GL call.
|
||||
*/
|
||||
struct SBatch
|
||||
{
|
||||
size_t chars; // sum of runs[i].text->size()
|
||||
CMatrix3D transform;
|
||||
CColor color;
|
||||
shared_ptr<CFont> font;
|
||||
std::wstring text;
|
||||
std::list<SBatchRun> runs;
|
||||
};
|
||||
|
||||
void PutString(float x, float y, const std::wstring* buf, bool owned);
|
||||
|
||||
CShaderProgramPtr m_Shader;
|
||||
|
||||
CMatrix3D m_Transform;
|
||||
|
||||
CColor m_Color;
|
||||
CStrW m_FontName;
|
||||
shared_ptr<CFont> m_Font;
|
||||
|
||||
std::vector<SBatch> m_Batches;
|
||||
bool m_Dirty;
|
||||
|
||||
std::list<SBatch> m_Batches;
|
||||
};
|
||||
|
||||
#endif // INCLUDED_TEXTRENDERER
|
||||
|
@ -966,7 +966,7 @@ void CGUI::DrawText(SGUIText &Text, const CColor &DefaultColor,
|
||||
|
||||
textRenderer.Color(color);
|
||||
textRenderer.Font(it->m_Font);
|
||||
textRenderer.Put((float)(int)(pos.x+it->m_Pos.x), (float)(int)(pos.y+it->m_Pos.y), it->m_String.c_str());
|
||||
textRenderer.Put((float)(int)(pos.x+it->m_Pos.x), (float)(int)(pos.y+it->m_Pos.y), &it->m_String);
|
||||
}
|
||||
|
||||
textRenderer.Render();
|
||||
|
Loading…
Reference in New Issue
Block a user