Purpose of subpass_input in GLSL Fragment Shaders
In Vulkan-targeted GLSL, declaring input attachments with the
subpass_input type allows fragment shaders to directly read
framebuffer data written by an earlier subpass within the same render
pass. This mechanism is primarily designed to optimize multipass
rendering techniques—such as deferred shading—by enabling high-bandwidth
pixel reads that can remain entirely within on-chip tile memory,
drastically reducing external memory bandwidth and power
consumption.
Pixel-Local Data Access
Unlike traditional texture samplers that can sample across arbitrary
coordinates, filtering modes, and mip levels, a
subpass_input enforces a strict one-to-one mapping. A
fragment shader executing at pixel coordinate \((x, y)\) can only read the data generated
at coordinate \((x, y)\) by a preceding
subpass. Because the GPU does not require texture filtering or
coordinates transformation, the data is retrieved using the GLSL
built-in function subpassLoad() rather than
texture().
Leveraging Tile-Based Architectures
The primary technical advantage of subpass_input targets
Tile-Based Deferred Rendering (TBDR) GPUs commonly found in mobile
devices, as well as modern desktop architectures. These GPUs process
frames in small, local memory tiles (often 16x16 pixels). When using
multiple subpasses connected via input attachments:
- Render targets written in an initial subpass (such as G-buffers containing albedo, normals, or depth) remain in ultra-fast, on-chip tile cache.
- Subsequent subpasses consume this data directly from the cache without writing it out to high-latency system memory (VRAM).
- Intermediate render targets never need to be resolved to main memory if they are not needed in later render passes.
GLSL Declaration and Usage
To use an input attachment, it must be bound with a layout qualifier that defines its attachment index corresponding to the Vulkan subpass description:
#version 450
// Declare the input attachment with its specific index
layout (input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput inputAlbedo;
layout (input_attachment_index = 1, set = 0, binding = 1) uniform subpassInput inputNormal;
layout (location = 0) out vec4 outColor;
void main() {
// Read the current pixel's data without texture coordinates
vec4 albedo = subpassLoad(inputAlbedo);
vec3 normal = subpassLoad(inputNormal).rgb;
// Perform lighting calculations
outColor = vec4(albedo.rgb * max(dot(normal, vec3(0.0, 1.0, 0.0)), 0.0), 1.0);
}There are also specialized variants such as
subpassInputMS for multisampled attachments, which require
specifying the sample index during the read operation using
subpassLoad(inputAttachment, sampleIndex).
Common Use Cases
The most common application of subpass_input is deferred
lighting, where the first subpass writes geometry attributes to multiple
render targets, and the second subpass consumes those targets as input
attachments to compute final lighting. Other applications include
order-independent transparency, post-processing effects, and custom
depth/stencil masking operations where subsequent steps depend entirely
on the immediate pixel output of the previous stage.