Bindless Texturing
Task
Your goal is to replace manual texture switching with a dynamic texture array approach using sampler2DArray in GLSL or Texture2DArray in HLSL. This will allow the shader to select the appropriate texture layer based on a per-instance.
Theory
In classic workflows, textures are accessed via bound texture units:
uniform sampler2D iChannel0;
vec4 color = texture2D(iChannel0, uv);
This model:
- Limits the number of active textures.
- Requires frequent texture binding calls.
- Forces multiple draw calls when switching textures.
Problem: Binding is a global state. It's slow, fragile, and scales poorly.
Bindless Texturing
Bindless texturing allows shaders to access resources like textures using dynamic indices, not fixed bindings. This shifts resource access from a CPU-driven binding model to a GPU-driven lookup model, enabling massive scalability and flexibility.
In this model, the shader receives either a GPU handle or an index into a descriptor heap, which it uses to directly access the texture or buffer stored in GPU memory. These handles are typically passed as uniforms, stored in buffers, or embedded in material data, allowing the shader to select resources dynamically at runtime. The GPU performs the lookup internally, enabling thousands of resources to be accessed without CPU intervention.
This means:
- no need to bind individual textures to fixed slots
- no need to update bindings between draw calls
- shader can select any texture dynamically
- reduce CPU involvement
Bindless rendering simplifies resource management and enables GPU-driven pipelines, where draw calls can be generated and dispatched with minimal CPU involvement - ideal for instancing, multi-draw indirect, and streaming large scenes.
WebGL
Unfortunately, WebGL does not support true bindless texturing. But with the right architectural tricks, we can emulate much of its behavior and unlock scalable, data-driven texture access in the browser.
Emulating Bindless in WebGL
1. Texture Atlas
Instead of binding multiple textures, you pack them into one large atlas and sample subregions using UV math.
vec2 atlasUV = uv * scale + offset;
vec4 color = texture2D(iChannel0, atlasUV);
✅ One bind, many textures
✅ Dynamic selection via per-object data
⚠️ Requires careful padding and mipmap management
2. Texture ID and Manual Texture Switching
Pass texture ID as an attribute. Declare multiple sampler2D uniforms and select via if/switch logic.
uniform sampler2D iChannels[4];
in int vLayer;
vec4 getColor(vec2 uv) {
if (vLayer == 0) {
return texture2D(iChannels[0], uv);
} else if (vLayer == 1) {
return texture2D(iChannels[1], uv);
// ...
}
✅ Works in WebGL 1.0
✅ Texture selection is data-driven
⚠️ Limited to ~8–16 textures
⚠️ No dynamic indexing - branching required
3.Texture Arrays
3.1 Texture Arrays in GLSL
WebGL2 supports sampler2DArray, allowing dynamic layer selection.
uniform mediump sampler2DArray iChannelsArray;
vec4 color = texture(u_textureArray, vec3(uv, vLayer));
A texture array is a single GPU resource that contains multiple 2D textures stacked along a third dimension - the layer index. Unlike atlases, each layer is a fully independent texture slice with its own mipmaps and filtering.
mediump is a precision qualifier that defines how accurate a variable's calculations should be. It does not change the texture data itself, but it can impact sampling accuracy, especially for high-resolution or precision-sensitive effects.
3.2 Texture Arrays in HLSL
In HLSL, bindless-like behavior can be emulated using Texture2DArray, allowing dynamic texture selection via a layer index.
Texture2DArray iChannelsArray : register(t0);
SamplerState samplerDefault : register(s0);
float4 color = iChannelsArray.Sample(samplerDefault, float3(uv, layer));
3.3 Texture Array Tradeoffs
✅ True dynamic indexing
✅ Cleanest bindless emulation
⚠️ All layers must share size and format