How to render text using OpenGL

The simplest way to render text with OpenGL is to use System.Drawing. This approach has three steps:

  1. Use Graphics.DrawString() to render text to a Bitmap.
  2. Upload the Bitmap to an OpenGL texture.
  3. Render the OpenGL texture as a fullscreen quad.

This approach is extremely efficient for text that changes infrequently, because only step 3 has to be performed every frame. Additionally, dynamic text can be reasonably efficient as long as care is taken to update only regions that are actually modified.

The downside of this approach is that (a) rendering is constrained by the capabilities of System.Drawing (i.e. poor support for complex scripts) and (b) it only supports 2d text. Moreover, care should be taken to recreate the Bitmap and OpenGL texture whenever the parent window changes size.

Sample code:

using System.Drawing;
using OpenTK.Graphics.OpenGL;
Bitmap text_bmp;
int text_texture;
window.OnLoad += (sender, e) =>
    // Create Bitmap and OpenGL texture
    text_bmp = new Bitmap(ClientSize.Width, ClientSize.Height); // match window size
    text_texture = GL.GenTexture();
    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)All.Linear);
    GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)All.Linear);
    GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, text_bmp.Width, text_bmp.Height, 0,
        PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero); // just allocate memory, so we can update efficiently using TexSubImage2D
window.Resize += (sender, e) =>
    // Ensure Bitmap and texture match window size
    text_bmp = new Bitmap(ClientSize.Width, ClientSize.Height);
    GL.TexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, text_bmp.Width, text_bmp.Height,
        PixelFormat.Bgra, PixelType.UnsignedByte, IntPtr.Zero);
// Render text using System.Drawing.
// Do this only when text changes.
using (Graphics gfx = Graphics.FromImage(text_bmp))
    gfx.DrawString(...); // Draw as many strings as you need
// Upload the Bitmap to OpenGL.
// Do this only when text changes.
BitmapData data = text_bmp.LockBits(new Rectangle(0, 0, text_bmp.Width, text_bmp.Height), ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, Width, Height, 0,
    PixelFormat.Bgra, PixelType.UnsignedByte, data.Scan0); 
// Finally, render using a quad. 
// Do this every frame.
GL.Ortho(0, Width, Height, 0, -1, 1);
GL.BlendFunc(BlendingFactorSrc.One, BlendingFactorDst.OneMinusSourceAlpha);
GL.TexCoord(0f, 1f); GL.Vertex2(0f, 0f);
GL.TexCoord(1f, 1f); GL.Vertex2(1f, 0f);
GL.TexCoord(1f, 0f); GL.Vertex2(1f, 1f);
GL.TexCoord(0f, 0f); GL.Vertex2(0f, 1f);

This method can be easily generalized to use a more powerful text rendering library, like Pango#.