io7m | archive (zip, signature)

Fragment Shader Outputs - Through The Ages

The following is a quick survey of how the outputs of fragment shaders have been declared and used since the inception of GLSL. The intention is to provide information useful for developers attempting to support a wide range of versions of OpenGL in their software.

GLSL 1.10 (OpenGL 2.0)

In GLSL 1.10, fragment shaders produced a single colour value, writing that value to a predefined variable named gl_FragColor, of type vec4 (full intensity red, in this case):

#version 110

void
main (void)
{
  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
}

Some hardware supported multiple draw buffers, which effectively allowed fragment shaders to produce multiple colour values per execution. This would typically be used for so-called deferred rendering, or for effects such as stereoscopic rendering.

In order to produce multiple colour values from a fragment shader, the values were instead written to a predefined variable gl_FragData, of type vec4[], with values assigned to gl_FragData[i] being written to draw buffer i. In typical error-prone OpenGL style, writing to an element gl_FragData[i] where i >= GL_MAX_DRAW_BUFFERS would result in undefined behaviour. Page 301 of the OpenGL 2.1 specification (Section 6.2 "State tables") states that the minimum required value of GL_MAX_DRAW_BUFFERS is 1, which effectively allowed implementations to support any number of draw buffers, as long as they at least supported one.

#version 110

void
main (void)
{
  gl_FragData[0] = vec4(1.0, 0.0, 0.0, 1.0);
  gl_FragData[1] = vec4(0.0, 1.0, 0.0, 1.0);
  gl_FragData[2] = vec4(0.0, 0.0, 1.0, 1.0);
}

OpenGL ES 2 was defined against the OpenGL 2.1 specification, but added the rather severe restriction that fragment shaders were only allowed to have one output. The specification does not mention GL_MAX_DRAW_BUFFERS and page 89 of the OpenGL ES 2 specification (Section 3.8 - "Fragment Shaders", "Shader Outputs") states that programs may only assign to either gl_FragData[0] or gl_FragColor, but not both.

GLSL 1.30 (OpenGL 3.0)

With the release of OpenGL 3.0, most of the API was deprecated. Likewise, much of what was defined in GLSL < 1.30 was also deprecated. As of 1.30, programmers were required to declare named fragment shader outputs rather than assigning to the built-in gl_FragData[] or gl_FragColor variables.

#version 130

out vec4 out_0;

void
main (void)
{
  out_0 = vec4(1.0, 0.0, 0.0, 1.0);
}

When only one output was declared, values written to the output were implicitly written to draw buffer 0. However, when multiple outputs were declared, the values written to each output were essentially written to completely arbitrary draw buffers. This behaviour was obviously quite unhelpful, to say the least. Therefore, a function glBindFragDataLocation was added to the OpenGL API to allow named fragment shader outputs to be associated with draw buffer indices.

#version 130

out vec4 out_r;
out vec4 out_g;
out vec4 out_b;

void
main (void)
{
  out_r = vec4(1.0, 0.0, 0.0, 1.0);
  out_g = vec4(0.0, 1.0, 0.0, 1.0);
  out_b = vec4(0.0, 0.0, 1.0, 1.0);
}

glBindFragDataLocation(program, 0, "out_r");
glBindFragDataLocation(program, 1, "out_g");
glBindFragDataLocation(program, 2, "out_b");

Page 391 of the OpenGL 3.0 specification (Section 6.2 "State tables") specifies that the minimum value of GL_MAX_DRAW_BUFFERS is 8, meaning that implementations are effectively required to support up to eight fragment shader outputs.

GLSL 3.30 (OpenGL 3.3)

With the release of OpenGL 3.3, it became possible to use layout qualifiers to statically associate fragment shader outputs with draw buffers in the GLSL source code.

#version 330

layout(location = 0) out vec4 out_r;
layout(location = 1) out vec4 out_g;
layout(location = 2) out vec4 out_b;

void
main (void)
{
  out_r = vec4(1.0, 0.0, 0.0, 1.0);
  out_g = vec4(0.0, 1.0, 0.0, 1.0);
  out_b = vec4(0.0, 0.0, 1.0, 1.0);
}

OpenGL ES 3 was defined against the OpenGL 3.3 specification, but for some reason did not include the glBindFragDataLocation function, placing a hard requirement on the use of layout qualifiers.

The obvious consequence of this was that programs, while executing on OpenGL ES 3 implementations, were required to configure the associations between draw buffers and fragment shader outputs statically, in GLSL ES source. However, the same programs, while executing on OpenGL [3.0, 3.2] implementations, were required to configure the associations between draw buffers and fragment shader outputs at runtime via the OpenGL API.

Page 263 of the OpenGL ES 3.0 specification (Section 6.2 "State tables") specifies that the minimum value of GL_MAX_DRAW_BUFFERS is 4, meaning that implementations are effectively required to support up to four fragment shader outputs.

Summary

To summarize, for fragment shaders that only have one output:

For fragment shaders that have multiple outputs: