In texture(sampler, uv) — and it is the most common line in real-time graphics: a modern
frame issues billions of samples. So ask the module's standing question: what
silicon answers it? Not the shader core. The sample is handed to a dedicated
texture unit, a fixed-function pipeline sitting beside the SMs, and what it does
for that one innocent call is a small saga:
Done in shader code, that saga is dozens of instructions per sample. Hardwired, it is a pipeline that answers one filtered sample per clock — which is why it has been fixed-function silicon since the very first consumer GPU, and still is.
Why mipmapping at all? Picture a textured floor stretching to the horizon. Up close, one screen pixel covers less than one texel — magnification, easy. Far away, one screen pixel covers hundreds of texels; sampling just one of them (whichever lands under the pixel centre) picks an effectively random texel, and as the camera moves the pick changes every frame — the distant floor shimmers and crawls with aliasing. The honest fix is to average all the texels under the pixel, but that is hopeless at one sample per clock.
The mipmap chain is that average, precomputed: level 0 is the full image,
level 1 half the size (each texel the average of 4), level 2 a quarter, down to a single texel —
all for one-third extra memory. Now "average hundreds of texels" becomes "read the level where one
texel ≈ the pixel's footprint". The unit computes the footprint from the screen-space
derivatives of
Step through one trilinear sample. Texel values are shown at their centres; watch four texels collapse to one bilinear value, twice, and the two levels blend into the answer.
Bilinear filtering on a tiny checkerboard — the four fetches, the four weights, and the mip-level arithmetic, in code you can poke at.
Notice the middle sample: at
Trilinear assumes a pixel's footprint in texel space is roughly square. Look along a floor at a grazing angle and it isn't — it is a long thin ellipse, perhaps 2 texels wide and 16 long. Trilinear must pick one level for both directions: match the short axis and the long axis aliases; match the long axis and everything blurs (the classic smeared-road look). Anisotropic filtering instead takes several trilinear probes spaced along the footprint's major axis — up to 16 of them at "16×" — and averages, sharp in one direction and smooth in the other. The cost is real (each probe is a full 8-texel trilinear sample), which is why it has a quality dial, and why the texture unit's ability to sustain it comes from the next idea.
Eight texels per sample, billions of samples — the arithmetic is the easy half. The hard half is memory. Texture units survive on two locality tricks:
The other bandwidth lever is to shrink the texels themselves. GPU formats (BC on
desktop, ASTC on mobile) are fixed-rate block compressors: every
This is also why you cannot feed the unit a JPEG: JPEG is variable-rate (each block compresses to however many bits it happens to need), so there is no arithmetic that maps texel coordinates to a memory address — you'd have to decode the whole image first, which is exactly the random access a texture sampler cannot live without. Lossy-but-fixed-rate beats better-but-variable-rate, because the consumer is hardware.
A texture unit is a deep pipeline: a sample takes hundreds of cycles to come back, but the SM hides
that by keeping many samples in flight and switching warps while they wait — as long as the
addresses are known up front. A dependent texture read — computing a UV from
the result of a previous sample, as in texture(B, texture(A, uv).xy) — breaks
that: the second request cannot even be issued until the first returns, so the two latencies
add instead of overlapping. One level of indirection is common and survivable (that's how
lookup-table effects work); chains of them serialise the very latency all this machinery exists to
hide. Keep your UV maths shallow, and let the compiler hoist sample addresses early.
How much of a GPU is texture hardware? Each SM carries its own cluster of texture units (typically four), so a big desktop chip ships several hundred of them, together answering on the order of half a trillion filtered samples per second. Historically the ratio ran the other way: the 1996 Voodoo Graphics was essentially a texture unit with a card attached — texturing was the whole product, and shading was an afterthought bolted around it. Thirty years on, the balance of the die has swung to programmable SMs, but the texture unit itself never became software: filtering's arithmetic is settled, its throughput demand is insatiable, and — as the first lesson of this module argued — that combination is exactly what silicon specialisation is for. The one line of shader code you write most often is the one the shader never executes.