How subgroupQuadSwapHorizontal Works in GLSL
The subgroupQuadSwapHorizontal function in GLSL allows
invocations within a 2x2 execution quad to directly swap values
horizontally with their immediate neighbor. This article breaks down the
quad execution model in graphics hardware, explains the lane-mapping
mechanics that govern horizontal data exchanges, and illustrates how
shader invocations execute these operations directly at the register
level without relying on shared memory or memory barriers.
The 2x2 Quad Layout
Modern GPUs execute fragment shaders in aligned 2x2 clusters of
pixels known as quads. These quads are essential for calculating
screen-space derivatives (such as dFdx and
dFdy) used in texture level-of-detail operations. Within a
subgroup—often referred to as a warp or wavefront—threads are assigned
indices that map to these 2x2 arrangements.
In GLSL, using the GL_KHR_shader_subgroup_quad
extension, the four invocations in each quad are arranged in a localized
coordinate grid:
- Lane 0: Top-left pixel
(x, y) - Lane 1: Top-right pixel
(x + 1, y) - Lane 2: Bottom-left pixel
(x, y + 1) - Lane 3: Bottom-right pixel
(x + 1, y + 1)
The Horizontal Swap Mechanism
subgroupQuadSwapHorizontal exchanges the provided
expression's value between adjacent horizontal lanes in the same quad.
The operation targets the X-axis neighbor while keeping the Y-axis
constant.
The exchange follows this mapping:
- Invocation 0 reads the value from Invocation 1, and Invocation 1 reads the value from Invocation 0.
- Invocation 2 reads the value from Invocation 3, and Invocation 3 reads the value from Invocation 2.
At the hardware execution level, this mapping corresponds to a
bitwise XOR of the relative quad index with 1
(relative_lane_id ^ 0x1):
0 (00b) ^ 1 = 1 (01b)
1 (01b) ^ 1 = 0 (00b)
2 (10b) ^ 1 = 3 (11b)
3 (11b) ^ 1 = 2 (10b)
Because the swap occurs across the horizontal boundary, vertical pairings (such as 0 with 2, or 1 with 3) remain unaffected.
Syntax and Implementation
To use this operation, the subgroup quad extension must be enabled in the shader:
#version 460
#extension GL_KHR_shader_subgroup_quad : enable
void main() {
float localValue = ...;
// Retrieve the value from the horizontally adjacent pixel in the quad
float neighborValue = subgroupQuadSwapHorizontal(localValue);
// Compute custom horizontal finite differences
float horizontalDifference = localValue - neighborValue;
}Hardware Execution and Performance
Unlike compute shader shared memory (shared variables)
or texture lookups, subgroupQuadSwapHorizontal does not
access the memory subsystem or GPU caches. Instead, it relies on
hardware shuffle or crossbar network routing within the execution
unit.
This provides several performance characteristics:
- Zero Memory Latency: Data moves directly between SIMD lane registers.
- Implicit Synchronization: Invocations within the
quad execute the instruction in lockstep, eliminating the need for
execution barriers like
memoryBarrierShared()orbarrier(). - Uniform Execution Requirement: All invocations in the quad must participate in the call. If invocations diverge due to branching, inactive invocations will result in undefined values for their corresponding partners.
This horizontal exchange mechanism is commonly used for custom image filtering, spatial reconstruction, anti-aliasing techniques, and building manual finite-difference approximations without accessing off-chip memory.