In my engine, I use a dynamic vertex buffer for batching up text data (each character == 1 vertex) and do point-to-quad expansion in geometry shader. As far as I know, glVertex*, glNormal*, glTexCoord* are considered obsolete (like display lists) and are not recommended to use. This gives a comprehensive overview of various OpenGL-based text rendering techniques: https://stackoverflow.com/questions/5262951/what-is-state-of-the-art-for-text-rendering-in-opengl-as-of-version-4-1 The details of the technique I'm using: Offline: 0) prepare a font texture atlas (with tightly packed glyphs, block-compressed) and remember glyph data (position, size, unicode => glyph index mapping); for these purposes I use MakeSpriteFont from [DirectX Tool Kit](https://directxtk.codeplex.com/). At launch time: 1) load the font texture and glyph data (submit data of each mipmap level to glCompressedTexSubImage2D); 2) create a dynamic vertex buffer (GL_DYNAMIC_DRAW) for holding MAX_TEXT_LENGTH vertices; At draw time: 4) parse the submitted text string and update the VBO using the glyph data; 5) draw the points from VBO glBindBuffer( GL_ARRAY_BUFFER, dynamicVBO_id ); glDrawArrays( GL_POINTS, 0/*first vertex*/, vertexCount/*==character count*/ ); My vertex shader code: #version 420 core in vec4 a_texCoord0; // .xy - center position, .zw - width and height in vec4 a_texCoord1; // st coords for top left and bottom right corners out VSO { vec4 xy_wh; vec4 tl_br; } outputs; void main() { // gl_Position will be written by geometry shader outputs.xy_wh = a_texCoord0; outputs.tl_br = a_texCoord1; } Geometry shader code: #version 420 core layout(points) in; layout(triangle_strip, max_vertices=4) out; in VSO { vec4 xy_wh; vec4 tl_br; // UVs for top left and bottom right corners } inputs[]; out GSO { vec2 texCoord; } outputs; void main() { vec2 pos = inputs[0].xy_wh.xy; float width = inputs[0].xy_wh.z; float height = inputs[0].xy_wh.w; vec2 tl = inputs[0].tl_br.xy; vec2 br = inputs[0].tl_br.zw; gl_Position = vec4( pos.x, pos.y, 0.0f, 1.0f ); outputs.texCoord = vec2( tl.x, tl.y ); EmitVertex(); gl_Position = vec4( pos.x + width, pos.y, 0.0f, 1.0f ); outputs.texCoord = vec2( br.x, tl.y ); EmitVertex(); gl_Position = vec4( pos.x, pos.y - height, 0.0f, 1.0f ); outputs.texCoord = vec2( tl.x, br.y ); EmitVertex(); gl_Position = vec4( pos.x + width, pos.y - height, 0.0f, 1.0f ); outputs.texCoord = vec2( br.x, br.y ); EmitVertex(); EndPrimitive(); } Fragment shader code: #version 420 core in GSO { vec2 texCoord; } inputs; out vec4 o_pixelColor; uniform sampler2D s_font; void main() { vec4 textureColor = texture( s_font, inputs.texCoord ).rgba; if( textureColor.w < 1.0/255.0 ) { discard; } o_pixelColor = textureColor; } I'm not in any way proficient in OpenGL.