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.

9 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!

10 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:

9 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

Quick question.

How do you quickly grab the pixels from the plug-in in the first place to do the background image for the blur?
I can’t find any references for some reason.

/edit/ - OK I need to use createComponentSnapshot on the parent component to grab an Image.

Thanks,
Dave H.

+1 for Mario’s “stack blur” algo becoming the default in JUCE. In addition to gin, a lot of frameworks on a lot of platforms implement this tried and true algo.

It looks like the JUCE DropShadow is currently buggy and lacking:

  1. Radius values don’t line up with what browsers/figma produces, rendering it useless for implementing designs
  2. It produces strange “streaking” artifacts on paths
  3. It doesn’t support “spread”
  4. There’s no inner shadow counterpart

These things were trivial to implement on top of Gin’s implementation of Mario’s algorithm.

Here’s a 200x200px box with 50px rounded edges and 50px blur radius shadow rendered in chrome, figma, and JUCE:

To reproduce the (IMO) buggy JUCE shadow:

    Path path;
    path.addRoundedRectangle(50, 50, 200, 200, 50);

    DropShadow(JUCE_LIVE_CONSTANT(Colours::black),
               jmax(1, JUCE_LIVE_CONSTANT(1)),
               {JUCE_LIVE_CONSTANT(0),
               JUCE_LIVE_CONSTANT(0)}).drawForPath(g, path);

    g.setColour(Colour::fromString("FFC4C4C4"));
    g.fillPath(path);
6 Likes

OK. I’ve hacked out my own version of the above stacked blur. It includes a tint value on the blur itself. :slightly_smiling_face:

QuiksynMods

2 Likes

I totally agree with this, trying to convert a Figma design to JUCE using the drop shadows is pretty much impossible right now

1 Like

I was also able to make a custom Dropshadow class using the stack blur algo above. It renders them perfectly in line with Figma and other contemporary design systems. Both juce’s dropshadow and blur are just not appropriate for anyone needing to make modern UI.

3 Likes

We’ve been using JUCE’s drop shadow in one of our latest GUI projects, and may be using shadows/blurs more in the future. I can’t say I necessarily agree that “juce’s dropshow is not appropriate for modern UI”.

I found that having a dedicated shadow component for anything that requires a shadow is the way to go (for our particular use case we also needed to be able to specify inner or outer shadows, something JUCE doesn’t really provide a neat API for). Then, by calling setBufferedToImage(true) on the shadow components, they’ll only be redrawn when their size changes - which for us means they’re only drawn once since the UI is static. If the component being shadowed needs to redraw it’s content, the shadow won’t be redrawn (especially important for us since we have animated level meters being drawn over component with an inner shadow, which themselves are within a panel with an outer shadow).


I spent a while last week trying to implement the Stack Blur algorithm as mentioned above as part of a juce::ImageEffectFilter. I never managed to get it looking 100% but I did find it to be particularly performant - larger blur radiuses didn’t have much of an impact on performance unlike with the current JUCE blurring implementation (which I believe uses a Gaussian blur).

I don’t think changing the current implementation is the right approach, I imagine there’s many projects out there relying on shadows looking as they do currently, changing that would likely upset many developers. I think the right approach would be to add a new shadow/blur effect, and possibly deprecate the current one.


TL;DR - +1 for adding an implementation of Stack Blur to JUCE.

2 Likes