Enabling Backdrop Blur in A Desktop Application

February 5, 2020

A calculator application with a translucent, blurry background.

tcw3-calc, a Rust application

Many modern UI designs feature a blurry window background. This page is a collection of my findings on regard to how to achieve this effect in your application in a standard or non-standard way.

This page does not intend to answer basic questions such as why this effect requires cooperation with a compositor and why applications can't just implement the effect, or provide an end-user-level guide.

đź”—Windows

The new UI design introduced in Windows Vista, dubbed Windows Aero Glass, applied a transparency effect on a window border1. This is implemented by a newly-introduced desktop compositor named Desktop Window Manager (DWM). DWM provides an API that allows applications to control the composition in limited ways such as enabling/disabling the desktop composition and customizing the region within a window where the blur-behind effect is applied.

This UI design remained until Windows 8, where it was replaced by a completely new UI design language named Metro. It was much simpler and lost most of Windows Aero's transparency effects including the frosted glass effect of window borders. DWM was still in use, but even more, they decided to remove the option of disabling desktop composition2. Systems with graphics hardware incapable of desktop composition are supplemented with WARP (Windows Advanced Rasterization Platform), a very performant software rasterizer.

It's Windows 8 where DirectComposition, a new API for interacting with the desktop compositor was added. Unlike the DWM API, DirectComposition offers applications a full capability to construct a tree of visual contents to be directly composited by DWM.

Windows 10 changed the UI design once again. The blur effect made its way back as a part of its renewed design language. Meanwhile, the UWP namespace Windows.UI.Composition was added in some version of Windows 10, available for both of UWP and Win32 applications. This API may seem like a higher-level interface to DWM, but actually exposes more functionalities than DirectComposition does. Windows.UI.Composition offers an API for creating an effect graph (which is a subset of what Direct2D can do) to apply on an application-provided visual or an image behind a window.

1

In Windows terminology, this is called a non-client region.

2

It's still possible to disable desktop composition by editing a registry, but this is not supported by Microsoft and breaks many applications, rendering the system unusable.

đź”—DWM API

The DwmExtendFrameIntoClientArea function enlarges specified sides of a window border to overlap with a client region. On Windows 7 and earlier, this meant the enlarged portion would have a frosty glass effect. However, it doesn't work that way as of Windows 8 where window borders don't have this effect anymore—the borders are rendered just as an opaque, solid color.

The DwmEnableBlurBehindWindow function used to do the exact job its name implied. It does not work anymore on the current version of Windows 103.

đź”—SetWindowCompositionAttribute

user32.dll provides an undocumented function named SetWindowCompositionAttribute that lets you enable the blur-behind effect in Windows 10. See 3 for the usage.

đź”—Windows.UI.Composition

Windows.UI.Composition, also known as the Visual layer, is a retained-mode graphics API provided as a part of UWP APIs. Although it's a part of UWP APIs, it's also designed to be used from non-UWP applications, and Microsoft provides a usage guide for each of WPF, Windows Forms, and Win32 applications.

A visual tree constructed through Windows.UI.Composition is rendered by DWM. This provides many advantages such as that applications don't have to include a component for composition by themselves, that the UI can be rendered more efficiently because there is no need for an intermediate buffer to hold the contents of a window, that a hidden portion of a window doesn't have to be rendered at all, and finally that it makes it possible to have a layer that applies an effect on a background image originating from other applications.

Windows.UI.Composition is an extremely flexible API, but that means you need to take many steps to get desired results. The following diagram summarizes the objects and their relationship created by the steps described below.

CompositionEffectSourceParameternamedGaussianBlurEffectSourceDispatchQueueCompositormanyobjectscreatedfromCompositorCompositionBackdropBrushCompositionEffectBrushHWNDSpriteVisualDesktopWindowTargetcreatedbyCreateBrushCompositorEffectFactorycreatedbyCreateEffectFactoryeffectgraphthreadlocalsourcearetheBrusheachwindowhastwoplacestoattachavisualRoot“backdrop”“backdrop”

The following is the outline of the initialization steps to use Windows.UI.Composition in a non-UWP application. Each step is thoroughly explained by the aforementioned guide by Microsoft.

  • Create a DispatcherQueue by calling CreateDispatcherQueueController function. The dispatch queue will be associated to the calling thread.
  • Create a Compositor on the same thread as where you called CreateDispatcherQueueController.
  • Create a DesktopWindowTarget from a HWND by calling ICompositorDesktopInterop::CreateDesktopWindowTarget. ICompositorDesktopInterop can be obtained by QueryInterface-ing the Compositor.

ICompositorDesktopInterop::CreateDesktopWindowTarget is defined as follows:

// IID: 29E691FA-4567-4DCA-B319-D0F207EB6807
// Defined in `windows.ui.composition.interop.h`
class ICompositorDesktopInterop: public IUnknown {
public:
    virtual HRESULT
    CreateDesktopWindowTarget(
        HWND hwndTarget,
        BOOL isTopmost,
        /* [out] */ IDesktopWindowTarget **result,
    );
};

isTopmost specifies one of two predefined composition layers: FALSE for the one between the target window and the target window's child windows, and TRUE for the one above the child windows.

From the page Basic concepts (DirectComposition) - Win32 apps. This image is licensed by Microsoft and contributors under CC-BY 4.0. Original version

Notice that there are no layers behind the target window. This means that to achieve the backdrop blur effect, you have to draw the foreground contents in a child window or as a top composition layer.

Before creating a Visual, you first need to create an effect graph.

  • Create a CompositionEffectSourceParameter representing an abstract input to the graph. You specify the name of the input. "backdrop" would be appropriate in this case. You'll need the name later when you assign the actual input.
  • Create a GaussianBlurEffect representing the node for a Gaussian blur effect. Assign the CompositionEffectSourceParameter object you just created to the Source property. Yes, GaussianBlurEffect is a part of Win2D, a separately distributed library, so you will have to get it by NuGet or other means. Actually, you can make one by yourself. See the section Creating A Custom Effect Class for how to do that.
  • Configure some other properties of the GaussianBlurEffect.
  • Create a CompositorEffectFactory from the root node (in this case, the GaussianBlurEffect object) by calling ICompositor::CreateEffectFactory.

Make sure your window has a transparent portion. For a Win32 application, the easiest way to see the effect is to give the window a WS_EX_NOREDIRECTIONBITMAP extended style, which will completely remove a redirection bitmap from the window, only leaving composition layers.

Now that you have CompositorEffectFactory, you can create a CompositionEffectBrush by calling ICompositionEffectFactory::CreateBrush.

4

There is a similarly named method called CreateHostBackdropBrush, but the documentation is unclear on their difference. I tried it in a Win32 app, but it didn't work. Maybe it's for a UWP app.

Now you need to create a visual to display this brush. Create and compose objects in the following way:

Finally, assign the SpriteVisual to the Root property of the DesktopWindowTarget you created earlier to attach the visual to the window.

DPI scaling

Like the Win32 API, the composition API represent coordinates in physical pixels. However, effect graphs don't seem to follow this rule. Gaussian blur radii are always calculated in logical pixels, regardless of the target window's DPI or a scaling transformation applied to the sprite visual.

đź”—Simulating The Acrylic Material

This image is licensed by Microsoft and contributors under CC-BY 4.0. Original version

The Acrylic material is a part of UWP's design language. As explained on this page, it's more than just a Gaussian blur:

We fine-tuned acrylic’s key components to arrive at its unique appearance and properties. We started with translucency, blur and noise to add visual depth and dimension to flat surfaces. We added an exclusion blend mode layer to ensure contrast and legibility of UI placed on an acrylic background.

However, this page does not include the exact recipe. The AcrylicEffect example by Microsoft includes what appears to be an effect graph of the Acrylic material.

đź”—Creating A Custom Effect Class

When calling ICompositor::CreateEffectFactory, you need to provide an effect graph comprised of “supported effect types”, which are all defined in Win2D instead of the UWP API. Linking to Win2D can be difficult when you are using a non-standard development tool, e.g., when you are writing a Win32 application in Rust. So a question arises: How does the system recognize those supported effect types? What does a custom type need to disguise as one of those types? The documentation mentions the existence of “a Win2D effect description format”, but doesn't go deeper than that.

To answer this question, I did some experiments and found that the following interfaces are essential to the effect type: IUnknown, IInspectable, IGraphicsEffect, IGraphicsEffectSource, and IGraphicsEffectD2D1Interop.

The following methods are required:

  • IUnknown::*: They are required to support a COM class.

  • IGraphicsEffect::get_Name: I'm not sure why this method is used. You can just return a null pointer.

  • IGraphicsEffectD2D1Interop::GetEffectId: It should return the CLSID of the Direct2D effect the class represents (e.g., CLSID_D2D1GaussianBlur).

  • IGraphicsEffectD2D1Interop::GetPropertyCount: It should return the number of properties the Direct2D effect has.

  • IGraphicsEffectD2D1Interop::GetProperty: It should return a property value for a given property index (e.g., D2D1_GAUSSIANBLUR_PROP_OPTIMIZATION).

  • IGraphicsEffectD2D1Interop::GetSourceCount: It should return the number of inputs the effect accepts. It's usually constant for each effect type.

  • IGraphicsEffectD2D1Interop::GetSource: It should return a IGraphicsEffectSource object representing the input at a given index in range [0, GetSourceCount() - 1]. It can be CompositionEffectSourceParameter or another IGraphicsEffect.

CreateEffectFactory returns E_UNEXPECTED if it detects that some of them are incorrectly implemented.

A Rust implementation can be found in my project's source code.

đź”—DirectComposition

DirectComposition looks like a lower-level API of Windows.UI.Composition. Although Windows.UI.Composition seems to be indeed backed by DirectComposition as I observed during the performance profiling of an application, the API surface exposed by DirectComposition is quite limited.

A Microsoft employee stated5 that they “do not have any immediate plans to add HostBackdropBrush to the DirectComposition API set.” Thus, DirectComposition can't be used to achieve the backdrop blur effect.

5

“We do not have any immediate plans to add HostBackdropBrush to the DirectComposition API set. [⋯]” — msftdavid commented on Jun 1, 2017

đź”—macOS

macOS has included a compositing window manager starting from the very first Mac OS X (macOS was called Mac OS X back then), but much of its potential has remained undocumented and hidden. The compositor has had a backdrop blur capability since Mac OS X 10.4 Tiger6.

The usage of the blur effect in standard UI elements was quite limited in earlier versions of macOS, but gradually increased in later versions. In macOS 10.6 Snow Leopard, sheets and menus acquired this effect, although it was quite subtle7. In macOS 10.15 Catalina, the following UI elements have the blur effect: Dock, menus, and sidebars, and a certain type of windows, and the effect is more emphasized than earlier versions.

The compositor includes a rendering path for software rendering used in uncommon circumstances such as when the system was booted from macOS Recovery or when forcefully enabled using Quartz Debug, a debugging tool distributed as a part of Xcode Tools. Unlike DWM, the software rendering path uses its own rendering code rather than a generic software renderer implementing a common 3D graphics API (although macOS does have one for OpenGL). Backdrop blur is not implemented by the software rendering path and renders as normal transparency or as opaque.

Some people reported high CPU usage when using the blur effect in iTerm28. This can be partly attributed to the fact that iTerm2 sets NSWindow.backgroundColor to [NSColor clearColor] when some panels are transparent, which does much harm to performance9. There may be other causes (I tried, but I haven't been able to figure out them yet), but in any case, it's definitely not because the effect is rendered on CPU. The effect is in fact rendered on GPU.

8

“[⋯] The blurring always incurs a substantial cpu load increase. [⋯]” — unphased commented on Jan 4, 2018

đź”—NSVisualEffectView

NSVisualEffectView is the only supported way to achieve this effect. When added to a window, NSVisualEffectView punches through other views and reveals the blurred image from the background. You can specify the appearance of the view by choosing one of the predefined materials defined in NSVisualEffectMaterial. Some materials correspond to common UI elements such as a sidebar. Note that the appearance is affected by the user's system preference as well as the containing window's focus state (e.g., the view turns opaque when the window is inactive).

đź”—CGSSetWindowBackgroundBlurRadius

The undocumented CGSSetWindowBackgroundBlurRadius function applies a blur effect on non-fully-transparent pixels of a window's contents. This solution is used by iTerm2 and even by Terminal.app. The usage is quite simple:

typedef void *CGSConnection;
extern OSStatus
CGSSetWindowBackgroundBlurRadius(CGSConnection connection,
    NSInteger windowNumber, int radius);
extern CGSConnection
CGSDefaultConnectionForThread();

CGSConnection connection = CGSDefaultConnectionForThread();
CGSSetWindowBackgroundBlurRadius(connection,
    nsWindow.windowNumber, 100);

You also have to remove the default opaque background:

window.backgroundColor =
    [[NSColor clearColor] colorWithAlphaComponent:0.01];
window.opaque = NO;

The alpha value must not be zero for two reasons: (1) Fully transparent pixels will not receive the blur effect. (2) It has serious performance implications. When the alpha value of NSWindow.backgroundColor is set to zero, NSWindow enters a special mode for irregularly-shaped windows. In this mode, a window shadow is generated on-the-fly based on the window contents, and this is extremely slow. Terminal.app avoids this by setting the alpha value to a slightly higher value.

Note that this is a private API, meaning applications using it will be rejected by Mac App Store10.

10

“Dont use this solution. My App rejected due to use of this API.” – Mrug Oct 21 '15 at 10:30

đź”—Linux

Unfortunately, the situation surrounding Linux systems is not good. To my knowledge, there is no reliable, portable way to enable the backdrop blur effect.

The _KDE_NET_WM_BLUR_BEHIND_REGION X11 atom, implemented by KWin, turns on the effect. You can set this even from a command-line using xprop:

for wid in $(xdotool search --pid $PID); do
    xprop -f _KDE_NET_WM_BLUR_BEHIND_REGION 32c -set _KDE_NET_WM_BLUR_BEHIND_REGION 0 -id $wid;
done

compton seems to provide the effect as a global option --blur-background.

đź”—Conclusion

On Windows, use Windows.UI.Composition, which is flexible enough to support every specific use case. If you can't afford the extra coding effort or need to support older versions of Windows, consider using DwmEnableBlurBehindWindow and SetWindowCompositionAttribute.

On macOS, use CGSSetWindowBackgroundBlurRadius if you need a (somewhat) high level of customization and can tolerate the use of a private API. Otherwise, use NSVisualEffectView.

For Linux, design your UI not to rely on a backdrop blur effect. Or, use _KDE_NET_WM_BLUR_BEHIND_REGION with a notice to the user saying the effect works only if supported by their window manager.