交換鏈結縮放和重迭

瞭解如何建立縮放交換鏈結以在行動裝置上更快轉譯,並使用覆蓋交換鏈結 (如果可用) 來提高視覺品質。

DirectX 11.2 中的交換鏈結

Direct3D 11.2 可讓您建立通用 Windows 平台 (UWP) 應用程式,其中包含從非原生 (縮減) 解析度相應增加的交換鏈結,以加快填滿率。 Direct3D 11.2 也包含使用硬體重疊轉譯的 API,讓您可以在原生解析度的另一個交換鏈結中呈現 UI。 這使您的遊戲能夠以全原生解析度繪製 UI,同時保持高畫面播放速率,進而充分利用行動裝置和高 DPI 顯示器 (例如 3840 x 2160)。 本文說明如何使用重疊的交換鏈結。

Direct3D 11.2 也引進了一項新功能,可透過翻轉模型交換鏈結來降低延遲。 請參閱透過 DXGI 1.3 交換鏈結減少延遲

使用交換鏈結調整

當您的遊戲在低階硬體 (或針對節能而最佳化的硬體) 上執行時,以低於顯示器本身能力的解析度轉譯即時遊戲內容可能會很有幫助。 若要這樣做,用於轉譯遊戲內容的交換鏈結必須小於原生解析度,或必須使用交換鏈結的子區域。

  1. 首先,在完整原生解析度建立交換鏈結。

    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 UWP apps must use this SwapEffect.
    swapChainDesc.Flags = 0;
    swapChainDesc.Scaling = DXGI_SCALING_STRETCH;
    
    // This sequence obtains the DXGI factory that was used to create the Direct3D device above.
    ComPtr<IDXGIDevice3> dxgiDevice;
    DX::ThrowIfFailed(
        m_d3dDevice.As(&dxgiDevice)
        );
    
    ComPtr<IDXGIAdapter> dxgiAdapter;
    DX::ThrowIfFailed(
        dxgiDevice->GetAdapter(&dxgiAdapter)
        );
    
    ComPtr<IDXGIFactory2> dxgiFactory;
    DX::ThrowIfFailed(
        dxgiAdapter->GetParent(IID_PPV_ARGS(&dxgiFactory))
        );
    
    ComPtr<IDXGISwapChain1> swapChain;
    DX::ThrowIfFailed(
        dxgiFactory->CreateSwapChainForCoreWindow(
            m_d3dDevice.Get(),
            reinterpret_cast<IUnknown*>(m_window.Get()),
            &swapChainDesc,
            nullptr,
            &swapChain
            )
        );
    
    DX::ThrowIfFailed(
        swapChain.As(&m_swapChain)
        );
    
  2. 然後,透過將來源大小設定為降低的解析度來選擇交換鏈結的一個子區域以進行放大。

    DX 前景交換鏈結範例會根據百分比計算減少的大小:

    m_d3dRenderSizePercentage = percentage;
    
    UINT renderWidth = static_cast<UINT>(m_d3dRenderTargetSize.Width * percentage + 0.5f);
    UINT renderHeight = static_cast<UINT>(m_d3dRenderTargetSize.Height * percentage + 0.5f);
    
    // Change the region of the swap chain that will be presented to the screen.
    DX::ThrowIfFailed(
        m_swapChain->SetSourceSize(
            renderWidth,
            renderHeight
            )
        );
    
  3. 建立檢視區以符合交換鏈結的子區域。

    // In Direct3D, change the Viewport to match the region of the swap
    // chain that will now be presented from.
    m_screenViewport = CD3D11_VIEWPORT(
        0.0f,
        0.0f,
        static_cast<float>(renderWidth),
        static_cast<float>(renderHeight)
        );
    
    m_d3dContext->RSSetViewports(1, &m_screenViewport);
    
  4. 如果使用 Direct2D,則必須調整旋轉轉換以補償來源區域。

建立 UI 元素的硬體重疊交換鏈結

使用交換鏈結調整時,存在一個固有的缺點,即 UI 也會縮小,可能會使其模糊且難以使用。 在硬體支援重疊交換鏈結的裝置上,透過在與即時遊戲內容分離的交換鏈結中,以本機解析度轉譯 UI 可以完全緩解此問題。 請注意,此技術僅適用於 CoreWindow 交換鏈結 - 它不能與 XAML Interop 一起使用。

使用下列步驟來建立使用硬體重疊功能的前景交換鏈結。 這些步驟是在如上所述首先為即時遊戲內容建立交換鏈結之後執行的。

  1. 首先,判斷 DXGI 介面卡是否支援重疊。 從交換鏈結取得 DXGI 輸出介面卡:

    ComPtr<IDXGIAdapter> outputDxgiAdapter;
    DX::ThrowIfFailed(
        dxgiFactory->EnumAdapters(0, &outputDxgiAdapter)
        );
    
    ComPtr<IDXGIOutput> dxgiOutput;
    DX::ThrowIfFailed(
        outputDxgiAdapter->EnumOutputs(0, &dxgiOutput)
        );
    
    ComPtr<IDXGIOutput2> dxgiOutput2;
    DX::ThrowIfFailed(
        dxgiOutput.As(&dxgiOutput2)
        );
    

    如果輸出介面卡為 SupportsOverlays 傳回 True,則 DXGI 介面卡支援重建。

    m_overlaySupportExists = dxgiOutput2->SupportsOverlays() ? true : false;
    

    注意:如果 DXGI 介面卡支援重疊,請繼續進行下一個步驟。 如果裝置不支援重疊,使用多個交換鏈結進行轉譯將不會有效率。 相反地,在與即時遊戲內容相同的交換鏈結中,以降低解析度轉譯 UI。

     

  2. 使用 IDXGIFactory2::CreateSwapChainForCoreWindow 建立前景交換鏈結。 必須在提供給 pDesc 參數的 DXGI_SWAP_CHAIN_DESC1 中設定以下選項:

     foregroundSwapChainDesc.Flags = DXGI_SWAP_CHAIN_FLAG_FOREGROUND_LAYER;
     foregroundSwapChainDesc.Scaling = DXGI_SCALING_NONE;
     foregroundSwapChainDesc.AlphaMode = DXGI_ALPHA_MODE_PREMULTIPLIED; // Foreground swap chain alpha values must be premultiplied.
    

    注意:每次調整交換鏈結大小時再次設定 DXGI_SWAP_CHAIN_FLAG_FOREGROUND_LAYER

    HRESULT hr = m_foregroundSwapChain->ResizeBuffers(
        2, // Double-buffered swap chain.
        static_cast<UINT>(m_d3dRenderTargetSize.Width),
        static_cast<UINT>(m_d3dRenderTargetSize.Height),
        DXGI_FORMAT_B8G8R8A8_UNORM,
        DXGI_SWAP_CHAIN_FLAG_FOREGROUND_LAYER // The FOREGROUND_LAYER flag cannot be removed with ResizeBuffers.
        );
    
  3. 使用兩個交換鏈結時,請將最大畫面延遲增加到 2,讓 DXGI 介面卡有時間同時呈現這兩個交換鏈結 (在相同的 VSync 間隔內)。

    // Create a render target view of the foreground swap chain's back buffer.
    if (m_foregroundSwapChain)
    {
        ComPtr<ID3D11Texture2D> foregroundBackBuffer;
        DX::ThrowIfFailed(
            m_foregroundSwapChain->GetBuffer(0, IID_PPV_ARGS(&foregroundBackBuffer))
            );
    
        DX::ThrowIfFailed(
            m_d3dDevice->CreateRenderTargetView(
                foregroundBackBuffer.Get(),
                nullptr,
                &m_d3dForegroundRenderTargetView
                )
            );
    }
    
  4. 前景交換鏈結一律使用預乘Alpha。 每個像素的色彩值應該都會在顯示畫面之前,先乘以 Alpha 值。 例如,100% 白色 BGRA 像素設定為 50% Alpha (0.5、0.5、0.5、0.5、0.5)。

    Alpha 預乘步驟可以在輸出合併階段,透過應用程式混合狀態 (請參閱 ID3D11BlendState) 並將 D3D11_RENDER_TARGET_BLEND_DESC 結構的 SrcBlend 欄位設定為 D3D11_SRC_ALPHA 來完成。 也可以使用具有預乘 alpha 值的資源。

    如果未完成 alpha 預乘步驟,前景交換鏈結上的色彩將比預期更亮。

  5. 根據是否建立了前景交換鏈結,UI 元素的 Direct2D 繪圖表面可能需要與前景交換鏈結關聯。

    建立轉譯目標檢視:

    // Create a render target view of the foreground swap chain's back buffer.
    if (m_foregroundSwapChain)
    {
        ComPtr<ID3D11Texture2D> foregroundBackBuffer;
        DX::ThrowIfFailed(
            m_foregroundSwapChain->GetBuffer(0, IID_PPV_ARGS(&foregroundBackBuffer))
            );
    
        DX::ThrowIfFailed(
            m_d3dDevice->CreateRenderTargetView(
                foregroundBackBuffer.Get(),
                nullptr,
                &m_d3dForegroundRenderTargetView
                )
            );
    }
    

    建立 Direct2D 繪圖介面:

    if (m_foregroundSwapChain)
    {
        // Create a Direct2D target bitmap for the foreground swap chain.
        ComPtr<IDXGISurface2> dxgiForegroundSwapChainBackBuffer;
        DX::ThrowIfFailed(
            m_foregroundSwapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiForegroundSwapChainBackBuffer))
            );
    
        DX::ThrowIfFailed(
            m_d2dContext->CreateBitmapFromDxgiSurface(
                dxgiForegroundSwapChainBackBuffer.Get(),
                &bitmapProperties,
                &m_d2dTargetBitmap
                )
            );
    }
    else
    {
        // Create a Direct2D target bitmap for the swap chain.
        ComPtr<IDXGISurface2> dxgiSwapChainBackBuffer;
        DX::ThrowIfFailed(
            m_swapChain->GetBuffer(0, IID_PPV_ARGS(&dxgiSwapChainBackBuffer))
            );
    
        DX::ThrowIfFailed(
            m_d2dContext->CreateBitmapFromDxgiSurface(
                dxgiSwapChainBackBuffer.Get(),
                &bitmapProperties,
                &m_d2dTargetBitmap
                )
            );
    }
    
    m_d2dContext->SetTarget(m_d2dTargetBitmap.Get());
    
    // Create a render target view of the swap chain's back buffer.
    ComPtr<ID3D11Texture2D> backBuffer;
    DX::ThrowIfFailed(
        m_swapChain->GetBuffer(0, IID_PPV_ARGS(&backBuffer))
        );
    
    DX::ThrowIfFailed(
        m_d3dDevice->CreateRenderTargetView(
            backBuffer.Get(),
            nullptr,
            &m_d3dRenderTargetView
            )
        );
    
  6. 將前景交換鏈結與用於即時遊戲內容的縮放交換鏈結一起呈現。 由於兩個交換鏈結的畫面延遲設定為 2,DXGI 可以在相同的 VSync 間隔內呈現兩者。

    // Present the contents of the swap chain to the screen.
    void DX::DeviceResources::Present()
    {
        // The first argument instructs DXGI to block until VSync, putting the application
        // to sleep until the next VSync. This ensures that we don't waste any cycles rendering
        // frames that will never be displayed to the screen.
        HRESULT hr = m_swapChain->Present(1, 0);
    
        if (SUCCEEDED(hr) && m_foregroundSwapChain)
        {
            m_foregroundSwapChain->Present(1, 0);
        }
    
        // Discard the contents of the render targets.
        // This is a valid operation only when the existing contents will be entirely
        // overwritten. If dirty or scroll rects are used, this call should be removed.
        m_d3dContext->DiscardView(m_d3dRenderTargetView.Get());
        if (m_foregroundSwapChain)
        {
            m_d3dContext->DiscardView(m_d3dForegroundRenderTargetView.Get());
        }
    
        // Discard the contents of the depth stencil.
        m_d3dContext->DiscardView(m_d3dDepthStencilView.Get());
    
        // If the device was removed either by a disconnection or a driver upgrade, we
        // must recreate all device resources.
        if (hr == DXGI_ERROR_DEVICE_REMOVED)
        {
            HandleDeviceLost();
        }
        else
        {
            DX::ThrowIfFailed(hr);
        }
    }