In entirely technical terms, fwidth(p)
is defined as
fwidth(p) := abs(dFdx(p)) + abs(dFdy(p))
And dFdx(p)
/dFdy(p)
are the partial derivates of the value p
with respect to the x
and y
screen dimensions. So they denote how the value of p
behaves when going one pixel to the right (x
) or one pixel up (y
).
Now how can they be practically computed? Well, if you know the neighbour pixels' values for p
, you can just compute those derivates as direct finite differences as an approximation for their actual mathematical derivatives (which might not have an exact analytical solution at all):
dFdx(p) := p(x+1) - p(x)
But of course now you may ask, how do we even know the values of p
(which could afterall be any arbitrarily computed value inside the shader program) for the neighbouring pixels? How do we compute those values without incurring major overhead by doing the whole shader computation two (or three) times?
Well, you know what, those neighbouring values are computed anyway, since for the neighbouring pixel you also run a fragment shader. So all that you need is access to this neighbouring fragment shader invocation when run for the neighbouring pixel. But it's even easier, because those neighbouring values are also computed at the exact same time.
Modern rasterizers call fragment shaders in larger tiles of more than one neighbouring pixels. At the smallest those would be a 2x2 grid of pixels. And for each such a pixel block the fragment shader is invoked for each pixel and those invocations run in perfectly parallel lock-step so that all computations are done in the exact same order and at the exact same time for each of those pixels in the block (which is also why branching in the fragment shader, while not deadly, should be avoided if possible, since each invocation of a block would have to explore every branch that is taken by at least one of the invocations, even if it just throws away the results afterwards, as also adressed in the answers to this related question). So at any moment, a fragment shader theoretically has access to its neighbouring pixels' fragment shader values. And while you don't have direct access to those values, you have access to values computed from them, like the derivative functions dFdx
, dFdy
, fwidth
, ...