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:

  • int and uint (returning int)
  • ivec2, ivec3, ivec4 (returning ivec2, ivec3, ivec4)
  • uvec2, uvec3, uvec4 (returning ivec2, 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 cbit in 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);
}
  1. Pairs of bits: The first step counts the number of set bits in each 2-bit field using the mask 0x55555555 (0101...).
  2. Nibbles (4-bit): The second step sums adjacent 2-bit counts into 4-bit fields using 0x33333333 (0011...).
  3. Bytes (8-bit): The sums are combined into 8-bit fields with 0x0F0F0F0F (00001111...).
  4. Final Accumulation: Subsequent shifts (>> 8 and >> 16) add all byte sums together, masking the final result with 0x3F (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) returns 1 (...00000001).
  • bitCount(-1) returns 32 because -1 in 32-bit two's complement is 0xFFFFFFFF (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]);
}