Win2D gives Windows App SDK and UWP developers access to Direct2D. It’s a native-code wrapper around Direct2D, which is in turn built on top of Direct3D, arguably the fastest rendering technology available for Windows. As you’ll see in the next post, with the help of a GPU, it runs circles around traditional rendering technologies, it doesn’t compete with the UI thread, and it’s incredibly easy to use.
Win2D is an immediate mode rendering library. Unlike, for example, WPF, it does not keep track of the state of your drawing. You do that. Each time you want to change something, you’re responsible for reissuing the drawing instructions for your entire drawing. This isn’t to say that you need to redraw every video frame (unless you want to). If nothing changes, the GPU will automatically re-display your most recent update.
Drawing state isn’t the only thing you’re taking responsibility for with Win2D. While you can certainly draw what looks like a button using the built-in drawing methods, the only interactions you’re going to have with that button are the ones you create. The lines and shapes you draw are not going to receive PointerPressed, or any of the other events you might be used to. Instead, you subscribe to events in your Win2D XAML control. The hit-testing, to determine when one of those events happens on top of your button, is up to you.
The performance gains achieved by Win2D are for the most part rooted in a simple realization that pushing whole blocks of high-level drawing instructions—entire scenes—to the GPU, will not only free up the CPU, but will also render the final pixels on incredibly fast, highly parallel hardware that has an almost obscenely high-bandwidth connection to the video frame buffer.
Said another way, Win2D is not chatty and it assumes the GPU is smart. It doesn’t create a communications bottleneck by drawing one line at a time (like some UI frameworks…). It sends the GPU the description of an entire scene in one big chunk and then lets it rip.
Win2D provides three pre-built XAML control that cover a wide variety of scenarios, as well as access to lower-level types that allow you to roll your own solution (XAML or not). In the next couple of posts, we’ll focus on the CanvasControl.
The CanvasControl is the simplest of Win2D XAML controls. It’s useful in situations where the complexity of your content is moderate (where virtualization does not help) and you are, for the most part, redrawing occasionally (not animating), for example a drawing or image editor application. Using it involves a four-step process that, with only slight changes, applies to all the Win2D XAML controls.
Step 1: Initialization
Part of the CPU/GPU dance is to think about how we can make our CPU/GPU drawing conversations as brief as possible. One way is to cache frequently-used drawing resources, like brushes and geometries, on the GPU. These are called device-dependent resources.
For the most part, device-dependent resources are an option. You can usually accomplish the same thing by creating the resources on the CPU and sending them to the GPU each time you reference them in your drawing.
Once loaded, one of the first things a CanvasControl does is fire its CreateResources event. It’s guaranteed to happen before the first Draw event. The CreateResources event handler is where you create all your device-dependent resources.
When you create a device-dependent resource, instead of sending a brush or geometry to the GPU on each redraw, they are created in collaboration with the GPU where they are cached for future use. After that you simply reference the brush or geometry instead of re-sending the entire description. We’re not going to take advantage of device-dependent resources for now, but it’s a sniggly bit you should keep in the back of your head for future use.
Before leaving initialization and device-dependent resources, we need talk a bit about device lost. Occasionally your CanvasControl might lose the connection to its GPU. What that means for you is that all those nice device-dependent resources you created are now gone. The good news is that Win2D handles this by firing another CreateResources event once the CanvasControl is attached to a GPU again. The long and the short of it is that you should always be prepared to recreate your device-dependent resource whenever your CreateResource event handler is called. Period.
Step 2: Draw
The CanvasControl raises its Draw event every time your content needs redrawing. It’s guaranteed to be called at least once, soon after the CreateResources event handlers return.
The magic happens in your Draw event handler, using the CanvasDrawingSession of the provided CanvasDrawEventArgs. The CanvasDrawingSession class contains dozens of methods for drawing just about anything from lines to images to complex geometries.
A CanvasDrawingSession is created for you just before the call to your event handler. The drawing session does not do any actual drawing as you invoke the various drawing methods. Instead, it accumulates a collection of drawing commands. As soon as you call Close (this is automatically done for you when the Draw event handler returns), the drawing session optimizes the collection and sends it to the rendering engine (GPU) where the screen is cleared and the drawing instructions are finally executed.
Step 3: Invalidate
You probably wouldn’t be here if you didn’t occasionally want to redraw, but unless you kick it, a CanvasControl will typically only fire its Draw event once. Somewhere in your app, there’s probably an event that motivates you to redraw your content, whether a mouse click or a new stock ticker quote. When that happens, you simply need to call CanvasControl.Invalidate(). Very shortly after that, your Draw event handler will be called once again with a fresh CanvasDrawingSession, and you simply need to rinse and repeat one more time.
Step 4: Cleanup
In this post, we’re heading towards a simple single page app. Once our CanvasControl is loaded, it will remain in the XAML tree for the life of the app. But it’s very easy, over time, to slip into situations where controls are being loaded and unloaded during the lifetime of your application, so it’s important to always have a plan for cleaning up your Win2D controls when they are unloaded. If you develop a habit around adding cleanup code now, you can avoid memory leaks later.
The problem occurs because under the covers, Win2D is calling into unmanaged code. Code that is not, in this case, disposed of by the .NET garbage collector. So, unless you take specific action, like calling CanvasControl.RemoveFromVisualTree, each time one of your Win2D XAML controls is unloaded, unmanaged bits remain in memory. That’s a memory leak, which over time, will consume more memory than I’m sure you are willing to pay for.
So, while our little sample application is not going to repeatedly load and unload Win2D controls, we’re still going to add cleanup code, just to get comfortable with the process. Read more about preventing memory leaks from dangling Win2D controls.
So that’s it: initialize, draw, invalidate and cleanup. In the next post, we’ll use all this to build our first Win2D application.