设置 DirectX 资源并显示图像

在这里,我们将介绍如何创建 Direct3D 设备、交换链和呈现目标视图,以及如何将呈现的图像呈现给显示器。

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

先决条件

我们假设你熟悉C++。 你还需要图形编程概念的基本经验。

完成时间: 20 分钟。

说明书

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

我们使用 Windows 运行时 C++ 模板库(WRL)的 ComPtr 智能指针 模板来声明 Direct3D 接口变量,以便能够异常安全地管理这些变量的生命周期。 然后,可以使用这些变量访问 ComPtr 类 及其成员。 例如:

    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 标志传递给 标志 参数,使 Direct3D 资源与 Direct2D 互操作。 如果使用调试版本,则还会传递 D3D11_CREATE_DEVICE_DEBUG 标志。 有关调试应用的详细信息,请参阅 使用调试层调试应用

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

        // 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 结构的 计数 成员设置为 1,并将 DXGI_SAMPLE_DESCQuality 成员设置为零,因为翻转模型不支持多个样本抗锯齿(MSAA)。 我们将 BufferCount 成员设置为 2,以便交换链可以使用前缓冲区将内容呈现给显示设备,并使用后缓冲区作为渲染目标。

我们通过查询 Direct3D 11.1 设备来获取基础 DXGI 设备。 为了将电池供电设备(如笔记本电脑和平板电脑)的耗电量降到最低,这一点非常重要,我们调用 IDXGIDevice1::SetMaximumFrameLatency 方法,并将 DXGI 可以排队的最大后台缓冲区帧数设定为 1。 这可确保应用仅在垂直空白之后呈现。

若要最终创建交换链,我们需要从 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 会让应用进入睡眠状态以减缓渲染循环。 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 设备、交换链(swap chain)和渲染目标视图,并将渲染的图像呈现给显示器。

接下来,我们还在显示器上绘制一个三角形。

创建着色器和绘制基元