Direct3D Rendering Cookbook
上QQ阅读APP看书,第一时间看更新

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.

Note

The framework is based on elements of the SharpDX Windows 8 sample code and the C++ DirectX Windows Store app template. Changes have been made to simplify the code and to make it suitable for desktop applications.

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:

  1. From the downloadable companion content for this book, retrieve the Common.csproj C# class library project and source files.
  2. Copy and add Common.csproj to our solution (for example, D3DRendering.sln).
  3. 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.

  1. Add a reference to Common.csproj within your Direct3D application project.
  2. 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 to System.Drawing and System.Windows.Forms are also required for desktop applications.
  3. Create a new class file that will house a descendent of the D3DApplicationDesktop class. Let's call this new class D3DApp. The following code snippet shows the class declaration and the required using 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)
        { }
        ...
    }
  4. Apart from the constructor shown previously, our D3DApp class must also implement the public void Run() method for the abstract D3DApplicationDesktop 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();
        }
    }
  5. 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 a D3DApplicationDesktop.Run implementation:
    public override void Run()
    {
        SharpDX.Windows.RenderLoop.Run(Window, () => {
            ... Render frame
            Present();
        });
    }
  6. To take advantage of the lifecycle management built into the Common.DeviceManager class and the Common.D3DApplicationBase abstract base class, we can optionally override the following methods in our D3DApp 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)
    { ... }
  7. 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 a using 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.

How it works…

Common project's class diagram showing the device manager and Direct3D application base

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