What Is the Difference Between in, out, and inout in GLSL?

In the OpenGL Shading Language (GLSL), function parameters use storage qualifiers—specifically in, out, and inout—to control how data flows between the caller and the function body. Unlike languages like C++ that use pointers or memory references, GLSL relies on value-copy semantics to pass data into and out of functions. This guide explains the distinct behavior, lifecycle, and use cases of each parameter qualifier so you can write predictable, high-performance shader routines.

The Copy-In / Copy-Out Semantic Model

GLSL executes across highly parallel GPU hardware that typically does not support traditional memory address dereferencing or pointer arithmetic. To maintain performance and safety, GLSL functions use a copy-in / copy-out execution model:

  • Copy-in: Values from the caller's arguments are copied into local parameter variables when the function begins.
  • Copy-out: Values from the local parameter variables are copied back to the caller's arguments right before the function returns.

The qualifiers determine whether a parameter undergoes copy-in, copy-out, or both.

The in Qualifier (Pass by Value / Read-Only Input)

The in qualifier signifies that a parameter is strictly an input to the function. It is copied into the function scope at invocation time.

  • Default behavior: If you omit a qualifier entirely (e.g., void compute(float val)), GLSL automatically treats it as in.
  • Caller safety: You can modify the variable inside the function body, but any changes remain local to that function and will not reflect in the caller's original variable.
  • Requirements: The caller must pass an initialized value or expression (such as a constant or the result of another operation).
void computeBrightness(in vec3 color, in float factor) {
    // Modifications to 'color' or 'factor' do not alter the caller's variables
    color *= factor; 
}

The out Qualifier (Write-Only Output)

The out qualifier defines a parameter used to return data back to the calling scope. It undergoes only the copy-out step.

  • Uninitialized upon entry: When the function begins, the out variable has an undefined/uninitialized value within the function. You should not read from an out parameter before writing to it.
  • Caller modification: Whatever value is written to the parameter during the function execution is written back to the caller's variable upon exit.
  • Requirements: The caller must provide a writable l-value (e.g., an existing variable), not a literal constant or expression.
void getViewportBounds(out vec2 minBound, out vec2 maxBound) {
    // Values are written and copied back to the caller upon return
    minBound = vec2(0.0, 0.0);
    maxBound = vec2(1920.0, 1080.0);
}

The inout Qualifier (Read-Write Input and Output)

The inout qualifier combines the behavior of both in and out. It performs both the copy-in upon entry and the copy-out upon exit.

  • Read and write access: The parameter receives the initial value from the caller, can be read and mutated during function execution, and passes the updated value back to the caller when finished.
  • Use case: Ideal for in-place modifications, iterative operations, stateful updates, or accumulator routines.
  • Requirements: The caller must provide an initialized, writable l-value variable.
void applyVignette(inout vec3 color, in float radius) {
    // Reads the incoming color value and updates it in-place
    color *= smoothstep(0.8, 0.2, radius);
}

Qualifier Comparison

Qualifier Direction Initial Value on Entry Written Back to Caller Default Qualifier Allowed Arguments
in Caller \(\rightarrow\) Function Value of caller's argument No Yes Variables, constants, expressions
out Function \(\rightarrow\) Caller Undefined / Uninitialized Yes No Writable variables (l-values) only
inout Caller \(\leftrightarrow\) Function Value of caller's argument Yes No Writable variables (l-values) only

Practical Code Example

The following shader function demonstrates all three qualifiers working together to process geometric attributes:

void transformPoint(
    in mat4 modelMatrix,      // Read-only input transformation
    inout vec4 position,      // Transformed in-place
    out vec3 surfaceNormal    // Computed and exported to caller
) {
    // position is read (in) and modified (out)
    position = modelMatrix * position;

    // surfaceNormal is purely calculated and exported (out)
    surfaceNormal = normalize(mat3(modelMatrix) * vec3(0.0, 1.0, 0.0));
}

Understanding these qualifiers ensures correct data propagation across shader pipelines while preventing unexpected uninitialized reads or lost state updates during GPU execution.