[Webkit-unassigned] [Bug 240865] [GTK] Webpages completely slow down when CSS blur filter is in use

bugzilla-daemon at webkit.org bugzilla-daemon at webkit.org
Mon Jun 12 01:54:31 PDT 2023


https://bugs.webkit.org/show_bug.cgi?id=240865

--- Comment #6 from Miguel Gomez <magomez at igalia.com> ---
I've been giving a look for a while to how the blur filter works with opengl, but I need to change to another task, so I'll do a brain dump here of the things that I found out to catch up when I have more time.

The blur filter is applied as an weighted average of the values of the pixels around the target pixel. These weights are calculated as from a gaussian distribution, so the weight is smaller as they get further from the target pixel.
The filter radius specifies how many pixels to the lest/right/above/below are taken into account for the calculation.
Instead of accessing all the pixel values that would be needed for each pixel, it's common that this is implemented as a 2 pass rendering, where during the first pass only the horizontal pixels are taken into account, and then during the second pass only the vertical pixels are taken into account. The combination of these two passes produces the same result with less accesses to the pixels, which improves the performance. This is what the TextureMapper implementation does.

Then, regarding the specific implementation details:

- TextureMapperGL is the one creating the gauss values and passing them to the TextureMapperShaderProgram in prepareFilterProgram.

- In theory, we should be sampling as many pixels in each direction as the radius, but we're not doing so. We are always sampling 10 pixels in each direction (defined in GaussianKernelHalfWidth, which is 11 but it includes the target pixel, so it means 10 really). This means that we sample 10 pixels, the original pixel and then another 10 pixels, and this happens in both passes, so it's done horizontally and vertically.

- If the radius is somewhat close to those 10 pixels, the result is going to be quite similar to the cairo implementation. But as the radius gets bigger and bigger, the result gets more different and the pixelated effect starts to show. This happens because as the radius grow, those 10 pixels that we sample get more and more separated among themselves, so we are sampling pixels that are not that close or related to the original pixel, so their contents are not related. 

Example to show the effect: 20x20 image, getting the result for pixel (10,10) by sampling 3 pixels (one to the left and one to the right, instead of the ten that we have in the real implementation). The gauss weights are (0.25, 0.5, 0.25). Talking only about the horizontal pass, but the vertical one has the same problem.

- if the radius is one, we're sampling pixels (9,10), (10,10) and (11,10) with the assigned weights, which is perfect, as we're averaging the value of nearby pixels.
- if the radius grows to 5, then we are sampling pixels (5,10), (10,10) and (15,10). But those pixels are not that close to the target pixel, so their content is not that related to it, and they will corrupt the result. Keep in mind that in the normal case, the second biggest weight is used for the pixels that are besides the target one, and in this case it's applied to a pixel far from the target one.

The test attached by Carlos uses a radius of 60px. This means that we're sampling 10 pixels to each side, one every (60 pixels / 10 samples) 6 pixels, which shows the strange pixelated effect. As the radius gets reduced, the result gets closer and closed to the cairo one, because the pixels sampled get closer and closer to the nearby ones.

There's also a detail in the implementation that I think it's a bug, and it's definition of GaussianKernelStep to 0.2. This defines the advance for each sample. If we're using 10 samples in each direction it should be 0.1, as the position gets calculated as i*step*radius (i=1..10). But being it 0.2, this means that we're sampling up to the double of the radius, which makes the result even worse.

So, after all this mess, what's the proper fix to this?
- First would be changing that GaussianKernelStep from 0.2 to 0.1. That will keep the result fine for bigger radius.
- Eventually the radius will get big enough to cause the buggy rendering. There are several options here:
  * Increase the number of samples to match the radius. This would produce the perfect result always, but the computational cost will grow a lot as the radius grows.
  * Increase the number of samples from the current 10 to something bigger, like 30. This would make the result fine fine for bigger radius values, but there will be a point where the result will get buggy anyway.
  * I saw that some implementations use a trick here. Taking advantage of the interpolation done by opengl when copying textures to different sizes, what they do is they reduce the size of the image to filter and the radius to match the number of pixels that we're sampling. So if the image is 100x100 and we want to use a radius of 40, that's the same than sampling and image of 50x50 with a radius of 20, and the same as an image of 25x25 with a radius of 10, that we can do with our current 10 samples!. I think this is the way to go, despite it requires some not so obvious changes to TextureMapperLayer to perform this downscaling.

-- 
You are receiving this mail because:
You are the assignee for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.webkit.org/pipermail/webkit-unassigned/attachments/20230612/e6bd82b1/attachment-0001.htm>


More information about the webkit-unassigned mailing list