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.