gl_PerVertex Redeclaration in Separable GLSL Programs
This article explores how explicit gl_PerVertex
redeclaration resolves interface mismatches in OpenGL separable shader
programs. In modular rendering pipelines, shaders are compiled
independently rather than as a monolithic whole, which removes the
driver's ability to automatically reconcile built-in stage inputs and
outputs. By enforcing explicit redeclaration of the
gl_PerVertex interface block, GLSL ensures that producer
and consumer stages agree on exact memory layouts, preventing silent
undefined behavior, structural size discrepancies, and interface
validation failures during pipeline execution.
The Role of Separable Programs
In traditional OpenGL, shaders are compiled and linked together into a single monolithic program object. During this monolithic link step, the compiler analyzes every stage simultaneously. It strips out unused built-in outputs and standardizes the memory offsets between stages such as vertex, tessellation, and geometry shaders.
Separable shader programs (introduced in OpenGL 4.1 via
GL_ARB_separate_shader_objects) alter this workflow.
Separable programs allow stages to be compiled and linked into isolated
program objects, which are then bound together dynamically at runtime
using Program Pipeline Objects (glGenProgramPipelines).
While this modularity increases flexibility and avoids recompilation
bottlenecks, it deprives the GLSL linker of global cross-stage
visibility.
The Default
gl_PerVertex Discrepancy
GLSL provides built-in attributes like gl_Position,
gl_PointSize, gl_ClipDistance, and
gl_CullDistance through an implicitly declared interface
block named gl_PerVertex.
The implicit definition of gl_PerVertex is not identical
across all shader stages. For example, a vertex shader might only write
to gl_Position, while a geometry or tessellation shader
might natively recognize or allocate space for clip distances and point
sizes. In a monolithic program, the linker inspects consumer stages,
prunes unreferenced members, and aligns the underlying struct.
In a separable program, an upstream stage (such as a vertex shader)
has no knowledge of what a downstream stage (such as a geometry shader)
expects. If left to implicit declarations, the vertex stage might emit a
gl_PerVertex layout with four floats for
gl_Position, whereas the geometry stage might expect an
internal structure containing space for clipping planes and point sizes.
This divergence results in a structural layout mismatch across the stage
interface, corrupting vertex data fetches.
How Redeclaration Enforces Compatibility
To prevent interface linking failures, the GLSL specification
mandates that if any member of gl_PerVertex is used in a
separable program (or if certain features like clip distances are
enabled), the application must explicitly redeclare the
gl_PerVertex block in all communicating stages.
An explicit redeclaration replaces the default, stage-specific built-in block with a user-defined subset:
// In the Vertex Shader (output interface)
out gl_PerVertex {
vec4 gl_Position;
float gl_PointSize;
};
// In the Geometry Shader (input interface)
in gl_PerVertex {
vec4 gl_Position;
float gl_PointSize;
} gl_in[];The redeclaration rule eliminates ambiguity by functioning as a strict contract between stages:
- Exact Structural Equivalence: The GLSL linker requires that interface blocks matching by name between separate program stages contain the exact same members, declared in the exact same order, with identical qualification.
- Fixed Member Offsets: By explicitly listing only
the active members (such as
gl_Positionandgl_PointSize), both programs agree on the size, stride, and byte offset of each field within the vertex data stream. - Link-Time Validation: When the pipeline object
executes stage interface validation (via
glValidateProgramPipeline), the driver compares the explicit block signatures. Because both stages define the identical layout, validation passes without requiring cross-stage binary patching at runtime.
By removing the driver's reliance on implicit layout assumptions, the
gl_PerVertex redeclaration rule guarantees predictable,
high-performance data transfer across decoupled shader stages.