Back to posts.

Some notes on OpenGL

FBO + Render-To-Texture + Depth testing

When you use and FBO to render the scene into a texture and then another pass to apply some effect, you need to make sure that you're not using the same depth values (gl_Position.z) because else the second pass will not be rendered when depth testing is enabled. A simple fix for this (but this might be a bit heavy if you do it do often) is turning on/off depth testing using glEnable(GL_DEPTH_TEST) and glDisable(GL_DEPTH_TEST)

Texture formats

When you use glTexImage2D() you specify the internalFormat, format and the type of your texture data. Choosing the correct values is important when you're streaming data to the gpu as the driver might want to rearrange the pixel data you provide which you really don't want.

  • internalFormat:
    number of color components in the texture (GL_R, GL_RG, GL_RGB, GL_RGBA).

  • format:
    format of the pixel data triggers swizzling! (GL_BGRA, GL_YCBCR_422_APPLE)

  • type:
    data type of the pixel data (GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT)

Apple advices to use these formats and types

Format               With type
--------------------------------------------------------
GL_BGRA             |  GL_UNSIGNED_INT_8_8_8_8_REV
GL_BGRA             |  GL_UNSIGNED_SHORT_1_5_5_5_REV
GL_YCBCR_422_APPLE  |  GL_UNSIGNED_SHORT_8_8_REV_APPLE
--------------------------------------------------------

YUV420P (I420p) shader

Snippet to remember for video decoding on gpu

static const char* DEBUG_VS = ""
  "#version 150\n"
  "const vec2 verts[4] = vec2[] ("
  "  vec2(-1.0, 1.0), "
  "  vec2(-1.0, -1.0), "
  "  vec2(1.0, 1.0), "
  "  vec2(1.0, -1.0) "
  ");"
  "const vec2 texcoords[4] = vec2[] ("
  "  vec2(0.0, 0.0), "
  "  vec2(0.0, 1.0), "
  "  vec2(1.0, 0.0), "
  "  vec2(1.0, 1.0) "
  ");"
  "out vec2 v_texcoord;"
  "void main() {"                               
  "  gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0);"
  "  v_texcoord = texcoords[gl_VertexID];"
  "}"
  ;
 
static const char* DEBUG_FS = "" 
  "#version 150\n"
  "uniform sampler2D y_tex;"
  "uniform sampler2D u_tex;"
  "uniform sampler2D v_tex;"
  "in vec2 v_texcoord;"
  "out vec4 fragcolor;"
  "const vec3 R_cf = vec3(1.164383,  0.000000,  1.596027);"
  "const vec3 G_cf = vec3(1.164383, -0.391762, -0.812968);"
  "const vec3 B_cf = vec3(1.164383,  2.017232,  0.000000);"
  "const vec3 offset = vec3(-0.0625, -0.5, -0.5);"
  "void main() {"
  "  float y = texture(y_tex, v_texcoord).r;"
  "  float u = texture(u_tex, v_texcoord).r;"
  "  float v = texture(v_tex, v_texcoord).r;"
  "  vec3 yuv = vec3(y,u,v);"
  "  yuv += offset;"
  "  fragcolor = vec4(0.0, 0.0, 0.0, 1.0);"
  "  fragcolor.r = dot(yuv, R_cf);"
  "  fragcolor.g = dot(yuv, G_cf);"
  "  fragcolor.b = dot(yuv, B_cf);"
  "}";

FBOs with different sized texture attachments

With the framebuffer feature it's possible to render offscreen into what is called color attachments. Color attachments can be textures or render buffers for example.

Although the specification states that you can use different sized color attachments, which is true, you need to be aware that the effective FBO size, is the size of the intersection of the attachments dimensions... aka the smallest dimension. This is important when you e.g. need to use glBlitFramebuffer as it will only blit the smallest area.

See this comment "Notice that there is no restriction based on size. The effective size of the FBO is the intersection of all of the sizes of the bound images (ie: the smallest in each dimension)." on the openGL wiki

Attribute less rendering

By using the build in gl_VertexID variable in your vertex shader and defining a array for vertex positions and texture coordinates you can draw basic triangle strips withouth using a VBO. You still need to create a vertex array object but you don't need to set any attributes or pointers. See the shader below which uses this attribute less rendering technique.

static const char* FULLSCREEN_VS = ""
  "#version 150\n"
 
  "const vec2 verts[4] = vec2[] ("
  "  vec2(-1.0, 1.0), " 
  "  vec2(-1.0, -1.0), "
  "  vec2(1.0, 1.0), "
  "  vec2(1.0, -1.0)"
  ");"
 
  "const vec2 tex[4] = vec2[] ("
  "  vec2(0.0, 1.0), " 
  "  vec2(0.0, 0.0), "
  "  vec2(1.0, 1.0), "
  "  vec2(1.0, 0.0)"
  ");"
 
  "out vec2 v_tex; " 
  "void main() {"
  "  gl_Position = vec4(verts[gl_VertexID], 0.0, 1.0);"
  "  v_tex = tex[gl_VertexID];"
  "}"
  "";
 
static const char* TEX_FS = ""
  "#version 150\n"
  "uniform sampler2D u_tex;"
  "in vec2 v_tex;"
  "out vec4 fragcolor;"
  "void main() {"
  "  vec4 tc = texture(u_tex, v_tex);"
  "  fragcolor.rgb = tc.rgb;"
  "  fragcolor.a = 1.0f;"
  "}"
  "";

By using the above shader and by taking care of your model matrix in a smart way you can draw textures using an orthographic matrix in such a way that the texture can be drawn at any location, size and rotation. In the following code I'm setting up a ortho matrix. See the updated attribute less shader below too.

bool Graphics::setup() 
 
  // tex_pm is a Mat4 and is the projection matrix   
  glGenVertexArrays(1, &tex_vao);
  tex_pm.ortho(0.0f, settings.win_w, settings.win_h, 0.0f, 0.0f, 100.0f);
 
  tex_vs = rx_create_shader(GL_VERTEX_SHADER, TEX_VS);
  tex_fs = rx_create_shader(GL_FRAGMENT_SHADER, TEX_FS);
  tex_prog = rx_create_program(tex_vs, tex_fs);
  glLinkProgram(tex_prog);
  rx_print_shader_link_info(tex_prog);
  glUseProgram(tex_prog);
  glUniformMatrix4fv(glGetUniformLocation(tex_prog, "u_pm"), 1, GL_FALSE, tex_pm.ptr());
 
  return true
}
 
void Graphics::drawTexture(GLuint tex, float x, float y, float w, float h) {
  glUseProgram(tex_prog);
  glActiveTexture(GL_TEXTURE0);
  glBindVertexArray(tex_vao);
 
  float hw = w * 0.5;
  float hh = h * 0.5;
 
  // use the following model matrix to draw the texture at the given (x,y) and (width,height)
  // here I also rotate (around the center)
  tex_mm.identity();
  tex_mm.translate(x + hw, y + hh, 0.0f);
  tex_mm.rotate(45.0f * DEG_TO_RAD, 0.0f, 0.0f, 1.0f);
  tex_mm.scale(hw, hh, 1.0f);
 
  glUniformMatrix4fv(glGetUniformLocation(tex_prog, "u_mm"), 1, GL_FALSE, tex_mm.ptr());
 
  glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
  glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
}

And the (updated) shader:

static const char* TEX_VS = ""
  "#version 150\n"
  "uniform mat4 u_pm;"
  "uniform mat4 u_mm;"
 
  "const vec2 verts[4] = vec2[] ("
  "  vec2(-1.0, 1.0), " 
  "  vec2(-1.0, -1.0), "
  "  vec2(1.0, 1.0), "
  "  vec2(1.0, -1.0)"
  ");"
 
  "const vec2 tex[4] = vec2[] ("
  "  vec2(0.0, 1.0), " 
  "  vec2(0.0, 0.0), "
  "  vec2(1.0, 1.0), "
  "  vec2(1.0, 0.0)"
  ");"
 
  "out vec2 v_tex; " 
  "void main() {"
  "  vec4 vert = vec4(verts[gl_VertexID], 0.0, 1.0);"
  "  gl_Position = vert; "
  "  gl_Position = u_pm * vert;"
  "  gl_Position = u_pm * u_mm * vert;"
  "  v_tex = tex[gl_VertexID];"
  "}"
  "";
 
static const char* TEX_FS = ""
  "#version 150\n"
  "in vec2 v_tex;"
  "out vec4 fragcolor;"
  "void main() {"
  "  fragcolor = vec4(1.0, 0.0, 0.0, 1.0);"
  "}"
  "";