How GLSL bitCount Counts Set Bits in an Integer
This article explains how the bitCount function works in
OpenGL Shading Language (GLSL) to calculate the number of set bits in an
integer. It covers the underlying hardware mechanisms—such as dedicated
GPU population count instructions—as well as the algorithmic fallbacks
based on parallel bitwise arithmetic and the handling of signed versus
unsigned types.
What is bitCount in GLSL?
The bitCount function is a built-in GLSL intrinsic
introduced in GLSL 4.00 (and via the GL_ARB_gpu_shader5
extension). It takes an integer—scalar or vector, signed or unsigned—and
returns the number of bits set to 1 in the binary
representation of each component. In computer science, this operation is
commonly referred to as a population count
(popcount) or Hamming weight.
The function supports the following types:
intanduint(returningint)ivec2,ivec3,ivec4(returningivec2,ivec3,ivec4)uvec2,uvec3,uvec4(returningivec2,ivec3,ivec4)
Hardware-Level Execution
On modern graphics processing units (GPUs) from NVIDIA, AMD, Intel,
and Apple, bitCount is generally not computed through a
software loop. Instead, the shader compiler translates
bitCount directly into a single hardware assembly
instruction:
- NVIDIA: Translates to the
POPC(Population Count) instruction in PTX/SASS. - AMD: Translates to
V_BCNT_U32_B32(or similar variants) in GCN/RDNA ISA. - Intel: Translates to
cbitin Intel GPU assembly.
Because dedicated Arithmetic Logic Units (ALUs) execute this
instruction, bitCount typically runs in a single clock
cycle per thread, making it substantially faster than manual looping or
lookup tables.
Algorithmic Implementation (Software Fallback)
When dedicated hardware instructions are unavailable or emulated,
compilers implement bitCount using a divide-and-conquer
parallel bit-counting algorithm. This algorithm masks and shifts bits in
parallel rather than evaluating bits sequentially:
int software_popcount(uint x) {
x = x - ((x >> 1) & 0x55555555u);
x = (x & 0x33333333u) + ((x >> 2) & 0x33333333u);
x = (x + (x >> 4)) & 0x0F0F0F0Fu;
x = x + (x >> 8);
x = x + (x >> 16);
return int(x & 0x0000003Fu);
}- Pairs of bits: The first step counts the number of
set bits in each 2-bit field using the mask
0x55555555(0101...). - Nibbles (4-bit): The second step sums adjacent
2-bit counts into 4-bit fields using
0x33333333(0011...). - Bytes (8-bit): The sums are combined into 8-bit
fields with
0x0F0F0F0F(00001111...). - Final Accumulation: Subsequent shifts
(
>> 8and>> 16) add all byte sums together, masking the final result with0x3F(decimal 63, enough to hold a maximum value of 32).
Signed vs. Unsigned Behavior
GLSL accepts both signed (int) and unsigned
(uint) integers in bitCount. For signed types,
the function operates on the two's complement binary representation.
Negative numbers include set bits for the sign and value
representations. For example:
bitCount(1)returns1(...00000001).bitCount(-1)returns32because-1in 32-bit two's complement is0xFFFFFFFF(all 32 bits set).
Example GLSL Usage
#version 450
layout(local_size_x = 64) in;
layout(std430, binding = 0) buffer DataBuffer {
uint inputValues[];
int outputCounts[];
};
void main() {
uint id = gl_GlobalInvocationID.x;
// Calculates the number of set bits directly using the hardware popcount
outputCounts[id] = bitCount(inputValues[id]);
}