How subpassLoad Reads Input Attachments in Vulkan GLSL

This article explains how the subpassLoad GLSL function accesses pixel data from input attachments inside Vulkan render passes. It covers the mechanics of pixel-local data fetching, the distinction between standard texture sampling and input attachments, memory interactions on tile-based architectures, and the required shader syntax and pipeline dependencies.

The Purpose of Input Attachments

In Vulkan, an input attachment is a specialized image view bound to a render pass subpass that allows a fragment shader to read values previously written by an earlier subpass. Unlike standard textures accessed via samplers, input attachments are restricted to reading only the data associated with the current fragment's pixel coordinate.

This restriction enables significant performance optimizations, particularly on Tile-Based Deferred Rendering (TBDR) architectures commonly found in mobile devices. By constraining reads to the current pixel, the GPU can keep framebuffer data within high-speed, on-chip tile memory (transient storage) between subpasses, entirely bypassing costly roundtrips to main system memory (VRAM).

How subpassLoad Operates

The subpassLoad function acts as an intrinsic fetch operation. When invoked within a fragment shader, it retrieves the color, depth, or stencil data directly associated with the executing fragment's coordinates (gl_FragCoord.xy).

Key characteristics of subpassLoad include:

  • Implicit Coordinate Mapping: Unlike texture() or texelFetch(), subpassLoad does not take UV coordinates or integer texel coordinates as an argument. The hardware automatically maps the read operation to the exact pixel location of the current fragment.
  • No Filtering: The fetch does not perform bilinear, trilinear, or anisotropic filtering. It retrieves raw, un-normalized texel data directly from the attachment.
  • Multisample Support: For multisampled attachments, subpassLoad accepts an integer sample index to fetch a specific sample from the pixel location.

GLSL Declaration and Syntax

To use subpassLoad, the attachment must be declared in GLSL as a subpassInput uniform type with an explicit input_attachment_index.

#version 450

// Declare the input attachment bound to index 0
layout(input_attachment_index = 0, set = 0, binding = 0) uniform subpassInput uDepthAttachment;
layout(input_attachment_index = 1, set = 0, binding = 1) uniform subpassInput uColorAttachment;

// Multisampled variants use subpassInputMS
layout(input_attachment_index = 2, set = 0, binding = 2) uniform subpassInputMS uMSAttachment;

layout(location = 0) out vec4 outFinalColor;

void main() 
{
    // Reading single-sampled attachments:
    vec4 previousColor = subpassLoad(uColorAttachment);
    float depthValue   = subpassLoad(uDepthAttachment).r;

    // Reading multisampled attachments requires an explicit sample index:
    int sampleIndex = 2;
    vec4 msColor = subpassLoad(uMSAttachment, sampleIndex);

    outFinalColor = previousColor * depthValue;
}

The input_attachment_index maps directly to the array index specified in the pInputAttachments field of the VkSubpassDescription structure on the host side.

Synchronization and Subpass Dependencies

Because subpassLoad reads data produced within the same render pass, proper execution order and memory visibility must be enforced via VkSubpassDependency. Without explicit dependencies, the GPU may attempt to read an attachment before the writing subpass has completed its output.

A typical dependency configuration includes:

  • Source Stage: VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT (or EARLY_FRAGMENT_TESTS_BIT / LATE_FRAGMENT_TESTS_BIT for depth).
  • Destination Stage: VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT.
  • Source Access Mask: VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT (or DEPTH_STENCIL_ATTACHMENT_WRITE_BIT).
  • Destination Access Mask: VK_ACCESS_INPUT_ATTACHMENT_READ_BIT.
  • Dependency Flags: Typically marked with VK_DEPENDENCY_BY_REGION_BIT, which specifies that the dependency is local to the frame's sub-regions or tiles, enabling hardware tile caches to remain un-flushed.

Architectural Constraints

Because subpassLoad strictly binds to the current fragment coordinate, it cannot be used for operations requiring neighborhood pixel awareness, such as Gaussian blurs, bloom, or post-process edge detection. When neighbor-pixel sampling is necessary, the render pass must be ended, and the image must be bound as a standard sampled texture (sampler2D) using standard texture coordinates.