GL_EXT_shader_framebuffer_fetch Programmable Blending
This article explores the
GL_EXT_shader_framebuffer_fetch extension in OpenGL and
OpenGL ES, explaining its architectural purpose and implementation
details. It covers the limitations of legacy fixed-function hardware
blending, demonstrates how this extension grants fragment shaders read
access to existing framebuffer data, and details how developers can
implement complex, single-pass programmable blending algorithms directly
within GLSL shaders.
What is GL_EXT_shader_framebuffer_fetch?
The GL_EXT_shader_framebuffer_fetch extension is an
OpenGL and OpenGL ES feature that allows a fragment shader to read the
current color, depth, or stencil data of the framebuffer at the exact
pixel location currently being processed.
Under standard rendering pipelines, the pipeline separates fragment
shading from the framebuffer operations stage. A fragment shader
computes an output color, and the hardware's fixed-function blending
unit combines that output with the existing destination color in memory.
Reading from the active render target while simultaneously writing to it
is traditionally disallowed because it creates read-after-write hazards
and pipeline stalls. GL_EXT_shader_framebuffer_fetch
resolves this by exposing the destination pixel data directly to the
fragment shader during execution.
The Limitations of Fixed-Function Blending
Standard OpenGL uses fixed-function APIs such as
glBlendFunc and glBlendEquation to handle
transparency and composition. While computationally efficient, these
fixed units support only a limited set of arithmetic operations:
- Linear combinations (add, subtract, reverse subtract)
- Component-wise minimum and maximum
- Basic source and destination factor scaling
Complex blending equations—such as Photoshop-style blend modes (Multiply, Screen, Overlay, Soft Light), HDR tone mapping with destination values, or non-photorealistic rendering effects—cannot be expressed through these fixed formulas. Previously, developers had to resort to multipass rendering, ping-ponging textures back and forth via Framebuffer Objects (FBOs). This introduced significant memory bandwidth overhead, cache thrashing, and pipeline synchronization penalties.
How the Extension Enables Programmable Blending
GL_EXT_shader_framebuffer_fetch eliminates the need for
multipass workarounds by turning the fragment shader itself into a
programmable blending stage. It modifies GLSL to treat fragment outputs
as readable inputs.
1. Unified Read and Write Access
In modern GLSL (OpenGL ES 3.0+), the extension allows fragment output
variables to be declared with the inout storage qualifier.
In legacy GLSL (OpenGL ES 2.0), it provides read access to the built-in
array gl_LastFragData.
When the GPU executes a fragment:
- The hardware populates the
inoutvariable with the current color stored in the framebuffer at that fragment's coordinate before shader execution begins. - The shader code reads this value (the destination color), performs custom mathematical operations combining it with the source color, and modifies the variable.
- Upon shader completion, the final value written to the
inoutvariable replaces the framebuffer value directly, bypassing fixed-function blending.
2. GLSL Implementation Example
To implement programmable blending using this extension in GLSL ES
3.00, declare the extension and define the fragment output as
inout:
#version 300 es
#extension GL_EXT_shader_framebuffer_fetch : require
precision mediump float;
// Declare the output variable as inout to read the existing framebuffer color
layout(location = 0) inout vec4 fragColor;
uniform vec4 uSourceColor;
void main()
{
// fragColor initially contains the destination color from the framebuffer
vec4 dstColor = fragColor;
vec4 srcColor = uSourceColor;
// Example: Implementing an "Overlay" blend mode directly in the shader
vec3 blendedRGB;
for (int i = 0; i < 3; ++i) {
if (dstColor[i] < 0.5) {
blendedRGB[i] = 2.0 * dstColor[i] * srcColor[i];
} else {
blendedRGB[i] = 1.0 - 2.0 * (1.0 - dstColor[i]) * (1.0 - srcColor[i]);
}
}
// Write back the final blended result
fragColor = vec4(blendedRGB, srcColor.a);
}When using this approach, fixed-function blending must be disabled
via glDisable(GL_BLEND) to prevent the hardware from
attempting to blend the final output a second time.
Architecture and Performance Advantages
The extension is especially effective on Tile-Based Deferred Rendering (TBDR) architectures, which are standard in mobile GPUs (such as ARM Mali, Qualcomm Adreno, and Imagination PowerVR).
In TBDR architectures, rendering operations occur inside ultra-fast,
on-chip tile memory before being resolved to system RAM.
GL_EXT_shader_framebuffer_fetch reads directly from this
fast local cache rather than external memory. This allows complex color
blending, deferred shading decoders, and custom compositing pipelines to
run at native hardware speeds without incurring costly memory bandwidth
and thermal overhead.