PDA

View Full Version : Rendering text without a recreation of the vertex



Vexator
05-22-2010, 06:51 AM
I am looking for a way to render text efficiently. so far I've tried two options, both of them being neither fast nor very elegant:

Option #1 - Create a vertex buffer which contains all the needed glyphs at startup and then render each glyph individually, advancing the raster position each time:


// render all lines
for( uint i = 0; i < lines.size(); i++ )
{
// render all glyphs in a line
for( uint j = 0; j < lines[i].length(); j++ )
{
// look up glyph associated with character code
map<unsigned int, shared_ptr<Glyph>>::iterator glyphIndex = font->m_glyphs.find( lines[i][j] );

// skip if not found
if( glyphIndex == font->m_glyphs.end() )
continue;

// get the glyph itself
shared_ptr<Glyph> glyph = glyphIndex->second;

// setup view matrix from raster position
viewMatrix[3] = vec4( (vec2)rasterPosition, 0.0f, 1.0f );

// bind view matrix to shader
Engine::getGraphicsEngine()->bindConstant( m_shader, "u_View", viewMatrix );

// render glyph
Engine::getGraphicsEngine()->renderVertexbuffer( font->m_vertexbuffer, glyph->m_indexbufferOffset, 6 );

// advance raster position
rasterPosition.x += (int)((glyph->m_spacing.x)*spacing);
}

// start new line
rasterPosition.x = position.x;
rasterPosition.y -= (int)(font->m_size*spacing);
}

This is straight forward but results in a lot of draw calls.

Option #2 - Each frame, compute a glpyh's vertices from its original vertices plus the current raster position and create and update the vertex buffer:


vector<Vertex> vertices;
vertices.reserve( text.length()*6 );

// render all lines
for( uint i = 0; i < lines.size(); i++ )
{
// render all glyphs in a line
for( uint j = 0; j < lines[i].length(); j++ )
{
// look up glyph associated with character code
map<unsigned int, shared_ptr<Glyph>>::iterator glyphIndex = font->m_glyphs.find( lines[i][j] );

// skip if not found
if( glyphIndex == font->m_glyphs.end() )
continue;

// get the glyph itself
shared_ptr<Glyph> glyph = glyphIndex->second;

for( uint i = 0; i < 6; i++ )
{
Vertex vertex = glyph->m_vertices[i];

vertex.position.x += rasterPosition.x;
vertex.position.y += rasterPosition.y;

vertices.push_back( vertex );
}

// advance raster position
rasterPosition.x += (int)((glyph->m_spacing.x)*spacing);
}

// start new line
rasterPosition.x = position.x;
rasterPosition.y -= (int)(font->m_size*spacing);
}

Engine::getGraphicsEngine()->updateVertexbuffer( font->m_vertexbuffer, vertices );
Engine::getGraphicsEngine()->renderVertexbuffer( font->m_vertexbuffer );

Option #2 seems to make more sense since it requires a lot less draw calls but I don't like the idea of having to re-create and re-upload the vertex buffer every time the text changes.

The only thing that has to be changed in between glyphs is the raster position. Let's say I store a glyph's horizontal and vertical spacing in the zw components of its vertices. Would there be way to update a global variable in the vertex shader which keeps track of the advancing raster position?

I'll try to illustrate what I want to do. So let's say the input vertex stores its position in its xy components and the glyph's spacing in the xw components. If I had a global variable to store the current raster position in the vertex shader, I could position the glyphs like this:


vec2 rasterPosition; // <- a global variable that is incremented with each vertex

void main()
{
...

// advance raster position by glyph's spacing
rasterPosition += gl_Vertex.zw;

// final position is vertex position + raster position
vec2 Vertex = gl_Vertex.xy+rasterPosition;

// multiply with modelview and projection matrix and output vertex
gl_Position = u_ModelviewProjectionMatrix * vec4( Vertex, 0.0, 1.0 );
}

Is that possible in OpenGL? A uniform variable won't work as it's read only..

Thank you for your input!

pjcozzi
05-22-2010, 03:24 PM
Yes, the offsets for each character can be computed in a vertex shader - so you can render all your text in a single draw call. I did a writeup on this a little while back: Rendering Text Fast (http://blogs.agi.com/insight3d/index.php/2008/08/29/rendering-text-fast/). If you have GL3 hardware, this can also be done in a geometry shader by rendering points for each character instead of two triangles.

Regards,
Patrick

Vexator
05-24-2010, 08:39 AM
Thank you for the link! I am, however, not sure how this would work. All the glyphs in the vertex buffer would be positioned at the origin, right? So how would adding gl_Vertex in the computation of the final position place the glyphs next to each other? and how would we account for the glyphs' different advance values?

pjcozzi
05-27-2010, 05:10 AM
Good questions. In the GLSL example in that article the world space origin for the string is passed to the shader as a uniform. Each glyph in the string is represented by two triangles (or one quad or one triangle strip, however you want to look at it). The position, gl_Vertex, of each point of the two triangles is the screen space translation to align that glyph with the rest of the string. This is computed once on the CPU based on the font's stride information, then stored in the vertex buffer. The vertex shader simply transforms the string's origin from world space to window space, then uses gl_Vertex to translate the glyph to the correct screen space location.

I should also mention that if you want to render strings with different origins in the same draw call, you can replace the origin uniform with an extra vertex attribute. This is somewhat redundant in that the vertices for each string store the same origin, which is why expanding points into quads in a geometry shader is more memory efficient (although it was slower in practice when I tested it on a GeForce 8800 GTX - I am not sure if that is true on more recent hardware).

Regards,
Patrick