Swap-Chain-Skalierung und Overlays

Erfahren Sie, wie Sie skalierte Swapchains für ein schnelleres Rendering auf mobilen Geräten erstellen und Überlagerungs-Swapchains (sofern verfügbar) verwenden, um die visuelle Qualität zu erhöhen.

Swap-Ketten in DirectX 11.2

Mit Direct3D 11.2 können Sie Apps für die Universelle Windows-Plattform (UWP) mit Swap-Chains erstellen, die aus nicht-nativen (reduzierten) Auflösungen hochskaliert werden, was schnellere Füllraten ermöglicht. Direct3D 11.2 enthält auch APIs zum Rendern mit Hardwareüberlagerungen, sodass Sie eine Benutzeroberfläche in einer anderen Swapkette in nativer Auflösung präsentieren können. Auf diese Weise kann Ihr Spiel die Benutzeroberfläche mit voller nativer Auflösung darstellen und gleichzeitig eine hohe Framerate beibehalten, wodurch mobile Geräte und Bildschirme mit hoher DPI optimal genutzt werden können (z. B. 3840 mal 2160). In diesem Artikel wird erläutert, wie überlappende Swapchains verwendet werden.

Direct3D 11.2 führt auch ein neues Feature für reduzierte Latenz mit Flip-Modell-Umtauschringe ein. Siehe Latenzreduzierung mit DXGI 1.3-Swapchains.

Verwenden der Swapchainskalierung

Wenn Ihr Spiel auf älterer Hardware ausgeführt wird – oder auf für Energieeinsparungen optimierter Hardware – kann es von Vorteil sein, Inhalte in Echtzeit mit einer niedrigeren Auflösung zu rendern, als das Display nativ fähig ist. Dazu muss die Swapchain, die zum Rendern von Spielinhalten verwendet wird, kleiner als die standardmäßige Auflösung sein, oder es muss ein Unterbereich der Swapchain verwendet werden.

  1. Erstellen Sie zunächst eine Swapchain mit vollständiger nativer Auflösung.

    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. Wählen Sie dann einen Unterbereich der Swapchain aus, um durch Festlegen der Quellgröße auf eine reduzierte Auflösung die Skalierung hochzusetzen.

    Im Beispiel für DX-Vordergrund-Swapchains wird eine reduzierte Größe basierend auf einem Prozentsatz berechnet:

    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. Erstellen Sie einen Viewport, der mit dem Unterbereich der Swapchain übereinstimmt.

    // 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. Wenn Direct2D verwendet wird, muss die Drehungstransformation angepasst werden, um den Quellbereich auszugleichen.

Erstellen einer Hardware-Overlay-Swapchain für UI-Elemente

Bei der Skalierung der Swapchain gibt es den Nachteil, dass die Benutzeroberfläche ebenfalls verkleinert wird, wodurch sie möglicherweise verschwommen und schwieriger zu bedienen ist. Auf Geräten mit Hardwareunterstützung für Overlay-Swapchains wird dieses Problem vollständig gemindert, indem die Benutzeroberfläche in einer nativen Auflösung in einer Swapchain gerendert wird, die vom Echtzeitinhalt des Spiels getrennt ist. Beachten Sie, dass diese Technik nur für CoreWindow Swapchains gilt – sie kann nicht mit XAML-Interop verwendet werden.

Führen Sie die folgenden Schritte aus, um eine Swap Chain im Vordergrund zu erstellen, die die Hardware-Overlay-Funktion verwendet. Diese Schritte werden nach dem ersten Erstellen einer Swapchain für Echtzeit-Spielinhalte ausgeführt, wie oben beschrieben.

  1. Ermitteln Sie zunächst, ob der DXGI-Adapter Überlagerungen unterstützt. Rufen Sie den DXGI-Ausgabeadapter aus der Swapchain ab:

    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)
        );
    

    Der DXGI-Adapter unterstützt Überlagerungen, wenn der Ausgabeadapter 'True' zurückgibt für SupportsOverlays.

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

    Hinweis Wenn der DXGI-Adapter Überlagerungen unterstützt, fahren Sie mit dem nächsten Schritt fort. Wenn das Gerät Überlagerungen nicht unterstützt, ist das Rendern mit mehreren Swapchains nicht effizient. Rendern Sie stattdessen die Benutzeroberfläche mit reduzierter Auflösung in derselben Swapchain wie Echtzeit-Spielinhalte.

     

  2. Erstellen Sie die Vordergrund-Swapchain mit IDXGIFactory2::CreateSwapChainForCoreWindow. Die folgenden Optionen müssen in der DXGI_SWAP_CHAIN_DESC1 festgelegt werden, die dem pDesc Parameter bereitgestellt wird:

     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.
    

    Hinweis Setzen Sie die DXGI_SWAP_CHAIN_FLAG_FOREGROUND_LAYER jedes Mal, wenn die Swap Chain in der Größe geändert wird.

    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. Wenn zwei Swapchains verwendet werden, erhöhen Sie die maximale Framelatenz auf 2, sodass der DXGI-Adapter Zeit hat, beide Swapchains gleichzeitig darzustellen (innerhalb desselben VSync-Intervalls).

    // 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. Vordergrund-Swapchains verwenden immer prämultipliziertes Alpha. Es wird erwartet, dass die Farbwerte jedes Pixels bereits mit dem Alphawert multipliziert werden, bevor der Frame angezeigt wird. Beispielsweise wird ein 100% weißes BGRA-Pixel bei 50% Alpha auf (0,5, 0,5, 0,5, 0,5) festgelegt.

    Der Alphaprämultiplizierungsschritt kann in der Ausgabezusammenführungsphase durchgeführt werden, indem ein App-Blend-Zustand angewendet wird (siehe ID3D11BlendState), wobei die D3D11_RENDER_TARGET_BLEND_DESC Struktur SrcBlend Feld auf D3D11_SRC_ALPHAfestgelegt ist. Objekte mit vorabmultiplizierten Alphawerten können ebenfalls verwendet werden.

    Wenn der Alpha-Prämultiplikationsschritt nicht durchgeführt wird, sind die Farben in der Vordergrund-Swap-Chain heller als erwartet.

  5. Je nachdem, ob die Vordergrund-Swapchain erstellt wurde, muss die Direct2D-Zeichnungsoberfläche für UI-Elemente möglicherweise der Vordergrund-Swapchain zugeordnet sein.

    Erstellen von Render-Zielansichten:

    // 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
                )
            );
    }
    

    Erstellen der Direct2D-Zeichnungsoberfläche:

    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. Präsentieren Sie die Vordergrund-Swapchain zusammen mit der skalierten Swapchain, die für Echtzeit-Spielinhalte verwendet wird. Da die Framelatenz für beide Swapchains auf 2 festgelegt wurde, kann DXGI beide innerhalb desselben VSync-Intervalls darstellen.

    // 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);
        }
    }