GLSL Break and Continue Rules and Limitations
In the OpenGL Shading Language (GLSL), the break and
continue statements alter loop execution flow, but their
usage is governed by strict scope rules, target specification
constraints, and hardware-level execution characteristics. While modern
desktop GLSL versions treat these jump statements similarly to standard
C, older mobile profiles (like GLSL ES 1.00) enforce strict compiler
limitations, and non-uniform branch exits across GPU threads can trigger
subtle bugs in operations that rely on implicit derivatives.
Syntactic Scope and Placement
The break and continue statements must
adhere to strict structural constraints within shader code:
- Enclosing Structure: A
continuestatement is only valid inside the body of an iteration statement (for,while, ordo-while). Using it anywhere else results in a compilation error. - Switch Interaction: The
breakstatement can appear inside iteration statements orswitchblocks. When placed inside aswitchcontained within a loop, abreakonly terminates the innermostswitchblock, not the loop itself. - Innermost Scope: Both statements only affect the immediate, innermost loop in which they reside. GLSL does not support labeled breaks or multi-level loop exits.
Profile and Version-Specific Restrictions (GLSL ES 1.00)
Older embedded environments, particularly GLSL ES 1.00 (commonly used in WebGL 1.0 and OpenGL ES 2.0), introduced severe restrictions in Appendix A ("Loop Limitations") due to early GPU hardware lacking dynamic branch support:
- Ban on
continue: In the strict profile of GLSL ES 1.00, thecontinuestatement is entirely forbidden inside loop bodies. - Conditional
breakLimitations: Abreakstatement was permitted only if enclosed within a simpleifcondition. The loop index comparison was heavily restricted to ensure the loop could be statically unrolled at compile time. - Dynamic Loop Bounds: Breaking out of a loop dynamically based on uniform or attribute variables often failed to compile on strict hardware, requiring loops to feature constant iteration counts and fixed exit conditions.
Modern desktop GLSL (versions 1.30 and later) and GLSL ES 3.00+ have
largely lifted these syntactic limitations, allowing dynamic branch
decisions and full continue support.
Thread Divergence and Implicit Derivatives
GPUs process fragment and compute shaders in parallel groups of
threads, often called warps or wavefronts. When some threads execute a
break or continue while others do not,
execution diverges:
- Undefined Derivatives: Executing a dynamic
breakorcontinuebefore functions that require implicit screen-space derivatives (such astexture(),textureLod(),dFdx(), ordFdy()) causes undefined behavior. These functions require values from adjacent 2x2 fragment quads to calculate gradients. If quad lanes exit the loop prematurely viabreakor skip iterations viacontinue, the hardware cannot determine the correct mipmap level or rate of change. - Workaround: Texture samples inside loops with
dynamic exits must explicitly specify derivatives or levels of detail
using functions such as
textureGrad()ortextureLod().
Performance and Static Unrolling
Early loop exits prevent compiler optimizations:
- Impaired Loop Unrolling: Shaders frequently unroll
loops to eliminate counter overhead and expose instruction-level
parallelism. A non-constant
breakorcontinueprevents full compile-time unrolling, forcing the shader compiler to emit dynamic branching instructions. - Execution Masking: GPUs handle dynamic exits within
a warp by masking out finished threads rather than immediately advancing
to the next instruction block. A warp containing even a single thread
that did not execute a
breakwill continue executing the loop iterations for all threads, negating potential performance gains from early termination.