GLSL Texture2D vs Sampler in Separate Pairs
Modern graphics APIs like Vulkan and modern OpenGL allow developers
to decouple texture data from the state used to read it. In GLSL, this
decoupling is represented by separating raw texture objects, such as
texture2D, from pure sampler objects, designated as
sampler. Understanding the difference between these types
is fundamental to optimizing resource usage, descriptor management, and
shader flexibility in modern rendering pipelines.
The Role of texture2D
The texture2D type (and related types like
textureCube or texture3D) represents the
underlying image resource. It acts purely as a handle to the texture's
data and its memory allocation.
Key characteristics of texture2D include:
- Data Storage: It holds the actual texel data, dimensions, mipmap levels, and format information (e.g., RGBA8, R32F).
- No Filtering Logic: It contains no information on how coordinates should wrap, whether texels should be interpolated using nearest-neighbor or bilinear filtering, or how level-of-detail (LOD) transitions should be computed.
- Direct Access: You can read directly from a
texture2Dwithout a sampler using texel fetching functions liketexelFetch(), which require exact integer pixel coordinates rather than normalized floating-point UV coordinates.
The Role of sampler
The sampler type represents the state and algorithms
used to read and filter image data. It contains no pixel data,
dimensions, or memory buffers of its own.
Key characteristics of sampler include:
- Sampling State: It encapsulates parameters such as minification and magnification filtering (nearest, linear), mipmap filtering modes, coordinate wrapping modes (clamp-to-edge, repeat, mirrored repeat), border colors, and anisotropic filtering settings.
- Reusability: A single
samplerstate can be bound once and reused across hundreds of different textures, ensuring consistent sampling behavior without duplicating state data.
How They Combine in GLSL
In legacy GLSL, a sampler2D was an opaque uniform that
inherently combined the texture data and the sampler state into a single
object. When using separate image-sampler pairs (commonly enabled via
Vulkan GLSL or OpenGL extensions like GL_ARB_gl_spirv), you
declare the texture and sampler independently:
layout(set = 0, binding = 0) uniform texture2D uTexture;
layout(set = 0, binding = 1) uniform sampler uSampler;To sample from uTexture using normalized coordinates and
filtering, you combine them on the fly into a combined image-sampler
constructor:
vec4 color = texture(sampler2D(uTexture, uSampler), uv);Key Differences and Advantages
- Decoupled Architecture:
texture2Dis the data payload, whilesampleris the access rulebook. Separating them mirrors how modern GPU hardware operates under APIs like Vulkan, DirectX 12, and Metal. - Descriptor Efficiency: Modern GPUs have hard limits on the number of active sampler states (often 16 to 32), while supporting thousands of bound textures. Decoupling them allows \(N\) textures to be paired with \(M\) samplers (\(N \times M\) combinations) without wasting descriptor slots on redundant, identical sampler states.
- Shader Flexibility: The same texture can be sampled
in the same shader pass using multiple methods—for instance, using a
bilinear
samplerfor general rendering and a point-filteredsamplerfor pixel-aligned processing—without rebinding or duplicating the texture resource.