What Is gl_ViewportIndex in GLSL Viewports?

The built-in variable gl_ViewportIndex in the OpenGL Shading Language (GLSL) allows shaders to dynamically route individual primitives to a specific viewport within a multi-viewport array. By assigning an integer index to gl_ViewportIndex, a single draw call can project and clip geometry across multiple viewports simultaneously. This article explores how gl_ViewportIndex operates, how to configure multiple viewports on the host side, its implementation across shader stages, and the primary rendering techniques that benefit from it.

Understanding Multi-Viewport Rendering

Standard OpenGL pipelines map normalized device coordinates (NDC) to window coordinates using a single viewport configured via glViewport. Modern rendering engines frequently require rendering the same scene or geometry from different perspectives or into distinct regions of a frame, such as:

Traditionally, achieving this required multiple draw calls or CPU-side viewport state changes. OpenGL introduced array-based viewport functions—such as glViewportArrayv and glViewportIndexedf—allowing applications to define an array of independent viewports (indexed from 0 to GL_MAX_VIEWPORTS - 1). The variable gl_ViewportIndex serves as the shader-side mechanism to select which viewport in that array processes a given primitive.

Role and Behavior of gl_ViewportIndex

gl_ViewportIndex is an integer output variable (out int gl_ViewportIndex;). When a primitive is processed, the rasterizer inspects the value of gl_ViewportIndex associated with the primitive's provoking vertex:

  1. Viewport Transformation: The geometry is transformed from NDC to the specific pixel rectangle defined by the viewport at index gl_ViewportIndex.
  2. Scissor Testing: If scissor testing is enabled, the primitive is clipped against the corresponding scissor rectangle at index gl_ViewportIndex (configured via glScissorArrayv or glScissorIndexed).
  3. Out-of-Bounds Handling: If the shader writes an index greater than or equal to the maximum supported viewports, the behavior is undefined, though typical drivers clamp the index to 0.

In the fragment shader, gl_ViewportIndex is available as a flat-interpolated input (flat in int gl_ViewportIndex;), allowing the fragment stage to identify which viewport generated the current fragment.

Utilizing gl_ViewportIndex Across Shader Stages

Geometry Shader Approach

In core OpenGL (version 4.1 and higher), gl_ViewportIndex is natively supported in the Geometry Shader (GS). The GS can clone input primitives and emit them with different viewport indices and transformation matrices.

#version 410 core

layout(triangles) in;
layout(triangle_strip, max_vertices = 6) out;

out int gl_ViewportIndex;

uniform mat4 u_ProjectionMatrices[2];

void main()
{
    // Emit primitive to Viewport 0 (e.g., Left Eye)
    gl_ViewportIndex = 0;
    for(int i = 0; i < 3; ++i)
    {
        gl_Position = u_ProjectionMatrices[0] * gl_in[i].gl_Position;
        EmitVertex();
    }
    EndPrimitive();

    // Emit primitive to Viewport 1 (e.g., Right Eye)
    gl_ViewportIndex = 1;
    for(int i = 0; i < 3; ++i)
    {
        gl_Position = u_ProjectionMatrices[1] * gl_in[i].gl_Position;
        EmitVertex();
    }
    EndPrimitive();
}

Vertex Shader Direct Routing

Geometry shaders introduce pipeline overhead when primitive amplification is not strictly necessary. Using extensions such as GL_ARB_shader_viewport_layer_array or GL_AMD_vertex_shader_viewport_index (core in Vulkan and available on modern OpenGL hardware), gl_ViewportIndex can be written directly from the Vertex Shader:

#version 450 core
#extension GL_ARB_shader_viewport_layer_array : require

layout(location = 0) in vec3 in_Position;
layout(location = 1) in int in_InstanceViewportIndex;

out int gl_ViewportIndex;

uniform mat4 u_ViewProjections[4];

void main()
{
    gl_ViewportIndex = in_InstanceViewportIndex;
    gl_Position = u_ViewProjections[in_InstanceViewportIndex] * vec4(in_Position, 1.0);
}

This approach allows instanced rendering to distribute batches of geometry across different viewports without activating the geometry stage.

Key Considerations and Performance