스왑 체인 확장 및 오버레이

모바일 장치에서 더 빠른 렌더링을 위해 확장된 스왑 체인을 생성하고 오버레이 스왑 체인(사용 가능한 경우)을 사용하여 시각적 품질을 높이는 방법을 알아봅니다.

DirectX 11.2의 스왑 체인

Direct3D 11.2를 사용하면 기본이 아닌 (감소된) 해상도에서 확장된 스왑 체인이 있는 UWP(유니버설 Windows 플랫폼) 앱을 생성할 수 있으므로 채우기 속도가 더 빨라집니다. 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. 두 스왑 체인을 사용하는 경우 DXGI 어댑터에서 두 스왑 체인을 동시에 표시할 시간을 가지도록 최대 프레임 대기 시간을 2로 늘립니다(동일한 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. 포그라운드 스왑 체인은 미리 곱한 알파를 항상 사용합니다. 각 픽셀의 색상 값은 프레임이 표시되기 전에 알파 값을 곱해야 합니다. 예를 들어, 50% 알파의 100% 흰색 BGRA 픽셀은 (0.5, 0.5, 0.5, 0.5)로 설정됩니다.

    알파 미리 곱하기 단계는 D3D11_RENDER_TARGET_BLEND_DESC 구조의 SrcBlend 필드가 D3D11_SRC_ALPHA로 설정된 앱 혼합 상태(ID3D11BlendState 참조)를 적용하여 출력 병합 단계에서 수행할 수 있습니다. 미리 곱한 알파 값이 있는 자산도 사용할 수 있습니다.

    알파 사전 곱하기 단계가 수행되지 않을 경우, 포그라운드 스왑 체인의 색이 예상보다 밝아집니다.

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