- Vertex Specification
- Vertex Processing
- Vertex Post-Processing
- Primitive Assembly
- Fragment Shader
- Per-Sample Processing
The specification states that all of these tests and such happen after the Fragment Shader. However, it is possible for these to be performed before the fragment shader. The following operations are able to be performed before the fragment shader, under certain circumstances:
There are two circumstances when these operations can be performed before the fragment shader. It can happen as an implicit optimization, if OpenGL detects that the fragment shader's execution will not interfere with these tests. Or it can happen if the fragment shader explicitly requests it.
If it happens by explicit request of the fragment shader, then all of these operations will happen first. Also, all writes to these buffers will happen first. Therefore, operations done in the fragment shader that affect such writes will be effectively ignored. For example, executing a discard in an early-test fragment shader will not stop the depth or stencil values from being updated (the update is considered part of the test, write masks notwithstanding, nor will it stop the occlusion query counter from being bumped.
OpenGL 4.2 or ARB_shader_image_load_store explicitly requires the pixel ownership test and scissor test to always be performed early, no matter what. Granted, earlier hardware probably did them early anyway, but without Image Load Store functionality, you would never be able to tell the difference (outside of improved performance).
All operations are processed in the following order:
Pixel ownership test
Because the Default Framebuffer is owned by a resource external to OpenGL, it is possible that particular pixels of the default framebuffer are not owned by OpenGL. And therefore, OpenGL cannot write to those pixels. Fragments aimed at such pixels are therefore discarded at this stage of the pipeline.
Generally speaking, if the window you are rendering to is partially obscured by another window, the pixels covered by the other window are no longer owned by OpenGL and thus fail the ownership test. Any fragments that cover those pixels will be discarded. This also includes framebuffer clearing operations.
Note that this test only affects rendering to the default framebuffer. When rendering to a Framebuffer Object, all fragments pass this test.
A rectangular area of the destination framebuffer can be designated as the only valid area for rendering to. All fragments aimed at pixels outside of this rectangle will be discarded at this stage.
The stencil test, when enabled, can cause a fragment to be discarded based on a bitwise operation between the fragment's stencil value and the stencil value stored in the current Stencil Buffer at that fragment's sample position. This allows the user to lay down stencil values in one rendering pass, then conditionally cull fragments based on this pattern.
The depth test, when enabled, allows a fragment to be culled based on a conditional test between the fragment's depth value and the depth value stored in the current Depth Buffer at that fragment's sample position. This is useful to cause geometry to be hidden behind other geometry. The closer geometry lays down depth values that mask the rendering of any fragments behind them, using the proper depth test condition.
Occlusion query updating
If the fragment passed the depth test (and only passing the depth test is checked), then at this point, an active occlusion query will be considered to have had a fragment pass. Therefore, if the query is a GL_SAMPLES_PASSED query, the counter will be incremented. If it is one of the boolean queries, the boolean value will be set to true.
Note that reaching this point does not guarantee that any particular value will actually be written to any particular buffer. The write masks below can turn off writing to particular buffers. The framebuffer's draw buffer settings can effectively ignore one or more fragment values. Indeed, a framebuffer object may be empty entirely, with no images attached at all.
Each of the colors in the fragment can be combined with the corresponding pixel color in the buffer that the fragment will be written to. The result of this blending operation is what will be written.
If the image being read from is in the sRGB colorspace and GL_FRAMEBUFFER_SRGB is currently enabled, then the color read for the blending operation will be converted to linear RGB before blending with the fragment color.
If GL_FRAMEBUFFER_SRGB is currently enabled, then for each fragment color that is being written to an image with an sRGB colorspace image format, the RGB components of the color are converted from linear to sRGB.
When a color is being written to a framebuffer image, the image may have less precision than the incoming value. This only matters when writing floating-point values to Normalized Integer Image Formats. For a given image format precision and a given color value, there will be at most two colors that could be used for that color: the one from rounding up and one from rounding down.
When dithering is disabled, which color is chosen is implementation-defined, but it is not allowed to depend on the fragment's position in the window. For low-precision image formats, a subtle gradation in floating-point values is rendered as several sharp and distinct bands.
Dithering can be used to avoid this banding. Dithering algorithms essentially fake a smooth gradient by varying which color is selected based on the position of the fragment. Therefore, if the color value is half-way between the two representable colors, then half of the pixels will be one color and half the other.
Dithering is enabled by enabling GL_DITHER. Dithering is enabled by default.
Fragment colors can be conditionally combined with the corresponding value in the framebuffer by performing boolean operations on them. This overrides Blending, and it only works for colors that are writing to integer (normalized or not) Image Formats that are not using sRGB writing to sRGB images.
Writes to particular buffers can be masked off. This also allows masking off of particular components of those writes, such that you can only update the red color component if you want. Each output buffer can have its own mask.