Using the sample rendering framework
To work with complex scenes more easily, we are going to explore a set of classes that will serve as our simple rendering framework throughout the book. This framework will take care of initializing our Direct3D device, swap chain and render targets, and provide appropriate methods and events for implementing the Direct3D resource lifecycle management in our example applications.
The three key elements of this framework include the following:
- Device manager: This is a class that manages the lifecycle of our Direct3D device and device context.
- Direct3D application: This is a set of classes that manage our swap chain and render targets, along with other common-size dependent resources, such as the depth/stencil buffer and viewport setup. We descend from one of these to create our Direct3D application/render loop.
- Renderer: This is a small class that we use to implement a renderer for a single element/area of a scene. We create instances of these within our Direct3D application class.
Getting ready
The sample rendering framework can be found with the downloadable companion content for this book. After downloading this content, we can prepare a project for use with the framework as follows:
- From the downloadable companion content for this book, retrieve the
Common.csproj
C# class library project and source files. - Copy and add
Common.csproj
to our solution (for example,D3DRendering.sln
). - Add a new Windows Form Application project to the solution in order to create your Direct3D application.
Note
For Windows Store apps, use the Common.WinRT.csproj
class library instead. Chapter 11, Integrating Direct3D with XAML and Windows 8.1, provides an overview of working with this library.
How to do it…
Here, we will walk through the steps necessary to work with the sample rendering framework.
- Add a reference to
Common.csproj
within your Direct3D application project. - In addition to the SharpDX References we added to our project in the Building a Direct3D 11 application with C# and SharpDX recipe in Chapter 1, Getting Started with Direct3D, we will now also include
.\External\bin\SharpDX.Direct2D1.dll
for supporting 2D text rendering. References toSystem.Drawing
andSystem.Windows.Forms
are also required for desktop applications. - Create a new class file that will house a descendent of the
D3DApplicationDesktop
class. Let's call this new classD3DApp
. The following code snippet shows the class declaration and the requiredusing
directives:using System; ... using System.Windows.Forms; // SharpDX namespaces using SharpDX; using SharpDX.DXGI; using SharpDX.Direct3D11; using Common; // Resolve class name conflicts by explicitly stating // which class they refer to: using Buffer = SharpDX.Direct3D11.Buffer; ... public class D3DApp : D3DApplicationDesktop { public PrimitivesApp(System.Windows.Forms.Form window) : base(window) { } ... }
- Apart from the constructor shown previously, our
D3DApp
class must also implement thepublic void Run()
method for the abstractD3DApplicationDesktop
class. This will typically contain the main application message loop and rendering loop like the following code snippet:public override void Run() { While(running) { ... Process messages ... Render frame Present(); } }
- For desktop applications, SharpDX provides a helper class
SharpDX.Windows.RenderLoop
that implements the rendering loop and processes any window messages for us. The following code demonstrates its use within aD3DApplicationDesktop.Run
implementation:public override void Run() { SharpDX.Windows.RenderLoop.Run(Window, () => { ... Render frame Present(); }); }
- To take advantage of the lifecycle management built into the
Common.DeviceManager
class and theCommon.D3DApplicationBase
abstract base class, we can optionally override the following methods in ourD3DApp
implementation:// Override / extend the default SwapChainDescription1 protected override SwapChainDescription1 CreateSwapChainDescription() { ... } // Event-handler for DeviceManager.OnInitialize protected override void CreateDeviceDependentResources(DeviceManager deviceManager) { ... } // Event-handler for D3DApplicationBase.OnSizeChanged protected override void CreateSizeDependentResources(D3DApplicationBase app) { ... }
- We are now able to create an instance of
D3DApp
, initialize the Direct3D device and resources, and then start the application loop. To ensure all resources are correctly released, we can use ausing
code block. The following code snippet shows how this might be done:using (var app = new D3DApp(form)) { // Only render frames at the maximum rate the // display device can handle. app.VSync = true; // Initialize the framework (creates D3D device etc) // and any device dependent resources are also created. app.Initialize(); // Run the application message/rendering loop. app.Run(); }
How it works…
The Common
project includes a simple framework that consists of three main areas: the device manager, the Direct3D application classes, and the renderer classes with a number of classes inheriting the latter two. The following class diagram shows the relevant methods and properties of the device manager and Direct3D base application classes. The methods that we have overridden and their respective events are highlighted along with the Direct3D device and context properties on the device manager.
The device manager (the DeviceManager
class) takes care of creating our Direct3D device and context within its Initialize
function. In addition, the device manager provides an event to notify any listeners whenever the device manager is initialized/reinitialized—the event that our CreateDeviceDependentResources
function is tied to. This facilitates the recreation of resources when a device is lost/recreated. This is necessary as resources that have been created with a specific device must now be recreated with the new device.
The D3DApplicationBase
base class provides appropriate methods that can be overridden to participate within the rendering process and Direct3D resource management. The D3DApplicationDesktop
class descends from this base class and provides the ability to initialize a swap chain from a Windows Desktop window handle. We then implement the abstract base Run()
method in order to provide a render and message loop.
By overriding the D3DApplicationBase.CreateSwapChainDescription
method, we are able to control the creation of the swap chain and render target. For example, if we wanted to implement multisample antialiasing (MSAA), we would override this method and update the description accordingly.
We override the D3DApplicationBase.CreateDeviceDependentResources
method to create any Direct3D resources that depend on the Direct3D device instance. This is an event-handler that is attached to the device manager that is triggered whenever the Direct3D device is created/recreated.
In addition, we create any resources that depend upon the swap chain/render target size within an overridden D3DApplicationBase.CreateSizeDependentResources
function. This is an event-handler that is attached to any appropriate window size change events.
Many of our Common
project classes descend from the SharpDX.Component
class. This utility class includes methods for managing the IDisposable
objects. As a majority of our code interacts with Direct3D, a native COM-based API, it is important that we are correctly disposing of these objects to prevent memory/resource leaks. The SharpDX.Component.ToDispose<T>(T obj)
method allows us to create an instance of IDisposable
objects without having to explicitly dispose of the instance; instead, any objects registered within the ToDispose
method will be automatically released upon disposal of our SharpDX.Component
instance.
By declaring the D3DApp
instance within a using
block, as long as our D3DApp
class passes all created Direct3D resources into the ToDispose
method, they will be correctly released. The counterpart to this is the SharpDX.Component.RemoveAndDispose<T>(ref T obj)
function, where we can manually release resources at the beginning of the implementation of our CreateDeviceDependentResources
or CreateSizeDependentResources
methods. The following code snippet shows how to reinitialize a resource. Note that there is no need to check for null
:
RemoveAndDispose(ref myDirect3DResource); ... myDirect3DResource = ToDispose(new ...);
See also
- The following recipes Creating the device-dependent resources, Creating the size-dependent resources, and Creating a Direct3D renderer class explore the sample rendering framework further, before we implement a full example within Rendering primitives
- Chapter 11, Integrating Direct3D with XAML and Windows 8.1 includes the changes to the rendering framework that are necessary to work with the Windows Store apps