GLSL xfb_buffer and xfb_offset Layout Qualifiers

The layout(xfb_buffer = N, xfb_offset = M) qualifier enables in-shader configuration of transform feedback in modern OpenGL (4.4+) and the GL_ARB_enhanced_layouts extension. Instead of declaring output varyings on the CPU using glTransformFeedbackVaryings before linking, this layout qualifier allows you to define the target buffer binding indices and byte offsets directly inside your GLSL shader source. This article explains how xfb_buffer and xfb_offset work together to route and align vertex output data into specific GPU buffer objects.

Understanding xfb_buffer and xfb_offset

The qualifier is applied to output variables in the last active shader stage prior to rasterization (typically the Vertex, Tessellation Evaluation, or Geometry shader).

  • xfb_buffer = N: Identifies which transform feedback buffer binding point receives the variable. The index N is an integer from 0 to GL_MAX_TRANSFORM_FEEDBACK_BUFFERS - 1. This corresponds directly to the target slot bound via glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, N, buffer) or glBindBufferRange.
  • xfb_offset = M: Specifies the exact byte offset M within the transform feedback buffer record where this specific variable begins.

Separate vs. Interleaved Recording

By combining these two qualifiers, you can structure your capture data into either separate buffers or interleaved streams without calling API-side configuration functions.

Separate Buffers

To write different attributes into separate buffer objects, assign each variable a distinct xfb_buffer index:

layout(xfb_buffer = 0, xfb_offset = 0) out vec4 outPosition;
layout(xfb_buffer = 1, xfb_offset = 0) out vec3 outNormal;

In this setup, outPosition streams into buffer binding 0, and outNormal streams into buffer binding 1, both starting at byte offset 0 of their respective per-vertex records.

Interleaved Buffers

To write multiple attributes into a single shared buffer, assign them the same xfb_buffer index and increment the xfb_offset so that variables do not overlap:

layout(xfb_buffer = 0, xfb_offset = 0)  out vec4 outPosition; // 16 bytes (bytes 0-15)
layout(xfb_buffer = 0, xfb_offset = 16) out vec3 outNormal;   // 12 bytes (bytes 16-27)
layout(xfb_buffer = 0, xfb_offset = 28) out vec2 outTexCoord; // 8 bytes  (bytes 28-35)

Here, all three attributes are interleaved into buffer binding 0. A single vertex consumes 36 bytes per record.

Stride Configuration with xfb_stride

When interleaving variables manually, OpenGL can automatically calculate the vertex record stride if variables are packed consecutively. However, you can explicitly enforce a stride using xfb_stride:

layout(xfb_buffer = 0, xfb_stride = 48) out; // Sets global stride for buffer 0
layout(xfb_buffer = 0, xfb_offset = 0)  out vec4 outPosition;
layout(xfb_buffer = 0, xfb_offset = 16) out vec3 outNormal;

An explicit xfb_stride is required if you want custom padding at the end of each vertex record (for example, aligning each vertex to a 48-byte boundary instead of 28 bytes).

Rules and Validation Constraints

When using explicit transform feedback layouts, several validation rules apply:

  1. Alignment: The byte offset M must be a multiple of 4 bytes (the component size of basic float, integer, and unsigned integer types).
  2. No Overlapping: Variables targeting the same xfb_buffer must not have overlapping byte ranges within the record.
  3. Array Packing: If an array is captured, each element is placed sequentially starting at xfb_offset, with each element consuming its standard size without extra alignment gaps unless explicitly structured.
  4. Link-Time Validation: If any shader stage uses xfb_buffer or xfb_offset, it overrides any configuration specified by glTransformFeedbackVaryings. Mixing CPU-side varying declarations with shader-side xfb_* qualifiers for the same program will cause a link error.