Building a Direct3D 11 application with C# and SharpDX

In this recipe we will prepare a blank project that contains the appropriate SharpDX references and a minimal rendering loop. The project will initialize necessary Direct3D objects and then provide a basic rendering loop that sets the color of the rendering surface to Color.LightBlue.

Getting ready

Make sure you have Visual Studio 2012 Express for Windows Desktop or Professional and higher installed. Download the SharpDX binary package and have it at hand.

To simplify the recipes in this book, lets put all our projects in a single solution:

  1. Create a new Blank Solution in Visual Studio by navigating to File | New | Project… (Ctrl + Shift + N), search for and select Blank Solution by typing that in the search box at the top right of the New Project form (Ctrl + E).
  2. Enter a solution name and location and click on Ok.

    Note

    The recipes in this book will assume that the solution has been named D3DRendering.sln and that it is located in C:\Projects\D3DRendering.

  3. You should now have a new Blank Solution at C:\Projects\D3DRendering\D3DRendering.sln.
  4. Extract the contents of the SharpDX package into C:\Projects\D3DRendering\External. The C:\Projects\D3DRendering\External\Bin folder should now exist among others.

How to do it…

With the solution open, let's create a new project:

  1. Add a new Windows Form Application project to the solution with .NET Framework 4.5 selected.
  2. We will name the project Ch01_01EmptyProject.
  3. Add the SharpDX references to the project by selecting the project in the solution explorer and then navigate to PROJECT | Add Reference from the main menu. Now click on the Browse option on the left and click on the Browse... button in Reference Manager.
  4. For a Direct3D 11.1 project compatible with Windows 7, Windows 8, and Windows 8.1, navigate to C:\Projects\D3DRendering\External\Bin\DirectX11_1-net40 and select SharpDX.dll, SharpDX.DXGI.dll, and SharpDX.Direct3D11.dll.
  5. For a Direct3D 11.2 project compatible only with Windows 8.1, navigate to C:\Projects\D3DRendering\External\Bin\DirectX11_2-net40 and add the same references located there.

    Note

    SharpDX.dll, SharpDX.DXGI.dll, and SharpDX.Direct3D11.dll are the minimum references required to create Direct3D 11 applications with SharpDX.

  6. Click on Ok in Reference Manager to accept the changes.
  7. Add the following using directives to Program.cs:
    using SharpDX;
    using SharpDX.Windows;
    using SharpDX.DXGI;
    using SharpDX.Direct3D11;
    // Resolve name conflicts by explicitly stating the class to use:
    using Device = SharpDX.Direct3D11.Device;
  8. In the same source file, replace the Main() function with the following code to initialize our Direct3D device and swap chain.
    [STAThread]
    static void Main()
    {
        #region Direct3D Initialization
        // Create the window to render to
        Form1 form = new Form1();
        form.Text = "D3DRendering - EmptyProject";
        form.Width = 640;
        form.Height = 480;
    
        // Declare the device and swapChain vars
        Device device;
        SwapChain swapChain;
    
        // Create the device and swapchain
        Device.CreateWithSwapChain(
            SharpDX.Direct3D.DriverType.Hardware,
            DeviceCreationFlags.None,
            new [] {
                SharpDX.Direct3D.FeatureLevel.Level_11_1,
                SharpDX.Direct3D.FeatureLevel.Level_11_0,
                SharpDX.Direct3D.FeatureLevel.Level_10_1,
                SharpDX.Direct3D.FeatureLevel.Level_10_0,
            },
            new SwapChainDescription()
            {
                ModeDescription =
                    new ModeDescription(
                        form.ClientSize.Width,
                        form.ClientSize.Height,
                        new Rational(60, 1),
                        Format.R8G8B8A8_UNorm
                    ),
                SampleDescription = new SampleDescription(1,0),
                Usage = SharpDX.DXGI.Usage.BackBuffer | Usage.RenderTargetOutput,
                BufferCount = 1,
                Flags = SwapChainFlags.None,
                IsWindowed = true,
                OutputHandle = form.Handle,
                SwapEffect = SwapEffect.Discard,
            },
            out device, out swapChain
        );
    
    // Create references for backBuffer and renderTargetView
     var backBuffer = Texture2D.FromSwapChain<Texture2D>(swapChain, 0);
     var renderTargetView = new RenderTargetView(device, backBuffer);
    
        #endregion
    
    ...
    }
  9. Within the same Main() function, we now create a simple render loop using a SharpDX utility class SharpDX.Windows.RenderLoop that clears the render target with a light blue color.
    #region Render loop
    // Create and run the render loop
    RenderLoop.Run(form, () =>
    {
       // Clear the render target with light blue
     device.ImmediateContext.ClearRenderTargetView(
     renderTargetView,
     Color.LightBlue);
      // Execute rendering commands here...
    
      // Present the frame
     swapChain.Present(0, PresentFlags.None);
    });
    #endregion
  10. And finally, after the render loop we have our code to clean up the Direct3D COM references.
    #region Direct3D Cleanup
    // Release the device and any other resources created
    renderTargetView.Dispose();
    backBuffer.Dispose();
    device.Dispose();
    swapChain.Dispose();
    #endregion
  11. Start debugging the project (F5). If all is well, the application will run and show a window like the following screenshot. Nothing very exciting yet but we now have a working device and swap chain.
    How to do it…

    Output from the empty project

How it works…

We've created a standard Windows Forms Application to simplify the example so that the project can be built on Windows 7, Windows 8, and Windows 8.1.

Adding the SharpDX.dll reference to your project provides access to all the common enumerations and structures that have been generated in SharpDX from the Direct3D SDK header files, along with a number of base classes and helpers such as a matrix implementation and the RenderLoop we have used. Adding the SharpDX.DXGI.dll reference provides access to the DXGI API (where we get our SwapChain from), and finally SharpDX.Direct3D11.dll provides us with access to the Direct3D 11 types.

The using directives added are fairly self-explanatory except perhaps the SharpDX.Windows namespace. This contains the implementation for RenderLoop and also a System.Windows.Form descendant that provides some helpful events for Direct3D applications (for example, when to pause/resume rendering).

When adding the using directives, there are sometimes conflicts in type names between namespaces. In this instance there is a definition for the Device class in the namespaces SharpDX.DXGI and SharpDX.Direct3D11. Rather than having to always use fully qualified type names, we can instead explicitly state which type should be used with a device using an alias directive as we have done with:

using Device = SharpDX.Direct3D11.Device;

Our Direct3D recipes will typically be split into three stages:

  • Initialization: This is where we will create the Direct3D device and resources
  • Render loop: This is where we will execute our rendering commands and logic
  • Finalization: This is where we will cleanup and free any resources

The previous code listing has each of the key lines of code highlighted so that you can easily follow along.

Initialization

First is the creation of a window so that we have a valid handle to provide while creating the SwapChain object. We then declare the device and swapChain variables that will store the output of our call to the static method Device.CreateDeviceAndSwapChain.

The creation of the device and swap chain takes place next. This is the first highlighted line in the code listing.

Here we are telling the API to create a Direct3D 11 device using the hardware driver, with no specific flags (the native enumeration for DeviceCreationFlags is D3D11_CREATE_DEVICE_FLAG) and to use the feature levels available between 11.1 and 10.0. Because we have not used the Device.CreateDeviceAndSwapChain override that accepts a SharpDX.DXGI.Adapter object instance, the device will be constructed using the first adapter found.

This is a common theme with the SharpDX constructors and method overrides, often implementing default behavior or excluding invalid combinations of parameters to simplify their usage, while still providing the option of more detailed control that is necessary with such a complex API.

SwapChainDescription (natively DXGI_SWAP_CHAIN_DESC) is describing a back buffer that is the same size as the window with a fullscreen refresh rate of 60 Hz. We have specified a format of SharpDX.DXGI.Format.R8G8B8A8_UNorm, meaning each pixel will be made up of 32-bits consisting of four 8-bit unsigned normalized values (for example, values between 0.0-1.0 represent the range 0-255) representing Red, Green, Blue, and Alpha respectively. UNorm refers to the fact that each of the values stored are normalized to 8-bit values between 0.0 and 1.0, for example, a red component stored in an unsigned byte of 255 is 1 and 127 becomes 0.5. A texture format ending in _UInt on the other hand is storing unsigned integer values, and _Float is using floating point values. Formats ending in _SRgb store gamma-corrected values, the hardware will linearize these values when reading and convert back to the sRGB format when writing out pixels.

The back buffer can only be created using a limited number of the available resource formats. The feature level also impacts the formats that can be used. Supported back buffer formats for feature level >= 11.0 are:

SharpDX.DXGI.Format.R8G8B8A8_UNorm

SharpDX.DXGI.Format.R8G8B8A8_UNorm_SRgb

SharpDX.DXGI.Format.B8G8R8A8_UNorm

SharpDX.DXGI.Format.B8G8R8A8_UNorm_SRgb

SharpDX.DXGI.Format.R16G16B16A16_Float

SharpDX.DXGI.Format.R10G10B10A2_UNorm

SharpDX.DXGI.Format.R10G10B10_Xr_Bias_A2_UNorm

We do not want to implement any multisampling of pixels at this time, so we have provided the default sampler mode for no anti-aliasing, that is, one sample and a quality of zero: new SampleDescription(1, 0).

The buffer usage flag is set to indicate that the buffer will be used as a back buffer and as a render-target output resource. The bitwise OR operator can be applied to all flags in Direct3D.

The number of back buffers for the swap chain is set to one and there are no flags that we need to add to modify the swap chain behavior.

With IsWindowed = true, we have indicated that the output will be windowed to begin with and we have passed the handle of the form we created earlier for the output window.

The swap effect used is SwapEffect.Discard, which will result in the back buffer contents being discarded after each swapChain.Present.

Note

Windows Store apps must use a swap effect of SwapEffect.FlipSequential, which in turn limits the valid resource formats for the back buffer to one of the following:

SharpDX.DXGI.Format.R8G8B8A8_UNorm

SharpDX.DXGI.Format.B8G8R8A8_UNorm

SharpDX.DXGI.Format.R16G16B16A16_Float

With the device and swap chain initialized, we now retrieve a reference to the back buffer so that we can create RenderTargetView. You can see here that we are not creating any new objects. We are simply querying the existing objects for a reference to the applicable Direct3D interfaces. We do still have to dispose of these correctly as the underlying COM reference counters will have been incremented.

Render loop

The next highlighted piece of code is the SharpDX.Windows.RenderLoop.Run helper function. This takes our form and delegate or Action as input, with delegate executed within a loop. The loop takes care of all application messages, and will listen for any application close events and exit the loop automatically, for example, if the form is closed. The render loop blocks the thread so that any code located after the call to RenderLoop.Run will not be executed until the loop has exited.

Now we execute our first rendering command which is to clear renderTargetView with a light blue color. This line is retrieving the immediate device context from the device and then executing the ClearRenderTargetView command. As this is not a deferred context the command is executed immediately.

Finally we tell the swap chain to present the back buffer (our renderTargetView that we just set to light blue) to the front buffer.

Finalization

The finalization stage is quite straight forward. After the RenderLoop exits, we clean up any resources that we have created and dispose of the device and swap chain.

All SharpDX classes that represent Direct3D objects implement the IDisposable interface and should be disposed off to release unmanaged resources.

There's more…

To make the example a little more interesting, try using a Linear interpolation (LERP) of the color that is being passed to the ClearRenderTargetView command. For example, the following code will interpolate the color between light and dark blue over 2 seconds:

var lerpColor = SharpDX.Color.Lerp(SharpDX.Color.LightBlue, 
                    SharpDX.Color.DarkBlue, 
                    (float)(totalSeconds / 2.0 % 1.0));
device.ImmediateContext.ClearRenderTargetView(
    renderTargetView,
    lerpColor);

You will have noticed that there are a number of other SharpDX assemblies available within the SharpDX binaries directory.

The SharpDX.Direct2D1.dll assembly provides you with the Direct2D API. SharpDX.D3DCompiler.dll provides runtime shader compilation, which we will be using to compile our shaders in later chapters. SharpDX.XAudio2.dll exposes the XAudio2 API for mixing voices and SharpDX.RawInput.dll provides access to the raw data sent from user input devices, such as the keyboard, mouse, and gamepads or joysticks. The Microsoft Media Foundation, for dealing with audio/video playback, is wrapped by the SharpDX.MediaFoundation.dll assembly.

Finally, the SharpDX.Toolkit.dll assemblies provide a high-level game API for Direct3D 11 much like XNA 4.0 does for Direct3D 9. These assemblies hide away a lot of the low-level Direct3D interaction and provide a number of compilation tools and convenience functions to streamline including shaders and other game content in your project. The framework is worth taking a look at for high-level operations, but as we will tend to be working with the low-level API, it is generally not suitable for our purposes here.

The SharpDX package provides binaries for various platforms. We have used the DirectX 11.1 .NET 4.0 or the DirectX 11.2 .NET 4.0 build here and will use the WinRT build in Chapter 11, Integrating Direct3D with XAML and Windows 8.1. SharpDX also provides assemblies and classes for Direct3D 11, Direct3D 10, and Direct3D 9.

See also

  • We will see how to gain access to the Direct3D 11.1/11.2 device and swap chain in the next recipe, Initializing a Direct3D 11.1/11.2 device and swap chain.
  • In Chapter 2, Rendering with Direct3D, we will cover more detail about rendering, and focus on resource creation, the rendering loop, and simple shaders.
  • Chapter 11, Integrating Direct3D with XAML and Windows 8.1, shows how to build a Windows Store app for Windows 8.1.
  • The Microsoft Developer Network (MSDN) provides a great deal of useful information. The Direct3D launch page can be found at http://msdn.microsoft.com/en-us/library/windows/desktop/hh309466(v=vs.85).aspx.