Partager via


Work with the DirectX app template's device resources

[ This article is for Windows 8.x and Windows Phone 8.x developers writing Windows Runtime apps. If you’re developing for Windows 10, see the latest documentation ]

Understand the role of the Microsoft DirectX Graphics Infrastructure (DXGI) in your Windows Store DirectX game. DXGI is a set of APIs used to configure and manage low-level graphics and graphics adapter resources. Without it, you'd have no way to draw your game's graphics to a window.

Think of DXGI this way: to directly access the GPU and manage its resources, you must have a way to describe it to your app. The most important piece of info you need about the GPU is the place to draw pixels so it can send those pixels to the screen. Typically this is called the "back buffer"—a location in GPU memory where you can draw the pixels and then have it "flipped" or "swapped" and sent to the screen on a refresh signal. DXGI lets you acquire that location and the means to use that buffer (called a swap chain because it is a chain of swappable buffers, allowing for multiple buffering strategies).

To do this, you need access to write to the swap chain, and a handle to the window that will display the current back buffer for the swap chain. You also need to connect the two to ensure that the operating system will refresh the window with the contents of the back buffer when you request it to do so.

The overall process for drawing to the screen is as follows:

  • Get a CoreWindow for your app.
  • Get an interface for the Direct3D device and context.
  • Create the swap chain to display your rendered image in the CoreWindow.
  • Create a render target for drawing and populate it with pixels.
  • Present the swap chain!

Get a CoreWindow for your app

In the DirectX app template, the CoreWindow object for your app is obtained in the view provider's implementation of the

IFrameworkView::SetWindow method (see App.cpp). (For more information on CoreWindow, see How to set up your app to display a view.)Additionally, the view provider passes a reference to the app class's DeviceResources object (see DeviceResources.cpp), where it is assigned as a class-level member for use with Direct3D configuration methods.

This is a good place to hook up any window-specific display events.

Here's an example of connecting your device resources to a CoreWindow, and registering the basic necessary input event callbacks. (This code is in App.cpp.)

// Called when the CoreWindow object is created (or recreated).
void App::SetWindow(CoreWindow^ window)
{
 m_coreWindow = window;
 m_deviceResources->SetWindow(m_coreWindow.Get());

    window->SizeChanged += 
        ref new TypedEventHandler<CoreWindow^, WindowSizeChangedEventArgs^>(this, &App::OnWindowSizeChanged);

    window->VisibilityChanged +=
        ref new TypedEventHandler<CoreWindow^, VisibilityChangedEventArgs^>(this, &App::OnVisibilityChanged);

    window->Closed += 
        ref new TypedEventHandler<CoreWindow^, CoreWindowEventArgs^>(this, &App::OnWindowClosed);

}

If your own app needs to handle other events on the CoreWindow, hook up those handlers here too.

Get an interface for the Direct3D device and context

At the same time, your app must acquire an interface for the Direct3D hardware (the GPU), represented as instances of ID3D11Device and ID3D11DeviceContext. The former is a virtual representation of the GPU resources, and the latter is a device-agnostic abstraction of the rendering pipeline and process. Here's an easy way to think of it: ID3D11Device contains the graphics methods you call infrequently, usually before any rendering occurs, to acquire and configure the set of resources you need to start drawing pixels. ID3D11DeviceContext, on the other hand, contains the methods you call every frame: loading in buffers and views and other resources, changing output-merger and rasterizer state, managing shaders, and drawing the results of passing those resources through the states and shaders.

There's one very important part of this process: setting the feature level. The feature level tells DirectX the minimum level of hardware your app supports, with D3D_FEATURE_LEVEL_9_1 as the lowest feature set and D3D_FEATURE_LEVEL_11_1 as the current highest. The DirectX app template supports 9_1 as the minimum. Take some time to read up on Direct3D feature levels and assess for yourself the minimum feature level you want to support in your game and to understand the implications of your choice.

The template obtains references (pointers) to both the Direct3D device and device context and stores them as class-level variables on the DeviceResources instance (as ComPtr smart pointers). These are the references you should use if you make modifications to the graphics configuration provided in the template.

(This code is in the CreateDeviceResources method implementation in DeviceResources.cpp.)

// This flag adds support for surfaces with a color-channel ordering different
    // from the API default. It is required for compatibility with Direct2D.
    UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

    // This array defines the set of DirectX hardware feature levels this app will support.
    // Note the ordering should be preserved.
    // Don't forget to declare your app's minimum required feature level in its
    // description.  All apps are assumed to support 9.1 unless otherwise stated.
    D3D_FEATURE_LEVEL featureLevels[] =
    {
        D3D_FEATURE_LEVEL_11_1,
        D3D_FEATURE_LEVEL_11_0,
        D3D_FEATURE_LEVEL_10_1,
        D3D_FEATURE_LEVEL_10_0,
        D3D_FEATURE_LEVEL_9_3,
        D3D_FEATURE_LEVEL_9_2,
        D3D_FEATURE_LEVEL_9_1
    };

    // Create the Direct3D 11 API device object and a corresponding context.
    ComPtr<ID3D11Device> device;
    ComPtr<ID3D11DeviceContext> context;

    HRESULT hr = D3D11CreateDevice(
        nullptr,                    // Specify nullptr to use the default adapter.
        D3D_DRIVER_TYPE_HARDWARE,   // Create a device using the hardware graphics driver.
        0,                          // Should be 0 unless the driver is D3D_DRIVER_TYPE_SOFTWARE.
        creationFlags,              // Set debug and Direct2D compatibility flags.
        featureLevels,              // List of feature levels this app can support.
        ARRAYSIZE(featureLevels),   // Size of the list above.
        D3D11_SDK_VERSION,          // Always set this to D3D11_SDK_VERSION for Windows Store apps.
        &device,                    // Returns the Direct3D device created.
        &m_d3dFeatureLevel,         // Returns feature level of device created.
        &context                    // Returns the device immediate context.
        );

    if (FAILED(hr))
    {
         // Handle device interface creation failure if it occurs.
    }

    // Store pointers to the Direct3D 11.1 API device and immediate context.
 device.As(&m_d3dDevice);
 context.As(&m_d3dContext);

    // Create the DXGI device object to use in other factories, such as Direct2D.
    ComPtr<IDXGIDevice3> dxgiDevice;
    m_d3dDevice.As(&dxgiDevice);

Create the swap chain

Okay, you have a window to draw into and an interface to the GPU to use for that drawing. Now let's see how the template brings them together.

First, you tell DXGI what values to use for the properties of the swap chain, by using a DXGI_SWAP_CHAIN_DESC1 structure. Six fields are particularly important:

  • BufferUsage: Set this to DXGI_USAGE_RENDER_TARGET_OUTPUT. This indicates that the swap chain will be drawn into as a Direct3D render-target resource, such as a Texture2D resource.
  • Width and Height: Set these to the current size of the render target that your device context draws into, which should be the same as the size of the window. If the size of the render target is smaller than the window, set the Scaling field accordingly. (Many modern GPU devices support hardware-based scaling.)
  • SwapEffect: Always set this to DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL.
  • Format: By default, the template sets this to DXGI_FORMAT_B8G8R8A8_UNORM, which specifies 32-bit color (8 bits for each of the three RGB color channels, and 8 bits for the alpha channel.)
  • BufferCount: The template sets this to 2, for a traditional double-buffer behavior and the minimum for Windows Store certification. Set it to 3 for a triple buffer if your graphics content takes more than one monitor refresh to render a single frame (for example, more than 16 ms).

After you have specified a configuration for the swap chain, you must acquire a DXGI "adapter" and the factory for it, and then use that factory to create the swap chain with a call to IDXGIFactory2::CreateSwapChainForCoreWindow. Short form? Get the ID3D11Device reference you created previously, upclass it to the base IDXGIDevice3, and then call IDXGIDevice::GetAdapter to acquire the adapter. Get the parent factory for that adapter by calling IDXGIFactory2::GetParent (IDXGIFactory2 inherits from IDXGIObject), and use that same factory to create the swap chain, as seen in the following code sample.

DXGI_SWAP_CHAIN_DESC1 swapChainDesc = { 0 };

swapChainDesc.Width = static_cast<UINT>(m_d3dRenderTargetSize.Width); // Match the size of the window.
swapChainDesc.Height = static_cast<UINT>(m_d3dRenderTargetSize.Height);
swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; // This is the most common swap chain format.
swapChainDesc.Stereo = false;
swapChainDesc.SampleDesc.Count = 1; // Don't use multi-sampling.
swapChainDesc.SampleDesc.Quality = 0;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.BufferCount = 2; // Use double-buffering to minimize latency.
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // All Windows Store apps must use this SwapEffect.
swapChainDesc.Flags = 0;
swapChainDesc.Scaling = DXGI_SCALING_NONE;
swapChainDesc.AlphaMode = DXGI_ALPHA_MODE_IGNORE;

// This sequence obtains the DXGI factory that was used to create the Direct3D device interface.
ComPtr<IDXGIDevice3> dxgiDevice;
DX::ThrowIfFailed(
            m_d3dDevice.As(&dxgiDevice)
);

ComPtr<IDXGIAdapter> dxgiAdapter;
DX::ThrowIfFailed(
            dxgiDevice->GetAdapter(&dxgiAdapter)
);

ComPtr<IDXGIFactory2> dxgiFactory;
            dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory));

dxgiFactory->CreateSwapChainForCoreWindow(
            m_d3dDevice.Get(),
            reinterpret_cast<IUnknown*>(m_window.Get()),
            &swapChainDesc,
            nullptr,
            &m_swapChain
);

If you're just starting out, it's probably best to use the default configurations that are provided in the template because those settings meet the specific requirements of Windows Store apps.

Now you've got a window from the operating system, a way to access the GPU and its resources, and a swap chain to display the rendering results. All that's left is to wire the whole thing together!

Create a render target for drawing

The shader pipeline needs a resource to draw pixels into. As you see in the DirectX app template, the simplest way to create this resource is to define a ID3D11Texture2D resource as a back buffer for the pixel shader to draw into, and then read that texture into the swap chain.

To do this, you create a render-target view. In Direct3D, a view is a way to access a specific resource. In this case, the view enables the pixel shader to write into the texture as it completes its per-pixel operations.

Let's look at the code for this in the DeviceResources class, in the CreateWindowSizeDependentResources method. First, the code creates a back buffer to use as a render target, returning a view to the render-target texture resource.

// Create a render-target view of the swap chain back buffer.
    ComPtr<ID3D11Texture2D> backBuffer;

    m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer));
    m_d3dDevice->CreateRenderTargetView(
            backBuffer.Get(),
            nullptr,
            &m_d3dRenderTargetView
    );

The template also creates a depth-stencil buffer. A depth-stencil buffer is also an ID3D11Texture2D resource, and is typically used to determine which pixels have draw priority during rasterization based on the distance of the objects in the scene from the camera. A depth-stencil buffer can also be used for stencil effects, where specific pixels are discarded or ignored during rasterization. This buffer must be the same size as the render target. Note that you cannot read from or render to the depth-stencil buffer's texture resource because it is used exclusively by the shader pipeline before and during final rasterization.

The template also creates a view for the depth-stencil buffer as an ID3D11DepthStencilView. If you don't supply this view, no per-pixel depth testing is performed and the objects in your scene may seem a bit inside-out at the very least!

// Create a depth-stencil view for use with 3D rendering if needed.
    CD3D11_TEXTURE2D_DESC depthStencilDesc(
        DXGI_FORMAT_D24_UNORM_S8_UINT, 
        static_cast<UINT>(m_d3dRenderTargetSize.Width),
        static_cast<UINT>(m_d3dRenderTargetSize.Height),
        1, // This depth stencil view has only one texture.
        1, // Use a single mipmap level.
        D3D11_BIND_DEPTH_STENCIL
        );

    ComPtr<ID3D11Texture2D> depthStencil;
    
    m_d3dDevice->CreateTexture2D(
            &depthStencilDesc,
            nullptr,
            &depthStencil
    );

    CD3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc(D3D11_DSV_DIMENSION_TEXTURE2D);

    m_d3dDevice->CreateDepthStencilView(
            depthStencil.Get(),
            &depthStencilViewDesc,
            &m_d3dDepthStencilView
    );

The last step is to create a viewport, which defines the visible rectangle of the back buffer displayed on the screen. You can change the part of the buffer that is displayed on the screen by changing the parameters. By default, the template targets the whole window size. For fun, change the supplied coordinate values and observe the results.

 // Set the 3D rendering viewport to target the entire window.
m_screenViewport = CD3D11_VIEWPORT(
        0.0f,
        0.0f,
        m_d3dRenderTargetSize.Width,
        m_d3dRenderTargetSize.Height
);

m_d3dContext->RSSetViewports(1, &m_screenViewport);

    

And that's how you go from nothing to drawing pixels in a window! The template code handles debugging and DirectX-specific errors, plus changes to screen orientation and resolution. As you start out, you don't need to modify DeviceResources much, but it's a good idea to become familiar with how DirectX, through DXGI, manages the core resources you need to start drawing pixels.

Next you'll look at the structure of the graphics pipeline in the DirectX app template; see Understand the DirectX app template's rendering pipeline.

Microsoft Visual Studio 2013 DirectX app template notes

The code for creating the swap chain can be found in the CreateWindowSizeDependentResources method on the DeviceResources class implementation, in DeviceResources.cpp. When you modify this code, note the following:

  • The code has two notable branches: one that creates a swap chain for a CoreWindow, and one that creates a swap chain for the XAML framework's composition engine. The branch you get depends on whether you chose the XAML version of the template.
  • For the XAML template, the swap chain is bound to a SwapChainPanel XAML element. In the template, that element exists at the bottom of the XAML control tree, with other controls drawn on top of it. Theoretically, the XAML framework can support any number of swap-chain panels, but performance will suffer significantly if you have more than two. For situations where you want to use the GPU and DirectX to draw into several controls, look at the SurfaceImageSource and VirtualSurfaceImageSource controls.
  • If you aren't making a game and have more experience with XAML, the DirectX app (XAML) template might be a better place to start.

Use the Microsoft Visual Studio 2013 DirectX templates

Understand the DirectX app template's rendering pipeline

Work with shaders and shader resources