How f16vec4 Reduces Mobile GLSL Register Pressure
Using half-precision four-component vector arithmetic
(f16vec4 or mediump vec4) significantly
reduces register allocation pressure in mobile GLSL shaders by halving
the physical memory footprint required to store transient data. On
mobile graphics processing units (GPUs), general-purpose registers
(GPRs) are scarce, shared resources directly tied to shader execution
concurrency. By downcasting from 32-bit standard precision
(highp/f32) to 16-bit half-precision
arithmetic, compilers can pack vector components into fewer physical
register slots, lowering per-thread register counts, preventing costly
register spilling, and dramatically increasing hardware occupancy.
Register Allocation on Mobile GPUs
Mobile GPU architectures—such as Qualcomm Adreno, ARM Mali, and Imagination PowerVR—rely on massively parallel thread execution to hide texture sampling and arithmetic latency. These architectures allocate a pool of physical registers to concurrently running execution threads (often grouped into waves, warps, or quads).
When a thread requests a high number of registers, the GPU must reduce the total number of active threads it can execute simultaneously. If a shader exceeds the hardware's register budget, the compiler must spill registers to off-chip scratch memory, causing a steep drop in memory bandwidth efficiency and overall frame rate.
50% Storage Reduction Per Vector
A standard 32-bit floating-point vector (f32vec4 or
highp vec4) requires 128 bits of storage (32 bits × 4
components). In contrast, an f16vec4 vector requires only
64 bits of storage (16 bits × 4 components).
Mobile register files are typically organized into 32-bit physical
registers. Storing a 32-bit vector occupies four individual 32-bit
registers. However, using 16-bit floats allows the compiler to pack two
half-precision scalars into a single 32-bit register lane. As a result,
an f16vec4 requires only two 32-bit physical registers,
cutting storage allocation for that variable directly by half.
Maximizing Warp Occupancy and Latency Hiding
Because each execution unit has a fixed register file capacity, reducing the register footprint per thread directly increases thread occupancy:
- Lower Register Usage: Each thread requires fewer hardware registers.
- Higher Active Thread Count: More threads can reside in hardware warps simultaneously.
- Effective Latency Hiding: The GPU scheduler can immediately switch to an alternate warp when the current warp waits on texture fetches, math pipelines, or memory loads.
When shaders operate entirely in highp, register
exhaustion often forces the GPU to run at minimum occupancy, leaving
arithmetic logic units (ALUs) idle during memory stalls.
Native 16-Bit SIMD Execution
Most modern mobile GPUs include dedicated arithmetic pathways for
16-bit floating-point data, commonly offering double-rate FP16
execution. By using f16vec4, the GPU does not need to
allocate extra temporary registers to unpack 16-bit values into 32-bit
formats for calculation.
The arithmetic logic units natively execute SIMD-within-a-register operations directly on the packed 64-bit chunk. This eliminates the need for the compiler to allocate scratch registers to manage intermediate 32-bit conversions.
Eliminating Implicit Conversions
To maintain these register savings, shader code must avoid accidental
type promotion. If an f16vec4 interacts with an untyped
literal float or a 32-bit vector, the GLSL compiler may automatically
promote all operands to 32 bits before performing the calculation. This
introduces temporary 128-bit values into the register file.
Using explicit 16-bit types via extensions like
GL_EXT_shader_explicit_arithmetic_types_float16 or
consistently enforcing mediump qualifiers across variables,
uniforms, and interpolators ensures that variables stay packed
throughout their entire live range, keeping total register usage at its
lowest possible threshold.