Loading…

Implementing a Backgroundd Blur in MonoGame

Hey guys. Got another update on my MonoGame endeavors today. I want to talk about how I wrote a background blur control in The Peacenet in under three hours last night.

Backstory

It all started yesterday morning when I was in the Discord server talking with AShifter and a few others about operating systems, desktop themes, etc. We were talking about macOS and how it has glass transparency in it. Like this:

macOS with a Terminal running nano with a glass background

Notice how the background is both transparent and blurred? Looks cool, right? This is pretty much modern-day Aero transparency. We then thought “Man…it’d be really cool to pull that off in The Peacenet somehow.”

So I started thinking…

How can we do this in Peacenet?

I started thinking about a way I could recreate that exact style in The Peacenet using MonoGame. I knew that in UE4 I could do it with a Background Blur widget, so, since I’m trying to design my GUI system to have a similar selection of widgets to UE4, I started thinking of how I could implement a Background Blur widget.

What is the glass transparency doing?

Before you can effectively implement something visual, it’s a good idea to ask yourself from a programmer’s perspective, “how the fuck are they pulling that off at 60 FPS?”

It seemed simple enough to figure out how they were pulling off the blur effect – it essentially works like this.

  1. Copy an area of the screen to a buffer.
  2. Draw that buffer to the screen with some sort of blur shader.
  3. Continue drawing the rest of the UI without the blur shader.

But there are a few things you need to realize about MonoGame and graphics programming in general, as well as with The Peacenet.

  • Allocating textures is a relatively expensive operation.
  • Switching Render Targets is an expensive operation.
  • GUI elements like to move and resize…
  • …And they like to render every frame not knowing what’s behind them.

So how do those guys at Apple and Microsoft pull off effects like frosted glass and Aero without tanking the framerate of the OS itself? Whatever secret sauce they use would need to be used in Peacenet as well and probably even Unreal Engine 4. Well, it actually turns out their secret sauce isn’t so secret, and it boils down to this – the Background Blur Algortthm that ultimately got implemented in The Peacenet.

Creating a Background Blur control.

Before I can actually do any blurring, I need to create a GUI element that I can plop on-screen somewhere to test things out. I created a ContentControl called BackgroundBlur. I chose to extend the ContentControl class because it will allow you to use it like you would a Border, Window, or the Background Blur element in UE4.

This control will perform the actions and keep track of the necessary to do a real-time background blur. So how do I do it?

The Algorithm

The algorithm I’m going to implement to run every frame is essentially this:

  • Get the on-screen area of the control and store it in a variable visibleRect.
  • If the visibleRect’s width or height is 0, stop right now because there’s nothing to render.
  • If the size of the visibleRect doesn’t match the pixelBuffer’s size, reallocate the pixelBuffer so it matches in size.
  • Copy the pixel data in the back buffer in the control’s visible area to the pixelBuffer.
  • Render the pixelBuffer to the screen with a blur effect.
  • Continue rendering the rest of the GUI.

But you may be asking, “what’s the pixelBuffer?”

Yeah, Michael, what is the pixelBuffer?

Well it’s actually misleading. The pixelBuffer is actually two things in one word. By pixelBuffer I mean an array of bytes and a texture. The array of bytes is an area I can copy back buffer data to, and the texture is what I can render to the screen. I need to have these two separate things because I need to copy the back buffer area into a Texture, and MonoGame will make you copy the data into an array and then copy that into the texture.

Let’s walk through the algorithm and implement it.

So we know what the algorithm is doing – it’s copying the back buffer (a part of the game screen) to a texture, drawing that texture to the screen with an effect, and letting the rest of the game render. So here’s what we’re going to do.

The blur control and pixel data buffer.

I need to create a control that holds a pixel buffer and can be used as a content control. We know that the pixel buffer is an array and a texture. To simplify things (I even did this in-game) I’m going to use an array of Colors instead of an array of bytes. I also added a blur amount property so you can adjust how blurry the background is, just like in UE4. So this is what we get so far:

using ThePeeacenet.Gui;
using Microsoft.xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ThePeacenet.Gui.Controls
{
    public class BackgroundBlur : ContentControl
    {
        // An array where we copy back buffer data to.
        private Color[] _pixelBuffer = null;

        // The texture where that above array is copied into, and the texture that gets rendered to the screen with the effect.
        private Texture2D _blurTexture = null;

        public float BlurAmount { get; set; } = 3.0f;
    }
}

Now we want to implement the algorithm. I did it in phases:

Phase 1: Getting a copy of the back buffer and rendering it.

I started by simply copying the back buffer to the pixel buffer and rendering it back to the screen. I expected the control to look like it wasn’t rendering anything at all but its content, and if this worked at a solid framerate then I knew it would be worth implementing the blur effect. To do this, I did the following:

using ThePeeacenet.Gui;
using Microsoft.xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace ThePeacenet.Gui.Controls
{
    public class BackgroundBlur : ContentControl
    {
        // An array where we copy back buffer data to.
        private Color[] _pixelBuffer = null;

        // The texture where that above array is copied into, and the texture that gets rendered to the screen with the effect.
        private Texture2D _blurTexture = null;

        public float BlurAmount { get; set; } = 3.0f;

        public override void Draw(IGuiContext context, IGuiRenderer renderer, float deltaSeconds)
        {
            // Get the visible area of the control.
            var visibleRect = Rectangle.Intersect(BoundingRectangle, renderer.BoundingRectangle); // Retrieves the intersecting area of the control's bounding rectangle and the screen.

            // Stop rendering if that intersected area is empty.
            if(visibleRect.Width * visibleRect.Height <= 0) return;

            // Get the amount of pixels in the rectangle.
            int pixelCount = visibleRect.Width * visibleRect.Height;

            // If the pixel buffer is null or its length doesn't match pixelCount, we'll reallocate it to match. This accounts for the control resizing.
            if(_pixelBuffer == null || _pixelBuffer.Length != pixelCount)
            {
                _pixelBuffer = new Color[pixelCount];
                // Free the blur texture if it's not null because we're about to reallocate it too.
                if(_blurTexture != null) _blurTexture.Dispose();
                _blurTexture = renderer.CreateTexture(visibleRect.Width, visibleRect.Height);
            }

            // Copy the back buffer data in the visible rectangle's area to the pixel buffer.
            renderer.GetBackBufferData<Color>(visibleRect, _pixelBuffer);

            // Blit it into the blur texture.
            _blurTexture.SetData<Color>(_pixelBuffer);

            // Render the texture to the screen.
            renderer.DrawBrush(new Brush(_blurTexture), visibleRect);

            // Renders the control's content to the screen.
            base.Draw(context, renderer, deltaSeconds);
        }
    }
}

Naturally, this resulted in the following output on-screen:

Peacenet’s Background Blur control in action with the above code.

Note that to test if the effect was actually working, I applied a 50% transparent black tint to the brush when rendering. You can see the Peacenet logo and wallpaper behind the window which indicates that the game is blitting everything correctly.

Doing it this way has a few major performance benefits:

  • Absolutely no Render Targets are used, we’re only moving bits and bytes around which is a lot faster.
  • We only reallocate the pixel buffers when they’re null or can’t hold the right amount of data in them, significantly improving framerate and RAM usage.
  • We completely skip rendering the control if the visible rectangle is empty because there’s nothing to do.

And guess what? The game holds a solid 60 FPS even when I start spastically moving that window around!

Applying the blur effect.

I decided to go for a Gaussian blur. It’s simple and effective. Plus I don’t know HLSL very well and there’s this open-source Gaussian shader already written for me. Oh…open-source…you’re awesome.

I won’t go over how I brought that code into the game’s APIs – instead, have a look at BackgroundBlur.cs and IGuiRenderer.cs for the code – but I essentially ended up with this. Do note this screenshot was taken last night before I applied a speed-patch that does a 2-pass blur instead of a 1-pass blur.

1-pass Gaussian background blur effect in The Peacenet

Looks cool right? We’re close. As I said this was before I did the patch. Gaussian blurs are one-dimensional meaning you can only blur in one direction…things look a little odd in this case if you only do one pass. The weirdness is best shown if you look at the “e” in “thepeacenet”.

To fix that issue, what you’d need to do is first apply a horizontal gaussian blur to the back buffer, submit the draw call, then apply a vertical blur to the same, now-blurred area of the back buffer and submit that draw call. That’ll be the closest you can get to the glass effect in the macOS screenshot above with a Gaussian blur, and is exactly what the current code does in-game.

In conclusion…

I learned a lot about MonoGame’s APIs as well as graphics programming simply by trying to recreate a cool UI effect in a game that doesn’t really need it. However I do think that we could pull off a much better GUI theme than what we had in UE4 now that this cool effect is now possible. What do you guys think? 🙂

Leave a Reply