Legacy GLSL ES Array Indexing and Texture Lookups
In legacy GLSL ES (specifically GLSL ES 1.00 used in OpenGL ES 2.0 and WebGL 1.0), the specification strictly restricted array indexing, prohibiting non-constant expressions when indexing into arrays of samplers. This article examines why this limitation existed, how it directly affected texture lookups by preventing dynamic texture selection, and the architectural workarounds—such as conditional branching and texture atlases—developers were forced to implement.
The Restriction in GLSL ES 1.00
The GLSL ES 1.00 specification mandated that indices used to access
arrays of sampler2D or samplerCube had to be
constant integral expressions. While some implementations permitted loop
indices within fully unrollable loops, arbitrary run-time expressions
(such as vertex attributes, varying variables, or dynamically calculated
values) could not be used to index sampler arrays.
Because early mobile hardware lacked unified register architectures and advanced indirect addressing capabilities in texture units, hardware drivers could not dynamically route texture sampling instructions to varying sampler units on a per-fragment basis. The specification reflected these hardware limits to ensure consistent baseline compatibility.
Impact on Shader Execution and Branching
Because shaders could not evaluate
texture2D(u_textures[dynamic_index], v_uv) directly,
developers had to replace indirect lookups with explicit conditional
branching trees. A typical implementation required chained
if-else blocks:
vec4 color;
if (materialIndex == 0) {
color = texture2D(u_texture0, v_uv);
} else if (materialIndex == 1) {
color = texture2D(u_texture1, v_uv);
} else {
color = texture2D(u_texture2, v_uv);
}This structural necessity produced several distinct performance challenges:
- Warp and Wavefront Divergence: GPUs execute fragments in lockstep across SIMD warps or wavefronts. When adjacent pixels required textures from different branches, both paths of the conditional statement were executed, masking out inactive threads and substantially degrading fragment throughput.
- Implicit Derivative Disruptions: Texture sampling operations rely on implicit screen-space derivatives (\(dFdx\) and \(dFdy\)) to select mipmap levels. Placing texture lookups inside non-uniform conditional branches creates undefined or discontinuous derivatives, often resulting in visual sampling artifacts or forced fallback to explicit LOD sampling where available.
- Instruction Cache Pressure: Large conditional ladders expand the compiled shader code significantly. In batch rendering systems attempting to bind dozens of materials at once, the code footprint often bloated beyond the cache capacities of legacy mobile GPUs.
Texture Atlases as the Primary Workaround
To circumvent the inability to dynamically index samplers without incurring branching penalties, developers predominantly relied on texture atlasing. By combining multiple distinct textures into a single large texture page, a single constant sampler could be referenced while dynamic indices were converted into UV coordinate offsets.
While texture atlasing avoided sampler array indexing entirely, it introduced its own graphics pipeline issues:
- Mipmapping Bleed: Sub-textures adjacent to one another in an atlas tend to bleed colors across borders at lower mipmap levels, requiring padding, clamping math, or manual derivative handling.
- Wrap Mode Incompatibility: Standard texture
wrapping modes like
GL_REPEATcannot be natively applied to regions inside an atlas, requiring complex modulo operations in the fragment shader that increase arithmetic overhead.
Resolution in Later Standards
The limitation was resolved with the introduction of GLSL ES 3.00
(OpenGL ES 3.0 / WebGL 2.0). Modern specifications introduced 2D texture
arrays (sampler2DArray), which treat layered slices of a
single texture as an indexed coordinate. This architecture provides the
performance profile of a single bound sampler while allowing
non-constant, dynamically computed float or integer layer selection in a
single sampling instruction, rendering the legacy indexing restrictions
obsolete.