Skip to content

Instantly share code, notes, and snippets.

@Shaptic
Last active October 5, 2021 00:53
Show Gist options
  • Save Shaptic/4705286 to your computer and use it in GitHub Desktop.
Save Shaptic/4705286 to your computer and use it in GitHub Desktop.
CFont::RenderText() - Renders a line of text at the given position in OpenGL.
rect_t CFont::RenderText(const std::string& text, const vector2_t& Pos)
{
// VAO and buffers that will contain our text vertex data.
uint32_t vao = 0, vbo = 0, ibo = 0;
// Track total text size.
rect_t Size(Pos.x, Pos.y, 0, 0);
if(!m_loaded || !CFont::s_loaded || text.empty()) return Size;
// Vertex buffer size, index buffer size.
uint16_t vlen = text.length() << 2;
uint16_t ilen = text.length() * ((1 << 2) + 2);
// Create buffers and zero them.
vertex2_t* verts = new vertex2_t[vlen];
uint16_t* inds = new uint16_t[ilen];
memset(inds, NULL, sizeof(uint16_t) * ilen);
memset(verts, NULL, sizeof(vertex2_t) * vlen);
// Track width and max height.
int max_w = 0, max_h = 0;
// The x-position to start the next character at.
int32_t last_w = Pos.x;
// The y-position of the starting character, for new-lines.
float y = Pos.y;
// Fill up buffers. Each character needs 4 vertices, so
// we increment by 4 each iteration then compensate for
// that throughout the loop.
for(size_t i = 0; i < vlen; i += 4)
{
// Shortcut.
char c = text[i >> 2];
// Handle new-lines by starting from the x position
// again and increasing Y by the height of an arbitrary
// large letter (here H).
if(c == '\n')
{
last_w = Pos.x;
y += mp_glyphTextures['H'].dim.y + mp_glyphTextures['H'].dim.h;
}
// We want to make sure the letters are in the range
// that we've loaded (ASCII printables).
char letter = (c > '~' || c < ' ') ? ' ' : c;
// Retrieve dimensions from the dictionary.
// Since we're doing i += 4, the index in the text string
// would be text[i / 4].
rect_t Dim = mp_glyphTextures[letter].dim;
float w = last_w;
float h = Dim.y;
last_w = w + Dim.w; // Increase until next char by the
// bitmap's horizontal advance value.
// [i] : top left
// [i + 1] : top right
// [i + 2] : bottom right
// [i + 3] : bottom left
verts[i].Position = math::vector2_t(w, y- Dim.h);
verts[i+1].Position = math::vector2_t(last_w, y - Dim.h);
verts[i+2].Position = math::vector2_t(last_w, y - Dim.h + h);
verts[i+3].Position = math::vector2_t(w, y - Dim.h + h);
// Load up the bitmap texture coordinates moving counter-clockwise
// from the origin.
verts[i].TexCoord = math::vector2_t(0, 0);
verts[i+1].TexCoord = math::vector2_t(1, 0);
verts[i+2].TexCoord = math::vector2_t(1, 1);
verts[i+3].TexCoord = math::vector2_t(0, 1);
// The vertices all use the font color.
// See CFont::SetColor()
for(size_t j = i; j < i + 4; ++j)
verts[j].Color = m_Color;
// A basic textured quad uses the indices [0, 1, 3, 3, 2, 1]
// assuming that verts[0] is the top-left, and the rest are
// calculated going clock-wise.
// The index in the buffer that we need is i / 4 * 6, since
// i / 4 gives us the current character, and each character
// needs 6 indices.
int x = (i >> 2) * 6;
// Assign the values.
inds[x] = i;
inds[x+1] = i + 1;
inds[x+2] = i + 3;
inds[x+3] = i + 3;
inds[x+4] = i + 2;
inds[x+5] = i + 1;
// Keep track of the overall dimensions.
max_w += Dim.w;
max_h = (max_h > Dim.h + h) ? max_h : Dim.h + h;
}
// Tally up dimensions.
Size.w = max_w;
Size.h = max_h;
// Enable font-rendering shader.
m_FontRender.Bind();
// Create GPU buffers for vertex/index data
glGenBuffers(1, &vao);
glGenBuffers(1, &vbo);
glGenBuffers(1, &ibo);
glBindVertexArray(vao);
glBindBuffer(GL_ARRAY_BUFFER, vbo);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
// Enable the vertex attributes for position, texcoord, and color.
// See the shader for details.
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
// Give data to GPU.
glBufferData(GL_ARRAY_BUFFER, sizeof(vertex2_t) * vlen, verts, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(uint16_t) * ilen, inds, GL_STATIC_DRAW);
// Vertices are arranged in memory like so:
// [ p0, p1, t0, t1, c0, c1, c2, c3 ]
// Specify vertex position arrangement.
// According to the diagram shown above, the vertex position
// would start at index 0.
// See the VBO_OFFSET macro in CFont.hpp.
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE,
sizeof(vertex2_t),
VBO_OFFSET(0, vertex2_t, Position));
// Specify texture coordinate position arrangement.
// According to the diagram, texture coordinates
// start at index 2.
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE,
sizeof(vertex2_t), VBO_OFFSET(0, vertex2_t, TexCoord));
// Specify the color arrangement.
// Starting at index 4.
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE,
sizeof(vertex2_t), VBO_OFFSET(0, vertex2_t, Color));
// Enable blending so that the text doesn't have a black background.
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// Draw each character with its texture enabled.
for(size_t i = 0; i < text.length(); ++i)
{
// Make invalid characters just spaces (' ')
char c = (text[i] > '~' || text[i] < ' ') ? ' ' : text[i];
glBindTexture(GL_TEXTURE_2D, mp_glyphTextures[c]);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT,
(void*)(sizeof(uint16_t) * i * 6));
}
// Delete GPU buffers.
glBufferData(GL_ARRAY_BUFFER, 0, NULL, GL_STATIC_DRAW);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, 0, NULL, GL_STATIC_DRAW);
// Unbind all the things.
glBindTexture(GL_TEXTURE_2D, 0);
glDisableVertexAttribArray(0);
glDisableVertexAttribArray(1);
glDisableVertexAttribArray(2);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
m_FontRender.Unbind();
// Delete all buffers.
glDeleteVertexArrays(1, &vao);
glDeleteBuffers(1, &vbo);
glDeleteBuffers(1, &ibo);
// Delete old buffers in RAM.
delete[] verts;
delete[] inds;
// Give back the total dimensions of the text rendered.
return Size;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment