17
$\begingroup$

The OpenGL documentation states that fwidth returns the sum of the absolute value of derivatives in x and y.

What does this mean in less mathematical terms, and is there a way to visualize it?

Based on my understanding of the function, fwidth(p) has access to the value of p in neighboring pixels. How does this work on the GPU without drastically impacting performance, and does it work reliably and uniformly across all pixels?

$\endgroup$
18
$\begingroup$

Pixel screen-space derivatives do drastically impact performance, but they impact performance whether you use them or not, so from a certain point of view they're free!

Every GPU in recent history packs a quad of four pixels together and puts them in the same warp/wavefront, which essentially means that they're running right next to each other on the GPU, so accessing values from them is very cheap. Because warps/wavefronts are run in lockstep, the other pixels will also be at exactly the same place in the shader as you are, so the value of p for those pixels will just be sitting in a register waiting for you. These other three pixels will always be executed, even if their results will be thrown away. So a triangle that covers a single pixel will always shade four pixels and throw away the results of three of them, just so that these derivative features work!

This is considered an acceptable cost (for current hardware) because it isn't just functions like fwidth that use these derivatives: every single texture sample does as well, in order to pick what mipmap of your texture to read from. Consider: if you are very close to a surface, the UV coordinate you are using to sample the texture will have a very small derivative in screen space, meaning you need to use a larger mipmap, and if you are farther the UV coordinate will have a larger derivative in screen space, meaning you need to use a smaller mipmap.

As far as what it means in less mathematical terms: fwidth is equivalent to abs(dFdx(p)) + abs(dFdy(p)). dFdx(p) is simply the difference between the value of p at pixel x+1 and the value of p at pixel x, and similarly for dFdy(p).

$\endgroup$
  • $\begingroup$ So, if dFdx(p) = p(x1) - p(x), then x1 can be either (x+1) or (x-1), depending on the position of pixel x in the quad. Either way, x1 has to be in the same warp/wavefront as x. Am I correct? $\endgroup$ – ApoorvaJ Aug 5 '15 at 15:44
  • 3
    $\begingroup$ @ApoorvaJ Essentially the same value for dFdx is computed for each of the 2 neighbouring pixels in the 2x2 grid. And this value is just computed using the difference between the two neighbor values, if that is p(x+1)-p(x) or p(x)-p(x-1) just depends on your notion of what x is exactly here. The result is the same however. So yeah, you're correct. $\endgroup$ – Christian Rau Aug 5 '15 at 15:46
  • $\begingroup$ @ChristianRau That answers my question. Thanks. $\endgroup$ – ApoorvaJ Aug 5 '15 at 15:47
11
$\begingroup$

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, ...

$\endgroup$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.