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:
- 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).
- Enter a solution name and location and click on Ok.
- You should now have a new Blank Solution at
C:\Projects\D3DRendering\D3DRendering.sln
. - Extract the contents of the SharpDX package into
C:\Projects\D3DRendering\External
. TheC:\Projects\D3DRendering\External\Bin
folder should now exist among others.
How to do it…
With the solution open, let's create a new project:
- Add a new Windows Form Application project to the solution with .NET Framework 4.5 selected.
- We will name the project
Ch01_01EmptyProject
. - 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.
- 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. - 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. - Click on Ok in Reference Manager to accept the changes.
- Add the following
using
directives toProgram.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;
- 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 ... }
- Within the same
Main()
function, we now create a simple render loop using a SharpDX utility classSharpDX.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
- 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
- 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 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.
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
.
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.
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.
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.