How to Fetch Individual Samples in GLSL sampler2DMS

Multisample antialiasing (MSAA) stores multiple color, depth, or stencil samples per pixel, requiring dedicated handling in shaders to access individual subpixel data. In GLSL, multisample textures bound to samplers such as sampler2DMS bypass standard texture filtering and cannot be sampled using the traditional texture() function. Instead, shaders must use the texelFetch function with non-normalized integer coordinates and an explicit sample index to retrieve discrete sample points for custom resolving, edge detection, or deferred shading.

The sampler2DMS Sampler Type

Standard 2D textures are accessed via sampler2D, which supports normalized coordinates [0.0, 1.0] and hardware filtering like bilinear or trilinear interpolation. Multisample textures do not support standard filtering or mipmaps because each texel holds multiple distinct color or depth values.

GLSL provides specialized multisample sampler types:

  • sampler2DMS (floating-point data)
  • isampler2DMS (signed integer data)
  • usampler2DMS (unsigned integer data)
  • sampler2DMSArray (multisample texture arrays)

Retrieving Samples with texelFetch

To access an individual sample point from a sampler2DMS, use the built-in texelFetch function overload:

gvec4 texelFetch(gsampler2DMS sampler, ivec2 P, int sample);

Parameters:

  1. sampler: The multisample texture uniform (e.g., uniform sampler2DMS u_TextureMS;).
  2. P: The texel coordinate as an integer vector (ivec2). Unlike texture(), which uses normalized coordinates (0.0 to 1.0), texelFetch expects non-normalized pixel coordinates ranging from (0, 0) to (width - 1, height - 1).
  3. sample: A zero-based integer specifying which subpixel sample to fetch, ranging from 0 to N - 1, where N is the total number of samples allocated for the texture (e.g., 2, 4, 8, or 16).

Querying Sample Count Dynamically

To iterate over all samples without hardcoding the sample count in your shader, GLSL provides the textureSamples function (available in GLSL 4.0 and later):

int numSamples = textureSamples(u_TextureMS);

Example: Manual Resolve in a Fragment Shader

A standard MSAA resolve averages all subpixel samples to produce the final pixel color. The following fragment shader demonstrates manual resolving using texelFetch:

#version 330 core

uniform sampler2DMS u_MultisampleTexture;
out vec4 FragColor;

void main()
{
    // Convert normalized screen coordinates to integer pixel coordinates
    ivec2 texCoord = ivec2(gl_FragCoord.xy);

    // Query or define the number of samples
    const int NUM_SAMPLES = 4;
    vec4 accumulatedColor = vec4(0.0);

    // Retrieve and accumulate each sample individually
    for (int i = 0; i < NUM_SAMPLES; ++i)
    {
        vec4 sampleColor = texelFetch(u_MultisampleTexture, texCoord, i);
        accumulatedColor += sampleColor;
    }

    // Compute the box-filter average
    FragColor = accumulatedColor / float(NUM_SAMPLES);
}

Why Manual Sample Retrieval Matters

Using texelFetch on sampler2DMS gives graphics pipelines granular control over subpixel data:

  • Custom Resolves: Allows non-linear tone mapping, HDR luminance-weighted averaging, or depth-preserving filters that prevent high-luminance aliasing or color bleeding.
  • Deferred Shading with MSAA: Enables running expensive lighting calculations once per pixel for non-edge fragments, and per-sample only along silhouette edges where sample colors diverge.
  • Depth and Stencil Testing: Permits manual per-sample depth testing in custom post-processing effects and screen-space ambient occlusion (SSAO).