Faster Blur? || Glassmorphism UI

Hey, I’ve been inspired by the resurgence of blurry glass elements in UI lately and tried building something like it in JUCE. Here’s my result:
GlassmorphicUI

It runs but its very slow because I’m grabbing a snapshot of the component underneath and using juce::ImageConvolutionKernel with createGaussianBlur and a very high kernel size to get such a thick blur. Is there any way I can achieve the same effect but faster? Would be very thankful for any pointers or advice :slight_smile:
Thank you!
~Nico

3 Likes

Almost impossible to get smooth results with the CPU rasterizer. I considered effects like that but quickly dropped the idea. Any OS that uses this effect probably utilizes the GPU to achieve that.

One way to do that a bit more performant is to render the background into an OpenGL framebuffer and then use an OpenGL shader to do the heavy blur post processing.

Unfortunately using OpenGLGraphicsContextCustomShader will not suffice, since as far as I know you can’t bind and unbind a texture to a custom shader withouth messing up the component drawing. Theoretically you could, if you restore the GL state. But this requires a hacky solution.

Alternatively you also could try to use the OpenGLRenderer to process a custom shader and send textures back and forth. Probably some combination with CachedComponentImage could improve the performance too.

Consider voting for juce_vulkan , so we can finally build effects like these:
JUCE Vulkan

4 Likes

One trick people use with GPU buffers is to use two passes of a 1D kernel. One horizontal and one vertical, applying the second to the first results. It’s much faster than doing a 2D convolution.
Here’s an example, look at buffers A and B:- https://www.shadertoy.com/view/XstGWB

1 Like

Alright, thank you @parawave, you got my vote for your vulkan request :slight_smile:

Thats an interesting approach, haven’t heard of something like that, thank you @DaveH! :slight_smile: I’ll give it a shot

We do it on some of our menus using CPU but we don’t try and do it in realtime, we just take a screenshot and blur it. It’s not right if something is animating underneath. I think I nicked some ocde from Vinn for doing the blur quickly. Though I admit I didn’t check to see if it was faster than the JUCE code.

I did once do it all on OpenGL but it was a fucking pain in the arse. Do not recommend :slight_smile:

2 Likes

Use Gin

gin::applyStackBlur ( juce::Image&, int radius );

Is your friend. It’s around 100x times faster than the JUCE native blur. No, I’m not exaggerating.

You basically take a snapshot of the component, then use the gin::applyStackBlur function and then use the portion you want for your glass effect.

7 Likes

WOW. This is incredible. That stack blur algorithms works like magic. Works in real time and looks just as good. BIG thank you @reFX for suggesting this :slight_smile:

Maybe JUCE could be updated with code from Gin! The existing blur is pretty uselessly slow!

9 Likes

Not as good as a true fast algo which can render real time, but I’ve been working on a neumorphic UI which relies on hundreds of dropshadows, which was having performance issues.

I ended up making a wrapper class around the effects, which blurs / shadows / etc the component or path desired once, and stores this in an image, which from that point onward just draws from the image instead of regenerating the effect on each paint call.

Had a big boost for us, not super happy with the code, but it works.

5 Likes

Here’s our menu rendering code in action.

Would be nice to see this stuff tackled by the JUCE team in a nice, fast, non-hacky way :slight_smile:

8 Likes

I’ve been doing a similar thing with a slightly optimized version of ImageConvolutionKernel’s applyToImage function. Seemingly works well enough with smaller blur radii, but still super slow with large blurred shadows.

1 Like

Isn’t this the same as “setBufferedToImage”?
https://docs.juce.com/master/classComponent.html#af19bbc2186e3297ddd55c328e46c014b

1 Like

Not quite, even if the component or contents redraw, the shadow will still not re-render