设置 DirectX 资源和显示图像

下面我们将为你介绍如何创建 Direct3D 设备、交换链和呈现目标视图,以及如何向屏幕显示呈现的图像。

目标:在 C++ 通用 Windows 平台 (UWP) 应用中设置 DirectX 资源并显示纯色。

先决条件

我们假定你熟悉 C++。 你还需要具有图形编程概念方面的基本经验。

完成所需时间:20 分钟。

Instructions

1. 使用 ComPtr 声明 Direct3D 接口变量

我们使用 Windows 运行时 C++ 模板库 (WRL) 中的 ComPtr 智能指针模板来声明 Direct3D 接口变量,以便可以采用防异常的方式管理这些变量的生存时间。 然后,使用这些变量访问 ComPtr class 及其成员。 例如:

    ComPtr<ID3D11RenderTargetView> m_renderTargetView;
    m_d3dDeviceContext->OMSetRenderTargets(
        1,
        m_renderTargetView.GetAddressOf(),
        nullptr // Use no depth stencil.
        );

如果使用 ComPtr 声明 ID3D11RenderTargetView,则可以接着使用 ComPtr 的 GetAddressOf 方法来获取指向 ID3D11RenderTargetView (**ID3D11RenderTargetView) 的指针的地址以传递给 ID3D11DeviceContext::OMSetRenderTargetsOMSetRenderTargets 会将呈现器目标绑定到输出合并阶段,以将呈现器目标指定为输出目标。

在启动示例应用之后,该示例应用将初始化并加载,然后就可以运行了。

2. 创建 Direct3D 设备

若要使用 Direct3D API 来呈现场景,必须首先创建一个代表显示适配器的 Direct3D 设备。 要创建 Direct3D 设备,需要调用 D3D11CreateDevice 函数。 在 D3D_FEATURE_LEVEL 值的数组中指定级别 9.1 至 11.1。 Direct3D 按顺序检查数组并返回支持的最高功能级别。 因此,为了获取可用的最高级功能级别,我们将 D3D_FEATURE_LEVEL 数组条目按从高到低的顺序列出。 将 D3D11_CREATE_DEVICE_BGRA_SUPPORT 标记传递到 Flags 参数,使 Direct3D 资源与 Direct2D 交互操作。 如果使用调试版本,则还需传递 D3D11_CREATE_DEVICE_DEBUG 标记。 有关调试应用的详细信息,请参阅使用调试层来调试应用

通过查询从 D3D11CreateDevice 返回的 Direct3D 11 设备和设备上下文来获取 Direct3D 11.1 设备 (ID3D11Device1) 和设备上下文 (ID3D11DeviceContext1)。

        // First, create the Direct3D device.

        // This flag is required in order to enable compatibility with Direct2D.
        UINT creationFlags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;

#if defined(_DEBUG)
        // If the project is in a debug build, enable debugging via SDK Layers with this flag.
        creationFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

        // This array defines the ordering of feature levels that D3D should attempt to create.
        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_1
        };

        ComPtr<ID3D11Device> d3dDevice;
        ComPtr<ID3D11DeviceContext> d3dDeviceContext;
        DX::ThrowIfFailed(
            D3D11CreateDevice(
                nullptr,                    // Specify nullptr to use the default adapter.
                D3D_DRIVER_TYPE_HARDWARE,
                nullptr,                    // leave as nullptr if hardware is used
                creationFlags,              // optionally set debug and Direct2D compatibility flags
                featureLevels,
                ARRAYSIZE(featureLevels),
                D3D11_SDK_VERSION,          // always set this to D3D11_SDK_VERSION
                &d3dDevice,
                nullptr,
                &d3dDeviceContext
                )
            );

        // Retrieve the Direct3D 11.1 interfaces.
        DX::ThrowIfFailed(
            d3dDevice.As(&m_d3dDevice)
            );

        DX::ThrowIfFailed(
            d3dDeviceContext.As(&m_d3dDeviceContext)
            );

3. 创建交换链

接下来,创建设备用于呈现和显示的交换链。 声明并初始化 DXGI_SWAP_CHAIN_DESC1 结构来描述该交换链。 接着,将该交换链设置为翻转模型(即,在 SwapEffect 成员中设置 DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL 值的交换链)并将 Format 成员设置为 DXGI_FORMAT_B8G8R8A8_UNORM。 将 SampleDesc 成员指定的 DXGI_SAMPLE_DESC 结构的 Count 成员设置为 1,并将 DXGI_SAMPLE_DESC 的 Quality 成员设置为 0,因为翻转模型不支持多重采样抗锯齿 (MSAA)。 将 BufferCount 成员设置为 2,让交换链可以使用前台缓冲区来向显示设备显示,并使用后台缓冲区充当呈现器目标。

通过查询 Direct3D 11.1 设备来获取基本 DXGI 设备。 为了最大程度降低电耗(对于笔记本电脑和平板电脑等使用电池供电的设备,这样做很重要),将 1 作为 DXGI 可排队的后台缓冲区帧的最大数量来调用 IDXGIDevice1::SetMaximumFrameLatency 方法。 这样可确保仅在垂直空白之后才呈现应用。

最后,要创建交换链,需要从 DXGI 设备获取父工厂。 调用 IDXGIDevice::GetAdapter 来获取设备的适配器,然后在适配器上调用 IDXGIObject::GetParent 来获取父工厂 (IDXGIFactory2)。 要创建交换链,需要使用交换链描述符和应用的核心窗口来调用 IDXGIFactory2::CreateSwapChainForCoreWindow

            // If the swap chain does not exist, create it.
            DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {0};

            swapChainDesc.Stereo = false;
            swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
            swapChainDesc.Scaling = DXGI_SCALING_NONE;
            swapChainDesc.Flags = 0;

            // Use automatic sizing.
            swapChainDesc.Width = 0;
            swapChainDesc.Height = 0;

            // This is the most common swap chain format.
            swapChainDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;

            // Don't use multi-sampling.
            swapChainDesc.SampleDesc.Count = 1;
            swapChainDesc.SampleDesc.Quality = 0;

            // Use two buffers to enable the flip effect.
            swapChainDesc.BufferCount = 2;

            // We recommend using this swap effect for all applications.
            swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;


            // Once the swap chain description is configured, it must be
            // created on the same adapter as the existing D3D Device.

            // First, retrieve the underlying DXGI Device from the D3D Device.
            ComPtr<IDXGIDevice2> dxgiDevice;
            DX::ThrowIfFailed(
                m_d3dDevice.As(&dxgiDevice)
                );

            // Ensure that DXGI does not queue more than one frame at a time. This both reduces
            // latency and ensures that the application will only render after each VSync, minimizing
            // power consumption.
            DX::ThrowIfFailed(
                dxgiDevice->SetMaximumFrameLatency(1)
                );

            // Next, get the parent factory from the DXGI Device.
            ComPtr<IDXGIAdapter> dxgiAdapter;
            DX::ThrowIfFailed(
                dxgiDevice->GetAdapter(&dxgiAdapter)
                );

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

            // Finally, create the swap chain.
            CoreWindow^ window = m_window.Get();
            DX::ThrowIfFailed(
                dxgiFactory->CreateSwapChainForCoreWindow(
                    m_d3dDevice.Get(),
                    reinterpret_cast<IUnknown*>(window),
                    &swapChainDesc,
                    nullptr, // Allow on all displays.
                    &m_swapChain
                    )
                );

4. 创建呈现器目标视图

要将图形呈现到窗口,需要创建一个呈现器目标视图。 调用 IDXGISwapChain::GetBuffer 来获取交换链接的后台缓冲区,以便在创建呈现器目标视图时使用。 将后台缓冲区指定为一个 2D 纹理 (ID3D11Texture2D)。 要创建呈现器目标视图,需要使用交换链的后台缓冲区调用 ID3D11Device::CreateRenderTargetView。 通过将视区 (D3D11_VIEWPORT) 指定为交换链的后台缓冲区的最大大小,指定绘制到整个核心窗口。 在对 ID3D11DeviceContext::RSSetViewports 的调用中使用视区将视区绑定到管道的光栅化阶段。 光栅化阶段将向量信息转换为光栅图像。 此情况下不要求转换,因为我们只是显示纯色。

        // Once the swap chain is created, create a render target view.  This will
        // allow Direct3D to render graphics to the window.

        ComPtr<ID3D11Texture2D> backBuffer;
        DX::ThrowIfFailed(
            m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))
            );

        DX::ThrowIfFailed(
            m_d3dDevice->CreateRenderTargetView(
                backBuffer.Get(),
                nullptr,
                &m_renderTargetView
                )
            );


        // After the render target view is created, specify that the viewport,
        // which describes what portion of the window to draw to, should cover
        // the entire window.

        D3D11_TEXTURE2D_DESC backBufferDesc = {0};
        backBuffer->GetDesc(&backBufferDesc);

        D3D11_VIEWPORT viewport;
        viewport.TopLeftX = 0.0f;
        viewport.TopLeftY = 0.0f;
        viewport.Width = static_cast<float>(backBufferDesc.Width);
        viewport.Height = static_cast<float>(backBufferDesc.Height);
        viewport.MinDepth = D3D11_MIN_DEPTH;
        viewport.MaxDepth = D3D11_MAX_DEPTH;

        m_d3dDeviceContext->RSSetViewports(1, &viewport);

5. 显示呈现的图像

我们将进入一个不断呈现和显示场景的无限循环。

在此循环中,我们调用:

  1. ID3D11DeviceContext::OMSetRenderTargets 以将呈现器目标指定为输出目标。
  2. ID3D11DeviceContext::ClearRenderTargetView 以将呈现目标清除为纯色。
  3. IDXGISwapChain::Present 以向窗口显示呈现的图像。

由于我们之前将最大帧延迟设置为 1,因此,Windows 通常会将呈现循环减慢至屏幕刷新速率,通常大约为 60 Hz。 Windows 通过在应用调用 Present 时使应用进入睡眠状态来减慢呈现循环。 在刷新屏幕之前,Windows 会将应用保持为睡眠状态。

        // Enter the render loop.  Note that UWP apps should never exit.
        while (true)
        {
            // Process events incoming to the window.
            m_window->Dispatcher->ProcessEvents(CoreProcessEventsOption::ProcessAllIfPresent);

            // Specify the render target we created as the output target.
            m_d3dDeviceContext->OMSetRenderTargets(
                1,
                m_renderTargetView.GetAddressOf(),
                nullptr // Use no depth stencil.
                );

            // Clear the render target to a solid color.
            const float clearColor[4] = { 0.071f, 0.04f, 0.561f, 1.0f };
            m_d3dDeviceContext->ClearRenderTargetView(
                m_renderTargetView.Get(),
                clearColor
                );

            // Present the rendered image to the window.  Because the maximum frame latency is set to 1,
            // the render loop will generally be throttled to the screen refresh rate, typically around
            // 60 Hz, by sleeping the application on Present until the screen is refreshed.
            DX::ThrowIfFailed(
                m_swapChain->Present(1, 0)
                );
        }

6. 调整应用窗口和交换链的缓冲区的大小

如果应用窗口大小发生改变,则应用必须调整交换链的缓冲区的大小,重新创建呈现器目标视图,然后显示大小调整后的呈现图像。 要调整交换链缓冲区的大小,需要调用 IDXGISwapChain::ResizeBuffers。 在此调用中,保持缓冲区的数量和缓冲区的格式不变(将 BufferCount 参数设置为 2,并将 NewFormat 参数设置为 DXGI_FORMAT_B8G8R8A8_UNORM)。 使交换链的后台缓冲区的大小与调整后的窗口大小相同。 在调整了交换链的缓冲区的大小之后,创建新的呈现器目标并显示新呈现的图像(与初始化应用时类似)。

            // If the swap chain already exists, resize it.
            DX::ThrowIfFailed(
                m_swapChain->ResizeBuffers(
                    2,
                    0,
                    0,
                    DXGI_FORMAT_B8G8R8A8_UNORM,
                    0
                    )
                );

总结和后续步骤

我们创建了一个 Direct3D 设备、交换链和呈现器目标视图,并向显示器显示了呈现的图像。

接下来,我们还要在屏幕上绘制一个三角形。

创建着色器和绘制基元