Freigeben über


Swapchainskalierung und Überlagerungen

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.

Swapchains in DirectX 11.2

Mit Direct3D 11.2 können Sie Universelle Windows-Plattform (UWP)-Apps mit Swapchains erstellen, die von nicht nativen (reduzierten) Auflösungen skaliert werden und schnellere Füllraten ermöglichen. Direct3D 11.2 enthält auch APIs zum Rendern mit Hardwareüberlagerungen, sodass Sie eine Benutzeroberfläche in einer anderen Swapchain mit systemeigener Auflösung präsentieren können. Auf diese Weise kann Ihr Spiel die Benutzeroberfläche mit vollständiger systemeigener Auflösung zeichnen und gleichzeitig eine hohe Framerate beibehalten, wodurch mobile Geräte und Bildschirme mit hohem DPI-Wert optimal genutzt werden können (z. B. 3840 bis 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 Flipmodell-Swapchains ein. Siehe Reduzieren der Latenz mit DXGI 1.3-Swapchains.

Verwenden der Swapchainskalierung

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

  1. Erstellen Sie zunächst eine Swapchain mit vollständiger systemeigener 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 die Skalierung durch Festlegen der Quellgröße auf eine reduzierte Auflösung festzulegen.

    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überlagerungs-Swapchain für UI-Elemente

Bei der Verwendung der Swapchainskalierung gibt es einen inhärenten Nachteil, dass die Benutzeroberfläche auch herunterskaliert wird, wodurch sie möglicherweise verschwommen und schwieriger zu verwenden 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-Interoperabilität verwendet werden.

Führen Sie die folgenden Schritte aus, um eine Vordergrund-Swapchain zu erstellen, die Hardwareüberlagerungsfunktion 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" für SupportsOverlays zurückgibt.

    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 für den pDesc-Parameter bereitgestellt werden:

     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.
    

    Beachten Sie , dass die DXGI_SWAP_CHAIN_FLAG_FOREGROUND_LAYER bei jeder Größenänderung der Swapchain erneut festgelegt 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 weißes BGRA-Pixel mit 50 % Alpha auf (0,5, 0,5, 0,5, 0,5, 0,5) festgelegt.

    Der Alphaprämultiplizierungsschritt kann in der Ausgabezusammenführungsphase erfolgen, indem ein App-Blend-Zustand (siehe ID3D11BlendState) angewendet wird, wobei das SrcBlend-Feld der D3D11_RENDER_TARGET_BLEND_DESC Struktur auf D3D11_SRC_ALPHA festgelegt ist. Objekte mit vorab multiplizierten Alphawerten können ebenfalls verwendet werden.

    Wenn der Alphaprämultiplizierungsschritt nicht durchgeführt wird, sind farben in der Vordergrund-Swapchain 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 Renderzielansichten:

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