Proper lifecycle management of SharpDX resources in C#/XAML Universal Apps

In the last tutorial we learned how to properly use SharpDX in our XAML apps so we can get the power of DirectX without having to touch native C++ code. However, it was a very superficial introduction for demonstrative purposes that didn’t take into account anything more than single page applications. Using that code in real-world applications would introduce lots of problems (memory leaks, bad performance, crashes) that we are going to learn how to fix in this article. So, we are going to learn how to extend that code so it behaves better when our application has page navigation, gets snapped or is suspended/reactivated.

Now that we are focusing on integrating SharpDX with XAML applications, we have to take into account proper finalization of the created resources when navigating away from pages that contain SwapChainPanel/SwapChainBackgroundPanel controls.

Start by adding a new Page to the shared project. Add a button in MainPage to navigate to the new page, and another one there to make the frame go back in the navigation stack so we don’t end with an infinite navigation loop. If you run the project you will see that it works correctly; however, there is a small problem that can only be spotted by running the Performance and Diagnostics tool with the Memory Usage option selected:

leak


There is a memory leak in the application, because when we are navigating away from the page, we aren’t disposing any of the SharpDX resources we have created for rendering. And since they are wrappers over native objects, they will hardly be automatically reclaimed by the garbage collector.

But this has a very easy solution. Since we subscribed to the Loaded event of the SwapChainPanel for creating everything, we can subscribe to the Unloaded one and perform the opposite operations:

private void SwapChainPanel_Unloaded(object sender, RoutedEventArgs e)
{
    CompositionTarget.Rendering -= CompositionTarget_Rendering;    using (DXGI.ISwapChainPanelNative nativeObject = ComObject.As<DXGI.ISwapChainPanelNative>(this.SwapChainPanel))
    {
        nativeObject.SwapChain = null;
    }

    Utilities.Dispose(ref this.backBufferView);
    Utilities.Dispose(ref this.backBufferTexture);
    Utilities.Dispose(ref this.swapChain);
    Utilities.Dispose(ref this.deviceContext);
    Utilities.Dispose(ref this.device);
}


If we run again the memory diagnostics tool, we can verify that all allocated memory is properly being released when navigating away from the page:

leak_fixed


Window resizing

UPDATE: SourceSize isn’t the most appropriate approach to use when the application is being snapped, since it’s thought for uniformly scaling the swap chain to smaller sizes in less powerful hardware; using it this way will stretch the output image when drawing with Direct 3D. In this case, setting a different viewport will be enough.

Now we take into account when resizing happens so the swap chain dimensions match those of the control it is associated with. DirectX 11.2 introduced the new SourceSize property for DXGI.SwapChain2 objects, which lets you specify a region equal or smaller than the swap chain’s total size so you don’t have to always destroy and recreate it, only in the cases that a bigger one is needed.

Let’s subscribe to the SizeChanged event of the SwapChainPanel to accomplish this. But, this event can fire before the Loaded one if we don’t specify a default size for the control. To check that our swap chain is initialized and ready for resizing, we must declare a boolean flag (in our case it’s named isDXInitialized) and set it to true at the end of the SwapChainPanel_Loaded function. Now we can safely resize set the SourceSize property:

private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (isDXInitialized)
    {
        Size2 newSize = RenderSizeToPixelSize(e.NewSize);
        swapChain.SourceSize = newSize;
    }
}

This will work nicely in a number of cases but it’s far from perfect. Remember that I said that the specified region should be equal or smaller in size to the swap chain’s dimensions? If you make the control bigger at runtime or launch the application in snapped mode and resize it to full screen, you will receive a 0x887A0001 (DXGI_ERROR_INVALID_CALL) exception.

So, the logic outcome is to check the new size of the control and resize the swap chain if a bigger one is needed then set the SourceSize property, isn’t it?

private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (isDXInitialized)
    {
        Size2 newSize = RenderSizeToPixelSize(e.NewSize);        if (newSize.Width > swapChain.Description1.Width || newSize.Height > swapChain.Description1.Height)
        {
            swapChain.ResizeBuffers(swapChain.Description.BufferCount, (int)e.NewSize.Width, (int)e.NewSize.Height, swapChain.Description1.Format, swapChain.Description1.Flags);
        }

        swapChain.SourceSize = newSize;
    }
}


While technically true, it is still missing a minor detail: all resources linked to a swap chain must be destroyed before calling SwapChain2.ResizeBuffers. In our case it’s the render target and its associated resource view, so if you call the previous code as-is, you will receive the a dreaded DXGI_ERROR_INVALID_CALL exception again.

To fix this, we must destroy the associated resources, resize the swap chain and recreate them:

private void SwapChainPanel_SizeChanged(object sender, SizeChangedEventArgs e)
{
    if (isDXInitialized)
    {
        Size2 newSize = RenderSizeToPixelSize(e.NewSize);        if (newSize.Width > swapChain.Description1.Width || newSize.Height > swapChain.Description1.Height)
        {
            Utilities.Dispose(ref this.backBufferView);
            Utilities.Dispose(ref this.backBufferTexture);

            swapChain.ResizeBuffers(swapChain.Description.BufferCount, (int)e.NewSize.Width, (int)e.NewSize.Height, swapChain.Description1.Format, swapChain.Description1.Flags);

this.backBufferTexture = D3D11.Texture2D.FromSwapChain<D3D11.Texture2D>(this.swapChain, 0);
            this.backBufferView = new D3D11.RenderTargetView(this.device, this.backBufferTexture);
        }

        swapChain.SourceSize = newSize;
    }
}


 

Application suspension

At last, we are going to implement a new requisite when creating Windows Store apps that use DirectX: calling the DXGI.Device3.Trim method when the app goes into suspension. This function frees some internal buffers created by the graphics driver for faster performance and must be used to reduce the app’s memory footprint so there are less chances of it being terminated while suspended. The buffers will be transparently recreated when the app is reactivated, introducing only a minor execution delay.

So, let’s subscribe to the Suspending event of the current Application instance inside the SwapChainPanel_Loaded function, and unsubscribe inside SwapChainPanel_Unloaded to ensure proper finalization. Our new function will perform minimal processing, calling only DeviceContext.ClearState and Device3.Trim as per the guidelines. Since we aren’t storing an instance of DXGI.Device3, you can use QueryInterface on the current Direct3D 11.2 device or call GetDevice on the swap chain; remember to put it inside an using block or dispose it manually since both calls return a new object that needs to be freed:

private void Application_Suspending(object sender, Windows.ApplicationModel.SuspendingEventArgs e)
{
    if (isDXInitialized)
    {
        this.deviceContext.ClearState();        using (DXGI.Device3 dxgiDevice3 = this.swapChain.GetDevice<DXGI.Device3>())
        {
            dxgiDevice3.Trim();
        }
    }
}


Source code

As always, you can get the full soure code (Windows Store C#/XAML Universal App project, compatible with Visual Studio 2013 only) from GitHub.

Initializing Direct3D in Windows/Windows Phone 8.1 C#/XAML Universal apps with SharpDX

Since the first SharpDX posts in this blog, the preferred method for creating the tutorial’s Direct3D Windows Store apps has been using the CoreWindow approach. This decision was made to focus on the core functionality of DirectX and because XAML interop performance was less than stellar in the first releases of Windows 8. However, time has passed and the state of this relationship has greatly improved, and with the availability of Windows Runtime in Windows Phone with the 8.1 update through Universal Apps, it’s time to explore this exciting new technology.

First of all, if you haven’t heard about Universal Apps yet, it’s a new project type that allows you to share as much code as you want between Windows Store and Windows Phone apps. This has been achieved by sacrificing the old Silverlight application model and enabling Windows Phone devices to run Windows Runtime apps thanks to an updated kernel. Although with minimal differences, the APIs in both operating systems are almost the same – in the case of DirectX, Windows Phone finally has support for Direct2D and DirectWrite among others, and the API version has been bumped up to 11.2. You can even share XAML pages, although providing a custom UX for each platform is strongly recommended.

Creating the base project

It’s as simple as opening your copy of Visual Studio 2013 (Express for Windows version is supported too) and create a new Visual C# > Store Apps > Blank App (Universal Apps). This will create a new solution with three projects: the base Windows 8.1 project, the base Windows Phone 8.1 project and a shared one that will contain all source code and assets common to both platforms.

The default template creates a MainPage page in each platform, but we are going to share the XAML code for now. So, move one of them to the shared project and delete the other. Now open the NuGet package manager and install the SharpDX.Direct3D11 package in both the Windows and Windows Phone projects; this will automatically add its dependencies too (SharpDX and SharpDX.DXGI).

Although usage of a SwapChainBackgroundPanel is highly recommended for fullscreen usage of Direct3D, we are going to use a SwapChainPanel. This is done because the later can be added to the existing visual tree, while a background panel needs to be specified as the first child of the Page element and doesn’t support the frame-based navigation that the application project provides out of the box. Go ahead and add one to the MainPage, and subscribe to its Loaded event:

  1. <SwapChainPanel x:Name=”SwapChainPanel” Loaded=”SwapChainPanel_Loaded”/>

Initializing Direct3D

As in previous tutorials, we are going to follow the same initialization steps, but in this case we will perform them inside the Loaded event of the SwapChainPanel. This is done this way because we need to know the size of the control to create a proper swap chain that exactly fits inside it. Moreover, we will take into account some minor changes:

  • We must query the DisplayInformation class to obtain the current logical DPI of the screen. Although not common in Windows, in Windows Phone devices XAML layout is specified in virtual pixel values that don’t match the physical screen resolution.
  • The swap chain dimensions must be explicitly specified. Using CoreWindow you could specify automatic sizing.
  • We must cast the SwapChainPanel control to the ISwapChainPanelNative interface to properly set its swap chain.
  • When available, we will be using the DirectX 11.2 version of the data types to take full advantage of the new features.

And here are the steps to follow:

  1. Create a new SharpDX.Direct3D11.Device and cast it to Device2:
  1. using (D3D11.Device defaultDevice = new D3D11.Device(D3D.DriverType.Hardware, D3D11.DeviceCreationFlags.Debug))
  2. {
  3.    this.device = defaultDevice.QueryInterface<D3D11.Device2>();
  4. }

  1. Obtain the handle to the SharpDX.Direct3D11.DeviceContext2:
  1. this.deviceContext = this.device.ImmediateContext2;

  1. Find the amount of physical pixels for each virtual pixel by dividing the logical DPI value between 96 (the default DPI amount):
  1. float pixelScale = Windows.Graphics.Display.DisplayInformation.GetForCurrentView().LogicalDpi / 96.0f;

  1. Specify the swap chain creation parameters:
  1. DXGI.SwapChainDescription1 swapChainDescription = new DXGI.SwapChainDescription1()
  2. {
  3.     // No transparency.
  4.     AlphaMode = DXGI.AlphaMode.Ignore,
  5.     // Double buffer.
  6.     BufferCount = 2,
  7.     // BGRA 32bit pixel format.
  8.     Format = DXGI.Format.B8G8R8A8_UNorm,
  9.     // Unlike in CoreWindow swap chains, the dimensions must be set.
  10.     Height = (int)(this.SwapChainPanel.RenderSize.Height * pixelScale),
  11.     Width = (int)(this.SwapChainPanel.RenderSize.Width * pixelScale),
  12.     // Default multisampling.
  13.     SampleDescription = new DXGI.SampleDescription(1, 0),
  14.     // In case the control is resized, stretch the swap chain accordingly.
  15.     Scaling = DXGI.Scaling.Stretch,
  16.     // No support for stereo display.
  17.     Stereo = false,
  18.     // Sequential displaying for double buffering.
  19.     SwapEffect = DXGI.SwapEffect.FlipSequential,
  20.     // This swapchain is going to be used as the back buffer.
  21.     Usage = DXGI.Usage.BackBuffer | DXGI.Usage.RenderTargetOutput,
  22. };

  1. Obtain the automatically created SharpDX.DXGI.Factory3 by queriying the Direct3D device for the SharpDX.DXGI.Device3 interface, and obtaining the reference to its parent. Use it to create the swap chain and then cast it to SwapChain2.
  1. using (DXGI.Device3 dxgiDevice3 = this.device.QueryInterface<DXGI.Device3>())
  2. {
  3.     // Get the DXGI factory automatically created when initializing the Direct3D device.
  4.     using (DXGI.Factory3 dxgiFactory3 = dxgiDevice3.Adapter.GetParent<DXGI.Factory3>())
  5.     {
  6.         // Create the swap chain and get the highest version available.
  7.         DXGI.SwapChain1 swapChain1 = new DXGI.SwapChain1(dxgiFactory3, this.device, ref swapChainDescription);
  8.         this.swapChain = swapChain1.QueryInterface<DXGI.SwapChain2>();
  9.     }
  10. }

  1. Obtain the *SharpDX.DXGI.ISwapChainPanelNative reference and set its swap chain:
  1. using (DXGI.ISwapChainPanelNative nativeObject = ComObject.As<DXGI.ISwapChainPanelNative>(this.SwapChainPanel))
  2. {
  3.     // Set its swap chain.
  4.     nativeObject.SwapChain = this.swapChain;
  5. }

  1. Finally, obtain a SharpDX.Direct3D11.Texture2D from the front buffer of the swap chain and create a SharpDX.Direct3D11.RenderTargetView with it so it can be set as the back buffer when drawing:
  1. this.backBufferTexture = D3D11.Texture2D.FromSwapChain<D3D11.Texture2D>(this.swapChain, 0);
  2. this.backBufferView = new D3D11.RenderTargetView(this.device, this.backBufferTexture);

Rendering

Unlike in other DirectX applications where you usually run a loop and use it to update the state and draw everything, XAML apps use events to notify the Direct3D hosts when they need a new frame. In this case, this is achieved by firing the CompositionTarget.Rendering event. It is advised to subscribe to it when your Direct3D initialization has finished successfully, so you don’t execute any code that could potentially access any uninitialized objects. In our case, we are going to subscribe at the end of the SwapChainPanel_Loaded function:

  1. CompositionTarget.Rendering += CompositionTarget_Rendering;

And, for now, we are only going to clear the backbuffer to a solid color and present it:

  1. // Set the active back buffer and clear it.
  2. this.deviceContext.OutputMerger.SetRenderTargets(this.backBufferView);
  3. this.deviceContext.ClearRenderTargetView(this.backBufferView, Color.CornflowerBlue);
  4. // Tell the swap chain to present the buffer.
  5. this.swapChain.Present(1, DXGI.PresentFlags.None, new DXGI.PresentParameters());

Now we can run the project in both Windows and Windows Phone and check the result:

Application running in Windows and Windows Phone emulator side by side

Source code

As always, you can download the source code for this tutorial in its corresponding GitHub repository. This time you will need Visual Studio 2013 to open it due to the new project types used by Universal Apps.